diff --git a/build.gradle b/build.gradle index 7ab7c4f2ac..518b8bd277 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,6 @@ plugins { } repositories { - mavenLocal() maven { url = 'https://repo.runelite.net' content { diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index 6cdf8b6741..7bd830ae82 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -433,6 +433,7 @@ public class HdPlugin extends Plugin { public boolean enableFreezeFrame; public boolean orthographicProjection; public boolean freezeCulling; + public boolean showCulling; @Getter private boolean isPluginStopPending; diff --git a/src/main/java/rs117/hd/opengl/GLPrimitives.java b/src/main/java/rs117/hd/opengl/GLPrimitives.java new file mode 100644 index 0000000000..b25c739237 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/GLPrimitives.java @@ -0,0 +1,117 @@ +package rs117.hd.opengl; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import lombok.Value; +import org.lwjgl.system.MemoryStack; +import rs117.hd.utils.buffer.GLBuffer; + +import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER; +import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW; + +public class GLPrimitives { + + @Value + public static class Mesh { + GLBuffer vbo; + GLBuffer ebo; + int indexCount; + + public void destroy() { + vbo.destroy(); + ebo.destroy(); + } + } + + public static Mesh buildCube(MemoryStack stack) { + FloatBuffer vertices = stack.mallocFloat(24).put(new float[]{ + -1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1, + -1,-1, 1, 1,-1, 1, 1, 1, 1, -1, 1, 1 + }).flip(); + + IntBuffer indices = stack.mallocInt(36).put(new int[]{ + 0, 1, 2, 0, 2, 3, + 4, 6, 5, 4, 7, 6, + 0, 3, 7, 0, 7, 4, + 1, 5, 6, 1, 6, 2, + 0, 4, 5, 0, 5, 1, + 3, 2, 6, 3, 6, 7 + }).flip(); + + return new Mesh( + new GLBuffer("VBO::Cube", GL_ARRAY_BUFFER, GL_STATIC_DRAW).initialize(vertices), + new GLBuffer.EBO("EBO::Cube", GL_STATIC_DRAW).initialize(indices), + 36 + ); + } + + public static Mesh buildSphere(MemoryStack stack, int stacks, int slices) { + FloatBuffer vertices = stack.mallocFloat((stacks + 1) * (slices + 1) * 3); + for (int s = 0; s <= stacks; s++) { + float phi = (float) (Math.PI * s / stacks); + for (int sl = 0; sl <= slices; sl++) { + float theta = (float) (2 * Math.PI * sl / slices); + vertices.put((float) (Math.sin(phi) * Math.cos(theta))); + vertices.put((float) Math.cos(phi)); + vertices.put((float) (Math.sin(phi) * Math.sin(theta))); + } + } + vertices.flip(); + + int indexCount = stacks * slices * 6; + IntBuffer indices = stack.mallocInt(indexCount); + for (int s = 0; s < stacks; s++) { + for (int sl = 0; sl < slices; sl++) { + int cur = s * (slices + 1) + sl; + int next = cur + (slices + 1); + indices.put(cur ).put(next ).put(cur + 1); + indices.put(cur + 1).put(next ).put(next + 1); + } + } + indices.flip(); + + return new Mesh( + new GLBuffer("VBO::Sphere", GL_ARRAY_BUFFER, GL_STATIC_DRAW).initialize(vertices), + new GLBuffer.EBO("EBO::Sphere", GL_STATIC_DRAW).initialize(indices), + indexCount + ); + } + + public static Mesh buildLine(MemoryStack stack) { + FloatBuffer vertices = stack.mallocFloat(24).put(new float[]{ + -1,-1, 0, 1,-1, 0, 1, 1, 0, -1, 1, 0, + 0,-1,-1, 0,-1, 1, 0, 1, 1, 0, 1,-1 + }).flip(); + + IntBuffer indices = stack.mallocInt(12).put(new int[]{ + 0, 1, 2, 0, 2, 3, + 4, 5, 6, 4, 6, 7 + }).flip(); + + return new Mesh( + new GLBuffer("VBO::Line", GL_ARRAY_BUFFER, GL_STATIC_DRAW).initialize(vertices), + new GLBuffer.EBO("EBO::Line", GL_STATIC_DRAW).initialize(indices), + 12 + ); + } + + public static Mesh buildQuad(MemoryStack stack) { + FloatBuffer vertices = stack.mallocFloat(12).put(new float[]{ + 0, 0, 0, + 1, 0, 0, + 1, 1, 0, + 0, 1, 0 + }).flip(); + + IntBuffer indices = stack.mallocInt(6).put(new int[]{ + 0, 1, 2, + 0, 2, 3 + }).flip(); + + return new Mesh( + new GLBuffer("VBO::Quad", GL_ARRAY_BUFFER, GL_STATIC_DRAW).initialize(vertices), + new GLBuffer.EBO("EBO::Quad", GL_STATIC_DRAW).initialize(indices), + 6 + ); + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/opengl/shader/DebugDrawShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/DebugDrawShaderProgram.java new file mode 100644 index 0000000000..761f3ce581 --- /dev/null +++ b/src/main/java/rs117/hd/opengl/shader/DebugDrawShaderProgram.java @@ -0,0 +1,49 @@ +package rs117.hd.opengl.shader; + +import java.io.IOException; +import rs117.hd.renderer.zone.passes.DebugDrawPass.PrimitiveDrawType; + +import static org.lwjgl.opengl.GL20C.GL_FRAGMENT_SHADER; +import static org.lwjgl.opengl.GL20C.GL_VERTEX_SHADER; + +public abstract class DebugDrawShaderProgram extends ShaderProgram { + private final PrimitiveDrawType type; + + public DebugDrawShaderProgram(PrimitiveDrawType type) { + super(t -> t + .add(GL_VERTEX_SHADER, "debug_draw_vert.glsl") + .add(GL_FRAGMENT_SHADER, "debug_draw_frag.glsl")); + this.type = type; + } + + @Override + public void compile(ShaderIncludes includes) throws ShaderException, IOException { + super.compile(includes.copy().define("PRIMITIVE_TYPE", type.ordinal())); + } + + public static class DebugDrawCubeShaderProgram extends DebugDrawShaderProgram { + public DebugDrawCubeShaderProgram() { + super(PrimitiveDrawType.AABB); + } + } + + public static class DebugDrawSphereShaderProgram extends DebugDrawShaderProgram { + public DebugDrawSphereShaderProgram() { + super(PrimitiveDrawType.SPHERE); + } + } + + public static class DebugDrawLineShaderProgram extends DebugDrawShaderProgram { + public DebugDrawLineShaderProgram() { + super(PrimitiveDrawType.LINE); + } + } + + public static class DebugDrawTextShaderProgram extends DebugDrawShaderProgram { + public final Uniform1f uniCharScale = addUniform1f("charScale"); + + public DebugDrawTextShaderProgram() { + super(PrimitiveDrawType.TEXT); + } + } +} diff --git a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java index 0d1ca3104f..7bd14987ca 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UniformBuffer.java @@ -315,6 +315,16 @@ private void markWaterLine(int position, int size) { dirtyHighTide = max(dirtyHighTide, position + size); } + protected void setSize(int size) { + assert properties.isEmpty() : "Uniform buffer size can only be set, if your not using addStruct() or addProperty()!"; + this.size = size; + } + + public void write(int position, int x) { + dataInt.put(x); + markWaterLine(position, 4); + } + public void initialize() { if (data != null) destroy(); diff --git a/src/main/java/rs117/hd/overlays/TileInfoOverlay.java b/src/main/java/rs117/hd/overlays/TileInfoOverlay.java index 64a2eeb51d..5436a4502f 100644 --- a/src/main/java/rs117/hd/overlays/TileInfoOverlay.java +++ b/src/main/java/rs117/hd/overlays/TileInfoOverlay.java @@ -948,7 +948,7 @@ private String getModelInfo(Renderable r) { case MODE_TILE_INFO: return isStatic ? " static" : isDynamic ? " dynamic" : - " maybe dynamic"; + " maybe dynamic"; case MODE_MODEL_INFO: int[] faceColors = model.getFaceColors1(); byte[] faceTransparencies = model.getFaceTransparencies(); @@ -1046,9 +1046,9 @@ private static int getHeight(SceneContext ctx, int localX, int localY, int plane int x = localX & (LOCAL_TILE_SIZE - 1); int y = localY & (LOCAL_TILE_SIZE - 1); int var8 = x * tileHeights[plane][sceneExX + 1][sceneExY] + - (LOCAL_TILE_SIZE - x) * tileHeights[plane][sceneExX][sceneExY] >> LOCAL_COORD_BITS; + (LOCAL_TILE_SIZE - x) * tileHeights[plane][sceneExX][sceneExY] >> LOCAL_COORD_BITS; int var9 = x * tileHeights[plane][sceneExX + 1][sceneExY + 1] + - (LOCAL_TILE_SIZE - x) * tileHeights[plane][sceneExX][sceneExY + 1] >> LOCAL_COORD_BITS; + (LOCAL_TILE_SIZE - x) * tileHeights[plane][sceneExX][sceneExY + 1] >> LOCAL_COORD_BITS; return y * var9 + (LOCAL_TILE_SIZE - y) * var8 >> 7; } diff --git a/src/main/java/rs117/hd/overlays/Timer.java b/src/main/java/rs117/hd/overlays/Timer.java index 5f998a8651..fd4ace97c6 100644 --- a/src/main/java/rs117/hd/overlays/Timer.java +++ b/src/main/java/rs117/hd/overlays/Timer.java @@ -26,6 +26,12 @@ public enum Timer { DRAW_TILED_LIGHTING, DRAW_SUBMIT, + // RENDER_PASSES + TILED_LIGHTING_PASS, + DIRECTIONAL_PASS, + SCENE_PASS, + DEBUG_DRAW_PASS, + // Miscellaneous SWAP_BUFFERS, EXECUTE_COMMAND_BUFFER, @@ -71,6 +77,7 @@ public enum Timer { RENDER_SHADOWS(GPU_TIMER), RENDER_SCENE(GPU_TIMER), RENDER_UI(GPU_TIMER, "Render UI"), + RENDER_DEBUG_DRAW(GPU_TIMER), ; public static final Timer[] TIMERS = values(); diff --git a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java index c85fd35e46..da11d51763 100644 --- a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java +++ b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java @@ -18,7 +18,10 @@ import rs117.hd.config.ShadowMode; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; +import rs117.hd.renderer.zone.passes.RenderPass; +import rs117.hd.renderer.zone.passes.RenderPipeline; import rs117.hd.scene.ModelOverrideManager; +import rs117.hd.scene.SceneCullingManager; import rs117.hd.scene.model_overrides.ModelOverride; import rs117.hd.utils.HDUtils; import rs117.hd.utils.ModelHash; @@ -71,6 +74,9 @@ public class ModelStreamingManager { @Inject private ZoneRenderer renderer; + @Inject + private RenderPipeline renderPipeline; + private final ArrayList pending = new ArrayList<>(); private final StreamingContext[] streamingContexts = new StreamingContext[RL_RENDER_THREADS + 1]; private int numRenderThreads = -1; @@ -213,23 +219,18 @@ public void drawTemp( final int modelClassification = renderer.sceneCamera.classifySphere( objectWorldPos[0], objectWorldPos[1], objectWorldPos[2], m.getRadius()); - boolean isOffScreen = modelClassification == -1; + boolean isOnScreen = modelClassification != -1; // Additional Culling checks to help reduce dynamic object perf impact when off-screen - if (isOffScreen && ( - !modelOverride.castShadows || - !renderer.directionalShadowCasterVolume.intersectsPoint( - (int) objectWorldPos[0], - (int) objectWorldPos[1], - (int) objectWorldPos[2] - ) - )) { - return; + if (!isOnScreen) { + isOnScreen = renderPipeline.dynamicInFrustum.execute(ctx, r, m, modelOverride, x, y, z); + if(!isOnScreen) + return; } streamingContext.renderableCount++; final boolean hasAlpha = (m.getFaceTransparencies() != null || modelOverride.mightHaveTransparency) && - (!sceneManager.isRoot(ctx) || zone.inSceneFrustum); + (!sceneManager.isRoot(ctx) || zone.isVisible(renderer.sceneCamera)); final Zone.AlphaModel alphaModel = hasAlpha ? zone.requestTempAlphaModel( modelOverride, @@ -372,8 +373,7 @@ public void uploadTempModel( if (culledFaces.length > 0 && modelOverride.castShadows && - plugin.configShadowMode != ShadowMode.OFF && - (!sceneManager.isRoot(ctx) || zone != null && zone.inShadowFrustum) + plugin.configShadowMode != ShadowMode.OFF ) { final DynamicModelVAO.View shadowView = ctx.beginDraw(VAO_SHADOW, culledFaces.length); sceneUploader.uploadTempModel( diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index 462b16c5b4..17f34bdbac 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -35,6 +35,7 @@ import rs117.hd.scene.MaterialManager; import rs117.hd.scene.ModelOverrideManager; import rs117.hd.scene.ProceduralGenerator; +import rs117.hd.scene.SceneCullingManager; import rs117.hd.scene.areas.Area; import rs117.hd.scene.ground_materials.GroundMaterial; import rs117.hd.scene.materials.Material; @@ -111,6 +112,9 @@ public class SceneUploader implements AutoCloseable { @Inject private ProceduralGenerator proceduralGenerator; + @Inject + private SceneCullingManager sceneCullingManager; + @FunctionalInterface public interface OnBeforeProcessTileFunc { void invoke(Tile t, boolean isEstimate) throws InterruptedException; @@ -134,6 +138,8 @@ public interface OnBeforeProcessTileFunc { private final float[] modelUvs = new float[12]; private final int[] modelNormals = new int[9]; private final short[][] tileNormals = new short[4][3]; + private final int[][] levelMinAABB = new int[Zone.LEVEL_COUNT][3]; + private final int[][] levelMaxAABB = new int[Zone.LEVEL_COUNT][3]; private int[] modelVertices; public int tempModelAlphaFaces = 0; @@ -224,6 +230,11 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws } } + for(int i = 0; i < Zone.LEVEL_COUNT; i++) { + Arrays.fill(levelMinAABB[i], Integer.MAX_VALUE); + Arrays.fill(levelMaxAABB[i], Integer.MIN_VALUE); + } + zone.rids = new int[4][roofIds.length]; zone.roofStart = new int[4][roofIds.length]; zone.roofEnd = new int[4][roofIds.length]; @@ -255,6 +266,13 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws uploadZoneGapFillers(ctx, zone, mzx, mzz, vb, fb); zone.levelOffsets[Zone.LEVEL_GAP_FILLER] = vb.position(); } + + for(int i = 0; i < Zone.LEVEL_COUNT; i++) { + final int[] minAABB = levelMinAABB[i]; + final int[] maxAABB = levelMaxAABB[i]; + if(minAABB[0] < maxAABB[0] && minAABB[1] < maxAABB[1] && minAABB[2] < maxAABB[2]) + zone.levelCullingResults[i] = sceneCullingManager.obtainBox(minAABB[0], minAABB[1], minAABB[2], maxAABB[0], maxAABB[1], maxAABB[2]); + } } private void uploadZoneLevel( @@ -354,6 +372,7 @@ private void uploadZoneWater( this.basez = (mzz - (ctx.sceneOffset >> 3)) << 10; for (int level = 0; level < MAX_Z; level++) { + this.level = level; for (int xoff = 0; xoff < CHUNK_SIZE; ++xoff) { for (int zoff = 0; zoff < CHUNK_SIZE; ++zoff) { final int msx = (mzx << 3) + xoff; @@ -806,6 +825,7 @@ private void uploadZoneRenderable( try { zone.addAlphaModel( plugin, + sceneCullingManager, materialManager, zone.glVaoA, zone.tboF.getTexId(), @@ -833,6 +853,18 @@ private void uploadZoneRenderable( } } + private void encapsulatePoint(int x, int y, int z) { + final int[] minAABB = levelMinAABB[level]; + final int[] maxAABB = levelMaxAABB[level]; + minAABB[0] = min(minAABB[0], x); + minAABB[1] = min(minAABB[1], y); + minAABB[2] = min(minAABB[2], z); + + maxAABB[0] = max(maxAABB[0], x); + maxAABB[1] = max(maxAABB[1], y); + maxAABB[2] = max(maxAABB[2], z); + } + @SuppressWarnings({ "UnnecessaryLocalVariable" }) private void uploadTilePaint( ZoneSceneContext ctx, @@ -1043,12 +1075,14 @@ private void uploadTilePaint( final var vb = writeCache.getVertexBuffer(); final var tb = writeCache.getTextureBuffer(); + int texturedFaceIdx = tb.putFace( neColor, nwColor, seColor, neMaterialData, nwMaterialData, seMaterialData, neTerrainData, nwTerrainData, seTerrainData ); + encapsulatePoint(lx2, neHeight, lz2); vb.putStaticVertex( lx2, neHeight, lz2, uvx, uvy, 0, @@ -1056,6 +1090,7 @@ private void uploadTilePaint( texturedFaceIdx ); + encapsulatePoint(lx3, nwHeight, lz3); vb.putStaticVertex( lx3, nwHeight, lz3, uvx - uvcos, uvy - uvsin, 0, @@ -1063,6 +1098,7 @@ private void uploadTilePaint( texturedFaceIdx ); + encapsulatePoint(lx1, seHeight, lz1); vb.putStaticVertex( lx1, seHeight, lz1, uvx + uvsin, uvy - uvcos, 0, @@ -1076,6 +1112,7 @@ private void uploadTilePaint( swTerrainData, seTerrainData, nwTerrainData ); + encapsulatePoint(lx0, swHeight, lz0); vb.putStaticVertex( lx0, swHeight, lz0, uvx - uvcos + uvsin, uvy - uvsin - uvcos, 0, @@ -1083,6 +1120,7 @@ private void uploadTilePaint( texturedFaceIdx ); + encapsulatePoint(lx1, seHeight, lz1); vb.putStaticVertex( lx1, seHeight, lz1, uvx + uvsin, uvy - uvcos, 0, @@ -1090,6 +1128,7 @@ private void uploadTilePaint( texturedFaceIdx ); + encapsulatePoint(lx3, nwHeight, lz3); vb.putStaticVertex( lx3, nwHeight, lz3, uvx - uvcos, uvy - uvsin, 0, @@ -1367,6 +1406,7 @@ private void uploadTileModel( terrainDataA, terrainDataB, terrainDataC ); + encapsulatePoint(lx0, ly0, lz0); vb.putStaticVertex( lx0, ly0, lz0, uvAx, uvAy, 0, @@ -1374,6 +1414,7 @@ private void uploadTileModel( texturedFaceIdx ); + encapsulatePoint(lx1, ly1, lz1); vb.putStaticVertex( lx1, ly1, lz1, uvBx, uvBy, 0, @@ -1381,6 +1422,7 @@ private void uploadTileModel( texturedFaceIdx ); + encapsulatePoint(lx2, ly2, lz2); vb.putStaticVertex( lx2, ly2, lz2, uvCx, uvCy, 0, @@ -1721,6 +1763,7 @@ private int uploadStaticModel( 0, 0, 0 ); + encapsulatePoint(vx1, vy1, vz1); vb.putStaticVertex( vx1, vy1, vz1, faceUVs[0], faceUVs[1], faceUVs[2], @@ -1728,6 +1771,7 @@ private int uploadStaticModel( texturedFaceIdx ); + encapsulatePoint(vx2, vy2, vz2); vb.putStaticVertex( vx2, vy2, vz2, faceUVs[4], faceUVs[5], faceUVs[6], @@ -1735,6 +1779,7 @@ private int uploadStaticModel( texturedFaceIdx ); + encapsulatePoint(vx3, vy3, vz3); vb.putStaticVertex( vx3, vy3, vz3, faceUVs[8], faceUVs[9], faceUVs[10], @@ -2227,6 +2272,8 @@ public void uploadZoneGapFillers( int sceneMax = SCENE_SIZE + ctx.expandedMapLoadingChunks * CHUNK_SIZE; Tile[][][] extendedTiles = ctx.scene.getExtendedTiles(); + level = 0; + int posBefore = vb.position(); for (int xoff = 0; xoff < CHUNK_SIZE; ++xoff) { for (int zoff = 0; zoff < CHUNK_SIZE; ++zoff) { diff --git a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java index 20a8ffd81d..69db9caf1f 100644 --- a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java +++ b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java @@ -15,6 +15,7 @@ import rs117.hd.HdPlugin; import rs117.hd.opengl.uniforms.UBOWorldViews; import rs117.hd.opengl.uniforms.UBOWorldViews.WorldViewStruct; +import rs117.hd.scene.SceneCullingManager; import rs117.hd.utils.Camera; import rs117.hd.utils.CommandBuffer; import rs117.hd.utils.DestructibleHandler; @@ -26,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.*; import static rs117.hd.utils.collections.Util.quickSort; @Slf4j @@ -54,11 +56,14 @@ public class WorldViewContext { @Inject private SceneManager sceneManager; + @Inject + private SceneCullingManager sceneCullingManager; + final int worldViewId; final int sizeX, sizeZ; @Nullable - WorldViewStruct uboWorldViewStruct; - ZoneSceneContext sceneContext; + public WorldViewStruct uboWorldViewStruct; + public ZoneSceneContext sceneContext; Zone[][] zones; GLBuffer vboM; boolean isLoading = true; @@ -69,8 +74,8 @@ public class WorldViewContext { private final Comparator alphaSortComparator = Comparator.comparingInt((Zone z) -> z.dist).reversed(); private final List alphaZones = new ArrayList<>(); - CommandBuffer vaoSceneCmd; - CommandBuffer vaoDirectionalCmd; + public CommandBuffer vaoSceneCmd; + public CommandBuffer vaoDirectionalCmd; final DynamicModelVAO[][] dynamicModelVaos = new DynamicModelVAO[FRAMES_IN_FLIGHT][VAO_COUNT]; public long loadTime; @@ -134,11 +139,6 @@ void initBuffers() { log.trace("WorldViewContext - WorldViewId: {} initBuffers took {}ms", worldViewId, (System.nanoTime() - start) / 1000000); } - void map() { - for (int i = 0; i < VAO_COUNT; i++) - dynamicModelVaos[plugin.frame % FRAMES_IN_FLIGHT][i].map(); - } - DynamicModelVAO.View beginDraw(int type, int faces) { return dynamicModelVaos[plugin.frame % FRAMES_IN_FLIGHT][type].beginDraw(faces); } @@ -151,7 +151,7 @@ int obtainDrawIndex(int type) { return dynamicModelVaos[plugin.frame % FRAMES_IN_FLIGHT][type].obtainDrawIndex(); } - void drawAll(int type, CommandBuffer cmd) { + public void drawAll(int type, CommandBuffer cmd) { dynamicModelVaos[plugin.frame % FRAMES_IN_FLIGHT][type].draw(cmd); } @@ -171,7 +171,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 == WorldView.TOPLEVEL && !z.inSceneFrustum)) + if (z.alphaModels.isEmpty() || (worldViewId == WorldView.TOPLEVEL && !z.isVisible(camera))) continue; final int dx = camPosX - ((zx - offset) << 10); @@ -213,11 +213,8 @@ void handleZoneSwap(int zx, int zz, boolean queue) { zones[zx][zz] = curZone = uploadTask.zone; clientThread.invoke(curZone::unmap); - if (prevZone != curZone) { - curZone.inSceneFrustum = prevZone.inSceneFrustum; - curZone.inShadowFrustum = prevZone.inShadowFrustum; + if (prevZone != curZone) DestructibleHandler.queueDestruction(prevZone); - } sceneContext.animatedDynamicObjectIds.addAll(curZone.animatedDynamicObjectIds); } else if (uploadTask.wasCancelled() && !curZone.cull) { @@ -258,6 +255,54 @@ void processZoneRebuilds() { } } + void preSceneDraw(Camera camera) { + completeInvalidation(); + + for (int zx = 0; zx < sizeX; ++zx) + for (int zz = 0; zz < sizeZ; ++zz) + zones[zx][zz].queueVisibility(this, zx, zz); + sceneCullingManager.flush(); + + int offset = sceneContext.sceneOffset >> 3; + for (int zx = 0; zx < sizeX; ++zx) { + for (int zz = 0; zz < sizeZ; ++zz) { + final Zone z = zones[zx][zz]; + z.resolveVisibility(); + + if(z.isVisible(camera)) + z.multizoneLocs(sceneContext, zx - offset, zz - offset, camera, zones); + } + } + + sortStaticAlphaModels(camera); + + for (int i = 0; i < VAO_COUNT; i++) + dynamicModelVaos[plugin.frame % FRAMES_IN_FLIGHT][i].map(); + } + + void debugDraw(Camera camera) { + int offset = sceneContext.sceneOffset >> 3; + int startX = clamp(((int)camera.getPositionX() >> 10) + offset, 0, sizeX - 1); + int startZ = clamp(((int)camera.getPositionZ() >> 10) + offset, 0, sizeZ - 1); + + int drawRange = 2; + for(int x = -drawRange; x < drawRange; x++) { + int zx = startX + x; + if(zx < 0 || zx >= sizeX) + continue; + + for(int z = -drawRange; z < drawRange; z++) { + int zz = startZ + z; + if(zz < 0 || zz >= sizeZ) + continue; + + Zone zone = zones[zx][zz]; + if(zone != null) + zone.debugDrawVisibility(sceneCullingManager); + } + } + } + void completeInvalidation() { if (invalidationGroup.getPendingCount() <= 0) return; diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 129b0f977a..e27330bbaf 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -15,6 +15,9 @@ import rs117.hd.HdPlugin; import rs117.hd.scene.MaterialManager; import rs117.hd.scene.SceneContext; +import rs117.hd.scene.SceneCullingManager; +import rs117.hd.scene.SceneCullingManager.CullingResult; +import rs117.hd.scene.SceneCullingManager.CullingSphere; import rs117.hd.scene.materials.Material; import rs117.hd.scene.model_overrides.ModelOverride; import rs117.hd.utils.Camera; @@ -86,24 +89,42 @@ public class Zone implements Destructible { public boolean hasWater; // whether the zone has any water tiles public boolean onlyWater; // whether the zone only contains water tiles public boolean hasGapFiller; // whether the zone has any gap filler geometry - public boolean inSceneFrustum; // whether the zone is visible to the scene camera - public boolean inShadowFrustum; // whether the zone casts shadows into the visible scene public boolean isFirstLoadingAttempt = true; + public byte visibilityFlags = (byte) 0xFF; + public IntHashSet animatedDynamicObjectIds = new IntHashSet(); final StaticAlphaSortingJob alphaSortingJob = new StaticAlphaSortingJob(); ZoneUploadJob uploadJob; int[] levelOffsets = new int[LEVEL_COUNT]; // buffer pos in ints for the end of the level + CullingResult[] levelCullingResults = new CullingResult[LEVEL_COUNT]; int[][] rids; int[][] roofStart; int[][] roofEnd; - final List alphaModels = new ArrayList<>(0); + public final List alphaModels = new ArrayList<>(0); final ConcurrentLinkedQueue pendingModelJobs = new ConcurrentLinkedQueue<>(); + public boolean setVisibility(Camera camera, boolean visible) { + if (visible) { + visibilityFlags |= (byte) camera.getCullingMask(); + } else { + visibilityFlags &= (byte) ~camera.getCullingMask(); + } + return visible; + } + + public boolean isVisible(Camera camera) { + return (visibilityFlags & camera.getCullingMask()) != 0; + } + + public boolean isVisible(int cameraId) { + return (visibilityFlags & (1 << cameraId)) != 0; + } + public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f) { assert glVao == 0; assert glVaoA == 0; @@ -185,19 +206,23 @@ public void destroy() { sortedAlphaFacesUpload.release(); + for(int i = 0; i < LEVEL_COUNT; i++) { + if(levelCullingResults[i] != null) + levelCullingResults[i].release(); + } + sizeO = 0; sizeA = 0; sizeF = 0; bufLen = 0; bufLenA = 0; + visibilityFlags = 0; initialized = false; cull = false; hasWater = false; onlyWater = false; hasGapFiller = false; - inSceneFrustum = false; - inShadowFrustum = false; Arrays.fill(levelOffsets, 0); rids = null; @@ -274,12 +299,12 @@ private void setupVao(int vao, int buffer, int metadata) { glBindBuffer(GL_ARRAY_BUFFER, 0); } - public void setMetadata(WorldViewContext viewContext, SceneContext sceneContext, int mx, int mz) { + public void setMetadata(WorldViewContext viewContext, SceneContext sceneContext, int zx, int zz) { if (vboM == null) return; - int baseX = (mx - (sceneContext.sceneOffset >> 3)) << 10; - int baseZ = (mz - (sceneContext.sceneOffset >> 3)) << 10; + final int baseX = (zx - (sceneContext.sceneOffset >> 3)) << 10; + final int baseZ = (zz - (sceneContext.sceneOffset >> 3)) << 10; try (MemoryStack stack = MemoryStack.stackPush()) { IntBuffer buf = stack.mallocInt(3) @@ -325,18 +350,21 @@ private void convertForDraw(int vertSize) { copyTo(glDrawLength, drawEnd, 0, drawIdx); } - void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) { + public void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, Camera camera, boolean ignoreRoofRemoval) { drawIdx = 0; int currentLevel = ctx.level; int maxLevel = ctx.maxLevel; var hiddenRoofIds = ctx.hideRoofIds; - if (roofShadows) { + if (ignoreRoofRemoval) { maxLevel = 3; hiddenRoofIds = Collections.emptySet(); } for (int level = ctx.minLevel; level <= maxLevel; ++level) { + if(camera != null && (levelCullingResults[level] != null && !levelCullingResults[level].isVisible(camera))) + continue; + int[] rids = this.rids[level]; int[] roofStart = this.roofStart[level]; int[] roofEnd = this.roofEnd[level]; @@ -382,7 +410,7 @@ void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) flush(cmd); } - void renderOpaqueLevel(CommandBuffer cmd, int level) { + public void renderOpaqueLevel(CommandBuffer cmd, int level) { drawIdx = 0; pushRange(this.levelOffsets[level - 1], this.levelOffsets[level]); @@ -413,6 +441,7 @@ private static void pushRange(int start, int end) { public static final class AlphaModel { int id; ModelOverride modelOverride; + CullingSphere cullingSphere; int startpos, endpos; short x, y, z; // local position short rid; @@ -462,8 +491,53 @@ void setView(DynamicModelVAO.View view) { } } + void queueVisibility(WorldViewContext ctx, int zx, int zz) { + + final Projection projection = ctx.uboWorldViewStruct != null ? ctx.uboWorldViewStruct.worldView.getMainWorldProjection() : null; + final int baseX = (zx - (ctx.sceneContext.sceneOffset >> 3)) << 10; + final int baseZ = (zz - (ctx.sceneContext.sceneOffset >> 3)) << 10; + + for(int i = 0; i < alphaModels.size(); i++) { + final AlphaModel m = alphaModels.get(i); + if(m.cullingSphere != null) { + m.cullingSphere.offsetX = baseX; + m.cullingSphere.offsetZ = baseZ; + m.cullingSphere.projection = projection; + m.cullingSphere.queue(); + } + } + + for(int i = 0; i < LEVEL_COUNT; i++) { + final CullingResult result = levelCullingResults[i]; + if(result != null) { + result.projection = projection; + result.offsetX = baseX; + result.offsetZ = baseZ; + result.queue(); + } + } + } + + void debugDrawVisibility(SceneCullingManager sceneCullingManager) { + sceneCullingManager.debugDraw(levelCullingResults); + + for(int i = 0; i < alphaModels.size(); i++) + sceneCullingManager.debugDraw(alphaModels.get(i).cullingSphere); + } + + void resolveVisibility(){ + visibilityFlags = 0; + for(int i = 0; i < LEVEL_COUNT; i++) { + if(levelCullingResults[i] != null) + visibilityFlags |= levelCullingResults[i].getVisibilityFlags(); + } + + // TODO: We build a "visible" set of alpha models + } + void addAlphaModel( HdPlugin plugin, + SceneCullingManager sceneCullingManager, MaterialManager materialManager, int vao, int tboF, @@ -617,6 +691,7 @@ void addAlphaModel( m.radius = 2 + (int) Math.sqrt(radius); m.sortedFaces = new int[bufferIdx * 3]; + m.cullingSphere = sceneCullingManager.obtainSphere(x + cx, y + cy, z + cz, m.radius * 2); assert packedFaces.length > 0; // Normally these will be equal, but transparency is used to hide faces in the TzHaar reskin @@ -710,12 +785,13 @@ void alphaStaticModelSort(Camera camera) { alphaSortingJob.queue(camera); } - void renderAlpha( + public void renderAlpha( CommandBuffer cmd, int zx, int zz, int level, WorldViewContext ctx, + Camera camera, boolean isShadowPass, boolean includeRoof ) { @@ -748,6 +824,16 @@ void renderAlpha( level > currentLevel && !hiddenRoofIds.isEmpty() && hiddenRoofIds.contains((int) m.rid)) continue; + if(camera != null) { + if(m.cullingSphere != null) { + if(!m.cullingSphere.isVisible(camera)) + continue; + } else { + if((levelCullingResults[m.level] != null && !levelCullingResults[m.level].isVisible(camera))) + continue; + } + } + int drawMode = STATIC; if (m.isTemp()) { // these are already sorted and so just requires a glMultiDrawArrays() from the active vao @@ -847,6 +933,9 @@ synchronized void multizoneLocs(SceneContext ctx, int zx, int zz, Camera camera, if (m.lx == -1) continue; + if(m.cullingSphere != null && !m.cullingSphere.isVisible(camera)) + continue; + // calculate which zone this model should be drawn from // TODO fix for boats int max = Integer.MAX_VALUE; @@ -861,7 +950,7 @@ 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) { + if (visibilityFlags != 0 && zones[zx2][zz2].initialized) { max = distance; closestZoneX = centerX >> 10; closestZoneZ = centerZ >> 10; @@ -883,6 +972,7 @@ synchronized void multizoneLocs(SceneContext ctx, int zx, int zz, Camera camera, AlphaModel m2 = ALPHA_MODEL_POOL.acquire(); m2.id = m.id; m2.modelOverride = m.modelOverride; + m2.cullingSphere = m.cullingSphere; m2.startpos = m.startpos; m2.endpos = m.endpos; m2.x = m.x; diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index 6a914772ec..fb5aee2d12 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -25,7 +25,6 @@ package rs117.hd.renderer.zone; import com.google.inject.Injector; -import java.io.IOException; import java.util.Arrays; import java.util.Set; import javax.inject.Inject; @@ -42,29 +41,24 @@ import rs117.hd.HdPluginConfig; import rs117.hd.config.ColorFilter; import rs117.hd.config.DynamicLights; -import rs117.hd.config.ShadowMode; -import rs117.hd.opengl.shader.SceneShaderProgram; -import rs117.hd.opengl.shader.ShaderException; import rs117.hd.opengl.shader.ShaderIncludes; -import rs117.hd.opengl.shader.ShadowShaderProgram; import rs117.hd.opengl.uniforms.UBOLights; import rs117.hd.opengl.uniforms.UBOWorldViews; import rs117.hd.overlays.FrameTimer; import rs117.hd.overlays.Timer; import rs117.hd.renderer.Renderer; +import rs117.hd.renderer.zone.passes.RenderPipeline; import rs117.hd.scene.EnvironmentManager; import rs117.hd.scene.LightManager; import rs117.hd.scene.ProceduralGenerator; import rs117.hd.scene.SceneContext; +import rs117.hd.scene.SceneCullingManager; import rs117.hd.scene.lights.Light; -import rs117.hd.scene.model_overrides.ModelOverride; import rs117.hd.utils.Camera; import rs117.hd.utils.ColorUtils; -import rs117.hd.utils.CommandBuffer; import rs117.hd.utils.HDUtils; import rs117.hd.utils.Mat4; import rs117.hd.utils.RenderState; -import rs117.hd.utils.ShadowCasterVolume; import rs117.hd.utils.buffer.GLBuffer; import rs117.hd.utils.buffer.GLMappedBufferIntWriter; import rs117.hd.utils.buffer.GpuIntBuffer; @@ -82,7 +76,6 @@ import static rs117.hd.HdPluginConfig.*; import static rs117.hd.renderer.zone.WorldViewContext.VAO_OPAQUE; import static rs117.hd.renderer.zone.WorldViewContext.VAO_PLAYER; -import static rs117.hd.renderer.zone.WorldViewContext.VAO_PRESCENE; import static rs117.hd.renderer.zone.WorldViewContext.VAO_SHADOW; import static rs117.hd.utils.MathUtils.*; @@ -91,6 +84,9 @@ public class ZoneRenderer implements Renderer { public static final int FRAMES_IN_FLIGHT = 3; + public static int CAMERA_COUNT; + public static final int SCENE_CAMERA_ID = CAMERA_COUNT++; + private static int TEXTURE_UNIT_COUNT = HdPlugin.TEXTURE_UNIT_COUNT; public static final int TEXTURE_UNIT_TEXTURED_FACES = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; @@ -128,16 +124,10 @@ public class ZoneRenderer implements Renderer { private ModelStreamingManager modelStreamingManager; @Inject - private FrameTimer frameTimer; - - @Inject - private SceneShaderProgram sceneProgram; - - @Inject - private ShadowShaderProgram.Fast fastShadowProgram; + private SceneCullingManager sceneCullingManager; @Inject - private ShadowShaderProgram.Detailed detailedShadowProgram; + private FrameTimer frameTimer; @Inject private JobSystem jobSystem; @@ -145,16 +135,14 @@ public class ZoneRenderer implements Renderer { @Inject private UBOWorldViews uboWorldViews; - public final Camera sceneCamera = new Camera().setReverseZ(true); - public final Camera directionalCamera = new Camera().setOrthographic(true); - public final ShadowCasterVolume directionalShadowCasterVolume = new ShadowCasterVolume(directionalCamera); + @Inject + private RenderPipeline renderPipeline; + + public final Camera sceneCamera = new Camera().setReverseZ(true).setCullingId(SCENE_CAMERA_ID); public final RenderState renderState = new RenderState(); - public final CommandBuffer sceneCmd = new CommandBuffer("Scene"); - public final CommandBuffer directionalCmd = new CommandBuffer("Directional"); - public final CommandBuffer gapFillerCmd = new CommandBuffer("GapFiller"); - private GLBuffer indirectDrawCmds; + public GLBuffer indirectDrawCmds; public static GpuIntBuffer indirectDrawCmdsStaging; public static GLBuffer.EBO eboAlpha; @@ -163,8 +151,6 @@ public class ZoneRenderer implements Renderer { private boolean sceneFboValid; private boolean shouldRenderSkybox; private boolean shouldRenderScene; - private boolean shouldClearShadowFbo; - private boolean shouldDrawRoofShadows; @Override public boolean supportsGpu(GLCapabilities glCaps) { @@ -189,18 +175,14 @@ public void initialize() { if (FacePrioritySorter.POOL == null) FacePrioritySorter.POOL = new ConcurrentPool<>(() -> injector.getInstance(FacePrioritySorter.class)); - sceneCmd.setFrameTimer(frameTimer); - directionalCmd.setFrameTimer(frameTimer); - gapFillerCmd.setFrameTimer(frameTimer); - jobSystem.startUp(config.cpuUsageLimit()); uboWorldViews.initialize(UNIFORM_BLOCK_WORLD_VIEWS); sceneManager.initialize(uboWorldViews); modelStreamingManager.initialize(); + renderPipeline.initialize(); // Force updates that only run when the cameras change sceneCamera.setDirty(); - directionalCamera.setDirty(); } @Override @@ -211,6 +193,7 @@ public void destroy() { modelStreamingManager.destroy(); sceneManager.destroy(); uboWorldViews.destroy(); + renderPipeline.destroy(); if (SceneUploader.POOL != null) SceneUploader.POOL.destroy(); @@ -231,20 +214,18 @@ public void addShaderIncludes(ShaderIncludes includes) { .define("MAX_SIMULTANEOUS_WORLD_VIEWS", UBOWorldViews.MAX_SIMULTANEOUS_WORLD_VIEWS) .addInclude("WORLD_VIEW_GETTER", () -> plugin.generateGetter("WorldView", UBOWorldViews.MAX_SIMULTANEOUS_WORLD_VIEWS)) .addUniformBuffer(uboWorldViews); + + renderPipeline.addShaderIncludes.execute(includes); } @Override - public void initializeShaders(ShaderIncludes includes) throws ShaderException, IOException { - sceneProgram.compile(includes); - fastShadowProgram.compile(includes); - detailedShadowProgram.compile(includes); + public void initializeShaders(ShaderIncludes includes) { + renderPipeline.initializeShaders.execute(includes); } @Override public void destroyShaders() { - sceneProgram.destroy(); - fastShadowProgram.destroy(); - detailedShadowProgram.destroy(); + renderPipeline.destroyShaders.execute(); } private void initializeBuffers() { @@ -278,6 +259,8 @@ private void destroyBuffers() { public void processConfigChanges(Set keys) { if (keys.contains(KEY_ASYNC_MODEL_PROCESSING)) modelStreamingManager.reinitialize(); + + renderPipeline.processConfigChanges.execute(keys); } @Override @@ -309,45 +292,15 @@ public void preSceneDraw( if (ctx.uboWorldViewStruct != null) ctx.uboWorldViewStruct.update(); - if (scene.getWorldViewId() == WorldView.TOPLEVEL) + final boolean isTopLevel = scene.getWorldViewId() == WorldView.TOPLEVEL; + if (isTopLevel) preSceneDrawTopLevel(scene, cameraX, cameraY, cameraZ, cameraPitch, cameraYaw); - 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); - - ctx.sortStaticAlphaModels(sceneCamera); - - ctx.map(); - - if (scene.getWorldViewId() == WorldView.TOPLEVEL) { - Model skybox = scene.getSkybox(); - if (skybox != null) { - skybox.calculateBoundsCylinder(); - modelStreamingManager.uploadTempModel( - ctx, - sceneCamera, - null, - skybox, - ModelOverride.UNLIT, - skybox, - null, - null, - true, - VAO_PRESCENE, - -1, - 0, - cameraX, cameraY, cameraZ - ); - } + renderPipeline.preSceneDraw.execute(ctx, isTopLevel); + ctx.preSceneDraw(sceneCamera); - sceneCmd.DepthMask(false); - ctx.drawAll(VAO_PRESCENE, sceneCmd); - sceneCmd.DepthMask(true); - } + if(plugin.showCulling) + ctx.debugDraw(sceneCamera); frameTimer.end(Timer.DRAW_PRESCENE); } catch (Throwable ex) { @@ -390,7 +343,6 @@ private void preSceneDrawTopLevel( Arrays.fill(plugin.cameraShift, 0); float zoom = client.get3dZoom(); - float drawDistance = (float) plugin.getDrawDistance(); if (plugin.orthographicProjection) zoom *= ORTHOGRAPHIC_ZOOM; @@ -431,86 +383,6 @@ private void preSceneDrawTopLevel( return; } - directionalCamera.setPitch(environmentManager.currentSunAngles[0]); - directionalCamera.setYaw(PI - environmentManager.currentSunAngles[1]); - boolean hasDirectionalCameraChanged = directionalCamera.isViewDirty() || directionalCamera.isProjDirty(); - - if (plugin.configShadowsEnabled && - (hasSceneCameraChanged || hasDirectionalCameraChanged) && - !sceneCamera.isOrthographic() - ) { - int shadowDrawDistance = 90 * LOCAL_TILE_SIZE; - - final float[][] volumeCorners = directionalShadowCasterVolume - .build(sceneCamera, drawDistance * LOCAL_TILE_SIZE, shadowDrawDistance); - - final float[] sceneCenter = new float[3]; - for (float[] corner : volumeCorners) - add(sceneCenter, sceneCenter, corner); - divide(sceneCenter, sceneCenter, (float) volumeCorners.length); - - // Reset position before transforming points - directionalCamera.setPosition(0, 0, 0); - - float minX = Float.POSITIVE_INFINITY, maxX = Float.NEGATIVE_INFINITY; - float minY = Float.POSITIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY; - float minZ = Float.POSITIVE_INFINITY, maxZ = Float.NEGATIVE_INFINITY; - float radius = 0f; - for (float[] corner : volumeCorners) { - radius = max(radius, distance(sceneCenter, corner)); - - directionalCamera.transformPoint(corner, corner); - - minX = min(minX, corner[0]); - maxX = max(maxX, corner[0]); - - minY = min(minY, corner[1]); - maxY = max(maxY, corner[1]); - - minZ = min(minZ, corner[2]); - maxZ = max(maxZ, corner[2]); - } - - // Offset the Directional Camera by the radius of the scene - float[] directionalFwd = directionalCamera.getForwardDirection(); - multiply(directionalFwd, directionalFwd, radius); - add(sceneCenter, sceneCenter, directionalFwd); - - // Calculate directional size from the AABB of the scene frustum corners - // Then snap to the nearest multiple of `LOCAL_HALF_TILE_SIZE` to prevent shimmering - int directionalSize = (int) max(abs(maxY - minY), abs(maxX - minX), abs(maxZ - minZ)); - directionalSize = Math.round(directionalSize / (float) LOCAL_HALF_TILE_SIZE) * LOCAL_HALF_TILE_SIZE; - directionalSize = max(8000, directionalSize); // Clamp the size to prevent going too small at reduced draw distances - - // Ignore directional size changes below the change threshold to avoid inducing shimmering - int previousDirectionalSize = directionalCamera.getViewportWidth(); - float changeThreshold = previousDirectionalSize * 0.05f; // 10% of the previous directional size - if (abs(directionalSize - previousDirectionalSize) < changeThreshold) - directionalSize = previousDirectionalSize; - - // Snap Position to Shadow Texel Grid to prevent shimmering - directionalCamera.transformPoint(sceneCenter, sceneCenter); - - float texelSize = (float) directionalSize / plugin.shadowMapResolution; - sceneCenter[0] = (float) floor(sceneCenter[0] / texelSize + 0.5f) * texelSize; - sceneCenter[1] = (float) floor(sceneCenter[1] / texelSize + 0.5f) * texelSize; - - directionalCamera.setPosition(directionalCamera.inverseTransformPoint(sceneCenter, sceneCenter)); - directionalCamera.setNearPlane(Math.max(0.1f, radius * 0.05f)); - directionalCamera.setFarPlane(radius * 2.0f); - directionalCamera.setZoom(1.0f); - directionalCamera.setViewportWidth(directionalSize); - directionalCamera.setViewportHeight(directionalSize); - - plugin.uboGlobal.lightProjectionMatrix.set(directionalCamera.getViewProjMatrix()); - } - - shouldDrawRoofShadows = - plugin.configShadowsEnabled && - plugin.configRoofShadows && - environmentManager.allowRoofShadows(); - - plugin.uboGlobal.lightDir.set(directionalCamera.getForwardDirection()); plugin.uboGlobal.cameraPos.set(plugin.cameraPosition); plugin.uboGlobal.viewMatrix.set(plugin.viewMatrix); plugin.uboGlobal.projectionMatrix.set(plugin.viewProjMatrix); @@ -647,9 +519,6 @@ private void preSceneDrawTopLevel( // Reset buffers for the next frame indirectDrawCmdsStaging.clear(); - sceneCmd.reset(); - directionalCmd.reset(); - gapFillerCmd.reset(); renderState.reset(); eboAlpha.orphan(); @@ -673,6 +542,9 @@ public void postSceneDraw(Scene scene) { frameTimer.begin(Timer.DRAW_POSTSCENE); if (scene.getWorldViewId() == WorldView.TOPLEVEL) postDrawTopLevel(); + + renderPipeline.postSceneDraw.execute(ctx); + frameTimer.end(Timer.DRAW_POSTSCENE); } catch (Throwable ex) { log.error("Error in postSceneDraw({}):", scene != null ? scene.getWorldViewId() : null, ex); @@ -709,135 +581,6 @@ private void postDrawTopLevel() { checkGLErrors(); } - private void tiledLightingPass() { - if (!plugin.configTiledLighting || plugin.configDynamicLights == DynamicLights.NONE) - return; - - plugin.updateTiledLightingFbo(); - assert plugin.fboTiledLighting != 0; - - frameTimer.begin(Timer.DRAW_TILED_LIGHTING); - frameTimer.begin(Timer.RENDER_TILED_LIGHTING); - - renderState.framebuffer.set(GL_FRAMEBUFFER, plugin.fboTiledLighting); - renderState.viewport.set(0, 0, plugin.tiledLightingResolution[0], plugin.tiledLightingResolution[1]); - renderState.vao.setVao(plugin.vaoTri); - - if (plugin.tiledLightingImageStoreProgram.isValid()) { - renderState.program.set(plugin.tiledLightingImageStoreProgram); - renderState.drawBuffer.set(GL_NONE); - renderState.apply(); - glDrawArrays(GL_TRIANGLES, 0, 3); - } else { - renderState.drawBuffer.set(GL_COLOR_ATTACHMENT0); - int layerCount = plugin.configDynamicLights.getTiledLightingLayers(); - for (int layer = 0; layer < layerCount; layer++) { - renderState.program.set(plugin.tiledLightingShaderPrograms.get(layer)); - renderState.framebufferTextureLayer.set(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, plugin.texTiledLighting, 0, layer); - renderState.apply(); - glDrawArrays(GL_TRIANGLES, 0, 3); - } - } - - frameTimer.end(Timer.RENDER_TILED_LIGHTING); - frameTimer.end(Timer.DRAW_TILED_LIGHTING); - } - - private void directionalShadowPass() { - final boolean shouldRenderShadows = - plugin.configShadowsEnabled && - plugin.fboShadowMap != 0 && - environmentManager.currentDirectionalStrength > 0; - - if (shouldRenderShadows || shouldClearShadowFbo) { - // Render to the shadow depth map - renderState.framebuffer.set(GL_FRAMEBUFFER, plugin.fboShadowMap); - renderState.viewport.set(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); - renderState.apply(); - - glClearDepth(1); - glClear(GL_DEPTH_BUFFER_BIT); - shouldClearShadowFbo = false; - } - - if (!shouldRenderShadows) - return; - - frameTimer.begin(Timer.RENDER_SHADOWS); - - renderState.enable.set(GL_DEPTH_TEST); - renderState.disable.set(GL_CULL_FACE); - renderState.depthFunc.set(GL_LEQUAL); - renderState.ido.set(indirectDrawCmds.id); - - CommandBuffer.SKIP_DEPTH_MASKING = true; - directionalCmd.execute(renderState); - CommandBuffer.SKIP_DEPTH_MASKING = false; - - glBindVertexArray(0); - - renderState.disable.set(GL_DEPTH_TEST); - - shouldClearShadowFbo = true; - frameTimer.end(Timer.RENDER_SHADOWS); - } - - private void scenePass() { - sceneProgram.use(); - - frameTimer.begin(Timer.DRAW_SCENE); - 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[] clearColor = { 0, 0, 0 }; - if (!shouldRenderSkybox) { - float[] fogColor = ColorUtils.linearToSrgb(environmentManager.currentFogColor); - pow(clearColor, fogColor, plugin.getGammaCorrection()); - } - glClearColor(clearColor[0], clearColor[1], clearColor[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); - - if (!gapFillerCmd.isEmpty()) { - renderState.depthMask.set(false); - gapFillerCmd.execute(renderState); - renderState.depthMask.set(true); - } - - sceneCmd.execute(renderState); - - frameTimer.end(Timer.RENDER_SCENE); - - glBindVertexArray(0); - - // Done rendering the scene - renderState.disable.set(GL_BLEND); - renderState.disable.set(GL_CULL_FACE); - renderState.disable.set(GL_DEPTH_TEST); - renderState.apply(); - - frameTimer.end(Timer.DRAW_SCENE); - } - @Override public boolean zoneInFrustum(int zx, int zz, int maxY, int minY) { if (plugin.isPluginStopPending()) @@ -847,64 +590,42 @@ public boolean zoneInFrustum(int zx, int zz, int maxY, int minY) { if (!sceneManager.isTopLevelValid()) return false; + if (plugin.enableDetailedTimers) + frameTimer.begin(Timer.VISIBILITY_CHECK); + 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; + int x = zx * CHUNK_SIZE - ctx.sceneContext.sceneOffset; + int z = 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); + true, base[0] + x, base[1] + z, base[0] + x + 7, base[1] + z + 7); if (!inArea) { - if (plugin.enableDetailedTimers) frameTimer.end(Timer.VISIBILITY_CHECK); 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; - } + return zone.visibilityFlags != 0; - final int PADDING = 4 * LOCAL_TILE_SIZE; - zone.inSceneFrustum = sceneCamera.intersectsAABB( - minX - PADDING, minY, minZ - PADDING, maxX + PADDING, maxY, maxZ + PADDING); + final int zMinX = x * LOCAL_TILE_SIZE; + final int zMinZ = z * LOCAL_TILE_SIZE; + final int zMaxX = zMinX + CHUNK_SIZE * LOCAL_TILE_SIZE; + final int zMaxZ = zMinZ + CHUNK_SIZE * LOCAL_TILE_SIZE; + final int zMinY = minY - (zone.hasWater ? ProceduralGenerator.MAX_DEPTH : 0); + final int zMaxY = maxY + (zone.hasWater ? ProceduralGenerator.MAX_DEPTH : 0); - if (zone.inSceneFrustum) { - if (plugin.enableDetailedTimers) - frameTimer.end(Timer.VISIBILITY_CHECK); - 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); - } - if (plugin.enableDetailedTimers) - frameTimer.end(Timer.VISIBILITY_CHECK); - return zone.inShadowFrustum; - } + renderPipeline.zoneInFrustum.execute(zone, zx, zz, zMinX, zMinY, zMinZ, zMaxX, zMaxY, zMaxZ); - if (plugin.enableDetailedTimers) - frameTimer.end(Timer.VISIBILITY_CHECK); - if (plugin.orthographicProjection) - return zone.inSceneFrustum = true; + return zone.visibilityFlags != 0; } catch (Throwable ex) { log.error("Error in zoneInFrustum({}, {}, {}, {}):", zx, zz, maxY, minY, ex); plugin.requestPluginStop(); + } finally { + if (plugin.enableDetailedTimers) + frameTimer.end(Timer.VISIBILITY_CHECK); } return false; } @@ -924,18 +645,7 @@ 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 (z.hasGapFiller) - z.renderOpaqueLevel(gapFillerCmd, Zone.LEVEL_GAP_FILLER); - } - - final boolean isSquashed = ctx.uboWorldViewStruct != null && ctx.uboWorldViewStruct.isSquashed(); - if (!isSquashed && (!sceneManager.isRoot(ctx) || z.inShadowFrustum)) { - directionalCmd.SetShader(fastShadowProgram); - z.renderOpaque(directionalCmd, ctx, shouldDrawRoofShadows); - } + renderPipeline.drawZoneOpaque.execute(ctx, z, zx, zz); frameTimer.end(Timer.DRAW_ZONE_OPAQUE); checkGLErrors(); @@ -960,28 +670,14 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i return; frameTimer.begin(Timer.DRAW_ZONE_ALPHA); - final boolean renderWater = z.inSceneFrustum && level == 0 && z.hasWater; - if (renderWater) - z.renderOpaqueLevel(sceneCmd, Zone.LEVEL_WATER_SURFACE); - modelStreamingManager.ensureAsyncUploadsComplete(z); - final boolean hasAlpha = z.sizeA != 0 || !z.alphaModels.isEmpty(); - if (hasAlpha) { - final int offset = ctx.sceneContext.sceneOffset >> 3; - // Only sort if the alpha will be directly visible, since shadows don't require sorting - if (level == 0 && (!sceneManager.isRoot(ctx) || z.inSceneFrustum)) - z.alphaSort(zx - offset, zz - offset, sceneCamera); - - 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, shouldDrawRoofShadows); - } + final int offset = ctx.sceneContext.sceneOffset >> 3; + if (level == 0) + z.alphaSort(zx - offset, zz - offset, sceneCamera); + + renderPipeline.drawZoneAlpha.execute(ctx, z, level, zx, zz); - if (!sceneManager.isRoot(ctx) || z.inSceneFrustum) - z.renderAlpha(sceneCmd, zx - offset, zz - offset, level, ctx, false, false); - } frameTimer.end(Timer.DRAW_ZONE_ALPHA); checkGLErrors(); @@ -1003,48 +699,41 @@ public void drawPass(Projection projection, Scene scene, int pass) { frameTimer.begin(Timer.DRAW_PASS); - switch (pass) { - case DrawCallbacks.PASS_OPAQUE: - directionalCmd.SetShader(fastShadowProgram); - directionalCmd.ExecuteSubCommandBuffer(ctx.vaoDirectionalCmd); + renderPipeline.drawPass.execute(ctx, pass); - sceneCmd.ExecuteSubCommandBuffer(ctx.vaoSceneCmd); - break; - case DrawCallbacks.PASS_ALPHA: - modelStreamingManager.ensureAsyncUploadsComplete(null); + if (pass == DrawCallbacks.PASS_ALPHA) { + modelStreamingManager.ensureAsyncUploadsComplete(null); - if (sceneManager.isRoot(ctx)) - frameTimer.begin(Timer.UNMAP_ROOT_CTX); + if (sceneManager.isRoot(ctx)) + frameTimer.begin(Timer.UNMAP_ROOT_CTX); - ctx.unmap(); + ctx.unmap(); - if (sceneManager.isRoot(ctx)) - frameTimer.end(Timer.UNMAP_ROOT_CTX); + if (sceneManager.isRoot(ctx)) + frameTimer.end(Timer.UNMAP_ROOT_CTX); - // Draw opaque - ctx.drawAll(VAO_OPAQUE, ctx.vaoSceneCmd); - ctx.drawAll(VAO_OPAQUE, ctx.vaoDirectionalCmd); - ctx.drawAll(VAO_PLAYER, ctx.vaoDirectionalCmd); + // Draw opaque + ctx.drawAll(VAO_OPAQUE, ctx.vaoSceneCmd); + ctx.drawAll(VAO_OPAQUE, ctx.vaoDirectionalCmd); + ctx.drawAll(VAO_PLAYER, ctx.vaoDirectionalCmd); - // Draw shadow-only models - ctx.drawAll(VAO_SHADOW, ctx.vaoDirectionalCmd); + // Draw shadow-only models + ctx.drawAll(VAO_SHADOW, ctx.vaoDirectionalCmd); - // Draw players with sorted alpha, without writing depth - ctx.vaoSceneCmd.DepthMask(false); - ctx.drawAll(VAO_PLAYER, ctx.vaoSceneCmd); - ctx.vaoSceneCmd.DepthMask(true); + // Draw players with sorted alpha, without writing depth + ctx.vaoSceneCmd.DepthMask(false); + ctx.drawAll(VAO_PLAYER, ctx.vaoSceneCmd); + ctx.vaoSceneCmd.DepthMask(true); - // Redraw players, this time only writing depth, for correct ordering with the background - ctx.vaoSceneCmd.ColorMask(false, false, false, false); - ctx.drawAll(VAO_PLAYER, ctx.vaoSceneCmd); - ctx.vaoSceneCmd.ColorMask(true, true, true, true); + // Redraw players, this time only writing depth, for correct ordering with the background + ctx.vaoSceneCmd.ColorMask(false, false, false, false); + ctx.drawAll(VAO_PLAYER, ctx.vaoSceneCmd); + ctx.vaoSceneCmd.ColorMask(true, true, true, true); - for (int zx = 0; zx < ctx.sizeX; ++zx) - for (int zz = 0; zz < ctx.sizeZ; ++zz) - ctx.zones[zx][zz].postAlphaPass(); - break; + for (int zx = 0; zx < ctx.sizeX; ++zx) + for (int zz = 0; zz < ctx.sizeZ; ++zz) + ctx.zones[zx][zz].postAlphaPass(); } - frameTimer.end(Timer.DRAW_PASS); checkGLErrors(); } catch (Throwable ex) { @@ -1117,11 +806,8 @@ public void draw(int overlayColor) { } frameTimer.begin(Timer.DRAW_SUBMIT); - if (shouldRenderScene) { - tiledLightingPass(); - directionalShadowPass(); - scenePass(); - } + if (shouldRenderScene) + renderPipeline.draw.execute(renderState); if (sceneFboValid && plugin.sceneResolution != null && plugin.sceneViewport != null) { glBindFramebuffer(GL_READ_FRAMEBUFFER, plugin.fboScene); @@ -1179,6 +865,9 @@ public void draw(int overlayColor) { log.error("Unable to swap buffers:", ex); } + if(shouldRenderScene) + renderPipeline.postDraw.execute(renderState); + glBindFramebuffer(GL_FRAMEBUFFER, plugin.awtContext.getFramebuffer(false)); frameTimer.endFrameAndReset(); diff --git a/src/main/java/rs117/hd/renderer/zone/passes/DebugDrawPass.java b/src/main/java/rs117/hd/renderer/zone/passes/DebugDrawPass.java new file mode 100644 index 0000000000..c536b0f48d --- /dev/null +++ b/src/main/java/rs117/hd/renderer/zone/passes/DebugDrawPass.java @@ -0,0 +1,494 @@ +package rs117.hd.renderer.zone.passes; + +import java.io.IOException; +import java.nio.IntBuffer; +import java.util.Iterator; +import java.util.concurrent.ConcurrentLinkedQueue; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import rs117.hd.HdPlugin; +import rs117.hd.opengl.GLPrimitives; +import rs117.hd.opengl.shader.DebugDrawShaderProgram; +import rs117.hd.opengl.shader.DebugDrawShaderProgram.DebugDrawCubeShaderProgram; +import rs117.hd.opengl.shader.DebugDrawShaderProgram.DebugDrawLineShaderProgram; +import rs117.hd.opengl.shader.DebugDrawShaderProgram.DebugDrawSphereShaderProgram; +import rs117.hd.opengl.shader.DebugDrawShaderProgram.DebugDrawTextShaderProgram; +import rs117.hd.opengl.shader.ShaderException; +import rs117.hd.opengl.shader.ShaderIncludes; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.utils.DebugDraw; +import rs117.hd.utils.RenderState; +import rs117.hd.utils.buffer.GLBuffer; +import rs117.hd.utils.collections.ConcurrentPool; + +import static org.lwjgl.opengl.GL11.GL_BLEND; +import static org.lwjgl.opengl.GL11.GL_CULL_FACE; +import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST; +import static org.lwjgl.opengl.GL11.GL_FILL; +import static org.lwjgl.opengl.GL11.GL_FLOAT; +import static org.lwjgl.opengl.GL11.GL_FRONT_AND_BACK; +import static org.lwjgl.opengl.GL11.GL_GEQUAL; +import static org.lwjgl.opengl.GL11.GL_INT; +import static org.lwjgl.opengl.GL11.GL_LINE; +import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA; +import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA; +import static org.lwjgl.opengl.GL11.GL_TRIANGLES; +import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; +import static org.lwjgl.opengl.GL11.glPolygonMode; +import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER; +import static org.lwjgl.opengl.GL15.GL_DYNAMIC_DRAW; +import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER; +import static org.lwjgl.opengl.GL15.glBindBuffer; +import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray; +import static org.lwjgl.opengl.GL20.glVertexAttribPointer; +import static org.lwjgl.opengl.GL30.glBindVertexArray; +import static org.lwjgl.opengl.GL30.glDeleteVertexArrays; +import static org.lwjgl.opengl.GL30.glGenVertexArrays; +import static org.lwjgl.opengl.GL30.glVertexAttribIPointer; +import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER; +import static org.lwjgl.opengl.GL31.glDrawElementsInstanced; +import static org.lwjgl.opengl.GL33.glVertexAttribDivisor; +import static rs117.hd.overlays.Timer.RENDER_DEBUG_DRAW; + +@Slf4j +@Singleton +public class DebugDrawPass implements RenderPass { + private static final ConcurrentPool POOL = new ConcurrentPool<>(Draw::new); + + private static final int INITIAL_CAPACITY = 256; + + private static final int CUBE_FLOATS = 7; // cx cy cz hx hy hz argb + private static final int SPHERE_FLOATS = 5; // cx cy cz r argb + private static final int LINE_FLOATS = 8; // x1 y1 z1 x2 y2 z2 thickness argb + private static final int TEXT_FLOATS = 7; // wx wy wz scale charCode charIndex argb + + private final ConcurrentLinkedQueue lineQueue = new ConcurrentLinkedQueue<>(); + private final ConcurrentLinkedQueue aabbQueue = new ConcurrentLinkedQueue<>(); + private final ConcurrentLinkedQueue sphereQueue = new ConcurrentLinkedQueue<>(); + private final ConcurrentLinkedQueue textQueue = new ConcurrentLinkedQueue<>(); + + private IntBuffer aabbBufSolid; + private IntBuffer aabbBufWire; + private IntBuffer sphereBufSolid; + private IntBuffer sphereBufWire; + private IntBuffer lineBuf; + private IntBuffer textBuf; + + private PrimitiveDraw cubeDraw; + private PrimitiveDraw sphereDraw; + private PrimitiveDraw lineDraw; + private PrimitiveDraw textDraw; + + @Inject + private HdPlugin plugin; + + @Inject + private FrameTimer frameTimer; + + @Inject + private DebugDrawCubeShaderProgram cubeShader; + + @Inject + private DebugDrawSphereShaderProgram sphereShader; + + @Inject + private DebugDrawLineShaderProgram lineShader; + + @Inject + private DebugDrawTextShaderProgram textShader; + + @Override + public RenderPassType getType() { return RenderPassType.DEBUG_DRAW; } + + @Override + public void initialize() { + try (MemoryStack stack = MemoryStack.stackPush()) { + cubeDraw = new PrimitiveDraw(GLPrimitives.buildCube(stack), "Cube", cubeShader, CUBE_FLOATS); + + glBindVertexArray(cubeDraw.vao); + glBindBuffer(GL_ARRAY_BUFFER, cubeDraw.mesh.getVbo().id); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, false, 3 * Float.BYTES, 0); + + cubeDraw.instanceVbo.bind(); + glEnableVertexAttribArray(1); // center + glVertexAttribPointer(1, 3, GL_FLOAT, false, cubeDraw.stride, 0); + glVertexAttribDivisor(1, 1); + + glEnableVertexAttribArray(2); // halfExtents + glVertexAttribPointer(2, 3, GL_FLOAT, false, cubeDraw.stride, 3 * Float.BYTES); + glVertexAttribDivisor(2, 1); + + glEnableVertexAttribArray(3); // argb + glVertexAttribIPointer(3, 1, GL_INT, cubeDraw.stride, 6 * Float.BYTES); + glVertexAttribDivisor(3, 1); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } + + try (MemoryStack stack = MemoryStack.stackPush()) { + sphereDraw = new PrimitiveDraw(GLPrimitives.buildSphere(stack, 16, 32), "Sphere", sphereShader, SPHERE_FLOATS); + + glBindVertexArray(sphereDraw.vao); + glBindBuffer(GL_ARRAY_BUFFER, sphereDraw.mesh.getVbo().id); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, false, 3 * Float.BYTES, 0); + + sphereDraw.instanceVbo.bind(); + glEnableVertexAttribArray(1); // center + glVertexAttribPointer(1, 3, GL_FLOAT, false, sphereDraw.stride, 0); + glVertexAttribDivisor(1, 1); + + glEnableVertexAttribArray(2); // radius + glVertexAttribPointer(2, 1, GL_FLOAT, false, sphereDraw.stride, 3 * Float.BYTES); + glVertexAttribDivisor(2, 1); + + glEnableVertexAttribArray(3); // argb + glVertexAttribIPointer(3, 1, GL_INT, sphereDraw.stride, 4 * Float.BYTES); + glVertexAttribDivisor(3, 1); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } + + try (MemoryStack stack = MemoryStack.stackPush()) { + lineDraw = new PrimitiveDraw(GLPrimitives.buildLine(stack), "Line", lineShader, LINE_FLOATS); + + glBindVertexArray(lineDraw.vao); + glBindBuffer(GL_ARRAY_BUFFER, lineDraw.mesh.getVbo().id); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, false, 3 * Float.BYTES, 0); + + lineDraw.instanceVbo.bind(); + glEnableVertexAttribArray(1); // start + glVertexAttribPointer(1, 3, GL_FLOAT, false, lineDraw.stride, 0); + glVertexAttribDivisor(1, 1); + + glEnableVertexAttribArray(2); // end + glVertexAttribPointer(2, 3, GL_FLOAT, false, lineDraw.stride, 3 * Float.BYTES); + glVertexAttribDivisor(2, 1); + + glEnableVertexAttribArray(3); // thickness + glVertexAttribPointer(3, 1, GL_FLOAT, false, lineDraw.stride, 6 * Float.BYTES); + glVertexAttribDivisor(3, 1); + + glEnableVertexAttribArray(4); // argb + glVertexAttribIPointer(4, 1, GL_INT, lineDraw.stride, 7 * Float.BYTES); + glVertexAttribDivisor(4, 1); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } + + try (MemoryStack stack = MemoryStack.stackPush()) { + textDraw = new PrimitiveDraw(GLPrimitives.buildQuad(stack), "Text", textShader, TEXT_FLOATS); + + glBindVertexArray(textDraw.vao); + glBindBuffer(GL_ARRAY_BUFFER, textDraw.mesh.getVbo().id); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, false, 3 * Float.BYTES, 0); + + textDraw.instanceVbo.bind(); + glEnableVertexAttribArray(1); // aCenter + glVertexAttribPointer(1, 3, GL_FLOAT, false, textDraw.stride, 0); + glVertexAttribDivisor(1, 1); + + glEnableVertexAttribArray(2); // aScale + glVertexAttribPointer(2, 1, GL_FLOAT, false, textDraw.stride, 3 * Float.BYTES); + glVertexAttribDivisor(2, 1); + + glEnableVertexAttribArray(3); // aCharCode + glVertexAttribIPointer(3, 1, GL_INT, textDraw.stride, 4 * Float.BYTES); + glVertexAttribDivisor(3, 1); + + glEnableVertexAttribArray(4); // aCharIndex + glVertexAttribIPointer(4, 1, GL_INT, textDraw.stride, 5 * Float.BYTES); + glVertexAttribDivisor(4, 1); + + glEnableVertexAttribArray(5); // argb + glVertexAttribIPointer(5, 1, GL_INT, textDraw.stride, 6 * Float.BYTES); + glVertexAttribDivisor(5, 1); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } + + aabbBufSolid = MemoryUtil.memAllocInt(INITIAL_CAPACITY * CUBE_FLOATS); + aabbBufWire = MemoryUtil.memAllocInt(INITIAL_CAPACITY * CUBE_FLOATS); + sphereBufSolid = MemoryUtil.memAllocInt(INITIAL_CAPACITY * SPHERE_FLOATS); + sphereBufWire = MemoryUtil.memAllocInt(INITIAL_CAPACITY * SPHERE_FLOATS); + lineBuf = MemoryUtil.memAllocInt(INITIAL_CAPACITY * LINE_FLOATS); + textBuf = MemoryUtil.memAllocInt(8192 * TEXT_FLOATS); + + DebugDraw.INSTANCE = this; + } + + @Override + public void initializeShaders(ShaderIncludes includes) throws ShaderException, IOException { + cubeShader.compile(includes); + sphereShader.compile(includes); + lineShader.compile(includes); + textShader.compile(includes); + } + + @Override + public void destroyShaders() { + cubeShader.destroy(); + sphereShader.destroy(); + lineShader.destroy(); + textShader.destroy(); + } + + @Override + public void destroy() { + DebugDraw.INSTANCE = null; + + if(cubeDraw != null) + cubeDraw.destroy(); + cubeDraw = null; + + if(sphereDraw != null) + sphereDraw.destroy(); + sphereDraw = null; + + if(lineDraw != null) + lineDraw.destroy(); + lineDraw = null; + + if(textDraw != null) + textDraw.destroy(); + textDraw = null; + + if (aabbBufSolid != null) { MemoryUtil.memFree(aabbBufSolid); aabbBufSolid = null; } + if (aabbBufWire != null) { MemoryUtil.memFree(aabbBufWire); aabbBufWire = null; } + if (sphereBufSolid != null) { MemoryUtil.memFree(sphereBufSolid); sphereBufSolid = null; } + if (sphereBufWire != null) { MemoryUtil.memFree(sphereBufWire); sphereBufWire = null; } + if (lineBuf != null) { MemoryUtil.memFree(lineBuf); lineBuf = null; } + + lineQueue.clear(); + aabbQueue.clear(); + sphereQueue.clear(); + textQueue.clear(); + } + + @Override + public void draw(RenderState renderState) { + if (lineQueue.isEmpty() && aabbQueue.isEmpty() && sphereQueue.isEmpty() && textQueue.isEmpty()) + return; + + frameTimer.begin(RENDER_DEBUG_DRAW); + + renderState.framebuffer.set(GL_DRAW_FRAMEBUFFER, plugin.fboScene); + renderState.viewport.set(0, 0, plugin.sceneResolution[0], plugin.sceneResolution[1]); + renderState.enable.set(GL_DEPTH_TEST); + renderState.depthFunc.set(GL_GEQUAL); + renderState.depthMask.set(false); + renderState.enable.set(GL_BLEND); + renderState.blendFunc.set(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + renderState.apply(); + + aabbBufSolid = ensureCapacity(aabbBufSolid, aabbQueue.size() * CUBE_FLOATS); + aabbBufWire = ensureCapacity(aabbBufWire, aabbQueue.size() * CUBE_FLOATS); + sphereBufSolid = ensureCapacity(sphereBufSolid, sphereQueue.size() * SPHERE_FLOATS); + sphereBufWire = ensureCapacity(sphereBufWire, sphereQueue.size() * SPHERE_FLOATS); + lineBuf = ensureCapacity(lineBuf, lineQueue.size() * LINE_FLOATS); + + aabbBufSolid.clear(); + aabbBufWire.clear(); + sphereBufSolid.clear(); + sphereBufWire.clear(); + lineBuf.clear(); + textBuf.clear(); + + for (Draw d : aabbQueue) + d.writeCube(d.filled ? aabbBufSolid : aabbBufWire); + + for (Draw d : sphereQueue) + d.writeSphere(d.filled ? sphereBufSolid : sphereBufWire); + + for (Draw d : lineQueue) + d.writeLine(lineBuf); + + for (Draw d : textQueue) + d.writeText(textBuf); + + renderState.enable.set(GL_CULL_FACE); + renderState.apply(); + + cubeDraw.uploadAndDraw(aabbBufSolid); + sphereDraw.uploadAndDraw(sphereBufSolid); + textDraw.uploadAndDraw(textBuf); + + renderState.disable.set(GL_CULL_FACE); + renderState.apply(); + + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + + cubeDraw.uploadAndDraw(aabbBufWire); + sphereDraw.uploadAndDraw(sphereBufWire); + + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + lineDraw.uploadAndDraw(lineBuf); + + renderState.disable.set(GL_DEPTH_TEST); + renderState.disable.set(GL_BLEND); + renderState.disable.set(GL_CULL_FACE); + renderState.depthMask.set(true); + renderState.apply(); + + frameTimer.end(RENDER_DEBUG_DRAW); + + expireQueue(lineQueue); + expireQueue(aabbQueue); + expireQueue(sphereQueue); + expireQueue(textQueue); + } + + public Draw pushDraw(PrimitiveDrawType type) { + Draw d = POOL.acquire(); + switch (type) { + case AABB: + aabbQueue.add(d); + break; + case SPHERE: + sphereQueue.add(d); + break; + case LINE: + lineQueue.add(d); + break; + case TEXT: + textQueue.add(d); + break; + default: + throw new RuntimeException("Invalid draw type: " + type); + } + return d; + } + + private void expireQueue(ConcurrentLinkedQueue queue) { + final float delta = plugin.deltaTime; + Iterator it = queue.iterator(); + while (it.hasNext()) { + Draw d = it.next(); + d.duration -= delta; + if (d.duration <= 0f) { + it.remove(); + POOL.recycle(d); + } + } + } + + private static IntBuffer ensureCapacity(IntBuffer buf, int requiredInts) { + if (buf.capacity() >= requiredInts) return buf; + MemoryUtil.memFree(buf); + int cap = Math.max(Integer.highestOneBit(requiredInts - 1) << 1, INITIAL_CAPACITY); + return MemoryUtil.memAllocInt(cap); + } + + public static class Draw { + public float x1, y1, z1; + public float x2, y2, z2; + public float thickness; + public int rgb; + public float duration; + public boolean filled; + public String text; + + private void writeCube(IntBuffer buf) { + buf.put(Float.floatToRawIntBits(x1)) + .put(Float.floatToRawIntBits(y1)) + .put(Float.floatToRawIntBits(z1)) + .put(Float.floatToRawIntBits(x2)) + .put(Float.floatToRawIntBits(y2)) + .put(Float.floatToRawIntBits(z2)) + .put(rgb); + } + + private void writeSphere(IntBuffer buf) { + buf.put(Float.floatToRawIntBits(x1)) + .put(Float.floatToRawIntBits(y1)) + .put(Float.floatToRawIntBits(z1)) + .put(Float.floatToRawIntBits(x2)) + .put(rgb); + } + + private void writeLine(IntBuffer buf) { + buf.put(Float.floatToRawIntBits(x1)) + .put(Float.floatToRawIntBits(y1)) + .put(Float.floatToRawIntBits(z1)) + .put(Float.floatToRawIntBits(x2)) + .put(Float.floatToRawIntBits(y2)) + .put(Float.floatToRawIntBits(z2)) + .put(Float.floatToRawIntBits(thickness)) + .put(rgb); + } + + private void writeText(IntBuffer buf) { + for(int c = 0; c < text.length(); c++) { + buf.put(Float.floatToRawIntBits(x1)) + .put(Float.floatToRawIntBits(y1)) + .put(Float.floatToRawIntBits(z1)) + .put(Float.floatToRawIntBits(x2)) + .put(text.charAt(c)) + .put(c) + .put(rgb); + } + } + } + + public enum PrimitiveDrawType { + AABB, + SPHERE, + LINE, + TEXT + }; + + private static class PrimitiveDraw { + final GLPrimitives.Mesh mesh; + final GLBuffer instanceVbo; + final DebugDrawShaderProgram shader; + final int vao; + final int floatsPerInstance; + final int stride; + + PrimitiveDraw(GLPrimitives.Mesh mesh, String name, DebugDrawShaderProgram shader, int floatsPerInstance) { + this.mesh = mesh; + this.floatsPerInstance = floatsPerInstance; + this.stride = floatsPerInstance * Float.BYTES; + this.instanceVbo = new GLBuffer("VBO::" + name + "::Instances", GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW); + this.instanceVbo.initialize((long) INITIAL_CAPACITY * stride); + this.shader = shader; + this.vao = glGenVertexArrays(); + } + + void uploadAndDraw(IntBuffer data) { + if(data.position() == 0) + return; + data.flip(); + + assert data.limit() % floatsPerInstance == 0; + final int count = data.limit() / floatsPerInstance; + if(count == 0) + return; + + shader.use(); + instanceVbo.upload(data); + glBindVertexArray(vao); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.getEbo().id); + glDrawElementsInstanced(GL_TRIANGLES, mesh.getIndexCount(), GL_UNSIGNED_INT, 0, count); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindVertexArray(0); + } + + void destroy() { + glDeleteVertexArrays(vao); + mesh.destroy(); + instanceVbo.destroy(); + } + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/renderer/zone/passes/DirectionalShadowPass.java b/src/main/java/rs117/hd/renderer/zone/passes/DirectionalShadowPass.java new file mode 100644 index 0000000000..fa33382903 --- /dev/null +++ b/src/main/java/rs117/hd/renderer/zone/passes/DirectionalShadowPass.java @@ -0,0 +1,312 @@ +package rs117.hd.renderer.zone.passes; + +import java.io.IOException; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.*; +import net.runelite.api.hooks.*; +import rs117.hd.HdPlugin; +import rs117.hd.config.ShadowMode; +import rs117.hd.opengl.shader.ShaderException; +import rs117.hd.opengl.shader.ShaderIncludes; +import rs117.hd.opengl.shader.ShadowShaderProgram; +import rs117.hd.opengl.uniforms.UBOGlobal; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; +import rs117.hd.renderer.zone.SceneManager; +import rs117.hd.renderer.zone.WorldViewContext; +import rs117.hd.renderer.zone.Zone; +import rs117.hd.renderer.zone.ZoneRenderer; +import rs117.hd.scene.EnvironmentManager; +import rs117.hd.scene.SceneCullingManager; +import rs117.hd.scene.model_overrides.ModelOverride; +import rs117.hd.utils.Camera; +import rs117.hd.utils.CommandBuffer; +import rs117.hd.utils.RenderState; +import rs117.hd.utils.ShadowCasterVolume; + +import static net.runelite.api.Perspective.*; +import static org.lwjgl.opengl.GL11C.GL_CULL_FACE; +import static org.lwjgl.opengl.GL11C.GL_DEPTH_BUFFER_BIT; +import static org.lwjgl.opengl.GL11C.GL_DEPTH_TEST; +import static org.lwjgl.opengl.GL11C.GL_LEQUAL; +import static org.lwjgl.opengl.GL11C.glClear; +import static org.lwjgl.opengl.GL11C.glClearDepth; +import static org.lwjgl.opengl.GL30C.GL_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30C.glBindVertexArray; +import static rs117.hd.utils.MathUtils.*; + +@Slf4j +@Singleton +public class DirectionalShadowPass implements RenderPass { + + public static final int DIRECTIONAL_CAMERA_ID = ZoneRenderer.CAMERA_COUNT++; + + @Inject + private HdPlugin plugin; + + @Inject + private ZoneRenderer renderer; + + @Inject + private FrameTimer frameTimer; + + @Inject + private SceneManager sceneManager; + + @Inject + private EnvironmentManager environmentManager; + + @Inject + private SceneCullingManager sceneCullingManager; + + @Inject + private ShadowShaderProgram.Fast fastShadowProgram; + + @Inject + private ShadowShaderProgram.Detailed detailedShadowProgram; + + public final Camera directionalCamera = new Camera().setOrthographic(true).setCullingId(DIRECTIONAL_CAMERA_ID); + public final ShadowCasterVolume directionalShadowCasterVolume = new ShadowCasterVolume(directionalCamera); + public final CommandBuffer directionalCmd = new CommandBuffer("Directional"); + + private UBOGlobal uboGlobal; + private Camera sceneCamera; + + private boolean isCameraAddedToCulling; + private boolean shouldDrawRoofShadows; + private boolean shouldClearShadowFbo; + + @Override + public void initialize() { + uboGlobal = plugin.uboGlobal; + sceneCamera = renderer.sceneCamera; + shouldClearShadowFbo = true; + + directionalCmd.setFrameTimer(frameTimer); + } + + @Override + public void initializeShaders(ShaderIncludes includes) throws ShaderException, IOException { + fastShadowProgram.compile(includes); + detailedShadowProgram.compile(includes); + } + + @Override + public void destroyShaders() { + fastShadowProgram.destroy(); + detailedShadowProgram.destroy(); + } + + @Override + public void preSceneDraw(WorldViewContext ctx, boolean isTopLevel) { + if(!isTopLevel) + return; + + directionalCamera.setPitch(environmentManager.currentSunAngles[0]); + directionalCamera.setYaw(PI - environmentManager.currentSunAngles[1]); + uboGlobal.lightDir.set(directionalCamera.getForwardDirection()); + + if(!plugin.configShadowsEnabled || sceneCamera.isOrthographic()) { + if(isCameraAddedToCulling) + sceneCullingManager.removeCamera(directionalCamera); + isCameraAddedToCulling = false; + return; + } + + if(!isCameraAddedToCulling) + sceneCullingManager.addCamera(directionalCamera); + isCameraAddedToCulling = true; + + float drawDistance = (float) plugin.getDrawDistance(); + int shadowDrawDistance = 90 * LOCAL_TILE_SIZE; + + final float[][] volumeCorners = directionalShadowCasterVolume + .build(sceneCamera, drawDistance * LOCAL_TILE_SIZE, shadowDrawDistance); + + final float[] sceneCenter = new float[3]; + for (float[] corner : volumeCorners) + add(sceneCenter, sceneCenter, corner); + divide(sceneCenter, sceneCenter, (float) volumeCorners.length); + + // Reset position before transforming points + directionalCamera.setPosition(0, 0, 0); + + float minX = Float.POSITIVE_INFINITY, maxX = Float.NEGATIVE_INFINITY; + float minY = Float.POSITIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY; + float minZ = Float.POSITIVE_INFINITY, maxZ = Float.NEGATIVE_INFINITY; + float radius = 0f; + for (float[] corner : volumeCorners) { + radius = max(radius, distance(sceneCenter, corner)); + + directionalCamera.transformPoint(corner, corner); + + minX = min(minX, corner[0]); + maxX = max(maxX, corner[0]); + + minY = min(minY, corner[1]); + maxY = max(maxY, corner[1]); + + minZ = min(minZ, corner[2]); + maxZ = max(maxZ, corner[2]); + } + + // Offset the Directional Camera by the radius of the scene + float[] directionalFwd = directionalCamera.getForwardDirection(); + multiply(directionalFwd, directionalFwd, radius); + add(sceneCenter, sceneCenter, directionalFwd); + + // Calculate directional size from the AABB of the scene frustum corners + // Then snap to the nearest multiple of `LOCAL_HALF_TILE_SIZE` to prevent shimmering + int directionalSize = (int) max(abs(maxY - minY), abs(maxX - minX), abs(maxZ - minZ)); + directionalSize = Math.round(directionalSize / (float) LOCAL_HALF_TILE_SIZE) * LOCAL_HALF_TILE_SIZE; + directionalSize = max(8000, directionalSize); // Clamp the size to prevent going too small at reduced draw distances + + // Ignore directional size changes below the change threshold to avoid inducing shimmering + int previousDirectionalSize = directionalCamera.getViewportWidth(); + float changeThreshold = previousDirectionalSize * 0.05f; // 10% of the previous directional size + if (abs(directionalSize - previousDirectionalSize) < changeThreshold) + directionalSize = previousDirectionalSize; + + // Snap Position to Shadow Texel Grid to prevent shimmering + directionalCamera.transformPoint(sceneCenter, sceneCenter); + + float texelSize = (float) directionalSize / plugin.shadowMapResolution; + sceneCenter[0] = (float) floor(sceneCenter[0] / texelSize + 0.5f) * texelSize; + sceneCenter[1] = (float) floor(sceneCenter[1] / texelSize + 0.5f) * texelSize; + + directionalCamera.setPosition(directionalCamera.inverseTransformPoint(sceneCenter, sceneCenter)); + directionalCamera.setNearPlane(Math.max(0.1f, radius * 0.05f)); + directionalCamera.setFarPlane(radius * 2.0f); + directionalCamera.setZoom(1.0f); + directionalCamera.setViewportWidth(directionalSize); + directionalCamera.setViewportHeight(directionalSize); + + uboGlobal.lightProjectionMatrix.set(directionalCamera.getViewProjMatrix()); + uboGlobal.upload(); + + shouldDrawRoofShadows = + plugin.configShadowsEnabled && + plugin.configRoofShadows && + environmentManager.allowRoofShadows(); + + directionalCmd.reset(); + } + + @Override + public boolean zoneInFrustum(Zone z, int zx, int zz, int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + if(!plugin.configShadowsEnabled) + return false; + + if(z.isVisible(sceneCamera)) + return true; + + boolean isVisible = z.isVisible(directionalCamera); + if (z.isVisible(directionalCamera) && plugin.configExpandShadowDraw) { + int centerX = minX + (maxX - minX) / 2; + int centerY = minY + (maxY - minY) / 2; + int centerZ = minZ + (maxZ - minZ) / 2; + isVisible = directionalShadowCasterVolume.intersectsPoint(centerX, centerY, centerZ); + } + + return z.setVisibility(directionalCamera, isVisible); + } + + @Override + public void drawZoneOpaque(WorldViewContext ctx, Zone z, int zx, int zz) { + if(!plugin.configShadowsEnabled) + return; + + if(sceneManager.isRoot(ctx) && !z.isVisible(directionalCamera)) + return; + + final boolean isSquashed = ctx.uboWorldViewStruct != null && ctx.uboWorldViewStruct.isSquashed(); + if (!isSquashed) { + directionalCmd.SetShader(fastShadowProgram); + z.renderOpaque(directionalCmd, ctx, directionalCamera, shouldDrawRoofShadows); + } + } + + @Override + public void drawZoneAlpha(WorldViewContext ctx, Zone z, int level, int zx, int zz) { + if(!plugin.configShadowsEnabled) + return; + + if(sceneManager.isRoot(ctx) && !z.isVisible(directionalCamera)) + return; + + if (z.sizeA == 0 || z.alphaModels.isEmpty()) + return; + + final int offset = ctx.sceneContext.sceneOffset >> 3; + final boolean isSquashed = ctx.uboWorldViewStruct != null && ctx.uboWorldViewStruct.isSquashed(); + if (!isSquashed) { + directionalCmd.SetShader(plugin.configShadowMode == ShadowMode.DETAILED ? detailedShadowProgram : fastShadowProgram); + z.renderAlpha(directionalCmd, zx - offset, zz - offset, level, ctx, directionalCamera, true, shouldDrawRoofShadows); + } + } + + @Override + public boolean dynamicInFrustum(WorldViewContext ctx, Renderable renderable, Model model, ModelOverride modelOverride, int x, int y, int z) { + if(!plugin.configShadowsEnabled || !modelOverride.castShadows) + return false; + + return directionalShadowCasterVolume.intersectsPoint(x, y, z); + } + + @Override + public void drawPass(WorldViewContext ctx, int pass) { + if(!plugin.configShadowsEnabled) + return; + + if(pass == DrawCallbacks.PASS_OPAQUE) { + directionalCmd.SetShader(fastShadowProgram); + directionalCmd.ExecuteSubCommandBuffer(ctx.vaoDirectionalCmd); + } + } + + public void draw(RenderState renderState) { + if(plugin.fboShadowMap == 0 || plugin.shadowMapResolution == 0) + return; + + final boolean shouldRenderShadows = + plugin.configShadowsEnabled && + environmentManager.currentDirectionalStrength > 0; + + if (shouldRenderShadows || shouldClearShadowFbo) { + // Render to the shadow depth map + renderState.framebuffer.set(GL_FRAMEBUFFER, plugin.fboShadowMap); + renderState.viewport.set(0, 0, plugin.shadowMapResolution, plugin.shadowMapResolution); + renderState.apply(); + + glClearDepth(1); + glClear(GL_DEPTH_BUFFER_BIT); + shouldClearShadowFbo = false; + } + + if (!shouldRenderShadows) + return; + + frameTimer.begin(Timer.RENDER_SHADOWS); + + renderState.enable.set(GL_DEPTH_TEST); + renderState.disable.set(GL_CULL_FACE); + renderState.depthFunc.set(GL_LEQUAL); + renderState.ido.set(renderer.indirectDrawCmds.id); + + CommandBuffer.SKIP_DEPTH_MASKING = true; + directionalCmd.execute(renderState); + CommandBuffer.SKIP_DEPTH_MASKING = false; + + glBindVertexArray(0); + + renderState.disable.set(GL_DEPTH_TEST); + + shouldClearShadowFbo = true; + frameTimer.end(Timer.RENDER_SHADOWS); + } + + @Override + public RenderPassType getType() { return RenderPassType.DIRECTIONAL; } +} diff --git a/src/main/java/rs117/hd/renderer/zone/passes/RenderPass.java b/src/main/java/rs117/hd/renderer/zone/passes/RenderPass.java new file mode 100644 index 0000000000..3581d47e23 --- /dev/null +++ b/src/main/java/rs117/hd/renderer/zone/passes/RenderPass.java @@ -0,0 +1,52 @@ +package rs117.hd.renderer.zone.passes; + +import java.io.IOException; +import java.util.Comparator; +import java.util.Set; +import net.runelite.api.*; +import rs117.hd.opengl.shader.ShaderException; +import rs117.hd.opengl.shader.ShaderIncludes; +import rs117.hd.renderer.zone.WorldViewContext; +import rs117.hd.renderer.zone.Zone; +import rs117.hd.scene.model_overrides.ModelOverride; +import rs117.hd.utils.RenderState; + +public interface RenderPass { + RenderPassType[] TYPES = RenderPassType.values(); + + RenderPassType getType(); + + default void initialize() {} + + default void initializeShaders(ShaderIncludes includes) throws ShaderException, IOException {} + + default void destroyShaders() {} + + default void destroy() {} + + default void addShaderIncludes(ShaderIncludes includes) {} + + default void processConfigChanges(Set keys) {} + + default boolean zoneInFrustum(Zone z, int zx, int zz, int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + return false; + } + + default boolean dynamicInFrustum(WorldViewContext ctx, Renderable renderable, Model model, ModelOverride modelOverride, int x, int y, int z) { + return false; + } + + default void drawZoneOpaque(WorldViewContext ctx, Zone z, int zx, int zz) {} + + default void drawZoneAlpha(WorldViewContext ctx, Zone z, int level, int zx, int zz) {} + + default void drawPass(WorldViewContext ctx, int pass) {} + + default void preSceneDraw(WorldViewContext ctx, boolean isTopLevel) {} + + default void postSceneDraw(WorldViewContext ctx) {} + + default void draw(RenderState renderState) {} + + default void postDraw(RenderState renderState) {} +} diff --git a/src/main/java/rs117/hd/renderer/zone/passes/RenderPassType.java b/src/main/java/rs117/hd/renderer/zone/passes/RenderPassType.java new file mode 100644 index 0000000000..3b2d5bf95b --- /dev/null +++ b/src/main/java/rs117/hd/renderer/zone/passes/RenderPassType.java @@ -0,0 +1,20 @@ +package rs117.hd.renderer.zone.passes; + +import rs117.hd.overlays.Timer; + +public enum RenderPassType { + TILED_LIGHTING(TiledLightingPass.class, Timer.TILED_LIGHTING_PASS), + DIRECTIONAL(DirectionalShadowPass.class, Timer.DIRECTIONAL_PASS), + SCENE(ScenePass.class, Timer.SCENE_PASS), + DEBUG_DRAW(DebugDrawPass.class, Timer.DEBUG_DRAW_PASS); + + public final Class clazz; + public final String name; + public final Timer timer; + + RenderPassType(Class clazz, Timer timer) { + this.clazz = clazz; + this.timer = timer; + this.name = clazz.getSimpleName(); + } +} diff --git a/src/main/java/rs117/hd/renderer/zone/passes/RenderPipeline.java b/src/main/java/rs117/hd/renderer/zone/passes/RenderPipeline.java new file mode 100644 index 0000000000..f1f57c8eea --- /dev/null +++ b/src/main/java/rs117/hd/renderer/zone/passes/RenderPipeline.java @@ -0,0 +1,390 @@ +package rs117.hd.renderer.zone.passes; + +import com.google.inject.Injector; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Set; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.*; +import rs117.hd.HdPlugin; +import rs117.hd.opengl.shader.ShaderIncludes; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.renderer.zone.WorldViewContext; +import rs117.hd.renderer.zone.Zone; +import rs117.hd.scene.model_overrides.ModelOverride; +import rs117.hd.utils.RenderState; + +import static rs117.hd.HdPlugin.checkGLErrors; +import static rs117.hd.utils.collections.Util.quickSort; + +@Slf4j +@Singleton +public final class RenderPipeline { + + @Inject + private Injector injector; + + @Inject + private HdPlugin plugin; + + @Inject + private FrameTimer frameTimer; + + public final InitializeFunction initialize = new InitializeFunction(); + public final InitializeShadersFunction initializeShaders = new InitializeShadersFunction(); + public final DestroyFunction destroy = new DestroyFunction(); + public final DestroyShadersFunction destroyShaders = new DestroyShadersFunction(); + public final AddShaderIncludesFunction addShaderIncludes = new AddShaderIncludesFunction(); + public final ProcessConfigChangesFunction processConfigChanges = new ProcessConfigChangesFunction(); + public final PreSceneDrawFunction preSceneDraw = new PreSceneDrawFunction(); + public final PostSceneDrawFunction postSceneDraw = new PostSceneDrawFunction(); + public final ZoneInFrustumFunction zoneInFrustum = new ZoneInFrustumFunction(); + public final DynamicInFrustumFunction dynamicInFrustum = new DynamicInFrustumFunction(); + public final DrawZoneOpaqueFunction drawZoneOpaque = new DrawZoneOpaqueFunction(); + public final DrawZoneAlphaFunction drawZoneAlpha = new DrawZoneAlphaFunction(); + public final DrawPassFunction drawPass = new DrawPassFunction(); + public final DrawFunction draw = new DrawFunction(); + public final PostDrawFunction postDraw = new PostDrawFunction(); + + private final int passCount = RenderPass.TYPES.length; + private final RenderPassType[] types = new RenderPassType[passCount]; + private final RenderPass[] passes = new RenderPass[passCount]; + + public void initialize() { + for(int i = 0; i < passCount; i++) + passes[i] = injector.getInstance(RenderPass.TYPES[i].clazz); + quickSort(passes, Comparator.comparingInt((A) -> A.getType().ordinal())); + + for(int i = 0; i < passCount; i++) + types[i] = passes[i].getType(); + + initialize.execute(); + } + + public void destroy() { + destroy.execute(); + Arrays.fill(passes, null); + } + + private boolean internalExecute(BaseRenderPassFunction function) { + final boolean detailedTimers = plugin.enableDetailedTimers; + boolean result = false; + + for (int i = 0; i < passCount; i++) { + final RenderPass renderPass = passes[i]; + final RenderPassType type = types[i]; + + if (renderPass == null || type == null) + continue; + + try { + if(detailedTimers) + frameTimer.begin(type.timer); + + result |= function.consumer.accept(renderPass); + } catch (Throwable e) { + log.error("Error during {} for render pass {}:", function.action, type.name, e); + if (!function.handleExceptions) + throw new RuntimeException(e); + plugin.requestPluginStop(); + } finally { + if(detailedTimers) + frameTimer.end(type.timer); + + if (function.checkGL) + checkGLErrors(() -> type.name + "::" + function.action); + } + } + return result; + } + + public final class InitializeFunction extends BaseRenderPassFunction { + private InitializeFunction() { + super("initializeShaders", false, false); + consumer = (renderPass) -> { + renderPass.initialize(); + return true; + }; + } + } + + public final class InitializeShadersFunction extends BaseRenderPassFunction { + + private ShaderIncludes includes; + + private InitializeShadersFunction() { + super("initializeShaders", false, false); + consumer = renderPass -> { + renderPass.initializeShaders(includes); + return true; + }; + } + + public void execute(ShaderIncludes includes) { + this.includes = includes; + execute(); + } + } + + public final class DestroyFunction extends BaseRenderPassFunction { + private DestroyFunction() { + super("destroy", false, true); + consumer = renderPass -> { + renderPass.destroy(); + return true; + }; + } + } + + public final class DestroyShadersFunction extends BaseRenderPassFunction { + private DestroyShadersFunction() { + super("destroyShaders", false, true); + consumer = renderPass -> { + renderPass.destroyShaders(); + return true; + }; + } + } + + public final class AddShaderIncludesFunction extends BaseRenderPassFunction { + + private ShaderIncludes includes; + + private AddShaderIncludesFunction() { + super("addShaderIncludes", false, false); + consumer = renderPass -> { + renderPass.addShaderIncludes(includes); + return true; + }; + } + + public void execute(ShaderIncludes includes) { + this.includes = includes; + execute(); + } + } + + public final class ProcessConfigChangesFunction extends BaseRenderPassFunction { + private Set keys; + + private ProcessConfigChangesFunction() { + super("processConfigChanges", false, false); + consumer = renderPass -> { + renderPass.processConfigChanges(keys); + return true; + }; + } + + public void execute(Set keys) { + this.keys = keys; + execute(); + } + } + + public final class PreSceneDrawFunction extends BaseRenderPassFunction { + + private WorldViewContext ctx; + private boolean isTopLevel; + + private PreSceneDrawFunction() { + super("preSceneDraw", true, false); + consumer = renderPass -> { + renderPass.preSceneDraw(ctx, isTopLevel); + return true; + }; + } + + public void execute(WorldViewContext ctx, boolean isTopLevel) { + this.ctx = ctx; + this.isTopLevel = isTopLevel; + execute(); + } + } + + public final class PostSceneDrawFunction extends BaseRenderPassFunction { + + private WorldViewContext ctx; + + private PostSceneDrawFunction() { + super("postSceneDraw", true, false); + consumer = renderPass -> { + renderPass.postSceneDraw(ctx); + return true; + }; + } + + public void execute(WorldViewContext ctx) { + this.ctx = ctx; + execute(); + } + } + + public final class ZoneInFrustumFunction extends BaseRenderPassFunction { + + private Zone zone; + private int zx, zz, minX, minY, minZ, maxX, maxY, maxZ; + + private ZoneInFrustumFunction() { + super("zoneInFrustum", false, true); + consumer = renderPass -> renderPass.zoneInFrustum(zone, zx, zz, minX, minY, minZ, maxX, maxY, maxZ); + } + + public boolean execute(Zone zone, int zx, int zz, int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + this.zone = zone; + this.zx = zx; + this.zz = zz; + this.minX = minX; + this.minY = minY; + this.minZ = minZ; + this.maxX = maxX; + this.maxY = maxY; + this.maxZ = maxZ; + return execute(); + } + } + + public final class DynamicInFrustumFunction extends BaseRenderPassFunction { + + private WorldViewContext ctx; + private Renderable renderable; + private Model model; + private ModelOverride modelOverride; + private int x, y, z; + + private DynamicInFrustumFunction() { + super("dynamicInFrustum", false, true); + consumer = renderPass -> renderPass.dynamicInFrustum(ctx, renderable, model, modelOverride, x, y, z); + } + + public boolean execute(WorldViewContext ctx, Renderable renderable, Model model, ModelOverride modelOverride, int x, int y, int z) { + this.ctx = ctx; + this.renderable = renderable; + this.model = model; + this.modelOverride = modelOverride; + this.x = x; + this.y = y; + this.z = z; + return execute(); + } + } + + public final class DrawZoneOpaqueFunction extends BaseRenderPassFunction { + private WorldViewContext ctx; + private Zone zone; + private int zx, zz; + + private DrawZoneOpaqueFunction() { + super("drawZoneOpaque", false, false); + consumer = renderPass -> { + renderPass.drawZoneOpaque(ctx, zone, zx, zz); + return true; + }; + } + + public void execute(WorldViewContext ctx, Zone zone, int zx, int zz) { + this.ctx = ctx; + this.zone = zone; + this.zx = zx; + this.zz = zz; + execute(); + } + } + + public final class DrawZoneAlphaFunction extends BaseRenderPassFunction { + private WorldViewContext ctx; + private Zone zone; + private int level, zx, zz; + + private DrawZoneAlphaFunction() { + super("drawZoneAlpha", false, false); + consumer = (renderPass) -> { + renderPass.drawZoneAlpha(ctx, zone, level, zx, zz); + return true; + }; + } + + public void execute(WorldViewContext ctx, Zone zone, int level, int zx, int zz) { + this.ctx = ctx; + this.zone = zone; + this.level = level; + this.zx = zx; + this.zz = zz; + execute(); + } + } + + public final class DrawPassFunction extends BaseRenderPassFunction { + + private WorldViewContext ctx; + private int pass; + + private DrawPassFunction() { + super("drawPass", true, false); + consumer = (renderPass) -> { + renderPass.drawPass(ctx, pass); + return true; + }; + } + + public void execute(WorldViewContext ctx, int pass) { + this.ctx = ctx; + this.pass = pass; + execute(); + } + } + + public final class DrawFunction extends BaseRenderPassFunction { + + private RenderState renderState; + private DrawFunction() { + super("draw", true, false); + + consumer = (renderPass) -> { + renderPass.draw(renderState); + return true; + }; + } + + public void execute(RenderState renderState) { + this.renderState = renderState; + execute(); + } + } + + public final class PostDrawFunction extends BaseRenderPassFunction { + private RenderState renderState; + + private PostDrawFunction() { + super("postDraw", true, false); + consumer = (renderPass) -> { + renderPass.postDraw(renderState); + return true; + }; + } + + public void execute(RenderState renderState) { + this.renderState = renderState; + execute(); + } + } + + @FunctionalInterface + private interface RenderPassConsumer { + boolean accept(RenderPass renderPass) throws Throwable; + } + + @RequiredArgsConstructor + public abstract class BaseRenderPassFunction { + private final String action; + private final boolean checkGL; + private final boolean handleExceptions; + protected RenderPassConsumer consumer; + + public boolean execute() { + return internalExecute(this); + } + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/renderer/zone/passes/ScenePass.java b/src/main/java/rs117/hd/renderer/zone/passes/ScenePass.java new file mode 100644 index 0000000000..140e0b80fe --- /dev/null +++ b/src/main/java/rs117/hd/renderer/zone/passes/ScenePass.java @@ -0,0 +1,234 @@ +package rs117.hd.renderer.zone.passes; + +import java.io.IOException; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.*; +import net.runelite.api.hooks.*; +import rs117.hd.HdPlugin; +import rs117.hd.opengl.shader.SceneShaderProgram; +import rs117.hd.opengl.shader.ShaderException; +import rs117.hd.opengl.shader.ShaderIncludes; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; +import rs117.hd.renderer.zone.ModelStreamingManager; +import rs117.hd.renderer.zone.SceneManager; +import rs117.hd.renderer.zone.WorldViewContext; +import rs117.hd.renderer.zone.Zone; +import rs117.hd.renderer.zone.ZoneRenderer; +import rs117.hd.scene.EnvironmentManager; +import rs117.hd.scene.SceneCullingManager; +import rs117.hd.scene.model_overrides.ModelOverride; +import rs117.hd.utils.Camera; +import rs117.hd.utils.ColorUtils; +import rs117.hd.utils.CommandBuffer; +import rs117.hd.utils.RenderState; + +import static net.runelite.api.Perspective.*; +import static org.lwjgl.opengl.GL11C.GL_BLEND; +import static org.lwjgl.opengl.GL11C.GL_COLOR_BUFFER_BIT; +import static org.lwjgl.opengl.GL11C.GL_CULL_FACE; +import static org.lwjgl.opengl.GL11C.GL_DEPTH_BUFFER_BIT; +import static org.lwjgl.opengl.GL11C.GL_DEPTH_TEST; +import static org.lwjgl.opengl.GL11C.GL_GEQUAL; +import static org.lwjgl.opengl.GL11C.GL_ONE; +import static org.lwjgl.opengl.GL11C.GL_ONE_MINUS_SRC_ALPHA; +import static org.lwjgl.opengl.GL11C.GL_SRC_ALPHA; +import static org.lwjgl.opengl.GL11C.GL_ZERO; +import static org.lwjgl.opengl.GL11C.glClear; +import static org.lwjgl.opengl.GL11C.glClearColor; +import static org.lwjgl.opengl.GL11C.glClearDepth; +import static org.lwjgl.opengl.GL13C.GL_MULTISAMPLE; +import static org.lwjgl.opengl.GL30C.GL_DRAW_FRAMEBUFFER; +import static org.lwjgl.opengl.GL30C.glBindVertexArray; +import static rs117.hd.renderer.zone.WorldViewContext.VAO_PRESCENE; +import static rs117.hd.utils.MathUtils.*; + +@Slf4j +@Singleton +public class ScenePass implements RenderPass { + + public static final int ZONE_VISIBILITY_PADDING = 4 * LOCAL_TILE_SIZE; + + @Inject + private HdPlugin plugin; + + @Inject + private ZoneRenderer renderer; + + @Inject + private SceneManager sceneManager; + + @Inject + private FrameTimer frameTimer; + + @Inject + private EnvironmentManager environmentManager; + + @Inject + private SceneCullingManager sceneCullingManager; + + @Inject + private ModelStreamingManager modelStreamingManager; + + @Inject + private SceneShaderProgram sceneProgram; + + public final CommandBuffer sceneCmd = new CommandBuffer("Scene"); + public final CommandBuffer gapFillerCmd = new CommandBuffer("GapFiller"); + + private Camera sceneCamera; + + @Override + public void initialize() { + sceneCamera = renderer.sceneCamera; + + sceneCullingManager.addCamera(sceneCamera); + + sceneCmd.setFrameTimer(frameTimer); + gapFillerCmd.setFrameTimer(frameTimer); + } + + @Override + public void initializeShaders(ShaderIncludes includes) throws ShaderException, IOException { + sceneProgram.compile(includes); + } + + @Override + public void destroy() { + sceneCullingManager.removeCamera(sceneCamera); + } + + @Override + public void destroyShaders() { + sceneProgram.destroy(); + } + + @Override + public void preSceneDraw(WorldViewContext ctx, boolean isTopLevel) { + final Scene scene = ctx.sceneContext.scene; + if(scene.getWorldViewId() != WorldView.TOPLEVEL) + return; + + gapFillerCmd.reset(); + sceneCmd.reset(); + + Model skybox = scene.getSkybox(); + if (skybox != null) { + skybox.calculateBoundsCylinder(); + modelStreamingManager.uploadTempModel( + ctx, + sceneCamera, + null, + skybox, + ModelOverride.UNLIT, + skybox, + null, + null, + true, + VAO_PRESCENE, + -1, + 0, + sceneCamera.getPositionX(), sceneCamera.getPositionY(), sceneCamera.getPositionZ() + ); + } + + sceneCmd.DepthMask(false); + ctx.drawAll(VAO_PRESCENE, sceneCmd); + sceneCmd.DepthMask(true); + } + + @Override + public void drawZoneOpaque(WorldViewContext ctx, Zone z, int zx, int zz) { + if (sceneManager.isRoot(ctx) && !z.isVisible(sceneCamera)) + return; + + z.renderOpaque(sceneCmd, ctx, sceneCamera, false); + + if (z.hasGapFiller) + z.renderOpaqueLevel(gapFillerCmd, Zone.LEVEL_GAP_FILLER); + } + + @Override + public void drawZoneAlpha(WorldViewContext ctx, Zone z, int level, int zx, int zz) { + if (sceneManager.isRoot(ctx) && !z.isVisible(sceneCamera)) + return; + + if (level == 0 && z.hasWater) + z.renderOpaqueLevel(sceneCmd, Zone.LEVEL_WATER_SURFACE); + + if (z.sizeA != 0 || !z.alphaModels.isEmpty()) { + final int offset = ctx.sceneContext.sceneOffset >> 3; + z.renderAlpha(sceneCmd, zx - offset, zz - offset, level, ctx, sceneCamera, false, false); + } + } + + @Override + public void drawPass(WorldViewContext ctx, int pass) { + if(pass == DrawCallbacks.PASS_OPAQUE) + sceneCmd.ExecuteSubCommandBuffer(ctx.vaoSceneCmd); + } + + @Override + public void draw(RenderState renderState) { + sceneProgram.use(); + + frameTimer.begin(Timer.DRAW_SCENE); + 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(renderer.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); + + if (!gapFillerCmd.isEmpty()) { + renderState.depthMask.set(false); + gapFillerCmd.execute(renderState); + renderState.depthMask.set(true); + } + + sceneCmd.execute(renderState); + + frameTimer.end(Timer.RENDER_SCENE); + + glBindVertexArray(0); + + // Done rendering the scene + renderState.disable.set(GL_BLEND); + renderState.disable.set(GL_CULL_FACE); + renderState.disable.set(GL_DEPTH_TEST); + renderState.apply(); + + frameTimer.end(Timer.DRAW_SCENE); + } + + @Override + public RenderPassType getType() { return RenderPassType.SCENE; } +} diff --git a/src/main/java/rs117/hd/renderer/zone/passes/TiledLightingPass.java b/src/main/java/rs117/hd/renderer/zone/passes/TiledLightingPass.java new file mode 100644 index 0000000000..a37b73fe29 --- /dev/null +++ b/src/main/java/rs117/hd/renderer/zone/passes/TiledLightingPass.java @@ -0,0 +1,63 @@ +package rs117.hd.renderer.zone.passes; + +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import rs117.hd.HdPlugin; +import rs117.hd.config.DynamicLights; +import rs117.hd.overlays.FrameTimer; +import rs117.hd.overlays.Timer; +import rs117.hd.utils.RenderState; + +import static org.lwjgl.opengl.GL11C.GL_NONE; +import static org.lwjgl.opengl.GL11C.GL_TRIANGLES; +import static org.lwjgl.opengl.GL11C.glDrawArrays; +import static org.lwjgl.opengl.GL30C.GL_COLOR_ATTACHMENT0; +import static org.lwjgl.opengl.GL30C.GL_FRAMEBUFFER; + +@Slf4j +@Singleton +public class TiledLightingPass implements RenderPass { + + @Inject + private HdPlugin plugin; + + @Inject + private FrameTimer frameTimer; + + @Override + public void draw(RenderState renderState) { + if (!plugin.configTiledLighting || plugin.configDynamicLights == DynamicLights.NONE) + return; + + plugin.updateTiledLightingFbo(); + assert plugin.fboTiledLighting != 0; + + frameTimer.begin(Timer.RENDER_TILED_LIGHTING); + + renderState.framebuffer.set(GL_FRAMEBUFFER, plugin.fboTiledLighting); + renderState.viewport.set(0, 0, plugin.tiledLightingResolution[0], plugin.tiledLightingResolution[1]); + renderState.vao.setVao(plugin.vaoTri); + + if (plugin.tiledLightingImageStoreProgram.isValid()) { + renderState.program.set(plugin.tiledLightingImageStoreProgram); + renderState.drawBuffer.set(GL_NONE); + renderState.apply(); + glDrawArrays(GL_TRIANGLES, 0, 3); + } else { + renderState.drawBuffer.set(GL_COLOR_ATTACHMENT0); + int layerCount = plugin.configDynamicLights.getTiledLightingLayers(); + for (int layer = 0; layer < layerCount; layer++) { + renderState.program.set(plugin.tiledLightingShaderPrograms.get(layer)); + renderState.framebufferTextureLayer.set(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, plugin.texTiledLighting, 0, layer); + renderState.apply(); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + } + + frameTimer.end(Timer.RENDER_TILED_LIGHTING); + } + + @Override + public RenderPassType getType() { return RenderPassType.TILED_LIGHTING; } +} diff --git a/src/main/java/rs117/hd/scene/SceneCullingManager.java b/src/main/java/rs117/hd/scene/SceneCullingManager.java new file mode 100644 index 0000000000..9a1437a670 --- /dev/null +++ b/src/main/java/rs117/hd/scene/SceneCullingManager.java @@ -0,0 +1,332 @@ +package rs117.hd.scene; + +import com.google.inject.Inject; +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import javax.inject.Singleton; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.*; +import rs117.hd.HdPlugin; +import rs117.hd.utils.Camera; +import rs117.hd.utils.DebugDraw; +import rs117.hd.utils.collections.ConcurrentPool; +import rs117.hd.utils.jobs.Job; + +import static rs117.hd.utils.MathUtils.*; + +@Singleton +@Slf4j +public class SceneCullingManager { + private static final int SCRATCH_SIZE = 32; + + private final ConcurrentPool CULLING_JOB_POOL = new ConcurrentPool<>(CullingJob::new); + private final ConcurrentPool CULLING_SPHERE_POOL = new ConcurrentPool<>(CullingSphere::new); + private final ConcurrentPool CULLING_AABB_POOL = new ConcurrentPool<>(CullingAABB::new); + + private final List pendingCullingResults = new ArrayList<>(); + private final List cullingCameras = new ArrayList<>(); + private final float[] debugProjected = new float[4]; + private final float[] debugScratch = new float[SCRATCH_SIZE]; + + @Inject + private Client client; + + public void addCamera(Camera camera) { + assert cullingCameras.size() < 8 : "SceneCullingManager supports at most 8 cameras"; + if(!cullingCameras.contains(camera)) + cullingCameras.add(camera); + } + + public void removeCamera(Camera camera) { + cullingCameras.remove(camera); + } + + public synchronized CullingSphere obtainSphere(float x, float y, float z, float radius) { + final CullingSphere result = CULLING_SPHERE_POOL.acquire(); + result.x = x; + result.y = y; + result.z = z; + result.radius = radius; + + return result; + } + + public synchronized CullingAABB obtainBox( + float minX, + float minY, + float minZ, + float maxX, + float maxY, + float maxZ + ) { + if (minX > maxX) { + float t = minX; + minX = maxX; + maxX = t; + } + + if (minY > maxY) { + float t = minY; + minY = maxY; + maxY = t; + } + + if (minZ > maxZ) { + float t = minZ; + minZ = maxZ; + maxZ = t; + } + + final CullingAABB result = CULLING_AABB_POOL.acquire(); + + result.x = (minX + maxX) * 0.5f; + result.y = (minY + maxY) * 0.5f; + result.z = (minZ + maxZ) * 0.5f; + + result.extentsX = (maxX - minX) * 0.5f; + result.extentsY = (maxY - minY) * 0.5f; + result.extentsZ = (maxZ - minZ) * 0.5f; + + return result; + } + + public void flush() { + if(pendingCullingResults.isEmpty() || cullingCameras.isEmpty()) + return; + + CullingJob job = CULLING_JOB_POOL.acquire(); + job.id++; + job.cullingCameras.addAll(cullingCameras); + + for (int i = 0; i < pendingCullingResults.size(); i++) { + final CullingResult result = pendingCullingResults.get(i); + result.job = job; + job.pendingCullingResults.add(result); + } + pendingCullingResults.clear(); + + job.queue(); + } + + public void debugDraw(CullingResult... results) { + for(int i = 0; i < results.length; i++) + debugDraw(results[i]); + } + + public void debugDraw(CullingResult result) { + if(result == null) + return; + + result.build(debugProjected, debugScratch); + result.debugDraw(debugScratch); + } + + public class CullingJob extends Job { + private final List pendingCullingResults = new ArrayList<>(); + private final List cullingCameras = new ArrayList<>(); + private final float[] projected = new float[4]; + private final float[] scratch = new float[SCRATCH_SIZE]; + private int id; + + @Override + protected void onRun() { + for (int i = 0; i < pendingCullingResults.size(); i++) { + CullingResult result = pendingCullingResults.get(i); + if (result == null) + continue; + + result.build(projected, scratch); + + byte newFlags = 0; + for (int camIdx = 0; camIdx < cullingCameras.size(); camIdx++) { + if (result.test(cullingCameras.get(camIdx), scratch)) + newFlags |= (byte) (1 << camIdx); + } + + result.visibilityFlags = newFlags; + } + + pendingCullingResults.clear(); + cullingCameras.clear(); + + CULLING_JOB_POOL.recycle(this); + } + } + + public class CullingAABB extends CullingResult { + public float extentsX; + public float extentsY; + public float extentsZ; + + @Override + protected void build(float[] p, float[] scratch) { + float worldX = offsetX + x; + float worldY = offsetY + y; + float worldZ = offsetZ + z; + + if (projection == null) { + scratch[0] = worldX - extentsX; + scratch[1] = worldY - extentsY; + scratch[2] = worldZ - extentsZ; + scratch[3] = worldX + extentsX; + scratch[4] = worldY + extentsY; + scratch[5] = worldZ + extentsZ; + return; + } + + float minX = Float.POSITIVE_INFINITY; + float minY = Float.POSITIVE_INFINITY; + float minZ = Float.POSITIVE_INFINITY; + + float maxX = Float.NEGATIVE_INFINITY; + float maxY = Float.NEGATIVE_INFINITY; + float maxZ = Float.NEGATIVE_INFINITY; + + for (int ix = -1; ix <= 1; ix += 2) { + for (int iy = -1; iy <= 1; iy += 2) { + for (int iz = -1; iz <= 1; iz += 2) { + projection.project( + worldX + ix * extentsX, + worldY + iy * extentsY, + worldZ + iz * extentsZ, + p + ); + + minX = min(minX, p[0]); + minY = min(minY, p[1]); + minZ = min(minZ, p[2]); + + maxX = max(maxX, p[0]); + maxY = max(maxY, p[1]); + maxZ = max(maxZ, p[2]); + } + } + } + + scratch[0] = minX; + scratch[1] = minY; + scratch[2] = minZ; + + scratch[3] = maxX; + scratch[4] = maxY; + scratch[5] = maxZ; + } + + @Override + protected boolean test(Camera camera, float[] scratch) { + return camera.intersectsAABB(scratch[0], scratch[1], scratch[2], scratch[3], scratch[4], scratch[5]); + } + + @Override + protected void debugDraw(float[] scratch) { + DebugDraw.drawMinMax(scratch[0], scratch[1], scratch[2], scratch[3], scratch[4], scratch[5], isVisible() ? Color.GREEN : Color.RED, false); + } + + @Override + public void release() { + super.release(); + CULLING_AABB_POOL.recycle(this); + } + } + + public class CullingSphere extends CullingResult { + public float radius; + + @Override + protected void build(float[] p, float[] scratch) { + float worldX = offsetX + x; + float worldY = offsetY + y; + float worldZ = offsetZ + z; + + if(projection == null) { + scratch[0] = worldX; + scratch[1] = worldY; + scratch[2] = worldZ; + scratch[3] = radius; + return; + } + + projection.project(worldX, worldY, worldZ, p); + scratch[0] = p[0]; + scratch[1] = p[0]; + scratch[2] = p[0]; + + projection.project(worldX + radius, worldY + radius, worldZ + radius, p); + scratch[3] = max( + abs(p[0] - scratch[0]), + max( + abs(p[1] - scratch[1]), + abs(p[2] - scratch[2]) + ) + ); + } + + @Override + protected boolean test(Camera camera, float[] scratch) { + return camera.intersectsSphere( scratch[0], scratch[1], scratch[2], scratch[3]); + } + + @Override + protected void debugDraw(float[] scratch) { + DebugDraw.drawSphere(scratch[0], scratch[1], scratch[2], scratch[3], isVisible() ? Color.GREEN : Color.RED, false); + } + + @Override + public void release() { + super.release(); + CULLING_SPHERE_POOL.recycle(this); + } + } + + public abstract class CullingResult { + @Getter + private byte visibilityFlags = 0; + + protected CullingJob job; + public Projection projection; + + public float x, y, z; + public float offsetX, offsetY, offsetZ; + protected int cullingJobId; + + protected abstract void build(float[] p, float[] scratch); + protected abstract boolean test(Camera camera, float[] scratch); + protected abstract void debugDraw(float[] scratch); + + private void ensureJobCompletion() { + if(job == null) + return; + + if(cullingJobId == job.id) + job.waitForCompletion(); + job = null; + } + + public boolean isVisible() { + ensureJobCompletion(); + return visibilityFlags != 0; + } + + public boolean isVisible(Camera camera) { + ensureJobCompletion(); + return (visibilityFlags & camera.getCullingMask()) != 0; + } + + public void queue() { + assert !pendingCullingResults.contains(this); + pendingCullingResults.add(this); + + if(pendingCullingResults.size() >= HdPlugin.PROCESSOR_COUNT) + flush(); + } + + public void release() { + x = y = z = offsetX = offsetY = offsetZ = 0; + visibilityFlags = 0; + projection = null; + job = null; + } + } +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/utils/Camera.java b/src/main/java/rs117/hd/utils/Camera.java index 6f91fcdbc7..0b367c1ed5 100644 --- a/src/main/java/rs117/hd/utils/Camera.java +++ b/src/main/java/rs117/hd/utils/Camera.java @@ -48,9 +48,13 @@ public final class Camera implements Projection { @Getter private float farPlane = 0.0f; @Getter + private int cullingMask = 0; + @Getter private boolean orthographic = false; @Getter private boolean reverseZ = false; + @Getter + private boolean flipY = false; @Override public float[] project(float x, float y, float z) { @@ -98,6 +102,22 @@ public Camera setReverseZ(boolean newReverseZ) { return this; } + public Camera setFlipY(boolean newFlipY) { + if (flipY != newFlipY) { + synchronized (this) { + flipY = newFlipY; + dirtyFlags |= PROJ_CHANGED; + } + } + return this; + } + + public Camera setCullingId(int id) { + assert id >= 0 && id <= 8; + cullingMask = 1 << id; + return this; + } + public Camera setViewportWidth(int newViewportWidth) { if (viewportWidth != newViewportWidth) { synchronized (this) { @@ -416,6 +436,10 @@ private void calculateProjectionMatrix() { } } } + if (flipY) { + for (int i = 1; i < 16; i += 4) + projectionMatrix[i] = -projectionMatrix[i]; + } try { invProjectionMatrix = Mat4.inverse(projectionMatrix); } catch (Exception ex) { @@ -545,7 +569,7 @@ public float[][] getFrustumCorners() { return getFrustumCorners(new float[8][3]); } - public boolean intersectsAABB(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + public boolean intersectsAABB(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) { calculateFrustumPlanes(); return HDUtils.isAABBIntersectingFrustum(minX, minY, minZ, maxX, maxY, maxZ, frustumPlanes); } @@ -575,4 +599,4 @@ public void copyFrom(Camera other) { dirtyFlags = PROJ_CHANGED | VIEW_CHANGED; } -} +} \ No newline at end of file diff --git a/src/main/java/rs117/hd/utils/DebugDraw.java b/src/main/java/rs117/hd/utils/DebugDraw.java new file mode 100644 index 0000000000..238e162e8f --- /dev/null +++ b/src/main/java/rs117/hd/utils/DebugDraw.java @@ -0,0 +1,221 @@ +package rs117.hd.utils; + +import java.awt.Color; +import rs117.hd.renderer.zone.passes.DebugDrawPass; +import rs117.hd.renderer.zone.passes.DebugDrawPass.Draw; +import rs117.hd.renderer.zone.passes.DebugDrawPass.PrimitiveDrawType; + +public class DebugDraw { + public static DebugDrawPass INSTANCE; + + public static void drawText( + float x, float y, float z, String text, float scale, + Color color, float duration + ) { + DebugDrawPass inst = INSTANCE; + if (inst == null || color == null || text == null) + return; + + final Draw d = inst.pushDraw(PrimitiveDrawType.TEXT); + d.x1 = x; + d.y1 = y; + d.z1 = z; + d.x2 = scale; + d.duration = duration; + d.text = text; + d.rgb = color.getRGB(); + } + + public static void drawText(float x, float y, float z, String text, float scale, Color color) { + drawText(x, y, z, text, scale, color, -1); + } + + public static void drawAABB( + float cx, float cy, float cz, float hx, float hy, float hz, + Color color, float duration, boolean filled + ) { + DebugDrawPass inst = INSTANCE; + if (inst == null || color == null) + return; + + final Draw d = inst.pushDraw(PrimitiveDrawType.AABB); + d.x1 = cx; + d.y1 = cy; + d.z1 = cz; + d.x2 = hx; + d.y2 = hy; + d.z2 = hz; + d.rgb = color.getRGB(); + d.duration = duration; + d.filled = filled; + } + + public static void drawAABB( + float cx, float cy, float cz, float hx, float hy, float hz, + Color color, boolean filled + ) { + drawAABB(cx, cy, cz, hx, hy, hz, color, -1, filled); + } + + public static void drawMinMax( + float minX, float minY, float minZ, + float maxX, float maxY, float maxZ, + Color color, float duration, boolean filled + ) { + DebugDrawPass inst = INSTANCE; + if (inst == null || color == null) + return; + + final Draw d = inst.pushDraw(PrimitiveDrawType.AABB); + d.x1 = (minX + maxX) / 2f; + d.y1 = (minY + maxY) / 2f; + d.z1 = (minZ + maxZ) / 2f; + d.x2 = (maxX - minX) / 2f; + d.y2 = (maxY - minY) / 2f; + d.z2 = (maxZ - minZ) / 2f; + d.rgb = color.getRGB(); + d.duration = duration; + d.filled = filled; + } + + public static void drawMinMax( + float minX, float minY, float minZ, + float maxX, float maxY, float maxZ, + Color color, boolean filled + ) { + drawMinMax(minX, minY, minZ, maxX, maxY, maxZ, color, -1, filled); + } + + public static void drawSphere( + float cx, float cy, float cz, float radius, + Color color, float duration, boolean filled + ) { + DebugDrawPass inst = INSTANCE; + if (inst == null || color == null) + return; + + final Draw d = inst.pushDraw(PrimitiveDrawType.SPHERE); + d.x1 = cx; + d.y1 = cy; + d.z1 = cz; + d.x2 = radius; + d.rgb = color.getRGB(); + d.duration = duration; + d.filled = filled; + } + + public static void drawSphere( + float cx, float cy, float cz, float radius, + Color color, boolean filled + ) { + drawSphere(cx, cy, cz, radius, color, -1, filled); + } + + public static void drawLine( + float x1, float y1, float z1, float x2, float y2, float z2, + Color color, float duration + ) { + drawLine(x1, y1, z1, x2, y2, z2, 1f, color, duration); + } + + public static void drawLine( + float x1, float y1, float z1, float x2, float y2, float z2, + float thickness, Color color + ) { + drawLine(x1, y1, z1, x2, y2, z2, thickness, color, -1); + } + + public static void drawLine( + float x1, float y1, float z1, float x2, float y2, float z2, + float thickness, Color color, float duration + ) { + DebugDrawPass inst = INSTANCE; + if (inst == null || color == null) return; + + final Draw d = inst.pushDraw(PrimitiveDrawType.LINE); + d.x1 = x1; + d.y1 = y1; + d.z1 = z1; + d.x2 = x2; + d.y2 = y2; + d.z2 = z2; + d.thickness = thickness; + d.rgb = color.getRGB(); + d.duration = duration; + d.filled = false; + } + + public static void drawArrow( + float x1, float y1, float z1, + float dx, float dy, float dz, + float headLength, float thickness, + Color color, float duration + ) { + DebugDrawPass inst = INSTANCE; + if (inst == null || color == null) + return; + + float x2 = x1 + dx; + float y2 = y1 + dy; + float z2 = z1 + dz; + + drawLine(x1, y1, z1, x2, y2, z2, thickness, color, duration); + + float len = (float) Math.sqrt(dx * dx + dy * dy + dz * dz); + if (len < 1e-5f) + return; + + float invLen = 1f / len; + float nx = dx * invLen; + float ny = dy * invLen; + float nz = dz * invLen; + + // Pick an axis that's not parallel to the direction. + float ax = Math.abs(ny) < 0.999f ? 0 : 1; + float ay = Math.abs(ny) < 0.999f ? 1 : 0; + float az = 0; + + // right = normalize(dir × axis) + float rx = ny * az - nz * ay; + float ry = nz * ax - nx * az; + float rz = nx * ay - ny * ax; + + float invRLen = 1f / (float) Math.sqrt(rx * rx + ry * ry + rz * rz); + rx *= invRLen; + ry *= invRLen; + rz *= invRLen; + + // up = dir × right + float ux = ny * rz - nz * ry; + float uy = nz * rx - nx * rz; + float uz = nx * ry - ny * rx; + + float bx = x2 - nx * headLength; + float by = y2 - ny * headLength; + float bz = z2 - nz * headLength; + float hs = headLength * 0.5f; + + addHead(bx, by, bz, rx, ry, rz, hs, x2, y2, z2, thickness, color, duration); + addHead(bx, by, bz, ux, uy, uz, hs, x2, y2, z2, thickness, color, duration); + } + + private static void addHead( + float bx, float by, float bz, + float vx, float vy, float vz, + float hs, + float x2, float y2, float z2, + float thickness, + Color color, + float duration + ) { + drawLine(bx + vx * hs, by + vy * hs, bz + vz * hs, x2, y2, z2, thickness, color, duration); + drawLine(bx - vx * hs, by - vy * hs, bz - vz * hs, x2, y2, z2, thickness, color, duration); + } + + public static void drawArrow( + float x1, float y1, float z1, float dx, float dy, float dz, + float headLength, float thickness, Color color + ) { + drawArrow(x1, y1, z1, dx, dy, dz, headLength, thickness, color, -1); + } +} diff --git a/src/main/java/rs117/hd/utils/DeveloperTools.java b/src/main/java/rs117/hd/utils/DeveloperTools.java index 36d42b6171..caa3f11f0d 100644 --- a/src/main/java/rs117/hd/utils/DeveloperTools.java +++ b/src/main/java/rs117/hd/utils/DeveloperTools.java @@ -167,7 +167,16 @@ public void onCommandExecuted(CommandExecuted commandExecuted) { plugin.renderer.reloadScene(); break; case "culling": - plugin.freezeCulling = !plugin.freezeCulling; + if(args.length < 2) { + log.debug("Usage: ::117hd culling freeze|show"); + return; + } + + if(args[1].equalsIgnoreCase("freeze")) + plugin.freezeCulling = !plugin.freezeCulling; + + if(args[1].equalsIgnoreCase("show")) + plugin.showCulling = !plugin.showCulling; break; } } diff --git a/src/main/java/rs117/hd/utils/buffer/GLBuffer.java b/src/main/java/rs117/hd/utils/buffer/GLBuffer.java index a8d8f7d301..d1d9c222d7 100644 --- a/src/main/java/rs117/hd/utils/buffer/GLBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GLBuffer.java @@ -24,6 +24,7 @@ */ package rs117.hd.utils.buffer; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; @@ -355,6 +356,19 @@ public GLBuffer initialize() { return initialize(0); } + public GLBuffer initialize(Buffer data) { + initialize(data.limit()); + if(data instanceof ByteBuffer) + upload((ByteBuffer) data); + else if(data instanceof IntBuffer) + upload((IntBuffer) data); + else if(data instanceof FloatBuffer) + upload((FloatBuffer) data); + else + throw new IllegalArgumentException("Unsupported buffer type: " + data.getClass()); + return this; + } + public GLBuffer initialize(long initialCapacity) { id = glGenBuffers(); // Initialize both GL and CL buffers to buffers of a single byte or more, diff --git a/src/main/java/rs117/hd/utils/collections/PrimitiveIntArray.java b/src/main/java/rs117/hd/utils/collections/PrimitiveIntArray.java index 197f9ae052..97a25867e8 100644 --- a/src/main/java/rs117/hd/utils/collections/PrimitiveIntArray.java +++ b/src/main/java/rs117/hd/utils/collections/PrimitiveIntArray.java @@ -25,6 +25,11 @@ public void put(int v) { array[length++] = v; } + public int pop() { + assert length > 0; + return array[--length]; + } + public void putUnique(int v) { if (length < array.length) { for (int i = 0; i < length; i++) diff --git a/src/main/resources/rs117/hd/debug_draw_frag.glsl b/src/main/resources/rs117/hd/debug_draw_frag.glsl new file mode 100644 index 0000000000..5d321bd399 --- /dev/null +++ b/src/main/resources/rs117/hd/debug_draw_frag.glsl @@ -0,0 +1,27 @@ +#version 330 + +#include + +#define PRIMITIVE_CUBE 0 +#define PRIMITIVE_SPHERE 1 +#define PRIMITIVE_LINE 2 +#define PRIMITIVE_TEXT 3 + +#include PRIMITIVE_TYPE + +flat in vec4 fColor; + +#if PRIMITIVE_TYPE == PRIMITIVE_TEXT +flat in int fChar; + in vec2 fUV; +#endif + +out vec4 FragColor; + +void main() { +#if PRIMITIVE_TYPE == PRIMITIVE_TEXT + if (!fontSample(fChar, fUV)) + discard; +#endif + FragColor = fColor; +} \ No newline at end of file diff --git a/src/main/resources/rs117/hd/debug_draw_vert.glsl b/src/main/resources/rs117/hd/debug_draw_vert.glsl new file mode 100644 index 0000000000..52f42c4e65 --- /dev/null +++ b/src/main/resources/rs117/hd/debug_draw_vert.glsl @@ -0,0 +1,133 @@ +#version 330 + +#include +#include + +#define PRIMITIVE_CUBE 0 +#define PRIMITIVE_SPHERE 1 +#define PRIMITIVE_LINE 2 +#define PRIMITIVE_TEXT 3 + +#include PRIMITIVE_TYPE + +layout(location = 0) in vec3 aPosition; + +#if PRIMITIVE_TYPE == PRIMITIVE_CUBE +layout(location = 1) in vec3 aCenter; +layout(location = 2) in vec3 aHalfExtents; +layout(location = 3) in int aArgb; + +#elif PRIMITIVE_TYPE == PRIMITIVE_SPHERE +layout(location = 1) in vec3 aCenter; +layout(location = 2) in float aRadius; +layout(location = 3) in int aArgb; + +#elif PRIMITIVE_TYPE == PRIMITIVE_LINE +layout(location = 1) in vec3 aStart; +layout(location = 2) in vec3 aEnd; +layout(location = 3) in float aThickness; +layout(location = 4) in int aArgb; + +#elif PRIMITIVE_TYPE == PRIMITIVE_TEXT +layout(location = 1) in vec3 aCenter; +layout(location = 2) in float aCharScale; +layout(location = 3) in int aCharCode; +layout(location = 4) in int aCharIndex; +layout(location = 5) in int aArgb; +#endif + +flat out vec4 fColor; + +#if PRIMITIVE_TYPE == PRIMITIVE_TEXT +flat out int fChar; + out vec2 fUV; +#endif + +vec4 unpackArgb(int argb) { + return vec4( + ((argb >> 16) & 0xFF) / 255.0, + ((argb >> 8) & 0xFF) / 255.0, + ((argb >> 0) & 0xFF) / 255.0, + ((argb >> 24) & 0xFF) / 255.0 + ); +} + +#if PRIMITIVE_TYPE == PRIMITIVE_LINE +mat4 lineModelMatrix(vec3 start, vec3 end, float thickness) { + vec3 dir = end - start; + float len = length(dir); + vec3 mid = (start + end) * 0.5; + + if (len < 1e-5) + return mat4(1.0); + + vec3 yAxis = dir / len; + vec3 ref = abs(yAxis.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); + vec3 xAxis = normalize(cross(ref, yAxis)); + vec3 zAxis = cross(yAxis, xAxis); + + return mat4( + vec4(xAxis * thickness * 0.5, 0.0), + vec4(yAxis * len * 0.5, 0.0), + vec4(zAxis * thickness * 0.5, 0.0), + vec4(mid, 1.0) + ); +} +#endif + +void main() { +#if PRIMITIVE_TYPE == PRIMITIVE_CUBE + mat4 model = mat4( + vec4(aHalfExtents.x, 0.0, 0.0, 0.0), + vec4(0.0, aHalfExtents.y, 0.0, 0.0), + vec4(0.0, 0.0, aHalfExtents.z, 0.0), + vec4(aCenter, 1.0) + ); + fColor = unpackArgb(aArgb); + gl_Position = projectionMatrix * model * vec4(aPosition, 1.0); + +#elif PRIMITIVE_TYPE == PRIMITIVE_SPHERE + mat4 model = mat4( + vec4(aRadius, 0.0, 0.0, 0.0), + vec4(0.0, aRadius, 0.0, 0.0), + vec4(0.0, 0.0, aRadius, 0.0), + vec4(aCenter, 1.0) + ); + fColor = unpackArgb(aArgb); + gl_Position = projectionMatrix * model * vec4(aPosition, 1.0); + +#elif PRIMITIVE_TYPE == PRIMITIVE_LINE + fColor = unpackArgb(aArgb); + gl_Position = projectionMatrix * lineModelMatrix(aStart, aEnd, aThickness) * vec4(aPosition, 1.0); + +#elif PRIMITIVE_TYPE == PRIMITIVE_TEXT + vec2 quad = aPosition.xy - vec2(0.5); + + // Extract camera basis from the view-projection matrix. + // If you have a separate view matrix available, use that instead. + vec3 cameraRight = vec3( + projectionMatrix[0][0], + projectionMatrix[1][0], + projectionMatrix[2][0] + ); + + vec3 cameraUp = vec3( + projectionMatrix[0][1], + projectionMatrix[1][1], + projectionMatrix[2][1] + ); + + float glyphSize = aCharScale; + + vec3 worldPos = + aCenter + + cameraRight * ((float(aCharIndex) + quad.x) * glyphSize) + + cameraUp * (quad.y * glyphSize); + + fUV = aPosition.xy; + fColor = unpackArgb(aArgb); + fChar = aCharCode; + + gl_Position = projectionMatrix * vec4(worldPos, 1.0); +#endif +} \ No newline at end of file diff --git a/src/main/resources/rs117/hd/utils/font.glsl b/src/main/resources/rs117/hd/utils/font.glsl new file mode 100644 index 0000000000..8c1bd30c74 --- /dev/null +++ b/src/main/resources/rs117/hd/utils/font.glsl @@ -0,0 +1,208 @@ +#pragma once + +// 8x8 bitmap font, ASCII 32–127 (96 glyphs × 8 rows) +// Each int is one row of 8 pixels, LSB = leftmost pixel +const int FONT[768] = int[]( + // 0x20 SPACE + 0,0,0,0,0,0,0,0, + // 0x21 ! + 24,24,24,24,24,0,24,0, + // 0x22 " + 54,54,54,0,0,0,0,0, + // 0x23 # + 54,54,127,54,127,54,54,0, + // 0x24 $ + 28,42,40,28,10,42,28,0, + // 0x25 % + 98,100,8,16,38,70,0,0, + // 0x26 & + 28,34,20,8,20,34,28,0, + // 0x27 ' + 24,24,8,0,0,0,0,0, + // 0x28 ( + 8,16,32,32,32,16,8,0, + // 0x29 ) + 32,16,8,8,8,16,32,0, + // 0x2A * + 0,20,8,62,8,20,0,0, + // 0x2B + + 0,8,8,62,8,8,0,0, + // 0x2C , + 0,0,0,0,24,24,8,16, + // 0x2D - + 0,0,0,62,0,0,0,0, + // 0x2E . + 0,0,0,0,0,24,24,0, + // 0x2F / + 2,4,8,16,32,64,0,0, + // 0x30 0 + 28,34,38,42,50,34,28,0, + // 0x31 1 + 8,24,8,8,8,8,28,0, + // 0x32 2 + 28,34,2,4,8,16,62,0, + // 0x33 3 + 28,34,2,12,2,34,28,0, + // 0x34 4 + 4,12,20,36,62,4,4,0, + // 0x35 5 + 62,32,60,2,2,34,28,0, + // 0x36 6 + 14,16,32,60,34,34,28,0, + // 0x37 7 + 62,2,4,8,16,16,16,0, + // 0x38 8 + 28,34,34,28,34,34,28,0, + // 0x39 9 + 28,34,34,30,2,4,56,0, + // 0x3A : + 0,24,24,0,24,24,0,0, + // 0x3B ; + 0,24,24,0,24,24,8,16, + // 0x3C + 4,8,16,32,16,8,4,0, + // 0x3D = + 0,0,62,0,62,0,0,0, + // 0x3E > + 32,16,8,4,8,16,32,0, + // 0x3F ? + 28,34,2,4,8,0,8,0, + // 0x40 @ + 28,34,2,26,42,42,28,0, + // 0x41 A + 8,20,34,34,62,34,34,0, + // 0x42 B + 60,34,34,60,34,34,60,0, + // 0x43 C + 28,34,32,32,32,34,28,0, + // 0x44 D + 60,34,34,34,34,34,60,0, + // 0x45 E + 62,32,32,60,32,32,62,0, + // 0x46 F + 62,32,32,60,32,32,32,0, + // 0x47 G + 28,34,32,46,34,34,28,0, + // 0x48 H + 34,34,34,62,34,34,34,0, + // 0x49 I + 28,8,8,8,8,8,28,0, + // 0x4A J + 14,4,4,4,4,36,24,0, + // 0x4B K + 34,36,40,48,40,36,34,0, + // 0x4C L + 32,32,32,32,32,32,62,0, + // 0x4D M + 34,54,42,42,34,34,34,0, + // 0x4E N + 34,34,50,42,38,34,34,0, + // 0x4F O + 28,34,34,34,34,34,28,0, + // 0x50 P + 60,34,34,60,32,32,32,0, + // 0x51 Q + 28,34,34,34,42,36,26,0, + // 0x52 R + 60,34,34,60,40,36,34,0, + // 0x53 S + 28,34,32,28,2,34,28,0, + // 0x54 T + 62,8,8,8,8,8,8,0, + // 0x55 U + 34,34,34,34,34,34,28,0, + // 0x56 V + 34,34,34,34,34,20,8,0, + // 0x57 W + 34,34,34,42,42,54,34,0, + // 0x58 X + 34,34,20,8,20,34,34,0, + // 0x59 Y + 34,34,20,8,8,8,8,0, + // 0x5A Z + 62,2,4,8,16,32,62,0, + // 0x5B [ + 28,16,16,16,16,16,28,0, + // 0x5C backslash + 64,32,16,8,4,2,0,0, + // 0x5D ] + 28,4,4,4,4,4,28,0, + // 0x5E ^ + 8,20,34,0,0,0,0,0, + // 0x5F _ + 0,0,0,0,0,0,62,0, + // 0x60 ` + 16,8,0,0,0,0,0,0, + // 0x61 a + 0,0,28,2,30,34,30,0, + // 0x62 b + 32,32,60,34,34,34,60,0, + // 0x63 c + 0,0,28,32,32,32,28,0, + // 0x64 d + 2,2,30,34,34,34,30,0, + // 0x65 e + 0,0,28,34,62,32,28,0, + // 0x66 f + 12,18,16,56,16,16,16,0, + // 0x67 g + 0,0,30,34,34,30,2,28, + // 0x68 h + 32,32,60,34,34,34,34,0, + // 0x69 i + 8,0,24,8,8,8,28,0, + // 0x6A j + 4,0,12,4,4,4,36,24, + // 0x6B k + 32,32,34,36,56,36,34,0, + // 0x6C l + 24,8,8,8,8,8,28,0, + // 0x6D m + 0,0,54,42,42,42,34,0, + // 0x6E n + 0,0,60,34,34,34,34,0, + // 0x6F o + 0,0,28,34,34,34,28,0, + // 0x70 p + 0,0,60,34,34,60,32,32, + // 0x71 q + 0,0,30,34,34,30,2,2, + // 0x72 r + 0,0,46,48,32,32,32,0, + // 0x73 s + 0,0,28,32,28,2,60,0, + // 0x74 t + 16,16,56,16,16,18,12,0, + // 0x75 u + 0,0,34,34,34,34,30,0, + // 0x76 v + 0,0,34,34,34,20,8,0, + // 0x77 w + 0,0,34,34,42,42,20,0, + // 0x78 x + 0,0,34,20,8,20,34,0, + // 0x79 y + 0,0,34,34,34,30,2,28, + // 0x7A z + 0,0,62,4,8,16,62,0, + // 0x7B { + 12,16,16,32,16,16,12,0, + // 0x7C | + 8,8,8,8,8,8,8,0, + // 0x7D } + 48,8,8,4,8,8,48,0, + // 0x7E ~ + 20,40,0,0,0,0,0,0, + // 0x7F DEL + 0,0,0,0,0,0,0,0 +); + +bool fontSample(int charCode, vec2 uv) +{ + int c = clamp(charCode - 32, 0, 95); + int col = clamp(int(uv.x * 8.0), 0, 7); + int row = clamp(int((1.0 - uv.y) * 8.0), 0, 7); + + int bits = FONT[c * 8 + row]; + return ((bits >> (7 - col)) & 1) != 0; +} \ No newline at end of file