Render pass and state stack optimizations (#2124)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-09-21 20:52:51 -07:00
1 parent 87c83f0645
commit 9dd60f6a75
151 files changed
+9242 -3156

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,7 @@ public final class SystemProperties {
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
// Debug
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
@@ -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);
}
}
}
@@ -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];
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,232 @@
package com.gtnewhorizons.angelica.glsm;
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;
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;
}
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);
}
}
@@ -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);
}
}
@@ -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
@@ -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;
@@ -151,15 +153,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 +178,9 @@ public final class ShaderManager {
}
}
private void uploadUniforms() {
private void uploadUniforms(GLContextState glCtx) {
if (currentProgram != null) {
uniforms.upload();
uniforms.upload(glCtx);
}
}
@@ -190,16 +193,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;
@@ -81,43 +83,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 +151,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 +192,7 @@ public class Uniforms {
stagedMisc++;
dirty = true;
}
final LineState line = GLStateManager.getLineState();
final int stippleFactor = line.getStippleFactor() < 1 ? 1 : line.getStippleFactor();
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 +217,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 +230,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 +241,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 +287,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 +296,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 +308,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 +320,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();
@@ -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;
@@ -126,54 +127,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 +223,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);
}
@@ -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);
}
@@ -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,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,725 @@
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 internalPushDuringDisplayListCompileThrowsAndLeavesDepthUnchanged() {
final int list = GLStateManager.glGenLists(1);
final int depthBefore = GLStateManager.getAttribDepth();
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE);
assertThrows(IllegalStateException.class, () -> GLStateManager.pushState(StateSet.BLEND));
GLStateManager.pushState(StateSet.forMask(GL11.GL_COLOR_BUFFER_BIT));
GLStateManager.popState();
assertEquals(depthBefore, GLStateManager.getAttribDepth(), "mask-based push/pop is recorded, not executed, and leaves live depth unchanged");
} finally {
GLStateManager.glEndList();
GLStateManager.glDeleteLists(list, 1);
}
}
@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
@@ -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,6 +192,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private void markAllPipelineInputDirty() {
pipelineStore.bumpLivenessGen();
for (ContextState st : registeredStates.snapshot()) st.pipeline.markInputDirty();
}
@@ -195,7 +201,9 @@ public class SDLGPURenderBackend extends RenderBackend {
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 +213,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 +498,7 @@ public class SDLGPURenderBackend extends RenderBackend {
registeredStates.clear();
drawDispatch.clearWarnedState();
attachmentClear.shutdown();
pipelineStore.shutdown();
shaderManager.shutdown();
resourceManager.shutdown();
@@ -940,8 +953,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 +997,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 +1029,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
if (affectsActivePass) {
frameManager.endRenderPassIfActive();
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
}
}
@@ -1741,14 +1776,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 +1810,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 +2065,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 +2116,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 +2308,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 +2749,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 +3064,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 +3216,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 +3258,7 @@ public class SDLGPURenderBackend extends RenderBackend {
vao.bindingDivisor[b] = effective;
st.bumpAttribStateGen();
cs.pipeline.markInputDirty();
vao.invalidateInputHash();
}
}
@@ -3237,10 +3275,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 +3288,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 +3298,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 +3315,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 +3326,7 @@ public class SDLGPURenderBackend extends RenderBackend {
vao.attribBinding[attribindex] = bindingindex;
st.bumpAttribStateGen();
cs.pipeline.markInputDirty();
vao.invalidateInputHash();
}
}
@@ -3354,7 +3403,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; }
}
@@ -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");
}
}
@@ -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;
@@ -623,73 +619,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 +668,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 +691,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 +785,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 +792,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 +875,6 @@ public class BatchingFontRenderer {
}
private static ResourceLocation flushLastTexture;
private static boolean flushTextureChanged;
private static float flushLastLightmapU;
private static float flushLastLightmapV;
@@ -969,7 +919,6 @@ public class BatchingFontRenderer {
}
if (cmd.texture != null) {
((FontRendererAccessor) owner.underlying).angelica$bindTexture(cmd.texture);
flushTextureChanged = true;
}
flushLastTexture = cmd.texture;
}
@@ -977,38 +926,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 +934,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 +946,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 +1004,6 @@ public class BatchingFontRenderer {
} else {
GLStateManager.enableTexture();
((FontRendererAccessor) owner.underlying).angelica$bindTexture(cmd.texture);
textureChanged = true;
}
lastTexture = cmd.texture;
}
@@ -1128,7 +1020,6 @@ public class BatchingFontRenderer {
tessellator.draw();
}
}
return textureChanged;
}
private void emitCommand(Tessellator tessellator, FontDrawCmd cmd) {
@@ -1154,15 +1045,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--) {
@@ -6,11 +6,14 @@ import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.debug.ChunkDebugMinimap;
import com.gtnewhorizons.angelica.debug.flyby.FlybyRoute;
import com.gtnewhorizons.angelica.debug.flyby.FlybyRunner;
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import net.minecraft.client.Minecraft;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommand;
import net.minecraft.command.ICommandSender;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
@@ -20,7 +23,7 @@ import java.util.List;
public class AngelicaCommand extends CommandBase {
private static final List<String> SUBCOMMANDS = Arrays.asList("wireframe", "fog", "minimap", "flyby", "help");
private static final List<String> SUBCOMMANDS = Arrays.asList("wireframe", "fog", "minimap", "flyby", "profile", "help");
@Override
public String getCommandName() {
@@ -29,12 +32,29 @@ public class AngelicaCommand extends CommandBase {
@Override
public String getCommandUsage(ICommandSender sender) {
return "/angelica <wireframe|fog|minimap|flyby|help>";
return "/angelica <wireframe|fog|minimap|flyby|profile|help>";
}
@Override
public int getRequiredPermissionLevel() {
return 2; // op
return 0;
}
@Override
public boolean canCommandSenderUseCommand(ICommandSender sender) {
return true;
}
static boolean requiresCheats(String subcommand) {
return "wireframe".equals(subcommand) || "flyby".equals(subcommand);
}
private static boolean cheatsAllowed() {
if (SystemProperties.debugTooling()) return true;
final Minecraft mc = Minecraft.getMinecraft();
final IntegratedServer server = mc.getIntegratedServer();
if (server == null || mc.thePlayer == null) return false;
return server.getConfigurationManager().func_152596_g(mc.thePlayer.getGameProfile());
}
@Override
@@ -50,6 +70,9 @@ public class AngelicaCommand extends CommandBase {
options.add("cancel");
return getListOfStringsMatchingLastWord(args, options.toArray(new String[0]));
}
if (args.length == 2 && "profile".equalsIgnoreCase(args[0])) {
return getListOfStringsMatchingLastWord(args, new String[] { "start", "stop", "status" });
}
return new ArrayList<>();
}
@@ -60,11 +83,18 @@ public class AngelicaCommand extends CommandBase {
return;
}
switch (args[0].toLowerCase()) {
final String subcommand = args[0].toLowerCase();
if (requiresCheats(subcommand) && !cheatsAllowed()) {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "[Angelica] " + subcommand + " needs cheats enabled on a world you host"));
return;
}
switch (subcommand) {
case "wireframe" -> handleWireframe(sender);
case "fog" -> handleFog(sender);
case "minimap" -> handleMinimap(sender);
case "flyby" -> handleFlyby(sender, args);
case "profile" -> handleProfile(sender, args);
default -> sendHelp(sender);
}
}
@@ -139,12 +169,58 @@ public class AngelicaCommand extends CommandBase {
+ " (" + route.toTicks(used, speed) + " ticks)"));
}
private void handleProfile(ICommandSender sender, String[] args) {
if (args.length < 2) {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Usage: /angelica profile <start|stop|status>"));
return;
}
switch (args[1].toLowerCase()) {
case "start" -> {
final String opts;
if (args.length > 2) {
final StringBuilder sb = new StringBuilder();
for (int i = 2; i < args.length; i++) {
if (i > 2) sb.append(',');
sb.append(args[i]);
}
opts = sb.toString();
} else {
opts = SystemProperties.PROFILE_OPTS;
}
final String error = AsprofRecorder.start("manual", opts);
if (error != null) {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "[Angelica] " + error));
} else {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Profiling started: " + AsprofRecorder.outputPath()));
}
}
case "stop" -> {
final String path = AsprofRecorder.outputPath();
final String error = AsprofRecorder.stop();
if (error != null) {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "[Angelica] " + error));
} else {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Profile written to " + path));
}
}
case "status" -> {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + AsprofRecorder.status()));
if (AsprofRecorder.isRecording()) {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " " + AsprofRecorder.outputPath()));
}
}
default -> sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Usage: /angelica profile <start|stop|status>"));
}
}
private void sendHelp(ICommandSender sender) {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] Debug Commands:"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica wireframe" + EnumChatFormatting.WHITE + " - Toggle wireframe rendering"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica fog" + EnumChatFormatting.WHITE + " - Toggle fog debug on F3"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica minimap" + EnumChatFormatting.WHITE + " - Toggle chunk debug overlay"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica flyby <" + FlybyRoute.ids() + ">" + EnumChatFormatting.WHITE + " - Run a deterministic benchmark route"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica profile <start|stop|status>" + EnumChatFormatting.WHITE + " - Control async-profiler (JFR) recording"));
}
@Override
@@ -1,28 +1,19 @@
package com.gtnewhorizons.angelica.compat.bop;
import net.minecraft.world.World;
public final class BopFogBlend {
public static final int NO_FOG = -1;
@FunctionalInterface
public interface ColorSource {
/** Packed 0xRRGGBB, or {@link #NO_FOG}. */
int fogColour(int x, int y, int z);
}
private static Object cachedWorld;
private static int cachedGeneration;
private static int cachedX, cachedY, cachedZ, cachedDistance;
private static float interiorR, interiorG, interiorB, interiorWeight;
private BopFogBlend() {}
public static void invalidate() {
cachedWorld = null;
FogBiomeCache.invalidate();
}
public static void accumulate(Object world, int generation, ColorSource source, double posX, double posZ,
int playerX, int playerY, int playerZ, int distance, float[] out) {
public static void accumulate(World world, double posX, double posZ, int playerX, int playerY, int playerZ,
int distance, float[] out) {
out[0] = 0;
out[1] = 0;
@@ -35,63 +26,31 @@ public final class BopFogBlend {
final double zDiffHigh = posZ - playerZ;
if (distance <= 0) {
cell(source, 0, 0, xDiffLow, xDiffHigh, zDiffLow, zDiffHigh, playerX, playerY, playerZ, distance, out);
cell(world, 0, 0, xDiffLow, xDiffHigh, zDiffLow, zDiffHigh, playerX, playerY, playerZ, distance, out);
return;
}
if (world != cachedWorld || generation != cachedGeneration || playerX != cachedX || playerY != cachedY
|| playerZ != cachedZ || distance != cachedDistance) {
recomputeInterior(source, playerX, playerY, playerZ, distance);
cachedWorld = world;
cachedGeneration = generation;
cachedX = playerX;
cachedY = playerY;
cachedZ = playerZ;
cachedDistance = distance;
}
out[0] = interiorR;
out[1] = interiorG;
out[2] = interiorB;
out[3] = interiorWeight;
FogColourGrid.accumulateInterior(world, playerX, playerY, playerZ, distance, out);
for (int z = -distance; z <= distance; ++z) {
cell(source, -distance, z, xDiffLow, xDiffHigh, zDiffLow, zDiffHigh, playerX, playerY, playerZ, distance, out);
cell(source, distance, z, xDiffLow, xDiffHigh, zDiffLow, zDiffHigh, playerX, playerY, playerZ, distance, out);
cell(world, -distance, z, xDiffLow, xDiffHigh, zDiffLow, zDiffHigh, playerX, playerY, playerZ, distance, out);
cell(world, distance, z, xDiffLow, xDiffHigh, zDiffLow, zDiffHigh, playerX, playerY, playerZ, distance, out);
}
for (int x = -distance + 1; x <= distance - 1; ++x) {
cell(source, x, -distance, xDiffLow, xDiffHigh, zDiffLow, zDiffHigh, playerX, playerY, playerZ, distance, out);
cell(source, x, distance, xDiffLow, xDiffHigh, zDiffLow, zDiffHigh, playerX, playerY, playerZ, distance, out);
cell(world, x, -distance, xDiffLow, xDiffHigh, zDiffLow, zDiffHigh, playerX, playerY, playerZ, distance, out);
cell(world, x, distance, xDiffLow, xDiffHigh, zDiffLow, zDiffHigh, playerX, playerY, playerZ, distance, out);
}
}
private static void recomputeInterior(ColorSource source, int playerX, int playerY, int playerZ, int distance) {
float r = 0, g = 0, b = 0, weight = 0;
for (int x = -distance + 1; x <= distance - 1; ++x) {
for (int z = -distance + 1; z <= distance - 1; ++z) {
final int colour = source.fogColour(playerX + x, playerY, playerZ + z);
if (colour == NO_FOG) continue;
r += (colour & 0xFF0000) >> 16;
g += (colour & 0x00FF00) >> 8;
b += colour & 0x0000FF;
weight += 1;
}
}
interiorR = r;
interiorG = g;
interiorB = b;
interiorWeight = weight;
}
private static void cell(ColorSource source, int x, int z, double xDiffLow, double xDiffHigh, double zDiffLow,
private static void cell(World world, int x, int z, double xDiffLow, double xDiffHigh, double zDiffLow,
double zDiffHigh, int playerX, int playerY, int playerZ, int distance, float[] out) {
final int colour = source.fogColour(playerX + x, playerY, playerZ + z);
if (colour == NO_FOG) return;
final int color = FogColourGrid.colour(world, playerX + x, playerY, playerZ + z);
if (color == NO_FOG) return;
float rPart = (colour & 0xFF0000) >> 16;
float gPart = (colour & 0x00FF00) >> 8;
float bPart = colour & 0x0000FF;
float rPart = (color & 0xFF0000) >> 16;
float gPart = (color & 0x00FF00) >> 8;
float bPart = color & 0x0000FF;
float weightPart = 1;
if (x == -distance) {
@@ -1,30 +0,0 @@
package com.gtnewhorizons.angelica.compat.bop;
import biomesoplenty.client.fog.IBiomeFog;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
public final class BopFogColorSource implements BopFogBlend.ColorSource {
public static final BopFogColorSource INSTANCE = new BopFogColorSource();
private World world;
private BopFogColorSource() {}
public BopFogColorSource forWorld(World world) {
this.world = world;
return this;
}
public void freeWorld() {
this.world = null;
}
@Override
public int fogColour(int x, int y, int z) {
final BiomeGenBase biome = FogBiomeCache.get(world, x, z);
if (!(biome instanceof IBiomeFog fog)) return BopFogBlend.NO_FOG;
return fog.getFogColour(x, y, z) & 0xFFFFFF;
}
}
@@ -10,33 +10,42 @@ public final class FogBiomeCache {
private static final Long2ObjectOpenHashMap<BiomeGenBase> CACHE = new Long2ObjectOpenHashMap<>();
private static World cachedWorld;
private static int generation;
private static boolean lastCacheable;
private FogBiomeCache() {}
public static int generation() {
return generation;
public static boolean lastWasCacheable() {
return lastCacheable;
}
public static void invalidate() {
CACHE.clear();
cachedWorld = null;
lastCacheable = false;
FogColourGrid.invalidate();
}
public static BiomeGenBase get(World world, int x, int z) {
static void useWorld(World world) {
if (world != cachedWorld) {
CACHE.clear();
cachedWorld = world;
generation++;
FogColourGrid.invalidate();
}
}
public static BiomeGenBase get(World world, int x, int z) {
useWorld(world);
final long key = ((long) x << 32) | (z & 0xFFFFFFFFL);
BiomeGenBase biome = CACHE.get(key);
if (biome == null) {
biome = world.getBiomeGenForCoords(x, z);
if (world.getChunkProvider().chunkExists(x >> 4, z >> 4)) {
lastCacheable = world.getChunkProvider().chunkExists(x >> 4, z >> 4);
if (lastCacheable) {
if (CACHE.size() >= MAX_ENTRIES) CACHE.clear();
CACHE.put(key, biome);
generation++;
}
} else {
lastCacheable = true;
}
return biome;
}
@@ -0,0 +1,116 @@
package com.gtnewhorizons.angelica.compat.bop;
import biomesoplenty.client.fog.IBiomeFog;
import java.util.Arrays;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
public final class FogColourGrid {
public static final int NO_FOG = BopFogBlend.NO_FOG;
private static final int SIZE = 128;
private static final int MASK = SIZE - 1;
private static final long EMPTY = Long.MIN_VALUE;
private static final FogColourGrid INSTANCE = new FogColourGrid();
private final long[] keys = new long[SIZE * SIZE];
private final int[] colours = new int[SIZE * SIZE];
private World gridWorld;
private boolean lastCacheable;
private boolean interiorValid;
private int interiorX;
private int interiorY;
private int interiorZ;
private int interiorDistance;
private float interiorR;
private float interiorG;
private float interiorB;
private float interiorWeight;
private FogColourGrid() {
reset();
}
public static void invalidate() {
INSTANCE.reset();
}
private void reset() {
Arrays.fill(keys, EMPTY);
gridWorld = null;
lastCacheable = false;
interiorValid = false;
}
private void useWorld(World world) {
FogBiomeCache.useWorld(world);
if (world != gridWorld) {
reset();
gridWorld = world;
}
}
public static int colour(World world, int x, int y, int z) {
INSTANCE.useWorld(world);
return INSTANCE.lookup(world, x, y, z);
}
static void accumulateInterior(World world, int x, int y, int z, int distance, float[] out) {
INSTANCE.useWorld(world);
if (!INSTANCE.interiorValid || INSTANCE.interiorX != x || INSTANCE.interiorY != y || INSTANCE.interiorZ != z || INSTANCE.interiorDistance != distance) {
INSTANCE.rebuildInterior(world, x, y, z, distance);
}
out[0] = INSTANCE.interiorR;
out[1] = INSTANCE.interiorG;
out[2] = INSTANCE.interiorB;
out[3] = INSTANCE.interiorWeight;
}
private void rebuildInterior(World world, int playerX, int playerY, int playerZ, int distance) {
float r = 0, g = 0, b = 0, weight = 0;
boolean cacheable = true;
interiorValid = false;
for (int x = -distance + 1; x <= distance - 1; ++x) {
for (int z = -distance + 1; z <= distance - 1; ++z) {
final int color = lookup(world, playerX + x, playerY, playerZ + z);
cacheable &= lastCacheable;
if (color == NO_FOG) continue;
r += (color & 0xFF0000) >> 16;
g += (color & 0x00FF00) >> 8;
b += color & 0x0000FF;
weight += 1;
}
}
interiorR = r;
interiorG = g;
interiorB = b;
interiorWeight = weight;
interiorX = playerX;
interiorY = playerY;
interiorZ = playerZ;
interiorDistance = distance;
interiorValid = cacheable;
}
private int lookup(World world, int x, int y, int z) {
final long key = ((long) (x & 0x3FFFFFF) << 35) | ((long) (z & 0x3FFFFFF) << 9) | (y & 0x1FF);
final int idx = ((x & MASK) << 7) | (z & MASK);
if (keys[idx] == key) {
lastCacheable = true;
return colours[idx];
}
final BiomeGenBase biome = FogBiomeCache.get(world, x, z);
final int result = (biome instanceof IBiomeFog fog) ? (fog.getFogColour(x, y, z) & 0xFFFFFF) : NO_FOG;
lastCacheable = FogBiomeCache.lastWasCacheable();
if (lastCacheable) {
keys[idx] = key;
colours[idx] = result;
}
return result;
}
}
@@ -1,434 +1,284 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.rendering.RenderFailures;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
import com.gtnewhorizons.angelica.api.tesr.TesrShader;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.rendering.tesr.DrawState;
import com.gtnewhorizons.angelica.shadercompat.ShaderGlint;
import it.unimi.dsi.fastutil.Hash;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import lombok.Getter;
import net.coderbot.batchedentityrendering.impl.BatchVertexFormats;
import net.coderbot.batchedentityrendering.impl.BlendingStateHolder;
import net.coderbot.batchedentityrendering.impl.TransparencyType;
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
import com.gtnewhorizons.angelica.shadercompat.ShaderGlint;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.layer.PassOverride;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
import javax.annotation.Nullable;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris)
private static final RenderLayer SOLID = of("solid", DefaultVertexFormat.POSITION_COLOR_TEXTURE_LIGHT_NORMAL, 7, 2097152, true, false, RenderLayer.MultiPhaseParameters.builder().shadeModel(SMOOTH_SHADE_MODEL).lightmap(ENABLE_LIGHTMAP).texture(MIPMAP_BLOCK_ATLAS_TEXTURE).build(true));
private static final RenderLayer CUTOUT = of("cutout", DefaultVertexFormat.POSITION_COLOR_TEXTURE_LIGHT_NORMAL, 7, 131072, true, false, RenderLayer.MultiPhaseParameters.builder().shadeModel(SMOOTH_SHADE_MODEL).lightmap(ENABLE_LIGHTMAP).texture(BLOCK_ATLAS_TEXTURE).alpha(HALF_ALPHA).build(true));
private static final RenderLayer TRANSLUCENT = of("translucent", DefaultVertexFormat.POSITION_COLOR_TEXTURE_LIGHT_NORMAL, 7, 262144, true, true, createTranslucentPhaseData());
public final class RenderLayer implements BlendingStateHolder {
@Getter
private final VertexFormat vertexFormat;
@Getter
private final int drawMode;
@Getter
private final int expectedBufferSize;
private static final int TESR_BUFFER_SIZE = 65536;
public RenderLayer(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, Runnable startAction, Runnable endAction) {
super(name, startAction, endAction);
private static final ObjectOpenCustomHashSet<RenderLayer> CACHE = new ObjectOpenCustomHashSet<>(HashStrategy.INSTANCE);
static final class Hook {
final Runnable begin;
final Runnable end;
Hook(Runnable begin, Runnable end) {
this.begin = begin;
this.end = end;
}
}
private static final Hook[] GLINT_HOOKS = new Hook[ShaderGlint.TINT_SLOTS + 1];
private static final Hook BEACON_BEAM_HOOK = new Hook(
() -> GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.BEACON_BEAM),
GbufferPrograms::teardownSpecialRenderCondition);
private static final Reference2ObjectOpenHashMap<TesrShader, Hook> SHADER_HOOKS = new Reference2ObjectOpenHashMap<>();
private static final Reference2ObjectOpenHashMap<PassOverride, Hook> PASS_HOOKS = new Reference2ObjectOpenHashMap<>();
private String name;
@Getter private final VertexFormat vertexFormat;
@Getter private final int drawMode;
@Getter private final int expectedBufferSize;
@Getter private final DrawState state;
@Getter private final ResourceLocation textureId;
@Getter private final boolean unfilteredAtlas;
private final TransparencyType transparencyType;
private final Hook hook;
private final boolean noPass;
private final int hash;
private RenderLayer(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, DrawState state,
ResourceLocation textureId, boolean unfilteredAtlas, TransparencyType transparencyType, Hook hook,
boolean noPass) {
this.name = name;
this.vertexFormat = vertexFormat;
this.drawMode = drawMode;
this.expectedBufferSize = expectedBufferSize;
this.state = state;
this.textureId = textureId;
this.unfilteredAtlas = unfilteredAtlas;
this.transparencyType = transparencyType;
this.hook = hook;
this.noPass = noPass;
int h = System.identityHashCode(vertexFormat);
h = h * 31 + drawMode;
h = h * 31 + expectedBufferSize;
h = h * 31 + System.identityHashCode(state);
h = h * 31 + Objects.hashCode(textureId);
h = h * 31 + (unfilteredAtlas ? 1 : 0);
h = h * 31 + transparencyType.ordinal();
h = h * 31 + System.identityHashCode(hook);
h = h * 31 + (noPass ? 1 : 0);
this.hash = h;
}
public static MultiPhase of(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, MultiPhaseParameters phaseData) {
return of(name, vertexFormat, drawMode, expectedBufferSize, false, false, phaseData);
public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit) {
return build(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cull, lit, false);
}
public static MultiPhase of(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent, MultiPhaseParameters phases) {
return RenderLayer.MultiPhase.of(name, vertexFormat, drawMode, expectedBufferSize, hasCrumbling, translucent, phases);
public static RenderLayer tesrNoPass(ResourceLocation texture, TesrMaterial material, int cull, boolean lit) {
return build(texture, material, PassOverride.NONE, 0.0f, 0.0f, ShaderGlint.NO_TINT, cull, lit, true);
}
public static RenderLayer solid() {
return SOLID;
}
public static RenderLayer cutout() {
return CUTOUT;
}
private static MultiPhaseParameters createTranslucentPhaseData() {
return RenderLayer.MultiPhaseParameters.builder().shadeModel(SMOOTH_SHADE_MODEL).lightmap(ENABLE_LIGHTMAP).texture(MIPMAP_BLOCK_ATLAS_TEXTURE).transparency(TRANSLUCENT_TRANSPARENCY).target(TRANSLUCENT_TARGET).build(true);
}
public static RenderLayer translucent() {
return TRANSLUCENT;
}
private static MultiPhaseParameters.Builder tesrMaterialPhases(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot) {
final MultiPhaseParameters.Builder b = MultiPhaseParameters.builder();
b.texture(texture != null ? new RenderPhase.Texture(texture, false, false, material.isUnfilteredAtlas()) : NO_TEXTURE);
if (material.isNoCull()) {
b.cull(DISABLE_CULLING);
private static RenderLayer build(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit, boolean noPass) {
final DrawState state = DrawState.forMaterial(material, cull, lit, offsetFactor, offsetUnits);
final Hook hook = hookFor(material, noPass ? PassOverride.NONE : pass, glintSlot);
final TransparencyType transparency = transparencyFor(state.getBlend());
final RenderLayer candidate = new RenderLayer(null, BatchVertexFormats.POSITION_COLOR_TEXTURE_LIGHTF_NORMAL,
GL11.GL_QUADS, TESR_BUFFER_SIZE, state, texture, material.isUnfilteredAtlas(), transparency, hook,
noPass);
final RenderLayer retained = intern(candidate);
if (retained == candidate) {
retained.name = "angelica_tesr_" + (noPass ? "nopass_" : "")
+ material.transparency().name().toLowerCase(Locale.ROOT) + (noPass ? "" : pass.nameSuffix())
+ (offsetFactor == 0.0f && offsetUnits == 0.0f ? "" : "_offset" + offsetFactor + "_" + offsetUnits)
+ "_cull" + state.getCull() + (state.isLit() ? "_lit" : "")
+ (glintSlot == ShaderGlint.NO_TINT ? "" : "_tint" + glintSlot);
}
if (material.isUnlit()) {
b.texturing(NO_FFP_LIGHTING);
return retained;
}
private static RenderLayer intern(RenderLayer layer) {
return CACHE.addOrGet(layer);
}
public static void clearInterningAndHooks() {
CACHE.clear();
SHADER_HOOKS.clear();
PASS_HOOKS.clear();
for (int i = 0; i < GLINT_HOOKS.length; i++) {
GLINT_HOOKS[i] = null;
}
if (material.isNoDepthWrite()) {
b.writeMaskState(COLOR_MASK);
}
if (material.isDepthOnly()) {
b.writeMaskState(DEPTH_MASK);
}
b.alpha(material.cutoutAlpha() > 0 ? new RenderPhase.Alpha(material.cutoutAlpha()) : ZERO_ALPHA);
b.depthTest(material.isDepthEqual() ? EQUAL_DEPTH_TEST : LEQUAL_DEPTH_TEST);
switch (material.transparency()) {
case TRANSLUCENT -> b.transparency(TRANSLUCENT_TRANSPARENCY);
case ADDITIVE -> b.transparency(ADDITIVE_TRANSPARENCY);
case ADDITIVE_ALPHA -> b.transparency(LIGHTNING_TRANSPARENCY);
case GLINT -> b.transparency(GLINT_TRANSPARENCY);
case OPAQUE -> b.transparency(NO_TRANSPARENCY);
}
boolean shaderPhaseTaken = true;
}
public static int cacheSize() {
return CACHE.size();
}
private static TransparencyType transparencyFor(int blend) {
return switch (blend) {
case DrawState.OPAQUE -> TransparencyType.OPAQUE;
case DrawState.GLINT -> TransparencyType.DECAL;
default -> TransparencyType.GENERAL_TRANSPARENT;
};
}
private static Hook hookFor(TesrMaterial material, PassOverride pass, int glintSlot) {
switch (material.special()) {
case GLINT -> b.shader(glintShader(glintSlot));
case BEACON_BEAM -> b.shader(SPECIAL_BEACON_BEAM);
case GLINT -> {
return glintHook(glintSlot);
}
case BEACON_BEAM -> {
return BEACON_BEAM_HOOK;
}
case NONE -> {
final TesrShader shader = material.shader();
if (shader != null) {
b.shader(new RenderPhase.Shader("angelica_tesr_shader_" + shader.name(), shader.bind(), shader.release()));
} else {
shaderPhaseTaken = false;
return shaderHook(shader);
}
}
}
if (!shaderPhaseTaken && pass != PassOverride.NONE) {
b.shader(new RenderPhase.Shader("angelica_tesr_pass" + pass.nameSuffix(), pass::apply, pass::clear));
return pass == PassOverride.NONE ? null : passHook(pass);
}
private static Hook glintHook(int slot) {
Hook hook = GLINT_HOOKS[slot + 1];
if (hook == null) {
hook = new Hook(() -> {
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.GLINT);
ShaderGlint.bindTintedGlint(slot);
}, GbufferPrograms::teardownSpecialRenderCondition);
GLINT_HOOKS[slot + 1] = hook;
}
b.layering(polygonOffset(offsetFactor, offsetUnits));
return b;
return hook;
}
private static final RenderPhase.Shader[] GLINT_SHADERS = new RenderPhase.Shader[ShaderGlint.TINT_SLOTS + 1];
private static RenderPhase.Shader glintShader(int slot) {
RenderPhase.Shader shader = GLINT_SHADERS[slot + 1];
if (shader == null) {
shader = new RenderPhase.Shader("angelica_special_glint_" + slot,
() -> {
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.GLINT);
ShaderGlint.bindTintedGlint(slot);
},
GbufferPrograms::teardownSpecialRenderCondition);
GLINT_SHADERS[slot + 1] = shader;
private static Hook shaderHook(TesrShader shader) {
Hook hook = SHADER_HOOKS.get(shader);
if (hook == null) {
hook = new Hook(shader.bind(), shader.release());
SHADER_HOOKS.put(shader, hook);
}
return shader;
}
private static final RenderPhase.Shader SPECIAL_BEACON_BEAM = new RenderPhase.Shader("angelica_special_beacon_beam",
() -> GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.BEACON_BEAM),
GbufferPrograms::teardownSpecialRenderCondition);
public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material) {
return tesr(texture, material, PassOverride.NONE, 0.0f, 0.0f, ShaderGlint.NO_TINT);
return hook;
}
public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) {
return tesr(texture, material, pass, offsetFactor, offsetUnits, ShaderGlint.NO_TINT);
}
public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot) {
final MultiPhaseParameters.Builder b = tesrMaterialPhases(texture, material, pass, offsetFactor, offsetUnits, glintSlot).shadeModel(SMOOTH_SHADE_MODEL);
final boolean sortsTranslucent = material.transparency() != TesrMaterial.Transparency.OPAQUE;
return of("angelica_tesr_" + material.transparency().name().toLowerCase(Locale.ROOT) + pass.nameSuffix()
+ (offsetFactor == 0.0f && offsetUnits == 0.0f ? "" : "_offset" + offsetFactor + "_" + offsetUnits),
BatchVertexFormats.POSITION_COLOR_TEXTURE_LIGHTF_NORMAL, GL11.GL_QUADS, 65536, true, sortsTranslucent, b.build(false));
}
public static RenderLayer tesrNoPass(ResourceLocation texture, TesrMaterial material) {
return of("angelica_tesr_nopass_" + material.transparency().name().toLowerCase(Locale.ROOT),
BatchVertexFormats.POSITION_COLOR_TEXTURE_LIGHTF_NORMAL, GL11.GL_QUADS, 65536, true, false,
tesrMaterialPhases(texture, material, PassOverride.NONE, 0.0f, 0.0f, ShaderGlint.NO_TINT).build(false));
}
public static RenderLayer getOutline(ResourceLocation texture, RenderPhase.Cull cull) {
return of("outline", DefaultVertexFormat.POSITION_COLOR_TEXTURE, 7, 256, RenderLayer.MultiPhaseParameters.builder().texture(new RenderPhase.Texture(texture, false, false)).cull(cull).depthTest(ALWAYS_DEPTH_TEST).alpha(ONE_TENTH_ALPHA).fog(NO_FOG).target(OUTLINE_TARGET).build(RenderLayer.OutlineMode.IS_OUTLINE));
}
public int mode() {
return this.drawMode;
}
public ResourceLocation getTextureId() {
return null;
}
static final class MultiPhase extends RenderLayer implements BlendingStateHolder {
private static final ObjectOpenCustomHashSet<MultiPhase> CACHE;
private final MultiPhaseParameters phases;
private final int hash;
private final Optional<RenderLayer> affectedOutline;
@Override
public TransparencyType getTransparencyType() {
return phases.transparency == null ? TransparencyType.OPAQUE : phases.transparency.getTransparencyType();
private static Hook passHook(PassOverride pass) {
Hook hook = PASS_HOOKS.get(pass);
if (hook == null) {
hook = new Hook(pass::apply, pass::clear);
PASS_HOOKS.put(pass, hook);
}
return hook;
}
private MultiPhase(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent, MultiPhaseParameters phases) {
super(name, vertexFormat, drawMode, expectedBufferSize, () -> {
final ImmutableList<RenderPhase> p = phases.phases;
for (int i = 0, n = p.size(); i < n; i++) {
p.get(i).startDrawing();
@Override
public TransparencyType getTransparencyType() {
return transparencyType;
}
public void startDrawing() {
boolean filtering = false;
boolean hookActive = false;
try {
state.apply();
if (textureId != null) {
GLStateManager.enableTexture();
Minecraft.getMinecraft().getTextureManager().bindTexture(textureId);
if (unfilteredAtlas) {
filtering = true;
TextureUtil.func_152777_a(false, false, 1.0F);
}
}, () -> {
final ImmutableList<RenderPhase> p = phases.phases;
for (int i = 0, n = p.size(); i < n; i++) {
p.get(i).endDrawing();
}
});
this.phases = phases;
this.affectedOutline = phases.outlineMode == RenderLayer.OutlineMode.AFFECTS_OUTLINE && phases.texture != null ? phases.texture.getId().map((arg2) -> {
return getOutline(arg2, phases.cull);
}) : Optional.empty();
this.hash = Objects.hash(new Object[]{super.hashCode(), phases});
}
public static MultiPhase of(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent,
MultiPhaseParameters phases) {
return (MultiPhase)CACHE.addOrGet(new MultiPhase(name, vertexFormat, drawMode, expectedBufferSize, hasCrumbling, translucent, phases));
}
public Optional<RenderLayer> getAffectedOutline() {
return this.affectedOutline;
}
@Override
public ResourceLocation getTextureId() {
return phases.texture == null ? null : phases.texture.getId().orElse(null);
}
@Override
public boolean equals(@Nullable Object object) {
return this == object;
}
@Override
public int hashCode() {
return this.hash;
}
@Override
public String toString() {
return "RenderType[" + this.phases + ']';
}
static {
CACHE = new ObjectOpenCustomHashSet<>(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
}
static enum HashStrategy implements Hash.Strategy<MultiPhase> {
INSTANCE;
private HashStrategy() {
}
public int hashCode(@Nullable MultiPhase arg) {
return arg == null ? 0 : arg.hash;
}
public boolean equals(@Nullable MultiPhase arg, @Nullable MultiPhase arg2) {
if (arg == arg2) {
return true;
} else {
return arg != null && arg2 != null && arg.name.equals(arg2.name)
&& arg.getVertexFormat() == arg2.getVertexFormat() && arg.getDrawMode() == arg2.getDrawMode()
&& Objects.equals(arg.phases, arg2.phases);
}
}
}
}
public static final class MultiPhaseParameters {
private final RenderPhase.Texture texture;
private final RenderPhase.Transparency transparency;
private final RenderPhase.DiffuseLighting diffuseLighting;
private final RenderPhase.ShadeModel shadeModel;
private final RenderPhase.Alpha alpha;
private final RenderPhase.DepthTest depthTest;
private final RenderPhase.Cull cull;
private final RenderPhase.Lightmap lightmap;
private final RenderPhase.Fog fog;
private final RenderPhase.Layering layering;
private final RenderPhase.Target target;
private final RenderPhase.Texturing texturing;
private final RenderPhase.WriteMaskState writeMaskState;
private final RenderPhase.Shader shader;
private final OutlineMode outlineMode;
private final ImmutableList<RenderPhase> phases;
private MultiPhaseParameters(RenderPhase.Texture texture, RenderPhase.Transparency transparency, RenderPhase.DiffuseLighting diffuseLighting, RenderPhase.ShadeModel shadeModel, RenderPhase.Alpha alpha, RenderPhase.DepthTest depthTest, RenderPhase.Cull cull, RenderPhase.Lightmap lightmap, RenderPhase.Fog fog, RenderPhase.Layering layering, RenderPhase.Target target, RenderPhase.Texturing texturing, RenderPhase.WriteMaskState writeMaskState, RenderPhase.Shader shader, OutlineMode outlineMode) {
this.texture = texture;
this.transparency = transparency;
this.diffuseLighting = diffuseLighting;
this.shadeModel = shadeModel;
this.alpha = alpha;
this.depthTest = depthTest;
this.cull = cull;
this.lightmap = lightmap;
this.fog = fog;
this.layering = layering;
this.target = target;
this.texturing = texturing;
this.writeMaskState = writeMaskState;
this.shader = shader;
this.outlineMode = outlineMode;
final ImmutableList.Builder<RenderPhase> declared = ImmutableList.builder();
for (RenderPhase phase : new RenderPhase[] { this.texture, this.transparency, this.diffuseLighting,
this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering,
this.target, this.texturing, this.writeMaskState, this.shader }) {
if (phase != null) declared.add(phase);
}
this.phases = declared.build();
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
} else if (object != null && this.getClass() == object.getClass()) {
MultiPhaseParameters rendertype$state = (MultiPhaseParameters)object;
return this.outlineMode == rendertype$state.outlineMode && this.phases.equals(rendertype$state.phases);
} else {
return false;
GLStateManager.disableTexture();
}
}
@Override
public int hashCode() {
return Objects.hash(new Object[]{this.phases, this.outlineMode});
}
@Override
public String toString() {
return "CompositeState[" + this.phases + ", outlineProperty=" + this.outlineMode + ']';
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private RenderPhase.Texture texture;
private RenderPhase.Transparency transparency;
private RenderPhase.DiffuseLighting diffuseLighting;
private RenderPhase.ShadeModel shadeModel;
private RenderPhase.Alpha alpha;
private RenderPhase.DepthTest depthTest;
private RenderPhase.Cull cull;
private RenderPhase.Lightmap lightmap;
private RenderPhase.Fog fog;
private RenderPhase.Layering layering;
private RenderPhase.Target target;
private RenderPhase.Texturing texturing;
private RenderPhase.WriteMaskState writeMaskState;
private RenderPhase.Shader shader;
private Builder() {}
public Builder texture(RenderPhase.Texture texture) {
this.texture = texture;
return this;
if (hook != null) {
hookActive = true;
hook.begin.run();
}
public Builder transparency(RenderPhase.Transparency transparency) {
this.transparency = transparency;
return this;
} catch (Throwable failure) {
if (hookActive) {
try {
hook.end.run();
} catch (Throwable cleanup) {
RenderFailures.suppress(failure, cleanup);
}
}
public void layering(Layering layering) {
this.layering = layering;
}
public Builder shadeModel(RenderPhase.ShadeModel shadeModel) {
this.shadeModel = shadeModel;
return this;
}
public Builder alpha(RenderPhase.Alpha alpha) {
this.alpha = alpha;
return this;
}
public Builder depthTest(RenderPhase.DepthTest depthTest) {
this.depthTest = depthTest;
return this;
}
public Builder cull(RenderPhase.Cull cull) {
this.cull = cull;
return this;
}
public Builder lightmap(RenderPhase.Lightmap lightmap) {
this.lightmap = lightmap;
return this;
}
public Builder fog(RenderPhase.Fog fog) {
this.fog = fog;
return this;
}
public Builder target(RenderPhase.Target target) {
this.target = target;
return this;
}
public Builder texturing(RenderPhase.Texturing texturing) {
this.texturing = texturing;
return this;
}
public Builder writeMaskState(RenderPhase.WriteMaskState writeMaskState) {
this.writeMaskState = writeMaskState;
return this;
}
public Builder shader(RenderPhase.Shader shader) {
this.shader = shader;
return this;
}
public MultiPhaseParameters build(boolean affectsOutline) {
return this.build(affectsOutline ? RenderLayer.OutlineMode.AFFECTS_OUTLINE : RenderLayer.OutlineMode.NONE);
}
public MultiPhaseParameters build(OutlineMode outlineMode) {
return new MultiPhaseParameters(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering, this.target, this.texturing, this.writeMaskState, this.shader, outlineMode);
if (filtering) {
try {
TextureUtil.func_147945_b();
} catch (Throwable cleanup) {
RenderFailures.suppress(failure, cleanup);
}
}
throw failure;
}
}
enum OutlineMode {
NONE("none"),
IS_OUTLINE("is_outline"),
AFFECTS_OUTLINE("affects_outline");
private final String name;
OutlineMode(String name) {
this.name = name;
public void endDrawing() {
Throwable failure = null;
try {
if (hook != null) hook.end.run();
} catch (Throwable t) {
failure = t;
} finally {
if (textureId != null && unfilteredAtlas) {
try {
TextureUtil.func_147945_b();
} catch (Throwable cleanup) {
failure = RenderFailures.suppress(failure, cleanup);
}
}
}
if (failure != null) RenderFailures.rethrow(failure);
}
@Override
public String toString() {
return this.name;
}
@Override
public boolean equals(@Nullable Object object) {
return this == object;
}
@Override
public int hashCode() {
return hash;
}
@Override
public String toString() {
return "RenderLayer[" + name + ", " + state + ", texture=" + textureId + ']';
}
private enum HashStrategy implements Hash.Strategy<RenderLayer> {
INSTANCE;
@Override
public int hashCode(@Nullable RenderLayer layer) {
return layer == null ? 0 : layer.hash;
}
@Override
public boolean equals(@Nullable RenderLayer a, @Nullable RenderLayer b) {
if (a == b) return true;
if (a == null || b == null) return false;
return a.vertexFormat == b.vertexFormat && a.drawMode == b.drawMode
&& a.expectedBufferSize == b.expectedBufferSize && a.state == b.state
&& Objects.equals(a.textureId, b.textureId) && a.unfilteredAtlas == b.unfilteredAtlas
&& a.transparencyType == b.transparencyType && a.hook == b.hook
&& a.noPass == b.noPass;
}
}
}
@@ -1,569 +0,0 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.coderbot.batchedentityrendering.impl.TransparencyType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Optional;
public abstract class RenderPhase {
protected final String name;
protected Runnable beginAction;
private final Runnable endAction;
protected static final Transparency NO_TRANSPARENCY = new Transparency("no_transparency", TransparencyType.OPAQUE, GLStateManager::disableBlend, () -> {
});
protected static final Transparency ADDITIVE_TRANSPARENCY = new Transparency("additive_transparency", () -> {
GLStateManager.enableBlend();
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
}, () -> {
GLStateManager.disableBlend();
GLStateManager.defaultBlendFunc();
});
protected static final Transparency LIGHTNING_TRANSPARENCY = new Transparency("lightning_transparency", () -> {
GLStateManager.enableBlend();
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
}, () -> {
GLStateManager.disableBlend();
GLStateManager.defaultBlendFunc();
});
protected static final Transparency GLINT_TRANSPARENCY = new Transparency("glint_transparency", TransparencyType.DECAL, () -> {
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_COLOR, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE);
}, () -> {
GLStateManager.disableBlend();
GLStateManager.defaultBlendFunc();
});
protected static final Transparency CRUMBLING_TRANSPARENCY = new Transparency("crumbling_transparency", TransparencyType.DECAL, () -> {
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_SRC_COLOR, GL11.GL_ONE, GL11.GL_ZERO);
}, () -> {
GLStateManager.disableBlend();
GLStateManager.defaultBlendFunc();
});
protected static final Transparency TRANSLUCENT_TRANSPARENCY = new Transparency("translucent_transparency", () -> {
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
}, () -> {
GLStateManager.disableBlend();
GLStateManager.defaultBlendFunc();
});
protected static final Alpha ZERO_ALPHA = new Alpha(0.0F);
protected static final Alpha ONE_TENTH_ALPHA = new Alpha(0.003921569F);
protected static final Alpha HALF_ALPHA = new Alpha(0.5F);
protected static final ShadeModel SHADE_MODEL = new ShadeModel(false);
protected static final ShadeModel SMOOTH_SHADE_MODEL = new ShadeModel(true);
protected static final Texture MIPMAP_BLOCK_ATLAS_TEXTURE;
protected static final Texture BLOCK_ATLAS_TEXTURE;
protected static final Texture NO_TEXTURE;
protected static final Texturing DEFAULT_TEXTURING;
protected static final Texturing NO_FFP_LIGHTING;
protected static final Lightmap ENABLE_LIGHTMAP;
protected static final Lightmap DISABLE_LIGHTMAP;
protected static final DiffuseLighting ENABLE_DIFFUSE_LIGHTING;
protected static final DiffuseLighting DISABLE_DIFFUSE_LIGHTING;
protected static final Cull ENABLE_CULLING;
protected static final Cull DISABLE_CULLING;
protected static final DepthTest ALWAYS_DEPTH_TEST;
protected static final DepthTest EQUAL_DEPTH_TEST;
protected static final DepthTest LEQUAL_DEPTH_TEST;
protected static final WriteMaskState ALL_MASK;
protected static final WriteMaskState COLOR_MASK;
protected static final WriteMaskState DEPTH_MASK;
protected static final Layering NO_LAYERING;
protected static final Fog NO_FOG;
protected static final Fog FOG;
protected static final Fog BLACK_FOG;
protected static final Target MAIN_TARGET;
protected static final Target OUTLINE_TARGET;
protected static final Target TRANSLUCENT_TARGET;
protected static final Shader NO_SHADER = new Shader("no_shader", () -> {}, () -> {});
private static final Map<Long, Layering> POLYGON_OFFSET_LAYERINGS = new ConcurrentHashMap<>();
public static Layering polygonOffset(float factor, float units) {
if (factor == 0.0f && units == 0.0f) {
return NO_LAYERING;
}
final long key = ((long) Float.floatToIntBits(factor) << 32) | (Float.floatToIntBits(units) & 0xFFFFFFFFL);
return POLYGON_OFFSET_LAYERINGS.computeIfAbsent(key, k -> new Layering("polygon_offset_" + factor + "_" + units, () -> {
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
GLStateManager.glPolygonOffset(factor, units);
}, () -> {
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
GLStateManager.glPolygonOffset(0.0f, 0.0f);
}));
}
public RenderPhase(String name, Runnable beginAction, Runnable endAction) {
this.name = name;
this.beginAction = beginAction;
this.endAction = endAction;
}
public void startDrawing() {
this.beginAction.run();
}
public void endDrawing() {
this.endAction.run();
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
} else if (object != null && this.getClass() == object.getClass()) {
RenderPhase lv = (RenderPhase)object;
return this.name.equals(lv.name);
} else {
return false;
}
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public String toString() {
return this.name;
}
private static ResourceLocation ATLAS = TextureMap.locationBlocksTexture;
static {
// TODO: Sodium - SpriteAtlasTexture
MIPMAP_BLOCK_ATLAS_TEXTURE = new Texture(ATLAS, false, true);
BLOCK_ATLAS_TEXTURE = new Texture(ATLAS, false, false);
NO_TEXTURE = new Texture();
DEFAULT_TEXTURING = new Texturing("default_texturing", () -> {
}, () -> {
});
NO_FFP_LIGHTING = new Texturing("no_ffp_lighting", GLStateManager::disableLighting, GLStateManager::enableLighting);
ENABLE_LIGHTMAP = new Lightmap(true);
DISABLE_LIGHTMAP = new Lightmap(false);
ENABLE_DIFFUSE_LIGHTING = new DiffuseLighting(true);
DISABLE_DIFFUSE_LIGHTING = new DiffuseLighting(false);
ENABLE_CULLING = new Cull(true);
DISABLE_CULLING = new Cull(false);
ALWAYS_DEPTH_TEST = new DepthTest("always", GL11.GL_ALWAYS);
EQUAL_DEPTH_TEST = new DepthTest("==", GL11.GL_EQUAL);
LEQUAL_DEPTH_TEST = new DepthTest("<=", GL11.GL_LEQUAL);
ALL_MASK = new WriteMaskState(true, true);
COLOR_MASK = new WriteMaskState(true, false);
DEPTH_MASK = new WriteMaskState(false, true);
NO_LAYERING = new Layering("no_layering", () -> {}, () -> {});
NO_FOG = new Fog("no_fog", () -> {
}, () -> {
});
FOG = new Fog("fog", GLStateManager::enableFog, GLStateManager::disableFog);
BLACK_FOG = new Fog("black_fog", () -> {
GLStateManager.fogColor(0.0F, 0.0F, 0.0F, 1.0F);
GLStateManager.enableFog();
}, GLStateManager::disableFog);
MAIN_TARGET = new Target("main_target", () -> {
}, () -> {
});
OUTLINE_TARGET = new Target("outline_target", () -> {
// TODO: Sodium - entity outline framebuffer
//MinecraftClient.getInstance().worldRenderer.getEntityOutlinesFramebuffer().beginWrite(false);
}, () -> {
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
});
TRANSLUCENT_TARGET = new Target("translucent_target", () -> {
if (Minecraft.isFancyGraphicsEnabled()) {
// TODO: Sodium - translucent framebuffer
// MinecraftClient.getInstance().worldRenderer.getTranslucentFramebuffer().beginWrite(false);
}
}, () -> {
if (Minecraft.isFancyGraphicsEnabled()) {
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
}
});
}
public static class Target extends RenderPhase {
public Target(String string, Runnable runnable, Runnable runnable2) {
super(string, runnable, runnable2);
}
}
public static class Fog extends RenderPhase {
public Fog(String string, Runnable runnable, Runnable runnable2) {
super(string, runnable, runnable2);
}
}
public static class Layering extends RenderPhase {
public Layering(String string, Runnable runnable, Runnable runnable2) {
super(string, runnable, runnable2);
}
}
public static class WriteMaskState extends RenderPhase {
private final boolean color;
private final boolean depth;
public WriteMaskState(boolean color, boolean depth) {
super("write_mask_state", () -> {
if (!depth) {
GLStateManager.glDepthMask(depth);
}
if (!color) {
GLStateManager.glColorMask(color, color, color, color);
}
}, () -> {
if (!depth) {
GLStateManager.glDepthMask(true);
}
if (!color) {
GLStateManager.glColorMask(true, true, true, true);
}
});
this.color = color;
this.depth = depth;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
} else if (object != null && this.getClass() == object.getClass()) {
WriteMaskState lv = (WriteMaskState)object;
return this.color == lv.color && this.depth == lv.depth;
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hash(this.color, this.depth);
}
@Override
public String toString() {
return this.name + "[writeColor=" + this.color + ", writeDepth=" + this.depth + ']';
}
}
public static class DepthTest extends RenderPhase {
private final String depthFunction;
private final int func;
public DepthTest(String string, int i) {
super("depth_test", () -> {
if (i != GL11.GL_ALWAYS) {
GLStateManager.enableDepthTest();
GLStateManager.glDepthFunc(i);
}
}, () -> {
if (i != GL11.GL_ALWAYS) {
GLStateManager.disableDepthTest();
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
}
});
this.depthFunction = string;
this.func = i;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
} else if (object != null && this.getClass() == object.getClass()) {
DepthTest lv = (DepthTest)object;
return this.func == lv.func;
} else {
return false;
}
}
@Override
public int hashCode() {
return Integer.hashCode(this.func);
}
@Override
public String toString() {
return this.name + '[' + this.depthFunction + ']';
}
}
public static class Cull extends Toggleable {
public Cull(boolean culling) {
super("cull", () -> {
if (!culling) {
GLStateManager.disableCull();
}
}, () -> {
if (!culling) {
GLStateManager.enableCull();
}
}, culling);
}
}
public static class DiffuseLighting extends Toggleable {
public DiffuseLighting(boolean guiLighting) {
super("diffuse_lighting", () -> {
if (guiLighting) {
throw new RuntimeException("Not Implemented Yet");
// net.minecraft.client.drawScreen.DiffuseLighting.enable();
}
}, () -> {
if (guiLighting) {
throw new RuntimeException("Not Implemented Yet");
// net.minecraft.client.drawScreen.DiffuseLighting.disable();
}
}, guiLighting);
}
}
public static class Lightmap extends Toggleable {
public Lightmap(boolean lightmap) {
super("lightmap", () -> {}, () -> {}, lightmap);
}
}
static class Toggleable extends RenderPhase {
private final boolean enabled;
public Toggleable(String string, Runnable runnable, Runnable runnable2, boolean bl) {
super(string, runnable, runnable2);
this.enabled = bl;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
} else if (object != null && this.getClass() == object.getClass()) {
Toggleable lv = (Toggleable)object;
return this.enabled == lv.enabled;
} else {
return false;
}
}
@Override
public int hashCode() {
return Boolean.hashCode(this.enabled);
}
@Override
public String toString() {
return this.name + '[' + this.enabled + ']';
}
}
public static class Shader extends RenderPhase {
public Shader(String name, Runnable bind, Runnable release) {
super(name, bind, release);
}
}
public static class Texturing extends RenderPhase {
public Texturing(String string, Runnable runnable, Runnable runnable2) {
super(string, runnable, runnable2);
}
}
public static class Texture extends RenderPhase {
private final Optional<ResourceLocation> id;
protected boolean bilinear;
protected boolean mipmap;
protected boolean unfilteredAtlas;
public Texture(ResourceLocation id, boolean bilinear, boolean mipmap) {
this(id, bilinear, mipmap, false);
}
public Texture(ResourceLocation id, boolean bilinear, boolean mipmap, boolean unfilteredAtlas) {
super("texture", () -> {
GLStateManager.enableTexture();
TextureManager lv = Minecraft.getMinecraft().getTextureManager();
lv.bindTexture(id);
//GLStateManager.setFilter(bilinear, mipmap); // breaks textures. TODO find out why
if (unfilteredAtlas) {
TextureUtil.func_152777_a(false, false, 1.0F);
}
}, () -> {
if (unfilteredAtlas) {
TextureUtil.func_147945_b();
}
});
this.id = Optional.of(id);
this.bilinear = bilinear;
this.mipmap = mipmap;
this.unfilteredAtlas = unfilteredAtlas;
}
public Texture() {
super("texture", GLStateManager::disableTexture, GLStateManager::enableTexture);
this.id = Optional.empty();
this.bilinear = false;
this.mipmap = false;
this.unfilteredAtlas = false;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
} else if (object != null && this.getClass() == object.getClass()) {
Texture lv = (Texture)object;
return this.id.equals(lv.id) && this.bilinear == lv.bilinear && this.mipmap == lv.mipmap && this.unfilteredAtlas == lv.unfilteredAtlas;
} else {
return false;
}
}
@Override
public int hashCode() {
return this.id.hashCode();
}
@Override
public String toString() {
return this.name + '[' + this.id + "(blur=" + this.bilinear + ", mipmap=" + this.mipmap + ", unfilteredAtlas=" + this.unfilteredAtlas + ")]";
}
protected Optional<ResourceLocation> getId() {
return this.id;
}
}
public static class ShadeModel extends RenderPhase {
private final boolean smooth;
public ShadeModel(boolean smooth) {
super("shade_model",
() -> GLStateManager.glShadeModel(smooth ? GL11.GL_SMOOTH : GL11.GL_FLAT),
() -> GLStateManager.glShadeModel(GL11.GL_FLAT));
this.smooth = smooth;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
} else if (object != null && this.getClass() == object.getClass()) {
ShadeModel lv = (ShadeModel)object;
return this.smooth == lv.smooth;
} else {
return false;
}
}
@Override
public int hashCode() {
return Boolean.hashCode(this.smooth);
}
@Override
public String toString() {
return this.name + '[' + (this.smooth ? "smooth" : "flat") + ']';
}
}
public static class Alpha extends RenderPhase {
private final float alpha;
public Alpha(float alpha) {
super("alpha", () -> {
if (alpha > 0.0F) {
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, alpha);
} else {
GLStateManager.disableAlphaTest();
}
}, () -> {
GLStateManager.disableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
});
this.alpha = alpha;
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
} else if (object != null && this.getClass() == object.getClass()) {
if (!super.equals(object)) {
return false;
} else {
return this.alpha == ((Alpha)object).alpha;
}
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), this.alpha);
}
@Override
public String toString() {
return this.name + '[' + this.alpha + ']';
}
}
public static class Transparency extends RenderPhase {
private final TransparencyType transparencyType;
public Transparency(String string, Runnable runnable, Runnable runnable2) {
this(string, TransparencyType.GENERAL_TRANSPARENT, runnable, runnable2);
}
public Transparency(String string, TransparencyType transparencyType, Runnable runnable, Runnable runnable2) {
super(string, runnable, runnable2);
this.transparencyType = transparencyType;
}
public TransparencyType getTransparencyType() {
return transparencyType;
}
}
}
@@ -87,36 +87,33 @@ public class ChunkDebugMinimap {
// Save GL state
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
GLStateManager.glPushMatrix();
try {
// Setup rendering
GLStateManager.glScaled(2, 2, 1);
GLStateManager.glTranslated(32, 32, 0);
// Setup rendering
GLStateManager.glScaled(2, 2, 1);
GLStateManager.glTranslated(32, 32, 0);
GLStateManager.disableLighting();
GLStateManager.disableTexture();
GLStateManager.disableDepthTest();
GLStateManager.enableBlend();
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLStateManager.disableLighting();
GLStateManager.disableTexture();
GLStateManager.disableDepthTest();
GLStateManager.enableBlend();
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
final Tessellator tess = Tessellator.instance;
tess.startDrawingQuads();
final Tessellator tess = Tessellator.instance;
tess.startDrawingQuads();
final ChunkProviderClient chunkProvider = (ChunkProviderClient) mc.theWorld.getChunkProvider();
final ChunkProviderClient chunkProvider = (ChunkProviderClient) mc.theWorld.getChunkProvider();
for (int x = playerChunkX - RANGE; x < playerChunkX + RANGE; x++) {
for (int z = playerChunkZ - RANGE; z < playerChunkZ + RANGE; z++) {
drawChunk(tess, x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
for (int x = playerChunkX - RANGE; x < playerChunkX + RANGE; x++) {
for (int z = playerChunkZ - RANGE; z < playerChunkZ + RANGE; z++) {
drawChunk(tess, x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
}
}
tess.draw();
} finally {
GLStateManager.glPopMatrix();
GLStateManager.glPopAttrib();
}
tess.draw();
// Restore GL state
GLStateManager.enableLighting();
GLStateManager.enableDepthTest();
GLStateManager.disableBlend();
GLStateManager.glPopMatrix();
GLStateManager.glPopAttrib();
}
private void drawChunk(Tessellator tess, int chunkX, int chunkZ, int playerChunkX, int playerChunkZ, AngelicaRenderSectionManager manager, ChunkProviderClient chunkProvider) {
@@ -2,6 +2,9 @@ package com.gtnewhorizons.angelica.debug.flyby;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.config.SystemProperties.FlybyPacing;
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder;
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder.StopResult;
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder.StopStatus;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
@@ -58,6 +61,10 @@ public final class FlybyRunner {
private boolean waitForFocus;
private boolean exitWhenDone;
private boolean startedFromProperties;
private boolean armed;
private int waitTicks;
private boolean jfr;
private long recordingId;
private final AtomicReference<FlybyRequest> pendingRequest = new AtomicReference<>();
private FlybyRequest activeRequest;
private String[] sceneCommands = NO_COMMANDS;
@@ -133,15 +140,20 @@ public final class FlybyRunner {
this.start(configured, SystemProperties.FLYBY_LENGTH, SystemProperties.FLYBY_WARMUP_TICKS, SystemProperties.FLYBY_SPEED);
this.fixedOrigin = origin;
this.waitForTracy = SystemProperties.FLYBY_WAIT_FOR_TRACY;
if (this.waitForTracy && !Tracy.ENABLED) {
LOGGER.warn("Flyby: waitForTracy requested but Tracy is disabled in this build");
this.waitForTracy = false;
}
this.waitForFocus = SystemProperties.FLYBY_WAIT_FOR_FOCUS;
this.exitWhenDone = SystemProperties.FLYBY_EXIT_WHEN_DONE;
this.jfr = SystemProperties.FLYBY_JFR;
if (!SystemProperties.FLYBY_COMMANDS.isEmpty()) {
this.sceneCommands = FlybyScene.load(SystemProperties.FLYBY_COMMANDS);
LOGGER.info("Flyby scene '{}': {} commands", SystemProperties.FLYBY_COMMANDS, this.sceneCommands.length);
}
LOGGER.info("Flyby started from properties: route={} warmup={} length={} {} ({} ticks) waitForTracy={} waitForFocus={} exitWhenDone={}",
LOGGER.info("Flyby started from properties: route={} warmup={} length={} {} ({} ticks) waitForTracy={} waitForFocus={} exitWhenDone={} jfr={}",
configured.id(), this.warmupTicks, this.runLength, configured.lengthUnit(), this.runTicks,
this.waitForTracy, this.waitForFocus, this.exitWhenDone);
this.waitForTracy, this.waitForFocus, this.exitWhenDone, this.jfr);
}
public void start(FlybyRoute route, int length, int warmupTicks, double speed) {
@@ -156,6 +168,8 @@ public final class FlybyRunner {
this.waitForTracy = false;
this.waitForFocus = false;
this.exitWhenDone = false;
this.jfr = false;
this.armed = false;
this.fixedOrigin = null;
sceneGuarded = false;
this.state = State.WAITING;
@@ -185,6 +199,11 @@ public final class FlybyRunner {
return;
}
if (mc.theWorld == null && this.armed) {
this.restorePauseOnLostFocus(mc);
this.armed = false;
}
if (this.state == State.EXITING) {
if (++this.tick >= EXIT_TICKS) {
this.state = State.DONE;
@@ -198,8 +217,21 @@ public final class FlybyRunner {
switch (this.state) {
case WAITING -> {
if (this.waitForTracy && !Tracy.isConnected()) return;
if (this.waitForFocus && !Display.isActive()) return;
if (!this.armed) {
this.armed = true;
this.waitTicks = 0;
this.overridePauseOnLostFocus(mc);
}
if (mc.currentScreen != null) mc.displayGuiScreen(null);
if (this.waitForTracy && !Tracy.isConnected()) {
if (this.waitTicks++ % 100 == 0) LOGGER.info("Flyby waiting for Tracy connection");
return;
}
if (this.waitForFocus && !Display.isActive()) {
if (this.waitTicks++ % 100 == 0) LOGGER.info("Flyby waiting for window focus");
return;
}
this.begin(mc, player);
}
case PREPARING -> {
@@ -210,6 +242,17 @@ public final class FlybyRunner {
}
case WARMUP -> {
this.applyPosition(player, 0);
if (this.jfr && this.tick == Math.max(0, this.warmupTicks - 40)) {
final String error;
synchronized (AsprofRecorder.class) {
error = AsprofRecorder.start(this.route.id(), SystemProperties.PROFILE_OPTS);
if (error == null) this.recordingId = AsprofRecorder.recordingId();
}
if (error != null) {
LOGGER.warn("Flyby: failed to start async-profiler: {}", error);
player.addChatMessage(new ChatComponentText(EnumChatFormatting.RED + "[Angelica] Flyby: failed to start async-profiler: " + error));
}
}
if (++this.tick >= this.warmupTicks) {
this.tick = 0;
this.beginMeasuring(mc);
@@ -247,15 +290,18 @@ public final class FlybyRunner {
this.lastFrameNs = now;
}
private void overridePauseOnLostFocus(Minecraft mc) {
if (this.pauseOnLostFocusOverridden) return;
this.pauseOnLostFocusSaved = mc.gameSettings.pauseOnLostFocus;
this.pauseOnLostFocusOverridden = true;
mc.gameSettings.pauseOnLostFocus = false;
}
private void begin(Minecraft mc, EntityClientPlayerMP player) {
if (mc.currentScreen != null) {
mc.displayGuiScreen(null);
}
if (!this.pauseOnLostFocusOverridden) {
this.pauseOnLostFocusSaved = mc.gameSettings.pauseOnLostFocus;
this.pauseOnLostFocusOverridden = true;
mc.gameSettings.pauseOnLostFocus = false;
}
this.overridePauseOnLostFocus(mc);
this.parkedX = player.posX;
this.parkedY = player.posY;
@@ -638,6 +684,24 @@ public final class FlybyRunner {
mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + summary));
}
this.stopRecording(mc);
}
private void stopRecording(Minecraft mc) {
final long ownedId = this.recordingId;
this.recordingId = 0;
final StopResult result = AsprofRecorder.stopIfRecording(ownedId);
if (result.status() == StopStatus.NO_MATCH) return;
final boolean failed = result.status() == StopStatus.FAILED;
final String message = failed ? "Flyby: failed to stop async-profiler: " + result.error()
: "Flyby: profile written to " + result.path();
if (failed) LOGGER.warn(message);
else LOGGER.info(message);
if (mc.thePlayer != null) {
mc.thePlayer.addChatMessage(new ChatComponentText((failed ? EnumChatFormatting.RED : EnumChatFormatting.AQUA)
+ "[Angelica] " + message));
}
if (Tracy.ENABLED) Tracy.message(message);
}
private void teardown(Minecraft mc, EntityClientPlayerMP player) {
@@ -645,6 +709,7 @@ public final class FlybyRunner {
this.returnToOrigin(player);
this.restorePauseOnLostFocus(mc);
this.armed = false;
sceneGuarded = false;
this.sceneClearRequested = this.sceneCommands.length > 0;
this.restorePacing(mc);
@@ -654,6 +719,7 @@ public final class FlybyRunner {
}
private void exitGame(Minecraft mc) {
this.stopRecording(mc);
LOGGER.info("Flyby complete, shutting down.");
if (mc.theWorld != null) {
mc.theWorld.sendQuittingDisconnectingPacket();
@@ -727,6 +793,8 @@ public final class FlybyRunner {
FramePacer.endStats();
final Minecraft mc = Minecraft.getMinecraft();
this.restorePauseOnLostFocus(mc);
this.armed = false;
this.stopRecording(mc);
this.pendingRequest.set(null);
this.activeRequest = null;
sceneGuarded = false;
@@ -0,0 +1,206 @@
package com.gtnewhorizons.angelica.debug.profiling;
import com.gtnewhorizons.angelica.config.SystemProperties;
import one.profiler.AsyncProfiler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public final class AsprofRecorder {
private static final Logger LOGGER = LogManager.getLogger("Angelica/Asprof");
private static boolean availabilityChecked;
private static boolean availabilityValue;
private static final Session SESSION = new Session(Asprof::execute);
private static boolean shutdownHookRegistered;
private AsprofRecorder() {}
public static synchronized boolean available() {
if (!availabilityChecked) {
availabilityChecked = true;
availabilityValue = computeAvailable();
}
return availabilityValue;
}
private static boolean computeAvailable() {
if (!SystemProperties.debugTooling()) return false;
final String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
if (!(os.contains("mac") || os.contains("linux"))) return false;
return AsprofRecorder.class.getResource("/one/profiler/AsyncProfiler.class") != null;
}
public static synchronized String start(String tag, String opts) {
if (!available()) return "async-profiler is not available on this platform/build";
if (SESSION.recordingId() != 0) return "already recording to " + SESSION.outputPath();
File file = SystemProperties.PROFILE_OUTPUT.isEmpty() ? new File(SystemProperties.PROFILE_DIR, fileName(tag, System.currentTimeMillis())) : new File(SystemProperties.PROFILE_OUTPUT);
file = file.getAbsoluteFile();
if (file.exists()) return "output file already exists: " + file.getPath();
final File parent = file.getParentFile();
if (parent != null) parent.mkdirs();
final String command = command(opts, file.getPath());
LOGGER.info("async-profiler: {}", command);
try {
SESSION.start(file.getPath(), command);
} catch (Throwable t) {
return handleFailure(t);
}
ensureShutdownHook();
return null;
}
public static synchronized String stop() {
final StopResult result = completeStop(SESSION.stop());
if (result.status() == StopStatus.NO_MATCH) return "not recording";
return result.error();
}
public static synchronized long recordingId() {
return SESSION.recordingId();
}
public static synchronized StopResult stopIfRecording(long expectedId) {
return completeStop(SESSION.stopIfRecording(expectedId));
}
private static StopResult completeStop(StopResult result) {
if (result.status() == StopStatus.FAILED) {
handleFailure(result.failure());
} else if (result.status() == StopStatus.STOPPED) {
LOGGER.info("Profile written: {} ({} bytes)", result.path(), new File(result.path()).length());
}
return result;
}
public static synchronized String status() {
if (!available()) return "async-profiler is not available on this platform/build";
try {
return SESSION.status();
} catch (Throwable t) {
return handleFailure(t);
}
}
public static synchronized boolean isRecording() {
return SESSION.recordingId() != 0;
}
public static synchronized String outputPath() {
return SESSION.outputPath();
}
private static void ensureShutdownHook() {
if (shutdownHookRegistered) return;
shutdownHookRegistered = true;
Runtime.getRuntime().addShutdownHook(new Thread(AsprofRecorder::shutdownStop, "Asprof-Shutdown"));
}
private static synchronized void shutdownStop() {
completeStop(SESSION.stop());
}
private static String handleFailure(Throwable t) {
LOGGER.warn("async-profiler command failed", t);
if (t instanceof LinkageError || t instanceof UnsupportedOperationException) {
availabilityChecked = true;
availabilityValue = false;
}
return firstLine(String.valueOf(t.getMessage()));
}
static String command(String opts, String file) {
return opts.isEmpty() ? "start,jfr,file=" + file : "start,jfr," + opts + ",file=" + file;
}
static String fileName(String tag, long nowMillis) {
final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.ROOT);
return "angelica-" + tag + "-" + format.format(new Date(nowMillis)) + ".jfr";
}
static String firstLine(String s) {
if (s == null || s.isEmpty()) return "";
final int newline = s.indexOf('\n');
final String line = newline < 0 ? s : s.substring(0, newline);
return line.trim();
}
public enum StopStatus { NO_MATCH, STOPPED, FAILED }
public record StopResult(StopStatus status, String path, Throwable failure) {
private static final StopResult NO_MATCH = new StopResult(StopStatus.NO_MATCH, null, null);
public String error() {
return failure == null ? null : AsprofRecorder.firstLine(String.valueOf(failure.getMessage()));
}
}
static final class Session {
@FunctionalInterface
interface CommandExecutor {
String execute(String command) throws Throwable;
}
private final CommandExecutor executor;
private long lastId;
private long recordingId;
private String path;
Session(CommandExecutor executor) {
this.executor = executor;
}
synchronized long start(String path, String command) throws Throwable {
if (recordingId != 0) throw new IllegalStateException("already recording to " + this.path);
if (lastId == Long.MAX_VALUE) throw new IllegalStateException("recording identity exhausted");
executor.execute(command);
this.path = path;
recordingId = ++lastId;
return recordingId;
}
synchronized long recordingId() {
return recordingId;
}
synchronized String outputPath() {
return path;
}
synchronized String status() throws Throwable {
return executor.execute("status");
}
synchronized StopResult stop() {
return stopIfRecording(recordingId);
}
synchronized StopResult stopIfRecording(long expectedId) {
if (expectedId == 0 || recordingId != expectedId) return StopResult.NO_MATCH;
final String stoppedPath = path;
Throwable failure = null;
try {
executor.execute("stop");
} catch (Throwable t) {
failure = t;
}
recordingId = 0;
path = null;
return new StopResult(failure == null ? StopStatus.STOPPED : StopStatus.FAILED, stoppedPath, failure);
}
}
private static final class Asprof {
static String execute(String cmd) throws Throwable {
return AsyncProfiler.getInstance().execute(cmd);
}
}
}
@@ -5,7 +5,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.rendering.items.ItemRenderListManager;
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
import com.gtnewhorizons.angelica.rendering.tesr.TesrLifecycle;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import net.coderbot.iris.Iris;
import net.minecraft.client.Minecraft;
@@ -70,7 +70,7 @@ public final class ImmediateExtendedAttribs implements ImmediateExtendedAttribHa
programWantsExtCache.clear();
ItemRenderListManager.clearCache();
FfpExtendedAttribs.reset();
TesrBatchRenderer.INSTANCE.clearRetained();
TesrLifecycle.reset();
}
private final Vector3f normal = new Vector3f();
@@ -10,8 +10,6 @@ import com.gtnewhorizons.angelica.glsm.hooks.ShaderTransformPostProcessor;
import com.gtnewhorizons.angelica.glsm.shader.ShaderType;
import org.taumc.glsl.grammar.GLSLParser;
import com.gtnewhorizons.angelica.glsm.hooks.ShaderWorkSubmitter;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
import net.coderbot.iris.Iris;
@@ -89,44 +87,6 @@ public class IrisGLSMBridge {
};
}
private static VanillaBooleanLayer gated(VanillaBooleanLayer layer) {
return new VanillaBooleanLayer() {
@Override
public boolean isOverrideHeld() {
return Iris.enabled && layer.isOverrideHeld();
}
@Override
public boolean getVanilla() {
return layer.getVanilla();
}
@Override
public void setVanilla(boolean enabled) {
layer.setVanilla(enabled);
}
};
}
private static <T> VanillaStateLayer<T> gated(VanillaStateLayer<T> layer) {
return new VanillaStateLayer<>() {
@Override
public boolean isOverrideHeld() {
return Iris.enabled && layer.isOverrideHeld();
}
@Override
public void readVanilla(T into) {
layer.readVanilla(into);
}
@Override
public void writeVanilla(T from) {
layer.writeVanilla(from);
}
};
}
public static void register() {
GLSMConfig.expandVertexFormats = Iris.enabled;
IrisSamplers.initRenderer();
@@ -164,12 +124,12 @@ public class IrisGLSMBridge {
}
};
GLStateManager.getBlendMode().setVanillaLayer(gated(BlendModeStorage.ENABLE_LAYER));
GLStateManager.getBlendState().setVanillaLayer(gated(BlendModeStorage.FUNC_LAYER));
GLStateManager.getAlphaTest().setVanillaLayer(gated(AlphaTestStorage.ENABLE_LAYER));
GLStateManager.getAlphaState().setVanillaLayer(gated(AlphaTestStorage.FUNC_LAYER));
GLStateManager.getDepthState().setVanillaLayer(gated(DepthColorStorage.DEPTH_LAYER));
GLStateManager.getColorMask().setVanillaLayer(gated(DepthColorStorage.COLOR_LAYER));
GLStateManager.getBlendMode().setVanillaLayer(BlendModeStorage.ENABLE_LAYER);
GLStateManager.getBlendState().setVanillaLayer(BlendModeStorage.FUNC_LAYER);
GLStateManager.getAlphaTest().setVanillaLayer(AlphaTestStorage.ENABLE_LAYER);
GLStateManager.getAlphaState().setVanillaLayer(AlphaTestStorage.FUNC_LAYER);
GLStateManager.getDepthState().setVanillaLayer(DepthColorStorage.DEPTH_LAYER);
GLStateManager.getColorMask().setVanillaLayer(DepthColorStorage.COLOR_LAYER);
GLSMHooks.alphaHandler = new DeferredAlphaHandler() {
@Override
@@ -178,13 +138,13 @@ public class IrisGLSMBridge {
}
@Override
public void deferAlphaTestToggle(boolean enabled) {
AlphaTestStorage.deferAlphaTestToggle(enabled);
public boolean deferAlphaTestToggle(boolean enabled) {
return AlphaTestStorage.deferAlphaTestToggle(enabled);
}
@Override
public void deferAlphaFunc(int function, float reference) {
AlphaTestStorage.deferAlphaFunc(function, reference);
public boolean deferAlphaFunc(int function, float reference) {
return AlphaTestStorage.deferAlphaFunc(function, reference);
}
};
@@ -91,7 +91,7 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
if (SDLGPUGate.isSDLGPUAvailable()) {
LOGGER.info("SDL GPU window mode enabled");
} else {
LOGGER.warn("angelica.sdlgpu.enable=true but SDL GPU dependencies not available, falling back to GL");
LOGGER.warn("SDL GPU disabled, running on OpenGL: {}", SDLGPUGate.missingRequirement());
}
}
@@ -35,6 +35,11 @@ public final class TracyFramePlots {
private static final PlotDelta P_GL_DRAW_CALLS = new PlotDelta("gl.drawCalls");
private static final PlotDelta P_GL_TEX_BIND_MISSES = new PlotDelta("gl.texBindMisses");
private static final PlotDelta P_GL_PROGRAM_SWITCHES = new PlotDelta("gl.programSwitches");
private static final PlotDelta P_GLSM_BRACKETS = new PlotDelta("glsm.brackets");
private static final PlotDelta P_GLSM_COW_SLOTS = new PlotDelta("glsm.cowSlots");
private static final PlotDelta P_GLSM_POP_RESTORES = new PlotDelta("glsm.popRestores");
private static final PlotDelta P_GLSM_POP_GL_CALLS = new PlotDelta("glsm.popGLCalls");
private static final PlotDelta P_GLSM_POP_DISCARDS = new PlotDelta("glsm.popDiscards");
private static final PlotDelta P_GL_LIST_PLAYBACKS = new PlotDelta("gl.listPlaybacks");
private static final PlotDelta P_GL_STREAMED_BYTES = new PlotDelta("gl.streamedBytes", TracyBackend.PLOT_FORMAT_MEMORY);
private static final PlotDelta P_GL_STREAM_DRAWS = new PlotDelta("gl.streamDraws");
@@ -176,6 +181,11 @@ public final class TracyFramePlots {
P_GL_DRAW_CALLS.plot(GLStateManager.drawCalls);
P_GL_TEX_BIND_MISSES.plot(GLStateManager.texBindMisses);
P_GL_PROGRAM_SWITCHES.plot(GLStateManager.programSwitches);
P_GLSM_BRACKETS.plot(GLStateManager.attribPushes);
P_GLSM_COW_SLOTS.plot(GLStateManager.attribSlotsSaved);
P_GLSM_POP_RESTORES.plot(GLStateManager.attribValueRestores);
P_GLSM_POP_GL_CALLS.plot(GLStateManager.attribBackendCalls);
P_GLSM_POP_DISCARDS.plot(GLStateManager.attribDiscards);
P_GL_LIST_PLAYBACKS.plot(DisplayListManager.listPlaybacks);
P_GL_STREAMED_BYTES.plot(TessellatorStreamingDrawer.streamedBytes);
P_GL_STREAM_DRAWS.plot(TessellatorStreamingDrawer.streamDraws);
@@ -74,6 +74,7 @@ import com.gtnewhorizons.angelica.rendering.celeritas.threading.DefaultChunkTask
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ThreadedChunkTaskProvider;
import com.gtnewhorizons.angelica.rendering.items.BlockRenderListManager;
import com.gtnewhorizons.angelica.rendering.items.ItemRenderListManager;
import com.gtnewhorizons.angelica.rendering.tesr.TesrLifecycle;
import com.gtnewhorizons.angelica.utils.AnimationMode;
import com.gtnewhorizons.angelica.utils.ManagedEnum;
import com.gtnewhorizons.angelica.zoom.Zoom;
@@ -253,6 +254,7 @@ public final class ClientProxy extends CommonProxy {
Tracy.sectionLeave(this.worldSection);
this.worldSection = 0L;
DynamicLights.get().removeAllLightSources();
TesrLifecycle.reset();
GpuCulling.onWorldUnload();
}
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff. Show more