mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-10-25 20:22:22 +00:00
Fix voxeliation performance under SDL GPU (#2188)
This commit is contained in:
1 parent
db7d328481
commit
80e5b544c0
15 files changed
+415
-216
No files matched your search
@@ -268,9 +268,8 @@ public abstract class RenderBackend {
|
||||
|
||||
public String getTransferDebugInfo() { return null; }
|
||||
|
||||
public boolean bindVoxelizationRegion(int ssboBinding, long openPass, float x, float y, float z) { return false; }
|
||||
public long beginVoxelizationBatch(int ssboBinding) { return 0L; }
|
||||
public void voxelizeRange(long pass, int vertexOffset, int vertexCount) {}
|
||||
public void voxelizeRegion(long pass, boolean rebindVertexBuffer, float x, float y, float z, int rangeBase, int rangeCount, int vertexTotal) {}
|
||||
public void endVoxelizationBatch(long pass) {}
|
||||
|
||||
public abstract void enable(int cap);
|
||||
|
||||
@@ -4338,19 +4338,11 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return frameManager.getFrameNumber();
|
||||
}
|
||||
|
||||
private int voxLocStart = -1;
|
||||
private int voxLocCount = -1;
|
||||
|
||||
@Override public boolean bindVoxelizationRegion(int ssboBinding, long openPass, float x, float y, float z) {
|
||||
if (ssboBinding < 0 || ssboBinding >= ContextState.MAX_INDEXED_BUFFERS) return false;
|
||||
final ContextState st = s();
|
||||
if (st.boundProgram == 0) return false;
|
||||
if (st.boundSsboByIndex[ssboBinding] == 0) return false;
|
||||
final int loc = shaderManager.getUniformLocation(st.boundProgram, "u_RegionOffset");
|
||||
if (loc >= 0) GLStateManager.glUniform3f(loc, x, y, z);
|
||||
if (openPass != 0) voxelizationDispatcher.rebindVertexBuffer(st, openPass);
|
||||
return true;
|
||||
}
|
||||
private int voxLocRangeBase = -1;
|
||||
private int voxLocRangeCount = -1;
|
||||
private int voxLocVertexTotal = -1;
|
||||
private int voxLocInvocationBase = -1;
|
||||
private int voxLocRegionOffset = -1;
|
||||
|
||||
/** Reflected at link time, so it names exactly the images the compute writes rather than everything bound. */
|
||||
public String[] getComputeWrittenImageNames(int programId) {
|
||||
@@ -4365,13 +4357,19 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final ContextState st = s();
|
||||
final int program = st.boundProgram;
|
||||
if (program == 0 || st.boundSsboByIndex[ssboBinding] == 0) return 0;
|
||||
voxLocStart = shaderManager.getUniformLocation(program, "_vg_startVertex");
|
||||
voxLocCount = shaderManager.getUniformLocation(program, "_vg_vertexCount");
|
||||
voxLocRangeBase = shaderManager.getUniformLocation(program, "_vg_rangeBase");
|
||||
voxLocRangeCount = shaderManager.getUniformLocation(program, "_vg_rangeCount");
|
||||
voxLocVertexTotal = shaderManager.getUniformLocation(program, "_vg_vertexTotal");
|
||||
voxLocInvocationBase = shaderManager.getUniformLocation(program, "_vg_invocationBase");
|
||||
voxLocRegionOffset = shaderManager.getUniformLocation(program, "u_RegionOffset");
|
||||
return voxelizationDispatcher.beginBatch(st);
|
||||
}
|
||||
|
||||
@Override public void voxelizeRange(long pass, int vertexOffset, int vertexCount) {
|
||||
voxelizationDispatcher.dispatchRange(pass, voxLocStart, voxLocCount, vertexOffset, vertexCount, s());
|
||||
@Override public void voxelizeRegion(long pass, boolean rebindVertexBuffer, float x, float y, float z, int rangeBase, int rangeCount, int vertexTotal) {
|
||||
final ContextState st = s();
|
||||
if (voxLocRegionOffset >= 0) GLStateManager.glUniform3f(voxLocRegionOffset, x, y, z);
|
||||
if (rebindVertexBuffer) voxelizationDispatcher.rebindVertexBuffer(st, pass);
|
||||
voxelizationDispatcher.dispatchRegion(pass, voxLocRangeBase, voxLocRangeCount, voxLocVertexTotal, voxLocInvocationBase, rangeBase, rangeCount, vertexTotal, st);
|
||||
}
|
||||
|
||||
@Override public void endVoxelizationBatch(long pass) {
|
||||
|
||||
+11
-10
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
|
||||
public final class VoxelizationDispatcher {
|
||||
static final int MAX_GROUPS_PER_DISPATCH = 65535;
|
||||
private static final int WORKGROUP_SIZE = 64;
|
||||
|
||||
private final ComputeDispatchSink computeBinder;
|
||||
@@ -20,19 +21,19 @@ public final class VoxelizationDispatcher {
|
||||
computeBinder.rebindRoStorageBuffers(st, pass);
|
||||
}
|
||||
|
||||
public void dispatchRange(long pass, int locStart, int locCount, int vertexOffset, int vertexCount, ContextState st) {
|
||||
if (pass == 0 || vertexCount <= 0) return;
|
||||
if (locStart >= 0) GLStateManager.glUniform1i(locStart, vertexOffset);
|
||||
if (locCount >= 0) GLStateManager.glUniform1i(locCount, vertexCount);
|
||||
computeBinder.pushPendingComputeUniforms(st);
|
||||
final int groups = (vertexCount + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
|
||||
computeBinder.dispatchInBatch(pass, groups, 1, 1);
|
||||
public void dispatchRegion(long pass, int locBase, int locCount, int locTotal, int locInvBase, int rangeBase, int rangeCount, int vertexTotal, ContextState st) {
|
||||
if (locBase >= 0) GLStateManager.glUniform1i(locBase, rangeBase);
|
||||
if (locCount >= 0) GLStateManager.glUniform1i(locCount, rangeCount);
|
||||
if (locTotal >= 0) GLStateManager.glUniform1i(locTotal, vertexTotal);
|
||||
final int groups = (vertexTotal + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
|
||||
for (int g0 = 0; g0 < groups; g0 += MAX_GROUPS_PER_DISPATCH) {
|
||||
if (locInvBase >= 0) GLStateManager.glUniform1i(locInvBase, g0 * WORKGROUP_SIZE);
|
||||
computeBinder.pushPendingComputeUniforms(st);
|
||||
computeBinder.dispatchInBatch(pass, Math.min(MAX_GROUPS_PER_DISPATCH, groups - g0), 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void endBatch(long pass) {
|
||||
if (pass == 0) return;
|
||||
computeBinder.endBatchedComputeDispatch(pass);
|
||||
}
|
||||
|
||||
public static int workgroupSize() { return WORKGROUP_SIZE; }
|
||||
}
|
||||
+23
-35
@@ -8,7 +8,6 @@ import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class VoxelizationDispatcherTest {
|
||||
|
||||
@@ -42,25 +41,29 @@ class VoxelizationDispatcherTest {
|
||||
assertFalse(sink.lastCycleRwBuffers, "voxelization must open the batched pass with cycle=false on rw SSBO bindings");
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchRange_pushesUniformsBeforeEachDispatch() {
|
||||
final RecordingSink sink = new RecordingSink();
|
||||
final VoxelizationDispatcher d = new VoxelizationDispatcher(sink);
|
||||
final ContextState st = new ContextState();
|
||||
d.dispatchRange(PASS_HANDLE, -1, -1, 0, 64, st);
|
||||
d.dispatchRange(PASS_HANDLE, -1, -1, 64, 64, st);
|
||||
assertEquals(2, sink.dispatches.size());
|
||||
assertTrue(sink.uniformPushCount >= sink.dispatches.size(), "pushPendingComputeUniforms must run at least once per dispatchInBatch so per-range uniforms land; pushes=" + sink.uniformPushCount + " dispatches=" + sink.dispatches.size());
|
||||
private static void region(VoxelizationDispatcher d, int rangeCount, int vertexTotal, ContextState st) {
|
||||
d.dispatchRegion(PASS_HANDLE, -1, -1, -1, -1, 0, rangeCount, vertexTotal, st);
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchRange_roundsGroupsUpToWorkgroupSize() {
|
||||
void dispatchRegion_pushesUniformsBeforeEachDispatch() {
|
||||
final RecordingSink sink = new RecordingSink();
|
||||
final VoxelizationDispatcher d = new VoxelizationDispatcher(sink);
|
||||
final ContextState st = new ContextState();
|
||||
d.dispatchRange(PASS_HANDLE, -1, -1, 0, 1, st);
|
||||
d.dispatchRange(PASS_HANDLE, -1, -1, 0, 64, st);
|
||||
d.dispatchRange(PASS_HANDLE, -1, -1, 0, 65, st);
|
||||
region(d, 1, 64, st);
|
||||
region(d, 3, 200, st);
|
||||
assertEquals(2, sink.dispatches.size());
|
||||
assertEquals(sink.dispatches.size(), sink.uniformPushCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchRegion_roundsGroupsUpToWorkgroupSize() {
|
||||
final RecordingSink sink = new RecordingSink();
|
||||
final VoxelizationDispatcher d = new VoxelizationDispatcher(sink);
|
||||
final ContextState st = new ContextState();
|
||||
region(d, 1, 1, st);
|
||||
region(d, 1, 64, st);
|
||||
region(d, 1, 65, st);
|
||||
assertEquals(1, sink.dispatches.get(0)[0]);
|
||||
assertEquals(1, sink.dispatches.get(1)[0]);
|
||||
assertEquals(2, sink.dispatches.get(2)[0]);
|
||||
@@ -71,24 +74,14 @@ class VoxelizationDispatcherTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchRange_ignoresEmptyRangeAndNullPass() {
|
||||
void dispatchRegion_splitsAboveMaxGroups() {
|
||||
final RecordingSink sink = new RecordingSink();
|
||||
final VoxelizationDispatcher d = new VoxelizationDispatcher(sink);
|
||||
final ContextState st = new ContextState();
|
||||
d.dispatchRange(PASS_HANDLE, -1, -1, 0, 0, st);
|
||||
d.dispatchRange(0, -1, -1, 0, 64, st);
|
||||
assertEquals(0, sink.dispatches.size());
|
||||
assertEquals(0, sink.uniformPushCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void endBatch_ignoresNullPass() {
|
||||
final RecordingSink sink = new RecordingSink();
|
||||
final VoxelizationDispatcher d = new VoxelizationDispatcher(sink);
|
||||
d.endBatch(0);
|
||||
assertEquals(0, sink.endCount);
|
||||
d.endBatch(PASS_HANDLE);
|
||||
assertEquals(1, sink.endCount);
|
||||
region(d, 1, VoxelizationDispatcher.MAX_GROUPS_PER_DISPATCH * 64 + 1, new ContextState());
|
||||
assertEquals(2, sink.dispatches.size());
|
||||
assertEquals(VoxelizationDispatcher.MAX_GROUPS_PER_DISPATCH, sink.dispatches.get(0)[0]);
|
||||
assertEquals(1, sink.dispatches.get(1)[0]);
|
||||
assertEquals(2, sink.uniformPushCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,9 +92,4 @@ class VoxelizationDispatcherTest {
|
||||
assertEquals(1, sink.rebindCount, "a region change inside one encoder must rebind the vertex buffer");
|
||||
assertEquals(0, sink.beginCount, "rebinding must not open a second pass");
|
||||
}
|
||||
|
||||
@Test
|
||||
void constants_matchExpectations() {
|
||||
assertEquals(64, VoxelizationDispatcher.workgroupSize());
|
||||
}
|
||||
}
|
||||
-32
@@ -9,18 +9,13 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlVoxelRegionBindTest {
|
||||
|
||||
private static final int BOUND_SLOT = 5;
|
||||
private static final int EMPTY_SLOT = 6;
|
||||
private static final int UNLINKED_PROGRAM = Integer.MAX_VALUE;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
@@ -49,33 +44,6 @@ class GlsmSdlVoxelRegionBindTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void regionHookRefusesWithoutProgramOrBoundSlotAndNeverRebinds() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
final int savedProgram = st.boundProgram;
|
||||
final int buf = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
unbind(EMPTY_SLOT);
|
||||
GLStateManager.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, BOUND_SLOT, buf);
|
||||
|
||||
st.boundProgram = 0;
|
||||
assertFalse(RENDER_BACKEND.bindVoxelizationRegion(BOUND_SLOT, 0L, 0f, 0f, 0f), "no program bound");
|
||||
assertEquals(buf, st.boundSsboByIndex[BOUND_SLOT], "refusal must leave the slot alone");
|
||||
|
||||
st.boundProgram = UNLINKED_PROGRAM;
|
||||
assertFalse(RENDER_BACKEND.bindVoxelizationRegion(EMPTY_SLOT, 0L, 0f, 0f, 0f), "slot has no buffer");
|
||||
assertEquals(0, st.boundSsboByIndex[EMPTY_SLOT], "the hook must not bind a buffer itself");
|
||||
|
||||
assertTrue(RENDER_BACKEND.bindVoxelizationRegion(BOUND_SLOT, 0L, 1f, 2f, 3f), "a GLSM-bound slot with a program must be accepted, or the refusals prove nothing");
|
||||
assertEquals(buf, st.boundSsboByIndex[BOUND_SLOT]);
|
||||
assertEquals(buf, glsmGenericSsbo(), "GLSM and SDL must still agree after the hook");
|
||||
} finally {
|
||||
st.boundProgram = savedProgram;
|
||||
unbind(BOUND_SLOT);
|
||||
GLStateManager.glDeleteBuffers(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletingTheRegionBufferClearsTheGlsmGenericBinding() {
|
||||
final int buf = GLStateManager.glGenBuffers();
|
||||
|
||||
+19
-6
@@ -225,12 +225,22 @@ class MslCrossCompileTest {
|
||||
layout(local_size_x = 64) in;
|
||||
layout(binding = 0, r32i) uniform iimage2D endcrystal_img;
|
||||
layout(binding = 1, r16ui) writeonly uniform uimage3D voxel_img;
|
||||
uniform int _vg_startVertex;
|
||||
uniform int _vg_vertexCount;
|
||||
uniform int _vg_rangeBase;
|
||||
uniform int _vg_rangeCount;
|
||||
uniform int _vg_vertexTotal;
|
||||
uniform int _vg_invocationBase;
|
||||
layout(std430, binding = 9) readonly buffer _VgVbuf { uint data[]; } _vg_vbuf;
|
||||
layout(std430, binding = 10) readonly buffer _VgRanges { uvec2 r[]; } _vg_ranges;
|
||||
void main() {
|
||||
int id = _vg_startVertex + int(gl_GlobalInvocationID.x);
|
||||
if (id >= _vg_startVertex + _vg_vertexCount) return;
|
||||
uint _vg_g = uint(_vg_invocationBase) + gl_GlobalInvocationID.x;
|
||||
if (_vg_g >= uint(_vg_vertexTotal)) return;
|
||||
int _vg_lo = _vg_rangeBase;
|
||||
int _vg_hi = _vg_rangeBase + _vg_rangeCount - 1;
|
||||
while (_vg_lo < _vg_hi) {
|
||||
int _vg_mid = (_vg_lo + _vg_hi + 1) >> 1;
|
||||
if (_vg_ranges.r[_vg_mid].y <= _vg_g) _vg_lo = _vg_mid; else _vg_hi = _vg_mid - 1;
|
||||
}
|
||||
int id = int(_vg_ranges.r[_vg_lo].x + (_vg_g - _vg_ranges.r[_vg_lo].y));
|
||||
imageAtomicAdd(endcrystal_img, ivec2(id, 0), 1);
|
||||
imageStore(voxel_img, ivec3(0), uvec4(uint(id), 0u, 0u, 0u));
|
||||
}
|
||||
@@ -301,9 +311,12 @@ class MslCrossCompileTest {
|
||||
#version 460 core
|
||||
layout(local_size_x = 64) in;
|
||||
layout(binding = 0, r32i) uniform iimage2D endcrystal_img;
|
||||
uniform int _vg_startVertex;
|
||||
uniform int _vg_vertexCount;
|
||||
uniform int _vg_rangeBase;
|
||||
uniform int _vg_rangeCount;
|
||||
uniform int _vg_vertexTotal;
|
||||
uniform int _vg_invocationBase;
|
||||
layout(std430, binding = 9) readonly buffer _VgVbuf { uint data[]; } _vg_vbuf;
|
||||
layout(std430, binding = 10) readonly buffer _VgRanges { uvec2 r[]; } _vg_ranges;
|
||||
void main() {
|
||||
{ int _vg_prev = imageLoad(endcrystal_img, ivec2(0, 0)).x;
|
||||
imageStore(endcrystal_img, ivec2(0, 0), ivec4(_vg_prev + 1, 0, 0, 0)); }
|
||||
|
||||
@@ -51,7 +51,7 @@ public final class TracyFramePlots {
|
||||
private static final long P_TERRAIN_DRAW_COMMANDS = Tracy.plotHandle("terrain.drawCommands");
|
||||
private static final long P_TERRAIN_REGIONS_DRAWN = Tracy.plotHandle("terrain.regionsDrawn");
|
||||
private static final long P_VOX_ENCODERS = Tracy.plotHandle("voxelization.encoders");
|
||||
private static final long P_VOX_DISPATCHES = Tracy.plotHandle("voxelization.dispatches");
|
||||
private static final long P_VOX_RANGES = Tracy.plotHandle("voxelization.ranges");
|
||||
private static final long P_VOX_REGIONS = Tracy.plotHandle("voxelization.regions");
|
||||
private static final long P_DRAW_BATCH_REBUILDS = Tracy.plotHandle("draw.batchRebuilds");
|
||||
private static final PlotDelta P_DRAW_INDEX_BUFFER_GROWTHS = new PlotDelta("draw.indexBufferGrowths");
|
||||
@@ -201,7 +201,7 @@ public final class TracyFramePlots {
|
||||
Tracy.plotInt(P_TERRAIN_REGIONS_DRAWN, TerrainDrawStats.takeRegionsDrawn());
|
||||
Tracy.plotInt(P_DRAW_BATCH_REBUILDS, TerrainDrawStats.takeBatchRebuilds());
|
||||
Tracy.plotInt(P_VOX_ENCODERS, SdlShadowVoxelizationSink.takeEncoders());
|
||||
Tracy.plotInt(P_VOX_DISPATCHES, SdlShadowVoxelizationSink.takeDispatches());
|
||||
Tracy.plotInt(P_VOX_RANGES, SdlShadowVoxelizationSink.takeRanges());
|
||||
Tracy.plotInt(P_VOX_REGIONS, SdlShadowVoxelizationSink.takeRegions());
|
||||
Tracy.plotInt(P_CULL_REGIONS_DRAWN, TerrainDrawStats.takeCullRegionsDrawn());
|
||||
Tracy.plotInt(P_CULL_MAX_REGION_COMMANDS, TerrainDrawStats.takeCullMaxRegionCommands());
|
||||
|
||||
+4
-1
@@ -73,10 +73,10 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
private int packTerrainSamplerAnisotropy = -1;
|
||||
private boolean terrainSamplerBound;
|
||||
private final GpuTerrainCuller culler;
|
||||
private final SdlShadowVoxelizationSink shadowVoxelSink = new SdlShadowVoxelizationSink();
|
||||
private final ReusableCachedBatch gpuBatch = new ReusableCachedBatch();
|
||||
|
||||
private static final ShadowVoxelizer shadowVoxelizer = new ShadowVoxelizer();
|
||||
private static final SdlShadowVoxelizationSink shadowVoxelSink = new SdlShadowVoxelizationSink();
|
||||
private static int loggedVoxelizationSkips;
|
||||
private static MultiDrawMode installedBatchMode;
|
||||
|
||||
@@ -277,6 +277,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
if (culler != null) {
|
||||
culler.delete();
|
||||
}
|
||||
shadowVoxelSink.delete();
|
||||
|
||||
unbindTerrainSampler();
|
||||
RenderSystem.destroySampler(terrainSampler);
|
||||
@@ -423,7 +424,9 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
deferred.prepareShadowVoxelizationCompute(matrices.modelView());
|
||||
try {
|
||||
shadowVoxelizer.walkPass(renderLists, renderPass, renderPass.vertexType().getVertexFormat(), camera, occlusionCamera, useBlockFaceCulling(), shadowVoxelSink);
|
||||
shadowVoxelSink.flush();
|
||||
} finally {
|
||||
shadowVoxelSink.discard();
|
||||
if (prevProgram != 0) GLStateManager.glUseProgram(prevProgram);
|
||||
}
|
||||
}
|
||||
|
||||
+61
-46
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.rendering.voxelization;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import net.coderbot.iris.pipeline.transform.RwImageStoreExtractor;
|
||||
@@ -9,24 +10,25 @@ import org.embeddedt.embeddium.impl.gl.buffer.GlBuffer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class SdlShadowVoxelizationSink implements ShadowVoxelizer.Sink {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica");
|
||||
private static final int SSBO_BINDING = RwImageStoreExtractor.VG_VBUF_SSBO_BINDING;
|
||||
private static final int VBUF_BINDING = RwImageStoreExtractor.VG_VBUF_SSBO_BINDING;
|
||||
private static final int RANGES_BINDING = RwImageStoreExtractor.VG_RANGES_SSBO_BINDING;
|
||||
|
||||
private static int encodersThisFrame;
|
||||
private static int dispatchesThisFrame;
|
||||
private static int regionsThisFrame;
|
||||
private static int rangesThisFrame;
|
||||
|
||||
private boolean warnedBindFailure;
|
||||
private final VoxelRangeTable table = new VoxelRangeTable();
|
||||
private int rangesBuf;
|
||||
private long rangesCapacityBytes;
|
||||
private boolean warnedBeginFailure;
|
||||
private long pass;
|
||||
private int pendingVertexBuffer;
|
||||
private float pendingX;
|
||||
private float pendingY;
|
||||
private float pendingZ;
|
||||
|
||||
private static RenderBackend sdl() {
|
||||
return BackendManager.RENDER_BACKEND;
|
||||
@@ -38,55 +40,68 @@ public final class SdlShadowVoxelizationSink implements ShadowVoxelizer.Sink {
|
||||
if (resources == null) return false;
|
||||
final GlBuffer vertexBuffer = resources.getVertexBuffer();
|
||||
if (vertexBuffer == null || vertexBuffer.handle() == 0) return false;
|
||||
pendingVertexBuffer = vertexBuffer.handle();
|
||||
pendingX = offsetX;
|
||||
pendingY = offsetY;
|
||||
pendingZ = offsetZ;
|
||||
table.beginRegion(vertexBuffer.handle(), offsetX, offsetY, offsetZ);
|
||||
regionsThisFrame++;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean range(int vertexOffset, int vertexCount) {
|
||||
if (pendingVertexBuffer != 0) {
|
||||
GLStateManager.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, SSBO_BINDING, pendingVertexBuffer);
|
||||
if (!sdl().bindVoxelizationRegion(SSBO_BINDING, pass, pendingX, pendingY, pendingZ)) {
|
||||
if (!warnedBindFailure) {
|
||||
warnedBindFailure = true;
|
||||
LOG.warn("shadow voxelization: could not bind region vertex buffer {} at SSBO slot {}", pendingVertexBuffer, SSBO_BINDING);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
pendingVertexBuffer = 0;
|
||||
}
|
||||
if (pass == 0) {
|
||||
pass = sdl().beginVoxelizationBatch(SSBO_BINDING);
|
||||
if (pass != 0) encodersThisFrame++;
|
||||
if (pass == 0) {
|
||||
if (!warnedBeginFailure) {
|
||||
warnedBeginFailure = true;
|
||||
LOG.warn("shadow voxelization: compute encoder refused; no voxel writes this pass");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sdl().voxelizeRange(pass, vertexOffset, vertexCount);
|
||||
dispatchesThisFrame++;
|
||||
return true;
|
||||
public void range(int vertexOffset, int vertexCount) {
|
||||
table.addRange(vertexOffset, vertexCount);
|
||||
}
|
||||
|
||||
public static int takeEncoders() { final int n = encodersThisFrame; encodersThisFrame = 0; return n; }
|
||||
|
||||
public static int takeDispatches() { final int n = dispatchesThisFrame; dispatchesThisFrame = 0; return n; }
|
||||
|
||||
public static int takeRegions() { final int n = regionsThisFrame; regionsThisFrame = 0; return n; }
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
if (pass != 0) {
|
||||
sdl().endVoxelizationBatch(pass);
|
||||
pass = 0;
|
||||
public static int takeRanges() { final int n = rangesThisFrame; rangesThisFrame = 0; return n; }
|
||||
|
||||
public void flush() {
|
||||
final int regions = table.regionCount();
|
||||
if (regions == 0) return;
|
||||
upload();
|
||||
RenderSystem.bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, VBUF_BINDING, table.regionVbuf(0));
|
||||
final long pass = sdl().beginVoxelizationBatch(VBUF_BINDING);
|
||||
if (pass == 0) {
|
||||
if (!warnedBeginFailure) {
|
||||
warnedBeginFailure = true;
|
||||
LOG.warn("shadow voxelization: compute encoder refused; no voxel writes this pass");
|
||||
}
|
||||
return;
|
||||
}
|
||||
pendingVertexBuffer = 0;
|
||||
encodersThisFrame++;
|
||||
rangesThisFrame += table.entryCount();
|
||||
for (int i = 0; i < regions; i++) {
|
||||
if (i > 0) RenderSystem.bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, VBUF_BINDING, table.regionVbuf(i));
|
||||
sdl().voxelizeRegion(pass, i > 0, table.regionX(i), table.regionY(i), table.regionZ(i), table.regionRangeBase(i), table.regionRangeCount(i), table.regionVertexTotal(i));
|
||||
}
|
||||
sdl().endVoxelizationBatch(pass);
|
||||
}
|
||||
|
||||
private void upload() {
|
||||
final ByteBuffer data = table.entries();
|
||||
final int bytes = data.remaining();
|
||||
if (rangesBuf == 0) rangesBuf = GLStateManager.glGenBuffers();
|
||||
RenderSystem.bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, RANGES_BINDING, rangesBuf);
|
||||
if (rangesCapacityBytes < bytes) {
|
||||
long cap = Math.max(rangesCapacityBytes, 4096L);
|
||||
while (cap < bytes) cap <<= 1;
|
||||
rangesCapacityBytes = cap;
|
||||
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, rangesCapacityBytes, GL15.GL_STREAM_DRAW);
|
||||
}
|
||||
GLStateManager.glBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, 0L, data);
|
||||
}
|
||||
|
||||
public void discard() {
|
||||
table.clear();
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (rangesBuf != 0) {
|
||||
GLStateManager.glDeleteBuffers(rangesBuf);
|
||||
rangesBuf = 0;
|
||||
rangesCapacityBytes = 0;
|
||||
}
|
||||
table.free();
|
||||
}
|
||||
}
|
||||
+12
-19
@@ -19,27 +19,20 @@ public final class ShadowVoxelizer {
|
||||
/** @return false to skip this region */
|
||||
boolean region(RenderRegion region, GlVertexFormat format, float offsetX, float offsetY, float offsetZ);
|
||||
|
||||
/** @return false to skip the rest of this region; later regions are still attempted */
|
||||
boolean range(int vertexOffset, int vertexCount);
|
||||
|
||||
void finish();
|
||||
void range(int vertexOffset, int vertexCount);
|
||||
}
|
||||
|
||||
public void walkPass(ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, GlVertexFormat format, CameraTransform camera, CameraTransform occlusionCamera, boolean useBlockFaceCulling, Sink sink) {
|
||||
try {
|
||||
final boolean reverse = renderPass.isReverseOrder();
|
||||
final int n = renderLists.getNumRegions();
|
||||
if (renderPass.isSorted()) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
walkRegionFull(renderLists.getRegion(reverse ? n - 1 - i : i), renderPass, format, camera, sink);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
walkRegionCompact(renderLists.getRegion(reverse ? n - 1 - i : i), renderPass, format, camera, occlusionCamera, useBlockFaceCulling, sink);
|
||||
}
|
||||
final boolean reverse = renderPass.isReverseOrder();
|
||||
final int n = renderLists.getNumRegions();
|
||||
if (renderPass.isSorted()) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
walkRegionFull(renderLists.getRegion(reverse ? n - 1 - i : i), renderPass, format, camera, sink);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
walkRegionCompact(renderLists.getRegion(reverse ? n - 1 - i : i), renderPass, format, camera, occlusionCamera, useBlockFaceCulling, sink);
|
||||
}
|
||||
} finally {
|
||||
sink.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +78,7 @@ public final class ShadowVoxelizer {
|
||||
if (!bindRegion(region, format, camera, sink)) return;
|
||||
bound = true;
|
||||
}
|
||||
if (!sink.range(startVertex, vertexCount)) return;
|
||||
sink.range(startVertex, vertexCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +115,7 @@ public final class ShadowVoxelizer {
|
||||
if (!bindRegion(region, format, camera, sink)) return;
|
||||
bound = true;
|
||||
}
|
||||
if (!sink.range(startVertex, vertexCount)) return;
|
||||
sink.range(startVertex, vertexCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.gtnewhorizons.angelica.rendering.voxelization;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memRealloc;
|
||||
|
||||
public final class VoxelRangeTable {
|
||||
private static final int ENTRY_BYTES = 8;
|
||||
|
||||
private ByteBuffer entries;
|
||||
private long entriesAddr;
|
||||
private int entryCount;
|
||||
private int runEnd;
|
||||
|
||||
private int regionCount;
|
||||
private int[] regVbuf = new int[16];
|
||||
private float[] regX = new float[16];
|
||||
private float[] regY = new float[16];
|
||||
private float[] regZ = new float[16];
|
||||
private int[] regBase = new int[16];
|
||||
private int[] regCount = new int[16];
|
||||
private int[] regTotal = new int[16];
|
||||
|
||||
public void beginRegion(int vbufGlId, float x, float y, float z) {
|
||||
final int g = regionCount++;
|
||||
if (g == regVbuf.length) {
|
||||
final int n = g << 1;
|
||||
regVbuf = Arrays.copyOf(regVbuf, n);
|
||||
regX = Arrays.copyOf(regX, n);
|
||||
regY = Arrays.copyOf(regY, n);
|
||||
regZ = Arrays.copyOf(regZ, n);
|
||||
regBase = Arrays.copyOf(regBase, n);
|
||||
regCount = Arrays.copyOf(regCount, n);
|
||||
regTotal = Arrays.copyOf(regTotal, n);
|
||||
}
|
||||
regVbuf[g] = vbufGlId;
|
||||
regX[g] = x;
|
||||
regY[g] = y;
|
||||
regZ[g] = z;
|
||||
regBase[g] = entryCount;
|
||||
regCount[g] = 0;
|
||||
regTotal[g] = 0;
|
||||
}
|
||||
|
||||
public void addRange(int vertexStart, int vertexCount) {
|
||||
final int g = regionCount - 1;
|
||||
if (regCount[g] > 0 && vertexStart == runEnd) {
|
||||
regTotal[g] += vertexCount;
|
||||
runEnd += vertexCount;
|
||||
return;
|
||||
}
|
||||
if (entries == null) {
|
||||
entries = memAlloc(4096);
|
||||
entriesAddr = memAddress0(entries);
|
||||
} else if ((entryCount + 1) * ENTRY_BYTES > entries.capacity()) {
|
||||
entries = memRealloc(entries, entries.capacity() << 1);
|
||||
entriesAddr = memAddress0(entries);
|
||||
}
|
||||
final long p = entriesAddr + (long) entryCount * ENTRY_BYTES;
|
||||
memPutInt(p, vertexStart);
|
||||
memPutInt(p + 4, regTotal[g]);
|
||||
entryCount++;
|
||||
regCount[g]++;
|
||||
regTotal[g] += vertexCount;
|
||||
runEnd = vertexStart + vertexCount;
|
||||
}
|
||||
|
||||
public ByteBuffer entries() {
|
||||
entries.position(0).limit(entryCount * ENTRY_BYTES);
|
||||
return entries;
|
||||
}
|
||||
|
||||
public int entryCount() {
|
||||
return entryCount;
|
||||
}
|
||||
|
||||
public int regionCount() {
|
||||
return regionCount;
|
||||
}
|
||||
|
||||
public int regionVbuf(int i) {
|
||||
return regVbuf[i];
|
||||
}
|
||||
|
||||
public float regionX(int i) {
|
||||
return regX[i];
|
||||
}
|
||||
|
||||
public float regionY(int i) {
|
||||
return regY[i];
|
||||
}
|
||||
|
||||
public float regionZ(int i) {
|
||||
return regZ[i];
|
||||
}
|
||||
|
||||
public int regionRangeBase(int i) {
|
||||
return regBase[i];
|
||||
}
|
||||
|
||||
public int regionRangeCount(int i) {
|
||||
return regCount[i];
|
||||
}
|
||||
|
||||
public int regionVertexTotal(int i) {
|
||||
return regTotal[i];
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
regionCount = 0;
|
||||
entryCount = 0;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
if (entries != null) {
|
||||
memFree(entries);
|
||||
entries = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ public final class RwImageStoreExtractor {
|
||||
private RwImageStoreExtractor() {}
|
||||
|
||||
public static final int VG_VBUF_SSBO_BINDING = 9;
|
||||
public static final int VG_RANGES_SSBO_BINDING = 10;
|
||||
private static final int STRIDE_BYTES = 48;
|
||||
private static final int STRIDE_UINTS = STRIDE_BYTES / 4;
|
||||
|
||||
@@ -561,9 +562,12 @@ public final class RwImageStoreExtractor {
|
||||
private static void emitChunkPrelude(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared, List<AttributeSlot> attrs) {
|
||||
out.append("layout(local_size_x = 64) in;\n\n");
|
||||
emitImageDecls(out, writtenImages, nonWriteonlyImages, declared);
|
||||
out.append("\nuniform int _vg_startVertex;\n");
|
||||
out.append("uniform int _vg_vertexCount;\n\n");
|
||||
out.append("layout(std430, binding = ").append(VG_VBUF_SSBO_BINDING).append(") readonly buffer _VgVbuf { uint data[]; } _vg_vbuf;\n\n");
|
||||
out.append("\nuniform int _vg_rangeBase;\n");
|
||||
out.append("uniform int _vg_rangeCount;\n");
|
||||
out.append("uniform int _vg_vertexTotal;\n");
|
||||
out.append("uniform int _vg_invocationBase;\n\n");
|
||||
out.append("layout(std430, binding = ").append(VG_VBUF_SSBO_BINDING).append(") readonly buffer _VgVbuf { uint data[]; } _vg_vbuf;\n");
|
||||
out.append("layout(std430, binding = ").append(VG_RANGES_SSBO_BINDING).append(") readonly buffer _VgRanges { uvec2 r[]; } _vg_ranges;\n\n");
|
||||
out.append("vec4 _vg_sink_pos;\n");
|
||||
out.append("float _vg_sink_psize;\n");
|
||||
out.append("int _vg_id_global;\n");
|
||||
@@ -626,8 +630,15 @@ public final class RwImageStoreExtractor {
|
||||
out.append("void main() {\n");
|
||||
switch (mode) {
|
||||
case CHUNK -> {
|
||||
out.append(" int id = _vg_startVertex + int(gl_GlobalInvocationID.x);\n");
|
||||
out.append(" if (id >= _vg_startVertex + _vg_vertexCount) return;\n");
|
||||
out.append(" uint _vg_g = uint(_vg_invocationBase) + gl_GlobalInvocationID.x;\n");
|
||||
out.append(" if (_vg_g >= uint(_vg_vertexTotal)) return;\n");
|
||||
out.append(" int _vg_lo = _vg_rangeBase;\n");
|
||||
out.append(" int _vg_hi = _vg_rangeBase + _vg_rangeCount - 1;\n");
|
||||
out.append(" while (_vg_lo < _vg_hi) {\n");
|
||||
out.append(" int _vg_mid = (_vg_lo + _vg_hi + 1) >> 1;\n");
|
||||
out.append(" if (_vg_ranges.r[_vg_mid].y <= _vg_g) _vg_lo = _vg_mid; else _vg_hi = _vg_mid - 1;\n");
|
||||
out.append(" }\n");
|
||||
out.append(" int id = int(_vg_ranges.r[_vg_lo].x + (_vg_g - _vg_ranges.r[_vg_lo].y));\n");
|
||||
out.append(" _vg_id_global = id;\n");
|
||||
out.append(" _vg_unpack(uint(id));\n");
|
||||
emitVertexDecode(out, attrs);
|
||||
|
||||
+1
-39
@@ -36,9 +36,7 @@ class ShadowVoxelizerTest {
|
||||
private static class RecordingSink implements ShadowVoxelizer.Sink {
|
||||
final List<float[]> regions = new ArrayList<>();
|
||||
final List<int[]> ranges = new ArrayList<>();
|
||||
int finishes;
|
||||
boolean refuseRegion;
|
||||
boolean refuseRange;
|
||||
|
||||
@Override
|
||||
public boolean region(RenderRegion region, GlVertexFormat format, float x, float y, float z) {
|
||||
@@ -48,15 +46,8 @@ class ShadowVoxelizerTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean range(int vertexOffset, int vertexCount) {
|
||||
if (refuseRange) return false;
|
||||
public void range(int vertexOffset, int vertexCount) {
|
||||
ranges.add(new int[]{vertexOffset, vertexCount});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
finishes++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +291,6 @@ class ShadowVoxelizerTest {
|
||||
|
||||
assertTrue(sink.regions.isEmpty(), "an empty region must not open an encoder");
|
||||
assertTrue(sink.ranges.isEmpty());
|
||||
assertEquals(1, sink.finishes);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -311,7 +301,6 @@ class ShadowVoxelizerTest {
|
||||
final RecordingSink sink = walk(lists(renderList(region, 0)), renderPass, new CameraTransform(0, 0, 0), false);
|
||||
|
||||
assertTrue(sink.ranges.isEmpty());
|
||||
assertEquals(1, sink.finishes);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -334,33 +323,6 @@ class ShadowVoxelizerTest {
|
||||
new CameraTransform(0, 0, 0), new CameraTransform(0, 0, 0), false, sink);
|
||||
|
||||
assertTrue(sink.ranges.isEmpty(), "a region that cannot be bound emits nothing");
|
||||
assertEquals(1, sink.finishes, "the encoder is still closed exactly once");
|
||||
}
|
||||
|
||||
@Test
|
||||
void refusedRangeSkipsOnlyThatRegion() {
|
||||
final TerrainRenderPass renderPass = pass("solid");
|
||||
final RenderRegion regionA = region(0, 0, 0);
|
||||
final RenderRegion regionB = region(1, 0, 0);
|
||||
final long heapA = heap();
|
||||
final long heapB = heap();
|
||||
final SectionRenderDataStorage storageA = storage(heapA);
|
||||
final SectionRenderDataStorage storageB = storage(heapB);
|
||||
writeSection(heapA, 0, 0, 4, 0, 4, 0, 0, 0, 0);
|
||||
writeSection(heapB, 0, 0, 4, 0, 0, 0, 0, 0, 0);
|
||||
attachStorage(regionA, renderPass, storageA);
|
||||
attachStorage(regionB, renderPass, storageB);
|
||||
|
||||
final RecordingSink sink = new RecordingSink() {
|
||||
@Override public boolean range(int vertexOffset, int vertexCount) {
|
||||
return regions.size() != 1 && super.range(vertexOffset, vertexCount);
|
||||
}
|
||||
};
|
||||
new ShadowVoxelizer().walkPass(lists(renderList(regionA, 0), renderList(regionB, 0)), renderPass, null, new CameraTransform(0, 0, 0), new CameraTransform(0, 0, 0), false, sink);
|
||||
|
||||
assertEquals(2, sink.regions.size(), "the second region is still attempted after the first is refused");
|
||||
assertEquals(1, sink.ranges.size(), "only the second region's range lands");
|
||||
assertEquals(1, sink.finishes);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package com.gtnewhorizons.angelica.rendering.voxelization;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class VoxelRangeTableTest {
|
||||
|
||||
private final VoxelRangeTable t = new VoxelRangeTable();
|
||||
|
||||
@AfterEach
|
||||
void free() {
|
||||
t.free();
|
||||
}
|
||||
|
||||
private int entry(int i) {
|
||||
return memGetInt(memAddress0(t.entries()) + i * 4L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleRegionPrefixes() {
|
||||
t.beginRegion(7, 1f, 2f, 3f);
|
||||
t.addRange(100, 10);
|
||||
t.addRange(200, 20);
|
||||
t.addRange(50, 5);
|
||||
assertEquals(1, t.regionCount());
|
||||
assertEquals(7, t.regionVbuf(0));
|
||||
assertEquals(1f, t.regionX(0));
|
||||
assertEquals(2f, t.regionY(0));
|
||||
assertEquals(3f, t.regionZ(0));
|
||||
assertEquals(0, t.regionRangeBase(0));
|
||||
assertEquals(3, t.regionRangeCount(0));
|
||||
assertEquals(35, t.regionVertexTotal(0));
|
||||
assertEquals(3, t.entryCount());
|
||||
assertEquals(24, t.entries().remaining());
|
||||
assertEquals(100, entry(0));
|
||||
assertEquals(0, entry(1));
|
||||
assertEquals(200, entry(2));
|
||||
assertEquals(10, entry(3));
|
||||
assertEquals(50, entry(4));
|
||||
assertEquals(30, entry(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void regionsAppendInOrderAndNeverMergeAcross() {
|
||||
t.beginRegion(1, 0f, 0f, 0f);
|
||||
t.addRange(0, 4);
|
||||
t.addRange(40, 6);
|
||||
t.beginRegion(2, 5f, 0f, 0f);
|
||||
t.addRange(46, 8);
|
||||
t.addRange(2000, 2);
|
||||
assertEquals(2, t.regionCount());
|
||||
assertEquals(1, t.regionVbuf(0));
|
||||
assertEquals(0f, t.regionX(0));
|
||||
assertEquals(0, t.regionRangeBase(0));
|
||||
assertEquals(2, t.regionRangeCount(0));
|
||||
assertEquals(10, t.regionVertexTotal(0));
|
||||
assertEquals(2, t.regionVbuf(1));
|
||||
assertEquals(5f, t.regionX(1));
|
||||
assertEquals(2, t.regionRangeBase(1));
|
||||
assertEquals(2, t.regionRangeCount(1));
|
||||
assertEquals(10, t.regionVertexTotal(1));
|
||||
assertEquals(4, t.entryCount());
|
||||
assertEquals(0, entry(0));
|
||||
assertEquals(0, entry(1));
|
||||
assertEquals(40, entry(2));
|
||||
assertEquals(4, entry(3));
|
||||
assertEquals(46, entry(4));
|
||||
assertEquals(0, entry(5));
|
||||
assertEquals(2000, entry(6));
|
||||
assertEquals(8, entry(7));
|
||||
}
|
||||
|
||||
@Test
|
||||
void contiguousRangesMerge() {
|
||||
t.beginRegion(3, 0f, 0f, 0f);
|
||||
t.addRange(100, 10);
|
||||
t.addRange(110, 5);
|
||||
t.addRange(200, 3);
|
||||
t.addRange(300, 2);
|
||||
assertEquals(3, t.regionRangeCount(0));
|
||||
assertEquals(20, t.regionVertexTotal(0));
|
||||
assertEquals(3, t.entryCount());
|
||||
assertEquals(100, entry(0));
|
||||
assertEquals(0, entry(1));
|
||||
assertEquals(200, entry(2));
|
||||
assertEquals(15, entry(3));
|
||||
assertEquals(300, entry(4));
|
||||
assertEquals(18, entry(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void growsAndClearsForReuse() {
|
||||
for (int i = 0; i < 300; i++) {
|
||||
t.beginRegion(i, 0f, 0f, 0f);
|
||||
t.addRange(i * 100, 3);
|
||||
t.addRange(i * 100 + 50, 3);
|
||||
}
|
||||
assertEquals(300, t.regionCount());
|
||||
assertEquals(600, t.entryCount());
|
||||
assertEquals(4800, t.entries().remaining());
|
||||
assertEquals(299 * 100 + 50, entry(1198));
|
||||
assertEquals(3, entry(1199));
|
||||
assertEquals(598, t.regionRangeBase(299));
|
||||
t.clear();
|
||||
t.beginRegion(5, 1f, 1f, 1f);
|
||||
t.addRange(9, 2);
|
||||
assertEquals(1, t.regionCount());
|
||||
assertEquals(1, t.entryCount());
|
||||
assertEquals(0, t.regionRangeBase(0));
|
||||
assertEquals(2, t.regionVertexTotal(0));
|
||||
assertEquals(9, entry(0));
|
||||
}
|
||||
}
|
||||
@@ -79,8 +79,14 @@ class RwImageStoreExtractorTest {
|
||||
@Test void compute_hasUniformsAndSsboBinding() {
|
||||
final var result = RwImageStoreExtractor.tryExtract(MINIMAL_VOX_VSH, PatchShaderType.VERTEX, "shadow");
|
||||
final String s = result.computeSource();
|
||||
assertTrue(s.contains("uniform int _vg_startVertex;"));
|
||||
assertTrue(s.contains("uniform int _vg_vertexCount;"));
|
||||
assertTrue(s.contains("uniform int _vg_rangeBase;"));
|
||||
assertTrue(s.contains("uniform int _vg_rangeCount;"));
|
||||
assertTrue(s.contains("uniform int _vg_vertexTotal;"));
|
||||
assertTrue(s.contains("uniform int _vg_invocationBase;"));
|
||||
assertTrue(s.contains("layout(std430, binding = 10) readonly buffer _VgRanges { uvec2 r[]; } _vg_ranges;"));
|
||||
assertTrue(s.contains("int id = int(_vg_ranges.r[_vg_lo].x + (_vg_g - _vg_ranges.r[_vg_lo].y));"));
|
||||
assertFalse(s.contains("_vg_startVertex"));
|
||||
assertFalse(s.contains("_vg_vertexCount"));
|
||||
assertTrue(s.contains("readonly buffer _VgVbuf"));
|
||||
assertTrue(s.contains("layout(std430, binding = " + RwImageStoreExtractor.VG_VBUF_SSBO_BINDING + ") readonly buffer _VgVbuf"), "VBO SSBO must bind at the dedicated voxelization slot (not 0..8 which shaderpacks claim via buffer.N)");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user