Compare commits

...
18 Commits
Author SHA1 Message Date
Jason Mitchell a8c29fa3f9 Give each GL drawable its own GLSM context (#2142) 2026-09-26 22:31:07 -07:00
Eclipse-Sol 7c28a69bfe Optimize Enchant Glint Rendering (#2140)
* initial commit for enchant-glint-optimizations

* Fix spacing issues and remove GlintCompatibility.java
2026-09-26 19:21:21 -04:00
Eclipse-Sol 7f3fb35ebb Use actual camera when doing cloud related culling and fuctions (#2139) 2026-09-26 14:58:03 -07:00
Jason Mitchell 44c930f4ab Fix scissor clears wiping the whole screen on SDL-GPU (#2138) 2026-09-26 13:54:10 -07:00
Jason Mitchell b76284048a Fix shader packs in modded dimensions (#2137)
* Use world0 for unmapped dimensions
* Fix iris warnings for shaderpacks

Fixes https://github.com/GTNewHorizons/Angelica/issues/2128
2026-09-26 13:18:17 -04:00
Jason Mitchell 762c5661eb Stop blocking chunk workers on deferred ISBRH blocks (#2136) 2026-09-26 08:44:22 -07:00
Jason Mitchell 1babfd968c Properly reset/flush state for crash interceptors (#2133) 2026-09-25 20:43:05 -07:00
Eclipse-Sol 888a4de0c9 Add Galaxy Space compat and fix culling issues (#2131) 2026-09-25 19:18:45 -07:00
DarkShadow44 a23cefba92 Only enable shader DH compat if DH is enabled (#2130) 2026-09-23 17:42:45 -07:00
Eclipse-Sol f2c07dea28 Weather instancing (#2127)
* Initial commit for weather-instancing

* attempt to make the builder less ugly
2026-09-22 19:27:05 -04:00
Jason Mitchell 53776684c8 Properly handle display lists with the push/pop optimizations (#2126) 2026-09-22 09:23:49 -07:00
Jason Mitchell 9dd60f6a75 Render pass and state stack optimizations (#2124) 2026-09-21 20:52:51 -07:00
Alexdoru 87c83f0645 fix asm class dumper following rfb changes (#2123) 2026-09-21 12:35:37 -07:00
Eclipse-Sol c578970a66 Fix edge cases with cloud wraps (#2122) 2026-09-20 17:16:03 +00:00
Eclipse-Sol ef68af5850 Fix texture filtering sampling regression (#2115) 2026-09-19 10:12:14 -07:00
James b6a4497fb3 Redirected Util.checkGLError to respect disableErrorChecks (#2110) 2026-09-18 22:33:17 -07:00
Eclipse-Sol 939e31461a More Cloud Optimizations (#2114) 2026-09-18 21:53:50 -07:00
Eclipse-Sol 02f0b0fca1 Fix mipmaps bleeding (#2112) 2026-09-17 20:29:53 -07:00
305 changed files with 19178 additions and 5667 deletions

No files matched your search

+1
View File
@@ -93,6 +93,7 @@ DependencyHandlerScope.of(dependencies).apply {
shadowImplementation(project(":lwjgl3-backend")) { isTransitive = false }
shadowImplementation(project(":sdl-gpu")) { isTransitive = false }
runtimeOnlyNonPublishable(project(":tracy-client")) { isTransitive = false }
devOnlyNonPublishable(libs.async.profiler)
shadowImplementation(libs.eventbus)
shadowImplementation(libs.jcpp) // Apache 2.0
@@ -37,6 +37,7 @@ public final class SystemProperties {
public static final boolean SDL_VERIFY_PER_FRAME_UNIFORM_BLOCK = Boolean.getBoolean("angelica.sdlgpu.verifyPerFrameUniformBlock");
public static final int SDL_FRAMES_IN_FLIGHT = Integer.getInteger("angelica.sdlgpu.framesInFlight", 2);
public static final boolean DISABLE_SDL_PRESENTER_THREAD = Boolean.getBoolean("angelica.sdlgpu.disablePresenterThread");
public static final boolean SDL_DISABLE_IN_PASS_CLEAR = Boolean.getBoolean("angelica.sdlgpu.disableInPassClear");
// Tracy
public static final boolean TRACY = Boolean.getBoolean("angelica.tracy");
@@ -44,6 +45,11 @@ public final class SystemProperties {
public static final String TRACY_DIR = System.getProperty("angelica.tracy.dir", "angelica" + File.separator + "natives" + File.separator + "tracy");
public static final int TRACY_MAX_SRC_LOCS = Math.max(16, Integer.getInteger("angelica.tracy.maxSrcLocs", 4096));
// Profiling
public static final String PROFILE_OPTS = System.getProperty("angelica.profile.opts", "event=wall,interval=5ms,alloc=512k,lock=10ms");
public static final String PROFILE_DIR = System.getProperty("angelica.profile.dir", "angelica" + File.separator + "profiles");
public static final String PROFILE_OUTPUT = System.getProperty("angelica.profile.output", "");
// Flyby
public static final String FLYBY_ROUTE = System.getProperty("angelica.flyby.route", "");
public static final boolean FLYBY_WAIT_FOR_TRACY = Boolean.getBoolean("angelica.flyby.waitForTracy");
@@ -56,6 +62,8 @@ public final class SystemProperties {
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
// Debug
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
@@ -70,6 +78,7 @@ public final class SystemProperties {
public static final boolean FFP_TRACE = Boolean.getBoolean("angelica.debug.ffpTrace");
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
public static final boolean FORCE_ORPHAN_STREAMING = Boolean.getBoolean("angelica.debug.forceOrphanStreaming");
public static final boolean WEATHER_REBUILD_ALWAYS = Boolean.getBoolean("angelica.debug.weatherRebuildAlways");
public static final String SHADER_DUMP_ROOT = "angelica_dumps";
// Set by us, read by celeritas
@@ -118,6 +127,20 @@ public final class SystemProperties {
CONFIGURED
}
public enum FlybyWeather {
CLEAR,
RAIN,
THUNDER;
public boolean isRaining() {
return this != CLEAR;
}
public boolean isThundering() {
return this == THUNDER;
}
}
private static <E extends Enum<E>> E parseEnum(String key, E whenAbsent, E whenInvalid, Class<E> type) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return whenAbsent;
@@ -0,0 +1,24 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
public final class ClipPlaneBooleanState extends BooleanStateStack {
private final GLContextState owner;
private final int index;
ClipPlaneBooleanState(GLContextState owner, int index, int glCap) {
super(glCap);
this.owner = owner;
this.index = index;
}
@Override
protected void onEnabledChanged() {
if (isEnabled()) {
owner.clipPlaneEnabledMask |= (1 << index);
} else {
owner.clipPlaneEnabledMask &= ~(1 << index);
}
}
}
@@ -117,6 +117,7 @@ public class CompatUniformManager {
private static final class ProgramUniforms {
final int[] locs;
GLContextState owner;
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
ProgramUniforms(int[] locs) { this.locs = locs; }
@@ -161,7 +162,13 @@ public class CompatUniformManager {
}
if (hasAny) {
programUniforms.put(program, new ProgramUniforms(locs));
final ProgramUniforms uniforms = new ProgramUniforms(locs);
final boolean locked = GLStateManager.acquireDrawLock();
try {
programUniforms.put(program, uniforms);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
@@ -171,16 +178,21 @@ public class CompatUniformManager {
*
* @return Whether the program needs emulation (FFP or Iris Program)
*/
public static boolean refreshCompatUniforms(int program) {
public static boolean refreshCompatUniforms(int program, GLContextState glCtx) {
final ProgramUniforms pu = programUniforms.get(program);
if (pu == null) return false;
final int[] locs = pu.locs;
if (pu.owner != glCtx) {
pu.owner = glCtx;
pu.lastMvGen = pu.lastMvLinearGen = pu.lastProjGen = pu.lastTexMatGen = -1;
pu.lastFragmentGen = pu.lastLightingGen = pu.lastClipPlaneGen = -1;
}
// Matrix uniforms — skip if this program's storage already holds the current generation
final int mvGen = GLStateManager.getMvGeneration();
final int mvLinearGen = GLStateManager.getMvLinearGeneration();
final int projGen = GLStateManager.getProjGeneration();
final int texMatGen = GLStateManager.getTexMatrixGeneration();
final int mvGen = glCtx.mvGeneration;
final int mvLinearGen = glCtx.mvLinearGeneration;
final int projGen = glCtx.projGeneration;
final int texMatGen = glCtx.texMatrixGeneration;
final boolean mvChanged = mvGen != pu.lastMvGen;
final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen;
final boolean projChanged = projGen != pu.lastProjGen;
@@ -194,14 +206,14 @@ public class CompatUniformManager {
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.getFragmentGeneration();
final int fragGen = glCtx.fragmentGeneration;
if (fragGen != pu.lastFragmentGen) {
pu.lastFragmentGen = fragGen;
uploadFragmentUniforms(locs);
}
// Lighting-derived uniforms (scene color, light sources, material)
final int litGen = GLStateManager.getLightingGeneration();
final int litGen = glCtx.lightingGeneration;
if (litGen != pu.lastLightingGen) {
pu.lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
@@ -211,7 +223,7 @@ public class CompatUniformManager {
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.getClipPlaneGeneration();
final int cpGen = glCtx.clipPlaneGeneration;
if (cpGen != pu.lastClipPlaneGen) {
pu.lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
@@ -438,7 +450,12 @@ public class CompatUniformManager {
}
public static void onDeleteProgram(int program) {
programUniforms.remove(program);
final boolean locked = GLStateManager.acquireDrawLock();
try {
programUniforms.remove(program);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
}
public static boolean hasProgram(int program) {
@@ -59,7 +59,7 @@ public class DisplayListIDAllocator {
numSetElements = 1;
}
public int allocRange(int count) {
public synchronized int allocRange(int count) {
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
if (count == 1) return allocSingle();
return allocMultiple(count);
@@ -132,7 +132,7 @@ public class DisplayListIDAllocator {
return numElements;
}
public void free(int id) {
public synchronized void free(int id) {
if (id <= 0) return; // ID 0 is reserved
int idx = id / 32;
if (idx >= numElements) return;
@@ -147,7 +147,7 @@ public class DisplayListIDAllocator {
}
}
public void freeRange(int base, int count) {
public synchronized void freeRange(int base, int count) {
if (count <= 0 || base <= 0) return;
int startIdx = base / 32;
@@ -166,7 +166,7 @@ public class DisplayListIDAllocator {
}
}
public boolean isAllocated(int id) {
public synchronized boolean isAllocated(int id) {
if (id <= 0) return false;
int idx = id / 32;
if (idx >= numSetElements) return false;
@@ -79,6 +79,8 @@ public class DisplayListManager {
// Display list compilation state (current/active context)
private static int glListMode = 0;
private static int glListId = -1;
private static int recordedStatePushes;
private static final DisplayListCommand POP_STATE = GLStateManager::popState;
private static CommandRecorder currentRecorder = null; // Command recorder (null when not recording)
private static volatile Thread recordingThread = null; // Thread that started recording (for thread-safety)
private static List<AccumulatedDraw> accumulatedDraws = null; // Accumulates quad draws for batching
@@ -114,6 +116,7 @@ public class DisplayListManager {
List<AccumulatedDraw> draws,
DisplayListCallback callback,
StackTraceElement[] stackTrace,
int recordedStatePushes,
// Debug logging fields (only used when LOG_DISPLAY_LIST_COMPILATION)
List<String> pendingOps,
@@ -141,7 +144,7 @@ public class DisplayListManager {
}
public static String describeCurrentCompilation() {
if (currentRecorder == null) {
if (!isRecording()) {
return "(not compiling)";
}
final String mode = glListMode == GL11.GL_COMPILE_AND_EXECUTE ? "GL_COMPILE_AND_EXECUTE" : "GL_COMPILE";
@@ -150,16 +153,20 @@ public class DisplayListManager {
}
public static boolean isCompileAndExecute() {
return glListMode == GL11.GL_COMPILE_AND_EXECUTE;
return isRecording() && glListMode == GL11.GL_COMPILE_AND_EXECUTE;
}
public static CommandRecorder pauseRecording() {
if (Thread.currentThread() != recordingThread) return null;
final CommandRecorder r = currentRecorder;
currentRecorder = null;
return r;
}
public static void resumeRecording(CommandRecorder r) {
if (Thread.currentThread() != recordingThread) {
throw new IllegalStateException("Cannot resume another thread's display list compilation");
}
currentRecorder = r;
}
@@ -226,15 +233,15 @@ public class DisplayListManager {
}
public static int getRecordingListId() {
return glListId;
return Thread.currentThread() == recordingThread ? glListId : -1;
}
public static int getListMode() {
return glListMode;
return Thread.currentThread() == recordingThread ? glListMode : 0;
}
public static void trackDrawRangeSource(String source) {
if (drawRangeSources != null) {
if (Thread.currentThread() == recordingThread && drawRangeSources != null) {
drawRangeSources.add(source);
}
}
@@ -451,6 +458,25 @@ public class DisplayListManager {
currentRecorder.writePopAttrib();
}
public static int virtualStateDepth(RecordMode mode) {
return mode == RecordMode.COMPILE ? recordedStatePushes : 0;
}
public static void recordPushState(StateSet set) {
currentRecorder.writeComplexCommand(set.pushCommand);
recordedStatePushes++;
}
public static boolean recordPopStateIfPending() {
if (recordedStatePushes > 0) {
drawBarrier();
currentRecorder.writeComplexCommand(POP_STATE);
recordedStatePushes--;
return true;
}
return false;
}
public static void recordFogf(int pname, float param) {
drawBarrier();
currentRecorder.writeFogf(pname, param);
@@ -739,8 +765,11 @@ public class DisplayListManager {
* @return true if the display list exists, false otherwise
*/
public static boolean displayListExists(int list) {
if (displayListCache.containsKey(list)) {
return true;
final boolean locked = GLStateManager.acquireDrawLock();
try {
if (displayListCache.containsKey(list)) return true;
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
// Check VBOManager for GTNHLib compatibility (negative IDs)
if (list < -1) {
@@ -756,7 +785,12 @@ public class DisplayListManager {
* @return The CompiledDisplayList, or null if not found
*/
public static CompiledDisplayList getDisplayList(int list) {
return displayListCache.get(list);
final boolean locked = GLStateManager.acquireDrawLock();
try {
return displayListCache.get(list);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
}
/**
@@ -765,71 +799,105 @@ public class DisplayListManager {
* GL_COMPILE: Commands are recorded only (not executed), GLSM cache unchanged
* GL_COMPILE_AND_EXECUTE: Commands are recorded AND executed (GLSM cache updated)
*/
public static void glNewList(int list, int mode) {
// Handle nested compilation - push current context onto stack
public static synchronized void glNewList(int list, int mode) {
final Thread recorder = recordingThread;
if (recorder != null && recorder != Thread.currentThread()) {
throw new IllegalStateException("glNewList(" + list + ") on " + Thread.currentThread().getName() + " while " + recorder.getName() + " is compiling a display list");
}
final boolean isNested = glListMode > 0;
final int captureDepth = TessellatorManager.getDirectCaptureDepth();
boolean pushed = false;
if (!isNested) recordingThread = Thread.currentThread();
try {
if (isNested) {
flushAll();
final CompilationContext parentContext = new CompilationContext(
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
compilationStackTrace, recordedStatePushes, pendingTransformOps, multMatrixSources, drawRangeSources
);
compilationStack.push(parentContext);
pushed = true;
}
if (isNested) {
flushAll();
// Nested display list compilation violates OpenGL spec, but some of our optimizations require it
// Save current compilation context and start fresh for nested list
final CompilationContext parentContext = new CompilationContext(
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
compilationStackTrace, pendingTransformOps, multMatrixSources, drawRangeSources
);
compilationStack.push(parentContext);
glListId = list;
glListMode = mode;
currentRecorder = new CommandRecorder();
accumulatedDraws = new ArrayList<>(8);
transformCallback = new DisplayListCallback();
recordedStatePushes = 0;
compilationStackTrace = SystemProperties.LOG_DISPLAY_LIST_COMPILATION ? Thread.currentThread().getStackTrace() : null;
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
pendingTransformOps = new ArrayList<>();
multMatrixSources = new ArrayList<>();
drawRangeSources = new ArrayList<>();
} else {
pendingTransformOps = null;
multMatrixSources = null;
drawRangeSources = null;
}
TessellatorManager.startCapturingDirect(transformCallback);
} catch (RuntimeException | Error failure) {
try {
if (TessellatorManager.getDirectCaptureDepth() > captureDepth) TessellatorManager.stopCapturingDirect();
} catch (RuntimeException | Error cleanup) {
failure.addSuppressed(cleanup);
}
try {
if (pushed) {
final CommandRecorder parent = compilationStack.peek().recorder();
try {
if (currentRecorder != null && currentRecorder != parent) currentRecorder.delete();
} finally {
popCompilationContext();
}
} else if (!isNested) {
try {
if (currentRecorder != null) currentRecorder.delete();
} finally {
resetCompilationState();
}
}
} catch (RuntimeException | Error cleanup) {
failure.addSuppressed(cleanup);
}
throw failure;
}
// Initialize fresh context for this (possibly nested) list
glListId = list;
glListMode = mode;
recordingThread = Thread.currentThread(); // Track which thread is recording
currentRecorder = new CommandRecorder(); // Create command recorder
accumulatedDraws = new ArrayList<>(8); // Fewer draws than commands typically
transformCallback = new DisplayListCallback();
compilationStackTrace = SystemProperties.LOG_DISPLAY_LIST_COMPILATION ? Thread.currentThread().getStackTrace() : null;
// Initialize debug logging fields (only when logging enabled)
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
pendingTransformOps = new ArrayList<>();
multMatrixSources = new ArrayList<>();
drawRangeSources = new ArrayList<>();
} else {
pendingTransformOps = null;
multMatrixSources = null;
drawRangeSources = null;
}
TessellatorManager.startCapturingDirect(transformCallback);
}
/**
* End display list compilation and build optimized/unoptimized versions.
*/
public static void glEndList() {
public static synchronized void glEndList() {
final Thread recorder = recordingThread;
if (recorder != null && recorder != Thread.currentThread()) {
throw new IllegalStateException("glEndList on " + Thread.currentThread().getName() + " while " + recorder.getName() + " is compiling a display list");
}
if (glListMode == 0) {
GLStateManager.warnOnce("endlist-outside", "glEndList called outside of a display list!");
return;
}
if (Tracy.ENABLED) Tracy.beginZone(Z_GL_LIST_COMPILE);
try {
TessellatorManager.stopCapturingDirect();
finishCurrentList();
} finally {
if (Tracy.ENABLED) Tracy.endZone();
// Non-null here means finishCurrentList threw before transferring or releasing
// ownership of the recorder.
if (currentRecorder != null) {
currentRecorder.delete();
try {
if (currentRecorder != null) currentRecorder.delete();
} finally {
popCompilationContext();
}
// Must always run, even when compilation fails - a leaked context leaves
// isRecording() true forever and every guarded GL call throws afterward.
popCompilationContext();
}
}
private static void finishCurrentList() {
// Stop compiling mode (works for both root and nested lists now)
TessellatorManager.stopCapturingDirect();
boolean unbalancedStatePush = false;
while (recordPopStateIfPending()) unbalancedStatePush = true;
if (unbalancedStatePush) {
GLStateManager.warnOnce("unbalanced-state-push", "Display list {} ended with unpopped internal state pushes - closing them", glListId);
}
flushAll();
@@ -921,6 +989,7 @@ public class DisplayListManager {
accumulatedDraws = parentContext.draws;
transformCallback = parentContext.callback;
compilationStackTrace = parentContext.stackTrace;
recordedStatePushes = parentContext.recordedStatePushes;
pendingTransformOps = parentContext.pendingOps;
multMatrixSources = parentContext.matrixSources;
drawRangeSources = parentContext.drawSources;
@@ -936,7 +1005,6 @@ public class DisplayListManager {
/** Clear all root-level compilation state back to "not recording". */
private static void resetCompilationState() {
currentRecorder = null;
recordingThread = null;
accumulatedDraws = null;
pendingDraw = null;
transformCallback = null;
@@ -946,9 +1014,14 @@ public class DisplayListManager {
drawRangeSources = null;
glListId = -1;
glListMode = 0;
recordedStatePushes = 0;
recordingThread = null;
}
public static void abortCompilation() {
public static synchronized void abortCompilation() {
if (recordingThread != null && recordingThread != Thread.currentThread()) {
throw new IllegalStateException("Cannot abort another thread's display list compilation");
}
if (glListMode == 0 && compilationStack.isEmpty()) {
return;
}
@@ -976,7 +1049,7 @@ public class DisplayListManager {
}
public static void abortIfLeaked() {
if (glListMode != 0 || !compilationStack.isEmpty()) {
if (Thread.currentThread() == recordingThread && (glListMode != 0 || !compilationStack.isEmpty())) {
GLStateManager.warnOnce("frame-leak", "Display list {} left open across a frame boundary - aborting to recover", glListId);
abortCompilation();
}
@@ -1012,7 +1085,7 @@ public class DisplayListManager {
* Execute a compiled display list.
*/
public static void glCallList(int list) {
if (currentRecorder != null) {
if (isRecording()) {
recordCallList(list);
if (getListMode() != GL11.GL_COMPILE) {
@@ -3,18 +3,13 @@ package com.gtnewhorizons.angelica.glsm;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Feature {
@@ -23,52 +18,49 @@ public class Feature {
GL13.GL_MULTISAMPLE_BIT, GL11.GL_PIXEL_MODE_BIT, GL11.GL_POINT_BIT, GL11.GL_POLYGON_BIT, GL11.GL_POLYGON_STIPPLE_BIT, GL11.GL_SCISSOR_BIT,
GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_TEXTURE_BIT, GL11.GL_TRANSFORM_BIT, GL11.GL_VIEWPORT_BIT };
static final Int2ObjectMap<List<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectMap<IStateStack<?>[]> maskToNonBooleanStacksMap = new Int2ObjectOpenHashMap<>();
private static final long[] NO_MEMBERS = new long[0];
static List<IStateStack<?>> maskToFeatures(int mask) {
if(maskToFeaturesMap.containsKey(mask)) {
return maskToFeaturesMap.get(mask);
}
private static final Int2ObjectMap<long[]> maskToMembersMap = new Int2ObjectOpenHashMap<>();
final Set<IStateStack<?>> features = new HashSet<>();
for(int attrib : Feature.supportedAttribs) {
if((mask & attrib) == attrib) {
features.addAll(getFeatures(attrib));
}
}
final List<IStateStack<?>> asList = new ArrayList<>(features);
maskToFeaturesMap.put(mask, asList);
return asList;
}
/**
* Returns only non-BooleanStateStack instances for the given mask.
* These use traditional push/pop without global depth tracking.
*/
static IStateStack<?>[] maskToNonBooleanStacks(int mask) {
IStateStack<?>[] cached = maskToNonBooleanStacksMap.get(mask);
static long[] maskToMembers(int mask) {
final long[] cached = maskToMembersMap.get(mask);
if (cached != null) {
return cached;
}
final List<IStateStack<?>> all = maskToFeatures(mask);
final List<IStateStack<?>> nonBooleans = new ArrayList<>();
for (int i = 0; i < all.size(); i++) {
final IStateStack<?> stack = all.get(i);
if (!(stack instanceof BooleanStateStack)) {
nonBooleans.add(stack);
long[] result = NO_MEMBERS;
for (int attrib : Feature.supportedAttribs) {
if ((mask & attrib) == attrib) {
result = union(result, attribToMembers.get(attrib));
}
}
cached = nonBooleans.toArray(new IStateStack<?>[0]);
maskToNonBooleanStacksMap.put(mask, cached);
return cached;
maskToMembersMap.put(mask, result);
return result;
}
private static final Map<Integer, Set<IStateStack<?>>> attribToFeatures = new HashMap<>();
private static long[] union(long[] a, long[] b) {
if (b == null || b.length == 0) return a;
if (a.length == 0) return b.clone();
final long[] result = new long[Math.max(a.length, b.length)];
System.arraycopy(a, 0, result, 0, a.length);
for (int i = 0; i < b.length; i++) result[i] |= b[i];
return result;
}
private static long[] toMembers(Set<IStateStack<?>> stacks) {
if (stacks.isEmpty()) return NO_MEMBERS;
int max = 0;
for (IStateStack<?> s : stacks) max = Math.max(max, ((CowStateStack<?>) s).stackId());
final long[] bits = new long[(max >> 6) + 1];
for (IStateStack<?> s : stacks) {
final int id = ((CowStateStack<?>) s).stackId();
bits[id >> 6] |= 1L << id;
}
return bits;
}
private static final Int2ObjectMap<long[]> attribToMembers = new Int2ObjectOpenHashMap<>();
/**
* Helper method to add all texture gen states (S, T, R, Q) for all texture units to a collection.
@@ -84,6 +76,8 @@ public class Feature {
}
static {
final Int2ObjectMap<Set<IStateStack<?>>> attribToFeatures = new Int2ObjectOpenHashMap<>();
attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of(
GLStateManager.getAlphaTest()
, GLStateManager.getAlphaState()
@@ -191,11 +185,25 @@ public class Feature {
attribToFeatures.put(GL11.GL_ENABLE_BIT, enableBits);
attribToFeatures.put(GL11.GL_EVAL_BIT, ImmutableSet.of(
// GL_MAP1_x enable bits, where x is a map type
// GL_MAP2_x enable bits, where x is a map type
// 1D grid endpoints and divisions
// 2D grid endpoints and divisions
// GL_AUTO_NORMAL enable bit
GLStateManager.getAutoNormalState()
, GLStateManager.getMap1Color4State()
, GLStateManager.getMap1IndexState()
, GLStateManager.getMap1NormalState()
, GLStateManager.getMap1TextureCoord1State()
, GLStateManager.getMap1TextureCoord2State()
, GLStateManager.getMap1TextureCoord3State()
, GLStateManager.getMap1TextureCoord4State()
, GLStateManager.getMap1Vertex3State()
, GLStateManager.getMap1Vertex4State()
, GLStateManager.getMap2Color4State()
, GLStateManager.getMap2IndexState()
, GLStateManager.getMap2NormalState()
, GLStateManager.getMap2TextureCoord1State()
, GLStateManager.getMap2TextureCoord2State()
, GLStateManager.getMap2TextureCoord3State()
, GLStateManager.getMap2TextureCoord4State()
, GLStateManager.getMap2Vertex3State()
, GLStateManager.getMap2Vertex4State()
));
attribToFeatures.put(GL11.GL_FOG_BIT, ImmutableSet.of(
GLStateManager.getFogMode()
@@ -258,10 +266,10 @@ public class Feature {
// GL_LIST_BASE setting
));
attribToFeatures.put(GL13.GL_MULTISAMPLE_BIT, ImmutableSet.of(
// GL_MULTISAMPLE enable bit
// GL_SAMPLE_ALPHA_TO_COVERAGE flag
// GL_SAMPLE_ALPHA_TO_ONE flag
// GL_SAMPLE_COVERAGE flag
GLStateManager.getMultisampleState()
, GLStateManager.getSampleAlphaToCoverageState()
, GLStateManager.getSampleAlphaToOneState()
, GLStateManager.getSampleCoverageState()
// GL_SAMPLE_COVERAGE_VALUE value
// GL_SAMPLE_COVERAGE_INVERT value
));
@@ -294,7 +302,7 @@ public class Feature {
));
attribToFeatures.put(GL11.GL_SCISSOR_BIT, ImmutableSet.of(
GLStateManager.getScissorTest()
// Scissor box
, GLStateManager.getScissorState()
));
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
GLStateManager.getStencilTest()
@@ -327,22 +335,24 @@ public class Feature {
attribToFeatures.put(GL11.GL_TEXTURE_BIT, textureAttribs);
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, ImmutableSet.of(
final Set<IStateStack<?>> transformAttribs = new HashSet<>(ImmutableSet.of(
// Coefficients of the six clipping planes
GLStateManager.getMatrixMode()
, GLStateManager.getNormalizeState()
, GLStateManager.getRescaleNormalState()
));
for(int i = 0; i < GLStateManager.getClipPlaneStates().length; i++) {
transformAttribs.add(GLStateManager.getClipPlaneStates()[i]);
}
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, transformAttribs);
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of(
GLStateManager.getViewportState()
));
for (Int2ObjectMap.Entry<Set<IStateStack<?>>> entry : attribToFeatures.int2ObjectEntrySet()) {
attribToMembers.put(entry.getIntKey(), toMembers(entry.getValue()));
}
}
public static Set<IStateStack<?>> getFeatures(int attrib) {
return attribToFeatures.getOrDefault(attrib, Collections.emptySet());
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.joml.Matrix4f;
@@ -48,6 +47,10 @@ public class FeedbackManager {
return renderMode == GL11.GL_FEEDBACK;
}
public static int getRenderMode() {
return renderMode;
}
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
if (renderMode == GL11.GL_FEEDBACK) {
GLStateManager.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
@@ -126,30 +129,32 @@ public class FeedbackManager {
feedbackVertex(winX, winY, winZ, winW);
}
private static int positionStride() {
final int s = VAOManager.getAttribEffectiveStride(0);
private static int positionStride(GLContextState glCtx) {
final int s = glCtx.vaos.attribEffectiveStride(0);
return (s != 0) ? s : 12;
}
private static long positionOffset() {
return VAOManager.getAttribOffset(0);
private static long positionOffset(GLContextState glCtx) {
return glCtx.vaos.attribOffset(0);
}
public static void processDrawArrays(int mode, int first, int count) {
computeMVP();
final int stride = positionStride();
final long posOffset = positionOffset();
final GLContextState glCtx = GLStateManager.ctx();
final int stride = positionStride(glCtx);
final long posOffset = positionOffset(glCtx);
final int byteCount = count * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) first * stride + posOffset, readbackBuf);
readPositionBuffer((long) first * stride + posOffset, readbackBuf, glCtx);
emitAllPrimitives(mode, count, i -> i, stride);
}
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
computeMVP();
final int stride = positionStride();
final GLContextState glCtx = GLStateManager.ctx();
final int stride = positionStride(glCtx);
final int elementSize = GLTypes.sizeBytes(type);
final int indexByteCount = indexCount * elementSize;
@@ -165,12 +170,12 @@ public class FeedbackManager {
if (idx > maxIdx) maxIdx = idx;
}
final long posOffset = positionOffset();
final long posOffset = positionOffset(glCtx);
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf, glCtx);
final int base = minIdx;
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
@@ -204,7 +209,8 @@ public class FeedbackManager {
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
final int stride = positionStride();
final GLContextState glCtx = GLStateManager.ctx();
final int stride = positionStride(glCtx);
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
@@ -213,7 +219,7 @@ public class FeedbackManager {
if (idx > maxIdx) maxIdx = idx;
}
readVerticesFromVBO(minIdx, maxIdx, stride);
readVerticesFromVBO(minIdx, maxIdx, stride, glCtx);
final int base = minIdx;
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
}
@@ -245,18 +251,18 @@ public class FeedbackManager {
}
}
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
final long posOffset = positionOffset();
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride, GLContextState glCtx) {
final long posOffset = positionOffset(glCtx);
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf, glCtx);
}
private static void readPositionBuffer(long offset, ByteBuffer destination) {
final int positionVbo = VAOManager.getAttribVBO(0);
final int previousVbo = GLStateManager.getBoundVBO();
private static void readPositionBuffer(long offset, ByteBuffer destination, GLContextState glCtx) {
final int positionVbo = glCtx.vaos.attribVBO(0);
final int previousVbo = glCtx.boundVBO;
if (positionVbo != 0 && positionVbo != previousVbo) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, positionVbo);
@@ -3,13 +3,14 @@ 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.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.Color4Stack;
import com.gtnewhorizons.angelica.glsm.stacks.ColorMaskStack;
import com.gtnewhorizons.angelica.glsm.stacks.CowDepths;
import com.gtnewhorizons.angelica.glsm.stacks.CowDispatch;
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.DepthStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.IntegerStateStack;
@@ -20,8 +21,11 @@ import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
@@ -29,25 +33,41 @@ 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;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingVaos;
import org.joml.Matrix4d;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector4f;
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 java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
class GLContextState {
public class GLContextState {
private final List<IStateStack<?>> allStacksBuilder = new ArrayList<>();
public IStateStack<?>[] allStacks;
final List<BooleanStateStack> allBooleanStates = new ArrayList<>();
private BooleanStateStack track(BooleanStateStack s) {
s.setStackId(StackIdAllocator.nextId());
allBooleanStates.add(s);
allStacksBuilder.add(s);
return s;
}
private <T extends IStateStack<?>> T member(T s) {
allStacksBuilder.add(s);
return s;
}
@@ -68,6 +88,7 @@ class GLContextState {
public int colorGeneration;
// clip plane equation changes
public int clipPlaneGeneration;
public int clipPlaneEnabledMask;
public float overlayR = 0.0f;
public float overlayG = 0.0f;
public float overlayB = 0.0f;
@@ -84,10 +105,15 @@ class GLContextState {
public boolean unit23TexCoordSetDuringDraw = false;
public int maxBoundTextureUnit = 0;
public int maxBoundImageUnit = 0;
public final IntStack attribs = new IntArrayList(GLStateManager.MAX_ATTRIB_STACK_DEPTH);
public final StateSet[] attribSets = new StateSet[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public int attribDepth = 0;
@SuppressWarnings("unchecked")
public final List<IStateStack<?>>[] modifiedAtDepth = new List[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public CowStateStack<?>[] stackById;
public CowDepths[] depthsById;
public int[] restoreBitById;
public int[] restoreUnitById;
public byte[] kindById;
public int[][] modifiedIds;
public int[] modifiedCount;
public final int[] savedMvGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedMvLinearGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedProjGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
@@ -97,29 +123,31 @@ class GLContextState {
public final int[] savedColorGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedNormalGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedTexCoordGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final IntegerStateStack activeTextureUnit = new IntegerStateStack(0);
public final IntegerStateStack activeTextureUnit = member(new IntegerStateStack(0, StackIdAllocator.nextId()).restoreBit(StateSet.R_ACTIVE_UNIT));
public int clientActiveTextureUnit = 0;
public final IntegerStateStack shadeModelState = new IntegerStateStack(GL11.GL_SMOOTH);
public final IntegerStateStack shadeModelState = member(new IntegerStateStack(GL11.GL_SMOOTH, StackIdAllocator.nextId()));
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 BlendStateStack blendState = member(new BlendStateStack(StackIdAllocator.nextId()));
public final BooleanStateStack blendMode = track(new BooleanStateStack(GL11.GL_BLEND));
public final BooleanStateStack scissorTest = track(new BooleanStateStack(GL11.GL_SCISSOR_TEST));
public final DepthStateStack depthState = new DepthStateStack();
public final BooleanStateStack rasterizerDiscard = track(new BooleanStateStack(GL30.GL_RASTERIZER_DISCARD));
public final DepthStateStack depthState = member(new DepthStateStack(StackIdAllocator.nextId()));
public final BooleanStateStack depthTest = track(new BooleanStateStack(GL11.GL_DEPTH_TEST));
public final FogStateStack fogState = new FogStateStack();
public final FogStateStack fogState = member(new FogStateStack(StackIdAllocator.nextId()));
public final BooleanStateStack fogMode = track(new BooleanStateStack(GL11.GL_FOG, false, true));
public final Color4Stack color = new Color4Stack();
public final Color4Stack color = member(new Color4Stack(StackIdAllocator.nextId()));
// GL_CURRENT_SECONDARY_COLOR - alpha is always 1 and unused, the color sum only adds RGB
public final Color4Stack secondaryColor = new Color4Stack(new Color4(0.0F, 0.0F, 0.0F, 1.0F));
public final Color4Stack secondaryColor = member(new Color4Stack(StackIdAllocator.nextId(), new Color4(0.0F, 0.0F, 0.0F, 1.0F)).restoreBit(StateSet.R_SECONDARY_COLOR));
public final BooleanStateStack colorSumState = track(new BooleanStateStack(GL14.GL_COLOR_SUM, false, true));
public final Color4Stack clearColor = new Color4Stack(new Color4(0.0F, 0.0F, 0.0F, 0.0F));
public final ColorMaskStack colorMask = new ColorMaskStack();
public final IntegerStateStack drawBuffer = new IntegerStateStack(GLStateManager.DEFAULT_DRAW_BUFFER);
public final IntegerStateStack logicOpMode = new IntegerStateStack(GL11.GL_COPY);
public final Color4Stack clearColor = member(new Color4Stack(StackIdAllocator.nextId(), new Color4(0.0F, 0.0F, 0.0F, 0.0F)).restoreBit(StateSet.R_CLEAR_COLOR));
public final ColorMaskStack colorMask = member(new ColorMaskStack(StackIdAllocator.nextId()));
public final IntegerStateStack drawBuffer = member(new IntegerStateStack(GLStateManager.DEFAULT_DRAW_BUFFER, StackIdAllocator.nextId()).restoreBit(StateSet.R_DRAW_BUFFER));
public final IntegerStateStack logicOpMode = member(new IntegerStateStack(GL11.GL_COPY, StackIdAllocator.nextId()).restoreBit(StateSet.R_LOGIC_OP));
public final BooleanStateStack cullState = track(new BooleanStateStack(GL11.GL_CULL_FACE));
public final AlphaStateStack alphaState = new AlphaStateStack();
public final AlphaState restoredAlphaScratch = new AlphaState();
public final AlphaStateStack alphaState = member(new AlphaStateStack(StackIdAllocator.nextId()));
public final BooleanStateStack alphaTest = track(new BooleanStateStack(GL11.GL_ALPHA_TEST, false, true));
public final BooleanStateStack lightingState = track(new BooleanStateStack(GL11.GL_LIGHTING, false, true));
public final BooleanStateStack rescaleNormalState = track(new BooleanStateStack(GL12.GL_RESCALE_NORMAL, false, true));
@@ -140,10 +168,10 @@ class GLContextState {
public final BooleanStateStack polygonOffsetPointState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_POINT));
public final BooleanStateStack polygonOffsetLineState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_LINE));
public final BooleanStateStack polygonOffsetFillState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_FILL));
public final LineStateStack lineState = new LineStateStack();
public final PointStateStack pointState = new PointStateStack();
public final PolygonStateStack polygonState = new PolygonStateStack();
public final StencilStateStack stencilState = new StencilStateStack();
public final LineStateStack lineState = member(new LineStateStack(StackIdAllocator.nextId()));
public final PointStateStack pointState = member(new PointStateStack(StackIdAllocator.nextId()));
public final PolygonStateStack polygonState = member(new PolygonStateStack(StackIdAllocator.nextId()));
public final StencilStateStack stencilState = member(new StencilStateStack(StackIdAllocator.nextId()));
public int stencilBitMask = 0xFFFFFFFF;
public final BooleanStateStack autoNormalState = track(new BooleanStateStack(GL11.GL_AUTO_NORMAL, false, true));
public final BooleanStateStack map1Color4State = track(new BooleanStateStack(GL11.GL_MAP1_COLOR_4, false, true));
@@ -168,19 +196,21 @@ class GLContextState {
public final ClipPlaneState clipPlaneState = new ClipPlaneState();
public final FloatBuffer queryScratch = BufferUtils.createFloatBuffer(16);
public final DoubleBuffer queryScratchDouble = BufferUtils.createDoubleBuffer(16);
public final MatrixModeStack matrixMode = new MatrixModeStack();
public final MatrixModeStack matrixMode = member(new MatrixModeStack(StackIdAllocator.nextId()));
public final Matrix4fStack modelViewMatrix = new Matrix4fStack(GLStateManager.MAX_MODELVIEW_STACK_DEPTH);
public final Matrix4fStack projectionMatrix = new Matrix4fStack(GLStateManager.MAX_PROJECTION_STACK_DEPTH);
public final BooleanStateStack[] lightStates = new BooleanStateStack[8];
public final LightStateStack[] lightDataStates = new LightStateStack[8];
public final BooleanStateStack colorMaterial = track(new BooleanStateStack(GL11.GL_COLOR_MATERIAL, false, true));
public final IntegerStateStack colorMaterialFace = new IntegerStateStack(GL11.GL_FRONT_AND_BACK);
public final IntegerStateStack colorMaterialParameter = new IntegerStateStack(GL11.GL_AMBIENT_AND_DIFFUSE);
public final LightModelStateStack lightModel = new LightModelStateStack();
public final MaterialStateStack frontMaterial = new MaterialStateStack(GL11.GL_FRONT);
public final MaterialStateStack backMaterial = new MaterialStateStack(GL11.GL_BACK);
public final ViewPortStateStack viewportState = new ViewPortStateStack();
public final IntegerStateStack colorMaterialFace = member(new IntegerStateStack(GL11.GL_FRONT_AND_BACK, StackIdAllocator.nextId()));
public final IntegerStateStack colorMaterialParameter = member(new IntegerStateStack(GL11.GL_AMBIENT_AND_DIFFUSE, StackIdAllocator.nextId()));
public final LightModelStateStack lightModel = member(new LightModelStateStack(StackIdAllocator.nextId()));
public final MaterialStateStack frontMaterial = member(new MaterialStateStack(GL11.GL_FRONT, StackIdAllocator.nextId()));
public final MaterialStateStack backMaterial = member(new MaterialStateStack(GL11.GL_BACK, StackIdAllocator.nextId()));
public final ViewPortStateStack viewportState = member(new ViewPortStateStack(StackIdAllocator.nextId()));
public final ScissorStateStack scissorState = member(new ScissorStateStack(StackIdAllocator.nextId()));
public int activeProgram = 0;
public final IntegerStateStack programStack = member(new IntegerStateStack(0, StackIdAllocator.nextId()));
public int listBase = 0;
public int boundVBO;
public int boundVAO;
@@ -189,33 +219,80 @@ class GLContextState {
public int boundCopyReadBuffer;
public int boundCopyWriteBuffer;
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
public final IntSet writeMappedBuffers = new IntOpenHashSet();
public PixelUnpackState pixelUnpackState = PixelUnpackState.DEFAULT;
public final int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public int clientAttribStackPointer = 0;
public final boolean[] restoreUnitChanged = new boolean[GLStateManager.MAX_TEXTURE_UNITS];
public boolean restoreDepthChanged, restoreBlendChanged, restoreColorMaskChanged, restoreClearColorChanged, restoreDrawBufferChanged, restoreLogicOpChanged, restoreStencilChanged, restoreViewportChanged, restoreLineChanged, restorePointChanged, restorePolygonChanged, restoreActiveUnitChanged, restoreSecondaryColorChanged;
public int restoreChangedMask;
public long restoreUnitChangedMask;
public final BlendState vanillaBlendBefore = new BlendState();
public final BlendState vanillaBlendAfter = new BlendState();
public boolean vanillaBlendEnabledBefore;
public int drawFramebuffer = 0;
public int readFramebuffer = 0;
public int texGenGeneration;
public int defaultVAO;
public final VAOManager vaos = new VAOManager();
public final StreamingVaos streamingVaos = new StreamingVaos();
public int fontVao;
public boolean lockBindCallback;
public int foreignDrawDepth;
public int internalGlDepth;
public int internalDrawDepth;
public int savedFilterTextureId;
public boolean wideLineEmulationActive = false;
public boolean lineStippleActive = false;
public Instancing ffpInstancing = Instancing.NONE;
public float lastBrightnessX;
public float lastBrightnessY;
public final Matrix4f multMatrix = new Matrix4f();
public final Matrix4d conversionMatrix4d = new Matrix4d();
public final Matrix4f conversionMatrix4f = new Matrix4f();
public final Matrix4f gluMatrix = new Matrix4f();
public final Matrix4f gluMatrix2 = new Matrix4f();
public final Vector4f gluVec4 = new Vector4f();
public final FloatBuffer gluBuffer = BufferUtils.createFloatBuffer(16);
public final float[] texGenTempPlane = new float[4];
public final IntBuffer shaderBuf = BufferUtils.createIntBuffer(8);
public final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
public final ShaderManager ffp = new ShaderManager();
GLContextState() {
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) modifiedAtDepth[i] = new ArrayList<>();
allStacksBuilder.addAll(textures.idStacks());
for (int i = 0; i < lightStates.length; i++) {
lightStates[i] = track(new BooleanStateStack(GL11.GL_LIGHT0 + i, false, true));
}
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
clipPlaneStates[i] = track(new BooleanStateStack(GL11.GL_CLIP_PLANE0 + i));
clipPlaneStates[i] = track(new ClipPlaneBooleanState(this, i, GL11.GL_CLIP_PLANE0 + i));
}
}
void init() {
for (int i = 0; i < lightDataStates.length; i++) {
lightDataStates[i] = new LightStateStack(GL11.GL_LIGHT0 + i);
lightDataStates[i] = member(new LightStateStack(GL11.GL_LIGHT0 + i, StackIdAllocator.nextId()));
}
allStacksBuilder.add(ffp.normalStack);
allStacksBuilder.add(ffp.texCoordStack);
allStacks = allStacksBuilder.toArray(new IStateStack<?>[0]);
final int capacity = StackIdAllocator.allocated();
stackById = new CowStateStack<?>[capacity];
depthsById = new CowDepths[capacity];
restoreBitById = new int[capacity];
restoreUnitById = new int[capacity];
kindById = new byte[capacity];
for (final IStateStack<?> stack : allStacks) {
final CowStateStack<?> s = (CowStateStack<?>) stack;
final int id = s.stackId();
stackById[id] = s;
depthsById[id] = s.cowDepths();
depthsById[id].owner = this;
restoreBitById[id] = s.restoreBit();
restoreUnitById[id] = s.restoreUnit();
kindById[id] = CowDispatch.kindOf(s);
}
modifiedIds = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH][allStacks.length];
modifiedCount = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
}
}
@@ -19,6 +19,7 @@ import org.lwjgl.opengl.GL43;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
public final class GLDebug {
@@ -586,6 +587,13 @@ public final class GLDebug {
}
}
public static void resetGroupStack() {
if (debugState instanceof KHRDebugState khr) {
khr.depth = 0;
Arrays.fill(khr.groupStack, null);
}
}
public static void debugMessage(String message) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.debugMessage(message);
@@ -0,0 +1,12 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.opengl.OpenGLException;
final class GLErrorReporter {
private GLErrorReporter() {}
static void reportError(int error) {
throw new OpenGLException(error);
}
}
File diff suppressed because it is too large. Load diff
@@ -82,17 +82,17 @@ public final class QuadConverter {
* @param first first vertex index (must be aligned to quad boundary, i.e. multiple of 4)
* @param vertexCount number of vertices (must be multiple of 4)
*/
public static void drawQuadsAsTriangles(int first, int vertexCount) {
drawSharedQuadEbo(first, vertexCount, 1, false);
public static void drawQuadsAsTriangles(GLContextState glCtx, int first, int vertexCount) {
drawSharedQuadEbo(glCtx, first, vertexCount, 1, false);
}
public static void drawQuadsAsTrianglesInstanced(int first, int vertexCount, int primcount) {
drawSharedQuadEbo(first, vertexCount, primcount, true);
public static void drawQuadsAsTrianglesInstanced(GLContextState glCtx, int first, int vertexCount, int primcount) {
drawSharedQuadEbo(glCtx, first, vertexCount, primcount, true);
}
private static int misalignedDraws;
private static void drawSharedQuadEbo(int first, int vertexCount, int primcount, boolean instanced) {
private static void drawSharedQuadEbo(GLContextState glCtx, int first, int vertexCount, int primcount, boolean instanced) {
final int quadCount = vertexCount / 4;
if ((first & 3) != 0) {
misalignedDraws++;
@@ -111,26 +111,21 @@ public final class QuadConverter {
memPutInt(ptr + 20, base + 3);
ptr += 24;
}
uploadAndDraw(dst, quadCount * 6, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
uploadAndDraw(glCtx, dst, quadCount * 6, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
return;
}
final boolean locked = GLStateManager.acquireDrawLock();
try {
GLStateManager.preDraw();
final int prevEbo = GLStateManager.getBoundEBO();
ensureCapacity(first / 4 + quadCount);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
final long indexOffset = (long) (first / 4) * 6 * 4;
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
} finally {
if (locked) GLStateManager.releaseDrawLock();
GLStateManager.preDraw(glCtx, GL11.GL_TRIANGLES);
final int prevEbo = glCtx.vaos.boundEBO;
ensureCapacity(first / 4 + quadCount);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
final long indexOffset = (long) (first / 4) * 6 * 4;
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
}
/**
@@ -143,42 +138,37 @@ public final class QuadConverter {
* @param indexType GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
* @param bytesPerIndex bytes per index element
*/
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
uploadAndDraw(dst, triIndexCount, indexType, bytesPerIndex, 1, false);
private static void uploadAndDraw(GLContextState glCtx, ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
uploadAndDraw(glCtx, dst, triIndexCount, indexType, bytesPerIndex, 1, false);
}
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
final boolean locked = GLStateManager.acquireDrawLock();
try {
GLStateManager.preDraw();
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = GLStateManager.getBoundEBO();
private static void uploadAndDraw(GLContextState glCtx, ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
GLStateManager.preDraw(glCtx, GL11.GL_TRIANGLES);
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = glCtx.vaos.boundEBO;
if (scratchEboId == 0) {
scratchEboId = RENDER_BACKEND.genBuffers();
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
if (needed > scratchEboCapacity) {
// Power-of-2 growth -- allocate full capacity, upload actual data
int newCap = Math.max(4096, scratchEboCapacity);
while (newCap < needed) newCap *= 2;
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
scratchEboCapacity = newCap;
}
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
} finally {
if (locked) GLStateManager.releaseDrawLock();
if (scratchEboId == 0) {
scratchEboId = RENDER_BACKEND.genBuffers();
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
if (needed > scratchEboCapacity) {
// Power-of-2 growth -- allocate full capacity, upload actual data
int newCap = Math.max(4096, scratchEboCapacity);
while (newCap < needed) newCap *= 2;
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
scratchEboCapacity = newCap;
}
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
memFree(dst);
}
@@ -190,15 +180,15 @@ public final class QuadConverter {
* @param type GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, or GL_UNSIGNED_BYTE
* @param offset byte offset into the currently bound EBO
*/
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
drawQuadElementsFromEbo(indexCount, type, offset, 1, false);
public static void drawQuadElementsAsTriangles(GLContextState glCtx, int indexCount, int type, long offset) {
drawQuadElementsFromEbo(glCtx, indexCount, type, offset, 1, false);
}
public static void drawQuadElementsAsTrianglesInstanced(int indexCount, int type, long offset, int primcount) {
drawQuadElementsFromEbo(indexCount, type, offset, primcount, true);
public static void drawQuadElementsAsTrianglesInstanced(GLContextState glCtx, int indexCount, int type, long offset, int primcount) {
drawQuadElementsFromEbo(glCtx, indexCount, type, offset, primcount, true);
}
private static void drawQuadElementsFromEbo(int indexCount, int type, long offset, int primcount, boolean instanced) {
private static void drawQuadElementsFromEbo(GLContextState glCtx, int indexCount, int type, long offset, int primcount, boolean instanced) {
if (indexCount == 0) return;
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
final int quadCount = indexCount / 4;
@@ -213,13 +203,13 @@ public final class QuadConverter {
triangulateQuads(memAddress0(src), type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
memFree(src);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
}
/**
* Convert a client-side IntBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(IntBuffer indices) {
public static void drawQuadElementsAsTriangles(GLContextState glCtx, IntBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
@@ -230,13 +220,13 @@ public final class QuadConverter {
final long srcAddr = memAddress0(indices) + (long) indices.position() * 4;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Convert a client-side ShortBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(ShortBuffer indices) {
public static void drawQuadElementsAsTriangles(GLContextState glCtx, ShortBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
@@ -247,13 +237,13 @@ public final class QuadConverter {
final long srcAddr = memAddress0(indices) + (long) indices.position() * 2;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_SHORT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
}
/**
* Convert a client-side ByteBuffer of quad indices (type-agnostic) to triangles.
*/
public static void drawQuadElementsAsTriangles(int count, int type, ByteBuffer indices) {
public static void drawQuadElementsAsTriangles(GLContextState glCtx, int count, int type, ByteBuffer indices) {
if (count == 0) return;
assert count % 4 == 0;
final int quadCount = count / 4;
@@ -263,23 +253,7 @@ public final class QuadConverter {
final ByteBuffer dst = memAlloc(triIndexCount * 4);
triangulateQuads(srcAddr, type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Clean up the shared EBO.
*/
public static void destroy() {
if (eboId != 0) {
GLStateManager.glDeleteBuffers(eboId);
eboId = 0;
maxQuads = 0;
}
if (scratchEboId != 0) {
GLStateManager.glDeleteBuffers(scratchEboId);
scratchEboId = 0;
scratchEboCapacity = 0;
}
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
@@ -169,13 +169,13 @@ public class RenderSystem {
if (isGLES) {
GLStateManager.LOGGER.info("GL ES context detected, enabling shader transformer.");
ShaderManager.getInstance().enable();
ShaderManager.enable();
} else if (GLStateManager.capabilities.OpenGL32) {
final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
coreProfile = true;
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
ShaderManager.getInstance().enable();
ShaderManager.enable();
} else {
throw new IllegalStateException("Non-core GL context (profile mask 0x" + Integer.toHexString(profileMask)
+ "); FFP emulation requires a core profile and nothing would render. Context creation should have rejected this.");
@@ -503,13 +503,12 @@ public class RenderSystem {
dsaState.bindTextureToUnit(target, unit, texture);
}
public static final FloatBuffer PROJECTION_MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
public static void setupProjectionMatrix(Matrix4f matrix) {
final FloatBuffer projectionMatrixBuffer = GLStateManager.ctx().projectionMatrixBuffer;
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPushMatrix();
matrix.get(0, PROJECTION_MATRIX_BUFFER);
GLStateManager.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
matrix.get(0, projectionMatrixBuffer);
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
@@ -637,6 +636,11 @@ public class RenderSystem {
GLStateManager.glSamplerParameteri(sampler, pname, param);
}
public static void samplerParameterf(int sampler, int pname, float param) {
if (!supportsSamplerObjects || sampler == 0) return;
GLStateManager.glSamplerParameterf(sampler, pname, param);
}
public static void bindSamplerToUnit(int unit, int sampler) {
if (!supportsSamplerObjects) return;
GLStateManager.glBindSampler(unit, sampler);
@@ -0,0 +1,239 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
public final class StateSet {
public static final int R_DEPTH = 1;
public static final int R_BLEND = 1 << 1;
public static final int R_COLOR_MASK = 1 << 2;
public static final int R_CLEAR_COLOR = 1 << 3;
public static final int R_DRAW_BUFFER = 1 << 4;
public static final int R_LOGIC_OP = 1 << 5;
public static final int R_SECONDARY_COLOR = 1 << 6;
public static final int R_STENCIL = 1 << 7;
public static final int R_VIEWPORT = 1 << 8;
public static final int R_LINE = 1 << 9;
public static final int R_POINT = 1 << 10;
public static final int R_POLYGON = 1 << 11;
public static final int R_TEXTURE = 1 << 12;
public static final int R_ACTIVE_UNIT = 1 << 13;
public static final int R_PROGRAM = 1 << 14;
public static final int R_SCISSOR = 1 << 15;
public static final int B_FRAG_COLORBUF = 1;
public static final int B_FRAG_TEXTURE = 1 << 1;
public static final int B_FRAG_FOG = 1 << 2;
public static final int B_CURRENT = 1 << 3;
public static final int B_LIGHTING = 1 << 4;
public static final int B_TRANSFORM = 1 << 5;
private static final long[] NO_MEMBERS = new long[0];
private static final Int2ObjectMap<StateSet> maskToStateSetMap = new Int2ObjectOpenHashMap<>();
public final int glMask;
public final long[] members;
public final int restore;
public final int bump;
public final boolean blendSnapshot;
public final DisplayListCommand pushCommand;
StateSet(int glMask, long[] members, int restore, int bump, boolean blendSnapshot) {
this.glMask = glMask;
this.members = members;
this.restore = restore;
this.bump = bump;
this.blendSnapshot = blendSnapshot;
this.pushCommand = () -> GLStateManager.pushStateLive(this);
}
public boolean contains(int id) {
if (id < 0) return false;
final int word = id >> 6;
return word < members.length && (members[word] & (1L << id)) != 0;
}
private static long[] ids(CowStateStack<?>... stacks) {
if (stacks.length == 0) return NO_MEMBERS;
int max = 0;
for (CowStateStack<?> s : stacks) max = Math.max(max, s.stackId());
final long[] bits = new long[(max >> 6) + 1];
for (CowStateStack<?> s : stacks) {
final int id = s.stackId();
bits[id >> 6] |= 1L << id;
}
return bits;
}
public static final StateSet BATCH = buildBatch();
public static final StateSet FONT = buildFont();
public static final StateSet FONT_PIPELINE = buildFontPipeline();
public static final StateSet HAND = buildHand();
public static final StateSet BLEND = buildBlend();
public static final StateSet ALPHA = buildAlpha();
public static final StateSet CUTOUT = buildCutout();
public static final StateSet MASKS = buildMasks();
public static final StateSet CULL = buildCull();
private static StateSet buildBatch() {
return new StateSet(0,
ids(
GLStateManager.getBlendState(), GLStateManager.getAlphaState(), GLStateManager.getDepthState(),
GLStateManager.getColorMask(), GLStateManager.getPolygonState(),
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
GLStateManager.getActiveTextureUnitStack(),
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
GLStateManager.getLightingState(), GLStateManager.getCullState(), GLStateManager.getPolygonOffsetFillState(),
GLStateManager.getTextures().getTextureUnitStates(0)
),
R_BLEND | R_DEPTH | R_COLOR_MASK | R_POLYGON | R_TEXTURE | R_ACTIVE_UNIT,
B_FRAG_COLORBUF,
true);
}
private static StateSet buildFont() {
return new StateSet(0,
ids(
GLStateManager.getBlendState(), GLStateManager.getDepthState(), GLStateManager.getShadeModelState(),
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
GLStateManager.getActiveTextureUnitStack(), GLStateManager.getProgramStack(),
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
GLStateManager.getTextures().getTextureUnitStates(0)
),
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT | R_PROGRAM,
0,
true);
}
private static StateSet buildFontPipeline() {
return new StateSet(0,
ids(
GLStateManager.getBlendState(), GLStateManager.getDepthState(),
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
GLStateManager.getActiveTextureUnitStack(),
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
GLStateManager.getTextures().getTextureUnitStates(0)
),
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT,
0,
true);
}
private static StateSet buildHand() {
return new StateSet(0,
ids(
GLStateManager.getBlendState(), GLStateManager.getAlphaState(), GLStateManager.getDepthState(),
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getTextures().getTextureUnitStates(1)
),
R_BLEND | R_DEPTH,
B_FRAG_COLORBUF,
true);
}
private static StateSet buildBlend() {
return new StateSet(0,
ids(GLStateManager.getBlendState(), GLStateManager.getBlendMode()),
R_BLEND,
0,
true);
}
private static StateSet buildAlpha() {
return new StateSet(0,
ids(GLStateManager.getAlphaState(), GLStateManager.getAlphaTest()),
0,
B_FRAG_COLORBUF,
false);
}
private static StateSet buildCutout() {
return new StateSet(0,
ids(GLStateManager.getAlphaState(), GLStateManager.getAlphaTest(), GLStateManager.getTextures().getTextureUnitStates(1)),
0,
B_FRAG_COLORBUF,
false);
}
private static StateSet buildMasks() {
return new StateSet(0,
ids(GLStateManager.getDepthState(), GLStateManager.getColorMask()),
R_DEPTH | R_COLOR_MASK,
0,
false);
}
private static StateSet buildCull() {
return new StateSet(0,
ids(GLStateManager.getPolygonState(), GLStateManager.getCullState()),
R_POLYGON,
0,
false);
}
public static StateSet forMask(int mask) {
StateSet cached = maskToStateSetMap.get(mask);
if (cached != null) {
return cached;
}
cached = buildForMask(mask);
maskToStateSetMap.put(mask, cached);
return cached;
}
private static StateSet buildForMask(int mask) {
int restore = 0;
int bump = 0;
final boolean blendSnapshot = (mask & (GL11.GL_COLOR_BUFFER_BIT | GL11.GL_ENABLE_BIT)) != 0;
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0) {
restore |= R_DEPTH;
}
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
restore |= R_BLEND | R_COLOR_MASK | R_CLEAR_COLOR | R_DRAW_BUFFER | R_LOGIC_OP;
bump |= B_FRAG_COLORBUF;
}
if ((mask & GL11.GL_CURRENT_BIT) != 0) {
restore |= R_SECONDARY_COLOR;
bump |= B_CURRENT;
}
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
restore |= R_STENCIL;
}
if ((mask & GL11.GL_VIEWPORT_BIT) != 0) {
restore |= R_VIEWPORT;
}
if ((mask & GL11.GL_SCISSOR_BIT) != 0) {
restore |= R_SCISSOR;
}
if ((mask & GL11.GL_LINE_BIT) != 0) {
restore |= R_LINE;
}
if ((mask & GL11.GL_POINT_BIT) != 0) {
restore |= R_POINT;
}
if ((mask & GL11.GL_POLYGON_BIT) != 0) {
restore |= R_POLYGON;
}
if ((mask & GL11.GL_TEXTURE_BIT) != 0) {
restore |= R_TEXTURE | R_ACTIVE_UNIT;
bump |= B_FRAG_TEXTURE;
}
if ((mask & GL11.GL_LIGHTING_BIT) != 0) {
bump |= B_LIGHTING;
}
if ((mask & GL11.GL_FOG_BIT) != 0) {
bump |= B_FRAG_FOG;
}
if ((mask & GL11.GL_TRANSFORM_BIT) != 0) {
bump |= B_TRANSFORM;
}
final long[] members = Feature.maskToMembers(mask);
return new StateSet(mask, members, restore, bump, blendSnapshot);
}
}
@@ -242,6 +242,8 @@ public abstract class RenderBackend {
}
}
public void invalidateStateMirror() {}
public void onFrameEnd() {}
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
@@ -104,7 +104,9 @@ public class DSAARB extends DSAUnsupported {
super.bindTextureToUnit(target != 0 ? target : GL11.GL_TEXTURE_2D, unit, texture);
} else {
RENDER_BACKEND.bindTextureUnit(unit, texture);
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture, target);
final var binding = GLStateManager.getTextures().getTextureUnitBindings(unit);
binding.beforeModify();
binding.setBinding(texture, target);
GLStateManager.trackMaxBoundTextureUnit(unit);
}
}
@@ -0,0 +1,55 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
public final class CombinedGlint {
private static final Matrix4f savedMatrix = new Matrix4f();
private static final Matrix4f inverse = new Matrix4f();
private static final Matrix4f relative = new Matrix4f();
private static boolean active;
private static boolean replaceAlpha;
private CombinedGlint() {}
public static boolean isActive() {
return active;
}
public static boolean replacesAlpha() {
return active && replaceAlpha;
}
public static boolean begin(Matrix4fc secondTextureMatrix) {
if (active || GLStateManager.getActiveProgram() != 0 || GLStateManager.getLightingState().isEnabled() || GLStateManager.getColorSumState().isEnabled() || !GLStateManager.glIsEnabled(GL11.GL_BLEND)) return false;
final var blend = GLStateManager.getBlendState();
final boolean lastAlpha = blend.getSrcAlpha() == GL11.GL_ONE && blend.getDstAlpha() == GL11.GL_ZERO && blend.getEquationAlpha() == GL14.GL_FUNC_ADD;
final boolean preserveAlpha = blend.getSrcAlpha() == GL11.GL_ZERO && blend.getDstAlpha() == GL11.GL_ONE && (blend.getEquationAlpha() == GL14.GL_FUNC_ADD || blend.getEquationAlpha() == GL14.GL_FUNC_REVERSE_SUBTRACT);
if (blend.getSrcRgb() != GL11.GL_SRC_COLOR || blend.getDstRgb() != GL11.GL_ONE
|| (!lastAlpha && !preserveAlpha && (blend.getSrcAlpha() != GL11.GL_SRC_COLOR || blend.getDstAlpha() != GL11.GL_ONE || blend.getEquationAlpha() != blend.getEquationRgb()))
|| (blend.getEquationRgb() != GL14.GL_FUNC_ADD && blend.getEquationRgb() != GL14.GL_FUNC_REVERSE_SUBTRACT)) return false;
final var textures = GLStateManager.getTextures();
if (!textures.getTextureUnitStates(0).isEnabled() || textures.getTextureUnitBindings(0).getBinding() == 0 || textures.getTexEnvState(0).mode != GL11.GL_MODULATE) return false;
if (textures.getTextureUnitStates(1).isEnabled() && textures.getTextureUnitBindings(1).getBinding() != 0 && textures.getTexEnvState(1).mode != GL11.GL_MODULATE) return false;
for (int unit = 2; unit < 4; unit++) {
if (textures.getTextureUnitStates(unit).isEnabled() && textures.getTextureUnitBindings(unit).getBinding() != 0) return false;
}
inverse.set(textures.getTextureUnitMatrix(0)).invert();
relative.set(secondTextureMatrix).mul(inverse);
if (!relative.isFinite()) return false;
savedMatrix.set(textures.getTextureUnitMatrix(3));
GLStateManager.setTextureMatrix(3, relative);
replaceAlpha = lastAlpha;
active = true;
return true;
}
public static void end() {
if (!active) return;
active = false;
GLStateManager.setTextureMatrix(3, savedMatrix);
}
}
@@ -107,6 +107,12 @@ public final class FFPUniformBlock {
public static final int FOG_PARAMS = vec4("u_FogParams");
public static final int FOG_COLOR = vec4("u_FogColor");
// Rain and snow, for Instancing.WEATHER. 0 = (translate.xyz, 1/radius),
// 1 = (cameraFrac.xy, partialTicks, age), 2 = (rainScroll, snowScroll, rainStrength, unused).
public static final int WEATHER_PARAMS_0 = vec4("u_WeatherParams0");
public static final int WEATHER_PARAMS_1 = vec4("u_WeatherParams1");
public static final int WEATHER_PARAMS_2 = vec4("u_WeatherParams2");
public static final int SIZE;
public static final String GLSL_DECL;
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
@@ -59,7 +60,6 @@ public final class FfpExtendedAttribs {
private static final HashMap<Key, ExtBuffer> cache = new HashMap<>();
private static final Key lookup = new Key();
private static int internalGlDepth = 0;
private static final HashMap<EboKey, Boolean> eboPatternCache = new HashMap<>();
private static final EboKey eboLookup = new EboKey();
@@ -79,15 +79,19 @@ public final class FfpExtendedAttribs {
return cache.isEmpty() && eboPatternCache.isEmpty();
}
private static int internalDrawDepth = 0;
public static void beginInternalDraw() { GLStateManager.ctx().internalDrawDepth++; }
public static void beginInternalDraw() { internalDrawDepth++; }
public static void endInternalDraw() {
final GLContextState glCtx = GLStateManager.ctx();
if (glCtx.internalDrawDepth > 0) glCtx.internalDrawDepth--;
}
public static void endInternalDraw() { if (internalDrawDepth > 0) internalDrawDepth--; }
private static void beginInternalGl() { GLStateManager.ctx().internalGlDepth++; }
private static void beginInternalGl() { internalGlDepth++; }
private static void endInternalGl() { if (internalGlDepth > 0) internalGlDepth--; }
private static void endInternalGl() {
final GLContextState glCtx = GLStateManager.ctx();
if (glCtx.internalGlDepth > 0) glCtx.internalGlDepth--;
}
private static boolean isNotConventionalTexturedArray(VAOManager.Attrib a) {
return a == null || !a.enabled || a.genericPointer || a.vboId == 0 || a.size < 2;
@@ -104,20 +108,20 @@ public final class FfpExtendedAttribs {
return true;
}
public static boolean maybeBind(int mode, int first, int count) {
public static boolean maybeBind(GLContextState glCtx, int mode, int first, int count) {
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
if (h == null) return false;
if (internalDrawDepth > 0) return false;
if (glCtx.internalDrawDepth > 0) return false;
final int extPrim = ImmediateExtendedAttribHandler.extPrimVerts(mode, count);
if (extPrim == 0 || first < 0) return false;
if (!h.wantsExtended()) return false;
final VAOManager.Attrib pos = VAOManager.get(POSITION_LOC);
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
final VAOManager.Attrib pos = glCtx.vaos.attrib(POSITION_LOC);
final VAOManager.Attrib uv = glCtx.vaos.attrib(UV_LOC);
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId)) return false;
VAOManager.Attrib normal = extPrim == 3 ? VAOManager.get(NORMAL_LOC) : null;
VAOManager.Attrib normal = extPrim == 3 ? glCtx.vaos.attrib(NORMAL_LOC) : null;
if (normal != null && (!normal.enabled || normal.genericPointer || normal.vboId == 0 || normal.size < 3
|| isStreaming(normal.vboId))) {
normal = null;
@@ -126,17 +130,17 @@ public final class FfpExtendedAttribs {
return bindExt(h, mode, first, count, extPrim, pos, uv, normal);
}
public static boolean maybeBindIndexed(int mode, int indexCount, int indexType, long indicesOffset) {
public static boolean maybeBindIndexed(GLContextState glCtx, int mode, int indexCount, int indexType, long indicesOffset) {
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
if (h == null) return false;
if (internalDrawDepth > 0) return false;
if (glCtx.internalDrawDepth > 0) return false;
if (mode != GL11.GL_TRIANGLES || indexCount <= 0 || indexCount % 6 != 0) return false;
if (!h.wantsExtended()) return false;
final VAOManager.Attrib pos = VAOManager.get(POSITION_LOC);
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
final VAOManager.Attrib pos = glCtx.vaos.attrib(POSITION_LOC);
final VAOManager.Attrib uv = glCtx.vaos.attrib(UV_LOC);
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId) || isStreaming(VAOManager.boundEBO)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId) || isStreaming(glCtx.vaos.boundEBO)) return false;
final int indexSize = indexTypeBytes(indexType);
if (indexSize == 0) return false;
@@ -144,7 +148,7 @@ public final class FfpExtendedAttribs {
final int first = (int) (indicesOffset / indexSize) / 6 * 4;
final int count = indexCount / 6 * 4;
if (!isQuadPatternEbo(VAOManager.boundEBO, indicesOffset, indexCount, indexType, indexSize, first)) return false;
if (!isQuadPatternEbo(glCtx.vaos.boundEBO, indicesOffset, indexCount, indexType, indexSize, first)) return false;
// Build/bind as GL_QUADS (the real primitive): face-normal tangents, 4-vertex tiles, no per-vertex normal.
return bindExt(h, GL11.GL_QUADS, first, count, 4, pos, uv, null);
@@ -226,8 +230,8 @@ public final class FfpExtendedAttribs {
GLStateManager.glVertexAttrib4f(ImmediateExtendedAttribHandler.LOC_TANGENT, 1.0f, 0.0f, 0.0f, 1.0f);
}
public static void onDeleteBuffer(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
public static void onDeleteBuffer(GLContextState glCtx, int vboId) {
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
dropDerivedFrom(vboId);
respecCounts.remove(vboId);
respecFrames.remove(vboId);
@@ -236,8 +240,8 @@ public final class FfpExtendedAttribs {
persistentSources.remove(vboId);
}
public static void onBufferRespecified(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
public static void onBufferRespecified(GLContextState glCtx, int vboId) {
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
if (streamingSources.contains(vboId)) {
respecFrames.put(vboId, frame);
return;
@@ -268,16 +272,15 @@ public final class FfpExtendedAttribs {
respecFrames.put(vboId, frame);
}
public static void markStreaming(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
private static void markStreaming(int vboId) {
respecCounts.remove(vboId);
respecRunStart.remove(vboId);
respecFrames.put(vboId, frame);
if (streamingSources.add(vboId)) dropDerivedFrom(vboId);
}
public static void markPersistent(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
public static void markPersistent(GLContextState glCtx, int vboId) {
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
markStreaming(vboId);
persistentSources.add(vboId);
}
@@ -2,8 +2,10 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import java.util.Arrays;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
@@ -64,6 +66,8 @@ public final class FragmentKey {
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 14;
private static final int BIT_COMBINED_GLINT = 61;
private static final int BIT_GLINT_REPLACE_ALPHA = 62;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
@@ -104,11 +108,17 @@ public final class FragmentKey {
* @return number of significant longs (1..4)
*/
public static int packFromState(long[] out) {
return packFromState(out, GLStateManager.ctx());
}
public static int packFromState(long[] out, GLContextState glCtx) {
long global = 0;
if (CombinedGlint.isActive()) global |= 1L << BIT_COMBINED_GLINT;
if (CombinedGlint.replacesAlpha()) global |= 1L << BIT_GLINT_REPLACE_ALPHA;
// Fog
if (GLStateManager.getFogMode().isEnabled()) {
final int fogMode = switch (GLStateManager.getFogState().getFogMode()) {
if (glCtx.fogMode.isEnabled()) {
final int fogMode = switch (glCtx.fogState.getFogMode()) {
case GL11.GL_LINEAR -> FOG_LINEAR;
case GL11.GL_EXP -> FOG_EXP;
case GL11.GL_EXP2 -> FOG_EXP2;
@@ -118,39 +128,40 @@ public final class FragmentKey {
}
// Alpha test
if (GLStateManager.getAlphaTest().isEnabled()) {
if (glCtx.alphaTest.isEnabled()) {
global |= (1L << BIT_ALPHA_TEST);
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
global |= ((long) (encodeAlphaFunc(glCtx.alphaState.getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
}
// Damage overlay
if (GLStateManager.ffpInstancing.hasInstanceHead()) {
if (glCtx.ffpInstancing.hasInstanceHead()) {
global |= (1L << BIT_OVERLAY_INSTANCED);
} else if (GLStateManager.getOverlayA() != 0.0f) {
} else if (glCtx.overlayA != 0.0f) {
global |= (1L << BIT_OVERLAY_ENABLED);
}
if (GLStateManager.lineStippleActive) {
if (glCtx.lineStippleActive) {
global |= (1L << BIT_LINE_STIPPLE);
}
// Separate specular / color sum
if (GLStateManager.getLightingState().isEnabled()
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
if (glCtx.lightingState.isEnabled()
&& glCtx.lightModel.colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
global |= (1L << BIT_SEPARATE_SPECULAR);
} else if (GLStateManager.getColorSumState().isEnabled()) {
} else if (glCtx.colorSumState.isEnabled()) {
global |= (1L << BIT_COLOR_SUM);
}
// Per-unit state
final TextureUnitArray textures = glCtx.textures;
int highestEnabled = -1;
long unit0Bits = 0;
for (int i = 0; i < MAX_UNITS; i++) {
// Per Mesa - an enabled unit with no complete texture bound counts as disabled.
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled() && GLStateManager.getTextures().getTextureUnitBindings(i).getBinding() != 0;
final boolean texEnabled = textures.getTextureUnitStates(i).isEnabled() && textures.getTextureUnitBindings(i).getBinding() != 0;
if (texEnabled) highestEnabled = i;
final long unitBits = packUnit(i, texEnabled);
final long unitBits = packUnit(i, texEnabled, textures);
if (i == 0) {
unit0Bits = unitBits;
} else {
@@ -166,10 +177,10 @@ public final class FragmentKey {
return Math.max(1, nrEnabled);
}
private static long packUnit(int unitIndex, boolean texEnabled) {
private static long packUnit(int unitIndex, boolean texEnabled, TextureUnitArray textures) {
if (!texEnabled) return 0;
final TexEnvState envState = GLStateManager.getTextures().getTexEnvState(unitIndex);
final TexEnvState envState = textures.getTexEnvState(unitIndex);
final int mode = encodeTexEnvMode(envState.mode);
long bits = 1L; // enabled
@@ -241,6 +252,8 @@ public final class FragmentKey {
}
public int fogMode() { return (int) (packed[0] & 0x3); }
public boolean combinedGlint() { return (packed[0] & (1L << BIT_COMBINED_GLINT)) != 0; }
public boolean glintReplaceAlpha() { return (packed[0] & (1L << BIT_GLINT_REPLACE_ALPHA)) != 0; }
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
@@ -382,7 +395,9 @@ public final class FragmentKey {
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(), alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(), overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(),
overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import org.lwjgl.opengl.GL11;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
/**
* Generates GLSL 330 core fragment shaders for FFP emulation.
@@ -17,6 +18,15 @@ public final class FragmentShaderGenerator {
emitInputs(sb, key);
emitUniforms(sb, key);
if (key.combinedGlint()) {
sb.append("#define GLINT_REPLACE_ALPHA ").append(key.glintReplaceAlpha() ? 1 : 0).append('\n');
sb.append("#define GLINT_FOG_MODE ").append(key.fogMode()).append('\n');
sb.append("#define GLINT_ALPHA_FUNC ").append(key.alphaTestEnabled() ? key.alphaTestFunc() : 7).append('\n');
sb.append("#define GLINT_OVERLAY ").append(key.overlayInstanced() ? 2 : key.overlayEnabled() ? 1 : 0).append('\n');
sb.append("#define GLINT_LIGHTMAP ").append(key.nrEnabledUnits() > 1 && key.unitEnabled(1) ? 1 : 0).append('\n');
sb.append(ShaderLoader.getShaderSource("angelica:combined_glint.frag.glsl"));
return sb.toString();
}
sb.append("out vec4 fragColor;\n\n");
sb.append("void main() {\n");
@@ -34,6 +34,62 @@ public final class InstancedGlslHelpers {
+ "0.0, 1.0)";
}
// wHalfWidth is vanilla's rainXCoords table.
private static final String WEATHER_PROLOGUE = """
vec2 {p}wColumn = {span}.xy;
float {p}wBottom = {span}.z;
float {p}wTop = {span}.w;
vec2 {p}wLightmap = {params}.xy;
float {p}wHash = {params}.z;
bool {p}wSnow = {params}.w > 0.5;
vec3 {p}wTranslate = {p}WeatherParams0.xyz;
float {p}wInvRadius = {p}WeatherParams0.w;
vec2 {p}wCameraFrac = {p}WeatherParams1.xy;
float {p}wPartialTicks = {p}WeatherParams1.z;
float {p}wAge = {p}WeatherParams1.w;
float {p}wRainScroll = {p}WeatherParams2.x;
float {p}wSnowScroll = {p}WeatherParams2.y;
float {p}wStrength = {p}WeatherParams2.z;
vec2 {p}wHalfWidth = vec2(-{p}wColumn.y, {p}wColumn.x) * (0.5 / sqrt(dot({p}wColumn, {p}wColumn)));
vec2 {p}wCornerXZ = {p}wColumn + vec2(0.5) + ({corner}.x * 2.0 - 1.0) * {p}wHalfWidth;
float {p}wCornerY = mix({p}wBottom, {p}wTop, {corner}.y);
{vertex} = vec4(vec3({p}wCornerXZ.x, {p}wCornerY, {p}wCornerXZ.y) + {p}wTranslate, 1.0);
float {p}wDistance = length({p}wColumn + vec2(0.5) - {p}wCameraFrac) * {p}wInvRadius;
float {p}wFade = 1.0 - {p}wDistance * {p}wDistance;
float {p}wScroll;
vec2 {p}wDrift;
float {p}wAlpha;
if ({p}wSnow) {
{p}wScroll = ({p}wSnowScroll + {p}wPartialTicks) / 512.0;
{p}wDrift = vec2({jitter}.x + {p}wAge * 0.01 * {jitter}.y, {jitter}.z + {p}wAge * {jitter}.w * 0.001);
{p}wAlpha = {p}wFade * 0.3 + 0.5;
} else {
{p}wScroll = (mod({p}wRainScroll + {p}wHash, 32.0) + {p}wPartialTicks) / 32.0 * (3.0 + {jitter}.x);
{p}wDrift = vec2(0.0);
{p}wAlpha = {p}wFade * 0.5 + 0.5;
}
{tex0} = vec4({corner}.x + {p}wDrift.x, {p}wCornerY * 0.25 + {p}wScroll + {p}wDrift.y, 0.0, 1.0);
{tex1} = vec4({p}wLightmap, 0.0, 1.0);
{color} = vec4(1.0, 1.0, 1.0, {p}wAlpha * {p}wStrength);""";
public static String weatherPrologue(String uniformPrefix, String attribPrefix, String corner,
String vertex, String color, String tex0, String tex1, String sep) {
return WEATHER_PROLOGUE
.replace("{p}", uniformPrefix)
.replace("{span}", attribPrefix + "InstColumnSpan")
.replace("{jitter}", attribPrefix + "InstJitter")
.replace("{params}", attribPrefix + "InstWeatherParams")
.replace("{corner}", corner)
.replace("{vertex}", vertex)
.replace("{color}", color)
.replace("{tex0}", tex0)
.replace("{tex1}", tex1)
.replace("\n", sep);
}
public static String cubePrelude(String prefix, String mid, String delta, String normal, String tex, String sep) {
return "float " + prefix + "cubeMirror = " + tex + ".z < 0.0 ? 1.0 : 0.0;" + sep
+ "vec2 " + prefix + "cubeMidU = " + mid + ".xy - " + prefix + "cubeMirror * " + normal + ".x;" + sep
@@ -65,6 +121,11 @@ public final class InstancedGlslHelpers {
decls.add(decl(ParticleInstancedAttribs.LOC_COLOR, "vec4", prefix + "InstColor"));
decls.add(decl(ParticleInstancedAttribs.LOC_LIGHTMAP, "vec2", prefix + "InstLightmap"));
}
case WEATHER -> {
decls.add(decl(WeatherInstancedAttribs.LOC_COLUMN_SPAN, "vec4", prefix + "InstColumnSpan"));
decls.add(decl(WeatherInstancedAttribs.LOC_JITTER, "vec4", prefix + "InstJitter"));
decls.add(decl(WeatherInstancedAttribs.LOC_PARAMS, "vec4", prefix + "InstWeatherParams"));
}
}
return decls.toArray(String[]::new);
}
@@ -4,7 +4,8 @@ public enum Instancing {
NONE(""),
TEMPLATE("_instanced"),
CUBE("_cubeinstanced"),
PARTICLE("_particleinstanced");
PARTICLE("_particleinstanced"),
WEATHER("_weatherinstanced");
public static final Instancing[] VALUES = values();
@@ -9,7 +9,6 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.IdentityHashMap;
@@ -271,25 +270,6 @@ public class ShaderCache {
}
}
/** Destroy all cached programs and clear caches. */
public void destroy() {
for (int i = 0; i < capacity; i++) {
if (programs[i] != null) {
programs[i].destroy();
}
}
Arrays.fill(keys, 0);
Arrays.fill(programs, null);
Arrays.fill(fragSources, null);
count = 0;
uniqueFragCount = 0;
vertexSourceCache.clear();
geometrySourceCache.clear();
lastProgram = null;
lastVK = Long.MIN_VALUE;
GLStateManager.LOGGER.debug("FFP shader cache cleared");
}
public int getProgramCount() {
return count;
}
@@ -4,12 +4,13 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack;
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
@@ -26,45 +27,42 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
*/
public final class ShaderManager {
private static final class Holder {
static final ShaderManager INSTANCE = new ShaderManager();
}
private final ShaderCache cache = new ShaderCache();
private static final ShaderCache cache = new ShaderCache();
private final Uniforms uniforms = new Uniforms();
@Getter
private boolean active = false;
private boolean active;
private Program currentProgram = null;
private int lastBoundProgramId = -1;
private int lastBoundProgramId;
private long currentVertexKeyPacked = Long.MIN_VALUE;
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
private int currentFKLen = 0;
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
private static final Vector4f[] currentTexCoords = {
public final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
public final Vector4f[] currentTexCoords = {
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
};
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal);
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0]);
@Getter private static int normalGeneration;
@Getter private static int texCoordGeneration;
public final Vec3fStack normalStack = new Vec3fStack(currentNormal, StackIdAllocator.nextId());
public final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0], StackIdAllocator.nextId());
public int normalGeneration;
public int texCoordGeneration;
private int preDrawCalls;
private int lastFramePreDrawCalls;
public static long variantSwitches;
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false;
public static Vector3f getCurrentNormal() { return GLStateManager.ctx().ffp.currentNormal; }
public static Vector4f getCurrentTexCoord() { return GLStateManager.ctx().ffp.currentTexCoords[0]; }
public static Vec3fStack getNormalStack() { return GLStateManager.ctx().ffp.normalStack; }
public static Vec4fStack getTexCoordStack() { return GLStateManager.ctx().ffp.texCoordStack; }
@Getter private static boolean enabled = false;
private ShaderManager() {
static {
cache.setDumpDir(SystemProperties.shaderDumpDir("ffp"));
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
VAOManager.setCurrentVertexFlags(format.getVertexFlags());
@@ -72,18 +70,20 @@ public final class ShaderManager {
});
}
public static ShaderManager getInstance() {
return Holder.INSTANCE;
public ShaderManager() {
active = enabled;
lastBoundProgramId = enabled ? 0 : -1;
}
public void enable() {
public static ShaderManager getInstance() {
return GLStateManager.ctx().ffp;
}
public static void enable() {
warmUp();
enabled = true;
if (GLStateManager.getActiveProgram() == 0) {
activate();
}
final GLContextState glCtx = GLStateManager.ctx();
if (glCtx.activeProgram == 0) glCtx.ffp.activate();
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
@@ -110,7 +110,7 @@ public final class ShaderManager {
}
}
public void disable() {
public static void disable() {
enabled = false;
}
@@ -119,6 +119,13 @@ public final class ShaderManager {
lastBoundProgramId = GLStateManager.getActiveProgram();
}
public void invalidateProgram() {
lastBoundProgramId = -1;
currentProgram = null;
currentVertexKeyPacked = Long.MIN_VALUE;
uniforms.invalidateBinding();
}
public void deactivate() {
active = false;
currentProgram = null;
@@ -128,38 +135,38 @@ public final class ShaderManager {
GLStateManager.forceAttribDefaultsDirty();
}
public void preDraw() {
public void preDraw(GLContextState glCtx) {
GLSMHooks.resolvePendingProgram();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
// Handle FFP & Iris uniforms
final int currentProgramId = GLStateManager.getActiveProgram();
final int currentProgramId = glCtx.activeProgram;
if (currentProgramId != 0) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId)) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId, glCtx)) {
return; // Don't emulate FFP on non-iris core shaders
}
}
final int vertexFlags = VAOManager.getCurrentVertexFlags();
final int vertexFlags = glCtx.vaos.getVertexFlags();
final boolean hasColor = (vertexFlags & VertexFlags.COLOR_BIT) != 0;
final boolean hasNormal = (vertexFlags & VertexFlags.NORMAL_BIT) != 0;
final boolean hasTexCoord = (vertexFlags & VertexFlags.TEXTURE_BIT) != 0;
final boolean hasLightmap = (vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0;
GLStateManager.flushDeferredVertexAttribs(hasColor, hasNormal, hasTexCoord, hasLightmap);
GLStateManager.flushDeferredVertexAttribs(glCtx, hasColor, hasNormal, hasTexCoord, hasLightmap);
if (!active) return;
preDrawCalls++;
final int fkLen = FragmentKey.packFromState(currentFKScratch);
final int fkLen = FragmentKey.packFromState(currentFKScratch, glCtx);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask, glCtx);
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
commitVariant(vkPacked, fkLen);
}
uploadUniforms();
uploadUniforms(glCtx);
}
private void commitVariant(long vkPacked, int fkLen) {
@@ -175,48 +182,38 @@ public final class ShaderManager {
}
}
private void uploadUniforms() {
private void uploadUniforms(GLContextState glCtx) {
if (currentProgram != null) {
uniforms.upload();
uniforms.upload(glCtx);
}
}
public static void endFrame() {
final ShaderManager sm = Holder.INSTANCE;
final ShaderManager sm = GLStateManager.ctx().ffp;
sm.lastFramePreDrawCalls = sm.preDrawCalls;
sm.preDrawCalls = 0;
sm.uniforms.endFrame();
FfpExtendedAttribs.endFrame();
}
public static void setCurrentNormal(float x, float y, float z) {
public void setNormal(float x, float y, float z) {
normalStack.beforeModify();
currentNormal.set(x, y, z);
normalGeneration++;
}
public static void setCurrentTexCoord(float s, float t, float r, float q) {
public void setTexCoord(float s, float t, float r, float q) {
texCoordStack.beforeModify();
currentTexCoords[0].set(s, t, r, q);
texCoordGeneration++;
}
public static void setCurrentTexCoord(int unit, float s, float t, float r, float q) {
public void setTexCoord(int unit, float s, float t, float r, float q) {
if (unit == 0) texCoordStack.beforeModify();
currentTexCoords[unit].set(s, t, r, q);
texCoordGeneration++;
}
public static void bumpNormalGeneration() { normalGeneration++; }
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
public void destroy() {
cache.destroy();
uniforms.destroy();
final GLSMInitConfig config = GLStateManager.getInitConfig();
if (config != null && config.getStreamingDrawerDestroy() != null) config.getStreamingDrawerDestroy().run();
QuadConverter.destroy();
active = false;
currentProgram = null;
}
public String getDebugInfo() {
return String.format("FFP: %d programs (%d vert, %d frag variants) | UBO: %d/%d draws wrote (mat %d, light %d, frag %d, misc %d)",
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount(),
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.FogState;
@@ -7,9 +8,9 @@ import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.LineState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import com.gtnewhorizons.angelica.glsm.streaming.UniformRingBuffer;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.joml.Math;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
@@ -47,6 +48,7 @@ public class Uniforms {
private float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
private int viewportX = Integer.MIN_VALUE, viewportY = Integer.MIN_VALUE, viewportWidth = -1, viewportHeight = -1;
private int lineStipple = -1;
private int weatherGen = -1;
int blockWrites;
int blockSkips;
@@ -81,66 +83,67 @@ public class Uniforms {
private static final double LN2 = java.lang.Math.log(2.0);
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
public void upload() {
public void upload(GLContextState glCtx) {
boolean dirty = false;
contentChanged = false;
final int mvG = GLStateManager.getMvGeneration();
final int projG = GLStateManager.getProjGeneration();
final int mvG = glCtx.mvGeneration;
final int projG = glCtx.projGeneration;
final boolean mvChanged = mvG != mvGen;
final boolean projChanged = projG != projGen;
if (mvChanged || projChanged) {
stageMatrices(mvChanged, projChanged);
stageMatrices(mvChanged, projChanged, glCtx);
mvGen = mvG;
projGen = projG;
stagedMatrices++;
dirty = true;
}
final int mvLinG = GLStateManager.getMvLinearGeneration();
final int mvLinG = glCtx.mvLinearGeneration;
if (mvLinG != mvLinearGen) {
stageNormalMatrix();
stageNormalMatrix(glCtx);
mvLinearGen = mvLinG;
dirty = true;
}
final int texMatG = GLStateManager.getTexMatrixGeneration();
final int texMatG = glCtx.texMatrixGeneration;
if (texMatG != texMatGen) {
stageTextureMatrices();
stageTextureMatrices(glCtx);
texMatGen = texMatG;
dirty = true;
}
final int litG = GLStateManager.getLightingGeneration();
final int litG = glCtx.lightingGeneration;
if (litG != lightingGen) {
stageLighting();
stageLighting(glCtx);
lightingGen = litG;
stagedLighting++;
dirty = true;
}
final int colG = GLStateManager.getColorGeneration();
final int colG = glCtx.colorGeneration;
if (colG != colorGen) {
stageCurrentColor();
stageCurrentColor(glCtx);
colorGen = colG;
stagedColor++;
dirty = true;
}
final int normG = ShaderManager.getNormalGeneration();
final ShaderManager ffp = glCtx.ffp;
final int normG = ffp.normalGeneration;
if (normG != normalGen) {
final Vector3f normal = ShaderManager.getCurrentNormal();
final Vector3f normal = ffp.currentNormal;
putVec3(FFPUniformBlock.CURRENT_NORMAL, normal.x, normal.y, normal.z);
normalGen = normG;
stagedNormal++;
dirty = true;
}
final int tcG = ShaderManager.getTexCoordGeneration();
final int tcG = ffp.texCoordGeneration;
if (tcG != texCoordGen) {
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_0, ShaderManager.getCurrentTexCoord(0));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_2, ShaderManager.getCurrentTexCoord(2));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_3, ShaderManager.getCurrentTexCoord(3));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_0, ffp.currentTexCoords[0]);
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_2, ffp.currentTexCoords[2]);
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_3, ffp.currentTexCoords[3]);
texCoordGen = tcG;
stagedTexCoord++;
dirty = true;
}
final float brightX = GLSMConfig.lastBrightnessX;
final float brightY = GLSMConfig.lastBrightnessY;
final float brightX = glCtx.lastBrightnessX;
final float brightY = glCtx.lastBrightnessY;
if (brightX != lightmapX || brightY != lightmapY) {
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD, brightX);
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD + 4, brightY);
@@ -149,35 +152,36 @@ public class Uniforms {
stagedLightmap++;
dirty = true;
}
final int tgG = GLStateManager.getTexGenGeneration();
final int tgG = glCtx.texGenGeneration;
if (tgG != texGenGen) {
stageTexGen();
stageTexGen(glCtx);
texGenGen = tgG;
stagedTexGen++;
dirty = true;
}
final int cpG = GLStateManager.getClipPlaneGeneration();
final int cpG = glCtx.clipPlaneGeneration;
if (cpG != clipPlaneGen) {
stageClipPlanes();
stageClipPlanes(glCtx);
clipPlaneGen = cpG;
stagedClipPlanes++;
dirty = true;
}
final int fragG = GLStateManager.getFragmentGeneration();
final int fragG = glCtx.fragmentGeneration;
if (fragG != fragmentGen) {
stageFragment();
stageFragment(glCtx);
fragmentGen = fragG;
stagedFragment++;
dirty = true;
}
final float lw = GLStateManager.getLineState().getWidth();
final LineState line = glCtx.lineState;
final float lw = line.getWidth();
if (lw != lineWidth) {
putFloat(FFPUniformBlock.LINE_WIDTH, lw);
lineWidth = lw;
stagedMisc++;
dirty = true;
}
final ViewportState vp = GLStateManager.getViewportState();
final ViewportState vp = glCtx.viewportState;
if (vp.x != viewportX || vp.y != viewportY || vp.width != viewportWidth || vp.height != viewportHeight) {
putFloat(FFPUniformBlock.VIEWPORT_SIZE, vp.width);
putFloat(FFPUniformBlock.VIEWPORT_SIZE + 4, vp.height);
@@ -189,8 +193,13 @@ public class Uniforms {
stagedMisc++;
dirty = true;
}
final LineState line = GLStateManager.getLineState();
final int stippleFactor = line.getStippleFactor() < 1 ? 1 : line.getStippleFactor();
if (WeatherParams.generation != weatherGen) {
stageWeatherParams();
weatherGen = WeatherParams.generation;
stagedMisc++;
dirty = true;
}
final int stippleFactor = Math.max(line.getStippleFactor(), 1);
final int stipplePacked = (line.getStipplePattern() & 0xFFFF) | (stippleFactor << 16);
if (stipplePacked != lineStipple) {
putInt(FFPUniformBlock.LINE_STIPPLE, stipplePacked);
@@ -215,9 +224,9 @@ public class Uniforms {
}
}
private void stageMatrices(boolean mvChanged, boolean projChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
private void stageMatrices(boolean mvChanged, boolean projChanged, GLContextState glCtx) {
final Matrix4f mv = glCtx.modelViewMatrix;
final Matrix4f proj = glCtx.projectionMatrix;
if (mvChanged) {
putMat4(FFPUniformBlock.MODEL_VIEW_MATRIX, mv);
}
@@ -228,8 +237,8 @@ public class Uniforms {
putMat4(FFPUniformBlock.MVP_MATRIX, mvpMatrix);
}
private void stageNormalMatrix() {
GLStateManager.getModelViewMatrix().normal(normalMatrix);
private void stageNormalMatrix(GLContextState glCtx) {
glCtx.modelViewMatrix.normal(normalMatrix);
putMat3(FFPUniformBlock.NORMAL_MATRIX, normalMatrix);
putFloat(FFPUniformBlock.NORMAL_SCALE, rescaleFactor(normalMatrix, tempVec3));
}
@@ -239,18 +248,19 @@ public class Uniforms {
return f > 1.0e-12f ? 1.0f / (float) Math.sqrt(f) : 1.0f;
}
private void stageTextureMatrices() {
putMat4(FFPUniformBlock.TEXTURE_MATRIX_0, GLStateManager.getTextures().getTextureUnitMatrix(0));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_2, GLStateManager.getTextures().getTextureUnitMatrix(2));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_3, GLStateManager.getTextures().getTextureUnitMatrix(3));
putMat4(FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, GLStateManager.getTextures().getTextureUnitMatrix(1));
private void stageTextureMatrices(GLContextState glCtx) {
final TextureUnitArray textures = glCtx.textures;
putMat4(FFPUniformBlock.TEXTURE_MATRIX_0, textures.getTextureUnitMatrix(0));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_2, textures.getTextureUnitMatrix(2));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_3, textures.getTextureUnitMatrix(3));
putMat4(FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, textures.getTextureUnitMatrix(1));
}
private void stageLighting() {
final MaterialState mat = GLStateManager.getFrontMaterial();
final Vector4f lmAmbient = GLStateManager.getLightModel().ambient;
final LightState light0 = GLStateManager.getLightDataStates()[0];
final LightState light1 = GLStateManager.getLightDataStates()[1];
private void stageLighting(GLContextState glCtx) {
final MaterialState mat = glCtx.frontMaterial;
final Vector4f lmAmbient = glCtx.lightModel.ambient;
final LightState light0 = glCtx.lightDataStates[0];
final LightState light1 = glCtx.lightDataStates[1];
putVec4(FFPUniformBlock.LIGHT_MODEL_AMBIENT, lmAmbient);
putVec4(FFPUniformBlock.MATERIAL_EMISSION, mat.emission);
@@ -284,8 +294,8 @@ public class Uniforms {
putVec3(offset, lightVal.x * materialVal.x, lightVal.y * materialVal.y, lightVal.z * materialVal.z);
}
private void stageCurrentColor() {
final var color = GLStateManager.getColor();
private void stageCurrentColor(GLContextState glCtx) {
final var color = glCtx.color;
putVec4(FFPUniformBlock.CURRENT_COLOR,
Math.clamp(0f, 1f, color.getRed()),
Math.clamp(0f, 1f, color.getGreen()),
@@ -293,8 +303,8 @@ public class Uniforms {
Math.clamp(0f, 1f, color.getAlpha()));
}
private void stageTexGen() {
final TexGenState tg = GLStateManager.getTextures().getTexGenState(0);
private void stageTexGen(GLContextState glCtx) {
final TexGenState tg = glCtx.textures.getTexGenState(0);
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_S, tg.getObjectPlane(GL11.GL_S));
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_T, tg.getObjectPlane(GL11.GL_T));
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_R, tg.getObjectPlane(GL11.GL_R));
@@ -305,8 +315,8 @@ public class Uniforms {
putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_Q, tg.getEyePlane(GL11.GL_Q));
}
private void stageClipPlanes() {
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
private void stageClipPlanes(GLContextState glCtx) {
final ClipPlaneState cps = glCtx.clipPlaneState;
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
@@ -317,24 +327,24 @@ public class Uniforms {
}
}
private void stageFragment() {
putFloat(FFPUniformBlock.ALPHA_REF, GLStateManager.getAlphaState().getReference());
private void stageFragment(GLContextState glCtx) {
putFloat(FFPUniformBlock.ALPHA_REF, glCtx.alphaState.getReference());
for (int i = 0; i < 4; i++) {
final var envState = GLStateManager.getTextures().getTexEnvState(i);
final var envState = glCtx.textures.getTexEnvState(i);
putVec4(FFPUniformBlock.TEX_ENV_COLOR_0 + i * 16,
envState.envColorR, envState.envColorG, envState.envColorB, envState.envColorA);
}
putVec4(FFPUniformBlock.OVERLAY_COLOR,
GLStateManager.getOverlayR(), GLStateManager.getOverlayG(),
GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
glCtx.overlayR, glCtx.overlayG,
glCtx.overlayB, glCtx.overlayA);
final var secondary = GLStateManager.getSecondaryColor();
final var secondary = glCtx.secondaryColor;
putVec3(FFPUniformBlock.SECONDARY_COLOR, secondary.getRed(), secondary.getGreen(), secondary.getBlue());
// Mesa STATE_FOG_PARAMS_OPTIMIZED
final FogState fog = GLStateManager.getFogState();
final FogState fog = glCtx.fogState;
final float start = fog.getStart();
final float end = fog.getEnd();
final float density = fog.getDensity();
@@ -376,6 +386,15 @@ public class Uniforms {
putVec3(offset + 32, m.m20(), m.m21(), m.m22());
}
private void stageWeatherParams() {
putVec4(FFPUniformBlock.WEATHER_PARAMS_0, WeatherParams.translateX, WeatherParams.translateY,
WeatherParams.translateZ, WeatherParams.invRadius);
putVec4(FFPUniformBlock.WEATHER_PARAMS_1, WeatherParams.cameraFracX, WeatherParams.cameraFracZ,
WeatherParams.partialTicks, WeatherParams.age);
putVec4(FFPUniformBlock.WEATHER_PARAMS_2, WeatherParams.rainScroll, WeatherParams.snowScroll,
WeatherParams.rainStrength, 0.0f);
}
private void putVec4(int offset, Vector4f v) {
putVec4(offset, v.x, v.y, v.z, v.w);
}
@@ -427,6 +446,10 @@ public class Uniforms {
stagedMisc = 0;
}
void invalidateBinding() {
bound = false;
}
UniformRingBuffer getRing() {
return ring;
}
@@ -14,30 +14,34 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
public final class VAOManager {
public static final int MAX_ATTRIBS = 16;
public static final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
public final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
private static VAOData current = null;
private static int currentVertexFlags = 0;
private static Attrib[] currentAttribs = null;
public static int boundEBO = 0;
private VAOData current = null;
private int currentVertexFlags = 0;
private Attrib[] currentAttribs = null;
public int boundEBO = 0;
private static int clientSideEnabledCount = 0;
private int clientSideEnabledCount = 0;
public static void init(int defaultVAO) {
public void init(int defaultVAO) {
final VAOData data = new VAOData();
current = data;
currentAttribs = data.attribs;
vaoMap.put(defaultVAO, data);
}
public static void onBindEBO(int ebo) {
public boolean isInitialized() {
return current != null;
}
public void onBindEBO(int ebo) {
boundEBO = ebo;
}
public static void onBindVertexArrayPre(int vaoId) {
public void onBindVertexArrayPre(int vaoId) {
// Save old VAO data
if (current != null) {
current.vertexFlags = currentVertexFlags;
@@ -62,14 +66,14 @@ public final class VAOManager {
clientSideEnabledCount = data.clientSideEnabledCount;
}
public static void onDeleteVertexArray(int vaoId) {
public void onDeleteVertexArray(int vaoId) {
final VAOData data = vaoMap.remove(vaoId);
if (data == null) return;
removeEboOwner(data.ebo, data);
if (data == current) current = null;
}
public static void onDeleteBuffer(int buffer) {
public void onDeleteBuffer(int buffer) {
if (buffer == 0) return;
VAOData owner = eboOwners.remove(buffer);
while (owner != null) {
@@ -80,12 +84,12 @@ public final class VAOManager {
}
}
private static void addEboOwner(int ebo, VAOData data) {
private void addEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
data.nextEboOwner = eboOwners.put(ebo, data);
}
private static void removeEboOwner(int ebo, VAOData data) {
private void removeEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
VAOData node = eboOwners.get(ebo);
if (node == data) {
@@ -105,10 +109,11 @@ public final class VAOManager {
}
}
public static void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public static void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public static int getCurrentVertexFlags() { return currentVertexFlags; }
public static void setCurrentVertexFlags(int flags) { currentVertexFlags = flags; }
public void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public int getVertexFlags() { return currentVertexFlags; }
public void setVertexFlags(int flags) { currentVertexFlags = flags; }
public static void setCurrentVertexFlags(int flags) { GLStateManager.ctx().vaos.currentVertexFlags = flags; }
public static final class VAOData {
public Attrib[] attribs;
@@ -122,7 +127,7 @@ public final class VAOManager {
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
public void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
@@ -139,7 +144,7 @@ public final class VAOManager {
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
public void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
@@ -156,19 +161,19 @@ public final class VAOManager {
}
}
public static boolean isGenericPointerEnabled(int index) {
public boolean isGenericPointerEnabled(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
final Attrib a = currentAttribs[index];
return a != null && a.genericPointer && a.enabled;
}
public static void markConventional(int index) {
public void markConventional(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = currentAttribs[index];
if (a != null) a.genericPointer = false;
}
public static void setEnabled(int index, boolean enabled) {
public void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled != enabled) {
@@ -177,7 +182,7 @@ public final class VAOManager {
a.enabled = enabled;
}
public static void enableAttribute(int index) {
public void enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && !a.enabled) {
@@ -186,7 +191,7 @@ public final class VAOManager {
a.enabled = true;
}
public static void disableAttribute(int index) {
public void disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled) {
@@ -196,7 +201,7 @@ public final class VAOManager {
}
// Lazily allocate each attrib. Most VAOs don't even have 4 attribs, let alone 16.
private static Attrib getAttrib(int index) {
private Attrib getAttrib(int index) {
Attrib a = currentAttribs[index];
if (a == null) {
a = new Attrib();
@@ -205,28 +210,32 @@ public final class VAOManager {
return a;
}
public static boolean isClientSideAttrib(int index) {
public boolean isClientSideAttrib(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null && attrib.isClientSide();
}
public static int getAttribEffectiveStride(int index) {
public Attrib attrib(int index) {
return currentAttribs[index];
}
public int attribEffectiveStride(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.effectiveStride() : 0;
}
public static long getAttribOffset(int index) {
public long attribOffset(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.offset : 0;
}
public static int getAttribVBO(int index) {
public int attribVBO(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.vboId : 0;
}
public static @Nullable Attrib get(int index) {
return currentAttribs[index];
return GLStateManager.ctx().vaos.attrib(index);
}
@@ -236,7 +245,7 @@ public final class VAOManager {
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
public boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
@@ -244,15 +253,15 @@ public final class VAOManager {
* If any enabled vertex attribute uses a client-side pointer (no VBO), upload all such
* attribs into a shared stream VBO so the draw succeeds under core profile.
*/
private static int clientArraysVBO = 0;
private static int clientArraysVBOCapacity = 0;
private static final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
private int clientArraysVBO = 0;
private int clientArraysVBOCapacity = 0;
private final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
public static void uploadClientArraysToVBO() {
public void uploadClientArraysToVBO() {
int totalBytes = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
clientArraysVBOOffsets[i] = -1;
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = currentAttribs[i];
if (a == null || !a.enabled || a.clientPointer == null) continue;
clientArraysVBOOffsets[i] = totalBytes;
totalBytes += a.clientPointer.remaining();
@@ -275,7 +284,7 @@ public final class VAOManager {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if (clientArraysVBOOffsets[i] < 0) continue;
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = currentAttribs[i];
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, clientArraysVBOOffsets[i], a.clientPointer.duplicate());
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
}
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
@@ -50,6 +51,13 @@ public final class VertexKey {
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
private static final long INSTANCING_MASK = 0x7L;
static {
if (Instancing.VALUES.length > INSTANCING_MASK + 1) {
throw new IllegalStateException("Instancing has outgrown the VertexKey field; widen INSTANCING_MASK");
}
}
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -97,7 +105,7 @@ public final class VertexKey {
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & 0x3)]; }
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & INSTANCING_MASK)]; }
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
@@ -126,54 +134,66 @@ public final class VertexKey {
}
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
return packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask, GLStateManager.ctx());
}
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask, GLContextState glCtx) {
long bits = 0;
final boolean lighting = GLStateManager.getLightingState().isEnabled();
final boolean lighting = glCtx.lightingState.isEnabled();
if (lighting) {
bits |= (1L << BIT_LIGHTING);
for (int i = 0; i < FFP_LIGHT_COUNT; i++) {
if (!GLStateManager.getLightStates()[i].isEnabled()) continue;
if (!glCtx.lightStates[i].isEnabled()) continue;
bits |= (1L << (BIT_LIGHT_BASE + i));
// position.w == 0 means directional
if (GLStateManager.getLightDataStates()[i].position.w == 0.0f) {
if (glCtx.lightDataStates[i].position.w == 0.0f) {
bits |= (1L << (BIT_LIGHT_DIR_BASE + i));
}
}
if (GLStateManager.getColorMaterial().isEnabled()) {
if (glCtx.colorMaterial.isEnabled()) {
bits |= (1L << BIT_COLOR_MATERIAL);
final int cmMode = encodeColorMaterialMode(GLStateManager.getColorMaterialParameter().getValue());
final int cmMode = encodeColorMaterialMode(glCtx.colorMaterialParameter.getValue());
bits |= ((long) cmMode & 0x7) << BIT_COLOR_MAT_MODE;
}
if (GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
if (glCtx.lightModel.colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
bits |= (1L << BIT_SEPARATE_SPECULAR);
}
}
if (GLStateManager.getFogMode().isEnabled()) {
if (glCtx.fogMode.isEnabled()) {
bits |= (1L << BIT_FOG);
final int fogDistMode = GLStateManager.getFogState().getFogDistanceMode();
final int fogDistMode = glCtx.fogState.getFogDistanceMode();
bits |= ((long) fogDistMode & 0x3) << BIT_FOG_DIST_MODE;
}
if (GLStateManager.getNormalizeState().isEnabled()) {
if (glCtx.normalizeState.isEnabled()) {
bits |= (1L << BIT_NORMALIZE);
}
if (GLStateManager.getRescaleNormalState().isEnabled()) {
if (glCtx.rescaleNormalState.isEnabled()) {
bits |= (1L << BIT_RESCALE_NORMAL);
}
final TextureUnitArray texUnit = GLStateManager.getTextures();
final TextureUnitArray texUnit = glCtx.textures;
for (int i = 0; i < MAX_UNITS; i++) {
if ((fragUnitMask & (1 << i)) != 0) {
bits |= (1L << (BIT_UNIT_TEX_BASE + i));
}
if (!MatrixHelper.isIdentity(texUnit.getTextureUnitMatrix(i))) {
bits |= (1L << (BIT_UNIT_TEXMAT_BASE + i));
}
}
if (texUnit.texMatIdentityGen != glCtx.texMatrixGeneration) {
int mask = 0;
for (int i = 0; i < MAX_UNITS; i++) {
if (!MatrixHelper.isIdentity(texUnit.getTextureUnitMatrix(i))) {
mask |= 1 << i;
}
}
texUnit.texMatIdentityMask = mask;
texUnit.texMatIdentityGen = glCtx.texMatrixGeneration;
}
bits |= ((long) texUnit.texMatIdentityMask) << BIT_UNIT_TEXMAT_BASE;
// TexGen (unit 0 only) - per-coordinate mode if enabled.
// TODO: per-unit texgen for units 2/3. Deferred - no MC code path uses glTexGen on those.
@@ -210,29 +230,29 @@ public final class VertexKey {
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
if (GLStateManager.consumeUnit23TexCoordSetDuringDraw() && hasTexCoord) {
if (GLStateManager.consumeUnit23TexCoordSetDuringDraw(glCtx) && hasTexCoord) {
bits |= (1L << BIT_UNIT23_UV_FROM_UNIT0);
}
if (GLStateManager.anyClipPlaneEnabled()) {
if (GLStateManager.anyClipPlaneEnabled(glCtx)) {
bits |= (1L << BIT_CLIP_PLANES);
}
if (GLStateManager.wideLineEmulationActive) {
if (glCtx.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
if (GLStateManager.lineStippleActive) {
if (glCtx.lineStippleActive) {
bits |= (1L << BIT_LINE_STIPPLE);
}
bits |= (long) GLStateManager.ffpInstancing.ordinal() << BIT_INSTANCING;
bits |= (long) glCtx.ffpInstancing.ordinal() << BIT_INSTANCING;
return bits;
}
static long withInstancing(long packed, Instancing kind) {
return (packed & ~(0x3L << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
return (packed & ~(INSTANCING_MASK << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
}
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
@@ -21,6 +21,10 @@ public final class VertexShaderGenerator {
emitOutputs(sb, key);
sb.append("void main() {\n");
if (key.instancing() == Instancing.WEATHER) {
sb.append(" ").append(InstancedGlslHelpers.weatherPrologue("u_", "a_", "a_TexCoord0",
"vec4 weatherVertex", "vec4 weatherColor", "vec4 weatherTex0", "vec4 weatherTex1", "\n ")).append('\n');
}
emitPositionTransform(sb, key);
if (key.lightingEnabled()) {
emitNormalTransform(sb, key);
@@ -58,7 +62,7 @@ public final class VertexShaderGenerator {
final Instancing kind = key.instancing();
sb.append("// Vertex attributes\n");
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
if (kind == Instancing.PARTICLE) {
if (kind == Instancing.PARTICLE || kind == Instancing.WEATHER) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
} else {
if (key.hasVertexColor()) {
@@ -88,6 +92,7 @@ public final class VertexShaderGenerator {
return switch (key.instancing()) {
case CUBE -> "cubeUv";
case PARTICLE -> PARTICLE_UV;
case WEATHER -> "weatherTex0";
case NONE, TEMPLATE -> key.hasVertexTexCoord() ? "vec4(a_TexCoord0, 0.0, 1.0)" : "u_CurrentTexCoord0";
};
}
@@ -134,6 +139,8 @@ public final class VertexShaderGenerator {
sb.append(" // Position transform\n");
if (key.instancing() == Instancing.PARTICLE) {
sb.append(" vec4 pos4 = ").append(InstancedGlslHelpers.particlePos("a_InstCenterHalf", "a_Position")).append(";\n");
} else if (key.instancing() == Instancing.WEATHER) {
sb.append(" vec4 pos4 = weatherVertex;\n");
} else {
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
}
@@ -282,6 +289,7 @@ public final class VertexShaderGenerator {
final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
switch (key.instancing()) {
case PARTICLE -> sb.append(" v_Color = a_InstColor;\n");
case WEATHER -> sb.append(" v_Color = weatherColor;\n");
case TEMPLATE, CUBE -> sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
case NONE -> sb.append(" v_Color = ").append(src).append(";\n");
}
@@ -312,6 +320,7 @@ public final class VertexShaderGenerator {
final String lightmap = switch (key.instancing()) {
case PARTICLE, TEMPLATE -> "a_InstLightmap";
case CUBE -> "a_InstLightmapScale.xy";
case WEATHER -> "weatherTex1.xy";
case NONE -> key.hasVertexLightmap() ? "a_TexCoord1" : "u_CurrentLightmapCoord";
};
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(").append(lightmap).append(", 0.0, 1.0);\n");
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
/**
* One rain or snow column per instance.
*/
public final class WeatherInstancedAttribs {
public static final int LOC_COLUMN_SPAN = ParticleInstancedAttribs.LOC_CENTER_HALF;
public static final int LOC_JITTER = ParticleInstancedAttribs.LOC_UV;
public static final int LOC_PARAMS = InstancedAttribs.LOC_COLOR;
public static final int OFFSET_COLUMN_SPAN = 0;
public static final int OFFSET_JITTER = 16;
public static final int OFFSET_PARAMS = 32;
public static final int STRIDE = 48;
public static void pointInstanceAttribs(long base) {
GLStateManager.glVertexAttribPointer(LOC_COLUMN_SPAN, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_COLUMN_SPAN);
GLStateManager.glVertexAttribPointer(LOC_JITTER, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_JITTER);
GLStateManager.glVertexAttribPointer(LOC_PARAMS, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_PARAMS);
}
static void enableInstanceArrays() {
InstancedAttribs.enableInstanced(LOC_COLUMN_SPAN);
InstancedAttribs.enableInstanced(LOC_JITTER);
InstancedAttribs.enableInstanced(LOC_PARAMS);
}
private WeatherInstancedAttribs() {}
}
@@ -0,0 +1,42 @@
package com.gtnewhorizons.angelica.glsm.ffp;
/**
* The per-frame half of the rain and snow instancing, set once per weather draw.
*/
public final class WeatherParams {
public static float translateX;
public static float translateY;
public static float translateZ;
public static float invRadius;
public static float cameraFracX;
public static float cameraFracZ;
public static float partialTicks;
public static float age;
public static float rainScroll;
public static float snowScroll;
public static float rainStrength;
public static int generation;
private WeatherParams() {}
public static void set(float translateX, float translateY, float translateZ, float invRadius,
float cameraFracX, float cameraFracZ, float partialTicks, float age,
float rainScroll, float snowScroll, float rainStrength) {
WeatherParams.translateX = translateX;
WeatherParams.translateY = translateY;
WeatherParams.translateZ = translateZ;
WeatherParams.invRadius = invRadius;
WeatherParams.cameraFracX = cameraFracX;
WeatherParams.cameraFracZ = cameraFracZ;
WeatherParams.partialTicks = partialTicks;
WeatherParams.age = age;
WeatherParams.rainScroll = rainScroll;
WeatherParams.snowScroll = snowScroll;
WeatherParams.rainStrength = rainStrength;
generation++;
}
}
@@ -0,0 +1,69 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* The four corners every rain and snow column is expanded from. x picks the end of the billboard, y picks
* the bottom or top edge.
*/
public final class WeatherQuadMesh {
public static final int VERTEX_FLAGS = VertexFlags.TEXTURE_BIT;
public static final int VERTEX_COUNT = 4;
private static final float[] CORNERS = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f };
private static int vao;
private static int vbo;
private WeatherQuadMesh() {}
public static int vao() {
if (vao != 0) return vao;
final ByteBuffer data = ByteBuffer.allocateDirect(CORNERS.length * Float.BYTES).order(ByteOrder.nativeOrder());
for (float corner : CORNERS) data.putFloat(corner);
data.flip();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
final int corner = VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation();
GLStateManager.glEnableVertexAttribArray(corner);
GLStateManager.glVertexAttribPointer(corner, 2, GL11.GL_FLOAT, false, 2 * Float.BYTES, 0L);
WeatherInstancedAttribs.enableInstanceArrays();
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
return vao;
}
public static void bindInstanceBuffer(int bufferId) {
GLStateManager.glBindVertexArray(vao());
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
WeatherInstancedAttribs.pointInstanceAttribs(0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
public static void delete() {
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
}
}
@@ -2,6 +2,6 @@ package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredAlphaHandler {
boolean isAlphaTestLocked();
void deferAlphaTestToggle(boolean enabled);
void deferAlphaFunc(int function, float reference);
boolean deferAlphaTestToggle(boolean enabled);
boolean deferAlphaFunc(int function, float reference);
}
@@ -41,10 +41,6 @@ public final class FrameHooks {
BackendManager.shutdown();
}
public static void bindSplashVao() {
GLStateManager.glBindVertexArray(GLStateManager.glGenVertexArrays());
}
public static void splashFinished() {
GLStateManager.markSplashComplete("SplashProgress.finish");
}
@@ -1,17 +1,17 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public final class GLSMConfig {
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
// Lightmap (replaces OpenGlHelper.lastBrightnessX/Y)
public static float lastBrightnessX;
public static float lastBrightnessY;
public final class GLSMConfig {
public static int packBrightness(float x, float y) {
return ((int) y << 16) | ((int) x & 0xFFFF);
}
public static int packedLastBrightness() {
return packBrightness(lastBrightnessX, lastBrightnessY);
final GLContextState glCtx = GLStateManager.ctx();
return packBrightness(glCtx.lastBrightnessX, glCtx.lastBrightnessY);
}
public static boolean hudCacheOverride;
@@ -12,7 +12,6 @@ public final class GLSMInitConfig {
@Getter private final boolean lwjglDebug;
@Getter private final StreamingUploader.UploadStrategy streamingUploadStrategy;
@Getter private final Consumer<DirectTessellator> directDrawer;
@Getter private final Runnable streamingDrawerDestroy;
@Getter private final int displayWidth;
@Getter private final int displayHeight;
@Getter private final Runnable postInitCallback;
@@ -24,7 +23,6 @@ public final class GLSMInitConfig {
this.streamingUploadStrategy = builder.streamingUploadStrategy;
this.noErrorChecks = builder.noErrorChecks;
this.directDrawer = builder.directDrawer;
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
this.displayWidth = builder.displayWidth;
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
@@ -43,7 +41,6 @@ public final class GLSMInitConfig {
private boolean lwjglDebug = SystemProperties.LWJGL_DEBUG;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
private Consumer<DirectTessellator> directDrawer = TessellatorStreamingDrawer::drawDirect;
private Runnable streamingDrawerDestroy = TessellatorStreamingDrawer::destroy;
private int displayWidth = 0;
private int displayHeight = 0;
private Runnable postInitCallback = null;
@@ -72,11 +69,6 @@ public final class GLSMInitConfig {
return this;
}
public Builder streamingDrawerDestroy(Runnable streamingDrawerDestroy) {
this.streamingDrawerDestroy = streamingDrawerDestroy;
return this;
}
public Builder displaySize(int width, int height) {
this.displayWidth = width;
this.displayHeight = height;
@@ -11,22 +11,20 @@ public final class TextureUtilHooks {
private TextureUtilHooks() {}
private static int savedFilterTextureId;
public static void setMaxLevel(int mipmapLevels) {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmapLevels);
}
public static void captureBoundFilterTexture() {
savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
GLStateManager.ctx().savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
}
public static void clearSavedFilterTexture() {
savedFilterTextureId = 0;
GLStateManager.ctx().savedFilterTextureId = 0;
}
public static void restoreFilter(int min, int mag) {
final int id = savedFilterTextureId;
final int id = GLStateManager.ctx().savedFilterTextureId;
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
@@ -37,7 +35,7 @@ public final class TextureUtilHooks {
}
public static void restoreAniso(float aniso) {
final int id = savedFilterTextureId;
final int id = GLStateManager.ctx().savedFilterTextureId;
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameterf(id, GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
} else {
@@ -21,6 +21,7 @@ public final class ClassDump {
public void dumpRFBClass(String className, ClassNodeHandle classNode, Object transformer) {
if (!enabled) return;
classNode.markDirty();
dumpClass(className, classNode.getOriginalBytes(), classNode.computeBytes(), transformer);
}
}
@@ -30,11 +30,20 @@ public final class DependencyVerifier {
* @param checks Dependency checks to run
*/
public static void verify(Class<?> anchor, List<Check> checks) {
final Check missing = firstMissing(anchor, checks);
if (missing != null) {
throw new RuntimeException(missing.errorMessage());
}
}
/** @return the first check whose resource is absent, or null when all pass. */
public static Check firstMissing(Class<?> anchor, List<Check> checks) {
for (Check check : checks) {
if (anchor.getResource(check.resourcePath()) == null) {
throw new RuntimeException(check.errorMessage());
return check;
}
}
return null;
}
public static List<Check> gtnhLibChecks(String productName) {
@@ -68,6 +68,7 @@ public final class AttribSnapshot {
final AttribDesc[] out = new AttribDesc[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
final VAOManager vaos = GLStateManager.ctx().vaos;
boolean success = false;
// Pass 1 — unioned [start,end] per vboId. Hot path: one source VBO, zero allocations.
@@ -78,7 +79,7 @@ public final class AttribSnapshot {
final long lastVtx = (long) firstVertex + vertexCount - 1;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = vaos.attrib(i);
if (a == null || !a.enabled || a.vboId == 0) continue;
final int stride = a.effectiveStride();
final long typeBytes = (long) a.size * GLTypes.sizeBytes(a.type);
@@ -114,7 +115,7 @@ public final class AttribSnapshot {
try {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = vaos.attrib(i);
if (a == null || !a.enabled) continue;
if (a.vboId != 0) {
@@ -810,6 +810,8 @@ public class GLSMRedirector {
.add("gluBuild2DMipmaps")
.add("gluErrorString")
);
// Must honor disableErrorChecks like glGetError, or it throws on errors those checks left latched
methodRedirects.put("org/lwjgl/opengl/Util", RedirectMap.newMap().add("checkGLError"));
// Quadric type replacements
typeRedirects.put("org/lwjgl/util/glu/Sphere", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaSphere");
@@ -1,45 +1,46 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
import lombok.Setter;
public class AlphaStateStack extends AlphaState implements IStateStack<AlphaState> {
public final class AlphaStateStack extends AlphaState implements CowStateStack<AlphaStateStack> {
protected final AlphaState[] stack;
protected int pointer;
private final CowDepths cow = new CowDepths();
@Setter private VanillaStateLayer<AlphaState> vanillaLayer;
public AlphaStateStack() {
public AlphaStateStack(int id) {
cow.id = id;
stack = new AlphaState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new AlphaState();
}
}
public AlphaStateStack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public AlphaStateStack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
}
final AlphaState saved = stack[--pointer];
@Override
public void restoreSlot(int s) {
final AlphaState saved = stack[s];
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
set(saved);
}
return this;
}
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
public AlphaState readEffective(AlphaState out) {
@@ -48,7 +49,8 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
return out;
}
public boolean isEmpty() {
return pointer == 0;
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,49 +1,51 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import lombok.Setter;
public class BlendStateStack extends BlendState implements IStateStack<BlendStateStack> {
public final class BlendStateStack extends BlendState implements CowStateStack<BlendStateStack> {
protected final BlendState[] stack;
protected int pointer;
private final CowDepths cow = new CowDepths(StateSet.R_BLEND);
private boolean funcUnknown;
@Setter private VanillaStateLayer<BlendState> vanillaLayer;
public BlendStateStack() {
public BlendStateStack(int id) {
cow.id = id;
stack = new BlendState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new BlendState();
}
}
public BlendStateStack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public BlendStateStack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
}
final BlendState saved = stack[--pointer];
@Override
public void restoreSlot(int s) {
final BlendState saved = stack[s];
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
setExceptFunc(saved);
} else {
set(saved);
}
return this;
}
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
public void setFuncUnknownState() {
@@ -64,11 +66,8 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
return out;
}
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
import com.gtnewhorizons.angelica.glsm.states.BooleanState;
@@ -13,17 +13,12 @@ import lombok.Setter;
* saved when actually modified, dramatically reducing overhead when most states
* don't change (e.g., GL_ENABLE_BIT saves ~270 states but typically only 3-4 change).
*/
public class BooleanStateStack extends BooleanState implements IStateStack<BooleanStateStack> {
public class BooleanStateStack extends BooleanState implements CowStateStack<BooleanStateStack> {
protected final boolean[] stack;
private final CowDepths cow = new CowDepths();
@Setter private VanillaBooleanLayer vanillaLayer;
/**
* The depth at which state has been saved. Compared against GLStateManager.getAttribDepth()
* to determine if we need to save before modification or restore on pop.
*/
protected int savedDepth;
public BooleanStateStack(int glCap) {
this(glCap, false, false);
}
@@ -52,24 +47,24 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
stack = new boolean[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
}
// ==================== Traditional Stack Operations ====================
@Override
public BooleanStateStack push() {
if (savedDepth >= stack.length) {
throw new IllegalStateException("Stack overflow size " + (savedDepth + 1) + " reached");
}
stack[savedDepth++] = vanillaValue();
return this;
public CowDepths cowDepths() {
return cow;
}
@Override
public BooleanStateStack pop() {
if (savedDepth == 0) {
throw new IllegalStateException("Stack underflow");
}
restore(stack[--savedDepth]);
return this;
public void captureSlot(int slot) {
stack[slot] = vanillaValue();
}
@Override
public void restoreSlot(int slot) {
restore(stack[slot]);
}
@Override
public int stackId() {
return cow.id;
}
public boolean isEffectivelyEnabled() {
@@ -88,45 +83,6 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
}
}
@Override
public boolean isEmpty() {
return savedDepth == 0;
}
// ==================== Lazy Copy-on-Write Operations ====================
@Override
public int pushDepth() {
// No-op: global depth is managed by GLStateManager
return GLStateManager.getAttribDepth();
}
/**
* Restore state if it was modified at the current depth.
* Only called by GLStateManager.popState() for states that registered as modified.
*/
@Override
public BooleanStateStack popDepth() {
// We're only called if we were modified, so savedDepth should match
if (savedDepth > 0) {
restore(stack[--savedDepth]);
}
return this;
}
/**
* Called before modifying state. If we haven't saved at the current depth yet,
* save the current value and register with GLStateManager for restoration.
*/
@Override
public void beforeModify() {
final int globalDepth = GLStateManager.getAttribDepth();
if (savedDepth < globalDepth) {
stack[savedDepth++] = vanillaValue();
GLStateManager.registerModifiedState(this);
}
}
@Override
public int getDepth() {
return GLStateManager.getAttribDepth();
@@ -134,7 +90,11 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
@Override
public void setEnabled(boolean enabled) {
beforeModify();
final GLContextState o = cow.owner;
if (o != null && o.attribDepth > 0
&& (stateUnknown || !GLStateManager.isCachingEnabled() || enabled != vanillaValue())) {
beforeModify();
}
super.setEnabled(enabled);
}
@@ -1,50 +1,55 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.Color4;
public class Color4Stack extends Color4 implements IStateStack<Color4Stack> {
public final class Color4Stack extends Color4 implements CowStateStack<Color4Stack> {
protected final Color4[] stack;
private CowDepths cow = new CowDepths();
protected int pointer;
public Color4Stack() {
public Color4Stack(int id) {
cow.id = id;
stack = new Color4[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new Color4();
}
}
public Color4Stack(Color4 color4) {
this();
public Color4Stack(int id, Color4 color4) {
this(id);
set(color4);
}
public Color4Stack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
public Color4Stack restoreBit(int bit) {
final int id = cow.id;
cow = new CowDepths(bit);
cow.id = id;
return this;
}
public Color4Stack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public boolean isEmpty() {
return pointer == 0;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,52 +1,51 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
import lombok.Setter;
public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskStack> {
public final class ColorMaskStack extends ColorMask implements CowStateStack<ColorMaskStack> {
protected final ColorMask[] stack;
protected int pointer;
private final CowDepths cow = new CowDepths(StateSet.R_COLOR_MASK);
@Setter private VanillaStateLayer<ColorMask> vanillaLayer;
public ColorMaskStack() {
public ColorMaskStack(int id) {
cow.id = id;
stack = new ColorMask[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new ColorMask();
}
}
public ColorMaskStack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public ColorMaskStack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
}
final ColorMask saved = stack[--pointer];
@Override
public void restoreSlot(int s) {
final ColorMask saved = stack[s];
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
set(saved);
}
return this;
}
public boolean isEmpty() {
return pointer == 0;
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public int stackId() {
return cow.id;
}
}
@@ -0,0 +1,71 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
/**
* __n__n__
* .------`-\00/-'
* / ## ## (oo)
* / \## __ ./
* |//YY \|/
* ||| |||
* Copy On Write Depths
*/
public final class CowDepths {
public int id = -1;
public GLContextState owner;
public final int restoreBit;
public final int restoreUnit;
private final int[] slotDepth = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
private int pointer;
public CowDepths() {
this(0, -1);
}
public CowDepths(int restoreBit) {
this(restoreBit, -1);
}
public CowDepths(int restoreBit, int restoreUnit) {
this.restoreBit = restoreBit;
this.restoreUnit = restoreUnit;
}
public int claim(int depth) {
if (depth <= 0) return -1;
if (pointer > 0 && slotDepth[pointer - 1] == depth) return -1;
slotDepth[pointer] = depth;
return pointer++;
}
public int claimUnconditional() {
slotDepth[pointer] = -1;
return pointer++;
}
public int pop() {
return pointer > 0 ? --pointer : -1;
}
public int discard(int poppedDepth) {
if (pointer == 0) return -1;
final int parent = poppedDepth - 1;
if (parent > 0 && (pointer < 2 || slotDepth[pointer - 2] != parent)) {
slotDepth[pointer - 1] = parent;
return pointer - 1;
}
pointer--;
return -1;
}
public int top() {
return pointer - 1;
}
public boolean isEmpty() {
return pointer == 0;
}
}
@@ -0,0 +1,133 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.ClipPlaneBooleanState;
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
/**
* __n__n__
* .------`-\00/-'
* / ## ## (oo) Casts look redundant, they aren't. Casting to the
* / \## __ ./ final class avoids an interface lookup on every
* |//YY \|/ call. 74-85% faster push/pops in JMH testing.
* ||| |||
* Copy On Write Dispatch
*/
@SuppressWarnings("RedundantCast")
public final class CowDispatch {
private CowDispatch() {}
public static final byte BLEND = 0;
public static final byte BOOLEAN = 1;
public static final byte CLIP_PLANE_BOOLEAN = 2;
public static final byte TEXTURE_UNIT_BOOLEAN = 3;
public static final byte COLOR_MASK = 4;
public static final byte LIGHT_STATE = 5;
public static final byte MATERIAL = 6;
public static final byte STENCIL = 7;
public static final byte MATRIX_MODE = 8;
public static final byte LIGHT_MODEL = 9;
public static final byte ALPHA = 10;
public static final byte POINT = 11;
public static final byte FOG = 12;
public static final byte VEC4F = 13;
public static final byte VEC3F = 14;
public static final byte INTEGER = 15;
public static final byte DEPTH = 16;
public static final byte VIEWPORT = 17;
public static final byte POLYGON = 18;
public static final byte COLOR4 = 19;
public static final byte LINE = 20;
public static final byte TEXTURE_BINDING = 21;
public static final byte TEX_ENV = 22;
public static final byte SCISSOR = 23;
public static byte kindOf(CowStateStack<?> s) {
final Class<?> c = s.getClass();
if (c == TextureUnitBooleanStateStack.class) return TEXTURE_UNIT_BOOLEAN;
if (c == ClipPlaneBooleanState.class) return CLIP_PLANE_BOOLEAN;
if (c == BooleanStateStack.class) return BOOLEAN;
if (c == BlendStateStack.class) return BLEND;
if (c == ColorMaskStack.class) return COLOR_MASK;
if (c == LightStateStack.class) return LIGHT_STATE;
if (c == MaterialStateStack.class) return MATERIAL;
if (c == StencilStateStack.class) return STENCIL;
if (c == MatrixModeStack.class) return MATRIX_MODE;
if (c == LightModelStateStack.class) return LIGHT_MODEL;
if (c == AlphaStateStack.class) return ALPHA;
if (c == PointStateStack.class) return POINT;
if (c == FogStateStack.class) return FOG;
if (c == Vec4fStack.class) return VEC4F;
if (c == Vec3fStack.class) return VEC3F;
if (c == IntegerStateStack.class) return INTEGER;
if (c == DepthStateStack.class) return DEPTH;
if (c == ViewPortStateStack.class) return VIEWPORT;
if (c == PolygonStateStack.class) return POLYGON;
if (c == Color4Stack.class) return COLOR4;
if (c == LineStateStack.class) return LINE;
if (c == TextureBindingStack.class) return TEXTURE_BINDING;
if (c == TexEnvState.class) return TEX_ENV;
if (c == ScissorStateStack.class) return SCISSOR;
throw new IllegalStateException("Unknown CowStateStack class: " + c.getName());
}
public static boolean topSlotChanged(byte kind, CowStateStack<?> s) {
return switch (kind) {
case BLEND -> ((BlendStateStack) s).topSlotChanged();
case BOOLEAN -> ((BooleanStateStack) s).topSlotChanged();
case CLIP_PLANE_BOOLEAN -> ((ClipPlaneBooleanState) s).topSlotChanged();
case TEXTURE_UNIT_BOOLEAN -> ((TextureUnitBooleanStateStack) s).topSlotChanged();
case COLOR_MASK -> ((ColorMaskStack) s).topSlotChanged();
case LIGHT_STATE -> ((LightStateStack) s).topSlotChanged();
case MATERIAL -> ((MaterialStateStack) s).topSlotChanged();
case STENCIL -> ((StencilStateStack) s).topSlotChanged();
case MATRIX_MODE -> ((MatrixModeStack) s).topSlotChanged();
case LIGHT_MODEL -> ((LightModelStateStack) s).topSlotChanged();
case ALPHA -> ((AlphaStateStack) s).topSlotChanged();
case POINT -> ((PointStateStack) s).topSlotChanged();
case FOG -> ((FogStateStack) s).topSlotChanged();
case VEC4F -> ((Vec4fStack) s).topSlotChanged();
case VEC3F -> ((Vec3fStack) s).topSlotChanged();
case INTEGER -> ((IntegerStateStack) s).topSlotChanged();
case DEPTH -> ((DepthStateStack) s).topSlotChanged();
case VIEWPORT -> ((ViewPortStateStack) s).topSlotChanged();
case POLYGON -> ((PolygonStateStack) s).topSlotChanged();
case COLOR4 -> ((Color4Stack) s).topSlotChanged();
case LINE -> ((LineStateStack) s).topSlotChanged();
case TEXTURE_BINDING -> ((TextureBindingStack) s).topSlotChanged();
case TEX_ENV -> ((TexEnvState) s).topSlotChanged();
case SCISSOR -> ((ScissorStateStack) s).topSlotChanged();
default -> throw new IllegalStateException("Unknown kind: " + kind);
};
}
public static void restoreSlot(byte kind, CowStateStack<?> s, int slot) {
switch (kind) {
case BLEND -> ((BlendStateStack) s).restoreSlot(slot);
case BOOLEAN -> ((BooleanStateStack) s).restoreSlot(slot);
case CLIP_PLANE_BOOLEAN -> ((ClipPlaneBooleanState) s).restoreSlot(slot);
case TEXTURE_UNIT_BOOLEAN -> ((TextureUnitBooleanStateStack) s).restoreSlot(slot);
case COLOR_MASK -> ((ColorMaskStack) s).restoreSlot(slot);
case LIGHT_STATE -> ((LightStateStack) s).restoreSlot(slot);
case MATERIAL -> ((MaterialStateStack) s).restoreSlot(slot);
case STENCIL -> ((StencilStateStack) s).restoreSlot(slot);
case MATRIX_MODE -> ((MatrixModeStack) s).restoreSlot(slot);
case LIGHT_MODEL -> ((LightModelStateStack) s).restoreSlot(slot);
case ALPHA -> ((AlphaStateStack) s).restoreSlot(slot);
case POINT -> ((PointStateStack) s).restoreSlot(slot);
case FOG -> ((FogStateStack) s).restoreSlot(slot);
case VEC4F -> ((Vec4fStack) s).restoreSlot(slot);
case VEC3F -> ((Vec3fStack) s).restoreSlot(slot);
case INTEGER -> ((IntegerStateStack) s).restoreSlot(slot);
case DEPTH -> ((DepthStateStack) s).restoreSlot(slot);
case VIEWPORT -> ((ViewPortStateStack) s).restoreSlot(slot);
case POLYGON -> ((PolygonStateStack) s).restoreSlot(slot);
case COLOR4 -> ((Color4Stack) s).restoreSlot(slot);
case LINE -> ((LineStateStack) s).restoreSlot(slot);
case TEXTURE_BINDING -> ((TextureBindingStack) s).restoreSlot(slot);
case TEX_ENV -> ((TexEnvState) s).restoreSlot(slot);
case SCISSOR -> ((ScissorStateStack) s).restoreSlot(slot);
default -> throw new IllegalStateException("Unknown kind: " + kind);
}
}
}
@@ -0,0 +1,88 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
/**
* __n__n__
* .------`-\00/-'
* / ## ## (oo)
* / \## __ ./
* |//YY \|/
* ||| |||
* Copy On Write State Stack
*/
public interface CowStateStack<T> extends IStateStack<T> {
CowDepths cowDepths();
void captureSlot(int slot);
void restoreSlot(int slot);
default int stackId() {
return cowDepths().id;
}
default void setStackId(int id) {
cowDepths().id = id;
}
default int restoreBit() {
return cowDepths().restoreBit;
}
default int restoreUnit() {
return cowDepths().restoreUnit;
}
default boolean topSlotChanged() {
return false;
}
@Override
default int pushDepth() {
final GLContextState o = cowDepths().owner;
return o == null ? 0 : o.attribDepth;
}
@Override
default void beforeModify() {
final CowDepths d = cowDepths();
final GLContextState o = d.owner;
if (o == null) return;
final int s = d.claim(o.attribDepth);
if (s >= 0) {
captureSlot(s);
GLStateManager.registerModifiedState(o, d.id);
}
}
@Override
default boolean isEmpty() {
return cowDepths().isEmpty();
}
default void drain() {
int s;
while ((s = cowDepths().pop()) >= 0) restoreSlot(s);
}
@SuppressWarnings("unchecked")
@Override
default T push() {
captureSlot(cowDepths().claimUnconditional());
return (T) this;
}
@SuppressWarnings("unchecked")
@Override
default T pop() {
final int s = cowDepths().pop();
if (s < 0) {
throw new IllegalStateException("Stack underflow");
}
restoreSlot(s);
return (T) this;
}
}
@@ -1,49 +1,51 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
import com.gtnewhorizons.angelica.glsm.states.DepthState;
import lombok.Setter;
public class DepthStateStack extends DepthState implements IStateStack<DepthStateStack> {
public final class DepthStateStack extends DepthState implements CowStateStack<DepthStateStack> {
protected final DepthState[] stack;
protected int pointer;
private final CowDepths cow = new CowDepths(StateSet.R_DEPTH);
@Setter private VanillaStateLayer<DepthState> vanillaLayer;
private final DepthState effective = new DepthState();
public DepthStateStack() {
public DepthStateStack(int id) {
cow.id = id;
stack = new DepthState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new DepthState();
}
}
public DepthStateStack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public DepthStateStack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
}
final DepthState saved = stack[--pointer];
@Override
public void restoreSlot(int s) {
final DepthState saved = stack[s];
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
setExceptMask(saved);
} else {
set(saved);
}
return this;
}
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
public boolean isEffectiveMaskEnabled() {
@@ -52,11 +54,8 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
return effective.isEnabled();
}
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,41 +1,38 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.FogState;
public class FogStateStack extends FogState implements IStateStack<FogStateStack> {
public final class FogStateStack extends FogState implements CowStateStack<FogStateStack> {
protected final FogState[] stack;
private final CowDepths cow = new CowDepths();
protected int pointer;
public FogStateStack() {
public FogStateStack(int id) {
cow.id = id;
stack = new FogState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new FogState();
}
}
public FogStateStack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public FogStateStack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean isEmpty() {
return pointer == 0;
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,16 +1,15 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.IntegerState;
public class IntegerStateStack extends IntegerState implements IStateStack<IntegerStateStack> {
public final class IntegerStateStack extends IntegerState implements CowStateStack<IntegerStateStack> {
protected final IntegerState[] stack;
private CowDepths cow = new CowDepths();
protected int pointer;
public IntegerStateStack(int val) {
public IntegerStateStack(int val, int id) {
cow.id = id;
setValue(val);
stack = new IntegerState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
@@ -19,34 +18,40 @@ public class IntegerStateStack extends IntegerState implements IStateStack<Integ
}
}
public IntegerStateStack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
public IntegerStateStack restoreBit(int bit) {
final int id = cow.id;
cow = new CowDepths(bit);
cow.id = id;
return this;
}
@Override
public CowDepths cowDepths() {
return cow;
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
public IntegerStateStack push(int value) {
push().setValue(value);
return this;
}
public IntegerStateStack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
}
public boolean isEmpty() {
return pointer == 0;
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,40 +1,39 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
public class LightModelStateStack extends LightModelState implements IStateStack<LightModelStateStack> {
public final class LightModelStateStack extends LightModelState implements CowStateStack<LightModelStateStack> {
protected final LightModelState[] stack;
private final CowDepths cow = new CowDepths();
protected int pointer;
public LightModelStateStack() {
public LightModelStateStack(int id) {
super();
cow.id = id;
stack = new LightModelState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new LightModelState();
}
}
public LightModelStateStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public LightModelStateStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean isEmpty() { return pointer == 0; }
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,40 +1,39 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.LightState;
public class LightStateStack extends LightState implements IStateStack<LightStateStack> {
public final class LightStateStack extends LightState implements CowStateStack<LightStateStack> {
protected final LightState[] stack;
private final CowDepths cow = new CowDepths();
protected int pointer;
public LightStateStack(int light) {
public LightStateStack(int light, int id) {
super(light);
cow.id = id;
stack = new LightState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new LightState(light);
}
}
public LightStateStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public LightStateStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean isEmpty() { return pointer == 0; }
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,46 +1,44 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.states.LineState;
/**
* Stack for GL line state (width, stipple factor, stipple pattern).
* Used by GL_LINE_BIT push/pop attrib.
*/
public class LineStateStack extends LineState implements IStateStack<LineStateStack> {
public final class LineStateStack extends LineState implements CowStateStack<LineStateStack> {
protected final LineState[] stack;
protected int pointer;
private final CowDepths cow = new CowDepths(StateSet.R_LINE);
public LineStateStack() {
public LineStateStack(int id) {
cow.id = id;
stack = new LineState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new LineState();
}
}
public LineStateStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public LineStateStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean isEmpty() {
return pointer == 0;
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,42 +1,39 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
public class MaterialStateStack extends MaterialState implements IStateStack<MaterialStateStack> {
public final class MaterialStateStack extends MaterialState implements CowStateStack<MaterialStateStack> {
protected final MaterialState[] stack;
private final CowDepths cow = new CowDepths();
protected int pointer;
public MaterialStateStack(int face) {
public MaterialStateStack(int face, int id) {
super(face);
cow.id = id;
stack = new MaterialState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new MaterialState(face);
}
}
public MaterialStateStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public MaterialStateStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean isEmpty() { return pointer == 0; }
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,41 +1,38 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.MatrixMode;
public class MatrixModeStack extends MatrixMode implements IStateStack<MatrixModeStack> {
public final class MatrixModeStack extends MatrixMode implements CowStateStack<MatrixModeStack> {
protected final MatrixMode[] stack;
private final CowDepths cow = new CowDepths();
protected int pointer;
public MatrixModeStack() {
public MatrixModeStack(int id) {
cow.id = id;
stack = new MatrixMode[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new MatrixMode();
}
}
public MatrixModeStack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public MatrixModeStack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean isEmpty() {
return pointer == 0;
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,46 +1,44 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.states.PointState;
/**
* Stack for GL point state (size).
* Used by GL_POINT_BIT push/pop attrib.
*/
public class PointStateStack extends PointState implements IStateStack<PointStateStack> {
public final class PointStateStack extends PointState implements CowStateStack<PointStateStack> {
protected final PointState[] stack;
protected int pointer;
private final CowDepths cow = new CowDepths(StateSet.R_POINT);
public PointStateStack() {
public PointStateStack(int id) {
cow.id = id;
stack = new PointState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new PointState();
}
}
public PointStateStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public PointStateStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean isEmpty() {
return pointer == 0;
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,46 +1,44 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
/**
* Stack for GL polygon state (mode, offset, cull face mode, front face).
* Used by GL_POLYGON_BIT push/pop attrib.
*/
public class PolygonStateStack extends PolygonState implements IStateStack<PolygonStateStack> {
public final class PolygonStateStack extends PolygonState implements CowStateStack<PolygonStateStack> {
protected final PolygonState[] stack;
protected int pointer;
private final CowDepths cow = new CowDepths(StateSet.R_POLYGON);
public PolygonStateStack() {
public PolygonStateStack(int id) {
cow.id = id;
stack = new PolygonState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new PolygonState();
}
}
public PolygonStateStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public PolygonStateStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean isEmpty() {
return pointer == 0;
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -0,0 +1,44 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.states.ScissorState;
public final class ScissorStateStack extends ScissorState implements CowStateStack<ScissorStateStack> {
protected final ScissorState[] stack;
private final CowDepths cow = new CowDepths(StateSet.R_SCISSOR);
public ScissorStateStack(int id) {
cow.id = id;
stack = new ScissorState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new ScissorState();
}
}
@Override
public CowDepths cowDepths() {
return cow;
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.glsm.stacks;
public final class StackIdAllocator {
private StackIdAllocator() {}
private static int next = -1;
private static int perContext = -1;
public static void beginContext() {
next = 0;
}
public static void endContext() {
if (perContext == -1) {
perContext = next;
} else if (next != perContext) {
throw new IllegalStateException("stack id count changed across contexts: " + perContext + " -> " + next);
}
next = -1;
}
public static int nextId() {
return next >= 0 ? next++ : -1;
}
public static int allocated() {
return next;
}
}
@@ -1,46 +1,44 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.states.StencilState;
/**
* Stack for GL stencil buffer state.
* Used by GL_STENCIL_BUFFER_BIT push/pop attrib.
*/
public class StencilStateStack extends StencilState implements IStateStack<StencilStateStack> {
public final class StencilStateStack extends StencilState implements CowStateStack<StencilStateStack> {
protected final StencilState[] stack;
protected int pointer;
private final CowDepths cow = new CowDepths(StateSet.R_STENCIL);
public StencilStateStack() {
public StencilStateStack(int id) {
cow.id = id;
stack = new StencilState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new StencilState();
}
}
public StencilStateStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public StencilStateStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean isEmpty() {
return pointer == 0;
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,49 +1,45 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
public class TextureBindingStack extends TextureBinding implements IStateStack<TextureBindingStack> {
public final class TextureBindingStack extends TextureBinding implements CowStateStack<TextureBindingStack> {
protected final TextureBinding[] stack;
private final CowDepths cow;
protected int pointer;
public TextureBindingStack() {
public TextureBindingStack(int id, int unit) {
cow = new CowDepths(StateSet.R_TEXTURE, unit);
cow.id = id;
stack = new TextureBinding[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new TextureBinding();
}
}
public TextureBindingStack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public TextureBindingStack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public TextureBinding peek() {
return stack[pointer];
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
public boolean isEmpty() {
return pointer == 0;
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public int stackId() {
return cow.id;
}
}
@@ -9,7 +9,7 @@ import org.lwjgl.opengl.GL13;
* BooleanStateStack for per-texture-unit state.
* Switches to correct unit (raw GL) before enable/disable calls.
*/
public class TextureUnitBooleanStateStack extends BooleanStateStack {
public final class TextureUnitBooleanStateStack extends BooleanStateStack {
private final int unitIndex;
public TextureUnitBooleanStateStack(int glCap, int unitIndex) {
@@ -29,12 +29,8 @@ public class TextureUnitBooleanStateStack extends BooleanStateStack {
}
@Override
public TextureUnitBooleanStateStack popDepth() {
if (savedDepth > 0) {
final boolean oldValue = stack[--savedDepth];
setEnabledWithUnitSwitch(oldValue);
}
return this;
public void restoreSlot(int slot) {
setEnabledWithUnitSwitch(stack[slot]);
}
private void setEnabledWithUnitSwitch(boolean enabled) {
@@ -1,47 +1,42 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Vector3f;
/**
* Stack for a Vector3f value (e.g. current normal).
*/
public class Vec3fStack implements IStateStack<Vec3fStack> {
public final class Vec3fStack implements CowStateStack<Vec3fStack> {
private final Vector3f value;
private final float[][] stack;
private int pointer;
private final CowDepths cow = new CowDepths();
public Vec3fStack(Vector3f value) {
public Vec3fStack(Vector3f value, int id) {
this.value = value;
cow.id = id;
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][3];
}
@Override
public Vec3fStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer][0] = value.x;
stack[pointer][1] = value.y;
stack[pointer][2] = value.z;
pointer++;
return this;
public CowDepths cowDepths() {
return cow;
}
@Override
public Vec3fStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
pointer--;
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2]);
return this;
public void captureSlot(int s) {
stack[s][0] = value.x;
stack[s][1] = value.y;
stack[s][2] = value.z;
}
@Override
public boolean isEmpty() {
return pointer == 0;
public void restoreSlot(int s) {
value.set(stack[s][0], stack[s][1], stack[s][2]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,48 +1,43 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Vector4f;
/**
* Stack for a Vector4f value (e.g. current texture coordinates).
*/
public class Vec4fStack implements IStateStack<Vec4fStack> {
public final class Vec4fStack implements CowStateStack<Vec4fStack> {
private final Vector4f value;
private final float[][] stack;
private int pointer;
private final CowDepths cow = new CowDepths();
public Vec4fStack(Vector4f value) {
public Vec4fStack(Vector4f value, int id) {
this.value = value;
cow.id = id;
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][4];
}
@Override
public Vec4fStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer][0] = value.x;
stack[pointer][1] = value.y;
stack[pointer][2] = value.z;
stack[pointer][3] = value.w;
pointer++;
return this;
public CowDepths cowDepths() {
return cow;
}
@Override
public Vec4fStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
pointer--;
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2], stack[pointer][3]);
return this;
public void captureSlot(int s) {
stack[s][0] = value.x;
stack[s][1] = value.y;
stack[s][2] = value.z;
stack[s][3] = value.w;
}
@Override
public boolean isEmpty() {
return pointer == 0;
public void restoreSlot(int s) {
value.set(stack[s][0], stack[s][1], stack[s][2], stack[s][3]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,45 +1,44 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
public class ViewPortStateStack extends ViewportState implements IStateStack<ViewPortStateStack> {
public final class ViewPortStateStack extends ViewportState implements CowStateStack<ViewPortStateStack> {
protected final ViewportState[] stack;
private final CowDepths cow = new CowDepths(StateSet.R_VIEWPORT);
protected int pointer;
public ViewPortStateStack() {
public ViewPortStateStack(int id) {
cow.id = id;
stack = new ViewportState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new ViewportState();
}
}
public ViewPortStateStack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public ViewPortStateStack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean isEmpty() {
return pointer == 0;
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -48,6 +48,7 @@ public class BooleanState implements ISettableState<BooleanState> {
if (stateUnknown || bypass || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.vendorIsAMD() && GLStateManager.isPoppingAttributes())) {
stateUnknown = false;
this.enabled = enabled;
onEnabledChanged();
if (!ffpStateOnly) {
if (enabled) {
RENDER_BACKEND.enable(this.glCap);
@@ -58,6 +59,8 @@ public class BooleanState implements ISettableState<BooleanState> {
}
}
protected void onEnabledChanged() {}
@Override
public BooleanState set(BooleanState state) {
this.enabled = state.enabled;
@@ -0,0 +1,54 @@
package com.gtnewhorizons.angelica.glsm.states;
import java.nio.IntBuffer;
public class ScissorState implements ISettableState<ScissorState> {
public int x;
public int y;
public int width;
public int height;
public void setScissor(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void get(IntBuffer params) {
params.put(0, x);
params.put(1, y);
params.put(2, width);
params.put(3, height);
}
public void get(int[] params) {
params[0] = x;
params[1] = y;
params[2] = width;
params[3] = height;
}
@Override
public ScissorState set(ScissorState state) {
this.x = state.x;
this.y = state.y;
this.width = state.width;
this.height = state.height;
return this;
}
@Override
public boolean sameAs(Object state) {
if (this == state) return true;
if (!(state instanceof ScissorState scissorState)) return false;
return x == scissorState.x && y == scissorState.y && width == scissorState.width && height == scissorState.height;
}
@Override
public ScissorState copy() {
return new ScissorState().set(this);
}
}
@@ -1,14 +1,15 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.stacks.CowDepths;
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
/**
* Per-texture-unit GL_TEXTURE_ENV state for FFP emulation. Tracks the simple texenv mode and all GL_COMBINE sub-parameters.
*/
public class TexEnvState implements IStateStack<TexEnvState> {
public final class TexEnvState implements CowStateStack<TexEnvState> {
public int mode;
@@ -30,22 +31,25 @@ public class TexEnvState implements IStateStack<TexEnvState> {
public float envColorA;
private final TexEnvState[] stack;
private int pointer;
private final CowDepths cow;
private final boolean isStackEntry;
public TexEnvState() {
public TexEnvState(int id) {
this(false);
cow.id = id;
}
private TexEnvState(boolean isStackEntry) {
this.isStackEntry = isStackEntry;
if (isStackEntry) {
stack = null;
cow = null;
} else {
stack = new TexEnvState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new TexEnvState(true);
}
cow = new CowDepths();
}
reset();
}
@@ -80,31 +84,27 @@ public class TexEnvState implements IStateStack<TexEnvState> {
}
@Override
public TexEnvState push() {
if (stack == null) throw new IllegalStateException("Cannot push stack entry");
if (pointer >= stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].copyFrom(this);
return this;
public CowDepths cowDepths() {
if (cow == null) throw new IllegalStateException("Cannot push/pop a stack entry");
return cow;
}
@Override
public TexEnvState pop() {
if (stack == null) throw new IllegalStateException("Cannot pop stack entry");
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
copyFrom(stack[--pointer]);
return this;
public void captureSlot(int s) {
stack[s].copyFrom(this);
}
@Override
public boolean isEmpty() {
return pointer == 0;
public void restoreSlot(int s) {
copyFrom(stack[s]);
}
public boolean isCombineMode() {
return mode == GL13.GL_COMBINE;
}
@Override
public int stackId() {
return cow == null ? -1 : cow.id;
}
}
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
import com.gtnewhorizons.angelica.glsm.stacks.TextureBindingStack;
import com.gtnewhorizons.angelica.glsm.stacks.TextureUnitBooleanStateStack;
import lombok.Getter;
@@ -8,7 +10,11 @@ import org.joml.Matrix4fStack;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import java.util.ArrayList;
import java.util.List;
public class TextureUnitArray {
private final List<IStateStack<?>> idStacks = new ArrayList<>();
private final TextureBindingStack[] bindings;
private final TextureUnitBooleanStateStack[] states;
private final TextureUnitBooleanStateStack[] texture1DStates;
@@ -22,6 +28,9 @@ public class TextureUnitArray {
@Getter
public final Matrix4fStack[] textureMatricies;
public int texMatIdentityMask;
public int texMatIdentityGen;
public TextureUnitArray() {
bindings = new TextureBindingStack[GLStateManager.MAX_TEXTURE_UNITS];
states = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
@@ -36,20 +45,35 @@ public class TextureUnitArray {
textureMatricies = new Matrix4fStack[GLStateManager.MAX_TEXTURE_UNITS];
for (int i = 0; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
bindings[i] = new TextureBindingStack();
states[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_2D, i);
texture1DStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_1D, i);
texture3DStates[i] = new TextureUnitBooleanStateStack(GL12.GL_TEXTURE_3D, i);
texGenSStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_S, i);
texGenTStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_T, i);
texGenRStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_R, i);
texGenQStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_Q, i);
bindings[i] = member(new TextureBindingStack(StackIdAllocator.nextId(), i));
states[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_2D, i));
texture1DStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_1D, i));
texture3DStates[i] = track(new TextureUnitBooleanStateStack(GL12.GL_TEXTURE_3D, i));
texGenSStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_S, i));
texGenTStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_T, i));
texGenRStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_R, i));
texGenQStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_Q, i));
texGenStates[i] = new TexGenState();
texEnvStates[i] = new TexEnvState();
texEnvStates[i] = member(new TexEnvState(StackIdAllocator.nextId()));
textureMatricies[i] = new Matrix4fStack(GLStateManager.MAX_TEXTURE_STACK_DEPTH);
}
}
private <T extends TextureUnitBooleanStateStack> T track(T s) {
s.setStackId(StackIdAllocator.nextId());
idStacks.add(s);
return s;
}
private <T extends IStateStack<?>> T member(T s) {
idStacks.add(s);
return s;
}
public List<IStateStack<?>> idStacks() {
return idStacks;
}
public TextureBindingStack getTextureUnitBindings(int index) {
return bindings[index];
}
@@ -0,0 +1,13 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
public final class StreamingVaos {
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE;
final int[] persistentVAOs = new int[FORMAT_COUNT];
final int[] orphanVAOs = new int[FORMAT_COUNT];
final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
}
@@ -6,6 +6,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
@@ -47,11 +48,6 @@ public class TessellatorStreamingDrawer {
private static PersistentStreamingBuffer persistentBuffer;
private static final OrphanStreamingBuffer[] orphanBuffers = new OrphanStreamingBuffer[FORMAT_COUNT];
private static final int[] persistentVAOs = new int[FORMAT_COUNT];
private static final int[] orphanVAOs = new int[FORMAT_COUNT];
private static final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
private static final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
private static ByteBuffer extScratch;
private static long extScratchAddress;
private static int extScratchCapacity;
@@ -243,13 +239,18 @@ public class TessellatorStreamingDrawer {
final int normalOffset = ImmediateExtendedAttribHandler.normalOffset(format);
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
final int combinedBytes = vertexCount * combinedStride;
ensureExtScratch(combinedBytes);
interleaveBase(srcBase, extScratchAddress, vertexCount, vertexSize, combinedStride);
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
final boolean locked = GLStateManager.acquireDrawLock();
try {
ensureExtScratch(combinedBytes);
interleaveBase(srcBase, extScratchAddress, vertexCount, vertexSize, combinedStride);
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
return true;
}
@@ -311,7 +312,9 @@ public class TessellatorStreamingDrawer {
Tracy.beginZone(Z_SDL_STREAM_DRAW);
final boolean locked = GLStateManager.acquireDrawLock();
try {
ensureVAO(flags, format);
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos, flags, format);
if (Tracy.ENABLED) {
streamedBytes += packed.remaining();
@@ -326,10 +329,10 @@ public class TessellatorStreamingDrawer {
final boolean fromRing = firstVertex >= 0;
if (fromRing) {
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.persistentVAOs[flags]);
} else {
if (Tracy.ENABLED && persistentBuffer != null) orphanFallbacks++;
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.orphanVAOs[flags]);
orphanBuffers[flags].upload(packed);
firstVertex = 0;
}
@@ -340,7 +343,7 @@ public class TessellatorStreamingDrawer {
lastStreamDrawMode = drawMode;
}
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
drawWithQuadConversion(glCtx, drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
} finally {
if (locked) GLStateManager.releaseDrawLock();
@@ -352,11 +355,11 @@ public class TessellatorStreamingDrawer {
return persistentBuffer == null ? 0L : persistentBuffer.getWraps();
}
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
private static void drawWithQuadConversion(GLContextState glCtx, int drawMode, int firstVertex, int vertexCount) {
FfpExtendedAttribs.beginInternalDraw();
try {
if (drawMode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount);
QuadConverter.drawQuadsAsTriangles(glCtx, firstVertex, vertexCount);
} else {
GLStateManager.glDrawArrays(drawMode, firstVertex, vertexCount);
}
@@ -366,8 +369,10 @@ public class TessellatorStreamingDrawer {
}
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int combinedStride, int drawMode, int vertexCount) {
ensureVAO(flags, format);
ensureExtendedVAOs(flags, format);
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos, flags, format);
ensureExtendedVAOs(vaos, flags, format);
int firstVertex = -1;
@@ -376,18 +381,20 @@ public class TessellatorStreamingDrawer {
}
if (firstVertex >= 0) {
GLStateManager.glBindVertexArray(extendedPersistentVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.extendedPersistentVAOs[flags]);
} else {
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.extendedOrphanVAOs[flags]);
orphanBuffers[flags].upload(combined);
firstVertex = 0;
}
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
drawWithQuadConversion(glCtx, drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
}
private static void ensureExtendedVAOs(int flags, VertexFormat format) {
private static void ensureExtendedVAOs(StreamingVaos vaos, int flags, VertexFormat format) {
final int[] extendedOrphanVAOs = vaos.extendedOrphanVAOs;
final int[] extendedPersistentVAOs = vaos.extendedPersistentVAOs;
if (extendedOrphanVAOs[flags] == 0) {
extendedOrphanVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
@@ -467,12 +474,13 @@ public class TessellatorStreamingDrawer {
return repackBuffer;
}
private static void ensureVAO(int flags, VertexFormat format) {
private static void ensureVAO(StreamingVaos vaos, int flags, VertexFormat format) {
init();
if (orphanBuffers[flags] == null) orphanBuffers[flags] = new OrphanStreamingBuffer();
final int[] orphanVAOs = vaos.orphanVAOs;
final int[] persistentVAOs = vaos.persistentVAOs;
if (orphanVAOs[flags] == 0) {
orphanBuffers[flags] = new OrphanStreamingBuffer();
orphanVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
@@ -489,33 +497,4 @@ public class TessellatorStreamingDrawer {
}
}
/**
* Clean up all VAOs, streaming buffers, and the repack buffer.
*/
public static void destroy() {
for (int i = 0; i < FORMAT_COUNT; i++) {
if (persistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(persistentVAOs[i]); persistentVAOs[i] = 0; }
if (orphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(orphanVAOs[i]); orphanVAOs[i] = 0; }
if (extendedPersistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(extendedPersistentVAOs[i]); extendedPersistentVAOs[i] = 0; }
if (extendedOrphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(extendedOrphanVAOs[i]); extendedOrphanVAOs[i] = 0; }
if (orphanBuffers[i] != null) { orphanBuffers[i].destroy(); orphanBuffers[i] = null; }
}
if (extScratch != null) {
memFree(extScratch);
extScratch = null;
extScratchAddress = 0;
extScratchCapacity = 0;
}
if (persistentBuffer != null) {
persistentBuffer.destroy();
persistentBuffer = null;
}
if (repackBuffer != null) {
memFree(repackBuffer);
repackBuffer = null;
repackAddress = 0;
repackCapacity = 0;
}
initialized = false;
}
}
@@ -0,0 +1,56 @@
out vec4 fragColor;
vec4 glintLayer(vec4 coord, out bool visible) {
vec4 color = textureProj(u_Sampler0, coord) * v_Color;
#if GLINT_OVERLAY == 2
color.rgb = mix(color.rgb, v_Overlay.rgb, v_Overlay.a);
#elif GLINT_OVERLAY == 1
color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);
#endif
#if GLINT_LIGHTMAP
color *= textureProj(u_Sampler1, v_TexCoord1);
#endif
float qa = round(color.a * 255.0);
float ref = u_AlphaRef * 255.0;
#if GLINT_ALPHA_FUNC == 0
visible = false;
#elif GLINT_ALPHA_FUNC == 1
visible = qa < ref;
#elif GLINT_ALPHA_FUNC == 2
visible = qa == round(ref);
#elif GLINT_ALPHA_FUNC == 3
visible = qa <= ref;
#elif GLINT_ALPHA_FUNC == 4
visible = qa > ref;
#elif GLINT_ALPHA_FUNC == 5
visible = qa != round(ref);
#elif GLINT_ALPHA_FUNC == 6
visible = qa >= ref;
#else
visible = true;
#endif
if (!visible) return vec4(0.0);
#if GLINT_FOG_MODE == 1
float fog = v_FogCoord * u_FogParams.x + u_FogParams.y;
#elif GLINT_FOG_MODE == 2
float fog = exp2(-(v_FogCoord * u_FogParams.z));
#elif GLINT_FOG_MODE == 3
float fogDistance = v_FogCoord * u_FogParams.w;
float fog = exp2(-(fogDistance * fogDistance));
#endif
#if GLINT_FOG_MODE != 0
color.rgb = mix(u_FogColor.rgb, color.rgb, clamp(fog, 0.0, 1.0));
#endif
return color;
}
void main() {
bool visible0, visible1;
vec4 a = glintLayer(v_TexCoord0, visible0);
vec4 b = glintLayer(u_TextureMatrix3 * v_TexCoord0, visible1);
if (!visible0 && !visible1) discard;
fragColor = sqrt(a * a + b * b);
#if GLINT_REPLACE_ALPHA
fragColor.a = visible1 ? b.a : a.a;
#endif
}
@@ -68,7 +68,7 @@ class CompatUniformManagerTest {
// Use program and upload — should not error
GL20.glUseProgram(program);
CompatUniformManager.refreshCompatUniforms(program);
CompatUniformManager.refreshCompatUniforms(program, GLStateManager.ctx());
// Verify no GL errors
assertEquals(0, GL11.glGetError(), "GL error after uniform upload");
@@ -146,7 +146,7 @@ class CompatUniformManagerTest {
// Use program and upload — should not error
GL20.glUseProgram(program);
CompatUniformManager.refreshCompatUniforms(program);
CompatUniformManager.refreshCompatUniforms(program, GLStateManager.ctx());
assertEquals(0, GL11.glGetError(), "GL error after fog uniform upload");
}
@@ -1,7 +1,11 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -12,6 +16,28 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class DisplayListIdAllocatorTest {
@Test
void concurrentAllocationsRemainUnique() throws Exception {
final DisplayListIDAllocator alloc = new DisplayListIDAllocator();
final int[][] ids = new int[2][500];
final CountDownLatch start = new CountDownLatch(1);
final TestThreads.Worker[] workers = new TestThreads.Worker[2];
for (int t = 0; t < workers.length; t++) {
final int[] lane = ids[t];
workers[t] = TestThreads.start("DisplayListIdAllocator-" + t, () -> {
if (!start.await(5, TimeUnit.SECONDS)) throw new AssertionError("start timed out");
for (int i = 0; i < lane.length; i++) lane[i] = alloc.allocRange(1);
});
}
start.countDown();
for (TestThreads.Worker worker : workers) worker.join();
final Set<Integer> unique = new HashSet<>();
for (int[] lane : ids) {
for (int id : lane) assertTrue(unique.add(id));
}
assertEquals(1000, unique.size());
}
@Test
void zeroNeverReturned() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator(1);
@@ -0,0 +1,38 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.lwjgl.opengl.SharedDrawable;
import java.util.Arrays;
import java.util.Map;
final class DrawableContexts {
private DrawableContexts() {}
static TestThreads.Worker start(SharedDrawable drawable, String name, TestThreads.Body body) {
return TestThreads.start(name, () -> {
GLStateManager.makeCurrent(drawable);
try {
body.run();
} finally {
GLStateManager.releaseContext(drawable);
}
});
}
static void run(SharedDrawable drawable, String name, TestThreads.Body body) throws InterruptedException {
start(drawable, name, body).join();
}
static void destroy(SharedDrawable drawable) {
final Map<Object, GLContextState> contexts = Reflect.getStatic(GLStateManager.class, "drawableContexts");
final GLContextState ctx = contexts.remove(drawable);
if (ctx != null) {
final GLContextState[] states = Reflect.getStatic(GLStateManager.class, "drawableContextStates");
Reflect.setStatic(GLStateManager.class, "drawableContextStates", Arrays.stream(states).filter(s -> s != ctx).toArray(GLContextState[]::new));
}
drawable.destroy();
}
}
@@ -0,0 +1,305 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@GLCoreTest
public class GLSM_DisplayList_StateSet_GLTest {
private final List<Integer> listsToDelete = new ArrayList<>();
@AfterEach
void cleanup() {
DisplayListManager.abortCompilation();
while (GLStateManager.getAttribDepth() > 0) {
GLStateManager.popState();
}
for (int id : listsToDelete) {
GLStateManager.glDeleteLists(id, 1);
}
listsToDelete.clear();
GLStateManager.disableBlend();
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.disableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.disableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
}
private int newList() {
final int id = GLStateManager.glGenLists(1);
listsToDelete.add(id);
return id;
}
private static Int2IntMap commandCounts(int listId) {
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(listId);
assertNotNull(compiled, "compiled display list must exist for id " + listId);
return compiled.getCommandCounts();
}
@Test
void compileBracketDoesNotThrowAndLeavesLiveStateUntouched() {
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int func0 = GLStateManager.getAlphaState().getFunction();
final float ref0 = GLStateManager.getAlphaState().getReference();
final int unit0 = GLStateManager.getActiveTextureUnit();
final int depth0 = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
verifyIsEnabled(GL11.GL_ALPHA_TEST, true, "cache alpha test enable unchanged after COMPILE-only bracket");
assertEquals(func0, GLStateManager.getAlphaState().getFunction(), "cache alpha func unchanged after COMPILE-only bracket");
assertEquals(ref0, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref unchanged after COMPILE-only bracket");
assertEquals(unit0, GLStateManager.getActiveTextureUnit(), "cache active unit unchanged after COMPILE-only bracket");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after COMPILE-only bracket");
assertEquals(2, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "push and pop each record one COMPLEX_REF");
}
@Test
void compiledBracketReplayRestoresState() {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.9f);
GLStateManager.disableAlphaTest();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int depth0 = GLStateManager.getAttribDepth();
GLStateManager.glCallList(list);
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored by replay");
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored by replay");
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled by replay");
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored by replay");
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "driver active unit restored by replay");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged by balanced replay");
}
@Test
void compileAndExecuteBracketIsBalanced() {
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.9f);
GLStateManager.disableAlphaTest();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int depth0 = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored after GL_COMPILE_AND_EXECUTE bracket");
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after balanced GL_COMPILE_AND_EXECUTE bracket");
GLStateManager.glCallList(list);
GLStateManager.glCallList(list);
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored after replay");
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored after replay");
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled after replay");
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored after replay");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after replay");
}
@Test
void nestedListsPushInBothLevels() {
GLStateManager.enableBlend();
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
final boolean cull0 = GLStateManager.glIsEnabled(GL11.GL_CULL_FACE);
final int depth0 = GLStateManager.getAttribDepth();
final int parent = newList();
final int child = newList();
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
final int dParent = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.glNewList(child, GL11.GL_COMPILE);
final int dChild = GLStateManager.pushState(StateSet.CULL);
if (cull0) {
GLStateManager.disableCull();
} else {
GLStateManager.enableCull();
}
GLStateManager.popStateTo(dChild);
GLStateManager.glEndList();
GLStateManager.disableBlend();
GLStateManager.popStateTo(dParent);
GLStateManager.glEndList();
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged by nested COMPILE-only bracket");
GLStateManager.glCallList(parent);
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored by the parent BLEND bracket after parent replay");
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored after parent replay");
GLStateManager.glCallList(child);
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored after child replay");
}
@Test
void nestedCompileAndExecuteChildInsideCompileParent() {
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
final boolean cull0 = GLStateManager.glIsEnabled(GL11.GL_CULL_FACE);
final int depth0 = GLStateManager.getAttribDepth();
final int parent = newList();
final int child = newList();
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
final int dParent = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.glNewList(child, GL11.GL_COMPILE_AND_EXECUTE);
final int dChild = GLStateManager.pushState(StateSet.CULL);
if (cull0) {
GLStateManager.disableCull();
} else {
GLStateManager.enableCull();
}
GLStateManager.popStateTo(dChild);
GLStateManager.glEndList();
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored live immediately after the COMPILE_AND_EXECUTE child bracket closes");
GLStateManager.popStateTo(dParent);
GLStateManager.glEndList();
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after balanced nested bracket");
assertEquals(2, commandCounts(parent).getOrDefault(GLCommand.COMPLEX_REF, 0), "parent list records exactly the BLEND push and pop");
assertEquals(2, commandCounts(child).getOrDefault(GLCommand.COMPLEX_REF, 0), "child list records exactly the CULL push and pop");
}
@Test
void unbalancedBracketIsClosedAtEndList() {
GLStateManager.enableBlend();
final boolean blend0 = GLStateManager.glIsEnabled(GL11.GL_BLEND);
final int depth0 = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.pushState(StateSet.BATCH);
GLStateManager.disableBlend();
GLStateManager.glEndList();
assertEquals(2, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "glEndList closes the unbalanced push, recording a matching pop");
GLStateManager.glCallList(list);
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after replaying an auto-closed bracket");
verifyIsEnabled(GL11.GL_BLEND, blend0, "blend enable unchanged after replaying an auto-closed bracket");
final int other = newList();
assertDoesNotThrow(() -> {
GLStateManager.glNewList(other, GL11.GL_COMPILE);
GLStateManager.glEndList();
}, "an unrelated list must compile normally after an auto-closed bracket");
}
@Test
void glPushAttribRecordsExactlyOnce() {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glPopAttrib();
GLStateManager.glEndList();
final Int2IntMap counts = commandCounts(list);
assertEquals(1, counts.getOrDefault(GLCommand.PUSH_ATTRIB, 0), "glPushAttrib records exactly one PUSH_ATTRIB opcode");
assertEquals(1, counts.getOrDefault(GLCommand.POP_ATTRIB, 0), "glPopAttrib records exactly one POP_ATTRIB opcode");
assertEquals(0, counts.getOrDefault(GLCommand.COMPLEX_REF, 0), "glPushAttrib/glPopAttrib never record a COMPLEX_REF");
}
@Test
void livePushStraddlesOpenList() {
GLStateManager.enableBlend();
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.disableBlend();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glEndList();
GLStateManager.popStateTo(d);
assertEquals(d, GLStateManager.getAttribDepth(), "live attrib depth restored to the pre-bracket value");
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored by the live pop that straddled the open-then-closed list");
}
@Test
void livePopInsideOpenListPopsLiveAndRecordsNothing() {
GLStateManager.enableBlend();
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.disableBlend();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
assertEquals(d, GLStateManager.getAttribDepth(), "live attrib depth restored by the live pop performed inside the open list");
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored live by the pop of a pre-list push");
assertEquals(0, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "popping a pre-existing live push records nothing into the list");
}
@Test
void virtualDepthOverflowThrowsAtRecordTime() {
final int startDepth = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
final int pushes = GLStateManager.MAX_ATTRIB_STACK_DEPTH - startDepth;
for (int i = 0; i < pushes; i++) {
GLStateManager.pushState(StateSet.CUTOUT);
}
assertThrows(IllegalStateException.class, () -> GLStateManager.pushState(StateSet.CUTOUT), "virtual depth overflow beyond MAX_ATTRIB_STACK_DEPTH must throw at record time");
DisplayListManager.abortCompilation();
assertEquals(startDepth, GLStateManager.getAttribDepth(), "aborting the overflowed compilation leaves live attrib depth untouched");
}
}
@@ -0,0 +1,252 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
public class GLSM_PopAttribMaskFidelity_GLTest {
private static int scanClipPlaneMask() {
final BooleanStateStack[] planes = GLStateManager.getClipPlaneStates();
int mask = 0;
for (int i = 0; i < planes.length; i++) {
if (planes[i].isEnabled()) mask |= (1 << i);
}
return mask;
}
private static void verifyClipPlaneMask(String message) {
final int scanned = scanClipPlaneMask();
assertEquals(scanned, GLStateManager.ctx().clipPlaneEnabledMask, message + " - mask mismatch");
assertEquals(scanned != 0, GLStateManager.anyClipPlaneEnabled(), message + " - anyClipPlaneEnabled mismatch");
}
@Test
void colorBufferBitDoesNotRestoreCull() {
try {
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Initial");
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_CULL_FACE);
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - Not a GL_COLOR_BUFFER_BIT member, must stay toggled");
} finally {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
}
}
@Test
void enableBitRestoresCull() {
try {
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Initial");
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glEnable(GL11.GL_CULL_FACE);
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - After enable");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - GL_ENABLE_BIT member, must restore");
} finally {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
}
}
@Test
void innerNonMemberPopKeepsChange_OuterMemberPopRestores() {
try {
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Initial");
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_CULL_FACE);
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - After enable inside inner bracket");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - Inner pop is not a member, must stay toggled");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Outer pop is a member, must restore to pre-inner value");
} finally {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
}
}
@Test
void modifyBeforeInnerPush_SurvivesInnerNonMemberPop() {
try {
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Initial");
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glEnable(GL11.GL_CULL_FACE);
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - After enable at outer depth");
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - Inner pop is not a member, must stay toggled");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Outer pop is a member, must restore");
} finally {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
}
}
@Test
void twoModifiesAtOneDepthRestoreOnce() {
try {
verifyIsEnabled(GL11.GL_BLEND, false, "Blend - Initial");
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_BLEND);
GLStateManager.glDisable(GL11.GL_BLEND);
GLStateManager.glEnable(GL11.GL_BLEND);
verifyIsEnabled(GL11.GL_BLEND, true, "Blend - After multiple toggles at one depth");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_BLEND, false, "Blend - GL_COLOR_BUFFER_BIT member, must restore original");
} finally {
GLStateManager.glDisable(GL11.GL_BLEND);
}
}
@Test
void lightingBitRestoresLightingAndLightEnables() {
try {
verifyIsEnabled(GL11.GL_LIGHTING, false, "Lighting - Initial");
verifyIsEnabled(GL11.GL_LIGHT0, false, "Light0 - Initial");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
GLStateManager.glEnable(GL11.GL_LIGHTING);
GLStateManager.glEnable(GL11.GL_LIGHT0);
verifyIsEnabled(GL11.GL_LIGHTING, true, "Lighting - After enable");
verifyIsEnabled(GL11.GL_LIGHT0, true, "Light0 - After enable");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_LIGHTING, false, "Lighting - GL_LIGHTING_BIT member, must restore");
verifyIsEnabled(GL11.GL_LIGHT0, false, "Light0 - GL_LIGHTING_BIT member, must restore");
} finally {
GLStateManager.glDisable(GL11.GL_LIGHTING);
GLStateManager.glDisable(GL11.GL_LIGHT0);
}
}
@Test
void enableDisableTracksClipPlaneMask() {
try {
verifyClipPlaneMask("Initial");
GLStateManager.glEnable(GL11.GL_CLIP_PLANE0);
verifyIsEnabled(GL11.GL_CLIP_PLANE0, true, "Clip0 - After enable");
verifyClipPlaneMask("After enable");
GLStateManager.glDisable(GL11.GL_CLIP_PLANE0);
verifyIsEnabled(GL11.GL_CLIP_PLANE0, false, "Clip0 - After disable");
verifyClipPlaneMask("After disable");
} finally {
GLStateManager.glDisable(GL11.GL_CLIP_PLANE0);
}
}
@Test
void transformBitPopRestoresClipPlaneAndMask() {
try {
verifyClipPlaneMask("Initial");
GLStateManager.glPushAttrib(GL11.GL_TRANSFORM_BIT);
GLStateManager.glEnable(GL11.GL_CLIP_PLANE1);
verifyIsEnabled(GL11.GL_CLIP_PLANE1, true, "Clip1 - After enable inside GL_TRANSFORM_BIT");
verifyClipPlaneMask("After enable inside GL_TRANSFORM_BIT");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CLIP_PLANE1, false, "Clip1 - GL_TRANSFORM_BIT member, must restore");
verifyClipPlaneMask("After GL_TRANSFORM_BIT pop");
} finally {
GLStateManager.glDisable(GL11.GL_CLIP_PLANE1);
}
}
@Test
void scissorBoxFollowsScissorBitOnly() {
final int[] boxA = { 10, 20, 100, 200 };
final int[] boxB = { 30, 40, 50, 60 };
try {
GLStateManager.glScissor(boxA[0], boxA[1], boxA[2], boxA[3]);
GLStateManager.disableScissorTest();
verifyState(GL11.GL_SCISSOR_BOX, boxA, "Scissor box - Initial");
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - Initial");
GLStateManager.glPushAttrib(GL11.GL_SCISSOR_BIT);
GLStateManager.enableScissorTest();
GLStateManager.glScissor(boxB[0], boxB[1], boxB[2], boxB[3]);
GLStateManager.glPopAttrib();
verifyState(GL11.GL_SCISSOR_BOX, boxA, "Scissor box - GL_SCISSOR_BIT member, must restore");
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - GL_SCISSOR_BIT member, must restore");
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.enableScissorTest();
GLStateManager.glScissor(boxB[0], boxB[1], boxB[2], boxB[3]);
GLStateManager.glPopAttrib();
verifyState(GL11.GL_SCISSOR_BOX, boxB, "Scissor box - Not a GL_ENABLE_BIT member, must stay changed");
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - GL_ENABLE_BIT member, must restore");
} finally {
GLStateManager.disableScissorTest();
GLStateManager.glScissor(0, 0, 800, 600);
}
}
@Test
void rasterizerDiscardIsNotAnEnableBitMember() {
try {
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - Initial");
GLStateManager.glEnable(GL30.GL_RASTERIZER_DISCARD);
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, true, "RasterizerDiscard - After enable");
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glDisable(GL30.GL_RASTERIZER_DISCARD);
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - After disable inside GL_ENABLE_BIT");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - Not a GL_ENABLE_BIT member, must stay disabled");
} finally {
GLStateManager.glDisable(GL30.GL_RASTERIZER_DISCARD);
}
}
@Test
void clearDepthIsClamped() {
try {
GLStateManager.glClearDepth(2.0);
verifyState(GL11.GL_DEPTH_CLEAR_VALUE, 1.0f, "Depth clear value - glClearDepth(2.0) must clamp to 1.0");
} finally {
GLStateManager.glClearDepth(1.0);
}
}
@Test
void colorBufferBitPopDoesNotRestoreClipPlaneButMaskStaysFaithful() {
try {
verifyClipPlaneMask("Initial");
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_CLIP_PLANE2);
verifyIsEnabled(GL11.GL_CLIP_PLANE2, true, "Clip2 - After enable inside GL_COLOR_BUFFER_BIT");
verifyClipPlaneMask("After enable inside GL_COLOR_BUFFER_BIT");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CLIP_PLANE2, true, "Clip2 - Not a GL_COLOR_BUFFER_BIT member, must stay toggled");
verifyClipPlaneMask("After GL_COLOR_BUFFER_BIT pop");
} finally {
GLStateManager.glDisable(GL11.GL_CLIP_PLANE2);
}
}
}
@@ -15,6 +15,7 @@ import java.util.concurrent.TimeUnit;
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.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -37,7 +38,10 @@ public class GLSM_ServerSideState_UnitTest {
private Runnable pendingCommand;
private Throwable backgroundError;
private volatile boolean shutdownRequested;
private volatile GLContextState workerContext;
private volatile boolean workerCaching;
private boolean originalSplashComplete;
private Thread originalDrawableGLHolder;
private static void setSplashComplete(boolean value) {
SplashWindow.setSplashComplete(value);
@@ -50,6 +54,7 @@ public class GLSM_ServerSideState_UnitTest {
@BeforeEach
void setupSharedDrawable() throws Exception {
originalSplashComplete = getSplashComplete();
originalDrawableGLHolder = GLStateManager.getDrawableGLHolder();
// Reset splash state to simulate splash screen scenario
setSplashComplete(false);
@@ -67,6 +72,8 @@ public class GLSM_ServerSideState_UnitTest {
shutdownRequested = false;
backgroundError = null;
workerContext = null;
workerCaching = false;
initLatch = new CountDownLatch(1);
commandLatch = new CountDownLatch(1);
completeLatch = new CountDownLatch(1);
@@ -75,10 +82,8 @@ public class GLSM_ServerSideState_UnitTest {
backgroundThread = new Thread(() -> {
try {
GLStateManager.makeCurrent(sharedDrawable);
if (GLStateManager.isCachingEnabled()) {
throw new AssertionError("SharedDrawable thread should have caching DISABLED");
}
workerContext = GLStateManager.ctx();
workerCaching = GLStateManager.isCachingEnabled();
// Signal that initialization is complete
initLatch.countDown();
@@ -102,11 +107,12 @@ public class GLSM_ServerSideState_UnitTest {
}
} catch (InterruptedException e) {
// Normal shutdown
} catch (LWJGLException e) {
backgroundError = e;
} catch (Throwable t) {
backgroundError = t;
initLatch.countDown();
} finally {
try {
sharedDrawable.releaseContext();
GLStateManager.releaseContext(sharedDrawable);
} catch (LWJGLException e) {
// Ignore on cleanup
}
@@ -116,6 +122,11 @@ public class GLSM_ServerSideState_UnitTest {
// Wait for background thread to complete initialization
assertTrue(initLatch.await(5, TimeUnit.SECONDS), "Background thread failed to initialize");
if (backgroundError != null) {
fail("Background thread failed to make SharedDrawable current", backgroundError);
}
assertTrue(workerCaching, "SharedDrawable thread should cache into its own context");
assertNotSame(GLStateManager.ctx(), workerContext, "SharedDrawable thread must not share the main thread's context");
}
@AfterEach
@@ -132,11 +143,12 @@ public class GLSM_ServerSideState_UnitTest {
}
if (sharedDrawable != null) {
sharedDrawable.destroy();
DrawableContexts.destroy(sharedDrawable);
}
// Restore original state
GLStateManager.setDrawableGL(null);
GLStateManager.setDrawableGLHolder(originalDrawableGLHolder);
setSplashComplete(originalSplashComplete);
if (originalSplashComplete) {
GLStateManager.markSplashComplete("test");
@@ -328,7 +340,11 @@ public class GLSM_ServerSideState_UnitTest {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId1);
assertEquals(texId1, GLStateManager.getBoundTextureForServerState(), "Main thread cache should show texId1 bound");
executeOnSharedDrawable(() -> GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId2));
executeOnSharedDrawable(() -> {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId2);
assertEquals(texId2, GLStateManager.getBoundTextureForServerState(), "SharedDrawable cache should show texId2 bound");
assertEquals(texId2, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "SharedDrawable actual GL binding should be texId2");
});
assertEquals(texId1, GLStateManager.getBoundTextureForServerState(), "Main thread cache should still show texId1 (client-side state is per-context)");
@@ -436,38 +452,40 @@ public class GLSM_ServerSideState_UnitTest {
}
}
/**
* Test glDeleteTextures: SharedDrawable deletes texture, verify cache cleared.
*/
@Test
void testDeleteTexturesFromSharedDrawable() throws InterruptedException {
int texId = GL11.glGenTextures();
final int texId = GLStateManager.glGenTextures();
int flushGen = 0;
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, 32, 32, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
int cachedFilter = GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
assertEquals(GL11.GL_LINEAR, cachedFilter, "Cache should have LINEAR before delete");
assertEquals(GL11.GL_LINEAR, GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER), "Cache should have LINEAR before delete");
assertTrue(GL11.glIsTexture(texId), "Texture should exist before delete");
// SharedDrawable: delete the texture (deferred — name stays valid)
executeOnSharedDrawable(() -> {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
GLStateManager.glDeleteTextures(texId);
assertEquals(0, GLStateManager.getBoundTextureForServerState(), "Delete should unbind from the deleting context");
assertTrue(GL11.glIsTexture(texId), "Texture name should still be valid (deferred delete)");
});
assertTrue(GL11.glIsTexture(texId), "Texture name should still be valid on main thread (deferred delete)");
assertEquals(texId, GLStateManager.getBoundTextureForServerState(), "Main thread binding should survive until the flush");
// glGenTextures flushes deferred deletes
int newTexId = GLStateManager.glGenTextures();
assertFalse(GL11.glIsTexture(texId), "Texture should be gone after flush via glGenTextures");
GL11.glDeleteTextures(newTexId);
final int deferredGen = GLStateManager.glGenTextures();
GL11.glDeleteTextures(deferredGen);
assertTrue(GL11.glIsTexture(texId), "Delete should stay deferred while the splash is not complete");
setSplashComplete(true);
flushGen = GLStateManager.glGenTextures();
assertFalse(GL11.glIsTexture(texId), "Texture should be gone after the post-splash flush");
assertEquals(0, GLStateManager.getBoundTextureForServerState(), "Flush should scrub the main thread binding");
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
if (flushGen != 0) GL11.glDeleteTextures(flushGen);
if (GL11.glIsTexture(texId)) {
GL11.glDeleteTextures(texId);
}
@@ -0,0 +1,302 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.SharedDrawable;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCoreTest
public class GLSM_SharedDrawableContext_GLTest {
private SharedDrawable sharedDrawable;
@AfterEach
void cleanup() {
if (sharedDrawable != null) {
DrawableContexts.destroy(sharedDrawable);
sharedDrawable = null;
}
}
@Test
void sharedDrawableBindsItsOwnContext() throws Exception {
final GLContextState primary = GLStateManager.ctx();
sharedDrawable = new SharedDrawable(Display.getDrawable());
TestThreads.run("SharedDrawableContext-Bind-Thread", () -> {
GLStateManager.makeCurrent(sharedDrawable);
try {
final GLContextState workerCtx = GLStateManager.ctx();
assertNotSame(primary, workerCtx);
assertNotEquals(0, workerCtx.defaultVAO);
assertEquals(workerCtx.defaultVAO, GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING));
final IntBuffer viewport = BufferUtils.createIntBuffer(16);
GL11.glGetInteger(GL11.GL_VIEWPORT, viewport);
assertEquals(0, viewport.get(0));
assertEquals(0, viewport.get(1));
assertEquals(Display.getWidth(), viewport.get(2));
assertEquals(Display.getHeight(), viewport.get(3));
} finally {
GLStateManager.releaseContext(sharedDrawable);
}
assertSame(primary, GLStateManager.ctx());
});
}
@Test
void failedContextSwitchRestoresPreviousBinding() throws Exception {
final GLContextState primary = GLStateManager.ctx();
sharedDrawable = new SharedDrawable(Display.getDrawable());
final CountDownLatch acquired = new CountDownLatch(1);
final CountDownLatch release = new CountDownLatch(1);
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-Owner-Thread", () -> {
acquired.countDown();
if (!release.await(5, TimeUnit.SECONDS)) throw new AssertionError("release timed out");
});
try {
assertTrue(acquired.await(5, TimeUnit.SECONDS));
assertThrows(IllegalStateException.class, () -> GLStateManager.makeCurrent(sharedDrawable));
assertSame(primary, GLStateManager.ctx());
assertEquals(primary.defaultVAO, GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING));
} finally {
release.countDown();
worker.join();
}
GLStateManager.makeCurrent(sharedDrawable);
assertNotSame(primary, GLStateManager.ctx());
GLStateManager.releaseContext(sharedDrawable);
GLStateManager.makeCurrent(Display.getDrawable());
assertSame(primary, GLStateManager.ctx());
}
@Test
void foreignThreadCannotFinishOrRecordIntoActiveList() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
final int existing = GLStateManager.glGenLists(1);
final int active = GLStateManager.glGenLists(1);
final int rejected = GLStateManager.glGenLists(1);
GLStateManager.glNewList(existing, GL11.GL_COMPILE);
GLStateManager.enableBlend();
GLStateManager.glEndList();
GLStateManager.disableBlend();
final CountDownLatch started = new CountDownLatch(1);
final CountDownLatch finish = new CountDownLatch(1);
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-List-Thread", () -> {
GLStateManager.glNewList(active, GL11.GL_COMPILE);
started.countDown();
if (!finish.await(5, TimeUnit.SECONDS)) throw new AssertionError("finish timed out");
GLStateManager.glEndList();
});
try {
try {
assertTrue(started.await(5, TimeUnit.SECONDS));
assertThrows(IllegalStateException.class, () -> GLStateManager.glNewList(rejected, GL11.GL_COMPILE));
assertThrows(IllegalStateException.class, GLStateManager::glEndList);
assertEquals(0, GLStateManager.getListMode());
DisplayListManager.abortIfLeaked();
assertThrows(IllegalStateException.class, DisplayListManager::abortCompilation);
GLStateManager.glCallList(existing);
assertTrue(GLStateManager.glIsEnabled(GL11.GL_BLEND));
assertEquals(0, GLStateManager.getListMode());
} finally {
finish.countDown();
worker.join();
}
assertTrue(DisplayListManager.displayListExists(active));
GLStateManager.glCallList(active);
} finally {
GLStateManager.glDeleteLists(existing, 1);
GLStateManager.glDeleteLists(active, 1);
GLStateManager.glDeleteLists(rejected, 1);
GLStateManager.disableBlend();
}
}
@Test
void simultaneousListStartsClaimOneOwner() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
final int first = GLStateManager.glGenLists(1);
final int second = GLStateManager.glGenLists(1);
final CyclicBarrier start = new CyclicBarrier(2);
final CountDownLatch attempted = new CountDownLatch(2);
final AtomicInteger completed = new AtomicInteger();
final AtomicInteger rejected = new AtomicInteger();
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-List-Race-Thread", () -> {
start.await();
try {
GLStateManager.glNewList(second, GL11.GL_COMPILE);
} catch (IllegalStateException expected) {
rejected.incrementAndGet();
attempted.countDown();
return;
}
attempted.countDown();
if (!attempted.await(5, TimeUnit.SECONDS)) throw new AssertionError("attempts timed out");
GLStateManager.glEndList();
completed.incrementAndGet();
});
try {
start.await();
try {
GLStateManager.glNewList(first, GL11.GL_COMPILE);
} catch (IllegalStateException expected) {
rejected.incrementAndGet();
attempted.countDown();
}
if (GLStateManager.getListMode() != 0) {
attempted.countDown();
assertTrue(attempted.await(5, TimeUnit.SECONDS));
GLStateManager.glEndList();
completed.incrementAndGet();
}
worker.join();
} finally {
GLStateManager.glDeleteLists(first, 1);
GLStateManager.glDeleteLists(second, 1);
}
assertEquals(1, completed.get());
assertEquals(1, rejected.get());
}
@Test
void reenteredContextRebindsFfpProgram() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
DrawableContexts.run(sharedDrawable, "SharedDrawableContext-Reentry-Thread", () -> {
final ShaderManager sm = ShaderManager.getInstance();
final boolean wasEnabled = ShaderManager.isEnabled();
ShaderManager.enable();
sm.activate();
try {
final FloatBuffer vertices = BufferUtils.createFloatBuffer(9);
vertices.put(new float[] { -0.5f, -0.5f, 0f, 0.5f, -0.5f, 0f, 0f, 0.5f, 0f });
vertices.flip();
final int vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, vertices, GL15.GL_STATIC_DRAW);
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, 12, 0);
VAOManager.setCurrentVertexFlags(0);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
GLStateManager.releaseContext(sharedDrawable);
GLStateManager.makeCurrent(sharedDrawable);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
assertNotEquals(0, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM), "invalidateProgram must force a rebind after replay clears the program to 0");
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteBuffers(vbo);
} finally {
sm.deactivate();
if (!wasEnabled) ShaderManager.disable();
}
});
}
@Test
void deferredTextureDeletesWaitForSplashAndScrubDrawableContexts() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
final int t = GLStateManager.glGenTextures();
final AtomicReference<GLContextState> workerCtxRef = new AtomicReference<>();
DrawableContexts.run(sharedDrawable, "SharedDrawableContext-DeferredDelete-Thread", () -> {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t);
workerCtxRef.set(GLStateManager.ctx());
});
final GLContextState workerCtx = workerCtxRef.get();
int deferredGen = 0;
int flushGen = 0;
try {
SplashWindow.setSplashComplete(false);
GLStateManager.glDeleteTextures(t);
deferredGen = GLStateManager.glGenTextures();
assertTrue(GL11.glIsTexture(t), "delete must stay deferred while the splash is not complete");
assertEquals(t, workerCtx.textures.getTextureUnitBindings(0).getBinding(), "worker ctx binding must survive until the flush");
SplashWindow.setSplashComplete(true);
flushGen = GLStateManager.glGenTextures();
assertFalse(GL11.glIsTexture(t), "the flush must delete the texture once the splash is complete");
assertEquals(0, workerCtx.textures.getTextureUnitBindings(0).getBinding(), "the flush must scrub the drawable context's binding");
} finally {
SplashWindow.setSplashComplete(true);
if (deferredGen != 0) GL11.glDeleteTextures(deferredGen);
if (flushGen != 0) GL11.glDeleteTextures(flushGen);
}
}
@Test
void concurrentContextsDoNotShareAttribOrCurrentState() throws Exception {
final GLContextState primary = GLStateManager.ctx();
sharedDrawable = new SharedDrawable(Display.getDrawable());
final CyclicBarrier barrier = new CyclicBarrier(2);
final AtomicReference<GLContextState> workerCtxRef = new AtomicReference<>();
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-Concurrent-Thread", () -> {
workerCtxRef.set(GLStateManager.ctx());
barrier.await();
for (int i = 0; i < 20000; i++) {
final int t = GLStateManager.pushState(StateSet.FONT);
GLStateManager.glNormal3f(0.1f, 0.2f, 0.3f);
GLStateManager.popStateTo(t);
}
GLStateManager.glNormal3f(0.1f, 0.2f, 0.3f);
});
try {
barrier.await();
for (int i = 0; i < 20000; i++) {
final int t = GLStateManager.pushState(StateSet.FONT);
GLStateManager.glNormal3f(0.25f, 0.5f, 0.75f);
GLStateManager.popStateTo(t);
}
GLStateManager.glNormal3f(0.25f, 0.5f, 0.75f);
worker.join();
final GLContextState workerCtx = workerCtxRef.get();
assertNotSame(primary, workerCtx);
assertEquals(0, primary.attribDepth);
assertEquals(0, workerCtx.attribDepth);
assertEquals(0.25f, primary.ffp.currentNormal.x, 0.0f);
assertEquals(0.5f, primary.ffp.currentNormal.y, 0.0f);
assertEquals(0.75f, primary.ffp.currentNormal.z, 0.0f);
assertEquals(0.1f, workerCtx.ffp.currentNormal.x, 0.0f);
assertEquals(0.2f, workerCtx.ffp.currentNormal.y, 0.0f);
assertEquals(0.3f, workerCtx.ffp.currentNormal.z, 0.0f);
} finally {
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
}
}
}
@@ -1,9 +1,14 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.Display;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -63,4 +68,37 @@ public class GLSM_SplashCompleteLatch_UnitTest {
t.join();
assertTrue(cachingOffThread.get(), "after the latch caching is global - this is what the latch buys");
}
@Test
void pausedSplashThreadDoesNotCompleteUntilItExits() throws Exception {
final CountDownLatch released = new CountDownLatch(1);
final CountDownLatch resume = new CountDownLatch(1);
GLStateManager.releaseContext(Display.getDrawable());
try {
final TestThreads.Worker worker = TestThreads.start("SplashPause-Worker-Thread", () -> {
GLStateManager.makeCurrent(Display.getDrawable());
GLStateManager.releaseContext(Display.getDrawable());
released.countDown();
resume.await();
});
assertTrue(released.await(10, TimeUnit.SECONDS), "worker did not release the Display drawable in time");
GLStateManager.makeCurrent(Display.getDrawable());
assertFalse(GLStateManager.isSplashComplete());
resume.countDown();
worker.join();
GLStateManager.releaseContext(Display.getDrawable());
GLStateManager.makeCurrent(Display.getDrawable());
assertTrue(GLStateManager.isSplashComplete());
} finally {
resume.countDown();
Reflect.setStatic(GLStateManager.class, "stateSeedPending", false);
Reflect.setStatic(GLStateManager.class, "splashDisplayReleaser", null);
GLStateManager.makeCurrent(Display.getDrawable());
}
}
}
@@ -0,0 +1,709 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCoreTest
public class GLSM_StateSet_GLTest {
private static int driverTextureBinding(int unit) {
final int saved = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
final int binding = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GL13.glActiveTexture(saved);
return binding;
}
private static boolean[] driverColorWriteMask() {
final ByteBuffer buf = BufferUtils.createByteBuffer(16);
GL11.glGetBoolean(GL11.GL_COLOR_WRITEMASK, buf);
return new boolean[] { buf.get(0) != 0, buf.get(1) != 0, buf.get(2) != 0, buf.get(3) != 0 };
}
private static int compileShader(int type, String src) {
final int s = GL20.glCreateShader(type);
GL20.glShaderSource(s, src);
GL20.glCompileShader(s);
assertEquals(GL11.GL_TRUE, GL20.glGetShaderi(s, GL20.GL_COMPILE_STATUS), () -> "shader compile failed: " + GL20.glGetShaderInfoLog(s, 4096));
return s;
}
private static final String TRIVIAL_VS = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }\n";
private static final String TRIVIAL_FS = "#version 330 core\nout vec4 o;\nvoid main(){ o = vec4(1.0); }\n";
private static int linkTrivialProgram() {
final int v = compileShader(GL20.GL_VERTEX_SHADER, TRIVIAL_VS);
final int f = compileShader(GL20.GL_FRAGMENT_SHADER, TRIVIAL_FS);
final int p = GL20.glCreateProgram();
GL20.glAttachShader(p, v);
GL20.glAttachShader(p, f);
GL20.glLinkProgram(p);
assertEquals(GL11.GL_TRUE, GL20.glGetProgrami(p, GL20.GL_LINK_STATUS), () -> "link failed: " + GL20.glGetProgramInfoLog(p, 4096));
GL20.glDeleteShader(v);
GL20.glDeleteShader(f);
return p;
}
@Test
void batchRestoresBlendAxisAndLightingBooleanButNotStencil() {
try {
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.disableLighting();
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
final int d = GLStateManager.pushState(StateSet.BATCH);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
GLStateManager.enableLighting();
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
GLStateManager.popStateTo(d);
verifyIsEnabled(GL11.GL_BLEND, true, "Blend enable - restored by BATCH pop");
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb");
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_DST_RGB), "driver blend dst rgb");
assertEquals(GL11.GL_SRC_ALPHA, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb");
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, GLStateManager.getBlendState().getDstRgb(), "cache blend dst rgb");
verifyIsEnabled(GL11.GL_LIGHTING, false, "Lighting enable - restored by BATCH pop");
verifyIsEnabled(GL11.GL_STENCIL_TEST, true, "Stencil test - not a BATCH member, must stay toggled");
} finally {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.disableLighting();
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
}
}
@Test
void fontRestoresDepthAxisAndShadeModelButNotCull() {
try {
GLStateManager.enableDepthTest();
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
final int d = GLStateManager.pushState(StateSet.FONT);
GLStateManager.disableDepthTest();
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
GLStateManager.glShadeModel(GL11.GL_FLAT);
GLStateManager.glCullFace(GL11.GL_FRONT);
GLStateManager.popStateTo(d);
verifyIsEnabled(GL11.GL_DEPTH_TEST, true, "Depth test enable - restored by FONT pop");
assertEquals(GL11.GL_LEQUAL, GLStateManager.getDepthState().getFunc(), "cache depth func");
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth func");
assertEquals(GL11.GL_SMOOTH, GLStateManager.getShadeModelState().getValue(), "cache shade model");
assertEquals(GL11.GL_FRONT, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - not a FONT member, must stay toggled");
assertEquals(GL11.GL_FRONT, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - not a FONT member, must stay toggled");
} finally {
GLStateManager.disableDepthTest();
GLStateManager.glDepthFunc(GL11.GL_LESS);
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
}
}
@Test
void fontPipelineRestoresBlendAndDepthFuncButNotProgram() {
final int p0 = linkTrivialProgram();
final int p1 = linkTrivialProgram();
try {
GLStateManager.glUseProgram(p0);
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
final int d = GLStateManager.pushState(StateSet.FONT_PIPELINE);
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
GLStateManager.glUseProgram(p1);
GLStateManager.popStateTo(d);
assertEquals(GL11.GL_SRC_ALPHA, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - restored by FONT_PIPELINE pop");
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb - restored by FONT_PIPELINE pop");
assertEquals(GL11.GL_LEQUAL, GLStateManager.getDepthState().getFunc(), "cache depth func - restored by FONT_PIPELINE pop");
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth func - restored by FONT_PIPELINE pop");
assertEquals(p1, GLStateManager.getActiveProgram(), "program - not a FONT_PIPELINE member, must stay toggled");
GLStateManager.glUseProgram(p0);
} finally {
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(p0);
GLStateManager.glDeleteProgram(p1);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glDepthFunc(GL11.GL_LESS);
}
}
@Test
void handRestoresBlendAxisAndUnit1TexturingButNotColorMask() {
try {
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glColorMask(true, true, true, true);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int d = GLStateManager.pushState(StateSet.HAND);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
GLStateManager.glColorMask(false, false, false, false);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.disableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
verifyIsEnabled(GL11.GL_BLEND, true, "Blend enable - restored by HAND pop");
assertEquals(GL11.GL_SRC_ALPHA, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb");
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb");
assertTrue(GLStateManager.getTextures().getTextureUnitStates(1).isEnabled(), "unit 1 texturing - restored by HAND pop");
final boolean[] driverMask = driverColorWriteMask();
assertFalse(driverMask[0] || driverMask[1] || driverMask[2] || driverMask[3], "driver color mask - not a HAND member, must stay toggled");
assertFalse(GLStateManager.getColorMask().red || GLStateManager.getColorMask().green
|| GLStateManager.getColorMask().blue || GLStateManager.getColorMask().alpha, "cache color mask - not a HAND member, must stay toggled");
} finally {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glColorMask(true, true, true, true);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.disableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
}
}
@Test
void masksRestoresDepthMaskAndColorMaskButNotDepthTestEnable() {
try {
GLStateManager.glDepthMask(true);
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.glColorMask(true, false, true, false);
GLStateManager.disableDepthTest();
final int d = GLStateManager.pushState(StateSet.MASKS);
GLStateManager.glDepthMask(false);
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
GLStateManager.glColorMask(false, true, false, true);
GLStateManager.enableDepthTest();
GLStateManager.popStateTo(d);
assertTrue(GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK), "driver depth write mask - restored by MASKS pop");
assertTrue(GLStateManager.getDepthState().isEnabled(), "cache depth write mask - restored by MASKS pop");
assertEquals(GL11.GL_LEQUAL, GLStateManager.getDepthState().getFunc(), "cache depth func");
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth func");
final boolean[] driverMask = driverColorWriteMask();
assertTrue(driverMask[0] && !driverMask[1] && driverMask[2] && !driverMask[3], "driver color mask - restored by MASKS pop");
assertTrue(GLStateManager.getColorMask().red && !GLStateManager.getColorMask().green
&& GLStateManager.getColorMask().blue && !GLStateManager.getColorMask().alpha, "cache color mask - restored by MASKS pop");
verifyIsEnabled(GL11.GL_DEPTH_TEST, true, "Depth test enable - not a MASKS member, must stay toggled");
} finally {
GLStateManager.glDepthMask(true);
GLStateManager.glDepthFunc(GL11.GL_LESS);
GLStateManager.glColorMask(true, true, true, true);
GLStateManager.disableDepthTest();
}
}
@Test
void cullRestoresPolygonAxisAndCullEnableButNotBlendFunc() {
try {
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
final int d = GLStateManager.pushState(StateSet.CULL);
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_FRONT);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
GLStateManager.popStateTo(d);
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull enable - restored by CULL pop");
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode");
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode");
verifyIsEnabled(GL11.GL_BLEND, false, "Blend enable - not a CULL member, must stay toggled");
assertEquals(GL11.GL_DST_COLOR, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - not a CULL member, must stay toggled");
assertEquals(GL11.GL_DST_COLOR, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb - not a CULL member, must stay toggled");
} finally {
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void blendRestoresBlendAxisButNotCull() {
try {
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_FRONT);
GLStateManager.popStateTo(d);
verifyIsEnabled(GL11.GL_BLEND, true, "Blend enable - restored by BLEND pop");
assertEquals(GL11.GL_SRC_ALPHA, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb");
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb");
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull enable - not a BLEND member, must stay toggled");
assertEquals(GL11.GL_FRONT, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - not a BLEND member, must stay toggled");
assertEquals(GL11.GL_FRONT, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - not a BLEND member, must stay toggled");
} finally {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
}
}
@Test
void alphaRestoresAlphaAxisButNotBlend() {
try {
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
final int d = GLStateManager.pushState(StateSet.ALPHA);
GLStateManager.disableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.1f);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
GLStateManager.popStateTo(d);
verifyIsEnabled(GL11.GL_ALPHA_TEST, true, "Alpha test enable - restored by ALPHA pop");
assertEquals(GL11.GL_GREATER, GLStateManager.getAlphaState().getFunction(), "cache alpha func");
assertEquals(0.5f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref");
verifyIsEnabled(GL11.GL_BLEND, false, "Blend enable - not an ALPHA member, must stay toggled");
assertEquals(GL11.GL_DST_COLOR, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - not an ALPHA member, must stay toggled");
assertEquals(GL11.GL_DST_COLOR, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb - not an ALPHA member, must stay toggled");
} finally {
GLStateManager.disableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void cutoutRestoresAlphaAxisAndUnit1TexturingButNotBlend() {
try {
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.disableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.disableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
final int fragBefore = GLStateManager.ctx().fragmentGeneration;
GLStateManager.popStateTo(d);
assertEquals(fragBefore + 1, GLStateManager.ctx().fragmentGeneration, "CUTOUT pop must bump fragment generation once when the value changed inside the bracket");
verifyIsEnabled(GL11.GL_ALPHA_TEST, true, "Alpha test enable - restored by CUTOUT pop");
assertEquals(GL11.GL_GREATER, GLStateManager.getAlphaState().getFunction(), "cache alpha func");
assertEquals(0.5f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref");
assertTrue(GLStateManager.getTextures().getTextureUnitStates(1).isEnabled(), "unit 1 texturing - restored by CUTOUT pop");
verifyIsEnabled(GL11.GL_BLEND, false, "Blend enable - not a CUTOUT member, must stay toggled");
assertEquals(GL11.GL_DST_COLOR, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - not a CUTOUT member, must stay toggled");
assertEquals(GL11.GL_DST_COLOR, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb - not a CUTOUT member, must stay toggled");
} finally {
GLStateManager.disableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.disableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void cullFaceModeRestoredThroughCullBracket() {
try {
GLStateManager.glCullFace(GL11.GL_BACK);
final int d = GLStateManager.pushState(StateSet.CULL);
GLStateManager.glCullFace(GL11.GL_FRONT);
GLStateManager.popStateTo(d);
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode");
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode");
} finally {
GLStateManager.glCullFace(GL11.GL_BACK);
}
}
@Test
void cullFaceModeRestoredThroughBatchBracketRegression2117() {
try {
GLStateManager.glCullFace(GL11.GL_BACK);
final int d = GLStateManager.pushState(StateSet.BATCH);
GLStateManager.glCullFace(GL11.GL_FRONT);
GLStateManager.popStateTo(d);
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode");
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode");
} finally {
GLStateManager.glCullFace(GL11.GL_BACK);
}
}
@Test
void batchRestoresUnit0And1BindingsAndActiveUnitButNotUnit2() {
if (GLStateManager.MAX_TEXTURE_UNITS < 3) return;
final int tex0Base = GLStateManager.glGenTextures();
final int tex1Base = GLStateManager.glGenTextures();
final int tex2Base = GLStateManager.glGenTextures();
final int tex0Dirty = GLStateManager.glGenTextures();
final int tex1Dirty = GLStateManager.glGenTextures();
final int tex2Dirty = GLStateManager.glGenTextures();
try {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex0Base);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex1Base);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + 2);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex2Base);
final int d = GLStateManager.pushState(StateSet.BATCH);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex0Dirty);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex1Dirty);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + 2);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex2Dirty);
GLStateManager.popStateTo(d);
assertEquals(2, GLStateManager.getActiveTextureUnit(), "cache active unit - restored by BATCH pop");
assertEquals(GL13.GL_TEXTURE0 + 2, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "driver active unit - restored by BATCH pop");
assertEquals(tex0Base, GLStateManager.getTextures().getTextureUnitBindings(0).getBinding(), "cache unit 0 binding - restored by BATCH pop");
assertEquals(tex0Base, driverTextureBinding(0), "driver unit 0 binding - restored by BATCH pop");
assertEquals(tex1Base, GLStateManager.getTextures().getTextureUnitBindings(1).getBinding(), "cache unit 1 binding - restored by BATCH pop");
assertEquals(tex1Base, driverTextureBinding(1), "driver unit 1 binding - restored by BATCH pop");
assertEquals(tex2Dirty, GLStateManager.getTextures().getTextureUnitBindings(2).getBinding(), "cache unit 2 binding - not a BATCH member, must stay toggled");
assertEquals(tex2Dirty, driverTextureBinding(2), "driver unit 2 binding - not a BATCH member, must stay toggled");
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + 2);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDeleteTextures(tex0Base);
GLStateManager.glDeleteTextures(tex1Base);
GLStateManager.glDeleteTextures(tex2Base);
GLStateManager.glDeleteTextures(tex0Dirty);
GLStateManager.glDeleteTextures(tex1Dirty);
GLStateManager.glDeleteTextures(tex2Dirty);
}
}
@Test
void popStateToRepairsUnbalancedInnerPush() {
try {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.pushState(StateSet.CULL);
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.popStateTo(d);
assertEquals(d, GLStateManager.getAttribDepth());
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb(), "popStateTo repairs the unbalanced non-member push and carries the change through to the BLEND member pop, restoring the pre-bracket baseline");
} finally {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void popStateToToleratesUnbalancedInnerPop() {
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.popState();
assertEquals(d, GLStateManager.getAttribDepth());
GLStateManager.popStateTo(d);
assertEquals(d, GLStateManager.getAttribDepth());
}
@Test
void exceptionMidBracketLeavesDepthAndStateCorrectViaFinally() {
try {
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
final int depthBefore = GLStateManager.getAttribDepth();
final RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
final int d = GLStateManager.pushState(StateSet.BLEND);
try {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
throw new RuntimeException("boom");
} finally {
GLStateManager.popStateTo(d);
}
});
assertEquals("boom", thrown.getMessage());
assertEquals(depthBefore, GLStateManager.getAttribDepth());
verifyIsEnabled(GL11.GL_BLEND, true, "Blend enable - restored despite exception");
assertEquals(GL11.GL_SRC_ALPHA, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - restored despite exception");
} finally {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void allAttribBitsNestedInsideInternalBracketRestoresBothLevels() {
try {
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
GLStateManager.glCullFace(GL11.GL_FRONT);
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull enable - restored by inner GL_ALL_ATTRIB_BITS pop");
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - restored by inner GL_ALL_ATTRIB_BITS pop");
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - restored by inner GL_ALL_ATTRIB_BITS pop");
GLStateManager.popStateTo(d);
verifyIsEnabled(GL11.GL_BLEND, false, "Blend enable - restored by outer BLEND pop");
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - restored by outer BLEND pop");
} finally {
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void internalBracketNestedInsideAllAttribBitsRestoresBothLevels() {
try {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
try {
GLStateManager.glCullFace(GL11.GL_FRONT);
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.popStateTo(d);
verifyIsEnabled(GL11.GL_BLEND, false, "Blend enable - restored by inner BLEND pop");
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - restored by inner BLEND pop");
assertEquals(GL11.GL_FRONT, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - change survives, not a BLEND member");
assertEquals(GL11.GL_FRONT, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - change survives, not a BLEND member");
} finally {
GLStateManager.glPopAttrib();
}
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - restored by outer GL_ALL_ATTRIB_BITS pop");
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - restored by outer GL_ALL_ATTRIB_BITS pop");
} finally {
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void rProgramFontRestoresRealProgramToDriverAndCache() {
final int p0 = linkTrivialProgram();
final int p1 = linkTrivialProgram();
try {
GLStateManager.glUseProgram(p0);
final int d = GLStateManager.pushState(StateSet.FONT);
GLStateManager.glUseProgram(p1);
GLStateManager.popStateTo(d);
assertEquals(p0, GLStateManager.getActiveProgram(), "cache active program restored");
assertEquals(p0, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM), "driver current program restored");
assertFalse(ShaderManager.getInstance().isActive(), "FFP must stay inactive when a real program was bound before the bracket");
} finally {
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(p0);
GLStateManager.glDeleteProgram(p1);
}
}
@Test
void rProgramFontReactivatesFfpAfterBracket() {
final int p1 = linkTrivialProgram();
try {
GLStateManager.glUseProgram(0);
assertTrue(ShaderManager.getInstance().isActive(), "FFP must be active before the bracket");
final int d = GLStateManager.pushState(StateSet.FONT);
GLStateManager.glUseProgram(p1);
assertFalse(ShaderManager.getInstance().isActive(), "FFP must deactivate once a real program binds");
GLStateManager.popStateTo(d);
assertEquals(0, GLStateManager.getActiveProgram(), "cache active program restored to the FFP sentinel");
assertTrue(ShaderManager.getInstance().isActive(), "FFP must be active again after the pop");
} finally {
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(p1);
}
}
@Test
void blendPopDoesNotBumpFragmentOrLightingGeneration() {
try {
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
final int fragBefore = GLStateManager.ctx().fragmentGeneration;
final int lightBefore = GLStateManager.ctx().lightingGeneration;
GLStateManager.popStateTo(d);
assertEquals(fragBefore, GLStateManager.ctx().fragmentGeneration, "BLEND pop must not bump fragment generation");
assertEquals(lightBefore, GLStateManager.ctx().lightingGeneration, "BLEND pop must not bump lighting generation");
} finally {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void alphaPopBumpsFragmentGenerationWhenChanged() {
try {
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
final int d = GLStateManager.pushState(StateSet.ALPHA);
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.1f);
final int fragBefore = GLStateManager.ctx().fragmentGeneration;
GLStateManager.popStateTo(d);
assertEquals(fragBefore + 1, GLStateManager.ctx().fragmentGeneration, "ALPHA pop must bump fragment generation once when the value changed inside the bracket");
} finally {
GLStateManager.disableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
}
}
@Test
void textureBitBracketRestoresUnit1BindingAndActiveUnit() {
final int texBase = GLStateManager.glGenTextures();
final int texDirty = GLStateManager.glGenTextures();
try {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texBase);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_TEXTURE_BIT));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texDirty);
GLStateManager.popStateTo(d);
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored by GL_TEXTURE_BIT pop");
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "driver active unit restored by GL_TEXTURE_BIT pop");
assertEquals(texBase, GLStateManager.getTextures().getTextureUnitBindings(1).getBinding(), "cache unit 1 binding restored by GL_TEXTURE_BIT pop");
assertEquals(texBase, driverTextureBinding(1), "driver unit 1 binding restored by GL_TEXTURE_BIT pop");
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDeleteTextures(texBase);
GLStateManager.glDeleteTextures(texDirty);
}
}
@Test
void textureBitBracketRestoresTexEnvModeAndColor() {
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
final FloatBuffer zero = BufferUtils.createFloatBuffer(4);
zero.put(0.0f).put(0.0f).put(0.0f).put(0.0f).flip();
GLStateManager.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, zero);
try {
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_TEXTURE_BIT));
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_DECAL);
final FloatBuffer dirty = BufferUtils.createFloatBuffer(4);
dirty.put(0.1f).put(0.2f).put(0.3f).put(0.4f).flip();
GLStateManager.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, dirty);
GLStateManager.popStateTo(d);
assertEquals(GL11.GL_MODULATE, GLStateManager.getTextures().getTexEnvState(0).mode, "cache tex env mode restored by GL_TEXTURE_BIT pop");
assertEquals(0.0f, GLStateManager.getTextures().getTexEnvState(0).envColorR, 0.0001f, "cache tex env color restored by GL_TEXTURE_BIT pop");
} finally {
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
}
}
@Test
void lightingBitBracketRestoresBakedFrontMaterialAfterColorMaterialToggle() {
try {
GLStateManager.glColor4f(0.9f, 0.9f, 0.9f, 0.9f);
GLStateManager.enableColorMaterial();
GLStateManager.disableColorMaterial();
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_LIGHTING_BIT));
GLStateManager.glColor4f(0.1f, 0.2f, 0.3f, 0.4f);
GLStateManager.enableColorMaterial();
GLStateManager.popStateTo(d);
assertEquals(0.9f, GLStateManager.getFrontMaterial().ambient.x, 0.0001f, "cache front material ambient restored by GL_LIGHTING_BIT pop");
assertEquals(0.9f, GLStateManager.getFrontMaterial().diffuse.x, 0.0001f, "cache front material diffuse restored by GL_LIGHTING_BIT pop");
} finally {
GLStateManager.disableColorMaterial();
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
}
}
}
@@ -17,7 +17,7 @@ public class GLSM_VAO_UnitTest {
@Test
void testDefaultStates() {
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.getDefaultVAO(), "GL_VERTEX_ARRAY_BINDING Initial State");
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.ctx().defaultVAO, "GL_VERTEX_ARRAY_BINDING Initial State");
verifyState(GL13.GL_CLIENT_ACTIVE_TEXTURE, GL13.GL_TEXTURE0, "GL_CLIENT_ACTIVE_TEXTURE Initial State");
testClientStates();
}
@@ -84,7 +84,7 @@ public class GLSM_VAO_UnitTest {
// Check if everything got reset to default
glBindVertexArray(0);
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.getDefaultVAO(), "GL_VERTEX_ARRAY_BINDING - Unbound");
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.ctx().defaultVAO, "GL_VERTEX_ARRAY_BINDING - Unbound");
verifyState(GL15.GL_ARRAY_BUFFER_BINDING, 0, "GL_ARRAY_BUFFER_BINDING - Unbound");
verifyState(GL11.GL_VERTEX_ARRAY, defaultVertexEnabled, "GL_VERTEX_ARRAY - Reset");
@@ -192,9 +192,10 @@ public class GLSM_VAO_UnitTest {
}
private static void glBindVertexArray(int array) {
if (array == 0) array = GLStateManager.getDefaultVAO();
GLStateManager.ctx().boundVAO = array;
VAOManager.onBindVertexArrayPre(array);
final GLContextState glCtx = GLStateManager.ctx();
if (array == 0) array = glCtx.defaultVAO;
glCtx.boundVAO = array;
glCtx.vaos.onBindVertexArrayPre(array);
UniversalVAO.bindVertexArray(array);
}
}
@@ -0,0 +1,152 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
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 java.nio.FloatBuffer;
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
public class GLSM_ValueStackCow_GLTest {
private static int fixtureTexA = -1;
private static int fixtureTexB = -1;
private static FloatBuffer buf4(float a, float b, float c, float d) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(4);
buf.put(a).put(b).put(c).put(d).flip();
return buf;
}
private static void ensureFixtureTextures() {
if (fixtureTexA == -1) {
fixtureTexA = GLStateManager.glGenTextures();
fixtureTexB = GLStateManager.glGenTextures();
}
}
@AfterAll
static void deleteFixtureTextures() {
if (fixtureTexA > 0) GLStateManager.glDeleteTextures(fixtureTexA);
if (fixtureTexB > 0) GLStateManager.glDeleteTextures(fixtureTexB);
fixtureTexA = fixtureTexB = -1;
}
private static final String[] NAMES = {
"glAlphaFunc", "glBlendFunc", "tryBlendFuncSeparate", "glBlendEquation", "glBlendEquationSeparate",
"glBlendColor", "glColorMask", "glClearColor", "glDrawBuffer", "glLogicOp",
"glDepthFunc", "glDepthMask", "glClearDepth", "glStencilFunc", "glStencilOp",
"glStencilMask", "glClearStencil", "glViewport", "glScissor", "glDepthRange", "glLineWidth",
"glLineStipple", "glPointSize", "glPolygonMode", "glCullFace", "glFrontFace",
"glPolygonOffset", "fogColor", "glFogf", "glFogi", "glLightf",
"glLighti", "glLight", "glLightModelf", "glLightModeli", "glLightModel",
"glMaterialf", "glMateriali", "glMaterial", "glShadeModel", "glColorMaterial",
"glMatrixMode", "glActiveTexture", "glBindTexture", "glTexEnvi", "glTexEnv",
"glSecondaryColor3ub", "enableBlend/disableBlend", "enableAlphaTest/disableAlphaTest",
"glEnable(GL_CULL_FACE)", "dsaBindTextureToUnit"
};
private static final Runnable[] MUTATIONS = {
() -> GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.1f),
() -> GLStateManager.glBlendFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO),
() -> GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO),
() -> GLStateManager.glBlendEquation(GL14.GL_FUNC_SUBTRACT),
() -> GLStateManager.glBlendEquationSeparate(GL14.GL_FUNC_SUBTRACT, GL14.GL_FUNC_REVERSE_SUBTRACT),
() -> GLStateManager.glBlendColor(0.5f, 0.6f, 0.7f, 0.8f),
() -> GLStateManager.glColorMask(false, true, false, true),
() -> GLStateManager.glClearColor(1.0f, 0.5f, 0.25f, 0.75f),
() -> GLStateManager.glDrawBuffer(GL11.GL_NONE),
() -> GLStateManager.glLogicOp(GL11.GL_XOR),
() -> GLStateManager.glDepthFunc(GL11.GL_ALWAYS),
() -> GLStateManager.glDepthMask(false),
() -> GLStateManager.glClearDepth(0.25),
() -> GLStateManager.glStencilFunc(GL11.GL_NOTEQUAL, 3, 0x0F),
() -> GLStateManager.glStencilOp(GL11.GL_REPLACE, GL11.GL_INCR, GL11.GL_DECR),
() -> GLStateManager.glStencilMask(0x0F),
() -> GLStateManager.glClearStencil(9),
() -> GLStateManager.glViewport(1, 2, 3, 4),
() -> GLStateManager.glScissor(1, 2, 3, 4),
() -> GLStateManager.glDepthRange(0.2, 0.8),
() -> GLStateManager.glLineWidth(3.0f),
() -> GLStateManager.glLineStipple(3, (short) 0x00FF),
() -> GLStateManager.glPointSize(4.0f),
() -> GLStateManager.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE),
() -> GLStateManager.glCullFace(GL11.GL_FRONT),
() -> GLStateManager.glFrontFace(GL11.GL_CW),
() -> GLStateManager.glPolygonOffset(3.0f, 4.0f),
() -> GLStateManager.fogColor(0.5f, 0.6f, 0.7f, 0.8f),
() -> GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.5f),
() -> GLStateManager.glFogi(GL11.GL_FOG_MODE, GL11.GL_LINEAR),
() -> GLStateManager.glLightf(GL11.GL_LIGHT0, GL11.GL_SPOT_EXPONENT, 12.0f),
() -> GLStateManager.glLighti(GL11.GL_LIGHT0, GL11.GL_SPOT_CUTOFF, 45),
() -> GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, buf4(1.0f, 2.0f, 3.0f, 1.0f)),
() -> GLStateManager.glLightModelf(GL11.GL_LIGHT_MODEL_LOCAL_VIEWER, 1.0f),
() -> GLStateManager.glLightModeli(GL12.GL_LIGHT_MODEL_COLOR_CONTROL, GL12.GL_SEPARATE_SPECULAR_COLOR),
() -> GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, buf4(0.5f, 0.6f, 0.7f, 0.8f)),
() -> GLStateManager.glMaterialf(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 32.0f),
() -> GLStateManager.glMateriali(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 32),
() -> GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_DIFFUSE, buf4(0.1f, 0.2f, 0.3f, 0.4f)),
() -> GLStateManager.glShadeModel(GL11.GL_FLAT),
() -> GLStateManager.glColorMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR),
() -> GLStateManager.glMatrixMode(GL11.GL_PROJECTION),
() -> GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + 2),
() -> {
ensureFixtureTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, fixtureTexA);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
},
() -> GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_DECAL),
() -> GLStateManager.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, buf4(0.1f, 0.2f, 0.3f, 0.4f)),
() -> GLStateManager.glSecondaryColor3ub((byte) 255, (byte) 128, (byte) 64),
() -> {
GLStateManager.disableBlend();
GLStateManager.enableBlend();
},
() -> {
GLStateManager.disableAlphaTest();
GLStateManager.enableAlphaTest();
},
() -> {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
GLStateManager.glEnable(GL11.GL_CULL_FACE);
},
() -> {
assumeTrue(GLStateManager.capabilities.GL_ARB_direct_state_access, "test requires GL_ARB_direct_state_access");
ensureFixtureTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, fixtureTexA);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
new DSAARB().bindTextureToUnit(1, fixtureTexB);
}
};
@Test
void everyMutationSiteReachesTheSaveHook() {
final int entryDepth = GLStateManager.getAttribDepth();
try {
for (int i = 0; i < MUTATIONS.length; i++) {
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_ALL_ATTRIB_BITS));
try {
final long before = GLStateManager.attribSlotsSaved;
MUTATIONS[i].run();
assertTrue(GLStateManager.attribSlotsSaved - before >= 1, NAMES[i] + ": mutation site did not reach the save hook");
} finally {
GLStateManager.popStateTo(d);
}
}
} finally {
GLStateManager.popStateTo(entryDepth);
}
}
}
@@ -0,0 +1,100 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@GLCoreTest
public class GLSM_WorkerContextStacks_GLTest {
@Test
void enableBitBracketRestoresOnWorkerContext() {
GLStateManager.enterWorkerContext();
try {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_ENABLE_BIT));
GLStateManager.glEnable(GL11.GL_CULL_FACE);
GLStateManager.popStateTo(d);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE), "cull face restored on the worker context");
} finally {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
GLStateManager.exitWorkerContext();
}
}
@Test
void colorBufferBitBracketRestoresOnWorkerContext() {
GLStateManager.enterWorkerContext();
try {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_COLOR_BUFFER_BIT));
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.popStateTo(d);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_BLEND), "blend enable restored on the worker context");
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb(), "blend src rgb restored on the worker context");
} finally {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.exitWorkerContext();
}
}
@Test
void workerResetLeavesPrimaryContextUntouched() {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
final int primaryDepthBefore = GLStateManager.getAttribDepth();
GLStateManager.enterWorkerContext();
try {
GLStateManager.pushState(StateSet.forMask(GL11.GL_ENABLE_BIT));
GLStateManager.glEnable(GL11.GL_CULL_FACE);
GLStateManager.reset();
assertEquals(0, GLStateManager.getAttribDepth(), "worker attrib depth drained by its own reset");
} finally {
GLStateManager.exitWorkerContext();
}
assertEquals(primaryDepthBefore, GLStateManager.getAttribDepth(), "primary context depth untouched by the worker reset");
assertFalse(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE), "primary context cull face untouched by the worker reset");
}
@Test
void stackIdsEqualAcrossContexts() {
final GLContextState primary = GLStateManager.ctx();
final GLContextState worker = GLStateManager.enterWorkerContext();
try {
final Object[] primaryStacks = {
primary.blendState, primary.depthState, primary.colorMask, primary.blendMode,
primary.cullState, primary.textures.getTextureUnitBindings(1), primary.polygonState
};
final Object[] workerStacks = {
worker.blendState, worker.depthState, worker.colorMask, worker.blendMode,
worker.cullState, worker.textures.getTextureUnitBindings(1), worker.polygonState
};
final int[] primaryIds = new int[primaryStacks.length];
final int[] workerIds = new int[workerStacks.length];
for (int i = 0; i < primaryStacks.length; i++) {
primaryIds[i] = Reflect.invoke(primaryStacks[i], "stackId", new Class[0]);
workerIds[i] = Reflect.invoke(workerStacks[i], "stackId", new Class[0]);
}
assertArrayEquals(primaryIds, workerIds, "stack ids must match between the primary and worker contexts for identically-constructed stacks");
} finally {
GLStateManager.exitWorkerContext();
}
}
}
@@ -28,7 +28,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlColor4fSetsVertexAttrib() {
GLStateManager.glColor4f(0.5f, 0.25f, 0.75f, 0.8f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.5f, color[0], EPSILON, "color.r");
@@ -40,7 +40,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlNormal3fSetsVertexAttrib() {
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] normal = getVertexAttrib4f(NORMAL_ATTRIB);
assertEquals(0.0f, normal[0], EPSILON, "normal.x");
@@ -51,7 +51,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testSetLightmapTextureCoordsOnUnit1SetsVertexAttrib() {
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, 240.0f, 128.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] lm = getVertexAttrib4f(SECONDARY_UV_ATTRIB);
assertEquals(240.0f, lm[0], EPSILON, "lightmap.s");
@@ -64,7 +64,7 @@ class VertexAttribDefaultBridgeTest {
void testGlTexCoord2fSetsVertexAttrib() {
// glTexCoord2f outside recording → sets default vertex attrib 2 (PRIMARY_UV)
GLStateManager.glTexCoord2f(0.5f, 0.75f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] uv = getVertexAttrib4f(PRIMARY_UV_ATTRIB);
assertEquals(0.5f, uv[0], EPSILON, "texcoord.s");
@@ -77,7 +77,7 @@ class VertexAttribDefaultBridgeTest {
// Second call is a cache hit — changeColor not called.
// Vertex attrib should still hold the correct value from the first call.
GLStateManager.glColor4f(0.3f, 0.6f, 0.9f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.3f, color[0], EPSILON, "color.r after dedup");
@@ -89,7 +89,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testCompileMode_DoesNotSetVertexAttrib() {
GLStateManager.glColor4f(0.1f, 0.2f, 0.3f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
int listId = GLStateManager.glGenLists(1);
GLStateManager.glNewList(listId, GL11.GL_COMPILE);
@@ -245,7 +245,7 @@ class FFPCombineShaderTest {
@Test
void testMultiTexCoordRoutesToShaderManager() {
// Not in immediate mode or display list recording, so should go to ShaderManager.currentTexCoord
ShaderManager.setCurrentTexCoord(0.0f, 0.0f, 0.0f, 1.0f); // reset
GLStateManager.ctx().ffp.setTexCoord(0.0f, 0.0f, 0.0f, 1.0f); // reset
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.5f, 0.75f);
var texCoord = ShaderManager.getCurrentTexCoord();
@@ -255,7 +255,7 @@ class FFPCombineShaderTest {
@Test
void testMultiTexCoord2dDelegatesToFloat() {
ShaderManager.setCurrentTexCoord(0.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.ctx().ffp.setTexCoord(0.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glMultiTexCoord2d(GL13.GL_TEXTURE0, 0.5, 0.75);
var texCoord = ShaderManager.getCurrentTexCoord();
@@ -46,7 +46,7 @@ class FFPUniformValuesTest {
final Uniforms uniforms = new Uniforms();
try {
GL20.glUseProgram(program.getProgramId());
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
final ByteBuffer block = uniforms.getStaging();
@@ -69,7 +69,7 @@ class FFPVertexLightingGLTest {
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
sm.disable();
ShaderManager.disable();
GLStateManager.glBindVertexArray(0);
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
@@ -148,7 +148,7 @@ class FFPVertexLightingGLTest {
private float[] drawAndReadCenter() {
buildTriangle();
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
@@ -50,8 +49,8 @@ class FfpUniformBlockDedupTest {
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
GLStateManager.glTexCoord2f(0.0f, 0.0f);
GLSMConfig.lastBrightnessX = 0.0f;
GLSMConfig.lastBrightnessY = 0.0f;
GLStateManager.ctx().lastBrightnessX = 0.0f;
GLStateManager.ctx().lastBrightnessY = 0.0f;
GLStateManager.glFog(GL11.GL_FOG_COLOR, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 1.0f);
GLStateManager.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, floats(0.0f, 0.0f, 0.0f, 0.0f));
@@ -68,18 +67,18 @@ class FfpUniformBlockDedupTest {
resetState();
final Uniforms uniforms = new Uniforms();
try {
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
assertEquals(1, uniforms.blockWrites, "first upload always writes");
final int firstOffset = boundRangeStart();
GLStateManager.glLoadIdentity();
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
assertEquals(1, uniforms.blockWrites, "a dirty generation with unchanged bytes must not write");
assertEquals(1, uniforms.blockSkips, "and must be counted as a skip");
assertEquals(firstOffset, boundRangeStart(), "the skipped write must not advance the ring or rebind");
GLStateManager.glTranslatef(1.0f, 0.0f, 0.0f);
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
assertEquals(2, uniforms.blockWrites, "a changed matrix must write");
assertEquals(1, uniforms.blockSkips, "a write is not a skip");
assertNotEquals(firstOffset, boundRangeStart(), "a write takes a fresh ring range and rebinds");
@@ -95,11 +94,11 @@ class FfpUniformBlockDedupTest {
final Uniforms first = new Uniforms();
final Uniforms second = new Uniforms();
try {
first.upload();
first.upload();
first.upload(GLStateManager.ctx());
first.upload(GLStateManager.ctx());
assertEquals(1, first.blockWrites);
second.upload();
second.upload(GLStateManager.ctx());
assertEquals(1, second.blockWrites, "a never-bound instance must write regardless of content");
} finally {
first.destroy();
@@ -128,7 +127,7 @@ class FfpUniformBlockDedupTest {
addCategory(tests, "currentColor", () -> GLStateManager.glColor4f(0.25f, 0.5f, 0.75f, 1.0f));
addCategory(tests, "currentNormal", () -> GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f));
addCategory(tests, "currentTexCoord", () -> GLStateManager.glTexCoord2f(0.125f, 0.375f));
addCategory(tests, "lightmapCoord", () -> GLSMConfig.lastBrightnessX = 96.0f);
addCategory(tests, "lightmapCoord", () -> GLStateManager.ctx().lastBrightnessX = 96.0f);
addCategory(tests, "texGenPlane", () -> GLStateManager.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(0.5f, 0.0f, 0.0f, 0.25f)));
addCategory(tests, "clipPlane", () -> GLStateManager.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 1.0, 0.0, 7.0)));
addCategory(tests, "alphaRef", () -> GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.35f));
@@ -146,19 +145,19 @@ class FfpUniformBlockDedupTest {
resetState();
final Uniforms uniforms = new Uniforms();
try {
uniforms.upload();
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
uniforms.upload(GLStateManager.ctx());
final int writesBefore = uniforms.blockWrites;
final int settledOffset = boundRangeStart();
mutate.run();
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
assertEquals(writesBefore + 1, uniforms.blockWrites, name + ": a changed member must write");
assertNotEquals(settledOffset, boundRangeStart(), name + ": a write must rebind to a fresh range");
final int skipsBefore = uniforms.blockSkips;
final int writtenOffset = boundRangeStart();
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
assertEquals(writesBefore + 1, uniforms.blockWrites, name + ": re-uploading unchanged bytes must not write");
assertEquals(writtenOffset, boundRangeStart(), name + ": and must not rebind");
if (uniforms.blockSkips != skipsBefore) {
@@ -147,7 +147,7 @@ class InstancedFFPDrawGLTest {
CubeParityFixture.setupScene(lightmap, lit);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
final CubeParityFixture.Spec[] specs = CubeParityFixture.specs();
@@ -172,7 +172,7 @@ class InstancedFFPDrawGLTest {
ParticleParityFixture.setupScene(true);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
final ParticleParityFixture.Particle[] particles = ParticleParityFixture.particles();
@@ -222,9 +222,9 @@ class InstancedFFPDrawGLTest {
@Test
void instancedSourceUsesInstanceAttribs() {
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
GLStateManager.setFfpInstancing(Instancing.TEMPLATE);
final VertexKey key = VertexKey.fromState(true, true, false, false, 0b10);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
final String source = VertexShaderGenerator.generate(key);
assertTrue(source.contains("a_InstRow0"), "instance matrix attribs declared");
@@ -248,7 +248,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
@@ -278,7 +278,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFF0000FF, 0, 0x0D0000FF, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
@@ -311,7 +311,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
@@ -351,7 +351,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFF0000FF);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
@@ -430,8 +430,8 @@ class InstancedFFPDrawGLTest {
}
private static void drawInstances() {
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
GLStateManager.setFfpInstancing(Instancing.TEMPLATE);
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
}
}
@@ -31,20 +31,20 @@ class ProgramUniformTrackingTest {
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
GL20.glUseProgram(a.getProgramId());
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
final int afterFirst = uniforms.blockWrites;
GL20.glUseProgram(b.getProgramId());
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
GL20.glUseProgram(a.getProgramId());
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
assertEquals(afterFirst, uniforms.blockWrites, "program switches with unchanged state write nothing");
GLStateManager.glTranslatef(4.0F, 0.0F, 0.0F);
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
assertEquals(afterFirst + 1, uniforms.blockWrites, "a gen bump writes exactly one block");
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
assertEquals(afterFirst + 1, uniforms.blockWrites, "clean draw writes nothing");
GLStateManager.glPopMatrix();
@@ -66,18 +66,18 @@ class ProgramUniformTrackingTest {
GLStateManager.glLoadIdentity();
GL20.glUseProgram(a.getProgramId());
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
final int base = uniforms.blockWrites;
GLStateManager.glPushAttrib(GL11.GL_TRANSFORM_BIT);
GLStateManager.glPopAttrib();
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
assertEquals(base, uniforms.blockWrites, "push/pop with no change inside writes nothing");
GLStateManager.glPushAttrib(GL11.GL_TRANSFORM_BIT);
GLStateManager.glTranslatef(7.0F, 0.0F, 0.0F);
GLStateManager.glPopAttrib();
uniforms.upload();
uniforms.upload(GLStateManager.ctx());
assertEquals(base + 1, uniforms.blockWrites, "pop after change stages once");
GLStateManager.glPopMatrix();
@@ -17,6 +17,7 @@ public class VAOManagerEboIndexTest {
private static final int DEFAULT_VAO = 0;
private static final int NO_VAO = -1;
private final VAOManager vaos = new VAOManager();
private final Int2IntOpenHashMap expectedEbo = new Int2IntOpenHashMap();
private int currentVao;
@@ -24,7 +25,7 @@ public class VAOManagerEboIndexTest {
void reset() {
drainManagerState();
expectedEbo.clear();
VAOManager.init(DEFAULT_VAO);
vaos.init(DEFAULT_VAO);
expectedEbo.put(DEFAULT_VAO, 0);
currentVao = DEFAULT_VAO;
}
@@ -34,23 +35,23 @@ public class VAOManagerEboIndexTest {
drainManagerState();
}
private static void drainManagerState() {
for (int vao : VAOManager.vaoMap.keySet().toIntArray()) {
VAOManager.onDeleteVertexArray(vao);
private void drainManagerState() {
for (int vao : vaos.vaoMap.keySet().toIntArray()) {
vaos.onDeleteVertexArray(vao);
}
VAOManager.boundEBO = 0;
assertEquals(0, VAOManager.eboOwners.size(), "draining every VAO left entries in the index");
vaos.boundEBO = 0;
assertEquals(0, vaos.eboOwners.size(), "draining every VAO left entries in the index");
}
private void bindVao(int vao) {
if (currentVao != NO_VAO) expectedEbo.put(currentVao, VAOManager.boundEBO);
VAOManager.onBindVertexArrayPre(vao);
if (currentVao != NO_VAO) expectedEbo.put(currentVao, vaos.boundEBO);
vaos.onBindVertexArrayPre(vao);
expectedEbo.putIfAbsent(vao, 0);
currentVao = vao;
}
private void bindEbo(int ebo) {
VAOManager.onBindEBO(ebo);
vaos.onBindEBO(ebo);
}
private int countOwners(int buffer) {
@@ -62,20 +63,20 @@ public class VAOManagerEboIndexTest {
}
private void deleteBuffer(int buffer) {
VAOManager.onDeleteBuffer(buffer);
vaos.onDeleteBuffer(buffer);
for (int vao : expectedEbo.keySet().toIntArray()) {
if (expectedEbo.get(vao) == buffer) expectedEbo.put(vao, 0);
}
}
private void deleteVao(int vao) {
VAOManager.onDeleteVertexArray(vao);
vaos.onDeleteVertexArray(vao);
expectedEbo.remove(vao);
if (vao == currentVao) currentVao = NO_VAO;
}
private void assertExpectedEbos(int op) {
for (var entry : VAOManager.vaoMap.int2ObjectEntrySet()) {
for (var entry : vaos.vaoMap.int2ObjectEntrySet()) {
final int vao = entry.getIntKey();
if (vao == currentVao) continue;
assertEquals(expectedEbo.get(vao), entry.getValue().ebo, () -> "op " + op + ": vao " + vao + " ebo mismatch");
@@ -140,10 +141,10 @@ public class VAOManagerEboIndexTest {
deleteBuffer(ebo);
assertEquals(0, VAOManager.vaoMap.get(1).ebo, "vao 1 kept a freed shared EBO");
assertEquals(0, VAOManager.vaoMap.get(2).ebo, "vao 2 kept a freed shared EBO");
assertEquals(0, VAOManager.vaoMap.get(3).ebo, "vao 3 kept a freed shared EBO");
assertFalse(VAOManager.eboOwners.containsKey(ebo), "index kept owners for a freed EBO");
assertEquals(0, vaos.vaoMap.get(1).ebo, "vao 1 kept a freed shared EBO");
assertEquals(0, vaos.vaoMap.get(2).ebo, "vao 2 kept a freed shared EBO");
assertEquals(0, vaos.vaoMap.get(3).ebo, "vao 3 kept a freed shared EBO");
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept owners for a freed EBO");
}
@Test
@@ -156,9 +157,9 @@ public class VAOManagerEboIndexTest {
bindVao(DEFAULT_VAO);
deleteVao(1);
assertTrue(VAOManager.eboOwners.containsKey(ebo), "index dropped a still-live owner");
assertTrue(vaos.eboOwners.containsKey(ebo), "index dropped a still-live owner");
deleteVao(2);
assertFalse(VAOManager.eboOwners.containsKey(ebo), "index kept an entry after its last owner went away");
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept an entry after its last owner went away");
}
@Test
@@ -170,8 +171,8 @@ public class VAOManagerEboIndexTest {
deleteVao(1);
bindVao(DEFAULT_VAO);
assertFalse(VAOManager.eboOwners.containsKey(ebo), "deleting the bound VAO left an unreachable owner in the index");
assertEquals(0, VAOManager.eboOwners.size(), "index leaked an entry for a deleted VAO");
assertFalse(vaos.eboOwners.containsKey(ebo), "deleting the bound VAO left an unreachable owner in the index");
assertEquals(0, vaos.eboOwners.size(), "index leaked an entry for a deleted VAO");
}
@Test
@@ -184,6 +185,6 @@ public class VAOManagerEboIndexTest {
for (int vao = 1; vao <= 64; vao++) {
deleteVao(vao);
}
assertEquals(0, VAOManager.eboOwners.size(), "index leaked entries after every VAO was deleted");
assertEquals(0, vaos.eboOwners.size(), "index leaked entries after every VAO was deleted");
}
}
@@ -0,0 +1,106 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCoreTest
class VertexKeyTexMatrixMaskTest {
private static boolean texMatEnabled(int unit) {
return VertexKey.fromState(false, false, false, false, 0).unitTexMatEnabled(unit);
}
@Test
void identityNonIdentityTransitionsPerUnit() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final boolean texGenS = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_GEN_S);
final boolean texGenT = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_GEN_T);
final boolean texGenR = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_GEN_R);
final boolean texGenQ = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_GEN_Q);
if (texGenS) GLStateManager.glDisable(GL11.GL_TEXTURE_GEN_S);
if (texGenT) GLStateManager.glDisable(GL11.GL_TEXTURE_GEN_T);
if (texGenR) GLStateManager.glDisable(GL11.GL_TEXTURE_GEN_R);
if (texGenQ) GLStateManager.glDisable(GL11.GL_TEXTURE_GEN_Q);
try {
for (int unit = 0; unit < VertexKey.MAX_UNITS; unit++) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
assertFalse(texMatEnabled(unit), "unit " + unit + " identity");
GLStateManager.glTranslatef(1.0f, 2.0f, 3.0f);
assertTrue(texMatEnabled(unit), "unit " + unit + " non-identity");
GLStateManager.glLoadIdentity();
assertFalse(texMatEnabled(unit), "unit " + unit + " back to identity");
}
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
if (texGenS) GLStateManager.glEnable(GL11.GL_TEXTURE_GEN_S);
if (texGenT) GLStateManager.glEnable(GL11.GL_TEXTURE_GEN_T);
if (texGenR) GLStateManager.glEnable(GL11.GL_TEXTURE_GEN_R);
if (texGenQ) GLStateManager.glEnable(GL11.GL_TEXTURE_GEN_Q);
}
}
@Test
void pushPopMatrixRestoresIdentity() {
final int unit = 1;
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
assertFalse(texMatEnabled(unit));
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(4.0f, 5.0f, 6.0f);
assertTrue(texMatEnabled(unit));
GLStateManager.glPopMatrix();
assertFalse(texMatEnabled(unit), "pop must restore identity and invalidate the cached mask");
}
@Test
void transformBitPopBumpsGeneration() {
final int unit = 2;
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
assertFalse(texMatEnabled(unit));
GLStateManager.glPushAttrib(GL11.GL_TRANSFORM_BIT);
try {
GLStateManager.glTranslatef(7.0f, 8.0f, 9.0f);
assertTrue(texMatEnabled(unit));
} finally {
GLStateManager.glPopAttrib();
}
assertTrue(texMatEnabled(unit), "GL_TRANSFORM_BIT pop does not restore matrix content; the pop bump must still force a correct recompute");
GLStateManager.glLoadIdentity();
assertFalse(texMatEnabled(unit));
}
@Test
void generationUnchangedSkipsRecompute() {
final int unit = 3;
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
assertFalse(texMatEnabled(unit));
final int genBefore = GLStateManager.getTexMatrixGeneration();
GLStateManager.ctx().textures.getTextureUnitMatrix(unit).translate(10.0f, 11.0f, 12.0f);
assertEquals(genBefore, GLStateManager.getTexMatrixGeneration(), "direct mutation of the matrix object must not bump the generation");
assertFalse(texMatEnabled(unit), "cached mask must stay stale (identity) while the generation is unchanged");
GLStateManager.glLoadIdentity();
assertFalse(texMatEnabled(unit));
}
}
@@ -314,10 +314,11 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
final ByteBuffer colorSlice = clientData.duplicate().order(ByteOrder.nativeOrder());
colorSlice.position(8);
VAOManager.setAttribute(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData);
VAOManager.enableAttribute(0);
VAOManager.setAttribute(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice);
VAOManager.enableAttribute(1);
final VAOManager vaos = GLStateManager.ctx().vaos;
vaos.setAttribute(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData);
vaos.enableAttribute(0);
vaos.setAttribute(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice);
vaos.enableAttribute(1);
GLStateManager.glBindVertexArray(0);
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
@@ -593,8 +594,9 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setAttribute(0, 3, GL11.GL_FLOAT, false, 0, clientVerts);
VAOManager.enableAttribute(0);
final VAOManager vaos = GLStateManager.ctx().vaos;
vaos.setAttribute(0, 3, GL11.GL_FLOAT, false, 0, clientVerts);
vaos.enableAttribute(0);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, indices);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
@@ -276,6 +276,11 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().isEmpty());
}
@Test
void utilCheckGLErrorIsRedirected() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{{"org/lwjgl/opengl/Util", "checkGLError", "()V"}});
}
@Test
void indexedGettersHaveRealTargets() throws Exception {
final Class<?> glsm = Class.forName(GLSM.replace('/', '.'), false, getClass().getClassLoader());
@@ -42,7 +42,6 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
GLStateManager.initialize(GLSMInitConfig.builder()
.displaySize(800, 600)
.directDrawer(t -> {})
.streamingDrawerDestroy(() -> {})
.enableDSA(false)
.build());
GLStateManager.setRunningSplash(false);
@@ -70,7 +70,6 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
final GLSMInitConfig config = GLSMInitConfig.builder()
.displaySize(displayMode.getWidth(), displayMode.getHeight())
.directDrawer(t -> {})
.streamingDrawerDestroy(() -> {})
.enableDSA(false)
.build();
setMainThread(Thread.currentThread());
@@ -144,7 +143,7 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
if (ffp.isActive()) {
ffp.deactivate();
}
ffp.disable();
ShaderManager.disable();
// Force-sync VAO binding. Tests may unbind via raw GL, desynchronizing GLSM's cache.
// Invalidate the cache so glBindVertexArray(0) actually issues the GL call.
Loaded 100 of 305 files, more files were not shown because too many files have changed in this diff. Show more