Compare commits

..
26 Commits
Author SHA1 Message Date
Nick Smith 61b5301f1a CTM Transitions - neighbor detection and influence (#2047) 2026-09-03 22:33:18 -07:00
Jakob Erber 0e539ae034 Synchronize CubicChunks tile entity mutations (#2031) 2026-09-03 22:32:49 -07:00
Chitak 985 e581326641 Fix animate-only-visible-textures setting (#2054) 2026-09-03 22:32:14 -07:00
kurrycat c707cc51f1 Use bound texture instead of hardcoded vanilla textures for cached vanilla model rendering (#2037) 2026-09-03 22:31:41 -07:00
Nick Smith 7d890bf3e7 Enable CTM mining/breaking overlays (#2052) 2026-09-03 22:30:39 -07:00
Eclipse-SolandJason Mitchell ffc324bab6 POM Support (#1927)
Co-authored-by: Jason Mitchell <[email protected]>
2026-09-03 22:28:20 -07:00
Jason Mitchell 0dfffca88e Flyby and OSX improvements (#2061) 2026-09-03 22:27:35 -07:00
Alexdoru a66db1517a fix world client leaks in bop fog handlers (#2056) 2026-09-02 11:15:25 -07:00
Jason Mitchell 269b7245d3 Make dynamic lights config less heavy (#2051) 2026-08-30 13:02:07 -07:00
Wokafishh 67f2484976 Blockrotations/Natural Textures (#2020) 2026-08-30 11:16:21 -07:00
KAMKEELandJason Mitchell 1c8687e6fd Add Secondary Color Support to Angelica (#2046)
Co-authored-by: Jason Mitchell <[email protected]>
2026-08-30 10:26:08 -07:00
Jason Mitchell e6bbd155da Stop hardcoding some translations (#2049) 2026-08-30 09:40:27 -07:00
Ranzu 2f64fef6a6 Fix color leak getting into guis (Broken ColorUtils) (#2045) 2026-08-27 14:19:55 -07:00
boubou19 b0fd1ea46e Fix some CTM Allocations (#2044) 2026-08-27 11:59:02 -07:00
ah-OOG-ah 6d8cbf9c2f Add a flag to StateAwareTesselator for per-quad directional shading (#1989)
* Add a flag for no directional shading

* Mark StateAwareTessellator as an API

* Reset internal state better
2026-08-19 18:16:04 +00:00
boubou19 096dd01446 Avoid rescanning atlas for each CTM tile (#2029) 2026-08-19 09:32:42 -07:00
Eclipse-SolandMartin Robertz 22c9479946 Many bug fixes (#2017)
Co-authored-by: Martin Robertz <[email protected]>
2026-08-19 08:55:43 -07:00
Jason Mitchell b2f597a73a SDL presenter fixes and frame pacer adjustments (#2025) 2026-08-16 17:59:33 -07:00
Jason Mitchell 553dec5b6d Mark tiles at transform time instead of using reflection (#2024) 2026-08-16 16:05:04 -07:00
Jason Mitchell bc8116006c Tracy 0.14 (#2019) 2026-08-15 19:09:36 -07:00
Jason Mitchell bf9492dffb Defer ticking on TEs we eagerly create until they get a description packet (#2018) 2026-08-15 17:04:50 -07:00
Jason Mitchell 69aed42b89 Sign Fix (#2016) 2026-08-15 14:32:50 -07:00
KAMKEELandJason Mitchell ffe54f61b9 Fixes World Lighting to Text Rendering so its not Fullbright (#1976)
* Fixes World Lighting to Text Rendering so its not Fullbright

* Carry lightmap state per text segment

* Text lighting: per-segment lightmap uniform, fix GL_RESCALE_NORMAL
* per segment uniform instead of stride increase
* Shade text based on GL light state
* fix u_NormalScale
* GL_LIGHT > 1 usage warnings (not currently handled)

---------

Co-authored-by: Jason Mitchell <[email protected]>
2026-08-14 21:34:58 -07:00
Jason Mitchell c86e1de9f6 TE and world access thread safety for rendering, and remesh on first TE update packet. (#2014) 2026-08-14 20:21:13 -07:00
brandyn 6af401c834 fix starminer rotation & optimize starminer rotateCorpseByGravity (#2005)
* fix starminer rotation & optimize starminer rotateCorpseByGravity

fixes #487

* remove Object[] allocation

* add shared helper,  fix NPEs, fallbacks
2026-08-13 11:27:43 -07:00
Jason Mitchell b17bb42c49 vsync adjustments: on/mailbox/off + windows input lag fix + glsl parsing optimizations (#2009) 2026-08-12 21:20:10 -07:00
275 changed files with 11285 additions and 2360 deletions

No files matched your search

+5 -3
View File
@@ -2,7 +2,8 @@
# Builds the libTracyClient shared library for the tracy-client module.
set -euo pipefail
TRACY_VERSION="${TRACY_VERSION:-v0.13.1}"
TRACY_VERSION="${TRACY_VERSION:-v0.14.0}"
TRACY_VERSION_BARE="${TRACY_VERSION#v}"
OUT_DIR="${OUT_DIR:-dist/tracy-natives}"
TRACY_REPO="https://github.com/wolfpld/tracy"
@@ -16,7 +17,7 @@ case "$(uname -m)" in
arm64|aarch64) PLAT_ARCH="arm64" ;;
*) PLAT_ARCH="x64" ;;
esac
RES_DIR="${RES_DIR:-$REPO_ROOT/tracy-client/src/main/resources/natives/tracy/$PLAT_OS-$PLAT_ARCH}"
RES_DIR="${RES_DIR:-$REPO_ROOT/tracy-client/src/main/resources/natives/tracy/$TRACY_VERSION_BARE/$PLAT_OS-$PLAT_ARCH}"
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
@@ -33,7 +34,8 @@ fi
BUILD_DIR="$WORK_DIR/build"
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
cmake -S "$SRC_DIR" -B "$BUILD_DIR" \
cmake -S "$REPO_ROOT/tracy-client/src/main/native" -B "$BUILD_DIR" \
-DTRACY_SRC_DIR="$SRC_DIR" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON \
-DTRACY_STATIC=OFF \
+12 -23
View File
@@ -6,10 +6,6 @@ on:
- "tracy-v*"
workflow_dispatch:
inputs:
tracy-version:
description: Tracy git tag to build
required: false
default: v0.13.1
publish:
description: Publish angelica-tracy.jar to the GTNH maven
type: boolean
@@ -20,25 +16,25 @@ jobs:
build:
strategy:
matrix:
platform: [linux-x64, windows-x64, macos-x64, macos-arm64]
include:
- os: ubuntu-22.04
platform: linux-x64
- os: windows-2025
platform: windows-x64
- os: macos-15-intel
platform: macos-x64
- os: macos-15
platform: macos-arm64
- platform: linux-x64
os: ubuntu-22.04
- platform: windows-x64
os: windows-2025
- platform: macos-x64
os: macos-15-intel
- platform: macos-arm64
os: macos-15
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- name: Build libTracyClient
shell: bash
env:
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
OUT_DIR: dist/tracy-natives
run: |
export TRACY_VERSION="${RAW_VERSION#tracy-}"
export TRACY_VERSION="v$(sed -nE 's/^tracy = "(.*)"$/\1/p' gradle/libs.versions.toml)"
bash .github/scripts/build-tracy-natives.sh
- uses: actions/upload-artifact@v4
with:
@@ -56,14 +52,6 @@ jobs:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- name: Check tracy version matches the version catalog
shell: bash
env:
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
run: |
ver="${RAW_VERSION#tracy-}"
grep -q "^tracy = \"${ver#v}\"$" gradle/libs.versions.toml \
|| { echo "libs.versions.toml tracy does not match $ver" >&2; exit 1; }
- uses: actions/download-artifact@v5
with:
pattern: tracy-natives-*
@@ -71,8 +59,9 @@ jobs:
- name: Stage natives into tracy-client resources
shell: bash
run: |
version="$(sed -nE 's/^tracy = "(.*)"$/\1/p' gradle/libs.versions.toml)"
for d in staging/tracy-natives-*; do
dest="tracy-client/src/main/resources/natives/tracy/${d#staging/tracy-natives-}"
dest="tracy-client/src/main/resources/natives/tracy/$version/${d#staging/tracy-natives-}"
mkdir -p "$dest"
cp "$d"/* "$dest/"
done
+3
View File
@@ -40,5 +40,8 @@ addon.late.local.gradle
addon.late.local.gradle.kts
local/
tracy-client/src/main/resources/natives/
tracy-client/angelica/
dist/tracy-natives/
*.tracy
tracy-client/angelica/
+3
View File
@@ -187,6 +187,9 @@ fun Test.configureAngelicaJava8() {
}
dependsOn(tasks.extractNatives2)
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
if (isMacOs) {
jvmArgs("-Dapple.awt.UIElement=true")
}
testLogging { events("passed", "skipped", "failed") }
val swaps = mapOf(
+5 -2
View File
@@ -52,8 +52,8 @@ configurations.configureEach {
}
DependencyHandlerScope.of(dependencies).apply {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
runtimeOnlyNonPublishable(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
@@ -161,6 +161,8 @@ DependencyHandlerScope.of(dependencies).apply {
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
compileOnly("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
compileOnly("com.github.GTNewHorizons:OpenBlocks:1.12.19-GTNH:dev")
compileOnly("com.github.GTNewHorizons:Chisel:2.17.31-GTNH:dev")
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
devOnlyNonPublishable("com.github.GTNewHorizons:CosmeticArmorReworked:1.0.6-GTNH:dev")
@@ -202,6 +204,7 @@ afterEvaluate {
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
testCompileOnly(testFixtures(project(":glsm")))
testImplementation("mega:fluidlogged-mc1.7.10:0.1.2")
testRuntimeOnly(libs.fastutil)
testRuntimeOnly(libs.joml)
+3
View File
@@ -224,6 +224,9 @@ fun Test.configureGlsmJava8() {
val extractNatives = rootProject.tasks.named("extractNatives2")
dependsOn(extractNatives)
jvmArgs("-Djava.library.path=${extractNatives.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}")
if (System.getProperty("os.name").lowercase().contains("mac")) {
jvmArgs("-Dapple.awt.UIElement=true")
}
}
tasks.test {
@@ -35,6 +35,8 @@ public final class SystemProperties {
public static final boolean SDL_ENCODER_ASSERTIONS_FATAL = ENCODER_ASSERTIONS == SdlAssertionMode.FATAL;
public static final boolean SDL_VERIFY_STATE_SYNC = Boolean.getBoolean("angelica.sdlgpu.verifyStateSync");
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");
// Tracy
public static final boolean TRACY = Boolean.getBoolean("angelica.tracy");
@@ -45,6 +47,7 @@ public final class SystemProperties {
// 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");
public static final boolean FLYBY_WAIT_FOR_FOCUS = Boolean.getBoolean("angelica.flyby.waitForFocus");
public static final int FLYBY_WARMUP_TICKS = Integer.getInteger("angelica.flyby.warmupTicks", 400);
public static final int FLYBY_LENGTH = Integer.getInteger("angelica.flyby.length", 0); // Blocks for moving routes, ticks for stationary
public static final double FLYBY_SPEED = parseDouble("angelica.flyby.speed"); // Travel speed in blocks per tick
@@ -56,7 +59,7 @@ public final class SystemProperties {
public static final boolean DEBUG_F3_DETAIL = Boolean.getBoolean("angelica.debug.f3Detail");
public static final boolean ENABLE_TEST_BLOCKS = Boolean.getBoolean("angelica.debug.testBlocks");
public static final boolean DUMP_CLASS = Boolean.getBoolean("angelica.debug.dumpClass");
public static final boolean DUMP_SHADERS = Boolean.getBoolean("angelica.debug.dumpShaders") || isDeobf();
private static final boolean DUMP_SHADERS_PROP = Boolean.getBoolean("angelica.debug.dumpShaders");
public static final boolean REDIRECTOR_LOGSPAM = Boolean.getBoolean("angelica.debug.redirectorLogspam");
public static final boolean DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debug.displayLists");
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
@@ -66,8 +69,12 @@ public final class SystemProperties {
// Set by us, read by celeritas
public static final String KEY_CELERITAS_ENABLE_GL_DEBUG = "celeritas.enableGLDebug";
public static boolean dumpShaders() {
return DUMP_SHADERS_PROP || isDeobf();
}
public static Path shaderDumpDir(String phase) {
return DUMP_SHADERS ? Paths.get(SHADER_DUMP_ROOT, phase) : null;
return dumpShaders() ? Paths.get(SHADER_DUMP_ROOT, phase) : null;
}
public enum UnmappedGLMode {
@@ -116,7 +123,7 @@ public final class SystemProperties {
}
}
private static boolean isDeobf() {
public static boolean isDeobf() {
return Launch.blackboard != null && Boolean.TRUE.equals(Launch.blackboard.get("fml.deobfuscatedEnvironment"));
}
@@ -307,6 +307,11 @@ public class DisplayListManager {
}
}
public static void recordSecondaryColor(float r, float g, float b) {
drawBarrier();
currentRecorder.writeSecondaryColor(r, g, b);
}
public static void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
drawBarrier();
currentRecorder.writeColorMask(r, g, b, a);
@@ -841,7 +846,12 @@ public class DisplayListManager {
if (hasCommands) {
DisplayListVBO compiledBuffers = null;
try {
compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
final CommandRecorder pausedVbo = pauseRecording();
try {
compiledBuffers = new DisplayListVBOBuilder().addDraws(accumulatedDraws).build();
} finally {
resumeRecording(pausedVbo);
}
final IndexedDrawBatchBuilder indexedBuilder = commandBuffer.getIndexedDraws();
final List<IndexedDrawBatch> indexedBatches;
if (indexedBuilder.isEmpty()) {
@@ -99,6 +99,7 @@ public class Feature {
));
attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of(
GLStateManager.getColor()
, GLStateManager.getSecondaryColor() // Current secondary color
, ShaderManager.getNormalStack() // Current normal vector
, ShaderManager.getTexCoordStack() // Current texture coordinates
// Current color index
@@ -122,6 +123,7 @@ public class Feature {
, GLStateManager.getBlendMode()
, GLStateManager.getColorLogicOpState()
, GLStateManager.getColorMaterial()
, GLStateManager.getColorSumState()
, GLStateManager.getCullState()
, GLStateManager.getDepthTest()
, GLStateManager.getDitherState()
@@ -197,6 +199,7 @@ public class Feature {
));
attribToFeatures.put(GL11.GL_FOG_BIT, ImmutableSet.of(
GLStateManager.getFogMode()
, GLStateManager.getColorSumState()
, GLStateManager.getFogState()
// ^^ Fog density
// ^^ Linear fog start
@@ -1,6 +1,10 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
@@ -18,6 +22,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
@@ -30,6 +35,7 @@ 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.DoubleBuffer;
import java.nio.FloatBuffer;
@@ -103,6 +109,9 @@ class GLContextState {
public final FogStateStack fogState = new FogStateStack();
public final BooleanStateStack fogMode = track(new BooleanStateStack(GL11.GL_FOG, false, true));
public final Color4Stack color = new Color4Stack();
// 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 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);
@@ -175,12 +184,19 @@ class GLContextState {
public int boundVAO;
public int boundPixelUnpackBuffer;
public int boundPixelPackBuffer;
public int boundCopyReadBuffer;
public int boundCopyWriteBuffer;
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
public final IntSet writeMappedBuffers = new IntOpenHashSet();
public PixelUnpackState pixelUnpackState = PixelUnpackState.DEFAULT;
public final int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public int clientAttribStackPointer = 0;
public final boolean[] restoreUnitChanged = new boolean[GLStateManager.MAX_TEXTURE_UNITS];
public boolean restoreDepthChanged, restoreBlendChanged, restoreColorMaskChanged, restoreClearColorChanged, restoreDrawBufferChanged, restoreLogicOpChanged, restoreStencilChanged, restoreViewportChanged, restoreLineChanged, restorePointChanged, restorePolygonChanged, restoreActiveUnitChanged;
public boolean restoreDepthChanged, restoreBlendChanged, restoreColorMaskChanged, restoreClearColorChanged, restoreDrawBufferChanged, restoreLogicOpChanged, restoreStencilChanged, restoreViewportChanged, restoreLineChanged, restorePointChanged, restorePolygonChanged, restoreActiveUnitChanged, restoreSecondaryColorChanged;
public final BlendState vanillaBlendBefore = new BlendState();
public final BlendState vanillaBlendAfter = new BlendState();
public boolean vanillaBlendEnabledBefore;
public int drawFramebuffer = 0;
public int readFramebuffer = 0;
public int texGenGeneration;
@@ -11,10 +11,11 @@ import com.gtnewhorizons.angelica.glsm.DisplayListManager.RecordMode;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.ffp.VertexKey;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredAlphaHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
@@ -45,6 +46,8 @@ import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
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;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
@@ -54,6 +57,7 @@ import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntStack;
@@ -502,6 +506,7 @@ public class GLStateManager {
.addFeature(GL11.GL_COLOR_WRITEMASK)
.addFeature(GL11.GL_CURRENT_COLOR)
.addFeature(GL11.GL_CURRENT_NORMAL)
.addFeature(GL14.GL_CURRENT_SECONDARY_COLOR)
.addFeature(GL11.GL_CURRENT_RASTER_COLOR)
.addFeature(GL11.GL_CURRENT_RASTER_POSITION)
.addFeature(GL11.GL_CURRENT_RASTER_TEXTURE_COORDS)
@@ -657,6 +662,7 @@ public class GLStateManager {
case GL11.GL_BLEND -> enableBlend();
case GL11.GL_COLOR_MATERIAL -> enableColorMaterial();
case GL11.GL_COLOR_LOGIC_OP -> glCtx.colorLogicOpState.enable();
case GL14.GL_COLOR_SUM -> glCtx.colorSumState.enable();
case GL11.GL_CULL_FACE -> enableCull();
case GL11.GL_DEPTH_TEST -> enableDepthTest();
case GL11.GL_DITHER -> glCtx.ditherState.enable();
@@ -738,6 +744,7 @@ public class GLStateManager {
case GL11.GL_BLEND -> disableBlend();
case GL11.GL_COLOR_MATERIAL -> disableColorMaterial();
case GL11.GL_COLOR_LOGIC_OP -> glCtx.colorLogicOpState.disable();
case GL14.GL_COLOR_SUM -> glCtx.colorSumState.disable();
case GL11.GL_CULL_FACE -> disableCull();
case GL11.GL_DEPTH_TEST -> disableDepthTest();
case GL11.GL_DITHER -> glCtx.ditherState.disable();
@@ -813,6 +820,7 @@ public class GLStateManager {
case GL11.GL_BLEND -> glCtx.blendMode.isEnabled();
case GL11.GL_COLOR_MATERIAL -> glCtx.colorMaterial.isEnabled();
case GL11.GL_COLOR_LOGIC_OP -> glCtx.colorLogicOpState.isEnabled();
case GL14.GL_COLOR_SUM -> glCtx.colorSumState.isEnabled();
case GL11.GL_CULL_FACE -> glCtx.cullState.isEnabled();
case GL11.GL_DEPTH_TEST -> glCtx.depthTest.isEnabled();
case GL11.GL_DITHER -> glCtx.ditherState.isEnabled();
@@ -888,6 +896,7 @@ public class GLStateManager {
case GL11.GL_BLEND -> glCtx.blendMode.isEnabled();
case GL11.GL_COLOR_MATERIAL -> glCtx.colorMaterial.isEnabled();
case GL11.GL_COLOR_LOGIC_OP -> glCtx.colorLogicOpState.isEnabled();
case GL14.GL_COLOR_SUM -> glCtx.colorSumState.isEnabled();
case GL11.GL_CULL_FACE -> glCtx.cullState.isEnabled();
case GL11.GL_DEPTH_TEST -> glCtx.depthTest.isEnabled();
case GL11.GL_DEPTH_WRITEMASK -> glCtx.depthState.isEnabled();
@@ -1131,6 +1140,7 @@ public class GLStateManager {
case GL11.GL_TEXTURE_MATRIX -> glCtx.textures.getTextureUnitMatrix(getActiveTextureUnit()).get(0, params);
case GL11.GL_COLOR_CLEAR_VALUE -> glCtx.clearColor.get(params);
case GL11.GL_CURRENT_COLOR -> glCtx.color.get(params);
case GL14.GL_CURRENT_SECONDARY_COLOR -> glCtx.secondaryColor.get(params);
case GL11.GL_DEPTH_RANGE -> {
final int pos = params.position();
params.put(pos, (float) glCtx.viewportState.depthRangeNear);
@@ -1229,7 +1239,7 @@ public class GLStateManager {
}
case GL11.GL_MODELVIEW_MATRIX, GL11.GL_PROJECTION_MATRIX, GL11.GL_TEXTURE_MATRIX -> widenFromFloat(pname, params, 16);
case GL11.GL_COLOR_CLEAR_VALUE, GL11.GL_CURRENT_COLOR, GL11.GL_CURRENT_TEXTURE_COORDS, GL11.GL_FOG_COLOR,
GL11.GL_LIGHT_MODEL_AMBIENT, GL14.GL_BLEND_COLOR -> widenFromFloat(pname, params, 4);
GL11.GL_LIGHT_MODEL_AMBIENT, GL14.GL_BLEND_COLOR, GL14.GL_CURRENT_SECONDARY_COLOR -> widenFromFloat(pname, params, 4);
case GL11.GL_CURRENT_NORMAL -> widenFromFloat(pname, params, 3);
default -> {
if (!HAS_MULTIPLE_SET.contains(pname)) {
@@ -1302,12 +1312,16 @@ public class GLStateManager {
return;
}
}
final BooleanStateStack blend = ctx().blendMode;
final boolean wasEnabled = blend.isEffectivelyEnabled();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isBlendLocked()) {
blend.beforeModify();
bh.deferBlendModeToggle(true);
return;
} else {
blend.enable();
}
ctx().blendMode.enable();
if (!wasEnabled && blend.isEffectivelyEnabled()) postVanillaBlendChange();
}
public static void disableBlend() {
@@ -1318,12 +1332,80 @@ public class GLStateManager {
return;
}
}
final BooleanStateStack blend = ctx().blendMode;
final boolean wasEnabled = blend.isEffectivelyEnabled();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isBlendLocked()) {
blend.beforeModify();
bh.deferBlendModeToggle(false);
return;
} else {
blend.disable();
}
ctx().blendMode.disable();
if (wasEnabled && !blend.isEffectivelyEnabled()) postVanillaBlendChange();
}
private static void postVanillaBlendChange() {
if (GLSMHooks.VANILLA_BLEND_CHANGE.hasListeners()) {
GLSMHooks.VANILLA_BLEND_CHANGE.post(GLSMHooks.vanillaBlendChangeEvent);
}
}
private static boolean snapshotVanillaBlendFunc() {
if (!GLSMHooks.VANILLA_BLEND_CHANGE.hasListeners()) return false;
final GLContextState glCtx = ctx();
glCtx.blendState.readEffective(glCtx.vanillaBlendBefore);
glCtx.vanillaBlendEnabledBefore = glCtx.blendMode.isEffectivelyEnabled();
return true;
}
private static void postVanillaBlendChangeIfMoved(boolean snapshotted) {
if (!snapshotted) return;
final GLContextState glCtx = ctx();
final BlendState before = glCtx.vanillaBlendBefore;
final BlendState after = glCtx.blendState.readEffective(glCtx.vanillaBlendAfter);
if (glCtx.blendMode.isEffectivelyEnabled() != glCtx.vanillaBlendEnabledBefore
|| after.getSrcRgb() != before.getSrcRgb()
|| after.getDstRgb() != before.getDstRgb()
|| after.getSrcAlpha() != before.getSrcAlpha()
|| after.getDstAlpha() != before.getDstAlpha()) {
postVanillaBlendChange();
}
}
public static boolean isEffectiveBlendEnabled() {
return ctx().blendMode.isEffectivelyEnabled();
}
public static BlendState getEffectiveBlendState(BlendState out) {
return ctx().blendState.readEffective(out);
}
public static boolean isEffectiveDepthMaskEnabled() {
return ctx().depthState.isEffectiveMaskEnabled();
}
public static boolean isEffectiveAlphaTestEnabled() {
return ctx().alphaTest.isEffectivelyEnabled();
}
public static AlphaState getEffectiveAlphaState(AlphaState out) {
return ctx().alphaState.readEffective(out);
}
private static int foreignDrawDepth;
public static void beginForeignDraw() {
foreignDrawDepth++;
}
public static void endForeignDraw() {
if (foreignDrawDepth > 0 && --foreignDrawDepth == 0 && GLSMHooks.FOREIGN_DRAW_END.hasListeners()) {
GLSMHooks.FOREIGN_DRAW_END.post(GLSMHooks.foreignDrawEndEvent);
}
}
public static boolean isForeignDraw() {
return foreignDrawDepth > 0;
}
public static void enableScissorTest() {
@@ -1357,9 +1439,11 @@ public class GLStateManager {
return;
}
}
final boolean snapshotted = snapshotVanillaBlendFunc();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isBlendLocked()) {
bh.deferBlendFunc(srcFactor, dstFactor, srcFactor, dstFactor);
postVanillaBlendChangeIfMoved(snapshotted);
return;
}
final boolean caching = isCachingEnabled();
@@ -1373,6 +1457,7 @@ public class GLStateManager {
GLSMHooks.blendFuncChangeEvent.dstAlpha = GL11.GL_ONE_MINUS_SRC_ALPHA;
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
}
postVanillaBlendChangeIfMoved(snapshotted);
return;
}
final boolean bypass = !caching;
@@ -1391,6 +1476,7 @@ public class GLStateManager {
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
}
}
postVanillaBlendChangeIfMoved(snapshotted);
}
public static void glBlendEquation(int mode) {
@@ -1430,9 +1516,11 @@ public class GLStateManager {
return;
}
}
final boolean snapshotted = snapshotVanillaBlendFunc();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isBlendLocked()) {
bh.deferBlendFunc(srcRgb, dstRgb, srcAlpha, dstAlpha);
postVanillaBlendChangeIfMoved(snapshotted);
return;
}
if (GLSMConfig.hudCacheOverride && dstAlpha != GL11.GL_ONE_MINUS_SRC_ALPHA) {
@@ -1452,6 +1540,7 @@ public class GLStateManager {
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
}
}
postVanillaBlendChangeIfMoved(snapshotted);
}
public static void glBlendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
@@ -1673,6 +1762,40 @@ public class GLStateManager {
return false;
}
public static void glSecondaryColor3f(float red, float green, float blue) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordSecondaryColor(red, green, blue);
if (mode == RecordMode.COMPILE) {
return;
}
}
changeSecondaryColor(red, green, blue);
}
public static void glSecondaryColor3d(double red, double green, double blue) {
glSecondaryColor3f((float) red, (float) green, (float) blue);
}
public static void glSecondaryColor3b(byte red, byte green, byte blue) {
glSecondaryColor3f(b2f(red), b2f(green), b2f(blue));
}
public static void glSecondaryColor3ub(byte red, byte green, byte blue) {
glSecondaryColor3f(ub2f(red), ub2f(green), ub2f(blue));
}
/** Helper for glSecondaryColor* - the color sum is emulated by the FFP shaders, nothing is sent to the driver. */
private static void changeSecondaryColor(float red, float green, float blue) {
final GLContextState glCtx = ctx();
if (!isCachingEnabled() || red != glCtx.secondaryColor.getRed() || green != glCtx.secondaryColor.getGreen() || blue != glCtx.secondaryColor.getBlue()) {
glCtx.secondaryColor.setRed(red);
glCtx.secondaryColor.setGreen(green);
glCtx.secondaryColor.setBlue(blue);
glCtx.fragmentGeneration++;
}
}
private static final Color4 DirtyColor = new Color4(-1.0F, -1.0F, -1.0F, -1.0F);
public static void clearCurrentColor() {
@@ -1746,6 +1869,7 @@ public class GLStateManager {
}
final DeferredAlphaHandler ah = GLSMHooks.alphaHandler;
if (ah != null && ah.isAlphaTestLocked()) {
glCtx.alphaTest.beforeModify();
ah.deferAlphaTestToggle(true);
return;
}
@@ -1767,6 +1891,7 @@ public class GLStateManager {
}
final DeferredAlphaHandler ah = GLSMHooks.alphaHandler;
if (ah != null && ah.isAlphaTestLocked()) {
glCtx.alphaTest.beforeModify();
ah.deferAlphaTestToggle(false);
return;
}
@@ -2454,12 +2579,17 @@ public class GLStateManager {
return;
}
try {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices_count, type, indices_buffer_offset);
return;
final boolean ffpExt = FfpExtendedAttribs.maybeBindIndexed(mode, indices_count, type, indices_buffer_offset);
try {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices_count, type, indices_buffer_offset);
return;
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices_count, type, indices_buffer_offset);
} finally {
if (ffpExt) FfpExtendedAttribs.unbind();
}
preDraw(mode);
RENDER_BACKEND.drawElements(mode, indices_count, type, indices_buffer_offset);
} finally {
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
@@ -2569,19 +2699,24 @@ public class GLStateManager {
FeedbackManager.processDrawArrays(mode, first, count);
return;
}
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(first, count);
} else if (mode == GL11.GL_QUAD_STRIP) {
preDraw();
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_STRIP, first, count & ~1);
} else if (mode == GL11.GL_POLYGON) {
preDraw();
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_FAN, first, count);
} else {
preDraw(mode);
RENDER_BACKEND.drawArrays(mode, first, count);
final boolean ffpExt = FfpExtendedAttribs.maybeBind(mode, first, count);
try {
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(first, count);
} else if (mode == GL11.GL_QUAD_STRIP) {
preDraw();
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_STRIP, first, count & ~1);
} else if (mode == GL11.GL_POLYGON) {
preDraw();
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_FAN, first, count);
} else {
preDraw(mode);
RENDER_BACKEND.drawArrays(mode, first, count);
}
} finally {
if (ffpExt) FfpExtendedAttribs.unbind();
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
if (savedRecorder != null) DisplayListManager.resumeRecording(savedRecorder);
}
private static void ffpClientArrayPointer(int index, int size, int type, boolean normalized, int stride, long offset) {
@@ -3058,9 +3193,15 @@ public class GLStateManager {
return;
}
}
if (light >= VertexKey.FFP_LIGHT_COUNT && (warnedFfpLightMask & (1 << light)) == 0 && ShaderManager.getInstance().isEnabled()) {
warnedFfpLightMask |= (1 << light);
LOGGER.warn("GL_LIGHT{} enabled, but FFP shader emulation implements only GL_LIGHT0..{}", light, VertexKey.FFP_LIGHT_COUNT - 1, new Throwable("call site"));
}
ctx().lightStates[light].enable();
}
private static int warnedFfpLightMask = 0;
public static void enableColorMaterial() {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -3383,14 +3524,11 @@ public class GLStateManager {
}
public static boolean isCurrent() {
if (BackendManager.RENDER_BACKEND == null) {
return Thread.currentThread() == MainThread;
}
return BackendManager.RENDER_BACKEND.isCurrent();
return RENDER_BACKEND.isCurrent();
}
public static void swapBuffers() throws LWJGLException {
if (BackendManager.RENDER_BACKEND != null && BackendManager.RENDER_BACKEND.handleSwapBuffers()) {
if (RENDER_BACKEND.handleSwapBuffers()) {
return;
}
Display.swapBuffers();
@@ -3406,7 +3544,14 @@ public class GLStateManager {
if (processMessages) Display.processMessages();
return;
}
if (!RENDER_BACKEND.wantsDisplayUpdateGateTiming()) {
Display.update(processMessages);
return;
}
final long start = System.nanoTime();
Display.update(processMessages);
final long end = System.nanoTime();
RENDER_BACKEND.recordFrameGate(end - start, end);
}
/**
@@ -3522,6 +3667,9 @@ public class GLStateManager {
final int mask = glCtx.attribs.popInt();
glCtx.attribDepth--;
// Snapshot before the stacks pop, so applyRestoredState can tell whether vanilla blend actually moved.
final boolean blendSnapshotted = (mask & (GL11.GL_COLOR_BUFFER_BIT | GL11.GL_ENABLE_BIT)) != 0 && snapshotVanillaBlendFunc();
// First: restore BooleanStateStack states that were actually modified (fast path)
// These use lazy copy-on-write with global depth tracking
final List<IStateStack<?>> modified = glCtx.modifiedAtDepth[glCtx.attribDepth];
@@ -3540,7 +3688,12 @@ public class GLStateManager {
// Third: apply restored state to the GL driver. BooleanStateStacks already issue GL calls via setEnabled(); non-boolean stacks are pure data
// containers, so we must explicitly drive GL here.
applyRestoredState(mask);
applyRestoredState(mask, blendSnapshotted);
}
private static boolean isDepthColorOverridden() {
final DeferredDepthColorHandler dch = GLSMHooks.depthColorHandler;
return dch != null && dch.isOverrideHeld();
}
private static void captureRestoreChanges(int mask) {
@@ -3555,6 +3708,9 @@ public class GLStateManager {
glCtx.restoreDrawBufferChanged = glCtx.drawBuffer.topChanged();
glCtx.restoreLogicOpChanged = glCtx.logicOpMode.topChanged();
}
if ((mask & GL11.GL_CURRENT_BIT) != 0) {
glCtx.restoreSecondaryColorChanged = glCtx.secondaryColor.topChanged();
}
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
glCtx.restoreStencilChanged = glCtx.stencilState.topChanged();
}
@@ -3629,20 +3785,25 @@ public class GLStateManager {
/**
* After popping GLSM stacks, apply restored state to the GL driver.
*/
private static void applyRestoredState(int mask) {
private static void applyRestoredState(int mask, boolean blendSnapshotted) {
final GLContextState glCtx = ctx();
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0 && glCtx.restoreDepthChanged) {
RENDER_BACKEND.depthFunc(glCtx.depthState.getFunc());
RENDER_BACKEND.depthMask(glCtx.depthState.isEnabled());
if (!isDepthColorOverridden()) {
RENDER_BACKEND.depthMask(glCtx.depthState.isEnabled());
}
RENDER_BACKEND.clearDepth(glCtx.depthState.getClearValue());
}
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
if (glCtx.restoreBlendChanged) {
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
final DeferredBlendHandler restoreBlendHandler = GLSMHooks.blendHandler;
if (restoreBlendHandler == null || !restoreBlendHandler.isOverrideHeld()) {
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
}
RENDER_BACKEND.blendEquationSeparate(glCtx.blendState.getEquationRgb(), glCtx.blendState.getEquationAlpha());
RENDER_BACKEND.blendColor(glCtx.blendState.getBlendColorR(), glCtx.blendState.getBlendColorG(), glCtx.blendState.getBlendColorB(), glCtx.blendState.getBlendColorA());
}
if (glCtx.restoreColorMaskChanged) {
if (glCtx.restoreColorMaskChanged && !isDepthColorOverridden()) {
RENDER_BACKEND.colorMask(glCtx.colorMask.red, glCtx.colorMask.green, glCtx.colorMask.blue, glCtx.colorMask.alpha);
}
if (glCtx.restoreClearColorChanged) {
@@ -3726,7 +3887,11 @@ public class GLStateManager {
ShaderManager.bumpTexCoordGeneration();
glCtx.dirtyTexCoordAttrib = true;
}
// The secondary color is a fragment stage uniform, not a vertex attribute
if (glCtx.restoreSecondaryColorChanged) glCtx.fragmentGeneration++;
}
postVanillaBlendChangeIfMoved(blendSnapshotted);
}
public static void glClear(int mask) {
@@ -4915,16 +5080,22 @@ public class GLStateManager {
final ShaderManager ffp = ShaderManager.getInstance();
if (program == 0 && ffp.isEnabled()) {
final int prev = glCtx.activeProgram;
final boolean caching = isCachingEnabled();
if (prev != 0) glCtx.programGeneration++;
final boolean changed = prev != 0;
if (changed) glCtx.programGeneration++;
glCtx.activeProgram = 0; // Track that FFP was requested
if (GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
if (changed && GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
GLSMHooks.programChangeEvent.previousProgram = prev;
GLSMHooks.programChangeEvent.newProgram = 0;
GLSMHooks.programChangeEvent.postBind = false;
GLSMHooks.PROGRAM_CHANGE.post(GLSMHooks.programChangeEvent);
}
ffp.activate();
if (changed && GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
GLSMHooks.programChangeEvent.previousProgram = prev;
GLSMHooks.programChangeEvent.newProgram = 0;
GLSMHooks.programChangeEvent.postBind = true;
GLSMHooks.PROGRAM_CHANGE.post(GLSMHooks.programChangeEvent);
}
return;
}
@@ -5048,41 +5219,57 @@ public class GLStateManager {
}
public static void setVSyncEnabled(boolean enabled) {
setVSyncMode(RENDER_BACKEND.getPreferredVSyncMode(), enabled);
RENDER_BACKEND.setVSyncEnabled(enabled);
}
public static void setVSyncMode(VSyncMode preferred, boolean vsyncEnabled) {
RENDER_BACKEND.setVSyncMode(preferred, vsyncEnabled);
public static void setVSyncMode(VSyncMode mode) {
RENDER_BACKEND.setVSyncMode(mode);
}
public static void setVSyncPreference(VSyncMode mode) {
RENDER_BACKEND.setVSyncPreference(mode);
}
public static boolean supportsVSyncMode(VSyncMode mode) {
return RENDER_BACKEND.supportsVSyncMode(mode);
}
public static VSyncMode preferredTearFreeMode() {
return RENDER_BACKEND.preferredTearFreeMode();
}
public static String getRenderBackendName() {
return RENDER_BACKEND.getName();
}
public static VSyncMode getEffectiveVSyncMode() {
return RENDER_BACKEND.getEffectiveVSyncMode();
}
public static List<VSyncMode> getSelectableVSyncModes() {
return VSyncMode.selectable(RENDER_BACKEND::supportsVSyncMode);
}
public static int getMinRenderAhead() {
return RENDER_BACKEND.getMinRenderAhead();
}
public static int getMaxRenderAhead() {
return RENDER_BACKEND.getMaxRenderAhead();
}
public static int renderAheadLimit(int configured) {
return RenderBackend.clampRenderAhead(configured, RENDER_BACKEND.getMinRenderAhead(), RENDER_BACKEND.getMaxRenderAhead());
}
public static void applyRenderAheadLimit(int configured) {
RENDER_BACKEND.setRenderAheadLimit(renderAheadLimit(configured));
}
public static int getDisplayRefreshRateHz() {
return RENDER_BACKEND.getDisplayRefreshRateHz();
}
public static boolean gateAnchorsNextFrameStart() {
return RENDER_BACKEND.gateAnchorsNextFrameStart();
}
public static long lastFrameGateNanos() {
return RENDER_BACKEND.lastFrameGateNanos();
}
public static long lastFrameGateEndNanos() {
return RENDER_BACKEND.lastFrameGateEndNanos();
}
public static boolean hasSwapchainBackpressure() {
return RENDER_BACKEND.hasSwapchainBackpressure();
}
public static void pumpDisplayMessages() {
Display.processMessages();
}
public static int glGetError() {
if (!RENDER_BACKEND.hasContext()) return 0;
if (initConfig.isNoErrorChecks()) return 0;
@@ -5711,10 +5898,19 @@ public class GLStateManager {
if (buffer == 0) return;
final boolean locked = acquireDrawLock();
try {
FfpExtendedAttribs.onDeleteBuffer(buffer);
if (glCtx.boundVBO == buffer) glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
if (VAOManager.boundEBO == buffer) glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
if (glCtx.boundPixelUnpackBuffer == buffer) glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
if (glCtx.boundPixelPackBuffer == buffer) glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
if (glCtx.boundCopyReadBuffer == buffer) glBindBuffer(GL31.GL_COPY_READ_BUFFER, 0);
if (glCtx.boundCopyWriteBuffer == buffer) glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, 0);
for (Int2IntMap.Entry e : glCtx.boundOtherBuffers.int2IntEntrySet()) {
if (e.getIntValue() != buffer) continue;
e.setValue(0);
RENDER_BACKEND.bindBuffer(e.getIntKey(), 0);
}
glCtx.writeMappedBuffers.remove(buffer);
VAOManager.onDeleteBuffer(buffer);
} finally {
@@ -5722,20 +5918,30 @@ public class GLStateManager {
}
}
/** glBindBufferBase/Range also set the generic binding point, so they share this. */
private static void trackBufferBinding(GLContextState glCtx, int target, int buffer) {
if (target == GL15.GL_ARRAY_BUFFER) {
// if (boundVBO == buffer) return; TODO figure out why this breaks switching async occlusion mode
glCtx.boundVBO = buffer;
} else if (target == GL15.GL_ELEMENT_ARRAY_BUFFER) {
VAOManager.onBindEBO(buffer);
} else if (target == GL21.GL_PIXEL_UNPACK_BUFFER) {
glCtx.boundPixelUnpackBuffer = buffer;
} else if (target == GL21.GL_PIXEL_PACK_BUFFER) {
glCtx.boundPixelPackBuffer = buffer;
} else if (target == GL31.GL_COPY_READ_BUFFER) {
glCtx.boundCopyReadBuffer = buffer;
} else if (target == GL31.GL_COPY_WRITE_BUFFER) {
glCtx.boundCopyWriteBuffer = buffer;
} else {
glCtx.boundOtherBuffers.put(target, buffer);
}
}
public static void glBindBuffer(int target, int buffer) {
final GLContextState glCtx = ctx();
final boolean locked = acquireDrawLock();
try {
if (target == GL15.GL_ARRAY_BUFFER) {
// if (boundVBO == buffer) return; TODO figure out why this breaks switching async occlusion mode
glCtx.boundVBO = buffer;
} else if (target == GL15.GL_ELEMENT_ARRAY_BUFFER) {
VAOManager.onBindEBO(buffer);
} else if (target == GL21.GL_PIXEL_UNPACK_BUFFER) {
glCtx.boundPixelUnpackBuffer = buffer;
} else if (target == GL21.GL_PIXEL_PACK_BUFFER) {
glCtx.boundPixelPackBuffer = buffer;
}
trackBufferBinding(ctx(), target, buffer);
RENDER_BACKEND.bindBuffer(target, buffer);
} finally {
if (locked) releaseDrawLock();
@@ -5814,15 +6020,78 @@ public class GLStateManager {
for (int i = 0; i < buffers.length; i++) buffers[i] = RENDER_BACKEND.createBuffers();
}
public static void glBufferData(int target, long size, int usage) { RENDER_BACKEND.bufferData(target, size, usage); }
public static void glBufferData(int target, ByteBuffer data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, ShortBuffer data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, IntBuffer data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, FloatBuffer data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, DoubleBuffer data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
private static final int MAP_PERSISTENT_BIT = 0x40;
private static int bufferForTarget(int target) {
final GLContextState glCtx = ctx();
if (target == GL15.GL_ARRAY_BUFFER) return glCtx.boundVBO;
if (target == GL15.GL_ELEMENT_ARRAY_BUFFER) return VAOManager.boundEBO;
if (target == GL31.GL_COPY_READ_BUFFER) return glCtx.boundCopyReadBuffer;
if (target == GL31.GL_COPY_WRITE_BUFFER) return glCtx.boundCopyWriteBuffer;
if (target == GL21.GL_PIXEL_UNPACK_BUFFER) return glCtx.boundPixelUnpackBuffer;
if (target == GL21.GL_PIXEL_PACK_BUFFER) return glCtx.boundPixelPackBuffer;
return glCtx.boundOtherBuffers.get(target);
}
private static void onBufferRespec(int target) {
if (FfpExtendedAttribs.isEmpty()) return;
final int buffer = bufferForTarget(target);
if (buffer != 0) FfpExtendedAttribs.onBufferRespecified(buffer);
}
private static void onBufferMap(int target, int access) {
if (access == GL15.GL_READ_ONLY) return;
markWriteMapped(target);
onBufferRespec(target);
}
private static void onBufferMapRange(int target, int access) {
if ((access & GL30.GL_MAP_WRITE_BIT) == 0) return;
if ((access & MAP_PERSISTENT_BIT) != 0) {
markPersistentlyMapped(target);
return;
}
markWriteMapped(target);
onBufferRespec(target);
}
private static void markWriteMapped(int target) {
final int buffer = bufferForTarget(target);
if (buffer != 0) ctx().writeMappedBuffers.add(buffer);
}
private static void onBufferUnmap(int target) {
final int buffer = bufferForTarget(target);
if (buffer == 0 || !ctx().writeMappedBuffers.remove(buffer)) return;
onBufferRespec(target);
}
private static void onBufferStorage(int target, int flags) {
if ((flags & MAP_PERSISTENT_BIT) != 0) {
markPersistentlyMapped(target);
return;
}
onBufferRespec(target);
}
private static void markPersistentlyMapped(int target) {
FfpExtendedAttribs.markPersistent(bufferForTarget(target));
}
private static void onNamedBufferRespec(int buffer) {
if (FfpExtendedAttribs.isEmpty()) return;
FfpExtendedAttribs.onBufferRespecified(buffer);
}
public static void glBufferData(int target, long size, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, size, usage); }
public static void glBufferData(int target, ByteBuffer data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, ShortBuffer data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, IntBuffer data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, FloatBuffer data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, DoubleBuffer data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, LongBuffer data, int usage) { glBufferData(target, MemoryUtilities.memByteBuffer(data), usage); }
public static void glBufferData(int target, int[] data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, float[] data, int usage) { RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, int[] data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, float[] data, int usage) { onBufferRespec(target); RENDER_BACKEND.bufferData(target, data, usage); }
public static void glBufferData(int target, short[] data, int usage) {
final ByteBuffer copy = copyOf(data);
glBufferData(target, copy, usage);
@@ -5847,11 +6116,11 @@ public class GLStateManager {
glBufferData(target, MemoryUtilities.memByteBuffer(data, checkedSize(size)), usage);
}
public static void glBufferSubData(int target, long offset, ByteBuffer data) { RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, ShortBuffer data) { RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, IntBuffer data) { RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, FloatBuffer data) { RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, DoubleBuffer data) { RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, ByteBuffer data) { onBufferRespec(target); RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, ShortBuffer data) { onBufferRespec(target); RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, IntBuffer data) { onBufferRespec(target); RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, FloatBuffer data) { onBufferRespec(target); RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, DoubleBuffer data) { onBufferRespec(target); RENDER_BACKEND.bufferSubData(target, offset, data); }
public static void glBufferSubData(int target, long offset, LongBuffer data) { glBufferSubData(target, offset, MemoryUtilities.memByteBuffer(data)); }
public static void glBufferSubData(int target, long offset, short[] data) {
final ByteBuffer copy = copyOf(data);
@@ -5881,13 +6150,13 @@ public class GLStateManager {
public static void nglBufferSubData(int target, long offset, long size, long data) { glBufferSubData(target, offset, MemoryUtilities.memByteBuffer(data, checkedSize(size))); }
public static ByteBuffer glMapBuffer(int target, int access) { return RENDER_BACKEND.mapBuffer(target, access); }
public static ByteBuffer glMapBuffer(int target, int access) { onBufferMap(target, access); return RENDER_BACKEND.mapBuffer(target, access); }
public static ByteBuffer glMapBuffer(int target, int access, ByteBuffer old_buffer) { return glMapBuffer(target, access); }
public static ByteBuffer glMapBuffer(int target, int access, long length, ByteBuffer old_buffer) { return RENDER_BACKEND.mapBuffer(target, access, length, old_buffer); }
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access) { return RENDER_BACKEND.mapBufferRange(target, offset, length, access); }
public static ByteBuffer glMapBuffer(int target, int access, long length, ByteBuffer old_buffer) { onBufferMap(target, access); return RENDER_BACKEND.mapBuffer(target, access, length, old_buffer); }
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access) { onBufferMapRange(target, access); return RENDER_BACKEND.mapBufferRange(target, offset, length, access); }
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access, ByteBuffer old_buffer) { return glMapBufferRange(target, offset, length, access); }
public static void glFlushMappedBufferRange(int target, long offset, long length) { RENDER_BACKEND.flushMappedBufferRange(target, offset, length); }
public static boolean glUnmapBuffer(int target) { return RENDER_BACKEND.unmapBuffer(target); }
public static boolean glUnmapBuffer(int target) { onBufferUnmap(target); return RENDER_BACKEND.unmapBuffer(target); }
public static void glGetBufferSubData(int target, long offset, ByteBuffer data) { RENDER_BACKEND.getBufferSubData(target, offset, data); }
public static void glGetBufferSubData(int target, long offset, ShortBuffer data) { RENDER_BACKEND.getBufferSubData(target, offset, data); }
@@ -5934,8 +6203,8 @@ public class GLStateManager {
public static void glGetBufferParameteriv(int target, int pname, IntBuffer params) { params.put(params.position(), glGetBufferParameteri(target, pname)); }
public static void glGetBufferParameteriv(int target, int pname, int[] params) { params[0] = glGetBufferParameteri(target, pname); }
public static void glBufferStorage(int target, long size, int flags) { RENDER_BACKEND.bufferStorage(target, size, flags); }
public static void glBufferStorage(int target, ByteBuffer data, int flags) { RENDER_BACKEND.bufferStorage(target, data, flags); }
public static void glBufferStorage(int target, long size, int flags) { onBufferStorage(target, flags); RENDER_BACKEND.bufferStorage(target, size, flags); }
public static void glBufferStorage(int target, ByteBuffer data, int flags) { onBufferStorage(target, flags); RENDER_BACKEND.bufferStorage(target, data, flags); }
public static void glBufferStorage(int target, ShortBuffer data, int flags) { glBufferStorage(target, MemoryUtilities.memByteBuffer(data), flags); }
public static void glBufferStorage(int target, IntBuffer data, int flags) { glBufferStorage(target, MemoryUtilities.memByteBuffer(data), flags); }
public static void glBufferStorage(int target, FloatBuffer data, int flags) { glBufferStorage(target, MemoryUtilities.memByteBuffer(data), flags); }
@@ -5961,9 +6230,9 @@ public class GLStateManager {
MemoryUtilities.memFree(copy);
}
public static void glNamedBufferData(int buffer, long size, int usage) { RENDER_BACKEND.namedBufferData(buffer, size, usage); }
public static void glNamedBufferData(int buffer, ByteBuffer data, int usage) { RENDER_BACKEND.namedBufferData(buffer, data, usage); }
public static void glNamedBufferData(int buffer, FloatBuffer data, int usage) { RENDER_BACKEND.namedBufferData(buffer, data, usage); }
public static void glNamedBufferData(int buffer, long size, int usage) { onNamedBufferRespec(buffer); RENDER_BACKEND.namedBufferData(buffer, size, usage); }
public static void glNamedBufferData(int buffer, ByteBuffer data, int usage) { onNamedBufferRespec(buffer); RENDER_BACKEND.namedBufferData(buffer, data, usage); }
public static void glNamedBufferData(int buffer, FloatBuffer data, int usage) { onNamedBufferRespec(buffer); RENDER_BACKEND.namedBufferData(buffer, data, usage); }
public static void glNamedBufferData(int buffer, ShortBuffer data, int usage) { glNamedBufferData(buffer, MemoryUtilities.memByteBuffer(data), usage); }
public static void glNamedBufferData(int buffer, IntBuffer data, int usage) { glNamedBufferData(buffer, MemoryUtilities.memByteBuffer(data), usage); }
public static void glNamedBufferData(int buffer, LongBuffer data, int usage) { glNamedBufferData(buffer, MemoryUtilities.memByteBuffer(data), usage); }
@@ -5994,7 +6263,7 @@ public class GLStateManager {
MemoryUtilities.memFree(copy);
}
public static void glNamedBufferSubData(int buffer, long offset, ByteBuffer data) { RENDER_BACKEND.namedBufferSubData(buffer, offset, data); }
public static void glNamedBufferSubData(int buffer, long offset, ByteBuffer data) { onNamedBufferRespec(buffer); RENDER_BACKEND.namedBufferSubData(buffer, offset, data); }
public static void glNamedBufferSubData(int buffer, long offset, ShortBuffer data) { glNamedBufferSubData(buffer, offset, MemoryUtilities.memByteBuffer(data)); }
public static void glNamedBufferSubData(int buffer, long offset, IntBuffer data) { glNamedBufferSubData(buffer, offset, MemoryUtilities.memByteBuffer(data)); }
public static void glNamedBufferSubData(int buffer, long offset, LongBuffer data) { glNamedBufferSubData(buffer, offset, MemoryUtilities.memByteBuffer(data)); }
@@ -6026,15 +6295,15 @@ public class GLStateManager {
MemoryUtilities.memFree(copy);
}
public static void glClearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data) { RENDER_BACKEND.clearBufferSubData(target, internalFormat, offset, size, format, type, data); }
public static void glClearBufferData(int target, int internalformat, int format, int type, ByteBuffer data) { RENDER_BACKEND.clearBufferData(target, internalformat, format, type, data); }
public static void glClearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data) { onBufferRespec(target); RENDER_BACKEND.clearBufferSubData(target, internalFormat, offset, size, format, type, data); }
public static void glClearBufferData(int target, int internalformat, int format, int type, ByteBuffer data) { onBufferRespec(target); RENDER_BACKEND.clearBufferData(target, internalformat, format, type, data); }
public static boolean glIsBuffer(int buffer) { return RENDER_BACKEND.isBuffer(buffer); }
public static boolean glIsTexture(int texture) { return RENDER_BACKEND.isTexture(texture); }
public static boolean glIsFramebuffer(int framebuffer) { return RENDER_BACKEND.isFramebuffer(framebuffer); }
public static boolean glIsRenderbuffer(int renderbuffer) { return RENDER_BACKEND.isRenderbuffer(renderbuffer); }
public static boolean glIsSampler(int sampler) { return RENDER_BACKEND.isSampler(sampler); }
public static boolean glIsQuery(int query) { return RENDER_BACKEND.isQuery(query); }
public static void glCopyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size) { RENDER_BACKEND.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); }
public static void glCopyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size) { onBufferRespec(writeTarget); RENDER_BACKEND.copyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size); }
private static int checkedSize(long size) {
if (size < 0 || size > Integer.MAX_VALUE) {
@@ -6198,7 +6467,26 @@ public class GLStateManager {
public static void glGetQueryObjectui(int id, int pname, IntBuffer params) { RENDER_BACKEND.getQueryObjectui(id, pname, params); }
public static int glGetQueryObjecti(int id, int pname) { return RENDER_BACKEND.getQueryObjecti(id, pname); }
public static void glBindBufferBase(int target, int index, int buffer) { RENDER_BACKEND.bindBufferBase(target, index, buffer); }
public static void glBindBufferBase(int target, int index, int buffer) {
final boolean locked = acquireDrawLock();
try {
trackBufferBinding(ctx(), target, buffer);
RENDER_BACKEND.bindBufferBase(target, index, buffer);
} finally {
if (locked) releaseDrawLock();
}
}
public static void glBindBufferRange(int target, int index, int buffer, long offset, long size) {
final boolean locked = acquireDrawLock();
try {
trackBufferBinding(ctx(), target, buffer);
RENDER_BACKEND.bindBufferRange(target, index, buffer, offset, size);
} finally {
if (locked) releaseDrawLock();
}
}
public static int glGetUniformBlockIndex(int program, CharSequence name) { return RENDER_BACKEND.getUniformBlockIndex(program, name); }
public static void glUniformBlockBinding(int program, int blockIndex, int binding) { RENDER_BACKEND.uniformBlockBinding(program, blockIndex, binding); }
public static void glBindFragDataLocation(int program, int colorNumber, CharSequence name) { RENDER_BACKEND.bindFragDataLocation(program, colorNumber, name); }
@@ -6236,6 +6524,7 @@ public class GLStateManager {
}
}
public static long nglMapBuffer(int target, int access) {
onBufferMap(target, access);
final ByteBuffer buf = RENDER_BACKEND.mapBuffer(target, access);
return buf == null ? 0L : MemoryUtilities.memAddress0(buf);
}
@@ -7281,6 +7570,14 @@ public class GLStateManager {
return ctx().color;
}
public static Color4Stack getSecondaryColor() {
return ctx().secondaryColor;
}
public static BooleanStateStack getColorSumState() {
return ctx().colorSumState;
}
public static Color4Stack getClearColor() {
return ctx().clearColor;
}
@@ -7545,6 +7842,14 @@ public class GLStateManager {
return ctx().boundVBO;
}
public static int getBoundCopyReadBuffer() {
return ctx().boundCopyReadBuffer;
}
public static int getBoundCopyWriteBuffer() {
return ctx().boundCopyWriteBuffer;
}
public static int getBoundVAO() {
return ctx().boundVAO;
}
@@ -2,8 +2,10 @@ package com.gtnewhorizons.angelica.glsm;
import org.antlr.v4.runtime.BufferedTokenStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.WritableToken;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.taumc.glsl.grammar.GLSLLexer;
@@ -88,6 +90,23 @@ public class GlslTransformUtils {
return source;
}
public static void restoreReservedWordsInTree(ParseTree tree) {
if (tree instanceof TerminalNode terminal) {
final Token token = terminal.getSymbol();
final String text = token.getText();
if (text != null && text.startsWith(RENAMED_PREFIX) && token instanceof WritableToken writable) {
final String rest = text.substring(RENAMED_PREFIX.length());
if (rest.startsWith("texture") || rest.startsWith("sample")) {
writable.setText(rest);
}
}
return;
}
for (int i = 0; i < tree.getChildCount(); i++) {
restoreReservedWordsInTree(tree.getChild(i));
}
}
public record QuietParse(GLSLParser.Translation_unitContext full, GLSLPreParser.Translation_unitContext pre) {}
public static GLSLParser.Translation_unitContext parseFullQuiet(String source) {
@@ -162,21 +181,32 @@ public class GlslTransformUtils {
public static String getFormattedShader(ParseTree tree, String header) {
final StringBuilder sb = new StringBuilder(header + "\n");
final String[] tabHolder = {""};
getFormattedShader(tree, sb, tabHolder);
getFormattedShader(tree, sb, tabHolder, false);
return sb.toString();
}
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder) {
if (tree instanceof TerminalNode) {
public static String getFormattedShaderRebased(ParseTree tree, String header) {
final StringBuilder sb = new StringBuilder(header + "\n");
final String[] tabHolder = {""};
getFormattedShader(tree, sb, tabHolder, true);
return sb.toString();
}
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder, boolean rebaseOffsets) {
if (tree instanceof TerminalNode terminal) {
final String text = tree.getText();
if (text.equals("<EOF>")) {
return;
}
if (text.equals("#")) {
stringBuilder.append("\n#");
rebaseAt(terminal, stringBuilder.length() - 1, text, rebaseOffsets);
return;
}
stringBuilder.append(text);
if (!text.equals("}")) {
rebaseAt(terminal, stringBuilder.length() - text.length(), text, rebaseOffsets);
}
if (text.equals("{")) {
stringBuilder.append(" \n\t");
tabHolder[0] = "\t";
@@ -186,6 +216,7 @@ public class GlslTransformUtils {
if (stringBuilder.length() >= 2) {
stringBuilder.deleteCharAt(stringBuilder.length() - 2);
}
rebaseAt(terminal, stringBuilder.length() - 1, text, rebaseOffsets);
tabHolder[0] = "";
stringBuilder.append(" \n");
} else {
@@ -193,8 +224,17 @@ public class GlslTransformUtils {
}
} else {
for (int i = 0; i < tree.getChildCount(); ++i) {
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder);
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder, rebaseOffsets);
}
}
}
private static void rebaseAt(TerminalNode terminal, int startIndex, String text, boolean rebaseOffsets) {
if (!rebaseOffsets) return;
if (terminal.getSymbol() instanceof CommonToken common) {
common.setText(text);
common.setStartIndex(startIndex);
common.setStopIndex(startIndex + text.length() - 1);
}
}
}
@@ -9,6 +9,7 @@ import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import com.mitchej123.lwjgl.LWJGLServiceProvider;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
@@ -111,6 +112,13 @@ public class RenderSystem {
BackendManager.init();
try {
// Force init on client thread
GLStateManager.LOGGER.info("Celeritas LWJGL service: {}", LWJGLServiceProvider.LWJGL.getClass().getName());
} catch (Throwable t) {
GLStateManager.LOGGER.debug("Could not resolve the celeritas LWJGL service: {}", t.toString());
}
supportsCompute = supportsCompute();
supportsTesselation = GLStateManager.capabilities.GL_ARB_tessellation_shader || GLStateManager.capabilities.OpenGL40;
@@ -551,7 +559,7 @@ public class RenderSystem {
}
public static void bindBufferBase(int target, int index, int buffer) {
RENDER_BACKEND.bindBufferBase(target, index, buffer);
GLStateManager.glBindBufferBase(target, index, buffer);
}
public static void bufferStorage(int target, long size, int flags) {
@@ -115,10 +115,14 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
public int getMinGLSLVersion() {return 330;}
@Override
protected void setSwapInterval(boolean vsync) {Display.setVSyncEnabled(vsync);}
protected VSyncMode applyVSyncMode(VSyncMode preferred) {
final boolean enabled = preferred.tearFree();
Display.setVSyncEnabled(enabled);
return enabled ? VSyncMode.ON : VSyncMode.OFF;
}
@Override
public int getDisplayRefreshRateHz() {
protected int queryDisplayRefreshRateHz() {
final DisplayMode mode = Display.getDisplayMode();
if (mode != null && mode.getFrequency() > 0) return mode.getFrequency();
final DisplayMode desktop = Display.getDesktopDisplayMode();
@@ -12,6 +12,7 @@ import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
/**
* Abstract rendering backend.
@@ -62,47 +63,77 @@ public abstract class RenderBackend {
public abstract void flush();
public abstract void finish();
protected abstract void setSwapInterval(boolean vsync);
private VSyncMode preferredVSyncMode = VSyncMode.AUTO;
private VSyncMode effectiveVSyncMode = VSyncMode.ON;
private VSyncMode preferredTearFreeMode;
public final void setVSyncMode(VSyncMode preferred, boolean vsyncEnabled) {
preferredVSyncMode = preferred == null ? VSyncMode.AUTO : preferred;
effectiveVSyncMode = applyVSyncMode(preferredVSyncMode, vsyncEnabled);
public final void setVSyncEnabled(boolean enabled) {
setVSyncMode(enabled ? preferredTearFreeMode() : VSyncMode.OFF);
}
protected VSyncMode applyVSyncMode(VSyncMode preferred, boolean vsyncEnabled) {
final VSyncMode mode = preferred.resolve(vsyncEnabled).swapIntervalEquivalent();
setSwapInterval(mode.usesVSyncSwapInterval());
return mode;
public final void setVSyncMode(VSyncMode preferred) {
setVSyncPreference(preferred);
publishVSyncMode(applyVSyncMode(preferred));
}
public final void setVSyncPreference(VSyncMode preferred) {
if (preferred.tearFree()) preferredTearFreeMode = preferred;
}
public final VSyncMode preferredTearFreeMode() {
return resolveTearFreeMode(preferredTearFreeMode, this::supportsVSyncMode);
}
static VSyncMode resolveTearFreeMode(VSyncMode preferred, Predicate<VSyncMode> supported) {
if (preferred != null && preferred.tearFree() && supported.test(preferred)) return preferred;
return supported.test(VSyncMode.MAILBOX) ? VSyncMode.MAILBOX : VSyncMode.ON;
}
protected abstract VSyncMode applyVSyncMode(VSyncMode preferred);
public boolean supportsVSyncMode(VSyncMode mode) { return mode != VSyncMode.MAILBOX; }
public int getMinRenderAhead() { return 0; }
public int getMaxRenderAhead() { return 9; }
public void setRenderAheadLimit(int frames) {}
public static int clampRenderAhead(int value, int min, int max) {
if (value <= 0) return min <= 0 ? 0 : max;
return Math.min(Math.max(value, Math.max(min, 1)), max);
}
public final VSyncMode getPreferredVSyncMode() { return preferredVSyncMode; }
protected final void publishVSyncMode(VSyncMode mode) { effectiveVSyncMode = mode; }
public final VSyncMode getEffectiveVSyncMode() { return effectiveVSyncMode; }
public int getDisplayRefreshRateHz() { return 0; }
private static final long REFRESH_POLL_NANOS = 1_000_000_000L;
private long refreshPollNanos;
private int cachedRefreshHz;
public final int getDisplayRefreshRateHz() {
final long now = System.nanoTime();
if (refreshPollNanos == 0L || now - refreshPollNanos >= REFRESH_POLL_NANOS) {
refreshPollNanos = now;
cachedRefreshHz = queryDisplayRefreshRateHz();
}
return cachedRefreshHz;
}
protected int queryDisplayRefreshRateHz() { return 0; }
public static int refreshHzFrom(int numerator, int denominator, float fallbackHz) {
if (numerator > 0 && denominator > 0) return (numerator + denominator - 1) / denominator;
return fallbackHz > 0.0f ? (int) Math.ceil(fallbackHz) : 0;
}
public String getPresentDebugInfo() { return null; }
public boolean hasSwapchainBackpressure() { return false; }
private long frameGateNanos;
private long frameGateEndNanos;
public boolean gateAnchorsNextFrameStart() { return false; }
public boolean wantsDisplayUpdateGateTiming() { return true; }
public void recordFrameGate(long durationNanos, long endNanos) {
frameGateNanos = durationNanos;
frameGateEndNanos = endNanos;
}
public long lastFrameGateNanos() { return frameGateNanos; }
public long lastFrameGateEndNanos() { return frameGateEndNanos; }
public void onFrameBegin() {
if (GLStateManager.takeStateSeedPending()) {
@@ -1,54 +1,11 @@
package com.gtnewhorizons.angelica.glsm.backend;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public enum VSyncMode {
AUTO,
OFF,
ON,
MAILBOX,
OFF;
private static final List<VSyncMode> OFF_ORDER = List.of(OFF, MAILBOX, ON);
private static final List<VSyncMode> MAILBOX_ORDER = List.of(MAILBOX, ON);
private static final List<VSyncMode> ON_ORDER = List.of(ON);
private static final List<VSyncMode> DISPLAY_ORDER = List.of(OFF, ON, MAILBOX);
public boolean blocksOnVBlank() {
return this == ON;
}
public boolean usesVSyncSwapInterval() {
return this == ON || this == MAILBOX;
}
public VSyncMode swapIntervalEquivalent() {
return this == MAILBOX ? ON : this;
}
public VSyncMode resolve(boolean vsyncEnabled) {
return this != AUTO ? this : (vsyncEnabled ? ON : OFF);
}
public List<VSyncMode> fallbackOrder() {
return switch (this) {
case OFF -> OFF_ORDER;
case MAILBOX -> MAILBOX_ORDER;
default -> ON_ORDER;
};
}
MAILBOX;
public boolean tearFree() {
return this != OFF;
}
public static List<VSyncMode> selectable(Predicate<VSyncMode> supported) {
final List<VSyncMode> modes = new ArrayList<>(DISPLAY_ORDER.size());
for (VSyncMode mode : DISPLAY_ORDER) {
if (supported.test(mode)) modes.add(mode);
}
return modes;
}
}
@@ -103,6 +103,7 @@ public final class FFPUniformBlock {
public static final int TEX_ENV_COLOR_2 = vec4("u_TexEnvColor2");
public static final int TEX_ENV_COLOR_3 = vec4("u_TexEnvColor3");
public static final int OVERLAY_COLOR = vec4("u_OverlayColor");
public static final int SECONDARY_COLOR = vec3("u_SecondaryColor");
public static final int FOG_PARAMS = vec4("u_FogParams");
public static final int FOG_COLOR = vec4("u_FogColor");
@@ -0,0 +1,83 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import org.joml.Matrix3f;
import org.joml.Vector3f;
import org.joml.Vector4f;
public final class FFPVertexLighting {
private static final Vector3f scratchNormal = new Vector3f();
private static final Vector3f scratchLightVec = new Vector3f();
private static final Vector4f scratchEyePos = new Vector4f();
private static final Matrix3f scratchNormalMat = new Matrix3f();
private static final Vector3f scratchVec = new Vector3f();
private FFPVertexLighting() {}
public static boolean modulatesVertexColor(Vector3f destFactor) {
destFactor.set(1.0f);
if (!GLStateManager.getLightingState().isEnabled()) return false;
if (!GLStateManager.getColorMaterial().isEnabled()) return false;
if (VertexKey.encodeColorMaterialMode(GLStateManager.getColorMaterialParameter().getValue()) != VertexKey.CM_AMBIENT_AND_DIFFUSE) return false;
final MaterialState material = GLStateManager.getFrontMaterial();
if (material.emission.x != 0.0f || material.emission.y != 0.0f || material.emission.z != 0.0f)
return false;
final boolean materialHasSpecular = material.shininess > 0.0f && (material.specular.x != 0.0f || material.specular.y != 0.0f || material.specular.z != 0.0f);
final Vector4f lightModelAmbient = GLStateManager.getLightModel().ambient;
float r = lightModelAmbient.x;
float g = lightModelAmbient.y;
float b = lightModelAmbient.z;
final var lightEnables = GLStateManager.getLightStates();
final var lightData = GLStateManager.getLightDataStates();
boolean normalReady = false;
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
if (!lightEnables[i].isEnabled()) continue;
final LightState light = lightData[i];
if (materialHasSpecular && (light.specular.x != 0.0f || light.specular.y != 0.0f || light.specular.z != 0.0f))
return false;
if (!normalReady) {
transformCurrentNormal();
normalReady = true;
}
final float nDotVP = Math.max(scratchNormal.dot(lightDirection(light)), 0.0f);
r += light.ambient.x + nDotVP * light.diffuse.x;
g += light.ambient.y + nDotVP * light.diffuse.y;
b += light.ambient.z + nDotVP * light.diffuse.z;
}
if (r == 1.0f && g == 1.0f && b == 1.0f) return false;
if (!Float.isFinite(r + g + b)) return false;
destFactor.set(r, g, b);
return true;
}
private static void transformCurrentNormal() {
GLStateManager.getModelViewMatrix().normal(scratchNormalMat);
scratchNormal.set(ShaderManager.getCurrentNormal());
scratchNormalMat.transform(scratchNormal);
if (GLStateManager.getNormalizeState().isEnabled()) {
scratchNormal.normalize();
} else if (GLStateManager.getRescaleNormalState().isEnabled()) {
scratchNormal.mul(Uniforms.rescaleFactor(scratchNormalMat, scratchVec));
}
}
private static Vector3f lightDirection(LightState light) {
if (light.position.w == 0.0f) {
scratchLightVec.set(light.position.x, light.position.y, light.position.z);
} else {
scratchEyePos.set(0.0f, 0.0f, 0.0f, 1.0f).mul(GLStateManager.getModelViewMatrix());
scratchLightVec.set(light.position.x - scratchEyePos.x, light.position.y - scratchEyePos.y, light.position.z - scratchEyePos.z);
}
return scratchLightVec.normalize();
}
}
@@ -0,0 +1,583 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectSet;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCalloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Supplies {@code mc_midTexCoord}/{@code at_tangent} for mod geometry drawn through the FFPclient-array path
* ({@code glVertexPointer}/{@code glTexCoordPointer} + {@code glDrawArrays}) out of a VBO, which bypasses the
* Tessellator and display-list ext-injection paths.
*
* <p>Tangents are reconstructed from the flat face normal (handler's {@code normalOffset < 0} path).
*/
public final class FfpExtendedAttribs {
private FfpExtendedAttribs() {}
private static final int POSITION_LOC = Usage.POSITION.getAttributeLocation();
private static final int UV_LOC = Usage.PRIMARY_UV.getAttributeLocation();
private static final int NORMAL_LOC = Usage.NORMAL.getAttributeLocation();
private static final int SRC_TEX_OFFSET = 12;
private static final int SRC_NORMAL_OFFSET = 20;
private static final int SRC_STRIDE_FLAT = 20;
private static final int SRC_STRIDE_SMOOTH = 24;
private static final int MAX_CACHE = 1024;
private static final int MAX_EXT_VERTS = 1 << 20;
private static final int MAX_RESPECS = 8;
private static final int RESPEC_DECAY_FRAMES = 2;
private static final int MIN_STREAM_SPAN_FRAMES = 4;
private static final int STREAM_EXPIRE_FRAMES = 60;
private static final HashMap<Key, ExtBuffer> cache = new HashMap<>();
private static final Key lookup = new Key();
private static int internalGlDepth = 0;
private static final HashMap<EboKey, Boolean> eboPatternCache = new HashMap<>();
private static final EboKey eboLookup = new EboKey();
private static final Int2ObjectMap<ObjectSet<Key>> keysBySource = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ObjectSet<EboKey>> eboKeysBySource = new Int2ObjectOpenHashMap<>();
private static final Int2IntMap respecCounts = new Int2IntOpenHashMap();
private static final Int2IntMap respecFrames = new Int2IntOpenHashMap();
private static final Int2IntMap respecRunStart = new Int2IntOpenHashMap();
private static final IntSet streamingSources = new IntOpenHashSet();
private static final IntSet persistentSources = new IntOpenHashSet();
private static int frame;
public static void endFrame() { frame++; }
public static boolean isEmpty() {
return cache.isEmpty() && eboPatternCache.isEmpty();
}
private static int internalDrawDepth = 0;
public static void beginInternalDraw() { internalDrawDepth++; }
public static void endInternalDraw() { if (internalDrawDepth > 0) internalDrawDepth--; }
private static void beginInternalGl() { internalGlDepth++; }
private static void endInternalGl() { if (internalGlDepth > 0) internalGlDepth--; }
private static boolean isNotConventionalTexturedArray(VAOManager.Attrib a) {
return a == null || !a.enabled || a.genericPointer || a.vboId == 0 || a.size < 2;
}
private static boolean isStreaming(int vboId) {
if (vboId == 0 || !streamingSources.contains(vboId)) return false;
if (persistentSources.contains(vboId)) return true;
if (frame - respecFrames.get(vboId) > STREAM_EXPIRE_FRAMES) {
streamingSources.remove(vboId);
respecFrames.remove(vboId);
return false;
}
return true;
}
public static boolean maybeBind(int mode, int first, int count) {
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
if (h == null) return false;
if (internalDrawDepth > 0) return false;
final int extPrim = ImmediateExtendedAttribHandler.extPrimVerts(mode, count);
if (extPrim == 0 || first < 0) return false;
if (!h.wantsExtended()) return false;
final VAOManager.Attrib pos = VAOManager.get(POSITION_LOC);
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId)) return false;
VAOManager.Attrib normal = extPrim == 3 ? VAOManager.get(NORMAL_LOC) : null;
if (normal != null && (!normal.enabled || normal.genericPointer || normal.vboId == 0 || normal.size < 3
|| isStreaming(normal.vboId))) {
normal = null;
}
return bindExt(h, mode, first, count, extPrim, pos, uv, normal);
}
public static boolean maybeBindIndexed(int mode, int indexCount, int indexType, long indicesOffset) {
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
if (h == null) return false;
if (internalDrawDepth > 0) return false;
if (mode != GL11.GL_TRIANGLES || indexCount <= 0 || indexCount % 6 != 0) return false;
if (!h.wantsExtended()) return false;
final VAOManager.Attrib pos = VAOManager.get(POSITION_LOC);
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId) || isStreaming(VAOManager.boundEBO)) return false;
final int indexSize = indexTypeBytes(indexType);
if (indexSize == 0) return false;
if (indicesOffset % (6L * indexSize) != 0) return false;
final int first = (int) (indicesOffset / indexSize) / 6 * 4;
final int count = indexCount / 6 * 4;
if (!isQuadPatternEbo(VAOManager.boundEBO, indicesOffset, indexCount, indexType, indexSize, first)) return false;
// Build/bind as GL_QUADS (the real primitive): face-normal tangents, 4-vertex tiles, no per-vertex normal.
return bindExt(h, GL11.GL_QUADS, first, count, 4, pos, uv, null);
}
private static boolean isQuadPatternEbo(int ebo, long offset, int indexCount, int indexType, int indexSize, int firstVertex) {
if (ebo == 0) return false;
eboLookup.set(ebo, offset, indexCount, indexType);
final Boolean cached = eboPatternCache.get(eboLookup);
if (cached != null) return cached;
if (eboPatternCache.size() >= MAX_CACHE) {
eboPatternCache.clear();
eboKeysBySource.clear();
}
final ByteBuffer buf = memCalloc(indexCount, indexSize);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, buf);
final long addr = memAddress0(buf);
boolean match = true;
for (int q = 0, quadCount = indexCount / 6; q < quadCount; q++) {
final int base = firstVertex + q * 4;
final long p = addr + (long) q * 6 * indexSize;
if (readIndex(p, 0, indexSize) != base
|| readIndex(p, 1, indexSize) != base + 1
|| readIndex(p, 2, indexSize) != base + 2
|| readIndex(p, 3, indexSize) != base + 2
|| readIndex(p, 4, indexSize) != base + 3
|| readIndex(p, 5, indexSize) != base) {
match = false;
break;
}
}
memFree(buf);
final EboKey stored = eboLookup.copy();
eboPatternCache.put(stored, match);
ObjectSet<EboKey> ebos = eboKeysBySource.get(stored.ebo);
if (ebos == null) eboKeysBySource.put(stored.ebo, ebos = new ObjectOpenHashSet<>());
ebos.add(stored);
return match;
}
private static long readIndex(long base, int i, int indexSize) {
return switch (indexSize) {
case 1 -> memGetByte(base + i) & 0xFFL;
case 2 -> memGetShort(base + (long) i * 2) & 0xFFFFL;
default -> memGetInt(base + (long) i * 4) & 0xFFFFFFFFL;
};
}
private static int indexTypeBytes(int indexType) {
return switch (indexType) {
case GL11.GL_UNSIGNED_BYTE -> 1;
case GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_UNSIGNED_INT -> 4;
default -> 0;
};
}
private static boolean bindExt(ImmediateExtendedAttribHandler h, int mode, int first, int count, int extPrim,
VAOManager.Attrib pos, VAOManager.Attrib uv, VAOManager.Attrib normal) {
final int extVbo = getOrBuild(h, pos, uv, normal, first, count, extPrim, mode);
if (extVbo == 0) return false;
final int savedVBO = GLStateManager.getBoundVBO();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, extVbo);
ImmediateExtendedAttribHandler.setupExtAttribPointers(0L, ImmediateExtendedAttribHandler.EXT_STRIDE);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
return true;
}
public static void unbind() {
GLStateManager.glDisableVertexAttribArray(ImmediateExtendedAttribHandler.LOC_MID_TEX);
GLStateManager.glDisableVertexAttribArray(ImmediateExtendedAttribHandler.LOC_TANGENT);
setNeutralCurrentValues();
}
public static void setNeutralCurrentValues() {
GLStateManager.glVertexAttrib2f(ImmediateExtendedAttribHandler.LOC_MID_TEX, 0.5f, 0.5f);
GLStateManager.glVertexAttrib4f(ImmediateExtendedAttribHandler.LOC_TANGENT, 1.0f, 0.0f, 0.0f, 1.0f);
}
public static void onDeleteBuffer(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
dropDerivedFrom(vboId);
respecCounts.remove(vboId);
respecFrames.remove(vboId);
respecRunStart.remove(vboId);
streamingSources.remove(vboId);
persistentSources.remove(vboId);
}
public static void onBufferRespecified(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
if (streamingSources.contains(vboId)) {
respecFrames.put(vboId, frame);
return;
}
if (!dropDerivedFrom(vboId)) return;
final boolean sameRun = respecCounts.containsKey(vboId) && frame - respecFrames.get(vboId) <= RESPEC_DECAY_FRAMES;
if (!sameRun) {
startRespecRun(vboId);
return;
}
final int respecs = respecCounts.get(vboId) + 1;
final int span = frame - respecRunStart.get(vboId);
if (respecs >= MAX_RESPECS && span >= MIN_STREAM_SPAN_FRAMES && span <= respecs) {
markStreaming(vboId);
return;
}
if (span > respecs) {
startRespecRun(vboId);
return;
}
respecCounts.put(vboId, respecs);
respecFrames.put(vboId, frame);
}
private static void startRespecRun(int vboId) {
respecCounts.put(vboId, 1);
respecRunStart.put(vboId, frame);
respecFrames.put(vboId, frame);
}
public static void markStreaming(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
respecCounts.remove(vboId);
respecRunStart.remove(vboId);
respecFrames.put(vboId, frame);
if (streamingSources.add(vboId)) dropDerivedFrom(vboId);
}
public static void markPersistent(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
markStreaming(vboId);
persistentSources.add(vboId);
}
public static void reset() {
invalidateAll();
respecCounts.clear();
respecFrames.clear();
respecRunStart.clear();
streamingSources.clear();
persistentSources.clear();
}
private static boolean dropDerivedFrom(int srcId) {
final ObjectSet<EboKey> ebos = eboKeysBySource.remove(srcId);
if (ebos != null) {
for (EboKey k : ebos) eboPatternCache.remove(k);
}
final ObjectSet<Key> keys = keysBySource.get(srcId);
if (keys == null || keys.isEmpty()) return ebos != null;
IntArrayList toFree = null;
for (Key k : keys.toArray(new Key[0])) {
final ExtBuffer buf = cache.remove(k);
if (buf != null && buf.vbo != 0) {
if (toFree == null) toFree = new IntArrayList();
toFree.add(buf.vbo);
}
unindexKey(k);
}
freeExtVbos(toFree);
return true;
}
private static void indexKey(Key k) {
addToBucket(k.posVBO, k);
if (k.uvVBO != k.posVBO) addToBucket(k.uvVBO, k);
if (k.normalVBO != k.posVBO && k.normalVBO != k.uvVBO) addToBucket(k.normalVBO, k);
}
private static void unindexKey(Key k) {
removeFromBucket(k.posVBO, k);
if (k.uvVBO != k.posVBO) removeFromBucket(k.uvVBO, k);
if (k.normalVBO != k.posVBO && k.normalVBO != k.uvVBO) removeFromBucket(k.normalVBO, k);
}
private static void addToBucket(int src, Key k) {
if (src == 0) return;
ObjectSet<Key> set = keysBySource.get(src);
if (set == null) keysBySource.put(src, set = new ObjectOpenHashSet<>());
set.add(k);
}
private static void removeFromBucket(int src, Key k) {
if (src == 0) return;
final ObjectSet<Key> set = keysBySource.get(src);
if (set != null && set.remove(k) && set.isEmpty()) keysBySource.remove(src);
}
private static int getOrBuild(ImmediateExtendedAttribHandler h, VAOManager.Attrib pos, VAOManager.Attrib uv,
VAOManager.Attrib normal, int first, int count, int extPrim, int mode) {
if (first + count > MAX_EXT_VERTS) return 0;
lookup.set(pos, uv, normal, mode, first % extPrim);
ExtBuffer buf = cache.get(lookup);
if (buf == null) {
if (cache.size() >= MAX_CACHE) invalidateExtBuffers();
buf = new ExtBuffer();
final Key stored = lookup.copy();
cache.put(stored, buf);
indexKey(stored);
}
final long range = (long) first << 32 | (count & 0xFFFFFFFFL);
if (buf.vbo != 0 && buf.builtRanges.contains(range)) return buf.vbo;
if (!ensureCapacity(buf, first + count)) return 0;
buildRange(h, buf.vbo, pos, uv, normal, first, count, extPrim);
buf.builtRanges.add(range);
return buf.vbo;
}
private static boolean ensureCapacity(ExtBuffer buf, int neededVerts) {
if (buf.vbo != 0 && buf.capacityVerts >= neededVerts) return true;
final int extStride = ImmediateExtendedAttribHandler.EXT_STRIDE;
final int newCap = Math.min(MAX_EXT_VERTS, Math.max(neededVerts, buf.capacityVerts * 2));
beginInternalGl();
try {
final int vbo = GLStateManager.glGenBuffers();
if (vbo == 0) return false;
final int savedVBO = GLStateManager.getBoundVBO();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, (long) newCap * extStride, GL15.GL_STATIC_DRAW);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
if (buf.vbo != 0) {
final int savedRead = GLStateManager.getBoundCopyReadBuffer();
final int savedWrite = GLStateManager.getBoundCopyWriteBuffer();
GLStateManager.glBindBuffer(GL31.GL_COPY_READ_BUFFER, buf.vbo);
GLStateManager.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, vbo);
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0L, 0L,
(long) buf.capacityVerts * extStride);
GLStateManager.glBindBuffer(GL31.GL_COPY_READ_BUFFER, savedRead);
GLStateManager.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, savedWrite);
GLStateManager.glDeleteBuffers(buf.vbo);
}
buf.vbo = vbo;
buf.capacityVerts = newCap;
return true;
} finally {
endInternalGl();
}
}
private static void buildRange(ImmediateExtendedAttribHandler h, int extVbo, VAOManager.Attrib pos,
VAOManager.Attrib uv, VAOManager.Attrib normal, int first, int count, int extPrim) {
final int posStride = pos.effectiveStride();
final int uvStride = uv.effectiveStride();
final boolean smooth = normal != null;
final int normalStride = smooth ? normal.effectiveStride() : 0;
final int srcStride = smooth ? SRC_STRIDE_SMOOTH : SRC_STRIDE_FLAT;
final int srcNormalOffset = smooth ? SRC_NORMAL_OFFSET : -1;
final ByteBuffer posBuf = readRange(pos, first, count, posStride);
final ByteBuffer uvBuf = readRange(uv, first, count, uvStride);
final ByteBuffer normalBuf = smooth ? readRange(normal, first, count, normalStride) : null;
final ByteBuffer src = memAlloc(count * srcStride).order(ByteOrder.nativeOrder());
for (int i = 0; i < count; i++) {
final int pb = i * posStride, ub = i * uvStride, d = i * srcStride;
src.putFloat(d, pos.readComponent(posBuf, pb, 0));
src.putFloat(d + 4, pos.readComponent(posBuf, pb, 1));
src.putFloat(d + 8, pos.size >= 3 ? pos.readComponent(posBuf, pb, 2) : 0f);
src.putFloat(d + SRC_TEX_OFFSET, uv.readComponent(uvBuf, ub, 0));
src.putFloat(d + SRC_TEX_OFFSET + 4, uv.readComponent(uvBuf, ub, 1));
if (smooth) {
final int nb = i * normalStride;
src.putInt(d + SRC_NORMAL_OFFSET, NormI8.pack(
normal.readComponent(normalBuf, nb, 0),
normal.readComponent(normalBuf, nb, 1),
normal.readComponent(normalBuf, nb, 2)));
}
}
final int extStride = ImmediateExtendedAttribHandler.EXT_STRIDE;
final ByteBuffer ext = memCalloc(count, extStride);
h.buildPacked(memAddress0(src), srcStride, 0, SRC_TEX_OFFSET, srcNormalOffset, count, extPrim, memAddress0(ext), extStride);
beginInternalGl();
try {
final int savedVBO = GLStateManager.getBoundVBO();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, extVbo);
ext.position(0).limit(count * extStride);
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, (long) first * extStride, ext);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
} finally {
endInternalGl();
}
memFree(posBuf);
memFree(uvBuf);
if (normalBuf != null) memFree(normalBuf);
memFree(src);
memFree(ext);
}
private static ByteBuffer readRange(VAOManager.Attrib a, int first, int count, int stride) {
final int elementBytes = a.size * a.typeSizeBytes();
final int bytes = (count - 1) * stride + elementBytes;
final ByteBuffer dst = memAlloc(bytes).order(ByteOrder.nativeOrder());
dst.position(0).limit(bytes);
final int savedVBO = GLStateManager.getBoundVBO();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, a.offset + (long) first * stride, dst);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
dst.position(0).limit(bytes);
return dst;
}
private static void invalidateAll() {
eboPatternCache.clear();
eboKeysBySource.clear();
invalidateExtBuffers();
}
private static void invalidateExtBuffers() {
if (cache.isEmpty()) return;
final IntArrayList toFree = new IntArrayList(cache.size());
for (ExtBuffer buf : cache.values()) {
if (buf.vbo != 0) toFree.add(buf.vbo);
}
cache.clear();
keysBySource.clear();
freeExtVbos(toFree);
}
private static void freeExtVbos(IntArrayList vbos) {
if (vbos == null || vbos.isEmpty()) return;
beginInternalGl();
try {
for (int i = 0; i < vbos.size(); i++) {
GLStateManager.glDeleteBuffers(vbos.getInt(i));
}
} finally {
endInternalGl();
}
}
private static final class ExtBuffer {
int vbo;
int capacityVerts;
final LongOpenHashSet builtRanges = new LongOpenHashSet();
}
private static final class EboKey {
int ebo, count, type;
long offset;
void set(int ebo, long offset, int count, int type) {
this.ebo = ebo;
this.offset = offset;
this.count = count;
this.type = type;
}
EboKey copy() {
final EboKey k = new EboKey();
k.set(ebo, offset, count, type);
return k;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof EboKey k)) return false;
return ebo == k.ebo && count == k.count && type == k.type && offset == k.offset;
}
@Override
public int hashCode() {
int h = ebo;
h = h * 31 + count;
h = h * 31 + type;
h = h * 31 + Long.hashCode(offset);
return h;
}
}
private static final class Key {
int posVBO, uvVBO, normalVBO, posStride, uvStride, normalStride, posFmt, uvFmt, normalFmt, mode, phase;
long posOffset, uvOffset, normalOffset;
void set(VAOManager.Attrib pos, VAOManager.Attrib uv, VAOManager.Attrib normal, int mode, int phase) {
posVBO = pos.vboId; posOffset = pos.offset; posStride = pos.effectiveStride(); posFmt = fmt(pos);
uvVBO = uv.vboId; uvOffset = uv.offset; uvStride = uv.effectiveStride(); uvFmt = fmt(uv);
if (normal != null) {
normalVBO = normal.vboId; normalOffset = normal.offset;
normalStride = normal.effectiveStride(); normalFmt = fmt(normal);
} else {
normalVBO = 0; normalOffset = 0; normalStride = 0; normalFmt = 0;
}
this.mode = mode;
this.phase = phase;
}
private static int fmt(VAOManager.Attrib a) {
return (a.size & 0x7F) | (a.normalized ? 0x80 : 0) | (a.type << 8);
}
Key copy() {
final Key k = new Key();
k.posVBO = posVBO; k.posOffset = posOffset; k.posStride = posStride; k.posFmt = posFmt;
k.uvVBO = uvVBO; k.uvOffset = uvOffset; k.uvStride = uvStride; k.uvFmt = uvFmt;
k.normalVBO = normalVBO; k.normalOffset = normalOffset; k.normalStride = normalStride; k.normalFmt = normalFmt;
k.mode = mode; k.phase = phase;
return k;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Key k)) return false;
return posVBO == k.posVBO && uvVBO == k.uvVBO && normalVBO == k.normalVBO
&& posStride == k.posStride && uvStride == k.uvStride && normalStride == k.normalStride
&& posFmt == k.posFmt && uvFmt == k.uvFmt && normalFmt == k.normalFmt
&& mode == k.mode && phase == k.phase
&& posOffset == k.posOffset && uvOffset == k.uvOffset && normalOffset == k.normalOffset;
}
@Override
public int hashCode() {
int h = posVBO;
h = h * 31 + uvVBO;
h = h * 31 + normalVBO;
h = h * 31 + posStride;
h = h * 31 + uvStride;
h = h * 31 + normalStride;
h = h * 31 + posFmt;
h = h * 31 + uvFmt;
h = h * 31 + normalFmt;
h = h * 31 + mode;
h = h * 31 + phase;
h = h * 31 + Long.hashCode(posOffset);
h = h * 31 + Long.hashCode(uvOffset);
h = h * 31 + Long.hashCode(normalOffset);
return h;
}
}
}
@@ -12,7 +12,7 @@ import org.lwjgl.opengl.GL13;
* Packed-long fragment shader permutation key for FFP emulation.
*
* Layout:
* long[0]: global bits (12) + unit 0 (47 bits at offset 12)
* long[0]: global bits (13) + unit 0 (47 bits at offset 13)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
@@ -63,7 +63,7 @@ public final class FragmentKey {
public static final int FOG_EXP = 2;
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 12;
private static final int GLOBAL_BITS = 13;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
@@ -71,6 +71,7 @@ public final class FragmentKey {
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
private static final int BIT_LINE_STIPPLE = 11; // 1 bit
private static final int BIT_COLOR_SUM = 12; // 1 bit
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
@@ -130,10 +131,12 @@ public final class FragmentKey {
global |= (1L << BIT_LINE_STIPPLE);
}
// Separate specular
// Separate specular / color sum
if (GLStateManager.getLightingState().isEnabled()
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
global |= (1L << BIT_SEPARATE_SPECULAR);
} else if (GLStateManager.getColorSumState().isEnabled()) {
global |= (1L << BIT_COLOR_SUM);
}
// Per-unit state
@@ -238,6 +241,7 @@ public final class FragmentKey {
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
public boolean colorSum() { return ((packed[0] >> BIT_COLOR_SUM) & 1) != 0; }
public boolean lineStipple() { return ((packed[0] >> BIT_LINE_STIPPLE) & 1) != 0; }
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
@@ -374,10 +378,10 @@ public final class FragmentKey {
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b overlay=%b units=%d",
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d",
fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
separateSpecular(), overlayEnabled(), nrEnabledUnits()));
separateSpecular(), colorSum(), overlayEnabled(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
@@ -276,6 +276,10 @@ public final class FragmentShaderGenerator {
sb.append(" // Add separate specular\n");
sb.append(" color.rgb += v_SpecularColor;\n");
}
if (key.colorSum()) {
sb.append(" // Color sum - GL_COLOR_SUM with the current secondary color\n");
sb.append(" color.rgb += u_SecondaryColor;\n");
}
}
private static void emitAlphaTest(StringBuilder sb, FragmentKey key) {
@@ -2,12 +2,12 @@ package com.gtnewhorizons.angelica.glsm.ffp;
public final class InstancedAttribs {
public static final int LOC_MATRIX_COL0 = 7;
public static final int LOC_MATRIX_COL1 = 8;
public static final int LOC_MATRIX_COL2 = 9;
public static final int LOC_MATRIX_COL3 = 10;
public static final int LOC_COLOR = 11;
public static final int LOC_LIGHTMAP = 12;
public static final int LOC_MATRIX_COL0 = 5;
public static final int LOC_MATRIX_COL1 = 6;
public static final int LOC_MATRIX_COL2 = 7;
public static final int LOC_MATRIX_COL3 = 8;
public static final int LOC_COLOR = 9;
public static final int LOC_LIGHTMAP = 10;
public static final int OFFSET_MATRIX = 0;
public static final int OFFSET_COLOR = 64;
@@ -179,6 +179,7 @@ public final class ShaderManager {
sm.lastFramePreDrawCalls = sm.preDrawCalls;
sm.preDrawCalls = 0;
sm.uniforms.endFrame();
FfpExtendedAttribs.endFrame();
}
public static void setCurrentNormal(float x, float y, float z) {
@@ -27,7 +27,6 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCalloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Maintains the shared {@link FFPUniformBlock} image from GLSM cached state and streams it to the UBO ring.
@@ -209,7 +208,7 @@ public class Uniforms {
ring = new UniformRingBuffer(RING_CAPACITY, FFPUniformBlock.SIZE);
}
final int offset = ring.writeBlock(staging);
RENDER_BACKEND.bindBufferRange(GL31.GL_UNIFORM_BUFFER, FFPUniformBlock.BINDING_POINT,
GLStateManager.glBindBufferRange(GL31.GL_UNIFORM_BUFFER, FFPUniformBlock.BINDING_POINT,
ring.getBufferId(), offset, FFPUniformBlock.SIZE);
bound = true;
blockWrites++;
@@ -232,8 +231,12 @@ public class Uniforms {
private void stageNormalMatrix() {
GLStateManager.getModelViewMatrix().normal(normalMatrix);
putMat3(FFPUniformBlock.NORMAL_MATRIX, normalMatrix);
final float scale = 1.0f / normalMatrix.getColumn(0, tempVec3).length();
putFloat(FFPUniformBlock.NORMAL_SCALE, scale);
putFloat(FFPUniformBlock.NORMAL_SCALE, rescaleFactor(normalMatrix, tempVec3));
}
static float rescaleFactor(Matrix3f normalMatrix, Vector3f scratch) {
final float f = normalMatrix.getColumn(2, scratch).lengthSquared();
return f > 1.0e-12f ? 1.0f / (float) Math.sqrt(f) : 1.0f;
}
private void stageTextureMatrices() {
@@ -327,6 +330,9 @@ public class Uniforms {
GLStateManager.getOverlayR(), GLStateManager.getOverlayG(),
GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
final var secondary = GLStateManager.getSecondaryColor();
putVec3(FFPUniformBlock.SECONDARY_COLOR, secondary.getRed(), secondary.getGreen(), secondary.getBlue());
// Mesa STATE_FOG_PARAMS_OPTIMIZED
final FogState fog = GLStateManager.getFogState();
final float start = fog.getStart();
@@ -20,38 +20,36 @@ public final class VertexKey {
public static final int MAX_UNITS = 4;
public static final int FFP_LIGHT_COUNT = 2;
private final long packed;
private static final int BIT_LIGHTING = 0;
private static final int BIT_LIGHT0 = 1;
private static final int BIT_LIGHT1 = 2;
private static final int BIT_LIGHT0_DIR = 3;
private static final int BIT_LIGHT1_DIR = 4;
private static final int BIT_COLOR_MATERIAL = 5;
private static final int BIT_SEPARATE_SPECULAR = 6;
private static final int BIT_FOG = 7;
private static final int BIT_FOG_DIST_MODE = 8; // 2 bits: 8-9
private static final int BIT_NORMALIZE = 10;
private static final int BIT_RESCALE_NORMAL = 11;
// Per-unit texcoord enable: bits 12-15 (unit 0..3). Unit 1 doubles as the lightmap flag.
private static final int BIT_UNIT_TEX_BASE = 12;
private static final int BIT_HAS_VERTEX_COLOR = 16;
private static final int BIT_HAS_VERTEX_NORMAL = 17;
private static final int BIT_HAS_VERTEX_TEX = 18;
private static final int BIT_HAS_VERTEX_LIGHTMAP = 19;
private static final int BIT_COLOR_MAT_MODE = 20; // 3 bits: 20-22
// TexGen modes (unit 0 only, for now): 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR).
private static final int BIT_TEXGEN_S = 23; // 3 bits: 23-25
private static final int BIT_TEXGEN_T = 26; // 3 bits: 26-28
private static final int BIT_TEXGEN_R = 29; // 3 bits: 29-31
private static final int BIT_TEXGEN_Q = 32; // 3 bits: 32-34
private static final int BIT_CLIP_PLANES = 35;
private static final int BIT_WIDE_LINE = 36;
// Per-unit texmat enable: bits 37-40 (unit 0..3). Unit 1 is the lightmap matrix.
private static final int BIT_UNIT_TEXMAT_BASE = 37;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = 41;
private static final int BIT_LINE_STIPPLE = 42;
private static final int BIT_INSTANCED = 44;
private static final int BIT_LIGHT_BASE = BIT_LIGHTING + 1;
private static final int BIT_LIGHT_DIR_BASE = BIT_LIGHT_BASE + FFP_LIGHT_COUNT;
private static final int BIT_COLOR_MATERIAL = BIT_LIGHT_DIR_BASE + FFP_LIGHT_COUNT;
private static final int BIT_SEPARATE_SPECULAR = BIT_COLOR_MATERIAL + 1;
private static final int BIT_FOG = BIT_SEPARATE_SPECULAR + 1;
private static final int BIT_FOG_DIST_MODE = BIT_FOG + 1;
private static final int BIT_NORMALIZE = BIT_FOG_DIST_MODE + 2;
private static final int BIT_RESCALE_NORMAL = BIT_NORMALIZE + 1;
private static final int BIT_UNIT_TEX_BASE = BIT_RESCALE_NORMAL + 1;
private static final int BIT_HAS_VERTEX_COLOR = BIT_UNIT_TEX_BASE + MAX_UNITS;
private static final int BIT_HAS_VERTEX_NORMAL = BIT_HAS_VERTEX_COLOR + 1;
private static final int BIT_HAS_VERTEX_TEX = BIT_HAS_VERTEX_NORMAL + 1;
private static final int BIT_HAS_VERTEX_LIGHTMAP = BIT_HAS_VERTEX_TEX + 1;
private static final int BIT_COLOR_MAT_MODE = BIT_HAS_VERTEX_LIGHTMAP + 1;
// TexGen modes are unit 0 only, for now.
private static final int BIT_TEXGEN_S = BIT_COLOR_MAT_MODE + 3;
private static final int BIT_TEXGEN_T = BIT_TEXGEN_S + 3;
private static final int BIT_TEXGEN_R = BIT_TEXGEN_T + 3;
private static final int BIT_TEXGEN_Q = BIT_TEXGEN_R + 3;
private static final int BIT_CLIP_PLANES = BIT_TEXGEN_Q + 3;
private static final int BIT_WIDE_LINE = BIT_CLIP_PLANES + 1;
private static final int BIT_UNIT_TEXMAT_BASE = BIT_WIDE_LINE + 1;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
private static final int BIT_INSTANCED = BIT_LINE_STIPPLE + 1;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -70,10 +68,8 @@ public final class VertexKey {
public long pack() { return packed; }
public boolean lightingEnabled() { return bit(BIT_LIGHTING); }
public boolean light0Enabled() { return bit(BIT_LIGHT0); }
public boolean light1Enabled() { return bit(BIT_LIGHT1); }
public boolean light0Directional() { return bit(BIT_LIGHT0_DIR); }
public boolean light1Directional() { return bit(BIT_LIGHT1_DIR); }
public boolean lightEnabled(int light) { return bit(BIT_LIGHT_BASE + light); }
public boolean lightDirectional(int light) { return bit(BIT_LIGHT_DIR_BASE + light); }
public boolean colorMaterialEnabled() { return bit(BIT_COLOR_MATERIAL); }
public boolean separateSpecular() { return bit(BIT_SEPARATE_SPECULAR); }
public boolean fogEnabled() { return bit(BIT_FOG); }
@@ -136,17 +132,12 @@ public final class VertexKey {
if (lighting) {
bits |= (1L << BIT_LIGHTING);
if (GLStateManager.getLightStates()[0].isEnabled()) {
bits |= (1L << BIT_LIGHT0);
for (int i = 0; i < FFP_LIGHT_COUNT; i++) {
if (!GLStateManager.getLightStates()[i].isEnabled()) continue;
bits |= (1L << (BIT_LIGHT_BASE + i));
// position.w == 0 means directional
if (GLStateManager.getLightDataStates()[0].position.w == 0.0f) {
bits |= (1L << BIT_LIGHT0_DIR);
}
}
if (GLStateManager.getLightStates()[1].isEnabled()) {
bits |= (1L << BIT_LIGHT1);
if (GLStateManager.getLightDataStates()[1].position.w == 0.0f) {
bits |= (1L << BIT_LIGHT1_DIR);
if (GLStateManager.getLightDataStates()[i].position.w == 0.0f) {
bits |= (1L << (BIT_LIGHT_DIR_BASE + i));
}
}
@@ -265,7 +256,7 @@ public final class VertexKey {
@Override
public String toString() {
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%b]",
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
packed, lightingEnabled(), lightEnabled(0), lightEnabled(1),
colorMaterialEnabled(), fogEnabled(),
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0,
unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
@@ -182,11 +182,10 @@ public final class VertexShaderGenerator {
}
// Per-light accumulation
if (key.light0Enabled()) {
emitLightContribution(sb, key, 0);
}
if (key.light1Enabled()) {
emitLightContribution(sb, key, 1);
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
if (key.lightEnabled(i)) {
emitLightContribution(sb, key, i);
}
}
sb.append(" v_Color = vec4(clamp(color0, 0.0, 1.0), outAlpha);\n");
@@ -198,7 +197,7 @@ public final class VertexShaderGenerator {
private static void emitLightContribution(StringBuilder sb, VertexKey key, int lightIndex) {
final String li = String.valueOf(lightIndex);
final boolean directional = (lightIndex == 0) ? key.light0Directional() : key.light1Directional();
final boolean directional = key.lightDirectional(lightIndex);
final String posUniform = "u_Light" + li + "Position";
sb.append(" { // Light ").append(li).append('\n');
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredBlendHandler {
boolean isBlendLocked();
boolean isOverrideHeld();
void deferBlendModeToggle(boolean enabled);
void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha);
void flushDeferredBlend();
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredDepthColorHandler {
boolean isDepthColorLocked();
boolean isOverrideHeld();
void deferDepthEnable(boolean enabled);
void deferColorMask(boolean r, boolean g, boolean b, boolean a);
}
@@ -6,8 +6,17 @@ public final class GLSMConfig {
public static float lastBrightnessX;
public static float lastBrightnessY;
public static int packBrightness(float x, float y) {
return ((int) y << 16) | ((int) x & 0xFFFF);
}
public static int packedLastBrightness() {
return packBrightness(lastBrightnessX, lastBrightnessY);
}
public static boolean hudCacheOverride;
public static boolean extendedAttribsExpected;
public static volatile boolean expandVertexFormats;
private GLSMConfig() {}
@@ -9,6 +9,7 @@ public final class GLSMHooks {
public static DeferredAlphaHandler alphaHandler;
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
public static ImmediateExtendedAttribHandler immediateExtendedHandler;
public static ShaderWorkSubmitter shaderWorkSubmitter;
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
@@ -25,6 +26,8 @@ public final class GLSMHooks {
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
public static final EventBus<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
public static final EventBus<ForeignDrawEndEvent> FOREIGN_DRAW_END = EventBus.create(ForeignDrawEndEvent.class);
public static final EventBus<VanillaBlendChangeEvent> VANILLA_BLEND_CHANGE = EventBus.create(VanillaBlendChangeEvent.class);
public static final EventBus<LoadingCheckpointEvent> LOADING_CHECKPOINT = EventBus.create(LoadingCheckpointEvent.class);
// Reusable event instances
@@ -38,6 +41,8 @@ public final class GLSMHooks {
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent();
public static final ForeignDrawEndEvent foreignDrawEndEvent = new ForeignDrawEndEvent();
public static final VanillaBlendChangeEvent vanillaBlendChangeEvent = new VanillaBlendChangeEvent();
public static final LoadingCheckpointEvent loadingCheckpointEvent = new LoadingCheckpointEvent();
private GLSMHooks() {}
@@ -0,0 +1,64 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
public interface ImmediateExtendedAttribHandler {
int EXT_STRIDE = 12;
int LOC_MC_ENTITY = 11;
int LOC_MID_TEX = 12;
int LOC_TANGENT = 13;
int LOC_MIDBLOCK = 14;
int RAW_NORMAL_INDEX = 6;
boolean wantsExtended();
boolean wantsTangent();
boolean wantsExtendedCapture();
void build(int[] rawBuffer, int vertexCount, int vertsPerPrim, int normalIntIndex, long dstAddr, int dstStride);
void buildPacked(long srcBase, int stride, int posOffset, int texOffset, int normalOffset,
int vertexCount, int vertsPerPrim, long dstAddr, int dstStride);
static int extPrimVerts(int drawMode) {
if (drawMode == GL11.GL_QUADS) return 4;
if (drawMode == GL11.GL_TRIANGLES) return 3;
return 0;
}
static int extPrimVerts(int drawMode, int vertexCount) {
final int p = extPrimVerts(drawMode);
return (p != 0 && vertexCount > 0 && vertexCount % p == 0) ? p : 0;
}
static int texOffset(VertexFormat format) {
int offset = 0;
for (VertexFormatElement element : format.elementsArray) {
if (element.getUsage() == VertexFormatElement.Usage.PRIMARY_UV) return offset;
offset += element.getByteSize();
}
return -1;
}
static int normalOffset(VertexFormat format) {
int offset = 0;
for (VertexFormatElement element : format.elementsArray) {
if (element.getUsage() == VertexFormatElement.Usage.NORMAL) return offset;
offset += element.getByteSize();
}
return -1;
}
static void setupExtAttribPointers(long baseOffset, int stride) {
GLStateManager.glEnableVertexAttribArray(LOC_MID_TEX);
GLStateManager.glVertexAttribPointer(LOC_MID_TEX, 2, GL11.GL_FLOAT, false, stride, baseOffset);
GLStateManager.glEnableVertexAttribArray(LOC_TANGENT);
GLStateManager.glVertexAttribPointer(LOC_TANGENT, 4, GL11.GL_BYTE, true, stride, baseOffset + 8);
}
}
@@ -1,8 +1,13 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.shader.ShaderType;
import org.taumc.glsl.grammar.GLSLParser;
@FunctionalInterface
public interface ShaderTransformPostProcessor {
void onTransformed(String transformedSource, ShaderType shaderType);
default void onTransformed(String transformedSource, GLSLParser.Translation_unitContext bodyTree, int headerLen, ShaderType shaderType) {
onTransformed(transformedSource, shaderType);
}
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface VanillaBooleanLayer {
boolean isOverrideHeld();
boolean getVanilla();
void setVanilla(boolean enabled);
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface VanillaStateLayer<T> {
boolean isOverrideHeld();
void readVanilla(T into);
void writeVanilla(T from);
static boolean isHeld(VanillaStateLayer<?> layer) {
return layer != null && layer.isOverrideHeld();
}
static <T> void capture(VanillaStateLayer<T> layer, T slot) {
if (isHeld(layer)) layer.readVanilla(slot);
}
static <T> boolean restore(VanillaStateLayer<T> layer, T saved) {
if (!isHeld(layer)) return false;
layer.writeVanilla(saved);
return true;
}
}
@@ -0,0 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ForeignDrawEndEvent extends MutableEvent {
}
@@ -0,0 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class VanillaBlendChangeEvent extends MutableEvent {
}
@@ -0,0 +1,92 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import java.util.ArrayList;
import java.util.List;
public final class SectionRegistry {
public interface Emitter {
long enter(int category, String text);
void leave(long nativeId);
void setup(int category, String name);
boolean isConnected();
}
private static final class Category {
final int id;
final String name;
Category(int id, String name) {
this.id = id;
this.name = name;
}
}
private static final class Section {
final long token;
final int category;
final String text;
long nativeId;
Section(long token, int category, String text, long nativeId) {
this.token = token;
this.category = category;
this.text = text;
this.nativeId = nativeId;
}
}
private final Emitter emitter;
private final List<Category> categories = new ArrayList<>();
private final List<Section> open = new ArrayList<>();
private long nextToken;
private boolean connected;
public SectionRegistry(Emitter emitter) {
this.emitter = emitter;
}
public synchronized void registerCategory(int category, String name) {
categories.add(new Category(category, name));
}
public synchronized long enter(int category, String text) {
syncConnection();
final long token = ++nextToken;
open.add(new Section(token, category, text, emitter.enter(category, text)));
return token;
}
public synchronized void leave(long token) {
for (int i = 0; i < open.size(); i++) {
final Section section = open.get(i);
if (section.token == token) {
open.remove(i);
if (section.nativeId != 0L) emitter.leave(section.nativeId);
return;
}
}
}
public synchronized void poll() {
syncConnection();
}
private void syncConnection() {
final boolean now = emitter.isConnected();
if (now == connected) return;
connected = now;
if (now) replay();
}
private void replay() {
for (int i = 0; i < categories.size(); i++) {
final Category category = categories.get(i);
emitter.setup(category.id, category.name);
}
for (int i = 0; i < open.size(); i++) {
final Section section = open.get(i);
section.nativeId = emitter.enter(section.category, section.text);
}
}
}
@@ -44,6 +44,18 @@ public final class Tracy {
public static final int COLOR_IRIS = 0x9E862E; // gold
public static final int COLOR_FFP = 0x7A4E9E; // purple
public static final int SECTION_WORLD = 1;
public static final int SECTION_SHADERS = 2;
public static final int SECTION_BENCHMARK = 3;
public static final int SECTION_UI = 4;
private static final SectionRegistry SECTIONS = new SectionRegistry(new SectionRegistry.Emitter() {
@Override public long enter(int category, String text) { return BACKEND.sectionEnter(category, text); }
@Override public void leave(long nativeId) { BACKEND.sectionLeave(nativeId); }
@Override public void setup(int category, String name) { BACKEND.sectionSetup(category, name); }
@Override public boolean isConnected() { return BACKEND.isConnected(); }
});
private static final ThreadLocal<ZoneStack> STACK = new ThreadLocal<>() {
@Override
protected ZoneStack initialValue() {
@@ -62,6 +74,10 @@ public final class Tracy {
FINE_ZONES = ENABLED && SystemProperties.TRACY_FINE_ZONES;
if (ENABLED) {
Runtime.getRuntime().addShutdownHook(new Thread(Tracy::shutdown, "Tracy-Shutdown"));
SECTIONS.registerCategory(SECTION_WORLD, "World");
SECTIONS.registerCategory(SECTION_SHADERS, "Shaders");
SECTIONS.registerCategory(SECTION_BENCHMARK, "Benchmark");
SECTIONS.registerCategory(SECTION_UI, "UI");
LOGGER.info("Tracy profiling enabled");
}
}
@@ -95,6 +111,7 @@ public final class Tracy {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but no TracyBackend service present");
return null;
}
if (!implementsCurrentAbi(found)) return null;
final TracyBackend backend = found;
final CountDownLatch done = new CountDownLatch(1);
final AtomicReference<Boolean> result = new AtomicReference<>();
@@ -120,10 +137,28 @@ public final class Tracy {
return backend;
}
private static final String ABI_SENTINEL = "sectionEnter";
private static boolean implementsCurrentAbi(TracyBackend backend) {
try {
backend.getClass().getMethod(ABI_SENTINEL, int.class, String.class);
return true;
} catch (NoSuchMethodException e) {
LOGGER.warn("Tracy backend {} was built against an older TracyBackend interface; profiling disabled", backend.getClass().getName());
return false;
}
}
private record InitRunner(TracyBackend backend, CountDownLatch done, AtomicReference<Boolean> result) implements Runnable {
@Override
public void run() {
final boolean ok = backend.init();
boolean ok;
try {
ok = backend.init();
} catch (LinkageError e) {
LOGGER.warn("Tracy backend {} failed to link; profiling disabled: {}", backend.getClass().getName(), e);
ok = false;
}
if (!result.compareAndSet(null, ok) && ok) {
backend.shutdown();
}
@@ -168,7 +203,7 @@ public final class Tracy {
final String name = stack.poppedName();
if (name != null && WARNED_LEAKS.add(name)) {
LOGGER.warn("Tracy: unbalanced profiler section '{}' left open (leaked by its caller); closed at next root", name);
BACKEND.message("unbalanced profiler section '" + name + "' closed at root");
BACKEND.message("unbalanced profiler section '" + name + "' closed at root", TracyBackend.SEVERITY_WARNING);
}
}
}
@@ -274,6 +309,7 @@ public final class Tracy {
threadNameRefreshed = true;
BACKEND.setCurrentThreadName(Thread.currentThread().getName());
}
SECTIONS.poll();
BACKEND.frameMark();
}
@@ -330,8 +366,22 @@ public final class Tracy {
}
public static void message(String text) {
message(text, TracyBackend.SEVERITY_INFO);
}
public static void message(String text, int severity) {
if (!ENABLED || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
BACKEND.message(text);
BACKEND.message(text, severity);
}
public static long sectionEnter(int category, String text) {
if (!ENABLED || SHUTTING_DOWN) return 0L;
return SECTIONS.enter(category, text);
}
public static void sectionLeave(long token) {
if (token == 0L || !ENABLED || SHUTTING_DOWN) return;
SECTIONS.leave(token);
}
public static boolean isConnected() {
@@ -5,6 +5,13 @@ public interface TracyBackend {
int PLOT_FORMAT_MEMORY = 1;
int PLOT_FORMAT_PERCENTAGE = 2;
int SEVERITY_TRACE = 0;
int SEVERITY_DEBUG = 1;
int SEVERITY_INFO = 2;
int SEVERITY_WARNING = 3;
int SEVERITY_ERROR = 4;
int SEVERITY_FATAL = 5;
boolean init();
long internSrcLoc(String name, int color);
@@ -24,7 +31,14 @@ public interface TracyBackend {
void plotInt(long namePtr, long value);
void plot(long namePtr, double value);
void message(String text);
void message(String text, int severity);
default void message(String text) { message(text, SEVERITY_INFO); }
long sectionEnter(int category, String text);
void sectionLeave(long id);
void sectionSetup(int category, String name);
void setCurrentThreadName(String name);
boolean isConnected();
void shutdown();
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.glsm.profiling;
public final class ZoneStack {
public static final long EMPTY = -1L;
static final int MAX_DEPTH = 512;
public static final int MAX_DEPTH = 512;
private long[] stack = new long[64];
private boolean[] gpu = new boolean[64];
@@ -274,6 +274,13 @@ public final class CommandBufferExecutor {
ptr += 16;
GLStateManager.glColor4f(r, g, b, a);
}
case GLCommand.SECONDARY_COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
final float b = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glSecondaryColor3f(r, g, b);
}
case GLCommand.CLEAR_COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
@@ -356,6 +356,14 @@ public final class CommandRecorder {
writeFloat(a);
}
public void writeSecondaryColor(float r, float g, float b) {
ensureCapacity(16);
writeInt(GLCommand.SECONDARY_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
}
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
@@ -1,6 +1,10 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import org.lwjgl.opengl.GL15;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
/**
@@ -9,15 +13,26 @@ import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
public final class DisplayListVBO {
private final SubVBO[] vbos;
private final int[] extVbos;
DisplayListVBO(SubVBO[] vbos) {
this(vbos, null);
}
DisplayListVBO(SubVBO[] vbos, int[] extVbos) {
this.vbos = vbos;
this.extVbos = extVbos;
}
public void delete() {
for (SubVBO vbo : vbos) {
vbo.delete();
}
if (extVbos != null) {
for (int extVbo : extVbos) {
if (extVbo != 0) GLStateManager.glDeleteBuffers(extVbo);
}
}
}
public SubVBO[] getVBOs() {
@@ -39,6 +54,9 @@ public final class DisplayListVBO {
private final int start;
private final int count;
private final int vertexFlags;
private int pendingExtVbo = 0;
void setPendingExtVbo(int extVbo) { this.pendingExtVbo = extVbo; }
public SubVBO(IVertexArrayObject vao, int drawMode, int start, int count, int vertexFlags) {
this.vao = vao;
@@ -67,9 +85,20 @@ public final class DisplayListVBO {
public void render() {
if (vao == null) return;
vao.bind();
if (pendingExtVbo != 0) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, pendingExtVbo);
ImmediateExtendedAttribHandler.setupExtAttribPointers(0L, ImmediateExtendedAttribHandler.EXT_STRIDE);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
pendingExtVbo = 0;
}
if (count <= 0) { vao.unbind(); return; }
VAOManager.setCurrentVertexFlags(vertexFlags);
vao.draw(drawMode, start, count);
FfpExtendedAttribs.beginInternalDraw();
try {
vao.draw(drawMode, start, count);
} finally {
FfpExtendedAttribs.endInternalDraw();
}
vao.unbind();
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.BaseVAO;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexedVAO;
@@ -8,7 +9,13 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vbo.IVertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -16,6 +23,7 @@ import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCalloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.nmemFree;
@@ -48,15 +56,29 @@ public final class DisplayListVBOBuilder {
}
public DisplayListVBO build() {
final ImmediateExtendedAttribHandler extHandler = GLSMHooks.immediateExtendedHandler;
final DisplayListVBO.SubVBO[] vbos = new DisplayListVBO.SubVBO[count];
final ArrayList<ByteBuffer> allBuffers = new ArrayList<>();
final IntArrayList extVbos = new IntArrayList();
for (int i = 0; i < VertexFlags.BITSET_SIZE; i++) {
List<FormatData> formatData = formats[i];
if (formatData == null) continue;
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[i];
final boolean wantExt = extHandler != null && format.hasTexture();
if (extHandler == null && GLSMConfig.extendedAttribsExpected && format.hasTexture()) {
GLStateManager.warnOnce("dl-ext-no-handler",
"Textured display list compiled before the immediate ext-attribute handler was installed! POM data will be missing for this geometry.");
}
int start = 0;
final IVertexArrayObject vao = VAOManager.createStorageVAO(format, -1, 0); // drawMode will be ignored
final IVertexBuffer vbo = vao.getVBO();
// SubVBO slots that need the ext attributes attached on their first draw.
final IntArrayList extDrawIndexes = wantExt ? new IntArrayList() : null;
final IntArrayList extStarts = wantExt ? new IntArrayList() : null;
final IntArrayList extCounts = wantExt ? new IntArrayList() : null;
final IntArrayList extPrims = wantExt ? new IntArrayList() : null;
for (FormatData data : formatData) {
int vertexCount;
final List<ByteBuffer> drawBuffers = data.buffers;
@@ -76,6 +98,23 @@ public final class DisplayListVBOBuilder {
if (data.drawMode == GL11.GL_QUADS) {
final IVertexArrayObject indexedVAO = new IndexedVAO(vbo, IndexBuffer.convertQuadsToTrigs(start, start + vertexCount));
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(indexedVAO, GL11.GL_TRIANGLES, 0, vertexCount / 4 * 6, flags);
if (wantExt) {
extDrawIndexes.add(data.drawIndex);
extStarts.add(start);
extCounts.add(vertexCount);
extPrims.add(4);
}
} else if (data.drawMode == GL11.GL_TRIANGLES) {
if (wantExt) {
final IVertexArrayObject triVao = new BaseVAO(vbo);
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(triVao, GL11.GL_TRIANGLES, start, vertexCount, flags);
extDrawIndexes.add(data.drawIndex);
extStarts.add(start);
extCounts.add(vertexCount);
extPrims.add(3);
} else {
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLES, start, vertexCount, flags);
}
} else if (data.drawMode == GL11.GL_QUAD_STRIP) {
// GL_QUAD_STRIP is removed in core profile; an even-length GL_TRIANGLE_STRIP produces the same quads
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_STRIP, start, vertexCount & ~1, flags);
@@ -91,11 +130,55 @@ public final class DisplayListVBOBuilder {
}
ByteBuffer bigBuffer = mergeAndDelete(allBuffers);
vbo.allocate(bigBuffer, start);
if (wantExt && !extDrawIndexes.isEmpty()) {
final int extVbo = buildExt(extHandler, format, bigBuffer, extStarts, extCounts, extPrims);
if (extVbo != 0) {
extVbos.add(extVbo);
for (int e = 0; e < extDrawIndexes.size(); e++) {
vbos[extDrawIndexes.getInt(e)].setPendingExtVbo(extVbo);
}
}
}
memFree(bigBuffer);
allBuffers.clear();
}
return new DisplayListVBO(vbos);
return new DisplayListVBO(vbos, extVbos.toIntArray());
}
private static int buildExt(ImmediateExtendedAttribHandler handler, VertexFormat format, ByteBuffer bigBuffer,
IntArrayList extStarts, IntArrayList extCounts, IntArrayList extPrims) {
final int extStride = ImmediateExtendedAttribHandler.EXT_STRIDE;
final int stride = format.getVertexSize();
final int posOffset = 0;
final int texOffset = ImmediateExtendedAttribHandler.texOffset(format);
final int normalOffset = ImmediateExtendedAttribHandler.normalOffset(format);
int extVerts = 0;
for (int q = 0; q < extStarts.size(); q++) {
extVerts = Math.max(extVerts, extStarts.getInt(q) + extCounts.getInt(q));
}
final ByteBuffer ext = memCalloc(extVerts, extStride);
final long extAddr = memAddress0(ext);
final long srcBase = memAddress0(bigBuffer);
for (int q = 0; q < extStarts.size(); q++) {
final int rStart = extStarts.getInt(q);
final int rCount = extCounts.getInt(q);
final int rPrim = extPrims.getInt(q);
handler.buildPacked(srcBase + (long) rStart * stride, stride, posOffset, texOffset, normalOffset, rCount, rPrim,
extAddr + (long) rStart * extStride, extStride);
}
final int extVbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, extVbo);
ext.position(0).limit(extVerts * extStride);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, ext, GL15.GL_STATIC_DRAW);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
memFree(ext);
return extVbo;
}
private static ByteBuffer mergeAndDelete(List<ByteBuffer> buffers) {
@@ -61,6 +61,7 @@ public final class GLCommand {
public static final int COLOR = 54; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int CLEAR_COLOR = 55; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int BLEND_COLOR = 56; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int SECONDARY_COLOR = 57; // [cmd:4][r:4f][g:4f][b:4f]
// === Mixed int+float commands ===
public static final int ALPHA_FUNC = 60; // [cmd:4][func:4][ref:4f]
@@ -155,6 +156,7 @@ public final class GLCommand {
case COLOR -> "COLOR";
case CLEAR_COLOR -> "CLEAR_COLOR";
case BLEND_COLOR -> "BLEND_COLOR";
case SECONDARY_COLOR -> "SECONDARY_COLOR";
case ALPHA_FUNC -> "ALPHA_FUNC";
case FOGF -> "FOGF";
case LIGHTF -> "LIGHTF";
@@ -211,7 +213,7 @@ public final class GLCommand {
// Three int commands (16 bytes)
case GLCommand.STENCIL_FUNC, GLCommand.STENCIL_OP, GLCommand.TEX_PARAMETERI,
GLCommand.LIGHTF, GLCommand.LIGHTI,
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF -> 16;
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF, GLCommand.SECONDARY_COLOR -> 16;
// Four int commands (20 bytes)
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
@@ -142,6 +142,7 @@ public class GLSMRedirector {
private static final String GLU = "org/lwjgl/util/glu/GLU";
private static final String OpenGlHelper = "net/minecraft/client/renderer/OpenGlHelper";
private static final String EXTBlendFunc = "org/lwjgl/opengl/EXTBlendFuncSeparate";
private static final String EXTSecondaryColor = "org/lwjgl/opengl/EXTSecondaryColor";
private static final String ARBMultiTexture = "org/lwjgl/opengl/ARBMultitexture";
private static final String ARBShaderObjects = "org/lwjgl/opengl/ARBShaderObjects";
private static final String ARBInstancedArrays = "org/lwjgl/opengl/ARBInstancedArrays";
@@ -162,6 +163,7 @@ public class GLSMRedirector {
private static final String EXTGpuShader4 = "org/lwjgl/opengl/EXTGpuShader4";
private static final String EXTGPUShader4 = "org/lwjgl/opengl/EXTGPUShader4";
private static final String ARBTimerQuery = "org/lwjgl/opengl/ARBTimerQuery";
private static final String EXTTimerQuery = "org/lwjgl/opengl/EXTTimerQuery";
// Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
@@ -402,7 +404,12 @@ public class GLSMRedirector {
.add("glBlendEquation")
.add("glMultiDrawArrays")
.add("glPointParameterf")
.add("glPointParameteri");
.add("glPointParameteri")
// Removed in core profile - emulated by the FFP shaders, calling through would abort the JVM
.add("glSecondaryColor3f")
.add("glSecondaryColor3d")
.add("glSecondaryColor3b")
.add("glSecondaryColor3ub");
final var gl15 = RedirectMap.newMap()
.add("glGenBuffers")
.add("glBindBuffer")
@@ -510,7 +517,8 @@ public class GLSMRedirector {
.add("glFramebufferRenderbuffer")
.add("glIsFramebuffer")
.add("glIsRenderbuffer")
.add("glBindBufferBase");
.add("glBindBufferBase")
.add("glBindBufferRange");
final var gl31 = RedirectMap.newMap()
.add("glDrawElementsInstanced")
.add("glDrawArraysInstanced")
@@ -588,6 +596,11 @@ public class GLSMRedirector {
.add("isFramebufferEnabled")
);
methodRedirects.put(EXTBlendFunc, RedirectMap.newMap().add("glBlendFuncSeparateEXT", "tryBlendFuncSeparate"));
methodRedirects.put(EXTSecondaryColor, RedirectMap.newMap()
.add("glSecondaryColor3bEXT", "glSecondaryColor3b")
.add("glSecondaryColor3fEXT", "glSecondaryColor3f")
.add("glSecondaryColor3dEXT", "glSecondaryColor3d")
.add("glSecondaryColor3ubEXT", "glSecondaryColor3ub"));
// ARB
methodRedirects.put(ARBMultiTexture, RedirectMap.newMap()
@@ -632,6 +645,10 @@ public class GLSMRedirector {
.add("glQueryCounter")
.add("glGetQueryObjectui64")
);
methodRedirects.put(EXTTimerQuery, RedirectMap.newMap()
.add("glGetQueryObjectuEXT", "glGetQueryObjectui64")
.add("glGetQueryObjectui64EXT", "glGetQueryObjectui64")
);
glDescRedirects.put("org/lwjgl/opengl/GL32C glFenceSync(II)J", "glFenceSync");
glDescRedirects.put("org/lwjgl/opengl/GL32C glClientWaitSync(JIJ)I", "glClientWaitSync");
@@ -706,6 +723,7 @@ public class GLSMRedirector {
);
final var uniformBufferObject = RedirectMap.newMap()
.add("glBindBufferBase")
.add("glBindBufferRange")
.add("glGetUniformBlockIndex")
.add("glUniformBlockBinding");
methodRedirects.put(ARBUniformBufferObject, uniformBufferObject);
@@ -100,7 +100,7 @@ public final class GlslVulkanPreprocess {
return out;
}
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs) {}
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless) {}
public static Metadata collectEdits(String source, GLSLParser.Translation_unitContext root, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
@@ -216,7 +216,7 @@ public final class GlslVulkanPreprocess {
}
}, root);
if (needsSamplerless[0]) {
if (needsSamplerless[0] && source != null) {
final int at = versionDirectiveEnd(source);
if (at >= 0) edits.add(new Edit(at, at - 1, "\n" + SAMPLERLESS_EXTENSION));
}
@@ -234,7 +234,7 @@ public final class GlslVulkanPreprocess {
}
}
return new Metadata(bools, explicitInputs);
return new Metadata(bools, explicitInputs, needsSamplerless[0]);
}
public static void clearCache() {
@@ -2,7 +2,9 @@ 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> {
@@ -10,6 +12,8 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
protected int pointer;
@Setter private VanillaStateLayer<AlphaState> vanillaLayer;
public AlphaStateStack() {
stack = new AlphaState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
@@ -22,7 +26,7 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,10 +35,19 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final AlphaState saved = stack[--pointer];
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
set(saved);
}
return this;
}
public AlphaState readEffective(AlphaState out) {
out.set(this);
VanillaStateLayer.capture(vanillaLayer, out);
return out;
}
public boolean isEmpty() {
return pointer == 0;
}
@@ -2,7 +2,9 @@ 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.BlendState;
import lombok.Setter;
public class BlendStateStack extends BlendState implements IStateStack<BlendStateStack> {
@@ -10,6 +12,8 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
protected int pointer;
@Setter private VanillaStateLayer<BlendState> vanillaLayer;
public BlendStateStack() {
stack = new BlendState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
@@ -22,7 +26,7 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,10 +35,21 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final BlendState saved = stack[--pointer];
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
setExceptFunc(saved);
} else {
set(saved);
}
return this;
}
public BlendState readEffective(BlendState out) {
out.set(this);
VanillaStateLayer.capture(vanillaLayer, out);
return out;
}
public boolean isEmpty() {
return pointer == 0;
}
@@ -2,7 +2,9 @@ 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;
import lombok.Setter;
/**
* A stack for boolean GL state with lazy copy-on-write optimization.
@@ -14,6 +16,7 @@ import com.gtnewhorizons.angelica.glsm.states.BooleanState;
public class BooleanStateStack extends BooleanState implements IStateStack<BooleanStateStack> {
protected final boolean[] stack;
@Setter private VanillaBooleanLayer vanillaLayer;
/**
* The depth at which state has been saved. Compared against GLStateManager.getAttribDepth()
@@ -56,7 +59,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
if (savedDepth >= stack.length) {
throw new IllegalStateException("Stack overflow size " + (savedDepth + 1) + " reached");
}
stack[savedDepth++] = enabled;
stack[savedDepth++] = vanillaValue();
return this;
}
@@ -65,12 +68,26 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
if (savedDepth == 0) {
throw new IllegalStateException("Stack underflow");
}
final boolean oldValue = stack[--savedDepth];
// Call setEnabledDirect to avoid triggering beforeModify during restore
setEnabledDirect(oldValue);
restore(stack[--savedDepth]);
return this;
}
public boolean isEffectivelyEnabled() {
return (vanillaLayer != null && vanillaLayer.isOverrideHeld()) ? vanillaLayer.getVanilla() : enabled;
}
private boolean vanillaValue() {
return isEffectivelyEnabled();
}
private void restore(boolean value) {
if (vanillaLayer != null && vanillaLayer.isOverrideHeld()) {
vanillaLayer.setVanilla(value);
} else {
setEnabledDirect(value);
}
}
@Override
public boolean isEmpty() {
return savedDepth == 0;
@@ -92,8 +109,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
public BooleanStateStack popDepth() {
// We're only called if we were modified, so savedDepth should match
if (savedDepth > 0) {
final boolean oldValue = stack[--savedDepth];
setEnabledDirect(oldValue);
restore(stack[--savedDepth]);
}
return this;
}
@@ -106,7 +122,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
public void beforeModify() {
final int globalDepth = GLStateManager.getAttribDepth();
if (savedDepth < globalDepth) {
stack[savedDepth++] = enabled;
stack[savedDepth++] = vanillaValue();
GLStateManager.registerModifiedState(this);
}
}
@@ -2,7 +2,9 @@ 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.ColorMask;
import lombok.Setter;
public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskStack> {
@@ -10,6 +12,8 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
protected int pointer;
@Setter private VanillaStateLayer<ColorMask> vanillaLayer;
public ColorMaskStack() {
stack = new ColorMask[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
@@ -22,7 +26,7 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,7 +35,10 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final ColorMask saved = stack[--pointer];
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
set(saved);
}
return this;
}
@@ -2,7 +2,9 @@ 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.DepthState;
import lombok.Setter;
public class DepthStateStack extends DepthState implements IStateStack<DepthStateStack> {
@@ -10,6 +12,10 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
protected int pointer;
@Setter private VanillaStateLayer<DepthState> vanillaLayer;
private final DepthState effective = new DepthState();
public DepthStateStack() {
stack = new DepthState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
@@ -22,7 +28,7 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,10 +37,21 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final DepthState saved = stack[--pointer];
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
setExceptMask(saved);
} else {
set(saved);
}
return this;
}
public boolean isEffectiveMaskEnabled() {
if (!VanillaStateLayer.isHeld(vanillaLayer)) return isEnabled();
vanillaLayer.readVanilla(effective);
return effective.isEnabled();
}
public boolean isEmpty() {
return pointer == 0;
}
@@ -55,6 +55,15 @@ public class BlendState implements ISettableState<BlendState> {
return this;
}
public void setExceptFunc(BlendState state) {
final int sRgb = srcRgb, dRgb = dstRgb, sAlpha = srcAlpha, dAlpha = dstAlpha;
set(state);
this.srcRgb = sRgb;
this.dstRgb = dRgb;
this.srcAlpha = sAlpha;
this.dstAlpha = dAlpha;
}
@Override
public boolean sameAs(Object state) {
if (this == state) return true;
@@ -18,6 +18,12 @@ public class DepthState implements ISettableState<DepthState> {
return this;
}
public void setExceptMask(DepthState state) {
final boolean mask = enabled;
set(state);
this.enabled = mask;
}
@Override
public boolean sameAs(Object state) {
if (this == state) return true;
@@ -4,12 +4,16 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
@@ -22,6 +26,7 @@ import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static org.joml.Math.clamp;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
@@ -36,6 +41,7 @@ public class TessellatorStreamingDrawer {
private static final Tracy.ZoneId Z_SDL_STREAM_DRAW = Tracy.zoneId("sdlStreamDraw", Tracy.COLOR_FFP);
private static final Logger LOGGER = LogManager.getLogger("TessellatorStreamingDrawer");
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE; // 16
private static PersistentStreamingBuffer persistentBuffer;
@@ -44,6 +50,12 @@ public class TessellatorStreamingDrawer {
private static final int[] persistentVAOs = new int[FORMAT_COUNT];
private static final int[] orphanVAOs = new int[FORMAT_COUNT];
private static final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
private static final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
private static ByteBuffer extScratch;
private static long extScratchAddress;
private static int extScratchCapacity;
private static ByteBuffer repackBuffer;
private static long repackAddress;
private static int repackCapacity;
@@ -108,7 +120,7 @@ public class TessellatorStreamingDrawer {
final int defaultBrightness;
if (!tess.hasBrightness) {
defaultBrightness = ((int) GLSMConfig.lastBrightnessY << 16) | ((int) GLSMConfig.lastBrightnessX & 0xFFFF);
defaultBrightness = GLSMConfig.packedLastBrightness();
} else {
defaultBrightness = 0;
}
@@ -153,7 +165,25 @@ public class TessellatorStreamingDrawer {
repackBuffer.position(0);
repackBuffer.limit((int)(writePtr - repackAddress));
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
final ImmediateExtendedAttribHandler extHandler = GLSMHooks.immediateExtendedHandler;
final int extPrim = extHandler == null ? 0 : ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount);
final boolean wantsExt = extHandler != null && extHandler.wantsExtended();
final boolean doExt = extPrim != 0 && tess.hasTexture && wantsExt;
if (doExt) {
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
final int combinedBytes = vertexCount * combinedStride;
ensureExtScratch(combinedBytes);
interleaveBase(repackAddress, extScratchAddress, vertexCount, vertexSize, combinedStride);
// format expansion backfills a normal into rawBuffer, so trust effectiveFlags rather than tess.hasNormals
final int normalIndex = (effectiveFlags & VertexFlags.NORMAL_BIT) != 0 ? ImmediateExtendedAttribHandler.RAW_NORMAL_INDEX : -1;
extHandler.build(tess.rawBuffer, vertexCount, extPrim, normalIndex, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, effectiveFlags, format, combinedStride, tess.drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
} else {
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
}
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
@@ -184,6 +214,7 @@ public class TessellatorStreamingDrawer {
final ByteBuffer buffer = dt.getWriteBuffer();
final int vertexSize = format.getVertexSize();
if (tryExtendedPacked(buffer, flags, format, vertexSize, drawMode, vertexCount)) return;
uploadAndDraw(buffer, flags, format, vertexSize, drawMode, vertexCount);
}
@@ -197,9 +228,37 @@ public class TessellatorStreamingDrawer {
public static void drawPacked(ByteBuffer packedData, int drawMode, int flags, int vertexCount) {
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
final int vertexSize = format.getVertexSize();
if (tryExtendedPacked(packedData, flags, format, vertexSize, drawMode, vertexCount)) return;
uploadAndDraw(packedData, flags, format, vertexSize, drawMode, vertexCount);
}
private static boolean tryExtendedPacked(ByteBuffer packed, int flags, VertexFormat format, int vertexSize, int drawMode, int vertexCount) {
final ImmediateExtendedAttribHandler extHandler = GLSMHooks.immediateExtendedHandler;
if (extHandler == null || !extHandler.wantsExtended()) return false;
final int extPrim = ImmediateExtendedAttribHandler.extPrimVerts(drawMode, vertexCount);
if (extPrim == 0 || !format.hasTexture()) return false;
final long srcBase = memAddress0(packed) + packed.position();
final int texOffset = ImmediateExtendedAttribHandler.texOffset(format);
final int normalOffset = ImmediateExtendedAttribHandler.normalOffset(format);
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
final int combinedBytes = vertexCount * combinedStride;
ensureExtScratch(combinedBytes);
interleaveBase(srcBase, extScratchAddress, vertexCount, vertexSize, combinedStride);
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
return true;
}
private static void interleaveBase(long src, long dst, int vertexCount, int vertexSize, int combinedStride) {
for (int i = 0; i < vertexCount; i++) {
memCopy(src + (long) i * vertexSize, dst + (long) i * combinedStride, vertexSize);
}
}
private static String cachedDebugInfo = "Stream: not initialized";
private static long lastDebugUpdateNanos;
private static final long DEBUG_UPDATE_INTERVAL_NS = 500_000_000L; // 500ms
@@ -294,10 +353,89 @@ public class TessellatorStreamingDrawer {
}
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
if (drawMode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount);
FfpExtendedAttribs.beginInternalDraw();
try {
if (drawMode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount);
} else {
GLStateManager.glDrawArrays(drawMode, firstVertex, vertexCount);
}
} finally {
FfpExtendedAttribs.endInternalDraw();
}
}
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int combinedStride, int drawMode, int vertexCount) {
ensureVAO(flags, format);
ensureExtendedVAOs(flags, format);
int firstVertex = -1;
if (persistentBuffer != null) {
firstVertex = persistentBuffer.upload(combined, combinedStride);
}
if (firstVertex >= 0) {
GLStateManager.glBindVertexArray(extendedPersistentVAOs[flags]);
} else {
GLStateManager.glDrawArrays(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
orphanBuffers[flags].upload(combined);
firstVertex = 0;
}
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
}
private static void ensureExtendedVAOs(int flags, VertexFormat format) {
if (extendedOrphanVAOs[flags] == 0) {
extendedOrphanVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
setupExtendedBufferState(format);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
if (persistentBuffer != null && extendedPersistentVAOs[flags] == 0) {
extendedPersistentVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(extendedPersistentVAOs[flags]);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, persistentBuffer.getBufferId());
setupExtendedBufferState(format);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
}
private static void setupExtendedBufferState(VertexFormat format) {
final int combinedStride = format.getVertexSize() + ImmediateExtendedAttribHandler.EXT_STRIDE;
long offset = 0;
for (final VertexFormatElement element : format.elementsArray) {
element.setupBufferState(offset, combinedStride);
offset += element.getByteSize();
}
ImmediateExtendedAttribHandler.setupExtAttribPointers(offset, combinedStride);
}
private static void ensureExtScratch(int requiredBytes) {
if (extScratch != null && requiredBytes <= extScratchCapacity) return;
int newCapacity = Math.max(0x4000, extScratchCapacity);
while (newCapacity < requiredBytes) {
newCapacity *= 2;
}
if (extScratch != null) memFree(extScratch);
extScratch = memAlloc(newCapacity);
extScratchAddress = memAddress0(extScratch);
extScratchCapacity = newCapacity;
}
private static void shrinkExtScratchIfOversized(int lastRequiredBytes) {
if (extScratchCapacity > 0x20000 && lastRequiredBytes < (extScratchCapacity >> 3)) {
memFree(extScratch);
extScratchCapacity = 0x4000;
extScratch = memAlloc(extScratchCapacity);
extScratchAddress = memAddress0(extScratch);
}
}
@@ -358,8 +496,16 @@ public class TessellatorStreamingDrawer {
for (int i = 0; i < FORMAT_COUNT; i++) {
if (persistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(persistentVAOs[i]); persistentVAOs[i] = 0; }
if (orphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(orphanVAOs[i]); orphanVAOs[i] = 0; }
if (extendedPersistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(extendedPersistentVAOs[i]); extendedPersistentVAOs[i] = 0; }
if (extendedOrphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(extendedOrphanVAOs[i]); extendedOrphanVAOs[i] = 0; }
if (orphanBuffers[i] != null) { orphanBuffers[i].destroy(); orphanBuffers[i] = null; }
}
if (extScratch != null) {
memFree(extScratch);
extScratch = null;
extScratchAddress = 0;
extScratchCapacity = 0;
}
if (persistentBuffer != null) {
persistentBuffer.destroy();
persistentBuffer = null;
@@ -4,8 +4,11 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import java.nio.FloatBuffer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -22,6 +25,7 @@ public class GLSM_CoreRemovedCaps_GLTest {
cap("GL_POLYGON_STIPPLE", GL11.GL_POLYGON_STIPPLE),
cap("GL_INDEX_LOGIC_OP", GL11.GL_INDEX_LOGIC_OP),
cap("GL_AUTO_NORMAL", GL11.GL_AUTO_NORMAL),
cap("GL_COLOR_SUM", GL14.GL_COLOR_SUM),
cap("GL_MAP1_COLOR_4", GL11.GL_MAP1_COLOR_4),
cap("GL_MAP1_INDEX", GL11.GL_MAP1_INDEX),
cap("GL_MAP1_NORMAL", GL11.GL_MAP1_NORMAL),
@@ -46,6 +50,14 @@ public class GLSM_CoreRemovedCaps_GLTest {
return Arguments.of(name, value);
}
private static void assertSecondaryColor(float red, float green, float blue) {
final FloatBuffer params = BufferUtils.createFloatBuffer(4);
GLStateManager.glGetFloat(GL14.GL_CURRENT_SECONDARY_COLOR, params);
assertEquals(red, params.get(0), "red must round-trip through the cache");
assertEquals(green, params.get(1), "green must round-trip through the cache");
assertEquals(blue, params.get(2), "blue must round-trip through the cache");
}
private static void assertNoGlError(String what) {
final int error = GL11.glGetError();
assertEquals(GL11.GL_NO_ERROR, error, () -> what + " raised 0x" + Integer.toHexString(error));
@@ -83,6 +95,71 @@ public class GLSM_CoreRemovedCaps_GLTest {
}
}
@Test
void secondaryColorIsCacheOnly() {
try {
// Calling through to the driver here aborts the JVM in a core profile - the color sum is emulated by the FFP shaders
GLStateManager.glSecondaryColor3f(0.25F, 0.5F, 0.75F);
assertNoGlError("glSecondaryColor3f");
assertSecondaryColor(0.25F, 0.5F, 0.75F);
} finally {
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@Test
void secondaryColorIsRestoredByPopAttrib() {
try {
GLStateManager.glSecondaryColor3f(0.125F, 0.25F, 0.375F);
GLStateManager.glPushAttrib(GL11.GL_CURRENT_BIT);
GLStateManager.glSecondaryColor3f(1.0F, 1.0F, 1.0F);
GLStateManager.glPopAttrib();
assertNoGlError("glPushAttrib/glSecondaryColor3f/glPopAttrib");
assertSecondaryColor(0.125F, 0.25F, 0.375F);
} finally {
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@Test
void colorSumIsRestoredByFogBitPopAttrib() {
try {
GLStateManager.glPushAttrib(GL11.GL_FOG_BIT);
GLStateManager.glEnable(GL14.GL_COLOR_SUM);
GLStateManager.glPopAttrib();
assertNoGlError("glPushAttrib(GL_FOG_BIT)/glEnable(GL_COLOR_SUM)/glPopAttrib");
assertFalse(GLStateManager.glIsEnabled(GL14.GL_COLOR_SUM));
} finally {
GLStateManager.glDisable(GL14.GL_COLOR_SUM);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@Test
void secondaryColorReplaysFromDisplayList() {
final int list = GLStateManager.glGenLists(1);
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glSecondaryColor3f(0.5F, 0.625F, 0.75F);
GLStateManager.glEndList();
assertNoGlError("glSecondaryColor3f display list compile");
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
GLStateManager.glCallList(list);
assertNoGlError("glSecondaryColor3f display list replay");
assertSecondaryColor(0.5F, 0.625F, 0.75F);
} finally {
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@Test
void displayListReplayIsCacheOnly() {
final int list = GLStateManager.glGenLists(1);
@@ -0,0 +1,67 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.taumc.glsl.grammar.GLSLParser;
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 ReservedWordRestoreTest {
private static void assertTreeRestoreMatchesStringRestore(String label, String renamedSource) {
final GLSLParser.Translation_unitContext treeA = GlslTransformUtils.parseFullQuiet(renamedSource);
final String legacy = GlslTransformUtils.restoreReservedWords(GlslTransformUtils.getFormattedShader(treeA, ""));
final GLSLParser.Translation_unitContext treeB = GlslTransformUtils.parseFullQuiet(renamedSource);
GlslTransformUtils.restoreReservedWordsInTree(treeB);
final String inTree = GlslTransformUtils.getFormattedShader(treeB, "");
assertEquals(legacy, inTree, "in-tree restore diverged from string restore for " + label);
}
@Test
void allThreeRenamesRestoreIdentically() {
final String src = "float angelica_renamed_sample = 1.0;\n"
+ "uniform sampler2D angelica_renamed_sampler;\n"
+ "vec4 angelica_renamed_texture = vec4(0.0);\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture + vec4(angelica_renamed_sample);\n"
+ "}\n";
assertTreeRestoreMatchesStringRestore("all-three", src);
}
@Test
void renamedNewStaysRenamed() {
final String src = "float angelica_renamed_new = 2.0;\n"
+ "void main() { float x = angelica_renamed_new; }\n";
assertTreeRestoreMatchesStringRestore("new-identifier", src);
final GLSLParser.Translation_unitContext tree = GlslTransformUtils.parseFullQuiet(src);
GlslTransformUtils.restoreReservedWordsInTree(tree);
final String out = GlslTransformUtils.getFormattedShader(tree, "");
assertTrue(out.contains("angelica_renamed_new"), "new is reserved in every GLSL version and must stay renamed");
}
@Test
void renamedTextureFunctionCallRestores() {
final String src = "uniform sampler2D tex;\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture2D(tex, vec2(0.5));\n"
+ "}\n";
assertTreeRestoreMatchesStringRestore("texture2D-call", src);
final GLSLParser.Translation_unitContext tree = GlslTransformUtils.parseFullQuiet(src);
GlslTransformUtils.restoreReservedWordsInTree(tree);
final String out = GlslTransformUtils.getFormattedShader(tree, "");
assertTrue(out.contains("texture2D"), "suffixed rename must strip only the prefix");
assertFalse(out.contains("angelica_renamed_"), "no renamed remnants expected");
}
@Test
void untouchedSourcePrintsIdentically() {
final String src = "layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = vec4(1.0); }\n";
assertTreeRestoreMatchesStringRestore("no-renames", src);
}
}
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.glsm.backend;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.function.Predicate;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -11,63 +11,36 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class BackendPolicyTest {
@Test
void autoFollowsTheVsyncToggle() {
assertEquals(VSyncMode.ON, VSyncMode.AUTO.resolve(true));
assertEquals(VSyncMode.OFF, VSyncMode.AUTO.resolve(false));
}
@Test
void anExplicitModeIgnoresTheVsyncToggle() {
for (VSyncMode mode : new VSyncMode[]{ VSyncMode.ON, VSyncMode.MAILBOX, VSyncMode.OFF }) {
assertEquals(mode, mode.resolve(true));
assertEquals(mode, mode.resolve(false));
}
}
@Test
void onlyOnBlocks() {
assertTrue(VSyncMode.ON.blocksOnVBlank());
assertFalse(VSyncMode.MAILBOX.blocksOnVBlank());
assertFalse(VSyncMode.OFF.blocksOnVBlank());
}
@Test
void mailboxRidesOnSwapIntervalOne() {
assertTrue(VSyncMode.ON.usesVSyncSwapInterval());
assertTrue(VSyncMode.MAILBOX.usesVSyncSwapInterval());
assertFalse(VSyncMode.OFF.usesVSyncSwapInterval());
}
@Test
void swapIntervalBackendsReportWhatTheyActuallyGet() {
assertEquals(VSyncMode.ON, VSyncMode.MAILBOX.swapIntervalEquivalent());
assertEquals(VSyncMode.ON, VSyncMode.ON.swapIntervalEquivalent());
assertEquals(VSyncMode.OFF, VSyncMode.OFF.swapIntervalEquivalent());
}
@Test
void fallbackNeverWidensTearing() {
assertEquals(List.of(VSyncMode.ON), VSyncMode.ON.fallbackOrder());
assertEquals(List.of(VSyncMode.MAILBOX, VSyncMode.ON), VSyncMode.MAILBOX.fallbackOrder());
assertEquals(List.of(VSyncMode.OFF, VSyncMode.MAILBOX, VSyncMode.ON), VSyncMode.OFF.fallbackOrder());
}
@Test
void onlyOffCanTear() {
void onlyImmediateCanTear() {
assertTrue(VSyncMode.ON.tearFree());
assertTrue(VSyncMode.MAILBOX.tearFree());
assertFalse(VSyncMode.OFF.tearFree());
}
private static final Predicate<VSyncMode> NO_MAILBOX = mode -> mode != VSyncMode.MAILBOX;
private static final Predicate<VSyncMode> EVERYTHING = mode -> true;
@Test
void onlyBackendSupportedModesAreSelectable() {
assertEquals(List.of(VSyncMode.OFF, VSyncMode.ON), VSyncMode.selectable(m -> m != VSyncMode.MAILBOX));
assertEquals(List.of(VSyncMode.OFF, VSyncMode.ON, VSyncMode.MAILBOX), VSyncMode.selectable(m -> true));
void mailboxIsTakenWhenTheBackendOffersIt() {
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(null, EVERYTHING));
}
@Test
void autoIsNeverSelectable() {
assertFalse(VSyncMode.selectable(m -> true).contains(VSyncMode.AUTO));
void vsyncIsTheFallbackWhenMailboxIsUnsupported() {
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(null, NO_MAILBOX));
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.MAILBOX, NO_MAILBOX));
}
@Test
void anExplicitSupportedPreferenceWins() {
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.ON, EVERYTHING));
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(VSyncMode.MAILBOX, EVERYTHING));
}
@Test
void offIsNeverATearFreePreference() {
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(VSyncMode.OFF, EVERYTHING));
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.OFF, NO_MAILBOX));
}
@Test
@@ -89,32 +62,4 @@ class BackendPolicyTest {
assertEquals(0, RenderBackend.refreshHzFrom(-1, -1, -1.0f));
assertEquals(0, RenderBackend.refreshHzFrom(144, 0, 0.0f));
}
@Test
void glRenderAheadKeepsZeroAsOff() {
assertEquals(0, RenderBackend.clampRenderAhead(0, 0, 9));
assertEquals(0, RenderBackend.clampRenderAhead(-1, 0, 9));
}
@Test
void glRenderAheadClampsToItsRange() {
assertEquals(1, RenderBackend.clampRenderAhead(1, 0, 9));
assertEquals(3, RenderBackend.clampRenderAhead(3, 0, 9));
assertEquals(9, RenderBackend.clampRenderAhead(9, 0, 9));
assertEquals(9, RenderBackend.clampRenderAhead(20, 0, 9));
}
@Test
void sdlRenderAheadHasNoOffSwitch() {
assertEquals(3, RenderBackend.clampRenderAhead(0, 1, 3));
assertEquals(3, RenderBackend.clampRenderAhead(-1, 1, 3));
}
@Test
void sdlRenderAheadClampsToItsRange() {
assertEquals(1, RenderBackend.clampRenderAhead(1, 1, 3));
assertEquals(2, RenderBackend.clampRenderAhead(2, 1, 3));
assertEquals(3, RenderBackend.clampRenderAhead(3, 1, 3));
assertEquals(3, RenderBackend.clampRenderAhead(6, 1, 3));
}
}
@@ -0,0 +1,241 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Vector3f;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.COLOR_BIT;
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 FFPVertexLightingGLTest {
private static final int POSITION_LOC = VertexFormatElement.Usage.POSITION.getAttributeLocation();
private static final int COLOR_LOC = VertexFormatElement.Usage.COLOR.getAttributeLocation();
private static final int STRIDE = 16;
private static final int VERTEX_COLOR = 0xFF808080;
private static final float VERTEX_CHANNEL = 0x80 / 255.0f;
private static final Vector3f IDENTITY_FACTOR = new Vector3f(1.0f, 1.0f, 1.0f);
private final Vector3f factor = new Vector3f();
private int vao;
private int vbo;
@BeforeEach
void setUpState() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
GLStateManager.disableBlend();
GLStateManager.disableTexture();
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
standardItemLighting();
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glMaterialf(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 0.0f);
}
@AfterEach
void cleanup() {
GLStateManager.glDisable(GL11.GL_NORMALIZE);
GLStateManager.glDisable(GL12.GL_RESCALE_NORMAL);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glDisable(GL11.GL_LIGHTING);
GLStateManager.glDisable(GL11.GL_LIGHT0);
GLStateManager.glDisable(GL11.GL_LIGHT1);
GLStateManager.glDisable(GL11.GL_LIGHT2);
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
sm.disable();
GLStateManager.glBindVertexArray(0);
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
}
private static void standardItemLighting() {
GLStateManager.glEnable(GL11.GL_LIGHTING);
GLStateManager.glEnable(GL11.GL_LIGHT0);
GLStateManager.glEnable(GL11.GL_LIGHT1);
GLStateManager.glEnable(GL11.GL_COLOR_MATERIAL);
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, floats(0.4f, 0.4f, 0.4f, 1.0f));
light(GL11.GL_LIGHT0, 0.2f, 1.0f, -0.7f);
light(GL11.GL_LIGHT1, -0.2f, 1.0f, 0.7f);
}
private static void light(int light, float x, float y, float z) {
GLStateManager.glLight(light, GL11.GL_POSITION, floats(x, y, z, 0.0f));
GLStateManager.glLight(light, GL11.GL_DIFFUSE, floats(0.6f, 0.6f, 0.6f, 1.0f));
GLStateManager.glLight(light, GL11.GL_AMBIENT, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glLight(light, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
}
private static String gateState() {
final var material = GLStateManager.getFrontMaterial();
return "lighting=" + GLStateManager.getLightingState().isEnabled()
+ " colorMaterial=" + GLStateManager.getColorMaterial().isEnabled()
+ " cmParam=0x" + Integer.toHexString(GLStateManager.getColorMaterialParameter().getValue())
+ " emission=" + material.emission
+ " specular=" + material.specular
+ " shininess=" + material.shininess
+ " lmAmbient=" + GLStateManager.getLightModel().ambient
+ " light0=" + GLStateManager.getLightStates()[0].isEnabled()
+ " light1=" + GLStateManager.getLightStates()[1].isEnabled();
}
private static FloatBuffer floats(float... values) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(values.length);
buf.put(values).flip();
return buf;
}
private void buildTriangle() {
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final ByteBuffer data = BufferUtils.createByteBuffer(3 * STRIDE);
putVertex(data, -0.9f, -0.9f);
putVertex(data, 0.9f, -0.9f);
putVertex(data, 0.0f, 0.9f);
data.flip();
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
GLStateManager.glEnableVertexAttribArray(POSITION_LOC);
GLStateManager.glVertexAttribPointer(POSITION_LOC, 3, GL11.GL_FLOAT, false, STRIDE, 0L);
GLStateManager.glEnableVertexAttribArray(COLOR_LOC);
GLStateManager.glVertexAttribPointer(COLOR_LOC, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 12L);
VAOManager.setCurrentVertexFlags(COLOR_BIT);
}
private static void putVertex(ByteBuffer buf, float x, float y) {
final int base = buf.position();
buf.putFloat(base, x).putFloat(base + 4, y).putFloat(base + 8, 0.0f);
buf.putInt(base + 12, VERTEX_COLOR);
buf.position(base + STRIDE);
}
private float[] drawAndReadCenter() {
buildTriangle();
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
sm.activate();
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "FFP draw must not raise a GL error");
final ByteBuffer pixel = BufferUtils.createByteBuffer(4);
GL11.glReadPixels(400, 200, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixel);
return new float[] { (pixel.get(0) & 0xFF) / 255.0f, (pixel.get(1) & 0xFF) / 255.0f, (pixel.get(2) & 0xFF) / 255.0f };
}
private void assertSolverMatchesShader(String context) {
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), context + ": expected a representable factor");
final float[] actual = drawAndReadCenter();
final float[] expected = { Math.min(1.0f, VERTEX_CHANNEL * factor.x), Math.min(1.0f, VERTEX_CHANNEL * factor.y), Math.min(1.0f, VERTEX_CHANNEL * factor.z) };
for (int c = 0; c < 3; c++) {
assertEquals(expected[c], actual[c], 2.0f / 255.0f, context + ": channel " + c + " solver=" + expected[c] + " shader=" + actual[c] + " factor=" + factor);
}
}
@Test
void faceOnNormalMatchesGeneratedShader() {
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
assertSolverMatchesShader("face-on normal");
}
@Test
void signNormalMatchesGeneratedShader() {
GLStateManager.glNormal3f(0.0f, 0.0f, -0.010416667f);
assertSolverMatchesShader("sign normal");
assertTrue(factor.x < 0.45f, "a 0.0104-long normal must stay at the 0.4 light model ambient floor rather than pick up 0.6 per light, got " + factor.x);
}
@Test
void tiltedNormalMatchesGeneratedShader() {
GLStateManager.glNormal3f(0.3f, 0.8f, 0.5f);
assertSolverMatchesShader("tilted normal");
}
@Test
void rescaleNormalMatchesGeneratedShader() {
GLStateManager.glEnable(GL12.GL_RESCALE_NORMAL);
GLStateManager.glScalef(1.0f, 2.0f, 1.0f);
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
assertSolverMatchesShader("rescale normal");
}
@Test
void normalizeMatchesGeneratedShader() {
GLStateManager.glEnable(GL11.GL_NORMALIZE);
GLStateManager.glScalef(1.0f, 2.0f, 1.0f);
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
assertSolverMatchesShader("normalize");
assertTrue(factor.x > 1.2f, "normalize restores the unit normal, which must pick up more diffuse than rescale's 0.5-long one, got " + factor.x);
}
@Test
void materialEmissionIsNotRepresentable() {
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION, floats(0.1f, 0.1f, 0.1f, 1.0f));
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "emission is additive, so the lit colour is not vertexColor * factor");
assertEquals(IDENTITY_FACTOR, factor, "a rejected solve must leave an identity factor");
}
@Test
void specularContributionIsNotRepresentable() {
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_SPECULAR, floats(1.0f, 1.0f, 1.0f, 1.0f));
GLStateManager.glMaterialf(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 16.0f);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, floats(1.0f, 1.0f, 1.0f, 1.0f));
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "specular is additive, so the lit colour is not vertexColor * factor");
}
@Test
void nonAmbientAndDiffuseColorMaterialIsNotRepresentable() {
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION);
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "only AMBIENT_AND_DIFFUSE makes the lit colour scale with the vertex colour");
}
@Test
void lightsBeyondTheFfpCountAreIgnored() {
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), FFPVertexLightingGLTest::gateState);
final Vector3f twoLights = new Vector3f(factor);
light(GL11.GL_LIGHT2, 0.0f, 0.0f, 1.0f);
GLStateManager.glEnable(GL11.GL_LIGHT2);
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), FFPVertexLightingGLTest::gateState);
assertEquals(twoLights, factor, "VertexShaderGenerator emits only GL_LIGHT0/GL_LIGHT1, so text must not see GL_LIGHT2 either");
}
}
@@ -0,0 +1,32 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class UniformsRescaleTest {
private static float rescaleFactorFor(float sx, float sy, float sz) {
final Matrix3f normalMatrix = new Matrix3f();
new Matrix4f().rotateY(0.7f).scale(sx, sy, sz).normal(normalMatrix);
return Uniforms.rescaleFactor(normalMatrix, new Vector3f());
}
@Test
void rescaleFactorUsesTheInverseModelviewBottomRow() {
assertEquals(8.0f, rescaleFactorFor(4.0f, 2.0f, 8.0f), 1.0e-5f, "wrong normal matrix column");
}
@Test
void uniformScaleIsRowIndependent() {
assertEquals(2.0f, rescaleFactorFor(2.0f, 2.0f, 2.0f), 1.0e-5f);
}
@Test
void singularMatrixFallsBackToUnitScale() {
assertEquals(1.0f, rescaleFactorFor(1.0f, 1.0f, 0.0f), 1.0e-5f, "a degenerate normal matrix must not divide by zero");
}
}
@@ -0,0 +1,113 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SectionRegistryTest {
private static final class RecordingEmitter implements SectionRegistry.Emitter {
final List<String> setups = new ArrayList<>();
final List<String> enters = new ArrayList<>();
final List<Long> leaves = new ArrayList<>();
long nextId;
boolean connected;
@Override
public long enter(int category, String text) {
enters.add(category + ":" + text);
return connected ? ++nextId : 0L;
}
@Override
public void leave(long nativeId) {
leaves.add(nativeId);
}
@Override
public void setup(int category, String name) {
setups.add(category + ":" + name);
}
@Override
public boolean isConnected() {
return connected;
}
}
private final RecordingEmitter emitter = new RecordingEmitter();
private final SectionRegistry registry = new SectionRegistry(emitter);
private void reconnect() {
emitter.connected = false;
registry.poll();
emitter.connected = true;
registry.poll();
}
@Test
void tokensOutliveEveryReplayAndLeaveUsesTheNewestId() {
final long openedWhileDisconnected = registry.enter(1, "world");
assertNotEquals(0L, openedWhileDisconnected, "token must be valid while disconnected");
assertEquals(0L, emitter.nextId, "no native id while disconnected");
emitter.connected = true;
registry.poll();
final long openedWhileConnected = registry.enter(2, "shaders");
assertNotEquals(openedWhileDisconnected, openedWhileConnected);
reconnect();
emitter.leaves.clear();
registry.leave(openedWhileDisconnected);
registry.leave(openedWhileConnected);
assertEquals(List.of(3L, 4L), emitter.leaves, "leave must use the post-reconnect ids");
}
@Test
void aSectionEnteredOnTheConnectingFrameIsNotEnteredTwice() {
emitter.connected = true;
registry.enter(2, "shaders pack / Overworld");
registry.poll();
assertEquals(List.of("2:shaders pack / Overworld"), emitter.enters, "must not re-enter a section already live on this connection");
}
@Test
void reconnectReplaysEveryCategoryAndEveryOpenSection() {
registry.registerCategory(1, "World");
registry.registerCategory(4, "UI");
emitter.connected = true;
registry.poll();
registry.enter(1, "world");
registry.enter(4, "pause");
emitter.setups.clear();
emitter.enters.clear();
reconnect();
assertEquals(List.of("1:World", "4:UI"), emitter.setups, "all categories re-registered");
assertEquals(List.of("1:world", "4:pause"), emitter.enters, "all open sections re-entered");
}
@Test
void leftSectionsAreNotResurrectedByAReconnect() {
emitter.connected = true;
registry.poll();
final long token = registry.enter(1, "transient");
registry.leave(token);
emitter.enters.clear();
emitter.leaves.clear();
reconnect();
registry.leave(token);
assertTrue(emitter.enters.isEmpty(), "closed sections must not be re-entered");
assertTrue(emitter.leaves.isEmpty(), "closed tokens must not be left twice");
}
}
@@ -331,6 +331,9 @@ class GLSMRedirectorTest {
void phase2NameKeyedGapsRedirect() {
assertRedirected("org/lwjgl/opengl/GL33C", "glQueryCounter", "(II)V", "glQueryCounter");
assertRedirected("org/lwjgl/opengl/GL33C", "glGetQueryObjectui64", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/ARBTimerQuery", "glGetQueryObjectui64", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/EXTTimerQuery", "glGetQueryObjectuEXT", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/EXTTimerQuery", "glGetQueryObjectui64EXT", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/GL30C", "glBindFragDataLocation", "(IILjava/lang/CharSequence;)V", "glBindFragDataLocation");
assertRedirected("org/lwjgl/opengl/GL11C", "glGetIntegerv", "(I[I)V", "glGetInteger");
assertRedirected("org/lwjgl/opengl/GL20C", "glUniform4fv", "(I[F)V", "glUniform4");
+2 -2
View File
@@ -9,13 +9,13 @@ asmCompile = "5.0.3"
lwjgl3 = "3.4.2-20260602.093430-9"
lwjgl3ify = "3.0.31"
celeritas = "2.5.9-GTNH"
celeritas = "2.5.10-GTNH"
gtnhlib = "0.11.36"
eventbus = "7.0-beta.15"
glslTransformLib = "0.2.0-33.g3ec381e"
antlr4Runtime = "4.13.2"
jcpp = "1.4.14"
tracy = "0.13.1"
tracy = "0.14.0"
retroFuturaBootstrap = "1.1.0"
lombok = "1.18.46"
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.glsm.GLESCaps;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.DebugMessageHandler;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -171,10 +172,14 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
public int getMinGLSLVersion() {return 330;}
@Override
protected void setSwapInterval(boolean vsync) {SDLVideo.SDL_GL_SetSwapInterval(vsync ? 1 : 0);}
protected VSyncMode applyVSyncMode(VSyncMode preferred) {
final boolean enabled = preferred.tearFree();
SDLVideo.SDL_GL_SetSwapInterval(enabled ? 1 : 0);
return enabled ? VSyncMode.ON : VSyncMode.OFF;
}
@Override
public int getDisplayRefreshRateHz() {
protected int queryDisplayRefreshRateHz() {
final long window = Display.getWindow();
final int displayId = window == 0 ? SDLVideo.SDL_GetPrimaryDisplay() : SDLVideo.SDL_GetDisplayForWindow(window);
if (displayId == 0) return 0;
+2
View File
@@ -59,6 +59,7 @@ dependencies {
api(project(":glsm"))
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
compileOnly(libs.celeritas.common) { isTransitive = false }
// GL enum constants (compileOnly -- no runtime GL dependency)
@@ -76,6 +77,7 @@ dependencies {
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testImplementation(testFixtures(project(":glsm")))
testImplementation(libs.celeritas.common) { isTransitive = false }
testImplementation(libs.lwjgl3.opengl)
// GL.<clinit> loads liblwjgl_opengl.so. Test scope only: a root-level runtimeOnly collides with lwjgl3Bindings.
testRuntimeOnly(libs.lwjgl3.opengl) { artifact { classifier = lwjglNatives } }
@@ -10,6 +10,7 @@ import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.sdl.SDLLog;
import org.taumc.glsl.grammar.GLSLParser;
import org.lwjgl.sdl.SDLProperties;
import org.lwjgl.system.Platform;
import org.lwjglx.Sys;
@@ -100,6 +101,11 @@ public final class SDLGPUGate {
ShaderManager.prewarmSpirv(transformedSource, glShaderType);
}
public static void prewarmSpirv(String transformedSource, GLSLParser.Translation_unitContext bodyTree, int headerLen, int glShaderType) {
if (!isActive()) return;
ShaderManager.prewarmSpirv(transformedSource, bodyTree, headerLen, glShaderType);
}
public static void clearShaderPrewarmCache() {
if (!isActive()) return;
ShaderManager.clearPrewarmCache();
@@ -152,6 +158,9 @@ public final class SDLGPUGate {
}
public static void fallBackToGL() {
if (SDLGPULWJGLService.isOfferedAsAvailable()) {
throw new IllegalStateException("Celeritas already selected the SDL GPU LWJGL service; falling back to OpenGL would leave it bound to a dead backend");
}
disarmed = true;
engaged = false;
deviceReady = false;
@@ -19,11 +19,17 @@ import java.util.Arrays;
/** SDL GPU implementation of {@link LWJGLService} for Celeritas */
public final class SDLGPULWJGLService extends LWJGLService {
private static volatile boolean offeredAsAvailable;
public static boolean isOfferedAsAvailable() { return offeredAsAvailable; }
@Override public int getPriority() {
if (SystemProperties.USE_SDL_GPU && SDLGPUGate.isSDLGPUAvailable() && SDLGPUGate.isEngaged()) {
return 200;
if (!SystemProperties.USE_SDL_GPU || !SDLGPUGate.isSDLGPUAvailable() || !SDLGPUGate.isEngaged()) {
return PRIORITY_UNAVAILABLE;
}
return Integer.MIN_VALUE;
offeredAsAvailable = true;
return 200;
}
@Override public boolean isOpenGLVersionSupported(int major, int minor) {
@@ -148,22 +154,30 @@ public final class SDLGPULWJGLService extends LWJGLService {
}
@Override public void glDeleteSync(long sync) { BackendManager.RENDER_BACKEND.deleteSync(sync); }
private long[] queryTimestamps = new long[64];
private long[] queryElapsed = new long[64];
private int activeQueryId = -1;
private long activeQueryStart;
private void ensureQueryCapacity(int id) {
if (id >= queryTimestamps.length) {
queryTimestamps = Arrays.copyOf(queryTimestamps, Math.max(id + 1, queryTimestamps.length * 2));
if (id >= queryElapsed.length) {
queryElapsed = Arrays.copyOf(queryElapsed, Math.max(id + 1, queryElapsed.length * 2));
}
}
@Override public int glGenQueries() { return BackendManager.RENDER_BACKEND.genQueries(); }
@Override public void glDeleteQueries(int query) { }
@Override public void glQueryCounter(int id, int target) {
@Override public void glBeginQuery(int target, int id) {
ensureQueryCapacity(id);
queryTimestamps[id] = System.nanoTime();
activeQueryId = id;
activeQueryStart = System.nanoTime();
}
@Override public void glEndQuery(int target) {
if (activeQueryId < 0) return;
queryElapsed[activeQueryId] = System.nanoTime() - activeQueryStart;
activeQueryId = -1;
}
@Override public long glGetQueryObjectui64(int id, int pname) {
if (id >= 0 && id < queryTimestamps.length) return queryTimestamps[id];
if (id >= 0 && id < queryElapsed.length) return queryElapsed[id];
return 0;
}
@@ -18,6 +18,8 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FenceTracker;
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.DrawDispatch;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
@@ -41,9 +43,10 @@ import com.gtnewhorizons.angelica.sdlgpu.sampler.StorageBufferBinder;
import com.gtnewhorizons.angelica.sdlgpu.sampler.StorageTextureBinder;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.splash.SplashDispatcher;
import com.gtnewhorizons.angelica.sdlgpu.splash.SplashOffscreenTarget;
import com.gtnewhorizons.angelica.sdlgpu.frame.OffscreenTarget;
import com.gtnewhorizons.angelica.sdlgpu.util.DebugLabels;
import com.gtnewhorizons.angelica.sdlgpu.util.DebugMessageRelay;
import com.gtnewhorizons.angelica.sdlgpu.util.ThreadRegistry;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
@@ -80,12 +83,11 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import org.lwjgl.sdl.SDLGPU;
import org.lwjgl.sdl.SDLSurface;
import org.lwjgl.sdl.SDL_GPUBlitInfo;
import org.lwjgl.sdl.SDL_GPUBufferLocation;
import org.lwjgl.sdl.SDL_GPUTextureLocation;
@@ -108,6 +110,7 @@ public class SDLGPURenderBackend extends RenderBackend {
private final Device device = SDLGPUGate.device();
private final FrameManager frameManager = new FrameManager(device);
private volatile Presenter presenter;
private final ResourceManager resourceManager = new ResourceManager(device, frameManager);
private final Image3DClear image3DClear = new Image3DClear();
private final ShaderManager shaderManager = new ShaderManager(device);
@@ -151,33 +154,31 @@ public class SDLGPURenderBackend extends RenderBackend {
private volatile boolean shutdown;
private SplashOffscreenTarget splashTarget;
private volatile OffscreenTarget splashTarget;
private volatile boolean splashTeardownPending;
private static final CopyOnWriteArrayList<ContextState> registeredStates = new CopyOnWriteArrayList<>();
private static final ThreadLocal<ContextState> tlState = ThreadLocal.withInitial(() -> {
private static final ThreadRegistry<ContextState> registeredStates = new ThreadRegistry<>(new ContextState[0]);
private static final ThreadLocal<ContextState> tlState = new ThreadLocal<>();
private static ContextState s() {
final ContextState sole = registeredStates.sole();
if (sole != null && sole.owner == Thread.currentThread()) return sole;
final ContextState st = tlState.get();
return st != null ? st : registerState();
}
private static ContextState registerState() {
final ContextState st = new ContextState();
for (int i = 0; i < ContextState.MAX_VERTEX_ATTRIBS; i++) st.attribDefaults[i * 4 + 3] = 1.0f;
st.attribDefaults[4] = 1.0f; st.attribDefaults[5] = 1.0f;
st.attribDefaults[6] = 1.0f;
tlState.set(st);
registeredStates.add(st);
refreshSoleState();
return st;
});
private static volatile ContextState soleState;
private static void refreshSoleState() {
soleState = registeredStates.size() == 1 ? registeredStates.get(0) : null;
}
private static ContextState s() {
final ContextState st = soleState;
if (st != null && st.owner == Thread.currentThread()) return st;
return tlState.get();
}
private void markAllPipelineInputDirty() {
for (ContextState st : registeredStates) st.pipeline.markInputDirty();
for (ContextState st : registeredStates.snapshot()) st.pipeline.markInputDirty();
}
public static final int CTR_SLOT_WRITES = 0;
@@ -189,7 +190,7 @@ public class SDLGPURenderBackend extends RenderBackend {
public static void takeContextCounters(int[] out) {
Arrays.fill(out, 0);
for (ContextState st : registeredStates) {
for (ContextState st : registeredStates.snapshot()) {
out[CTR_SLOT_WRITES] += st.slotWrites;
out[CTR_SLOT_WRITES_ELIDED] += st.slotWritesElided;
out[CTR_SSBO_BINDS] += st.ssboBinds;
@@ -204,7 +205,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private void releaseUniformStagingAllThreads(ShaderManager.ProgramObject prog) {
for (ContextState st : registeredStates) st.releaseUniformStaging(prog);
for (ContextState st : registeredStates.snapshot()) st.releaseUniformStaging(prog);
}
@@ -240,16 +241,12 @@ public class SDLGPURenderBackend extends RenderBackend {
});
GLSMHooks.LOADING_CHECKPOINT
.addListener(event -> {
if (Thread.currentThread() == GLStateManager.getMainThread()) {
if (!GLStateManager.isSplashComplete()) {
SplashDispatcher.tryDispatch(this, splashTarget);
} else if (splashTarget != null) {
SplashDispatcher.signalFrameReady();
SplashDispatcher.tryDispatch(this, splashTarget);
splashTarget.destroy(resourceManager);
splashTarget = null;
SplashDispatcher.reset();
if (!GLStateManager.isSplashComplete()) {
if (Thread.currentThread() == GLStateManager.getMainThread()) {
SplashDispatcher.tryDispatch(this, splashTarget, false);
}
} else if (splashTarget != null) {
splashTeardownPending = true;
}
if (event.requiresSync) {
frameManager.requestSyncFlushOnAllRegisteredFrames();
@@ -290,13 +287,19 @@ public class SDLGPURenderBackend extends RenderBackend {
resourceManager.setBufferLivenessListener(this::markAllPipelineInputDirty);
shaderManager.setUniformReleaseListener(this::releaseUniformStagingAllThreads);
fenceTracker.setUnresolvedFenceFlush(this::midFrameFenceFlush);
frameManager.setMainThread(GLStateManager.getMainThread());
if (!SystemProperties.DISABLE_SDL_PRESENTER_THREAD) {
presenter = new Presenter(frameManager, MainStartOnFirstThread.instance());
frameManager.setPresenter(presenter);
LOG.info("SDL presenter thread enabled: presents run on '{}'", device.getWindowThread() != null ? device.getWindowThread().getName() : "window thread");
}
final int splashW = Math.max(1, Display.getWidth());
final int splashH = Math.max(1, Display.getHeight());
final int[] maxDesktop = device.getMaxDesktopSizePixels();
final int splashTexW = Math.max(splashW, maxDesktop[0]);
final int splashTexH = Math.max(splashH, maxDesktop[1]);
splashTarget = new SplashOffscreenTarget();
splashTarget = new OffscreenTarget();
splashTarget.create(device, resourceManager, splashTexW, splashTexH, device.getSwapchainTextureFormat());
SplashDispatcher.seedDrawnSize(splashW, splashH);
LOG.info("Splash offscreen target {}x{} (window {}x{}, max desktop {}x{})", splashTexW, splashTexH, splashW, splashH, maxDesktop[0], maxDesktop[1]);
@@ -376,31 +379,50 @@ public class SDLGPURenderBackend extends RenderBackend {
LOG.info("Populated GL capabilities for SDL GPU backend");
}
@Override protected void setSwapInterval(boolean vsync) {
device.setVSyncMode(VSyncMode.AUTO, vsync);
}
private final AtomicReference<VSyncMode> pendingVSync = new AtomicReference<>();
@Override protected VSyncMode applyVSyncMode(VSyncMode preferred, boolean vsyncEnabled) {
return device.setVSyncMode(preferred, vsyncEnabled);
@Override protected VSyncMode applyVSyncMode(VSyncMode preferred) {
pendingVSync.set(preferred);
return device.chooseVSyncMode(preferred);
}
@Override public boolean supportsVSyncMode(VSyncMode mode) {
return mode == VSyncMode.AUTO || device.supportsVSyncMode(mode);
return device.supportsVSyncMode(mode);
}
@Override public int getMinRenderAhead() { return Device.MIN_FRAMES_IN_FLIGHT; }
private void applyPendingVSync() {
final VSyncMode preferred = pendingVSync.getAndSet(null);
if (preferred != null) {
if (presenter != null) presenter.drain();
publishVSyncMode(device.applyVSync(preferred));
}
}
@Override public int getMaxRenderAhead() { return Device.MAX_FRAMES_IN_FLIGHT; }
@Override public void setRenderAheadLimit(int frames) { device.setFramesInFlight(frames); }
@Override public int getDisplayRefreshRateHz() {
@Override protected int queryDisplayRefreshRateHz() {
return device.getDisplayRefreshRateHz();
}
@Override public boolean gateAnchorsNextFrameStart() { return true; }
@Override public boolean wantsDisplayUpdateGateTiming() { return false; }
@Override public long lastFrameGateNanos() {
if (presenter != null) return frameManager.windowFrame().lastFrameGateNanos;
return frameManager.lastFrameGateNanos();
}
@Override public long lastFrameGateEndNanos() {
if (presenter != null) return frameManager.windowFrame().lastFrameGateEndNanos;
return frameManager.lastFrameGateEndNanos();
}
@Override public boolean hasSwapchainBackpressure() { return true; }
@Override public void shutdown() {
if (shutdown) return;
shutdown = true;
if (presenter != null) presenter.drain();
frameManager.destroyFinalTarget();
if (splashTarget != null) {
splashTarget.destroy(resourceManager);
splashTarget = null;
@@ -423,7 +445,7 @@ public class SDLGPURenderBackend extends RenderBackend {
fenceTracker.dispose();
frameManager.releaseAllRegisteredFrames();
resourceManager.shutdownTexSamplerStates();
for (ContextState st : registeredStates) {
for (ContextState st : registeredStates.snapshot()) {
if (st.fanIndexBuffer != 0) {
resourceManager.releaseBufferHandle(st.fanIndexBuffer);
st.fanIndexBuffer = 0;
@@ -441,7 +463,6 @@ public class SDLGPURenderBackend extends RenderBackend {
clearMappedState(st);
}
registeredStates.clear();
soleState = null;
drawDispatch.clearWarnedState();
pipelineStore.shutdown();
@@ -480,32 +501,35 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public void onRenderThreadReleased(Thread t) {
if (shutdown || t == GLStateManager.getMainThread()) return;
if (frameManager.isFrameActive()) {
frameManager.endFrame();
}
frameManager.releaseThreadState();
registeredStates.remove(tlState.get());
tlState.remove();
refreshSoleState();
final ContextState st = tlState.get();
if (st != null) {
tlState.remove();
registeredStates.remove(st);
}
}
@Override public boolean handleMakeCurrent(Object drawable) {
if (!isSDLManagedDrawable(drawable)) return false;
Lwjgl3GLCapabilitiesShim.installOnCurrentThread(advertisedCapabilities());
if (splashTarget != null && Thread.currentThread() != GLStateManager.getMainThread()) {
s().boundFboId = splashTarget.fboId();
final OffscreenTarget splash = splashTarget;
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
s().boundFboId = splash.fboId();
frameManager.frame().swapchainUnavailable = true;
}
return true;
}
@Override public boolean handleReleaseContext(Object drawable) {
if (isSDLManagedDrawable(drawable)) {
Lwjgl3GLCapabilitiesShim.clearOnCurrentThread();
onRenderThreadReleased(Thread.currentThread());
return true;
}
return false;
if (!isSDLManagedDrawable(drawable)) return false;
if (Thread.currentThread() == GLStateManager.getMainThread()) return true;
Lwjgl3GLCapabilitiesShim.clearOnCurrentThread();
onRenderThreadReleased(Thread.currentThread());
return true;
}
private static boolean isSDLManagedDrawable(Object drawable) {
@@ -515,7 +539,8 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public boolean handleSwapBuffers() {
if (splashTarget != null && splashTarget.isFor(s()) && Thread.currentThread() != GLStateManager.getMainThread()) {
final OffscreenTarget splash = splashTarget;
if (splash != null && splash.isFor(s()) && Thread.currentThread() != GLStateManager.getMainThread()) {
endFrameUploadFlush();
frameManager.endFrame();
SplashDispatcher.signalFrameReady((int) s().viewportW, (int) s().viewportH);
@@ -528,33 +553,30 @@ public class SDLGPURenderBackend extends RenderBackend {
return true;
}
public void dispatchSplashBlit(SplashOffscreenTarget target) {
public void dispatchSplashBlit(OffscreenTarget target) {
if (shutdown || target == null) return;
onFrameBegin();
final FrameManager.FrameState f = frameManager.frame();
if (!frameManager.ensureSwapchainAcquired(f)) {
onFrameEnd();
return;
}
frameManager.endCopyPassIfActive();
frameManager.endRenderPassIfActive();
final long drawnSize = SplashDispatcher.getDrawnSize();
final int srcW = Math.max(1, Math.min((int) (drawnSize >> 32), target.width()));
final int srcH = Math.max(1, Math.min((int) drawnSize, target.height()));
try (var stack = MemoryStack.stackPush()) {
final var info = SDL_GPUBlitInfo.calloc(stack);
info.source(s -> s.texture(target.colorTexture()).x(0).y(0).w(srcW).h(srcH));
info.destination(d -> d.texture(f.swapchainTexture).x(0).y(0).w(f.swapchainWidth).h(f.swapchainHeight));
info.filter(SDL_GPU_FILTER_LINEAR);
info.flip_mode(SDLSurface.SDL_FLIP_VERTICAL);
SDL_BlitGPUTexture(f.commandBuffer, info);
}
f.swapchainUsedThisFrame = true;
onFrameEnd();
frameManager.presentSplash(target.colorTexture(), srcW, srcH);
}
private void finishSplash() {
splashTeardownPending = false;
final OffscreenTarget target = splashTarget;
if (target == null) return;
SplashDispatcher.signalFrameReady();
SplashDispatcher.tryDispatch(this, target, true);
if (presenter != null) presenter.drain();
splashTarget = null;
target.destroy(resourceManager);
SplashDispatcher.reset();
}
@Override public void onFrameBegin() {
if (shutdown) return;
if (splashTeardownPending) finishSplash();
if (GLStateManager.isSplashComplete()) applyPendingVSync();
frameManager.beginFrame();
beginFrameInit();
}
@@ -636,22 +658,24 @@ public class SDLGPURenderBackend extends RenderBackend {
endFrameUploadFlushNoWait();
final FrameState f = frameManager.frame();
if (f.frameActive && !f.swapchainUsedThisFrame) {
if (f.frameActive && !f.fbo0UsedThisFrame) {
if (!emptyFrameWarned) {
emptyFrameWarned = true;
LOG.info("Frame {} ended without drawing to the swapchain; skipping present (further occurrences counted in sdl.emptyFrames)", f.frameNumber);
LOG.info("Frame {} ended without drawing to FBO 0; presenting the previous contents (further occurrences counted in sdl.emptyFrames)", f.frameNumber);
}
frameManager.markFrameEmpty(f);
}
awaitUploadFlush();
frameManager.endFrame();
frameManager.presentFinalTarget();
fenceTracker.resolvePendingFences();
resourceManager.flushDeferredReleases();
resourceManager.recycleGpuBufferPool(frameManager.getFrameNumber());
}
@Override public void onPreSwapchainInvalidatingChange(Object change) {
if (presenter != null) presenter.drain();
endFrameUploadFlush();
if (frameManager.isFrameActive()) {
frameManager.endFrame();
@@ -700,54 +724,6 @@ public class SDLGPURenderBackend extends RenderBackend {
return transferThread != null ? transferThread.getDebugInfo() : null;
}
private static final long PRESENT_STATS_WINDOW_NANOS = 1_000_000_000L;
private static final long PRESENT_STATS_STALE_NANOS = 2_000_000_000L;
private long presentStatsNanos;
private long presentStatsLoopFrames;
private long presentStatsPresentedFrames;
private long presentStatsSkips;
private long presentStatsEmpty;
private long presentStatsAcquireNanos;
private String presentDebugLine;
@Override public String getPresentDebugInfo() {
if (shutdown) return null;
final long now = System.nanoTime();
final long elapsed = now - presentStatsNanos;
if (presentStatsNanos == 0L || elapsed > PRESENT_STATS_STALE_NANOS) {
snapshotPresentStats(now);
presentDebugLine = null;
return null;
}
if (elapsed < PRESENT_STATS_WINDOW_NANOS) return presentDebugLine;
final long loopDelta = frameManager.statLoopFrames() - presentStatsLoopFrames;
final long presentedDelta = frameManager.statPresentedFrames() - presentStatsPresentedFrames;
final long skipDelta = frameManager.statPresentSkips() - presentStatsSkips;
final long emptyDelta = frameManager.statEmptyFrames() - presentStatsEmpty;
final long acquireDelta = frameManager.statAcquireWaitNanos() - presentStatsAcquireNanos;
final double seconds = elapsed / 1.0e9;
presentDebugLine = String.format("Present: %s %dHz | loop %.0f/s shown %.0f/s | skip %.0f/s empty %.0f/s | acq %.2fms | fif %d",
getEffectiveVSyncMode(), device.getDisplayRefreshRateHz(),
loopDelta / seconds, presentedDelta / seconds,
skipDelta / seconds, emptyDelta / seconds,
loopDelta == 0 ? 0.0 : acquireDelta / (double) loopDelta / 1.0e6,
Device.framesInFlight());
snapshotPresentStats(now);
return presentDebugLine;
}
private void snapshotPresentStats(long now) {
presentStatsNanos = now;
presentStatsLoopFrames = frameManager.statLoopFrames();
presentStatsPresentedFrames = frameManager.statPresentedFrames();
presentStatsSkips = frameManager.statPresentSkips();
presentStatsEmpty = frameManager.statEmptyFrames();
presentStatsAcquireNanos = frameManager.statAcquireWaitNanos();
}
@Override public void enable(int cap) { setBoolCap(cap, true); }
@Override public void enablei(int cap, int index) { setBoolCapIndexed(cap, index, true); }
@Override public void disable(int cap) { setBoolCap(cap, false); }
@@ -952,7 +928,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final ContextState st = cs;
// Swapchain path: just record pending state; ensureSwapchainRenderPass consumes it lazily.
// FBO0 path: just record pending state; ensureFbo0RenderPass consumes it lazily.
if (st.boundFboId == 0) {
if (wantColor) {
st.pendingSwapchainClear = true;
@@ -1710,7 +1686,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final long texHandle;
final int srcSdlFormat;
if (cs.boundReadFboId == 0) {
texHandle = frameManager.getSwapchainTexture();
texHandle = frameManager.getFbo0Texture();
srcSdlFormat = frameManager.getSwapchainFormat();
} else {
final FboState fbo = resourceManager.getFbo(cs.boundReadFboId);
@@ -20,6 +20,7 @@ import org.lwjgl.system.Platform;
import org.lwjglx.opengl.Display;
import java.nio.IntBuffer;
import java.util.List;
import java.util.function.Supplier;
import static org.lwjgl.sdl.SDLGPU.*;
@@ -31,13 +32,11 @@ import static org.lwjgl.system.MemoryUtil.memUTF8;
public final class Device {
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
public static final int MIN_FRAMES_IN_FLIGHT = 1;
public static final int DEFAULT_FRAMES_IN_FLIGHT = 2;
public static final int MAX_FRAMES_IN_FLIGHT = 3;
private static volatile int framesInFlight = DEFAULT_FRAMES_IN_FLIGHT;
private static final int FRAMES_IN_FLIGHT = Math.min(Math.max(SystemProperties.SDL_FRAMES_IN_FLIGHT, 1), MAX_FRAMES_IN_FLIGHT);
public static int framesInFlight() { return framesInFlight; }
public static int framesInFlight() { return FRAMES_IN_FLIGHT; }
private static final int REQUESTED_SHADER_FORMATS = SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_DXBC | SDL_GPU_SHADERFORMAT_DXIL;
@@ -49,7 +48,7 @@ public final class Device {
private String driverVersion;
private String driverInfo;
private VSyncMode vsyncMode = VSyncMode.ON;
private volatile String windowThreadName;
private volatile Thread windowThread;
private SDL_LogOutputFunction logCallback; // prevent GC
private volatile boolean lost;
@@ -155,7 +154,13 @@ public final class Device {
throw new RuntimeException("Failed to claim window for SDL GPU device: " + SDLError.SDL_GetError());
}
claimed = true;
windowThreadName = Thread.currentThread().getName();
windowThread = Thread.currentThread();
if (!SDL_SetGPUAllowedFramesInFlight(device, FRAMES_IN_FLIGHT)) {
LOG.warn("SDL_SetGPUAllowedFramesInFlight({}) failed: {}", FRAMES_IN_FLIGHT, SDLError.SDL_GetError());
} else {
LOG.info("SDL frames-in-flight set to {}", FRAMES_IN_FLIGHT);
}
logWindowDiagnostics(window);
LOG.info("SDL GPU device claimed window successfully");
@@ -263,8 +268,8 @@ public final class Device {
return driverName;
}
public String getWindowThreadName() {
return windowThreadName;
public Thread getWindowThread() {
return windowThread;
}
public String getDeviceName() {
@@ -295,34 +300,48 @@ public final class Device {
return (supportedShaderFormats & SDL_GPU_SHADERFORMAT_DXBC) != 0;
}
public void setFramesInFlight(int frames) {
final int n = Math.min(Math.max(frames, MIN_FRAMES_IN_FLIGHT), MAX_FRAMES_IN_FLIGHT);
if (n == framesInFlight || device == 0 || !claimed) return;
if (!SDL_SetGPUAllowedFramesInFlight(device, n)) {
LOG.warn("SDL_SetGPUAllowedFramesInFlight({}) failed: {}; keeping {}", n, SDLError.SDL_GetError(), framesInFlight);
return;
}
framesInFlight = n;
LOG.info("SDL frames-in-flight set to {}", n);
private static final List<VSyncMode> ON_ORDER = List.of(VSyncMode.ON);
private static final List<VSyncMode> OFF_ORDER = List.of(VSyncMode.OFF, VSyncMode.ON);
private static final List<VSyncMode> MAILBOX_ORDER = List.of(VSyncMode.MAILBOX, VSyncMode.ON);
private static List<VSyncMode> presentModeOrder(VSyncMode preferred) {
return switch (preferred) {
case MAILBOX -> MAILBOX_ORDER;
case OFF -> OFF_ORDER;
default -> ON_ORDER;
};
}
public VSyncMode setVSyncMode(VSyncMode preferred, boolean vsyncEnabled) {
public VSyncMode chooseVSyncMode(VSyncMode preferred) {
for (VSyncMode candidate : presentModeOrder(preferred)) {
if (supportsVSyncMode(candidate)) return candidate;
}
return vsyncMode;
}
public VSyncMode applyVSync(VSyncMode preferred) {
if (device == 0 || !claimed) return vsyncMode;
final long window = Display.getWindow();
final VSyncMode requested = preferred.resolve(vsyncEnabled);
for (VSyncMode candidate : requested.fallbackOrder()) {
final List<VSyncMode> order = presentModeOrder(preferred);
for (VSyncMode candidate : order) {
final int sdlMode = toSdl(candidate);
if (!SDL_WindowSupportsGPUPresentMode(device, window, sdlMode)) continue;
if (!SDL_SetGPUSwapchainParameters(device, window, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, sdlMode)) {
LOG.warn("Failed to set present mode {}: {}", candidate, SDLError.SDL_GetError());
continue;
}
if (candidate != order.get(0)) {
LOG.warn("Present mode {} is not supported by this window; using {} instead", order.get(0), candidate);
}
vsyncMode = candidate;
LOG.info("SDL present mode {} (requested {}, preference {}, vsync={}), refresh {}Hz, supported: vsync={} mailbox={} immediate={}",
candidate, requested, preferred, vsyncEnabled, getDisplayRefreshRateHz(),
supportsVSyncMode(VSyncMode.ON), supportsVSyncMode(VSyncMode.MAILBOX), supportsVSyncMode(VSyncMode.OFF));
LOG.info("SDL present mode {} (preferred={}), refresh {}Hz, {}, video={}, framesInFlight={}, supported: vsync={} immediate={} mailbox={}",
candidate, preferred, getDisplayRefreshRateHz(),
(SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) != 0 ? "fullscreen" : "windowed",
SDL_GetCurrentVideoDriver(), FRAMES_IN_FLIGHT,
supportsVSyncMode(VSyncMode.ON), supportsVSyncMode(VSyncMode.OFF), supportsVSyncMode(VSyncMode.MAILBOX));
return candidate;
}
LOG.warn("No usable present mode for request {}; keeping {}", requested, vsyncMode);
LOG.warn("No usable present mode for preference {}; keeping {}", preferred, vsyncMode);
return vsyncMode;
}
@@ -330,6 +349,25 @@ public final class Device {
return device != 0 && claimed && SDL_WindowSupportsGPUPresentMode(device, Display.getWindow(), toSdl(mode));
}
public long getClaimedWindow() {
return device != 0 && claimed ? Display.getWindow() : 0L;
}
public boolean wasWindowResized() {
return getClaimedWindow() != 0L && Display.wasResized();
}
public long getWindowSizeInPixels() {
final long window = getClaimedWindow();
if (window == 0) return 0L;
try (MemoryStack stack = MemoryStack.stackPush()) {
final IntBuffer w = stack.mallocInt(1);
final IntBuffer h = stack.mallocInt(1);
SDL_GetWindowSizeInPixels(window, w, h);
return ((long) w.get(0) << 32) | (h.get(0) & 0xFFFFFFFFL);
}
}
private static int toSdl(VSyncMode mode) {
return switch (mode) {
case OFF -> SDL_GPU_PRESENTMODE_IMMEDIATE;
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
import com.gtnewhorizons.angelica.sdlgpu.util.ThreadRegistry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.gtnewhorizons.angelica.sdlgpu.resource.UploadArena;
@@ -16,16 +17,20 @@ import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDL_FColor;
import org.lwjgl.sdl.SDL_GPUBlitInfo;
import org.lwjgl.sdl.SDL_GPUBlitRegion;
import org.lwjgl.sdl.SDL_GPUColorTargetInfo;
import org.lwjgl.sdl.SDL_GPUDepthStencilTargetInfo;
import org.lwjglx.opengl.Display;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import static org.lwjgl.sdl.SDLGPU.*;
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_VERTICAL;
import static org.lwjgl.system.MemoryStack.*;
/**
@@ -47,9 +52,6 @@ public final class FrameManager {
public long renderPass;
public long copyPass;
public boolean computePassOpen;
public long swapchainTexture;
public int swapchainWidth;
public int swapchainHeight;
public boolean frameActive;
public long currentColorTarget;
public long currentDepthTarget;
@@ -57,10 +59,9 @@ public final class FrameManager {
public long frameNumber;
public boolean clearedThisFrame;
public boolean depthClearedThisFrame;
public boolean swapchainUsedThisFrame;
public boolean swapchainAcquiredThisFrame;
public boolean fbo0UsedThisFrame;
public boolean presentedThisFrame;
public boolean swapchainUnavailable;
public boolean skipPresent;
public long renderPassGeneration;
public boolean wantFenceOnNextSubmit;
@@ -82,7 +83,12 @@ public final class FrameManager {
public int batchBreakSamplerThisFrame;
public int stateAppliesThisFrame;
public long acquireWaitNanosThisFrame;
public volatile long lastFrameGateNanos;
public volatile long lastFrameGateEndNanos;
public int midFrameSubmitsThisFrame;
public int mipGensThisFrame;
public int mipGenSubmitsThisFrame;
public int uploadFlushSubmitsThisFrame;
public int blitsThisFrame;
public int presentSkipsThisFrame;
public int emptyFramesThisFrame;
@@ -143,53 +149,52 @@ public final class FrameManager {
private volatile boolean globalSyncAtNextBeginFrame;
private final CopyOnWriteArrayList<FrameState> registeredFrames = new CopyOnWriteArrayList<>();
private final ThreadRegistry<FrameState> registeredFrames = new ThreadRegistry<>(new FrameState[0]);
private volatile FrameState soleFrame;
private final ThreadLocal<FrameState> tlFrame = ThreadLocal.withInitial(() -> {
final FrameState f = new FrameState();
registeredFrames.add(f);
refreshSoleFrame();
return f;
});
private void refreshSoleFrame() {
soleFrame = registeredFrames.size() == 1 ? registeredFrames.get(0) : null;
}
private final ThreadLocal<FrameState> tlFrame = new ThreadLocal<>();
public FrameState frame() {
final FrameState f = soleFrame;
if (f != null && f.owner == Thread.currentThread()) return f;
return tlFrame.get();
final FrameState sole = registeredFrames.sole();
if (sole != null && sole.owner == Thread.currentThread()) return sole;
final FrameState f = tlFrame.get();
return f != null ? f : registerFrame();
}
private FrameState registerFrame() {
final FrameState f = new FrameState();
tlFrame.set(f);
registeredFrames.add(f);
return f;
}
public int registeredFrameCount() {
return registeredFrames.size();
}
public FrameState busiestFrame() {
final FrameState[] frames = registeredFrames.snapshot();
FrameState best = null;
for (final FrameState f : registeredFrames) {
for (final FrameState f : frames) {
if (best == null || f.frameNumber > best.frameNumber) best = f;
}
return best != null ? best : frame();
}
public static final long SWAPCHAIN_LAYOUT_HASH = 0x5CA1AB1E5CA1AB1EL;
public static final long FBO0_LAYOUT_HASH = 0x5CA1AB1E5CA1AB1EL;
private final OffscreenTarget finalTarget = new OffscreenTarget();
public OffscreenTarget finalTarget() { return finalTarget; }
public void destroyFinalTarget() {
if (resourceManager != null) finalTarget.destroy(resourceManager);
}
public static final long MAX_PENDING_UPLOAD_BYTES = 16L * 1024L * 1024L;
public static final int MAX_PENDING_UPLOAD_COMMANDS = 256;
private Runnable beforeEndCopyPass;
private long statLoopFrames;
private long statPresentedFrames;
private long statPresentSkips;
private long statEmptyFrames;
private long statAcquireWaitNanos;
public long statLoopFrames() { return statLoopFrames; }
public long statPresentedFrames() { return statPresentedFrames; }
public long statPresentSkips() { return statPresentSkips; }
public long statEmptyFrames() { return statEmptyFrames; }
public long statAcquireWaitNanos() { return statAcquireWaitNanos; }
public FrameManager(Device device) {
this.device = device;
@@ -199,24 +204,12 @@ public final class FrameManager {
this.resourceManager = resourceManager;
}
private static volatile boolean threadLogged;
public void beginFrame() {
final FrameState f = frame();
if (f.frameActive) {
LOG.warn("beginFrame() called while frame already active on thread {}", Thread.currentThread().getName());
return;
}
if (!threadLogged) {
threadLogged = true;
final String renderThread = Thread.currentThread().getName();
final String windowThread = device.getWindowThreadName();
if (windowThread != null && !windowThread.equals(renderThread)) {
LOG.warn("Frames are recorded on '{}' but the SDL window was created on '{}'; SDL requires swapchain acquire/wait on the window-creating thread", renderThread, windowThread);
} else {
LOG.info("First beginFrame() on thread={}", renderThread);
}
}
flushPendingUploadCommandBuffer(f);
if (globalSyncAtNextBeginFrame) {
@@ -231,11 +224,10 @@ public final class FrameManager {
throw new RuntimeException("Failed to acquire GPU command buffer: " + SDLError.SDL_GetError());
}
f.swapchainTexture = 0;
f.swapchainWidth = 0;
f.swapchainHeight = 0;
f.swapchainAcquiredThisFrame = false;
f.swapchainUnavailable = false;
f.presentedThisFrame = false;
maintainFinalTarget();
f.frameActive = true;
f.renderPass = 0;
@@ -244,25 +236,53 @@ public final class FrameManager {
f.frameNumber++;
f.clearedThisFrame = false;
f.depthClearedThisFrame = false;
f.swapchainUsedThisFrame = false;
f.skipPresent = false;
f.fbo0UsedThisFrame = false;
}
private static final long FINAL_TARGET_SIZE_POLL_NANOS = 1_000_000_000L;
private long finalTargetPollNanos;
private volatile Thread mainThread;
public void setMainThread(Thread thread) {
this.mainThread = thread;
}
// Resizing releases a texture an in-flight present may still reference, so drain before ensureSize.
private void maintainFinalTarget() {
if (resourceManager == null) return;
final Thread owner = mainThread;
if (owner != null && Thread.currentThread() != owner) return;
final long now = System.nanoTime();
final boolean poll = finalTargetPollNanos == 0L || now - finalTargetPollNanos >= FINAL_TARGET_SIZE_POLL_NANOS;
if (!poll && !device.wasWindowResized() && finalTarget.isReady()) return;
if (poll) finalTargetPollNanos = now;
final long size = device.getWindowSizeInPixels();
if (size == 0L) return;
final int width = (int) (size >> 32);
final int height = (int) size;
if (finalTarget.isReady() && width == finalTarget.width() && height == finalTarget.height()) return;
if (presenter != null) presenter.drain();
finalTarget.ensureSize(device, resourceManager, width, height);
}
public void markFrameEmpty(FrameState f) {
f.skipPresent = true;
f.emptyFramesThisFrame++;
}
static boolean swapchainClearNeedsPassBreak(ContextState st, FrameState f) {
return (st.pendingSwapchainClear || st.pendingSwapchainDepthClear || st.pendingSwapchainStencilClear) && f.renderPass != 0 && f.currentColorTarget == f.swapchainTexture;
return (st.pendingSwapchainClear || st.pendingSwapchainDepthClear || st.pendingSwapchainStencilClear) && f.renderPass != 0 && f.activeLayoutHash == FBO0_LAYOUT_HASH;
}
public boolean ensureSwapchainRenderPass(FrameState f, ContextState st) {
public boolean ensureFbo0RenderPass(FrameState f, ContextState st) {
final boolean clearColor = st.pendingSwapchainClear;
if (swapchainClearNeedsPassBreak(st, f)) {
endRenderPassIfActive(f);
}
final boolean applied = ensureSwapchainRenderPass(f,
final boolean applied = ensureFbo0RenderPass(f,
clearColor ? st.pendingSwapchainR : st.clearR,
clearColor ? st.pendingSwapchainG : st.clearG,
clearColor ? st.pendingSwapchainB : st.clearB,
@@ -277,9 +297,9 @@ public final class FrameManager {
return true;
}
private boolean ensureSwapchainRenderPass(FrameState f, float clearR, float clearG, float clearB, float clearA, boolean clear, boolean clearDepth, float depthValue, boolean clearStencil, int stencilValue) {
if (!ensureSwapchainAcquired(f)) return false; // present-skip; don't bind tex=0
if (f.renderPass != 0 && f.currentColorTarget == f.swapchainTexture) {
private boolean ensureFbo0RenderPass(FrameState f, float clearR, float clearG, float clearB, float clearA, boolean clear, boolean clearDepth, float depthValue, boolean clearStencil, int stencilValue) {
if (!finalTarget.isReady() || f.commandBuffer == 0) return false;
if (f.renderPass != 0 && f.activeLayoutHash == FBO0_LAYOUT_HASH) {
return false;
}
@@ -289,7 +309,7 @@ public final class FrameManager {
clear = true;
}
final long depthTexture = resourceManager != null ? resourceManager.getOrCreateSwapchainDepthStencil(f.swapchainWidth, f.swapchainHeight) : 0L;
final long depthTexture = resourceManager != null ? resourceManager.getOrCreateSwapchainDepthStencil(finalTarget.width(), finalTarget.height()) : 0L;
if (depthTexture != 0 && !f.depthClearedThisFrame) {
clearDepth = true;
clearStencil = true;
@@ -298,7 +318,7 @@ public final class FrameManager {
try (var stack = stackPush()) {
final SDL_GPUColorTargetInfo.Buffer colorTargets = SDL_GPUColorTargetInfo.calloc(1, stack);
final long ctAddr = colorTargets.get(0).address();
MemoryAccess.putAddress(ctAddr + SDL_GPUColorTargetInfo.TEXTURE, f.swapchainTexture);
MemoryAccess.putAddress(ctAddr + SDL_GPUColorTargetInfo.TEXTURE, finalTarget.colorTexture());
MemoryAccess.putInt(ctAddr + SDL_GPUColorTargetInfo.LOAD_OP, clear ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
MemoryAccess.putInt(ctAddr + SDL_GPUColorTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
@@ -333,10 +353,11 @@ public final class FrameManager {
}
f.renderPassGeneration++;
f.swapchainPassesThisFrame++;
f.currentColorTarget = f.swapchainTexture;
f.currentColorTarget = finalTarget.colorTexture();
f.currentDepthTarget = depthTexture;
f.activeLayoutHash = SWAPCHAIN_LAYOUT_HASH;
f.swapchainUsedThisFrame = true;
f.activeLayoutHash = FBO0_LAYOUT_HASH;
f.fbo0UsedThisFrame = true;
finalTarget.markContent();
}
return true;
}
@@ -388,6 +409,8 @@ public final class FrameManager {
public void noteMipGen() { frame().mipGensThisFrame++; }
public void noteMipGenSubmit() { frame().mipGenSubmitsThisFrame++; }
public void noteBlit() { frame().blitsThisFrame++; }
public void noteClearPass() { frame().clearPassesThisFrame++; }
@@ -441,6 +464,20 @@ public final class FrameManager {
}
}
private volatile boolean acquireThreadReported;
private void checkAcquireOnWindowThread() {
if (acquireThreadReported) return;
final Thread self = Thread.currentThread();
final Thread window = device.getWindowThread();
if (window == null || window == self) return;
acquireThreadReported = true;
final String message = "Swapchain acquired on '" + self.getName() + "', window created on '" + window.getName() + "'";
if (SystemProperties.isDeobf() && !SystemProperties.DISABLE_SDL_PRESENTER_THREAD) throw new IllegalStateException(message);
LOG.warn(message);
}
public long ensureCopyPass() {
final FrameState f = frame();
if (f.copyPass != 0) {
@@ -477,17 +514,14 @@ public final class FrameManager {
}
public void requestFlushOnAllRegisteredFrames() {
final int n = registeredFrames.size();
for (int i = 0; i < n; i++) {
registeredFrames.get(i).flushRequested = true;
for (final FrameState f : registeredFrames.snapshot()) {
f.flushRequested = true;
}
}
public void requestSyncFlushOnAllRegisteredFrames() {
globalSyncAtNextBeginFrame = true;
final int n = registeredFrames.size();
for (int i = 0; i < n; i++) {
final FrameState f = registeredFrames.get(i);
for (final FrameState f : registeredFrames.snapshot()) {
f.flushRequested = true;
f.syncOnNextFlush = true;
}
@@ -514,10 +548,6 @@ public final class FrameManager {
endCopyPassIfActive(f);
endRenderPassIfActive(f);
if (!f.swapchainAcquiredThisFrame && !f.swapchainUnavailable && !f.skipPresent && f.commandBuffer != 0) {
ensureSwapchainAcquired(f);
}
if (f.commandBuffer != 0) {
Tracy.beginZone(Z_SDL_SUBMIT);
try {
@@ -538,13 +568,13 @@ public final class FrameManager {
f.commandBuffer = 0;
}
if (Tracy.ENABLED && resourceManager != null) SdlFramePlots.emit(f, resourceManager);
drainWindowPresentCounters(f);
statLoopFrames++;
if (f.swapchainAcquiredThisFrame) statPresentedFrames++;
statPresentSkips += f.presentSkipsThisFrame;
statEmptyFrames += f.emptyFramesThisFrame;
statAcquireWaitNanos += f.acquireWaitNanosThisFrame;
if (Tracy.ENABLED && resourceManager != null) {
SdlFramePlots.emit(f, resourceManager);
}
f.midFrameSubmitsThisFrame = 0;
f.lastPlottedRenderPassGen = f.renderPassGeneration;
f.computePassesThisFrame = 0;
@@ -563,6 +593,8 @@ public final class FrameManager {
f.stateAppliesThisFrame = 0;
f.acquireWaitNanosThisFrame = 0;
f.mipGensThisFrame = 0;
f.mipGenSubmitsThisFrame = 0;
f.uploadFlushSubmitsThisFrame = 0;
f.blitsThisFrame = 0;
f.clearPassesThisFrame = 0;
f.swapchainPassesThisFrame = 0;
@@ -577,9 +609,6 @@ public final class FrameManager {
f.computeBatchJoinsThisFrame = 0;
f.frameActive = false;
f.swapchainTexture = 0;
if (afterPresent != null) afterPresent.run();
}
public void submitMidFrame() {
@@ -603,6 +632,7 @@ public final class FrameManager {
Tracy.endZone();
}
f.submitsThisFrame++;
f.midFrameSubmitsThisFrame++;
f.commandBuffer = 0;
if (f.frameActive) {
reacquireCommandBuffer();
@@ -614,6 +644,7 @@ public final class FrameManager {
if (f.frameActive) {
endFrame();
}
presentFinalTarget();
beginFrame();
}
@@ -690,6 +721,7 @@ public final class FrameManager {
device.reportGpuFailure("submit pending upload command buffer");
}
f.submitsThisFrame++;
f.uploadFlushSubmitsThisFrame++;
f.pendingUploadCommandBuffer = 0;
f.pendingUploadBytes = 0;
f.pendingUploadCommands = 0;
@@ -724,22 +756,16 @@ public final class FrameManager {
public long getActiveLayoutHash(FrameState f) { return f.activeLayoutHash; }
public long getSwapchainTexture() {
final FrameState f = frame();
ensureSwapchainAcquired(f);
return f.swapchainTexture;
public long getFbo0Texture() {
return finalTarget.colorTexture();
}
public int getSwapchainWidth() {
final FrameState f = frame();
ensureSwapchainAcquired(f);
return f.swapchainWidth;
public int getFbo0Width() {
return finalTarget.width();
}
public int getSwapchainHeight() {
final FrameState f = frame();
ensureSwapchainAcquired(f);
return f.swapchainHeight;
public int getFbo0Height() {
return finalTarget.height();
}
public int getSwapchainFormat() {
@@ -774,8 +800,8 @@ public final class FrameManager {
return frame().frameNumber;
}
public boolean isSwapchainUsedThisFrame() {
return frame().swapchainUsedThisFrame;
public boolean isFbo0UsedThisFrame() {
return frame().fbo0UsedThisFrame;
}
public void reacquireCommandBuffer() {
@@ -788,24 +814,18 @@ public final class FrameManager {
f.currentDepthTarget = 0;
if (f.frameActive) {
final boolean wasAcquired = f.swapchainAcquiredThisFrame;
f.swapchainAcquiredThisFrame = false;
f.swapchainTexture = 0;
if (wasAcquired && !ensureSwapchainAcquired(f)) {
LOG.warn("Failed to re-acquire swapchain after readback: {}", SDLError.SDL_GetError());
}
f.clearedThisFrame = false;
}
}
public void releaseThreadState() {
final FrameState f = frame();
final FrameState f = tlFrame.get();
if (f == null) return;
if (resourceManager != null) resourceManager.flushUploadArena(f);
flushPendingUploadCommandBuffer(f);
releaseFrameStateResources(f);
registeredFrames.remove(f);
tlFrame.remove();
refreshSoleFrame();
registeredFrames.remove(f);
}
private void releaseFrameStateResources(FrameState f) {
@@ -843,23 +863,95 @@ public final class FrameManager {
}
public void releaseAllRegisteredFrames() {
for (FrameState f : registeredFrames) {
for (final FrameState f : registeredFrames.snapshot()) {
releaseFrameStateResources(f);
}
registeredFrames.clear();
soleFrame = null;
}
public boolean ensureSwapchainAcquired() {
return ensureSwapchainAcquired(frame());
public long lastFrameGateNanos() {
return frame().lastFrameGateNanos;
}
public boolean ensureSwapchainAcquired(FrameState f) {
if (f.swapchainAcquiredThisFrame) return true;
if (f.swapchainUnavailable) return false;
if (!f.frameActive || f.commandBuffer == 0) return false;
public long lastFrameGateEndNanos() {
return frame().lastFrameGateEndNanos;
}
endActiveEncoders(f);
private volatile Presenter presenter;
public void setPresenter(Presenter presenter) {
this.presenter = presenter;
}
private final FrameState windowFrame = new FrameState();
public FrameState windowFrame() {
return windowFrame;
}
public void presentFinalTarget() {
if (!finalTarget.hasContent()) return;
present(finalTarget.colorTexture(), finalTarget.width(), finalTarget.height(), SDL_FLIP_NONE, false);
}
public void presentSplash(long srcTexture, int srcW, int srcH) {
present(srcTexture, srcW, srcH, SDL_FLIP_VERTICAL, true);
}
private void present(long srcTexture, int srcW, int srcH, int flipMode, boolean splash) {
final FrameState f = frame();
if (f.presentedThisFrame) return;
final Presenter p = presenter;
if (p != null) {
f.presentedThisFrame = true;
p.requestPresent(srcTexture, srcW, srcH, flipMode);
} else {
presentBlit(f, srcTexture, srcW, srcH, flipMode);
}
if (splash) f.presentedThisFrame = false;
}
private final AtomicLong windowAcquireWaitNanos = new AtomicLong();
private final AtomicInteger windowPresentSkips = new AtomicInteger();
private void drainWindowPresentCounters(FrameState f) {
if (f == windowFrame) return;
final long acquire = windowAcquireWaitNanos.getAndSet(0L);
if (acquire != 0L) {
f.acquireWaitNanosThisFrame += acquire;
f.lastFrameGateNanos = windowFrame.lastFrameGateNanos;
f.lastFrameGateEndNanos = windowFrame.lastFrameGateEndNanos;
}
f.presentSkipsThisFrame += windowPresentSkips.getAndSet(0);
}
private void noteAcquireWait(FrameState f, long waitNanos) {
if (f == windowFrame) windowAcquireWaitNanos.addAndGet(waitNanos);
else f.acquireWaitNanosThisFrame += waitNanos;
}
private void notePresentSkip(FrameState f) {
if (f == windowFrame) windowPresentSkips.incrementAndGet();
else f.presentSkipsThisFrame++;
}
public void presentOnWindowThread(long srcTexture, int srcW, int srcH, int flipMode) {
presentBlit(windowFrame, srcTexture, srcW, srcH, flipMode);
windowFrame.presentedThisFrame = false;
windowFrame.swapchainUnavailable = false;
}
public boolean presentBlit(FrameState f, long srcTexture, int srcW, int srcH, int flipMode) {
if (srcTexture == 0 || srcW <= 0 || srcH <= 0) return false;
if (f.presentedThisFrame || f.swapchainUnavailable) return false;
checkAcquireOnWindowThread();
final long window = device.getClaimedWindow();
if (window == 0) return false;
final long cb = SDL_AcquireGPUCommandBuffer(device.getDevice());
if (cb == 0) return false;
final long t0 = System.nanoTime();
final long tex;
@@ -872,7 +964,7 @@ public final class FrameManager {
final IntBuffer pHeight = stack.ints(0);
Tracy.beginZone(Z_SDL_ACQUIRE_WAIT);
try {
callOk = SDL_WaitAndAcquireGPUSwapchainTexture(f.commandBuffer, Display.getWindow(), pTexture, pWidth, pHeight);
callOk = SDL_WaitAndAcquireGPUSwapchainTexture(cb, window, pTexture, pWidth, pHeight);
} finally {
Tracy.endZone();
}
@@ -880,25 +972,43 @@ public final class FrameManager {
w = pWidth.get(0);
h = pHeight.get(0);
} catch (RuntimeException re) {
f.acquireWaitNanosThisFrame += System.nanoTime() - t0;
SDL_CancelGPUCommandBuffer(cb);
f.swapchainUnavailable = true;
throw re;
}
f.acquireWaitNanosThisFrame += System.nanoTime() - t0;
final long tEnd = System.nanoTime();
f.lastFrameGateNanos = tEnd - t0;
f.lastFrameGateEndNanos = tEnd;
noteAcquireWait(f, tEnd - t0);
if (!callOk) {
if (!callOk || tex == 0) {
SDL_CancelGPUCommandBuffer(cb);
f.swapchainUnavailable = true;
notePresentSkip(f);
return false;
}
if (tex == 0) {
f.swapchainUnavailable = true;
f.presentSkipsThisFrame++;
try (MemoryStack stack = stackPush()) {
final long info = stack.ncalloc(SDL_GPUBlitInfo.ALIGNOF, 1, SDL_GPUBlitInfo.SIZEOF);
writeBlitRegion(info + SDL_GPUBlitInfo.SOURCE, srcTexture, srcW, srcH);
writeBlitRegion(info + SDL_GPUBlitInfo.DESTINATION, tex, w, h);
MemoryAccess.putInt(info + SDL_GPUBlitInfo.FILTER, SDL_GPU_FILTER_LINEAR);
MemoryAccess.putInt(info + SDL_GPUBlitInfo.FLIP_MODE, flipMode);
nSDL_BlitGPUTexture(cb, info);
}
if (!SDL_SubmitGPUCommandBuffer(cb)) {
device.reportGpuFailure("submit present blit");
return false;
}
f.swapchainTexture = tex;
f.swapchainWidth = w;
f.swapchainHeight = h;
f.swapchainAcquiredThisFrame = true;
f.presentedThisFrame = true;
if (afterPresent != null) afterPresent.run();
return true;
}
private static void writeBlitRegion(long region, long texture, int w, int h) {
MemoryAccess.putAddress(region + SDL_GPUBlitRegion.TEXTURE, texture);
MemoryAccess.putInt(region + SDL_GPUBlitRegion.W, w);
MemoryAccess.putInt(region + SDL_GPUBlitRegion.H, h);
}
}
@@ -1,7 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu.splash;
package com.gtnewhorizons.angelica.sdlgpu.frame;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import org.lwjgl.opengl.GL11;
@@ -9,13 +8,14 @@ import org.lwjgl.opengl.GL11;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_SAMPLER;
public final class SplashOffscreenTarget {
public final class OffscreenTarget {
private int fboGlId;
private int colorTexGlId;
private long colorTexHandle;
private int width;
private int height;
private boolean hasContent;
public void create(Device device, ResourceManager rm, int width, int height, int swapchainSdlFormat) {
this.width = width;
@@ -37,10 +37,20 @@ public final class SplashOffscreenTarget {
fbo.cachedFormatsDirty = true;
}
public void ensureSize(Device device, ResourceManager rm, int width, int height) {
if (width <= 0 || height <= 0) return;
if (colorTexHandle != 0 && width == this.width && height == this.height) return;
destroy(rm);
create(device, rm, width, height, device.getSwapchainTextureFormat());
}
public int fboId() { return fboGlId; }
public long colorTexture() { return colorTexHandle; }
public int width() { return width; }
public int height() { return height; }
public boolean isReady() { return colorTexHandle != 0; }
public boolean hasContent() { return hasContent; }
public void markContent() { hasContent = true; }
public boolean isFor(ContextState st) {
return fboGlId != 0 && st.boundFboId == fboGlId;
@@ -56,5 +66,6 @@ public final class SplashOffscreenTarget {
colorTexGlId = 0;
colorTexHandle = 0;
}
hasContent = false;
}
}
@@ -0,0 +1,38 @@
package com.gtnewhorizons.angelica.sdlgpu.frame;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
public final class Presenter {
private final FrameManager frameManager;
private final Executor windowThreadExecutor;
private final Semaphore pending = new Semaphore(1);
public Presenter(FrameManager frameManager, Executor windowThreadExecutor) {
this.frameManager = frameManager;
this.windowThreadExecutor = windowThreadExecutor;
}
public void requestPresent(long srcTexture, int srcW, int srcH, int flipMode) {
pending.acquireUninterruptibly();
boolean submitted = false;
try {
windowThreadExecutor.execute(() -> {
try {
frameManager.presentOnWindowThread(srcTexture, srcW, srcH, flipMode);
} finally {
pending.release();
}
});
submitted = true;
} finally {
if (!submitted) pending.release();
}
}
public void drain() {
pending.acquireUninterruptibly();
pending.release();
}
}
@@ -40,6 +40,8 @@ final class SdlFramePlots {
private static final long PLOT_PRESENT_TO_PRESENT_US = Tracy.plotHandle("sdl.presentToPresentUs");
private static final long PLOT_FRAMES_IN_FLIGHT = Tracy.plotHandle("sdl.framesInFlight");
private static final long PLOT_MIP_GENS = Tracy.plotHandle("sdl.mipGens");
private static final long PLOT_MIP_GEN_SUBMITS = Tracy.plotHandle("sdl.mipGenSubmits");
private static final long PLOT_UPLOAD_FLUSH_SUBMITS = Tracy.plotHandle("sdl.uploadFlushSubmits");
private static final long PLOT_BLIT_FALLBACKS = Tracy.plotHandle("sdl.blitFallbacks");
private static final long PLOT_PRESENT_SKIPS = Tracy.plotHandle("sdl.presentSkips");
private static final long PLOT_EMPTY_FRAMES = Tracy.plotHandle("sdl.emptyFrames");
@@ -54,6 +56,7 @@ 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_MID_FRAME_SUBMITS = Tracy.plotHandle("sdl.midFrameSubmits");
private static final int[] contextCounters = new int[SDLGPURenderBackend.CTR_COUNT];
@@ -99,9 +102,12 @@ final class SdlFramePlots {
f.lastEndFrameNanos = nowNanos;
Tracy.plotInt(PLOT_FRAMES_IN_FLIGHT, Device.framesInFlight());
Tracy.plotInt(PLOT_MIP_GENS, f.mipGensThisFrame);
Tracy.plotInt(PLOT_MIP_GEN_SUBMITS, f.mipGenSubmitsThisFrame);
Tracy.plotInt(PLOT_UPLOAD_FLUSH_SUBMITS, f.uploadFlushSubmitsThisFrame);
Tracy.plotInt(PLOT_BLIT_FALLBACKS, f.blitsThisFrame);
Tracy.plotInt(PLOT_PRESENT_SKIPS, f.presentSkipsThisFrame);
Tracy.plotInt(PLOT_EMPTY_FRAMES, f.emptyFramesThisFrame);
Tracy.plotInt(PLOT_DROPPED_DRAWS, f.droppedDrawsThisFrame);
Tracy.plotInt(PLOT_MID_FRAME_SUBMITS, f.midFrameSubmitsThisFrame);
}
}
@@ -109,7 +109,7 @@ public final class PipelineApplier {
}
if (st.boundFboId == 0) {
if (frameManager.ensureSwapchainRenderPass(f, st)) {
if (frameManager.ensureFbo0RenderPass(f, st)) {
st.pipeline.setDepthTargetFormat(resourceManager.getSwapchainDepthStencilFormat());
}
return;
@@ -64,7 +64,7 @@ public final class FboState {
h = Hashing.fmix64(h, db);
}
h = Hashing.fmix64(h, depthTexture);
if (h == 0L || h == FrameManager.SWAPCHAIN_LAYOUT_HASH) h = 0xD1CE5EEDL;
if (h == 0L || h == FrameManager.FBO0_LAYOUT_HASH) h = 0xD1CE5EEDL;
structuralLayoutHash = h;
}
@@ -587,7 +587,7 @@ public final class ResourceManager {
final GpuBucket b = gpuBufferPool.get(Hashing.packHiLo(usage, bucket));
if (b == null || b.handles.isEmpty()) return 0;
for (int i = 0, n = b.handles.size(); i < n; i++) {
if (gpuPoolCurrentFrame - b.frames.getLong(i) >= Device.framesInFlight()) {
if (gpuPoolCurrentFrame - b.frames.getLong(i) >= Device.MAX_FRAMES_IN_FLIGHT) {
final long handle = b.handles.getLong(i);
final int last = n - 1;
if (i != last) {
@@ -1376,7 +1376,7 @@ public final class ResourceManager {
for (int i = 0; i < n; i += 2) {
final long handle = batchSegmentPending.getLong(i);
final long stamp = batchSegmentPending.getLong(i + 1);
if (currentFrame - stamp >= Device.framesInFlight()) {
if (currentFrame - stamp >= Device.MAX_FRAMES_IN_FLIGHT) {
batchSegmentFree.enqueue(handle);
} else {
batchSegmentPending.set(w, handle);
@@ -101,19 +101,11 @@ public final class TextureOps {
public void readbackTexture(long texHandle, int x, int y, int w, int h, int level, ByteBuffer output) {
if (texHandle == 0 || output == null) return;
frameManager.endCopyPassIfActive();
frameManager.endRenderPassIfActive();
frameManager.submitMidFrame();
if (texHandle == frameManager.getSwapchainTexture()) {
final long cb = frameManager.getCommandBuffer();
if (cb == 0) return;
resourceManager.downloadFromTexture(cb, texHandle, x, y, w, h, level, output);
frameManager.reacquireCommandBuffer();
} else {
final long cb = SDL_AcquireGPUCommandBuffer(device.getDevice());
if (cb == 0) return;
resourceManager.downloadFromTexture(cb, texHandle, x, y, w, h, level, output);
}
final long cb = SDL_AcquireGPUCommandBuffer(device.getDevice());
if (cb == 0) return;
resourceManager.downloadFromTexture(cb, texHandle, x, y, w, h, level, output);
}
public void copyTexSubImageImpl(ContextState st, int destGlId, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
@@ -210,6 +202,7 @@ public final class TextureOps {
if (!SDL_SubmitGPUCommandBuffer(uploadCb)) {
device.reportGpuFailure("submit mipmap generation CB");
}
frameManager.noteMipGenSubmit();
if (f.commandBuffer == uploadCb) {
f.commandBuffer = 0;
@@ -31,11 +31,6 @@ public final class PerFrameBlockInjector {
private PerFrameBlockInjector() {}
public static String inject(String source, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {
final PerFrameUniformBlock[] blocks = new PerFrameUniformBlock[ShaderManager.BLOCK_COUNT];
blocks[ShaderManager.BLOCK_PER_FRAME] = normalize(perFrame);
blocks[ShaderManager.BLOCK_PER_PASS] = normalize(perPass);
if (blocks[0] == null && blocks[1] == null) return source;
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(source);
@@ -43,8 +38,19 @@ public final class PerFrameBlockInjector {
return source;
}
final List<Edit> edits = new ArrayList<>();
collectEdits(root, perFrame, perPass, edits);
return edits.isEmpty() ? source : applyEdits(source, edits);
}
public static void collectEdits(GLSLParser.Translation_unitContext root, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass, List<Edit> out) {
final PerFrameUniformBlock[] blocks = new PerFrameUniformBlock[ShaderManager.BLOCK_COUNT];
blocks[ShaderManager.BLOCK_PER_FRAME] = normalize(perFrame);
blocks[ShaderManager.BLOCK_PER_PASS] = normalize(perPass);
if (blocks[0] == null && blocks[1] == null) return;
final int insertAt = firstDeclarationStart(root);
if (insertAt < 0) return source;
if (insertAt < 0) return;
final Map<String, String> memberTypes = new LinkedHashMap<>();
final Object2IntOpenHashMap<String> memberBlock = new Object2IntOpenHashMap<>();
@@ -71,18 +77,16 @@ public final class PerFrameBlockInjector {
if (referenced.contains(m.name())) { injected[b] = true; anyInjected = true; break; }
}
}
if (!anyInjected) return source;
if (!anyInjected) return;
final List<Edit> edits = new ArrayList<>(stripEdits.size() + 2);
final StringBuilder blockDecls = new StringBuilder(128);
for (int b = 0; b < blocks.length; b++) {
if (injected[b]) appendBlockText(blockDecls, b, blocks[b].members(), shadowed);
}
edits.add(new Edit(insertAt, insertAt - 1, blockDecls.toString()));
out.add(new Edit(insertAt, insertAt - 1, blockDecls.toString()));
for (int i = 0; i < stripEdits.size(); i++) {
if (injected[stripBlocks.getInt(i)]) edits.add(stripEdits.get(i));
if (injected[stripBlocks.getInt(i)]) out.add(stripEdits.get(i));
}
return applyEdits(source, edits);
}
private static PerFrameUniformBlock normalize(PerFrameUniformBlock block) {
@@ -188,19 +188,46 @@ public final class ShaderManager {
return new PrewarmTransformResult(transformedSource, Set.of());
}
final List<Edit> edits = new ArrayList<>();
if (glShaderType == GL20.GL_VERTEX_SHADER || glShaderType == GL20.GL_FRAGMENT_SHADER) {
PerFrameBlockInjector.collectEdits(root, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock, edits);
}
final GlslVulkanPreprocess.Metadata meta = GlslVulkanPreprocess.collectEdits(transformedSource, root, glShaderType, "prewarm", true, edits);
if (glShaderType == GL20.GL_VERTEX_SHADER) {
ClipZRemap.collectEdits(root, edits);
}
SamplerStripper.collectEdits(root, transformedSource, edits);
String s = edits.isEmpty() ? transformedSource : GlslVulkanPreprocess.applyEdits(transformedSource, edits);
if (glShaderType == GL20.GL_VERTEX_SHADER || glShaderType == GL20.GL_FRAGMENT_SHADER) {
s = PerFrameBlockInjector.inject(s, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
}
final String s = edits.isEmpty() ? transformedSource : GlslVulkanPreprocess.applyEdits(transformedSource, edits);
return new PrewarmTransformResult(s, meta.boolUniforms());
}
public static PrewarmTransformResult applyPrewarmTransformsFull(String finalSource, GLSLParser.Translation_unitContext bodyTree, int headerLen, int glShaderType) {
final String header = finalSource.substring(0, headerLen);
final String body = finalSource.substring(headerLen);
final List<Edit> edits = new ArrayList<>();
if (glShaderType == GL20.GL_VERTEX_SHADER || glShaderType == GL20.GL_FRAGMENT_SHADER) {
PerFrameBlockInjector.collectEdits(bodyTree, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock, edits);
}
final GlslVulkanPreprocess.Metadata meta = GlslVulkanPreprocess.collectEdits(null, bodyTree, glShaderType, "prewarm", true, edits);
if (glShaderType == GL20.GL_VERTEX_SHADER) {
ClipZRemap.collectEdits(bodyTree, edits);
}
SamplerStripper.collectEdits(bodyTree, body, edits);
String outHeader = header;
if (meta.needsSamplerless()) {
final int nl = header.indexOf('\n');
outHeader = header.substring(0, nl) + "\n" + GlslVulkanPreprocess.SAMPLERLESS_EXTENSION + header.substring(nl);
}
if (edits.isEmpty() && outHeader == header) {
return new PrewarmTransformResult(finalSource, meta.boolUniforms());
}
return new PrewarmTransformResult(outHeader + GlslVulkanPreprocess.applyEdits(body, edits), meta.boolUniforms());
}
public static void prewarmSpirv(String transformedSource, int glShaderType) {
prewarmSpirv(transformedSource, null, 0, glShaderType);
}
public static void prewarmSpirv(String transformedSource, GLSLParser.Translation_unitContext bodyTree, int headerLen, int glShaderType) {
if (glShaderType != GL20.GL_VERTEX_SHADER && glShaderType != GL20.GL_FRAGMENT_SHADER
&& glShaderType != GL43.GL_COMPUTE_SHADER && glShaderType != GL32.GL_GEOMETRY_SHADER) {
return;
@@ -211,7 +238,9 @@ public final class ShaderManager {
if (PREWARM_CACHE.containsKey(key)) return;
}
final PrewarmTransformResult pre = applyPrewarmTransformsFull(transformedSource, glShaderType);
final PrewarmTransformResult pre = bodyTree != null
? applyPrewarmTransformsFull(transformedSource, bodyTree, headerLen, glShaderType)
: applyPrewarmTransformsFull(transformedSource, glShaderType);
final SpirvCompiler.Result r = SpirvCompiler.compile(pre.source(), shaderKindFor(glShaderType), "prewarm", SpirvCompiler.Options.vulkanForced460Core());
if (r.spirv() == null) return;
@@ -652,7 +681,7 @@ public final class ShaderManager {
return null;
}
LOG.info("Vertex attribute conversion: program {} converted {}", program, attribs);
if (SystemProperties.DUMP_SHADERS) dumpVariantSource(program, src);
if (SystemProperties.dumpShaders()) dumpVariantSource(program, src);
return v;
} finally {
memFree(r.spirv());
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.taumc.glsl.grammar.GLSLParser;
@@ -18,11 +19,29 @@ public final class UscaledRetype {
public record Attrib(String name, int location, int declVecSize, int boundVecSize, boolean signed) {}
private record RetypeKey(String source, List<Attrib> attribs) {}
private static final int CACHE_MAX = 64;
private static final Object2ObjectLinkedOpenHashMap<RetypeKey, String> CACHE = new Object2ObjectLinkedOpenHashMap<>();
private UscaledRetype() {}
public static String retype(String source, List<Attrib> attribs) {
if (attribs.isEmpty()) return source;
final RetypeKey key = new RetypeKey(source, List.copyOf(attribs));
synchronized (CACHE) {
if (CACHE.containsKey(key)) return CACHE.getAndMoveToFirst(key);
}
final String result = retypeUncached(source, attribs);
synchronized (CACHE) {
CACHE.putAndMoveToFirst(key, result);
while (CACHE.size() > CACHE_MAX) CACHE.removeLast();
}
return result;
}
private static String retypeUncached(String source, List<Attrib> attribs) {
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(source);
@@ -80,7 +80,7 @@ public final class DxbcCrossCompile {
return new Output(copy, cached.entrypoint);
}
final int dumpId = SystemProperties.DUMP_SHADERS ? CrossCompileUtil.SHADER_DUMP_COUNTER.getAndIncrement() : -1;
final int dumpId = SystemProperties.dumpShaders() ? CrossCompileUtil.SHADER_DUMP_COUNTER.getAndIncrement() : -1;
if (dumpId >= 0) CrossCompileUtil.dumpSpirv(spirv, dumpId, glShaderType);
final String hlsl = compileToHlsl(spirv, glShaderType);
@@ -87,7 +87,7 @@ public final class MslCrossCompile {
return new Output(copy, cached.entrypoint);
}
final int dumpId = SystemProperties.DUMP_SHADERS ? CrossCompileUtil.SHADER_DUMP_COUNTER.getAndIncrement() : -1;
final int dumpId = SystemProperties.dumpShaders() ? CrossCompileUtil.SHADER_DUMP_COUNTER.getAndIncrement() : -1;
if (dumpId >= 0) CrossCompileUtil.dumpSpirv(spirv, dumpId, glShaderType);
try (MemoryStack stack = stackPush()) {
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.sdlgpu.splash;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.frame.OffscreenTarget;
public final class SplashDispatcher {
@@ -32,10 +33,10 @@ public final class SplashDispatcher {
return (long) w << 32 | (h & 0xffffffffL);
}
public static void tryDispatch(SDLGPURenderBackend backend, SplashOffscreenTarget target) {
public static void tryDispatch(SDLGPURenderBackend backend, OffscreenTarget target, boolean force) {
if (!frameReady || target == null) return;
final long now = System.nanoTime();
if (now - lastBlitNanos < MIN_INTERVAL_NS) return;
if (!force && now - lastBlitNanos < MIN_INTERVAL_NS) return;
lastBlitNanos = now;
frameReady = false;
backend.dispatchSplashBlit(target);
@@ -0,0 +1,67 @@
package com.gtnewhorizons.angelica.sdlgpu.util;
import java.util.Arrays;
public final class ThreadRegistry<T> {
private final T[] empty;
private final Object lock = new Object();
private volatile T[] entries;
private volatile T sole;
public ThreadRegistry(T[] empty) {
this.empty = empty;
this.entries = empty;
}
public T sole() {
return sole;
}
public T[] snapshot() {
return entries;
}
public int size() {
return entries.length;
}
public void add(T entry) {
synchronized (lock) {
final T[] prev = entries;
final T[] next = Arrays.copyOf(prev, prev.length + 1);
next[prev.length] = entry;
publish(next);
}
}
public boolean remove(T entry) {
synchronized (lock) {
final T[] prev = entries;
int idx = -1;
for (int i = 0; i < prev.length; i++) {
if (prev[i] == entry) {
idx = i;
break;
}
}
if (idx < 0) return false;
final T[] next = Arrays.copyOf(prev, prev.length - 1);
System.arraycopy(prev, idx + 1, next, idx, prev.length - idx - 1);
publish(next);
}
return true;
}
public void clear() {
synchronized (lock) {
publish(empty);
}
}
private void publish(T[] next) {
entries = next;
sole = next.length == 1 ? next[0] : null;
}
}
@@ -7,7 +7,6 @@ import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL42;
import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -15,17 +14,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class ImageUnitBindingStateTest {
@SuppressWarnings("unchecked")
private static ContextState state() throws Exception {
final Field f = SDLGPURenderBackend.class.getDeclaredField("tlState");
f.setAccessible(true);
return ((ThreadLocal<ContextState>) f.get(null)).get();
}
@Test
void bindImageTextureRecordsTheFullTuple() throws Exception {
void bindImageTextureRecordsTheFullTuple() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final ContextState st = state();
final ContextState st = SdlTestRig.contextState();
backend.bindImageTexture(2, 77, 3, true, 5, GL15.GL_READ_WRITE, GL30.GL_R32UI);
@@ -38,7 +31,7 @@ class ImageUnitBindingStateTest {
}
@Test
void getIntegerIndexedAnswersImageBindingsFromState() throws Exception {
void getIntegerIndexedAnswersImageBindingsFromState() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
backend.bindImageTexture(1, 42, 2, false, 4, GL15.GL_READ_ONLY, GL30.GL_RGBA32UI);
@@ -52,9 +45,9 @@ class ImageUnitBindingStateTest {
}
@Test
void outOfRangeUnitIsIgnored() throws Exception {
void outOfRangeUnitIsIgnored() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final ContextState st = state();
final ContextState st = SdlTestRig.contextState();
backend.bindImageTexture(0, 11, 0, true, 0, GL15.GL_READ_ONLY, GL30.GL_R32UI);
backend.bindImageTexture(ContextState.MAX_IMAGE_UNITS, 99, 0, true, 0, GL15.GL_READ_ONLY, GL30.GL_R32UI);
@@ -0,0 +1,17 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.mitchej123.lwjgl.LWJGLService;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
class SDLGPULWJGLServiceGatingTest {
@Test
void anUnengagedSdlDeclinesAndNeverArmsTheFallbackTripwire() {
assertFalse(SDLGPUGate.isEngaged(), "test JVM must not have an engaged SDL GPU device");
assertEquals(LWJGLService.PRIORITY_UNAVAILABLE, new SDLGPULWJGLService().getPriority());
assertFalse(SDLGPULWJGLService.isOfferedAsAvailable(), "declining must leave fallBackToGL() usable");
}
}
@@ -1,9 +1,12 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDLHints;
import org.lwjgl.sdl.SDLInit;
import java.lang.reflect.Field;
@@ -57,12 +60,21 @@ public final class SdlTestRig {
return create().frameManager;
}
public static ContextState contextState() {
return Reflect.invokeStatic(SDLGPURenderBackend.class, "s", new Class<?>[0]);
}
private static long realSdlDevice;
private static final int SHADER_FORMATS = SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_DXBC | SDL_GPU_SHADERFORMAT_DXIL;
public static void initSdlVideo() {
SDLHints.SDL_SetHint(SDLHints.SDL_HINT_MAC_BACKGROUND_APP, "1");
assumeTrue(SDLInit.SDL_Init(SDL_INIT_VIDEO), () -> "SDL_Init failed: " + SDLError.SDL_GetError());
}
public static SdlTestRig acquireRealDevice() throws ReflectiveOperationException {
assumeTrue(SDLInit.SDL_Init(SDL_INIT_VIDEO), "SDL_Init failed: " + SDLError.SDL_GetError());
initSdlVideo();
realSdlDevice = SDL_CreateGPUDevice(SHADER_FORMATS, true, (CharSequence) null);
assumeTrue(realSdlDevice != 0, "No SDL GPU device: " + SDLError.SDL_GetError());
@@ -48,13 +48,6 @@ class UniformNegativeLocationTest {
st.releaseUniformStaging(prog);
}
@SuppressWarnings("unchecked")
private static ContextState threadState() throws Exception {
final Field f = SDLGPURenderBackend.class.getDeclaredField("tlState");
f.setAccessible(true);
return ((ThreadLocal<ContextState>) f.get(null)).get();
}
private static void clearApplier(SDLGPURenderBackend backend) throws Exception {
final Field f = SDLGPURenderBackend.class.getDeclaredField("pipelineApplier");
f.setAccessible(true);
@@ -66,7 +59,7 @@ class UniformNegativeLocationTest {
void scalarAndVectorEntryPointsNeverReachTheApplier() throws Exception {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
clearApplier(backend);
threadState().boundProgramObj = null;
SdlTestRig.contextState().boundProgramObj = null;
final IntBuffer iv = MemoryUtil.memAllocInt(4);
try {
@@ -1,16 +1,14 @@
package com.gtnewhorizons.angelica.sdlgpu.device;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDLInit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.lwjgl.sdl.SDLInit.SDL_INIT_VIDEO;
class DeviceLifecycleGpuTest {
@@ -18,7 +16,7 @@ class DeviceLifecycleGpuTest {
@BeforeAll
static void initSdl() {
assumeTrue(SDLInit.SDL_Init(SDL_INIT_VIDEO), () -> "SDL_Init failed: " + SDLError.SDL_GetError());
SdlTestRig.initSdlVideo();
}
@AfterEach
@@ -0,0 +1,33 @@
package com.gtnewhorizons.angelica.sdlgpu.frame;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AcquireAffinityTest {
private static final Thread WINDOW = new Thread("window");
private static final Thread OTHER = new Thread("other");
@Test
void acquiringOnTheWindowThreadIsTheOnlyAcceptedCase() {
assertFalse(FrameManager.acquireAffinityViolated(WINDOW, WINDOW, 0));
}
@Test
void acquiringOnAnyOtherThreadViolatesTheSdlContract() {
assertTrue(FrameManager.acquireAffinityViolated(OTHER, WINDOW, 0));
}
@Test
void aLaterAcquireFromASecondThreadViolatesEvenWhenTheFirstWasCorrect() {
assertTrue(FrameManager.acquireAffinityViolated(WINDOW, WINDOW, 1),
"drift after a correct first acquire must still be caught");
}
@Test
void anUnknownWindowThreadCannotBeJudged() {
assertFalse(FrameManager.acquireAffinityViolated(OTHER, null, 3));
}
}
@@ -6,13 +6,13 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import com.gtnewhorizons.angelica.sdlgpu.util.ThreadRegistry;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ContextCounterAggregationTest {
private List<ContextState> registered;
private ThreadRegistry<ContextState> registered;
private ContextState a;
private ContextState b;
@@ -0,0 +1,113 @@
package com.gtnewhorizons.angelica.sdlgpu.frame;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.Executor;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
@Timeout(10)
class PresenterTest {
private static final class DeferredExecutor implements Executor {
final Deque<Runnable> tasks = new ArrayDeque<>();
@Override
public void execute(Runnable command) {
tasks.add(command);
}
void runNext() {
tasks.removeFirst().run();
}
}
private static Presenter presenter(DeferredExecutor executor) {
return new Presenter(new FrameManager(new Device()), executor);
}
private static void awaitWaiting(Thread t) throws InterruptedException {
while (t.getState() != Thread.State.WAITING && t.getState() != Thread.State.TIMED_WAITING) {
assertTrue(t.isAlive(), "thread died instead of blocking");
Thread.onSpinWait();
}
}
@Test
void aSecondRequestBlocksUntilTheFirstPresentRuns() throws Exception {
final DeferredExecutor executor = new DeferredExecutor();
final Presenter p = presenter(executor);
p.requestPresent(1L, 10, 10, SDL_FLIP_NONE);
assertEquals(1, executor.tasks.size());
final Thread second = new Thread(() -> p.requestPresent(2L, 10, 10, SDL_FLIP_NONE));
second.start();
awaitWaiting(second);
assertEquals(1, executor.tasks.size(), "second present must not enqueue while the first is in flight");
executor.runNext();
second.join();
assertEquals(1, executor.tasks.size(), "second present enqueues only after the first released the slot");
}
@Test
void drainBlocksUntilThePendingPresentCompletes() throws Exception {
final DeferredExecutor executor = new DeferredExecutor();
final Presenter p = presenter(executor);
p.requestPresent(1L, 10, 10, SDL_FLIP_NONE);
final Thread drainer = new Thread(p::drain);
drainer.start();
awaitWaiting(drainer);
executor.runNext();
drainer.join();
}
@Test
void anInlineExecutorReleasesTheSlotImmediately() {
final Presenter p = new Presenter(new FrameManager(new Device()), Runnable::run);
p.requestPresent(1L, 10, 10, SDL_FLIP_NONE);
p.requestPresent(2L, 10, 10, SDL_FLIP_NONE);
p.drain();
}
@Test
void presentingDoesNotEnterTheFrameRegistry() throws Exception {
final DeferredExecutor executor = new DeferredExecutor();
final FrameManager fm = new FrameManager(new Device());
final Presenter p = new Presenter(fm, executor);
fm.frame();
final int before = fm.registeredFrameCount();
p.requestPresent(1L, 10, 10, SDL_FLIP_NONE);
final Thread windowThread = new Thread(executor::runNext, "window-thread");
windowThread.start();
windowThread.join();
assertEquals(before, fm.registeredFrameCount(), "the window thread must stay out of the frame registry");
}
@Test
void theSplashPresentGoesThroughTheWindowThreadToo() {
final DeferredExecutor executor = new DeferredExecutor();
final FrameManager fm = new FrameManager(new Device());
fm.setPresenter(new Presenter(fm, executor));
fm.frame();
final int before = fm.registeredFrameCount();
fm.presentSplash(1L, 10, 10);
assertEquals(1, executor.tasks.size(), "the splash blit must be enqueued on the window thread");
assertEquals(before, fm.registeredFrameCount(), "the splash present must not touch the calling thread's frame");
}
}
@@ -8,13 +8,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class SwapchainClearPassBreakTest {
private static final long SWAPCHAIN = 0x5A1CL;
private static FrameManager.FrameState activeSwapchainPass() {
private static FrameManager.FrameState activeFbo0Pass() {
final FrameManager.FrameState f = new FrameManager.FrameState();
f.swapchainTexture = SWAPCHAIN;
f.renderPass = 0xBEEFL;
f.currentColorTarget = SWAPCHAIN;
f.activeLayoutHash = FrameManager.FBO0_LAYOUT_HASH;
return f;
}
@@ -22,23 +19,23 @@ class SwapchainClearPassBreakTest {
void aPendingColorClearBreaksTheActivePass() {
final ContextState st = new ContextState();
st.pendingSwapchainClear = true;
assertTrue(swapchainClearNeedsPassBreak(st, activeSwapchainPass()), "otherwise the clear is deferred to the next pass and wipes what this one drew");
assertTrue(swapchainClearNeedsPassBreak(st, activeFbo0Pass()), "otherwise the clear is deferred to the next pass and wipes what this one drew");
}
@Test
void aPendingDepthOrStencilClearBreaksTheActivePass() {
final ContextState depth = new ContextState();
depth.pendingSwapchainDepthClear = true;
assertTrue(swapchainClearNeedsPassBreak(depth, activeSwapchainPass()));
assertTrue(swapchainClearNeedsPassBreak(depth, activeFbo0Pass()));
final ContextState stencil = new ContextState();
stencil.pendingSwapchainStencilClear = true;
assertTrue(swapchainClearNeedsPassBreak(stencil, activeSwapchainPass()));
assertTrue(swapchainClearNeedsPassBreak(stencil, activeFbo0Pass()));
}
@Test
void nothingPendingLeavesThePassAlone() {
assertFalse(swapchainClearNeedsPassBreak(new ContextState(), activeSwapchainPass()));
assertFalse(swapchainClearNeedsPassBreak(new ContextState(), activeFbo0Pass()));
}
@Test
@@ -46,18 +43,18 @@ class SwapchainClearPassBreakTest {
final ContextState st = new ContextState();
st.pendingSwapchainClear = true;
final FrameManager.FrameState f = activeSwapchainPass();
final FrameManager.FrameState f = activeFbo0Pass();
f.renderPass = 0;
assertFalse(swapchainClearNeedsPassBreak(st, f));
}
@Test
void aPassBoundToAnFboIsNotTheSwapchainPass() {
void aPassBoundToAnFboIsNotTheFbo0Pass() {
final ContextState st = new ContextState();
st.pendingSwapchainClear = true;
final FrameManager.FrameState f = activeSwapchainPass();
f.currentColorTarget = 0xF80L;
final FrameManager.FrameState f = activeFbo0Pass();
f.activeLayoutHash = 0xF80L;
assertFalse(swapchainClearNeedsPassBreak(st, f));
}
}
@@ -0,0 +1,58 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_SAMPLER;
import static org.lwjgl.system.MemoryUtil.memAlloc;
import static org.lwjgl.system.MemoryUtil.memFree;
@Timeout(60)
class ReadbackSubmitOrderGpuTest {
private static final int SIZE = 4;
@AfterAll
static void releaseDevice() {
SdlTestRig.releaseRealDevice();
}
@Test
void aReadbackSubmitsTheFramesWritesFirst() throws Exception {
final SdlTestRig rig = SdlTestRig.acquireRealDevice();
final ResourceManager rm = rig.resourceManager;
final FrameManager fm = rig.frameManager;
final TextureOps textureOps = new TextureOps(rig.device, fm, rm, null);
final int glId = rm.genTexture();
final long texture = rm.createTexture(glId, GL11.GL_TEXTURE_2D, GL11.GL_RGBA8, SIZE, SIZE, 1, 1, SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET);
assertNotEquals(0L, texture, "texture creation failed");
fm.beginFrame();
final FrameManager.FrameState f = fm.frame();
final long frameCb = f.commandBuffer;
assertNotEquals(0L, frameCb, "no frame command buffer to order against");
assertEquals(0, f.midFrameSubmitsThisFrame);
final ByteBuffer out = memAlloc(SIZE * SIZE * 4);
try {
textureOps.readbackTexture(texture, 0, 0, SIZE, SIZE, 0, out);
} finally {
memFree(out);
}
assertEquals(1, f.midFrameSubmitsThisFrame, "the frame command buffer must be submitted before the download buffer, or the readback sees stale contents");
assertNotEquals(frameCb, f.commandBuffer, "the frame must continue on a freshly acquired command buffer");
fm.endFrame();
}
}
@@ -1,29 +1,23 @@
package com.gtnewhorizons.angelica.sdlgpu.sampler;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
class SamplerBindGenerationTest {
@SuppressWarnings("unchecked")
private static ContextState state() throws Exception {
final Field f = SDLGPURenderBackend.class.getDeclaredField("tlState");
f.setAccessible(true);
return ((ThreadLocal<ContextState>) f.get(null)).get();
}
@Test
void switchingActiveUnitDoesNotBumpTheGeneration() throws Exception {
void switchingActiveUnitDoesNotBumpTheGeneration() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final ContextState st = state();
final ContextState st = SdlTestRig.contextState();
backend.activeTexture(GL13.GL_TEXTURE0);
final int before = st.samplerBindGen;
@@ -37,9 +31,9 @@ class SamplerBindGenerationTest {
}
@Test
void aRealBindChangeBumpsTheGeneration() throws Exception {
void aRealBindChangeBumpsTheGeneration() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final ContextState st = state();
final ContextState st = SdlTestRig.contextState();
backend.activeTexture(GL13.GL_TEXTURE0);
backend.bindTexture(GL11.GL_TEXTURE_2D, 0);
@@ -50,9 +44,9 @@ class SamplerBindGenerationTest {
}
@Test
void rebindingTheSameTextureDoesNotBumpTheGeneration() throws Exception {
void rebindingTheSameTextureDoesNotBumpTheGeneration() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final ContextState st = state();
final ContextState st = SdlTestRig.contextState();
backend.activeTexture(GL13.GL_TEXTURE0);
backend.bindTexture(GL11.GL_TEXTURE_2D, 99);
@@ -63,9 +57,9 @@ class SamplerBindGenerationTest {
}
@Test
void bindingsAreHeldPerUnitSoTheActiveUnitIsNotSamplerState() throws Exception {
void bindingsAreHeldPerUnitSoTheActiveUnitIsNotSamplerState() {
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
final ContextState st = state();
final ContextState st = SdlTestRig.contextState();
backend.activeTexture(GL13.GL_TEXTURE2);
backend.bindTexture(GL11.GL_TEXTURE_2D, 55);
@@ -0,0 +1,76 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
import com.gtnewhorizons.angelica.glsm.testutil.TestPaths;
import com.gtnewhorizons.angelica.sdlgpu.sampler.SamplerLookup;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL20;
import org.lwjgl.util.shaderc.Shaderc;
import java.nio.ByteBuffer;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.lwjgl.system.MemoryUtil.memFree;
class FontFilterVertexSamplerTest {
private static final String FONT_FILTER_VSH = TestPaths.readString("src/main/resources/assets/angelica/shaders/fontFilter.vsh");
private static String runSdlPipeline(String raw) {
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(raw, GL20.GL_VERTEX_SHADER, "fontFilter.vsh", true);
String src = pre != null ? pre.rewrittenSource() : raw;
src = ClipZRemap.injectGLToVulkanClipZ(src);
src = SamplerStripper.stripUnused(src);
return src;
}
private static ShaderManager.StageReflection reflect(String source) {
final SpirvCompiler.Result r = SpirvCompiler.compile(source, Shaderc.shaderc_vertex_shader,
"fontFilter.vsh", SpirvCompiler.Options.vulkanForced460Core());
assertNotNull(r.spirv(), () -> "fontFilter.vsh failed to compile for Vulkan: " + r.error());
final ByteBuffer spirv = r.spirv();
try {
return ShaderManager.reflectStage(spirv, true);
} finally {
memFree(spirv);
}
}
@Test
void samplerSurvivesStrippingAndReflectsOnTheVertexStage() {
final String transformed = runSdlPipeline(FONT_FILTER_VSH);
assertTrue(transformed.contains("uniform sampler2D lightmap"), "a reference inside a runtime if must keep the declaration; only preprocessor #if hides it");
final List<String> samplers = reflect(transformed).samplerNames();
assertEquals(List.of("lightmap"), samplers, "the vertex stage must expose exactly the lightmap sampler");
}
@Test
void looseVec3UniformReflectsWithAStd140Offset() {
final List<ShaderManager.UboMember> members = reflect(runSdlPipeline(FONT_FILTER_VSH)).uboMembers();
final ShaderManager.UboMember lightmap = members.stream()
.filter(m -> m.name().endsWith("u_Lightmap"))
.findFirst()
.orElseThrow(() -> new AssertionError("u_Lightmap missing from the default uniform block: " + members));
assertEquals(3, lightmap.vectorSize(), "u_Lightmap must reflect as a 3-wide vector");
assertEquals(1, lightmap.columns(), "u_Lightmap is a vector, not a matrix");
assertEquals(0, lightmap.arrayStride(), "a scalar member takes Std140Writer's contiguous path");
assertEquals(0, lightmap.offset() % 16, "std140 aligns a vec3 to 16 bytes");
}
@Test
void samplerNamedLightmapResolvesToTextureUnitOne() {
final List<String> samplers = reflect(runSdlPipeline(FONT_FILTER_VSH)).samplerNames();
final Object2IntOpenHashMap<String> noExplicitUnits = new Object2IntOpenHashMap<>();
noExplicitUnits.defaultReturnValue(-1);
assertEquals(1, SamplerLookup.getSamplerTextureUnit(samplers, noExplicitUnits, 0), "even without a glUniform1i the SDL fallback must put a sampler named 'lightmap' on unit 1");
}
}
@@ -1,15 +1,28 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock;
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock.Member;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
import com.gtnewhorizons.angelica.glsm.shader.UniformType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL43;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
class PrewarmSingleParseEquivalenceTest {
@AfterEach
void clearBlocks() {
GLSMHooks.perFrameUniformBlock = null;
GLSMHooks.perPassUniformBlock = null;
}
private static String legacy(String source, int glShaderType) {
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(source, glShaderType, "test", true);
String src = pre != null ? pre.rewrittenSource() : source;
@@ -17,6 +30,9 @@ class PrewarmSingleParseEquivalenceTest {
src = ClipZRemap.injectGLToVulkanClipZ(src);
}
src = SamplerStripper.stripUnused(src);
if (glShaderType == GL20.GL_VERTEX_SHADER || glShaderType == GL20.GL_FRAGMENT_SHADER) {
src = PerFrameBlockInjector.inject(src, GLSMHooks.perFrameUniformBlock, GLSMHooks.perPassUniformBlock);
}
return src;
}
@@ -28,6 +44,14 @@ class PrewarmSingleParseEquivalenceTest {
assertEquals(legacyOut, parseOnceOut, "single-parse vs chained pipeline diverged for " + label);
}
private static void assertEquivalentIgnoringWhitespace(String label, String source, int glShaderType) {
GlslVulkanPreprocess.clearCache();
final String legacyOut = legacy(source, glShaderType);
GlslVulkanPreprocess.clearCache();
final String parseOnceOut = ShaderManager.applyPrewarmTransforms(source, glShaderType);
assertEquals(legacyOut.replaceAll("\\s+", " "), parseOnceOut.replaceAll("\\s+", " "), "single-parse vs chained pipeline diverged beyond whitespace for " + label);
}
@Test
void minimalFragment_noTransformsNeeded() {
final String src = "#version 460 core\n"
@@ -135,4 +159,87 @@ class PrewarmSingleParseEquivalenceTest {
final String src = "this is not valid GLSL @@@@\n";
assertEquivalent("unparseable", src, GL20.GL_FRAGMENT_SHADER);
}
private static final PerFrameUniformBlock PER_FRAME = new PerFrameUniformBlock(List.of(
new Member("cameraPosition", UniformType.VEC3),
new Member("frameTimeCounter", UniformType.FLOAT)));
private static final PerFrameUniformBlock PER_PASS = new PerFrameUniformBlock(List.of(
new Member("alphaTestRef", UniformType.FLOAT)));
@Test
void perFrameBlock_replaceableUniformStrippedAndInjected() {
GLSMHooks.perFrameUniformBlock = PER_FRAME;
GLSMHooks.perPassUniformBlock = PER_PASS;
final String src = "#version 460 core\n"
+ "uniform float frameTimeCounter;\n"
+ "uniform float alphaTestRef;\n"
+ "layout(location = 0) out vec4 fragColor;\n"
+ "void main() {\n"
+ " if (fragColor.a < alphaTestRef) discard;\n"
+ " fragColor = vec4(frameTimeCounter);\n"
+ "}\n";
assertEquivalent("pfb-replaceable", src, GL20.GL_FRAGMENT_SHADER);
}
@Test
void perFrameBlock_firstDeclIsUnlocatedVsInput() {
GLSMHooks.perFrameUniformBlock = PER_FRAME;
final String src = "#version 460 core\n"
+ "in vec3 a_Position;\n"
+ "layout(location = 5) in vec2 a_TexCoord;\n"
+ "uniform vec3 cameraPosition;\n"
+ "void main() {\n"
+ " gl_Position = vec4(a_Position - cameraPosition, 1.0);\n"
+ "}\n";
assertEquivalent("pfb-unlocated-first-in", src, GL20.GL_VERTEX_SHADER);
}
@Test
void perFrameBlock_memberUniformWithInitializer() {
GLSMHooks.perPassUniformBlock = PER_PASS;
final String src = "#version 460 core\n"
+ "uniform float alphaTestRef = 0.1;\n"
+ "layout(location = 0) out vec4 fragColor;\n"
+ "void main() {\n"
+ " if (fragColor.a < alphaTestRef) discard;\n"
+ "}\n";
assertEquivalent("pfb-initializer", src, GL20.GL_FRAGMENT_SHADER);
}
@Test
void perFrameBlock_firstDeclIsStrippedSampler() {
GLSMHooks.perFrameUniformBlock = PER_FRAME;
final String src = "#version 460 core\n"
+ "uniform sampler2D u_Unused;\n"
+ "uniform float frameTimeCounter;\n"
+ "layout(location = 0) out vec4 fragColor;\n"
+ "void main() {\n"
+ " fragColor = vec4(frameTimeCounter);\n"
+ "}\n";
assertEquivalentIgnoringWhitespace("pfb-stripped-first-sampler", src, GL20.GL_FRAGMENT_SHADER);
}
@Test
void perFrameBlock_shadowedMemberKeepsLocalDeclaration() {
GLSMHooks.perFrameUniformBlock = PER_FRAME;
final String src = "#version 460 core\n"
+ "uniform vec4 frameTimeCounter;\n"
+ "uniform vec3 cameraPosition;\n"
+ "layout(location = 0) out vec4 fragColor;\n"
+ "void main() {\n"
+ " fragColor = frameTimeCounter + vec4(cameraPosition, 0.0);\n"
+ "}\n";
assertEquivalent("pfb-shadowed", src, GL20.GL_FRAGMENT_SHADER);
}
@Test
void perFrameBlock_noReferencesLeavesSourceUntouched() {
GLSMHooks.perFrameUniformBlock = PER_FRAME;
GLSMHooks.perPassUniformBlock = PER_PASS;
final String src = "#version 460 core\n"
+ "layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = vec4(1.0); }\n";
assertEquivalent("pfb-unreferenced", src, GL20.GL_FRAGMENT_SHADER);
}
}
@@ -0,0 +1,123 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock;
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock.Member;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
import com.gtnewhorizons.angelica.glsm.shader.UniformType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.grammar.GLSLParser;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
class PrewarmTreeHandoffEquivalenceTest {
private static final String HEADER = "#version 460 core\n";
@AfterEach
void clearBlocks() {
GLSMHooks.perFrameUniformBlock = null;
GLSMHooks.perPassUniformBlock = null;
}
private static void assertHandoffEquivalent(String label, String body, int glShaderType) {
final GLSLParser.Translation_unitContext tree = ShaderParser.parseShader(body).full();
GlslTransformUtils.restoreReservedWordsInTree(tree);
final String printedBody = GlslTransformUtils.getFormattedShaderRebased(tree, "");
final String finalSource = HEADER + printedBody;
GlslVulkanPreprocess.clearCache();
final ShaderManager.PrewarmTransformResult reparse = ShaderManager.applyPrewarmTransformsFull(finalSource, glShaderType);
GlslVulkanPreprocess.clearCache();
final ShaderManager.PrewarmTransformResult treeFed = ShaderManager.applyPrewarmTransformsFull(finalSource, tree, HEADER.length(), glShaderType);
assertEquals(reparse.source(), treeFed.source(), "tree handoff diverged from re-parse for " + label);
assertEquals(reparse.boolUniforms(), treeFed.boolUniforms(), "bool uniforms diverged for " + label);
}
@Test
void minimalFragment() {
assertHandoffEquivalent("minimal-fragment",
"layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = vec4(1.0); }\n",
GL20.GL_FRAGMENT_SHADER);
}
@Test
void unusedSamplerStripped() {
assertHandoffEquivalent("unused-sampler",
"uniform sampler2D u_Unused;\n"
+ "uniform sampler2D u_Used;\n"
+ "layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = texture(u_Used, vec2(0.5)); }\n",
GL20.GL_FRAGMENT_SHADER);
}
@Test
void perFrameBlockInjection() {
GLSMHooks.perFrameUniformBlock = new PerFrameUniformBlock(List.of(
new Member("cameraPosition", UniformType.VEC3),
new Member("frameTimeCounter", UniformType.FLOAT)));
assertHandoffEquivalent("per-frame-block",
"uniform float frameTimeCounter;\n"
+ "layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = vec4(frameTimeCounter); }\n",
GL20.GL_FRAGMENT_SHADER);
}
@Test
void vertexClipZAndUnlocatedInputs() {
GLSMHooks.perFrameUniformBlock = new PerFrameUniformBlock(List.of(
new Member("cameraPosition", UniformType.VEC3)));
assertHandoffEquivalent("vertex-clipz-unlocated",
"layout(location = 0) in vec3 a_Position;\n"
+ "in vec2 a_TexCoord;\n"
+ "uniform vec3 cameraPosition;\n"
+ "out vec2 v_TexCoord;\n"
+ "void main() {\n"
+ " v_TexCoord = a_TexCoord;\n"
+ " gl_Position = vec4(a_Position - cameraPosition, 1.0);\n"
+ "}\n",
GL20.GL_VERTEX_SHADER);
}
@Test
void boolUniformMetadata() {
assertHandoffEquivalent("bool-uniform",
"uniform bool u_Enabled;\n"
+ "layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = vec4(u_Enabled ? 1.0 : 0.0); }\n",
GL20.GL_FRAGMENT_SHADER);
}
@Test
void texelFetchPath() {
assertHandoffEquivalent("texelfetch",
"uniform sampler2D u_Data;\n"
+ "layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = texelFetch(u_Data, ivec2(0), 0); }\n",
GL20.GL_FRAGMENT_SHADER);
}
@Test
void geometryStage() {
assertHandoffEquivalent("geometry",
"layout(triangles) in;\n"
+ "layout(triangle_strip, max_vertices = 3) out;\n"
+ "void main() {\n"
+ " for (int i = 0; i < 3; i++) {\n"
+ " gl_Position = gl_in[i].gl_Position;\n"
+ " EmitVertex();\n"
+ " }\n"
+ " EndPrimitive();\n"
+ "}\n",
GL32.GL_GEOMETRY_SHADER);
}
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.sdlgpu.splash;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SplashDispatcherTest {
private static final long INTERVAL = 16_000_000L;
@Test
void theIntervalGateSwallowsFramesArrivingTooSoon() {
assertFalse(SplashDispatcher.intervalElapsed(INTERVAL - 1, 0L, false));
assertTrue(SplashDispatcher.intervalElapsed(INTERVAL, 0L, false));
}
@Test
void theFinalFrameIsNotSwallowedByTheIntervalGate() {
assertTrue(SplashDispatcher.intervalElapsed(1L, 0L, true), "teardown must present the last splash frame whenever it arrives");
}
}
@@ -0,0 +1,46 @@
package com.gtnewhorizons.angelica.sdlgpu.util;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertFalse;
class ThreadRegistryTest {
private static ThreadRegistry<Object> registry() {
return new ThreadRegistry<>(new Object[0]);
}
@Test
void soleTracksTheSingleEntry() {
final ThreadRegistry<Object> r = registry();
final Object a = new Object();
final Object b = new Object();
assertNull(r.sole(), "empty registry has no sole entry");
r.add(a);
assertSame(a, r.sole());
r.add(b);
assertNull(r.sole(), "two entries means no sole entry");
r.remove(b);
assertSame(a, r.sole());
r.remove(a);
assertNull(r.sole());
assertEquals(0, r.size());
}
@Test
void removeOfAnAbsentEntryLeavesTheRegistryAlone() {
final ThreadRegistry<Object> r = registry();
final Object a = new Object();
r.add(a);
final Object[] before = r.snapshot();
assertFalse(r.remove(new Object()));
assertSame(before, r.snapshot(), "a no-op remove must not republish");
assertSame(a, r.sole());
}
}
Loaded 100 of 275 files, more files were not shown because too many files have changed in this diff. Show more