mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-10-25 20:22:22 +00:00
SDL Vertex Replay + Dragon API (#2224)
This commit is contained in:
1 parent
3389b952d0
commit
5ed1c92590
25 files changed
+2035
-101
No files matched your search
@@ -276,6 +276,10 @@ public abstract class RenderBackend {
|
||||
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 void setVertexWriteReplay(int graphicsProgram, VertexWriteReplaySetup setup) {}
|
||||
|
||||
public boolean supportsComputeImageAtomics() { return true; }
|
||||
|
||||
public abstract void enable(int cap);
|
||||
public abstract void enablei(int cap, int index);
|
||||
public abstract void disable(int cap);
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
/**
|
||||
* How to replay a graphics program's vertex-stage image writes.
|
||||
*
|
||||
* @param inputLocations per input, the declared location or -1 for whatever the graphics program linked
|
||||
* @param images per image the replay uses, in binding order: the written ones, then the read-only ones
|
||||
* @param writtenImageCount how many of {@code images} the replay writes
|
||||
* @param samplerNames samplers the graphics program may not have, because only the stripped writes read them
|
||||
* @param samplerUnits per sampler name, the texture unit it reads at each draw, or -1 to use samplerTextures
|
||||
* @param samplerTextures per sampler name, its texture when samplerUnits is -1
|
||||
* @param samplerObjects per sampler name, the GL sampler object to sample it with when samplerUnits is -1, or 0
|
||||
* @param vertexBufferBinding storage buffer binding of the first vertex buffer the replay program declares
|
||||
* @param vertexBufferCount how many vertex buffers it declares, at consecutive bindings
|
||||
* @param indexBufferBinding storage buffer binding of its index buffer
|
||||
*/
|
||||
public record VertexWriteReplaySetup(int replayProgram, String[] inputNames, int[] inputLocations,
|
||||
IntSupplier[] images, int writtenImageCount,
|
||||
String[] samplerNames, int[] samplerUnits, IntSupplier[] samplerTextures, int[] samplerObjects,
|
||||
int vertexBufferBinding, int vertexBufferCount, int indexBufferBinding) {
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compute.VertexWriteReplayer;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compute.VoxelizationDispatcher;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.CaptureGate;
|
||||
@@ -10,6 +11,7 @@ import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendOptions;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.MainThreadPump;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VertexWriteReplaySetup;
|
||||
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
|
||||
import org.lwjglx.Lwjgl3ifyEventLoop;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
@@ -164,6 +166,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public long nextSeq() { return TransferThread.nextSeq(); }
|
||||
});
|
||||
private final PipelineApplier pipelineApplier = new PipelineApplier(frameManager, resourceManager, shaderManager, pipelineStore, fboClearTracker, persistentSync, samplerBinder, storageTextureBinder, storageBufferBinder);
|
||||
private final VertexWriteReplayer vertexWriteReplayer = new VertexWriteReplayer(shaderManager, resourceManager, pipelineApplier, computeBinder, SDLGPURenderBackend::s);
|
||||
private final AttachmentClear attachmentClear = new AttachmentClear(frameManager, pipelineStore, shaderManager);
|
||||
private final DepthStencilReadback depthStencilReadback = new DepthStencilReadback(frameManager, resourceManager, pipelineStore, shaderManager, fboClearTracker);
|
||||
private final DrawDispatch drawDispatch = new DrawDispatch(device, frameManager, resourceManager, pipelineApplier, this::enqueuePreCopied);
|
||||
@@ -301,6 +304,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
});
|
||||
pipelineApplier.setDeferredUploadSink(this::enqueuePreCopied);
|
||||
frameManager.setRenderPassEndHook(vertexWriteReplayer::flush);
|
||||
resourceManager.setTextureReplaceHook(this::flushVertexWritesBeforeTextureChange);
|
||||
frameManager.setPreRenderPassHook(() -> {
|
||||
final ContextState st = s();
|
||||
pipelineApplier.flushUniformBlocks(st);
|
||||
@@ -1207,9 +1212,11 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (st.rasterizerDiscard) return;
|
||||
final FrameState f = frameManager.frame();
|
||||
if (!f.frameActive) { f.droppedDrawsThisFrame++; return; }
|
||||
prepareVertexWrites(st);
|
||||
if (mode == GL11.GL_TRIANGLE_FAN && count >= 3) {
|
||||
if (!drawDispatch.drawTriangleFanAsTriangleList(st, first, count)) { f.droppedDrawsThisFrame++; return; }
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysFan", mode, count, 0, first, 0);
|
||||
captureVertexWrites(st, first, count, 1, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
|
||||
@@ -1218,6 +1225,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (!pipelineApplier.applyPipelineAndState(st, f)) { f.droppedDrawsThisFrame++; return; }
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arrays", mode, count, 0, first, 0);
|
||||
SDL_DrawGPUPrimitives(f.renderPass, count, 1, first, 0);
|
||||
captureVertexWrites(st, first, count, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Override public void drawElements(int mode, int count, int type, long indices) {
|
||||
@@ -1227,6 +1235,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elements", mode, count, type, firstIndex, 0);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, 1, firstIndex, 0);
|
||||
captureVertexWrites(st, 0, count, 1, type, indices, 0);
|
||||
}
|
||||
|
||||
@Override public void drawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
|
||||
@@ -1236,6 +1245,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsInstanced", mode, count, type, firstIndex, 0);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, primcount, firstIndex, 0);
|
||||
captureVertexWrites(st, 0, count, primcount, type, indices, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1246,10 +1256,12 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsBaseVertex", mode, count, type, firstIndex, baseVertex);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, 1, firstIndex, baseVertex);
|
||||
captureVertexWrites(st, 0, count, 1, type, indices, baseVertex);
|
||||
}
|
||||
|
||||
private long prepareIndexedDrawBind(ContextState st, int mode, int type, String opName) {
|
||||
if (st.rasterizerDiscard) return 0;
|
||||
prepareVertexWrites(st);
|
||||
if (!drawDispatch.prepareIndexedDraw(st, mode, type)) return 0;
|
||||
final int ebo = st.currentVao.elementBuffer;
|
||||
final long eboHandle = resourceManager.getBufferHandle(ebo);
|
||||
@@ -1278,6 +1290,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int baseVertex = MemoryUtil.memGetInt(pBaseVertex + offset);
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "multiElementsBaseVertex", mode, count, type, firstIndex, baseVertex);
|
||||
drawDispatch.issueIndexedDraw(st, rp, ebo, type, count, 1, firstIndex, baseVertex);
|
||||
captureVertexWrites(st, 0, count, 1, type, indices, baseVertex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1304,6 +1317,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsIndirect", mode, drawcount, type, (int) indirect, 0);
|
||||
SDL_DrawGPUIndexedPrimitivesIndirect(rp, indirectHandle, (int) indirect, drawcount);
|
||||
if (st.boundProgramObj != null && st.boundProgramObj.vertexWriteReplay != null) vertexWriteReplayer.noteIndirectDraw();
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
}
|
||||
@@ -1418,6 +1432,11 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
resourceManager.deleteTexture(texture);
|
||||
}
|
||||
|
||||
/** Captured vertex-write replays name textures by GL id, which a delete or reallocation would make mean something else. */
|
||||
private void flushVertexWritesBeforeTextureChange() {
|
||||
if (vertexWriteReplayer.hasPending()) frameManager.endRenderPassIfActive(FrameManager.PASS_END_COMPUTE);
|
||||
}
|
||||
|
||||
|
||||
@Override public void bindTexture(int target, int texture) {
|
||||
if (isProxyTarget(target)) return;
|
||||
@@ -4271,6 +4290,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public void memoryBarrier(int barriers) {
|
||||
// Graphics storage bindings are read-only on SDL_GPU; RW is compute-pass only
|
||||
frameManager.endCopyPassIfActive();
|
||||
// Replayed vertex image writes land when the render pass ends, and a barrier promises them to the draws after it
|
||||
if ((barriers & (GL42.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL42.GL_TEXTURE_FETCH_BARRIER_BIT)) != 0 && vertexWriteReplayer.hasPending()) {
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COMPUTE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void copyImageSubData(int srcName, int srcTarget, int srcLevel, int srcX, int srcY, int srcZ,
|
||||
@@ -4417,18 +4440,41 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
voxelizationDispatcher.endBatch(pass);
|
||||
}
|
||||
|
||||
@Override public void setVertexWriteReplay(int graphicsProgram, VertexWriteReplaySetup setup) {
|
||||
vertexWriteReplayer.setReplay(graphicsProgram, setup);
|
||||
}
|
||||
|
||||
@Override public boolean supportsComputeImageAtomics() {
|
||||
return device.supportsSpirv() || !device.supportsMsl();
|
||||
}
|
||||
|
||||
private void prepareVertexWrites(ContextState st) {
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog != null && prog.vertexWriteReplay != null) vertexWriteReplayer.ensureImageUsage(prog.vertexWriteReplay);
|
||||
}
|
||||
|
||||
private void captureVertexWrites(ContextState st, int first, int count, int instances, int indexType, long indexOffset, int baseVertex) {
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog != null && prog.vertexWriteReplay != null) {
|
||||
vertexWriteReplayer.capture(st, first, count, instances, indexType, indexOffset, baseVertex);
|
||||
if (!frameManager.isRenderPassActive()) vertexWriteReplayer.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
|
||||
if (count <= 0 || primcount <= 0) return;
|
||||
final ContextState st = s();
|
||||
if (st.rasterizerDiscard) return;
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
prepareVertexWrites(st);
|
||||
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
|
||||
pipelineApplier.ensureDrawRenderPass(st);
|
||||
if (!frameManager.isRenderPassActive()) return;
|
||||
if (!pipelineApplier.applyPipelineAndState(st)) return;
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysInstanced", mode, count, 0, first, 0);
|
||||
SDL_DrawGPUPrimitives(frameManager.getRenderPass(), count, primcount, first, 0);
|
||||
captureVertexWrites(st, first, count, primcount, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+94
-19
@@ -45,6 +45,27 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
private final ShaderManager shaderManager;
|
||||
private final SamplerBinder samplerBinder;
|
||||
private boolean droppedDispatchWarned;
|
||||
private ShaderManager.ProgramObject lastUniformProgram;
|
||||
private long lastUniformCb;
|
||||
private long[] samplerOverrides;
|
||||
private long[] rwBufferOverrides;
|
||||
private ByteBuffer[] uniformBlockOverrides;
|
||||
|
||||
public void setSamplerOverrides(long[] byUnit) {
|
||||
samplerOverrides = byUnit;
|
||||
}
|
||||
|
||||
public void setReadWriteStorageBufferOverrides(long[] byGlSlot) {
|
||||
rwBufferOverrides = byGlSlot;
|
||||
}
|
||||
|
||||
public void setUniformBlockOverrides(ByteBuffer[] byIndex) {
|
||||
uniformBlockOverrides = byIndex;
|
||||
}
|
||||
|
||||
public long samplerHandle(int samplerObject, int glTexId) {
|
||||
return samplerBinder.getSampler(samplerObject, glTexId);
|
||||
}
|
||||
|
||||
public ComputeBinder(FrameManager frameManager, ResourceManager resourceManager, ShaderManager shaderManager, SamplerBinder samplerBinder) {
|
||||
this.frameManager = frameManager;
|
||||
@@ -153,6 +174,43 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
|
||||
@Override
|
||||
public long beginBatchedComputeDispatch(ContextState st, boolean cycleRwBuffers) {
|
||||
return beginComputePass(st, cycleRwBuffers, true);
|
||||
}
|
||||
|
||||
public long beginComputePassWithoutStorageBuffers(ContextState st) {
|
||||
return beginComputePass(st, false, false);
|
||||
}
|
||||
|
||||
public boolean bindRoStorageBufferHandles(long pass, ContextState st, long[] handlesByGlSlot) {
|
||||
if (pass == 0) return false;
|
||||
final ShaderManager.ProgramObject prog = shaderManager.getProgram(st.boundProgram);
|
||||
if (prog == null || !prog.linked) return false;
|
||||
final int[] slots = prog.computeBindingMap.roSsboGlSlots();
|
||||
if (slots.length == 0) return true;
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
final PointerBuffer ptrs = stack.mallocPointer(slots.length);
|
||||
for (int i = 0; i < slots.length; i++) {
|
||||
long handle = slots[i] < handlesByGlSlot.length ? handlesByGlSlot[slots[i]] : 0;
|
||||
if (handle == 0) handle = resourceManager.getOrCreateFallbackStorageBuffer();
|
||||
if (handle == 0) return false;
|
||||
ptrs.put(i, handle);
|
||||
}
|
||||
SDL_BindGPUComputeStorageBuffers(pass, 0, ptrs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean rebindSamplers(long pass, ContextState st) {
|
||||
if (pass == 0) return false;
|
||||
final ShaderManager.ProgramObject prog = shaderManager.getProgram(st.boundProgram);
|
||||
if (prog == null || !prog.linked) return false;
|
||||
final ShaderManager.ComputeBindingMap map = prog.computeBindingMap;
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
return bindComputeSamplers(st, prog, pass, map.samplerGlSlots(), map.samplerNames(), stack);
|
||||
}
|
||||
}
|
||||
|
||||
private long beginComputePass(ContextState st, boolean cycleRwBuffers, boolean bindRoBuffers) {
|
||||
final int programId = st.boundProgram;
|
||||
if (programId == 0) return 0;
|
||||
final ShaderManager.ProgramObject prog = shaderManager.getProgram(programId);
|
||||
@@ -196,7 +254,7 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
|
||||
final boolean ok = bindComputeSamplers(st, prog, pass, samplerSlots, map.samplerNames(), stack)
|
||||
&& bindRoStorageTextures(st, prog, pass, map, stack)
|
||||
&& bindRoStorageBuffers(st, pass, roBufSlots, stack);
|
||||
&& (!bindRoBuffers || bindRoStorageBuffers(st, pass, roBufSlots, stack));
|
||||
if (!ok) {
|
||||
SDL_EndGPUComputePass(pass);
|
||||
frameManager.noteComputePassEnded();
|
||||
@@ -298,7 +356,7 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
private SDL_GPUStorageBufferReadWriteBinding.Buffer buildRwBufferBindings(ContextState st, int[] slots, MemoryStack stack, boolean cycle) {
|
||||
final SDL_GPUStorageBufferReadWriteBinding.Buffer buf = SDL_GPUStorageBufferReadWriteBinding.calloc(slots.length, stack);
|
||||
for (int i = 0; i < slots.length; i++) {
|
||||
final long handle = resolveStorageBufHandle(st, slots[i]);
|
||||
final long handle = rwBufferOverrides != null && slots[i] < rwBufferOverrides.length ? rwBufferOverrides[slots[i]] : resolveStorageBufHandle(st, slots[i]);
|
||||
|
||||
if (handle == 0) { warnMissingBinding(st, 1, "RW storage buffer slot", slots[i], true); return null; }
|
||||
buf.get(i).buffer(handle).cycle(cycle);
|
||||
@@ -319,7 +377,8 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
if (texHandle == 0) { warnMissingBinding(st, 2, "sampler texture unit", unit, true); return false; }
|
||||
warnMissingBinding(st, 2, "sampler texture unit", unit, false);
|
||||
}
|
||||
long sampHandle = samplerBinder.getSamplerForUnit(st, unit, texGlId);
|
||||
final long override = samplerOverrides != null && unit >= 0 && unit < samplerOverrides.length ? samplerOverrides[unit] : 0;
|
||||
long sampHandle = override != 0 ? override : samplerBinder.getSamplerForUnit(st, unit, texGlId);
|
||||
if (sampHandle == 0) {
|
||||
sampHandle = resourceManager.getOrCreateDefaultSampler();
|
||||
if (sampHandle == 0) { warnMissingBinding(st, 3, "sampler unit", unit, true); return false; }
|
||||
@@ -374,6 +433,28 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
return true;
|
||||
}
|
||||
|
||||
public ByteBuffer uniformBlockView(ContextState st, ShaderManager.ProgramObject prog, int i) {
|
||||
final ShaderManager.ComputeBindingMap map = prog.computeBindingMap;
|
||||
final int[] uboSlots = map.uboGlSlots();
|
||||
final int[] declaredSizes = map.uboSizes();
|
||||
if (i >= uboSlots.length) return null;
|
||||
final int slot = uboSlots[i];
|
||||
final int bufGlId = st.boundUboByIndex[slot];
|
||||
if (bufGlId == 0) return null;
|
||||
final ByteBuffer shadow = resourceManager.getUboShadow(bufGlId);
|
||||
if (shadow == null) return null;
|
||||
final int offset = st.uboRangeOffset[slot];
|
||||
if (offset < 0 || offset >= shadow.capacity()) return null;
|
||||
int size = (i < declaredSizes.length && declaredSizes[i] > 0) ? declaredSizes[i] : shadow.capacity() - offset;
|
||||
final int rangeSize = st.uboRangeSize[slot];
|
||||
if (rangeSize > 0) size = Math.min(size, rangeSize);
|
||||
size = Math.min(size, shadow.capacity() - offset);
|
||||
if (size <= 0) return null;
|
||||
final ByteBuffer view = frameManager.getUboPushView(bufGlId, shadow);
|
||||
view.position(offset).limit(offset + size);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void pushComputeUboData(ContextState st, long cb, ShaderManager.ProgramObject prog, int[] uboSlots) {
|
||||
if (prog.lastComputeUboHash == null || prog.lastComputeUboHash.length != uboSlots.length) {
|
||||
prog.lastComputeUboHash = new long[uboSlots.length];
|
||||
@@ -381,16 +462,18 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
prog.lastComputeFrame = 0;
|
||||
}
|
||||
final long curFrame = frameManager.getFrameNumber();
|
||||
final boolean cacheValid = cb == prog.lastComputeCb && curFrame == prog.lastComputeFrame;
|
||||
final boolean cacheValid = cb == prog.lastComputeCb && curFrame == prog.lastComputeFrame
|
||||
&& lastUniformProgram == prog && lastUniformCb == cb;
|
||||
final long[] cache = prog.lastComputeUboHash;
|
||||
if (!cacheValid) {
|
||||
for (int i = 0; i < cache.length; i++) cache[i] = 0L;
|
||||
prog.lastComputeCb = cb;
|
||||
prog.lastComputeFrame = curFrame;
|
||||
}
|
||||
lastUniformProgram = prog;
|
||||
lastUniformCb = cb;
|
||||
final ShaderManager.ComputeBindingMap map = prog.computeBindingMap;
|
||||
final boolean[] isDefault = map.uboIsDefaultBlock();
|
||||
final int[] declaredSizes = map.uboSizes();
|
||||
final UniformStaging us = st.uniformStaging(prog);
|
||||
for (int i = 0; i < uboSlots.length; i++) {
|
||||
final ByteBuffer view;
|
||||
@@ -398,21 +481,13 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
if (us.fsUniformBuf == null || prog.fragmentUboSize <= 0) continue;
|
||||
view = us.fsUniformBuf;
|
||||
view.position(0).limit(prog.fragmentUboSize);
|
||||
} else if (uniformBlockOverrides != null && i < uniformBlockOverrides.length && uniformBlockOverrides[i] != null) {
|
||||
view = uniformBlockOverrides[i];
|
||||
view.position(0);
|
||||
if (!view.hasRemaining()) continue;
|
||||
} else {
|
||||
final int slot = uboSlots[i];
|
||||
final int bufGlId = st.boundUboByIndex[slot];
|
||||
if (bufGlId == 0) continue;
|
||||
final ByteBuffer shadow = resourceManager.getUboShadow(bufGlId);
|
||||
if (shadow == null) continue;
|
||||
final int offset = st.uboRangeOffset[slot];
|
||||
if (offset < 0 || offset >= shadow.capacity()) continue;
|
||||
int size = (i < declaredSizes.length && declaredSizes[i] > 0) ? declaredSizes[i] : shadow.capacity() - offset;
|
||||
final int rangeSize = st.uboRangeSize[slot];
|
||||
if (rangeSize > 0) size = Math.min(size, rangeSize);
|
||||
size = Math.min(size, shadow.capacity() - offset);
|
||||
if (size <= 0) continue;
|
||||
view = frameManager.getUboPushView(bufGlId, shadow);
|
||||
view.position(offset).limit(offset + size);
|
||||
view = uniformBlockView(st, prog, i);
|
||||
if (view == null) continue;
|
||||
}
|
||||
final int pos = view.position();
|
||||
final int len = view.remaining();
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.compute;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
|
||||
public final class InvocationDispatch {
|
||||
static final int MAX_GROUPS_PER_DISPATCH = 65535;
|
||||
static final int WORKGROUP_SIZE = 64;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface IntUniformWriter {
|
||||
void write(ContextState st, int location, int value);
|
||||
}
|
||||
|
||||
private InvocationDispatch() {}
|
||||
|
||||
static void dispatch(ComputeDispatchSink sink, ContextState st, long pass, long invocations, int invocationBaseLocation, IntUniformWriter writer) {
|
||||
final long groups = (invocations + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
|
||||
for (long g0 = 0; g0 < groups; g0 += MAX_GROUPS_PER_DISPATCH) {
|
||||
if (invocationBaseLocation >= 0) writer.write(st, invocationBaseLocation, (int) (g0 * WORKGROUP_SIZE));
|
||||
sink.pushPendingComputeUniforms(st);
|
||||
sink.dispatchInBatch(pass, (int) Math.min(MAX_GROUPS_PER_DISPATCH, groups - g0), 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+635
@@ -0,0 +1,635 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.compute;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VertexWriteReplaySetup;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.opengl.GL41;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Performs the image writes of a vertex shader that SDL-GPU strips (graphics stages may only read storage images).
|
||||
* Every draw made with a program that has a {@link Replay} is captured: its vertex and index buffers, attribute layout,
|
||||
* draw range, the uniform values the replay reads and the textures it samples. When the render pass ends, the replay
|
||||
* compute shader re-runs the vertex shader for each captured vertex and performs the writes.
|
||||
*/
|
||||
public final class VertexWriteReplayer {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
|
||||
private static final int MAX_CAPTURES = 1 << 16;
|
||||
private static final int[] COMPONENT_SIZE = { 4, 2, 1, 1, 2, 2, 4, 4, 4, 4, 4, 4 };
|
||||
private static final int FIRST_PACKED_TYPE = 9;
|
||||
private static final int PACKED_10F_11F_11F = 11;
|
||||
|
||||
private static final int ENABLED = 0x2000;
|
||||
private static final int BGRA = 0x4000;
|
||||
private static final int NO_ELEMENT = 0x8000;
|
||||
private static final int MAX_DIVISOR = 0xFFFF;
|
||||
private static final int UNCHANGED = -1;
|
||||
private static final int TEXTURE_UNITS = 32;
|
||||
private static final int SAMPLER_OBJECT_OF_DRAW = -1;
|
||||
|
||||
public static final class Replay {
|
||||
final int replayProgram;
|
||||
final VertexWriteReplaySetup setup;
|
||||
|
||||
private boolean resolved;
|
||||
private boolean broken;
|
||||
int[] inputLocations;
|
||||
int[] uniformSources;
|
||||
int[] uniformTargets;
|
||||
int[] samplerUnits;
|
||||
IntSupplier[] samplerSources;
|
||||
int[] samplerObjects;
|
||||
int[] packStorageSlots;
|
||||
int[] packRwStorageSlots;
|
||||
int[] uniformBlockIndices;
|
||||
int uniformBlockCount;
|
||||
int locAttr, locAttrDefault, locDraw, locIndex, locRestart, locInvocationBase;
|
||||
long[] lastHashes;
|
||||
boolean lastHashesApplied;
|
||||
|
||||
public Replay(VertexWriteReplaySetup setup) {
|
||||
this.replayProgram = setup.replayProgram();
|
||||
this.setup = setup;
|
||||
}
|
||||
|
||||
IntSupplier[] images() {
|
||||
return setup.images();
|
||||
}
|
||||
|
||||
boolean resolve(ShaderManager.ProgramObject graphics, ShaderManager.ProgramObject replay) {
|
||||
if (resolved) return !broken;
|
||||
resolved = true;
|
||||
|
||||
final String[] inputNames = setup.inputNames();
|
||||
inputLocations = new int[inputNames.length];
|
||||
for (int i = 0; i < inputNames.length; i++) {
|
||||
int loc = graphics.resolvedAttribLocations.getInt(inputNames[i]);
|
||||
if (loc < 0) loc = graphics.attribLocationBindings.getInt(inputNames[i]);
|
||||
if (loc < 0) loc = setup.inputLocations()[i];
|
||||
inputLocations[i] = loc;
|
||||
}
|
||||
|
||||
final IntArrayList sources = new IntArrayList();
|
||||
final IntArrayList targets = new IntArrayList();
|
||||
final List<String> samplerNames = new ArrayList<>();
|
||||
for (int target = 0; target < replay.uniformSlotCount; target++) {
|
||||
final String name = replay.locationName[target];
|
||||
if (name == null || name.startsWith("_vg_")) continue;
|
||||
if (replay.locationKind[target] == ShaderManager.ProgramObject.LOCATION_KIND_SAMPLER) {
|
||||
samplerNames.add(name);
|
||||
continue;
|
||||
}
|
||||
if (replay.locationKind[target] != ShaderManager.ProgramObject.LOCATION_KIND_PLAIN) continue;
|
||||
final int source = graphics.nameToLocation.getInt(name);
|
||||
if (source < 0) continue;
|
||||
sources.add(source);
|
||||
targets.add(target);
|
||||
}
|
||||
uniformSources = sources.toIntArray();
|
||||
uniformTargets = targets.toIntArray();
|
||||
lastHashes = new long[uniformSources.length];
|
||||
resolveSamplers(graphics, replay, samplerNames);
|
||||
|
||||
final IntArrayList storage = new IntArrayList();
|
||||
for (int slot : replay.computeBindingMap.roSsboGlSlots()) {
|
||||
final boolean vertexBuffer = slot >= setup.vertexBufferBinding() && slot < setup.vertexBufferBinding() + setup.vertexBufferCount();
|
||||
if (!vertexBuffer && slot != setup.indexBufferBinding()) storage.add(slot);
|
||||
}
|
||||
packStorageSlots = storage.toIntArray();
|
||||
packRwStorageSlots = replay.computeBindingMap.rwSsboGlSlots().clone();
|
||||
final IntArrayList blocks = new IntArrayList();
|
||||
final boolean[] isDefault = replay.computeBindingMap.uboIsDefaultBlock();
|
||||
for (int i = 0; i < replay.computeBindingMap.uboGlSlots().length; i++) {
|
||||
if (i >= isDefault.length || !isDefault[i]) blocks.add(i);
|
||||
}
|
||||
uniformBlockIndices = blocks.toIntArray();
|
||||
uniformBlockCount = replay.computeBindingMap.uboGlSlots().length;
|
||||
|
||||
locAttr = replay.nameToLocation.getInt("_vg_attr");
|
||||
locAttrDefault = replay.nameToLocation.getInt("_vg_attrDefault");
|
||||
locDraw = replay.nameToLocation.getInt("_vg_draw");
|
||||
locIndex = replay.nameToLocation.getInt("_vg_index");
|
||||
locRestart = replay.nameToLocation.getInt("_vg_restart");
|
||||
locInvocationBase = replay.nameToLocation.getInt("_vg_invocationBase");
|
||||
if (locDraw < 0 || locInvocationBase < 0) {
|
||||
broken = true;
|
||||
LOG.warn("Vertex write replay program {} is missing its dispatch uniforms; its writes are dropped", replayProgram);
|
||||
}
|
||||
return !broken;
|
||||
}
|
||||
|
||||
private void resolveSamplers(ShaderManager.ProgramObject graphics, ShaderManager.ProgramObject replay, List<String> names) {
|
||||
final IntArrayList units = new IntArrayList();
|
||||
final List<IntSupplier> sources = new ArrayList<>();
|
||||
final IntArrayList objects = new IntArrayList();
|
||||
final IntArrayList ownTextureIndices = new IntArrayList();
|
||||
final List<String> ownTextureNames = new ArrayList<>();
|
||||
final List<String> pipelineNames = Arrays.asList(setup.samplerNames());
|
||||
for (String name : names) {
|
||||
int unit = graphics.samplerTextureUnits.getInt(name);
|
||||
if (unit < 0) {
|
||||
final int i = pipelineNames.indexOf(name);
|
||||
if (i < 0) continue;
|
||||
if (setup.samplerTextures()[i] != null && setup.samplerUnits()[i] < 0) {
|
||||
ownTextureNames.add(name);
|
||||
ownTextureIndices.add(i);
|
||||
continue;
|
||||
}
|
||||
unit = setup.samplerUnits()[i];
|
||||
if (unit < 0) continue;
|
||||
}
|
||||
replay.samplerTextureUnits.put(name, unit);
|
||||
if (!units.contains(unit)) {
|
||||
units.add(unit);
|
||||
sources.add(null);
|
||||
objects.add(SAMPLER_OBJECT_OF_DRAW);
|
||||
}
|
||||
}
|
||||
int free = TEXTURE_UNITS - 1;
|
||||
for (int k = 0; k < ownTextureNames.size(); k++) {
|
||||
while (free >= 0 && units.contains(free)) free--;
|
||||
if (free < 0) break;
|
||||
final int i = ownTextureIndices.getInt(k);
|
||||
replay.samplerTextureUnits.put(ownTextureNames.get(k), free);
|
||||
units.add(free);
|
||||
sources.add(setup.samplerTextures()[i]);
|
||||
objects.add(setup.samplerObjects()[i]);
|
||||
}
|
||||
samplerUnits = units.toIntArray();
|
||||
samplerSources = sources.toArray(new IntSupplier[0]);
|
||||
samplerObjects = objects.toIntArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Capture {
|
||||
Replay replay;
|
||||
long[] buffers = new long[0];
|
||||
int bufferCount;
|
||||
long indexBuffer;
|
||||
float[] attr = new float[0];
|
||||
float[] attrDefault = new float[0];
|
||||
int[] samplerTextures = new int[0];
|
||||
long[] samplerHandles = new long[0];
|
||||
int[] imageTextures = new int[0];
|
||||
long[] packStorage = new long[0];
|
||||
long[] packRwStorage = new long[0];
|
||||
ByteBuffer[] uniformBlocks = new ByteBuffer[0];
|
||||
int first, count, instances, baseVertex, indexSize, indexOffset, indexCount;
|
||||
boolean restart;
|
||||
int restartIndex;
|
||||
}
|
||||
|
||||
private final ShaderManager shaderManager;
|
||||
private final ResourceManager resourceManager;
|
||||
private final PipelineApplier pipelineApplier;
|
||||
private final ComputeBinder computeBinder;
|
||||
private final Supplier<ContextState> state;
|
||||
|
||||
private final ArrayList<Capture> captures = new ArrayList<>();
|
||||
private int pending;
|
||||
private float[] uniformValues = new float[4096];
|
||||
private int[] uniformLengths = new int[256];
|
||||
private int uniformValueCount;
|
||||
private int uniformCount;
|
||||
private boolean flushing;
|
||||
private final long[] handlesByGlSlot = new long[ContextState.MAX_INDEXED_BUFFERS];
|
||||
private final long[] rwHandlesByGlSlot = new long[ContextState.MAX_INDEXED_BUFFERS];
|
||||
private final long[] samplerOverrides = new long[TEXTURE_UNITS];
|
||||
private final int[] savedImages = new int[ContextState.MAX_IMAGE_UNITS];
|
||||
private final int[] savedLevels = new int[ContextState.MAX_IMAGE_UNITS];
|
||||
private final int[] savedAccess = new int[ContextState.MAX_IMAGE_UNITS];
|
||||
private final int[] savedTextures = new int[TEXTURE_UNITS];
|
||||
private int uniformCursor;
|
||||
private int valueCursor;
|
||||
private final InvocationDispatch.IntUniformWriter invocationBaseWriter = (st, location, value) -> putIntVector(st, location, 1, value, 0, 0, 0);
|
||||
private final Set<String> warned = new HashSet<>();
|
||||
|
||||
public VertexWriteReplayer(ShaderManager shaderManager, ResourceManager resourceManager, PipelineApplier pipelineApplier, ComputeBinder computeBinder, Supplier<ContextState> state) {
|
||||
this.shaderManager = shaderManager;
|
||||
this.resourceManager = resourceManager;
|
||||
this.pipelineApplier = pipelineApplier;
|
||||
this.computeBinder = computeBinder;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public void setReplay(int graphicsProgram, VertexWriteReplaySetup setup) {
|
||||
final ShaderManager.ProgramObject prog = shaderManager.getProgram(graphicsProgram);
|
||||
if (prog == null) return;
|
||||
if (setup == null) {
|
||||
prog.vertexWriteReplay = null;
|
||||
return;
|
||||
}
|
||||
if (setup.images().length > ContextState.MAX_IMAGE_UNITS) {
|
||||
warnOnce("images:" + setup.replayProgram(), "Vertex write replay program " + setup.replayProgram() + " uses " + setup.images().length + " images, more than the " + ContextState.MAX_IMAGE_UNITS + " image units; its writes are dropped");
|
||||
prog.vertexWriteReplay = null;
|
||||
return;
|
||||
}
|
||||
prog.vertexWriteReplay = new Replay(setup);
|
||||
}
|
||||
|
||||
public void ensureImageUsage(Replay replay) {
|
||||
final IntSupplier[] images = replay.images();
|
||||
final int written = replay.setup.writtenImageCount();
|
||||
for (int i = 0; i < images.length; i++) {
|
||||
final int texture = images[i].getAsInt();
|
||||
if (texture != 0) resourceManager.ensureTextureUsage(texture, SDLGPURenderBackend.imageUsageForAccess(imageAccess(i, written)));
|
||||
}
|
||||
}
|
||||
|
||||
public void capture(ContextState st, int first, int count, int instances, int indexType, long indexOffset, int baseVertex) {
|
||||
final ShaderManager.ProgramObject graphics = st.boundProgramObj;
|
||||
final Replay replay = graphics.vertexWriteReplay;
|
||||
final ShaderManager.ProgramObject twin = shaderManager.getProgram(replay.replayProgram);
|
||||
if (twin == null || !twin.linked || twin.sdlComputePipeline == 0) return;
|
||||
if (!replay.resolve(graphics, twin)) return;
|
||||
if (count <= 0 || instances <= 0) return;
|
||||
if (pending == MAX_CAPTURES) {
|
||||
warnOnce("captures", "More than " + MAX_CAPTURES + " draws to replay vertex image writes for in one render pass; the rest are dropped");
|
||||
return;
|
||||
}
|
||||
|
||||
final IntSupplier[] images = replay.images();
|
||||
final Capture c = acquire();
|
||||
c.replay = replay;
|
||||
if (c.buffers.length != replay.setup.vertexBufferCount()) c.buffers = new long[replay.setup.vertexBufferCount()];
|
||||
c.bufferCount = 0;
|
||||
if (!captureInputs(st, replay, c)) return;
|
||||
|
||||
if (indexType != 0) {
|
||||
c.indexBuffer = resourceManager.getBufferHandle(st.currentVao.elementBuffer);
|
||||
c.indexSize = indexType == GL11.GL_UNSIGNED_BYTE ? 1 : indexType == GL11.GL_UNSIGNED_SHORT ? 2 : 4;
|
||||
c.indexOffset = (int) indexOffset;
|
||||
if (c.indexBuffer == 0) return;
|
||||
final long indexBytes = resourceManager.getBufferSize(st.currentVao.elementBuffer);
|
||||
c.indexCount = indexBytes <= 0 ? -1 : (int) Math.min(Math.max(0L, indexBytes - indexOffset) / c.indexSize, Integer.MAX_VALUE);
|
||||
c.restart = st.primitiveRestartEnabled && indexType != GL11.GL_UNSIGNED_BYTE;
|
||||
c.restartIndex = indexType == GL11.GL_UNSIGNED_SHORT ? st.primitiveRestartSentinel & 0xFFFF : st.primitiveRestartSentinel;
|
||||
} else {
|
||||
c.indexBuffer = 0;
|
||||
c.indexSize = 0;
|
||||
c.indexOffset = 0;
|
||||
c.indexCount = 0;
|
||||
c.restart = false;
|
||||
}
|
||||
c.first = first;
|
||||
c.count = count;
|
||||
c.instances = instances;
|
||||
c.baseVertex = baseVertex;
|
||||
|
||||
if (c.samplerTextures.length != replay.samplerUnits.length) {
|
||||
c.samplerTextures = new int[replay.samplerUnits.length];
|
||||
c.samplerHandles = new long[replay.samplerUnits.length];
|
||||
}
|
||||
for (int i = 0; i < replay.samplerUnits.length; i++) {
|
||||
final int unit = replay.samplerUnits[i];
|
||||
final IntSupplier source = replay.samplerSources[i];
|
||||
c.samplerTextures[i] = source != null ? source.getAsInt() : st.boundTextures[unit];
|
||||
final int samplerObject = replay.samplerObjects[i] != SAMPLER_OBJECT_OF_DRAW ? replay.samplerObjects[i] : st.boundSamplerObjects[unit];
|
||||
c.samplerHandles[i] = computeBinder.samplerHandle(samplerObject, c.samplerTextures[i]);
|
||||
}
|
||||
if (c.imageTextures.length != images.length) c.imageTextures = new int[images.length];
|
||||
for (int i = 0; i < images.length; i++) c.imageTextures[i] = images[i].getAsInt();
|
||||
if (c.packStorage.length != replay.packStorageSlots.length) c.packStorage = new long[replay.packStorageSlots.length];
|
||||
for (int i = 0; i < replay.packStorageSlots.length; i++) {
|
||||
c.packStorage[i] = resourceManager.getBufferHandle(st.boundSsboByIndex[replay.packStorageSlots[i]]);
|
||||
}
|
||||
if (c.packRwStorage.length != replay.packRwStorageSlots.length) c.packRwStorage = new long[replay.packRwStorageSlots.length];
|
||||
for (int i = 0; i < replay.packRwStorageSlots.length; i++) {
|
||||
c.packRwStorage[i] = resourceManager.getBufferHandle(st.boundSsboByIndex[replay.packRwStorageSlots[i]]);
|
||||
}
|
||||
captureUniformBlocks(st, twin, replay, c);
|
||||
|
||||
captureUniforms(st, graphics, replay);
|
||||
pending++;
|
||||
}
|
||||
|
||||
private boolean captureInputs(ContextState st, Replay replay, Capture c) {
|
||||
final int n = replay.inputLocations.length;
|
||||
if (c.attr.length < n * 4) {
|
||||
c.attr = new float[n * 4];
|
||||
c.attrDefault = new float[n * 4];
|
||||
}
|
||||
final ContextState.VAOState vao = st.currentVao;
|
||||
for (int k = 0; k < n; k++) {
|
||||
final int loc = replay.inputLocations[k];
|
||||
int x = 0, y = 0, z = 0, w = 0;
|
||||
if (loc >= 0 && loc < ContextState.MAX_VERTEX_ATTRIBS) {
|
||||
System.arraycopy(st.attribDefaults, loc * 4, c.attrDefault, k * 4, 4);
|
||||
if (vao.attribEnabled[loc]) {
|
||||
final int type = componentType(vao.attribType[loc]);
|
||||
final int binding = vao.attribBinding[loc];
|
||||
final int buffer = vao.bindingBuffer[binding];
|
||||
final long handle = type >= 0 ? resourceManager.getBufferHandle(buffer) : 0;
|
||||
if (type < 0) warnOnce("type:" + vao.attribType[loc], "Vertex write replay cannot read vertex attribute type 0x" + Integer.toHexString(vao.attribType[loc]) + "; that input reads its default value");
|
||||
if (handle != 0) {
|
||||
final int slot = bufferSlot(c, handle);
|
||||
if (slot < 0) {
|
||||
warnOnce("buffers", "A draw reads more than " + c.buffers.length + " vertex buffers; its vertex image writes are dropped");
|
||||
return false;
|
||||
}
|
||||
final boolean bgra = vao.attribSize[loc] == GL12.GL_BGRA;
|
||||
final int size = type == PACKED_10F_11F_11F ? 3 : bgra ? 4 : vao.attribSize[loc];
|
||||
final int elementBytes = type >= FIRST_PACKED_TYPE ? 4 : size * COMPONENT_SIZE[type];
|
||||
final int stride = vao.bindingStride[binding] != 0 ? vao.bindingStride[binding] : elementBytes;
|
||||
final long offset = vao.bindingOffset[binding] + vao.attribRelativeOffset[loc];
|
||||
final int divisor = Math.min(vao.bindingDivisor[binding], MAX_DIVISOR);
|
||||
final long bufferSize = resourceManager.getBufferSize(buffer);
|
||||
final boolean noElement = bufferSize > 0 && bufferSize < offset + elementBytes;
|
||||
x = slot | type << 4 | size << 8 | (vao.attribNormalized[loc] ? 0x800 : 0) | (vao.attribIsInteger[loc] ? 0x1000 : 0)
|
||||
| ENABLED | (bgra ? BGRA : 0) | (noElement ? NO_ELEMENT : 0) | divisor << 16;
|
||||
y = (int) offset;
|
||||
z = stride;
|
||||
w = lastElement(bufferSize, offset, elementBytes, stride);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.attrDefault[k * 4] = 0f;
|
||||
c.attrDefault[k * 4 + 1] = 0f;
|
||||
c.attrDefault[k * 4 + 2] = 0f;
|
||||
c.attrDefault[k * 4 + 3] = 1f;
|
||||
}
|
||||
c.attr[k * 4] = Float.intBitsToFloat(x);
|
||||
c.attr[k * 4 + 1] = Float.intBitsToFloat(y);
|
||||
c.attr[k * 4 + 2] = Float.intBitsToFloat(z);
|
||||
c.attr[k * 4 + 3] = Float.intBitsToFloat(w);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int lastElement(long bufferSize, long offset, int elementBytes, int stride) {
|
||||
if (bufferSize <= 0) return -1;
|
||||
final long room = bufferSize - offset - elementBytes;
|
||||
if (room < 0 || stride <= 0) return 0;
|
||||
return (int) Math.min(room / stride, 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
private static int bufferSlot(Capture c, long handle) {
|
||||
for (int i = 0; i < c.bufferCount; i++) {
|
||||
if (c.buffers[i] == handle) return i;
|
||||
}
|
||||
if (c.bufferCount == c.buffers.length) return -1;
|
||||
c.buffers[c.bufferCount] = handle;
|
||||
return c.bufferCount++;
|
||||
}
|
||||
|
||||
static int componentType(int glType) {
|
||||
return switch (glType) {
|
||||
case GL11.GL_FLOAT -> 0;
|
||||
case GL30.GL_HALF_FLOAT -> 1;
|
||||
case GL11.GL_BYTE -> 2;
|
||||
case GL11.GL_UNSIGNED_BYTE -> 3;
|
||||
case GL11.GL_SHORT -> 4;
|
||||
case GL11.GL_UNSIGNED_SHORT -> 5;
|
||||
case GL11.GL_INT -> 6;
|
||||
case GL11.GL_UNSIGNED_INT -> 7;
|
||||
case GL41.GL_FIXED -> 8;
|
||||
case GL33.GL_INT_2_10_10_10_REV -> 9;
|
||||
case GL12.GL_UNSIGNED_INT_2_10_10_10_REV -> 10;
|
||||
case GL30.GL_UNSIGNED_INT_10F_11F_11F_REV -> PACKED_10F_11F_11F;
|
||||
default -> -1;
|
||||
};
|
||||
}
|
||||
|
||||
private void captureUniformBlocks(ContextState st, ShaderManager.ProgramObject twin, Replay replay, Capture c) {
|
||||
if (c.uniformBlocks.length != replay.uniformBlockCount) c.uniformBlocks = new ByteBuffer[replay.uniformBlockCount];
|
||||
for (int index : replay.uniformBlockIndices) {
|
||||
final ByteBuffer view = computeBinder.uniformBlockView(st, twin, index);
|
||||
if (view == null) {
|
||||
if (c.uniformBlocks[index] != null) c.uniformBlocks[index].limit(0);
|
||||
continue;
|
||||
}
|
||||
ByteBuffer copy = c.uniformBlocks[index];
|
||||
if (copy == null || copy.capacity() < view.remaining()) copy = c.uniformBlocks[index] = ByteBuffer.allocateDirect(view.remaining()).order(ByteOrder.nativeOrder());
|
||||
copy.clear();
|
||||
copy.put(view);
|
||||
copy.flip();
|
||||
}
|
||||
}
|
||||
|
||||
private void captureUniforms(ContextState st, ShaderManager.ProgramObject graphics, Replay replay) {
|
||||
final UniformStaging us = st.uniformStaging(graphics);
|
||||
final long[] hashes = us.uniformValueHashBySlot;
|
||||
for (int u = 0; u < replay.uniformSources.length; u++) {
|
||||
final int source = replay.uniformSources[u];
|
||||
final float[] value = us.uniformDataBySlot != null && source < us.uniformDataBySlot.length ? us.uniformDataBySlot[source] : null;
|
||||
final long hash = hashes != null && source < hashes.length ? hashes[source] : 0L;
|
||||
if (uniformCount == uniformLengths.length) uniformLengths = Arrays.copyOf(uniformLengths, uniformLengths.length * 2);
|
||||
if (value != null && replay.lastHashesApplied && hash == replay.lastHashes[u]) {
|
||||
uniformLengths[uniformCount++] = UNCHANGED;
|
||||
continue;
|
||||
}
|
||||
replay.lastHashes[u] = hash;
|
||||
final int len = value == null ? 0 : value.length;
|
||||
uniformLengths[uniformCount++] = len;
|
||||
if (len == 0) continue;
|
||||
if (uniformValueCount + len > uniformValues.length) uniformValues = Arrays.copyOf(uniformValues, Math.max(uniformValues.length * 2, uniformValueCount + len));
|
||||
System.arraycopy(value, 0, uniformValues, uniformValueCount, len);
|
||||
uniformValueCount += len;
|
||||
}
|
||||
replay.lastHashesApplied = true;
|
||||
}
|
||||
|
||||
public boolean hasPending() {
|
||||
return pending > 0;
|
||||
}
|
||||
|
||||
public void noteIndirectDraw() {
|
||||
warnOnce("indirect", "An indirect draw used a program whose vertex shader writes images; those writes are dropped");
|
||||
}
|
||||
|
||||
private Capture acquire() {
|
||||
if (pending == captures.size()) captures.add(new Capture());
|
||||
return captures.get(pending);
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (pending == 0 || flushing) return;
|
||||
flushing = true;
|
||||
final ContextState st = state.get();
|
||||
final int savedProgram = st.boundProgram;
|
||||
final ShaderManager.ProgramObject savedProgramObj = st.boundProgramObj;
|
||||
try {
|
||||
uniformCursor = 0;
|
||||
valueCursor = 0;
|
||||
int start = 0;
|
||||
while (start < pending) {
|
||||
final Capture first = captures.get(start);
|
||||
final Replay replay = first.replay;
|
||||
int end = start;
|
||||
while (end < pending && captures.get(end).replay == replay && Arrays.equals(captures.get(end).imageTextures, first.imageTextures)
|
||||
&& Arrays.equals(captures.get(end).packRwStorage, first.packRwStorage)) end++;
|
||||
replayRun(st, replay, start, end);
|
||||
start = end;
|
||||
}
|
||||
} finally {
|
||||
st.boundProgram = savedProgram;
|
||||
st.boundProgramObj = savedProgramObj;
|
||||
for (int i = 0; i < pending; i++) captures.get(i).replay = null;
|
||||
pending = 0;
|
||||
uniformCount = 0;
|
||||
uniformValueCount = 0;
|
||||
flushing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void replayRun(ContextState st, Replay replay, int start, int end) {
|
||||
final ShaderManager.ProgramObject twin = shaderManager.getProgram(replay.replayProgram);
|
||||
if (twin == null || !twin.linked || twin.sdlComputePipeline == 0) {
|
||||
skipUniforms(replay, start, end);
|
||||
return;
|
||||
}
|
||||
|
||||
final Capture first = captures.get(start);
|
||||
final int[] imageTextures = first.imageTextures;
|
||||
final int imageCount = imageTextures.length;
|
||||
final int written = replay.setup.writtenImageCount();
|
||||
System.arraycopy(st.boundStorageTextureByUnit, 0, savedImages, 0, imageCount);
|
||||
System.arraycopy(st.boundStorageTextureLevel, 0, savedLevels, 0, imageCount);
|
||||
System.arraycopy(st.boundStorageTextureAccess, 0, savedAccess, 0, imageCount);
|
||||
for (int i = 0; i < replay.samplerUnits.length; i++) savedTextures[i] = st.boundTextures[replay.samplerUnits[i]];
|
||||
computeBinder.setSamplerOverrides(samplerOverrides);
|
||||
Arrays.fill(rwHandlesByGlSlot, 0L);
|
||||
for (int i = 0; i < replay.packRwStorageSlots.length; i++) rwHandlesByGlSlot[replay.packRwStorageSlots[i]] = first.packRwStorage[i];
|
||||
computeBinder.setReadWriteStorageBufferOverrides(rwHandlesByGlSlot);
|
||||
|
||||
try {
|
||||
for (int i = 0; i < imageCount; i++) {
|
||||
st.boundStorageTextureByUnit[i] = imageTextures[i];
|
||||
st.boundStorageTextureLevel[i] = 0;
|
||||
st.boundStorageTextureAccess[i] = imageAccess(i, written);
|
||||
}
|
||||
applySamplerTextures(st, replay, captures.get(start));
|
||||
|
||||
st.boundProgram = replay.replayProgram;
|
||||
st.boundProgramObj = twin;
|
||||
final long pass = computeBinder.beginComputePassWithoutStorageBuffers(st);
|
||||
if (pass == 0) {
|
||||
for (int i = start; i < end; i++) applyUniforms(st, replay);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (int i = start; i < end; i++) {
|
||||
final Capture c = captures.get(i);
|
||||
if (i > start && applySamplerTextures(st, replay, c)) computeBinder.rebindSamplers(pass, st);
|
||||
dispatch(st, pass, replay, c);
|
||||
}
|
||||
} finally {
|
||||
computeBinder.endBatchedComputeDispatch(pass);
|
||||
}
|
||||
} finally {
|
||||
computeBinder.setReadWriteStorageBufferOverrides(null);
|
||||
computeBinder.setUniformBlockOverrides(null);
|
||||
System.arraycopy(savedImages, 0, st.boundStorageTextureByUnit, 0, imageCount);
|
||||
System.arraycopy(savedLevels, 0, st.boundStorageTextureLevel, 0, imageCount);
|
||||
System.arraycopy(savedAccess, 0, st.boundStorageTextureAccess, 0, imageCount);
|
||||
for (int i = 0; i < replay.samplerUnits.length; i++) {
|
||||
st.boundTextures[replay.samplerUnits[i]] = savedTextures[i];
|
||||
samplerOverrides[replay.samplerUnits[i]] = 0L;
|
||||
}
|
||||
computeBinder.setSamplerOverrides(null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean applySamplerTextures(ContextState st, Replay replay, Capture c) {
|
||||
boolean changed = false;
|
||||
for (int i = 0; i < replay.samplerUnits.length; i++) {
|
||||
final int unit = replay.samplerUnits[i];
|
||||
if (st.boundTextures[unit] != c.samplerTextures[i] || samplerOverrides[unit] != c.samplerHandles[i]) {
|
||||
st.boundTextures[unit] = c.samplerTextures[i];
|
||||
samplerOverrides[unit] = c.samplerHandles[i];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private void skipUniforms(Replay replay, int start, int end) {
|
||||
replay.lastHashesApplied = false;
|
||||
for (int i = start; i < end; i++) {
|
||||
for (int u = 0; u < replay.uniformSources.length; u++) valueCursor += Math.max(0, uniformLengths[uniformCursor++]);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatch(ContextState st, long pass, Replay replay, Capture c) {
|
||||
Arrays.fill(handlesByGlSlot, 0L);
|
||||
for (int i = 0; i < replay.packStorageSlots.length; i++) handlesByGlSlot[replay.packStorageSlots[i]] = c.packStorage[i];
|
||||
System.arraycopy(c.buffers, 0, handlesByGlSlot, replay.setup.vertexBufferBinding(), c.bufferCount);
|
||||
handlesByGlSlot[replay.setup.indexBufferBinding()] = c.indexBuffer;
|
||||
final boolean bound = computeBinder.bindRoStorageBufferHandles(pass, st, handlesByGlSlot);
|
||||
|
||||
applyUniforms(st, replay);
|
||||
if (!bound) return;
|
||||
|
||||
final int inputs = replay.inputLocations.length;
|
||||
putFloats(st, replay.locAttr, c.attr, inputs * 4);
|
||||
putFloats(st, replay.locAttrDefault, c.attrDefault, inputs * 4);
|
||||
putIntVector(st, replay.locDraw, 4, c.first, c.count, c.instances, c.baseVertex);
|
||||
putIntVector(st, replay.locIndex, 3, c.indexSize, c.indexOffset, c.indexCount, 0);
|
||||
putIntVector(st, replay.locRestart, 2, c.restart ? 1 : 0, c.restartIndex, 0, 0);
|
||||
computeBinder.setUniformBlockOverrides(c.uniformBlocks);
|
||||
|
||||
InvocationDispatch.dispatch(computeBinder, st, pass, (long) c.count * c.instances, replay.locInvocationBase, invocationBaseWriter);
|
||||
}
|
||||
|
||||
private void putFloats(ContextState st, int location, float[] source, int len) {
|
||||
if (location < 0 || len == 0) return;
|
||||
final float[] value = pipelineApplier.reuseOrAlloc(st, location, len);
|
||||
if (value == null) return;
|
||||
System.arraycopy(source, 0, value, 0, len);
|
||||
pipelineApplier.putUniform(st, location, value);
|
||||
}
|
||||
|
||||
private void applyUniforms(ContextState st, Replay replay) {
|
||||
for (int u = 0; u < replay.uniformSources.length; u++) {
|
||||
final int len = uniformLengths[uniformCursor++];
|
||||
if (len <= 0) continue;
|
||||
final float[] value = pipelineApplier.reuseOrAlloc(st, replay.uniformTargets[u], len);
|
||||
if (value != null) {
|
||||
System.arraycopy(uniformValues, valueCursor, value, 0, len);
|
||||
pipelineApplier.putUniform(st, replay.uniformTargets[u], value);
|
||||
}
|
||||
valueCursor += len;
|
||||
}
|
||||
}
|
||||
|
||||
private static int imageAccess(int index, int writtenImageCount) {
|
||||
return index < writtenImageCount ? GL15.GL_READ_WRITE : GL15.GL_READ_ONLY;
|
||||
}
|
||||
|
||||
private void putIntVector(ContextState st, int location, int count, int x, int y, int z, int w) {
|
||||
if (location < 0) return;
|
||||
final float[] value = pipelineApplier.reuseOrAlloc(st, location, count);
|
||||
if (value == null) return;
|
||||
value[0] = Float.intBitsToFloat(x);
|
||||
if (count > 1) value[1] = Float.intBitsToFloat(y);
|
||||
if (count > 2) value[2] = Float.intBitsToFloat(z);
|
||||
if (count > 3) value[3] = Float.intBitsToFloat(w);
|
||||
pipelineApplier.putUniform(st, location, value);
|
||||
}
|
||||
|
||||
private void warnOnce(String key, String message) {
|
||||
if (warned.add(key)) LOG.warn(message);
|
||||
}
|
||||
}
|
||||
+2
-8
@@ -4,8 +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 static final InvocationDispatch.IntUniformWriter UNIFORM_1I = (st, location, value) -> GLStateManager.glUniform1i(location, value);
|
||||
|
||||
private final ComputeDispatchSink computeBinder;
|
||||
|
||||
@@ -25,12 +24,7 @@ public final class VoxelizationDispatcher {
|
||||
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);
|
||||
}
|
||||
InvocationDispatch.dispatch(computeBinder, st, pass, vertexTotal, locInvBase, UNIFORM_1I);
|
||||
}
|
||||
|
||||
public void endBatch(long pass) {
|
||||
|
||||
@@ -381,11 +381,14 @@ public final class FrameManager {
|
||||
|
||||
|
||||
private volatile Runnable preRenderPassHook;
|
||||
private volatile Runnable renderPassEndHook;
|
||||
private volatile Runnable beforeSubmit;
|
||||
private volatile Runnable afterPresent;
|
||||
|
||||
public void setPreRenderPassHook(Runnable hook) { this.preRenderPassHook = hook; }
|
||||
|
||||
public void setRenderPassEndHook(Runnable hook) { this.renderPassEndHook = hook; }
|
||||
|
||||
public void noteComputePassBegun() {
|
||||
final FrameState f = frame();
|
||||
f.computePassesThisFrame++;
|
||||
@@ -472,6 +475,8 @@ public final class FrameManager {
|
||||
f.currentColorTarget = 0;
|
||||
f.currentDepthTarget = 0;
|
||||
f.activeLayoutHash = 0;
|
||||
final Runnable hook = renderPassEndHook;
|
||||
if (hook != null) hook.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -248,6 +248,7 @@ public final class ResourceManager {
|
||||
}
|
||||
|
||||
private long createTextureCore(int glId, int glTarget, int sdlFormat, int glFormat, int width, int height, int depth, int levels, int usage) {
|
||||
if (getTextureHandle(glId) != 0) beforeTextureReplaced();
|
||||
usage = reconcileUsageAndWarn(glId, usage);
|
||||
final int sdlType = mapTextureType(glTarget);
|
||||
if (!SDL_GPUTextureSupportsFormat(device.getDevice(), sdlFormat, sdlType, usage)) {
|
||||
@@ -309,7 +310,19 @@ public final class ResourceManager {
|
||||
return sdlFormat;
|
||||
}
|
||||
|
||||
private Runnable textureReplaceHook;
|
||||
|
||||
public void setTextureReplaceHook(Runnable hook) {
|
||||
textureReplaceHook = hook;
|
||||
}
|
||||
|
||||
private void beforeTextureReplaced() {
|
||||
final Runnable hook = textureReplaceHook;
|
||||
if (hook != null) hook.run();
|
||||
}
|
||||
|
||||
public void deleteTexture(int glId) {
|
||||
beforeTextureReplaced();
|
||||
final long handle;
|
||||
wLock.lock();
|
||||
try {
|
||||
@@ -340,6 +353,7 @@ public final class ResourceManager {
|
||||
}
|
||||
|
||||
public void releaseTextureHandleForRealloc(int glId) {
|
||||
beforeTextureReplaced();
|
||||
final long handle;
|
||||
wLock.lock();
|
||||
try {
|
||||
|
||||
@@ -43,8 +43,13 @@ public final class SamplerBinder {
|
||||
}
|
||||
|
||||
public long getSamplerForUnit(ContextState st, int glUnit, int glTexId) {
|
||||
if (glUnit >= 0 && glUnit < st.boundSamplerObjects.length && st.boundSamplerObjects[glUnit] != 0) {
|
||||
final TextureSamplerState ss = resourceManager.getSamplerObject(st.boundSamplerObjects[glUnit]);
|
||||
final int samplerObject = glUnit >= 0 && glUnit < st.boundSamplerObjects.length ? st.boundSamplerObjects[glUnit] : 0;
|
||||
return getSampler(samplerObject, glTexId);
|
||||
}
|
||||
|
||||
public long getSampler(int samplerObject, int glTexId) {
|
||||
if (samplerObject != 0) {
|
||||
final TextureSamplerState ss = resourceManager.getSamplerObject(samplerObject);
|
||||
if (ss != null) return getOrCreateSdlSampler(ss);
|
||||
}
|
||||
if (glTexId != 0) return getSamplerForTexture(glTexId);
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.gtnewhorizons.angelica.glsm.shader.ShaderCacheIO;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import com.gtnewhorizons.angelica.glsm.threading.AngelicaWorkers;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compute.VertexWriteReplayer;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.cross.CrossCompileCache;
|
||||
@@ -2078,6 +2079,7 @@ public final class ShaderManager {
|
||||
public int vertexShader;
|
||||
public int vertexUboSize;
|
||||
public long sdlComputePipeline;
|
||||
public VertexWriteReplayer.Replay vertexWriteReplay;
|
||||
public long sdlFragmentShader;
|
||||
public long sdlVertexShader;
|
||||
public long lastComputeCb;
|
||||
|
||||
+2
-2
@@ -77,9 +77,9 @@ class VoxelizationDispatcherTest {
|
||||
void dispatchRegion_splitsAboveMaxGroups() {
|
||||
final RecordingSink sink = new RecordingSink();
|
||||
final VoxelizationDispatcher d = new VoxelizationDispatcher(sink);
|
||||
region(d, 1, VoxelizationDispatcher.MAX_GROUPS_PER_DISPATCH * 64 + 1, new ContextState());
|
||||
region(d, 1, InvocationDispatch.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(InvocationDispatch.MAX_GROUPS_PER_DISPATCH, sink.dispatches.get(0)[0]);
|
||||
assertEquals(1, sink.dispatches.get(1)[0]);
|
||||
assertEquals(2, sink.uniformPushCount);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
|
||||
public final class EnderDragonBeams {
|
||||
|
||||
private static final NamespacedId CRYSTAL_BEAM = new NamespacedId("minecraft", "end_crystal_beam");
|
||||
private static final NamespacedId DEATH_RAYS = new NamespacedId("minecraft", "dragon_death_rays");
|
||||
|
||||
private EnderDragonBeams() {}
|
||||
|
||||
public static void beginCrystalBeam() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
|
||||
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(CRYSTAL_BEAM);
|
||||
}
|
||||
|
||||
public static void endCrystalBeam() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
|
||||
}
|
||||
|
||||
public static void beginDeathRays() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.LIGHTNING);
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
|
||||
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(DEATH_RAYS);
|
||||
}
|
||||
|
||||
public static void endDeathRays() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
|
||||
GbufferPrograms.teardownSpecialRenderCondition();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
public interface EyePassRenderer {
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.helpers;
|
||||
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_CHAR;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.EyePassRenderer;
|
||||
import net.minecraft.client.renderer.entity.RenderDragon;
|
||||
import net.minecraft.client.renderer.entity.RenderEnderman;
|
||||
import net.minecraft.client.renderer.entity.RenderSpider;
|
||||
@@ -15,7 +16,8 @@ public final class RendererLivingEntityHelper {
|
||||
private RendererLivingEntityHelper() {}
|
||||
|
||||
public static boolean hasEyePass(Object renderer) {
|
||||
return renderer instanceof RenderSpider || renderer instanceof RenderEnderman || renderer instanceof RenderDragon;
|
||||
return renderer instanceof RenderSpider || renderer instanceof RenderEnderman || renderer instanceof RenderDragon
|
||||
|| renderer instanceof EyePassRenderer;
|
||||
}
|
||||
|
||||
public static String getUpsideDownName(EntityLivingBase entity) {
|
||||
|
||||
@@ -181,6 +181,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
// Custom images and SSBOs
|
||||
private final Set<GlImage> customImages;
|
||||
private final VertexWriteReplays vertexWriteReplays = new VertexWriteReplays();
|
||||
private final GlImage[] imagesToClear;
|
||||
@Nullable
|
||||
private final ShaderStorageBufferHolder ssboHolder;
|
||||
@@ -1181,6 +1182,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
ProgramBuilder builder = beginBuilder(source.getName(), transformed);
|
||||
wireGbufferProgram(builder, availability, shadow);
|
||||
final Program program = builder.build();
|
||||
vertexWriteReplays.attach(source.getName(), program, transformed, (samplers, images) -> wireGbufferResources(samplers, images, availability, shadow));
|
||||
|
||||
final Pass pass = createPassInner(program, source.getDirectives(), shadow, id);
|
||||
this.customUniforms.mapholderToPass(builder, pass);
|
||||
@@ -1194,17 +1196,20 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
private void wireGbufferProgram(ProgramBuilder builder, InputAvailability availability, boolean shadow) {
|
||||
CommonUniforms.addDynamicUniforms(builder, FogMode.PER_VERTEX);
|
||||
this.customUniforms.assignTo(builder);
|
||||
wireGbufferResources(builder, builder, availability, shadow);
|
||||
}
|
||||
|
||||
private void wireGbufferResources(SamplerHolder samplers, ImageHolder images, InputAvailability availability, boolean shadow) {
|
||||
final Supplier<ImmutableSet<Integer>> flipped = shadow ? flippedShadowGbuffers : flippedGbuffers;
|
||||
|
||||
TextureStage textureStage = TextureStage.GBUFFERS_AND_SHADOW;
|
||||
|
||||
ProgramSamplers.CustomTextureSamplerInterceptor customTextureSamplerInterceptor =
|
||||
ProgramSamplers.customTextureSamplerInterceptor(builder,
|
||||
ProgramSamplers.customTextureSamplerInterceptor(samplers,
|
||||
customTextureManager.getCustomTextureIdMap(textureStage));
|
||||
|
||||
IrisSamplers.addRenderTargetSamplers(customTextureSamplerInterceptor, flipped, renderTargets, false, this);
|
||||
IrisImages.addRenderTargetImages(builder, flipped, renderTargets);
|
||||
IrisImages.addRenderTargetImages(images, flipped, renderTargets);
|
||||
|
||||
if (!shouldBindPBR) {
|
||||
shouldBindPBR = IrisSamplers.hasPBRSamplers(customTextureSamplerInterceptor);
|
||||
@@ -1220,7 +1225,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
IrisSamplers.addCustomImages(customTextureSamplerInterceptor, customImages);
|
||||
IrisSamplers.addCustomTextures(customTextureSamplerInterceptor, customTextureManager.getIrisCustomTextures());
|
||||
IrisImages.addCustomImages(builder, customImages);
|
||||
IrisImages.addCustomImages(images, customImages);
|
||||
|
||||
if (IrisSamplers.hasShadowSamplers(customTextureSamplerInterceptor)) {
|
||||
if (!shadow) {
|
||||
@@ -1229,7 +1234,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
if (shadowRenderTargets != null) {
|
||||
recordSamplerUsage(IrisSamplers.addShadowSamplers(customTextureSamplerInterceptor, shadowRenderTargets, null, true));
|
||||
IrisImages.addShadowColorImages(builder, shadowRenderTargets, null);
|
||||
IrisImages.addShadowColorImages(images, shadowRenderTargets, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1527,6 +1532,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
final ProgramBuilder builder = beginBuilder(sourceName + kind.variantSuffix, transformed);
|
||||
wireGbufferProgram(builder, availability, shadow);
|
||||
final Program variant = builder.build();
|
||||
vertexWriteReplays.attach(sourceName + kind.variantSuffix, variant, transformed, (samplers, images) -> wireGbufferResources(samplers, images, availability, shadow));
|
||||
this.customUniforms.mapholderToPass(builder, variant);
|
||||
return variant;
|
||||
} catch (Exception e) {
|
||||
@@ -1606,6 +1612,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
}
|
||||
}
|
||||
vertexWriteReplays.destroy();
|
||||
if (shadowVoxelizationCompute != null) {
|
||||
shadowVoxelizationCompute.destroy();
|
||||
shadowVoxelizationCompute = null;
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package net.coderbot.iris.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VertexWriteReplaySetup;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.InternalTextureFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureType;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.gl.image.ImageHolder;
|
||||
import net.coderbot.iris.gl.program.ComputeProgram;
|
||||
import net.coderbot.iris.gl.program.Program;
|
||||
import net.coderbot.iris.gl.program.ProgramBuilder;
|
||||
import net.coderbot.iris.gl.sampler.GlSampler;
|
||||
import net.coderbot.iris.gl.sampler.SamplerHolder;
|
||||
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
|
||||
import net.coderbot.iris.pipeline.transform.PatchShaderType;
|
||||
import net.coderbot.iris.pipeline.transform.RwImageStoreExtractor;
|
||||
import net.coderbot.iris.pipeline.transform.RwImageStoreExtractor.VertexInput;
|
||||
import net.coderbot.iris.samplers.IrisSamplers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
/**
|
||||
* SDL-GPU only allows image writes from compute, so a gbuffers/shadow vertex shader that writes images gets a compute
|
||||
* twin that the backend runs over every draw made with the program.
|
||||
*/
|
||||
final class VertexWriteReplays {
|
||||
|
||||
private final List<ComputeProgram> twins = new ArrayList<>();
|
||||
private final IntArrayList attachedPrograms = new IntArrayList();
|
||||
|
||||
void attach(String name, Program program, Map<PatchShaderType, String> transformed, BiConsumer<SamplerHolder, ImageHolder> wireResources) {
|
||||
BackendManager.RENDER_BACKEND.setVertexWriteReplay(program.getProgramId(), null);
|
||||
final String source = transformed.get(PatchShaderType.COMPUTE);
|
||||
if (RwImageStoreExtractor.parseSentinel(source) != RwImageStoreExtractor.RwExtractMode.VERTEX_REPLAY) return;
|
||||
|
||||
final ComputeProgram twin;
|
||||
try {
|
||||
twin = ProgramBuilder.beginCompute(name + "_vertex_replay", source, IrisSamplers.WORLD_RESERVED_TEXTURE_UNITS).buildCompute();
|
||||
} catch (RuntimeException e) {
|
||||
Iris.logger.error("Vertex image write replay for {} failed to build; its image writes are dropped", name, e);
|
||||
return;
|
||||
}
|
||||
|
||||
final List<String> writtenImages = RwImageStoreExtractor.parseVertexReplayImages(source);
|
||||
final List<String> images = new ArrayList<>(writtenImages);
|
||||
images.addAll(RwImageStoreExtractor.parseVertexReplayReadImages(source));
|
||||
final List<String> samplers = new ArrayList<>();
|
||||
for (String sampler : RwImageStoreExtractor.parseVertexReplaySamplers(source)) {
|
||||
if (RenderSystem.getUniformLocation(twin.getProgramId(), sampler) != -1) samplers.add(sampler);
|
||||
}
|
||||
final ResourceCollector resources = new ResourceCollector(images, samplers);
|
||||
wireResources.accept(resources, resources);
|
||||
for (int i = 0; i < resources.images.length; i++) {
|
||||
if (resources.images[i] == null) {
|
||||
Iris.logger.warn("Vertex image writes of {} use image {}, which the pipeline does not provide; they are dropped", name, resources.imageNames.get(i));
|
||||
twin.destroy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final List<VertexInput> inputs = RwImageStoreExtractor.parseVertexReplayInputs(source);
|
||||
final String[] inputNames = new String[inputs.size()];
|
||||
final int[] inputLocations = new int[inputs.size()];
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
inputNames[i] = inputs.get(i).name();
|
||||
inputLocations[i] = inputs.get(i).location();
|
||||
}
|
||||
BackendManager.RENDER_BACKEND.setVertexWriteReplay(program.getProgramId(), new VertexWriteReplaySetup(twin.getProgramId(),
|
||||
inputNames, inputLocations, resources.images, writtenImages.size(),
|
||||
resources.samplerNames.toArray(new String[0]), resources.samplerUnits, resources.samplerTextures, resources.samplerObjects,
|
||||
RwImageStoreExtractor.VG_REPLAY_VERTEX_BUFFER_BINDING, RwImageStoreExtractor.VG_REPLAY_VERTEX_BUFFER_COUNT, RwImageStoreExtractor.VG_REPLAY_INDEX_BUFFER_BINDING));
|
||||
attachedPrograms.add(program.getProgramId());
|
||||
twins.add(twin);
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
for (int i = 0; i < attachedPrograms.size(); i++) BackendManager.RENDER_BACKEND.setVertexWriteReplay(attachedPrograms.getInt(i), null);
|
||||
attachedPrograms.clear();
|
||||
for (ComputeProgram twin : twins) twin.destroy();
|
||||
twins.clear();
|
||||
}
|
||||
|
||||
private static final class ResourceCollector implements SamplerHolder, ImageHolder {
|
||||
final List<String> imageNames;
|
||||
final IntSupplier[] images;
|
||||
final List<String> samplerNames;
|
||||
final int[] samplerUnits;
|
||||
final IntSupplier[] samplerTextures;
|
||||
final int[] samplerObjects;
|
||||
|
||||
ResourceCollector(List<String> imageNames, List<String> samplerNames) {
|
||||
this.imageNames = imageNames;
|
||||
this.images = new IntSupplier[imageNames.size()];
|
||||
this.samplerNames = samplerNames;
|
||||
this.samplerUnits = new int[samplerNames.size()];
|
||||
Arrays.fill(samplerUnits, -1);
|
||||
this.samplerTextures = new IntSupplier[samplerNames.size()];
|
||||
this.samplerObjects = new int[samplerNames.size()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasImage(String name) {
|
||||
return imageNames.contains(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTextureImage(IntSupplier textureID, InternalTextureFormat internalFormat, String name) {
|
||||
final int index = imageNames.indexOf(name);
|
||||
if (index >= 0 && images[index] == null) images[index] = textureID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSampler(String name) {
|
||||
return samplerNames.contains(name) || imageNames.contains(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExternalSampler(int textureUnit, String... names) {
|
||||
for (String name : names) {
|
||||
final int index = samplerNames.indexOf(name);
|
||||
if (index >= 0 && unassigned(index)) samplerUnits[index] = textureUnit;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
|
||||
return addDynamicSampler(type, texture, notifier, sampler, names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addDynamicSampler(TextureType type, IntSupplier texture, GlSampler sampler, String... names) {
|
||||
return addDynamicSampler(type, texture, null, sampler, names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
|
||||
boolean used = false;
|
||||
for (String name : names) {
|
||||
final int index = samplerNames.indexOf(name);
|
||||
if (index < 0) continue;
|
||||
used = true;
|
||||
if (unassigned(index)) {
|
||||
samplerTextures[index] = texture;
|
||||
samplerObjects[index] = sampler != null ? sampler.getId() : 0;
|
||||
}
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
private boolean unassigned(int index) {
|
||||
return samplerUnits[index] < 0 && samplerTextures[index] == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package net.coderbot.iris.pipeline.transform;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import net.coderbot.iris.gl.program.ComputeProgram;
|
||||
import net.coderbot.iris.postprocess.FullScreenQuadRenderer;
|
||||
import net.coderbot.iris.uniforms.custom.CustomUniforms;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
@@ -16,6 +17,7 @@ public final class PreRasterComputeDispatcher {
|
||||
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
compute.use();
|
||||
uniforms.push(compute);
|
||||
if (mode == RwImageStoreExtractor.RwExtractMode.COMPOSITE_VSH) FullScreenQuadRenderer.uploadCompositeMatrices();
|
||||
int targetSizeLoc = cachedTargetSizeLoc;
|
||||
if (mode == RwImageStoreExtractor.RwExtractMode.COMPOSITE_FSH) {
|
||||
if (targetSizeLoc == LOC_UNRESOLVED) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.antlr.v4.runtime.CommonToken;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.taumc.glsl.ShaderParser;
|
||||
import org.taumc.glsl.Transformer;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
@@ -25,23 +26,26 @@ import java.util.Set;
|
||||
|
||||
public final class RwImageStoreExtractor {
|
||||
|
||||
public enum RwExtractMode { CHUNK, COMPOSITE_VSH, COMPOSITE_FSH }
|
||||
public enum RwExtractMode { CHUNK, COMPOSITE_VSH, COMPOSITE_FSH, VERTEX_REPLAY }
|
||||
|
||||
public static RwExtractMode parseSentinel(String computeSource) {
|
||||
if (computeSource == null) return null;
|
||||
if (computeSource.startsWith("// _vg_mode: chunk")) return RwExtractMode.CHUNK;
|
||||
if (computeSource.startsWith("// _vg_mode: composite-vsh")) return RwExtractMode.COMPOSITE_VSH;
|
||||
if (computeSource.startsWith("// _vg_mode: composite-fsh")) return RwExtractMode.COMPOSITE_FSH;
|
||||
if (computeSource.startsWith("// _vg_mode: vertex-replay")) return RwExtractMode.VERTEX_REPLAY;
|
||||
return null;
|
||||
}
|
||||
|
||||
public record VertexInput(String name, String glslType, int location) {}
|
||||
|
||||
public record AttributeSlot(String name, String glslType, int byteOffset, String unpackExpr, boolean liveBranch) {
|
||||
public AttributeSlot(String name, String glslType, int byteOffset, String unpackExpr) {
|
||||
this(name, glslType, byteOffset, unpackExpr, true);
|
||||
}
|
||||
}
|
||||
|
||||
public record Result(String strippedSource, String computeSource, Set<String> writtenImages, RwExtractMode mode) {}
|
||||
public record Result(String strippedSource, String computeSource, Set<String> writtenImages, RwExtractMode mode, boolean keepsImageAtomics) {}
|
||||
|
||||
public record ImageDecl(String name, String glslType, String layoutFormat) {}
|
||||
|
||||
@@ -53,6 +57,9 @@ public final class RwImageStoreExtractor {
|
||||
|
||||
public static final int VG_VBUF_SSBO_BINDING = 9;
|
||||
public static final int VG_RANGES_SSBO_BINDING = 10;
|
||||
public static final int VG_REPLAY_VERTEX_BUFFER_BINDING = 9;
|
||||
public static final int VG_REPLAY_VERTEX_BUFFER_COUNT = 4;
|
||||
public static final int VG_REPLAY_INDEX_BUFFER_BINDING = VG_REPLAY_VERTEX_BUFFER_BINDING + VG_REPLAY_VERTEX_BUFFER_COUNT;
|
||||
private static final int STRIDE_BYTES = 48;
|
||||
private static final int STRIDE_UINTS = STRIDE_BYTES / 4;
|
||||
|
||||
@@ -73,6 +80,10 @@ public final class RwImageStoreExtractor {
|
||||
);
|
||||
|
||||
public static Result tryExtract(String source, PatchShaderType stage, String programName) {
|
||||
return tryExtract(source, stage, programName, null);
|
||||
}
|
||||
|
||||
public static Result tryExtract(String source, PatchShaderType stage, String programName, RwExtractMode vertexMode) {
|
||||
if (source == null) return null;
|
||||
if (stage != PatchShaderType.VERTEX && stage != PatchShaderType.FRAGMENT) return null;
|
||||
|
||||
@@ -94,17 +105,184 @@ public final class RwImageStoreExtractor {
|
||||
final Map<String, String> chunkAttrAstTypes = (stage == PatchShaderType.VERTEX) ? collectChunkAttrAstTypes(decls) : Map.of();
|
||||
final Set<String> chunkAttrsUsed = (stage == PatchShaderType.VERTEX) ? collectChunkAttrsUsed(decls, root, source) : Set.of();
|
||||
|
||||
final RwExtractMode mode = (stage == PatchShaderType.VERTEX) ? (chunkAttrsUsed.isEmpty() ? RwExtractMode.COMPOSITE_VSH : RwExtractMode.CHUNK) : RwExtractMode.COMPOSITE_FSH;
|
||||
final RwExtractMode mode = (stage == PatchShaderType.VERTEX)
|
||||
? (vertexMode != null ? vertexMode : chunkAttrsUsed.isEmpty() ? RwExtractMode.COMPOSITE_VSH : RwExtractMode.CHUNK)
|
||||
: RwExtractMode.COMPOSITE_FSH;
|
||||
|
||||
final List<AttributeSlot> attrs = (mode == RwExtractMode.CHUNK) ? collectVertexAttributes(chunkAttrsUsed, chunkAttrAstTypes) : List.of();
|
||||
final Set<String> quadInputs = (mode == RwExtractMode.COMPOSITE_VSH) ? declaredQuadInputs(decls) : Set.of();
|
||||
final boolean definesFtransform = definesFunction(root, "iris_ftransform");
|
||||
|
||||
final String stripped = buildRasterOutputViaSourceSlicing(source, decls, exprs, writtenImages, declared, mode);
|
||||
|
||||
final Transformer tc = new Transformer(root);
|
||||
applyComputeMutations(tc, writtenImages, declared, chunkAttrsUsed, mode, nonWriteonlyImages);
|
||||
final String compute = buildComputeOutput(root, mode, writtenImages, nonWriteonlyImages, declared, attrs);
|
||||
applyComputeMutations(tc, writtenImages, declared, mode == RwExtractMode.COMPOSITE_VSH ? quadInputs : chunkAttrsUsed, mode, nonWriteonlyImages);
|
||||
final String compute = buildComputeOutput(root, mode, writtenImages, nonWriteonlyImages, declared, attrs, quadInputs, definesFtransform, null);
|
||||
|
||||
return new Result(stripped, compute, writtenImages, mode);
|
||||
return new Result(stripped, compute, writtenImages, mode, keepsImageAtomics(root, writtenImages));
|
||||
}
|
||||
|
||||
public static Result tryExtractVertexReplay(String source) {
|
||||
if (source == null) return null;
|
||||
|
||||
final ShaderParser.ParsedShader parsed = ShaderParser.parseShader(source);
|
||||
final GLSLParser.Translation_unitContext root = parsed.full();
|
||||
|
||||
final List<GLSLParser.Single_declarationContext> decls = new ArrayList<>();
|
||||
final List<GLSLParser.Postfix_expressionContext> exprs = new ArrayList<>();
|
||||
GlslAstHelpers.collectInto(root,
|
||||
new List[] { decls, exprs },
|
||||
new Class<?>[] { GLSLParser.Single_declarationContext.class, GLSLParser.Postfix_expressionContext.class });
|
||||
|
||||
final Map<String, ImageDecl> declared = collectImageDecls(decls);
|
||||
final Set<String> writtenImages = new LinkedHashSet<>();
|
||||
final Set<String> nonWriteonlyImages = new LinkedHashSet<>();
|
||||
classifyImageCalls(exprs, declared, writtenImages, nonWriteonlyImages);
|
||||
if (writtenImages.isEmpty()) return null;
|
||||
|
||||
final String stripped = buildRasterOutputViaSourceSlicing(source, decls, exprs, writtenImages, declared, RwExtractMode.VERTEX_REPLAY);
|
||||
final List<VertexInput> inputs = collectVertexInputs(decls);
|
||||
if (inputs == null) return new Result(stripped, null, writtenImages, RwExtractMode.VERTEX_REPLAY, false);
|
||||
|
||||
final List<String> readImages = readOnlyImages(exprs, declared, writtenImages);
|
||||
final Set<String> inputNames = new LinkedHashSet<>();
|
||||
for (VertexInput input : inputs) inputNames.add(input.name());
|
||||
final Transformer tc = new Transformer(root);
|
||||
applyComputeMutations(tc, writtenImages, declared, inputNames, RwExtractMode.VERTEX_REPLAY, nonWriteonlyImages);
|
||||
for (String name : readImages) tc.removeVariable(name);
|
||||
final ReplayLayout layout = new ReplayLayout(inputs, readImages, replayHeader(inputs, writtenImages, readImages, collectSamplerNames(decls)));
|
||||
final String compute = buildComputeOutput(root, RwExtractMode.VERTEX_REPLAY, writtenImages, nonWriteonlyImages, declared, List.of(), Set.of(), false, layout);
|
||||
|
||||
return new Result(stripped, compute, writtenImages, RwExtractMode.VERTEX_REPLAY, keepsImageAtomics(root, writtenImages));
|
||||
}
|
||||
|
||||
private record ReplayLayout(List<VertexInput> inputs, List<String> readImages, String header) {}
|
||||
|
||||
private static final String REPLAY_INPUTS_HEADER = "// _vg_inputs:";
|
||||
private static final String REPLAY_IMAGES_HEADER = "// _vg_images:";
|
||||
private static final String REPLAY_READ_IMAGES_HEADER = "// _vg_read_images:";
|
||||
private static final String REPLAY_SAMPLERS_HEADER = "// _vg_samplers:";
|
||||
|
||||
private static String replayHeader(List<VertexInput> inputs, Set<String> writtenImages, List<String> readImages, List<String> samplers) {
|
||||
final StringBuilder out = new StringBuilder(REPLAY_INPUTS_HEADER);
|
||||
for (VertexInput input : inputs) out.append(' ').append(input.name()).append(':').append(input.glslType()).append(':').append(input.location());
|
||||
out.append('\n').append(REPLAY_IMAGES_HEADER);
|
||||
for (String image : sortedImages(writtenImages)) out.append(' ').append(image);
|
||||
out.append('\n').append(REPLAY_READ_IMAGES_HEADER);
|
||||
for (String image : readImages) out.append(' ').append(image);
|
||||
out.append('\n').append(REPLAY_SAMPLERS_HEADER);
|
||||
for (String sampler : samplers) out.append(' ').append(sampler);
|
||||
return out.append('\n').toString();
|
||||
}
|
||||
|
||||
/** Images the shader reads but never writes, sorted; the replay binds them after the written ones. */
|
||||
private static List<String> readOnlyImages(List<GLSLParser.Postfix_expressionContext> exprs, Map<String, ImageDecl> declared, Set<String> writtenImages) {
|
||||
final Set<String> read = new LinkedHashSet<>();
|
||||
for (var expr : exprs) {
|
||||
if (GlslAstHelpers.extractCallName(expr) == null) continue;
|
||||
final String firstArg = GlslAstHelpers.firstArgIdentifier(expr);
|
||||
if (declared.containsKey(firstArg) && !writtenImages.contains(firstArg)) read.add(firstArg);
|
||||
}
|
||||
final List<String> sorted = new ArrayList<>(read);
|
||||
Collections.sort(sorted);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/** The images a vertex-replay compute source only reads, in the order of their bindings after the written ones. */
|
||||
public static List<String> parseVertexReplayReadImages(String computeSource) {
|
||||
return headerTokens(computeSource, REPLAY_READ_IMAGES_HEADER);
|
||||
}
|
||||
|
||||
/** The samplers a vertex-replay compute source declares. */
|
||||
public static List<String> parseVertexReplaySamplers(String computeSource) {
|
||||
return headerTokens(computeSource, REPLAY_SAMPLERS_HEADER);
|
||||
}
|
||||
|
||||
/** The inputs of a vertex-replay compute source, in the order of its {@code _vg_attr} slots. */
|
||||
public static List<VertexInput> parseVertexReplayInputs(String computeSource) {
|
||||
final List<VertexInput> out = new ArrayList<>();
|
||||
for (String token : headerTokens(computeSource, REPLAY_INPUTS_HEADER)) {
|
||||
final String[] parts = token.split(":");
|
||||
if (parts.length != 3) continue;
|
||||
out.add(new VertexInput(parts[0], parts[1], Integer.parseInt(parts[2])));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** The images a vertex-replay compute source writes, in the order of their bindings. */
|
||||
public static List<String> parseVertexReplayImages(String computeSource) {
|
||||
return headerTokens(computeSource, REPLAY_IMAGES_HEADER);
|
||||
}
|
||||
|
||||
private static List<String> headerTokens(String computeSource, String header) {
|
||||
if (computeSource == null) return List.of();
|
||||
final int start = computeSource.indexOf(header);
|
||||
if (start < 0) return List.of();
|
||||
final int end = computeSource.indexOf('\n', start);
|
||||
final String line = computeSource.substring(start + header.length(), end < 0 ? computeSource.length() : end).trim();
|
||||
if (line.isEmpty()) return List.of();
|
||||
return List.of(line.split(" "));
|
||||
}
|
||||
|
||||
private static final Set<String> REPLAYABLE_INPUT_TYPES = Set.of(
|
||||
"float", "vec2", "vec3", "vec4", "int", "ivec2", "ivec3", "ivec4", "uint", "uvec2", "uvec3", "uvec4");
|
||||
|
||||
/** Global {@code in}/{@code attribute} declarations, or null if one of them cannot be fetched per component. */
|
||||
private static List<VertexInput> collectVertexInputs(List<GLSLParser.Single_declarationContext> decls) {
|
||||
final List<VertexInput> out = new ArrayList<>();
|
||||
for (var decl : decls) {
|
||||
final var fst = decl.fully_specified_type();
|
||||
if (fst == null || fst.type_qualifier() == null) continue;
|
||||
if (GlslAstHelpers.enclosingOfType(decl, GLSLParser.Function_definitionContext.class) != null) continue;
|
||||
boolean isInput = false;
|
||||
int location = -1;
|
||||
for (var sq : fst.type_qualifier().single_type_qualifier()) {
|
||||
if (sq.storage_qualifier() != null) {
|
||||
final String storage = sq.storage_qualifier().getText();
|
||||
if (storage.equals("in") || storage.equals("attribute")) isInput = true;
|
||||
}
|
||||
final var lq = sq.layout_qualifier();
|
||||
if (lq == null || lq.layout_qualifier_id_list() == null) continue;
|
||||
for (var id : lq.layout_qualifier_id_list().layout_qualifier_id()) {
|
||||
if (id.IDENTIFIER() == null || !"location".equals(id.IDENTIFIER().getText())) continue;
|
||||
try {
|
||||
location = Integer.parseInt(id.getText().substring(id.getText().indexOf('=') + 1).trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isInput) continue;
|
||||
final String type = fst.type_specifier().getText();
|
||||
if (!REPLAYABLE_INPUT_TYPES.contains(type)) return null;
|
||||
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
|
||||
declarators.add(decl.typeless_declaration());
|
||||
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) declarators.addAll(list.typeless_declaration());
|
||||
for (var typeless : declarators) {
|
||||
if (typeless == null || typeless.IDENTIFIER() == null || typeless.array_specifier() != null) return null;
|
||||
out.add(new VertexInput(typeless.IDENTIFIER().getText(), type, location));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Global sampler uniforms, which the replay may read even after the graphics program stops reading them. */
|
||||
private static List<String> collectSamplerNames(List<GLSLParser.Single_declarationContext> decls) {
|
||||
final List<String> out = new ArrayList<>();
|
||||
for (var decl : decls) {
|
||||
final var fst = decl.fully_specified_type();
|
||||
if (fst == null || fst.type_specifier() == null) continue;
|
||||
if (GlslAstHelpers.enclosingOfType(decl, GLSLParser.Function_definitionContext.class) != null) continue;
|
||||
final String type = fst.type_specifier().getText();
|
||||
if (!type.startsWith("sampler") && !type.startsWith("isampler") && !type.startsWith("usampler")) continue;
|
||||
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
|
||||
declarators.add(decl.typeless_declaration());
|
||||
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) declarators.addAll(list.typeless_declaration());
|
||||
for (var typeless : declarators) {
|
||||
if (typeless != null && typeless.IDENTIFIER() != null) out.add(typeless.IDENTIFIER().getText());
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Map<String, ImageDecl> collectImageDecls(List<GLSLParser.Single_declarationContext> decls) {
|
||||
@@ -270,13 +448,22 @@ public final class RwImageStoreExtractor {
|
||||
private static void applyComputeMutations(Transformer tc, Set<String> writtenImages, Map<String, ImageDecl> declared, Set<String> chunkAttrsUsed, RwExtractMode mode, Set<String> nonWriteonlyImages) {
|
||||
hoistNonConstGlobalInitializers(tc);
|
||||
renameMainToVgBody(tc);
|
||||
rewriteImageAtomicsToNonAtomic(tc, declared, writtenImages, nonWriteonlyImages);
|
||||
if (mode != RwExtractMode.VERTEX_REPLAY) rewriteImageAtomicsToNonAtomic(tc, declared, writtenImages, nonWriteonlyImages);
|
||||
for (String name : writtenImages) tc.removeVariable(name);
|
||||
if (mode == RwExtractMode.CHUNK) {
|
||||
if (mode == RwExtractMode.CHUNK || mode == RwExtractMode.VERTEX_REPLAY) {
|
||||
for (String name : chunkAttrsUsed) tc.removeVariable(name);
|
||||
convertInterfaceBlocks(tc);
|
||||
if (mode == RwExtractMode.VERTEX_REPLAY) {
|
||||
stripInterfaceLayoutQualifiers(tc);
|
||||
removeQualifierOnlyDeclarations(tc);
|
||||
}
|
||||
stripStorageQualifiers(tc, Set.of("out", "varying"));
|
||||
} else if (mode == RwExtractMode.COMPOSITE_VSH) {
|
||||
for (String name : chunkAttrsUsed) tc.removeVariable(name);
|
||||
tc.renameFunctionCall("ftransform", "iris_ftransform");
|
||||
convertInterfaceBlocks(tc);
|
||||
stripInterfaceLayoutQualifiers(tc);
|
||||
removeQualifierOnlyDeclarations(tc);
|
||||
stripStorageQualifiers(tc, Set.of("in", "attribute", "out", "varying"));
|
||||
} else if (mode == RwExtractMode.COMPOSITE_FSH) {
|
||||
stripFragOutputs(tc);
|
||||
@@ -311,6 +498,14 @@ public final class RwImageStoreExtractor {
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean keepsImageAtomics(GLSLParser.Translation_unitContext computeRoot, Set<String> writtenImages) {
|
||||
for (var expr : GlslAstHelpers.collectAll(computeRoot, GLSLParser.Postfix_expressionContext.class)) {
|
||||
final String fname = GlslAstHelpers.extractCallName(expr);
|
||||
if (fname != null && ATOMIC_CALLS.contains(fname) && writtenImages.contains(GlslAstHelpers.firstArgIdentifier(expr))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isCallTopLevelStatement(GLSLParser.Postfix_expressionContext expr) {
|
||||
final GLSLParser.StatementContext stmt = GlslAstHelpers.enclosingOfType(expr, GLSLParser.StatementContext.class);
|
||||
if (stmt == null) return false;
|
||||
@@ -409,6 +604,7 @@ public final class RwImageStoreExtractor {
|
||||
t.mutateTree(tree -> {
|
||||
for (var sq : GlslAstHelpers.collectAll(tree, GLSLParser.Storage_qualifierContext.class)) {
|
||||
if (!kill.contains(sq.getText())) continue;
|
||||
if (GlslAstHelpers.enclosingOfType(sq, GLSLParser.Parameter_declarationContext.class) != null) continue;
|
||||
blankLeadingToken(sq);
|
||||
}
|
||||
});
|
||||
@@ -428,6 +624,91 @@ public final class RwImageStoreExtractor {
|
||||
|
||||
private static final Set<String> AUXILIARY_STORAGE_QUALIFIERS = Set.of("centroid", "sample", "patch");
|
||||
|
||||
private static final Set<String> INTERFACE_STORAGE_QUALIFIERS = Set.of("in", "out", "attribute", "varying");
|
||||
|
||||
private static void removeQualifierOnlyDeclarations(Transformer t) {
|
||||
t.mutateTree(tree -> {
|
||||
if (tree.children == null) return;
|
||||
tree.children.removeIf(child -> child instanceof GLSLParser.External_declarationContext ed
|
||||
&& ed.declaration() != null && isQualifierOnly(ed.declaration()));
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isQualifierOnly(GLSLParser.DeclarationContext decl) {
|
||||
if (decl.LEFT_BRACE() != null) return false;
|
||||
if (decl.init_declarator_list() == null) return decl.type_qualifier() != null && qualifiesStageInterface(decl.type_qualifier());
|
||||
final var single = decl.init_declarator_list().single_declaration();
|
||||
return single != null && single.typeless_declaration() == null && decl.init_declarator_list().COMMA().isEmpty()
|
||||
&& single.fully_specified_type() != null && single.fully_specified_type().type_qualifier() != null
|
||||
&& qualifiesStageInterface(single.fully_specified_type().type_qualifier());
|
||||
}
|
||||
|
||||
private static boolean qualifiesStageInterface(GLSLParser.Type_qualifierContext tq) {
|
||||
for (var sq : tq.single_type_qualifier()) {
|
||||
if (sq.invariant_qualifier() != null || sq.precise_qualifier() != null) return true;
|
||||
if (sq.storage_qualifier() != null && INTERFACE_STORAGE_QUALIFIERS.contains(sq.storage_qualifier().getText())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void convertInterfaceBlocks(Transformer t) {
|
||||
t.mutateTree(tree -> {
|
||||
if (tree.children == null) return;
|
||||
tree.children.removeIf(child -> child instanceof GLSLParser.External_declarationContext ed
|
||||
&& isInterfaceBlock(ed.declaration()) && "gl_PerVertex".equals(ed.declaration().IDENTIFIER(0).getText()));
|
||||
for (var child : tree.children) {
|
||||
if (!(child instanceof GLSLParser.External_declarationContext ed) || !isInterfaceBlock(ed.declaration())) continue;
|
||||
final GLSLParser.DeclarationContext decl = ed.declaration();
|
||||
final String blockName = decl.IDENTIFIER(0).getText();
|
||||
blankAllTerminals(decl.type_qualifier());
|
||||
if (decl.IDENTIFIER().size() > 1) {
|
||||
setFirstTerminal(decl.type_qualifier(), "struct");
|
||||
setText(decl.RIGHT_BRACE(), "}; " + blockName);
|
||||
} else {
|
||||
setText(decl.IDENTIFIER(0), "");
|
||||
setText(decl.LEFT_BRACE(), "");
|
||||
setText(decl.RIGHT_BRACE(), "");
|
||||
setText(decl.SEMICOLON(), "");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isInterfaceBlock(GLSLParser.DeclarationContext decl) {
|
||||
return decl != null && decl.LEFT_BRACE() != null && decl.type_qualifier() != null && !decl.IDENTIFIER().isEmpty()
|
||||
&& qualifiesStageInterface(decl.type_qualifier());
|
||||
}
|
||||
|
||||
private static void setText(TerminalNode node, String text) {
|
||||
if (node != null && node.getSymbol() instanceof CommonToken ct) ct.setText(text);
|
||||
}
|
||||
|
||||
private static void setFirstTerminal(ParseTree node, String text) {
|
||||
if (node instanceof TerminalNode tn) {
|
||||
setText(tn, text);
|
||||
return;
|
||||
}
|
||||
if (node.getChildCount() > 0) setFirstTerminal(node.getChild(0), text);
|
||||
}
|
||||
|
||||
private static void stripInterfaceLayoutQualifiers(Transformer t) {
|
||||
t.mutateTree(tree -> {
|
||||
for (var tq : GlslAstHelpers.collectAll(tree, GLSLParser.Type_qualifierContext.class)) {
|
||||
boolean isInterface = false;
|
||||
for (var sq : tq.single_type_qualifier()) {
|
||||
if (sq.storage_qualifier() != null && INTERFACE_STORAGE_QUALIFIERS.contains(sq.storage_qualifier().getText())) {
|
||||
isInterface = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isInterface) continue;
|
||||
for (var sq : tq.single_type_qualifier()) {
|
||||
if (sq.layout_qualifier() != null) blankAllTerminals(sq.layout_qualifier());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void blankLeadingToken(ParserRuleContext ctx) {
|
||||
if (ctx.getChildCount() == 0) return;
|
||||
if (!(ctx.getChild(0) instanceof TerminalNode tn)) return;
|
||||
@@ -537,19 +818,42 @@ public final class RwImageStoreExtractor {
|
||||
});
|
||||
}
|
||||
|
||||
private static String buildComputeOutput(GLSLParser.Translation_unitContext computeRoot, RwExtractMode mode, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared, List<AttributeSlot> attrs) {
|
||||
private static final Set<String> QUAD_INPUTS = Set.of("iris_Vertex", "iris_MultiTexCoord0");
|
||||
|
||||
private static Set<String> declaredQuadInputs(List<GLSLParser.Single_declarationContext> decls) {
|
||||
final Set<String> out = new LinkedHashSet<>();
|
||||
for (var decl : decls) {
|
||||
if (decl.typeless_declaration() == null || decl.typeless_declaration().IDENTIFIER() == null) continue;
|
||||
if (GlslAstHelpers.enclosingOfType(decl, GLSLParser.Function_definitionContext.class) != null) continue;
|
||||
final String name = decl.typeless_declaration().IDENTIFIER().getText();
|
||||
if (QUAD_INPUTS.contains(name)) out.add(name);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static boolean definesFunction(GLSLParser.Translation_unitContext root, String name) {
|
||||
for (var fdef : GlslAstHelpers.collectAll(root, GLSLParser.Function_definitionContext.class)) {
|
||||
final var id = fdef.function_prototype().IDENTIFIER();
|
||||
if (id != null && name.equals(id.getText())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String buildComputeOutput(GLSLParser.Translation_unitContext computeRoot, RwExtractMode mode, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared, List<AttributeSlot> attrs, Set<String> quadInputs, boolean definesFtransform, ReplayLayout replay) {
|
||||
final StringBuilder out = new StringBuilder(8192);
|
||||
out.append("// _vg_mode: ").append(modeTag(mode)).append('\n');
|
||||
if (replay != null) out.append(replay.header());
|
||||
out.append("#version 460 core\n");
|
||||
switch (mode) {
|
||||
case CHUNK -> emitChunkPrelude(out, writtenImages, nonWriteonlyImages, declared, attrs);
|
||||
case COMPOSITE_VSH -> emitCompositeVshPrelude(out, writtenImages, nonWriteonlyImages, declared);
|
||||
case COMPOSITE_VSH -> emitCompositeVshPrelude(out, writtenImages, nonWriteonlyImages, declared, quadInputs, definesFtransform);
|
||||
case COMPOSITE_FSH -> emitCompositeFshPrelude(out, writtenImages, nonWriteonlyImages, declared);
|
||||
case VERTEX_REPLAY -> emitVertexReplayPrelude(out, writtenImages, nonWriteonlyImages, declared, replay);
|
||||
}
|
||||
out.append('\n');
|
||||
out.append(GlslTransformUtils.getFormattedShader(computeRoot, ""));
|
||||
out.append('\n');
|
||||
emitDispatchMain(out, mode, attrs);
|
||||
emitDispatchMain(out, mode, attrs, replay != null ? replay.inputs() : List.of(), quadInputs);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
@@ -558,19 +862,26 @@ public final class RwImageStoreExtractor {
|
||||
case CHUNK -> "chunk";
|
||||
case COMPOSITE_VSH -> "composite-vsh";
|
||||
case COMPOSITE_FSH -> "composite-fsh";
|
||||
case VERTEX_REPLAY -> "vertex-replay";
|
||||
};
|
||||
}
|
||||
|
||||
private static void emitImageDecls(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared) {
|
||||
private static List<String> sortedImages(Set<String> writtenImages) {
|
||||
final List<String> sorted = new ArrayList<>(writtenImages);
|
||||
Collections.sort(sorted);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private static void emitImageDecls(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared) {
|
||||
final List<String> sorted = sortedImages(writtenImages);
|
||||
final Map<String, ImageInformation> infos = activeCustomImages;
|
||||
for (int i = 0; i < sorted.size(); i++) {
|
||||
final String name = sorted.get(i);
|
||||
final ImageDecl d = declared.get(name);
|
||||
if (d == null) continue;
|
||||
final InternalTextureFormat fmt = (infos != null && infos.get(name) != null) ? infos.get(name).internalTextureFormat() : null;
|
||||
final String layoutInner = (fmt != null) ? "binding = " + i + ", " + fmt.name().toLowerCase(Locale.ROOT) : "binding = " + i;
|
||||
final String format = fmt != null ? fmt.name().toLowerCase(Locale.ROOT) : d.layoutFormat();
|
||||
final String layoutInner = (format != null) ? "binding = " + i + ", " + format : "binding = " + i;
|
||||
final String qualifier = nonWriteonlyImages.contains(name) ? "" : "writeonly ";
|
||||
out.append("layout(").append(layoutInner).append(") ").append(qualifier).append("uniform ").append(d.glslType()).append(' ').append(name).append(";\n");
|
||||
}
|
||||
@@ -604,7 +915,7 @@ public final class RwImageStoreExtractor {
|
||||
out.append("void _vert_init() {}\n");
|
||||
}
|
||||
|
||||
private static void emitCompositeVshPrelude(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared) {
|
||||
private static void emitCompositeVshPrelude(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared, Set<String> quadInputs, boolean definesFtransform) {
|
||||
out.append("layout(local_size_x = 64) in;\n\n");
|
||||
emitImageDecls(out, writtenImages, nonWriteonlyImages, declared);
|
||||
out.append("\nconst vec4 _vg_quad[4] = vec4[4](\n");
|
||||
@@ -614,10 +925,67 @@ public final class RwImageStoreExtractor {
|
||||
out.append(" vec4(0.0, 0.0, 0.0, 1.0));\n");
|
||||
out.append("vec4 _vg_gl_vertex;\n");
|
||||
out.append("int _vg_gl_vertex_id;\n");
|
||||
out.append("vec4 _vg_sink_pos;\n");
|
||||
out.append("float _vg_sink_psize;\n");
|
||||
out.append("float _vg_sink_clip[8];\n");
|
||||
out.append("#define gl_Vertex _vg_gl_vertex\n");
|
||||
out.append("#define gl_VertexID _vg_gl_vertex_id\n");
|
||||
out.append("uniform mat4 gl_ModelViewProjectionMatrix;\n");
|
||||
out.append("vec4 iris_ftransform() { return gl_ModelViewProjectionMatrix * gl_Vertex; }\n");
|
||||
out.append("#define gl_Position _vg_sink_pos\n");
|
||||
out.append("#define gl_PointSize _vg_sink_psize\n");
|
||||
out.append("#define gl_ClipDistance _vg_sink_clip\n");
|
||||
for (String input : quadInputs) out.append("vec4 ").append(input).append(";\n");
|
||||
if (!definesFtransform) {
|
||||
out.append("uniform mat4 gl_ModelViewProjectionMatrix;\n");
|
||||
out.append("vec4 iris_ftransform() { return gl_ModelViewProjectionMatrix * gl_Vertex; }\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitVertexReplayPrelude(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared, ReplayLayout replay) {
|
||||
final List<VertexInput> inputs = replay.inputs();
|
||||
out.append("layout(local_size_x = 64) in;\n\n");
|
||||
emitImageDecls(out, writtenImages, nonWriteonlyImages, declared);
|
||||
final Map<String, ImageInformation> infos = activeCustomImages;
|
||||
for (int i = 0; i < replay.readImages().size(); i++) {
|
||||
final ImageDecl d = declared.get(replay.readImages().get(i));
|
||||
final InternalTextureFormat fmt = infos.get(d.name()) != null ? infos.get(d.name()).internalTextureFormat() : null;
|
||||
final String format = fmt != null ? fmt.name().toLowerCase(Locale.ROOT) : d.layoutFormat();
|
||||
out.append("layout(binding = ").append(writtenImages.size() + i).append(format != null ? ", " + format : "").append(") readonly uniform ")
|
||||
.append(d.glslType()).append(' ').append(d.name()).append(";\n");
|
||||
}
|
||||
out.append('\n');
|
||||
for (int i = 0; i < VG_REPLAY_VERTEX_BUFFER_COUNT; i++) {
|
||||
out.append("layout(std430, binding = ").append(VG_REPLAY_VERTEX_BUFFER_BINDING + i)
|
||||
.append(") readonly buffer _VgVertices").append(i).append(" { uint d[]; } _vg_vertices").append(i).append(";\n");
|
||||
}
|
||||
out.append("layout(std430, binding = ").append(VG_REPLAY_INDEX_BUFFER_BINDING).append(") readonly buffer _VgIndices { uint d[]; } _vg_indices;\n\n");
|
||||
final int slots = Math.max(1, inputs.size());
|
||||
out.append("uniform uvec4 _vg_attr[").append(slots).append("];\n");
|
||||
out.append("uniform vec4 _vg_attrDefault[").append(slots).append("];\n");
|
||||
out.append("uniform ivec4 _vg_draw;\n");
|
||||
out.append("uniform ivec3 _vg_index;\n");
|
||||
out.append("uniform uvec2 _vg_restart;\n");
|
||||
out.append("uniform int _vg_invocationBase;\n\n");
|
||||
out.append("vec4 _vg_sink_pos;\n");
|
||||
out.append("float _vg_sink_psize;\n");
|
||||
out.append("float _vg_sink_clip[8];\n");
|
||||
out.append("int _vg_vertex_id;\n");
|
||||
out.append("int _vg_instance_id;\n");
|
||||
out.append("#define gl_Position _vg_sink_pos\n");
|
||||
out.append("#define gl_PointSize _vg_sink_psize\n");
|
||||
out.append("#define gl_ClipDistance _vg_sink_clip\n");
|
||||
out.append("#define gl_VertexID _vg_vertex_id\n");
|
||||
out.append("#define gl_VertexIndex _vg_vertex_id\n");
|
||||
out.append("#define gl_InstanceID _vg_instance_id\n");
|
||||
out.append("#define gl_InstanceIndex _vg_instance_id\n");
|
||||
for (String suffix : new String[] { "", "ARB" }) {
|
||||
out.append("#define gl_BaseVertex").append(suffix).append(" _vg_draw.w\n");
|
||||
out.append("#define gl_BaseInstance").append(suffix).append(" 0\n");
|
||||
out.append("#define gl_DrawID").append(suffix).append(" 0\n");
|
||||
}
|
||||
out.append('\n');
|
||||
for (VertexInput input : inputs) out.append(input.glslType()).append(' ').append(input.name()).append(";\n");
|
||||
out.append('\n');
|
||||
out.append(ShaderLoader.getShaderSource("angelica:include/vertex_replay_fetch.glsl"));
|
||||
}
|
||||
|
||||
private static void emitCompositeFshPrelude(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared) {
|
||||
@@ -643,7 +1011,7 @@ public final class RwImageStoreExtractor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitDispatchMain(StringBuilder out, RwExtractMode mode, List<AttributeSlot> attrs) {
|
||||
private static void emitDispatchMain(StringBuilder out, RwExtractMode mode, List<AttributeSlot> attrs, List<VertexInput> inputs, Set<String> quadInputs) {
|
||||
out.append("void main() {\n");
|
||||
switch (mode) {
|
||||
case CHUNK -> {
|
||||
@@ -665,6 +1033,7 @@ public final class RwImageStoreExtractor {
|
||||
out.append(" uint vid = gl_GlobalInvocationID.x;\n");
|
||||
out.append(" if (vid >= 4u) return;\n");
|
||||
out.append(" _vg_gl_vertex = _vg_quad[vid];\n");
|
||||
for (String input : quadInputs) out.append(" ").append(input).append(" = _vg_quad[vid];\n");
|
||||
out.append(" _vg_gl_vertex_id = int(vid);\n");
|
||||
out.append(" _vg_body();\n");
|
||||
}
|
||||
@@ -674,10 +1043,37 @@ public final class RwImageStoreExtractor {
|
||||
out.append(" _vg_gl_fragcoord_raw = vec4(vec2(px) + 0.5, 0.0, 1.0);\n");
|
||||
out.append(" _vg_body();\n");
|
||||
}
|
||||
case VERTEX_REPLAY -> {
|
||||
out.append(" uint _vg_g = uint(_vg_invocationBase) + gl_GlobalInvocationID.x;\n");
|
||||
out.append(" uint _vg_count = uint(_vg_draw.y);\n");
|
||||
out.append(" if (_vg_count == 0u || _vg_g >= _vg_count * uint(_vg_draw.z)) return;\n");
|
||||
out.append(" uint _vg_v = _vg_g % _vg_count;\n");
|
||||
out.append(" uint _vg_i = _vg_g / _vg_count;\n");
|
||||
out.append(" uint _vg_vertex = uint(_vg_draw.x) + _vg_v;\n");
|
||||
out.append(" if (_vg_index.x != 0) {\n");
|
||||
out.append(" uint _vg_index_value = _vg_v < uint(_vg_index.z) ? _vg_read(4u, uint(_vg_index.y) + _vg_v * uint(_vg_index.x), uint(_vg_index.x)) : 0u;\n");
|
||||
out.append(" if (_vg_restart.x != 0u && _vg_index_value == _vg_restart.y) return;\n");
|
||||
out.append(" _vg_vertex = _vg_index_value + uint(_vg_draw.w);\n");
|
||||
out.append(" }\n");
|
||||
out.append(" _vg_vertex_id = int(_vg_vertex);\n");
|
||||
out.append(" _vg_instance_id = int(_vg_i);\n");
|
||||
for (int k = 0; k < inputs.size(); k++) {
|
||||
final VertexInput input = inputs.get(k);
|
||||
out.append(" ").append(input.name()).append(" = ").append(input.glslType()).append('(')
|
||||
.append(fetchFunctionFor(input.glslType())).append('(').append(k).append("u, _vg_vertex, _vg_i));\n");
|
||||
}
|
||||
out.append(" _vg_body();\n");
|
||||
}
|
||||
}
|
||||
out.append("}\n");
|
||||
}
|
||||
|
||||
private static String fetchFunctionFor(String glslType) {
|
||||
if (glslType.startsWith("u")) return "_vg_uint";
|
||||
if (glslType.startsWith("i")) return "_vg_int";
|
||||
return "_vg_float";
|
||||
}
|
||||
|
||||
private static List<AttributeSlot> collectVertexAttributes(Set<String> chunkAttrsUsed, Map<String, String> chunkAttrAstTypes) {
|
||||
final List<AttributeSlot> out = new ArrayList<>();
|
||||
for (String name : chunkAttrsUsed) {
|
||||
|
||||
@@ -237,7 +237,8 @@ public class ShaderTransformer {
|
||||
final ShaderDiskCache.Key k = ShaderDiskCache.key("iris-transform").str(patch.name());
|
||||
parameters.appendDiskKey(k);
|
||||
k.i(RenderSystem.getMaxGlslVersion()).b(RenderSystem.supportsSSBO()).b(RenderSystem.supportsImageLoadStore())
|
||||
.b(RenderSystem.isGLES()).b(BackendManager.RENDER_BACKEND.isSDLGPU()).i(maxSupportedHoistVersion);
|
||||
.b(RenderSystem.isGLES()).b(BackendManager.RENDER_BACKEND.isSDLGPU()).b(BackendManager.RENDER_BACKEND.supportsComputeImageAtomics())
|
||||
.i(maxSupportedHoistVersion);
|
||||
if (enabledRequirements == null) {
|
||||
k.i(-1);
|
||||
} else {
|
||||
@@ -559,18 +560,23 @@ public class ShaderTransformer {
|
||||
artifactsOut.put(shaderType, new StageArtifact(treeHolder[0], formattedShader.length() - printedBody.length()));
|
||||
}
|
||||
}
|
||||
if (BackendManager.RENDER_BACKEND.isSDLGPU()) extractRwImageStores(result, patchType, artifactsOut);
|
||||
if (BackendManager.RENDER_BACKEND.isSDLGPU()) extractRwImageStores(result, patchType, artifactsOut, BackendManager.RENDER_BACKEND.supportsComputeImageAtomics());
|
||||
watch.stop();
|
||||
Iris.logger.info("[Load #{}] Transformed shader for {} in {}", Iris.getShaderPackLoadId(), patchType.name(), watch);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void extractRwImageStores(EnumMap<PatchShaderType, String> result, Patch patchType, EnumMap<PatchShaderType, StageArtifact> artifactsOut) {
|
||||
static void extractRwImageStores(EnumMap<PatchShaderType, String> result, Patch patchType, EnumMap<PatchShaderType, StageArtifact> artifactsOut, boolean computeImageAtomics) {
|
||||
if (result.containsKey(PatchShaderType.COMPUTE)) return;
|
||||
if (patchType == Patch.ATTRIBUTES) {
|
||||
extractVertexReplay(result, artifactsOut, computeImageAtomics);
|
||||
return;
|
||||
}
|
||||
|
||||
final String vsh = result.get(PatchShaderType.VERTEX);
|
||||
final String fsh = result.get(PatchShaderType.FRAGMENT);
|
||||
final RwImageStoreExtractor.Result vshResult = (vsh != null) ? RwImageStoreExtractor.tryExtract(vsh, PatchShaderType.VERTEX, patchType.name()) : null;
|
||||
final RwImageStoreExtractor.RwExtractMode vertexMode = patchType == Patch.COMPOSITE ? RwImageStoreExtractor.RwExtractMode.COMPOSITE_VSH : null;
|
||||
final RwImageStoreExtractor.Result vshResult = (vsh != null) ? RwImageStoreExtractor.tryExtract(vsh, PatchShaderType.VERTEX, patchType.name(), vertexMode) : null;
|
||||
final RwImageStoreExtractor.Result fshResult = (fsh != null) ? RwImageStoreExtractor.tryExtract(fsh, PatchShaderType.FRAGMENT, patchType.name()) : null;
|
||||
|
||||
if (vshResult != null && fshResult != null) {
|
||||
@@ -581,14 +587,42 @@ public class ShaderTransformer {
|
||||
final PatchShaderType stage = vshResult != null ? PatchShaderType.VERTEX : PatchShaderType.FRAGMENT;
|
||||
result.put(stage, extracted.strippedSource());
|
||||
if (artifactsOut != null) artifactsOut.remove(stage);
|
||||
if (patchType == Patch.COMPOSITE || patchType == Patch.CELERITAS_TERRAIN) {
|
||||
if (patchType != Patch.COMPOSITE && patchType != Patch.CELERITAS_TERRAIN) {
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped image writes from {} (written={})", patchType.name(), extracted.writtenImages());
|
||||
} else if (extracted.keepsImageAtomics() && !computeImageAtomics) {
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped image writes from {}, they need image atomics this backend cannot run (written={})", patchType.name(), extracted.writtenImages());
|
||||
} else {
|
||||
result.put(PatchShaderType.COMPUTE, extracted.computeSource());
|
||||
Iris.logger.info("[RwImageStoreExtractor] Extracted compute pre-pass for {} (mode={}, written={})", patchType.name(), extracted.mode(), extracted.writtenImages());
|
||||
} else {
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped image writes from {} (written={})", patchType.name(), extracted.writtenImages());
|
||||
}
|
||||
}
|
||||
|
||||
private static void extractVertexReplay(EnumMap<PatchShaderType, String> result, EnumMap<PatchShaderType, StageArtifact> artifactsOut, boolean computeImageAtomics) {
|
||||
final String fsh = result.get(PatchShaderType.FRAGMENT);
|
||||
final RwImageStoreExtractor.Result fshResult = (fsh != null) ? RwImageStoreExtractor.tryExtract(fsh, PatchShaderType.FRAGMENT, Patch.ATTRIBUTES.name()) : null;
|
||||
if (fshResult != null) {
|
||||
result.put(PatchShaderType.FRAGMENT, fshResult.strippedSource());
|
||||
if (artifactsOut != null) artifactsOut.remove(PatchShaderType.FRAGMENT);
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped fragment image writes from ATTRIBUTES (written={})", fshResult.writtenImages());
|
||||
}
|
||||
|
||||
final String vsh = result.get(PatchShaderType.VERTEX);
|
||||
final RwImageStoreExtractor.Result vshResult = (vsh != null) ? RwImageStoreExtractor.tryExtractVertexReplay(vsh) : null;
|
||||
if (vshResult == null) return;
|
||||
result.put(PatchShaderType.VERTEX, vshResult.strippedSource());
|
||||
if (artifactsOut != null) artifactsOut.remove(PatchShaderType.VERTEX);
|
||||
if (vshResult.computeSource() == null) {
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped vertex image writes from ATTRIBUTES, an input cannot be replayed (written={})", vshResult.writtenImages());
|
||||
return;
|
||||
}
|
||||
if (vshResult.keepsImageAtomics() && !computeImageAtomics) {
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped vertex image writes from ATTRIBUTES, they need image atomics this backend cannot run (written={})", vshResult.writtenImages());
|
||||
return;
|
||||
}
|
||||
result.put(PatchShaderType.COMPUTE, vshResult.computeSource());
|
||||
Iris.logger.info("[RwImageStoreExtractor] Extracted vertex replay for ATTRIBUTES (written={})", vshResult.writtenImages());
|
||||
}
|
||||
|
||||
private static void doTransform(Transformer transformer, Patch patchType, Parameters parameters, int versionInt) {
|
||||
switch (patchType) {
|
||||
case CELERITAS_TERRAIN:
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// Taken mostly from mesa
|
||||
|
||||
uint _vg_word(uint slot, uint i) {
|
||||
switch (slot) {
|
||||
case 0u: return _vg_vertices0.d[i];
|
||||
case 1u: return _vg_vertices1.d[i];
|
||||
case 2u: return _vg_vertices2.d[i];
|
||||
case 3u: return _vg_vertices3.d[i];
|
||||
default: return _vg_indices.d[i];
|
||||
}
|
||||
}
|
||||
|
||||
uint _vg_read(uint slot, uint at, uint size) {
|
||||
uint shift = (at & 3u) * 8u;
|
||||
uint value = _vg_word(slot, at >> 2) >> shift;
|
||||
if (shift + size * 8u > 32u) value |= _vg_word(slot, (at >> 2) + 1u) << (32u - shift);
|
||||
return size == 4u ? value : value & ((1u << (size * 8u)) - 1u);
|
||||
}
|
||||
|
||||
const uint _VG_COMPONENT_SIZE[12] = uint[12](4u, 2u, 1u, 1u, 2u, 2u, 4u, 4u, 4u, 4u, 4u, 4u);
|
||||
|
||||
bool _vg_enabled(uint a) {
|
||||
return (_vg_attr[a].x & 0x2000u) != 0u;
|
||||
}
|
||||
|
||||
uint _vg_element(uint a, uint vertex, uint instance) {
|
||||
uvec4 desc = _vg_attr[a];
|
||||
uint divisor = desc.x >> 16;
|
||||
uint element = divisor == 0u ? vertex : instance / divisor;
|
||||
return element > desc.w ? 0u : element;
|
||||
}
|
||||
|
||||
uvec4 _vg_raw(uint a, uint element) {
|
||||
uvec4 desc = _vg_attr[a];
|
||||
uint type = (desc.x >> 4) & 15u;
|
||||
uint at = desc.y + element * desc.z;
|
||||
bool none = (desc.x & 0x8000u) != 0u;
|
||||
if (type >= 9u) {
|
||||
uint packed = none ? 0u : _vg_read(desc.x & 7u, at, 4u);
|
||||
if (type == 9u) return uvec4(ivec4(int(packed << 22), int(packed << 12), int(packed << 2), int(packed)) >> ivec4(22, 22, 22, 30));
|
||||
if (type == 10u) return (uvec4(packed) >> uvec4(0u, 10u, 20u, 30u)) & uvec4(1023u, 1023u, 1023u, 3u);
|
||||
return uvec4(packed, 0u, 0u, 1u);
|
||||
}
|
||||
uint count = (desc.x >> 8) & 7u;
|
||||
uint size = _VG_COMPONENT_SIZE[type];
|
||||
uvec4 raw = uvec4(0u, 0u, 0u, 1u);
|
||||
for (uint c = 0u; c < count; c++) {
|
||||
uint v = none ? 0u : _vg_read(desc.x & 7u, at + c * size, size);
|
||||
if (type == 2u) v = uint(int(v << 24) >> 24);
|
||||
else if (type == 4u) v = uint(int(v << 16) >> 16);
|
||||
raw[c] = v;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
vec4 _vg_float(uint a, uint vertex, uint instance) {
|
||||
if (!_vg_enabled(a)) return _vg_attrDefault[a];
|
||||
uvec4 desc = _vg_attr[a];
|
||||
uint type = (desc.x >> 4) & 15u;
|
||||
uint count = (desc.x >> 8) & 7u;
|
||||
bool normalized = (desc.x & 0x800u) != 0u;
|
||||
uvec4 raw = _vg_raw(a, _vg_element(a, vertex, instance));
|
||||
vec4 v = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
if (type == 9u) {
|
||||
vec4 c = vec4(ivec4(raw));
|
||||
v = normalized ? max(c / vec4(511.0, 511.0, 511.0, 1.0), vec4(-1.0)) : c;
|
||||
} else if (type == 10u) {
|
||||
vec4 c = vec4(raw);
|
||||
v = normalized ? c / vec4(1023.0, 1023.0, 1023.0, 3.0) : c;
|
||||
} else if (type == 11u) {
|
||||
uint p = raw.x;
|
||||
v = vec4(unpackHalf2x16((p & 0x7ffu) << 4).x, unpackHalf2x16((p & 0x3ff800u) >> 7).x,
|
||||
unpackHalf2x16((p & 0xffc00000u) >> 17).x, 1.0);
|
||||
} else {
|
||||
for (uint c = 0u; c < count; c++) {
|
||||
uint r = raw[c];
|
||||
switch (type) {
|
||||
case 0u: v[c] = uintBitsToFloat(r); break;
|
||||
case 1u: v[c] = unpackHalf2x16(r).x; break;
|
||||
case 2u: v[c] = normalized ? max(float(int(r)) / 127.0, -1.0) : float(int(r)); break;
|
||||
case 3u: v[c] = normalized ? float(r) / 255.0 : float(r); break;
|
||||
case 4u: v[c] = normalized ? max(float(int(r)) / 32767.0, -1.0) : float(int(r)); break;
|
||||
case 5u: v[c] = normalized ? float(r) / 65535.0 : float(r); break;
|
||||
case 6u: v[c] = normalized ? max(float(int(r)) / 2147483647.0, -1.0) : float(int(r)); break;
|
||||
case 7u: v[c] = normalized ? float(r) / 4294967295.0 : float(r); break;
|
||||
default: v[c] = float(int(r)) / 65536.0; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (desc.x & 0x4000u) != 0u ? v.zyxw : v;
|
||||
}
|
||||
|
||||
ivec4 _vg_int(uint a, uint vertex, uint instance) {
|
||||
if (!_vg_enabled(a)) return ivec4(_vg_attrDefault[a]);
|
||||
if ((_vg_attr[a].x & 0x1000u) == 0u) return ivec4(_vg_float(a, vertex, instance));
|
||||
return ivec4(_vg_raw(a, _vg_element(a, vertex, instance)));
|
||||
}
|
||||
|
||||
uvec4 _vg_uint(uint a, uint vertex, uint instance) {
|
||||
if (!_vg_enabled(a)) return uvec4(_vg_attrDefault[a]);
|
||||
if ((_vg_attr[a].x & 0x1000u) == 0u) return uvec4(_vg_float(a, vertex, instance));
|
||||
return _vg_raw(a, _vg_element(a, vertex, instance));
|
||||
}
|
||||
+11
-39
@@ -2,18 +2,13 @@ package com.gtnewhorizons.angelica.mixins.early.shaders;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import com.gtnewhorizons.angelica.api.EnderDragonBeams;
|
||||
import net.minecraft.client.renderer.entity.RenderDragon;
|
||||
import net.minecraft.entity.boss.EntityDragon;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Mixin allows devs to target the ender crystal beams and dragon death rays as separate entities.
|
||||
@@ -21,43 +16,24 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
*/
|
||||
@Mixin(RenderDragon.class)
|
||||
public abstract class MixinRenderDragon {
|
||||
@Unique
|
||||
private static final NamespacedId END_CRYSTAL_BEAM = new NamespacedId("minecraft", "end_crystal_beam");
|
||||
|
||||
@Unique
|
||||
private static final NamespacedId DRAGON_DEATH_RAY = new NamespacedId("minecraft", "dragon_death_rays");
|
||||
|
||||
@Unique
|
||||
private boolean angelica$beamScope;
|
||||
|
||||
@Unique
|
||||
private int angelica$depthPassReplay = 0;
|
||||
|
||||
@Invoker("renderEquippedItems")
|
||||
protected abstract void angelica$invokeRenderEquippedItems(EntityDragon dragon, float partialTicks);
|
||||
|
||||
@Inject(
|
||||
@Surround(
|
||||
id = "crystalBeam",
|
||||
method = "doRender(Lnet/minecraft/entity/boss/EntityDragon;DDDFF)V",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glPushMatrix()V", ordinal = 0, shift = At.Shift.AFTER, remap = false)
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I")
|
||||
)
|
||||
private void iris$setBeamEntityId(EntityDragon dragon, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) {
|
||||
// Only set the ID if the dragon is being healed by a crystal
|
||||
if (dragon.healingEnderCrystal != null) {
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
|
||||
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(END_CRYSTAL_BEAM);
|
||||
angelica$beamScope = true;
|
||||
}
|
||||
private void iris$beginCrystalBeam() {
|
||||
EnderDragonBeams.beginCrystalBeam();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/boss/EntityDragon;DDDFF)V",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glPopMatrix()V", ordinal = 0, shift = At.Shift.BEFORE, remap = false)
|
||||
)
|
||||
private void iris$restoreEntityId(EntityDragon dragon, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) {
|
||||
if (angelica$beamScope) {
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
|
||||
angelica$beamScope = false;
|
||||
}
|
||||
@Surround.Finally("crystalBeam")
|
||||
private void iris$endCrystalBeam() {
|
||||
EnderDragonBeams.endCrystalBeam();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,9 +48,7 @@ public abstract class MixinRenderDragon {
|
||||
@Surround.Carry
|
||||
final boolean deathBeams = angelica$depthPassReplay == 0 && dragon.deathTicks > 0;
|
||||
if (deathBeams) {
|
||||
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.LIGHTNING);
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
|
||||
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(DRAGON_DEATH_RAY);
|
||||
EnderDragonBeams.beginDeathRays();
|
||||
|
||||
angelica$depthPassReplay++;
|
||||
GLStateManager.glColorMask(false, false, false, false);
|
||||
@@ -114,8 +88,6 @@ public abstract class MixinRenderDragon {
|
||||
|
||||
@Surround.Finally("deathBeams")
|
||||
private void angelica$endDeathBeamsLighting(@Surround.Carry boolean deathBeams) {
|
||||
if (!deathBeams) return;
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
|
||||
GbufferPrograms.teardownSpecialRenderCondition();
|
||||
if (deathBeams) EnderDragonBeams.endDeathRays();
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
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 CompositeVertexWriteExtractionTest {
|
||||
|
||||
// Shaped like Euphoria Patches' End prepare pass
|
||||
static final String PREPARE_VERTEX = """
|
||||
#version 130
|
||||
#extension GL_ARB_shader_image_load_store : enable
|
||||
uniform float frameTime;
|
||||
layout(r32i) uniform iimage2D endcrystal_img;
|
||||
varying vec2 texCoord;
|
||||
void main() {
|
||||
texCoord = gl_MultiTexCoord0.xy;
|
||||
gl_Position = ftransform();
|
||||
vec4 position0 = ftransform();
|
||||
if (position0.x < 0.0 && position0.y > 0.0) {
|
||||
for (int index = 0; index < 20; index++) {
|
||||
int state = imageLoad(endcrystal_img, ivec2(index, 8)).r - max(1, int(10000.0 * frameTime));
|
||||
int previousWeight = imageAtomicCompSwap(endcrystal_img, ivec2(index, 8), 0, -1);
|
||||
imageStore(endcrystal_img, ivec2(index, 5), ivec4(state + previousWeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
static final String PREPARE_FRAGMENT = """
|
||||
#version 130
|
||||
varying vec2 texCoord;
|
||||
void main() { gl_FragData[0] = vec4(texCoord, 0.0, 1.0); }
|
||||
""";
|
||||
|
||||
@BeforeAll
|
||||
static void initShaderTransformer() throws Exception {
|
||||
Reflect.setStatic(RenderSystem.class, "maxGlslVersion", 460);
|
||||
ShaderTransformer.init();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void clearCaches() {
|
||||
TransformPatcher.clearCache();
|
||||
ShaderTransformer.clearCache();
|
||||
}
|
||||
|
||||
@Test
|
||||
void compositeVertexWritesRunOverTheQuadNotTerrain() {
|
||||
final Map<PatchShaderType, String> transformed = TransformPatcher.patchComposite(PREPARE_VERTEX, null, PREPARE_FRAGMENT);
|
||||
final EnumMap<PatchShaderType, String> result = new EnumMap<>(transformed);
|
||||
result.remove(PatchShaderType.COMPUTE);
|
||||
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, null, true);
|
||||
|
||||
final String compute = result.get(PatchShaderType.COMPUTE);
|
||||
assertEquals(RwImageStoreExtractor.RwExtractMode.COMPOSITE_VSH, RwImageStoreExtractor.parseSentinel(compute));
|
||||
assertTrue(compute.contains("iris_Vertex = _vg_quad[vid];"), compute);
|
||||
assertTrue(compute.contains("iris_MultiTexCoord0 = _vg_quad[vid];"), compute);
|
||||
assertFalse(compute.contains("location"), "a location on a former interface variable does not compile in compute");
|
||||
assertEquals(1, compute.split("vec4 iris_ftransform", -1).length - 1, "the transform's own iris_ftransform is the only one");
|
||||
assertTrue(compute.contains("#define gl_Position _vg_sink_pos"), compute);
|
||||
}
|
||||
|
||||
@Test
|
||||
void keptAtomicDropsThePrePassWithoutBackendSupport() {
|
||||
final EnumMap<PatchShaderType, String> result = new EnumMap<>(TransformPatcher.patchComposite(PREPARE_VERTEX, null, PREPARE_FRAGMENT));
|
||||
result.remove(PatchShaderType.COMPUTE);
|
||||
|
||||
// The CompSwap's result is used, so it stays a real atomic, which native Metal cannot bind
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, null, false);
|
||||
|
||||
assertFalse(result.containsKey(PatchShaderType.COMPUTE));
|
||||
assertFalse(result.get(PatchShaderType.VERTEX).contains("imageAtomic"), result.get(PatchShaderType.VERTEX));
|
||||
}
|
||||
}
|
||||
@@ -246,7 +246,7 @@ class ShaderTransformerTest {
|
||||
|
||||
final EnumMap<PatchShaderType, String> result = new EnumMap<>(out);
|
||||
final String fragmentBefore = result.get(PatchShaderType.FRAGMENT);
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, artifacts);
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, artifacts, true);
|
||||
|
||||
assertNotNull(result.get(PatchShaderType.COMPUTE), "fixture must actually exercise the extraction");
|
||||
assertFalse(fragmentBefore.equals(result.get(PatchShaderType.FRAGMENT)), "fixture must actually rewrite the fragment stage");
|
||||
@@ -263,7 +263,7 @@ class ShaderTransformerTest {
|
||||
assertNotNull(out);
|
||||
|
||||
final EnumMap<PatchShaderType, String> result = new EnumMap<>(out);
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, artifacts);
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, artifacts, true);
|
||||
|
||||
assertFalse(artifacts.isEmpty(), "fixture must leave at least one artifact to check");
|
||||
for (Map.Entry<PatchShaderType, ShaderTransformer.StageArtifact> e : artifacts.entrySet()) {
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.InputAvailability;
|
||||
import net.coderbot.iris.pipeline.transform.RwImageStoreExtractor.VertexInput;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class VertexReplayExtractionTest {
|
||||
|
||||
// Shaped like Euphoria Patches' end crystal voxelization in its shadow program
|
||||
private static final String CRYSTAL_VERTEX = """
|
||||
#version 130
|
||||
#extension GL_ARB_shader_image_load_store : enable
|
||||
uniform int entityId;
|
||||
uniform mat4 shadowModelViewInverse;
|
||||
uniform mat4 shadowProjectionInverse;
|
||||
attribute vec4 mc_Entity;
|
||||
layout(r32i) uniform iimage2D endcrystal_img;
|
||||
varying vec2 texCoord;
|
||||
void UpdateEndCrystalMap(vec3 pos) {
|
||||
int packedPos = int(pos.x + 512.5);
|
||||
for (int k = 0; k < 20; k++) {
|
||||
int registeredPos = imageAtomicCompSwap(endcrystal_img, ivec2(k, 0), 0, packedPos);
|
||||
if (registeredPos == 0 || registeredPos == packedPos) {
|
||||
imageAtomicAdd(endcrystal_img, ivec2(k, 1), int(10.0 * pos.x));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void main() {
|
||||
texCoord = gl_MultiTexCoord0.xy;
|
||||
vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
|
||||
vec4 position = shadowModelViewInverse * shadowProjectionInverse * ftransform();
|
||||
if (entityId == 50000 && abs(normal.y) > 0.5 && mc_Entity.x < 0.0) UpdateEndCrystalMap(position.xyz);
|
||||
gl_Position = ftransform();
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String FRAGMENT = """
|
||||
#version 130
|
||||
uniform sampler2D tex;
|
||||
varying vec2 texCoord;
|
||||
void main() { gl_FragData[0] = texture2D(tex, texCoord); }
|
||||
""";
|
||||
|
||||
private static final InputAvailability TEX_LM = InputAvailability.of(true, true);
|
||||
|
||||
@BeforeAll
|
||||
static void initShaderTransformer() throws Exception {
|
||||
Reflect.setStatic(RenderSystem.class, "maxGlslVersion", 460);
|
||||
ShaderTransformer.init();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void clearCaches() {
|
||||
TransformPatcher.clearCache();
|
||||
ShaderTransformer.clearCache();
|
||||
}
|
||||
|
||||
private static RwImageStoreExtractor.Result replayOf(Map<PatchShaderType, String> transformed) {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay(transformed.get(PatchShaderType.VERTEX));
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.computeSource(), "every input of an Iris gbuffers vertex shader is replayable");
|
||||
return result;
|
||||
}
|
||||
|
||||
private static VertexInput input(List<VertexInput> inputs, String name) {
|
||||
return inputs.stream().filter(i -> i.name().equals(name)).findFirst()
|
||||
.orElseThrow(() -> new AssertionError(name + " missing from " + inputs));
|
||||
}
|
||||
|
||||
@Test
|
||||
void replayKeepsWritesAtomicAndRasterDropsThem() {
|
||||
final RwImageStoreExtractor.Result result = replayOf(TransformPatcher.patchAttributes(CRYSTAL_VERTEX, null, FRAGMENT, TEX_LM));
|
||||
|
||||
assertEquals(RwImageStoreExtractor.RwExtractMode.VERTEX_REPLAY, RwImageStoreExtractor.parseSentinel(result.computeSource()));
|
||||
assertFalse(result.strippedSource().contains("imageAtomic"), result.strippedSource());
|
||||
assertFalse(result.strippedSource().contains("endcrystal_img"), result.strippedSource());
|
||||
// Many vertices of one crystal add into the same texels at once
|
||||
assertTrue(result.computeSource().contains("imageAtomicAdd"), result.computeSource());
|
||||
assertFalse(result.computeSource().contains("_vg_prev"), result.computeSource());
|
||||
assertTrue(result.computeSource().contains("layout(binding = 0, r32i) uniform iimage2D endcrystal_img;"), result.computeSource());
|
||||
assertEquals(List.of("endcrystal_img"), RwImageStoreExtractor.parseVertexReplayImages(result.computeSource()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void replayFetchesEveryDeclaredInput() {
|
||||
final RwImageStoreExtractor.Result result = replayOf(TransformPatcher.patchAttributes(CRYSTAL_VERTEX, null, FRAGMENT, TEX_LM));
|
||||
final List<VertexInput> inputs = RwImageStoreExtractor.parseVertexReplayInputs(result.computeSource());
|
||||
|
||||
assertEquals("vec4", input(inputs, "mc_Entity").glslType());
|
||||
for (int k = 0; k < inputs.size(); k++) {
|
||||
final VertexInput in = inputs.get(k);
|
||||
assertTrue(result.computeSource().contains(in.name() + " = " + in.glslType() + "("), "no fetch for " + in.name());
|
||||
assertTrue(result.computeSource().contains("(" + k + "u, _vg_vertex, _vg_i)"), "input " + in.name() + " is not fetched from slot " + k);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void instancedVariantReadsEntityIdFromItsInstanceAttribute() {
|
||||
final RwImageStoreExtractor.Result result = replayOf(TransformPatcher.patchAttributesInstanced(CRYSTAL_VERTEX, null, null, null, FRAGMENT, TEX_LM, false, Instancing.TEMPLATE));
|
||||
final List<VertexInput> inputs = RwImageStoreExtractor.parseVertexReplayInputs(result.computeSource());
|
||||
|
||||
final VertexInput entity = input(inputs, "iris_Entity");
|
||||
assertEquals(InstancedAttribs.LOC_ENTITY, entity.location());
|
||||
assertEquals("uvec4", entity.glslType());
|
||||
assertTrue(result.computeSource().contains("iris_Entity = uvec4(_vg_uint("), result.computeSource());
|
||||
assertFalse(result.computeSource().contains("location"), "a location on a former interface variable does not compile in compute");
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyDeclaratorOfAnInputListIsFetched() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
in vec3 position, normal;
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(normal.xy), uvec4(1u));
|
||||
gl_Position = vec4(position, 1.0);
|
||||
}
|
||||
""");
|
||||
assertNotNull(result.computeSource());
|
||||
final List<VertexInput> inputs = RwImageStoreExtractor.parseVertexReplayInputs(result.computeSource());
|
||||
assertEquals(List.of("position", "normal"), inputs.stream().map(VertexInput::name).toList());
|
||||
assertFalse(result.computeSource().contains("in vec3"), "a stage input does not compile in compute\n" + result.computeSource());
|
||||
assertTrue(result.computeSource().contains("normal = vec3(_vg_float(1u, _vg_vertex, _vg_i));"), result.computeSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
void samplerOnlyTheWriteReadsIsListedForThePipeline() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
in vec4 iris_Vertex;
|
||||
uniform sampler2D noisetex;
|
||||
layout(rgba8) uniform image2D marks;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(0), textureLod(noisetex, iris_Vertex.xy, 0.0));
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
assertFalse(result.strippedSource().contains("textureLod"), result.strippedSource());
|
||||
assertEquals(List.of("noisetex"), RwImageStoreExtractor.parseVertexReplaySamplers(result.computeSource()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readOnlyImagesBindAfterTheWrittenOnes() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
in vec4 iris_Vertex;
|
||||
layout(rgba8) uniform image2D source;
|
||||
layout(rgba8) uniform image2D target;
|
||||
void main() {
|
||||
imageStore(target, ivec2(0), imageLoad(source, ivec2(iris_Vertex.xy)));
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
final String compute = result.computeSource();
|
||||
assertEquals(List.of("target"), RwImageStoreExtractor.parseVertexReplayImages(compute));
|
||||
assertEquals(List.of("source"), RwImageStoreExtractor.parseVertexReplayReadImages(compute));
|
||||
assertTrue(compute.contains("layout(binding = 0, rgba8) writeonly uniform image2D target;"), compute);
|
||||
assertTrue(compute.contains("layout(binding = 1, rgba8) readonly uniform image2D source;"), compute);
|
||||
assertEquals(1, compute.split("image2D source", -1).length - 1, compute);
|
||||
}
|
||||
|
||||
@Test
|
||||
void outParametersOfHelpersStayOutParameters() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
in vec4 iris_Vertex;
|
||||
out vec2 texCoord;
|
||||
layout(r32f) uniform image2D marks;
|
||||
void fill(out float value) { value = iris_Vertex.x; }
|
||||
void main() {
|
||||
float v = 0.0;
|
||||
fill(v);
|
||||
imageStore(marks, ivec2(0), vec4(v));
|
||||
texCoord = iris_Vertex.xy;
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
final String compute = result.computeSource().replaceAll("\\s+", " ");
|
||||
assertTrue(compute.contains("void fill ( out float value )"), compute);
|
||||
assertFalse(compute.contains("out vec2 texCoord"), compute);
|
||||
}
|
||||
|
||||
@Test
|
||||
void outputBlocksBecomeGlobalsAndLayoutDefaultsStay() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
layout(std430) buffer;
|
||||
in vec4 iris_Vertex;
|
||||
out gl_PerVertex { vec4 gl_Position; };
|
||||
out Outputs { flat int id; vec2 uv; } v;
|
||||
out Extra { float fade; };
|
||||
buffer Marks { uint counts[]; };
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
void main() {
|
||||
v.uv = iris_Vertex.xy;
|
||||
fade = iris_Vertex.z;
|
||||
imageStore(marks, ivec2(v.uv), uvec4(counts[0] + uint(fade)));
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
assertNotNull(result);
|
||||
final String compute = result.computeSource().replaceAll("\\s+", " ");
|
||||
assertTrue(compute.contains("layout ( std430 ) buffer ;"), compute);
|
||||
assertFalse(compute.contains("gl_PerVertex"), compute);
|
||||
assertTrue(compute.contains("struct Outputs { int id ; vec2 uv ; }; Outputs v ;"), compute);
|
||||
assertTrue(compute.contains("float fade ;"), compute);
|
||||
assertFalse(compute.contains("Extra"), compute);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveRestartMarkersAreNotReplayedAsVertices() {
|
||||
final String compute = replayOf(TransformPatcher.patchAttributes(CRYSTAL_VERTEX, null, FRAGMENT, TEX_LM)).computeSource();
|
||||
final int skip = compute.indexOf("if (_vg_restart.x != 0u && _vg_index_value == _vg_restart.y) return;");
|
||||
assertTrue(skip >= 0, compute);
|
||||
assertTrue(skip < compute.indexOf("_vg_vertex = _vg_index_value + uint(_vg_draw.w);"), compute);
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomicReplayIsDroppedWithoutBackendSupport() {
|
||||
final EnumMap<PatchShaderType, String> atomic = new EnumMap<>(PatchShaderType.class);
|
||||
atomic.put(PatchShaderType.VERTEX, TransformPatcher.patchAttributes(CRYSTAL_VERTEX, null, FRAGMENT, TEX_LM).get(PatchShaderType.VERTEX));
|
||||
ShaderTransformer.extractRwImageStores(atomic, Patch.ATTRIBUTES, null, false);
|
||||
assertFalse(atomic.containsKey(PatchShaderType.COMPUTE));
|
||||
assertFalse(atomic.get(PatchShaderType.VERTEX).contains("imageAtomic"), atomic.get(PatchShaderType.VERTEX));
|
||||
|
||||
final EnumMap<PatchShaderType, String> plain = new EnumMap<>(PatchShaderType.class);
|
||||
plain.put(PatchShaderType.VERTEX, """
|
||||
#version 460 core
|
||||
in vec4 iris_Vertex;
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(0), uvec4(1u));
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
ShaderTransformer.extractRwImageStores(plain, Patch.ATTRIBUTES, null, false);
|
||||
assertTrue(plain.containsKey(PatchShaderType.COMPUTE), "plain stores need no atomics, so the replay stays");
|
||||
}
|
||||
|
||||
@Test
|
||||
void matrixInputIsNotReplayed() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
in mat4 instanceMatrix;
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(0), uvec4(1u));
|
||||
gl_Position = instanceMatrix[0];
|
||||
}
|
||||
""");
|
||||
assertNotNull(result);
|
||||
assertNull(result.computeSource());
|
||||
assertFalse(result.strippedSource().contains("imageStore"), result.strippedSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
void attributesProgramKeepsVertexReplayAndDropsFragmentWrites() {
|
||||
final EnumMap<PatchShaderType, String> result = new EnumMap<>(PatchShaderType.class);
|
||||
result.put(PatchShaderType.VERTEX, """
|
||||
#version 460 core
|
||||
in vec4 iris_Vertex;
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(0), uvec4(1u));
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
result.put(PatchShaderType.FRAGMENT, """
|
||||
#version 460 core
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
out vec4 color;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(1), uvec4(2u));
|
||||
color = vec4(1.0);
|
||||
}
|
||||
""");
|
||||
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.ATTRIBUTES, null, true);
|
||||
|
||||
assertNotNull(result.get(PatchShaderType.COMPUTE));
|
||||
assertFalse(result.get(PatchShaderType.VERTEX).contains("imageStore"));
|
||||
assertFalse(result.get(PatchShaderType.FRAGMENT).contains("imageStore"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user