From 95129f691c7ac7cad6f26fd431e53d8156189c18 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sun, 10 May 2026 02:09:06 +0100 Subject: [PATCH 1/8] Added Support for TextureBuffer to use RGBA when available * Created TextureBufferReader to simplify reading Buffers * Added texture_facees.glsl which proves a `getFaceData` which returns a struct Use Macros to build the parser Define the buffer to remove the need to pass it into each read func Dont Align to texal size underlying buffer will always be pow2, so even if its RGB or RGBA it'll fit correctly Implemented ModelData Both static & dynamic models can now sample data specific to that model Ensure ModelDataSize is updated correctly Write less data for models --- src/main/java/rs117/hd/HdPlugin.java | 2 + .../hd/opengl/shader/SceneShaderProgram.java | 4 + .../hd/opengl/shader/ShadowShaderProgram.java | 5 + .../hd/renderer/zone/DynamicModelVAO.java | 45 +++-- .../renderer/zone/ModelStreamingManager.java | 16 ++ .../rs117/hd/renderer/zone/SceneUploader.java | 115 ++++++----- .../hd/renderer/zone/VertexWriteCache.java | 34 +++- .../java/rs117/hd/renderer/zone/Zone.java | 32 ++- .../rs117/hd/renderer/zone/ZoneRenderer.java | 1 + .../rs117/hd/renderer/zone/ZoneUploadJob.java | 12 +- .../hd/utils/buffer/GLTextureBuffer.java | 29 +-- .../rs117/hd/utils/buffer/GpuIntBuffer.java | 4 +- src/main/resources/rs117/hd/scene_vert.glsl | 51 ++--- src/main/resources/rs117/hd/shadow_vert.glsl | 24 ++- .../rs117/hd/uniforms/model_data.glsl | 25 +++ .../rs117/hd/uniforms/texture_faces.glsl | 39 ++++ .../rs117/hd/utils/texture_buffer_reader.glsl | 187 ++++++++++++++++++ 17 files changed, 511 insertions(+), 114 deletions(-) create mode 100644 src/main/resources/rs117/hd/uniforms/model_data.glsl create mode 100644 src/main/resources/rs117/hd/uniforms/texture_faces.glsl create mode 100644 src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index e08fa46dd5..bf215902de 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -128,6 +128,7 @@ import rs117.hd.utils.ResourcePath; import rs117.hd.utils.ShaderRecompile; import rs117.hd.utils.buffer.GLBuffer; +import rs117.hd.utils.buffer.GLTextureBuffer; import rs117.hd.utils.jobs.GenericJob; import rs117.hd.utils.jobs.JobSystem; @@ -901,6 +902,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("TEXEL_SIZE", GLTextureBuffer.isRGBASupported() ? 4 : 3) .define("UI_SCALING_MODE", config.uiScalingMode()) .define("COLOR_BLINDNESS", config.colorBlindness()) .define("APPLY_COLOR_FILTER", configColorFilter != ColorFilter.NONE) diff --git a/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java index 1c75140e78..cb2275a09a 100644 --- a/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java @@ -4,6 +4,7 @@ import static rs117.hd.HdPlugin.TEXTURE_UNIT_GAME; 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_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; public class SceneShaderProgram extends ShaderProgram { @@ -11,6 +12,7 @@ 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 uniModelData = addUniformTexture("modelData"); public SceneShaderProgram() { super(t -> t @@ -25,12 +27,14 @@ protected void initialize() { uniShadowMap.set(TEXTURE_UNIT_SHADOW_MAP); uniTiledLightingTextureArray.set(TEXTURE_UNIT_TILED_LIGHTING_MAP); uniTextureFaces.set(TEXTURE_UNIT_TEXTURED_FACES); + uniModelData.set(TEXTURE_UNIT_MODEL_DATA); } public static class Legacy extends SceneShaderProgram { Legacy() { shaderTemplate.add(GL_GEOMETRY_SHADER, "scene_geom.glsl"); uniTextureFaces.ignoreMissing = true; + uniModelData.ignoreMissing = true; } } } diff --git a/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java index 1663800a11..bdd8e42197 100644 --- a/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java @@ -5,11 +5,13 @@ import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPlugin.TEXTURE_UNIT_GAME; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; public abstract class ShadowShaderProgram extends ShaderProgram { protected final UniformTexture uniTextureArray = addUniformTexture("textureArray"); protected final UniformTexture uniTextureFaces = addUniformTexture("textureFaces"); + protected final UniformTexture uniModelData = addUniformTexture("modelData"); protected ShadowMode mode; @@ -23,6 +25,7 @@ public abstract class ShadowShaderProgram extends ShaderProgram { protected void initialize() { uniTextureArray.set(TEXTURE_UNIT_GAME); uniTextureFaces.set(TEXTURE_UNIT_TEXTURED_FACES); + uniModelData.set(TEXTURE_UNIT_MODEL_DATA); } @Override @@ -47,6 +50,8 @@ public static class Legacy extends ShadowShaderProgram { public Legacy setMode(ShadowMode mode) { this.mode = mode; uniTextureArray.ignoreMissing = mode != ShadowMode.DETAILED; + uniTextureFaces.ignoreMissing = true; + uniModelData.ignoreMissing = true; return this; } diff --git a/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java b/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java index 5e1dccb3c0..cbdf3d66e0 100644 --- a/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java +++ b/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java @@ -17,6 +17,7 @@ import static rs117.hd.HdPlugin.NVIDIA_GPU; import static rs117.hd.HdPlugin.SUPPORTS_INDIRECT_DRAW; import static rs117.hd.HdPlugin.SUPPORTS_STORAGE_BUFFERS; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; import static rs117.hd.utils.MathUtils.*; import static rs117.hd.utils.buffer.GLBuffer.STORAGE_IMMUTABLE; @@ -44,13 +45,15 @@ public class DynamicModelVAO implements Destructible { private final GLBuffer vboRender; private final GLBuffer vboStaging; - private final GLTextureBuffer tbo; + private final GLTextureBuffer tboF; + private final GLTextureBuffer tboM; private final ArrayDeque usedViews = new ArrayDeque<>(); private final ArrayDeque freeViews = new ArrayDeque<>(); private final GLMappedBufferIntWriter vboWriter; - private final GLMappedBufferIntWriter tboWriter; + private final GLMappedBufferIntWriter tboFWriter; + private final GLMappedBufferIntWriter tboMWriter; private boolean isMapped = false; private int[] drawOffsets = new int[16]; @@ -81,15 +84,18 @@ public class DynamicModelVAO implements Destructible { } this.vboWriter = new GLMappedBufferIntWriter(this.vboStaging); - this.tbo = new GLTextureBuffer("VAO::TBO::" + name, GL_STREAM_DRAW, STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE); - this.tboWriter = new GLMappedBufferIntWriter(this.tbo); + this.tboF = new GLTextureBuffer("VAO::TexturedFaces::" + name, GL_STREAM_DRAW, STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE); + this.tboM = new GLTextureBuffer("VAO::ModelData::" + name, GL_STREAM_DRAW, STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE); + this.tboFWriter = new GLMappedBufferIntWriter(this.tboF); + this.tboMWriter = new GLMappedBufferIntWriter(this.tboM); } public boolean hasStagingBuffer() { return vboRender != vboStaging; } void initialize() { vao = glGenVertexArrays(); - tbo.initialize(INITIAL_SIZE); + tboF.initialize(INITIAL_SIZE); + tboM.initialize(INITIAL_SIZE); vboRender.initialize(INITIAL_SIZE); if (vboRender != vboStaging) vboStaging.initialize(INITIAL_SIZE); @@ -143,7 +149,8 @@ void bindRenderVAO() { void map() { vboWriter.map(false); - tboWriter.map(false); + tboFWriter.map(false); + tboMWriter.map(false); reset(); isMapped = true; @@ -152,7 +159,8 @@ void map() { synchronized void unmap(boolean coalesce) { final int renderVBOId = vboRender.id; long vboWrittenBytes = vboWriter.flush(); - tboWriter.flush(); + tboFWriter.flush(); + tboMWriter.flush(); if (drawRangeCount > 0) { mergeRanges(); @@ -192,10 +200,12 @@ synchronized void unmap(boolean coalesce) { @Override public void destroy() { vboWriter.destroy(); - tboWriter.destroy(); + tboFWriter.destroy(); + tboMWriter.destroy(); vboRender.destroy(); vboStaging.destroy(); - tbo.destroy(); + tboF.destroy(); + tboM.destroy(); if (vao != 0) glDeleteVertexArrays(vao); @@ -231,9 +241,11 @@ public synchronized View beginDraw(int drawIdx, int faceCount) { if (view == null) view = new View(); view.vbo = vboWriter.reserve(faceCount * 3 * VERT_SIZE_INTS); - view.tbo = tboWriter.reserve(faceCount * 9); + view.tboF = tboFWriter.reserve(faceCount * 4); + view.tboM = tboMWriter.reserve(4); view.vao = vao; - view.tboTexId = tbo.getTexId(); + view.tboFId = tboF.getTexId(); + view.tboMId = tboM.getTexId(); view.drawIdx = drawIdx; return view; @@ -252,7 +264,7 @@ private synchronized void endDraw(View view) { // Clear ReservedViews before returning to pool view.vbo = null; - view.tbo = null; + view.tboF = null; usedViews.add(view); } @@ -280,7 +292,8 @@ void draw(CommandBuffer cmd) { return; cmd.BindVertexArray(vao); - cmd.BindTextureUnit(GL_TEXTURE_BUFFER, tbo.getTexId(), TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, tboF.getTexId(), TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, tboM.getTexId(), TEXTURE_UNIT_MODEL_DATA); if (drawRangeCount == 1) { if (GL_CAPS.OpenGL40 && SUPPORTS_INDIRECT_DRAW) { @@ -307,9 +320,11 @@ void reset() { public final class View { public ReservedView vbo; - public ReservedView tbo; + public ReservedView tboF; + public ReservedView tboM; public int vao; - public int tboTexId; + public int tboFId; + public int tboMId; private int drawIdx; public int getStartOffset() { diff --git a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java index 7b9a941558..54533d0dec 100644 --- a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java +++ b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java @@ -328,12 +328,15 @@ private void uploadTempModel( (!sceneManager.isRoot(ctx) || zone.inShadowFrustum) ) { final DynamicModelVAO.View shadowView = ctx.beginDraw(VAO_SHADOW, culledFaces.length); + int shadowModelIdx = SceneUploader.writeModelData(shadowView.tboM.getBuffer(), x, y, z, m, modelOverride); sceneUploader.uploadTempModel( culledFaces, m, modelOverride, preOrientation, orientation, + shadowModelIdx, + shadowModelIdx, true, shadowView, shadowView @@ -351,12 +354,17 @@ private void uploadTempModel( final DynamicModelVAO.View opaqueView = ctx.beginDraw(isPlayer ? VAO_PLAYER : VAO_OPAQUE, drawIndex, opaqueFaceCount); final DynamicModelVAO.View alphaView = alphaFaceCount > 0 ? ctx.beginDraw(VAO_ALPHA, alphaFaceCount) : opaqueView; + final int opaqueModelIdx = SceneUploader.writeModelData(opaqueView.tboM.getBuffer(), x, y, z, m, modelOverride); + final int alphaModelIdx = alphaFaceCount > 0 ? SceneUploader.writeModelData(alphaView.tboM.getBuffer(), x, y, z, m, modelOverride) : opaqueModelIdx; + sceneUploader.uploadTempModel( visibleFaces, m, modelOverride, preOrientation, orientation, + opaqueModelIdx, + alphaModelIdx, isSquashed, opaqueView, alphaView @@ -590,12 +598,15 @@ private void uploadDynamicModel( (!sceneManager.isRoot(ctx) || zone.inShadowFrustum) ) { final DynamicModelVAO.View shadowView = ctx.beginDraw(VAO_SHADOW, culledFaces.length); + final int shadowModelIdx = SceneUploader.writeModelData(shadowView.tboM.getBuffer(), x, y, z, m, modelOverride); sceneUploader.uploadTempModel( culledFaces, m, modelOverride, preOrientation, orient, + shadowModelIdx, + shadowModelIdx, true, shadowView, shadowView @@ -610,12 +621,17 @@ private void uploadDynamicModel( final DynamicModelVAO.View opaqueView = ctx.beginDraw(VAO_OPAQUE, drawIndex, opaqueFaceCount); final DynamicModelVAO.View alphaView = alphaFaceCount > 0 ? ctx.beginDraw(VAO_ALPHA, alphaFaceCount) : opaqueView; + final int opaqueModelIdx = SceneUploader.writeModelData(opaqueView.tboM.getBuffer(), x, y, z, m, modelOverride); + final int alphaModelIdx = alphaFaceCount > 0 ? SceneUploader.writeModelData(alphaView.tboM.getBuffer(), x, y, z, m, modelOverride) : opaqueModelIdx; + sceneUploader.uploadTempModel( visibleFaces, m, modelOverride, preOrientation, orient, + opaqueModelIdx, + alphaModelIdx, isSquashed, opaqueView, alphaView diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index 160bab014e..637b3b5616 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.nio.IntBuffer; import java.util.HashSet; import java.util.Set; import javax.inject.Inject; @@ -196,7 +197,8 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws var vb = zone.vboO != null ? new GpuIntBuffer(zone.vboO.mapped()) : null; var ab = zone.vboA != null ? new GpuIntBuffer(zone.vboA.mapped()) : null; var fb = zone.tboF != null ? new GpuIntBuffer(zone.tboF.mapped()) : null; - assert fb != null; + var md = zone.tboF != null ? new GpuIntBuffer(zone.tboM.mapped()) : null; + assert fb != null && md != null; roofIds.clear(); for (int level = 0; level <= 3; ++level) { @@ -217,13 +219,13 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws this.level = z; if (z == 0) { - uploadZoneLevel(ctx, zone, mzx, mzz, 0, false, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 0, true, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 1, true, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 2, true, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 3, true, roofIds, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, 0, false, roofIds, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 0, true, roofIds, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 1, true, roofIds, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 2, true, roofIds, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 3, true, roofIds, vb, ab, fb, md); } else { - uploadZoneLevel(ctx, zone, mzx, mzz, z, false, roofIds, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, z, false, roofIds, vb, ab, fb, md); } if (vb != null) { @@ -249,7 +251,8 @@ private void uploadZoneLevel( Set roofIds, GpuIntBuffer vb, GpuIntBuffer ab, - GpuIntBuffer fb + GpuIntBuffer fb, + GpuIntBuffer md ) throws InterruptedException { int ridx = 0; @@ -257,7 +260,7 @@ private void uploadZoneLevel( for (int id : roofIds) { int pos = vb != null ? vb.position() : 0; - uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, id, visbelow, vb, ab, fb); + uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, id, visbelow, vb, ab, fb, md); int endpos = vb != null ? vb.position() : 0; @@ -270,7 +273,7 @@ private void uploadZoneLevel( } // upload everything else - uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, 0, visbelow, vb, ab, fb); + uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, 0, visbelow, vb, ab, fb, md); } private void uploadZoneLevelRoof( @@ -283,7 +286,8 @@ private void uploadZoneLevelRoof( boolean visbelow, GpuIntBuffer vb, GpuIntBuffer ab, - GpuIntBuffer fb + GpuIntBuffer fb, + GpuIntBuffer md ) throws InterruptedException { this.basex = (mzx - (ctx.sceneOffset >> 3)) << 10; this.basez = (mzz - (ctx.sceneOffset >> 3)) << 10; @@ -317,7 +321,7 @@ private void uploadZoneLevelRoof( this.rid = rid; if (onBeforeProcessTile != null) onBeforeProcessTile.invoke(t, false); - uploadZoneTile(ctx, zone, t, false, false, vb, ab, fb); + uploadZoneTile(ctx, zone, t, false, false, vb, ab, fb, md); } } } @@ -344,7 +348,7 @@ private void uploadZoneWater( if (t != null) { if (onBeforeProcessTile != null) onBeforeProcessTile.invoke(t, false); - uploadZoneTile(ctx, zone, t, false, true, vb, null, fb); + uploadZoneTile(ctx, zone, t, false, true, vb, null, fb, null); } } } @@ -464,7 +468,8 @@ private void uploadZoneTile( boolean onlyWaterSurface, GpuIntBuffer vertexBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { var tilePoint = t.getSceneLocation(); int tileExX = tilePoint.getX() + ctx.sceneOffset; @@ -496,11 +501,11 @@ private void uploadZoneTile( uploadTileModel(ctx, t, model, onlyWaterSurface, tileExX, tileExY, tileZ, basex, basez, vertexBuffer, textureBuffer); if (!onlyWaterSurface) - uploadZoneTileRenderables(ctx, zone, t, vertexBuffer, alphaBuffer, textureBuffer); + uploadZoneTileRenderables(ctx, zone, t, vertexBuffer, alphaBuffer, textureBuffer, modelBuffer); Tile bridge = t.getBridge(); if (bridge != null) - uploadZoneTile(ctx, zone, bridge, true, onlyWaterSurface, vertexBuffer, alphaBuffer, textureBuffer); + uploadZoneTile(ctx, zone, bridge, true, onlyWaterSurface, vertexBuffer, alphaBuffer, textureBuffer, modelBuffer); } private void uploadZoneTileRenderables( @@ -509,7 +514,8 @@ private void uploadZoneTileRenderables( Tile t, GpuIntBuffer vertexBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { WallObject wallObject = t.getWallObject(); if (wallObject != null && renderCallbackManager.drawObject(ctx.scene, wallObject)) { @@ -533,7 +539,8 @@ private void uploadZoneTileRenderables( wallObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); Renderable renderable2 = wallObject.getRenderable2(); @@ -555,7 +562,8 @@ private void uploadZoneTileRenderables( wallObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } @@ -582,7 +590,8 @@ private void uploadZoneTileRenderables( decorativeObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); Renderable renderable2 = decorativeObject.getRenderable2(); @@ -604,7 +613,8 @@ private void uploadZoneTileRenderables( decorativeObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } @@ -626,7 +636,8 @@ private void uploadZoneTileRenderables( groundObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } @@ -659,7 +670,8 @@ private void uploadZoneTileRenderables( gameObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } } @@ -688,6 +700,7 @@ private void estimateRenderableSize(Zone z, Renderable r, ModelOverride modelOve z.sizeA += faceCount; } z.sizeF += faceCount; + z.sizeM++; } private void uploadZoneRenderable( @@ -708,7 +721,8 @@ private void uploadZoneRenderable( int id, GpuIntBuffer opaqueBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { Model model; if (r instanceof Model) { @@ -742,7 +756,8 @@ private void uploadZoneRenderable( x - basex, y, z - basez, opaqueBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } catch (Throwable ex) { log.warn( @@ -777,6 +792,7 @@ private void uploadZoneRenderable( materialManager, zone.glVaoA, zone.tboF.getTexId(), + zone.tboM.getTexId(), model, modelOverride, alphaStart, alphaEnd, x - basex, y, z - basez, lx, lz, ux, uz, @@ -1341,6 +1357,19 @@ private void uploadTileModel( } } + public static final int MODEL_DATA_SIZE = 4; + + public static int writeModelData(IntBuffer modelBuffer, int x, int y, int z, Model model, ModelOverride override) { + int modelIdx = modelBuffer.position() / MODEL_DATA_SIZE; + modelBuffer + .put(x) + .put(y) + .put(z) + .put(model.getModelHeight()); + assert modelBuffer.position() % MODEL_DATA_SIZE == 0; + return modelIdx + 1; + } + // scene upload private int uploadStaticModel( ZoneSceneContext ctx, @@ -1352,7 +1381,8 @@ private int uploadStaticModel( int x, int y, int z, GpuIntBuffer opaqueBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { if (writeCache == null) writeCache = new VertexWriteCache.Collection(); @@ -1442,6 +1472,8 @@ private int uploadStaticModel( final Material baseMaterial = modelOverride.baseMaterial; final Material textureMaterial = modelOverride.textureMaterial; + final int modelIdx = writeModelData(modelBuffer.getBuffer(), x, y, z, model, modelOverride); + int len = 0; for (int face = 0; face < faceCount; ++face) { int color1 = color1s[face]; @@ -1673,31 +1705,27 @@ private int uploadStaticModel( color2 |= packedAlphaBiasHsl; color3 |= packedAlphaBiasHsl; - final int texturedFaceIdx = tb.putFace( - color1, color2, color3, - materialData, materialData, materialData, - 0, 0, 0 - ); + final int texturedFaceIdx = tb.putModelFace(color1, color2, color3, materialData); vb.putStaticVertex( vx1, vy1, vz1, faceUVs[0], faceUVs[1], faceUVs[2], modelNormals[0], modelNormals[1], modelNormals[2], - texturedFaceIdx + texturedFaceIdx, modelIdx ); vb.putStaticVertex( vx2, vy2, vz2, faceUVs[4], faceUVs[5], faceUVs[6], modelNormals[3], modelNormals[4], modelNormals[5], - texturedFaceIdx + texturedFaceIdx, modelIdx ); vb.putStaticVertex( vx3, vy3, vz3, faceUVs[8], faceUVs[9], faceUVs[10], modelNormals[6], modelNormals[7], modelNormals[8], - texturedFaceIdx + texturedFaceIdx, modelIdx ); len += 3; } @@ -1932,6 +1960,8 @@ public void uploadTempModel( ModelOverride modelOverride, int preOrientation, int orientation, + int opaqueModelIdx, + int alphaModelIdx, boolean isShadow, DynamicModelVAO.View opaqueView, DynamicModelVAO.View alphaView @@ -1941,8 +1971,8 @@ public void uploadTempModel( writeCache.setOutputBuffers( opaqueView.vbo.getBuffer(), alphaView.vbo.getBuffer(), - opaqueView.tbo.getBuffer(), - alphaView.tbo.getBuffer() + opaqueView.tboF.getBuffer(), + alphaView.tboF.getBuffer() ); final int[] indices1 = model.getFaceIndices1(); @@ -2095,29 +2125,26 @@ else if (color3 == -1) color2 |= packedAlphaBiasHsl; color3 |= packedAlphaBiasHsl; - final int texturedFaceIdx = tb.putFace( - color1, color2, color3, - materialData, materialData, materialData, - 0, 0, 0 - ); + final int modelIdx = hasAlpha ? alphaModelIdx : opaqueModelIdx; + final int texturedFaceIdx = tb.putModelFace(color1, color2, color3, materialData); vb.putVertex( modelLocalI[vertexOffsetA], modelLocalI[vertexOffsetA + 1], modelLocalI[vertexOffsetA + 2], faceUVs[0], faceUVs[1], faceUVs[2], faceNormals[0], faceNormals[1], faceNormals[2], - texturedFaceIdx + texturedFaceIdx, modelIdx ); vb.putVertex( modelLocalI[vertexOffsetB], modelLocalI[vertexOffsetB + 1], modelLocalI[vertexOffsetB + 2], faceUVs[4], faceUVs[5], faceUVs[6], faceNormals[3], faceNormals[4], faceNormals[5], - texturedFaceIdx + texturedFaceIdx, modelIdx ); vb.putVertex( modelLocalI[vertexOffsetC], modelLocalI[vertexOffsetC + 1], modelLocalI[vertexOffsetC + 2], faceUVs[8], faceUVs[9], faceUVs[10], faceNormals[6], faceNormals[7], faceNormals[8], - texturedFaceIdx + texturedFaceIdx, modelIdx ); } diff --git a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java index b72914f70c..6997310538 100644 --- a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java +++ b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java @@ -38,7 +38,7 @@ private void flushAndGrow() { stagingBuffer = new int[min(stagingBuffer.length * 2, maxCapacity)]; } - public int putFace( + public int putStaticFace( int alphaBiasHslA, int alphaBiasHslB, int alphaBiasHslC, int materialDataA, int materialDataB, int materialDataC, int terrainDataA, int terrainDataB, int terrainDataC @@ -46,7 +46,7 @@ public int putFace( if (stagingPosition + 9 > stagingBuffer.length) flushAndGrow(); - final int textureFaceIdx = (outputBuffer.position() + stagingPosition) / 3; + final int textureFaceIdx = outputBuffer.position() + stagingPosition; final int[] stagingBuffer = this.stagingBuffer; final int stagingPosition = this.stagingPosition; @@ -64,14 +64,32 @@ public int putFace( this.stagingPosition += 9; - return textureFaceIdx; + return textureFaceIdx << 1; + } + + public int putModelFace(int alphaBiasHslA, int alphaBiasHslB, int alphaBiasHslC, int materialData) { + if (stagingPosition + 4 > stagingBuffer.length) + flushAndGrow(); + + final int textureFaceIdx = outputBuffer.position() + stagingPosition; + + final int[] stagingBuffer = this.stagingBuffer; + final int stagingPosition = this.stagingPosition; + + stagingBuffer[stagingPosition] = alphaBiasHslA; + stagingBuffer[stagingPosition + 1] = alphaBiasHslB; + stagingBuffer[stagingPosition + 2] = alphaBiasHslC; + stagingBuffer[stagingPosition + 3] = materialData; + + this.stagingPosition += 4; + return 1 | textureFaceIdx << 1; } 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 modelIdx ) { if (stagingPosition + 8 > stagingBuffer.length) flushAndGrow(); @@ -83,9 +101,9 @@ public void putVertex( stagingBuffer[stagingPosition + 1] = y; stagingBuffer[stagingPosition + 2] = z; stagingBuffer[stagingPosition + 3] = float16(v) << 16 | float16(u); - stagingBuffer[stagingPosition + 4] = float16(w); + stagingBuffer[stagingPosition + 4] = modelIdx << 16 | float16(w); stagingBuffer[stagingPosition + 5] = (ny & 0xFFFF) << 16 | nx & 0xFFFF; - stagingBuffer[stagingPosition + 6] = nz & 0xFFFF; + stagingBuffer[stagingPosition + 6] = (modelIdx & 0xFFFF) << 16 | nz & 0xFFFF; stagingBuffer[stagingPosition + 7] = textureFaceIdx; this.stagingPosition += 8; @@ -95,7 +113,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 modelIdx ) { if (stagingPosition + 7 > stagingBuffer.length) flushAndGrow(); @@ -109,7 +127,7 @@ public void putStaticVertex( stagingBuffer[stagingPosition + 3] = float16(w); // Unnormalized normals, assumed to be within short max stagingBuffer[stagingPosition + 4] = (ny & 0xFFFF) << 16 | nx & 0xFFFF; - stagingBuffer[stagingPosition + 5] = nz & 0xFFFF; + stagingBuffer[stagingPosition + 5] = (modelIdx & 0xFFFF) << 16 | nz & 0xFFFF; stagingBuffer[stagingPosition + 6] = textureFaceIdx; this.stagingPosition += 7; diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 97f8004142..be926450cd 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -32,6 +32,7 @@ import static rs117.hd.HdPlugin.GL_CAPS; import static rs117.hd.HdPlugin.SUPPORTS_INDIRECT_DRAW; import static rs117.hd.HdPlugin.checkGLErrors; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; import static rs117.hd.renderer.zone.ZoneRenderer.eboAlpha; import static rs117.hd.utils.MathUtils.*; @@ -53,6 +54,10 @@ public class Zone implements Destructible { // terrainData ivec3 public static final int TEXTURE_SIZE = 36; + // position vec3 + // height float + public static final int MODEL_DATA_SIZE = 16; + // Metadata format // worldViewIndex int int // sceneOffset int vec2(x, y) @@ -67,10 +72,10 @@ public class Zone implements Destructible { public int glVaoA; public int bufLenA; - public int sizeO, sizeA, sizeF; + public int sizeO, sizeA, sizeF, sizeM; @Nullable public GLBuffer vboO, vboA, vboM; - public GLTextureBuffer tboF; + public GLTextureBuffer tboF, tboM; public boolean initialized; // whether the zone vao and vbos are ready public boolean cull; // whether the zone is queued for deletion @@ -97,7 +102,7 @@ public class Zone implements Destructible { final List alphaModels = new ArrayList<>(0); final ConcurrentLinkedQueue pendingModelJobs = new ConcurrentLinkedQueue<>(); - public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f) { + public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f, GLTextureBuffer m) { assert glVao == 0; assert glVaoA == 0; if (o == null && a == null || f == null) @@ -119,6 +124,7 @@ public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f) { } tboF = f; + tboM = m; } public static void freeZones(@Nullable Zone[][] zones) { @@ -160,6 +166,11 @@ public void destroy() { tboF = null; } + if (tboM != null) { + tboM.destroy(); + tboM = null; + } + if (glVao != 0) { glDeleteVertexArrays(glVao); glVao = 0; @@ -218,6 +229,8 @@ public void unmap() { vboA.unmap(); if (tboF != null) tboF.unmap(); + if(tboM != null) + tboM.unmap(); if (vboO != null) { this.bufLen = vboO.mapped().byteView().position() / VERT_SIZE; @@ -372,6 +385,7 @@ void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) lastDrawMode = STATIC_UNSORTED; lastVao = glVao; lastTboF = tboF.getTexId(); + lastTboM = tboM.getTexId(); flush(cmd); } @@ -386,6 +400,7 @@ void renderOpaqueLevel(CommandBuffer cmd, int level) { lastDrawMode = STATIC_UNSORTED; lastVao = glVao; lastTboF = tboF.getTexId(); + lastTboM = tboM.getTexId(); flush(cmd); } @@ -411,6 +426,7 @@ public static class AlphaModel { short rid; int vao; int tboF; + int tboM; byte level; byte lx, lz, ux, uz; // lower/upper zone coords byte zofx, zofz; // for temp alpha models, offset of source zone from target zone @@ -443,7 +459,8 @@ boolean isTemp() { void setView(DynamicModelVAO.View view) { vao = view.vao; - tboF = view.tboTexId; + tboF = view.tboFId; + tboM = view.tboMId; startpos = view.getStartOffset(); endpos = view.getEndOffset(); } @@ -456,6 +473,7 @@ void addAlphaModel( MaterialManager materialManager, int vao, int tboF, + int tboM, Model model, ModelOverride modelOverride, int startpos, @@ -481,6 +499,7 @@ void addAlphaModel( m.z = (short) z; m.vao = vao; m.tboF = tboF; + m.tboM = tboM; m.rid = (short) rid; m.level = (byte) level; if (lx > -1) { @@ -657,6 +676,7 @@ synchronized void postAlphaPass() { private static int lastDrawMode; private static int lastVao; private static int lastTboF; + private static int lastTboM; private static int lastzx, lastzz; private static final class AlphaSortPredicate implements ToIntFunction { @@ -747,6 +767,7 @@ void renderAlpha( if (lastDrawMode != drawMode || lastVao != m.vao || lastTboF != m.tboF || + lastTboM != m.tboM || lastzx != (zx - m.zofx) || lastzz != (zz - m.zofz) ) { @@ -754,6 +775,7 @@ void renderAlpha( lastDrawMode = drawMode; lastVao = m.vao; lastTboF = m.tboF; + lastTboM = m.tboM; lastzx = zx - m.zofx; lastzz = zz - m.zofz; } @@ -797,6 +819,7 @@ private void flush(CommandBuffer cmd) { long byteOffset = 4L * (eboAlphaOffset - vertexCount); cmd.BindVertexArray(lastVao, eboAlpha); cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboF, TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboM, TEXTURE_UNIT_MODEL_DATA); // The EBO & IDO is bound by in ZoneRenderer if (GL_CAPS.OpenGL40 && SUPPORTS_INDIRECT_DRAW) { cmd.DrawElementsIndirect(GL_TRIANGLES, vertexCount, (int) (byteOffset / 4L), ZoneRenderer.indirectDrawCmdsStaging); @@ -809,6 +832,7 @@ private void flush(CommandBuffer cmd) { convertForDraw(lastDrawMode == STATIC_UNSORTED ? VERT_SIZE : DynamicModelVAO.VERT_SIZE); cmd.BindVertexArray(lastVao); cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboF, TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboM, TEXTURE_UNIT_MODEL_DATA); if (drawIdx == 1) { if (GL_CAPS.OpenGL40 && SUPPORTS_INDIRECT_DRAW) { cmd.DrawArraysIndirect(GL_TRIANGLES, drawOff[0], drawEnd[0], ZoneRenderer.indirectDrawCmdsStaging); diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index be35d19b5c..c4daf422c1 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -91,6 +91,7 @@ public class ZoneRenderer implements Renderer { private static int TEXTURE_UNIT_COUNT = HdPlugin.TEXTURE_UNIT_COUNT; public static final int TEXTURE_UNIT_TEXTURED_FACES = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_MODEL_DATA = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; private static int UNIFORM_BLOCK_COUNT = HdPlugin.UNIFORM_BLOCK_COUNT; public static final int UNIFORM_BLOCK_WORLD_VIEWS = UNIFORM_BLOCK_COUNT++; diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java b/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java index e7f41ee7a0..1620e85452 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java @@ -72,12 +72,20 @@ private void mapZoneVertexBuffers() { GLTextureBuffer f = null; sz = zone.sizeF * Zone.TEXTURE_SIZE; if (sz > 0) { - f = new GLTextureBuffer("Zone::TBO", GL_STATIC_DRAW); + f = new GLTextureBuffer("Zone::TexturedFaces", GL_STATIC_DRAW); f.initialize(sz); f.map(MAP_WRITE); } - zone.initialize(o, a, f); + GLTextureBuffer m = null; + sz = zone.sizeM * Zone.MODEL_DATA_SIZE; + if (sz > 0) { + m = new GLTextureBuffer("Zone::ModelData", GL_STATIC_DRAW); + m.initialize(sz); + m.map(MAP_WRITE); + } + + zone.initialize(o, a, f, m); zone.setMetadata(viewContext, sceneContext, x, z); } catch (Throwable ex) { log.warn( diff --git a/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java b/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java index 1b58183741..713c993c93 100644 --- a/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java @@ -1,55 +1,62 @@ package rs117.hd.utils.buffer; import lombok.Getter; +import rs117.hd.HdPlugin; import static org.lwjgl.opengl.GL33C.*; +import static rs117.hd.HdPlugin.GL_CAPS; public class GLTextureBuffer extends GLBuffer { + + public static boolean isRGBASupported() { return HdPlugin.GL_CAPS.GL_ARB_texture_buffer_object; } + @Getter private int texId; + private final int internalFormat; + public GLTextureBuffer(String name, int usage) { this(name, usage, 0); } public GLTextureBuffer(String name, int usage, int storageFlags) { super(name, GL_TEXTURE_BUFFER, usage, storageFlags); + + internalFormat = isRGBASupported() ? GL_RGBA32I : GL_RGB32I; } @Override public GLTextureBuffer initialize(long initialCapacity) { super.initialize(initialCapacity); - // Create texture texId = glGenTextures(); glBindTexture(target, texId); - - // RGB32 signed integer texture buffer - glTexBuffer(target, GL_RGB32I, id); - + glTexBuffer(target, internalFormat, id); glBindTexture(target, 0); + return this; } @Override public boolean ensureCapacity(long byteOffset, long numBytes) { int oldId = id; - boolean resized = super.ensureCapacity(byteOffset, numBytes); + final boolean resized = super.ensureCapacity(byteOffset, numBytes); + if (oldId != id) { glBindTexture(target, texId); - glTexBuffer(target, GL_RGB32I, id); + glTexBuffer(target, internalFormat, id); glBindTexture(target, 0); } + return resized; } @Override public void destroy() { - if (texId != 0) { + if (texId != 0) glDeleteTextures(texId); - texId = 0; - } + texId = 0; super.destroy(); } -} +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java b/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java index cdf5c6e79a..c3a543c973 100644 --- a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java @@ -145,7 +145,7 @@ public static int putFace( int materialDataA, int materialDataB, int materialDataC, int terrainDataA, int terrainDataB, int terrainDataC ) { - final int textureFaceIdx = buffer.position() / 3; + final int textureFaceIdx = buffer.position(); buffer.put(alphaBiasHslA); buffer.put(alphaBiasHslB); buffer.put(alphaBiasHslC); @@ -157,7 +157,7 @@ public static int putFace( buffer.put(terrainDataA); // TODO: Remove? buffer.put(terrainDataB); buffer.put(terrainDataC); - return textureFaceIdx; + return textureFaceIdx << 1; } public int position() { diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index 06b5268582..db45dfd6ab 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -27,20 +27,21 @@ #include #include +#include +#include #include #include +#include layout (location = 0) in vec3 vPosition; #if ZONE_RENDERER layout (location = 1) in vec4 vUv; layout (location = 2) in vec4 vNormal; - layout (location = 3) in int vTextureFaceIdx; + layout (location = 3) in int vPackedTextureFace; layout (location = 6) in int vWorldViewId; layout (location = 7) in ivec2 vSceneBase; - - uniform isamplerBuffer textureFaces; #else layout (location = 1) in vec3 vUv; layout (location = 2) in vec3 vNormal; @@ -67,32 +68,38 @@ layout (location = 0) in vec3 vPosition; } OUT; void main() { + fWorldViewId = vWorldViewId; + int vertex = gl_VertexID % 3; - bool isProvoking = vertex == 2; - int materialData = 0; - int alphaBiasHsl = 0; - - 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; - fWorldViewId = vWorldViewId; - alphaBiasHsl = fAlphaBiasHsl[vertex]; - materialData = fMaterialData[vertex]; + int alphaBiasHsl; + int materialData; + + if(isModelFace(vPackedTextureFace)) { + ModelFaceData faceData = getModelFaceData(getFaceOffset(vPackedTextureFace)); + fAlphaBiasHsl = faceData.AlphaBiasHsl; + fMaterialData = ivec3(faceData.MaterialData); + fTerrainData = ivec3(0); + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData; } else { - // All outputs must be written to for macOS compatibility - fAlphaBiasHsl = ivec3(0); - fMaterialData = ivec3(0); - fTerrainData = ivec3(0); - fWorldViewId = 0; - alphaBiasHsl = texelFetch(textureFaces, vTextureFaceIdx)[vertex]; - materialData = texelFetch(textureFaces, vTextureFaceIdx + 1)[vertex]; + StaticFaceData faceData = getStaticFaceData(getFaceOffset(vPackedTextureFace)); + fAlphaBiasHsl = faceData.AlphaBiasHsl; + fMaterialData = faceData.MaterialData; + fTerrainData = faceData.TerrainData; + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData[vertex]; } vec3 sceneOffset = vec3(vSceneBase.x, 0, vSceneBase.y); vec3 worldNormal = vNormal.xyz; vec3 worldPosition = sceneOffset + vPosition; + + int modelIdx = int(vNormal.w); + if(modelIdx > 0) { + //ModelData modelData = getModelData(modelIdx); + + } + if (vWorldViewId != -1) { mat4x3 worldViewProjection = mat4x3(getWorldViewProjection(vWorldViewId)); worldPosition = worldViewProjection * vec4(worldPosition, 1.0); diff --git a/src/main/resources/rs117/hd/shadow_vert.glsl b/src/main/resources/rs117/hd/shadow_vert.glsl index 82ead2102d..b632a38ae5 100644 --- a/src/main/resources/rs117/hd/shadow_vert.glsl +++ b/src/main/resources/rs117/hd/shadow_vert.glsl @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -35,12 +36,10 @@ layout (location = 0) in vec3 vPosition; #if ZONE_RENDERER layout (location = 1) in vec4 vUv; - layout (location = 3) in int vTextureFaceIdx; + layout (location = 3) in int vPackedTextureFace; layout (location = 6) in int vWorldViewId; layout (location = 7) in ivec2 vSceneBase; - uniform isamplerBuffer textureFaces; - #if SHADOW_MODE == SHADOW_MODE_DETAILED out vec4 fUvw; flat out int fMaterialData; @@ -52,9 +51,22 @@ layout (location = 0) in vec3 vPosition; 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 alphaBiasHsl; + int materialData; + int terrainData; + int faceDataOffset; + + if(isModelFace(vPackedTextureFace)) { + ModelFaceData faceData = getModelFaceData(getFaceOffset(vPackedTextureFace)); + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData; + terrainData = 0; + } else { + StaticFaceData faceData = getStaticFaceData(getFaceOffset(vPackedTextureFace)); + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData[vertex]; + terrainData = faceData.TerrainData[vertex]; + } int waterTypeIndex = terrainData >> 3 & 0xFF; float opacity = 1 - (alphaBiasHsl >> 24 & 0xFF) / float(0xFF); diff --git a/src/main/resources/rs117/hd/uniforms/model_data.glsl b/src/main/resources/rs117/hd/uniforms/model_data.glsl new file mode 100644 index 0000000000..a5840f628d --- /dev/null +++ b/src/main/resources/rs117/hd/uniforms/model_data.glsl @@ -0,0 +1,25 @@ +#pragma once + +#include + +#define MODEL_DATA_SIZE 4 +#define PARSER_TARGET_BUFFER modelData + +uniform isamplerBuffer modelData; + +struct ModelData { + ivec3 position; + int height; +}; + + +BEGIN_BUFFER_PARSER(readModelData, ModelData) + READ_IVEC3(position) + READ_INT(height) +END_BUFFER_PARSER() + +ModelData getModelData(int modelIdx) { + return readModelData((modelIdx - 1) * MODEL_DATA_SIZE); +} + +#undef PARSER_TARGET_BUFFER \ No newline at end of file diff --git a/src/main/resources/rs117/hd/uniforms/texture_faces.glsl b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl new file mode 100644 index 0000000000..8801f9ae9d --- /dev/null +++ b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl @@ -0,0 +1,39 @@ +#pragma once + +#include + +#define PARSER_TARGET_BUFFER textureFaces + +uniform isamplerBuffer textureFaces; + +struct StaticFaceData { + ivec3 AlphaBiasHsl; + ivec3 MaterialData; + ivec3 TerrainData; +}; + +struct ModelFaceData { + ivec3 AlphaBiasHsl; + int MaterialData; +}; + +bool isModelFace(int packedFaceData) { + return (packedFaceData & 1) == 1; +} + +int getFaceOffset(int packedFaceData) { + return packedFaceData >> 1; +} + +BEGIN_BUFFER_PARSER(getStaticFaceData,StaticFaceData) + READ_IVEC3(AlphaBiasHsl) + READ_IVEC3(MaterialData) + READ_IVEC3(TerrainData) +END_BUFFER_PARSER() + +BEGIN_BUFFER_PARSER(getModelFaceData,ModelFaceData) + READ_IVEC3(AlphaBiasHsl) + READ_INT(MaterialData) +END_BUFFER_PARSER() + +#undef PARSER_TARGET_BUFFER \ No newline at end of file diff --git a/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl b/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl new file mode 100644 index 0000000000..d85e300788 --- /dev/null +++ b/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl @@ -0,0 +1,187 @@ +#pragma once + +// Number of scalar components per fetched texel. +// Valid range: 1-4. +#include TEXEL_SIZE +#ifndef TEXEL_SIZE + #define TEXEL_SIZE 4 +#endif + +#if TEXEL_SIZE < 1 || TEXEL_SIZE > 4 + #error TEXEL_SIZE must be between 1 and 4 +#endif + +// Sequential reader for tightly-packed scalar data stored in a buffer texture. +// +// Layout assumptions: +// - Data is packed scalar-by-scalar with NO padding. +// - Floats are stored as IEEE-754 bit patterns inside integer components. +// - TEXEL_SIZE defines how many usable components exist per fetched texel. +// +// Example packed stream: +// [int][float][vec3][ivec2]... +// +// This reader caches the currently loaded texel to avoid redundant texelFetch +// calls during sequential access. + +struct TexBufferReader { + // Cached texel data. + // Always ivec4 regardless of TEXEL_SIZE. + ivec4 data; + + // Current scalar position in stream. + int position; + + // Currently cached texel index. + int loadedTexel; +}; + +TexBufferReader buildTexBufferReader( + int position +) { + TexBufferReader reader; + + reader.position = position; + + reader.data = ivec4(0); + reader.loadedTexel = -1; + + return reader; +} + +int readInt(isamplerBuffer buf, inout TexBufferReader reader) { +#if TEXEL_SIZE == 4 + int texelIndex = reader.position >> 2; + int component = reader.position & 3; +#else + int texelIndex = reader.position / TEXEL_SIZE; + int component = reader.position % TEXEL_SIZE; +#endif + + if (texelIndex != reader.loadedTexel) { + reader.data = texelFetch(buf, texelIndex); + reader.loadedTexel = texelIndex; + } + + reader.position++; + + switch (component) { + case 0: return reader.data.x; + case 1: return reader.data.y; + case 2: return reader.data.z; + default: return reader.data.w; + } +} + +uint readUInt(isamplerBuffer buf, inout TexBufferReader reader) { + return uint(readInt(buf, reader)); +} + +float readFloat(isamplerBuffer buf, inout TexBufferReader reader) { + return intBitsToFloat(readInt(buf, reader)); +} + +bool readBool(isamplerBuffer buf, inout TexBufferReader reader) { + return readInt(buf, reader) != 0; +} + +ivec2 readIVec2(isamplerBuffer buf, inout TexBufferReader reader) { + return ivec2( + readInt(buf, reader), + readInt(buf, reader) + ); +} + +ivec3 readIVec3(isamplerBuffer buf, inout TexBufferReader reader) { + return ivec3( + readInt(buf, reader), + readInt(buf, reader), + readInt(buf, reader) + ); +} + +ivec4 readIVec4(isamplerBuffer buf, inout TexBufferReader reader) { + return ivec4( + readInt(buf, reader), + readInt(buf, reader), + readInt(buf, reader), + readInt(buf, reader) + ); +} + +uvec2 readUVec2(isamplerBuffer buf, inout TexBufferReader reader) { + return uvec2( + readUInt(buf, reader), + readUInt(buf, reader) + ); +} + +uvec3 readUVec3(isamplerBuffer buf, inout TexBufferReader reader) { + return uvec3( + readUInt(buf, reader), + readUInt(buf, reader), + readUInt(buf, reader) + ); +} + +uvec4 readUVec4(isamplerBuffer buf, inout TexBufferReader reader) { + return uvec4( + readUInt(buf, reader), + readUInt(buf, reader), + readUInt(buf, reader), + readUInt(buf, reader) + ); +} + +vec2 readVec2(isamplerBuffer buf, inout TexBufferReader reader) { + return vec2( + readFloat(buf, reader), + readFloat(buf, reader) + ); +} + +vec3 readVec3(isamplerBuffer buf, inout TexBufferReader reader) { + return vec3( + readFloat(buf, reader), + readFloat(buf, reader), + readFloat(buf, reader) + ); +} + +vec4 readVec4(isamplerBuffer buf, inout TexBufferReader reader) { + return vec4( + readFloat(buf, reader), + readFloat(buf, reader), + readFloat(buf, reader), + readFloat(buf, reader) + ); +} + +void skipScalars(inout TexBufferReader reader, int count) { + reader.position += count; +} + +void rewindReader(inout TexBufferReader reader, int position) { + reader.position = position; +} + +#define BEGIN_BUFFER_PARSER(FuncName, StructType) \ +StructType FuncName(int offset) { \ + TexBufferReader reader = \ + buildTexBufferReader(offset); \ + \ + StructType data; + +#define END_BUFFER_PARSER() \ + return data; \ +} + +#define READ_INT(field) data.field = readInt(PARSER_TARGET_BUFFER, reader); +#define READ_FLOAT(field) data.field = readFloat(PARSER_TARGET_BUFFER, reader); +#define READ_BOOL(field) data.field = readBool(PARSER_TARGET_BUFFER, reader); +#define READ_IVEC2(field) data.field = readIVec2(PARSER_TARGET_BUFFER, reader); +#define READ_IVEC3(field) data.field = readIVec3(PARSER_TARGET_BUFFER, reader); +#define READ_IVEC4(field) data.field = readIVec4(PARSER_TARGET_BUFFER, reader); +#define READ_VEC2(field) data.field = readVec2(PARSER_TARGET_BUFFER, reader); +#define READ_VEC3(field) data.field = readVec3(PARSER_TARGET_BUFFER, reader); +#define READ_VEC4(field) data.field = readVec4(PARSER_TARGET_BUFFER, reader); \ No newline at end of file From b50d54b90e260d54ef13a19930f4d53e2395e587 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Fri, 13 Mar 2026 20:38:18 +0000 Subject: [PATCH 2/8] Dither out geom that is about the clip with the near plane --- src/main/resources/rs117/hd/scene_frag.glsl | 14 ++++++++++++++ src/main/resources/rs117/hd/scene_vert.glsl | 9 +++++---- src/main/resources/rs117/hd/utils/misc.glsl | 19 +++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/main/resources/rs117/hd/scene_frag.glsl b/src/main/resources/rs117/hd/scene_frag.glsl index bcc9ac06f0..f43ebd92c2 100644 --- a/src/main/resources/rs117/hd/scene_frag.glsl +++ b/src/main/resources/rs117/hd/scene_frag.glsl @@ -32,6 +32,8 @@ #define DISPLAY_SHADOWS 0 #define DISPLAY_LIGHTING 0 +#define NEAR_PLANE_DITHER_START 0.2 + #include #include #include @@ -55,6 +57,9 @@ flat in ivec3 fTerrainData; #endif in FragmentData { +#if ZONE_RENDERER + vec4 positionCS; +#endif vec3 position; vec2 uv; vec3 normal; @@ -84,6 +89,15 @@ vec2 worldUvs(float scale) { void main() { vec3 downDir = vec3(0, -1, 0); +#if ZONE_RENDERER + float viewZ = 1.0 - (0.5 + (IN.positionCS.z / IN.positionCS.w) * 0.5); + if(viewZ < NEAR_PLANE_DITHER_START) { + float fadeAmount = 1.0 - saturate(viewZ / NEAR_PLANE_DITHER_START); + if(orderedDither(gl_FragCoord.xy, pow(fadeAmount, 1.5) - 0.01, 1.75)) + discard; + } +#endif + // View & light directions are from the fragment to the camera/light vec3 viewDir = normalize(cameraPos - IN.position); diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index db45dfd6ab..884e08a97b 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -61,6 +61,7 @@ layout (location = 0) in vec3 vPosition; #endif out FragmentData { + vec4 positionCS; vec3 position; vec2 uv; vec3 normal; @@ -116,12 +117,12 @@ layout (location = 0) in vec3 vPosition; fFlatNormal = worldNormal; #endif - vec4 clipPosition = projectionMatrix * vec4(worldPosition, 1.0); int depthBias = (alphaBiasHsl >> 16) & 0xff; - if (projectionMatrix[2][3] != 0) // Disable depth bias for orthographic projection - clipPosition.z += depthBias / 128.0; + OUT.positionCS = projectionMatrix * vec4(worldPosition, 1.0); + if (projectionMatrix[2][3] != 0) // Disable depth bias for orthographic projection + OUT.positionCS.z += depthBias / 128.0; - gl_Position = clipPosition; + gl_Position = OUT.positionCS; } #else out vec3 gPosition; diff --git a/src/main/resources/rs117/hd/utils/misc.glsl b/src/main/resources/rs117/hd/utils/misc.glsl index 6e5406693b..b194fcff9c 100644 --- a/src/main/resources/rs117/hd/utils/misc.glsl +++ b/src/main/resources/rs117/hd/utils/misc.glsl @@ -112,6 +112,25 @@ void undoVanillaShading(inout int hsl, vec3 unrotatedNormal) { } #endif +const int DITHER_MAP_LEN = 4; +const float DITHER_MAP_SCALE = 16.0; + +const float DITHER_MAP[DITHER_MAP_LEN * DITHER_MAP_LEN] = float[]( + 0.0 / DITHER_MAP_SCALE, 8.0 / DITHER_MAP_SCALE, 2.0 / DITHER_MAP_SCALE, 10.0 / DITHER_MAP_SCALE, + 12.0 / DITHER_MAP_SCALE, 4.0 / DITHER_MAP_SCALE, 14.0 / DITHER_MAP_SCALE, 6.0 / DITHER_MAP_SCALE, + 3.0 / DITHER_MAP_SCALE, 11.0 / DITHER_MAP_SCALE, 1.0 / DITHER_MAP_SCALE, 9.0 / DITHER_MAP_SCALE, + 15.0 / DITHER_MAP_SCALE, 7.0 / DITHER_MAP_SCALE, 13.0 / DITHER_MAP_SCALE, 5.0 / DITHER_MAP_SCALE +); + +// ------------------------------------------------------------ +// Based on https://www.shadertoy.com/view/4t2cRt +// Returns a dither value (0.0 or 1.0) based on coords + opacity +// ------------------------------------------------------------ +bool orderedDither(vec2 pixelCoord, float opacity, float scaleFactor) { + ivec2 coord = ivec2(pixelCoord / scaleFactor) & (DITHER_MAP_LEN - 1); + return DITHER_MAP[coord.x + coord.y * DITHER_MAP_LEN] < clamp(opacity, 0.0, 1.0); +} + // 2D Random float hash(in vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); From 7fcb3acf7f07ee7ec4ec5c2cd610fe2cda6882bf Mon Sep 17 00:00:00 2001 From: Hooder Date: Sat, 9 May 2026 09:09:40 +0200 Subject: [PATCH 3/8] Clarify licensing --- src/main/resources/rs117/hd/utils/misc.glsl | 25 +++++++++------------ 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/main/resources/rs117/hd/utils/misc.glsl b/src/main/resources/rs117/hd/utils/misc.glsl index b194fcff9c..a625100fce 100644 --- a/src/main/resources/rs117/hd/utils/misc.glsl +++ b/src/main/resources/rs117/hd/utils/misc.glsl @@ -112,23 +112,20 @@ void undoVanillaShading(inout int hsl, vec3 unrotatedNormal) { } #endif -const int DITHER_MAP_LEN = 4; -const float DITHER_MAP_SCALE = 16.0; - -const float DITHER_MAP[DITHER_MAP_LEN * DITHER_MAP_LEN] = float[]( - 0.0 / DITHER_MAP_SCALE, 8.0 / DITHER_MAP_SCALE, 2.0 / DITHER_MAP_SCALE, 10.0 / DITHER_MAP_SCALE, - 12.0 / DITHER_MAP_SCALE, 4.0 / DITHER_MAP_SCALE, 14.0 / DITHER_MAP_SCALE, 6.0 / DITHER_MAP_SCALE, - 3.0 / DITHER_MAP_SCALE, 11.0 / DITHER_MAP_SCALE, 1.0 / DITHER_MAP_SCALE, 9.0 / DITHER_MAP_SCALE, - 15.0 / DITHER_MAP_SCALE, 7.0 / DITHER_MAP_SCALE, 13.0 / DITHER_MAP_SCALE, 5.0 / DITHER_MAP_SCALE +const int BAYER_ORDER = 4; +const float BAYER_DIVISOR = 16.0; +const float BAYER_MATRIX[BAYER_ORDER * BAYER_ORDER] = float[]( + 0.0 / BAYER_DIVISOR, 8.0 / BAYER_DIVISOR, 2.0 / BAYER_DIVISOR, 10.0 / BAYER_DIVISOR, + 12.0 / BAYER_DIVISOR, 4.0 / BAYER_DIVISOR, 14.0 / BAYER_DIVISOR, 6.0 / BAYER_DIVISOR, + 3.0 / BAYER_DIVISOR, 11.0 / BAYER_DIVISOR, 1.0 / BAYER_DIVISOR, 9.0 / BAYER_DIVISOR, + 15.0 / BAYER_DIVISOR, 7.0 / BAYER_DIVISOR, 13.0 / BAYER_DIVISOR, 5.0 / BAYER_DIVISOR ); -// ------------------------------------------------------------ -// Based on https://www.shadertoy.com/view/4t2cRt -// Returns a dither value (0.0 or 1.0) based on coords + opacity -// ------------------------------------------------------------ +// Based on https://www.shadertoy.com/view/4t2cRt (merger doctrine) +// Returns a dither value (0.0 or 1.0) based on coords & opacity bool orderedDither(vec2 pixelCoord, float opacity, float scaleFactor) { - ivec2 coord = ivec2(pixelCoord / scaleFactor) & (DITHER_MAP_LEN - 1); - return DITHER_MAP[coord.x + coord.y * DITHER_MAP_LEN] < clamp(opacity, 0.0, 1.0); + ivec2 coord = ivec2(pixelCoord / scaleFactor) & (BAYER_ORDER - 1); + return BAYER_MATRIX[coord.x + coord.y * BAYER_ORDER] < clamp(opacity, 0.0, 1.0); } // 2D Random From 2375e81ce6e0397832d592b500f66bd19149e9b0 Mon Sep 17 00:00:00 2001 From: Hooder Date: Sat, 9 May 2026 09:09:45 +0200 Subject: [PATCH 4/8] Formatting --- src/main/resources/rs117/hd/scene_frag.glsl | 16 ++++++++-------- src/main/resources/rs117/hd/scene_vert.glsl | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/resources/rs117/hd/scene_frag.glsl b/src/main/resources/rs117/hd/scene_frag.glsl index f43ebd92c2..77fc4b55e3 100644 --- a/src/main/resources/rs117/hd/scene_frag.glsl +++ b/src/main/resources/rs117/hd/scene_frag.glsl @@ -89,14 +89,14 @@ vec2 worldUvs(float scale) { void main() { vec3 downDir = vec3(0, -1, 0); -#if ZONE_RENDERER - float viewZ = 1.0 - (0.5 + (IN.positionCS.z / IN.positionCS.w) * 0.5); - if(viewZ < NEAR_PLANE_DITHER_START) { - float fadeAmount = 1.0 - saturate(viewZ / NEAR_PLANE_DITHER_START); - if(orderedDither(gl_FragCoord.xy, pow(fadeAmount, 1.5) - 0.01, 1.75)) - discard; - } -#endif + #if ZONE_RENDERER + float viewZ = 1.0 - (0.5 + (IN.positionCS.z / IN.positionCS.w) * 0.5); + if (viewZ < NEAR_PLANE_DITHER_START) { + float fadeAmount = 1.0 - saturate(viewZ / NEAR_PLANE_DITHER_START); + if (orderedDither(gl_FragCoord.xy, pow(fadeAmount, 1.5) - 0.01, 1.75)) + discard; + } + #endif // View & light directions are from the fragment to the camera/light vec3 viewDir = normalize(cameraPos - IN.position); diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index 884e08a97b..329f1347fb 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -119,8 +119,8 @@ layout (location = 0) in vec3 vPosition; int depthBias = (alphaBiasHsl >> 16) & 0xff; OUT.positionCS = projectionMatrix * vec4(worldPosition, 1.0); - if (projectionMatrix[2][3] != 0) // Disable depth bias for orthographic projection - OUT.positionCS.z += depthBias / 128.0; + if (projectionMatrix[2][3] != 0) // Disable depth bias for orthographic projection + OUT.positionCS.z += depthBias / 128.0; gl_Position = OUT.positionCS; } From 003855262867134ac25509e1efe15919fc76dc5c Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sat, 9 May 2026 13:30:16 +0100 Subject: [PATCH 5/8] Added Toggle & switched to Interleaved Gradient Dithering * Use FragDepth to calculate view depth * Optimise Bayer matrix, lookup table has initialisation cost * Implemented Interleaved Gradient Dithering * https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence/ --- src/main/java/rs117/hd/HdPlugin.java | 2 + src/main/java/rs117/hd/HdPluginConfig.java | 16 ++++- src/main/resources/rs117/hd/scene_frag.glsl | 21 +++--- src/main/resources/rs117/hd/scene_vert.glsl | 7 +- .../resources/rs117/hd/utils/constants.glsl | 1 + src/main/resources/rs117/hd/utils/misc.glsl | 66 +++++++++++++++---- 6 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index bf215902de..2fa0481fd2 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -905,6 +905,7 @@ public ShaderIncludes getShaderIncludes() { .define("TEXEL_SIZE", GLTextureBuffer.isRGBASupported() ? 4 : 3) .define("UI_SCALING_MODE", config.uiScalingMode()) .define("COLOR_BLINDNESS", config.colorBlindness()) + .define("DITHER_FADE", config.ditherFading()) .define("APPLY_COLOR_FILTER", configColorFilter != ColorFilter.NONE) .define("MATERIAL_COUNT", MaterialManager.MATERIALS.length) .define("WATER_TYPE_COUNT", waterTypeManager.uboWaterTypes.getCount()) @@ -1816,6 +1817,7 @@ public void processPendingConfigChanges() { case KEY_WIREFRAME: case KEY_SHADOW_FILTERING: case KEY_WINDOWS_HDR_CORRECTION: + case KEY_DITHER_FADING: recompilePrograms = true; break; case KEY_ANTI_ALIASING_MODE: diff --git a/src/main/java/rs117/hd/HdPluginConfig.java b/src/main/java/rs117/hd/HdPluginConfig.java index b24ab75201..f72e36e3b7 100644 --- a/src/main/java/rs117/hd/HdPluginConfig.java +++ b/src/main/java/rs117/hd/HdPluginConfig.java @@ -314,12 +314,22 @@ default boolean flashingEffects() return false; } + String KEY_DITHER_FADING = "ditherFading"; + @ConfigItem( + keyName = KEY_DITHER_FADING, + name = "Dither Fading", + description = "Whether to dither fade near plane geometry that would normally clip.", + position = 16, + section = generalSettings + ) + default boolean ditherFading() { return true; } + @ConfigItem( keyName = "fSaturation", name = "Saturation", description = "Controls the saturation of the final rendered image.
" + "Intended to be kept between 0% and 120%.", - position = 16, + position = 17, section = generalSettings ) @Units(Units.PERCENT) @@ -339,7 +349,7 @@ default Saturation oldSaturationDropdown() name = "Contrast", description = "Controls the contrast of the final rendered image.
" + "Intended to be kept between 90% and 110%.", - position = 17, + position = 18, section = generalSettings ) @Units(Units.PERCENT) @@ -366,7 +376,7 @@ default Contrast oldContrastDropdown() description = "Controls the brightness of the game, excluding UI.
" + "Adjust until the circle on the left is barely visible.", - position = 18, + position = 19, section = generalSettings ) default int brightness() { diff --git a/src/main/resources/rs117/hd/scene_frag.glsl b/src/main/resources/rs117/hd/scene_frag.glsl index 77fc4b55e3..c895e99c37 100644 --- a/src/main/resources/rs117/hd/scene_frag.glsl +++ b/src/main/resources/rs117/hd/scene_frag.glsl @@ -57,9 +57,6 @@ flat in ivec3 fTerrainData; #endif in FragmentData { -#if ZONE_RENDERER - vec4 positionCS; -#endif vec3 position; vec2 uv; vec3 normal; @@ -89,14 +86,16 @@ vec2 worldUvs(float scale) { void main() { vec3 downDir = vec3(0, -1, 0); - #if ZONE_RENDERER - float viewZ = 1.0 - (0.5 + (IN.positionCS.z / IN.positionCS.w) * 0.5); - if (viewZ < NEAR_PLANE_DITHER_START) { - float fadeAmount = 1.0 - saturate(viewZ / NEAR_PLANE_DITHER_START); - if (orderedDither(gl_FragCoord.xy, pow(fadeAmount, 1.5) - 0.01, 1.75)) - discard; - } - #endif +#if DITHER_FADE + float viewZ = 1.0 - gl_FragCoord.z; + if (viewZ < NEAR_PLANE_DITHER_START) { + float fadeAmount = 1.0 - saturate(viewZ / NEAR_PLANE_DITHER_START); + float threshold = smoothstep(0.0, 1.0, pow(fadeAmount, 1.35)); + float noise = interleavedGradientNoise(gl_FragCoord.xy); + if (noise < threshold) + discard; + } +#endif // View & light directions are from the fragment to the camera/light vec3 viewDir = normalize(cameraPos - IN.position); diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index 329f1347fb..db45dfd6ab 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -61,7 +61,6 @@ layout (location = 0) in vec3 vPosition; #endif out FragmentData { - vec4 positionCS; vec3 position; vec2 uv; vec3 normal; @@ -117,12 +116,12 @@ layout (location = 0) in vec3 vPosition; fFlatNormal = worldNormal; #endif + vec4 clipPosition = projectionMatrix * vec4(worldPosition, 1.0); int depthBias = (alphaBiasHsl >> 16) & 0xff; - OUT.positionCS = projectionMatrix * vec4(worldPosition, 1.0); if (projectionMatrix[2][3] != 0) // Disable depth bias for orthographic projection - OUT.positionCS.z += depthBias / 128.0; + clipPosition.z += depthBias / 128.0; - gl_Position = OUT.positionCS; + gl_Position = clipPosition; } #else out vec3 gPosition; diff --git a/src/main/resources/rs117/hd/utils/constants.glsl b/src/main/resources/rs117/hd/utils/constants.glsl index 1ef390944e..7d19663951 100644 --- a/src/main/resources/rs117/hd/utils/constants.glsl +++ b/src/main/resources/rs117/hd/utils/constants.glsl @@ -62,6 +62,7 @@ #include FLAT_SHADING #include APPLY_COLOR_FILTER #include WIREFRAME +#include DITHER_FADE #include WIND_DISPLACEMENT #include WIND_DISPLACEMENT_NOISE_RESOLUTION #include CHARACTER_DISPLACEMENT diff --git a/src/main/resources/rs117/hd/utils/misc.glsl b/src/main/resources/rs117/hd/utils/misc.glsl index a625100fce..14e822a422 100644 --- a/src/main/resources/rs117/hd/utils/misc.glsl +++ b/src/main/resources/rs117/hd/utils/misc.glsl @@ -112,20 +112,64 @@ void undoVanillaShading(inout int hsl, vec3 unrotatedNormal) { } #endif -const int BAYER_ORDER = 4; -const float BAYER_DIVISOR = 16.0; -const float BAYER_MATRIX[BAYER_ORDER * BAYER_ORDER] = float[]( - 0.0 / BAYER_DIVISOR, 8.0 / BAYER_DIVISOR, 2.0 / BAYER_DIVISOR, 10.0 / BAYER_DIVISOR, - 12.0 / BAYER_DIVISOR, 4.0 / BAYER_DIVISOR, 14.0 / BAYER_DIVISOR, 6.0 / BAYER_DIVISOR, - 3.0 / BAYER_DIVISOR, 11.0 / BAYER_DIVISOR, 1.0 / BAYER_DIVISOR, 9.0 / BAYER_DIVISOR, - 15.0 / BAYER_DIVISOR, 7.0 / BAYER_DIVISOR, 13.0 / BAYER_DIVISOR, 5.0 / BAYER_DIVISOR -); +// 2x2 Bayer via bit permutation +// Generates ordered dithering Bayer matrix without a lookup table +float bayer2x2(vec2 pixelCoord) { + uvec2 p = uvec2(pixelCoord) & 1u; + uint v = + ((p.x & 1u) << 1u) | + ((p.y & 1u) << 0u); + + return (float(v) + 0.5) * (1.0 / 4.0); +} + +// 4x4 Bayer via bit permutation +// Generates ordered dithering Bayer matrix without a lookup table +float bayer4x4(vec2 pixelCoord) { + uvec2 p = uvec2(pixelCoord) & 3u; + uint v = + ((p.x & 1u) << 3u) | + ((p.y & 1u) << 2u) | + ((p.x & 2u) << 0u) | + ((p.y & 2u) >> 1u); + + return (float(v) + 0.5) * (1.0 / 16.0); +} + +// 8x8 Bayer via bit permutation +// Generates ordered dithering Bayer matrix without a lookup table +float bayer8x8(vec2 pixelCoord) { + uvec2 p = uvec2(pixelCoord) & 7u; + uint v = + ((p.x & 1u) << 5u) | + ((p.y & 1u) << 4u) | + ((p.x & 2u) << 2u) | + ((p.y & 2u) << 1u) | + ((p.x & 4u) >> 1u) | + ((p.y & 4u) >> 2u); + + return (float(v) + 0.5) * (1.0 / 64.0); +} // Based on https://www.shadertoy.com/view/4t2cRt (merger doctrine) // Returns a dither value (0.0 or 1.0) based on coords & opacity -bool orderedDither(vec2 pixelCoord, float opacity, float scaleFactor) { - ivec2 coord = ivec2(pixelCoord / scaleFactor) & (BAYER_ORDER - 1); - return BAYER_MATRIX[coord.x + coord.y * BAYER_ORDER] < clamp(opacity, 0.0, 1.0); +bool orderedDither4x4(vec2 pixelCoord, float opacity, float scaleFactor) { + float threshold = bayer4x4(pixelCoord / scaleFactor); + return threshold < clamp(opacity, 0.0, 1.0); +} + +bool orderedDither2x2(vec2 pixelCoord, float opacity, float scaleFactor) { + float threshold = bayer2x2(pixelCoord / scaleFactor); + return threshold < clamp(opacity, 0.0, 1.0); +} + +bool orderedDither8x8(vec2 pixelCoord, float opacity, float scaleFactor) { + float threshold = bayer8x8(pixelCoord / scaleFactor); + return threshold < clamp(opacity, 0.0, 1.0); +} + +float interleavedGradientNoise(vec2 p) { + return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); } // 2D Random From d77c86d4e50056c4d5ddf8627839b383192fd7c5 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 14 May 2026 12:03:47 +0100 Subject: [PATCH 6/8] Fade Zones In Instead of zone upload jobs being delayed to avoid random reveal, now use dithering to reveal them instead --- .../hd/renderer/zone/DynamicModelVAO.java | 6 ++- .../renderer/zone/ModelStreamingManager.java | 12 +++--- .../rs117/hd/renderer/zone/SceneManager.java | 10 +++-- .../rs117/hd/renderer/zone/SceneUploader.java | 38 ++++++++++++++----- .../hd/renderer/zone/VertexWriteCache.java | 4 +- .../hd/renderer/zone/WorldViewContext.java | 31 +++++++-------- .../java/rs117/hd/renderer/zone/Zone.java | 29 ++++++++++---- .../rs117/hd/renderer/zone/ZoneUploadJob.java | 2 - src/main/resources/rs117/hd/scene_frag.glsl | 11 ++++-- src/main/resources/rs117/hd/scene_vert.glsl | 7 +++- .../rs117/hd/uniforms/model_data.glsl | 4 +- 11 files changed, 96 insertions(+), 58 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java b/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java index cbdf3d66e0..2da210c6bf 100644 --- a/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java +++ b/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java @@ -38,7 +38,8 @@ public class DynamicModelVAO implements Destructible { // Metadata format // worldViewIndex int // dummy sceneOffset ivec2 for macOS workaround - static final int METADATA_SIZE = 12; + // fade float + static final int METADATA_SIZE = 16; @Getter private int vao; @@ -242,7 +243,7 @@ public synchronized View beginDraw(int drawIdx, int faceCount) { view = new View(); view.vbo = vboWriter.reserve(faceCount * 3 * VERT_SIZE_INTS); view.tboF = tboFWriter.reserve(faceCount * 4); - view.tboM = tboMWriter.reserve(4); + view.tboM = tboMWriter.reserve(Zone.MODEL_DATA_SIZE / Integer.BYTES); view.vao = vao; view.tboFId = tboF.getTexId(); view.tboMId = tboM.getTexId(); @@ -265,6 +266,7 @@ private synchronized void endDraw(View view) { // Clear ReservedViews before returning to pool view.vbo = null; view.tboF = null; + view.tboM = null; usedViews.add(view); } diff --git a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java index 54533d0dec..5af6a12c48 100644 --- a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java +++ b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java @@ -328,7 +328,7 @@ private void uploadTempModel( (!sceneManager.isRoot(ctx) || zone.inShadowFrustum) ) { final DynamicModelVAO.View shadowView = ctx.beginDraw(VAO_SHADOW, culledFaces.length); - int shadowModelIdx = SceneUploader.writeModelData(shadowView.tboM.getBuffer(), x, y, z, m, modelOverride); + int shadowModelIdx = SceneUploader.writeDynamicModelData(shadowView.tboM, x, y, z, m, modelOverride, zone); sceneUploader.uploadTempModel( culledFaces, m, @@ -354,8 +354,8 @@ private void uploadTempModel( final DynamicModelVAO.View opaqueView = ctx.beginDraw(isPlayer ? VAO_PLAYER : VAO_OPAQUE, drawIndex, opaqueFaceCount); final DynamicModelVAO.View alphaView = alphaFaceCount > 0 ? ctx.beginDraw(VAO_ALPHA, alphaFaceCount) : opaqueView; - final int opaqueModelIdx = SceneUploader.writeModelData(opaqueView.tboM.getBuffer(), x, y, z, m, modelOverride); - final int alphaModelIdx = alphaFaceCount > 0 ? SceneUploader.writeModelData(alphaView.tboM.getBuffer(), x, y, z, m, modelOverride) : opaqueModelIdx; + final int opaqueModelIdx = SceneUploader.writeDynamicModelData(opaqueView.tboM, x, y, z, m, modelOverride, zone); + final int alphaModelIdx = alphaFaceCount > 0 ? SceneUploader.writeDynamicModelData(alphaView.tboM, x, y, z, m, modelOverride, zone) : opaqueModelIdx; sceneUploader.uploadTempModel( visibleFaces, @@ -598,7 +598,7 @@ private void uploadDynamicModel( (!sceneManager.isRoot(ctx) || zone.inShadowFrustum) ) { final DynamicModelVAO.View shadowView = ctx.beginDraw(VAO_SHADOW, culledFaces.length); - final int shadowModelIdx = SceneUploader.writeModelData(shadowView.tboM.getBuffer(), x, y, z, m, modelOverride); + final int shadowModelIdx = SceneUploader.writeDynamicModelData(shadowView.tboM, x, y, z, m, modelOverride, zone); sceneUploader.uploadTempModel( culledFaces, m, @@ -621,8 +621,8 @@ private void uploadDynamicModel( final DynamicModelVAO.View opaqueView = ctx.beginDraw(VAO_OPAQUE, drawIndex, opaqueFaceCount); final DynamicModelVAO.View alphaView = alphaFaceCount > 0 ? ctx.beginDraw(VAO_ALPHA, alphaFaceCount) : opaqueView; - final int opaqueModelIdx = SceneUploader.writeModelData(opaqueView.tboM.getBuffer(), x, y, z, m, modelOverride); - final int alphaModelIdx = alphaFaceCount > 0 ? SceneUploader.writeModelData(alphaView.tboM.getBuffer(), x, y, z, m, modelOverride) : opaqueModelIdx; + final int opaqueModelIdx = SceneUploader.writeDynamicModelData(opaqueView.tboM, x, y, z, m, modelOverride, zone); + final int alphaModelIdx = alphaFaceCount > 0 ? SceneUploader.writeDynamicModelData(alphaView.tboM, x, y, z, m, modelOverride, zone) : opaqueModelIdx; sceneUploader.uploadTempModel( visibleFaces, diff --git a/src/main/java/rs117/hd/renderer/zone/SceneManager.java b/src/main/java/rs117/hd/renderer/zone/SceneManager.java index f7258f3620..6fd9b784a7 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneManager.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneManager.java @@ -589,17 +589,19 @@ public synchronized void loadScene(WorldView worldView, Scene scene) { } } - long timeMs = System.currentTimeMillis(); + //long timeMs = System.currentTimeMillis(); for (SortedZone sorted : sortedZones) { Zone newZone = injector.getInstance(Zone.class); newZone.dirty = sorted.zone.dirty; if (staggerLoad) { + if(!sorted.zone.cull) + newZone.fadingAlpha = saturate(sorted.dist / 15.0f) * 3.0f; // Fade in new chunks that are appearing out of the fog + // Reuse the old zone while uploading a correct one sorted.zone.cull = false; sorted.zone.uploadJob = ZoneUploadJob - .build(ctx, nextSceneContext, newZone, false, sorted.x, sorted.z); - sorted.zone.uploadJob.revealAfterTimestampMs = - timeMs + ceil(clamp(sorted.dist / 15.0f, 0.25f, 1.5f) * 1000.0f); + .build(ctx, nextSceneContext, newZone, false, sorted.x, sorted.z) + .queue(ctx.streamingGroup, generateSceneDataTask); } else { nextZones[sorted.x][sorted.z] = newZone; ZoneUploadJob diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index 637b3b5616..68d0b9bb02 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -47,6 +47,7 @@ import rs117.hd.scene.water_types.WaterType; import rs117.hd.utils.HDUtils; import rs117.hd.utils.ModelHash; +import rs117.hd.utils.buffer.GLMappedBufferIntWriter; import rs117.hd.utils.buffer.GpuIntBuffer; import rs117.hd.utils.collections.ConcurrentPool; import rs117.hd.utils.collections.PrimitiveIntArray; @@ -197,8 +198,8 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws var vb = zone.vboO != null ? new GpuIntBuffer(zone.vboO.mapped()) : null; var ab = zone.vboA != null ? new GpuIntBuffer(zone.vboA.mapped()) : null; var fb = zone.tboF != null ? new GpuIntBuffer(zone.tboF.mapped()) : null; - var md = zone.tboF != null ? new GpuIntBuffer(zone.tboM.mapped()) : null; - assert fb != null && md != null; + var md = zone.tboM != null ? new GpuIntBuffer(zone.tboM.mapped()) : null; + assert fb != null; roofIds.clear(); for (int level = 0; level <= 3; ++level) { @@ -751,7 +752,7 @@ private void uploadZoneRenderable( int alphaStart = alphaBuffer != null ? alphaBuffer.position() : 0; try { uploadStaticModel( - ctx, tile, model, modelOverride, uuid, + ctx, tile, model, modelOverride, zone, uuid, preOrientation, orient, x - basex, y, z - basez, opaqueBuffer, @@ -1357,16 +1358,32 @@ private void uploadTileModel( } } - public static final int MODEL_DATA_SIZE = 4; - - public static int writeModelData(IntBuffer modelBuffer, int x, int y, int z, Model model, ModelOverride override) { - int modelIdx = modelBuffer.position() / MODEL_DATA_SIZE; + private static void writeModelData(IntBuffer modelBuffer, int x, int y, int z, Model model, ModelOverride override, Zone zone, boolean isStatic) { modelBuffer .put(x) .put(y) .put(z) - .put(model.getModelHeight()); - assert modelBuffer.position() % MODEL_DATA_SIZE == 0; + .put(model.getModelHeight()) + .put(Float.floatToIntBits(isStatic ? -1.0f : saturate(zone.fadingAlpha))); + } + + public static int writeStaticModelData(IntBuffer modelBuffer, int x, int y, int z, Model model, ModelOverride override, Zone zone) { + final int modelDataSizeInts = Zone.MODEL_DATA_SIZE / Integer.BYTES; + final int modelIdx = modelBuffer.position() / modelDataSizeInts; + + writeModelData(modelBuffer, x, y, z, model, override, zone, true); + + assert modelBuffer.position() % modelDataSizeInts == 0; + return modelIdx + 1; + } + + public static int writeDynamicModelData(GLMappedBufferIntWriter.ReservedView view, int x, int y, int z, Model model, ModelOverride override, Zone zone) { + final int modelDataSizeInts = Zone.MODEL_DATA_SIZE / Integer.BYTES; + final int modelIdx = view.getBufferOffsetInts() / modelDataSizeInts; + + writeModelData(view.getBuffer(), x, y, z, model, override, zone, false); + + assert view.getBufferOffsetInts() % modelDataSizeInts == 0; return modelIdx + 1; } @@ -1376,6 +1393,7 @@ private int uploadStaticModel( Tile tile, Model model, ModelOverride modelOverride, + Zone zone, int uuid, int preOrientation, int orientation, int x, int y, int z, @@ -1472,7 +1490,7 @@ private int uploadStaticModel( final Material baseMaterial = modelOverride.baseMaterial; final Material textureMaterial = modelOverride.textureMaterial; - final int modelIdx = writeModelData(modelBuffer.getBuffer(), x, y, z, model, modelOverride); + final int modelIdx = writeStaticModelData(modelBuffer.getBuffer(), x, y, z, model, modelOverride, zone); int len = 0; for (int face = 0; face < faceCount; ++face) { diff --git a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java index 6997310538..91c674e7ba 100644 --- a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java +++ b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java @@ -97,11 +97,13 @@ public void putVertex( final int[] stagingBuffer = this.stagingBuffer; final int stagingPosition = this.stagingPosition; + assert modelIdx < 0xFFFF; + stagingBuffer[stagingPosition] = x; stagingBuffer[stagingPosition + 1] = y; stagingBuffer[stagingPosition + 2] = z; stagingBuffer[stagingPosition + 3] = float16(v) << 16 | float16(u); - stagingBuffer[stagingPosition + 4] = modelIdx << 16 | float16(w); + stagingBuffer[stagingPosition + 4] = float16(w); stagingBuffer[stagingPosition + 5] = (ny & 0xFFFF) << 16 | nx & 0xFFFF; stagingBuffer[stagingPosition + 6] = (modelIdx & 0xFFFF) << 16 | nz & 0xFFFF; stagingBuffer[stagingPosition + 7] = textureFaceIdx; diff --git a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java index 429c67f3d1..79a79dba86 100644 --- a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java +++ b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java @@ -27,6 +27,7 @@ import static rs117.hd.renderer.zone.DynamicModelVAO.METADATA_SIZE; import static rs117.hd.renderer.zone.SceneManager.NUM_ZONES; import static rs117.hd.renderer.zone.ZoneRenderer.FRAMES_IN_FLIGHT; +import static rs117.hd.utils.MathUtils.*; @Slf4j public class WorldViewContext { @@ -193,15 +194,6 @@ void handleZoneSwap(int zx, int zz, boolean queue) { if (uploadTask == null) return; - if (!uploadTask.isQueued()) { - if (queue && uploadTask.revealAfterTimestampMs < System.currentTimeMillis()) { - log.trace("queueing zone({}): [{}-{},{}]", uploadTask.zone.hashCode(), worldViewId, zx, zz); - uploadTask.revealAfterTimestampMs = 0; - uploadTask.queue(streamingGroup, sceneManager.getGenerateSceneDataTask()); - } - return; - } - if (uploadTask.isDone()) { curZone.uploadJob = null; if (uploadTask.ranToCompletion() && !uploadTask.wasCancelled()) { @@ -241,9 +233,18 @@ void handleZoneSwap(int zx, int zz, boolean queue) { } void processZoneSwaps() { - for (int x = 0; x < sizeX; x++) - for (int z = 0; z < sizeZ; z++) + for (int x = 0; x < sizeX; x++) { + for (int z = 0; z < sizeZ; z++) { handleZoneSwap(x, z, true); + + final Zone zone = zones[x][z]; + if(zone.fadingAlpha <= 0) + continue; + + zone.fadingAlpha = max(0.0f, zone.fadingAlpha - plugin.deltaTime); + zone.setMetadata(this, sceneContext, x, z); + } + } } void processZoneRebuilds() { @@ -311,7 +312,6 @@ void invalidate() { void invalidateZone(int zx, int zz) { Zone curZone = zones[zx][zz]; - long revealAfterTimestampMs = 0; if (curZone.uploadJob != null) { Zone pendingZone = curZone.uploadJob.zone; log.trace( @@ -322,7 +322,6 @@ void invalidateZone(int zx, int zz) { zz, pendingZone.hashCode() ); - revealAfterTimestampMs = curZone.uploadJob.revealAfterTimestampMs; curZone.uploadJob.cancel(); curZone.uploadJob.release(); @@ -334,10 +333,6 @@ void invalidateZone(int zx, int zz) { newZone.dirty = zones[zx][zz].dirty; curZone.uploadJob = ZoneUploadJob.build(this, sceneContext, newZone, false, zx, zz); - curZone.uploadJob.revealAfterTimestampMs = revealAfterTimestampMs; - - // Queue right away, so we can wait for it while in the POH in order to hide building mode placeholders - if (sceneContext.isInHouse || revealAfterTimestampMs <= 0) - curZone.uploadJob.queue(invalidationGroup, sceneManager.getGenerateSceneDataTask()); + curZone.uploadJob.queue(invalidationGroup, sceneManager.getGenerateSceneDataTask()); } } diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index be926450cd..544d56ff79 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -56,12 +56,14 @@ public class Zone implements Destructible { // position vec3 // height float - public static final int MODEL_DATA_SIZE = 16; + // fade float + public static final int MODEL_DATA_SIZE = 20; // Metadata format // worldViewIndex int int // sceneOffset int vec2(x, y) - public static final int METADATA_SIZE = 12; + // fade float float + public static final int METADATA_SIZE = 16; public static final int LEVEL_WATER_SURFACE = 4; @@ -92,6 +94,7 @@ public class Zone implements Destructible { final StaticAlphaSortingJob alphaSortingJob = new StaticAlphaSortingJob(); ZoneUploadJob uploadJob; + float fadingAlpha; int[] levelOffsets = new int[5]; // buffer pos in ints for the end of the level @@ -273,6 +276,11 @@ private void setupVao(int vao, int buffer, int metadata) { glVertexAttribDivisor(7, 1); glVertexAttribIPointer(7, 2, GL_INT, METADATA_SIZE, 4); + // Scene offset + glEnableVertexAttribArray(8); + glVertexAttribDivisor(8, 1); + glVertexAttribPointer(8, 1, GL_FLOAT, false, METADATA_SIZE, 12); + checkGLErrors(); glBindVertexArray(0); @@ -283,14 +291,16 @@ public void setMetadata(WorldViewContext viewContext, SceneContext sceneContext, if (vboM == null) return; + float fade = saturate(fadingAlpha); int baseX = (mx - (sceneContext.sceneOffset >> 3)) << 10; int baseZ = (mz - (sceneContext.sceneOffset >> 3)) << 10; try (MemoryStack stack = MemoryStack.stackPush()) { - IntBuffer buf = stack.mallocInt(3) + IntBuffer buf = stack.mallocInt(METADATA_SIZE / Integer.BYTES) .put(viewContext.uboWorldViewStruct != null ? viewContext.uboWorldViewStruct.worldViewIdx + 1 : 0) .put(baseX) - .put(baseZ); + .put(baseZ) + .put(Float.floatToIntBits(fade)); buf.flip(); vboM.upload(buf); } @@ -332,6 +342,8 @@ private void convertForDraw(int vertSize) { } void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) { + if(fadingAlpha > 1.0) + return; drawIdx = 0; int currentLevel = ctx.level; @@ -385,7 +397,7 @@ void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) lastDrawMode = STATIC_UNSORTED; lastVao = glVao; lastTboF = tboF.getTexId(); - lastTboM = tboM.getTexId(); + lastTboM = tboM != null ? tboM.getTexId() : 0; flush(cmd); } @@ -400,7 +412,7 @@ void renderOpaqueLevel(CommandBuffer cmd, int level) { lastDrawMode = STATIC_UNSORTED; lastVao = glVao; lastTboF = tboF.getTexId(); - lastTboM = tboM.getTexId(); + lastTboM = tboM != null ? tboM.getTexId() : 0; flush(cmd); } @@ -643,7 +655,7 @@ synchronized AlphaModel requestTempAlphaModel(ModelOverride modelOverride, int l m.y = (short) y; m.z = (short) z; m.level = (byte) level; - m.vao = m.tboF = m.rid = m.lx = m.lz = m.ux = m.uz = -1; + m.vao = m.tboF = m.tboM = m.rid = m.lx = m.lz = m.ux = m.uz = -1; m.flags = 0; m.zofx = m.zofz = 0; alphaModels.add(m); @@ -727,7 +739,7 @@ void renderAlpha( boolean isShadowPass, boolean includeRoof ) { - if (alphaModels.isEmpty()) + if (alphaModels.isEmpty() || fadingAlpha > 1.0) return; int minLevel = ctx.minLevel; @@ -904,6 +916,7 @@ synchronized void multizoneLocs(SceneContext ctx, int zx, int zz, Camera camera, m2.z = m.z; m2.vao = m.vao; m2.tboF = m.tboF; + m2.tboM = m.tboM; m2.rid = m.rid; m2.level = m.level; m2.lx = m.lx; diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java b/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java index 1620e85452..b7db764ef5 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java @@ -20,7 +20,6 @@ public final class ZoneUploadJob extends Job { Zone zone; int x, z; - long revealAfterTimestampMs; boolean shouldUnmap; @Override @@ -116,7 +115,6 @@ protected void onReleased() { sceneContext = null; zone.uploadJob = null; zone = null; - revealAfterTimestampMs = 0; assert !POOL.contains(this) : "Task is already in pool"; POOL.add(this); } diff --git a/src/main/resources/rs117/hd/scene_frag.glsl b/src/main/resources/rs117/hd/scene_frag.glsl index c895e99c37..eb3b1248f3 100644 --- a/src/main/resources/rs117/hd/scene_frag.glsl +++ b/src/main/resources/rs117/hd/scene_frag.glsl @@ -52,8 +52,11 @@ flat in ivec3 fAlphaBiasHsl; flat in ivec3 fMaterialData; flat in ivec3 fTerrainData; -#if FLAT_SHADING && ZONE_RENDERER - flat in vec3 fFlatNormal; +#if ZONE_RENDERER + #if FLAT_SHADING + flat in vec3 fFlatNormal; + #endif + flat in float fFade; #endif in FragmentData { @@ -88,8 +91,8 @@ void main() { vec3 downDir = vec3(0, -1, 0); #if DITHER_FADE float viewZ = 1.0 - gl_FragCoord.z; - if (viewZ < NEAR_PLANE_DITHER_START) { - float fadeAmount = 1.0 - saturate(viewZ / NEAR_PLANE_DITHER_START); + if (fFade > 0.0 || viewZ < NEAR_PLANE_DITHER_START) { + float fadeAmount = mix(1.0 - saturate(viewZ / NEAR_PLANE_DITHER_START), fFade, saturate(fFade)); float threshold = smoothstep(0.0, 1.0, pow(fadeAmount, 1.35)); float noise = interleavedGradientNoise(gl_FragCoord.xy); if (noise < threshold) diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index db45dfd6ab..0f9b638ba0 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -42,6 +42,7 @@ layout (location = 0) in vec3 vPosition; layout (location = 3) in int vPackedTextureFace; layout (location = 6) in int vWorldViewId; layout (location = 7) in ivec2 vSceneBase; + layout (location = 8) in float vFade; #else layout (location = 1) in vec3 vUv; layout (location = 2) in vec3 vNormal; @@ -52,6 +53,7 @@ layout (location = 0) in vec3 vPosition; #if ZONE_RENDERER flat out int fWorldViewId; + flat out float fFade; flat out ivec3 fAlphaBiasHsl; flat out ivec3 fMaterialData; flat out ivec3 fTerrainData; @@ -88,6 +90,7 @@ layout (location = 0) in vec3 vPosition; fTerrainData = faceData.TerrainData; alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; materialData = faceData.MaterialData[vertex]; + fFade = vFade; } vec3 sceneOffset = vec3(vSceneBase.x, 0, vSceneBase.y); @@ -96,8 +99,8 @@ layout (location = 0) in vec3 vPosition; int modelIdx = int(vNormal.w); if(modelIdx > 0) { - //ModelData modelData = getModelData(modelIdx); - + ModelData modelData = getModelData(modelIdx); + fFade = max(modelData.fade, fFade); } if (vWorldViewId != -1) { diff --git a/src/main/resources/rs117/hd/uniforms/model_data.glsl b/src/main/resources/rs117/hd/uniforms/model_data.glsl index a5840f628d..ed5aaa6d0d 100644 --- a/src/main/resources/rs117/hd/uniforms/model_data.glsl +++ b/src/main/resources/rs117/hd/uniforms/model_data.glsl @@ -2,7 +2,7 @@ #include -#define MODEL_DATA_SIZE 4 +#define MODEL_DATA_SIZE 5 #define PARSER_TARGET_BUFFER modelData uniform isamplerBuffer modelData; @@ -10,12 +10,14 @@ uniform isamplerBuffer modelData; struct ModelData { ivec3 position; int height; + float fade; // Used by Dynamic Models }; BEGIN_BUFFER_PARSER(readModelData, ModelData) READ_IVEC3(position) READ_INT(height) + READ_FLOAT(fade) END_BUFFER_PARSER() ModelData getModelData(int modelIdx) { From 9bef119bc14c7d360486d4e3a16da3c7d43813c4 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 14 May 2026 12:14:32 +0100 Subject: [PATCH 7/8] Skip Drawing when fully faded out --- .../java/rs117/hd/renderer/zone/ModelStreamingManager.java | 5 ++++- src/main/java/rs117/hd/renderer/zone/Zone.java | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java index 5af6a12c48..b4099985d2 100644 --- a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java +++ b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java @@ -174,6 +174,9 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj int zz = (gameObject.getY() >> 10) + offset; Zone zone = ctx.zones[zx][zz]; + if(!zone.initialized || zone.fadingAlpha > 1.0f) + return; + m.calculateBoundsCylinder(); final float[] objectWorldPos = vec4(streamingContext.objectWorldPos, x, y, z, 1.0f); @@ -409,7 +412,7 @@ public void drawDynamic( int zz = (z >> 10) + offset; Zone zone = ctx.zones[zx][zz]; - if (!zone.initialized) + if (!zone.initialized || zone.fadingAlpha > 1.0f) return; final StreamingContext streamingContext = context(renderThreadId); diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 544d56ff79..f3440ac91a 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -402,6 +402,9 @@ void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) } void renderOpaqueLevel(CommandBuffer cmd, int level) { + if(fadingAlpha > 1.0) + return; + drawIdx = 0; pushRange(this.levelOffsets[level - 1], this.levelOffsets[level]); From 46186597e20e5de6387b524569df049039ff80da Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sat, 16 May 2026 20:29:22 +0100 Subject: [PATCH 8/8] Dither Fade needs to be guarded behind the ZoneRenderer --- src/main/resources/rs117/hd/scene_frag.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/rs117/hd/scene_frag.glsl b/src/main/resources/rs117/hd/scene_frag.glsl index eb3b1248f3..2f5de40194 100644 --- a/src/main/resources/rs117/hd/scene_frag.glsl +++ b/src/main/resources/rs117/hd/scene_frag.glsl @@ -89,7 +89,7 @@ vec2 worldUvs(float scale) { void main() { vec3 downDir = vec3(0, -1, 0); -#if DITHER_FADE +#if DITHER_FADE && ZONE_RENDERER float viewZ = 1.0 - gl_FragCoord.z; if (fFade > 0.0 || viewZ < NEAR_PLANE_DITHER_START) { float fadeAmount = mix(1.0 - saturate(viewZ / NEAR_PLANE_DITHER_START), fFade, saturate(fFade));