From d12a0f1ba49f9fa3028e060cc863dc5985009dcc Mon Sep 17 00:00:00 2001 From: mcrcortex <18544518+MCRcortex@users.noreply.github.com> Date: Thu, 1 May 2025 11:20:55 +1000 Subject: [PATCH] work on chunk outline renderer --- .../cortex/voxy/client/core/gl/GlTexture.java | 18 +++++ .../core/model/bakery/ModelTextureBakery.java | 1 + .../core/rendering/ChunkBoundRenderer.java | 75 +++++++++++++++++-- .../sodium/MixinRenderSectionManager.java | 11 +++ .../voxy/shaders/chunkoutline/outline.fsh | 4 + .../voxy/shaders/chunkoutline/outline.vsh | 21 ++++++ 6 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/main/resources/assets/voxy/shaders/chunkoutline/outline.fsh create mode 100644 src/main/resources/assets/voxy/shaders/chunkoutline/outline.vsh diff --git a/src/main/java/me/cortex/voxy/client/core/gl/GlTexture.java b/src/main/java/me/cortex/voxy/client/core/gl/GlTexture.java index 7eb7eeda..36a70eba 100644 --- a/src/main/java/me/cortex/voxy/client/core/gl/GlTexture.java +++ b/src/main/java/me/cortex/voxy/client/core/gl/GlTexture.java @@ -12,6 +12,9 @@ public class GlTexture extends TrackedObject { public final int id; private final int type; private int format; + private int width; + private int height; + private int layers; public GlTexture() { this(GL_TEXTURE_2D); } @@ -34,6 +37,9 @@ public class GlTexture extends TrackedObject { this.format = format; if (this.type == GL_TEXTURE_2D) { glTextureStorage2D(this.id, levels, format, width, height); + this.width = width; + this.height = height; + this.layers = layers; } else { throw new IllegalStateException("Unknown texture type"); } @@ -55,4 +61,16 @@ public class GlTexture extends TrackedObject { public GlTexture name(String name) { return GlDebug.name(name, this); } + + public int getWidth() { + return this.width; + } + + public int getHeight() { + return this.height; + } + + public int getLayers() { + return this.layers; + } } diff --git a/src/main/java/me/cortex/voxy/client/core/model/bakery/ModelTextureBakery.java b/src/main/java/me/cortex/voxy/client/core/model/bakery/ModelTextureBakery.java index 32da0cd6..4a365eea 100644 --- a/src/main/java/me/cortex/voxy/client/core/model/bakery/ModelTextureBakery.java +++ b/src/main/java/me/cortex/voxy/client/core/model/bakery/ModelTextureBakery.java @@ -111,6 +111,7 @@ public class ModelTextureBakery { glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id); + //TODO: make use glClearNamedFramebuffer* to reduce/remove state change glClearColor(0,0,0,0); glClearDepth(1); glClearStencil(0); diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/ChunkBoundRenderer.java b/src/main/java/me/cortex/voxy/client/core/rendering/ChunkBoundRenderer.java index 3381a106..3e7f1a45 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/ChunkBoundRenderer.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/ChunkBoundRenderer.java @@ -3,19 +3,30 @@ package me.cortex.voxy.client.core.rendering; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import me.cortex.voxy.client.core.gl.GlBuffer; +import me.cortex.voxy.client.core.gl.GlFramebuffer; +import me.cortex.voxy.client.core.gl.GlTexture; +import me.cortex.voxy.client.core.gl.shader.Shader; +import me.cortex.voxy.client.core.gl.shader.ShaderType; import me.cortex.voxy.client.core.rendering.util.UploadStream; +import net.minecraft.util.math.MathHelper; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.joml.Vector3i; import org.lwjgl.system.MemoryUtil; +import static me.cortex.voxy.client.core.rendering.PrintfDebugUtil.PRINTF_processor; import static org.lwjgl.opengl.GL11.GL_TRIANGLES; import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER; import static org.lwjgl.opengl.GL15.glBindBuffer; import static org.lwjgl.opengl.GL30.glBindBufferBase; import static org.lwjgl.opengl.GL30.glBindVertexArray; +import static org.lwjgl.opengl.GL30C.*; import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER; import static org.lwjgl.opengl.GL31.glDrawElementsInstanced; import static org.lwjgl.opengl.GL40C.GL_DRAW_INDIRECT_BUFFER; import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER; +import static org.lwjgl.opengl.GL45.glClearNamedFramebufferfv; //This is a render subsystem, its very simple in what it does // it renders an AABB around loaded chunks, thats it @@ -25,12 +36,24 @@ public class ChunkBoundRenderer { private final GlBuffer uniformBuffer = new GlBuffer(128); private final Long2IntOpenHashMap chunk2idx = new Long2IntOpenHashMap(MAX_CHUNK_COUNT); private final long[] idx2chunk = new long[MAX_CHUNK_COUNT]; + private final Shader rasterShader = Shader.makeAuto() + .add(ShaderType.VERTEX, "voxy:chunkoutline/outline.vsh") + .add(ShaderType.FRAGMENT, "voxy:chunkoutline/outline.fsh") + .compile() + .ssbo(0, this.uniformBuffer) + .ssbo(1, this.chunkPosBuffer); + + private GlTexture depthBuffer = new GlTexture().store(GL_DEPTH_COMPONENT24, 1, 128, 128); + private final GlFramebuffer frameBuffer = new GlFramebuffer().bind(GL_DEPTH_ATTACHMENT, this.depthBuffer).verify(); public ChunkBoundRenderer() { this.chunk2idx.defaultReturnValue(-1); } public void addChunk(long pos) { + if (this.chunk2idx.size() >= MAX_CHUNK_COUNT) { + throw new IllegalStateException("At capacity"); + } if (this.chunk2idx.containsKey(pos)) { throw new IllegalArgumentException("Chunk already in map"); } @@ -75,22 +98,62 @@ public class ChunkBoundRenderer { //Bind and render, changing as little gl state as possible so that the caller may configure how it wants to render public void render(Viewport viewport) { + if (this.depthBuffer.getWidth() != viewport.width || this.depthBuffer.getHeight() != viewport.height) { + this.depthBuffer.free(); + this.depthBuffer = new GlTexture().store(GL_DEPTH_COMPONENT24, 1, viewport.width, viewport.height); + this.frameBuffer.bind(GL_DEPTH_ATTACHMENT, this.depthBuffer).verify(); + } + long ptr = UploadStream.INSTANCE.upload(this.uniformBuffer, 0, 128); - viewport.MVP.getToAddress(ptr); ptr += 4*4*4; - viewport.section.getToAddress(ptr); ptr += 4*4; - viewport.innerTranslation.getToAddress(ptr); ptr += 4*4; + long matPtr = ptr; ptr += 4*4*4; + {//This is recomputed to be in chunk section space not worldsection + int sx = MathHelper.floor(viewport.cameraX) >> 4; + int sy = MathHelper.floor(viewport.cameraY) >> 4; + int sz = MathHelper.floor(viewport.cameraZ) >> 4; + new Vector3i(sx, sy, sz).getToAddress(ptr); ptr += 4*4; + + viewport.MVP.translate( + -(float) (viewport.cameraX - (sx << 4)), + -(float) (viewport.cameraY - (sy << 4)), + -(float) (viewport.cameraZ - (sz << 4)), + new Matrix4f()).getToAddress(matPtr); + } UploadStream.INSTANCE.commit(); + //TODO: NOTE: need to reverse the winding order since we want the back faces of the AABB, not the front - //this.cullShader.bind(); + { + glFrontFace(GL_CW);//Reverse winding order + + //"reverse depth buffer" it goes from 0->1 where 1 is far away + glClearNamedFramebufferfv(this.frameBuffer.id, GL_DEPTH, 0, new float[]{0}); + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_GREATER); + } + glBindVertexArray(RenderService.STATIC_VAO); - glBindBufferBase(GL_UNIFORM_BUFFER, 0, this.uniformBuffer.id); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, this.chunkPosBuffer.id); + glBindFramebuffer(GL_FRAMEBUFFER, this.frameBuffer.id); + this.rasterShader.bind(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, SharedIndexBuffer.INSTANCE.id()); glDrawElementsInstanced(GL_TRIANGLES, 6*2*3, GL_UNSIGNED_BYTE, SharedIndexBuffer.CUBE_INDEX_OFFSET, this.chunk2idx.size()); + + { + glFrontFace(GL_CCW);//Restore winding order + + glDepthFunc(GL_LEQUAL); + + //TODO: check this is correct + glEnable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + } } public void free() { + this.depthBuffer.free(); + this.frameBuffer.free(); + + this.rasterShader.free(); this.uniformBuffer.free(); this.chunkPosBuffer.free(); } diff --git a/src/main/java/me/cortex/voxy/client/mixin/sodium/MixinRenderSectionManager.java b/src/main/java/me/cortex/voxy/client/mixin/sodium/MixinRenderSectionManager.java index bcda2c46..300df3ef 100644 --- a/src/main/java/me/cortex/voxy/client/mixin/sodium/MixinRenderSectionManager.java +++ b/src/main/java/me/cortex/voxy/client/mixin/sodium/MixinRenderSectionManager.java @@ -2,6 +2,7 @@ package me.cortex.voxy.client.mixin.sodium; import me.cortex.voxy.client.VoxyClientInstance; import me.cortex.voxy.client.config.VoxyConfig; +import me.cortex.voxy.client.core.IGetVoxyRenderSystem; import me.cortex.voxy.commonImpl.VoxyCommon; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionManager; import net.minecraft.client.world.ClientWorld; @@ -18,12 +19,22 @@ public class MixinRenderSectionManager { @Inject(method = "onChunkAdded", at = @At("HEAD")) private void voxy$trackChunkAdd(int x, int z, CallbackInfo ci) { + if (this.level.worldRenderer != null) { + var system = ((IGetVoxyRenderSystem)(this.level.worldRenderer)).getVoxyRenderSystem(); + if (system != null) { + } + } } @Inject(method = "onChunkRemoved", at = @At("HEAD")) private void voxy$trackChunkRemove(int x, int z, CallbackInfo ci) { + if (this.level.worldRenderer != null) { + var system = ((IGetVoxyRenderSystem)(this.level.worldRenderer)).getVoxyRenderSystem(); + if (system != null) { + } + } } @Inject(method = "onChunkRemoved", at = @At("HEAD")) diff --git a/src/main/resources/assets/voxy/shaders/chunkoutline/outline.fsh b/src/main/resources/assets/voxy/shaders/chunkoutline/outline.fsh new file mode 100644 index 00000000..ef733610 --- /dev/null +++ b/src/main/resources/assets/voxy/shaders/chunkoutline/outline.fsh @@ -0,0 +1,4 @@ +#version 460 core +layout(early_fragment_tests) in; + +void main() {} \ No newline at end of file diff --git a/src/main/resources/assets/voxy/shaders/chunkoutline/outline.vsh b/src/main/resources/assets/voxy/shaders/chunkoutline/outline.vsh new file mode 100644 index 00000000..74a96791 --- /dev/null +++ b/src/main/resources/assets/voxy/shaders/chunkoutline/outline.vsh @@ -0,0 +1,21 @@ +#version 450 + +layout(location = 0) out vec2 uv; +layout(binding = 0, std140) uniform SceneUniform { + mat4 MVP; + ivec4 section; +}; + +layout(binding = 1, std430) restrict readonly buffer ChunkPosBuffer { + ivec2[] chunkPos; +}; + +void main() { + ivec3 cubeCornerI = ivec3(gl_VertexID&1, (gl_VertexID>>2)&1, (gl_VertexID>>1)&1); + cubeCornerI.xz += chunkPos[gl_InstanceID]; + //Expand the y height to be big (will be +- 8192) + //TODO: make it W.R.T world height and offsets + cubeCornerI.y = cubeCornerI.y*1024-512; + cubeCornerI -= section.xyz; + gl_Position = MVP * vec4(vec3(cubeCornerI*16), 1); +} \ No newline at end of file