From 93c38a96631cb6287f9169d8d735852846911c8a Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Thu, 2 Apr 2026 20:43:04 +0100 Subject: [PATCH] Replaced Zone Upload Estimate with staging buffers * SceneUploader Estimate Functions Removed * `Zone::HasWater` calculated by checking if `uploadZoneWater` writes anything to the vertex buffer * `ZoneUploadJob` now obtains `GPUIntBuffer` from a pool and writes all the zone data before requesting GLBuffers to be created and mapped * StagingBuffers are written to the Mapped Buffers Async GLBuffer::initialize can now take a Buffer for initial content Removed Zone Mapping in favor of simpler `glBufferData` upload since the cost equals out and is significantly less complex Merge Rids, roofStart, roofEnd & levelOffsets into a single object: LevelData Reducing the amount of arrays from 17 to just 2 Build RoofData into a staging array to reduce resulting array size since not all rids will write anything Improve Garbage & Assertion Performance Initialise Zone when `OnCompleted` is called, instead of in a callback Cleanup Reverted Assertions changes --- .../rs117/hd/overlays/FrameTimerOverlay.java | 1 + .../rs117/hd/renderer/zone/SceneManager.java | 27 +- .../rs117/hd/renderer/zone/SceneUploader.java | 254 +++----------- .../hd/renderer/zone/WorldViewContext.java | 23 +- .../java/rs117/hd/renderer/zone/Zone.java | 328 +++++++++--------- .../rs117/hd/renderer/zone/ZoneRenderer.java | 38 +- .../rs117/hd/renderer/zone/ZoneUploadJob.java | 134 ++++--- .../java/rs117/hd/utils/buffer/GLBuffer.java | 16 + .../hd/utils/buffer/GLTextureBuffer.java | 11 +- .../rs117/hd/utils/buffer/GpuIntBuffer.java | 11 +- .../utils/collections/PrimitiveIntArray.java | 17 + src/main/java/rs117/hd/utils/jobs/Job.java | 4 +- .../java/rs117/hd/utils/jobs/JobHandle.java | 15 +- 13 files changed, 409 insertions(+), 470 deletions(-) diff --git a/src/main/java/rs117/hd/overlays/FrameTimerOverlay.java b/src/main/java/rs117/hd/overlays/FrameTimerOverlay.java index d596fc9efb..597b091162 100644 --- a/src/main/java/rs117/hd/overlays/FrameTimerOverlay.java +++ b/src/main/java/rs117/hd/overlays/FrameTimerOverlay.java @@ -214,6 +214,7 @@ public Dimension render(Graphics2D g) { WorldViewContext root = sceneManager.getRoot(); addTiming("Root Scene Load", root.loadTime, false); addTiming("Root Scene Upload", root.uploadTime, false); + addTiming("Root Scene Buffer", root.bufferInit / ((long) root.sizeX * root.sizeZ), false); addTiming("Root Scene Swap", root.sceneSwapTime, false); // TODO: Maybe this should be calculated somewhere else diff --git a/src/main/java/rs117/hd/renderer/zone/SceneManager.java b/src/main/java/rs117/hd/renderer/zone/SceneManager.java index f7258f3620..1931828b06 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneManager.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneManager.java @@ -361,7 +361,7 @@ private static boolean isEdgeTile(Zone[][] zones, int zx, int zz) { Zone zone = zones[x][z]; if (!zone.initialized) return true; - if (zone.sizeO == 0 && zone.sizeA == 0) + if (zone.sizeIntsOpaque == 0 && zone.sizeIntsAlpha == 0) return true; } } @@ -434,7 +434,7 @@ public synchronized void loadScene(WorldView worldView, Scene scene) { Stopwatch sw = Stopwatch.createStarted(); root.isLoading = true; - root.loadTime = root.uploadTime = root.sceneSwapTime = 0; + root.loadTime = root.uploadTime = root.bufferInit = root.sceneSwapTime = 0; root.sceneLoadGroup.complete(); root.streamingGroup.complete(); @@ -543,7 +543,7 @@ public synchronized void loadScene(WorldView worldView, Scene scene) { continue; final Zone old = ctx.zones[ox][oz]; - if (!old.initialized || (old.sizeO == 0 && old.sizeA == 0)) + if (!old.initialized || (old.sizeIntsOpaque == 0 && old.sizeIntsAlpha == 0)) continue; old.needsRoofUpdate = true; @@ -578,7 +578,7 @@ public synchronized void loadScene(WorldView worldView, Scene scene) { float dist = distance(vec(x, z), vec(NUM_ZONES / 2, NUM_ZONES / 2)); if (!staggerLoad || dist < ZONE_DEFER_DIST_START) { ZoneUploadJob - .build(ctx, nextSceneContext, zone, true, x, z) + .build(ctx, nextSceneContext, zone, x, z) .queue(ctx.sceneLoadGroup, generateSceneDataTask); nextSceneContext.totalMapZones++; } else { @@ -597,13 +597,18 @@ public synchronized void loadScene(WorldView worldView, Scene scene) { // Reuse the old zone while uploading a correct one sorted.zone.cull = false; sorted.zone.uploadJob = ZoneUploadJob - .build(ctx, nextSceneContext, newZone, false, sorted.x, sorted.z); - sorted.zone.uploadJob.revealAfterTimestampMs = - timeMs + ceil(clamp(sorted.dist / 15.0f, 0.25f, 1.5f) * 1000.0f); + .build( + ctx, + nextSceneContext, + newZone, + sorted.x, + sorted.z, + timeMs + ceil(clamp(sorted.dist / 15.0f, 0.25f, 1.5f) * 1000.0f) + ); } else { nextZones[sorted.x][sorted.z] = newZone; ZoneUploadJob - .build(ctx, nextSceneContext, newZone, true, sorted.x, sorted.z) + .build(ctx, nextSceneContext, newZone, sorted.x, sorted.z) .queue(ctx.sceneLoadGroup, generateSceneDataTask); } sorted.free(); @@ -677,8 +682,8 @@ public void swapScene(Scene scene) { int totalAlpha = 0; for (int x = 0; x < NUM_ZONES; ++x) { for (int z = 0; z < NUM_ZONES; ++z) { - totalOpaque += nextZones[x][z].bufLen; - totalAlpha += nextZones[x][z].bufLenA; + totalOpaque += nextZones[x][z].sizeIntsOpaque; + totalAlpha += nextZones[x][z].sizeIntsAlpha; } } @@ -764,7 +769,7 @@ private void loadSubScene(WorldView worldView, Scene scene) { for (int x = 0; x < ctx.sizeX; ++x) for (int z = 0; z < ctx.sizeZ; ++z) ZoneUploadJob - .build(ctx, sceneContext, ctx.zones[x][z], true, x, z) + .build(ctx, sceneContext, ctx.zones[x][z], x, z) .queue(ctx.sceneLoadGroup); ctx.loadTime = sw.elapsed(TimeUnit.NANOSECONDS); diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index ba79134987..0294a3e1e0 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -24,8 +24,7 @@ */ package rs117.hd.renderer.zone; -import java.util.HashSet; -import java.util.Set; +import java.util.Arrays; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; @@ -111,14 +110,14 @@ public class SceneUploader implements AutoCloseable { @FunctionalInterface public interface OnBeforeProcessTileFunc { - void invoke(Tile t, boolean isEstimate) throws InterruptedException; + void invoke(Tile t) throws InterruptedException; } public OnBeforeProcessTileFunc onBeforeProcessTile; private int basex, basez, rid, level; - private final Set roofIds = new HashSet<>(); + private final PrimitiveIntArray roofIds = new PrimitiveIntArray(); private Scene currentScene; private Tile[][][] tiles; private byte[][][] settings; @@ -126,6 +125,8 @@ public interface OnBeforeProcessTileFunc { private short[][][] overlayIds; private short[][][] underlayIds; private int[][][] tileHeights; + private int[] roofData = new int[300]; + private int roofDataPos; private final int[] worldPos = new int[3]; private final int[][] vertices = new int[4][3]; @@ -174,69 +175,53 @@ public void clear() { onBeforeProcessTile = null; } - public void estimateZoneSize(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws InterruptedException { - // Initialize the zone as containing only water, until a non-water tile is found - zone.onlyWater = true; - - for (int z = 3; z >= 0; --z) { - for (int xoff = 0; xoff < 8; ++xoff) { - for (int zoff = 0; zoff < 8; ++zoff) { - Tile t = tiles[z][(mzx << 3) + xoff][(mzz << 3) + zoff]; - if (t != null) { - if (onBeforeProcessTile != null) - onBeforeProcessTile.invoke(t, true); - estimateZoneTileSize(ctx, zone, t); - } - } - } - } - } - - public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws InterruptedException { - var vb = zone.vboO != null ? new GpuIntBuffer(zone.vboO.mapped()) : null; - var ab = zone.vboA != null ? new GpuIntBuffer(zone.vboA.mapped()) : null; - var fb = zone.tboF != null ? new GpuIntBuffer(zone.tboF.mapped()) : null; + public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz, GpuIntBuffer vb, GpuIntBuffer ab, GpuIntBuffer fb) throws InterruptedException { assert fb != null; - roofIds.clear(); + roofIds.reset(); for (int level = 0; level <= 3; ++level) { for (int xoff = 0; xoff < 8; ++xoff) { for (int zoff = 0; zoff < 8; ++zoff) { int rid = roofs[level][(mzx << 3) + xoff][(mzz << 3) + zoff]; if (rid > 0) - roofIds.add(rid); + roofIds.addUnique(rid); } } } - zone.rids = new int[4][roofIds.size()]; - zone.roofStart = new int[4][roofIds.size()]; - zone.roofEnd = new int[4][roofIds.size()]; + if(roofIds.length > 0) + zone.roofData = new int[4 * 3 * roofIds.length]; for (int z = 0; z <= 3; ++z) { this.level = z; if (z == 0) { - uploadZoneLevel(ctx, zone, mzx, mzz, 0, false, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 0, true, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 1, true, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 2, true, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 3, true, roofIds, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, 0, false, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, 0, true, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, 1, true, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, 2, true, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, 3, true, vb, ab, fb); } else { - uploadZoneLevel(ctx, zone, mzx, mzz, z, false, roofIds, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, z, false, vb, ab, fb); } - if (vb != null) { - int pos = vb.position(); - zone.levelOffsets[z] = pos; - } + if (vb != null) + zone.levelData[z].offset = vb.position(); } // Upload water surface tiles to be drawn after everything else - if (zone.hasWater && vb != null) { + if (vb != null) { + int start = vb.position(); uploadZoneWater(ctx, zone, mzx, mzz, vb, fb); - zone.levelOffsets[Zone.LEVEL_WATER_SURFACE] = vb.position(); + if(vb.position() > start) { + zone.levelData[Zone.LEVEL_WATER_SURFACE].offset = vb.position(); + zone.hasWater = true; + } } + + if(roofDataPos > 0) + zone.roofData = Arrays.copyOf(roofData, roofDataPos); + roofDataPos = 0; } private void uploadZoneLevel( @@ -246,26 +231,23 @@ private void uploadZoneLevel( int mzz, int level, boolean visbelow, - Set roofIds, GpuIntBuffer vb, GpuIntBuffer ab, GpuIntBuffer fb ) throws InterruptedException { - int ridx = 0; - // upload the roofs and save their positions - for (int id : roofIds) { + final Zone.LevelData ld = zone.levelData[level]; + for (int i = 0; i < roofIds.length; i++) { + int rid = roofIds.array[i]; int pos = vb != null ? vb.position() : 0; - uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, id, visbelow, vb, ab, fb); + uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, rid, visbelow, vb, ab, fb); - int endpos = vb != null ? vb.position() : 0; - - if (endpos > pos) { - zone.rids[level][ridx] = id; - zone.roofStart[level][ridx] = pos; - zone.roofEnd[level][ridx] = endpos; - ++ridx; + int endPos = vb != null ? vb.position() : 0; + if (endPos > pos) { + if(roofDataPos + 3 >= roofData.length) + roofData = Arrays.copyOf(roofData, roofData.length * 2); + roofDataPos = ld.addRoof(roofData, roofDataPos, rid, pos, endPos); } } @@ -316,7 +298,7 @@ private void uploadZoneLevelRoof( if (t != null) { this.rid = rid; if (onBeforeProcessTile != null) - onBeforeProcessTile.invoke(t, false); + onBeforeProcessTile.invoke(t); uploadZoneTile(ctx, zone, t, false, false, vb, ab, fb); } } @@ -343,7 +325,7 @@ private void uploadZoneWater( Tile t = tiles[level][msx][msz]; if (t != null) { if (onBeforeProcessTile != null) - onBeforeProcessTile.invoke(t, false); + onBeforeProcessTile.invoke(t); uploadZoneTile(ctx, zone, t, false, true, vb, null, fb); } } @@ -351,111 +333,6 @@ private void uploadZoneWater( } } - private void estimateZoneTileSize(ZoneSceneContext ctx, Zone z, Tile t) { - var tilePoint = t.getSceneLocation(); - ctx.sceneToWorld(tilePoint.getX(), tilePoint.getY(), t.getPlane(), worldPos); - - SceneTilePaint paint = t.getSceneTilePaint(); - if (paint != null && paint.getNeColor() != HIDDEN_HSL) { - z.sizeO += 2; - z.sizeF += 2; - - TileOverride override = tileOverrideManager.getOverride(ctx, t, worldPos); - WaterType waterType = proceduralGenerator.seasonalWaterType(override, paint.getTexture()); - if (waterType != WaterType.NONE) { - z.hasWater = true; - // Since these are surface tiles, they should perhaps technically be in the alpha buffer, - // but we'll render them in the correct order without needing face sorting, - // so we might as well use the opaque buffer for simplicity - z.sizeO += 2; - z.sizeF += 2; - } else { - z.onlyWater = false; - } - } - - SceneTileModel model = t.getSceneTileModel(); - if (model != null) { - int len = model.getFaceX().length; - z.sizeO += len; - z.sizeF += len; - - int tileExX = tilePoint.getX() + ctx.sceneOffset; - int tileExY = tilePoint.getY() + ctx.sceneOffset; - int tileZ = t.getRenderLevel(); - int overlayId = OVERLAY_FLAG | overlayIds[tileZ][tileExX][tileExY]; - int underlayId = underlayIds[tileZ][tileExX][tileExY]; - var overlayOverride = tileOverrideManager.getOverride(ctx, t, worldPos, overlayId); - var underlayOverride = tileOverrideManager.getOverride(ctx, t, worldPos, underlayId); - - final int[] triangleTextures = model.getTriangleTextureId(); - boolean isFallbackWater = false; - if (triangleTextures != null) { - for (int textureId : triangleTextures) { - if (textureId != -1 && proceduralGenerator.seasonalWaterType(TileOverride.NONE, textureId) != WaterType.NONE) { - isFallbackWater = true; - break; - } - } - } - WaterType overlayWaterType = proceduralGenerator.seasonalWaterType(overlayOverride, 0); - WaterType underlayWaterType = proceduralGenerator.seasonalWaterType(underlayOverride, 0); - boolean isOverlayWater = overlayWaterType != WaterType.NONE; - boolean isUnderlayWater = underlayWaterType != WaterType.NONE; - if (isFallbackWater || isOverlayWater || isUnderlayWater) { - z.hasWater = true; - z.sizeO += len; - z.sizeF += len; - } else { - z.onlyWater = false; - } - } - - WallObject wallObject = t.getWallObject(); - if (wallObject != null) { - ModelOverride modelOverride = modelOverrideManager.getOverride(wallObject, worldPos); - if (!modelOverride.hide) { - estimateRenderableSize(z, wallObject.getRenderable1(), modelOverride); - estimateRenderableSize(z, wallObject.getRenderable2(), modelOverride); - } - } - - DecorativeObject decorativeObject = t.getDecorativeObject(); - if (decorativeObject != null) { - ModelOverride modelOverride = modelOverrideManager.getOverride(decorativeObject, worldPos); - if (!modelOverride.hide) { - estimateRenderableSize(z, decorativeObject.getRenderable(), modelOverride); - estimateRenderableSize(z, decorativeObject.getRenderable2(), modelOverride); - } - } - - GroundObject groundObject = t.getGroundObject(); - if (groundObject != null) { - ModelOverride modelOverride = modelOverrideManager.getOverride(groundObject, worldPos); - if (!modelOverride.hide) - estimateRenderableSize(z, groundObject.getRenderable(), modelOverride); - } - - GameObject[] gameObjects = t.getGameObjects(); - for (GameObject gameObject : gameObjects) { - if (gameObject == null || !gameObject.getSceneMinLocation().equals(t.getSceneLocation())) - continue; - - if (ModelHash.isTemporaryObject(gameObject.getHash())) - continue; - - ModelOverride modelOverride = modelOverrideManager.getOverride(gameObject, worldPos); - if (modelOverride.hide) - continue; - - estimateRenderableSize(z, gameObject.getRenderable(), modelOverride); - } - - Tile bridge = t.getBridge(); - if (bridge != null) - estimateZoneTileSize(ctx, z, bridge); - } - private void uploadZoneTile( ZoneSceneContext ctx, Zone zone, @@ -664,32 +541,6 @@ private void uploadZoneTileRenderables( } } - private void estimateRenderableSize(Zone z, Renderable r, ModelOverride modelOverride) { - boolean mightHaveTransparency = modelOverride.mightHaveTransparency; - Model m = null; - if (r instanceof Model) { - m = (Model) r; - } else if (r instanceof DynamicObject) { - var dynamic = (DynamicObject) r; - m = dynamic.getModelZbuf(); - if (dynamic.getRecordedObjectComposition() != null) - mightHaveTransparency = true; - } - if (m == null) - return; - - int faceCount = m.getFaceCount(); - byte[] transparencies = m.getFaceTransparencies(); - short[] faceTextures = m.getFaceTextures(); - if (transparencies == null && faceTextures == null && !mightHaveTransparency) { - z.sizeO += faceCount; - } else { - z.sizeO += faceCount; - z.sizeA += faceCount; - } - z.sizeF += faceCount; - } - private void uploadZoneRenderable( ZoneSceneContext ctx, Zone zone, @@ -775,8 +626,6 @@ private void uploadZoneRenderable( zone.addAlphaModel( plugin, materialManager, - zone.glVaoA, - zone.tboF.getTexId(), model, modelOverride, alphaStart, alphaEnd, x - basex, y, z - basez, lx, lz, ux, uz, @@ -1001,6 +850,9 @@ private void uploadTilePaint( uvx = fract(uvx * uvcos - uvy * uvsin); uvy = fract(tmp * uvsin + uvy * uvcos); + fb.ensureCapacity(Zone.TEXTURE_SIZE * 2); + vb.ensureCapacity(Zone.VERT_SIZE * 6); + int texturedFaceIdx = fb.putFace( neColor, nwColor, seColor, neMaterialData, nwMaterialData, seMaterialData, @@ -1107,6 +959,8 @@ private void uploadTileModel( int tileX = sceneLoc.getX(); int tileY = sceneLoc.getY(); + fb.ensureCapacity(Zone.TEXTURE_SIZE * faceCount); + for (int face = 0; face < faceCount; ++face) { int colorA = triangleColorA[face]; int colorB = triangleColorB[face]; @@ -1318,6 +1172,7 @@ private void uploadTileModel( terrainDataA, terrainDataB, terrainDataC ); + vb.ensureCapacity(Zone.VERT_SIZE * 3); vb.putVertex( lx0, ly0, lz0, uvAx, uvAy, 0, @@ -1354,10 +1209,6 @@ private int uploadStaticModel( GpuIntBuffer alphaBuffer, GpuIntBuffer textureBuffer ) { - if (writeCache == null) - writeCache = new VertexWriteCache.Collection(); - writeCache.setOutputBuffers(opaqueBuffer, alphaBuffer, textureBuffer); - final int[][][] tileHeights = ctx.scene.getTileHeights(); final int faceCount = model.getFaceCount(); final int vertexCount = model.getVerticesCount(); @@ -1442,6 +1293,8 @@ private int uploadStaticModel( final Material baseMaterial = modelOverride.baseMaterial; final Material textureMaterial = modelOverride.textureMaterial; + textureBuffer.ensureCapacity(Zone.TEXTURE_SIZE * faceCount); + int len = 0; for (int face = 0; face < faceCount; ++face) { int color1 = color1s[face]; @@ -1661,34 +1514,34 @@ private int uploadStaticModel( bias == null ? 0 : bias[face] & 0xFF; int packedAlphaBiasHsl = transparency << 24 | depthBias << 16; boolean hasAlpha = material.hasTransparency || transparency != 0; - final VertexWriteCache vb = writeCache.useAlphaBuffer && hasAlpha ? writeCache.alpha : writeCache.opaque; - final VertexWriteCache tb = writeCache.opaqueTex; + final GpuIntBuffer vb = hasAlpha ? alphaBuffer : opaqueBuffer; color1 |= packedAlphaBiasHsl; color2 |= packedAlphaBiasHsl; color3 |= packedAlphaBiasHsl; - final int texturedFaceIdx = tb.putFace( + final int texturedFaceIdx = textureBuffer.putFace( color1, color2, color3, materialData, materialData, materialData, 0, 0, 0 ); - vb.putStaticVertex( + vb.ensureCapacity(Zone.VERT_SIZE * 3); + vb.putVertex( vx1, vy1, vz1, faceUVs[0], faceUVs[1], faceUVs[2], modelNormals[0], modelNormals[1], modelNormals[2], texturedFaceIdx ); - vb.putStaticVertex( + vb.putVertex( vx2, vy2, vz2, faceUVs[4], faceUVs[5], faceUVs[6], modelNormals[3], modelNormals[4], modelNormals[5], texturedFaceIdx ); - vb.putStaticVertex( + vb.putVertex( vx3, vy3, vz3, faceUVs[8], faceUVs[9], faceUVs[10], modelNormals[6], modelNormals[7], modelNormals[8], @@ -1696,7 +1549,6 @@ private int uploadStaticModel( ); len += 3; } - writeCache.flush(); return len; } diff --git a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java index 429c67f3d1..a8c5d47266 100644 --- a/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java +++ b/src/main/java/rs117/hd/renderer/zone/WorldViewContext.java @@ -5,7 +5,6 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Set; import javax.annotation.Nullable; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; @@ -54,7 +53,7 @@ public class WorldViewContext { private SceneManager sceneManager; final int worldViewId; - final int sizeX, sizeZ; + public final int sizeX, sizeZ; @Nullable WorldViewStruct uboWorldViewStruct; ZoneSceneContext sceneContext; @@ -63,7 +62,6 @@ public class WorldViewContext { boolean isLoading = true; int minLevel, level, maxLevel; - Set hideRoofIds; private final Comparator alphaSortComparator = Comparator.comparingInt((Zone z) -> z.dist).reversed(); private final List alphaZones = new ArrayList<>(); @@ -74,6 +72,7 @@ public class WorldViewContext { public long loadTime; public long uploadTime; + public long bufferInit; public long sceneSwapTime; final JobGroup sceneLoadGroup = new JobGroup<>(true, true); @@ -194,9 +193,8 @@ void handleZoneSwap(int zx, int zz, boolean queue) { return; if (!uploadTask.isQueued()) { - if (queue && uploadTask.revealAfterTimestampMs < System.currentTimeMillis()) { - log.trace("queueing zone({}): [{}-{},{}]", uploadTask.zone.hashCode(), worldViewId, zx, zz); - uploadTask.revealAfterTimestampMs = 0; + if (queue && uploadTask.getRevealAfterTimestampMs() < System.currentTimeMillis()) { + log.trace("queueing zone({}): [{}-{},{}]", uploadTask.getZone().hashCode(), worldViewId, zx, zz); uploadTask.queue(streamingGroup, sceneManager.getGenerateSceneDataTask()); } return; @@ -205,13 +203,11 @@ void handleZoneSwap(int zx, int zz, boolean queue) { if (uploadTask.isDone()) { curZone.uploadJob = null; if (uploadTask.ranToCompletion() && !uploadTask.wasCancelled()) { - log.trace("swapping zone({}): [{}-{},{}]", uploadTask.zone.hashCode(), worldViewId, zx, zz); + log.trace("swapping zone({}): [{}-{},{}]", uploadTask.getZone().hashCode(), worldViewId, zx, zz); Zone prevZone = curZone; // Swap the zone out with the one we just uploaded - zones[zx][zz] = curZone = uploadTask.zone; - clientThread.invoke(curZone::unmap); - + zones[zx][zz] = curZone = uploadTask.getZone(); if (prevZone != curZone) { curZone.inSceneFrustum = prevZone.inSceneFrustum; curZone.inShadowFrustum = prevZone.inShadowFrustum; @@ -313,7 +309,7 @@ void invalidateZone(int zx, int zz) { Zone curZone = zones[zx][zz]; long revealAfterTimestampMs = 0; if (curZone.uploadJob != null) { - Zone pendingZone = curZone.uploadJob.zone; + Zone pendingZone = curZone.uploadJob.getZone(); log.trace( "Invalidate Zone({}) - Cancelled upload task: [{}-{},{}] task zone({})", curZone.hashCode(), @@ -322,7 +318,7 @@ void invalidateZone(int zx, int zz) { zz, pendingZone.hashCode() ); - revealAfterTimestampMs = curZone.uploadJob.revealAfterTimestampMs; + revealAfterTimestampMs = curZone.uploadJob.getRevealAfterTimestampMs(); curZone.uploadJob.cancel(); curZone.uploadJob.release(); @@ -333,8 +329,7 @@ void invalidateZone(int zx, int zz) { Zone newZone = injector.getInstance(Zone.class); newZone.dirty = zones[zx][zz].dirty; - curZone.uploadJob = ZoneUploadJob.build(this, sceneContext, newZone, false, zx, zz); - curZone.uploadJob.revealAfterTimestampMs = revealAfterTimestampMs; + curZone.uploadJob = ZoneUploadJob.build(this, sceneContext, newZone, zx, zz, revealAfterTimestampMs); // Queue right away, so we can wait for it while in the POH in order to hide building mode placeholders if (sceneContext.isInHouse || revealAfterTimestampMs <= 0) diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 97f8004142..d2cff77dcf 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -3,7 +3,7 @@ import java.nio.IntBuffer; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; +import java.util.BitSet; import java.util.Comparator; import java.util.HashSet; import java.util.List; @@ -11,7 +11,6 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.ToIntFunction; import javax.annotation.Nullable; -import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; import org.lwjgl.system.MemoryStack; @@ -27,6 +26,8 @@ import rs117.hd.utils.HDUtils; import rs117.hd.utils.buffer.GLBuffer; import rs117.hd.utils.buffer.GLTextureBuffer; +import rs117.hd.utils.buffer.GpuIntBuffer; +import rs117.hd.utils.collections.ConcurrentPool; import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPlugin.GL_CAPS; @@ -38,8 +39,8 @@ @Slf4j public class Zone implements Destructible { - @Inject - private Client client; + + private static final BitSet EMPTY_SET = new BitSet(); // Zone vertex format // pos short vec3(x, y, z) @@ -59,15 +60,13 @@ public class Zone implements Destructible { public static final int METADATA_SIZE = 12; public static final int LEVEL_WATER_SURFACE = 4; + public static final int LEVEL_COUNT = 5; public int glVao; - int bufLen; - int dist; - public int glVaoA; - public int bufLenA; + public int dist; - public int sizeO, sizeA, sizeF; + public int sizeIntsOpaque, sizeIntsAlpha, sizeIntsFace; @Nullable public GLBuffer vboO, vboA, vboM; public GLTextureBuffer tboF; @@ -85,40 +84,56 @@ public class Zone implements Destructible { public HashSet animatedDynamicObjectIds = new HashSet<>(); - final StaticAlphaSortingJob alphaSortingJob = new StaticAlphaSortingJob(); - ZoneUploadJob uploadJob; - - int[] levelOffsets = new int[5]; // buffer pos in ints for the end of the level - - int[][] rids; - int[][] roofStart; - int[][] roofEnd; + public final StaticAlphaSortingJob alphaSortingJob = new StaticAlphaSortingJob(); + public ZoneUploadJob uploadJob; + int[] roofData; + final LevelData[] levelData = new LevelData[LEVEL_COUNT]; final List alphaModels = new ArrayList<>(0); final ConcurrentLinkedQueue pendingModelJobs = new ConcurrentLinkedQueue<>(); - public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f) { - assert glVao == 0; - assert glVaoA == 0; - if (o == null && a == null || f == null) - return; + protected Zone() { + for(int i = 0; i < LEVEL_COUNT; i++) + levelData[i] = new LevelData(); + } + + public void initialize(WorldViewContext viewCtx, SceneContext sceneCtx, int x, int z, GpuIntBuffer opaqueStaging, GpuIntBuffer alphaStaging, GpuIntBuffer tboStaging) { + long start = System.nanoTime(); vboM = new GLBuffer("ZoneMetadata", GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW); vboM.initialize(METADATA_SIZE); - if (o != null) { - vboO = o; + if (sizeIntsOpaque > 0) { + vboO = new GLBuffer("Zone::VBO::Opaque", GL_ARRAY_BUFFER, GL_STATIC_DRAW); + vboO.initialize(opaqueStaging.getBuffer()); glVao = glGenVertexArrays(); - setupVao(glVao, o.id, vboM.id); + setupVao(glVao, vboO.id, vboM.id); } - if (a != null) { - vboA = a; + if (sizeIntsAlpha > 0) { + vboA = new GLBuffer("Zone::VBO::Alpha", GL_ARRAY_BUFFER, GL_STATIC_DRAW); + vboA.initialize(alphaStaging.getBuffer()); glVaoA = glGenVertexArrays(); - setupVao(glVaoA, a.id, vboM.id); + setupVao(glVaoA, vboA.id, vboM.id); } - tboF = f; + if (sizeIntsFace > 0) { + tboF = new GLTextureBuffer("Zone::TBO", GL_STATIC_DRAW); + tboF.initialize(tboStaging.getBuffer()); + } + + if(sizeIntsAlpha > 0 && sizeIntsFace > 0) { + for (int i = 0; i < alphaModels.size(); i++) { + final Zone.AlphaModel m = alphaModels.get(i); + m.vao = glVaoA; + m.tboF = tboF.getTexId(); + } + } + + setMetadata(viewCtx, sceneCtx, x, z); + initialized = true; + + viewCtx.bufferInit += System.nanoTime() - start; } public static void freeZones(@Nullable Zone[][] zones) { @@ -172,17 +187,15 @@ public void destroy() { if (uploadJob != null) { uploadJob.cancel(); - DestructibleHandler.destroy(uploadJob.zone); + DestructibleHandler.destroy(uploadJob.getZone()); uploadJob = null; } sortedAlphaFacesUpload.release(); - sizeO = 0; - sizeA = 0; - sizeF = 0; - bufLen = 0; - bufLenA = 0; + sizeIntsOpaque = 0; + sizeIntsAlpha = 0; + sizeIntsFace = 0; initialized = false; cull = false; @@ -191,10 +204,7 @@ public void destroy() { inSceneFrustum = false; inShadowFrustum = false; - Arrays.fill(levelOffsets, 0); - rids = null; - roofStart = null; - roofEnd = null; + Arrays.fill(levelData, null); // don't add permanent alphamodels to the cache as permanent alphamodels are always allocated // to avoid having to synchronize the cache @@ -205,29 +215,10 @@ public void destroy() { public String toString() { return String.format( "Zone Initialized: %b, culled: %b hasUploadJob: %b opaqueSize: %d alphaSize: %d", - initialized, cull, uploadJob != null, sizeO, sizeA + initialized, cull, uploadJob != null, sizeIntsOpaque, sizeIntsAlpha ); } - public void unmap() { - assert client.isClientThread(); - - if (vboO != null) - vboO.unmap(); - if (vboA != null) - vboA.unmap(); - if (tboF != null) - tboF.unmap(); - - if (vboO != null) { - this.bufLen = vboO.mapped().byteView().position() / VERT_SIZE; - } - - if (vboA != null) { - this.bufLenA = vboA.mapped().byteView().position() / VERT_SIZE; - } - } - private void setupVao(int vao, int buffer, int metadata) { glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, buffer); @@ -285,12 +276,15 @@ public void setMetadata(WorldViewContext viewContext, SceneContext sceneContext, void updateRoofs(Map updates) { for (int level = 0; level < 4; ++level) { - for (int i = 0; i < rids[level].length; ++i) { - rids[level][i] = updates.getOrDefault(rids[level][i], rids[level][i]); + final LevelData ld = levelData[level]; + for (int i = 0; i < ld.ridCount; ++i) { + int rid = ld.getRoofId(i); + ld.setRoofId(i, updates.getOrDefault(rid, rid)); } } - for (AlphaModel m : alphaModels) { + for (int i = 0; i < alphaModels.size(); i++) { + final AlphaModel m = alphaModels.get(i); m.rid = (short) (int) updates.getOrDefault((int) m.rid, (int) m.rid); } } @@ -323,47 +317,45 @@ void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) int currentLevel = ctx.level; int maxLevel = ctx.maxLevel; - var hiddenRoofIds = ctx.hideRoofIds; + var hiddenRoofIds = ZoneRenderer.hiddenRoofIdsBitField; if (roofShadows) { maxLevel = 3; - hiddenRoofIds = Collections.emptySet(); + hiddenRoofIds = EMPTY_SET; } for (int level = ctx.minLevel; level <= maxLevel; ++level) { - int[] rids = this.rids[level]; - int[] roofStart = this.roofStart[level]; - int[] roofEnd = this.roofEnd[level]; + final LevelData ld = levelData[level]; - if (rids.length == 0 || hiddenRoofIds.isEmpty() || level <= currentLevel) { + if (ld.ridCount == 0 || hiddenRoofIds.isEmpty() || level <= currentLevel) { // draw the whole level - int start = level == 0 ? 0 : this.levelOffsets[level - 1]; - int end = this.levelOffsets[level]; + int start = level == 0 ? 0 : levelData[level - 1].offset; + int end = levelData[level].offset; pushRange(start, end); continue; } - for (int roofIdx = 0; roofIdx < rids.length; ++roofIdx) { - int rid = rids[roofIdx]; - if (rid > 0 && !hiddenRoofIds.contains(rid)) { + for (int roofIdx = 0; roofIdx < ld.ridCount; ++roofIdx) { + int rid = ld.getRoofId(roofIdx); + if (rid > 0 && !hiddenRoofIds.get(rid)) { // draw the roof - assert roofEnd[roofIdx] >= roofStart[roofIdx]; - if (roofEnd[roofIdx] > roofStart[roofIdx]) { - pushRange(roofStart[roofIdx], roofEnd[roofIdx]); - } + int roofStart = ld.getRoofStart(roofIdx); + int roofEnd = ld.getRoofEnd(roofIdx); + if (roofEnd > roofStart) + pushRange(roofStart, roofEnd); } } // push from the end of the last roof to the end of the level - int endpos = level == 0 ? 0 : this.levelOffsets[level - 1]; - for (int roofIdx = rids.length - 1; roofIdx >= 0; --roofIdx) { - int rid = rids[roofIdx]; + int endpos = level == 0 ? 0 : levelData[level - 1].offset; + for (int roofIdx = ld.ridCount - 1; roofIdx >= 0; --roofIdx) { + int rid = ld.getRoofId(roofIdx); if (rid > 0) { - endpos = roofEnd[roofIdx]; + endpos = ld.getRoofEnd(roofIdx); break; } } // draw the non roofs - pushRange(endpos, this.levelOffsets[level]); + pushRange(endpos, levelData[level].offset); } if (drawIdx == 0) @@ -378,7 +370,7 @@ void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) void renderOpaqueLevel(CommandBuffer cmd, int level) { drawIdx = 0; - pushRange(this.levelOffsets[level - 1], this.levelOffsets[level]); + pushRange(levelData[level - 1].offset, levelData[level].offset); if (drawIdx == 0) return; @@ -403,59 +395,10 @@ private static void pushRange(int start, int end) { } } - public static class AlphaModel { - int id; - ModelOverride modelOverride; - int startpos, endpos; - short x, y, z; // local position - short rid; - int vao; - int tboF; - byte level; - byte lx, lz, ux, uz; // lower/upper zone coords - byte zofx, zofz; // for temp alpha models, offset of source zone from target zone - byte flags; - - // only set for static geometry as they require sorting - int radius; - int[] packedFaces; - int[] sortedFaces; - int sortedFacesLen; - - int dist; - int asyncSortIdx = -1; - - static final int SKIP = 1; // temporary model is in a closer zone - static final int TEMP = 2; // temporary model added to a closer zone - static final int SORT_COMPLETED = 4; - - void setSorted() { - flags |= SORT_COMPLETED; - } - - boolean needsSorting() { - return (flags & SORT_COMPLETED) == 0; - } - - boolean isTemp() { - return packedFaces == null || sortedFaces == null; - } - - void setView(DynamicModelVAO.View view) { - vao = view.vao; - tboF = view.tboTexId; - startpos = view.getStartOffset(); - endpos = view.getEndOffset(); - } - } - - static final ConcurrentLinkedQueue modelCache = new ConcurrentLinkedQueue<>(); void addAlphaModel( HdPlugin plugin, MaterialManager materialManager, - int vao, - int tboF, Model model, ModelOverride modelOverride, int startpos, @@ -479,8 +422,6 @@ void addAlphaModel( m.x = (short) x; m.y = (short) y; m.z = (short) z; - m.vao = vao; - m.tboF = tboF; m.rid = (short) rid; m.level = (byte) level; if (lx > -1) { @@ -615,9 +556,7 @@ void addAlphaModel( } synchronized AlphaModel requestTempAlphaModel(ModelOverride modelOverride, int level, int x, int y, int z) { - AlphaModel m = modelCache.poll(); - if (m == null) - m = new AlphaModel(); + AlphaModel m = AlphaModel.POOL.acquire(); m.id = -1; m.modelOverride = modelOverride; m.x = (short) x; @@ -641,7 +580,7 @@ synchronized void postAlphaPass() { alphaModels.remove(i); m.packedFaces = null; m.sortedFaces = null; - modelCache.add(m); + AlphaModel.POOL.recycle(m); } m.asyncSortIdx = -1; m.flags &= ~(AlphaModel.SKIP | AlphaModel.SORT_COMPLETED); @@ -659,19 +598,6 @@ synchronized void postAlphaPass() { private static int lastTboF; private static int lastzx, lastzz; - private static final class AlphaSortPredicate implements ToIntFunction { - int cx, cy, cz; - int zx, zz; - - @Override - public int applyAsInt(AlphaModel m) { - final int mx = m.x + ((zx - m.zofx) << 10); - final int mz = m.z + ((zz - m.zofz) << 10); - final int my = m.y; - return (mx - cx) * (mx - cx) + (my - cy) * (my - cy) + (mz - cz) * (mz - cz); - } - } - private final AlphaSortPredicate alphaSortPred = new AlphaSortPredicate(); private final Comparator alphaSortComparator = Comparator.comparingInt(alphaSortPred).reversed(); @@ -688,7 +614,8 @@ synchronized void alphaSort(int zx, int zz, Camera camera) { void alphaStaticModelSort(Camera camera) { alphaSortingJob.reset(); - for (AlphaModel m : alphaModels) { + for (int i = 0; i < alphaModels.size(); i++) { + final AlphaModel m = alphaModels.get(i); if ((m.flags & AlphaModel.SKIP) != 0 || m.isTemp()) continue; @@ -713,10 +640,10 @@ void renderAlpha( int minLevel = ctx.minLevel; int currentLevel = ctx.level; int maxLevel = ctx.maxLevel; - var hiddenRoofIds = ctx.hideRoofIds; + var hiddenRoofIds = ZoneRenderer.hiddenRoofIdsBitField; if (includeRoof) { maxLevel = 3; - hiddenRoofIds = Collections.emptySet(); + hiddenRoofIds = EMPTY_SET; } drawIdx = 0; @@ -733,7 +660,7 @@ void renderAlpha( continue; if (level < minLevel || level > maxLevel || - level > currentLevel && !hiddenRoofIds.isEmpty() && hiddenRoofIds.contains((int) m.rid)) + level > currentLevel && m.rid >= 0 && !hiddenRoofIds.isEmpty() && hiddenRoofIds.get(m.rid)) continue; int drawMode = STATIC; @@ -868,9 +795,7 @@ synchronized void multizoneLocs(SceneContext ctx, int zx, int zz, Camera camera, assert z != null; assert z != this; - AlphaModel m2 = modelCache.poll(); - if (m2 == null) - m2 = new AlphaModel(); + AlphaModel m2 = AlphaModel.POOL.acquire(); m2.id = m.id; m2.modelOverride = m.modelOverride; m2.startpos = m.startpos; @@ -902,4 +827,91 @@ synchronized void multizoneLocs(SceneContext ctx, int zx, int zz, Camera camera, } } } + + private static final class AlphaSortPredicate implements ToIntFunction { + int cx, cy, cz; + int zx, zz; + + @Override + public int applyAsInt(AlphaModel m) { + final int mx = m.x + ((zx - m.zofx) << 10); + final int mz = m.z + ((zz - m.zofz) << 10); + final int my = m.y; + return (mx - cx) * (mx - cx) + (my - cy) * (my - cy) + (mz - cz) * (mz - cz); + } + } + + static class AlphaModel { + static final ConcurrentPool POOL = new ConcurrentPool<>(AlphaModel::new); + + int id; + ModelOverride modelOverride; + int startpos, endpos; + short x, y, z; // local position + short rid; + int vao; + int tboF; + byte level; + byte lx, lz, ux, uz; // lower/upper zone coords + byte zofx, zofz; // for temp alpha models, offset of source zone from target zone + byte flags; + + // only set for static geometry as they require sorting + int radius; + int[] packedFaces; + int[] sortedFaces; + int sortedFacesLen; + + int dist; + int asyncSortIdx = -1; + + static final int SKIP = 1; // temporary model is in a closer zone + static final int TEMP = 2; // temporary model added to a closer zone + static final int SORT_COMPLETED = 4; + + void setSorted() { + flags |= SORT_COMPLETED; + } + + boolean needsSorting() { + return (flags & SORT_COMPLETED) == 0; + } + + boolean isTemp() { + return packedFaces == null || sortedFaces == null; + } + void setView(DynamicModelVAO.View view) { + vao = view.vao; + tboF = view.tboTexId; + startpos = view.getStartOffset(); + endpos = view.getEndOffset(); + } + } + + public final class LevelData { + public int offset; + public int ridCount; + + private int roofDataStart = -1; + + void setRoofId(int i, int rid) { roofData[roofDataStart + (i * 3)] = rid; } + + int getRoofId(int i) { return roofData[roofDataStart + (i * 3)]; } + int getRoofStart(int i) { return roofData[roofDataStart + (i * 3) + 1]; } + int getRoofEnd(int i) { return roofData[roofDataStart + (i * 3) + 2]; } + + int addRoof(int[] roofData, int pos, int rid, int start, int end) { + assert end >= start : String.format("rid: %d %d >= %d", rid, end, start); + if(roofDataStart == -1) + roofDataStart = pos; + + assert pos + 3 <= roofData.length; + roofData[pos++] = rid; + roofData[pos++] = start; + roofData[pos++] = end; + + ridCount++; + return pos; + } + } } diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index be35d19b5c..5a81315070 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -27,6 +27,8 @@ import com.google.inject.Injector; import java.io.IOException; import java.util.Arrays; +import java.util.BitSet; +import java.util.HashSet; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; @@ -157,6 +159,9 @@ public class ZoneRenderer implements Renderer { public static GLBuffer.EBO eboAlpha; public static GLMappedBufferIntWriter eboAlphaWriter; + public final Set hiddenRoofIdsSet = new HashSet<>(); + public static final BitSet hiddenRoofIdsBitField = new BitSet(Short.MAX_VALUE); + private boolean sceneFboValid; private boolean shouldRenderScene; private boolean shouldClearShadowFbo; @@ -207,6 +212,9 @@ public void destroy() { sceneManager.destroy(); uboWorldViews.destroy(); + ZoneUploadJob.POOL.destroy(); + GpuIntBuffer.POOL.destroy(); + if (SceneUploader.POOL != null) SceneUploader.POOL.destroy(); @@ -275,6 +283,28 @@ public void processConfigChanges(Set keys) { modelStreamingManager.reinitialize(); } + private void buildHiddenRoofBitField(Set hiddenRoofIds) { + boolean hasChanged = false; + for(Integer id : hiddenRoofIdsSet) { + if(!hiddenRoofIds.contains(id)) { + hiddenRoofIdsBitField.set(id, false); + hasChanged = true; + } + } + + for(Integer id : hiddenRoofIds) { + if(!hiddenRoofIdsSet.contains(id)) { + hiddenRoofIdsBitField.set(id, true); + hasChanged = true; + } + } + + if(hasChanged) { + hiddenRoofIdsSet.clear(); + hiddenRoofIdsSet.addAll(hiddenRoofIds); + } + } + @Override public void preSceneDraw( Scene scene, @@ -294,16 +324,18 @@ public void preSceneDraw( } frameTimer.begin(Timer.DRAW_PRESCENE); + ctx.minLevel = minLevel; ctx.level = level; ctx.maxLevel = maxLevel; - ctx.hideRoofIds = hideRoofIds; ctx.vaoSceneCmd.reset(); ctx.vaoDirectionalCmd.reset(); if (ctx.uboWorldViewStruct != null) ctx.uboWorldViewStruct.update(); + buildHiddenRoofBitField(hideRoofIds); + if (scene.getWorldViewId() == WorldView.TOPLEVEL) preSceneDrawTopLevel(scene, cameraX, cameraY, cameraZ, cameraPitch, cameraYaw); @@ -881,7 +913,7 @@ public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int return; Zone z = ctx.zones[zx][zz]; - if (!z.initialized || z.sizeO == 0) + if (!z.initialized || z.sizeIntsOpaque == 0) return; frameTimer.begin(Timer.DRAW_ZONE_OPAQUE); @@ -923,7 +955,7 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i modelStreamingManager.ensureAsyncUploadsComplete(z); - final boolean hasAlpha = z.sizeA != 0 || !z.alphaModels.isEmpty(); + final boolean hasAlpha = z.sizeIntsAlpha != 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 diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java b/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java index e7f41ee7a0..4df9af183d 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java @@ -1,95 +1,66 @@ package rs117.hd.renderer.zone; -import java.util.concurrent.ConcurrentLinkedQueue; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.*; import rs117.hd.utils.DestructibleHandler; -import rs117.hd.utils.buffer.GLBuffer; -import rs117.hd.utils.buffer.GLTextureBuffer; +import rs117.hd.utils.buffer.GpuIntBuffer; +import rs117.hd.utils.collections.ConcurrentPool; import rs117.hd.utils.jobs.Job; -import static org.lwjgl.opengl.GL33C.*; -import static rs117.hd.utils.buffer.GLBuffer.MAP_WRITE; - @Slf4j public final class ZoneUploadJob extends Job { - private static final ConcurrentLinkedQueue POOL = new ConcurrentLinkedQueue<>(); + public static final ConcurrentPool POOL = new ConcurrentPool<>(ZoneUploadJob::new); private WorldViewContext viewContext; private ZoneSceneContext sceneContext; + private GpuIntBuffer opaqueStaging; + private GpuIntBuffer alphaStaging; + private GpuIntBuffer tboStaging; - Zone zone; - int x, z; - long revealAfterTimestampMs; - boolean shouldUnmap; + @Getter + private Zone zone; + @Getter + private int x, z; + @Getter + private long revealAfterTimestampMs; @Override protected void onRun() throws InterruptedException { try (SceneUploader sceneUploader = SceneUploader.POOL.acquire()) { workerHandleCancel(); + opaqueStaging = GpuIntBuffer.POOL.acquire(); + alphaStaging = GpuIntBuffer.POOL.acquire(); + tboStaging = GpuIntBuffer.POOL.acquire(); + sceneUploader.onBeforeProcessTile = this::onBeforeProcessTile; sceneUploader.setScene(sceneContext.scene); - sceneUploader.estimateZoneSize(sceneContext, zone, x, z); - - if (zone.sizeO > 0 || zone.sizeA > 0) { - workerHandleCancel(); - - invokeClientCallback(this::mapZoneVertexBuffers); - workerHandleCancel(); - - sceneUploader.uploadZone(sceneContext, zone, x, z); - workerHandleCancel(); - - if (shouldUnmap) - invokeClientCallback(zone::unmap); - } - zone.initialized = true; + sceneUploader.uploadZone(sceneContext, zone, x, z, opaqueStaging, alphaStaging, tboStaging); } } - private void onBeforeProcessTile(Tile t, boolean isEstimate) throws InterruptedException { + private void onBeforeProcessTile(Tile t) throws InterruptedException { workerHandleCancel(); } - private void mapZoneVertexBuffers() { - try { - GLBuffer o = null, a = null; - int sz = zone.sizeO * Zone.VERT_SIZE * 3; - if (sz > 0) { - o = new GLBuffer("Zone::VBO::Opaque", GL_ARRAY_BUFFER, GL_STATIC_DRAW); - o.initialize(sz); - o.map(MAP_WRITE); - } - - sz = zone.sizeA * Zone.VERT_SIZE * 3; - if (sz > 0) { - a = new GLBuffer("Zone::VBO::Alpha", GL_ARRAY_BUFFER, GL_STATIC_DRAW); - a.initialize(sz); - a.map(MAP_WRITE); - } - - GLTextureBuffer f = null; - sz = zone.sizeF * Zone.TEXTURE_SIZE; - if (sz > 0) { - f = new GLTextureBuffer("Zone::TBO", GL_STATIC_DRAW); - f.initialize(sz); - f.map(MAP_WRITE); - } - - zone.initialize(o, a, f); - zone.setMetadata(viewContext, sceneContext, x, z); - } catch (Throwable ex) { - log.warn( - "Caught exception whilst processing zone [{}, {}] worldId [{}] group priority [{}] cancelling...\n", - x, - z, - viewContext.worldViewId, - isHighPriority(), - ex - ); - cancel(); - } + @Override + protected void onCompletion() { + if(opaqueStaging == null || alphaStaging == null || tboStaging == null) + return; + + zone.sizeIntsOpaque = opaqueStaging.position(); + zone.sizeIntsAlpha = alphaStaging.position(); + zone.sizeIntsFace = tboStaging.position(); + + if(zone.sizeIntsOpaque <= 0 && zone.sizeIntsAlpha <= 0 && zone.sizeIntsFace <= 0) + return; + + opaqueStaging.flip(); + alphaStaging.flip(); + tboStaging.flip(); + + zone.initialize(viewContext, sceneContext, x, z, opaqueStaging, alphaStaging, tboStaging); } @Override @@ -104,37 +75,56 @@ protected void onCancel() { @Override protected void onReleased() { + if(opaqueStaging != null) + GpuIntBuffer.POOL.recycle(opaqueStaging.clear()); + opaqueStaging = null; + + if(alphaStaging != null) + GpuIntBuffer.POOL.recycle(alphaStaging.clear()); + alphaStaging = null; + + if(tboStaging != null) + GpuIntBuffer.POOL.recycle(tboStaging.clear()); + tboStaging = null; + viewContext = null; sceneContext = null; zone.uploadJob = null; zone = null; revealAfterTimestampMs = 0; - assert !POOL.contains(this) : "Task is already in pool"; - POOL.add(this); + POOL.recycle(this); } public static ZoneUploadJob build( WorldViewContext viewContext, ZoneSceneContext sceneContext, Zone zone, - boolean shouldUnmap, int x, int z + ) { + return build(viewContext, sceneContext, zone, x, z, 0); + } + + public static ZoneUploadJob build( + WorldViewContext viewContext, + ZoneSceneContext sceneContext, + Zone zone, + int x, + int z, + long revealAfterTimestampMs ) { assert viewContext != null : "WorldViewContext cant be null"; assert sceneContext != null : "ZoneSceneContext cant be null"; assert zone != null : "Zone cant be null"; assert !zone.initialized : "Zone is already initialized"; - ZoneUploadJob newTask = POOL.poll(); - if (newTask == null) - newTask = new ZoneUploadJob(); + ZoneUploadJob newTask = POOL.acquire(); newTask.viewContext = viewContext; newTask.sceneContext = sceneContext; newTask.zone = zone; - newTask.shouldUnmap = shouldUnmap; newTask.x = x; newTask.z = z; + newTask.revealAfterTimestampMs = revealAfterTimestampMs; newTask.isReleased = false; return newTask; diff --git a/src/main/java/rs117/hd/utils/buffer/GLBuffer.java b/src/main/java/rs117/hd/utils/buffer/GLBuffer.java index 755ba238e3..ddaa2cc36f 100644 --- a/src/main/java/rs117/hd/utils/buffer/GLBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GLBuffer.java @@ -366,6 +366,22 @@ public GLBuffer initialize(long initialCapacity) { return this; } + public GLBuffer initialize(java.nio.Buffer data) { + id = glGenBuffers(); + glBindBuffer(target, id); + if(data instanceof ByteBuffer) { + glBufferData(target, (ByteBuffer) data, usage); + } else if(data instanceof IntBuffer) { + glBufferData(target, (IntBuffer) data, usage); + } else if(data instanceof FloatBuffer) { + glBufferData(target, (FloatBuffer) data, usage); + } else { + throw new IllegalArgumentException("Unsupported data type: " + data.getClass()); + } + unbind(); + return this; + } + public void setName(String newName) { if (newName != null && !newName.equals(name)) { name = newName; diff --git a/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java b/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java index 1b58183741..f9bf688499 100644 --- a/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java @@ -19,7 +19,17 @@ public GLTextureBuffer(String name, int usage, int storageFlags) { @Override public GLTextureBuffer initialize(long initialCapacity) { super.initialize(initialCapacity); + initializeTexBuffer(); + return this; + } + + public GLTextureBuffer initialize(java.nio.Buffer data) { + super.initialize(data); + initializeTexBuffer(); + return this; + } + private void initializeTexBuffer() { // Create texture texId = glGenTextures(); glBindTexture(target, texId); @@ -28,7 +38,6 @@ public GLTextureBuffer initialize(long initialCapacity) { glTexBuffer(target, GL_RGB32I, id); glBindTexture(target, 0); - return this; } @Override diff --git a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java b/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java index cdf5c6e79a..40585a2766 100644 --- a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java @@ -29,11 +29,14 @@ import lombok.extern.slf4j.Slf4j; import org.lwjgl.system.MemoryUtil; import rs117.hd.HdPlugin; +import rs117.hd.utils.collections.ConcurrentPool; import static rs117.hd.utils.MathUtils.*; @Slf4j -public class GpuIntBuffer { +public class GpuIntBuffer implements AutoCloseable { + public static final ConcurrentPool POOL = new ConcurrentPool<>(GpuIntBuffer::new); + @Getter private IntBuffer buffer; private final boolean ownsBuffer; @@ -195,4 +198,10 @@ public GpuIntBuffer ensureCapacity(int size) { return this; } + + @Override + public void close() { + buffer.clear(); + POOL.recycle(this); + } } diff --git a/src/main/java/rs117/hd/utils/collections/PrimitiveIntArray.java b/src/main/java/rs117/hd/utils/collections/PrimitiveIntArray.java index a680dadb35..a6c0f75833 100644 --- a/src/main/java/rs117/hd/utils/collections/PrimitiveIntArray.java +++ b/src/main/java/rs117/hd/utils/collections/PrimitiveIntArray.java @@ -20,6 +20,23 @@ public PrimitiveIntArray ensureCapacity(int count) { return this; } + public boolean contains(int i) { + for (int j = 0; j < length; j++) + if (array[j] == i) + return true; + return false; + } + + public void addUnique(int i) { + if(!contains(i)) + add(i); + } + + public void add(int i) { + ensureCapacity(1); + array[length++] = i; + } + public void put(int i) { if (length < array.length) array[length++] = i; diff --git a/src/main/java/rs117/hd/utils/jobs/Job.java b/src/main/java/rs117/hd/utils/jobs/Job.java index af2ef9575b..5b61783a7f 100644 --- a/src/main/java/rs117/hd/utils/jobs/Job.java +++ b/src/main/java/rs117/hd/utils/jobs/Job.java @@ -46,8 +46,10 @@ public final boolean waitForCompletion(int timeoutNanos) { log.warn("Job {} was interrupted while waiting for completion", this); throw new RuntimeException(e); } finally { - if (completed) + if (completed) { handle.release(); + onCompletion(); + } } } else { completed = true; diff --git a/src/main/java/rs117/hd/utils/jobs/JobHandle.java b/src/main/java/rs117/hd/utils/jobs/JobHandle.java index 9d38fb5740..ac2cb3a1df 100644 --- a/src/main/java/rs117/hd/utils/jobs/JobHandle.java +++ b/src/main/java/rs117/hd/utils/jobs/JobHandle.java @@ -9,6 +9,7 @@ import javax.annotation.Nullable; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import rs117.hd.utils.collections.ConcurrentPool; import static rs117.hd.utils.HDUtils.getThreadStackTrace; import static rs117.hd.utils.jobs.JobSystem.VALIDATE; @@ -25,7 +26,7 @@ final class JobHandle extends AbstractQueuedSynchronizer { private static final String[] STATE_NAMES = { "NONE", "QUEUED", "RUNNING", "CANCELLED", "COMPLETED" }; private static final long DEADLOCK_TIMEOUT_SECONDS = 10; - private static final ConcurrentLinkedQueue POOL = new ConcurrentLinkedQueue<>(); + private static final ConcurrentPool POOL = new ConcurrentPool<>(JobHandle::new); private static final ThreadLocal> CYCLE_STACK = ThreadLocal.withInitial(ArrayDeque::new); private static final ThreadLocal> VISITED = ThreadLocal.withInitial(HashSet::new); @@ -43,10 +44,10 @@ final class JobHandle extends AbstractQueuedSynchronizer { boolean highPriority; static JobHandle obtain() { - JobHandle handle = POOL.poll(); + JobHandle handle = POOL.acquire(); if (handle == null || handle.refCounter.get() > 0) { if (handle != null) { - POOL.add(handle); // Re-add to the end of the pool + POOL.recycle(handle); // Re-add to the end of the pool } handle = new JobHandle(); } @@ -148,9 +149,6 @@ synchronized void setCompleted() throws InterruptedException { // Signal completion via AQS releaseShared(0); - if (item != null) - item.onCompletion(); - if (VALIDATE) log.debug("Handle [{}] Completed", this); @@ -200,7 +198,6 @@ synchronized void release() { assert item != null : "Double Release, item is already null"; assert isCompleted() : "Release before setCompleted() has been called?!"; - assert !VALIDATE || !POOL.contains(this) : "POOL already contains this Handle?!"; if (VALIDATE) log.debug("Releasing [{}] state: [{}]", this, STATE_NAMES[jobState.get()]); setJobState(STATE_NONE); @@ -208,7 +205,7 @@ synchronized void release() { item = null; worker = null; - POOL.add(this); + POOL.recycle(this); } void cancel(boolean block) throws InterruptedException { @@ -293,6 +290,8 @@ boolean await(int timeoutNanos) throws InterruptedException { } finally { refCounter.decrementAndGet(); } + + return true; }