From 133dad09c10c6218fb82110b67e9a390e868cb6a Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:39:42 +0000 Subject: [PATCH 01/21] Depth Buffer & TiledLighting MinMaxing ### SceneFBO Changes * SceneOpaqueDepth buffer switched from RenderBuffer to Texture2DMSAA * Format changed from 32F to 24 * SceneAlphaDepth added, format set to 16 since precision isn't needed for depth testing ### Depth Shader Program * DepthBias encoded into last 8 bits of the textureFaceIdx, to avoid needing to sample the texture buffer to resolve it * FastDepth is utilized without a fragment shader attached ### Zone Renderer * Clearing of the fboScene has been moved to its own pass * depthprePass added * Renders Opaque Depth to the sceneFBO * Renders Alpha Depth into a separate fbo for sampling only * zone & VAO are added into a new command buffer for drawing into opaque & alpha depth buffers separately optimised calculateTileMinMax Sampling corners & midpoints is sufficently accurate, which reduces depth taps from 16x16x2 to just 3x3x2 Optimised LightZ Vs MinMax by removing sqrt Also optimised distanceScore by using squared values instead of linear Sync Simplify tile depth check Moved Tile Depth check to be before all the cone culling checks we want 3x3x2 not 4x4x2 --- src/main/java/rs117/hd/HdPlugin.java | 132 ++++++++++++-- .../java/rs117/hd/config/DynamicLights.java | 1 - .../hd/opengl/shader/DepthShaderProgram.java | 18 ++ .../hd/opengl/shader/SceneShaderProgram.java | 6 + .../shader/TiledLightingShaderProgram.java | 8 + .../rs117/hd/opengl/uniforms/UBOGlobal.java | 2 + src/main/java/rs117/hd/overlays/Timer.java | 1 + .../rs117/hd/renderer/zone/SceneUploader.java | 30 ++-- .../hd/renderer/zone/VertexWriteCache.java | 10 +- .../java/rs117/hd/renderer/zone/Zone.java | 8 +- .../rs117/hd/renderer/zone/ZoneRenderer.java | 161 ++++++++++++++---- .../java/rs117/hd/utils/CommandBuffer.java | 72 ++++++-- .../rs117/hd/utils/buffer/GpuIntBuffer.java | 4 +- src/main/resources/rs117/hd/depth_vert.glsl | 26 +++ src/main/resources/rs117/hd/scene_frag.glsl | 14 ++ src/main/resources/rs117/hd/scene_vert.glsl | 14 +- src/main/resources/rs117/hd/shadow_vert.glsl | 7 +- .../rs117/hd/tiled_lighting_frag.glsl | 88 ++++++++-- .../resources/rs117/hd/uniforms/global.glsl | 2 + .../resources/rs117/hd/utils/constants.glsl | 1 + src/main/resources/rs117/hd/utils/misc.glsl | 15 ++ 21 files changed, 510 insertions(+), 110 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java create mode 100644 src/main/resources/rs117/hd/depth_vert.glsl diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 044e1ff81f..8d1bf868e8 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -163,6 +163,8 @@ public class HdPlugin extends Plugin { public static final int TEXTURE_UNIT_SHADOW_MAP = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; public static final int TEXTURE_UNIT_TILE_HEIGHT_MAP = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; public static final int TEXTURE_UNIT_TILED_LIGHTING_MAP = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_SCENE_OPAQUE_DEPTH = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_SCENE_ALPHA_DEPTH = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; public static int MAX_IMAGE_UNITS; public static int IMAGE_UNIT_COUNT = 0; @@ -365,10 +367,13 @@ public class HdPlugin extends Plugin { public int[] sceneResolution; public int fboScene; + public int fboSceneDepth; + public int fboSceneAlphaDepth; private int rboSceneColor; - private int rboSceneDepth; public int fboSceneResolve; private int rboSceneResolveColor; + private int texSceneDepth; + private int texSceneAlphaDepth; public int shadowMapResolution; public int fboShadowMap; @@ -504,8 +509,10 @@ protected void startUp() { return false; fboScene = 0; + fboSceneDepth = 0; rboSceneColor = 0; - rboSceneDepth = 0; + texSceneDepth = 0; + texSceneAlphaDepth = 0; fboSceneResolve = 0; rboSceneResolveColor = 0; fboShadowMap = 0; @@ -852,6 +859,7 @@ public ShaderIncludes getShaderIncludes() { var includes = new ShaderIncludes() .addIncludePath(SHADER_PATH) .addInclude("VERSION_HEADER", OSType.getOSType() == OSType.Linux ? LINUX_VERSION_HEADER : WINDOWS_VERSION_HEADER) + .define("MSAA_SAMPLES", config.antiAliasingMode().getSamples()) .define("UI_SCALING_MODE", config.uiScalingMode()) .define("COLOR_BLINDNESS", config.colorBlindness()) .define("APPLY_COLOR_FILTER", configColorFilter != ColorFilter.NONE) @@ -1293,13 +1301,6 @@ public void updateSceneFbo() { glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboSceneColor); checkGLErrors(); - // Create depth render buffer - rboSceneDepth = glGenRenderbuffers(); - glBindRenderbuffer(GL_RENDERBUFFER, rboSceneDepth); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaaSamples, GL_DEPTH_COMPONENT32F, sceneResolution[0], sceneResolution[1]); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboSceneDepth); - checkGLErrors(); - // If necessary, create an FBO for resolving multisampling if (msaaSamples > 1 && resolutionScale != 1) { fboSceneResolve = glGenFramebuffers(); @@ -1311,9 +1312,101 @@ public void updateSceneFbo() { checkGLErrors(); } + int sceneDepthTarget = msaaSamples > 1 ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; + texSceneDepth = glGenTextures(); + glActiveTexture(TEXTURE_UNIT_SCENE_OPAQUE_DEPTH); + glBindTexture(sceneDepthTarget, texSceneDepth); + if(msaaSamples > 1) { + glTexImage2DMultisample( + GL_TEXTURE_2D_MULTISAMPLE, + msaaSamples, + GL_DEPTH_COMPONENT24, + sceneResolution[0], + sceneResolution[1], + true + ); + } else { + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_DEPTH_COMPONENT24, + sceneResolution[0], + sceneResolution[1], + 0, + GL_DEPTH_COMPONENT, + GL_FLOAT, + 0 + ); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + sceneDepthTarget, + texSceneDepth, + 0 + ); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + throw new RuntimeException("Scene FBO incomplete"); + + fboSceneDepth = glGenFramebuffers(); + glBindFramebuffer(GL_FRAMEBUFFER, fboSceneDepth); + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + sceneDepthTarget, + texSceneDepth, + 0 + ); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + throw new RuntimeException("Opaque depth FBO incomplete"); + + texSceneAlphaDepth = glGenTextures(); + glActiveTexture(TEXTURE_UNIT_SCENE_ALPHA_DEPTH); + glBindTexture(GL_TEXTURE_2D, texSceneAlphaDepth); + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_DEPTH_COMPONENT16, + sceneResolution[0], + sceneResolution[1], + 0, + GL_DEPTH_COMPONENT, + GL_FLOAT, + 0 + ); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + fboSceneAlphaDepth = glGenFramebuffers(); + glBindFramebuffer(GL_FRAMEBUFFER, fboSceneAlphaDepth); + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + GL_TEXTURE_2D, + texSceneAlphaDepth, + 0 + ); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + throw new RuntimeException("Alpha depth FBO incomplete"); + // Reset - glBindFramebuffer(GL_FRAMEBUFFER, awtContext.getFramebuffer(false)); + glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); glBindRenderbuffer(GL_RENDERBUFFER, 0); + glActiveTexture(TEXTURE_UNIT_UI); + glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); } private void destroySceneFbo() { @@ -1323,13 +1416,21 @@ private void destroySceneFbo() { glDeleteFramebuffers(fboScene); fboScene = 0; + if(fboSceneDepth != 0) + glDeleteRenderbuffers(fboSceneDepth); + fboSceneDepth = 0; + if (rboSceneColor != 0) glDeleteRenderbuffers(rboSceneColor); rboSceneColor = 0; - if (rboSceneDepth != 0) - glDeleteRenderbuffers(rboSceneDepth); - rboSceneDepth = 0; + if (texSceneDepth != 0) + glDeleteTextures(texSceneDepth); + texSceneDepth = 0; + + if(texSceneAlphaDepth != 0) + glDeleteTextures(texSceneAlphaDepth); + texSceneAlphaDepth = 0; if (fboSceneResolve != 0) glDeleteFramebuffers(fboSceneResolve); @@ -1544,8 +1645,8 @@ public void drawUi(int overlayColor) { } /** - * Convert the front framebuffer to an Image - */ + * Convert the front framebuffer to an Image + */ public Image screenshot() { if (uiResolution == null) return null; @@ -1769,6 +1870,7 @@ public void processPendingConfigChanges() { case KEY_ANTI_ALIASING_MODE: case KEY_SCENE_RESOLUTION_SCALE: recreateSceneFbo = true; + recompilePrograms = true; break; case KEY_SHADOW_MODE: case KEY_SHADOW_TRANSPARENCY: diff --git a/src/main/java/rs117/hd/config/DynamicLights.java b/src/main/java/rs117/hd/config/DynamicLights.java index 12c0da3ea2..c7d7394854 100644 --- a/src/main/java/rs117/hd/config/DynamicLights.java +++ b/src/main/java/rs117/hd/config/DynamicLights.java @@ -43,7 +43,6 @@ public enum DynamicLights static { int max = 0; for (var e : values()) { - assert e.tiledLightingLayers % 4 == 0; // Needs to be divisible by 4 max = max(max, e.tiledLightingLayers); } MAX_LAYERS_PER_TILE = max; diff --git a/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java new file mode 100644 index 0000000000..1286cd8dfc --- /dev/null +++ b/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java @@ -0,0 +1,18 @@ +package rs117.hd.opengl.shader; + +import static org.lwjgl.opengl.GL20C.GL_VERTEX_SHADER; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; + +public class DepthShaderProgram extends ShaderProgram { + protected final UniformTexture uniTextureFaces = addUniformTexture("textureFaces"); + + public DepthShaderProgram() { + super(t -> t + .add(GL_VERTEX_SHADER, "depth_vert.glsl")); + } + + @Override + protected void initialize() { + uniTextureFaces.set(TEXTURE_UNIT_TEXTURED_FACES); + } +} diff --git a/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java index 1c75140e78..e6d970c2a7 100644 --- a/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java @@ -2,6 +2,8 @@ import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPlugin.TEXTURE_UNIT_GAME; +import static rs117.hd.HdPlugin.TEXTURE_UNIT_SCENE_ALPHA_DEPTH; +import static rs117.hd.HdPlugin.TEXTURE_UNIT_SCENE_OPAQUE_DEPTH; import static rs117.hd.HdPlugin.TEXTURE_UNIT_SHADOW_MAP; import static rs117.hd.HdPlugin.TEXTURE_UNIT_TILED_LIGHTING_MAP; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; @@ -11,6 +13,8 @@ public class SceneShaderProgram extends ShaderProgram { protected final UniformTexture uniShadowMap = addUniformTexture("shadowMap"); protected final UniformTexture uniTiledLightingTextureArray = addUniformTexture("tiledLightingArray"); protected final UniformTexture uniTextureFaces = addUniformTexture("textureFaces"); + protected final UniformTexture uniSceneOpaqueDepth = addUniformTexture("sceneOpaqueDepth"); + protected final UniformTexture uniSceneAlphaDepth = addUniformTexture("sceneAlphaDepth"); public SceneShaderProgram() { super(t -> t @@ -25,6 +29,8 @@ protected void initialize() { uniShadowMap.set(TEXTURE_UNIT_SHADOW_MAP); uniTiledLightingTextureArray.set(TEXTURE_UNIT_TILED_LIGHTING_MAP); uniTextureFaces.set(TEXTURE_UNIT_TEXTURED_FACES); + uniSceneOpaqueDepth.set(TEXTURE_UNIT_SCENE_OPAQUE_DEPTH); + uniSceneAlphaDepth.set(TEXTURE_UNIT_SCENE_ALPHA_DEPTH); } public static class Legacy extends SceneShaderProgram { diff --git a/src/main/java/rs117/hd/opengl/shader/TiledLightingShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/TiledLightingShaderProgram.java index b0cda8f703..1fae86d728 100644 --- a/src/main/java/rs117/hd/opengl/shader/TiledLightingShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/TiledLightingShaderProgram.java @@ -2,12 +2,17 @@ import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPlugin.IMAGE_UNIT_TILED_LIGHTING; +import static rs117.hd.HdPlugin.TEXTURE_UNIT_SCENE_ALPHA_DEPTH; +import static rs117.hd.HdPlugin.TEXTURE_UNIT_SCENE_OPAQUE_DEPTH; import static rs117.hd.HdPlugin.TEXTURE_UNIT_TILED_LIGHTING_MAP; public class TiledLightingShaderProgram extends ShaderProgram { private final UniformTexture uniTiledLightingTextureArray = addUniformTexture("tiledLightingArray"); private final UniformImage uniTiledLightingTextureStore = addUniformImage("tiledLightingImage"); + private final UniformTexture uniSceneOpaqueDepth = addUniformTexture("sceneOpaqueDepth"); + private final UniformTexture uniSceneAlphaDepth = addUniformTexture("sceneAlphaDepth"); + public TiledLightingShaderProgram() { super(t -> t .add(GL_VERTEX_SHADER, "tiled_lighting_vert.glsl") @@ -20,5 +25,8 @@ public TiledLightingShaderProgram() { protected void initialize() { uniTiledLightingTextureArray.set(TEXTURE_UNIT_TILED_LIGHTING_MAP); uniTiledLightingTextureStore.set(IMAGE_UNIT_TILED_LIGHTING); + + uniSceneOpaqueDepth.set(TEXTURE_UNIT_SCENE_OPAQUE_DEPTH); + uniSceneAlphaDepth.set(TEXTURE_UNIT_SCENE_ALPHA_DEPTH); } } diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java b/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java index 1343819ecc..5578123307 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java @@ -56,6 +56,8 @@ public void initialize() { public Property pointLightsCount = addProperty(PropertyType.Int, "pointLightsCount"); public Property cameraPos = addProperty(PropertyType.FVec3, "cameraPos"); + public Property cameraNear = addProperty(PropertyType.Float, "cameraNear"); + public Property cameraFar = addProperty(PropertyType.Float, "cameraFar"); public Property viewMatrix = addProperty(PropertyType.Mat4, "viewMatrix"); public Property projectionMatrix = addProperty(PropertyType.Mat4, "projectionMatrix"); public Property invProjectionMatrix = addProperty(PropertyType.Mat4, "invProjectionMatrix"); diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 5f998a8651..3d46645c0d 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -68,6 +68,7 @@ public enum Timer { COMPUTE(GPU_TIMER), UNMAP_ROOT_CTX(GPU_TIMER), CLEAR_SCENE(GPU_TIMER), + RENDER_DEPTH_PRE_PASS(GPU_TIMER), RENDER_SHADOWS(GPU_TIMER), RENDER_SCENE(GPU_TIMER), RENDER_UI(GPU_TIMER, "Render UI"), diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index 0e801fb2ca..c24a1ac5c7 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -1004,21 +1004,21 @@ private void uploadTilePaint( lx2, neHeight, lz2, uvx, uvy, 0, neNormals[0], neNormals[2], neNormals[1], - texturedFaceIdx + texturedFaceIdx, 0 ); vb.putVertex( lx3, nwHeight, lz3, uvx - uvcos, uvy - uvsin, 0, nwNormals[0], nwNormals[2], nwNormals[1], - texturedFaceIdx + texturedFaceIdx, 0 ); vb.putVertex( lx1, seHeight, lz1, uvx + uvsin, uvy - uvcos, 0, seNormals[0], seNormals[2], seNormals[1], - texturedFaceIdx + texturedFaceIdx, 0 ); texturedFaceIdx = fb.putFace( @@ -1031,21 +1031,21 @@ private void uploadTilePaint( lx0, swHeight, lz0, uvx - uvcos + uvsin, uvy - uvsin - uvcos, 0, swNormals[0], swNormals[2], swNormals[1], - texturedFaceIdx + texturedFaceIdx, 0 ); vb.putVertex( lx1, seHeight, lz1, uvx + uvsin, uvy - uvcos, 0, seNormals[0], seNormals[2], seNormals[1], - texturedFaceIdx + texturedFaceIdx, 0 ); vb.putVertex( lx3, nwHeight, lz3, uvx - uvcos, uvy - uvsin, 0, nwNormals[0], nwNormals[2], nwNormals[1], - texturedFaceIdx + texturedFaceIdx, 0 ); } @@ -1315,21 +1315,21 @@ private void uploadTileModel( lx0, ly0, lz0, uvAx, uvAy, 0, normalsA[0], normalsA[2], normalsA[1], - texturedFaceIdx + texturedFaceIdx, 0 ); vb.putVertex( lx1, ly1, lz1, uvBx, uvBy, 0, normalsB[0], normalsB[2], normalsB[1], - texturedFaceIdx + texturedFaceIdx, 0 ); vb.putVertex( lx2, ly2, lz2, uvCx, uvCy, 0, normalsC[0], normalsC[2], normalsC[1], - texturedFaceIdx + texturedFaceIdx, 0 ); } } @@ -1671,21 +1671,21 @@ private int uploadStaticModel( vx1, vy1, vz1, faceUVs[0], faceUVs[1], faceUVs[2], modelNormals[0], modelNormals[1], modelNormals[2], - texturedFaceIdx + texturedFaceIdx, depthBias ); vb.putStaticVertex( vx2, vy2, vz2, faceUVs[4], faceUVs[5], faceUVs[6], modelNormals[3], modelNormals[4], modelNormals[5], - texturedFaceIdx + texturedFaceIdx, depthBias ); vb.putStaticVertex( vx3, vy3, vz3, faceUVs[8], faceUVs[9], faceUVs[10], modelNormals[6], modelNormals[7], modelNormals[8], - texturedFaceIdx + texturedFaceIdx, depthBias ); len += 3; } @@ -2090,19 +2090,19 @@ else if (color3 == -1) modelLocalI[vertexOffsetA], modelLocalI[vertexOffsetA + 1], modelLocalI[vertexOffsetA + 2], faceUVs[0], faceUVs[1], faceUVs[2], faceNormals[0], faceNormals[1], faceNormals[2], - texturedFaceIdx + texturedFaceIdx, depthBias ); vb.putVertex( modelLocalI[vertexOffsetB], modelLocalI[vertexOffsetB + 1], modelLocalI[vertexOffsetB + 2], faceUVs[4], faceUVs[5], faceUVs[6], faceNormals[3], faceNormals[4], faceNormals[5], - texturedFaceIdx + texturedFaceIdx, depthBias ); vb.putVertex( modelLocalI[vertexOffsetC], modelLocalI[vertexOffsetC + 1], modelLocalI[vertexOffsetC + 2], faceUVs[8], faceUVs[9], faceUVs[10], faceNormals[6], faceNormals[7], faceNormals[8], - texturedFaceIdx + texturedFaceIdx, depthBias ); } diff --git a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java index 828d11e4f0..76084baa83 100644 --- a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java +++ b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java @@ -71,7 +71,7 @@ public void putVertex( int x, int y, int z, float u, float v, float w, int nx, int ny, int nz, - int textureFaceIdx + int textureFaceIdx, int depth ) { if (stagingPosition + 7 > stagingBuffer.length) flushAndGrow(); @@ -79,13 +79,15 @@ public void putVertex( final int[] stagingBuffer = this.stagingBuffer; final int stagingPosition = this.stagingPosition; + assert textureFaceIdx < 0xFFFFFF; + stagingBuffer[stagingPosition] = x; stagingBuffer[stagingPosition + 1] = y; stagingBuffer[stagingPosition + 2] = z; stagingBuffer[stagingPosition + 3] = float16(v) << 16 | float16(u); stagingBuffer[stagingPosition + 4] = (nx & 0xFFFF) << 16 | float16(w); stagingBuffer[stagingPosition + 5] = (nz & 0xFFFF) << 16 | ny & 0xFFFF; - stagingBuffer[stagingPosition + 6] = textureFaceIdx; + stagingBuffer[stagingPosition + 6] = (depth & 0xFF) << 24 | (textureFaceIdx & 0xFFFFFF); this.stagingPosition += 7; } @@ -94,7 +96,7 @@ public void putStaticVertex( int x, int y, int z, float u, float v, float w, int nx, int ny, int nz, - int textureFaceIdx + int textureFaceIdx, int depth ) { if (stagingPosition + 6 > stagingBuffer.length) flushAndGrow(); @@ -108,7 +110,7 @@ public void putStaticVertex( // Unnormalized normals, assumed to be within short max stagingBuffer[stagingPosition + 3] = (ny & 0xFFFF) << 16 | nx & 0xFFFF; stagingBuffer[stagingPosition + 4] = nz & 0xFFFF; - stagingBuffer[stagingPosition + 5] = textureFaceIdx; + stagingBuffer[stagingPosition + 5] = (depth & 0xFF) << 24 | (textureFaceIdx & 0xFFFFFF); this.stagingPosition += 6; } diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 9b4b0ec1ad..dfbc57f4c3 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -815,7 +815,7 @@ void renderAlpha( int zz, int level, WorldViewContext ctx, - boolean isShadowPass, + boolean isScenePass, boolean includeRoof ) { if (alphaModels.isEmpty()) @@ -832,8 +832,6 @@ void renderAlpha( drawIdx = 0; - cmd.DepthMask(false); - boolean shouldQueueUpload = false; for (int i = 0; i < alphaModels.size(); i++) { final AlphaModel m = alphaModels.get(i); @@ -859,7 +857,7 @@ void renderAlpha( continue; } - if (isShadowPass || m.asyncSortIdx < 0) { + if (!isScenePass || m.asyncSortIdx < 0) { lastDrawMode = STATIC_UNSORTED; pushRange(m.startpos, m.endpos); continue; @@ -890,8 +888,6 @@ void renderAlpha( } flush(cmd); - - cmd.DepthMask(true); } private void flush(CommandBuffer cmd) { diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index d9efbc7fd2..10cc8537a1 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -41,6 +41,7 @@ import rs117.hd.config.ColorFilter; import rs117.hd.config.DynamicLights; import rs117.hd.config.ShadowMode; +import rs117.hd.opengl.shader.DepthShaderProgram; import rs117.hd.opengl.shader.SceneShaderProgram; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; @@ -121,6 +122,9 @@ public class ZoneRenderer implements Renderer { @Inject private FrameTimer frameTimer; + @Inject + private DepthShaderProgram depthProgram; + @Inject private SceneShaderProgram sceneProgram; @@ -141,9 +145,11 @@ public class ZoneRenderer implements Renderer { public final ShadowCasterVolume directionalShadowCasterVolume = new ShadowCasterVolume(directionalCamera); public final RenderState renderState = new RenderState(); + public final CommandBuffer tempCmd = new CommandBuffer("Temp", renderState); + public final CommandBuffer depthOpaqueCmd = new CommandBuffer("Depth Opaque", renderState); + public final CommandBuffer depthAlphaCmd = new CommandBuffer("Depth Alpha", renderState); public final CommandBuffer sceneCmd = new CommandBuffer("Scene", renderState); public final CommandBuffer directionalCmd = new CommandBuffer("Directional", renderState); - public final CommandBuffer playerCmd = new CommandBuffer("Player", renderState); private GLBuffer indirectDrawCmds; public static GpuIntBuffer indirectDrawCmdsStaging; @@ -176,6 +182,8 @@ public void initialize() { SceneUploader.POOL = new ConcurrentPool<>(plugin.getInjector(), SceneUploader.class); FacePrioritySorter.POOL = new ConcurrentPool<>(plugin.getInjector(), FacePrioritySorter.class); + depthOpaqueCmd.setFrameTimer(frameTimer); + depthAlphaCmd.setFrameTimer(frameTimer); sceneCmd.setFrameTimer(frameTimer); directionalCmd.setFrameTimer(frameTimer); @@ -218,6 +226,7 @@ public void addShaderIncludes(ShaderIncludes includes) { @Override public void initializeShaders(ShaderIncludes includes) throws ShaderException, IOException { + depthProgram.compile(includes); sceneProgram.compile(includes); fastShadowProgram.compile(includes); detailedShadowProgram.compile(includes); @@ -225,6 +234,7 @@ public void initializeShaders(ShaderIncludes includes) throws ShaderException, I @Override public void destroyShaders() { + depthProgram.destroy(); sceneProgram.destroy(); fastShadowProgram.destroy(); detailedShadowProgram.destroy(); @@ -303,6 +313,9 @@ public void preSceneDraw( ctx.sortStaticAlphaModels(sceneCamera); + // Depth PrePass means opaque should only draw if its equal to the value stored in the mask + sceneCmd.DepthFunc(GL_EQUAL); + ctx.map(); frameTimer.end(Timer.DRAW_PRESCENE); } @@ -448,6 +461,8 @@ private void preSceneDrawTopLevel( } plugin.uboGlobal.cameraPos.set(plugin.cameraPosition); + plugin.uboGlobal.cameraNear.set(sceneCamera.getNearPlane()); + plugin.uboGlobal.cameraFar.set(sceneCamera.getFarPlane()); plugin.uboGlobal.viewMatrix.set(plugin.viewMatrix); plugin.uboGlobal.projectionMatrix.set(plugin.viewProjMatrix); plugin.uboGlobal.invProjectionMatrix.set(plugin.invViewProjMatrix); @@ -579,6 +594,8 @@ private void preSceneDrawTopLevel( // Reset buffers for the next frame indirectDrawCmdsStaging.clear(); + depthOpaqueCmd.reset(); + depthAlphaCmd.reset(); sceneCmd.reset(); directionalCmd.reset(); renderState.reset(); @@ -650,6 +667,73 @@ private void postDrawTopLevel() { checkGLErrors(); } + private void clearScene() { + renderState.framebuffer.set(GL_DRAW_FRAMEBUFFER, plugin.fboScene); + if (plugin.msaaSamples > 1) { + renderState.enable.set(GL_MULTISAMPLE); + } else { + renderState.disable.set(GL_MULTISAMPLE); + } + renderState.viewport.set(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); + renderState.ido.set(indirectDrawCmds.id); + renderState.apply(); + + // Clear scene + frameTimer.begin(Timer.CLEAR_SCENE); + + float[] fogColor = ColorUtils.linearToSrgb(environmentManager.currentFogColor); + float[] gammaCorrectedFogColor = pow(fogColor, plugin.getGammaCorrection()); + glClearColor( + gammaCorrectedFogColor[0], + gammaCorrectedFogColor[1], + gammaCorrectedFogColor[2], + 1f + ); + glClearDepth(0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + frameTimer.end(Timer.CLEAR_SCENE); + } + + private void depthPrePass() { + depthProgram.use(); + + frameTimer.begin(Timer.DRAW_SCENE); + + renderState.framebuffer.set(GL_DRAW_FRAMEBUFFER, plugin.fboSceneDepth); + renderState.viewport.set(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); + renderState.ido.set(indirectDrawCmds.id); + + renderState.enable.set(GL_CULL_FACE); + renderState.enable.set(GL_DEPTH_TEST); + renderState.depthMask.set(true); + renderState.colorMask.set(false, false, false, false); + + renderState.apply(); + + glClearDepth(0.0); + glClear(GL_DEPTH_BUFFER_BIT); + + frameTimer.begin(Timer.RENDER_DEPTH_PRE_PASS); + + depthOpaqueCmd.execute(); + + renderState.framebuffer.set(GL_DRAW_FRAMEBUFFER, plugin.fboSceneAlphaDepth); + renderState.framebuffer.apply(); + + glClearDepth(0.0); + glClear(GL_DEPTH_BUFFER_BIT); + + depthAlphaCmd.execute(); + + frameTimer.end(Timer.RENDER_DEPTH_PRE_PASS); + renderState.disable.set(GL_CULL_FACE); + renderState.disable.set(GL_DEPTH_TEST); + renderState.colorMask.set(true, true, true, true); + renderState.apply(); + + frameTimer.end(Timer.DRAW_SCENE); + } + private void tiledLightingPass() { if (!plugin.configTiledLighting || plugin.configDynamicLights == DynamicLights.NONE) return; @@ -703,9 +787,7 @@ private void directionalShadowPass() { renderState.disable.set(GL_CULL_FACE); renderState.depthFunc.set(GL_LEQUAL); - CommandBuffer.SKIP_DEPTH_MASKING = true; directionalCmd.execute(); - CommandBuffer.SKIP_DEPTH_MASKING = false; renderState.disable.set(GL_DEPTH_TEST); @@ -726,27 +808,11 @@ private void scenePass() { renderState.ido.set(indirectDrawCmds.id); renderState.apply(); - // Clear scene - frameTimer.begin(Timer.CLEAR_SCENE); - - float[] fogColor = ColorUtils.linearToSrgb(environmentManager.currentFogColor); - float[] gammaCorrectedFogColor = pow(fogColor, plugin.getGammaCorrection()); - glClearColor( - gammaCorrectedFogColor[0], - gammaCorrectedFogColor[1], - gammaCorrectedFogColor[2], - 1f - ); - glClearDepth(0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - frameTimer.end(Timer.CLEAR_SCENE); - frameTimer.begin(Timer.RENDER_SCENE); renderState.enable.set(GL_BLEND); renderState.enable.set(GL_CULL_FACE); renderState.enable.set(GL_DEPTH_TEST); - renderState.depthFunc.set(GL_GEQUAL); renderState.blendFunc.set(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); // Render the scene @@ -841,8 +907,14 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int return; frameTimer.begin(Timer.DRAW_ZONE_OPAQUE); - if (!sceneManager.isRoot(ctx) || z.inSceneFrustum) - z.renderOpaque(sceneCmd, ctx, false); + if (!sceneManager.isRoot(ctx) || z.inSceneFrustum) { + z.renderOpaque(tempCmd, ctx, false); + + depthOpaqueCmd.append(tempCmd); + sceneCmd.append(tempCmd); + + tempCmd.reset(); + } final boolean isSquashed = ctx.uboWorldViewStruct != null && ctx.uboWorldViewStruct.isSquashed(); if (!isSquashed && (!sceneManager.isRoot(ctx) || z.inShadowFrustum)) { @@ -866,8 +938,14 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i frameTimer.begin(Timer.DRAW_ZONE_ALPHA); final boolean renderWater = z.inSceneFrustum && level == 0 && z.hasWater; - if (renderWater) - z.renderOpaqueLevel(sceneCmd, Zone.LEVEL_WATER_SURFACE); + if (renderWater) { + z.renderOpaqueLevel(tempCmd, Zone.LEVEL_WATER_SURFACE); + + depthAlphaCmd.append(tempCmd); + sceneCmd.append(tempCmd); + + tempCmd.reset(); + } modelStreamingManager.ensureAsyncUploadsComplete(z); @@ -881,11 +959,15 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i final boolean isSquashed = ctx.uboWorldViewStruct != null && ctx.uboWorldViewStruct.isSquashed(); if (!isSquashed && (!sceneManager.isRoot(ctx) || z.inShadowFrustum)) { directionalCmd.SetShader(plugin.configShadowMode == ShadowMode.DETAILED ? detailedShadowProgram : fastShadowProgram); - z.renderAlpha(directionalCmd, zx - offset, zz - offset, level, ctx, true, plugin.configRoofShadows); + z.renderAlpha(directionalCmd, zx - offset, zz - offset, level, ctx, false, plugin.configRoofShadows); } - if (!sceneManager.isRoot(ctx) || z.inSceneFrustum) - z.renderAlpha(sceneCmd, zx - offset, zz - offset, level, ctx, false, false); + if (!sceneManager.isRoot(ctx) || z.inSceneFrustum) { + sceneCmd.DepthMask(false); + z.renderAlpha(sceneCmd, zx - offset, zz - offset, level, ctx, true, false); + z.renderAlpha(depthAlphaCmd, zx - offset, zz - offset, level, ctx, false, false); + sceneCmd.DepthMask(true); + } } frameTimer.end(Timer.DRAW_ZONE_ALPHA); @@ -904,6 +986,9 @@ public void drawPass(Projection projection, Scene scene, int pass) { case DrawCallbacks.PASS_OPAQUE: directionalCmd.SetShader(fastShadowProgram); + // Switching to Alpha, so now it should only draw whilst greater than the depth value + sceneCmd.DepthFunc(GL_GREATER); + sceneCmd.ExecuteSubCommandBuffer(ctx.vaoSceneCmd); directionalCmd.ExecuteSubCommandBuffer(ctx.vaoDirectionalCmd); @@ -919,13 +1004,20 @@ public void drawPass(Projection projection, Scene scene, int pass) { if (sceneManager.isRoot(ctx)) frameTimer.end(Timer.UNMAP_ROOT_CTX); + // Equal for Dynamic Draw Opaque + ctx.vaoSceneCmd.DepthFunc(GL_EQUAL); + // Draw opaque + ctx.drawAll(VAO_OPAQUE, depthOpaqueCmd); ctx.drawAll(VAO_OPAQUE, ctx.vaoSceneCmd); ctx.drawAll(VAO_OPAQUE, ctx.vaoDirectionalCmd); // Draw shadow-only models ctx.drawAll(VAO_SHADOW, ctx.vaoDirectionalCmd); + // Greater for Players since they dont depth write during the pre-pass + ctx.vaoSceneCmd.DepthFunc(GL_GREATER); + final int offset = ctx.sceneContext.sceneOffset >> 3; for (int zx = 0; zx < ctx.sizeX; ++zx) { for (int zz = 0; zz < ctx.sizeZ; ++zz) { @@ -934,23 +1026,26 @@ public void drawPass(Projection projection, Scene scene, int pass) { if (!z.playerModels.isEmpty() && (!sceneManager.isRoot(ctx) || z.inSceneFrustum || z.inShadowFrustum)) { z.playerSort(zx - offset, zz - offset, sceneCamera); - z.renderPlayers(playerCmd, zx - offset, zz - offset); + z.renderPlayers(tempCmd, zx - offset, zz - offset); - if (!playerCmd.isEmpty()) { + if (!tempCmd.isEmpty()) { // Draw players shadow, with depth writes & alpha - ctx.vaoDirectionalCmd.append(playerCmd); + ctx.vaoDirectionalCmd.append(tempCmd); + + // Treat players as alpha depth + depthAlphaCmd.append(tempCmd); ctx.vaoSceneCmd.DepthMask(false); - ctx.vaoSceneCmd.append(playerCmd); + ctx.vaoSceneCmd.append(tempCmd); ctx.vaoSceneCmd.DepthMask(true); // Draw players opaque, writing only depth ctx.vaoSceneCmd.ColorMask(false, false, false, false); - ctx.vaoSceneCmd.append(playerCmd); + ctx.vaoSceneCmd.append(tempCmd); ctx.vaoSceneCmd.ColorMask(true, true, true, true); } - playerCmd.reset(); + tempCmd.reset(); } } } @@ -1020,6 +1115,8 @@ public void draw(int overlayColor) { frameTimer.begin(Timer.DRAW_SUBMIT); if (shouldRenderScene) { + clearScene(); + depthPrePass(); tiledLightingPass(); directionalShadowPass(); scenePass(); diff --git a/src/main/java/rs117/hd/utils/CommandBuffer.java b/src/main/java/rs117/hd/utils/CommandBuffer.java index 410711c79b..fa24651dfd 100644 --- a/src/main/java/rs117/hd/utils/CommandBuffer.java +++ b/src/main/java/rs117/hd/utils/CommandBuffer.java @@ -6,6 +6,7 @@ import java.util.stream.Collectors; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.lwjgl.opengl.*; import org.lwjgl.system.MemoryStack; import rs117.hd.opengl.GLFence; import rs117.hd.opengl.shader.ShaderProgram; @@ -21,8 +22,6 @@ @Slf4j public class CommandBuffer { - public static boolean SKIP_DEPTH_MASKING; - private static final int GL_MULTI_DRAW_ARRAYS_TYPE = 0; private static final int GL_MULTI_DRAW_ARRAYS_INDIRECT_TYPE = 1; private static final int GL_DRAW_ARRAYS_TYPE = 2; @@ -36,13 +35,18 @@ public class CommandBuffer { private static final int GL_BIND_INDIRECT_ARRAY_TYPE = 8; private static final int GL_BIND_TEXTURE_UNIT_TYPE = 9; private static final int GL_DEPTH_MASK_TYPE = 10; - private static final int GL_COLOR_MASK_TYPE = 11; - private static final int GL_USE_PROGRAM = 12; + private static final int GL_DEPTH_FUNC_TYPE = 11; + private static final int GL_COLOR_MASK_TYPE = 12; + private static final int GL_BEGIN_QUERY_TYPE = 13; + private static final int GL_END_QUERY_TYPE = 14; + private static final int GL_CONDITIONAL_RENDERING_BEGIN_TYPE = 15; + private static final int GL_CONDITIONAL_RENDERING_END_TYPE = 16; + private static final int GL_USE_PROGRAM = 17; - private static final int GL_TOGGLE_TYPE = 13; // Combined glEnable & glDisable - private static final int GL_FENCE_SYNC = 14; + private static final int GL_TOGGLE_TYPE = 18; // Combined glEnable & glDisable + private static final int GL_FENCE_SYNC = 19; - private static final int GL_EXECUTE_SUB_COMMAND_BUFFER = 15; + private static final int GL_EXECUTE_SUB_COMMAND_BUFFER = 20; private static final long INT_MASK = 0xFFFF_FFFFL; private static final int DRAW_MODE_MASK = 0xF; @@ -134,6 +138,11 @@ public void DepthMask(boolean writeDepth) { cmd[writeHead++] = GL_DEPTH_MASK_TYPE & 0xFF | (writeDepth ? 1 : 0) << 8; } + public void DepthFunc(int depth) { + ensureCapacity(1); + cmd[writeHead++] = GL_DEPTH_FUNC_TYPE & 0xFF | (long) depth << 8; + } + public void ColorMask(boolean writeRed, boolean writeGreen, boolean writeBlue, boolean writeAlpha) { ensureCapacity(1); cmd[writeHead++] = @@ -277,6 +286,26 @@ public void Toggle(int capability, boolean enabled) { cmd[writeHead++] = (enabled ? 1L : 0) << 32 | capability & INT_MASK; } + public void BeginQuery(int mode, int query) { + ensureCapacity(1); + cmd[writeHead++] = GL_BEGIN_QUERY_TYPE & 0xFF | (long) mode << 8 | (long) query << 32; + } + + public void EndQuery(int mode) { + ensureCapacity(1); + cmd[writeHead++] = GL_END_QUERY_TYPE & 0xFF | (long) mode << 8; + } + + public void BeginConditionalRender(int query, int mode) { + ensureCapacity(1); + cmd[writeHead++] = GL_CONDITIONAL_RENDERING_BEGIN_TYPE & 0xFF | (long) mode << 8 | (long) query << 32; + } + + public void EndConditionalRender() { + ensureCapacity(1); + cmd[writeHead++] = GL_CONDITIONAL_RENDERING_END_TYPE & 0xFF; + } + public void append(CommandBuffer other) { if (other.isEmpty()) return; @@ -301,10 +330,11 @@ public void execute() { switch (type) { case GL_DEPTH_MASK_TYPE: { - int state = (int) (data >> 8) & 1; - if (SKIP_DEPTH_MASKING) - continue; - renderState.depthMask.set(state == 1); + renderState.depthMask.set(((int) (data >> 8) & 1) == 1); + break; + } + case GL_DEPTH_FUNC_TYPE: { + renderState.depthFunc.set((int) (data >> 8)); break; } case GL_COLOR_MASK_TYPE: { @@ -315,6 +345,26 @@ public void execute() { renderState.colorMask.set(red, green, blue, alpha); break; } + case GL_BEGIN_QUERY_TYPE: { + int mode = (int) data >> 8; + int query = (int) (data >> 32); + glBeginQuery(mode, query); + break; + } + case GL_END_QUERY_TYPE: { + glEndQuery((int) data >> 8); + break; + } + case GL_CONDITIONAL_RENDERING_BEGIN_TYPE: { + int mode = (int) data >> 8; + int query = (int) (data >> 32); + glBeginConditionalRender(query, mode); + break; + } + case GL_CONDITIONAL_RENDERING_END_TYPE: { + glEndConditionalRender(); + break; + } case GL_BIND_VERTEX_ARRAY_TYPE: { renderState.vao.set((int) (data >> 8)); break; diff --git a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java b/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java index 74013548ce..e18868f283 100644 --- a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java @@ -127,7 +127,7 @@ public void putVertex( int x, int y, int z, float u, float v, float w, int nx, int ny, int nz, - int textureFaceIdx + int textureFaceIdx, int depth ) { buffer.put((y & 0xFFFF) << 16 | x & 0xFFFF); buffer.put(float16(u) << 16 | z & 0xFFFF); @@ -135,7 +135,7 @@ public void putVertex( // Unnormalized normals, assumed to be within short max buffer.put((ny & 0xFFFF) << 16 | nx & 0xFFFF); buffer.put(nz & 0xFFFF); - buffer.put(textureFaceIdx); + buffer.put((depth & 0xFF) << 24 | (textureFaceIdx & 0xFFFFFF)); } public static int putFace( diff --git a/src/main/resources/rs117/hd/depth_vert.glsl b/src/main/resources/rs117/hd/depth_vert.glsl new file mode 100644 index 0000000000..dc4af05a9b --- /dev/null +++ b/src/main/resources/rs117/hd/depth_vert.glsl @@ -0,0 +1,26 @@ +#version 330 + +#include +#include + +#include + +layout (location = 0) in vec3 vPosition; +layout (location = 3) in int vTextureFaceIdx; +layout (location = 6) in int vWorldViewId; +layout (location = 7) in ivec2 vSceneBase; + + void main() { + vec3 sceneOffset = vec3(vSceneBase.x, 0, vSceneBase.y); + vec3 worldPosition = sceneOffset + vPosition; + if (vWorldViewId != -1) { + mat4x3 worldViewProjection = mat4x3(getWorldViewProjection(vWorldViewId)); + worldPosition = worldViewProjection * vec4(worldPosition, 1.0); + } + + vec4 clipPosition = projectionMatrix * vec4(worldPosition, 1.0); + int depthBias = (vTextureFaceIdx >> 24) & 0xff; + clipPosition.z += depthBias * (1.0 / 128.0); + + gl_Position = clipPosition; +} \ No newline at end of file diff --git a/src/main/resources/rs117/hd/scene_frag.glsl b/src/main/resources/rs117/hd/scene_frag.glsl index bcc9ac06f0..d547221791 100644 --- a/src/main/resources/rs117/hd/scene_frag.glsl +++ b/src/main/resources/rs117/hd/scene_frag.glsl @@ -31,6 +31,7 @@ #define DISPLAY_TANGENT 0 #define DISPLAY_SHADOWS 0 #define DISPLAY_LIGHTING 0 +#define DISPLAY_DEPTH 0 #include #include @@ -43,6 +44,13 @@ uniform sampler2DArray textureArray; uniform sampler2D shadowMap; uniform usampler2DArray tiledLightingArray; +#if MSAA_SAMPLES > 0 + uniform sampler2DMS sceneOpaqueDepth; +#else + uniform sampler2D sceneOpaqueDepth; +#endif +uniform sampler2D sceneAlphaDepth; + // general HD settings flat in int fWorldViewId; @@ -116,6 +124,12 @@ void main() { vec4 outputColor = vec4(1); + #if DISPLAY_DEPTH + float depth = texelFetch(sceneAlphaDepth, ivec2(gl_FragCoord.xy), 0).r; + FragColor = vec4(depth, 0.0, 0.0, 1.0); + if (DISPLAY_DEPTH == 1) return; // Redundant, for syntax highlighting in IntelliJ + #endif + if (isWater) { outputColor = sampleWater(waterTypeIndex, viewDir); } else { diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index 616639e6ca..7615331b9a 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -68,15 +68,14 @@ layout (location = 2) in vec3 vNormal; int vertex = gl_VertexID % 3; bool isProvoking = vertex == 2; int materialData = 0; - int alphaBiasHsl = 0; + int textureFaceIdx = vTextureFaceIdx & 0xFFFFFF; if (isProvoking) { // Only the Provoking vertex needs to fetch the face data - fAlphaBiasHsl = texelFetch(textureFaces, vTextureFaceIdx).xyz; - fMaterialData = texelFetch(textureFaces, vTextureFaceIdx + 1).xyz; - fTerrainData = texelFetch(textureFaces, vTextureFaceIdx + 2).xyz; + fAlphaBiasHsl = texelFetch(textureFaces, textureFaceIdx).xyz; + fMaterialData = texelFetch(textureFaces, textureFaceIdx + 1).xyz; + fTerrainData = texelFetch(textureFaces, textureFaceIdx + 2).xyz; fWorldViewId = vWorldViewId; - alphaBiasHsl = fAlphaBiasHsl[vertex]; materialData = fMaterialData[vertex]; } else { // All outputs must be written to for macOS compatibility @@ -84,8 +83,7 @@ layout (location = 2) in vec3 vNormal; fMaterialData = ivec3(0); fTerrainData = ivec3(0); fWorldViewId = 0; - alphaBiasHsl = texelFetch(textureFaces, vTextureFaceIdx)[vertex]; - materialData = texelFetch(textureFaces, vTextureFaceIdx + 1)[vertex]; + materialData = texelFetch(textureFaces, textureFaceIdx + 1)[vertex]; } vec3 sceneOffset = vec3(vSceneBase.x, 0, vSceneBase.y); @@ -108,7 +106,7 @@ layout (location = 2) in vec3 vNormal; #endif vec4 clipPosition = projectionMatrix * vec4(worldPosition, 1.0); - int depthBias = (alphaBiasHsl >> 16) & 0xff; + int depthBias = (vTextureFaceIdx >> 24) & 0xff; clipPosition.z += depthBias / 128.0; gl_Position = clipPosition; diff --git a/src/main/resources/rs117/hd/shadow_vert.glsl b/src/main/resources/rs117/hd/shadow_vert.glsl index b31af77060..4174a93f53 100644 --- a/src/main/resources/rs117/hd/shadow_vert.glsl +++ b/src/main/resources/rs117/hd/shadow_vert.glsl @@ -52,9 +52,10 @@ layout (location = 1) in vec3 vUv; void main() { int vertex = gl_VertexID % 3; - int alphaBiasHsl = texelFetch(textureFaces, vTextureFaceIdx)[vertex]; - int materialData = texelFetch(textureFaces, vTextureFaceIdx + 1)[vertex]; - int terrainData = texelFetch(textureFaces, vTextureFaceIdx + 2)[vertex]; + int textureFaceIdx = vTextureFaceIdx & 0xFFFFFF; + int alphaBiasHsl = texelFetch(textureFaces, textureFaceIdx)[vertex]; + int materialData = texelFetch(textureFaces, textureFaceIdx + 1)[vertex]; + int terrainData = texelFetch(textureFaces, textureFaceIdx + 2)[vertex]; int waterTypeIndex = terrainData >> 3 & 0xFF; float opacity = 1 - (alphaBiasHsl >> 24 & 0xFF) / float(0xFF); diff --git a/src/main/resources/rs117/hd/tiled_lighting_frag.glsl b/src/main/resources/rs117/hd/tiled_lighting_frag.glsl index d0b26be2ba..dd95628f94 100644 --- a/src/main/resources/rs117/hd/tiled_lighting_frag.glsl +++ b/src/main/resources/rs117/hd/tiled_lighting_frag.glsl @@ -2,6 +2,7 @@ #include TILED_LIGHTING_LAYER #include TILED_IMAGE_STORE +#define TILE_MIN_MAX 1 #if TILED_IMAGE_STORE #extension GL_EXT_shader_image_load_store : enable @@ -12,11 +13,19 @@ out uvec4 TiledData; #endif +#include +#include + +#if MSAA_SAMPLES > 0 + uniform sampler2DMS sceneOpaqueDepth; +#else + uniform sampler2D sceneOpaqueDepth; +#endif +uniform sampler2D sceneAlphaDepth; + #include #include -#include - in vec2 fUv; #if TILED_IMAGE_STORE @@ -60,12 +69,47 @@ uint packLightIndices(in SortedLight bin[SORTING_BIN_SIZE], in int binSize, inou return (idx0 <= 32767) ? uint(idx0 & 0x7FFF) : 0u; } +bool calculateTileMinMax(vec2 bl, vec2 tr, out float tileMin, out float tileMax) { +#if TILE_MIN_MAX + ivec2 minPix = clamp(ivec2(floor(bl)), ivec2(0), ivec2(sceneResolution) - 1); + ivec2 maxPix = clamp(ivec2(ceil(tr)), ivec2(0), ivec2(sceneResolution)); + + // Instead of sampling 16x16x2, it's accurate enough just to sample the corners and mid points + float minDepth = 1e20; + float maxDepth = -1e20; + + float stepSize = 1.0f / 2.0f; + for(float x = 0; x <= 1.0f; x += stepSize) { + for(float y = 0; y <= 1.0f; y += stepSize) { + ivec2 pix = ivec2(int(mix(minPix.x, maxPix.x, x)), int(mix(minPix.y, maxPix.y, y))); + float depth = texelFetch(sceneOpaqueDepth, pix, 0).r; + if (depth > 0.0) { + minDepth = min(minDepth, depth); + maxDepth = max(maxDepth, depth); + } + + float alphaDepth = texelFetch(sceneAlphaDepth, pix, 0).r; + if (alphaDepth > 0.0) + maxDepth = max(maxDepth, alphaDepth); + } + } + + if(minDepth >= maxDepth) + return false; + + tileMin = depth01ToViewZ(minDepth, projectionMatrix); + tileMax = depth01ToViewZ(maxDepth, projectionMatrix); +#endif + return true; +} + + void main() { ivec2 pixelCoord = ivec2(fUv * tiledLightingResolution); #if USE_LIGHTS_MASK int LightMaskSize = int(ceil(pointLightsCount / 32.0)); - uint LightsMask[32]; // 32 Words = 1024 Lights + uint LightsMask[(TILED_LIGHTING_LAYER + 1) * 4]; // 32 Words = 1024 Lights for (int i = 0; i < LightMaskSize; i++) LightsMask[i] = 0u; @@ -100,6 +144,20 @@ void main() { vec2 tr = tileOrigin + tileSize; // top-right vec2 bl = tileOrigin; // bottom-left vec2 br = tileOrigin + vec2(tileSize.x, 0.0); // bottom-right + float tileMin = 0.0f; + float tileMax = 1.0f; + +#if TILE_MIN_MAX + if(!calculateTileMinMax(bl, tr, tileMin, tileMax)) { + #if TILED_IMAGE_STORE + for (int layer = 0; layer < TILED_LIGHTING_LAYER_COUNT; layer++) + imageStore(tiledLightingImage, ivec3(pixelCoord, layer), uvec4(0.0)); + #else + TiledData = uvec4(0.0); + #endif + return; + } +#endif vec2 ndcTL = (tl / sceneResolution) * 2.0 - 1.0; vec2 ndcTR = (tr / sceneResolution) * 2.0 - 1.0; @@ -130,16 +188,19 @@ void main() { vec3 lightViewPos = lightData.xyz; float lightRadiusSqr = lightData.w; - float lightDistSqr = dot(lightViewPos, lightViewPos); - - vec3 lightCenterVec = (lightDistSqr > 0.0) ? lightViewPos / sqrt(lightDistSqr) : vec3(0.0); + #if TILE_MIN_MAX + float dz = max(lightViewPos.z - tileMin, tileMax - lightViewPos.z); + if (dz > 0.0 && dz * dz > lightRadiusSqr) + continue; + #endif - float lightSinSqr = clamp(lightRadiusSqr / max(lightDistSqr, 1e-6), 0.0, 1.0); - float lightCos = sqrt(0.999 - lightSinSqr); - float lightTileCos = dot(lightCenterVec, tileCenterVec); + float lightTileDot = dot(lightViewPos, tileCenterVec); + if (lightTileDot <= 0.0) + continue; - float sumCos = (lightRadiusSqr > lightDistSqr) ? -1.0 : (tileCos * lightCos - tileSin * sqrt(lightSinSqr)); - if (lightTileCos < sumCos) + float lightDistSqr = dot(lightViewPos, lightViewPos); + float rhs = lightDistSqr * tileCos * tileCos - lightRadiusSqr; + if (lightTileDot * lightTileDot < rhs) continue; #if USE_LIGHTS_MASK @@ -150,8 +211,9 @@ void main() { #endif const float PROXIMITY_WEIGHT = 0.75; - float distanceScore = clamp(1.0 - sqrt(lightDistSqr) / (sqrt(lightRadiusSqr) + 1e-6), 0.0, 1.0); - float combinedScore = (lightTileCos * PROXIMITY_WEIGHT) + distanceScore * (1.0 - PROXIMITY_WEIGHT); + float distanceScore = clamp(1.0 - lightDistSqr / (lightRadiusSqr + 1e-6), 0.0, 1.0); + float angularScore = lightTileDot * lightTileDot / (lightDistSqr + 1e-6); + float combinedScore = (angularScore * PROXIMITY_WEIGHT) + distanceScore * (1.0 - PROXIMITY_WEIGHT); int idx = 0; for (; idx < sortingBinSize; idx++) { diff --git a/src/main/resources/rs117/hd/uniforms/global.glsl b/src/main/resources/rs117/hd/uniforms/global.glsl index a50e951d99..280a527edc 100644 --- a/src/main/resources/rs117/hd/uniforms/global.glsl +++ b/src/main/resources/rs117/hd/uniforms/global.glsl @@ -45,6 +45,8 @@ layout(std140) uniform UBOGlobal { int pointLightsCount; vec3 cameraPos; + float cameraNear; + float cameraFar; mat4 viewMatrix; mat4 projectionMatrix; mat4 invProjectionMatrix; diff --git a/src/main/resources/rs117/hd/utils/constants.glsl b/src/main/resources/rs117/hd/utils/constants.glsl index fe871b0e36..a2c4587e54 100644 --- a/src/main/resources/rs117/hd/utils/constants.glsl +++ b/src/main/resources/rs117/hd/utils/constants.glsl @@ -48,6 +48,7 @@ #define SHADOW_DEFAULT_OPACITY_THRESHOLD 0.71 // Lowest while keeping Prifddinas glass walkways transparent #endif +#include MSAA_SAMPLES #include VANILLA_COLOR_BANDING #include UNDO_VANILLA_SHADING #include LEGACY_GREY_COLORS diff --git a/src/main/resources/rs117/hd/utils/misc.glsl b/src/main/resources/rs117/hd/utils/misc.glsl index fb76ba2f37..2027826973 100644 --- a/src/main/resources/rs117/hd/utils/misc.glsl +++ b/src/main/resources/rs117/hd/utils/misc.glsl @@ -111,3 +111,18 @@ void undoVanillaShading(inout int hsl, vec3 unrotatedNormal) { return mat3(T * invmax, B * invmax, N); } #endif + +float depth01ToViewZ(float depth01, mat4 proj) { + // depth buffer [0,1] -> NDC z [-1,1] + float zNdc = depth01 * 2.0 - 1.0; + + // Invert the perspective projection mapping for z: + // z_view = P[3][2] / (z_ndc - P[2][2]) + float denom = zNdc - proj[2][2]; + + // Guard against division by ~0 (can show up with infinite-far / extreme values) + if (abs(denom) < 1e-20) + denom = (denom < 0.0) ? -1e-20 : 1e-20; + + return proj[3][2] / denom; +} \ No newline at end of file From a258b6f0650bea21e3dce1b092f5c4c77a367a02 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Tue, 17 Feb 2026 23:07:30 +0000 Subject: [PATCH 02/21] Fixed issue when looking straight down Disabled culling lights behind the camera, it will now be culled by the min/maxing Sync --- .../resources/rs117/hd/tiled_lighting_frag.glsl | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/resources/rs117/hd/tiled_lighting_frag.glsl b/src/main/resources/rs117/hd/tiled_lighting_frag.glsl index dd95628f94..47e98f48c6 100644 --- a/src/main/resources/rs117/hd/tiled_lighting_frag.glsl +++ b/src/main/resources/rs117/hd/tiled_lighting_frag.glsl @@ -94,7 +94,7 @@ bool calculateTileMinMax(vec2 bl, vec2 tr, out float tileMin, out float tileMax) } } - if(minDepth >= maxDepth) + if(minDepth > maxDepth) return false; tileMin = depth01ToViewZ(minDepth, projectionMatrix); @@ -103,7 +103,6 @@ bool calculateTileMinMax(vec2 bl, vec2 tr, out float tileMin, out float tileMax) return true; } - void main() { ivec2 pixelCoord = ivec2(fUv * tiledLightingResolution); @@ -144,10 +143,10 @@ void main() { vec2 tr = tileOrigin + tileSize; // top-right vec2 bl = tileOrigin; // bottom-left vec2 br = tileOrigin + vec2(tileSize.x, 0.0); // bottom-right - float tileMin = 0.0f; - float tileMax = 1.0f; #if TILE_MIN_MAX + float tileMin = 0.0f; + float tileMax = 1.0f; if(!calculateTileMinMax(bl, tr, tileMin, tileMax)) { #if TILED_IMAGE_STORE for (int layer = 0; layer < TILED_LIGHTING_LAYER_COUNT; layer++) @@ -195,12 +194,8 @@ void main() { #endif float lightTileDot = dot(lightViewPos, tileCenterVec); - if (lightTileDot <= 0.0) - continue; - float lightDistSqr = dot(lightViewPos, lightViewPos); - float rhs = lightDistSqr * tileCos * tileCos - lightRadiusSqr; - if (lightTileDot * lightTileDot < rhs) + if (lightTileDot * lightTileDot < lightDistSqr * tileCos * tileCos - lightRadiusSqr) continue; #if USE_LIGHTS_MASK From dfa142fac27db1d7fc4e1d7e932e4ba08c0eedb4 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Wed, 18 Feb 2026 02:49:07 +0000 Subject: [PATCH 03/21] Added Depth Fragment Stub for MacOS --- .../rs117/hd/opengl/shader/DepthShaderProgram.java | 11 +++++++++-- src/main/resources/rs117/hd/depth_frag.glsl | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/rs117/hd/depth_frag.glsl diff --git a/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java index 1286cd8dfc..04cf7582e2 100644 --- a/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java @@ -1,5 +1,9 @@ package rs117.hd.opengl.shader; +import rs117.hd.HdPlugin; + +import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER; +import static org.lwjgl.opengl.GL20C.GL_FRAGMENT_SHADER_DERIVATIVE_HINT; import static org.lwjgl.opengl.GL20C.GL_VERTEX_SHADER; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; @@ -7,8 +11,11 @@ public class DepthShaderProgram extends ShaderProgram { protected final UniformTexture uniTextureFaces = addUniformTexture("textureFaces"); public DepthShaderProgram() { - super(t -> t - .add(GL_VERTEX_SHADER, "depth_vert.glsl")); + super(t -> { + t.add(GL_VERTEX_SHADER, "depth_vert.glsl"); + if(HdPlugin.APPLE) + t.add(GL_FRAGMENT_SHADER, "depth_frag.glsl"); + }); } @Override diff --git a/src/main/resources/rs117/hd/depth_frag.glsl b/src/main/resources/rs117/hd/depth_frag.glsl new file mode 100644 index 0000000000..410f5b978f --- /dev/null +++ b/src/main/resources/rs117/hd/depth_frag.glsl @@ -0,0 +1,5 @@ +#version 330 + + void main() { + // MACOS STUB +} \ No newline at end of file From 5fbb1ab287a20a1148a46a3cbe0a3779bf5e0f55 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Wed, 18 Feb 2026 04:39:38 +0000 Subject: [PATCH 04/21] Unroll depth sampling to avoid control flow --- .../rs117/hd/tiled_lighting_frag.glsl | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/main/resources/rs117/hd/tiled_lighting_frag.glsl b/src/main/resources/rs117/hd/tiled_lighting_frag.glsl index 47e98f48c6..a87bae24f9 100644 --- a/src/main/resources/rs117/hd/tiled_lighting_frag.glsl +++ b/src/main/resources/rs117/hd/tiled_lighting_frag.glsl @@ -69,6 +69,18 @@ uint packLightIndices(in SortedLight bin[SORTING_BIN_SIZE], in int binSize, inou return (idx0 <= 32767) ? uint(idx0 & 0x7FFF) : 0u; } +#define SAMPLE_DEPTH(FRAC_X, FRAC_Y) \ + pix = ivec2(int(mix(float(minPix.x), float(maxPix.x), FRAC_X)), int(mix(float(minPix.y), float(maxPix.y), FRAC_Y))); \ + depth = texelFetch(sceneOpaqueDepth, pix, 0).r; \ + if (depth > 0.0) { \ + minDepth = min(minDepth, depth); \ + maxDepth = max(maxDepth, depth); \ + } \ + depth = texelFetch(sceneAlphaDepth, pix, 0).r; \ + if(depth > 0.0) \ + maxDepth = max(maxDepth, depth); \ + + bool calculateTileMinMax(vec2 bl, vec2 tr, out float tileMin, out float tileMax) { #if TILE_MIN_MAX ivec2 minPix = clamp(ivec2(floor(bl)), ivec2(0), ivec2(sceneResolution) - 1); @@ -78,21 +90,19 @@ bool calculateTileMinMax(vec2 bl, vec2 tr, out float tileMin, out float tileMax) float minDepth = 1e20; float maxDepth = -1e20; - float stepSize = 1.0f / 2.0f; - for(float x = 0; x <= 1.0f; x += stepSize) { - for(float y = 0; y <= 1.0f; y += stepSize) { - ivec2 pix = ivec2(int(mix(minPix.x, maxPix.x, x)), int(mix(minPix.y, maxPix.y, y))); - float depth = texelFetch(sceneOpaqueDepth, pix, 0).r; - if (depth > 0.0) { - minDepth = min(minDepth, depth); - maxDepth = max(maxDepth, depth); - } + ivec2 pix; + float depth; + SAMPLE_DEPTH(0.0, 0.0) + SAMPLE_DEPTH(0.5, 0.0) + SAMPLE_DEPTH(1.0, 0.0) - float alphaDepth = texelFetch(sceneAlphaDepth, pix, 0).r; - if (alphaDepth > 0.0) - maxDepth = max(maxDepth, alphaDepth); - } - } + SAMPLE_DEPTH(0.0, 0.5) + SAMPLE_DEPTH(0.5, 0.5) + SAMPLE_DEPTH(1.0, 0.5) + + SAMPLE_DEPTH(0.0, 1.0) + SAMPLE_DEPTH(0.5, 1.0) + SAMPLE_DEPTH(1.0, 1.0) if(minDepth > maxDepth) return false; From e04123b2fabdd9d9aebc4a7c4c24eda68443a261 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 16 Feb 2026 01:39:42 +0000 Subject: [PATCH 05/21] Occlusion Culling Expanded Occlusion Queries to directional draw added isVisible Sync Sync Disable Occlusion Culling Direction Queries when Shadow Alpha enabled Added Debug Visualiser Support Sync Sync Dont spin wait, continue if its not available Queries need to be double buffered Sync Sync Sync Sync Buffer Samples to ensure minimal readback delay Directional Queries should sample scene depth FBO with the AABB expanded along the directional camera forward to see if the expanded AABB is visible Fixed Directional Shadow AABB Expanding Zones will now remove encapsulated AABBs Sync Dont getAABB when occlusion culling is disabled Sync Added BindBufferRange support to uniform buffer Sync Sync Sync Fix Flicker Always set worldView since the player can enter a boat & leave Switch to back face culling, otherwise we consider AABBs occluded whilst inside the AABB. Alternatively we could test if the camera is inside & skip those checks Skip occlusion queries if the camera is within any of its AABBs Debug Stuff Moved assert Fixed Dynamic Occlusion queries not resting their AABBs each frame AABB Fixes Fix World View projected AABBs Frustum Cull AABBs Sync Occlusion Query Frustum Culling Sync Formatting Frustum Cull Query AABBs to reduce needless rendering Fixes Sync Sync Sync Sync Added KeyBind to toggle between Static+Dynamic/Static/Dynamic --- src/main/java/rs117/hd/HdPlugin.java | 31 +- src/main/java/rs117/hd/HdPluginConfig.java | 11 + .../java/rs117/hd/opengl/GLConstants.java | 66 ++ .../opengl/shader/OcclusionShaderProgram.java | 24 + .../hd/opengl/shader/ShaderTemplate.java | 5 +- .../hd/opengl/uniforms/UBOOcclusion.java | 15 + .../hd/opengl/uniforms/UniformBuffer.java | 25 +- .../rs117/hd/overlays/FrameTimerOverlay.java | 9 + src/main/java/rs117/hd/overlays/Timer.java | 3 + .../renderer/zone/ModelStreamingManager.java | 12 + .../hd/renderer/zone/OcclusionManager.java | 844 ++++++++++++++++++ .../rs117/hd/renderer/zone/SceneUploader.java | 61 +- .../hd/renderer/zone/WorldViewContext.java | 3 + .../java/rs117/hd/renderer/zone/Zone.java | 42 +- .../rs117/hd/renderer/zone/ZoneRenderer.java | 149 ++-- src/main/java/rs117/hd/utils/Camera.java | 6 +- .../java/rs117/hd/utils/DeveloperTools.java | 13 + src/main/java/rs117/hd/utils/HDUtils.java | 30 +- .../rs117/hd/occlusion_debug_frag.glsl | 12 + .../resources/rs117/hd/occlusion_vert.glsl | 13 + src/main/resources/rs117/hd/scene_frag.glsl | 2 +- .../rs117/hd/uniforms/occlusion.glsl | 6 + .../resources/rs117/hd/utils/color_utils.glsl | 20 + 23 files changed, 1305 insertions(+), 97 deletions(-) create mode 100644 src/main/java/rs117/hd/opengl/GLConstants.java create mode 100644 src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java create mode 100644 src/main/java/rs117/hd/opengl/uniforms/UBOOcclusion.java create mode 100644 src/main/java/rs117/hd/renderer/zone/OcclusionManager.java create mode 100644 src/main/resources/rs117/hd/occlusion_debug_frag.glsl create mode 100644 src/main/resources/rs117/hd/occlusion_vert.glsl create mode 100644 src/main/resources/rs117/hd/uniforms/occlusion.glsl diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 8d1bf868e8..ed3dcc59ae 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -82,6 +82,7 @@ import rs117.hd.config.ShadingMode; import rs117.hd.config.ShadowMode; import rs117.hd.config.VanillaShadowMode; +import rs117.hd.opengl.GLConstants; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; import rs117.hd.opengl.shader.TiledLightingShaderProgram; @@ -131,6 +132,10 @@ import static net.runelite.api.Constants.*; import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPluginConfig.*; +import static rs117.hd.opengl.GLConstants.getMaxImageUnits; +import static rs117.hd.opengl.GLConstants.getMaxSamples; +import static rs117.hd.opengl.GLConstants.getMaxTextureSize; +import static rs117.hd.opengl.GLConstants.getMaxTextureUnits; import static rs117.hd.utils.MathUtils.*; import static rs117.hd.utils.ResourcePath.path; import static rs117.hd.utils.buffer.GLBuffer.MAP_WRITE; @@ -156,7 +161,6 @@ public class HdPlugin extends Plugin { public static final String INTEL_DRIVER_URL = "https://www.intel.com/content/www/us/en/support/detect.html"; public static final String NVIDIA_DRIVER_URL = "https://www.nvidia.com/en-us/geforce/drivers/"; - public static int MAX_TEXTURE_UNITS; public static int TEXTURE_UNIT_COUNT = 0; public static final int TEXTURE_UNIT_UI = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; public static final int TEXTURE_UNIT_GAME = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; @@ -166,7 +170,6 @@ public class HdPlugin extends Plugin { public static final int TEXTURE_UNIT_SCENE_OPAQUE_DEPTH = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; public static final int TEXTURE_UNIT_SCENE_ALPHA_DEPTH = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; - public static int MAX_IMAGE_UNITS; public static int IMAGE_UNIT_COUNT = 0; public static final int IMAGE_UNIT_TILED_LIGHTING = IMAGE_UNIT_COUNT++; @@ -401,6 +404,7 @@ public class HdPlugin extends Plugin { public boolean configModelBatching; public boolean configModelCaching; public boolean configShadowsEnabled; + public boolean configShadowTransparency; public boolean configRoofShadows; public boolean configExpandShadowDraw; public boolean configUseFasterModelHashing; @@ -594,13 +598,10 @@ protected void startUp() { lwjglInitialized = true; checkGLErrors(); - MAX_TEXTURE_UNITS = glGetInteger(GL_MAX_TEXTURE_IMAGE_UNITS); // Not the fixed pipeline MAX_TEXTURE_UNITS - if (MAX_TEXTURE_UNITS < TEXTURE_UNIT_COUNT) - log.warn("The GPU only supports {} texture units", MAX_TEXTURE_UNITS); - MAX_IMAGE_UNITS = GL_CAPS.GL_ARB_shader_image_load_store ? - glGetInteger(ARBShaderImageLoadStore.GL_MAX_IMAGE_UNITS) : 0; - if (MAX_IMAGE_UNITS < IMAGE_UNIT_COUNT) - log.warn("The GPU only supports {} image units", MAX_IMAGE_UNITS); + if (getMaxTextureUnits() < TEXTURE_UNIT_COUNT) + log.warn("The GPU only supports {} texture units", getMaxTextureUnits()); + if (getMaxImageUnits() < IMAGE_UNIT_COUNT) + log.warn("The GPU only supports {} image units", getMaxImageUnits()); if (log.isDebugEnabled() && GL_CAPS.glDebugMessageControl != 0) { debugCallback = GLUtil.setupDebugMessageCallback(); @@ -1249,8 +1250,8 @@ public void updateSceneFbo() { // Bind default FBO to check whether anti-aliasing is forced int defaultFramebuffer = awtContext.getFramebuffer(false); glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); - final int forcedAASamples = glGetInteger(GL_SAMPLES); - msaaSamples = forcedAASamples != 0 ? forcedAASamples : min(config.antiAliasingMode().getSamples(), glGetInteger(GL_MAX_SAMPLES)); + final int forcedAASamples = GLConstants.getForcedSamples(); + msaaSamples = forcedAASamples != 0 ? forcedAASamples : min(config.antiAliasingMode().getSamples(), getMaxSamples()); // Since there's seemingly no reliable way to check if the default framebuffer will do sRGB conversions with GL_FRAMEBUFFER_SRGB // enabled, we always replace the default framebuffer with an sRGB one. We could technically support rendering to the default @@ -1457,10 +1458,9 @@ private void initializeShadowMapFbo() { glBindTexture(GL_TEXTURE_2D, texShadowMap); shadowMapResolution = config.shadowResolution().getValue(); - int maxResolution = glGetInteger(GL_MAX_TEXTURE_SIZE); - if (maxResolution < shadowMapResolution) { - log.info("Capping shadow resolution from {} to {}", shadowMapResolution, maxResolution); - shadowMapResolution = maxResolution; + if (getMaxTextureSize() < shadowMapResolution) { + log.info("Capping shadow resolution from {} to {}", shadowMapResolution, getMaxTextureSize()); + shadowMapResolution = getMaxTextureSize(); } glTexImage2D( @@ -1691,6 +1691,7 @@ public boolean isLoadingScene() { private void updateCachedConfigs() { configShadowMode = config.shadowMode(); configShadowsEnabled = configShadowMode != ShadowMode.OFF; + configShadowTransparency = config.enableShadowTransparency(); configRoofShadows = config.roofShadows(); configGroundTextures = config.groundTextures(); configGroundBlending = config.groundBlending(); diff --git a/src/main/java/rs117/hd/HdPluginConfig.java b/src/main/java/rs117/hd/HdPluginConfig.java index 6541df291e..3b052b7041 100644 --- a/src/main/java/rs117/hd/HdPluginConfig.java +++ b/src/main/java/rs117/hd/HdPluginConfig.java @@ -1184,6 +1184,17 @@ default boolean forceIndirectDraw() { return false; } + String KEY_OCCLUSION_CULLING = "experimentalOcclusionCulling"; + @ConfigItem( + keyName = KEY_OCCLUSION_CULLING, + name = "Occlusion Culling", + description = "", + section = experimentalSettings + ) + default boolean occlusionCulling() { + return true; + } + String KEY_ASYNC_MODEL_CACHE_SIZE = "asyncModelCacheSizeMiB"; @Range( min = 16, diff --git a/src/main/java/rs117/hd/opengl/GLConstants.java b/src/main/java/rs117/hd/opengl/GLConstants.java new file mode 100644 index 0000000000..0bf748c818 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/GLConstants.java @@ -0,0 +1,66 @@ +package rs117.hd.opengl; + +import static org.lwjgl.opengl.ARBShaderImageLoadStore.GL_MAX_IMAGE_UNITS; +import static org.lwjgl.opengl.GL11C.GL_MAX_TEXTURE_SIZE; +import static org.lwjgl.opengl.GL11C.glGetInteger; +import static org.lwjgl.opengl.GL13C.GL_SAMPLES; +import static org.lwjgl.opengl.GL20C.GL_MAX_TEXTURE_IMAGE_UNITS; +import static org.lwjgl.opengl.GL30C.GL_MAX_SAMPLES; +import static org.lwjgl.opengl.GL31C.GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT; +import static org.lwjgl.opengl.GL41.GL_NUM_PROGRAM_BINARY_FORMATS; +import static rs117.hd.HdPlugin.GL_CAPS; + +public class GLConstants { + + private static int FORCED_SAMPLES_VALUE = -1; + public static int getForcedSamples() { + if(FORCED_SAMPLES_VALUE == -1) + FORCED_SAMPLES_VALUE = glGetInteger(GL_SAMPLES); + return FORCED_SAMPLES_VALUE; + } + + private static int MAX_SAMPLES_VALUE = -1; + public static int getMaxSamples() { + if(MAX_SAMPLES_VALUE == -1) + MAX_SAMPLES_VALUE = glGetInteger(GL_MAX_SAMPLES); + return MAX_SAMPLES_VALUE; + } + + private static int MAX_IMAGE_UNITS_VALUE = -1; + public static int getMaxImageUnits() { + if(!GL_CAPS.GL_ARB_shader_image_load_store) + return 0; + if(MAX_IMAGE_UNITS_VALUE == -1) + MAX_IMAGE_UNITS_VALUE = glGetInteger(GL_MAX_IMAGE_UNITS); + return MAX_IMAGE_UNITS_VALUE; + } + + private static int MAX_TEXTURE_UNITS_VALUE = -1; + public static int getMaxTextureUnits() { + if(MAX_TEXTURE_UNITS_VALUE == -1) + MAX_TEXTURE_UNITS_VALUE = glGetInteger(GL_MAX_TEXTURE_IMAGE_UNITS); + return MAX_TEXTURE_UNITS_VALUE; + } + + private static int MAX_TEXTURE_SIZE_VALUE = -1; + public static int getMaxTextureSize() { + if(MAX_TEXTURE_SIZE_VALUE == -1) + MAX_TEXTURE_SIZE_VALUE = glGetInteger(GL_MAX_TEXTURE_SIZE); + return MAX_TEXTURE_SIZE_VALUE; + } + + private static int BUFFER_OFFSET_ALIGNMENT_VALUE = -1; + public static int getBufferOffsetAlignment() { + if(BUFFER_OFFSET_ALIGNMENT_VALUE == -1) + BUFFER_OFFSET_ALIGNMENT_VALUE = glGetInteger(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT); + + return BUFFER_OFFSET_ALIGNMENT_VALUE; + } + + private static int NUM_PROGRAM_BINARY_FORMATS_VALUE = -1; + public static int getNumProgramBinaryFormats() { + if(NUM_PROGRAM_BINARY_FORMATS_VALUE == -1) + NUM_PROGRAM_BINARY_FORMATS_VALUE = glGetInteger(GL_NUM_PROGRAM_BINARY_FORMATS); + return NUM_PROGRAM_BINARY_FORMATS_VALUE; + } +} diff --git a/src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java new file mode 100644 index 0000000000..bbf4adbd20 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java @@ -0,0 +1,24 @@ +package rs117.hd.opengl.shader; + +import rs117.hd.HdPlugin; + +import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER; +import static org.lwjgl.opengl.GL20C.GL_VERTEX_SHADER; + +public class OcclusionShaderProgram extends ShaderProgram { + public OcclusionShaderProgram() { + super(t -> { + t.add(GL_VERTEX_SHADER, "occlusion_vert.glsl"); + if(HdPlugin.APPLE) + t.add(GL_FRAGMENT_SHADER, "depth_frag.glsl"); + }); + } + + public static class Debug extends OcclusionShaderProgram { + public Uniform1i queryId = addUniform1i("queryId"); + + public Debug() { + shaderTemplate.remove(GL_FRAGMENT_SHADER).add(GL_FRAGMENT_SHADER, "occlusion_debug_frag.glsl"); + } + } +} diff --git a/src/main/java/rs117/hd/opengl/shader/ShaderTemplate.java b/src/main/java/rs117/hd/opengl/shader/ShaderTemplate.java index b3621b19d8..d2e814a3e2 100644 --- a/src/main/java/rs117/hd/opengl/shader/ShaderTemplate.java +++ b/src/main/java/rs117/hd/opengl/shader/ShaderTemplate.java @@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.*; +import rs117.hd.opengl.GLConstants; import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.opengl.shader.ShaderIncludes.SHADER_DUMP_PATH; @@ -103,9 +104,7 @@ public int compile(ShaderIncludes includes) throws ShaderException, IOException ok = true; if (SHADER_DUMP_PATH != null) { - int[] numFormats = { 0 }; - glGetIntegerv(GL41C.GL_NUM_PROGRAM_BINARY_FORMATS, numFormats); - if (numFormats[0] < 1) { + if (GLConstants.getNumProgramBinaryFormats() < 1) { log.error("OpenGL driver does not support any binary formats"); } else { int[] size = { 0 }; diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOOcclusion.java b/src/main/java/rs117/hd/opengl/uniforms/UBOOcclusion.java new file mode 100644 index 0000000000..6fddb8237b --- /dev/null +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOOcclusion.java @@ -0,0 +1,15 @@ +package rs117.hd.opengl.uniforms; + +import rs117.hd.utils.buffer.GLBuffer; + +import static org.lwjgl.opengl.GL15C.GL_DYNAMIC_DRAW; + +public class UBOOcclusion extends UniformBuffer { + public static final int MAX_AABBS = 2000; + + public UBOOcclusion() { + super(GL_DYNAMIC_DRAW); + } + + public final Property[] aabbs = addPropertyArray(PropertyType.FVec3, "aabbs", MAX_AABBS * 2); +} diff --git a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java index 0d1ca3104f..b7a7d1d6b3 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java @@ -11,17 +11,19 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.lwjgl.BufferUtils; +import rs117.hd.opengl.GLConstants; import rs117.hd.utils.RenderState; import rs117.hd.utils.buffer.GLBuffer; import rs117.hd.utils.buffer.SharedGLBuffer; import static org.lwjgl.opengl.GL33C.*; +import static rs117.hd.utils.HDUtils.align; import static rs117.hd.utils.MathUtils.*; @Slf4j public abstract class UniformBuffer { @RequiredArgsConstructor - protected enum PropertyType { + public enum PropertyType { Int(4, 4, 1), IVec2(8, 8, 2), IVec3(12, 16, 3), @@ -35,10 +37,10 @@ protected enum PropertyType { Mat3(48, 16, 9), Mat4(64, 16, 16); - private final int size; - private final int alignment; - private final int elementCount; - private final boolean isInt = name().startsWith("I"); + public final int size; + public final int alignment; + public final int elementCount; + public final boolean isInt = name().startsWith("I"); } @AllArgsConstructor @@ -47,6 +49,7 @@ public static class Property { private UniformBuffer owner; private int position; private int offset = -1; + @Getter private final PropertyType type; private final String name; @@ -343,6 +346,18 @@ public void bind(int bindingIndex) { glBindBufferBase(GL_UNIFORM_BUFFER, bindingIndex, glBuffer.id); } + public void bindRange(Property startProperty, Property endProperty) { + assert endProperty.offset >= startProperty.offset; + + long bufferAlignment = GLConstants.getBufferOffsetAlignment(); + long bytesOffset = (long)startProperty.offset * Integer.BYTES; + long bytesSize = ((endProperty.offset - startProperty.offset) + align(endProperty.type.size, endProperty.type.alignment, true)) * Integer.BYTES; + + long alignedBytesOffset = align(bytesOffset, bufferAlignment, false); + + glBindBufferRange(GL_UNIFORM_BUFFER, bindingIndex, glBuffer.id, alignedBytesOffset, bytesSize); + } + protected void preUpload() {} public final void upload() { diff --git a/src/main/java/rs117/hd/overlays/FrameTimerOverlay.java b/src/main/java/rs117/hd/overlays/FrameTimerOverlay.java index d596fc9efb..29cff8a61e 100644 --- a/src/main/java/rs117/hd/overlays/FrameTimerOverlay.java +++ b/src/main/java/rs117/hd/overlays/FrameTimerOverlay.java @@ -17,6 +17,7 @@ import net.runelite.client.ui.overlay.components.LineComponent; import net.runelite.client.ui.overlay.components.TitleComponent; import rs117.hd.HdPlugin; +import rs117.hd.renderer.zone.OcclusionManager; import rs117.hd.renderer.zone.SceneManager; import rs117.hd.renderer.zone.WorldViewContext; import rs117.hd.renderer.zone.ZoneRenderer; @@ -206,6 +207,14 @@ public Dimension render(Graphics2D g) { .build()); } + var occlusionManager = OcclusionManager.getInstance(); + if(occlusionManager != null && occlusionManager.isActive()) { + children.add(LineComponent.builder() + .left("Visible Occlusion Queries:") + .right(String.format("%d/%d", occlusionManager.getPassedQueryCount(), occlusionManager.getQueryCount())) + .build()); + } + children.add(LineComponent.builder() .leftFont(boldFont) .left("Streaming Stats:") diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 3d46645c0d..29dc692123 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -24,6 +24,7 @@ public enum Timer { DRAW_TEMP, DRAW_POSTSCENE, DRAW_TILED_LIGHTING, + DRAW_OCCLUSION, DRAW_SUBMIT, // Miscellaneous @@ -31,6 +32,7 @@ public enum Timer { EXECUTE_COMMAND_BUFFER, MAP_UI_BUFFER("Map UI Buffer"), COPY_UI("Copy UI"), + OCCLUSION_READBACK, MODEL_UPLOAD_COMPLETE, // Logic @@ -69,6 +71,7 @@ public enum Timer { UNMAP_ROOT_CTX(GPU_TIMER), CLEAR_SCENE(GPU_TIMER), RENDER_DEPTH_PRE_PASS(GPU_TIMER), + RENDER_OCCLUSION(GPU_TIMER), RENDER_SHADOWS(GPU_TIMER), RENDER_SCENE(GPU_TIMER), RENDER_UI(GPU_TIMER, "Render UI"), diff --git a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java index 77238aa374..b28915a1da 100644 --- a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java +++ b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java @@ -18,6 +18,7 @@ import rs117.hd.config.ShadowMode; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; +import rs117.hd.renderer.zone.OcclusionManager.OcclusionQuery; import rs117.hd.scene.ModelOverrideManager; import rs117.hd.scene.model_overrides.ModelOverride; import rs117.hd.utils.HDUtils; @@ -62,6 +63,9 @@ public class ModelStreamingManager { @Inject private ModelOverrideManager modelOverrideManager; + @Inject + private OcclusionManager occlusionManager; + @Inject private FrameTimer frameTimer; @@ -194,6 +198,10 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj )) { return; } + + final OcclusionQuery occlusionQuery = occlusionManager.obtainOcclusionQuery(ctx, gameObject.getId(), zone, orientation, false, m, x, y, z); + if(occlusionQuery != null && occlusionQuery.isOccluded()) + return; plugin.drawnTempRenderableCount++; final boolean isModelPartiallyVisible = sceneManager.isRoot(ctx) && modelClassification == 0; @@ -432,6 +440,10 @@ public void drawDynamic( )) { return; } + + final OcclusionQuery occlusionQuery = occlusionManager.obtainOcclusionQuery(ctx, tileObject.getHash(), zone, orient, true, m, x, y, z); + if(occlusionQuery != null && occlusionQuery.isOccluded()) + return; streamingContext.renderableCount++; final int preOrientation = HDUtils.getModelPreOrientation(HDUtils.getObjectConfig(tileObject)); diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java new file mode 100644 index 0000000000..45320c01ce --- /dev/null +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -0,0 +1,844 @@ +package rs117.hd.renderer.zone; + +import java.io.IOException; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.*; +import org.lwjgl.system.MemoryStack; +import rs117.hd.HdPlugin; +import rs117.hd.HdPluginConfig; +import rs117.hd.opengl.GLConstants; +import rs117.hd.opengl.shader.OcclusionShaderProgram; +import rs117.hd.opengl.shader.ShaderException; +import rs117.hd.opengl.shader.ShaderIncludes; +import rs117.hd.opengl.uniforms.UBOOcclusion; +import rs117.hd.opengl.uniforms.UBOWorldViews.WorldViewStruct; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; +import rs117.hd.utils.HDUtils; +import rs117.hd.utils.RenderState; + +import static org.lwjgl.opengl.GL11.GL_TRIANGLES; +import static org.lwjgl.opengl.GL15.glDeleteBuffers; +import static org.lwjgl.opengl.GL15C.GL_ARRAY_BUFFER; +import static org.lwjgl.opengl.GL15C.GL_STATIC_DRAW; +import static org.lwjgl.opengl.GL15C.glBindBuffer; +import static org.lwjgl.opengl.GL15C.glBufferData; +import static org.lwjgl.opengl.GL15C.glGenBuffers; +import static org.lwjgl.opengl.GL20C.glEnableVertexAttribArray; +import static org.lwjgl.opengl.GL20C.glVertexAttribPointer; +import static org.lwjgl.opengl.GL30C.glBindVertexArray; +import static org.lwjgl.opengl.GL30C.glDeleteVertexArrays; +import static org.lwjgl.opengl.GL30C.glGenVertexArrays; +import static org.lwjgl.opengl.GL33C.*; +import static org.lwjgl.opengl.GL43.GL_ANY_SAMPLES_PASSED_CONSERVATIVE; +import static rs117.hd.HdPlugin.GL_CAPS; +import static rs117.hd.HdPlugin.checkGLErrors; +import static rs117.hd.utils.HDUtils.align; +import static rs117.hd.utils.MathUtils.*; + +@Slf4j +@Singleton +public final class OcclusionManager { + private static final int FRAMES_IN_FLIGHT = 3; + + @Getter + private static OcclusionManager instance; + private final ConcurrentHashMap dynamicOcclusionQueries = new ConcurrentHashMap<>(); + private final ConcurrentHashMap tempOcclusionQueries = new ConcurrentHashMap<>(); + private final Set> dynamicOcclusionQuerySet = dynamicOcclusionQueries.entrySet(); + private final Set> tempOcclusionQuerySet = tempOcclusionQueries.entrySet(); + private final ConcurrentLinkedQueue freeQueries = new ConcurrentLinkedQueue<>(); + private final List queuedQueries = new ArrayList<>(); + private final List prevQueuedQueries = new ArrayList<>(); + private final int[] result = new int[1]; + private final float[] vec = new float[4]; + private final float[][] sceneFrustumPlanes = new float[6][4]; + private final float[] directionalFwd = new float[3]; + private final float[] projected = new float[6]; + @Inject + private HdPlugin plugin; + @Inject + private ZoneRenderer zoneRenderer; + @Inject + private HdPluginConfig config; + @Inject + private FrameTimer frameTimer; + @Inject + private OcclusionShaderProgram occlusionProgram; + @Inject + private OcclusionShaderProgram.Debug occlusionDebugProgram; + private RenderState renderState; + private UBOOcclusion uboOcclusion; + @Getter + private boolean active; + private int debugMode; + private int debugVisibility; + + @Getter + private int queryCount = 0; + @Getter + private int passedQueryCount; + + private int glCubeVAO; + private int glCubeVBO; + private int glCubeEBO; + private int anySamplesPassedTarget; + + public void toggleDebug() { debugMode = (debugMode + 1) % 3; } + public void toggleDebugVisibility() { + debugVisibility = (debugVisibility + 1) % 3; + } + + public void initialize(RenderState renderState, UBOOcclusion uboOcclusion) { + this.renderState = renderState; + this.uboOcclusion = uboOcclusion; + + instance = this; + active = config.occlusionCulling(); + + // Check if conservative queries are supported + if (GL_CAPS.GL_ARB_occlusion_query2) { + anySamplesPassedTarget = GL_ANY_SAMPLES_PASSED_CONSERVATIVE; + log.info("Using GL_ANY_SAMPLES_PASSED_CONSERVATIVE for occlusion queries"); + } else { + anySamplesPassedTarget = GL_ANY_SAMPLES_PASSED; + log.info("Using fallback GL_ANY_SAMPLES_PASSED for occlusion queries"); + } + + try (MemoryStack stack = MemoryStack.stackPush()) { + // Create cube VAO + glCubeVAO = glGenVertexArrays(); + glCubeVBO = glGenBuffers(); + glBindVertexArray(glCubeVAO); + + FloatBuffer vboCubeData = stack.mallocFloat(8 * 3) + .put(new float[] { + // 8 unique cube corners + -1, -1, -1, // 0 + 1, -1, -1, // 1 + 1, 1, -1, // 2 + -1, 1, -1, // 3 + -1, -1, 1, // 4 + 1, -1, 1, // 5 + 1, 1, 1, // 6 + -1, 1, 1 // 7 + }) + .flip(); + glBindBuffer(GL_ARRAY_BUFFER, glCubeVBO); + glBufferData(GL_ARRAY_BUFFER, vboCubeData, GL_STATIC_DRAW); + + IntBuffer eboCubeData = stack.mallocInt(36) + .put(new int[] { + // Front face (-Z) + 0, 1, 2, + 0, 2, 3, + + // Back face (+Z) + 4, 6, 5, + 4, 7, 6, + + // Left face (-X) + 0, 3, 7, + 0, 7, 4, + + // Right face (+X) + 1, 5, 6, + 1, 6, 2, + + // Bottom face (-Y) + 0, 4, 5, + 0, 5, 1, + + // Top face (+Y) + 3, 2, 6, + 3, 6, 7 + }) + .flip(); + + glCubeEBO = glGenBuffers(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glCubeEBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboCubeData, GL_STATIC_DRAW); + + // position attribute + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, false, 3 * Float.BYTES, 0); + + // reset + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } + } + + public void initializeShaders(ShaderIncludes includes) throws ShaderException, IOException { + occlusionProgram.compile(includes); + occlusionDebugProgram.compile(includes); + } + + public void destroyShaders() { + occlusionProgram.destroy(); + occlusionDebugProgram.destroy(); + } + + public void destroy() { + if (glCubeVAO != 0) + glDeleteVertexArrays(glCubeVAO); + glCubeVAO = 0; + + if (glCubeVBO != 0) + glDeleteBuffers(glCubeVBO); + glCubeVBO = 0; + + deleteQueries(freeQueries); + deleteQueries(queuedQueries); + deleteQueries(prevQueuedQueries); + } + + private void deleteQueries(Collection queries) { + for (OcclusionQuery query : queries) { + if (query.id[0] != 0) { + glDeleteQueries(query.id); + Arrays.fill(query.id, 0); + } + } + queries.clear(); + } + + public OcclusionQuery obtainOcclusionQuery( + WorldViewContext ctx, + long hash, + Zone zone, + int orientation, + boolean isDynamic, + Model m, + float x, + float y, + float z + ) { + final ConcurrentHashMap occlusionQueries = isDynamic ? dynamicOcclusionQueries : tempOcclusionQueries; + OcclusionQuery query = occlusionQueries.get(hash); + if (query == null) { + query = obtainQuery(); + occlusionQueries.put(hash, query); + } + query.setWorldView(ctx.uboWorldViewStruct); + query.queue(); + if (!query.resetThisFrame) + query.reset(); + if (active) { + query.addAABB(m.getAABB(orientation), x, y, z); + zone.additionalOcclusionQueries.add(query); + } + return query; + } + + public OcclusionQuery obtainQuery() { + OcclusionQuery query = freeQueries.poll(); + if (query == null) + query = new OcclusionQuery(); + return query; + } + + private void processDynamicOcclusionQueries(Iterator> iter) { + while (iter.hasNext()) { + final OcclusionQuery query = iter.next().getValue(); + if (query.isQueued()) { + query.resetThisFrame = false; + continue; + } + query.free(); + iter.remove(); + } + } + + public void readbackQueries() { + active = config.occlusionCulling(); + + processDynamicOcclusionQueries(dynamicOcclusionQuerySet.iterator()); + processDynamicOcclusionQueries(tempOcclusionQuerySet.iterator()); + + if (prevQueuedQueries.isEmpty()) + return; + + frameTimer.begin(Timer.OCCLUSION_READBACK); + queryCount = prevQueuedQueries.size(); + passedQueryCount = 0; + for (int i = 0; i < queryCount; i++) { + final OcclusionQuery query = prevQueuedQueries.get(i); + if (!query.queued) + continue; + query.queued = false; + + final int id = query.getReadbackId(); + if (id == 0) + continue; + + if (query.frustumCulled) + continue; + + query.occluded = glGetQueryObjecti(id, GL_QUERY_RESULT) == 0; + if (!query.occluded) + passedQueryCount++; + } + frameTimer.end(Timer.OCCLUSION_READBACK); + prevQueuedQueries.clear(); + + checkGLErrors(); + } + + public void occlusionDebugPass() { + if (queuedQueries.isEmpty() || debugMode == 0) + return; + + renderState.viewport.set(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); + renderState.framebuffer.set(GL_DRAW_FRAMEBUFFER, plugin.fboScene); + renderState.depthFunc.set(GL_GEQUAL); + renderState.disable.set(GL_CULL_FACE); + renderState.enable.set(GL_BLEND); + renderState.enable.set(GL_DEPTH_TEST); + renderState.depthMask.set(false); + renderState.ebo.set(glCubeEBO); + renderState.vao.set(glCubeVAO); + renderState.apply(); + occlusionDebugProgram.use(); + + if (debugMode == 1) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + glLineWidth(2.5f); + } + + processQueries(queuedQueries, true); + + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + renderState.ebo.set(0); + renderState.vao.set(0); + renderState.disable.set(GL_BLEND); + renderState.disable.set(GL_DEPTH_TEST); + renderState.depthMask.set(true); + renderState.apply(); + } + + public void occlusionPass() { + if (queuedQueries.isEmpty()) + return; + + frameTimer.begin(Timer.RENDER_OCCLUSION); + frameTimer.begin(Timer.DRAW_OCCLUSION); + + zoneRenderer.sceneCamera.getFrustumPlanes(sceneFrustumPlanes); + zoneRenderer.directionalCamera.getForwardDirection(directionalFwd); + normalize(directionalFwd, directionalFwd); + + renderState.viewport.set(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); + renderState.framebuffer.set(GL_DRAW_FRAMEBUFFER, plugin.fboSceneDepth); + renderState.depthFunc.set(GL_GEQUAL); + renderState.disable.set(GL_CULL_FACE); + renderState.enable.set(GL_DEPTH_TEST); + renderState.depthMask.set(false); + renderState.colorMask.set(false, false, false, false); + renderState.ebo.set(glCubeEBO); + renderState.vao.set(glCubeVAO); + renderState.apply(); + occlusionProgram.use(); + + processQueries(queuedQueries, false); + + renderState.ebo.set(0); + renderState.vao.set(0); + renderState.disable.set(GL_DEPTH_TEST); + renderState.depthMask.set(true); + renderState.colorMask.set(true, true, true, true); + renderState.apply(); + + prevQueuedQueries.addAll(queuedQueries); + queuedQueries.clear(); + + frameTimer.end(Timer.DRAW_OCCLUSION); + frameTimer.end(Timer.RENDER_OCCLUSION); + + checkGLErrors(); + } + + private void processQueries(List queries, boolean isDebug) { + int start = 0; + int uboOffset = 0; + + for (int i = 0; i < queries.size(); i++) { + final OcclusionQuery query = queries.get(i); + if (query.count == 0) + continue; + + if (uboOffset + query.count >= UBOOcclusion.MAX_AABBS) { + flushQueries(queries, start, i, isDebug); + start = i; + uboOffset = 0; + } + + if (query.id[0] == 0) + glGenQueries(query.id); + + uboOffset = buildQueryAABBs(query, uboOffset, isDebug); + } + flushQueries(queries, start, queries.size(), isDebug); + checkGLErrors(); + } + + private int buildQueryAABBs(OcclusionQuery query, int uboOffset, boolean isDebug) { + if (query.count <= 0) + return uboOffset; + + final float EXPAND_FACTOR = 4.0f; + final float dirX = -abs(directionalFwd[0]); + final float dirY = -abs(directionalFwd[1]); + final float dirZ = -abs(directionalFwd[2]); + + if (!isDebug && query.globalAABB) { + float posX = query.offsetX + (query.globalMinX + query.globalMaxX) * 0.5f; + float posY = query.offsetY + (query.globalMinY + query.globalMaxY) * 0.5f; + float posZ = query.offsetZ + (query.globalMinZ + query.globalMaxZ) * 0.5f; + + float sizeX = (query.globalMaxX - query.globalMinX) * 0.5f; + float sizeY = (query.globalMaxY - query.globalMinY) * 0.5f; + float sizeZ = (query.globalMaxZ - query.globalMinZ) * 0.5f; + + projectAABB(query, projected, posX, posY, posZ, sizeX, sizeY, sizeZ); + if (plugin.configShadowsEnabled) + expandAABBAlongShadow(projected, dirX, dirY, dirZ, EXPAND_FACTOR); + + if (!isAABBVisible(projected)) { + query.frustumCulled = true; + return uboOffset; + } + } + + final int elementSize = (int) align(uboOcclusion.aabbs[0].getType().size, 16, true); + final int alignment = GLConstants.getBufferOffsetAlignment(); + + long startByteOffset = (long) uboOffset * elementSize; + long alignedStartByteOffset = align(startByteOffset, alignment, true); + int alignedUboOffset = (int) (alignedStartByteOffset / elementSize); + + query.uboOffset = alignedUboOffset; + query.writtenCount = 0; + for (int j = 0; j < query.count; j++) { + float posX = query.offsetX + query.aabb[j * 6]; + float posY = query.offsetY + query.aabb[j * 6 + 1]; + float posZ = query.offsetZ + query.aabb[j * 6 + 2]; + + float sizeX = query.aabb[j * 6 + 3]; + float sizeY = query.aabb[j * 6 + 4]; + float sizeZ = query.aabb[j * 6 + 5]; + + projectAABB(query, projected, posX, posY, posZ, sizeX, sizeY, sizeZ); + if (plugin.configShadowsEnabled && !isDebug) + expandAABBAlongShadow(projected, dirX, dirY, dirZ, EXPAND_FACTOR); + + if(!isAABBVisible(projected)) + continue; + + uboOcclusion.aabbs[alignedUboOffset++].set( + projected[0], projected[1], projected[2] + ); + uboOcclusion.aabbs[alignedUboOffset++].set( + projected[3], projected[4], projected[5] + ); + query.writtenCount++; + } + + if(!isDebug) + query.frustumCulled = query.writtenCount == 0; + + return alignedUboOffset; + } + + private boolean isAABBVisible(float[] aabb) { + float minX = aabb[0] - aabb[3]; + float minY = aabb[1] - aabb[4]; + float minZ = aabb[2] - aabb[5]; + + float maxX = aabb[0] + aabb[3]; + float maxY = aabb[1] + aabb[4]; + float maxZ = aabb[2] + aabb[5]; + + return HDUtils.isAABBIntersectingFrustum(minX, minY, minZ, maxX, maxY, maxZ, sceneFrustumPlanes); + } + + private void expandAABBAlongShadow( + float[] aabb, + float dirX, float dirY, float dirZ, + float expandFactor + ) { + float sizeX = aabb[3]; + float sizeY = aabb[4]; + float sizeZ = aabb[5]; + + float projectedExtent = dirX * sizeX + dirY * sizeY + dirZ * sizeZ; + float offset = projectedExtent * expandFactor; + + aabb[3] = sizeX + dirX * offset; + aabb[4] = sizeY + dirY * offset; + aabb[5] = sizeZ + dirZ * offset; + + aabb[0] += dirX * offset; + aabb[1] += dirY * offset; + aabb[2] += dirZ * offset; + } + + private void projectAABB( + OcclusionQuery query, + float[] out, + float posX, float posY, float posZ, + float sizeX, float sizeY, float sizeZ + ) { + if (query.worldView == null) { + out[0] = posX; + out[1] = posY; + out[2] = posZ; + out[3] = sizeX; + out[4] = sizeY; + out[5] = sizeZ; + return; + } + + query.worldView.project(vec4( + vec, + posX - sizeX, + posY - sizeY, + posZ - sizeZ, + 1.0f + )); + + float minX = vec[0]; + float minY = vec[1]; + float minZ = vec[2]; + + query.worldView.project(vec4( + vec, + posX + sizeX, + posY + sizeY, + posZ + sizeZ, + 1.0f + )); + + float maxX = vec[0]; + float maxY = vec[1]; + float maxZ = vec[2]; + + out[0] = (minX + maxX) * 0.5f; + out[1] = (minY + maxY) * 0.5f; + out[2] = (minZ + maxZ) * 0.5f; + out[3] = (maxX - minX) * 0.5f; + out[4] = (maxY - minY) * 0.5f; + out[5] = (maxZ - minZ) * 0.5f; + } + + private void flushQueries(List queries, int start, int end, boolean isDebug) { + uboOcclusion.upload(); + for (int i = start; i < end; i++) { + final OcclusionQuery query = queries.get(i); + if (query.count <= 0 || query.writtenCount <= 0 || query.frustumCulled) + continue; + assert query.writtenCount < UBOOcclusion.MAX_AABBS : "Exceeded Max AABBS"; + assert query.writtenCount <= query.count : "Exceeded query aabb written:" + query.writtenCount + " count:" + query.count; + + uboOcclusion.bindRange(uboOcclusion.aabbs[query.uboOffset], uboOcclusion.aabbs[query.uboOffset + 1 + (query.writtenCount * 2)]); + + if (isDebug) { + if(debugVisibility > 0) { + if (debugVisibility == 1 && !query.isStatic) + continue; + + if (debugVisibility == 2 && query.isStatic) + continue; + } + occlusionDebugProgram.queryId.set(query.id[0]); + glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0, query.writtenCount); + } else { + glBeginQuery(anySamplesPassedTarget, query.getSampleId()); + glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0, query.writtenCount); + glEndQuery(anySamplesPassedTarget); + query.advance(); + } + } + } + + public void shutdown() { + if (glCubeVAO != 0) + glDeleteVertexArrays(glCubeVAO); + glCubeVAO = 0; + + if (glCubeEBO != 0) + glDeleteBuffers(glCubeEBO); + glCubeEBO = 0; + + if (glCubeVBO != 0) + glDeleteBuffers(glCubeVBO); + glCubeVBO = 0; + } + + public final class OcclusionQuery { + private final int[] id = new int[FRAMES_IN_FLIGHT]; + private final boolean[] sampled = new boolean[FRAMES_IN_FLIGHT]; + + @Getter + private boolean queued; + + private boolean occluded; + private boolean frustumCulled; + private boolean resetThisFrame; + private boolean globalAABB; + private boolean isStatic; + + private int activeId; + + private int uboOffset; + private float offsetX; + private float offsetY; + private float offsetZ; + + private float globalMinX = Float.POSITIVE_INFINITY; + private float globalMinY = Float.POSITIVE_INFINITY; + private float globalMinZ = Float.POSITIVE_INFINITY; + private float globalMaxX = Float.NEGATIVE_INFINITY; + private float globalMaxY = Float.NEGATIVE_INFINITY; + private float globalMaxZ = Float.NEGATIVE_INFINITY; + + @Setter + private WorldViewStruct worldView; + + private float[] aabb = new float[6]; + private int count = 0; + private int writtenCount = 0; + + private void advance() { + activeId = (activeId + 1) % FRAMES_IN_FLIGHT; + } + + private int getReadbackId() { + int idx = (activeId + 1) % FRAMES_IN_FLIGHT; + if (!sampled[idx]) + return 0; + + sampled[idx] = false; + return id[idx]; + } + + private int getSampleId() { + sampled[activeId] = true; + return id[activeId]; + } + + public boolean isOccluded() { + return (occluded || frustumCulled) && active; + } + + public boolean isVisible() { + return !isOccluded(); + } + + public void setOffset(float x, float y, float z) { + offsetX = x; + offsetY = y; + offsetZ = z; + } + + public void addSphere(float x, float y, float z, float radius) { + addAABB(x, y, z, radius, radius, radius); + } + + public void addAABB(AABB aabb) { + addAABB(aabb, 0, 0, 0); + } + + public void addAABB(AABB aabb, float x, float y, float z) { + addAABB( + x + aabb.getCenterX(), + y + aabb.getCenterY(), + z + aabb.getCenterZ(), + aabb.getExtremeX(), + aabb.getExtremeY(), + aabb.getExtremeZ() + ); + } + + public void addMinMax( + float minX, float minY, float minZ, + float maxX, float maxY, float maxZ + ) { + float sizeX = (maxX - minX) * 0.5f; + float sizeY = (maxY - minY) * 0.5f; + float sizeZ = (maxZ - minZ) * 0.5f; + + addAABB( + minX + sizeX, + minY + sizeY, + minZ + sizeZ, + sizeX, + sizeY, + sizeZ + ); + } + + public void addAABB( + float posX, float posY, float posZ, + float sizeX, float sizeY, float sizeZ + ) { + if (count >= UBOOcclusion.MAX_AABBS) { + log.warn("Tried to add too many AABBs to OcclusionQuery"); + return; + } + assert !isStatic; + + if (count * 6 >= aabb.length) { + aabb = Arrays.copyOf( + aabb, + Math.min(aabb.length * 2, UBOOcclusion.MAX_AABBS * 6) + ); + } + + int base = count * 6; + + aabb[base] = posX; + aabb[base + 1] = posY; + aabb[base + 2] = posZ; + aabb[base + 3] = sizeX; + aabb[base + 4] = sizeY; + aabb[base + 5] = sizeZ; + + count++; + } + + public void setStatic() { + if (count == 0) + return; + + globalMinX = globalMinY = globalMinZ = Float.POSITIVE_INFINITY; + globalMaxX = globalMaxY = globalMaxZ = Float.NEGATIVE_INFINITY; + isStatic = true; + + int writeIndex = 0; + for (int i = 0; i < count; i++) { + int base1 = i * 6; + + float posX1 = aabb[base1]; + float posY1 = aabb[base1 + 1]; + float posZ1 = aabb[base1 + 2]; + float sizeX1 = aabb[base1 + 3]; + float sizeY1 = aabb[base1 + 4]; + float sizeZ1 = aabb[base1 + 5]; + + float minX1 = posX1 - sizeX1; + float minY1 = posY1 - sizeY1; + float minZ1 = posZ1 - sizeZ1; + float maxX1 = posX1 + sizeX1; + float maxY1 = posY1 + sizeY1; + float maxZ1 = posZ1 + sizeZ1; + + boolean encapsulated = false; + + for (int j = 0; j < count; j++) { + if (i == j) + continue; + + int base2 = j * 6; + float posX2 = aabb[base2]; + float posY2 = aabb[base2 + 1]; + float posZ2 = aabb[base2 + 2]; + float sizeX2 = aabb[base2 + 3]; + float sizeY2 = aabb[base2 + 4]; + float sizeZ2 = aabb[base2 + 5]; + + float minX2 = posX2 - sizeX2; + float minY2 = posY2 - sizeY2; + float minZ2 = posZ2 - sizeZ2; + float maxX2 = posX2 + sizeX2; + float maxY2 = posY2 + sizeY2; + float maxZ2 = posZ2 + sizeZ2; + + if (minX1 >= minX2 && maxX1 <= maxX2 && + minY1 >= minY2 && maxY1 <= maxY2 && + minZ1 >= minZ2 && maxZ1 <= maxZ2) { + encapsulated = true; + break; + } + } + + if (!encapsulated) { + // Move this surviving AABB in-place to the writeIndex position + if (writeIndex != i) { + int baseWrite = writeIndex * 6; + aabb[baseWrite] = posX1; + aabb[baseWrite + 1] = posY1; + aabb[baseWrite + 2] = posZ1; + aabb[baseWrite + 3] = sizeX1; + aabb[baseWrite + 4] = sizeY1; + aabb[baseWrite + 5] = sizeZ1; + } + writeIndex++; + + // Update global bounds + globalMinX = Math.min(globalMinX, minX1); + globalMinY = Math.min(globalMinY, minY1); + globalMinZ = Math.min(globalMinZ, minZ1); + globalMaxX = Math.max(globalMaxX, maxX1); + globalMaxY = Math.max(globalMaxY, maxY1); + globalMaxZ = Math.max(globalMaxZ, maxZ1); + } + } + globalAABB = true; + count = writeIndex; + } + + public void reset() { + count = 0; + resetThisFrame = true; + } + + public void queue() { + if (!active || queued) + return; + + queued = true; + + synchronized (queuedQueries) { + queuedQueries.add(this); + } + } + + public void free() { + count = 0; + queued = false; + occluded = false; + isStatic = false; + frustumCulled = false; + resetThisFrame = false; + worldView = null; + + offsetX = offsetY = offsetZ = 0f; + + globalMinX = globalMinY = globalMinZ = Float.POSITIVE_INFINITY; + globalMaxX = globalMaxY = globalMaxZ = Float.NEGATIVE_INFINITY; + globalAABB = false; + + Arrays.fill(sampled, false); + + freeQueries.add(this); + } + } +} diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index c24a1ac5c7..59c2317e6a 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -24,6 +24,7 @@ */ package rs117.hd.renderer.zone; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; import javax.inject.Inject; @@ -127,6 +128,10 @@ public interface OnBeforeProcessTileFunc { private short[][][] underlayIds; private int[][][] tileHeights; + private final float[] abbMin = new float[3]; + private final float[] abbMax = new float[3]; + private final float[] abbVec = new float[3]; + private final int[] worldPos = new int[3]; private final int[][] vertices = new int[4][3]; private final int[] vertexKeys = new int[4]; @@ -237,6 +242,7 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws uploadZoneWater(ctx, zone, mzx, mzz, vb, fb); zone.levelOffsets[Zone.LEVEL_WATER_SURFACE] = vb.position(); } + zone.occlusionQuery.setStatic(); } private void uploadZoneLevel( @@ -252,6 +258,8 @@ private void uploadZoneLevel( GpuIntBuffer fb ) throws InterruptedException { int ridx = 0; + Arrays.fill(abbMin, Float.MAX_VALUE); + Arrays.fill(abbMax, -Float.MAX_VALUE); // upload the roofs and save their positions for (int id : roofIds) { @@ -271,6 +279,11 @@ private void uploadZoneLevel( // upload everything else uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, 0, visbelow, vb, ab, fb); + + if(abbMin[0] != Float.MAX_VALUE && abbMin[1] != Float.MAX_VALUE && abbMin[2] != Float.MAX_VALUE && + abbMax[0] != -Float.MAX_VALUE && abbMax[1] != -Float.MAX_VALUE && abbMax[2] != -Float.MAX_VALUE) { + zone.occlusionQuery.addMinMax(abbMin[0], abbMin[1] - LOCAL_HALF_TILE_SIZE, abbMin[2], abbMax[0], abbMax[1] + LOCAL_HALF_TILE_SIZE, abbMax[2]); + } } private void uploadZoneLevelRoof( @@ -335,6 +348,9 @@ private void uploadZoneWater( this.basex = (mzx - (ctx.sceneOffset >> 3)) << 10; this.basez = (mzz - (ctx.sceneOffset >> 3)) << 10; + Arrays.fill(abbMin, Float.MAX_VALUE); + Arrays.fill(abbMax, -Float.MAX_VALUE); + for (int level = 0; level < MAX_Z; level++) { for (int xoff = 0; xoff < 8; ++xoff) { for (int zoff = 0; zoff < 8; ++zoff) { @@ -349,6 +365,11 @@ private void uploadZoneWater( } } } + + if(abbMin[0] != Float.MAX_VALUE && abbMin[1] != Float.MAX_VALUE && abbMin[2] != Float.MAX_VALUE && + abbMax[0] != -Float.MAX_VALUE && abbMax[1] != -Float.MAX_VALUE && abbMax[2] != -Float.MAX_VALUE) { + zone.occlusionQuery.addMinMax(abbMin[0], abbMin[1], abbMin[2], abbMax[0], abbMax[1], abbMax[2]); + } } private void estimateZoneTileSize(ZoneSceneContext ctx, Zone z, Tile t) { @@ -730,7 +751,7 @@ private void uploadZoneRenderable( int alphaStart = alphaBuffer != null ? alphaBuffer.position() : 0; try { uploadStaticModel( - ctx, tile, model, modelOverride, uuid, + ctx, zone, tile, model, modelOverride, uuid, preOrientation, orient, x - basex, y, z - basez, opaqueBuffer, @@ -999,21 +1020,18 @@ private void uploadTilePaint( neMaterialData, nwMaterialData, seMaterialData, neTerrainData, nwTerrainData, seTerrainData ); - vb.putVertex( lx2, neHeight, lz2, uvx, uvy, 0, neNormals[0], neNormals[2], neNormals[1], texturedFaceIdx, 0 ); - vb.putVertex( lx3, nwHeight, lz3, uvx - uvcos, uvy - uvsin, 0, nwNormals[0], nwNormals[2], nwNormals[1], texturedFaceIdx, 0 ); - vb.putVertex( lx1, seHeight, lz1, uvx + uvsin, uvy - uvcos, 0, @@ -1026,7 +1044,6 @@ private void uploadTilePaint( swMaterialData, seMaterialData, nwMaterialData, swTerrainData, seTerrainData, nwTerrainData ); - vb.putVertex( lx0, swHeight, lz0, uvx - uvcos + uvsin, uvy - uvsin - uvcos, 0, @@ -1047,6 +1064,22 @@ private void uploadTilePaint( nwNormals[0], nwNormals[2], nwNormals[1], texturedFaceIdx, 0 ); + + vec3(abbVec, lx2, neHeight, lz2); + min(abbMin, abbMin, abbVec); + max(abbMax, abbMax, abbVec); + + vec3(abbVec, lx3, nwHeight, lz3); + min(abbMin, abbMin, abbVec); + max(abbMax, abbMax, abbVec); + + vec3(abbVec, lx1, seHeight, lz1); + min(abbMin, abbMin, abbVec); + max(abbMax, abbMax, abbVec); + + vec3(abbVec, lx0, swHeight, lz0); + min(abbMin, abbMin, abbVec); + max(abbMax, abbMax, abbVec); } private void uploadTileModel( @@ -1331,12 +1364,25 @@ private void uploadTileModel( normalsC[0], normalsC[2], normalsC[1], texturedFaceIdx, 0 ); + + vec3(abbVec, lx0, ly0, lz0); + min(abbMin, abbMin, abbVec); + max(abbMax, abbMax, abbVec); + + vec3(abbVec, lx1, ly1, lz1); + min(abbMin, abbMin, abbVec); + max(abbMax, abbMax, abbVec); + + vec3(abbVec, lx2, ly2, lz2); + min(abbMin, abbMin, abbVec); + max(abbMax, abbMax, abbVec); } } // scene upload private int uploadStaticModel( ZoneSceneContext ctx, + Zone zone, Tile tile, Model model, ModelOverride modelOverride, @@ -1687,8 +1733,13 @@ private int uploadStaticModel( modelNormals[6], modelNormals[7], modelNormals[8], texturedFaceIdx, depthBias ); + len += 3; } + + if(len > 0) + zone.occlusionQuery.addAABB(model.getAABB(orientation), x, y, z); + writeCache.flush(); return len; } diff --git a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java index 8a7d1380ce..8ffbfb6d40 100644 --- a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java +++ b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java @@ -166,6 +166,9 @@ void sortStaticAlphaModels(Camera camera) { if (z.alphaModels.isEmpty() || (worldViewId == -1 && !z.inSceneFrustum)) continue; + if(z.occlusionQuery != null && z.occlusionQuery.isOccluded()) + continue; + final int dx = camPosX - ((zx - offset) << 10); final int dz = camPosZ - ((zz - offset) << 10); z.dist = dx * dx + dz * dz; diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index dfbc57f4c3..f402c882c2 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -17,6 +17,7 @@ import net.runelite.api.*; import org.lwjgl.system.MemoryStack; import rs117.hd.HdPlugin; +import rs117.hd.renderer.zone.OcclusionManager.OcclusionQuery; import rs117.hd.scene.MaterialManager; import rs117.hd.scene.SceneContext; import rs117.hd.scene.materials.Material; @@ -74,6 +75,9 @@ public class Zone { @Nullable public GLBuffer vboO, vboA, vboM; public GLTextureBuffer tboF; + public OcclusionQuery occlusionQuery; + public ConcurrentLinkedQueue additionalOcclusionQueries = new ConcurrentLinkedQueue<>(); + public boolean isFullyOccluded; public boolean initialized; // whether the zone vao and vbos are ready public boolean cull; // whether the zone is queued for deletion @@ -121,6 +125,7 @@ public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f, int eboShared) } tboF = f; + occlusionQuery = OcclusionManager.getInstance().obtainQuery(); } public static void freeZones(@Nullable Zone[][] zones) { @@ -169,6 +174,11 @@ public void free() { uploadJob = null; } + if(occlusionQuery != null) { + occlusionQuery.free(); + occlusionQuery = null; + } + sortedAlphaFacesUpload.release(); sizeO = 0; @@ -194,6 +204,30 @@ public void free() { alphaModels.clear(); } + public void evaluateOcclusion(){ + if(occlusionQuery != null) { + isFullyOccluded = occlusionQuery.isOccluded(); + if(isFullyOccluded) { + // Check if any of the dynamic occlusion queries are not occluded + for(OcclusionQuery dynamicQuery : additionalOcclusionQueries) { + if(dynamicQuery.isVisible()) { + isFullyOccluded = false; + break; + } + } + + if(isFullyOccluded) { + // Zone is fully occluded, we need to requeue all dynamic queries since they are revelvant to if the zone is fully occluded + for(OcclusionQuery dynamicQuery : additionalOcclusionQueries) + dynamicQuery.queue(); + } + } + occlusionQuery.queue(); + } + if(!isFullyOccluded) // Dynamics will reappend when they are processed + additionalOcclusionQueries.clear(); + } + public static void processPendingDeletions() { int leakCount = 0; GLBuffer vbo; @@ -310,6 +344,11 @@ public void setMetadata(WorldViewContext viewContext, SceneContext sceneContext, int baseX = (mx - (sceneContext.sceneOffset >> 3)) << 10; int baseZ = (mz - (sceneContext.sceneOffset >> 3)) << 10; + if(occlusionQuery != null) { + occlusionQuery.setOffset(baseX, 0, baseZ); + occlusionQuery.setWorldView(viewContext.uboWorldViewStruct); + } + try (MemoryStack stack = MemoryStack.stackPush()) { IntBuffer buf = stack.mallocInt(3) .put(viewContext.uboWorldViewStruct != null ? viewContext.uboWorldViewStruct.worldViewIdx + 1 : 0) @@ -949,7 +988,8 @@ synchronized void multizoneLocs(SceneContext ctx, int zx, int zz, Camera camera, int zx2 = (centerX >> 10) + offset; int zz2 = (centerZ >> 10) + offset; if (zx2 >= 0 && zx2 < zones.length && zz2 >= 0 && zz2 < zones[0].length) { - if (zones[zx2][zz2].inSceneFrustum && zones[zx2][zz2].initialized) { + Zone z2 = zones[zx2][zz2]; + if(z2.inSceneFrustum && z2.initialized && (z2.occlusionQuery == null || z2.occlusionQuery.isVisible())) { max = distance; closestZoneX = centerX >> 10; closestZoneZ = centerZ >> 10; diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 10cc8537a1..ca8f957dc1 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -33,6 +33,7 @@ import net.runelite.api.*; import net.runelite.api.events.*; import net.runelite.api.hooks.*; +import net.runelite.client.callback.RenderCallbackManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.ui.DrawManager; import org.lwjgl.opengl.*; @@ -47,6 +48,7 @@ import rs117.hd.opengl.shader.ShaderIncludes; import rs117.hd.opengl.shader.ShadowShaderProgram; import rs117.hd.opengl.uniforms.UBOLights; +import rs117.hd.opengl.uniforms.UBOOcclusion; import rs117.hd.opengl.uniforms.UBOWorldViews; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; @@ -94,6 +96,7 @@ public class ZoneRenderer implements Renderer { private static int UNIFORM_BLOCK_COUNT = HdPlugin.UNIFORM_BLOCK_COUNT; public static final int UNIFORM_BLOCK_WORLD_VIEWS = UNIFORM_BLOCK_COUNT++; + public static final int UNIFORM_BLOCK_OCCLUSION = UNIFORM_BLOCK_COUNT++; @Inject private Client client; @@ -119,6 +122,12 @@ public class ZoneRenderer implements Renderer { @Inject private ModelStreamingManager modelStreamingManager; + @Inject + private OcclusionManager occlusionManager; + + @Inject + private RenderCallbackManager renderCallbackManager; + @Inject private FrameTimer frameTimer; @@ -139,8 +148,12 @@ public class ZoneRenderer implements Renderer { @Inject private UBOWorldViews uboWorldViews; + + @Inject + private UBOOcclusion uboOcclusion; public final Camera sceneCamera = new Camera().setReverseZ(true); + public final Camera directionalCamera = new Camera().setOrthographic(true); public final ShadowCasterVolume directionalShadowCasterVolume = new ShadowCasterVolume(directionalCamera); @@ -186,9 +199,12 @@ public void initialize() { depthAlphaCmd.setFrameTimer(frameTimer); sceneCmd.setFrameTimer(frameTimer); directionalCmd.setFrameTimer(frameTimer); - - jobSystem.startUp(config.cpuUsageLimit()); + uboWorldViews.initialize(UNIFORM_BLOCK_WORLD_VIEWS); + uboOcclusion.initialize(UNIFORM_BLOCK_OCCLUSION); + jobSystem.startUp(config.cpuUsageLimit()); + modelStreamingManager.initialize(); + occlusionManager.initialize(renderState, uboOcclusion); sceneManager.initialize(renderState, uboWorldViews); modelStreamingManager.initialize(); @@ -201,10 +217,12 @@ public void initialize() { public void destroy() { destroyBuffers(); + occlusionManager.destroy(); jobSystem.shutDown(); modelStreamingManager.destroy(); sceneManager.destroy(); uboWorldViews.destroy(); + uboOcclusion.destroy(); SceneUploader.POOL = null; FacePrioritySorter.POOL = null; @@ -221,6 +239,7 @@ public void addShaderIncludes(ShaderIncludes includes) { includes .define("MAX_SIMULTANEOUS_WORLD_VIEWS", UBOWorldViews.MAX_SIMULTANEOUS_WORLD_VIEWS) .addInclude("WORLD_VIEW_GETTER", () -> plugin.generateGetter("WorldView", UBOWorldViews.MAX_SIMULTANEOUS_WORLD_VIEWS)) + .addUniformBuffer(uboOcclusion) .addUniformBuffer(uboWorldViews); } @@ -230,6 +249,7 @@ public void initializeShaders(ShaderIncludes includes) throws ShaderException, I sceneProgram.compile(includes); fastShadowProgram.compile(includes); detailedShadowProgram.compile(includes); + occlusionManager.initializeShaders(includes); } @Override @@ -238,6 +258,7 @@ public void destroyShaders() { sceneProgram.destroy(); fastShadowProgram.destroy(); detailedShadowProgram.destroy(); + occlusionManager.destroyShaders(); } private void initializeBuffers() { @@ -307,9 +328,15 @@ public void preSceneDraw( ctx.completeInvalidation(); int offset = ctx.sceneContext.sceneOffset >> 3; - for (int zx = 0; zx < ctx.sizeX; ++zx) - for (int zz = 0; zz < ctx.sizeZ; ++zz) - ctx.zones[zx][zz].multizoneLocs(ctx.sceneContext, zx - offset, zz - offset, sceneCamera, ctx.zones); + for (int zx = 0; zx < ctx.sizeX; ++zx) { + for (int zz = 0; zz < ctx.sizeZ; ++zz) { + final Zone zone = ctx.zones[zx][zz]; + if(!zone.initialized) + continue; + zone.multizoneLocs(ctx.sceneContext, zx - offset, zz - offset, sceneCamera, ctx.zones); + zone.evaluateOcclusion(); + } + } ctx.sortStaticAlphaModels(sceneCamera); @@ -385,6 +412,8 @@ private void preSceneDrawTopLevel( frameTimer.begin(Timer.UPDATE_LIGHTS); lightManager.update(ctx.sceneContext, plugin.cameraShift, plugin.cameraFrustum); frameTimer.end(Timer.UPDATE_LIGHTS); + + occlusionManager.readbackQueries(); } catch (Exception ex) { log.error("Error while updating environment or lights:", ex); plugin.stopPlugin(); @@ -705,6 +734,7 @@ private void depthPrePass() { renderState.enable.set(GL_CULL_FACE); renderState.enable.set(GL_DEPTH_TEST); + renderState.depthFunc.set(GL_GEQUAL); renderState.depthMask.set(true); renderState.colorMask.set(false, false, false, false); @@ -832,66 +862,62 @@ private void scenePass() { @Override public boolean zoneInFrustum(int zx, int zz, int maxY, int minY) { - if (!sceneManager.isTopLevelValid()) - return false; - - WorldViewContext ctx = sceneManager.getRoot(); - if (plugin.enableDetailedTimers) frameTimer.begin(Timer.VISIBILITY_CHECK); - int minX = zx * CHUNK_SIZE - ctx.sceneContext.sceneOffset; - int minZ = zz * CHUNK_SIZE - ctx.sceneContext.sceneOffset; - if (ctx.sceneContext.currentArea != null) { - var base = ctx.sceneContext.sceneBase; - assert base != null; - boolean inArea = ctx.sceneContext.currentArea.intersects( - true, base[0] + minX, base[1] + minZ, base[0] + minX + 7, base[1] + minZ + 7); - if (!inArea) { - if (plugin.enableDetailedTimers) frameTimer.end(Timer.VISIBILITY_CHECK); + try(var ignored = frameTimer.begin(Timer.VISIBILITY_CHECK)) { + if (!sceneManager.isTopLevelValid()) return false; + + WorldViewContext ctx = sceneManager.getRoot(); + int minX = zx * CHUNK_SIZE - ctx.sceneContext.sceneOffset; + int minZ = zz * CHUNK_SIZE - ctx.sceneContext.sceneOffset; + if (ctx.sceneContext.currentArea != null) { + var base = ctx.sceneContext.sceneBase; + assert base != null; + boolean inArea = ctx.sceneContext.currentArea.intersects( + true, base[0] + minX, base[1] + minZ, base[0] + minX + 7, base[1] + minZ + 7); + if (!inArea) { + return false; + } } - } - Zone zone = ctx.zones[zx][zz]; - if (plugin.freezeCulling) - return zone.inSceneFrustum || zone.inShadowFrustum; - - minX *= LOCAL_TILE_SIZE; - minZ *= LOCAL_TILE_SIZE; - int maxX = minX + CHUNK_SIZE * LOCAL_TILE_SIZE; - int maxZ = minZ + CHUNK_SIZE * LOCAL_TILE_SIZE; - if (zone.hasWater) { - maxY += ProceduralGenerator.MAX_DEPTH; - minY -= ProceduralGenerator.MAX_DEPTH; - } + Zone zone = ctx.zones[zx][zz]; + if (plugin.freezeCulling) + return zone.inSceneFrustum || zone.inShadowFrustum; - final int PADDING = 4 * LOCAL_TILE_SIZE; - zone.inSceneFrustum = sceneCamera.intersectsAABB( - minX - PADDING, minY, minZ - PADDING, maxX + PADDING, maxY, maxZ + PADDING); + if (zone.isFullyOccluded) + return zone.inSceneFrustum = zone.inShadowFrustum = false; - if (zone.inSceneFrustum) { - if (plugin.enableDetailedTimers) - frameTimer.end(Timer.VISIBILITY_CHECK); - return zone.inShadowFrustum = true; - } + minX *= LOCAL_TILE_SIZE; + minZ *= LOCAL_TILE_SIZE; + int maxX = minX + CHUNK_SIZE * LOCAL_TILE_SIZE; + int maxZ = minZ + CHUNK_SIZE * LOCAL_TILE_SIZE; + if (zone.hasWater) { + maxY += ProceduralGenerator.MAX_DEPTH; + minY -= ProceduralGenerator.MAX_DEPTH; + } + + final int PADDING = 4 * LOCAL_TILE_SIZE; + zone.inSceneFrustum = sceneCamera.intersectsAABB( + minX - PADDING, minY, minZ - PADDING, maxX + PADDING, maxY, maxZ + PADDING); - if (plugin.configShadowsEnabled && plugin.configExpandShadowDraw) { - zone.inShadowFrustum = directionalCamera.intersectsAABB(minX, minY, minZ, maxX, maxY, maxZ); - if (zone.inShadowFrustum) { - int centerX = minX + (maxX - minX) / 2; - int centerY = minY + (maxY - minY) / 2; - int centerZ = minZ + (maxZ - minZ) / 2; - zone.inShadowFrustum = directionalShadowCasterVolume.intersectsPoint(centerX, centerY, centerZ); + if (zone.inSceneFrustum) + return zone.inShadowFrustum = true; + + if (plugin.configShadowsEnabled && plugin.configExpandShadowDraw) { + zone.inShadowFrustum = directionalCamera.intersectsAABB(minX, minY, minZ, maxX, maxY, maxZ); + if (zone.inShadowFrustum) { + int centerX = minX + (maxX - minX) / 2; + int centerY = minY + (maxY - minY) / 2; + int centerZ = minZ + (maxZ - minZ) / 2; + zone.inShadowFrustum = directionalShadowCasterVolume.intersectsPoint(centerX, centerY, centerZ); + } + return zone.inShadowFrustum; } - if (plugin.enableDetailedTimers) - frameTimer.end(Timer.VISIBILITY_CHECK); - return zone.inShadowFrustum; - } - if (plugin.enableDetailedTimers) - frameTimer.end(Timer.VISIBILITY_CHECK); - if (plugin.orthographicProjection) - return zone.inSceneFrustum = true; + if (plugin.orthographicProjection) + return zone.inSceneFrustum = true; - return false; + return false; + } } @Override @@ -903,7 +929,7 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int return; Zone z = ctx.zones[zx][zz]; - if (!z.initialized || z.sizeO == 0) + if(!z.initialized || z.sizeO == 0 || z.occlusionQuery == null || z.occlusionQuery.isOccluded()) return; frameTimer.begin(Timer.DRAW_ZONE_OPAQUE); @@ -933,7 +959,7 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i return; final Zone z = ctx.zones[zx][zz]; - if (!z.initialized) + if (!z.initialized || z.occlusionQuery == null || z.occlusionQuery.isOccluded()) return; frameTimer.begin(Timer.DRAW_ZONE_ALPHA); @@ -942,7 +968,9 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i z.renderOpaqueLevel(tempCmd, Zone.LEVEL_WATER_SURFACE); depthAlphaCmd.append(tempCmd); + sceneCmd.DepthMask(false); sceneCmd.append(tempCmd); + sceneCmd.DepthMask(true); tempCmd.reset(); } @@ -1120,6 +1148,7 @@ public void draw(int overlayColor) { tiledLightingPass(); directionalShadowPass(); scenePass(); + occlusionManager.occlusionDebugPass(); } if (sceneFboValid && plugin.sceneResolution != null && plugin.sceneViewport != null) { @@ -1160,7 +1189,6 @@ public void draw(int overlayColor) { jobSystem.processPendingClientCallbacks(); - frameTimer.end(Timer.DRAW_FRAME); frameTimer.end(Timer.RENDER_FRAME); try { @@ -1174,11 +1202,12 @@ public void draw(int overlayColor) { // this might be AWT shutting down on VM shutdown, ignore it return; } - log.error("Unable to swap buffers:", ex); } + occlusionManager.occlusionPass(); glBindFramebuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); + frameTimer.end(Timer.DRAW_FRAME); frameTimer.endFrameAndReset(); checkGLErrors(); diff --git a/src/main/java/rs117/hd/utils/Camera.java b/src/main/java/rs117/hd/utils/Camera.java index fc63b0d288..2f7dd49fba 100644 --- a/src/main/java/rs117/hd/utils/Camera.java +++ b/src/main/java/rs117/hd/utils/Camera.java @@ -203,8 +203,12 @@ public Camera setPosition(float[] newPosition) { return this; } + public float[] getPosition(float[] out) { + return copyTo(out, position); + } + public float[] getPosition() { - return copy(position); + return getPosition(new float[3]); } public float distanceTo(float[] point) { diff --git a/src/main/java/rs117/hd/utils/DeveloperTools.java b/src/main/java/rs117/hd/utils/DeveloperTools.java index 36d42b6171..287e6b52c9 100644 --- a/src/main/java/rs117/hd/utils/DeveloperTools.java +++ b/src/main/java/rs117/hd/utils/DeveloperTools.java @@ -17,6 +17,7 @@ import rs117.hd.overlays.ShadowMapOverlay; import rs117.hd.overlays.TileInfoOverlay; import rs117.hd.overlays.TiledLightingOverlay; +import rs117.hd.renderer.zone.OcclusionManager; import rs117.hd.scene.AreaManager; import rs117.hd.scene.areas.AABB; import rs117.hd.scene.areas.Area; @@ -37,6 +38,8 @@ public class DeveloperTools implements KeyListener { private static final Keybind KEY_TOGGLE_ORTHOGRAPHIC = new Keybind(KeyEvent.VK_TAB, SHIFT_DOWN_MASK); private static final Keybind KEY_TOGGLE_HIDE_UI = new Keybind(KeyEvent.VK_H, CTRL_DOWN_MASK); private static final Keybind KEY_RELOAD_SCENE = new Keybind(KeyEvent.VK_R, CTRL_DOWN_MASK); + private static final Keybind KEY_TOGGLE_OCCLUSION = new Keybind(KeyEvent.VK_O, CTRL_DOWN_MASK); + private static final Keybind KEY_TOGGLE_OCCLUSION_VISIBILITY = new Keybind(KeyEvent.VK_O, CTRL_DOWN_MASK | SHIFT_DOWN_MASK); @Inject private ClientThread clientThread; @@ -50,6 +53,9 @@ public class DeveloperTools implements KeyListener { @Inject private HdPlugin plugin; + @Inject + private OcclusionManager occlusionManager; + @Inject private TileInfoOverlay tileInfoOverlay; @@ -169,6 +175,9 @@ public void onCommandExecuted(CommandExecuted commandExecuted) { case "culling": plugin.freezeCulling = !plugin.freezeCulling; break; + case "occlusion": + occlusionManager.toggleDebug(); + break; } } @@ -194,6 +203,10 @@ public void keyPressed(KeyEvent e) { hideUiEnabled = !hideUiEnabled; } else if (KEY_RELOAD_SCENE.matches(e)) { plugin.renderer.reloadScene(); + } else if(KEY_TOGGLE_OCCLUSION.matches(e)) { + occlusionManager.toggleDebug(); + } else if(KEY_TOGGLE_OCCLUSION_VISIBILITY.matches(e)) { + occlusionManager.toggleDebugVisibility(); } else { return; } diff --git a/src/main/java/rs117/hd/utils/HDUtils.java b/src/main/java/rs117/hd/utils/HDUtils.java index 0d9b1da7fe..c4160458bc 100644 --- a/src/main/java/rs117/hd/utils/HDUtils.java +++ b/src/main/java/rs117/hd/utils/HDUtils.java @@ -420,12 +420,12 @@ public static boolean isTriangleIntersectingFrustum( } public static boolean isAABBIntersectingFrustum( - int minX, - int minY, - int minZ, - int maxX, - int maxY, - int maxZ, + float minX, + float minY, + float minZ, + float maxX, + float maxY, + float maxZ, float[][] cullingPlanes ) { for (float[] plane : cullingPlanes) { @@ -508,4 +508,22 @@ public static JFrame getJFrame(Canvas canvas) { return null; } + + public static long align(long value, long alignment) { + return align(value, alignment, false); + } + + public static long align(long value, long alignment, boolean up) { + assert alignment > 0 : "Alignment must be positive"; + assert (alignment & (alignment - 1)) == 0 + : "Alignment must be a power of two"; + + if (up) { + // Align up + return (value + alignment - 1) & -alignment; + } else { + // Align down + return value & -alignment; + } + } } diff --git a/src/main/resources/rs117/hd/occlusion_debug_frag.glsl b/src/main/resources/rs117/hd/occlusion_debug_frag.glsl new file mode 100644 index 0000000000..0a9295d65d --- /dev/null +++ b/src/main/resources/rs117/hd/occlusion_debug_frag.glsl @@ -0,0 +1,12 @@ +#version 330 + +#include + +out vec4 fragColor; + +uniform int queryId; + +void main() { + float hue = float(queryId % 1024) / 1024.0; + fragColor = vec4(hsv2rgb(hue), 0.25); +} \ No newline at end of file diff --git a/src/main/resources/rs117/hd/occlusion_vert.glsl b/src/main/resources/rs117/hd/occlusion_vert.glsl new file mode 100644 index 0000000000..458fc25bc7 --- /dev/null +++ b/src/main/resources/rs117/hd/occlusion_vert.glsl @@ -0,0 +1,13 @@ +#version 330 + +#include +#include + +layout (location = 0) in vec3 vPosition; + + void main() { + vec3 position = aabbs[gl_InstanceID * 2]; + vec3 scale = aabbs[gl_InstanceID * 2 + 1]; + vec3 worldPosition = vPosition * scale + position; + gl_Position = projectionMatrix * vec4(worldPosition, 1.0); +} \ No newline at end of file diff --git a/src/main/resources/rs117/hd/scene_frag.glsl b/src/main/resources/rs117/hd/scene_frag.glsl index d547221791..21859cc384 100644 --- a/src/main/resources/rs117/hd/scene_frag.glsl +++ b/src/main/resources/rs117/hd/scene_frag.glsl @@ -125,7 +125,7 @@ void main() { vec4 outputColor = vec4(1); #if DISPLAY_DEPTH - float depth = texelFetch(sceneAlphaDepth, ivec2(gl_FragCoord.xy), 0).r; + float depth = texelFetch(sceneOpaqueDepth, ivec2(gl_FragCoord.xy), 0).r; FragColor = vec4(depth, 0.0, 0.0, 1.0); if (DISPLAY_DEPTH == 1) return; // Redundant, for syntax highlighting in IntelliJ #endif diff --git a/src/main/resources/rs117/hd/uniforms/occlusion.glsl b/src/main/resources/rs117/hd/uniforms/occlusion.glsl new file mode 100644 index 0000000000..9ce45624d3 --- /dev/null +++ b/src/main/resources/rs117/hd/uniforms/occlusion.glsl @@ -0,0 +1,6 @@ +#pragma once + +layout(std140) uniform UBOOcclusion { + vec3 aabbs[4000]; +}; + diff --git a/src/main/resources/rs117/hd/utils/color_utils.glsl b/src/main/resources/rs117/hd/utils/color_utils.glsl index 163f6c2d24..5a434c6a82 100644 --- a/src/main/resources/rs117/hd/utils/color_utils.glsl +++ b/src/main/resources/rs117/hd/utils/color_utils.glsl @@ -110,6 +110,26 @@ float linearToSrgb(float rgb) { step(0.0031308, rgb)); } +vec3 hsv2rgb(float h) { + float s = 1.0; + float v = 1.0; + + float i = floor(h * 6.0); + float f = h * 6.0 - i; + float p = v * (1.0 - s); + float q = v * (1.0 - f * s); + float t = v * (1.0 - (1.0 - f) * s); + + int modI = int(mod(i, 6.0)); + + if (modI == 0) return vec3(v, t, p); + if (modI == 1) return vec3(q, v, p); + if (modI == 2) return vec3(p, v, t); + if (modI == 3) return vec3(p, q, v); + if (modI == 4) return vec3(t, p, v); + return vec3(v, p, q); +} + // https://web.archive.org/web/20230619214343/https://en.wikipedia.org/wiki/HSL_and_HSV#Color_conversion_formulae vec3 srgbToHsl(vec3 srgb) { float V = max(max(srgb.r, srgb.g), srgb.b); From 53a29c2866af0ee52c24dd5e2933ecd1ce270cde Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 01:23:23 +0000 Subject: [PATCH 06/21] Blit Scene Depth to an occlusion depth buffer at quarter res --- .../hd/renderer/zone/OcclusionManager.java | 87 ++++++++++++++++++- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java index 45320c01ce..3032fdfd4b 100644 --- a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -65,7 +65,6 @@ public final class OcclusionManager { private final ConcurrentLinkedQueue freeQueries = new ConcurrentLinkedQueue<>(); private final List queuedQueries = new ArrayList<>(); private final List prevQueuedQueries = new ArrayList<>(); - private final int[] result = new int[1]; private final float[] vec = new float[4]; private final float[][] sceneFrustumPlanes = new float[6][4]; private final float[] directionalFwd = new float[3]; @@ -94,6 +93,16 @@ public final class OcclusionManager { @Getter private int passedQueryCount; + private int fboOcclusionDepth = 0; + private int rboOcclusionDepth = 0; + + private int occlusionWidth = 0; + private int occlusionHeight = 0; + + private static final int OCCLUSION_DOWNSCALE = 4; + private static final int MIN_OCCLUSION_SIZE = 256; + private static final int MAX_OCCLUSION_SIZE = 1024; + private int glCubeVAO; private int glCubeVBO; private int glCubeEBO; @@ -266,6 +275,64 @@ private void processDynamicOcclusionQueries(Iterator queries, int start, int end, bool } public void shutdown() { + destroyOcclusionFbo(); + if (glCubeVAO != 0) glDeleteVertexArrays(glCubeVAO); glCubeVAO = 0; From f8d6c50e5cc27087a43fe1ef882b42619e7f414f Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 01:25:22 +0000 Subject: [PATCH 07/21] Disable occlusion culling if shader didn;t compile --- src/main/java/rs117/hd/renderer/zone/OcclusionManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java index 3032fdfd4b..0bff3830bf 100644 --- a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -334,7 +334,7 @@ private void ensureOcclusionFbo() { } public void readbackQueries() { - active = config.occlusionCulling(); + active = config.occlusionCulling() && occlusionProgram.isValid(); processDynamicOcclusionQueries(dynamicOcclusionQuerySet.iterator()); processDynamicOcclusionQueries(tempOcclusionQuerySet.iterator()); From b3ee5a5c3427d4757180ed5bfbff24ea3ce51b28 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 01:27:24 +0000 Subject: [PATCH 08/21] Removed texturedFaces uniform from depth shader --- .../java/rs117/hd/opengl/shader/DepthShaderProgram.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java index 04cf7582e2..d8c40fa7c5 100644 --- a/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java @@ -8,8 +8,6 @@ import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; public class DepthShaderProgram extends ShaderProgram { - protected final UniformTexture uniTextureFaces = addUniformTexture("textureFaces"); - public DepthShaderProgram() { super(t -> { t.add(GL_VERTEX_SHADER, "depth_vert.glsl"); @@ -17,9 +15,4 @@ public DepthShaderProgram() { t.add(GL_FRAGMENT_SHADER, "depth_frag.glsl"); }); } - - @Override - protected void initialize() { - uniTextureFaces.set(TEXTURE_UNIT_TEXTURED_FACES); - } } From e0fa06742f326a2194ea869eeab358ea299c6097 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 01:34:04 +0000 Subject: [PATCH 09/21] Sync --- .../rs117/hd/renderer/zone/OcclusionManager.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java index 0bff3830bf..919147441f 100644 --- a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -99,6 +99,8 @@ public final class OcclusionManager { private int occlusionWidth = 0; private int occlusionHeight = 0; + private final int result[] = new int[1]; + private static final int OCCLUSION_DOWNSCALE = 4; private static final int MIN_OCCLUSION_SIZE = 256; private static final int MAX_OCCLUSION_SIZE = 1024; @@ -358,9 +360,13 @@ public void readbackQueries() { if (query.frustumCulled) continue; - query.occluded = glGetQueryObjecti(id, GL_QUERY_RESULT) == 0; - if (!query.occluded) - passedQueryCount++; + glGetQueryObjectuiv(id, GL_QUERY_RESULT_AVAILABLE, result); + if(result[0] != 0) { + glGetQueryObjectuiv(id, GL_QUERY_RESULT, result); + query.occluded = result[0] == 0; + if (!query.occluded) + passedQueryCount++; + } } frameTimer.end(Timer.OCCLUSION_READBACK); prevQueuedQueries.clear(); From a10680df25b13bd0b1e9ec22708d690ca51bc7ef Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 02:27:21 +0000 Subject: [PATCH 10/21] Resolve depth before blitting TODO: move sceneDepthFBo resolve out of occlusion so that tiledlighting min/max & scene shader can use it --- .../hd/renderer/zone/OcclusionManager.java | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java index 919147441f..e7b3c40a15 100644 --- a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -93,6 +93,9 @@ public final class OcclusionManager { @Getter private int passedQueryCount; + private int fboDepthResolve = 0; + private int rboDepthResolve = 0; + private int fboOcclusionDepth = 0; private int rboOcclusionDepth = 0; @@ -287,6 +290,49 @@ private void destroyOcclusionFbo() { glDeleteFramebuffers(fboOcclusionDepth); fboOcclusionDepth = 0; } + + if (rboOcclusionDepth != 0) { + glDeleteRenderbuffers(rboOcclusionDepth); + rboOcclusionDepth = 0; + } + + if (fboOcclusionDepth != 0) { + glDeleteFramebuffers(fboOcclusionDepth); + fboOcclusionDepth = 0; + } + } + + private void ensureDepthResolveFbo() { + if (fboDepthResolve != 0 || plugin.msaaSamples == 0) + return; + + fboDepthResolve = glGenFramebuffers(); + glBindFramebuffer(GL_FRAMEBUFFER, fboDepthResolve); + + rboDepthResolve = glGenRenderbuffers(); + glBindRenderbuffer(GL_RENDERBUFFER, rboDepthResolve); + + glRenderbufferStorage( + GL_RENDERBUFFER, + GL_DEPTH_COMPONENT24, + plugin.sceneResolution[0], + plugin.sceneResolution[1] + ); + + glFramebufferRenderbuffer( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, + rboDepthResolve + ); + + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + throw new RuntimeException("Depth resolve FBO incomplete"); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); } private void ensureOcclusionFbo() { @@ -332,6 +378,8 @@ private void ensureOcclusionFbo() { throw new RuntimeException("Occlusion FBO incomplete: " + status); } + ensureDepthResolveFbo(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); } @@ -419,7 +467,25 @@ public void occlusionPass() { normalize(directionalFwd, directionalFwd); ensureOcclusionFbo(); - glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboSceneDepth); + + if(plugin.msaaSamples > 0) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboSceneDepth); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboDepthResolve); + + glBlitFramebuffer( + 0, 0, + plugin.sceneResolution[0], plugin.sceneResolution[1], + 0, 0, + plugin.sceneResolution[0], plugin.sceneResolution[1], + GL_DEPTH_BUFFER_BIT, + GL_NEAREST + ); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, fboDepthResolve); + } else { + glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboSceneDepth); + } + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboOcclusionDepth); glBlitFramebuffer( From 47a96e460cec0a2e7572dfe7a06c5d6d2e2d35eb Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 03:23:41 +0000 Subject: [PATCH 11/21] Moved Resolve out of Occlusion Manager --- src/main/java/rs117/hd/HdPlugin.java | 188 ++++++++++++------ .../hd/renderer/zone/OcclusionManager.java | 57 +----- .../rs117/hd/renderer/zone/ZoneRenderer.java | 17 +- src/main/resources/rs117/hd/scene_frag.glsl | 6 +- .../rs117/hd/tiled_lighting_frag.glsl | 6 +- 5 files changed, 149 insertions(+), 125 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index ed3dcc59ae..b4384bc424 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -370,10 +370,11 @@ public class HdPlugin extends Plugin { public int[] sceneResolution; public int fboScene; - public int fboSceneDepth; public int fboSceneAlphaDepth; private int rboSceneColor; public int fboSceneResolve; + public int fboSceneDepthResolve = 0; + private int rboSceneDepthResolve = 0; private int rboSceneResolveColor; private int texSceneDepth; private int texSceneAlphaDepth; @@ -513,10 +514,11 @@ protected void startUp() { return false; fboScene = 0; - fboSceneDepth = 0; rboSceneColor = 0; texSceneDepth = 0; texSceneAlphaDepth = 0; + fboSceneDepthResolve = 0; + rboSceneDepthResolve = 0; fboSceneResolve = 0; rboSceneResolveColor = 0; fboShadowMap = 0; @@ -1225,14 +1227,11 @@ public void updateSceneFbo() { client.getViewportHeight() }; - // Skip rendering when there's no viewport to render to, which happens while world hopping if (viewport[2] == 0 || viewport[3] == 0) return; - // DPI scaling and stretched mode also affects the game's viewport divide(sceneViewportScale, vec(actualUiResolution), vec(uiResolution)); if (sceneViewportScale[0] != 1 || sceneViewportScale[1] != 1) { - // Pad the viewport before scaling, so it always covers the game's viewport in the UI for (int i = 0; i < 2; i++) { viewport[i] -= 1; viewport[i + 2] += 2; @@ -1240,28 +1239,28 @@ public void updateSceneFbo() { viewport = round(multiply(vec(viewport), sceneViewportScale)); } - // Check if scene FBO needs to be recreated if (Arrays.equals(sceneViewport, viewport)) return; destroySceneFbo(); sceneViewport = viewport; - // Bind default FBO to check whether anti-aliasing is forced int defaultFramebuffer = awtContext.getFramebuffer(false); glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebuffer); + final int forcedAASamples = GLConstants.getForcedSamples(); - msaaSamples = forcedAASamples != 0 ? forcedAASamples : min(config.antiAliasingMode().getSamples(), getMaxSamples()); + msaaSamples = forcedAASamples != 0 + ? forcedAASamples + : min(config.antiAliasingMode().getSamples(), getMaxSamples()); - // Since there's seemingly no reliable way to check if the default framebuffer will do sRGB conversions with GL_FRAMEBUFFER_SRGB - // enabled, we always replace the default framebuffer with an sRGB one. We could technically support rendering to the default - // framebuffer when sRGB conversions aren't needed, but the goal is to transition to linear blending in the future anyway. - boolean sRGB = false; // This is currently unused + boolean sRGB = false; - // Some implementations (*cough* Apple) complain when blitting from an FBO without an alpha channel to a (default) FBO with alpha. - // To work around this, we select a format which includes an alpha channel, even though we don't need it. int defaultColorAttachment = defaultFramebuffer == 0 ? GL_BACK_LEFT : GL_COLOR_ATTACHMENT0; - int alphaBits = glGetFramebufferAttachmentParameteri(GL_FRAMEBUFFER, defaultColorAttachment, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE); + int alphaBits = glGetFramebufferAttachmentParameteri( + GL_FRAMEBUFFER, + defaultColorAttachment, + GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE + ); checkGLErrors(); boolean alpha = alphaBits > 0; @@ -1272,22 +1271,33 @@ public void updateSceneFbo() { float resolutionScale = config.sceneResolutionScale() / 100f; sceneResolution = round(max(vec(1), multiply(slice(vec(sceneViewport), 2), resolutionScale))); uboGlobal.sceneResolution.set(sceneResolution); - uboGlobal.upload(); // Ensure this is up to date with rendering + uboGlobal.upload(); + + // ------------------------------------------------- + // Create scene FBO + // ------------------------------------------------- - // Create and bind the FBO fboScene = glGenFramebuffers(); glBindFramebuffer(GL_FRAMEBUFFER, fboScene); - // Create color render buffer + // ------------------------------------------------- + // Color RBO + // ------------------------------------------------- + rboSceneColor = glGenRenderbuffers(); glBindRenderbuffer(GL_RENDERBUFFER, rboSceneColor); - // Flush out all pending errors, so we can check whether the next step succeeds clearGLErrors(); int format = 0; for (int desiredFormat : desiredFormats) { - glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaaSamples, desiredFormat, sceneResolution[0], sceneResolution[1]); + glRenderbufferStorageMultisample( + GL_RENDERBUFFER, + msaaSamples, + desiredFormat, + sceneResolution[0], + sceneResolution[1] + ); if (glGetError() == GL_NO_ERROR) { format = desiredFormat; @@ -1298,35 +1308,66 @@ public void updateSceneFbo() { if (format == 0) throw new RuntimeException("No supported " + (sRGB ? "sRGB" : "linear") + " formats"); - // Found a usable format. Bind the RBO to the scene FBO - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboSceneColor); + glFramebufferRenderbuffer( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, + rboSceneColor + ); checkGLErrors(); - // If necessary, create an FBO for resolving multisampling if (msaaSamples > 1 && resolutionScale != 1) { fboSceneResolve = glGenFramebuffers(); glBindFramebuffer(GL_FRAMEBUFFER, fboSceneResolve); + rboSceneResolveColor = glGenRenderbuffers(); glBindRenderbuffer(GL_RENDERBUFFER, rboSceneResolveColor); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, 0, format, sceneResolution[0], sceneResolution[1]); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rboSceneResolveColor); + glRenderbufferStorageMultisample( + GL_RENDERBUFFER, + 0, + format, + sceneResolution[0], + sceneResolution[1] + ); + + glFramebufferRenderbuffer( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, + rboSceneResolveColor + ); + checkGLErrors(); + + glBindFramebuffer(GL_FRAMEBUFFER, fboScene); } - int sceneDepthTarget = msaaSamples > 1 ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; - texSceneDepth = glGenTextures(); - glActiveTexture(TEXTURE_UNIT_SCENE_OPAQUE_DEPTH); - glBindTexture(sceneDepthTarget, texSceneDepth); - if(msaaSamples > 1) { - glTexImage2DMultisample( - GL_TEXTURE_2D_MULTISAMPLE, + if (msaaSamples > 1) { + // Multisampled depth renderbuffer + rboSceneDepthResolve = glGenRenderbuffers(); + glBindRenderbuffer(GL_RENDERBUFFER, rboSceneDepthResolve); + glRenderbufferStorageMultisample( + GL_RENDERBUFFER, msaaSamples, GL_DEPTH_COMPONENT24, sceneResolution[0], - sceneResolution[1], - true + sceneResolution[1] ); - } else { + + glFramebufferRenderbuffer( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + GL_RENDERBUFFER, + rboSceneDepthResolve + ); + + checkGLErrors(); + + // Single-sample depth texture (resolve target) + texSceneDepth = glGenTextures(); + glActiveTexture(TEXTURE_UNIT_SCENE_OPAQUE_DEPTH); + glBindTexture(GL_TEXTURE_2D, texSceneDepth); + glTexImage2D( GL_TEXTURE_2D, 0, @@ -1343,31 +1384,58 @@ public void updateSceneFbo() { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Depth resolve FBO + fboSceneDepthResolve = glGenFramebuffers(); + glBindFramebuffer(GL_FRAMEBUFFER, fboSceneDepthResolve); + + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + GL_TEXTURE_2D, + texSceneDepth, + 0 + ); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + throw new RuntimeException("Depth resolve FBO incomplete"); + + glBindFramebuffer(GL_FRAMEBUFFER, fboScene); } + else { + // No MSAA: attach depth texture directly + texSceneDepth = glGenTextures(); + glActiveTexture(TEXTURE_UNIT_SCENE_OPAQUE_DEPTH); + glBindTexture(GL_TEXTURE_2D, texSceneDepth); - glFramebufferTexture2D( - GL_FRAMEBUFFER, - GL_DEPTH_ATTACHMENT, - sceneDepthTarget, - texSceneDepth, - 0 - ); + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_DEPTH_COMPONENT24, + sceneResolution[0], + sceneResolution[1], + 0, + GL_DEPTH_COMPONENT, + GL_FLOAT, + 0 + ); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - throw new RuntimeException("Scene FBO incomplete"); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - fboSceneDepth = glGenFramebuffers(); - glBindFramebuffer(GL_FRAMEBUFFER, fboSceneDepth); - glFramebufferTexture2D( - GL_FRAMEBUFFER, - GL_DEPTH_ATTACHMENT, - sceneDepthTarget, - texSceneDepth, - 0 - ); + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + GL_TEXTURE_2D, + texSceneDepth, + 0 + ); + } if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - throw new RuntimeException("Opaque depth FBO incomplete"); + throw new RuntimeException("Scene FBO incomplete"); texSceneAlphaDepth = glGenTextures(); glActiveTexture(TEXTURE_UNIT_SCENE_ALPHA_DEPTH); @@ -1417,10 +1485,6 @@ private void destroySceneFbo() { glDeleteFramebuffers(fboScene); fboScene = 0; - if(fboSceneDepth != 0) - glDeleteRenderbuffers(fboSceneDepth); - fboSceneDepth = 0; - if (rboSceneColor != 0) glDeleteRenderbuffers(rboSceneColor); rboSceneColor = 0; @@ -1437,6 +1501,14 @@ private void destroySceneFbo() { glDeleteFramebuffers(fboSceneResolve); fboSceneResolve = 0; + if(fboSceneDepthResolve != 0) + glDeleteFramebuffers(fboSceneDepthResolve); + fboSceneDepthResolve = 0; + + if(rboSceneDepthResolve != 0) + glDeleteRenderbuffers(rboSceneDepthResolve); + rboSceneDepthResolve = 0; + if (rboSceneResolveColor != 0) glDeleteRenderbuffers(rboSceneResolveColor); rboSceneResolveColor = 0; diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java index e7b3c40a15..9296a55236 100644 --- a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -93,9 +93,6 @@ public final class OcclusionManager { @Getter private int passedQueryCount; - private int fboDepthResolve = 0; - private int rboDepthResolve = 0; - private int fboOcclusionDepth = 0; private int rboOcclusionDepth = 0; @@ -302,39 +299,6 @@ private void destroyOcclusionFbo() { } } - private void ensureDepthResolveFbo() { - if (fboDepthResolve != 0 || plugin.msaaSamples == 0) - return; - - fboDepthResolve = glGenFramebuffers(); - glBindFramebuffer(GL_FRAMEBUFFER, fboDepthResolve); - - rboDepthResolve = glGenRenderbuffers(); - glBindRenderbuffer(GL_RENDERBUFFER, rboDepthResolve); - - glRenderbufferStorage( - GL_RENDERBUFFER, - GL_DEPTH_COMPONENT24, - plugin.sceneResolution[0], - plugin.sceneResolution[1] - ); - - glFramebufferRenderbuffer( - GL_FRAMEBUFFER, - GL_DEPTH_ATTACHMENT, - GL_RENDERBUFFER, - rboDepthResolve - ); - - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) - throw new RuntimeException("Depth resolve FBO incomplete"); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - } - private void ensureOcclusionFbo() { int targetWidth = clamp(plugin.sceneResolution[0] / OCCLUSION_DOWNSCALE, MIN_OCCLUSION_SIZE, MAX_OCCLUSION_SIZE); int targetHeight = clamp(plugin.sceneResolution[1] / OCCLUSION_DOWNSCALE, MIN_OCCLUSION_SIZE, MAX_OCCLUSION_SIZE); @@ -378,8 +342,6 @@ private void ensureOcclusionFbo() { throw new RuntimeException("Occlusion FBO incomplete: " + status); } - ensureDepthResolveFbo(); - glBindFramebuffer(GL_FRAMEBUFFER, 0); } @@ -468,24 +430,7 @@ public void occlusionPass() { ensureOcclusionFbo(); - if(plugin.msaaSamples > 0) { - glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboSceneDepth); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboDepthResolve); - - glBlitFramebuffer( - 0, 0, - plugin.sceneResolution[0], plugin.sceneResolution[1], - 0, 0, - plugin.sceneResolution[0], plugin.sceneResolution[1], - GL_DEPTH_BUFFER_BIT, - GL_NEAREST - ); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, fboDepthResolve); - } else { - glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboSceneDepth); - } - + glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.msaaSamples > 0 ? plugin.fboSceneDepthResolve : plugin.fboScene); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboOcclusionDepth); glBlitFramebuffer( diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index ca8f957dc1..2b9aeccc88 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -728,7 +728,7 @@ private void depthPrePass() { frameTimer.begin(Timer.DRAW_SCENE); - renderState.framebuffer.set(GL_DRAW_FRAMEBUFFER, plugin.fboSceneDepth); + renderState.framebuffer.set(GL_DRAW_FRAMEBUFFER, plugin.fboScene); renderState.viewport.set(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); renderState.ido.set(indirectDrawCmds.id); @@ -761,6 +761,21 @@ private void depthPrePass() { renderState.colorMask.set(true, true, true, true); renderState.apply(); + if (plugin.msaaSamples > 1) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboScene); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, plugin.fboSceneDepthResolve); + + glBlitFramebuffer( + 0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1], + 0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1], + GL_DEPTH_BUFFER_BIT, + GL_NEAREST + ); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + frameTimer.end(Timer.DRAW_SCENE); } diff --git a/src/main/resources/rs117/hd/scene_frag.glsl b/src/main/resources/rs117/hd/scene_frag.glsl index 21859cc384..e35833a1b2 100644 --- a/src/main/resources/rs117/hd/scene_frag.glsl +++ b/src/main/resources/rs117/hd/scene_frag.glsl @@ -44,11 +44,7 @@ uniform sampler2DArray textureArray; uniform sampler2D shadowMap; uniform usampler2DArray tiledLightingArray; -#if MSAA_SAMPLES > 0 - uniform sampler2DMS sceneOpaqueDepth; -#else - uniform sampler2D sceneOpaqueDepth; -#endif +uniform sampler2D sceneOpaqueDepth; uniform sampler2D sceneAlphaDepth; // general HD settings diff --git a/src/main/resources/rs117/hd/tiled_lighting_frag.glsl b/src/main/resources/rs117/hd/tiled_lighting_frag.glsl index a87bae24f9..a13f77f17d 100644 --- a/src/main/resources/rs117/hd/tiled_lighting_frag.glsl +++ b/src/main/resources/rs117/hd/tiled_lighting_frag.glsl @@ -16,11 +16,7 @@ #include #include -#if MSAA_SAMPLES > 0 - uniform sampler2DMS sceneOpaqueDepth; -#else - uniform sampler2D sceneOpaqueDepth; -#endif +uniform sampler2D sceneOpaqueDepth; uniform sampler2D sceneAlphaDepth; #include From c539c678bc81c388f885227aba0aad80442b2d02 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 04:03:51 +0000 Subject: [PATCH 12/21] Use InstanceData instead of a UBO Optimizations Optimizations --- .../hd/opengl/uniforms/UBOOcclusion.java | 15 -- .../hd/renderer/zone/OcclusionManager.java | 252 ++++++++---------- .../rs117/hd/renderer/zone/ZoneRenderer.java | 9 +- src/main/java/rs117/hd/utils/HDUtils.java | 24 +- .../resources/rs117/hd/occlusion_vert.glsl | 9 +- .../rs117/hd/uniforms/occlusion.glsl | 6 - 6 files changed, 124 insertions(+), 191 deletions(-) delete mode 100644 src/main/java/rs117/hd/opengl/uniforms/UBOOcclusion.java delete mode 100644 src/main/resources/rs117/hd/uniforms/occlusion.glsl diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOOcclusion.java b/src/main/java/rs117/hd/opengl/uniforms/UBOOcclusion.java deleted file mode 100644 index 6fddb8237b..0000000000 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOOcclusion.java +++ /dev/null @@ -1,15 +0,0 @@ -package rs117.hd.opengl.uniforms; - -import rs117.hd.utils.buffer.GLBuffer; - -import static org.lwjgl.opengl.GL15C.GL_DYNAMIC_DRAW; - -public class UBOOcclusion extends UniformBuffer { - public static final int MAX_AABBS = 2000; - - public UBOOcclusion() { - super(GL_DYNAMIC_DRAW); - } - - public final Property[] aabbs = addPropertyArray(PropertyType.FVec3, "aabbs", MAX_AABBS * 2); -} diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java index 9296a55236..f376f61b30 100644 --- a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -21,24 +21,21 @@ import org.lwjgl.system.MemoryStack; import rs117.hd.HdPlugin; import rs117.hd.HdPluginConfig; -import rs117.hd.opengl.GLConstants; import rs117.hd.opengl.shader.OcclusionShaderProgram; import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; -import rs117.hd.opengl.uniforms.UBOOcclusion; import rs117.hd.opengl.uniforms.UBOWorldViews.WorldViewStruct; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; import rs117.hd.utils.HDUtils; import rs117.hd.utils.RenderState; +import rs117.hd.utils.buffer.GLBuffer; +import rs117.hd.utils.buffer.GpuFloatBuffer; import static org.lwjgl.opengl.GL11.GL_TRIANGLES; -import static org.lwjgl.opengl.GL15.glDeleteBuffers; import static org.lwjgl.opengl.GL15C.GL_ARRAY_BUFFER; import static org.lwjgl.opengl.GL15C.GL_STATIC_DRAW; import static org.lwjgl.opengl.GL15C.glBindBuffer; -import static org.lwjgl.opengl.GL15C.glBufferData; -import static org.lwjgl.opengl.GL15C.glGenBuffers; import static org.lwjgl.opengl.GL20C.glEnableVertexAttribArray; import static org.lwjgl.opengl.GL20C.glVertexAttribPointer; import static org.lwjgl.opengl.GL30C.glBindVertexArray; @@ -48,7 +45,6 @@ import static org.lwjgl.opengl.GL43.GL_ANY_SAMPLES_PASSED_CONSERVATIVE; import static rs117.hd.HdPlugin.GL_CAPS; import static rs117.hd.HdPlugin.checkGLErrors; -import static rs117.hd.utils.HDUtils.align; import static rs117.hd.utils.MathUtils.*; @Slf4j @@ -82,7 +78,6 @@ public final class OcclusionManager { @Inject private OcclusionShaderProgram.Debug occlusionDebugProgram; private RenderState renderState; - private UBOOcclusion uboOcclusion; @Getter private boolean active; private int debugMode; @@ -93,21 +88,22 @@ public final class OcclusionManager { @Getter private int passedQueryCount; + private GpuFloatBuffer aabbBuffer; + private int fboOcclusionDepth = 0; private int rboOcclusionDepth = 0; private int occlusionWidth = 0; private int occlusionHeight = 0; - private final int result[] = new int[1]; - private static final int OCCLUSION_DOWNSCALE = 4; private static final int MIN_OCCLUSION_SIZE = 256; private static final int MAX_OCCLUSION_SIZE = 1024; private int glCubeVAO; - private int glCubeVBO; - private int glCubeEBO; + private GLBuffer glCubeVBO; + private GLBuffer glCubeEBO; + private GLBuffer glCubeInstanceData; private int anySamplesPassedTarget; public void toggleDebug() { debugMode = (debugMode + 1) % 3; } @@ -115,13 +111,14 @@ public void toggleDebugVisibility() { debugVisibility = (debugVisibility + 1) % 3; } - public void initialize(RenderState renderState, UBOOcclusion uboOcclusion) { + public void initialize(RenderState renderState) { this.renderState = renderState; - this.uboOcclusion = uboOcclusion; instance = this; active = config.occlusionCulling(); + aabbBuffer = new GpuFloatBuffer(1024); + // Check if conservative queries are supported if (GL_CAPS.GL_ARB_occlusion_query2) { anySamplesPassedTarget = GL_ANY_SAMPLES_PASSED_CONSERVATIVE; @@ -131,10 +128,13 @@ public void initialize(RenderState renderState, UBOOcclusion uboOcclusion) { log.info("Using fallback GL_ANY_SAMPLES_PASSED for occlusion queries"); } + glCubeVBO = new GLBuffer("Occlusion VBO", GL_ARRAY_BUFFER, GL_STATIC_DRAW).initialize(); + glCubeEBO = new GLBuffer("Occlusion EBO", GL_ELEMENT_ARRAY_BUFFER, GL_STATIC_DRAW).initialize(); + glCubeInstanceData = new GLBuffer("Occlusion Instance Data", GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW).initialize(); + try (MemoryStack stack = MemoryStack.stackPush()) { // Create cube VAO glCubeVAO = glGenVertexArrays(); - glCubeVBO = glGenBuffers(); glBindVertexArray(glCubeVAO); FloatBuffer vboCubeData = stack.mallocFloat(8 * 3) @@ -150,8 +150,7 @@ public void initialize(RenderState renderState, UBOOcclusion uboOcclusion) { -1, 1, 1 // 7 }) .flip(); - glBindBuffer(GL_ARRAY_BUFFER, glCubeVBO); - glBufferData(GL_ARRAY_BUFFER, vboCubeData, GL_STATIC_DRAW); + glCubeVBO.upload(vboCubeData); IntBuffer eboCubeData = stack.mallocInt(36) .put(new int[] { @@ -180,15 +179,20 @@ public void initialize(RenderState renderState, UBOOcclusion uboOcclusion) { 3, 6, 7 }) .flip(); - - glCubeEBO = glGenBuffers(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glCubeEBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, eboCubeData, GL_STATIC_DRAW); + glCubeEBO.upload(eboCubeData); // position attribute glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, false, 3 * Float.BYTES, 0); + // aabb center attribute + glEnableVertexAttribArray(1); + glVertexAttribDivisor(1, 1); + + // aabb scale attribute + glEnableVertexAttribArray(2); + glVertexAttribDivisor(2, 1); + // reset glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -211,10 +215,23 @@ public void destroy() { glDeleteVertexArrays(glCubeVAO); glCubeVAO = 0; - if (glCubeVBO != 0) - glDeleteBuffers(glCubeVBO); - glCubeVBO = 0; + if (glCubeVBO != null) + glCubeVBO.destroy(); + glCubeVBO = null; + + if (glCubeEBO != null) + glCubeEBO.destroy(); + glCubeEBO = null; + + if (glCubeInstanceData != null) + glCubeInstanceData.destroy(); + glCubeInstanceData = null; + if(aabbBuffer != null) + aabbBuffer.destroy(); + aabbBuffer = null; + + destroyOcclusionFbo(); deleteQueries(freeQueries); deleteQueries(queuedQueries); deleteQueries(prevQueuedQueries); @@ -370,13 +387,9 @@ public void readbackQueries() { if (query.frustumCulled) continue; - glGetQueryObjectuiv(id, GL_QUERY_RESULT_AVAILABLE, result); - if(result[0] != 0) { - glGetQueryObjectuiv(id, GL_QUERY_RESULT, result); - query.occluded = result[0] == 0; - if (!query.occluded) - passedQueryCount++; - } + query.occluded = glGetQueryObjecti(id, GL_QUERY_RESULT) == 0; + if (!query.occluded) + passedQueryCount++; } frameTimer.end(Timer.OCCLUSION_READBACK); prevQueuedQueries.clear(); @@ -395,7 +408,7 @@ public void occlusionDebugPass() { renderState.enable.set(GL_BLEND); renderState.enable.set(GL_DEPTH_TEST); renderState.depthMask.set(false); - renderState.ebo.set(glCubeEBO); + renderState.ebo.set(glCubeEBO.id); renderState.vao.set(glCubeVAO); renderState.apply(); occlusionDebugProgram.use(); @@ -448,7 +461,7 @@ public void occlusionPass() { renderState.enable.set(GL_DEPTH_TEST); renderState.depthMask.set(false); renderState.colorMask.set(false, false, false, false); - renderState.ebo.set(glCubeEBO); + renderState.ebo.set(glCubeEBO.id); renderState.vao.set(glCubeVAO); renderState.apply(); occlusionProgram.use(); @@ -472,32 +485,55 @@ public void occlusionPass() { } private void processQueries(List queries, boolean isDebug) { - int start = 0; - int uboOffset = 0; - for (int i = 0; i < queries.size(); i++) { final OcclusionQuery query = queries.get(i); if (query.count == 0) continue; - if (uboOffset + query.count >= UBOOcclusion.MAX_AABBS) { - flushQueries(queries, start, i, isDebug); - start = i; - uboOffset = 0; - } - if (query.id[0] == 0) glGenQueries(query.id); - uboOffset = buildQueryAABBs(query, uboOffset, isDebug); + buildQueryAABBs(query, isDebug); + } + + aabbBuffer.flip(); + glCubeInstanceData.upload(aabbBuffer); + glCubeInstanceData.bind(); + aabbBuffer.clear(); + + checkGLErrors(); + + for (int i = 0; i < queries.size(); i++) { + final OcclusionQuery query = queries.get(i); + if (query.count <= 0 || query.frustumCulled) + continue; + + glVertexAttribPointer(1, 3, GL_FLOAT, false, 24, query.vboOffset); + glVertexAttribPointer(2, 3, GL_FLOAT, false, 24, query.vboOffset + 12); + + if (isDebug) { + if(debugVisibility > 0) { + if (debugVisibility == 1 && !query.isStatic) + continue; + + if (debugVisibility == 2 && query.isStatic) + continue; + } + occlusionDebugProgram.queryId.set(query.id[0]); + glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0, query.count); + } else { + glBeginQuery(anySamplesPassedTarget, query.getSampleId()); + glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0, query.count); + glEndQuery(anySamplesPassedTarget); + query.advance(); + } } - flushQueries(queries, start, queries.size(), isDebug); checkGLErrors(); } - private int buildQueryAABBs(OcclusionQuery query, int uboOffset, boolean isDebug) { + private void buildQueryAABBs(OcclusionQuery query, boolean isDebug) { if (query.count <= 0) - return uboOffset; + return; final float EXPAND_FACTOR = 4.0f; final float dirX = -abs(directionalFwd[0]); @@ -505,62 +541,42 @@ private int buildQueryAABBs(OcclusionQuery query, int uboOffset, boolean isDebug final float dirZ = -abs(directionalFwd[2]); if (!isDebug && query.globalAABB) { - float posX = query.offsetX + (query.globalMinX + query.globalMaxX) * 0.5f; - float posY = query.offsetY + (query.globalMinY + query.globalMaxY) * 0.5f; - float posZ = query.offsetZ + (query.globalMinZ + query.globalMaxZ) * 0.5f; - - float sizeX = (query.globalMaxX - query.globalMinX) * 0.5f; - float sizeY = (query.globalMaxY - query.globalMinY) * 0.5f; - float sizeZ = (query.globalMaxZ - query.globalMinZ) * 0.5f; + projectAABB(query, projected, + query.offsetX + (query.globalMinX + query.globalMaxX) * 0.5f, + query.offsetY + (query.globalMinY + query.globalMaxY) * 0.5f, + query.offsetZ + (query.globalMinZ + query.globalMaxZ) * 0.5f, + (query.globalMaxX - query.globalMinX) * 0.5f, + (query.globalMaxY - query.globalMinY) * 0.5f, + (query.globalMaxZ - query.globalMinZ) * 0.5f + ); - projectAABB(query, projected, posX, posY, posZ, sizeX, sizeY, sizeZ); if (plugin.configShadowsEnabled) expandAABBAlongShadow(projected, dirX, dirY, dirZ, EXPAND_FACTOR); - if (!isAABBVisible(projected)) { - query.frustumCulled = true; - return uboOffset; - } + query.frustumCulled = !isAABBVisible(projected); } - final int elementSize = (int) align(uboOcclusion.aabbs[0].getType().size, 16, true); - final int alignment = GLConstants.getBufferOffsetAlignment(); - - long startByteOffset = (long) uboOffset * elementSize; - long alignedStartByteOffset = align(startByteOffset, alignment, true); - int alignedUboOffset = (int) (alignedStartByteOffset / elementSize); - - query.uboOffset = alignedUboOffset; - query.writtenCount = 0; - for (int j = 0; j < query.count; j++) { - float posX = query.offsetX + query.aabb[j * 6]; - float posY = query.offsetY + query.aabb[j * 6 + 1]; - float posZ = query.offsetZ + query.aabb[j * 6 + 2]; + if (query.frustumCulled) + return; - float sizeX = query.aabb[j * 6 + 3]; - float sizeY = query.aabb[j * 6 + 4]; - float sizeZ = query.aabb[j * 6 + 5]; + query.vboOffset = (long)aabbBuffer.position() * Float.BYTES; + aabbBuffer.ensureCapacity(query.count * 8); + int aabbEnd = query.count * 6; + for (int base = 0; base < aabbEnd;) { + projectAABB(query, projected, + query.offsetX + query.aabb[base++], + query.offsetY + query.aabb[base++], + query.offsetZ + query.aabb[base++], + query.aabb[base++], + query.aabb[base++], + query.aabb[base++] + ); - projectAABB(query, projected, posX, posY, posZ, sizeX, sizeY, sizeZ); if (plugin.configShadowsEnabled && !isDebug) expandAABBAlongShadow(projected, dirX, dirY, dirZ, EXPAND_FACTOR); - if(!isAABBVisible(projected)) - continue; - - uboOcclusion.aabbs[alignedUboOffset++].set( - projected[0], projected[1], projected[2] - ); - uboOcclusion.aabbs[alignedUboOffset++].set( - projected[3], projected[4], projected[5] - ); - query.writtenCount++; + aabbBuffer.put(projected); } - - if(!isDebug) - query.frustumCulled = query.writtenCount == 0; - - return alignedUboOffset; } private boolean isAABBVisible(float[] aabb) { @@ -644,52 +660,6 @@ private void projectAABB( out[5] = (maxZ - minZ) * 0.5f; } - private void flushQueries(List queries, int start, int end, boolean isDebug) { - uboOcclusion.upload(); - for (int i = start; i < end; i++) { - final OcclusionQuery query = queries.get(i); - if (query.count <= 0 || query.writtenCount <= 0 || query.frustumCulled) - continue; - assert query.writtenCount < UBOOcclusion.MAX_AABBS : "Exceeded Max AABBS"; - assert query.writtenCount <= query.count : "Exceeded query aabb written:" + query.writtenCount + " count:" + query.count; - - uboOcclusion.bindRange(uboOcclusion.aabbs[query.uboOffset], uboOcclusion.aabbs[query.uboOffset + 1 + (query.writtenCount * 2)]); - - if (isDebug) { - if(debugVisibility > 0) { - if (debugVisibility == 1 && !query.isStatic) - continue; - - if (debugVisibility == 2 && query.isStatic) - continue; - } - occlusionDebugProgram.queryId.set(query.id[0]); - glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0, query.writtenCount); - } else { - glBeginQuery(anySamplesPassedTarget, query.getSampleId()); - glDrawElementsInstanced(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0, query.writtenCount); - glEndQuery(anySamplesPassedTarget); - query.advance(); - } - } - } - - public void shutdown() { - destroyOcclusionFbo(); - - if (glCubeVAO != 0) - glDeleteVertexArrays(glCubeVAO); - glCubeVAO = 0; - - if (glCubeEBO != 0) - glDeleteBuffers(glCubeEBO); - glCubeEBO = 0; - - if (glCubeVBO != 0) - glDeleteBuffers(glCubeVBO); - glCubeVBO = 0; - } - public final class OcclusionQuery { private final int[] id = new int[FRAMES_IN_FLIGHT]; private final boolean[] sampled = new boolean[FRAMES_IN_FLIGHT]; @@ -704,8 +674,8 @@ public final class OcclusionQuery { private boolean isStatic; private int activeId; + private long vboOffset; - private int uboOffset; private float offsetX; private float offsetY; private float offsetZ; @@ -722,7 +692,6 @@ public final class OcclusionQuery { private float[] aabb = new float[6]; private int count = 0; - private int writtenCount = 0; private void advance() { activeId = (activeId + 1) % FRAMES_IN_FLIGHT; @@ -797,17 +766,10 @@ public void addAABB( float posX, float posY, float posZ, float sizeX, float sizeY, float sizeZ ) { - if (count >= UBOOcclusion.MAX_AABBS) { - log.warn("Tried to add too many AABBs to OcclusionQuery"); - return; - } assert !isStatic; if (count * 6 >= aabb.length) { - aabb = Arrays.copyOf( - aabb, - Math.min(aabb.length * 2, UBOOcclusion.MAX_AABBS * 6) - ); + aabb = Arrays.copyOf(aabb, aabb.length * 2); } int base = count * 6; diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 2b9aeccc88..51fa57013a 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -48,7 +48,6 @@ import rs117.hd.opengl.shader.ShaderIncludes; import rs117.hd.opengl.shader.ShadowShaderProgram; import rs117.hd.opengl.uniforms.UBOLights; -import rs117.hd.opengl.uniforms.UBOOcclusion; import rs117.hd.opengl.uniforms.UBOWorldViews; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; @@ -148,9 +147,6 @@ public class ZoneRenderer implements Renderer { @Inject private UBOWorldViews uboWorldViews; - - @Inject - private UBOOcclusion uboOcclusion; public final Camera sceneCamera = new Camera().setReverseZ(true); @@ -201,10 +197,9 @@ public void initialize() { directionalCmd.setFrameTimer(frameTimer); uboWorldViews.initialize(UNIFORM_BLOCK_WORLD_VIEWS); - uboOcclusion.initialize(UNIFORM_BLOCK_OCCLUSION); jobSystem.startUp(config.cpuUsageLimit()); modelStreamingManager.initialize(); - occlusionManager.initialize(renderState, uboOcclusion); + occlusionManager.initialize(renderState); sceneManager.initialize(renderState, uboWorldViews); modelStreamingManager.initialize(); @@ -222,7 +217,6 @@ public void destroy() { modelStreamingManager.destroy(); sceneManager.destroy(); uboWorldViews.destroy(); - uboOcclusion.destroy(); SceneUploader.POOL = null; FacePrioritySorter.POOL = null; @@ -239,7 +233,6 @@ public void addShaderIncludes(ShaderIncludes includes) { includes .define("MAX_SIMULTANEOUS_WORLD_VIEWS", UBOWorldViews.MAX_SIMULTANEOUS_WORLD_VIEWS) .addInclude("WORLD_VIEW_GETTER", () -> plugin.generateGetter("WorldView", UBOWorldViews.MAX_SIMULTANEOUS_WORLD_VIEWS)) - .addUniformBuffer(uboOcclusion) .addUniformBuffer(uboWorldViews); } diff --git a/src/main/java/rs117/hd/utils/HDUtils.java b/src/main/java/rs117/hd/utils/HDUtils.java index c4160458bc..db7f515cde 100644 --- a/src/main/java/rs117/hd/utils/HDUtils.java +++ b/src/main/java/rs117/hd/utils/HDUtils.java @@ -428,19 +428,19 @@ public static boolean isAABBIntersectingFrustum( float maxZ, float[][] cullingPlanes ) { - for (float[] plane : cullingPlanes) { - if ( - plane[0] * minX + plane[1] * minY + plane[2] * minZ + plane[3] < 0 && - plane[0] * maxX + plane[1] * minY + plane[2] * minZ + plane[3] < 0 && - plane[0] * minX + plane[1] * maxY + plane[2] * minZ + plane[3] < 0 && - plane[0] * maxX + plane[1] * maxY + plane[2] * minZ + plane[3] < 0 && - plane[0] * minX + plane[1] * minY + plane[2] * maxZ + plane[3] < 0 && - plane[0] * maxX + plane[1] * minY + plane[2] * maxZ + plane[3] < 0 && - plane[0] * minX + plane[1] * maxY + plane[2] * maxZ + plane[3] < 0 && - plane[0] * maxX + plane[1] * maxY + plane[2] * maxZ + plane[3] < 0 - ) { + for (int i = 0; i < cullingPlanes.length; i++ ) { + final float[] plane = cullingPlanes[i]; + final float px = plane[0]; + final float py = plane[1]; + final float pz = plane[2]; + final float pw = plane[3]; + + final float pVertexX = px >= 0 ? maxX : minX; + final float pVertexY = py >= 0 ? maxY : minY; + final float pVertexZ = pz >= 0 ? maxZ : minZ; + + if (px * pVertexX + py * pVertexY + pz * pVertexZ + pw < 0) return false; - } } // Potentially visible diff --git a/src/main/resources/rs117/hd/occlusion_vert.glsl b/src/main/resources/rs117/hd/occlusion_vert.glsl index 458fc25bc7..ff36ceee7a 100644 --- a/src/main/resources/rs117/hd/occlusion_vert.glsl +++ b/src/main/resources/rs117/hd/occlusion_vert.glsl @@ -1,13 +1,12 @@ #version 330 -#include #include -layout (location = 0) in vec3 vPosition; +layout (location = 0) in vec3 vVertex; +layout (location = 1) in vec3 vCenter; +layout (location = 2) in vec3 vScale; void main() { - vec3 position = aabbs[gl_InstanceID * 2]; - vec3 scale = aabbs[gl_InstanceID * 2 + 1]; - vec3 worldPosition = vPosition * scale + position; + vec3 worldPosition = vVertex * vScale + vCenter; gl_Position = projectionMatrix * vec4(worldPosition, 1.0); } \ No newline at end of file diff --git a/src/main/resources/rs117/hd/uniforms/occlusion.glsl b/src/main/resources/rs117/hd/uniforms/occlusion.glsl deleted file mode 100644 index 9ce45624d3..0000000000 --- a/src/main/resources/rs117/hd/uniforms/occlusion.glsl +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -layout(std140) uniform UBOOcclusion { - vec3 aabbs[4000]; -}; - From b4fd5b36a629910f8fad691eb522b37ac550f2b8 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 06:18:26 +0000 Subject: [PATCH 13/21] Occlusion Cull AlphaModels --- .../hd/renderer/zone/OcclusionManager.java | 8 ++++- .../rs117/hd/renderer/zone/SceneManager.java | 1 + .../hd/renderer/zone/WorldViewContext.java | 1 + .../java/rs117/hd/renderer/zone/Zone.java | 33 ++++++++++++++++++- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java index f376f61b30..215a65cc1d 100644 --- a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -50,7 +50,7 @@ @Slf4j @Singleton public final class OcclusionManager { - private static final int FRAMES_IN_FLIGHT = 3; + private static final int FRAMES_IN_FLIGHT = 2; @Getter private static OcclusionManager instance; @@ -387,6 +387,9 @@ public void readbackQueries() { if (query.frustumCulled) continue; + while(glGetQueryObjecti(id, GL_QUERY_RESULT_AVAILABLE) == 0) + Thread.onSpinWait(); + query.occluded = glGetQueryObjecti(id, GL_QUERY_RESULT) == 0; if (!query.occluded) passedQueryCount++; @@ -575,6 +578,9 @@ private void buildQueryAABBs(OcclusionQuery query, boolean isDebug) { if (plugin.configShadowsEnabled && !isDebug) expandAABBAlongShadow(projected, dirX, dirY, dirZ, EXPAND_FACTOR); + if(query.count == 1) + query.frustumCulled = !isAABBVisible(projected); + aabbBuffer.put(projected); } } diff --git a/src/main/java/rs117/hd/renderer/zone/SceneManager.java b/src/main/java/rs117/hd/renderer/zone/SceneManager.java index 6370b07e71..4ac8f7f164 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneManager.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneManager.java @@ -656,6 +656,7 @@ public void swapScene(Scene scene) { root.pendingCull.add(preZone); nextZone.setMetadata(ctx, nextSceneContext, x, z); + nextZone.setAlphaModelsOffset(ctx, nextSceneContext, x, z); } } diff --git a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java index 8ffbfb6d40..196834c89f 100644 --- a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java +++ b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java @@ -214,6 +214,7 @@ void handleZoneSwap(float deltaTime, int zx, int zz) { if (prevZone != curZone) { curZone.inSceneFrustum = prevZone.inSceneFrustum; curZone.inShadowFrustum = prevZone.inShadowFrustum; + clientThread.invoke(() -> zones[zx][zz].setAlphaModelsOffset(this, sceneContext, zx, zz)); pendingCull.add(prevZone); } } else if (uploadTask.wasCancelled() && !curZone.cull) { diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index f402c882c2..0fedbf342d 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -179,6 +179,12 @@ public void free() { occlusionQuery = null; } + for(AlphaModel m : alphaModels) { + if(m.occlusionQuery != null) + m.occlusionQuery.free(); + m.occlusionQuery = null; + } + sortedAlphaFacesUpload.release(); sizeO = 0; @@ -337,6 +343,18 @@ private void setupVao(int vao, int buffer, int metadata, int ebo) { glBindBuffer(GL_ARRAY_BUFFER, 0); } + public void setAlphaModelsOffset(WorldViewContext viewContext, SceneContext sceneContext, int mx, int mz) { + int baseX = (mx - (sceneContext.sceneOffset >> 3)) << 10; + int baseZ = (mz - (sceneContext.sceneOffset >> 3)) << 10; + + for(AlphaModel m : alphaModels) { + if(m.occlusionQuery != null) { + m.occlusionQuery.setOffset(baseX, 0, baseZ); + m.occlusionQuery.setWorldView(viewContext.uboWorldViewStruct); + } + } + } + public void setMetadata(WorldViewContext viewContext, SceneContext sceneContext, int mx, int mz) { if (vboM == null) return; @@ -497,6 +515,7 @@ public static class AlphaModel { int[] packedFaces; int[] sortedFaces; int sortedFacesLen; + OcclusionQuery occlusionQuery; int dist; int asyncSortIdx = -1; @@ -677,6 +696,11 @@ void addAlphaModel( m.radius = 2 + (int) Math.sqrt(radius); m.sortedFaces = new int[bufferIdx * 3]; + if(bufferIdx > 0) { + m.occlusionQuery = OcclusionManager.getInstance().obtainQuery(); + m.occlusionQuery.addSphere(x + cx, y + cy, z + cz, m.radius); + } + assert packedFaces.length > 0; // Normally these will be equal, but transparency is used to hide faces in the TzHaar reskin assert bufferIdx <= packedFaces.length : String.format("%d > %d", (int) bufferIdx, packedFaces.length); @@ -814,6 +838,12 @@ void alphaStaticModelSort(Camera camera) { if ((m.flags & AlphaModel.SKIP) != 0 || m.isTemp()) continue; + if(m.occlusionQuery != null) { + m.occlusionQuery.queue(); + if(m.occlusionQuery.isOccluded()) + continue; + } + m.dist = dist; alphaSortingJob.addAlphaModel(m); } @@ -874,7 +904,7 @@ void renderAlpha( boolean shouldQueueUpload = false; for (int i = 0; i < alphaModels.size(); i++) { final AlphaModel m = alphaModels.get(i); - if ((m.flags & AlphaModel.SKIP) != 0 || m.level != level) + if ((m.flags & AlphaModel.SKIP) != 0 || m.level != level || (m.occlusionQuery != null && m.occlusionQuery.isOccluded())) continue; if (level < minLevel || level > maxLevel || @@ -1029,6 +1059,7 @@ synchronized void multizoneLocs(SceneContext ctx, int zx, int zz, Camera camera, m2.zofx = (byte) (closestZoneX - zx); m2.zofz = (byte) (closestZoneZ - zz); + m2.occlusionQuery = m.occlusionQuery; m2.packedFaces = m.packedFaces; m2.radius = m.radius; m2.asyncSortIdx = m.asyncSortIdx; From 90782d37e935041654f11bd4a18ba75631bcabae Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 10:58:43 +0000 Subject: [PATCH 14/21] Sync --- src/main/java/rs117/hd/renderer/zone/OcclusionManager.java | 4 +--- src/main/java/rs117/hd/renderer/zone/Zone.java | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java index 215a65cc1d..a762d6b0fc 100644 --- a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -387,9 +387,6 @@ public void readbackQueries() { if (query.frustumCulled) continue; - while(glGetQueryObjecti(id, GL_QUERY_RESULT_AVAILABLE) == 0) - Thread.onSpinWait(); - query.occluded = glGetQueryObjecti(id, GL_QUERY_RESULT) == 0; if (!query.occluded) passedQueryCount++; @@ -543,6 +540,7 @@ private void buildQueryAABBs(OcclusionQuery query, boolean isDebug) { final float dirY = -abs(directionalFwd[1]); final float dirZ = -abs(directionalFwd[2]); + query.frustumCulled = false; if (!isDebug && query.globalAABB) { projectAABB(query, projected, query.offsetX + (query.globalMinX + query.globalMaxX) * 0.5f, diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 0fedbf342d..a3e3889372 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -696,7 +696,7 @@ void addAlphaModel( m.radius = 2 + (int) Math.sqrt(radius); m.sortedFaces = new int[bufferIdx * 3]; - if(bufferIdx > 0) { + if(bufferIdx >= 32) { m.occlusionQuery = OcclusionManager.getInstance().obtainQuery(); m.occlusionQuery.addSphere(x + cx, y + cy, z + cz, m.radius); } @@ -766,6 +766,7 @@ private void cleanAlphaModels(List alphaModels) { alphaModels.remove(i); m.packedFaces = null; m.sortedFaces = null; + m.occlusionQuery = null; modelCache.add(m); } m.asyncSortIdx = -1; From 99fe4f5ea337146d94566b0fddb2b1e3d44bbd92 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:27:49 +0000 Subject: [PATCH 15/21] Sync --- .../java/rs117/hd/renderer/zone/OcclusionManager.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java index a762d6b0fc..f6d8c77564 100644 --- a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -51,6 +51,7 @@ @Singleton public final class OcclusionManager { private static final int FRAMES_IN_FLIGHT = 2; + private static final int VISIBILITY_TEST_DELAY = 3; @Getter private static OcclusionManager instance; @@ -388,6 +389,7 @@ public void readbackQueries() { continue; query.occluded = glGetQueryObjecti(id, GL_QUERY_RESULT) == 0; + query.nextVisibilityTest = plugin.frame + VISIBILITY_TEST_DELAY; if (!query.occluded) passedQueryCount++; } @@ -487,7 +489,7 @@ public void occlusionPass() { private void processQueries(List queries, boolean isDebug) { for (int i = 0; i < queries.size(); i++) { final OcclusionQuery query = queries.get(i); - if (query.count == 0) + if (query.count == 0 || plugin.frame < query.nextVisibilityTest) continue; if (query.id[0] == 0) @@ -505,7 +507,7 @@ private void processQueries(List queries, boolean isDebug) { for (int i = 0; i < queries.size(); i++) { final OcclusionQuery query = queries.get(i); - if (query.count <= 0 || query.frustumCulled) + if (query.count <= 0 || query.frustumCulled || plugin.frame < query.nextVisibilityTest) continue; glVertexAttribPointer(1, 3, GL_FLOAT, false, 24, query.vboOffset); @@ -677,6 +679,7 @@ public final class OcclusionQuery { private boolean globalAABB; private boolean isStatic; + private int nextVisibilityTest; private int activeId; private long vboOffset; @@ -886,7 +889,7 @@ public void queue() { } public void free() { - count = 0; + count = nextVisibilityTest = 0; queued = false; occluded = false; isStatic = false; From 0d661f7faaeb597ac5a5cf2c204d69903094cf29 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 19:45:15 +0000 Subject: [PATCH 16/21] Write to gl_fragDepth to hopefully fix macOs --- src/main/resources/rs117/hd/depth_frag.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/rs117/hd/depth_frag.glsl b/src/main/resources/rs117/hd/depth_frag.glsl index 410f5b978f..b6c7cd3336 100644 --- a/src/main/resources/rs117/hd/depth_frag.glsl +++ b/src/main/resources/rs117/hd/depth_frag.glsl @@ -1,5 +1,5 @@ #version 330 void main() { - // MACOS STUB + gl_FragDepth = gl_FragCoord.z; } \ No newline at end of file From c875e577699a78632fa432bc772e133c3c135f03 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 19:50:43 +0000 Subject: [PATCH 17/21] MacOs Sync --- src/main/resources/rs117/hd/depth_frag.glsl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/rs117/hd/depth_frag.glsl b/src/main/resources/rs117/hd/depth_frag.glsl index b6c7cd3336..5d2b16d99a 100644 --- a/src/main/resources/rs117/hd/depth_frag.glsl +++ b/src/main/resources/rs117/hd/depth_frag.glsl @@ -1,5 +1,5 @@ -#version 330 +#version 330 core - void main() { - gl_FragDepth = gl_FragCoord.z; -} \ No newline at end of file +layout(location = 0) out vec4 fragColor; + +void main() { /* MAC OS STUB */ } \ No newline at end of file From ebd02be3c5fa663ece146c994eb5330752934fe2 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:38:58 +0000 Subject: [PATCH 18/21] Move APPLE check to initialize so that the check is ready to be read --- .../hd/opengl/shader/DepthShaderProgram.java | 14 ++++++++----- .../opengl/shader/OcclusionShaderProgram.java | 21 ++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java index d8c40fa7c5..876bef450a 100644 --- a/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java @@ -5,14 +5,18 @@ import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER; import static org.lwjgl.opengl.GL20C.GL_FRAGMENT_SHADER_DERIVATIVE_HINT; import static org.lwjgl.opengl.GL20C.GL_VERTEX_SHADER; +import static rs117.hd.HdPlugin.GL_CAPS; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; public class DepthShaderProgram extends ShaderProgram { public DepthShaderProgram() { - super(t -> { - t.add(GL_VERTEX_SHADER, "depth_vert.glsl"); - if(HdPlugin.APPLE) - t.add(GL_FRAGMENT_SHADER, "depth_frag.glsl"); - }); + super(t -> t.add(GL_VERTEX_SHADER, "depth_vert.glsl")); + } + + @Override + protected void initialize() { + super.initialize(); + if(HdPlugin.APPLE || !GL_CAPS.OpenGL46) + shaderTemplate.add(GL_FRAGMENT_SHADER, "depth_frag.glsl"); } } diff --git a/src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java index bbf4adbd20..1b05d75dc6 100644 --- a/src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java @@ -4,21 +4,28 @@ import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER; import static org.lwjgl.opengl.GL20C.GL_VERTEX_SHADER; +import static rs117.hd.HdPlugin.GL_CAPS; public class OcclusionShaderProgram extends ShaderProgram { public OcclusionShaderProgram() { - super(t -> { - t.add(GL_VERTEX_SHADER, "occlusion_vert.glsl"); - if(HdPlugin.APPLE) - t.add(GL_FRAGMENT_SHADER, "depth_frag.glsl"); - }); + super(t -> t.add(GL_VERTEX_SHADER, "occlusion_vert.glsl")); + } + + @Override + protected void initialize() { + super.initialize(); + if(HdPlugin.APPLE || !GL_CAPS.OpenGL46) + shaderTemplate.add(GL_FRAGMENT_SHADER, "depth_frag.glsl"); } public static class Debug extends OcclusionShaderProgram { public Uniform1i queryId = addUniform1i("queryId"); - public Debug() { - shaderTemplate.remove(GL_FRAGMENT_SHADER).add(GL_FRAGMENT_SHADER, "occlusion_debug_frag.glsl"); + @Override + protected void initialize() { + super.initialize(); + shaderTemplate.remove(GL_FRAGMENT_SHADER); + shaderTemplate.add(GL_FRAGMENT_SHADER, "occlusion_debug_frag.glsl"); } } } From 5565001fba3bf0a7a0125672ebb394b88e84ebf9 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 21:31:07 +0000 Subject: [PATCH 19/21] Move MacOS Check to compile --- .../java/rs117/hd/opengl/shader/DepthShaderProgram.java | 5 +++-- .../rs117/hd/opengl/shader/OcclusionShaderProgram.java | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java index 876bef450a..0b7da7db88 100644 --- a/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/DepthShaderProgram.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.shader; +import java.io.IOException; import rs117.hd.HdPlugin; import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER; @@ -14,9 +15,9 @@ public DepthShaderProgram() { } @Override - protected void initialize() { - super.initialize(); + public void compile(ShaderIncludes includes) throws ShaderException, IOException { if(HdPlugin.APPLE || !GL_CAPS.OpenGL46) shaderTemplate.add(GL_FRAGMENT_SHADER, "depth_frag.glsl"); + super.compile(includes); } } diff --git a/src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java index 1b05d75dc6..d9fec265e3 100644 --- a/src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/OcclusionShaderProgram.java @@ -1,5 +1,6 @@ package rs117.hd.opengl.shader; +import java.io.IOException; import rs117.hd.HdPlugin; import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER; @@ -12,20 +13,20 @@ public OcclusionShaderProgram() { } @Override - protected void initialize() { - super.initialize(); + public void compile(ShaderIncludes includes) throws ShaderException, IOException { if(HdPlugin.APPLE || !GL_CAPS.OpenGL46) shaderTemplate.add(GL_FRAGMENT_SHADER, "depth_frag.glsl"); + super.compile(includes); } public static class Debug extends OcclusionShaderProgram { public Uniform1i queryId = addUniform1i("queryId"); @Override - protected void initialize() { - super.initialize(); + public void compile(ShaderIncludes includes) throws ShaderException, IOException { shaderTemplate.remove(GL_FRAGMENT_SHADER); shaderTemplate.add(GL_FRAGMENT_SHADER, "occlusion_debug_frag.glsl"); + super.compile(includes); } } } From 76e32dc3c4eb4cec29920c06b1723b1e1d2e5d73 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 22:55:04 +0000 Subject: [PATCH 20/21] Level base occlusion culling --- .../hd/renderer/zone/OcclusionManager.java | 11 +++- .../rs117/hd/renderer/zone/SceneUploader.java | 10 +-- .../hd/renderer/zone/WorldViewContext.java | 5 +- .../java/rs117/hd/renderer/zone/Zone.java | 62 +++++++++++-------- .../rs117/hd/renderer/zone/ZoneRenderer.java | 6 +- 5 files changed, 55 insertions(+), 39 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java index f6d8c77564..d7ce950bf2 100644 --- a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -389,8 +389,9 @@ public void readbackQueries() { continue; query.occluded = glGetQueryObjecti(id, GL_QUERY_RESULT) == 0; - query.nextVisibilityTest = plugin.frame + VISIBILITY_TEST_DELAY; - if (!query.occluded) + if(query.occluded) + query.nextVisibilityTest = plugin.frame + VISIBILITY_TEST_DELAY; + else passedQueryCount++; } frameTimer.end(Timer.OCCLUSION_READBACK); @@ -433,7 +434,7 @@ public void occlusionDebugPass() { } public void occlusionPass() { - if (queuedQueries.isEmpty()) + if (queuedQueries.isEmpty() || true) return; frameTimer.begin(Timer.RENDER_OCCLUSION); @@ -542,6 +543,7 @@ private void buildQueryAABBs(OcclusionQuery query, boolean isDebug) { final float dirY = -abs(directionalFwd[1]); final float dirZ = -abs(directionalFwd[2]); + boolean wasFrustumCulled = query.frustumCulled; query.frustumCulled = false; if (!isDebug && query.globalAABB) { projectAABB(query, projected, @@ -583,6 +585,9 @@ private void buildQueryAABBs(OcclusionQuery query, boolean isDebug) { aabbBuffer.put(projected); } + + if(wasFrustumCulled) // If we we're frustum culled & are no longer, retest the query ignoring existing delay + query.nextVisibilityTest = 0; } private boolean isAABBVisible(float[] aabb) { diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index 59c2317e6a..6f9551c122 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -242,7 +242,9 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws uploadZoneWater(ctx, zone, mzx, mzz, vb, fb); zone.levelOffsets[Zone.LEVEL_WATER_SURFACE] = vb.position(); } - zone.occlusionQuery.setStatic(); + + for(int i = 0; i < 5; i++) + zone.levelOcclusionQueries[i].setStatic(); } private void uploadZoneLevel( @@ -282,7 +284,7 @@ private void uploadZoneLevel( if(abbMin[0] != Float.MAX_VALUE && abbMin[1] != Float.MAX_VALUE && abbMin[2] != Float.MAX_VALUE && abbMax[0] != -Float.MAX_VALUE && abbMax[1] != -Float.MAX_VALUE && abbMax[2] != -Float.MAX_VALUE) { - zone.occlusionQuery.addMinMax(abbMin[0], abbMin[1] - LOCAL_HALF_TILE_SIZE, abbMin[2], abbMax[0], abbMax[1] + LOCAL_HALF_TILE_SIZE, abbMax[2]); + zone.levelOcclusionQueries[level].addMinMax(abbMin[0], abbMin[1] - LOCAL_HALF_TILE_SIZE, abbMin[2], abbMax[0], abbMax[1] + LOCAL_HALF_TILE_SIZE, abbMax[2]); } } @@ -368,7 +370,7 @@ private void uploadZoneWater( if(abbMin[0] != Float.MAX_VALUE && abbMin[1] != Float.MAX_VALUE && abbMin[2] != Float.MAX_VALUE && abbMax[0] != -Float.MAX_VALUE && abbMax[1] != -Float.MAX_VALUE && abbMax[2] != -Float.MAX_VALUE) { - zone.occlusionQuery.addMinMax(abbMin[0], abbMin[1], abbMin[2], abbMax[0], abbMax[1], abbMax[2]); + zone.levelOcclusionQueries[level].addMinMax(abbMin[0], abbMin[1] - LOCAL_HALF_TILE_SIZE, abbMin[2], abbMax[0], abbMax[1] + LOCAL_HALF_TILE_SIZE, abbMax[2]); } } @@ -1738,7 +1740,7 @@ private int uploadStaticModel( } if(len > 0) - zone.occlusionQuery.addAABB(model.getAABB(orientation), x, y, z); + zone.levelOcclusionQueries[level].addAABB(model.getAABB(orientation), x, y, z); writeCache.flush(); return len; diff --git a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java index 196834c89f..3d29a36c84 100644 --- a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java +++ b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java @@ -163,10 +163,7 @@ void sortStaticAlphaModels(Camera camera) { for (int zx = 0; zx < sizeX; zx++) { for (int zz = 0; zz < sizeZ; zz++) { final Zone z = zones[zx][zz]; - if (z.alphaModels.isEmpty() || (worldViewId == -1 && !z.inSceneFrustum)) - continue; - - if(z.occlusionQuery != null && z.occlusionQuery.isOccluded()) + if (z.alphaModels.isEmpty() || (worldViewId == -1 && !z.inSceneFrustum) || z.isFullyOccluded) continue; final int dx = camPosX - ((zx - offset) << 10); diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index a3e3889372..b7e64ce5a3 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -75,7 +75,6 @@ public class Zone { @Nullable public GLBuffer vboO, vboA, vboM; public GLTextureBuffer tboF; - public OcclusionQuery occlusionQuery; public ConcurrentLinkedQueue additionalOcclusionQueries = new ConcurrentLinkedQueue<>(); public boolean isFullyOccluded; @@ -94,6 +93,7 @@ public class Zone { ZoneUploadJob uploadJob; int[] levelOffsets = new int[5]; // buffer pos in ints for the end of the level + OcclusionQuery[] levelOcclusionQueries = new OcclusionQuery[5]; int[][] rids; int[][] roofStart; @@ -125,7 +125,9 @@ public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f, int eboShared) } tboF = f; - occlusionQuery = OcclusionManager.getInstance().obtainQuery(); + + for(int i = 0; i < 5; ++i) + levelOcclusionQueries[i] = OcclusionManager.getInstance().obtainQuery(); } public static void freeZones(@Nullable Zone[][] zones) { @@ -174,9 +176,10 @@ public void free() { uploadJob = null; } - if(occlusionQuery != null) { - occlusionQuery.free(); - occlusionQuery = null; + for(int i = 0; i < 5; ++i){ + if(levelOcclusionQueries[i] != null) + levelOcclusionQueries[i].free(); + levelOcclusionQueries[i] = null; } for(AlphaModel m : alphaModels) { @@ -210,26 +213,31 @@ public void free() { alphaModels.clear(); } - public void evaluateOcclusion(){ - if(occlusionQuery != null) { - isFullyOccluded = occlusionQuery.isOccluded(); - if(isFullyOccluded) { - // Check if any of the dynamic occlusion queries are not occluded - for(OcclusionQuery dynamicQuery : additionalOcclusionQueries) { - if(dynamicQuery.isVisible()) { - isFullyOccluded = false; - break; - } - } + public void evaluateOcclusion(WorldViewContext ctx){ + isFullyOccluded = true; + for (int level = ctx.minLevel; level <= ctx.maxLevel; ++level) { + if(levelOcclusionQueries[level] == null || levelOcclusionQueries[level].isVisible()) + isFullyOccluded = false; + if(levelOcclusionQueries[level] != null) + levelOcclusionQueries[level].queue(); + } - if(isFullyOccluded) { - // Zone is fully occluded, we need to requeue all dynamic queries since they are revelvant to if the zone is fully occluded - for(OcclusionQuery dynamicQuery : additionalOcclusionQueries) - dynamicQuery.queue(); + if(isFullyOccluded) { + // Check if any of the dynamic occlusion queries are not occluded + for(OcclusionQuery dynamicQuery : additionalOcclusionQueries) { + if(dynamicQuery.isVisible()) { + isFullyOccluded = false; + break; } } - occlusionQuery.queue(); + + if(isFullyOccluded) { + // Zone is fully occluded, we need to requeue all dynamic queries since they are revelvant to if the zone is fully occluded + for(OcclusionQuery dynamicQuery : additionalOcclusionQueries) + dynamicQuery.queue(); + } } + if(!isFullyOccluded) // Dynamics will reappend when they are processed additionalOcclusionQueries.clear(); } @@ -362,9 +370,11 @@ public void setMetadata(WorldViewContext viewContext, SceneContext sceneContext, int baseX = (mx - (sceneContext.sceneOffset >> 3)) << 10; int baseZ = (mz - (sceneContext.sceneOffset >> 3)) << 10; - if(occlusionQuery != null) { - occlusionQuery.setOffset(baseX, 0, baseZ); - occlusionQuery.setWorldView(viewContext.uboWorldViewStruct); + for(int i = 0; i < 5; i++) { + if(levelOcclusionQueries[i] != null) { + levelOcclusionQueries[i].setOffset(baseX, 0, baseZ); + levelOcclusionQueries[i].setWorldView(viewContext.uboWorldViewStruct); + } } try (MemoryStack stack = MemoryStack.stackPush()) { @@ -424,6 +434,8 @@ void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) } for (int level = ctx.minLevel; level <= maxLevel; ++level) { + if(levelOcclusionQueries[level] != null && levelOcclusionQueries[level].isOccluded()) + continue; int[] rids = this.rids[level]; int[] roofStart = this.roofStart[level]; int[] roofEnd = this.roofEnd[level]; @@ -1020,7 +1032,7 @@ synchronized void multizoneLocs(SceneContext ctx, int zx, int zz, Camera camera, int zz2 = (centerZ >> 10) + offset; if (zx2 >= 0 && zx2 < zones.length && zz2 >= 0 && zz2 < zones[0].length) { Zone z2 = zones[zx2][zz2]; - if(z2.inSceneFrustum && z2.initialized && (z2.occlusionQuery == null || z2.occlusionQuery.isVisible())) { + if(z2.inSceneFrustum && z2.initialized && !z2.isFullyOccluded) { max = distance; closestZoneX = centerX >> 10; closestZoneZ = centerZ >> 10; diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 51fa57013a..37ff52cc27 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -327,7 +327,7 @@ public void preSceneDraw( if(!zone.initialized) continue; zone.multizoneLocs(ctx.sceneContext, zx - offset, zz - offset, sceneCamera, ctx.zones); - zone.evaluateOcclusion(); + zone.evaluateOcclusion(ctx); } } @@ -937,7 +937,7 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int return; Zone z = ctx.zones[zx][zz]; - if(!z.initialized || z.sizeO == 0 || z.occlusionQuery == null || z.occlusionQuery.isOccluded()) + if(!z.initialized || z.sizeO == 0 ) return; frameTimer.begin(Timer.DRAW_ZONE_OPAQUE); @@ -967,7 +967,7 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i return; final Zone z = ctx.zones[zx][zz]; - if (!z.initialized || z.occlusionQuery == null || z.occlusionQuery.isOccluded()) + if (!z.initialized || (z.levelOcclusionQueries[level] != null && z.levelOcclusionQueries[level].isOccluded())) return; frameTimer.begin(Timer.DRAW_ZONE_ALPHA); From 8f9988507f518cf7228a1bcf312b01dec3137145 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 26 Feb 2026 22:55:26 +0000 Subject: [PATCH 21/21] Removed debug code --- src/main/java/rs117/hd/renderer/zone/OcclusionManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java index d7ce950bf2..4b5ea5338b 100644 --- a/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java +++ b/src/main/java/rs117/hd/renderer/zone/OcclusionManager.java @@ -434,7 +434,7 @@ public void occlusionDebugPass() { } public void occlusionPass() { - if (queuedQueries.isEmpty() || true) + if (queuedQueries.isEmpty()) return; frameTimer.begin(Timer.RENDER_OCCLUSION);