mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8c29fa3f9 | ||
|
|
7c28a69bfe | ||
|
|
7f3fb35ebb | ||
|
|
44c930f4ab | ||
|
|
b76284048a | ||
|
|
762c5661eb | ||
|
|
1babfd968c | ||
|
|
888a4de0c9 | ||
|
|
a23cefba92 | ||
|
|
f2c07dea28 |
No files matched your search
@@ -62,6 +62,7 @@ public final class SystemProperties {
|
||||
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
|
||||
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
|
||||
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
|
||||
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
|
||||
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
|
||||
|
||||
// Debug
|
||||
@@ -77,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
|
||||
@@ -125,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -144,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";
|
||||
@@ -153,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;
|
||||
}
|
||||
|
||||
@@ -229,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);
|
||||
}
|
||||
}
|
||||
@@ -761,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) {
|
||||
@@ -778,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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -787,73 +799,100 @@ 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, recordedStatePushes, 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();
|
||||
recordedStatePushes = 0;
|
||||
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) {
|
||||
@@ -966,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;
|
||||
@@ -977,9 +1015,13 @@ public class DisplayListManager {
|
||||
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;
|
||||
}
|
||||
@@ -1007,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();
|
||||
}
|
||||
@@ -1043,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) {
|
||||
|
||||
@@ -302,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()
|
||||
|
||||
@@ -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,8 +3,6 @@ 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;
|
||||
@@ -23,6 +21,7 @@ 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;
|
||||
@@ -34,16 +33,24 @@ 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 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;
|
||||
|
||||
@@ -125,6 +132,7 @@ public class GLContextState {
|
||||
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 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 = member(new FogStateStack(StackIdAllocator.nextId()));
|
||||
@@ -200,6 +208,7 @@ public class GLContextState {
|
||||
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;
|
||||
@@ -210,7 +219,6 @@ public 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];
|
||||
@@ -223,6 +231,31 @@ public class GLContextState {
|
||||
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() {
|
||||
allStacksBuilder.addAll(textures.idStacks());
|
||||
@@ -238,11 +271,11 @@ public class GLContextState {
|
||||
for (int i = 0; i < lightDataStates.length; i++) {
|
||||
lightDataStates[i] = member(new LightStateStack(GL11.GL_LIGHT0 + i, StackIdAllocator.nextId()));
|
||||
}
|
||||
allStacksBuilder.add(ShaderManager.getNormalStack());
|
||||
allStacksBuilder.add(ShaderManager.getTexCoordStack());
|
||||
allStacksBuilder.add(ffp.normalStack);
|
||||
allStacksBuilder.add(ffp.texCoordStack);
|
||||
allStacks = allStacksBuilder.toArray(new IStateStack<?>[0]);
|
||||
|
||||
final int capacity = StackIdAllocator.capacity();
|
||||
final int capacity = StackIdAllocator.allocated();
|
||||
stackById = new CowStateStack<?>[capacity];
|
||||
depthsById = new CowDepths[capacity];
|
||||
restoreBitById = new int[capacity];
|
||||
@@ -253,6 +286,7 @@ public class GLContextState {
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ public final class StateSet {
|
||||
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;
|
||||
@@ -205,6 +206,9 @@ public final class StateSet {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -242,6 +242,8 @@ public abstract class RenderBackend {
|
||||
}
|
||||
}
|
||||
|
||||
public void invalidateStateMirror() {}
|
||||
|
||||
public void onFrameEnd() {}
|
||||
|
||||
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
public final class CombinedGlint {
|
||||
private static final Matrix4f savedMatrix = new Matrix4f();
|
||||
private static final Matrix4f inverse = new Matrix4f();
|
||||
private static final Matrix4f relative = new Matrix4f();
|
||||
private static boolean active;
|
||||
private static boolean replaceAlpha;
|
||||
|
||||
private CombinedGlint() {}
|
||||
|
||||
public static boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public static boolean replacesAlpha() {
|
||||
return active && replaceAlpha;
|
||||
}
|
||||
|
||||
public static boolean begin(Matrix4fc secondTextureMatrix) {
|
||||
if (active || GLStateManager.getActiveProgram() != 0 || GLStateManager.getLightingState().isEnabled() || GLStateManager.getColorSumState().isEnabled() || !GLStateManager.glIsEnabled(GL11.GL_BLEND)) return false;
|
||||
final var blend = GLStateManager.getBlendState();
|
||||
final boolean lastAlpha = blend.getSrcAlpha() == GL11.GL_ONE && blend.getDstAlpha() == GL11.GL_ZERO && blend.getEquationAlpha() == GL14.GL_FUNC_ADD;
|
||||
final boolean preserveAlpha = blend.getSrcAlpha() == GL11.GL_ZERO && blend.getDstAlpha() == GL11.GL_ONE && (blend.getEquationAlpha() == GL14.GL_FUNC_ADD || blend.getEquationAlpha() == GL14.GL_FUNC_REVERSE_SUBTRACT);
|
||||
if (blend.getSrcRgb() != GL11.GL_SRC_COLOR || blend.getDstRgb() != GL11.GL_ONE
|
||||
|| (!lastAlpha && !preserveAlpha && (blend.getSrcAlpha() != GL11.GL_SRC_COLOR || blend.getDstAlpha() != GL11.GL_ONE || blend.getEquationAlpha() != blend.getEquationRgb()))
|
||||
|| (blend.getEquationRgb() != GL14.GL_FUNC_ADD && blend.getEquationRgb() != GL14.GL_FUNC_REVERSE_SUBTRACT)) return false;
|
||||
final var textures = GLStateManager.getTextures();
|
||||
if (!textures.getTextureUnitStates(0).isEnabled() || textures.getTextureUnitBindings(0).getBinding() == 0 || textures.getTexEnvState(0).mode != GL11.GL_MODULATE) return false;
|
||||
if (textures.getTextureUnitStates(1).isEnabled() && textures.getTextureUnitBindings(1).getBinding() != 0 && textures.getTexEnvState(1).mode != GL11.GL_MODULATE) return false;
|
||||
for (int unit = 2; unit < 4; unit++) {
|
||||
if (textures.getTextureUnitStates(unit).isEnabled() && textures.getTextureUnitBindings(unit).getBinding() != 0) return false;
|
||||
}
|
||||
inverse.set(textures.getTextureUnitMatrix(0)).invert();
|
||||
relative.set(secondTextureMatrix).mul(inverse);
|
||||
if (!relative.isFinite()) return false;
|
||||
savedMatrix.set(textures.getTextureUnitMatrix(3));
|
||||
GLStateManager.setTextureMatrix(3, relative);
|
||||
replaceAlpha = lastAlpha;
|
||||
active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void end() {
|
||||
if (!active) return;
|
||||
active = false;
|
||||
GLStateManager.setTextureMatrix(3, savedMatrix);
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,12 @@ public final class FFPUniformBlock {
|
||||
public static final int FOG_PARAMS = vec4("u_FogParams");
|
||||
public static final int FOG_COLOR = vec4("u_FogColor");
|
||||
|
||||
// Rain and snow, for Instancing.WEATHER. 0 = (translate.xyz, 1/radius),
|
||||
// 1 = (cameraFrac.xy, partialTicks, age), 2 = (rainScroll, snowScroll, rainStrength, unused).
|
||||
public static final int WEATHER_PARAMS_0 = vec4("u_WeatherParams0");
|
||||
public static final int WEATHER_PARAMS_1 = vec4("u_WeatherParams1");
|
||||
public static final int WEATHER_PARAMS_2 = vec4("u_WeatherParams2");
|
||||
|
||||
public static final int SIZE;
|
||||
public static final String GLSL_DECL;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
@@ -59,7 +60,6 @@ public final class FfpExtendedAttribs {
|
||||
|
||||
private static final HashMap<Key, ExtBuffer> cache = new HashMap<>();
|
||||
private static final Key lookup = new Key();
|
||||
private static int internalGlDepth = 0;
|
||||
|
||||
private static final HashMap<EboKey, Boolean> eboPatternCache = new HashMap<>();
|
||||
private static final EboKey eboLookup = new EboKey();
|
||||
@@ -79,15 +79,19 @@ public final class FfpExtendedAttribs {
|
||||
return cache.isEmpty() && eboPatternCache.isEmpty();
|
||||
}
|
||||
|
||||
private static int internalDrawDepth = 0;
|
||||
public static void beginInternalDraw() { GLStateManager.ctx().internalDrawDepth++; }
|
||||
|
||||
public static void beginInternalDraw() { internalDrawDepth++; }
|
||||
public static void endInternalDraw() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (glCtx.internalDrawDepth > 0) glCtx.internalDrawDepth--;
|
||||
}
|
||||
|
||||
public static void endInternalDraw() { if (internalDrawDepth > 0) internalDrawDepth--; }
|
||||
private static void beginInternalGl() { GLStateManager.ctx().internalGlDepth++; }
|
||||
|
||||
private static void beginInternalGl() { internalGlDepth++; }
|
||||
|
||||
private static void endInternalGl() { if (internalGlDepth > 0) internalGlDepth--; }
|
||||
private static void endInternalGl() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (glCtx.internalGlDepth > 0) glCtx.internalGlDepth--;
|
||||
}
|
||||
|
||||
private static boolean isNotConventionalTexturedArray(VAOManager.Attrib a) {
|
||||
return a == null || !a.enabled || a.genericPointer || a.vboId == 0 || a.size < 2;
|
||||
@@ -104,20 +108,20 @@ public final class FfpExtendedAttribs {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean maybeBind(int mode, int first, int count) {
|
||||
public static boolean maybeBind(GLContextState glCtx, int mode, int first, int count) {
|
||||
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
|
||||
if (h == null) return false;
|
||||
if (internalDrawDepth > 0) return false;
|
||||
if (glCtx.internalDrawDepth > 0) return false;
|
||||
final int extPrim = ImmediateExtendedAttribHandler.extPrimVerts(mode, count);
|
||||
if (extPrim == 0 || first < 0) return false;
|
||||
if (!h.wantsExtended()) return false;
|
||||
|
||||
final VAOManager.Attrib pos = VAOManager.get(POSITION_LOC);
|
||||
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
|
||||
final VAOManager.Attrib pos = glCtx.vaos.attrib(POSITION_LOC);
|
||||
final VAOManager.Attrib uv = glCtx.vaos.attrib(UV_LOC);
|
||||
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
|
||||
if (isStreaming(pos.vboId) || isStreaming(uv.vboId)) return false;
|
||||
|
||||
VAOManager.Attrib normal = extPrim == 3 ? VAOManager.get(NORMAL_LOC) : null;
|
||||
VAOManager.Attrib normal = extPrim == 3 ? glCtx.vaos.attrib(NORMAL_LOC) : null;
|
||||
if (normal != null && (!normal.enabled || normal.genericPointer || normal.vboId == 0 || normal.size < 3
|
||||
|| isStreaming(normal.vboId))) {
|
||||
normal = null;
|
||||
@@ -126,17 +130,17 @@ public final class FfpExtendedAttribs {
|
||||
return bindExt(h, mode, first, count, extPrim, pos, uv, normal);
|
||||
}
|
||||
|
||||
public static boolean maybeBindIndexed(int mode, int indexCount, int indexType, long indicesOffset) {
|
||||
public static boolean maybeBindIndexed(GLContextState glCtx, int mode, int indexCount, int indexType, long indicesOffset) {
|
||||
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
|
||||
if (h == null) return false;
|
||||
if (internalDrawDepth > 0) return false;
|
||||
if (glCtx.internalDrawDepth > 0) return false;
|
||||
if (mode != GL11.GL_TRIANGLES || indexCount <= 0 || indexCount % 6 != 0) return false;
|
||||
if (!h.wantsExtended()) return false;
|
||||
|
||||
final VAOManager.Attrib pos = VAOManager.get(POSITION_LOC);
|
||||
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
|
||||
final VAOManager.Attrib pos = glCtx.vaos.attrib(POSITION_LOC);
|
||||
final VAOManager.Attrib uv = glCtx.vaos.attrib(UV_LOC);
|
||||
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
|
||||
if (isStreaming(pos.vboId) || isStreaming(uv.vboId) || isStreaming(VAOManager.boundEBO)) return false;
|
||||
if (isStreaming(pos.vboId) || isStreaming(uv.vboId) || isStreaming(glCtx.vaos.boundEBO)) return false;
|
||||
|
||||
final int indexSize = indexTypeBytes(indexType);
|
||||
if (indexSize == 0) return false;
|
||||
@@ -144,7 +148,7 @@ public final class FfpExtendedAttribs {
|
||||
final int first = (int) (indicesOffset / indexSize) / 6 * 4;
|
||||
final int count = indexCount / 6 * 4;
|
||||
|
||||
if (!isQuadPatternEbo(VAOManager.boundEBO, indicesOffset, indexCount, indexType, indexSize, first)) return false;
|
||||
if (!isQuadPatternEbo(glCtx.vaos.boundEBO, indicesOffset, indexCount, indexType, indexSize, first)) return false;
|
||||
|
||||
// Build/bind as GL_QUADS (the real primitive): face-normal tangents, 4-vertex tiles, no per-vertex normal.
|
||||
return bindExt(h, GL11.GL_QUADS, first, count, 4, pos, uv, null);
|
||||
@@ -226,8 +230,8 @@ public final class FfpExtendedAttribs {
|
||||
GLStateManager.glVertexAttrib4f(ImmediateExtendedAttribHandler.LOC_TANGENT, 1.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public static void onDeleteBuffer(int vboId) {
|
||||
if (internalGlDepth > 0 || vboId == 0) return;
|
||||
public static void onDeleteBuffer(GLContextState glCtx, int vboId) {
|
||||
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
|
||||
dropDerivedFrom(vboId);
|
||||
respecCounts.remove(vboId);
|
||||
respecFrames.remove(vboId);
|
||||
@@ -236,8 +240,8 @@ public final class FfpExtendedAttribs {
|
||||
persistentSources.remove(vboId);
|
||||
}
|
||||
|
||||
public static void onBufferRespecified(int vboId) {
|
||||
if (internalGlDepth > 0 || vboId == 0) return;
|
||||
public static void onBufferRespecified(GLContextState glCtx, int vboId) {
|
||||
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
|
||||
if (streamingSources.contains(vboId)) {
|
||||
respecFrames.put(vboId, frame);
|
||||
return;
|
||||
@@ -268,16 +272,15 @@ public final class FfpExtendedAttribs {
|
||||
respecFrames.put(vboId, frame);
|
||||
}
|
||||
|
||||
public static void markStreaming(int vboId) {
|
||||
if (internalGlDepth > 0 || vboId == 0) return;
|
||||
private static void markStreaming(int vboId) {
|
||||
respecCounts.remove(vboId);
|
||||
respecRunStart.remove(vboId);
|
||||
respecFrames.put(vboId, frame);
|
||||
if (streamingSources.add(vboId)) dropDerivedFrom(vboId);
|
||||
}
|
||||
|
||||
public static void markPersistent(int vboId) {
|
||||
if (internalGlDepth > 0 || vboId == 0) return;
|
||||
public static void markPersistent(GLContextState glCtx, int vboId) {
|
||||
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
|
||||
markStreaming(vboId);
|
||||
persistentSources.add(vboId);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ public final class FragmentKey {
|
||||
public static final int FOG_EXP2 = 3;
|
||||
|
||||
private static final int GLOBAL_BITS = 14;
|
||||
private static final int BIT_COMBINED_GLINT = 61;
|
||||
private static final int BIT_GLINT_REPLACE_ALPHA = 62;
|
||||
private static final int BIT_FOG_MODE = 0; // 2 bits
|
||||
private static final int BIT_ALPHA_TEST = 2; // 1 bit
|
||||
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
|
||||
@@ -111,6 +113,8 @@ public final class FragmentKey {
|
||||
|
||||
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 (glCtx.fogMode.isEnabled()) {
|
||||
@@ -130,13 +134,13 @@ public final class FragmentKey {
|
||||
}
|
||||
|
||||
// Damage overlay
|
||||
if (GLStateManager.ffpInstancing.hasInstanceHead()) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -248,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; }
|
||||
@@ -389,7 +395,9 @@ public final class FragmentKey {
|
||||
default -> "?";
|
||||
};
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(), alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(), overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
|
||||
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(),
|
||||
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(),
|
||||
overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
|
||||
for (int i = 0; i < nrEnabledUnits(); i++) {
|
||||
if (!unitEnabled(i)) {
|
||||
sb.append(String.format(" u%d=OFF", i));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core fragment shaders for FFP emulation.
|
||||
@@ -17,6 +18,15 @@ public final class FragmentShaderGenerator {
|
||||
|
||||
emitInputs(sb, key);
|
||||
emitUniforms(sb, key);
|
||||
if (key.combinedGlint()) {
|
||||
sb.append("#define GLINT_REPLACE_ALPHA ").append(key.glintReplaceAlpha() ? 1 : 0).append('\n');
|
||||
sb.append("#define GLINT_FOG_MODE ").append(key.fogMode()).append('\n');
|
||||
sb.append("#define GLINT_ALPHA_FUNC ").append(key.alphaTestEnabled() ? key.alphaTestFunc() : 7).append('\n');
|
||||
sb.append("#define GLINT_OVERLAY ").append(key.overlayInstanced() ? 2 : key.overlayEnabled() ? 1 : 0).append('\n');
|
||||
sb.append("#define GLINT_LIGHTMAP ").append(key.nrEnabledUnits() > 1 && key.unitEnabled(1) ? 1 : 0).append('\n');
|
||||
sb.append(ShaderLoader.getShaderSource("angelica:combined_glint.frag.glsl"));
|
||||
return sb.toString();
|
||||
}
|
||||
sb.append("out vec4 fragColor;\n\n");
|
||||
|
||||
sb.append("void main() {\n");
|
||||
|
||||
@@ -34,6 +34,62 @@ public final class InstancedGlslHelpers {
|
||||
+ "0.0, 1.0)";
|
||||
}
|
||||
|
||||
// wHalfWidth is vanilla's rainXCoords table.
|
||||
private static final String WEATHER_PROLOGUE = """
|
||||
vec2 {p}wColumn = {span}.xy;
|
||||
float {p}wBottom = {span}.z;
|
||||
float {p}wTop = {span}.w;
|
||||
vec2 {p}wLightmap = {params}.xy;
|
||||
float {p}wHash = {params}.z;
|
||||
bool {p}wSnow = {params}.w > 0.5;
|
||||
vec3 {p}wTranslate = {p}WeatherParams0.xyz;
|
||||
float {p}wInvRadius = {p}WeatherParams0.w;
|
||||
vec2 {p}wCameraFrac = {p}WeatherParams1.xy;
|
||||
float {p}wPartialTicks = {p}WeatherParams1.z;
|
||||
float {p}wAge = {p}WeatherParams1.w;
|
||||
float {p}wRainScroll = {p}WeatherParams2.x;
|
||||
float {p}wSnowScroll = {p}WeatherParams2.y;
|
||||
float {p}wStrength = {p}WeatherParams2.z;
|
||||
|
||||
vec2 {p}wHalfWidth = vec2(-{p}wColumn.y, {p}wColumn.x) * (0.5 / sqrt(dot({p}wColumn, {p}wColumn)));
|
||||
vec2 {p}wCornerXZ = {p}wColumn + vec2(0.5) + ({corner}.x * 2.0 - 1.0) * {p}wHalfWidth;
|
||||
float {p}wCornerY = mix({p}wBottom, {p}wTop, {corner}.y);
|
||||
{vertex} = vec4(vec3({p}wCornerXZ.x, {p}wCornerY, {p}wCornerXZ.y) + {p}wTranslate, 1.0);
|
||||
|
||||
float {p}wDistance = length({p}wColumn + vec2(0.5) - {p}wCameraFrac) * {p}wInvRadius;
|
||||
float {p}wFade = 1.0 - {p}wDistance * {p}wDistance;
|
||||
float {p}wScroll;
|
||||
vec2 {p}wDrift;
|
||||
float {p}wAlpha;
|
||||
if ({p}wSnow) {
|
||||
{p}wScroll = ({p}wSnowScroll + {p}wPartialTicks) / 512.0;
|
||||
{p}wDrift = vec2({jitter}.x + {p}wAge * 0.01 * {jitter}.y, {jitter}.z + {p}wAge * {jitter}.w * 0.001);
|
||||
{p}wAlpha = {p}wFade * 0.3 + 0.5;
|
||||
} else {
|
||||
{p}wScroll = (mod({p}wRainScroll + {p}wHash, 32.0) + {p}wPartialTicks) / 32.0 * (3.0 + {jitter}.x);
|
||||
{p}wDrift = vec2(0.0);
|
||||
{p}wAlpha = {p}wFade * 0.5 + 0.5;
|
||||
}
|
||||
|
||||
{tex0} = vec4({corner}.x + {p}wDrift.x, {p}wCornerY * 0.25 + {p}wScroll + {p}wDrift.y, 0.0, 1.0);
|
||||
{tex1} = vec4({p}wLightmap, 0.0, 1.0);
|
||||
{color} = vec4(1.0, 1.0, 1.0, {p}wAlpha * {p}wStrength);""";
|
||||
|
||||
public static String weatherPrologue(String uniformPrefix, String attribPrefix, String corner,
|
||||
String vertex, String color, String tex0, String tex1, String sep) {
|
||||
return WEATHER_PROLOGUE
|
||||
.replace("{p}", uniformPrefix)
|
||||
.replace("{span}", attribPrefix + "InstColumnSpan")
|
||||
.replace("{jitter}", attribPrefix + "InstJitter")
|
||||
.replace("{params}", attribPrefix + "InstWeatherParams")
|
||||
.replace("{corner}", corner)
|
||||
.replace("{vertex}", vertex)
|
||||
.replace("{color}", color)
|
||||
.replace("{tex0}", tex0)
|
||||
.replace("{tex1}", tex1)
|
||||
.replace("\n", sep);
|
||||
}
|
||||
|
||||
public static String cubePrelude(String prefix, String mid, String delta, String normal, String tex, String sep) {
|
||||
return "float " + prefix + "cubeMirror = " + tex + ".z < 0.0 ? 1.0 : 0.0;" + sep
|
||||
+ "vec2 " + prefix + "cubeMidU = " + mid + ".xy - " + prefix + "cubeMirror * " + normal + ".x;" + sep
|
||||
@@ -65,6 +121,11 @@ public final class InstancedGlslHelpers {
|
||||
decls.add(decl(ParticleInstancedAttribs.LOC_COLOR, "vec4", prefix + "InstColor"));
|
||||
decls.add(decl(ParticleInstancedAttribs.LOC_LIGHTMAP, "vec2", prefix + "InstLightmap"));
|
||||
}
|
||||
case WEATHER -> {
|
||||
decls.add(decl(WeatherInstancedAttribs.LOC_COLUMN_SPAN, "vec4", prefix + "InstColumnSpan"));
|
||||
decls.add(decl(WeatherInstancedAttribs.LOC_JITTER, "vec4", prefix + "InstJitter"));
|
||||
decls.add(decl(WeatherInstancedAttribs.LOC_PARAMS, "vec4", prefix + "InstWeatherParams"));
|
||||
}
|
||||
}
|
||||
return decls.toArray(String[]::new);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ public enum Instancing {
|
||||
NONE(""),
|
||||
TEMPLATE("_instanced"),
|
||||
CUBE("_cubeinstanced"),
|
||||
PARTICLE("_particleinstanced");
|
||||
PARTICLE("_particleinstanced"),
|
||||
WEATHER("_weatherinstanced");
|
||||
|
||||
public static final Instancing[] VALUES = values();
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.IdentityHashMap;
|
||||
|
||||
|
||||
@@ -271,25 +270,6 @@ public class ShaderCache {
|
||||
}
|
||||
}
|
||||
|
||||
/** Destroy all cached programs and clear caches. */
|
||||
public void destroy() {
|
||||
for (int i = 0; i < capacity; i++) {
|
||||
if (programs[i] != null) {
|
||||
programs[i].destroy();
|
||||
}
|
||||
}
|
||||
Arrays.fill(keys, 0);
|
||||
Arrays.fill(programs, null);
|
||||
Arrays.fill(fragSources, null);
|
||||
count = 0;
|
||||
uniqueFragCount = 0;
|
||||
vertexSourceCache.clear();
|
||||
geometrySourceCache.clear();
|
||||
lastProgram = null;
|
||||
lastVK = Long.MIN_VALUE;
|
||||
GLStateManager.LOGGER.debug("FFP shader cache cleared");
|
||||
}
|
||||
|
||||
public int getProgramCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ 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;
|
||||
@@ -28,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, StackIdAllocator.nextStaticId());
|
||||
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0], StackIdAllocator.nextStaticId());
|
||||
@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());
|
||||
@@ -74,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");
|
||||
}
|
||||
|
||||
@@ -112,7 +110,7 @@ public final class ShaderManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
public static void disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
@@ -121,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;
|
||||
@@ -130,30 +135,29 @@ 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 GLContextState glCtx = GLStateManager.ctx();
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch, glCtx);
|
||||
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask, glCtx);
|
||||
@@ -185,44 +189,31 @@ public final class ShaderManager {
|
||||
}
|
||||
|
||||
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(),
|
||||
|
||||
@@ -11,7 +11,6 @@ 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;
|
||||
@@ -49,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;
|
||||
@@ -124,25 +124,26 @@ public class Uniforms {
|
||||
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);
|
||||
@@ -192,6 +193,12 @@ public class Uniforms {
|
||||
stagedMisc++;
|
||||
dirty = true;
|
||||
}
|
||||
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) {
|
||||
@@ -379,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);
|
||||
}
|
||||
@@ -430,6 +446,10 @@ public class Uniforms {
|
||||
stagedMisc = 0;
|
||||
}
|
||||
|
||||
void invalidateBinding() {
|
||||
bound = false;
|
||||
}
|
||||
|
||||
UniformRingBuffer getRing() {
|
||||
return ring;
|
||||
}
|
||||
|
||||
@@ -14,30 +14,34 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
|
||||
public final class VAOManager {
|
||||
public static final int MAX_ATTRIBS = 16;
|
||||
|
||||
public static final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
|
||||
public final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
static final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
|
||||
final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
private static VAOData current = null;
|
||||
private static int currentVertexFlags = 0;
|
||||
private static Attrib[] currentAttribs = null;
|
||||
public static int boundEBO = 0;
|
||||
private VAOData current = null;
|
||||
private int currentVertexFlags = 0;
|
||||
private Attrib[] currentAttribs = null;
|
||||
public int boundEBO = 0;
|
||||
|
||||
private static int clientSideEnabledCount = 0;
|
||||
private int clientSideEnabledCount = 0;
|
||||
|
||||
|
||||
public static void init(int defaultVAO) {
|
||||
public void init(int defaultVAO) {
|
||||
final VAOData data = new VAOData();
|
||||
current = data;
|
||||
currentAttribs = data.attribs;
|
||||
vaoMap.put(defaultVAO, data);
|
||||
}
|
||||
|
||||
public static void onBindEBO(int ebo) {
|
||||
public boolean isInitialized() {
|
||||
return current != null;
|
||||
}
|
||||
|
||||
public void onBindEBO(int ebo) {
|
||||
boundEBO = ebo;
|
||||
}
|
||||
|
||||
public static void onBindVertexArrayPre(int vaoId) {
|
||||
public void onBindVertexArrayPre(int vaoId) {
|
||||
// Save old VAO data
|
||||
if (current != null) {
|
||||
current.vertexFlags = currentVertexFlags;
|
||||
@@ -62,14 +66,14 @@ public final class VAOManager {
|
||||
clientSideEnabledCount = data.clientSideEnabledCount;
|
||||
}
|
||||
|
||||
public static void onDeleteVertexArray(int vaoId) {
|
||||
public void onDeleteVertexArray(int vaoId) {
|
||||
final VAOData data = vaoMap.remove(vaoId);
|
||||
if (data == null) return;
|
||||
removeEboOwner(data.ebo, data);
|
||||
if (data == current) current = null;
|
||||
}
|
||||
|
||||
public static void onDeleteBuffer(int buffer) {
|
||||
public void onDeleteBuffer(int buffer) {
|
||||
if (buffer == 0) return;
|
||||
VAOData owner = eboOwners.remove(buffer);
|
||||
while (owner != null) {
|
||||
@@ -80,12 +84,12 @@ public final class VAOManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static void addEboOwner(int ebo, VAOData data) {
|
||||
private void addEboOwner(int ebo, VAOData data) {
|
||||
if (ebo == 0) return;
|
||||
data.nextEboOwner = eboOwners.put(ebo, data);
|
||||
}
|
||||
|
||||
private static void removeEboOwner(int ebo, VAOData data) {
|
||||
private void removeEboOwner(int ebo, VAOData data) {
|
||||
if (ebo == 0) return;
|
||||
VAOData node = eboOwners.get(ebo);
|
||||
if (node == data) {
|
||||
@@ -105,10 +109,11 @@ public final class VAOManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
|
||||
public static void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
|
||||
public static int getCurrentVertexFlags() { return currentVertexFlags; }
|
||||
public static void setCurrentVertexFlags(int flags) { currentVertexFlags = flags; }
|
||||
public void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
|
||||
public void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
|
||||
public int getVertexFlags() { return currentVertexFlags; }
|
||||
public void setVertexFlags(int flags) { currentVertexFlags = flags; }
|
||||
public static void setCurrentVertexFlags(int flags) { GLStateManager.ctx().vaos.currentVertexFlags = flags; }
|
||||
|
||||
public static final class VAOData {
|
||||
public Attrib[] attribs;
|
||||
@@ -122,7 +127,7 @@ public final class VAOManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
|
||||
public void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
|
||||
if (index < 0 || index >= MAX_ATTRIBS) return;
|
||||
final Attrib a = getAttrib(index);
|
||||
final boolean wasClient = a.clientPointer != null;
|
||||
@@ -139,7 +144,7 @@ public final class VAOManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
|
||||
public void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
|
||||
if (index < 0 || index >= MAX_ATTRIBS) return;
|
||||
final Attrib a = getAttrib(index);
|
||||
final boolean wasClient = a.clientPointer != null;
|
||||
@@ -156,19 +161,19 @@ public final class VAOManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isGenericPointerEnabled(int index) {
|
||||
public boolean isGenericPointerEnabled(int index) {
|
||||
if (index < 0 || index >= MAX_ATTRIBS) return false;
|
||||
final Attrib a = currentAttribs[index];
|
||||
return a != null && a.genericPointer && a.enabled;
|
||||
}
|
||||
|
||||
public static void markConventional(int index) {
|
||||
public void markConventional(int index) {
|
||||
if (index < 0 || index >= MAX_ATTRIBS) return;
|
||||
final Attrib a = currentAttribs[index];
|
||||
if (a != null) a.genericPointer = false;
|
||||
}
|
||||
|
||||
public static void setEnabled(int index, boolean enabled) {
|
||||
public void setEnabled(int index, boolean enabled) {
|
||||
if (index < 0 || index >= MAX_ATTRIBS) return;
|
||||
final Attrib a = getAttrib(index);
|
||||
if (a.clientPointer != null && a.enabled != enabled) {
|
||||
@@ -177,7 +182,7 @@ public final class VAOManager {
|
||||
a.enabled = enabled;
|
||||
}
|
||||
|
||||
public static void enableAttribute(int index) {
|
||||
public void enableAttribute(int index) {
|
||||
if (index < 0 || index >= MAX_ATTRIBS) return;
|
||||
final Attrib a = getAttrib(index);
|
||||
if (a.clientPointer != null && !a.enabled) {
|
||||
@@ -186,7 +191,7 @@ public final class VAOManager {
|
||||
a.enabled = true;
|
||||
}
|
||||
|
||||
public static void disableAttribute(int index) {
|
||||
public void disableAttribute(int index) {
|
||||
if (index < 0 || index >= MAX_ATTRIBS) return;
|
||||
final Attrib a = getAttrib(index);
|
||||
if (a.clientPointer != null && a.enabled) {
|
||||
@@ -196,7 +201,7 @@ public final class VAOManager {
|
||||
}
|
||||
|
||||
// Lazily allocate each attrib. Most VAOs don't even have 4 attribs, let alone 16.
|
||||
private static Attrib getAttrib(int index) {
|
||||
private Attrib getAttrib(int index) {
|
||||
Attrib a = currentAttribs[index];
|
||||
if (a == null) {
|
||||
a = new Attrib();
|
||||
@@ -205,28 +210,32 @@ public final class VAOManager {
|
||||
return a;
|
||||
}
|
||||
|
||||
public static boolean isClientSideAttrib(int index) {
|
||||
public boolean isClientSideAttrib(int index) {
|
||||
final Attrib attrib = currentAttribs[index];
|
||||
return attrib != null && attrib.isClientSide();
|
||||
}
|
||||
|
||||
public static int getAttribEffectiveStride(int index) {
|
||||
public Attrib attrib(int index) {
|
||||
return currentAttribs[index];
|
||||
}
|
||||
|
||||
public int attribEffectiveStride(int index) {
|
||||
final Attrib attrib = currentAttribs[index];
|
||||
return attrib != null ? attrib.effectiveStride() : 0;
|
||||
}
|
||||
|
||||
public static long getAttribOffset(int index) {
|
||||
public long attribOffset(int index) {
|
||||
final Attrib attrib = currentAttribs[index];
|
||||
return attrib != null ? attrib.offset : 0;
|
||||
}
|
||||
|
||||
public static int getAttribVBO(int index) {
|
||||
public int attribVBO(int index) {
|
||||
final Attrib attrib = currentAttribs[index];
|
||||
return attrib != null ? attrib.vboId : 0;
|
||||
}
|
||||
|
||||
public static @Nullable Attrib get(int index) {
|
||||
return currentAttribs[index];
|
||||
return GLStateManager.ctx().vaos.attrib(index);
|
||||
}
|
||||
|
||||
|
||||
@@ -236,7 +245,7 @@ public final class VAOManager {
|
||||
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
|
||||
* offsets into a non-existent buffer, causing a native crash at draw time.
|
||||
*/
|
||||
public static boolean hasAnyClientSideEnabledAttrib() {
|
||||
public boolean hasAnyClientSideEnabledAttrib() {
|
||||
return clientSideEnabledCount > 0;
|
||||
}
|
||||
|
||||
@@ -244,15 +253,15 @@ public final class VAOManager {
|
||||
* If any enabled vertex attribute uses a client-side pointer (no VBO), upload all such
|
||||
* attribs into a shared stream VBO so the draw succeeds under core profile.
|
||||
*/
|
||||
private static int clientArraysVBO = 0;
|
||||
private static int clientArraysVBOCapacity = 0;
|
||||
private static final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
|
||||
private int clientArraysVBO = 0;
|
||||
private int clientArraysVBOCapacity = 0;
|
||||
private final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
|
||||
|
||||
public static void uploadClientArraysToVBO() {
|
||||
public void uploadClientArraysToVBO() {
|
||||
int totalBytes = 0;
|
||||
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
|
||||
clientArraysVBOOffsets[i] = -1;
|
||||
final VAOManager.Attrib a = VAOManager.get(i);
|
||||
final VAOManager.Attrib a = currentAttribs[i];
|
||||
if (a == null || !a.enabled || a.clientPointer == null) continue;
|
||||
clientArraysVBOOffsets[i] = totalBytes;
|
||||
totalBytes += a.clientPointer.remaining();
|
||||
@@ -275,7 +284,7 @@ public final class VAOManager {
|
||||
|
||||
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
|
||||
if (clientArraysVBOOffsets[i] < 0) continue;
|
||||
final VAOManager.Attrib a = VAOManager.get(i);
|
||||
final VAOManager.Attrib a = currentAttribs[i];
|
||||
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, clientArraysVBOOffsets[i], a.clientPointer.duplicate());
|
||||
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,13 @@ public final class VertexKey {
|
||||
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
|
||||
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
|
||||
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
|
||||
private static final long INSTANCING_MASK = 0x7L;
|
||||
|
||||
static {
|
||||
if (Instancing.VALUES.length > INSTANCING_MASK + 1) {
|
||||
throw new IllegalStateException("Instancing has outgrown the VertexKey field; widen INSTANCING_MASK");
|
||||
}
|
||||
}
|
||||
|
||||
public static final int TG_NONE = 0;
|
||||
public static final int TG_OBJ_LINEAR = 1;
|
||||
@@ -98,7 +105,7 @@ public final class VertexKey {
|
||||
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
|
||||
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
|
||||
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
|
||||
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & 0x3)]; }
|
||||
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & INSTANCING_MASK)]; }
|
||||
|
||||
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
@@ -231,21 +238,21 @@ public final class VertexKey {
|
||||
bits |= (1L << BIT_CLIP_PLANES);
|
||||
}
|
||||
|
||||
if (GLStateManager.wideLineEmulationActive) {
|
||||
if (glCtx.wideLineEmulationActive) {
|
||||
bits |= (1L << BIT_WIDE_LINE);
|
||||
}
|
||||
|
||||
if (GLStateManager.lineStippleActive) {
|
||||
if (glCtx.lineStippleActive) {
|
||||
bits |= (1L << BIT_LINE_STIPPLE);
|
||||
}
|
||||
|
||||
bits |= (long) GLStateManager.ffpInstancing.ordinal() << BIT_INSTANCING;
|
||||
bits |= (long) glCtx.ffpInstancing.ordinal() << BIT_INSTANCING;
|
||||
|
||||
return bits;
|
||||
}
|
||||
|
||||
static long withInstancing(long packed, Instancing kind) {
|
||||
return (packed & ~(0x3L << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
|
||||
return (packed & ~(INSTANCING_MASK << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
|
||||
}
|
||||
|
||||
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
|
||||
|
||||
@@ -21,6 +21,10 @@ public final class VertexShaderGenerator {
|
||||
emitOutputs(sb, key);
|
||||
|
||||
sb.append("void main() {\n");
|
||||
if (key.instancing() == Instancing.WEATHER) {
|
||||
sb.append(" ").append(InstancedGlslHelpers.weatherPrologue("u_", "a_", "a_TexCoord0",
|
||||
"vec4 weatherVertex", "vec4 weatherColor", "vec4 weatherTex0", "vec4 weatherTex1", "\n ")).append('\n');
|
||||
}
|
||||
emitPositionTransform(sb, key);
|
||||
if (key.lightingEnabled()) {
|
||||
emitNormalTransform(sb, key);
|
||||
@@ -58,7 +62,7 @@ public final class VertexShaderGenerator {
|
||||
final Instancing kind = key.instancing();
|
||||
sb.append("// Vertex attributes\n");
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
|
||||
if (kind == Instancing.PARTICLE) {
|
||||
if (kind == Instancing.PARTICLE || kind == Instancing.WEATHER) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
|
||||
} else {
|
||||
if (key.hasVertexColor()) {
|
||||
@@ -88,6 +92,7 @@ public final class VertexShaderGenerator {
|
||||
return switch (key.instancing()) {
|
||||
case CUBE -> "cubeUv";
|
||||
case PARTICLE -> PARTICLE_UV;
|
||||
case WEATHER -> "weatherTex0";
|
||||
case NONE, TEMPLATE -> key.hasVertexTexCoord() ? "vec4(a_TexCoord0, 0.0, 1.0)" : "u_CurrentTexCoord0";
|
||||
};
|
||||
}
|
||||
@@ -134,6 +139,8 @@ public final class VertexShaderGenerator {
|
||||
sb.append(" // Position transform\n");
|
||||
if (key.instancing() == Instancing.PARTICLE) {
|
||||
sb.append(" vec4 pos4 = ").append(InstancedGlslHelpers.particlePos("a_InstCenterHalf", "a_Position")).append(";\n");
|
||||
} else if (key.instancing() == Instancing.WEATHER) {
|
||||
sb.append(" vec4 pos4 = weatherVertex;\n");
|
||||
} else {
|
||||
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
|
||||
}
|
||||
@@ -282,6 +289,7 @@ public final class VertexShaderGenerator {
|
||||
final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
|
||||
switch (key.instancing()) {
|
||||
case PARTICLE -> sb.append(" v_Color = a_InstColor;\n");
|
||||
case WEATHER -> sb.append(" v_Color = weatherColor;\n");
|
||||
case TEMPLATE, CUBE -> sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
|
||||
case NONE -> sb.append(" v_Color = ").append(src).append(";\n");
|
||||
}
|
||||
@@ -312,6 +320,7 @@ public final class VertexShaderGenerator {
|
||||
final String lightmap = switch (key.instancing()) {
|
||||
case PARTICLE, TEMPLATE -> "a_InstLightmap";
|
||||
case CUBE -> "a_InstLightmapScale.xy";
|
||||
case WEATHER -> "weatherTex1.xy";
|
||||
case NONE -> key.hasVertexLightmap() ? "a_TexCoord1" : "u_CurrentLightmapCoord";
|
||||
};
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(").append(lightmap).append(", 0.0, 1.0);\n");
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
|
||||
/**
|
||||
* One rain or snow column per instance.
|
||||
*/
|
||||
public final class WeatherInstancedAttribs {
|
||||
|
||||
public static final int LOC_COLUMN_SPAN = ParticleInstancedAttribs.LOC_CENTER_HALF;
|
||||
public static final int LOC_JITTER = ParticleInstancedAttribs.LOC_UV;
|
||||
public static final int LOC_PARAMS = InstancedAttribs.LOC_COLOR;
|
||||
|
||||
public static final int OFFSET_COLUMN_SPAN = 0;
|
||||
public static final int OFFSET_JITTER = 16;
|
||||
public static final int OFFSET_PARAMS = 32;
|
||||
public static final int STRIDE = 48;
|
||||
|
||||
public static void pointInstanceAttribs(long base) {
|
||||
GLStateManager.glVertexAttribPointer(LOC_COLUMN_SPAN, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_COLUMN_SPAN);
|
||||
GLStateManager.glVertexAttribPointer(LOC_JITTER, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_JITTER);
|
||||
GLStateManager.glVertexAttribPointer(LOC_PARAMS, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_PARAMS);
|
||||
}
|
||||
|
||||
static void enableInstanceArrays() {
|
||||
InstancedAttribs.enableInstanced(LOC_COLUMN_SPAN);
|
||||
InstancedAttribs.enableInstanced(LOC_JITTER);
|
||||
InstancedAttribs.enableInstanced(LOC_PARAMS);
|
||||
}
|
||||
|
||||
private WeatherInstancedAttribs() {}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
/**
|
||||
* The per-frame half of the rain and snow instancing, set once per weather draw.
|
||||
*/
|
||||
public final class WeatherParams {
|
||||
|
||||
public static float translateX;
|
||||
public static float translateY;
|
||||
public static float translateZ;
|
||||
public static float invRadius;
|
||||
|
||||
public static float cameraFracX;
|
||||
public static float cameraFracZ;
|
||||
public static float partialTicks;
|
||||
public static float age;
|
||||
|
||||
public static float rainScroll;
|
||||
public static float snowScroll;
|
||||
public static float rainStrength;
|
||||
|
||||
public static int generation;
|
||||
|
||||
private WeatherParams() {}
|
||||
|
||||
public static void set(float translateX, float translateY, float translateZ, float invRadius,
|
||||
float cameraFracX, float cameraFracZ, float partialTicks, float age,
|
||||
float rainScroll, float snowScroll, float rainStrength) {
|
||||
WeatherParams.translateX = translateX;
|
||||
WeatherParams.translateY = translateY;
|
||||
WeatherParams.translateZ = translateZ;
|
||||
WeatherParams.invRadius = invRadius;
|
||||
WeatherParams.cameraFracX = cameraFracX;
|
||||
WeatherParams.cameraFracZ = cameraFracZ;
|
||||
WeatherParams.partialTicks = partialTicks;
|
||||
WeatherParams.age = age;
|
||||
WeatherParams.rainScroll = rainScroll;
|
||||
WeatherParams.snowScroll = snowScroll;
|
||||
WeatherParams.rainStrength = rainStrength;
|
||||
generation++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* The four corners every rain and snow column is expanded from. x picks the end of the billboard, y picks
|
||||
* the bottom or top edge.
|
||||
*/
|
||||
public final class WeatherQuadMesh {
|
||||
|
||||
public static final int VERTEX_FLAGS = VertexFlags.TEXTURE_BIT;
|
||||
public static final int VERTEX_COUNT = 4;
|
||||
|
||||
private static final float[] CORNERS = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f };
|
||||
|
||||
private static int vao;
|
||||
private static int vbo;
|
||||
|
||||
private WeatherQuadMesh() {}
|
||||
|
||||
public static int vao() {
|
||||
if (vao != 0) return vao;
|
||||
|
||||
final ByteBuffer data = ByteBuffer.allocateDirect(CORNERS.length * Float.BYTES).order(ByteOrder.nativeOrder());
|
||||
for (float corner : CORNERS) data.putFloat(corner);
|
||||
data.flip();
|
||||
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
vbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
|
||||
final int corner = VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation();
|
||||
GLStateManager.glEnableVertexAttribArray(corner);
|
||||
GLStateManager.glVertexAttribPointer(corner, 2, GL11.GL_FLOAT, false, 2 * Float.BYTES, 0L);
|
||||
WeatherInstancedAttribs.enableInstanceArrays();
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
return vao;
|
||||
}
|
||||
|
||||
public static void bindInstanceBuffer(int bufferId) {
|
||||
GLStateManager.glBindVertexArray(vao());
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
|
||||
WeatherInstancedAttribs.pointInstanceAttribs(0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
public static void delete() {
|
||||
if (vbo != 0) {
|
||||
GLStateManager.glDeleteBuffers(vbo);
|
||||
vbo = 0;
|
||||
}
|
||||
if (vao != 0) {
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
vao = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,10 +41,6 @@ public final class FrameHooks {
|
||||
BackendManager.shutdown();
|
||||
}
|
||||
|
||||
public static void bindSplashVao() {
|
||||
GLStateManager.glBindVertexArray(GLStateManager.glGenVertexArrays());
|
||||
}
|
||||
|
||||
public static void splashFinished() {
|
||||
GLStateManager.markSplashComplete("SplashProgress.finish");
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public final class GLSMConfig {
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
// Lightmap (replaces OpenGlHelper.lastBrightnessX/Y)
|
||||
public static float lastBrightnessX;
|
||||
public static float lastBrightnessY;
|
||||
public final class GLSMConfig {
|
||||
|
||||
public static int packBrightness(float x, float y) {
|
||||
return ((int) y << 16) | ((int) x & 0xFFFF);
|
||||
}
|
||||
|
||||
public static int packedLastBrightness() {
|
||||
return packBrightness(lastBrightnessX, lastBrightnessY);
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
return packBrightness(glCtx.lastBrightnessX, glCtx.lastBrightnessY);
|
||||
}
|
||||
|
||||
public static boolean hudCacheOverride;
|
||||
|
||||
@@ -12,7 +12,6 @@ public final class GLSMInitConfig {
|
||||
@Getter private final boolean lwjglDebug;
|
||||
@Getter private final StreamingUploader.UploadStrategy streamingUploadStrategy;
|
||||
@Getter private final Consumer<DirectTessellator> directDrawer;
|
||||
@Getter private final Runnable streamingDrawerDestroy;
|
||||
@Getter private final int displayWidth;
|
||||
@Getter private final int displayHeight;
|
||||
@Getter private final Runnable postInitCallback;
|
||||
@@ -24,7 +23,6 @@ public final class GLSMInitConfig {
|
||||
this.streamingUploadStrategy = builder.streamingUploadStrategy;
|
||||
this.noErrorChecks = builder.noErrorChecks;
|
||||
this.directDrawer = builder.directDrawer;
|
||||
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
|
||||
this.displayWidth = builder.displayWidth;
|
||||
this.displayHeight = builder.displayHeight;
|
||||
this.postInitCallback = builder.postInitCallback;
|
||||
@@ -43,7 +41,6 @@ public final class GLSMInitConfig {
|
||||
private boolean lwjglDebug = SystemProperties.LWJGL_DEBUG;
|
||||
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
|
||||
private Consumer<DirectTessellator> directDrawer = TessellatorStreamingDrawer::drawDirect;
|
||||
private Runnable streamingDrawerDestroy = TessellatorStreamingDrawer::destroy;
|
||||
private int displayWidth = 0;
|
||||
private int displayHeight = 0;
|
||||
private Runnable postInitCallback = null;
|
||||
@@ -72,11 +69,6 @@ public final class GLSMInitConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder streamingDrawerDestroy(Runnable streamingDrawerDestroy) {
|
||||
this.streamingDrawerDestroy = streamingDrawerDestroy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder displaySize(int width, int height) {
|
||||
this.displayWidth = width;
|
||||
this.displayHeight = height;
|
||||
|
||||
@@ -11,22 +11,20 @@ public final class TextureUtilHooks {
|
||||
|
||||
private TextureUtilHooks() {}
|
||||
|
||||
private static int savedFilterTextureId;
|
||||
|
||||
public static void setMaxLevel(int mipmapLevels) {
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmapLevels);
|
||||
}
|
||||
|
||||
public static void captureBoundFilterTexture() {
|
||||
savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
|
||||
GLStateManager.ctx().savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
|
||||
}
|
||||
|
||||
public static void clearSavedFilterTexture() {
|
||||
savedFilterTextureId = 0;
|
||||
GLStateManager.ctx().savedFilterTextureId = 0;
|
||||
}
|
||||
|
||||
public static void restoreFilter(int min, int mag) {
|
||||
final int id = savedFilterTextureId;
|
||||
final int id = GLStateManager.ctx().savedFilterTextureId;
|
||||
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
|
||||
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
|
||||
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
|
||||
@@ -37,7 +35,7 @@ public final class TextureUtilHooks {
|
||||
}
|
||||
|
||||
public static void restoreAniso(float aniso) {
|
||||
final int id = savedFilterTextureId;
|
||||
final int id = GLStateManager.ctx().savedFilterTextureId;
|
||||
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
|
||||
RenderSystem.texParameterf(id, GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
|
||||
} else {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
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;
|
||||
@@ -89,7 +90,8 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
if (GLStateManager.getAttribDepth() > 0
|
||||
final GLContextState o = cow.owner;
|
||||
if (o != null && o.attribDepth > 0
|
||||
&& (stateUnknown || !GLStateManager.isCachingEnabled() || enabled != vanillaValue())) {
|
||||
beforeModify();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
/**
|
||||
@@ -14,6 +15,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
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];
|
||||
|
||||
@@ -40,6 +40,7 @@ public final class CowDispatch {
|
||||
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();
|
||||
@@ -66,6 +67,7 @@ public final class CowDispatch {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -94,6 +96,7 @@ public final class CowDispatch {
|
||||
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);
|
||||
};
|
||||
}
|
||||
@@ -123,6 +126,7 @@ public final class CowDispatch {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -41,15 +42,19 @@ public interface CowStateStack<T> extends IStateStack<T> {
|
||||
|
||||
@Override
|
||||
default int pushDepth() {
|
||||
return GLStateManager.getAttribDepth();
|
||||
final GLContextState o = cowDepths().owner;
|
||||
return o == null ? 0 : o.attribDepth;
|
||||
}
|
||||
|
||||
@Override
|
||||
default void beforeModify() {
|
||||
final int s = cowDepths().claim(GLStateManager.getAttribDepth());
|
||||
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(stackId());
|
||||
GLStateManager.registerModifiedState(o, d.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;
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,8 @@ public final class StackIdAllocator {
|
||||
|
||||
private StackIdAllocator() {}
|
||||
|
||||
private static final int STATIC_BASE = 4096;
|
||||
private static final int STATIC_CAPACITY = 16;
|
||||
|
||||
private static int next = -1;
|
||||
private static int perContext = -1;
|
||||
private static int staticNext;
|
||||
|
||||
public static void beginContext() {
|
||||
next = 0;
|
||||
@@ -28,14 +24,7 @@ public final class StackIdAllocator {
|
||||
return next >= 0 ? next++ : -1;
|
||||
}
|
||||
|
||||
public static int nextStaticId() {
|
||||
if (staticNext >= STATIC_CAPACITY) {
|
||||
throw new IllegalStateException("static stack id capacity exceeded");
|
||||
}
|
||||
return STATIC_BASE + staticNext++;
|
||||
}
|
||||
|
||||
public static int capacity() {
|
||||
return STATIC_BASE + STATIC_CAPACITY;
|
||||
public static int allocated() {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
+35
-56
@@ -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();
|
||||
}
|
||||
}
|
||||
+62
@@ -3,8 +3,10 @@ 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
|
||||
@@ -170,6 +172,66 @@ public class GLSM_PopAttribMaskFidelity_GLTest {
|
||||
}
|
||||
}
|
||||
|
||||
@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 {
|
||||
|
||||
+40
-22
@@ -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);
|
||||
}
|
||||
|
||||
+302
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ public class GLSM_ValueStackCow_GLTest {
|
||||
"glAlphaFunc", "glBlendFunc", "tryBlendFuncSeparate", "glBlendEquation", "glBlendEquationSeparate",
|
||||
"glBlendColor", "glColorMask", "glClearColor", "glDrawBuffer", "glLogicOp",
|
||||
"glDepthFunc", "glDepthMask", "glClearDepth", "glStencilFunc", "glStencilOp",
|
||||
"glStencilMask", "glClearStencil", "glViewport", "glDepthRange", "glLineWidth",
|
||||
"glStencilMask", "glClearStencil", "glViewport", "glScissor", "glDepthRange", "glLineWidth",
|
||||
"glLineStipple", "glPointSize", "glPolygonMode", "glCullFace", "glFrontFace",
|
||||
"glPolygonOffset", "fogColor", "glFogf", "glFogi", "glLightf",
|
||||
"glLighti", "glLight", "glLightModelf", "glLightModeli", "glLightModel",
|
||||
@@ -74,6 +74,7 @@ public class GLSM_ValueStackCow_GLTest {
|
||||
() -> 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),
|
||||
|
||||
+6
-6
@@ -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();
|
||||
|
||||
@@ -69,7 +69,7 @@ class FFPVertexLightingGLTest {
|
||||
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
if (sm.isActive()) sm.deactivate();
|
||||
sm.disable();
|
||||
ShaderManager.disable();
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
if (vbo != 0) {
|
||||
GLStateManager.glDeleteBuffers(vbo);
|
||||
@@ -148,7 +148,7 @@ class FFPVertexLightingGLTest {
|
||||
private float[] drawAndReadCenter() {
|
||||
buildTriangle();
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
ShaderManager.enable();
|
||||
sm.activate();
|
||||
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
+3
-4
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
@@ -50,8 +49,8 @@ class FfpUniformBlockDedupTest {
|
||||
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glTexCoord2f(0.0f, 0.0f);
|
||||
GLSMConfig.lastBrightnessX = 0.0f;
|
||||
GLSMConfig.lastBrightnessY = 0.0f;
|
||||
GLStateManager.ctx().lastBrightnessX = 0.0f;
|
||||
GLStateManager.ctx().lastBrightnessY = 0.0f;
|
||||
GLStateManager.glFog(GL11.GL_FOG_COLOR, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 1.0f);
|
||||
GLStateManager.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, floats(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
@@ -128,7 +127,7 @@ class FfpUniformBlockDedupTest {
|
||||
addCategory(tests, "currentColor", () -> GLStateManager.glColor4f(0.25f, 0.5f, 0.75f, 1.0f));
|
||||
addCategory(tests, "currentNormal", () -> GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f));
|
||||
addCategory(tests, "currentTexCoord", () -> GLStateManager.glTexCoord2f(0.125f, 0.375f));
|
||||
addCategory(tests, "lightmapCoord", () -> GLSMConfig.lastBrightnessX = 96.0f);
|
||||
addCategory(tests, "lightmapCoord", () -> GLStateManager.ctx().lastBrightnessX = 96.0f);
|
||||
addCategory(tests, "texGenPlane", () -> GLStateManager.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(0.5f, 0.0f, 0.0f, 0.25f)));
|
||||
addCategory(tests, "clipPlane", () -> GLStateManager.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 1.0, 0.0, 7.0)));
|
||||
addCategory(tests, "alphaRef", () -> GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.35f));
|
||||
|
||||
+10
-10
@@ -147,7 +147,7 @@ class InstancedFFPDrawGLTest {
|
||||
CubeParityFixture.setupScene(lightmap, lit);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
ShaderManager.enable();
|
||||
sm.activate();
|
||||
|
||||
final CubeParityFixture.Spec[] specs = CubeParityFixture.specs();
|
||||
@@ -172,7 +172,7 @@ class InstancedFFPDrawGLTest {
|
||||
ParticleParityFixture.setupScene(true);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
ShaderManager.enable();
|
||||
sm.activate();
|
||||
|
||||
final ParticleParityFixture.Particle[] particles = ParticleParityFixture.particles();
|
||||
@@ -222,9 +222,9 @@ class InstancedFFPDrawGLTest {
|
||||
|
||||
@Test
|
||||
void instancedSourceUsesInstanceAttribs() {
|
||||
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
|
||||
GLStateManager.setFfpInstancing(Instancing.TEMPLATE);
|
||||
final VertexKey key = VertexKey.fromState(true, true, false, false, 0b10);
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
GLStateManager.setFfpInstancing(Instancing.NONE);
|
||||
|
||||
final String source = VertexShaderGenerator.generate(key);
|
||||
assertTrue(source.contains("a_InstRow0"), "instance matrix attribs declared");
|
||||
@@ -248,7 +248,7 @@ class InstancedFFPDrawGLTest {
|
||||
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
ShaderManager.enable();
|
||||
sm.activate();
|
||||
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
|
||||
@@ -278,7 +278,7 @@ class InstancedFFPDrawGLTest {
|
||||
uploadInstances(0xFF0000FF, 0, 0x0D0000FF, 0);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
ShaderManager.enable();
|
||||
sm.activate();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
|
||||
@@ -311,7 +311,7 @@ class InstancedFFPDrawGLTest {
|
||||
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
ShaderManager.enable();
|
||||
sm.activate();
|
||||
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
|
||||
@@ -351,7 +351,7 @@ class InstancedFFPDrawGLTest {
|
||||
uploadInstances(0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFF0000FF);
|
||||
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
ShaderManager.enable();
|
||||
sm.activate();
|
||||
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
|
||||
@@ -430,8 +430,8 @@ class InstancedFFPDrawGLTest {
|
||||
}
|
||||
|
||||
private static void drawInstances() {
|
||||
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
|
||||
GLStateManager.setFfpInstancing(Instancing.TEMPLATE);
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
GLStateManager.setFfpInstancing(Instancing.NONE);
|
||||
}
|
||||
}
|
||||
+22
-21
@@ -17,6 +17,7 @@ public class VAOManagerEboIndexTest {
|
||||
private static final int DEFAULT_VAO = 0;
|
||||
private static final int NO_VAO = -1;
|
||||
|
||||
private final VAOManager vaos = new VAOManager();
|
||||
private final Int2IntOpenHashMap expectedEbo = new Int2IntOpenHashMap();
|
||||
private int currentVao;
|
||||
|
||||
@@ -24,7 +25,7 @@ public class VAOManagerEboIndexTest {
|
||||
void reset() {
|
||||
drainManagerState();
|
||||
expectedEbo.clear();
|
||||
VAOManager.init(DEFAULT_VAO);
|
||||
vaos.init(DEFAULT_VAO);
|
||||
expectedEbo.put(DEFAULT_VAO, 0);
|
||||
currentVao = DEFAULT_VAO;
|
||||
}
|
||||
@@ -34,23 +35,23 @@ public class VAOManagerEboIndexTest {
|
||||
drainManagerState();
|
||||
}
|
||||
|
||||
private static void drainManagerState() {
|
||||
for (int vao : VAOManager.vaoMap.keySet().toIntArray()) {
|
||||
VAOManager.onDeleteVertexArray(vao);
|
||||
private void drainManagerState() {
|
||||
for (int vao : vaos.vaoMap.keySet().toIntArray()) {
|
||||
vaos.onDeleteVertexArray(vao);
|
||||
}
|
||||
VAOManager.boundEBO = 0;
|
||||
assertEquals(0, VAOManager.eboOwners.size(), "draining every VAO left entries in the index");
|
||||
vaos.boundEBO = 0;
|
||||
assertEquals(0, vaos.eboOwners.size(), "draining every VAO left entries in the index");
|
||||
}
|
||||
|
||||
private void bindVao(int vao) {
|
||||
if (currentVao != NO_VAO) expectedEbo.put(currentVao, VAOManager.boundEBO);
|
||||
VAOManager.onBindVertexArrayPre(vao);
|
||||
if (currentVao != NO_VAO) expectedEbo.put(currentVao, vaos.boundEBO);
|
||||
vaos.onBindVertexArrayPre(vao);
|
||||
expectedEbo.putIfAbsent(vao, 0);
|
||||
currentVao = vao;
|
||||
}
|
||||
|
||||
private void bindEbo(int ebo) {
|
||||
VAOManager.onBindEBO(ebo);
|
||||
vaos.onBindEBO(ebo);
|
||||
}
|
||||
|
||||
private int countOwners(int buffer) {
|
||||
@@ -62,20 +63,20 @@ public class VAOManagerEboIndexTest {
|
||||
}
|
||||
|
||||
private void deleteBuffer(int buffer) {
|
||||
VAOManager.onDeleteBuffer(buffer);
|
||||
vaos.onDeleteBuffer(buffer);
|
||||
for (int vao : expectedEbo.keySet().toIntArray()) {
|
||||
if (expectedEbo.get(vao) == buffer) expectedEbo.put(vao, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteVao(int vao) {
|
||||
VAOManager.onDeleteVertexArray(vao);
|
||||
vaos.onDeleteVertexArray(vao);
|
||||
expectedEbo.remove(vao);
|
||||
if (vao == currentVao) currentVao = NO_VAO;
|
||||
}
|
||||
|
||||
private void assertExpectedEbos(int op) {
|
||||
for (var entry : VAOManager.vaoMap.int2ObjectEntrySet()) {
|
||||
for (var entry : vaos.vaoMap.int2ObjectEntrySet()) {
|
||||
final int vao = entry.getIntKey();
|
||||
if (vao == currentVao) continue;
|
||||
assertEquals(expectedEbo.get(vao), entry.getValue().ebo, () -> "op " + op + ": vao " + vao + " ebo mismatch");
|
||||
@@ -140,10 +141,10 @@ public class VAOManagerEboIndexTest {
|
||||
|
||||
deleteBuffer(ebo);
|
||||
|
||||
assertEquals(0, VAOManager.vaoMap.get(1).ebo, "vao 1 kept a freed shared EBO");
|
||||
assertEquals(0, VAOManager.vaoMap.get(2).ebo, "vao 2 kept a freed shared EBO");
|
||||
assertEquals(0, VAOManager.vaoMap.get(3).ebo, "vao 3 kept a freed shared EBO");
|
||||
assertFalse(VAOManager.eboOwners.containsKey(ebo), "index kept owners for a freed EBO");
|
||||
assertEquals(0, vaos.vaoMap.get(1).ebo, "vao 1 kept a freed shared EBO");
|
||||
assertEquals(0, vaos.vaoMap.get(2).ebo, "vao 2 kept a freed shared EBO");
|
||||
assertEquals(0, vaos.vaoMap.get(3).ebo, "vao 3 kept a freed shared EBO");
|
||||
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept owners for a freed EBO");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -156,9 +157,9 @@ public class VAOManagerEboIndexTest {
|
||||
bindVao(DEFAULT_VAO);
|
||||
|
||||
deleteVao(1);
|
||||
assertTrue(VAOManager.eboOwners.containsKey(ebo), "index dropped a still-live owner");
|
||||
assertTrue(vaos.eboOwners.containsKey(ebo), "index dropped a still-live owner");
|
||||
deleteVao(2);
|
||||
assertFalse(VAOManager.eboOwners.containsKey(ebo), "index kept an entry after its last owner went away");
|
||||
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept an entry after its last owner went away");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -170,8 +171,8 @@ public class VAOManagerEboIndexTest {
|
||||
deleteVao(1);
|
||||
bindVao(DEFAULT_VAO);
|
||||
|
||||
assertFalse(VAOManager.eboOwners.containsKey(ebo), "deleting the bound VAO left an unreachable owner in the index");
|
||||
assertEquals(0, VAOManager.eboOwners.size(), "index leaked an entry for a deleted VAO");
|
||||
assertFalse(vaos.eboOwners.containsKey(ebo), "deleting the bound VAO left an unreachable owner in the index");
|
||||
assertEquals(0, vaos.eboOwners.size(), "index leaked an entry for a deleted VAO");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -184,6 +185,6 @@ public class VAOManagerEboIndexTest {
|
||||
for (int vao = 1; vao <= 64; vao++) {
|
||||
deleteVao(vao);
|
||||
}
|
||||
assertEquals(0, VAOManager.eboOwners.size(), "index leaked entries after every VAO was deleted");
|
||||
assertEquals(0, vaos.eboOwners.size(), "index leaked entries after every VAO was deleted");
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -314,10 +314,11 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
final ByteBuffer colorSlice = clientData.duplicate().order(ByteOrder.nativeOrder());
|
||||
colorSlice.position(8);
|
||||
VAOManager.setAttribute(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData);
|
||||
VAOManager.enableAttribute(0);
|
||||
VAOManager.setAttribute(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice);
|
||||
VAOManager.enableAttribute(1);
|
||||
final VAOManager vaos = GLStateManager.ctx().vaos;
|
||||
vaos.setAttribute(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData);
|
||||
vaos.enableAttribute(0);
|
||||
vaos.setAttribute(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice);
|
||||
vaos.enableAttribute(1);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
|
||||
@@ -593,8 +594,9 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
VAOManager.setAttribute(0, 3, GL11.GL_FLOAT, false, 0, clientVerts);
|
||||
VAOManager.enableAttribute(0);
|
||||
final VAOManager vaos = GLStateManager.ctx().vaos;
|
||||
vaos.setAttribute(0, 3, GL11.GL_FLOAT, false, 0, clientVerts);
|
||||
vaos.enableAttribute(0);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, indices);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
@@ -42,7 +42,6 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
|
||||
GLStateManager.initialize(GLSMInitConfig.builder()
|
||||
.displaySize(800, 600)
|
||||
.directDrawer(t -> {})
|
||||
.streamingDrawerDestroy(() -> {})
|
||||
.enableDSA(false)
|
||||
.build());
|
||||
GLStateManager.setRunningSplash(false);
|
||||
|
||||
@@ -70,7 +70,6 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
|
||||
final GLSMInitConfig config = GLSMInitConfig.builder()
|
||||
.displaySize(displayMode.getWidth(), displayMode.getHeight())
|
||||
.directDrawer(t -> {})
|
||||
.streamingDrawerDestroy(() -> {})
|
||||
.enableDSA(false)
|
||||
.build();
|
||||
setMainThread(Thread.currentThread());
|
||||
@@ -144,7 +143,7 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
|
||||
if (ffp.isActive()) {
|
||||
ffp.deactivate();
|
||||
}
|
||||
ffp.disable();
|
||||
ShaderManager.disable();
|
||||
|
||||
// Force-sync VAO binding. Tests may unbind via raw GL, desynchronizing GLSM's cache.
|
||||
// Invalidate the cache so glBindVertexArray(0) actually issues the GL call.
|
||||
|
||||
+2
-2
@@ -272,9 +272,9 @@ public final class CubeParityFixture {
|
||||
CubeInstancedAttribs.pointInstanceAttribs(0L);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
GLStateManager.ffpInstancing = Instancing.CUBE;
|
||||
GLStateManager.setFfpInstancing(Instancing.CUBE);
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, UnitCubeMesh.VERTEX_COUNT, specs.length);
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
GLStateManager.setFfpInstancing(Instancing.NONE);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -244,10 +244,10 @@ public final class FfpFixture {
|
||||
}
|
||||
|
||||
public static void resetFfpState() {
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
GLStateManager.setFfpInstancing(Instancing.NONE);
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
if (sm.isActive()) sm.deactivate();
|
||||
sm.disable();
|
||||
ShaderManager.disable();
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
|
||||
+2
-2
@@ -184,9 +184,9 @@ public final class ParticleParityFixture {
|
||||
ParticleInstancedAttribs.pointInstanceAttribs(0L);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
GLStateManager.ffpInstancing = Instancing.PARTICLE;
|
||||
GLStateManager.setFfpInstancing(Instancing.PARTICLE);
|
||||
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, ParticleQuadMesh.VERTEX_COUNT, particles.length);
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
GLStateManager.setFfpInstancing(Instancing.NONE);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.gtnewhorizons.angelica.glsm.testutil;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public final class TestThreads {
|
||||
|
||||
private static final long JOIN_TIMEOUT_MS = 10_000;
|
||||
|
||||
private TestThreads() {}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Body {
|
||||
void run() throws Throwable;
|
||||
}
|
||||
|
||||
public static final class Worker {
|
||||
private final Thread thread;
|
||||
private final AtomicReference<Throwable> error = new AtomicReference<>();
|
||||
|
||||
private Worker(String name, Body body) {
|
||||
thread = new Thread(() -> {
|
||||
try {
|
||||
body.run();
|
||||
} catch (Throwable t) {
|
||||
error.set(t);
|
||||
}
|
||||
}, name);
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public Throwable error() {
|
||||
return error.get();
|
||||
}
|
||||
|
||||
public void join() throws InterruptedException {
|
||||
thread.join(JOIN_TIMEOUT_MS);
|
||||
assertFalse(thread.isAlive(), thread.getName() + " did not finish");
|
||||
if (error.get() != null) fail(error.get());
|
||||
}
|
||||
}
|
||||
|
||||
public static Worker start(String name, Body body) {
|
||||
return new Worker(name, body);
|
||||
}
|
||||
|
||||
public static void run(String name, Body body) throws InterruptedException {
|
||||
start(name, body).join();
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,9 @@ PLOTS = [
|
||||
"items.bail.ineligible", "items.bail.material", "items.bail.isbrh", "items.bail.blockState", "items.bail.notAllowed", "items.bail.template", "items.bail.queue",
|
||||
"particles.direct", "particles.captured", "particles.spilled", "particles.undecodable", "particles.draws", "sdl.renderPasses", "sdl.submits", "sdl.gpuWaitUs",
|
||||
"sdl.acquireWaitUs",
|
||||
"weather.strength", "weather.quads", "weather.drawCalls", "weather.micros",
|
||||
]
|
||||
ZONES = ["entityModelParts", "tesrBatch", "tesrInstanced", "entityLayerLoop", "tesrOpaque", "particlePass"]
|
||||
ZONES = ["entityModelParts", "tesrBatch", "tesrInstanced", "entityLayerLoop", "tesrOpaque", "particlePass", "cl:weather"]
|
||||
CLOCK = "gl.drawCalls"
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compute.VoxelizationDispatcher;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.CaptureGate;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
|
||||
@@ -162,6 +163,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
private boolean droppedSsboWriteWarned;
|
||||
private boolean depthClearSuppressedWarned;
|
||||
private boolean stencilClearSuppressedWarned;
|
||||
private boolean partialClearFallbackWarned;
|
||||
private final DeferredCopyOp deferredCopyOp = new DeferredCopyOp();
|
||||
|
||||
private TransferThread transferThread;
|
||||
@@ -196,6 +198,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
for (ContextState st : registeredStates.snapshot()) st.pipeline.markInputDirty();
|
||||
}
|
||||
|
||||
@Override public void invalidateStateMirror() {
|
||||
s().invalidateMirror();
|
||||
}
|
||||
|
||||
public static final int CTR_SLOT_WRITES = 0;
|
||||
public static final int CTR_SLOT_WRITES_ELIDED = 1;
|
||||
public static final int CTR_SSBO_BINDS = 2;
|
||||
@@ -550,14 +556,34 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public boolean handleMakeCurrent(Object drawable) {
|
||||
if (!isSDLManagedDrawable(drawable)) return false;
|
||||
Lwjgl3GLCapabilitiesShim.installOnCurrentThread(advertisedCapabilities());
|
||||
final OffscreenTarget splash = splashTarget;
|
||||
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
s().boundFboId = splash.fboId();
|
||||
frameManager.frame().swapchainUnavailable = true;
|
||||
}
|
||||
enterContext(drawable instanceof SDLDrawable);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void enterContext(boolean displayDrawable) {
|
||||
final ContextState st = s();
|
||||
final GLContextState ctx = GLStateManager.ctx();
|
||||
if (st.mirroredContext != ctx) {
|
||||
st.invalidateMirror();
|
||||
st.mirroredContext = ctx;
|
||||
}
|
||||
final OffscreenTarget splash = splashTarget;
|
||||
final boolean splashContext = displayDrawable && splash != null && Thread.currentThread() != GLStateManager.getMainThread();
|
||||
st.defaultFboId = splashContext ? splash.fboId() : 0;
|
||||
if (splashContext && !frameManager.isFrameActive()) {
|
||||
beginSplashFrame();
|
||||
} else if (st.needsSeed) {
|
||||
st.needsSeed = false;
|
||||
GLStateManager.replayStateToBackend();
|
||||
}
|
||||
}
|
||||
|
||||
private void beginSplashFrame() {
|
||||
frameManager.beginFrame();
|
||||
beginFrameInit();
|
||||
frameManager.frame().swapchainUnavailable = true;
|
||||
}
|
||||
|
||||
@Override public boolean handleReleaseContext(Object drawable) {
|
||||
if (!isSDLManagedDrawable(drawable)) return false;
|
||||
if (Thread.currentThread() == GLStateManager.getMainThread()) return true;
|
||||
@@ -578,9 +604,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
endFrameUploadFlush();
|
||||
frameManager.endFrame();
|
||||
SplashDispatcher.signalFrameReady((int) s().viewportW, (int) s().viewportH);
|
||||
frameManager.beginFrame();
|
||||
beginFrameInit();
|
||||
frameManager.frame().swapchainUnavailable = true;
|
||||
beginSplashFrame();
|
||||
return true;
|
||||
}
|
||||
frameManager.presentFrame();
|
||||
@@ -810,6 +834,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
st.scissorDirty = true;
|
||||
}
|
||||
case GL31.GL_PRIMITIVE_RESTART -> cs.primitiveRestartEnabled = on;
|
||||
case GL30.GL_RASTERIZER_DISCARD -> cs.rasterizerDiscard = on;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -950,28 +975,78 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public void clearStencil(int s) { s().stencilClearValue = s & 0xFF; }
|
||||
|
||||
@Override public void clear(int mask) {
|
||||
final ContextState cs = s();
|
||||
final ContextState st = s();
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
final boolean wantColor = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
|
||||
boolean wantColor = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
|
||||
boolean wantDepth = (mask & GL11.GL_DEPTH_BUFFER_BIT) != 0;
|
||||
boolean wantStencil = (mask & GL11.GL_STENCIL_BUFFER_BIT) != 0;
|
||||
if (wantDepth && !cs.pipeline.depthWriteEnabled) {
|
||||
if (wantDepth && !st.pipeline.depthWriteEnabled) {
|
||||
wantDepth = false;
|
||||
if (!depthClearSuppressedWarned) {
|
||||
depthClearSuppressedWarned = true;
|
||||
LOG.warn("clear: GL_DEPTH_BUFFER_BIT requested with glDepthMask(false); clear suppressed per GL semantics");
|
||||
}
|
||||
}
|
||||
if (wantStencil && cs.pipeline.stencilWriteMask == 0) {
|
||||
if (wantStencil && st.pipeline.stencilWriteMask == 0) {
|
||||
wantStencil = false;
|
||||
if (!stencilClearSuppressedWarned) {
|
||||
stencilClearSuppressedWarned = true;
|
||||
LOG.warn("clear: GL_STENCIL_BUFFER_BIT requested with a zero glStencilMask; clear suppressed per GL semantics");
|
||||
}
|
||||
}
|
||||
if (wantColor && (st.pipeline.colorWriteMask & 0xF) == 0) {
|
||||
wantColor = false;
|
||||
}
|
||||
if (!wantColor && !wantDepth && !wantStencil) return;
|
||||
|
||||
final ContextState st = cs;
|
||||
final FboState fbo;
|
||||
if (st.boundFboId == 0) {
|
||||
fbo = null;
|
||||
} else {
|
||||
fbo = resourceManager.getFbo(st.boundFboId);
|
||||
if (fbo == null) return;
|
||||
if (fbo.targetsDirty) fbo.recomputeTargets();
|
||||
if (fbo.cachedFormatsDirty && fbo.hasAnyColor) fboClearTracker.updatePipelineCacheColorFormats(st, fbo);
|
||||
}
|
||||
|
||||
final AttachmentClear.Target target = fbo != null ? attachmentClear.forFbo(fbo) : attachmentClear.forFbo0(cachedSwapchainFormatArray, frameManager.getFbo0Width(), frameManager.getFbo0Height());
|
||||
|
||||
int skipSlots = 0;
|
||||
boolean colorPartial = false;
|
||||
if (wantColor) {
|
||||
final int colorWriteMask = st.pipeline.colorWriteMask;
|
||||
final int[] formats = target.colorFormats;
|
||||
final int slotCount = formats != null ? formats.length : 0;
|
||||
boolean anyLive = false;
|
||||
for (int i = 0; i < slotCount; i++) {
|
||||
final boolean present;
|
||||
if (fbo != null) {
|
||||
final int db = target.drawBuffers[i];
|
||||
present = db >= 0 && db < ContextState.MAX_COLOR_ATTACHMENTS && fbo.colorTextures[db] != 0;
|
||||
} else {
|
||||
present = i == 0;
|
||||
}
|
||||
if (!present) continue;
|
||||
final int fm = PixelOps.colorComponentMask(formats[i]);
|
||||
final int m = colorWriteMask & fm;
|
||||
if (m == 0) {
|
||||
skipSlots |= 1 << i;
|
||||
} else {
|
||||
anyLive = true;
|
||||
if (m != fm) colorPartial = true;
|
||||
}
|
||||
}
|
||||
wantColor = anyLive;
|
||||
}
|
||||
|
||||
if (target.width > 0 && target.height > 0) {
|
||||
final int coverage = attachmentClear.coverage(st, target);
|
||||
if (coverage == AttachmentClear.EMPTY) return;
|
||||
final boolean partial = coverage == AttachmentClear.PARTIAL || colorPartial || (wantStencil && (st.pipeline.stencilWriteMask & 0xFF) != 0xFF);
|
||||
if (partial && !(wantColor && attachmentClear.hasIntegerColorTarget(target)) && clearPartial(st, fbo, target, wantColor, wantDepth, wantStencil)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// FBO0 path: just record pending state; ensureFbo0RenderPass consumes it lazily.
|
||||
if (st.boundFboId == 0) {
|
||||
@@ -994,15 +1069,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return;
|
||||
}
|
||||
|
||||
final FboState fbo = resourceManager.getFbo(st.boundFboId);
|
||||
if (fbo == null) return;
|
||||
|
||||
if (!wantColor && (wantDepth || wantStencil)) {
|
||||
final FrameState f = frameManager.frame();
|
||||
if (attachmentClear.eligible(st, f, fbo, wantDepth, wantStencil)
|
||||
&& attachmentClear.clearInPass(st, f, fbo, wantDepth, wantStencil)) {
|
||||
return;
|
||||
}
|
||||
final FrameState f = frameManager.frame();
|
||||
if (!wantColor && !SystemProperties.SDL_DISABLE_IN_PASS_CLEAR && f.renderPass != 0 && f.activeLayoutHash == fbo.structuralLayoutHash && !FBOClearTracker.fboHasPendingClear(st, fbo) && clearPartial(st, fbo, target, false, wantDepth, wantStencil)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean affectsActivePass = false;
|
||||
@@ -1011,7 +1080,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long activeDepth = passActive ? frameManager.getCurrentDepthTarget() : 0;
|
||||
|
||||
if (wantColor) {
|
||||
for (int db : fbo.drawBuffers) {
|
||||
final int[] drawBuffers = fbo.drawBuffers;
|
||||
for (int i = 0; i < drawBuffers.length; i++) {
|
||||
if ((skipSlots & (1 << i)) != 0) continue;
|
||||
final int db = drawBuffers[i];
|
||||
if (db < 0 || db >= ContextState.MAX_COLOR_ATTACHMENTS) continue;
|
||||
final long tex = fbo.colorTextures[db];
|
||||
if (tex == 0) continue;
|
||||
@@ -1020,11 +1092,11 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
}
|
||||
if (wantDepth && fbo.depthTexture != 0) {
|
||||
FBOClearTracker.recordPendingDepthClear(st, fbo.depthTexture, cs.depthClearValue);
|
||||
FBOClearTracker.recordPendingDepthClear(st, fbo.depthTexture, st.depthClearValue);
|
||||
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
|
||||
}
|
||||
if (wantStencil && fbo.depthTexture != 0 && PixelOps.isDepthStencilFormat(fbo.depthFormat)) {
|
||||
FBOClearTracker.recordPendingStencilClear(st, fbo.depthTexture, cs.stencilClearValue);
|
||||
FBOClearTracker.recordPendingStencilClear(st, fbo.depthTexture, st.stencilClearValue);
|
||||
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
|
||||
}
|
||||
|
||||
@@ -1033,6 +1105,33 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean clearPartial(ContextState st, FboState fbo, AttachmentClear.Target t, boolean color, boolean depth, boolean stencil) {
|
||||
final FrameState f = frameManager.frame();
|
||||
pipelineApplier.ensureRenderPass(st, f);
|
||||
final long expectedLayout = fbo == null ? FrameManager.FBO0_LAYOUT_HASH : fbo.structuralLayoutHash;
|
||||
if (f.renderPass == 0 || f.activeLayoutHash != expectedLayout) {
|
||||
return warnPartialClearFallback();
|
||||
}
|
||||
if (fbo == null) {
|
||||
t.depthTexture = f.currentDepthTarget;
|
||||
t.depthFormat = t.depthTexture != 0 ? resourceManager.getSwapchainDepthStencilFormat() : 0;
|
||||
}
|
||||
color &= t.colorFormats != null && (fbo == null || fbo.hasAnyColor);
|
||||
depth &= t.depthTexture != 0;
|
||||
stencil &= PixelOps.isDepthStencilFormat(t.depthFormat);
|
||||
if (!color && !depth && !stencil) return true;
|
||||
if (attachmentClear.clearInPass(st, f, t, color, depth, stencil)) return true;
|
||||
return warnPartialClearFallback();
|
||||
}
|
||||
|
||||
private boolean warnPartialClearFallback() {
|
||||
if (!partialClearFallbackWarned) {
|
||||
partialClearFallbackWarned = true;
|
||||
LOG.error("clear: in-pass partial clear unavailable; falling back to the legacy clear path");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public void lineWidth(float width) { /* no-op */ }
|
||||
@Override public void pointSize(float size) { /* no-op */ }
|
||||
@Override public void logicOp(int opcode) { /* no-op */ }
|
||||
@@ -1047,9 +1146,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override public void drawArrays(int mode, int first, int count) {
|
||||
if (count <= 0) return;
|
||||
final ContextState st = s();
|
||||
if (st.rasterizerDiscard) return;
|
||||
final FrameState f = frameManager.frame();
|
||||
if (!f.frameActive) { f.droppedDrawsThisFrame++; return; }
|
||||
final ContextState st = s();
|
||||
if (mode == GL11.GL_TRIANGLE_FAN && count >= 3) {
|
||||
if (!drawDispatch.drawTriangleFanAsTriangleList(st, first, count)) { f.droppedDrawsThisFrame++; return; }
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysFan", mode, count, 0, first, 0);
|
||||
@@ -1092,6 +1192,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
private long prepareIndexedDrawBind(ContextState st, int mode, int type, String opName) {
|
||||
if (st.rasterizerDiscard) return 0;
|
||||
if (!drawDispatch.prepareIndexedDraw(st, mode, type)) return 0;
|
||||
final int ebo = st.currentVao.elementBuffer;
|
||||
final long eboHandle = resourceManager.getBufferHandle(ebo);
|
||||
@@ -1644,13 +1745,16 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override public void deleteFramebuffers(int framebuffer) {
|
||||
final ContextState cs = s();
|
||||
final boolean drawBound = cs.boundFboId == framebuffer;
|
||||
final boolean readBound = cs.boundReadFboId == framebuffer;
|
||||
resourceManager.deleteFbo(framebuffer);
|
||||
if (cs.boundFboId == framebuffer) cs.boundFboId = 0;
|
||||
if (cs.boundReadFboId == framebuffer) cs.boundReadFboId = 0;
|
||||
if (drawBound) bindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
|
||||
if (readBound) bindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
@Override public void bindFramebuffer(int target, int framebuffer) {
|
||||
final ContextState cs = s();
|
||||
if (framebuffer == 0) framebuffer = cs.defaultFboId;
|
||||
final boolean isRead = target == GL30.GL_READ_FRAMEBUFFER;
|
||||
final boolean isDraw = target == GL30.GL_DRAW_FRAMEBUFFER;
|
||||
final boolean isBoth = !isRead && !isDraw;
|
||||
@@ -1689,7 +1793,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override public void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
|
||||
final ContextState cs = s();
|
||||
if (cs.boundFboId == 0) return;
|
||||
if (cs.boundFboId == cs.defaultFboId) return;
|
||||
final FboState fbo = resourceManager.getFbo(cs.boundFboId);
|
||||
if (fbo == null) return;
|
||||
|
||||
@@ -1770,7 +1874,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override public void drawBuffers(int buffer) {
|
||||
final ContextState cs = s();
|
||||
if (cs.boundFboId == 0) return;
|
||||
if (cs.boundFboId == cs.defaultFboId) return;
|
||||
final FboState fbo = resourceManager.getFbo(cs.boundFboId);
|
||||
if (fbo == null) return;
|
||||
|
||||
@@ -1793,7 +1897,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void drawBuffers(IntBuffer bufs) {
|
||||
final ContextState cs = s();
|
||||
if (cs.boundFboId == 0) return;
|
||||
if (cs.boundFboId == cs.defaultFboId) return;
|
||||
final FboState fbo = resourceManager.getFbo(cs.boundFboId);
|
||||
if (fbo == null) return;
|
||||
applyDrawBuffersFromIntBuffer(fbo, bufs, true);
|
||||
@@ -1829,8 +1933,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
}
|
||||
@Override public void readBuffer(int mode) {
|
||||
final int fboId = s().boundReadFboId;
|
||||
if (fboId == 0) return;
|
||||
final ContextState cs = s();
|
||||
final int fboId = cs.boundReadFboId;
|
||||
if (fboId == cs.defaultFboId) return;
|
||||
final FboState fbo = resourceManager.getFbo(fboId);
|
||||
if (fbo == null) return;
|
||||
if (mode >= GL30.GL_COLOR_ATTACHMENT0 && mode < GL30.GL_COLOR_ATTACHMENT0 + ContextState.MAX_COLOR_ATTACHMENTS) {
|
||||
@@ -1864,6 +1969,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
srcWidth = meta != null ? meta.width() : fbo.width;
|
||||
srcHeight = meta != null ? meta.height() : fbo.height;
|
||||
srcY = y;
|
||||
if (frameManager.isFrameActive()) fboClearTracker.materializePendingClearForTexture(cs, texHandle);
|
||||
}
|
||||
if (texHandle == 0 || srcWidth <= 0 || srcHeight <= 0) return;
|
||||
|
||||
@@ -1964,6 +2070,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int colorFormat = cachedSwapchainFormatArray != null ? cachedSwapchainFormatArray[0] : 0;
|
||||
return defaultFramebufferAttachmentParameteri(attachment, pname, colorFormat, resourceManager.getSwapchainDepthStencilFormat());
|
||||
}
|
||||
if (fboId == cs.defaultFboId) {
|
||||
final FboState fbo = resourceManager.getFbo(fboId);
|
||||
return defaultFramebufferAttachmentParameteri(attachment, pname, fbo.colorFormats[0], fbo.depthFormat);
|
||||
}
|
||||
return fboAttachmentParameteri(resourceManager.getFbo(fboId), attachment, pname);
|
||||
}
|
||||
|
||||
@@ -3637,8 +3747,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL43.GL_MAX_DEBUG_GROUP_STACK_DEPTH -> 64;
|
||||
case GL43.GL_MAX_LABEL_LENGTH -> 256;
|
||||
case GL43.GL_MAX_DEBUG_MESSAGE_LENGTH -> 1024;
|
||||
case GL11.GL_DRAW_BUFFER -> drawBufferEnum(cs.boundFboId);
|
||||
case GL11.GL_READ_BUFFER -> readBufferEnum(cs.boundReadFboId);
|
||||
case GL11.GL_DRAW_BUFFER -> drawBufferEnum(cs.boundFboId, cs.defaultFboId);
|
||||
case GL11.GL_READ_BUFFER -> readBufferEnum(cs.boundReadFboId, cs.defaultFboId);
|
||||
case GL11.GL_TEXTURE_BINDING_2D -> boundTextureOf(cs.activeTextureUnit, cs.boundTextures);
|
||||
case GL11.GL_DEPTH_BITS -> getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE);
|
||||
case GL11.GL_STENCIL_BITS -> getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE);
|
||||
@@ -3655,15 +3765,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return GL30.GL_COLOR_ATTACHMENT0 + attachmentIndex;
|
||||
}
|
||||
|
||||
private int drawBufferEnum(int fboId) {
|
||||
if (fboId == 0) return bufferEnum(0, 0);
|
||||
private int drawBufferEnum(int fboId, int defaultFboId) {
|
||||
if (fboId == defaultFboId) return bufferEnum(0, 0);
|
||||
final FboState fbo = resourceManager.getFbo(fboId);
|
||||
final int idx = (fbo == null || fbo.drawBuffers.length == 0) ? -1 : fbo.drawBuffers[0];
|
||||
return bufferEnum(fboId, idx);
|
||||
}
|
||||
|
||||
private int readBufferEnum(int fboId) {
|
||||
if (fboId == 0) return bufferEnum(0, 0);
|
||||
private int readBufferEnum(int fboId, int defaultFboId) {
|
||||
if (fboId == defaultFboId) return bufferEnum(0, 0);
|
||||
final FboState fbo = resourceManager.getFbo(fboId);
|
||||
return bufferEnum(fboId, fbo == null ? -1 : fbo.readBufferIndex);
|
||||
}
|
||||
@@ -3743,6 +3853,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL11.GL_SCISSOR_TEST -> cs.scissorEnabled;
|
||||
case GL11.GL_STENCIL_TEST -> cs.pipeline.stencilTestEnabled;
|
||||
case GL11.GL_DEPTH_WRITEMASK -> cs.pipeline.depthWriteEnabled;
|
||||
case GL30.GL_RASTERIZER_DISCARD -> cs.rasterizerDiscard;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
@@ -4020,8 +4131,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override
|
||||
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
|
||||
if (count <= 0 || primcount <= 0) return;
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
final ContextState st = s();
|
||||
if (st.rasterizerDiscard) return;
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
|
||||
pipelineApplier.ensureRenderPass(st);
|
||||
if (!frameManager.isRenderPassActive()) return;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.frame;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
@@ -113,6 +114,7 @@ public final class ContextState {
|
||||
public float viewportDepthFar = 1.0f;
|
||||
public int scissorX, scissorY, scissorW, scissorH;
|
||||
public boolean scissorEnabled;
|
||||
public boolean rasterizerDiscard;
|
||||
public int activeTextureUnit;
|
||||
public int boundProgram;
|
||||
public int autoPushedProgram;
|
||||
@@ -327,6 +329,8 @@ public final class ContextState {
|
||||
|
||||
public int boundFboId;
|
||||
public int boundReadFboId;
|
||||
public int defaultFboId;
|
||||
public GLContextState mirroredContext;
|
||||
|
||||
public final LongOpenHashSet clearedTexturesThisFrame = new LongOpenHashSet();
|
||||
public final LongOpenHashSet clearedStencilTexturesThisFrame = new LongOpenHashSet();
|
||||
@@ -398,4 +402,50 @@ public final class ContextState {
|
||||
cachedScissor = SDL_Rect.calloc();
|
||||
cachedBlendColor = SDL_FColor.calloc();
|
||||
}
|
||||
|
||||
public void invalidateMirror() {
|
||||
boundFboId = -1;
|
||||
boundReadFboId = -1;
|
||||
viewportDirty = true;
|
||||
scissorDirty = true;
|
||||
blendColorDirty = true;
|
||||
lastBoundPipeline = 0;
|
||||
lastAppliedRenderPassGen = 0;
|
||||
lastAppliedStencilRef = Integer.MIN_VALUE;
|
||||
lastBoundEboHandle = 0;
|
||||
lastBoundEboIndexSize = -1;
|
||||
lastBoundEboOffset = 0;
|
||||
lastAppliedSamplerBindGen = -1;
|
||||
lastAppliedSamplerProgram = 0;
|
||||
lastAppliedSamplerCb = 0;
|
||||
lastFragSamplerProgram = 0;
|
||||
lastVertSamplerProgram = 0;
|
||||
lastAppliedStorageTexBindGen = -1;
|
||||
lastAppliedStorageTexProgram = 0;
|
||||
lastAppliedStorageTexCb = 0;
|
||||
lastFragStorageTexProgram = 0;
|
||||
lastVertStorageTexProgram = 0;
|
||||
lastAppliedStorageBufBindGen = -1;
|
||||
lastAppliedStorageBufProgram = 0;
|
||||
lastAppliedStorageBufCb = 0;
|
||||
lastFragStorageBufProgram = 0;
|
||||
lastVertStorageBufProgram = 0;
|
||||
lastAppliedVboBindGen = -1;
|
||||
lastAppliedVboBindProgram = 0;
|
||||
lastAppliedVboBindCb = 0;
|
||||
lastFlushedSamplerBindGen = -1;
|
||||
lastFlushedProgram = -1;
|
||||
lastFlushedPendingMutationGen = -1;
|
||||
lastPushedCbVs = 0;
|
||||
lastPushedCbFs = 0;
|
||||
lastPushedProgramVs = -1;
|
||||
lastPushedProgramFs = -1;
|
||||
lastPushedUboGenVs = -1;
|
||||
lastPushedUboGenFs = -1;
|
||||
pipeline.markInputDirty();
|
||||
pipeline.markOutputDirty();
|
||||
pipeline.markShaderDirty();
|
||||
currentVao.invalidateInputHash();
|
||||
needsSeed = true;
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ public final class OffscreenTarget {
|
||||
public void markContent() { hasContent = true; }
|
||||
|
||||
public boolean isFor(ContextState st) {
|
||||
return fboGlId != 0 && st.boundFboId == fboGlId;
|
||||
return fboGlId != 0 && st.defaultFboId == fboGlId;
|
||||
}
|
||||
|
||||
public void destroy(ResourceManager rm) {
|
||||
|
||||
+173
-73
@@ -1,10 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
@@ -23,7 +22,10 @@ import org.lwjgl.sdl.SDL_GPURasterizerState;
|
||||
import org.lwjgl.sdl.SDL_GPUVertexInputState;
|
||||
import org.lwjgl.sdl.SDL_GPUViewport;
|
||||
import org.lwjgl.sdl.SDL_Rect;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.taumc.glsl.Transformer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
@@ -38,15 +40,33 @@ public final class AttachmentClear {
|
||||
private static final String FRAGMENT_SOURCE = "angelica:sdlgpu/clear_attachment.fsh";
|
||||
private static final long KEY_SEED = 0x5B1D2A7C4E9F3061L;
|
||||
private static final int CACHE_SLOTS = 4;
|
||||
private static final int MAX_COLOR_TARGETS = ContextState.MAX_COLOR_ATTACHMENTS;
|
||||
|
||||
public static final int EMPTY = 0;
|
||||
public static final int PARTIAL = 1;
|
||||
public static final int FULL = 2;
|
||||
|
||||
public static final class Target {
|
||||
public int[] colorFormats;
|
||||
public int[] drawBuffers;
|
||||
public int depthFormat;
|
||||
public long depthTexture;
|
||||
public int width;
|
||||
public int height;
|
||||
public boolean flipY;
|
||||
}
|
||||
|
||||
private final FrameManager frameManager;
|
||||
private final PipelineStore pipelineStore;
|
||||
private final ShaderManager shaderManager;
|
||||
|
||||
private int program;
|
||||
private final Target fboTarget = new Target();
|
||||
private final Target fbo0Target = new Target();
|
||||
|
||||
private int vertexShaderId;
|
||||
private long sdlVertexShader;
|
||||
private long sdlFragmentShader;
|
||||
private boolean failed;
|
||||
private final long[] sdlFragmentShaders = new long[MAX_COLOR_TARGETS + 1];
|
||||
private final boolean[] fragmentFailed = new boolean[MAX_COLOR_TARGETS + 1];
|
||||
|
||||
private final long[] cacheKeys = new long[CACHE_SLOTS];
|
||||
private final long[] cachePipelines = new long[CACHE_SLOTS];
|
||||
@@ -55,12 +75,12 @@ public final class AttachmentClear {
|
||||
private final int[] scissorRect = new int[4];
|
||||
|
||||
private final Builder builder = new Builder();
|
||||
private int[] buildColorFormats;
|
||||
private int buildColorCount;
|
||||
private int buildDepthFormat;
|
||||
private Target buildTarget;
|
||||
private int buildColorTargets;
|
||||
private boolean buildDepthFlag;
|
||||
private boolean buildStencilFlag;
|
||||
private int buildWriteMask;
|
||||
private int buildStencilMask;
|
||||
private int buildColorMask;
|
||||
|
||||
public AttachmentClear(FrameManager frameManager, PipelineStore pipelineStore, ShaderManager shaderManager) {
|
||||
this.frameManager = frameManager;
|
||||
@@ -72,40 +92,69 @@ public final class AttachmentClear {
|
||||
@Override public long getAsLong() { return createPipeline(); }
|
||||
}
|
||||
|
||||
public boolean eligible(ContextState st, FrameState f, FboState fbo, boolean wantDepth, boolean wantStencil) {
|
||||
if (SystemProperties.SDL_DISABLE_IN_PASS_CLEAR) return false;
|
||||
if (st.boundFboId == 0) return false;
|
||||
if (f.renderPass == 0) return false;
|
||||
if (fbo.depthTexture == 0) return false;
|
||||
if (f.currentDepthTarget != fbo.depthTexture) return false;
|
||||
if (f.activeLayoutHash != fbo.structuralLayoutHash || f.currentColorTarget != fbo.primaryTarget) return false;
|
||||
if (FBOClearTracker.fboHasPendingClear(st, fbo)) return false;
|
||||
if (wantStencil && !PixelOps.isDepthStencilFormat(fbo.depthFormat)) return false;
|
||||
if (fbo.width <= 0 || fbo.height <= 0) return false;
|
||||
return computeScissor(st, fbo);
|
||||
public Target forFbo(FboState fbo) {
|
||||
final Target t = fboTarget;
|
||||
t.colorFormats = fbo.cachedColorFormats;
|
||||
t.drawBuffers = fbo.drawBuffers;
|
||||
t.depthFormat = fbo.depthTexture != 0 ? fbo.depthFormat : 0;
|
||||
t.depthTexture = fbo.depthTexture;
|
||||
t.width = fbo.width;
|
||||
t.height = fbo.height;
|
||||
t.flipY = false;
|
||||
return t;
|
||||
}
|
||||
|
||||
private boolean computeScissor(ContextState st, FboState fbo) {
|
||||
public Target forFbo0(int[] swapchainFormats, int w, int h) {
|
||||
final Target t = fbo0Target;
|
||||
t.colorFormats = swapchainFormats;
|
||||
t.drawBuffers = null;
|
||||
t.depthFormat = 0;
|
||||
t.depthTexture = 0;
|
||||
t.width = w;
|
||||
t.height = h;
|
||||
t.flipY = true;
|
||||
return t;
|
||||
}
|
||||
|
||||
static boolean enabled(Target t, int i) {
|
||||
return t.drawBuffers == null || t.drawBuffers[i] >= 0;
|
||||
}
|
||||
|
||||
public boolean hasIntegerColorTarget(Target t) {
|
||||
final int[] formats = t.colorFormats;
|
||||
if (formats == null) return false;
|
||||
for (int i = 0; i < formats.length; i++) {
|
||||
if (enabled(t, i) && PixelOps.isSdlFormatInteger(formats[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int coverage(ContextState st, Target t) {
|
||||
final int sx, sy, sw, sh;
|
||||
if (st.scissorEnabled) {
|
||||
sx = st.scissorX;
|
||||
sy = st.scissorY;
|
||||
sy = t.flipY ? t.height - st.scissorY - st.scissorH : st.scissorY;
|
||||
sw = st.scissorW;
|
||||
sh = st.scissorH;
|
||||
} else {
|
||||
sx = 0;
|
||||
sy = 0;
|
||||
sw = fbo.width;
|
||||
sh = fbo.height;
|
||||
sw = t.width;
|
||||
sh = t.height;
|
||||
}
|
||||
return ScissorClamp.clamp(sx, sy, sw, sh, fbo.width, fbo.height, scissorRect);
|
||||
if (!ScissorClamp.clamp(sx, sy, sw, sh, t.width, t.height, scissorRect)) return EMPTY;
|
||||
if (scissorRect[ScissorClamp.X] == 0 && scissorRect[ScissorClamp.Y] == 0
|
||||
&& scissorRect[ScissorClamp.W] == t.width && scissorRect[ScissorClamp.H] == t.height) return FULL;
|
||||
return PARTIAL;
|
||||
}
|
||||
|
||||
public boolean clearInPass(ContextState st, FrameState f, FboState fbo, boolean depth, boolean stencil) {
|
||||
final int writeMask = stencil ? (st.pipeline.stencilWriteMask & 0xFF) : 0;
|
||||
final long pipeline = getOrCreatePipeline(fbo, depth, stencil, writeMask);
|
||||
public boolean clearInPass(ContextState st, FrameState f, Target t, boolean color, boolean depth, boolean stencil) {
|
||||
final int colorTargets = color ? t.colorFormats.length : 0;
|
||||
final int stencilMask = stencil ? (st.pipeline.stencilWriteMask & 0xFF) : 0;
|
||||
final int colorMask = color ? st.pipeline.colorWriteMask : 0;
|
||||
final long pipeline = getOrCreatePipeline(t, colorTargets, depth, stencil, stencilMask, colorMask);
|
||||
if (pipeline == 0) return false;
|
||||
if (!computeScissor(st, fbo)) return false;
|
||||
if (coverage(st, t) == EMPTY) return false;
|
||||
|
||||
final long rp = f.renderPass;
|
||||
SDL_BindGPUGraphicsPipeline(rp, pipeline);
|
||||
@@ -114,8 +163,8 @@ public final class AttachmentClear {
|
||||
final long vpAddr = st.cachedViewport.address();
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.X, 0f);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.Y, 0f);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.W, fbo.width);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.H, fbo.height);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.W, t.width);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.H, t.height);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.MIN_DEPTH, clearDepth);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.MAX_DEPTH, clearDepth);
|
||||
SDL_SetGPUViewport(rp, st.cachedViewport);
|
||||
@@ -131,6 +180,18 @@ public final class AttachmentClear {
|
||||
SDL_SetGPUStencilReference(rp, (byte) st.stencilClearValue);
|
||||
}
|
||||
|
||||
if (colorTargets > 0) {
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final ByteBuffer clearColor = stack.malloc(16);
|
||||
clearColor.putFloat(0, st.clearR);
|
||||
clearColor.putFloat(4, st.clearG);
|
||||
clearColor.putFloat(8, st.clearB);
|
||||
clearColor.putFloat(12, st.clearA);
|
||||
SDL_PushGPUFragmentUniformData(frameManager.getCommandBuffer(f), 0, clearColor);
|
||||
}
|
||||
st.lastPushedProgramFs = -1;
|
||||
}
|
||||
|
||||
SDL_DrawGPUPrimitives(rp, 3, 1, 0, 0);
|
||||
|
||||
st.lastBoundPipeline = 0;
|
||||
@@ -139,34 +200,40 @@ public final class AttachmentClear {
|
||||
st.lastAppliedStencilRef = Integer.MIN_VALUE;
|
||||
st.lastAppliedVboBindCb = 0;
|
||||
|
||||
if (depth) st.clearedTexturesThisFrame.add(fbo.depthTexture);
|
||||
if (stencil) st.clearedStencilTexturesThisFrame.add(fbo.depthTexture);
|
||||
if (t.depthTexture != 0) {
|
||||
if (depth) st.clearedTexturesThisFrame.add(t.depthTexture);
|
||||
if (stencil) st.clearedStencilTexturesThisFrame.add(t.depthTexture);
|
||||
}
|
||||
|
||||
frameManager.noteInPassClear();
|
||||
return true;
|
||||
}
|
||||
|
||||
long getOrCreatePipeline(FboState fbo, boolean depthFlag, boolean stencilFlag, int writeMask) {
|
||||
if (!ensureShaders()) return 0;
|
||||
long getOrCreatePipeline(Target t, int colorTargets, boolean depth, boolean stencil, int stencilMask, int colorMask) {
|
||||
if (!ensureShaders(colorTargets)) return 0;
|
||||
|
||||
final int[] formats = fbo.cachedColorFormats;
|
||||
final int[] formats = t.colorFormats;
|
||||
final int count = formats != null ? formats.length : 0;
|
||||
long key = KEY_SEED;
|
||||
key = Hashing.fmix64(key, count);
|
||||
for (int i = 0; i < count; i++) key = Hashing.fmix64(key, formats[i]);
|
||||
key = Hashing.fmix64(key, fbo.depthFormat);
|
||||
key = Hashing.fmix64(key, (depthFlag ? 1 : 0) | ((stencilFlag ? 1 : 0) << 1) | (writeMask << 8));
|
||||
for (int i = 0; i < count; i++) {
|
||||
key = Hashing.fmix64(key, formats[i]);
|
||||
key = Hashing.fmix64(key, enabled(t, i) ? 1 : 0);
|
||||
}
|
||||
key = Hashing.fmix64(key, t.depthFormat);
|
||||
key = Hashing.fmix64(key, colorTargets);
|
||||
key = Hashing.fmix64(key, (depth ? 1 : 0) | ((stencil ? 1 : 0) << 1) | (stencilMask << 8) | (colorMask << 16));
|
||||
|
||||
for (int i = 0; i < CACHE_SLOTS; i++) {
|
||||
if (cacheKeys[i] == key && cachePipelines[i] != 0) return cachePipelines[i];
|
||||
}
|
||||
|
||||
buildColorFormats = formats;
|
||||
buildColorCount = count;
|
||||
buildDepthFormat = fbo.depthFormat;
|
||||
buildDepthFlag = depthFlag;
|
||||
buildStencilFlag = stencilFlag;
|
||||
buildWriteMask = writeMask;
|
||||
buildTarget = t;
|
||||
buildColorTargets = colorTargets;
|
||||
buildDepthFlag = depth;
|
||||
buildStencilFlag = stencil;
|
||||
buildStencilMask = stencilMask;
|
||||
buildColorMask = colorMask;
|
||||
final long pipeline = pipelineStore.getOrBuild(key, builder);
|
||||
if (pipeline != 0) {
|
||||
cacheKeys[cacheNext] = key;
|
||||
@@ -178,24 +245,27 @@ public final class AttachmentClear {
|
||||
|
||||
private long createPipeline() {
|
||||
try (var stack = stackPush()) {
|
||||
final int numColorTargets = buildColorCount;
|
||||
final Target t = buildTarget;
|
||||
final int numColorTargets = t.colorFormats != null ? t.colorFormats.length : 0;
|
||||
final SDL_GPUColorTargetDescription.Buffer colorDesc = SDL_GPUColorTargetDescription.calloc(numColorTargets, stack);
|
||||
for (int i = 0; i < numColorTargets; i++) {
|
||||
final var desc = colorDesc.get(i);
|
||||
desc.format(buildColorFormats[i]);
|
||||
desc.format(t.colorFormats[i]);
|
||||
final int mask = (buildColorTargets > 0 && enabled(t, i)) ? buildColorMask : 0;
|
||||
desc.blend_state()
|
||||
.enable_blend(false)
|
||||
.color_write_mask((byte) 0)
|
||||
.color_write_mask((byte) mask)
|
||||
.enable_color_write_mask(true);
|
||||
}
|
||||
|
||||
final boolean hasDepthTarget = t.depthFormat != 0;
|
||||
final SDL_GPUDepthStencilState depthStencil = SDL_GPUDepthStencilState.calloc(stack)
|
||||
.enable_depth_test(buildDepthFlag)
|
||||
.enable_depth_write(buildDepthFlag)
|
||||
.compare_op(SDL_GPU_COMPAREOP_ALWAYS)
|
||||
.enable_stencil_test(buildStencilFlag)
|
||||
.compare_mask((byte) 0xFF)
|
||||
.write_mask((byte) buildWriteMask);
|
||||
.write_mask((byte) buildStencilMask);
|
||||
if (buildStencilFlag) {
|
||||
depthStencil.front_stencil_state()
|
||||
.fail_op(SDL_GPU_STENCILOP_REPLACE)
|
||||
@@ -220,14 +290,14 @@ public final class AttachmentClear {
|
||||
final SDL_GPUGraphicsPipelineTargetInfo targetInfo = SDL_GPUGraphicsPipelineTargetInfo.calloc(stack)
|
||||
.num_color_targets(numColorTargets)
|
||||
.color_target_descriptions(colorDesc)
|
||||
.depth_stencil_format(buildDepthFormat)
|
||||
.has_depth_stencil_target(true);
|
||||
.depth_stencil_format(t.depthFormat)
|
||||
.has_depth_stencil_target(hasDepthTarget);
|
||||
|
||||
final SDL_GPUVertexInputState vertexInput = SDL_GPUVertexInputState.calloc(stack);
|
||||
|
||||
final SDL_GPUGraphicsPipelineCreateInfo ci = SDL_GPUGraphicsPipelineCreateInfo.calloc(stack)
|
||||
.vertex_shader(sdlVertexShader)
|
||||
.fragment_shader(sdlFragmentShader)
|
||||
.fragment_shader(sdlFragmentShaders[buildColorTargets])
|
||||
.primitive_type(SDL_GPU_PRIMITIVETYPE_TRIANGLELIST)
|
||||
.rasterizer_state(rasterizer)
|
||||
.multisample_state(multisample)
|
||||
@@ -244,48 +314,78 @@ public final class AttachmentClear {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureShaders() {
|
||||
if (program != 0) return true;
|
||||
if (failed) return false;
|
||||
failed = true;
|
||||
private boolean ensureShaders(int colorTargets) {
|
||||
if (colorTargets < 0 || colorTargets > MAX_COLOR_TARGETS) return false;
|
||||
if (sdlFragmentShaders[colorTargets] != 0) return true;
|
||||
if (fragmentFailed[colorTargets]) return false;
|
||||
fragmentFailed[colorTargets] = true;
|
||||
|
||||
if (vertexShaderId == 0) {
|
||||
vertexShaderId = shaderManager.createShader(GL20.GL_VERTEX_SHADER);
|
||||
shaderManager.shaderSource(vertexShaderId, ShaderLoader.getShaderSource(VERTEX_SOURCE));
|
||||
shaderManager.compileShader(vertexShaderId);
|
||||
}
|
||||
|
||||
final String baseSource = ShaderLoader.getShaderSource(FRAGMENT_SOURCE);
|
||||
final String fragmentSource = colorTargets == 0 ? baseSource : withColorOutputs(baseSource, colorTargets);
|
||||
|
||||
final int vs = shaderManager.createShader(GL20.GL_VERTEX_SHADER);
|
||||
shaderManager.shaderSource(vs, ShaderLoader.getShaderSource(VERTEX_SOURCE));
|
||||
shaderManager.compileShader(vs);
|
||||
final int fs = shaderManager.createShader(GL20.GL_FRAGMENT_SHADER);
|
||||
shaderManager.shaderSource(fs, ShaderLoader.getShaderSource(FRAGMENT_SOURCE));
|
||||
shaderManager.shaderSource(fs, fragmentSource);
|
||||
shaderManager.compileShader(fs);
|
||||
|
||||
final int prog = shaderManager.createProgram();
|
||||
shaderManager.attachShader(prog, vs);
|
||||
shaderManager.attachShader(prog, vertexShaderId);
|
||||
shaderManager.attachShader(prog, fs);
|
||||
shaderManager.linkProgram(prog);
|
||||
|
||||
final ShaderManager.ProgramObject po = shaderManager.getProgram(prog);
|
||||
if (po == null || !po.linked) {
|
||||
LOG.error("AttachmentClear: clear_attachment program failed to link: {}", po == null ? "no program object" : po.infoLog);
|
||||
LOG.error("AttachmentClear: clear_attachment program (COLOR_TARGETS={}) failed to link: {}", colorTargets, po == null ? "no program object" : po.infoLog);
|
||||
return false;
|
||||
}
|
||||
if (colorTargets > 0 && po.fragmentUboSize != 16) {
|
||||
LOG.error("AttachmentClear: clear_attachment fragment UBO size {} != 16 for COLOR_TARGETS={}", po.fragmentUboSize, colorTargets);
|
||||
return false;
|
||||
}
|
||||
|
||||
po.sdlVertexShader = shaderManager.createSDLShader(po.vertexSpirv, SDL_GPU_SHADERSTAGE_VERTEX, po.vertexResources.numSamplers(), po.vertexResources.numUBOs(), po.vertexResources.numStorageBuffers(), po.vertexResources.numStorageTextures());
|
||||
po.sdlFragmentShader = shaderManager.createSDLShader(po.fragmentSpirv, SDL_GPU_SHADERSTAGE_FRAGMENT, po.fragmentResources.numSamplers(), po.fragmentResources.numUBOs(), po.fragmentResources.numStorageBuffers(), po.fragmentResources.numStorageTextures());
|
||||
if (po.sdlVertexShader == 0 || po.sdlFragmentShader == 0) {
|
||||
LOG.error("AttachmentClear: clear_attachment SDL shader creation failed");
|
||||
if (sdlVertexShader == 0) {
|
||||
sdlVertexShader = shaderManager.createSDLShader(po.vertexSpirv, SDL_GPU_SHADERSTAGE_VERTEX, po.vertexResources.numSamplers(), po.vertexResources.numUBOs(), po.vertexResources.numStorageBuffers(), po.vertexResources.numStorageTextures());
|
||||
if (sdlVertexShader == 0) {
|
||||
LOG.error("AttachmentClear: clear_attachment vertex SDL shader creation failed");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final long fragmentShader = shaderManager.createSDLShader(po.fragmentSpirv, SDL_GPU_SHADERSTAGE_FRAGMENT, po.fragmentResources.numSamplers(), po.fragmentResources.numUBOs(), po.fragmentResources.numStorageBuffers(), po.fragmentResources.numStorageTextures());
|
||||
if (fragmentShader == 0) {
|
||||
LOG.error("AttachmentClear: clear_attachment fragment SDL shader creation failed (COLOR_TARGETS={})", colorTargets);
|
||||
return false;
|
||||
}
|
||||
|
||||
program = prog;
|
||||
sdlVertexShader = po.sdlVertexShader;
|
||||
sdlFragmentShader = po.sdlFragmentShader;
|
||||
failed = false;
|
||||
sdlFragmentShaders[colorTargets] = fragmentShader;
|
||||
fragmentFailed[colorTargets] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String withColorOutputs(String source, int count) {
|
||||
final GlslTransformUtils.QuietParse parsed = GlslTransformUtils.parseBothQuiet(source);
|
||||
final Transformer transformer = new Transformer(parsed.full());
|
||||
transformer.injectVariable("uniform vec4 clearColor;");
|
||||
for (int i = 0; i < count; i++) {
|
||||
transformer.injectVariable("layout(location = " + i + ") out vec4 angelica_ClearColor" + i + ";");
|
||||
transformer.appendMain("angelica_ClearColor" + i + " = clearColor;");
|
||||
}
|
||||
final String header = GlslTransformUtils.getFormattedShader(parsed.pre(), "").trim();
|
||||
final StringBuilder out = new StringBuilder();
|
||||
transformer.mutateTree(tree -> out.append(GlslTransformUtils.getFormattedShader(tree, header)));
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
program = 0;
|
||||
vertexShaderId = 0;
|
||||
sdlVertexShader = 0;
|
||||
sdlFragmentShader = 0;
|
||||
failed = false;
|
||||
Arrays.fill(sdlFragmentShaders, 0L);
|
||||
Arrays.fill(fragmentFailed, false);
|
||||
Arrays.fill(cacheKeys, 0L);
|
||||
Arrays.fill(cachePipelines, 0L);
|
||||
cacheNext = 0;
|
||||
|
||||
@@ -74,6 +74,14 @@ public final class PixelOps {
|
||||
};
|
||||
}
|
||||
|
||||
public static int colorComponentMask(int sdlFormat) {
|
||||
int mask = 0;
|
||||
for (int c = 0; c < 4; c++) {
|
||||
if (colorChannelBits(sdlFormat, c) > 0) mask |= 1 << c;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
private static int uniformChannelBits(int sdlFormat) {
|
||||
return switch (sdlFormat) {
|
||||
case SDL_GPU_TEXTUREFORMAT_R8_UNORM, SDL_GPU_TEXTUREFORMAT_R8_SNORM,
|
||||
@@ -174,6 +182,16 @@ public final class PixelOps {
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean isSdlFormatInteger(int sdlFormat) {
|
||||
if (isSdlFormatUnsignedInteger(sdlFormat)) return true;
|
||||
return switch (sdlFormat) {
|
||||
case SDL_GPU_TEXTUREFORMAT_R8_INT, SDL_GPU_TEXTUREFORMAT_R8G8_INT, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16_INT, SDL_GPU_TEXTUREFORMAT_R16G16_INT, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32_INT, SDL_GPU_TEXTUREFORMAT_R32G32_INT, SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
public static ByteBuffer expandToRGBA(int format, ByteBuffer src, int width, int height) {
|
||||
if (src == null) return null;
|
||||
final int pixelCount = width * height;
|
||||
|
||||
+212
@@ -10,9 +10,14 @@ import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -67,6 +72,12 @@ class GlsmSdlAttachmentClearTest {
|
||||
return r > 200 && g < 64;
|
||||
}
|
||||
|
||||
private static boolean isGreen(int argb) {
|
||||
final int r = (argb >> 16) & 0xFF;
|
||||
final int g = (argb >> 8) & 0xFF;
|
||||
return g > 200 && r < 64;
|
||||
}
|
||||
|
||||
private static boolean depthEquals(float depthValue) {
|
||||
GLStateManager.glEnable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDepthFunc(GL11.GL_EQUAL);
|
||||
@@ -358,4 +369,205 @@ class GlsmSdlAttachmentClearTest {
|
||||
assertTrue(Math.abs(g - 102) <= 2, "green must be untouched: " + g);
|
||||
assertTrue(Math.abs(b - 153) <= 2, "blue must be untouched: " + b);
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissoredColorAndDepthClearStaysInTheRect() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClearDepth(0.9);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 1.0f);
|
||||
|
||||
final FrameManager.FrameState f = frame();
|
||||
final long passBefore = f.renderPass;
|
||||
assertTrue(passBefore != 0, "expected an active render pass before the scissored clear");
|
||||
final int inPassBefore = f.inPassClearsThisFrame;
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, SIZE / 2, SIZE);
|
||||
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClearDepth(0.0);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
assertEquals(passBefore, f.renderPass, "the scissored clear must not break the active render pass");
|
||||
assertEquals(inPassBefore + 1, f.inPassClearsThisFrame, "the scissored clear must take the in-pass path");
|
||||
|
||||
final int probeSize = 4;
|
||||
final int leftProbeX = SIZE / 2 - probeSize * 2;
|
||||
final int rightProbeX = SIZE / 2 + probeSize;
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDepthFunc(GL11.GL_EQUAL);
|
||||
GLStateManager.glDepthMask(false);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(leftProbeX, 0, probeSize, SIZE);
|
||||
drawFullQuadAtZ(0.0f * 2.0f - 1.0f, 0.0f, 1.0f, 0.0f);
|
||||
GLStateManager.glScissor(rightProbeX, 0, probeSize, SIZE);
|
||||
drawFullQuadAtZ(0.9f * 2.0f - 1.0f, 0.0f, 1.0f, 0.0f);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 8, SIZE / 2)), "inside the scissor rect must be the clear color");
|
||||
assertFalse(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE - SIZE / 8, SIZE / 2)), "outside the scissor rect must keep the prior blue quad");
|
||||
assertTrue(isGreen(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, leftProbeX + probeSize / 2, SIZE / 2)), "inside the scissor rect the depth must have been cleared to 0");
|
||||
assertTrue(isGreen(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, rightProbeX + probeSize / 2, SIZE / 2)), "outside the scissor rect the prior depth must be untouched");
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissoredClearAfterPendingFullClear() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, SIZE / 2, SIZE);
|
||||
GLStateManager.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLStateManager.glScissor(0, 0, 0, SIZE);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertTrue(isGreen(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 4, SIZE / 2)), "the scissored clear must have painted the left half green");
|
||||
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2 + SIZE / 4, SIZE / 2)), "the empty-scissor clear must not have touched the right half");
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorMaskIsHonoredByClear() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.solidQuad(0.2f, 0.4f, 0.6f);
|
||||
|
||||
GLStateManager.glColorMask(true, false, false, true);
|
||||
GLStateManager.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
final int argb = GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
|
||||
final int r = (argb >> 16) & 0xFF;
|
||||
final int g = (argb >> 8) & 0xFF;
|
||||
final int b = argb & 0xFF;
|
||||
assertEquals(255, r, "red must be written: colorMask allowed it");
|
||||
assertTrue(Math.abs(g - 102) <= 2, "green must be untouched by the masked clear: " + g);
|
||||
assertTrue(Math.abs(b - 153) <= 2, "blue must be untouched by the masked clear: " + b);
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissoredClearWritesEveryDrawBuffer() {
|
||||
final int texA = createColorTexture();
|
||||
final int texB = createColorTexture();
|
||||
|
||||
final int mrtFbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, mrtFbo);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texA, 0);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT1, GL11.GL_TEXTURE_2D, texB, 0);
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
GLStateManager.glDrawBuffers(IntBuffer.wrap(new int[]{ GL30.GL_COLOR_ATTACHMENT0, GL30.GL_COLOR_ATTACHMENT1 }));
|
||||
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, SIZE / 2, SIZE);
|
||||
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
final int readFbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, readFbo);
|
||||
|
||||
assertLeftHalfRed(texA, "texture A");
|
||||
assertLeftHalfRed(texB, "texture B");
|
||||
|
||||
GLStateManager.glDeleteFramebuffers(readFbo);
|
||||
GLStateManager.glDeleteFramebuffers(mrtFbo);
|
||||
GLStateManager.glDeleteTextures(texA);
|
||||
GLStateManager.glDeleteTextures(texB);
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
}
|
||||
|
||||
private static int createColorTexture() {
|
||||
return createColorTexture(GL11.GL_RGBA8, GL11.GL_RGBA);
|
||||
}
|
||||
|
||||
private static int createColorTexture(int internalFormat, int format) {
|
||||
final int tex = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, internalFormat, SIZE, SIZE, 0, format, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
return tex;
|
||||
}
|
||||
|
||||
private static void assertLeftHalfRed(int texture, String label) {
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture, 0);
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 4, SIZE / 2)), label + " left half must be cleared red");
|
||||
assertFalse(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2 + SIZE / 4, SIZE / 2)), label + " right half must keep the base clear");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rasterizerDiscardSkipsClearsAndDraws() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
|
||||
GLStateManager.glEnable(GL30.GL_RASTERIZER_DISCARD);
|
||||
GLStateManager.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glDisable(GL30.GL_RASTERIZER_DISCARD);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2)), "rasterizer discard must skip both the clear and the draw");
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearIsIgnoredOutsideRenderMode() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
|
||||
final FloatBuffer feedbackBuffer = BufferUtils.createFloatBuffer(16);
|
||||
GLStateManager.glFeedbackBuffer(GL11.GL_3D, feedbackBuffer);
|
||||
GLStateManager.glRenderMode(GL11.GL_FEEDBACK);
|
||||
GLStateManager.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glRenderMode(GL11.GL_RENDER);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2)), "a clear issued outside GL_RENDER mode must be ignored");
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorMaskCoveringEveryPresentChannelIsAFullClear() {
|
||||
final int tex = createColorTexture(GL30.GL_R8, GL11.GL_RED);
|
||||
final int fbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
|
||||
GLStateManager.glClearColor(0.2f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.4f, 0.0f, 0.0f);
|
||||
|
||||
final FrameManager.FrameState f = frame();
|
||||
final int inPassBefore = f.inPassClearsThisFrame;
|
||||
|
||||
GLStateManager.glColorMask(true, false, false, false);
|
||||
GLStateManager.glClearColor(0.6f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
|
||||
assertEquals(inPassBefore, f.inPassClearsThisFrame, "masking every present channel on a single-red-channel target must take the load-op path, not in-pass");
|
||||
|
||||
GLStateManager.glDeleteFramebuffers(fbo);
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
}
|
||||
}
|
||||
+13
@@ -95,6 +95,19 @@ class GlsmSdlFbo0ViewportTest {
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, FBO0_H - VIEW, VIEW, VIEW), BLUE, "above the viewport");
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissoredClearFlipsYOnFbo0() {
|
||||
bindFbo0WithFullViewport();
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, FBO0_W, VIEW);
|
||||
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, 0, FBO0_W, VIEW), RED, "GL scissor y=0 is the bottom rows of the FBO0 texture");
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, FBO0_H - VIEW, FBO0_W, VIEW), BLUE, "the top rows must stay untouched by the flipped scissor");
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissorLargerThanFbo0StillCoversTheWholeTarget() {
|
||||
bindFbo0WithFullViewport();
|
||||
|
||||
@@ -47,7 +47,7 @@ class GlsmSdlFfpTest {
|
||||
|
||||
@AfterEach
|
||||
void cleanupInstancing() {
|
||||
GLStateManager.ffpInstancing = Instancing.NONE;
|
||||
GLStateManager.setFfpInstancing(Instancing.NONE);
|
||||
CubeParityFixture.deleteResources();
|
||||
ParticleParityFixture.deleteResources();
|
||||
FfpFixture.IntegerInstances.delete();
|
||||
|
||||
+1
-2
@@ -51,7 +51,6 @@ public final class GlsmSdlHeadlessRig {
|
||||
GLStateManager.initialize(GLSMInitConfig.builder()
|
||||
.displaySize(SIZE, SIZE)
|
||||
.directDrawer(TessellatorStreamingDrawer::drawDirect)
|
||||
.streamingDrawerDestroy(TessellatorStreamingDrawer::destroy)
|
||||
.build());
|
||||
GLStateManager.setRunningSplash(false);
|
||||
GLStateManager.markSplashComplete("glsmSdlHeadlessRig");
|
||||
@@ -92,7 +91,7 @@ public final class GlsmSdlHeadlessRig {
|
||||
return bound.get(target);
|
||||
}
|
||||
|
||||
private static int colorTargetSdlFormat() {
|
||||
static int colorTargetSdlFormat() {
|
||||
final ResourceManager rm = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
|
||||
final ResourceManager.TextureMeta meta = rm.getTextureMeta(colorTexture);
|
||||
return meta != null ? meta.sdlFormat() : 0;
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.OffscreenTarget;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.splash.SplashDispatcher;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlSplashTargetTest {
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
}
|
||||
|
||||
@Test
|
||||
void seededSplashContextDrawsIntoSplashTarget() throws Exception {
|
||||
final SDLGPURenderBackend backend = (SDLGPURenderBackend) BackendManager.RENDER_BACKEND;
|
||||
final OffscreenTarget target = createSplashTarget(backend);
|
||||
Reflect.set(backend, "splashTarget", target);
|
||||
try {
|
||||
TestThreads.run("SplashTarget-Seed-Thread", () -> {
|
||||
Reflect.invoke(backend, "enterContext", new Class<?>[]{boolean.class}, true);
|
||||
GlsmSdlHeadlessRig.solidQuad(0f, 1f, 0f);
|
||||
backend.handleSwapBuffers();
|
||||
backend.onRenderThreadReleased(Thread.currentThread());
|
||||
});
|
||||
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, target.fboId());
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(), 0xFF00FF00, "splash target");
|
||||
} finally {
|
||||
releaseSplashTarget(backend, target);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindZeroOnSplashThreadTargetsSplash() throws Exception {
|
||||
final SDLGPURenderBackend backend = (SDLGPURenderBackend) BackendManager.RENDER_BACKEND;
|
||||
final OffscreenTarget target = createSplashTarget(backend);
|
||||
Reflect.set(backend, "splashTarget", target);
|
||||
try {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0f, 0f, 1f, 1f);
|
||||
TestThreads.run("SplashTarget-BindZero-Thread", () -> {
|
||||
Reflect.invoke(backend, "enterContext", new Class<?>[]{boolean.class}, true);
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GlsmSdlHeadlessRig.clearTo(1f, 0f, 0f, 1f);
|
||||
GlsmSdlHeadlessRig.solidQuad(0f, 1f, 0f);
|
||||
backend.handleSwapBuffers();
|
||||
backend.onRenderThreadReleased(Thread.currentThread());
|
||||
});
|
||||
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, target.fboId());
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(), 0xFF00FF00, "splash target");
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(), 0xFF0000FF, "rig target");
|
||||
} finally {
|
||||
releaseSplashTarget(backend, target);
|
||||
}
|
||||
}
|
||||
|
||||
private static OffscreenTarget createSplashTarget(SDLGPURenderBackend backend) {
|
||||
final Device device = Reflect.get(backend, "device");
|
||||
final ResourceManager resourceManager = Reflect.get(backend, "resourceManager");
|
||||
final int sdlFormat = GlsmSdlHeadlessRig.colorTargetSdlFormat();
|
||||
final OffscreenTarget target = new OffscreenTarget();
|
||||
target.create(device, resourceManager, GlsmSdlHeadlessRig.SIZE, GlsmSdlHeadlessRig.SIZE, sdlFormat);
|
||||
return target;
|
||||
}
|
||||
|
||||
private static void releaseSplashTarget(SDLGPURenderBackend backend, OffscreenTarget target) {
|
||||
Reflect.set(backend, "splashTarget", null);
|
||||
SplashDispatcher.reset();
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
final ResourceManager resourceManager = Reflect.get(backend, "resourceManager");
|
||||
target.destroy(resourceManager);
|
||||
}
|
||||
}
|
||||
+13
-79
@@ -1,10 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
@@ -15,10 +11,8 @@ import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
|
||||
|
||||
class AttachmentClearTest {
|
||||
@@ -40,7 +34,6 @@ class AttachmentClearTest {
|
||||
rig = SdlTestRig.acquireRealDevice();
|
||||
rig.resourceManager.cachePreferredDepthFormats();
|
||||
sm = new ShaderManager(rig.device);
|
||||
VAOManager.init(0);
|
||||
store = new PipelineStore(rig.device);
|
||||
attachmentClear = new AttachmentClear(rig.frameManager, store, sm);
|
||||
|
||||
@@ -76,92 +69,33 @@ class AttachmentClearTest {
|
||||
return fbo;
|
||||
}
|
||||
|
||||
@Test
|
||||
void eligibleTruthTable() {
|
||||
final FboState fbo = makeFbo();
|
||||
final ContextState baseline = new ContextState();
|
||||
baseline.boundFboId = 1;
|
||||
final FrameManager.FrameState f = new FrameManager.FrameState();
|
||||
f.renderPass = 0xBEEFL;
|
||||
f.currentDepthTarget = fbo.depthTexture;
|
||||
f.currentColorTarget = fbo.primaryTarget;
|
||||
f.activeLayoutHash = fbo.structuralLayoutHash;
|
||||
assertTrue(attachmentClear.eligible(baseline, f, fbo, true, false), "baseline row must be eligible");
|
||||
|
||||
final ContextState noFbo = new ContextState();
|
||||
noFbo.boundFboId = 0;
|
||||
assertFalse(attachmentClear.eligible(noFbo, f, fbo, true, false), "boundFboId==0 must be ineligible");
|
||||
|
||||
final FrameManager.FrameState noPass = new FrameManager.FrameState();
|
||||
noPass.currentDepthTarget = fbo.depthTexture;
|
||||
noPass.currentColorTarget = fbo.primaryTarget;
|
||||
noPass.activeLayoutHash = fbo.structuralLayoutHash;
|
||||
assertFalse(attachmentClear.eligible(baseline, noPass, fbo, true, false), "renderPass==0 must be ineligible");
|
||||
|
||||
final FrameManager.FrameState wrongDepth = new FrameManager.FrameState();
|
||||
wrongDepth.renderPass = 0xBEEFL;
|
||||
wrongDepth.currentDepthTarget = fbo.depthTexture + 1;
|
||||
wrongDepth.currentColorTarget = fbo.primaryTarget;
|
||||
wrongDepth.activeLayoutHash = fbo.structuralLayoutHash;
|
||||
assertFalse(attachmentClear.eligible(baseline, wrongDepth, fbo, true, false), "depth target mismatch must be ineligible");
|
||||
|
||||
final FrameManager.FrameState wrongLayout = new FrameManager.FrameState();
|
||||
wrongLayout.renderPass = 0xBEEFL;
|
||||
wrongLayout.currentDepthTarget = fbo.depthTexture;
|
||||
wrongLayout.currentColorTarget = fbo.primaryTarget;
|
||||
wrongLayout.activeLayoutHash = fbo.structuralLayoutHash ^ 1L;
|
||||
assertFalse(attachmentClear.eligible(baseline, wrongLayout, fbo, true, false), "layout hash mismatch must be ineligible");
|
||||
|
||||
final FrameManager.FrameState wrongColor = new FrameManager.FrameState();
|
||||
wrongColor.renderPass = 0xBEEFL;
|
||||
wrongColor.currentDepthTarget = fbo.depthTexture;
|
||||
wrongColor.currentColorTarget = fbo.primaryTarget + 1;
|
||||
wrongColor.activeLayoutHash = fbo.structuralLayoutHash;
|
||||
assertFalse(attachmentClear.eligible(baseline, wrongColor, fbo, true, false), "color target mismatch must be ineligible");
|
||||
|
||||
final ContextState pending = new ContextState();
|
||||
pending.boundFboId = 1;
|
||||
FBOClearTracker.recordPendingDepthClear(pending, fbo.depthTexture, 1.0f);
|
||||
assertFalse(attachmentClear.eligible(pending, f, fbo, true, false), "a pending deferred clear must be ineligible");
|
||||
|
||||
final FboState depthOnlyFormat = makeFbo();
|
||||
depthOnlyFormat.depthFormat = SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
|
||||
assertFalse(attachmentClear.eligible(baseline, f, depthOnlyFormat, false, true), "stencil clear on a non depth-stencil format must be ineligible");
|
||||
assertTrue(attachmentClear.eligible(baseline, f, depthOnlyFormat, true, false), "depth clear on a depth-only format stays eligible");
|
||||
|
||||
final FboState zeroSize = makeFbo();
|
||||
zeroSize.width = 0;
|
||||
assertFalse(attachmentClear.eligible(baseline, f, zeroSize, true, false), "zero-size target must be ineligible");
|
||||
|
||||
final ContextState scissoredEmpty = new ContextState();
|
||||
scissoredEmpty.boundFboId = 1;
|
||||
scissoredEmpty.scissorEnabled = true;
|
||||
scissoredEmpty.scissorX = SIZE + 10;
|
||||
scissoredEmpty.scissorY = 0;
|
||||
scissoredEmpty.scissorW = 4;
|
||||
scissoredEmpty.scissorH = 4;
|
||||
assertFalse(attachmentClear.eligible(scissoredEmpty, f, fbo, true, false), "a scissor clamped to empty must be ineligible");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onePipelinePerLayoutModeAndMask() {
|
||||
final FboState fbo = makeFbo();
|
||||
final long p1 = attachmentClear.getOrCreatePipeline(fbo, true, false, 0);
|
||||
final long p1Again = attachmentClear.getOrCreatePipeline(fbo, true, false, 0);
|
||||
final long p1 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, true, false, 0, 0);
|
||||
final long p1Again = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, true, false, 0, 0);
|
||||
assertTrue(p1 != 0, "pipeline build must succeed");
|
||||
assertEquals(p1, p1Again, "the same (layout, mode, mask) must reuse the cached pipeline");
|
||||
|
||||
final long p2 = attachmentClear.getOrCreatePipeline(fbo, false, true, 0xFF);
|
||||
final long p2 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, false, true, 0xFF, 0);
|
||||
assertNotEquals(p1, p2, "a different clear mode must build a different pipeline");
|
||||
|
||||
final long p3 = attachmentClear.getOrCreatePipeline(fbo, false, true, 0x0F);
|
||||
final long p3 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, false, true, 0x0F, 0);
|
||||
assertNotEquals(p2, p3, "a different stencil write mask must build a different pipeline");
|
||||
|
||||
final FboState depthOnlyLayout = makeFbo();
|
||||
depthOnlyLayout.drawBuffers = new int[0];
|
||||
depthOnlyLayout.cachedColorFormats = new int[0];
|
||||
final long p4 = attachmentClear.getOrCreatePipeline(depthOnlyLayout, true, false, 0);
|
||||
final long p4 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(depthOnlyLayout), 0, true, false, 0, 0);
|
||||
assertTrue(p4 != 0, "a depth-only layout pipeline must still build");
|
||||
assertNotEquals(p1, p4, "a different color-target layout must build a different pipeline");
|
||||
|
||||
final long pc1 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 1, false, false, 0, 0xF);
|
||||
final long pc1Again = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 1, false, false, 0, 0xF);
|
||||
assertTrue(pc1 != 0, "color pipeline build must succeed");
|
||||
assertEquals(pc1, pc1Again, "the same color mask must reuse the cached pipeline");
|
||||
|
||||
final long pc2 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 1, false, false, 0, 0x9);
|
||||
assertNotEquals(pc1, pc2, "a different color write mask must build a different pipeline");
|
||||
}
|
||||
}
|
||||
-2
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -17,7 +16,6 @@ class PipelineCacheKeyTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
VAOManager.init(0);
|
||||
PipelineCache.setSwapchainFormats(new int[]{SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM});
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -1,7 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -31,7 +30,6 @@ class PipelineCacheVertexInputTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
VAOManager.init(0);
|
||||
PipelineCache.setSwapchainFormats(new int[]{SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM});
|
||||
store = new PipelineStore(null);
|
||||
}
|
||||
|
||||
-2
@@ -1,6 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
|
||||
@@ -69,7 +68,6 @@ class StencilMaskGpuTest {
|
||||
rig = SdlTestRig.acquireRealDevice();
|
||||
rig.resourceManager.cachePreferredDepthFormats();
|
||||
sm = new ShaderManager(rig.device);
|
||||
VAOManager.init(0);
|
||||
store = new PipelineStore(rig.device);
|
||||
|
||||
colorTexture = rig.resourceManager.createTexture(9500, GL11.GL_TEXTURE_2D, GL11.GL_RGBA8, SIZE, SIZE, 1, 1);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.api.clouds;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public record CloudLayer(
|
||||
String id,
|
||||
ResourceLocation texture,
|
||||
float height,
|
||||
float cellWidth,
|
||||
float cellHeight,
|
||||
float coordinateWidth,
|
||||
double offsetX,
|
||||
double offsetZ,
|
||||
float red,
|
||||
float green,
|
||||
float blue,
|
||||
float alpha) {
|
||||
|
||||
public CloudLayer {
|
||||
if (id == null || id.isBlank()) throw new IllegalArgumentException("Cloud layer id must not be empty");
|
||||
if (texture == null) throw new IllegalArgumentException("Cloud layer texture must not be null");
|
||||
if (!Float.isFinite(height)) throw new IllegalArgumentException("Cloud layer height must be finite");
|
||||
if (!Float.isFinite(cellWidth) || cellWidth <= 0
|
||||
|| !Float.isFinite(cellHeight) || cellHeight <= 0
|
||||
|| !Float.isFinite(coordinateWidth) || coordinateWidth <= 0) {
|
||||
throw new IllegalArgumentException("Cloud layer dimensions must be finite and positive");
|
||||
}
|
||||
if (!Double.isFinite(offsetX) || !Double.isFinite(offsetZ)) {
|
||||
throw new IllegalArgumentException("Cloud layer offsets must be finite");
|
||||
}
|
||||
if (!Float.isFinite(red) || red < 0 || !Float.isFinite(green) || green < 0
|
||||
|| !Float.isFinite(blue) || blue < 0) {
|
||||
throw new IllegalArgumentException("Cloud layer colors must be finite and nonnegative");
|
||||
}
|
||||
if (!Float.isFinite(alpha) || alpha < 0 || alpha > 1) {
|
||||
throw new IllegalArgumentException("Cloud layer alpha must be between zero and one");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.api.clouds;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
|
||||
public interface CloudLayerProvider {
|
||||
|
||||
List<CloudLayer> getCloudLayers(WorldClient world, int cloudTicks, float partialTicks);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
|
||||
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FFPVertexLighting;
|
||||
@@ -159,7 +160,6 @@ public class BatchingFontRenderer {
|
||||
|
||||
|
||||
// OpenGL objects (static, can be used between multiple BatchingFontRenderer)
|
||||
private static int fontVAO = 0;
|
||||
private static int vbo;
|
||||
private static IndexBuffer ebo;
|
||||
private static int lightmapSamplerProgram;
|
||||
@@ -169,6 +169,7 @@ public class BatchingFontRenderer {
|
||||
private static boolean streamingInitialized;
|
||||
private static int pendingBuffer;
|
||||
private static int pendingBase;
|
||||
private static GLContextState vaoContext;
|
||||
private static int vaoBuffer = -1;
|
||||
private static int vaoBase = -1;
|
||||
|
||||
@@ -219,7 +220,8 @@ public class BatchingFontRenderer {
|
||||
float v = 0.0f;
|
||||
int texture = 0;
|
||||
if (active) {
|
||||
scratchLightmapUv.set(GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY, 0.0f, 1.0f).mul(GLStateManager.getTextures().getTextureUnitMatrix(LIGHTMAP_TEX_UNIT));
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
scratchLightmapUv.set(glCtx.lastBrightnessX, glCtx.lastBrightnessY, 0.0f, 1.0f).mul(GLStateManager.getTextures().getTextureUnitMatrix(LIGHTMAP_TEX_UNIT));
|
||||
u = scratchLightmapUv.x;
|
||||
v = scratchLightmapUv.y;
|
||||
texture = unitBinding;
|
||||
@@ -582,6 +584,17 @@ public class BatchingFontRenderer {
|
||||
endBatch();
|
||||
}
|
||||
|
||||
public void resetAfterCrash() {
|
||||
discardDeferredText();
|
||||
batchDepth = 0;
|
||||
deferredCmdWatermark = 0;
|
||||
deferredVertexPos = 0;
|
||||
deferredIdxPos = 0;
|
||||
truncateBatchToWatermark();
|
||||
arenaOwner = null;
|
||||
flushLastTexture = null;
|
||||
}
|
||||
|
||||
private void deferBatch() {
|
||||
sealBatchSegment();
|
||||
if (batchSegments.isEmpty()) {
|
||||
@@ -609,11 +622,16 @@ public class BatchingFontRenderer {
|
||||
return;
|
||||
}
|
||||
|
||||
GLStateManager.beginForeignDraw();
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
flushDeferredTextInner();
|
||||
GLStateManager.beginForeignDraw();
|
||||
try {
|
||||
flushDeferredTextInner();
|
||||
} finally {
|
||||
GLStateManager.endForeignDraw();
|
||||
}
|
||||
} finally {
|
||||
GLStateManager.endForeignDraw();
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -848,8 +866,15 @@ public class BatchingFontRenderer {
|
||||
lightmapSamplerProgram = fontShaderId;
|
||||
}
|
||||
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (vaoContext != glCtx) {
|
||||
vaoContext = glCtx;
|
||||
vaoBuffer = -1;
|
||||
}
|
||||
int fontVAO = glCtx.fontVao;
|
||||
if (fontVAO == 0) {
|
||||
fontVAO = GLStateManager.glGenVertexArrays();
|
||||
glCtx.fontVao = fontVAO;
|
||||
|
||||
GLStateManager.glBindVertexArray(fontVAO);
|
||||
ebo.bind();
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.debug.ChunkDebugMinimap;
|
||||
import com.gtnewhorizons.angelica.debug.flyby.FlybyRoute;
|
||||
import com.gtnewhorizons.angelica.debug.flyby.FlybyRunner;
|
||||
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderRecovery;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -23,7 +24,19 @@ import java.util.List;
|
||||
|
||||
public class AngelicaCommand extends CommandBase {
|
||||
|
||||
private static final List<String> SUBCOMMANDS = Arrays.asList("wireframe", "fog", "minimap", "flyby", "profile", "help");
|
||||
private static final List<String> SUBCOMMANDS = buildSubcommands();
|
||||
|
||||
private static List<String> buildSubcommands() {
|
||||
final List<String> subcommands = new ArrayList<>(Arrays.asList("wireframe", "fog", "minimap", "flyby", "profile", "help"));
|
||||
if (isDeobfuscatedEnvironment()) {
|
||||
subcommands.add("crashtest");
|
||||
}
|
||||
return subcommands;
|
||||
}
|
||||
|
||||
private static boolean isDeobfuscatedEnvironment() {
|
||||
return SystemProperties.isDeobf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
@@ -32,7 +45,7 @@ public class AngelicaCommand extends CommandBase {
|
||||
|
||||
@Override
|
||||
public String getCommandUsage(ICommandSender sender) {
|
||||
return "/angelica <wireframe|fog|minimap|flyby|profile|help>";
|
||||
return "/angelica <wireframe|fog|minimap|flyby|profile" + (isDeobfuscatedEnvironment() ? "|crashtest" : "") + "|help>";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,6 +108,13 @@ public class AngelicaCommand extends CommandBase {
|
||||
case "minimap" -> handleMinimap(sender);
|
||||
case "flyby" -> handleFlyby(sender, args);
|
||||
case "profile" -> handleProfile(sender, args);
|
||||
case "crashtest" -> {
|
||||
if (isDeobfuscatedEnvironment()) {
|
||||
handleCrashTest(sender);
|
||||
} else {
|
||||
sendHelp(sender);
|
||||
}
|
||||
}
|
||||
default -> sendHelp(sender);
|
||||
}
|
||||
}
|
||||
@@ -119,6 +139,11 @@ public class AngelicaCommand extends CommandBase {
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Chunk debug minimap: " + state));
|
||||
}
|
||||
|
||||
private void handleCrashTest(ICommandSender sender) {
|
||||
RenderRecovery.armCrashTest();
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Crash test armed: fires on the next tile entity render"));
|
||||
}
|
||||
|
||||
private void handleFlyby(ICommandSender sender, String[] args) {
|
||||
if (args.length >= 2 && "cancel".equalsIgnoreCase(args[1])) {
|
||||
FlybyRunner.INSTANCE.cancel();
|
||||
@@ -221,6 +246,9 @@ public class AngelicaCommand extends CommandBase {
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica minimap" + EnumChatFormatting.WHITE + " - Toggle chunk debug overlay"));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica flyby <" + FlybyRoute.ids() + ">" + EnumChatFormatting.WHITE + " - Run a deterministic benchmark route"));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica profile <start|stop|status>" + EnumChatFormatting.WHITE + " - Control async-profiler (JFR) recording"));
|
||||
if (isDeobfuscatedEnvironment()) {
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica crashtest" + EnumChatFormatting.WHITE + " - Arm a crash on the next tile entity render"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-3
@@ -1,8 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.compat.bettercrashes;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IRenderGlobalExt;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderRecovery;
|
||||
import cpw.mods.fml.common.Optional;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
@@ -29,7 +29,6 @@ public class BetterCrashesCompat implements StateManager.IResettable {
|
||||
Tessellator.instance.isDrawing = false;
|
||||
AngelicaMod.LOGGER.info("Reloading SodiumRenderer");
|
||||
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).angelica$reload();
|
||||
AngelicaMod.LOGGER.info("Resetting GLStateManager");
|
||||
GLStateManager.reset();
|
||||
RenderRecovery.resetAfterCrash();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.compat.dragonapi;
|
||||
|
||||
public final class DragonAPICompat {
|
||||
|
||||
private DragonAPICompat() {}
|
||||
|
||||
public static boolean forcingSeasonalSnow;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.gtnewhorizons.angelica.compat.galaxyspace;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.clouds.CloudLayer;
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.client.IRenderHandler;
|
||||
|
||||
// Compat should live here, not porting all of this to Galaxy Space :trolley:
|
||||
public final class GalaxySpaceClouds {
|
||||
|
||||
private static final String VENUS = "galaxyspace.SolarSystem.planets.venus.dimension.WorldProviderVenus";
|
||||
private static final String BARNARDA_C = "galaxyspace.BarnardsSystem.planets.barnardaC.dimension.WorldProviderBarnardaC";
|
||||
private static final String TCETI_E = "galaxyspace.TCetiSystem.planets.tcetiE.dimension.WorldProviderTCetiE";
|
||||
|
||||
private static final String VENUS_RENDERER =
|
||||
"galaxyspace.SolarSystem.planets.venus.dimension.sky.CloudProviderVenus";
|
||||
private static final String BARNARDA_C_RENDERER =
|
||||
"galaxyspace.BarnardsSystem.planets.barnardaC.dimension.sky.CloudProviderBarnardaC";
|
||||
private static final String TCETI_E_RENDERER =
|
||||
"galaxyspace.TCetiSystem.planets.tcetiE.dimension.sky.CloudProviderTCetiE";
|
||||
|
||||
private static final ResourceLocation CLOUD_TEXTURE = new ResourceLocation("textures/environment/clouds.png");
|
||||
private static final double Z_PHASE = 0.33000001311302185;
|
||||
|
||||
private GalaxySpaceClouds() {}
|
||||
|
||||
public static boolean supports(WorldClient world, IRenderHandler renderer) {
|
||||
if (world == null || world.provider == null) return false;
|
||||
String expected = switch (world.provider.getClass().getName()) {
|
||||
case VENUS -> VENUS_RENDERER;
|
||||
case BARNARDA_C -> BARNARDA_C_RENDERER;
|
||||
case TCETI_E -> TCETI_E_RENDERER;
|
||||
default -> null;
|
||||
};
|
||||
return expected != null && (renderer == null || expected.equals(renderer.getClass().getName()));
|
||||
}
|
||||
|
||||
public static List<CloudLayer> getLayers(WorldClient world, int cloudTicks, float partialTicks) {
|
||||
String provider = world.provider.getClass().getName();
|
||||
if (VENUS.equals(provider)) {
|
||||
return fourLayers("galaxyspace:venus", world.provider.getCloudHeight(), 2.0f, cloudTicks, partialTicks);
|
||||
}
|
||||
if (BARNARDA_C.equals(provider)) {
|
||||
return fourLayers("galaxyspace:barnarda_c", world.provider.getCloudHeight(), 5.0f, cloudTicks, partialTicks);
|
||||
}
|
||||
if (TCETI_E.equals(provider)) {
|
||||
return List.of(new CloudLayer("galaxyspace:tceti_e/0", CLOUD_TEXTURE, world.provider.getCloudHeight(),
|
||||
12.0f, 4.0f, 12.0f, offsetX(cloudTicks, partialTicks, 0), Z_PHASE,
|
||||
230.0f / 1200.0f, 230.0f / 1200.0f, 230.0f / 1200.0f, 0.9f));
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private static List<CloudLayer> fourLayers(String id, float baseHeight, float widthStep,
|
||||
int cloudTicks, float partialTicks) {
|
||||
List<CloudLayer> layers = new ArrayList<>(4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float divisor = 1200.0f + 300.0f * i;
|
||||
layers.add(new CloudLayer(id + "/" + i, CLOUD_TEXTURE, baseHeight + 20.0f * i,
|
||||
12.0f + widthStep * i, 4.0f, 12.0f, offsetX(cloudTicks, partialTicks, i), Z_PHASE,
|
||||
234.0f / divisor, 147.0f / divisor, 9.0f / divisor, 0.9f));
|
||||
}
|
||||
return layers;
|
||||
}
|
||||
|
||||
private static double offsetX(int cloudTicks, float partialTicks, int layer) {
|
||||
final double layerFraction = layer / 5.0;
|
||||
return (cloudTicks * (1.0 - layerFraction) + layerFraction * partialTicks + layer * 250000.0) * 0.03 / 12.0;
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,10 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableVBOClouds;
|
||||
|
||||
@Config.Comment("Replace rain/snow rendering with a cached, instanced version.")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean enableInstancedWeather;
|
||||
|
||||
@Config.Comment("Uses cached attributes for VBO rendering, resulting in less CPU overhead. Disable if you notice any graphical issues.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
|
||||
@@ -22,6 +22,7 @@ import net.minecraft.util.ChunkCoordinates;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.world.MinecraftException;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.WorldServer;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.IChunkProvider;
|
||||
@@ -215,6 +216,8 @@ public final class FlybyRunner {
|
||||
final EntityClientPlayerMP player = mc.thePlayer;
|
||||
if (player == null || mc.theWorld == null) return;
|
||||
|
||||
pinClientWeather(mc.theWorld);
|
||||
|
||||
switch (this.state) {
|
||||
case WAITING -> {
|
||||
if (!this.armed) {
|
||||
@@ -569,6 +572,13 @@ public final class FlybyRunner {
|
||||
server.getConfigurationManager().sendChatMsg(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Flyby: world saving is off until you leave this world"));
|
||||
}
|
||||
|
||||
|
||||
private static void pinClientWeather(World world) {
|
||||
final SystemProperties.FlybyWeather weather = SystemProperties.FLYBY_WEATHER;
|
||||
world.rainingStrength = world.prevRainingStrength = weather.isRaining() ? 1.0f : 0.0f;
|
||||
world.thunderingStrength = world.prevThunderingStrength = weather.isThundering() ? 1.0f : 0.0f;
|
||||
}
|
||||
|
||||
private void applyFreeze(MinecraftServer server) {
|
||||
for (WorldServer world : server.worldServers) {
|
||||
if (world == null) continue;
|
||||
@@ -577,13 +587,17 @@ public final class FlybyRunner {
|
||||
world.getGameRules().setOrCreateGameRule("doDaylightCycle", "false");
|
||||
LOGGER.warn("Flyby: doDaylightCycle was on, disabled it");
|
||||
}
|
||||
if (world.getWorldInfo().isRaining() || world.getWorldInfo().isThundering()) {
|
||||
LOGGER.warn("Flyby: weather was active, clearing it");
|
||||
world.getWorldInfo().setRaining(false);
|
||||
world.getWorldInfo().setThundering(false);
|
||||
final SystemProperties.FlybyWeather weather = SystemProperties.FLYBY_WEATHER;
|
||||
if (world.getWorldInfo().isRaining() != weather.isRaining()
|
||||
|| world.getWorldInfo().isThundering() != weather.isThundering()) {
|
||||
LOGGER.warn("Flyby: forcing weather to {}", weather);
|
||||
world.getWorldInfo().setRaining(weather.isRaining());
|
||||
world.getWorldInfo().setThundering(weather.isThundering());
|
||||
}
|
||||
world.getWorldInfo().setRainTime(Integer.MAX_VALUE);
|
||||
world.getWorldInfo().setThunderTime(Integer.MAX_VALUE);
|
||||
world.rainingStrength = world.prevRainingStrength = weather.isRaining() ? 1.0f : 0.0f;
|
||||
world.thunderingStrength = world.prevThunderingStrength = weather.isThundering() ? 1.0f : 0.0f;
|
||||
|
||||
if (world.getWorldTime() % 24000L != SystemProperties.FLYBY_TIME_OF_DAY) {
|
||||
LOGGER.info("Flyby: freezing time at {}", SystemProperties.FLYBY_TIME_OF_DAY);
|
||||
|
||||
@@ -65,6 +65,12 @@ public class HUDCaching {
|
||||
framebuffer = new SharedDepthFramebuffer(CustomFramebuffer.STENCIL_BUFFER);
|
||||
}
|
||||
|
||||
public static void resetAfterCrash() {
|
||||
renderingCacheOverride = false;
|
||||
GLSMConfig.hudCacheOverride = false;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public static void renderCachedHud(EntityRenderer renderer, GuiIngame ingame, float partialTicks, boolean hasScreen, int mouseX, int mouseY) {
|
||||
ClientEvent.post(ClientEventType.PRE_RENDER_GUI, renderer, partialTicks);
|
||||
ClientEvent.post(ClientEventType.PRE_RENDER_HUD, renderer, partialTicks);
|
||||
@@ -99,10 +105,13 @@ public class HUDCaching {
|
||||
}
|
||||
renderingCacheOverride = true;
|
||||
GLSMConfig.hudCacheOverride = true;
|
||||
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
|
||||
renderingCacheOverride = false;
|
||||
GLSMConfig.hudCacheOverride = false;
|
||||
mc.getFramebuffer().bindFramebuffer(false);
|
||||
try {
|
||||
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
|
||||
} finally {
|
||||
renderingCacheOverride = false;
|
||||
GLSMConfig.hudCacheOverride = false;
|
||||
mc.getFramebuffer().bindFramebuffer(false);
|
||||
}
|
||||
} else {
|
||||
renderer.setupOverlayRendering();
|
||||
}
|
||||
|
||||
@@ -71,6 +71,13 @@ public enum Mixins implements IMixins {
|
||||
)
|
||||
),
|
||||
|
||||
ANGELICA_WEATHER(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
.addExcludedMod(TargetedMod.DYNAMIC_SURROUNDINGS_MIST)
|
||||
.addExcludedMod(TargetedMod.DYNAMIC_SURROUNDINGS_ORIGINAL)
|
||||
.addClientMixins("angelica.rendering.MixinEntityRenderer_Weather")
|
||||
),
|
||||
|
||||
STARMINER_RENDERER_LIVING_ENTITY_OPTIMIZATION(new MixinBuilder()
|
||||
.setPhase(Phase.LATE)
|
||||
.addRequiredMod(TargetedMod.STARMINER)
|
||||
@@ -505,6 +512,14 @@ public enum Mixins implements IMixins {
|
||||
)
|
||||
),
|
||||
|
||||
DRAGONAPI_SEASONAL_SNOW(new MixinBuilder("Let DragonAPI's Xmas forced-snow event override weather rendering")
|
||||
.setPhase(Phase.EARLY)
|
||||
.addRequiredMod(TargetedMod.DRAGON_API)
|
||||
.addClientMixins(
|
||||
"dragonapi.MixinSpecialDayTracker"
|
||||
)
|
||||
),
|
||||
|
||||
IRIS_RENDERING_NOBACKHAND(new MixinBuilder("Iris Hand Shaders")
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> AngelicaConfig.enableIris)
|
||||
@@ -665,6 +680,7 @@ public enum Mixins implements IMixins {
|
||||
"gui.MixinGuiSlot",
|
||||
|
||||
"glint.MixinRenderBiped",
|
||||
"glint.MixinEntityLiving_ArmorEnchantCache",
|
||||
"glint.MixinRenderPlayer",
|
||||
|
||||
"optimization.MixinRenderItemFrame",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Whether a mob's worn armor piece is enchanted, cached per stack instance.
|
||||
*/
|
||||
public interface ArmorEnchantCache {
|
||||
boolean angelica$isArmorEnchanted(int slot, ItemStack stack);
|
||||
}
|
||||
@@ -10,4 +10,7 @@ public interface EntityRendererAccessor {
|
||||
* Get the lightmap texture for shader binding.
|
||||
*/
|
||||
DynamicTexture getLightmapTexture();
|
||||
|
||||
/** Drives the rain and snow texture scroll. */
|
||||
int getRendererUpdateCount();
|
||||
}
|
||||
+3
@@ -2,10 +2,13 @@ package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkTaskOutput;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
public interface RenderSectionManagerAccessor {
|
||||
Long2ReferenceMap<RenderSection> angelica$getSectionByPosition();
|
||||
ConcurrentLinkedDeque<Runnable> angelica$getAsyncSubmittedTasks();
|
||||
ConcurrentLinkedDeque<ChunkJobResult<? extends ChunkTaskOutput>> angelica$getBuildResults();
|
||||
}
|
||||
@@ -91,6 +91,11 @@ public final class TracyFramePlots {
|
||||
private static final PlotDelta P_TESR_CACHE_HITS = new PlotDelta("tesr.cacheHits");
|
||||
private static final PlotDelta P_TESR_CACHE_MISSES = new PlotDelta("tesr.cacheMisses");
|
||||
private static final PlotDelta P_TESR_MODEL_PARTS = new PlotDelta("tesr.modelParts");
|
||||
private static final PlotDelta P_ARMOR_BASE_REUSES = new PlotDelta("glint.armorBaseReuses");
|
||||
private static final PlotDelta P_GLINT_PASS_REUSES = new PlotDelta("glint.passReuses");
|
||||
private static final PlotDelta P_ARMOR_LOOP_SKIPS = new PlotDelta("glint.armorLoopSkips");
|
||||
private static final PlotDelta P_ARMOR_SECTION_SKIPS = new PlotDelta("glint.armorSectionSkips");
|
||||
private static final PlotDelta P_REDRAWN_INSTANCES = new PlotDelta("glint.redrawnInstances");
|
||||
private static final PlotDelta P_TESR_LIVE_FALLBACKS = new PlotDelta("tesr.liveFallbacks");
|
||||
private static final PlotDelta P_ITEMS_INSTANCED = new PlotDelta("items.instanced");
|
||||
private static final PlotDelta P_ITEMS_FALLBACK = new PlotDelta("items.fallback");
|
||||
@@ -239,6 +244,11 @@ public final class TracyFramePlots {
|
||||
P_TESR_CACHE_HITS.plot(AngelicaTesrMeshCache.cacheHits);
|
||||
P_TESR_CACHE_MISSES.plot(AngelicaTesrMeshCache.cacheMisses);
|
||||
P_TESR_MODEL_PARTS.plot(batcher.statParts());
|
||||
P_ARMOR_BASE_REUSES.plot(batcher.statArmorBaseReuses());
|
||||
P_GLINT_PASS_REUSES.plot(batcher.statGlintPassReuses());
|
||||
P_ARMOR_LOOP_SKIPS.plot(batcher.statArmorLoopSkips());
|
||||
P_ARMOR_SECTION_SKIPS.plot(batcher.statArmorSectionSkips());
|
||||
P_REDRAWN_INSTANCES.plot(batcher.statRedrawnInstances());
|
||||
P_TESR_LIVE_FALLBACKS.plot(batcher.statLiveFallbacks());
|
||||
P_ITEMS_INSTANCED.plot(DroppedItemInstancer.statInstanced());
|
||||
P_ITEMS_FALLBACK.plot(DroppedItemInstancer.statFallback());
|
||||
|
||||
@@ -243,8 +243,7 @@ public final class ClientProxy extends CommonProxy {
|
||||
|
||||
// Register all blocks. Because blockids are unique to a world, this must be done each load
|
||||
GameData.getBlockRegistry().typeSafeIterable().forEach(o -> {
|
||||
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o, true, true);
|
||||
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o, false, true);
|
||||
AngelicaBlockSafetyRegistry.canBlockRenderOffThread(o);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
/**
|
||||
* The shape of the drawn disc, how it splits into wedges, and how far out each kind of geometry should
|
||||
* really be worth building.
|
||||
@@ -49,6 +51,29 @@ final class CloudDisc {
|
||||
return (displayHeight * 0.5) / Math.tan(Math.toRadians(fovDegrees) * 0.5);
|
||||
}
|
||||
|
||||
static double pixelsPerRadian(int displayWidth, int displayHeight, Matrix4fc projection) {
|
||||
if (displayWidth <= 0 || displayHeight <= 0 || projection == null) return 0.0;
|
||||
final float perspectiveW = projection.m23();
|
||||
if (!Float.isFinite(perspectiveW) || perspectiveW >= 0.0f || projection.m33() != 0.0f) return 0.0;
|
||||
|
||||
final double scale = -0.5 / perspectiveW;
|
||||
final double a = displayWidth * projection.m00() * scale;
|
||||
final double b = displayWidth * projection.m10() * scale;
|
||||
final double c = displayHeight * projection.m01() * scale;
|
||||
final double d = displayHeight * projection.m11() * scale;
|
||||
if (!Double.isFinite(a) || !Double.isFinite(b) || !Double.isFinite(c) || !Double.isFinite(d)) {
|
||||
return 0.0;
|
||||
}
|
||||
if (b == 0.0 && c == 0.0) return Math.max(Math.abs(a), Math.abs(d));
|
||||
final double column0 = a * a + c * c;
|
||||
final double column1 = b * b + d * d;
|
||||
final double cross = a * b + c * d;
|
||||
final double largest = 0.5 * (column0 + column1
|
||||
+ Math.hypot(column0 - column1, 2.0 * cross));
|
||||
final double pixels = Math.sqrt(largest);
|
||||
return Double.isFinite(pixels) ? pixels : 0.0;
|
||||
}
|
||||
|
||||
static int wallCutCells(double pixelsPerRadian, boolean platesInFront) {
|
||||
if (!platesInFront || pixelsPerRadian <= 0.0) return Integer.MAX_VALUE;
|
||||
|
||||
|
||||
@@ -73,6 +73,27 @@ final class CloudFaceMesh {
|
||||
interiorCount = 0;
|
||||
}
|
||||
|
||||
void delete() {
|
||||
if (vao != -1) GLStateManager.glDeleteVertexArrays(vao);
|
||||
if (vbo != -1) GLStateManager.glDeleteBuffers(vbo);
|
||||
if (ebo != -1) GLStateManager.glDeleteBuffers(ebo);
|
||||
vao = -1;
|
||||
vbo = -1;
|
||||
ebo = -1;
|
||||
vboCapacityBytes = 0;
|
||||
clear();
|
||||
stampedCount = 0;
|
||||
ringsPerBucket = 1;
|
||||
buildAnchorX = Integer.MIN_VALUE;
|
||||
buildAnchorZ = Integer.MIN_VALUE;
|
||||
orderAnchorX = Integer.MIN_VALUE;
|
||||
orderAnchorZ = Integer.MIN_VALUE;
|
||||
shape = null;
|
||||
uploadBuffer = null;
|
||||
Arrays.fill(wallStart, 0);
|
||||
Arrays.fill(plateStart, 0);
|
||||
}
|
||||
|
||||
void build(CloudShape shape, int anchorX, int anchorZ, int radiusCells, int radiusCellsSq, int wallCutCells) {
|
||||
stampedCount = 0;
|
||||
this.shape = shape;
|
||||
|
||||
@@ -0,0 +1,713 @@
|
||||
/*
|
||||
* Backported from Minecraft Forge 1.12 under the LGPL 2.1
|
||||
*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.clouds.CloudLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fStack;
|
||||
import org.joml.Vector3d;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.CELLS_PER_CHUNK;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.MARGIN_CELLS;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.WEDGE_COUNT;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.WEDGES_PER_RADIAN;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.textureOffset;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.withinMargin;
|
||||
|
||||
/**
|
||||
* Draws a cloud layer.
|
||||
*
|
||||
* <h2>Per frame, in {@link #render}</h2>
|
||||
* <ol>
|
||||
* <li>Reduce the texture to cells, plate rectangles and wall runs into {@link CloudShape}.
|
||||
* <li>Convert the camera position to cell space: the anchor cell, the fraction across it, and how far the
|
||||
* cloud base sits above the camera.
|
||||
* <li>Work out which faces the view needs: the underside, the top surface, or both when the camera is
|
||||
* inside the deck.
|
||||
* <li>Get the two detail distances from {@link CloudDisc}: where walls stop being built, and where
|
||||
* {@link CloudPlateCover} switches to coarse plate rectangles.
|
||||
* <li>Choose the mesher: {@link CloudFaceMesh} inside the deck, {@link CloudVertexMesh} elsewhere and
|
||||
* always for shader packs.
|
||||
* <li>Pick the matching program depending on whether every solid texel is plain white.
|
||||
* <li>Rebuild when the cache is stale. {@link CloudVertexMesh} builds on a worker thread.
|
||||
* <li>Reorder near-to-far on every cell crossed, and keep the cell the camera sits in up to date.
|
||||
* <li>Upload matrices, fog and the texture scroll into {@link CloudUniforms}, then
|
||||
* {@link #drawVisibleWedges}.
|
||||
* </ol>
|
||||
*/
|
||||
final class CloudLayerRenderer {
|
||||
|
||||
static final int MODE_FAST = 1;
|
||||
static final int MODE_FANCY = 2;
|
||||
private static final int UPLOAD_SLICE_BYTES = 256 * 1024;
|
||||
|
||||
private static final Tracy.ZoneId Z_RENDER = Tracy.zoneId("cloudRender", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_SUBMIT = Tracy.zoneId("cloudSubmit", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_DRAW = Tracy.zoneId("cloudDraw", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_ASYNC_BUILD = Tracy.zoneId("cloudAsyncBuild", Tracy.COLOR_CLIENT);
|
||||
private static final Executor BUILD_THREAD = Executors.newSingleThreadExecutor(task -> {
|
||||
final Thread thread = new Thread(task, "Angelica Cloud Builder");
|
||||
thread.setDaemon(true);
|
||||
thread.setPriority(Thread.NORM_PRIORITY - 1);
|
||||
return thread;
|
||||
});
|
||||
private final Minecraft mc = Minecraft.getMinecraft();
|
||||
private final CloudRenderResources resources;
|
||||
final CloudRenderResources.Texture texture;
|
||||
CloudLayer layer;
|
||||
private final CloudFaceMesh faceMesh = new CloudFaceMesh();
|
||||
private CloudVertexMesh vertexMesh = new CloudVertexMesh();
|
||||
private CloudVertexMesh spareMesh = new CloudVertexMesh();
|
||||
private final CloudVertexMesh interiorMesh = new CloudVertexMesh();
|
||||
private int interiorAnchorX = Integer.MIN_VALUE, interiorAnchorZ = Integer.MIN_VALUE;
|
||||
private int interiorShapeGeneration = Integer.MIN_VALUE;
|
||||
private float interiorCellHeight = Float.NaN;
|
||||
private boolean interiorShadersActive, interiorActive;
|
||||
private float interiorDriftX, interiorDriftZ;
|
||||
private CompletableFuture<Void> buildInFlight;
|
||||
private CompletableFuture<Void> reorderInFlight;
|
||||
private CloudVertexMesh reorderMesh;
|
||||
private BuildParams buildParams;
|
||||
private BuildParams uploadParams;
|
||||
private final Matrix4f mvpScratch = new Matrix4f();
|
||||
private final Matrix4f mvpInteriorScratch = new Matrix4f();
|
||||
private final Matrix4f modelViewScratch = new Matrix4f();
|
||||
private CloudShape shape;
|
||||
private int shapeGeneration;
|
||||
private boolean faceMeshActive;
|
||||
private int anchorCellX = Integer.MIN_VALUE, anchorCellZ = Integer.MIN_VALUE;
|
||||
private int cachedShapeGeneration = Integer.MIN_VALUE;
|
||||
private boolean cachedEmitUnderside, cachedEmitTopSurface;
|
||||
private int cachedRenderRadiusChunks = -1;
|
||||
private float cachedCellHeightBlocks = Float.NaN;
|
||||
private boolean cachedShadersActive;
|
||||
private int cachedWallCutCells = -1;
|
||||
private int cachedPlateLodCells = -1;
|
||||
private boolean geometryBuilt;
|
||||
private int wallCutCells = Integer.MAX_VALUE;
|
||||
private int plateLodCells = Integer.MAX_VALUE;
|
||||
private CloudView cloudView;
|
||||
private int cloudMode = -1, renderDistance = -1, scaleMult = -1;
|
||||
private float alpha;
|
||||
private double driftCells;
|
||||
private double cullingHeightCells;
|
||||
private int arcFrom, arcTo;
|
||||
private int drawCalls;
|
||||
private GlProgram<CloudUniforms> program;
|
||||
|
||||
CloudLayerRenderer(CloudRenderResources resources, CloudRenderResources.Texture texture) {
|
||||
this.resources = resources;
|
||||
this.texture = texture;
|
||||
}
|
||||
|
||||
void configure(CloudLayer layer, int mode, int distance, int scale) {
|
||||
this.layer = layer;
|
||||
if (cloudMode != mode || renderDistance != distance || scaleMult != scale) {
|
||||
invalidateGeometry();
|
||||
}
|
||||
cloudMode = mode;
|
||||
renderDistance = distance;
|
||||
scaleMult = scale;
|
||||
alpha = layer.alpha();
|
||||
}
|
||||
|
||||
private void invalidateGeometry() {
|
||||
anchorCellX = Integer.MIN_VALUE;
|
||||
anchorCellZ = Integer.MIN_VALUE;
|
||||
cachedRenderRadiusChunks = -1;
|
||||
cachedShapeGeneration = Integer.MIN_VALUE;
|
||||
cachedCellHeightBlocks = Float.NaN;
|
||||
cancelAsyncBuild();
|
||||
vertexMesh.clear();
|
||||
spareMesh.clear();
|
||||
interiorMesh.clear();
|
||||
interiorAnchorX = Integer.MIN_VALUE;
|
||||
interiorAnchorZ = Integer.MIN_VALUE;
|
||||
interiorActive = false;
|
||||
faceMesh.clear();
|
||||
geometryBuilt = false;
|
||||
}
|
||||
|
||||
private boolean untextured() {
|
||||
return cloudMode == MODE_FANCY && shape != null && shape.opaqueTexelsAllWhite;
|
||||
}
|
||||
|
||||
private void uploadFogUniforms(CloudUniforms uniforms, float fogStart, float fogEnd) {
|
||||
final boolean fogEnabled = GLStateManager.getFogMode().isEnabled();
|
||||
uniforms.setFogEnabled(fogEnabled);
|
||||
if (!fogEnabled) return;
|
||||
final FogState fog = GLStateManager.getFogState();
|
||||
final Vector3d color = fog.getFogColor();
|
||||
uniforms.setFogColor((float) color.x, (float) color.y, (float) color.z);
|
||||
switch (fog.getFogMode()) {
|
||||
case GL11.GL_LINEAR -> {
|
||||
final float range = fogEnd - fogStart;
|
||||
uniforms.setFogParams(range != 0.0f ? -1.0f / range : 0.0f, range != 0.0f ? fogEnd / range : 1.0f, 0.0f, 0.0f);
|
||||
}
|
||||
case GL11.GL_EXP -> uniforms.setFogParams(0.0f, 0.0f, fog.getDensity(), 1.0f);
|
||||
default -> uniforms.setFogParams(0.0f, 0.0f, fog.getDensity(), 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
boolean render(float partialTicks, CloudView cloudView) {
|
||||
this.cloudView = cloudView;
|
||||
if (!Tracy.ENABLED) return renderInner(partialTicks);
|
||||
Tracy.beginZone(Z_RENDER);
|
||||
try {
|
||||
return renderInner(partialTicks);
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean renderInner(float partialTicks) {
|
||||
texture.bind();
|
||||
shape = texture.shape;
|
||||
shapeGeneration = texture.generation;
|
||||
if (shape == null) return false;
|
||||
|
||||
final Entity viewEntity = mc.renderViewEntity;
|
||||
final float cellWidthBlocks = layer.cellWidth() * scaleMult;
|
||||
final float cellHeightBlocks = layer.cellHeight() * scaleMult;
|
||||
final float coordinateWidth = layer.coordinateWidth() * scaleMult;
|
||||
|
||||
final float cameraY = (float) (viewEntity.lastTickPosY + (viewEntity.posY - viewEntity.lastTickPosY) * partialTicks + cloudView.offsetY);
|
||||
final double cameraCellX = CloudDisc.cellCoordinate(viewEntity.prevPosX + (viewEntity.posX - viewEntity.prevPosX) * partialTicks + cloudView.offsetX, coordinateWidth, layer.offsetX());
|
||||
final double cameraCellZ = CloudDisc.cellCoordinate(viewEntity.prevPosZ + (viewEntity.posZ - viewEntity.prevPosZ) * partialTicks + cloudView.offsetZ, coordinateWidth, layer.offsetZ());
|
||||
|
||||
final float cloudBaseRelativeY = layer.height() - cameraY + 0.33F;
|
||||
final float cellFractionX = (float) (cameraCellX - MathHelper.floor_double(cameraCellX));
|
||||
final float cellFractionZ = (float) (cameraCellZ - MathHelper.floor_double(cameraCellZ));
|
||||
|
||||
final int anchorX = MathHelper.floor_double(cameraCellX);
|
||||
final int anchorZ = MathHelper.floor_double(cameraCellZ);
|
||||
|
||||
final boolean emitUnderside = cloudBaseRelativeY + cellHeightBlocks >= 0.0F;
|
||||
final boolean emitTopSurface = cloudBaseRelativeY <= 0.0F;
|
||||
final boolean insideDeck = emitUnderside && emitTopSurface;
|
||||
|
||||
final int renderRadiusChunks = (int) Math.ceil(renderDistance * 64.0f / (CELLS_PER_CHUNK * cellWidthBlocks));
|
||||
|
||||
final boolean shadersActive = IrisApi.getInstance().isShaderPackInUse();
|
||||
final boolean backFaceCulling = cullBackFaces();
|
||||
|
||||
final double pixelsPerRadian = cloudView.pixelsPerRadian;
|
||||
wallCutCells = CloudDisc.wallCutCells(pixelsPerRadian, !(backFaceCulling && insideDeck));
|
||||
plateLodCells = CloudDisc.plateLodCells(pixelsPerRadian, cloudBaseRelativeY, cellWidthBlocks,
|
||||
shape.coarsePlateRects != null && shape.opaqueTexelsAllWhite);
|
||||
|
||||
final int marginChunks = (MARGIN_CELLS + CELLS_PER_CHUNK - 1) / CELLS_PER_CHUNK;
|
||||
final int radiusChunks = renderRadiusChunks + marginChunks;
|
||||
final int radiusCells = radiusChunks * CELLS_PER_CHUNK;
|
||||
faceMeshActive = cloudMode == MODE_FANCY && !shadersActive && insideDeck && radiusCells <= CloudFaceMesh.CELL_LIMIT;
|
||||
|
||||
if (!shadersActive) program = resources.program(faceMeshActive, untextured());
|
||||
|
||||
final boolean anchorInRange = geometryBuilt
|
||||
&& withinMargin(anchorX - anchorCellX, anchorZ - anchorCellZ);
|
||||
final boolean settingsUnchanged = geometryBuilt
|
||||
&& cachedShapeGeneration == shapeGeneration
|
||||
&& cachedEmitUnderside == emitUnderside
|
||||
&& cachedEmitTopSurface == emitTopSurface
|
||||
&& cachedRenderRadiusChunks == renderRadiusChunks
|
||||
&& cachedCellHeightBlocks == cellHeightBlocks
|
||||
&& cachedShadersActive == shadersActive
|
||||
&& cachedWallCutCells == wallCutCells
|
||||
&& (faceMeshActive || cachedPlateLodCells == plateLodCells);
|
||||
final boolean geomCacheValid = anchorInRange && settingsUnchanged;
|
||||
|
||||
final boolean canBuildAsync = settingsUnchanged && !faceMeshActive && vertexMesh.built() && CloudDisc.withinDisc((long) anchorX - anchorCellX, (long) anchorZ - anchorCellZ, radiusCells);
|
||||
|
||||
final boolean builderBusy = buildInFlight != null || uploadParams != null;
|
||||
if (!geomCacheValid && !(canBuildAsync && builderBusy)) {
|
||||
final BuildParams params = captureBuildParams(anchorX, anchorZ, radiusCells, renderRadiusChunks,
|
||||
cellHeightBlocks, emitUnderside, emitTopSurface, shadersActive, backFaceCulling);
|
||||
if (canBuildAsync) {
|
||||
startAsyncBuild(params);
|
||||
} else {
|
||||
cancelAsyncBuild();
|
||||
if (faceMeshActive) {
|
||||
faceMesh.build(shape, anchorX, anchorZ, radiusCells, radiusCells * radiusCells, wallCutCells);
|
||||
} else {
|
||||
buildVertexMesh(vertexMesh, params);
|
||||
vertexMesh.uploadBuilt();
|
||||
}
|
||||
markGeometryCached(params);
|
||||
}
|
||||
}
|
||||
|
||||
collectFinishedBuild();
|
||||
|
||||
final boolean wantInterior = insideDeck && cloudMode == MODE_FANCY && !faceMeshActive;
|
||||
if (wantInterior && (interiorAnchorX != anchorX || interiorAnchorZ != anchorZ
|
||||
|| interiorShapeGeneration != shapeGeneration || interiorCellHeight != cellHeightBlocks
|
||||
|| interiorShadersActive != shadersActive)) {
|
||||
interiorMesh.buildInterior(shape, anchorX, anchorZ, cellHeightBlocks, textureOffset(anchorX, shape.width), textureOffset(anchorZ, shape.height), untextured() && !shadersActive, shadersActive);
|
||||
interiorMesh.uploadBuilt();
|
||||
interiorAnchorX = anchorX;
|
||||
interiorAnchorZ = anchorZ;
|
||||
interiorShapeGeneration = shapeGeneration;
|
||||
interiorCellHeight = cellHeightBlocks;
|
||||
interiorShadersActive = shadersActive;
|
||||
}
|
||||
interiorActive = wantInterior && interiorMesh.built();
|
||||
|
||||
if (faceMeshActive) {
|
||||
if (faceMesh.orderStale(anchorX, anchorZ)) faceMesh.reorder(anchorX, anchorZ);
|
||||
if (faceMesh.uploadedCount() == 0 && faceMesh.interiorCount() == 0) return true;
|
||||
} else if (!vertexMesh.built()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!faceMeshActive && !shadersActive && cloudMode == MODE_FANCY) {
|
||||
if (reorderInFlight != null && reorderInFlight.isDone()) {
|
||||
final boolean usable = !reorderInFlight.isCompletedExceptionally() && reorderMesh == vertexMesh;
|
||||
reorderInFlight = null;
|
||||
reorderMesh = null;
|
||||
if (usable) vertexMesh.reorderUpload();
|
||||
else vertexMesh.discardPendingReorder();
|
||||
}
|
||||
if (reorderInFlight == null && vertexMesh.orderStale(anchorX, anchorZ)) {
|
||||
final CloudVertexMesh target = vertexMesh;
|
||||
reorderMesh = target;
|
||||
reorderInFlight = CompletableFuture.runAsync(() -> target.reorder(anchorX, anchorZ), BUILD_THREAD);
|
||||
}
|
||||
}
|
||||
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_SUBMIT);
|
||||
float r = layer.red();
|
||||
float g = layer.green();
|
||||
float b = layer.blue();
|
||||
if (mc.gameSettings.anaglyph) {
|
||||
final float tempR = (r * 30.0F + g * 59.0F + b * 11.0F) / 100.0F;
|
||||
final float tempG = (r * 30.0F + g * 70.0F) / 100.0F;
|
||||
final float tempB = (r * 30.0F + b * 70.0F) / 100.0F;
|
||||
r = tempR;
|
||||
g = tempG;
|
||||
b = tempB;
|
||||
}
|
||||
|
||||
final float origFogStart = GLStateManager.getFogState().getStart();
|
||||
final float origFogEnd = GLStateManager.getFogState().getEnd();
|
||||
final float cloudExtentBlocks = renderRadiusChunks * CELLS_PER_CHUNK * cellWidthBlocks;
|
||||
final boolean stretchFog = GLStateManager.getFogMode().isEnabled()
|
||||
&& GLStateManager.getFogState().getFogMode() == GL11.GL_LINEAR
|
||||
&& origFogEnd > 0.0f
|
||||
&& origFogEnd < cloudExtentBlocks;
|
||||
final float fogStart = stretchFog ? origFogStart * (cloudExtentBlocks / origFogEnd) : origFogStart;
|
||||
final float fogEnd = stretchFog ? cloudExtentBlocks : origFogEnd;
|
||||
|
||||
final boolean pushFog = stretchFog && shadersActive;
|
||||
if (pushFog) {
|
||||
GLStateManager.glFogf(GL11.GL_FOG_START, fogStart);
|
||||
GLStateManager.glFogf(GL11.GL_FOG_END, fogEnd);
|
||||
}
|
||||
|
||||
final float anchorDriftX = (float) (anchorX - anchorCellX);
|
||||
final float anchorDriftZ = (float) (anchorZ - anchorCellZ);
|
||||
// Face wedges are re-bucketed around the current cell; vertex wedges keep their build anchor.
|
||||
final double cameraDriftX = (faceMeshActive ? 0 : anchorDriftX) + cellFractionX;
|
||||
final double cameraDriftZ = (faceMeshActive ? 0 : anchorDriftZ) + cellFractionZ;
|
||||
driftCells = Math.hypot(cameraDriftX, cameraDriftZ);
|
||||
cullingHeightCells = Math.max(Math.abs(cloudBaseRelativeY), Math.abs(cloudBaseRelativeY + cellHeightBlocks)) / cellWidthBlocks;
|
||||
|
||||
interiorDriftX = anchorDriftX;
|
||||
interiorDriftZ = anchorDriftZ;
|
||||
|
||||
final float entityToCameraX = cloudView.offsetX / cellWidthBlocks;
|
||||
final float entityToCameraZ = cloudView.offsetZ / cellWidthBlocks;
|
||||
final float deckBaseY = cloudBaseRelativeY + cloudView.offsetY;
|
||||
|
||||
if (!shadersActive) {
|
||||
program.bind();
|
||||
final CloudUniforms uniforms = program.getInterface();
|
||||
final Matrix4fStack modelView = GLStateManager.getModelViewMatrix();
|
||||
modelView.pushMatrix();
|
||||
modelView.scale(cellWidthBlocks, 1.0f, cellWidthBlocks);
|
||||
if (interiorActive) {
|
||||
modelView.pushMatrix();
|
||||
modelView.translate(entityToCameraX - cellFractionX, deckBaseY, entityToCameraZ - cellFractionZ);
|
||||
GLStateManager.getProjectionMatrix().mul(modelView, mvpInteriorScratch);
|
||||
modelView.popMatrix();
|
||||
}
|
||||
modelView.translate(entityToCameraX - (cellFractionX + anchorDriftX), deckBaseY, entityToCameraZ - (cellFractionZ + anchorDriftZ));
|
||||
modelViewScratch.set(modelView);
|
||||
GLStateManager.getProjectionMatrix().mul(modelView, mvpScratch);
|
||||
modelView.popMatrix();
|
||||
uniforms.mvp.set(mvpScratch);
|
||||
uniforms.modelView.set(modelViewScratch);
|
||||
if (faceMeshActive) {
|
||||
uniforms.setCellHeight(cellHeightBlocks);
|
||||
uniforms.setScroll(textureOffset(faceMesh.buildAnchorX(), shape.width), textureOffset(faceMesh.buildAnchorZ(), shape.height), 1.0f / shape.width, 1.0f / shape.height);
|
||||
}
|
||||
uploadFogUniforms(uniforms, fogStart, fogEnd);
|
||||
drawClouds(r, g, b, false);
|
||||
program.unbind();
|
||||
} else {
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glScalef(cellWidthBlocks, 1.0f, cellWidthBlocks);
|
||||
GLStateManager.glTranslatef(entityToCameraX - (cellFractionX + anchorDriftX), deckBaseY, entityToCameraZ - (cellFractionZ + anchorDriftZ));
|
||||
drawClouds(r, g, b, true);
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
if (pushFog) {
|
||||
GLStateManager.glFogf(GL11.GL_FOG_START, origFogStart);
|
||||
GLStateManager.glFogf(GL11.GL_FOG_END, origFogEnd);
|
||||
}
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
return true;
|
||||
}
|
||||
|
||||
private record BuildParams(CloudShape shape, boolean fancy, int anchorX, int anchorZ, int radiusCells,
|
||||
float cellHeightBlocks, boolean emitUnderside, boolean emitTopSurface, boolean emitPlates,
|
||||
boolean shadersActive, int wallCutCells, int plateLodCells, boolean untextured,
|
||||
int shapeGeneration, int renderRadiusChunks) {}
|
||||
|
||||
private BuildParams captureBuildParams(int anchorX, int anchorZ, int radiusCells, int renderRadiusChunks,
|
||||
float cellHeightBlocks, boolean emitUnderside, boolean emitTopSurface,
|
||||
boolean shadersActive, boolean backFaceCulling) {
|
||||
return new BuildParams(shape, cloudMode == MODE_FANCY, anchorX, anchorZ, radiusCells, cellHeightBlocks,
|
||||
emitUnderside, emitTopSurface, !(backFaceCulling && emitUnderside && emitTopSurface), shadersActive,
|
||||
wallCutCells, plateLodCells, untextured() && !shadersActive, shapeGeneration, renderRadiusChunks);
|
||||
}
|
||||
|
||||
private static void buildVertexMesh(CloudVertexMesh mesh, BuildParams p) {
|
||||
final float scrollX = textureOffset(p.anchorX(), p.shape().width);
|
||||
final float scrollZ = textureOffset(p.anchorZ(), p.shape().height);
|
||||
|
||||
if (!p.fancy()) {
|
||||
mesh.buildFast(p.shape(), p.radiusCells(), scrollX, scrollZ, p.shadersActive());
|
||||
return;
|
||||
}
|
||||
|
||||
mesh.build(p.shape(), p.anchorX(), p.anchorZ(), p.radiusCells(), p.radiusCells() * p.radiusCells(),
|
||||
p.cellHeightBlocks(), scrollX, scrollZ, p.emitUnderside(), p.emitTopSurface(), p.emitPlates(),
|
||||
p.wallCutCells(), p.plateLodCells(), p.untextured(), p.shadersActive());
|
||||
}
|
||||
|
||||
private void markGeometryCached(BuildParams p) {
|
||||
anchorCellX = p.anchorX();
|
||||
anchorCellZ = p.anchorZ();
|
||||
cachedShapeGeneration = p.shapeGeneration();
|
||||
cachedEmitUnderside = p.emitUnderside();
|
||||
cachedEmitTopSurface = p.emitTopSurface();
|
||||
cachedRenderRadiusChunks = p.renderRadiusChunks();
|
||||
cachedCellHeightBlocks = p.cellHeightBlocks();
|
||||
cachedShadersActive = p.shadersActive();
|
||||
cachedWallCutCells = p.wallCutCells();
|
||||
cachedPlateLodCells = p.plateLodCells();
|
||||
geometryBuilt = true;
|
||||
}
|
||||
|
||||
private void startAsyncBuild(BuildParams p) {
|
||||
if (buildInFlight != null || uploadParams != null) return;
|
||||
final CloudVertexMesh target = spareMesh;
|
||||
buildParams = p;
|
||||
buildInFlight = CompletableFuture.runAsync(() -> {
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_ASYNC_BUILD);
|
||||
try {
|
||||
buildVertexMesh(target, p);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
}, BUILD_THREAD);
|
||||
}
|
||||
|
||||
private void collectFinishedBuild() {
|
||||
if (buildInFlight != null) {
|
||||
if (!buildInFlight.isDone()) return;
|
||||
|
||||
final boolean usable = !buildInFlight.isCompletedExceptionally()
|
||||
&& buildParams.shapeGeneration() == shapeGeneration;
|
||||
buildInFlight = null;
|
||||
if (!usable) {
|
||||
spareMesh.discardPendingUpload();
|
||||
buildParams = null;
|
||||
return;
|
||||
}
|
||||
uploadParams = buildParams;
|
||||
buildParams = null;
|
||||
if (!spareMesh.beginUpload()) {
|
||||
swapMeshes();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (uploadParams == null) return;
|
||||
if (!spareMesh.uploadSlice(UPLOAD_SLICE_BYTES)) return;
|
||||
swapMeshes();
|
||||
}
|
||||
|
||||
private void swapMeshes() {
|
||||
discardReorder();
|
||||
final CloudVertexMesh finished = spareMesh;
|
||||
spareMesh = vertexMesh;
|
||||
vertexMesh = finished;
|
||||
markGeometryCached(uploadParams);
|
||||
uploadParams = null;
|
||||
}
|
||||
|
||||
private void discardReorder() {
|
||||
if (reorderInFlight != null) {
|
||||
reorderInFlight.join();
|
||||
reorderInFlight = null;
|
||||
}
|
||||
if (reorderMesh != null) {
|
||||
reorderMesh.discardPendingReorder();
|
||||
reorderMesh = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelAsyncBuild() {
|
||||
discardReorder();
|
||||
if (buildInFlight != null) {
|
||||
buildInFlight.join();
|
||||
buildInFlight = null;
|
||||
}
|
||||
buildParams = null;
|
||||
uploadParams = null;
|
||||
spareMesh.discardPendingUpload();
|
||||
}
|
||||
|
||||
private void drawClouds(float r, float g, float b, boolean shadersActive) {
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_DRAW);
|
||||
try {
|
||||
drawCloudsInner(r, g, b, shadersActive);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCloudsInner(float r, float g, float b, boolean shadersActive) {
|
||||
drawCalls = 0;
|
||||
|
||||
if (faceMeshActive) {
|
||||
drawFaces(r, g, b);
|
||||
} else {
|
||||
drawVertices(r, g, b, shadersActive);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawFaces(float r, float g, float b) {
|
||||
GLStateManager.enableCull();
|
||||
applyAnaglyphColorMask();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glDepthMask(true);
|
||||
|
||||
program.getInterface().setColorMult(r, g, b, alpha);
|
||||
|
||||
faceMesh.bind();
|
||||
final int interiorFaces = faceMesh.interiorCount();
|
||||
if (interiorFaces > 0) {
|
||||
faceMesh.drawInterior();
|
||||
drawCalls++;
|
||||
}
|
||||
drawVisibleWedges(faceMesh.plateStarts(), faceMesh.wallStarts(), faceMesh.uploadedCount(), true);
|
||||
faceMesh.unbind();
|
||||
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
private void drawVertices(float r, float g, float b, boolean depthPrepass) {
|
||||
vertexMesh.bind();
|
||||
final int totalQuads = vertexMesh.quadCount();
|
||||
|
||||
if (cullBackFaces()) {
|
||||
GLStateManager.enableCull();
|
||||
} else {
|
||||
GLStateManager.disableCull();
|
||||
}
|
||||
|
||||
if (depthPrepass) {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glColorMask(false, false, false, false);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, alpha);
|
||||
vertexMesh.drawAll();
|
||||
drawCalls++;
|
||||
if (interiorActive) {
|
||||
vertexMesh.unbind();
|
||||
pushInteriorTransform(true);
|
||||
interiorMesh.bind();
|
||||
interiorMesh.drawAll();
|
||||
interiorMesh.unbind();
|
||||
popInteriorTransform(true);
|
||||
vertexMesh.bind();
|
||||
drawCalls++;
|
||||
}
|
||||
}
|
||||
|
||||
applyAnaglyphColorMask();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glDepthMask(!depthPrepass);
|
||||
|
||||
if (depthPrepass) {
|
||||
for (int face = 0; face < 4; face++) {
|
||||
if (vertexMesh.faceEmpty(face)) continue;
|
||||
final float shade = CloudVertexMesh.FACE_SHADE[face];
|
||||
GLStateManager.glColor4f(shade * r, shade * g, shade * b, alpha);
|
||||
vertexMesh.drawFace(face);
|
||||
drawCalls++;
|
||||
}
|
||||
} else {
|
||||
program.getInterface().setColorMult(r, g, b, alpha);
|
||||
if (cloudMode == MODE_FANCY && vertexMesh.orderPublished()) {
|
||||
drawVisibleWedges(vertexMesh.plateStarts(), vertexMesh.wallStarts(), totalQuads, false);
|
||||
} else {
|
||||
drawRange(0, totalQuads, false);
|
||||
}
|
||||
}
|
||||
|
||||
vertexMesh.unbind();
|
||||
|
||||
if (interiorActive) {
|
||||
pushInteriorTransform(depthPrepass);
|
||||
interiorMesh.bind();
|
||||
if (depthPrepass) {
|
||||
for (int face = 0; face < 4; face++) {
|
||||
if (interiorMesh.faceEmpty(face)) continue;
|
||||
final float shade = CloudVertexMesh.FACE_SHADE[face];
|
||||
GLStateManager.glColor4f(shade * r, shade * g, shade * b, alpha);
|
||||
interiorMesh.drawFace(face);
|
||||
drawCalls++;
|
||||
}
|
||||
} else {
|
||||
drawCalls++;
|
||||
interiorMesh.drawRange(0, interiorMesh.quadCount());
|
||||
}
|
||||
interiorMesh.unbind();
|
||||
popInteriorTransform(depthPrepass);
|
||||
}
|
||||
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
private void pushInteriorTransform(boolean shadersActive) {
|
||||
if (shadersActive) {
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glTranslatef(interiorDriftX, 0.0f, interiorDriftZ);
|
||||
} else {
|
||||
program.getInterface().mvp.set(mvpInteriorScratch);
|
||||
}
|
||||
}
|
||||
|
||||
private void popInteriorTransform(boolean shadersActive) {
|
||||
if (shadersActive) {
|
||||
GLStateManager.glPopMatrix();
|
||||
} else {
|
||||
program.getInterface().mvp.set(mvpScratch);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean cullBackFaces() {
|
||||
return cloudMode == MODE_FANCY;
|
||||
}
|
||||
|
||||
private void drawVisibleWedges(int[] plateStarts, int[] wallStarts, int total, boolean fromFaceMesh) {
|
||||
if (!computeVisibleArc()) {
|
||||
drawRange(0, total, fromFaceMesh);
|
||||
return;
|
||||
}
|
||||
|
||||
drawRange(0, plateStarts[0], fromFaceMesh);
|
||||
|
||||
if (arcFrom <= arcTo) {
|
||||
drawRange(plateStarts[arcFrom], plateStarts[arcTo + 1], fromFaceMesh);
|
||||
drawRange(plateStarts[WEDGE_COUNT], wallStarts[0], fromFaceMesh);
|
||||
drawRange(wallStarts[arcFrom], wallStarts[arcTo + 1], fromFaceMesh);
|
||||
} else {
|
||||
drawRange(plateStarts[arcFrom], plateStarts[WEDGE_COUNT], fromFaceMesh);
|
||||
drawRange(plateStarts[0], plateStarts[arcTo + 1], fromFaceMesh);
|
||||
drawRange(plateStarts[WEDGE_COUNT], wallStarts[0], fromFaceMesh);
|
||||
drawRange(wallStarts[arcFrom], wallStarts[WEDGE_COUNT], fromFaceMesh);
|
||||
drawRange(wallStarts[0], wallStarts[arcTo + 1], fromFaceMesh);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void drawRange(int start, int end, boolean fromFaceMesh) {
|
||||
final int count = end - start;
|
||||
if (count <= 0) return;
|
||||
drawCalls++;
|
||||
if (fromFaceMesh) {
|
||||
faceMesh.drawRange(start, end);
|
||||
} else {
|
||||
vertexMesh.drawRange(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean computeVisibleArc() {
|
||||
final double halfSpan = cloudView.halfSpan(cullingHeightCells, driftCells) * WEDGES_PER_RADIAN + 0.25;
|
||||
if (halfSpan * 2.0 + 1.0 >= WEDGE_COUNT) return false;
|
||||
|
||||
final double centre = (cloudView.facing + Math.PI) * WEDGES_PER_RADIAN;
|
||||
arcFrom = Math.floorMod((int) Math.floor(centre - halfSpan), WEDGE_COUNT);
|
||||
arcTo = Math.floorMod((int) Math.ceil(centre + halfSpan), WEDGE_COUNT);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void applyAnaglyphColorMask() {
|
||||
if (!mc.gameSettings.anaglyph) {
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
return;
|
||||
}
|
||||
switch (EntityRenderer.anaglyphField) {
|
||||
case 0 -> GLStateManager.glColorMask(false, true, true, true);
|
||||
case 1 -> GLStateManager.glColorMask(true, false, false, true);
|
||||
default -> GLStateManager.glColorMask(true, true, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void delete() {
|
||||
invalidateGeometry();
|
||||
vertexMesh.deleteVao();
|
||||
vertexMesh.deleteEbo();
|
||||
spareMesh.deleteVao();
|
||||
spareMesh.deleteEbo();
|
||||
interiorMesh.deleteVao();
|
||||
interiorMesh.deleteEbo();
|
||||
faceMesh.delete();
|
||||
program = null;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 171 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user