Compare commits

...
25 Commits
Author SHA1 Message Date
Eclipse-Sol 159f7ff8cc Fix NaNs on EP (#2231)
* Fix NaNs on EP

* Also add hidden writes handling

* optimize this mess

* Another optimization pass
2026-10-07 17:59:21 -04:00
Ranzu 0d959c2b90 Add Darkmode Transformer target (#2221) 2026-10-07 13:55:18 -07:00
Alexdoru ce2462cad3 Fix server thread leak in ThreadedBlockData (#2230) 2026-10-07 12:30:09 -07:00
Death Fuel 5457df5369 Marginal improvements (#2222) 2026-10-07 12:23:32 -07:00
Eclipse-Sol acedba4eac Modern Font Sheets (#2225)
* Initial commit for modern-font-sheets

* Fix dev env error log

* Fix private use characters still using their own unifont character
2026-10-07 11:19:19 -04:00
Alexdoru 21a054a4db fix world client leak in CloudRenderer (#2227) 2026-10-06 06:22:08 -07:00
Jason Mitchell d056bf6fa7 Run image atomics natively on Metal where the runtime allows (#2226) 2026-10-05 22:57:30 -07:00
Eclipse-Sol 5ed1c92590 SDL Vertex Replay + Dragon API (#2224) 2026-10-05 20:23:36 -07:00
Eclipse-Sol 3389b952d0 Fix SDL compile failure when an image atomic's result is used (#2218) 2026-10-04 18:56:12 -07:00
Jason Mitchell b49d37d827 Ignore additional tags when deriving the mod version (#2219) 2026-10-04 18:33:06 -07:00
Jason Mitchell 12857006c6 Harden SDL-GPU buffer mapping lifetime (#2217) 2026-10-04 16:31:48 -07:00
Eclipse-Sol 38af64f10c Initial commit for shader-loading-optimizations (#2213) 2026-10-04 15:36:50 -07:00
Jason Mitchell 10922edca3 Include missed scripts (#2216) 2026-10-04 15:22:09 -07:00
Jason Mitchell 2a8c8756d3 Add renderer selection to video settings - (#2215) 2026-10-04 15:16:51 -07:00
Jason Mitchell 23e36f2b76 Narrow overly broad transformer exclusions for PowerConverters (#2214) 2026-10-04 14:27:43 -07:00
Jason Mitchell fcf8ad064a Fix terrain disappearing at screen edges with compute culling (#2212) 2026-10-04 13:05:14 -07:00
Jason Mitchell 6dfb0312b1 Answer legacy stack and client state queries from GLSM (#2211) 2026-10-04 11:43:56 -07:00
Eclipse-Sol 0a4d48964e Cache Iris Shaders (#2206) 2026-10-03 20:06:39 -07:00
Ranzu 231a2ab216 Add Darkmode Transformer target (#2205) 2026-10-03 15:28:37 -07:00
Jason Mitchell 61bb779dea Fix crash when mods measure text off-thread with a custom font (#2204) 2026-10-03 15:28:15 -07:00
Jason Mitchell 7cd97d1216 Fix HUD caching flicker and match non-cached GL state (#2201) 2026-10-03 13:19:29 -07:00
Eclipse-Sol fe82cd320a Plenty of fixes for GTNH (#2202) 2026-10-03 13:19:15 -07:00
Li Xiangrong c7a0f3a208 Default raw custom textures to bilinear filtering and clamping (#2198) 2026-10-03 11:57:30 -07:00
Matt Van Horn 338d94197c Settle frame pacing after an inconsistent vsync gate (#2197) 2026-10-03 11:51:30 -07:00
Death Fuel 75a0278138 Fix splash issues (#2200) 2026-10-03 11:42:57 -07:00
267 changed files with 12530 additions and 3653 deletions

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# usage: companion-version.sh <name> <catalog-key>; TAG is <name>-v<version>[-N] or unset
set -euo pipefail
name="${1:?name required}"
key="${2:?catalog key required}"
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
base="$(sed -nE "s/^$key = \"(.*)\"\$/\\1/p" gradle/libs.versions.toml)"
[[ -n "$base" ]] || { echo "$key not found" >&2; exit 1; }
tag="${TAG:-}"
if [[ -z "$tag" ]]; then
echo "$base"
exit 0
fi
version="${tag#"$name"-v}"
if [[ "$version" == "$tag" || ( "$version" != "$base" && ! "$version" =~ ^"$base"-[1-9][0-9]*$ ) ]]; then
echo "bad tag '$tag': want $name-v$base[-N]" >&2
exit 1
fi
echo "$version"
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# usage: release-companion-jar.sh <jar> <notes>; TAG is <name>-v<version>
set -euo pipefail
: "${TAG:?TAG required}"
jar="${1:?jar required}"
notes="${2:?notes required}"
gh release create "$TAG" "$jar" --latest=false --title "angelica-${TAG%%-v*} ${TAG#*-v}" --notes "$notes"
+53
View File
@@ -0,0 +1,53 @@
name: Build angelica-moltenvk
on:
push:
tags:
- "moltenvk-v*"
workflow_dispatch:
inputs:
publish:
description: Publish angelica-moltenvk.jar to the GTNH maven
type: boolean
required: false
default: false
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- name: Compute version
shell: bash
env:
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
run: |
version="$(bash .github/scripts/companion-version.sh moltenvk lwjgl3Release)"
echo "MOLTENVK_VERSION=$version" >> "$GITHUB_ENV"
- name: Build angelica-moltenvk.jar
run: ./gradlew -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:distJar
- uses: actions/upload-artifact@v4
with:
name: angelica-moltenvk
path: moltenvk/build/libs/angelica-moltenvk-*.jar
if-no-files-found: error
- name: Publish to GTNH maven
if: github.ref_type == 'tag' || inputs.publish
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
run: ./gradlew -PpublishMoltenVK=true -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:publish
- name: Create GitHub release
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: bash .github/scripts/release-companion-jar.sh moltenvk/build/libs/angelica-moltenvk-*.jar "MoltenVK for the SDL GPU Vulkan driver on macOS."
+18 -2
View File
@@ -12,6 +12,9 @@ on:
required: false
default: false
permissions:
contents: write
jobs:
build:
strategy:
@@ -66,8 +69,15 @@ jobs:
cp "$d"/* "$dest/"
done
find tracy-client/src/main/resources/natives -type f
- name: Compute version
shell: bash
env:
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
run: |
version="$(bash .github/scripts/companion-version.sh tracy tracy)"
echo "TRACY_JAR_VERSION=$version" >> "$GITHUB_ENV"
- name: Build angelica-tracy.jar
run: ./gradlew :tracy-client:distJar
run: ./gradlew -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:distJar
- uses: actions/upload-artifact@v4
with:
name: angelica-tracy
@@ -78,4 +88,10 @@ jobs:
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
run: ./gradlew -PpublishTracy=true :tracy-client:publish
run: ./gradlew -PpublishTracy=true -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:publish
- name: Create GitHub release
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: bash .github/scripts/release-companion-jar.sh tracy-client/build/libs/angelica-tracy-*.jar "Tracy profiler backend for Angelica."
+1 -1
View File
@@ -3,7 +3,7 @@ name: Release tagged build
on:
push:
tags: [ '*', '!tracy-v*' ]
tags: [ '*', '!tracy-v*', '!moltenvk-v*' ]
permissions:
contents: write
+13
View File
@@ -6,6 +6,8 @@ plugins {
id("com.gtnewhorizons.gtnhconvention")
}
apply(from = "gradle/mod-version.gradle.kts")
val lwjglDebug = false
val gpuHud = false
val renderdoc = false
@@ -146,6 +148,17 @@ fun Test.configureAngelicaJava8() {
}
}
if (isMacOs && providers.gradleProperty("clientJvmVendor").isPresent) {
for (javaVersion in listOf(17, 21, 25)) {
tasks.named<JavaExec>("runClient$javaVersion") {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(javaVersion)
vendor = JvmVendorSpec.matching(providers.gradleProperty("clientJvmVendor").get())
}
}
}
}
tasks.test {
useJUnitPlatform { excludeTags = setOf("gl-core") }
configureAngelicaJava8()
+3
View File
@@ -93,6 +93,9 @@ DependencyHandlerScope.of(dependencies).apply {
shadowImplementation(project(":lwjgl3-backend")) { isTransitive = false }
shadowImplementation(project(":sdl-gpu")) { isTransitive = false }
runtimeOnlyNonPublishable(project(":tracy-client")) { isTransitive = false }
if (System.getProperty("os.name").lowercase().contains("mac")) {
runtimeOnlyNonPublishable(project(":moltenvk")) { isTransitive = false }
}
devOnlyNonPublishable(libs.async.profiler)
shadowImplementation(libs.eventbus)
@@ -17,7 +17,7 @@ public final class SystemProperties {
// Backend selection
public static final String KEY_USE_SDL_GPU = "angelica.sdlgpu.enable";
public static final boolean USE_SDL_GPU = Boolean.getBoolean(KEY_USE_SDL_GPU);
public static final Boolean SDL_GPU_OVERRIDE = parseBooleanOverride(KEY_USE_SDL_GPU);
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
@@ -28,7 +28,8 @@ public final class SystemProperties {
// SDL-GPU
public static final String KEY_SDL_GPU_DRIVER = "angelica.sdlgpu.driver";
public static final String SDL_GPU_DRIVER = System.getProperty(KEY_SDL_GPU_DRIVER, "");
public static final String SDL_GPU_DRIVER_OVERRIDE = parseStringOverride(KEY_SDL_GPU_DRIVER);
public static final String MOLTENVK_DIR = System.getProperty("angelica.moltenvk.dir", "angelica" + File.separator + "natives" + File.separator + "moltenvk");
public static final boolean SDL_GPU_DEBUG = Boolean.getBoolean("angelica.sdlgpu.debug");
private static final SdlAssertionMode ENCODER_ASSERTIONS = parseEnum("angelica.sdlgpu.encoderAssertions", SdlAssertionMode.OFF, SdlAssertionMode.WARN, SdlAssertionMode.class);
public static final boolean SDL_ENCODER_ASSERTIONS = ENCODER_ASSERTIONS != SdlAssertionMode.OFF;
@@ -61,8 +62,10 @@ public final class SystemProperties {
public static final long FLYBY_TIME_OF_DAY = Math.floorMod(Long.getLong("angelica.flyby.timeOfDay", 6000L), 24000L);
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
public static final Integer FLYBY_DIMENSION = Integer.getInteger("angelica.flyby.dimension");
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
public static final FlybyCamera FLYBY_CAMERA = parseEnum("angelica.flyby.camera", FlybyCamera.FIRST_PERSON, FlybyCamera.FIRST_PERSON, FlybyCamera.class);
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
public static final float FLYBY_PITCH = parseFloat("angelica.flyby.pitch");
public static final int FLYBY_SCREENSHOTS = Integer.getInteger("angelica.flyby.screenshots", 0);
@@ -131,6 +134,12 @@ public final class SystemProperties {
CONFIGURED
}
public enum FlybyCamera {
FIRST_PERSON,
THIRD_PERSON_BACK,
THIRD_PERSON_FRONT
}
public enum FlybyWeather {
CLEAR,
RAIN,
@@ -158,7 +167,12 @@ public final class SystemProperties {
private static Boolean parseBooleanOverride(String key) {
final String raw = System.getProperty(key);
return raw == null ? null : Boolean.parseBoolean(raw);
return raw == null || raw.isEmpty() ? null : Boolean.parseBoolean(raw);
}
private static String parseStringOverride(String key) {
final String raw = System.getProperty(key);
return raw == null || raw.isEmpty() ? null : raw;
}
private static double parseDouble(String key) {
@@ -4,6 +4,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
import com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
@@ -456,12 +457,7 @@ public class CompatShaderTransformer {
continue;
}
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
declarators.add(decl.typeless_declaration());
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) {
declarators.addAll(list.typeless_declaration());
}
for (GLSLParser.Typeless_declarationContext d : declarators) {
for (GLSLParser.Typeless_declarationContext d : ShaderIndex.declarators((GLSLParser.Init_declarator_listContext) decl.getParent())) {
if (d.IDENTIFIER() == null) continue;
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(d.array_specifier());
final String arraySuffix = typeArray + arraySuffix(d.array_specifier());
@@ -21,6 +21,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.RetainedState;
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
@@ -29,7 +30,7 @@ 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;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
@@ -106,6 +107,7 @@ public class GLContextState {
public int maxBoundTextureUnit = 0;
public int maxBoundImageUnit = 0;
public final StateSet[] attribSets = new StateSet[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public RetainedState retainedOwner;
public int attribDepth = 0;
public boolean savesState(int id) {
@@ -229,9 +231,16 @@ public class GLContextState {
public int boundCopyReadBuffer;
public int boundCopyWriteBuffer;
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
public PixelUnpackState pixelUnpackState = PixelUnpackState.DEFAULT;
public PixelStoreState pixelUnpackState = PixelStoreState.DEFAULT;
public PixelStoreState pixelPackState = PixelStoreState.DEFAULT;
public final int[] clientAttribSavedMask = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final PixelStoreState[] clientAttribSavedPixelUnpack = new PixelStoreState[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final PixelStoreState[] clientAttribSavedPixelPack = new PixelStoreState[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedUnpackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedPackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final boolean[] clientAttribSavedVertexArray = new boolean[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public int clientAttribStackPointer = 0;
public int restoreChangedMask;
public long restoreUnitChangedMask;
@@ -31,6 +31,7 @@ import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawCapture;
import com.gtnewhorizons.angelica.glsm.recording.commands.TexImage2DCmd;
import com.gtnewhorizons.angelica.glsm.recording.commands.TexSubImage2DCmd;
import com.gtnewhorizons.angelica.glsm.shader.ProgramBinaryCache;
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
@@ -49,6 +50,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.RetainedState;
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
@@ -59,7 +61,7 @@ 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.ColorMask;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitBinding;
@@ -307,17 +309,25 @@ public class GLStateManager {
return Thread.currentThread() == MainThread;
}
public static void requireContext(String call) {
if (Thread.currentThread() == MainThread || RENDER_BACKEND.hasContextOnThread()) return;
throw new IllegalStateException(call + " on thread " + Thread.currentThread().getName() + ", which has no GL context");
}
@Getter private static GLSMInitConfig initConfig;
public static ContextCapabilities capabilities;
// Software stack depths for FFP emulation
public static final int MAX_ATTRIB_STACK_DEPTH = 32;
public static final int RETAINED_SLOT = MAX_ATTRIB_STACK_DEPTH;
public static final int STATE_SLOTS = MAX_ATTRIB_STACK_DEPTH + 1;
public static final int MAX_MODELVIEW_STACK_DEPTH = 32 + 2;
public static final int MAX_PROJECTION_STACK_DEPTH = 4;
public static final int MAX_TEXTURE_STACK_DEPTH = 4;
public static final int MAX_CLIP_PLANES = 8;
public static final int MAX_TEXTURE_UNITS = RENDER_BACKEND.getInteger(GL20.GL_MAX_TEXTURE_IMAGE_UNITS);
public static final int MAX_LEGACY_TEXTURE_UNITS = Math.min(VertexKey.MAX_UNITS, MAX_TEXTURE_UNITS);
public static final GLFeatureSet HAS_MULTIPLE_SET = new GLFeatureSet();
@@ -542,6 +552,7 @@ public class GLStateManager {
final GLContextState glCtx = ctx();
DisplayListManager.abortCompilation();
glCtx.attribDepth = 0;
glCtx.retainedOwner = null;
for (int i = 0; i < glCtx.attribSets.length; i++) {
glCtx.attribSets[i] = null;
}
@@ -1182,11 +1193,35 @@ public class GLStateManager {
case GL11.GL_LINE_STIPPLE_PATTERN -> glCtx.lineState.getStipplePattern() & 0xFFFF;
case GL11.GL_LINE_STIPPLE_REPEAT -> glCtx.lineState.getStippleFactor();
case GL11.GL_LIST_INDEX -> DisplayListManager.isRecording() ? Math.max(DisplayListManager.getRecordingListId(), 0) : 0;
case GL11.GL_ATTRIB_STACK_DEPTH -> glCtx.attribDepth;
case GL11.GL_CLIENT_ATTRIB_STACK_DEPTH -> glCtx.clientAttribStackPointer;
case GL11.GL_TEXTURE_STACK_DEPTH -> getMatrixStackDepth(glCtx.textures.getTextureUnitMatrix(glCtx.activeTextureUnit.getValue()));
case GL11.GL_MAX_ATTRIB_STACK_DEPTH -> MAX_ATTRIB_STACK_DEPTH;
case GL11.GL_MAX_CLIENT_ATTRIB_STACK_DEPTH -> CLIENT_ATTRIB_STACK_DEPTH;
case GL11.GL_MAX_MODELVIEW_STACK_DEPTH -> MAX_MODELVIEW_STACK_DEPTH;
case GL11.GL_MAX_PROJECTION_STACK_DEPTH -> MAX_PROJECTION_STACK_DEPTH;
case GL11.GL_MAX_TEXTURE_STACK_DEPTH -> MAX_TEXTURE_STACK_DEPTH;
case GL13.GL_MAX_TEXTURE_UNITS -> MAX_LEGACY_TEXTURE_UNITS;
case GL11.GL_MAX_LIGHTS -> glCtx.lightStates.length;
case GL11.GL_STENCIL_BITS -> drawFramebufferBits(glCtx, true);
case GL11.GL_DEPTH_BITS -> drawFramebufferBits(glCtx, false);
default -> RENDER_BACKEND.getInteger(pname);
};
};
}
private static int drawFramebufferBits(GLContextState glCtx, boolean stencil) {
final int sizePname = stencil ? GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE : GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE;
if (glCtx.drawFramebuffer == 0) {
return RENDER_BACKEND.getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, stencil ? GL11.GL_STENCIL : GL11.GL_DEPTH, sizePname);
}
final int attachment = stencil ? GL30.GL_STENCIL_ATTACHMENT : GL30.GL_DEPTH_ATTACHMENT;
if (RENDER_BACKEND.getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, attachment, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) == GL11.GL_NONE) {
return 0;
}
return RENDER_BACKEND.getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, attachment, sizePname);
}
public static void glGetInteger(int pname, IntBuffer params) {
if (!isCachingEnabled()) {
RENDER_BACKEND.getInteger(pname, params);
@@ -1235,6 +1270,48 @@ public class GLStateManager {
}
}
private static void putLightingInts(FloatBuffer values, int pname, boolean material, IntBuffer params) {
final int count = switch (pname) {
case GL11.GL_AMBIENT, GL11.GL_DIFFUSE, GL11.GL_SPECULAR -> 4;
case GL11.GL_EMISSION -> material ? 4 : 0;
case GL11.GL_COLOR_INDEXES -> material ? 3 : 0;
case GL11.GL_SHININESS -> material ? 1 : 0;
case GL11.GL_POSITION -> material ? 0 : 4;
case GL11.GL_SPOT_DIRECTION -> material ? 0 : 3;
case GL11.GL_SPOT_EXPONENT, GL11.GL_SPOT_CUTOFF, GL11.GL_CONSTANT_ATTENUATION, GL11.GL_LINEAR_ATTENUATION,
GL11.GL_QUADRATIC_ATTENUATION -> material ? 0 : 1;
default -> 0;
};
final boolean color = count == 4 && pname != GL11.GL_POSITION;
final int pos = params.position();
for (int i = 0; i < count; i++) {
final float value = values.get(i);
final int converted;
if (color) {
converted = (int) (2147483647.0 * value);
} else if (material) {
converted = value < 0 ? -Math.round(-value) : Math.round(value);
} else {
converted = (int) value;
}
params.put(pos + i, converted);
}
}
public static void glGetMaterial(int face, int pname, IntBuffer params) {
final FloatBuffer scratch = ctx().queryScratch;
scratch.clear();
glGetMaterial(face, pname, scratch);
putLightingInts(scratch, pname, true, params);
}
public static void glGetLight(int light, int pname, IntBuffer params) {
final FloatBuffer scratch = ctx().queryScratch;
scratch.clear();
glGetLight(light, pname, scratch);
putLightingInts(scratch, pname, false, params);
}
public static void glGetLight(int light, int pname, FloatBuffer params) {
final LightStateStack state = ctx().lightDataStates[light - GL11.GL_LIGHT0];
switch (pname) {
@@ -1287,12 +1364,25 @@ public class GLStateManager {
params.put(pos + 2, normal.z);
}
case GL11.GL_CURRENT_TEXTURE_COORDS -> {
final Vector4f tc = glCtx.ffp.currentTexCoords[0];
final int unit = glCtx.activeTextureUnit.getValue();
final int pos = params.position();
params.put(pos, tc.x);
params.put(pos + 1, tc.y);
params.put(pos + 2, tc.z);
params.put(pos + 3, tc.w);
if (unit == 1) {
params.put(pos, glCtx.lastBrightnessX);
params.put(pos + 1, glCtx.lastBrightnessY);
params.put(pos + 2, 0.0f);
params.put(pos + 3, 1.0f);
} else if (unit >= 0 && unit < glCtx.ffp.currentTexCoords.length) {
final Vector4f tc = glCtx.ffp.currentTexCoords[unit];
params.put(pos, tc.x);
params.put(pos + 1, tc.y);
params.put(pos + 2, tc.z);
params.put(pos + 3, tc.w);
} else {
params.put(pos, 0.0f);
params.put(pos + 1, 0.0f);
params.put(pos + 2, 0.0f);
params.put(pos + 3, 1.0f);
}
}
case GL11.GL_FOG_COLOR -> {
final FloatBuffer fogBuf = glCtx.fogState.getFogColorBuffer();
@@ -1604,58 +1694,25 @@ public class GLStateManager {
}
public static void glBlendFunc(int srcFactor, int dstFactor) {
final GLContextState glCtx = ctx();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordBlendFunc(srcFactor, dstFactor, srcFactor, dstFactor);
if (mode == RecordMode.COMPILE) {
return;
}
}
final boolean snapshotted = snapshotVanillaBlendFunc();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isBlendLocked()) {
mod(glCtx.blendState);
bh.deferBlendFunc(srcFactor, dstFactor, srcFactor, dstFactor);
postVanillaBlendChangeIfMoved(snapshotted);
return;
}
final boolean caching = isCachingEnabled();
tryBlendFuncSeparate(srcFactor, dstFactor, srcFactor, dstFactor);
}
private static void issueBlendFunc(BlendStateStack blend) {
if (GLSMConfig.hudCacheOverride) {
mod(glCtx.blendState);
glCtx.blendState.setAll(srcFactor, dstFactor, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendFuncSeparate(srcFactor, dstFactor, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
GLSMHooks.blendFuncChangeEvent.srcRgb = srcFactor;
GLSMHooks.blendFuncChangeEvent.dstRgb = dstFactor;
GLSMHooks.blendFuncChangeEvent.srcAlpha = GL11.GL_ONE;
GLSMHooks.blendFuncChangeEvent.dstAlpha = GL11.GL_ONE_MINUS_SRC_ALPHA;
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
}
postVanillaBlendChangeIfMoved(snapshotted);
return;
RENDER_BACKEND.blendFuncSeparate(blend.getSrcRgb(), blend.getDstRgb(), GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
} else {
RENDER_BACKEND.blendFuncSeparate(blend.getSrcRgb(), blend.getDstRgb(), blend.getSrcAlpha(), blend.getDstAlpha());
}
final boolean bypass = !caching;
if (bypass
|| glCtx.blendState.getSrcRgb() != srcFactor
|| glCtx.blendState.getDstRgb() != dstFactor
|| glCtx.blendState.getSrcAlpha() != srcFactor
|| glCtx.blendState.getDstAlpha() != dstFactor
|| glCtx.blendState.isFuncUnknown()) {
mod(glCtx.blendState);
glCtx.blendState.setAll(srcFactor, dstFactor, srcFactor, dstFactor);
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendFunc(srcFactor, dstFactor);
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
GLSMHooks.blendFuncChangeEvent.srcRgb = srcFactor;
GLSMHooks.blendFuncChangeEvent.dstRgb = dstFactor;
GLSMHooks.blendFuncChangeEvent.srcAlpha = srcFactor;
GLSMHooks.blendFuncChangeEvent.dstAlpha = dstFactor;
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
}
}
postVanillaBlendChangeIfMoved(snapshotted);
}
public static void setHudCacheOverride(boolean enabled) {
if (GLSMConfig.hudCacheOverride == enabled) return;
GLSMConfig.hudCacheOverride = enabled;
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isOverrideHeld()) return;
final BlendStateStack blend = ctx().blendState;
issueBlendFunc(blend);
blend.clearFuncUnknownState();
}
public static void glBlendEquation(int mode) {
@@ -1707,22 +1764,19 @@ public class GLStateManager {
postVanillaBlendChangeIfMoved(snapshotted);
return;
}
if (GLSMConfig.hudCacheOverride && dstAlpha != GL11.GL_ONE_MINUS_SRC_ALPHA) {
srcAlpha = GL11.GL_ONE;
dstAlpha = GL11.GL_ONE_MINUS_SRC_ALPHA;
}
final boolean caching = isCachingEnabled();
final boolean bypass = !caching;
if (bypass || glCtx.blendState.getSrcRgb() != srcRgb || glCtx.blendState.getDstRgb() != dstRgb || glCtx.blendState.getSrcAlpha() != srcAlpha || glCtx.blendState.getDstAlpha() != dstAlpha || glCtx.blendState.isFuncUnknown()) {
mod(glCtx.blendState);
glCtx.blendState.setAll(srcRgb, dstRgb, srcAlpha, dstAlpha);
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha);
issueBlendFunc(glCtx.blendState);
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
final boolean hudCache = GLSMConfig.hudCacheOverride;
GLSMHooks.blendFuncChangeEvent.srcRgb = srcRgb;
GLSMHooks.blendFuncChangeEvent.dstRgb = dstRgb;
GLSMHooks.blendFuncChangeEvent.srcAlpha = srcAlpha;
GLSMHooks.blendFuncChangeEvent.dstAlpha = dstAlpha;
GLSMHooks.blendFuncChangeEvent.srcAlpha = hudCache ? GL11.GL_ONE : srcAlpha;
GLSMHooks.blendFuncChangeEvent.dstAlpha = hudCache ? GL11.GL_ONE_MINUS_SRC_ALPHA : dstAlpha;
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
}
}
@@ -2557,11 +2611,13 @@ public class GLStateManager {
}
public static int glGenTextures() {
requireContext("glGenTextures");
flushDeferredTextureDeletes();
return RENDER_BACKEND.genTextures();
}
public static void glGenTextures(IntBuffer textures) {
requireContext("glGenTextures");
flushDeferredTextureDeletes();
RENDER_BACKEND.genTextures(textures);
}
@@ -3282,22 +3338,63 @@ public class GLStateManager {
};
}
private static boolean isPositionArrayEnabled(GLContextState glCtx) {
final VAOManager.Attrib position = glCtx.vaos.attrib(Usage.POSITION.getAttributeLocation());
return position != null && position.enabled;
}
static final int CLIENT_ATTRIB_STACK_DEPTH = 16;
public static void glPushClientAttrib(int mask) {
final GLContextState glCtx = ctx();
if (glCtx.clientAttribStackPointer < CLIENT_ATTRIB_STACK_DEPTH) {
glCtx.clientAttribSavedTextureUnit[glCtx.clientAttribStackPointer] = glCtx.clientActiveTextureUnit;
glCtx.clientAttribSavedVertexFlags[glCtx.clientAttribStackPointer] = glCtx.vaos.getVertexFlags();
glCtx.clientAttribStackPointer++;
final int sp = glCtx.clientAttribStackPointer;
if (sp < CLIENT_ATTRIB_STACK_DEPTH) {
glCtx.clientAttribSavedMask[sp] = mask;
if ((mask & GL11.GL_CLIENT_PIXEL_STORE_BIT) != 0) {
glCtx.clientAttribSavedPixelPack[sp] = glCtx.pixelPackState;
glCtx.clientAttribSavedPixelUnpack[sp] = glCtx.pixelUnpackState;
glCtx.clientAttribSavedPackBuffer[sp] = glCtx.boundPixelPackBuffer;
glCtx.clientAttribSavedUnpackBuffer[sp] = glCtx.boundPixelUnpackBuffer;
}
if ((mask & GL11.GL_CLIENT_VERTEX_ARRAY_BIT) != 0) {
glCtx.clientAttribSavedTextureUnit[sp] = glCtx.clientActiveTextureUnit;
glCtx.clientAttribSavedVertexFlags[sp] = glCtx.vaos.getVertexFlags();
glCtx.clientAttribSavedVertexArray[sp] = isPositionArrayEnabled(glCtx);
}
glCtx.clientAttribStackPointer = sp + 1;
}
}
public static void glPopClientAttrib() {
final GLContextState glCtx = ctx();
if (glCtx.clientAttribStackPointer > 0) {
glCtx.clientAttribStackPointer--;
glCtx.clientActiveTextureUnit = glCtx.clientAttribSavedTextureUnit[glCtx.clientAttribStackPointer];
glCtx.vaos.setVertexFlags(glCtx.clientAttribSavedVertexFlags[glCtx.clientAttribStackPointer]);
final int sp = --glCtx.clientAttribStackPointer;
final int mask = glCtx.clientAttribSavedMask[sp];
if ((mask & GL11.GL_CLIENT_PIXEL_STORE_BIT) != 0) {
final PixelStoreState pack = glCtx.clientAttribSavedPixelPack[sp];
final PixelStoreState unpack = glCtx.clientAttribSavedPixelUnpack[sp];
glCtx.clientAttribSavedPixelPack[sp] = null;
glCtx.clientAttribSavedPixelUnpack[sp] = null;
PixelStoreState.applyDiff(glCtx.pixelPackState, pack, true);
glCtx.pixelPackState = pack;
PixelStoreState.applyDiff(glCtx.pixelUnpackState, unpack, false);
glCtx.pixelUnpackState = unpack;
final int packBuffer = glCtx.clientAttribSavedPackBuffer[sp];
if (glCtx.boundPixelPackBuffer != packBuffer) glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, packBuffer);
final int unpackBuffer = glCtx.clientAttribSavedUnpackBuffer[sp];
if (glCtx.boundPixelUnpackBuffer != unpackBuffer) glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, unpackBuffer);
}
if ((mask & GL11.GL_CLIENT_VERTEX_ARRAY_BIT) != 0) {
glCtx.clientActiveTextureUnit = glCtx.clientAttribSavedTextureUnit[sp];
glCtx.vaos.setVertexFlags(glCtx.clientAttribSavedVertexFlags[sp]);
final boolean vertexArray = glCtx.clientAttribSavedVertexArray[sp];
if (isPositionArrayEnabled(glCtx) != vertexArray) {
if (vertexArray) {
glEnableClientState(GL11.GL_VERTEX_ARRAY);
} else {
glDisableClientState(GL11.GL_VERTEX_ARRAY);
}
}
}
}
}
@@ -4177,6 +4274,89 @@ public class GLStateManager {
}
}
public static void retainModifiedState(int depth, RetainedState out) {
final GLContextState glCtx = ctx();
if (depth < 0 || depth != glCtx.attribDepth - 1) {
throw new IllegalStateException("retainModifiedState: depth " + depth + " is not the open push (attrib depth " + glCtx.attribDepth + ")");
}
glCtx.retainedOwner = out;
final int count = glCtx.modifiedCount[depth];
final int[] ids = glCtx.modifiedIds[depth];
final CowStateStack<?>[] stackById = glCtx.stackById;
out.ensureCapacity(count);
for (int i = 0; i < count; i++) {
final int id = ids[i];
stackById[id].captureSlot(RETAINED_SLOT);
out.ids[i] = id;
}
out.count = count;
out.program = glCtx.activeProgram;
out.programGen = programLifetimeGeneration;
}
public static void applyRetainedState(RetainedState in) {
final GLContextState glCtx = ctx();
if (glCtx.retainedOwner != in) return;
final CowStateStack<?>[] stackById = glCtx.stackById;
final byte[] kindById = glCtx.kindById;
final int[] ids = in.ids;
final int count = in.count;
int first = 0;
while (first < count && !CowDispatch.slotChanged(kindById[ids[first]], stackById[ids[first]], RETAINED_SLOT)) first++;
if (first < count) applyRetainedChanges(glCtx, in, first);
if (in.program != glCtx.activeProgram && (in.program == 0 || in.programGen == programLifetimeGeneration)) glUseProgram(in.program);
}
private static void applyRetainedChanges(GLContextState glCtx, RetainedState in, int first) {
beforeUncapturedStateChange();
final boolean blendSnapshotted = snapshotVanillaBlendFunc();
final int alphaFuncId = glCtx.alphaState.stackId();
final int alphaTestId = glCtx.alphaTest.stackId();
boolean alphaChanged = false;
final boolean prevPoppingAttributes = glCtx.poppingAttributes;
glCtx.poppingAttributes = true;
try {
final CowStateStack<?>[] stackById = glCtx.stackById;
final int[] restoreBitById = glCtx.restoreBitById;
final int[] restoreUnitById = glCtx.restoreUnitById;
final byte[] kindById = glCtx.kindById;
int changed = 0;
long unitsChanged = 0L;
for (int i = first; i < in.count; i++) {
final int id = in.ids[i];
final CowStateStack<?> cow = stackById[id];
final byte kind = kindById[id];
if (!CowDispatch.slotChanged(kind, cow, RETAINED_SLOT)) continue;
cow.beforeModify();
final int bit = restoreBitById[id];
if (bit != 0) {
changed |= bit;
final int unit = restoreUnitById[id];
if (unit >= 0) unitsChanged |= 1L << Math.min(unit, 63);
}
CowDispatch.restoreSlot(kind, cow, RETAINED_SLOT);
if (id == alphaFuncId || id == alphaTestId) alphaChanged = true;
}
glCtx.restoreChangedMask = changed;
glCtx.restoreUnitChangedMask = unitsChanged;
issueRestoredBackendState(glCtx, -1);
glCtx.lightingGeneration++;
glCtx.fragmentGeneration++;
glCtx.colorGeneration++;
glCtx.ffp.normalGeneration++;
glCtx.ffp.texCoordGeneration++;
glCtx.dirtyColorAttrib = true;
glCtx.dirtyNormalAttrib = true;
glCtx.dirtyTexCoordAttrib = true;
postVanillaBlendChangeIfMoved(blendSnapshotted);
} finally {
glCtx.poppingAttributes = prevPoppingAttributes;
}
if (alphaChanged && GLSMHooks.ALPHA_STATE_CHANGE.hasListeners()) {
GLSMHooks.ALPHA_STATE_CHANGE.post(GLSMHooks.alphaStateChangeEvent);
}
}
private static boolean isDepthColorOverridden() {
final DeferredDepthColorHandler dch = GLSMHooks.depthColorHandler;
return dch != null && dch.isOverrideHeld();
@@ -4195,7 +4375,7 @@ public class GLStateManager {
RENDER_BACKEND.depthMask(glCtx.depthState.isEnabled());
RENDER_BACKEND.clearDepth(glCtx.depthState.getClearValue());
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
issueBlendFunc(glCtx.blendState);
glCtx.blendState.clearFuncUnknownState();
RENDER_BACKEND.blendEquationSeparate(glCtx.blendState.getEquationRgb(), glCtx.blendState.getEquationAlpha());
RENDER_BACKEND.blendColor(glCtx.blendState.getBlendColorR(), glCtx.blendState.getBlendColorG(), glCtx.blendState.getBlendColorB(), glCtx.blendState.getBlendColorA());
@@ -4256,12 +4436,7 @@ public class GLStateManager {
}
}
/**
* After popping GLSM stacks, apply restored state to the GL driver.
*/
private static void applyRestoredState(StateSet set, boolean blendSnapshotted) {
final GLContextState glCtx = ctx();
final int restore = set.restore;
private static void issueRestoredBackendState(GLContextState glCtx, int restore) {
final int changed = glCtx.restoreChangedMask;
if ((restore & StateSet.R_DEPTH) != 0 && (changed & StateSet.R_DEPTH) != 0) {
attribValueRestores++;
@@ -4280,7 +4455,7 @@ public class GLStateManager {
attribValueRestores++;
final DeferredBlendHandler restoreBlendHandler = GLSMHooks.blendHandler;
if (restoreBlendHandler == null || !restoreBlendHandler.isOverrideHeld()) {
RENDER_BACKEND.blendFuncSeparate(blend.getSrcRgb(), blend.getDstRgb(), blend.getSrcAlpha(), blend.getDstAlpha());
issueBlendFunc(blend);
attribBackendCalls++;
blend.clearFuncUnknownState();
}
@@ -4377,6 +4552,16 @@ public class GLStateManager {
attribBackendCalls++;
}
}
}
/**
* After popping GLSM stacks, apply restored state to the GL driver.
*/
private static void applyRestoredState(StateSet set, boolean blendSnapshotted) {
final GLContextState glCtx = ctx();
final int restore = set.restore;
final int changed = glCtx.restoreChangedMask;
issueRestoredBackendState(glCtx, restore);
final int bump = set.bump;
final int depth = glCtx.attribDepth;
@@ -4466,6 +4651,10 @@ public class GLStateManager {
return;
}
}
if (ctx().attribDepth == 0) {
warnOnce("popattrib-underflow", "glPopAttrib called with an empty attrib stack; ignoring");
return;
}
GLDebug.popGroup();
GLDebug.pushGroup("popState");
try {
@@ -5269,13 +5458,18 @@ public class GLStateManager {
}
public static void glClearTexImage(int texture, int level, int format, int type, ByteBuffer data) { RENDER_BACKEND.clearTexImage(texture, level, format, type); }
public static int glGenSamplers() { return RENDER_BACKEND.genSamplers(); }
public static int glGenSamplers() {
requireContext("glGenSamplers");
return RENDER_BACKEND.genSamplers();
}
public static void glGenSamplers(IntBuffer samplers) {
requireContext("glGenSamplers");
for (int i = samplers.position(); i < samplers.limit(); i++) samplers.put(i, RENDER_BACKEND.genSamplers());
}
public static void glGenSamplers(int[] samplers) {
requireContext("glGenSamplers");
for (int i = 0; i < samplers.length; i++) samplers[i] = RENDER_BACKEND.genSamplers();
}
@@ -6342,7 +6536,11 @@ public class GLStateManager {
public static void glPixelStorei(int pname, int param) {
final GLContextState glCtx = ctx();
if (isCachingEnabled()) {
glCtx.pixelUnpackState = glCtx.pixelUnpackState.with(pname, param);
if (PixelStoreState.isPack(pname)) {
glCtx.pixelPackState = glCtx.pixelPackState.with(pname, param);
} else {
glCtx.pixelUnpackState = glCtx.pixelUnpackState.with(pname, param);
}
}
RENDER_BACKEND.pixelStorei(pname, param);
}
@@ -6643,12 +6841,12 @@ public class GLStateManager {
}
}
public static void forcePixelUnpackState(PixelUnpackState target) {
PixelUnpackState.applyDiff(ctx().pixelUnpackState, target);
public static void forcePixelUnpackState(PixelStoreState target) {
PixelStoreState.applyDiff(ctx().pixelUnpackState, target, false);
}
public static void restorePixelUnpackState(PixelUnpackState applied) {
PixelUnpackState.applyDiff(applied, ctx().pixelUnpackState);
public static void restorePixelUnpackState(PixelStoreState applied) {
PixelStoreState.applyDiff(applied, ctx().pixelUnpackState, false);
}
private static boolean warnedUntrackedPixelBuffer;
@@ -6692,26 +6890,32 @@ public class GLStateManager {
}
public static int glGenBuffers() {
requireContext("glGenBuffers");
return RENDER_BACKEND.genBuffers();
}
public static void glGenBuffers(IntBuffer buffers) {
requireContext("glGenBuffers");
for (int i = buffers.position(); i < buffers.limit(); i++) buffers.put(i, RENDER_BACKEND.genBuffers());
}
public static void glGenBuffers(int[] buffers) {
requireContext("glGenBuffers");
for (int i = 0; i < buffers.length; i++) buffers[i] = RENDER_BACKEND.genBuffers();
}
public static int glCreateBuffers() {
requireContext("glCreateBuffers");
return RENDER_BACKEND.createBuffers();
}
public static void glCreateBuffers(IntBuffer buffers) {
requireContext("glCreateBuffers");
for (int i = buffers.position(); i < buffers.limit(); i++) buffers.put(i, RENDER_BACKEND.createBuffers());
}
public static void glCreateBuffers(int[] buffers) {
requireContext("glCreateBuffers");
for (int i = 0; i < buffers.length; i++) buffers[i] = RENDER_BACKEND.createBuffers();
}
@@ -6783,7 +6987,7 @@ public class GLStateManager {
private static void onBufferStorage(int target, int flags) {
if ((flags & MAP_PERSISTENT_BIT) != 0) {
markPersistentlyMapped(target);
if (!RENDER_BACKEND.isBufferImmutable(bufferForTarget(ctx(), target))) markPersistentlyMapped(target);
return;
}
onBufferRespec(target);
@@ -6866,14 +7070,27 @@ 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) { onBufferMap(target, access); return RENDER_BACKEND.mapBuffer(target, access); }
public static ByteBuffer glMapBuffer(int target, int access) {
final ByteBuffer buf = RENDER_BACKEND.mapBuffer(target, access);
if (buf != null) onBufferMap(target, access);
return buf;
}
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) { 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 glMapBuffer(int target, int access, long length, ByteBuffer old_buffer) {
final ByteBuffer buf = RENDER_BACKEND.mapBuffer(target, access, length, old_buffer);
if (buf != null) onBufferMap(target, access);
return buf;
}
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access) {
final ByteBuffer buf = RENDER_BACKEND.mapBufferRange(target, offset, length, access);
if (buf != null) onBufferMapRange(target, access);
return buf;
}
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access, ByteBuffer old_buffer) { return glMapBufferRange(target, offset, length, access); }
public static long glMapBufferRangeAddress(int target, long offset, long length, int access) {
onBufferMapRange(target, access);
return RENDER_BACKEND.mapBufferRangeAddress(target, offset, length, access);
final long address = RENDER_BACKEND.mapBufferRangeAddress(target, offset, length, access);
if (address != 0L) onBufferMapRange(target, access);
return address;
}
public static void glFlushMappedBufferRange(int target, long offset, long length) { RENDER_BACKEND.flushMappedBufferRange(target, offset, length); }
public static boolean glUnmapBuffer(int target) { onBufferUnmap(target); return RENDER_BACKEND.unmapBuffer(target); }
@@ -7102,6 +7319,7 @@ public class GLStateManager {
}
public static int glGenVertexArrays() {
requireContext("glGenVertexArrays");
return RENDER_BACKEND.genVertexArrays();
}
@@ -7155,6 +7373,7 @@ public class GLStateManager {
}
public static int glGenFramebuffers() {
requireContext("glGenFramebuffers");
return RENDER_BACKEND.genFramebuffers();
}
@@ -7172,15 +7391,24 @@ public class GLStateManager {
public static void glFramebufferTexture(int target, int attachment, int texture, int level) { RENDER_BACKEND.framebufferTexture(target, attachment, texture, level); }
public static int glGenRenderbuffers() { return RENDER_BACKEND.genRenderbuffers(); }
public static int glGenRenderbuffers() {
requireContext("glGenRenderbuffers");
return RENDER_BACKEND.genRenderbuffers();
}
public static void glDeleteRenderbuffers(int renderbuffer) { RENDER_BACKEND.deleteRenderbuffers(renderbuffer); }
public static void glBindRenderbuffer(int target, int renderbuffer) { RENDER_BACKEND.bindRenderbuffer(target, renderbuffer); }
public static void glRenderbufferStorage(int target, int internalformat, int width, int height) { RENDER_BACKEND.renderbufferStorage(target, internalformat, width, height); }
public static void glRenderbufferStorageMultisample(int target, int samples, int internalformat, int width, int height) { RENDER_BACKEND.renderbufferStorageMultisample(target, samples, internalformat, width, height); }
public static void glFramebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer) { RENDER_BACKEND.framebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer); }
public static void glGenQueries(IntBuffer ids) { RENDER_BACKEND.genQueries(ids); }
public static int glGenQueries() { return RENDER_BACKEND.genQueries(); }
public static void glGenQueries(IntBuffer ids) {
requireContext("glGenQueries");
RENDER_BACKEND.genQueries(ids);
}
public static int glGenQueries() {
requireContext("glGenQueries");
return RENDER_BACKEND.genQueries();
}
public static void glDeleteQueries(int id) { RENDER_BACKEND.deleteQueries(id); }
public static void glBeginQuery(int target, int id) { RENDER_BACKEND.beginQuery(target, id); }
public static void glEndQuery(int target) { RENDER_BACKEND.endQuery(target); }
@@ -7251,8 +7479,8 @@ public class GLStateManager {
}
}
public static long nglMapBuffer(int target, int access) {
onBufferMap(target, access);
final ByteBuffer buf = RENDER_BACKEND.mapBuffer(target, access);
if (buf != null) onBufferMap(target, access);
return buf == null ? 0L : MemoryUtilities.memAddress0(buf);
}
public static void nglMultiDrawElementsBaseVertex(int mode, long count, int type, long indices, int primcount, long basevertex) {
@@ -7779,6 +8007,9 @@ public class GLStateManager {
if (ShaderManager.isEnabled()) {
generateVertexShaderIfNeeded(program);
}
if (ProgramBinaryCache.isEnabled()) {
ProgramBinaryCache.markRetrievable(program);
}
RENDER_BACKEND.linkProgram(program);
if (ShaderManager.isEnabled() && RENDER_BACKEND.getProgrami(program, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) {
LOGGER.warn("Program {} failed to link: {}", program, RENDER_BACKEND.getProgramInfoLog(program));
@@ -7786,6 +8017,21 @@ public class GLStateManager {
CompatUniformManager.onLinkProgram(program);
}
public static void glProgramParameteri(int program, int pname, int value) {
RENDER_BACKEND.programParameteri(program, pname, value);
}
public static void glGetProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {
RENDER_BACKEND.getProgramBinary(program, length, binaryFormat, binary);
}
public static void glProgramBinary(int program, int binaryFormat, ByteBuffer binary) {
RENDER_BACKEND.programBinary(program, binaryFormat, binary);
if (RENDER_BACKEND.getProgrami(program, GL20.GL_LINK_STATUS) == GL11.GL_TRUE) {
CompatUniformManager.onLinkProgram(program);
}
}
/**
* If a program has a fragment shader but no vertex shader, generate a passthrough vertex shader from the fragment's in declarations and attach it.
*/
@@ -7860,6 +8106,7 @@ public class GLStateManager {
}
public static int glCreateShader(int type) {
requireContext("glCreateShader");
return RENDER_BACKEND.createShader(type);
}
@@ -7868,6 +8115,7 @@ public class GLStateManager {
}
public static int glCreateProgram() {
requireContext("glCreateProgram");
final int program = RENDER_BACKEND.createProgram();
programsPendingDeletion.remove(program);
programLifetimeGeneration++;
@@ -1,21 +1,25 @@
package com.gtnewhorizons.angelica.glsm;
import org.antlr.v4.runtime.BailErrorStrategy;
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.DefaultErrorStrategy;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.WritableToken;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.grammar.GLSLPreParser;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -72,6 +76,10 @@ public class GlslTransformUtils {
}
public static String renameReservedWords(String source, int targetVersion) {
if (!containsWholeWord(source, "sample") && !containsWholeWord(source, "new")
&& (targetVersion < 400 || !containsWholeWord(source, "sampler"))) {
return source;
}
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
// Rename identifiers that become reserved words at or above this GLSL version
if (targetVersion >= entry.getKey()) {
@@ -83,6 +91,23 @@ public class GlslTransformUtils {
return source;
}
static boolean containsWholeWord(String source, String word) {
int from = 0;
while (true) {
final int match = source.indexOf(word, from);
if (match < 0) return false;
final int end = match + word.length();
if ((match == 0 || !isAsciiWordChar(source.charAt(match - 1))) && (end == source.length() || !isAsciiWordChar(source.charAt(end)))) {
return true;
}
from = match + 1;
}
}
private static boolean isAsciiWordChar(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
}
public static String restoreReservedWords(String source) {
source = source.replace(RENAMED_PREFIX + "texture", "texture");
source = source.replace(RENAMED_PREFIX + "sampler", "sampler");
@@ -110,72 +135,35 @@ public class GlslTransformUtils {
public record QuietParse(GLSLParser.Translation_unitContext full, GLSLPreParser.Translation_unitContext pre) {}
public static GLSLParser.Translation_unitContext parseFullQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
parser.removeErrorListeners();
parser.setBuildParseTree(true);
return parser.translation_unit();
}
public static GLSLPreParser.Translation_unitContext parsePreQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLPreParser preParser = new GLSLPreParser(new CommonTokenStream(lexer, GLSLLexer.DIRECTIVES));
preParser.removeErrorListeners();
preParser.setBuildParseTree(true);
return preParser.translation_unit();
return parseQuiet(new GLSLParser(new CommonTokenStream(quietLexer(source))), GLSLParser::translation_unit);
}
public static QuietParse parseBothQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLPreParser preParser = new GLSLPreParser(new BufferedTokenStream(lexer));
preParser.removeErrorListeners();
preParser.setBuildParseTree(true);
final GLSLPreParser.Translation_unitContext pre = preParser.translation_unit();
final GLSLLexer lexer = quietLexer(source);
final GLSLPreParser.Translation_unitContext pre = parseQuiet(new GLSLPreParser(new BufferedTokenStream(lexer)), GLSLPreParser::translation_unit);
lexer.reset();
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
parser.removeErrorListeners();
parser.setBuildParseTree(true);
return new QuietParse(parser.translation_unit(), pre);
return new QuietParse(parseQuiet(new GLSLParser(new CommonTokenStream(lexer)), GLSLParser::translation_unit), pre);
}
public static Set<String> identifiersInDirectiveText(String source) {
private static <P extends Parser, T> T parseQuiet(P parser, Function<P, T> rule) {
parser.removeErrorListeners();
parser.setErrorHandler(new BailErrorStrategy());
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
try {
return rule.apply(parser);
} catch (ParseCancellationException e) {
// Rewinds the token stream, which keeps every token SLL already pulled from the lexer.
parser.reset();
parser.setErrorHandler(new DefaultErrorStrategy());
parser.getInterpreter().setPredictionMode(PredictionMode.LL);
return rule.apply(parser);
}
}
public static GLSLLexer quietLexer(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final BufferedTokenStream tokens = new BufferedTokenStream(lexer);
tokens.fill();
final Set<String> names = new HashSet<>();
for (Token token : tokens.getTokens()) {
switch (token.getType()) {
case GLSLLexer.PROGRAM_TEXT, GLSLLexer.MACRO_TEXT, GLSLLexer.CONSTANT_EXPRESSION -> collectIdentifiers(token.getText(), names);
default -> { }
}
}
return names;
}
private static void collectIdentifiers(String text, Set<String> out) {
if (text == null) return;
final int len = text.length();
int i = 0;
while (i < len) {
final char c = text.charAt(i);
if (c != '_' && !Character.isLetter(c)) {
i++;
continue;
}
int end = i + 1;
while (end < len) {
final char n = text.charAt(end);
if (n != '_' && !Character.isLetterOrDigit(n)) break;
end++;
}
out.add(text.substring(i, end));
i = end;
}
return lexer;
}
public static String getFormattedShader(ParseTree tree, String header) {
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -25,10 +25,10 @@ public final class LTWWorkaround {
oneTexel = BufferUtils.createByteBuffer(4);
}
oneTexel.clear();
GLStateManager.forcePixelUnpackState(PixelUnpackState.DEFAULT);
GLStateManager.forcePixelUnpackState(PixelStoreState.DEFAULT);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage2D(target, level, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, oneTexel);
GLStateManager.restorePixelUnpackBuffer();
GLStateManager.restorePixelUnpackState(PixelUnpackState.DEFAULT);
GLStateManager.restorePixelUnpackState(PixelStoreState.DEFAULT);
}
}
@@ -48,6 +48,7 @@ public class RenderSystem {
private static boolean supportsClearTexture;
private static boolean supportsTesselation;
private static boolean supportsSamplerObjects;
private static boolean supportsProgramBinary;
private static int maxImageUnits;
@Getter private static int maxCombinedTextureImageUnits;
private static int maxSSBOBindings;
@@ -159,6 +160,9 @@ public class RenderSystem {
maxCombinedTextureImageUnits = RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
}
supportsProgramBinary = RENDER_BACKEND.supportsProgramBinary();
GLStateManager.LOGGER.info("Program Binary: {}", supportsProgramBinary);
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
@@ -611,6 +615,10 @@ public class RenderSystem {
return supportsSamplerObjects;
}
public static boolean supportsProgramBinary() {
return supportsProgramBinary;
}
public static int genSampler() {
if (!supportsSamplerObjects) return 0;
return GLStateManager.glGenSamplers();
@@ -107,9 +107,11 @@ public final class StateSet {
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
GLStateManager.getActiveTextureUnitStack(), GLStateManager.getProgramStack(),
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
GLStateManager.getTextures().getTextureUnitStates(0)
GLStateManager.getTextures().getTextureUnitStates(0),
GLStateManager.getColorMask(), GLStateManager.getCullState(),
GLStateManager.getPolygonState(), GLStateManager.getPolygonOffsetFillState()
),
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT | R_PROGRAM,
R_BLEND | R_DEPTH | R_COLOR_MASK | R_TEXTURE | R_ACTIVE_UNIT | R_PROGRAM | R_POLYGON,
0,
true);
}
@@ -121,9 +123,11 @@ public final class StateSet {
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
GLStateManager.getActiveTextureUnitStack(),
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
GLStateManager.getTextures().getTextureUnitStates(0)
GLStateManager.getTextures().getTextureUnitStates(0),
GLStateManager.getColorMask(), GLStateManager.getCullState(),
GLStateManager.getPolygonState(), GLStateManager.getPolygonOffsetFillState()
),
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT,
R_BLEND | R_DEPTH | R_COLOR_MASK | R_TEXTURE | R_ACTIVE_UNIT | R_POLYGON,
0,
true);
}
@@ -0,0 +1,38 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizons.angelica.config.SystemProperties;
public final class BackendOptions {
private static boolean configSdlGpu;
private static String configDriver = "";
private static int configGlVersion;
private BackendOptions() {}
public static void latch(boolean sdlGpu, String driver, int glVersion) {
configSdlGpu = sdlGpu;
configDriver = driver;
configGlVersion = glVersion;
}
public static boolean sdlGpuRequested() {
return SystemProperties.SDL_GPU_OVERRIDE != null ? SystemProperties.SDL_GPU_OVERRIDE : configSdlGpu;
}
public static String sdlGpuDriver() {
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : configDriver;
}
public static boolean configSdlGpu() {
return configSdlGpu;
}
public static String configDriver() {
return configDriver;
}
public static int configGlVersion() {
return configGlVersion;
}
}
@@ -0,0 +1,55 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
public final class BackendStartGuard {
private static final Logger LOGGER = LogManager.getLogger("Angelica/BackendStartGuard");
private static File pending;
private static boolean tripped;
private BackendStartGuard() {}
public static boolean begin(File marker, boolean sdlGpuConfigured, boolean flagForced) {
pending = null;
tripped = false;
if (!sdlGpuConfigured || flagForced) {
delete(marker);
return false;
}
if (marker.isFile()) {
delete(marker);
tripped = true;
return true;
}
try {
final File dir = marker.getParentFile();
if (dir != null && !dir.isDirectory() && !dir.mkdirs()) throw new IOException("cannot create " + dir);
if (marker.createNewFile()) pending = marker;
} catch (IOException e) {
LOGGER.warn("Could not write {}: {}", marker, e.toString());
}
return false;
}
public static void finish() {
if (pending == null) return;
delete(pending);
pending = null;
}
public static boolean tripped() {
return tripped;
}
private static void delete(File marker) {
if (marker.isFile() && !marker.delete()) {
LOGGER.warn("Could not delete {}", marker);
}
}
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.AMDDebugOutput;
import org.lwjgl.opengl.AMDDebugOutputCallback;
@@ -23,6 +24,7 @@ import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL41;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL42;
@@ -807,6 +809,28 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return GL20.glIsProgram(obj);
}
@Override
public boolean supportsProgramBinary() {
final ContextCapabilities caps = GLStateManager.capabilities;
return !RenderSystem.isGLES() && (caps.OpenGL41 || caps.GL_ARB_get_program_binary)
&& GL11.glGetInteger(GL41.GL_NUM_PROGRAM_BINARY_FORMATS) > 0;
}
@Override
public void programParameteri(int program, int pname, int value) {
GL41.glProgramParameteri(program, pname, value);
}
@Override
public void getProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {
GL41.glGetProgramBinary(program, length, binaryFormat, binary);
}
@Override
public void programBinary(int program, int binaryFormat, ByteBuffer binary) {
GL41.glProgramBinary(program, binaryFormat, binary);
}
@Override
public void validateProgram(int program) {
GL20.glValidateProgram(program);
@@ -0,0 +1,125 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizons.angelica.config.SystemProperties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
public final class MoltenVK {
public enum Source {
ENV,
JAR,
SYSTEM,
NONE
}
public static final String ENV_VULKAN_LIBRARY = "SDL_VULKAN_LIBRARY";
public static final String JAR_NAME = "angelica-moltenvk";
private static final Logger LOGGER = LogManager.getLogger("Angelica/MoltenVK");
private static final String LIB_NAME = "libMoltenVK.dylib";
private static final String[] SYSTEM_PATHS = { "/opt/homebrew/lib/" + LIB_NAME, "/usr/local/lib/" + LIB_NAME };
private static final String ARCH = archName(System.getProperty("os.arch", ""));
private static Source source;
private static String path;
private static boolean located;
private MoltenVK() {}
public static synchronized Source source() {
if (source == null) {
final boolean macos = System.getProperty("os.name", "").toLowerCase().contains("mac");
source = detect(macos, System.getenv(ENV_VULKAN_LIBRARY), MoltenVK.class.getResource(bundledResource(ARCH)) != null, SYSTEM_PATHS);
}
return source;
}
public static synchronized String locate() {
if (!located) {
path = resolve(source(), System.getenv(ENV_VULKAN_LIBRARY), bundledResource(ARCH), new File(SystemProperties.MOLTENVK_DIR), ARCH, SYSTEM_PATHS);
located = true;
}
return path;
}
static String archName(String osArch) {
final String arch = osArch.toLowerCase();
return arch.contains("aarch64") || arch.contains("arm64") ? "arm64" : "x64";
}
static String bundledResource(String arch) {
return "/macos/" + arch + "/org/lwjgl/vulkan/" + LIB_NAME;
}
static Source detect(boolean macos, String env, boolean bundled, String[] systemPaths) {
if (!macos) return Source.NONE;
if (env != null && !env.isEmpty()) return Source.ENV;
if (bundled) return Source.JAR;
return systemPath(systemPaths) != null ? Source.SYSTEM : Source.NONE;
}
static String resolve(Source source, String env, String resource, File dir, String arch, String[] systemPaths) {
return switch (source) {
case ENV -> env;
case JAR -> {
final String extracted = extract(resource, dir, arch);
yield extracted != null ? extracted : systemPath(systemPaths);
}
case SYSTEM -> systemPath(systemPaths);
case NONE -> null;
};
}
private static String systemPath(String[] systemPaths) {
for (final String candidate : systemPaths) {
if (new File(candidate).isFile()) return candidate;
}
return null;
}
private static String extract(String resource, File dir, String arch) {
final byte[] lib;
try (InputStream in = MoltenVK.class.getResourceAsStream(resource)) {
if (in == null) return null;
lib = readAll(in);
} catch (IOException e) {
LOGGER.warn("MoltenVK: failed reading {}: {}", resource, e.toString());
return null;
}
final File target = new File(dir, "libMoltenVK-" + arch + ".dylib");
File tmp = null;
try {
if (!target.isFile() || target.length() != lib.length || !Arrays.equals(Files.readAllBytes(target.toPath()), lib)) {
if (!dir.isDirectory() && !dir.mkdirs()) throw new IOException("cannot create " + dir);
tmp = File.createTempFile("libMoltenVK", ".dylib", dir);
Files.write(tmp.toPath(), lib);
Files.move(tmp.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
LOGGER.warn("MoltenVK: failed extracting to {}: {}", target, e.toString());
if (tmp != null) tmp.delete();
return null;
}
return target.getAbsolutePath();
}
private static byte[] readAll(InputStream in) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream(1 << 20);
final byte[] buf = new byte[16384];
int n;
while ((n = in.read(buf)) != -1) {
out.write(buf, 0, n);
}
return out.toByteArray();
}
}
@@ -36,6 +36,9 @@ public abstract class RenderBackend {
/** Returns true if the current thread has a valid render context. */
public abstract boolean hasContext();
/** Returns true if the calling thread may issue render commands. */
public boolean hasContextOnThread() { return hasContext(); }
/** Returns true if the calling thread owns the "GL Context" */
public abstract boolean isCurrent();
@@ -56,6 +59,7 @@ public abstract class RenderBackend {
public boolean framebufferCompletenessIsMeaningful() { return true; }
public void onPersistentBufferWrite(int glId, long offset, long size) {}
public boolean isBufferImmutable(int glId) { return false; }
public boolean isAnisotropicSupported() { return true; }
@@ -272,6 +276,12 @@ public abstract class RenderBackend {
public void voxelizeRegion(long pass, boolean rebindVertexBuffer, float x, float y, float z, int rangeBase, int rangeCount, int vertexTotal) {}
public void endVoxelizationBatch(long pass) {}
public void setVertexWriteReplay(int graphicsProgram, VertexWriteReplaySetup setup) {}
public boolean supportsComputeImageAtomics() { return true; }
public boolean supportsComputeImageAtomics(int width, int height, int depth) { return supportsComputeImageAtomics(); }
public abstract void enable(int cap);
public abstract void enablei(int cap, int index);
public abstract void disable(int cap);
@@ -504,6 +514,10 @@ public abstract class RenderBackend {
public abstract int getUniformLocation(int program, ByteBuffer name);
public boolean isShader(int obj) { return false; }
public boolean isProgram(int obj) { return false; }
public boolean supportsProgramBinary() { return false; }
public void programParameteri(int program, int pname, int value) {}
public void getProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {}
public void programBinary(int program, int binaryFormat, ByteBuffer binary) {}
public void validateProgram(int program) {}
public void getAttachedShaders(int program, IntBuffer count, IntBuffer shaders) {
if (count != null) count.put(0, 0);
@@ -0,0 +1,23 @@
package com.gtnewhorizons.angelica.glsm.backend;
import java.util.function.IntSupplier;
/**
* How to replay a graphics program's vertex-stage image writes.
*
* @param inputLocations per input, the declared location or -1 for whatever the graphics program linked
* @param images per image the replay uses, in binding order: the written ones, then the read-only ones
* @param writtenImageCount how many of {@code images} the replay writes
* @param samplerNames samplers the graphics program may not have, because only the stripped writes read them
* @param samplerUnits per sampler name, the texture unit it reads at each draw, or -1 to use samplerTextures
* @param samplerTextures per sampler name, its texture when samplerUnits is -1
* @param samplerObjects per sampler name, the GL sampler object to sample it with when samplerUnits is -1, or 0
* @param vertexBufferBinding storage buffer binding of the first vertex buffer the replay program declares
* @param vertexBufferCount how many vertex buffers it declares, at consecutive bindings
* @param indexBufferBinding storage buffer binding of its index buffer
*/
public record VertexWriteReplaySetup(int replayProgram, String[] inputNames, int[] inputLocations,
IntSupplier[] images, int writtenImageCount,
String[] samplerNames, int[] samplerUnits, IntSupplier[] samplerTextures, int[] samplerObjects,
int vertexBufferBinding, int vertexBufferCount, int indexBufferBinding) {
}
@@ -133,11 +133,13 @@ public class DSAARB extends DSAUnsupported {
@Override
public int createFramebuffer() {
GLStateManager.requireContext("glCreateFramebuffers");
return RENDER_BACKEND.createFramebuffers();
}
@Override
public int createTexture(int target) {
GLStateManager.requireContext("glCreateTextures");
return RENDER_BACKEND.createTextures(target);
}
}
@@ -165,7 +165,7 @@ public class DSAUnsupported implements DSAAccess {
@Override
public int createTexture(int target) {
final int texture = RENDER_BACKEND.genTextures();
final int texture = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
return texture;
}
@@ -54,9 +54,17 @@ public final class GeometryShaderGenerator {
sb.append(" vec4 c0 = gl_in[0].gl_Position;\n");
sb.append(" vec4 c1 = gl_in[1].gl_Position;\n\n");
sb.append(" float d0 = c0.z + c0.w;\n");
sb.append(" float d1 = c1.z + c1.w;\n");
sb.append(" if (d0 < 0.0 && d1 < 0.0) return;\n");
sb.append(" float t0 = d0 < 0.0 ? d0 / (d0 - d1) : 0.0;\n");
sb.append(" float t1 = d1 < 0.0 ? d0 / (d0 - d1) : 1.0;\n");
sb.append(" vec4 p0 = mix(c0, c1, t0);\n");
sb.append(" vec4 p1 = mix(c0, c1, t1);\n\n");
sb.append(" // NDC positions\n");
sb.append(" vec2 n0 = c0.xy / c0.w;\n");
sb.append(" vec2 n1 = c1.xy / c1.w;\n\n");
sb.append(" vec2 n0 = p0.xy / p0.w;\n");
sb.append(" vec2 n1 = p1.xy / p1.w;\n\n");
sb.append(" // Screen-space direction and perpendicular\n");
sb.append(" vec2 dir = normalize((n1 - n0) * u_ViewportSize);\n");
@@ -74,22 +82,22 @@ public final class GeometryShaderGenerator {
}
private static void emitVertex(StringBuilder sb, VertexKey key, int endpointIdx, String offsetExpr) {
final String ci = "c" + endpointIdx;
final String pi = "p" + endpointIdx;
final String ni = "n" + endpointIdx;
final String idx = "[" + endpointIdx + "]";
final String ti = "t" + endpointIdx;
sb.append(" // Endpoint ").append(endpointIdx).append(' ').append(offsetExpr).append('\n');
sb.append(" v_Color = v_Color_gs").append(idx).append(";\n");
sb.append(" v_Color = ").append(lerp("v_Color_gs", ti)).append(";\n");
if (key.separateSpecular()) {
sb.append(" v_SpecularColor = v_SpecularColor_gs").append(idx).append(";\n");
sb.append(" v_SpecularColor = ").append(lerp("v_SpecularColor_gs", ti)).append(";\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
if (key.lightmapEnabled()) sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = v_TexCoord2_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = v_TexCoord3_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append(" v_TexCoord0 = ").append(lerp("v_TexCoord0_gs", ti)).append(";\n");
if (key.lightmapEnabled()) sb.append(" v_TexCoord1 = ").append(lerp("v_TexCoord1_gs", ti)).append(";\n");
if (key.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = ").append(lerp("v_TexCoord2_gs", ti)).append(";\n");
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = ").append(lerp("v_TexCoord3_gs", ti)).append(";\n");
if (key.fogEnabled()) {
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
sb.append(" v_FogCoord = ").append(lerp("v_FogCoord_gs", ti)).append(";\n");
}
if (key.lineStipple()) {
sb.append(" v_LineStart = v_LineStart_gs[0];\n");
@@ -97,11 +105,15 @@ public final class GeometryShaderGenerator {
if (key.clipPlanesEnabled()) {
for (int i = 0; i < 8; i++) {
sb.append(" gl_ClipDistance[").append(i).append("] = gl_in").append(idx).append(".gl_ClipDistance[").append(i).append("];\n");
sb.append(" gl_ClipDistance[").append(i).append("] = mix(gl_in[0].gl_ClipDistance[").append(i).append("], gl_in[1].gl_ClipDistance[").append(i).append("], ").append(ti).append(");\n");
}
}
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(ci).append(".w, ").append(ci).append(".z, ").append(ci).append(".w);\n");
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(pi).append(".w, ").append(pi).append(".z, ").append(pi).append(".w);\n");
sb.append(" EmitVertex();\n\n");
}
private static String lerp(String input, String t) {
return "mix(" + input + "[0], " + input + "[1], " + t + ")";
}
}
@@ -85,6 +85,11 @@ public final class EcosystemNarrowRules {
"com.tihyo.legends.management.asm.AbstractTransformer",
"com.tihyo.legends.management.asm.ASMHelper",
"com.tihyo.legends.management.asm.Transformer"
)),
new NarrowRule("PowerConverters", "covers1624.powerconverters.", List.of(
"covers1624.powerconverters.asm",
"covers1624.powerconverters.randomdebugshit.CoFHDevEnvHacks"
))
);
@@ -3,7 +3,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -28,7 +28,7 @@ public record TexImage2DCmd(
int format,
int type,
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
@Nullable PixelStoreState unpack
) implements DisplayListCommand {
public TexImage2DCmd {
@@ -37,19 +37,19 @@ public record TexImage2DCmd(
}
}
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@@ -3,7 +3,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -28,7 +28,7 @@ public record TexSubImage2DCmd(
int format,
int type,
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
@Nullable PixelStoreState unpack
) implements DisplayListCommand {
public TexSubImage2DCmd {
@@ -37,19 +37,19 @@ public record TexSubImage2DCmd(
}
}
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@@ -626,6 +626,7 @@ public class GLSMRedirector {
.add("glGenRenderbuffers")
.add("glRenderbufferStorage")
.add("glFramebufferRenderbuffer")
.add("glGenerateMipmap")
);
methodRedirects.put(EXTFramebufferObject, RedirectMap.newMap()
.add("glBindFramebufferEXT", "glBindFramebuffer")
@@ -638,6 +639,7 @@ public class GLSMRedirector {
.add("glGenRenderbuffersEXT", "glGenRenderbuffers")
.add("glRenderbufferStorageEXT", "glRenderbufferStorage")
.add("glFramebufferRenderbufferEXT", "glFramebufferRenderbuffer")
.add("glGenerateMipmapEXT", "glGenerateMipmap")
);
methodRedirects.put(ARBOcclusionQuery, RedirectMap.newMap()
.add("glGenQueriesARB", "glGenQueries")
@@ -4,17 +4,11 @@ import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL20;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.grammar.GLSLPreParser;
import org.taumc.glsl.grammar.GLSLParserBaseListener;
import java.util.ArrayList;
import java.util.Comparator;
@@ -99,7 +93,7 @@ public final class GlslVulkanPreprocess {
}
final List<Edit> edits = new ArrayList<>();
final Metadata meta = collectEdits(source, root, glShaderType, debugName, separateReadOnlyImages, edits);
final Metadata meta = collectEdits(new ShaderIndex(root, source), glShaderType, debugName, separateReadOnlyImages, edits);
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs());
synchronized (CACHE) {
@@ -111,129 +105,100 @@ public final class GlslVulkanPreprocess {
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) {
public static Metadata collectEdits(ShaderIndex index, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
final Set<String> bools = new HashSet<>();
final Set<String> explicitInputs = new HashSet<>();
final Set<String> readOnlyImages = new HashSet<>();
final boolean[] needsSamplerless = { false };
boolean needsSamplerless = false;
final int[] maxExplicitLoc = { -1 };
int maxExplicitLoc = -1;
final List<Integer> unlocatedVsInputStarts = new ArrayList<>();
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
@Override
public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) {
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
if (single == null || single.fully_specified_type() == null) return;
for (GLSLParser.DeclarationContext declaration : index.globalDeclarations()) {
final GLSLParser.Init_declarator_listContext list = declaration.init_declarator_list();
if (list == null) continue;
final GLSLParser.Single_declarationContext single = list.single_declaration();
if (single == null || single.fully_specified_type() == null) continue;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
final GLSLParser.Type_qualifierContext tq = fst.type_qualifier();
if (tq == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
final GLSLParser.Type_qualifierContext tq = fst.type_qualifier();
if (tq == null) continue;
boolean hasUniform = false, hasIn = false, hasLocation = false, hasReadonly = false;
int locValue = -1;
int bindingValue = -1;
for (GLSLParser.Single_type_qualifierContext stq : tq.single_type_qualifier()) {
if (stq.storage_qualifier() != null) {
final String s = stq.storage_qualifier().getText();
if ("uniform".equals(s)) hasUniform = true;
else if ("in".equals(s)) hasIn = true;
else if ("readonly".equals(s)) hasReadonly = true;
} else if (stq.layout_qualifier() != null) {
for (GLSLParser.Layout_qualifier_idContext id : stq.layout_qualifier().layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() == null) continue;
final String qualifier = id.IDENTIFIER().getText();
if ("location".equals(qualifier)) {
hasLocation = true;
if (id.constant_expression() != null) {
try { locValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
}
} else if ("binding".equals(qualifier) && id.constant_expression() != null) {
try { bindingValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
boolean hasUniform = false, hasIn = false, hasLocation = false, hasReadonly = false;
int locValue = -1;
int bindingValue = -1;
for (GLSLParser.Single_type_qualifierContext stq : tq.single_type_qualifier()) {
if (stq.storage_qualifier() != null) {
final String s = stq.storage_qualifier().getText();
if ("uniform".equals(s)) hasUniform = true;
else if ("in".equals(s)) hasIn = true;
else if ("readonly".equals(s)) hasReadonly = true;
} else if (stq.layout_qualifier() != null) {
for (GLSLParser.Layout_qualifier_idContext id : stq.layout_qualifier().layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() == null) continue;
final String qualifier = id.IDENTIFIER().getText();
if ("location".equals(qualifier)) {
hasLocation = true;
if (id.constant_expression() != null) {
try { locValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
}
} else if ("binding".equals(qualifier) && id.constant_expression() != null) {
try { bindingValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
}
}
}
}
final boolean boolDecl = hasUniform && isBool(fst);
final boolean explicitInputDecl = isVertex && hasIn && hasLocation;
final boolean boolDecl = hasUniform && isBool(fst);
final boolean explicitInputDecl = isVertex && hasIn && hasLocation;
final List<GLSLParser.Typeless_declarationContext> declarators = ShaderIndex.declarators(list);
for (GLSLParser.Typeless_declarationContext td : declarators) {
handleDeclarator(td, hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
}
if (single.typeless_declaration() != null) {
handleDeclarator(single.typeless_declaration(), hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
}
for (GLSLParser.Typeless_declarationContext td : ctx.typeless_declaration()) {
handleDeclarator(td, hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
}
if (separateReadOnlyImages && hasUniform && fst.type_specifier() != null) {
final String typeText = fst.type_specifier().getText();
if (separateTextureType(typeText) != null) {
needsSamplerless[0] = true;
} else if (hasReadonly) {
final String asTexture = separateTextureTypeForImage(typeText);
if (asTexture != null) {
needsSamplerless[0] = true;
collectDeclaredNames(single, ctx, readOnlyImages);
final String binding = bindingValue >= 0 ? "layout(binding = " + bindingValue + ") " : "";
edits.add(new Edit(startIdx(fst), stopIdx(fst), binding + "uniform " + asTexture));
if (separateReadOnlyImages && hasUniform && fst.type_specifier() != null) {
final String typeText = fst.type_specifier().getText();
if (separateTextureType(typeText) != null) {
needsSamplerless = true;
} else if (hasReadonly) {
final String asTexture = separateTextureTypeForImage(typeText);
if (asTexture != null) {
needsSamplerless = true;
for (GLSLParser.Typeless_declarationContext td : declarators) {
if (td.IDENTIFIER() != null) readOnlyImages.add(td.IDENTIFIER().getText());
}
}
}
if (isVertex && hasIn) {
if (hasLocation && locValue >= 0) {
if (locValue > maxExplicitLoc[0]) maxExplicitLoc[0] = locValue;
} else if (!hasLocation) {
unlocatedVsInputStarts.add(ctx.getStart().getStartIndex());
final String binding = bindingValue >= 0 ? "layout(binding = " + bindingValue + ") " : "";
edits.add(new Edit(startIdx(fst), stopIdx(fst), binding + "uniform " + asTexture));
}
}
}
@Override
public void enterPostfix_expression(GLSLParser.Postfix_expressionContext ctx) {
if (readOnlyImages.isEmpty() || ctx.LEFT_PAREN() == null || ctx.RIGHT_PAREN() == null) return;
final GLSLParser.Function_call_parametersContext params = ctx.function_call_parameters();
if (params == null || params.assignment_expression() == null || params.assignment_expression().isEmpty()) return;
if (!readOnlyImages.contains(params.assignment_expression(0).getText())) return;
final ParseTree callee = ctx.getChild(0);
if (!(callee instanceof ParserRuleContext calleeCtx)) return;
final String replacement = switch (calleeCtx.getText()) {
case "imageLoad" -> "texelFetch";
case "imageSize" -> "textureSize";
default -> null;
};
if (replacement == null) return;
edits.add(new Edit(startIdx(calleeCtx), stopIdx(calleeCtx), replacement));
final int rparen = ctx.RIGHT_PAREN().getSymbol().getStartIndex();
edits.add(new Edit(rparen, rparen - 1, ", 0"));
}
@Override
public void visitTerminal(TerminalNode node) {
final Token tok = node.getSymbol();
if (tok.getType() != GLSLLexer.IDENTIFIER) return;
final String text = tok.getText();
switch (text) {
case "sampler" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), SAMPLER_RENAMED));
case "gl_VertexID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_VertexIndex"));
case "gl_InstanceID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_InstanceIndex"));
default -> {
if (TARGET_RESERVED.contains(text)) edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), RESERVED_RENAMED_PREFIX + text));
}
if (isVertex && hasIn) {
if (hasLocation && locValue >= 0) {
if (locValue > maxExplicitLoc) maxExplicitLoc = locValue;
} else if (!hasLocation) {
unlocatedVsInputStarts.add(list.getStart().getStartIndex());
}
}
}, root);
if (needsSamplerless[0] && source != null) {
final int at = versionDirectiveEnd(source);
if (at >= 0) edits.add(new Edit(at, at - 1, "\n" + SAMPLERLESS_EXTENSION));
}
if (maxExplicitLoc[0] >= 0 && !unlocatedVsInputStarts.isEmpty()) {
int next = maxExplicitLoc[0] + 1;
if (!readOnlyImages.isEmpty()) {
rewriteReadOnlyImageCalls(index.callsTo("imageLoad"), "texelFetch", readOnlyImages, edits);
rewriteReadOnlyImageCalls(index.callsTo("imageSize"), "textureSize", readOnlyImages, edits);
}
if (needsSamplerless) {
final int versionEnd = index.directives().versionEnd();
if (versionEnd >= 0) edits.add(new Edit(versionEnd, versionEnd - 1, "\n" + SAMPLERLESS_EXTENSION));
}
rename(index, "sampler", SAMPLER_RENAMED, edits);
rename(index, "gl_VertexID", "gl_VertexIndex", edits);
rename(index, "gl_InstanceID", "gl_InstanceIndex", edits);
for (String reserved : TARGET_RESERVED) rename(index, reserved, RESERVED_RENAMED_PREFIX + reserved, edits);
if (maxExplicitLoc >= 0 && !unlocatedVsInputStarts.isEmpty()) {
int next = maxExplicitLoc + 1;
int dropped = 0;
for (int start : unlocatedVsInputStarts) {
if (next >= MAX_VS_INPUT_LOCATIONS) { dropped++; continue; }
@@ -245,22 +210,31 @@ public final class GlslVulkanPreprocess {
}
}
return new Metadata(bools, explicitInputs, needsSamplerless[0]);
return new Metadata(bools, explicitInputs, needsSamplerless);
}
private static void rename(ShaderIndex index, String name, String replacement, List<Edit> edits) {
for (Token tok : index.occurrences(name)) edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), replacement));
}
private static void rewriteReadOnlyImageCalls(List<GLSLParser.Postfix_expressionContext> calls, String replacement, Set<String> readOnlyImages, List<Edit> edits) {
for (GLSLParser.Postfix_expressionContext call : calls) {
if (call.RIGHT_PAREN() == null) continue;
final GLSLParser.Function_call_parametersContext params = call.function_call_parameters();
if (params == null || params.assignment_expression().isEmpty()) continue;
if (!readOnlyImages.contains(params.assignment_expression(0).getText())) continue;
final ParserRuleContext callee = (ParserRuleContext) call.getChild(0);
edits.add(new Edit(startIdx(callee), stopIdx(callee), replacement));
final int rparen = call.RIGHT_PAREN().getSymbol().getStartIndex();
edits.add(new Edit(rparen, rparen - 1, ", 0"));
}
}
public static void clearCache() {
synchronized (CACHE) { CACHE.clear(); }
}
private static void collectDeclaredNames(GLSLParser.Single_declarationContext single, GLSLParser.Init_declarator_listContext ctx, Set<String> out) {
if (single.typeless_declaration() != null && single.typeless_declaration().IDENTIFIER() != null) {
out.add(single.typeless_declaration().IDENTIFIER().getText());
}
for (GLSLParser.Typeless_declarationContext td : ctx.typeless_declaration()) {
if (td.IDENTIFIER() != null) out.add(td.IDENTIFIER().getText());
}
}
private static @Nullable String separateTextureTypeForImage(String typeText) {
final String dim = switch (typeText) {
case "image1D", "iimage1D", "uimage1D" -> "1D";
@@ -281,26 +255,6 @@ public final class GlslVulkanPreprocess {
};
}
private static int versionDirectiveEnd(String source) {
final GLSLPreParser.Translation_unitContext pre;
try {
pre = GlslTransformUtils.parsePreQuiet(source);
} catch (Exception e) {
return -1;
}
final GLSLPreParser.Version_directiveContext ctx = firstVersionDirective(pre);
return ctx == null || ctx.getStop() == null ? -1 : ctx.getStop().getStopIndex() + 1;
}
private static GLSLPreParser.@Nullable Version_directiveContext firstVersionDirective(ParseTree node) {
if (node instanceof GLSLPreParser.Version_directiveContext v) return v;
for (int i = 0; i < node.getChildCount(); i++) {
final GLSLPreParser.Version_directiveContext found = firstVersionDirective(node.getChild(i));
if (found != null) return found;
}
return null;
}
private static void handleDeclarator(GLSLParser.Typeless_declarationContext td, boolean hasUniform, boolean asBool, boolean asInput, Set<String> bools, Set<String> inputs, List<Edit> edits) {
if (td.IDENTIFIER() == null) return;
final String name = td.IDENTIFIER().getText();
@@ -0,0 +1,255 @@
package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.threading.AngelicaWorkers;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL41;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Saves linked GL programs to disk and loads them back with glProgramBinary, skipping compile and link.
*/
public final class ProgramBinaryCache {
private static final Logger LOGGER = LogManager.getLogger("ProgramBinaryCache");
static final String LAYER = "gl-program";
private static final int PACKS_KEPT = 3;
private static volatile boolean enabled;
private static volatile String packLayer;
private static String driverId;
private static String fullCompileLayer;
private static Set<String> keysInUse;
private static List<CompletableFuture<Boolean>> fullCompileWrites;
private static Object fullCompileGeneration;
private static final AtomicBoolean rejectWarned = new AtomicBoolean();
private static final Object WRITE_LOCK = new Object();
private static final Map<String, Object> compileGenerations = new HashMap<>();
private static int deleteCount;
private ProgramBinaryCache() {}
public static void setEnabled(boolean value) {
enabled = value;
}
public static boolean isEnabled() {
return enabled && packLayer != null && RenderSystem.supportsProgramBinary() && ShaderDiskCache.isEnabled();
}
public static void usePack(String packName) {
if (packName == null) {
packLayer = null;
return;
}
final String layer = LAYER + '/' + folderName(packName);
packLayer = layer;
if (!enabled) return;
ShaderDiskCache.touchLayer(layer);
AngelicaWorkers.run(() -> ShaderDiskCache.keepNewestSubLayers(LAYER, PACKS_KEPT));
}
public static Key key() {
return new Key(ShaderDiskCache.key(packLayer).str(driverId()).b(ShaderManager.isEnabled()));
}
public static void markRetrievable(int program) {
GLStateManager.glProgramParameteri(program, GL41.GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL11.GL_TRUE);
}
public static int load(Key key) {
final ShaderDiskCache.Key diskKey = key.diskKey;
final ShaderDiskCache.Blob blob = ShaderDiskCache.getBlob(diskKey);
if (blob == null) return 0;
final int format;
try {
format = Integer.parseInt(blob.tag());
} catch (NumberFormatException e) {
ShaderDiskCache.remove(diskKey);
return 0;
}
final int program = GLStateManager.glCreateProgram();
final ByteBuffer data = MemoryUtilities.memAlloc(blob.data().length);
try {
data.put(blob.data()).flip();
GLStateManager.glProgramBinary(program, format, data);
} finally {
MemoryUtilities.memFree(data);
}
if (GLStateManager.glGetProgrami(program, GL20.GL_LINK_STATUS) != GL11.GL_TRUE) {
GLStateManager.glDeleteProgram(program);
ShaderDiskCache.remove(diskKey);
if (rejectWarned.compareAndSet(false, true)) LOGGER.error("Driver rejected a saved shader program!! Recompiling rejected programs from source");
return 0;
}
keep(diskKey);
return program;
}
public static void save(Key key, int program) {
if (!saveBinary(key, program) && fullCompileWrites != null) {
fullCompileWrites.add(CompletableFuture.completedFuture(false));
}
}
private static boolean saveBinary(Key key, int program) {
final int length = GLStateManager.glGetProgrami(program, GL41.GL_PROGRAM_BINARY_LENGTH);
if (length <= 0) return false;
final ByteBuffer data = MemoryUtilities.memAlloc(length);
final IntBuffer written = MemoryUtilities.memAllocInt(1);
final IntBuffer format = MemoryUtilities.memAllocInt(1);
try {
GLStateManager.glGetProgramBinary(program, written, format, data);
if (written.get(0) <= 0) return false;
data.limit(written.get(0));
final byte[] binary = ShaderCacheIO.toHeap(data);
final String formatTag = Integer.toString(format.get(0));
final ShaderDiskCache.Key diskKey = key.diskKey;
keep(diskKey);
final int deletesBefore;
synchronized (WRITE_LOCK) {
deletesBefore = deleteCount;
}
final CompletableFuture<Boolean> write = AngelicaWorkers.submit(() -> {
synchronized (WRITE_LOCK) {
return deletesBefore == deleteCount && ShaderDiskCache.putBlob(diskKey, formatTag, binary);
}
});
final List<CompletableFuture<Boolean>> writes = fullCompileWrites;
if (writes != null) writes.add(write);
return true;
} finally {
MemoryUtilities.memFree(data);
MemoryUtilities.memFree(written);
MemoryUtilities.memFree(format);
}
}
public static boolean isFullCompileRunning() {
return keysInUse != null;
}
public static boolean saveRetained(Key key, int program) {
if (ShaderDiskCache.getBlob(key.diskKey) != null) {
keep(key.diskKey);
return true;
}
return saveBinary(key, program);
}
public static void beginFullCompile() {
fullCompileLayer = packLayer;
keysInUse = new HashSet<>();
fullCompileWrites = new ArrayList<>();
synchronized (WRITE_LOCK) {
fullCompileGeneration = new Object();
if (fullCompileLayer != null) compileGenerations.put(fullCompileLayer, fullCompileGeneration);
}
}
public static void finishFullCompile(@Nullable String completedSettings) {
final String layer = fullCompileLayer;
final Set<String> used = keysInUse;
final List<CompletableFuture<Boolean>> writes = fullCompileWrites;
final Object generation = fullCompileGeneration;
fullCompileLayer = null;
keysInUse = null;
fullCompileWrites = null;
fullCompileGeneration = null;
if (layer == null || used == null || writes == null) return;
final ShaderDiskCache.Key marker = completedSettings != null ? compiledSettingsKey(layer, completedSettings) : null;
final String markerHex = marker != null ? marker.hex() : null;
CompletableFuture.allOf(writes.toArray(new CompletableFuture<?>[0])).whenComplete((ignored, failure) -> {
synchronized (WRITE_LOCK) {
if (compileGenerations.get(layer) != generation) return;
if (marker == null || failure != null) return;
for (CompletableFuture<Boolean> write : writes) {
if (!write.join()) return;
}
ShaderDiskCache.retainLayer(layer, used);
ShaderDiskCache.retainLayer(layer + "/settings", Collections.singleton(markerHex));
ShaderDiskCache.putString(marker, "");
}
});
}
public static void deleteAll() {
synchronized (WRITE_LOCK) {
deleteCount++;
compileGenerations.clear();
ShaderDiskCache.deleteLayer(LAYER);
}
}
public static boolean wasCompiledFor(String settings) {
final String layer = packLayer;
return layer != null && ShaderDiskCache.getString(compiledSettingsKey(layer, settings)) != null;
}
private static ShaderDiskCache.Key compiledSettingsKey(String layer, String settings) {
return ShaderDiskCache.key(layer + "/settings").str(driverId()).b(ShaderManager.isEnabled()).str(settings);
}
private static String folderName(String packName) {
final StringBuilder name = new StringBuilder(Math.min(packName.length(), 64));
for (int i = 0; i < packName.length() && name.length() < 64; i++) {
final char c = packName.charAt(i);
name.append(c < 128 && (Character.isLetterOrDigit(c) || c == '.' || c == '-' || c == '_') ? c : '_');
}
return name.toString();
}
private static void keep(ShaderDiskCache.Key diskKey) {
final String hex = diskKey.hex();
final Set<String> used = keysInUse;
if (used != null) used.add(hex);
}
private static String driverId() {
String id = driverId;
if (id == null) {
id = RENDER_BACKEND.getString(GL11.GL_VENDOR) + '|' + RENDER_BACKEND.getString(GL11.GL_RENDERER) + '|'
+ RENDER_BACKEND.getString(GL11.GL_VERSION) + '|' + RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION) + '|'
+ RenderSystem.isGLES() + '|' + RENDER_BACKEND.getMinGLSLVersion();
driverId = id;
}
return id;
}
public static final class Key {
private final ShaderDiskCache.Key diskKey;
private Key(ShaderDiskCache.Key diskKey) {
this.diskKey = diskKey;
}
public Key stage(int shaderType, String source) {
diskKey.str("stage").i(shaderType).str(source);
return this;
}
public Key attribute(String name, int index) {
diskKey.str("attribute").str(name).i(index);
return this;
}
}
}
@@ -19,14 +19,18 @@ import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
@@ -49,7 +53,10 @@ public final class ShaderDiskCache {
private ShaderDiskCache() {}
public static void configure(Path dir, String salt) {
private static String configuredSalt;
public static synchronized void configure(Path dir, String salt) {
if (dir.equals(root) && salt.equals(configuredSalt)) return;
try {
Files.createDirectories(dir);
final Path saltFile = dir.resolve("salt");
@@ -59,6 +66,7 @@ public final class ShaderDiskCache {
Files.write(saltFile, salt.getBytes(StandardCharsets.UTF_8));
}
root = dir;
configuredSalt = salt;
AngelicaWorkers.run(() -> enforceCap(dir, MAX_BYTES));
LOGGER.info("Shader disk cache at {}", dir);
} catch (IOException | RuntimeException e) {
@@ -116,9 +124,9 @@ public final class ShaderDiskCache {
}
}
public static void put(Key key, byte[] payload) {
public static boolean put(Key key, byte[] payload) {
final Path r = root;
if (r == null) return;
if (r == null) return false;
final Path target = key.path(r);
final Path tmp = target.resolveSibling(key.hex() + '.' + TMP_SEQ.getAndIncrement() + ".tmp");
try {
@@ -129,11 +137,13 @@ public final class ShaderDiskCache {
write(tmp, payload);
}
Files.move(tmp, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
return true;
} catch (IOException e) {
try {
Files.deleteIfExists(tmp);
} catch (IOException ignored) {}
if (writeWarned.compareAndSet(false, true)) LOGGER.warn("Shader disk cache write failed for {}", target, e);
return false;
}
}
@@ -202,7 +212,7 @@ public final class ShaderDiskCache {
}
}
public static void putBlob(Key key, String tag, byte[] data) {
public static boolean putBlob(Key key, String tag, byte[] data) {
final ByteArrayOutputStream bytes = new ByteArrayOutputStream(data.length + 64);
try (DataOutputStream out = new DataOutputStream(bytes)) {
writeStr(out, tag);
@@ -210,11 +220,101 @@ public final class ShaderDiskCache {
} catch (IOException e) {
throw new UncheckedIOException(e);
}
put(key, bytes.toByteArray());
return put(key, bytes.toByteArray());
}
public record Blob(String tag, byte[] data) {}
public static void remove(Key key) {
final Path r = root;
if (r == null) return;
try {
Files.deleteIfExists(key.path(r));
} catch (IOException ignored) {}
}
public static void retainLayer(String layer, Set<String> keep) {
final Path r = root;
if (r == null) return;
final Path dir = r.resolve(layer);
if (!Files.isDirectory(dir)) return;
final List<Path> files = new ArrayList<>();
try (Stream<Path> list = Files.list(dir)) {
list.forEach(files::add);
} catch (IOException e) {
LOGGER.warn("Shader disk cache could not list {}", dir, e);
return;
}
for (Path p : files) {
final String name = p.getFileName().toString();
if (!name.endsWith(".bin")) continue; // in-flight writes are still .tmp
if (keep != null && keep.contains(name.substring(0, name.length() - 4))) continue;
try {
Files.deleteIfExists(p);
} catch (IOException ignored) {}
}
}
public static void touchLayer(String layer) {
final Path r = root;
if (r == null) return;
final Path dir = r.resolve(layer);
try {
Files.createDirectories(dir);
Files.setLastModifiedTime(dir, FileTime.fromMillis(System.currentTimeMillis()));
} catch (IOException e) {
LOGGER.warn("Shader disk cache could not mark {} as used", dir, e);
}
}
public static void keepNewestSubLayers(String layer, int keep) {
final Path r = root;
if (r == null) return;
final Path dir = r.resolve(layer);
if (!Files.isDirectory(dir)) return;
final List<Path> subLayers = new ArrayList<>();
try (Stream<Path> list = Files.list(dir)) {
list.filter(Files::isDirectory).forEach(subLayers::add);
} catch (IOException e) {
LOGGER.warn("Shader disk cache could not list {}", dir, e);
return;
}
subLayers.sort(Comparator.comparingLong(ShaderDiskCache::lastModifiedMillis).reversed());
for (int i = keep; i < subLayers.size(); i++) {
deleteRecursively(subLayers.get(i));
}
}
public static void deleteLayer(String layer) {
final Path r = root;
if (r != null) deleteRecursively(r.resolve(layer));
}
private static long lastModifiedMillis(Path path) {
try {
return Files.getLastModifiedTime(path).toMillis();
} catch (IOException e) {
return 0L;
}
}
private static void deleteRecursively(Path dir) {
if (!Files.exists(dir)) return;
final List<Path> paths = new ArrayList<>();
try (Stream<Path> walk = Files.walk(dir)) {
walk.forEach(paths::add);
} catch (IOException e) {
LOGGER.warn("Shader disk cache could not list {}", dir, e);
return;
}
Collections.reverse(paths);
for (Path p : paths) {
try {
Files.deleteIfExists(p);
} catch (IOException ignored) {}
}
}
static void enforceCap(Path dir, long maxBytes) {
try {
final long[] total = {0};
@@ -0,0 +1,153 @@
package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.jetbrains.annotations.Nullable;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* What the source passes look up in a parsed shader.
*/
public final class ShaderIndex {
public record Directives(Set<String> identifiers, boolean hasConditionalOrMacro, int versionEnd) {
private static final Directives NONE = new Directives(Set.of(), false, -1);
}
private final GLSLParser.Translation_unitContext root;
private final String source;
private final Map<String, List<Token>> identifiers = new HashMap<>();
private final Map<String, List<GLSLParser.Postfix_expressionContext>> calls = new HashMap<>();
private final List<GLSLParser.DeclarationContext> globalDeclarations = new ArrayList<>();
private @Nullable Directives directives;
public ShaderIndex(GLSLParser.Translation_unitContext root, String source) {
this.root = root;
this.source = source;
collect(root);
for (GLSLParser.External_declarationContext external : root.external_declaration()) {
if (external.declaration() != null) globalDeclarations.add(external.declaration());
}
}
private void collect(ParseTree node) {
if (node instanceof TerminalNode terminal) {
final Token token = terminal.getSymbol();
if (token.getType() == GLSLLexer.IDENTIFIER) identifiers.computeIfAbsent(token.getText(), k -> new ArrayList<>(2)).add(token);
return;
}
if (node instanceof GLSLParser.Postfix_expressionContext p && p.LEFT_PAREN() != null) {
calls.computeIfAbsent(calleeName(p), k -> new ArrayList<>(2)).add(p);
}
for (int i = 0, n = node.getChildCount(); i < n; i++) collect(node.getChild(i));
}
public GLSLParser.Translation_unitContext root() {
return root;
}
public String source() {
return source;
}
public List<Token> occurrences(String name) {
return identifiers.getOrDefault(name, List.of());
}
public List<GLSLParser.Postfix_expressionContext> callsTo(String name) {
return calls.getOrDefault(name, List.of());
}
public List<GLSLParser.DeclarationContext> globalDeclarations() {
return globalDeclarations;
}
/** Lexes the source on first use. */
public Directives directives() {
if (directives == null) directives = scanDirectives(source);
return directives;
}
private static Directives scanDirectives(String source) {
if (source.indexOf('#') < 0) return Directives.NONE;
final GLSLLexer lexer = GlslTransformUtils.quietLexer(source);
final Set<String> names = new HashSet<>();
boolean conditionalOrMacro = false;
int versionEnd = -1;
boolean inVersion = false;
for (Token token = lexer.nextToken(); token.getType() != Token.EOF; token = lexer.nextToken()) {
final int type = token.getType();
if (type == GLSLLexer.VERSION_DIRECTIVE && versionEnd < 0) {
inVersion = true;
versionEnd = token.getStopIndex() + 1;
continue;
}
if (inVersion && (type == GLSLLexer.NUMBER || type == GLSLLexer.PROFILE)) {
versionEnd = token.getStopIndex() + 1;
continue;
}
inVersion = false;
switch (type) {
case GLSLLexer.PROGRAM_TEXT, GLSLLexer.MACRO_TEXT, GLSLLexer.CONSTANT_EXPRESSION -> collectIdentifiers(token.getText(), names);
case GLSLLexer.IF_DIRECTIVE, GLSLLexer.IFDEF_DIRECTIVE, GLSLLexer.IFNDEF_DIRECTIVE, GLSLLexer.DEFINE_DIRECTIVE -> conditionalOrMacro = true;
default -> { }
}
}
return new Directives(names, conditionalOrMacro, versionEnd);
}
private static void collectIdentifiers(String text, Set<String> out) {
if (text == null) return;
final int len = text.length();
int i = 0;
while (i < len) {
final char c = text.charAt(i);
if (c != '_' && !Character.isLetter(c)) {
i++;
continue;
}
int end = i + 1;
while (end < len) {
final char n = text.charAt(end);
if (n != '_' && !Character.isLetterOrDigit(n)) break;
end++;
}
out.add(text.substring(i, end));
i = end;
}
}
public static String calleeName(GLSLParser.Postfix_expressionContext call) {
final ParseTree callee = call.getChild(0);
return callee instanceof ParserRuleContext ? callee.getText() : "";
}
public static List<GLSLParser.Typeless_declarationContext> declarators(GLSLParser.Init_declarator_listContext list) {
final GLSLParser.Single_declarationContext single = list.single_declaration();
final List<GLSLParser.Typeless_declarationContext> rest = list.typeless_declaration();
if (single == null || single.typeless_declaration() == null) return rest;
final List<GLSLParser.Typeless_declarationContext> all = new ArrayList<>(rest.size() + 1);
all.add(single.typeless_declaration());
all.addAll(rest);
return all;
}
public static boolean hasStorageQualifier(@Nullable GLSLParser.Type_qualifierContext qualifier, String keyword) {
if (qualifier == null) return false;
for (GLSLParser.Single_type_qualifierContext q : qualifier.single_type_qualifier()) {
if (q.storage_qualifier() != null && keyword.equals(q.storage_qualifier().getText())) return true;
}
return false;
}
}
@@ -47,14 +47,6 @@ public final class SpirvShaderTranslator {
private SpirvShaderTranslator() {}
public static @Nullable String glslToGlslEs(String source, int glShaderType, String debugName) {
return glslToGlslEsImpl(source, glShaderType, debugName, false);
}
static @Nullable String glslToGlslEsRawForTest(String source, int glShaderType, String debugName) {
return glslToGlslEsImpl(source, glShaderType, debugName, true);
}
private static @Nullable String glslToGlslEsImpl(String source, int glShaderType, String debugName, boolean rawOutput) {
final int shaderKind = shaderKindForGlType(glShaderType);
if (shaderKind < 0) {
LOGGER.warn("Unknown GL shader type 0x{} for '{}' - skipping SPIR-V translation", Integer.toHexString(glShaderType), debugName);
@@ -74,7 +66,7 @@ public final class SpirvShaderTranslator {
try {
final String emitted = emitGlslEs(cr.spirv(), debugName, glShaderType, pre.explicitVsInputs());
if (emitted == null) return null;
return rawOutput ? emitted : postProcessEsOutput(emitted, pre.boolUniforms());
return postProcessEsOutput(emitted, pre.boolUniforms());
} finally {
memFree(cr.spirv());
}
@@ -163,16 +155,7 @@ public final class SpirvShaderTranslator {
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
if (single == null || single.typeless_declaration() == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
if (fst == null || fst.type_qualifier() == null) return;
boolean isUniform = false;
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
if (stq.storage_qualifier() != null && "uniform".equals(stq.storage_qualifier().getText())) {
isUniform = true;
break;
}
}
if (!isUniform) return;
if (fst == null || !ShaderIndex.hasStorageQualifier(fst.type_qualifier(), "uniform")) return;
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
final GLSLParser.Type_specifier_nonarrayContext ts = fst.type_specifier().type_specifier_nonarray();
@@ -14,8 +14,8 @@ public final class AlphaStateStack extends AlphaState implements CowStateStack<A
public AlphaStateStack(int id) {
cow.id = id;
stack = new AlphaState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new AlphaState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new AlphaState();
}
}
@@ -25,6 +25,11 @@ public final class AlphaStateStack extends AlphaState implements CowStateStack<A
return cow;
}
@Override
public boolean slotChanged(int slot) {
return VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
@@ -17,8 +17,8 @@ public final class BlendStateStack extends BlendState implements CowStateStack<B
public BlendStateStack(int id) {
cow.id = id;
stack = new BlendState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new BlendState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new BlendState();
}
}
@@ -28,6 +28,11 @@ public final class BlendStateStack extends BlendState implements CowStateStack<B
return cow;
}
@Override
public boolean slotChanged(int slot) {
return funcUnknown || VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
@@ -44,7 +44,7 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
public BooleanStateStack(int glCap, boolean initialState, boolean ffpStateOnly) {
super(glCap, ffpStateOnly);
this.enabled = initialState;
stack = new boolean[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
stack = new boolean[GLStateManager.STATE_SLOTS];
}
@Override
@@ -52,6 +52,11 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
return cow;
}
@Override
public boolean slotChanged(int slot) {
return stateUnknown || (vanillaLayer != null && vanillaLayer.isOverrideHeld()) || enabled != stack[slot];
}
@Override
public void captureSlot(int slot) {
stack[slot] = vanillaValue();
@@ -10,8 +10,8 @@ public final class Color4Stack extends Color4 implements CowStateStack<Color4Sta
public Color4Stack(int id) {
cow.id = id;
stack = new Color4[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new Color4[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new Color4();
}
}
@@ -33,6 +33,11 @@ public final class Color4Stack extends Color4 implements CowStateStack<Color4Sta
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -15,8 +15,8 @@ public final class ColorMaskStack extends ColorMask implements CowStateStack<Col
public ColorMaskStack(int id) {
cow.id = id;
stack = new ColorMask[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new ColorMask[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new ColorMask();
}
}
@@ -26,6 +26,11 @@ public final class ColorMaskStack extends ColorMask implements CowStateStack<Col
return cow;
}
@Override
public boolean slotChanged(int slot) {
return VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
@@ -101,6 +101,36 @@ public final class CowDispatch {
};
}
public static boolean slotChanged(byte kind, CowStateStack<?> s, int slot) {
return switch (kind) {
case BLEND -> ((BlendStateStack) s).slotChanged(slot);
case BOOLEAN -> ((BooleanStateStack) s).slotChanged(slot);
case CLIP_PLANE_BOOLEAN -> ((ClipPlaneBooleanState) s).slotChanged(slot);
case TEXTURE_UNIT_BOOLEAN -> ((TextureUnitBooleanStateStack) s).slotChanged(slot);
case COLOR_MASK -> ((ColorMaskStack) s).slotChanged(slot);
case LIGHT_STATE -> ((LightStateStack) s).slotChanged(slot);
case MATERIAL -> ((MaterialStateStack) s).slotChanged(slot);
case STENCIL -> ((StencilStateStack) s).slotChanged(slot);
case MATRIX_MODE -> ((MatrixModeStack) s).slotChanged(slot);
case LIGHT_MODEL -> ((LightModelStateStack) s).slotChanged(slot);
case ALPHA -> ((AlphaStateStack) s).slotChanged(slot);
case POINT -> ((PointStateStack) s).slotChanged(slot);
case FOG -> ((FogStateStack) s).slotChanged(slot);
case VEC4F -> ((Vec4fStack) s).slotChanged(slot);
case VEC3F -> ((Vec3fStack) s).slotChanged(slot);
case INTEGER -> ((IntegerStateStack) s).slotChanged(slot);
case DEPTH -> ((DepthStateStack) s).slotChanged(slot);
case VIEWPORT -> ((ViewPortStateStack) s).slotChanged(slot);
case POLYGON -> ((PolygonStateStack) s).slotChanged(slot);
case COLOR4 -> ((Color4Stack) s).slotChanged(slot);
case LINE -> ((LineStateStack) s).slotChanged(slot);
case TEXTURE_BINDING -> ((TextureBindingStack) s).slotChanged(slot);
case TEX_ENV -> ((TexEnvState) s).slotChanged(slot);
case SCISSOR -> ((ScissorStateStack) s).slotChanged(slot);
default -> throw new IllegalStateException("Unknown kind: " + kind);
};
}
public static void restoreSlot(byte kind, CowStateStack<?> s, int slot) {
switch (kind) {
case BLEND -> ((BlendStateStack) s).restoreSlot(slot);
@@ -20,6 +20,8 @@ public interface CowStateStack<T> extends IStateStack<T> {
void restoreSlot(int slot);
boolean slotChanged(int slot);
default int stackId() {
return cowDepths().id;
}
@@ -17,8 +17,8 @@ public final class DepthStateStack extends DepthState implements CowStateStack<D
public DepthStateStack(int id) {
cow.id = id;
stack = new DepthState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new DepthState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new DepthState();
}
}
@@ -28,6 +28,11 @@ public final class DepthStateStack extends DepthState implements CowStateStack<D
return cow;
}
@Override
public boolean slotChanged(int slot) {
return VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
@@ -10,8 +10,8 @@ public final class FogStateStack extends FogState implements CowStateStack<FogSt
public FogStateStack(int id) {
cow.id = id;
stack = new FogState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new FogState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new FogState();
}
}
@@ -21,6 +21,11 @@ public final class FogStateStack extends FogState implements CowStateStack<FogSt
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class IntegerStateStack extends IntegerState implements CowStateSta
public IntegerStateStack(int val, int id) {
cow.id = id;
setValue(val);
stack = new IntegerState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new IntegerState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new IntegerState();
stack[i].setValue(val);
}
@@ -30,6 +30,11 @@ public final class IntegerStateStack extends IntegerState implements CowStateSta
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class LightModelStateStack extends LightModelState implements CowSt
public LightModelStateStack(int id) {
super();
cow.id = id;
stack = new LightModelState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new LightModelState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new LightModelState();
}
}
@@ -22,6 +22,11 @@ public final class LightModelStateStack extends LightModelState implements CowSt
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class LightStateStack extends LightState implements CowStateStack<L
public LightStateStack(int light, int id) {
super(light);
cow.id = id;
stack = new LightState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new LightState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new LightState(light);
}
}
@@ -22,6 +22,11 @@ public final class LightStateStack extends LightState implements CowStateStack<L
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class LineStateStack extends LineState implements CowStateStack<Lin
public LineStateStack(int id) {
cow.id = id;
stack = new LineState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new LineState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new LineState();
}
}
@@ -22,6 +22,11 @@ public final class LineStateStack extends LineState implements CowStateStack<Lin
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class MaterialStateStack extends MaterialState implements CowStateS
public MaterialStateStack(int face, int id) {
super(face);
cow.id = id;
stack = new MaterialState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new MaterialState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new MaterialState(face);
}
}
@@ -22,6 +22,11 @@ public final class MaterialStateStack extends MaterialState implements CowStateS
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -10,8 +10,8 @@ public final class MatrixModeStack extends MatrixMode implements CowStateStack<M
public MatrixModeStack(int id) {
cow.id = id;
stack = new MatrixMode[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new MatrixMode[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new MatrixMode();
}
}
@@ -21,6 +21,11 @@ public final class MatrixModeStack extends MatrixMode implements CowStateStack<M
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class PointStateStack extends PointState implements CowStateStack<P
public PointStateStack(int id) {
cow.id = id;
stack = new PointState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new PointState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new PointState();
}
}
@@ -22,6 +22,11 @@ public final class PointStateStack extends PointState implements CowStateStack<P
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class PolygonStateStack extends PolygonState implements CowStateSta
public PolygonStateStack(int id) {
cow.id = id;
stack = new PolygonState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new PolygonState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new PolygonState();
}
}
@@ -22,6 +22,11 @@ public final class PolygonStateStack extends PolygonState implements CowStateSta
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -0,0 +1,12 @@
package com.gtnewhorizons.angelica.glsm.stacks;
public final class RetainedState {
public int[] ids = new int[64];
public int count;
public int program;
public long programGen;
public void ensureCapacity(int capacity) {
if (ids.length < capacity) ids = new int[capacity];
}
}
@@ -11,8 +11,8 @@ public final class ScissorStateStack extends ScissorState implements CowStateSta
public ScissorStateStack(int id) {
cow.id = id;
stack = new ScissorState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new ScissorState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new ScissorState();
}
}
@@ -22,6 +22,11 @@ public final class ScissorStateStack extends ScissorState implements CowStateSta
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class StencilStateStack extends StencilState implements CowStateSta
public StencilStateStack(int id) {
cow.id = id;
stack = new StencilState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new StencilState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new StencilState();
}
}
@@ -22,6 +22,11 @@ public final class StencilStateStack extends StencilState implements CowStateSta
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -12,8 +12,8 @@ public final class TextureBindingStack extends TextureBinding implements CowStat
public TextureBindingStack(int id, int unit) {
cow = new CowDepths(StateSet.R_TEXTURE, unit);
cow.id = id;
stack = new TextureBinding[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new TextureBinding[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new TextureBinding();
}
}
@@ -23,6 +23,11 @@ public final class TextureBindingStack extends TextureBinding implements CowStat
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -15,7 +15,7 @@ public final class Vec3fStack implements CowStateStack<Vec3fStack> {
public Vec3fStack(Vector3f value, int id) {
this.value = value;
cow.id = id;
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][3];
stack = new float[GLStateManager.STATE_SLOTS][3];
}
@Override
@@ -23,6 +23,11 @@ public final class Vec3fStack implements CowStateStack<Vec3fStack> {
return cow;
}
@Override
public boolean slotChanged(int slot) {
return value.x != stack[slot][0] || value.y != stack[slot][1] || value.z != stack[slot][2];
}
@Override
public void captureSlot(int s) {
stack[s][0] = value.x;
@@ -15,7 +15,7 @@ public final class Vec4fStack implements CowStateStack<Vec4fStack> {
public Vec4fStack(Vector4f value, int id) {
this.value = value;
cow.id = id;
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][4];
stack = new float[GLStateManager.STATE_SLOTS][4];
}
@Override
@@ -23,6 +23,11 @@ public final class Vec4fStack implements CowStateStack<Vec4fStack> {
return cow;
}
@Override
public boolean slotChanged(int slot) {
return value.x != stack[slot][0] || value.y != stack[slot][1] || value.z != stack[slot][2] || value.w != stack[slot][3];
}
@Override
public void captureSlot(int s) {
stack[s][0] = value.x;
@@ -11,8 +11,8 @@ public final class ViewPortStateStack extends ViewportState implements CowStateS
public ViewPortStateStack(int id) {
cow.id = id;
stack = new ViewportState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new ViewportState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new ViewportState();
}
}
@@ -22,6 +22,11 @@ public final class ViewPortStateStack extends ViewportState implements CowStateS
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -0,0 +1,57 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public record PixelStoreState(
int alignment,
int rowLength,
int skipRows,
int skipPixels,
int imageHeight,
int skipImages,
boolean swapBytes,
boolean lsbFirst
) {
public static final PixelStoreState DEFAULT = new PixelStoreState(4, 0, 0, 0, 0, 0, false, false);
public boolean isDefault() {
return this.equals(DEFAULT);
}
public static boolean isPack(int pname) {
return switch (pname) {
case GL11.GL_PACK_ALIGNMENT, GL11.GL_PACK_ROW_LENGTH, GL11.GL_PACK_SKIP_ROWS, GL11.GL_PACK_SKIP_PIXELS,
GL12.GL_PACK_IMAGE_HEIGHT, GL12.GL_PACK_SKIP_IMAGES, GL11.GL_PACK_SWAP_BYTES, GL11.GL_PACK_LSB_FIRST -> true;
default -> false;
};
}
public PixelStoreState with(int pname, int param) {
return switch (pname) {
case GL11.GL_UNPACK_ALIGNMENT, GL11.GL_PACK_ALIGNMENT -> (param == alignment || (param != 1 && param != 2 && param != 4 && param != 8)) ? this : new PixelStoreState(param, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst);
case GL11.GL_UNPACK_ROW_LENGTH, GL11.GL_PACK_ROW_LENGTH -> (param == rowLength || param < 0) ? this : new PixelStoreState(alignment, param, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst);
case GL11.GL_UNPACK_SKIP_ROWS, GL11.GL_PACK_SKIP_ROWS -> (param == skipRows || param < 0) ? this : new PixelStoreState(alignment, rowLength, param, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst);
case GL11.GL_UNPACK_SKIP_PIXELS, GL11.GL_PACK_SKIP_PIXELS -> (param == skipPixels || param < 0) ? this : new PixelStoreState(alignment, rowLength, skipRows, param, imageHeight, skipImages, swapBytes, lsbFirst);
case GL12.GL_UNPACK_IMAGE_HEIGHT, GL12.GL_PACK_IMAGE_HEIGHT -> (param == imageHeight || param < 0) ? this : new PixelStoreState(alignment, rowLength, skipRows, skipPixels, param, skipImages, swapBytes, lsbFirst);
case GL12.GL_UNPACK_SKIP_IMAGES, GL12.GL_PACK_SKIP_IMAGES -> (param == skipImages || param < 0) ? this : new PixelStoreState(alignment, rowLength, skipRows, skipPixels, imageHeight, param, swapBytes, lsbFirst);
case GL11.GL_UNPACK_SWAP_BYTES, GL11.GL_PACK_SWAP_BYTES -> (param != 0) == swapBytes ? this : new PixelStoreState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, param != 0, lsbFirst);
case GL11.GL_UNPACK_LSB_FIRST, GL11.GL_PACK_LSB_FIRST -> (param != 0) == lsbFirst ? this : new PixelStoreState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, param != 0);
default -> this;
};
}
public static void applyDiff(PixelStoreState from, PixelStoreState to, boolean pack) {
if (from == to) return;
if (from.alignment != to.alignment) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_ALIGNMENT : GL11.GL_UNPACK_ALIGNMENT, to.alignment);
if (from.rowLength != to.rowLength) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_ROW_LENGTH : GL11.GL_UNPACK_ROW_LENGTH, to.rowLength);
if (from.skipRows != to.skipRows) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_SKIP_ROWS : GL11.GL_UNPACK_SKIP_ROWS, to.skipRows);
if (from.skipPixels != to.skipPixels) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_SKIP_PIXELS : GL11.GL_UNPACK_SKIP_PIXELS, to.skipPixels);
if (from.imageHeight != to.imageHeight) RENDER_BACKEND.pixelStorei(pack ? GL12.GL_PACK_IMAGE_HEIGHT : GL12.GL_UNPACK_IMAGE_HEIGHT, to.imageHeight);
if (from.skipImages != to.skipImages) RENDER_BACKEND.pixelStorei(pack ? GL12.GL_PACK_SKIP_IMAGES : GL12.GL_UNPACK_SKIP_IMAGES, to.skipImages);
if (from.swapBytes != to.swapBytes) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_SWAP_BYTES : GL11.GL_UNPACK_SWAP_BYTES, to.swapBytes ? 1 : 0);
if (from.lsbFirst != to.lsbFirst) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_LSB_FIRST : GL11.GL_UNPACK_LSB_FIRST, to.lsbFirst ? 1 : 0);
}
}
@@ -1,51 +0,0 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Immutable client-side pixel unpack state (glPixelStore UNPACK_* params)
*/
public record PixelUnpackState(
int alignment,
int rowLength,
int skipRows,
int skipPixels,
int imageHeight,
int skipImages,
boolean swapBytes,
boolean lsbFirst
) {
public static final PixelUnpackState DEFAULT = new PixelUnpackState(4, 0, 0, 0, 0, 0, false, false);
public boolean isDefault() {
return this.equals(DEFAULT);
}
public PixelUnpackState with(int pname, int param) {
return switch (pname) {
case GL11.GL_UNPACK_ALIGNMENT -> (param == 1 || param == 2 || param == 4 || param == 8) ? new PixelUnpackState(param, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_ROW_LENGTH -> param >= 0 ? new PixelUnpackState(alignment, param, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_ROWS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, param, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_PIXELS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, param, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_IMAGE_HEIGHT -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, param, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_SKIP_IMAGES -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, param, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SWAP_BYTES -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, param != 0, lsbFirst);
case GL11.GL_UNPACK_LSB_FIRST -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, param != 0);
default -> this;
};
}
public static void applyDiff(PixelUnpackState from, PixelUnpackState to) {
if (from.alignment != to.alignment) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ALIGNMENT, to.alignment);
if (from.rowLength != to.rowLength) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ROW_LENGTH, to.rowLength);
if (from.skipRows != to.skipRows) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_ROWS, to.skipRows);
if (from.skipPixels != to.skipPixels) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, to.skipPixels);
if (from.imageHeight != to.imageHeight) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_IMAGE_HEIGHT, to.imageHeight);
if (from.skipImages != to.skipImages) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_SKIP_IMAGES, to.skipImages);
if (from.swapBytes != to.swapBytes) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SWAP_BYTES, to.swapBytes ? 1 : 0);
if (from.lsbFirst != to.lsbFirst) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_LSB_FIRST, to.lsbFirst ? 1 : 0);
}
}
@@ -6,6 +6,8 @@ import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.Arrays;
/**
* Per-texture-unit GL_TEXTURE_ENV state for FFP emulation. Tracks the simple texenv mode and all GL_COMBINE sub-parameters.
*/
@@ -45,8 +47,8 @@ public final class TexEnvState implements CowStateStack<TexEnvState> {
stack = null;
cow = null;
} else {
stack = new TexEnvState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new TexEnvState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new TexEnvState(true);
}
cow = new CowDepths();
@@ -70,6 +72,14 @@ public final class TexEnvState implements CowStateStack<TexEnvState> {
this.envColorA = other.envColorA;
}
private boolean sameAs(TexEnvState other) {
return mode == other.mode && combineRgb == other.combineRgb && combineAlpha == other.combineAlpha
&& Arrays.equals(sourceRgb, other.sourceRgb) && Arrays.equals(sourceAlpha, other.sourceAlpha)
&& Arrays.equals(operandRgb, other.operandRgb) && Arrays.equals(operandAlpha, other.operandAlpha)
&& scaleRgb == other.scaleRgb && scaleAlpha == other.scaleAlpha
&& envColorR == other.envColorR && envColorG == other.envColorG && envColorB == other.envColorB && envColorA == other.envColorA;
}
public void reset() {
mode = GL11.GL_MODULATE;
combineRgb = GL11.GL_MODULATE;
@@ -89,6 +99,11 @@ public final class TexEnvState implements CowStateStack<TexEnvState> {
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].copyFrom(this);
@@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
@@ -16,6 +17,7 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
public final class AngelicaWorkers {
@@ -153,6 +155,51 @@ public final class AngelicaWorkers {
}
}
/**
* Runs every task and returns the results in task order.
*/
public static <T> List<T> invokeAll(List<? extends Supplier<? extends T>> tasks) {
final int count = tasks.size();
final Object[] results = new Object[count];
if (count == 0) return new ArrayList<>();
final AtomicInteger next = new AtomicInteger();
final CountDownLatch finished = new CountDownLatch(count);
final AtomicReference<Throwable> failure = new AtomicReference<>();
final Runnable drain = () -> {
int i;
while ((i = next.getAndIncrement()) < count) {
try {
results[i] = tasks.get(i).get();
} catch (Throwable t) {
failure.compareAndSet(null, t);
} finally {
finished.countDown();
}
}
};
final int helpers = Math.min(threads(), count - 1);
for (int h = 0; h < helpers; h++) {
run(drain);
}
drain.run();
try {
finished.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted waiting for worker tasks", e);
}
final Throwable t = failure.get();
if (t instanceof RuntimeException re) throw re;
if (t instanceof Error err) throw err;
if (t != null) throw new IllegalStateException(t);
final List<T> out = new ArrayList<>(count);
for (Object result : results) {
@SuppressWarnings("unchecked") final T typed = (T) result;
out.add(typed);
}
return out;
}
private static void checkIdleShutdown() {
synchronized (lock) {
final ExecutorService current = executor;
@@ -0,0 +1,83 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
public class GLSM_HudCacheBlendOverride_GLTest {
private static void assertDriverFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
assertEquals(srcRgb, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB));
assertEquals(dstRgb, GL11.glGetInteger(GL14.GL_BLEND_DST_RGB));
assertEquals(srcAlpha, GL11.glGetInteger(GL14.GL_BLEND_SRC_ALPHA));
assertEquals(dstAlpha, GL11.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
}
private static void assertCachedFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
assertEquals(srcRgb, GLStateManager.glGetInteger(GL14.GL_BLEND_SRC_RGB));
assertEquals(dstRgb, GLStateManager.glGetInteger(GL14.GL_BLEND_DST_RGB));
assertEquals(srcAlpha, GLStateManager.glGetInteger(GL14.GL_BLEND_SRC_ALPHA));
assertEquals(dstAlpha, GLStateManager.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
}
@Test
void overrideChangesTheDriverFuncButNotTheCache() {
try {
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.setHudCacheOverride(true);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_SRC_ALPHA, GL11.GL_ONE);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
assertCachedFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
assertDriverFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLStateManager.setHudCacheOverride(false);
assertCachedFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
assertDriverFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
} finally {
GLStateManager.setHudCacheOverride(false);
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void popUnderOverrideKeepsTheOverriddenAlphaOnTheDriver() {
try {
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
final int d = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.setHudCacheOverride(true);
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
GLStateManager.popStateTo(d);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLStateManager.setHudCacheOverride(false);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
} finally {
GLStateManager.setHudCacheOverride(false);
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void separateAlphaIsForcedEvenWhenDstAlphaAlreadyMatches() {
try {
GLStateManager.setHudCacheOverride(true);
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_ALPHA);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_ALPHA);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
} finally {
GLStateManager.setHudCacheOverride(false);
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
}
}
}
@@ -0,0 +1,737 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.ffp.VertexKey;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
public class GLSM_LegacyStackQueries_GLTest {
private static final int[] SINGLE_VALUE_QUERIES = {
GL11.GL_ATTRIB_STACK_DEPTH,
GL11.GL_CLIENT_ATTRIB_STACK_DEPTH,
GL11.GL_TEXTURE_STACK_DEPTH,
GL11.GL_MAX_ATTRIB_STACK_DEPTH,
GL11.GL_MAX_CLIENT_ATTRIB_STACK_DEPTH,
GL11.GL_MAX_MODELVIEW_STACK_DEPTH,
GL11.GL_MAX_PROJECTION_STACK_DEPTH,
GL11.GL_MAX_TEXTURE_STACK_DEPTH,
GL13.GL_MAX_TEXTURE_UNITS,
GL11.GL_MAX_LIGHTS,
GL11.GL_STENCIL_BITS,
GL11.GL_DEPTH_BITS,
};
private static boolean positionArrayEnabled() {
final VAOManager.Attrib position = GLStateManager.ctx().vaos.attrib(Usage.POSITION.getAttributeLocation());
return position != null && position.enabled;
}
private static boolean colorArrayEnabled() {
return (GLStateManager.ctx().vaos.getVertexFlags() & VertexFlags.COLOR_BIT) != 0;
}
private static void drainErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private static void drainClientAttribStack() {
while (GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH) > 0) {
GLStateManager.glPopClientAttrib();
}
}
private static void drainAttribStack() {
while (GLStateManager.getAttribDepth() > 0) {
GLStateManager.glPopAttrib();
}
}
private static int pushToLimit(int depthPname, int maxPname) {
final int max = GLStateManager.glGetInteger(maxPname);
for (int i = 0; i < max + 4; i++) {
GLStateManager.glPushMatrix();
}
return GLStateManager.glGetInteger(depthPname);
}
private static void popToBase(int depthPname) {
for (int i = 0; i < 128 && GLStateManager.glGetInteger(depthPname) > 1; i++) {
GLStateManager.glPopMatrix();
}
}
@Test
void attribDepthTracksPushAndPop() {
final int base = GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH);
try {
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
assertEquals(base + 1, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
assertEquals(base + 2, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
GLStateManager.glPopAttrib();
assertEquals(base + 1, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
GLStateManager.glPopAttrib();
assertEquals(base, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
assertEquals(GLStateManager.MAX_ATTRIB_STACK_DEPTH, GLStateManager.glGetInteger(GL11.GL_MAX_ATTRIB_STACK_DEPTH));
} finally {
while (GLStateManager.getAttribDepth() > base) {
GLStateManager.glPopAttrib();
}
}
}
@Test
void clientAttribDepthTracksPushAndPop() {
try {
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
assertEquals(2, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
GLStateManager.glPopClientAttrib();
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
GLStateManager.glPopClientAttrib();
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
GLStateManager.glPopClientAttrib();
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
final int max = GLStateManager.glGetInteger(GL11.GL_MAX_CLIENT_ATTRIB_STACK_DEPTH);
for (int i = 0; i < max + 2; i++) {
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
}
assertEquals(max, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
} finally {
drainClientAttribStack();
}
}
@Test
void textureStackDepthIsPerActiveUnit() {
try {
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glPushMatrix();
assertEquals(2, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertEquals(2, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
GLStateManager.glPopMatrix();
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
popToBase(GL11.GL_TEXTURE_STACK_DEPTH);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
}
@Test
void maxModelviewStackDepthIsExactlyReachable() {
try {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH));
assertEquals(GLStateManager.glGetInteger(GL11.GL_MAX_MODELVIEW_STACK_DEPTH), pushToLimit(GL11.GL_MODELVIEW_STACK_DEPTH, GL11.GL_MAX_MODELVIEW_STACK_DEPTH));
} finally {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
popToBase(GL11.GL_MODELVIEW_STACK_DEPTH);
}
}
@Test
void maxProjectionStackDepthIsExactlyReachable() {
try {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_PROJECTION_STACK_DEPTH));
assertEquals(GLStateManager.glGetInteger(GL11.GL_MAX_PROJECTION_STACK_DEPTH), pushToLimit(GL11.GL_PROJECTION_STACK_DEPTH, GL11.GL_MAX_PROJECTION_STACK_DEPTH));
} finally {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
popToBase(GL11.GL_PROJECTION_STACK_DEPTH);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
}
@Test
void maxTextureStackDepthIsExactlyReachable() {
try {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
assertEquals(GLStateManager.glGetInteger(GL11.GL_MAX_TEXTURE_STACK_DEPTH), pushToLimit(GL11.GL_TEXTURE_STACK_DEPTH, GL11.GL_MAX_TEXTURE_STACK_DEPTH));
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
popToBase(GL11.GL_TEXTURE_STACK_DEPTH);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
}
@Test
void constantQueriesReportEmulatedLimits() {
assertEquals(Math.min(VertexKey.MAX_UNITS, GLStateManager.MAX_TEXTURE_UNITS), GLStateManager.glGetInteger(GL13.GL_MAX_TEXTURE_UNITS));
assertEquals(8, GLStateManager.glGetInteger(GL11.GL_MAX_LIGHTS));
}
@Test
void stencilAndDepthBitsMatchDefaultFramebuffer() {
final GLContextState glCtx = GLStateManager.ctx();
final int savedDraw = glCtx.drawFramebuffer;
final int savedRead = glCtx.readFramebuffer;
try {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
drainErrors();
assertEquals(
GL30.glGetFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE),
GLStateManager.glGetInteger(GL11.GL_STENCIL_BITS));
assertEquals(
GL30.glGetFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE),
GLStateManager.glGetInteger(GL11.GL_DEPTH_BITS));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, savedDraw);
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, savedRead);
}
}
@Test
void stencilAndDepthBitsFollowBoundDrawFramebuffer() {
final GLContextState glCtx = GLStateManager.ctx();
final int savedDraw = glCtx.drawFramebuffer;
final int savedRead = glCtx.readFramebuffer;
final int fbo = GLStateManager.glGenFramebuffers();
final int depthOnly = GLStateManager.glGenRenderbuffers();
final int depthStencil = GLStateManager.glGenRenderbuffers();
try {
drainErrors();
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthOnly);
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL14.GL_DEPTH_COMPONENT24, 16, 16);
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthStencil);
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, 16, 16);
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, 0);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_STENCIL_BITS));
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_DEPTH_BITS));
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, depthOnly);
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_STENCIL_BITS));
final int depthOnlyBits = GL30.glGetFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE);
assertTrue(depthOnlyBits >= 24, "driver depth size " + depthOnlyBits);
assertEquals(depthOnlyBits, GLStateManager.glGetInteger(GL11.GL_DEPTH_BITS));
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, depthStencil);
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depthStencil);
assertEquals(8, GLStateManager.glGetInteger(GL11.GL_STENCIL_BITS));
assertEquals(24, GLStateManager.glGetInteger(GL11.GL_DEPTH_BITS));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, savedDraw);
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, savedRead);
GLStateManager.glDeleteFramebuffers(fbo);
GLStateManager.glDeleteRenderbuffers(depthOnly);
GLStateManager.glDeleteRenderbuffers(depthStencil);
}
}
@Test
void intBufferOverloadMatchesAndDriverIsNotAsked() {
final int base = GLStateManager.getAttribDepth();
final IntBuffer buf = BufferUtils.createIntBuffer(16);
try {
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
drainErrors();
GL11.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH);
assumeTrue(GL11.glGetError() != GL11.GL_NO_ERROR, "driver accepts GL_ATTRIB_STACK_DEPTH, so a clean error state proves nothing");
for (final int pname : SINGLE_VALUE_QUERIES) {
final int direct = GLStateManager.glGetInteger(pname);
buf.put(0, -12345);
GLStateManager.glGetInteger(pname, buf);
assertEquals(direct, buf.get(0), "IntBuffer overload for 0x" + Integer.toHexString(pname));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "driver error after querying 0x" + Integer.toHexString(pname));
}
} finally {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
drainClientAttribStack();
while (GLStateManager.getAttribDepth() > base) {
GLStateManager.glPopAttrib();
}
}
}
@Test
void attribDepthUnchangedWhileCompilingDisplayList() {
final int base = GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH);
final int list = GLStateManager.glGenLists(1);
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
assertEquals(base, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
GLStateManager.glPopAttrib();
assertEquals(base, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
GLStateManager.glEndList();
assertEquals(base, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
} finally {
if (DisplayListManager.isRecording()) {
GLStateManager.glEndList();
}
GLStateManager.glDeleteLists(list, 1);
}
}
@Test
void popAttribOnEmptyStackIsIgnored() {
drainAttribStack();
drainErrors();
assertEquals(0, GLStateManager.getAttribDepth());
assertDoesNotThrow(GLStateManager::glPopAttrib);
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
assertDoesNotThrow(GLStateManager::glPopAttrib);
assertEquals(0, GLStateManager.getAttribDepth());
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void pixelStoreBitRestoresPixelStoreAndPboBindings() {
final GLContextState glCtx = GLStateManager.ctx();
final int unpackBuffer = GLStateManager.glGenBuffers();
final int packBuffer = GLStateManager.glGenBuffers();
try {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 0);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
final PixelStoreState unpackBefore = glCtx.pixelUnpackState;
final PixelStoreState packBefore = glCtx.pixelPackState;
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 2);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 7);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, unpackBuffer);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, packBuffer);
assertEquals(1, glCtx.pixelUnpackState.alignment());
assertEquals(2, glCtx.pixelPackState.alignment());
assertEquals(7, glCtx.pixelPackState.rowLength());
assertEquals(2, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
GLStateManager.glPopClientAttrib();
assertSame(unpackBefore, glCtx.pixelUnpackState);
assertSame(packBefore, glCtx.pixelPackState);
assertEquals(4, GLStateManager.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
assertEquals(0, GLStateManager.glGetInteger(GL21.GL_PIXEL_UNPACK_BUFFER_BINDING));
assertEquals(0, GLStateManager.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
assertEquals(4, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
assertEquals(4, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
assertEquals(0, GL11.glGetInteger(GL11.GL_PACK_ROW_LENGTH));
assertEquals(0, GL11.glGetInteger(GL21.GL_PIXEL_UNPACK_BUFFER_BINDING));
assertEquals(0, GL11.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
drainClientAttribStack();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 0);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
GLStateManager.glDeleteBuffers(unpackBuffer);
GLStateManager.glDeleteBuffers(packBuffer);
}
}
@Test
void pixelStoreIsNotRestoredWithoutPixelStoreBit() {
final GLContextState glCtx = GLStateManager.ctx();
final int unpackBuffer = GLStateManager.glGenBuffers();
final int packBuffer = GLStateManager.glGenBuffers();
try {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 2);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, unpackBuffer);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, packBuffer);
GLStateManager.glPopClientAttrib();
assertEquals(1, glCtx.pixelUnpackState.alignment());
assertEquals(2, glCtx.pixelPackState.alignment());
assertEquals(unpackBuffer, GLStateManager.glGetInteger(GL21.GL_PIXEL_UNPACK_BUFFER_BINDING));
assertEquals(packBuffer, GLStateManager.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
assertEquals(1, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
assertEquals(2, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
assertEquals(unpackBuffer, GL11.glGetInteger(GL21.GL_PIXEL_UNPACK_BUFFER_BINDING));
assertEquals(packBuffer, GL11.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
} finally {
drainClientAttribStack();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
GLStateManager.glDeleteBuffers(unpackBuffer);
GLStateManager.glDeleteBuffers(packBuffer);
}
}
@Test
void pixelStoreWithReturnsSameInstanceForUnchangedValue() {
final GLContextState glCtx = GLStateManager.ctx();
try {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 9);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 11);
final PixelStoreState unpack = glCtx.pixelUnpackState;
final PixelStoreState pack = glCtx.pixelPackState;
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 9);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, unpack.alignment());
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SWAP_BYTES, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 11);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, pack.alignment());
GLStateManager.glPixelStorei(GL11.GL_PACK_LSB_FIRST, 0);
assertSame(unpack, glCtx.pixelUnpackState);
assertSame(pack, glCtx.pixelPackState);
assertSame(unpack, unpack.with(GL11.GL_UNPACK_ROW_LENGTH, 9));
assertSame(pack, pack.with(GL11.GL_PACK_ROW_LENGTH, 11));
} finally {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 0);
}
}
@Test
void packAndUnpackRestoreToTheirOwnPriorValues() {
final GLContextState glCtx = GLStateManager.ctx();
try {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 2);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 8);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 3);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 5);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 6);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 7);
assertEquals(1, glCtx.pixelUnpackState.alignment());
assertEquals(4, glCtx.pixelPackState.alignment());
assertEquals(6, glCtx.pixelUnpackState.skipRows());
assertEquals(7, glCtx.pixelPackState.skipRows());
assertEquals(6, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
assertEquals(7, GL11.glGetInteger(GL11.GL_PACK_SKIP_ROWS));
GLStateManager.glPopClientAttrib();
assertEquals(2, glCtx.pixelUnpackState.alignment());
assertEquals(8, glCtx.pixelPackState.alignment());
assertEquals(3, glCtx.pixelUnpackState.skipRows());
assertEquals(5, glCtx.pixelPackState.skipRows());
assertEquals(2, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
assertEquals(8, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
assertEquals(3, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
assertEquals(5, GL11.glGetInteger(GL11.GL_PACK_SKIP_ROWS));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
drainClientAttribStack();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 0);
}
}
@Test
void vertexArrayEnableRestoredOnlyWithVertexArrayBit() {
final int savedVao = GLStateManager.getBoundVAO();
final int vao = GLStateManager.glGenVertexArrays();
final IntBuffer enabled = BufferUtils.createIntBuffer(16);
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glBindVertexArray(vao);
drainErrors();
assertFalse(positionArrayEnabled());
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
assertTrue(positionArrayEnabled());
GLStateManager.glPopClientAttrib();
assertFalse(positionArrayEnabled());
GL20.glGetVertexAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED, enabled);
assertEquals(GL11.GL_FALSE, enabled.get(0));
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPopClientAttrib();
assertTrue(positionArrayEnabled());
GL20.glGetVertexAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED, enabled);
assertEquals(GL11.GL_TRUE, enabled.get(0));
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPopClientAttrib();
assertTrue(positionArrayEnabled());
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
drainClientAttribStack();
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindVertexArray(savedVao);
GLStateManager.glDeleteVertexArrays(vao);
}
}
@Test
void vertexArrayStateRestoredOnlyWithVertexArrayBit() {
final int savedVao = GLStateManager.getBoundVAO();
final int vao = GLStateManager.glGenVertexArrays();
try {
GLStateManager.glBindVertexArray(vao);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
final int flagsBefore = GLStateManager.ctx().vaos.getVertexFlags();
assertFalse(colorArrayEnabled());
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
assertTrue(colorArrayEnabled());
GLStateManager.glPopClientAttrib();
assertFalse(colorArrayEnabled());
assertEquals(flagsBefore, GLStateManager.ctx().vaos.getVertexFlags());
assertEquals(0, GLStateManager.getClientActiveTextureUnit());
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glPopClientAttrib();
assertTrue(colorArrayEnabled());
assertEquals(1, GLStateManager.getClientActiveTextureUnit());
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
drainClientAttribStack();
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glBindVertexArray(savedVao);
GLStateManager.glDeleteVertexArrays(vao);
}
}
@Test
void intLightGettersConvertLikeMesa() {
final FloatBuffer savedDiffuse = BufferUtils.createFloatBuffer(16);
final FloatBuffer savedPosition = BufferUtils.createFloatBuffer(16);
final FloatBuffer savedDirection = BufferUtils.createFloatBuffer(16);
final FloatBuffer savedCutoff = BufferUtils.createFloatBuffer(16);
final FloatBuffer savedAttenuation = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, savedDiffuse);
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_POSITION, savedPosition);
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_SPOT_DIRECTION, savedDirection);
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_SPOT_CUTOFF, savedCutoff);
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_LINEAR_ATTENUATION, savedAttenuation);
final IntBuffer ints = BufferUtils.createIntBuffer(16);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
try {
GLStateManager.glLoadIdentity();
final FloatBuffer values = BufferUtils.createFloatBuffer(4);
values.put(0, 0.25f).put(1, 0.5f).put(2, 1.0f).put(3, -0.5f);
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, values);
values.put(0, 3.9f).put(1, -2.7f).put(2, 0.5f).put(3, 1.0f);
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_POSITION, values);
values.put(0, 2.5f).put(1, -1.5f).put(2, -3.9f).put(3, 0.0f);
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_SPOT_DIRECTION, values);
GLStateManager.glLightf(GL11.GL_LIGHT1, GL11.GL_SPOT_CUTOFF, 45.75f);
GLStateManager.glLightf(GL11.GL_LIGHT1, GL11.GL_LINEAR_ATTENUATION, 2.5f);
drainErrors();
ints.position(2);
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, ints);
assertEquals(2, ints.position());
assertEquals(536870911, ints.get(2));
assertEquals(1073741823, ints.get(3));
assertEquals(2147483647, ints.get(4));
assertEquals(-1073741823, ints.get(5));
ints.position(0);
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_POSITION, ints);
assertEquals(3, ints.get(0));
assertEquals(-2, ints.get(1));
assertEquals(0, ints.get(2));
assertEquals(1, ints.get(3));
ints.put(3, -777);
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_SPOT_DIRECTION, ints);
assertEquals(2, ints.get(0));
assertEquals(-1, ints.get(1));
assertEquals(-3, ints.get(2));
assertEquals(-777, ints.get(3));
ints.put(1, -777);
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_SPOT_CUTOFF, ints);
assertEquals(45, ints.get(0));
assertEquals(-777, ints.get(1));
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_LINEAR_ATTENUATION, ints);
assertEquals(2, ints.get(0));
ints.put(0, -777);
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_EMISSION, ints);
assertEquals(-777, ints.get(0));
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_SHININESS, ints);
assertEquals(-777, ints.get(0));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glLoadIdentity();
savedDiffuse.limit(4);
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, savedDiffuse);
savedPosition.limit(4);
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_POSITION, savedPosition);
savedDirection.limit(4);
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_SPOT_DIRECTION, savedDirection);
GLStateManager.glLightf(GL11.GL_LIGHT1, GL11.GL_SPOT_CUTOFF, savedCutoff.get(0));
GLStateManager.glLightf(GL11.GL_LIGHT1, GL11.GL_LINEAR_ATTENUATION, savedAttenuation.get(0));
GLStateManager.glPopMatrix();
}
}
@Test
void intMaterialGettersConvertLikeMesa() {
final FloatBuffer savedSpecular = BufferUtils.createFloatBuffer(16);
final FloatBuffer savedShininess = BufferUtils.createFloatBuffer(16);
final FloatBuffer savedIndexes = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, savedSpecular);
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SHININESS, savedShininess);
GLStateManager.glGetMaterial(GL11.GL_BACK, GL11.GL_COLOR_INDEXES, savedIndexes);
final IntBuffer ints = BufferUtils.createIntBuffer(16);
try {
final FloatBuffer values = BufferUtils.createFloatBuffer(4);
values.put(0, 0.5f).put(1, 0.75f).put(2, 1.0f).put(3, 0.0f);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, values);
GLStateManager.glMaterialf(GL11.GL_FRONT, GL11.GL_SHININESS, 12.6f);
values.put(0, 2.5f).put(1, -2.5f).put(2, 7.4f).put(3, 0.0f);
GLStateManager.glMaterial(GL11.GL_BACK, GL11.GL_COLOR_INDEXES, values);
drainErrors();
ints.position(1);
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, ints);
assertEquals(1, ints.position());
assertEquals(1073741823, ints.get(1));
assertEquals(1610612735, ints.get(2));
assertEquals(2147483647, ints.get(3));
assertEquals(0, ints.get(4));
ints.position(0);
ints.put(1, -777);
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SHININESS, ints);
assertEquals(13, ints.get(0));
assertEquals(-777, ints.get(1));
ints.put(3, -777);
GLStateManager.glGetMaterial(GL11.GL_BACK, GL11.GL_COLOR_INDEXES, ints);
assertEquals(3, ints.get(0));
assertEquals(-3, ints.get(1));
assertEquals(7, ints.get(2));
assertEquals(-777, ints.get(3));
ints.put(0, -777);
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_POSITION, ints);
assertEquals(-777, ints.get(0));
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SPOT_CUTOFF, ints);
assertEquals(-777, ints.get(0));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
savedSpecular.limit(4);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, savedSpecular);
GLStateManager.glMaterialf(GL11.GL_FRONT, GL11.GL_SHININESS, savedShininess.get(0));
savedIndexes.limit(4);
GLStateManager.glMaterial(GL11.GL_BACK, GL11.GL_COLOR_INDEXES, savedIndexes);
}
}
@Test
void currentTextureCoordsFollowActiveUnit() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(16);
final float[][] saved = new float[4][4];
try {
for (int unit = 0; unit < 4; unit++) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glGetFloat(GL11.GL_CURRENT_TEXTURE_COORDS, buf);
for (int i = 0; i < 4; i++) {
saved[unit][i] = buf.get(i);
}
}
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.25f, 0.5f);
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE1, 240.0f, 16.0f);
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE2, 0.75f, 0.125f);
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE3, 0.375f, 0.625f);
drainErrors();
final float[][] expected = { { 0.25f, 0.5f }, { 240.0f, 16.0f }, { 0.75f, 0.125f }, { 0.375f, 0.625f } };
for (int unit = 0; unit < 4; unit++) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glGetFloat(GL11.GL_CURRENT_TEXTURE_COORDS, buf);
assertEquals(expected[unit][0], buf.get(0), "s of unit " + unit);
assertEquals(expected[unit][1], buf.get(1), "t of unit " + unit);
assertEquals(0.0f, buf.get(2), "r of unit " + unit);
assertEquals(1.0f, buf.get(3), "q of unit " + unit);
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
for (int unit = 0; unit < 4; unit++) {
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0 + unit, saved[unit][0], saved[unit][1]);
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
}
}
@Test
void currentTextureCoordsOfUntrackedUnitAreInitialValue() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(16);
final int unit = GLStateManager.ctx().ffp.currentTexCoords.length + 1;
try {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.25f, 0.5f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
drainErrors();
for (int i = 0; i < 4; i++) {
buf.put(i, -7.0f);
}
GLStateManager.glGetFloat(GL11.GL_CURRENT_TEXTURE_COORDS, buf);
assertEquals(0.0f, buf.get(0));
assertEquals(0.0f, buf.get(1));
assertEquals(0.0f, buf.get(2));
assertEquals(1.0f, buf.get(3));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.0f, 0.0f);
}
}
}
@@ -0,0 +1,75 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.SharedDrawable;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@GLCoreTest
public class GLSM_OffContextCreation_GLTest {
private SharedDrawable sharedDrawable;
@AfterEach
void cleanup() {
if (sharedDrawable != null) {
DrawableContexts.destroy(sharedDrawable);
sharedDrawable = null;
}
}
private static Map<String, Executable> creationCalls() {
final Map<String, Executable> calls = new LinkedHashMap<>();
calls.put("glGenTextures()", GLStateManager::glGenTextures);
calls.put("glGenTextures(IntBuffer)", () -> GLStateManager.glGenTextures(BufferUtils.createIntBuffer(1)));
calls.put("glGenSamplers()", GLStateManager::glGenSamplers);
calls.put("glGenSamplers(IntBuffer)", () -> GLStateManager.glGenSamplers(BufferUtils.createIntBuffer(1)));
calls.put("glGenSamplers(int[])", () -> GLStateManager.glGenSamplers(new int[1]));
calls.put("glGenBuffers()", GLStateManager::glGenBuffers);
calls.put("glGenBuffers(IntBuffer)", () -> GLStateManager.glGenBuffers(BufferUtils.createIntBuffer(1)));
calls.put("glGenBuffers(int[])", () -> GLStateManager.glGenBuffers(new int[1]));
calls.put("glCreateBuffers()", GLStateManager::glCreateBuffers);
calls.put("glCreateBuffers(IntBuffer)", () -> GLStateManager.glCreateBuffers(BufferUtils.createIntBuffer(1)));
calls.put("glCreateBuffers(int[])", () -> GLStateManager.glCreateBuffers(new int[1]));
calls.put("glGenVertexArrays()", GLStateManager::glGenVertexArrays);
calls.put("glGenFramebuffers()", GLStateManager::glGenFramebuffers);
calls.put("glGenRenderbuffers()", GLStateManager::glGenRenderbuffers);
calls.put("glGenQueries()", GLStateManager::glGenQueries);
calls.put("glGenQueries(IntBuffer)", () -> GLStateManager.glGenQueries(BufferUtils.createIntBuffer(1)));
calls.put("glCreateShader", () -> GLStateManager.glCreateShader(GL20.GL_VERTEX_SHADER));
calls.put("glCreateProgram", GLStateManager::glCreateProgram);
calls.put("RenderSystem.createTexture", () -> RenderSystem.createTexture(GL11.GL_TEXTURE_2D));
calls.put("RenderSystem.createFramebuffer", RenderSystem::createFramebuffer);
return calls;
}
@Test
void creationWithoutContextThrows() throws InterruptedException {
TestThreads.run("glsm-no-context", () -> {
for (Map.Entry<String, Executable> call : creationCalls().entrySet()) {
assertThrows(IllegalStateException.class, call.getValue(), call.getKey() + " did not throw off-context");
}
});
}
@Test
void creationOnSharedContextThreadPasses() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
DrawableContexts.run(sharedDrawable, "glsm-shared-context", () -> {
final int texture = GLStateManager.glGenTextures();
assertNotEquals(0, texture, "no texture name on a thread that owns a shared context");
GLStateManager.glDeleteTextures(texture);
});
}
}
@@ -0,0 +1,256 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.RetainedState;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
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
public class GLSM_RetainedState_GLTest {
private static final StateSet ALL = StateSet.forMask(GL11.GL_ALL_ATTRIB_BITS);
private static int driverTextureBinding(int unit) {
final int saved = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
final int binding = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GL13.glActiveTexture(saved);
return binding;
}
private static void baseline() {
GLStateManager.enableCull();
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glDepthMask(true);
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
private static void mutate(int texture) {
GLStateManager.disableCull();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ONE);
GLStateManager.glDepthMask(false);
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
GLStateManager.glAlphaFunc(GL11.GL_GEQUAL, 0.5F);
GLStateManager.glColor4f(0.25F, 0.5F, 0.75F, 0.5F);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
}
private static void assertBaseline() {
assertTrue(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE));
assertTrue(GL11.glIsEnabled(GL11.GL_CULL_FACE));
assertFalse(GLStateManager.glIsEnabled(GL11.GL_BLEND));
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND));
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB));
assertEquals(GL11.GL_ZERO, GL11.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
assertTrue(GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK));
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC));
assertEquals(GL11.GL_GREATER, GLStateManager.glGetInteger(GL11.GL_ALPHA_TEST_FUNC));
assertEquals(1.0F, GLStateManager.getColor().getRed(), 0.0F);
assertEquals(0, driverTextureBinding(0));
}
private static void assertMutated(int texture) {
assertFalse(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE));
assertFalse(GL11.glIsEnabled(GL11.GL_CULL_FACE));
assertTrue(GLStateManager.glIsEnabled(GL11.GL_BLEND));
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND));
assertEquals(GL11.GL_DST_COLOR, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB));
assertEquals(GL11.GL_ZERO, GL11.glGetInteger(GL14.GL_BLEND_DST_RGB));
assertEquals(GL11.GL_ONE, GL11.glGetInteger(GL14.GL_BLEND_SRC_ALPHA));
assertEquals(GL11.GL_ONE, GL11.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
assertEquals(GL11.GL_DST_COLOR, GLStateManager.glGetInteger(GL14.GL_BLEND_SRC_RGB));
assertFalse(GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK));
assertEquals(GL11.GL_ALWAYS, GL11.glGetInteger(GL11.GL_DEPTH_FUNC));
assertEquals(GL11.GL_GEQUAL, GLStateManager.glGetInteger(GL11.GL_ALPHA_TEST_FUNC));
assertEquals(0.25F, GLStateManager.getColor().getRed(), 0.0F);
assertEquals(0.5F, GLStateManager.getColor().getAlpha(), 0.0F);
assertEquals(texture, driverTextureBinding(0));
assertEquals(texture, GLStateManager.getBoundTextureForServerState(0));
}
@Test
void retainedDeltaReappliesAfterPop() {
final int texture = GL11.glGenTextures();
final RetainedState retained = new RetainedState();
try {
baseline();
final int d = GLStateManager.pushState(ALL);
mutate(texture);
GLStateManager.retainModifiedState(d, retained);
GLStateManager.popStateTo(d);
assertBaseline();
GLStateManager.applyRetainedState(retained);
assertMutated(texture);
baseline();
assertBaseline();
GLStateManager.applyRetainedState(retained);
assertMutated(texture);
} finally {
baseline();
GL11.glDeleteTextures(texture);
}
}
@Test
void appliedDeltaIsRestoredByAnEnclosingPush() {
final int texture = GL11.glGenTextures();
final RetainedState retained = new RetainedState();
try {
baseline();
final int d = GLStateManager.pushState(ALL);
mutate(texture);
GLStateManager.retainModifiedState(d, retained);
GLStateManager.popStateTo(d);
final int outer = GLStateManager.pushState(ALL);
GLStateManager.applyRetainedState(retained);
assertMutated(texture);
GLStateManager.popStateTo(outer);
assertBaseline();
} finally {
baseline();
GL11.glDeleteTextures(texture);
}
}
@Test
void statesRestoredByANestedPushAreNotRetained() {
final RetainedState retained = new RetainedState();
try {
baseline();
final int d = GLStateManager.pushState(ALL);
GLStateManager.enableBlend();
final int nested = GLStateManager.pushState(ALL);
GLStateManager.disableCull();
GLStateManager.popStateTo(nested);
GLStateManager.retainModifiedState(d, retained);
GLStateManager.popStateTo(d);
GLStateManager.applyRetainedState(retained);
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND));
assertTrue(GL11.glIsEnabled(GL11.GL_CULL_FACE));
assertTrue(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE));
} finally {
baseline();
}
}
@Test
void onlyDifferingStateIsReissued() {
final int texture = GL11.glGenTextures();
final RetainedState retained = new RetainedState();
try {
baseline();
final int d = GLStateManager.pushState(ALL);
mutate(texture);
GLStateManager.retainModifiedState(d, retained);
GLStateManager.popStateTo(d);
GLStateManager.applyRetainedState(retained);
final GLContextState glCtx = GLStateManager.ctx();
final long backendCalls = GLStateManager.attribBackendCalls;
final int colorGen = glCtx.colorGeneration;
final int fragmentGen = glCtx.fragmentGeneration;
final int batchGen = glCtx.batchStateGeneration;
GLStateManager.applyRetainedState(retained);
assertEquals(backendCalls, GLStateManager.attribBackendCalls);
assertEquals(colorGen, glCtx.colorGeneration);
assertEquals(fragmentGen, glCtx.fragmentGeneration);
assertEquals(batchGen, glCtx.batchStateGeneration);
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.applyRetainedState(retained);
assertEquals(backendCalls + 3, GLStateManager.attribBackendCalls);
assertMutated(texture);
} finally {
baseline();
GL11.glDeleteTextures(texture);
}
}
@Test
void aSecondCaptureInvalidatesTheFirst() {
final RetainedState first = new RetainedState();
final RetainedState second = new RetainedState();
try {
baseline();
int d = GLStateManager.pushState(ALL);
GLStateManager.enableBlend();
GLStateManager.retainModifiedState(d, first);
GLStateManager.popStateTo(d);
d = GLStateManager.pushState(ALL);
GLStateManager.disableCull();
GLStateManager.retainModifiedState(d, second);
GLStateManager.popStateTo(d);
GLStateManager.applyRetainedState(first);
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND));
assertTrue(GL11.glIsEnabled(GL11.GL_CULL_FACE));
GLStateManager.applyRetainedState(second);
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND));
assertFalse(GL11.glIsEnabled(GL11.GL_CULL_FACE));
} finally {
baseline();
}
}
@Test
void writesInAnOuterPushAreNotRetained() {
final RetainedState retained = new RetainedState();
try {
baseline();
GLStateManager.glClearColor(0.25F, 0.5F, 0.75F, 1.0F);
final int outer = GLStateManager.pushState(ALL);
GLStateManager.glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
final int inner = GLStateManager.pushState(ALL);
GLStateManager.enableBlend();
GLStateManager.retainModifiedState(inner, retained);
GLStateManager.popStateTo(outer);
GLStateManager.applyRetainedState(retained);
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND));
assertEquals(0.25F, GLStateManager.getClearColor().getRed(), 0.0F);
assertEquals(1.0F, GLStateManager.getClearColor().getAlpha(), 0.0F);
} finally {
baseline();
GLStateManager.glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
}
}
@Test
void aDeletedProgramIsNotRebound() {
final RetainedState retained = new RetainedState();
final int program = GLSM_StateSet_GLTest.linkTrivialProgram();
try {
baseline();
final int d = GLStateManager.pushState(ALL);
GLStateManager.glUseProgram(program);
GLStateManager.retainModifiedState(d, retained);
GLStateManager.popStateTo(d);
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(program);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
GLStateManager.applyRetainedState(retained);
assertEquals(0, GLStateManager.getActiveProgram());
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glUseProgram(0);
baseline();
}
}
}
@@ -45,7 +45,7 @@ public class GLSM_StateSet_GLTest {
private static final String TRIVIAL_VS = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }\n";
private static final String TRIVIAL_FS = "#version 330 core\nout vec4 o;\nvoid main(){ o = vec4(1.0); }\n";
private static int linkTrivialProgram() {
static int linkTrivialProgram() {
final int v = compileShader(GL20.GL_VERTEX_SHADER, TRIVIAL_VS);
final int f = compileShader(GL20.GL_FRAGMENT_SHADER, TRIVIAL_FS);
final int p = GL20.glCreateProgram();
@@ -89,33 +89,41 @@ public class GLSM_StateSet_GLTest {
}
@Test
void fontRestoresDepthAxisAndShadeModelButNotCull() {
void fontRestoresDepthAxisShadeModelAndPolygonOffsetButNotStencil() {
try {
GLStateManager.enableDepthTest();
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
GLStateManager.glPolygonOffset(0.0f, 0.0f);
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
final int d = GLStateManager.pushState(StateSet.FONT);
GLStateManager.disableDepthTest();
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
GLStateManager.glShadeModel(GL11.GL_FLAT);
GLStateManager.glCullFace(GL11.GL_FRONT);
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
GLStateManager.glPolygonOffset(-10.0f, -10.0f);
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
GLStateManager.popStateTo(d);
verifyIsEnabled(GL11.GL_DEPTH_TEST, true, "Depth test enable - restored by FONT pop");
assertEquals(GL11.GL_LEQUAL, GLStateManager.getDepthState().getFunc(), "cache depth func");
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth func");
assertEquals(GL11.GL_SMOOTH, GLStateManager.getShadeModelState().getValue(), "cache shade model");
assertEquals(GL11.GL_FRONT, GLStateManager.getPolygonState().getCullFaceMode(), "cache cull face mode - not a FONT member, must stay toggled");
assertEquals(GL11.GL_FRONT, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode - not a FONT member, must stay toggled");
verifyIsEnabled(GL11.GL_POLYGON_OFFSET_FILL, false, "Polygon offset fill - restored by FONT pop");
assertEquals(0.0f, GLStateManager.getPolygonState().getOffsetFactor(), "cache polygon offset factor");
assertEquals(0.0f, GL11.glGetFloat(GL11.GL_POLYGON_OFFSET_FACTOR), "driver polygon offset factor");
assertEquals(0.0f, GLStateManager.getPolygonState().getOffsetUnits(), "cache polygon offset units");
assertEquals(0.0f, GL11.glGetFloat(GL11.GL_POLYGON_OFFSET_UNITS), "driver polygon offset units");
verifyIsEnabled(GL11.GL_STENCIL_TEST, false, "Stencil test - not a FONT member, must stay toggled");
} finally {
GLStateManager.disableDepthTest();
GLStateManager.glDepthFunc(GL11.GL_LESS);
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
GLStateManager.glPolygonOffset(0.0f, 0.0f);
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
}
}
@@ -0,0 +1,65 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class BackendStartGuardTest {
private static File marker(Path dir) {
return dir.resolve("angelica").resolve("sdlgpu-starting").toFile();
}
@Test
void firstStartCreatesTheMarkerAndFinishRemovesIt(@TempDir Path dir) {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, true, false));
assertFalse(BackendStartGuard.tripped());
assertTrue(marker.isFile());
BackendStartGuard.finish();
assertFalse(marker.exists());
}
@Test
void leftoverMarkerTripsOnceAndIsRemoved(@TempDir Path dir) {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, true, false));
assertTrue(BackendStartGuard.begin(marker, true, false));
assertTrue(BackendStartGuard.tripped());
assertFalse(marker.exists());
BackendStartGuard.finish();
assertFalse(marker.exists());
assertFalse(BackendStartGuard.begin(marker, true, false));
assertFalse(BackendStartGuard.tripped());
BackendStartGuard.finish();
}
@Test
void openGlConfigWritesNoMarkerAndClearsAStaleOne(@TempDir Path dir) throws IOException {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, false, false));
assertFalse(marker.exists());
assertTrue(marker.getParentFile().mkdirs());
assertTrue(marker.createNewFile());
assertFalse(BackendStartGuard.begin(marker, false, false));
assertFalse(BackendStartGuard.tripped());
assertFalse(marker.exists());
}
@Test
void forcedFlagNeverTripsOrWrites(@TempDir Path dir) throws IOException {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, true, true));
assertFalse(marker.exists());
assertTrue(marker.getParentFile().mkdirs());
assertTrue(marker.createNewFile());
assertFalse(BackendStartGuard.begin(marker, true, true));
assertFalse(BackendStartGuard.tripped());
}
}
@@ -0,0 +1,77 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class MoltenVKTest {
private static final String PRESENT_RESOURCE = "/com/gtnewhorizons/angelica/glsm/backend/MoltenVK.class";
private static final String ABSENT_RESOURCE = "/macos/arm64/org/lwjgl/vulkan/absent.dylib";
@Test
void detectOrderIsEnvThenJarThenSystem(@TempDir Path dir) throws IOException {
final String[] system = { Files.createFile(dir.resolve("system.dylib")).toString() };
final String[] none = { dir.resolve("missing.dylib").toString() };
assertEquals(MoltenVK.Source.NONE, MoltenVK.detect(false, "/env/lib.dylib", true, system));
assertEquals(MoltenVK.Source.ENV, MoltenVK.detect(true, "/env/lib.dylib", true, system));
assertEquals(MoltenVK.Source.JAR, MoltenVK.detect(true, "", true, system));
assertEquals(MoltenVK.Source.SYSTEM, MoltenVK.detect(true, null, false, system));
assertEquals(MoltenVK.Source.NONE, MoltenVK.detect(true, null, false, none));
assertEquals(1, dir.toFile().list().length);
}
@Test
void jarIsExtractedOnceAndNotRewritten(@TempDir Path dir) throws IOException {
final File out = dir.resolve("natives").toFile();
final String first = MoltenVK.resolve(MoltenVK.Source.JAR, null, PRESENT_RESOURCE, out, "arm64", new String[0]);
final File extracted = new File(out, "libMoltenVK-arm64.dylib");
assertEquals(extracted.getAbsolutePath(), first);
assertTrue(extracted.length() > 0);
assertTrue(extracted.setLastModified(1_000_000L));
assertEquals(first, MoltenVK.resolve(MoltenVK.Source.JAR, null, PRESENT_RESOURCE, out, "arm64", new String[0]));
assertEquals(1_000_000L, extracted.lastModified());
assertEquals(1, out.list().length);
}
@Test
void changedFileIsRewritten(@TempDir Path dir) throws IOException {
final File extracted = new File(dir.toFile(), "libMoltenVK-arm64.dylib");
Files.write(extracted.toPath(), new byte[] { 1, 2, 3 });
MoltenVK.resolve(MoltenVK.Source.JAR, null, PRESENT_RESOURCE, dir.toFile(), "arm64", new String[0]);
assertTrue(extracted.length() > 3);
}
@Test
void failedExtractionFallsBackToTheSystemInstall(@TempDir Path dir) throws IOException {
final Path system = Files.createFile(dir.resolve("system.dylib"));
final File notADirectory = Files.createFile(dir.resolve("blocker")).toFile();
assertEquals(system.toString(), MoltenVK.resolve(MoltenVK.Source.JAR, null, PRESENT_RESOURCE, notADirectory, "arm64", new String[] { system.toString() }));
assertNull(MoltenVK.resolve(MoltenVK.Source.JAR, null, ABSENT_RESOURCE, dir.toFile(), "arm64", new String[0]));
}
@Test
void envAndSystemAndNoneResolveWithoutExtracting(@TempDir Path dir) throws IOException {
final Path system = Files.createFile(dir.resolve("system.dylib"));
final File out = dir.resolve("natives").toFile();
assertEquals("/env/lib.dylib", MoltenVK.resolve(MoltenVK.Source.ENV, "/env/lib.dylib", PRESENT_RESOURCE, out, "arm64", new String[0]));
assertEquals(system.toString(), MoltenVK.resolve(MoltenVK.Source.SYSTEM, null, PRESENT_RESOURCE, out, "arm64", new String[] { system.toString() }));
assertNull(MoltenVK.resolve(MoltenVK.Source.NONE, null, PRESENT_RESOURCE, out, "arm64", new String[] { system.toString() }));
assertFalse(out.exists());
}
@Test
void archNames() {
assertEquals("arm64", MoltenVK.archName("aarch64"));
assertEquals("x64", MoltenVK.archName("x86_64"));
}
}
@@ -0,0 +1,114 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
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.GL15;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
class WideLineClipGLTest {
private static final int SIZE = 64;
private static final double NEAR = 0.05;
private static final float LINE_WIDTH = 3.0f;
private static final int BACKGROUND = 0xFF000000;
private static final float[] IN_FRONT = { 0f, -1f, -2f };
private static final float[] BEHIND = { 0f, 0.8f, 2f };
private boolean savedEmulation;
private int vao;
private int vbo;
@BeforeEach
void setUp() {
assumeTrue(GLStateManager.supportsGeometryShaders());
savedEmulation = GLStateManager.wideLineEmulationEnabled;
GLStateManager.wideLineEmulationEnabled = true;
FfpFixture.resetFfpState();
ShaderManager.enable();
ShaderManager.getInstance().activate();
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glFrustum(-NEAR, NEAR, -NEAR, NEAR, NEAR, 100.0);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
GLStateManager.glLineWidth(LINE_WIDTH);
}
@AfterEach
void tearDown() {
GLStateManager.wideLineEmulationEnabled = savedEmulation;
GLStateManager.glLineWidth(1.0f);
if (vbo != 0) { GLStateManager.glDeleteBuffers(vbo); vbo = 0; }
if (vao != 0) { GLStateManager.glDeleteVertexArrays(vao); vao = 0; }
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
FfpFixture.resetFfpState();
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
@Test
void lineEndingBehindTheCameraKeepsItsWidth() {
assertConstantWidth(IN_FRONT, BEHIND);
}
@Test
void lineStartingBehindTheCameraKeepsItsWidth() {
assertConstantWidth(BEHIND, IN_FRONT);
}
@Test
void lineFullyBehindTheCameraDrawsNothing() {
final int[] pixels = drawLine(new float[] { 0f, -1f, 2f }, BEHIND);
for (int px : pixels) {
assertEquals(BACKGROUND, px);
}
}
private void assertConstantWidth(float[] start, float[] end) {
final int[] pixels = drawLine(start, end);
for (int row = 0; row < SIZE / 4; row++) {
int lit = 0;
for (int x = 0; x < SIZE; x++) {
if (pixels[row * SIZE + x] != BACKGROUND) lit++;
}
final int y = row, count = lit;
assertTrue(count > 0 && count <= LINE_WIDTH + 1, () -> "row " + y + " is " + count + " pixels wide");
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
private int[] drawLine(float[] start, float[] end) {
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
final FloatBuffer positions = BufferUtils.createFloatBuffer(6);
positions.put(start).put(end).flip();
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, positions, GL15.GL_STATIC_DRAW);
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setCurrentVertexFlags(0);
FfpFixture.clear();
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.glDrawArrays(GL11.GL_LINES, 0, 2);
GLStateManager.glBindVertexArray(0);
return FfpFixture.readRegion(SIZE);
}
}
@@ -252,6 +252,32 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().isEmpty());
}
@Test
void arbGenerateMipmapAliasIsRedirected() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/ARBFramebufferObject", "glGenerateMipmap", "(I)V");
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
assertEquals(GLSM, firstCall(cn).owner);
assertEquals("glGenerateMipmap", firstCall(cn).name);
assertEquals("(I)V", firstCall(cn).desc);
assertTrue(unmappedSeen().isEmpty());
}
@Test
void extGenerateMipmapAliasIsRedirected() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/EXTFramebufferObject", "glGenerateMipmapEXT", "(I)V");
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
assertEquals(GLSM, firstCall(cn).owner);
assertEquals("glGenerateMipmap", firstCall(cn).name);
assertEquals("(I)V", firstCall(cn).desc);
assertTrue(unmappedSeen().isEmpty());
}
@Test
void newlyMappedReadPixelsIsRedirected() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GL11", "glReadPixels", "(IIIIIILjava/nio/IntBuffer;)V");
@@ -10,6 +10,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@@ -122,4 +123,27 @@ class ShaderDiskCacheTest {
ShaderDiskCache.put(ShaderDiskCache.key("t").i(9), new byte[] {5});
assertArrayEquals(new byte[] {5}, ShaderDiskCache.get(ShaderDiskCache.key("t").i(9)));
}
@Test
void retainLayerKeepsListedEntriesOtherLayersAndInFlightWrites() throws IOException {
ShaderDiskCache.configure(dir, "a");
final ShaderDiskCache.Key kept = ShaderDiskCache.key("p").str("kept");
final ShaderDiskCache.Key dropped = ShaderDiskCache.key("p").str("dropped");
final ShaderDiskCache.Key otherLayer = ShaderDiskCache.key("q").str("dropped");
ShaderDiskCache.put(kept, new byte[] {1});
ShaderDiskCache.put(dropped, new byte[] {2});
ShaderDiskCache.put(otherLayer, new byte[] {3});
final Path inFlight = dir.resolve("p").resolve(dropped.hex() + ".7.tmp");
Files.write(inFlight, new byte[] {4});
ShaderDiskCache.retainLayer("p", Set.of(kept.hex()));
assertArrayEquals(new byte[] {1}, ShaderDiskCache.get(kept));
assertNull(ShaderDiskCache.get(dropped));
assertArrayEquals(new byte[] {3}, ShaderDiskCache.get(otherLayer));
assertTrue(Files.exists(inFlight));
ShaderDiskCache.retainLayer("p", null);
assertNull(ShaderDiskCache.get(kept));
assertArrayEquals(new byte[] {3}, ShaderDiskCache.get(otherLayer));
}
}
@@ -0,0 +1,115 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PixelStoreStateTest {
@Test
void defaults() {
assertTrue(PixelStoreState.DEFAULT.isDefault());
assertEquals(4, PixelStoreState.DEFAULT.alignment());
assertEquals(0, PixelStoreState.DEFAULT.rowLength());
assertFalse(PixelStoreState.DEFAULT.swapBytes());
assertFalse(PixelStoreState.DEFAULT.lsbFirst());
}
@Test
void withUpdatesUnpackParams() {
PixelStoreState s = PixelStoreState.DEFAULT
.with(GL11.GL_UNPACK_ALIGNMENT, 1)
.with(GL11.GL_UNPACK_ROW_LENGTH, 64)
.with(GL11.GL_UNPACK_SKIP_ROWS, 2)
.with(GL11.GL_UNPACK_SKIP_PIXELS, 3)
.with(GL12.GL_UNPACK_IMAGE_HEIGHT, 32)
.with(GL12.GL_UNPACK_SKIP_IMAGES, 1)
.with(GL11.GL_UNPACK_SWAP_BYTES, 1)
.with(GL11.GL_UNPACK_LSB_FIRST, 1);
assertEquals(new PixelStoreState(1, 64, 2, 3, 32, 1, true, true), s);
assertFalse(s.isDefault());
}
@Test
void invalidValuesIgnored() {
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 3));
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 0));
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_ROW_LENGTH, -1));
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_SKIP_ROWS, -5));
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL12.GL_UNPACK_IMAGE_HEIGHT, -1));
}
@Test
void booleansAcceptAnyNonzero() {
assertTrue(PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 2).swapBytes());
assertTrue(PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_LSB_FIRST, -1).lsbFirst());
PixelStoreState on = PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 1);
assertFalse(on.with(GL11.GL_UNPACK_SWAP_BYTES, 0).swapBytes());
}
@Test
void packPnamesSetTheSameFields() {
PixelStoreState s = PixelStoreState.DEFAULT
.with(GL11.GL_PACK_ALIGNMENT, 1)
.with(GL11.GL_PACK_ROW_LENGTH, 64)
.with(GL11.GL_PACK_SKIP_ROWS, 2)
.with(GL11.GL_PACK_SKIP_PIXELS, 3)
.with(GL12.GL_PACK_IMAGE_HEIGHT, 32)
.with(GL12.GL_PACK_SKIP_IMAGES, 1)
.with(GL11.GL_PACK_SWAP_BYTES, 1)
.with(GL11.GL_PACK_LSB_FIRST, 1);
assertEquals(new PixelStoreState(1, 64, 2, 3, 32, 1, true, true), s);
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_PACK_ALIGNMENT, 3));
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_PACK_ROW_LENGTH, -1));
}
@Test
void unknownPnamesIgnored() {
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(0x1234, 1));
}
@Test
void unchangedValueReturnsSameInstance() {
final PixelStoreState s = new PixelStoreState(1, 64, 2, 3, 32, 1, true, true);
assertSame(s, s.with(GL11.GL_UNPACK_ALIGNMENT, 1));
assertSame(s, s.with(GL11.GL_PACK_ALIGNMENT, 1));
assertSame(s, s.with(GL11.GL_UNPACK_ROW_LENGTH, 64));
assertSame(s, s.with(GL11.GL_PACK_ROW_LENGTH, 64));
assertSame(s, s.with(GL11.GL_UNPACK_SKIP_ROWS, 2));
assertSame(s, s.with(GL11.GL_PACK_SKIP_ROWS, 2));
assertSame(s, s.with(GL11.GL_UNPACK_SKIP_PIXELS, 3));
assertSame(s, s.with(GL11.GL_PACK_SKIP_PIXELS, 3));
assertSame(s, s.with(GL12.GL_UNPACK_IMAGE_HEIGHT, 32));
assertSame(s, s.with(GL12.GL_PACK_IMAGE_HEIGHT, 32));
assertSame(s, s.with(GL12.GL_UNPACK_SKIP_IMAGES, 1));
assertSame(s, s.with(GL12.GL_PACK_SKIP_IMAGES, 1));
assertSame(s, s.with(GL11.GL_UNPACK_SWAP_BYTES, 5));
assertSame(s, s.with(GL11.GL_PACK_SWAP_BYTES, 5));
assertSame(s, s.with(GL11.GL_UNPACK_LSB_FIRST, 1));
assertSame(s, s.with(GL11.GL_PACK_LSB_FIRST, 1));
}
@Test
void isPackSeparatesTheFamilies() {
final int[] pack = {
GL11.GL_PACK_ALIGNMENT, GL11.GL_PACK_ROW_LENGTH, GL11.GL_PACK_SKIP_ROWS, GL11.GL_PACK_SKIP_PIXELS,
GL12.GL_PACK_IMAGE_HEIGHT, GL12.GL_PACK_SKIP_IMAGES, GL11.GL_PACK_SWAP_BYTES, GL11.GL_PACK_LSB_FIRST,
};
final int[] unpack = {
GL11.GL_UNPACK_ALIGNMENT, GL11.GL_UNPACK_ROW_LENGTH, GL11.GL_UNPACK_SKIP_ROWS, GL11.GL_UNPACK_SKIP_PIXELS,
GL12.GL_UNPACK_IMAGE_HEIGHT, GL12.GL_UNPACK_SKIP_IMAGES, GL11.GL_UNPACK_SWAP_BYTES, GL11.GL_UNPACK_LSB_FIRST,
};
for (final int pname : pack) {
assertTrue(PixelStoreState.isPack(pname));
}
for (final int pname : unpack) {
assertFalse(PixelStoreState.isPack(pname));
}
assertFalse(PixelStoreState.isPack(0x1234));
}
}
@@ -1,61 +0,0 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PixelUnpackStateTest {
@Test
void defaults() {
assertTrue(PixelUnpackState.DEFAULT.isDefault());
assertEquals(4, PixelUnpackState.DEFAULT.alignment());
assertEquals(0, PixelUnpackState.DEFAULT.rowLength());
assertFalse(PixelUnpackState.DEFAULT.swapBytes());
assertFalse(PixelUnpackState.DEFAULT.lsbFirst());
}
@Test
void withUpdatesUnpackParams() {
PixelUnpackState s = PixelUnpackState.DEFAULT
.with(GL11.GL_UNPACK_ALIGNMENT, 1)
.with(GL11.GL_UNPACK_ROW_LENGTH, 64)
.with(GL11.GL_UNPACK_SKIP_ROWS, 2)
.with(GL11.GL_UNPACK_SKIP_PIXELS, 3)
.with(GL12.GL_UNPACK_IMAGE_HEIGHT, 32)
.with(GL12.GL_UNPACK_SKIP_IMAGES, 1)
.with(GL11.GL_UNPACK_SWAP_BYTES, 1)
.with(GL11.GL_UNPACK_LSB_FIRST, 1);
assertEquals(new PixelUnpackState(1, 64, 2, 3, 32, 1, true, true), s);
assertFalse(s.isDefault());
}
@Test
void invalidValuesIgnored() {
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 3));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 0));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ROW_LENGTH, -1));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SKIP_ROWS, -5));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL12.GL_UNPACK_IMAGE_HEIGHT, -1));
}
@Test
void booleansAcceptAnyNonzero() {
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 2).swapBytes());
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_LSB_FIRST, -1).lsbFirst());
PixelUnpackState on = PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 1);
assertFalse(on.with(GL11.GL_UNPACK_SWAP_BYTES, 0).swapBytes());
}
@Test
void packAndUnknownPnamesIgnored() {
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ALIGNMENT, 1));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ROW_LENGTH, 16));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(0x1234, 1));
}
}
+3
View File
@@ -2,6 +2,9 @@
# LOCAL to test local config updates.
gtnh.settings.blowdryerTag = 0.2.2
# See mod-version-gradle.kts
gtnh.modules.gitVersion = false
# Human-readable mod name, available for mcmod.info population.
modName = Angelica
+3
View File
@@ -8,6 +8,8 @@ launchwrapper = "1.12"
# End Frozen
lwjgl3 = "3.4.2-SNAPSHOT"
# Must equal the LWJGL release shipped by lwjgl3ify. Do not bump without bumping lwjgl3ify.
lwjgl3Release = "3.4.2"
lwjgl3ify = "3.0.31"
celeritas = "2.5.12-GTNH"
@@ -75,6 +77,7 @@ lwjgl3-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl3" }
lwjgl3-sdl = { module = "org.lwjgl:lwjgl-sdl", version.ref = "lwjgl3" }
lwjgl3-spvc = { module = "org.lwjgl:lwjgl-spvc", version.ref = "lwjgl3" }
lwjgl3-shaderc = { module = "org.lwjgl:lwjgl-shaderc", version.ref = "lwjgl3" }
lwjgl3-vulkan = { module = "org.lwjgl:lwjgl-vulkan", version.ref = "lwjgl3Release" }
# Consumed with an explicit :dev / :universal / :downgraded-8 classifier at the call site.
gtnhlib = { module = "com.github.GTNewHorizons:GTNHLib", version.ref = "gtnhlib" }
+37
View File
@@ -0,0 +1,37 @@
// Replaces GTNHGradle's GitVersionModule (gtnh.modules.gitVersion = false): same version format, but only
// x.y.z[-suffix] tags count, so additional tags (moltenvk-v*, tracy-v*) on master never become the mod version.
fun git(vararg args: String): String? = try {
val exec = providers.exec {
commandLine("git", *args)
workingDir = rootDir
isIgnoreExitValue = true
}
if (exec.result.get().exitValue == 0) exec.standardOutput.asText.get().trim() else null
} catch (e: Exception) {
null
}
fun gitVersion(): String {
val described = git("describe", "--tags", "--long", "--first-parent", "--abbrev=7", "--match=[0-9]*.[0-9]*.[0-9]*", "HEAD")
val match = described?.let { Regex("(.*)-([0-9]+)-g[0-9a-f]+").matchEntire(it) }
val tag = match?.groupValues?.get(1)
val distance = match?.groupValues?.get(2)?.toInt() ?: 0
val hash = git("rev-parse", "HEAD")?.take(10)?.ifEmpty { null }
val base = tag ?: hash
if (base == null) {
logger.error("No git repository found; set the VERSION environment variable to version this build.")
return "NO-GIT-TAG-SET"
}
val dirty = !git("status", "--porcelain").isNullOrEmpty()
val branch = (git("branch", "--show-current")?.ifEmpty { null } ?: System.getenv("GIT_BRANCH") ?: "git")
.removePrefix("origin/")
.replace(Regex("[^a-zA-Z0-9-]+"), "-")
return when {
distance > 0 -> "$base-$branch.$distance+$hash${if (dirty) "-dirty" else ""}"
dirty -> "$base-$branch+$hash-dirty"
else -> base
}
}
extra["modVersion"] = providers.environmentVariable("VERSION").orNull ?: gitVersion()
@@ -28,6 +28,7 @@ import org.lwjgl.opengl.GL31C;
import org.lwjgl.opengl.GL32C;
import org.lwjgl.opengl.GL33C;
import org.lwjgl.opengl.GL40C;
import org.lwjgl.opengl.GL41C;
import org.lwjgl.opengl.GL42C;
import org.lwjgl.opengl.GL43C;
import org.lwjgl.opengl.GL44C;
@@ -965,6 +966,27 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
return GL20C.glIsProgram(obj);
}
@Override
public boolean supportsProgramBinary() {
return !RenderSystem.isGLES() && (caps.OpenGL41 || caps.GL_ARB_get_program_binary)
&& GL11C.glGetInteger(GL41C.GL_NUM_PROGRAM_BINARY_FORMATS) > 0;
}
@Override
public void programParameteri(int program, int pname, int value) {
GL41C.glProgramParameteri(program, pname, value);
}
@Override
public void getProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {
GL41C.glGetProgramBinary(program, length, binaryFormat, binary);
}
@Override
public void programBinary(int program, int binaryFormat, ByteBuffer binary) {
GL41C.glProgramBinary(program, binaryFormat, binary);
}
@Override
public void validateProgram(int program) {
GL20C.glValidateProgram(program);
+112
View File
@@ -0,0 +1,112 @@
plugins {
`java-library`
`maven-publish`
}
version = rootProject.version
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}
tasks.withType<JavaCompile>().configureEach {
options.release.set(8)
}
val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation", String::class.java)
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
configurations {
listOf("apiElements", "runtimeElements").forEach { name ->
named(name) {
attributes {
attribute(rfgObfAttr, "mcp")
attribute(rfgTransformedAttr, true)
}
}
}
}
val moltenvkVersion = (findProperty("moltenvkVersion") as String?) ?: libs.versions.lwjgl3Release.get()
repositories {
maven {
name = "Forge"
url = uri("https://maven.minecraftforge.net/")
}
maven {
name = "Forge artifact-only"
url = uri("https://maven.minecraftforge.net/")
metadataSources { artifact() }
content { includeModule("net.minecraftforge", "forge") }
}
mavenCentral()
}
val moltenvkNatives by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
compileOnly("net.minecraftforge:forge:1.7.10-10.13.4.1614-1.7.10:universal") { isTransitive = false }
listOf("natives-macos", "natives-macos-arm64").forEach { nativesClassifier ->
moltenvkNatives(libs.lwjgl3.vulkan) {
artifact { classifier = nativesClassifier }
isTransitive = false
}
}
}
val extractMoltenVK = tasks.register<Sync>("extractMoltenVK") {
val outDir = layout.buildDirectory.dir("generated/resources/moltenvk")
from(provider { moltenvkNatives.files.map { zipTree(it) } }) {
include("macos/**/libMoltenVK.dylib")
}
includeEmptyDirs = false
into(outDir)
doLast {
listOf("macos/x64", "macos/arm64").forEach { dir ->
val rel = "$dir/org/lwjgl/vulkan/libMoltenVK.dylib"
if (!outDir.get().file(rel).asFile.isFile) {
throw GradleException("MoltenVK dylib missing after extraction: $rel")
}
}
}
}
sourceSets["main"].resources.srcDir(extractMoltenVK)
val distJar = tasks.register<Jar>("distJar") {
archiveBaseName.set("angelica-moltenvk")
archiveVersion.set(moltenvkVersion)
from(sourceSets["main"].output)
manifest {
attributes(
"FMLCorePlugin" to "com.gtnewhorizons.angelica.moltenvk.loading.MoltenVKCorePlugin"
)
}
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
groupId = "com.gtnewhorizons.angelica"
artifactId = "angelica-moltenvk"
version = moltenvkVersion
artifact(distJar)
}
}
repositories {
if (hasProperty("publishMoltenVK") && System.getenv("MAVEN_USER") != null) {
maven {
name = "GTNHMaven"
url = uri(rootProject.findProperty("mavenPublishUrl")?.toString() ?: "https://nexus.gtnewhorizons.com/repository/releases/")
credentials {
username = System.getenv("MAVEN_USER")
password = System.getenv("MAVEN_PASSWORD")
}
}
}
}
}
@@ -0,0 +1,32 @@
package com.gtnewhorizons.angelica.moltenvk.loading;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import java.util.Map;
@IFMLLoadingPlugin.MCVersion("1.7.10")
public class MoltenVKCorePlugin implements IFMLLoadingPlugin {
@Override
public String[] getASMTransformerClass() {
return null;
}
@Override
public String getModContainerClass() {
return null;
}
@Override
public String getSetupClass() {
return null;
}
@Override
public void injectData(Map<String, Object> data) {}
@Override
public String getAccessTransformerClass() {
return null;
}
}
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+3
View File
@@ -9,6 +9,9 @@
# route - straight | pan | circuit | static
# length - Units: blocks (straight), blocks per leg (circuit), degrees (pan), ticks (static).
# extra - passed through to gradle; -Dangelica.* reaches the client (e.g. -Dangelica.flyby.timeOfDay=1500).
# -Dangelica.flyby.camera=FIRST_PERSON|THIRD_PERSON_BACK|THIRD_PERSON_FRONT (default FIRST_PERSON).
# -Dangelica.flyby.dimension=<id> moves the player there first (1 = End), after world saving is off.
# -PclientJvmVendor=adoptium (macOS) picks the JVM vendor for runClient17/21/25.
#
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
# FLYBY_PACING - UNCAPPED | CONFIGURED. Passed through when set.
+4
View File
@@ -0,0 +1,4 @@
# End crystals next to the arrival platform. Use with -Dangelica.flyby.dimension=1, route pan.
summon EnderCrystal ~ ~2 ~12 {ForgeData:{flyby:1b}}
summon EnderCrystal ~8 ~2 ~8 {ForgeData:{flyby:1b}}
summon EnderCrystal ~-8 ~4 ~10 {ForgeData:{flyby:1b}}
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.sdlgpu;
import static org.lwjgl.sdl.SDLVideo.SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.backend.BackendOptions;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier;
@@ -83,7 +83,7 @@ public final class SDLGPUGate {
private static synchronized boolean probe() {
if (deviceReady != null) return deviceReady;
if (!SystemProperties.USE_SDL_GPU || !isSDLGPUAvailable()) {
if (!BackendOptions.sdlGpuRequested() || !isSDLGPUAvailable()) {
deviceReady = false;
return false;
}
@@ -1,14 +1,17 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.sdlgpu.compute.VertexWriteReplayer;
import com.gtnewhorizons.angelica.sdlgpu.compute.VoxelizationDispatcher;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.backend.BackendOptions;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
import com.gtnewhorizons.angelica.glsm.backend.MainThreadPump;
import com.gtnewhorizons.angelica.glsm.backend.VertexWriteReplaySetup;
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
import org.lwjglx.Lwjgl3ifyEventLoop;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
@@ -40,6 +43,7 @@ import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FormatMap;
import com.gtnewhorizons.angelica.sdlgpu.resource.Image3DClear;
import com.gtnewhorizons.angelica.sdlgpu.resource.MappedRange;
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentBufferSync;
import com.gtnewhorizons.angelica.sdlgpu.resource.PackState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentMapping;
@@ -63,6 +67,7 @@ import com.gtnewhorizons.angelica.sdlgpu.util.ThreadRegistry;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -161,12 +166,14 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public long nextSeq() { return TransferThread.nextSeq(); }
});
private final PipelineApplier pipelineApplier = new PipelineApplier(frameManager, resourceManager, shaderManager, pipelineStore, fboClearTracker, persistentSync, samplerBinder, storageTextureBinder, storageBufferBinder);
private final VertexWriteReplayer vertexWriteReplayer = new VertexWriteReplayer(shaderManager, resourceManager, pipelineApplier, computeBinder, SDLGPURenderBackend::s);
private final AttachmentClear attachmentClear = new AttachmentClear(frameManager, pipelineStore, shaderManager);
private final DepthStencilReadback depthStencilReadback = new DepthStencilReadback(frameManager, resourceManager, pipelineStore, shaderManager, fboClearTracker);
private final DrawDispatch drawDispatch = new DrawDispatch(device, frameManager, resourceManager, pipelineApplier, this::enqueuePreCopied);
private final FFPTrace ffpTrace = SystemProperties.FFP_TRACE ? new FFPTrace(resourceManager) : null;
private final IntOpenHashSet missingProgramWarned = new IntOpenHashSet();
private final LongOpenHashSet warnedBufferOps = new LongOpenHashSet();
private boolean droppedCopyWarned;
private boolean droppedSsboWriteWarned;
@@ -247,7 +254,9 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void onPersistentBufferWrite(int glId, long offset, long size) {
persistentSync.onPersistentBufferWrite(glId, offset, size);
if (!persistentSync.onPersistentBufferWrite(glId, offset, size)) {
warnInvalidBufferOp("persistent write out of range", glId);
}
}
@Override public void onPostWindowCreate(long window) {
@@ -295,6 +304,8 @@ public class SDLGPURenderBackend extends RenderBackend {
}
});
pipelineApplier.setDeferredUploadSink(this::enqueuePreCopied);
frameManager.setRenderPassEndHook(vertexWriteReplayer::flush);
resourceManager.setTextureReplaceHook(this::flushVertexWritesBeforeTextureChange);
frameManager.setPreRenderPassHook(() -> {
final ContextState st = s();
pipelineApplier.flushUniformBlocks(st);
@@ -514,14 +525,13 @@ public class SDLGPURenderBackend extends RenderBackend {
st.appliedLogicOpKey = 0;
st.fragSamplerBindings.free();
st.vertSamplerBindings.free();
if (st.mappedStagingBuffer != null) {
MemoryUtil.memFree(st.mappedStagingBuffer);
}
clearMappedState(st);
}
registeredStates.clear();
drawDispatch.clearWarnedState();
synchronized (warnedBufferOps) {
warnedBufferOps.clear();
}
attachmentClear.shutdown();
depthStencilReadback.shutdown();
textureOps.shutdown();
@@ -532,7 +542,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public boolean isAvailable() {
return SystemProperties.USE_SDL_GPU && SDLGPUGate.isSDLGPUAvailable() && SDLGPUGate.isEngaged();
return BackendOptions.sdlGpuRequested() && SDLGPUGate.isSDLGPUAvailable() && SDLGPUGate.isEngaged();
}
@Override public String getName() {
@@ -543,6 +553,10 @@ public class SDLGPURenderBackend extends RenderBackend {
return device.getDevice() != 0;
}
@Override public boolean hasContextOnThread() {
return hasContext() && (Thread.currentThread() == GLStateManager.getMainThread() || tlState.get() != null);
}
@Override public boolean isCurrent() {
return Thread.currentThread() == GLStateManager.getMainThread();
}
@@ -1198,9 +1212,11 @@ public class SDLGPURenderBackend extends RenderBackend {
if (st.rasterizerDiscard) return;
final FrameState f = frameManager.frame();
if (!f.frameActive) { f.droppedDrawsThisFrame++; return; }
prepareVertexWrites(st);
if (mode == GL11.GL_TRIANGLE_FAN && count >= 3) {
if (!drawDispatch.drawTriangleFanAsTriangleList(st, first, count)) { f.droppedDrawsThisFrame++; return; }
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysFan", mode, count, 0, first, 0);
captureVertexWrites(st, first, count, 1, 0, 0, 0);
return;
}
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
@@ -1209,6 +1225,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (!pipelineApplier.applyPipelineAndState(st, f)) { f.droppedDrawsThisFrame++; return; }
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arrays", mode, count, 0, first, 0);
SDL_DrawGPUPrimitives(f.renderPass, count, 1, first, 0);
captureVertexWrites(st, first, count, 1, 0, 0, 0);
}
@Override public void drawElements(int mode, int count, int type, long indices) {
@@ -1218,6 +1235,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elements", mode, count, type, firstIndex, 0);
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, 1, firstIndex, 0);
captureVertexWrites(st, 0, count, 1, type, indices, 0);
}
@Override public void drawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
@@ -1227,6 +1245,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsInstanced", mode, count, type, firstIndex, 0);
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, primcount, firstIndex, 0);
captureVertexWrites(st, 0, count, primcount, type, indices, 0);
}
@@ -1237,10 +1256,12 @@ public class SDLGPURenderBackend extends RenderBackend {
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsBaseVertex", mode, count, type, firstIndex, baseVertex);
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, 1, firstIndex, baseVertex);
captureVertexWrites(st, 0, count, 1, type, indices, baseVertex);
}
private long prepareIndexedDrawBind(ContextState st, int mode, int type, String opName) {
if (st.rasterizerDiscard) return 0;
prepareVertexWrites(st);
if (!drawDispatch.prepareIndexedDraw(st, mode, type)) return 0;
final int ebo = st.currentVao.elementBuffer;
final long eboHandle = resourceManager.getBufferHandle(ebo);
@@ -1269,6 +1290,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final int baseVertex = MemoryUtil.memGetInt(pBaseVertex + offset);
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "multiElementsBaseVertex", mode, count, type, firstIndex, baseVertex);
drawDispatch.issueIndexedDraw(st, rp, ebo, type, count, 1, firstIndex, baseVertex);
captureVertexWrites(st, 0, count, 1, type, indices, baseVertex);
}
}
@@ -1295,6 +1317,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsIndirect", mode, drawcount, type, (int) indirect, 0);
SDL_DrawGPUIndexedPrimitivesIndirect(rp, indirectHandle, (int) indirect, drawcount);
if (st.boundProgramObj != null && st.boundProgramObj.vertexWriteReplay != null) vertexWriteReplayer.noteIndirectDraw();
} finally {
Tracy.endZone();
}
@@ -1328,6 +1351,12 @@ public class SDLGPURenderBackend extends RenderBackend {
final int srcGlId = getBoundBuffer(readTarget);
final int dstGlId = getBoundBuffer(writeTarget);
if (srcGlId == 0 || dstGlId == 0 || size <= 0) return;
final long srcSize = resourceManager.getBufferSize(srcGlId);
final long dstSize = resourceManager.getBufferSize(dstGlId);
if (readOffset < 0 || writeOffset < 0 || (srcSize > 0 && (readOffset > srcSize || size > srcSize - readOffset)) || (dstSize > 0 && (writeOffset > dstSize || size > dstSize - writeOffset))) {
warnInvalidBufferOp("glCopyBufferSubData out of range", dstGlId);
return;
}
resourceManager.dropArrayShadow(dstGlId);
resourceManager.dropEboShadow(dstGlId);
resourceManager.markBufferContentsDefined(dstGlId);
@@ -1342,7 +1371,7 @@ public class SDLGPURenderBackend extends RenderBackend {
deferredCopyOp.dstHandle = dstHandle;
deferredCopyOp.writeOffset = writeOffset;
deferredCopyOp.seq = seq;
final boolean handled = resourceManager.enqueuePersistentCopyDeferred(srcGlId, dstGlId, seq, deferredCopyOp);
final boolean handled = resourceManager.enqueuePersistentCopyDeferred(srcGlId, dstGlId, seq, readOffset, size, deferredCopyOp);
if (handled) {
wakeTransferThread();
return;
@@ -1403,6 +1432,11 @@ public class SDLGPURenderBackend extends RenderBackend {
resourceManager.deleteTexture(texture);
}
/** Captured vertex-write replays name textures by GL id, which a delete or reallocation would make mean something else. */
private void flushVertexWritesBeforeTextureChange() {
if (vertexWriteReplayer.hasPending()) frameManager.endRenderPassIfActive(FrameManager.PASS_END_COMPUTE);
}
@Override public void bindTexture(int target, int texture) {
if (isProxyTarget(target)) return;
@@ -2756,8 +2790,10 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private void deleteBuffer(int buffer) {
dropMappingFor(buffer);
if (buffer != 0) unbindDeletedBuffer(s(), buffer);
if (buffer != 0) {
freePlainMappingFor(buffer, false);
unbindDeletedBuffer(s(), buffer);
}
readbackShadows.release(buffer);
resourceManager.deleteBuffer(buffer);
}
@@ -2804,12 +2840,6 @@ public class SDLGPURenderBackend extends RenderBackend {
if (ssboChanged) st.ssboBindGen++;
}
private static void dropMappingFor(int buffer) {
final ContextState st = s();
if (buffer == 0 || st.mappedBufferGlId != buffer || st.mappedStagingBuffer == null) return;
MemoryUtil.memFree(st.mappedStagingBuffer);
clearMappedState(st);
}
@Override public void bindBuffer(int target, int buffer) {
final ContextState st = s();
switch (target) {
@@ -2909,8 +2939,15 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public void bufferData(int target, long size, int usage) {
final int glId = getBoundBuffer(target);
final ContextState cs = s();
final int glId = cs.getBoundBuffer(target);
if (glId == 0) return;
if (refuseImmutable(glId, "glBufferData on immutable storage")) return;
respecifyStore(cs, target, glId, size, usage);
}
private void respecifyStore(ContextState cs, int target, int glId, long size, int usage) {
freePlainMappingFor(glId, true);
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
if (target == GL31.GL_UNIFORM_BUFFER) {
if (resourceManager.getBufferSize(glId) == size) return;
@@ -2926,14 +2963,45 @@ public class SDLGPURenderBackend extends RenderBackend {
readbackShadows.release(glId);
resourceManager.deleteBuffer(glId);
resourceManager.createBuffer(glId, sdlUsage, size);
if (target == GL15.GL_ARRAY_BUFFER) s().bumpAttribStateGen();
if (target == GL15.GL_ARRAY_BUFFER) cs.bumpAttribStateGen();
}
private void freePlainMappingFor(int glId, boolean warn) {
final ByteBuffer staging = resourceManager.takePlainStaging(glId);
if (staging == null) return;
MemoryUtil.memFree(staging);
if (warn) warnInvalidBufferOp("glBufferData on mapped buffer", glId);
}
private boolean refuseImmutable(int glId, String op) {
if (resourceManager.getBufferStorageFlags(glId) == BufferParams.MUTABLE_STORE) return false;
warnInvalidBufferOp(op, glId);
return true;
}
@Override public boolean isBufferImmutable(int glId) {
return resourceManager.getBufferStorageFlags(glId) != BufferParams.MUTABLE_STORE;
}
private void warnInvalidBufferOp(String op, int glId) {
synchronized (warnedBufferOps) {
if (warnedBufferOps.add(((long) op.hashCode() << 32) | (glId & 0xFFFFFFFFL))) {
LOG.warn("{} (buffer {})", op, glId, new Throwable());
}
}
}
@Override public void bufferData(int target, ByteBuffer data, int usage) {
final ContextState cs = s();
final int glId = getBoundBuffer(target);
final int glId = cs.getBoundBuffer(target);
if (glId == 0 || data == null) return;
if (refuseImmutable(glId, "glBufferData on immutable storage")) return;
respecifyStore(cs, target, glId, data, usage);
}
private void respecifyStore(ContextState cs, int target, int glId, ByteBuffer data, int usage) {
final long dataSize = data.remaining();
freePlainMappingFor(glId, true);
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
if (target == GL31.GL_UNIFORM_BUFFER) {
if (dataSize > 0) {
@@ -3023,11 +3091,15 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void bufferSubData(int target, long offset, ByteBuffer data) {
final ContextState cs = s();
final int glId = getBoundBuffer(target);
final int glId = cs.getBoundBuffer(target);
if (glId == 0 || data == null) return;
if (target == GL31.GL_UNIFORM_BUFFER) {
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
if (shadow != null) {
if (offset < 0 || offset + data.remaining() > shadow.capacity()) {
warnInvalidBufferOp("glBufferSubData out of range on uniform buffer", glId);
return;
}
shadow.position(0);
ByteRegionCopy.copyByteRegion(data, data.position(), shadow, (int) offset, data.remaining());
}
@@ -3096,133 +3168,132 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public boolean unmapBuffer(int target) {
final ContextState cs = s();
final int glId = getBoundBuffer(target);
final int glId = cs.getBoundBuffer(target);
final PersistentMapping pm = resourceManager.removePersistentMapping(glId);
if (pm != null) {
resourceManager.releasePersistentStaging(pm);
if (resourceManager.getPersistentMapping(glId) != null) {
final PersistentMapping pm = resourceManager.removePersistentMapping(glId);
if (pm != null) resourceManager.releasePersistentStaging(pm, glId, "unmap");
return true;
}
final ContextState st = cs;
if (st.mappedStagingBuffer == null) return false;
final MappedRange m = cs.mappingScratch;
if (!resourceManager.takePlainMapping(glId, m)) {
warnInvalidBufferOp("glUnmapBuffer on unmapped buffer", glId);
return false;
}
final boolean writeMap = (st.mappedAccessFlags & GL30.GL_MAP_WRITE_BIT) != 0;
final ByteBuffer staging = m.staging;
m.staging = null;
final boolean writeMap = (m.accessFlags & GL30.GL_MAP_WRITE_BIT) != 0;
if (target == GL30.GL_PIXEL_UNPACK_BUFFER) {
st.mappedStagingBuffer.position(0).limit((int) st.mappedLength);
final long gpuHandle = resourceManager.getBufferHandle(st.mappedBufferGlId);
staging.position(0).limit((int) m.length);
final long gpuHandle = resourceManager.getBufferHandle(glId);
if (writeMap && gpuHandle != 0) {
if (st.deferUploads) {
enqueuePreCopied(st.mappedStagingBuffer, gpuHandle, st.mappedOffset, false);
if (cs.deferUploads) {
enqueuePreCopied(staging, gpuHandle, m.offset, false);
} else if (frameManager.getCommandBuffer() != 0) {
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), st.mappedStagingBuffer, gpuHandle, st.mappedOffset, false);
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), staging, gpuHandle, m.offset, false);
}
resourceManager.markBufferContentsDefined(st.mappedBufferGlId);
resourceManager.markBufferContentsDefined(glId);
}
final ByteBuffer old = resourceManager.putPboStaging(glId, st.mappedStagingBuffer);
final ByteBuffer old = resourceManager.putPboStaging(glId, staging);
if (old != null) MemoryUtil.memFree(old);
clearMappedState(st);
return true;
}
if (target == GL31.GL_UNIFORM_BUFFER) {
final ByteBuffer shadow = resourceManager.getUboShadow(st.mappedBufferGlId);
if (writeMap && shadow != null && st.mappedOffset + st.mappedLength <= shadow.capacity()) {
st.mappedStagingBuffer.position(0).limit((int) st.mappedLength);
shadow.position((int) st.mappedOffset).limit((int) (st.mappedOffset + st.mappedLength));
shadow.put(st.mappedStagingBuffer);
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
if (writeMap && shadow != null && m.offset + m.length <= shadow.capacity()) {
staging.position(0).limit((int) m.length);
shadow.position((int) m.offset).limit((int) (m.offset + m.length));
shadow.put(staging);
shadow.position(0).limit(shadow.capacity());
resourceManager.markBufferContentsDefined(st.mappedBufferGlId);
resourceManager.markBufferContentsDefined(glId);
}
MemoryUtil.memFree(st.mappedStagingBuffer);
clearMappedState(st);
MemoryUtil.memFree(staging);
return true;
}
if (!writeMap) {
MemoryUtil.memFree(st.mappedStagingBuffer);
clearMappedState(st);
MemoryUtil.memFree(staging);
return true;
}
st.mappedStagingBuffer.position(0).limit((int) st.mappedLength);
staging.position(0).limit((int) m.length);
final long gpuHandle;
if (st.mappedInvalidate) {
if (m.invalidate) {
final int sdlUsage = FormatMap.mapBufferUsage(0);
final long glSize = Math.max(resourceManager.getBufferSize(st.mappedBufferGlId), st.mappedLength);
final int glUsage = resourceManager.getBufferGlUsage(st.mappedBufferGlId);
final int storageFlags = resourceManager.getBufferStorageFlags(st.mappedBufferGlId);
resourceManager.deleteBuffer(st.mappedBufferGlId);
gpuHandle = resourceManager.createBuffer(st.mappedBufferGlId, sdlUsage, glSize);
resourceManager.recordBufferGlParams(st.mappedBufferGlId, glUsage, storageFlags);
st.bumpAttribStateGen();
final long glSize = Math.max(resourceManager.getBufferSize(glId), m.length);
final int glUsage = resourceManager.getBufferGlUsage(glId);
final int storageFlags = resourceManager.getBufferStorageFlags(glId);
resourceManager.deleteBuffer(glId);
gpuHandle = resourceManager.createBuffer(glId, sdlUsage, glSize);
resourceManager.recordBufferGlParams(glId, glUsage, storageFlags);
cs.bumpAttribStateGen();
} else {
gpuHandle = resourceManager.getBufferHandle(st.mappedBufferGlId);
gpuHandle = resourceManager.getBufferHandle(glId);
}
if (target == GL15.GL_ELEMENT_ARRAY_BUFFER
&& (cs.primitiveRestartEnabled || resourceManager.hasEboShadow(st.mappedBufferGlId))) {
final int len = (int) st.mappedLength;
st.mappedStagingBuffer.position(0).limit(len);
persistentSync.mirrorEboShadow(st.mappedBufferGlId, st.mappedStagingBuffer, (int) st.mappedOffset, len);
st.mappedStagingBuffer.position(0).limit(len);
&& (cs.primitiveRestartEnabled || resourceManager.hasEboShadow(glId))) {
final int len = (int) m.length;
staging.position(0).limit(len);
persistentSync.mirrorEboShadow(glId, staging, (int) m.offset, len);
staging.position(0).limit(len);
}
if (target == GL15.GL_ARRAY_BUFFER && resourceManager.hasArrayShadow(st.mappedBufferGlId)) {
final int len = (int) st.mappedLength;
st.mappedStagingBuffer.position(0).limit(len);
resourceManager.mirrorArrayShadowRegion(st.mappedBufferGlId, st.mappedStagingBuffer, (int) st.mappedOffset);
st.mappedStagingBuffer.position(0).limit(len);
if (target == GL15.GL_ARRAY_BUFFER && resourceManager.hasArrayShadow(glId)) {
final int len = (int) m.length;
staging.position(0).limit(len);
resourceManager.mirrorArrayShadowRegion(glId, staging, (int) m.offset);
staging.position(0).limit(len);
}
if (gpuHandle != 0) {
if (st.deferUploads) {
enqueuePreCopied(st.mappedStagingBuffer, gpuHandle, st.mappedOffset, false);
if (cs.deferUploads) {
enqueuePreCopied(staging, gpuHandle, m.offset, false);
} else if (frameManager.getCommandBuffer() != 0) {
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), st.mappedStagingBuffer, gpuHandle, st.mappedOffset, false);
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), staging, gpuHandle, m.offset, false);
}
resourceManager.markBufferContentsDefined(st.mappedBufferGlId);
resourceManager.markBufferContentsDefined(glId);
}
MemoryUtil.memFree(st.mappedStagingBuffer);
clearMappedState(st);
MemoryUtil.memFree(staging);
return true;
}
private static void clearMappedState(ContextState st) {
st.mappedStagingBuffer = null;
st.mappedBufferGlId = 0;
st.mappedOffset = 0;
st.mappedLength = 0;
st.mappedInvalidate = false;
st.mappedAccessFlags = 0;
}
@Override public ByteBuffer mapBufferRange(int target, long offset, long length, int access) {
final int glId = getBoundBuffer(target);
final ContextState cs = s();
final int glId = cs.getBoundBuffer(target);
if (glId == 0 || length <= 0) return null;
final boolean persistent = (access & GL44.GL_MAP_PERSISTENT_BIT) != 0;
if (persistent) {
if (target == GL15.GL_ARRAY_BUFFER) resourceManager.dropArrayShadow(glId);
final int storageFlags = resourceManager.getBufferStorageFlags(glId);
if (storageFlags < 0 || (storageFlags & GL44.GL_MAP_PERSISTENT_BIT) == 0) {
warnInvalidBufferOp("glMapBufferRange persistent without persistent storage", glId);
return null;
}
final ByteBuffer staging = MemoryUtil.memCalloc((int) length);
final PersistentMapping fresh = new PersistentMapping(staging, offset, length, access);
final PersistentMapping prior = resourceManager.swapPersistentMapping(glId, fresh);
if (prior != null) resourceManager.releasePersistentStaging(prior);
if (!resourceManager.putPersistentMappingIfAbsent(glId, fresh)) {
MemoryUtil.memFree(staging);
warnInvalidBufferOp("glMapBufferRange on mapped buffer", glId);
return null;
}
if (target == GL15.GL_ARRAY_BUFFER) resourceManager.dropArrayShadow(glId);
return staging;
}
final ContextState st = s();
if (st.mappedStagingBuffer != null) MemoryUtil.memFree(st.mappedStagingBuffer);
st.mappedStagingBuffer = MemoryUtil.memAlloc((int) length);
st.mappedBufferGlId = glId;
st.mappedOffset = offset;
st.mappedLength = length;
st.mappedInvalidate = (access & GL30.GL_MAP_INVALIDATE_BUFFER_BIT) != 0;
st.mappedAccessFlags = access;
final ByteBuffer staging = MemoryUtil.memAlloc((int) length);
if ((access & (GL30.GL_MAP_INVALIDATE_BUFFER_BIT | GL30.GL_MAP_INVALIDATE_RANGE_BIT)) == 0) {
seedMappedStaging(target, glId, offset, length, st.mappedStagingBuffer);
seedMappedStaging(target, glId, offset, length, staging);
}
return st.mappedStagingBuffer;
if (!resourceManager.putPlainMappingIfAbsent(glId, staging, offset, length, (access & GL30.GL_MAP_INVALIDATE_BUFFER_BIT) != 0, access)) {
MemoryUtil.memFree(staging);
warnInvalidBufferOp("glMapBufferRange on mapped buffer", glId);
return null;
}
return staging;
}
private void seedMappedStaging(int target, int glId, long offset, long length, ByteBuffer staging) {
@@ -3310,22 +3381,31 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void flushMappedBufferRange(int target, long offset, long length) {
final int glId = getBoundBuffer(target);
if (glId == 0 || length <= 0) return;
persistentSync.onPersistentBufferWrite(glId, offset, length);
if (!persistentSync.onPersistentBufferWrite(glId, offset, length)) {
warnInvalidBufferOp("glFlushMappedBufferRange out of range", glId);
}
}
@Override public void bufferStorage(int target, ByteBuffer data, int flags) {
bufferData(target, data, GL15.GL_DYNAMIC_DRAW);
recordImmutableStorage(target, flags);
final ContextState cs = s();
final int glId = cs.getBoundBuffer(target);
if (glId == 0) return;
if (refuseImmutable(glId, "glBufferStorage on immutable storage")) return;
if (data != null) respecifyStore(cs, target, glId, data, GL15.GL_DYNAMIC_DRAW);
recordImmutableStorage(glId, flags);
}
@Override public void bufferStorage(int target, long size, int flags) {
bufferData(target, size, GL15.GL_DYNAMIC_DRAW);
recordImmutableStorage(target, flags);
final ContextState cs = s();
final int glId = cs.getBoundBuffer(target);
if (glId == 0) return;
if (refuseImmutable(glId, "glBufferStorage on immutable storage")) return;
respecifyStore(cs, target, glId, size, GL15.GL_DYNAMIC_DRAW);
recordImmutableStorage(glId, flags);
}
private void recordImmutableStorage(int target, int flags) {
final int glId = getBoundBuffer(target);
if (glId != 0) resourceManager.recordBufferGlParams(glId, GL15.GL_DYNAMIC_DRAW, flags);
private void recordImmutableStorage(int glId, int flags) {
resourceManager.recordBufferGlParams(glId, GL15.GL_DYNAMIC_DRAW, flags);
}
@Override public void getBufferSubData(int target, long offset, ByteBuffer data) {
final int glId = getBoundBuffer(target);
@@ -3341,23 +3421,12 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private void downloadBufferBlocking(long handle, int offset, int size, ByteBuffer out) {
flushDeferredUploadsForRead();
frameManager.endCopyPassIfActive();
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COPY);
flushAndSubmitMidFrame();
final long cb = SDL_AcquireGPUCommandBuffer(device.getDevice());
if (cb == 0) return;
resourceManager.downloadFromBuffer(cb, handle, offset, size, out);
}
private void flushDeferredUploadsForRead() {
final ContextState st = s();
drainDeferredPersistentRegions(st);
resourceManager.flushUploadArena();
if (transferThread != null && st.frameHighestEnqueuedSeq > transferThread.getSubmittedSeq()) {
transferThread.awaitSubmittedUpTo(st.frameHighestEnqueuedSeq);
}
}
private static void serveFromShadow(ByteBuffer shadow, int srcOffset, ByteBuffer data, int rem) {
final int dstPos = data.position();
if (shadow.isDirect() && data.isDirect()) {
@@ -3394,8 +3463,9 @@ public class SDLGPURenderBackend extends RenderBackend {
}
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
final ContextState st = s();
final boolean plainMapped = st.mappedStagingBuffer != null && st.mappedBufferGlId == glId;
final MappedRange plain = s().mappingScratch;
final boolean plainMapped = resourceManager.peekPlainMapping(glId, plain);
plain.staging = null;
final boolean mapped;
final int accessFlags;
@@ -3408,9 +3478,9 @@ public class SDLGPURenderBackend extends RenderBackend {
mapLength = pm.length;
} else if (plainMapped) {
mapped = true;
accessFlags = st.mappedAccessFlags;
mapOffset = st.mappedOffset;
mapLength = st.mappedLength;
accessFlags = plain.accessFlags;
mapOffset = plain.offset;
mapLength = plain.length;
} else {
mapped = false;
accessFlags = 0;
@@ -3848,14 +3918,18 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public int createBuffers() { return resourceManager.genBuffer(); }
@Override public void namedBufferData(int buffer, long size, int usage) {
if (buffer == 0 || size <= 0) return;
if (refuseImmutable(buffer, "glNamedBufferData on immutable storage")) return;
final int sdlUsage = FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER);
freePlainMappingFor(buffer, true);
resourceManager.deleteBuffer(buffer);
resourceManager.createBuffer(buffer, sdlUsage, size);
resourceManager.recordBufferGlParams(buffer, usage, BufferParams.MUTABLE_STORE);
}
@Override public void namedBufferData(int buffer, ByteBuffer data, int usage) {
if (buffer == 0 || data == null) return;
if (refuseImmutable(buffer, "glNamedBufferData on immutable storage")) return;
final int sdlUsage = FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER);
freePlainMappingFor(buffer, true);
resourceManager.deleteBuffer(buffer);
final long handle = resourceManager.createBuffer(buffer, sdlUsage, data.remaining());
resourceManager.recordBufferGlParams(buffer, usage, BufferParams.MUTABLE_STORE);
@@ -4104,36 +4178,53 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private static final int CLEAR_CHUNK_BYTES = 64 * 1024;
private static final int FILL_CHUNK_BYTES = 4 * 1024 * 1024;
private static boolean isAllZero(ByteBuffer data, int elemBytes) {
final int n = Math.min(elemBytes, data.remaining());
for (int i = 0; i < n; i++) {
if (data.get(data.position() + i) != 0) return false;
}
return true;
}
@Override public void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data) {
final int glId = getBoundBuffer(target);
if (glId == 0 || size <= 0) return;
final long bufSize = resourceManager.getBufferSize(glId);
if (offset < 0 || (bufSize > 0 && (offset > bufSize || size > bufSize - offset))) {
warnInvalidBufferOp("glClearBufferSubData out of range", glId);
return;
}
final long handle = resourceManager.getBufferHandle(glId);
if (handle == 0 || frameManager.getCommandBuffer() == 0) return;
final int elemBytes = Math.max(1, PixelOps.glPixelSize(format, type));
final int chunkBytes = (int) Math.min(size, CLEAR_CHUNK_BYTES);
final boolean defer = s().deferUploads && target != GL43.GL_SHADER_STORAGE_BUFFER;
// Undeferred clears stage one large pattern and upload it repeatedly
final int chunkBytes = (int) Math.min(size, defer ? CLEAR_CHUNK_BYTES : FILL_CHUNK_BYTES);
final int alignedChunk = (chunkBytes / elemBytes) * elemBytes;
if (alignedChunk == 0) return;
resourceManager.markBufferContentsDefined(glId);
final ByteBuffer chunk = MemoryUtil.memAlloc(alignedChunk);
final boolean zero = data == null || isAllZero(data, elemBytes);
final ByteBuffer chunk = zero ? MemoryUtil.memCalloc(alignedChunk) : MemoryUtil.memAlloc(alignedChunk);
try {
for (int i = 0; i < alignedChunk; i++) {
final byte v = (data != null && (i % elemBytes) < data.remaining())
? data.get(data.position() + (i % elemBytes)) : 0;
chunk.put(i, v);
if (!zero) {
for (int i = 0; i < alignedChunk; i++) {
final byte v = (i % elemBytes) < data.remaining() ? data.get(data.position() + (i % elemBytes)) : 0;
chunk.put(i, v);
}
}
chunk.position(0).limit(alignedChunk);
if (!defer) {
resourceManager.fillBuffer(frameManager.ensureCopyPass(), chunk, handle, offset, size);
return;
}
long remaining = size;
long writeOffset = offset;
final boolean defer = s().deferUploads && target != GL43.GL_SHADER_STORAGE_BUFFER;
while (remaining > 0) {
final int n = (int) Math.min(remaining, alignedChunk);
chunk.position(0).limit(n);
if (defer) {
enqueuePreCopied(chunk, handle, writeOffset, false);
} else {
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), chunk, handle, writeOffset, false);
}
enqueuePreCopied(chunk, handle, writeOffset, false);
remaining -= n;
writeOffset += n;
}
@@ -4158,11 +4249,14 @@ public class SDLGPURenderBackend extends RenderBackend {
return;
}
// Volume-sized staging, so it is only reached for 3D formats the compute clear cannot address.
final long totalBytes = (long) mipW * mipH * mipD * bpp;
if (totalBytes <= 0 || totalBytes > Integer.MAX_VALUE) return;
final long cp = frameManager.ensureCopyPass();
if (cp == 0) return;
// A volume-sized staging buffer would allocate and upload each byte (1 GB for Euphoria Patches' floodfill!!)
if (mipD > 1 && resourceManager.zeroTexture3D(cp, texHandle, mipW, mipH, mipD, level, bpp)) {
return;
}
final long totalBytes = (long) mipW * mipH * mipD * bpp;
if (totalBytes <= 0 || totalBytes > Integer.MAX_VALUE) return;
final ByteBuffer zeros = MemoryUtil.memCalloc((int) totalBytes);
try {
if (mipD > 1) {
@@ -4196,6 +4290,10 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void memoryBarrier(int barriers) {
// Graphics storage bindings are read-only on SDL_GPU; RW is compute-pass only
frameManager.endCopyPassIfActive();
// Replayed vertex image writes land when the render pass ends, and a barrier promises them to the draws after it
if ((barriers & (GL42.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL42.GL_TEXTURE_FETCH_BARRIER_BIT)) != 0 && vertexWriteReplayer.hasPending()) {
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COMPUTE);
}
}
@Override public void copyImageSubData(int srcName, int srcTarget, int srcLevel, int srcX, int srcY, int srcZ,
@@ -4342,18 +4440,45 @@ public class SDLGPURenderBackend extends RenderBackend {
voxelizationDispatcher.endBatch(pass);
}
@Override public void setVertexWriteReplay(int graphicsProgram, VertexWriteReplaySetup setup) {
vertexWriteReplayer.setReplay(graphicsProgram, setup);
}
@Override public boolean supportsComputeImageAtomics() {
return device.supportsSpirv() || !device.supportsMsl();
}
@Override public boolean supportsComputeImageAtomics(int width, int height, int depth) {
return supportsComputeImageAtomics() || device.supportsImageAtomics(width, height, depth);
}
private void prepareVertexWrites(ContextState st) {
final ShaderManager.ProgramObject prog = st.boundProgramObj;
if (prog != null && prog.vertexWriteReplay != null) vertexWriteReplayer.ensureImageUsage(prog.vertexWriteReplay);
}
private void captureVertexWrites(ContextState st, int first, int count, int instances, int indexType, long indexOffset, int baseVertex) {
final ShaderManager.ProgramObject prog = st.boundProgramObj;
if (prog != null && prog.vertexWriteReplay != null) {
vertexWriteReplayer.capture(st, first, count, instances, indexType, indexOffset, baseVertex);
if (!frameManager.isRenderPassActive()) vertexWriteReplayer.flush();
}
}
@Override
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
if (count <= 0 || primcount <= 0) return;
final ContextState st = s();
if (st.rasterizerDiscard) return;
if (!frameManager.isFrameActive()) return;
prepareVertexWrites(st);
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
pipelineApplier.ensureDrawRenderPass(st);
if (!frameManager.isRenderPassActive()) return;
if (!pipelineApplier.applyPipelineAndState(st)) return;
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysInstanced", mode, count, 0, first, 0);
SDL_DrawGPUPrimitives(frameManager.getRenderPass(), count, primcount, first, 0);
captureVertexWrites(st, first, count, primcount, 0, 0, 0);
}
@Override
@@ -4464,8 +4589,8 @@ public class SDLGPURenderBackend extends RenderBackend {
long readOffset, size, dstHandle, writeOffset, seq;
@Override
public void accept(PersistentMapping src, PersistentMapping dst) {
enqueueUpload(TransferThread.StagingReadUpload.acquire(src.staging, readOffset, size, dstHandle, writeOffset, seq, false));
if (dst != null) PersistentBufferSync.mirrorPersistentCopy(src.staging, readOffset, dst.staging, writeOffset, size);
enqueueUpload(TransferThread.StagingReadUpload.acquire(src.staging, src.stagingIndex(readOffset), size, dstHandle, writeOffset, seq, false));
if (dst != null) PersistentBufferSync.mirrorPersistentCopy(src, readOffset, dst, writeOffset, size);
}
}
}
@@ -45,6 +45,27 @@ public final class ComputeBinder implements ComputeDispatchSink {
private final ShaderManager shaderManager;
private final SamplerBinder samplerBinder;
private boolean droppedDispatchWarned;
private ShaderManager.ProgramObject lastUniformProgram;
private long lastUniformCb;
private long[] samplerOverrides;
private long[] rwBufferOverrides;
private ByteBuffer[] uniformBlockOverrides;
public void setSamplerOverrides(long[] byUnit) {
samplerOverrides = byUnit;
}
public void setReadWriteStorageBufferOverrides(long[] byGlSlot) {
rwBufferOverrides = byGlSlot;
}
public void setUniformBlockOverrides(ByteBuffer[] byIndex) {
uniformBlockOverrides = byIndex;
}
public long samplerHandle(int samplerObject, int glTexId) {
return samplerBinder.getSampler(samplerObject, glTexId);
}
public ComputeBinder(FrameManager frameManager, ResourceManager resourceManager, ShaderManager shaderManager, SamplerBinder samplerBinder) {
this.frameManager = frameManager;
@@ -153,6 +174,43 @@ public final class ComputeBinder implements ComputeDispatchSink {
@Override
public long beginBatchedComputeDispatch(ContextState st, boolean cycleRwBuffers) {
return beginComputePass(st, cycleRwBuffers, true);
}
public long beginComputePassWithoutStorageBuffers(ContextState st) {
return beginComputePass(st, false, false);
}
public boolean bindRoStorageBufferHandles(long pass, ContextState st, long[] handlesByGlSlot) {
if (pass == 0) return false;
final ShaderManager.ProgramObject prog = shaderManager.getProgram(st.boundProgram);
if (prog == null || !prog.linked) return false;
final int[] slots = prog.computeBindingMap.roSsboGlSlots();
if (slots.length == 0) return true;
try (MemoryStack stack = MemoryStack.stackPush()) {
final PointerBuffer ptrs = stack.mallocPointer(slots.length);
for (int i = 0; i < slots.length; i++) {
long handle = slots[i] < handlesByGlSlot.length ? handlesByGlSlot[slots[i]] : 0;
if (handle == 0) handle = resourceManager.getOrCreateFallbackStorageBuffer();
if (handle == 0) return false;
ptrs.put(i, handle);
}
SDL_BindGPUComputeStorageBuffers(pass, 0, ptrs);
}
return true;
}
public boolean rebindSamplers(long pass, ContextState st) {
if (pass == 0) return false;
final ShaderManager.ProgramObject prog = shaderManager.getProgram(st.boundProgram);
if (prog == null || !prog.linked) return false;
final ShaderManager.ComputeBindingMap map = prog.computeBindingMap;
try (MemoryStack stack = MemoryStack.stackPush()) {
return bindComputeSamplers(st, prog, pass, map.samplerGlSlots(), map.samplerNames(), stack);
}
}
private long beginComputePass(ContextState st, boolean cycleRwBuffers, boolean bindRoBuffers) {
final int programId = st.boundProgram;
if (programId == 0) return 0;
final ShaderManager.ProgramObject prog = shaderManager.getProgram(programId);
@@ -196,7 +254,7 @@ public final class ComputeBinder implements ComputeDispatchSink {
final boolean ok = bindComputeSamplers(st, prog, pass, samplerSlots, map.samplerNames(), stack)
&& bindRoStorageTextures(st, prog, pass, map, stack)
&& bindRoStorageBuffers(st, pass, roBufSlots, stack);
&& (!bindRoBuffers || bindRoStorageBuffers(st, pass, roBufSlots, stack));
if (!ok) {
SDL_EndGPUComputePass(pass);
frameManager.noteComputePassEnded();
@@ -298,7 +356,7 @@ public final class ComputeBinder implements ComputeDispatchSink {
private SDL_GPUStorageBufferReadWriteBinding.Buffer buildRwBufferBindings(ContextState st, int[] slots, MemoryStack stack, boolean cycle) {
final SDL_GPUStorageBufferReadWriteBinding.Buffer buf = SDL_GPUStorageBufferReadWriteBinding.calloc(slots.length, stack);
for (int i = 0; i < slots.length; i++) {
final long handle = resolveStorageBufHandle(st, slots[i]);
final long handle = rwBufferOverrides != null && slots[i] < rwBufferOverrides.length ? rwBufferOverrides[slots[i]] : resolveStorageBufHandle(st, slots[i]);
if (handle == 0) { warnMissingBinding(st, 1, "RW storage buffer slot", slots[i], true); return null; }
buf.get(i).buffer(handle).cycle(cycle);
@@ -319,7 +377,8 @@ public final class ComputeBinder implements ComputeDispatchSink {
if (texHandle == 0) { warnMissingBinding(st, 2, "sampler texture unit", unit, true); return false; }
warnMissingBinding(st, 2, "sampler texture unit", unit, false);
}
long sampHandle = samplerBinder.getSamplerForUnit(st, unit, texGlId);
final long override = samplerOverrides != null && unit >= 0 && unit < samplerOverrides.length ? samplerOverrides[unit] : 0;
long sampHandle = override != 0 ? override : samplerBinder.getSamplerForUnit(st, unit, texGlId);
if (sampHandle == 0) {
sampHandle = resourceManager.getOrCreateDefaultSampler();
if (sampHandle == 0) { warnMissingBinding(st, 3, "sampler unit", unit, true); return false; }
@@ -374,6 +433,28 @@ public final class ComputeBinder implements ComputeDispatchSink {
return true;
}
public ByteBuffer uniformBlockView(ContextState st, ShaderManager.ProgramObject prog, int i) {
final ShaderManager.ComputeBindingMap map = prog.computeBindingMap;
final int[] uboSlots = map.uboGlSlots();
final int[] declaredSizes = map.uboSizes();
if (i >= uboSlots.length) return null;
final int slot = uboSlots[i];
final int bufGlId = st.boundUboByIndex[slot];
if (bufGlId == 0) return null;
final ByteBuffer shadow = resourceManager.getUboShadow(bufGlId);
if (shadow == null) return null;
final int offset = st.uboRangeOffset[slot];
if (offset < 0 || offset >= shadow.capacity()) return null;
int size = (i < declaredSizes.length && declaredSizes[i] > 0) ? declaredSizes[i] : shadow.capacity() - offset;
final int rangeSize = st.uboRangeSize[slot];
if (rangeSize > 0) size = Math.min(size, rangeSize);
size = Math.min(size, shadow.capacity() - offset);
if (size <= 0) return null;
final ByteBuffer view = frameManager.getUboPushView(bufGlId, shadow);
view.position(offset).limit(offset + size);
return view;
}
private void pushComputeUboData(ContextState st, long cb, ShaderManager.ProgramObject prog, int[] uboSlots) {
if (prog.lastComputeUboHash == null || prog.lastComputeUboHash.length != uboSlots.length) {
prog.lastComputeUboHash = new long[uboSlots.length];
@@ -381,16 +462,18 @@ public final class ComputeBinder implements ComputeDispatchSink {
prog.lastComputeFrame = 0;
}
final long curFrame = frameManager.getFrameNumber();
final boolean cacheValid = cb == prog.lastComputeCb && curFrame == prog.lastComputeFrame;
final boolean cacheValid = cb == prog.lastComputeCb && curFrame == prog.lastComputeFrame
&& lastUniformProgram == prog && lastUniformCb == cb;
final long[] cache = prog.lastComputeUboHash;
if (!cacheValid) {
for (int i = 0; i < cache.length; i++) cache[i] = 0L;
prog.lastComputeCb = cb;
prog.lastComputeFrame = curFrame;
}
lastUniformProgram = prog;
lastUniformCb = cb;
final ShaderManager.ComputeBindingMap map = prog.computeBindingMap;
final boolean[] isDefault = map.uboIsDefaultBlock();
final int[] declaredSizes = map.uboSizes();
final UniformStaging us = st.uniformStaging(prog);
for (int i = 0; i < uboSlots.length; i++) {
final ByteBuffer view;
@@ -398,21 +481,13 @@ public final class ComputeBinder implements ComputeDispatchSink {
if (us.fsUniformBuf == null || prog.fragmentUboSize <= 0) continue;
view = us.fsUniformBuf;
view.position(0).limit(prog.fragmentUboSize);
} else if (uniformBlockOverrides != null && i < uniformBlockOverrides.length && uniformBlockOverrides[i] != null) {
view = uniformBlockOverrides[i];
view.position(0);
if (!view.hasRemaining()) continue;
} else {
final int slot = uboSlots[i];
final int bufGlId = st.boundUboByIndex[slot];
if (bufGlId == 0) continue;
final ByteBuffer shadow = resourceManager.getUboShadow(bufGlId);
if (shadow == null) continue;
final int offset = st.uboRangeOffset[slot];
if (offset < 0 || offset >= shadow.capacity()) continue;
int size = (i < declaredSizes.length && declaredSizes[i] > 0) ? declaredSizes[i] : shadow.capacity() - offset;
final int rangeSize = st.uboRangeSize[slot];
if (rangeSize > 0) size = Math.min(size, rangeSize);
size = Math.min(size, shadow.capacity() - offset);
if (size <= 0) continue;
view = frameManager.getUboPushView(bufGlId, shadow);
view.position(offset).limit(offset + size);
view = uniformBlockView(st, prog, i);
if (view == null) continue;
}
final int pos = view.position();
final int len = view.remaining();
@@ -0,0 +1,152 @@
package com.gtnewhorizons.angelica.sdlgpu.compute;
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.shader.cross.CrossCompileCache;
import com.gtnewhorizons.angelica.sdlgpu.shader.msl.MslCrossCompile;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.lwjgl.opengl.GL43;
import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDL_GPUComputePipelineCreateInfo;
import org.lwjgl.sdl.SDL_GPUStorageTextureReadWriteBinding;
import org.lwjgl.sdl.SDL_GPUTextureCreateInfo;
import org.lwjgl.sdl.SDL_GPUTextureRegion;
import org.lwjgl.sdl.SDL_GPUTextureTransferInfo;
import org.lwjgl.sdl.SDL_GPUTransferBufferCreateInfo;
import org.lwjgl.system.JNI;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.system.macosx.ObjCRuntime;
import org.lwjgl.util.shaderc.Shaderc;
import static org.lwjgl.sdl.SDLGPU.*;
public final class ImageAtomicsProbe {
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
private static final String SOURCE = "angelica:sdlgpu/atomic_probe.csh";
public static final int MTL_LANGUAGE_VERSION_3_1 = (3 << 16) + 1;
private static final int GROUPS = 64;
private final long device;
private long pipeline;
public ImageAtomicsProbe(long device) {
this.device = device;
final SpirvCompiler.Result compiled = SpirvCompiler.compile(ShaderLoader.getShaderSource(SOURCE), Shaderc.shaderc_compute_shader, "atomic_probe", SpirvCompiler.Options.vulkanForced460Core(), false);
if (compiled.spirv() == null) {
LOG.warn("Metal image atomics: probe pipeline failed: {}", compiled.error());
return;
}
CrossCompileCache.Output msl = null;
try (MemoryStack stack = MemoryStack.stackPush()) {
ShaderManager.remapSpirvForComputeSDLGPU(compiled.spirv());
msl = MslCrossCompile.compileAtVersion(compiled.spirv(), GL43.GL_COMPUTE_SHADER, MslCrossCompile.SPVC_MSL_3_1);
pipeline = SDL_CreateGPUComputePipeline(device, SDL_GPUComputePipelineCreateInfo.calloc(stack)
.code(msl.code())
.entrypoint(stack.ASCII(msl.entrypoint()))
.format(SDL_GPU_SHADERFORMAT_MSL)
.num_readwrite_storage_textures(1)
.threadcount_x(InvocationDispatch.WORKGROUP_SIZE).threadcount_y(1).threadcount_z(1));
if (pipeline == 0) LOG.warn("Metal image atomics: probe pipeline failed: {}", SDLError.SDL_GetError());
} catch (RuntimeException e) {
LOG.warn("Metal image atomics: probe pipeline failed", e);
} finally {
if (msl != null) MemoryUtil.memFree(msl.code());
MemoryUtil.memFree(compiled.spirv());
}
}
public static int defaultMslVersion() {
try {
final long msgSend = ObjCRuntime.getLibrary().getFunctionAddress("objc_msgSend");
final long cls = ObjCRuntime.objc_getClass("MTLCompileOptions");
if (msgSend == 0 || cls == 0) return 0;
final long options = JNI.invokePPP(cls, ObjCRuntime.sel_getUid("new"), msgSend);
if (options == 0) return 0;
final long version = JNI.invokePPP(options, ObjCRuntime.sel_getUid("languageVersion"), msgSend);
JNI.invokePPV(options, ObjCRuntime.sel_getUid("release"), msgSend);
return (int) version;
} catch (RuntimeException | LinkageError e) {
LOG.warn("Could not read the default Metal language version", e);
return 0;
}
}
public boolean run(int width, int height) {
if (pipeline == 0) return false;
long texture = 0, upload = 0, download = 0, cb = 0;
try (MemoryStack stack = MemoryStack.stackPush()) {
texture = SDL_CreateGPUTexture(device, SDL_GPUTextureCreateInfo.calloc(stack)
.type(SDL_GPU_TEXTURETYPE_2D)
.format(SDL_GPU_TEXTUREFORMAT_R32_INT)
.usage(SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE)
.width(width).height(height).layer_count_or_depth(1).num_levels(1)
.sample_count(SDL_GPU_SAMPLECOUNT_1));
if (texture == 0) return failSdl(width, height, "texture");
upload = SDL_CreateGPUTransferBuffer(device, SDL_GPUTransferBufferCreateInfo.calloc(stack).usage(SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD).size(Integer.BYTES));
download = SDL_CreateGPUTransferBuffer(device, SDL_GPUTransferBufferCreateInfo.calloc(stack).usage(SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD).size(Integer.BYTES));
if (upload == 0 || download == 0) return failSdl(width, height, "transfer buffer");
final long zero = nSDL_MapGPUTransferBuffer(device, upload, false);
if (zero == 0) return failSdl(width, height, "map");
MemoryUtil.memPutInt(zero, 0);
SDL_UnmapGPUTransferBuffer(device, upload);
cb = SDL_AcquireGPUCommandBuffer(device);
if (cb == 0) return failSdl(width, height, "command buffer");
final SDL_GPUTextureRegion region = SDL_GPUTextureRegion.calloc(stack).texture(texture).w(1).h(1).d(1);
long copy = SDL_BeginGPUCopyPass(cb);
SDL_UploadToGPUTexture(copy, SDL_GPUTextureTransferInfo.calloc(stack).transfer_buffer(upload), region, false);
SDL_EndGPUCopyPass(copy);
final SDL_GPUStorageTextureReadWriteBinding.Buffer binding = SDL_GPUStorageTextureReadWriteBinding.calloc(1, stack);
binding.get(0).texture(texture);
final long pass = SDL_BeginGPUComputePass(cb, binding, null);
if (pass == 0) return failSdl(width, height, "compute pass");
SDL_BindGPUComputePipeline(pass, pipeline);
SDL_DispatchGPUCompute(pass, GROUPS, 1, 1);
SDL_EndGPUComputePass(pass);
copy = SDL_BeginGPUCopyPass(cb);
SDL_DownloadFromGPUTexture(copy, region, SDL_GPUTextureTransferInfo.calloc(stack).transfer_buffer(download));
SDL_EndGPUCopyPass(copy);
final boolean submitted = SDL_SubmitGPUCommandBuffer(cb);
cb = 0;
if (!submitted) return failSdl(width, height, "submit");
SDL_WaitForGPUIdle(device);
final long result = nSDL_MapGPUTransferBuffer(device, download, false);
if (result == 0) return failSdl(width, height, "map");
final int adds = MemoryUtil.memGetInt(result);
SDL_UnmapGPUTransferBuffer(device, download);
final int invocations = GROUPS * InvocationDispatch.WORKGROUP_SIZE;
if (adds != invocations) LOG.info("Metal image atomics on {}x{}: {} invocations added {}", width, height, invocations, adds);
return adds == invocations;
} catch (RuntimeException e) {
return fail(width, height, e.toString());
} finally {
if (cb != 0) SDL_CancelGPUCommandBuffer(cb);
if (download != 0) SDL_ReleaseGPUTransferBuffer(device, download);
if (upload != 0) SDL_ReleaseGPUTransferBuffer(device, upload);
if (texture != 0) SDL_ReleaseGPUTexture(device, texture);
}
}
public void release() {
if (pipeline != 0) SDL_ReleaseGPUComputePipeline(device, pipeline);
pipeline = 0;
}
private static boolean failSdl(int width, int height, String what) {
return fail(width, height, what + " failed: " + SDLError.SDL_GetError());
}
private static boolean fail(int width, int height, String reason) {
LOG.warn("Metal image atomics on {}x{}: {}", width, height, reason);
return false;
}
}
@@ -0,0 +1,24 @@
package com.gtnewhorizons.angelica.sdlgpu.compute;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
public final class InvocationDispatch {
static final int MAX_GROUPS_PER_DISPATCH = 65535;
static final int WORKGROUP_SIZE = 64;
@FunctionalInterface
public interface IntUniformWriter {
void write(ContextState st, int location, int value);
}
private InvocationDispatch() {}
static void dispatch(ComputeDispatchSink sink, ContextState st, long pass, long invocations, int invocationBaseLocation, IntUniformWriter writer) {
final long groups = (invocations + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
for (long g0 = 0; g0 < groups; g0 += MAX_GROUPS_PER_DISPATCH) {
if (invocationBaseLocation >= 0) writer.write(st, invocationBaseLocation, (int) (g0 * WORKGROUP_SIZE));
sink.pushPendingComputeUniforms(st);
sink.dispatchInBatch(pass, (int) Math.min(MAX_GROUPS_PER_DISPATCH, groups - g0), 1, 1);
}
}
}
@@ -0,0 +1,635 @@
package com.gtnewhorizons.angelica.sdlgpu.compute;
import com.gtnewhorizons.angelica.glsm.backend.VertexWriteReplaySetup;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL41;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
/**
* Performs the image writes of a vertex shader that SDL-GPU strips (graphics stages may only read storage images).
* Every draw made with a program that has a {@link Replay} is captured: its vertex and index buffers, attribute layout,
* draw range, the uniform values the replay reads and the textures it samples. When the render pass ends, the replay
* compute shader re-runs the vertex shader for each captured vertex and performs the writes.
*/
public final class VertexWriteReplayer {
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
private static final int MAX_CAPTURES = 1 << 16;
private static final int[] COMPONENT_SIZE = { 4, 2, 1, 1, 2, 2, 4, 4, 4, 4, 4, 4 };
private static final int FIRST_PACKED_TYPE = 9;
private static final int PACKED_10F_11F_11F = 11;
private static final int ENABLED = 0x2000;
private static final int BGRA = 0x4000;
private static final int NO_ELEMENT = 0x8000;
private static final int MAX_DIVISOR = 0xFFFF;
private static final int UNCHANGED = -1;
private static final int TEXTURE_UNITS = 32;
private static final int SAMPLER_OBJECT_OF_DRAW = -1;
public static final class Replay {
final int replayProgram;
final VertexWriteReplaySetup setup;
private boolean resolved;
private boolean broken;
int[] inputLocations;
int[] uniformSources;
int[] uniformTargets;
int[] samplerUnits;
IntSupplier[] samplerSources;
int[] samplerObjects;
int[] packStorageSlots;
int[] packRwStorageSlots;
int[] uniformBlockIndices;
int uniformBlockCount;
int locAttr, locAttrDefault, locDraw, locIndex, locRestart, locInvocationBase;
long[] lastHashes;
boolean lastHashesApplied;
public Replay(VertexWriteReplaySetup setup) {
this.replayProgram = setup.replayProgram();
this.setup = setup;
}
IntSupplier[] images() {
return setup.images();
}
boolean resolve(ShaderManager.ProgramObject graphics, ShaderManager.ProgramObject replay) {
if (resolved) return !broken;
resolved = true;
final String[] inputNames = setup.inputNames();
inputLocations = new int[inputNames.length];
for (int i = 0; i < inputNames.length; i++) {
int loc = graphics.resolvedAttribLocations.getInt(inputNames[i]);
if (loc < 0) loc = graphics.attribLocationBindings.getInt(inputNames[i]);
if (loc < 0) loc = setup.inputLocations()[i];
inputLocations[i] = loc;
}
final IntArrayList sources = new IntArrayList();
final IntArrayList targets = new IntArrayList();
final List<String> samplerNames = new ArrayList<>();
for (int target = 0; target < replay.uniformSlotCount; target++) {
final String name = replay.locationName[target];
if (name == null || name.startsWith("_vg_")) continue;
if (replay.locationKind[target] == ShaderManager.ProgramObject.LOCATION_KIND_SAMPLER) {
samplerNames.add(name);
continue;
}
if (replay.locationKind[target] != ShaderManager.ProgramObject.LOCATION_KIND_PLAIN) continue;
final int source = graphics.nameToLocation.getInt(name);
if (source < 0) continue;
sources.add(source);
targets.add(target);
}
uniformSources = sources.toIntArray();
uniformTargets = targets.toIntArray();
lastHashes = new long[uniformSources.length];
resolveSamplers(graphics, replay, samplerNames);
final IntArrayList storage = new IntArrayList();
for (int slot : replay.computeBindingMap.roSsboGlSlots()) {
final boolean vertexBuffer = slot >= setup.vertexBufferBinding() && slot < setup.vertexBufferBinding() + setup.vertexBufferCount();
if (!vertexBuffer && slot != setup.indexBufferBinding()) storage.add(slot);
}
packStorageSlots = storage.toIntArray();
packRwStorageSlots = replay.computeBindingMap.rwSsboGlSlots().clone();
final IntArrayList blocks = new IntArrayList();
final boolean[] isDefault = replay.computeBindingMap.uboIsDefaultBlock();
for (int i = 0; i < replay.computeBindingMap.uboGlSlots().length; i++) {
if (i >= isDefault.length || !isDefault[i]) blocks.add(i);
}
uniformBlockIndices = blocks.toIntArray();
uniformBlockCount = replay.computeBindingMap.uboGlSlots().length;
locAttr = replay.nameToLocation.getInt("_vg_attr");
locAttrDefault = replay.nameToLocation.getInt("_vg_attrDefault");
locDraw = replay.nameToLocation.getInt("_vg_draw");
locIndex = replay.nameToLocation.getInt("_vg_index");
locRestart = replay.nameToLocation.getInt("_vg_restart");
locInvocationBase = replay.nameToLocation.getInt("_vg_invocationBase");
if (locDraw < 0 || locInvocationBase < 0) {
broken = true;
LOG.warn("Vertex write replay program {} is missing its dispatch uniforms; its writes are dropped", replayProgram);
}
return !broken;
}
private void resolveSamplers(ShaderManager.ProgramObject graphics, ShaderManager.ProgramObject replay, List<String> names) {
final IntArrayList units = new IntArrayList();
final List<IntSupplier> sources = new ArrayList<>();
final IntArrayList objects = new IntArrayList();
final IntArrayList ownTextureIndices = new IntArrayList();
final List<String> ownTextureNames = new ArrayList<>();
final List<String> pipelineNames = Arrays.asList(setup.samplerNames());
for (String name : names) {
int unit = graphics.samplerTextureUnits.getInt(name);
if (unit < 0) {
final int i = pipelineNames.indexOf(name);
if (i < 0) continue;
if (setup.samplerTextures()[i] != null && setup.samplerUnits()[i] < 0) {
ownTextureNames.add(name);
ownTextureIndices.add(i);
continue;
}
unit = setup.samplerUnits()[i];
if (unit < 0) continue;
}
replay.samplerTextureUnits.put(name, unit);
if (!units.contains(unit)) {
units.add(unit);
sources.add(null);
objects.add(SAMPLER_OBJECT_OF_DRAW);
}
}
int free = TEXTURE_UNITS - 1;
for (int k = 0; k < ownTextureNames.size(); k++) {
while (free >= 0 && units.contains(free)) free--;
if (free < 0) break;
final int i = ownTextureIndices.getInt(k);
replay.samplerTextureUnits.put(ownTextureNames.get(k), free);
units.add(free);
sources.add(setup.samplerTextures()[i]);
objects.add(setup.samplerObjects()[i]);
}
samplerUnits = units.toIntArray();
samplerSources = sources.toArray(new IntSupplier[0]);
samplerObjects = objects.toIntArray();
}
}
private static final class Capture {
Replay replay;
long[] buffers = new long[0];
int bufferCount;
long indexBuffer;
float[] attr = new float[0];
float[] attrDefault = new float[0];
int[] samplerTextures = new int[0];
long[] samplerHandles = new long[0];
int[] imageTextures = new int[0];
long[] packStorage = new long[0];
long[] packRwStorage = new long[0];
ByteBuffer[] uniformBlocks = new ByteBuffer[0];
int first, count, instances, baseVertex, indexSize, indexOffset, indexCount;
boolean restart;
int restartIndex;
}
private final ShaderManager shaderManager;
private final ResourceManager resourceManager;
private final PipelineApplier pipelineApplier;
private final ComputeBinder computeBinder;
private final Supplier<ContextState> state;
private final ArrayList<Capture> captures = new ArrayList<>();
private int pending;
private float[] uniformValues = new float[4096];
private int[] uniformLengths = new int[256];
private int uniformValueCount;
private int uniformCount;
private boolean flushing;
private final long[] handlesByGlSlot = new long[ContextState.MAX_INDEXED_BUFFERS];
private final long[] rwHandlesByGlSlot = new long[ContextState.MAX_INDEXED_BUFFERS];
private final long[] samplerOverrides = new long[TEXTURE_UNITS];
private final int[] savedImages = new int[ContextState.MAX_IMAGE_UNITS];
private final int[] savedLevels = new int[ContextState.MAX_IMAGE_UNITS];
private final int[] savedAccess = new int[ContextState.MAX_IMAGE_UNITS];
private final int[] savedTextures = new int[TEXTURE_UNITS];
private int uniformCursor;
private int valueCursor;
private final InvocationDispatch.IntUniformWriter invocationBaseWriter = (st, location, value) -> putIntVector(st, location, 1, value, 0, 0, 0);
private final Set<String> warned = new HashSet<>();
public VertexWriteReplayer(ShaderManager shaderManager, ResourceManager resourceManager, PipelineApplier pipelineApplier, ComputeBinder computeBinder, Supplier<ContextState> state) {
this.shaderManager = shaderManager;
this.resourceManager = resourceManager;
this.pipelineApplier = pipelineApplier;
this.computeBinder = computeBinder;
this.state = state;
}
public void setReplay(int graphicsProgram, VertexWriteReplaySetup setup) {
final ShaderManager.ProgramObject prog = shaderManager.getProgram(graphicsProgram);
if (prog == null) return;
if (setup == null) {
prog.vertexWriteReplay = null;
return;
}
if (setup.images().length > ContextState.MAX_IMAGE_UNITS) {
warnOnce("images:" + setup.replayProgram(), "Vertex write replay program " + setup.replayProgram() + " uses " + setup.images().length + " images, more than the " + ContextState.MAX_IMAGE_UNITS + " image units; its writes are dropped");
prog.vertexWriteReplay = null;
return;
}
prog.vertexWriteReplay = new Replay(setup);
}
public void ensureImageUsage(Replay replay) {
final IntSupplier[] images = replay.images();
final int written = replay.setup.writtenImageCount();
for (int i = 0; i < images.length; i++) {
final int texture = images[i].getAsInt();
if (texture != 0) resourceManager.ensureTextureUsage(texture, SDLGPURenderBackend.imageUsageForAccess(imageAccess(i, written)));
}
}
public void capture(ContextState st, int first, int count, int instances, int indexType, long indexOffset, int baseVertex) {
final ShaderManager.ProgramObject graphics = st.boundProgramObj;
final Replay replay = graphics.vertexWriteReplay;
final ShaderManager.ProgramObject twin = shaderManager.getProgram(replay.replayProgram);
if (twin == null || !twin.linked || twin.sdlComputePipeline == 0) return;
if (!replay.resolve(graphics, twin)) return;
if (count <= 0 || instances <= 0) return;
if (pending == MAX_CAPTURES) {
warnOnce("captures", "More than " + MAX_CAPTURES + " draws to replay vertex image writes for in one render pass; the rest are dropped");
return;
}
final IntSupplier[] images = replay.images();
final Capture c = acquire();
c.replay = replay;
if (c.buffers.length != replay.setup.vertexBufferCount()) c.buffers = new long[replay.setup.vertexBufferCount()];
c.bufferCount = 0;
if (!captureInputs(st, replay, c)) return;
if (indexType != 0) {
c.indexBuffer = resourceManager.getBufferHandle(st.currentVao.elementBuffer);
c.indexSize = indexType == GL11.GL_UNSIGNED_BYTE ? 1 : indexType == GL11.GL_UNSIGNED_SHORT ? 2 : 4;
c.indexOffset = (int) indexOffset;
if (c.indexBuffer == 0) return;
final long indexBytes = resourceManager.getBufferSize(st.currentVao.elementBuffer);
c.indexCount = indexBytes <= 0 ? -1 : (int) Math.min(Math.max(0L, indexBytes - indexOffset) / c.indexSize, Integer.MAX_VALUE);
c.restart = st.primitiveRestartEnabled && indexType != GL11.GL_UNSIGNED_BYTE;
c.restartIndex = indexType == GL11.GL_UNSIGNED_SHORT ? st.primitiveRestartSentinel & 0xFFFF : st.primitiveRestartSentinel;
} else {
c.indexBuffer = 0;
c.indexSize = 0;
c.indexOffset = 0;
c.indexCount = 0;
c.restart = false;
}
c.first = first;
c.count = count;
c.instances = instances;
c.baseVertex = baseVertex;
if (c.samplerTextures.length != replay.samplerUnits.length) {
c.samplerTextures = new int[replay.samplerUnits.length];
c.samplerHandles = new long[replay.samplerUnits.length];
}
for (int i = 0; i < replay.samplerUnits.length; i++) {
final int unit = replay.samplerUnits[i];
final IntSupplier source = replay.samplerSources[i];
c.samplerTextures[i] = source != null ? source.getAsInt() : st.boundTextures[unit];
final int samplerObject = replay.samplerObjects[i] != SAMPLER_OBJECT_OF_DRAW ? replay.samplerObjects[i] : st.boundSamplerObjects[unit];
c.samplerHandles[i] = computeBinder.samplerHandle(samplerObject, c.samplerTextures[i]);
}
if (c.imageTextures.length != images.length) c.imageTextures = new int[images.length];
for (int i = 0; i < images.length; i++) c.imageTextures[i] = images[i].getAsInt();
if (c.packStorage.length != replay.packStorageSlots.length) c.packStorage = new long[replay.packStorageSlots.length];
for (int i = 0; i < replay.packStorageSlots.length; i++) {
c.packStorage[i] = resourceManager.getBufferHandle(st.boundSsboByIndex[replay.packStorageSlots[i]]);
}
if (c.packRwStorage.length != replay.packRwStorageSlots.length) c.packRwStorage = new long[replay.packRwStorageSlots.length];
for (int i = 0; i < replay.packRwStorageSlots.length; i++) {
c.packRwStorage[i] = resourceManager.getBufferHandle(st.boundSsboByIndex[replay.packRwStorageSlots[i]]);
}
captureUniformBlocks(st, twin, replay, c);
captureUniforms(st, graphics, replay);
pending++;
}
private boolean captureInputs(ContextState st, Replay replay, Capture c) {
final int n = replay.inputLocations.length;
if (c.attr.length < n * 4) {
c.attr = new float[n * 4];
c.attrDefault = new float[n * 4];
}
final ContextState.VAOState vao = st.currentVao;
for (int k = 0; k < n; k++) {
final int loc = replay.inputLocations[k];
int x = 0, y = 0, z = 0, w = 0;
if (loc >= 0 && loc < ContextState.MAX_VERTEX_ATTRIBS) {
System.arraycopy(st.attribDefaults, loc * 4, c.attrDefault, k * 4, 4);
if (vao.attribEnabled[loc]) {
final int type = componentType(vao.attribType[loc]);
final int binding = vao.attribBinding[loc];
final int buffer = vao.bindingBuffer[binding];
final long handle = type >= 0 ? resourceManager.getBufferHandle(buffer) : 0;
if (type < 0) warnOnce("type:" + vao.attribType[loc], "Vertex write replay cannot read vertex attribute type 0x" + Integer.toHexString(vao.attribType[loc]) + "; that input reads its default value");
if (handle != 0) {
final int slot = bufferSlot(c, handle);
if (slot < 0) {
warnOnce("buffers", "A draw reads more than " + c.buffers.length + " vertex buffers; its vertex image writes are dropped");
return false;
}
final boolean bgra = vao.attribSize[loc] == GL12.GL_BGRA;
final int size = type == PACKED_10F_11F_11F ? 3 : bgra ? 4 : vao.attribSize[loc];
final int elementBytes = type >= FIRST_PACKED_TYPE ? 4 : size * COMPONENT_SIZE[type];
final int stride = vao.bindingStride[binding] != 0 ? vao.bindingStride[binding] : elementBytes;
final long offset = vao.bindingOffset[binding] + vao.attribRelativeOffset[loc];
final int divisor = Math.min(vao.bindingDivisor[binding], MAX_DIVISOR);
final long bufferSize = resourceManager.getBufferSize(buffer);
final boolean noElement = bufferSize > 0 && bufferSize < offset + elementBytes;
x = slot | type << 4 | size << 8 | (vao.attribNormalized[loc] ? 0x800 : 0) | (vao.attribIsInteger[loc] ? 0x1000 : 0)
| ENABLED | (bgra ? BGRA : 0) | (noElement ? NO_ELEMENT : 0) | divisor << 16;
y = (int) offset;
z = stride;
w = lastElement(bufferSize, offset, elementBytes, stride);
}
}
} else {
c.attrDefault[k * 4] = 0f;
c.attrDefault[k * 4 + 1] = 0f;
c.attrDefault[k * 4 + 2] = 0f;
c.attrDefault[k * 4 + 3] = 1f;
}
c.attr[k * 4] = Float.intBitsToFloat(x);
c.attr[k * 4 + 1] = Float.intBitsToFloat(y);
c.attr[k * 4 + 2] = Float.intBitsToFloat(z);
c.attr[k * 4 + 3] = Float.intBitsToFloat(w);
}
return true;
}
static int lastElement(long bufferSize, long offset, int elementBytes, int stride) {
if (bufferSize <= 0) return -1;
final long room = bufferSize - offset - elementBytes;
if (room < 0 || stride <= 0) return 0;
return (int) Math.min(room / stride, 0xFFFFFFFFL);
}
private static int bufferSlot(Capture c, long handle) {
for (int i = 0; i < c.bufferCount; i++) {
if (c.buffers[i] == handle) return i;
}
if (c.bufferCount == c.buffers.length) return -1;
c.buffers[c.bufferCount] = handle;
return c.bufferCount++;
}
static int componentType(int glType) {
return switch (glType) {
case GL11.GL_FLOAT -> 0;
case GL30.GL_HALF_FLOAT -> 1;
case GL11.GL_BYTE -> 2;
case GL11.GL_UNSIGNED_BYTE -> 3;
case GL11.GL_SHORT -> 4;
case GL11.GL_UNSIGNED_SHORT -> 5;
case GL11.GL_INT -> 6;
case GL11.GL_UNSIGNED_INT -> 7;
case GL41.GL_FIXED -> 8;
case GL33.GL_INT_2_10_10_10_REV -> 9;
case GL12.GL_UNSIGNED_INT_2_10_10_10_REV -> 10;
case GL30.GL_UNSIGNED_INT_10F_11F_11F_REV -> PACKED_10F_11F_11F;
default -> -1;
};
}
private void captureUniformBlocks(ContextState st, ShaderManager.ProgramObject twin, Replay replay, Capture c) {
if (c.uniformBlocks.length != replay.uniformBlockCount) c.uniformBlocks = new ByteBuffer[replay.uniformBlockCount];
for (int index : replay.uniformBlockIndices) {
final ByteBuffer view = computeBinder.uniformBlockView(st, twin, index);
if (view == null) {
if (c.uniformBlocks[index] != null) c.uniformBlocks[index].limit(0);
continue;
}
ByteBuffer copy = c.uniformBlocks[index];
if (copy == null || copy.capacity() < view.remaining()) copy = c.uniformBlocks[index] = ByteBuffer.allocateDirect(view.remaining()).order(ByteOrder.nativeOrder());
copy.clear();
copy.put(view);
copy.flip();
}
}
private void captureUniforms(ContextState st, ShaderManager.ProgramObject graphics, Replay replay) {
final UniformStaging us = st.uniformStaging(graphics);
final long[] hashes = us.uniformValueHashBySlot;
for (int u = 0; u < replay.uniformSources.length; u++) {
final int source = replay.uniformSources[u];
final float[] value = us.uniformDataBySlot != null && source < us.uniformDataBySlot.length ? us.uniformDataBySlot[source] : null;
final long hash = hashes != null && source < hashes.length ? hashes[source] : 0L;
if (uniformCount == uniformLengths.length) uniformLengths = Arrays.copyOf(uniformLengths, uniformLengths.length * 2);
if (value != null && replay.lastHashesApplied && hash == replay.lastHashes[u]) {
uniformLengths[uniformCount++] = UNCHANGED;
continue;
}
replay.lastHashes[u] = hash;
final int len = value == null ? 0 : value.length;
uniformLengths[uniformCount++] = len;
if (len == 0) continue;
if (uniformValueCount + len > uniformValues.length) uniformValues = Arrays.copyOf(uniformValues, Math.max(uniformValues.length * 2, uniformValueCount + len));
System.arraycopy(value, 0, uniformValues, uniformValueCount, len);
uniformValueCount += len;
}
replay.lastHashesApplied = true;
}
public boolean hasPending() {
return pending > 0;
}
public void noteIndirectDraw() {
warnOnce("indirect", "An indirect draw used a program whose vertex shader writes images; those writes are dropped");
}
private Capture acquire() {
if (pending == captures.size()) captures.add(new Capture());
return captures.get(pending);
}
public void flush() {
if (pending == 0 || flushing) return;
flushing = true;
final ContextState st = state.get();
final int savedProgram = st.boundProgram;
final ShaderManager.ProgramObject savedProgramObj = st.boundProgramObj;
try {
uniformCursor = 0;
valueCursor = 0;
int start = 0;
while (start < pending) {
final Capture first = captures.get(start);
final Replay replay = first.replay;
int end = start;
while (end < pending && captures.get(end).replay == replay && Arrays.equals(captures.get(end).imageTextures, first.imageTextures)
&& Arrays.equals(captures.get(end).packRwStorage, first.packRwStorage)) end++;
replayRun(st, replay, start, end);
start = end;
}
} finally {
st.boundProgram = savedProgram;
st.boundProgramObj = savedProgramObj;
for (int i = 0; i < pending; i++) captures.get(i).replay = null;
pending = 0;
uniformCount = 0;
uniformValueCount = 0;
flushing = false;
}
}
private void replayRun(ContextState st, Replay replay, int start, int end) {
final ShaderManager.ProgramObject twin = shaderManager.getProgram(replay.replayProgram);
if (twin == null || !twin.linked || twin.sdlComputePipeline == 0) {
skipUniforms(replay, start, end);
return;
}
final Capture first = captures.get(start);
final int[] imageTextures = first.imageTextures;
final int imageCount = imageTextures.length;
final int written = replay.setup.writtenImageCount();
System.arraycopy(st.boundStorageTextureByUnit, 0, savedImages, 0, imageCount);
System.arraycopy(st.boundStorageTextureLevel, 0, savedLevels, 0, imageCount);
System.arraycopy(st.boundStorageTextureAccess, 0, savedAccess, 0, imageCount);
for (int i = 0; i < replay.samplerUnits.length; i++) savedTextures[i] = st.boundTextures[replay.samplerUnits[i]];
computeBinder.setSamplerOverrides(samplerOverrides);
Arrays.fill(rwHandlesByGlSlot, 0L);
for (int i = 0; i < replay.packRwStorageSlots.length; i++) rwHandlesByGlSlot[replay.packRwStorageSlots[i]] = first.packRwStorage[i];
computeBinder.setReadWriteStorageBufferOverrides(rwHandlesByGlSlot);
try {
for (int i = 0; i < imageCount; i++) {
st.boundStorageTextureByUnit[i] = imageTextures[i];
st.boundStorageTextureLevel[i] = 0;
st.boundStorageTextureAccess[i] = imageAccess(i, written);
}
applySamplerTextures(st, replay, captures.get(start));
st.boundProgram = replay.replayProgram;
st.boundProgramObj = twin;
final long pass = computeBinder.beginComputePassWithoutStorageBuffers(st);
if (pass == 0) {
for (int i = start; i < end; i++) applyUniforms(st, replay);
return;
}
try {
for (int i = start; i < end; i++) {
final Capture c = captures.get(i);
if (i > start && applySamplerTextures(st, replay, c)) computeBinder.rebindSamplers(pass, st);
dispatch(st, pass, replay, c);
}
} finally {
computeBinder.endBatchedComputeDispatch(pass);
}
} finally {
computeBinder.setReadWriteStorageBufferOverrides(null);
computeBinder.setUniformBlockOverrides(null);
System.arraycopy(savedImages, 0, st.boundStorageTextureByUnit, 0, imageCount);
System.arraycopy(savedLevels, 0, st.boundStorageTextureLevel, 0, imageCount);
System.arraycopy(savedAccess, 0, st.boundStorageTextureAccess, 0, imageCount);
for (int i = 0; i < replay.samplerUnits.length; i++) {
st.boundTextures[replay.samplerUnits[i]] = savedTextures[i];
samplerOverrides[replay.samplerUnits[i]] = 0L;
}
computeBinder.setSamplerOverrides(null);
}
}
private boolean applySamplerTextures(ContextState st, Replay replay, Capture c) {
boolean changed = false;
for (int i = 0; i < replay.samplerUnits.length; i++) {
final int unit = replay.samplerUnits[i];
if (st.boundTextures[unit] != c.samplerTextures[i] || samplerOverrides[unit] != c.samplerHandles[i]) {
st.boundTextures[unit] = c.samplerTextures[i];
samplerOverrides[unit] = c.samplerHandles[i];
changed = true;
}
}
return changed;
}
private void skipUniforms(Replay replay, int start, int end) {
replay.lastHashesApplied = false;
for (int i = start; i < end; i++) {
for (int u = 0; u < replay.uniformSources.length; u++) valueCursor += Math.max(0, uniformLengths[uniformCursor++]);
}
}
private void dispatch(ContextState st, long pass, Replay replay, Capture c) {
Arrays.fill(handlesByGlSlot, 0L);
for (int i = 0; i < replay.packStorageSlots.length; i++) handlesByGlSlot[replay.packStorageSlots[i]] = c.packStorage[i];
System.arraycopy(c.buffers, 0, handlesByGlSlot, replay.setup.vertexBufferBinding(), c.bufferCount);
handlesByGlSlot[replay.setup.indexBufferBinding()] = c.indexBuffer;
final boolean bound = computeBinder.bindRoStorageBufferHandles(pass, st, handlesByGlSlot);
applyUniforms(st, replay);
if (!bound) return;
final int inputs = replay.inputLocations.length;
putFloats(st, replay.locAttr, c.attr, inputs * 4);
putFloats(st, replay.locAttrDefault, c.attrDefault, inputs * 4);
putIntVector(st, replay.locDraw, 4, c.first, c.count, c.instances, c.baseVertex);
putIntVector(st, replay.locIndex, 3, c.indexSize, c.indexOffset, c.indexCount, 0);
putIntVector(st, replay.locRestart, 2, c.restart ? 1 : 0, c.restartIndex, 0, 0);
computeBinder.setUniformBlockOverrides(c.uniformBlocks);
InvocationDispatch.dispatch(computeBinder, st, pass, (long) c.count * c.instances, replay.locInvocationBase, invocationBaseWriter);
}
private void putFloats(ContextState st, int location, float[] source, int len) {
if (location < 0 || len == 0) return;
final float[] value = pipelineApplier.reuseOrAlloc(st, location, len);
if (value == null) return;
System.arraycopy(source, 0, value, 0, len);
pipelineApplier.putUniform(st, location, value);
}
private void applyUniforms(ContextState st, Replay replay) {
for (int u = 0; u < replay.uniformSources.length; u++) {
final int len = uniformLengths[uniformCursor++];
if (len <= 0) continue;
final float[] value = pipelineApplier.reuseOrAlloc(st, replay.uniformTargets[u], len);
if (value != null) {
System.arraycopy(uniformValues, valueCursor, value, 0, len);
pipelineApplier.putUniform(st, replay.uniformTargets[u], value);
}
valueCursor += len;
}
}
private static int imageAccess(int index, int writtenImageCount) {
return index < writtenImageCount ? GL15.GL_READ_WRITE : GL15.GL_READ_ONLY;
}
private void putIntVector(ContextState st, int location, int count, int x, int y, int z, int w) {
if (location < 0) return;
final float[] value = pipelineApplier.reuseOrAlloc(st, location, count);
if (value == null) return;
value[0] = Float.intBitsToFloat(x);
if (count > 1) value[1] = Float.intBitsToFloat(y);
if (count > 2) value[2] = Float.intBitsToFloat(z);
if (count > 3) value[3] = Float.intBitsToFloat(w);
pipelineApplier.putUniform(st, location, value);
}
private void warnOnce(String key, String message) {
if (warned.add(key)) LOG.warn(message);
}
}
@@ -4,8 +4,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
public final class VoxelizationDispatcher {
static final int MAX_GROUPS_PER_DISPATCH = 65535;
private static final int WORKGROUP_SIZE = 64;
private static final InvocationDispatch.IntUniformWriter UNIFORM_1I = (st, location, value) -> GLStateManager.glUniform1i(location, value);
private final ComputeDispatchSink computeBinder;
@@ -25,12 +24,7 @@ public final class VoxelizationDispatcher {
if (locBase >= 0) GLStateManager.glUniform1i(locBase, rangeBase);
if (locCount >= 0) GLStateManager.glUniform1i(locCount, rangeCount);
if (locTotal >= 0) GLStateManager.glUniform1i(locTotal, vertexTotal);
final int groups = (vertexTotal + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
for (int g0 = 0; g0 < groups; g0 += MAX_GROUPS_PER_DISPATCH) {
if (locInvBase >= 0) GLStateManager.glUniform1i(locInvBase, g0 * WORKGROUP_SIZE);
computeBinder.pushPendingComputeUniforms(st);
computeBinder.dispatchInBatch(pass, Math.min(MAX_GROUPS_PER_DISPATCH, groups - g0), 1, 1);
}
InvocationDispatch.dispatch(computeBinder, st, pass, vertexTotal, locInvBase, UNIFORM_1I);
}
public void endBatch(long pass) {
@@ -1,9 +1,13 @@
package com.gtnewhorizons.angelica.sdlgpu.device;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.backend.BackendOptions;
import com.gtnewhorizons.angelica.glsm.backend.MoltenVK;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.compute.ImageAtomicsProbe;
import com.gtnewhorizons.angelica.sdlgpu.util.DebugMessageRelay;
import it.unimi.dsi.fastutil.longs.Long2BooleanOpenHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.sdl.SDLError;
@@ -20,11 +24,12 @@ import org.lwjgl.system.Platform;
import org.lwjglx.opengl.Display;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import static org.lwjgl.sdl.SDLGPU.*;
import static org.lwjgl.sdl.SDLHints.SDL_HINT_GPU_DRIVER;
import static org.lwjgl.sdl.SDLHints.SDL_HINT_VULKAN_LIBRARY;
import static org.lwjgl.sdl.SDLHints.SDL_SetHint;
import static org.lwjgl.sdl.SDLVideo.*;
import static org.lwjgl.system.MemoryUtil.memUTF8;
@@ -46,6 +51,9 @@ public final class Device {
private String driverName;
private boolean fencePollEnabled;
private boolean fenceQueryInverted;
private int defaultMslVersion;
private ImageAtomicsProbe imageAtomicsProbe;
private final Long2BooleanOpenHashMap imageAtomicsBySize = new Long2BooleanOpenHashMap();
private final FenceReleaser fenceReleaser = new FenceReleaser(fence -> device == 0 || FenceWait.isSignaled(this, fence), fence -> { if (device != 0) SDL_ReleaseGPUFence(device, fence); });
private String deviceName;
private String driverVersion;
@@ -82,9 +90,23 @@ public final class Device {
throw new GpuDeviceLostException(operation, err);
}
// Default to vulkan for now until D3D is more tested
static String resolveDriverName(String userHint, boolean windows) {
return userHint.isEmpty() && windows ? "vulkan" : null;
static String[] driverCandidates(String requested, Platform platform) {
final String[] platformOrder = switch (platform) {
case MACOSX -> new String[] { "metal", "vulkan" };
case WINDOWS -> new String[] { "vulkan", "direct3d12" };
default -> new String[] { "vulkan" };
};
if (requested.isEmpty()) return platformOrder;
final List<String> candidates = new ArrayList<>(platformOrder.length + 1);
candidates.add(requested);
for (final String driver : platformOrder) {
if (!driver.equalsIgnoreCase(requested)) candidates.add(driver);
}
return candidates.toArray(new String[0]);
}
static boolean isVulkan(String driverName) {
return "vulkan".equalsIgnoreCase(driverName);
}
static boolean isMetal(String driverName) {
@@ -103,17 +125,16 @@ public final class Device {
final int ver = SDLVersion.SDL_GetVersion();
LOG.info("SDL runtime version {}.{}.{} (raw {})", SDLVersion.SDL_VERSIONNUM_MAJOR(ver), SDLVersion.SDL_VERSIONNUM_MINOR(ver), SDLVersion.SDL_VERSIONNUM_MICRO(ver), ver);
final String driverHint = SystemProperties.SDL_GPU_DRIVER;
if (!driverHint.isEmpty()) {
SDL_SetHint(SDL_HINT_GPU_DRIVER, driverHint);
LOG.info("SDL GPU driver hint set to '{}'", driverHint);
final String requestedDriver = BackendOptions.sdlGpuDriver();
final String[] candidates = driverCandidates(requestedDriver, Platform.get());
if (!requestedDriver.isEmpty()) {
LOG.info("SDL GPU driver requested: '{}'", requestedDriver);
}
final boolean gpuDebug = SystemProperties.LWJGL_DEBUG || SystemProperties.SDL_GPU_DEBUG;
final String forcedDriver = resolveDriverName(driverHint, Platform.get() == Platform.WINDOWS);
device = createGPUDevice(gpuDebug, forcedDriver);
device = createGPUDevice(gpuDebug, candidates);
if (device == 0 && VideoDriverRecovery.shouldRetryOnX11(SDL_GetCurrentVideoDriver(), vulkanLoaderAvailable(), System.getenv("DISPLAY"))) {
device = VideoDriverRecovery.retryUnderX11(() -> createGPUDevice(gpuDebug, forcedDriver));
device = VideoDriverRecovery.retryUnderX11(() -> createGPUDevice(gpuDebug, candidates));
}
if (device == 0) {
logDeviceCreationFailure(REQUESTED_SHADER_FORMATS);
@@ -128,7 +149,7 @@ public final class Device {
driverVersion = SDLProperties.SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_DRIVER_VERSION_STRING, "");
driverInfo = SDLProperties.SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_DRIVER_INFO_STRING, "");
LOG.info("SDL GPU device created: gpu={}, driver={} {}, video={}, formats=0x{}, debug={}, forcedDriver={}", deviceName, driverName, driverVersion, SDL_GetCurrentVideoDriver(), Integer.toHexString(supportedShaderFormats), gpuDebug, forcedDriver);
LOG.info("SDL GPU device created: gpu={}, driver={} {}, video={}, formats=0x{}, debug={}, requestedDriver={}", deviceName, driverName, driverVersion, SDL_GetCurrentVideoDriver(), Integer.toHexString(supportedShaderFormats), gpuDebug, requestedDriver);
if (metalNeedsNewerSdl(driverName, ver)) {
LOG.error("SDL GPU on Metal requires SDL 3.4.6 or newer (have {}.{}.{}); falling back to OpenGL", SDLVersion.SDL_VERSIONNUM_MAJOR(ver), SDLVersion.SDL_VERSIONNUM_MINOR(ver), SDLVersion.SDL_VERSIONNUM_MICRO(ver));
@@ -137,10 +158,36 @@ public final class Device {
}
probeFences(ver);
readMetalLanguageVersion();
return true;
}
private void readMetalLanguageVersion() {
if (!isMetal(driverName)) return;
defaultMslVersion = ImageAtomicsProbe.defaultMslVersion();
if (metalTextureAtomics()) {
LOG.info("Metal image atomics: MSL 3.1 available, checked per image size");
} else {
LOG.info("Metal image atomics: unavailable, this Java runtime compiles Metal shaders as MSL {}.{} and they need 3.1. A runtime built with the macOS 14 SDK or newer enables them.", defaultMslVersion >>> 16, defaultMslVersion & 0xFFFF);
}
}
public boolean metalTextureAtomics() {
return defaultMslVersion >= ImageAtomicsProbe.MTL_LANGUAGE_VERSION_3_1;
}
public boolean supportsImageAtomics(int width, int height, int depth) {
if (!metalTextureAtomics() || width <= 0 || height <= 0 || depth > 1) return false;
final long key = (long) width << 32 | height;
if (imageAtomicsBySize.containsKey(key)) return imageAtomicsBySize.get(key);
if (imageAtomicsProbe == null) imageAtomicsProbe = new ImageAtomicsProbe(device);
final boolean ok = imageAtomicsProbe.run(width, height);
if (ok) LOG.info("Metal image atomics on {}x{}: ok", width, height);
imageAtomicsBySize.put(key, ok);
return ok;
}
private void probeFences(int ver) {
fencePollEnabled = FenceWait.pollEnabled(driverName, ver);
final boolean expectInverted = isMetal(driverName) && ver == SDLVersion.SDL_VERSIONNUM(3, 4, 14);
@@ -151,13 +198,29 @@ public final class Device {
}
}
private long createGPUDevice(boolean gpuDebug, String forcedDriver) {
long dev = SDL_CreateGPUDevice(REQUESTED_SHADER_FORMATS, gpuDebug, forcedDriver);
if (dev == 0 && forcedDriver != null) {
LOG.warn("Failed to create a '{}' SDL GPU device ({}); falling back to SDL's default backend. Set -D{} to pin one.", forcedDriver, SDLError.SDL_GetError(), SystemProperties.KEY_SDL_GPU_DRIVER);
dev = SDL_CreateGPUDevice(REQUESTED_SHADER_FORMATS, gpuDebug, (CharSequence) null);
private static void useMoltenVk() {
final String path = MoltenVK.locate();
if (path == null || MoltenVK.source() == MoltenVK.Source.ENV) return;
if (SDL_SetHint(SDL_HINT_VULKAN_LIBRARY, path)) {
LOG.info("MoltenVK: {}", path);
} else {
LOG.warn("Could not point SDL at MoltenVK {}: {}", path, SDLError.SDL_GetError());
}
return dev;
}
private long createGPUDevice(boolean gpuDebug, String[] candidates) {
final boolean macos = Platform.get() == Platform.MACOSX;
for (final String driver : candidates) {
final boolean macVulkan = macos && isVulkan(driver);
if (macVulkan) useMoltenVk();
final long dev = SDL_CreateGPUDevice(REQUESTED_SHADER_FORMATS, gpuDebug, driver);
if (dev != 0) return dev;
LOG.warn("Failed to create a '{}' SDL GPU device: {}", driver, SDLError.SDL_GetError());
if (macVulkan && MoltenVK.locate() == null) {
LOG.warn("Vulkan on macOS needs MoltenVK. Add {} to the mods folder or install MoltenVK.", MoltenVK.JAR_NAME);
}
}
return 0;
}
public void claimWindow(long window) {
@@ -213,7 +276,7 @@ public final class Device {
}
}
}
LOG.error("Continuing on OpenGL. Set -D{}=false to skip this probe entirely.", SystemProperties.KEY_USE_SDL_GPU);
LOG.error("Continuing on OpenGL. Set Video Settings > Renderer > Backend to OpenGL, or -D{}=false, to skip this probe entirely.", SystemProperties.KEY_USE_SDL_GPU);
}
public void destroyDevice() {
@@ -224,11 +287,15 @@ public final class Device {
claimed = false;
}
fenceReleaser.releaseAll();
if (imageAtomicsProbe != null) imageAtomicsProbe.release();
imageAtomicsProbe = null;
SDL_DestroyGPUDevice(device);
device = 0;
driverName = null;
fencePollEnabled = false;
fenceQueryInverted = false;
defaultMslVersion = 0;
imageAtomicsBySize.clear();
deviceName = null;
driverVersion = null;
driverInfo = null;
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.sdlgpu.frame;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import com.gtnewhorizons.angelica.sdlgpu.resource.MappedRange;
import com.gtnewhorizons.angelica.sdlgpu.resource.PackState;
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpVariant;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
@@ -314,12 +315,7 @@ public final class ContextState {
public final long[] computeBatchRwTexs = new long[MAX_IMAGE_UNITS];
public final int[] computeBatchRwTexLevels = new int[MAX_IMAGE_UNITS];
public int mappedBufferGlId;
public ByteBuffer mappedStagingBuffer;
public long mappedOffset;
public long mappedLength;
public boolean mappedInvalidate;
public int mappedAccessFlags;
public final MappedRange mappingScratch = new MappedRange();
public final SDL_GPUTextureSamplerBinding.Buffer fragSamplerBindings = SDL_GPUTextureSamplerBinding.calloc(MAX_SAMPLERS);
public final SDL_GPUTextureSamplerBinding.Buffer vertSamplerBindings = SDL_GPUTextureSamplerBinding.calloc(MAX_SAMPLERS);
@@ -381,11 +381,14 @@ public final class FrameManager {
private volatile Runnable preRenderPassHook;
private volatile Runnable renderPassEndHook;
private volatile Runnable beforeSubmit;
private volatile Runnable afterPresent;
public void setPreRenderPassHook(Runnable hook) { this.preRenderPassHook = hook; }
public void setRenderPassEndHook(Runnable hook) { this.renderPassEndHook = hook; }
public void noteComputePassBegun() {
final FrameState f = frame();
f.computePassesThisFrame++;
@@ -472,6 +475,8 @@ public final class FrameManager {
f.currentColorTarget = 0;
f.currentDepthTarget = 0;
f.activeLayoutHash = 0;
final Runnable hook = renderPassEndHook;
if (hook != null) hook.run();
}
}
@@ -173,8 +173,9 @@ public final class FFPTrace {
sb.append(" | ffpUbo=buffer").append(glId).append(" noStaging");
return;
}
final int base = st.uboRangeOffset[binding];
sb.append(" | ffpUbo=buffer").append(glId).append(" base=").append(base);
final int rangeOffset = st.uboRangeOffset[binding];
final int base = pm != null ? (int) pm.stagingIndex(rangeOffset) : rangeOffset;
sb.append(" | ffpUbo=buffer").append(glId).append(" base=").append(rangeOffset);
appendUboFloat(buf, base, FFPUniformBlock.ALPHA_REF, " u_AlphaRef=");
appendUboMatrix(buf, base, FFPUniformBlock.MVP_MATRIX, " u_MVPMatrix=");
appendUboMatrix(buf, base, FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, " u_LightmapTextureMatrix=");
@@ -698,8 +698,8 @@ public final class PipelineApplier {
final int offset = st.uboRangeOffset[binding];
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
if (pm != null) {
if (offset < 0 || offset + size > pm.staging.capacity()) return 0L;
return MemoryUtil.memAddress0(pm.staging) + offset;
if (!pm.covers(offset, size)) return 0L;
return MemoryUtil.memAddress0(pm.staging) + pm.stagingIndex(offset);
}
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
if (shadow != null) {
@@ -8,16 +8,13 @@ public final class ByteRegionCopy {
private ByteRegionCopy() {}
public static void copyByteRegion(ByteBuffer src, int srcOff, ByteBuffer dst, int dstOff, int len) {
if ((srcOff | dstOff | len) < 0 || srcOff + (long) len > src.capacity() || dstOff + (long) len > dst.capacity()) {
throw new IllegalStateException("copyByteRegion out of bounds: len=" + len + " srcOff=" + srcOff + " src.capacity=" + src.capacity() + " dstOff=" + dstOff + " dst.capacity=" + dst.capacity());
}
if (src.isDirect() && dst.isDirect()) {
MemoryUtil.memCopy(MemoryUtil.memAddress(src, srcOff), MemoryUtil.memAddress(dst, dstOff), len);
return;
}
if (src.hasArray() && dst.hasArray()) {
System.arraycopy(src.array(), src.arrayOffset() + srcOff, dst.array(), dst.arrayOffset() + dstOff, len);
return;
}
for (int i = 0; i < len; i++) {
dst.put(dstOff + i, src.get(srcOff + i));
}
dst.put(dstOff, src, srcOff, len);
}
}
@@ -0,0 +1,12 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import java.nio.ByteBuffer;
public final class MappedRange {
public int glId;
public ByteBuffer staging;
public long offset;
public long length;
public boolean invalidate;
public int accessFlags;
}
@@ -33,11 +33,14 @@ public final class PersistentBufferSync {
this.sink = sink;
}
public void onPersistentBufferWrite(int glId, long offset, long size) {
public boolean onPersistentBufferWrite(int glId, long offset, long size) {
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
if (pm != null && pm.markDirty(offset, size)) {
if (pm == null || size == 0) return true;
if (offset < 0 || size < 0 || offset > pm.length || size > pm.length - offset) return false;
if (pm.markDirty(offset, size)) {
resourceManager.trackPersistentDirty();
}
return true;
}
public void uploadDirtyPersistentRegion() { processDirtyPersistentRegions(false); }
@@ -59,30 +62,41 @@ public final class PersistentBufferSync {
for (int i = 0; i < n; i++) {
final PersistentMapping pm = vals.get(i);
if (!pm.isDirty()) continue;
final long gpuHandle = resourceManager.getBufferHandle(keys.getInt(i));
if (gpuHandle == 0) continue;
final long claimed = pm.claimDirty();
if (PersistentMapping.isClean(claimed)) continue;
final long off = PersistentMapping.rangeOffset(claimed);
final long size = PersistentMapping.rangeSize(claimed);
if (Tracy.ENABLED) frameManager.notePersistentDrain();
if (defer) {
final long seq = sink.nextSeq();
pm.lastEnqueuedSeq = seq;
sink.enqueue(TransferThread.StagingReadUpload.acquire(pm.staging, off, size, gpuHandle, off, seq, false));
enqueued++;
} else {
pm.staging.position((int) off).limit((int) (off + size));
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), pm.staging, gpuHandle, off, false);
pm.staging.clear();
final int glId = keys.getInt(i);
long copyPass = 0L;
if (!defer) {
if (resourceManager.getBufferHandle(glId) == 0) continue;
copyPass = frameManager.ensureCopyPass();
}
resourceManager.beginPersistentDrain();
try {
if (resourceManager.getPersistentMapping(glId) != pm) continue;
final long gpuHandle = resourceManager.getBufferHandle(glId);
if (gpuHandle == 0) continue;
final long claimed = pm.claimDirty();
if (PersistentMapping.isClean(claimed)) continue;
resourceManager.clearPersistentDirty();
final long off = PersistentMapping.rangeOffset(claimed);
final long size = PersistentMapping.rangeSize(claimed);
if (Tracy.ENABLED) frameManager.notePersistentDrain();
if (defer) {
final long seq = sink.nextSeq();
pm.lastEnqueuedSeq = seq;
sink.enqueue(TransferThread.StagingReadUpload.acquire(pm.staging, off, size, gpuHandle, pm.offset + off, seq, false));
enqueued++;
} else {
resourceManager.uploadRangeToBuffer(copyPass, pm.staging, off, size, gpuHandle, pm.offset + off, false);
}
} finally {
resourceManager.endPersistentDrain();
}
resourceManager.clearPersistentDirty();
}
return enqueued;
}
public static void mirrorPersistentCopy(ByteBuffer srcStaging, long srcOffset, ByteBuffer dstStaging, long dstOffset, long size) {
ByteRegionCopy.copyByteRegion(srcStaging, (int) srcOffset, dstStaging, (int) dstOffset, (int) size);
public static void mirrorPersistentCopy(PersistentMapping src, long readOffset, PersistentMapping dst, long writeOffset, long size) {
if (!src.covers(readOffset, size) || !dst.covers(writeOffset, size)) return;
ByteRegionCopy.copyByteRegion(src.staging, (int) src.stagingIndex(readOffset), dst.staging, (int) dst.stagingIndex(writeOffset), (int) size);
}
public void mirrorEboShadow(int glId, ByteBuffer src, int dstOffset, int len) {
@@ -24,6 +24,12 @@ public final class PersistentMapping {
public static long rangeSize(long packed) { return (packed & 0xFFFFFFFFL) - (packed >>> 32); }
static long packRange(long min, long max) { return (min << 32) | (max & 0xFFFFFFFFL); }
public long stagingIndex(long bufferOffset) { return bufferOffset - offset; }
public boolean covers(long bufferOffset, long size) {
return size >= 0 && bufferOffset >= offset && bufferOffset - offset <= length && size <= length - (bufferOffset - offset);
}
public boolean isDirty() { return dirtyRange.get() != CLEAN; }
public long claimDirty() { return dirtyRange.getAndSet(CLEAN); }
@@ -31,6 +31,7 @@ import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -96,6 +97,9 @@ public final class ResourceManager {
private final Int2ObjectOpenHashMap<ContextState.VAOState> vaoStates = new Int2ObjectOpenHashMap<>();
private final Int2ObjectOpenHashMap<PersistentMapping> persistentMappings = new Int2ObjectOpenHashMap<>();
private final Int2ObjectOpenHashMap<MappedRange> plainMappings = new Int2ObjectOpenHashMap<>();
private MappedRange[] plainMappingPool = new MappedRange[4];
private int plainMappingPoolCount;
private final Int2ObjectOpenHashMap<ByteBuffer> uboShadows = new Int2ObjectOpenHashMap<>();
private final Int2ObjectOpenHashMap<ByteBuffer> eboShadows = new Int2ObjectOpenHashMap<>();
private final IntOpenHashSet eboShadowWanted = new IntOpenHashSet();
@@ -244,6 +248,7 @@ public final class ResourceManager {
}
private long createTextureCore(int glId, int glTarget, int sdlFormat, int glFormat, int width, int height, int depth, int levels, int usage) {
if (getTextureHandle(glId) != 0) beforeTextureReplaced();
usage = reconcileUsageAndWarn(glId, usage);
final int sdlType = mapTextureType(glTarget);
if (!SDL_GPUTextureSupportsFormat(device.getDevice(), sdlFormat, sdlType, usage)) {
@@ -305,7 +310,19 @@ public final class ResourceManager {
return sdlFormat;
}
private Runnable textureReplaceHook;
public void setTextureReplaceHook(Runnable hook) {
textureReplaceHook = hook;
}
private void beforeTextureReplaced() {
final Runnable hook = textureReplaceHook;
if (hook != null) hook.run();
}
public void deleteTexture(int glId) {
beforeTextureReplaced();
final long handle;
wLock.lock();
try {
@@ -336,6 +353,7 @@ public final class ResourceManager {
}
public void releaseTextureHandleForRealloc(int glId) {
beforeTextureReplaced();
final long handle;
wLock.lock();
try {
@@ -752,6 +770,7 @@ public final class ResourceManager {
public void deleteBuffer(int glId) {
final ByteBuffer droppedPboStaging;
final ByteBuffer droppedPlainStaging;
final PersistentMapping droppedPm;
wLock.lock();
try {
@@ -763,6 +782,7 @@ public final class ResourceManager {
undefinedContentBuffers.remove(glId);
droppedPm = persistentMappings.remove(glId);
if (droppedPm != null) mappingsVersion++;
droppedPlainStaging = removePlainMappingLocked(glId);
droppedPboStaging = pboStagingData.remove(glId);
deleteUboShadow(glId);
deleteEboShadow(glId);
@@ -777,8 +797,11 @@ public final class ResourceManager {
if (droppedPboStaging != null) {
memFree(droppedPboStaging);
}
if (droppedPlainStaging != null) {
memFree(droppedPlainStaging);
}
untrackPersistentDirty(droppedPm);
releasePersistentStaging(droppedPm);
releasePersistentStaging(droppedPm, glId, "delete");
}
public void flushDeferredReleases() {
@@ -953,10 +976,81 @@ public final class ResourceManager {
try { vaoStates.remove(glId); } finally { wLock.unlock(); }
}
public void putPersistentMapping(int bufferGlId, PersistentMapping m) {
public boolean putPersistentMappingIfAbsent(int glId, PersistentMapping pm) {
wLock.lock();
try { persistentMappings.put(bufferGlId, m); mappingsVersion++; } finally { wLock.unlock(); }
try {
if (plainMappings.containsKey(glId) || persistentMappings.putIfAbsent(glId, pm) != null) return false;
mappingsVersion++;
return true;
} finally { wLock.unlock(); }
}
public boolean putPlainMappingIfAbsent(int glId, ByteBuffer staging, long offset, long length, boolean invalidate, int accessFlags) {
wLock.lock();
try {
if (plainMappings.containsKey(glId) || persistentMappings.containsKey(glId)) return false;
final MappedRange m;
if (plainMappingPoolCount > 0) {
m = plainMappingPool[--plainMappingPoolCount];
plainMappingPool[plainMappingPoolCount] = null;
} else {
m = new MappedRange();
}
m.glId = glId;
m.staging = staging;
m.offset = offset;
m.length = length;
m.invalidate = invalidate;
m.accessFlags = accessFlags;
plainMappings.put(glId, m);
return true;
} finally { wLock.unlock(); }
}
public boolean takePlainMapping(int glId, MappedRange out) {
wLock.lock();
try {
final MappedRange m = plainMappings.get(glId);
if (m == null) return false;
copyMappedRange(m, out);
removePlainMappingLocked(glId);
return true;
} finally { wLock.unlock(); }
}
public ByteBuffer takePlainStaging(int glId) {
wLock.lock();
try { return removePlainMappingLocked(glId); } finally { wLock.unlock(); }
}
public boolean peekPlainMapping(int glId, MappedRange out) {
if (fastRead()) return copyMappedRange(plainMappings.get(glId), out);
rLock.lock();
try { return copyMappedRange(plainMappings.get(glId), out); } finally { rLock.unlock(); }
}
private static boolean copyMappedRange(MappedRange m, MappedRange out) {
if (m == null) return false;
out.glId = m.glId;
out.staging = m.staging;
out.offset = m.offset;
out.length = m.length;
out.invalidate = m.invalidate;
out.accessFlags = m.accessFlags;
return true;
}
private ByteBuffer removePlainMappingLocked(int glId) {
final MappedRange m = plainMappings.remove(glId);
if (m == null) return null;
final ByteBuffer staging = m.staging;
m.staging = null;
if (plainMappingPoolCount == plainMappingPool.length) plainMappingPool = Arrays.copyOf(plainMappingPool, plainMappingPoolCount * 2);
plainMappingPool[plainMappingPoolCount++] = m;
return staging;
}
public void beginPersistentDrain() { rLock.lock(); }
public void endPersistentDrain() { rLock.unlock(); }
public PersistentMapping getPersistentMapping(int bufferGlId) {
if (fastRead()) return persistentMappings.get(bufferGlId);
rLock.lock();
@@ -965,17 +1059,13 @@ public final class ResourceManager {
public PersistentMapping removePersistentMapping(int bufferGlId) {
final PersistentMapping dropped;
wLock.lock();
try { dropped = persistentMappings.remove(bufferGlId); mappingsVersion++; } finally { wLock.unlock(); }
try {
dropped = persistentMappings.remove(bufferGlId);
if (dropped != null) mappingsVersion++;
} finally { wLock.unlock(); }
untrackPersistentDirty(dropped);
return dropped;
}
public PersistentMapping swapPersistentMapping(int bufferGlId, PersistentMapping fresh) {
final PersistentMapping prior;
wLock.lock();
try { prior = persistentMappings.put(bufferGlId, fresh); mappingsVersion++; } finally { wLock.unlock(); }
untrackPersistentDirty(prior);
return prior;
}
private volatile int mappingsVersion;
@@ -989,8 +1079,9 @@ public final class ResourceManager {
if (pm != null && !PersistentMapping.isClean(pm.claimDirty())) persistentDirtyCount.decrementAndGet();
}
public void releasePersistentStaging(PersistentMapping pm) {
public void releasePersistentStaging(PersistentMapping pm, int glId, String cause) {
if (pm == null || pm.staging == null) return;
if (LOG.isDebugEnabled()) LOG.debug("Persistent mapping released: buffer {} ({} KiB) via {}", glId, pm.length >> 10, cause);
final TransferThread tt = transferThread;
final long seq = pm.lastEnqueuedSeq;
if (tt != null && seq > tt.getSubmittedSeq()) tt.freeAfterSeq(pm.staging, seq);
@@ -1001,24 +1092,20 @@ public final class ResourceManager {
rLock.lock();
try {
final PersistentMapping srcPm = persistentMappings.get(srcGlId);
if (srcPm == null) return false;
final ByteBuffer slice = srcPm.staging.duplicate();
slice.position((int) readOffset).limit((int) (readOffset + size));
uploadToBuffer(copyPass, slice, dstHandle, writeOffset, false);
if (srcPm == null || !srcPm.covers(readOffset, size)) return false;
uploadRangeToBuffer(copyPass, srcPm.staging, srcPm.stagingIndex(readOffset), size, dstHandle, writeOffset, false);
final PersistentMapping dstPm = persistentMappings.get(dstGlId);
if (dstPm != null) {
PersistentBufferSync.mirrorPersistentCopy(srcPm.staging, readOffset, dstPm.staging, writeOffset, size);
}
if (dstPm != null) PersistentBufferSync.mirrorPersistentCopy(srcPm, readOffset, dstPm, writeOffset, size);
return true;
} finally { rLock.unlock(); }
}
public boolean enqueuePersistentCopyDeferred(int srcGlId, int dstGlId, long seq,
public boolean enqueuePersistentCopyDeferred(int srcGlId, int dstGlId, long seq, long readOffset, long size,
BiConsumer<PersistentMapping, PersistentMapping> inLock) {
rLock.lock();
try {
final PersistentMapping srcPm = persistentMappings.get(srcGlId);
if (srcPm == null) return false;
if (srcPm == null || !srcPm.covers(readOffset, size)) return false;
final PersistentMapping dstPm = persistentMappings.get(dstGlId);
srcPm.lastEnqueuedSeq = seq;
inLock.accept(srcPm, dstPm);
@@ -1192,7 +1279,7 @@ public final class ResourceManager {
public void mirrorArrayShadowFull(int glId, ByteBuffer data) {
final int len = data.remaining();
final ByteBuffer shadow = getOrAllocArrayShadow(glId, len);
copyShadowRegion(data, data.position(), shadow, 0, len);
ByteRegionCopy.copyByteRegion(data, data.position(), shadow, 0, len);
shadow.position(0).limit(len);
}
@@ -1206,7 +1293,7 @@ public final class ResourceManager {
removeArrayShadowLocked(glId);
return;
}
copyShadowRegion(data, data.position(), shadow, dstOffset, len);
ByteRegionCopy.copyByteRegion(data, data.position(), shadow, dstOffset, len);
} finally { wLock.unlock(); }
}
@@ -1226,18 +1313,6 @@ public final class ResourceManager {
arrayShadowWanted.remove(glId);
}
private static void copyShadowRegion(ByteBuffer src, int srcOff, ByteBuffer dst, int dstOff, int len) {
if (src.isDirect() && dst.isDirect()) {
MemoryUtil.memCopy(MemoryUtil.memAddress(src) + srcOff, MemoryUtil.memAddress(dst) + dstOff, len);
return;
}
final ByteBuffer s = src.duplicate();
s.position(srcOff).limit(srcOff + len);
final ByteBuffer d = dst.duplicate();
d.position(dstOff);
d.put(s);
}
public EBOSplitScanner.EboSplit[] getOrScanSplits(int glId, int indexType, int sentinel) {
if (!hasEboShadow(glId)) return null;
final long version = getEboShadowVersion(glId);
@@ -1502,10 +1577,10 @@ public final class ResourceManager {
}
}
public static final int COPY_CALLSITE_UPLOAD_BUFFER = 1;
public static final int COPY_CALLSITE_UPLOAD_TEX_BATCH = 2;
public static final int COPY_CALLSITE_UPLOAD_TEX_DIRECT = 3;
public static final int COPY_CALLSITE_ARENA = 4;
public static final int COPY_CALLSITE_FILL_BUFFER = 5;
public boolean arenaUpload(ByteBuffer data, long dstHandle, long dstOffset, boolean cycle) {
final FrameManager.FrameState f = frameManager.frame();
@@ -1600,7 +1675,13 @@ public final class ResourceManager {
}
public void uploadToBuffer(long copyPass, ByteBuffer data, long gpuBuffer, long offset, boolean cycle) {
final long size = data.remaining();
uploadRangeToBuffer(copyPass, data, data.position(), data.remaining(), gpuBuffer, offset, cycle);
}
public void uploadRangeToBuffer(long copyPass, ByteBuffer src, long srcOff, long size, long gpuBuffer, long dstOff, boolean cycle) {
if (srcOff < 0 || size < 0 || srcOff + size > src.capacity()) {
throw new IllegalStateException("uploadRangeToBuffer: bad source range srcOff=" + srcOff + " size=" + size + " src.capacity=" + src.capacity());
}
final long xfer = acquireTransferBuffer(size);
if (xfer == 0) return;
@@ -1609,28 +1690,102 @@ public final class ResourceManager {
if (mapped == null || mapped.capacity() < size) {
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
returnTransferBuffer(xfer, size);
throw new IllegalStateException("uploadToBuffer: bad mapping size=" + size + " mapSize=" + mapSize + " mapped=" + (mapped == null ? "null" : "cap=" + mapped.capacity()));
throw new IllegalStateException("uploadRangeToBuffer: bad mapping size=" + size + " mapSize=" + mapSize + " mapped=" + (mapped == null ? "null" : "cap=" + mapped.capacity()));
}
if (src.isDirect()) {
MemoryUtil.memCopy(MemoryUtil.memAddress0(src) + srcOff, MemoryUtil.memAddress0(mapped), size);
} else {
mapped.put(0, src, (int) srcOff, (int) size);
}
copyMappedFromData(mapped, data, (int) size, COPY_CALLSITE_UPLOAD_BUFFER);
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
try (var stack = stackPush()) {
final SDL_GPUTransferBufferLocation src = SDL_GPUTransferBufferLocation.calloc(stack)
final SDL_GPUTransferBufferLocation srcLoc = SDL_GPUTransferBufferLocation.calloc(stack)
.transfer_buffer(xfer)
.offset(0);
final SDL_GPUBufferRegion dst = SDL_GPUBufferRegion.calloc(stack)
.buffer(gpuBuffer)
.offset((int) offset)
.offset((int) dstOff)
.size((int) size);
SDL_UploadToGPUBuffer(copyPass, src, dst, cycle);
SDL_UploadToGPUBuffer(copyPass, srcLoc, dst, cycle);
}
frameManager.recordUploadCommands(size, 1);
returnTransferBuffer(xfer, size);
}
/**
* Fills {@code size} bytes of a buffer by staging {@code pattern} once and uploading it repeatedly. Staging every
* byte instead maps a transfer buffer per chunk, which stalls for seconds on the hundreds of megabytes some shader
* packs allocate as storage buffers.
*/
public void fillBuffer(long copyPass, ByteBuffer pattern, long gpuBuffer, long offset, long size) {
final int chunk = pattern.remaining();
if (chunk <= 0 || size <= 0) return;
final long xfer = acquireTransferBuffer(chunk);
if (xfer == 0) return;
try {
final ByteBuffer mapped = SDL_MapGPUTransferBuffer(device.getDevice(), xfer, true, getTransferBufferMapSize(chunk));
if (mapped == null || mapped.capacity() < chunk) {
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
throw new IllegalStateException("fillBuffer: bad mapping size=" + chunk + " mapped=" + (mapped == null ? "null" : "cap=" + mapped.capacity()));
}
copyMappedFromData(mapped, pattern, chunk, COPY_CALLSITE_FILL_BUFFER);
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
int uploads = 0;
try (var stack = stackPush()) {
final SDL_GPUTransferBufferLocation src = SDL_GPUTransferBufferLocation.calloc(stack).transfer_buffer(xfer).offset(0);
final SDL_GPUBufferRegion dst = SDL_GPUBufferRegion.calloc(stack).buffer(gpuBuffer);
for (long done = 0; done < size; done += chunk) {
dst.offset((int) (offset + done)).size((int) Math.min(chunk, size - done));
SDL_UploadToGPUBuffer(copyPass, src, dst, false);
uploads++;
}
}
frameManager.recordUploadCommands(size, uploads);
} finally {
returnTransferBuffer(xfer, chunk);
}
}
public boolean zeroTexture3D(long copyPass, long gpuTexture, int w, int h, int d, int level, int bytesPerTexel) {
final long sliceBytes = (long) w * h * bytesPerTexel;
if (sliceBytes <= 0 || sliceBytes > Integer.MAX_VALUE) return false;
final long xfer = acquireTransferBuffer(sliceBytes);
if (xfer == 0) return false;
try {
final ByteBuffer mapped = SDL_MapGPUTransferBuffer(device.getDevice(), xfer, true, getTransferBufferMapSize(sliceBytes));
if (mapped == null || mapped.capacity() < sliceBytes) {
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
return false;
}
MemoryUtil.memSet(MemoryUtil.memAddress(mapped), 0, sliceBytes);
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
flushBatchedUploads(copyPass);
try (var stack = stackPush()) {
final SDL_GPUTextureTransferInfo src = SDL_GPUTextureTransferInfo.calloc(stack).transfer_buffer(xfer).offset(0);
final SDL_GPUTextureRegion dst = SDL_GPUTextureRegion.calloc(stack)
.texture(gpuTexture)
.mip_level(level)
.x(0).y(0)
.w(w).h(h).d(1);
for (int z = 0; z < d; z++) {
dst.z(z);
SDL_UploadToGPUTexture(copyPass, src, dst, false);
}
}
markTextureContentDefined(gpuTexture);
frameManager.recordUploadCommands(sliceBytes * d, d);
return true;
} finally {
returnTransferBuffer(xfer, sliceBytes);
}
}
public static final int BATCH_SEGMENT_CAPACITY = 4 * 1024 * 1024;
public void uploadToTexture(long copyPass, ByteBuffer data, long gpuTexture, int x, int y, int w, int h, int level) {
@@ -2506,12 +2661,20 @@ public final class ResourceManager {
arrayShadowWanted.clear();
for (ByteBuffer bb : pboStagingData.values()) memFree(bb);
pboStagingData.clear();
for (PersistentMapping pm : persistentMappings.values()) {
for (var e : persistentMappings.int2ObjectEntrySet()) {
final PersistentMapping pm = e.getValue();
untrackPersistentDirty(pm);
releasePersistentStaging(pm);
releasePersistentStaging(pm, e.getIntKey(), "shutdown");
}
persistentMappings.clear();
mappingsVersion++;
for (MappedRange m : plainMappings.values()) {
if (m.staging != null) memFree(m.staging);
m.staging = null;
}
plainMappings.clear();
Arrays.fill(plainMappingPool, null);
plainMappingPoolCount = 0;
}
public record TextureMeta(int glTarget, int glFormat, int sdlFormat, int width, int height, int depth, int levels, int usage) {}
@@ -329,11 +329,15 @@ public final class TransferThread {
final ByteBuffer mapped = SDL_MapGPUTransferBuffer(device.getDevice(), xfer, true, mapSize);
if (mapped != null) {
final ByteBuffer src = sr.stagingBuffer;
final int prevPos = src.position();
final int prevLim = src.limit();
src.limit((int) (sr.srcOffset + size)).position((int) sr.srcOffset);
mapped.put(src);
src.limit(prevLim).position(prevPos);
if (sr.srcOffset < 0 || sr.srcOffset + size > src.capacity() || size > mapped.capacity()) {
LOG.error("StagingReadUpload out of bounds: srcOffset={} size={} src.capacity={} mapped.capacity={}; upload dropped", sr.srcOffset, size, src.capacity(), mapped.capacity());
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
resourceManager.returnTransferBufferThreadSafe(xfer, size);
retireSeq(sr.seq);
StagingReadUpload.release(sr);
return;
}
MemoryUtil.memCopy(MemoryUtil.memAddress0(src) + sr.srcOffset, MemoryUtil.memAddress0(mapped), size);
}
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
recordBufferUpload(xfer, size, sr.dstGpuBuffer, sr.dstOffset, sr.seq, sr.cycle);
Loaded 100 of 267 files, more files were not shown because too many files have changed in this diff. Show more