diff --git a/src/main/java/me/cortex/zenith/client/core/VoxelCore.java b/src/main/java/me/cortex/zenith/client/core/VoxelCore.java index c83fb545..0d85d1e0 100644 --- a/src/main/java/me/cortex/zenith/client/core/VoxelCore.java +++ b/src/main/java/me/cortex/zenith/client/core/VoxelCore.java @@ -1,8 +1,6 @@ package me.cortex.zenith.client.core; import me.cortex.zenith.client.config.ZenithConfig; -import me.cortex.zenith.client.core.model.ModelTextureBakery; -import me.cortex.zenith.client.core.model.TextureUtils; import me.cortex.zenith.client.core.rendering.*; import me.cortex.zenith.client.core.rendering.building.RenderGenerationService; import me.cortex.zenith.client.core.util.DebugUtil; @@ -11,8 +9,6 @@ import me.cortex.zenith.client.importers.WorldImporter; import me.cortex.zenith.common.world.storage.FragmentedStorageBackendAdaptor; import net.minecraft.block.Block; import net.minecraft.block.Blocks; -import net.minecraft.block.CropBlock; -import net.minecraft.block.SnowBlock; import net.minecraft.client.render.Camera; import net.minecraft.client.render.Frustum; import net.minecraft.client.util.math.MatrixStack; @@ -87,7 +83,7 @@ public class VoxelCore { for (var state : this.world.getMapper().getStateEntries()) { - this.renderer.getModelManager().updateEntry(state.id, state.state); + this.renderer.getModelManager().addEntry(state.id, state.state); } //this.renderer.getModelManager().updateEntry(0, Blocks.GRASS_BLOCK.getDefaultState()); } diff --git a/src/main/java/me/cortex/zenith/client/core/model/ColourDepthTextureData.java b/src/main/java/me/cortex/zenith/client/core/model/ColourDepthTextureData.java index b3685847..ca19f3f0 100644 --- a/src/main/java/me/cortex/zenith/client/core/model/ColourDepthTextureData.java +++ b/src/main/java/me/cortex/zenith/client/core/model/ColourDepthTextureData.java @@ -1,4 +1,17 @@ package me.cortex.zenith.client.core.model; +import java.util.Arrays; + public record ColourDepthTextureData(int[] colour, int[] depth) { + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + var other = ((ColourDepthTextureData)obj); + return Arrays.equals(other.colour, this.colour) && Arrays.equals(other.depth, this.depth); + } + + @Override + public int hashCode() { + return Arrays.hashCode(this.colour) ^ Arrays.hashCode(this.depth); + } } diff --git a/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java b/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java index 956c330b..91041202 100644 --- a/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java +++ b/src/main/java/me/cortex/zenith/client/core/model/ModelManager.java @@ -2,13 +2,18 @@ package me.cortex.zenith.client.core.model; import com.mojang.blaze3d.platform.GlConst; import com.mojang.blaze3d.platform.GlStateManager; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import me.cortex.zenith.client.core.gl.GlBuffer; import me.cortex.zenith.client.core.gl.GlTexture; import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.color.block.BlockColors; +import net.minecraft.client.render.RenderLayers; import net.minecraft.registry.Registries; -import org.lwjgl.opengl.GL45C; +import net.minecraft.util.math.Direction; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL11C.GL_NEAREST; @@ -34,6 +39,8 @@ public class ModelManager { private final GlTexture textures; private final int blockSampler = glGenSamplers(); + private final int modelTextureSize; + //Model data might also contain a constant colour if the colour resolver produces a constant colour, this saves space in the // section buffer reverse indexing @@ -65,7 +72,7 @@ public class ModelManager { //Provides a map from id -> model id as multiple ids might have the same internal model id private final int[] idMappings; - private final int modelTextureSize; + private final Object2IntOpenHashMap> modelTexture2id = new Object2IntOpenHashMap<>(); public ModelManager(int modelTextureSize) { this.modelTextureSize = modelTextureSize; @@ -75,24 +82,59 @@ public class ModelManager { this.textures = new GlTexture().store(GL_RGBA8, 1, modelTextureSize*3*256,modelTextureSize*2*256); this.metadataCache = new long[1<<16]; this.idMappings = new int[1<<16]; + Arrays.fill(this.idMappings, -1); glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glSamplerParameteri(this.blockSampler, GL_TEXTURE_MIN_LOD, 0); glSamplerParameteri(this.blockSampler, GL_TEXTURE_MAX_LOD, 4); + + this.modelTexture2id.defaultReturnValue(-1); } - public void updateEntry(int id, BlockState blockState) { + + //TODO: so need a few things, per face sizes and offsets, the sizes should be computed from the pixels and find the minimum bounding pixel + // while the depth is computed from the depth buffer data + public int addEntry(int blockId, BlockState blockState) { + if (this.idMappings[blockId] != -1) { + throw new IllegalArgumentException("Trying to add entry for duplicate id"); + } + + int modelId = -1; + var textureData = this.bakery.renderFaces(blockState, 123456); + {//Deduplicate same entries + int possibleDuplicate = this.modelTexture2id.getInt(List.of(textureData)); + if (possibleDuplicate != -1) {//Duplicate found + this.idMappings[blockId] = possibleDuplicate; + return possibleDuplicate; + } else {//Not a duplicate so create a new entry + modelId = this.modelTexture2id.size(); + this.idMappings[blockId] = modelId; + this.modelTexture2id.put(List.of(textureData), modelId); + } + } + this.putTextures(modelId, textureData); + var colourProvider = MinecraftClient.getInstance().getBlockColors().providers.get(Registries.BLOCK.getRawId(blockState.getBlock())); + var blockRenderLayer = RenderLayers.getBlockLayer(blockState); + //If it is the solid layer, it is _always_ going to occlude fully for all written pixels, even if they are 100% translucent, this should save alot of resources + // if it is cutout it might occlude might not, need to test + // if it is translucent it will _never_ occlude + + //NOTE: this is excluding fluid states + //This also checks if there is a block colour resolver for the given blockstate and marks that the block has a resolver - var textureData = this.bakery.renderFaces(blockState, 123456); - int depth = TextureUtils.computeDepth(textureData[0], TextureUtils.DEPTH_MODE_AVG); + var sizes = this.computeModelDepth(textureData); - int aaaa = 1; - - this.putTextures(id, textureData); + for (int face = 0; face < 6; face++) { + if (sizes[face] == -1) {//Face is empty, so ignore + continue; + } + //TODO: combine all the methods into a single + //boolean fullyOccluding = TextureUtils.hasAlpha() + } //Model data contains, the quad size and offset of each face and whether the face needs to be resolved with a colour modifier @@ -104,6 +146,60 @@ public class ModelManager { //TODO: need to make an option for like leaves to be fully opaque as by default they are not!!!! + + + + + + + + + //Models data is computed per face, the axis offset of the face is computed from the depth component of the rasterized data + // the size and offset of the face data is computed from the remaining pixels that where actually rastered (e.g. depth != 1.0) + // (note this might be able to be optimized for cuttout layers where it automatically tries to squish as much as possible) + // solid layer renders it as black so might need to add a bitset in the model data of whether the face is rendering + // in discard or solid mode maybe? + + + + + + + + + return modelId; + } + + private int[] computeModelDepth(ColourDepthTextureData[] textures) { + int[] res = new int[6]; + for (var dir : Direction.values()) { + var data = textures[dir.getId()]; + float fd = TextureUtils.computeDepth(data, TextureUtils.DEPTH_MODE_MIN);//Compute the min float depth, smaller means closer to the camera, range 0-1 + int depth = Math.round(fd * this.modelTextureSize); + //If fd is -1, it means that there was nothing rendered on that face and it should be discarded + if (fd < -0.1) { + res[dir.ordinal()] = -1; + } else { + res[dir.ordinal()] = depth; + } + } + return res; + } + + public long getModelMetadata(int blockId) { + int map = this.idMappings[blockId]; + if (map == -1) { + throw new IllegalArgumentException("Id hasnt been computed yet"); + } + return this.metadataCache[map]; + } + + public int getModelId(int blockId) { + int map = this.idMappings[blockId]; + if (map == -1) { + throw new IllegalArgumentException("Id hasnt been computed yet"); + } + return map; } private void putTextures(int id, ColourDepthTextureData[] textures) { @@ -121,10 +217,6 @@ public class ModelManager { } } - public long getModelMetadata(int id) { - return this.metadataCache[id]; - } - public int getBufferId() { return this.modelBuffer.id; } @@ -132,6 +224,7 @@ public class ModelManager { public int getTextureId() { return this.textures.id; } + public int getSamplerId() { return this.blockSampler; } diff --git a/src/main/java/me/cortex/zenith/client/core/model/ModelTextureBakery.java b/src/main/java/me/cortex/zenith/client/core/model/ModelTextureBakery.java index 3987c7db..ca646945 100644 --- a/src/main/java/me/cortex/zenith/client/core/model/ModelTextureBakery.java +++ b/src/main/java/me/cortex/zenith/client/core/model/ModelTextureBakery.java @@ -1,15 +1,22 @@ package me.cortex.zenith.client.core.model; +import com.mojang.blaze3d.platform.GlConst; import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.VertexSorter; import me.cortex.zenith.client.core.gl.GlFramebuffer; import me.cortex.zenith.client.core.gl.GlTexture; +import me.cortex.zenith.client.core.gl.shader.Shader; +import me.cortex.zenith.client.core.gl.shader.ShaderLoader; +import me.cortex.zenith.client.core.gl.shader.ShaderType; +import me.jellysquid.mods.sodium.client.gl.shader.GlShader; import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.GlUniform; import net.minecraft.client.render.*; import net.minecraft.client.render.model.BakedModel; import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.Identifier; import net.minecraft.util.math.Direction; import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.random.LocalRandom; @@ -24,6 +31,8 @@ import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BI import static org.lwjgl.opengl.ARBShaderImageLoadStore.glMemoryBarrier; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate; +import static org.lwjgl.opengl.GL20C.glUniformMatrix4fv; +import static org.lwjgl.opengl.GL20C.glUseProgram; import static org.lwjgl.opengl.GL45C.glBlitNamedFramebuffer; import static org.lwjgl.opengl.GL45C.glGetTextureImage; @@ -34,6 +43,10 @@ public class ModelTextureBakery { private final GlTexture colourTex; private final GlTexture depthTex; private final GlFramebuffer framebuffer; + private final Shader rasterShader = Shader.make() + .add(ShaderType.VERTEX, "zenith:bakery/position_tex.vsh") + .add(ShaderType.FRAGMENT, "zenith:bakery/position_tex.fsh") + .compile(); private static final List FACE_VIEWS = new ArrayList<>(); static { @@ -42,8 +55,8 @@ public class ModelTextureBakery { addView(0,180, 0);//Direction.NORTH addView(0,0, 0);//Direction.SOUTH //TODO: check these arnt the wrong way round - addView(0,90, -90);//Direction.EAST - addView(0,270, -90);//Direction.WEST + addView(0,90, 270);//Direction.EAST + addView(0,270, 270);//Direction.WEST } public ModelTextureBakery(int width, int height) { @@ -64,6 +77,9 @@ public class ModelTextureBakery { FACE_VIEWS.add(stack); } + + //TODO: For block entities, also somehow attempt to render the default block entity, e.g. chests and stuff + // cause that will result in ok looking micro details in the terrain public ColourDepthTextureData[] renderFaces(BlockState state, long randomValue) { var model = MinecraftClient.getInstance() .getBakedModelManager() @@ -82,9 +98,7 @@ public class ModelTextureBakery { var renderLayer = RenderLayers.getBlockLayer(state); - if (renderLayer == RenderLayer.getTranslucent()) { - //TODO: TRANSLUCENT, must sort the quad first - } + renderLayer.startDrawing(); RenderSystem.depthMask(true); RenderSystem.enableBlend(); @@ -92,8 +106,16 @@ public class ModelTextureBakery { RenderSystem.enableCull(); RenderSystem.depthFunc(GL_LESS); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - RenderSystem.setShader(GameRenderer::getPositionTexProgram); + //TODO: bind the required uniforms and + this.rasterShader.bind(); + RenderSystem.bindTexture(RenderSystem.getShaderTexture(0)); + GlUniform.uniform1(0, 0); + RenderSystem.activeTexture(GlConst.GL_TEXTURE0); + + if (renderLayer == RenderLayer.getTranslucent()) { + //TODO: TRANSLUCENT, must sort the quad first, or something idk + } if (!state.getFluidState().isEmpty()) { //TODO: render fluid @@ -109,7 +131,7 @@ public class ModelTextureBakery { RenderSystem.setProjectionMatrix(oldProjection, VertexSorter.BY_DISTANCE); glBindFramebuffer(GL_FRAMEBUFFER, oldFB); GL11C.glViewport(GlStateManager.Viewport.getX(), GlStateManager.Viewport.getY(), GlStateManager.Viewport.getWidth(), GlStateManager.Viewport.getHeight()); - //glBlitNamedFramebuffer(this.framebuffer.id, oldFB, 0,0,16,16,0,0,256,256, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBlitNamedFramebuffer(this.framebuffer.id, oldFB, 0,0,16,16,0,0,256,256, GL_COLOR_BUFFER_BIT, GL_NEAREST); return faces; } @@ -118,7 +140,11 @@ public class ModelTextureBakery { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); vc.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE); renderQuads(vc, state, model, stack, randomValue); - BufferRenderer.drawWithGlobalProgram(vc.end()); + + float[] mat = new float[4*4]; + new Matrix4f(RenderSystem.getModelViewMatrix()).mul(RenderSystem.getProjectionMatrix()).get(mat); + glUniformMatrix4fv(1, false, mat); + BufferRenderer.draw(vc.end()); glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT); int[] colourData = new int[this.width*this.height]; @@ -141,5 +167,6 @@ public class ModelTextureBakery { this.framebuffer.free(); this.colourTex.free(); this.depthTex.free(); + this.rasterShader.free(); } } diff --git a/src/main/java/me/cortex/zenith/client/core/model/TextureUtils.java b/src/main/java/me/cortex/zenith/client/core/model/TextureUtils.java index 84de9b92..326d5380 100644 --- a/src/main/java/me/cortex/zenith/client/core/model/TextureUtils.java +++ b/src/main/java/me/cortex/zenith/client/core/model/TextureUtils.java @@ -4,15 +4,27 @@ import java.util.Map; //Texturing utils to manipulate data from the model bakery public class TextureUtils { - //Returns if any pixels are not fully transparent or fully translucent - public static boolean hasAlpha(ColourDepthTextureData texture) { - for (int pixel : texture.colour()) { + //Returns a bitset of + public static int computeColourData(ColourDepthTextureData texture) { + final var colour = texture.colour(); + int bitset = 0b101; + for (int i = 0; i < colour.length && bitset != 0b010; i++) { + int pixel = colour[i]; int alpha = (pixel>>24)&0xFF; - if (alpha != 0 && alpha != 255) { - return true; - } + bitset |= (alpha != 0 && alpha != 255)?2:0;//Test if the pixel is translucent (has alpha) + bitset &= (alpha != 0)?3:7;// test if the pixel is not empty (assumes that if alpha is 0 it wasnt written to!!) FIXME: THIS MIGHT NOT BE CORRECT + bitset &= alpha != 255?6:7;// test if the pixel is anything but solid, (occlusion culling stuff) } - return false; + return bitset; + } + + //Returns the number of non pixels not written to + public static int getNonWrittenPixels(ColourDepthTextureData texture) { + int count = 0; + for (int pixel : texture.depth()) { + count += (((pixel>>8)&0xFFFFFF) == 0xFFFFFF)?1:0; + } + return count; } public static boolean isSolid(ColourDepthTextureData texture) { @@ -29,7 +41,7 @@ public class TextureUtils { public static final int DEPTH_MODE_MIN = 3; //Computes depth info based on written pixel data - public static int computeDepth(ColourDepthTextureData texture, int mode) { + public static float computeDepth(ColourDepthTextureData texture, int mode) { final var colourData = texture.colour(); final var depthData = texture.depth(); long a = 0; @@ -37,11 +49,14 @@ public class TextureUtils { if (mode == DEPTH_MODE_MIN) { a = Long.MAX_VALUE; } + if (mode == DEPTH_MODE_MAX) { + a = Long.MIN_VALUE; + } for (int i = 0; i < colourData.length; i++) { - if ((colourData[0]&0xFF)==0) { + if ((colourData[i]&0xFF)==0) { continue; } - int depth = depthData[0]>>>8; + int depth = depthData[i]>>>8; if (mode == DEPTH_MODE_AVG) { a++; b += depth; @@ -56,10 +71,22 @@ public class TextureUtils { if (a == 0) { return -1; } - return (int) (b/a); - } else if (mode == DEPTH_MODE_MAX || mode == DEPTH_MODE_MIN) { - return (int) a; + return u2fdepth((int) (b/a)); + } else if (mode == DEPTH_MODE_MAX) { + if (a == Long.MIN_VALUE) { + return -1; + } + return u2fdepth((int) a); + } else if (mode == DEPTH_MODE_MIN) { + if (a == Long.MAX_VALUE) { + return -1; + } + return u2fdepth((int) a); } throw new IllegalArgumentException(); } + + private static float u2fdepth(int depth) { + return (((float)depth)/(float)(1<<24)); + } } diff --git a/src/main/java/me/cortex/zenith/client/core/rendering/Gl46FarWorldRenderer.java b/src/main/java/me/cortex/zenith/client/core/rendering/Gl46FarWorldRenderer.java index cf9f5bb7..86f6c7b5 100644 --- a/src/main/java/me/cortex/zenith/client/core/rendering/Gl46FarWorldRenderer.java +++ b/src/main/java/me/cortex/zenith/client/core/rendering/Gl46FarWorldRenderer.java @@ -1,6 +1,5 @@ package me.cortex.zenith.client.core.rendering; -import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; import me.cortex.zenith.client.core.gl.GlBuffer; import me.cortex.zenith.client.core.gl.shader.Shader; @@ -13,7 +12,6 @@ import net.minecraft.client.util.math.MatrixStack; import org.joml.Matrix4f; import org.joml.Vector3f; import org.lwjgl.opengl.GL11C; -import org.lwjgl.opengl.GL45C; import org.lwjgl.system.MemoryUtil; import java.util.List; @@ -31,7 +29,6 @@ import static org.lwjgl.opengl.GL42.GL_FRAMEBUFFER_BARRIER_BIT; import static org.lwjgl.opengl.GL43.*; import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER; import static org.lwjgl.opengl.GL45C.glClearNamedBufferData; -import static org.lwjgl.opengl.NVRepresentativeFragmentTest.GL_REPRESENTATIVE_FRAGMENT_TEST_NV; public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer { private final Shader commandGen = Shader.make() @@ -82,7 +79,7 @@ public class Gl46FarWorldRenderer extends AbstractFarWorldRenderer { return; } - //this.getModelManager().updateEntry(this.frameId%(1<<15), Blocks.STONE.getDefaultState()); + //this.getModelManager().addEntry(this.frameId%(1<<15), Blocks.OAK_BUTTON.getDefaultState()); RenderLayer.getCutoutMipped().startDrawing(); int oldActiveTexture = glGetInteger(GL_ACTIVE_TEXTURE); diff --git a/src/main/resources/assets/zenith/shaders/bakery/position_tex.fsh b/src/main/resources/assets/zenith/shaders/bakery/position_tex.fsh new file mode 100644 index 00000000..340a9f9c --- /dev/null +++ b/src/main/resources/assets/zenith/shaders/bakery/position_tex.fsh @@ -0,0 +1,8 @@ +#version 430 + +layout(location=0) uniform sampler2D tex; +in vec2 texCoord; +out vec4 colour; +void main() { + colour = texture(tex, texCoord); +} diff --git a/src/main/resources/assets/zenith/shaders/bakery/position_tex.vsh b/src/main/resources/assets/zenith/shaders/bakery/position_tex.vsh new file mode 100644 index 00000000..5429d546 --- /dev/null +++ b/src/main/resources/assets/zenith/shaders/bakery/position_tex.vsh @@ -0,0 +1,12 @@ +#version 430 + +in vec3 pos; +in vec2 uv; + +layout(location=1) uniform mat4 transform; +out vec2 texCoord; + +void main() { + gl_Position = transform * vec4(pos, 1.0); + texCoord = uv; +}