Stop stalls on some GPUs when closing the questbook (#2286)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-10-22 00:24:31 -07:00
1 parent 73d61d385c
commit e84c8c8d7a
18 files changed
+1085 -96

No files matched your search

@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
@@ -257,6 +258,7 @@ public class GLContextState {
public boolean vanillaBlendEnabledBefore;
public int drawFramebuffer = 0;
public int readFramebuffer = 0;
public final Int2ObjectOpenHashMap<int[]> framebufferTextures = new Int2ObjectOpenHashMap<>();
public int texGenGeneration;
public int defaultVAO;
public final VAOManager vaos = new VAOManager();
@@ -75,8 +75,14 @@ import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
import com.gtnewhorizons.angelica.glsm.texture.TextureTargets;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2LongMap;
import it.unimi.dsi.fastutil.ints.Int2LongMaps;
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongIterator;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
@@ -106,6 +112,7 @@ import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.GLContext;
@@ -430,8 +437,24 @@ public class GLStateManager {
}
// Deferred texture deletion: names kept valid until glGenTextures recycles them.
// Emulates Mesa/compat-profile behavior where delete-then-bind doesn't error.
private static final IntOpenHashSet deferredDeleteTextures = new IntOpenHashSet();
private static final IntOpenHashSet unshrinkableTextures = new IntOpenHashSet();
private static final Int2LongOpenHashMap deferredDeleteTextures = new Int2LongOpenHashMap();
private static final long DEFERRED_ABSENT = Long.MIN_VALUE;
private static final long DEFERRED_ATTACHED = -1;
private static final long DEFERRED_RELEASED = -2;
private static final int RELEASE_DELAY_FRAMES = 3;
private static long frameIndex;
private static long nextReleaseFrame = Long.MAX_VALUE;
private static final Int2IntOpenHashMap non2DTextureTargets = new Int2IntOpenHashMap();
private static final Int2IntOpenHashMap textureFramebufferRefs = new Int2IntOpenHashMap();
private static final FloatBuffer TRANSPARENT_BLACK = BufferUtils.createFloatBuffer(4);
private static final Tracy.ZoneId Z_TEX_DELETE = Tracy.zoneId("texDelete", Tracy.COLOR_FFP);
private static final Tracy.ZoneId Z_TEX_RELEASE_STORAGE = Tracy.zoneId("texReleaseStorage", Tracy.COLOR_FFP);
private static final Tracy.ZoneId Z_TEX_RESCUE_RESET = Tracy.zoneId("texRescueReset", Tracy.COLOR_FFP);
private static final Tracy.ZoneId Z_TEX_FLUSH_DELETES = Tracy.zoneId("texFlushDeletes", Tracy.COLOR_FFP);
static {
deferredDeleteTextures.defaultReturnValue(DEFERRED_ABSENT);
non2DTextureTargets.defaultReturnValue(GL11.GL_TEXTURE_2D);
}
private static final IntOpenHashSet writeMappedBuffers = new IntOpenHashSet();
@Getter private static Vendor VENDOR;
@@ -2376,18 +2399,28 @@ public class GLStateManager {
}
}
// Rebinding a deferred-delete texture rescues it (Mesa/compat-profile behavior)
long rescued = DEFERRED_ABSENT;
boolean rejected = false;
if (texture != 0) {
final boolean locked = acquireDrawLock();
try {
deferredDeleteTextures.remove(texture);
if (target != GL11.GL_TEXTURE_2D) unshrinkableTextures.add(texture);
final long state = deferredDeleteTextures.isEmpty() ? DEFERRED_ABSENT : deferredDeleteTextures.get(texture);
if (state == DEFERRED_ABSENT) {
recordTarget(texture, target);
} else if (non2DTextureTargets.get(texture) == target) {
rescued = deferredDeleteTextures.remove(texture);
} else {
rejected = true;
}
} finally {
if (locked) releaseDrawLock();
}
}
if (rejected) return;
if (!isCachingEnabled()) {
RENDER_BACKEND.bindTexture(target, texture);
if (rescued != DEFERRED_ABSENT) resetRescuedTexture(target, texture, rescued);
return;
}
@@ -2413,6 +2446,54 @@ public class GLStateManager {
glCtx.lockBindCallback = false;
}
}
if (rescued != DEFERRED_ABSENT) resetRescuedTexture(target, texture, rescued);
}
private static void bindTextureUnitByName(int unit, int texture) {
if (isDeferredDelete(texture)) return;
final CommandRecorder paused = DisplayListManager.pauseRecording();
final int saved = getActiveTextureUnit();
try {
glActiveTexture(GL13.GL_TEXTURE0 + unit);
if (texture == 0) {
// Read before the 2D unbind, which overwrites the unit's cached target.
final int target = ctx().textures.getTextureUnitBindings(unit).getTarget();
glBindTexture(GL11.GL_TEXTURE_2D, 0);
if (!is2D(target)) glBindTexture(target, 0);
} else {
glBindTexture(non2DTextureTargets.get(texture), texture);
}
} finally {
glActiveTexture(GL13.GL_TEXTURE0 + saved);
if (paused != null) DisplayListManager.resumeRecording(paused);
}
}
public static void glBindTextures(int first, int count, IntBuffer textures) {
if (first < 0 || !isValidTextureUnit(first + count - 1, GL13.GL_TEXTURE0 + first + count - 1, "glBindTextures")) return;
if (textures != null) requireRemaining(textures, count);
for (int i = 0; i < count; i++) bindTextureUnitByName(first + i, textures == null ? 0 : textures.get(textures.position() + i));
}
public static void glBindTextures(int first, IntBuffer textures) {
if (textures != null) glBindTextures(first, textures.remaining(), textures);
}
public static void glBindTextures(int first, int[] textures) {
if (textures != null) glBindTextures(first, textures.length, IntBuffer.wrap(textures));
}
public static void glBindTextureUnit(int unit, int texture) {
if (isValidTextureUnit(unit, GL13.GL_TEXTURE0 + unit, "glBindTextureUnit")) bindTextureUnitByName(unit, texture);
}
public static void glBindMultiTextureEXT(int texunit, int target, int texture) {
if (DisplayListManager.isRecording()) throw DisplayListManager.unsupportedInList("glBindMultiTextureEXT");
if (!isValidTextureUnit(texunit - GL13.GL_TEXTURE0, texunit, "glBindMultiTextureEXT")) return;
final int saved = getActiveTextureUnit();
glActiveTexture(texunit);
glBindTexture(target, texture);
glActiveTexture(GL13.GL_TEXTURE0 + saved);
}
private static int changeFormatIfDeprecated(int internalformat) {
@@ -2620,15 +2701,26 @@ public class GLStateManager {
}
public static void glDeleteTextures(int id) {
deferDeleteTexture(id);
onDeleteTexture(id);
deleteTexture(id);
}
public static void glDeleteTextures(IntBuffer ids) {
for (int i = 0; i < ids.remaining(); i++) {
final int id = ids.get(ids.position() + i);
deleteTexture(ids.get(ids.position() + i));
}
}
public static void glDeleteTextures(int[] ids) {
for (int id : ids) deleteTexture(id);
}
private static void deleteTexture(int id) {
if (Tracy.ENABLED) Tracy.beginZone(Z_TEX_DELETE);
try {
deferDeleteTexture(id);
onDeleteTexture(id);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
}
@@ -2637,26 +2729,21 @@ public class GLStateManager {
* Why you might ask? In compat profile if you bind a texture that doesn't exist, ie a deleted texture,
* mesa will silently create it for you, in core this is an error. Why would you be binding a deleted texture?
* Damned if I know, but vanilla and mods do it... they create one, delete it, and then bind it....
*
* So... we shrink the texture to 1x1 to free GPU memory, unbind from all units, but keep the name valid.
* */
private static void deferDeleteTexture(int id) {
final GLContextState glCtx = ctx();
if (id == 0) return;
final int savedUnit = glCtx.activeTextureUnit.getValue();
final TextureBindingStack savedUnitBindings = glCtx.textures.getTextureUnitBindings(savedUnit);
final int savedBinding = savedUnitBindings.getBinding();
final int savedTarget = savedUnitBindings.getTarget();
boolean changedUnit = false;
int activeUnit = savedUnit;
// Unbind from all units that have this texture
for (int i = 0; i <= glCtx.maxBoundTextureUnit; i++) {
final TextureBindingStack unitBindings = glCtx.textures.getTextureUnitBindings(i);
if (unitBindings.getBinding() == id) {
if (i != savedUnit) {
if (i != activeUnit) {
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + i);
changedUnit = true;
activeUnit = i;
}
final int unitTarget = unitBindings.getTarget();
RENDER_BACKEND.bindTexture(unitTarget == 0 ? GL11.GL_TEXTURE_2D : unitTarget, 0);
@@ -2670,48 +2757,245 @@ public class GLStateManager {
}
}
// Restore active unit, then shrink the texture via a temporary bind
if (changedUnit) {
if (activeUnit != savedUnit) {
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + savedUnit);
}
final boolean shrinkable;
final TextureBindingStack savedBindings = glCtx.textures.getTextureUnitBindings(savedUnit);
if (!is2D(savedBindings.getTarget()) && previous2D(savedBindings) == id) restoreUnitAfter2DBind(savedBindings, id, id);
final boolean locked = acquireDrawLock();
try {
shrinkable = !unshrinkableTextures.contains(id);
deferredDeleteTextures.add(id);
detachFromFramebuffer(glCtx, GL30.GL_DRAW_FRAMEBUFFER, glCtx.drawFramebuffer, id);
if (glCtx.readFramebuffer != glCtx.drawFramebuffer) detachFromFramebuffer(glCtx, GL30.GL_READ_FRAMEBUFFER, glCtx.readFramebuffer, id);
if (textureFramebufferRefs.get(id) > 0) deferredDeleteTextures.put(id, DEFERRED_ATTACHED);
else scheduleRelease(id);
} finally {
if (locked) releaseDrawLock();
}
if (!shrinkable) return;
}
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, id);
RENDER_BACKEND.texParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
private static void scheduleRelease(int id) {
deferredDeleteTextures.put(id, frameIndex);
nextReleaseFrame = Math.min(nextReleaseFrame, frameIndex + RELEASE_DELAY_FRAMES);
}
private static boolean isDeferredDelete(int texture) {
if (texture == 0 || deferredDeleteTextures.isEmpty()) return false;
final boolean locked = acquireDrawLock();
try {
return deferredDeleteTextures.containsKey(texture);
} finally {
if (locked) releaseDrawLock();
}
}
public static void onFrameEnd() {
frameIndex++;
if (!splashComplete || frameIndex < nextReleaseFrame) return;
nextReleaseFrame = Long.MAX_VALUE;
for (Int2LongMap.Entry entry : Int2LongMaps.fastIterable(deferredDeleteTextures)) {
final long since = entry.getLongValue();
if (since < 0) continue;
if (frameIndex - since < RELEASE_DELAY_FRAMES) {
nextReleaseFrame = Math.min(nextReleaseFrame, since + RELEASE_DELAY_FRAMES);
continue;
}
final int id = entry.getIntKey();
if (non2DTextureTargets.get(id) == GL11.GL_TEXTURE_2D) releaseStorage2D(id);
entry.setValue(DEFERRED_RELEASED);
}
}
public static int deferredTexturesAwaitingRelease() {
int count = 0;
final LongIterator it = deferredDeleteTextures.values().iterator();
while (it.hasNext()) {
if (it.nextLong() != DEFERRED_RELEASED) count++;
}
return count;
}
private static void releaseStorage2D(int id) {
if (Tracy.ENABLED) Tracy.beginZone(Z_TEX_RELEASE_STORAGE);
try {
final TextureBindingStack unit = ctx().textures.getTextureUnitBindings(getActiveTextureUnit());
final int previous2D = previous2D(unit);
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, id);
if (bindTook(GL11.GL_TEXTURE_2D, id)) releaseBoundStorage2D();
restoreUnitAfter2DBind(unit, id, previous2D);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
}
private static void restoreUnitAfter2DBind(TextureBindingStack unit, int deleted, int previous2D) {
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, previous2D == deleted ? 0 : previous2D);
final int target = unit.getTarget();
if (is2D(target)) return;
RENDER_BACKEND.bindTexture(target, unit.getBinding() == deleted ? 0 : unit.getBinding());
}
private static boolean is2D(int target) {
return target == 0 || target == GL11.GL_TEXTURE_2D;
}
private static int previous2D(TextureBindingStack unit) {
return is2D(unit.getTarget()) ? unit.getBinding() : RENDER_BACKEND.getInteger(GL11.GL_TEXTURE_BINDING_2D);
}
private static void recordTarget(int texture, int target) {
if (target != GL11.GL_TEXTURE_2D) non2DTextureTargets.put(texture, target);
}
private static void releaseBoundStorage2D() {
if (RenderSystem.supportsTextureStorage() && RENDER_BACKEND.getTexParameteri(GL11.GL_TEXTURE_2D, GL42.GL_TEXTURE_IMMUTABLE_FORMAT) != 0) return;
final int size = Math.max(RENDER_BACKEND.getTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH), RENDER_BACKEND.getTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT));
final int levels = Math.max(1, 32 - Integer.numberOfLeadingZeros(size));
suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_R8, 1, 1, 0, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
for (int level = 0; level < levels; level++) {
RENDER_BACKEND.texImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA8, 0, 0, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
}
restorePixelUnpackBuffer();
}
// Restore previous binding on active unit
final boolean savedIs2D = savedTarget == GL11.GL_TEXTURE_2D || savedTarget == 0;
RENDER_BACKEND.bindTexture(GL11.GL_TEXTURE_2D, savedBinding == id || !savedIs2D ? 0 : savedBinding);
private static boolean bindTook(int target, int texture) {
return RENDER_BACKEND.getInteger(TextureTargets.bindingQuery(target)) == texture;
}
private static void resetRescuedTexture(int target, int texture, long state) {
if (Tracy.ENABLED) Tracy.beginZone(Z_TEX_RESCUE_RESET);
try {
if (!bindTook(target, texture)) return;
if (target == GL11.GL_TEXTURE_2D && state >= 0) releaseBoundStorage2D();
if (target == GL31.GL_TEXTURE_BUFFER || target == GL32.GL_TEXTURE_2D_MULTISAMPLE || target == GL32.GL_TEXTURE_2D_MULTISAMPLE_ARRAY) return;
final boolean rectangle = target == GL31.GL_TEXTURE_RECTANGLE;
final int wrap = rectangle ? GL12.GL_CLAMP_TO_EDGE : GL11.GL_REPEAT;
RENDER_BACKEND.texParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, rectangle ? GL11.GL_LINEAR : GL11.GL_NEAREST_MIPMAP_LINEAR);
RENDER_BACKEND.texParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
RENDER_BACKEND.texParameteri(target, GL11.GL_TEXTURE_WRAP_S, wrap);
RENDER_BACKEND.texParameteri(target, GL11.GL_TEXTURE_WRAP_T, wrap);
RENDER_BACKEND.texParameteri(target, GL12.GL_TEXTURE_WRAP_R, wrap);
RENDER_BACKEND.texParameteri(target, GL12.GL_TEXTURE_BASE_LEVEL, 0);
if (!rectangle) RENDER_BACKEND.texParameteri(target, GL12.GL_TEXTURE_MAX_LEVEL, 1000);
RENDER_BACKEND.texParameterf(target, GL12.GL_TEXTURE_MIN_LOD, -1000.0f);
RENDER_BACKEND.texParameterf(target, GL12.GL_TEXTURE_MAX_LOD, 1000.0f);
RENDER_BACKEND.texParameteri(target, GL14.GL_TEXTURE_COMPARE_MODE, GL11.GL_NONE);
RENDER_BACKEND.texParameteri(target, GL14.GL_TEXTURE_COMPARE_FUNC, GL11.GL_LEQUAL);
RENDER_BACKEND.texParameteri(target, GL33.GL_TEXTURE_SWIZZLE_R, GL11.GL_RED);
RENDER_BACKEND.texParameteri(target, GL33.GL_TEXTURE_SWIZZLE_G, GL11.GL_GREEN);
RENDER_BACKEND.texParameteri(target, GL33.GL_TEXTURE_SWIZZLE_B, GL11.GL_BLUE);
RENDER_BACKEND.texParameteri(target, GL33.GL_TEXTURE_SWIZZLE_A, GL11.GL_ALPHA);
if (!RenderSystem.isGLES()) {
RENDER_BACKEND.texParameterf(target, GL14.GL_TEXTURE_LOD_BIAS, 0.0f);
RENDER_BACKEND.texParameterfv(target, GL11.GL_TEXTURE_BORDER_COLOR, TRANSPARENT_BLACK);
}
if (RenderSystem.supportsStencilTexturing()) RENDER_BACKEND.texParameteri(target, GL43.GL_DEPTH_STENCIL_TEXTURE_MODE, GL11.GL_DEPTH_COMPONENT);
if (RENDER_BACKEND.isAnisotropicSupported()) RENDER_BACKEND.texParameterf(target, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
}
/** Flush deferred deletes so the driver can recycle names. */
private static void flushDeferredTextureDeletes() {
if (!splashComplete || deferredDeleteTextures.isEmpty()) return;
final GLContextState primary = primaryContext();
final GLContextState[] drawables = drawableContextStates;
final var it = deferredDeleteTextures.iterator();
while (it.hasNext()) {
final int id = it.nextInt();
RENDER_BACKEND.deleteTextures(id);
unshrinkableTextures.remove(id);
clearTextureBindings(primary, id);
for (int i = 0; i < drawables.length; i++) {
clearTextureBindings(drawables[i], id);
if (Tracy.ENABLED) Tracy.beginZone(Z_TEX_FLUSH_DELETES);
try {
final GLContextState primary = primaryContext();
final GLContextState[] drawables = drawableContextStates;
final var it = Int2LongMaps.fastIterator(deferredDeleteTextures);
while (it.hasNext()) {
final Int2LongMap.Entry entry = it.next();
if (entry.getLongValue() == DEFERRED_ATTACHED) continue;
final int id = entry.getIntKey();
it.remove();
RENDER_BACKEND.deleteTextures(id);
non2DTextureTargets.remove(id);
clearTextureBindings(primary, id);
for (int i = 0; i < drawables.length; i++) {
clearTextureBindings(drawables[i], id);
}
}
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
}
private static final int DEPTH_SLOT = 16;
private static final int STENCIL_SLOT = 17;
private static final int FRAMEBUFFER_SLOTS = 18;
private static int attachmentSlot(int attachment) {
return switch (attachment) {
case GL30.GL_DEPTH_ATTACHMENT -> DEPTH_SLOT;
case GL30.GL_STENCIL_ATTACHMENT -> STENCIL_SLOT;
default -> attachment >= GL30.GL_COLOR_ATTACHMENT0 && attachment <= GL30.GL_COLOR_ATTACHMENT15 ? attachment - GL30.GL_COLOR_ATTACHMENT0 : -1;
};
}
private static int slotAttachment(int slot) {
return switch (slot) {
case DEPTH_SLOT -> GL30.GL_DEPTH_ATTACHMENT;
case STENCIL_SLOT -> GL30.GL_STENCIL_ATTACHMENT;
default -> GL30.GL_COLOR_ATTACHMENT0 + slot;
};
}
private static boolean beginAttach(int target, int attachment, int texture) {
final GLContextState glCtx = ctx();
return beginAttach(glCtx, target == GL30.GL_READ_FRAMEBUFFER ? glCtx.readFramebuffer : glCtx.drawFramebuffer, attachment, texture);
}
private static boolean beginAttach(GLContextState glCtx, int framebuffer, int attachment, int texture) {
if (isDeferredDelete(texture)) return false;
TextureInfoCache.INSTANCE.onWrittenOutsideUploads(texture);
trackFramebufferAttachment(glCtx, framebuffer, attachment, texture);
return true;
}
private static void trackFramebufferAttachment(GLContextState glCtx, int framebuffer, int attachment, int texture) {
if (framebuffer == 0) return;
if (attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT) {
trackFramebufferAttachment(glCtx, framebuffer, GL30.GL_DEPTH_ATTACHMENT, texture);
trackFramebufferAttachment(glCtx, framebuffer, GL30.GL_STENCIL_ATTACHMENT, texture);
return;
}
final int slot = attachmentSlot(attachment);
if (slot < 0) return;
int[] slots = glCtx.framebufferTextures.get(framebuffer);
if (slots == null) {
if (texture == 0) return;
slots = new int[FRAMEBUFFER_SLOTS];
glCtx.framebufferTextures.put(framebuffer, slots);
}
setFramebufferSlot(slots, slot, texture);
}
private static void setFramebufferSlot(int[] slots, int slot, int texture) {
final int previous = slots[slot];
if (previous == texture) return;
slots[slot] = texture;
final boolean locked = acquireDrawLock();
try {
if (texture != 0) textureFramebufferRefs.addTo(texture, 1);
if (previous != 0 && textureFramebufferRefs.addTo(previous, -1) == 1) {
textureFramebufferRefs.remove(previous);
if (deferredDeleteTextures.get(previous) == DEFERRED_ATTACHED) scheduleRelease(previous);
}
} finally {
if (locked) releaseDrawLock();
}
}
private static void detachFromFramebuffer(GLContextState glCtx, int target, int framebuffer, int texture) {
final int[] slots = glCtx.framebufferTextures.get(framebuffer);
if (slots == null) return;
for (int slot = 0; slot < slots.length; slot++) {
if (slots[slot] != texture) continue;
RENDER_BACKEND.framebufferTexture2D(target, slotAttachment(slot), GL11.GL_TEXTURE_2D, 0, 0);
setFramebufferSlot(slots, slot, 0);
}
deferredDeleteTextures.clear();
}
public static int glGenTextures() {
@@ -2726,6 +3010,32 @@ public class GLStateManager {
RENDER_BACKEND.genTextures(textures);
}
public static void glGenTextures(int[] textures) {
requireContext("glGenTextures");
flushDeferredTextureDeletes();
for (int i = 0; i < textures.length; i++) textures[i] = RENDER_BACKEND.genTextures();
}
public static int glCreateTextures(int target) {
requireContext("glCreateTextures");
final int id = RENDER_BACKEND.createTextures(target);
final boolean locked = acquireDrawLock();
try {
recordTarget(id, target);
} finally {
if (locked) releaseDrawLock();
}
return id;
}
public static void glCreateTextures(int target, IntBuffer textures) {
for (int i = textures.position(); i < textures.limit(); i++) textures.put(i, glCreateTextures(target));
}
public static void glCreateTextures(int target, int[] textures) {
for (int i = 0; i < textures.length; i++) textures[i] = glCreateTextures(target);
}
public static void enableTexture() {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -5891,14 +6201,6 @@ public class GLStateManager {
}
public static void glTexStorage2D(int target, int levels, int internalFormat, int width, int height) {
RENDER_BACKEND.texStorage2D(target, levels, internalFormat, width, height);
if (target == GL11.GL_TEXTURE_2D) {
final boolean locked = acquireDrawLock();
try {
unshrinkableTextures.add(getBoundTextureForServerState());
} finally {
if (locked) releaseDrawLock();
}
}
TextureInfoCache.INSTANCE.onTexStorage2D(target, internalFormat, width, height);
}
public static void glClearTexImage(int texture, int level, int format, int type, ByteBuffer data) {
@@ -5950,13 +6252,15 @@ public class GLStateManager {
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);
}
requireRemaining(samplers, count);
final int base = samplers.position();
for (int i = 0; i < count; i++) glBindSampler(first + i, samplers.get(base + i));
}
private static void requireRemaining(IntBuffer buffer, int count) {
if (buffer.remaining() < count) throw new IllegalArgumentException("Number of remaining buffer elements is " + buffer.remaining() + ", must be at least " + count);
}
public static void glBindSamplers(int first, IntBuffer samplers) {
if (samplers != null) glBindSamplers(first, samplers.remaining(), samplers);
}
@@ -7739,7 +8043,7 @@ public class GLStateManager {
public static void glClearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data) { onBufferRespec(target); RENDER_BACKEND.clearBufferSubData(target, internalFormat, offset, size, format, type, data); }
public static void glClearBufferData(int target, int internalformat, int format, int type, ByteBuffer data) { onBufferRespec(target); RENDER_BACKEND.clearBufferData(target, internalformat, format, type, data); }
public static boolean glIsBuffer(int buffer) { return RENDER_BACKEND.isBuffer(buffer); }
public static boolean glIsTexture(int texture) { return RENDER_BACKEND.isTexture(texture); }
public static boolean glIsTexture(int texture) { return !isDeferredDelete(texture) && RENDER_BACKEND.isTexture(texture); }
public static boolean glIsFramebuffer(int framebuffer) { return RENDER_BACKEND.isFramebuffer(framebuffer); }
public static boolean glIsRenderbuffer(int renderbuffer) { return RENDER_BACKEND.isRenderbuffer(renderbuffer); }
public static boolean glIsSampler(int sampler) { return RENDER_BACKEND.isSampler(sampler); }
@@ -7872,9 +8176,21 @@ public class GLStateManager {
final GLContextState glCtx = ctx();
if (glCtx.drawFramebuffer == framebuffer) { glCtx.drawFramebuffer = 0; glCtx.drawFramebufferGeneration++; }
if (glCtx.readFramebuffer == framebuffer) glCtx.readFramebuffer = 0;
final int[] slots = glCtx.framebufferTextures.remove(framebuffer);
if (slots != null) {
for (int slot = 0; slot < slots.length; slot++) setFramebufferSlot(slots, slot, 0);
}
RENDER_BACKEND.deleteFramebuffers(framebuffer);
}
public static void glDeleteFramebuffers(IntBuffer framebuffers) {
for (int i = framebuffers.position(); i < framebuffers.limit(); i++) glDeleteFramebuffers(framebuffers.get(i));
}
public static void glDeleteFramebuffers(int[] framebuffers) {
for (int framebuffer : framebuffers) glDeleteFramebuffers(framebuffer);
}
public static int glGenFramebuffers() {
requireContext("glGenFramebuffers");
return RENDER_BACKEND.genFramebuffers();
@@ -7889,16 +8205,91 @@ public class GLStateManager {
}
public static void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
TextureInfoCache.INSTANCE.onWrittenOutsideUploads(texture);
if (!beginAttach(target, attachment, texture)) return;
RENDER_BACKEND.framebufferTexture2D(target, attachment, textarget, texture, level);
}
public static void glFramebufferTexture1D(int target, int attachment, int textarget, int texture, int level) {
if (!beginAttach(target, attachment, texture)) return;
RENDER_BACKEND.framebufferTexture1D(target, attachment, textarget, texture, level);
}
public static void glFramebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset) {
if (!beginAttach(target, attachment, texture)) return;
RENDER_BACKEND.framebufferTexture3D(target, attachment, textarget, texture, level, zoffset);
}
public static void glFramebufferTextureLayer(int target, int attachment, int texture, int level, int layer) {
if (!beginAttach(target, attachment, texture)) return;
RENDER_BACKEND.framebufferTextureLayer(target, attachment, texture, level, layer);
}
public static void glFramebufferTexture(int target, int attachment, int texture, int level) {
TextureInfoCache.INSTANCE.onWrittenOutsideUploads(texture);
if (!beginAttach(target, attachment, texture)) return;
RENDER_BACKEND.framebufferTexture(target, attachment, texture, level);
}
public static void glFramebufferTextureFace(int target, int attachment, int texture, int level, int face) {
glFramebufferTexture2D(target, attachment, face, texture, level);
}
public static void glNamedFramebufferTexture(int framebuffer, int attachment, int texture, int level) {
if (!beginAttach(ctx(), framebuffer, attachment, texture)) return;
RENDER_BACKEND.namedFramebufferTexture(framebuffer, attachment, texture, level);
}
private static int swapDrawFramebuffer(int framebuffer) {
final GLContextState glCtx = ctx();
final int previous = glCtx.drawFramebuffer;
glCtx.drawFramebuffer = framebuffer;
RENDER_BACKEND.bindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, framebuffer);
return previous;
}
public static void glNamedFramebufferTextureLayer(int framebuffer, int attachment, int texture, int level, int layer) {
final int previous = swapDrawFramebuffer(framebuffer);
glFramebufferTextureLayer(GL30.GL_DRAW_FRAMEBUFFER, attachment, texture, level, layer);
swapDrawFramebuffer(previous);
}
public static void glNamedFramebufferRenderbuffer(int framebuffer, int attachment, int renderbuffertarget, int renderbuffer) {
final int previous = swapDrawFramebuffer(framebuffer);
glFramebufferRenderbuffer(GL30.GL_DRAW_FRAMEBUFFER, attachment, renderbuffertarget, renderbuffer);
swapDrawFramebuffer(previous);
}
public static void glNamedFramebufferTexture1DEXT(int framebuffer, int attachment, int textarget, int texture, int level) {
final int previous = swapDrawFramebuffer(framebuffer);
glFramebufferTexture1D(GL30.GL_DRAW_FRAMEBUFFER, attachment, textarget, texture, level);
swapDrawFramebuffer(previous);
}
public static void glNamedFramebufferTexture2DEXT(int framebuffer, int attachment, int textarget, int texture, int level) {
final int previous = swapDrawFramebuffer(framebuffer);
glFramebufferTexture2D(GL30.GL_DRAW_FRAMEBUFFER, attachment, textarget, texture, level);
swapDrawFramebuffer(previous);
}
public static void glNamedFramebufferTexture3DEXT(int framebuffer, int attachment, int textarget, int texture, int level, int zoffset) {
final int previous = swapDrawFramebuffer(framebuffer);
glFramebufferTexture3D(GL30.GL_DRAW_FRAMEBUFFER, attachment, textarget, texture, level, zoffset);
swapDrawFramebuffer(previous);
}
public static void glNamedFramebufferTextureEXT(int framebuffer, int attachment, int texture, int level) {
final int previous = swapDrawFramebuffer(framebuffer);
glFramebufferTexture(GL30.GL_DRAW_FRAMEBUFFER, attachment, texture, level);
swapDrawFramebuffer(previous);
}
public static void glNamedFramebufferTextureFaceEXT(int framebuffer, int attachment, int texture, int level, int face) {
glNamedFramebufferTexture2DEXT(framebuffer, attachment, face, texture, level);
}
public static void attachTextureForReading(int attachment, int texture) {
if (isDeferredDelete(texture)) return;
final GLContextState glCtx = ctx();
trackFramebufferAttachment(glCtx, glCtx.readFramebuffer, attachment, texture);
RENDER_BACKEND.framebufferTexture2D(GL30.GL_READ_FRAMEBUFFER, attachment, GL11.GL_TEXTURE_2D, texture, 0);
}
@@ -7910,7 +8301,10 @@ public class GLStateManager {
public static void glBindRenderbuffer(int target, int renderbuffer) { RENDER_BACKEND.bindRenderbuffer(target, renderbuffer); }
public static void glRenderbufferStorage(int target, int internalformat, int width, int height) { RENDER_BACKEND.renderbufferStorage(target, internalformat, width, height); }
public static void glRenderbufferStorageMultisample(int target, int samples, int internalformat, int width, int height) { RENDER_BACKEND.renderbufferStorageMultisample(target, samples, internalformat, width, height); }
public static void glFramebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer) { RENDER_BACKEND.framebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer); }
public static void glFramebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer) {
beginAttach(target, attachment, 0);
RENDER_BACKEND.framebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer);
}
public static void glGenQueries(IntBuffer ids) {
requireContext("glGenQueries");
@@ -8713,6 +9107,7 @@ public class GLStateManager {
}
public static void glBindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) {
if (isDeferredDelete(texture)) return;
final GLContextState glCtx = ctx();
final ImageUnitBinding binding = glCtx.imageUnits.get(unit);
if (binding == null) {
@@ -8725,6 +9120,50 @@ public class GLStateManager {
RENDER_BACKEND.bindImageTexture(unit, texture, level, layered, layer, access, format);
}
public static void glBindImageTextures(int first, int count, IntBuffer textures) {
if (first < 0 || first + count > ctx().imageUnits.size()) return;
if (textures == null) {
for (int i = 0; i < count; i++) bindImageUnitByName(first + i, 0);
return;
}
requireRemaining(textures, count);
for (int i = 0; i < count; i++) bindImageUnitByName(first + i, textures.get(textures.position() + i));
}
public static void glBindImageTextures(int first, IntBuffer textures) {
if (textures != null) glBindImageTextures(first, textures.remaining(), textures);
}
public static void glBindImageTextures(int first, int[] textures) {
if (textures != null) glBindImageTextures(first, textures.length, IntBuffer.wrap(textures));
}
private static void bindImageUnitByName(int unit, int texture) {
if (texture == 0) {
glBindImageTexture(unit, 0, 0, false, 0, GL15.GL_READ_ONLY, GL30.GL_R8);
return;
}
if (isDeferredDelete(texture)) return;
final int format = TextureInfoCache.INSTANCE.getInfo(texture).getInternalFormat();
if (!isImageFormat(format)) return;
final boolean layered = switch (non2DTextureTargets.get(texture)) {
case GL30.GL_TEXTURE_1D_ARRAY, GL30.GL_TEXTURE_2D_ARRAY, GL12.GL_TEXTURE_3D, GL13.GL_TEXTURE_CUBE_MAP, GL40.GL_TEXTURE_CUBE_MAP_ARRAY, GL32.GL_TEXTURE_2D_MULTISAMPLE_ARRAY -> true;
default -> false;
};
glBindImageTexture(unit, texture, 0, layered, 0, GL15.GL_READ_WRITE, format);
}
private static boolean isImageFormat(int format) {
return switch (format) {
case GL30.GL_RGBA32F, GL30.GL_RGBA16F, GL30.GL_RG32F, GL30.GL_RG16F, GL30.GL_R11F_G11F_B10F, GL30.GL_R32F, GL30.GL_R16F,
GL30.GL_RGBA32UI, GL30.GL_RGBA16UI, GL33.GL_RGB10_A2UI, GL30.GL_RGBA8UI, GL30.GL_RG32UI, GL30.GL_RG16UI, GL30.GL_RG8UI, GL30.GL_R32UI, GL30.GL_R16UI, GL30.GL_R8UI,
GL30.GL_RGBA32I, GL30.GL_RGBA16I, GL30.GL_RGBA8I, GL30.GL_RG32I, GL30.GL_RG16I, GL30.GL_RG8I, GL30.GL_R32I, GL30.GL_R16I, GL30.GL_R8I,
GL11.GL_RGBA16, GL11.GL_RGB10_A2, GL11.GL_RGBA8, GL30.GL_RG16, GL30.GL_RG8, GL30.GL_R16, GL30.GL_R8,
GL31.GL_RGBA16_SNORM, GL31.GL_RGBA8_SNORM, GL31.GL_RG16_SNORM, GL31.GL_RG8_SNORM, GL31.GL_R16_SNORM, GL31.GL_R8_SNORM -> true;
default -> false;
};
}
public static ImageUnitBinding getImageUnitBinding(int unit) {
return ctx().imageUnits.get(unit);
}
@@ -46,6 +46,8 @@ public class RenderSystem {
private static boolean supportsMultiDrawIndirect;
private static boolean supportsBufferStorage;
private static boolean supportsClearTexture;
private static boolean supportsStencilTexturing;
private static boolean supportsTextureStorage;
private static boolean supportsTesselation;
private static boolean supportsSamplerObjects;
private static boolean supportsProgramBinary;
@@ -128,6 +130,8 @@ public class RenderSystem {
supportsMultiDrawIndirect = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_multi_draw_indirect;
supportsBufferStorage = !isGLES && (GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage);
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
supportsStencilTexturing = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_stencil_texturing;
supportsTextureStorage = isGLES ? glesVersion >= 300 : GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_texture_storage;
hasClipCullDistance = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
// Cache maximum image units
@@ -307,14 +311,14 @@ public class RenderSystem {
}
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
GLStateManager.glBindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
GLStateManager.glBindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
@@ -363,7 +367,6 @@ public class RenderSystem {
}
public static void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
TextureInfoCache.INSTANCE.onWrittenOutsideUploads(texture);
dsaState.framebufferTexture2D(fb, fbtarget, attachment, target, texture, levels);
}
@@ -459,6 +462,14 @@ public class RenderSystem {
return supportsClearTexture;
}
public static boolean supportsStencilTexturing() {
return supportsStencilTexturing;
}
public static boolean supportsTextureStorage() {
return supportsTextureStorage;
}
public static int getMaxSSBOBindings() {
return maxSSBOBindings;
}
@@ -609,6 +609,21 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL30.glFramebufferTexture2D(target, attachment, textarget, texture, level);
}
@Override
public void framebufferTexture1D(int target, int attachment, int textarget, int texture, int level) {
GL30.glFramebufferTexture1D(target, attachment, textarget, texture, level);
}
@Override
public void framebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset) {
GL30.glFramebufferTexture3D(target, attachment, textarget, texture, level, zoffset);
}
@Override
public void framebufferTextureLayer(int target, int attachment, int texture, int level, int layer) {
GL30.glFramebufferTextureLayer(target, attachment, texture, level, layer);
}
@Override
public void framebufferTexture(int target, int attachment, int texture, int level) {
GL32.glFramebufferTexture(target, attachment, texture, level);
@@ -457,6 +457,9 @@ public abstract class RenderBackend {
public abstract void deleteFramebuffers(int framebuffer);
public abstract void bindFramebuffer(int target, int framebuffer);
public abstract void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level);
public abstract void framebufferTexture1D(int target, int attachment, int textarget, int texture, int level);
public abstract void framebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset);
public abstract void framebufferTextureLayer(int target, int attachment, int texture, int level, int layer);
public abstract void framebufferTexture(int target, int attachment, int texture, int level);
public abstract int genRenderbuffers();
public abstract void deleteRenderbuffers(int renderbuffer);
@@ -128,7 +128,7 @@ public class DSAARB extends DSAUnsupported {
@Override
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
RENDER_BACKEND.namedFramebufferTexture(fb, attachment, texture, levels);
GLStateManager.glNamedFramebufferTexture(fb, attachment, texture, levels);
}
@Override
@@ -139,7 +139,6 @@ public class DSAARB extends DSAUnsupported {
@Override
public int createTexture(int target) {
GLStateManager.requireContext("glCreateTextures");
return RENDER_BACKEND.createTextures(target);
return GLStateManager.glCreateTextures(target);
}
}
@@ -2,13 +2,12 @@ package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.stacks.TextureBindingStack;
import com.gtnewhorizons.angelica.glsm.texture.TextureTargets;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
@@ -153,7 +152,7 @@ public class DSAUnsupported implements DSAAccess {
@Override
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
GLStateManager.glBindFramebuffer(fbtarget, fb);
RENDER_BACKEND.framebufferTexture2D(fbtarget, attachment, target, texture, levels);
GLStateManager.glFramebufferTexture2D(fbtarget, attachment, target, texture, levels);
}
@Override
@@ -166,23 +165,12 @@ public class DSAUnsupported implements DSAAccess {
@Override
public int createTexture(int target) {
final int texture = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glBindTexture(target, texture);
return texture;
}
private static int bindingQuery(int target) {
return switch (target) {
case GL11.GL_TEXTURE_1D -> GL11.GL_TEXTURE_BINDING_1D;
case GL12.GL_TEXTURE_3D -> GL12.GL_TEXTURE_BINDING_3D;
case GL13.GL_TEXTURE_CUBE_MAP -> GL13.GL_TEXTURE_BINDING_CUBE_MAP;
case GL30.GL_TEXTURE_2D_ARRAY -> GL30.GL_TEXTURE_BINDING_2D_ARRAY;
case GL31.GL_TEXTURE_RECTANGLE -> GL31.GL_TEXTURE_BINDING_RECTANGLE;
default -> GL11.GL_TEXTURE_BINDING_2D;
};
}
private static int previousBinding(int target) {
return target == GL11.GL_TEXTURE_2D ? previous2D() : RENDER_BACKEND.getInteger(bindingQuery(target));
return target == GL11.GL_TEXTURE_2D ? previous2D() : RENDER_BACKEND.getInteger(TextureTargets.bindingQuery(target));
}
private static int previous2D() {
@@ -26,6 +26,7 @@ public final class FrameHooks {
}
public static void frameEnd() {
GLStateManager.onFrameEnd();
popFrameGroup();
RENDER_BACKEND.onFrameEnd();
}
@@ -167,6 +167,14 @@ public class GLSMRedirector {
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";
private static final String ARBDirectStateAccess = "org/lwjgl/opengl/ARBDirectStateAccess";
private static final String ARBGeometryShader4 = "org/lwjgl/opengl/ARBGeometryShader4";
private static final String EXTGeometryShader4 = "org/lwjgl/opengl/EXTGeometryShader4";
private static final String NVGeometryProgram4 = "org/lwjgl/opengl/NVGeometryProgram4";
private static final String EXTTextureArray = "org/lwjgl/opengl/EXTTextureArray";
private static final String EXTDirectStateAccess = "org/lwjgl/opengl/EXTDirectStateAccess";
private static final String ARBShaderImageLoadStore = "org/lwjgl/opengl/ARBShaderImageLoadStore";
private static final String EXTShaderImageLoadStore = "org/lwjgl/opengl/EXTShaderImageLoadStore";
// Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
@@ -513,7 +521,10 @@ public class GLSMRedirector {
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
.add("glCheckFramebufferStatus")
.add("glFramebufferTexture1D")
.add("glFramebufferTexture2D")
.add("glFramebufferTexture3D")
.add("glFramebufferTextureLayer")
.add("glVertexAttribIPointer")
.add("glGenerateMipmap")
.add("glGetFramebufferAttachmentParameteri")
@@ -567,11 +578,18 @@ public class GLSMRedirector {
final var gl44 = RedirectMap.newMap()
.add("glBufferStorage")
.add("glBindSamplers")
.add("glClearTexImage");
.add("glClearTexImage")
.add("glBindTextures")
.add("glBindImageTextures");
final var gl45 = RedirectMap.newMap()
.add("glCreateBuffers")
.add("glNamedBufferData")
.add("glNamedBufferSubData");
.add("glNamedBufferSubData")
.add("glNamedFramebufferTexture")
.add("glNamedFramebufferTextureLayer")
.add("glNamedFramebufferRenderbuffer")
.add("glCreateTextures")
.add("glBindTextureUnit");
final var gl46 = RedirectMap.newMap()
.add("glPolygonOffsetClamp")
.add("glMultiDrawArraysIndirectCount")
@@ -630,7 +648,10 @@ public class GLSMRedirector {
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
.add("glCheckFramebufferStatus")
.add("glFramebufferTexture1D")
.add("glFramebufferTexture2D")
.add("glFramebufferTexture3D")
.add("glFramebufferTextureLayer")
.add("glBindRenderbuffer")
.add("glDeleteRenderbuffers")
.add("glGenRenderbuffers")
@@ -643,7 +664,9 @@ public class GLSMRedirector {
.add("glDeleteFramebuffersEXT", "glDeleteFramebuffers")
.add("glGenFramebuffersEXT", "glGenFramebuffers")
.add("glCheckFramebufferStatusEXT", "glCheckFramebufferStatus")
.add("glFramebufferTexture1DEXT", "glFramebufferTexture1D")
.add("glFramebufferTexture2DEXT", "glFramebufferTexture2D")
.add("glFramebufferTexture3DEXT", "glFramebufferTexture3D")
.add("glBindRenderbufferEXT", "glBindRenderbuffer")
.add("glDeleteRenderbuffersEXT", "glDeleteRenderbuffers")
.add("glGenRenderbuffersEXT", "glGenRenderbuffers")
@@ -793,6 +816,45 @@ public class GLSMRedirector {
);
methodRedirects.put(ARBMultiBind, RedirectMap.newMap()
.add("glBindSamplers")
.add("glBindTextures")
.add("glBindImageTextures")
);
methodRedirects.put(ARBDirectStateAccess, RedirectMap.newMap()
.add("glNamedFramebufferTexture")
.add("glNamedFramebufferTextureLayer")
.add("glNamedFramebufferRenderbuffer")
.add("glCreateTextures")
.add("glBindTextureUnit")
);
methodRedirects.put(ARBGeometryShader4, RedirectMap.newMap()
.add("glFramebufferTextureARB", "glFramebufferTexture")
.add("glFramebufferTextureLayerARB", "glFramebufferTextureLayer")
.add("glFramebufferTextureFaceARB", "glFramebufferTextureFace")
);
final var geometryShader4 = RedirectMap.newMap()
.add("glFramebufferTextureEXT", "glFramebufferTexture")
.add("glFramebufferTextureLayerEXT", "glFramebufferTextureLayer")
.add("glFramebufferTextureFaceEXT", "glFramebufferTextureFace");
methodRedirects.put(EXTGeometryShader4, geometryShader4);
methodRedirects.put(NVGeometryProgram4, geometryShader4);
methodRedirects.put(EXTTextureArray, RedirectMap.newMap()
.add("glFramebufferTextureLayerEXT", "glFramebufferTextureLayer")
);
methodRedirects.put(EXTDirectStateAccess, RedirectMap.newMap()
.add("glNamedFramebufferTexture1DEXT")
.add("glNamedFramebufferTexture2DEXT")
.add("glNamedFramebufferTexture3DEXT")
.add("glNamedFramebufferTextureEXT")
.add("glNamedFramebufferTextureLayerEXT", "glNamedFramebufferTextureLayer")
.add("glNamedFramebufferTextureFaceEXT")
.add("glNamedFramebufferRenderbufferEXT", "glNamedFramebufferRenderbuffer")
.add("glBindMultiTextureEXT")
);
methodRedirects.put(ARBShaderImageLoadStore, RedirectMap.newMap()
.add("glBindImageTexture")
);
methodRedirects.put(EXTShaderImageLoadStore, RedirectMap.newMap()
.add("glBindImageTextureEXT", "glBindImageTexture")
);
// APPLE
@@ -27,4 +27,20 @@ public final class TextureTargets {
default -> false;
};
}
public static int bindingQuery(int target) {
return switch (target) {
case GL11.GL_TEXTURE_1D -> GL11.GL_TEXTURE_BINDING_1D;
case GL12.GL_TEXTURE_3D -> GL12.GL_TEXTURE_BINDING_3D;
case GL13.GL_TEXTURE_CUBE_MAP -> GL13.GL_TEXTURE_BINDING_CUBE_MAP;
case GL30.GL_TEXTURE_1D_ARRAY -> GL30.GL_TEXTURE_BINDING_1D_ARRAY;
case GL30.GL_TEXTURE_2D_ARRAY -> GL30.GL_TEXTURE_BINDING_2D_ARRAY;
case GL31.GL_TEXTURE_RECTANGLE -> GL31.GL_TEXTURE_BINDING_RECTANGLE;
case GL31.GL_TEXTURE_BUFFER -> GL31.GL_TEXTURE_BINDING_BUFFER;
case GL32.GL_TEXTURE_2D_MULTISAMPLE -> GL32.GL_TEXTURE_BINDING_2D_MULTISAMPLE;
case GL32.GL_TEXTURE_2D_MULTISAMPLE_ARRAY -> GL32.GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY;
case GL40.GL_TEXTURE_CUBE_MAP_ARRAY -> GL40.GL_TEXTURE_BINDING_CUBE_MAP_ARRAY;
default -> GL11.GL_TEXTURE_BINDING_2D;
};
}
}
@@ -1,9 +1,20 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL33;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -11,6 +22,10 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
public class GLSM_DeferredTextureDelete_GLTest {
private static final int RELEASE_DELAY_FRAMES = Reflect.getStatic(GLStateManager.class, "RELEASE_DELAY_FRAMES");
private static final int SIZE = 4;
private static final int LEVELS = 3;
private static int immutableTexture() {
assumeTrue(GLStateManager.capabilities.OpenGL42, "no glTexStorage2D on this context");
final int tex = GLStateManager.glGenTextures();
@@ -20,6 +35,292 @@ public class GLSM_DeferredTextureDelete_GLTest {
return tex;
}
private static int mipmappedTexture() {
final int tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
for (int level = 0; level < LEVELS; level++) {
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA8, SIZE >> level, SIZE >> level, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
}
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
return tex;
}
private static int levelWidth(int tex, int level) {
final int previous = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, tex);
final int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_WIDTH);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previous);
return width;
}
private static int cachedBinding(int unit) {
return GLStateManager.getTextures().getTextureUnitBindings(unit).getBinding();
}
private static void framesPass(int frames) {
for (int i = 0; i < frames; i++) GLStateManager.onFrameEnd();
}
enum Attachment {
NONE {
@Override int attach(int tex) { return 0; }
},
FRAMEBUFFER {
@Override int attach(int tex) {
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
return fbo;
}
},
NAMED_EXT {
@Override int attach(int tex) {
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glNamedFramebufferTexture2DEXT(fbo, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
assertEquals(0, GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING), "named attach must restore the draw binding");
return fbo;
}
};
abstract int attach(int tex);
}
@ParameterizedTest
@EnumSource(Attachment.class)
void storageIsReleasedOnlyAfterTheDelayFramesUnattached(Attachment attachment) {
framesPass(RELEASE_DELAY_FRAMES);
final int before = GLStateManager.deferredTexturesAwaitingRelease();
final int tex = mipmappedTexture();
final int fbo = attachment.attach(tex);
GLStateManager.glDeleteTextures(tex);
assertEquals(before + 1, GLStateManager.deferredTexturesAwaitingRelease());
framesPass(RELEASE_DELAY_FRAMES - 1);
assertEquals(SIZE, levelWidth(tex, 0), "storage must outlive the delete by the delay frames");
if (fbo != 0) {
framesPass(RELEASE_DELAY_FRAMES);
assertEquals(SIZE, levelWidth(tex, 0), "an attached texture keeps its storage");
assertEquals(before + 1, GLStateManager.deferredTexturesAwaitingRelease());
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glDeleteTextures(GLStateManager.glGenTextures());
assertEquals(tex, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME), "a flush must not delete an attached texture");
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(BufferUtils.createIntBuffer(1).put(fbo).flip());
framesPass(RELEASE_DELAY_FRAMES - 1);
assertEquals(SIZE, levelWidth(tex, 0), "storage must outlive the detach by the delay frames");
}
framesPass(1);
for (int level = 0; level < LEVELS; level++) assertEquals(0, levelWidth(tex, level), "level " + level);
assertEquals(before, GLStateManager.deferredTexturesAwaitingRelease(), "released storage leaves nothing awaiting release");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
enum Rejection { ATTACH, BIND_TEXTURE_UNIT }
@ParameterizedTest
@EnumSource(Rejection.class)
void deletedNamesAreRejected(Rejection rejection) {
final int tex = mipmappedTexture();
final int other = mipmappedTexture();
GLStateManager.glDeleteTextures(tex);
try {
switch (rejection) {
case ATTACH -> {
final int fbo = GLStateManager.glGenFramebuffers();
try {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
assertEquals(GL11.GL_NONE, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE));
} finally {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(fbo);
}
}
case BIND_TEXTURE_UNIT -> {
GLStateManager.glBindTextureUnit(2, other);
GLStateManager.glBindTextureUnit(2, tex);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
assertEquals(other, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "driver binding");
assertEquals(other, GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "cached binding");
}
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTextureUnit(2, 0);
GLStateManager.glDeleteTextures(other);
}
}
@Test
void bindTexturesBindsEachTextureToItsTarget() {
final int t2d = mipmappedTexture();
final int t3d = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, t3d);
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
try {
GLStateManager.glBindTextures(1, new int[] {t2d, t3d});
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "active unit unchanged");
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertEquals(t2d, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "unit 1 driver binding");
assertEquals(t2d, GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "unit 1 cached binding");
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
assertEquals(t3d, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D), "unit 2 driver binding");
assertEquals(t3d, cachedBinding(2), "unit 2 cached binding");
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int unit0 = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GLStateManager.glBindTextures(GLStateManager.MAX_TEXTURE_UNITS - 1, new int[] {t3d, t2d});
assertEquals(unit0, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "an out-of-range multi-bind must not land on the active unit");
GLStateManager.glDeleteTextures(t2d);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertEquals(0, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "delete unbinds a multi-bound texture");
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTextures(1, new int[] {0, 0});
GLStateManager.glDeleteTextures(t3d);
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void dsaCreated3DTextureIsBoundAs3D() {
final int tex = RenderSystem.createTexture(GL12.GL_TEXTURE_3D);
final int other = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, other);
RenderSystem.texImage3D(tex, GL12.GL_TEXTURE_3D, 0, GL11.GL_RGBA8, SIZE, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(tex, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D), "texImage3D binds the texture as 3D");
assertEquals(tex, cachedBinding(0), "cached 3D binding");
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GLStateManager.glDeleteTextures(tex);
GLStateManager.glDeleteTextures(other);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void createdNon2DTextureIsNotReleasedAs2D() {
assumeTrue(GLStateManager.capabilities.OpenGL45, "no glCreateTextures on this context");
final int tex = GLStateManager.glCreateTextures(GL12.GL_TEXTURE_3D);
GLStateManager.glDeleteTextures(tex);
framesPass(RELEASE_DELAY_FRAMES);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void deletingDetachesOnlyFromBoundFramebuffers() {
final int tex = mipmappedTexture();
final int unbound = Attachment.FRAMEBUFFER.attach(tex);
final int bound = Attachment.FRAMEBUFFER.attach(tex);
try {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, bound);
GLStateManager.glDeleteTextures(tex);
assertEquals(GL11.GL_NONE, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE));
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, unbound);
assertEquals(tex, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME));
} finally {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(bound);
GLStateManager.glDeleteFramebuffers(unbound);
}
}
enum Rescue { PENDING, RELEASED, ATTACHED }
@ParameterizedTest
@EnumSource(Rescue.class)
void rescuedTextureIsEmptyWithDefaultParameters(Rescue rescue) {
final int tex = mipmappedTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_BASE_LEVEL, 1);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 2);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL33.GL_TEXTURE_SWIZZLE_R, GL11.GL_GREEN);
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 1.5f);
GLStateManager.glTexParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_BORDER_COLOR, (FloatBuffer) BufferUtils.createFloatBuffer(4).put(new float[] {1, 1, 1, 1}).flip());
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
final int fbo = rescue == Rescue.ATTACHED ? Attachment.FRAMEBUFFER.attach(tex) : 0;
GLStateManager.glDeleteTextures(tex);
if (rescue != Rescue.PENDING) framesPass(RELEASE_DELAY_FRAMES);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
try {
assertEquals(rescue == Rescue.ATTACHED ? SIZE : 0, levelWidth(tex, 0), "only an attached texture keeps its storage");
final int[][] defaults = {
{GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_LINEAR},
{GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR},
{GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT},
{GL12.GL_TEXTURE_BASE_LEVEL, 0},
{GL12.GL_TEXTURE_MAX_LEVEL, 1000},
{GL33.GL_TEXTURE_SWIZZLE_R, GL11.GL_RED},
};
for (int[] pair : defaults) {
assertEquals(pair[1], GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, pair[0]), "driver pname 0x" + Integer.toHexString(pair[0]));
assertEquals(pair[1], GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, pair[0]), "cached pname 0x" + Integer.toHexString(pair[0]));
}
assertEquals(0.0f, GL11.glGetTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS));
final FloatBuffer border = BufferUtils.createFloatBuffer(16);
GL11.glGetTexParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_BORDER_COLOR, border);
assertEquals(0.0f, border.get(0) + border.get(1) + border.get(2) + border.get(3), "border color");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(tex);
if (fbo != 0) GLStateManager.glDeleteFramebuffers(fbo);
}
}
@Test
void rescuingWithADifferentTargetLeavesTheBoundTextureAlone() {
final int t3d = GLStateManager.glGenTextures();
final int tex = mipmappedTexture();
try {
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, t3d);
GLStateManager.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glDeleteTextures(tex);
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, tex);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GLSM drops the bind");
assertEquals(t3d, cachedBinding(0), "cached 3D binding");
assertEquals(GL11.GL_LINEAR, GL11.glGetTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER));
framesPass(RELEASE_DELAY_FRAMES);
assertEquals(t3d, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D), "the release must not unbind the 3D texture");
assertEquals(0, levelWidth(tex, 0), "the mismatched bind must leave the texture deferred");
} finally {
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GLStateManager.glDeleteTextures(t3d);
}
}
@Test
void deletingAStaleNameKeepsTheBoundTexture() {
final int live = mipmappedTexture();
final int stale = GLStateManager.glGenTextures();
try {
GLStateManager.glDeleteTextures(stale);
GLStateManager.glDeleteTextures(GLStateManager.glGenTextures());
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, live);
GLStateManager.glDeleteTextures(stale);
framesPass(RELEASE_DELAY_FRAMES);
assertEquals(SIZE, levelWidth(live, 0), "a stale delete must not empty the bound texture");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(live);
}
}
@Test
void deletingAnImmutableTextureRaisesNoError() {
final int tex = immutableTexture();
@@ -30,33 +331,40 @@ public class GLSM_DeferredTextureDelete_GLTest {
}
@Test
void deletingARescuedImmutableTextureRaisesNoError() {
void rescuedImmutableTextureResetsParametersAndDeletesAgain() {
final int tex = immutableTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
GLStateManager.glDeleteTextures(tex);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void deletingA2DTextureWhileA3DTextureIsBoundLeavesNothingBoundTo2D() {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void deletingA2DTextureWhileA3DTextureIsBound(boolean deletedIsHidden2D) {
final int t2d = GLStateManager.glGenTextures();
final int t3d = GLStateManager.glGenTextures();
final int other = GLStateManager.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t2d);
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, t3d);
GLStateManager.glDeleteTextures(t2d);
GLStateManager.glDeleteTextures(deletedIsHidden2D ? t2d : other);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
assertEquals(0, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "the deleted texture must not stay bound");
assertEquals(deletedIsHidden2D ? 0 : t2d, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "only the deleted texture leaves the hidden 2D binding");
assertEquals(t3d, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D));
} finally {
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(t3d);
GLStateManager.glDeleteTextures(deletedIsHidden2D ? other : t2d);
}
}
}
@@ -230,6 +230,7 @@ class GLSMRedirectorTest {
@ParameterizedTest(name = "{0}.{1}{2}")
@CsvSource(delimiter = '|', value = {
"org/lwjgl/opengl/EXTFramebufferObject | glBindFramebufferEXT | (II)V | glBindFramebuffer",
"org/lwjgl/opengl/EXTFramebufferObject | glFramebufferTexture3DEXT | (IIIIII)V | glFramebufferTexture3D",
"org/lwjgl/opengl/ARBFramebufferObject | glGenerateMipmap | (I)V | glGenerateMipmap",
"org/lwjgl/opengl/EXTFramebufferObject | glGenerateMipmapEXT | (I)V | glGenerateMipmap",
"org/lwjgl/opengl/GL11 | glReadPixels | (IIIIIILjava/nio/IntBuffer;)V | glReadPixels",
@@ -261,9 +262,15 @@ class GLSMRedirectorTest {
"org/lwjgl/opengl/GL11 | glLoadMatrixf | (Ljava/nio/FloatBuffer;)V | glLoadMatrix",
"org/lwjgl/opengl/ARBVertexArrayObject | glGenVertexArrays | ()I | glGenVertexArrays",
"org/lwjgl/opengl/APPLEVertexArrayObject | glGenVertexArraysAPPLE | ()I | glGenVertexArrays",
"org/lwjgl/opengl/ARBGeometryShader4 | glFramebufferTextureFaceARB | (IIIII)V | glFramebufferTextureFace",
"org/lwjgl/opengl/EXTGeometryShader4 | glFramebufferTextureEXT | (IIII)V | glFramebufferTexture",
"org/lwjgl/opengl/EXTTextureArray | glFramebufferTextureLayerEXT | (IIIII)V | glFramebufferTextureLayer",
"org/lwjgl/opengl/EXTShaderImageLoadStore | glBindImageTextureEXT | (IIIZIII)V | glBindImageTexture",
"org/lwjgl/opengl/EXTFramebufferObject | glDeleteFramebuffersEXT | (Ljava/nio/IntBuffer;)V | glDeleteFramebuffers",
})
void aliasIsRedirected(String owner, String name, String desc, String expectedName) {
void aliasIsRedirected(String owner, String name, String desc, String expectedName) throws ClassNotFoundException {
assertEquals(desc, assertRedirected(owner, name, desc, expectedName).desc);
assertTrue(glsmDeclares(expectedName, desc), () -> "GLStateManager lacks " + expectedName + desc);
assertTrue(debugSeen().isEmpty());
}
@@ -303,6 +310,32 @@ class GLSMRedirectorTest {
"org/lwjgl/opengl/ARBSamplerObjects | glSamplerParameteri | (III)V",
"org/lwjgl/opengl/ARBSamplerObjects | glSamplerParameterf | (IIF)V",
"org/lwjgl/opengl/GL43 | glGetDebugMessageLog | (ILjava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/ByteBuffer;)I",
"org/lwjgl/opengl/GL30 | glFramebufferTexture1D | (IIIII)V",
"org/lwjgl/opengl/GL30 | glFramebufferTexture3D | (IIIIII)V",
"org/lwjgl/opengl/GL30 | glFramebufferTextureLayer | (IIIII)V",
"org/lwjgl/opengl/GL30C | glDeleteFramebuffers | ([I)V",
"org/lwjgl/opengl/GL11C | glDeleteTextures | ([I)V",
"org/lwjgl/opengl/GL11C | glGenTextures | ([I)V",
"org/lwjgl/opengl/GL44 | glBindTextures | (IILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL44C | glBindTextures | (ILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL44 | glBindTextures | (I[I)V",
"org/lwjgl/opengl/GL44 | glBindImageTextures | (IILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL44C | glBindImageTextures | (ILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL44 | glBindImageTextures | (I[I)V",
"org/lwjgl/opengl/GL45 | glBindTextureUnit | (II)V",
"org/lwjgl/opengl/GL45 | glCreateTextures | (I)I",
"org/lwjgl/opengl/GL45C | glCreateTextures | (ILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL45C | glCreateTextures | (I[I)V",
"org/lwjgl/opengl/GL45 | glNamedFramebufferTexture | (IIII)V",
"org/lwjgl/opengl/GL45 | glNamedFramebufferTextureLayer | (IIIII)V",
"org/lwjgl/opengl/ARBDirectStateAccess | glNamedFramebufferRenderbuffer | (IIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glNamedFramebufferTexture1DEXT | (IIIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glNamedFramebufferTexture2DEXT | (IIIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glNamedFramebufferTexture3DEXT | (IIIIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glNamedFramebufferTextureEXT | (IIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glNamedFramebufferTextureFaceEXT | (IIIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glBindMultiTextureEXT | (III)V",
"org/lwjgl/opengl/ARBShaderImageLoadStore | glBindImageTexture | (IIIZIII)V",
})
void redirectsToADeclaredGlsmTarget(String owner, String name, String desc) throws ClassNotFoundException {
for (boolean aware : new boolean[]{false, true}) {
@@ -747,6 +747,21 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL30C.glFramebufferTexture2D(target, attachment, textarget, texture, level);
}
@Override
public void framebufferTexture1D(int target, int attachment, int textarget, int texture, int level) {
GL30C.glFramebufferTexture1D(target, attachment, textarget, texture, level);
}
@Override
public void framebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset) {
GL30C.glFramebufferTexture3D(target, attachment, textarget, texture, level, zoffset);
}
@Override
public void framebufferTextureLayer(int target, int attachment, int texture, int level, int layer) {
GL30C.glFramebufferTextureLayer(target, attachment, texture, level, layer);
}
@Override
public void framebufferTexture(int target, int attachment, int texture, int level) {
GL32C.glFramebufferTexture(target, attachment, texture, level);
@@ -1557,8 +1557,8 @@ public class SDLGPURenderBackend extends RenderBackend {
return isProxyTarget(target) ? 0 : st.boundTextures[st.activeTextureUnit];
}
private int requestedMipLevels(int glId) {
return Math.max(1, resourceManager.getOrCreateTexSamplerState(glId).maxLevel + 1);
private int requestedMipLevels(int glId, int width, int height) {
return Math.max(1, Math.min(resourceManager.getOrCreateTexSamplerState(glId).maxLevel + 1, PixelOps.defaultMipLevels(width, height)));
}
private int fullMipLevels(int glId, int width, int height) {
@@ -1576,7 +1576,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private void ensureLevel0Storage(ContextState st, int glId, int target, int internalFormat, int width, int height, int depth, int format, boolean mipmapped) {
final int numLevels = mipmapped ? requestedMipLevels(glId) : 1;
final int numLevels = mipmapped ? requestedMipLevels(glId, width, height) : 1;
final boolean bgraSwizzle = format == GL12.GL_BGRA && (internalFormat == GL11.GL_RGBA || internalFormat == GL11.GL_RGBA8);
final int targetSdlFormat = bgraSwizzle
? SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM
@@ -1594,6 +1594,15 @@ public class SDLGPURenderBackend extends RenderBackend {
resourceManager.refreshTextureReferences(glId);
}
private boolean dropStorageIfEmpty(ContextState st, int glId, int level, int width, int height, int depth) {
if (width > 0 && height > 0 && depth > 0) return false;
if (level == 0) {
textureOps.releaseTextureForRealloc(st, glId);
resourceManager.refreshTextureReferences(glId);
}
return true;
}
private static boolean canReuseTexture(ResourceManager.TextureMeta m, long handle, int target, int glFormat, int sdlFormat, int w, int h, int d, int levels) {
return m != null
&& handle != 0
@@ -1613,7 +1622,7 @@ public class SDLGPURenderBackend extends RenderBackend {
return;
}
final int glId = st.boundTextures[st.activeTextureUnit];
if (glId == 0) return;
if (glId == 0 || dropStorageIfEmpty(st, glId, level, width, height, 1)) return;
if (level == 0) {
ensureLevel0Storage(st, glId, target, internalFormat, width, height, 1, format, true);
@@ -1639,7 +1648,7 @@ public class SDLGPURenderBackend extends RenderBackend {
return;
}
final int glId = st.boundTextures[st.activeTextureUnit];
if (glId == 0) return;
if (glId == 0 || dropStorageIfEmpty(st, glId, level, width, height, depth)) return;
if (level == 0) {
ensureLevel0Storage(st, glId, target, internalFormat, width, height, depth, GL11.GL_NONE, false);
@@ -1669,7 +1678,7 @@ public class SDLGPURenderBackend extends RenderBackend {
return;
}
final int glId = st.boundTextures[st.activeTextureUnit];
if (glId == 0) return;
if (glId == 0 || dropStorageIfEmpty(st, glId, level, width, 1, 1)) return;
if (level == 0) {
ensureLevel0Storage(st, glId, target, internalFormat, width, 1, 1, GL11.GL_NONE, false);
@@ -1685,7 +1694,7 @@ public class SDLGPURenderBackend extends RenderBackend {
return;
}
final int glId = st.boundTextures[st.activeTextureUnit];
if (glId == 0) return;
if (glId == 0 || dropStorageIfEmpty(st, glId, level, width, height, 1)) return;
if (level == 0) {
ensureLevel0Storage(st, glId, target, internalFormat, width, height, 1, format, true);
@@ -1768,7 +1777,7 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
final ContextState cs = s();
final int destGlId = boundTextureFor(cs, target);
if (destGlId == 0) return;
if (destGlId == 0 || dropStorageIfEmpty(cs, destGlId, level, width, height, 1)) return;
if (level == 0) {
ensureLevel0Storage(cs, destGlId, target, internalFormat, width, height, 1, GL11.GL_NONE, true);
@@ -2023,6 +2032,18 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
@Override public void framebufferTexture1D(int target, int attachment, int textarget, int texture, int level) {
framebufferTexture2D(target, attachment, GL11.GL_TEXTURE_2D, texture, level);
}
@Override public void framebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset) {
throw new UnsupportedOperationException("SDL-GPU FboState attachments store only texture and mip level; no depth-plane (layer_or_depth_plane) slot for glFramebufferTexture3D");
}
@Override public void framebufferTextureLayer(int target, int attachment, int texture, int level, int layer) {
throw new UnsupportedOperationException("SDL-GPU FboState attachments store only texture and mip level; no array-layer slot for glFramebufferTextureLayer");
}
@Override public void framebufferTexture(int target, int attachment, int texture, int level) {
framebufferTexture2D(target, attachment, GL11.GL_TEXTURE_2D, texture, level);
}
@@ -3832,6 +3853,7 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
if (texture == 0) return;
final ContextState st = s();
if (dropStorageIfEmpty(st, texture, level, width, height, 1)) return;
if (level == 0) {
ensureLevel0Storage(st, texture, target, internalformat, width, height, 1, format, true);
} else if (!growForLevel(texture, level)) {
@@ -4068,7 +4090,7 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL11.GL_TEXTURE_WRAP_S -> ss.wrapS;
case GL11.GL_TEXTURE_WRAP_T -> ss.wrapT;
case GL12.GL_TEXTURE_WRAP_R -> ss.wrapR;
case GL12.GL_TEXTURE_MAX_LEVEL -> ss.maxLevel;
case GL12.GL_TEXTURE_MAX_LEVEL -> ss.maxLevel < 0 ? 1000 : ss.maxLevel;
default -> 0;
};
}
@@ -33,7 +33,8 @@ public final class TextureSamplerState {
case GL11.GL_TEXTURE_WRAP_S -> { if (wrapS == param) return false; wrapS = param; }
case GL11.GL_TEXTURE_WRAP_T -> { if (wrapT == param) return false; wrapT = param; }
case GL12.GL_TEXTURE_WRAP_R -> { if (wrapR == param) return false; wrapR = param; }
case GL12.GL_TEXTURE_MAX_LEVEL -> { maxLevel = param; return false; }
// 1000 must map to unset, or every rescued texture allocates a full mip chain.
case GL12.GL_TEXTURE_MAX_LEVEL -> { maxLevel = param >= 1000 ? -1 : param; return false; }
case GL14.GL_TEXTURE_COMPARE_MODE -> { if (compareMode == param) return false; compareMode = param; }
case GL14.GL_TEXTURE_COMPARE_FUNC -> { if (compareFunc == param) return false; compareFunc = param; }
case GL12.GL_TEXTURE_MIN_LOD, GL12.GL_TEXTURE_MAX_LOD, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT -> { return setf(pname, (float) param); }
@@ -47,6 +47,7 @@ class GlsmSdlSamplerOverflowTest {
private static final int FETCH_GREEN = 17;
private static final int FILTERED_BLUE = 15;
private static final int EXPECTED = 0xFF4080C0;
private static final int RELEASE_DELAY_FRAMES = Reflect.getStatic(GLStateManager.class, "RELEASE_DELAY_FRAMES");
private final int[] textures = new int[SAMPLERS];
private int program;
@@ -282,9 +283,12 @@ class GlsmSdlSamplerOverflowTest {
GLStateManager.glDeleteTextures(textures[FETCH_RED]);
textures[FETCH_RED] = 0;
GlsmSdlHeadlessRig.beginFrame();
GlsmSdlHeadlessRig.endFrame();
assertEquals(beforeDelete - 1, liveTextureCount(), "the copy is released while GLSM retains a 1x1 placeholder for the deleted name");
for (int i = 0; i < RELEASE_DELAY_FRAMES; i++) {
GlsmSdlHeadlessRig.beginFrame();
GLStateManager.onFrameEnd();
GlsmSdlHeadlessRig.endFrame();
}
assertEquals(beforeDelete - 2, liveTextureCount(), "the deleted texture and its copy are released after the delay");
}
@Test
@@ -1,6 +1,9 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@@ -11,6 +14,7 @@ import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
@@ -18,6 +22,7 @@ import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.describe
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.pixelAt;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("glsm-sdl")
@@ -37,6 +42,7 @@ class GlsmSdlVanillaTextureTest {
};
private static final int LIGHTMAP_SIZE = 16;
private static final int RELEASE_DELAY_FRAMES = Reflect.getStatic(GLStateManager.class, "RELEASE_DELAY_FRAMES");
private static final int CLEAR_COLOR = 0xFF000000;
private static int atlasTexture;
@@ -259,4 +265,66 @@ class GlsmSdlVanillaTextureTest {
final int actual = center();
assertColorNear(expected, actual, 2, "display list with bindings outside");
}
private static void texImageEmpty(int level) {
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA8, 0, 0, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
}
@Test
void zeroSizedLevelZeroDropsStorageAndRespecifyRecreatesIt() {
final ResourceManager rm = GlsmSdlHeadlessRig.resourceManager();
final int tex = GlsmSdlHeadlessRig.newNearestTexture();
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 2);
GlsmSdlHeadlessRig.uploadSolidLevel(0, 4, LEVEL_COLORS[0]);
assertEquals(3, rm.getTextureMeta(tex).levels(), "MAX_LEVEL 2 allocates the 4x4 chain");
final long handle = rm.getTextureHandle(tex);
texImageEmpty(1);
assertEquals(handle, rm.getTextureHandle(tex), "zero-sized level 1 must leave storage alone");
texImageEmpty(0);
assertEquals(0L, rm.getTextureHandle(tex), "zero-sized level 0 must release the handle");
assertNull(rm.getTextureMeta(tex), "zero-sized level 0 must drop the meta");
texImageEmpty(1);
assertEquals(0L, rm.getTextureHandle(tex), "zero-sized level 1 on an empty texture must not allocate");
GlsmSdlHeadlessRig.uploadSolidLevel(0, 4, LEVEL_COLORS[0]);
assertNotEquals(0L, rm.getTextureHandle(tex), "respecified level 0 must recreate storage");
assertEquals(4, rm.getTextureMeta(tex).width(), "recreated width");
assertEquals(3, rm.getTextureMeta(tex).levels(), "MAX_LEVEL survives the release");
GLStateManager.glDeleteTextures(tex);
}
@Test
void rescuedTextureAllocatesOneLevelUntilAHigherLevelIsUploaded() {
final ResourceManager rm = GlsmSdlHeadlessRig.resourceManager();
final int tex = GlsmSdlHeadlessRig.newNearestTexture();
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 2);
GLStateManager.glDeleteTextures(tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GlsmSdlHeadlessRig.uploadSolidLevel(0, 4, LEVEL_COLORS[0]);
assertEquals(1, rm.getTextureMeta(tex).levels(), "level-0 upload");
GlsmSdlHeadlessRig.uploadSolidLevel(1, 2, LEVEL_COLORS[1]);
assertEquals(3, rm.getTextureMeta(tex).levels(), "level-1 upload grows to the full chain");
GLStateManager.glDeleteTextures(tex);
}
@Test
void deletingA2DTextureKeepsTheUnitsNon2DBinding() {
final ResourceManager rm = GlsmSdlHeadlessRig.resourceManager();
final int deleted = GlsmSdlHeadlessRig.newNearestTexture();
GlsmSdlHeadlessRig.uploadSolidLevel(0, 4, LEVEL_COLORS[0]);
final int volume = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, volume);
GLStateManager.glDeleteTextures(deleted);
assertEquals(volume, BackendManager.RENDER_BACKEND.getInteger(GL12.GL_TEXTURE_BINDING_3D), "after the delete");
for (int i = 0; i < RELEASE_DELAY_FRAMES; i++) GLStateManager.onFrameEnd();
assertEquals(0L, rm.getTextureHandle(deleted), "the frame-end release must have run");
assertEquals(volume, BackendManager.RENDER_BACKEND.getInteger(GL12.GL_TEXTURE_BINDING_3D), "after the release");
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GLStateManager.glDeleteTextures(volume);
}
}
@@ -35,6 +35,7 @@ public final class TracyFramePlots {
private static final PlotDelta P_GL_DRAW_CALLS = new PlotDelta("gl.drawCalls");
private static final PlotDelta P_GL_TEX_BIND_MISSES = new PlotDelta("gl.texBindMisses");
private static final PlotDelta P_GL_PROGRAM_SWITCHES = new PlotDelta("gl.programSwitches");
private static final long P_GL_TEX_AWAITING_RELEASE = Tracy.plotHandle("gl.texAwaitingRelease");
private static final PlotDelta P_GLSM_BRACKETS = new PlotDelta("glsm.brackets");
private static final PlotDelta P_GLSM_COW_SLOTS = new PlotDelta("glsm.cowSlots");
private static final PlotDelta P_GLSM_POP_RESTORES = new PlotDelta("glsm.popRestores");
@@ -191,6 +192,7 @@ public final class TracyFramePlots {
P_GL_DRAW_CALLS.plot(GLStateManager.drawCalls);
P_GL_TEX_BIND_MISSES.plot(GLStateManager.texBindMisses);
P_GL_PROGRAM_SWITCHES.plot(GLStateManager.programSwitches);
Tracy.plotInt(P_GL_TEX_AWAITING_RELEASE, GLStateManager.deferredTexturesAwaitingRelease());
P_GLSM_BRACKETS.plot(GLStateManager.attribPushes);
P_GLSM_COW_SLOTS.plot(GLStateManager.attribSlotsSaved);
P_GLSM_POP_RESTORES.plot(GLStateManager.attribValueRestores);