Compare commits

...
25 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
Nick Smith b7d439e6b1 Added abutting block detection and processing (#2068) 2026-09-16 16:09:40 -07:00
KAMKEEL c36647af34 Hex Text and other Font Mod Compat System (#1972) 2026-09-16 16:01:16 -07:00
Eclipse-Sol f787801a0e Fix borking modded mobs with broken eyes (#2108) 2026-09-16 16:00:09 -07:00
Algent f192793395 Fix microblock transparency in the hotbar (#2107) 2026-09-16 15:48:26 -07:00
Jason Mitchell 3876f7e693 Instance more things (#2104)
* Flyby: pin the start origin and fly just above the route's terrain

* Instancing: particles, dropped items, entity cubes + shadow batching and some SDL-GPU fixes
2026-09-16 14:56:24 -07:00
danyadev 94ee37bfed fix resize handling on non-main thread for lwjgl2 (#2106) 2026-09-16 13:59:15 -07:00
Eclipse-Sol 26c161f0e8 Improve upon Texture Filtering Work (#2096) 2026-09-15 16:00:43 -07:00
429 changed files with 30023 additions and 7068 deletions

No files matched your search

+2
View File
@@ -101,3 +101,5 @@ Targets GL 3.3+ core profile
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
* RGSS Terrain Texture Filtering
* Indiesoft for [defold-sharp-sprite](https://github.com/indiesoftby/defold-sharp-sprite), used as the reference implementation under the MIT license
+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
+7 -1
View File
@@ -103,6 +103,11 @@ repositories {
mavenLocal()
}
val spirvTestRuntime by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
// MC compile stubs
compileOnly(sourceSets["stubs"].output)
@@ -133,6 +138,7 @@ dependencies {
testRuntimeOnly(libs.lwjgl3.spvc) { artifact { classifier = lwjglNatives } }
testRuntimeOnly(libs.lwjgl3)
testRuntimeOnly(libs.lwjgl3) { artifact { classifier = lwjglNatives } }
spirvTestRuntime(libs.lwjgl3.opengl)
// @Lwjgl3Aware annotation
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
@@ -309,7 +315,7 @@ val spirvTest by tasks.registering(Test::class) {
classpath = sourceSets["test"].runtimeClasspath.filter { file ->
// Strip the LWJGL2 jars - they collide with LWJGL3's sealed org.lwjgl package.
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
}
}.plus(spirvTestRuntime)
filter {
includeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
@@ -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");
@@ -54,7 +60,10 @@ public final class SystemProperties {
public static final boolean FLYBY_EXIT_WHEN_DONE = Boolean.getBoolean("angelica.flyby.exitWhenDone");
public static final long FLYBY_TIME_OF_DAY = Math.floorMod(Long.getLong("angelica.flyby.timeOfDay", 6000L), 24000L);
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");
@@ -69,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
@@ -117,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);
}
}
@@ -134,6 +134,12 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return caps != null && caps.GL_EXT_texture_filter_anisotropic;
}
@Override
public void updateDisplayFromWorkerThread(boolean processMessages) {
if (processMessages) pumpDisplayMessages();
Display.update(false);
}
@Override
public void flush() {GL11.glFlush();}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL20;
@@ -241,6 +242,8 @@ public abstract class RenderBackend {
}
}
public void invalidateStateMirror() {}
public void onFrameEnd() {}
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
@@ -251,6 +254,13 @@ public abstract class RenderBackend {
public boolean handleSwapBuffers() { return false; }
public void onRenderThreadReleased(Thread thread) {}
public void updateDisplayFromWorkerThread(boolean processMessages) throws LWJGLException {
GLStateManager.swapBuffers();
if (processMessages) {
pumpDisplayMessages();
}
}
public String getTransferDebugInfo() { return null; }
public boolean bindVoxelizationRegion(int ssboBinding, long openPass, float x, float y, float z) { return false; }
@@ -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);
}
}
@@ -0,0 +1,27 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
public final class CubeInstancedAttribs {
public static final int LOC_LIGHTMAP_SCALE = InstancedAttribs.LOC_LIGHTMAP;
public static final int LOC_CUBE_TEX = 15;
public static final int OFFSET_LIGHTMAP_SCALE = InstancedAttribs.HEAD_SIZE;
public static final int OFFSET_CUBE_TEX = OFFSET_LIGHTMAP_SCALE + 16;
public static final int STRIDE = OFFSET_CUBE_TEX + 16;
public static void pointInstanceAttribs(long base) {
InstancedAttribs.pointHead(base, STRIDE);
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP_SCALE, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP_SCALE);
GLStateManager.glVertexAttribPointer(LOC_CUBE_TEX, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_CUBE_TEX);
}
static void enableInstanceArrays() {
InstancedAttribs.enableHeadArrays();
InstancedAttribs.enableInstanced(LOC_CUBE_TEX);
}
private CubeInstancedAttribs() {}
}
@@ -0,0 +1,54 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
public record CubeParams(float minX, float minY, float minZ, float spanX, float spanY, float spanZ, float u0, float v0, float uScaleX, float vScaleY, float uScaleZ, float vScaleZ) {
private static final float MIN_SPAN = 1.0e-4f;
public CubeParams {
if (!(spanX > MIN_SPAN && spanY > MIN_SPAN && spanZ > MIN_SPAN)) throw new IllegalArgumentException("degenerate cube span");
}
public static CubeParams of(float texWidth, float texHeight, boolean mirror, int texU, int texV, float x, float y, float z, int sizeX, int sizeY, int sizeZ, float inflate) {
if (texWidth <= 0.0f || texHeight <= 0.0f) return null;
if (sizeX < 0 || sizeY < 0 || sizeZ < 0) return null;
if (mirror && sizeX == 0) return null;
final float minX = x - inflate;
final float minY = y - inflate;
final float minZ = z - inflate;
final float spanX = ((x + sizeX) + inflate) - minX;
final float spanY = ((y + sizeY) + inflate) - minY;
final float spanZ = ((z + sizeZ) + inflate) - minZ;
if (spanX <= MIN_SPAN || spanY <= MIN_SPAN || spanZ <= MIN_SPAN) return null;
return new CubeParams(minX, minY, minZ, spanX, spanY, spanZ, texU / texWidth, texV / texHeight, (mirror ? -sizeX : sizeX) / texWidth, sizeY / texHeight, sizeZ / texWidth, sizeZ / texHeight);
}
public void writeRows(long ptr, float[] mv, int off, float scale) {
final float tx = minX * scale;
final float ty = minY * scale;
final float tz = minZ * scale;
final float sx = spanX * scale;
final float sy = spanY * scale;
final float sz = spanZ * scale;
for (int r = 0; r < 3; r++) {
final float c0 = mv[off + r];
final float c1 = mv[off + 4 + r];
final float c2 = mv[off + 8 + r];
final long row = ptr + InstancedAttribs.OFFSET_ROWS + r * 16L;
memPutFloat(row, c0 * sx);
memPutFloat(row + 4, c1 * sy);
memPutFloat(row + 8, c2 * sz);
memPutFloat(row + 12, mv[off + 12 + r] + c0 * tx + c1 * ty + c2 * tz);
}
}
public void writeTexture(long ptr) {
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 8, uScaleZ);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 12, vScaleZ);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX, u0);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 4, v0);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 8, uScaleX);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 12, vScaleY);
}
}
@@ -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;
@@ -12,7 +14,7 @@ import org.lwjgl.opengl.GL13;
* Packed-long fragment shader permutation key for FFP emulation.
*
* Layout:
* long[0]: global bits (13) + unit 0 (47 bits at offset 13)
* long[0]: global bits (14) + unit 0 (47 bits at offset 14)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
@@ -63,7 +65,9 @@ public final class FragmentKey {
public static final int FOG_EXP = 2;
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 13;
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
@@ -72,6 +76,7 @@ public final class FragmentKey {
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
private static final int BIT_LINE_STIPPLE = 11; // 1 bit
private static final int BIT_COLOR_SUM = 12; // 1 bit
private static final int BIT_OVERLAY_INSTANCED = 13;
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
@@ -103,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;
@@ -117,37 +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.getOverlayA() != 0.0f) {
if (glCtx.ffpInstancing.hasInstanceHead()) {
global |= (1L << BIT_OVERLAY_INSTANCED);
} 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 {
@@ -163,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
@@ -238,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; }
@@ -245,6 +261,7 @@ public final class FragmentKey {
public boolean lineStipple() { return ((packed[0] >> BIT_LINE_STIPPLE) & 1) != 0; }
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
public boolean overlayInstanced() { return ((packed[0] >> BIT_OVERLAY_INSTANCED) & 1) != 0; }
private long unitBits(int i) {
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
@@ -378,10 +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(), 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");
@@ -35,6 +45,9 @@ public final class FragmentShaderGenerator {
private static void emitInputs(StringBuilder sb, FragmentKey key) {
sb.append("// Inputs from vertex shader\n");
sb.append("in vec4 v_Color;\n");
if (key.overlayInstanced()) {
sb.append("in vec4 v_Overlay;\n");
}
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor;\n");
}
@@ -116,8 +129,11 @@ public final class FragmentShaderGenerator {
* Emit the modern-style damage overlay mix.
*/
private static void emitOverlay(StringBuilder sb, FragmentKey key) {
if (!key.overlayEnabled()) return;
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
if (key.overlayInstanced()) {
sb.append(" color.rgb = mix(color.rgb, v_Overlay.rgb, v_Overlay.a);\n");
} else if (key.overlayEnabled()) {
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
}
}
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
@@ -1,18 +1,84 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutLong;
public final class InstancedAttribs {
public static final int LOC_MATRIX_COL0 = 5;
public static final int LOC_MATRIX_COL1 = 6;
public static final int LOC_MATRIX_COL2 = 7;
public static final int LOC_MATRIX_COL3 = 8;
public static final int LOC_COLOR = 9;
public static final int LOC_ROW0 = 5;
public static final int LOC_ROW1 = 6;
public static final int LOC_ROW2 = 7;
public static final int LOC_COLOR = 8;
public static final int LOC_OVERLAY = 9;
public static final int LOC_LIGHTMAP = 10;
public static final int LOC_ENTITY = 11;
public static final int OFFSET_MATRIX = 0;
public static final int OFFSET_COLOR = 64;
public static final int OFFSET_LIGHTMAP = 68;
public static final int STRIDE = 80;
public static final int OFFSET_ROWS = 0;
public static final int OFFSET_COLOR = 48;
public static final int OFFSET_OVERLAY = 52;
public static final int OFFSET_ENTITY = 56;
public static final int HEAD_SIZE = 64;
public static final int OFFSET_LIGHTMAP = HEAD_SIZE;
public static final int STRIDE = 72;
public static void writeHead(long ptr, float[] colMajor, int off, int colorABGR, int overlayABGR, long entityInfo) {
writeRows(ptr, colMajor, off);
writeTail(ptr, colorABGR, overlayABGR, entityInfo);
}
private static void writeRows(long ptr, float[] colMajor, int off) {
for (int r = 0; r < 3; r++) {
final long row = ptr + OFFSET_ROWS + r * 16L;
memPutFloat(row, colMajor[off + r]);
memPutFloat(row + 4, colMajor[off + 4 + r]);
memPutFloat(row + 8, colMajor[off + 8 + r]);
memPutFloat(row + 12, colMajor[off + 12 + r]);
}
}
public static void writeTail(long ptr, int colorABGR, int overlayABGR, long entityInfo) {
memPutInt(ptr + OFFSET_COLOR, colorABGR);
memPutInt(ptr + OFFSET_OVERLAY, overlayABGR);
memPutLong(ptr + OFFSET_ENTITY, entityInfo);
}
public static void writeLightmap(long ptr, int packedLight) {
memPutFloat(ptr, packedLight & 0xFFFF);
memPutFloat(ptr + 4, (packedLight >>> 16) & 0xFFFF);
}
public static long packEntityInfo(int entity, int blockEntity, int item) {
return (entity & 0xFFFFL) | ((blockEntity & 0xFFFFL) << 16) | ((item & 0xFFFFL) << 32);
}
public static void pointHead(long base, int stride) {
for (int r = 0; r < 3; r++) {
GLStateManager.glVertexAttribPointer(LOC_ROW0 + r, 4, GL11.GL_FLOAT, false, stride, base + OFFSET_ROWS + r * 16L);
}
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_COLOR);
GLStateManager.glVertexAttribPointer(LOC_OVERLAY, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_OVERLAY);
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_SHORT, stride, base + OFFSET_ENTITY);
}
public static void pointTemplate(long base) {
pointHead(base, STRIDE);
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP, 2, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP);
}
public static void enableHeadArrays() {
for (int loc = LOC_ROW0; loc <= LOC_ENTITY; loc++) {
enableInstanced(loc);
}
}
static void enableInstanced(int loc) {
GLStateManager.glEnableVertexAttribArray(loc);
GLStateManager.glVertexAttribDivisor(loc, 1);
}
private InstancedAttribs() {}
}
@@ -0,0 +1,145 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import java.util.ArrayList;
public final class InstancedGlslHelpers {
private InstancedGlslHelpers() {}
public static String mat4FromRows(String row0, String row1, String row2) {
return "mat4("
+ "vec4(" + row0 + ".x, " + row1 + ".x, " + row2 + ".x, 0.0), "
+ "vec4(" + row0 + ".y, " + row1 + ".y, " + row2 + ".y, 0.0), "
+ "vec4(" + row0 + ".z, " + row1 + ".z, " + row2 + ".z, 0.0), "
+ "vec4(" + row0 + ".w, " + row1 + ".w, " + row2 + ".w, 1.0))";
}
public static String mat3FromRows(String row0, String row1, String row2) {
return "mat3("
+ "normalize(vec3(" + row0 + ".x, " + row1 + ".x, " + row2 + ".x)), "
+ "normalize(vec3(" + row0 + ".y, " + row1 + ".y, " + row2 + ".y)), "
+ "normalize(vec3(" + row0 + ".z, " + row1 + ".z, " + row2 + ".z)))";
}
public static String particlePos(String centerHalf, String offset) {
return "vec4(" + centerHalf + ".xyz + " + offset + " * " + centerHalf + ".w, 1.0)";
}
public static String particleUv(String uv, String corner) {
return "vec4("
+ "mix(" + uv + ".x, " + uv + ".z, " + corner + ".x), "
+ "mix(" + uv + ".y, " + uv + ".w, " + corner + ".y), "
+ "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
+ "vec2 " + prefix + "cubeU = " + prefix + "cubeMidU + (1.0 - 2.0 * " + prefix + "cubeMirror) * " + delta + ".xy;" + sep
+ "vec2 " + prefix + "cubeV = " + mid + ".zw + " + delta + ".zw;";
}
public static String cubeUv(String tex, String lightScale, String uA, String uZ, String vA, String vZ) {
return "vec4("
+ tex + ".x + " + uA + " * abs(" + tex + ".z) + " + uZ + " * " + lightScale + ".z, "
+ tex + ".y + " + vA + " * " + tex + ".w + " + vZ + " * " + lightScale + ".w, "
+ "0.0, 1.0)";
}
public static String[] attributeDecls(String prefix, Instancing kind) {
final ArrayList<String> decls = new ArrayList<>(9);
switch (kind) {
case NONE -> {}
case TEMPLATE -> addHead(decls, prefix, "vec2", "InstLightmap");
case CUBE -> {
decls.add(decl(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), "vec4", prefix + "CubeMid"));
decls.add(decl(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation(), "vec4", prefix + "CubeDelta"));
addHead(decls, prefix, "vec4", "InstLightmapScale");
decls.add(decl(CubeInstancedAttribs.LOC_CUBE_TEX, "vec4", prefix + "CubeTex"));
}
case PARTICLE -> {
decls.add(decl(ParticleInstancedAttribs.LOC_CENTER_HALF, "vec4", prefix + "InstCenterHalf"));
decls.add(decl(ParticleInstancedAttribs.LOC_UV, "vec4", prefix + "InstUv"));
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);
}
private static void addHead(ArrayList<String> decls, String prefix, String lightmapType, String lightmapName) {
decls.add(decl(InstancedAttribs.LOC_ROW0, "vec4", prefix + "InstRow0"));
decls.add(decl(InstancedAttribs.LOC_ROW1, "vec4", prefix + "InstRow1"));
decls.add(decl(InstancedAttribs.LOC_ROW2, "vec4", prefix + "InstRow2"));
decls.add(decl(InstancedAttribs.LOC_COLOR, "vec4", prefix + "InstColor"));
decls.add(decl(InstancedAttribs.LOC_OVERLAY, "vec4", prefix + "InstOverlay"));
decls.add(decl(InstancedAttribs.LOC_LIGHTMAP, lightmapType, prefix + lightmapName));
}
private static String decl(int loc, String type, String name) {
return "layout(location = " + loc + ") in " + type + " " + name + ";";
}
}
@@ -0,0 +1,21 @@
package com.gtnewhorizons.angelica.glsm.ffp;
public enum Instancing {
NONE(""),
TEMPLATE("_instanced"),
CUBE("_cubeinstanced"),
PARTICLE("_particleinstanced"),
WEATHER("_weatherinstanced");
public static final Instancing[] VALUES = values();
public final String variantSuffix;
Instancing(String variantSuffix) {
this.variantSuffix = variantSuffix;
}
public boolean hasInstanceHead() {
return this == TEMPLATE || this == CUBE;
}
}
@@ -0,0 +1,50 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
public final class ParticleInstancedAttribs {
public static final int LOC_CENTER_HALF = 5;
public static final int LOC_UV = 6;
public static final int LOC_COLOR = InstancedAttribs.LOC_COLOR;
public static final int LOC_LIGHTMAP = InstancedAttribs.LOC_LIGHTMAP;
public static final int OFFSET_CENTER_HALF = 0;
public static final int OFFSET_UV = 16;
public static final int OFFSET_COLOR = 32;
public static final int OFFSET_LIGHTMAP = 36;
public static final int STRIDE = 48;
public static void pointInstanceAttribs(long base) {
GLStateManager.glVertexAttribPointer(LOC_CENTER_HALF, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_CENTER_HALF);
GLStateManager.glVertexAttribPointer(LOC_UV, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_UV);
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, base + OFFSET_COLOR);
GLStateManager.glVertexAttribPointer(LOC_LIGHTMAP, 2, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LIGHTMAP);
}
static void enableInstanceArrays() {
InstancedAttribs.enableInstanced(LOC_CENTER_HALF);
InstancedAttribs.enableInstanced(LOC_UV);
InstancedAttribs.enableInstanced(LOC_COLOR);
InstancedAttribs.enableInstanced(LOC_LIGHTMAP);
}
public static void writeInstance(long ptr, float cx, float cy, float cz, float half, float u0, float v0, float u1, float v1, int colorABGR, int packedLight) {
memPutFloat(ptr + OFFSET_CENTER_HALF, cx);
memPutFloat(ptr + OFFSET_CENTER_HALF + 4, cy);
memPutFloat(ptr + OFFSET_CENTER_HALF + 8, cz);
memPutFloat(ptr + OFFSET_CENTER_HALF + 12, half);
memPutFloat(ptr + OFFSET_UV, u0);
memPutFloat(ptr + OFFSET_UV + 4, v0);
memPutFloat(ptr + OFFSET_UV + 8, u1);
memPutFloat(ptr + OFFSET_UV + 12, v1);
memPutInt(ptr + OFFSET_COLOR, colorABGR);
InstancedAttribs.writeLightmap(ptr + OFFSET_LIGHTMAP, packedLight);
}
private ParticleInstancedAttribs() {}
}
@@ -0,0 +1,160 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
public final class ParticleQuadMesh {
public static final int VERTEX_FLAGS = VertexFlags.TEXTURE_BIT;
public static final int VERTEX_COUNT = 4;
private static final int VERTEX_STRIDE = 24;
private static final int OFFSET_OFFSET = 0;
private static final int OFFSET_CORNER = 12;
private static final int OFFSET_TANGENT = 20;
private static final float[] CORNER_A = { -1.0f, -1.0f, 1.0f, 1.0f };
private static final float[] CORNER_B = { -1.0f, 1.0f, 1.0f, -1.0f };
private static int vao;
private static int vbo;
private static ByteBuffer staging;
private static boolean written;
private static float lastRotX, lastRotXZ, lastRotZ, lastRotYZ, lastRotXY;
private ParticleQuadMesh() {}
public static float cornerA(int corner) {
return CORNER_A[corner];
}
public static float cornerB(int corner) {
return CORNER_B[corner];
}
public static float offsetX(float a, float b, float rotX, float rotYZ) {
return a * rotX + b * rotYZ;
}
public static float offsetY(float b, float rotXZ) {
return b * rotXZ;
}
public static float offsetZ(float a, float b, float rotZ, float rotXY) {
return a * rotZ + b * rotXY;
}
public static int vao() {
if (vao == 0) build();
return vao;
}
public static void update(float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
if (vao == 0) build();
if (written && rotX == lastRotX && rotXZ == lastRotXZ && rotZ == lastRotZ && rotYZ == lastRotYZ && rotXY == lastRotXY) {
return;
}
lastRotX = rotX;
lastRotXZ = rotXZ;
lastRotZ = rotZ;
lastRotYZ = rotYZ;
lastRotXY = rotXY;
written = true;
write(memAddress0(staging), rotX, rotXZ, rotZ, rotYZ, rotXY);
final int savedVBO = GLStateManager.getBoundVBO();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
staging.position(0).limit(VERTEX_COUNT * VERTEX_STRIDE);
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0L, staging);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
private static void write(long dst, float rotX, float rotXZ, float rotZ, float rotYZ, float rotXY) {
final int tangent = packTangent(rotX, 0.0f, rotZ);
long ptr = dst;
for (int i = 0; i < VERTEX_COUNT; i++) {
final float a = cornerA(i);
final float b = cornerB(i);
memPutFloat(ptr + OFFSET_OFFSET, offsetX(a, b, rotX, rotYZ));
memPutFloat(ptr + OFFSET_OFFSET + 4, offsetY(b, rotXZ));
memPutFloat(ptr + OFFSET_OFFSET + 8, offsetZ(a, b, rotZ, rotXY));
memPutFloat(ptr + OFFSET_CORNER, (a + 1.0f) * 0.5f);
memPutFloat(ptr + OFFSET_CORNER + 4, (b + 1.0f) * 0.5f);
memPutInt(ptr + OFFSET_TANGENT, tangent);
ptr += VERTEX_STRIDE;
}
}
private static int packTangent(float x, float y, float z) {
final float lenSq = x * x + y * y + z * z;
final float nx, ny, nz;
if (lenSq < 1.0e-12f) {
nx = 1.0f;
ny = 0.0f;
nz = 0.0f;
} else {
final float inv = (float) (1.0 / Math.sqrt(lenSq));
nx = x * inv;
ny = y * inv;
nz = z * inv;
}
return NormI8.pack(nx, ny, nz) | (127 << 24);
}
private static void build() {
final int savedVAO = GLStateManager.getBoundVAO();
final int savedVBO = GLStateManager.getBoundVBO();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, (long) VERTEX_COUNT * VERTEX_STRIDE, GL15.GL_DYNAMIC_DRAW);
attrib(VertexFormatElement.Usage.POSITION.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_OFFSET);
attrib(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), 2, GL11.GL_FLOAT, false, OFFSET_CORNER);
attrib(ImmediateExtendedAttribHandler.LOC_TANGENT, 4, GL11.GL_BYTE, true, OFFSET_TANGENT);
ParticleInstancedAttribs.enableInstanceArrays();
GLStateManager.glBindVertexArray(savedVAO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
if (staging == null) staging = memAlloc(VERTEX_COUNT * VERTEX_STRIDE);
written = false;
}
private static void attrib(int loc, int size, int type, boolean normalized, int offset) {
GLStateManager.glEnableVertexAttribArray(loc);
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, VERTEX_STRIDE, offset);
}
public static void delete() {
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
if (staging != null) {
memFree(staging);
staging = null;
}
written = false;
}
}
@@ -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");
}
@@ -97,6 +97,8 @@ public final class ShaderManager {
final long vkPacked = VertexKey.packFromState(true, true, true, true, fragMask);
final VertexKey vk = VertexKey.fromPacked(vkPacked);
VertexShaderGenerator.generate(vk);
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.CUBE)));
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.PARTICLE)));
FragmentShaderGenerator.generate(FragmentKey.fromPacked(fkScratch, fkLen));
GeometryShaderGenerator.generate(vk);
final Class<?>[] touched = { Program.class, ShaderCache.class, TessellatorStreamingDrawer.class, QuadConverter.class };
@@ -108,7 +110,7 @@ public final class ShaderManager {
}
}
public void disable() {
public static void disable() {
enabled = false;
}
@@ -117,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;
@@ -126,37 +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) {
@@ -172,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;
}
@@ -0,0 +1,161 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
public final class UnitCubeMesh {
public static final int VERTEX_FLAGS = VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT | VertexFlags.NORMAL_BIT;
public static final int FACE_COUNT = 6;
public static final int VERTEX_COUNT = 24;
public static final int VERTEX_STRIDE = 64;
public static final int OFFSET_POSITION = 0;
private static final int OFFSET_COLOR = 12;
public static final int OFFSET_MID = 16;
public static final int OFFSET_DELTA = 32;
public static final int OFFSET_NORMAL = 48;
public static final int OFFSET_TANGENT = 60;
public static final float[][] CORNERS = {
{ 0, 0, 0 }, { 1, 0, 0 }, { 1, 1, 0 }, { 0, 1, 0 },
{ 0, 0, 1 }, { 1, 0, 1 }, { 1, 1, 1 }, { 0, 1, 1 },
};
public record Span(int a, int z) {
public int texels(int sizeA, int sizeZ) {
return a * sizeA + z * sizeZ;
}
}
public record Net(Span uLow, Span uHigh, Span vLow, Span vHigh) {
public Span u(int vertex) {
return vertex == 0 || vertex == 3 ? uHigh : uLow;
}
public Span v(int vertex) {
return vertex >= 2 ? vHigh : vLow;
}
}
public record Face(int[] corners, float[] normal, float[] tangent, Net net) {}
public static final Face[] FACES = {
new Face(new int[] { 5, 1, 2, 6 }, new float[] { 1, 0, 0 }, new float[] { 0, 0, 1, 1 }, new Net(new Span(1, 1), new Span(1, 2), new Span(0, 1), new Span(1, 1))),
new Face(new int[] { 0, 4, 7, 3 }, new float[] { -1, 0, 0 }, new float[] { 0, 0, -1, 1 }, new Net(new Span(0, 0), new Span(0, 1), new Span(0, 1), new Span(1, 1))),
new Face(new int[] { 5, 4, 0, 1 }, new float[] { 0, -1, 0 }, new float[] { 1, 0, 0, 1 }, new Net(new Span(0, 1), new Span(1, 1), new Span(0, 0), new Span(0, 1))),
new Face(new int[] { 2, 3, 7, 6 }, new float[] { 0, 1, 0 }, new float[] { 1, 0, 0, -1 }, new Net(new Span(1, 1), new Span(2, 1), new Span(0, 1), new Span(0, 0))),
new Face(new int[] { 1, 0, 3, 2 }, new float[] { 0, 0, -1 }, new float[] { 1, 0, 0, 1 }, new Net(new Span(0, 1), new Span(1, 1), new Span(0, 1), new Span(1, 1))),
new Face(new int[] { 4, 5, 6, 7 }, new float[] { 0, 0, 1 }, new float[] { -1, 0, 0, 1 }, new Net(new Span(1, 2), new Span(2, 2), new Span(0, 1), new Span(1, 1))),
};
private static int vao;
private static int vbo;
private UnitCubeMesh() {}
public static void write(long dst) {
long ptr = dst;
for (int f = 0; f < FACE_COUNT; f++) {
final Face face = FACES[f];
final Net net = face.net();
final float midUA = (net.uLow().a() + net.uHigh().a()) * 0.5f;
final float midUZ = (net.uLow().z() + net.uHigh().z()) * 0.5f;
final float midVA = (net.vLow().a() + net.vHigh().a()) * 0.5f;
final float midVZ = (net.vLow().z() + net.vHigh().z()) * 0.5f;
for (int c = 0; c < 4; c++) {
final float[] corner = CORNERS[face.corners()[c]];
final Span u = net.u(c);
final Span v = net.v(c);
memPutFloat(ptr + OFFSET_POSITION, corner[0]);
memPutFloat(ptr + OFFSET_POSITION + 4, corner[1]);
memPutFloat(ptr + OFFSET_POSITION + 8, corner[2]);
memPutInt(ptr + OFFSET_COLOR, 0xFFFFFFFF);
memPutFloat(ptr + OFFSET_MID, midUA);
memPutFloat(ptr + OFFSET_MID + 4, midUZ);
memPutFloat(ptr + OFFSET_MID + 8, midVA);
memPutFloat(ptr + OFFSET_MID + 12, midVZ);
memPutFloat(ptr + OFFSET_DELTA, u.a() - midUA);
memPutFloat(ptr + OFFSET_DELTA + 4, u.z() - midUZ);
memPutFloat(ptr + OFFSET_DELTA + 8, v.a() - midVA);
memPutFloat(ptr + OFFSET_DELTA + 12, v.z() - midVZ);
memPutFloat(ptr + OFFSET_NORMAL, face.normal()[0]);
memPutFloat(ptr + OFFSET_NORMAL + 4, face.normal()[1]);
memPutFloat(ptr + OFFSET_NORMAL + 8, face.normal()[2]);
final float[] tangent = face.tangent();
memPutInt(ptr + OFFSET_TANGENT, packTangent(tangent[0], tangent[1], tangent[2], tangent[3]));
ptr += VERTEX_STRIDE;
}
}
}
private static int packTangent(float x, float y, float z, float w) {
return NormI8.pack(x, y, z) | (((int) (w * 127.0f) & 0xFF) << 24);
}
public static int vao() {
if (vao == 0) build();
return vao;
}
private static void build() {
final int savedVAO = GLStateManager.getBoundVAO();
final int savedVBO = GLStateManager.getBoundVBO();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
final int bytes = VERTEX_COUNT * VERTEX_STRIDE;
final ByteBuffer data = memAlloc(bytes);
try {
write(memAddress0(data));
data.position(0).limit(bytes);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
} finally {
memFree(data);
}
attrib(VertexFormatElement.Usage.POSITION.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_POSITION);
attrib(VertexFormatElement.Usage.COLOR.getAttributeLocation(), 4, GL11.GL_UNSIGNED_BYTE, true, OFFSET_COLOR);
attrib(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation(), 4, GL11.GL_FLOAT, false, OFFSET_MID);
attrib(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation(), 4, GL11.GL_FLOAT, false, OFFSET_DELTA);
attrib(VertexFormatElement.Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_FLOAT, false, OFFSET_NORMAL);
attrib(ImmediateExtendedAttribHandler.LOC_TANGENT, 4, GL11.GL_BYTE, true, OFFSET_TANGENT);
CubeInstancedAttribs.enableInstanceArrays();
GLStateManager.glBindVertexArray(savedVAO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
private static void attrib(int loc, int size, int type, boolean normalized, int offset) {
GLStateManager.glEnableVertexAttribArray(loc);
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, VERTEX_STRIDE, offset);
}
public static void delete() {
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
}
}
@@ -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;
@@ -49,7 +50,14 @@ public final class VertexKey {
private static final int BIT_UNIT_TEXMAT_BASE = BIT_WIDE_LINE + 1;
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_INSTANCED = BIT_LINE_STIPPLE + 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 boolean instancedDraw() { return bit(BIT_INSTANCED); }
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,31 @@ 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);
}
if (GLStateManager.instancedFfpDrawActive) {
bits |= (1L << BIT_INSTANCED);
}
bits |= (long) glCtx.ffpInstancing.ordinal() << BIT_INSTANCING;
return bits;
}
static long withInstancing(long packed, Instancing kind) {
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) {
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask));
}
@@ -255,12 +277,6 @@ public final class VertexKey {
@Override
public String toString() {
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%b]",
packed, lightingEnabled(), lightEnabled(0), lightEnabled(1),
colorMaterialEnabled(), fogEnabled(),
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0,
unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancedDraw());
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancing());
}
}
@@ -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);
@@ -55,31 +59,44 @@ public final class VertexShaderGenerator {
}
private static void emitAttributes(StringBuilder sb, VertexKey key) {
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 (key.hasVertexColor()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
}
if (key.hasVertexTexCoord()) {
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()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.COLOR.getAttributeLocation()).append(") in vec4 a_Color;\n");
}
if (kind != Instancing.CUBE) {
if (key.hasVertexTexCoord()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
}
if (key.hasVertexLightmap()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
}
}
if (key.hasVertexNormal() || kind == Instancing.CUBE) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
}
}
if (key.hasVertexLightmap()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.SECONDARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord1;\n");
}
if (key.hasVertexNormal()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
}
if (key.instancedDraw()) {
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL0).append(") in vec4 a_InstCol0;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL1).append(") in vec4 a_InstCol1;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL2).append(") in vec4 a_InstCol2;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL3).append(") in vec4 a_InstCol3;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_COLOR).append(") in vec4 a_InstColor;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_LIGHTMAP).append(") in vec2 a_InstLightmap;\n");
for (String d : InstancedGlslHelpers.attributeDecls("a_", kind)) {
sb.append(d).append('\n');
}
sb.append('\n');
}
private static final String PARTICLE_UV = InstancedGlslHelpers.particleUv("a_InstUv", "a_TexCoord0");
private static String unit0Source(VertexKey key) {
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";
};
}
private static void emitLineStart(StringBuilder sb) {
sb.append(" vec2 ndc = gl_Position.xy / gl_Position.w;\n");
sb.append(" v_LineStart = u_Viewport.xy + (ndc * 0.5 + 0.5) * u_Viewport.zw;\n\n");
@@ -93,6 +110,10 @@ public final class VertexShaderGenerator {
private static void emitOutputs(StringBuilder sb, VertexKey key) {
sb.append("// Outputs\n");
sb.append("out vec4 v_Color;\n");
if (key.instancing().hasInstanceHead()) {
sb.append("out vec4 v_Overlay;\n");
sb.append("invariant gl_Position;\n");
}
if (key.lineStipple()) {
sb.append("flat out vec2 v_LineStart;\n");
}
@@ -116,11 +137,18 @@ public final class VertexShaderGenerator {
private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Position transform\n");
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
if (key.instancedDraw()) {
sb.append(" mat4 instMV = mat4(a_InstCol0, a_InstCol1, a_InstCol2, a_InstCol3);\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");
}
if (key.instancing().hasInstanceHead()) {
sb.append(" mat4 instMV = ").append(InstancedGlslHelpers.mat4FromRows("a_InstRow0", "a_InstRow1", "a_InstRow2")).append(";\n");
sb.append(" vec4 eyePos = instMV * pos4;\n");
sb.append(" gl_Position = u_ProjectionMatrix * eyePos;\n");
sb.append(" v_Overlay = a_InstOverlay;\n");
} else {
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
@@ -134,8 +162,8 @@ public final class VertexShaderGenerator {
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Normal transform\n");
final String srcNormal = key.hasVertexNormal() ? "a_Normal" : "u_CurrentNormal";
if (key.instancedDraw()) {
final String srcNormal = key.hasVertexNormal() && key.instancing() != Instancing.PARTICLE ? "a_Normal" : "u_CurrentNormal";
if (key.instancing().hasInstanceHead()) {
sb.append(" vec3 normal = normalize(mat3(instMV) * ").append(srcNormal).append(");\n");
sb.append('\n');
return;
@@ -156,9 +184,13 @@ public final class VertexShaderGenerator {
if (key.colorMaterialEnabled()) {
// Color material: vertex color replaces specific material properties per glColorMaterial mode (Mesa ffvertex_prog.c get_material() bitmask approach)
sb.append(" vec4 matColor = ");
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
if (key.instancedDraw()) {
sb.append(" * a_InstColor");
if (key.instancing() == Instancing.PARTICLE) {
sb.append("a_InstColor");
} else {
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
if (key.instancing().hasInstanceHead()) {
sb.append(" * a_InstColor");
}
}
sb.append(";\n");
@@ -255,22 +287,28 @@ public final class VertexShaderGenerator {
private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
sb.append(" // Color passthrough (no lighting)\n");
final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
if (key.instancedDraw()) {
sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
} else {
sb.append(" v_Color = ").append(src).append(";\n");
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");
}
sb.append('\n');
}
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
final boolean cube = key.instancing() == Instancing.CUBE;
final boolean unit0FromCube = cube && key.unitTexCoordEnabled(0) && !key.texGenEnabled();
final boolean unit23FromCube = cube && key.unit23UvFromUnit0() && (key.unitTexCoordEnabled(2) || key.unitTexCoordEnabled(3));
if (unit0FromCube || unit23FromCube) {
emitCubeTexCoord(sb);
}
if (key.texGenEnabled()) {
emitTexGenCoordGeneration(sb, key);
} else if (key.unitTexCoordEnabled(0)) {
sb.append(" // Texture coordinates - unit 0\n");
final String src = key.hasVertexTexCoord()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord0";
final String src = unit0Source(key);
if (key.unitTexMatEnabled(0)) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * ").append(src).append(";\n");
} else {
@@ -279,20 +317,18 @@ public final class VertexShaderGenerator {
}
if (key.lightmapEnabled()) {
if (key.instancedDraw()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_InstLightmap, 0.0, 1.0);\n");
} else if (key.hasVertexLightmap()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
} else {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
}
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");
}
for (int i = 2; i < VertexKey.MAX_UNITS; i++) {
if (!key.unitTexCoordEnabled(i)) continue;
final String src = key.unit23UvFromUnit0()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord" + i;
final String src = key.unit23UvFromUnit0() ? unit0Source(key) : "u_CurrentTexCoord" + i;
if (!key.unit23UvFromUnit0() && key.unitTexMatEnabled(i)) {
sb.append(" v_TexCoord").append(i).append(" = u_TextureMatrix").append(i).append(" * ").append(src).append(";\n");
} else {
@@ -301,6 +337,12 @@ public final class VertexShaderGenerator {
}
}
private static void emitCubeTexCoord(StringBuilder sb) {
sb.append(" // Cube texture net\n");
sb.append(" ").append(InstancedGlslHelpers.cubePrelude("", "a_CubeMid", "a_CubeDelta", "a_Normal", "a_CubeTex", "\n ")).append('\n');
sb.append(" vec4 cubeUv = ").append(InstancedGlslHelpers.cubeUv("a_CubeTex", "a_InstLightmapScale", "cubeU.x", "cubeU.y", "cubeV.x", "cubeV.y")).append(";\n\n");
}
/**
* Emit per-coordinate texgen code following Mesa's ffvertex_prog.c build order.
* OBJ_LINEAR: dot(pos4, objPlane), EYE_LINEAR: dot(eyePos, eyePlane).
@@ -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;
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.hooks.events.*;
import net.minecraftforge.eventbus.api.bus.EventBus;
@@ -14,6 +15,12 @@ public final class GLSMHooks {
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
public static PerFrameUniformBlock perPassUniformBlock;
public static PendingProgramSelection pendingProgramSelection;
public static void resolvePendingProgram() {
final PendingProgramSelection p = pendingProgramSelection;
if (p != null && !DisplayListManager.isRecording()) p.resolvePendingProgram();
}
// EventBus instances
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
@@ -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;
@@ -0,0 +1,5 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface PendingProgramSelection {
void resolvePendingProgram();
}
@@ -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();
Loaded 100 of 429 files, more files were not shown because too many files have changed in this diff. Show more