mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-14 17:24:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
159f7ff8cc | ||
|
|
0d959c2b90 | ||
|
|
ce2462cad3 | ||
|
|
5457df5369 | ||
|
|
acedba4eac | ||
|
|
21a054a4db | ||
|
|
d056bf6fa7 | ||
|
|
5ed1c92590 | ||
|
|
3389b952d0 | ||
|
|
b49d37d827 | ||
|
|
12857006c6 | ||
|
|
38af64f10c | ||
|
|
10922edca3 | ||
|
|
2a8c8756d3 | ||
|
|
23e36f2b76 | ||
|
|
fcf8ad064a | ||
|
|
6dfb0312b1 |
No files matched your search
Executable
+23
@@ -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"
|
||||
Executable
+9
@@ -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"
|
||||
@@ -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."
|
||||
@@ -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."
|
||||
@@ -3,7 +3,7 @@ name: Release tagged build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ '*', '!tracy-v*' ]
|
||||
tags: [ '*', '!tracy-v*', '!moltenvk-v*' ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -30,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;
|
||||
@@ -231,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;
|
||||
|
||||
@@ -61,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;
|
||||
@@ -327,6 +327,7 @@ public class GLStateManager {
|
||||
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();
|
||||
|
||||
@@ -1192,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);
|
||||
@@ -1245,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) {
|
||||
@@ -1297,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();
|
||||
@@ -3258,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4530,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 {
|
||||
@@ -6411,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);
|
||||
}
|
||||
@@ -6712,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;
|
||||
@@ -6858,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);
|
||||
@@ -6941,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); }
|
||||
@@ -7337,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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -59,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; }
|
||||
|
||||
@@ -275,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);
|
||||
|
||||
+23
@@ -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) {
|
||||
}
|
||||
@@ -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"
|
||||
))
|
||||
);
|
||||
|
||||
|
||||
+6
-6
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -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")
|
||||
|
||||
+90
-136
@@ -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();
|
||||
|
||||
@@ -106,14 +106,20 @@ public final class ProgramBinaryCache {
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
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));
|
||||
@@ -130,6 +136,7 @@ public final class ProgramBinaryCache {
|
||||
});
|
||||
final List<CompletableFuture<Boolean>> writes = fullCompileWrites;
|
||||
if (writes != null) writes.add(write);
|
||||
return true;
|
||||
} finally {
|
||||
MemoryUtilities.memFree(data);
|
||||
MemoryUtilities.memFree(written);
|
||||
@@ -137,6 +144,18 @@ public final class ProgramBinaryCache {
|
||||
}
|
||||
}
|
||||
|
||||
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<>();
|
||||
|
||||
@@ -53,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");
|
||||
@@ -63,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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
-19
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+737
@@ -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,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"));
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+256
-135
@@ -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() {
|
||||
@@ -1202,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));
|
||||
@@ -1213,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) {
|
||||
@@ -1222,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) {
|
||||
@@ -1231,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1241,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);
|
||||
@@ -1273,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1299,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();
|
||||
}
|
||||
@@ -1332,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);
|
||||
@@ -1346,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;
|
||||
@@ -1407,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;
|
||||
@@ -2760,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);
|
||||
}
|
||||
@@ -2808,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) {
|
||||
@@ -2913,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;
|
||||
@@ -2930,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) {
|
||||
@@ -3027,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());
|
||||
}
|
||||
@@ -3100,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) {
|
||||
@@ -3314,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);
|
||||
@@ -3345,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()) {
|
||||
@@ -3398,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;
|
||||
@@ -3412,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;
|
||||
@@ -3852,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);
|
||||
@@ -4108,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;
|
||||
}
|
||||
@@ -4162,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) {
|
||||
@@ -4200,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,
|
||||
@@ -4346,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
|
||||
@@ -4468,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
-19
@@ -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();
|
||||
|
||||
+152
@@ -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;
|
||||
}
|
||||
}
|
||||
+24
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+635
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-8
@@ -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=");
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
+4
-7
@@ -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;
|
||||
}
|
||||
+35
-21
@@ -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) {
|
||||
|
||||
+6
@@ -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); }
|
||||
|
||||
+207
-44
@@ -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) {}
|
||||
|
||||
+9
-5
@@ -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);
|
||||
|
||||
@@ -43,8 +43,13 @@ public final class SamplerBinder {
|
||||
}
|
||||
|
||||
public long getSamplerForUnit(ContextState st, int glUnit, int glTexId) {
|
||||
if (glUnit >= 0 && glUnit < st.boundSamplerObjects.length && st.boundSamplerObjects[glUnit] != 0) {
|
||||
final TextureSamplerState ss = resourceManager.getSamplerObject(st.boundSamplerObjects[glUnit]);
|
||||
final int samplerObject = glUnit >= 0 && glUnit < st.boundSamplerObjects.length ? st.boundSamplerObjects[glUnit] : 0;
|
||||
return getSampler(samplerObject, glTexId);
|
||||
}
|
||||
|
||||
public long getSampler(int samplerObject, int glTexId) {
|
||||
if (samplerObject != 0) {
|
||||
final TextureSamplerState ss = resourceManager.getSamplerObject(samplerObject);
|
||||
if (ss != null) return getOrCreateSdlSampler(ss);
|
||||
}
|
||||
if (glTexId != 0) return getSamplerForTexture(glTexId);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.taumc.glsl.grammar.GLSLParserBaseListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -14,22 +12,15 @@ public final class ClipZRemap {
|
||||
private ClipZRemap() {}
|
||||
|
||||
public static void collectEdits(GLSLParser.Translation_unitContext root, List<Edit> edits) {
|
||||
final int[] mainCloseBraceIdx = { -1 };
|
||||
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
|
||||
@Override
|
||||
public void enterFunction_definition(GLSLParser.Function_definitionContext ctx) {
|
||||
if (mainCloseBraceIdx[0] >= 0) return;
|
||||
final GLSLParser.Function_prototypeContext proto = ctx.function_prototype();
|
||||
if (proto == null || proto.IDENTIFIER() == null) return;
|
||||
if (!"main".equals(proto.IDENTIFIER().getText())) return;
|
||||
final GLSLParser.Compound_statement_no_new_scopeContext body = ctx.compound_statement_no_new_scope();
|
||||
if (body == null || body.RIGHT_BRACE() == null) return;
|
||||
mainCloseBraceIdx[0] = body.RIGHT_BRACE().getSymbol().getStartIndex();
|
||||
}
|
||||
}, root);
|
||||
|
||||
if (mainCloseBraceIdx[0] < 0) return;
|
||||
final int idx = mainCloseBraceIdx[0];
|
||||
edits.add(new Edit(idx, idx - 1, INJECTION_STD));
|
||||
for (GLSLParser.External_declarationContext external : root.external_declaration()) {
|
||||
final GLSLParser.Function_definitionContext function = external.function_definition();
|
||||
if (function == null || function.function_prototype().IDENTIFIER() == null) continue;
|
||||
if (!"main".equals(function.function_prototype().IDENTIFIER().getText())) continue;
|
||||
final GLSLParser.Compound_statement_no_new_scopeContext body = function.compound_statement_no_new_scope();
|
||||
if (body == null || body.RIGHT_BRACE() == null) return;
|
||||
final int idx = body.RIGHT_BRACE().getSymbol().getStartIndex();
|
||||
edits.add(new Edit(idx, idx - 1, INJECTION_STD));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.startIdx;
|
||||
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.stopIdx;
|
||||
|
||||
/**
|
||||
* Moves {@code dFdx}/{@code dFdy}/{@code fwidth} calls in front of the {@code if} blocks around them, so every section of
|
||||
* the 2x2 quad computes them. Packs take derivatives inside branches that only part of a quad enters, which GLSL
|
||||
* leaves undefined. ACO writes {@code src - src[lane0]} in the active lanes only, then reads lane 1/2 with
|
||||
* fetch-inactive, so a neighbor that skipped the branch hands back a stale register, often NaN!
|
||||
* This was observed on Euphoria Patches and this aims to fix this wherever possible on Vulkan.
|
||||
*/
|
||||
public final class DerivativeHoisting {
|
||||
|
||||
private static final String PREFIX = "angelica_derivative_";
|
||||
|
||||
private static final Set<String> DERIVATIVES = Set.of(
|
||||
"dFdx", "dFdy", "fwidth",
|
||||
"dFdxFine", "dFdyFine", "fwidthFine",
|
||||
"dFdxCoarse", "dFdyCoarse", "fwidthCoarse");
|
||||
|
||||
private static final Map<String, Set<Integer>> BUILTIN_OUT_PARAMS = Map.of(
|
||||
"modf", Set.of(1),
|
||||
"frexp", Set.of(1),
|
||||
"uaddCarry", Set.of(2),
|
||||
"usubBorrow", Set.of(2),
|
||||
"umulExtended", Set.of(2, 3),
|
||||
"imulExtended", Set.of(2, 3));
|
||||
|
||||
private static final String UNKNOWN = "*";
|
||||
|
||||
private DerivativeHoisting() {}
|
||||
|
||||
private record Declared(String type, boolean global) {}
|
||||
|
||||
private record Operand(String name, @Nullable String swizzle) {}
|
||||
|
||||
private record DerivativeCall(GLSLParser.Postfix_expressionContext call, String callee) {}
|
||||
|
||||
private record Effects(Set<String> declared, Set<String> written, Set<String> called) {
|
||||
Effects() {
|
||||
this(new HashSet<>(), new HashSet<>(), new HashSet<>());
|
||||
}
|
||||
}
|
||||
|
||||
public static void collectEdits(ShaderIndex index, List<Edit> edits) {
|
||||
final List<DerivativeCall> calls = new ArrayList<>();
|
||||
for (String callee : DERIVATIVES) {
|
||||
for (GLSLParser.Postfix_expressionContext call : index.callsTo(callee)) calls.add(new DerivativeCall(call, callee));
|
||||
}
|
||||
if (calls.isEmpty()) return;
|
||||
calls.sort(Comparator.comparingInt(c -> startIdx(c.call())));
|
||||
|
||||
final String source = index.source();
|
||||
final GLSLParser.Translation_unitContext root = index.root();
|
||||
final Functions functions = new Functions(index);
|
||||
final int earlierEditCount = edits.size();
|
||||
final Map<GLSLParser.Selection_statementContext, Effects> ifEffects = new HashMap<>();
|
||||
final Map<GLSLParser.Selection_statementContext, StringBuilder> declarations = new LinkedHashMap<>();
|
||||
final Map<String, String> hoisted = new HashMap<>();
|
||||
boolean checkedDirectives = false;
|
||||
|
||||
for (DerivativeCall derivative : calls) {
|
||||
final GLSLParser.Postfix_expressionContext call = derivative.call();
|
||||
final GLSLParser.Function_call_parametersContext params = call.function_call_parameters();
|
||||
if (params == null || params.assignment_expression().size() != 1) continue;
|
||||
final GLSLParser.Assignment_expressionContext arg = params.assignment_expression(0);
|
||||
final Operand operand = operand(arg);
|
||||
if (operand == null) continue;
|
||||
|
||||
final Declared declared = declaredType(call, operand.name(), root);
|
||||
if (declared == null) continue;
|
||||
final String type = resultType(declared.type(), operand.swizzle());
|
||||
if (type == null) continue;
|
||||
|
||||
final GLSLParser.Selection_statementContext target = outermostSafeIf(call, operand.name(), declared.global(), functions, ifEffects);
|
||||
if (target == null) continue;
|
||||
if (!checkedDirectives) {
|
||||
if (index.directives().hasConditionalOrMacro()) return;
|
||||
checkedDirectives = true;
|
||||
}
|
||||
|
||||
final String callee = derivative.callee();
|
||||
final String argText = sliceWithEdits(source, startIdx(arg), stopIdx(arg), edits.subList(0, earlierEditCount));
|
||||
final String key = startIdx(target) + "|" + callee + "|" + argText;
|
||||
String variable = hoisted.get(key);
|
||||
if (variable == null) {
|
||||
variable = PREFIX + hoisted.size();
|
||||
hoisted.put(key, variable);
|
||||
declarations.computeIfAbsent(target, k -> new StringBuilder())
|
||||
.append(type).append(' ').append(variable).append(" = ").append(callee).append('(').append(argText).append(");\n")
|
||||
.append(indentBefore(source, startIdx(target)));
|
||||
}
|
||||
edits.add(new Edit(startIdx(call), stopIdx(call), variable));
|
||||
}
|
||||
|
||||
for (Map.Entry<GLSLParser.Selection_statementContext, StringBuilder> e : declarations.entrySet()) {
|
||||
final int at = startIdx(e.getKey());
|
||||
edits.add(new Edit(at, at - 1, e.getValue().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Functions {
|
||||
final Map<String, Set<Integer>> outParams = new HashMap<>();
|
||||
final Set<String> names = new HashSet<>();
|
||||
final Map<GLSLParser.Postfix_expressionContext, String> calleeNames = new HashMap<>();
|
||||
private final List<GLSLParser.Function_definitionContext> definitions = new ArrayList<>();
|
||||
private @Nullable Map<String, Set<String>> globalWrites;
|
||||
|
||||
Functions(ShaderIndex index) {
|
||||
for (GLSLParser.External_declarationContext external : index.root().external_declaration()) {
|
||||
final GLSLParser.Function_prototypeContext prototype;
|
||||
if (external.function_definition() != null) {
|
||||
definitions.add(external.function_definition());
|
||||
prototype = external.function_definition().function_prototype();
|
||||
} else if (external.declaration() != null) {
|
||||
prototype = external.declaration().function_prototype();
|
||||
} else {
|
||||
prototype = null;
|
||||
}
|
||||
if (prototype == null || prototype.IDENTIFIER() == null) continue;
|
||||
final String name = prototype.IDENTIFIER().getText();
|
||||
names.add(name);
|
||||
final List<GLSLParser.Parameter_declarationContext> params = parameters(prototype);
|
||||
for (int i = 0; i < params.size(); i++) {
|
||||
final GLSLParser.Type_qualifierContext q = params.get(i).type_qualifier();
|
||||
if (ShaderIndex.hasStorageQualifier(q, "out") || ShaderIndex.hasStorageQualifier(q, "inout")) {
|
||||
outParams.computeIfAbsent(name, k -> new HashSet<>()).add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (String name : names) addCalls(index, name);
|
||||
for (String name : BUILTIN_OUT_PARAMS.keySet()) addCalls(index, name);
|
||||
}
|
||||
|
||||
private void addCalls(ShaderIndex index, String name) {
|
||||
for (GLSLParser.Postfix_expressionContext call : index.callsTo(name)) calleeNames.put(call, name);
|
||||
}
|
||||
|
||||
Set<String> globalWrites(String function) {
|
||||
if (globalWrites == null) globalWrites = summarize();
|
||||
return globalWrites.getOrDefault(function, Set.of());
|
||||
}
|
||||
|
||||
private Map<String, Set<String>> summarize() {
|
||||
final Map<String, Set<String>> writes = new HashMap<>();
|
||||
final Map<String, Set<String>> callees = new HashMap<>();
|
||||
for (GLSLParser.Function_definitionContext definition : definitions) {
|
||||
final String name = definition.function_prototype().IDENTIFIER().getText();
|
||||
final Effects effects = new Effects();
|
||||
collectEffects(definition.compound_statement_no_new_scope(), this, effects);
|
||||
for (GLSLParser.Parameter_declarationContext param : parameters(definition.function_prototype())) {
|
||||
final String parameter = parameterName(param);
|
||||
if (parameter != null) effects.written().remove(parameter);
|
||||
}
|
||||
writes.computeIfAbsent(name, k -> new HashSet<>()).addAll(effects.written());
|
||||
callees.computeIfAbsent(name, k -> new HashSet<>()).addAll(effects.called());
|
||||
}
|
||||
boolean changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (Map.Entry<String, Set<String>> e : callees.entrySet()) {
|
||||
final Set<String> into = writes.get(e.getKey());
|
||||
for (String callee : e.getValue()) {
|
||||
final Set<String> from = writes.get(callee);
|
||||
if (from != null) changed |= into.addAll(from);
|
||||
}
|
||||
}
|
||||
}
|
||||
return writes;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable Operand operand(GLSLParser.Assignment_expressionContext arg) {
|
||||
if (arg.assignment_operator() != null) return null;
|
||||
final GLSLParser.Constant_expressionContext c = arg.constant_expression();
|
||||
if (c == null || c.QUESTION() != null) return null;
|
||||
final GLSLParser.Unary_expressionContext u = c.binary_expression().unary_expression();
|
||||
if (u == null || u.postfix_expression() == null) return null;
|
||||
final GLSLParser.Postfix_expressionContext p = u.postfix_expression();
|
||||
|
||||
final String name = variableName(p);
|
||||
if (name != null) return new Operand(name, null);
|
||||
if (p.DOT() == null || p.field_selection() == null || p.field_selection().variable_identifier() == null) return null;
|
||||
final String base = variableName(p.postfix_expression());
|
||||
return base == null ? null : new Operand(base, p.field_selection().variable_identifier().getText());
|
||||
}
|
||||
|
||||
private static @Nullable String variableName(@Nullable GLSLParser.Postfix_expressionContext p) {
|
||||
if (p == null || p.primary_expression() == null || p.primary_expression().variable_identifier() == null) return null;
|
||||
return p.primary_expression().variable_identifier().getText();
|
||||
}
|
||||
|
||||
private static @Nullable String resultType(String declaredType, @Nullable String swizzle) {
|
||||
final int size = switch (declaredType) {
|
||||
case "float" -> 1;
|
||||
case "vec2" -> 2;
|
||||
case "vec3" -> 3;
|
||||
case "vec4" -> 4;
|
||||
default -> 0;
|
||||
};
|
||||
if (size == 0) return null;
|
||||
if (swizzle == null) return declaredType;
|
||||
if (size == 1 || swizzle.isEmpty() || swizzle.length() > 4) return null;
|
||||
for (String set : new String[] { "xyzw", "rgba", "stpq" }) {
|
||||
boolean all = true;
|
||||
for (int i = 0; i < swizzle.length(); i++) {
|
||||
final int component = set.indexOf(swizzle.charAt(i));
|
||||
if (component < 0 || component >= size) { all = false; break; }
|
||||
}
|
||||
if (all) return swizzle.length() == 1 ? "float" : "vec" + swizzle.length();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable Declared declaredType(ParserRuleContext from, String name, GLSLParser.Translation_unitContext root) {
|
||||
ParseTree child = from;
|
||||
for (ParserRuleContext p = from.getParent(); p != null; child = p, p = p.getParent()) {
|
||||
if (p instanceof GLSLParser.Statement_listContext list) {
|
||||
for (int i = list.children.indexOf(child) - 1; i >= 0; i--) {
|
||||
final String type = typeOf(list.getChild(i), name);
|
||||
if (type != null) return new Declared(type, false);
|
||||
}
|
||||
} else if (p instanceof GLSLParser.Iteration_statementContext loop && loop.for_init_statement() != null && child != loop.for_init_statement()) {
|
||||
final String type = typeOf(loop.for_init_statement(), name);
|
||||
if (type != null) return new Declared(type, false);
|
||||
} else if (p instanceof GLSLParser.Function_definitionContext function) {
|
||||
for (GLSLParser.Parameter_declarationContext param : parameters(function.function_prototype())) {
|
||||
if (name.equals(parameterName(param))) {
|
||||
final GLSLParser.Parameter_declaratorContext d = param.parameter_declarator();
|
||||
return new Declared(d.array_specifier() == null ? baseType(d.type_specifier()) : "", false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (GLSLParser.External_declarationContext external : root.external_declaration()) {
|
||||
if (external.declaration() == null) continue;
|
||||
final String type = typeOf(external.declaration(), name);
|
||||
if (type != null) return new Declared(type, true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable String typeOf(ParseTree node, String name) {
|
||||
final GLSLParser.Init_declarator_listContext list = firstDeclaratorList(node);
|
||||
if (list == null || list.single_declaration() == null) return null;
|
||||
for (GLSLParser.Typeless_declarationContext d : ShaderIndex.declarators(list)) {
|
||||
if (d.IDENTIFIER() == null || !name.equals(d.IDENTIFIER().getText())) continue;
|
||||
if (d.array_specifier() != null) return "";
|
||||
return baseType(list.single_declaration().fully_specified_type().type_specifier());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static GLSLParser.@Nullable Init_declarator_listContext firstDeclaratorList(ParseTree node) {
|
||||
ParseTree n = node;
|
||||
while (n != null) {
|
||||
if (n instanceof GLSLParser.Init_declarator_listContext list) return list;
|
||||
if (n instanceof GLSLParser.Simple_statementContext s) n = s.declaration_statement();
|
||||
else if (n instanceof GLSLParser.StatementContext s) n = s.simple_statement();
|
||||
else if (n instanceof GLSLParser.For_init_statementContext f) n = f.declaration_statement();
|
||||
else if (n instanceof GLSLParser.Declaration_statementContext d) n = d.declaration();
|
||||
else if (n instanceof GLSLParser.DeclarationContext d) n = d.init_declarator_list();
|
||||
else return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String baseType(GLSLParser.Type_specifierContext type) {
|
||||
if (type == null || type.array_specifier() != null || type.type_specifier_nonarray() == null) return "";
|
||||
return type.type_specifier_nonarray().getText();
|
||||
}
|
||||
|
||||
private static List<GLSLParser.Parameter_declarationContext> parameters(GLSLParser.Function_prototypeContext prototype) {
|
||||
final GLSLParser.Function_parametersContext params = prototype.function_parameters();
|
||||
return params == null ? List.of() : params.parameter_declaration();
|
||||
}
|
||||
|
||||
private static @Nullable String parameterName(GLSLParser.Parameter_declarationContext param) {
|
||||
final GLSLParser.Parameter_declaratorContext d = param.parameter_declarator();
|
||||
return d == null || d.IDENTIFIER() == null ? null : d.IDENTIFIER().getText();
|
||||
}
|
||||
|
||||
private static GLSLParser.@Nullable Selection_statementContext outermostSafeIf(ParserRuleContext call, String name, boolean global, Functions functions,
|
||||
Map<GLSLParser.Selection_statementContext, Effects> ifEffects) {
|
||||
final List<GLSLParser.Selection_statementContext> enclosing = new ArrayList<>();
|
||||
for (ParserRuleContext p = call.getParent(); p != null; p = p.getParent()) {
|
||||
if (p instanceof GLSLParser.Selection_rest_statementContext rest) {
|
||||
enclosing.add((GLSLParser.Selection_statementContext) rest.getParent());
|
||||
} else if (p instanceof GLSLParser.Iteration_statementContext || p instanceof GLSLParser.Switch_statementContext
|
||||
|| p instanceof GLSLParser.Function_definitionContext) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = enclosing.size() - 1; i >= 0; i--) {
|
||||
final GLSLParser.Selection_statementContext candidate = enclosing.get(i);
|
||||
if (!inStatementList(candidate)) continue;
|
||||
Effects effects = ifEffects.get(candidate);
|
||||
if (effects == null) {
|
||||
effects = new Effects();
|
||||
collectEffects(candidate, functions, effects);
|
||||
ifEffects.put(candidate, effects);
|
||||
}
|
||||
if (effects.declared().contains(name) || writes(effects, name, global, functions)) continue;
|
||||
return candidate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean inStatementList(GLSLParser.Selection_statementContext s) {
|
||||
return s.getParent() instanceof GLSLParser.Simple_statementContext simple
|
||||
&& simple.getParent() instanceof GLSLParser.StatementContext statement
|
||||
&& statement.getParent() instanceof GLSLParser.Statement_listContext list
|
||||
&& !(list.getParent() instanceof GLSLParser.Switch_statementContext);
|
||||
}
|
||||
|
||||
private static boolean writes(Effects effects, String name, boolean global, Functions functions) {
|
||||
if (effects.written().contains(name) || effects.written().contains(UNKNOWN)) return true;
|
||||
if (!global) return false;
|
||||
for (String callee : effects.called()) {
|
||||
final Set<String> w = functions.globalWrites(callee);
|
||||
if (w.contains(name) || w.contains(UNKNOWN)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void collectEffects(ParseTree node, Functions functions, Effects effects) {
|
||||
final Set<String> written = effects.written();
|
||||
if (node instanceof GLSLParser.Typeless_declarationContext d && d.IDENTIFIER() != null) {
|
||||
effects.declared().add(d.IDENTIFIER().getText());
|
||||
} else if (node instanceof GLSLParser.ConditionContext c && c.IDENTIFIER() != null) {
|
||||
effects.declared().add(c.IDENTIFIER().getText());
|
||||
} else if (node instanceof GLSLParser.Assignment_expressionContext a && a.assignment_operator() != null) {
|
||||
written.add(lvalueName(a.unary_expression()));
|
||||
} else if (node instanceof GLSLParser.Unary_expressionContext u && (u.INC_OP() != null || u.DEC_OP() != null)) {
|
||||
written.add(lvalueName(u.unary_expression()));
|
||||
} else if (node instanceof GLSLParser.Postfix_expressionContext p) {
|
||||
if (p.INC_OP() != null || p.DEC_OP() != null) written.add(lvalueName(p.postfix_expression()));
|
||||
final String callee = functions.calleeNames.get(p);
|
||||
if (callee != null) {
|
||||
if (functions.names.contains(callee)) effects.called().add(callee);
|
||||
Set<Integer> positions = functions.outParams.get(callee);
|
||||
if (positions == null) positions = BUILTIN_OUT_PARAMS.get(callee);
|
||||
if (positions != null && p.function_call_parameters() != null) {
|
||||
final List<GLSLParser.Assignment_expressionContext> args = p.function_call_parameters().assignment_expression();
|
||||
for (int i : positions) {
|
||||
if (i < args.size()) written.add(lvalueName(args.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
collectEffects(node.getChild(i), functions, effects);
|
||||
}
|
||||
}
|
||||
|
||||
private static String lvalueName(@Nullable ParserRuleContext lvalue) {
|
||||
if (lvalue == null) return UNKNOWN;
|
||||
final Token start = lvalue.getStart();
|
||||
return start != null && start.getType() == GLSLLexer.IDENTIFIER ? start.getText() : UNKNOWN;
|
||||
}
|
||||
|
||||
private static String indentBefore(String source, int at) {
|
||||
int lineStart = at;
|
||||
while (lineStart > 0 && (source.charAt(lineStart - 1) == ' ' || source.charAt(lineStart - 1) == '\t')) lineStart--;
|
||||
return source.substring(lineStart, at);
|
||||
}
|
||||
|
||||
private static String sliceWithEdits(String source, int start, int stop, List<Edit> edits) {
|
||||
final List<Edit> inside = new ArrayList<>();
|
||||
for (Edit e : edits) {
|
||||
if (e.startIdx() >= start && e.startIdx() <= stop && e.stopIdx() <= stop) {
|
||||
inside.add(new Edit(e.startIdx() - start, e.stopIdx() - start, e.replacement()));
|
||||
}
|
||||
}
|
||||
return GlslVulkanPreprocess.applyEdits(source.substring(start, stop + 1), inside);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
@@ -112,11 +113,8 @@ public final class LogicOpLowering {
|
||||
final int vecSize = floatVecSize(ts.type_specifier_nonarray().getText());
|
||||
final int typeArrayLen = ts.array_specifier() != null ? arrayLen(ts.array_specifier(), program) : 0;
|
||||
|
||||
final List<GLSLParser.Typeless_declarationContext> names = new ArrayList<>();
|
||||
if (single.typeless_declaration() != null) names.add(single.typeless_declaration());
|
||||
names.addAll(idl.typeless_declaration());
|
||||
int nextLocation = location;
|
||||
for (GLSLParser.Typeless_declarationContext td : names) {
|
||||
for (GLSLParser.Typeless_declarationContext td : ShaderIndex.declarators(idl)) {
|
||||
final int len = td.array_specifier() != null ? arrayLen(td.array_specifier(), program) : typeArrayLen;
|
||||
outputs.add(new Output(td.IDENTIFIER().getText(), nextLocation, len, vecSize));
|
||||
if (nextLocation >= 0) nextLocation += Math.max(len, 1);
|
||||
|
||||
+13
-45
@@ -3,13 +3,9 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock.Member;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.UniformType;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.taumc.glsl.grammar.GLSLParserBaseListener;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
@@ -28,13 +24,13 @@ public final class PerFrameBlockInjector {
|
||||
|
||||
private PerFrameBlockInjector() {}
|
||||
|
||||
public static void collectEdits(GLSLParser.Translation_unitContext root, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass, List<Edit> out) {
|
||||
public static void collectEdits(ShaderIndex index, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass, List<Edit> out) {
|
||||
final PerFrameUniformBlock[] blocks = new PerFrameUniformBlock[ShaderManager.BLOCK_COUNT];
|
||||
blocks[ShaderManager.BLOCK_PER_FRAME] = normalize(perFrame);
|
||||
blocks[ShaderManager.BLOCK_PER_PASS] = normalize(perPass);
|
||||
if (blocks[0] == null && blocks[1] == null) return;
|
||||
|
||||
final int insertAt = firstDeclarationStart(root);
|
||||
final int insertAt = firstDeclarationStart(index.root());
|
||||
if (insertAt < 0) return;
|
||||
|
||||
final Map<String, String> memberTypes = new LinkedHashMap<>();
|
||||
@@ -51,9 +47,12 @@ public final class PerFrameBlockInjector {
|
||||
final List<Edit> stripEdits = new ArrayList<>();
|
||||
final IntArrayList stripBlocks = new IntArrayList();
|
||||
final Set<String> shadowed = new HashSet<>();
|
||||
classifyDeclarations(root, memberTypes, memberBlock, stripEdits, stripBlocks, shadowed);
|
||||
classifyDeclarations(index, memberTypes, memberBlock, stripEdits, stripBlocks, shadowed);
|
||||
|
||||
final Set<String> referenced = referencedMembers(root, memberTypes.keySet(), shadowed);
|
||||
final Set<String> referenced = new HashSet<>();
|
||||
for (String name : memberTypes.keySet()) {
|
||||
if (!shadowed.contains(name) && !index.occurrences(name).isEmpty()) referenced.add(name);
|
||||
}
|
||||
final boolean[] injected = new boolean[blocks.length];
|
||||
boolean anyInjected = false;
|
||||
for (int b = 0; b < blocks.length; b++) {
|
||||
@@ -108,11 +107,8 @@ public final class PerFrameBlockInjector {
|
||||
return (decls == null || decls.isEmpty()) ? -1 : decls.get(0).getStart().getStartIndex();
|
||||
}
|
||||
|
||||
private static void classifyDeclarations(GLSLParser.Translation_unitContext root, Map<String, String> memberTypes, Object2IntOpenHashMap<String> memberBlock, List<Edit> stripEdits, IntArrayList stripBlocks, Set<String> shadowed) {
|
||||
for (GLSLParser.External_declarationContext ed : root.external_declaration()) {
|
||||
final GLSLParser.DeclarationContext ctx = ed.declaration();
|
||||
if (ctx == null) continue;
|
||||
|
||||
private static void classifyDeclarations(ShaderIndex index, Map<String, String> memberTypes, Object2IntOpenHashMap<String> memberBlock, List<Edit> stripEdits, IntArrayList stripBlocks, Set<String> shadowed) {
|
||||
for (GLSLParser.DeclarationContext ctx : index.globalDeclarations()) {
|
||||
final List<String> declared = declaredNames(ctx);
|
||||
if (declared.isEmpty()) continue;
|
||||
|
||||
@@ -141,16 +137,7 @@ public final class PerFrameBlockInjector {
|
||||
final GLSLParser.Single_declarationContext single = idl.single_declaration();
|
||||
if (single == null || single.fully_specified_type() == null) return false;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
if (fst.type_qualifier() == null) return false;
|
||||
|
||||
boolean hasUniform = false;
|
||||
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
|
||||
if (stq.storage_qualifier() != null && "uniform".equals(stq.storage_qualifier().getText())) {
|
||||
hasUniform = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasUniform) return false;
|
||||
if (!ShaderIndex.hasStorageQualifier(fst.type_qualifier(), "uniform")) return false;
|
||||
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return false;
|
||||
final String declaredType = fst.type_specifier().type_specifier_nonarray().getText();
|
||||
|
||||
@@ -160,30 +147,11 @@ public final class PerFrameBlockInjector {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Set<String> referencedMembers(GLSLParser.Translation_unitContext root, Set<String> memberNames, Set<String> shadowed) {
|
||||
final Set<String> referenced = new HashSet<>();
|
||||
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
|
||||
@Override
|
||||
public void visitTerminal(TerminalNode node) {
|
||||
final Token tok = node.getSymbol();
|
||||
if (tok.getType() != GLSLLexer.IDENTIFIER) return;
|
||||
final String name = tok.getText();
|
||||
if (memberNames.contains(name) && !shadowed.contains(name)) referenced.add(name);
|
||||
}
|
||||
}, root);
|
||||
return referenced;
|
||||
}
|
||||
|
||||
private static List<String> declaredNames(GLSLParser.DeclarationContext ctx) {
|
||||
final GLSLParser.Init_declarator_listContext idl = ctx.init_declarator_list();
|
||||
if (idl == null) return List.of();
|
||||
final GLSLParser.Single_declarationContext single = idl.single_declaration();
|
||||
if (single == null) return List.of();
|
||||
if (idl == null || idl.single_declaration() == null) return List.of();
|
||||
final List<String> out = new ArrayList<>(2);
|
||||
if (single.typeless_declaration() != null && single.typeless_declaration().IDENTIFIER() != null) {
|
||||
out.add(single.typeless_declaration().IDENTIFIER().getText());
|
||||
}
|
||||
for (GLSLParser.Typeless_declarationContext td : idl.typeless_declaration()) {
|
||||
for (GLSLParser.Typeless_declarationContext td : ShaderIndex.declarators(idl)) {
|
||||
if (td.IDENTIFIER() != null) out.add(td.IDENTIFIER().getText());
|
||||
}
|
||||
return out;
|
||||
|
||||
+54
-70
@@ -1,20 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
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.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.taumc.glsl.grammar.GLSLParserBaseListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@@ -24,79 +19,68 @@ public final class SamplerStripper {
|
||||
|
||||
private SamplerStripper() {}
|
||||
|
||||
public static void collectEdits(GLSLParser.Translation_unitContext root, String source, List<Edit> edits) {
|
||||
record SamplerDecl(String name, int start, int stop) {}
|
||||
private record SamplerDecl(List<String> names, int start, int stop) {}
|
||||
|
||||
public static void collectEdits(ShaderIndex index, List<Edit> edits) {
|
||||
final List<SamplerDecl> samplers = new ArrayList<>();
|
||||
|
||||
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
|
||||
@Override
|
||||
public void enterDeclaration(GLSLParser.DeclarationContext ctx) {
|
||||
final GLSLParser.Init_declarator_listContext idl = ctx.init_declarator_list();
|
||||
if (idl == null) return;
|
||||
final GLSLParser.Single_declarationContext single = idl.single_declaration();
|
||||
if (single == null || single.fully_specified_type() == null) return;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
if (fst.type_qualifier() == null) return;
|
||||
|
||||
boolean hasUniform = false;
|
||||
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
|
||||
if (stq.storage_qualifier() != null && "uniform".equals(stq.storage_qualifier().getText())) {
|
||||
hasUniform = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasUniform) return;
|
||||
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
|
||||
|
||||
final String typeText = fst.type_specifier().type_specifier_nonarray().getText();
|
||||
if (!isSamplerType(typeText)) return;
|
||||
|
||||
final int start = ctx.getStart().getStartIndex();
|
||||
final int stop = ctx.getStop().getStopIndex();
|
||||
if (single.typeless_declaration() != null && single.typeless_declaration().IDENTIFIER() != null) {
|
||||
samplers.add(new SamplerDecl(single.typeless_declaration().IDENTIFIER().getText(), start, stop));
|
||||
}
|
||||
for (GLSLParser.Typeless_declarationContext td : idl.typeless_declaration()) {
|
||||
if (td.IDENTIFIER() != null) {
|
||||
samplers.add(new SamplerDecl(td.IDENTIFIER().getText(), start, stop));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, root);
|
||||
|
||||
for (GLSLParser.DeclarationContext ctx : index.globalDeclarations()) {
|
||||
final SamplerDecl decl = samplerDeclaration(ctx);
|
||||
if (decl != null) samplers.add(decl);
|
||||
}
|
||||
if (samplers.isEmpty()) return;
|
||||
|
||||
final Map<String, Integer> refCounts = new HashMap<>();
|
||||
for (SamplerDecl s : samplers) refCounts.put(s.name(), 0);
|
||||
|
||||
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
|
||||
@Override
|
||||
public void visitTerminal(TerminalNode node) {
|
||||
final Token tok = node.getSymbol();
|
||||
if (tok.getType() != GLSLLexer.IDENTIFIER) return;
|
||||
final String name = tok.getText();
|
||||
final Integer prior = refCounts.get(name);
|
||||
if (prior == null) return;
|
||||
final int pos = tok.getStartIndex();
|
||||
for (SamplerDecl s : samplers) {
|
||||
if (pos >= s.start() && pos <= s.stop()) return;
|
||||
}
|
||||
refCounts.put(name, prior + 1);
|
||||
}
|
||||
}, root);
|
||||
|
||||
final Set<String> directiveRefs = GlslTransformUtils.identifiersInDirectiveText(source);
|
||||
|
||||
Set<String> directiveRefs = null;
|
||||
for (SamplerDecl s : samplers) {
|
||||
if (refCounts.getOrDefault(s.name(), 0) != 0) continue;
|
||||
if (directiveRefs.contains(s.name())) {
|
||||
LOGGER.debug("Keeping sampler {}: referenced only inside a preprocessor block", s.name());
|
||||
if (!allUnreferenced(s, samplers, index)) continue;
|
||||
if (directiveRefs == null) directiveRefs = index.directives().identifiers();
|
||||
if (anyIn(s, directiveRefs)) {
|
||||
LOGGER.debug("Keeping sampler {}: referenced only inside a preprocessor block", s.names());
|
||||
continue;
|
||||
}
|
||||
edits.add(new Edit(s.start(), s.stop(), ""));
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable SamplerDecl samplerDeclaration(GLSLParser.DeclarationContext ctx) {
|
||||
final GLSLParser.Init_declarator_listContext idl = ctx.init_declarator_list();
|
||||
if (idl == null) return null;
|
||||
final GLSLParser.Single_declarationContext single = idl.single_declaration();
|
||||
if (single == null || single.fully_specified_type() == null) return null;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
if (!ShaderIndex.hasStorageQualifier(fst.type_qualifier(), "uniform")) return null;
|
||||
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return null;
|
||||
if (!isSamplerType(fst.type_specifier().type_specifier_nonarray().getText())) return null;
|
||||
|
||||
final List<String> names = new ArrayList<>(1);
|
||||
for (GLSLParser.Typeless_declarationContext td : ShaderIndex.declarators(idl)) {
|
||||
if (td.IDENTIFIER() != null) names.add(td.IDENTIFIER().getText());
|
||||
}
|
||||
return names.isEmpty() ? null : new SamplerDecl(names, ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex());
|
||||
}
|
||||
|
||||
private static boolean insideAny(List<SamplerDecl> samplers, int pos) {
|
||||
for (SamplerDecl s : samplers) {
|
||||
if (pos >= s.start() && pos <= s.stop()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean allUnreferenced(SamplerDecl s, List<SamplerDecl> samplers, ShaderIndex index) {
|
||||
for (String name : s.names()) {
|
||||
for (Token tok : index.occurrences(name)) {
|
||||
if (!insideAny(samplers, tok.getStartIndex())) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean anyIn(SamplerDecl s, Set<String> names) {
|
||||
for (String name : s.names()) {
|
||||
if (names.contains(name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isSamplerType(String t) {
|
||||
return t.startsWith("sampler") || t.startsWith("isampler") || t.startsWith("usampler");
|
||||
}
|
||||
|
||||
+150
-54
@@ -9,8 +9,10 @@ import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderCacheIO;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import com.gtnewhorizons.angelica.glsm.threading.AngelicaWorkers;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compute.VertexWriteReplayer;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.cross.CrossCompileCache;
|
||||
@@ -53,6 +55,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.system.MemoryStack.*;
|
||||
@@ -64,6 +67,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrays;
|
||||
import it.unimi.dsi.fastutil.ints.IntIterator;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
@@ -234,7 +238,35 @@ public final class ShaderManager {
|
||||
final int shaderKind = shaderKindFor(obj);
|
||||
final int glType = obj.type;
|
||||
final String finalSrc = obj.source;
|
||||
obj.spirvFuture = AngelicaWorkers.submit(() -> compileStageAsync(finalSrc, shaderKind, glType, shader));
|
||||
final QueuedCompile compile = new QueuedCompile(() -> compileStageAsync(finalSrc, shaderKind, glType, shader));
|
||||
AngelicaWorkers.run(compile);
|
||||
obj.queuedCompile = compile;
|
||||
obj.spirvFuture = compile.result;
|
||||
}
|
||||
|
||||
private static final class QueuedCompile implements Runnable {
|
||||
private final AtomicBoolean claimed = new AtomicBoolean();
|
||||
private final Supplier<AsyncCompile> work;
|
||||
final CompletableFuture<AsyncCompile> result = new CompletableFuture<>();
|
||||
|
||||
QueuedCompile(Supplier<AsyncCompile> work) {
|
||||
this.work = work;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!claimed.compareAndSet(false, true)) return;
|
||||
try {
|
||||
result.complete(work.get());
|
||||
} catch (Throwable t) {
|
||||
result.completeExceptionally(t);
|
||||
}
|
||||
}
|
||||
|
||||
/** Stops the compile if no thread has started it; false when it is running or done. */
|
||||
boolean cancel() {
|
||||
return claimed.compareAndSet(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static AsyncCompile compileStageAsync(String src, int shaderKind, int glType, int shader) {
|
||||
@@ -285,40 +317,40 @@ public final class ShaderManager {
|
||||
LOG.warn("glsl-transformation-lib parse failed for '{}': {}", debugName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
if (isGraphics(glShaderType)) {
|
||||
PerFrameBlockInjector.collectEdits(root, perFrame, perPass, edits);
|
||||
}
|
||||
final GlslVulkanPreprocess.Metadata meta = GlslVulkanPreprocess.collectEdits(transformedSource, root, glShaderType, debugName, true, edits);
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER) {
|
||||
ClipZRemap.collectEdits(root, edits);
|
||||
}
|
||||
SamplerStripper.collectEdits(root, transformedSource, edits);
|
||||
final String s = edits.isEmpty() ? transformedSource : GlslVulkanPreprocess.applyEdits(transformedSource, edits);
|
||||
return new PrewarmTransformResult(s, meta.boolUniforms());
|
||||
return transform(transformedSource, 0, root, glShaderType, debugName, perFrame, perPass);
|
||||
}
|
||||
|
||||
public static PrewarmTransformResult applyPrewarmTransformsFull(String finalSource, GLSLParser.Translation_unitContext bodyTree, int headerLen, int glShaderType, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {
|
||||
final String header = finalSource.substring(0, headerLen);
|
||||
final String body = finalSource.substring(headerLen);
|
||||
return transform(finalSource, headerLen, bodyTree, glShaderType, "prewarm", perFrame, perPass);
|
||||
}
|
||||
|
||||
private static PrewarmTransformResult transform(String source, int headerLen, GLSLParser.Translation_unitContext tree, int glShaderType, String debugName, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {
|
||||
final String body = source.substring(headerLen);
|
||||
final ShaderIndex index = new ShaderIndex(tree, body);
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
if (isGraphics(glShaderType)) {
|
||||
PerFrameBlockInjector.collectEdits(bodyTree, perFrame, perPass, edits);
|
||||
PerFrameBlockInjector.collectEdits(index, perFrame, perPass, edits);
|
||||
}
|
||||
final GlslVulkanPreprocess.Metadata meta = GlslVulkanPreprocess.collectEdits(null, bodyTree, glShaderType, "prewarm", true, edits);
|
||||
final GlslVulkanPreprocess.Metadata meta = GlslVulkanPreprocess.collectEdits(index, glShaderType, debugName, true, edits);
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER) {
|
||||
ClipZRemap.collectEdits(bodyTree, edits);
|
||||
ClipZRemap.collectEdits(tree, edits);
|
||||
}
|
||||
SamplerStripper.collectEdits(bodyTree, body, edits);
|
||||
String outHeader = header;
|
||||
SamplerStripper.collectEdits(index, edits);
|
||||
if (glShaderType == GL20.GL_FRAGMENT_SHADER) {
|
||||
DerivativeHoisting.collectEdits(index, edits);
|
||||
}
|
||||
if (headerLen == 0) {
|
||||
return new PrewarmTransformResult(GlslVulkanPreprocess.applyEdits(source, edits), meta.boolUniforms());
|
||||
}
|
||||
if (edits.isEmpty() && !meta.needsSamplerless()) {
|
||||
return new PrewarmTransformResult(source, meta.boolUniforms());
|
||||
}
|
||||
String header = source.substring(0, headerLen);
|
||||
if (meta.needsSamplerless()) {
|
||||
final int nl = header.indexOf('\n');
|
||||
outHeader = header.substring(0, nl) + "\n" + GlslVulkanPreprocess.SAMPLERLESS_EXTENSION + header.substring(nl);
|
||||
header = header.substring(0, nl) + "\n" + GlslVulkanPreprocess.SAMPLERLESS_EXTENSION + header.substring(nl);
|
||||
}
|
||||
if (edits.isEmpty() && outHeader == header) {
|
||||
return new PrewarmTransformResult(finalSource, meta.boolUniforms());
|
||||
}
|
||||
return new PrewarmTransformResult(outHeader + GlslVulkanPreprocess.applyEdits(body, edits), meta.boolUniforms());
|
||||
return new PrewarmTransformResult(header + GlslVulkanPreprocess.applyEdits(body, edits), meta.boolUniforms());
|
||||
}
|
||||
|
||||
public static void prewarmSpirv(String transformedSource, int glShaderType) {
|
||||
@@ -377,6 +409,10 @@ public final class ShaderManager {
|
||||
final SpirvCompiler.Result r;
|
||||
final boolean alreadyRemapped;
|
||||
if (obj.spirvFuture != null) {
|
||||
if (obj.queuedCompile != null) {
|
||||
obj.queuedCompile.run();
|
||||
obj.queuedCompile = null;
|
||||
}
|
||||
final AsyncCompile compiled = obj.spirvFuture.join();
|
||||
obj.spirvFuture = null;
|
||||
r = compiled.result();
|
||||
@@ -431,6 +467,9 @@ public final class ShaderManager {
|
||||
final CompletableFuture<AsyncCompile> future = obj.spirvFuture;
|
||||
if (future == null) return;
|
||||
obj.spirvFuture = null;
|
||||
final QueuedCompile queued = obj.queuedCompile;
|
||||
obj.queuedCompile = null;
|
||||
if (queued != null && queued.cancel()) return;
|
||||
future.thenAccept(ShaderManager::freeSpirvResult);
|
||||
}
|
||||
|
||||
@@ -567,7 +606,7 @@ public final class ShaderManager {
|
||||
registerExtraNames(prog, vs.reflection);
|
||||
registerExtraNames(prog, fs.reflection);
|
||||
|
||||
for (final String name : prog.allSamplerNames) ensureLocation(prog, name);
|
||||
registerSamplerLocations(prog, vs.reflection, fs.reflection);
|
||||
for (final String name : prog.allImageNames) ensureLocation(prog, name);
|
||||
|
||||
prog.buildUniformSlotArrays();
|
||||
@@ -611,7 +650,7 @@ public final class ShaderManager {
|
||||
|
||||
applyUboMembers(prog, refl, false);
|
||||
registerExtraNames(prog, refl);
|
||||
for (final String name : prog.allSamplerNames) ensureLocation(prog, name);
|
||||
registerSamplerLocations(prog, refl);
|
||||
for (final String name : prog.allImageNames) ensureLocation(prog, name);
|
||||
prog.buildUniformSlotArrays();
|
||||
|
||||
@@ -820,8 +859,52 @@ public final class ShaderManager {
|
||||
return built;
|
||||
}
|
||||
|
||||
private static ShaderDiskCache.Key variantKey(String vertexSource, List<UscaledRetype.Attrib> attribs) {
|
||||
final ShaderDiskCache.Key key = ShaderDiskCache.key("sdl-vertex-variant").str(SpirvCompiler.toolchainId()).str(CrossCompileUtil.spvcId())
|
||||
.str(vertexSource).i(attribs.size());
|
||||
for (UscaledRetype.Attrib a : attribs) {
|
||||
key.str(a.name()).i(a.location()).i(a.declVecSize()).i(a.boundVecSize()).i(a.signed() ? 1 : 0);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
private VertexVariant buildVertexVariant(int program, ProgramObject prog, List<UscaledRetype.Attrib> attribs) {
|
||||
final String preprocessed = SpirvCompiler.preprocess(prog.vertexSource, Shaderc.shaderc_vertex_shader, "variant" + program, SpirvCompiler.Options.vulkanForced460Core());
|
||||
final ShaderDiskCache.Key diskKey = ShaderDiskCache.isEnabled() ? variantKey(prog.vertexSource, attribs) : null;
|
||||
final ShaderStageSerializer.Stage cached = diskKey != null ? loadStage(diskKey) : null;
|
||||
final ShaderStageSerializer.Stage stage = cached != null ? cached : compileVertexVariant(program, prog.vertexSource, attribs, diskKey);
|
||||
if (stage == null) return null;
|
||||
|
||||
final StageReflection refl = stage.reflection();
|
||||
if (!sameVaryingLayout(prog.vertexReflection.vsOutputs(), refl.vsOutputs())) {
|
||||
LOG.error("Vertex attribute conversion: program {} variant changed VS output locations; the fragment stage is patched against the base layout. Falling back to normalized coercion.", program);
|
||||
return null;
|
||||
}
|
||||
final VertexVariant v = new VertexVariant();
|
||||
v.spirv = ShaderCacheIO.toNative(stage.spirv());
|
||||
|
||||
final Object2IntOpenHashMap<String> bindings = new Object2IntOpenHashMap<>(prog.attribLocationBindings);
|
||||
bindings.defaultReturnValue(-1);
|
||||
for (UscaledRetype.Attrib a : attribs) bindings.put(a.name() + UscaledRetype.SUFFIX, a.location());
|
||||
v.inputMask = patchAttribLocations(v.spirv, refl, bindings, v.inputVecSize, v.inputBaseType, null, null);
|
||||
if (v.inputMask != prog.vertexInputMask) {
|
||||
memFree(v.spirv);
|
||||
LOG.error("Vertex attribute conversion: program {} variant resolved inputs to 0x{} but the program binds 0x{}; a renamed input landed on the wrong location. Falling back to normalized coercion.",
|
||||
program, Integer.toHexString(v.inputMask), Integer.toHexString(prog.vertexInputMask));
|
||||
return null;
|
||||
}
|
||||
v.sdlShader = createSDLShader(v.spirv, SDL_GPU_SHADERSTAGE_VERTEX, prog.vertexResources.numSamplers(), prog.vertexResources.numUBOs(), prog.vertexResources.numStorageBuffers(), prog.vertexResources.numStorageTextures());
|
||||
if (v.sdlShader == 0) {
|
||||
memFree(v.spirv);
|
||||
LOG.error("Vertex attribute conversion: program {} variant shader creation failed: {}", program, SDLError.SDL_GetError());
|
||||
return null;
|
||||
}
|
||||
LOG.info("Vertex attribute conversion: program {} converted {}", program, attribs);
|
||||
if (SystemProperties.dumpShaders()) dumpVariantSource(program, stage.source());
|
||||
return v;
|
||||
}
|
||||
|
||||
private static ShaderStageSerializer.Stage compileVertexVariant(int program, String vertexSource, List<UscaledRetype.Attrib> attribs, ShaderDiskCache.Key diskKey) {
|
||||
final String preprocessed = SpirvCompiler.preprocess(vertexSource, Shaderc.shaderc_vertex_shader, "variant" + program, SpirvCompiler.Options.vulkanForced460Core());
|
||||
if (preprocessed == null) {
|
||||
LOG.warn("Vertex attribute conversion: program {} preprocessing failed; falling back to normalized coercion, values will be scaled", program);
|
||||
return null;
|
||||
@@ -839,32 +922,11 @@ public final class ShaderManager {
|
||||
try {
|
||||
remapSpirvForSDLGPU(r.spirv(), GL20.GL_VERTEX_SHADER);
|
||||
final StageReflection refl = reflectStage(r.spirv(), true);
|
||||
if (!sameVaryingLayout(prog.vertexReflection.vsOutputs(), refl.vsOutputs())) {
|
||||
LOG.error("Vertex attribute conversion: program {} variant changed VS output locations; the fragment stage is patched against the base layout. Falling back to normalized coercion.", program);
|
||||
return null;
|
||||
final byte[] heap = ShaderCacheIO.toHeap(r.spirv());
|
||||
if (diskKey != null && refl != StageReflection.EMPTY) {
|
||||
ShaderDiskCache.put(diskKey, ShaderStageSerializer.encode(heap, refl, GraphicsBindingMap.EMPTY, null, src));
|
||||
}
|
||||
final VertexVariant v = new VertexVariant();
|
||||
v.spirv = copyBuffer(r.spirv());
|
||||
|
||||
final Object2IntOpenHashMap<String> bindings = new Object2IntOpenHashMap<>(prog.attribLocationBindings);
|
||||
bindings.defaultReturnValue(-1);
|
||||
for (UscaledRetype.Attrib a : attribs) bindings.put(a.name() + UscaledRetype.SUFFIX, a.location());
|
||||
v.inputMask = patchAttribLocations(v.spirv, refl, bindings, v.inputVecSize, v.inputBaseType, null, null);
|
||||
if (v.inputMask != prog.vertexInputMask) {
|
||||
memFree(v.spirv);
|
||||
LOG.error("Vertex attribute conversion: program {} variant resolved inputs to 0x{} but the program binds 0x{}; a renamed input landed on the wrong location. Falling back to normalized coercion.",
|
||||
program, Integer.toHexString(v.inputMask), Integer.toHexString(prog.vertexInputMask));
|
||||
return null;
|
||||
}
|
||||
v.sdlShader = createSDLShader(v.spirv, SDL_GPU_SHADERSTAGE_VERTEX, prog.vertexResources.numSamplers(), prog.vertexResources.numUBOs(), prog.vertexResources.numStorageBuffers(), prog.vertexResources.numStorageTextures());
|
||||
if (v.sdlShader == 0) {
|
||||
memFree(v.spirv);
|
||||
LOG.error("Vertex attribute conversion: program {} variant shader creation failed: {}", program, SDLError.SDL_GetError());
|
||||
return null;
|
||||
}
|
||||
LOG.info("Vertex attribute conversion: program {} converted {}", program, attribs);
|
||||
if (SystemProperties.dumpShaders()) dumpVariantSource(program, src);
|
||||
return v;
|
||||
return new ShaderStageSerializer.Stage(heap, refl, GraphicsBindingMap.EMPTY, Set.of(), src);
|
||||
} finally {
|
||||
memFree(r.spirv());
|
||||
}
|
||||
@@ -1101,7 +1163,7 @@ public final class ShaderManager {
|
||||
return new CrossCompiled(SDL_GPU_SHADERFORMAT_SPIRV, spirv, "main", false);
|
||||
}
|
||||
if (device.supportsMsl()) {
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, glShaderType);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, glShaderType, device.metalTextureAtomics());
|
||||
return new CrossCompiled(SDL_GPU_SHADERFORMAT_MSL, out.code(), out.entrypoint(), true);
|
||||
}
|
||||
if (device.supportsDxbc()) {
|
||||
@@ -1476,6 +1538,7 @@ public final class ShaderManager {
|
||||
public boolean deletePending;
|
||||
public int attachCount;
|
||||
public CompletableFuture<AsyncCompile> spirvFuture;
|
||||
QueuedCompile queuedCompile;
|
||||
public StageReflection reflection = StageReflection.EMPTY;
|
||||
public GraphicsBindingMap graphicsBindingMap = GraphicsBindingMap.EMPTY;
|
||||
public Set<String> boolUniforms = Set.of();
|
||||
@@ -1520,6 +1583,7 @@ public final class ShaderManager {
|
||||
public record StageReflection(
|
||||
ResourceCounts counts,
|
||||
List<String> samplerNames, // SAMPLED_IMAGE, sorted by binding (post-remap)
|
||||
List<String> unusedSamplerNames, // declared samplers the entry point never reads
|
||||
List<String> extraUniformNames, // SEPARATE_SAMPLERS - registered in nameToLocation only
|
||||
List<String> storageImageNames, // SEPARATE_IMAGE + STORAGE_IMAGE (RO + RW), sorted by binding (post-remap)
|
||||
int uboSize, // binding-0 UBO total size in bytes; 0 if absent
|
||||
@@ -1535,7 +1599,7 @@ public final class ShaderManager {
|
||||
BlockReflection[] blocks
|
||||
) {
|
||||
public static final StageReflection EMPTY = new StageReflection(
|
||||
ResourceCounts.EMPTY, List.of(), List.of(), List.of(), 0, List.of(), List.of(), List.of(), List.of(),
|
||||
ResourceCounts.EMPTY, List.of(), List.of(), List.of(), List.of(), 0, List.of(), List.of(), List.of(), List.of(),
|
||||
-1, 0, 0, 0, 0, BlockReflection.emptyBlocks());
|
||||
}
|
||||
|
||||
@@ -1739,6 +1803,7 @@ public final class ShaderManager {
|
||||
return new StageReflection(
|
||||
new ResourceCounts(samplerNames.size(), numUBOs, numStorageBuffers, numStorageTextures),
|
||||
samplerNames,
|
||||
unusedSamplerNames(compiler, resources, stack),
|
||||
extraNames,
|
||||
storageImageNames,
|
||||
uboSize,
|
||||
@@ -1822,6 +1887,36 @@ public final class ShaderManager {
|
||||
});
|
||||
}
|
||||
|
||||
private static List<String> unusedSamplerNames(long compiler, long resources, MemoryStack stack) {
|
||||
final PointerBuffer pActive = stack.mallocPointer(1);
|
||||
final PointerBuffer pActiveResources = stack.mallocPointer(1);
|
||||
if (Spvc.spvc_compiler_get_active_interface_variables(compiler, pActive) != Spvc.SPVC_SUCCESS
|
||||
|| Spvc.spvc_compiler_create_shader_resources_for_active_variables(compiler, pActiveResources, pActive.get(0)) != Spvc.SPVC_SUCCESS) {
|
||||
return List.of();
|
||||
}
|
||||
final IntOpenHashSet active = new IntOpenHashSet();
|
||||
forEachResource(pActiveResources.get(0), Spvc.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, stack, (i, r) -> active.add(r.id()));
|
||||
final List<String> unused = new ArrayList<>();
|
||||
forEachResource(resources, Spvc.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, stack, (i, r) -> {
|
||||
if (active.contains(r.id())) return;
|
||||
final String name = Spvc.spvc_compiler_get_name(compiler, r.id());
|
||||
if (name != null && !name.isEmpty()) unused.add(name);
|
||||
});
|
||||
return unused;
|
||||
}
|
||||
|
||||
private static void registerSamplerLocations(ProgramObject prog, StageReflection... stages) {
|
||||
final Set<String> read = new HashSet<>();
|
||||
for (StageReflection stage : stages) {
|
||||
for (String name : stage.samplerNames()) {
|
||||
if (!stage.unusedSamplerNames().contains(name)) read.add(name);
|
||||
}
|
||||
}
|
||||
for (String name : prog.allSamplerNames) {
|
||||
if (read.contains(name)) ensureLocation(prog, name);
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectResourceNames(long resources, long compiler, int resourceType, List<String> out, MemoryStack stack) {
|
||||
forEachResource(resources, resourceType, stack, (i, r) -> {
|
||||
final String name = r.nameString();
|
||||
@@ -1985,6 +2080,7 @@ public final class ShaderManager {
|
||||
public int vertexShader;
|
||||
public int vertexUboSize;
|
||||
public long sdlComputePipeline;
|
||||
public VertexWriteReplayer.Replay vertexWriteReplay;
|
||||
public long sdlFragmentShader;
|
||||
public long sdlVertexShader;
|
||||
public long lastComputeCb;
|
||||
|
||||
+3
-1
@@ -102,6 +102,7 @@ final class ShaderStageSerializer {
|
||||
out.writeInt(r.counts().numStorageBuffers());
|
||||
out.writeInt(r.counts().numStorageTextures());
|
||||
writeStrList(out, r.samplerNames());
|
||||
writeStrList(out, r.unusedSamplerNames());
|
||||
writeStrList(out, r.extraUniformNames());
|
||||
writeStrList(out, r.storageImageNames());
|
||||
out.writeInt(r.uboSize());
|
||||
@@ -139,6 +140,7 @@ final class ShaderStageSerializer {
|
||||
private static StageReflection readReflection(ByteBuffer in) {
|
||||
final ResourceCounts counts = new ResourceCounts(in.getInt(), in.getInt(), in.getInt(), in.getInt());
|
||||
final List<String> samplerNames = readStrList(in);
|
||||
final List<String> unusedSamplerNames = readStrList(in);
|
||||
final List<String> extraNames = readStrList(in);
|
||||
final List<String> storageImageNames = readStrList(in);
|
||||
final int uboSize = in.getInt();
|
||||
@@ -156,7 +158,7 @@ final class ShaderStageSerializer {
|
||||
for (int b = 0; b < blockCount; b++) {
|
||||
blocks[b] = new BlockReflection(in.getInt(), in.getInt(), readList(in, ShaderStageSerializer::readMember), in.get() != 0);
|
||||
}
|
||||
return new StageReflection(counts, samplerNames, extraNames, storageImageNames, uboSize, uboMembers, vsInputs, vsOutputs, fsInputs,
|
||||
return new StageReflection(counts, samplerNames, unusedSamplerNames, extraNames, storageImageNames, uboSize, uboMembers, vsInputs, vsOutputs, fsInputs,
|
||||
maxOutputLocation, roBuf, rwBuf, roTex, rwTex, blocks);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
@@ -60,16 +61,7 @@ public final class UscaledRetype {
|
||||
final GLSLParser.Single_declarationContext single = idl.single_declaration();
|
||||
if (single == null || single.fully_specified_type() == null) return;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
if (fst.type_qualifier() == null) return;
|
||||
|
||||
boolean isIn = false;
|
||||
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
|
||||
if (stq.storage_qualifier() != null && "in".equals(stq.storage_qualifier().getText())) {
|
||||
isIn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isIn) return;
|
||||
if (!ShaderIndex.hasStorageQualifier(fst.type_qualifier(), "in")) return;
|
||||
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
|
||||
if (single.typeless_declaration() == null || single.typeless_declaration().IDENTIFIER() == null) return;
|
||||
|
||||
|
||||
+17
-6
@@ -150,18 +150,30 @@ public final class CrossCompileUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean usesImageAtomics(ByteBuffer spirv) {
|
||||
final IntBuffer w = spirv.asIntBuffer();
|
||||
for (int i = SPIRV_HEADER_WORDS; i < w.limit(); i = nextInstruction(w, i)) {
|
||||
if ((w.get(i) & 0xFFFF) == Spv.SpvOpImageTexelPointer) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final int SPIRV_HEADER_WORDS = 5; // magic, version, generator, bound, schema
|
||||
|
||||
private static int nextInstruction(IntBuffer w, int i) {
|
||||
final int len = w.get(i) >>> 16;
|
||||
return len == 0 ? w.limit() : i + len;
|
||||
}
|
||||
|
||||
public static int[] parseLocalSize(ByteBuffer spirv) {
|
||||
final IntBuffer w = spirv.asIntBuffer();
|
||||
final int wordCount = w.limit();
|
||||
int i = 5; // 5-word SPIR-V header (magic, version, generator, bound, schema)
|
||||
int lx = 1, ly = 1, lz = 1;
|
||||
boolean foundLiteral = false;
|
||||
boolean sawExecutionModeId = false;
|
||||
while (i < wordCount) {
|
||||
for (int i = SPIRV_HEADER_WORDS; i < w.limit(); i = nextInstruction(w, i)) {
|
||||
final int word0 = w.get(i);
|
||||
final int opcode = word0 & 0xFFFF;
|
||||
final int instrLen = (word0 >>> 16) & 0xFFFF;
|
||||
if (instrLen == 0) break;
|
||||
final int instrLen = word0 >>> 16;
|
||||
if (opcode == 16 && instrLen >= 6) { // SpvOpExecutionMode
|
||||
final int mode = w.get(i + 2);
|
||||
if (mode == 17) { // SpvExecutionModeLocalSize
|
||||
@@ -173,7 +185,6 @@ public final class CrossCompileUtil {
|
||||
} else if (opcode == 331) { // SpvOpExecutionModeId
|
||||
sawExecutionModeId = true;
|
||||
}
|
||||
i += instrLen;
|
||||
}
|
||||
if (!foundLiteral && sawExecutionModeId) {
|
||||
LOG.warn("SPIR-V uses OpExecutionModeId (spec-constant LocalSizeId); falling back to {{1,1,1}} - not supported by parseLocalSize");
|
||||
|
||||
+10
-5
@@ -26,7 +26,11 @@ public final class MslCrossCompile {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
private static final String BACKEND = "MSL";
|
||||
|
||||
private static final CrossCompileCache CACHE = new CrossCompileCache("msl", CrossCompileUtil::spvcId, MslCrossCompile::crossCompileUncached);
|
||||
private static final int SPVC_MSL_2_2 = mslVersion(2, 2, 0);
|
||||
public static final int SPVC_MSL_3_1 = mslVersion(3, 1, 0);
|
||||
|
||||
private static final CrossCompileCache CACHE = new CrossCompileCache("msl", CrossCompileUtil::spvcId, (spirv, glShaderType) -> compileAtVersion(spirv, glShaderType, SPVC_MSL_2_2));
|
||||
private static final CrossCompileCache CACHE_3_1 = new CrossCompileCache("msl31", CrossCompileUtil::spvcId, (spirv, glShaderType) -> compileAtVersion(spirv, glShaderType, SPVC_MSL_3_1));
|
||||
|
||||
@FunctionalInterface
|
||||
private interface BufferSlotFn { public int slot(int set, int binding); }
|
||||
@@ -36,8 +40,9 @@ public final class MslCrossCompile {
|
||||
|
||||
private MslCrossCompile() {}
|
||||
|
||||
public static Output compile(ByteBuffer spirv, int glShaderType) {
|
||||
return CACHE.compile(spirv, glShaderType);
|
||||
public static Output compile(ByteBuffer spirv, int glShaderType, boolean textureAtomics) {
|
||||
final boolean atomics = textureAtomics && glShaderType == GL43.GL_COMPUTE_SHADER && CrossCompileUtil.usesImageAtomics(spirv);
|
||||
return (atomics ? CACHE_3_1 : CACHE).compile(spirv, glShaderType);
|
||||
}
|
||||
|
||||
private static int executionModelFor(int glShaderType) {
|
||||
@@ -47,7 +52,7 @@ public final class MslCrossCompile {
|
||||
throw new UnsupportedOperationException("Unsupported shader type for MSL cross-compile: 0x" + Integer.toHexString(glShaderType));
|
||||
}
|
||||
|
||||
private static Output crossCompileUncached(ByteBuffer spirv, int glShaderType) {
|
||||
public static Output compileAtVersion(ByteBuffer spirv, int glShaderType, int version) {
|
||||
final int executionModel = executionModelFor(glShaderType);
|
||||
final int dumpId = SystemProperties.dumpShaders() ? CrossCompileUtil.SHADER_DUMP_COUNTER.getAndIncrement() : -1;
|
||||
if (dumpId >= 0) CrossCompileUtil.dumpSpirv(spirv, dumpId, glShaderType);
|
||||
@@ -77,7 +82,7 @@ public final class MslCrossCompile {
|
||||
throw CrossCompileUtil.spvcError(ctx, BACKEND, "create_compiler_options");
|
||||
}
|
||||
final long opts = pOpts.get(0);
|
||||
Spvc.spvc_compiler_options_set_uint(opts, Spvc.SPVC_COMPILER_OPTION_MSL_VERSION, mslVersion(2, 2, 0));
|
||||
Spvc.spvc_compiler_options_set_uint(opts, Spvc.SPVC_COMPILER_OPTION_MSL_VERSION, version);
|
||||
Spvc.spvc_compiler_options_set_uint(opts, Spvc.SPVC_COMPILER_OPTION_MSL_PLATFORM, Spvc.SPVC_MSL_PLATFORM_MACOS);
|
||||
if (Spvc.spvc_compiler_install_compiler_options(compiler, opts) != Spvc.SPVC_SUCCESS) {
|
||||
throw CrossCompileUtil.spvcError(ctx, BACKEND, "install_compiler_options");
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#version 460 core
|
||||
layout(local_size_x = 64) in;
|
||||
layout(binding = 0, r32i) uniform iimage2D counter;
|
||||
|
||||
void main() {
|
||||
imageAtomicAdd(counter, ivec2(0), 1);
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.compute;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GetGPUDeviceDriver;
|
||||
|
||||
class ImageAtomicsGpuTest {
|
||||
|
||||
private static SdlTestRig rig;
|
||||
private static ImageAtomicsProbe probe;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() throws Exception {
|
||||
rig = SdlTestRig.acquireRealDevice();
|
||||
assumeTrue("metal".equalsIgnoreCase(SDL_GetGPUDeviceDriver(rig.sdlHandle)), "native Metal only");
|
||||
assumeTrue(ImageAtomicsProbe.defaultMslVersion() >= ImageAtomicsProbe.MTL_LANGUAGE_VERSION_3_1, "this process compiles Metal source below MSL 3.1");
|
||||
probe = new ImageAtomicsProbe(rig.sdlHandle);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
if (probe != null) probe.release();
|
||||
SdlTestRig.releaseRealDevice();
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomicsCountEveryInvocationOnAnImageTheSizeOfEuphoriasEndCrystalMap() {
|
||||
assertTrue(probe.run(40, 10));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -77,9 +77,9 @@ class VoxelizationDispatcherTest {
|
||||
void dispatchRegion_splitsAboveMaxGroups() {
|
||||
final RecordingSink sink = new RecordingSink();
|
||||
final VoxelizationDispatcher d = new VoxelizationDispatcher(sink);
|
||||
region(d, 1, VoxelizationDispatcher.MAX_GROUPS_PER_DISPATCH * 64 + 1, new ContextState());
|
||||
region(d, 1, InvocationDispatch.MAX_GROUPS_PER_DISPATCH * 64 + 1, new ContextState());
|
||||
assertEquals(2, sink.dispatches.size());
|
||||
assertEquals(VoxelizationDispatcher.MAX_GROUPS_PER_DISPATCH, sink.dispatches.get(0)[0]);
|
||||
assertEquals(InvocationDispatch.MAX_GROUPS_PER_DISPATCH, sink.dispatches.get(0)[0]);
|
||||
assertEquals(1, sink.dispatches.get(1)[0]);
|
||||
assertEquals(2, sink.uniformPushCount);
|
||||
}
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import it.unimi.dsi.fastutil.longs.Long2BooleanOpenHashMap;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
class DeviceImageAtomicsGpuTest {
|
||||
|
||||
private Device device;
|
||||
|
||||
@BeforeAll
|
||||
static void initSdl() {
|
||||
SdlTestRig.initSdlVideo();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (device != null) device.destroyDevice();
|
||||
}
|
||||
|
||||
private Long2BooleanOpenHashMap verdicts() {
|
||||
return Reflect.get(device, "imageAtomicsBySize");
|
||||
}
|
||||
|
||||
@Test
|
||||
void verdictsAreCachedPerSizeAndDroppedWithTheDevice() {
|
||||
device = new Device();
|
||||
assumeTrue(device.createDevice(), "no SDL GPU device on this machine");
|
||||
assumeTrue(device.metalTextureAtomics(), "needs native Metal compiling at MSL 3.1 or newer");
|
||||
|
||||
assertFalse(device.supportsImageAtomics(40, 10, 2));
|
||||
assertFalse(device.supportsImageAtomics(0, 10, 1));
|
||||
assertFalse(device.supportsImageAtomics(40, -1, 1));
|
||||
assertEquals(0, verdicts().size());
|
||||
|
||||
assertNull(Reflect.get(device, "imageAtomicsProbe"));
|
||||
|
||||
assertTrue(device.supportsImageAtomics(40, 10, 1));
|
||||
assertFalse(device.supportsImageAtomics(64, 64, 1));
|
||||
assertEquals(2, verdicts().size());
|
||||
final Object probe = Reflect.get(device, "imageAtomicsProbe");
|
||||
assertTrue(device.supportsImageAtomics(40, 10, 1));
|
||||
assertFalse(device.supportsImageAtomics(64, 64, 1));
|
||||
assertEquals(2, verdicts().size());
|
||||
assertEquals(probe, Reflect.get(device, "imageAtomicsProbe"));
|
||||
|
||||
device.destroyDevice();
|
||||
assertFalse(device.metalTextureAtomics());
|
||||
assertFalse(device.supportsImageAtomics(40, 10, 1));
|
||||
assertEquals(0, verdicts().size());
|
||||
assertNull(Reflect.get(device, "imageAtomicsProbe"));
|
||||
}
|
||||
}
|
||||
+15
-11
@@ -1,32 +1,36 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.system.Platform;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.device.Device.driverCandidates;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.device.Device.metalNeedsNewerSdl;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.device.Device.resolveDriverName;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLVersion.SDL_VERSIONNUM;
|
||||
|
||||
class DriverSelectionTest {
|
||||
|
||||
@Test
|
||||
void windowsWithoutUserHintForcesVulkan() {
|
||||
assertEquals("vulkan", resolveDriverName("", true));
|
||||
void autoUsesThePlatformOrder() {
|
||||
assertArrayEquals(new String[] { "metal", "vulkan" }, driverCandidates("", Platform.MACOSX));
|
||||
assertArrayEquals(new String[] { "vulkan", "direct3d12" }, driverCandidates("", Platform.WINDOWS));
|
||||
assertArrayEquals(new String[] { "vulkan" }, driverCandidates("", Platform.LINUX));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userHintWinsOnWindows() {
|
||||
assertNull(resolveDriverName("direct3d12", true));
|
||||
assertNull(resolveDriverName("vulkan", true));
|
||||
void requestedDriverGoesFirstWithoutDuplicates() {
|
||||
assertArrayEquals(new String[] { "vulkan", "metal" }, driverCandidates("vulkan", Platform.MACOSX));
|
||||
assertArrayEquals(new String[] { "direct3d12", "vulkan" }, driverCandidates("direct3d12", Platform.WINDOWS));
|
||||
assertArrayEquals(new String[] { "vulkan" }, driverCandidates("vulkan", Platform.LINUX));
|
||||
assertArrayEquals(new String[] { "Vulkan", "direct3d12" }, driverCandidates("Vulkan", Platform.WINDOWS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonWindowsIsUntouched() {
|
||||
assertNull(resolveDriverName("", false));
|
||||
assertNull(resolveDriverName("direct3d12", false));
|
||||
void unsupportedDriverStillFallsBack() {
|
||||
assertArrayEquals(new String[] { "metal", "vulkan", "direct3d12" }, driverCandidates("metal", Platform.WINDOWS));
|
||||
assertArrayEquals(new String[] { "bogus", "vulkan" }, driverCandidates("bogus", Platform.LINUX));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+393
@@ -0,0 +1,393 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.opengl.GL44;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
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.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlBufferMappingLifetimeTest {
|
||||
|
||||
private static final int SIZE = 4096;
|
||||
private static final int PERSISTENT_STORAGE = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT | GL44.GL_CLIENT_STORAGE_BIT;
|
||||
private static final int PERSISTENT_MAP = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT;
|
||||
private static final int PLAIN_MAP = GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
}
|
||||
|
||||
private static void fill(ByteBuffer buf, int seed) {
|
||||
for (int i = 0; i < buf.capacity(); i++) buf.put(i, (byte) (seed * 37 + i * 7 + 1));
|
||||
}
|
||||
|
||||
private static void assertPattern(ByteBuffer actual, int seed) {
|
||||
for (int i = 0; i < actual.capacity(); i++) {
|
||||
assertEquals((byte) (seed * 37 + i * 7 + 1), actual.get(i), "byte " + i + " of pattern " + seed);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer readBack(int target, int size) {
|
||||
final ByteBuffer out = MemoryUtil.memAlloc(size);
|
||||
GLStateManager.glGetBufferSubData(target, 0, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static ByteBuffer mapPersistent(int target, int id) {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferStorage(target, SIZE, PERSISTENT_STORAGE);
|
||||
return GLStateManager.glMapBufferRange(target, 0, SIZE, PERSISTENT_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bufferDataOnPersistentlyMappedBufferIsRefusedAndMappingSurvives() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
final ByteBuffer mapped = mapPersistent(target, id);
|
||||
assertNotNull(mapped);
|
||||
GLStateManager.glBufferData(target, SIZE * 2L, GL15.GL_STATIC_DRAW);
|
||||
fill(mapped, 1);
|
||||
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
|
||||
out = readBack(target, SIZE);
|
||||
assertPattern(out, 1);
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistentMapWithoutPersistentStorageReturnsNull() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferData(target, (long) SIZE, GL15.GL_STATIC_DRAW);
|
||||
assertNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PERSISTENT_MAP));
|
||||
} finally {
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondPersistentMapReturnsNullAndFirstStillRoundTrips() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
final ByteBuffer first = mapPersistent(target, id);
|
||||
assertNotNull(first);
|
||||
assertNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PERSISTENT_MAP));
|
||||
fill(first, 2);
|
||||
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
|
||||
out = readBack(target, SIZE);
|
||||
assertPattern(out, 2);
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoBuffersMappedNonPersistentlyKeepTheirOwnData() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int a = GLStateManager.glGenBuffers();
|
||||
final int b = GLStateManager.glGenBuffers();
|
||||
ByteBuffer outA = null;
|
||||
ByteBuffer outB = null;
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, a);
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
GLStateManager.glBindBuffer(target, b);
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
|
||||
GLStateManager.glBindBuffer(target, a);
|
||||
final ByteBuffer mapA = GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP);
|
||||
assertNotNull(mapA);
|
||||
GLStateManager.glBindBuffer(target, b);
|
||||
final ByteBuffer mapB = GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP);
|
||||
assertNotNull(mapB);
|
||||
fill(mapA, 3);
|
||||
fill(mapB, 4);
|
||||
|
||||
GLStateManager.glBindBuffer(target, a);
|
||||
assertTrue(GLStateManager.glUnmapBuffer(target));
|
||||
GLStateManager.glBindBuffer(target, b);
|
||||
assertTrue(GLStateManager.glUnmapBuffer(target));
|
||||
|
||||
GLStateManager.glBindBuffer(target, a);
|
||||
outA = readBack(target, SIZE);
|
||||
assertPattern(outA, 3);
|
||||
GLStateManager.glBindBuffer(target, b);
|
||||
outB = readBack(target, SIZE);
|
||||
assertPattern(outB, 4);
|
||||
} finally {
|
||||
if (outA != null) MemoryUtil.memFree(outA);
|
||||
if (outB != null) MemoryUtil.memFree(outB);
|
||||
GLStateManager.glDeleteBuffers(a);
|
||||
GLStateManager.glDeleteBuffers(b);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void unmapOfUnmappedBufferReturnsFalseAndLeavesOtherMappingIntact() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int mappedId = GLStateManager.glGenBuffers();
|
||||
final int idle = GLStateManager.glGenBuffers();
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, mappedId);
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
final ByteBuffer map = GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP);
|
||||
assertNotNull(map);
|
||||
fill(map, 5);
|
||||
|
||||
GLStateManager.glBindBuffer(target, idle);
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
assertFalse(GLStateManager.glUnmapBuffer(target));
|
||||
|
||||
GLStateManager.glBindBuffer(target, mappedId);
|
||||
assertTrue(GLStateManager.glUnmapBuffer(target));
|
||||
out = readBack(target, SIZE);
|
||||
assertPattern(out, 5);
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
GLStateManager.glDeleteBuffers(mappedId);
|
||||
GLStateManager.glDeleteBuffers(idle);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void outOfRangeUniformSubDataIsIgnored() {
|
||||
final int ubo = GLStateManager.glGenBuffers();
|
||||
final ByteBuffer data = MemoryUtil.memAlloc(256);
|
||||
final ByteBuffer big = MemoryUtil.memAlloc(64);
|
||||
try {
|
||||
fill(data, 6);
|
||||
fill(big, 7);
|
||||
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, ubo);
|
||||
GLStateManager.glBufferData(GL31.GL_UNIFORM_BUFFER, data, GL15.GL_DYNAMIC_DRAW);
|
||||
assertDoesNotThrow(() -> {
|
||||
big.clear();
|
||||
GLStateManager.glBufferSubData(GL31.GL_UNIFORM_BUFFER, 1024, big);
|
||||
big.clear();
|
||||
GLStateManager.glBufferSubData(GL31.GL_UNIFORM_BUFFER, 224, big);
|
||||
big.clear();
|
||||
GLStateManager.glBufferSubData(GL31.GL_UNIFORM_BUFFER, -8, big);
|
||||
});
|
||||
} finally {
|
||||
MemoryUtil.memFree(data);
|
||||
MemoryUtil.memFree(big);
|
||||
GLStateManager.glDeleteBuffers(ubo);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void outOfRangeCopyBufferSubDataIsIgnored() {
|
||||
final int src = GLStateManager.glGenBuffers();
|
||||
final int dst = GLStateManager.glGenBuffers();
|
||||
final ByteBuffer data = MemoryUtil.memAlloc(256);
|
||||
ByteBuffer outDst = null;
|
||||
try {
|
||||
fill(data, 6);
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_READ_BUFFER, src);
|
||||
GLStateManager.glBufferData(GL31.GL_COPY_READ_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, dst);
|
||||
data.clear();
|
||||
GLStateManager.glBufferData(GL31.GL_COPY_WRITE_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 200, 0, 128);
|
||||
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0, 200, 128);
|
||||
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, -4, 0, 16);
|
||||
outDst = readBack(GL31.GL_COPY_WRITE_BUFFER, 256);
|
||||
assertPattern(outDst, 6);
|
||||
} finally {
|
||||
if (outDst != null) MemoryUtil.memFree(outDst);
|
||||
MemoryUtil.memFree(data);
|
||||
GLStateManager.glDeleteBuffers(src);
|
||||
GLStateManager.glDeleteBuffers(dst);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void outOfRangeClearBufferSubDataIsIgnored() {
|
||||
final int clearStart = 64;
|
||||
final int clearEnd = 96;
|
||||
final int ssbo = GLStateManager.glGenBuffers();
|
||||
final ByteBuffer data = MemoryUtil.memAlloc(256);
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
fill(data, 8);
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, ssbo);
|
||||
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, data, GL15.GL_DYNAMIC_DRAW);
|
||||
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, clearStart, clearEnd - clearStart, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
|
||||
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, 200, 128, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
|
||||
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, -4, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
|
||||
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, 1024, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
out = readBack(GL43.GL_SHADER_STORAGE_BUFFER, 256);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
final boolean cleared = i >= clearStart && i < clearEnd;
|
||||
assertEquals(cleared ? 0 : data.get(i), out.get(i), cleared ? "in-range clear did not zero byte " + i : "out-of-range clear touched byte " + i);
|
||||
}
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
MemoryUtil.memFree(data);
|
||||
GLStateManager.glDeleteBuffers(ssbo);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondBufferStorageIsRefused() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferStorage(target, SIZE, PERSISTENT_STORAGE);
|
||||
GLStateManager.glBufferStorage(target, SIZE * 2L, PERSISTENT_STORAGE);
|
||||
assertEquals(GL11.GL_TRUE, GLStateManager.glGetBufferParameteri(target, GL44.GL_BUFFER_IMMUTABLE_STORAGE));
|
||||
assertEquals(SIZE, GLStateManager.glGetBufferParameteri(target, GL15.GL_BUFFER_SIZE));
|
||||
} finally {
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedBufferDataOnImmutableStorageIsRefused() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferStorage(target, SIZE, PERSISTENT_STORAGE);
|
||||
GLStateManager.glNamedBufferData(id, SIZE * 2L, GL15.GL_STATIC_DRAW);
|
||||
assertEquals(SIZE, GLStateManager.glGetBufferParameteri(target, GL15.GL_BUFFER_SIZE));
|
||||
} finally {
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void bufferStorageWithNullDataMarksBufferImmutable() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferStorage(target, (ByteBuffer) null, PERSISTENT_STORAGE);
|
||||
assertEquals(GL11.GL_TRUE, GLStateManager.glGetBufferParameteri(target, GL44.GL_BUFFER_IMMUTABLE_STORAGE));
|
||||
} finally {
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertBufferDataUnmapsPlainMapping(int target) {
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
assertNotNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP));
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
assertNotNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP));
|
||||
assertTrue(GLStateManager.glUnmapBuffer(target));
|
||||
} finally {
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void bufferDataOnMappedArrayBufferUnmapsIt() {
|
||||
assertBufferDataUnmapsPlainMapping(GL15.GL_ARRAY_BUFFER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bufferDataOnMappedUniformBufferUnmapsIt() {
|
||||
assertBufferDataUnmapsPlainMapping(GL31.GL_UNIFORM_BUFFER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void outOfRangeFlushOfPersistentMappingIsIgnored() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
final ByteBuffer mapped = mapPersistent(target, id);
|
||||
assertNotNull(mapped);
|
||||
GLStateManager.glFlushMappedBufferRange(target, SIZE, 64);
|
||||
GLStateManager.glFlushMappedBufferRange(target, SIZE - 16, 64);
|
||||
GLStateManager.glFlushMappedBufferRange(target, -8, 16);
|
||||
fill(mapped, 9);
|
||||
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
|
||||
out = readBack(target, SIZE);
|
||||
assertPattern(out, 9);
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistentMappingAtNonZeroOffsetRoundTrips() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
final ByteBuffer out = MemoryUtil.memAlloc(SIZE);
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferStorage(target, SIZE * 2L, PERSISTENT_STORAGE);
|
||||
final ByteBuffer mapped = GLStateManager.glMapBufferRange(target, SIZE, SIZE, PERSISTENT_MAP);
|
||||
assertNotNull(mapped);
|
||||
fill(mapped, 10);
|
||||
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
|
||||
GLStateManager.glGetBufferSubData(target, SIZE, out);
|
||||
assertPattern(out, 10);
|
||||
} finally {
|
||||
MemoryUtil.memFree(out);
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyFromPersistentMappingShorterThanBufferReadsGpuContents() {
|
||||
final int src = GLStateManager.glGenBuffers();
|
||||
final int dst = GLStateManager.glGenBuffers();
|
||||
final ByteBuffer data = MemoryUtil.memAlloc(SIZE * 2);
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
fill(data, 11);
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_READ_BUFFER, src);
|
||||
GLStateManager.glBufferStorage(GL31.GL_COPY_READ_BUFFER, data, PERSISTENT_STORAGE);
|
||||
assertNotNull(GLStateManager.glMapBufferRange(GL31.GL_COPY_READ_BUFFER, 0, SIZE, PERSISTENT_MAP));
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, dst);
|
||||
GLStateManager.glBufferData(GL31.GL_COPY_WRITE_BUFFER, SIZE, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, SIZE, 0, 256);
|
||||
out = readBack(GL31.GL_COPY_WRITE_BUFFER, 256);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
assertEquals(data.get(SIZE + i), out.get(i), "byte " + i);
|
||||
}
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
MemoryUtil.memFree(data);
|
||||
GLStateManager.glDeleteBuffers(src);
|
||||
GLStateManager.glDeleteBuffers(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPULWJGLService;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@@ -35,7 +35,7 @@ class GlsmSdlLWJGLServiceRoutingTest {
|
||||
}
|
||||
|
||||
private static int glsmUnpackAlignment() {
|
||||
final PixelUnpackState state = Reflect.get(glsmContext(), "pixelUnpackState");
|
||||
final PixelStoreState state = Reflect.get(glsmContext(), "pixelUnpackState");
|
||||
return state.alignment();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@@ -45,6 +47,30 @@ class GlsmSdlReadbackTest {
|
||||
GLStateManager.glDeleteTextures(texture);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBufferSubDataSeesSameFrameStorageBufferWrite() {
|
||||
final int size = 64;
|
||||
final int ssbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, ssbo);
|
||||
|
||||
final ByteBuffer initial = BufferUtils.createByteBuffer(size);
|
||||
final ByteBuffer update = BufferUtils.createByteBuffer(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
initial.put(i, (byte) 0x11);
|
||||
update.put(i, (byte) (i + 1));
|
||||
}
|
||||
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, initial, GL15.GL_DYNAMIC_DRAW);
|
||||
GLStateManager.glBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, 0, update);
|
||||
|
||||
final ByteBuffer dst = BufferUtils.createByteBuffer(size);
|
||||
GLStateManager.glGetBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, 0, dst);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertEquals((byte) (i + 1), dst.get(i), "byte " + i + " should hold the same-frame sub-data write");
|
||||
}
|
||||
GLStateManager.glDeleteBuffers(ssbo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readPixelsSeesPriorDraw() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ class AbsoluteCopyRegionTest {
|
||||
for (int i = 0; i < 32; i++) src.put(i, (byte) i);
|
||||
src.position(7).limit(24);
|
||||
dst.position(9).limit(28);
|
||||
PersistentBufferSync.mirrorPersistentCopy(src, 4, dst, 16, 8);
|
||||
PersistentBufferSync.mirrorPersistentCopy(new PersistentMapping(src, 0L, 32L, 0), 4, new PersistentMapping(dst, 0L, 32L, 0), 16, 8);
|
||||
for (int i = 0; i < 8; i++) assertEquals((byte) (4 + i), dst.get(16 + i));
|
||||
assertEquals(7, src.position());
|
||||
assertEquals(24, src.limit());
|
||||
|
||||
+13
-11
@@ -40,7 +40,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(64);
|
||||
try {
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
|
||||
|
||||
assertFalse(rm.hasDirtyPersistentRegions(), "a mapping with no writes is not dirty");
|
||||
@@ -70,7 +70,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(64);
|
||||
try {
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
|
||||
|
||||
sync.onPersistentBufferWrite(1, 0, 16);
|
||||
@@ -95,7 +95,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
try {
|
||||
final int atStart = rm.getMappingsVersion();
|
||||
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
final int afterPut = rm.getMappingsVersion();
|
||||
assertNotEquals(atStart, afterPut, "adding a mapping must move the version");
|
||||
|
||||
@@ -107,7 +107,8 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
sync.enqueueDirtyPersistentRegions();
|
||||
assertEquals(afterPut, rm.getMappingsVersion(), "dirtying and draining is not a mapping change");
|
||||
|
||||
rm.swapPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.removePersistentMapping(1);
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
final int afterSwap = rm.getMappingsVersion();
|
||||
assertNotEquals(afterPut, afterSwap, "swapping a mapping must move the version");
|
||||
|
||||
@@ -129,7 +130,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(256);
|
||||
try {
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 256, 0);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
@@ -156,13 +157,14 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
final ByteBuffer first = MemoryUtil.memCalloc(64);
|
||||
final ByteBuffer second = MemoryUtil.memCalloc(64);
|
||||
try {
|
||||
rm.putPersistentMapping(1, new PersistentMapping(first, 0L, first.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(first, 0L, first.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
|
||||
sync.onPersistentBufferWrite(1, 0, 16);
|
||||
sync.enqueueDirtyPersistentRegions();
|
||||
assertEquals(1, sink.enqueued);
|
||||
|
||||
rm.swapPersistentMapping(1, new PersistentMapping(second, 0L, second.capacity(), 0));
|
||||
rm.removePersistentMapping(1);
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(second, 0L, second.capacity(), 0));
|
||||
sync.onPersistentBufferWrite(1, 0, 16);
|
||||
sync.enqueueDirtyPersistentRegions();
|
||||
assertEquals(2, sink.enqueued, "the drain must see the replacement, not a stale snapshot");
|
||||
@@ -184,7 +186,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(4096);
|
||||
try {
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 4096, 0);
|
||||
|
||||
final int writers = 6;
|
||||
@@ -235,7 +237,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(64);
|
||||
try {
|
||||
final PersistentMapping pm = new PersistentMapping(staging, 0L, staging.capacity(), 0);
|
||||
rm.putPersistentMapping(1, pm);
|
||||
rm.putPersistentMappingIfAbsent(1, pm);
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
|
||||
|
||||
final long[] stampAtEnqueue = {-1L};
|
||||
@@ -267,7 +269,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
final int seedCount = 100;
|
||||
for (int i = 0; i < seedCount; i++) {
|
||||
final ByteBuffer b = MemoryUtil.memAlloc(64);
|
||||
rm.putPersistentMapping(i, new PersistentMapping(b, 0L, b.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(i, new PersistentMapping(b, 0L, b.capacity(), 0));
|
||||
}
|
||||
|
||||
final AtomicBoolean stop = new AtomicBoolean(false);
|
||||
@@ -279,7 +281,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
int next = seedCount;
|
||||
while (!stop.get()) {
|
||||
final ByteBuffer b = MemoryUtil.memAlloc(64);
|
||||
rm.putPersistentMapping(next, new PersistentMapping(b, 0L, b.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(next, new PersistentMapping(b, 0L, b.capacity(), 0));
|
||||
rm.removePersistentMapping(next - 50);
|
||||
next++;
|
||||
if (next > seedCount + 10000) next = seedCount;
|
||||
|
||||
+2
-2
@@ -37,13 +37,13 @@ class PersistentDrainBoundaryTest {
|
||||
final ResourceManager rm;
|
||||
final PersistentBufferSync sync;
|
||||
final RecordingSink sink = new RecordingSink();
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(4096);
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(16384);
|
||||
|
||||
Fixture() {
|
||||
final SdlTestRig rig = SdlTestRig.create();
|
||||
rm = rig.resourceManager;
|
||||
sync = new PersistentBufferSync(rig.frameManager, rm, sink);
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, staging.capacity(), 0);
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -18,14 +18,14 @@ class ReleasePersistentStagingTest {
|
||||
@Test
|
||||
void nullPmAndNullStagingNoOp() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
rm.releasePersistentStaging(null);
|
||||
rm.releasePersistentStaging(new PersistentMapping(null, 0L, 0L, 0));
|
||||
rm.releasePersistentStaging(null, 7, "unmap");
|
||||
rm.releasePersistentStaging(new PersistentMapping(null, 0L, 0L, 0), 7, "unmap");
|
||||
}
|
||||
|
||||
@Test
|
||||
void noTransferThreadFreesInline() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
rm.releasePersistentStaging(new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0));
|
||||
rm.releasePersistentStaging(new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0), 7, "unmap");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -39,7 +39,7 @@ class ReleasePersistentStagingTest {
|
||||
try {
|
||||
final PersistentMapping pm = new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0);
|
||||
pm.lastEnqueuedSeq = 0L;
|
||||
rm.releasePersistentStaging(pm);
|
||||
rm.releasePersistentStaging(pm, 7, "unmap");
|
||||
assertEquals(0, SdlReflect.pendingFreeCount(tt));
|
||||
} finally { shutdown(tt); }
|
||||
}
|
||||
@@ -55,7 +55,7 @@ class ReleasePersistentStagingTest {
|
||||
try {
|
||||
final PersistentMapping pm = new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0);
|
||||
pm.lastEnqueuedSeq = 50L;
|
||||
rm.releasePersistentStaging(pm);
|
||||
rm.releasePersistentStaging(pm, 7, "unmap");
|
||||
assertEquals(1, SdlReflect.pendingFreeCount(tt));
|
||||
} finally { shutdown(tt); }
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class ReleasePersistentStagingTest {
|
||||
void deleteBufferReleasesPersistentStaging() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
SdlReflect.recordBuffer(rm, 7, 0xDEADBEEFL, 64, 0);
|
||||
rm.putPersistentMapping(7, new PersistentMapping(MemoryUtil.memCalloc(64), 0L, 64L, 0));
|
||||
rm.putPersistentMappingIfAbsent(7, new PersistentMapping(MemoryUtil.memCalloc(64), 0L, 64L, 0));
|
||||
|
||||
rm.deleteBuffer(7);
|
||||
|
||||
@@ -84,7 +84,7 @@ class ReleasePersistentStagingTest {
|
||||
final PersistentMapping pm = new PersistentMapping(staging, 0L, staging.capacity(), 0);
|
||||
pm.lastEnqueuedSeq = 99L;
|
||||
SdlReflect.recordBuffer(rm, 7, 0xDEADBEEFL, 64, 0);
|
||||
rm.putPersistentMapping(7, pm);
|
||||
rm.putPersistentMappingIfAbsent(7, pm);
|
||||
|
||||
rm.deleteBuffer(7);
|
||||
|
||||
|
||||
+97
@@ -3,7 +3,9 @@ package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -159,4 +161,99 @@ class ResourceManagerConcurrencyTest {
|
||||
|
||||
assertTrue(created.get() >= threads, "each thread should have created at least one FBO");
|
||||
}
|
||||
|
||||
@Test
|
||||
void putPlainMappingIfAbsent_oneWinnerAcrossThreads() throws Exception {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
final int glId = 9001;
|
||||
final int n = 8;
|
||||
final AtomicInteger winners = new AtomicInteger();
|
||||
|
||||
race(n, slot -> {
|
||||
final ByteBuffer staging = MemoryUtil.memAlloc(16);
|
||||
if (rm.putPlainMappingIfAbsent(glId, staging, 0L, 16L, false, slot)) winners.incrementAndGet();
|
||||
else MemoryUtil.memFree(staging);
|
||||
});
|
||||
|
||||
assertEquals(1, winners.get(), "exactly one thread may map the buffer");
|
||||
final ByteBuffer staging = rm.takePlainStaging(glId);
|
||||
assertTrue(staging != null, "the winning mapping must still be registered");
|
||||
MemoryUtil.memFree(staging);
|
||||
assertNull(rm.takePlainStaging(glId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void plainMappingTakenFromAnotherThread() throws Exception {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
final int glId = 9002;
|
||||
final ByteBuffer staging = MemoryUtil.memAlloc(32);
|
||||
try {
|
||||
assertTrue(rm.putPlainMappingIfAbsent(glId, staging, 8L, 32L, true, 0x2A));
|
||||
final MappedRange peeked = new MappedRange();
|
||||
final MappedRange taken = new MappedRange();
|
||||
final AtomicBoolean peekedOk = new AtomicBoolean();
|
||||
final AtomicBoolean takenOk = new AtomicBoolean();
|
||||
final AtomicBoolean secondTake = new AtomicBoolean(true);
|
||||
|
||||
race(1, slot -> {
|
||||
peekedOk.set(rm.peekPlainMapping(glId, peeked));
|
||||
takenOk.set(rm.takePlainMapping(glId, taken));
|
||||
secondTake.set(rm.takePlainMapping(glId, new MappedRange()));
|
||||
});
|
||||
|
||||
assertTrue(peekedOk.get(), "peek from another thread must see the mapping");
|
||||
assertSame(staging, peeked.staging);
|
||||
assertTrue(takenOk.get(), "take from another thread must succeed");
|
||||
assertSame(staging, taken.staging);
|
||||
assertEquals(glId, taken.glId);
|
||||
assertEquals(8L, taken.offset);
|
||||
assertEquals(32L, taken.length);
|
||||
assertTrue(taken.invalidate);
|
||||
assertEquals(0x2A, taken.accessFlags);
|
||||
assertFalse(secondTake.get(), "a taken mapping must be gone");
|
||||
assertFalse(rm.peekPlainMapping(glId, new MappedRange()));
|
||||
} finally {
|
||||
MemoryUtil.memFree(staging);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteBufferFromAnotherThreadDropsPlainMapping() throws Exception {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
final int glId = 9003;
|
||||
assertTrue(rm.putPlainMappingIfAbsent(glId, MemoryUtil.memAlloc(16), 0L, 16L, false, 0));
|
||||
|
||||
race(1, slot -> rm.deleteBuffer(glId));
|
||||
|
||||
assertFalse(rm.peekPlainMapping(glId, new MappedRange()));
|
||||
assertFalse(rm.takePlainMapping(glId, new MappedRange()));
|
||||
final ByteBuffer again = MemoryUtil.memAlloc(16);
|
||||
assertTrue(rm.putPlainMappingIfAbsent(glId, again, 0L, 16L, false, 0), "a deleted buffer's id must be mappable again");
|
||||
MemoryUtil.memFree(rm.takePlainStaging(glId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistentAndPlainMappingsExcludeEachOther() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
final int plainFirst = 9004;
|
||||
final int persistentFirst = 9005;
|
||||
final ByteBuffer plainStaging = MemoryUtil.memAlloc(16);
|
||||
final ByteBuffer persistentStaging = MemoryUtil.memAlloc(16);
|
||||
final ByteBuffer refused = MemoryUtil.memAlloc(16);
|
||||
try {
|
||||
assertTrue(rm.putPlainMappingIfAbsent(plainFirst, plainStaging, 0L, 16L, false, 0));
|
||||
assertFalse(rm.putPersistentMappingIfAbsent(plainFirst, new PersistentMapping(refused, 0L, 16L, 0)), "persistent map of a plain-mapped buffer must be refused");
|
||||
assertNull(rm.getPersistentMapping(plainFirst));
|
||||
|
||||
assertTrue(rm.putPersistentMappingIfAbsent(persistentFirst, new PersistentMapping(persistentStaging, 0L, 16L, 0)));
|
||||
assertFalse(rm.putPlainMappingIfAbsent(persistentFirst, refused, 0L, 16L, false, 0), "plain map of a persistently mapped buffer must be refused");
|
||||
assertFalse(rm.peekPlainMapping(persistentFirst, new MappedRange()));
|
||||
} finally {
|
||||
rm.takePlainStaging(plainFirst);
|
||||
rm.removePersistentMapping(persistentFirst);
|
||||
MemoryUtil.memFree(plainStaging);
|
||||
MemoryUtil.memFree(persistentStaging);
|
||||
MemoryUtil.memFree(refused);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ class AttribLocationPatchTest {
|
||||
inputs[i] = new ShaderManager.VsInput(names[i], i, i, i < VEC_SIZES.length ? VEC_SIZES[i] : 4, i < BASE_TYPES.length ? BASE_TYPES[i] : Spvc.SPVC_BASETYPE_FP32);
|
||||
}
|
||||
final ShaderManager.StageReflection e = ShaderManager.StageReflection.EMPTY;
|
||||
return new ShaderManager.StageReflection(e.counts(), e.samplerNames(), e.extraUniformNames(),
|
||||
return new ShaderManager.StageReflection(e.counts(), e.samplerNames(), e.unusedSamplerNames(), e.extraUniformNames(),
|
||||
e.storageImageNames(), e.uboSize(), e.uboMembers(), List.of(inputs), e.vsOutputs(), e.fsInputs(),
|
||||
e.maxOutputLocation(), e.numReadonlyStorageBuffers(), e.numReadwriteStorageBuffers(),
|
||||
e.numReadonlyStorageTextures(), e.numReadwriteStorageTextures(), e.blocks());
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ class AttribLocationReflectionTest {
|
||||
|
||||
private static StageReflection reflectionWith(VsInput... inputs) {
|
||||
return new StageReflection(
|
||||
ShaderManager.ResourceCounts.EMPTY, List.of(), List.of(), List.of(), 0, List.of(),
|
||||
ShaderManager.ResourceCounts.EMPTY, List.of(), List.of(), List.of(), List.of(), 0, List.of(),
|
||||
List.of(inputs), List.of(), List.of(), -1, 0, 0, 0, 0, ShaderManager.BlockReflection.emptyBlocks());
|
||||
}
|
||||
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.util.shaderc.Shaderc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.system.MemoryUtil.memFree;
|
||||
|
||||
class DerivativeHoistingTest {
|
||||
|
||||
private static String hoist(String source) {
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
DerivativeHoisting.collectEdits(new ShaderIndex(GlslTransformUtils.parseFullQuiet(source), source), edits);
|
||||
return GlslVulkanPreprocess.applyEdits(source, edits);
|
||||
}
|
||||
|
||||
private static void assertCompiles(String source) {
|
||||
final SpirvCompiler.Result r = SpirvCompiler.compile(source, Shaderc.shaderc_fragment_shader, "derivatives.frag", SpirvCompiler.Options.vulkanForced460Core(), false);
|
||||
assertNotNull(r.spirv(), () -> "compile failed: " + r.error() + "\n" + source);
|
||||
memFree(r.spirv());
|
||||
}
|
||||
|
||||
@Test
|
||||
void directionalBlocklightDerivativesLeaveTheLightmapBranch() {
|
||||
final String out = hoist("""
|
||||
#version 420 core
|
||||
in vec2 lmCoord;
|
||||
in vec3 viewIn;
|
||||
out vec4 color;
|
||||
float light(vec3 viewPos) {
|
||||
vec2 oldLightmap = lmCoord;
|
||||
float result = oldLightmap.x;
|
||||
if (oldLightmap.x > 0.035) {
|
||||
vec2 dFdBlock = vec2(dFdx(oldLightmap.x), dFdy(oldLightmap.x));
|
||||
vec3 dir = vec3(0.0);
|
||||
if (length(dFdBlock) < 1e-6) {
|
||||
dir = vec3(1.0);
|
||||
} else {
|
||||
dir = dFdx(viewPos) * dFdBlock.x + dFdy(viewPos) * dFdBlock.y;
|
||||
}
|
||||
result *= dot(normalize(dir), vec3(0.0, 1.0, 0.0));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
void main() {
|
||||
color = vec4(light(viewIn));
|
||||
}
|
||||
""");
|
||||
|
||||
final int branch = out.indexOf("if (oldLightmap.x > 0.035)");
|
||||
assertTrue(branch > 0, out);
|
||||
assertFalse(out.substring(branch).contains("dFdx("), out);
|
||||
assertFalse(out.substring(branch).contains("dFdy("), out);
|
||||
final String hoisted = out.substring(out.indexOf("float result"), branch);
|
||||
assertTrue(hoisted.contains("float angelica_derivative_0 = dFdx(oldLightmap.x);"), out);
|
||||
assertTrue(hoisted.contains("float angelica_derivative_1 = dFdy(oldLightmap.x);"), out);
|
||||
assertTrue(hoisted.contains("vec3 angelica_derivative_2 = dFdx(viewPos);"), out);
|
||||
assertTrue(hoisted.contains("vec3 angelica_derivative_3 = dFdy(viewPos);"), out);
|
||||
assertCompiles(out);
|
||||
}
|
||||
|
||||
@Test
|
||||
void variableWrittenInsideTheBranchStaysPut() {
|
||||
final String source = """
|
||||
#version 420 core
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
void bump(inout vec2 p) { p += 1.0; }
|
||||
void main() {
|
||||
vec2 a = uv;
|
||||
vec2 b = uv;
|
||||
if (uv.x > 0.5) {
|
||||
a *= 2.0;
|
||||
bump(b);
|
||||
color = vec4(dFdx(a), dFdy(b));
|
||||
}
|
||||
}
|
||||
""";
|
||||
assertEquals(source, hoist(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writesHiddenInsideDirectivesBlockTheMove() {
|
||||
final String source = """
|
||||
#version 420 core
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
void main() {
|
||||
vec2 p = uv;
|
||||
if (true) {
|
||||
#if 1
|
||||
p *= 2.0;
|
||||
#endif
|
||||
color = vec4(dFdx(p), 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
""";
|
||||
assertEquals(source, hoist(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parenthesizedBuiltinOutArgumentCountsAsAWrite() {
|
||||
final String source = """
|
||||
#version 420 core
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
void main() {
|
||||
vec2 p = uv;
|
||||
if (true) {
|
||||
modf(uv * 2.0, (p));
|
||||
color = vec4(dFdx(p), 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
""";
|
||||
assertEquals(source, hoist(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
void globalsStayOnlyWhenACalleeWritesThem() {
|
||||
final String out = hoist("""
|
||||
#version 420 core
|
||||
in vec4 colorIn;
|
||||
out vec4 color;
|
||||
vec4 glColor = colorIn;
|
||||
vec2 nudged = vec2(0.0);
|
||||
float square(float x) { return x * x; }
|
||||
void nudge() { nudged += 1.0; }
|
||||
void touch() { nudge(); }
|
||||
void main() {
|
||||
if (glColor.a > 0.5) {
|
||||
touch();
|
||||
color = vec4(dFdx(glColor.a) + square(1.0), dFdy(nudged), 1.0);
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
final int branch = out.indexOf("if (glColor.a > 0.5)");
|
||||
assertTrue(out.substring(0, branch).contains("float angelica_derivative_0 = dFdx(glColor.a);"), out);
|
||||
assertTrue(out.substring(branch).contains("dFdy(nudged)"), out);
|
||||
assertCompiles(out);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loopsAreNotCrossed() {
|
||||
final String out = hoist("""
|
||||
#version 420 core
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
void main() {
|
||||
color = vec4(0.0);
|
||||
if (uv.y > 0.5) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
vec2 p = uv * float(i);
|
||||
if (p.x > 1.0) {
|
||||
color.xy += dFdx(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
final int loop = out.indexOf("for (");
|
||||
final int inner = out.indexOf("if (p.x > 1.0)");
|
||||
final int hoisted = out.indexOf("vec2 angelica_derivative_0 = dFdx(p);");
|
||||
assertTrue(loop < hoisted && hoisted < inner, out);
|
||||
assertCompiles(out);
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
|
||||
@@ -30,19 +31,19 @@ final class ShaderTransformChain {
|
||||
}
|
||||
|
||||
static String clipZ(String source) {
|
||||
return edit(source, ClipZRemap::collectEdits);
|
||||
return edit(source, (index, edits) -> ClipZRemap.collectEdits(index.root(), edits));
|
||||
}
|
||||
|
||||
static String stripUnused(String source) {
|
||||
if (!source.contains("sampler")) return source;
|
||||
return edit(source, (root, edits) -> SamplerStripper.collectEdits(root, source, edits));
|
||||
return edit(source, SamplerStripper::collectEdits);
|
||||
}
|
||||
|
||||
static String inject(String source, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {
|
||||
return edit(source, (root, edits) -> PerFrameBlockInjector.collectEdits(root, perFrame, perPass, edits));
|
||||
return edit(source, (index, edits) -> PerFrameBlockInjector.collectEdits(index, perFrame, perPass, edits));
|
||||
}
|
||||
|
||||
private static String edit(String source, BiConsumer<GLSLParser.Translation_unitContext, List<Edit>> collect) {
|
||||
private static String edit(String source, BiConsumer<ShaderIndex, List<Edit>> collect) {
|
||||
final GLSLParser.Translation_unitContext root;
|
||||
try {
|
||||
root = GlslTransformUtils.parseFullQuiet(source);
|
||||
@@ -50,7 +51,7 @@ final class ShaderTransformChain {
|
||||
return source;
|
||||
}
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
collect.accept(root, edits);
|
||||
collect.accept(new ShaderIndex(root, source), edits);
|
||||
return edits.isEmpty() ? source : GlslVulkanPreprocess.applyEdits(source, edits);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -73,7 +73,7 @@ class StageDiskCacheTest {
|
||||
|
||||
private static byte[] poison(String marker) {
|
||||
final ShaderManager.StageReflection reflection = new ShaderManager.StageReflection(
|
||||
ShaderManager.ResourceCounts.EMPTY, List.of(marker), List.of(), List.of(), 0, List.of(), List.of(), List.of(), List.of(),
|
||||
ShaderManager.ResourceCounts.EMPTY, List.of(marker), List.of(), List.of(), List.of(), 0, List.of(), List.of(), List.of(), List.of(),
|
||||
-1, 0, 0, 0, 0, ShaderManager.BlockReflection.emptyBlocks());
|
||||
return ShaderStageSerializer.encode(POISON_SPIRV, reflection, ShaderManager.GraphicsBindingMap.EMPTY, Set.of("poison_bool"), "poison_source");
|
||||
}
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.util.shaderc.Shaderc;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
class UnusedSamplerReflectionTest {
|
||||
|
||||
private static final String FRAGMENT = """
|
||||
#version 330 core
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D normals;
|
||||
uniform sampler2D specular;
|
||||
in vec2 uv;
|
||||
out vec4 fragColor;
|
||||
float highlight(float d) {
|
||||
float specular = d * d;
|
||||
return specular;
|
||||
}
|
||||
void main() {
|
||||
fragColor = texture(tex, uv) * highlight(uv.x);
|
||||
}
|
||||
""";
|
||||
|
||||
@Test
|
||||
void samplersOnlyShadowedOrDeclaredAreUnused() {
|
||||
final SpirvCompiler.Result r = SpirvCompiler.compile(FRAGMENT, Shaderc.shaderc_fragment_shader, "test", SpirvCompiler.Options.vulkanRelaxed());
|
||||
if (r.spirv() == null) fail("Compilation failed: " + r.error());
|
||||
final ByteBuffer spirv = r.spirv();
|
||||
try {
|
||||
final ShaderManager.StageReflection refl = ShaderManager.reflectStage(spirv, false);
|
||||
assertEquals(List.of("normals", "specular"), refl.unusedSamplerNames().stream().sorted().toList(),
|
||||
"declared samplers: " + refl.samplerNames());
|
||||
} finally {
|
||||
MemoryUtil.memFree(spirv);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -57,7 +57,7 @@ class FragmentStorageTextureSlotTest {
|
||||
final ByteBuffer spirv = r.spirv();
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, false);
|
||||
try {
|
||||
final String msl = StandardCharsets.UTF_8.decode(out.code().duplicate()).toString();
|
||||
final boolean[] seen = new boolean[samplerCount];
|
||||
@@ -136,7 +136,7 @@ class FragmentStorageTextureSlotTest {
|
||||
final ByteBuffer spirv = Reflect.get(obj, "spirv");
|
||||
final ShaderManager.StageReflection refl = Reflect.get(obj, "reflection");
|
||||
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, false);
|
||||
try {
|
||||
final String msl = StandardCharsets.UTF_8.decode(out.code().duplicate()).toString();
|
||||
final int samplerCount = refl.counts().numSamplers();
|
||||
|
||||
+102
-12
@@ -8,6 +8,7 @@ import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.SpirvTestShaders;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.cross.CrossCompileCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.cross.CrossCompileUtil;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -52,7 +53,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(SpirvTestShaders.VERTEX_GLSL, Shaderc.shaderc_vertex_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.length() > 0, "MSL output non-empty");
|
||||
@@ -75,7 +76,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(SpirvTestShaders.FRAGMENT_GLSL, Shaderc.shaderc_fragment_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.contains("fragment "), "MSL should contain fragment stage qualifier:\n" + msl);
|
||||
@@ -114,7 +115,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(SpirvTestShaders.LDS_MULTI_MIP_GLSL, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.contains("kernel "), "Compute MSL should contain kernel stage qualifier:\n" + msl);
|
||||
@@ -146,7 +147,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(glsl, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.contains("kernel "), "compute MSL should contain kernel stage qualifier:\n" + msl);
|
||||
@@ -167,7 +168,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(CHUNK_CULL_GLSL, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.contains("kernel "), "Compute MSL should contain kernel stage qualifier:\n" + msl);
|
||||
@@ -202,7 +203,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(VERTEX_WRITES_IMAGE_GLSL, Shaderc.shaderc_vertex_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertFalse(msl.contains("vertex void main0"),
|
||||
@@ -236,7 +237,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(pre.rewrittenSource(), Shaderc.shaderc_fragment_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.contains("angelica_renamed_try"), "the local must keep a renamed identifier:\n" + msl);
|
||||
@@ -255,7 +256,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(SpirvTestShaders.VERTEX_GLSL, Shaderc.shaderc_vertex_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false);
|
||||
MemoryUtil.memFree(out.code());
|
||||
} finally {
|
||||
MemoryUtil.memFree(spirv);
|
||||
@@ -381,7 +382,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(glsl, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertUniqueBufferSlots(msl);
|
||||
@@ -436,7 +437,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(glsl, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
|
||||
try {
|
||||
assertTrue(decode(out.code()).contains("kernel "), name + " should produce compute MSL");
|
||||
} finally {
|
||||
@@ -456,10 +457,10 @@ class MslCrossCompileTest {
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final byte[] heap = ShaderCacheIO.toHeap(spirv);
|
||||
MemoryUtil.memFree(MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER).code());
|
||||
MemoryUtil.memFree(MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false).code());
|
||||
Reflect.<Map<?, ?>>get(cache(), "cache").clear();
|
||||
ShaderDiskCache.putBlob(cache().diskKey(heap, GL20.GL_VERTEX_SHADER), "poison_entry", "poison".getBytes(StandardCharsets.UTF_8));
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false);
|
||||
try {
|
||||
assertEquals("poison_entry", out.entrypoint());
|
||||
assertEquals("poison", new String(ShaderCacheIO.toHeap(out.code()), StandardCharsets.UTF_8));
|
||||
@@ -471,6 +472,95 @@ class MslCrossCompileTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String IMAGE_ATOMIC_GLSL = """
|
||||
#version 460 core
|
||||
layout(local_size_x = 64) in;
|
||||
layout(binding = 0, r32i) uniform iimage2D counter;
|
||||
void main() {
|
||||
imageAtomicAdd(counter, ivec2(0, 0), 1);
|
||||
}
|
||||
""";
|
||||
|
||||
@Test
|
||||
void imageAtomicsUseTheAuxBufferWithoutTextureAtomics() {
|
||||
assertTrue(computeMsl(IMAGE_ATOMIC_GLSL, false).contains("counter_atomic"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void imageAtomicsCompileToTextureAtomicsWhenAvailable() {
|
||||
final String msl = computeMsl(IMAGE_ATOMIC_GLSL, true);
|
||||
assertFalse(msl.contains("counter_atomic"), msl);
|
||||
assertTrue(msl.contains("counter.atomic_fetch_add("), msl);
|
||||
}
|
||||
|
||||
@Test
|
||||
void textureAtomicsDoNotChangeShadersWithoutImageAtomics() {
|
||||
final String base = computeMsl(CHUNK_CULL_GLSL, false);
|
||||
Reflect.<Map<?, ?>>get(cache(), "cache").clear();
|
||||
assertEquals(base, computeMsl(CHUNK_CULL_GLSL, true));
|
||||
}
|
||||
|
||||
private static final String FRAGMENT_IMAGE_ATOMIC_GLSL = """
|
||||
#version 460 core
|
||||
layout(binding = 0, r32i) uniform iimage2D counter;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = vec4(imageAtomicAdd(counter, ivec2(0, 0), 1));
|
||||
}
|
||||
""";
|
||||
|
||||
@Test
|
||||
void textureAtomicsLeaveRasterStagesAtMsl22() {
|
||||
final ByteBuffer spirv = compile(FRAGMENT_IMAGE_ATOMIC_GLSL, Shaderc.shaderc_fragment_shader);
|
||||
try {
|
||||
assertTrue(CrossCompileUtil.usesImageAtomics(spirv));
|
||||
final String base = fragmentMsl(spirv, false);
|
||||
Reflect.<Map<?, ?>>get(cache(), "cache").clear();
|
||||
final String atomics = fragmentMsl(spirv, true);
|
||||
assertEquals(base, atomics);
|
||||
assertFalse(atomics.contains("counter.atomic_fetch_add("), atomics);
|
||||
} finally {
|
||||
MemoryUtil.memFree(spirv);
|
||||
}
|
||||
}
|
||||
|
||||
private static String fragmentMsl(ByteBuffer spirv, boolean textureAtomics) {
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, textureAtomics);
|
||||
try {
|
||||
return decode(out.code());
|
||||
} finally {
|
||||
MemoryUtil.memFree(out.code());
|
||||
}
|
||||
}
|
||||
|
||||
private static String computeMsl(String glsl, boolean textureAtomics) {
|
||||
final ByteBuffer spirv = compile(glsl, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, textureAtomics);
|
||||
try {
|
||||
return decode(out.code());
|
||||
} finally {
|
||||
MemoryUtil.memFree(out.code());
|
||||
}
|
||||
} finally {
|
||||
MemoryUtil.memFree(spirv);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyShadersWithImageAtomicsAreDetected() {
|
||||
final ByteBuffer atomic = compile(IMAGE_ATOMIC_GLSL, Shaderc.shaderc_compute_shader);
|
||||
final ByteBuffer plain = compile(CHUNK_CULL_GLSL, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
assertTrue(CrossCompileUtil.usesImageAtomics(atomic));
|
||||
assertFalse(CrossCompileUtil.usesImageAtomics(plain));
|
||||
} finally {
|
||||
MemoryUtil.memFree(atomic);
|
||||
MemoryUtil.memFree(plain);
|
||||
}
|
||||
}
|
||||
|
||||
private static String decode(ByteBuffer buf) {
|
||||
final ByteBuffer slice = buf.duplicate();
|
||||
int len = slice.remaining();
|
||||
|
||||
@@ -25,4 +25,5 @@ apply(from = file("experimental-surround/surround.settings.gradle.kts"))
|
||||
include("glsm")
|
||||
include("lwjgl3-backend")
|
||||
include("tracy-client")
|
||||
include("moltenvk")
|
||||
include("sdl-gpu")
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
|
||||
public final class EnderDragonBeams {
|
||||
|
||||
private static final NamespacedId CRYSTAL_BEAM = new NamespacedId("minecraft", "end_crystal_beam");
|
||||
private static final NamespacedId DEATH_RAYS = new NamespacedId("minecraft", "dragon_death_rays");
|
||||
|
||||
private EnderDragonBeams() {}
|
||||
|
||||
public static void beginCrystalBeam() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
|
||||
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(CRYSTAL_BEAM);
|
||||
}
|
||||
|
||||
public static void endCrystalBeam() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
|
||||
}
|
||||
|
||||
public static void beginDeathRays() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.LIGHTNING);
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
|
||||
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(DEATH_RAYS);
|
||||
}
|
||||
|
||||
public static void endDeathRays() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
|
||||
GbufferPrograms.teardownSpecialRenderCondition();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
public interface EyePassRenderer {
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package com.gtnewhorizons.angelica.client.font;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
|
||||
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
@@ -393,14 +392,33 @@ public class BatchingFontRenderer {
|
||||
pushQuadIdx();
|
||||
}
|
||||
|
||||
private static final float[] MAX_AA_REACH = {0f, 6f, 8f};
|
||||
|
||||
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz, boolean flipV) {
|
||||
ensureCapacity();
|
||||
|
||||
final float tBu0 = uStart;
|
||||
final float tBu1 = uStart + uSz;
|
||||
final float tBv0 = vStart;
|
||||
final float tBv1 = vStart + vSz;
|
||||
|
||||
if (FontConfig.fontAAMode > 0 && w > 0 && h > 0) {
|
||||
final float margin = MAX_AA_REACH[FontConfig.fontAAMode] * preprocessAAStrength() + 0.5f;
|
||||
final float marginU = margin * (uSz / w);
|
||||
final float marginV = margin * (vSz / h);
|
||||
itOff *= (h + 2 * margin) / h;
|
||||
x -= margin; w += 2 * margin;
|
||||
y -= margin; h += 2 * margin;
|
||||
uStart -= marginU; uSz += 2 * marginU;
|
||||
vStart -= marginV; vSz += 2 * marginV;
|
||||
}
|
||||
|
||||
float vTop = flipV ? vStart + vSz : vStart;
|
||||
float vBot = flipV ? vStart : vStart + vSz;
|
||||
pushVtx(x + itOff, y, rgba, uStart, vTop, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x - itOff, y + h, rgba, uStart, vBot, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vTop, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vBot, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x + itOff, y, rgba, uStart, vTop, tBu0, tBu1, tBv0, tBv1);
|
||||
pushVtx(x - itOff, y + h, rgba, uStart, vBot, tBu0, tBu1, tBv0, tBv1);
|
||||
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vTop, tBu0, tBu1, tBv0, tBv1);
|
||||
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vBot, tBu0, tBu1, tBv0, tBv1);
|
||||
pushQuadIdx();
|
||||
}
|
||||
|
||||
@@ -859,6 +877,10 @@ public class BatchingFontRenderer {
|
||||
truncateBatchToWatermark();
|
||||
}
|
||||
|
||||
private static float preprocessAAStrength() {
|
||||
return FontConfig.fontAAStrength / 120.f;
|
||||
}
|
||||
|
||||
private void setupFontDrawState() {
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.enableAlphaTest();
|
||||
@@ -873,7 +895,7 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
|
||||
fontAAStrengthLast = FontConfig.fontAAStrength;
|
||||
GLStateManager.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
|
||||
GLStateManager.glUniform1f(AAStrength, preprocessAAStrength());
|
||||
}
|
||||
if (FontConfig.fontBrightness != fontBrightnessLast) {
|
||||
fontBrightnessLast = FontConfig.fontBrightness;
|
||||
@@ -1429,16 +1451,7 @@ public class BatchingFontRenderer {
|
||||
|
||||
if (chr == ColorCodeUtils.ESCAPED_AMPERSAND) { chr = '&'; }
|
||||
|
||||
if (FontConfig.enableCustomFont && FontConfig.enableGlyphReplacements) {
|
||||
final char replacement = GlyphReplacements.getReplacementGlyph(chr);
|
||||
if (replacement != 0) {
|
||||
if (FontProviderCustom.getPrimary().isGlyphAvailable(replacement)
|
||||
|| FontProviderCustom.getFallback().isGlyphAvailable(replacement)
|
||||
) {
|
||||
chr = replacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
chr = FontStrategist.replaceCustomGlyph(this, chr, unicodeFlag);
|
||||
|
||||
// ASCII space, NBSP, NNBSP, decided before obfuscation as vanilla does.
|
||||
final boolean whitespace = chr == ' ' || chr == '\u00A0' || chr == '\u202F';
|
||||
@@ -1451,7 +1464,8 @@ public class BatchingFontRenderer {
|
||||
chr = fontProvider.getRandomReplacement(chr);
|
||||
}
|
||||
|
||||
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2);
|
||||
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2)
|
||||
+ fontProvider.getBaselineShift() * glyphScaleY;
|
||||
float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY * fontProvider.getYScaleMultiplier();
|
||||
|
||||
visibleCharIndex++;
|
||||
@@ -1623,7 +1637,9 @@ public class BatchingFontRenderer {
|
||||
return 4 * this.getWhitespaceScale();
|
||||
}
|
||||
|
||||
FontProvider fp = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, underlying.getUnicodeFlag());
|
||||
final boolean unicodeFlag = underlying.getUnicodeFlag();
|
||||
chr = FontStrategist.replaceCustomGlyph(this, chr, unicodeFlag);
|
||||
FontProvider fp = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, unicodeFlag);
|
||||
|
||||
return fp.getXAdvance(chr) * this.getGlyphScaleX();
|
||||
}
|
||||
|
||||
@@ -20,4 +20,7 @@ public interface FontProvider {
|
||||
float getShadowOffset();
|
||||
int getTexture(char chr);
|
||||
float getYScaleMultiplier();
|
||||
|
||||
/** Vertical offset in font pixels, applied after the glyph is centered on the line. */
|
||||
default float getBaselineShift() { return 0.0f; }
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import it.unimi.dsi.fastutil.chars.CharArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.IReloadableResourceManager;
|
||||
import net.minecraft.client.resources.IResource;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* For modern Minecraft's hand-drawn font sheets.
|
||||
*/
|
||||
public final class FontProviderBitmap implements FontProvider {
|
||||
|
||||
private static final ResourceLocation DEFINITION = new ResourceLocation("angelica", "font/modern.json");
|
||||
private static final int DEFAULT_HEIGHT = 8;
|
||||
private static final FontProviderBitmap[] NONE = new FontProviderBitmap[0];
|
||||
|
||||
private static volatile FontProviderBitmap[] loaded;
|
||||
|
||||
public static void registerReloadListener() {
|
||||
((IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager()).registerReloadListener(manager -> loaded = null);
|
||||
}
|
||||
|
||||
private final ResourceLocation sheet;
|
||||
private final int columns;
|
||||
private final float uSize;
|
||||
private final float vSize;
|
||||
private final float glyphW;
|
||||
private final float yScale;
|
||||
private final float baselineShift;
|
||||
private final char[][] slotPages = new char[256][];
|
||||
private final int[] advances;
|
||||
private final Int2ObjectOpenHashMap<char[]> charsByAdvance = new Int2ObjectOpenHashMap<>();
|
||||
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
|
||||
|
||||
public static FontProviderBitmap find(char chr) {
|
||||
for (FontProviderBitmap provider : providers()) {
|
||||
if (provider.slot(chr) >= 0) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static FontProviderBitmap[] providers() {
|
||||
FontProviderBitmap[] local = loaded;
|
||||
if (local == null) {
|
||||
final IResourceManager resources = Minecraft.getMinecraft().getResourceManager();
|
||||
if (!resources.getResourceDomains().contains(DEFINITION.getResourceDomain())) {
|
||||
return NONE;
|
||||
}
|
||||
synchronized (FontProviderBitmap.class) {
|
||||
local = loaded;
|
||||
if (local == null) {
|
||||
local = load(resources);
|
||||
loaded = local;
|
||||
}
|
||||
}
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
private static FontProviderBitmap[] load(IResourceManager resources) {
|
||||
final List<FontProviderBitmap> providers = new ArrayList<>();
|
||||
try (Reader reader = new InputStreamReader(resources.getResource(DEFINITION).getInputStream(), StandardCharsets.UTF_8)) {
|
||||
final JsonArray definitions = new JsonParser().parse(reader).getAsJsonObject().getAsJsonArray("providers");
|
||||
for (JsonElement element : definitions) {
|
||||
final JsonObject definition = element.getAsJsonObject();
|
||||
if (!"bitmap".equals(definition.get("type").getAsString())) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
providers.add(new FontProviderBitmap(definition, resources));
|
||||
} catch (IOException | RuntimeException e) {
|
||||
FontStrategist.LOGGER.warn("Skipping modern font sheet {}", definition.get("file"), e);
|
||||
}
|
||||
}
|
||||
} catch (IOException | RuntimeException e) {
|
||||
FontStrategist.LOGGER.error("Could not read modern font definition {}", DEFINITION, e);
|
||||
}
|
||||
return providers.toArray(NONE);
|
||||
}
|
||||
|
||||
private static ResourceLocation resolveSheet(IResourceManager resources, ResourceLocation sheet) {
|
||||
final ResourceLocation bundled = new ResourceLocation("angelica", sheet.getResourcePath());
|
||||
try {
|
||||
final int vanillaCopies = Minecraft.getMinecraft().mcDefaultResourcePack.resourceExists(sheet) ? 1 : 0;
|
||||
final List<IResource> copies = resources.getAllResources(sheet);
|
||||
for (IResource copy : copies) {
|
||||
copy.getInputStream().close();
|
||||
}
|
||||
return copies.size() > vanillaCopies ? sheet : bundled;
|
||||
} catch (IOException e) {
|
||||
return bundled;
|
||||
}
|
||||
}
|
||||
|
||||
private FontProviderBitmap(JsonObject definition, IResourceManager resources) throws IOException {
|
||||
final ResourceLocation file = new ResourceLocation(definition.get("file").getAsString());
|
||||
this.sheet = resolveSheet(resources, new ResourceLocation(file.getResourceDomain(), "textures/" + file.getResourcePath()));
|
||||
final int height = definition.has("height") ? definition.get("height").getAsInt() : DEFAULT_HEIGHT;
|
||||
final int ascent = definition.get("ascent").getAsInt();
|
||||
final JsonArray rows = definition.getAsJsonArray("chars");
|
||||
|
||||
final BufferedImage image;
|
||||
try (InputStream in = resources.getResource(this.sheet).getInputStream()) {
|
||||
image = ImageIO.read(in);
|
||||
}
|
||||
final String firstRow = rows.get(0).getAsString();
|
||||
this.columns = firstRow.codePointCount(0, firstRow.length());
|
||||
final int cellWidth = image.getWidth() / this.columns;
|
||||
final int cellHeight = image.getHeight() / rows.size();
|
||||
final float scale = (float) height / cellHeight;
|
||||
|
||||
this.uSize = (float) cellWidth / image.getWidth();
|
||||
this.vSize = (float) cellHeight / image.getHeight();
|
||||
this.glyphW = cellWidth * scale + 1.0f;
|
||||
this.yScale = (float) height / DEFAULT_HEIGHT;
|
||||
this.baselineShift = 3.0f + height / 2.0f - ascent;
|
||||
this.advances = new int[this.columns * rows.size()];
|
||||
|
||||
final Int2ObjectOpenHashMap<CharArrayList> byAdvance = new Int2ObjectOpenHashMap<>();
|
||||
for (int row = 0; row < rows.size(); row++) {
|
||||
final int[] codepoints = rows.get(row).getAsString().codePoints().toArray();
|
||||
for (int column = 0; column < codepoints.length; column++) {
|
||||
final int codepoint = codepoints[column];
|
||||
if (codepoint == 0 || codepoint > Character.MAX_VALUE) {
|
||||
continue;
|
||||
}
|
||||
final char chr = (char) codepoint;
|
||||
final int slot = row * this.columns + column;
|
||||
final int ink = inkWidth(image, column * cellWidth, row * cellHeight, cellWidth, cellHeight);
|
||||
this.advances[slot] = (int) (0.5f + ink * scale) + 1;
|
||||
|
||||
char[] page = this.slotPages[chr >>> 8];
|
||||
if (page == null) {
|
||||
page = this.slotPages[chr >>> 8] = new char[256];
|
||||
}
|
||||
page[chr & 0xFF] = (char) (slot + 1);
|
||||
byAdvance.computeIfAbsent(this.advances[slot], k -> new CharArrayList()).add(chr);
|
||||
}
|
||||
}
|
||||
byAdvance.int2ObjectEntrySet().forEach(e -> this.charsByAdvance.put(e.getIntKey(), e.getValue().toCharArray()));
|
||||
}
|
||||
|
||||
private static int inkWidth(BufferedImage image, int cellX, int cellY, int cellWidth, int cellHeight) {
|
||||
for (int x = cellWidth - 1; x >= 0; x--) {
|
||||
for (int y = 0; y < cellHeight; y++) {
|
||||
if ((image.getRGB(cellX + x, cellY + y) >>> 24) != 0) {
|
||||
return x + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int slot(char chr) {
|
||||
final char[] page = this.slotPages[chr >>> 8];
|
||||
return page == null ? -1 : page[chr & 0xFF] - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
return slot(chr) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getRandomReplacement(char chr) {
|
||||
final int slot = slot(chr);
|
||||
if (slot < 0) {
|
||||
return chr;
|
||||
}
|
||||
final char[] sameWidth = this.charsByAdvance.get(this.advances[slot]);
|
||||
return sameWidth[this.fontRandom.nextInt(sameWidth.length)];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
return (slot(chr) % this.columns) * this.uSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
return (float) (slot(chr) / this.columns) * this.vSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
return this.advances[slot(chr)];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
return this.glyphW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
return this.uSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return this.vSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTexture(char chr) {
|
||||
return FontStrategist.getIntFromResourceLocation(this.sheet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return this.yScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBaselineShift() {
|
||||
return this.baselineShift;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 199 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user