diff --git a/src/main/java/me/cortex/voxy/client/core/VoxyRenderSystem.java b/src/main/java/me/cortex/voxy/client/core/VoxyRenderSystem.java index 65ae5373..62e8fe7c 100644 --- a/src/main/java/me/cortex/voxy/client/core/VoxyRenderSystem.java +++ b/src/main/java/me/cortex/voxy/client/core/VoxyRenderSystem.java @@ -1,11 +1,13 @@ package me.cortex.voxy.client.core; import com.mojang.blaze3d.opengl.GlConst; +import com.mojang.blaze3d.opengl.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; import me.cortex.voxy.client.TimingStatistics; import me.cortex.voxy.client.config.VoxyConfig; import me.cortex.voxy.client.core.gl.Capabilities; import me.cortex.voxy.client.core.gl.GlBuffer; +import me.cortex.voxy.client.core.gl.GlTexture; import me.cortex.voxy.client.core.model.ModelBakerySubsystem; import me.cortex.voxy.client.core.rendering.ChunkBoundRenderer; import me.cortex.voxy.client.core.rendering.RenderDistanceTracker; @@ -42,6 +44,7 @@ import java.util.concurrent.atomic.AtomicInteger; import static org.lwjgl.opengl.GL11C.*; import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER_BINDING; import static org.lwjgl.opengl.GL30C.glBindFramebuffer; +import static org.lwjgl.opengl.GL33.glBindSampler; public class VoxyRenderSystem { private final RenderService renderer; @@ -217,11 +220,27 @@ public class VoxyRenderSystem { TimingStatistics.postDynamic.stop(); glBindFramebuffer(GlConst.GL_FRAMEBUFFER, oldFB); + + {//Reset state manager stuffs + GlStateManager._glBindVertexArray(0);//Clear binding + + GlStateManager._activeTexture(GlConst.GL_TEXTURE0); + GlStateManager._bindTexture(0); + glBindSampler(0, 0); + + GlStateManager._activeTexture(GlConst.GL_TEXTURE1); + GlStateManager._bindTexture(0); + glBindSampler(1, 0); + + GlStateManager._activeTexture(GlConst.GL_TEXTURE2); + GlStateManager._bindTexture(0); + glBindSampler(2, 0); + } TimingStatistics.all.stop(); } public void addDebugInfo(List debug) { - debug.add("GlBuffer, Count/Size (mb): " + GlBuffer.getCount() + "/" + (GlBuffer.getTotalSize()/1_000_000)); + debug.add("Buf/Tex [#/Mb]: [" + GlBuffer.getCount() + "/" + (GlBuffer.getTotalSize()/1_000_000) + "],[" + GlTexture.getCount() + "/" + (GlTexture.getEstimatedTotalSize()/1_000_000)+"]"); this.renderer.addDebugData(debug); { TimingStatistics.update(); diff --git a/src/main/java/me/cortex/voxy/client/core/gl/Capabilities.java b/src/main/java/me/cortex/voxy/client/core/gl/Capabilities.java index b3a93fc2..cb825a3f 100644 --- a/src/main/java/me/cortex/voxy/client/core/gl/Capabilities.java +++ b/src/main/java/me/cortex/voxy/client/core/gl/Capabilities.java @@ -7,6 +7,7 @@ import org.lwjgl.opengl.GL20C; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL32.glGetInteger64; import static org.lwjgl.opengl.GL43C.GL_MAX_SHADER_STORAGE_BLOCK_SIZE; +import static org.lwjgl.opengl.NVXGPUMemoryInfo.*; public class Capabilities { @@ -16,9 +17,13 @@ public class Capabilities { public final boolean INT64_t; public final long ssboMaxSize; public final boolean isMesa; + public final boolean canQueryGpuMemory; + public final long totalDedicatedMemory;//Bytes, dedicated memory + public final long totalDynamicMemory;//Bytes, total allocation memory - dedicated memory public Capabilities() { var cap = GL.getCapabilities(); this.meshShaders = cap.GL_NV_mesh_shader && cap.GL_NV_representative_fragment_test; + this.canQueryGpuMemory = cap.GL_NVX_gpu_memory_info; //this.INT64_t = cap.GL_ARB_gpu_shader_int64 || cap.GL_AMD_gpu_shader_int64; //The only reliable way to test for int64 support is to try compile a shader this.INT64_t = testShaderCompilesOk(ShaderType.COMPUTE, """ @@ -33,6 +38,14 @@ public class Capabilities { this.ssboMaxSize = glGetInteger64(GL_MAX_SHADER_STORAGE_BLOCK_SIZE); this.isMesa = glGetString(GL_VERSION).toLowerCase().contains("mesa"); + + if (this.canQueryGpuMemory) { + this.totalDedicatedMemory = glGetInteger64(GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX)*1024;//Since its in Kb + this.totalDynamicMemory = (glGetInteger64(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX)*1024) - this.totalDedicatedMemory;//Since its in Kb + } else { + this.totalDedicatedMemory = -1; + this.totalDynamicMemory = -1; + } } public static void init() { @@ -47,4 +60,13 @@ public class Capabilities { return result == GL20C.GL_TRUE; } + + public long getFreeDedicatedGpuMemory() { + if (!this.canQueryGpuMemory) { + throw new IllegalStateException("Cannot query gpu memory, missing extension"); + } + return glGetInteger64(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX)*1024;//Since its in Kb + } + + //TODO: add gpu eviction tracking } 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 36a70eba..c9a2e9d0 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 @@ -4,8 +4,9 @@ import me.cortex.voxy.common.util.TrackedObject; import static org.lwjgl.opengl.ARBFramebufferObject.glDeleteFramebuffers; import static org.lwjgl.opengl.ARBFramebufferObject.glGenFramebuffers; +import static org.lwjgl.opengl.GL11.GL_RGBA8; import static org.lwjgl.opengl.GL11C.*; -import static org.lwjgl.opengl.GL30C.glGetIntegeri; +import static org.lwjgl.opengl.GL30.GL_DEPTH24_STENCIL8; import static org.lwjgl.opengl.GL45C.*; public class GlTexture extends TrackedObject { @@ -14,7 +15,12 @@ public class GlTexture extends TrackedObject { private int format; private int width; private int height; - private int layers; + private int levels; + private boolean hasAllocated; + + private static int COUNT; + private static long ESTIMATED_TOTAL_SIZE; + public GlTexture() { this(GL_TEXTURE_2D); } @@ -22,6 +28,7 @@ public class GlTexture extends TrackedObject { public GlTexture(int type) { this.id = glCreateTextures(type); this.type = type; + COUNT++; } private GlTexture(int type, boolean useGenTypes) { @@ -31,22 +38,30 @@ public class GlTexture extends TrackedObject { this.id = glCreateTextures(type); } this.type = type; + COUNT++; } public GlTexture store(int format, int levels, int width, int height) { + if (this.hasAllocated) { + throw new IllegalStateException("Texture already allocated"); + } + this.hasAllocated = true; + 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; + this.levels = levels; } else { throw new IllegalStateException("Unknown texture type"); } + ESTIMATED_TOTAL_SIZE += this.getEstimatedSize(); return this; } public GlTexture createView() { + this.assertAllocated(); var view = new GlTexture(this.type, true); glTextureView(view.id, this.type, this.id, this.format, 0, 1, 0, 1); return view; @@ -54,23 +69,63 @@ public class GlTexture extends TrackedObject { @Override public void free() { + if (this.hasAllocated) { + ESTIMATED_TOTAL_SIZE -= this.getEstimatedSize(); + } + COUNT--; + this.hasAllocated = false; super.free0(); glDeleteTextures(this.id); } public GlTexture name(String name) { + this.assertAllocated(); return GlDebug.name(name, this); } public int getWidth() { + this.assertAllocated(); return this.width; } public int getHeight() { + this.assertAllocated(); return this.height; } - public int getLayers() { - return this.layers; + public int getLevels() { + this.assertAllocated(); + return this.levels; + } + + private long getEstimatedSize() { + this.assertAllocated(); + long elemSize = switch (this.format) { + case GL_RGBA8, GL_DEPTH24_STENCIL8 -> 4; + case GL_DEPTH_COMPONENT24 -> 4;//TODO: check this is right???? + + default -> throw new IllegalStateException("Unknown element size"); + }; + + long size = 0; + for (int lvl = 0; lvl < this.levels; lvl++) { + size += Math.max((((long)this.width)>>lvl), 1) * Math.max((((long)this.height)>>lvl), 1) * elemSize; + } + return size; + } + + public void assertAllocated() { + if (!this.hasAllocated) { + throw new IllegalStateException("Texture not yet allocated"); + } + } + + + public static int getCount() { + return COUNT; + } + + public static long getEstimatedTotalSize() { + return ESTIMATED_TOTAL_SIZE; } } diff --git a/src/main/java/me/cortex/voxy/client/core/gl/shader/AutoBindingShader.java b/src/main/java/me/cortex/voxy/client/core/gl/shader/AutoBindingShader.java index 228fb48b..ce2d69a7 100644 --- a/src/main/java/me/cortex/voxy/client/core/gl/shader/AutoBindingShader.java +++ b/src/main/java/me/cortex/voxy/client/core/gl/shader/AutoBindingShader.java @@ -1,18 +1,14 @@ package me.cortex.voxy.client.core.gl.shader; -import com.mojang.blaze3d.opengl.GlConst; -import com.mojang.blaze3d.opengl.GlStateManager; import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlDebug; import me.cortex.voxy.client.core.gl.GlTexture; -import me.cortex.voxy.common.util.Pair; import java.util.ArrayList; import java.util.List; import java.util.Map; import static org.lwjgl.opengl.ARBDirectStateAccess.glBindTextureUnit; -import static org.lwjgl.opengl.GL11.glBindTexture; import static org.lwjgl.opengl.GL30.glBindBufferBase; import static org.lwjgl.opengl.GL30.glBindBufferRange; import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER; @@ -117,8 +113,6 @@ public class AutoBindingShader extends Shader { for (var binding : this.textureBindings) { if (binding.texture != null) { binding.texture.assertNotFreed(); - GlStateManager._activeTexture(GlConst.GL_TEXTURE0+binding.unit); - GlStateManager._bindTexture(0); glBindTextureUnit(binding.unit, binding.texture.id); } if (binding.sampler != -1) { diff --git a/src/main/java/me/cortex/voxy/client/core/model/bakery/BudgetBufferRenderer.java b/src/main/java/me/cortex/voxy/client/core/model/bakery/BudgetBufferRenderer.java index 3182585e..b0ce813b 100644 --- a/src/main/java/me/cortex/voxy/client/core/model/bakery/BudgetBufferRenderer.java +++ b/src/main/java/me/cortex/voxy/client/core/model/bakery/BudgetBufferRenderer.java @@ -1,10 +1,5 @@ package me.cortex.voxy.client.core.model.bakery; -import com.mojang.blaze3d.buffers.GpuBuffer; -import com.mojang.blaze3d.opengl.GlConst; -import com.mojang.blaze3d.opengl.GlStateManager; -import com.mojang.blaze3d.pipeline.RenderPipeline; -import com.mojang.blaze3d.platform.DepthTestFunction; -import com.mojang.blaze3d.systems.RenderPass; + import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.textures.GpuTexture; import com.mojang.blaze3d.vertex.VertexFormat; @@ -12,22 +7,14 @@ import me.cortex.voxy.client.core.gl.GlBuffer; import me.cortex.voxy.client.core.gl.GlVertexArray; 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.SharedIndexBuffer; import me.cortex.voxy.client.core.rendering.util.UploadStream; -import me.cortex.voxy.common.util.UnsafeUtil; -import net.minecraft.client.gl.*; +import net.minecraft.client.gl.GlGpuBuffer; import net.minecraft.client.render.BuiltBuffer; -import net.minecraft.client.render.VertexFormats; -import net.minecraft.util.Identifier; import org.joml.Matrix4f; import org.lwjgl.system.MemoryUtil; -import static org.lwjgl.opengl.ARBVertexArrayObject.glBindVertexArray; -import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER; -import static org.lwjgl.opengl.GL15.glBindBuffer; import static org.lwjgl.opengl.GL20.glUniformMatrix4fv; import static org.lwjgl.opengl.GL33.glBindSampler; -import static org.lwjgl.opengl.GL43.glBindVertexBuffer; import static org.lwjgl.opengl.GL45.*; public class BudgetBufferRenderer { @@ -82,9 +69,6 @@ public class BudgetBufferRenderer { throw new IllegalStateException(); } - GlStateManager._activeTexture(GlConst.GL_TEXTURE0); - GlStateManager._bindTexture(0); - quadCount = quads; long size = quads * 4L * STRIDE; @@ -101,6 +85,7 @@ public class BudgetBufferRenderer { bakeryShader.bind(); VA.bind(); + glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT); glBindSampler(0, 0); glBindTextureUnit(0, texId); } 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 33f6c9a1..7a72232b 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 @@ -21,8 +21,7 @@ import net.minecraft.world.chunk.light.LightingProvider; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; -import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_FRAMEBUFFER_BARRIER_BIT; -import static org.lwjgl.opengl.ARBShaderImageLoadStore.glMemoryBarrier; +import static org.lwjgl.opengl.ARBShaderImageLoadStore.*; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL14C.glBlendFuncSeparate; import static org.lwjgl.opengl.GL30.*; @@ -268,7 +267,7 @@ public class ModelTextureBakery { glDisable(GL_BLEND); //Finish and download - glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT); + glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT|GL_TEXTURE_UPDATE_BARRIER_BIT|GL_PIXEL_BUFFER_BARRIER_BIT|GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);//Am not sure if barriers are right this.capture.emitToStream(streamBuffer, streamOffset); glBindFramebuffer(GL_FRAMEBUFFER, this.capture.framebuffer.id); diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java b/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java index a45baa49..915d0fd3 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/RenderService.java @@ -39,15 +39,27 @@ public class RenderService, J extends Vi private final WorldEngine world; + private static long getGeometryBufferSize() { + long geometryCapacity = Math.min((1L<<(64-Long.numberOfLeadingZeros(Capabilities.INSTANCE.ssboMaxSize-1)))<<1, 1L<<32)-1024/*(1L<<32)-1024*/; + //Limit to available dedicated memory if possible + if (Capabilities.INSTANCE.canQueryGpuMemory) { + //512mb less than avalible, + long limit = Capabilities.INSTANCE.getFreeDedicatedGpuMemory() - 512*1024*1024; + // Give a minimum of 512 mb requirement + limit = Math.max(512*1024*1024, limit); + + geometryCapacity = Math.min(geometryCapacity, limit); + } + //geometryCapacity = 1<<24; + return geometryCapacity; + } + @SuppressWarnings("unchecked") public RenderService(WorldEngine world, ServiceThreadPool serviceThreadPool) { this.world = world; this.modelService = new ModelBakerySubsystem(world.getMapper()); - //Max geometry: 1 gb - long geometryCapacity = Math.min((1L<<(64-Long.numberOfLeadingZeros(Capabilities.INSTANCE.ssboMaxSize-1)))<<1, 1L<<32)-1024/*(1L<<32)-1024*/; - //geometryCapacity = 1<<24; - + long geometryCapacity = getGeometryBufferSize(); this.geometryData = (Q) new BasicSectionGeometryData(1<<20, geometryCapacity); //Max sections: ~500k @@ -102,7 +114,6 @@ public class RenderService, J extends Vi this.sectionRenderer.renderOpaque(viewport, depthBoundTexture); - //NOTE: need to do the upload and download tick here, after the section renderer renders the world, to ensure "stable" // sections diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/AsyncNodeManager.java b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/AsyncNodeManager.java index a7792f88..93e9e161 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/AsyncNodeManager.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/hierachical/AsyncNodeManager.java @@ -130,21 +130,21 @@ public class AsyncNodeManager { private void run() { if (this.workCounter.get() == 0) { LockSupport.park(); - if (this.workCounter.get() == 0) {//No work + if (this.workCounter.get() == 0 || !this.running) {//No work return; } + //This is a funny thing, wait a bit, this allows for better batching, but this thread is independent of everything else so waiting a bit should be mostly ok + try { + Thread.sleep(25); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } } if (!this.running) { return; } - //This is a funny thing, wait a bit, this allows for better batching, but this thread is independent of everything else so waiting a bit should be mostly ok - try { - Thread.sleep(10); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } int workDone = 0; @@ -370,8 +370,14 @@ public class AsyncNodeManager { int val = iter.nextInt(); int placeId = results.geometryIdUpdateMap.putIfAbsent(val, results.geometryIdUpdateMap.size()); placeId = placeId==-1?results.geometryIdUpdateMap.size()-1:placeId; - if (512<=placeId) { - throw new IllegalStateException("Outside range of allowed updates"); + if (results.geometryIdUpdateData.size<=placeId*32L) { + //We need to expand the buffer :( + var old = results.geometryIdUpdateData; + var newBuffer = new MemoryBuffer((long) (old.size*1.5)); + Logger.info("Expanding geometry update buffer to " + newBuffer.size); + old.cpyTo(newBuffer.address); + old.free(); + results.geometryIdUpdateData = newBuffer; } //Write updated data this.geometryManager.writeMetadata(val, placeId*32L + results.geometryIdUpdateData.address); @@ -388,8 +394,14 @@ public class AsyncNodeManager { int val = iter.nextInt(); int placeId = results.nodeIdUpdateMap.putIfAbsent(val, results.nodeIdUpdateMap.size()); placeId = placeId==-1?results.nodeIdUpdateMap.size()-1:placeId; - if (1024<=placeId) { - throw new IllegalStateException("Outside range of allowed updates"); + if (results.nodeIdUpdateData.size<=placeId*16L) { + //We need to expand the buffer :( + var old = results.nodeIdUpdateData; + var newBuffer = new MemoryBuffer((long) (old.size*1.5)); + Logger.info("Expanding node update buffer to " + newBuffer.size); + old.cpyTo(newBuffer.address); + old.free(); + results.nodeIdUpdateData = newBuffer; } //Write updated data this.manager.writeNode(val, placeId*16L + results.nodeIdUpdateData.address); @@ -633,7 +645,7 @@ public class AsyncNodeManager { //Node id updates + size private final Int2IntOpenHashMap nodeIdUpdateMap = new Int2IntOpenHashMap();//node id to update data location - private final MemoryBuffer nodeIdUpdateData = new MemoryBuffer(8192*2);//capacity for 1024 entries, TODO: ADD RESIZE + private MemoryBuffer nodeIdUpdateData = new MemoryBuffer(8192*2);//capacity for 1024 entries, TODO: ADD RESIZE private int currentMaxNodeId;// the id of the ending of the node ids //TLN add/rem @@ -643,7 +655,7 @@ public class AsyncNodeManager { private int geometrySectionCount; private final Int2ObjectOpenHashMap geometryUploads = new Int2ObjectOpenHashMap<>(); private final Int2IntOpenHashMap geometryIdUpdateMap = new Int2IntOpenHashMap();//geometry id to update data location - private final MemoryBuffer geometryIdUpdateData = new MemoryBuffer(8192*2);//capacity for 512 entries, TODO: ADD RESIZE + private MemoryBuffer geometryIdUpdateData = new MemoryBuffer(8192*2);//capacity for 512 entries, TODO: ADD RESIZE public SyncResults() { diff --git a/src/main/resources/assets/voxy/shaders/bakery/position_tex.fsh b/src/main/resources/assets/voxy/shaders/bakery/position_tex.fsh index 28c679d4..b55b592f 100644 --- a/src/main/resources/assets/voxy/shaders/bakery/position_tex.fsh +++ b/src/main/resources/assets/voxy/shaders/bakery/position_tex.fsh @@ -7,7 +7,7 @@ out vec4 colour; void main() { colour = texture(tex, texCoord); - if (colour.a < 0.0001f && ((metadata&1u)!=0)) { + if (colour.a < 0.001f && ((metadata&1u)!=0)) { discard; } }