Fix some GLSM bypasses (#2099)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-09-14 14:22:19 -07:00
1 parent 81b82748ce
commit a07cecb879
39 files changed
+2176 -247

No files matched your search

@@ -27,6 +27,7 @@ import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntStack;
@@ -101,6 +102,7 @@ class GLContextState {
public final IntegerStateStack shadeModelState = new IntegerStateStack(GL11.GL_SMOOTH);
public final TextureUnitArray textures = new TextureUnitArray();
public final ImageUnitArray imageUnits = new ImageUnitArray();
public final SamplerUnitArray samplerUnits = new SamplerUnitArray();
public final BlendStateStack blendState = new BlendStateStack();
public final BooleanStateStack blendMode = track(new BooleanStateStack(GL11.GL_BLEND));
public final BooleanStateStack scissorTest = track(new BooleanStateStack(GL11.GL_SCISSOR_TEST));
@@ -52,6 +52,7 @@ import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
@@ -91,6 +92,7 @@ import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.KHRDebug;
import org.lwjgl.opengl.KHRDebugCallback;
@@ -1054,6 +1056,8 @@ public class GLStateManager {
case GL30.GL_DRAW_FRAMEBUFFER_BINDING -> glCtx.drawFramebuffer;
case GL30.GL_READ_FRAMEBUFFER_BINDING -> glCtx.readFramebuffer;
case GL33.GL_SAMPLER_BINDING -> glCtx.samplerUnits.get(glCtx.activeTextureUnit.getValue());
default -> switch (pname) {
case GL11.GL_FOG_MODE -> glCtx.fogState.getFogMode();
case GL11.GL_LINE_STIPPLE_PATTERN -> glCtx.lineState.getStipplePattern() & 0xFFFF;
@@ -1306,6 +1310,29 @@ public class GLStateManager {
}
}
public static void glEnablei(int cap, int index) {
RENDER_BACKEND.enablei(cap, index);
markIndexedCapUnknown(cap);
}
public static void glDisablei(int cap, int index) {
RENDER_BACKEND.disablei(cap, index);
markIndexedCapUnknown(cap);
}
private static void markIndexedCapUnknown(int cap) {
if (cap == GL11.GL_BLEND) {
ctx().blendMode.setUnknownState();
} else if (cap == GL11.GL_SCISSOR_TEST) {
ctx().scissorTest.setUnknownState();
}
}
public static void glBlendFuncSeparatei(int buf, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
RENDER_BACKEND.blendFuncSeparatei(buf, srcRGB, dstRGB, srcAlpha, dstAlpha);
ctx().blendState.setFuncUnknownState();
}
public static void enableBlend() {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -1451,6 +1478,7 @@ public class GLStateManager {
final boolean caching = isCachingEnabled();
if (GLSMConfig.hudCacheOverride) {
glCtx.blendState.setAll(srcFactor, dstFactor, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendFuncSeparate(srcFactor, dstFactor, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
GLSMHooks.blendFuncChangeEvent.srcRgb = srcFactor;
@@ -1467,8 +1495,10 @@ public class GLStateManager {
|| glCtx.blendState.getSrcRgb() != srcFactor
|| glCtx.blendState.getDstRgb() != dstFactor
|| glCtx.blendState.getSrcAlpha() != srcFactor
|| glCtx.blendState.getDstAlpha() != dstFactor) {
|| glCtx.blendState.getDstAlpha() != dstFactor
|| glCtx.blendState.isFuncUnknown()) {
glCtx.blendState.setAll(srcFactor, dstFactor, srcFactor, dstFactor);
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendFunc(srcFactor, dstFactor);
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
GLSMHooks.blendFuncChangeEvent.srcRgb = srcFactor;
@@ -1531,8 +1561,9 @@ public class GLStateManager {
}
final boolean caching = isCachingEnabled();
final boolean bypass = !caching;
if (bypass || glCtx.blendState.getSrcRgb() != srcRgb || glCtx.blendState.getDstRgb() != dstRgb || glCtx.blendState.getSrcAlpha() != srcAlpha || glCtx.blendState.getDstAlpha() != dstAlpha) {
if (bypass || glCtx.blendState.getSrcRgb() != srcRgb || glCtx.blendState.getDstRgb() != dstRgb || glCtx.blendState.getSrcAlpha() != srcAlpha || glCtx.blendState.getDstAlpha() != dstAlpha || glCtx.blendState.isFuncUnknown()) {
glCtx.blendState.setAll(srcRgb, dstRgb, srcAlpha, dstAlpha);
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha);
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
GLSMHooks.blendFuncChangeEvent.srcRgb = srcRgb;
@@ -3756,6 +3787,7 @@ public class GLStateManager {
RENDER_BACKEND.clearDepth(glCtx.depthState.getClearValue());
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendEquationSeparate(glCtx.blendState.getEquationRgb(), glCtx.blendState.getEquationAlpha());
RENDER_BACKEND.blendColor(glCtx.blendState.getBlendColorR(), glCtx.blendState.getBlendColorG(), glCtx.blendState.getBlendColorB(), glCtx.blendState.getBlendColorA());
RENDER_BACKEND.colorMask(glCtx.colorMask.red, glCtx.colorMask.green, glCtx.colorMask.blue, glCtx.colorMask.alpha);
@@ -3779,6 +3811,12 @@ public class GLStateManager {
applyPolygonOffset(glCtx);
RENDER_BACKEND.cullFace(glCtx.polygonState.getCullFaceMode());
RENDER_BACKEND.frontFace(glCtx.polygonState.getFrontFace());
final SamplerUnitArray samplerUnits = glCtx.samplerUnits;
for (int i = 0; i < samplerUnits.size(); i++) {
final int sampler = samplerUnits.get(i);
if (sampler != 0) RENDER_BACKEND.bindSampler(i, sampler);
}
}
private static void applyPolygonOffset(GLContextState glCtx) {
@@ -3803,13 +3841,18 @@ public class GLStateManager {
RENDER_BACKEND.clearDepth(glCtx.depthState.getClearValue());
}
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
if (glCtx.restoreBlendChanged) {
final BlendStateStack blend = glCtx.blendState;
if (glCtx.restoreBlendChanged || blend.isFuncUnknown()) {
final DeferredBlendHandler restoreBlendHandler = GLSMHooks.blendHandler;
if (restoreBlendHandler == null || !restoreBlendHandler.isOverrideHeld()) {
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
RENDER_BACKEND.blendFuncSeparate(blend.getSrcRgb(), blend.getDstRgb(), blend.getSrcAlpha(), blend.getDstAlpha());
blend.clearFuncUnknownState();
}
if (glCtx.restoreBlendChanged) {
RENDER_BACKEND.blendEquationSeparate(blend.getEquationRgb(), blend.getEquationAlpha());
RENDER_BACKEND.blendColor(
blend.getBlendColorR(), blend.getBlendColorG(), blend.getBlendColorB(), blend.getBlendColorA());
}
RENDER_BACKEND.blendEquationSeparate(glCtx.blendState.getEquationRgb(), glCtx.blendState.getEquationAlpha());
RENDER_BACKEND.blendColor(glCtx.blendState.getBlendColorR(), glCtx.blendState.getBlendColorG(), glCtx.blendState.getBlendColorB(), glCtx.blendState.getBlendColorA());
}
if (glCtx.restoreColorMaskChanged && !isDepthColorOverridden()) {
RENDER_BACKEND.colorMask(glCtx.colorMask.red, glCtx.colorMask.green, glCtx.colorMask.blue, glCtx.colorMask.alpha);
@@ -4721,8 +4764,61 @@ public class GLStateManager {
public static void glClearTexImage(int texture, int level, int format, int type, ByteBuffer data) { RENDER_BACKEND.clearTexImage(texture, level, format, type); }
public static int glGenSamplers() { return RENDER_BACKEND.genSamplers(); }
public static void glDeleteSamplers(int sampler) { RENDER_BACKEND.deleteSamplers(sampler); }
public static void glBindSampler(int unit, int sampler) { RENDER_BACKEND.bindSampler(unit, sampler); }
public static void glGenSamplers(IntBuffer samplers) {
for (int i = samplers.position(); i < samplers.limit(); i++) samplers.put(i, RENDER_BACKEND.genSamplers());
}
public static void glGenSamplers(int[] samplers) {
for (int i = 0; i < samplers.length; i++) samplers[i] = RENDER_BACKEND.genSamplers();
}
public static void glDeleteSamplers(int sampler) {
if (sampler == 0) return;
ctx().samplerUnits.unbindEverywhere(sampler);
RENDER_BACKEND.deleteSamplers(sampler);
}
public static void glDeleteSamplers(IntBuffer samplers) {
for (int i = samplers.position(); i < samplers.limit(); i++) glDeleteSamplers(samplers.get(i));
}
public static void glDeleteSamplers(int[] samplers) {
for (int sampler : samplers) glDeleteSamplers(sampler);
}
public static void glBindSampler(int unit, int sampler) {
final SamplerUnitArray units = ctx().samplerUnits;
if (isCachingEnabled() && unit >= 0 && unit < units.size() && units.get(unit) == sampler) return;
if (!units.set(unit, sampler)) {
warnOnce("samplerUnitRange", "glBindSampler unit {} exceeds GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS {}", unit, units.size());
}
RENDER_BACKEND.bindSampler(unit, sampler);
}
public static void glBindSamplers(int first, int count, IntBuffer samplers) {
if (samplers == null) {
for (int i = 0; i < count; i++) glBindSampler(first + i, 0);
return;
}
if (samplers.remaining() < count) {
throw new IllegalArgumentException("Number of remaining buffer elements is " + samplers.remaining() + ", must be at least " + count);
}
final int base = samplers.position();
for (int i = 0; i < count; i++) glBindSampler(first + i, samplers.get(base + i));
}
public static void glBindSamplers(int first, IntBuffer samplers) {
if (samplers != null) glBindSamplers(first, samplers.remaining(), samplers);
}
public static void glBindSamplers(int first, int[] samplers) {
if (samplers == null) return;
for (int i = 0; i < samplers.length; i++) glBindSampler(first + i, samplers[i]);
}
public static int getSamplerBinding(int unit) { return ctx().samplerUnits.get(unit); }
public static int getSamplerUnitCount() { return ctx().samplerUnits.size(); }
public static void glSamplerParameteri(int sampler, int pname, int param) { RENDER_BACKEND.samplerParameteri(sampler, pname, param); }
public static void glSamplerParameterf(int sampler, int pname, float param) { RENDER_BACKEND.samplerParameterf(sampler, pname, param); }
@@ -6173,6 +6269,10 @@ public class GLStateManager {
public static ByteBuffer glMapBuffer(int target, int access, long length, ByteBuffer old_buffer) { onBufferMap(target, access); return RENDER_BACKEND.mapBuffer(target, access, length, old_buffer); }
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access) { onBufferMapRange(target, access); return RENDER_BACKEND.mapBufferRange(target, offset, length, access); }
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access, ByteBuffer old_buffer) { return glMapBufferRange(target, offset, length, access); }
public static long glMapBufferRangeAddress(int target, long offset, long length, int access) {
onBufferMapRange(target, access);
return RENDER_BACKEND.mapBufferRangeAddress(target, offset, length, access);
}
public static void glFlushMappedBufferRange(int target, long offset, long length) { RENDER_BACKEND.flushMappedBufferRange(target, offset, length); }
public static boolean glUnmapBuffer(int target) { onBufferUnmap(target); return RENDER_BACKEND.unmapBuffer(target); }
@@ -49,6 +49,7 @@ public class RenderSystem {
private static boolean supportsTesselation;
private static boolean supportsSamplerObjects;
private static int maxImageUnits;
@Getter private static int maxCombinedTextureImageUnits;
private static int maxSSBOBindings;
private static int maxGlslVersion;
private static boolean supportsGpuShader4;
@@ -64,9 +65,6 @@ public class RenderSystem {
private static volatile boolean isLTW;
private static volatile boolean ltwDetected;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
private static final Pattern SEMVER_PATTERN = Pattern.compile("(?<major>\\d+)\\.(?<minor>\\d+)\\.*(?<bugfix>\\d*)(.*)");
private RenderSystem() {}
@@ -158,7 +156,7 @@ public class RenderSystem {
// Check for sampler objects support (GL 3.3+ or ARB extension)
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
if (supportsSamplerObjects) {
samplers = new int[RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)];
maxCombinedTextureImageUnits = RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
}
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
@@ -486,17 +484,15 @@ public class RenderSystem {
}
public static void disableBufferBlend(int buffer) {
RENDER_BACKEND.disablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
GLStateManager.glDisablei(GL11.GL_BLEND, buffer);
}
public static void enableBufferBlend(int buffer) {
RENDER_BACKEND.enablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
GLStateManager.glEnablei(GL11.GL_BLEND, buffer);
}
public static void blendFuncSeparatei(int buffer, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
RENDER_BACKEND.blendFuncSeparatei(buffer, srcRGB, dstRGB, srcAlpha, dstAlpha);
GLStateManager.glBlendFuncSeparatei(buffer, srcRGB, dstRGB, srcAlpha, dstAlpha);
}
public static void bindTextureToUnit(int unit, int texture) {
@@ -628,7 +624,7 @@ public class RenderSystem {
public static int genSampler() {
if (!supportsSamplerObjects) return 0;
return RENDER_BACKEND.genSamplers();
return GLStateManager.glGenSamplers();
}
public static void destroySampler(int sampler) {
@@ -638,22 +634,20 @@ public class RenderSystem {
public static void samplerParameteri(int sampler, int pname, int param) {
if (!supportsSamplerObjects || sampler == 0) return;
RENDER_BACKEND.samplerParameteri(sampler, pname, param);
GLStateManager.glSamplerParameteri(sampler, pname, param);
}
public static void bindSamplerToUnit(int unit, int sampler) {
if (!supportsSamplerObjects) return;
if (samplers[unit] == sampler) return;
RENDER_BACKEND.bindSampler(unit, sampler);
samplers[unit] = sampler;
GLStateManager.glBindSampler(unit, sampler);
}
public static void unbindAllSamplers() {
if (!supportsSamplerObjects) return;
for (int i = 0; i < samplers.length; i++) {
if (samplers[i] != 0) {
RENDER_BACKEND.bindSampler(i, 0);
samplers[i] = 0;
final int count = GLStateManager.getSamplerUnitCount();
for (int i = 0; i < count; i++) {
if (GLStateManager.getSamplerBinding(i) != 0) {
GLStateManager.glBindSampler(i, 0);
}
}
}
@@ -253,7 +253,7 @@ public abstract class RenderBackend {
public String getTransferDebugInfo() { return null; }
public boolean bindVoxelizationRegion(int ssboBinding, int vertexBufferGlId, long openPass, float x, float y, float z) { return false; }
public boolean bindVoxelizationRegion(int ssboBinding, long openPass, float x, float y, float z) { return false; }
public long beginVoxelizationBatch(int ssboBinding) { return 0L; }
public void voxelizeRange(long pass, int vertexOffset, int vertexCount) {}
public void endVoxelizationBatch(long pass) {}
@@ -164,6 +164,8 @@ public class GLSMRedirector {
private static final String EXTGPUShader4 = "org/lwjgl/opengl/EXTGPUShader4";
private static final String ARBTimerQuery = "org/lwjgl/opengl/ARBTimerQuery";
private static final String EXTTimerQuery = "org/lwjgl/opengl/EXTTimerQuery";
private static final String ARBSamplerObjects = "org/lwjgl/opengl/ARBSamplerObjects";
private static final String ARBMultiBind = "org/lwjgl/opengl/ARBMultiBind";
// Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
@@ -555,6 +557,7 @@ public class GLSMRedirector {
.add("glMultiDrawElementsIndirect");
final var gl44 = RedirectMap.newMap()
.add("glBufferStorage")
.add("glBindSamplers")
.add("glClearTexImage");
final var gl45 = RedirectMap.newMap()
.add("glCreateBuffers")
@@ -767,6 +770,17 @@ public class GLSMRedirector {
.add("glMapBufferRange")
.add("glFlushMappedBufferRange")
);
methodRedirects.put(ARBSamplerObjects, RedirectMap.newMap()
.add("glGenSamplers")
.add("glDeleteSamplers")
.add("glIsSampler")
.add("glBindSampler")
.add("glSamplerParameteri")
.add("glSamplerParameterf")
);
methodRedirects.put(ARBMultiBind, RedirectMap.newMap()
.add("glBindSamplers")
);
// APPLE
methodRedirects.put(APPLEVertexArrayObject, RedirectMap.newMap()
@@ -12,6 +12,8 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
protected int pointer;
private boolean funcUnknown;
@Setter private VanillaStateLayer<BlendState> vanillaLayer;
public BlendStateStack() {
@@ -44,6 +46,18 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
return this;
}
public void setFuncUnknownState() {
funcUnknown = true;
}
public boolean isFuncUnknown() {
return funcUnknown;
}
public void clearFuncUnknownState() {
funcUnknown = false;
}
public BlendState readEffective(BlendState out) {
out.set(this);
VanillaStateLayer.capture(vanillaLayer, out);
@@ -0,0 +1,40 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
public class SamplerUnitArray {
private static final int MIN_UNITS = 16;
private int[] samplers;
public int get(int unit) {
final int[] local = samplers;
return local != null && unit >= 0 && unit < local.length ? local[unit] : 0;
}
public boolean set(int unit, int sampler) {
if (unit < 0) return false;
int[] local = samplers;
if (local == null) {
local = new int[Math.max(MIN_UNITS, RenderSystem.getMaxCombinedTextureImageUnits())];
samplers = local;
}
if (unit >= local.length) return false;
local[unit] = sampler;
return true;
}
public void unbindEverywhere(int sampler) {
final int[] local = samplers;
if (local == null) return;
for (int i = 0; i < local.length; i++) {
if (local[i] == sampler) local[i] = 0;
}
}
public int size() {
final int[] local = samplers;
return local == null ? 0 : local.length;
}
}
@@ -3,7 +3,8 @@ package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL44;
@@ -11,7 +12,6 @@ import org.lwjgl.opengl.GL44;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Persistent-mapped fence-reclaimed ring over any buffer target. Callers reserve a range and
@@ -28,7 +28,8 @@ final class GlStreamingRing {
private static final long SYNC_WAIT_SLICE_NANOS = 10_000_000L;
private static final long SYNC_WAIT_TOTAL_NANOS = 5_000_000_000L;
private final ObjectArrayFIFOQueue<FencedRegion> fenceQueue = new ObjectArrayFIFOQueue<>();
private final LongArrayFIFOQueue fenceIds = new LongArrayFIFOQueue();
private final IntArrayFIFOQueue fenceBytes = new IntArrayFIFOQueue();
private int writePos;
private int remaining;
private int pendingBytes;
@@ -51,13 +52,13 @@ final class GlStreamingRing {
static GlStreamingRing create(int target, int capacity, boolean forceOrphan) {
if (forceOrphan || !RenderSystem.supportsBufferStorage()) return null;
final int id = RENDER_BACKEND.genBuffers();
RENDER_BACKEND.bindBuffer(target, id);
final int id = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(target, id);
final int storageFlags = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT | GL44.GL_CLIENT_STORAGE_BIT;
RenderSystem.bufferStorage(target, capacity, storageFlags);
final int mapFlags = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT;
final ByteBuffer mapped = RENDER_BACKEND.mapBufferRange(target, 0, capacity, mapFlags);
RENDER_BACKEND.bindBuffer(target, 0);
final ByteBuffer mapped = GLStateManager.glMapBufferRange(target, 0, capacity, mapFlags);
GLStateManager.glBindBuffer(target, 0);
if (mapped == null) {
GLStateManager.glDeleteBuffers(id);
GLStateManager.LOGGER.warn("Persistent map failed for streaming ring (target=0x{}, {} bytes)", Integer.toHexString(target), capacity);
@@ -92,7 +93,7 @@ final class GlStreamingRing {
private boolean ensureRemaining(int needed) {
while (remaining < needed) {
if (fenceQueue.isEmpty()) {
if (fenceIds.isEmpty()) {
if (pendingBytes == 0) return false;
fence();
}
@@ -103,7 +104,8 @@ final class GlStreamingRing {
private void fence() {
if (pendingBytes > 0) {
fenceQueue.enqueue(new FencedRegion(RENDER_BACKEND.fenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0), pendingBytes));
fenceIds.enqueue(GLStateManager.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0));
fenceBytes.enqueue(pendingBytes);
pendingBytes = 0;
fencesIssued++;
}
@@ -114,30 +116,32 @@ final class GlStreamingRing {
}
private void reclaim() {
while (!fenceQueue.isEmpty()) {
final FencedRegion region = fenceQueue.first();
if (!signaled(RENDER_BACKEND.clientWaitSync(region.fenceId, 0, 0L))) break;
GLStateManager.glDeleteSync(region.fenceId);
fenceQueue.dequeue();
remaining += region.bytes;
while (!fenceIds.isEmpty()) {
final long fenceId = fenceIds.firstLong();
if (!signaled(GLStateManager.glClientWaitSync(fenceId, 0, 0L))) break;
GLStateManager.glDeleteSync(fenceId);
fenceIds.dequeueLong();
remaining += fenceBytes.dequeueInt();
}
}
private void syncOldest() {
final FencedRegion region = fenceQueue.dequeue();
final long fenceId = fenceIds.dequeueLong();
final int bytes = fenceBytes.dequeueInt();
forcedReclaims++;
final long deadline = System.nanoTime() + SYNC_WAIT_TOTAL_NANOS;
long remainingNanos = SYNC_WAIT_TOTAL_NANOS;
int status;
do {
status = RENDER_BACKEND.clientWaitSync(region.fenceId, GL32.GL_SYNC_FLUSH_COMMANDS_BIT, Math.min(SYNC_WAIT_SLICE_NANOS, remainingNanos));
status = GLStateManager.glClientWaitSync(fenceId, GL32.GL_SYNC_FLUSH_COMMANDS_BIT,
Math.min(SYNC_WAIT_SLICE_NANOS, remainingNanos));
remainingNanos = deadline - System.nanoTime();
} while (status == GL32.GL_TIMEOUT_EXPIRED && remainingNanos > 0);
if (!signaled(status)) {
GLStateManager.LOGGER.warn("Streaming ring fence wait did not signal (status=0x{}); reclaiming anyway", Integer.toHexString(status));
}
GLStateManager.glDeleteSync(region.fenceId);
remaining += region.bytes;
GLStateManager.glDeleteSync(fenceId);
remaining += bytes;
reclaim();
}
@@ -156,17 +160,16 @@ final class GlStreamingRing {
int forcedReclaims() { return forcedReclaims; }
void destroy() {
while (!fenceQueue.isEmpty()) {
GLStateManager.glDeleteSync(fenceQueue.dequeue().fenceId);
while (!fenceIds.isEmpty()) {
GLStateManager.glDeleteSync(fenceIds.dequeueLong());
}
fenceBytes.clear();
if (bufferId != 0) {
RENDER_BACKEND.bindBuffer(target, bufferId);
RENDER_BACKEND.unmapBuffer(target);
RENDER_BACKEND.bindBuffer(target, 0);
GLStateManager.glBindBuffer(target, bufferId);
GLStateManager.glUnmapBuffer(target);
GLStateManager.glBindBuffer(target, 0);
GLStateManager.glDeleteBuffers(bufferId);
bufferId = 0;
}
}
private record FencedRegion(long fenceId, int bytes) {}
}
@@ -5,8 +5,6 @@ import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Streaming buffer using the classic orphan pattern.
*/
@@ -16,19 +14,19 @@ public final class OrphanStreamingBuffer {
private int capacity;
public OrphanStreamingBuffer() {
this.bufferId = RENDER_BACKEND.genBuffers();
this.bufferId = GLStateManager.glGenBuffers();
}
public void upload(ByteBuffer data) {
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
capacity = StreamingUploader.upload(data, capacity);
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
void upload(StreamingUploader.UploadStrategy strategy, ByteBuffer data) {
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
capacity = StreamingUploader.upload(strategy, data, capacity);
RENDER_BACKEND.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
public int getCapacity() {
@@ -7,7 +7,6 @@ import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
@@ -28,30 +27,30 @@ public final class StreamingUploader {
public static int upload(UploadStrategy strategy, ByteBuffer data, int capacity) {
switch (strategy) {
case BUFFER_DATA -> {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
case BUFFER_SUB_DATA -> {
if (data.remaining() > capacity) {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
return capacity;
}
case MAP_BUFFER_RANGE -> {
if (data.remaining() > capacity) {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
final int dataSize = data.remaining();
final long dst = RENDER_BACKEND.mapBufferRangeAddress(GL15.GL_ARRAY_BUFFER, 0, dataSize, MAP_WRITE_INVALIDATE_BUFFER);
final long dst = GLStateManager.glMapBufferRangeAddress(GL15.GL_ARRAY_BUFFER, 0, dataSize, MAP_WRITE_INVALIDATE_BUFFER);
if (dst == 0L) {
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
return data.remaining();
}
memCopy(memAddress0(data), dst, dataSize);
RENDER_BACKEND.unmapBuffer(GL15.GL_ARRAY_BUFFER);
GLStateManager.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
return capacity;
}
default -> throw new UnsupportedOperationException();
@@ -10,7 +10,6 @@ import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class UniformRingBuffer {
@@ -35,10 +34,10 @@ public final class UniformRingBuffer {
this.stride = (blockSize + OFFSET_ALIGNMENT - 1) & -OFFSET_ALIGNMENT;
this.ring = GlStreamingRing.create(GL31.GL_UNIFORM_BUFFER, capacity, forceOrphan);
if (ring == null) {
orphanBufferId = RENDER_BACKEND.genBuffers();
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, orphanBufferId);
RENDER_BACKEND.bufferData(GL31.GL_UNIFORM_BUFFER, capacity, GL15.GL_STREAM_DRAW);
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
orphanBufferId = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, orphanBufferId);
GLStateManager.glBufferData(GL31.GL_UNIFORM_BUFFER, capacity, GL15.GL_STREAM_DRAW);
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
}
}
@@ -56,20 +55,20 @@ public final class UniformRingBuffer {
}
private int writeOrphan(long srcAddress) {
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, orphanBufferId);
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, orphanBufferId);
int offset = writePos;
if (offset + stride > capacity) {
RENDER_BACKEND.bufferData(GL31.GL_UNIFORM_BUFFER, capacity, GL15.GL_STREAM_DRAW);
GLStateManager.glBufferData(GL31.GL_UNIFORM_BUFFER, capacity, GL15.GL_STREAM_DRAW);
offset = 0;
orphanWraps++;
}
final long dst = RENDER_BACKEND.mapBufferRangeAddress(GL31.GL_UNIFORM_BUFFER, offset, blockSize, GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_RANGE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT);
final long dst = GLStateManager.glMapBufferRangeAddress(GL31.GL_UNIFORM_BUFFER, offset, blockSize, GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_RANGE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT);
if (dst == 0L) {
throw new IllegalStateException("mapBufferRange failed on uniform ring (offset=" + offset + ")");
}
memCopy(srcAddress, dst, blockSize);
RENDER_BACKEND.unmapBuffer(GL31.GL_UNIFORM_BUFFER);
RENDER_BACKEND.bindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
GLStateManager.glUnmapBuffer(GL31.GL_UNIFORM_BUFFER);
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
writePos = offset + stride;
return offset;
}
@@ -0,0 +1,121 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
class GLSM_BlendFuncUnknownState_GLTest {
private static final int VANILLA_SRC_RGB = GL11.GL_SRC_ALPHA;
private static final int VANILLA_DST_RGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
private static final int VANILLA_SRC_ALPHA = GL11.GL_ONE;
private static final int VANILLA_DST_ALPHA = GL11.GL_ZERO;
@BeforeEach
void requireBufferBlending() {
assumeTrue(RenderSystem.supportsBufferBlending());
drainGlErrors();
}
@AfterEach
void restoreDefaultBlendFunc() {
if (!RenderSystem.supportsBufferBlending()) return;
GLStateManager.tryBlendFuncSeparate(GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ZERO);
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
drainGlErrors();
}
private static void drainGlErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private static void seedVanillaSeparate() {
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
GLStateManager.tryBlendFuncSeparate(VANILLA_SRC_RGB, VANILLA_DST_RGB, VANILLA_SRC_ALPHA, VANILLA_DST_ALPHA);
}
private static void overrideBufferZero() {
RenderSystem.blendFuncSeparatei(0, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
assertBufferBlend(0, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
}
private static void assertBufferBlend(int buffer, int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
assertEquals(srcRgb, GL30.glGetInteger(GL14.GL_BLEND_SRC_RGB, buffer), "buffer " + buffer + " src rgb");
assertEquals(dstRgb, GL30.glGetInteger(GL14.GL_BLEND_DST_RGB, buffer), "buffer " + buffer + " dst rgb");
assertEquals(srcAlpha, GL30.glGetInteger(GL14.GL_BLEND_SRC_ALPHA, buffer), "buffer " + buffer + " src alpha");
assertEquals(dstAlpha, GL30.glGetInteger(GL14.GL_BLEND_DST_ALPHA, buffer), "buffer " + buffer + " dst alpha");
}
private static void assertVanillaOnBothBuffers() {
for (int buffer = 0; buffer < 2; buffer++) {
assertBufferBlend(buffer, VANILLA_SRC_RGB, VANILLA_DST_RGB, VANILLA_SRC_ALPHA, VANILLA_DST_ALPHA);
}
assertFalse(GLStateManager.getBlendState().isFuncUnknown(), "forward must clear the unknown flag");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void tryBlendFuncSeparateForwardsAfterIndexedOverride() {
seedVanillaSeparate();
overrideBufferZero();
GLStateManager.tryBlendFuncSeparate(VANILLA_SRC_RGB, VANILLA_DST_RGB, VANILLA_SRC_ALPHA, VANILLA_DST_ALPHA);
assertVanillaOnBothBuffers();
}
@Test
void glBlendFuncForwardsAfterIndexedOverride() {
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
overrideBufferZero();
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
for (int buffer = 0; buffer < 2; buffer++) {
assertBufferBlend(buffer, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
}
assertFalse(GLStateManager.getBlendState().isFuncUnknown(), "forward must clear the unknown flag");
}
@Test
void popAttribForwardsAfterIndexedOverride() {
seedVanillaSeparate();
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
try {
overrideBufferZero();
} finally {
GLStateManager.glPopAttrib();
}
assertVanillaOnBothBuffers();
}
@Test
void displayListReplayForwardsAfterIndexedOverride() {
final int list = GLStateManager.glGenLists(1);
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.tryBlendFuncSeparate(VANILLA_SRC_RGB, VANILLA_DST_RGB, VANILLA_SRC_ALPHA, VANILLA_DST_ALPHA);
GLStateManager.glEndList();
seedVanillaSeparate();
overrideBufferZero();
GLStateManager.glCallList(list);
assertVanillaOnBothBuffers();
} finally {
GLStateManager.glDeleteLists(list, 1);
}
}
}
@@ -0,0 +1,235 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL33;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
class GLSM_SamplerBinding_GLTest {
private static final int UNITS = 4;
@BeforeEach
void requireSamplerObjects() {
assumeTrue(RenderSystem.supportsSamplerObjects());
drainGlErrors();
}
@AfterEach
void unbindTestUnits() {
if (!RenderSystem.supportsSamplerObjects()) return;
for (int unit = 0; unit < UNITS; unit++) {
GLStateManager.glBindSampler(unit, 0);
GL33.glBindSampler(unit, 0);
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
drainGlErrors();
}
private static void drainGlErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private static int driverBinding(int unit) {
final int saved = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
final int binding = GL11.glGetInteger(GL33.GL_SAMPLER_BINDING);
GL13.glActiveTexture(saved);
return binding;
}
private static void assertUnits(String context, int... expected) {
for (int unit = 0; unit < expected.length; unit++) {
assertEquals(expected[unit], GLStateManager.getSamplerBinding(unit), context + ": cache unit " + unit);
assertEquals(expected[unit], driverBinding(unit), context + ": driver unit " + unit);
}
}
private static IntBuffer ints(int... values) {
final IntBuffer buf = ByteBuffer.allocateDirect(values.length * 4).order(ByteOrder.nativeOrder()).asIntBuffer();
buf.put(values).flip();
return buf;
}
@Test
void renderSystemRebindAfterGlsmUnbindReachesDriver() {
final int sampler = RenderSystem.genSampler();
try {
RenderSystem.bindSamplerToUnit(0, sampler);
GLStateManager.glBindSampler(0, 0);
assertEquals(0, driverBinding(0), "setup: GLSM unbind must reach the driver");
RenderSystem.bindSamplerToUnit(0, sampler);
assertEquals(sampler, driverBinding(0), "RenderSystem skipped a bind the driver no longer holds");
} finally {
RenderSystem.destroySampler(sampler);
}
}
@Test
void deleteClearsEveryUnitAndTheNextBindReachesDriver() {
final int sampler = RenderSystem.genSampler();
GLStateManager.glBindSampler(1, sampler);
GLStateManager.glBindSampler(3, sampler);
GLStateManager.glDeleteSamplers(sampler);
assertUnits("after delete", 0, 0, 0, 0);
final int next = RenderSystem.genSampler();
try {
GLStateManager.glBindSampler(1, next);
assertEquals(next, driverBinding(1), "bind after delete never reached the driver");
} finally {
RenderSystem.destroySampler(next);
}
}
@Test
void samplerBindingQueryAnswersForTheActiveUnit() {
final int sampler = RenderSystem.genSampler();
try {
GLStateManager.glBindSampler(1, sampler);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertEquals(GL11.glGetInteger(GL33.GL_SAMPLER_BINDING),
GLStateManager.glGetInteger(GL33.GL_SAMPLER_BINDING));
assertEquals(sampler, GLStateManager.glGetInteger(GL33.GL_SAMPLER_BINDING));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
assertEquals(GL11.glGetInteger(GL33.GL_SAMPLER_BINDING),
GLStateManager.glGetInteger(GL33.GL_SAMPLER_BINDING));
assertEquals(0, GLStateManager.glGetInteger(GL33.GL_SAMPLER_BINDING));
} finally {
RenderSystem.destroySampler(sampler);
}
}
@Test
void unbindAllSamplersClearsUnitsBoundThroughGlsm() {
final int sampler = RenderSystem.genSampler();
try {
GLStateManager.glBindSampler(1, sampler);
GLStateManager.glBindSampler(3, sampler);
RenderSystem.unbindAllSamplers();
assertEquals(0, driverBinding(1), "unit 1 still bound on the driver");
assertEquals(0, driverBinding(3), "unit 3 still bound on the driver");
assertEquals(0, GLStateManager.getSamplerBinding(1));
assertEquals(0, GLStateManager.getSamplerBinding(3));
} finally {
RenderSystem.destroySampler(sampler);
}
}
@Test
void replayForwardsCachedSamplerBindings() {
final int sampler = RenderSystem.genSampler();
try {
GLStateManager.glBindSampler(2, sampler);
GL33.glBindSampler(2, 0);
assertEquals(0, driverBinding(2), "setup failed to diverge the driver");
GLStateManager.replayStateToBackend();
assertEquals(sampler, driverBinding(2), "replay dropped a cached sampler binding");
} finally {
RenderSystem.destroySampler(sampler);
}
}
@Test
void intBufferGenFillsFromPositionWithBindableSamplers() {
final IntBuffer buf = ints(-1, 0, 0);
buf.position(1);
GLStateManager.glGenSamplers(buf);
final int a = buf.get(1);
final int b = buf.get(2);
try {
assertEquals(-1, buf.get(0), "slot before position was overwritten");
assertEquals(1, buf.position(), "gen must not move the buffer position");
assertNotEquals(0, a);
assertNotEquals(0, b);
assertNotEquals(a, b);
GLStateManager.glBindSampler(0, a);
GLStateManager.glBindSampler(1, b);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "generated name rejected by the driver");
assertEquals(a, driverBinding(0));
assertEquals(b, driverBinding(1));
} finally {
GLStateManager.glDeleteSamplers(a);
GLStateManager.glDeleteSamplers(b);
}
}
@Test
void bindSamplersFormsBindFromPosition() {
final int a = RenderSystem.genSampler();
final int b = RenderSystem.genSampler();
try {
final IntBuffer buf = ints(-1, a, b);
buf.position(1);
GLStateManager.glBindSamplers(1, 2, buf);
assertEquals(1, buf.position(), "count form must not move the buffer position");
assertUnits("count form", 0, a, b, 0);
GLStateManager.glBindSampler(1, 0);
GLStateManager.glBindSampler(2, 0);
buf.position(1);
GLStateManager.glBindSamplers(1, buf);
assertEquals(1, buf.position(), "remaining form must not move the buffer position");
assertUnits("remaining form", 0, a, b, 0);
GLStateManager.glBindSamplers(1, (IntBuffer) null);
assertUnits("null remaining form", 0, a, b, 0);
GLStateManager.glBindSamplers(2, new int[]{a, b});
assertUnits("int[] form", 0, a, a, b);
} finally {
RenderSystem.destroySampler(a);
RenderSystem.destroySampler(b);
}
}
@Test
void bindSamplersNullUnbindsRangeAndShortBufferThrows() {
final int prior = RenderSystem.genSampler();
final int next = RenderSystem.genSampler();
try {
for (int unit = 0; unit < UNITS; unit++) GLStateManager.glBindSampler(unit, prior);
GLStateManager.glBindSamplers(1, 2, (IntBuffer) null);
assertUnits("null count form", prior, 0, 0, prior);
for (int unit = 0; unit < UNITS; unit++) GLStateManager.glBindSampler(unit, prior);
final IntBuffer buf = ints(next, next);
assertThrows(IllegalArgumentException.class, () -> GLStateManager.glBindSamplers(0, UNITS, buf));
assertEquals(0, buf.position());
assertUnits("short buffer", prior, prior, prior, prior);
} finally {
RenderSystem.destroySampler(prior);
RenderSystem.destroySampler(next);
}
}
}
@@ -17,6 +17,8 @@ import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Set;
@@ -300,6 +302,74 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().isEmpty(), owner + "." + name + " should not be recorded");
}
private static boolean glsmDeclares(String name, String desc) throws ClassNotFoundException {
final Class<?> glsm = Class.forName(GLSM.replace('/', '.'), false, GLSMRedirectorTest.class.getClassLoader());
for (Method m : glsm.getDeclaredMethods()) {
final int mods = m.getModifiers();
if (!Modifier.isPublic(mods) || !Modifier.isStatic(mods) || !m.getName().equals(name)) continue;
if (Type.getMethodDescriptor(m).equals(desc)) return true;
}
return false;
}
private static void assertRedirectedToDeclaredTarget(String[][] cases) throws ClassNotFoundException {
for (boolean aware : new boolean[]{false, true}) {
for (String[] c : cases) {
final String call = c[0] + "." + c[1] + c[2] + (aware ? " aware" : "");
final ClassNode cn = makeClassWithMethodCall(c[0], c[1], c[2]);
assertTrue(new GLSMRedirector().transformClassNode("com.example.TestClass", cn, aware), call);
final MethodInsnNode target = firstCall(cn);
assertEquals(GLSM, target.owner, call);
assertEquals(c[1], target.name, call);
assertEquals(c[2], target.desc, call);
assertTrue(glsmDeclares(target.name, target.desc), () -> "GLStateManager lacks the target of " + call);
}
}
assertTrue(unmappedSeen().isEmpty(), () -> "unexpected reports: " + unmappedSeen());
}
@Test
void samplerGenDeleteOverloadsHaveDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/GL33", "glGenSamplers", "()I"},
{"org/lwjgl/opengl/GL33", "glGenSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL33C", "glGenSamplers", "([I)V"},
{"org/lwjgl/opengl/GL33", "glGenSamplers", "([I)V"},
{"org/lwjgl/opengl/GL33", "glDeleteSamplers", "(I)V"},
{"org/lwjgl/opengl/GL33", "glDeleteSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL33C", "glDeleteSamplers", "([I)V"},
{"org/lwjgl/opengl/GL33", "glDeleteSamplers", "([I)V"},
});
}
@Test
void bindSamplersHasDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/GL44", "glBindSamplers", "(IILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL44C", "glBindSamplers", "(ILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL44", "glBindSamplers", "(I[I)V"},
{"org/lwjgl/opengl/ARBMultiBind", "glBindSamplers", "(IILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBMultiBind", "glBindSamplers", "(ILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBMultiBind", "glBindSamplers", "(I[I)V"},
});
}
@Test
void arbSamplerObjectsHasDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/ARBSamplerObjects", "glGenSamplers", "()I"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glGenSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glGenSamplers", "([I)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glDeleteSamplers", "(I)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glDeleteSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glDeleteSamplers", "([I)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glIsSampler", "(I)Z"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glBindSampler", "(II)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glSamplerParameteri", "(III)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glSamplerParameterf", "(IIF)V"},
});
}
@Test
void descKeyedSyncRedirectsOnlyLongForm() {
assertRedirected("org/lwjgl/opengl/GL32C", "glFenceSync", "(II)J", "glFenceSync");
@@ -0,0 +1,104 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader.UploadStrategy;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntSet;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
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.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
class StreamingBindingCacheGLTest {
private static ByteBuffer filled(int bytes) {
final ByteBuffer data = memAlloc(bytes);
for (int i = 0; i < bytes; i++) {
data.put(i, (byte) i);
}
data.position(0).limit(bytes);
return data;
}
private static int glsmUniformBufferBinding() {
final Object ctx = Reflect.invokeStatic(GLStateManager.class, "ctx", new Class<?>[0]);
final Int2IntMap bound = Reflect.get(ctx, "boundOtherBuffers");
return bound.get(GL31.GL_UNIFORM_BUFFER);
}
@ParameterizedTest
@EnumSource(UploadStrategy.class)
void orphanUploadLeavesTheArrayBufferCacheMatchingTheDriver(UploadStrategy strategy) {
final int other = GLStateManager.glGenBuffers();
final OrphanStreamingBuffer buf = new OrphanStreamingBuffer();
final ByteBuffer data = filled(64);
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, other);
buf.upload(strategy, data);
assertEquals(GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING), GLStateManager.getBoundVBO(), "first upload respecifies the buffer");
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, other);
buf.upload(strategy, data);
assertEquals(GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING), GLStateManager.getBoundVBO(), "second upload takes the in-capacity path");
} finally {
memFree(data);
buf.destroy();
GLStateManager.glDeleteBuffers(other);
}
}
@Test
void uniformRingOrphanPathLeavesTheUniformBufferCacheMatchingTheDriver() {
final int other = GLStateManager.glGenBuffers();
final ByteBuffer src = filled(64);
UniformRingBuffer ring = null;
try {
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, other);
ring = new UniformRingBuffer(1024, 64, true);
assertFalse(ring.isPersistent());
assertEquals(GL11.glGetInteger(GL31.GL_UNIFORM_BUFFER_BINDING), glsmUniformBufferBinding(), "constructor allocation");
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, other);
ring.writeBlock(src);
assertEquals(GL11.glGetInteger(GL31.GL_UNIFORM_BUFFER_BINDING), glsmUniformBufferBinding(), "writeBlock");
} finally {
if (ring != null) ring.destroy();
GLStateManager.glDeleteBuffers(other);
memFree(src);
}
}
@Test
void persistentRingMarksItsOwnBufferAndLeavesTheCacheMatchingTheDriver() {
assumeTrue(RenderSystem.supportsBufferStorage(), "GL4.4+ required");
final int other = GLStateManager.glGenBuffers();
PersistentStreamingBuffer ring = null;
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, other);
ring = PersistentStreamingBuffer.createOrNull(4096, false);
assertNotNull(ring);
final IntSet persistent = Reflect.getStatic(FfpExtendedAttribs.class, "persistentSources");
assertTrue(persistent.contains(ring.getBufferId()), "the ring buffer is the persistent source");
assertFalse(persistent.contains(other), "the previously bound buffer is not persistent");
assertEquals(GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING), GLStateManager.getBoundVBO());
} finally {
if (ring != null) ring.destroy();
GLStateManager.glDeleteBuffers(other);
}
}
}
@@ -75,39 +75,58 @@ public final class SDLGPULWJGLService implements LWJGLService {
@Override public int getPointerSize() { return 8; } // 64-bit
@Override public int glGenBuffers() { return BackendManager.RENDER_BACKEND.genBuffers(); }
@Override public void glDeleteBuffers(int buffer) { BackendManager.RENDER_BACKEND.deleteBuffers(buffer); }
@Override public int glGenBuffers() { return GLStateManager.glGenBuffers(); }
@Override public void glDeleteBuffers(int buffer) { GLStateManager.glDeleteBuffers(buffer); }
@Override public void glBindBuffer(int target, int buffer) { GLStateManager.glBindBuffer(target, buffer); }
@Override public void glBufferData(int target, long size, int usage) { BackendManager.RENDER_BACKEND.bufferData(target, size, usage); }
@Override public void glBufferData(int target, ByteBuffer data, int usage) { BackendManager.RENDER_BACKEND.bufferData(target, data, usage); }
@Override public void glBufferData(int target, long size, int usage) {
GLStateManager.glBufferData(target, size, usage);
}
@Override public void glBufferData(int target, ByteBuffer data, int usage) {
GLStateManager.glBufferData(target, data, usage);
}
@Override public void glBufferData(int target, long size, long data, int usage) {
if (data != 0) {
BackendManager.RENDER_BACKEND.bufferData(target, MemoryUtil.memByteBuffer(data, (int) size), usage);
GLStateManager.nglBufferData(target, size, data, usage);
} else {
BackendManager.RENDER_BACKEND.bufferData(target, size, usage);
GLStateManager.glBufferData(target, size, usage);
}
}
@Override public void glBufferStorage(int target, long size, int flags) { BackendManager.RENDER_BACKEND.bufferStorage(target, size, flags); }
@Override public void glBlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
BackendManager.RENDER_BACKEND.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
@Override public void glBufferStorage(int target, long size, int flags) {
GLStateManager.glBufferStorage(target, size, flags);
}
@Override public void glBlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
GLStateManager.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
@Override public void glDrawArrays(int mode, int first, int count) {
GLStateManager.glDrawArrays(mode, first, count);
}
@Override public void glDrawArrays(int mode, int first, int count) { BackendManager.RENDER_BACKEND.drawArrays(mode, first, count); }
@Override public void glDrawBuffers(int[] buffers) {
try (var stack = MemoryStack.stackPush()) {
BackendManager.RENDER_BACKEND.drawBuffers(stack.ints(buffers));
GLStateManager.glDrawBuffers(stack.ints(buffers));
}
}
@Override public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
GLStateManager.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
}
@Override public void glTexParameteri(int target, int pname, int param) { GLStateManager.glTexParameteri(target, pname, param); }
@Override public ByteBuffer glMapBufferRange(int target, long offset, long length, int flags) { return BackendManager.RENDER_BACKEND.mapBufferRange(target, offset, length, flags); }
@Override public ByteBuffer glMapBufferRange(int target, long offset, long length, int flags) {
return GLStateManager.glMapBufferRange(target, offset, length, flags);
}
@Override public long nglMapBuffer(int target, int access) { return 0; }
@Override public ByteBuffer glMapBuffer(int target, int access) { return BackendManager.RENDER_BACKEND.mapBuffer(target, access); }
@Override public void glUnmapBuffer(int target) { BackendManager.RENDER_BACKEND.unmapBuffer(target); }
@Override public void glFlushMappedBufferRange(int target, long offset, long length) { BackendManager.RENDER_BACKEND.flushMappedBufferRange(target, offset, length); }
@Override public void glCopyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size) { BackendManager.RENDER_BACKEND.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); }
@Override public void glBindBufferBase(int target, int index, int buffer) { BackendManager.RENDER_BACKEND.bindBufferBase(target, index, buffer); }
@Override public ByteBuffer glMapBuffer(int target, int access) {
return GLStateManager.glMapBuffer(target, access);
}
@Override public void glUnmapBuffer(int target) { GLStateManager.glUnmapBuffer(target); }
@Override public void glFlushMappedBufferRange(int target, long offset, long length) {
GLStateManager.glFlushMappedBufferRange(target, offset, length);
}
@Override public void glCopyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset,
long size) {
GLStateManager.glCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size);
}
@Override public void glBindBufferBase(int target, int index, int buffer) {
GLStateManager.glBindBufferBase(target, index, buffer);
}
@Override public int glGenVertexArrays() { return GLStateManager.glGenVertexArrays(); }
@Override public void glDeleteVertexArrays(int array) { GLStateManager.glDeleteVertexArrays(array); }
@@ -117,63 +136,80 @@ public final class SDLGPULWJGLService implements LWJGLService {
@Override public void glEnableVertexAttribArray(int index) { GLStateManager.glEnableVertexAttribArray(index); }
@Override public void glVertexAttribDivisor(int index, int divisor) { GLStateManager.glVertexAttribDivisor(index, divisor); }
@Override public int glCreateShader(int type) { return BackendManager.RENDER_BACKEND.createShader(type); }
@Override public void glShaderSource(int shader, CharSequence source) { BackendManager.RENDER_BACKEND.shaderSource(shader, source); }
@Override public void glShaderSourceSafe(int shader, CharSequence source) { BackendManager.RENDER_BACKEND.shaderSource(shader, source); }
@Override public void glCompileShader(int shader) { BackendManager.RENDER_BACKEND.compileShader(shader); }
@Override public String glGetShaderInfoLog(int shader, int maxLength) { return BackendManager.RENDER_BACKEND.getShaderInfoLog(shader, maxLength); }
@Override public int glGetShaderi(int shader, int pname) { return BackendManager.RENDER_BACKEND.getShaderi(shader, pname); }
@Override public void glDeleteShader(int shader) { BackendManager.RENDER_BACKEND.deleteShader(shader); }
@Override public int glCreateShader(int type) { return GLStateManager.glCreateShader(type); }
@Override public void glShaderSource(int shader, CharSequence source) {
GLStateManager.glShaderSource(shader, source);
}
@Override public void glShaderSourceSafe(int shader, CharSequence source) {
GLStateManager.glShaderSource(shader, source);
}
@Override public void glCompileShader(int shader) { GLStateManager.glCompileShader(shader); }
@Override public String glGetShaderInfoLog(int shader, int maxLength) {
return GLStateManager.glGetShaderInfoLog(shader, maxLength);
}
@Override public int glGetShaderi(int shader, int pname) { return GLStateManager.glGetShaderi(shader, pname); }
@Override public void glDeleteShader(int shader) { GLStateManager.glDeleteShader(shader); }
@Override public int glCreateProgram() { return BackendManager.RENDER_BACKEND.createProgram(); }
@Override public void glAttachShader(int program, int shader) { BackendManager.RENDER_BACKEND.attachShader(program, shader); }
@Override public void glLinkProgram(int program) { BackendManager.RENDER_BACKEND.linkProgram(program); }
@Override public String glGetProgramInfoLog(int program, int maxLength) { return BackendManager.RENDER_BACKEND.getProgramInfoLog(program, maxLength); }
@Override public int glGetProgrami(int program, int pname) { return BackendManager.RENDER_BACKEND.getProgrami(program, pname); }
@Override public int glCreateProgram() { return GLStateManager.glCreateProgram(); }
@Override public void glAttachShader(int program, int shader) { GLStateManager.glAttachShader(program, shader); }
@Override public void glLinkProgram(int program) { GLStateManager.glLinkProgram(program); }
@Override public String glGetProgramInfoLog(int program, int maxLength) {
return GLStateManager.glGetProgramInfoLog(program, maxLength);
}
@Override public int glGetProgrami(int program, int pname) { return GLStateManager.glGetProgrami(program, pname); }
@Override public void glUseProgram(int program) { GLStateManager.glUseProgram(program); }
@Override public void glDeleteProgram(int program) { BackendManager.RENDER_BACKEND.deleteProgram(program); }
@Override public void glBindAttribLocation(int program, int index, CharSequence name) { BackendManager.RENDER_BACKEND.bindAttribLocation(program, index, name); }
@Override public void glDeleteProgram(int program) { GLStateManager.glDeleteProgram(program); }
@Override public void glBindAttribLocation(int program, int index, CharSequence name) {
GLStateManager.glBindAttribLocation(program, index, name);
}
@Override public void glBindFragDataLocation(int program, int colorNumber, CharSequence name) {}
@Override public int glGetUniformLocation(int program, CharSequence name) { return BackendManager.RENDER_BACKEND.getUniformLocation(program, name); }
@Override public int glGetUniformLocation(int program, CharSequence name) {
return GLStateManager.glGetUniformLocation(program, name);
}
@Override public int glGetUniformBlockIndex(int program, CharSequence name) { return -1; /* TODO */ }
@Override public void glUniformBlockBinding(int program, int blockIndex, int blockBinding) { /* TODO */ }
@Override public void glUniform1f(int location, float v0) { BackendManager.RENDER_BACKEND.uniform1f(location, v0); }
@Override public void glUniform1i(int location, int v0) { BackendManager.RENDER_BACKEND.uniform1i(location, v0); }
@Override public void glUniform1fv(int location, FloatBuffer value) { BackendManager.RENDER_BACKEND.uniform1fv(location, value); }
@Override public void glUniform2i(int location, int v0, int v1) { BackendManager.RENDER_BACKEND.uniform2i(location, v0, v1); }
@Override public void glUniform3f(int location, float v0, float v1, float v2) { BackendManager.RENDER_BACKEND.uniform3f(location, v0, v1, v2); }
@Override public void glUniform3fv(int location, FloatBuffer value) { BackendManager.RENDER_BACKEND.uniform3(location, value); }
@Override public void glUniform3fv(int location, float[] value) {
try (var stack = MemoryStack.stackPush()) {
final FloatBuffer buf = stack.floats(value);
BackendManager.RENDER_BACKEND.uniform3(location, buf);
}
@Override public void glUniform1f(int location, float v0) { GLStateManager.glUniform1f(location, v0); }
@Override public void glUniform1i(int location, int v0) { GLStateManager.glUniform1i(location, v0); }
@Override public void glUniform1fv(int location, FloatBuffer value) { GLStateManager.glUniform1(location, value); }
@Override public void glUniform2i(int location, int v0, int v1) { GLStateManager.glUniform2i(location, v0, v1); }
@Override public void glUniform3f(int location, float v0, float v1, float v2) {
GLStateManager.glUniform3f(location, v0, v1, v2);
}
@Override public void glUniform4fv(int location, FloatBuffer value) { BackendManager.RENDER_BACKEND.uniform4(location, value); }
@Override public void glUniform4fv(int location, float[] value) {
try (var stack = MemoryStack.stackPush()) {
final FloatBuffer buf = stack.floats(value);
BackendManager.RENDER_BACKEND.uniform4(location, buf);
}
@Override public void glUniform3fv(int location, FloatBuffer value) { GLStateManager.glUniform3(location, value); }
@Override public void glUniform3fv(int location, float[] value) { GLStateManager.glUniform3(location, value); }
@Override public void glUniform4fv(int location, FloatBuffer value) { GLStateManager.glUniform4(location, value); }
@Override public void glUniform4fv(int location, float[] value) { GLStateManager.glUniform4(location, value); }
@Override public void glUniformMatrix3fv(int location, boolean transpose, FloatBuffer value) {
GLStateManager.glUniformMatrix3(location, transpose, value);
}
@Override public void glUniformMatrix4fv(int location, boolean transpose, FloatBuffer value) {
GLStateManager.glUniformMatrix4(location, transpose, value);
}
@Override public void glUniformMatrix3fv(int location, boolean transpose, FloatBuffer value) { BackendManager.RENDER_BACKEND.uniformMatrix3(location, transpose, value); }
@Override public void glUniformMatrix4fv(int location, boolean transpose, FloatBuffer value) { BackendManager.RENDER_BACKEND.uniformMatrix4(location, transpose, value); }
@Override public void glDrawElementsBaseVertex(int mode, int count, int type, long indices, int basevertex) { BackendManager.RENDER_BACKEND.drawElementsBaseVertex(mode, count, type, indices, basevertex); }
@Override public void glMultiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) { BackendManager.RENDER_BACKEND.multiDrawElementsBaseVertex(mode, pCount, type, pIndices, drawcount, pBaseVertex); }
@Override public void glMultiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) { BackendManager.RENDER_BACKEND.multiDrawElementsIndirect(mode, type, indirect, drawcount, stride); }
@Override public void glDrawElementsBaseVertex(int mode, int count, int type, long indices, int basevertex) {
GLStateManager.glDrawElementsBaseVertex(mode, count, type, indices, basevertex);
}
@Override public void glMultiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount,
long pBaseVertex) {
GLStateManager.glMultiDrawElementsBaseVertex(mode, pCount, type, pIndices, drawcount, pBaseVertex);
}
@Override public void glMultiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
GLStateManager.glMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
}
@Override public long glFenceSync(int condition, int flags) { return BackendManager.RENDER_BACKEND.fenceSync(condition, flags); }
@Override public int glClientWaitSync(long sync, int flags, long timeout) { return BackendManager.RENDER_BACKEND.clientWaitSync(sync, flags, timeout); }
@Override public long glFenceSync(int condition, int flags) { return GLStateManager.glFenceSync(condition, flags); }
@Override public int glClientWaitSync(long sync, int flags, long timeout) {
return GLStateManager.glClientWaitSync(sync, flags, timeout);
}
@Override public int glGetSynci(long sync, int pname, IntBuffer length) {
if (length != null && length.remaining() > 0) length.put(0, 1);
return ((SDLGPURenderBackend) BackendManager.RENDER_BACKEND).getSyncStatus(sync);
}
@Override public void glWaitSync(long sync, int flags, long timeout) {
((SDLGPURenderBackend) BackendManager.RENDER_BACKEND).waitSync(sync, flags, timeout);
GLStateManager.glWaitSync(sync, flags, timeout);
}
@Override public void glDeleteSync(long sync) { BackendManager.RENDER_BACKEND.deleteSync(sync); }
@Override public void glDeleteSync(long sync) { GLStateManager.glDeleteSync(sync); }
private long[] queryElapsed = new long[64];
private int activeQueryId = -1;
@@ -185,7 +221,7 @@ public final class SDLGPULWJGLService implements LWJGLService {
}
}
@Override public int glGenQueries() { return BackendManager.RENDER_BACKEND.genQueries(); }
@Override public int glGenQueries() { return GLStateManager.glGenQueries(); }
@Override public void glDeleteQueries(int query) { }
@Override public void glBeginQuery(int target, int id) {
ensureQueryCapacity(id);
@@ -204,29 +240,42 @@ public final class SDLGPULWJGLService implements LWJGLService {
@Override public int setupDebugCallback(DebugMessageHandler handler) { return 0; }
@Override public void disableDebugCallback() { }
@Override public void glObjectLabel(int identifier, int name, CharSequence label) { BackendManager.RENDER_BACKEND.objectLabel(identifier, name, label); }
@Override public void glPushDebugGroup(int source, int id, CharSequence message) { BackendManager.RENDER_BACKEND.pushDebugGroup(source, id, message); }
@Override public void glPopDebugGroup() { BackendManager.RENDER_BACKEND.popDebugGroup(); }
@Override public int glGenTextures() { return BackendManager.RENDER_BACKEND.genTextures(); }
@Override public void glGenTextures(int[] textures) {
for (int i = 0; i < textures.length; i++) textures[i] = BackendManager.RENDER_BACKEND.genTextures();
@Override public void glObjectLabel(int identifier, int name, CharSequence label) {
GLStateManager.glObjectLabel(identifier, name, label);
}
@Override public void glDeleteTextures(int texture) { BackendManager.RENDER_BACKEND.deleteTextures(texture); }
@Override public void glPushDebugGroup(int source, int id, CharSequence message) {
GLStateManager.glPushDebugGroup(source, id, message);
}
@Override public void glPopDebugGroup() { GLStateManager.glPopDebugGroup(); }
@Override public int glGenTextures() { return GLStateManager.glGenTextures(); }
@Override public void glGenTextures(int[] textures) {
for (int i = 0; i < textures.length; i++) textures[i] = GLStateManager.glGenTextures();
}
@Override public void glDeleteTextures(int texture) { GLStateManager.glDeleteTextures(texture); }
@Override public void glDeleteTextures(int[] textures) {
for (int t : textures) BackendManager.RENDER_BACKEND.deleteTextures(t);
for (int t : textures) GLStateManager.glDeleteTextures(t);
}
@Override public void glBindTexture(int target, int texture) { GLStateManager.glBindTexture(target, texture); }
@Override public void glActiveTexture(int texture) { GLStateManager.glActiveTexture(texture); }
@Override public int glGetTexLevelParameteri(int target, int level, int pname) { return BackendManager.RENDER_BACKEND.getTexLevelParameteri(target, level, pname); }
@Override public void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) { BackendManager.RENDER_BACKEND.copyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); }
@Override public void glPixelStorei(int pname, int param) { BackendManager.RENDER_BACKEND.pixelStorei(pname, param); }
@Override public int glGetTexLevelParameteri(int target, int level, int pname) {
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
}
@Override public void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width,
int height) {
GLStateManager.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
}
@Override public void glPixelStorei(int pname, int param) { GLStateManager.glPixelStorei(pname, param); }
@Override public int glGenFramebuffers() { return BackendManager.RENDER_BACKEND.genFramebuffers(); }
@Override public void glDeleteFramebuffers(int framebuffer) { BackendManager.RENDER_BACKEND.deleteFramebuffers(framebuffer); }
@Override public int glGenFramebuffers() { return GLStateManager.glGenFramebuffers(); }
@Override public void glDeleteFramebuffers(int framebuffer) { GLStateManager.glDeleteFramebuffers(framebuffer); }
@Override public void glBindFramebuffer(int target, int framebuffer) { GLStateManager.glBindFramebuffer(target, framebuffer); }
@Override public int glCheckFramebufferStatus(int target) { return BackendManager.RENDER_BACKEND.checkFramebufferStatus(target); }
@Override public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) { BackendManager.RENDER_BACKEND.framebufferTexture2D(target, attachment, textarget, texture, level); }
@Override public int glCheckFramebufferStatus(int target) {
return GLStateManager.glCheckFramebufferStatus(target);
}
@Override public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
GLStateManager.glFramebufferTexture2D(target, attachment, textarget, texture, level);
}
@Override public void glEnable(int cap) { GLStateManager.glEnable(cap); }
@Override public void glDisable(int cap) { GLStateManager.glDisable(cap); }
@@ -236,20 +285,22 @@ public final class SDLGPULWJGLService implements LWJGLService {
@Override public void glDepthMask(boolean flag) { GLStateManager.glDepthMask(flag); }
@Override public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) { GLStateManager.glColorMask(red, green, blue, alpha); }
@Override public void glViewport(int x, int y, int width, int height) { GLStateManager.glViewport(x, y, width, height); }
@Override public void glClear(int mask) { BackendManager.RENDER_BACKEND.clear(mask); }
@Override public void glClearColor(float red, float green, float blue, float alpha) { BackendManager.RENDER_BACKEND.clearColor(red, green, blue, alpha); }
@Override public int glGetError() { return BackendManager.RENDER_BACKEND.getError(); }
@Override public void glMatrixMode(int mode) { /* Handled by GLSM FFP emulation */ }
@Override public void glLoadMatrixf(FloatBuffer m) { /* Handled by GLSM FFP emulation */ }
@Override public int glGetInteger(int pname) { return BackendManager.RENDER_BACKEND.getInteger(pname); }
@Override public void glGetIntegerv(int pname, int[] params) {
if (params.length > 0) params[0] = BackendManager.RENDER_BACKEND.getInteger(pname);
@Override public void glClear(int mask) { GLStateManager.glClear(mask); }
@Override public void glClearColor(float red, float green, float blue, float alpha) {
GLStateManager.glClearColor(red, green, blue, alpha);
}
@Override public int glGetError() { return GLStateManager.glGetError(); }
@Override public void glMatrixMode(int mode) { GLStateManager.glMatrixMode(mode); }
@Override public void glLoadMatrixf(FloatBuffer m) { GLStateManager.glLoadMatrix(m); }
@Override public int glGetInteger(int pname) { return GLStateManager.glGetInteger(pname); }
@Override public void glGetIntegerv(int pname, int[] params) { GLStateManager.glGetInteger(pname, params); }
@Override public boolean glGetBoolean(int pname) { return GLStateManager.glGetBoolean(pname); }
@Override public String glGetString(int pname) { return GLStateManager.glGetString(pname); }
@Override public int glGetAttribLocation(int program, CharSequence name) {
return GLStateManager.glGetAttribLocation(program, name);
}
@Override public boolean glGetBoolean(int pname) { return BackendManager.RENDER_BACKEND.getBoolean(pname); }
@Override public String glGetString(int pname) { return BackendManager.RENDER_BACKEND.getString(pname); }
@Override public int glGetAttribLocation(int program, CharSequence name) { return BackendManager.RENDER_BACKEND.getAttribLocation(program, name); }
@Override public MemoryStack stackPush() {
org.lwjgl.system.MemoryStack.stackPush();
@@ -760,8 +760,8 @@ public class SDLGPURenderBackend extends RenderBackend {
final ContextState cs = s();
if (cap == GL11.GL_BLEND) {
if (index < 0 || index >= ContextState.MAX_COLOR_ATTACHMENTS) return;
if (cs.pipeline.blendEnabledPerAttachment[index] != on) {
cs.pipeline.blendEnabledPerAttachment[index] = on;
if (cs.pipeline.blendEnabledPerDrawBuffer[index] != on) {
cs.pipeline.blendEnabledPerDrawBuffer[index] = on;
cs.pipeline.markOutputDirty();
}
return;
@@ -775,8 +775,8 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL11.GL_BLEND -> {
boolean changed = false;
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
if (cs.pipeline.blendEnabledPerAttachment[i] != on) {
cs.pipeline.blendEnabledPerAttachment[i] = on;
if (cs.pipeline.blendEnabledPerDrawBuffer[i] != on) {
cs.pipeline.blendEnabledPerDrawBuffer[i] = on;
changed = true;
}
}
@@ -805,21 +805,12 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
final ContextState cs = s();
final int sc = FormatMap.mapBlendFactor(srcRGB);
final int dc = FormatMap.mapBlendFactor(dstRGB);
final int sa = FormatMap.mapBlendFactor(srcAlpha);
final int da = FormatMap.mapBlendFactor(dstAlpha);
if (sc == cs.pipeline.srcColorFactor && dc == cs.pipeline.dstColorFactor && sa == cs.pipeline.srcAlphaFactor && da == cs.pipeline.dstAlphaFactor) return;
cs.pipeline.srcColorFactor = sc;
cs.pipeline.dstColorFactor = dc;
cs.pipeline.srcAlphaFactor = sa;
cs.pipeline.dstAlphaFactor = da;
cs.pipeline.markOutputDirty();
s().pipeline.setBlendFactors(FormatMap.mapBlendFactor(srcRGB), FormatMap.mapBlendFactor(dstRGB), FormatMap.mapBlendFactor(srcAlpha), FormatMap.mapBlendFactor(dstAlpha));
}
@Override public void blendFuncSeparatei(int buf, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
if (buf < 0 || buf >= ContextState.MAX_COLOR_ATTACHMENTS) return;
s().pipeline.setBlendFactors(buf, FormatMap.mapBlendFactor(srcRGB), FormatMap.mapBlendFactor(dstRGB), FormatMap.mapBlendFactor(srcAlpha), FormatMap.mapBlendFactor(dstAlpha));
}
@Override public void blendEquation(int mode) {
@@ -1755,6 +1746,7 @@ public class SDLGPURenderBackend extends RenderBackend {
private void applyDrawBuffersFromIntBuffer(FboState fbo, IntBuffer bufs, boolean isBound) {
final int count = bufs.remaining();
if (count > ContextState.MAX_COLOR_ATTACHMENTS) return;
final int basePos = bufs.position();
if (fbo.drawBuffers.length == count) {
boolean same = true;
@@ -2018,10 +2010,8 @@ public class SDLGPURenderBackend extends RenderBackend {
cs.pipeline.fragmentShader = prog.sdlFragmentShader;
cs.pipeline.programId = program;
cs.pipeline.maxFragOutputLocation = prog.maxFragOutputLocation;
cs.pipeline.shaderInputMask = prog.vertexInputMask;
cs.pipeline.shaderInputVecSize = prog.vertexInputVecSize;
cs.pipeline.shaderInputBaseType = prog.vertexInputBaseType;
cs.pipeline.shaderInputName = prog.vertexInputName;
cs.pipeline.setVertexInputs(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType, prog.vertexInputName);
cs.pipeline.markInputDirty();
cs.pipeline.markShaderDirty();
}
@Override public void useProgram(int program) {
@@ -2071,10 +2061,7 @@ public class SDLGPURenderBackend extends RenderBackend {
cs.pipeline.fragmentShader = prog.sdlFragmentShader;
cs.pipeline.programId = program;
cs.pipeline.maxFragOutputLocation = prog.maxFragOutputLocation;
cs.pipeline.shaderInputMask = prog.vertexInputMask;
cs.pipeline.shaderInputVecSize = prog.vertexInputVecSize;
cs.pipeline.shaderInputBaseType = prog.vertexInputBaseType;
cs.pipeline.shaderInputName = prog.vertexInputName;
cs.pipeline.setVertexInputs(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType, prog.vertexInputName);
cs.pipeline.markShaderDirty();
}
@@ -2282,6 +2269,7 @@ public class SDLGPURenderBackend extends RenderBackend {
LOG.warn("uniform1i: loc={} val={} -- NO program bound", location, v0);
}
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 1);
if (a == null) return;
a[0] = Float.intBitsToFloat(v0);
pipelineApplier.putUniform(st, location, a);
}
@@ -2289,6 +2277,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 1);
if (a == null) return;
a[0] = v0;
pipelineApplier.putUniform(st, location, a);
}
@@ -2296,6 +2285,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 2);
if (a == null) return;
a[0] = v0; a[1] = v1;
pipelineApplier.putUniform(st, location, a);
}
@@ -2303,6 +2293,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 2);
if (a == null) return;
a[0] = Float.intBitsToFloat(v0); a[1] = Float.intBitsToFloat(v1);
pipelineApplier.putUniform(st, location, a);
}
@@ -2310,6 +2301,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 3);
if (a == null) return;
a[0] = v0; a[1] = v1; a[2] = v2;
pipelineApplier.putUniform(st, location, a);
}
@@ -2317,6 +2309,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 3);
if (a == null) return;
a[0] = Float.intBitsToFloat(v0); a[1] = Float.intBitsToFloat(v1); a[2] = Float.intBitsToFloat(v2);
pipelineApplier.putUniform(st, location, a);
}
@@ -2324,6 +2317,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 4);
if (a == null) return;
a[0] = v0; a[1] = v1; a[2] = v2; a[3] = v3;
pipelineApplier.putUniform(st, location, a);
}
@@ -2331,6 +2325,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, 4);
if (a == null) return;
a[0] = Float.intBitsToFloat(v0); a[1] = Float.intBitsToFloat(v1);
a[2] = Float.intBitsToFloat(v2); a[3] = Float.intBitsToFloat(v3);
pipelineApplier.putUniform(st, location, a);
@@ -2352,6 +2347,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final int n = value.remaining();
final int pos = value.position();
final float[] v = pipelineApplier.reuseOrAlloc(st, location, n);
if (v == null) return;
for (int i = 0; i < n; i++) v[i] = Float.intBitsToFloat(value.get(pos + i));
pipelineApplier.putUniform(st, location, v);
}
@@ -2361,8 +2357,16 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void uniform4(int location, FloatBuffer value) {
pipelineApplier.putUniformFv(s(), location, value);
}
@Override public void uniform3fv(int location, float[] values) { pipelineApplier.putUniform(s(), location, values); }
@Override public void uniform4fv(int location, float[] values) { pipelineApplier.putUniform(s(), location, values); }
@Override public void uniform3fv(int location, float[] values) { storeFloatUniform(location, values); }
@Override public void uniform4fv(int location, float[] values) { storeFloatUniform(location, values); }
private void storeFloatUniform(int location, float[] values) {
if (location < 0) return;
final ContextState st = s();
final float[] a = pipelineApplier.reuseOrAlloc(st, location, values.length);
if (a == null) return;
System.arraycopy(values, 0, a, 0, values.length);
pipelineApplier.putUniform(st, location, a);
}
@Override public void uniformMatrix2(int location, boolean transpose, FloatBuffer value) {
pipelineApplier.storeMatrix(s(), location, transpose, value, 2);
}
@@ -2405,16 +2409,62 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public int genBuffers() { return resourceManager.genBuffer(); }
@Override public void deleteBuffers(int buffer) { dropMappingFor(buffer); readbackShadows.release(buffer); resourceManager.deleteBuffer(buffer); }
@Override public void deleteBuffers(int buffer) { deleteBuffer(buffer); }
@Override public void deleteBuffers(IntBuffer buffers) {
for (int i = 0; i < buffers.remaining(); i++) {
final int buffer = buffers.get(buffers.position() + i);
dropMappingFor(buffer);
readbackShadows.release(buffer);
resourceManager.deleteBuffer(buffer);
deleteBuffer(buffers.get(buffers.position() + i));
}
}
private void deleteBuffer(int buffer) {
dropMappingFor(buffer);
if (buffer != 0) unbindDeletedBuffer(s(), buffer);
readbackShadows.release(buffer);
resourceManager.deleteBuffer(buffer);
}
private static void unbindDeletedBuffer(ContextState st, int buffer) {
final ContextState.VAOState vao = st.currentVao;
boolean vaoChanged = false;
for (int i = 0; i < ContextState.MAX_VERTEX_ATTRIBS; i++) {
if (vao.bindingBuffer[i] == buffer) {
vao.bindingBuffer[i] = 0;
vaoChanged = true;
}
if (vao.attribVBO[i] == buffer) vao.attribVBO[i] = 0;
}
if (vao.elementBuffer == buffer) {
vao.elementBuffer = 0;
vaoChanged = true;
}
if (vaoChanged) st.bumpAttribStateGen();
if (st.boundArrayBuffer == buffer) st.boundArrayBuffer = 0;
if (st.boundIndirectBuffer == buffer) st.boundIndirectBuffer = 0;
if (st.boundDispatchIndirectBuffer == buffer) st.boundDispatchIndirectBuffer = 0;
if (st.boundCopyReadBuffer == buffer) st.boundCopyReadBuffer = 0;
if (st.boundCopyWriteBuffer == buffer) st.boundCopyWriteBuffer = 0;
if (st.boundUniformBuffer == buffer) st.boundUniformBuffer = 0;
if (st.boundSSBO == buffer) st.boundSSBO = 0;
if (st.boundPixelPackBuffer == buffer) st.boundPixelPackBuffer = 0;
if (st.boundPixelUnpackBuffer == buffer) st.boundPixelUnpackBuffer = 0;
boolean uboChanged = false;
boolean ssboChanged = false;
for (int i = 0; i < ContextState.MAX_INDEXED_BUFFERS; i++) {
if (st.boundUboByIndex[i] == buffer) {
st.boundUboByIndex[i] = 0;
st.uboRangeOffset[i] = 0;
st.uboRangeSize[i] = 0;
uboChanged = true;
}
if (st.boundSsboByIndex[i] == buffer) {
st.boundSsboByIndex[i] = 0;
ssboChanged = true;
}
}
if (uboChanged) st.uboRangeGen++;
if (ssboChanged) st.ssboBindGen++;
}
private static void dropMappingFor(int buffer) {
final ContextState st = s();
if (buffer == 0 || st.mappedBufferGlId != buffer || st.mappedStagingBuffer == null) return;
@@ -3606,7 +3656,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final ContextState cs = s();
return switch (pname) {
case GL11.GL_DEPTH_TEST -> cs.pipeline.depthTestEnabled;
case GL11.GL_BLEND -> cs.pipeline.blendEnabledPerAttachment[0];
case GL11.GL_BLEND -> cs.pipeline.blendEnabledPerDrawBuffer[0];
case GL11.GL_CULL_FACE -> cs.pipeline.cullEnabled;
case GL11.GL_SCISSOR_TEST -> cs.scissorEnabled;
case GL11.GL_STENCIL_TEST -> cs.pipeline.stencilTestEnabled;
@@ -3848,11 +3898,11 @@ public class SDLGPURenderBackend extends RenderBackend {
private int voxLocStart = -1;
private int voxLocCount = -1;
@Override public boolean bindVoxelizationRegion(int ssboBinding, int vertexBufferGlId, long openPass, float x, float y, float z) {
if (ssboBinding < 0 || ssboBinding >= ContextState.MAX_INDEXED_BUFFERS || vertexBufferGlId == 0) return false;
@Override public boolean bindVoxelizationRegion(int ssboBinding, long openPass, float x, float y, float z) {
if (ssboBinding < 0 || ssboBinding >= ContextState.MAX_INDEXED_BUFFERS) return false;
final ContextState st = s();
if (st.boundProgram == 0) return false;
bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, ssboBinding, vertexBufferGlId);
if (st.boundSsboByIndex[ssboBinding] == 0) return false;
final int loc = shaderManager.getUniformLocation(st.boundProgram, "u_RegionOffset");
if (loc >= 0) GLStateManager.glUniform3f(loc, x, y, z);
if (openPass != 0) voxelizationDispatcher.rebindVertexBuffer(st, openPass);
@@ -104,9 +104,9 @@ public final class FFPTrace {
.append("/front=").append(p.frontFace).append("/all=").append(p.cullAll);
sb.append(" depth=").append(p.depthTestEnabled).append("/func=").append(p.depthCompareOp)
.append("/write=").append(p.depthWriteEnabled);
sb.append(" blend=").append(p.blendEnabledPerAttachment[0])
.append("/src=").append(p.srcColorFactor).append("/dst=").append(p.dstColorFactor)
.append("/srcA=").append(p.srcAlphaFactor).append("/dstA=").append(p.dstAlphaFactor)
sb.append(" blend=").append(p.blendEnabledPerDrawBuffer[0])
.append("/src=").append(p.srcColorFactors[0]).append("/dst=").append(p.dstColorFactors[0])
.append("/srcA=").append(p.srcAlphaFactors[0]).append("/dstA=").append(p.dstAlphaFactors[0])
.append("/op=").append(p.colorBlendOp).append('/').append(p.alphaBlendOp);
sb.append(" colorMask=0x").append(Integer.toHexString(p.colorWriteMask));
sb.append(" primitiveType=").append(p.primitiveType);
@@ -677,10 +677,9 @@ public final class PipelineApplier {
public float[] reuseOrAlloc(ContextState st, int location, int length) {
final ShaderManager.ProgramObject prog = st.boundProgramObj;
if (prog != null && location >= 0 && location < prog.uniformSlotCount) {
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
if (existing != null && existing.length == length) return existing;
}
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return null;
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
if (existing != null && existing.length == length) return existing;
return new float[length];
}
@@ -828,11 +827,13 @@ public final class PipelineApplier {
final int count = n / floatsPerMatrix;
if (count == 0) {
final float[] v = reuseOrAlloc(st, location, n);
if (v == null) return;
value.get(value.position(), v);
putUniform(st, location, v);
return;
}
final float[] out = reuseOrAlloc(st, location, count * floatsPerMatrix);
if (out == null) return;
MatrixMarshal.marshalMatrixToColumnMajor(value, value.position(), count, size, transpose, out, 0);
putUniform(st, location, out);
}
@@ -59,11 +59,11 @@ public final class PipelineCache {
public int primitiveType = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
public final boolean[] blendEnabledPerAttachment = new boolean[ContextState.MAX_COLOR_ATTACHMENTS];
public int srcColorFactor = SDL_GPU_BLENDFACTOR_ONE;
public int dstColorFactor = SDL_GPU_BLENDFACTOR_ZERO;
public int srcAlphaFactor = SDL_GPU_BLENDFACTOR_ONE;
public int dstAlphaFactor = SDL_GPU_BLENDFACTOR_ZERO;
public final boolean[] blendEnabledPerDrawBuffer = new boolean[ContextState.MAX_COLOR_ATTACHMENTS];
public final int[] srcColorFactors = new int[ContextState.MAX_COLOR_ATTACHMENTS];
public final int[] dstColorFactors = new int[ContextState.MAX_COLOR_ATTACHMENTS];
public final int[] srcAlphaFactors = new int[ContextState.MAX_COLOR_ATTACHMENTS];
public final int[] dstAlphaFactors = new int[ContextState.MAX_COLOR_ATTACHMENTS];
public int colorBlendOp = SDL_GPU_BLENDOP_ADD;
public int alphaBlendOp = SDL_GPU_BLENDOP_ADD;
@@ -160,15 +160,35 @@ public final class PipelineCache {
return true;
}
private int attachmentForSlot(int slot) {
final int[] db = drawBuffers;
if (db == null || slot < 0 || slot >= db.length) return 0;
final int idx = db[slot];
return (idx >= 0 && idx < ContextState.MAX_COLOR_ATTACHMENTS) ? idx : 0;
private boolean blendEnabledForSlot(int slot) {
return slot >= 0 && slot < ContextState.MAX_COLOR_ATTACHMENTS && blendEnabledPerDrawBuffer[slot];
}
private boolean blendEnabledForSlot(int slot) {
return blendEnabledPerAttachment[attachmentForSlot(slot)];
public boolean setBlendFactors(int sc, int dc, int sa, int da) {
boolean changed = false;
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
if (srcColorFactors[i] != sc || dstColorFactors[i] != dc || srcAlphaFactors[i] != sa || dstAlphaFactors[i] != da) {
srcColorFactors[i] = sc;
dstColorFactors[i] = dc;
srcAlphaFactors[i] = sa;
dstAlphaFactors[i] = da;
changed = true;
}
}
if (changed) markOutputDirty();
return changed;
}
public boolean setBlendFactors(int drawBuffer, int sc, int dc, int sa, int da) {
if (srcColorFactors[drawBuffer] == sc && dstColorFactors[drawBuffer] == dc && srcAlphaFactors[drawBuffer] == sa && dstAlphaFactors[drawBuffer] == da) {
return false;
}
srcColorFactors[drawBuffer] = sc;
dstColorFactors[drawBuffer] = dc;
srcAlphaFactors[drawBuffer] = sa;
dstAlphaFactors[drawBuffer] = da;
markOutputDirty();
return true;
}
public int maxAttribs;
@@ -183,6 +203,10 @@ public final class PipelineCache {
public PipelineCache() {
this.maxAttribs = ContextState.MAX_VERTEX_ATTRIBS;
Arrays.fill(srcColorFactors, SDL_GPU_BLENDFACTOR_ONE);
Arrays.fill(dstColorFactors, SDL_GPU_BLENDFACTOR_ZERO);
Arrays.fill(srcAlphaFactors, SDL_GPU_BLENDFACTOR_ONE);
Arrays.fill(dstAlphaFactors, SDL_GPU_BLENDFACTOR_ZERO);
}
long lastKey() {
@@ -199,6 +223,16 @@ public final class PipelineCache {
return true;
}
public void setVertexInputs(int mask, int[] vecSize, int[] baseType, String[] names) {
if (shaderInputMask != mask || !Arrays.equals(shaderInputVecSize, vecSize) || !Arrays.equals(shaderInputBaseType, baseType)) {
markInputDirty();
}
shaderInputMask = mask;
shaderInputVecSize = vecSize;
shaderInputBaseType = baseType;
shaderInputName = names;
}
public void markOutputDirty() { outputDirty = true; lastKey = 0L; }
public void markShaderDirty() { shaderDirty = true; lastKey = 0L; }
@@ -296,9 +330,11 @@ public final class PipelineCache {
| (((long) blendBits & 0xFFFFFFFFL) << 32);
long h = OUTPUT_SEED;
h = Hashing.fmix64(h, bits);
h = Hashing.fmix64(h, Hashing.packHiLo(primitiveType, srcColorFactor));
h = Hashing.fmix64(h, Hashing.packHiLo(dstColorFactor, srcAlphaFactor));
h = Hashing.fmix64(h, Hashing.packHiLo(dstAlphaFactor, colorBlendOp));
h = Hashing.fmix64(h, Hashing.packHiLo(primitiveType, colorBlendOp));
for (int i = 0; i < ctfLen; i++) {
h = Hashing.fmix64(h, Hashing.packHiLo(srcColorFactors[i], dstColorFactors[i]));
h = Hashing.fmix64(h, Hashing.packHiLo(srcAlphaFactors[i], dstAlphaFactors[i]));
}
h = Hashing.fmix64(h, Hashing.packHiLo(alphaBlendOp, depthCompareOp));
h = Hashing.fmix64(h, Hashing.packHiLo(stencilFrontCompareOp, stencilFrontFailOp));
h = Hashing.fmix64(h, Hashing.packHiLo(stencilFrontDepthFailOp, stencilFrontPassOp));
@@ -390,11 +426,11 @@ public final class PipelineCache {
desc.format(colorTargetFormats()[i]);
desc.blend_state()
.enable_blend(blendEnabledForSlot(i))
.src_color_blendfactor(srcColorFactor)
.dst_color_blendfactor(dstColorFactor)
.src_color_blendfactor(srcColorFactors[i])
.dst_color_blendfactor(dstColorFactors[i])
.color_blend_op(colorBlendOp)
.src_alpha_blendfactor(srcAlphaFactor)
.dst_alpha_blendfactor(dstAlphaFactor)
.src_alpha_blendfactor(srcAlphaFactors[i])
.dst_alpha_blendfactor(dstAlphaFactors[i])
.alpha_blend_op(alphaBlendOp)
.color_write_mask((byte) colorWriteMask)
.enable_color_write_mask(true);
@@ -504,8 +540,10 @@ public final class PipelineCache {
.append(" backCmp=").append(stencilBackCompareOp).append('\n');
}
sb.append(" Blend: enabled=").append(blendEnabledForSlot(0))
.append(" srcColor=").append(srcColorFactor).append(" dstColor=").append(dstColorFactor).append(" colorOp=").append(colorBlendOp)
.append(" srcAlpha=").append(srcAlphaFactor).append(" dstAlpha=").append(dstAlphaFactor).append(" alphaOp=").append(alphaBlendOp)
.append(" srcColor=").append(srcColorFactors[0]).append(" dstColor=").append(dstColorFactors[0])
.append(" colorOp=").append(colorBlendOp)
.append(" srcAlpha=").append(srcAlphaFactors[0]).append(" dstAlpha=").append(dstAlphaFactors[0])
.append(" alphaOp=").append(alphaBlendOp)
.append(" mask=0x").append(Integer.toHexString(colorWriteMask)).append('\n');
sb.append(" Rasterizer: cull=").append(getEffectiveCullMode())
.append(" front=").append(effectiveFrontFace())
@@ -1,8 +1,9 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.sdl.SDLError;
@@ -86,13 +87,14 @@ public final class ReadbackShadows {
}
public void release(int glId) {
final LongArrayList doomed = new LongArrayList();
for (long k : slots.keySet()) {
if ((int) (k >>> 32) == glId) doomed.add(k);
}
for (int i = 0; i < doomed.size(); i++) {
final Slot slot = slots.remove(doomed.getLong(i));
if (slot != null) releaseSlot(slot);
if (slots.isEmpty()) return;
final ObjectIterator<Long2ObjectMap.Entry<Slot>> it = slots.long2ObjectEntrySet().fastIterator();
while (it.hasNext()) {
final Long2ObjectMap.Entry<Slot> entry = it.next();
if ((int) (entry.getLongKey() >>> 32) == glId) {
releaseSlot(entry.getValue());
it.remove();
}
}
}
@@ -0,0 +1,159 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL43;
import org.lwjgl.system.MemoryUtil;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
class BufferDeleteUnbindTest {
private static final int BUFFER = 7001;
private static final int OTHER = 7002;
private static final int UBO_INDEX = 3;
private static final int SSBO_INDEX = 5;
private static final int VERTEX_BINDING = 2;
private static final int ATTRIB = 4;
private static SDLGPURenderBackend backend;
private ContextState st;
@BeforeAll
static void createBackend() {
backend = new SDLGPURenderBackend();
}
@BeforeEach
void setUp() {
st = SdlTestRig.contextState();
backend.bindBufferRange(GL31.GL_UNIFORM_BUFFER, UBO_INDEX, BUFFER, 16L, 64L);
backend.bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, SSBO_INDEX, BUFFER);
backend.bindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, BUFFER);
backend.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, BUFFER);
backend.bindVertexBuffer(VERTEX_BINDING, BUFFER, 8L, 12);
backend.bindBuffer(GL15.GL_ARRAY_BUFFER, BUFFER);
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, BUFFER);
backend.bindBuffer(GL31.GL_COPY_READ_BUFFER, BUFFER);
backend.bindBuffer(GL31.GL_COPY_WRITE_BUFFER, BUFFER);
backend.bindBuffer(GL30.GL_PIXEL_PACK_BUFFER, BUFFER);
backend.bindBuffer(GL30.GL_PIXEL_UNPACK_BUFFER, BUFFER);
st.currentVao.attribVBO[ATTRIB] = BUFFER;
}
@AfterEach
void tearDown() {
backend.bindBufferBase(GL31.GL_UNIFORM_BUFFER, UBO_INDEX, 0);
backend.bindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, SSBO_INDEX, 0);
backend.bindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, 0);
backend.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
backend.bindVertexBuffer(VERTEX_BINDING, 0, 0L, 0);
backend.bindBuffer(GL15.GL_ARRAY_BUFFER, 0);
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, 0);
backend.bindBuffer(GL31.GL_COPY_READ_BUFFER, 0);
backend.bindBuffer(GL31.GL_COPY_WRITE_BUFFER, 0);
backend.bindBuffer(GL30.GL_PIXEL_PACK_BUFFER, 0);
backend.bindBuffer(GL30.GL_PIXEL_UNPACK_BUFFER, 0);
st.currentVao.attribVBO[ATTRIB] = 0;
}
private void assertAllCleared() {
assertEquals(0, st.boundUboByIndex[UBO_INDEX]);
assertEquals(0, st.uboRangeOffset[UBO_INDEX]);
assertEquals(0, st.uboRangeSize[UBO_INDEX]);
assertEquals(0, st.boundUniformBuffer);
assertEquals(0, st.boundSsboByIndex[SSBO_INDEX]);
assertEquals(0, st.boundSSBO);
assertEquals(0, st.boundIndirectBuffer);
assertEquals(0, st.currentVao.elementBuffer);
assertEquals(0, st.currentVao.bindingBuffer[VERTEX_BINDING]);
assertEquals(0, st.currentVao.attribVBO[ATTRIB]);
assertEquals(0, st.boundArrayBuffer);
assertEquals(0, st.boundDispatchIndirectBuffer);
assertEquals(0, st.boundCopyReadBuffer);
assertEquals(0, st.boundCopyWriteBuffer);
assertEquals(0, st.boundPixelPackBuffer);
assertEquals(0, st.boundPixelUnpackBuffer);
}
private void assertAllBound() {
assertEquals(BUFFER, st.boundUboByIndex[UBO_INDEX]);
assertEquals(16, st.uboRangeOffset[UBO_INDEX]);
assertEquals(64, st.uboRangeSize[UBO_INDEX]);
assertEquals(BUFFER, st.boundUniformBuffer);
assertEquals(BUFFER, st.boundSsboByIndex[SSBO_INDEX]);
assertEquals(BUFFER, st.boundSSBO);
assertEquals(BUFFER, st.boundIndirectBuffer);
assertEquals(BUFFER, st.currentVao.elementBuffer);
assertEquals(BUFFER, st.currentVao.bindingBuffer[VERTEX_BINDING]);
assertEquals(BUFFER, st.currentVao.attribVBO[ATTRIB]);
assertEquals(BUFFER, st.boundArrayBuffer);
assertEquals(BUFFER, st.boundDispatchIndirectBuffer);
assertEquals(BUFFER, st.boundCopyReadBuffer);
assertEquals(BUFFER, st.boundCopyWriteBuffer);
assertEquals(BUFFER, st.boundPixelPackBuffer);
assertEquals(BUFFER, st.boundPixelUnpackBuffer);
}
@Test
void deleteClearsEveryBindingOfTheDeletedBuffer() {
assertAllBound();
final int uboGen = st.uboRangeGen;
final int ssboGen = st.ssboBindGen;
final long attribGen = st.attribStateGen;
backend.deleteBuffers(BUFFER);
assertAllCleared();
assertEquals(8L, st.currentVao.bindingOffset[VERTEX_BINDING]);
assertEquals(12, st.currentVao.bindingStride[VERTEX_BINDING]);
assertNotEquals(uboGen, st.uboRangeGen);
assertNotEquals(ssboGen, st.ssboBindGen);
assertNotEquals(attribGen, st.attribStateGen);
}
@Test
void intBufferDeleteClearsEveryBindingOfTheDeletedBuffer() {
final int uboGen = st.uboRangeGen;
final int ssboGen = st.ssboBindGen;
final long attribGen = st.attribStateGen;
final IntBuffer ids = MemoryUtil.memAllocInt(2);
try {
ids.put(0, OTHER).put(1, BUFFER);
backend.deleteBuffers(ids);
} finally {
MemoryUtil.memFree(ids);
}
assertAllCleared();
assertNotEquals(uboGen, st.uboRangeGen);
assertNotEquals(ssboGen, st.ssboBindGen);
assertNotEquals(attribGen, st.attribStateGen);
}
@Test
void deletingAnUnrelatedBufferChangesNothing() {
final int uboGen = st.uboRangeGen;
final int ssboGen = st.ssboBindGen;
final long attribGen = st.attribStateGen;
backend.deleteBuffers(OTHER);
backend.deleteBuffers(0);
assertAllBound();
assertEquals(uboGen, st.uboRangeGen);
assertEquals(ssboGen, st.ssboBindGen);
assertEquals(attribGen, st.attribStateGen);
}
}
@@ -0,0 +1,83 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL30;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
class DrawBuffersLimitTest {
private static final int BOUND_FBO = 901;
private static final int UNBOUND_FBO = 902;
private static SDLGPURenderBackend backend;
private static ResourceManager resourceManager;
private ContextState st;
private int savedFbo;
@BeforeAll
static void createBackend() {
backend = new SDLGPURenderBackend();
resourceManager = Reflect.get(backend, "resourceManager");
}
@BeforeEach
void setUp() {
st = SdlTestRig.contextState();
savedFbo = st.boundFboId;
st.boundFboId = BOUND_FBO;
}
@AfterEach
void tearDown() {
st.boundFboId = savedFbo;
resourceManager.deleteFbo(BOUND_FBO);
resourceManager.deleteFbo(UNBOUND_FBO);
}
private static IntBuffer attachments(int count) {
final int[] bufs = new int[count];
for (int i = 0; i < count; i++) bufs[i] = GL30.GL_COLOR_ATTACHMENT0 + i;
return IntBuffer.wrap(bufs);
}
@Test
void namedDrawBuffersOverTheLimitAreIgnored() {
final FboState fbo = resourceManager.createFbo(UNBOUND_FBO);
backend.namedFramebufferDrawBuffers(UNBOUND_FBO, attachments(2));
final int[] before = fbo.drawBuffers;
assertArrayEquals(new int[]{0, 1}, before, "a legal count must apply, or the refusal proves nothing");
backend.namedFramebufferDrawBuffers(UNBOUND_FBO, attachments(ContextState.MAX_COLOR_ATTACHMENTS + 1));
assertSame(before, fbo.drawBuffers, "a count above MAX_DRAW_BUFFERS must leave the draw buffers untouched");
assertArrayEquals(new int[]{0, 1}, fbo.drawBuffers);
backend.namedFramebufferDrawBuffers(UNBOUND_FBO, attachments(ContextState.MAX_COLOR_ATTACHMENTS));
assertEquals(ContextState.MAX_COLOR_ATTACHMENTS, fbo.drawBuffers.length, "exactly the limit is legal");
}
@Test
void boundDrawBuffersOverTheLimitAreIgnored() {
final FboState fbo = resourceManager.createFbo(BOUND_FBO);
assertSame(fbo, resourceManager.getFbo(st.boundFboId));
final int[] before = fbo.drawBuffers;
backend.drawBuffers(attachments(ContextState.MAX_COLOR_ATTACHMENTS + 1));
assertSame(before, fbo.drawBuffers, "a count above MAX_DRAW_BUFFERS must leave the draw buffers untouched");
assertArrayEquals(new int[]{0}, fbo.drawBuffers);
assertNull(fbo.cachedColorFormats, "an ignored call must not reach the pipeline color formats");
}
}
@@ -0,0 +1,37 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import com.sun.management.ThreadMXBean;
import java.lang.management.ManagementFactory;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public final class SdlAsserts {
private static final int WARMUP_ROUNDS = 50_000;
private static final int MEASURED_ROUNDS = 100_000;
private static final long ALLOCATION_BUDGET_BYTES = 256L * 1024L;
private SdlAsserts() {}
public static void assertBlendFactors(PipelineCache p, int drawBuffer, int sc, int dc, int sa, int da, String when) {
assertEquals(sc, p.srcColorFactors[drawBuffer], when + ": srcColor of draw buffer " + drawBuffer);
assertEquals(dc, p.dstColorFactors[drawBuffer], when + ": dstColor of draw buffer " + drawBuffer);
assertEquals(sa, p.srcAlphaFactors[drawBuffer], when + ": srcAlpha of draw buffer " + drawBuffer);
assertEquals(da, p.dstAlphaFactors[drawBuffer], when + ": dstAlpha of draw buffer " + drawBuffer);
}
public static void assertAllocationFree(Runnable round, String what) {
assumeTrue(ManagementFactory.getThreadMXBean() instanceof ThreadMXBean);
final ThreadMXBean bean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
assumeTrue(bean.isThreadAllocatedMemorySupported() && bean.isThreadAllocatedMemoryEnabled());
for (int i = 0; i < WARMUP_ROUNDS; i++) round.run();
final long before = bean.getCurrentThreadAllocatedBytes();
for (int i = 0; i < MEASURED_ROUNDS; i++) round.run();
final long allocated = bean.getCurrentThreadAllocatedBytes() - before;
assertTrue(allocated < ALLOCATION_BUDGET_BYTES, allocated + " bytes allocated across " + MEASURED_ROUNDS + " rounds of " + what);
}
}
@@ -0,0 +1,153 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.system.MemoryUtil;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class UniformStoreTest {
private static final int VEC4 = 0;
private static final int OUT_OF_RANGE = 7;
private static final float[] VALUES = {5f, 6f, 7f, 8f};
private static SDLGPURenderBackend backend;
private ContextState st;
private ShaderManager.ProgramObject prog;
private ShaderManager.ProgramObject savedProgramObj;
private int savedProgram;
private IntBuffer ints;
@BeforeAll
static void createBackend() {
backend = new SDLGPURenderBackend();
}
@BeforeEach
void setUp() {
st = SdlTestRig.contextState();
savedProgramObj = st.boundProgramObj;
savedProgram = st.boundProgram;
prog = new ShaderManager.ProgramObject();
prog.linked = true;
prog.vertexUboSize = 16;
prog.nextUniformLocation = 1;
prog.vertexMemberInfo.put(VEC4, new ShaderManager.UniformMemberInfo(0, 16, 0, true, 4, 1, 0, 1));
prog.buildUniformSlotArrays();
st.boundProgramObj = prog;
st.boundProgram = 1;
ints = MemoryUtil.memAllocInt(4);
ints.put(0, 1).put(1, 2).put(2, 3).put(3, 4);
}
@AfterEach
void tearDown() {
MemoryUtil.memFree(ints);
st.releaseUniformStaging(prog);
st.boundProgramObj = savedProgramObj;
st.boundProgram = savedProgram;
}
private void writeAll(int location) {
backend.uniform4f(location, 1f, 2f, 3f, 4f);
backend.uniform4fv(location, VALUES);
backend.uniform4i(location, 1, 2, 3, 4);
backend.uniform4iv(location, ints);
}
private void dropRound() {
st.boundProgramObj = null;
writeAll(VEC4);
st.boundProgramObj = prog;
writeAll(OUT_OF_RANGE);
}
private void assertNothingStaged() {
final UniformStaging us = st.uniformStaging(prog);
assertNull(us.uniformDataBySlot[VEC4]);
assertEquals(0L, us.uniformValueHashBySlot[VEC4]);
assertFalse(us.vsUniformDirty);
}
private float[] staged(int location) {
return st.uniformStaging(prog).uniformDataBySlot[location];
}
@Test
void noProgramBoundStoresNothing() {
st.boundProgramObj = null;
assertDoesNotThrow(() -> writeAll(VEC4));
st.boundProgramObj = prog;
assertNothingStaged();
}
@Test
void outOfRangeLocationStoresNothing() {
assertEquals(1, prog.uniformSlotCount);
assertDoesNotThrow(() -> writeAll(OUT_OF_RANGE));
assertNothingStaged();
}
@Test
void droppedStoresDoNotAllocate() {
SdlAsserts.assertAllocationFree(this::dropRound, "dropped uniform stores");
assertNothingStaged();
}
@Test
void boundProgramWriteStillStores() {
backend.uniform4f(VEC4, 1f, 2f, 3f, 4f);
assertArrayEquals(new float[]{1f, 2f, 3f, 4f}, staged(VEC4));
backend.uniform4fv(VEC4, VALUES);
assertArrayEquals(VALUES, staged(VEC4));
backend.uniform4iv(VEC4, ints);
final float[] stagedValues = staged(VEC4);
for (int i = 0; i < 4; i++) {
assertEquals(i + 1, Float.floatToRawIntBits(stagedValues[i]));
}
assertTrue(st.uniformStaging(prog).vsUniformDirty);
}
@Test
void vectorWriteCopiesTheCallerArrayAndReusesTheSlot() {
final float[] first = {5f, 6f, 7f, 8f};
backend.uniform4fv(VEC4, first);
final float[] slot = staged(VEC4);
assertNotSame(first, slot, "the staging must not hold the caller's array");
assertArrayEquals(new float[]{5f, 6f, 7f, 8f}, slot);
final float[] second = {9f, 10f, 11f, 12f};
backend.uniform4fv(VEC4, second);
assertSame(slot, staged(VEC4), "same-length writes must reuse the slot array");
assertNotSame(second, staged(VEC4));
assertArrayEquals(new float[]{9f, 10f, 11f, 12f}, slot);
final float[] equalValues = second.clone();
backend.uniform4fv(VEC4, equalValues);
assertSame(slot, staged(VEC4), "an equal-valued caller array must not replace the slot array");
backend.uniform4f(VEC4, 1f, 2f, 3f, 4f);
assertArrayEquals(new float[]{5f, 6f, 7f, 8f}, first);
assertArrayEquals(new float[]{9f, 10f, 11f, 12f}, second);
assertArrayEquals(new float[]{9f, 10f, 11f, 12f}, equalValues);
assertArrayEquals(new float[]{1f, 2f, 3f, 4f}, slot);
}
}
@@ -11,6 +11,7 @@ import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
@@ -85,6 +86,12 @@ public final class GlsmSdlHeadlessRig {
endFrame();
}
public static int glsmBufferBinding(int target) {
final Object ctx = Reflect.getStatic(GLStateManager.class, "primaryContext");
final Int2IntMap bound = Reflect.get(ctx, "boundOtherBuffers");
return bound.get(target);
}
private static int colorTargetSdlFormat() {
final ResourceManager rm = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
final ResourceManager.TextureMeta meta = rm.getTextureMeta(colorTexture);
@@ -0,0 +1,59 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.SdlAsserts;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_ONE;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_SRC_ALPHA;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlIndexedBlendRestoreTest {
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
private static PipelineCache pipeline() {
final ContextState st = Reflect.invokeStatic(SDLGPURenderBackend.class, "s", new Class<?>[0]);
return st.pipeline;
}
private static void vanillaBlendFunc() {
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
}
@Test
void vanillaBlendFuncAfterIndexedOverrideReachesEveryDrawBuffer() {
try {
vanillaBlendFunc();
RenderSystem.blendFuncSeparatei(1, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
final PipelineCache p = pipeline();
SdlAsserts.assertBlendFactors(p, 1, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, "override");
SdlAsserts.assertBlendFactors(p, 0, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, "override");
vanillaBlendFunc();
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
SdlAsserts.assertBlendFactors(p, i, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, "restore");
}
assertFalse(GLStateManager.getBlendState().isFuncUnknown(), "restore forward must clear the unknown flag");
} finally {
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
}
}
}
@@ -0,0 +1,106 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPULWJGLService;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlLWJGLServiceRoutingTest {
private static final int UBO_INDEX = 3;
private static SDLGPULWJGLService svc;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
svc = SDLGPULWJGLService.create();
}
private static Object glsmContext() {
return Reflect.getStatic(GLStateManager.class, "primaryContext");
}
private static int glsmUnpackAlignment() {
final PixelUnpackState state = Reflect.get(glsmContext(), "pixelUnpackState");
return state.alignment();
}
@Test
void deleteBuffersClearsTheGlsmArrayBufferBinding() {
final int buffer = svc.glGenBuffers();
svc.glBindBuffer(GL15.GL_ARRAY_BUFFER, buffer);
svc.glBufferData(GL15.GL_ARRAY_BUFFER, 64L, GL15.GL_STATIC_DRAW);
assertEquals(buffer, GLStateManager.getBoundVBO());
svc.glDeleteBuffers(buffer);
assertEquals(0, GLStateManager.getBoundVBO());
}
@Test
void bindBufferBaseUpdatesTheGlsmGenericBinding() {
final int buffer = svc.glGenBuffers();
svc.glBindBuffer(GL31.GL_UNIFORM_BUFFER, buffer);
svc.glBufferData(GL31.GL_UNIFORM_BUFFER, 64L, GL15.GL_DYNAMIC_DRAW);
svc.glBindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
assertEquals(0, GlsmSdlHeadlessRig.glsmBufferBinding(GL31.GL_UNIFORM_BUFFER));
try {
svc.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, UBO_INDEX, buffer);
assertEquals(buffer, GlsmSdlHeadlessRig.glsmBufferBinding(GL31.GL_UNIFORM_BUFFER));
} finally {
svc.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, UBO_INDEX, 0);
svc.glDeleteBuffers(buffer);
}
}
@Test
void clearColorUpdatesTheGlsmCache() {
svc.glClearColor(0.25f, 0.5f, 0.75f, 1.0f);
assertEquals(0.25f, GLStateManager.getClearColor().getRed());
assertEquals(0.5f, GLStateManager.getClearColor().getGreen());
assertEquals(0.75f, GLStateManager.getClearColor().getBlue());
}
@Test
void pixelStoreiUpdatesTheGlsmUnpackState() {
assertNotEquals(1, glsmUnpackAlignment());
try {
svc.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
assertEquals(1, glsmUnpackAlignment());
} finally {
svc.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
}
assertEquals(4, glsmUnpackAlignment());
}
@Test
void deleteFramebuffersClearsTheGlsmDrawFramebuffer() {
final int framebuffer = svc.glGenFramebuffers();
try {
svc.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
assertEquals(framebuffer, GLStateManager.getDrawFramebuffer());
svc.glDeleteFramebuffers(framebuffer);
assertEquals(0, GLStateManager.getDrawFramebuffer());
} finally {
GlsmSdlHeadlessRig.bindTarget();
}
}
}
@@ -0,0 +1,63 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlSamplerBindingTest {
private static final int UNIT = 3;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
@Test
void glsmDeleteClearsBothCaches() {
final ContextState st = SdlTestRig.contextState();
final int sampler = GLStateManager.glGenSamplers();
GLStateManager.glBindSampler(UNIT, sampler);
assertEquals(sampler, st.boundSamplerObjects[UNIT], "SDL unit binding");
assertEquals(sampler, GLStateManager.getSamplerBinding(UNIT), "GLSM unit binding");
GLStateManager.glDeleteSamplers(sampler);
assertEquals(0, st.boundSamplerObjects[UNIT], "SDL must drop the deleted sampler");
assertEquals(0, GLStateManager.getSamplerBinding(UNIT), "GLSM must drop the deleted sampler");
}
@Test
void renderSystemRebindAfterGlsmUnbindReachesSdl() {
assertTrue(RenderSystem.supportsSamplerObjects(), "RenderSystem sampler calls would no-op and prove nothing");
final ContextState st = SdlTestRig.contextState();
final int sampler = RenderSystem.genSampler();
try {
RenderSystem.bindSamplerToUnit(UNIT, sampler);
GLStateManager.glBindSampler(UNIT, 0);
assertEquals(0, st.boundSamplerObjects[UNIT], "setup: GLSM unbind must reach SDL");
RenderSystem.bindSamplerToUnit(UNIT, sampler);
assertEquals(sampler, st.boundSamplerObjects[UNIT], "RenderSystem skipped a bind SDL no longer holds");
} finally {
RenderSystem.destroySampler(sampler);
}
}
@Test
void unitCountMatchesTheSdlSamplerArray() {
GLStateManager.glBindSampler(0, 0);
assertEquals(SdlTestRig.contextState().boundSamplerObjects.length, GLStateManager.getSamplerUnitCount());
}
}
@@ -0,0 +1,48 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import it.unimi.dsi.fastutil.ints.IntSet;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL15;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlStreamingRingTest {
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
@Test
void ringCreationLeavesTheGlsmArrayBufferCacheMatchingSdl() {
assertTrue(RenderSystem.supportsBufferStorage(), "SDL-GPU must advertise buffer storage");
final int other = GLStateManager.glGenBuffers();
PersistentStreamingBuffer ring = null;
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, other);
ring = PersistentStreamingBuffer.createOrNull(4096);
assertNotNull(ring);
final IntSet persistent = Reflect.getStatic(FfpExtendedAttribs.class, "persistentSources");
assertTrue(persistent.contains(ring.getBufferId()), "the ring buffer is the persistent source");
assertFalse(persistent.contains(other), "the previously bound buffer is not persistent");
assertEquals(RENDER_BACKEND.getInteger(GL15.GL_ARRAY_BUFFER_BINDING), GLStateManager.getBoundVBO(), "SDL ContextState.boundArrayBuffer must equal the GLSM cache after ring creation");
} finally {
if (ring != null) ring.destroy();
GLStateManager.glDeleteBuffers(other);
}
}
}
@@ -0,0 +1,91 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL43;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlVoxelRegionBindTest {
private static final int BOUND_SLOT = 5;
private static final int EMPTY_SLOT = 6;
private static final int UNLINKED_PROGRAM = Integer.MAX_VALUE;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
private static int glsmGenericSsbo() {
return GlsmSdlHeadlessRig.glsmBufferBinding(GL43.GL_SHADER_STORAGE_BUFFER);
}
private static void unbind(int slot) {
GLStateManager.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, slot, 0);
}
@Test
void glsmBindBufferBaseReachesBothCaches() {
final ContextState st = SdlTestRig.contextState();
final int buf = GLStateManager.glGenBuffers();
try {
GLStateManager.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, BOUND_SLOT, buf);
assertEquals(buf, st.boundSsboByIndex[BOUND_SLOT], "SDL indexed SSBO slot");
assertEquals(buf, glsmGenericSsbo(), "GLSM generic SSBO binding");
} finally {
unbind(BOUND_SLOT);
GLStateManager.glDeleteBuffers(buf);
}
}
@Test
void regionHookRefusesWithoutProgramOrBoundSlotAndNeverRebinds() {
final ContextState st = SdlTestRig.contextState();
final int savedProgram = st.boundProgram;
final int buf = GLStateManager.glGenBuffers();
try {
unbind(EMPTY_SLOT);
GLStateManager.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, BOUND_SLOT, buf);
st.boundProgram = 0;
assertFalse(RENDER_BACKEND.bindVoxelizationRegion(BOUND_SLOT, 0L, 0f, 0f, 0f), "no program bound");
assertEquals(buf, st.boundSsboByIndex[BOUND_SLOT], "refusal must leave the slot alone");
st.boundProgram = UNLINKED_PROGRAM;
assertFalse(RENDER_BACKEND.bindVoxelizationRegion(EMPTY_SLOT, 0L, 0f, 0f, 0f), "slot has no buffer");
assertEquals(0, st.boundSsboByIndex[EMPTY_SLOT], "the hook must not bind a buffer itself");
assertTrue(RENDER_BACKEND.bindVoxelizationRegion(BOUND_SLOT, 0L, 1f, 2f, 3f), "a GLSM-bound slot with a program must be accepted, or the refusals prove nothing");
assertEquals(buf, st.boundSsboByIndex[BOUND_SLOT]);
assertEquals(buf, glsmGenericSsbo(), "GLSM and SDL must still agree after the hook");
} finally {
st.boundProgram = savedProgram;
unbind(BOUND_SLOT);
GLStateManager.glDeleteBuffers(buf);
}
}
@Test
void deletingTheRegionBufferClearsTheGlsmGenericBinding() {
final int buf = GLStateManager.glGenBuffers();
try {
GLStateManager.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, BOUND_SLOT, buf);
assertEquals(buf, glsmGenericSsbo());
GLStateManager.glDeleteBuffers(buf);
assertEquals(0, glsmGenericSsbo(), "delete must see the tracked SSBO binding");
} finally {
unbind(BOUND_SLOT);
}
}
}
@@ -0,0 +1,79 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.SdlAsserts;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_ONE;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_SRC_ALPHA;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_BLENDFACTOR_ZERO;
class IndexedBlendStateTest {
private SDLGPURenderBackend backend;
private ContextState st;
@BeforeEach
void resetBlend() {
backend = new SDLGPURenderBackend();
st = SdlTestRig.contextState();
backend.blendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
backend.disable(GL11.GL_BLEND);
}
@AfterEach
void restoreBlend() {
backend.blendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
backend.disable(GL11.GL_BLEND);
}
@Test
void indexedBlendFuncChangesOnlyThatDrawBuffer() {
backend.blendFuncSeparatei(1, GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE);
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
if (i == 1) {
SdlAsserts.assertBlendFactors(st.pipeline, i, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, "indexed override");
} else {
SdlAsserts.assertBlendFactors(st.pipeline, i, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, "indexed override");
}
}
}
@Test
void globalBlendFuncSetsEveryDrawBuffer() {
backend.blendFuncSeparatei(2, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
backend.blendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
SdlAsserts.assertBlendFactors(st.pipeline, i, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, "global set");
}
}
@Test
void indexedEnableSetsOnlyThatDrawBuffer() {
backend.enablei(GL11.GL_BLEND, 2);
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
assertEquals(i == 2, st.pipeline.blendEnabledPerDrawBuffer[i], "blend enable of draw buffer " + i);
}
}
@Test
void outOfRangeIndexedBlendFuncIsIgnored() {
final int pastEnd = ContextState.MAX_COLOR_ATTACHMENTS;
backend.blendFuncSeparatei(pastEnd, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
backend.blendFuncSeparatei(-1, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE);
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
SdlAsserts.assertBlendFactors(st.pipeline, i, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, "out of range ignored");
}
}
}
@@ -8,6 +8,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
import static org.lwjgl.sdl.SDLGPU.*;
@@ -45,7 +46,7 @@ class PipelineCacheKeyTest {
void testBlendChangeProducesDifferentKey() {
final PipelineCache a = createCache();
final PipelineCache b = createCache();
b.blendEnabledPerAttachment[0] = true;
b.blendEnabledPerDrawBuffer[0] = true;
assertNotEquals(computeKey(a), computeKey(b), "Blend state change should produce different key");
}
@@ -105,12 +106,75 @@ class PipelineCacheKeyTest {
void testBlendFactorChangeProducesDifferentKey() {
final PipelineCache a = createCache();
final PipelineCache b = createCache();
b.srcColorFactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
b.dstColorFactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
b.setBlendFactors(SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
assertNotEquals(computeKey(a), computeKey(b), "Blend factor change should produce different key");
}
@Test
void perDrawBufferFactorOnLiveSlotChangesKey() {
final PipelineCache a = createCache();
final PipelineCache b = createCache();
b.setBlendFactors(0, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
assertNotEquals(computeKey(a), computeKey(b), "factor on the only color target must change the key");
}
@Test
void perDrawBufferFactorOnUnusedSlotKeepsKey() {
final PipelineCache a = createCache();
final PipelineCache b = createCache();
b.setBlendFactors(1, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
assertEquals(computeKey(a), computeKey(b), "factor on a draw buffer with no color target must keep the key");
}
@Test
void globalFactorsAfterIndexedSetMatchFreshCache() {
final PipelineCache fresh = createCache();
final PipelineCache b = createCache();
b.setBlendFactors(0, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE);
b.setBlendFactors(3, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE);
b.setBlendFactors(SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
final int[] fourTargets = new int[4];
Arrays.fill(fourTargets, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM);
fresh.setColorTargetFormats(fourTargets);
b.setColorTargetFormats(fourTargets);
assertEquals(computeKey(fresh), computeKey(b), "a global blend func must overwrite every indexed entry");
}
@Test
void blendFactorSettersReportNoChangeForIdenticalValues() {
final PipelineCache c = createCache();
assertFalse(c.setBlendFactors(SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO));
assertFalse(c.setBlendFactors(2, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO));
assertTrue(c.setBlendFactors(2, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO));
assertFalse(c.setBlendFactors(2, SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO));
assertTrue(c.setBlendFactors(SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO));
}
@Test
void blendEnableIsIndexedByDrawBufferNotAttachment() {
final int[] twoTargets = {SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT};
final PipelineCache base = createCache();
base.setColorTargetFormats(twoTargets);
base.setDrawBuffers(new int[]{3, 5});
final PipelineCache slot0 = createCache();
slot0.setColorTargetFormats(twoTargets);
slot0.setDrawBuffers(new int[]{3, 5});
slot0.blendEnabledPerDrawBuffer[0] = true;
final PipelineCache attachment3 = createCache();
attachment3.setColorTargetFormats(twoTargets);
attachment3.setDrawBuffers(new int[]{3, 5});
attachment3.blendEnabledPerDrawBuffer[3] = true;
assertNotEquals(computeKey(base), computeKey(slot0), "draw buffer 0 is the first color target");
assertEquals(computeKey(base), computeKey(attachment3), "index 3 is a draw buffer past the two targets");
}
@Test
void testColorWriteMaskChangeProducesDifferentKey() {
final PipelineCache a = createCache();
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -454,6 +455,47 @@ class PipelineCacheVertexInputTest {
assertFalse(c.markInputDirtyIfLivenessChanged(store, 42, 7), "swapping two live buffers must not force an input rehash");
}
@Test
void programInputSignatureChangeInvalidatesInputHash() {
final int[] vecSizesA = new int[ContextState.MAX_VERTEX_ATTRIBS];
vecSizesA[0] = 3;
final int[] baseTypesA = new int[ContextState.MAX_VERTEX_ATTRIBS];
baseTypesA[0] = Spvc.SPVC_BASETYPE_FP32;
final String[] namesA = new String[ContextState.MAX_VERTEX_ATTRIBS];
namesA[0] = "a_Pos";
final PipelineCache c = cache(0x1, vecSizesA, baseTypesA);
c.shaderInputName = namesA;
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 12);
final long keyA = Reflect.invoke(c, "currentKey", new Class<?>[] { PipelineStore.class, ContextState.class }, store, cs);
final boolean dirtyAfterKeyA = Reflect.get(c, "inputDirty");
assertFalse(dirtyAfterKeyA, "currentKey must clear the dirty flag once recomputed");
c.setVertexInputs(0x1, vecSizesA, baseTypesA, namesA);
final boolean dirtyAfterSameSignature = Reflect.get(c, "inputDirty");
assertFalse(dirtyAfterSameSignature, "an identical signature must not force a rehash");
c.setVertexInputs(0x1, vecSizesA.clone(), baseTypesA.clone(), namesA.clone());
final boolean dirtyAfterEqualCopies = Reflect.get(c, "inputDirty");
assertFalse(dirtyAfterEqualCopies, "another program with an equal signature must not force a rehash");
final int[] vecSizesB = new int[ContextState.MAX_VERTEX_ATTRIBS];
vecSizesB[0] = 3; vecSizesB[1] = 4;
final int[] baseTypesB = new int[ContextState.MAX_VERTEX_ATTRIBS];
baseTypesB[0] = Spvc.SPVC_BASETYPE_FP32; baseTypesB[1] = Spvc.SPVC_BASETYPE_FP32;
final String[] namesB = new String[ContextState.MAX_VERTEX_ATTRIBS];
namesB[0] = "a_Pos"; namesB[1] = "a_Extra";
c.setVertexInputs(0x3, vecSizesB, baseTypesB, namesB);
final boolean dirtyAfterChangedSignature = Reflect.get(c, "inputDirty");
assertTrue(dirtyAfterChangedSignature, "a changed vertex-input signature must invalidate the cached input hash");
final long keyB = Reflect.invoke(c, "currentKey", new Class<?>[] { PipelineStore.class, ContextState.class }, store, cs);
assertTrue(keyA != keyB, "currentKey must reflect the new signature, not a stale cached input hash");
}
@Test
void bindingsArrayIsDenseIndexedBySlot() {
// D3D12 requirement: SDL's D3D12 backend reads vertex_buffer_descriptions[buffer_slot] as a flat array, so bindings.get(i).slot() must equal i
@@ -1,16 +1,22 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.SdlAsserts;
import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
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 ReadbackShadowsTest {
private static final long SENTINEL_HANDLE = 0xBEEFL;
private static final int SLOTS_PER_BUFFER = 32;
private static final int SLOT_STRIDE = 64;
private static ReadbackShadows shadows() {
return new ReadbackShadows(null);
@@ -62,4 +68,39 @@ class ReadbackShadowsTest {
assertFalse(shadows.hasSlotsFor(7), "deleting a buffer must drop all of its ring slots");
assertTrue(shadows.hasSlotsFor(8), "deleting one buffer must not drop another's slots");
}
private static int slotCount(ReadbackShadows shadows) {
return Reflect.<Map<Long, Object>>get(shadows, "slots").size();
}
private static ReadbackShadows ringShadows(int... glIds) {
final ReadbackShadows shadows = shadows();
for (int glId : glIds) {
for (int i = 0; i < SLOTS_PER_BUFFER; i++) {
SdlReflect.putReadbackSlot(shadows, glId, (long) i * SLOT_STRIDE, 0L, SLOT_STRIDE, true);
}
}
return shadows;
}
@Test
void releaseRemovesExactlyTheSlotsOfItsBuffer() {
final ReadbackShadows shadows = ringShadows(1, 2, 3);
shadows.release(2);
assertFalse(shadows.hasSlotsFor(2));
assertEquals(2 * SLOTS_PER_BUFFER, slotCount(shadows));
for (int glId = 1; glId <= 3; glId += 2) {
for (int i = 0; i < SLOTS_PER_BUFFER; i++) {
assertTrue(SdlReflect.readbackSlotValid(shadows, glId, (long) i * SLOT_STRIDE));
}
}
}
@Test
void releaseWithNoSlotsDoesNotAllocate() {
final ReadbackShadows shadows = shadows();
SdlAsserts.assertAllocationFree(() -> shadows.release(7), "releases with no readback slots");
}
}
@@ -10,6 +10,7 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -118,13 +119,10 @@ class SDLGPUUniformDedupTest {
}
@Test
void noProgramBound_reuseOrAllocReturnsFresh_putIsNoOp() {
void noProgramBound_reuseOrAllocReturnsNull_putIsNoOp() {
st.boundProgramObj = null;
final float[] a = applier.reuseOrAlloc(st, 0, 4);
assertEquals(4, a.length);
final float[] b = applier.reuseOrAlloc(st, 0, 4);
assertNotSame(a, b, "no caching without bound program");
applier.putUniform(st, 0, a);
assertNull(applier.reuseOrAlloc(st, 0, 4), "no program means nowhere to store");
applier.putUniform(st, 0, new float[4]);
}
@Test
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.rendering.voxelization;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import net.coderbot.iris.pipeline.transform.RwImageStoreExtractor;
@@ -8,6 +9,7 @@ import org.embeddedt.embeddium.impl.gl.buffer.GlBuffer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
import org.lwjgl.opengl.GL43;
public final class SdlShadowVoxelizationSink implements ShadowVoxelizer.Sink {
@@ -47,7 +49,8 @@ public final class SdlShadowVoxelizationSink implements ShadowVoxelizer.Sink {
@Override
public boolean range(int vertexOffset, int vertexCount) {
if (pendingVertexBuffer != 0) {
if (!sdl().bindVoxelizationRegion(SSBO_BINDING, pendingVertexBuffer, pass, pendingX, pendingY, pendingZ)) {
GLStateManager.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, SSBO_BINDING, pendingVertexBuffer);
if (!sdl().bindVoxelizationRegion(SSBO_BINDING, pass, pendingX, pendingY, pendingZ)) {
if (!warnedBindFailure) {
warnedBindFailure = true;
LOG.warn("shadow voxelization: could not bind region vertex buffer {} at SSBO slot {}", pendingVertexBuffer, SSBO_BINDING);
@@ -85,11 +85,15 @@ public class BlendModeStorage {
}
public static void deferBlendModeToggle(boolean enabled) {
if (enabled == vanillaBlendEnable) return;
vanillaBlendEnable = enabled;
hasDeferredChanges = true;
}
public static void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
if (vanillaBlend.getSrcRgb() == srcRgb && vanillaBlend.getDstRgb() == dstRgb && vanillaBlend.getSrcAlpha() == srcAlpha && vanillaBlend.getDstAlpha() == dstAlpha) {
return;
}
vanillaBlend.setAll(srcRgb, dstRgb, srcAlpha, dstAlpha);
hasDeferredChanges = true;
}
@@ -0,0 +1,57 @@
package net.coderbot.iris.gl.blending;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
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 BlendModeStorageDeferDedupeTest {
@BeforeEach
@AfterEach
void resetStorage() {
BlendModeStorage.restoreBlend();
Reflect.setStatic(BlendModeStorage.class, "hasDeferredChanges", false);
Reflect.setStatic(BlendModeStorage.class, "vanillaBlendEnable", false);
vanillaBlend().setAll(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
}
private static BlendState vanillaBlend() {
return Reflect.getStatic(BlendModeStorage.class, "vanillaBlend");
}
private static boolean hasDeferredChanges() {
return Reflect.<Boolean>getStatic(BlendModeStorage.class, "hasDeferredChanges");
}
@Test
void blendFuncDefersOnlyOnChange() {
BlendModeStorage.deferBlendFunc(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
assertFalse(hasDeferredChanges());
BlendModeStorage.deferBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
assertTrue(hasDeferredChanges());
assertEquals(GL11.GL_SRC_ALPHA, vanillaBlend().getSrcRgb());
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, vanillaBlend().getDstRgb());
BlendModeStorage.deferBlendFunc(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
assertTrue(hasDeferredChanges());
assertEquals(GL11.GL_ONE, vanillaBlend().getSrcRgb());
}
@Test
void toggleDefersOnlyOnChange() {
BlendModeStorage.deferBlendModeToggle(false);
assertFalse(hasDeferredChanges());
BlendModeStorage.deferBlendModeToggle(true);
assertTrue(hasDeferredChanges());
assertTrue(Reflect.<Boolean>getStatic(BlendModeStorage.class, "vanillaBlendEnable"));
}
}