mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1babfd968c | ||
|
|
888a4de0c9 | ||
|
|
a23cefba92 | ||
|
|
f2c07dea28 | ||
|
|
53776684c8 | ||
|
|
9dd60f6a75 | ||
|
|
87c83f0645 | ||
|
|
c578970a66 | ||
|
|
ef68af5850 | ||
|
|
b6a4497fb3 | ||
|
|
939e31461a | ||
|
|
02f0b0fca1 |
No files matched your search
@@ -93,6 +93,7 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
shadowImplementation(project(":lwjgl3-backend")) { isTransitive = false }
|
||||
shadowImplementation(project(":sdl-gpu")) { isTransitive = false }
|
||||
runtimeOnlyNonPublishable(project(":tracy-client")) { isTransitive = false }
|
||||
devOnlyNonPublishable(libs.async.profiler)
|
||||
shadowImplementation(libs.eventbus)
|
||||
|
||||
shadowImplementation(libs.jcpp) // Apache 2.0
|
||||
|
||||
@@ -37,6 +37,7 @@ public final class SystemProperties {
|
||||
public static final boolean SDL_VERIFY_PER_FRAME_UNIFORM_BLOCK = Boolean.getBoolean("angelica.sdlgpu.verifyPerFrameUniformBlock");
|
||||
public static final int SDL_FRAMES_IN_FLIGHT = Integer.getInteger("angelica.sdlgpu.framesInFlight", 2);
|
||||
public static final boolean DISABLE_SDL_PRESENTER_THREAD = Boolean.getBoolean("angelica.sdlgpu.disablePresenterThread");
|
||||
public static final boolean SDL_DISABLE_IN_PASS_CLEAR = Boolean.getBoolean("angelica.sdlgpu.disableInPassClear");
|
||||
|
||||
// Tracy
|
||||
public static final boolean TRACY = Boolean.getBoolean("angelica.tracy");
|
||||
@@ -44,6 +45,11 @@ public final class SystemProperties {
|
||||
public static final String TRACY_DIR = System.getProperty("angelica.tracy.dir", "angelica" + File.separator + "natives" + File.separator + "tracy");
|
||||
public static final int TRACY_MAX_SRC_LOCS = Math.max(16, Integer.getInteger("angelica.tracy.maxSrcLocs", 4096));
|
||||
|
||||
// Profiling
|
||||
public static final String PROFILE_OPTS = System.getProperty("angelica.profile.opts", "event=wall,interval=5ms,alloc=512k,lock=10ms");
|
||||
public static final String PROFILE_DIR = System.getProperty("angelica.profile.dir", "angelica" + File.separator + "profiles");
|
||||
public static final String PROFILE_OUTPUT = System.getProperty("angelica.profile.output", "");
|
||||
|
||||
// Flyby
|
||||
public static final String FLYBY_ROUTE = System.getProperty("angelica.flyby.route", "");
|
||||
public static final boolean FLYBY_WAIT_FOR_TRACY = Boolean.getBoolean("angelica.flyby.waitForTracy");
|
||||
@@ -56,6 +62,8 @@ public final class SystemProperties {
|
||||
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
|
||||
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
|
||||
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
|
||||
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
|
||||
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
|
||||
|
||||
// Debug
|
||||
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
|
||||
@@ -70,6 +78,7 @@ public final class SystemProperties {
|
||||
public static final boolean FFP_TRACE = Boolean.getBoolean("angelica.debug.ffpTrace");
|
||||
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
|
||||
public static final boolean FORCE_ORPHAN_STREAMING = Boolean.getBoolean("angelica.debug.forceOrphanStreaming");
|
||||
public static final boolean WEATHER_REBUILD_ALWAYS = Boolean.getBoolean("angelica.debug.weatherRebuildAlways");
|
||||
public static final String SHADER_DUMP_ROOT = "angelica_dumps";
|
||||
|
||||
// Set by us, read by celeritas
|
||||
@@ -118,6 +127,20 @@ public final class SystemProperties {
|
||||
CONFIGURED
|
||||
}
|
||||
|
||||
public enum FlybyWeather {
|
||||
CLEAR,
|
||||
RAIN,
|
||||
THUNDER;
|
||||
|
||||
public boolean isRaining() {
|
||||
return this != CLEAR;
|
||||
}
|
||||
|
||||
public boolean isThundering() {
|
||||
return this == THUNDER;
|
||||
}
|
||||
}
|
||||
|
||||
private static <E extends Enum<E>> E parseEnum(String key, E whenAbsent, E whenInvalid, Class<E> type) {
|
||||
final String raw = System.getProperty(key);
|
||||
if (raw == null || raw.isEmpty()) return whenAbsent;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
|
||||
public final class ClipPlaneBooleanState extends BooleanStateStack {
|
||||
|
||||
private final GLContextState owner;
|
||||
private final int index;
|
||||
|
||||
ClipPlaneBooleanState(GLContextState owner, int index, int glCap) {
|
||||
super(glCap);
|
||||
this.owner = owner;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnabledChanged() {
|
||||
if (isEnabled()) {
|
||||
owner.clipPlaneEnabledMask |= (1 << index);
|
||||
} else {
|
||||
owner.clipPlaneEnabledMask &= ~(1 << index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,8 @@ public class DisplayListManager {
|
||||
// Display list compilation state (current/active context)
|
||||
private static int glListMode = 0;
|
||||
private static int glListId = -1;
|
||||
private static int recordedStatePushes;
|
||||
private static final DisplayListCommand POP_STATE = GLStateManager::popState;
|
||||
private static CommandRecorder currentRecorder = null; // Command recorder (null when not recording)
|
||||
private static volatile Thread recordingThread = null; // Thread that started recording (for thread-safety)
|
||||
private static List<AccumulatedDraw> accumulatedDraws = null; // Accumulates quad draws for batching
|
||||
@@ -114,6 +116,7 @@ public class DisplayListManager {
|
||||
List<AccumulatedDraw> draws,
|
||||
DisplayListCallback callback,
|
||||
StackTraceElement[] stackTrace,
|
||||
int recordedStatePushes,
|
||||
|
||||
// Debug logging fields (only used when LOG_DISPLAY_LIST_COMPILATION)
|
||||
List<String> pendingOps,
|
||||
@@ -451,6 +454,25 @@ public class DisplayListManager {
|
||||
currentRecorder.writePopAttrib();
|
||||
}
|
||||
|
||||
public static int virtualStateDepth(RecordMode mode) {
|
||||
return mode == RecordMode.COMPILE ? recordedStatePushes : 0;
|
||||
}
|
||||
|
||||
public static void recordPushState(StateSet set) {
|
||||
currentRecorder.writeComplexCommand(set.pushCommand);
|
||||
recordedStatePushes++;
|
||||
}
|
||||
|
||||
public static boolean recordPopStateIfPending() {
|
||||
if (recordedStatePushes > 0) {
|
||||
drawBarrier();
|
||||
currentRecorder.writeComplexCommand(POP_STATE);
|
||||
recordedStatePushes--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void recordFogf(int pname, float param) {
|
||||
drawBarrier();
|
||||
currentRecorder.writeFogf(pname, param);
|
||||
@@ -775,7 +797,7 @@ public class DisplayListManager {
|
||||
// Save current compilation context and start fresh for nested list
|
||||
final CompilationContext parentContext = new CompilationContext(
|
||||
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
|
||||
compilationStackTrace, pendingTransformOps, multMatrixSources, drawRangeSources
|
||||
compilationStackTrace, recordedStatePushes, pendingTransformOps, multMatrixSources, drawRangeSources
|
||||
);
|
||||
compilationStack.push(parentContext);
|
||||
}
|
||||
@@ -787,6 +809,7 @@ public class DisplayListManager {
|
||||
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)
|
||||
@@ -831,6 +854,12 @@ public class DisplayListManager {
|
||||
// Stop compiling mode (works for both root and nested lists now)
|
||||
TessellatorManager.stopCapturingDirect();
|
||||
|
||||
boolean unbalancedStatePush = false;
|
||||
while (recordPopStateIfPending()) unbalancedStatePush = true;
|
||||
if (unbalancedStatePush) {
|
||||
GLStateManager.warnOnce("unbalanced-state-push", "Display list {} ended with unpopped internal state pushes - closing them", glListId);
|
||||
}
|
||||
|
||||
flushAll();
|
||||
|
||||
// Reset back to MODELVIEW (default state)
|
||||
@@ -921,6 +950,7 @@ public class DisplayListManager {
|
||||
accumulatedDraws = parentContext.draws;
|
||||
transformCallback = parentContext.callback;
|
||||
compilationStackTrace = parentContext.stackTrace;
|
||||
recordedStatePushes = parentContext.recordedStatePushes;
|
||||
pendingTransformOps = parentContext.pendingOps;
|
||||
multMatrixSources = parentContext.matrixSources;
|
||||
drawRangeSources = parentContext.drawSources;
|
||||
@@ -946,6 +976,7 @@ public class DisplayListManager {
|
||||
drawRangeSources = null;
|
||||
glListId = -1;
|
||||
glListMode = 0;
|
||||
recordedStatePushes = 0;
|
||||
}
|
||||
|
||||
public static void abortCompilation() {
|
||||
|
||||
@@ -3,18 +3,13 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class Feature {
|
||||
@@ -23,52 +18,49 @@ public class Feature {
|
||||
GL13.GL_MULTISAMPLE_BIT, GL11.GL_PIXEL_MODE_BIT, GL11.GL_POINT_BIT, GL11.GL_POLYGON_BIT, GL11.GL_POLYGON_STIPPLE_BIT, GL11.GL_SCISSOR_BIT,
|
||||
GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_TEXTURE_BIT, GL11.GL_TRANSFORM_BIT, GL11.GL_VIEWPORT_BIT };
|
||||
|
||||
static final Int2ObjectMap<List<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
|
||||
static final Int2ObjectMap<IStateStack<?>[]> maskToNonBooleanStacksMap = new Int2ObjectOpenHashMap<>();
|
||||
private static final long[] NO_MEMBERS = new long[0];
|
||||
|
||||
static List<IStateStack<?>> maskToFeatures(int mask) {
|
||||
if(maskToFeaturesMap.containsKey(mask)) {
|
||||
return maskToFeaturesMap.get(mask);
|
||||
}
|
||||
private static final Int2ObjectMap<long[]> maskToMembersMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
final Set<IStateStack<?>> features = new HashSet<>();
|
||||
|
||||
for(int attrib : Feature.supportedAttribs) {
|
||||
if((mask & attrib) == attrib) {
|
||||
features.addAll(getFeatures(attrib));
|
||||
}
|
||||
}
|
||||
|
||||
final List<IStateStack<?>> asList = new ArrayList<>(features);
|
||||
|
||||
maskToFeaturesMap.put(mask, asList);
|
||||
return asList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only non-BooleanStateStack instances for the given mask.
|
||||
* These use traditional push/pop without global depth tracking.
|
||||
*/
|
||||
static IStateStack<?>[] maskToNonBooleanStacks(int mask) {
|
||||
IStateStack<?>[] cached = maskToNonBooleanStacksMap.get(mask);
|
||||
static long[] maskToMembers(int mask) {
|
||||
final long[] cached = maskToMembersMap.get(mask);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final List<IStateStack<?>> all = maskToFeatures(mask);
|
||||
final List<IStateStack<?>> nonBooleans = new ArrayList<>();
|
||||
for (int i = 0; i < all.size(); i++) {
|
||||
final IStateStack<?> stack = all.get(i);
|
||||
if (!(stack instanceof BooleanStateStack)) {
|
||||
nonBooleans.add(stack);
|
||||
long[] result = NO_MEMBERS;
|
||||
for (int attrib : Feature.supportedAttribs) {
|
||||
if ((mask & attrib) == attrib) {
|
||||
result = union(result, attribToMembers.get(attrib));
|
||||
}
|
||||
}
|
||||
cached = nonBooleans.toArray(new IStateStack<?>[0]);
|
||||
maskToNonBooleanStacksMap.put(mask, cached);
|
||||
return cached;
|
||||
|
||||
maskToMembersMap.put(mask, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static final Map<Integer, Set<IStateStack<?>>> attribToFeatures = new HashMap<>();
|
||||
private static long[] union(long[] a, long[] b) {
|
||||
if (b == null || b.length == 0) return a;
|
||||
if (a.length == 0) return b.clone();
|
||||
final long[] result = new long[Math.max(a.length, b.length)];
|
||||
System.arraycopy(a, 0, result, 0, a.length);
|
||||
for (int i = 0; i < b.length; i++) result[i] |= b[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
private static long[] toMembers(Set<IStateStack<?>> stacks) {
|
||||
if (stacks.isEmpty()) return NO_MEMBERS;
|
||||
int max = 0;
|
||||
for (IStateStack<?> s : stacks) max = Math.max(max, ((CowStateStack<?>) s).stackId());
|
||||
final long[] bits = new long[(max >> 6) + 1];
|
||||
for (IStateStack<?> s : stacks) {
|
||||
final int id = ((CowStateStack<?>) s).stackId();
|
||||
bits[id >> 6] |= 1L << id;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
private static final Int2ObjectMap<long[]> attribToMembers = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
/**
|
||||
* Helper method to add all texture gen states (S, T, R, Q) for all texture units to a collection.
|
||||
@@ -84,6 +76,8 @@ public class Feature {
|
||||
}
|
||||
|
||||
static {
|
||||
final Int2ObjectMap<Set<IStateStack<?>>> attribToFeatures = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of(
|
||||
GLStateManager.getAlphaTest()
|
||||
, GLStateManager.getAlphaState()
|
||||
@@ -191,11 +185,25 @@ public class Feature {
|
||||
|
||||
attribToFeatures.put(GL11.GL_ENABLE_BIT, enableBits);
|
||||
attribToFeatures.put(GL11.GL_EVAL_BIT, ImmutableSet.of(
|
||||
// GL_MAP1_x enable bits, where x is a map type
|
||||
// GL_MAP2_x enable bits, where x is a map type
|
||||
// 1D grid endpoints and divisions
|
||||
// 2D grid endpoints and divisions
|
||||
// GL_AUTO_NORMAL enable bit
|
||||
GLStateManager.getAutoNormalState()
|
||||
, GLStateManager.getMap1Color4State()
|
||||
, GLStateManager.getMap1IndexState()
|
||||
, GLStateManager.getMap1NormalState()
|
||||
, GLStateManager.getMap1TextureCoord1State()
|
||||
, GLStateManager.getMap1TextureCoord2State()
|
||||
, GLStateManager.getMap1TextureCoord3State()
|
||||
, GLStateManager.getMap1TextureCoord4State()
|
||||
, GLStateManager.getMap1Vertex3State()
|
||||
, GLStateManager.getMap1Vertex4State()
|
||||
, GLStateManager.getMap2Color4State()
|
||||
, GLStateManager.getMap2IndexState()
|
||||
, GLStateManager.getMap2NormalState()
|
||||
, GLStateManager.getMap2TextureCoord1State()
|
||||
, GLStateManager.getMap2TextureCoord2State()
|
||||
, GLStateManager.getMap2TextureCoord3State()
|
||||
, GLStateManager.getMap2TextureCoord4State()
|
||||
, GLStateManager.getMap2Vertex3State()
|
||||
, GLStateManager.getMap2Vertex4State()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_FOG_BIT, ImmutableSet.of(
|
||||
GLStateManager.getFogMode()
|
||||
@@ -258,10 +266,10 @@ public class Feature {
|
||||
// GL_LIST_BASE setting
|
||||
));
|
||||
attribToFeatures.put(GL13.GL_MULTISAMPLE_BIT, ImmutableSet.of(
|
||||
// GL_MULTISAMPLE enable bit
|
||||
// GL_SAMPLE_ALPHA_TO_COVERAGE flag
|
||||
// GL_SAMPLE_ALPHA_TO_ONE flag
|
||||
// GL_SAMPLE_COVERAGE flag
|
||||
GLStateManager.getMultisampleState()
|
||||
, GLStateManager.getSampleAlphaToCoverageState()
|
||||
, GLStateManager.getSampleAlphaToOneState()
|
||||
, GLStateManager.getSampleCoverageState()
|
||||
// GL_SAMPLE_COVERAGE_VALUE value
|
||||
// GL_SAMPLE_COVERAGE_INVERT value
|
||||
));
|
||||
@@ -327,22 +335,24 @@ public class Feature {
|
||||
|
||||
attribToFeatures.put(GL11.GL_TEXTURE_BIT, textureAttribs);
|
||||
|
||||
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, ImmutableSet.of(
|
||||
final Set<IStateStack<?>> transformAttribs = new HashSet<>(ImmutableSet.of(
|
||||
// Coefficients of the six clipping planes
|
||||
|
||||
|
||||
GLStateManager.getMatrixMode()
|
||||
, GLStateManager.getNormalizeState()
|
||||
, GLStateManager.getRescaleNormalState()
|
||||
));
|
||||
for(int i = 0; i < GLStateManager.getClipPlaneStates().length; i++) {
|
||||
transformAttribs.add(GLStateManager.getClipPlaneStates()[i]);
|
||||
}
|
||||
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, transformAttribs);
|
||||
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of(
|
||||
|
||||
|
||||
GLStateManager.getViewportState()
|
||||
));
|
||||
|
||||
for (Int2ObjectMap.Entry<Set<IStateStack<?>>> entry : attribToFeatures.int2ObjectEntrySet()) {
|
||||
attribToMembers.put(entry.getIntKey(), toMembers(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
public static Set<IStateStack<?>> getFeatures(int attrib) {
|
||||
return attribToFeatures.getOrDefault(attrib, Collections.emptySet());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -10,6 +10,9 @@ import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.Color4Stack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ColorMaskStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowDepths;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowDispatch;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.DepthStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.IntegerStateStack;
|
||||
@@ -20,8 +23,10 @@ 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.StackIdAllocator;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
@@ -29,8 +34,7 @@ import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntStack;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import org.joml.Matrix4fStack;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -43,11 +47,20 @@ import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class GLContextState {
|
||||
public class GLContextState {
|
||||
private final List<IStateStack<?>> allStacksBuilder = new ArrayList<>();
|
||||
public IStateStack<?>[] allStacks;
|
||||
final List<BooleanStateStack> allBooleanStates = new ArrayList<>();
|
||||
|
||||
private BooleanStateStack track(BooleanStateStack s) {
|
||||
s.setStackId(StackIdAllocator.nextId());
|
||||
allBooleanStates.add(s);
|
||||
allStacksBuilder.add(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
private <T extends IStateStack<?>> T member(T s) {
|
||||
allStacksBuilder.add(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -68,6 +81,7 @@ class GLContextState {
|
||||
public int colorGeneration;
|
||||
// clip plane equation changes
|
||||
public int clipPlaneGeneration;
|
||||
public int clipPlaneEnabledMask;
|
||||
public float overlayR = 0.0f;
|
||||
public float overlayG = 0.0f;
|
||||
public float overlayB = 0.0f;
|
||||
@@ -84,10 +98,15 @@ class GLContextState {
|
||||
public boolean unit23TexCoordSetDuringDraw = false;
|
||||
public int maxBoundTextureUnit = 0;
|
||||
public int maxBoundImageUnit = 0;
|
||||
public final IntStack attribs = new IntArrayList(GLStateManager.MAX_ATTRIB_STACK_DEPTH);
|
||||
public final StateSet[] attribSets = new StateSet[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public int attribDepth = 0;
|
||||
@SuppressWarnings("unchecked")
|
||||
public final List<IStateStack<?>>[] modifiedAtDepth = new List[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public CowStateStack<?>[] stackById;
|
||||
public CowDepths[] depthsById;
|
||||
public int[] restoreBitById;
|
||||
public int[] restoreUnitById;
|
||||
public byte[] kindById;
|
||||
public int[][] modifiedIds;
|
||||
public int[] modifiedCount;
|
||||
public final int[] savedMvGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedMvLinearGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedProjGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
@@ -97,29 +116,30 @@ class GLContextState {
|
||||
public final int[] savedColorGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedNormalGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final int[] savedTexCoordGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
public final IntegerStateStack activeTextureUnit = new IntegerStateStack(0);
|
||||
public final IntegerStateStack activeTextureUnit = member(new IntegerStateStack(0, StackIdAllocator.nextId()).restoreBit(StateSet.R_ACTIVE_UNIT));
|
||||
public int clientActiveTextureUnit = 0;
|
||||
public final IntegerStateStack shadeModelState = new IntegerStateStack(GL11.GL_SMOOTH);
|
||||
public final IntegerStateStack shadeModelState = member(new IntegerStateStack(GL11.GL_SMOOTH, StackIdAllocator.nextId()));
|
||||
public final TextureUnitArray textures = new TextureUnitArray();
|
||||
public final ImageUnitArray imageUnits = new ImageUnitArray();
|
||||
public final SamplerUnitArray samplerUnits = new SamplerUnitArray();
|
||||
public final BlendStateStack blendState = new BlendStateStack();
|
||||
public final BlendStateStack blendState = member(new BlendStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack blendMode = track(new BooleanStateStack(GL11.GL_BLEND));
|
||||
public final BooleanStateStack scissorTest = track(new BooleanStateStack(GL11.GL_SCISSOR_TEST));
|
||||
public final DepthStateStack depthState = new DepthStateStack();
|
||||
public final DepthStateStack depthState = member(new DepthStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack depthTest = track(new BooleanStateStack(GL11.GL_DEPTH_TEST));
|
||||
public final FogStateStack fogState = new FogStateStack();
|
||||
public final FogStateStack fogState = member(new FogStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack fogMode = track(new BooleanStateStack(GL11.GL_FOG, false, true));
|
||||
public final Color4Stack color = new Color4Stack();
|
||||
public final Color4Stack color = member(new Color4Stack(StackIdAllocator.nextId()));
|
||||
// GL_CURRENT_SECONDARY_COLOR - alpha is always 1 and unused, the color sum only adds RGB
|
||||
public final Color4Stack secondaryColor = new Color4Stack(new Color4(0.0F, 0.0F, 0.0F, 1.0F));
|
||||
public final Color4Stack secondaryColor = member(new Color4Stack(StackIdAllocator.nextId(), new Color4(0.0F, 0.0F, 0.0F, 1.0F)).restoreBit(StateSet.R_SECONDARY_COLOR));
|
||||
public final BooleanStateStack colorSumState = track(new BooleanStateStack(GL14.GL_COLOR_SUM, false, true));
|
||||
public final Color4Stack clearColor = new Color4Stack(new Color4(0.0F, 0.0F, 0.0F, 0.0F));
|
||||
public final ColorMaskStack colorMask = new ColorMaskStack();
|
||||
public final IntegerStateStack drawBuffer = new IntegerStateStack(GLStateManager.DEFAULT_DRAW_BUFFER);
|
||||
public final IntegerStateStack logicOpMode = new IntegerStateStack(GL11.GL_COPY);
|
||||
public final Color4Stack clearColor = member(new Color4Stack(StackIdAllocator.nextId(), new Color4(0.0F, 0.0F, 0.0F, 0.0F)).restoreBit(StateSet.R_CLEAR_COLOR));
|
||||
public final ColorMaskStack colorMask = member(new ColorMaskStack(StackIdAllocator.nextId()));
|
||||
public final IntegerStateStack drawBuffer = member(new IntegerStateStack(GLStateManager.DEFAULT_DRAW_BUFFER, StackIdAllocator.nextId()).restoreBit(StateSet.R_DRAW_BUFFER));
|
||||
public final IntegerStateStack logicOpMode = member(new IntegerStateStack(GL11.GL_COPY, StackIdAllocator.nextId()).restoreBit(StateSet.R_LOGIC_OP));
|
||||
public final BooleanStateStack cullState = track(new BooleanStateStack(GL11.GL_CULL_FACE));
|
||||
public final AlphaStateStack alphaState = new AlphaStateStack();
|
||||
public final AlphaState restoredAlphaScratch = new AlphaState();
|
||||
public final AlphaStateStack alphaState = member(new AlphaStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack alphaTest = track(new BooleanStateStack(GL11.GL_ALPHA_TEST, false, true));
|
||||
public final BooleanStateStack lightingState = track(new BooleanStateStack(GL11.GL_LIGHTING, false, true));
|
||||
public final BooleanStateStack rescaleNormalState = track(new BooleanStateStack(GL12.GL_RESCALE_NORMAL, false, true));
|
||||
@@ -140,10 +160,10 @@ class GLContextState {
|
||||
public final BooleanStateStack polygonOffsetPointState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_POINT));
|
||||
public final BooleanStateStack polygonOffsetLineState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_LINE));
|
||||
public final BooleanStateStack polygonOffsetFillState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_FILL));
|
||||
public final LineStateStack lineState = new LineStateStack();
|
||||
public final PointStateStack pointState = new PointStateStack();
|
||||
public final PolygonStateStack polygonState = new PolygonStateStack();
|
||||
public final StencilStateStack stencilState = new StencilStateStack();
|
||||
public final LineStateStack lineState = member(new LineStateStack(StackIdAllocator.nextId()));
|
||||
public final PointStateStack pointState = member(new PointStateStack(StackIdAllocator.nextId()));
|
||||
public final PolygonStateStack polygonState = member(new PolygonStateStack(StackIdAllocator.nextId()));
|
||||
public final StencilStateStack stencilState = member(new StencilStateStack(StackIdAllocator.nextId()));
|
||||
public int stencilBitMask = 0xFFFFFFFF;
|
||||
public final BooleanStateStack autoNormalState = track(new BooleanStateStack(GL11.GL_AUTO_NORMAL, false, true));
|
||||
public final BooleanStateStack map1Color4State = track(new BooleanStateStack(GL11.GL_MAP1_COLOR_4, false, true));
|
||||
@@ -168,19 +188,20 @@ class GLContextState {
|
||||
public final ClipPlaneState clipPlaneState = new ClipPlaneState();
|
||||
public final FloatBuffer queryScratch = BufferUtils.createFloatBuffer(16);
|
||||
public final DoubleBuffer queryScratchDouble = BufferUtils.createDoubleBuffer(16);
|
||||
public final MatrixModeStack matrixMode = new MatrixModeStack();
|
||||
public final MatrixModeStack matrixMode = member(new MatrixModeStack(StackIdAllocator.nextId()));
|
||||
public final Matrix4fStack modelViewMatrix = new Matrix4fStack(GLStateManager.MAX_MODELVIEW_STACK_DEPTH);
|
||||
public final Matrix4fStack projectionMatrix = new Matrix4fStack(GLStateManager.MAX_PROJECTION_STACK_DEPTH);
|
||||
public final BooleanStateStack[] lightStates = new BooleanStateStack[8];
|
||||
public final LightStateStack[] lightDataStates = new LightStateStack[8];
|
||||
public final BooleanStateStack colorMaterial = track(new BooleanStateStack(GL11.GL_COLOR_MATERIAL, false, true));
|
||||
public final IntegerStateStack colorMaterialFace = new IntegerStateStack(GL11.GL_FRONT_AND_BACK);
|
||||
public final IntegerStateStack colorMaterialParameter = new IntegerStateStack(GL11.GL_AMBIENT_AND_DIFFUSE);
|
||||
public final LightModelStateStack lightModel = new LightModelStateStack();
|
||||
public final MaterialStateStack frontMaterial = new MaterialStateStack(GL11.GL_FRONT);
|
||||
public final MaterialStateStack backMaterial = new MaterialStateStack(GL11.GL_BACK);
|
||||
public final ViewPortStateStack viewportState = new ViewPortStateStack();
|
||||
public final IntegerStateStack colorMaterialFace = member(new IntegerStateStack(GL11.GL_FRONT_AND_BACK, StackIdAllocator.nextId()));
|
||||
public final IntegerStateStack colorMaterialParameter = member(new IntegerStateStack(GL11.GL_AMBIENT_AND_DIFFUSE, StackIdAllocator.nextId()));
|
||||
public final LightModelStateStack lightModel = member(new LightModelStateStack(StackIdAllocator.nextId()));
|
||||
public final MaterialStateStack frontMaterial = member(new MaterialStateStack(GL11.GL_FRONT, StackIdAllocator.nextId()));
|
||||
public final MaterialStateStack backMaterial = member(new MaterialStateStack(GL11.GL_BACK, StackIdAllocator.nextId()));
|
||||
public final ViewPortStateStack viewportState = member(new ViewPortStateStack(StackIdAllocator.nextId()));
|
||||
public int activeProgram = 0;
|
||||
public final IntegerStateStack programStack = member(new IntegerStateStack(0, StackIdAllocator.nextId()));
|
||||
public int listBase = 0;
|
||||
public int boundVBO;
|
||||
public int boundVAO;
|
||||
@@ -194,8 +215,8 @@ class GLContextState {
|
||||
public final int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public int clientAttribStackPointer = 0;
|
||||
public final boolean[] restoreUnitChanged = new boolean[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
public boolean restoreDepthChanged, restoreBlendChanged, restoreColorMaskChanged, restoreClearColorChanged, restoreDrawBufferChanged, restoreLogicOpChanged, restoreStencilChanged, restoreViewportChanged, restoreLineChanged, restorePointChanged, restorePolygonChanged, restoreActiveUnitChanged, restoreSecondaryColorChanged;
|
||||
public int restoreChangedMask;
|
||||
public long restoreUnitChangedMask;
|
||||
public final BlendState vanillaBlendBefore = new BlendState();
|
||||
public final BlendState vanillaBlendAfter = new BlendState();
|
||||
public boolean vanillaBlendEnabledBefore;
|
||||
@@ -204,18 +225,40 @@ class GLContextState {
|
||||
public int texGenGeneration;
|
||||
|
||||
GLContextState() {
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) modifiedAtDepth[i] = new ArrayList<>();
|
||||
allStacksBuilder.addAll(textures.idStacks());
|
||||
for (int i = 0; i < lightStates.length; i++) {
|
||||
lightStates[i] = track(new BooleanStateStack(GL11.GL_LIGHT0 + i, false, true));
|
||||
}
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
clipPlaneStates[i] = track(new BooleanStateStack(GL11.GL_CLIP_PLANE0 + i));
|
||||
clipPlaneStates[i] = track(new ClipPlaneBooleanState(this, i, GL11.GL_CLIP_PLANE0 + i));
|
||||
}
|
||||
}
|
||||
|
||||
void init() {
|
||||
for (int i = 0; i < lightDataStates.length; i++) {
|
||||
lightDataStates[i] = new LightStateStack(GL11.GL_LIGHT0 + i);
|
||||
lightDataStates[i] = member(new LightStateStack(GL11.GL_LIGHT0 + i, StackIdAllocator.nextId()));
|
||||
}
|
||||
allStacksBuilder.add(ShaderManager.getNormalStack());
|
||||
allStacksBuilder.add(ShaderManager.getTexCoordStack());
|
||||
allStacks = allStacksBuilder.toArray(new IStateStack<?>[0]);
|
||||
|
||||
final int capacity = StackIdAllocator.capacity();
|
||||
stackById = new CowStateStack<?>[capacity];
|
||||
depthsById = new CowDepths[capacity];
|
||||
restoreBitById = new int[capacity];
|
||||
restoreUnitById = new int[capacity];
|
||||
kindById = new byte[capacity];
|
||||
for (final IStateStack<?> stack : allStacks) {
|
||||
final CowStateStack<?> s = (CowStateStack<?>) stack;
|
||||
final int id = s.stackId();
|
||||
stackById[id] = s;
|
||||
depthsById[id] = s.cowDepths();
|
||||
restoreBitById[id] = s.restoreBit();
|
||||
restoreUnitById[id] = s.restoreUnit();
|
||||
kindById[id] = CowDispatch.kindOf(s);
|
||||
}
|
||||
|
||||
modifiedIds = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH][allStacks.length];
|
||||
modifiedCount = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import org.lwjgl.opengl.GL43;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class GLDebug {
|
||||
|
||||
@@ -586,6 +587,13 @@ public final class GLDebug {
|
||||
}
|
||||
}
|
||||
|
||||
public static void resetGroupStack() {
|
||||
if (debugState instanceof KHRDebugState khr) {
|
||||
khr.depth = 0;
|
||||
Arrays.fill(khr.groupStack, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void debugMessage(String message) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.debugMessage(message);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.lwjgl.opengl.OpenGLException;
|
||||
|
||||
final class GLErrorReporter {
|
||||
|
||||
private GLErrorReporter() {}
|
||||
|
||||
static void reportError(int error) {
|
||||
throw new OpenGLException(error);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -637,6 +637,11 @@ public class RenderSystem {
|
||||
GLStateManager.glSamplerParameteri(sampler, pname, param);
|
||||
}
|
||||
|
||||
public static void samplerParameterf(int sampler, int pname, float param) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
GLStateManager.glSamplerParameterf(sampler, pname, param);
|
||||
}
|
||||
|
||||
public static void bindSamplerToUnit(int unit, int sampler) {
|
||||
if (!supportsSamplerObjects) return;
|
||||
GLStateManager.glBindSampler(unit, sampler);
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class StateSet {
|
||||
|
||||
public static final int R_DEPTH = 1;
|
||||
public static final int R_BLEND = 1 << 1;
|
||||
public static final int R_COLOR_MASK = 1 << 2;
|
||||
public static final int R_CLEAR_COLOR = 1 << 3;
|
||||
public static final int R_DRAW_BUFFER = 1 << 4;
|
||||
public static final int R_LOGIC_OP = 1 << 5;
|
||||
public static final int R_SECONDARY_COLOR = 1 << 6;
|
||||
public static final int R_STENCIL = 1 << 7;
|
||||
public static final int R_VIEWPORT = 1 << 8;
|
||||
public static final int R_LINE = 1 << 9;
|
||||
public static final int R_POINT = 1 << 10;
|
||||
public static final int R_POLYGON = 1 << 11;
|
||||
public static final int R_TEXTURE = 1 << 12;
|
||||
public static final int R_ACTIVE_UNIT = 1 << 13;
|
||||
public static final int R_PROGRAM = 1 << 14;
|
||||
|
||||
public static final int B_FRAG_COLORBUF = 1;
|
||||
public static final int B_FRAG_TEXTURE = 1 << 1;
|
||||
public static final int B_FRAG_FOG = 1 << 2;
|
||||
public static final int B_CURRENT = 1 << 3;
|
||||
public static final int B_LIGHTING = 1 << 4;
|
||||
public static final int B_TRANSFORM = 1 << 5;
|
||||
|
||||
private static final long[] NO_MEMBERS = new long[0];
|
||||
private static final Int2ObjectMap<StateSet> maskToStateSetMap = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
public final int glMask;
|
||||
public final long[] members;
|
||||
public final int restore;
|
||||
public final int bump;
|
||||
public final boolean blendSnapshot;
|
||||
public final DisplayListCommand pushCommand;
|
||||
|
||||
StateSet(int glMask, long[] members, int restore, int bump, boolean blendSnapshot) {
|
||||
this.glMask = glMask;
|
||||
this.members = members;
|
||||
this.restore = restore;
|
||||
this.bump = bump;
|
||||
this.blendSnapshot = blendSnapshot;
|
||||
this.pushCommand = () -> GLStateManager.pushStateLive(this);
|
||||
}
|
||||
|
||||
public boolean contains(int id) {
|
||||
if (id < 0) return false;
|
||||
final int word = id >> 6;
|
||||
return word < members.length && (members[word] & (1L << id)) != 0;
|
||||
}
|
||||
|
||||
private static long[] ids(CowStateStack<?>... stacks) {
|
||||
if (stacks.length == 0) return NO_MEMBERS;
|
||||
int max = 0;
|
||||
for (CowStateStack<?> s : stacks) max = Math.max(max, s.stackId());
|
||||
final long[] bits = new long[(max >> 6) + 1];
|
||||
for (CowStateStack<?> s : stacks) {
|
||||
final int id = s.stackId();
|
||||
bits[id >> 6] |= 1L << id;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static final StateSet BATCH = buildBatch();
|
||||
public static final StateSet FONT = buildFont();
|
||||
public static final StateSet FONT_PIPELINE = buildFontPipeline();
|
||||
public static final StateSet HAND = buildHand();
|
||||
public static final StateSet BLEND = buildBlend();
|
||||
public static final StateSet ALPHA = buildAlpha();
|
||||
public static final StateSet CUTOUT = buildCutout();
|
||||
public static final StateSet MASKS = buildMasks();
|
||||
public static final StateSet CULL = buildCull();
|
||||
|
||||
private static StateSet buildBatch() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getAlphaState(), GLStateManager.getDepthState(),
|
||||
GLStateManager.getColorMask(), GLStateManager.getPolygonState(),
|
||||
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
|
||||
GLStateManager.getActiveTextureUnitStack(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
|
||||
GLStateManager.getLightingState(), GLStateManager.getCullState(), GLStateManager.getPolygonOffsetFillState(),
|
||||
GLStateManager.getTextures().getTextureUnitStates(0)
|
||||
),
|
||||
R_BLEND | R_DEPTH | R_COLOR_MASK | R_POLYGON | R_TEXTURE | R_ACTIVE_UNIT,
|
||||
B_FRAG_COLORBUF,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildFont() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getDepthState(), GLStateManager.getShadeModelState(),
|
||||
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
|
||||
GLStateManager.getActiveTextureUnitStack(), GLStateManager.getProgramStack(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
|
||||
GLStateManager.getTextures().getTextureUnitStates(0)
|
||||
),
|
||||
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT | R_PROGRAM,
|
||||
0,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildFontPipeline() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getDepthState(),
|
||||
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
|
||||
GLStateManager.getActiveTextureUnitStack(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
|
||||
GLStateManager.getTextures().getTextureUnitStates(0)
|
||||
),
|
||||
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT,
|
||||
0,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildHand() {
|
||||
return new StateSet(0,
|
||||
ids(
|
||||
GLStateManager.getBlendState(), GLStateManager.getAlphaState(), GLStateManager.getDepthState(),
|
||||
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getTextures().getTextureUnitStates(1)
|
||||
),
|
||||
R_BLEND | R_DEPTH,
|
||||
B_FRAG_COLORBUF,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildBlend() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getBlendState(), GLStateManager.getBlendMode()),
|
||||
R_BLEND,
|
||||
0,
|
||||
true);
|
||||
}
|
||||
|
||||
private static StateSet buildAlpha() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getAlphaState(), GLStateManager.getAlphaTest()),
|
||||
0,
|
||||
B_FRAG_COLORBUF,
|
||||
false);
|
||||
}
|
||||
|
||||
private static StateSet buildCutout() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getAlphaState(), GLStateManager.getAlphaTest(), GLStateManager.getTextures().getTextureUnitStates(1)),
|
||||
0,
|
||||
B_FRAG_COLORBUF,
|
||||
false);
|
||||
}
|
||||
|
||||
private static StateSet buildMasks() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getDepthState(), GLStateManager.getColorMask()),
|
||||
R_DEPTH | R_COLOR_MASK,
|
||||
0,
|
||||
false);
|
||||
}
|
||||
|
||||
private static StateSet buildCull() {
|
||||
return new StateSet(0,
|
||||
ids(GLStateManager.getPolygonState(), GLStateManager.getCullState()),
|
||||
R_POLYGON,
|
||||
0,
|
||||
false);
|
||||
}
|
||||
|
||||
public static StateSet forMask(int mask) {
|
||||
StateSet cached = maskToStateSetMap.get(mask);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
cached = buildForMask(mask);
|
||||
maskToStateSetMap.put(mask, cached);
|
||||
return cached;
|
||||
}
|
||||
|
||||
private static StateSet buildForMask(int mask) {
|
||||
int restore = 0;
|
||||
int bump = 0;
|
||||
final boolean blendSnapshot = (mask & (GL11.GL_COLOR_BUFFER_BIT | GL11.GL_ENABLE_BIT)) != 0;
|
||||
|
||||
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0) {
|
||||
restore |= R_DEPTH;
|
||||
}
|
||||
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
|
||||
restore |= R_BLEND | R_COLOR_MASK | R_CLEAR_COLOR | R_DRAW_BUFFER | R_LOGIC_OP;
|
||||
bump |= B_FRAG_COLORBUF;
|
||||
}
|
||||
if ((mask & GL11.GL_CURRENT_BIT) != 0) {
|
||||
restore |= R_SECONDARY_COLOR;
|
||||
bump |= B_CURRENT;
|
||||
}
|
||||
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
|
||||
restore |= R_STENCIL;
|
||||
}
|
||||
if ((mask & GL11.GL_VIEWPORT_BIT) != 0) {
|
||||
restore |= R_VIEWPORT;
|
||||
}
|
||||
if ((mask & GL11.GL_LINE_BIT) != 0) {
|
||||
restore |= R_LINE;
|
||||
}
|
||||
if ((mask & GL11.GL_POINT_BIT) != 0) {
|
||||
restore |= R_POINT;
|
||||
}
|
||||
if ((mask & GL11.GL_POLYGON_BIT) != 0) {
|
||||
restore |= R_POLYGON;
|
||||
}
|
||||
if ((mask & GL11.GL_TEXTURE_BIT) != 0) {
|
||||
restore |= R_TEXTURE | R_ACTIVE_UNIT;
|
||||
bump |= B_FRAG_TEXTURE;
|
||||
}
|
||||
if ((mask & GL11.GL_LIGHTING_BIT) != 0) {
|
||||
bump |= B_LIGHTING;
|
||||
}
|
||||
if ((mask & GL11.GL_FOG_BIT) != 0) {
|
||||
bump |= B_FRAG_FOG;
|
||||
}
|
||||
if ((mask & GL11.GL_TRANSFORM_BIT) != 0) {
|
||||
bump |= B_TRANSFORM;
|
||||
}
|
||||
|
||||
final long[] members = Feature.maskToMembers(mask);
|
||||
|
||||
return new StateSet(mask, members, restore, bump, blendSnapshot);
|
||||
}
|
||||
}
|
||||
@@ -242,6 +242,8 @@ public abstract class RenderBackend {
|
||||
}
|
||||
}
|
||||
|
||||
public void invalidateStateMirror() {}
|
||||
|
||||
public void onFrameEnd() {}
|
||||
|
||||
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
|
||||
|
||||
@@ -104,7 +104,9 @@ public class DSAARB extends DSAUnsupported {
|
||||
super.bindTextureToUnit(target != 0 ? target : GL11.GL_TEXTURE_2D, unit, texture);
|
||||
} else {
|
||||
RENDER_BACKEND.bindTextureUnit(unit, texture);
|
||||
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture, target);
|
||||
final var binding = GLStateManager.getTextures().getTextureUnitBindings(unit);
|
||||
binding.beforeModify();
|
||||
binding.setBinding(texture, target);
|
||||
GLStateManager.trackMaxBoundTextureUnit(unit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,8 +2,10 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
@@ -104,11 +106,15 @@ public final class FragmentKey {
|
||||
* @return number of significant longs (1..4)
|
||||
*/
|
||||
public static int packFromState(long[] out) {
|
||||
return packFromState(out, GLStateManager.ctx());
|
||||
}
|
||||
|
||||
public static int packFromState(long[] out, GLContextState glCtx) {
|
||||
long global = 0;
|
||||
|
||||
// Fog
|
||||
if (GLStateManager.getFogMode().isEnabled()) {
|
||||
final int fogMode = switch (GLStateManager.getFogState().getFogMode()) {
|
||||
if (glCtx.fogMode.isEnabled()) {
|
||||
final int fogMode = switch (glCtx.fogState.getFogMode()) {
|
||||
case GL11.GL_LINEAR -> FOG_LINEAR;
|
||||
case GL11.GL_EXP -> FOG_EXP;
|
||||
case GL11.GL_EXP2 -> FOG_EXP2;
|
||||
@@ -118,15 +124,15 @@ public final class FragmentKey {
|
||||
}
|
||||
|
||||
// Alpha test
|
||||
if (GLStateManager.getAlphaTest().isEnabled()) {
|
||||
if (glCtx.alphaTest.isEnabled()) {
|
||||
global |= (1L << BIT_ALPHA_TEST);
|
||||
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
|
||||
global |= ((long) (encodeAlphaFunc(glCtx.alphaState.getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
|
||||
}
|
||||
|
||||
// Damage overlay
|
||||
if (GLStateManager.ffpInstancing.hasInstanceHead()) {
|
||||
global |= (1L << BIT_OVERLAY_INSTANCED);
|
||||
} else if (GLStateManager.getOverlayA() != 0.0f) {
|
||||
} else if (glCtx.overlayA != 0.0f) {
|
||||
global |= (1L << BIT_OVERLAY_ENABLED);
|
||||
}
|
||||
|
||||
@@ -135,22 +141,23 @@ public final class FragmentKey {
|
||||
}
|
||||
|
||||
// Separate specular / color sum
|
||||
if (GLStateManager.getLightingState().isEnabled()
|
||||
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
if (glCtx.lightingState.isEnabled()
|
||||
&& glCtx.lightModel.colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
global |= (1L << BIT_SEPARATE_SPECULAR);
|
||||
} else if (GLStateManager.getColorSumState().isEnabled()) {
|
||||
} else if (glCtx.colorSumState.isEnabled()) {
|
||||
global |= (1L << BIT_COLOR_SUM);
|
||||
}
|
||||
|
||||
// Per-unit state
|
||||
final TextureUnitArray textures = glCtx.textures;
|
||||
int highestEnabled = -1;
|
||||
long unit0Bits = 0;
|
||||
for (int i = 0; i < MAX_UNITS; i++) {
|
||||
// Per Mesa - an enabled unit with no complete texture bound counts as disabled.
|
||||
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled() && GLStateManager.getTextures().getTextureUnitBindings(i).getBinding() != 0;
|
||||
final boolean texEnabled = textures.getTextureUnitStates(i).isEnabled() && textures.getTextureUnitBindings(i).getBinding() != 0;
|
||||
if (texEnabled) highestEnabled = i;
|
||||
|
||||
final long unitBits = packUnit(i, texEnabled);
|
||||
final long unitBits = packUnit(i, texEnabled, textures);
|
||||
if (i == 0) {
|
||||
unit0Bits = unitBits;
|
||||
} else {
|
||||
@@ -166,10 +173,10 @@ public final class FragmentKey {
|
||||
return Math.max(1, nrEnabled);
|
||||
}
|
||||
|
||||
private static long packUnit(int unitIndex, boolean texEnabled) {
|
||||
private static long packUnit(int unitIndex, boolean texEnabled, TextureUnitArray textures) {
|
||||
if (!texEnabled) return 0;
|
||||
|
||||
final TexEnvState envState = GLStateManager.getTextures().getTexEnvState(unitIndex);
|
||||
final TexEnvState envState = textures.getTexEnvState(unitIndex);
|
||||
final int mode = encodeTexEnvMode(envState.mode);
|
||||
|
||||
long bits = 1L; // enabled
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.QuadConverter;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
@@ -50,8 +52,8 @@ public final class ShaderManager {
|
||||
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
|
||||
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
|
||||
};
|
||||
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal);
|
||||
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0]);
|
||||
@Getter private static 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;
|
||||
|
||||
@@ -119,6 +121,12 @@ public final class ShaderManager {
|
||||
lastBoundProgramId = GLStateManager.getActiveProgram();
|
||||
}
|
||||
|
||||
public static void invalidateBoundProgram() {
|
||||
final ShaderManager sm = Holder.INSTANCE;
|
||||
sm.lastBoundProgramId = -1;
|
||||
sm.currentProgram = null;
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
active = false;
|
||||
currentProgram = null;
|
||||
@@ -151,15 +159,16 @@ public final class ShaderManager {
|
||||
if (!active) return;
|
||||
|
||||
preDrawCalls++;
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch);
|
||||
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);
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask, glCtx);
|
||||
|
||||
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
|
||||
commitVariant(vkPacked, fkLen);
|
||||
}
|
||||
|
||||
uploadUniforms();
|
||||
uploadUniforms(glCtx);
|
||||
}
|
||||
|
||||
private void commitVariant(long vkPacked, int fkLen) {
|
||||
@@ -175,9 +184,9 @@ public final class ShaderManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadUniforms() {
|
||||
private void uploadUniforms(GLContextState glCtx) {
|
||||
if (currentProgram != null) {
|
||||
uniforms.upload();
|
||||
uniforms.upload(glCtx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,16 +199,19 @@ public final class ShaderManager {
|
||||
}
|
||||
|
||||
public static void setCurrentNormal(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) {
|
||||
texCoordStack.beforeModify();
|
||||
currentTexCoords[0].set(s, t, r, q);
|
||||
texCoordGeneration++;
|
||||
}
|
||||
|
||||
public static void setCurrentTexCoord(int unit, float s, float t, float r, float q) {
|
||||
if (unit == 0) texCoordStack.beforeModify();
|
||||
currentTexCoords[unit].set(s, t, r, q);
|
||||
texCoordGeneration++;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
@@ -7,6 +8,7 @@ import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LineState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.UniformRingBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
@@ -47,6 +49,7 @@ public class Uniforms {
|
||||
private float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
|
||||
private int viewportX = Integer.MIN_VALUE, viewportY = Integer.MIN_VALUE, viewportWidth = -1, viewportHeight = -1;
|
||||
private int lineStipple = -1;
|
||||
private int weatherGen = -1;
|
||||
|
||||
int blockWrites;
|
||||
int blockSkips;
|
||||
@@ -81,43 +84,43 @@ public class Uniforms {
|
||||
private static final double LN2 = java.lang.Math.log(2.0);
|
||||
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
|
||||
|
||||
public void upload() {
|
||||
public void upload(GLContextState glCtx) {
|
||||
boolean dirty = false;
|
||||
contentChanged = false;
|
||||
|
||||
final int mvG = GLStateManager.getMvGeneration();
|
||||
final int projG = GLStateManager.getProjGeneration();
|
||||
final int mvG = glCtx.mvGeneration;
|
||||
final int projG = glCtx.projGeneration;
|
||||
final boolean mvChanged = mvG != mvGen;
|
||||
final boolean projChanged = projG != projGen;
|
||||
if (mvChanged || projChanged) {
|
||||
stageMatrices(mvChanged, projChanged);
|
||||
stageMatrices(mvChanged, projChanged, glCtx);
|
||||
mvGen = mvG;
|
||||
projGen = projG;
|
||||
stagedMatrices++;
|
||||
dirty = true;
|
||||
}
|
||||
final int mvLinG = GLStateManager.getMvLinearGeneration();
|
||||
final int mvLinG = glCtx.mvLinearGeneration;
|
||||
if (mvLinG != mvLinearGen) {
|
||||
stageNormalMatrix();
|
||||
stageNormalMatrix(glCtx);
|
||||
mvLinearGen = mvLinG;
|
||||
dirty = true;
|
||||
}
|
||||
final int texMatG = GLStateManager.getTexMatrixGeneration();
|
||||
final int texMatG = glCtx.texMatrixGeneration;
|
||||
if (texMatG != texMatGen) {
|
||||
stageTextureMatrices();
|
||||
stageTextureMatrices(glCtx);
|
||||
texMatGen = texMatG;
|
||||
dirty = true;
|
||||
}
|
||||
final int litG = GLStateManager.getLightingGeneration();
|
||||
final int litG = glCtx.lightingGeneration;
|
||||
if (litG != lightingGen) {
|
||||
stageLighting();
|
||||
stageLighting(glCtx);
|
||||
lightingGen = litG;
|
||||
stagedLighting++;
|
||||
dirty = true;
|
||||
}
|
||||
final int colG = GLStateManager.getColorGeneration();
|
||||
final int colG = glCtx.colorGeneration;
|
||||
if (colG != colorGen) {
|
||||
stageCurrentColor();
|
||||
stageCurrentColor(glCtx);
|
||||
colorGen = colG;
|
||||
stagedColor++;
|
||||
dirty = true;
|
||||
@@ -149,35 +152,36 @@ public class Uniforms {
|
||||
stagedLightmap++;
|
||||
dirty = true;
|
||||
}
|
||||
final int tgG = GLStateManager.getTexGenGeneration();
|
||||
final int tgG = glCtx.texGenGeneration;
|
||||
if (tgG != texGenGen) {
|
||||
stageTexGen();
|
||||
stageTexGen(glCtx);
|
||||
texGenGen = tgG;
|
||||
stagedTexGen++;
|
||||
dirty = true;
|
||||
}
|
||||
final int cpG = GLStateManager.getClipPlaneGeneration();
|
||||
final int cpG = glCtx.clipPlaneGeneration;
|
||||
if (cpG != clipPlaneGen) {
|
||||
stageClipPlanes();
|
||||
stageClipPlanes(glCtx);
|
||||
clipPlaneGen = cpG;
|
||||
stagedClipPlanes++;
|
||||
dirty = true;
|
||||
}
|
||||
final int fragG = GLStateManager.getFragmentGeneration();
|
||||
final int fragG = glCtx.fragmentGeneration;
|
||||
if (fragG != fragmentGen) {
|
||||
stageFragment();
|
||||
stageFragment(glCtx);
|
||||
fragmentGen = fragG;
|
||||
stagedFragment++;
|
||||
dirty = true;
|
||||
}
|
||||
final float lw = GLStateManager.getLineState().getWidth();
|
||||
final LineState line = glCtx.lineState;
|
||||
final float lw = line.getWidth();
|
||||
if (lw != lineWidth) {
|
||||
putFloat(FFPUniformBlock.LINE_WIDTH, lw);
|
||||
lineWidth = lw;
|
||||
stagedMisc++;
|
||||
dirty = true;
|
||||
}
|
||||
final ViewportState vp = GLStateManager.getViewportState();
|
||||
final ViewportState vp = glCtx.viewportState;
|
||||
if (vp.x != viewportX || vp.y != viewportY || vp.width != viewportWidth || vp.height != viewportHeight) {
|
||||
putFloat(FFPUniformBlock.VIEWPORT_SIZE, vp.width);
|
||||
putFloat(FFPUniformBlock.VIEWPORT_SIZE + 4, vp.height);
|
||||
@@ -189,8 +193,13 @@ public class Uniforms {
|
||||
stagedMisc++;
|
||||
dirty = true;
|
||||
}
|
||||
final LineState line = GLStateManager.getLineState();
|
||||
final int stippleFactor = line.getStippleFactor() < 1 ? 1 : line.getStippleFactor();
|
||||
if (WeatherParams.generation != weatherGen) {
|
||||
stageWeatherParams();
|
||||
weatherGen = WeatherParams.generation;
|
||||
stagedMisc++;
|
||||
dirty = true;
|
||||
}
|
||||
final int stippleFactor = Math.max(line.getStippleFactor(), 1);
|
||||
final int stipplePacked = (line.getStipplePattern() & 0xFFFF) | (stippleFactor << 16);
|
||||
if (stipplePacked != lineStipple) {
|
||||
putInt(FFPUniformBlock.LINE_STIPPLE, stipplePacked);
|
||||
@@ -215,9 +224,9 @@ public class Uniforms {
|
||||
}
|
||||
}
|
||||
|
||||
private void stageMatrices(boolean mvChanged, boolean projChanged) {
|
||||
final Matrix4f mv = GLStateManager.getModelViewMatrix();
|
||||
final Matrix4f proj = GLStateManager.getProjectionMatrix();
|
||||
private void stageMatrices(boolean mvChanged, boolean projChanged, GLContextState glCtx) {
|
||||
final Matrix4f mv = glCtx.modelViewMatrix;
|
||||
final Matrix4f proj = glCtx.projectionMatrix;
|
||||
if (mvChanged) {
|
||||
putMat4(FFPUniformBlock.MODEL_VIEW_MATRIX, mv);
|
||||
}
|
||||
@@ -228,8 +237,8 @@ public class Uniforms {
|
||||
putMat4(FFPUniformBlock.MVP_MATRIX, mvpMatrix);
|
||||
}
|
||||
|
||||
private void stageNormalMatrix() {
|
||||
GLStateManager.getModelViewMatrix().normal(normalMatrix);
|
||||
private void stageNormalMatrix(GLContextState glCtx) {
|
||||
glCtx.modelViewMatrix.normal(normalMatrix);
|
||||
putMat3(FFPUniformBlock.NORMAL_MATRIX, normalMatrix);
|
||||
putFloat(FFPUniformBlock.NORMAL_SCALE, rescaleFactor(normalMatrix, tempVec3));
|
||||
}
|
||||
@@ -239,18 +248,19 @@ public class Uniforms {
|
||||
return f > 1.0e-12f ? 1.0f / (float) Math.sqrt(f) : 1.0f;
|
||||
}
|
||||
|
||||
private void stageTextureMatrices() {
|
||||
putMat4(FFPUniformBlock.TEXTURE_MATRIX_0, GLStateManager.getTextures().getTextureUnitMatrix(0));
|
||||
putMat4(FFPUniformBlock.TEXTURE_MATRIX_2, GLStateManager.getTextures().getTextureUnitMatrix(2));
|
||||
putMat4(FFPUniformBlock.TEXTURE_MATRIX_3, GLStateManager.getTextures().getTextureUnitMatrix(3));
|
||||
putMat4(FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, GLStateManager.getTextures().getTextureUnitMatrix(1));
|
||||
private void stageTextureMatrices(GLContextState glCtx) {
|
||||
final TextureUnitArray textures = glCtx.textures;
|
||||
putMat4(FFPUniformBlock.TEXTURE_MATRIX_0, textures.getTextureUnitMatrix(0));
|
||||
putMat4(FFPUniformBlock.TEXTURE_MATRIX_2, textures.getTextureUnitMatrix(2));
|
||||
putMat4(FFPUniformBlock.TEXTURE_MATRIX_3, textures.getTextureUnitMatrix(3));
|
||||
putMat4(FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, textures.getTextureUnitMatrix(1));
|
||||
}
|
||||
|
||||
private void stageLighting() {
|
||||
final MaterialState mat = GLStateManager.getFrontMaterial();
|
||||
final Vector4f lmAmbient = GLStateManager.getLightModel().ambient;
|
||||
final LightState light0 = GLStateManager.getLightDataStates()[0];
|
||||
final LightState light1 = GLStateManager.getLightDataStates()[1];
|
||||
private void stageLighting(GLContextState glCtx) {
|
||||
final MaterialState mat = glCtx.frontMaterial;
|
||||
final Vector4f lmAmbient = glCtx.lightModel.ambient;
|
||||
final LightState light0 = glCtx.lightDataStates[0];
|
||||
final LightState light1 = glCtx.lightDataStates[1];
|
||||
|
||||
putVec4(FFPUniformBlock.LIGHT_MODEL_AMBIENT, lmAmbient);
|
||||
putVec4(FFPUniformBlock.MATERIAL_EMISSION, mat.emission);
|
||||
@@ -284,8 +294,8 @@ public class Uniforms {
|
||||
putVec3(offset, lightVal.x * materialVal.x, lightVal.y * materialVal.y, lightVal.z * materialVal.z);
|
||||
}
|
||||
|
||||
private void stageCurrentColor() {
|
||||
final var color = GLStateManager.getColor();
|
||||
private void stageCurrentColor(GLContextState glCtx) {
|
||||
final var color = glCtx.color;
|
||||
putVec4(FFPUniformBlock.CURRENT_COLOR,
|
||||
Math.clamp(0f, 1f, color.getRed()),
|
||||
Math.clamp(0f, 1f, color.getGreen()),
|
||||
@@ -293,8 +303,8 @@ public class Uniforms {
|
||||
Math.clamp(0f, 1f, color.getAlpha()));
|
||||
}
|
||||
|
||||
private void stageTexGen() {
|
||||
final TexGenState tg = GLStateManager.getTextures().getTexGenState(0);
|
||||
private void stageTexGen(GLContextState glCtx) {
|
||||
final TexGenState tg = glCtx.textures.getTexGenState(0);
|
||||
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_S, tg.getObjectPlane(GL11.GL_S));
|
||||
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_T, tg.getObjectPlane(GL11.GL_T));
|
||||
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_R, tg.getObjectPlane(GL11.GL_R));
|
||||
@@ -305,8 +315,8 @@ public class Uniforms {
|
||||
putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_Q, tg.getEyePlane(GL11.GL_Q));
|
||||
}
|
||||
|
||||
private void stageClipPlanes() {
|
||||
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
|
||||
private void stageClipPlanes(GLContextState glCtx) {
|
||||
final ClipPlaneState cps = glCtx.clipPlaneState;
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
@@ -317,24 +327,24 @@ public class Uniforms {
|
||||
}
|
||||
}
|
||||
|
||||
private void stageFragment() {
|
||||
putFloat(FFPUniformBlock.ALPHA_REF, GLStateManager.getAlphaState().getReference());
|
||||
private void stageFragment(GLContextState glCtx) {
|
||||
putFloat(FFPUniformBlock.ALPHA_REF, glCtx.alphaState.getReference());
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final var envState = GLStateManager.getTextures().getTexEnvState(i);
|
||||
final var envState = glCtx.textures.getTexEnvState(i);
|
||||
putVec4(FFPUniformBlock.TEX_ENV_COLOR_0 + i * 16,
|
||||
envState.envColorR, envState.envColorG, envState.envColorB, envState.envColorA);
|
||||
}
|
||||
|
||||
putVec4(FFPUniformBlock.OVERLAY_COLOR,
|
||||
GLStateManager.getOverlayR(), GLStateManager.getOverlayG(),
|
||||
GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
|
||||
glCtx.overlayR, glCtx.overlayG,
|
||||
glCtx.overlayB, glCtx.overlayA);
|
||||
|
||||
final var secondary = GLStateManager.getSecondaryColor();
|
||||
final var secondary = glCtx.secondaryColor;
|
||||
putVec3(FFPUniformBlock.SECONDARY_COLOR, secondary.getRed(), secondary.getGreen(), secondary.getBlue());
|
||||
|
||||
// Mesa STATE_FOG_PARAMS_OPTIMIZED
|
||||
final FogState fog = GLStateManager.getFogState();
|
||||
final FogState fog = glCtx.fogState;
|
||||
final float start = fog.getStart();
|
||||
final float end = fog.getEnd();
|
||||
final float density = fog.getDensity();
|
||||
@@ -376,6 +386,15 @@ public class Uniforms {
|
||||
putVec3(offset + 32, m.m20(), m.m21(), m.m22());
|
||||
}
|
||||
|
||||
private void stageWeatherParams() {
|
||||
putVec4(FFPUniformBlock.WEATHER_PARAMS_0, WeatherParams.translateX, WeatherParams.translateY,
|
||||
WeatherParams.translateZ, WeatherParams.invRadius);
|
||||
putVec4(FFPUniformBlock.WEATHER_PARAMS_1, WeatherParams.cameraFracX, WeatherParams.cameraFracZ,
|
||||
WeatherParams.partialTicks, WeatherParams.age);
|
||||
putVec4(FFPUniformBlock.WEATHER_PARAMS_2, WeatherParams.rainScroll, WeatherParams.snowScroll,
|
||||
WeatherParams.rainStrength, 0.0f);
|
||||
}
|
||||
|
||||
private void putVec4(int offset, Vector4f v) {
|
||||
putVec4(offset, v.x, v.y, v.z, v.w);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
@@ -50,6 +51,13 @@ public final class VertexKey {
|
||||
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
|
||||
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
|
||||
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
|
||||
private static final long INSTANCING_MASK = 0x7L;
|
||||
|
||||
static {
|
||||
if (Instancing.VALUES.length > INSTANCING_MASK + 1) {
|
||||
throw new IllegalStateException("Instancing has outgrown the VertexKey field; widen INSTANCING_MASK");
|
||||
}
|
||||
}
|
||||
|
||||
public static final int TG_NONE = 0;
|
||||
public static final int TG_OBJ_LINEAR = 1;
|
||||
@@ -97,7 +105,7 @@ public final class VertexKey {
|
||||
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
|
||||
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
|
||||
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
|
||||
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & 0x3)]; }
|
||||
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & INSTANCING_MASK)]; }
|
||||
|
||||
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
@@ -126,54 +134,66 @@ public final class VertexKey {
|
||||
}
|
||||
|
||||
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
|
||||
return packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask, GLStateManager.ctx());
|
||||
}
|
||||
|
||||
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask, GLContextState glCtx) {
|
||||
long bits = 0;
|
||||
|
||||
final boolean lighting = GLStateManager.getLightingState().isEnabled();
|
||||
final boolean lighting = glCtx.lightingState.isEnabled();
|
||||
if (lighting) {
|
||||
bits |= (1L << BIT_LIGHTING);
|
||||
|
||||
for (int i = 0; i < FFP_LIGHT_COUNT; i++) {
|
||||
if (!GLStateManager.getLightStates()[i].isEnabled()) continue;
|
||||
if (!glCtx.lightStates[i].isEnabled()) continue;
|
||||
bits |= (1L << (BIT_LIGHT_BASE + i));
|
||||
// position.w == 0 means directional
|
||||
if (GLStateManager.getLightDataStates()[i].position.w == 0.0f) {
|
||||
if (glCtx.lightDataStates[i].position.w == 0.0f) {
|
||||
bits |= (1L << (BIT_LIGHT_DIR_BASE + i));
|
||||
}
|
||||
}
|
||||
|
||||
if (GLStateManager.getColorMaterial().isEnabled()) {
|
||||
if (glCtx.colorMaterial.isEnabled()) {
|
||||
bits |= (1L << BIT_COLOR_MATERIAL);
|
||||
final int cmMode = encodeColorMaterialMode(GLStateManager.getColorMaterialParameter().getValue());
|
||||
final int cmMode = encodeColorMaterialMode(glCtx.colorMaterialParameter.getValue());
|
||||
bits |= ((long) cmMode & 0x7) << BIT_COLOR_MAT_MODE;
|
||||
}
|
||||
|
||||
if (GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
if (glCtx.lightModel.colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
bits |= (1L << BIT_SEPARATE_SPECULAR);
|
||||
}
|
||||
}
|
||||
|
||||
if (GLStateManager.getFogMode().isEnabled()) {
|
||||
if (glCtx.fogMode.isEnabled()) {
|
||||
bits |= (1L << BIT_FOG);
|
||||
final int fogDistMode = GLStateManager.getFogState().getFogDistanceMode();
|
||||
final int fogDistMode = glCtx.fogState.getFogDistanceMode();
|
||||
bits |= ((long) fogDistMode & 0x3) << BIT_FOG_DIST_MODE;
|
||||
}
|
||||
|
||||
if (GLStateManager.getNormalizeState().isEnabled()) {
|
||||
if (glCtx.normalizeState.isEnabled()) {
|
||||
bits |= (1L << BIT_NORMALIZE);
|
||||
}
|
||||
if (GLStateManager.getRescaleNormalState().isEnabled()) {
|
||||
if (glCtx.rescaleNormalState.isEnabled()) {
|
||||
bits |= (1L << BIT_RESCALE_NORMAL);
|
||||
}
|
||||
|
||||
final TextureUnitArray texUnit = GLStateManager.getTextures();
|
||||
final TextureUnitArray texUnit = glCtx.textures;
|
||||
for (int i = 0; i < MAX_UNITS; i++) {
|
||||
if ((fragUnitMask & (1 << i)) != 0) {
|
||||
bits |= (1L << (BIT_UNIT_TEX_BASE + i));
|
||||
}
|
||||
if (!MatrixHelper.isIdentity(texUnit.getTextureUnitMatrix(i))) {
|
||||
bits |= (1L << (BIT_UNIT_TEXMAT_BASE + i));
|
||||
}
|
||||
}
|
||||
if (texUnit.texMatIdentityGen != glCtx.texMatrixGeneration) {
|
||||
int mask = 0;
|
||||
for (int i = 0; i < MAX_UNITS; i++) {
|
||||
if (!MatrixHelper.isIdentity(texUnit.getTextureUnitMatrix(i))) {
|
||||
mask |= 1 << i;
|
||||
}
|
||||
}
|
||||
texUnit.texMatIdentityMask = mask;
|
||||
texUnit.texMatIdentityGen = glCtx.texMatrixGeneration;
|
||||
}
|
||||
bits |= ((long) texUnit.texMatIdentityMask) << BIT_UNIT_TEXMAT_BASE;
|
||||
|
||||
// TexGen (unit 0 only) - per-coordinate mode if enabled.
|
||||
// TODO: per-unit texgen for units 2/3. Deferred - no MC code path uses glTexGen on those.
|
||||
@@ -210,11 +230,11 @@ public final class VertexKey {
|
||||
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
|
||||
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
|
||||
|
||||
if (GLStateManager.consumeUnit23TexCoordSetDuringDraw() && hasTexCoord) {
|
||||
if (GLStateManager.consumeUnit23TexCoordSetDuringDraw(glCtx) && hasTexCoord) {
|
||||
bits |= (1L << BIT_UNIT23_UV_FROM_UNIT0);
|
||||
}
|
||||
|
||||
if (GLStateManager.anyClipPlaneEnabled()) {
|
||||
if (GLStateManager.anyClipPlaneEnabled(glCtx)) {
|
||||
bits |= (1L << BIT_CLIP_PLANES);
|
||||
}
|
||||
|
||||
@@ -232,7 +252,7 @@ public final class VertexKey {
|
||||
}
|
||||
|
||||
static long withInstancing(long packed, Instancing kind) {
|
||||
return (packed & ~(0x3L << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
|
||||
return (packed & ~(INSTANCING_MASK << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
|
||||
}
|
||||
|
||||
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
|
||||
|
||||
@@ -21,6 +21,10 @@ public final class VertexShaderGenerator {
|
||||
emitOutputs(sb, key);
|
||||
|
||||
sb.append("void main() {\n");
|
||||
if (key.instancing() == Instancing.WEATHER) {
|
||||
sb.append(" ").append(InstancedGlslHelpers.weatherPrologue("u_", "a_", "a_TexCoord0",
|
||||
"vec4 weatherVertex", "vec4 weatherColor", "vec4 weatherTex0", "vec4 weatherTex1", "\n ")).append('\n');
|
||||
}
|
||||
emitPositionTransform(sb, key);
|
||||
if (key.lightingEnabled()) {
|
||||
emitNormalTransform(sb, key);
|
||||
@@ -58,7 +62,7 @@ public final class VertexShaderGenerator {
|
||||
final Instancing kind = key.instancing();
|
||||
sb.append("// Vertex attributes\n");
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
|
||||
if (kind == Instancing.PARTICLE) {
|
||||
if (kind == Instancing.PARTICLE || kind == Instancing.WEATHER) {
|
||||
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
|
||||
} else {
|
||||
if (key.hasVertexColor()) {
|
||||
@@ -88,6 +92,7 @@ public final class VertexShaderGenerator {
|
||||
return switch (key.instancing()) {
|
||||
case CUBE -> "cubeUv";
|
||||
case PARTICLE -> PARTICLE_UV;
|
||||
case WEATHER -> "weatherTex0";
|
||||
case NONE, TEMPLATE -> key.hasVertexTexCoord() ? "vec4(a_TexCoord0, 0.0, 1.0)" : "u_CurrentTexCoord0";
|
||||
};
|
||||
}
|
||||
@@ -134,6 +139,8 @@ public final class VertexShaderGenerator {
|
||||
sb.append(" // Position transform\n");
|
||||
if (key.instancing() == Instancing.PARTICLE) {
|
||||
sb.append(" vec4 pos4 = ").append(InstancedGlslHelpers.particlePos("a_InstCenterHalf", "a_Position")).append(";\n");
|
||||
} else if (key.instancing() == Instancing.WEATHER) {
|
||||
sb.append(" vec4 pos4 = weatherVertex;\n");
|
||||
} else {
|
||||
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
|
||||
}
|
||||
@@ -282,6 +289,7 @@ public final class VertexShaderGenerator {
|
||||
final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
|
||||
switch (key.instancing()) {
|
||||
case PARTICLE -> sb.append(" v_Color = a_InstColor;\n");
|
||||
case WEATHER -> sb.append(" v_Color = weatherColor;\n");
|
||||
case TEMPLATE, CUBE -> sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
|
||||
case NONE -> sb.append(" v_Color = ").append(src).append(";\n");
|
||||
}
|
||||
@@ -312,6 +320,7 @@ public final class VertexShaderGenerator {
|
||||
final String lightmap = switch (key.instancing()) {
|
||||
case PARTICLE, TEMPLATE -> "a_InstLightmap";
|
||||
case CUBE -> "a_InstLightmapScale.xy";
|
||||
case WEATHER -> "weatherTex1.xy";
|
||||
case NONE -> key.hasVertexLightmap() ? "a_TexCoord1" : "u_CurrentLightmapCoord";
|
||||
};
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(").append(lightmap).append(", 0.0, 1.0);\n");
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
|
||||
/**
|
||||
* One rain or snow column per instance.
|
||||
*/
|
||||
public final class WeatherInstancedAttribs {
|
||||
|
||||
public static final int LOC_COLUMN_SPAN = ParticleInstancedAttribs.LOC_CENTER_HALF;
|
||||
public static final int LOC_JITTER = ParticleInstancedAttribs.LOC_UV;
|
||||
public static final int LOC_PARAMS = InstancedAttribs.LOC_COLOR;
|
||||
|
||||
public static final int OFFSET_COLUMN_SPAN = 0;
|
||||
public static final int OFFSET_JITTER = 16;
|
||||
public static final int OFFSET_PARAMS = 32;
|
||||
public static final int STRIDE = 48;
|
||||
|
||||
public static void pointInstanceAttribs(long base) {
|
||||
GLStateManager.glVertexAttribPointer(LOC_COLUMN_SPAN, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_COLUMN_SPAN);
|
||||
GLStateManager.glVertexAttribPointer(LOC_JITTER, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_JITTER);
|
||||
GLStateManager.glVertexAttribPointer(LOC_PARAMS, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_PARAMS);
|
||||
}
|
||||
|
||||
static void enableInstanceArrays() {
|
||||
InstancedAttribs.enableInstanced(LOC_COLUMN_SPAN);
|
||||
InstancedAttribs.enableInstanced(LOC_JITTER);
|
||||
InstancedAttribs.enableInstanced(LOC_PARAMS);
|
||||
}
|
||||
|
||||
private WeatherInstancedAttribs() {}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
/**
|
||||
* The per-frame half of the rain and snow instancing, set once per weather draw.
|
||||
*/
|
||||
public final class WeatherParams {
|
||||
|
||||
public static float translateX;
|
||||
public static float translateY;
|
||||
public static float translateZ;
|
||||
public static float invRadius;
|
||||
|
||||
public static float cameraFracX;
|
||||
public static float cameraFracZ;
|
||||
public static float partialTicks;
|
||||
public static float age;
|
||||
|
||||
public static float rainScroll;
|
||||
public static float snowScroll;
|
||||
public static float rainStrength;
|
||||
|
||||
public static int generation;
|
||||
|
||||
private WeatherParams() {}
|
||||
|
||||
public static void set(float translateX, float translateY, float translateZ, float invRadius,
|
||||
float cameraFracX, float cameraFracZ, float partialTicks, float age,
|
||||
float rainScroll, float snowScroll, float rainStrength) {
|
||||
WeatherParams.translateX = translateX;
|
||||
WeatherParams.translateY = translateY;
|
||||
WeatherParams.translateZ = translateZ;
|
||||
WeatherParams.invRadius = invRadius;
|
||||
WeatherParams.cameraFracX = cameraFracX;
|
||||
WeatherParams.cameraFracZ = cameraFracZ;
|
||||
WeatherParams.partialTicks = partialTicks;
|
||||
WeatherParams.age = age;
|
||||
WeatherParams.rainScroll = rainScroll;
|
||||
WeatherParams.snowScroll = snowScroll;
|
||||
WeatherParams.rainStrength = rainStrength;
|
||||
generation++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* The four corners every rain and snow column is expanded from. x picks the end of the billboard, y picks
|
||||
* the bottom or top edge.
|
||||
*/
|
||||
public final class WeatherQuadMesh {
|
||||
|
||||
public static final int VERTEX_FLAGS = VertexFlags.TEXTURE_BIT;
|
||||
public static final int VERTEX_COUNT = 4;
|
||||
|
||||
private static final float[] CORNERS = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f };
|
||||
|
||||
private static int vao;
|
||||
private static int vbo;
|
||||
|
||||
private WeatherQuadMesh() {}
|
||||
|
||||
public static int vao() {
|
||||
if (vao != 0) return vao;
|
||||
|
||||
final ByteBuffer data = ByteBuffer.allocateDirect(CORNERS.length * Float.BYTES).order(ByteOrder.nativeOrder());
|
||||
for (float corner : CORNERS) data.putFloat(corner);
|
||||
data.flip();
|
||||
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
vbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
|
||||
final int corner = VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation();
|
||||
GLStateManager.glEnableVertexAttribArray(corner);
|
||||
GLStateManager.glVertexAttribPointer(corner, 2, GL11.GL_FLOAT, false, 2 * Float.BYTES, 0L);
|
||||
WeatherInstancedAttribs.enableInstanceArrays();
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
return vao;
|
||||
}
|
||||
|
||||
public static void bindInstanceBuffer(int bufferId) {
|
||||
GLStateManager.glBindVertexArray(vao());
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
|
||||
WeatherInstancedAttribs.pointInstanceAttribs(0L);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
public static void delete() {
|
||||
if (vbo != 0) {
|
||||
GLStateManager.glDeleteBuffers(vbo);
|
||||
vbo = 0;
|
||||
}
|
||||
if (vao != 0) {
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
vao = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,6 @@ package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface DeferredAlphaHandler {
|
||||
boolean isAlphaTestLocked();
|
||||
void deferAlphaTestToggle(boolean enabled);
|
||||
void deferAlphaFunc(int function, float reference);
|
||||
boolean deferAlphaTestToggle(boolean enabled);
|
||||
boolean deferAlphaFunc(int function, float reference);
|
||||
}
|
||||
@@ -21,6 +21,7 @@ public final class ClassDump {
|
||||
|
||||
public void dumpRFBClass(String className, ClassNodeHandle classNode, Object transformer) {
|
||||
if (!enabled) return;
|
||||
classNode.markDirty();
|
||||
dumpClass(className, classNode.getOriginalBytes(), classNode.computeBytes(), transformer);
|
||||
}
|
||||
}
|
||||
@@ -30,11 +30,20 @@ public final class DependencyVerifier {
|
||||
* @param checks Dependency checks to run
|
||||
*/
|
||||
public static void verify(Class<?> anchor, List<Check> checks) {
|
||||
final Check missing = firstMissing(anchor, checks);
|
||||
if (missing != null) {
|
||||
throw new RuntimeException(missing.errorMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** @return the first check whose resource is absent, or null when all pass. */
|
||||
public static Check firstMissing(Class<?> anchor, List<Check> checks) {
|
||||
for (Check check : checks) {
|
||||
if (anchor.getResource(check.resourcePath()) == null) {
|
||||
throw new RuntimeException(check.errorMessage());
|
||||
return check;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static List<Check> gtnhLibChecks(String productName) {
|
||||
|
||||
@@ -810,6 +810,8 @@ public class GLSMRedirector {
|
||||
.add("gluBuild2DMipmaps")
|
||||
.add("gluErrorString")
|
||||
);
|
||||
// Must honor disableErrorChecks like glGetError, or it throws on errors those checks left latched
|
||||
methodRedirects.put("org/lwjgl/opengl/Util", RedirectMap.newMap().add("checkGLError"));
|
||||
|
||||
// Quadric type replacements
|
||||
typeRedirects.put("org/lwjgl/util/glu/Sphere", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaSphere");
|
||||
|
||||
@@ -1,45 +1,46 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
|
||||
import lombok.Setter;
|
||||
|
||||
public class AlphaStateStack extends AlphaState implements IStateStack<AlphaState> {
|
||||
public final class AlphaStateStack extends AlphaState implements CowStateStack<AlphaStateStack> {
|
||||
|
||||
protected final AlphaState[] stack;
|
||||
|
||||
protected int pointer;
|
||||
private final CowDepths cow = new CowDepths();
|
||||
|
||||
@Setter private VanillaStateLayer<AlphaState> vanillaLayer;
|
||||
|
||||
public AlphaStateStack() {
|
||||
public AlphaStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new AlphaState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new AlphaState();
|
||||
}
|
||||
}
|
||||
|
||||
public AlphaStateStack push() {
|
||||
if(pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public AlphaStateStack pop() {
|
||||
if(pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
|
||||
}
|
||||
|
||||
final AlphaState saved = stack[--pointer];
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
final AlphaState saved = stack[s];
|
||||
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
|
||||
set(saved);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
public AlphaState readEffective(AlphaState out) {
|
||||
@@ -48,7 +49,8 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
|
||||
return out;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,51 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import lombok.Setter;
|
||||
|
||||
public class BlendStateStack extends BlendState implements IStateStack<BlendStateStack> {
|
||||
public final class BlendStateStack extends BlendState implements CowStateStack<BlendStateStack> {
|
||||
|
||||
protected final BlendState[] stack;
|
||||
|
||||
protected int pointer;
|
||||
private final CowDepths cow = new CowDepths(StateSet.R_BLEND);
|
||||
|
||||
private boolean funcUnknown;
|
||||
|
||||
@Setter private VanillaStateLayer<BlendState> vanillaLayer;
|
||||
|
||||
public BlendStateStack() {
|
||||
public BlendStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new BlendState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new BlendState();
|
||||
}
|
||||
}
|
||||
|
||||
public BlendStateStack push() {
|
||||
if(pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public BlendStateStack pop() {
|
||||
if(pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
|
||||
}
|
||||
|
||||
final BlendState saved = stack[--pointer];
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
final BlendState saved = stack[s];
|
||||
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
|
||||
setExceptFunc(saved);
|
||||
} else {
|
||||
set(saved);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
public void setFuncUnknownState() {
|
||||
@@ -64,11 +66,8 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
|
||||
return out;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
|
||||
public boolean topChanged() {
|
||||
return pointer > 0 && !sameAs(stack[pointer - 1]);
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BooleanState;
|
||||
@@ -13,17 +12,12 @@ import lombok.Setter;
|
||||
* saved when actually modified, dramatically reducing overhead when most states
|
||||
* don't change (e.g., GL_ENABLE_BIT saves ~270 states but typically only 3-4 change).
|
||||
*/
|
||||
public class BooleanStateStack extends BooleanState implements IStateStack<BooleanStateStack> {
|
||||
public class BooleanStateStack extends BooleanState implements CowStateStack<BooleanStateStack> {
|
||||
|
||||
protected final boolean[] stack;
|
||||
private final CowDepths cow = new CowDepths();
|
||||
@Setter private VanillaBooleanLayer vanillaLayer;
|
||||
|
||||
/**
|
||||
* The depth at which state has been saved. Compared against GLStateManager.getAttribDepth()
|
||||
* to determine if we need to save before modification or restore on pop.
|
||||
*/
|
||||
protected int savedDepth;
|
||||
|
||||
public BooleanStateStack(int glCap) {
|
||||
this(glCap, false, false);
|
||||
}
|
||||
@@ -52,24 +46,24 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
|
||||
stack = new boolean[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
}
|
||||
|
||||
// ==================== Traditional Stack Operations ====================
|
||||
|
||||
@Override
|
||||
public BooleanStateStack push() {
|
||||
if (savedDepth >= stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (savedDepth + 1) + " reached");
|
||||
}
|
||||
stack[savedDepth++] = vanillaValue();
|
||||
return this;
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BooleanStateStack pop() {
|
||||
if (savedDepth == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
restore(stack[--savedDepth]);
|
||||
return this;
|
||||
public void captureSlot(int slot) {
|
||||
stack[slot] = vanillaValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreSlot(int slot) {
|
||||
restore(stack[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
|
||||
public boolean isEffectivelyEnabled() {
|
||||
@@ -88,45 +82,6 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return savedDepth == 0;
|
||||
}
|
||||
|
||||
// ==================== Lazy Copy-on-Write Operations ====================
|
||||
|
||||
@Override
|
||||
public int pushDepth() {
|
||||
// No-op: global depth is managed by GLStateManager
|
||||
return GLStateManager.getAttribDepth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore state if it was modified at the current depth.
|
||||
* Only called by GLStateManager.popState() for states that registered as modified.
|
||||
*/
|
||||
@Override
|
||||
public BooleanStateStack popDepth() {
|
||||
// We're only called if we were modified, so savedDepth should match
|
||||
if (savedDepth > 0) {
|
||||
restore(stack[--savedDepth]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before modifying state. If we haven't saved at the current depth yet,
|
||||
* save the current value and register with GLStateManager for restoration.
|
||||
*/
|
||||
@Override
|
||||
public void beforeModify() {
|
||||
final int globalDepth = GLStateManager.getAttribDepth();
|
||||
if (savedDepth < globalDepth) {
|
||||
stack[savedDepth++] = vanillaValue();
|
||||
GLStateManager.registerModifiedState(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDepth() {
|
||||
return GLStateManager.getAttribDepth();
|
||||
@@ -134,7 +89,10 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
beforeModify();
|
||||
if (GLStateManager.getAttribDepth() > 0
|
||||
&& (stateUnknown || !GLStateManager.isCachingEnabled() || enabled != vanillaValue())) {
|
||||
beforeModify();
|
||||
}
|
||||
super.setEnabled(enabled);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +1,55 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
|
||||
public class Color4Stack extends Color4 implements IStateStack<Color4Stack> {
|
||||
public final class Color4Stack extends Color4 implements CowStateStack<Color4Stack> {
|
||||
|
||||
protected final Color4[] stack;
|
||||
private CowDepths cow = new CowDepths();
|
||||
|
||||
protected int pointer;
|
||||
|
||||
public Color4Stack() {
|
||||
public Color4Stack(int id) {
|
||||
cow.id = id;
|
||||
stack = new Color4[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new Color4();
|
||||
}
|
||||
}
|
||||
|
||||
public Color4Stack(Color4 color4) {
|
||||
this();
|
||||
public Color4Stack(int id, Color4 color4) {
|
||||
this(id);
|
||||
set(color4);
|
||||
}
|
||||
|
||||
public Color4Stack push() {
|
||||
if(pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
public Color4Stack restoreBit(int bit) {
|
||||
final int id = cow.id;
|
||||
cow = new CowDepths(bit);
|
||||
cow.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Color4Stack pop() {
|
||||
if(pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public boolean topChanged() {
|
||||
return pointer > 0 && !sameAs(stack[pointer - 1]);
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,51 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
|
||||
import lombok.Setter;
|
||||
|
||||
public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskStack> {
|
||||
public final class ColorMaskStack extends ColorMask implements CowStateStack<ColorMaskStack> {
|
||||
|
||||
protected final ColorMask[] stack;
|
||||
|
||||
protected int pointer;
|
||||
private final CowDepths cow = new CowDepths(StateSet.R_COLOR_MASK);
|
||||
|
||||
@Setter private VanillaStateLayer<ColorMask> vanillaLayer;
|
||||
|
||||
public ColorMaskStack() {
|
||||
public ColorMaskStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new ColorMask[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new ColorMask();
|
||||
}
|
||||
}
|
||||
|
||||
public ColorMaskStack push() {
|
||||
if(pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public ColorMaskStack pop() {
|
||||
if(pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
|
||||
}
|
||||
|
||||
final ColorMask saved = stack[--pointer];
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
final ColorMask saved = stack[s];
|
||||
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
|
||||
set(saved);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
public boolean topChanged() {
|
||||
return pointer > 0 && !sameAs(stack[pointer - 1]);
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
/**
|
||||
* __n__n__
|
||||
* .------`-\00/-'
|
||||
* / ## ## (oo)
|
||||
* / \## __ ./
|
||||
* |//YY \|/
|
||||
* ||| |||
|
||||
* Copy On Write Depths
|
||||
*/
|
||||
|
||||
public final class CowDepths {
|
||||
public int id = -1;
|
||||
public final int restoreBit;
|
||||
public final int restoreUnit;
|
||||
private final int[] slotDepth = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
private int pointer;
|
||||
|
||||
public CowDepths() {
|
||||
this(0, -1);
|
||||
}
|
||||
|
||||
public CowDepths(int restoreBit) {
|
||||
this(restoreBit, -1);
|
||||
}
|
||||
|
||||
public CowDepths(int restoreBit, int restoreUnit) {
|
||||
this.restoreBit = restoreBit;
|
||||
this.restoreUnit = restoreUnit;
|
||||
}
|
||||
|
||||
public int claim(int depth) {
|
||||
if (depth <= 0) return -1;
|
||||
if (pointer > 0 && slotDepth[pointer - 1] == depth) return -1;
|
||||
slotDepth[pointer] = depth;
|
||||
return pointer++;
|
||||
}
|
||||
|
||||
public int claimUnconditional() {
|
||||
slotDepth[pointer] = -1;
|
||||
return pointer++;
|
||||
}
|
||||
|
||||
public int pop() {
|
||||
return pointer > 0 ? --pointer : -1;
|
||||
}
|
||||
|
||||
public int discard(int poppedDepth) {
|
||||
if (pointer == 0) return -1;
|
||||
final int parent = poppedDepth - 1;
|
||||
if (parent > 0 && (pointer < 2 || slotDepth[pointer - 2] != parent)) {
|
||||
slotDepth[pointer - 1] = parent;
|
||||
return pointer - 1;
|
||||
}
|
||||
pointer--;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int top() {
|
||||
return pointer - 1;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ClipPlaneBooleanState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
|
||||
|
||||
/**
|
||||
* __n__n__
|
||||
* .------`-\00/-'
|
||||
* / ## ## (oo) Casts look redundant, they aren't. Casting to the
|
||||
* / \## __ ./ final class avoids an interface lookup on every
|
||||
* |//YY \|/ call. 74-85% faster push/pops in JMH testing.
|
||||
* ||| |||
|
||||
* Copy On Write Dispatch
|
||||
*/
|
||||
@SuppressWarnings("RedundantCast")
|
||||
public final class CowDispatch {
|
||||
|
||||
private CowDispatch() {}
|
||||
|
||||
public static final byte BLEND = 0;
|
||||
public static final byte BOOLEAN = 1;
|
||||
public static final byte CLIP_PLANE_BOOLEAN = 2;
|
||||
public static final byte TEXTURE_UNIT_BOOLEAN = 3;
|
||||
public static final byte COLOR_MASK = 4;
|
||||
public static final byte LIGHT_STATE = 5;
|
||||
public static final byte MATERIAL = 6;
|
||||
public static final byte STENCIL = 7;
|
||||
public static final byte MATRIX_MODE = 8;
|
||||
public static final byte LIGHT_MODEL = 9;
|
||||
public static final byte ALPHA = 10;
|
||||
public static final byte POINT = 11;
|
||||
public static final byte FOG = 12;
|
||||
public static final byte VEC4F = 13;
|
||||
public static final byte VEC3F = 14;
|
||||
public static final byte INTEGER = 15;
|
||||
public static final byte DEPTH = 16;
|
||||
public static final byte VIEWPORT = 17;
|
||||
public static final byte POLYGON = 18;
|
||||
public static final byte COLOR4 = 19;
|
||||
public static final byte LINE = 20;
|
||||
public static final byte TEXTURE_BINDING = 21;
|
||||
public static final byte TEX_ENV = 22;
|
||||
|
||||
public static byte kindOf(CowStateStack<?> s) {
|
||||
final Class<?> c = s.getClass();
|
||||
if (c == TextureUnitBooleanStateStack.class) return TEXTURE_UNIT_BOOLEAN;
|
||||
if (c == ClipPlaneBooleanState.class) return CLIP_PLANE_BOOLEAN;
|
||||
if (c == BooleanStateStack.class) return BOOLEAN;
|
||||
if (c == BlendStateStack.class) return BLEND;
|
||||
if (c == ColorMaskStack.class) return COLOR_MASK;
|
||||
if (c == LightStateStack.class) return LIGHT_STATE;
|
||||
if (c == MaterialStateStack.class) return MATERIAL;
|
||||
if (c == StencilStateStack.class) return STENCIL;
|
||||
if (c == MatrixModeStack.class) return MATRIX_MODE;
|
||||
if (c == LightModelStateStack.class) return LIGHT_MODEL;
|
||||
if (c == AlphaStateStack.class) return ALPHA;
|
||||
if (c == PointStateStack.class) return POINT;
|
||||
if (c == FogStateStack.class) return FOG;
|
||||
if (c == Vec4fStack.class) return VEC4F;
|
||||
if (c == Vec3fStack.class) return VEC3F;
|
||||
if (c == IntegerStateStack.class) return INTEGER;
|
||||
if (c == DepthStateStack.class) return DEPTH;
|
||||
if (c == ViewPortStateStack.class) return VIEWPORT;
|
||||
if (c == PolygonStateStack.class) return POLYGON;
|
||||
if (c == Color4Stack.class) return COLOR4;
|
||||
if (c == LineStateStack.class) return LINE;
|
||||
if (c == TextureBindingStack.class) return TEXTURE_BINDING;
|
||||
if (c == TexEnvState.class) return TEX_ENV;
|
||||
throw new IllegalStateException("Unknown CowStateStack class: " + c.getName());
|
||||
}
|
||||
|
||||
public static boolean topSlotChanged(byte kind, CowStateStack<?> s) {
|
||||
return switch (kind) {
|
||||
case BLEND -> ((BlendStateStack) s).topSlotChanged();
|
||||
case BOOLEAN -> ((BooleanStateStack) s).topSlotChanged();
|
||||
case CLIP_PLANE_BOOLEAN -> ((ClipPlaneBooleanState) s).topSlotChanged();
|
||||
case TEXTURE_UNIT_BOOLEAN -> ((TextureUnitBooleanStateStack) s).topSlotChanged();
|
||||
case COLOR_MASK -> ((ColorMaskStack) s).topSlotChanged();
|
||||
case LIGHT_STATE -> ((LightStateStack) s).topSlotChanged();
|
||||
case MATERIAL -> ((MaterialStateStack) s).topSlotChanged();
|
||||
case STENCIL -> ((StencilStateStack) s).topSlotChanged();
|
||||
case MATRIX_MODE -> ((MatrixModeStack) s).topSlotChanged();
|
||||
case LIGHT_MODEL -> ((LightModelStateStack) s).topSlotChanged();
|
||||
case ALPHA -> ((AlphaStateStack) s).topSlotChanged();
|
||||
case POINT -> ((PointStateStack) s).topSlotChanged();
|
||||
case FOG -> ((FogStateStack) s).topSlotChanged();
|
||||
case VEC4F -> ((Vec4fStack) s).topSlotChanged();
|
||||
case VEC3F -> ((Vec3fStack) s).topSlotChanged();
|
||||
case INTEGER -> ((IntegerStateStack) s).topSlotChanged();
|
||||
case DEPTH -> ((DepthStateStack) s).topSlotChanged();
|
||||
case VIEWPORT -> ((ViewPortStateStack) s).topSlotChanged();
|
||||
case POLYGON -> ((PolygonStateStack) s).topSlotChanged();
|
||||
case COLOR4 -> ((Color4Stack) s).topSlotChanged();
|
||||
case LINE -> ((LineStateStack) s).topSlotChanged();
|
||||
case TEXTURE_BINDING -> ((TextureBindingStack) s).topSlotChanged();
|
||||
case TEX_ENV -> ((TexEnvState) s).topSlotChanged();
|
||||
default -> throw new IllegalStateException("Unknown kind: " + kind);
|
||||
};
|
||||
}
|
||||
|
||||
public static void restoreSlot(byte kind, CowStateStack<?> s, int slot) {
|
||||
switch (kind) {
|
||||
case BLEND -> ((BlendStateStack) s).restoreSlot(slot);
|
||||
case BOOLEAN -> ((BooleanStateStack) s).restoreSlot(slot);
|
||||
case CLIP_PLANE_BOOLEAN -> ((ClipPlaneBooleanState) s).restoreSlot(slot);
|
||||
case TEXTURE_UNIT_BOOLEAN -> ((TextureUnitBooleanStateStack) s).restoreSlot(slot);
|
||||
case COLOR_MASK -> ((ColorMaskStack) s).restoreSlot(slot);
|
||||
case LIGHT_STATE -> ((LightStateStack) s).restoreSlot(slot);
|
||||
case MATERIAL -> ((MaterialStateStack) s).restoreSlot(slot);
|
||||
case STENCIL -> ((StencilStateStack) s).restoreSlot(slot);
|
||||
case MATRIX_MODE -> ((MatrixModeStack) s).restoreSlot(slot);
|
||||
case LIGHT_MODEL -> ((LightModelStateStack) s).restoreSlot(slot);
|
||||
case ALPHA -> ((AlphaStateStack) s).restoreSlot(slot);
|
||||
case POINT -> ((PointStateStack) s).restoreSlot(slot);
|
||||
case FOG -> ((FogStateStack) s).restoreSlot(slot);
|
||||
case VEC4F -> ((Vec4fStack) s).restoreSlot(slot);
|
||||
case VEC3F -> ((Vec3fStack) s).restoreSlot(slot);
|
||||
case INTEGER -> ((IntegerStateStack) s).restoreSlot(slot);
|
||||
case DEPTH -> ((DepthStateStack) s).restoreSlot(slot);
|
||||
case VIEWPORT -> ((ViewPortStateStack) s).restoreSlot(slot);
|
||||
case POLYGON -> ((PolygonStateStack) s).restoreSlot(slot);
|
||||
case COLOR4 -> ((Color4Stack) s).restoreSlot(slot);
|
||||
case LINE -> ((LineStateStack) s).restoreSlot(slot);
|
||||
case TEXTURE_BINDING -> ((TextureBindingStack) s).restoreSlot(slot);
|
||||
case TEX_ENV -> ((TexEnvState) s).restoreSlot(slot);
|
||||
default -> throw new IllegalStateException("Unknown kind: " + kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
/**
|
||||
* __n__n__
|
||||
* .------`-\00/-'
|
||||
* / ## ## (oo)
|
||||
* / \## __ ./
|
||||
* |//YY \|/
|
||||
* ||| |||
|
||||
* Copy On Write State Stack
|
||||
*/
|
||||
public interface CowStateStack<T> extends IStateStack<T> {
|
||||
CowDepths cowDepths();
|
||||
|
||||
void captureSlot(int slot);
|
||||
|
||||
void restoreSlot(int slot);
|
||||
|
||||
default int stackId() {
|
||||
return cowDepths().id;
|
||||
}
|
||||
|
||||
default void setStackId(int id) {
|
||||
cowDepths().id = id;
|
||||
}
|
||||
|
||||
default int restoreBit() {
|
||||
return cowDepths().restoreBit;
|
||||
}
|
||||
|
||||
default int restoreUnit() {
|
||||
return cowDepths().restoreUnit;
|
||||
}
|
||||
|
||||
default boolean topSlotChanged() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default int pushDepth() {
|
||||
return GLStateManager.getAttribDepth();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void beforeModify() {
|
||||
final int s = cowDepths().claim(GLStateManager.getAttribDepth());
|
||||
if (s >= 0) {
|
||||
captureSlot(s);
|
||||
GLStateManager.registerModifiedState(stackId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isEmpty() {
|
||||
return cowDepths().isEmpty();
|
||||
}
|
||||
|
||||
default void drain() {
|
||||
int s;
|
||||
while ((s = cowDepths().pop()) >= 0) restoreSlot(s);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
default T push() {
|
||||
captureSlot(cowDepths().claimUnconditional());
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
default T pop() {
|
||||
final int s = cowDepths().pop();
|
||||
if (s < 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
restoreSlot(s);
|
||||
return (T) this;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,51 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.DepthState;
|
||||
import lombok.Setter;
|
||||
|
||||
public class DepthStateStack extends DepthState implements IStateStack<DepthStateStack> {
|
||||
public final class DepthStateStack extends DepthState implements CowStateStack<DepthStateStack> {
|
||||
|
||||
protected final DepthState[] stack;
|
||||
|
||||
protected int pointer;
|
||||
private final CowDepths cow = new CowDepths(StateSet.R_DEPTH);
|
||||
|
||||
@Setter private VanillaStateLayer<DepthState> vanillaLayer;
|
||||
|
||||
private final DepthState effective = new DepthState();
|
||||
|
||||
public DepthStateStack() {
|
||||
public DepthStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new DepthState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new DepthState();
|
||||
}
|
||||
}
|
||||
|
||||
public DepthStateStack push() {
|
||||
if(pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public DepthStateStack pop() {
|
||||
if(pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
|
||||
}
|
||||
|
||||
final DepthState saved = stack[--pointer];
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
final DepthState saved = stack[s];
|
||||
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
|
||||
setExceptMask(saved);
|
||||
} else {
|
||||
set(saved);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
public boolean isEffectiveMaskEnabled() {
|
||||
@@ -52,11 +54,8 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
|
||||
return effective.isEnabled();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
|
||||
public boolean topChanged() {
|
||||
return pointer > 0 && !sameAs(stack[pointer - 1]);
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,38 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
|
||||
public class FogStateStack extends FogState implements IStateStack<FogStateStack> {
|
||||
public final class FogStateStack extends FogState implements CowStateStack<FogStateStack> {
|
||||
|
||||
protected final FogState[] stack;
|
||||
private final CowDepths cow = new CowDepths();
|
||||
|
||||
protected int pointer;
|
||||
|
||||
public FogStateStack() {
|
||||
public FogStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new FogState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new FogState();
|
||||
}
|
||||
}
|
||||
|
||||
public FogStateStack push() {
|
||||
if(pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public FogStateStack pop() {
|
||||
if(pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.IntegerState;
|
||||
|
||||
public class IntegerStateStack extends IntegerState implements IStateStack<IntegerStateStack> {
|
||||
public final class IntegerStateStack extends IntegerState implements CowStateStack<IntegerStateStack> {
|
||||
|
||||
protected final IntegerState[] stack;
|
||||
private CowDepths cow = new CowDepths();
|
||||
|
||||
protected int pointer;
|
||||
|
||||
public IntegerStateStack(int val) {
|
||||
public IntegerStateStack(int val, int id) {
|
||||
cow.id = id;
|
||||
setValue(val);
|
||||
stack = new IntegerState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
@@ -19,34 +18,40 @@ public class IntegerStateStack extends IntegerState implements IStateStack<Integ
|
||||
}
|
||||
}
|
||||
|
||||
public IntegerStateStack push() {
|
||||
if(pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
public IntegerStateStack restoreBit(int bit) {
|
||||
final int id = cow.id;
|
||||
cow = new CowDepths(bit);
|
||||
cow.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
public IntegerStateStack push(int value) {
|
||||
push().setValue(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IntegerStateStack pop() {
|
||||
if(pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
|
||||
public boolean topChanged() {
|
||||
return pointer > 0 && !sameAs(stack[pointer - 1]);
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
+19
-20
@@ -1,40 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
|
||||
|
||||
public class LightModelStateStack extends LightModelState implements IStateStack<LightModelStateStack> {
|
||||
public final class LightModelStateStack extends LightModelState implements CowStateStack<LightModelStateStack> {
|
||||
|
||||
protected final LightModelState[] stack;
|
||||
private final CowDepths cow = new CowDepths();
|
||||
|
||||
protected int pointer;
|
||||
|
||||
public LightModelStateStack() {
|
||||
public LightModelStateStack(int id) {
|
||||
super();
|
||||
cow.id = id;
|
||||
stack = new LightModelState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new LightModelState();
|
||||
}
|
||||
}
|
||||
|
||||
public LightModelStateStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public LightModelStateStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public boolean isEmpty() { return pointer == 0; }
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
|
||||
public class LightStateStack extends LightState implements IStateStack<LightStateStack> {
|
||||
public final class LightStateStack extends LightState implements CowStateStack<LightStateStack> {
|
||||
|
||||
protected final LightState[] stack;
|
||||
private final CowDepths cow = new CowDepths();
|
||||
|
||||
protected int pointer;
|
||||
|
||||
public LightStateStack(int light) {
|
||||
public LightStateStack(int light, int id) {
|
||||
super(light);
|
||||
cow.id = id;
|
||||
stack = new LightState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new LightState(light);
|
||||
}
|
||||
}
|
||||
|
||||
public LightStateStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public LightStateStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public boolean isEmpty() { return pointer == 0; }
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LineState;
|
||||
|
||||
/**
|
||||
* Stack for GL line state (width, stipple factor, stipple pattern).
|
||||
* Used by GL_LINE_BIT push/pop attrib.
|
||||
*/
|
||||
public class LineStateStack extends LineState implements IStateStack<LineStateStack> {
|
||||
public final class LineStateStack extends LineState implements CowStateStack<LineStateStack> {
|
||||
|
||||
protected final LineState[] stack;
|
||||
protected int pointer;
|
||||
private final CowDepths cow = new CowDepths(StateSet.R_LINE);
|
||||
|
||||
public LineStateStack() {
|
||||
public LineStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new LineState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new LineState();
|
||||
}
|
||||
}
|
||||
|
||||
public LineStateStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public LineStateStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
public boolean topChanged() {
|
||||
return pointer > 0 && !sameAs(stack[pointer - 1]);
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
|
||||
public class MaterialStateStack extends MaterialState implements IStateStack<MaterialStateStack> {
|
||||
public final class MaterialStateStack extends MaterialState implements CowStateStack<MaterialStateStack> {
|
||||
|
||||
protected final MaterialState[] stack;
|
||||
private final CowDepths cow = new CowDepths();
|
||||
|
||||
protected int pointer;
|
||||
|
||||
public MaterialStateStack(int face) {
|
||||
public MaterialStateStack(int face, int id) {
|
||||
super(face);
|
||||
cow.id = id;
|
||||
stack = new MaterialState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new MaterialState(face);
|
||||
}
|
||||
}
|
||||
|
||||
public MaterialStateStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public MaterialStateStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public boolean isEmpty() { return pointer == 0; }
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,38 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MatrixMode;
|
||||
|
||||
public class MatrixModeStack extends MatrixMode implements IStateStack<MatrixModeStack> {
|
||||
public final class MatrixModeStack extends MatrixMode implements CowStateStack<MatrixModeStack> {
|
||||
|
||||
protected final MatrixMode[] stack;
|
||||
private final CowDepths cow = new CowDepths();
|
||||
|
||||
protected int pointer;
|
||||
|
||||
public MatrixModeStack() {
|
||||
public MatrixModeStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new MatrixMode[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new MatrixMode();
|
||||
}
|
||||
}
|
||||
|
||||
public MatrixModeStack push() {
|
||||
if(pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public MatrixModeStack pop() {
|
||||
if(pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PointState;
|
||||
|
||||
/**
|
||||
* Stack for GL point state (size).
|
||||
* Used by GL_POINT_BIT push/pop attrib.
|
||||
*/
|
||||
public class PointStateStack extends PointState implements IStateStack<PointStateStack> {
|
||||
public final class PointStateStack extends PointState implements CowStateStack<PointStateStack> {
|
||||
|
||||
protected final PointState[] stack;
|
||||
protected int pointer;
|
||||
private final CowDepths cow = new CowDepths(StateSet.R_POINT);
|
||||
|
||||
public PointStateStack() {
|
||||
public PointStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new PointState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new PointState();
|
||||
}
|
||||
}
|
||||
|
||||
public PointStateStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public PointStateStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
public boolean topChanged() {
|
||||
return pointer > 0 && !sameAs(stack[pointer - 1]);
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
|
||||
|
||||
/**
|
||||
* Stack for GL polygon state (mode, offset, cull face mode, front face).
|
||||
* Used by GL_POLYGON_BIT push/pop attrib.
|
||||
*/
|
||||
public class PolygonStateStack extends PolygonState implements IStateStack<PolygonStateStack> {
|
||||
public final class PolygonStateStack extends PolygonState implements CowStateStack<PolygonStateStack> {
|
||||
|
||||
protected final PolygonState[] stack;
|
||||
protected int pointer;
|
||||
private final CowDepths cow = new CowDepths(StateSet.R_POLYGON);
|
||||
|
||||
public PolygonStateStack() {
|
||||
public PolygonStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new PolygonState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new PolygonState();
|
||||
}
|
||||
}
|
||||
|
||||
public PolygonStateStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public PolygonStateStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
public boolean topChanged() {
|
||||
return pointer > 0 && !sameAs(stack[pointer - 1]);
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static void endContext() {
|
||||
if (perContext == -1) {
|
||||
perContext = next;
|
||||
} else if (next != perContext) {
|
||||
throw new IllegalStateException("stack id count changed across contexts: " + perContext + " -> " + next);
|
||||
}
|
||||
next = -1;
|
||||
}
|
||||
|
||||
public static int nextId() {
|
||||
return next >= 0 ? next++ : -1;
|
||||
}
|
||||
|
||||
public static int 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;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.states.StencilState;
|
||||
|
||||
/**
|
||||
* Stack for GL stencil buffer state.
|
||||
* Used by GL_STENCIL_BUFFER_BIT push/pop attrib.
|
||||
*/
|
||||
public class StencilStateStack extends StencilState implements IStateStack<StencilStateStack> {
|
||||
public final class StencilStateStack extends StencilState implements CowStateStack<StencilStateStack> {
|
||||
|
||||
protected final StencilState[] stack;
|
||||
protected int pointer;
|
||||
private final CowDepths cow = new CowDepths(StateSet.R_STENCIL);
|
||||
|
||||
public StencilStateStack() {
|
||||
public StencilStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new StencilState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new StencilState();
|
||||
}
|
||||
}
|
||||
|
||||
public StencilStateStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public StencilStateStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
public boolean topChanged() {
|
||||
return pointer > 0 && !sameAs(stack[pointer - 1]);
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
+21
-25
@@ -1,49 +1,45 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
|
||||
|
||||
public class TextureBindingStack extends TextureBinding implements IStateStack<TextureBindingStack> {
|
||||
public final class TextureBindingStack extends TextureBinding implements CowStateStack<TextureBindingStack> {
|
||||
|
||||
protected final TextureBinding[] stack;
|
||||
private final CowDepths cow;
|
||||
|
||||
protected int pointer;
|
||||
|
||||
public TextureBindingStack() {
|
||||
public TextureBindingStack(int id, int unit) {
|
||||
cow = new CowDepths(StateSet.R_TEXTURE, unit);
|
||||
cow.id = id;
|
||||
stack = new TextureBinding[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new TextureBinding();
|
||||
}
|
||||
}
|
||||
|
||||
public TextureBindingStack push() {
|
||||
if(pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public TextureBindingStack pop() {
|
||||
if(pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public TextureBinding peek() {
|
||||
return stack[pointer];
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
public boolean topChanged() {
|
||||
return pointer > 0 && !sameAs(stack[pointer - 1]);
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
+3
-7
@@ -9,7 +9,7 @@ import org.lwjgl.opengl.GL13;
|
||||
* BooleanStateStack for per-texture-unit state.
|
||||
* Switches to correct unit (raw GL) before enable/disable calls.
|
||||
*/
|
||||
public class TextureUnitBooleanStateStack extends BooleanStateStack {
|
||||
public final class TextureUnitBooleanStateStack extends BooleanStateStack {
|
||||
private final int unitIndex;
|
||||
|
||||
public TextureUnitBooleanStateStack(int glCap, int unitIndex) {
|
||||
@@ -29,12 +29,8 @@ public class TextureUnitBooleanStateStack extends BooleanStateStack {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureUnitBooleanStateStack popDepth() {
|
||||
if (savedDepth > 0) {
|
||||
final boolean oldValue = stack[--savedDepth];
|
||||
setEnabledWithUnitSwitch(oldValue);
|
||||
}
|
||||
return this;
|
||||
public void restoreSlot(int slot) {
|
||||
setEnabledWithUnitSwitch(stack[slot]);
|
||||
}
|
||||
|
||||
private void setEnabledWithUnitSwitch(boolean enabled) {
|
||||
|
||||
@@ -1,47 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
/**
|
||||
* Stack for a Vector3f value (e.g. current normal).
|
||||
*/
|
||||
public class Vec3fStack implements IStateStack<Vec3fStack> {
|
||||
public final class Vec3fStack implements CowStateStack<Vec3fStack> {
|
||||
|
||||
private final Vector3f value;
|
||||
private final float[][] stack;
|
||||
private int pointer;
|
||||
private final CowDepths cow = new CowDepths();
|
||||
|
||||
public Vec3fStack(Vector3f value) {
|
||||
public Vec3fStack(Vector3f value, int id) {
|
||||
this.value = value;
|
||||
cow.id = id;
|
||||
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][3];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3fStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer][0] = value.x;
|
||||
stack[pointer][1] = value.y;
|
||||
stack[pointer][2] = value.z;
|
||||
pointer++;
|
||||
return this;
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3fStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
pointer--;
|
||||
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2]);
|
||||
return this;
|
||||
public void captureSlot(int s) {
|
||||
stack[s][0] = value.x;
|
||||
stack[s][1] = value.y;
|
||||
stack[s][2] = value.z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
public void restoreSlot(int s) {
|
||||
value.set(stack[s][0], stack[s][1], stack[s][2]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,43 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
/**
|
||||
* Stack for a Vector4f value (e.g. current texture coordinates).
|
||||
*/
|
||||
public class Vec4fStack implements IStateStack<Vec4fStack> {
|
||||
public final class Vec4fStack implements CowStateStack<Vec4fStack> {
|
||||
|
||||
private final Vector4f value;
|
||||
private final float[][] stack;
|
||||
private int pointer;
|
||||
private final CowDepths cow = new CowDepths();
|
||||
|
||||
public Vec4fStack(Vector4f value) {
|
||||
public Vec4fStack(Vector4f value, int id) {
|
||||
this.value = value;
|
||||
cow.id = id;
|
||||
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][4];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec4fStack push() {
|
||||
if (pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer][0] = value.x;
|
||||
stack[pointer][1] = value.y;
|
||||
stack[pointer][2] = value.z;
|
||||
stack[pointer][3] = value.w;
|
||||
pointer++;
|
||||
return this;
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec4fStack pop() {
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
pointer--;
|
||||
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2], stack[pointer][3]);
|
||||
return this;
|
||||
public void captureSlot(int s) {
|
||||
stack[s][0] = value.x;
|
||||
stack[s][1] = value.y;
|
||||
stack[s][2] = value.z;
|
||||
stack[s][3] = value.w;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
public void restoreSlot(int s) {
|
||||
value.set(stack[s][0], stack[s][1], stack[s][2], stack[s][3]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
|
||||
|
||||
public class ViewPortStateStack extends ViewportState implements IStateStack<ViewPortStateStack> {
|
||||
public final class ViewPortStateStack extends ViewportState implements CowStateStack<ViewPortStateStack> {
|
||||
|
||||
protected final ViewportState[] stack;
|
||||
private final CowDepths cow = new CowDepths(StateSet.R_VIEWPORT);
|
||||
|
||||
protected int pointer;
|
||||
|
||||
public ViewPortStateStack() {
|
||||
public ViewPortStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new ViewportState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new ViewportState();
|
||||
}
|
||||
}
|
||||
|
||||
public ViewPortStateStack push() {
|
||||
if(pointer == stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
return this;
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
public ViewPortStateStack pop() {
|
||||
if(pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
return this;
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
public boolean topChanged() {
|
||||
return pointer > 0 && !sameAs(stack[pointer - 1]);
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ public class BooleanState implements ISettableState<BooleanState> {
|
||||
if (stateUnknown || bypass || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.vendorIsAMD() && GLStateManager.isPoppingAttributes())) {
|
||||
stateUnknown = false;
|
||||
this.enabled = enabled;
|
||||
onEnabledChanged();
|
||||
if (!ffpStateOnly) {
|
||||
if (enabled) {
|
||||
RENDER_BACKEND.enable(this.glCap);
|
||||
@@ -58,6 +59,8 @@ public class BooleanState implements ISettableState<BooleanState> {
|
||||
}
|
||||
}
|
||||
|
||||
protected void onEnabledChanged() {}
|
||||
|
||||
@Override
|
||||
public BooleanState set(BooleanState state) {
|
||||
this.enabled = state.enabled;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowDepths;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
/**
|
||||
* Per-texture-unit GL_TEXTURE_ENV state for FFP emulation. Tracks the simple texenv mode and all GL_COMBINE sub-parameters.
|
||||
*/
|
||||
public class TexEnvState implements IStateStack<TexEnvState> {
|
||||
public final class TexEnvState implements CowStateStack<TexEnvState> {
|
||||
|
||||
public int mode;
|
||||
|
||||
@@ -30,22 +31,25 @@ public class TexEnvState implements IStateStack<TexEnvState> {
|
||||
public float envColorA;
|
||||
|
||||
private final TexEnvState[] stack;
|
||||
private int pointer;
|
||||
private final CowDepths cow;
|
||||
private final boolean isStackEntry;
|
||||
|
||||
public TexEnvState() {
|
||||
public TexEnvState(int id) {
|
||||
this(false);
|
||||
cow.id = id;
|
||||
}
|
||||
|
||||
private TexEnvState(boolean isStackEntry) {
|
||||
this.isStackEntry = isStackEntry;
|
||||
if (isStackEntry) {
|
||||
stack = null;
|
||||
cow = null;
|
||||
} else {
|
||||
stack = new TexEnvState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new TexEnvState(true);
|
||||
}
|
||||
cow = new CowDepths();
|
||||
}
|
||||
reset();
|
||||
}
|
||||
@@ -80,31 +84,27 @@ public class TexEnvState implements IStateStack<TexEnvState> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TexEnvState push() {
|
||||
if (stack == null) throw new IllegalStateException("Cannot push stack entry");
|
||||
if (pointer >= stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
stack[pointer++].copyFrom(this);
|
||||
return this;
|
||||
public CowDepths cowDepths() {
|
||||
if (cow == null) throw new IllegalStateException("Cannot push/pop a stack entry");
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TexEnvState pop() {
|
||||
if (stack == null) throw new IllegalStateException("Cannot pop stack entry");
|
||||
if (pointer == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
copyFrom(stack[--pointer]);
|
||||
return this;
|
||||
public void captureSlot(int s) {
|
||||
stack[s].copyFrom(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
public void restoreSlot(int s) {
|
||||
copyFrom(stack[s]);
|
||||
}
|
||||
|
||||
public boolean isCombineMode() {
|
||||
return mode == GL13.GL_COMBINE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow == null ? -1 : cow.id;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.TextureBindingStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.TextureUnitBooleanStateStack;
|
||||
import lombok.Getter;
|
||||
@@ -8,7 +10,11 @@ import org.joml.Matrix4fStack;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TextureUnitArray {
|
||||
private final List<IStateStack<?>> idStacks = new ArrayList<>();
|
||||
private final TextureBindingStack[] bindings;
|
||||
private final TextureUnitBooleanStateStack[] states;
|
||||
private final TextureUnitBooleanStateStack[] texture1DStates;
|
||||
@@ -22,6 +28,9 @@ public class TextureUnitArray {
|
||||
@Getter
|
||||
public final Matrix4fStack[] textureMatricies;
|
||||
|
||||
public int texMatIdentityMask;
|
||||
public int texMatIdentityGen;
|
||||
|
||||
public TextureUnitArray() {
|
||||
bindings = new TextureBindingStack[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
states = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
@@ -36,20 +45,35 @@ public class TextureUnitArray {
|
||||
textureMatricies = new Matrix4fStack[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
|
||||
for (int i = 0; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
|
||||
bindings[i] = new TextureBindingStack();
|
||||
states[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_2D, i);
|
||||
texture1DStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_1D, i);
|
||||
texture3DStates[i] = new TextureUnitBooleanStateStack(GL12.GL_TEXTURE_3D, i);
|
||||
texGenSStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_S, i);
|
||||
texGenTStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_T, i);
|
||||
texGenRStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_R, i);
|
||||
texGenQStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_Q, i);
|
||||
bindings[i] = member(new TextureBindingStack(StackIdAllocator.nextId(), i));
|
||||
states[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_2D, i));
|
||||
texture1DStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_1D, i));
|
||||
texture3DStates[i] = track(new TextureUnitBooleanStateStack(GL12.GL_TEXTURE_3D, i));
|
||||
texGenSStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_S, i));
|
||||
texGenTStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_T, i));
|
||||
texGenRStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_R, i));
|
||||
texGenQStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_Q, i));
|
||||
texGenStates[i] = new TexGenState();
|
||||
texEnvStates[i] = new TexEnvState();
|
||||
texEnvStates[i] = member(new TexEnvState(StackIdAllocator.nextId()));
|
||||
textureMatricies[i] = new Matrix4fStack(GLStateManager.MAX_TEXTURE_STACK_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends TextureUnitBooleanStateStack> T track(T s) {
|
||||
s.setStackId(StackIdAllocator.nextId());
|
||||
idStacks.add(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
private <T extends IStateStack<?>> T member(T s) {
|
||||
idStacks.add(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
public List<IStateStack<?>> idStacks() {
|
||||
return idStacks;
|
||||
}
|
||||
|
||||
public TextureBindingStack getTextureUnitBindings(int index) {
|
||||
return bindings[index];
|
||||
}
|
||||
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@GLCoreTest
|
||||
public class GLSM_DisplayList_StateSet_GLTest {
|
||||
|
||||
private final List<Integer> listsToDelete = new ArrayList<>();
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
DisplayListManager.abortCompilation();
|
||||
while (GLStateManager.getAttribDepth() > 0) {
|
||||
GLStateManager.popState();
|
||||
}
|
||||
for (int id : listsToDelete) {
|
||||
GLStateManager.glDeleteLists(id, 1);
|
||||
}
|
||||
listsToDelete.clear();
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
|
||||
private int newList() {
|
||||
final int id = GLStateManager.glGenLists(1);
|
||||
listsToDelete.add(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static Int2IntMap commandCounts(int listId) {
|
||||
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(listId);
|
||||
assertNotNull(compiled, "compiled display list must exist for id " + listId);
|
||||
return compiled.getCommandCounts();
|
||||
}
|
||||
|
||||
@Test
|
||||
void compileBracketDoesNotThrowAndLeavesLiveStateUntouched() {
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
|
||||
final int func0 = GLStateManager.getAlphaState().getFunction();
|
||||
final float ref0 = GLStateManager.getAlphaState().getReference();
|
||||
final int unit0 = GLStateManager.getActiveTextureUnit();
|
||||
final int depth0 = GLStateManager.getAttribDepth();
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
final int d = GLStateManager.pushState(StateSet.CUTOUT);
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.popStateTo(d);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
verifyIsEnabled(GL11.GL_ALPHA_TEST, true, "cache alpha test enable unchanged after COMPILE-only bracket");
|
||||
assertEquals(func0, GLStateManager.getAlphaState().getFunction(), "cache alpha func unchanged after COMPILE-only bracket");
|
||||
assertEquals(ref0, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref unchanged after COMPILE-only bracket");
|
||||
assertEquals(unit0, GLStateManager.getActiveTextureUnit(), "cache active unit unchanged after COMPILE-only bracket");
|
||||
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after COMPILE-only bracket");
|
||||
assertEquals(2, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "push and pop each record one COMPLEX_REF");
|
||||
}
|
||||
|
||||
@Test
|
||||
void compiledBracketReplayRestoresState() {
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
final int d = GLStateManager.pushState(StateSet.CUTOUT);
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.popStateTo(d);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.9f);
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
final int depth0 = GLStateManager.getAttribDepth();
|
||||
|
||||
GLStateManager.glCallList(list);
|
||||
|
||||
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored by replay");
|
||||
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored by replay");
|
||||
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled by replay");
|
||||
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored by replay");
|
||||
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "driver active unit restored by replay");
|
||||
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged by balanced replay");
|
||||
}
|
||||
|
||||
@Test
|
||||
void compileAndExecuteBracketIsBalanced() {
|
||||
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.9f);
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
final int depth0 = GLStateManager.getAttribDepth();
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
|
||||
final int d = GLStateManager.pushState(StateSet.CUTOUT);
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.popStateTo(d);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored after GL_COMPILE_AND_EXECUTE bracket");
|
||||
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored after GL_COMPILE_AND_EXECUTE bracket");
|
||||
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled after GL_COMPILE_AND_EXECUTE bracket");
|
||||
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored after GL_COMPILE_AND_EXECUTE bracket");
|
||||
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after balanced GL_COMPILE_AND_EXECUTE bracket");
|
||||
|
||||
GLStateManager.glCallList(list);
|
||||
GLStateManager.glCallList(list);
|
||||
|
||||
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored after replay");
|
||||
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored after replay");
|
||||
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled after replay");
|
||||
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored after replay");
|
||||
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after replay");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedListsPushInBothLevels() {
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
final boolean cull0 = GLStateManager.glIsEnabled(GL11.GL_CULL_FACE);
|
||||
final int depth0 = GLStateManager.getAttribDepth();
|
||||
|
||||
final int parent = newList();
|
||||
final int child = newList();
|
||||
|
||||
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
|
||||
final int dParent = GLStateManager.pushState(StateSet.BLEND);
|
||||
GLStateManager.glNewList(child, GL11.GL_COMPILE);
|
||||
final int dChild = GLStateManager.pushState(StateSet.CULL);
|
||||
if (cull0) {
|
||||
GLStateManager.disableCull();
|
||||
} else {
|
||||
GLStateManager.enableCull();
|
||||
}
|
||||
GLStateManager.popStateTo(dChild);
|
||||
GLStateManager.glEndList();
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.popStateTo(dParent);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged by nested COMPILE-only bracket");
|
||||
|
||||
GLStateManager.glCallList(parent);
|
||||
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored by the parent BLEND bracket after parent replay");
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored after parent replay");
|
||||
|
||||
GLStateManager.glCallList(child);
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored after child replay");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedCompileAndExecuteChildInsideCompileParent() {
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
final boolean cull0 = GLStateManager.glIsEnabled(GL11.GL_CULL_FACE);
|
||||
final int depth0 = GLStateManager.getAttribDepth();
|
||||
|
||||
final int parent = newList();
|
||||
final int child = newList();
|
||||
|
||||
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
|
||||
final int dParent = GLStateManager.pushState(StateSet.BLEND);
|
||||
|
||||
GLStateManager.glNewList(child, GL11.GL_COMPILE_AND_EXECUTE);
|
||||
final int dChild = GLStateManager.pushState(StateSet.CULL);
|
||||
if (cull0) {
|
||||
GLStateManager.disableCull();
|
||||
} else {
|
||||
GLStateManager.enableCull();
|
||||
}
|
||||
GLStateManager.popStateTo(dChild);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored live immediately after the COMPILE_AND_EXECUTE child bracket closes");
|
||||
|
||||
GLStateManager.popStateTo(dParent);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after balanced nested bracket");
|
||||
assertEquals(2, commandCounts(parent).getOrDefault(GLCommand.COMPLEX_REF, 0), "parent list records exactly the BLEND push and pop");
|
||||
assertEquals(2, commandCounts(child).getOrDefault(GLCommand.COMPLEX_REF, 0), "child list records exactly the CULL push and pop");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unbalancedBracketIsClosedAtEndList() {
|
||||
GLStateManager.enableBlend();
|
||||
final boolean blend0 = GLStateManager.glIsEnabled(GL11.GL_BLEND);
|
||||
final int depth0 = GLStateManager.getAttribDepth();
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.pushState(StateSet.BATCH);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.glEndList();
|
||||
|
||||
assertEquals(2, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "glEndList closes the unbalanced push, recording a matching pop");
|
||||
|
||||
GLStateManager.glCallList(list);
|
||||
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after replaying an auto-closed bracket");
|
||||
verifyIsEnabled(GL11.GL_BLEND, blend0, "blend enable unchanged after replaying an auto-closed bracket");
|
||||
|
||||
final int other = newList();
|
||||
assertDoesNotThrow(() -> {
|
||||
GLStateManager.glNewList(other, GL11.GL_COMPILE);
|
||||
GLStateManager.glEndList();
|
||||
}, "an unrelated list must compile normally after an auto-closed bracket");
|
||||
}
|
||||
|
||||
@Test
|
||||
void glPushAttribRecordsExactlyOnce() {
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
GLStateManager.glPopAttrib();
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final Int2IntMap counts = commandCounts(list);
|
||||
assertEquals(1, counts.getOrDefault(GLCommand.PUSH_ATTRIB, 0), "glPushAttrib records exactly one PUSH_ATTRIB opcode");
|
||||
assertEquals(1, counts.getOrDefault(GLCommand.POP_ATTRIB, 0), "glPopAttrib records exactly one POP_ATTRIB opcode");
|
||||
assertEquals(0, counts.getOrDefault(GLCommand.COMPLEX_REF, 0), "glPushAttrib/glPopAttrib never record a COMPLEX_REF");
|
||||
}
|
||||
|
||||
@Test
|
||||
void livePushStraddlesOpenList() {
|
||||
GLStateManager.enableBlend();
|
||||
final int d = GLStateManager.pushState(StateSet.BLEND);
|
||||
GLStateManager.disableBlend();
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertEquals(d, GLStateManager.getAttribDepth(), "live attrib depth restored to the pre-bracket value");
|
||||
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored by the live pop that straddled the open-then-closed list");
|
||||
}
|
||||
|
||||
@Test
|
||||
void livePopInsideOpenListPopsLiveAndRecordsNothing() {
|
||||
GLStateManager.enableBlend();
|
||||
final int d = GLStateManager.pushState(StateSet.BLEND);
|
||||
GLStateManager.disableBlend();
|
||||
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.popStateTo(d);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
assertEquals(d, GLStateManager.getAttribDepth(), "live attrib depth restored by the live pop performed inside the open list");
|
||||
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored live by the pop of a pre-list push");
|
||||
assertEquals(0, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "popping a pre-existing live push records nothing into the list");
|
||||
}
|
||||
|
||||
@Test
|
||||
void virtualDepthOverflowThrowsAtRecordTime() {
|
||||
final int startDepth = GLStateManager.getAttribDepth();
|
||||
final int list = newList();
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
|
||||
final int pushes = GLStateManager.MAX_ATTRIB_STACK_DEPTH - startDepth;
|
||||
for (int i = 0; i < pushes; i++) {
|
||||
GLStateManager.pushState(StateSet.CUTOUT);
|
||||
}
|
||||
assertThrows(IllegalStateException.class, () -> GLStateManager.pushState(StateSet.CUTOUT), "virtual depth overflow beyond MAX_ATTRIB_STACK_DEPTH must throw at record time");
|
||||
|
||||
DisplayListManager.abortCompilation();
|
||||
|
||||
assertEquals(startDepth, GLStateManager.getAttribDepth(), "aborting the overflowed compilation leaves live attrib depth untouched");
|
||||
}
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@GLCoreTest
|
||||
public class GLSM_PopAttribMaskFidelity_GLTest {
|
||||
|
||||
private static int scanClipPlaneMask() {
|
||||
final BooleanStateStack[] planes = GLStateManager.getClipPlaneStates();
|
||||
int mask = 0;
|
||||
for (int i = 0; i < planes.length; i++) {
|
||||
if (planes[i].isEnabled()) mask |= (1 << i);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
private static void verifyClipPlaneMask(String message) {
|
||||
final int scanned = scanClipPlaneMask();
|
||||
assertEquals(scanned, GLStateManager.ctx().clipPlaneEnabledMask, message + " - mask mismatch");
|
||||
assertEquals(scanned != 0, GLStateManager.anyClipPlaneEnabled(), message + " - anyClipPlaneEnabled mismatch");
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorBufferBitDoesNotRestoreCull() {
|
||||
try {
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Initial");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.glPopAttrib();
|
||||
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - Not a GL_COLOR_BUFFER_BIT member, must stay toggled");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableBitRestoresCull() {
|
||||
try {
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Initial");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - After enable");
|
||||
GLStateManager.glPopAttrib();
|
||||
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - GL_ENABLE_BIT member, must restore");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void innerNonMemberPopKeepsChange_OuterMemberPopRestores() {
|
||||
try {
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Initial");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - After enable inside inner bracket");
|
||||
|
||||
GLStateManager.glPopAttrib();
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - Inner pop is not a member, must stay toggled");
|
||||
|
||||
GLStateManager.glPopAttrib();
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Outer pop is a member, must restore to pre-inner value");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void modifyBeforeInnerPush_SurvivesInnerNonMemberPop() {
|
||||
try {
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Initial");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - After enable at outer depth");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glPopAttrib();
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - Inner pop is not a member, must stay toggled");
|
||||
|
||||
GLStateManager.glPopAttrib();
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Outer pop is a member, must restore");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoModifiesAtOneDepthRestoreOnce() {
|
||||
try {
|
||||
verifyIsEnabled(GL11.GL_BLEND, false, "Blend - Initial");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glEnable(GL11.GL_BLEND);
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glEnable(GL11.GL_BLEND);
|
||||
verifyIsEnabled(GL11.GL_BLEND, true, "Blend - After multiple toggles at one depth");
|
||||
|
||||
GLStateManager.glPopAttrib();
|
||||
verifyIsEnabled(GL11.GL_BLEND, false, "Blend - GL_COLOR_BUFFER_BIT member, must restore original");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void lightingBitRestoresLightingAndLightEnables() {
|
||||
try {
|
||||
verifyIsEnabled(GL11.GL_LIGHTING, false, "Lighting - Initial");
|
||||
verifyIsEnabled(GL11.GL_LIGHT0, false, "Light0 - Initial");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
|
||||
GLStateManager.glEnable(GL11.GL_LIGHTING);
|
||||
GLStateManager.glEnable(GL11.GL_LIGHT0);
|
||||
verifyIsEnabled(GL11.GL_LIGHTING, true, "Lighting - After enable");
|
||||
verifyIsEnabled(GL11.GL_LIGHT0, true, "Light0 - After enable");
|
||||
|
||||
GLStateManager.glPopAttrib();
|
||||
verifyIsEnabled(GL11.GL_LIGHTING, false, "Lighting - GL_LIGHTING_BIT member, must restore");
|
||||
verifyIsEnabled(GL11.GL_LIGHT0, false, "Light0 - GL_LIGHTING_BIT member, must restore");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_LIGHTING);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHT0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void enableDisableTracksClipPlaneMask() {
|
||||
try {
|
||||
verifyClipPlaneMask("Initial");
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_CLIP_PLANE0);
|
||||
verifyIsEnabled(GL11.GL_CLIP_PLANE0, true, "Clip0 - After enable");
|
||||
verifyClipPlaneMask("After enable");
|
||||
|
||||
GLStateManager.glDisable(GL11.GL_CLIP_PLANE0);
|
||||
verifyIsEnabled(GL11.GL_CLIP_PLANE0, false, "Clip0 - After disable");
|
||||
verifyClipPlaneMask("After disable");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_CLIP_PLANE0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void transformBitPopRestoresClipPlaneAndMask() {
|
||||
try {
|
||||
verifyClipPlaneMask("Initial");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_TRANSFORM_BIT);
|
||||
GLStateManager.glEnable(GL11.GL_CLIP_PLANE1);
|
||||
verifyIsEnabled(GL11.GL_CLIP_PLANE1, true, "Clip1 - After enable inside GL_TRANSFORM_BIT");
|
||||
verifyClipPlaneMask("After enable inside GL_TRANSFORM_BIT");
|
||||
|
||||
GLStateManager.glPopAttrib();
|
||||
verifyIsEnabled(GL11.GL_CLIP_PLANE1, false, "Clip1 - GL_TRANSFORM_BIT member, must restore");
|
||||
verifyClipPlaneMask("After GL_TRANSFORM_BIT pop");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_CLIP_PLANE1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorBufferBitPopDoesNotRestoreClipPlaneButMaskStaysFaithful() {
|
||||
try {
|
||||
verifyClipPlaneMask("Initial");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glEnable(GL11.GL_CLIP_PLANE2);
|
||||
verifyIsEnabled(GL11.GL_CLIP_PLANE2, true, "Clip2 - After enable inside GL_COLOR_BUFFER_BIT");
|
||||
verifyClipPlaneMask("After enable inside GL_COLOR_BUFFER_BIT");
|
||||
|
||||
GLStateManager.glPopAttrib();
|
||||
verifyIsEnabled(GL11.GL_CLIP_PLANE2, true, "Clip2 - Not a GL_COLOR_BUFFER_BIT member, must stay toggled");
|
||||
verifyClipPlaneMask("After GL_COLOR_BUFFER_BIT pop");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_CLIP_PLANE2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,709 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@GLCoreTest
|
||||
public class GLSM_StateSet_GLTest {
|
||||
|
||||
private static int driverTextureBinding(int unit) {
|
||||
final int saved = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
|
||||
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
final int binding = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
GL13.glActiveTexture(saved);
|
||||
return binding;
|
||||
}
|
||||
|
||||
private static boolean[] driverColorWriteMask() {
|
||||
final ByteBuffer buf = BufferUtils.createByteBuffer(16);
|
||||
GL11.glGetBoolean(GL11.GL_COLOR_WRITEMASK, buf);
|
||||
return new boolean[] { buf.get(0) != 0, buf.get(1) != 0, buf.get(2) != 0, buf.get(3) != 0 };
|
||||
}
|
||||
|
||||
private static int compileShader(int type, String src) {
|
||||
final int s = GL20.glCreateShader(type);
|
||||
GL20.glShaderSource(s, src);
|
||||
GL20.glCompileShader(s);
|
||||
assertEquals(GL11.GL_TRUE, GL20.glGetShaderi(s, GL20.GL_COMPILE_STATUS), () -> "shader compile failed: " + GL20.glGetShaderInfoLog(s, 4096));
|
||||
return s;
|
||||
}
|
||||
|
||||
private static final String TRIVIAL_VS = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }\n";
|
||||
private static final String TRIVIAL_FS = "#version 330 core\nout vec4 o;\nvoid main(){ o = vec4(1.0); }\n";
|
||||
|
||||
private static int linkTrivialProgram() {
|
||||
final int v = compileShader(GL20.GL_VERTEX_SHADER, TRIVIAL_VS);
|
||||
final int f = compileShader(GL20.GL_FRAGMENT_SHADER, TRIVIAL_FS);
|
||||
final int p = GL20.glCreateProgram();
|
||||
GL20.glAttachShader(p, v);
|
||||
GL20.glAttachShader(p, f);
|
||||
GL20.glLinkProgram(p);
|
||||
assertEquals(GL11.GL_TRUE, GL20.glGetProgrami(p, GL20.GL_LINK_STATUS), () -> "link failed: " + GL20.glGetProgramInfoLog(p, 4096));
|
||||
GL20.glDeleteShader(v);
|
||||
GL20.glDeleteShader(f);
|
||||
return p;
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchRestoresBlendAxisAndLightingBooleanButNotStencil() {
|
||||
try {
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.disableLighting();
|
||||
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.BATCH);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
|
||||
GLStateManager.enableLighting();
|
||||
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
verifyIsEnabled(GL11.GL_BLEND, true, "Blend enable - restored by BATCH pop");
|
||||
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb");
|
||||
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_DST_RGB), "driver blend dst rgb");
|
||||
assertEquals(GL11.GL_SRC_ALPHA, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb");
|
||||
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, GLStateManager.getBlendState().getDstRgb(), "cache blend dst rgb");
|
||||
verifyIsEnabled(GL11.GL_LIGHTING, false, "Lighting enable - restored by BATCH pop");
|
||||
verifyIsEnabled(GL11.GL_STENCIL_TEST, true, "Stencil test - not a BATCH member, must stay toggled");
|
||||
} finally {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.disableLighting();
|
||||
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void fontRestoresDepthAxisAndShadeModelButNotCull() {
|
||||
try {
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.FONT);
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
|
||||
GLStateManager.glShadeModel(GL11.GL_FLAT);
|
||||
GLStateManager.glCullFace(GL11.GL_FRONT);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
verifyIsEnabled(GL11.GL_DEPTH_TEST, true, "Depth test enable - restored by FONT pop");
|
||||
assertEquals(GL11.GL_LEQUAL, GLStateManager.getDepthState().getFunc(), "cache depth func");
|
||||
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth func");
|
||||
assertEquals(GL11.GL_SMOOTH, GLStateManager.getShadeModelState().getValue(), "cache shade model");
|
||||
assertEquals(GL11.GL_FRONT, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - not a FONT member, must stay toggled");
|
||||
assertEquals(GL11.GL_FRONT, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - not a FONT member, must stay toggled");
|
||||
} finally {
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_LESS);
|
||||
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void fontPipelineRestoresBlendAndDepthFuncButNotProgram() {
|
||||
final int p0 = linkTrivialProgram();
|
||||
final int p1 = linkTrivialProgram();
|
||||
try {
|
||||
GLStateManager.glUseProgram(p0);
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.FONT_PIPELINE);
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
|
||||
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
|
||||
GLStateManager.glUseProgram(p1);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertEquals(GL11.GL_SRC_ALPHA, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - restored by FONT_PIPELINE pop");
|
||||
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb - restored by FONT_PIPELINE pop");
|
||||
assertEquals(GL11.GL_LEQUAL, GLStateManager.getDepthState().getFunc(), "cache depth func - restored by FONT_PIPELINE pop");
|
||||
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth func - restored by FONT_PIPELINE pop");
|
||||
assertEquals(p1, GLStateManager.getActiveProgram(), "program - not a FONT_PIPELINE member, must stay toggled");
|
||||
|
||||
GLStateManager.glUseProgram(p0);
|
||||
} finally {
|
||||
GLStateManager.glUseProgram(0);
|
||||
GLStateManager.glDeleteProgram(p0);
|
||||
GLStateManager.glDeleteProgram(p1);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glDepthFunc(GL11.GL_LESS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void handRestoresBlendAxisAndUnit1TexturingButNotColorMask() {
|
||||
try {
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.HAND);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
|
||||
GLStateManager.glColorMask(false, false, false, false);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
verifyIsEnabled(GL11.GL_BLEND, true, "Blend enable - restored by HAND pop");
|
||||
assertEquals(GL11.GL_SRC_ALPHA, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb");
|
||||
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb");
|
||||
assertTrue(GLStateManager.getTextures().getTextureUnitStates(1).isEnabled(), "unit 1 texturing - restored by HAND pop");
|
||||
|
||||
final boolean[] driverMask = driverColorWriteMask();
|
||||
assertFalse(driverMask[0] || driverMask[1] || driverMask[2] || driverMask[3], "driver color mask - not a HAND member, must stay toggled");
|
||||
assertFalse(GLStateManager.getColorMask().red || GLStateManager.getColorMask().green
|
||||
|| GLStateManager.getColorMask().blue || GLStateManager.getColorMask().alpha, "cache color mask - not a HAND member, must stay toggled");
|
||||
} finally {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void masksRestoresDepthMaskAndColorMaskButNotDepthTestEnable() {
|
||||
try {
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.glColorMask(true, false, true, false);
|
||||
GLStateManager.disableDepthTest();
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.MASKS);
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
|
||||
GLStateManager.glColorMask(false, true, false, true);
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertTrue(GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK), "driver depth write mask - restored by MASKS pop");
|
||||
assertTrue(GLStateManager.getDepthState().isEnabled(), "cache depth write mask - restored by MASKS pop");
|
||||
assertEquals(GL11.GL_LEQUAL, GLStateManager.getDepthState().getFunc(), "cache depth func");
|
||||
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth func");
|
||||
|
||||
final boolean[] driverMask = driverColorWriteMask();
|
||||
assertTrue(driverMask[0] && !driverMask[1] && driverMask[2] && !driverMask[3], "driver color mask - restored by MASKS pop");
|
||||
assertTrue(GLStateManager.getColorMask().red && !GLStateManager.getColorMask().green
|
||||
&& GLStateManager.getColorMask().blue && !GLStateManager.getColorMask().alpha, "cache color mask - restored by MASKS pop");
|
||||
|
||||
verifyIsEnabled(GL11.GL_DEPTH_TEST, true, "Depth test enable - not a MASKS member, must stay toggled");
|
||||
} finally {
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glDepthFunc(GL11.GL_LESS);
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
GLStateManager.disableDepthTest();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void cullRestoresPolygonAxisAndCullEnableButNotBlendFunc() {
|
||||
try {
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.CULL);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_FRONT);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull enable - restored by CULL pop");
|
||||
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode");
|
||||
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode");
|
||||
|
||||
verifyIsEnabled(GL11.GL_BLEND, false, "Blend enable - not a CULL member, must stay toggled");
|
||||
assertEquals(GL11.GL_DST_COLOR, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - not a CULL member, must stay toggled");
|
||||
assertEquals(GL11.GL_DST_COLOR, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb - not a CULL member, must stay toggled");
|
||||
} finally {
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void blendRestoresBlendAxisButNotCull() {
|
||||
try {
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.BLEND);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_FRONT);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
verifyIsEnabled(GL11.GL_BLEND, true, "Blend enable - restored by BLEND pop");
|
||||
assertEquals(GL11.GL_SRC_ALPHA, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb");
|
||||
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb");
|
||||
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull enable - not a BLEND member, must stay toggled");
|
||||
assertEquals(GL11.GL_FRONT, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - not a BLEND member, must stay toggled");
|
||||
assertEquals(GL11.GL_FRONT, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - not a BLEND member, must stay toggled");
|
||||
} finally {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void alphaRestoresAlphaAxisButNotBlend() {
|
||||
try {
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.ALPHA);
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.1f);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
verifyIsEnabled(GL11.GL_ALPHA_TEST, true, "Alpha test enable - restored by ALPHA pop");
|
||||
assertEquals(GL11.GL_GREATER, GLStateManager.getAlphaState().getFunction(), "cache alpha func");
|
||||
assertEquals(0.5f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref");
|
||||
|
||||
verifyIsEnabled(GL11.GL_BLEND, false, "Blend enable - not an ALPHA member, must stay toggled");
|
||||
assertEquals(GL11.GL_DST_COLOR, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - not an ALPHA member, must stay toggled");
|
||||
assertEquals(GL11.GL_DST_COLOR, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb - not an ALPHA member, must stay toggled");
|
||||
} finally {
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void cutoutRestoresAlphaAxisAndUnit1TexturingButNotBlend() {
|
||||
try {
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.CUTOUT);
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.1f);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
|
||||
|
||||
final int fragBefore = GLStateManager.ctx().fragmentGeneration;
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertEquals(fragBefore + 1, GLStateManager.ctx().fragmentGeneration, "CUTOUT pop must bump fragment generation once when the value changed inside the bracket");
|
||||
|
||||
verifyIsEnabled(GL11.GL_ALPHA_TEST, true, "Alpha test enable - restored by CUTOUT pop");
|
||||
assertEquals(GL11.GL_GREATER, GLStateManager.getAlphaState().getFunction(), "cache alpha func");
|
||||
assertEquals(0.5f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref");
|
||||
assertTrue(GLStateManager.getTextures().getTextureUnitStates(1).isEnabled(), "unit 1 texturing - restored by CUTOUT pop");
|
||||
|
||||
verifyIsEnabled(GL11.GL_BLEND, false, "Blend enable - not a CUTOUT member, must stay toggled");
|
||||
assertEquals(GL11.GL_DST_COLOR, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - not a CUTOUT member, must stay toggled");
|
||||
assertEquals(GL11.GL_DST_COLOR, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb - not a CUTOUT member, must stay toggled");
|
||||
} finally {
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void cullFaceModeRestoredThroughCullBracket() {
|
||||
try {
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
final int d = GLStateManager.pushState(StateSet.CULL);
|
||||
GLStateManager.glCullFace(GL11.GL_FRONT);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode");
|
||||
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode");
|
||||
} finally {
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void cullFaceModeRestoredThroughBatchBracketRegression2117() {
|
||||
try {
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
final int d = GLStateManager.pushState(StateSet.BATCH);
|
||||
GLStateManager.glCullFace(GL11.GL_FRONT);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode");
|
||||
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode");
|
||||
} finally {
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchRestoresUnit0And1BindingsAndActiveUnitButNotUnit2() {
|
||||
if (GLStateManager.MAX_TEXTURE_UNITS < 3) return;
|
||||
final int tex0Base = GLStateManager.glGenTextures();
|
||||
final int tex1Base = GLStateManager.glGenTextures();
|
||||
final int tex2Base = GLStateManager.glGenTextures();
|
||||
final int tex0Dirty = GLStateManager.glGenTextures();
|
||||
final int tex1Dirty = GLStateManager.glGenTextures();
|
||||
final int tex2Dirty = GLStateManager.glGenTextures();
|
||||
try {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex0Base);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex1Base);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + 2);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex2Base);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.BATCH);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex0Dirty);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex1Dirty);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + 2);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex2Dirty);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertEquals(2, GLStateManager.getActiveTextureUnit(), "cache active unit - restored by BATCH pop");
|
||||
assertEquals(GL13.GL_TEXTURE0 + 2, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "driver active unit - restored by BATCH pop");
|
||||
|
||||
assertEquals(tex0Base, GLStateManager.getTextures().getTextureUnitBindings(0).getBinding(), "cache unit 0 binding - restored by BATCH pop");
|
||||
assertEquals(tex0Base, driverTextureBinding(0), "driver unit 0 binding - restored by BATCH pop");
|
||||
assertEquals(tex1Base, GLStateManager.getTextures().getTextureUnitBindings(1).getBinding(), "cache unit 1 binding - restored by BATCH pop");
|
||||
assertEquals(tex1Base, driverTextureBinding(1), "driver unit 1 binding - restored by BATCH pop");
|
||||
|
||||
assertEquals(tex2Dirty, GLStateManager.getTextures().getTextureUnitBindings(2).getBinding(), "cache unit 2 binding - not a BATCH member, must stay toggled");
|
||||
assertEquals(tex2Dirty, driverTextureBinding(2), "driver unit 2 binding - not a BATCH member, must stay toggled");
|
||||
} finally {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + 2);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glDeleteTextures(tex0Base);
|
||||
GLStateManager.glDeleteTextures(tex1Base);
|
||||
GLStateManager.glDeleteTextures(tex2Base);
|
||||
GLStateManager.glDeleteTextures(tex0Dirty);
|
||||
GLStateManager.glDeleteTextures(tex1Dirty);
|
||||
GLStateManager.glDeleteTextures(tex2Dirty);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void popStateToRepairsUnbalancedInnerPush() {
|
||||
try {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
final int d = GLStateManager.pushState(StateSet.BLEND);
|
||||
GLStateManager.pushState(StateSet.CULL);
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.popStateTo(d);
|
||||
assertEquals(d, GLStateManager.getAttribDepth());
|
||||
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb(), "popStateTo repairs the unbalanced non-member push and carries the change through to the BLEND member pop, restoring the pre-bracket baseline");
|
||||
} finally {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void popStateToToleratesUnbalancedInnerPop() {
|
||||
final int d = GLStateManager.pushState(StateSet.BLEND);
|
||||
GLStateManager.popState();
|
||||
assertEquals(d, GLStateManager.getAttribDepth());
|
||||
GLStateManager.popStateTo(d);
|
||||
assertEquals(d, GLStateManager.getAttribDepth());
|
||||
}
|
||||
|
||||
@Test
|
||||
void exceptionMidBracketLeavesDepthAndStateCorrectViaFinally() {
|
||||
try {
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
final int depthBefore = GLStateManager.getAttribDepth();
|
||||
|
||||
final RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
|
||||
final int d = GLStateManager.pushState(StateSet.BLEND);
|
||||
try {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
|
||||
throw new RuntimeException("boom");
|
||||
} finally {
|
||||
GLStateManager.popStateTo(d);
|
||||
}
|
||||
});
|
||||
|
||||
assertEquals("boom", thrown.getMessage());
|
||||
assertEquals(depthBefore, GLStateManager.getAttribDepth());
|
||||
verifyIsEnabled(GL11.GL_BLEND, true, "Blend enable - restored despite exception");
|
||||
assertEquals(GL11.GL_SRC_ALPHA, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - restored despite exception");
|
||||
} finally {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void allAttribBitsNestedInsideInternalBracketRestoresBothLevels() {
|
||||
try {
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.BLEND);
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
|
||||
GLStateManager.glCullFace(GL11.GL_FRONT);
|
||||
GLStateManager.glPopAttrib();
|
||||
|
||||
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull enable - restored by inner GL_ALL_ATTRIB_BITS pop");
|
||||
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - restored by inner GL_ALL_ATTRIB_BITS pop");
|
||||
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - restored by inner GL_ALL_ATTRIB_BITS pop");
|
||||
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
verifyIsEnabled(GL11.GL_BLEND, false, "Blend enable - restored by outer BLEND pop");
|
||||
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - restored by outer BLEND pop");
|
||||
} finally {
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void internalBracketNestedInsideAllAttribBitsRestoresBothLevels() {
|
||||
try {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
|
||||
try {
|
||||
GLStateManager.glCullFace(GL11.GL_FRONT);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.BLEND);
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
verifyIsEnabled(GL11.GL_BLEND, false, "Blend enable - restored by inner BLEND pop");
|
||||
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb(), "cache blend src rgb - restored by inner BLEND pop");
|
||||
assertEquals(GL11.GL_FRONT, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - change survives, not a BLEND member");
|
||||
assertEquals(GL11.GL_FRONT, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - change survives, not a BLEND member");
|
||||
} finally {
|
||||
GLStateManager.glPopAttrib();
|
||||
}
|
||||
|
||||
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - restored by outer GL_ALL_ATTRIB_BITS pop");
|
||||
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - restored by outer GL_ALL_ATTRIB_BITS pop");
|
||||
} finally {
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void rProgramFontRestoresRealProgramToDriverAndCache() {
|
||||
final int p0 = linkTrivialProgram();
|
||||
final int p1 = linkTrivialProgram();
|
||||
try {
|
||||
GLStateManager.glUseProgram(p0);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.FONT);
|
||||
GLStateManager.glUseProgram(p1);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertEquals(p0, GLStateManager.getActiveProgram(), "cache active program restored");
|
||||
assertEquals(p0, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM), "driver current program restored");
|
||||
assertFalse(ShaderManager.getInstance().isActive(), "FFP must stay inactive when a real program was bound before the bracket");
|
||||
} finally {
|
||||
GLStateManager.glUseProgram(0);
|
||||
GLStateManager.glDeleteProgram(p0);
|
||||
GLStateManager.glDeleteProgram(p1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void rProgramFontReactivatesFfpAfterBracket() {
|
||||
final int p1 = linkTrivialProgram();
|
||||
try {
|
||||
GLStateManager.glUseProgram(0);
|
||||
assertTrue(ShaderManager.getInstance().isActive(), "FFP must be active before the bracket");
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.FONT);
|
||||
GLStateManager.glUseProgram(p1);
|
||||
assertFalse(ShaderManager.getInstance().isActive(), "FFP must deactivate once a real program binds");
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertEquals(0, GLStateManager.getActiveProgram(), "cache active program restored to the FFP sentinel");
|
||||
assertTrue(ShaderManager.getInstance().isActive(), "FFP must be active again after the pop");
|
||||
} finally {
|
||||
GLStateManager.glUseProgram(0);
|
||||
GLStateManager.glDeleteProgram(p1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void blendPopDoesNotBumpFragmentOrLightingGeneration() {
|
||||
try {
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.BLEND);
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO);
|
||||
|
||||
final int fragBefore = GLStateManager.ctx().fragmentGeneration;
|
||||
final int lightBefore = GLStateManager.ctx().lightingGeneration;
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertEquals(fragBefore, GLStateManager.ctx().fragmentGeneration, "BLEND pop must not bump fragment generation");
|
||||
assertEquals(lightBefore, GLStateManager.ctx().lightingGeneration, "BLEND pop must not bump lighting generation");
|
||||
} finally {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void alphaPopBumpsFragmentGenerationWhenChanged() {
|
||||
try {
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.ALPHA);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.1f);
|
||||
|
||||
final int fragBefore = GLStateManager.ctx().fragmentGeneration;
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertEquals(fragBefore + 1, GLStateManager.ctx().fragmentGeneration, "ALPHA pop must bump fragment generation once when the value changed inside the bracket");
|
||||
} finally {
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void textureBitBracketRestoresUnit1BindingAndActiveUnit() {
|
||||
final int texBase = GLStateManager.glGenTextures();
|
||||
final int texDirty = GLStateManager.glGenTextures();
|
||||
try {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texBase);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_TEXTURE_BIT));
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texDirty);
|
||||
GLStateManager.popStateTo(d);
|
||||
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored by GL_TEXTURE_BIT pop");
|
||||
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "driver active unit restored by GL_TEXTURE_BIT pop");
|
||||
assertEquals(texBase, GLStateManager.getTextures().getTextureUnitBindings(1).getBinding(), "cache unit 1 binding restored by GL_TEXTURE_BIT pop");
|
||||
assertEquals(texBase, driverTextureBinding(1), "driver unit 1 binding restored by GL_TEXTURE_BIT pop");
|
||||
} finally {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glDeleteTextures(texBase);
|
||||
GLStateManager.glDeleteTextures(texDirty);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void textureBitBracketRestoresTexEnvModeAndColor() {
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
|
||||
final FloatBuffer zero = BufferUtils.createFloatBuffer(4);
|
||||
zero.put(0.0f).put(0.0f).put(0.0f).put(0.0f).flip();
|
||||
GLStateManager.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, zero);
|
||||
try {
|
||||
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_TEXTURE_BIT));
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_DECAL);
|
||||
final FloatBuffer dirty = BufferUtils.createFloatBuffer(4);
|
||||
dirty.put(0.1f).put(0.2f).put(0.3f).put(0.4f).flip();
|
||||
GLStateManager.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, dirty);
|
||||
GLStateManager.popStateTo(d);
|
||||
assertEquals(GL11.GL_MODULATE, GLStateManager.getTextures().getTexEnvState(0).mode, "cache tex env mode restored by GL_TEXTURE_BIT pop");
|
||||
assertEquals(0.0f, GLStateManager.getTextures().getTexEnvState(0).envColorR, 0.0001f, "cache tex env color restored by GL_TEXTURE_BIT pop");
|
||||
} finally {
|
||||
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void lightingBitBracketRestoresBakedFrontMaterialAfterColorMaterialToggle() {
|
||||
try {
|
||||
GLStateManager.glColor4f(0.9f, 0.9f, 0.9f, 0.9f);
|
||||
GLStateManager.enableColorMaterial();
|
||||
GLStateManager.disableColorMaterial();
|
||||
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_LIGHTING_BIT));
|
||||
GLStateManager.glColor4f(0.1f, 0.2f, 0.3f, 0.4f);
|
||||
GLStateManager.enableColorMaterial();
|
||||
GLStateManager.popStateTo(d);
|
||||
assertEquals(0.9f, GLStateManager.getFrontMaterial().ambient.x, 0.0001f, "cache front material ambient restored by GL_LIGHTING_BIT pop");
|
||||
assertEquals(0.9f, GLStateManager.getFrontMaterial().diffuse.x, 0.0001f, "cache front material diffuse restored by GL_LIGHTING_BIT pop");
|
||||
} finally {
|
||||
GLStateManager.disableColorMaterial();
|
||||
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
@GLCoreTest
|
||||
public class GLSM_ValueStackCow_GLTest {
|
||||
|
||||
private static int fixtureTexA = -1;
|
||||
private static int fixtureTexB = -1;
|
||||
|
||||
private static FloatBuffer buf4(float a, float b, float c, float d) {
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(4);
|
||||
buf.put(a).put(b).put(c).put(d).flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static void ensureFixtureTextures() {
|
||||
if (fixtureTexA == -1) {
|
||||
fixtureTexA = GLStateManager.glGenTextures();
|
||||
fixtureTexB = GLStateManager.glGenTextures();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void deleteFixtureTextures() {
|
||||
if (fixtureTexA > 0) GLStateManager.glDeleteTextures(fixtureTexA);
|
||||
if (fixtureTexB > 0) GLStateManager.glDeleteTextures(fixtureTexB);
|
||||
fixtureTexA = fixtureTexB = -1;
|
||||
}
|
||||
|
||||
private static final String[] NAMES = {
|
||||
"glAlphaFunc", "glBlendFunc", "tryBlendFuncSeparate", "glBlendEquation", "glBlendEquationSeparate",
|
||||
"glBlendColor", "glColorMask", "glClearColor", "glDrawBuffer", "glLogicOp",
|
||||
"glDepthFunc", "glDepthMask", "glClearDepth", "glStencilFunc", "glStencilOp",
|
||||
"glStencilMask", "glClearStencil", "glViewport", "glDepthRange", "glLineWidth",
|
||||
"glLineStipple", "glPointSize", "glPolygonMode", "glCullFace", "glFrontFace",
|
||||
"glPolygonOffset", "fogColor", "glFogf", "glFogi", "glLightf",
|
||||
"glLighti", "glLight", "glLightModelf", "glLightModeli", "glLightModel",
|
||||
"glMaterialf", "glMateriali", "glMaterial", "glShadeModel", "glColorMaterial",
|
||||
"glMatrixMode", "glActiveTexture", "glBindTexture", "glTexEnvi", "glTexEnv",
|
||||
"glSecondaryColor3ub", "enableBlend/disableBlend", "enableAlphaTest/disableAlphaTest",
|
||||
"glEnable(GL_CULL_FACE)", "dsaBindTextureToUnit"
|
||||
};
|
||||
|
||||
private static final Runnable[] MUTATIONS = {
|
||||
() -> GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.1f),
|
||||
() -> GLStateManager.glBlendFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO),
|
||||
() -> GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_DST_COLOR, GL11.GL_ZERO),
|
||||
() -> GLStateManager.glBlendEquation(GL14.GL_FUNC_SUBTRACT),
|
||||
() -> GLStateManager.glBlendEquationSeparate(GL14.GL_FUNC_SUBTRACT, GL14.GL_FUNC_REVERSE_SUBTRACT),
|
||||
() -> GLStateManager.glBlendColor(0.5f, 0.6f, 0.7f, 0.8f),
|
||||
() -> GLStateManager.glColorMask(false, true, false, true),
|
||||
() -> GLStateManager.glClearColor(1.0f, 0.5f, 0.25f, 0.75f),
|
||||
() -> GLStateManager.glDrawBuffer(GL11.GL_NONE),
|
||||
() -> GLStateManager.glLogicOp(GL11.GL_XOR),
|
||||
() -> GLStateManager.glDepthFunc(GL11.GL_ALWAYS),
|
||||
() -> GLStateManager.glDepthMask(false),
|
||||
() -> GLStateManager.glClearDepth(0.25),
|
||||
() -> GLStateManager.glStencilFunc(GL11.GL_NOTEQUAL, 3, 0x0F),
|
||||
() -> GLStateManager.glStencilOp(GL11.GL_REPLACE, GL11.GL_INCR, GL11.GL_DECR),
|
||||
() -> GLStateManager.glStencilMask(0x0F),
|
||||
() -> GLStateManager.glClearStencil(9),
|
||||
() -> GLStateManager.glViewport(1, 2, 3, 4),
|
||||
() -> GLStateManager.glDepthRange(0.2, 0.8),
|
||||
() -> GLStateManager.glLineWidth(3.0f),
|
||||
() -> GLStateManager.glLineStipple(3, (short) 0x00FF),
|
||||
() -> GLStateManager.glPointSize(4.0f),
|
||||
() -> GLStateManager.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE),
|
||||
() -> GLStateManager.glCullFace(GL11.GL_FRONT),
|
||||
() -> GLStateManager.glFrontFace(GL11.GL_CW),
|
||||
() -> GLStateManager.glPolygonOffset(3.0f, 4.0f),
|
||||
() -> GLStateManager.fogColor(0.5f, 0.6f, 0.7f, 0.8f),
|
||||
() -> GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.5f),
|
||||
() -> GLStateManager.glFogi(GL11.GL_FOG_MODE, GL11.GL_LINEAR),
|
||||
() -> GLStateManager.glLightf(GL11.GL_LIGHT0, GL11.GL_SPOT_EXPONENT, 12.0f),
|
||||
() -> GLStateManager.glLighti(GL11.GL_LIGHT0, GL11.GL_SPOT_CUTOFF, 45),
|
||||
() -> GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, buf4(1.0f, 2.0f, 3.0f, 1.0f)),
|
||||
() -> GLStateManager.glLightModelf(GL11.GL_LIGHT_MODEL_LOCAL_VIEWER, 1.0f),
|
||||
() -> GLStateManager.glLightModeli(GL12.GL_LIGHT_MODEL_COLOR_CONTROL, GL12.GL_SEPARATE_SPECULAR_COLOR),
|
||||
() -> GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, buf4(0.5f, 0.6f, 0.7f, 0.8f)),
|
||||
() -> GLStateManager.glMaterialf(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 32.0f),
|
||||
() -> GLStateManager.glMateriali(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 32),
|
||||
() -> GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_DIFFUSE, buf4(0.1f, 0.2f, 0.3f, 0.4f)),
|
||||
() -> GLStateManager.glShadeModel(GL11.GL_FLAT),
|
||||
() -> GLStateManager.glColorMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR),
|
||||
() -> GLStateManager.glMatrixMode(GL11.GL_PROJECTION),
|
||||
() -> GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + 2),
|
||||
() -> {
|
||||
ensureFixtureTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, fixtureTexA);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
},
|
||||
() -> GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_DECAL),
|
||||
() -> GLStateManager.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, buf4(0.1f, 0.2f, 0.3f, 0.4f)),
|
||||
() -> GLStateManager.glSecondaryColor3ub((byte) 255, (byte) 128, (byte) 64),
|
||||
() -> {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.enableBlend();
|
||||
},
|
||||
() -> {
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.enableAlphaTest();
|
||||
},
|
||||
() -> {
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
},
|
||||
() -> {
|
||||
assumeTrue(GLStateManager.capabilities.GL_ARB_direct_state_access, "test requires GL_ARB_direct_state_access");
|
||||
ensureFixtureTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, fixtureTexA);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
new DSAARB().bindTextureToUnit(1, fixtureTexB);
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
void everyMutationSiteReachesTheSaveHook() {
|
||||
final int entryDepth = GLStateManager.getAttribDepth();
|
||||
try {
|
||||
for (int i = 0; i < MUTATIONS.length; i++) {
|
||||
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_ALL_ATTRIB_BITS));
|
||||
try {
|
||||
final long before = GLStateManager.attribSlotsSaved;
|
||||
MUTATIONS[i].run();
|
||||
assertTrue(GLStateManager.attribSlotsSaved - before >= 1, NAMES[i] + ": mutation site did not reach the save hook");
|
||||
} finally {
|
||||
GLStateManager.popStateTo(d);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
GLStateManager.popStateTo(entryDepth);
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
@GLCoreTest
|
||||
public class GLSM_WorkerContextStacks_GLTest {
|
||||
|
||||
@Test
|
||||
void enableBitBracketRestoresOnWorkerContext() {
|
||||
GLStateManager.enterWorkerContext();
|
||||
try {
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_ENABLE_BIT));
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertFalse(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE), "cull face restored on the worker context");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.exitWorkerContext();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorBufferBitBracketRestoresOnWorkerContext() {
|
||||
GLStateManager.enterWorkerContext();
|
||||
try {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
|
||||
final int d = GLStateManager.pushState(StateSet.forMask(GL11.GL_COLOR_BUFFER_BIT));
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.popStateTo(d);
|
||||
|
||||
assertFalse(GLStateManager.glIsEnabled(GL11.GL_BLEND), "blend enable restored on the worker context");
|
||||
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb(), "blend src rgb restored on the worker context");
|
||||
} finally {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.exitWorkerContext();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void workerResetLeavesPrimaryContextUntouched() {
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
final int primaryDepthBefore = GLStateManager.getAttribDepth();
|
||||
|
||||
GLStateManager.enterWorkerContext();
|
||||
try {
|
||||
GLStateManager.pushState(StateSet.forMask(GL11.GL_ENABLE_BIT));
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
|
||||
GLStateManager.reset();
|
||||
|
||||
assertEquals(0, GLStateManager.getAttribDepth(), "worker attrib depth drained by its own reset");
|
||||
} finally {
|
||||
GLStateManager.exitWorkerContext();
|
||||
}
|
||||
|
||||
assertEquals(primaryDepthBefore, GLStateManager.getAttribDepth(), "primary context depth untouched by the worker reset");
|
||||
assertFalse(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE), "primary context cull face untouched by the worker reset");
|
||||
}
|
||||
|
||||
@Test
|
||||
void stackIdsEqualAcrossContexts() {
|
||||
final GLContextState primary = GLStateManager.ctx();
|
||||
final GLContextState worker = GLStateManager.enterWorkerContext();
|
||||
try {
|
||||
final Object[] primaryStacks = {
|
||||
primary.blendState, primary.depthState, primary.colorMask, primary.blendMode,
|
||||
primary.cullState, primary.textures.getTextureUnitBindings(1), primary.polygonState
|
||||
};
|
||||
final Object[] workerStacks = {
|
||||
worker.blendState, worker.depthState, worker.colorMask, worker.blendMode,
|
||||
worker.cullState, worker.textures.getTextureUnitBindings(1), worker.polygonState
|
||||
};
|
||||
|
||||
final int[] primaryIds = new int[primaryStacks.length];
|
||||
final int[] workerIds = new int[workerStacks.length];
|
||||
for (int i = 0; i < primaryStacks.length; i++) {
|
||||
primaryIds[i] = Reflect.invoke(primaryStacks[i], "stackId", new Class[0]);
|
||||
workerIds[i] = Reflect.invoke(workerStacks[i], "stackId", new Class[0]);
|
||||
}
|
||||
|
||||
assertArrayEquals(primaryIds, workerIds, "stack ids must match between the primary and worker contexts for identically-constructed stacks");
|
||||
} finally {
|
||||
GLStateManager.exitWorkerContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ class FFPUniformValuesTest {
|
||||
final Uniforms uniforms = new Uniforms();
|
||||
try {
|
||||
GL20.glUseProgram(program.getProgramId());
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
|
||||
final ByteBuffer block = uniforms.getStaging();
|
||||
|
||||
|
||||
+10
-10
@@ -68,18 +68,18 @@ class FfpUniformBlockDedupTest {
|
||||
resetState();
|
||||
final Uniforms uniforms = new Uniforms();
|
||||
try {
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
assertEquals(1, uniforms.blockWrites, "first upload always writes");
|
||||
final int firstOffset = boundRangeStart();
|
||||
|
||||
GLStateManager.glLoadIdentity();
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
assertEquals(1, uniforms.blockWrites, "a dirty generation with unchanged bytes must not write");
|
||||
assertEquals(1, uniforms.blockSkips, "and must be counted as a skip");
|
||||
assertEquals(firstOffset, boundRangeStart(), "the skipped write must not advance the ring or rebind");
|
||||
|
||||
GLStateManager.glTranslatef(1.0f, 0.0f, 0.0f);
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
assertEquals(2, uniforms.blockWrites, "a changed matrix must write");
|
||||
assertEquals(1, uniforms.blockSkips, "a write is not a skip");
|
||||
assertNotEquals(firstOffset, boundRangeStart(), "a write takes a fresh ring range and rebinds");
|
||||
@@ -95,11 +95,11 @@ class FfpUniformBlockDedupTest {
|
||||
final Uniforms first = new Uniforms();
|
||||
final Uniforms second = new Uniforms();
|
||||
try {
|
||||
first.upload();
|
||||
first.upload();
|
||||
first.upload(GLStateManager.ctx());
|
||||
first.upload(GLStateManager.ctx());
|
||||
assertEquals(1, first.blockWrites);
|
||||
|
||||
second.upload();
|
||||
second.upload(GLStateManager.ctx());
|
||||
assertEquals(1, second.blockWrites, "a never-bound instance must write regardless of content");
|
||||
} finally {
|
||||
first.destroy();
|
||||
@@ -146,19 +146,19 @@ class FfpUniformBlockDedupTest {
|
||||
resetState();
|
||||
final Uniforms uniforms = new Uniforms();
|
||||
try {
|
||||
uniforms.upload();
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
final int writesBefore = uniforms.blockWrites;
|
||||
final int settledOffset = boundRangeStart();
|
||||
|
||||
mutate.run();
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
assertEquals(writesBefore + 1, uniforms.blockWrites, name + ": a changed member must write");
|
||||
assertNotEquals(settledOffset, boundRangeStart(), name + ": a write must rebind to a fresh range");
|
||||
|
||||
final int skipsBefore = uniforms.blockSkips;
|
||||
final int writtenOffset = boundRangeStart();
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
assertEquals(writesBefore + 1, uniforms.blockWrites, name + ": re-uploading unchanged bytes must not write");
|
||||
assertEquals(writtenOffset, boundRangeStart(), name + ": and must not rebind");
|
||||
if (uniforms.blockSkips != skipsBefore) {
|
||||
|
||||
+8
-8
@@ -31,20 +31,20 @@ class ProgramUniformTrackingTest {
|
||||
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
|
||||
|
||||
GL20.glUseProgram(a.getProgramId());
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
final int afterFirst = uniforms.blockWrites;
|
||||
|
||||
GL20.glUseProgram(b.getProgramId());
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
GL20.glUseProgram(a.getProgramId());
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
assertEquals(afterFirst, uniforms.blockWrites, "program switches with unchanged state write nothing");
|
||||
|
||||
GLStateManager.glTranslatef(4.0F, 0.0F, 0.0F);
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
assertEquals(afterFirst + 1, uniforms.blockWrites, "a gen bump writes exactly one block");
|
||||
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
assertEquals(afterFirst + 1, uniforms.blockWrites, "clean draw writes nothing");
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
@@ -66,18 +66,18 @@ class ProgramUniformTrackingTest {
|
||||
GLStateManager.glLoadIdentity();
|
||||
|
||||
GL20.glUseProgram(a.getProgramId());
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
final int base = uniforms.blockWrites;
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_TRANSFORM_BIT);
|
||||
GLStateManager.glPopAttrib();
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
assertEquals(base, uniforms.blockWrites, "push/pop with no change inside writes nothing");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_TRANSFORM_BIT);
|
||||
GLStateManager.glTranslatef(7.0F, 0.0F, 0.0F);
|
||||
GLStateManager.glPopAttrib();
|
||||
uniforms.upload();
|
||||
uniforms.upload(GLStateManager.ctx());
|
||||
assertEquals(base + 1, uniforms.blockWrites, "pop after change stages once");
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@GLCoreTest
|
||||
class VertexKeyTexMatrixMaskTest {
|
||||
|
||||
private static boolean texMatEnabled(int unit) {
|
||||
return VertexKey.fromState(false, false, false, false, 0).unitTexMatEnabled(unit);
|
||||
}
|
||||
|
||||
@Test
|
||||
void identityNonIdentityTransitionsPerUnit() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
final boolean texGenS = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_GEN_S);
|
||||
final boolean texGenT = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_GEN_T);
|
||||
final boolean texGenR = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_GEN_R);
|
||||
final boolean texGenQ = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_GEN_Q);
|
||||
if (texGenS) GLStateManager.glDisable(GL11.GL_TEXTURE_GEN_S);
|
||||
if (texGenT) GLStateManager.glDisable(GL11.GL_TEXTURE_GEN_T);
|
||||
if (texGenR) GLStateManager.glDisable(GL11.GL_TEXTURE_GEN_R);
|
||||
if (texGenQ) GLStateManager.glDisable(GL11.GL_TEXTURE_GEN_Q);
|
||||
try {
|
||||
for (int unit = 0; unit < VertexKey.MAX_UNITS; unit++) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
assertFalse(texMatEnabled(unit), "unit " + unit + " identity");
|
||||
|
||||
GLStateManager.glTranslatef(1.0f, 2.0f, 3.0f);
|
||||
assertTrue(texMatEnabled(unit), "unit " + unit + " non-identity");
|
||||
|
||||
GLStateManager.glLoadIdentity();
|
||||
assertFalse(texMatEnabled(unit), "unit " + unit + " back to identity");
|
||||
}
|
||||
} finally {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
if (texGenS) GLStateManager.glEnable(GL11.GL_TEXTURE_GEN_S);
|
||||
if (texGenT) GLStateManager.glEnable(GL11.GL_TEXTURE_GEN_T);
|
||||
if (texGenR) GLStateManager.glEnable(GL11.GL_TEXTURE_GEN_R);
|
||||
if (texGenQ) GLStateManager.glEnable(GL11.GL_TEXTURE_GEN_Q);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void pushPopMatrixRestoresIdentity() {
|
||||
final int unit = 1;
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
assertFalse(texMatEnabled(unit));
|
||||
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glTranslatef(4.0f, 5.0f, 6.0f);
|
||||
assertTrue(texMatEnabled(unit));
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
assertFalse(texMatEnabled(unit), "pop must restore identity and invalidate the cached mask");
|
||||
}
|
||||
|
||||
@Test
|
||||
void transformBitPopBumpsGeneration() {
|
||||
final int unit = 2;
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
assertFalse(texMatEnabled(unit));
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_TRANSFORM_BIT);
|
||||
try {
|
||||
GLStateManager.glTranslatef(7.0f, 8.0f, 9.0f);
|
||||
assertTrue(texMatEnabled(unit));
|
||||
} finally {
|
||||
GLStateManager.glPopAttrib();
|
||||
}
|
||||
assertTrue(texMatEnabled(unit), "GL_TRANSFORM_BIT pop does not restore matrix content; the pop bump must still force a correct recompute");
|
||||
|
||||
GLStateManager.glLoadIdentity();
|
||||
assertFalse(texMatEnabled(unit));
|
||||
}
|
||||
|
||||
@Test
|
||||
void generationUnchangedSkipsRecompute() {
|
||||
final int unit = 3;
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
assertFalse(texMatEnabled(unit));
|
||||
|
||||
final int genBefore = GLStateManager.getTexMatrixGeneration();
|
||||
GLStateManager.ctx().textures.getTextureUnitMatrix(unit).translate(10.0f, 11.0f, 12.0f);
|
||||
assertEquals(genBefore, GLStateManager.getTexMatrixGeneration(), "direct mutation of the matrix object must not bump the generation");
|
||||
assertFalse(texMatEnabled(unit), "cached mask must stay stale (identity) while the generation is unchanged");
|
||||
|
||||
GLStateManager.glLoadIdentity();
|
||||
assertFalse(texMatEnabled(unit));
|
||||
}
|
||||
}
|
||||
@@ -276,6 +276,11 @@ class GLSMRedirectorTest {
|
||||
assertTrue(unmappedSeen().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void utilCheckGLErrorIsRedirected() throws Exception {
|
||||
assertRedirectedToDeclaredTarget(new String[][]{{"org/lwjgl/opengl/Util", "checkGLError", "()V"}});
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexedGettersHaveRealTargets() throws Exception {
|
||||
final Class<?> glsm = Class.forName(GLSM.replace('/', '.'), false, getClass().getClassLoader());
|
||||
|
||||
@@ -153,7 +153,7 @@ public final class FfpFixture {
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 0f);
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ glslTransformLib = "0.2.0-33.g3ec381e"
|
||||
antlr4Runtime = "4.13.2"
|
||||
jcpp = "1.4.14"
|
||||
tracy = "0.14.0"
|
||||
asyncProfiler = "4.4"
|
||||
retroFuturaBootstrap = "1.1.0"
|
||||
|
||||
lombok = "1.18.46"
|
||||
@@ -38,6 +39,7 @@ eventbus = { module = "net.minecraftforge:eventbus", version.ref = "eventbus" }
|
||||
glsl-transformation-lib = { module = "org.taumc:glsl-transformation-lib", version.ref = "glslTransformLib" }
|
||||
antlr4-runtime = { module = "org.antlr:antlr4-runtime", version.ref = "antlr4Runtime" }
|
||||
jcpp = { module = "org.anarres:jcpp", version.ref = "jcpp" }
|
||||
async-profiler = { module = "tools.profiler:async-profiler", version.ref = "asyncProfiler" }
|
||||
lombok = { module = "org.projectlombok:lombok", version.ref = "lombok" }
|
||||
annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
|
||||
log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Usage: scripts/flyby-bench.sh <name> ffp|pack [flyby-capture.sh args...]
|
||||
# Env: FLYBY_SCENE, FLYBY_ORIGIN, TIME_OF_DAY, QUICKPLAY_WORLD, TRACY_CAPTURE, SDLGPU, RENDER_DISTANCE.
|
||||
# BENCH_EXT=tracy|jfr - output extension, selects the profiler via flyby-capture.sh (default: tracy).
|
||||
# Forces particles:0 for the run and restores it after exit; the game rewrites options.txt on quit.
|
||||
# A worktree needs tracy-client/src/main/resources/natives copied in.
|
||||
|
||||
@@ -43,7 +44,8 @@ export QUICKPLAY_WORLD="${QUICKPLAY_WORLD:-Flat World - No Mods}"
|
||||
if [ "$QUICKPLAY_WORLD" = "Flat World - No Mods" ]; then export FLYBY_ORIGIN="${FLYBY_ORIGIN-1509.5,-924.5,0}"; fi
|
||||
export TRACY_CAPTURE="${TRACY_CAPTURE:-$HOME/dev/mc/tracy/capture/build/tracy-capture}"
|
||||
export SDLGPU="${SDLGPU:-1}"
|
||||
BENCH_EXT="${BENCH_EXT:-tracy}"
|
||||
|
||||
echo "=== $NAME ($MODE) start $(date '+%H:%M:%S')"
|
||||
scripts/flyby-capture.sh "$OUT_DIR/$NAME.tracy" circuit 128 -Dangelica.flyby.timeOfDay="${TIME_OF_DAY:-6000}" "$@" >| "$OUT_DIR/$NAME.console.log" 2>&1
|
||||
scripts/flyby-capture.sh "$OUT_DIR/$NAME.$BENCH_EXT" circuit 128 -Dangelica.flyby.timeOfDay="${TIME_OF_DAY:-6000}" "$@" >| "$OUT_DIR/$NAME.console.log" 2>&1
|
||||
echo "=== $NAME exit=$? $(date '+%H:%M:%S'), $(grep -c 'did not execute' "$OUT_DIR/$NAME.console.log" || true) failed scene lines"
|
||||
+111
-30
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run a flyby benchmark under using tracy-capture
|
||||
# Run a flyby benchmark under tracy-capture and/or an in-process JFR profile
|
||||
#
|
||||
# * Launches the game with the flyby properties set and tracy-capture
|
||||
# * Game waits for the tracy connection, runs the route, returns to the original coordinates and exits.
|
||||
# * tracy-capture finalizes
|
||||
#
|
||||
# Usage: scripts/flyby-capture.sh <output.tracy> [route] [length] [extra gradle args...]
|
||||
# Usage: scripts/flyby-capture.sh <output.tracy|output.jfr> [route] [length] [extra gradle args...]
|
||||
# route - straight | pan | circuit | static
|
||||
# length - Units: blocks (straight), blocks per leg (circuit), degrees (pan), ticks (static).
|
||||
# extra - passed through to gradle; -Dangelica.* reaches the client (e.g. -Dangelica.flyby.timeOfDay=1500).
|
||||
@@ -16,15 +16,20 @@
|
||||
# QUICKPLAY_WORLD - save to auto-load (default: latest).
|
||||
# FLYBY_SCENE - server command file run at flyby start.
|
||||
# SDLGPU=0|1 - force OpenGL (0) or SDL-GPU (1); unset uses the build default.
|
||||
# ALSO_JFR=1 - with a .tracy output, also record a JFR alongside it (timings inflated, correlation only).
|
||||
# ASPROF_OPTS - comma separated async-profiler agent opts, passed through when a JFR is recorded.
|
||||
#
|
||||
# Requires tracy-capture on PATH (TRACY_CAPTURE=/path/to/tracy-capture).
|
||||
# The output extension selects the profiler: .tracy for Tracy, .jfr for an in-process async-profiler
|
||||
# JFR that starts near the end of warmup and stops when the measured run ends.
|
||||
#
|
||||
# Requires tracy-capture on PATH (TRACY_CAPTURE=/path/to/tracy-capture) for .tracy output.
|
||||
# QuickPlay loads QUICKPLAY_WORLD on launch; the run starts once the world is up.
|
||||
#
|
||||
# Ensure the chunks in the route have been generated before capturing
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
OUT="${1:?usage: flyby-capture.sh <output.tracy> [route] [length] [extra gradle args...]}"
|
||||
OUT="${1:?usage: flyby-capture.sh <output.tracy|output.jfr> [route] [length] [extra gradle args...]}"
|
||||
ROUTE="${2:-straight}"
|
||||
LENGTH="${3:-0}"
|
||||
if [ $# -ge 3 ]; then
|
||||
@@ -35,6 +40,15 @@ fi
|
||||
WARMUP="${FLYBY_WARMUP:-400}"
|
||||
TRACY_CAPTURE="${TRACY_CAPTURE:-tracy-capture}"
|
||||
|
||||
case "$OUT" in
|
||||
*.tracy) MODE="tracy" ;;
|
||||
*.jfr) MODE="jfr" ;;
|
||||
*)
|
||||
echo "usage: flyby-capture.sh <output.tracy|output.jfr> [route] [length] [extra gradle args...]" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
BACKEND_ARGS=()
|
||||
if [ "${SDLGPU:-}" = "1" ]; then
|
||||
BACKEND_ARGS+=(-Dangelica.sdlgpu.enable=true)
|
||||
@@ -62,58 +76,125 @@ if [ -n "${FLYBY_SCENE:-}" ]; then
|
||||
SCENE_ARGS+=(-Dangelica.flyby.commands="$SCENE_ABS")
|
||||
fi
|
||||
|
||||
if ! command -v "$TRACY_CAPTURE" >/dev/null 2>&1 && [ ! -x "$TRACY_CAPTURE" ]; then
|
||||
cat >&2 <<'EOF'
|
||||
# JFR_OUT is set when a JFR is being recorded this run: always for MODE=jfr,
|
||||
# additionally for MODE=tracy when ALSO_JFR=1 (both profilers active).
|
||||
JFR_OUT=""
|
||||
DISABLE_TRACY=0
|
||||
if [ "$MODE" = "jfr" ]; then
|
||||
JFR_OUT="$OUT"
|
||||
DISABLE_TRACY=1
|
||||
elif [ "${ALSO_JFR:-}" = "1" ]; then
|
||||
JFR_OUT="${OUT%.tracy}.jfr"
|
||||
fi
|
||||
|
||||
PROFILE_ARGS=()
|
||||
JFR_ABS=""
|
||||
if [ -n "$JFR_OUT" ]; then
|
||||
JFR_DIR="$(dirname "$JFR_OUT")"
|
||||
if [ ! -d "$JFR_DIR" ]; then
|
||||
echo "error: directory for $JFR_OUT does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
JFR_ABS="$(cd "$JFR_DIR" && pwd)/$(basename "$JFR_OUT")"
|
||||
if [ "$DISABLE_TRACY" = "1" ]; then
|
||||
PROFILE_ARGS+=(-Dangelica.tracy=false)
|
||||
fi
|
||||
PROFILE_ARGS+=(-Dangelica.flyby.jfr=true -Dangelica.profile.output="$JFR_ABS")
|
||||
if [ -n "${ASPROF_OPTS:-}" ]; then
|
||||
PROFILE_ARGS+=(-Dangelica.profile.opts="$ASPROF_OPTS")
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$MODE" = "tracy" ]; then
|
||||
if ! command -v "$TRACY_CAPTURE" >/dev/null 2>&1 && [ ! -x "$TRACY_CAPTURE" ]; then
|
||||
cat >&2 <<'EOF'
|
||||
error: tracy-capture not found.
|
||||
|
||||
Build and launch with: TRACY_CAPTURE=/path/to/tracy/capture/build/tracy-capture scripts/flyby-capture.sh ...
|
||||
EOF
|
||||
exit 1
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -e "$OUT" ]; then
|
||||
echo "error: $OUT already exists; refusing to overwrite a capture" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> starting capture -> $OUT"
|
||||
if [ -n "${CAPTURE_SECONDS:-}" ]; then
|
||||
"$TRACY_CAPTURE" -o "$OUT" -s "$CAPTURE_SECONDS" &
|
||||
else
|
||||
"$TRACY_CAPTURE" -o "$OUT" &
|
||||
if [ -n "$JFR_OUT" ] && [ "$JFR_OUT" != "$OUT" ] && [ -e "$JFR_OUT" ]; then
|
||||
echo "error: $JFR_OUT already exists; refusing to overwrite a capture" >&2
|
||||
exit 1
|
||||
fi
|
||||
CAPTURE_PID=$!
|
||||
|
||||
cleanup() {
|
||||
if kill -0 "$CAPTURE_PID" 2>/dev/null; then
|
||||
echo "==> stopping capture"
|
||||
kill -INT "$CAPTURE_PID" 2>/dev/null || true
|
||||
wait "$CAPTURE_PID" 2>/dev/null || true
|
||||
WAIT_ARGS=()
|
||||
CAPTURE_PID=""
|
||||
if [ "$MODE" = "tracy" ]; then
|
||||
WAIT_ARGS+=(-Dangelica.flyby.waitForTracy=true)
|
||||
|
||||
echo "==> starting capture -> $OUT"
|
||||
if [ -n "${CAPTURE_SECONDS:-}" ]; then
|
||||
"$TRACY_CAPTURE" -o "$OUT" -s "$CAPTURE_SECONDS" &
|
||||
else
|
||||
"$TRACY_CAPTURE" -o "$OUT" &
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
CAPTURE_PID=$!
|
||||
|
||||
cleanup() {
|
||||
if kill -0 "$CAPTURE_PID" 2>/dev/null; then
|
||||
echo "==> stopping capture"
|
||||
kill -INT "$CAPTURE_PID" 2>/dev/null || true
|
||||
wait "$CAPTURE_PID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
fi
|
||||
|
||||
echo "==> launching game (route=$ROUTE warmup=$WARMUP length=${LENGTH:-default} scene=${SCENE_ABS:-none})"
|
||||
if [ -n "$JFR_OUT" ]; then
|
||||
echo "==> recording JFR -> $JFR_ABS"
|
||||
fi
|
||||
if [ "$MODE" = "tracy" ] && [ -n "$JFR_OUT" ]; then
|
||||
echo "==> both profilers active: timings inflated, use for correlation only"
|
||||
fi
|
||||
|
||||
./gradlew runClient25 --console=plain \
|
||||
-Dangelica.flyby.route="$ROUTE" \
|
||||
-Dangelica.flyby.length="$LENGTH" \
|
||||
-Dangelica.flyby.speed="${FLYBY_SPEED:-0}" \
|
||||
-Dangelica.flyby.warmupTicks="$WARMUP" \
|
||||
-Dangelica.flyby.waitForTracy=true \
|
||||
${WAIT_ARGS[@]+"${WAIT_ARGS[@]}"} \
|
||||
-Dangelica.flyby.exitWhenDone=true \
|
||||
-DquickPlaySingleplayer="${QUICKPLAY_WORLD:-latest}" \
|
||||
${BACKEND_ARGS[@]+"${BACKEND_ARGS[@]}"} \
|
||||
${SCENE_ARGS[@]+"${SCENE_ARGS[@]}"} \
|
||||
${PROFILE_ARGS[@]+"${PROFILE_ARGS[@]}"} \
|
||||
"$@"
|
||||
|
||||
echo "==> game exited, finalising capture"
|
||||
cleanup
|
||||
trap - EXIT
|
||||
|
||||
if [ -s "$OUT" ]; then
|
||||
echo "==> wrote $OUT ($(du -h "$OUT" | cut -f1))"
|
||||
else
|
||||
echo "error: $OUT is empty - did the game connect to Tracy?" >&2
|
||||
exit 1
|
||||
if [ "$MODE" = "tracy" ]; then
|
||||
cleanup
|
||||
trap - EXIT
|
||||
fi
|
||||
|
||||
if [ "$MODE" = "jfr" ]; then
|
||||
if [ -s "$OUT" ]; then
|
||||
echo "==> wrote $OUT ($(du -h "$OUT" | cut -f1))"
|
||||
else
|
||||
echo "error: $OUT is empty - did the run reach warmup?" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if [ -s "$OUT" ]; then
|
||||
echo "==> wrote $OUT ($(du -h "$OUT" | cut -f1))"
|
||||
else
|
||||
echo "error: $OUT is empty - did the game connect to Tracy?" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$JFR_OUT" ]; then
|
||||
if [ -s "$JFR_OUT" ]; then
|
||||
echo "==> wrote $JFR_OUT ($(du -h "$JFR_OUT" | cut -f1))"
|
||||
else
|
||||
echo "error: $JFR_OUT is empty - did the run reach warmup?" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package me.eigenraven.lwjgl3ify.api;
|
||||
|
||||
public final class DisplayEvents {
|
||||
|
||||
private DisplayEvents() {}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import static org.lwjgl.sdl.SDLVideo.SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier.Check;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -25,6 +27,7 @@ import me.eigenraven.lwjgl3ify.api.SwapchainInvalidatingChange;
|
||||
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class SDLGPUGate {
|
||||
@@ -51,10 +54,26 @@ public final class SDLGPUGate {
|
||||
return device;
|
||||
}
|
||||
|
||||
private static final boolean SDL_GPU_AVAILABLE = SDLGPUGate.class.getClassLoader().getResource("org/lwjgl/sdl/SDLGPU.class") != null;
|
||||
public static final String LWJGL3IFY_MIN_VERSION = "3.0.25";
|
||||
|
||||
private static final List<Check> REQUIREMENTS = List.of(
|
||||
new Check("/org/lwjgl/sdl/SDLGPU.class", "no lwjgl-sdl on the classpath"),
|
||||
new Check("/me/eigenraven/lwjgl3ify/api/DisplayEvents.class", "lwjgl3ify older than " + LWJGL3IFY_MIN_VERSION + ", update the mod: https://modrinth.com/mod/lwjgl3ify"),
|
||||
new Check("/org/lwjgl/util/shaderc/Shaderc.class", staleInstanceLibraries("lwjgl-shaderc")),
|
||||
new Check("/org/lwjgl/util/spvc/Spvc.class", staleInstanceLibraries("lwjgl-spvc")));
|
||||
|
||||
private static final Check MISSING_REQUIREMENT = DependencyVerifier.firstMissing(SDLGPUGate.class, REQUIREMENTS);
|
||||
|
||||
private static String staleInstanceLibraries(String artifact) {
|
||||
return "no " + artifact + " on the classpath; the launcher instance library list predates lwjgl3ify " + LWJGL3IFY_MIN_VERSION + ", re-apply lwjgl3ify-<version>-multimc.zip (overwrites mmc-pack.json)";
|
||||
}
|
||||
|
||||
public static String missingRequirement() {
|
||||
return MISSING_REQUIREMENT == null ? null : MISSING_REQUIREMENT.errorMessage();
|
||||
}
|
||||
|
||||
public static boolean isSDLGPUAvailable() {
|
||||
return SDL_GPU_AVAILABLE;
|
||||
return MISSING_REQUIREMENT == null;
|
||||
}
|
||||
|
||||
public static boolean isDeviceReady() {
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.Presenter;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.MainStartOnFirstThread;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.AttachmentClear;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.DrawDispatch;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.FFPTrace;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier;
|
||||
@@ -30,6 +31,7 @@ import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineStore;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.VertexAttribs;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.BufferParams;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ByteRegionCopy;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.CopyRectClip;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
@@ -150,6 +152,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public long nextSeq() { return TransferThread.nextSeq(); }
|
||||
});
|
||||
private final PipelineApplier pipelineApplier = new PipelineApplier(frameManager, resourceManager, shaderManager, pipelineStore, fboClearTracker, persistentSync, samplerBinder, storageTextureBinder, storageBufferBinder);
|
||||
private final AttachmentClear attachmentClear = new AttachmentClear(frameManager, pipelineStore, shaderManager);
|
||||
private final DrawDispatch drawDispatch = new DrawDispatch(device, frameManager, resourceManager, pipelineApplier, this::enqueuePreCopied);
|
||||
private final FFPTrace ffpTrace = SystemProperties.FFP_TRACE ? new FFPTrace(resourceManager) : null;
|
||||
|
||||
@@ -157,6 +160,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
private boolean droppedCopyWarned;
|
||||
private boolean droppedSsboWriteWarned;
|
||||
private boolean depthClearSuppressedWarned;
|
||||
private boolean stencilClearSuppressedWarned;
|
||||
private final DeferredCopyOp deferredCopyOp = new DeferredCopyOp();
|
||||
|
||||
private TransferThread transferThread;
|
||||
@@ -187,15 +192,22 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
private void markAllPipelineInputDirty() {
|
||||
pipelineStore.bumpLivenessGen();
|
||||
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;
|
||||
public static final int CTR_KEY_RECOMPUTES = 3;
|
||||
public static final int CTR_FAST_PATH_HITS = 4;
|
||||
public static final int CTR_COUNT = 5;
|
||||
public static final int CTR_INPUT_HASH_LOOPS = 5;
|
||||
public static final int CTR_INPUT_HASH_HITS = 6;
|
||||
public static final int CTR_COUNT = 7;
|
||||
|
||||
public static void takeContextCounters(int[] out) {
|
||||
Arrays.fill(out, 0);
|
||||
@@ -205,11 +217,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
out[CTR_SSBO_BINDS] += st.ssboBinds;
|
||||
out[CTR_KEY_RECOMPUTES] += st.pipeline.keyRecomputes;
|
||||
out[CTR_FAST_PATH_HITS] += st.pipeline.fastPathHits;
|
||||
out[CTR_INPUT_HASH_LOOPS] += st.pipeline.inputHashLoopRuns;
|
||||
out[CTR_INPUT_HASH_HITS] += st.pipeline.inputHashHits;
|
||||
st.slotWrites = 0;
|
||||
st.slotWritesElided = 0;
|
||||
st.ssboBinds = 0;
|
||||
st.pipeline.keyRecomputes = 0;
|
||||
st.pipeline.fastPathHits = 0;
|
||||
st.pipeline.inputHashLoopRuns = 0;
|
||||
st.pipeline.inputHashHits = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,6 +502,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
registeredStates.clear();
|
||||
|
||||
drawDispatch.clearWarnedState();
|
||||
attachmentClear.shutdown();
|
||||
pipelineStore.shutdown();
|
||||
shaderManager.shutdown();
|
||||
resourceManager.shutdown();
|
||||
@@ -940,8 +957,22 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final ContextState cs = s();
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
final boolean wantColor = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
|
||||
final boolean wantDepth = (mask & GL11.GL_DEPTH_BUFFER_BIT) != 0;
|
||||
final boolean wantStencil = (mask & GL11.GL_STENCIL_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) {
|
||||
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) {
|
||||
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 && !wantDepth && !wantStencil) return;
|
||||
|
||||
final ContextState st = cs;
|
||||
@@ -970,6 +1001,14 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
boolean affectsActivePass = false;
|
||||
final boolean passActive = frameManager.isRenderPassActive();
|
||||
final long activeColor = passActive ? frameManager.getCurrentColorTarget() : 0;
|
||||
@@ -994,7 +1033,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
if (affectsActivePass) {
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1741,14 +1780,14 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
if (buffer == GL11.GL_NONE) {
|
||||
if (fbo.drawBuffers.length == 0) return;
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_TARGET);
|
||||
fbo.drawBuffers = EMPTY_DRAW_BUFFERS;
|
||||
markDrawBuffersChanged(fbo, true);
|
||||
return;
|
||||
}
|
||||
final int idx = classifyDrawBuffer(buffer);
|
||||
if (fbo.drawBuffers.length == 1 && fbo.drawBuffers[0] == idx) return;
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_TARGET);
|
||||
if (fbo.drawBuffers.length == 1) {
|
||||
fbo.drawBuffers[0] = idx;
|
||||
} else {
|
||||
@@ -1775,7 +1814,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
if (same) return;
|
||||
}
|
||||
if (isBound) frameManager.endRenderPassIfActive();
|
||||
if (isBound) frameManager.endRenderPassIfActive(FrameManager.PASS_END_TARGET);
|
||||
final int[] indices = (fbo.drawBuffers.length == count) ? fbo.drawBuffers : new int[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
indices[i] = classifyDrawBuffer(bufs.get(basePos + i));
|
||||
@@ -2030,7 +2069,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
cs.pipeline.fragmentShader = prog.sdlFragmentShader;
|
||||
cs.pipeline.programId = program;
|
||||
cs.pipeline.maxFragOutputLocation = prog.maxFragOutputLocation;
|
||||
cs.pipeline.setVertexInputs(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType, prog.vertexInputName);
|
||||
cs.pipeline.setVertexInputs(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType, prog.vertexInputName, prog.vertexInputSetId);
|
||||
cs.pipeline.markInputDirty();
|
||||
cs.pipeline.markShaderDirty();
|
||||
}
|
||||
@@ -2081,7 +2120,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
cs.pipeline.fragmentShader = prog.sdlFragmentShader;
|
||||
cs.pipeline.programId = program;
|
||||
cs.pipeline.maxFragOutputLocation = prog.maxFragOutputLocation;
|
||||
cs.pipeline.setVertexInputs(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType, prog.vertexInputName);
|
||||
cs.pipeline.setVertexInputs(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType, prog.vertexInputName, prog.vertexInputSetId);
|
||||
cs.pipeline.markShaderDirty();
|
||||
}
|
||||
|
||||
@@ -2273,16 +2312,21 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final ContextState st = s();
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog != null) {
|
||||
final String name = prog.locationToName.get(location);
|
||||
if (name != null && prog.allSamplerNames.contains(name)) {
|
||||
if (prog.samplerTextureUnits.put(name, v0) != v0) prog.samplerUnitsDirty = true;
|
||||
final boolean inBounds = location < prog.locationKind.length;
|
||||
final byte kind = inBounds ? prog.locationKind[location] : ShaderManager.ProgramObject.LOCATION_KIND_PLAIN;
|
||||
if (kind == ShaderManager.ProgramObject.LOCATION_KIND_SAMPLER) {
|
||||
if (prog.locationSamplerUnit[location] != v0) {
|
||||
prog.locationSamplerUnit[location] = v0;
|
||||
prog.samplerTextureUnits.put(prog.locationName[location], v0);
|
||||
prog.samplerUnitsDirty = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (name != null && prog.allImageNames.contains(name)) {
|
||||
prog.imageTextureUnits.put(name, v0);
|
||||
if (kind == ShaderManager.ProgramObject.LOCATION_KIND_IMAGE) {
|
||||
prog.imageTextureUnits.put(prog.locationName[location], v0);
|
||||
return;
|
||||
}
|
||||
if (debugLabels.isEnabled() && name == null && !prog.allSamplerNames.isEmpty() && v0 >= 0 && v0 <= 31) {
|
||||
if (debugLabels.isEnabled() && !inBounds && !prog.allSamplerNames.isEmpty() && v0 >= 0 && v0 <= 31) {
|
||||
LOG.warn("uniform1i: loc={} val={} prog={} -- location NOT in locationToName (samplers={})", location, v0, st.boundProgram, prog.allSamplerNames);
|
||||
}
|
||||
} else if (debugLabels.isEnabled() && v0 >= 0 && v0 <= 16) {
|
||||
@@ -2709,11 +2753,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (target == GL31.GL_UNIFORM_BUFFER) {
|
||||
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
|
||||
if (shadow != null) {
|
||||
final ByteBuffer src = data.duplicate();
|
||||
final int rem = src.remaining();
|
||||
shadow.position((int) offset).limit((int) offset + rem);
|
||||
shadow.put(src);
|
||||
shadow.position(0);
|
||||
ByteRegionCopy.copyByteRegion(data, data.position(), shadow, (int) offset, data.remaining());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -3027,7 +3068,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
private void downloadBufferBlocking(long handle, int offset, int size, ByteBuffer out) {
|
||||
flushDeferredUploadsForRead();
|
||||
frameManager.endCopyPassIfActive();
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COPY);
|
||||
final long cb = SDL_AcquireGPUCommandBuffer(device.getDevice());
|
||||
if (cb == 0) return;
|
||||
resourceManager.downloadFromBuffer(cb, handle, offset, size, out);
|
||||
@@ -3179,7 +3220,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
private void applyVertexAttribPointer(int index, int size, int type, boolean normalized, boolean isInteger, int stride, long pointer) {
|
||||
vertexAttribs.applyVertexAttribPointer(s(), index, size, type, normalized, isInteger, stride, pointer);
|
||||
vertexAttribs.applyVertexAttribPointer(pipelineStore, s(), index, size, type, normalized, isInteger, stride, pointer);
|
||||
}
|
||||
@Override public void enableVertexAttribArray(int index) {
|
||||
final ContextState cs = s();
|
||||
@@ -3221,6 +3262,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
vao.bindingDivisor[b] = effective;
|
||||
st.bumpAttribStateGen();
|
||||
cs.pipeline.markInputDirty();
|
||||
vao.invalidateInputHash();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3237,10 +3279,12 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (oldBuffer != buffer) {
|
||||
cs.pipeline.markInputDirtyIfLivenessChanged(pipelineStore, oldBuffer, buffer);
|
||||
}
|
||||
vao.invalidateInputHash();
|
||||
}
|
||||
if (vao.bindingStride[bindingindex] != stride) {
|
||||
vao.bindingStride[bindingindex] = stride;
|
||||
cs.pipeline.markInputDirty();
|
||||
vao.invalidateInputHash();
|
||||
}
|
||||
}
|
||||
@Override public void vertexAttribFormat(int attribindex, int size, int type, boolean normalized, int relativeoffset) {
|
||||
@@ -3248,6 +3292,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (attribindex >= ContextState.MAX_VERTEX_ATTRIBS) return;
|
||||
final ContextState st = cs;
|
||||
final ContextState.VAOState vao = st.currentVao;
|
||||
if (vao.attribSize[attribindex] == size && vao.attribType[attribindex] == type && vao.attribNormalized[attribindex] == normalized
|
||||
&& !vao.attribIsInteger[attribindex] && vao.attribRelativeOffset[attribindex] == relativeoffset)
|
||||
return;
|
||||
vao.attribSize[attribindex] = size;
|
||||
vao.attribType[attribindex] = type;
|
||||
vao.attribNormalized[attribindex] = normalized;
|
||||
@@ -3255,12 +3302,16 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
vao.attribRelativeOffset[attribindex] = relativeoffset;
|
||||
st.bumpAttribStateGen();
|
||||
cs.pipeline.markInputDirty();
|
||||
vao.invalidateInputHash();
|
||||
}
|
||||
@Override public void vertexAttribIFormat(int attribindex, int size, int type, int relativeoffset) {
|
||||
final ContextState cs = s();
|
||||
if (attribindex >= ContextState.MAX_VERTEX_ATTRIBS) return;
|
||||
final ContextState st = cs;
|
||||
final ContextState.VAOState vao = st.currentVao;
|
||||
if (vao.attribSize[attribindex] == size && vao.attribType[attribindex] == type && vao.attribIsInteger[attribindex]
|
||||
&& vao.attribRelativeOffset[attribindex] == relativeoffset)
|
||||
return;
|
||||
vao.attribSize[attribindex] = size;
|
||||
vao.attribType[attribindex] = type;
|
||||
vao.attribNormalized[attribindex] = false;
|
||||
@@ -3268,6 +3319,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
vao.attribRelativeOffset[attribindex] = relativeoffset;
|
||||
st.bumpAttribStateGen();
|
||||
cs.pipeline.markInputDirty();
|
||||
vao.invalidateInputHash();
|
||||
}
|
||||
@Override public void vertexAttribBinding(int attribindex, int bindingindex) {
|
||||
final ContextState cs = s();
|
||||
@@ -3278,6 +3330,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
vao.attribBinding[attribindex] = bindingindex;
|
||||
st.bumpAttribStateGen();
|
||||
cs.pipeline.markInputDirty();
|
||||
vao.invalidateInputHash();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3354,7 +3407,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (meta.levels() <= 1) return;
|
||||
// SDL asserts if any pass (render or copy) is active when generating mipmaps.
|
||||
frameManager.endCopyPassIfActive();
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COPY);
|
||||
frameManager.noteMipGen();
|
||||
textureOps.clearPendingMipGen(texture);
|
||||
SDL_GenerateMipmapsForGPUTexture(frameManager.getCommandBuffer(), resourceManager.getTextureHandle(texture));
|
||||
|
||||
@@ -158,7 +158,7 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
final ShaderManager.ProgramObject prog = shaderManager.getProgram(programId);
|
||||
if (prog == null || !prog.linked || prog.sdlComputePipeline == 0) return 0;
|
||||
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COMPUTE);
|
||||
frameManager.endCopyPassIfActive();
|
||||
final long cb = frameManager.getCommandBuffer();
|
||||
if (cb == 0) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Per-thread (per-GL-context) client state for {@link SDLGPURenderBackend}.
|
||||
@@ -93,6 +94,16 @@ public final class ContextState {
|
||||
public final int[] bindingStride = new int[MAX_VERTEX_ATTRIBS];
|
||||
public final int[] bindingDivisor = new int[MAX_VERTEX_ATTRIBS];
|
||||
public int elementBuffer;
|
||||
|
||||
public static final int INPUT_HASH_SLOTS = 8;
|
||||
public final int[] slotInputSetId = new int[INPUT_HASH_SLOTS];
|
||||
public final int[] slotEnabledMask = new int[INPUT_HASH_SLOTS];
|
||||
public final int[] slotMaxAttribs = new int[INPUT_HASH_SLOTS];
|
||||
public final long[] slotHash = new long[INPUT_HASH_SLOTS];
|
||||
public int slotsLivenessGen;
|
||||
public int slotCursor;
|
||||
public void invalidateInputHash() { Arrays.fill(slotInputSetId, 0); }
|
||||
|
||||
public VAOState() { for (int i = 0; i < MAX_VERTEX_ATTRIBS; i++) attribBinding[i] = i; }
|
||||
}
|
||||
|
||||
@@ -387,4 +398,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;
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,14 @@ public final class FrameManager {
|
||||
private final Device device;
|
||||
private ResourceManager resourceManager;
|
||||
|
||||
public static final int PASS_END_CLEAR = 0;
|
||||
public static final int PASS_END_COMPUTE = 1;
|
||||
public static final int PASS_END_COPY = 2;
|
||||
public static final int PASS_END_TARGET = 3;
|
||||
public static final int PASS_END_UNIFORM_BLOCK = 4;
|
||||
public static final int PASS_END_FRAME_END = 5;
|
||||
public static final int PASS_END_CAUSE_COUNT = 6;
|
||||
|
||||
public static final class FrameState {
|
||||
public final Thread owner = Thread.currentThread();
|
||||
public long commandBuffer;
|
||||
@@ -103,6 +111,8 @@ public final class FrameManager {
|
||||
public int arenaOverflowFlushesThisFrame;
|
||||
public int copyPassesThisFrame;
|
||||
public int materializedClearPassesThisFrame;
|
||||
public int inPassClearsThisFrame;
|
||||
public final int[] passEndCauseCountsThisFrame = new int[PASS_END_CAUSE_COUNT];
|
||||
public long lastEndFrameNanos;
|
||||
|
||||
public long pendingUploadCommandBuffer;
|
||||
@@ -282,7 +292,7 @@ public final class FrameManager {
|
||||
public boolean ensureFbo0RenderPass(FrameState f, ContextState st) {
|
||||
final boolean clearColor = st.pendingSwapchainClear;
|
||||
if (swapchainClearNeedsPassBreak(st, f)) {
|
||||
endRenderPassIfActive(f);
|
||||
endRenderPassIfActive(f, PASS_END_CLEAR);
|
||||
}
|
||||
final boolean applied = ensureFbo0RenderPass(f,
|
||||
clearColor ? st.pendingSwapchainR : st.clearR,
|
||||
@@ -305,7 +315,7 @@ public final class FrameManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
endActiveEncoders(f);
|
||||
endActiveEncoders(f, PASS_END_TARGET);
|
||||
|
||||
if (!f.clearedThisFrame) {
|
||||
clear = true;
|
||||
@@ -419,9 +429,11 @@ public final class FrameManager {
|
||||
|
||||
public void noteMaterializedClearPass() { frame().materializedClearPassesThisFrame++; }
|
||||
|
||||
public void noteInPassClear() { frame().inPassClearsThisFrame++; }
|
||||
|
||||
public long beginRenderPass(SDL_GPUColorTargetInfo.Buffer colorTargets, SDL_GPUDepthStencilTargetInfo depthTarget) {
|
||||
final FrameState f = frame();
|
||||
endActiveEncoders(f);
|
||||
endActiveEncoders(f, PASS_END_TARGET);
|
||||
if (preRenderPassHook != null) preRenderPassHook.run();
|
||||
assertNoEncoderActive(f, "SDL_BeginGPURenderPass");
|
||||
f.renderPass = SDL_BeginGPURenderPass(f.commandBuffer, colorTargets, depthTarget);
|
||||
@@ -436,11 +448,20 @@ public final class FrameManager {
|
||||
}
|
||||
|
||||
public void endRenderPassIfActive() {
|
||||
endRenderPassIfActive(frame());
|
||||
endRenderPassIfActive(frame(), PASS_END_TARGET);
|
||||
}
|
||||
|
||||
public void endRenderPassIfActive(FrameState f) {
|
||||
endRenderPassIfActive(f, PASS_END_TARGET);
|
||||
}
|
||||
|
||||
public void endRenderPassIfActive(int cause) {
|
||||
endRenderPassIfActive(frame(), cause);
|
||||
}
|
||||
|
||||
public void endRenderPassIfActive(FrameState f, int cause) {
|
||||
if (f.renderPass != 0) {
|
||||
f.passEndCauseCountsThisFrame[cause]++;
|
||||
SDL_EndGPURenderPass(f.renderPass);
|
||||
f.renderPass = 0;
|
||||
f.currentColorTarget = 0;
|
||||
@@ -449,9 +470,9 @@ public final class FrameManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void endActiveEncoders(FrameState f) {
|
||||
private void endActiveEncoders(FrameState f, int cause) {
|
||||
endCopyPassIfActive(f);
|
||||
endRenderPassIfActive(f);
|
||||
endRenderPassIfActive(f, cause);
|
||||
}
|
||||
|
||||
/** Dev-only check */
|
||||
@@ -459,7 +480,7 @@ public final class FrameManager {
|
||||
if (!SystemProperties.SDL_ENCODER_ASSERTIONS) return;
|
||||
if (f.renderPass != 0 || f.copyPass != 0 || f.computePassOpen) {
|
||||
LOG.error("Encoder invariant violated before {}: renderPass={} copyPass={} computePassOpen={} on CB={}", about, f.renderPass, f.copyPass, f.computePassOpen, f.commandBuffer);
|
||||
endActiveEncoders(f);
|
||||
endActiveEncoders(f, PASS_END_TARGET);
|
||||
if (SystemProperties.SDL_ENCODER_ASSERTIONS_FATAL) {
|
||||
throw new IllegalStateException("Encoder invariant violated before " + about);
|
||||
}
|
||||
@@ -485,6 +506,10 @@ public final class FrameManager {
|
||||
}
|
||||
|
||||
public long ensureCopyPass() {
|
||||
return ensureCopyPass(PASS_END_COPY);
|
||||
}
|
||||
|
||||
public long ensureCopyPass(int cause) {
|
||||
final FrameState f = frame();
|
||||
if (f.copyPass != 0) {
|
||||
if (f.commandBuffer == 0 && shouldAutoSubmitPendingUpload(f)) {
|
||||
@@ -494,7 +519,7 @@ public final class FrameManager {
|
||||
return f.copyPass;
|
||||
}
|
||||
}
|
||||
endActiveEncoders(f);
|
||||
endActiveEncoders(f, cause);
|
||||
final long cb = getCommandBuffer(f);
|
||||
if (cb == 0) return 0;
|
||||
assertNoEncoderActive(f, "SDL_BeginGPUCopyPass");
|
||||
@@ -552,7 +577,7 @@ public final class FrameManager {
|
||||
if (beforeSubmit != null) beforeSubmit.run();
|
||||
|
||||
endCopyPassIfActive(f);
|
||||
endRenderPassIfActive(f);
|
||||
endRenderPassIfActive(f, PASS_END_FRAME_END);
|
||||
|
||||
if (f.commandBuffer != 0) {
|
||||
Tracy.beginZone(Z_SDL_SUBMIT);
|
||||
@@ -610,6 +635,8 @@ public final class FrameManager {
|
||||
f.arenaOverflowFlushesThisFrame = 0;
|
||||
f.copyPassesThisFrame = 0;
|
||||
f.materializedClearPassesThisFrame = 0;
|
||||
f.inPassClearsThisFrame = 0;
|
||||
Arrays.fill(f.passEndCauseCountsThisFrame, 0);
|
||||
f.presentSkipsThisFrame = 0;
|
||||
f.emptyFramesThisFrame = 0;
|
||||
f.droppedDrawsThisFrame = 0;
|
||||
@@ -622,7 +649,7 @@ public final class FrameManager {
|
||||
final FrameState f = frame();
|
||||
if (f.commandBuffer == 0) return;
|
||||
endCopyPassIfActive(f);
|
||||
endRenderPassIfActive(f);
|
||||
endRenderPassIfActive(f, PASS_END_FRAME_END);
|
||||
Tracy.beginZone(Z_SDL_SUBMIT);
|
||||
try {
|
||||
if (f.wantFenceOnNextSubmit) {
|
||||
|
||||
@@ -57,7 +57,16 @@ final class SdlFramePlots {
|
||||
private static final long PLOT_BUFFER_POOL_HITS = Tracy.plotHandle("sdl.bufferPoolHits");
|
||||
private static final long PLOT_PIPELINE_KEY_RECOMPUTES = Tracy.plotHandle("sdl.pipelineKeyRecomputes");
|
||||
private static final long PLOT_PIPELINE_FAST_PATH_HITS = Tracy.plotHandle("sdl.pipelineFastPathHits");
|
||||
private static final long PLOT_INPUT_HASH_LOOPS = Tracy.plotHandle("sdl.inputHashLoops");
|
||||
private static final long PLOT_INPUT_HASH_HITS = Tracy.plotHandle("sdl.inputHash.hits");
|
||||
private static final long PLOT_MID_FRAME_SUBMITS = Tracy.plotHandle("sdl.midFrameSubmits");
|
||||
private static final long PLOT_IN_PASS_CLEARS = Tracy.plotHandle("sdl.clears.inPass");
|
||||
private static final long PLOT_PASS_END_CLEAR = Tracy.plotHandle("sdl.passEnd.clear");
|
||||
private static final long PLOT_PASS_END_COMPUTE = Tracy.plotHandle("sdl.passEnd.compute");
|
||||
private static final long PLOT_PASS_END_COPY = Tracy.plotHandle("sdl.passEnd.copy");
|
||||
private static final long PLOT_PASS_END_TARGET = Tracy.plotHandle("sdl.passEnd.target");
|
||||
private static final long PLOT_PASS_END_UNIFORM_BLOCK = Tracy.plotHandle("sdl.passEnd.uniformBlock");
|
||||
private static final long PLOT_PASS_END_FRAME_END = Tracy.plotHandle("sdl.passEnd.frameEnd");
|
||||
|
||||
private static final int[] contextCounters = new int[SDLGPURenderBackend.CTR_COUNT];
|
||||
|
||||
@@ -80,6 +89,8 @@ final class SdlFramePlots {
|
||||
Tracy.plotInt(PLOT_BUFFER_POOL_HITS, resourceManager.takeBufferPoolHitCount());
|
||||
Tracy.plotInt(PLOT_PIPELINE_KEY_RECOMPUTES, contextCounters[SDLGPURenderBackend.CTR_KEY_RECOMPUTES]);
|
||||
Tracy.plotInt(PLOT_PIPELINE_FAST_PATH_HITS, contextCounters[SDLGPURenderBackend.CTR_FAST_PATH_HITS]);
|
||||
Tracy.plotInt(PLOT_INPUT_HASH_LOOPS, contextCounters[SDLGPURenderBackend.CTR_INPUT_HASH_LOOPS]);
|
||||
Tracy.plotInt(PLOT_INPUT_HASH_HITS, contextCounters[SDLGPURenderBackend.CTR_INPUT_HASH_HITS]);
|
||||
Tracy.plotInt(PLOT_COMPUTE_PASSES, f.computePassesThisFrame);
|
||||
Tracy.plotInt(PLOT_COMPUTE_BATCH_JOINS, f.computeBatchJoinsThisFrame);
|
||||
Tracy.plotInt(PLOT_SUBMITS, f.submitsThisFrame);
|
||||
@@ -111,5 +122,12 @@ final class SdlFramePlots {
|
||||
Tracy.plotInt(PLOT_EMPTY_FRAMES, f.emptyFramesThisFrame);
|
||||
Tracy.plotInt(PLOT_DROPPED_DRAWS, f.droppedDrawsThisFrame);
|
||||
Tracy.plotInt(PLOT_MID_FRAME_SUBMITS, f.midFrameSubmitsThisFrame);
|
||||
Tracy.plotInt(PLOT_IN_PASS_CLEARS, f.inPassClearsThisFrame);
|
||||
Tracy.plotInt(PLOT_PASS_END_CLEAR, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_CLEAR]);
|
||||
Tracy.plotInt(PLOT_PASS_END_COMPUTE, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_COMPUTE]);
|
||||
Tracy.plotInt(PLOT_PASS_END_COPY, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_COPY]);
|
||||
Tracy.plotInt(PLOT_PASS_END_TARGET, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_TARGET]);
|
||||
Tracy.plotInt(PLOT_PASS_END_UNIFORM_BLOCK, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_UNIFORM_BLOCK]);
|
||||
Tracy.plotInt(PLOT_PASS_END_FRAME_END, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_FRAME_END]);
|
||||
}
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
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;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDL_GPUColorTargetDescription;
|
||||
import org.lwjgl.sdl.SDL_GPUDepthStencilState;
|
||||
import org.lwjgl.sdl.SDL_GPUGraphicsPipelineCreateInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUGraphicsPipelineTargetInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUMultisampleState;
|
||||
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 java.util.Arrays;
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.system.MemoryStack.stackPush;
|
||||
|
||||
public final class AttachmentClear {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
|
||||
private static final String VERTEX_SOURCE = "angelica:sdlgpu/clear_attachment.vsh";
|
||||
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 final FrameManager frameManager;
|
||||
private final PipelineStore pipelineStore;
|
||||
private final ShaderManager shaderManager;
|
||||
|
||||
private int program;
|
||||
private long sdlVertexShader;
|
||||
private long sdlFragmentShader;
|
||||
private boolean failed;
|
||||
|
||||
private final long[] cacheKeys = new long[CACHE_SLOTS];
|
||||
private final long[] cachePipelines = new long[CACHE_SLOTS];
|
||||
private int cacheNext;
|
||||
|
||||
private final int[] scissorRect = new int[4];
|
||||
|
||||
private final Builder builder = new Builder();
|
||||
private int[] buildColorFormats;
|
||||
private int buildColorCount;
|
||||
private int buildDepthFormat;
|
||||
private boolean buildDepthFlag;
|
||||
private boolean buildStencilFlag;
|
||||
private int buildWriteMask;
|
||||
|
||||
public AttachmentClear(FrameManager frameManager, PipelineStore pipelineStore, ShaderManager shaderManager) {
|
||||
this.frameManager = frameManager;
|
||||
this.pipelineStore = pipelineStore;
|
||||
this.shaderManager = shaderManager;
|
||||
}
|
||||
|
||||
private final class Builder implements LongSupplier {
|
||||
@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);
|
||||
}
|
||||
|
||||
private boolean computeScissor(ContextState st, FboState fbo) {
|
||||
final int sx, sy, sw, sh;
|
||||
if (st.scissorEnabled) {
|
||||
sx = st.scissorX;
|
||||
sy = st.scissorY;
|
||||
sw = st.scissorW;
|
||||
sh = st.scissorH;
|
||||
} else {
|
||||
sx = 0;
|
||||
sy = 0;
|
||||
sw = fbo.width;
|
||||
sh = fbo.height;
|
||||
}
|
||||
return ScissorClamp.clamp(sx, sy, sw, sh, fbo.width, fbo.height, scissorRect);
|
||||
}
|
||||
|
||||
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);
|
||||
if (pipeline == 0) return false;
|
||||
if (!computeScissor(st, fbo)) return false;
|
||||
|
||||
final long rp = f.renderPass;
|
||||
SDL_BindGPUGraphicsPipeline(rp, pipeline);
|
||||
|
||||
final float clearDepth = Math.min(1.0f, Math.max(0.0f, st.depthClearValue));
|
||||
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.MIN_DEPTH, clearDepth);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.MAX_DEPTH, clearDepth);
|
||||
SDL_SetGPUViewport(rp, st.cachedViewport);
|
||||
|
||||
final long scAddr = st.cachedScissor.address();
|
||||
MemoryAccess.putInt(scAddr + SDL_Rect.X, scissorRect[ScissorClamp.X]);
|
||||
MemoryAccess.putInt(scAddr + SDL_Rect.Y, scissorRect[ScissorClamp.Y]);
|
||||
MemoryAccess.putInt(scAddr + SDL_Rect.W, scissorRect[ScissorClamp.W]);
|
||||
MemoryAccess.putInt(scAddr + SDL_Rect.H, scissorRect[ScissorClamp.H]);
|
||||
SDL_SetGPUScissor(rp, st.cachedScissor);
|
||||
|
||||
if (stencil) {
|
||||
SDL_SetGPUStencilReference(rp, (byte) st.stencilClearValue);
|
||||
}
|
||||
|
||||
SDL_DrawGPUPrimitives(rp, 3, 1, 0, 0);
|
||||
|
||||
st.lastBoundPipeline = 0;
|
||||
st.viewportDirty = true;
|
||||
st.scissorDirty = true;
|
||||
st.lastAppliedStencilRef = Integer.MIN_VALUE;
|
||||
st.lastAppliedVboBindCb = 0;
|
||||
|
||||
if (depth) st.clearedTexturesThisFrame.add(fbo.depthTexture);
|
||||
if (stencil) st.clearedStencilTexturesThisFrame.add(fbo.depthTexture);
|
||||
|
||||
frameManager.noteInPassClear();
|
||||
return true;
|
||||
}
|
||||
|
||||
long getOrCreatePipeline(FboState fbo, boolean depthFlag, boolean stencilFlag, int writeMask) {
|
||||
if (!ensureShaders()) return 0;
|
||||
|
||||
final int[] formats = fbo.cachedColorFormats;
|
||||
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 < 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;
|
||||
final long pipeline = pipelineStore.getOrBuild(key, builder);
|
||||
if (pipeline != 0) {
|
||||
cacheKeys[cacheNext] = key;
|
||||
cachePipelines[cacheNext] = pipeline;
|
||||
cacheNext = (cacheNext + 1) & (CACHE_SLOTS - 1);
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
private long createPipeline() {
|
||||
try (var stack = stackPush()) {
|
||||
final int numColorTargets = buildColorCount;
|
||||
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.blend_state()
|
||||
.enable_blend(false)
|
||||
.color_write_mask((byte) 0)
|
||||
.enable_color_write_mask(true);
|
||||
}
|
||||
|
||||
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);
|
||||
if (buildStencilFlag) {
|
||||
depthStencil.front_stencil_state()
|
||||
.fail_op(SDL_GPU_STENCILOP_REPLACE)
|
||||
.pass_op(SDL_GPU_STENCILOP_REPLACE)
|
||||
.depth_fail_op(SDL_GPU_STENCILOP_REPLACE)
|
||||
.compare_op(SDL_GPU_COMPAREOP_ALWAYS);
|
||||
depthStencil.back_stencil_state()
|
||||
.fail_op(SDL_GPU_STENCILOP_REPLACE)
|
||||
.pass_op(SDL_GPU_STENCILOP_REPLACE)
|
||||
.depth_fail_op(SDL_GPU_STENCILOP_REPLACE)
|
||||
.compare_op(SDL_GPU_COMPAREOP_ALWAYS);
|
||||
}
|
||||
|
||||
final SDL_GPURasterizerState rasterizer = SDL_GPURasterizerState.calloc(stack)
|
||||
.cull_mode(SDL_GPU_CULLMODE_NONE)
|
||||
.front_face(SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE)
|
||||
.fill_mode(SDL_GPU_FILLMODE_FILL);
|
||||
|
||||
final SDL_GPUMultisampleState multisample = SDL_GPUMultisampleState.calloc(stack)
|
||||
.sample_count(SDL_GPU_SAMPLECOUNT_1);
|
||||
|
||||
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);
|
||||
|
||||
final SDL_GPUVertexInputState vertexInput = SDL_GPUVertexInputState.calloc(stack);
|
||||
|
||||
final SDL_GPUGraphicsPipelineCreateInfo ci = SDL_GPUGraphicsPipelineCreateInfo.calloc(stack)
|
||||
.vertex_shader(sdlVertexShader)
|
||||
.fragment_shader(sdlFragmentShader)
|
||||
.primitive_type(SDL_GPU_PRIMITIVETYPE_TRIANGLELIST)
|
||||
.rasterizer_state(rasterizer)
|
||||
.multisample_state(multisample)
|
||||
.depth_stencil_state(depthStencil)
|
||||
.target_info(targetInfo)
|
||||
.vertex_input_state(vertexInput);
|
||||
|
||||
final long pipeline = SDL_CreateGPUGraphicsPipeline(pipelineStore.device().getDevice(), ci);
|
||||
if (pipeline == 0) {
|
||||
LOG.error("AttachmentClear: pipeline creation failed: {}", SDLError.SDL_GetError());
|
||||
return PipelineStore.BAD_PIPELINE_SENTINEL;
|
||||
}
|
||||
return pipeline;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureShaders() {
|
||||
if (program != 0) return true;
|
||||
if (failed) return false;
|
||||
failed = true;
|
||||
|
||||
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.compileShader(fs);
|
||||
|
||||
final int prog = shaderManager.createProgram();
|
||||
shaderManager.attachShader(prog, vs);
|
||||
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);
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
|
||||
program = prog;
|
||||
sdlVertexShader = po.sdlVertexShader;
|
||||
sdlFragmentShader = po.sdlFragmentShader;
|
||||
failed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
program = 0;
|
||||
sdlVertexShader = 0;
|
||||
sdlFragmentShader = 0;
|
||||
failed = false;
|
||||
Arrays.fill(cacheKeys, 0L);
|
||||
Arrays.fill(cachePipelines, 0L);
|
||||
cacheNext = 0;
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ public final class DrawDispatch {
|
||||
st.fanIndexBuffer = createFanIndexBuffer(st.fanIndexBufferCapacity);
|
||||
st.fanIndexBufferOffset = 0;
|
||||
} else if (st.fanIndexBufferOffset + indexDataSize > st.fanIndexBufferCapacity) {
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COPY);
|
||||
resourceManager.releaseBufferDeferred(st.fanIndexBuffer);
|
||||
st.fanIndexBufferCapacity = Math.max(indexDataSize, st.fanIndexBufferCapacity * 2);
|
||||
st.fanIndexBuffer = createFanIndexBuffer(st.fanIndexBufferCapacity);
|
||||
|
||||
+14
-12
@@ -103,11 +103,11 @@ public final class PipelineApplier {
|
||||
}
|
||||
|
||||
if (!st.deferUploads && f.renderPass != 0 && st.attribDefaultsDirtyMask != 0 && (st.attribDefaultsDirtyMask & ~st.currentVao.attribEnabledMask) != 0) {
|
||||
frameManager.endRenderPassIfActive(f);
|
||||
frameManager.endRenderPassIfActive(f, FrameManager.PASS_END_COPY);
|
||||
}
|
||||
|
||||
if (f.renderPass != 0 && st.anyUniformBlockDirty()) {
|
||||
frameManager.endRenderPassIfActive(f);
|
||||
frameManager.endRenderPassIfActive(f, FrameManager.PASS_END_UNIFORM_BLOCK);
|
||||
if (Tracy.ENABLED) frameManager.notePerFrameBlockPassBreak();
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ public final class PipelineApplier {
|
||||
proposedStencilClear = false;
|
||||
}
|
||||
|
||||
frameManager.endRenderPassIfActive(f);
|
||||
frameManager.endRenderPassIfActive(f, FrameManager.PASS_END_TARGET);
|
||||
|
||||
final boolean reuse = fbo.cachedTargetsValid && fbo.cachedTargetsLayoutHash == layoutHash && fbo.cachedTargetsCount == totalTargets && fbo.cachedClearOpFlags == proposedClearOps && fbo.cachedDepthClearLast == proposedDepthClear && fbo.cachedStencilClearLast == proposedStencilClear;
|
||||
|
||||
@@ -503,13 +503,15 @@ public final class PipelineApplier {
|
||||
}
|
||||
|
||||
if (st.blendColorDirty) {
|
||||
final long addr = st.cachedBlendColor.address();
|
||||
MemoryAccess.putFloat(addr + SDL_FColor.R, st.blendColorR);
|
||||
MemoryAccess.putFloat(addr + SDL_FColor.G, st.blendColorG);
|
||||
MemoryAccess.putFloat(addr + SDL_FColor.B, st.blendColorB);
|
||||
MemoryAccess.putFloat(addr + SDL_FColor.A, st.blendColorA);
|
||||
SDL_SetGPUBlendConstants(rp, st.cachedBlendColor);
|
||||
st.blendColorDirty = false;
|
||||
if (st.pipeline.usesBlendConstants()) {
|
||||
final long addr = st.cachedBlendColor.address();
|
||||
MemoryAccess.putFloat(addr + SDL_FColor.R, st.blendColorR);
|
||||
MemoryAccess.putFloat(addr + SDL_FColor.G, st.blendColorG);
|
||||
MemoryAccess.putFloat(addr + SDL_FColor.B, st.blendColorB);
|
||||
MemoryAccess.putFloat(addr + SDL_FColor.A, st.blendColorA);
|
||||
SDL_SetGPUBlendConstants(rp, st.cachedBlendColor);
|
||||
st.blendColorDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (st.pipeline.effectiveStencilTestEnabled() && st.stencilRef != st.lastAppliedStencilRef) {
|
||||
@@ -729,6 +731,7 @@ public final class PipelineApplier {
|
||||
for (int b = 0; b < ShaderManager.BLOCK_COUNT; b++) {
|
||||
flushUniformBlock(st, b);
|
||||
}
|
||||
frameManager.endCopyPassIfActive();
|
||||
}
|
||||
|
||||
private void flushUniformBlock(ContextState st, int b) {
|
||||
@@ -752,13 +755,12 @@ public final class PipelineApplier {
|
||||
if (bytes == null) return;
|
||||
final long handle = resourceManager.getBufferHandle(block.glId);
|
||||
if (handle == 0) return;
|
||||
final long copyPass = frameManager.ensureCopyPass();
|
||||
final long copyPass = frameManager.ensureCopyPass(FrameManager.PASS_END_UNIFORM_BLOCK);
|
||||
if (copyPass == 0) return;
|
||||
|
||||
bytes.position(0).limit(Math.min(size, bytes.capacity()));
|
||||
|
||||
resourceManager.uploadToBuffer(copyPass, bytes, handle, 0, true);
|
||||
frameManager.endCopyPassIfActive();
|
||||
block.dirty = false;
|
||||
block.flushedThisFrame = true;
|
||||
if (Tracy.ENABLED) frameManager.noteUniformBlockFlush(b, size);
|
||||
|
||||
+65
-6
@@ -52,6 +52,7 @@ public final class PipelineCache {
|
||||
private boolean outputDirty = true;
|
||||
private boolean shaderDirty = true;
|
||||
private boolean inputDirty = true;
|
||||
private boolean usesBlendConstants;
|
||||
|
||||
private long cachedOutputHash;
|
||||
private long cachedShaderHash;
|
||||
@@ -191,7 +192,10 @@ public final class PipelineCache {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int maxAttribs;
|
||||
private int maxAttribs;
|
||||
private int shaderInputSetId;
|
||||
public int inputHashLoopRuns;
|
||||
public int inputHashHits;
|
||||
|
||||
private static final int[] EMPTY_INT = new int[0];
|
||||
private static final String[] EMPTY_STR = new String[0];
|
||||
@@ -213,6 +217,14 @@ public final class PipelineCache {
|
||||
return lastKey;
|
||||
}
|
||||
|
||||
public int maxAttribs() { return maxAttribs; }
|
||||
|
||||
public void setMaxAttribs(int n) {
|
||||
if (maxAttribs == n) return;
|
||||
maxAttribs = n;
|
||||
markInputDirty();
|
||||
}
|
||||
|
||||
public void markInputDirty() { inputDirty = true; lastKey = 0L; }
|
||||
|
||||
public boolean markInputDirtyIfLivenessChanged(PipelineStore store, int oldBuffer, int newBuffer) {
|
||||
@@ -223,19 +235,43 @@ public final class PipelineCache {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setVertexInputs(int mask, int[] vecSize, int[] baseType, String[] names) {
|
||||
if (shaderInputMask != mask || !Arrays.equals(shaderInputVecSize, vecSize) || !Arrays.equals(shaderInputBaseType, baseType)) {
|
||||
public void setVertexInputs(int mask, int[] vecSize, int[] baseType, String[] names, int vertexInputSetId) {
|
||||
if (shaderInputSetId != vertexInputSetId) {
|
||||
markInputDirty();
|
||||
}
|
||||
shaderInputSetId = vertexInputSetId;
|
||||
shaderInputMask = mask;
|
||||
shaderInputVecSize = vecSize;
|
||||
shaderInputBaseType = baseType;
|
||||
shaderInputName = names;
|
||||
}
|
||||
|
||||
public void markOutputDirty() { outputDirty = true; lastKey = 0L; }
|
||||
public void markOutputDirty() {
|
||||
outputDirty = true;
|
||||
lastKey = 0L;
|
||||
usesBlendConstants = computeUsesBlendConstants();
|
||||
}
|
||||
public void markShaderDirty() { shaderDirty = true; lastKey = 0L; }
|
||||
|
||||
public boolean usesBlendConstants() {
|
||||
return usesBlendConstants;
|
||||
}
|
||||
|
||||
private boolean computeUsesBlendConstants() {
|
||||
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
|
||||
if (!blendEnabledPerDrawBuffer[i]) continue;
|
||||
if (isBlendConstantFactor(srcColorFactors[i]) || isBlendConstantFactor(dstColorFactors[i])
|
||||
|| isBlendConstantFactor(srcAlphaFactors[i]) || isBlendConstantFactor(dstAlphaFactors[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isBlendConstantFactor(int factor) {
|
||||
return factor == SDL_GPU_BLENDFACTOR_CONSTANT_COLOR || factor == SDL_GPU_BLENDFACTOR_ONE_MINUS_CONSTANT_COLOR;
|
||||
}
|
||||
|
||||
public void invalidateShaderHandles() {
|
||||
vertexShader = 0;
|
||||
fragmentShader = 0;
|
||||
@@ -361,10 +397,27 @@ public final class PipelineCache {
|
||||
}
|
||||
|
||||
private long computeInputHash(PipelineStore store, ContextState cs) {
|
||||
if (cs == null) {
|
||||
return Hashing.fmix64(INPUT_SEED, Hashing.packHiLo(shaderInputMask, maxAttribs));
|
||||
}
|
||||
final ContextState.VAOState vao = cs.currentVao;
|
||||
final int livenessGen = store.livenessGen();
|
||||
if (vao.slotsLivenessGen != livenessGen) {
|
||||
Arrays.fill(vao.slotInputSetId, 0);
|
||||
vao.slotsLivenessGen = livenessGen;
|
||||
}
|
||||
final int enabledMask = vao.attribEnabledMask;
|
||||
for (int s = 0; s < ContextState.VAOState.INPUT_HASH_SLOTS; s++) {
|
||||
final int id = vao.slotInputSetId[s];
|
||||
if (id == 0) continue;
|
||||
if (id == shaderInputSetId && vao.slotEnabledMask[s] == enabledMask && vao.slotMaxAttribs[s] == maxAttribs) {
|
||||
if (Tracy.ENABLED) inputHashHits++;
|
||||
return vao.slotHash[s];
|
||||
}
|
||||
}
|
||||
inputHashLoopRuns++;
|
||||
long h = INPUT_SEED;
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(shaderInputMask, maxAttribs));
|
||||
if (cs == null) return h;
|
||||
final ContextState.VAOState vao = cs.currentVao;
|
||||
final int sibtLen = shaderInputBaseType.length;
|
||||
final int sivsLen = shaderInputVecSize.length;
|
||||
|
||||
@@ -392,6 +445,12 @@ public final class PipelineCache {
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(i, shaderInputVecSize[i]));
|
||||
}
|
||||
}
|
||||
final int slot = vao.slotCursor;
|
||||
vao.slotInputSetId[slot] = shaderInputSetId;
|
||||
vao.slotEnabledMask[slot] = enabledMask;
|
||||
vao.slotMaxAttribs[slot] = maxAttribs;
|
||||
vao.slotHash[slot] = h;
|
||||
vao.slotCursor = (slot + 1) % ContextState.VAOState.INPUT_HASH_SLOTS;
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ public final class PipelineStore {
|
||||
private final Device device;
|
||||
private final Long2LongOpenHashMap map = new Long2LongOpenHashMap();
|
||||
private volatile IntToLongFunction bufferHandleResolver;
|
||||
private int livenessGen = 1;
|
||||
|
||||
final Set<Long> loggedMismatch = ConcurrentHashMap.newKeySet();
|
||||
final Set<Long> loggedTypeZeroAttrib = ConcurrentHashMap.newKeySet();
|
||||
@@ -36,6 +37,9 @@ public final class PipelineStore {
|
||||
public void setBufferHandleResolver(IntToLongFunction resolver) { this.bufferHandleResolver = resolver; }
|
||||
IntToLongFunction bufferHandleResolver() { return bufferHandleResolver; }
|
||||
|
||||
public void bumpLivenessGen() { livenessGen++; }
|
||||
int livenessGen() { return livenessGen; }
|
||||
|
||||
public interface VertexVariantResolver {
|
||||
ShaderManager.VertexVariant resolve(int program, long key, List<UscaledRetype.Attrib> attribs);
|
||||
}
|
||||
|
||||
+16
-2
@@ -15,7 +15,7 @@ public final class VertexAttribs {
|
||||
public VertexAttribs() {
|
||||
}
|
||||
|
||||
public void applyVertexAttribPointer(ContextState st, int index, int size, int type, boolean normalized, boolean isInteger, int stride, long pointer) {
|
||||
public void applyVertexAttribPointer(PipelineStore store, ContextState st, int index, int size, int type, boolean normalized, boolean isInteger, int stride, long pointer) {
|
||||
if (index >= ContextState.MAX_VERTEX_ATTRIBS) return;
|
||||
final ContextState.VAOState vao = st.currentVao;
|
||||
final int vbo = st.boundArrayBuffer;
|
||||
@@ -24,6 +24,17 @@ public final class VertexAttribs {
|
||||
&& vao.attribVBO[index] == vbo && vao.attribBinding[index] == index && vao.attribRelativeOffset[index] == 0
|
||||
&& vao.bindingBuffer[index] == vbo && vao.bindingOffset[index] == pointer && vao.bindingStride[index] == stride)
|
||||
return;
|
||||
final boolean livenessChanged = st.pipeline.markInputDirtyIfLivenessChanged(store, vao.bindingBuffer[index], vbo);
|
||||
final boolean layoutChanged = livenessChanged
|
||||
|| vao.attribSize[index] != size
|
||||
|| vao.attribType[index] != type
|
||||
|| vao.attribNormalized[index] != normalized
|
||||
|| vao.attribIsInteger[index] != isInteger
|
||||
|| vao.attribStride[index] != stride
|
||||
|| vao.attribBinding[index] != index
|
||||
|| vao.attribRelativeOffset[index] != 0
|
||||
|| vao.bindingStride[index] != stride
|
||||
|| ((vao.bindingOffset[index] ^ pointer) & 3L) != 0;
|
||||
vao.attribSize[index] = size;
|
||||
vao.attribType[index] = type;
|
||||
vao.attribNormalized[index] = normalized;
|
||||
@@ -37,7 +48,10 @@ public final class VertexAttribs {
|
||||
vao.bindingOffset[index] = pointer;
|
||||
vao.bindingStride[index] = stride;
|
||||
st.bumpAttribStateGen();
|
||||
st.pipeline.markInputDirty();
|
||||
if (layoutChanged) {
|
||||
st.pipeline.markInputDirty();
|
||||
vao.invalidateInputHash();
|
||||
}
|
||||
}
|
||||
|
||||
public void warnDivisorClamp(int program, int attribIndex, int divisor) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class ByteRegionCopy {
|
||||
private ByteRegionCopy() {}
|
||||
|
||||
public static void copyByteRegion(ByteBuffer src, int srcOff, ByteBuffer dst, int dstOff, int len) {
|
||||
if (src.isDirect() && dst.isDirect()) {
|
||||
MemoryUtil.memCopy(MemoryUtil.memAddress(src, srcOff), MemoryUtil.memAddress(dst, dstOff), len);
|
||||
return;
|
||||
}
|
||||
if (src.hasArray() && dst.hasArray()) {
|
||||
System.arraycopy(src.array(), src.arrayOffset() + srcOff, dst.array(), dst.arrayOffset() + dstOff, len);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
dst.put(dstOff + i, src.get(srcOff + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -134,7 +134,7 @@ public final class FBOClearTracker {
|
||||
}
|
||||
|
||||
private void materializeFlush(ContextState st, LongArrayList colorHandles, IntArrayList colorGlIds, LongArrayList depthHandles, IntArrayList depthGlIds) {
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
|
||||
|
||||
final int n = colorHandles.size();
|
||||
boolean[] consumed = null;
|
||||
@@ -185,7 +185,7 @@ public final class FBOClearTracker {
|
||||
}
|
||||
frameManager.noteClearPass();
|
||||
frameManager.beginRenderPass(targets, null);
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ public final class FBOClearTracker {
|
||||
frameManager.noteClearPass();
|
||||
if (materialized) frameManager.noteMaterializedClearPass();
|
||||
frameManager.beginRenderPass(null, dt);
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
|
||||
}
|
||||
if (clearDepth) {
|
||||
st.pendingDepthValues.remove(handle);
|
||||
@@ -253,14 +253,14 @@ public final class FBOClearTracker {
|
||||
final boolean depthPending = st.pendingDepthTextures.remove(handle);
|
||||
final boolean stencilPending = st.pendingStencilTextures.remove(handle);
|
||||
if (depthPending || stencilPending) {
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
|
||||
emitDepthStencilClearPass(st, handle, depthPending, stencilPending, true);
|
||||
st.pendingMutationGen++;
|
||||
return;
|
||||
}
|
||||
if (st.pendingColorTextures.remove(handle)) {
|
||||
final float[] color = st.pendingColorValues.remove(handle);
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final SDL_GPUColorTargetInfo.Buffer targets = SDL_GPUColorTargetInfo.calloc(1, stack);
|
||||
final long addr = targets.get(0).address();
|
||||
@@ -275,7 +275,7 @@ public final class FBOClearTracker {
|
||||
frameManager.noteClearPass();
|
||||
frameManager.noteMaterializedClearPass();
|
||||
frameManager.beginRenderPass(targets, null);
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
resourceManager.markTextureContentDefined(handle);
|
||||
}
|
||||
|
||||
+2
-15
@@ -5,7 +5,6 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
@@ -80,7 +79,7 @@ public final class PersistentBufferSync {
|
||||
}
|
||||
|
||||
public static void mirrorPersistentCopy(ByteBuffer srcStaging, long srcOffset, ByteBuffer dstStaging, long dstOffset, long size) {
|
||||
copyByteRegion(srcStaging, (int) srcOffset, dstStaging, (int) dstOffset, (int) size);
|
||||
ByteRegionCopy.copyByteRegion(srcStaging, (int) srcOffset, dstStaging, (int) dstOffset, (int) size);
|
||||
}
|
||||
|
||||
public void mirrorEboShadow(int glId, ByteBuffer src, int dstOffset, int len) {
|
||||
@@ -93,21 +92,9 @@ public final class PersistentBufferSync {
|
||||
else growHint = shadow.capacity() * 2;
|
||||
shadow = resourceManager.getOrAllocEboShadow(glId, Math.max(requiredCap, growHint));
|
||||
}
|
||||
copyByteRegion(src, src.position(), shadow, dstOffset, len);
|
||||
ByteRegionCopy.copyByteRegion(src, src.position(), shadow, dstOffset, len);
|
||||
shadow.position(0);
|
||||
resourceManager.bumpEboShadowVersion(glId);
|
||||
resourceManager.invalidateSplitCacheFor(glId);
|
||||
}
|
||||
|
||||
private static void copyByteRegion(ByteBuffer src, int srcOff, ByteBuffer dst, int dstOff, int len) {
|
||||
if (src.isDirect() && dst.isDirect()) {
|
||||
MemoryUtil.memCopy(MemoryUtil.memAddress(src) + srcOff, MemoryUtil.memAddress(dst) + dstOff, len);
|
||||
return;
|
||||
}
|
||||
final ByteBuffer s = src.duplicate();
|
||||
s.position(srcOff).limit(srcOff + len);
|
||||
final ByteBuffer d = dst.duplicate();
|
||||
d.position(dstOff);
|
||||
d.put(s);
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ public final class TextureOps {
|
||||
|
||||
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstLevel, int dstX, int dstY, int dstW, int dstH, int glFilter, int flipMode) {
|
||||
frameManager.endCopyPassIfActive();
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COPY);
|
||||
final long cb = frameManager.getCommandBuffer();
|
||||
if (cb == 0) return;
|
||||
|
||||
@@ -243,7 +243,7 @@ public final class TextureOps {
|
||||
f.pendingMipGen.clear();
|
||||
|
||||
frameManager.endCopyPassIfActive();
|
||||
frameManager.endRenderPassIfActive();
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COPY);
|
||||
|
||||
for (int glId : glIds) {
|
||||
final long handle = resourceManager.getTextureHandle(glId);
|
||||
|
||||
@@ -83,6 +83,28 @@ public final class ShaderManager {
|
||||
private final Int2ObjectOpenHashMap<ShaderObject> shaderObjects = new Int2ObjectOpenHashMap<>();
|
||||
private final Int2ObjectOpenHashMap<ProgramObject> programObjects = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
private record VertexInputSetKey(int mask, int[] vecSize, int[] baseType) {
|
||||
@Override public boolean equals(Object o) {
|
||||
if (!(o instanceof VertexInputSetKey k)) return false;
|
||||
return mask == k.mask && Arrays.equals(vecSize, k.vecSize) && Arrays.equals(baseType, k.baseType);
|
||||
}
|
||||
@Override public int hashCode() {
|
||||
return (mask * 31 + Arrays.hashCode(vecSize)) * 31 + Arrays.hashCode(baseType);
|
||||
}
|
||||
}
|
||||
|
||||
private final HashMap<VertexInputSetKey, Integer> vertexInputSetIds = new HashMap<>();
|
||||
private int nextVertexInputSetId = 1;
|
||||
|
||||
private int internVertexInputSet(int mask, int[] vecSize, int[] baseType) {
|
||||
final VertexInputSetKey key = new VertexInputSetKey(mask, vecSize.clone(), baseType.clone());
|
||||
final Integer existing = vertexInputSetIds.get(key);
|
||||
if (existing != null) return existing;
|
||||
final int id = nextVertexInputSetId++;
|
||||
vertexInputSetIds.put(key, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
private record PrewarmKey(String source, int glShaderType) {}
|
||||
private record PrewarmEntry(byte[] remappedSpirv, StageReflection reflection, GraphicsBindingMap graphicsBindingMap, Set<String> boolUniforms, String transformedSource) {}
|
||||
private record PrewarmHit(ByteBuffer spirv, StageReflection reflection, GraphicsBindingMap graphicsBindingMap, Set<String> boolUniforms, String transformedSource) {}
|
||||
@@ -415,6 +437,7 @@ public final class ShaderManager {
|
||||
|
||||
applyVaryingMatch(prog, vs.reflection, fs.reflection);
|
||||
applyAttribLocationsAndInputMask(prog, vs.reflection);
|
||||
prog.vertexInputSetId = internVertexInputSet(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType);
|
||||
|
||||
prog.vertexResources = vs.reflection.counts();
|
||||
prog.fragmentResources = fs.reflection.counts();
|
||||
@@ -1244,13 +1267,19 @@ public final class ShaderManager {
|
||||
}
|
||||
|
||||
private static int collectResourceIds(long resources, long compiler, int resourceType, Int2IntOpenHashMap idToNewSet, Int2IntOpenHashMap idToNewBinding, int targetSet, int nextBinding, MemoryStack stack) {
|
||||
final int[] next = { nextBinding };
|
||||
forEachResource(resources, resourceType, stack, (j, res) -> {
|
||||
final int spvId = res.id();
|
||||
final PointerBuffer pList = stack.pointers(0);
|
||||
final PointerBuffer pCount = stack.pointers(0);
|
||||
if (Spvc.spvc_resources_get_resource_list_for_type(resources, resourceType, pList, pCount) != Spvc.SPVC_SUCCESS) return nextBinding;
|
||||
final int count = (int) pCount.get(0);
|
||||
if (count == 0) return nextBinding;
|
||||
final SpvcReflectedResource.Buffer list = SpvcReflectedResource.create(pList.get(0), count);
|
||||
int next = nextBinding;
|
||||
for (int j = 0; j < count; j++) {
|
||||
final int spvId = list.get(j).id();
|
||||
idToNewSet.put(spvId, targetSet);
|
||||
idToNewBinding.put(spvId, next[0]++);
|
||||
});
|
||||
return next[0];
|
||||
idToNewBinding.put(spvId, next++);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
private static ByteBuffer copyBuffer(ByteBuffer src) {
|
||||
@@ -1726,7 +1755,17 @@ public final class ShaderManager {
|
||||
public int[] fragmentSamplerUnits = EMPTY_UNITS;
|
||||
public boolean samplerUnitsDirty = true;
|
||||
|
||||
public static final byte LOCATION_KIND_PLAIN = 0;
|
||||
public static final byte LOCATION_KIND_SAMPLER = 1;
|
||||
public static final byte LOCATION_KIND_IMAGE = 2;
|
||||
|
||||
public byte[] locationKind = EMPTY_KIND;
|
||||
public String[] locationName = EMPTY_NAMES;
|
||||
public int[] locationSamplerUnit = EMPTY_UNITS;
|
||||
|
||||
private static final int[] EMPTY_UNITS = new int[0];
|
||||
private static final byte[] EMPTY_KIND = new byte[0];
|
||||
private static final String[] EMPTY_NAMES = new String[0];
|
||||
|
||||
private static Object2IntOpenHashMap<String> newLocMap() {
|
||||
final Object2IntOpenHashMap<String> m = new Object2IntOpenHashMap<>();
|
||||
@@ -1787,6 +1826,7 @@ public final class ShaderManager {
|
||||
public int geometryShader;
|
||||
public int nextUniformLocation = 0;
|
||||
public int vertexInputMask;
|
||||
public int vertexInputSetId;
|
||||
public int vertexShader;
|
||||
public int vertexUboSize;
|
||||
public long sdlComputePipeline;
|
||||
@@ -1801,6 +1841,21 @@ public final class ShaderManager {
|
||||
uniformSlotCount = n;
|
||||
vsInfoBySlot = new UniformMemberInfo[n];
|
||||
fsInfoBySlot = new UniformMemberInfo[n];
|
||||
locationKind = new byte[n];
|
||||
locationName = new String[n];
|
||||
locationSamplerUnit = new int[n];
|
||||
Arrays.fill(locationSamplerUnit, -1);
|
||||
for (int loc = 0; loc < n; loc++) {
|
||||
final String name = locationToName.get(loc);
|
||||
locationName[loc] = name;
|
||||
if (name != null && allSamplerNames.contains(name)) {
|
||||
locationKind[loc] = LOCATION_KIND_SAMPLER;
|
||||
} else if (name != null && allImageNames.contains(name)) {
|
||||
locationKind[loc] = LOCATION_KIND_IMAGE;
|
||||
} else {
|
||||
locationKind[loc] = LOCATION_KIND_PLAIN;
|
||||
}
|
||||
}
|
||||
for (int b = 0; b < BLOCK_COUNT; b++) {
|
||||
blockInfoBySlot[b] = new UniformMemberInfo[n];
|
||||
for (var it = blockMemberInfo[b].int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
#version 460 core
|
||||
void main() { }
|
||||
@@ -0,0 +1,2 @@
|
||||
#version 460 core
|
||||
void main() { vec2 p = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2); gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0); }
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class UboShadowSubDataTest {
|
||||
|
||||
private static SDLGPURenderBackend backend;
|
||||
private static ResourceManager resourceManager;
|
||||
|
||||
@BeforeAll
|
||||
static void createBackend() throws Exception {
|
||||
backend = new SDLGPURenderBackend();
|
||||
final Field f = SDLGPURenderBackend.class.getDeclaredField("resourceManager");
|
||||
f.setAccessible(true);
|
||||
resourceManager = (ResourceManager) f.get(backend);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void subDataFromANonZeroPositionLandsAtTheAbsoluteShadowOffset(boolean direct) {
|
||||
final int glId = backend.genBuffers();
|
||||
backend.bindBuffer(GL31.GL_UNIFORM_BUFFER, glId);
|
||||
backend.bufferData(GL31.GL_UNIFORM_BUFFER, 64, GL15.GL_DYNAMIC_DRAW);
|
||||
|
||||
final ByteBuffer payload = direct ? ByteBuffer.allocateDirect(32) : ByteBuffer.allocate(32);
|
||||
try {
|
||||
for (int i = 16; i < 24; i++) payload.put(i, (byte) (0x10 + (i - 16)));
|
||||
payload.position(16).limit(24);
|
||||
|
||||
backend.bufferSubData(GL31.GL_UNIFORM_BUFFER, 40, payload);
|
||||
|
||||
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
assertEquals((byte) (0x10 + i), shadow.get(40 + i), "byte " + i + " must land at the absolute shadow offset, not payload.position()+offset");
|
||||
}
|
||||
assertEquals(16, payload.position(), "bufferSubData must not move the caller's buffer position");
|
||||
assertEquals(24, payload.limit(), "bufferSubData must not move the caller's buffer limit");
|
||||
} finally {
|
||||
backend.bindBuffer(GL31.GL_UNIFORM_BUFFER, 0);
|
||||
backend.deleteBuffers(glId);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void eboSubDataCopiesPositionedSourcesExactlyOnce(boolean direct) {
|
||||
final int glId = backend.genBuffers();
|
||||
backend.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, glId);
|
||||
final ByteBuffer shadow = resourceManager.getOrAllocEboShadow(glId, 64);
|
||||
final ByteBuffer payload = direct ? ByteBuffer.allocateDirect(32) : ByteBuffer.allocate(32);
|
||||
try {
|
||||
for (int i = 0; i < 32; i++) payload.put(i, (byte) (i + 1));
|
||||
payload.position(8).limit(16);
|
||||
shadow.position(3);
|
||||
backend.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 24, payload);
|
||||
for (int i = 0; i < 8; i++) assertEquals((byte) (9 + i), shadow.get(24 + i));
|
||||
assertEquals(8, payload.position());
|
||||
assertEquals(16, payload.limit());
|
||||
} finally {
|
||||
backend.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
backend.deleteBuffers(glId);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondSubDataDoesNotDriftFromAStalePriorShadowPosition() {
|
||||
final int glId = backend.genBuffers();
|
||||
backend.bindBuffer(GL31.GL_UNIFORM_BUFFER, glId);
|
||||
backend.bufferData(GL31.GL_UNIFORM_BUFFER, 64, GL15.GL_DYNAMIC_DRAW);
|
||||
|
||||
final ByteBuffer a = MemoryUtil.memAlloc(8);
|
||||
final ByteBuffer b = MemoryUtil.memAlloc(8);
|
||||
try {
|
||||
for (int i = 0; i < 8; i++) a.put(i, (byte) 1);
|
||||
for (int i = 0; i < 8; i++) b.put(i, (byte) 2);
|
||||
|
||||
backend.bufferSubData(GL31.GL_UNIFORM_BUFFER, 0, a);
|
||||
backend.bufferSubData(GL31.GL_UNIFORM_BUFFER, 8, b);
|
||||
|
||||
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
|
||||
for (int i = 0; i < 8; i++) assertEquals((byte) 1, shadow.get(i));
|
||||
for (int i = 0; i < 8; i++) assertEquals((byte) 2, shadow.get(8 + i));
|
||||
} finally {
|
||||
MemoryUtil.memFree(a);
|
||||
MemoryUtil.memFree(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
+361
@@ -0,0 +1,361 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
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.opengl.GL11;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlAttachmentClearTest {
|
||||
|
||||
private static final int SIZE = GlsmSdlHeadlessRig.SIZE;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
}
|
||||
|
||||
private static FrameManager frameManager() {
|
||||
return Reflect.get(BackendManager.RENDER_BACKEND, "frameManager");
|
||||
}
|
||||
|
||||
private static FrameManager.FrameState frame() {
|
||||
return frameManager().frame();
|
||||
}
|
||||
|
||||
private static void setDisableInPassClear(boolean value) {
|
||||
try {
|
||||
final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
|
||||
theUnsafe.setAccessible(true);
|
||||
final Unsafe unsafe = (Unsafe) theUnsafe.get(null);
|
||||
final Field field = SystemProperties.class.getDeclaredField("SDL_DISABLE_IN_PASS_CLEAR");
|
||||
unsafe.putBoolean(unsafe.staticFieldBase(field), unsafe.staticFieldOffset(field), value);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawFullQuadAtZ(float z, float r, float g, float b) {
|
||||
GLStateManager.glColor4f(r, g, b, 1.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
GLStateManager.glVertex3f(-1.0f, -1.0f, z);
|
||||
GLStateManager.glVertex3f(1.0f, -1.0f, z);
|
||||
GLStateManager.glVertex3f(1.0f, 1.0f, z);
|
||||
GLStateManager.glVertex3f(-1.0f, 1.0f, z);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
private static boolean isRed(int argb) {
|
||||
final int r = (argb >> 16) & 0xFF;
|
||||
final int g = (argb >> 8) & 0xFF;
|
||||
return r > 200 && g < 64;
|
||||
}
|
||||
|
||||
private static boolean depthEquals(float depthValue) {
|
||||
GLStateManager.glEnable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDepthFunc(GL11.GL_EQUAL);
|
||||
GLStateManager.glDepthMask(false);
|
||||
drawFullQuadAtZ(depthValue * 2.0f - 1.0f, 1.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
return isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void depthMaskOffSuppressesTheClearAndBreaksNoPass() {
|
||||
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.1f, 0.1f, 0.1f);
|
||||
|
||||
final FrameManager.FrameState f = frame();
|
||||
final long passBefore = f.renderPass;
|
||||
assertTrue(passBefore != 0, "expected an active render pass before the masked clear");
|
||||
final int inPassBefore = f.inPassClearsThisFrame;
|
||||
final int passEndClearBefore = f.passEndCauseCountsThisFrame[FrameManager.PASS_END_CLEAR];
|
||||
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glClearDepth(0.0);
|
||||
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GLStateManager.glDepthMask(true);
|
||||
|
||||
assertEquals(passBefore, f.renderPass, "a masked clear must not break the active render pass");
|
||||
assertEquals(inPassBefore, f.inPassClearsThisFrame, "a masked clear must not take the in-pass path");
|
||||
assertEquals(passEndClearBefore, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_CLEAR], "a masked clear must not end the pass for a clear");
|
||||
assertTrue(depthEquals(0.9f), "a masked clear must leave the prior depth content untouched");
|
||||
}
|
||||
|
||||
@Test
|
||||
void inPassPathIsTakenForACompatibleActivePass() {
|
||||
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.1f, 0.1f, 0.1f);
|
||||
|
||||
final FrameManager.FrameState f = frame();
|
||||
final long passBefore = f.renderPass;
|
||||
assertTrue(passBefore != 0, "expected an active render pass before the clear");
|
||||
final int inPassBefore = f.inPassClearsThisFrame;
|
||||
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearDepth(0.25);
|
||||
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
assertEquals(passBefore, f.renderPass, "the in-pass clear must not break the active render pass");
|
||||
assertEquals(inPassBefore + 1, f.inPassClearsThisFrame, "the in-pass clear must be taken exactly once");
|
||||
assertTrue(depthEquals(0.25f), "the in-pass clear must have written the new depth value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void disableInPassClearFlagForcesTheLegacyPath() {
|
||||
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.1f, 0.1f, 0.1f);
|
||||
|
||||
final FrameManager.FrameState f = frame();
|
||||
assertTrue(f.renderPass != 0, "expected an active render pass before the clear");
|
||||
final int inPassBefore = f.inPassClearsThisFrame;
|
||||
|
||||
setDisableInPassClear(true);
|
||||
try {
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearDepth(0.6);
|
||||
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
assertEquals(inPassBefore, f.inPassClearsThisFrame, "angelica.sdlgpu.disableInPassClear must force the legacy path, never the in-pass path");
|
||||
} finally {
|
||||
setDisableInPassClear(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void inPassClearMatchesTheLegacyPathForVariousDepths() {
|
||||
for (final float depthValue : new float[]{ 0.0f, 0.25f, 0.5f, 1.0f }) {
|
||||
assertTrue(clearAndProbe(depthValue, false), "in-pass: depth=" + depthValue + " must read back exactly");
|
||||
assertTrue(clearAndProbe(depthValue, true), "legacy: depth=" + depthValue + " must read back exactly");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean clearAndProbe(float depthValue, boolean disableInPass) {
|
||||
setDisableInPassClear(disableInPass);
|
||||
try {
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
GlsmSdlHeadlessRig.resetState();
|
||||
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.1f, 0.1f, 0.1f);
|
||||
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearDepth(depthValue);
|
||||
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
return depthEquals(depthValue);
|
||||
} finally {
|
||||
setDisableInPassClear(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissoredClearOnlyAffectsTheScissorRect() {
|
||||
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.1f, 0.1f, 0.1f);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, SIZE / 2, SIZE);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearDepth(0.0);
|
||||
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDepthFunc(GL11.GL_EQUAL);
|
||||
GLStateManager.glDepthMask(false);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, SIZE / 2, SIZE);
|
||||
drawFullQuadAtZ(-1.0f, 1.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glScissor(SIZE / 2, 0, SIZE - SIZE / 2, SIZE);
|
||||
drawFullQuadAtZ(0.9f * 2.0f - 1.0f, 1.0f, 0.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 / 4, SIZE / 2)), "inside the scissor rect the depth must have been cleared to 0");
|
||||
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2 + SIZE / 4, SIZE / 2)), "outside the scissor rect the prior depth must be untouched");
|
||||
}
|
||||
|
||||
@Test
|
||||
void stencilWriteMaskIsHonored() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClearDepth(1.0);
|
||||
GLStateManager.glClearStencil(0xA5);
|
||||
GLStateManager.glStencilMask(0xFF);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.1f, 0.1f, 0.1f);
|
||||
|
||||
final FrameManager.FrameState f = frame();
|
||||
final long passBefore = f.renderPass;
|
||||
final int inPassBefore = f.inPassClearsThisFrame;
|
||||
|
||||
GLStateManager.glClearStencil(0x3F);
|
||||
GLStateManager.glStencilMask(0x0F);
|
||||
GLStateManager.glClear(GL11.GL_STENCIL_BUFFER_BIT);
|
||||
GLStateManager.glStencilMask(0xFF);
|
||||
|
||||
assertEquals(passBefore, f.renderPass, "the stencil-only clear must not break the active render pass");
|
||||
assertEquals(inPassBefore + 1, f.inPassClearsThisFrame, "the stencil clear must take the in-pass path");
|
||||
|
||||
final StencilProbeResult result = probeStencilReconcile();
|
||||
assertEquals(0xAF, result.bitPlaneValue(), "bit-plane read: only the low nibble should have been replaced: 0xA5 & ~0x0F | 0x3F & 0x0F == 0xAF");
|
||||
assertFalse(result.fullMask3F(), "GL_EQUAL(ref=0x3F, mask=0xFF) must not match: the write mask must have been honored, not ignored");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("stencil reference >= 0x80 compares as 0xFF on this backend; see stencil reference finding")
|
||||
void stencilReferenceAtOrAbove0x80ComparesExactly() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClearDepth(1.0);
|
||||
GLStateManager.glClearStencil(0xAF);
|
||||
GLStateManager.glStencilMask(0xFF);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.1f, 0.1f, 0.1f);
|
||||
|
||||
final StencilProbeResult result = probeStencilReconcile();
|
||||
assertEquals(0xAF, result.bitPlaneValue(), "bit-plane read of a plain 0xAF stencil clear");
|
||||
assertTrue(result.fullMaskAF(), "GL_EQUAL(ref=0xAF, mask=0xFF) must match a stored 0xAF");
|
||||
}
|
||||
|
||||
@Test
|
||||
void baselineStencilClearReadsBackExactly() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClearDepth(1.0);
|
||||
GLStateManager.glClearStencil(0xA5);
|
||||
GLStateManager.glStencilMask(0xFF);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.1f, 0.1f, 0.1f);
|
||||
|
||||
assertEquals(0xA5, readStencilByteSameFrame(), "a full-write-mask clear (the legacy load-op path) must read back exactly what was cleared");
|
||||
}
|
||||
|
||||
private static int readStencilByteSameFrame() {
|
||||
final int regionW = Math.max(1, SIZE / 8);
|
||||
for (int bit = 0; bit < 8; bit++) {
|
||||
probeStrip(bit, regionW, 8, GL11.GL_EQUAL, 1 << bit, 1 << bit);
|
||||
}
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
int value = 0;
|
||||
for (int bit = 0; bit < 8; bit++) {
|
||||
if (isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, stripCenter(bit, regionW, 8), SIZE / 2))) value |= (1 << bit);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private record StencilProbeResult(int bitPlaneValue, boolean fullMaskAF, boolean fullMask3F) {}
|
||||
|
||||
private static StencilProbeResult probeStencilReconcile() {
|
||||
final int[][] extra = {
|
||||
{ 0xAF, 0xFF }, { 0xAF, 0xFE }, { 0xAF, 0x7F }, { 0xAF, 0x80 }, { 0xAF, 0x0F },
|
||||
{ 0x2F, 0x7F }, { 0xAF, 0x70 }, { 0x20, 0x70 },
|
||||
};
|
||||
final int strips = 8 + extra.length + 1;
|
||||
final int regionW = SIZE / strips;
|
||||
for (int bit = 0; bit < 8; bit++) {
|
||||
probeStrip(bit, regionW, strips, GL11.GL_EQUAL, 1 << bit, 1 << bit);
|
||||
}
|
||||
for (int i = 0; i < extra.length; i++) {
|
||||
probeStrip(8 + i, regionW, strips, GL11.GL_EQUAL, extra[i][0], extra[i][1]);
|
||||
}
|
||||
probeStrip(8 + extra.length, regionW, strips, GL11.GL_EQUAL, 0x3F, 0xFF);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
int bitPlaneValue = 0;
|
||||
for (int bit = 0; bit < 8; bit++) {
|
||||
final boolean set = isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, stripCenter(bit, regionW, strips), SIZE / 2));
|
||||
if (set) bitPlaneValue |= (1 << bit);
|
||||
}
|
||||
final boolean fullMaskAF = isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, stripCenter(8, regionW, strips), SIZE / 2));
|
||||
final boolean fullMask3F = isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, stripCenter(8 + extra.length, regionW, strips), SIZE / 2));
|
||||
return new StencilProbeResult(bitPlaneValue, fullMaskAF, fullMask3F);
|
||||
}
|
||||
|
||||
private static void probeStrip(int index, int regionW, int strips, int func, int ref, int mask) {
|
||||
final int x = index * regionW;
|
||||
final int w = (index == strips - 1) ? SIZE - x : regionW;
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(x, 0, w, SIZE);
|
||||
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
|
||||
GLStateManager.glStencilFunc(func, ref, mask);
|
||||
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
|
||||
drawFullQuadAtZ(0.0f, 1.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
private static int stripCenter(int index, int regionW, int strips) {
|
||||
final int x = index * regionW;
|
||||
final int w = (index == strips - 1) ? SIZE - x : regionW;
|
||||
return x + w / 2;
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorIsUntouchedByADepthStencilOnlyClear() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearColor(0.2f, 0.4f, 0.6f, 1.0f);
|
||||
GLStateManager.glClearDepth(0.9);
|
||||
GLStateManager.glClearStencil(0x55);
|
||||
GLStateManager.glStencilMask(0xFF);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.2f, 0.4f, 0.6f);
|
||||
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearDepth(0.1);
|
||||
GLStateManager.glClearStencil(0x77);
|
||||
GLStateManager.glStencilMask(0xFF);
|
||||
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
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;
|
||||
assertTrue(Math.abs(r - 51) <= 2, "red must be untouched: " + r);
|
||||
assertTrue(Math.abs(g - 102) <= 2, "green must be untouched: " + g);
|
||||
assertTrue(Math.abs(b - 153) <= 2, "blue must be untouched: " + b);
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlBlendConstantsTest {
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void restoreBlend() {
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glBlendColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pendingConstantsSurviveADrawWithoutConstantBlending() {
|
||||
verifyConstants(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void constantsChangedWhileBlendingIsDisabledReachTheNextBlendedDraw() {
|
||||
verifyConstants(true);
|
||||
}
|
||||
|
||||
private static void verifyConstants(boolean previouslyEnabled) {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glBlendFunc(GL14.GL_CONSTANT_COLOR, GL11.GL_ZERO);
|
||||
if (previouslyEnabled) {
|
||||
GLStateManager.glEnable(GL11.GL_BLEND);
|
||||
GLStateManager.glBlendColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glBlendColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 1.0f, 1.0f);
|
||||
final FrameManager manager = Reflect.get(BackendManager.RENDER_BACKEND, "frameManager");
|
||||
final FrameManager.FrameState frame = manager.frame();
|
||||
final long pass = frame.renderPass;
|
||||
final long generation = frame.renderPassGeneration;
|
||||
assertNotEquals(0, pass);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_BLEND);
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 1.0f, 1.0f);
|
||||
assertEquals(pass, frame.renderPass);
|
||||
assertEquals(generation, frame.renderPassGeneration);
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
final int size = GlsmSdlHeadlessRig.SIZE;
|
||||
assertEquals(0xFF00FF00, GlsmSdlHeadlessRig.pixelAt(pixels, size, size / 2, size / 2));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -43,7 +43,7 @@ public final class GlsmSdlHeadlessRig {
|
||||
Reflect.setStatic(SDLGPUGate.class, "engaged", true);
|
||||
|
||||
final RenderBackend backend = BackendManager.RENDER_BACKEND;
|
||||
assumeTrue(backend instanceof SDLGPURenderBackend, () -> "selected backend is " + backend.getName());
|
||||
assertTrue(backend instanceof SDLGPURenderBackend, () -> "selected backend is " + backend.getName());
|
||||
|
||||
backend.onPostWindowCreate(0L);
|
||||
Reflect.setStaticFinal(GLStateManager.class, "MainThread", Thread.class, Thread.currentThread());
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
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;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
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 {
|
||||
|
||||
private static SdlTestRig rig;
|
||||
private static ShaderManager sm;
|
||||
private static PipelineStore store;
|
||||
private static AttachmentClear attachmentClear;
|
||||
|
||||
private static final int SIZE = 32;
|
||||
|
||||
private static long colorTexture;
|
||||
private static int colorFormat;
|
||||
private static long depthTexture;
|
||||
private static int depthFormat;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() throws Exception {
|
||||
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);
|
||||
|
||||
colorTexture = rig.resourceManager.createTexture(9700, GL11.GL_TEXTURE_2D, GL11.GL_RGBA8, SIZE, SIZE, 1, 1);
|
||||
assertTrue(colorTexture != 0, "color target creation failed");
|
||||
colorFormat = rig.resourceManager.getTextureMeta(9700).sdlFormat();
|
||||
PipelineCache.setSwapchainFormats(new int[]{ colorFormat });
|
||||
|
||||
rig.resourceManager.createTexture(9701, GL11.GL_TEXTURE_2D, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE, 1, 1);
|
||||
depthTexture = rig.resourceManager.ensureTextureUsage(9701, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET);
|
||||
assertTrue(depthTexture != 0, "depth+stencil target creation failed");
|
||||
depthFormat = rig.resourceManager.getTextureMeta(9701).sdlFormat();
|
||||
assertTrue(PixelOps.isDepthStencilFormat(depthFormat), "test needs a packed depth+stencil format");
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
SdlTestRig.releaseRealDevice();
|
||||
}
|
||||
|
||||
private static FboState makeFbo() {
|
||||
final FboState fbo = new FboState();
|
||||
fbo.colorTextures[0] = colorTexture;
|
||||
fbo.colorFormats[0] = colorFormat;
|
||||
fbo.colorAttachmentCount = 1;
|
||||
fbo.drawBuffers = new int[]{ 0 };
|
||||
fbo.cachedColorFormats = new int[]{ colorFormat };
|
||||
fbo.depthTexture = depthTexture;
|
||||
fbo.depthFormat = depthFormat;
|
||||
fbo.width = SIZE;
|
||||
fbo.height = SIZE;
|
||||
fbo.recomputeTargets();
|
||||
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);
|
||||
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);
|
||||
assertNotEquals(p1, p2, "a different clear mode must build a different pipeline");
|
||||
|
||||
final long p3 = attachmentClear.getOrCreatePipeline(fbo, false, true, 0x0F);
|
||||
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);
|
||||
assertTrue(p4 != 0, "a depth-only layout pipeline must still build");
|
||||
assertNotEquals(p1, p4, "a different color-target layout must build a different pipeline");
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
|
||||
class BlendConstantsGuardTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
PipelineCache.setSwapchainFormats(new int[]{SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM});
|
||||
}
|
||||
|
||||
private static PipelineCache createCache() {
|
||||
return new PipelineCache();
|
||||
}
|
||||
|
||||
@Test
|
||||
void freshCacheDoesNotUseBlendConstants() {
|
||||
final PipelineCache pc = createCache();
|
||||
assertFalse(pc.usesBlendConstants());
|
||||
}
|
||||
|
||||
@Test
|
||||
void constantColorFactorWithBlendDisabledDoesNotUseBlendConstants() {
|
||||
final PipelineCache pc = createCache();
|
||||
pc.setBlendFactors(SDL_GPU_BLENDFACTOR_CONSTANT_COLOR, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
|
||||
assertFalse(pc.usesBlendConstants(), "blend is disabled on every draw buffer; the constant is never sampled");
|
||||
}
|
||||
|
||||
@Test
|
||||
void constantColorFactorWithBlendEnabledUsesBlendConstants() {
|
||||
final PipelineCache pc = createCache();
|
||||
pc.blendEnabledPerDrawBuffer[0] = true;
|
||||
pc.setBlendFactors(SDL_GPU_BLENDFACTOR_CONSTANT_COLOR, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
|
||||
assertTrue(pc.usesBlendConstants());
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneMinusConstantColorFactorAlsoUsesBlendConstants() {
|
||||
final PipelineCache pc = createCache();
|
||||
pc.blendEnabledPerDrawBuffer[0] = true;
|
||||
pc.setBlendFactors(SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ONE_MINUS_CONSTANT_COLOR, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
|
||||
assertTrue(pc.usesBlendConstants());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonConstantFactorsWithBlendEnabledDoNotUseBlendConstants() {
|
||||
final PipelineCache pc = createCache();
|
||||
pc.blendEnabledPerDrawBuffer[0] = true;
|
||||
pc.setBlendFactors(SDL_GPU_BLENDFACTOR_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
|
||||
assertFalse(pc.usesBlendConstants());
|
||||
}
|
||||
|
||||
@Test
|
||||
void disablingBlendAfterConstantColorClearsUsesBlendConstants() {
|
||||
final PipelineCache pc = createCache();
|
||||
pc.blendEnabledPerDrawBuffer[0] = true;
|
||||
pc.setBlendFactors(SDL_GPU_BLENDFACTOR_CONSTANT_COLOR, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
|
||||
assertTrue(pc.usesBlendConstants());
|
||||
|
||||
pc.blendEnabledPerDrawBuffer[0] = false;
|
||||
pc.markOutputDirty();
|
||||
assertFalse(pc.usesBlendConstants());
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondDrawBufferAloneCanTriggerBlendConstants() {
|
||||
final PipelineCache pc = createCache();
|
||||
pc.blendEnabledPerDrawBuffer[1] = true;
|
||||
pc.setBlendFactors(1, SDL_GPU_BLENDFACTOR_CONSTANT_COLOR, SDL_GPU_BLENDFACTOR_ZERO, SDL_GPU_BLENDFACTOR_ONE, SDL_GPU_BLENDFACTOR_ZERO);
|
||||
assertTrue(pc.usesBlendConstants(), "the loop must scan every draw buffer slot, not only slot 0");
|
||||
}
|
||||
}
|
||||
+30
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager.ProgramObject;
|
||||
@@ -7,6 +8,7 @@ import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager.UniformMemberInfo;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.util.spvc.Spvc;
|
||||
|
||||
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;
|
||||
@@ -93,4 +95,32 @@ class PerFrameBlockDedupTest {
|
||||
|
||||
assertNotEquals(first, second, "the new value must be in the staging buffer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void flushUniformBlocksEndsTheCopyPassOnceForTwoDirtyBlocks() throws Exception {
|
||||
final SdlTestRig rig = SdlTestRig.acquireRealDevice();
|
||||
try {
|
||||
rig.frameManager.beginFrame();
|
||||
rig.frameManager.frame().swapchainUnavailable = true;
|
||||
|
||||
final int[] endCount = { 0 };
|
||||
rig.frameManager.setBeforeEndCopyPassCallback(() -> endCount[0]++);
|
||||
|
||||
final PipelineApplier realApplier = new PipelineApplier(rig.frameManager, rig.resourceManager, null, null, null, null, null, null, null);
|
||||
final ContextState cs = new ContextState();
|
||||
cs.boundProgramObj = programWithBlockMemberAt(16, ShaderManager.BLOCK_PER_FRAME);
|
||||
realApplier.putUniform(cs, 0, new float[]{0.5f});
|
||||
cs.boundProgramObj = programWithBlockMemberAt(16, ShaderManager.BLOCK_PER_PASS);
|
||||
realApplier.putUniform(cs, 0, new float[]{0.5f});
|
||||
assertTrue(cs.uniformBlocks[ShaderManager.BLOCK_PER_FRAME].dirty, "setup: per-frame block must be dirty");
|
||||
assertTrue(cs.uniformBlocks[ShaderManager.BLOCK_PER_PASS].dirty, "setup: per-pass block must be dirty");
|
||||
|
||||
realApplier.flushUniformBlocks(cs);
|
||||
|
||||
assertEquals(1, endCount[0], "two dirty blocks must share one copy-pass transition, not one each");
|
||||
} finally {
|
||||
rig.frameManager.endFrame();
|
||||
SdlTestRig.releaseRealDevice();
|
||||
}
|
||||
}
|
||||
}
|
||||
+371
-16
@@ -10,6 +10,8 @@ import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.util.spvc.Spvc;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
@@ -34,15 +36,35 @@ class PipelineCacheVertexInputTest {
|
||||
store = new PipelineStore(null);
|
||||
}
|
||||
|
||||
private static int nextTestSetId = 1;
|
||||
|
||||
private static PipelineCache cache(int shaderInputMask, int[] vecSizes, int[] baseTypes) {
|
||||
return cache(nextTestSetId++, shaderInputMask, vecSizes, baseTypes);
|
||||
}
|
||||
|
||||
private static PipelineCache cache(int vertexInputSetId, int shaderInputMask, int[] vecSizes, int[] baseTypes) {
|
||||
final PipelineCache c = new PipelineCache();
|
||||
c.maxAttribs = ContextState.MAX_VERTEX_ATTRIBS;
|
||||
c.shaderInputMask = shaderInputMask;
|
||||
c.shaderInputVecSize = vecSizes;
|
||||
c.shaderInputBaseType = baseTypes;
|
||||
c.setMaxAttribs(ContextState.MAX_VERTEX_ATTRIBS);
|
||||
c.setVertexInputs(shaderInputMask, vecSizes, baseTypes, new String[ContextState.MAX_VERTEX_ATTRIBS], vertexInputSetId);
|
||||
return c;
|
||||
}
|
||||
|
||||
private static void copyVaoState(ContextState.VAOState src, ContextState.VAOState dst) {
|
||||
System.arraycopy(src.attribEnabled, 0, dst.attribEnabled, 0, src.attribEnabled.length);
|
||||
dst.attribEnabledMask = src.attribEnabledMask;
|
||||
System.arraycopy(src.attribSize, 0, dst.attribSize, 0, src.attribSize.length);
|
||||
System.arraycopy(src.attribType, 0, dst.attribType, 0, src.attribType.length);
|
||||
System.arraycopy(src.attribNormalized, 0, dst.attribNormalized, 0, src.attribNormalized.length);
|
||||
System.arraycopy(src.attribIsInteger, 0, dst.attribIsInteger, 0, src.attribIsInteger.length);
|
||||
System.arraycopy(src.attribStride, 0, dst.attribStride, 0, src.attribStride.length);
|
||||
System.arraycopy(src.attribBinding, 0, dst.attribBinding, 0, src.attribBinding.length);
|
||||
System.arraycopy(src.attribRelativeOffset, 0, dst.attribRelativeOffset, 0, src.attribRelativeOffset.length);
|
||||
System.arraycopy(src.bindingBuffer, 0, dst.bindingBuffer, 0, src.bindingBuffer.length);
|
||||
System.arraycopy(src.bindingOffset, 0, dst.bindingOffset, 0, src.bindingOffset.length);
|
||||
System.arraycopy(src.bindingStride, 0, dst.bindingStride, 0, src.bindingStride.length);
|
||||
System.arraycopy(src.bindingDivisor, 0, dst.bindingDivisor, 0, src.bindingDivisor.length);
|
||||
}
|
||||
|
||||
private static void enableFloatAttrib(ContextState cs, int loc, int size, int stride) {
|
||||
final ContextState.VAOState vao = cs.currentVao;
|
||||
vao.attribEnabled[loc] = true;
|
||||
@@ -440,6 +462,7 @@ class PipelineCacheVertexInputTest {
|
||||
store.setBufferHandleResolver(id -> 0xBEEFL); // both live
|
||||
final long liveKey = c.computeKey(store, cs);
|
||||
store.setBufferHandleResolver(id -> id == 42 ? 0xBEEFL : 0L); // loc 1's buffer dead
|
||||
store.bumpLivenessGen();
|
||||
final long deadKey = c.computeKey(store, cs);
|
||||
|
||||
assertTrue(liveKey != deadKey, "demotion must produce a distinct pipeline key or a cached per-vertex pipeline would be reused for the constant layout");
|
||||
@@ -456,15 +479,14 @@ class PipelineCacheVertexInputTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void programInputSignatureChangeInvalidatesInputHash() {
|
||||
void vertexInputSetIdChangeInvalidatesInputHash() {
|
||||
final int[] vecSizesA = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizesA[0] = 3;
|
||||
final int[] baseTypesA = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
baseTypesA[0] = Spvc.SPVC_BASETYPE_FP32;
|
||||
final String[] namesA = new String[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
namesA[0] = "a_Pos";
|
||||
final PipelineCache c = cache(0x1, vecSizesA, baseTypesA);
|
||||
c.shaderInputName = namesA;
|
||||
final PipelineCache c = cache(1, 0x1, vecSizesA, baseTypesA);
|
||||
|
||||
final ContextState cs = new ContextState();
|
||||
enableFloatAttrib(cs, 0, 3, 12);
|
||||
@@ -473,13 +495,13 @@ class PipelineCacheVertexInputTest {
|
||||
final boolean dirtyAfterKeyA = Reflect.get(c, "inputDirty");
|
||||
assertFalse(dirtyAfterKeyA, "currentKey must clear the dirty flag once recomputed");
|
||||
|
||||
c.setVertexInputs(0x1, vecSizesA, baseTypesA, namesA);
|
||||
final boolean dirtyAfterSameSignature = Reflect.get(c, "inputDirty");
|
||||
assertFalse(dirtyAfterSameSignature, "an identical signature must not force a rehash");
|
||||
c.setVertexInputs(0x1, vecSizesA, baseTypesA, namesA, 1);
|
||||
final boolean dirtyAfterSameId = Reflect.get(c, "inputDirty");
|
||||
assertFalse(dirtyAfterSameId, "an identical input-set id must not force a rehash");
|
||||
|
||||
c.setVertexInputs(0x1, vecSizesA.clone(), baseTypesA.clone(), namesA.clone());
|
||||
final boolean dirtyAfterEqualCopies = Reflect.get(c, "inputDirty");
|
||||
assertFalse(dirtyAfterEqualCopies, "another program with an equal signature must not force a rehash");
|
||||
c.setVertexInputs(0x1, vecSizesA.clone(), baseTypesA.clone(), namesA.clone(), 1);
|
||||
final boolean dirtyAfterSameIdDistinctArrays = Reflect.get(c, "inputDirty");
|
||||
assertFalse(dirtyAfterSameIdDistinctArrays, "the same interned id must not force a rehash even with distinct array instances");
|
||||
|
||||
final int[] vecSizesB = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizesB[0] = 3; vecSizesB[1] = 4;
|
||||
@@ -488,9 +510,9 @@ class PipelineCacheVertexInputTest {
|
||||
final String[] namesB = new String[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
namesB[0] = "a_Pos"; namesB[1] = "a_Extra";
|
||||
|
||||
c.setVertexInputs(0x3, vecSizesB, baseTypesB, namesB);
|
||||
final boolean dirtyAfterChangedSignature = Reflect.get(c, "inputDirty");
|
||||
assertTrue(dirtyAfterChangedSignature, "a changed vertex-input signature must invalidate the cached input hash");
|
||||
c.setVertexInputs(0x3, vecSizesB, baseTypesB, namesB, 2);
|
||||
final boolean dirtyAfterChangedId = Reflect.get(c, "inputDirty");
|
||||
assertTrue(dirtyAfterChangedId, "a changed input-set id must invalidate the cached input hash");
|
||||
|
||||
final long keyB = Reflect.invoke(c, "currentKey", new Class<?>[] { PipelineStore.class, ContextState.class }, store, cs);
|
||||
assertTrue(keyA != keyB, "currentKey must reflect the new signature, not a stale cached input hash");
|
||||
@@ -518,4 +540,337 @@ class PipelineCacheVertexInputTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void offsetOnlyPointerChange_keepsKeyAndSkipsRecompute() {
|
||||
final int[] vecSizes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizes[0] = 3;
|
||||
final int[] baseTypes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
baseTypes[0] = Spvc.SPVC_BASETYPE_FP32;
|
||||
final PipelineCache c = cache(0x1, vecSizes, baseTypes);
|
||||
|
||||
final ContextState cs = new ContextState();
|
||||
final VertexAttribs attribs = new VertexAttribs();
|
||||
attribs.applyVertexAttribPointer(store, cs, 0, 3, GL11.GL_FLOAT, false, false, 12, 100L);
|
||||
|
||||
final long keyA = c.computeKey(store, cs);
|
||||
final int runsAfterA = c.inputHashLoopRuns;
|
||||
|
||||
attribs.applyVertexAttribPointer(store, cs, 0, 3, GL11.GL_FLOAT, false, false, 12, 104L);
|
||||
final long keyB = c.computeKey(store, cs);
|
||||
|
||||
assertEquals(keyA, keyB, "an offset change that keeps the alignment class must not change the pipeline key");
|
||||
assertEquals(runsAfterA, c.inputHashLoopRuns, "an offset-only change must not force the input-hash loop to rerun");
|
||||
}
|
||||
|
||||
@Test
|
||||
void identicalReissuedPointer_isNoOp() {
|
||||
final int[] vecSizes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizes[0] = 3;
|
||||
final int[] baseTypes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
baseTypes[0] = Spvc.SPVC_BASETYPE_FP32;
|
||||
final PipelineCache c = cache(0x1, vecSizes, baseTypes);
|
||||
|
||||
final ContextState cs = new ContextState();
|
||||
final VertexAttribs attribs = new VertexAttribs();
|
||||
attribs.applyVertexAttribPointer(store, cs, 0, 3, GL11.GL_FLOAT, false, false, 12, 100L);
|
||||
|
||||
final long keyA = c.computeKey(store, cs);
|
||||
final int runsAfterA = c.inputHashLoopRuns;
|
||||
|
||||
attribs.applyVertexAttribPointer(store, cs, 0, 3, GL11.GL_FLOAT, false, false, 12, 100L);
|
||||
final long keyB = c.computeKey(store, cs);
|
||||
|
||||
assertEquals(keyA, keyB, "an identical re-issued pointer must not change the key");
|
||||
assertEquals(runsAfterA, c.inputHashLoopRuns, "an identical re-issued pointer must be a no-op and not force a rehash");
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoEqualLayoutVaosShareKey() {
|
||||
final int[] vecSizes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizes[0] = 3; vecSizes[1] = 4;
|
||||
final int[] baseTypes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
baseTypes[0] = Spvc.SPVC_BASETYPE_FP32; baseTypes[1] = Spvc.SPVC_BASETYPE_FP32;
|
||||
final PipelineCache c = cache(0x3, vecSizes, baseTypes);
|
||||
|
||||
final ContextState csA = new ContextState();
|
||||
enableFloatAttrib(csA, 0, 3, 28);
|
||||
enableFloatAttrib(csA, 1, 4, 28);
|
||||
|
||||
final ContextState csB = new ContextState();
|
||||
enableFloatAttrib(csB, 0, 3, 28);
|
||||
enableFloatAttrib(csB, 1, 4, 28);
|
||||
|
||||
assertEquals(c.computeKey(store, csA), c.computeKey(store, csB), "two VAOs with identical layouts must hash to the same input key");
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyLayoutFieldChangesKey() {
|
||||
final int[] vecSizes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizes[0] = 3;
|
||||
final int[] baseTypes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
baseTypes[0] = Spvc.SPVC_BASETYPE_FP32;
|
||||
final PipelineCache c = cache(0x1, vecSizes, baseTypes);
|
||||
|
||||
final ContextState base = new ContextState();
|
||||
enableFloatAttrib(base, 0, 3, 16);
|
||||
final long baseKey = c.computeKey(store, base);
|
||||
|
||||
final ContextState sizeVariant = new ContextState();
|
||||
enableFloatAttrib(sizeVariant, 0, 4, 16);
|
||||
assertTrue(baseKey != c.computeKey(store, sizeVariant), "size must affect the input key");
|
||||
|
||||
final ContextState typeVariant = new ContextState();
|
||||
enableAttrib(typeVariant, 0, 3, GL11.GL_UNSIGNED_BYTE, false, false, 16);
|
||||
assertTrue(baseKey != c.computeKey(store, typeVariant), "type must affect the input key");
|
||||
|
||||
final ContextState normalizedVariant = new ContextState();
|
||||
enableAttrib(normalizedVariant, 0, 3, GL11.GL_FLOAT, true, false, 16);
|
||||
assertTrue(baseKey != c.computeKey(store, normalizedVariant), "normalized must affect the input key");
|
||||
|
||||
final ContextState integerVariant = new ContextState();
|
||||
enableAttrib(integerVariant, 0, 3, GL11.GL_FLOAT, false, true, 16);
|
||||
assertTrue(baseKey != c.computeKey(store, integerVariant), "isInteger must affect the input key");
|
||||
|
||||
final ContextState strideVariant = new ContextState();
|
||||
enableFloatAttrib(strideVariant, 0, 3, 20);
|
||||
assertTrue(baseKey != c.computeKey(store, strideVariant), "stride must affect the input key");
|
||||
|
||||
final ContextState bindingStrideVariant = new ContextState();
|
||||
enableFloatAttrib(bindingStrideVariant, 0, 3, 16);
|
||||
bindingStrideVariant.currentVao.bindingStride[0] = 20;
|
||||
assertTrue(baseKey != c.computeKey(store, bindingStrideVariant), "binding stride must affect the input key");
|
||||
|
||||
final ContextState divisorVariant = new ContextState();
|
||||
enableFloatAttrib(divisorVariant, 0, 3, 16);
|
||||
divisorVariant.currentVao.bindingDivisor[0] = 1;
|
||||
assertTrue(baseKey != c.computeKey(store, divisorVariant), "binding divisor must affect the input key");
|
||||
|
||||
final ContextState relativeOffsetVariant = new ContextState();
|
||||
enableFloatAttrib(relativeOffsetVariant, 0, 3, 16);
|
||||
relativeOffsetVariant.currentVao.attribRelativeOffset[0] = 4;
|
||||
assertTrue(baseKey != c.computeKey(store, relativeOffsetVariant), "relative offset must affect the input key");
|
||||
|
||||
final ContextState bindingVariant = new ContextState();
|
||||
enableFloatAttrib(bindingVariant, 0, 3, 16);
|
||||
bindingVariant.currentVao.attribBinding[0] = 1;
|
||||
assertTrue(baseKey != c.computeKey(store, bindingVariant), "binding index must affect the input key");
|
||||
|
||||
final ContextState misalignVariant = new ContextState();
|
||||
enableFloatAttrib(misalignVariant, 0, 3, 16);
|
||||
misalignVariant.currentVao.bindingOffset[0] = 1;
|
||||
assertTrue(baseKey != c.computeKey(store, misalignVariant), "binding offset misalignment must affect the input key");
|
||||
|
||||
final ContextState disabledVariant = new ContextState();
|
||||
assertTrue(baseKey != c.computeKey(store, disabledVariant), "enabled state must affect the input key");
|
||||
}
|
||||
|
||||
@Test
|
||||
void livenessGenBumpForcesRecompute() {
|
||||
final int[] vecSizes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizes[0] = 3;
|
||||
final int[] baseTypes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
baseTypes[0] = Spvc.SPVC_BASETYPE_FP32;
|
||||
final PipelineCache c = cache(0x1, vecSizes, baseTypes);
|
||||
|
||||
final ContextState cs = new ContextState();
|
||||
enableFloatAttrib(cs, 0, 3, 12);
|
||||
|
||||
c.computeKey(store, cs);
|
||||
final int runsAfterFirst = c.inputHashLoopRuns;
|
||||
|
||||
c.computeKey(store, cs);
|
||||
assertEquals(runsAfterFirst, c.inputHashLoopRuns, "an unchanged VAO must hit the cached input hash");
|
||||
|
||||
store.bumpLivenessGen();
|
||||
c.computeKey(store, cs);
|
||||
assertEquals(runsAfterFirst + 1, c.inputHashLoopRuns, "a liveness generation bump must force the input-hash loop to rerun");
|
||||
}
|
||||
|
||||
@Test
|
||||
void alternatingTwoInputSetsOnOneVao_loopRunsExactlyTwice() {
|
||||
final int[] vecSizesA = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizesA[0] = 3;
|
||||
final int[] baseTypesA = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
baseTypesA[0] = Spvc.SPVC_BASETYPE_FP32;
|
||||
final int[] vecSizesB = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizesB[0] = 3; vecSizesB[1] = 4;
|
||||
final int[] baseTypesB = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
baseTypesB[0] = Spvc.SPVC_BASETYPE_FP32; baseTypesB[1] = Spvc.SPVC_BASETYPE_FP32;
|
||||
final String[] names = new String[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
|
||||
final PipelineCache c = new PipelineCache();
|
||||
c.setMaxAttribs(ContextState.MAX_VERTEX_ATTRIBS);
|
||||
|
||||
final ContextState cs = new ContextState();
|
||||
enableFloatAttrib(cs, 0, 3, 12);
|
||||
enableFloatAttrib(cs, 1, 4, 16);
|
||||
|
||||
c.setVertexInputs(0x1, vecSizesA, baseTypesA, names, 1);
|
||||
c.computeKey(store, cs);
|
||||
c.setVertexInputs(0x3, vecSizesB, baseTypesB, names, 2);
|
||||
c.computeKey(store, cs);
|
||||
c.setVertexInputs(0x1, vecSizesA, baseTypesA, names, 1);
|
||||
c.computeKey(store, cs);
|
||||
c.setVertexInputs(0x3, vecSizesB, baseTypesB, names, 2);
|
||||
c.computeKey(store, cs);
|
||||
|
||||
assertEquals(2, c.inputHashLoopRuns, "alternating between two known input sets on one VAO must only recompute twice, then hit the cached slots");
|
||||
}
|
||||
|
||||
@Test
|
||||
void togglingOneAttribEnable_loopRunsExactlyTwice() {
|
||||
final int[] vecSizes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizes[0] = 3; vecSizes[1] = 4;
|
||||
final int[] baseTypes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
baseTypes[0] = Spvc.SPVC_BASETYPE_FP32; baseTypes[1] = Spvc.SPVC_BASETYPE_FP32;
|
||||
final PipelineCache c = cache(1, 0x3, vecSizes, baseTypes);
|
||||
|
||||
final ContextState cs = new ContextState();
|
||||
enableFloatAttrib(cs, 0, 3, 12);
|
||||
enableFloatAttrib(cs, 1, 4, 16);
|
||||
final ContextState.VAOState vao = cs.currentVao;
|
||||
|
||||
vao.attribEnabled[1] = false;
|
||||
vao.attribEnabledMask &= ~(1 << 1);
|
||||
c.computeKey(store, cs);
|
||||
|
||||
vao.attribEnabled[1] = true;
|
||||
vao.attribEnabledMask |= (1 << 1);
|
||||
c.computeKey(store, cs);
|
||||
|
||||
vao.attribEnabled[1] = false;
|
||||
vao.attribEnabledMask &= ~(1 << 1);
|
||||
c.computeKey(store, cs);
|
||||
|
||||
vao.attribEnabled[1] = true;
|
||||
vao.attribEnabledMask |= (1 << 1);
|
||||
c.computeKey(store, cs);
|
||||
|
||||
assertEquals(2, c.inputHashLoopRuns, "toggling one attrib's enabled state between two known masks must only recompute twice, then hit the cached slots");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nineDistinctInputSets_correctKeysAfterEviction() {
|
||||
final ContextState cs = new ContextState();
|
||||
enableFloatAttrib(cs, 0, 3, 12);
|
||||
final PipelineCache c = new PipelineCache();
|
||||
c.setMaxAttribs(ContextState.MAX_VERTEX_ATTRIBS);
|
||||
final int[] vecSizes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
final int[] baseTypes = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
final String[] names = new String[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
|
||||
for (int mask = 1; mask <= 9; mask++) {
|
||||
c.setVertexInputs(mask, vecSizes, baseTypes, names, mask);
|
||||
c.computeKey(store, cs);
|
||||
}
|
||||
|
||||
for (int mask = 1; mask <= 9; mask++) {
|
||||
c.setVertexInputs(mask, vecSizes, baseTypes, names, mask);
|
||||
final long recomputed = c.computeKey(store, cs);
|
||||
|
||||
final PipelineCache fresh = new PipelineCache();
|
||||
fresh.setMaxAttribs(ContextState.MAX_VERTEX_ATTRIBS);
|
||||
fresh.setVertexInputs(mask, vecSizes, baseTypes, names, mask);
|
||||
final ContextState freshCs = new ContextState();
|
||||
enableFloatAttrib(freshCs, 0, 3, 12);
|
||||
final long reference = fresh.computeKey(store, freshCs);
|
||||
|
||||
assertEquals(reference, recomputed, "key for input set mask=" + mask + " must match a fresh, uncached computation even after 8-slot eviction");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void internVertexInputSet_equalSetsShareId_differentSetsDiffer() {
|
||||
final ShaderManager sm = new ShaderManager(null);
|
||||
final int[] vecSizesA = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizesA[0] = 3;
|
||||
final int[] baseTypesA = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
baseTypesA[0] = Spvc.SPVC_BASETYPE_FP32;
|
||||
|
||||
final int idA1 = Reflect.invoke(sm, "internVertexInputSet", new Class<?>[] { int.class, int[].class, int[].class }, 0x1, vecSizesA, baseTypesA);
|
||||
final int idA2 = Reflect.invoke(sm, "internVertexInputSet", new Class<?>[] { int.class, int[].class, int[].class }, 0x1, vecSizesA.clone(), baseTypesA.clone());
|
||||
assertEquals(idA1, idA2, "two equal input sets, even from distinct array instances, must intern to the same id");
|
||||
|
||||
final int[] vecSizesB = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
vecSizesB[0] = 3; vecSizesB[1] = 4;
|
||||
final int[] baseTypesB = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
baseTypesB[0] = Spvc.SPVC_BASETYPE_FP32; baseTypesB[1] = Spvc.SPVC_BASETYPE_FP32;
|
||||
final int idB = Reflect.invoke(sm, "internVertexInputSet", new Class<?>[] { int.class, int[].class, int[].class }, 0x3, vecSizesB, baseTypesB);
|
||||
assertTrue(idA1 != idB, "different input sets must never share an id");
|
||||
assertTrue(idA1 >= 1 && idB >= 1, "interned ids start at 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void randomizedCachedKeyMatchesFreshComputation() {
|
||||
final Random rnd = new Random(12345L);
|
||||
|
||||
final int programCount = 4;
|
||||
final int[] progMasks = { 0x1, 0x3, 0x5, 0x7 };
|
||||
final int[][] progVecSizes = new int[programCount][];
|
||||
final int[][] progBaseTypes = new int[programCount][];
|
||||
for (int p = 0; p < programCount; p++) {
|
||||
progVecSizes[p] = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
progBaseTypes[p] = new int[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
for (int i = 0; i < 3; i++) {
|
||||
progVecSizes[p][i] = 1 + ((p + i) % 4);
|
||||
progBaseTypes[p][i] = (p + i) % 3;
|
||||
}
|
||||
}
|
||||
final String[] names = new String[ContextState.MAX_VERTEX_ATTRIBS];
|
||||
|
||||
final PipelineCache cachedC = new PipelineCache();
|
||||
cachedC.setMaxAttribs(ContextState.MAX_VERTEX_ATTRIBS);
|
||||
final ContextState cachedCs = new ContextState();
|
||||
int currentProgram = 0;
|
||||
cachedC.setVertexInputs(progMasks[0], progVecSizes[0], progBaseTypes[0], names, 1);
|
||||
|
||||
for (int step = 0; step < 300; step++) {
|
||||
final ContextState.VAOState vao = cachedCs.currentVao;
|
||||
final int action = rnd.nextInt(5);
|
||||
switch (action) {
|
||||
case 0 -> {
|
||||
final int idx = rnd.nextInt(4);
|
||||
if (vao.attribEnabled[idx]) {
|
||||
vao.attribEnabled[idx] = false;
|
||||
vao.attribEnabledMask &= ~(1 << idx);
|
||||
} else {
|
||||
vao.attribEnabled[idx] = true;
|
||||
vao.attribEnabledMask |= (1 << idx);
|
||||
if (vao.attribType[idx] == 0) vao.attribType[idx] = GL11.GL_FLOAT;
|
||||
}
|
||||
}
|
||||
case 1 -> {
|
||||
final int idx = rnd.nextInt(4);
|
||||
vao.attribSize[idx] = 1 + rnd.nextInt(4);
|
||||
vao.attribType[idx] = GL11.GL_FLOAT;
|
||||
vao.attribStride[idx] = 4 + rnd.nextInt(28);
|
||||
vao.bindingStride[vao.attribBinding[idx]] = vao.attribStride[idx];
|
||||
vao.invalidateInputHash();
|
||||
}
|
||||
case 2 -> {
|
||||
final int idx = rnd.nextInt(4);
|
||||
vao.bindingDivisor[vao.attribBinding[idx]] = rnd.nextInt(2);
|
||||
vao.invalidateInputHash();
|
||||
}
|
||||
case 3 -> {
|
||||
currentProgram = rnd.nextInt(programCount);
|
||||
cachedC.setVertexInputs(progMasks[currentProgram], progVecSizes[currentProgram], progBaseTypes[currentProgram], names, currentProgram + 1);
|
||||
}
|
||||
case 4 -> store.bumpLivenessGen();
|
||||
default -> throw new AssertionError("unreachable");
|
||||
}
|
||||
|
||||
final long cachedKey = cachedC.computeKey(store, cachedCs);
|
||||
|
||||
final PipelineCache freshC = new PipelineCache();
|
||||
freshC.setMaxAttribs(cachedC.maxAttribs());
|
||||
freshC.setVertexInputs(progMasks[currentProgram], progVecSizes[currentProgram], progBaseTypes[currentProgram], names, currentProgram + 1);
|
||||
final ContextState freshCs = new ContextState();
|
||||
copyVaoState(vao, freshCs.currentVao);
|
||||
final long freshKey = freshC.computeKey(store, freshCs);
|
||||
|
||||
assertEquals(freshKey, cachedKey, "step " + step + ": cached key must equal a from-scratch computation");
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlAsserts;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class AbsoluteCopyRegionTest {
|
||||
|
||||
private static ByteBuffer buffer(boolean direct, boolean sliced) {
|
||||
final ByteBuffer buffer = direct ? ByteBuffer.allocateDirect(64) : ByteBuffer.allocate(64);
|
||||
if (!sliced) return buffer;
|
||||
buffer.position(7).limit(55);
|
||||
return buffer.slice();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})
|
||||
void copiesAbsoluteIndicesAcrossBufferKinds(int flags) {
|
||||
ByteBuffer src = buffer((flags & 1) != 0, (flags & 4) != 0);
|
||||
final ByteBuffer dst = buffer((flags & 2) != 0, (flags & 4) != 0);
|
||||
for (int i = 0; i < src.capacity(); i++) src.put(i, (byte) (i * 3 + 1));
|
||||
for (int i = 0; i < dst.capacity(); i++) dst.put(i, (byte) 0x7F);
|
||||
if ((flags & 8) != 0) src = src.asReadOnlyBuffer();
|
||||
src.position(13).limit(22);
|
||||
dst.position(23).limit(28);
|
||||
|
||||
ByteRegionCopy.copyByteRegion(src, 8, dst, 16, 6);
|
||||
|
||||
for (int i = 0; i < dst.limit(); i++) {
|
||||
assertEquals(i >= 16 && i < 22 ? src.get(i - 8) : (byte) 0x7F, dst.get(i));
|
||||
}
|
||||
assertEquals(13, src.position());
|
||||
assertEquals(22, src.limit());
|
||||
assertEquals(23, dst.position());
|
||||
assertEquals(28, dst.limit());
|
||||
ByteRegionCopy.copyByteRegion(src, src.limit(), dst, dst.limit(), 0);
|
||||
assertEquals(13, src.position());
|
||||
assertEquals(22, src.limit());
|
||||
assertEquals(23, dst.position());
|
||||
assertEquals(28, dst.limit());
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistentCopyUsesAbsoluteOffsetsWithPositionedStaging() {
|
||||
final ByteBuffer src = ByteBuffer.allocateDirect(32);
|
||||
final ByteBuffer dst = ByteBuffer.allocateDirect(32);
|
||||
for (int i = 0; i < 32; i++) src.put(i, (byte) i);
|
||||
src.position(7).limit(24);
|
||||
dst.position(9).limit(28);
|
||||
PersistentBufferSync.mirrorPersistentCopy(src, 4, dst, 16, 8);
|
||||
for (int i = 0; i < 8; i++) assertEquals((byte) (4 + i), dst.get(16 + i));
|
||||
assertEquals(7, src.position());
|
||||
assertEquals(24, src.limit());
|
||||
assertEquals(9, dst.position());
|
||||
assertEquals(28, dst.limit());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 1, 2, 3, 8, 9, 10, 11})
|
||||
void warmedCopiesDoNotAllocate(int flags) {
|
||||
final ByteBuffer writable = buffer((flags & 1) != 0, true);
|
||||
final ByteBuffer src = (flags & 8) != 0 ? writable.asReadOnlyBuffer() : writable;
|
||||
final ByteBuffer dst = buffer((flags & 2) != 0, true);
|
||||
src.position(5).limit(40);
|
||||
dst.position(7).limit(40);
|
||||
SdlAsserts.assertAllocationFree(() -> ByteRegionCopy.copyByteRegion(src, 2, dst, 4, 32), "absolute buffer copy");
|
||||
}
|
||||
}
|
||||
Loaded 100 of 215 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user