Compare commits

..
7 Commits
195 changed files with 12190 additions and 4021 deletions

No files matched your search

+1
View File
@@ -93,6 +93,7 @@ DependencyHandlerScope.of(dependencies).apply {
shadowImplementation(project(":lwjgl3-backend")) { isTransitive = false }
shadowImplementation(project(":sdl-gpu")) { isTransitive = false }
runtimeOnlyNonPublishable(project(":tracy-client")) { isTransitive = false }
devOnlyNonPublishable(libs.async.profiler)
shadowImplementation(libs.eventbus)
shadowImplementation(libs.jcpp) // Apache 2.0
@@ -37,6 +37,7 @@ public final class SystemProperties {
public static final boolean SDL_VERIFY_PER_FRAME_UNIFORM_BLOCK = Boolean.getBoolean("angelica.sdlgpu.verifyPerFrameUniformBlock");
public static final int SDL_FRAMES_IN_FLIGHT = Integer.getInteger("angelica.sdlgpu.framesInFlight", 2);
public static final boolean DISABLE_SDL_PRESENTER_THREAD = Boolean.getBoolean("angelica.sdlgpu.disablePresenterThread");
public static final boolean SDL_DISABLE_IN_PASS_CLEAR = Boolean.getBoolean("angelica.sdlgpu.disableInPassClear");
// Tracy
public static final boolean TRACY = Boolean.getBoolean("angelica.tracy");
@@ -44,6 +45,11 @@ public final class SystemProperties {
public static final String TRACY_DIR = System.getProperty("angelica.tracy.dir", "angelica" + File.separator + "natives" + File.separator + "tracy");
public static final int TRACY_MAX_SRC_LOCS = Math.max(16, Integer.getInteger("angelica.tracy.maxSrcLocs", 4096));
// Profiling
public static final String PROFILE_OPTS = System.getProperty("angelica.profile.opts", "event=wall,interval=5ms,alloc=512k,lock=10ms");
public static final String PROFILE_DIR = System.getProperty("angelica.profile.dir", "angelica" + File.separator + "profiles");
public static final String PROFILE_OUTPUT = System.getProperty("angelica.profile.output", "");
// Flyby
public static final String FLYBY_ROUTE = System.getProperty("angelica.flyby.route", "");
public static final boolean FLYBY_WAIT_FOR_TRACY = Boolean.getBoolean("angelica.flyby.waitForTracy");
@@ -56,6 +62,8 @@ public final class SystemProperties {
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
// Debug
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
@@ -70,6 +78,7 @@ public final class SystemProperties {
public static final boolean FFP_TRACE = Boolean.getBoolean("angelica.debug.ffpTrace");
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
public static final boolean FORCE_ORPHAN_STREAMING = Boolean.getBoolean("angelica.debug.forceOrphanStreaming");
public static final boolean WEATHER_REBUILD_ALWAYS = Boolean.getBoolean("angelica.debug.weatherRebuildAlways");
public static final String SHADER_DUMP_ROOT = "angelica_dumps";
// Set by us, read by celeritas
@@ -118,6 +127,20 @@ public final class SystemProperties {
CONFIGURED
}
public enum FlybyWeather {
CLEAR,
RAIN,
THUNDER;
public boolean isRaining() {
return this != CLEAR;
}
public boolean isThundering() {
return this == THUNDER;
}
}
private static <E extends Enum<E>> E parseEnum(String key, E whenAbsent, E whenInvalid, Class<E> type) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return whenAbsent;
@@ -0,0 +1,24 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
public final class ClipPlaneBooleanState extends BooleanStateStack {
private final GLContextState owner;
private final int index;
ClipPlaneBooleanState(GLContextState owner, int index, int glCap) {
super(glCap);
this.owner = owner;
this.index = index;
}
@Override
protected void onEnabledChanged() {
if (isEnabled()) {
owner.clipPlaneEnabledMask |= (1 << index);
} else {
owner.clipPlaneEnabledMask &= ~(1 << index);
}
}
}
@@ -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);
File diff suppressed because it is too large. Load diff
@@ -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) {
@@ -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;
}
}
@@ -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;
}
}
@@ -1,49 +1,45 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
public class TextureBindingStack extends TextureBinding implements IStateStack<TextureBindingStack> {
public final class TextureBindingStack extends TextureBinding implements CowStateStack<TextureBindingStack> {
protected final TextureBinding[] stack;
private final CowDepths cow;
protected int pointer;
public TextureBindingStack() {
public TextureBindingStack(int id, int unit) {
cow = new CowDepths(StateSet.R_TEXTURE, unit);
cow.id = id;
stack = new TextureBinding[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new TextureBinding();
}
}
public TextureBindingStack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public TextureBindingStack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public TextureBinding peek() {
return stack[pointer];
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
public boolean isEmpty() {
return pointer == 0;
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public int stackId() {
return cow.id;
}
}
@@ -9,7 +9,7 @@ import org.lwjgl.opengl.GL13;
* BooleanStateStack for per-texture-unit state.
* Switches to correct unit (raw GL) before enable/disable calls.
*/
public class TextureUnitBooleanStateStack extends BooleanStateStack {
public final class TextureUnitBooleanStateStack extends BooleanStateStack {
private final int unitIndex;
public TextureUnitBooleanStateStack(int glCap, int unitIndex) {
@@ -29,12 +29,8 @@ public class TextureUnitBooleanStateStack extends BooleanStateStack {
}
@Override
public TextureUnitBooleanStateStack popDepth() {
if (savedDepth > 0) {
final boolean oldValue = stack[--savedDepth];
setEnabledWithUnitSwitch(oldValue);
}
return this;
public void restoreSlot(int slot) {
setEnabledWithUnitSwitch(stack[slot]);
}
private void setEnabledWithUnitSwitch(boolean enabled) {
@@ -1,47 +1,42 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Vector3f;
/**
* Stack for a Vector3f value (e.g. current normal).
*/
public class Vec3fStack implements IStateStack<Vec3fStack> {
public final class Vec3fStack implements CowStateStack<Vec3fStack> {
private final Vector3f value;
private final float[][] stack;
private int pointer;
private final CowDepths cow = new CowDepths();
public Vec3fStack(Vector3f value) {
public Vec3fStack(Vector3f value, int id) {
this.value = value;
cow.id = id;
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][3];
}
@Override
public Vec3fStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer][0] = value.x;
stack[pointer][1] = value.y;
stack[pointer][2] = value.z;
pointer++;
return this;
public CowDepths cowDepths() {
return cow;
}
@Override
public Vec3fStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
pointer--;
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2]);
return this;
public void captureSlot(int s) {
stack[s][0] = value.x;
stack[s][1] = value.y;
stack[s][2] = value.z;
}
@Override
public boolean isEmpty() {
return pointer == 0;
public void restoreSlot(int s) {
value.set(stack[s][0], stack[s][1], stack[s][2]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,48 +1,43 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Vector4f;
/**
* Stack for a Vector4f value (e.g. current texture coordinates).
*/
public class Vec4fStack implements IStateStack<Vec4fStack> {
public final class Vec4fStack implements CowStateStack<Vec4fStack> {
private final Vector4f value;
private final float[][] stack;
private int pointer;
private final CowDepths cow = new CowDepths();
public Vec4fStack(Vector4f value) {
public Vec4fStack(Vector4f value, int id) {
this.value = value;
cow.id = id;
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][4];
}
@Override
public Vec4fStack push() {
if (pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer][0] = value.x;
stack[pointer][1] = value.y;
stack[pointer][2] = value.z;
stack[pointer][3] = value.w;
pointer++;
return this;
public CowDepths cowDepths() {
return cow;
}
@Override
public Vec4fStack pop() {
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
pointer--;
value.set(stack[pointer][0], stack[pointer][1], stack[pointer][2], stack[pointer][3]);
return this;
public void captureSlot(int s) {
stack[s][0] = value.x;
stack[s][1] = value.y;
stack[s][2] = value.z;
stack[s][3] = value.w;
}
@Override
public boolean isEmpty() {
return pointer == 0;
public void restoreSlot(int s) {
value.set(stack[s][0], stack[s][1], stack[s][2], stack[s][3]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -1,45 +1,44 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
public class ViewPortStateStack extends ViewportState implements IStateStack<ViewPortStateStack> {
public final class ViewPortStateStack extends ViewportState implements CowStateStack<ViewPortStateStack> {
protected final ViewportState[] stack;
private final CowDepths cow = new CowDepths(StateSet.R_VIEWPORT);
protected int pointer;
public ViewPortStateStack() {
public ViewPortStateStack(int id) {
cow.id = id;
stack = new ViewportState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new ViewportState();
}
}
public ViewPortStateStack push() {
if(pointer == stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
return this;
@Override
public CowDepths cowDepths() {
return cow;
}
public ViewPortStateStack pop() {
if(pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
return this;
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
public boolean isEmpty() {
return pointer == 0;
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
public boolean topChanged() {
return pointer > 0 && !sameAs(stack[pointer - 1]);
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -48,6 +48,7 @@ public class BooleanState implements ISettableState<BooleanState> {
if (stateUnknown || bypass || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.vendorIsAMD() && GLStateManager.isPoppingAttributes())) {
stateUnknown = false;
this.enabled = enabled;
onEnabledChanged();
if (!ffpStateOnly) {
if (enabled) {
RENDER_BACKEND.enable(this.glCap);
@@ -58,6 +59,8 @@ public class BooleanState implements ISettableState<BooleanState> {
}
}
protected void onEnabledChanged() {}
@Override
public BooleanState set(BooleanState state) {
this.enabled = state.enabled;
@@ -1,14 +1,15 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.stacks.CowDepths;
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
/**
* Per-texture-unit GL_TEXTURE_ENV state for FFP emulation. Tracks the simple texenv mode and all GL_COMBINE sub-parameters.
*/
public class TexEnvState implements IStateStack<TexEnvState> {
public final class TexEnvState implements CowStateStack<TexEnvState> {
public int mode;
@@ -30,22 +31,25 @@ public class TexEnvState implements IStateStack<TexEnvState> {
public float envColorA;
private final TexEnvState[] stack;
private int pointer;
private final CowDepths cow;
private final boolean isStackEntry;
public TexEnvState() {
public TexEnvState(int id) {
this(false);
cow.id = id;
}
private TexEnvState(boolean isStackEntry) {
this.isStackEntry = isStackEntry;
if (isStackEntry) {
stack = null;
cow = null;
} else {
stack = new TexEnvState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new TexEnvState(true);
}
cow = new CowDepths();
}
reset();
}
@@ -80,31 +84,27 @@ public class TexEnvState implements IStateStack<TexEnvState> {
}
@Override
public TexEnvState push() {
if (stack == null) throw new IllegalStateException("Cannot push stack entry");
if (pointer >= stack.length) {
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].copyFrom(this);
return this;
public CowDepths cowDepths() {
if (cow == null) throw new IllegalStateException("Cannot push/pop a stack entry");
return cow;
}
@Override
public TexEnvState pop() {
if (stack == null) throw new IllegalStateException("Cannot pop stack entry");
if (pointer == 0) {
throw new IllegalStateException("Stack underflow");
}
copyFrom(stack[--pointer]);
return this;
public void captureSlot(int s) {
stack[s].copyFrom(this);
}
@Override
public boolean isEmpty() {
return pointer == 0;
public void restoreSlot(int s) {
copyFrom(stack[s]);
}
public boolean isCombineMode() {
return mode == GL13.GL_COMBINE;
}
@Override
public int stackId() {
return cow == null ? -1 : cow.id;
}
}
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
import com.gtnewhorizons.angelica.glsm.stacks.TextureBindingStack;
import com.gtnewhorizons.angelica.glsm.stacks.TextureUnitBooleanStateStack;
import lombok.Getter;
@@ -8,7 +10,11 @@ import org.joml.Matrix4fStack;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import java.util.ArrayList;
import java.util.List;
public class TextureUnitArray {
private final List<IStateStack<?>> idStacks = new ArrayList<>();
private final TextureBindingStack[] bindings;
private final TextureUnitBooleanStateStack[] states;
private final TextureUnitBooleanStateStack[] texture1DStates;
@@ -22,6 +28,9 @@ public class TextureUnitArray {
@Getter
public final Matrix4fStack[] textureMatricies;
public int texMatIdentityMask;
public int texMatIdentityGen;
public TextureUnitArray() {
bindings = new TextureBindingStack[GLStateManager.MAX_TEXTURE_UNITS];
states = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
@@ -36,20 +45,35 @@ public class TextureUnitArray {
textureMatricies = new Matrix4fStack[GLStateManager.MAX_TEXTURE_UNITS];
for (int i = 0; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
bindings[i] = new TextureBindingStack();
states[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_2D, i);
texture1DStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_1D, i);
texture3DStates[i] = new TextureUnitBooleanStateStack(GL12.GL_TEXTURE_3D, i);
texGenSStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_S, i);
texGenTStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_T, i);
texGenRStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_R, i);
texGenQStates[i] = new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_Q, i);
bindings[i] = member(new TextureBindingStack(StackIdAllocator.nextId(), i));
states[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_2D, i));
texture1DStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_1D, i));
texture3DStates[i] = track(new TextureUnitBooleanStateStack(GL12.GL_TEXTURE_3D, i));
texGenSStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_S, i));
texGenTStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_T, i));
texGenRStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_R, i));
texGenQStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_Q, i));
texGenStates[i] = new TexGenState();
texEnvStates[i] = new TexEnvState();
texEnvStates[i] = member(new TexEnvState(StackIdAllocator.nextId()));
textureMatricies[i] = new Matrix4fStack(GLStateManager.MAX_TEXTURE_STACK_DEPTH);
}
}
private <T extends TextureUnitBooleanStateStack> T track(T s) {
s.setStackId(StackIdAllocator.nextId());
idStacks.add(s);
return s;
}
private <T extends IStateStack<?>> T member(T s) {
idStacks.add(s);
return s;
}
public List<IStateStack<?>> idStacks() {
return idStacks;
}
public TextureBindingStack getTextureUnitBindings(int index) {
return bindings[index];
}
@@ -0,0 +1,305 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@GLCoreTest
public class GLSM_DisplayList_StateSet_GLTest {
private final List<Integer> listsToDelete = new ArrayList<>();
@AfterEach
void cleanup() {
DisplayListManager.abortCompilation();
while (GLStateManager.getAttribDepth() > 0) {
GLStateManager.popState();
}
for (int id : listsToDelete) {
GLStateManager.glDeleteLists(id, 1);
}
listsToDelete.clear();
GLStateManager.disableBlend();
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.disableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.disableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
}
private int newList() {
final int id = GLStateManager.glGenLists(1);
listsToDelete.add(id);
return id;
}
private static Int2IntMap commandCounts(int listId) {
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(listId);
assertNotNull(compiled, "compiled display list must exist for id " + listId);
return compiled.getCommandCounts();
}
@Test
void compileBracketDoesNotThrowAndLeavesLiveStateUntouched() {
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int func0 = GLStateManager.getAlphaState().getFunction();
final float ref0 = GLStateManager.getAlphaState().getReference();
final int unit0 = GLStateManager.getActiveTextureUnit();
final int depth0 = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
verifyIsEnabled(GL11.GL_ALPHA_TEST, true, "cache alpha test enable unchanged after COMPILE-only bracket");
assertEquals(func0, GLStateManager.getAlphaState().getFunction(), "cache alpha func unchanged after COMPILE-only bracket");
assertEquals(ref0, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref unchanged after COMPILE-only bracket");
assertEquals(unit0, GLStateManager.getActiveTextureUnit(), "cache active unit unchanged after COMPILE-only bracket");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after COMPILE-only bracket");
assertEquals(2, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "push and pop each record one COMPLEX_REF");
}
@Test
void compiledBracketReplayRestoresState() {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.9f);
GLStateManager.disableAlphaTest();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int depth0 = GLStateManager.getAttribDepth();
GLStateManager.glCallList(list);
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored by replay");
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored by replay");
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled by replay");
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored by replay");
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "driver active unit restored by replay");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged by balanced replay");
}
@Test
void compileAndExecuteBracketIsBalanced() {
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.9f);
GLStateManager.disableAlphaTest();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int depth0 = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored after GL_COMPILE_AND_EXECUTE bracket");
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after balanced GL_COMPILE_AND_EXECUTE bracket");
GLStateManager.glCallList(list);
GLStateManager.glCallList(list);
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored after replay");
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored after replay");
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled after replay");
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored after replay");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after replay");
}
@Test
void nestedListsPushInBothLevels() {
GLStateManager.enableBlend();
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
final boolean cull0 = GLStateManager.glIsEnabled(GL11.GL_CULL_FACE);
final int depth0 = GLStateManager.getAttribDepth();
final int parent = newList();
final int child = newList();
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
final int dParent = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.glNewList(child, GL11.GL_COMPILE);
final int dChild = GLStateManager.pushState(StateSet.CULL);
if (cull0) {
GLStateManager.disableCull();
} else {
GLStateManager.enableCull();
}
GLStateManager.popStateTo(dChild);
GLStateManager.glEndList();
GLStateManager.disableBlend();
GLStateManager.popStateTo(dParent);
GLStateManager.glEndList();
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged by nested COMPILE-only bracket");
GLStateManager.glCallList(parent);
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored by the parent BLEND bracket after parent replay");
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored after parent replay");
GLStateManager.glCallList(child);
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored after child replay");
}
@Test
void nestedCompileAndExecuteChildInsideCompileParent() {
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
final boolean cull0 = GLStateManager.glIsEnabled(GL11.GL_CULL_FACE);
final int depth0 = GLStateManager.getAttribDepth();
final int parent = newList();
final int child = newList();
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
final int dParent = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.glNewList(child, GL11.GL_COMPILE_AND_EXECUTE);
final int dChild = GLStateManager.pushState(StateSet.CULL);
if (cull0) {
GLStateManager.disableCull();
} else {
GLStateManager.enableCull();
}
GLStateManager.popStateTo(dChild);
GLStateManager.glEndList();
verifyIsEnabled(GL11.GL_CULL_FACE, cull0, "cull enable restored live immediately after the COMPILE_AND_EXECUTE child bracket closes");
GLStateManager.popStateTo(dParent);
GLStateManager.glEndList();
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after balanced nested bracket");
assertEquals(2, commandCounts(parent).getOrDefault(GLCommand.COMPLEX_REF, 0), "parent list records exactly the BLEND push and pop");
assertEquals(2, commandCounts(child).getOrDefault(GLCommand.COMPLEX_REF, 0), "child list records exactly the CULL push and pop");
}
@Test
void unbalancedBracketIsClosedAtEndList() {
GLStateManager.enableBlend();
final boolean blend0 = GLStateManager.glIsEnabled(GL11.GL_BLEND);
final int depth0 = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.pushState(StateSet.BATCH);
GLStateManager.disableBlend();
GLStateManager.glEndList();
assertEquals(2, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "glEndList closes the unbalanced push, recording a matching pop");
GLStateManager.glCallList(list);
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after replaying an auto-closed bracket");
verifyIsEnabled(GL11.GL_BLEND, blend0, "blend enable unchanged after replaying an auto-closed bracket");
final int other = newList();
assertDoesNotThrow(() -> {
GLStateManager.glNewList(other, GL11.GL_COMPILE);
GLStateManager.glEndList();
}, "an unrelated list must compile normally after an auto-closed bracket");
}
@Test
void glPushAttribRecordsExactlyOnce() {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glPopAttrib();
GLStateManager.glEndList();
final Int2IntMap counts = commandCounts(list);
assertEquals(1, counts.getOrDefault(GLCommand.PUSH_ATTRIB, 0), "glPushAttrib records exactly one PUSH_ATTRIB opcode");
assertEquals(1, counts.getOrDefault(GLCommand.POP_ATTRIB, 0), "glPopAttrib records exactly one POP_ATTRIB opcode");
assertEquals(0, counts.getOrDefault(GLCommand.COMPLEX_REF, 0), "glPushAttrib/glPopAttrib never record a COMPLEX_REF");
}
@Test
void livePushStraddlesOpenList() {
GLStateManager.enableBlend();
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.disableBlend();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glEndList();
GLStateManager.popStateTo(d);
assertEquals(d, GLStateManager.getAttribDepth(), "live attrib depth restored to the pre-bracket value");
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored by the live pop that straddled the open-then-closed list");
}
@Test
void livePopInsideOpenListPopsLiveAndRecordsNothing() {
GLStateManager.enableBlend();
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.disableBlend();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
assertEquals(d, GLStateManager.getAttribDepth(), "live attrib depth restored by the live pop performed inside the open list");
verifyIsEnabled(GL11.GL_BLEND, true, "blend enable restored live by the pop of a pre-list push");
assertEquals(0, commandCounts(list).getOrDefault(GLCommand.COMPLEX_REF, 0), "popping a pre-existing live push records nothing into the list");
}
@Test
void virtualDepthOverflowThrowsAtRecordTime() {
final int startDepth = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
final int pushes = GLStateManager.MAX_ATTRIB_STACK_DEPTH - startDepth;
for (int i = 0; i < pushes; i++) {
GLStateManager.pushState(StateSet.CUTOUT);
}
assertThrows(IllegalStateException.class, () -> GLStateManager.pushState(StateSet.CUTOUT), "virtual depth overflow beyond MAX_ATTRIB_STACK_DEPTH must throw at record time");
DisplayListManager.abortCompilation();
assertEquals(startDepth, GLStateManager.getAttribDepth(), "aborting the overflowed compilation leaves live attrib depth untouched");
}
}
@@ -0,0 +1,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);
}
}
}
@@ -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();
@@ -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) {
@@ -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();
@@ -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));
}
}
@@ -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);
}
+2
View File
@@ -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" }
+3 -1
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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]);
}
}
@@ -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);
@@ -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);
@@ -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);
}
@@ -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));
}
}
}
@@ -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);
}
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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));
}
}
@@ -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());
@@ -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");
}
}
@@ -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");
}
}
@@ -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();
}
}
}
@@ -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");
}
}
}
@@ -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");
}
}
@@ -0,0 +1,112 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.SdlAsserts;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager.ProgramObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class UniformLocationKindTest {
private static final int PLAIN_LOC = 0;
private static final int SAMPLER_LOC = 1;
private static final int IMAGE_LOC = 2;
private static SDLGPURenderBackend backend;
private ContextState st;
private ProgramObject prog;
private ProgramObject savedProgramObj;
private int savedProgram;
@BeforeAll
static void createBackend() {
backend = new SDLGPURenderBackend();
}
@BeforeEach
void setUp() {
st = SdlTestRig.contextState();
savedProgramObj = st.boundProgramObj;
savedProgram = st.boundProgram;
prog = new ProgramObject();
prog.linked = true;
prog.nextUniformLocation = 3;
registerLocation(PLAIN_LOC, "u_plain");
registerLocation(SAMPLER_LOC, "u_sampler");
registerLocation(IMAGE_LOC, "u_image");
prog.allSamplerNames.add("u_sampler");
prog.allImageNames.add("u_image");
prog.buildUniformSlotArrays();
st.boundProgramObj = prog;
st.boundProgram = 1;
}
@AfterEach
void tearDown() {
st.releaseUniformStaging(prog);
st.boundProgramObj = savedProgramObj;
st.boundProgram = savedProgram;
}
private void registerLocation(int loc, String name) {
prog.nameToLocation.put(name, loc);
prog.locationToName.put(loc, name);
}
@Test
void buildUniformSlotArraysClassifiesEveryLocation() {
assertEquals(ProgramObject.LOCATION_KIND_PLAIN, prog.locationKind[PLAIN_LOC]);
assertEquals(ProgramObject.LOCATION_KIND_SAMPLER, prog.locationKind[SAMPLER_LOC]);
assertEquals(ProgramObject.LOCATION_KIND_IMAGE, prog.locationKind[IMAGE_LOC]);
assertEquals("u_plain", prog.locationName[PLAIN_LOC]);
assertEquals("u_sampler", prog.locationName[SAMPLER_LOC]);
assertEquals("u_image", prog.locationName[IMAGE_LOC]);
assertEquals(-1, prog.locationSamplerUnit[SAMPLER_LOC], "a fresh link has not bound a sampler unit yet");
}
@Test
void samplerLocationRoutesToTheSamplerMapAndDirtiesOnFirstWrite() {
backend.uniform1i(SAMPLER_LOC, 3);
assertEquals(3, prog.samplerTextureUnits.getInt("u_sampler"));
assertEquals(3, prog.locationSamplerUnit[SAMPLER_LOC]);
assertTrue(prog.samplerUnitsDirty);
prog.samplerUnitsDirty = false;
backend.uniform1i(SAMPLER_LOC, 3);
assertFalse(prog.samplerUnitsDirty, "an unchanged sampler unit must not re-dirty");
}
@Test
void imageLocationRoutesToTheImageMap() {
backend.uniform1i(IMAGE_LOC, 5);
assertEquals(5, prog.imageTextureUnits.getInt("u_image"));
}
@Test
void changedSamplerValueDirtiesAndUpdatesBothTheArrayAndTheMap() {
backend.uniform1i(SAMPLER_LOC, 3);
prog.samplerUnitsDirty = false;
backend.uniform1i(SAMPLER_LOC, 4);
assertEquals(4, prog.samplerTextureUnits.getInt("u_sampler"));
assertEquals(4, prog.locationSamplerUnit[SAMPLER_LOC]);
assertTrue(prog.samplerUnitsDirty);
}
@Test
void repeatedUnchangedSamplerWritesDoNotAllocate() {
backend.uniform1i(SAMPLER_LOC, 7);
SdlAsserts.assertAllocationFree(() -> backend.uniform1i(SAMPLER_LOC, 7), "unchanged sampler-unit uniform1i");
}
}
@@ -0,0 +1,39 @@
package com.gtnewhorizons.angelica.api.clouds;
import net.minecraft.util.ResourceLocation;
public record CloudLayer(
String id,
ResourceLocation texture,
float height,
float cellWidth,
float cellHeight,
float coordinateWidth,
double offsetX,
double offsetZ,
float red,
float green,
float blue,
float alpha) {
public CloudLayer {
if (id == null || id.isBlank()) throw new IllegalArgumentException("Cloud layer id must not be empty");
if (texture == null) throw new IllegalArgumentException("Cloud layer texture must not be null");
if (!Float.isFinite(height)) throw new IllegalArgumentException("Cloud layer height must be finite");
if (!Float.isFinite(cellWidth) || cellWidth <= 0
|| !Float.isFinite(cellHeight) || cellHeight <= 0
|| !Float.isFinite(coordinateWidth) || coordinateWidth <= 0) {
throw new IllegalArgumentException("Cloud layer dimensions must be finite and positive");
}
if (!Double.isFinite(offsetX) || !Double.isFinite(offsetZ)) {
throw new IllegalArgumentException("Cloud layer offsets must be finite");
}
if (!Float.isFinite(red) || red < 0 || !Float.isFinite(green) || green < 0
|| !Float.isFinite(blue) || blue < 0) {
throw new IllegalArgumentException("Cloud layer colors must be finite and nonnegative");
}
if (!Float.isFinite(alpha) || alpha < 0 || alpha > 1) {
throw new IllegalArgumentException("Cloud layer alpha must be between zero and one");
}
}
}
@@ -0,0 +1,10 @@
package com.gtnewhorizons.angelica.api.clouds;
import java.util.List;
import net.minecraft.client.multiplayer.WorldClient;
public interface CloudLayerProvider {
List<CloudLayer> getCloudLayers(WorldClient world, int cloudTicks, float partialTicks);
}
@@ -8,10 +8,10 @@ import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.ffp.FFPVertexLighting;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
@@ -38,7 +38,6 @@ import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
@@ -184,9 +183,6 @@ public class BatchingFontRenderer {
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
private final BlendState blendStateBefore = new BlendState();
private static final BlendState deferredBlendStateBefore = new BlendState();
private float lightmapU = 0.0f;
private float lightmapV = 0.0f;
private boolean lightmapActive = false;
@@ -586,6 +582,17 @@ public class BatchingFontRenderer {
endBatch();
}
public void resetAfterCrash() {
discardDeferredText();
batchDepth = 0;
deferredCmdWatermark = 0;
deferredVertexPos = 0;
deferredIdxPos = 0;
truncateBatchToWatermark();
arenaOwner = null;
flushLastTexture = null;
}
private void deferBatch() {
sealBatchSegment();
if (batchSegments.isEmpty()) {
@@ -623,73 +630,48 @@ public class BatchingFontRenderer {
private static void flushDeferredThroughPipeline() {
final BatchingFontRenderer first = deferredSegments.getFirst().owner;
final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled();
final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled();
GLStateManager.getEffectiveBlendState(deferredBlendStateBefore);
final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
final boolean depthTestBefore = GLStateManager.getDepthTest().isEnabled();
final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled();
final int prevBlockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
boolean textureChanged = false;
GLStateManager.enableTexture();
GLStateManager.enableAlphaTest();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(first.blendSrcRGB, first.blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glDepthMask(false);
final Boolean prevTranslucency = GbufferPrograms.beginTranslucencyDeclaration(Boolean.TRUE);
GbufferPrograms.setBlockEntityDefaults();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
final int d = GLStateManager.pushState(StateSet.FONT_PIPELINE);
try {
boolean curDepthTest = depthTestBefore;
for (final TextSegment segment : deferredSegments) {
final BatchingFontRenderer owner = segment.owner;
if (segment.cmdStart == segment.cmdEnd) continue;
GLStateManager.enableTexture();
GLStateManager.enableAlphaTest();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(first.blendSrcRGB, first.blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glDepthMask(false);
GLStateManager.setModelViewMatrix(segment.modelView);
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(segment.blockEntityId);
final Boolean prevTranslucency = GbufferPrograms.beginTranslucencyDeclaration(Boolean.TRUE);
GbufferPrograms.setBlockEntityDefaults();
if (segment.depthTest != curDepthTest) {
if (segment.depthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
curDepthTest = segment.depthTest;
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
try {
boolean curDepthTest = GLStateManager.getDepthTest().isEnabled();
for (final TextSegment segment : deferredSegments) {
final BatchingFontRenderer owner = segment.owner;
if (segment.cmdStart == segment.cmdEnd) continue;
GLStateManager.setModelViewMatrix(segment.modelView);
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(segment.blockEntityId);
if (segment.depthTest != curDepthTest) {
if (segment.depthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
curDepthTest = segment.depthTest;
}
final FontDrawCmd[] cmdsData = owner.batchCommands.elements();
Arrays.sort(cmdsData, segment.cmdStart, segment.cmdEnd, FontDrawCmd.DRAW_ORDER_COMPARATOR);
emitRangeThroughPipeline(owner, cmdsData, segment.cmdStart, segment.cmdEnd,
segment.packedLight, segment.normalX, segment.normalY, segment.normalZ);
}
final FontDrawCmd[] cmdsData = owner.batchCommands.elements();
Arrays.sort(cmdsData, segment.cmdStart, segment.cmdEnd, FontDrawCmd.DRAW_ORDER_COMPARATOR);
textureChanged |= emitRangeThroughPipeline(owner, cmdsData, segment.cmdStart, segment.cmdEnd,
segment.packedLight, segment.normalX, segment.normalY, segment.normalZ);
} finally {
GLStateManager.glPopMatrix();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
GbufferPrograms.endTranslucencyDeclaration(prevTranslucency);
}
} finally {
GLStateManager.glPopMatrix();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
GbufferPrograms.endTranslucencyDeclaration(prevTranslucency);
restoreDepth(depthTestBefore, depthMaskBefore);
if (isTextureEnabledBefore) {
GLStateManager.enableTexture();
} else {
GLStateManager.disableTexture();
}
if (isBlendEnabledBefore) {
GLStateManager.enableBlend();
} else {
GLStateManager.disableBlend();
}
GLStateManager.tryBlendFuncSeparate(deferredBlendStateBefore.getSrcRgb(), deferredBlendStateBefore.getDstRgb(),
deferredBlendStateBefore.getSrcAlpha(), deferredBlendStateBefore.getDstAlpha());
if (isAlphaTestEnabledBefore) {
GLStateManager.enableAlphaTest();
} else {
GLStateManager.disableAlphaTest();
}
if (textureChanged) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore);
}
GLStateManager.popStateTo(d);
discardDeferredText();
}
}
@@ -697,27 +679,15 @@ public class BatchingFontRenderer {
private static void flushDeferredTextInner() {
uploadVertexData(deferredVertexPos);
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled();
final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled();
GLStateManager.getEffectiveBlendState(deferredBlendStateBefore);
final int shadeModelBefore = GLStateManager.glGetInteger(GL11.GL_SHADE_MODEL);
final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
final int lightmapBindingBefore = boundLightmapTexture();
final boolean depthTestBefore = GLStateManager.getDepthTest().isEnabled();
final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled();
deferredSegments.get(0).owner.setupFontDrawState();
GLStateManager.glDepthMask(false);
flushLastTexture = DUMMY_RESOURCE_LOCATION;
flushTextureChanged = false;
resetFlushLightmap();
final int d = GLStateManager.pushState(StateSet.FONT);
try {
deferredSegments.get(0).owner.setupFontDrawState();
GLStateManager.glDepthMask(false);
flushLastTexture = DUMMY_RESOURCE_LOCATION;
resetFlushLightmap();
try (MemoryStack stack = stackPush()) {
final FloatBuffer mvpBuf = stack.mallocFloat(16);
boolean curDepthTest = depthTestBefore;
boolean curDepthTest = GLStateManager.getDepthTest().isEnabled();
for (final TextSegment segment : deferredSegments) {
mvpBuf.clear();
segment.mvp.get(mvpBuf);
@@ -732,9 +702,9 @@ public class BatchingFontRenderer {
}
}
} finally {
restoreFontDrawState(prevProgram, isTextureEnabledBefore, isBlendEnabledBefore, boundTextureBefore,
lightmapBindingBefore, isAlphaTestEnabledBefore, deferredBlendStateBefore, shadeModelBefore);
restoreDepth(depthTestBefore, depthMaskBefore);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
GLStateManager.popStateTo(d);
discardDeferredText();
}
}
@@ -826,8 +796,6 @@ public class BatchingFontRenderer {
uploadVertexData(vertexDataPos);
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
final FontDrawCmd[] cmds = batchCommands.elements();
for (int i = 0; i < segmentCount; i++) {
final TextSegment segment = batchSegments.get(i);
@@ -835,42 +803,36 @@ public class BatchingFontRenderer {
}
Arrays.sort(cmds, batchSealedEnd, cmdCount, FontDrawCmd.DRAW_ORDER_COMPARATOR);
final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled();
final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled();
GLStateManager.getEffectiveBlendState(blendStateBefore);
final int shadeModelBefore = GLStateManager.glGetInteger(GL11.GL_SHADE_MODEL);
final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
final int lightmapBindingBefore = boundLightmapTexture();
final boolean depthTestBefore = GLStateManager.getDepthTest().isEnabled();
final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled();
setupFontDrawState();
flushLastTexture = DUMMY_RESOURCE_LOCATION;
flushTextureChanged = false;
resetFlushLightmap();
try (MemoryStack stack = stackPush()) {
final FloatBuffer mvpBuf = stack.mallocFloat(16);
for (int i = 0; i < segmentCount; i++) {
final TextSegment segment = batchSegments.get(i);
mvpBuf.clear();
segment.mvp.get(mvpBuf);
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
uploadLightmap(lightmapLocation, segment.lightmapActive, segment.lightmapU, segment.lightmapV, segment.lightmapTexture);
drawCommands(cmds, segment.cmdStart, segment.cmdEnd, this);
}
if (batchSealedEnd < cmdCount) {
mvpBuf.clear();
resolveMvp(scratchMvp);
scratchMvp.get(mvpBuf);
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
uploadLightmap(lightmapLocation, lightmapActive, lightmapU, lightmapV, lightmapTextureId);
drawCommands(cmds, batchSealedEnd, cmdCount, this);
final int d = GLStateManager.pushState(StateSet.FONT);
try {
setupFontDrawState();
flushLastTexture = DUMMY_RESOURCE_LOCATION;
resetFlushLightmap();
try (MemoryStack stack = stackPush()) {
final FloatBuffer mvpBuf = stack.mallocFloat(16);
for (int i = 0; i < segmentCount; i++) {
final TextSegment segment = batchSegments.get(i);
mvpBuf.clear();
segment.mvp.get(mvpBuf);
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
uploadLightmap(lightmapLocation, segment.lightmapActive, segment.lightmapU, segment.lightmapV, segment.lightmapTexture);
drawCommands(cmds, segment.cmdStart, segment.cmdEnd, this);
}
if (batchSealedEnd < cmdCount) {
mvpBuf.clear();
resolveMvp(scratchMvp);
scratchMvp.get(mvpBuf);
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
uploadLightmap(lightmapLocation, lightmapActive, lightmapU, lightmapV, lightmapTextureId);
drawCommands(cmds, batchSealedEnd, cmdCount, this);
}
} finally {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
} finally {
GLStateManager.popStateTo(d);
}
restoreFontDrawState(prevProgram, isTextureEnabledBefore, isBlendEnabledBefore, boundTextureBefore,
lightmapBindingBefore, isAlphaTestEnabledBefore, blendStateBefore, shadeModelBefore);
restoreDepth(depthTestBefore, depthMaskBefore);
truncateBatchToWatermark();
}
@@ -924,7 +886,6 @@ public class BatchingFontRenderer {
}
private static ResourceLocation flushLastTexture;
private static boolean flushTextureChanged;
private static float flushLastLightmapU;
private static float flushLastLightmapV;
@@ -969,7 +930,6 @@ public class BatchingFontRenderer {
}
if (cmd.texture != null) {
((FontRendererAccessor) owner.underlying).angelica$bindTexture(cmd.texture);
flushTextureChanged = true;
}
flushLastTexture = cmd.texture;
}
@@ -977,38 +937,6 @@ public class BatchingFontRenderer {
}
}
private static void restoreFontDrawState(int prevProgram, boolean textureEnabledBefore, boolean blendEnabledBefore,
int boundTextureBefore, int lightmapBindingBefore, boolean alphaTestEnabledBefore, BlendState blendBefore,
int shadeModelBefore) {
GLStateManager.glUseProgram(prevProgram);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
if (textureEnabledBefore) {
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
}
if (blendEnabledBefore) {
GLStateManager.enableBlend();
} else {
GLStateManager.disableBlend();
}
GLStateManager.tryBlendFuncSeparate(blendBefore.getSrcRgb(), blendBefore.getDstRgb(),
blendBefore.getSrcAlpha(), blendBefore.getDstAlpha());
if (alphaTestEnabledBefore) {
GLStateManager.enableAlphaTest();
} else {
GLStateManager.disableAlphaTest();
}
GLStateManager.glShadeModel(shadeModelBefore);
if (flushTextureChanged) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore);
}
if (boundLightmapTexture() != lightmapBindingBefore) {
bindLightmapTexture(lightmapBindingBefore);
}
}
private void flushBatchThroughPipeline() {
final int cmdCount = batchCommands.size();
final int segmentCount = batchSegments.size();
@@ -1017,21 +945,7 @@ public class BatchingFontRenderer {
return;
}
final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled();
final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled();
GLStateManager.getEffectiveBlendState(blendStateBefore);
final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
final int prevBlockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
boolean textureChanged = false;
GLStateManager.enableTexture();
GLStateManager.enableAlphaTest();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
final Boolean prevTranslucency = GbufferPrograms.beginTranslucencyDeclaration(Boolean.TRUE);
GbufferPrograms.setBlockEntityDefaults();
final FontDrawCmd[] cmdsData = batchCommands.elements();
for (int i = 0; i < segmentCount; i++) {
@@ -1043,60 +957,50 @@ public class BatchingFontRenderer {
// The unsealed tail still belongs to the modelview live at flush time; capture it before the segments move it.
resolveModelView(scratchModelView);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
final int d = GLStateManager.pushState(StateSet.FONT_PIPELINE);
try {
for (int i = 0; i < segmentCount; i++) {
final TextSegment segment = batchSegments.get(i);
if (segment.cmdStart == segment.cmdEnd) continue;
GLStateManager.setModelViewMatrix(segment.modelView);
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(segment.blockEntityId);
textureChanged |= emitRangeThroughPipeline(this, cmdsData, segment.cmdStart, segment.cmdEnd,
segment.packedLight, segment.normalX, segment.normalY, segment.normalZ);
}
if (batchSealedEnd < cmdCount) {
GLStateManager.setModelViewMatrix(scratchModelView);
GLStateManager.enableTexture();
GLStateManager.enableAlphaTest();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
final Boolean prevTranslucency = GbufferPrograms.beginTranslucencyDeclaration(Boolean.TRUE);
GbufferPrograms.setBlockEntityDefaults();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
try {
for (int i = 0; i < segmentCount; i++) {
final TextSegment segment = batchSegments.get(i);
if (segment.cmdStart == segment.cmdEnd) continue;
GLStateManager.setModelViewMatrix(segment.modelView);
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(segment.blockEntityId);
emitRangeThroughPipeline(this, cmdsData, segment.cmdStart, segment.cmdEnd,
segment.packedLight, segment.normalX, segment.normalY, segment.normalZ);
}
if (batchSealedEnd < cmdCount) {
GLStateManager.setModelViewMatrix(scratchModelView);
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
final Vector3f normal = ShaderManager.getCurrentNormal();
emitRangeThroughPipeline(this, cmdsData, batchSealedEnd, cmdCount,
GLSMConfig.packedLastBrightness(), normal.x, normal.y, normal.z);
}
} finally {
GLStateManager.glPopMatrix();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
final Vector3f normal = ShaderManager.getCurrentNormal();
textureChanged |= emitRangeThroughPipeline(this, cmdsData, batchSealedEnd, cmdCount,
GLSMConfig.packedLastBrightness(), normal.x, normal.y, normal.z);
GbufferPrograms.endTranslucencyDeclaration(prevTranslucency);
}
} finally {
GLStateManager.glPopMatrix();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
GbufferPrograms.endTranslucencyDeclaration(prevTranslucency);
if (isTextureEnabledBefore) {
GLStateManager.enableTexture();
} else {
GLStateManager.disableTexture();
}
if (isBlendEnabledBefore) {
GLStateManager.enableBlend();
} else {
GLStateManager.disableBlend();
}
GLStateManager.tryBlendFuncSeparate(blendStateBefore.getSrcRgb(), blendStateBefore.getDstRgb(),
blendStateBefore.getSrcAlpha(), blendStateBefore.getDstAlpha());
if (isAlphaTestEnabledBefore) {
GLStateManager.enableAlphaTest();
} else {
GLStateManager.disableAlphaTest();
}
if (textureChanged) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore);
}
GLStateManager.popStateTo(d);
truncateBatchToWatermark();
}
}
/** Replays one sealed command range through the Tessellator so Iris sees it as ordinary geometry. */
private static boolean emitRangeThroughPipeline(BatchingFontRenderer owner, FontDrawCmd[] cmdsData, int from, int to,
private static void emitRangeThroughPipeline(BatchingFontRenderer owner, FontDrawCmd[] cmdsData, int from, int to,
int packedLight, float normalX, float normalY, float normalZ) {
final Tessellator tessellator = Tessellator.instance;
ResourceLocation lastTexture = DUMMY_RESOURCE_LOCATION;
boolean textureChanged = false;
boolean drawing = false;
try {
for (int i = from; i < to; i++) {
@@ -1111,7 +1015,6 @@ public class BatchingFontRenderer {
} else {
GLStateManager.enableTexture();
((FontRendererAccessor) owner.underlying).angelica$bindTexture(cmd.texture);
textureChanged = true;
}
lastTexture = cmd.texture;
}
@@ -1128,7 +1031,6 @@ public class BatchingFontRenderer {
tessellator.draw();
}
}
return textureChanged;
}
private void emitCommand(Tessellator tessellator, FontDrawCmd cmd) {
@@ -1154,15 +1056,6 @@ public class BatchingFontRenderer {
memGetFloat(ptr + 8), memGetFloat(ptr + 12));
}
private static void restoreDepth(boolean testBefore, boolean maskBefore) {
if (testBefore != GLStateManager.getDepthTest().isEnabled()) {
if (testBefore) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
}
if (maskBefore != GLStateManager.isEffectiveDepthMaskEnabled()) {
GLStateManager.glDepthMask(maskBefore);
}
}
private void truncateBatchToWatermark() {
recycleBatchSegments();
for (int i = batchCommands.size() - 1; i >= deferredCmdWatermark; i--) {
Loaded 100 of 195 files, more files were not shown because too many files have changed in this diff. Show more