on our way to super fancy contraption lighting

This commit is contained in:
JozsefA 2021-01-17 22:18:31 -08:00
parent 0cb18c532d
commit 22a90c8e5d
24 changed files with 810 additions and 394 deletions

View File

@ -16,7 +16,8 @@ import com.simibubi.create.foundation.block.render.CustomBlockModels;
import com.simibubi.create.foundation.block.render.SpriteShifter;
import com.simibubi.create.foundation.item.CustomItemModels;
import com.simibubi.create.foundation.item.CustomRenderedItems;
import com.simibubi.create.foundation.render.FastContraptionRenderer;
import com.simibubi.create.foundation.render.ContraptionRenderDispatcher;
import com.simibubi.create.foundation.render.RenderedContraption;
import com.simibubi.create.foundation.render.FastKineticRenderer;
import com.simibubi.create.foundation.render.SuperByteBufferCache;
import com.simibubi.create.foundation.utility.outliner.Outliner;
@ -182,6 +183,6 @@ public class CreateClient {
public static void invalidateRenderers() {
CreateClient.bufferCache.invalidate();
CreateClient.kineticRenderer.invalidate();
FastContraptionRenderer.invalidateAll();
ContraptionRenderDispatcher.invalidateAll();
}
}

View File

@ -2,7 +2,9 @@ package com.simibubi.create.content.contraptions.components.structureMovement;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.simibubi.create.foundation.render.FastContraptionRenderer;
import com.simibubi.create.foundation.render.ContraptionRenderDispatcher;
import com.simibubi.create.foundation.render.RenderedContraption;
import net.java.games.input.Controller;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.culling.ClippingHelperImpl;
@ -50,7 +52,7 @@ public abstract class AbstractContraptionEntityRenderer<C extends AbstractContra
Contraption contraption = entity.getContraption();
if (contraption != null) {
ContraptionRenderer.renderDynamic(entity.world, contraption, ms, msLocal, buffers);
FastContraptionRenderer.markForRendering(entity.world, contraption, msLocal);
ContraptionRenderDispatcher.markForRendering(entity.world, contraption, msLocal);
}
ms.pop();

View File

@ -18,6 +18,10 @@ import java.util.stream.Collectors;
import javax.annotation.Nullable;
import com.simibubi.create.foundation.render.light.ContraptionLighter;
import com.simibubi.create.foundation.render.light.EmptyLighter;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
@ -956,4 +960,9 @@ public abstract class Contraption {
mountedFluidStorage.updateFluid(containedFluid);
}
@OnlyIn(Dist.CLIENT)
public ContraptionLighter<?> makeLighter() {
return new EmptyLighter(this);
}
}

View File

@ -84,7 +84,7 @@ public class ContraptionRenderer {
return new SuperByteBuffer(builder);
}
protected static BufferBuilder buildStructure(Contraption c, RenderType layer) {
public static BufferBuilder buildStructure(Contraption c, RenderType layer) {
if (renderWorld == null || renderWorld.getWorld() != Minecraft.getInstance().world)
renderWorld = new PlacementSimulationWorld(Minecraft.getInstance().world);

View File

@ -1,5 +1,8 @@
package com.simibubi.create.content.contraptions.components.structureMovement.bearing;
import com.simibubi.create.foundation.render.light.ContraptionLighter;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.commons.lang3.tuple.Pair;
import com.simibubi.create.AllTags.AllBlockTags;
@ -88,4 +91,9 @@ public class BearingContraption extends Contraption {
return axis == facing.getAxis();
}
@OnlyIn(Dist.CLIENT)
@Override
public ContraptionLighter<?> makeLighter() {
return new BearingLighter(this);
}
}

View File

@ -0,0 +1,19 @@
package com.simibubi.create.content.contraptions.components.structureMovement.bearing;
import com.simibubi.create.foundation.render.light.ContraptionLighter;
import com.simibubi.create.foundation.render.light.GridAlignedBB;
import net.minecraft.util.math.AxisAlignedBB;
public class BearingLighter extends ContraptionLighter<BearingContraption> {
public BearingLighter(BearingContraption contraption) {
super(contraption);
}
@Override
public GridAlignedBB getContraptionBounds() {
GridAlignedBB localBounds = GridAlignedBB.fromAABB(contraption.bounds);
localBounds.translate(contraption.anchor);
return localBounds;
}
}

View File

@ -26,6 +26,7 @@ import com.simibubi.create.foundation.networking.AllPackets;
import com.simibubi.create.foundation.networking.LeftClickPacket;
import com.simibubi.create.foundation.render.FastRenderDispatcher;
import com.simibubi.create.foundation.render.RenderWork;
import com.simibubi.create.foundation.render.light.LightVolumeDebugger;
import com.simibubi.create.foundation.renderState.SuperRenderTypeBuffer;
import com.simibubi.create.foundation.tileEntity.behaviour.edgeInteraction.EdgeInteractionRenderer;
import com.simibubi.create.foundation.tileEntity.behaviour.filtering.FilteringRenderer;
@ -118,7 +119,6 @@ public class ClientEvents {
Vec3d cameraPos = Minecraft.getInstance().gameRenderer.getActiveRenderInfo().getProjectedView();
MatrixStack ms = event.getMatrixStack();
ActiveRenderInfo info = Minecraft.getInstance().gameRenderer.getActiveRenderInfo();
ms.push();
ms.translate(-cameraPos.getX(), -cameraPos.getY(), -cameraPos.getZ());
SuperRenderTypeBuffer buffer = SuperRenderTypeBuffer.getInstance();
@ -126,6 +126,7 @@ public class ClientEvents {
CouplingRenderer.renderAll(ms, buffer);
CreateClient.schematicHandler.render(ms, buffer);
CreateClient.outliner.renderOutlines(ms, buffer);
LightVolumeDebugger.render(ms, buffer);
// CollisionDebugger.render(ms, buffer);
buffer.draw();

View File

@ -1,149 +0,0 @@
package com.simibubi.create.foundation.render;
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
import org.lwjgl.opengl.*;
import java.nio.ByteBuffer;
import static com.simibubi.create.foundation.render.RenderMath.nextPowerOf2;
public class ContraptionLighter {
private int minX;
private int minY;
private int minZ;
private final int sizeX;
private final int sizeY;
private final int sizeZ;
private SafeDirectBuffer lightVolume;
private boolean dirty;
private int texture;
public ContraptionLighter(Contraption contraption) {
texture = GL11.glGenTextures();
AxisAlignedBB bounds = contraption.bounds;
int minX = (int) Math.floor(bounds.minX) - 1;
int minY = (int) Math.floor(bounds.minY) - 1;
int minZ = (int) Math.floor(bounds.minZ) - 1;
int maxX = (int) Math.ceil(bounds.maxX) + 1;
int maxY = (int) Math.ceil(bounds.maxY) + 1;
int maxZ = (int) Math.ceil(bounds.maxZ) + 1;
sizeX = nextPowerOf2(maxX - minX);
sizeY = nextPowerOf2(maxY - minY);
sizeZ = nextPowerOf2(maxZ - minZ);
lightVolume = new SafeDirectBuffer(sizeX * sizeY * sizeZ * 2);
update(contraption);
}
public int getSizeX() {
return sizeX;
}
public int getSizeY() {
return sizeY;
}
public int getSizeZ() {
return sizeZ;
}
public int getMinX() {
return minX;
}
public int getMinY() {
return minY;
}
public int getMinZ() {
return minZ;
}
public void delete() {
RenderWork.enqueue(() -> {
GL15.glDeleteTextures(texture);
texture = 0;
try {
lightVolume.close();
} catch (Exception e) {
e.printStackTrace();
}
lightVolume = null;
});
}
private void setupPosition(Contraption c) {
Vec3d positionVec = c.entity.getPositionVec();
minX = (int) (Math.floor(positionVec.x) - sizeX / 2);
minY = (int) (Math.floor(positionVec.y) - 1);
minZ = (int) (Math.floor(positionVec.z) - sizeZ / 2);
}
public void update(Contraption c) {
if (lightVolume == null) return;
setupPosition(c);
World world = c.entity.world;
BlockPos.Mutable pos = new BlockPos.Mutable();
for (int x = 0; x < sizeX; x++) {
for (int y = 0; y < sizeY; y++) {
for (int z = 0; z < sizeZ; z++) {
pos.setPos(minX + x, minY + y, minZ + z);
int blockLight = world.getLightLevel(LightType.BLOCK, pos);
int skyLight = world.getLightLevel(LightType.SKY, pos);
writeLight(x, y, z, blockLight, skyLight);
}
}
}
dirty = true;
}
private void writeLight(int x, int y, int z, int block, int sky) {
int i = (x + sizeX * (y + z * sizeY)) * 2;
byte b = (byte) ((block & 0xF) << 4);
byte s = (byte) ((sky & 0xF) << 4);
lightVolume.put(i, b);
lightVolume.put(i + 1, s);
}
public void use() {
if (texture == 0 || lightVolume == null) return;
GL12.glBindTexture(GL12.GL_TEXTURE_3D, texture);
GL11.glTexParameteri(GL13.GL_TEXTURE_3D, GL13.GL_TEXTURE_MIN_FILTER, GL13.GL_LINEAR);
GL11.glTexParameteri(GL13.GL_TEXTURE_3D, GL13.GL_TEXTURE_MAG_FILTER, GL13.GL_LINEAR);
GL11.glTexParameteri(GL13.GL_TEXTURE_3D, GL13.GL_TEXTURE_WRAP_S, GL13.GL_CLAMP);
GL11.glTexParameteri(GL13.GL_TEXTURE_3D, GL13.GL_TEXTURE_WRAP_R, GL13.GL_CLAMP);
GL11.glTexParameteri(GL13.GL_TEXTURE_3D, GL13.GL_TEXTURE_WRAP_T, GL13.GL_CLAMP);
if (dirty) {
GL12.glTexImage3D(GL12.GL_TEXTURE_3D, 0, GL40.GL_RG8, sizeX, sizeY, sizeZ, 0, GL40.GL_RG, GL40.GL_UNSIGNED_BYTE, lightVolume.getBacking());
dirty = false;
}
}
public void release() {
GL12.glBindTexture(GL12.GL_TEXTURE_3D, 0);
}
}

View File

@ -0,0 +1,111 @@
package com.simibubi.create.foundation.render;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
import com.simibubi.create.foundation.render.light.LightVolume;
import com.simibubi.create.foundation.render.shader.Shader;
import com.simibubi.create.foundation.render.shader.ShaderCallback;
import com.simibubi.create.foundation.render.shader.ShaderHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.SectionPos;
import net.minecraft.world.ILightReader;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL40;
import java.util.ArrayList;
import java.util.HashMap;
public class ContraptionRenderDispatcher {
public static final HashMap<Integer, RenderedContraption> renderers = new HashMap<>();
public static void markForRendering(World world, Contraption c, MatrixStack model) {
getRenderer(world, c).setRenderSettings(model.peek().getModel());
}
public static void notifyLightUpdate(ILightReader world, LightType type, SectionPos pos) {
for (RenderedContraption renderer : renderers.values()) {
renderer.getLighter().lightVolume.notifyLightUpdate(world, type, pos);
}
}
private static RenderedContraption getRenderer(World world, Contraption c) {
RenderedContraption renderer;
int entityId = c.entity.getEntityId();
if (renderers.containsKey(entityId)) {
renderer = renderers.get(entityId);
} else {
renderer = new RenderedContraption(world, c);
renderers.put(entityId, renderer);
}
return renderer;
}
public static void renderLayer(RenderType renderType, Matrix4f projectionMat, Matrix4f viewMat) {
removeDeadContraptions();
if (renderers.isEmpty()) return;
FastKineticRenderer.setup(Minecraft.getInstance().gameRenderer);
GL11.glEnable(GL13.GL_TEXTURE_3D);
GL13.glActiveTexture(GL40.GL_TEXTURE4); // the shaders expect light volumes to be in texture 4
ShaderCallback callback = ShaderHelper.getViewProjectionCallback(projectionMat, viewMat);
int structureShader = ShaderHelper.useShader(Shader.CONTRAPTION_STRUCTURE, callback);
for (RenderedContraption renderer : renderers.values()) {
renderer.doRenderLayer(renderType, structureShader);
}
if (renderType == FastKineticRenderer.getKineticRenderLayer()) {
int rotatingShader = ShaderHelper.useShader(Shader.CONTRAPTION_ROTATING, callback);
for (RenderedContraption renderer : renderers.values()) {
renderer.setup(rotatingShader);
renderer.kinetics.renderRotating();
renderer.teardown();
}
int beltShader = ShaderHelper.useShader(Shader.CONTRAPTION_BELT, callback);
for (RenderedContraption renderer : renderers.values()) {
renderer.setup(beltShader);
renderer.kinetics.renderBelts();
renderer.teardown();
}
}
ShaderHelper.releaseShader();
GL11.glDisable(GL13.GL_TEXTURE_3D);
FastKineticRenderer.teardown();
GL13.glActiveTexture(GL40.GL_TEXTURE0);
}
public static void removeDeadContraptions() {
ArrayList<Integer> toRemove = new ArrayList<>();
for (RenderedContraption renderer : renderers.values()) {
if (renderer.isDead()) {
toRemove.add(renderer.getEntityId());
renderer.invalidate();
}
}
for (Integer id : toRemove) {
renderers.remove(id);
}
}
public static void invalidateAll() {
for (RenderedContraption renderer : renderers.values()) {
renderer.invalidate();
}
renderers.clear();
}
}

View File

@ -1,226 +0,0 @@
package com.simibubi.create.foundation.render;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.platform.GlStateManager;
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionRenderer;
import com.simibubi.create.foundation.render.instancing.IInstanceRendered;
import com.simibubi.create.foundation.render.instancing.IInstancedTileEntityRenderer;
import com.simibubi.create.foundation.render.shader.Shader;
import com.simibubi.create.foundation.render.shader.ShaderCallback;
import com.simibubi.create.foundation.render.shader.ShaderHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL40;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class FastContraptionRenderer extends ContraptionRenderer {
private static final HashMap<Integer, FastContraptionRenderer> renderers = new HashMap<>();
private HashMap<RenderType, ContraptionBuffer> renderLayers = new HashMap<>();
private ContraptionLighter lighter;
public final FastKineticRenderer kinetics;
private Contraption c;
private Matrix4f model;
public FastContraptionRenderer(World world, Contraption c) {
this.c = c;
this.lighter = new ContraptionLighter(c);
this.kinetics = new FastKineticRenderer();
buildLayers(c);
buildInstancedTiles(c);
}
private void buildLayers(Contraption c) {
for (ContraptionBuffer buffer : renderLayers.values()) {
buffer.delete();
}
renderLayers.clear();
List<RenderType> blockLayers = RenderType.getBlockLayers();
for (RenderType layer : blockLayers) {
renderLayers.put(layer, buildStructureBuffer(c, layer));
}
}
private void buildInstancedTiles(Contraption c) {
List<TileEntity> tileEntities = c.renderedTileEntities;
if (!tileEntities.isEmpty()) {
for (TileEntity te : tileEntities) {
if (te instanceof IInstanceRendered) {
TileEntityRenderer<TileEntity> renderer = TileEntityRendererDispatcher.instance.getRenderer(te);
if (renderer instanceof IInstancedTileEntityRenderer) {
kinetics.addInstancedData(this, te, (IInstancedTileEntityRenderer<? super TileEntity>) renderer);
}
}
}
}
kinetics.markAllDirty();
}
public static void tick() {
if (Minecraft.getInstance().isGamePaused()) return;
for (FastContraptionRenderer renderer : renderers.values()) {
renderer.lighter.update(renderer.c);
}
}
private void setRenderSettings(Matrix4f model) {
this.model = model;
}
private void setup(int shader) {
setupShaderUniforms(shader);
lighter.use();
}
private void teardown() {
lighter.release();
}
private void setupShaderUniforms(int shader) {
FloatBuffer buf = ShaderHelper.VEC3_BUFFER;
int lightBoxSize = GlStateManager.getUniformLocation(shader, "lightBoxSize");
buf.put(0, (float) lighter.getSizeX());
buf.put(1, (float) lighter.getSizeY());
buf.put(2, (float) lighter.getSizeZ());
buf.rewind();
GlStateManager.uniform3(lightBoxSize, buf);
int lightBoxMin = GlStateManager.getUniformLocation(shader, "lightBoxMin");
buf.put(0, (float) lighter.getMinX());
buf.put(1, (float) lighter.getMinY());
buf.put(2, (float) lighter.getMinZ());
buf.rewind();
GlStateManager.uniform3(lightBoxMin, buf);
int model = GlStateManager.getUniformLocation(shader, "model");
this.model.write(ShaderHelper.MATRIX_BUFFER);
ShaderHelper.MATRIX_BUFFER.rewind();
GlStateManager.uniformMatrix4(model, false, ShaderHelper.MATRIX_BUFFER);
}
private void invalidate() {
for (ContraptionBuffer buffer : renderLayers.values()) {
buffer.delete();
}
renderLayers.clear();
lighter.delete();
kinetics.invalidate();
}
public static void markForRendering(World world, Contraption c, MatrixStack model) {
getRenderer(world, c).setRenderSettings(model.peek().getModel());
}
private static FastContraptionRenderer getRenderer(World world, Contraption c) {
FastContraptionRenderer renderer;
int entityId = c.entity.getEntityId();
if (renderers.containsKey(entityId)) {
renderer = renderers.get(entityId);
} else {
renderer = new FastContraptionRenderer(world, c);
renderers.put(entityId, renderer);
}
return renderer;
}
public static void renderLayer(RenderType renderType, Matrix4f projectionMat, Matrix4f viewMat) {
removeDeadContraptions();
if (renderers.isEmpty()) return;
FastKineticRenderer.setup(Minecraft.getInstance().gameRenderer);
GL11.glEnable(GL13.GL_TEXTURE_3D);
GL13.glActiveTexture(GL40.GL_TEXTURE4); // the shaders expect light volumes to be in texture 4
ShaderCallback callback = ShaderHelper.getViewProjectionCallback(projectionMat, viewMat);
int structureShader = ShaderHelper.useShader(Shader.CONTRAPTION_STRUCTURE, callback);
for (FastContraptionRenderer renderer : renderers.values()) {
ContraptionBuffer buffer = renderer.renderLayers.get(renderType);
if (buffer != null) {
renderer.setup(structureShader);
buffer.render();
renderer.teardown();
}
}
if (renderType == FastKineticRenderer.getKineticRenderLayer()) {
int rotatingShader = ShaderHelper.useShader(Shader.CONTRAPTION_ROTATING, callback);
for (FastContraptionRenderer renderer : renderers.values()) {
renderer.setup(rotatingShader);
renderer.kinetics.renderRotating();
renderer.teardown();
}
int beltShader = ShaderHelper.useShader(Shader.CONTRAPTION_BELT, callback);
for (FastContraptionRenderer renderer : renderers.values()) {
renderer.setup(beltShader);
renderer.kinetics.renderBelts();
renderer.teardown();
}
}
ShaderHelper.releaseShader();
GL11.glDisable(GL13.GL_TEXTURE_3D);
FastKineticRenderer.teardown();
GL13.glActiveTexture(GL40.GL_TEXTURE0);
}
public static void removeDeadContraptions() {
ArrayList<Integer> toRemove = new ArrayList<>();
for (FastContraptionRenderer renderer : renderers.values()) {
if (!renderer.c.entity.isAlive()) {
toRemove.add(renderer.c.entity.getEntityId());
renderer.invalidate();
}
}
for (Integer id : toRemove) {
renderers.remove(id);
}
}
public static void invalidateAll() {
for (FastContraptionRenderer renderer : renderers.values()) {
renderer.invalidate();
}
renderers.clear();
}
private static ContraptionBuffer buildStructureBuffer(Contraption c, RenderType layer) {
BufferBuilder builder = buildStructure(c, layer);
return new ContraptionBuffer(builder);
}
}

View File

@ -70,7 +70,7 @@ public class FastKineticRenderer {
renderer.addInstanceData(new InstanceContext.World<>(te));
}
<T extends TileEntity> void addInstancedData(FastContraptionRenderer c, T te, IInstancedTileEntityRenderer<T> renderer) {
<T extends TileEntity> void addInstancedData(RenderedContraption c, T te, IInstancedTileEntityRenderer<T> renderer) {
renderer.addInstanceData(new InstanceContext.Contraption<>(te, c));
}
@ -87,6 +87,7 @@ public class FastKineticRenderer {
runOnAll(InstanceBuffer::delete);
belts.values().forEach(Cache::invalidateAll);
rotating.values().forEach(Cache::invalidateAll);
dirty = true;
}
private void runOnAll(Consumer<InstanceBuffer<?>> f) {

View File

@ -19,6 +19,7 @@ import net.minecraft.potion.Effects;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.SectionPos;
import net.minecraft.world.ILightReader;
import net.minecraft.world.LightType;
import net.minecraft.world.chunk.Chunk;
@ -42,11 +43,11 @@ public class FastRenderDispatcher {
CreateClient.kineticRenderer.renderInstancesAsWorld(type, projection, view);
}
FastContraptionRenderer.renderLayer(type, projection, view);
ContraptionRenderDispatcher.renderLayer(type, projection, view);
}
public static void notifyLightUpdate(ClientChunkProvider world, LightType type, SectionPos pos) {
FastContraptionRenderer.tick();
ContraptionRenderDispatcher.notifyLightUpdate((ILightReader) world.getWorld(), type, pos);
Chunk chunk = world.getChunk(pos.getSectionX(), pos.getSectionZ(), false);

View File

@ -5,4 +5,9 @@ public class RenderMath {
int h = Integer.highestOneBit(a);
return (h == a) ? h : (h << 1);
}
public static boolean isPowerOf2(int n) {
int b = n & (n - 1);
return b == 0 && n != 0;
}
}

View File

@ -0,0 +1,152 @@
package com.simibubi.create.foundation.render;
import com.mojang.blaze3d.platform.GlStateManager;
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
import com.simibubi.create.content.contraptions.components.structureMovement.ContraptionRenderer;
import com.simibubi.create.foundation.render.instancing.IInstanceRendered;
import com.simibubi.create.foundation.render.instancing.IInstancedTileEntityRenderer;
import com.simibubi.create.foundation.render.light.ContraptionLighter;
import com.simibubi.create.foundation.render.light.LightVolume;
import com.simibubi.create.foundation.render.shader.ShaderHelper;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.SectionPos;
import net.minecraft.world.ILightReader;
import net.minecraft.world.LightType;
import net.minecraft.world.World;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
public class RenderedContraption {
private HashMap<RenderType, ContraptionBuffer> renderLayers = new HashMap<>();
private final ContraptionLighter<?> lighter;
public final FastKineticRenderer kinetics;
private Contraption contraption;
private Matrix4f model;
public RenderedContraption(World world, Contraption contraption) {
this.contraption = contraption;
this.lighter = contraption.makeLighter();
this.kinetics = new FastKineticRenderer();
buildLayers(contraption);
buildInstancedTiles(contraption);
}
public int getEntityId() {
return contraption.entity.getEntityId();
}
public boolean isDead() {
return !contraption.entity.isAlive();
}
public ContraptionLighter<?> getLighter() {
return lighter;
}
public void doRenderLayer(RenderType layer, int shader) {
ContraptionBuffer buffer = renderLayers.get(layer);
if (buffer != null) {
setup(shader);
buffer.render();
teardown();
}
}
private void buildLayers(Contraption c) {
for (ContraptionBuffer buffer : renderLayers.values()) {
buffer.delete();
}
renderLayers.clear();
List<RenderType> blockLayers = RenderType.getBlockLayers();
for (RenderType layer : blockLayers) {
renderLayers.put(layer, buildStructureBuffer(c, layer));
}
}
private void buildInstancedTiles(Contraption c) {
List<TileEntity> tileEntities = c.renderedTileEntities;
if (!tileEntities.isEmpty()) {
for (TileEntity te : tileEntities) {
if (te instanceof IInstanceRendered) {
TileEntityRenderer<TileEntity> renderer = TileEntityRendererDispatcher.instance.getRenderer(te);
if (renderer instanceof IInstancedTileEntityRenderer) {
kinetics.addInstancedData(this, te, (IInstancedTileEntityRenderer<? super TileEntity>) renderer);
}
}
}
}
kinetics.markAllDirty();
}
void setRenderSettings(Matrix4f model) {
this.model = model;
}
void setup(int shader) {
setupShaderUniforms(shader);
lighter.lightVolume.use();
}
void teardown() {
lighter.lightVolume.release();
}
void setupShaderUniforms(int shader) {
FloatBuffer buf = ShaderHelper.VEC3_BUFFER;
int lightBoxSize = GlStateManager.getUniformLocation(shader, "lightBoxSize");
buf.put(0, (float) lighter.lightVolume.getSizeX());
buf.put(1, (float) lighter.lightVolume.getSizeY());
buf.put(2, (float) lighter.lightVolume.getSizeZ());
buf.rewind();
GlStateManager.uniform3(lightBoxSize, buf);
int lightBoxMin = GlStateManager.getUniformLocation(shader, "lightBoxMin");
buf.put(0, (float) lighter.lightVolume.getMinX());
buf.put(1, (float) lighter.lightVolume.getMinY());
buf.put(2, (float) lighter.lightVolume.getMinZ());
buf.rewind();
GlStateManager.uniform3(lightBoxMin, buf);
int model = GlStateManager.getUniformLocation(shader, "model");
this.model.write(ShaderHelper.MATRIX_BUFFER);
ShaderHelper.MATRIX_BUFFER.rewind();
GlStateManager.uniformMatrix4(model, false, ShaderHelper.MATRIX_BUFFER);
}
void invalidate() {
for (ContraptionBuffer buffer : renderLayers.values()) {
buffer.delete();
}
renderLayers.clear();
lighter.lightVolume.delete();
kinetics.invalidate();
}
private static ContraptionBuffer buildStructureBuffer(Contraption c, RenderType layer) {
BufferBuilder builder = ContraptionRenderer.buildStructure(c, layer);
return new ContraptionBuffer(builder);
}
}

View File

@ -1,7 +1,7 @@
package com.simibubi.create.foundation.render.instancing;
import com.simibubi.create.CreateClient;
import com.simibubi.create.foundation.render.FastContraptionRenderer;
import com.simibubi.create.foundation.render.RenderedContraption;
import com.simibubi.create.foundation.render.FastKineticRenderer;
import net.minecraft.tileentity.TileEntity;
@ -19,9 +19,9 @@ public abstract class InstanceContext<T extends TileEntity> {
public static class Contraption<T extends TileEntity> extends InstanceContext<T> {
public final FastContraptionRenderer c;
public final RenderedContraption c;
public Contraption(T te, FastContraptionRenderer c) {
public Contraption(T te, RenderedContraption c) {
super(te);
this.c = c;
}

View File

@ -0,0 +1,27 @@
package com.simibubi.create.foundation.render.light;
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
import net.minecraft.util.math.SectionPos;
import net.minecraft.world.ILightReader;
import net.minecraft.world.LightType;
import static com.simibubi.create.foundation.render.RenderMath.nextPowerOf2;
public abstract class ContraptionLighter<C extends Contraption> {
protected final C contraption;
public final LightVolume lightVolume;
protected ContraptionLighter(C contraption) {
this.contraption = contraption;
GridAlignedBB bounds = getContraptionBounds();
bounds.grow(1); // so we have at least enough data on the edges to avoid artifacts
bounds.nextPowerOf2Centered();
lightVolume = new LightVolume(bounds);
lightVolume.initialize(contraption.entity.world);
}
public abstract GridAlignedBB getContraptionBounds();
}

View File

@ -0,0 +1,6 @@
package com.simibubi.create.foundation.render.light;
@FunctionalInterface
public interface CoordinateConsumer {
void consume(int x, int y, int z);
}

View File

@ -0,0 +1,15 @@
package com.simibubi.create.foundation.render.light;
import com.simibubi.create.content.contraptions.components.structureMovement.Contraption;
// so other contraptions don't crash before they have a lighter
public class EmptyLighter extends ContraptionLighter<Contraption> {
public EmptyLighter(Contraption contraption) {
super(contraption);
}
@Override
public GridAlignedBB getContraptionBounds() {
return new GridAlignedBB(0, 0, 0, 1, 1, 1);
}
}

View File

@ -0,0 +1,203 @@
package com.simibubi.create.foundation.render.light;
import com.simibubi.create.foundation.render.RenderMath;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.SectionPos;
import net.minecraft.util.math.Vec3i;
import net.minecraft.world.LightType;
import java.util.function.IntFunction;
import static com.simibubi.create.foundation.render.RenderMath.isPowerOf2;
public class GridAlignedBB {
public int minX;
public int minY;
public int minZ;
public int maxX;
public int maxY;
public int maxZ;
public GridAlignedBB(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
this.minX = minX;
this.minY = minY;
this.minZ = minZ;
this.maxX = maxX;
this.maxY = maxY;
this.maxZ = maxZ;
}
public static GridAlignedBB copy(GridAlignedBB bb) {
return new GridAlignedBB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ);
}
public static GridAlignedBB fromAABB(AxisAlignedBB aabb) {
int minX = (int) Math.floor(aabb.minX);
int minY = (int) Math.floor(aabb.minY);
int minZ = (int) Math.floor(aabb.minZ);
int maxX = (int) Math.ceil(aabb.maxX);
int maxY = (int) Math.ceil(aabb.maxY);
int maxZ = (int) Math.ceil(aabb.maxZ);
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
}
public static GridAlignedBB fromSection(SectionPos pos) {
return new GridAlignedBB(pos.getWorldStartX(),
pos.getWorldStartY(),
pos.getWorldStartZ(),
pos.getWorldEndX() + 1,
pos.getWorldEndY() + 1,
pos.getWorldEndZ() + 1);
}
public static AxisAlignedBB toAABB(GridAlignedBB bb) {
return new AxisAlignedBB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ);
}
public int sizeX() {
return maxX - minX;
}
public int sizeY() {
return maxY - minY;
}
public int sizeZ() {
return maxZ - minZ;
}
public int volume() {
return sizeX() * sizeY() * sizeZ();
}
public boolean empty() {
// if any dimension has side length 0 this box contains no volume
return minX == maxX ||
minY == maxY ||
minZ == maxZ;
}
public void translate(Vec3i by) {
this.minX += by.getX();
this.minY += by.getY();
this.minZ += by.getZ();
this.maxX += by.getX();
this.maxY += by.getY();
this.maxZ += by.getZ();
}
/**
* Grow this bounding box to have power of 2 side length, scaling from the center.
*/
public void nextPowerOf2Centered() {
int sizeX = sizeX();
int sizeY = sizeY();
int sizeZ = sizeZ();
int newSizeX = RenderMath.nextPowerOf2(sizeX);
int newSizeY = RenderMath.nextPowerOf2(sizeY);
int newSizeZ = RenderMath.nextPowerOf2(sizeZ);
int diffX = newSizeX - sizeX;
int diffY = newSizeY - sizeY;
int diffZ = newSizeZ - sizeZ;
this.minX -= diffX / 2; // floor division for the minimums
this.minY -= diffY / 2;
this.minZ -= diffZ / 2;
this.maxX += (diffX + 1) / 2; // ceiling divison for the maximums
this.maxY += (diffY + 1) / 2;
this.maxZ += (diffZ + 1) / 2;
}
/**
* Grow this bounding box to have power of 2 side lengths, scaling from the minimum coords.
*/
public void nextPowerOf2() {
int sizeX = RenderMath.nextPowerOf2(sizeX());
int sizeY = RenderMath.nextPowerOf2(sizeY());
int sizeZ = RenderMath.nextPowerOf2(sizeZ());
this.maxX = this.minX + sizeX;
this.maxY = this.minY + sizeY;
this.maxZ = this.minZ + sizeZ;
}
public boolean hasPowerOf2Sides() {
// this is only true if all individual side lengths are powers of 2
return isPowerOf2(volume());
}
public void grow(int s) {
this.grow(s, s, s);
}
public void grow(int x, int y, int z) {
this.minX -= x;
this.minY -= y;
this.minZ -= z;
this.maxX += x;
this.maxY += y;
this.maxZ += z;
}
public GridAlignedBB intersect(GridAlignedBB other) {
int minX = Math.max(this.minX, other.minX);
int minY = Math.max(this.minY, other.minY);
int minZ = Math.max(this.minZ, other.minZ);
int maxX = Math.min(this.maxX, other.maxX);
int maxY = Math.min(this.maxY, other.maxY);
int maxZ = Math.min(this.maxZ, other.maxZ);
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
}
public void intersectAssign(GridAlignedBB other) {
this.minX = Math.max(this.minX, other.minX);
this.minY = Math.max(this.minY, other.minY);
this.minZ = Math.max(this.minZ, other.minZ);
this.maxX = Math.min(this.maxX, other.maxX);
this.maxY = Math.min(this.maxY, other.maxY);
this.maxZ = Math.min(this.maxZ, other.maxZ);
}
public GridAlignedBB union(GridAlignedBB other) {
int minX = Math.min(this.minX, other.minX);
int minY = Math.min(this.minY, other.minY);
int minZ = Math.min(this.minZ, other.minZ);
int maxX = Math.max(this.maxX, other.maxX);
int maxY = Math.max(this.maxY, other.maxY);
int maxZ = Math.max(this.maxZ, other.maxZ);
return new GridAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
}
public void unionAssign(GridAlignedBB other) {
this.minX = Math.min(this.minX, other.minX);
this.minY = Math.min(this.minY, other.minY);
this.minZ = Math.min(this.minZ, other.minZ);
this.maxX = Math.max(this.maxX, other.maxX);
this.maxY = Math.max(this.maxY, other.maxY);
this.maxZ = Math.max(this.maxZ, other.maxZ);
}
public boolean intersects(GridAlignedBB other) {
return this.intersects(other.minX, other.minY, other.minZ, other.maxX, other.maxY, other.maxZ);
}
public boolean intersects(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
return this.minX < maxX && this.maxX > minX && this.minY < maxY && this.maxY > minY && this.minZ < maxZ && this.maxZ > minZ;
}
public void forEachContained(CoordinateConsumer func) {
if (empty()) return;
for (int x = minX; x < maxX; x++) {
for (int y = Math.max(minY, 0); y < Math.min(maxY, 255); y++) { // clamp to world height limits
for (int z = minZ; z < maxZ; z++) {
func.consume(x, y, z);
}
}
}
}
}

View File

@ -0,0 +1,204 @@
package com.simibubi.create.foundation.render.light;
import com.simibubi.create.foundation.render.RenderWork;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.SectionPos;
import net.minecraft.world.ILightReader;
import net.minecraft.world.LightType;
import org.lwjgl.opengl.*;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
public class LightVolume {
private final GridAlignedBB volume;
private ByteBuffer lightData;
private boolean bufferDirty;
private int glTexture;
public LightVolume(GridAlignedBB volume) {
// the gpu requires that all textures have power of 2 side lengths
if (!volume.hasPowerOf2Sides())
throw new IllegalArgumentException("LightVolume must have power of 2 side lengths");
this.volume = volume;
this.glTexture = GL11.glGenTextures();
this.lightData = MemoryUtil.memAlloc(this.volume.volume() * 2); // TODO: maybe figure out how to pack light coords into a single byte
}
public GridAlignedBB getBox() {
return GridAlignedBB.copy(volume);
}
public int getMinX() {
return volume.minX;
}
public int getMinY() {
return volume.minY;
}
public int getMinZ() {
return volume.minZ;
}
public int getMaxX() {
return volume.maxX;
}
public int getMaxY() {
return volume.maxY;
}
public int getMaxZ() {
return volume.maxZ;
}
public int getSizeX() {
return volume.sizeX();
}
public int getSizeY() {
return volume.sizeY();
}
public int getSizeZ() {
return volume.sizeZ();
}
public void notifyLightUpdate(ILightReader world, LightType type, SectionPos location) {
GridAlignedBB changedVolume = GridAlignedBB.fromSection(location);
changedVolume.intersectAssign(volume); // compute the region contained by us that has dirty lighting data.
if (!changedVolume.empty()) {
if (type == LightType.BLOCK) copyBlock(world, changedVolume);
else if (type == LightType.SKY) copySky(world, changedVolume);
}
}
/**
* Completely (re)populate this volume with block and sky lighting data.
* This is expensive and should be avoided.
*/
public void initialize(ILightReader world) {
BlockPos.Mutable pos = new BlockPos.Mutable();
int shiftX = volume.minX;
int shiftY = volume.minY;
int shiftZ = volume.minZ;
volume.forEachContained((x, y, z) -> {
pos.setPos(x, y, z);
int blockLight = world.getLightLevel(LightType.BLOCK, pos);
int skyLight = world.getLightLevel(LightType.SKY, pos);
writeLight(x - shiftX, y - shiftY, z - shiftZ, blockLight, skyLight);
});
bufferDirty = true;
}
/**
* Copy block light from the world into this volume.
* @param worldVolume the region in the world to copy data from.
*/
public void copyBlock(ILightReader world, GridAlignedBB worldVolume) {
BlockPos.Mutable pos = new BlockPos.Mutable();
int xShift = volume.minX;
int yShift = volume.minY;
int zShift = volume.minZ;
worldVolume.forEachContained((x, y, z) -> {
pos.setPos(x, y, z);
int light = world.getLightLevel(LightType.BLOCK, pos);
writeBlock(x - xShift, y - yShift, z - zShift, light);
});
bufferDirty = true;
}
/**
* Copy sky light from the world into this volume.
* @param worldVolume the region in the world to copy data from.
*/
public void copySky(ILightReader world, GridAlignedBB worldVolume) {
BlockPos.Mutable pos = new BlockPos.Mutable();
int xShift = volume.minX;
int yShift = volume.minY;
int zShift = volume.minZ;
worldVolume.forEachContained((x, y, z) -> {
pos.setPos(x, y, z);
int light = world.getLightLevel(LightType.SKY, pos);
writeSky(x - xShift, y - yShift, z - zShift, light);
});
bufferDirty = true;
}
public void use() {
// just in case something goes wrong or we accidentally call this before this volume is properly disposed of.
if (glTexture == 0 || lightData == null) return;
GL12.glBindTexture(GL12.GL_TEXTURE_3D, glTexture);
GL11.glTexParameteri(GL13.GL_TEXTURE_3D, GL13.GL_TEXTURE_MIN_FILTER, GL13.GL_LINEAR);
GL11.glTexParameteri(GL13.GL_TEXTURE_3D, GL13.GL_TEXTURE_MAG_FILTER, GL13.GL_LINEAR);
GL11.glTexParameteri(GL13.GL_TEXTURE_3D, GL13.GL_TEXTURE_WRAP_S, GL20.GL_MIRRORED_REPEAT);
GL11.glTexParameteri(GL13.GL_TEXTURE_3D, GL13.GL_TEXTURE_WRAP_R, GL20.GL_MIRRORED_REPEAT);
GL11.glTexParameteri(GL13.GL_TEXTURE_3D, GL13.GL_TEXTURE_WRAP_T, GL20.GL_MIRRORED_REPEAT);
if (bufferDirty) {
GL12.glTexImage3D(GL12.GL_TEXTURE_3D, 0, GL40.GL_RG8, volume.sizeX(), volume.sizeY(), volume.sizeZ(), 0, GL40.GL_RG, GL40.GL_UNSIGNED_BYTE, lightData);
bufferDirty = false;
}
}
public void release() {
GL12.glBindTexture(GL12.GL_TEXTURE_3D, 0);
}
public void delete() {
RenderWork.enqueue(() -> {
GL15.glDeleteTextures(glTexture);
glTexture = 0;
MemoryUtil.memFree(lightData);
lightData = null;
});
}
private void writeLight(int x, int y, int z, int block, int sky) {
byte b = (byte) ((block & 0xF) << 4);
byte s = (byte) ((sky & 0xF) << 4);
int i = index(x, y, z);
lightData.put(i, b);
lightData.put(i + 1, s);
}
private void writeBlock(int x, int y, int z, int block) {
byte b = (byte) ((block & 0xF) << 4);
lightData.put(index(x, y, z), b);
}
private void writeSky(int x, int y, int z, int sky) {
byte b = (byte) ((sky & 0xF) << 4);
lightData.put(index(x, y, z) + 1, b);
}
private int index(int x, int y, int z) {
return (x + volume.sizeX() * (y + z * volume.sizeY())) * 2;
}
}

View File

@ -0,0 +1,18 @@
package com.simibubi.create.foundation.render.light;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.simibubi.create.foundation.render.ContraptionRenderDispatcher;
import com.simibubi.create.foundation.render.RenderedContraption;
import com.simibubi.create.foundation.renderState.SuperRenderTypeBuffer;
import com.simibubi.create.foundation.utility.outliner.AABBOutline;
import com.simibubi.create.foundation.utility.outliner.Outline;
public class LightVolumeDebugger {
public static void render(MatrixStack ms, SuperRenderTypeBuffer buffer) {
ContraptionRenderDispatcher.renderers.values()
.stream()
.map(r -> r.getLighter().lightVolume.getBox())
.map(volume -> new AABBOutline(GridAlignedBB.toAABB(volume)))
.forEach(outline -> outline.render(ms, buffer));
}
}

View File

@ -13,6 +13,8 @@ import net.minecraft.resources.IReloadableResourceManager;
import net.minecraft.resources.IResourceManager;
import net.minecraft.resources.IResourceManagerReloadListener;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.resource.ISelectiveResourceReloadListener;
import net.minecraftforge.resource.VanillaResourceType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil;
@ -41,11 +43,13 @@ public class ShaderHelper {
if (Minecraft.getInstance() != null
&& Minecraft.getInstance().getResourceManager() instanceof IReloadableResourceManager) {
((IReloadableResourceManager) Minecraft.getInstance().getResourceManager()).addReloadListener(
(IResourceManagerReloadListener) manager -> {
PROGRAMS.values().forEach(ShaderLinkHelper::deleteShader);
PROGRAMS.clear();
for (Shader shader : Shader.values()) {
createProgram(manager, shader);
(ISelectiveResourceReloadListener) (manager, predicate) -> {
if (predicate.test(VanillaResourceType.SHADERS)) {
PROGRAMS.values().forEach(ShaderLinkHelper::deleteShader);
PROGRAMS.clear();
for (Shader shader : Shader.values()) {
createProgram(manager, shader);
}
}
});
}

View File

@ -14,7 +14,7 @@ Technology that empowers the player.'''
[[dependencies.create]]
modId="forge"
mandatory=true
versionRange="[31.2.0,)"
versionRange="[31.2.44,)"
ordering="NONE"
side="BOTH"

View File

@ -55,13 +55,17 @@ void main() {
float scroll = fract(speed * time / (36 * 16.)) * scrollSize * scrollMult;
Diffuse = diffuse(normalize((rotation * vec4(aNormal, 0.)).xyz));
vec3 norm = (rotation * vec4(aNormal, 0.)).xyz;
Diffuse = diffuse(norm);
Light = light;
TexCoords = aTexCoords - sourceUV + scrollTexture.xy + vec2(0., scroll);
gl_Position = projection * view * renderPos;
if (debug == 1) {
Color = vec4(networkTint, 1);
} else if (debug == 2) {
Color = vec4(norm, 1);
} else {
Color = vec4(1);
}