Skip to content

Instantly share code, notes, and snippets.

@Cadiboo
Last active May 20, 2019 02:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Cadiboo/753607e41ca4e2ca9e0ce3b928bab5ef to your computer and use it in GitHub Desktop.
Save Cadiboo/753607e41ca4e2ca9e0ce3b928bab5ef to your computer and use it in GitHub Desktop.
Cache Rendering Blocks in RenderWorldLastEvent
package io.github.cadiboo.testing.client;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.renderer.vertex.VertexFormatElement;
import net.minecraft.entity.Entity;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import static io.github.cadiboo.nocubes.util.ModReference.MOD_ID;
import static net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent;
import static net.minecraftforge.fml.relauncher.Side.CLIENT;
/**
* Subscribe to events that should be handled on the PHYSICAL CLIENT in this class
*
* @author Cadiboo
*/
@Mod.EventBusSubscriber(modid = MOD_ID, value = CLIENT)
public final class ClientEventSubscriber {
// These buffers are large enough for an entire chunk, consider using smaller buffers
private static final RegionRenderCacheBuilder bufferCache = new RegionRenderCacheBuilder();
private static final boolean[] usedBlockRenderLayers = new boolean[BlockRenderLayer.values().length];
private static final boolean[] startedBufferBuilders = new boolean[BlockRenderLayer.values().length];
@SubscribeEvent
public static void onClientTickEvent(final ClientTickEvent event) {
if (event.phase != TickEvent.Phase.END) {
return;
}
final Minecraft minecraft = Minecraft.getMinecraft();
if (minecraft.world == null) {
return;
}
// only update once every 5 seconds, to show the caching
if (minecraft.world.getTotalWorldTime() % 100 != 0) {
return;
}
if (minecraft.player == null) {
return;
}
// Reset values
Arrays.fill(usedBlockRenderLayers, false);
Arrays.fill(startedBufferBuilders, false);
final IBlockAccess blockAccess = minecraft.world;
final BlockRendererDispatcher blockRendererDispatcher = minecraft.getBlockRendererDispatcher();
final BlockPos startPos = minecraft.player.getPosition().add(-15, -15, -15);
final BlockPos endPos = minecraft.player.getPosition().add(15, -1, 15);
for (BlockPos pos : BlockPos.getAllInBoxMutable(startPos, endPos)) {
final IBlockState state = blockAccess.getBlockState(pos);
for (BlockRenderLayer blockRenderLayer : BlockRenderLayer.values()) {
if (!state.getBlock().canRenderInLayer(state, blockRenderLayer)) {
continue;
}
ForgeHooksClient.setRenderLayer(blockRenderLayer);
final int blockRenderLayerId = blockRenderLayer.ordinal();
final BufferBuilder bufferBuilder = bufferCache.getWorldRendererByLayerId(blockRenderLayerId);
if (!startedBufferBuilders[blockRenderLayerId]) {
startedBufferBuilders[blockRenderLayerId] = true;
// Copied from RenderChunk
{
bufferBuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.BLOCK);
}
}
// OptiFine Shaders compatibility
// if (Config.isShaders()) SVertexBuilder.pushEntity(state, pos, blockAccess, bufferBuilder);
usedBlockRenderLayers[blockRenderLayerId] |= blockRendererDispatcher.renderBlock(state, pos, blockAccess, bufferBuilder);
// if (Config.isShaders()) SVertexBuilder.popEntity(bufferBuilder);
}
ForgeHooksClient.setRenderLayer(null);
}
// finishDrawing
for (int blockRenderLayerId = 0; blockRenderLayerId < usedBlockRenderLayers.length; blockRenderLayerId++) {
if (!startedBufferBuilders[blockRenderLayerId]) {
continue;
}
bufferCache.getWorldRendererByLayerId(blockRenderLayerId).finishDrawing();
}
}
@SubscribeEvent
public static void onRenderWorldLastEvent(final RenderWorldLastEvent event) {
// Code to draw the buffer
{
final Entity entity = Minecraft.getMinecraft().getRenderViewEntity();
if (entity == null) {
return;
}
final float partialTicks = event.getPartialTicks();
// Copied from EntityRenderer. This code can be found by looking at usages of Entity.prevPosX.
// It also appears in many other places throughout Minecraft's rendering
double renderPosX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * (double) partialTicks;
double renderPosY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * (double) partialTicks;
double renderPosZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * (double) partialTicks;
for (int blockRenderLayerId = 0; blockRenderLayerId < usedBlockRenderLayers.length; blockRenderLayerId++) {
if (!usedBlockRenderLayers[blockRenderLayerId]) {
continue;
}
final BufferBuilder bufferBuilder = bufferCache.getWorldRendererByLayerId(blockRenderLayerId);
GlStateManager.pushMatrix();
//Render everything 1 block higher as an example
GlStateManager.translate(-renderPosX, -renderPosY + 1, -renderPosZ);
drawBuffer(bufferBuilder);
GlStateManager.popMatrix();
}
}
}
// Coppied from the Tesselator's vboUploader - Draw everything but don't reset the buffer
private static void drawBuffer(final BufferBuilder bufferBuilder) {
if (bufferBuilder.getVertexCount() > 0) {
VertexFormat vertexformat = bufferBuilder.getVertexFormat();
int i = vertexformat.getSize();
ByteBuffer bytebuffer = bufferBuilder.getByteBuffer();
List<VertexFormatElement> list = vertexformat.getElements();
for (int j = 0; j < list.size(); ++j) {
VertexFormatElement vertexformatelement = list.get(j);
VertexFormatElement.EnumUsage vertexformatelement$enumusage = vertexformatelement.getUsage();
int k = vertexformatelement.getType().getGlConstant();
int l = vertexformatelement.getIndex();
bytebuffer.position(vertexformat.getOffset(j));
// moved to VertexFormatElement.preDraw
vertexformatelement.getUsage().preDraw(vertexformat, j, i, bytebuffer);
}
GlStateManager.glDrawArrays(bufferBuilder.getDrawMode(), 0, bufferBuilder.getVertexCount());
int i1 = 0;
for (int j1 = list.size(); i1 < j1; ++i1) {
VertexFormatElement vertexformatelement1 = list.get(i1);
VertexFormatElement.EnumUsage vertexformatelement$enumusage1 = vertexformatelement1.getUsage();
int k1 = vertexformatelement1.getIndex();
// moved to VertexFormatElement.postDraw
vertexformatelement1.getUsage().postDraw(vertexformat, i1, i, bytebuffer);
}
}
}
}
package io.github.cadiboo.testing.client;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.renderer.BlockRendererDispatcher;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RegionRenderCacheBuilder;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.renderer.vertex.VertexFormatElement;
import net.minecraft.entity.Entity;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.math.BlockPos;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import static io.github.cadiboo.nocubes.NoCubes.MOD_ID;
import static net.minecraftforge.api.distmarker.Dist.CLIENT;
import static net.minecraftforge.fml.common.gameevent.TickEvent.ClientTickEvent;
/**
* Subscribe to events that should be handled on the PHYSICAL CLIENT in this class
*
* @author Cadiboo
*/
@Mod.EventBusSubscriber(modid = MOD_ID, value = CLIENT)
public final class ClientEventSubscriber {
// These buffers are large enough for an entire chunk, consider using smaller buffers
private static final RegionRenderCacheBuilder bufferCache = new RegionRenderCacheBuilder();
private static final boolean[] usedBlockRenderLayers = new boolean[BlockRenderLayer.values().length];
@SubscribeEvent
public static void onClientTickEvent(final ClientTickEvent event) {
if (event.phase != TickEvent.Phase.END) {
return;
}
final Minecraft minecraft = Minecraft.getInstance();
final WorldClient world = minecraft.world;
if (world == null) {
return;
}
// only update once every 5 seconds, to show the caching
if (world.getGameTime() % 100 != 0) {
return;
}
final EntityPlayerSP player = minecraft.player;
if (player == null) {
return;
}
// Reset values
Arrays.fill(usedBlockRenderLayers, false);
final boolean[] startedBufferBuilders = new boolean[BlockRenderLayer.values().length];
final BlockRendererDispatcher blockRendererDispatcher = minecraft.getBlockRendererDispatcher();
final BlockPos startPos = player.getPosition().add(-15, -15, -15);
final BlockPos endPos = player.getPosition().add(15, -1, 15);
final Random random = new Random();
for (BlockPos pos : BlockPos.getAllInBoxMutable(startPos, endPos)) {
final IBlockState state = world.getBlockState(pos);
for (BlockRenderLayer blockRenderLayer : BlockRenderLayer.values()) {
if (!state.getBlock().canRenderInLayer(state, blockRenderLayer)) {
continue;
}
ForgeHooksClient.setRenderLayer(blockRenderLayer);
final int blockRenderLayerId = blockRenderLayer.ordinal();
final BufferBuilder bufferBuilder = bufferCache.getBuilder(blockRenderLayerId);
if (!startedBufferBuilders[blockRenderLayerId]) {
startedBufferBuilders[blockRenderLayerId] = true;
// Copied from RenderChunk
{
bufferBuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.BLOCK);
}
}
// OptiFine Shaders compatibility
// if (Config.isShaders()) SVertexBuilder.pushEntity(state, pos, reader, bufferBuilder);
usedBlockRenderLayers[blockRenderLayerId] |= blockRendererDispatcher.renderBlock(state, pos, world, bufferBuilder, random);
// if (Config.isShaders()) SVertexBuilder.popEntity(bufferBuilder);
}
ForgeHooksClient.setRenderLayer(null);
}
// finishDrawing
for (int blockRenderLayerId = 0; blockRenderLayerId < usedBlockRenderLayers.length; blockRenderLayerId++) {
if (!startedBufferBuilders[blockRenderLayerId]) {
continue;
}
bufferCache.getBuilder(blockRenderLayerId).finishDrawing();
}
}
@SubscribeEvent
public static void onRenderWorldLastEvent(final RenderWorldLastEvent event) {
// Code to draw the buffer
{
final Entity entity = Minecraft.getInstance().getRenderViewEntity();
if (entity == null) {
return;
}
final float partialTicks = event.getPartialTicks();
// Copied from EntityRenderer. This code can be found by looking at usages of Entity.prevPosX.
// It also appears in many other places throughout Minecraft's rendering
double renderPosX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * (double) partialTicks;
double renderPosY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * (double) partialTicks;
double renderPosZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * (double) partialTicks;
for (int blockRenderLayerId = 0; blockRenderLayerId < usedBlockRenderLayers.length; blockRenderLayerId++) {
if (!usedBlockRenderLayers[blockRenderLayerId]) {
continue;
}
final BufferBuilder bufferBuilder = bufferCache.getBuilder(blockRenderLayerId);
GlStateManager.pushMatrix();
// Render everything 1 block higher as an example
// You can add blockpos coords to -renderPos to render at a specific block
GlStateManager.translated(-renderPosX, -renderPosY + 1, -renderPosZ);
drawBuffer(bufferBuilder);
GlStateManager.popMatrix();
}
}
}
// Coppied from the Tesselator's vboUploader - Draw everything but don't reset the buffer
// Unused code from vanilla is commented out, along with the code that resets the buffer
private static void drawBuffer(final BufferBuilder bufferBuilder) {
if (bufferBuilder.getVertexCount() > 0) {
VertexFormat vertexformat = bufferBuilder.getVertexFormat();
int i = vertexformat.getSize();
ByteBuffer bytebuffer = bufferBuilder.getByteBuffer();
List<VertexFormatElement> list = vertexformat.getElements();
for (int j = 0; j < list.size(); ++j) {
VertexFormatElement vertexformatelement = list.get(j);
// VertexFormatElement.EnumUsage vertexformatelement$enumusage = vertexformatelement.getUsage();
// int k = vertexformatelement.getType().getGlConstant();
// int l = vertexformatelement.getIndex();
bytebuffer.position(vertexformat.getOffset(j));
// moved to VertexFormatElement.preDraw
vertexformatelement.getUsage().preDraw(vertexformat, j, i, bytebuffer);
}
GlStateManager.drawArrays(bufferBuilder.getDrawMode(), 0, bufferBuilder.getVertexCount());
int i1 = 0;
for (int j1 = list.size(); i1 < j1; ++i1) {
VertexFormatElement vertexformatelement1 = list.get(i1);
// VertexFormatElement.EnumUsage vertexformatelement$enumusage1 = vertexformatelement1.getUsage();
// int k1 = vertexformatelement1.getIndex();
// moved to VertexFormatElement.postDraw
vertexformatelement1.getUsage().postDraw(vertexformat, i1, i, bytebuffer);
}
}
// bufferBuilder.reset(); // Commented out from the tesselator's vboUploader. Don't reset the buffer
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment