Compare commits

..
9 Commits
144 changed files with 4985 additions and 2139 deletions

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# usage: companion-version.sh <name> <catalog-key>; TAG is <name>-v<version>[-N] or unset
set -euo pipefail
name="${1:?name required}"
key="${2:?catalog key required}"
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
base="$(sed -nE "s/^$key = \"(.*)\"\$/\\1/p" gradle/libs.versions.toml)"
[[ -n "$base" ]] || { echo "$key not found" >&2; exit 1; }
tag="${TAG:-}"
if [[ -z "$tag" ]]; then
echo "$base"
exit 0
fi
version="${tag#"$name"-v}"
if [[ "$version" == "$tag" || ( "$version" != "$base" && ! "$version" =~ ^"$base"-[1-9][0-9]*$ ) ]]; then
echo "bad tag '$tag': want $name-v$base[-N]" >&2
exit 1
fi
echo "$version"
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# usage: release-companion-jar.sh <jar> <notes>; TAG is <name>-v<version>
set -euo pipefail
: "${TAG:?TAG required}"
jar="${1:?jar required}"
notes="${2:?notes required}"
gh release create "$TAG" "$jar" --latest=false --title "angelica-${TAG%%-v*} ${TAG#*-v}" --notes "$notes"
+53
View File
@@ -0,0 +1,53 @@
name: Build angelica-moltenvk
on:
push:
tags:
- "moltenvk-v*"
workflow_dispatch:
inputs:
publish:
description: Publish angelica-moltenvk.jar to the GTNH maven
type: boolean
required: false
default: false
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- name: Compute version
shell: bash
env:
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
run: |
version="$(bash .github/scripts/companion-version.sh moltenvk lwjgl3Release)"
echo "MOLTENVK_VERSION=$version" >> "$GITHUB_ENV"
- name: Build angelica-moltenvk.jar
run: ./gradlew -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:distJar
- uses: actions/upload-artifact@v4
with:
name: angelica-moltenvk
path: moltenvk/build/libs/angelica-moltenvk-*.jar
if-no-files-found: error
- name: Publish to GTNH maven
if: github.ref_type == 'tag' || inputs.publish
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
run: ./gradlew -PpublishMoltenVK=true -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:publish
- name: Create GitHub release
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: bash .github/scripts/release-companion-jar.sh moltenvk/build/libs/angelica-moltenvk-*.jar "MoltenVK for the SDL GPU Vulkan driver on macOS."
+18 -2
View File
@@ -12,6 +12,9 @@ on:
required: false
default: false
permissions:
contents: write
jobs:
build:
strategy:
@@ -66,8 +69,15 @@ jobs:
cp "$d"/* "$dest/"
done
find tracy-client/src/main/resources/natives -type f
- name: Compute version
shell: bash
env:
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
run: |
version="$(bash .github/scripts/companion-version.sh tracy tracy)"
echo "TRACY_JAR_VERSION=$version" >> "$GITHUB_ENV"
- name: Build angelica-tracy.jar
run: ./gradlew :tracy-client:distJar
run: ./gradlew -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:distJar
- uses: actions/upload-artifact@v4
with:
name: angelica-tracy
@@ -78,4 +88,10 @@ jobs:
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
run: ./gradlew -PpublishTracy=true :tracy-client:publish
run: ./gradlew -PpublishTracy=true -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:publish
- name: Create GitHub release
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: bash .github/scripts/release-companion-jar.sh tracy-client/build/libs/angelica-tracy-*.jar "Tracy profiler backend for Angelica."
+1 -1
View File
@@ -3,7 +3,7 @@ name: Release tagged build
on:
push:
tags: [ '*', '!tracy-v*' ]
tags: [ '*', '!tracy-v*', '!moltenvk-v*' ]
permissions:
contents: write
+2
View File
@@ -6,6 +6,8 @@ plugins {
id("com.gtnewhorizons.gtnhconvention")
}
apply(from = "gradle/mod-version.gradle.kts")
val lwjglDebug = false
val gpuHud = false
val renderdoc = false
+3
View File
@@ -93,6 +93,9 @@ DependencyHandlerScope.of(dependencies).apply {
shadowImplementation(project(":lwjgl3-backend")) { isTransitive = false }
shadowImplementation(project(":sdl-gpu")) { isTransitive = false }
runtimeOnlyNonPublishable(project(":tracy-client")) { isTransitive = false }
if (System.getProperty("os.name").lowercase().contains("mac")) {
runtimeOnlyNonPublishable(project(":moltenvk")) { isTransitive = false }
}
devOnlyNonPublishable(libs.async.profiler)
shadowImplementation(libs.eventbus)
@@ -17,7 +17,7 @@ public final class SystemProperties {
// Backend selection
public static final String KEY_USE_SDL_GPU = "angelica.sdlgpu.enable";
public static final boolean USE_SDL_GPU = Boolean.getBoolean(KEY_USE_SDL_GPU);
public static final Boolean SDL_GPU_OVERRIDE = parseBooleanOverride(KEY_USE_SDL_GPU);
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
@@ -28,7 +28,8 @@ public final class SystemProperties {
// SDL-GPU
public static final String KEY_SDL_GPU_DRIVER = "angelica.sdlgpu.driver";
public static final String SDL_GPU_DRIVER = System.getProperty(KEY_SDL_GPU_DRIVER, "");
public static final String SDL_GPU_DRIVER_OVERRIDE = parseStringOverride(KEY_SDL_GPU_DRIVER);
public static final String MOLTENVK_DIR = System.getProperty("angelica.moltenvk.dir", "angelica" + File.separator + "natives" + File.separator + "moltenvk");
public static final boolean SDL_GPU_DEBUG = Boolean.getBoolean("angelica.sdlgpu.debug");
private static final SdlAssertionMode ENCODER_ASSERTIONS = parseEnum("angelica.sdlgpu.encoderAssertions", SdlAssertionMode.OFF, SdlAssertionMode.WARN, SdlAssertionMode.class);
public static final boolean SDL_ENCODER_ASSERTIONS = ENCODER_ASSERTIONS != SdlAssertionMode.OFF;
@@ -63,6 +64,7 @@ public final class SystemProperties {
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
public static final 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 +133,12 @@ public final class SystemProperties {
CONFIGURED
}
public enum FlybyCamera {
FIRST_PERSON,
THIRD_PERSON_BACK,
THIRD_PERSON_FRONT
}
public enum FlybyWeather {
CLEAR,
RAIN,
@@ -158,7 +166,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) {
@@ -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) {
@@ -72,6 +72,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 +87,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");
@@ -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; }
@@ -85,6 +85,11 @@ public final class EcosystemNarrowRules {
"com.tihyo.legends.management.asm.AbstractTransformer",
"com.tihyo.legends.management.asm.ASMHelper",
"com.tihyo.legends.management.asm.Transformer"
)),
new NarrowRule("PowerConverters", "covers1624.powerconverters.", List.of(
"covers1624.powerconverters.asm",
"covers1624.powerconverters.randomdebugshit.CoFHDevEnvHacks"
))
);
@@ -3,7 +3,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -28,7 +28,7 @@ public record TexImage2DCmd(
int format,
int type,
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
@Nullable PixelStoreState unpack
) implements DisplayListCommand {
public TexImage2DCmd {
@@ -37,19 +37,19 @@ public record TexImage2DCmd(
}
}
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@@ -3,7 +3,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -28,7 +28,7 @@ public record TexSubImage2DCmd(
int format,
int type,
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
@Nullable PixelStoreState unpack
) implements DisplayListCommand {
public TexSubImage2DCmd {
@@ -37,19 +37,19 @@ public record TexSubImage2DCmd(
}
}
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@@ -626,6 +626,7 @@ public class GLSMRedirector {
.add("glGenRenderbuffers")
.add("glRenderbufferStorage")
.add("glFramebufferRenderbuffer")
.add("glGenerateMipmap")
);
methodRedirects.put(EXTFramebufferObject, RedirectMap.newMap()
.add("glBindFramebufferEXT", "glBindFramebuffer")
@@ -638,6 +639,7 @@ public class GLSMRedirector {
.add("glGenRenderbuffersEXT", "glGenRenderbuffers")
.add("glRenderbufferStorageEXT", "glRenderbufferStorage")
.add("glFramebufferRenderbufferEXT", "glFramebufferRenderbuffer")
.add("glGenerateMipmapEXT", "glGenerateMipmap")
);
methodRedirects.put(ARBOcclusionQuery, RedirectMap.newMap()
.add("glGenQueriesARB", "glGenQueries")
@@ -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,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;
@@ -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));
}
}
+3
View File
@@ -2,6 +2,9 @@
# LOCAL to test local config updates.
gtnh.settings.blowdryerTag = 0.2.2
# See mod-version-gradle.kts
gtnh.modules.gitVersion = false
# Human-readable mod name, available for mcmod.info population.
modName = Angelica
+3
View File
@@ -8,6 +8,8 @@ launchwrapper = "1.12"
# End Frozen
lwjgl3 = "3.4.2-SNAPSHOT"
# Must equal the LWJGL release shipped by lwjgl3ify. Do not bump without bumping lwjgl3ify.
lwjgl3Release = "3.4.2"
lwjgl3ify = "3.0.31"
celeritas = "2.5.12-GTNH"
@@ -75,6 +77,7 @@ lwjgl3-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl3" }
lwjgl3-sdl = { module = "org.lwjgl:lwjgl-sdl", version.ref = "lwjgl3" }
lwjgl3-spvc = { module = "org.lwjgl:lwjgl-spvc", version.ref = "lwjgl3" }
lwjgl3-shaderc = { module = "org.lwjgl:lwjgl-shaderc", version.ref = "lwjgl3" }
lwjgl3-vulkan = { module = "org.lwjgl:lwjgl-vulkan", version.ref = "lwjgl3Release" }
# Consumed with an explicit :dev / :universal / :downgraded-8 classifier at the call site.
gtnhlib = { module = "com.github.GTNewHorizons:GTNHLib", version.ref = "gtnhlib" }
+37
View File
@@ -0,0 +1,37 @@
// Replaces GTNHGradle's GitVersionModule (gtnh.modules.gitVersion = false): same version format, but only
// x.y.z[-suffix] tags count, so additional tags (moltenvk-v*, tracy-v*) on master never become the mod version.
fun git(vararg args: String): String? = try {
val exec = providers.exec {
commandLine("git", *args)
workingDir = rootDir
isIgnoreExitValue = true
}
if (exec.result.get().exitValue == 0) exec.standardOutput.asText.get().trim() else null
} catch (e: Exception) {
null
}
fun gitVersion(): String {
val described = git("describe", "--tags", "--long", "--first-parent", "--abbrev=7", "--match=[0-9]*.[0-9]*.[0-9]*", "HEAD")
val match = described?.let { Regex("(.*)-([0-9]+)-g[0-9a-f]+").matchEntire(it) }
val tag = match?.groupValues?.get(1)
val distance = match?.groupValues?.get(2)?.toInt() ?: 0
val hash = git("rev-parse", "HEAD")?.take(10)?.ifEmpty { null }
val base = tag ?: hash
if (base == null) {
logger.error("No git repository found; set the VERSION environment variable to version this build.")
return "NO-GIT-TAG-SET"
}
val dirty = !git("status", "--porcelain").isNullOrEmpty()
val branch = (git("branch", "--show-current")?.ifEmpty { null } ?: System.getenv("GIT_BRANCH") ?: "git")
.removePrefix("origin/")
.replace(Regex("[^a-zA-Z0-9-]+"), "-")
return when {
distance > 0 -> "$base-$branch.$distance+$hash${if (dirty) "-dirty" else ""}"
dirty -> "$base-$branch+$hash-dirty"
else -> base
}
}
extra["modVersion"] = providers.environmentVariable("VERSION").orNull ?: gitVersion()
+112
View File
@@ -0,0 +1,112 @@
plugins {
`java-library`
`maven-publish`
}
version = rootProject.version
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}
tasks.withType<JavaCompile>().configureEach {
options.release.set(8)
}
val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation", String::class.java)
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
configurations {
listOf("apiElements", "runtimeElements").forEach { name ->
named(name) {
attributes {
attribute(rfgObfAttr, "mcp")
attribute(rfgTransformedAttr, true)
}
}
}
}
val moltenvkVersion = (findProperty("moltenvkVersion") as String?) ?: libs.versions.lwjgl3Release.get()
repositories {
maven {
name = "Forge"
url = uri("https://maven.minecraftforge.net/")
}
maven {
name = "Forge artifact-only"
url = uri("https://maven.minecraftforge.net/")
metadataSources { artifact() }
content { includeModule("net.minecraftforge", "forge") }
}
mavenCentral()
}
val moltenvkNatives by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
compileOnly("net.minecraftforge:forge:1.7.10-10.13.4.1614-1.7.10:universal") { isTransitive = false }
listOf("natives-macos", "natives-macos-arm64").forEach { nativesClassifier ->
moltenvkNatives(libs.lwjgl3.vulkan) {
artifact { classifier = nativesClassifier }
isTransitive = false
}
}
}
val extractMoltenVK = tasks.register<Sync>("extractMoltenVK") {
val outDir = layout.buildDirectory.dir("generated/resources/moltenvk")
from(provider { moltenvkNatives.files.map { zipTree(it) } }) {
include("macos/**/libMoltenVK.dylib")
}
includeEmptyDirs = false
into(outDir)
doLast {
listOf("macos/x64", "macos/arm64").forEach { dir ->
val rel = "$dir/org/lwjgl/vulkan/libMoltenVK.dylib"
if (!outDir.get().file(rel).asFile.isFile) {
throw GradleException("MoltenVK dylib missing after extraction: $rel")
}
}
}
}
sourceSets["main"].resources.srcDir(extractMoltenVK)
val distJar = tasks.register<Jar>("distJar") {
archiveBaseName.set("angelica-moltenvk")
archiveVersion.set(moltenvkVersion)
from(sourceSets["main"].output)
manifest {
attributes(
"FMLCorePlugin" to "com.gtnewhorizons.angelica.moltenvk.loading.MoltenVKCorePlugin"
)
}
}
publishing {
publications {
create<MavenPublication>("mavenJava") {
groupId = "com.gtnewhorizons.angelica"
artifactId = "angelica-moltenvk"
version = moltenvkVersion
artifact(distJar)
}
}
repositories {
if (hasProperty("publishMoltenVK") && System.getenv("MAVEN_USER") != null) {
maven {
name = "GTNHMaven"
url = uri(rootProject.findProperty("mavenPublishUrl")?.toString() ?: "https://nexus.gtnewhorizons.com/repository/releases/")
credentials {
username = System.getenv("MAVEN_USER")
password = System.getenv("MAVEN_PASSWORD")
}
}
}
}
}
@@ -0,0 +1,32 @@
package com.gtnewhorizons.angelica.moltenvk.loading;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import java.util.Map;
@IFMLLoadingPlugin.MCVersion("1.7.10")
public class MoltenVKCorePlugin implements IFMLLoadingPlugin {
@Override
public String[] getASMTransformerClass() {
return null;
}
@Override
public String getModContainerClass() {
return null;
}
@Override
public String getSetupClass() {
return null;
}
@Override
public void injectData(Map<String, Object> data) {}
@Override
public String getAccessTransformerClass() {
return null;
}
}
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+1
View File
@@ -9,6 +9,7 @@
# 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).
#
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
# FLYBY_PACING - UNCAPPED | CONFIGURED. Passed through when set.
@@ -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;
}
@@ -7,6 +7,7 @@ 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 me.eigenraven.lwjgl3ify.client.MainThreadExec;
@@ -40,6 +41,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 +65,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;
@@ -167,6 +170,7 @@ public class SDLGPURenderBackend extends RenderBackend {
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 +251,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) {
@@ -514,14 +520,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 +537,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() {
@@ -1332,6 +1337,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 +1357,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;
@@ -2760,8 +2771,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 +2821,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 +2920,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 +2944,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 +3072,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 +3149,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 +3362,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 +3402,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 +3444,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 +3459,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 +3899,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 +4159,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 +4230,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) {
@@ -4468,8 +4539,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);
}
}
}
@@ -1,6 +1,8 @@
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.util.DebugMessageRelay;
@@ -20,11 +22,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;
@@ -82,9 +85,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 +120,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 +144,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));
@@ -151,13 +167,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 +245,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() {
@@ -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);
@@ -173,8 +173,9 @@ public final class FFPTrace {
sb.append(" | ffpUbo=buffer").append(glId).append(" noStaging");
return;
}
final int base = st.uboRangeOffset[binding];
sb.append(" | ffpUbo=buffer").append(glId).append(" base=").append(base);
final int rangeOffset = st.uboRangeOffset[binding];
final int base = pm != null ? (int) pm.stagingIndex(rangeOffset) : rangeOffset;
sb.append(" | ffpUbo=buffer").append(glId).append(" base=").append(rangeOffset);
appendUboFloat(buf, base, FFPUniformBlock.ALPHA_REF, " u_AlphaRef=");
appendUboMatrix(buf, base, FFPUniformBlock.MVP_MATRIX, " u_MVPMatrix=");
appendUboMatrix(buf, base, FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, " u_LightmapTextureMatrix=");
@@ -698,8 +698,8 @@ public final class PipelineApplier {
final int offset = st.uboRangeOffset[binding];
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
if (pm != null) {
if (offset < 0 || offset + size > pm.staging.capacity()) return 0L;
return MemoryUtil.memAddress0(pm.staging) + offset;
if (!pm.covers(offset, size)) return 0L;
return MemoryUtil.memAddress0(pm.staging) + pm.stagingIndex(offset);
}
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
if (shadow != null) {
@@ -8,16 +8,13 @@ public final class ByteRegionCopy {
private ByteRegionCopy() {}
public static void copyByteRegion(ByteBuffer src, int srcOff, ByteBuffer dst, int dstOff, int len) {
if ((srcOff | dstOff | len) < 0 || srcOff + (long) len > src.capacity() || dstOff + (long) len > dst.capacity()) {
throw new IllegalStateException("copyByteRegion out of bounds: len=" + len + " srcOff=" + srcOff + " src.capacity=" + src.capacity() + " dstOff=" + dstOff + " dst.capacity=" + dst.capacity());
}
if (src.isDirect() && dst.isDirect()) {
MemoryUtil.memCopy(MemoryUtil.memAddress(src, srcOff), MemoryUtil.memAddress(dst, dstOff), len);
return;
}
if (src.hasArray() && dst.hasArray()) {
System.arraycopy(src.array(), src.arrayOffset() + srcOff, dst.array(), dst.arrayOffset() + dstOff, len);
return;
}
for (int i = 0; i < len; i++) {
dst.put(dstOff + i, src.get(srcOff + i));
}
dst.put(dstOff, src, srcOff, len);
}
}
@@ -0,0 +1,12 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import java.nio.ByteBuffer;
public final class MappedRange {
public int glId;
public ByteBuffer staging;
public long offset;
public long length;
public boolean invalidate;
public int accessFlags;
}
@@ -33,11 +33,14 @@ public final class PersistentBufferSync {
this.sink = sink;
}
public void onPersistentBufferWrite(int glId, long offset, long size) {
public boolean onPersistentBufferWrite(int glId, long offset, long size) {
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
if (pm != null && pm.markDirty(offset, size)) {
if (pm == null || size == 0) return true;
if (offset < 0 || size < 0 || offset > pm.length || size > pm.length - offset) return false;
if (pm.markDirty(offset, size)) {
resourceManager.trackPersistentDirty();
}
return true;
}
public void uploadDirtyPersistentRegion() { processDirtyPersistentRegions(false); }
@@ -59,30 +62,41 @@ public final class PersistentBufferSync {
for (int i = 0; i < n; i++) {
final PersistentMapping pm = vals.get(i);
if (!pm.isDirty()) continue;
final long gpuHandle = resourceManager.getBufferHandle(keys.getInt(i));
if (gpuHandle == 0) continue;
final long claimed = pm.claimDirty();
if (PersistentMapping.isClean(claimed)) continue;
final long off = PersistentMapping.rangeOffset(claimed);
final long size = PersistentMapping.rangeSize(claimed);
if (Tracy.ENABLED) frameManager.notePersistentDrain();
if (defer) {
final long seq = sink.nextSeq();
pm.lastEnqueuedSeq = seq;
sink.enqueue(TransferThread.StagingReadUpload.acquire(pm.staging, off, size, gpuHandle, off, seq, false));
enqueued++;
} else {
pm.staging.position((int) off).limit((int) (off + size));
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), pm.staging, gpuHandle, off, false);
pm.staging.clear();
final int glId = keys.getInt(i);
long copyPass = 0L;
if (!defer) {
if (resourceManager.getBufferHandle(glId) == 0) continue;
copyPass = frameManager.ensureCopyPass();
}
resourceManager.beginPersistentDrain();
try {
if (resourceManager.getPersistentMapping(glId) != pm) continue;
final long gpuHandle = resourceManager.getBufferHandle(glId);
if (gpuHandle == 0) continue;
final long claimed = pm.claimDirty();
if (PersistentMapping.isClean(claimed)) continue;
resourceManager.clearPersistentDirty();
final long off = PersistentMapping.rangeOffset(claimed);
final long size = PersistentMapping.rangeSize(claimed);
if (Tracy.ENABLED) frameManager.notePersistentDrain();
if (defer) {
final long seq = sink.nextSeq();
pm.lastEnqueuedSeq = seq;
sink.enqueue(TransferThread.StagingReadUpload.acquire(pm.staging, off, size, gpuHandle, pm.offset + off, seq, false));
enqueued++;
} else {
resourceManager.uploadRangeToBuffer(copyPass, pm.staging, off, size, gpuHandle, pm.offset + off, false);
}
} finally {
resourceManager.endPersistentDrain();
}
resourceManager.clearPersistentDirty();
}
return enqueued;
}
public static void mirrorPersistentCopy(ByteBuffer srcStaging, long srcOffset, ByteBuffer dstStaging, long dstOffset, long size) {
ByteRegionCopy.copyByteRegion(srcStaging, (int) srcOffset, dstStaging, (int) dstOffset, (int) size);
public static void mirrorPersistentCopy(PersistentMapping src, long readOffset, PersistentMapping dst, long writeOffset, long size) {
if (!src.covers(readOffset, size) || !dst.covers(writeOffset, size)) return;
ByteRegionCopy.copyByteRegion(src.staging, (int) src.stagingIndex(readOffset), dst.staging, (int) dst.stagingIndex(writeOffset), (int) size);
}
public void mirrorEboShadow(int glId, ByteBuffer src, int dstOffset, int len) {
@@ -24,6 +24,12 @@ public final class PersistentMapping {
public static long rangeSize(long packed) { return (packed & 0xFFFFFFFFL) - (packed >>> 32); }
static long packRange(long min, long max) { return (min << 32) | (max & 0xFFFFFFFFL); }
public long stagingIndex(long bufferOffset) { return bufferOffset - offset; }
public boolean covers(long bufferOffset, long size) {
return size >= 0 && bufferOffset >= offset && bufferOffset - offset <= length && size <= length - (bufferOffset - offset);
}
public boolean isDirty() { return dirtyRange.get() != CLEAN; }
public long claimDirty() { return dirtyRange.getAndSet(CLEAN); }
@@ -31,6 +31,7 @@ import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -96,6 +97,9 @@ public final class ResourceManager {
private final Int2ObjectOpenHashMap<ContextState.VAOState> vaoStates = new Int2ObjectOpenHashMap<>();
private final Int2ObjectOpenHashMap<PersistentMapping> persistentMappings = new Int2ObjectOpenHashMap<>();
private final Int2ObjectOpenHashMap<MappedRange> plainMappings = new Int2ObjectOpenHashMap<>();
private MappedRange[] plainMappingPool = new MappedRange[4];
private int plainMappingPoolCount;
private final Int2ObjectOpenHashMap<ByteBuffer> uboShadows = new Int2ObjectOpenHashMap<>();
private final Int2ObjectOpenHashMap<ByteBuffer> eboShadows = new Int2ObjectOpenHashMap<>();
private final IntOpenHashSet eboShadowWanted = new IntOpenHashSet();
@@ -752,6 +756,7 @@ public final class ResourceManager {
public void deleteBuffer(int glId) {
final ByteBuffer droppedPboStaging;
final ByteBuffer droppedPlainStaging;
final PersistentMapping droppedPm;
wLock.lock();
try {
@@ -763,6 +768,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 +783,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 +962,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 +1045,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 +1065,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 +1078,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 +1265,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 +1279,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 +1299,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 +1563,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 +1661,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 +1676,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 +2647,20 @@ public final class ResourceManager {
arrayShadowWanted.clear();
for (ByteBuffer bb : pboStagingData.values()) memFree(bb);
pboStagingData.clear();
for (PersistentMapping pm : persistentMappings.values()) {
for (var e : persistentMappings.int2ObjectEntrySet()) {
final PersistentMapping pm = e.getValue();
untrackPersistentDirty(pm);
releasePersistentStaging(pm);
releasePersistentStaging(pm, e.getIntKey(), "shutdown");
}
persistentMappings.clear();
mappingsVersion++;
for (MappedRange m : plainMappings.values()) {
if (m.staging != null) memFree(m.staging);
m.staging = null;
}
plainMappings.clear();
Arrays.fill(plainMappingPool, null);
plainMappingPoolCount = 0;
}
public record TextureMeta(int glTarget, int glFormat, int sdlFormat, int width, int height, int depth, int levels, int usage) {}
@@ -329,11 +329,15 @@ public final class TransferThread {
final ByteBuffer mapped = SDL_MapGPUTransferBuffer(device.getDevice(), xfer, true, mapSize);
if (mapped != null) {
final ByteBuffer src = sr.stagingBuffer;
final int prevPos = src.position();
final int prevLim = src.limit();
src.limit((int) (sr.srcOffset + size)).position((int) sr.srcOffset);
mapped.put(src);
src.limit(prevLim).position(prevPos);
if (sr.srcOffset < 0 || sr.srcOffset + size > src.capacity() || size > mapped.capacity()) {
LOG.error("StagingReadUpload out of bounds: srcOffset={} size={} src.capacity={} mapped.capacity={}; upload dropped", sr.srcOffset, size, src.capacity(), mapped.capacity());
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
resourceManager.returnTransferBufferThreadSafe(xfer, size);
retireSeq(sr.seq);
StagingReadUpload.release(sr);
return;
}
MemoryUtil.memCopy(MemoryUtil.memAddress0(src) + sr.srcOffset, MemoryUtil.memAddress0(mapped), size);
}
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
recordBufferUpload(xfer, size, sr.dstGpuBuffer, sr.dstOffset, sr.seq, sr.cycle);
@@ -53,6 +53,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 +65,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 +236,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) {
@@ -377,6 +407,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 +465,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 +604,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 +648,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 +857,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 +920,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());
}
@@ -1476,6 +1536,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 +1581,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 +1597,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 +1801,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 +1885,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();
@@ -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);
}
@@ -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
@@ -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);
}
}
}
@@ -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();
@@ -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());
@@ -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;
@@ -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);
}
@@ -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);
@@ -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);
}
}
}
@@ -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());
@@ -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());
}
@@ -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");
}
@@ -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);
}
}
}
+1
View File
@@ -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,174 @@
package com.gtnewhorizons.angelica.client.gui;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.GLVersionChoice;
import com.gtnewhorizons.angelica.config.RenderBackendChoice;
import com.gtnewhorizons.angelica.config.SdlGpuDriver;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendOptions;
import com.gtnewhorizons.angelica.glsm.backend.BackendStartGuard;
import com.gtnewhorizons.angelica.glsm.backend.MoltenVK;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
import me.flashyreese.mods.reeses_sodium_options.client.gui.ReeseSodiumVideoOptionsScreen;
import me.jellysquid.mods.sodium.client.gui.options.ActionOption;
import me.jellysquid.mods.sodium.client.gui.options.OptionFlag;
import me.jellysquid.mods.sodium.client.gui.options.OptionGroup;
import me.jellysquid.mods.sodium.client.gui.options.OptionImpl;
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
import me.jellysquid.mods.sodium.client.gui.options.control.CyclingControl;
import me.jellysquid.mods.sodium.client.gui.options.storage.AngelicaOptionsStorage;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.Util;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class RendererOptionPages {
private static final AngelicaOptionsStorage angelicaOpts = new AngelicaOptionsStorage();
public static OptionPage renderer() {
final Util.EnumOS os = Util.getOSType();
final boolean sdlAvailable = SDLGPUGate.isSDLGPUAvailable();
final RenderBackendChoice[] backends = sdlAvailable ? RenderBackendChoice.values() : new RenderBackendChoice[] { RenderBackendChoice.OPENGL };
final SdlGpuDriver[] drivers = allowedDrivers(os);
final GLVersionChoice[] glVersions = allowedGlVersions(glVersionCeiling(os));
final String active = activeLabel();
final OptionGroup.Builder status = OptionGroup.createBuilder()
.add(ActionOption.label(I18n.format("options.angelica.renderer.status"), I18n.format("options.angelica.renderer.status.tooltip"), statusLabel()))
.add(ActionOption.label(I18n.format("options.angelica.renderer.active"), I18n.format("options.angelica.renderer.active.tooltip"), () -> active));
if (os == Util.EnumOS.OSX) {
final String moltenVk = I18n.format("options.angelica.renderer.moltenvk." + MoltenVK.source().name().toLowerCase());
status.add(ActionOption.label(I18n.format("options.angelica.renderer.moltenvk"), I18n.format("options.angelica.renderer.moltenvk.tooltip"), () -> moltenVk));
}
return new OptionPage(I18n.format("options.angelica.renderer.page"), ImmutableList.of(
OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(RenderBackendChoice.class, angelicaOpts)
.setName(I18n.format("options.angelica.renderer.backend"))
.setTooltip(overrideTooltip(backendTooltip(sdlAvailable), SystemProperties.KEY_USE_SDL_GPU, SystemProperties.SDL_GPU_OVERRIDE != null))
.setControl(o -> new CyclingControl<>(o, RenderBackendChoice.class, backends))
.setBinding((opts, value) -> AngelicaConfig.renderBackend = value, opts -> backendOf(SystemProperties.SDL_GPU_OVERRIDE != null ? SystemProperties.SDL_GPU_OVERRIDE : AngelicaConfig.sdlGpuConfigured()))
.setEnabled(SystemProperties.SDL_GPU_OVERRIDE == null)
.setFlags(OptionFlag.REQUIRES_GAME_RESTART)
.build())
.add(OptionImpl.createBuilder(SdlGpuDriver.class, angelicaOpts)
.setName(I18n.format("options.angelica.renderer.driver"))
.setTooltip(overrideTooltip(driverTooltip(os), SystemProperties.KEY_SDL_GPU_DRIVER, SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null))
.setControl(o -> new CyclingControl<>(o, SdlGpuDriver.class, drivers))
.setBinding((opts, value) -> AngelicaConfig.sdlGpuDriver = value, opts -> shownDriver())
.setEnabled(SystemProperties.SDL_GPU_DRIVER_OVERRIDE == null)
.setFlags(OptionFlag.REQUIRES_GAME_RESTART)
.build())
.add(OptionImpl.createBuilder(GLVersionChoice.class, angelicaOpts)
.setName(I18n.format("options.angelica.renderer.glVersion"))
.setTooltip(I18n.format("options.angelica.renderer.glVersion.tooltip"))
.setControl(o -> new CyclingControl<>(o, GLVersionChoice.class, glVersions, glVersionNames()))
.setBinding((opts, value) -> AngelicaConfig.glVersion = value.version(), opts -> GLVersionChoice.of(AngelicaConfig.glVersion, GLVersionChoice.GL46.version()))
.setFlags(OptionFlag.REQUIRES_GAME_RESTART)
.build())
.build(),
status.build()));
}
public static OptionPage withLink(OptionPage general, ReeseSodiumVideoOptionsScreen gui) {
final String rendererPage = I18n.format("options.angelica.renderer.page");
final String active = activeLabel();
return new OptionPage(general.getName(), ImmutableList.<OptionGroup>builder()
.add(OptionGroup.createBuilder()
.add(new ActionOption(I18n.format("options.angelica.renderer.link"), I18n.format("options.angelica.renderer.link.tooltip"),
() -> active, () -> gui.showPage(rendererPage), () -> true))
.build())
.addAll(general.getGroups())
.build());
}
private static RenderBackendChoice backendOf(boolean sdlGpu) {
return sdlGpu ? RenderBackendChoice.SDL_GPU : RenderBackendChoice.OPENGL;
}
private static SdlGpuDriver shownDriver() {
final SdlGpuDriver forced = SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SdlGpuDriver.fromSdlName(SystemProperties.SDL_GPU_DRIVER_OVERRIDE) : null;
if (forced != null) return forced;
return AngelicaConfig.sdlGpuDriver != null ? AngelicaConfig.sdlGpuDriver : SdlGpuDriver.AUTO;
}
static SdlGpuDriver[] allowedDrivers(Util.EnumOS os) {
return switch (os) {
case OSX -> new SdlGpuDriver[] { SdlGpuDriver.AUTO, SdlGpuDriver.METAL, SdlGpuDriver.VULKAN };
case WINDOWS -> new SdlGpuDriver[] { SdlGpuDriver.AUTO, SdlGpuDriver.VULKAN, SdlGpuDriver.D3D12 };
default -> new SdlGpuDriver[] { SdlGpuDriver.AUTO, SdlGpuDriver.VULKAN };
};
}
private static int glVersionCeiling(Util.EnumOS os) {
final int platformMax = os == Util.EnumOS.OSX ? 41 : 46;
final int pinned = AngelicaConfig.pinnedGLVersion;
return pinned >= 33 && pinned < platformMax ? pinned : platformMax;
}
static GLVersionChoice[] allowedGlVersions(int ceiling) {
final List<GLVersionChoice> allowed = new ArrayList<>();
for (final GLVersionChoice choice : GLVersionChoice.values()) {
if (choice.version() <= ceiling) allowed.add(choice);
}
return allowed.toArray(new GLVersionChoice[0]);
}
private static String[] glVersionNames() {
final GLVersionChoice[] universe = GLVersionChoice.values();
final String[] names = new String[universe.length];
for (int i = 0; i < universe.length; i++) {
names[i] = universe[i] == GLVersionChoice.AUTO ? I18n.format("options.angelica.renderer.glVersion.auto") : universe[i].label();
}
return names;
}
private static String backendTooltip(boolean sdlAvailable) {
final String base = EnumChatFormatting.RED + I18n.format("options.angelica.renderer.backend.experimental") + EnumChatFormatting.RESET + " " + I18n.format("options.angelica.renderer.backend.tooltip");
return sdlAvailable ? base : base + " " + I18n.format("options.angelica.renderer.backend.unavailable", SDLGPUGate.missingRequirement());
}
private static String driverTooltip(Util.EnumOS os) {
final String base = I18n.format("options.angelica.renderer.driver.tooltip");
return switch (os) {
case OSX -> base + " " + I18n.format("options.angelica.renderer.driver.tooltip.macos");
case WINDOWS -> base + " " + I18n.format("options.angelica.renderer.driver.tooltip.windows") + " " + EnumChatFormatting.RED + I18n.format("options.angelica.renderer.driver.tooltip.d3d12") + EnumChatFormatting.RESET;
default -> base + " " + I18n.format("options.angelica.renderer.driver.tooltip.other");
};
}
private static Supplier<String> statusLabel() {
final String restartLabel = I18n.format("options.angelica.status.restart");
final String forcedLabel = I18n.format("options.angelica.renderer.status.forced");
final String failedLabel = I18n.format("options.angelica.renderer.status.failed");
final String activeLabel = I18n.format("options.angelica.renderer.status.active");
final String resetLabel = I18n.format("options.angelica.renderer.status.reset");
return () -> {
if (AngelicaConfig.sdlGpuConfigured() != BackendOptions.configSdlGpu()
|| !AngelicaConfig.sdlGpuDriverName().equals(BackendOptions.configDriver())
|| AngelicaConfig.glVersion != BackendOptions.configGlVersion()) {
return restartLabel;
}
if (BackendStartGuard.tripped() && !AngelicaConfig.sdlGpuConfigured()) return resetLabel;
if (BackendOptions.sdlGpuRequested() && !BackendManager.RENDER_BACKEND.isSDLGPU()) return failedLabel;
return SystemProperties.SDL_GPU_OVERRIDE != null || SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? forcedLabel : activeLabel;
};
}
private static String activeLabel() {
final String name = GLStateManager.getRenderBackendName();
return BackendManager.RENDER_BACKEND.isSDLGPU() ? name : name + " " + RenderSystem.getGlMajor() + "." + RenderSystem.getGlMinor();
}
static String overrideTooltip(String base, String flag, boolean overridden) {
return overridden ? base + " " + I18n.format("options.angelica.overriddenBy", flag) : base;
}
}
@@ -34,7 +34,7 @@ public class TracyOptionPages {
default -> startLabel;
};
final String restartLabel = I18n.format("options.angelica.tracy.status.restart");
final String restartLabel = I18n.format("options.angelica.status.restart");
final String runningLabel = I18n.format("options.angelica.tracy.status.running");
final String runningForcedLabel = I18n.format("options.angelica.tracy.status.runningForced");
final String failedLabel = I18n.format("options.angelica.tracy.status.failed");
@@ -87,8 +87,7 @@ public class TracyOptionPages {
.build())
.build(),
OptionGroup.createBuilder()
.add(new ActionOption(I18n.format("options.angelica.tracy.status"), I18n.format("options.angelica.tracy.status.tooltip"),
statusLabel, () -> {}, () -> false))
.add(ActionOption.label(I18n.format("options.angelica.tracy.status"), I18n.format("options.angelica.tracy.status.tooltip"), statusLabel))
.build(),
OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(int.class, angelicaOpts)
@@ -104,7 +103,6 @@ public class TracyOptionPages {
}
private static String overrideTooltip(String baseKey, String flag, boolean overridden) {
final String base = I18n.format(baseKey);
return overridden ? base + " " + I18n.format("options.angelica.tracy.overriddenBy", flag) : base;
return RendererOptionPages.overrideTooltip(I18n.format(baseKey), flag, overridden);
}
}
@@ -55,14 +55,6 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static int workerThreadCount;
@Config.Comment("Enable NotFine Options")
@Config.DefaultBoolean(false)
public static boolean enableNotFineOptions;
@Config.Comment("Enable Reese's Sodium Options")
@Config.DefaultBoolean(true)
public static boolean enableReesesSodiumOptions;
@Config.Comment("Inject BakedModel rendering into some vanilla blocks")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
@@ -434,6 +426,11 @@ public class AngelicaConfig {
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public boolean narrowLegendsMod;
@Config.Comment("Narrow Power Converters transformer exclusions to allow GL redirection")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public boolean narrowPowerConverters;
}
@Config.Comment("Renders chunks before neighbors are ready. Improves loading at render distance edges, useful for low render distance servers.")
@@ -463,6 +460,22 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean disableGLVersionPinning;
@Config.Comment("Requested OpenGL context version as an integer (e.g. 46, 41, 33). 0 = highest available. Falls back to probing when the version cannot be created.")
@Config.DefaultInt(0)
@Config.RangeInt(min = 0, max = 46)
@Config.RequiresMcRestart
public static int glVersion;
@Config.Comment("Render backend: OPENGL, or SDL_GPU (experimental; Vulkan/Metal/Direct3D 12 through SDL3, falls back to OpenGL when no device can be created). -Dangelica.sdlgpu.enable=true|false overrides this.")
@Config.DefaultEnum("OPENGL")
@Config.RequiresMcRestart
public static RenderBackendChoice renderBackend;
@Config.Comment("SDL GPU driver: AUTO, VULKAN, METAL (macOS), D3D12 (Windows). Vulkan on macOS needs MoltenVK and uses Metal without it. -Dangelica.sdlgpu.driver overrides this.")
@Config.DefaultEnum("AUTO")
@Config.RequiresMcRestart
public static SdlGpuDriver sdlGpuDriver;
@Config.Comment("Disables GL Error checks. Always set to false in dev env or if LWJGL debug is on. Improves performance.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
@@ -531,6 +544,14 @@ public class AngelicaConfig {
return enableEntityBatching && enableCubeInstancing;
}
public static boolean sdlGpuConfigured() {
return renderBackend == RenderBackendChoice.SDL_GPU;
}
public static String sdlGpuDriverName() {
return sdlGpuDriver == null ? "" : sdlGpuDriver.sdlName();
}
public static GLProfile getEffectiveGlProfile() {
final String sys = SystemProperties.GL_PROFILE.trim().toUpperCase();
if ("GLES".equals(sys)) return GLProfile.ES;
@@ -0,0 +1,33 @@
package com.gtnewhorizons.angelica.config;
public enum GLVersionChoice {
AUTO(0),
GL33(33),
GL40(40),
GL41(41),
GL42(42),
GL43(43),
GL44(44),
GL45(45),
GL46(46);
private final int version;
GLVersionChoice(int version) {
this.version = version;
}
public int version() { return version; }
public String label() {
return version / 10 + "." + version % 10;
}
public static GLVersionChoice of(int version, int ceiling) {
GLVersionChoice best = AUTO;
for (final GLVersionChoice choice : values()) {
if (choice.version != 0 && choice.version <= version && choice.version <= ceiling) best = choice;
}
return best;
}
}
@@ -0,0 +1,19 @@
package com.gtnewhorizons.angelica.config;
import me.jellysquid.mods.sodium.client.gui.options.named.NamedState;
public enum RenderBackendChoice implements NamedState {
OPENGL ("options.angelica.renderer.backend.opengl"),
SDL_GPU("options.angelica.renderer.backend.sdl_gpu");
private final String key;
RenderBackendChoice(String key) {
this.key = key;
}
@Override
public String getKey() {
return key;
}
}
@@ -0,0 +1,32 @@
package com.gtnewhorizons.angelica.config;
import me.jellysquid.mods.sodium.client.gui.options.named.NamedState;
public enum SdlGpuDriver implements NamedState {
AUTO ("", "options.angelica.renderer.driver.auto"),
VULKAN("vulkan", "options.angelica.renderer.driver.vulkan"),
METAL ("metal", "options.angelica.renderer.driver.metal"),
D3D12 ("direct3d12", "options.angelica.renderer.driver.d3d12");
private final String sdlName;
private final String key;
SdlGpuDriver(String sdlName, String key) {
this.sdlName = sdlName;
this.key = key;
}
@Override
public String getKey() {
return key;
}
public String sdlName() { return sdlName; }
public static SdlGpuDriver fromSdlName(String sdlName) {
for (final SdlGpuDriver driver : values()) {
if (driver.sdlName.equalsIgnoreCase(sdlName)) return driver;
}
return null;
}
}
@@ -84,6 +84,8 @@ public final class FlybyRunner {
private boolean vsyncOverridden;
private boolean showDebugInfoSaved;
private boolean showDebugInfoOverridden;
private int cameraSaved;
private boolean cameraOverridden;
private int shotEvery;
private int nextShot;
private int shot;
@@ -375,6 +377,11 @@ public final class FlybyRunner {
this.showDebugInfoOverridden = true;
mc.gameSettings.showDebugInfo = true;
}
if (!this.cameraOverridden) {
this.cameraSaved = mc.gameSettings.thirdPersonView;
this.cameraOverridden = true;
mc.gameSettings.thirdPersonView = SystemProperties.FLYBY_CAMERA.ordinal();
}
if ("isbrh".equals(SystemProperties.FLYBY_CRASH_TEST)) {
IsbrhTestRenderer.armCrashTest();
}
@@ -535,7 +542,7 @@ public final class FlybyRunner {
this.nextShot = this.shotEvery / 2;
FramePacer.beginStats();
final String config = "backend=" + GLStateManager.getRenderBackendName() + " pacing=" + SystemProperties.FLYBY_PACING + " vsync=" + GLStateManager.getEffectiveVSyncMode() + " discard=" + (mc.getIntegratedServer() != null);
final String config = "backend=" + GLStateManager.getRenderBackendName() + " pacing=" + SystemProperties.FLYBY_PACING + " vsync=" + GLStateManager.getEffectiveVSyncMode() + " discard=" + (mc.getIntegratedServer() != null) + " camera=" + SystemProperties.FLYBY_CAMERA;
if (Tracy.ENABLED) {
Tracy.message("flyby start route=" + this.route.id() + " length=" + this.runLength + this.route.lengthUnit() + " speed=" + this.route.speedOr(this.speed) + "b/t ticks=" + this.runTicks + " " + config);
}
@@ -757,6 +764,9 @@ public final class FlybyRunner {
final String summary = this.summarise(elapsedNs, player);
LOGGER.info(summary);
LOGGER.info(FramePacer.endStats());
if (mc.gameSettings.thirdPersonView != SystemProperties.FLYBY_CAMERA.ordinal()) {
LOGGER.warn("Flyby: camera left {} during the run (thirdPersonView={}), screenshots are not comparable", SystemProperties.FLYBY_CAMERA, mc.gameSettings.thirdPersonView);
}
if (mc.thePlayer != null) {
mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + summary));
}
@@ -787,6 +797,7 @@ public final class FlybyRunner {
this.returnToOrigin(player);
this.restorePauseOnLostFocus(mc);
this.restoreShowDebugInfo(mc);
this.restoreCamera(mc);
this.armed = false;
sceneGuarded = false;
this.sceneClearRequested = this.sceneCommands.length > 0;
@@ -819,6 +830,12 @@ public final class FlybyRunner {
this.showDebugInfoOverridden = false;
}
private void restoreCamera(Minecraft mc) {
if (!this.cameraOverridden) return;
mc.gameSettings.thirdPersonView = this.cameraSaved;
this.cameraOverridden = false;
}
private void returnToOrigin(EntityClientPlayerMP player) {
player.motionX = 0.0D;
player.motionY = 0.0D;
@@ -969,6 +986,7 @@ public final class FlybyRunner {
final Minecraft mc = Minecraft.getMinecraft();
this.restorePauseOnLostFocus(mc);
this.restoreShowDebugInfo(mc);
this.restoreCamera(mc);
this.armed = false;
this.stopRecording(mc);
this.pendingRequest.set(null);
@@ -0,0 +1,96 @@
package com.gtnewhorizons.angelica.iris;
import com.gtnewhorizons.angelica.utils.AngelicaJar;
import net.coderbot.iris.Iris;
import net.coderbot.iris.parsing.IrisFunctions;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Prepares for the first shader pack load on a background thread while the game sits at the main menu.
*/
public final class ShaderWarmup {
private static final Logger LOGGER = LogManager.getLogger("Angelica/ShaderWarmup");
private static final String[] PACKAGES = { "net/coderbot/iris/", "kroppeb/stareval/" };
private static final String SKIPPED_PACKAGE = "net/coderbot/iris/compat/dh/";
private static boolean started;
private ShaderWarmup() {}
/** Main thread only */
public static void start() {
if (started) return;
started = true;
final Runnable readSelectedPack = Iris.prepareSelectedShaderPack();
final Thread thread = new Thread(() -> {
preloadClasses();
if (readSelectedPack != null) readSelectedPack.run();
}, "Angelica-Shader-Warmup");
thread.setDaemon(true);
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
private static void preloadClasses() {
final List<String> names;
try {
names = listClasses();
} catch (Exception e) {
LOGGER.debug("Could not list shader classes to preload", e);
return;
}
final ClassLoader loader = Iris.class.getClassLoader();
for (String name : names) {
try {
Class.forName(name, false, loader);
} catch (Throwable ignored) {
// A class that cannot load here fails the same way when the pipeline reaches it
}
}
try {
Class.forName(IrisFunctions.class.getName(), true, loader);
} catch (Throwable ignored) {
}
}
private static List<String> listClasses() throws Exception {
final Path source = AngelicaJar.location();
final List<String> names = new ArrayList<>();
if (source == null) return names;
if (Files.isDirectory(source)) {
try (Stream<Path> files = Files.walk(source)) {
files.forEach(file -> addIfShaderClass(names, source.relativize(file).toString().replace('\\', '/')));
}
} else {
try (ZipFile jar = new ZipFile(source.toFile())) {
final Enumeration<? extends ZipEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
addIfShaderClass(names, entries.nextElement().getName());
}
}
}
Collections.sort(names);
return names;
}
private static void addIfShaderClass(List<String> names, String entry) {
if (!entry.endsWith(".class") || entry.startsWith(SKIPPED_PACKAGE)) return;
for (String packagePath : PACKAGES) {
if (entry.startsWith(packagePath)) {
names.add(entry.substring(0, entry.length() - ".class".length()).replace('/', '.'));
return;
}
}
}
}
@@ -8,7 +8,10 @@ import com.gtnewhorizon.gtnhmixins.builders.ITransformers;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.CompatConfig;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.config.RenderBackendChoice;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.backend.BackendOptions;
import com.gtnewhorizons.angelica.glsm.backend.BackendStartGuard;
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier;
import com.gtnewhorizons.angelica.glsm.loading.Lwjgl3ifyExclusions;
import com.gtnewhorizons.angelica.glsm.profiling.TracyOptions;
@@ -33,6 +36,7 @@ import org.apache.logging.log4j.core.config.LoggerConfig;
import org.spongepowered.asm.launch.GlobalProperties;
import org.spongepowered.asm.service.mojang.MixinServiceLaunchWrapper;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -68,6 +72,12 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
// Angelica Config
ConfigurationManager.registerConfig(AngelicaConfig.class);
TracyOptions.latch(AngelicaConfig.enableTracy, AngelicaConfig.tracyAllowRemote, AngelicaConfig.tracyFineZones, AngelicaConfig.tracyMaxSrcLocs);
if (BackendStartGuard.begin(new File(Launch.minecraftHome, "angelica" + File.separator + "sdlgpu-starting"), AngelicaConfig.sdlGpuConfigured(), SystemProperties.SDL_GPU_OVERRIDE != null)) {
LOGGER.warn("The last start with SDL GPU did not reach the main menu; resetting the render backend to OpenGL");
AngelicaConfig.renderBackend = RenderBackendChoice.OPENGL;
ConfigurationManager.save(AngelicaConfig.class);
}
BackendOptions.latch(AngelicaConfig.sdlGpuConfigured(), AngelicaConfig.sdlGpuDriverName(), AngelicaConfig.glVersion);
ConfigurationManager.registerConfig(CompatConfig.class);
ConfigurationManager.registerConfig(FontConfig.class);
MCPatcherForgeConfig.registerConfig();
@@ -89,7 +99,7 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
DependencyVerifier.gtnhLibChecks("Angelica"), LOGGER,
(title, message) -> MissingDependencySdl.showFatal(title, message));
if (SystemProperties.USE_SDL_GPU) {
if (BackendOptions.sdlGpuRequested()) {
if (SDLGPUGate.isSDLGPUAvailable()) {
LOGGER.info("SDL GPU window mode enabled");
} else {
@@ -153,6 +163,7 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
narrowTransformerConfig("FiskHeroes", AngelicaConfig.transformerCompat.narrowFiskHeroes);
narrowTransformerConfig("FoamFix", AngelicaConfig.transformerCompat.narrowFoamFix);
narrowTransformerConfig("LegendsMod", AngelicaConfig.transformerCompat.narrowLegendsMod);
narrowTransformerConfig("PowerConverters", AngelicaConfig.transformerCompat.narrowPowerConverters);
tweaks.add("com.gtnewhorizons.angelica.loading.fml.tweakers.IncompatibleModsDisablerTweaker");
if (AngelicaConfig.enableHudCaching) {
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.CompatConfig;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.backend.BackendOptions;
import com.gtnewhorizons.angelica.glsm.profiling.TracyOptions;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
import jss.notfine.config.MCPatcherForgeConfig;
@@ -104,7 +105,7 @@ public enum Mixins implements IMixins {
ANGELICA_SDL_GPU_DISPLAY(new MixinBuilder("SDL-GPU-aware Display.create path")
.setPhase(Phase.EARLY)
.setApplyIf(() -> SystemProperties.USE_SDL_GPU && SDLGPUGate.isSDLGPUAvailable())
.setApplyIf(() -> BackendOptions.sdlGpuRequested() && SDLGPUGate.isSDLGPUAvailable())
.addClientMixins(
"sdlgpu.MixinForgeHooksClient_SDLGPUDisplay",
"sdlgpu.MixinMinecraft_SDLGPUIcons"
@@ -4,7 +4,6 @@ import com.gtnewhorizons.angelica.debug.DebugKeyHandler;
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.Locale;
@@ -41,7 +40,6 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizons.angelica.client.font.DarkModeUtils;
import com.gtnewhorizons.angelica.commands.AngelicaCommand;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.Tags;
import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.common.BlockIsbrhTest;
import com.gtnewhorizons.angelica.compat.ModStatus;
@@ -62,11 +60,12 @@ import com.gtnewhorizons.angelica.debug.TPSGraph;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendStartGuard;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
import com.gtnewhorizons.angelica.iris.IrisGLSMBridge;
import com.gtnewhorizons.angelica.iris.ShaderWarmup;
import com.gtnewhorizons.angelica.mixins.interfaces.IGameSettingsExt;
import com.gtnewhorizons.angelica.render.CloudRenderer;
import com.gtnewhorizons.angelica.render.EmissiveTextureAutoloader;
@@ -83,6 +82,7 @@ import com.gtnewhorizons.angelica.rendering.celeritas.threading.ThreadedChunkTas
import com.gtnewhorizons.angelica.rendering.items.BlockRenderListManager;
import com.gtnewhorizons.angelica.rendering.items.ItemRenderListManager;
import com.gtnewhorizons.angelica.rendering.tesr.TesrLifecycle;
import com.gtnewhorizons.angelica.utils.AngelicaJar;
import com.gtnewhorizons.angelica.utils.AnimationMode;
import com.gtnewhorizons.angelica.utils.ManagedEnum;
import com.gtnewhorizons.angelica.zoom.Zoom;
@@ -97,13 +97,10 @@ import cpw.mods.fml.common.gameevent.TickEvent;
import cpw.mods.fml.common.registry.GameData;
import cpw.mods.fml.relauncher.ReflectionHelper;
import jss.notfine.core.Settings;
import jss.notfine.gui.GuiCustomMenu;
import jss.notfine.gui.NotFineGameOptionPages;
import jss.notfine.gui.options.named.FOVMode;
import me.flashyreese.mods.reeses_sodium_options.client.gui.ReeseSodiumVideoOptionsScreen;
import me.jellysquid.mods.sodium.client.gui.FrameRateOptions;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.SodiumOptionsGUI;
import net.coderbot.iris.Iris;
import net.coderbot.iris.client.IrisDebugScreenHandler;
@@ -151,11 +148,7 @@ public final class ClientProxy extends CommonProxy {
BlockIsbrhTest.renderId = RenderingRegistry.getNextAvailableRenderId();
RenderingRegistry.registerBlockHandler(BlockIsbrhTest.renderId, new IsbrhTestRenderer());
}
final File source = event.getSourceFile();
if (source != null && source.isFile()) {
ShaderDiskCache.configure(Minecraft.getMinecraft().mcDataDir.toPath().resolve("angelica").resolve("shadercache"),
Tags.VERSION + "|" + source.length() + "|" + source.lastModified());
}
AngelicaJar.configureShaderDiskCache(event.getSourceFile());
if (AngelicaConfig.enableIris) {
IrisGLSMBridge.installImmediateExtendedHandler();
Iris.warmupShaderTransforms();
@@ -388,10 +381,12 @@ public final class ClientProxy extends CommonProxy {
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onGuiOpen(GuiOpenEvent event) {
BackendStartGuard.finish();
if (!event.isCanceled()) {
if (event.gui instanceof GuiMainMenu && gameStartTime == -1) {
gameStartTime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
LOGGER.info("The game loaded in {} seconds.", gameStartTime);
if (AngelicaConfig.enableIris) ShaderWarmup.start();
}
// force reset zoom when a GUI is opened
@@ -403,19 +398,7 @@ public final class ClientProxy extends CommonProxy {
public void onGuiInit(GuiScreenEvent.InitGuiEvent.Pre event) {
if (event.gui instanceof GuiVideoSettings eventGui) {
event.setCanceled(true);
if (AngelicaConfig.enableNotFineOptions || GuiScreen.isShiftKeyDown()) {
Minecraft.getMinecraft().displayGuiScreen(new GuiCustomMenu(
eventGui.parentGuiScreen,
NotFineGameOptionPages.general(),
NotFineGameOptionPages.detail(),
NotFineGameOptionPages.atmosphere(),
NotFineGameOptionPages.particles(),
NotFineGameOptionPages.other()));
} else if (!AngelicaConfig.enableReesesSodiumOptions || GuiScreen.isCtrlKeyDown()) {
Minecraft.getMinecraft().displayGuiScreen(new SodiumOptionsGUI(eventGui.parentGuiScreen));
} else {
Minecraft.getMinecraft().displayGuiScreen(new ReeseSodiumVideoOptionsScreen(eventGui.parentGuiScreen));
}
Minecraft.getMinecraft().displayGuiScreen(new ReeseSodiumVideoOptionsScreen(eventGui.parentGuiScreen));
}
}
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica.rendering.culling;
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import java.nio.ByteBuffer;
@@ -10,9 +12,11 @@ public final class FrustumExtractor {
public static final int UBO_SIZE_BYTES = 160;
public static final int PLANE_COUNT = 6;
private static final float SECTION_HALF_EXTENT = 8.0f + 1.0f + 0.125f;
private static final float SECTION_CENTER = 8.0f;
private static final int PLANE_STRIDE = 16;
private static final int CONTROL_OFFSET = 96;
private static final int CAMERA_WORLD_OFFSET = 112;
private static final int CAMERA_BLOCK_OFFSET = 112;
private static final int BATCH_OFFSET = 128;
private static final int PYR_OFFSET = 144;
@@ -22,24 +26,26 @@ public final class FrustumExtractor {
return ByteBuffer.allocateDirect(UBO_SIZE_BYTES).order(ByteOrder.nativeOrder());
}
public static void writeStd140(Matrix4fc mvp, int visibleCount, int indexPointerMask, ByteBuffer out) {
public static void writeFrustum(Matrix4fc projection, Matrix4fc modelView, CameraTransform camera, Matrix4f mvp, ByteBuffer out) {
projection.mul(modelView, mvp);
final float r0x = mvp.m00(), r0y = mvp.m10(), r0z = mvp.m20(), r0w = mvp.m30();
final float r1x = mvp.m01(), r1y = mvp.m11(), r1z = mvp.m21(), r1w = mvp.m31();
final float r2x = mvp.m02(), r2y = mvp.m12(), r2z = mvp.m22(), r2w = mvp.m32();
final float r3x = mvp.m03(), r3y = mvp.m13(), r3z = mvp.m23(), r3w = mvp.m33();
final float cx = SECTION_CENTER - camera.fracX;
final float cy = SECTION_CENTER - camera.fracY;
final float cz = SECTION_CENTER - camera.fracZ;
writePlane(out, 0 * PLANE_STRIDE, r3x + r0x, r3y + r0y, r3z + r0z, r3w + r0w);
writePlane(out, 1 * PLANE_STRIDE, r3x - r0x, r3y - r0y, r3z - r0z, r3w - r0w);
writePlane(out, 2 * PLANE_STRIDE, r3x + r1x, r3y + r1y, r3z + r1z, r3w + r1w);
writePlane(out, 3 * PLANE_STRIDE, r3x - r1x, r3y - r1y, r3z - r1z, r3w - r1w);
writePlane(out, 4 * PLANE_STRIDE, r3x + r2x, r3y + r2y, r3z + r2z, r3w + r2w);
writePlane(out, 5 * PLANE_STRIDE, r3x - r2x, r3y - r2y, r3z - r2z, r3w - r2w);
writePlane(out, 0 * PLANE_STRIDE, r3x + r0x, r3y + r0y, r3z + r0z, r3w + r0w, cx, cy, cz);
writePlane(out, 1 * PLANE_STRIDE, r3x - r0x, r3y - r0y, r3z - r0z, r3w - r0w, cx, cy, cz);
writePlane(out, 2 * PLANE_STRIDE, r3x + r1x, r3y + r1y, r3z + r1z, r3w + r1w, cx, cy, cz);
writePlane(out, 3 * PLANE_STRIDE, r3x - r1x, r3y - r1y, r3z - r1z, r3w - r1w, cx, cy, cz);
writePlane(out, 4 * PLANE_STRIDE, r3x + r2x, r3y + r2y, r3z + r2z, r3w + r2w, cx, cy, cz);
writePlane(out, 5 * PLANE_STRIDE, r3x - r2x, r3y - r2y, r3z - r2z, r3w - r2w, cx, cy, cz);
out.putInt(CONTROL_OFFSET + 0, visibleCount);
out.putInt(CONTROL_OFFSET + 4, indexPointerMask);
out.putInt(CONTROL_OFFSET + 8, 0);
out.putInt(CONTROL_OFFSET + 12, 0);
out.putInt(BATCH_OFFSET, 0);
out.putInt(CAMERA_BLOCK_OFFSET + 0, camera.intX);
out.putInt(CAMERA_BLOCK_OFFSET + 4, camera.intY);
out.putInt(CAMERA_BLOCK_OFFSET + 8, camera.intZ);
}
public static void patchBatchEntryBase(int entryBase, ByteBuffer out) {
@@ -61,17 +67,10 @@ public final class FrustumExtractor {
out.putInt(CONTROL_OFFSET + 8, bypass ? 1 : 0);
}
public static void patchCameraWorld(float x, float y, float z, ByteBuffer out) {
out.putFloat(CAMERA_WORLD_OFFSET + 0, x);
out.putFloat(CAMERA_WORLD_OFFSET + 4, y);
out.putFloat(CAMERA_WORLD_OFFSET + 8, z);
}
private static void writePlane(ByteBuffer out, int offset, float a, float b, float c, float d) {
final float invLen = 1.0f / (float) Math.sqrt(a * a + b * b + c * c);
out.putFloat(offset + 0, a * invLen);
out.putFloat(offset + 4, b * invLen);
out.putFloat(offset + 8, c * invLen);
out.putFloat(offset + 12, d * invLen);
private static void writePlane(ByteBuffer out, int offset, float a, float b, float c, float d, float cx, float cy, float cz) {
out.putFloat(offset + 0, a);
out.putFloat(offset + 4, b);
out.putFloat(offset + 8, c);
out.putFloat(offset + 12, d + SECTION_HALF_EXTENT * (Math.abs(a) + Math.abs(b) + Math.abs(c)) + a * cx + b * cy + c * cz);
}
}
@@ -28,7 +28,6 @@ import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL40;
@@ -132,7 +131,6 @@ public final class GpuTerrainCuller {
private ByteBuffer uboBytes;
private Matrix4f mvp;
private Matrix4f mvNoTranslation;
private int passOutputBase;
private ChunkPrimitiveType walkPrimitiveType;
@@ -399,17 +397,8 @@ public final class GpuTerrainCuller {
uboBytes = FrustumExtractor.allocateUboByteBuffer();
}
final boolean shadow = ShadowRenderer.ACTIVE;
final Matrix4fc proj = matrices.projection();
final Matrix4fc mv = matrices.modelView();
if (mvp == null) mvp = new Matrix4f();
if (mvNoTranslation == null) mvNoTranslation = new Matrix4f();
mvNoTranslation.set(mv).setTranslation(0f, 0f, 0f);
proj.mul(mvNoTranslation, mvp);
final float camX = (float) camera.intX + camera.fracX;
final float camY = (float) camera.intY + camera.fracY;
final float camZ = (float) camera.intZ + camera.fracZ;
FrustumExtractor.writeStd140(mvp, 0, 0, uboBytes);
FrustumExtractor.patchCameraWorld(camX, camY, camZ, uboBytes);
FrustumExtractor.writeFrustum(matrices.projection(), matrices.modelView(), camera, mvp, uboBytes);
FrustumExtractor.patchBypassFrustum(shadow, uboBytes);
frustumUboBytes = uboBytes;
@@ -459,7 +448,7 @@ public final class GpuTerrainCuller {
}
if (computeActiveThisPass) {
prepareFrustumUbo(matrices, camera);
if (!chained) prepareFrustumUbo(matrices, camera);
syncSectionMetaIfDirty();
walkPass(renderLists, AngelicaRenderPassConfiguration.TRANSLUCENT_PASS, occlusionCamera, useBlockFaceCulling);
@@ -0,0 +1,36 @@
package com.gtnewhorizons.angelica.utils;
import com.gtnewhorizons.angelica.Tags;
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
import net.minecraft.client.Minecraft;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
public final class AngelicaJar {
private AngelicaJar() {}
/** The jar or classes directory Angelica was loaded from, or null if something really went wrong. */
@Nullable
public static Path location() {
try {
String location = AngelicaJar.class.getProtectionDomain().getCodeSource().getLocation().toString();
if (location.startsWith("jar:")) {
final int separator = location.indexOf("!/");
location = location.substring("jar:".length(), separator < 0 ? location.length() : separator);
}
return Paths.get(new URI(location));
} catch (Exception e) {
return null;
}
}
public static void configureShaderDiskCache(@Nullable File jar) {
if (jar == null || !jar.isFile()) return;
ShaderDiskCache.configure(Minecraft.getMinecraft().mcDataDir.toPath().resolve("angelica").resolve("shadercache"),
Tags.VERSION + "|" + jar.length() + "|" + jar.lastModified());
}
}
@@ -1,107 +0,0 @@
package jss.notfine.gui;
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
import me.jellysquid.mods.sodium.client.gui.options.storage.OptionStorage;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.resources.I18n;
import org.lwjgl.input.Keyboard;
import java.util.HashSet;
public class GuiCustomMenu extends GuiScreen {
public static GuiCustomMenu instance;
public static HashSet<OptionStorage<?>> dirtyStorages = new HashSet<>();
private final GuiScreen parentGuiScreen;
private final OptionPage optionPage;
private final OptionPage[] subPages;
protected String screenTitle;
private GuiCustomOptionsRowList optionsRowList;
public GuiCustomMenu(GuiScreen parentGuiScreen, OptionPage optionPage, OptionPage... subPages) {
this.parentGuiScreen = parentGuiScreen;
this.screenTitle = optionPage.getName();
this.optionPage = optionPage;
this.subPages = subPages;
}
@Override
public void initGui() {
Keyboard.enableRepeatEvents(true);
buttonList.clear();
buttonList.add(new GuiButton(200, width / 2 - 110, height - 27, I18n.format("gui.done")));
optionsRowList = new GuiCustomOptionsRowList(mc, width, height, 32, height - 32, 25, optionPage, subPages);
instance = this;
}
@Override
protected void actionPerformed(GuiButton button) {
if(button.enabled && button.id == 200) {
if(!(parentGuiScreen instanceof GuiCustomMenu)) {
saveChanges();
}
mc.displayGuiScreen(parentGuiScreen);
}
}
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
int originalScale = mc.gameSettings.guiScale;
super.mouseClicked(mouseX, mouseY, mouseButton);
optionsRowList.func_148179_a(mouseX, mouseY, mouseButton);
if(mc.gameSettings.guiScale != originalScale) {
ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
setWorldAndResolution(mc, scaledresolution.getScaledWidth(), scaledresolution.getScaledHeight());
}
}
@Override
protected void mouseMovedOrUp(int mouseX, int mouseY, int state) {
int originalScale = mc.gameSettings.guiScale;
super.mouseMovedOrUp(mouseX, mouseY, state);
optionsRowList.func_148181_b(mouseX, mouseY, state);
if(mc.gameSettings.guiScale != originalScale) {
ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
setWorldAndResolution(mc, scaledresolution.getScaledWidth(), scaledresolution.getScaledHeight());
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
if(mc.theWorld != null && Keyboard.isKeyDown(Keyboard.KEY_F1)) {
return;
}
drawDefaultBackground();
optionsRowList.drawScreen(mouseX, mouseY, partialTicks);
drawCenteredString(fontRendererObj, screenTitle, width / 2, 5, 16777215);
super.drawScreen(mouseX, mouseY, partialTicks);
}
@Override
protected void keyTyped(char typedChar, int keyCode) {
if(keyCode == Keyboard.KEY_ESCAPE && !(parentGuiScreen instanceof GuiCustomMenu)) {
saveChanges();
}
optionsRowList.keyTyped(keyCode);
super.keyTyped(typedChar, keyCode);
}
@Override
public void onGuiClosed() {
Keyboard.enableRepeatEvents(false);
}
private void saveChanges() {
for(OptionStorage<?> storage : dirtyStorages) {
storage.save();
}
dirtyStorages = new HashSet<>();
}
}
@@ -1,31 +0,0 @@
package jss.notfine.gui;
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
public class GuiCustomMenuButton extends GuiButton {
private final OptionPage optionPage;
private final OptionPage[] subPages;
public GuiCustomMenuButton(int xPosition, int yPosition, int width, int height, OptionPage optionPage, OptionPage... subPages) {
super(-10, xPosition, yPosition, width, height, optionPage.getName());
this.optionPage = optionPage;
this.subPages = subPages;
}
public GuiCustomMenuButton(int xPosition, int yPosition, OptionPage optionPage, OptionPage... subPages) {
this(xPosition, yPosition, 150, 20, optionPage, subPages);
}
@Override
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) {
boolean load = super.mousePressed(mc, mouseX, mouseY);
if(load) {
mc.displayGuiScreen(new GuiCustomMenu(mc.currentScreen, optionPage, subPages));
}
return load;
}
}
@@ -1,126 +0,0 @@
package jss.notfine.gui;
import com.google.common.collect.Lists;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import jss.notfine.gui.options.control.element.NotFineControlElementFactory;
import jss.notfine.gui.options.control.element.SliderControlElement;
import me.jellysquid.mods.sodium.client.gui.options.Option;
import me.jellysquid.mods.sodium.client.gui.options.OptionGroup;
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
import me.jellysquid.mods.sodium.client.util.Dim2i;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiListExtended;
import net.minecraft.client.renderer.Tessellator;
import java.util.Iterator;
import java.util.List;
@SideOnly(Side.CLIENT)
public class GuiCustomOptionsRowList extends GuiListExtended {
private static final NotFineControlElementFactory factory = new NotFineControlElementFactory();
private final List<Row> settingsList = Lists.newArrayList();
private GuiButton focusedButton;
public GuiCustomOptionsRowList(Minecraft mc, int width, int height, int top, int bottom, int slotHeight, OptionPage optionPage, OptionPage... subPages) {
super(mc, width, height, top, bottom, slotHeight);
field_148163_i = false;
for(OptionGroup optionGroup : optionPage.getGroups()) {
Iterator<Option<?>> settings = optionGroup.getOptions().stream().iterator();
while(settings.hasNext()) {
Option<?> optionOne = settings.next();
Option<?> optionTwo = settings.hasNext() ? settings.next() : null;
GuiButton buttonOne = (GuiButton)optionOne.getControl().createElement(new Dim2i(width / 2 - 155,0,150,20), factory);
GuiButton buttonTwo = optionTwo == null ? null : (GuiButton)optionTwo.getControl().createElement(new Dim2i(width / 2 - 155 + 160,0,150,20), factory);
settingsList.add(new GuiCustomOptionsRowList.Row(buttonOne, buttonTwo));
}
}
for(int i = 0; i < subPages.length; i += 2) {
OptionPage pageOne = subPages[i];
OptionPage pageTwo = i < subPages.length - 1 ? subPages[i + 1] : null;
GuiButton buttonOne = new GuiCustomMenuButton(width / 2 - 155, 0, pageOne);
GuiButton buttonTwo = pageTwo == null ? null : new GuiCustomMenuButton(width / 2 - 155 + 160, 0, pageTwo);
settingsList.add(new Row(buttonOne, buttonTwo));
}
}
@Override
public GuiCustomOptionsRowList.Row getListEntry(int index) {
return settingsList.get(index);
}
@Override
protected int getSize() {
return settingsList.size();
}
@Override
public int getListWidth() {
return 400;
}
@Override
protected int getScrollBarX() {
return super.getScrollBarX() + 32;
}
public void keyTyped(int keyCode) {
if(focusedButton instanceof SliderControlElement slider) {
slider.keyTyped(keyCode);
}
}
@SideOnly(Side.CLIENT)
public class Row implements GuiListExtended.IGuiListEntry {
private final Minecraft mc = Minecraft.getMinecraft();
private final GuiButton buttonOne, buttonTwo;
public Row(GuiButton one, GuiButton two) {
buttonOne = one;
buttonTwo = two;
if(one != null && two == null) {
one.width += 160;
}
}
@Override
public void drawEntry(int varU1, int x, int y, int varU2, int varU3, Tessellator tessellator, int mouseX, int mouseY, boolean varU4) {
buttonOne.yPosition = y;
buttonOne.drawButton(mc, mouseX, mouseY);
if(buttonTwo != null) {
buttonTwo.yPosition = y;
buttonTwo.drawButton(mc, mouseX, mouseY);
}
}
@Override
public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) {
if(buttonOne.mousePressed(mc, x, y)) {
focusedButton = buttonOne;
return true;
}
if(buttonTwo != null && buttonTwo.mousePressed(mc, x, y)) {
focusedButton = buttonTwo;
return true;
}
return false;
}
@Override
public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) {
if(buttonOne != null) {
buttonOne.mouseReleased(x, y);
}
if(buttonTwo != null) {
buttonTwo.mouseReleased(x, y);
}
}
}
}
@@ -1,389 +0,0 @@
package jss.notfine.gui;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import jss.notfine.config.NotFineConfig;
import jss.notfine.core.Settings;
import jss.notfine.core.SettingsManager;
import me.jellysquid.mods.sodium.client.gui.FrameRateOptions;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptionPages;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.options.OptionFlag;
import me.jellysquid.mods.sodium.client.gui.options.OptionGroup;
import me.jellysquid.mods.sodium.client.gui.options.OptionImpact;
import me.jellysquid.mods.sodium.client.gui.options.OptionImpl;
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
import me.jellysquid.mods.sodium.client.gui.options.control.ControlValueFormatter;
import me.jellysquid.mods.sodium.client.gui.options.control.CyclingControl;
import me.jellysquid.mods.sodium.client.gui.options.control.SliderControl;
import me.jellysquid.mods.sodium.client.gui.options.control.TickBoxControl;
import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsMode;
import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsQuality;
import me.jellysquid.mods.sodium.client.gui.options.named.LightingQuality;
import me.jellysquid.mods.sodium.client.gui.options.named.ParticleMode;
import me.jellysquid.mods.sodium.client.gui.options.named.TextureFilterMode;
import me.jellysquid.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage;
import me.jellysquid.mods.sodium.client.gui.options.storage.SodiumOptionsStorage;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gui.option.IrisVideoSettings;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.settings.GameSettings;
import org.lwjgl.opengl.Display;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class NotFineGameOptionPages {
private static final MinecraftOptionsStorage vanillaOpts = new MinecraftOptionsStorage();
private static final SodiumOptionsStorage sodiumOpts = new SodiumOptionsStorage();
public static OptionPage general() {
List<OptionGroup> groups = new ArrayList<>();
final FrameRateOptions frameRate = FrameRateOptions.create(vanillaOpts, sodiumOpts);
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(GraphicsMode.class, vanillaOpts)
.setName(I18n.format("options.graphics"))
.setTooltip(I18n.format("sodium.options.graphics_quality.tooltip"))
.setControl(option -> new CyclingControl<>(option, GraphicsMode.class))
.setBinding((opts, value) -> {
opts.fancyGraphics = value.isFancy();
SettingsManager.graphicsUpdated();
}, opts -> GraphicsMode.fromBoolean(opts.fancyGraphics))
.setImpact(OptionImpact.HIGH)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.add(OptionImpl.createBuilder(int.class, vanillaOpts)
.setName(I18n.format("options.renderDistance"))
.setTooltip(I18n.format("sodium.options.view_distance.tooltip"))
.setControl(option -> new SliderControl(option, 2, (int) GameSettings.Options.RENDER_DISTANCE.getValueMax(), 1, ControlValueFormatter.quantity("options.chunks")))
.setBinding((options, value) -> options.renderDistanceChunks = value, options -> options.renderDistanceChunks)
.setImpact(OptionImpact.HIGH)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.add(frameRate.vsync())
.add(OptionImpl.createBuilder(boolean.class, vanillaOpts)
.setName(I18n.format("options.fullscreen"))
.setTooltip(I18n.format("sodium.options.fullscreen.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> {
opts.fullScreen = value;
Minecraft client = Minecraft.getMinecraft();
if (client.isFullScreen() != opts.fullScreen) {
client.toggleFullscreen();
//The client might not be able to enter full-screen mode
opts.fullScreen = client.isFullScreen();
}
}, (opts) -> opts.fullScreen)
.build())
.build());
groups.add(OptionGroup.createBuilder()
.add(frameRate.maxFramerate())
.build());
final OptionImpl<GameSettings, Integer> mipmapLevels = OptionImpl.createBuilder(int.class, vanillaOpts)
.setName(I18n.format("options.mipmapLevels"))
.setTooltip(I18n.format("sodium.options.mipmap_levels.tooltip"))
.setControl(option -> new SliderControl(option, 0, 4, 1, ControlValueFormatter.multiplier()))
.setBinding((opts, value) -> opts.mipmapLevels = value, opts -> opts.mipmapLevels)
.setImpact(OptionImpact.MEDIUM)
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD)
.build();
final OptionImpl<SodiumGameOptions, TextureFilterMode> textureFilterMode =
OptionImpl.createBuilder(TextureFilterMode.class, sodiumOpts)
.setName(I18n.format("sodium.options.texture_filtering.name"))
.setTooltip(I18n.format("sodium.options.texture_filtering.tooltip"))
.setControl(option -> new CyclingControl<>(option, TextureFilterMode.class,
TextureFilterMode.selectableValues(SodiumGameOptions.anisotropySupported())))
.setBinding((opts, value) -> opts.quality.textureFilterMode = value,
opts -> SodiumGameOptions.resolveFilterMode(opts.quality.textureFilterMode))
.setImpact(OptionImpact.MEDIUM)
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD, OptionFlag.REQUIRES_RENDERER_RELOAD)
.build();
int maxGuiScale = Math.max(3, Math.min(Minecraft.getMinecraft().displayWidth / 320, Minecraft.getMinecraft().displayHeight / 240));
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(int.class, vanillaOpts)
.setName(I18n.format("options.guiScale"))
.setTooltip(I18n.format("sodium.options.gui_scale.tooltip"))
.setControl(option -> new SliderControl(option, 0, maxGuiScale, 1, ControlValueFormatter.guiScale()))
.setBinding((opts, value) -> opts.guiScale = value, opts -> opts.guiScale)
.build())
.add(OptionImpl.createBuilder(boolean.class, vanillaOpts)
.setName(I18n.format("options.viewBobbing"))
.setTooltip(I18n.format("sodium.options.view_bobbing.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.viewBobbing = value, opts -> opts.viewBobbing)
.build())
.add(OptionImpl.createBuilder(LightingQuality.class, vanillaOpts)
.setName(I18n.format("options.ao"))
.setTooltip(I18n.format("sodium.options.smooth_lighting.tooltip"))
.setControl(option -> new CyclingControl<>(option, LightingQuality.class))
.setBinding((opts, value) -> opts.ambientOcclusion = value.getVanilla(), opts -> LightingQuality.fromOrdinal(opts.ambientOcclusion))
.setImpact(OptionImpact.MEDIUM)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.add(OptionImpl.createBuilder(int.class, vanillaOpts)
.setName(I18n.format("options.gamma"))
.setTooltip(I18n.format("sodium.options.brightness.tooltip"))
.setControl(opt -> new SliderControl(opt, 0, 100, 1, ControlValueFormatter.brightness()))
.setBinding((opts, value) -> opts.gammaSetting = value * 0.01F, (opts) -> (int) (opts.gammaSetting / 0.01F))
.build())
.add(Settings.MODE_LIGHT_FLICKER.option)
.add(Settings.DYNAMIC_FOV.option)
.add(mipmapLevels)
.add(textureFilterMode)
.add(SodiumGameOptionPages.anisotropicFilteringSlider(vanillaOpts, textureFilterMode::getValue),
SodiumGameOptions.anisotropySupported())
.build());
if (Iris.enabled) {
final OptionImpl<GameSettings, Integer> maxShadowDistanceSlider = OptionImpl.createBuilder(int.class, vanillaOpts)
.setName(I18n.format("options.iris.shadowDistance"))
.setTooltip(I18n.format("options.iris.shadowDistance.sodium_tooltip"))
.setControl(option -> new SliderControl(option, 0, 32, 1, ControlValueFormatter.quantity("options.chunks")))
.setBinding((options, value) -> {
IrisVideoSettings.shadowDistance = value;
try {
Iris.getIrisConfig().save();
} catch (IOException e) {
e.printStackTrace();
}
},
options -> IrisVideoSettings.getOverriddenShadowDistance(IrisVideoSettings.shadowDistance))
.setImpact(OptionImpact.HIGH)
.setEnabled(true)
.build();
maxShadowDistanceSlider.iris$dynamicallyEnable(IrisVideoSettings::isShadowDistanceSliderEnabled);
groups.add(OptionGroup.createBuilder()
.add(maxShadowDistanceSlider)
.build());
}
if (AngelicaConfig.enableZoom) {
groups.add(OptionGroup.createBuilder()
.add(Settings.ZOOM_SMOOTH.option)
.add(Settings.ZOOM_SMOOTH_SPEED.option)
.add(Settings.ZOOM_CINEMATIC.option)
.build());
}
return new OptionPage(I18n.format("options.video"), ImmutableList.copyOf(groups));
}
public static OptionPage detail() {
List<OptionGroup> groups = new ArrayList<>();
groups.add(OptionGroup.createBuilder()
.add(Settings.MODE_LEAVES.option)
.add(OptionImpl.createBuilder(GraphicsQuality.class, sodiumOpts)
.setName(I18n.format("sodium.options.grass_quality.name"))
.setTooltip(I18n.format("sodium.options.grass_quality.tooltip"))
.setControl(option -> new CyclingControl<>(option, GraphicsQuality.class))
.setBinding((opts, value) -> opts.quality.grassQuality = value, opts -> opts.quality.grassQuality)
.setImpact(OptionImpact.MEDIUM)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.add(Settings.MODE_WATER.option)
.add(Settings.DOWNFALL_DISTANCE.option)
.add(Settings.MODE_VIGNETTE.option)
.add(Settings.MODE_SHADOWS.option)
.add(Settings.VOID_FOG.option)
.add(Settings.MODE_DROPPED_ITEMS.option)
.add(Settings.MODE_GLINT_WORLD.option)
.add(Settings.MODE_GLINT_INV.option)
.build());
return new OptionPage(I18n.format("options.button.detail"), ImmutableList.copyOf(groups));
}
public static OptionPage atmosphere() {
List<OptionGroup> groups = new ArrayList<>();
groups.add(OptionGroup.createBuilder()
.add(Settings.MODE_SKY.option)
.add(Settings.MODE_SUN_MOON.option)
.add(Settings.MODE_CLOUDS.option)
.add(Settings.RENDER_DISTANCE_CLOUDS.option)
.add(Settings.CLOUD_HEIGHT.option)
.add(Settings.CLOUD_SCALE.option)
.add(Settings.MODE_CLOUD_TRANSLUCENCY.option)
.add(Settings.MODE_STARS.option)
.add(Settings.TOTAL_STARS.option)
.add(Settings.HORIZON.option)
.add(Settings.TERRAIN_FOG.option)
.add(Settings.FOG_NEAR_DISTANCE.option)
.build());
return new OptionPage(I18n.format("options.button.sky"), ImmutableList.copyOf(groups));
}
public static OptionPage particles() {
List<OptionGroup> groups = new ArrayList<>();
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(ParticleMode.class, vanillaOpts)
.setName(I18n.format("options.particles"))
.setTooltip(I18n.format("sodium.options.particle_quality.tooltip"))
.setControl(opt -> new CyclingControl<>(opt, ParticleMode.class))
.setBinding((opts, value) -> opts.particleSetting = value.ordinal(), (opts) -> ParticleMode.fromOrdinal(opts.particleSetting))
.setImpact(OptionImpact.LOW)
.build())
.add(Settings.PARTICLES_VOID.option)
.add(Settings.PARTICLES_ENC_TABLE.option)
.build());
return new OptionPage(I18n.format("options.button.particle"), ImmutableList.copyOf(groups));
}
public static OptionPage other() {
List<OptionGroup> groups = new ArrayList<>();
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, vanillaOpts)
.setName(I18n.format("options.anaglyph"))
.setTooltip(I18n.format("sodium.options.anaglyph.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.anaglyph = value, opts -> opts.anaglyph)
.setImpact(OptionImpact.HIGH)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.setEnabled(NotFineConfig.allowToggle3DAnaglyph)
.build())
.add(OptionImpl.createBuilder(boolean.class, vanillaOpts)
.setName(I18n.format("options.showCape"))
.setTooltip(I18n.format("sodium.options.show_cape.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.showCape = value, opts -> opts.showCape)
.build())
.add(Settings.MODE_GUI_BACKGROUND.option)
.build());
groups.add(OptionGroup.createBuilder()
.add(Settings.GUI_BACKGROUND.option)
.build());
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.use_chunk_multidraw.name"))
.setTooltip(I18n.format("sodium.options.use_chunk_multidraw.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.advanced.useChunkMultidraw = value, opts -> opts.advanced.useChunkMultidraw)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.setImpact(OptionImpact.EXTREME)
.setEnabled(true)
.build())
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.use_vertex_objects.name"))
.setTooltip(I18n.format("sodium.options.use_vertex_objects.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.advanced.useVertexArrayObjects = value, opts -> opts.advanced.useVertexArrayObjects)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.setImpact(OptionImpact.LOW)
.build())
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.use_block_face_culling.name"))
.setTooltip(I18n.format("sodium.options.use_block_face_culling.tooltip"))
.setControl(TickBoxControl::new)
.setImpact(OptionImpact.MEDIUM)
.setBinding((opts, value) -> opts.performance.useBlockFaceCulling = value, opts -> opts.performance.useBlockFaceCulling)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.use_compact_vertex_format.name"))
.setTooltip(I18n.format("sodium.options.use_compact_vertex_format.tooltip"))
.setControl(TickBoxControl::new)
.setImpact(OptionImpact.MEDIUM)
.setBinding((opts, value) -> opts.performance.useCompactVertexFormat = value, opts -> opts.performance.useCompactVertexFormat)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.use_fog_occlusion.name"))
.setTooltip(I18n.format("sodium.options.use_fog_occlusion.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.performance.useFogOcclusion = value, opts -> opts.performance.useFogOcclusion)
.setImpact(OptionImpact.MEDIUM)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.translucency_sorting.name"))
.setTooltip(I18n.format("sodium.options.translucency_sorting.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.performance.translucencySorting = value, opts -> opts.performance.translucencySorting)
.setImpact(OptionImpact.MEDIUM)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.use_entity_culling.name"))
.setTooltip(I18n.format("sodium.options.use_entity_culling.tooltip"))
.setControl(TickBoxControl::new)
.setImpact(OptionImpact.MEDIUM)
.setBinding((opts, value) -> opts.performance.useEntityCulling = value, opts -> opts.performance.useEntityCulling)
.build())
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.use_particle_culling.name"))
.setTooltip(I18n.format("sodium.options.use_particle_culling.tooltip"))
.setControl(TickBoxControl::new)
.setImpact(OptionImpact.MEDIUM)
.setBinding((opts, value) -> opts.advanced.useParticleCulling = value, opts -> opts.advanced.useParticleCulling)
.build())
.build());
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.animate_only_visible_textures.name"))
.setTooltip(I18n.format("sodium.options.animate_only_visible_textures.tooltip"))
.setControl(TickBoxControl::new)
.setImpact(OptionImpact.MEDIUM)
.setBinding((opts, value) -> opts.performance.animateOnlyVisibleTextures = value, opts -> opts.performance.animateOnlyVisibleTextures)
.build())
.build());
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.allow_direct_memory_access.name"))
.setTooltip(I18n.format("sodium.options.allow_direct_memory_access.tooltip"))
.setControl(TickBoxControl::new)
.setImpact(OptionImpact.HIGH)
.setBinding((opts, value) -> opts.advanced.allowDirectMemoryAccess = value, opts -> opts.advanced.allowDirectMemoryAccess)
.build())
.build());
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.ignore_driver_blacklist.name"))
.setTooltip(I18n.format("sodium.options.ignore_driver_blacklist.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.advanced.ignoreDriverBlacklist = value, opts -> opts.advanced.ignoreDriverBlacklist)
.build())
.build());
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(int.class, sodiumOpts)
.setName(I18n.format("sodium.options.chunk_update_threads.name"))
.setTooltip(I18n.format("sodium.options.chunk_update_threads.tooltip"))
.setControl(o -> new SliderControl(o, 0, Runtime.getRuntime().availableProcessors(), 1, ControlValueFormatter.quantityOrDisabled("sodium.options.threads.value", "sodium.options.default")))
.setImpact(OptionImpact.HIGH)
.setBinding((opts, value) -> opts.performance.chunkBuilderThreads = value, opts -> opts.performance.chunkBuilderThreads)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.build());
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.always_defer_chunk_updates.name"))
.setTooltip(I18n.format("sodium.options.always_defer_chunk_updates.tooltip"))
.setControl(TickBoxControl::new)
.setImpact(OptionImpact.HIGH)
.setBinding((opts, value) -> opts.performance.alwaysDeferChunkUpdates = value, opts -> opts.performance.alwaysDeferChunkUpdates)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.build());
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(I18n.format("sodium.options.use_no_error_context.name"))
.setTooltip(I18n.format("sodium.options.use_no_error_context.tooltip"))
.setControl(TickBoxControl::new)
.setImpact(OptionImpact.LOW)
.setBinding((opts, value) -> opts.performance.useNoErrorGLContext = value, opts -> opts.performance.useNoErrorGLContext)
.setFlags(OptionFlag.REQUIRES_GAME_RESTART)
.build())
.build());
return new OptionPage(I18n.format("options.button.other"), ImmutableList.copyOf(groups));
}
}
@@ -1,43 +0,0 @@
package jss.notfine.gui.options.control.element;
import me.jellysquid.mods.sodium.client.gui.options.Option;
import me.jellysquid.mods.sodium.client.util.Dim2i;
import net.minecraft.client.Minecraft;
public class CyclingControlElement<T extends Enum<T>> extends NotFineControlElement<T> {
private final T[] allowedValues;
private final String[] names;
public CyclingControlElement(Option<T> option, Dim2i dim, T[] allowedValues, String[] names) {
super(option, dim);
this.allowedValues = allowedValues;
this.names = names;
}
private int currentIndex() {
for(int i = 0; i < allowedValues.length; ++i) {
if(allowedValues[i] == option.getValue()) {
return i;
}
}
return 0;
}
@Override
public String getLabel() {
Enum<T> value = option.getValue();
return super.getLabel() + formatValue(names[value.ordinal()]);
}
@Override
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) {
if(super.mousePressed(mc, mouseX, mouseY)) {
option.setValue(allowedValues[(currentIndex() + 1) % allowedValues.length]);
onOptionValueChanged();
return true;
} else {
return false;
}
}
}
@@ -1,85 +0,0 @@
package jss.notfine.gui.options.control.element;
import jss.notfine.gui.GuiCustomMenu;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.options.Option;
import me.jellysquid.mods.sodium.client.gui.options.OptionFlag;
import me.jellysquid.mods.sodium.client.gui.options.control.ControlElement;
import me.jellysquid.mods.sodium.client.util.Dim2i;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.util.EnumChatFormatting;
import java.util.Collection;
public class NotFineControlElement<T> extends GuiButton implements ControlElement<T> {
protected final Option<T> option;
protected final Dim2i dim;
public NotFineControlElement(Option<T> option, Dim2i dim) {
super(-5, dim.getOriginX(), dim.getOriginY(), dim.getWidth(), dim.getHeight(), option.getName());
this.option = option;
this.dim = dim;
enabled = option.isAvailable();
}
private static String disabledStyle() {
return String.valueOf(EnumChatFormatting.GRAY) + EnumChatFormatting.STRIKETHROUGH;
}
@Override
public void drawButton(Minecraft mc, int mouseX, int mouseY) {
displayString = getLabel();
//TODO: tooltips
//hovered
super.drawButton(mc, mouseX, mouseY);
}
public String getLabel() {
String name = option.getName();
String label;
enabled = option.isAvailable();
if (!enabled) {
label = disabledStyle() + name;
} else if (option.hasChanged()) {
label = EnumChatFormatting.ITALIC + name;
} else {
label = name;
}
label += ": ";
return label;
}
protected String formatValue(String value) {
return option.isAvailable() ? value : disabledStyle() + value;
}
protected void onOptionValueChanged() {
option.applyChanges();
Collection<OptionFlag> flags = option.getFlags();
Minecraft mc = Minecraft.getMinecraft();
if (flags.contains(OptionFlag.REQUIRES_ASSET_RELOAD)) {
SodiumGameOptions.applyAtlasSettings();
mc.refreshResources();
} else if (flags.contains(OptionFlag.REQUIRES_RENDERER_RELOAD)) {
mc.renderGlobal.loadRenderers();
}
GuiCustomMenu.dirtyStorages.add(option.getStorage());
}
@Override
public boolean isHovered() {
return false;
}
@Override
public Option<T> getOption() {
return this.option;
}
@Override
public Dim2i getDimensions() {
return this.dim;
}
}
@@ -1,26 +0,0 @@
package jss.notfine.gui.options.control.element;
import me.jellysquid.mods.sodium.client.gui.options.Option;
import me.jellysquid.mods.sodium.client.gui.options.control.ControlElement;
import me.jellysquid.mods.sodium.client.gui.options.control.ControlValueFormatter;
import me.jellysquid.mods.sodium.client.gui.options.control.element.ControlElementFactory;
import me.jellysquid.mods.sodium.client.util.Dim2i;
public class NotFineControlElementFactory implements ControlElementFactory {
@Override
public <T extends Enum<T>> ControlElement<T> cyclingControlElement(Option<T> option, Dim2i dim, T[] allowedValues, String[] names) {
return new CyclingControlElement<>(option, dim, allowedValues, names);
}
@Override
public ControlElement<Integer> sliderControlElement(Option<Integer> option, Dim2i dim, int min, int max, int interval, ControlValueFormatter formatter) {
return new SliderControlElement(option, dim, min, max, interval, formatter);
}
@Override
public ControlElement<Boolean> tickBoxElement(Option<Boolean> option, Dim2i dim) {
return new TickBoxControlElement(option, dim);
}
}
@@ -1,115 +0,0 @@
package jss.notfine.gui.options.control.element;
import me.jellysquid.mods.sodium.client.gui.options.Option;
import me.jellysquid.mods.sodium.client.gui.options.control.ControlValueFormatter;
import me.jellysquid.mods.sodium.client.util.Dim2i;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.minecraft.client.Minecraft;
import net.minecraft.util.MathHelper;
import org.lwjgl.input.Keyboard;
public class SliderControlElement extends NotFineControlElement<Integer> {
private final ControlValueFormatter formatter;
private final int min, max, interval;
private float value;
private boolean mousePressed;
private boolean dirty = false;
public SliderControlElement(Option<Integer> option, Dim2i dim, int min, int max, int interval, ControlValueFormatter formatter) {
super(option, dim);
this.min = min;
this.max = max;
this.interval = interval;
this.formatter = formatter;
value = option.getValue();
//Normalize value
value = MathHelper.clamp_float((value - min) / (max - min), 0f, 1f);
}
@Override
public String getLabel() {
return super.getLabel() + formatValue(formatter.format(option.getValue()));
}
@Override
public int getHoverState(boolean mouseOver) {
return 0;
}
@Override
protected void mouseDragged(Minecraft mc, int mouseX, int mouseY) {
if(visible) {
if(mousePressed) {
updateSlider(mouseX);
}
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
drawTexturedModalRect(xPosition + (int)(value * (float)(width - 8)), yPosition, 0, 66, 4, 20);
drawTexturedModalRect(xPosition + (int)(value * (float)(width - 8)) + 4, yPosition, 196, 66, 4, 20);
}
}
@Override
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) {
if(super.mousePressed(mc, mouseX, mouseY)) {
updateSlider(mouseX);
mousePressed = true;
return true;
} else {
return false;
}
}
@Override
public void mouseReleased(int mouseX, int mouseY) {
mousePressed = false;
if(dirty) {
onOptionValueChanged();
dirty = false;
}
}
public boolean keyTyped(int keyCode) {
if(!option.isAvailable()) {
return false;
}
final int step;
if(keyCode == Keyboard.KEY_LEFT) {
step = -interval;
} else if(keyCode == Keyboard.KEY_RIGHT) {
step = interval;
} else {
return false;
}
final int stepped = MathHelper.clamp_int(option.getValue() + step, min, max);
if(stepped != option.getValue()) {
option.setValue(stepped);
value = MathHelper.clamp_float((float)(stepped - min) / (max - min), 0f, 1f);
onOptionValueChanged();
}
return true;
}
private void updateSlider(int mouseX) {
value = (float)(mouseX - (xPosition + 4)) / (float)(width - 8);
//Clamp normalized value
value = MathHelper.clamp_float(value, 0f, 1f);
//Un-normalize value
value = min + (max - min) * MathHelper.clamp_float(value, 0f, 1f);
//Clamp value
value = MathHelper.clamp_float(value, min, max);
//Snap value
if(interval > 0) {
value = interval * (float)Math.round(value / interval);
}
//Commit value
option.setValue((int)value);
//Normalize value
value = MathHelper.clamp_float((value - min) / (max - min), 0f, 1f);
dirty = true;
}
}
@@ -1,29 +0,0 @@
package jss.notfine.gui.options.control.element;
import me.jellysquid.mods.sodium.client.gui.options.Option;
import me.jellysquid.mods.sodium.client.util.Dim2i;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.I18n;
public class TickBoxControlElement extends NotFineControlElement<Boolean> {
public TickBoxControlElement(Option<Boolean> option, Dim2i dim) {
super(option, dim);
}
@Override
public String getLabel() {
return super.getLabel() + formatValue(I18n.format(option.getValue() ? "options.on" : "options.off"));
}
@Override
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) {
if(super.mousePressed(mc, mouseX, mouseY)) {
option.setValue(!option.getValue());
onOptionValueChanged();
return true;
} else {
return false;
}
}
}
@@ -1,9 +1,15 @@
package me.flashyreese.mods.reeses_sodium_options.client.gui;
import com.gtnewhorizons.angelica.client.gui.DynamicLightsOptionPages;
import com.gtnewhorizons.angelica.client.gui.FontConfigScreen;
import com.gtnewhorizons.angelica.client.gui.RendererOptionPages;
import com.gtnewhorizons.angelica.client.gui.ScrollableGuiScreen;
import com.gtnewhorizons.angelica.client.gui.TracyOptionPages;
import com.gtnewhorizons.angelica.compat.mojang.Element;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import jss.notfine.gui.GuiCustomMenu;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.glsm.profiling.TracyOptions;
import me.flashyreese.mods.reeses_sodium_options.client.gui.frame.AbstractFrame;
import me.flashyreese.mods.reeses_sodium_options.client.gui.frame.BasicFrame;
import me.flashyreese.mods.reeses_sodium_options.client.gui.frame.components.SearchTextFieldComponent;
@@ -11,7 +17,6 @@ import me.flashyreese.mods.reeses_sodium_options.client.gui.frame.tab.Tab;
import me.flashyreese.mods.reeses_sodium_options.client.gui.frame.tab.TabFrame;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptionPages;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.SodiumOptionsGUI;
import me.jellysquid.mods.sodium.client.gui.options.Option;
import me.jellysquid.mods.sodium.client.gui.options.OptionFlag;
import me.jellysquid.mods.sodium.client.gui.options.OptionGroup;
@@ -27,13 +32,16 @@ import net.minecraft.client.resources.I18n;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.input.Keyboard;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class ReeseSodiumVideoOptionsScreen extends SodiumOptionsGUI {
public class ReeseSodiumVideoOptionsScreen extends ScrollableGuiScreen {
private static final float ASPECT_RATIO = 5f / 4f;
private static final int MINIMUM_WIDTH = 550;
@@ -46,15 +54,63 @@ public class ReeseSodiumVideoOptionsScreen extends SodiumOptionsGUI {
private static final AtomicReference<String> lastSearch = new AtomicReference<>("");
private final List<Element> children = new CopyOnWriteArrayList<>();
private final List<OptionPage> pages = new ArrayList<>();
public final GuiScreen prevScreen;
private FlatButtonWidget applyButton, closeButton, undoButton;
private boolean hasPendingChanges;
private String pendingPage;
private AbstractFrame frame;
private SearchTextFieldComponent searchTextField;
public ReeseSodiumVideoOptionsScreen(GuiScreen prevScreen) {
super(prevScreen);
this.prevScreen = prevScreen;
this.pages.add(RendererOptionPages.withLink(SodiumGameOptionPages.general(), this));
this.pages.add(RendererOptionPages.renderer());
this.pages.add(SodiumGameOptionPages.quality());
this.pages.add(SodiumGameOptionPages.advanced());
this.pages.add(SodiumGameOptionPages.performance());
this.pages.add(SodiumGameOptionPages.fpsReducer());
this.pages.add(SodiumGameOptionPages.appearance());
this.pages.add(SodiumGameOptionPages.text());
if (DynamicLights.configEnabled) {
this.pages.add(DynamicLightsOptionPages.dynamicLights());
}
if (SystemProperties.debugTooling() || TracyOptions.backendPresent()) {
this.pages.add(TracyOptionPages.tracy());
}
}
public void showPage(String name) {
this.pendingPage = name;
}
private void applyPendingPage() {
if (this.pendingPage == null) return;
lastSearch.set("");
tabFrameSelectedTab.set(this.pendingPage);
optionPageScrollBarOffset.set(0);
this.pendingPage = null;
this.rebuildGUI();
}
@Override
public List<? extends Element> children() {
return children;
}
@Override
public void onGuiClosed() {
Keyboard.enableRepeatEvents(false);
}
// Hackalicious! Rebuild UI
@Override
public void rebuildGUI() {
// Preserve search focus state across rebuilds
boolean wasSearchFocused = this.searchTextField != null && this.searchTextField.isFocused();
@@ -196,6 +252,7 @@ public class ReeseSodiumVideoOptionsScreen extends SodiumOptionsGUI {
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
this.applyPendingPage();
super.drawDefaultBackground();
handleMouseScroll(mouseX, mouseY, partialTicks);
this.updateControls();
@@ -275,14 +332,6 @@ public class ReeseSodiumVideoOptionsScreen extends SodiumOptionsGUI {
onClose();
return;
}
if (keyCode == Keyboard.KEY_P) {
if(isShiftKeyDown()){
this.mc.displayGuiScreen(new GuiCustomMenu(this.prevScreen, SodiumGameOptionPages.general(), SodiumGameOptionPages.quality(), SodiumGameOptionPages.advanced(), SodiumGameOptionPages.performance(), SodiumGameOptionPages.appearance()));
} else if (isCtrlKeyDown()) {
this.mc.displayGuiScreen(new SodiumOptionsGUI(this.prevScreen));
}
}
if(focused != null) {
focused.keyTyped(typedChar, keyCode);
}
@@ -293,6 +342,7 @@ public class ReeseSodiumVideoOptionsScreen extends SodiumOptionsGUI {
final boolean onSearchField = this.searchTextField.isMouseOver(mouseX, mouseY);
super.mouseClicked(mouseX, mouseY, mouseButton);
this.children.forEach(element -> element.mouseClicked(mouseX, mouseY, mouseButton));
if (!onSearchField) {
this.searchTextField.setFocused(false);
@@ -301,13 +351,20 @@ public class ReeseSodiumVideoOptionsScreen extends SodiumOptionsGUI {
}
@Override
protected void mouseClickMove(int mouseX, int mouseY, int mouseButton, long timeSinceLastClick) {
super.mouseClickMove(mouseX, mouseY, mouseButton, timeSinceLastClick);
this.children.forEach(element -> element.mouseDragged(mouseX, mouseY, mouseButton));
}
public boolean shouldCloseOnEsc() {
return !this.hasPendingChanges;
}
@Override
// We can't override onGuiClosed due to StackOverflow
public void onClose() {
lastSearch.set("");
super.onClose();
this.mc.displayGuiScreen(this.prevScreen);
super.onGuiClosed();
}
}
@@ -24,6 +24,7 @@ import com.gtnewhorizons.angelica.config.GpuCullingMode;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import cpw.mods.fml.common.Optional.Method;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import jss.notfine.config.NotFineConfig;
import jss.notfine.core.Settings;
import jss.notfine.core.SettingsManager;
import me.flashyreese.mods.reeses_sodium_options.client.gui.ReeseSodiumVideoOptionsScreen;
@@ -131,9 +132,6 @@ public class SodiumGameOptionPages {
if(Minecraft.getMinecraft().currentScreen instanceof ReeseSodiumVideoOptionsScreen oldGui) {
Minecraft.getMinecraft().displayGuiScreen(new ReeseSodiumVideoOptionsScreen(oldGui.prevScreen));
}
else if(Minecraft.getMinecraft().currentScreen instanceof SodiumOptionsGUI oldGui) {
Minecraft.getMinecraft().displayGuiScreen(new SodiumOptionsGUI(oldGui.prevScreen));
}
}, opts -> opts.guiScale)
.build())
.add(OptionImpl.createBuilder(boolean.class, vanillaOpts)
@@ -812,6 +810,21 @@ public class SodiumGameOptionPages {
groups.add(OptionGroup.createBuilder()
.add(Settings.MODE_GUI_BACKGROUND.option)
.add(Settings.GUI_BACKGROUND.option)
.add(OptionImpl.createBuilder(boolean.class, vanillaOpts)
.setName(I18n.format("options.showCape"))
.setTooltip(I18n.format("sodium.options.show_cape.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.showCape = value, opts -> opts.showCape)
.build())
.add(OptionImpl.createBuilder(boolean.class, vanillaOpts)
.setName(I18n.format("options.anaglyph"))
.setTooltip(I18n.format("sodium.options.anaglyph.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.anaglyph = value, opts -> opts.anaglyph)
.setImpact(OptionImpact.HIGH)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.setEnabled(NotFineConfig.allowToggle3DAnaglyph)
.build())
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName(I18n.format("options.angelica.disablef3"))
.setTooltip(I18n.format("options.angelica.disablef3.tooltip"))
@@ -204,7 +204,6 @@ public class SodiumGameOptions {
public static class AdvancedSettings {
public VSyncMode vsyncMode;
public boolean useVertexArrayObjects = true;
public boolean useChunkMultidraw = true;
public MultiDrawMode multiDrawMode = MultiDrawMode.DIRECT;
public boolean useParticleCulling = true;
@@ -1,343 +0,0 @@
package me.jellysquid.mods.sodium.client.gui;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.client.gui.DynamicLightsOptionPages;
import com.gtnewhorizons.angelica.client.gui.ScrollableGuiScreen;
import com.gtnewhorizons.angelica.client.gui.TracyOptionPages;
import com.gtnewhorizons.angelica.compat.mojang.Drawable;
import com.gtnewhorizons.angelica.compat.mojang.Element;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.glsm.profiling.TracyOptions;
import jss.notfine.gui.GuiCustomMenu;
import me.jellysquid.mods.sodium.client.gui.options.Option;
import me.jellysquid.mods.sodium.client.gui.options.OptionFlag;
import me.jellysquid.mods.sodium.client.gui.options.OptionGroup;
import me.jellysquid.mods.sodium.client.gui.options.OptionImpact;
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
import me.jellysquid.mods.sodium.client.gui.options.control.Control;
import me.jellysquid.mods.sodium.client.gui.options.control.ControlElement;
import me.jellysquid.mods.sodium.client.gui.options.control.element.SodiumControlElementFactory;
import me.jellysquid.mods.sodium.client.gui.options.storage.OptionStorage;
import me.jellysquid.mods.sodium.client.gui.widgets.FlatButtonWidget;
import me.jellysquid.mods.sodium.client.util.Dim2i;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import org.lwjgl.input.Keyboard;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Stream;
public class SodiumOptionsGUI extends ScrollableGuiScreen {
protected static final SodiumControlElementFactory elementFactory = new SodiumControlElementFactory();
protected final List<Element> children = new CopyOnWriteArrayList<>();
protected final List<OptionPage> pages = new ArrayList<>();
protected final List<ControlElement<?>> controls = new ArrayList<>();
protected final List<Drawable> drawable = new ArrayList<>();
public final GuiScreen prevScreen;
protected OptionPage currentPage;
protected FlatButtonWidget applyButton, closeButton, undoButton;
protected boolean hasPendingChanges;
protected ControlElement<?> hoveredElement;
private Element focusedElement;
protected OptionPage shaderPacks;
public SodiumOptionsGUI(GuiScreen prevScreen) {
this.prevScreen = prevScreen;
this.pages.add(SodiumGameOptionPages.general());
this.pages.add(SodiumGameOptionPages.quality());
this.pages.add(SodiumGameOptionPages.advanced());
this.pages.add(SodiumGameOptionPages.performance());
this.pages.add(SodiumGameOptionPages.fpsReducer());
this.pages.add(SodiumGameOptionPages.appearance());
this.pages.add(SodiumGameOptionPages.text());
// Add Dynamic Lights page if enabled
if (DynamicLights.configEnabled) {
this.pages.add(DynamicLightsOptionPages.dynamicLights());
}
if (SystemProperties.debugTooling() || TracyOptions.backendPresent()) {
this.pages.add(TracyOptionPages.tracy());
}
if (Iris.enabled) {
shaderPacks = new OptionPage(I18n.format("options.iris.shaderPackSelection"), ImmutableList.of());
this.pages.add(shaderPacks);
}
}
public void setPage(OptionPage page) {
if (Iris.enabled && page == shaderPacks) {
mc.displayGuiScreen(new ShaderPackScreen(this));
return;
}
this.currentPage = page;
this.rebuildGUI();
}
@Override
public void initGui() {
Keyboard.enableRepeatEvents(true);
this.rebuildGUI();
}
@Override
public void onGuiClosed() {
Keyboard.enableRepeatEvents(false);
}
protected void rebuildGUI() {
this.controls.clear();
this.children.clear();
this.drawable.clear();
this.focusedElement = null;
if (this.currentPage == null) {
if (this.pages.isEmpty()) {
throw new IllegalStateException("No pages are available?!");
}
// Just use the first page for now
this.currentPage = this.pages.getFirst();
}
this.rebuildGUIPages();
this.rebuildGUIOptions();
this.undoButton = new FlatButtonWidget(new Dim2i(this.width - 211, this.height - 26, 65, 20), I18n.format("sodium.options.buttons.undo"), this::undoChanges);
this.applyButton = new FlatButtonWidget(new Dim2i(this.width - 142, this.height - 26, 65, 20), I18n.format("sodium.options.buttons.apply"), this::applyChanges);
this.closeButton = new FlatButtonWidget(new Dim2i(this.width - 73, this.height - 26, 65, 20), I18n.format("gui.done"), this::onClose);
this.children.add(this.undoButton);
this.children.add(this.applyButton);
this.children.add(this.closeButton);
for (Element element : this.children) {
if (element instanceof Drawable) {
this.drawable.add((Drawable) element);
}
}
}
private void rebuildGUIPages() {
int x = 6;
final int y = 6;
for (OptionPage page : this.pages) {
final int width = 12 + this.fontRendererObj.getStringWidth(page.getName());
final FlatButtonWidget button = new FlatButtonWidget(new Dim2i(x, y, width, 18), page.getName(), () -> this.setPage(page));
button.setSelected(this.currentPage == page);
x += width + 6;
this.children.add(button);
}
}
private void rebuildGUIOptions() {
final int x = 6;
int y = 28;
for (OptionGroup group : this.currentPage.getGroups()) {
// Add each option's control element
for (Option<?> option : group.getOptions()) {
final Control<?> control = option.getControl();
final ControlElement<?> element = control.createElement(new Dim2i(x, y, 200, 18), elementFactory);
this.controls.add(element);
//Safe if SodiumControlElementFactory or a compatible ControlElementFactory is used to create element.
this.children.add((Element)element);
// Move down to the next option
y += 18;
}
// Add padding beneath each option group
y += 4;
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float delta) {
super.drawScreen(mouseX, mouseY, delta);
super.drawDefaultBackground();
this.updateControls();
for (Drawable drawable : this.drawable) {
drawable.render(mouseX, mouseY, delta);
}
if (this.hoveredElement != null) {
this.renderOptionTooltip(this.hoveredElement);
}
}
@Override
public List<? extends Element> children() {
return children;
}
private void updateControls() {
ControlElement<?> hovered = this.getActiveControls()
.filter(ControlElement::isHovered)
.findFirst()
.orElse(null);
final boolean hasChanges = this.getAllOptions().anyMatch(Option::hasChanged);
this.applyButton.setEnabled(hasChanges);
this.undoButton.setVisible(hasChanges);
this.closeButton.setEnabled(!hasChanges);
this.hasPendingChanges = hasChanges;
this.hoveredElement = hovered;
}
private Stream<Option<?>> getAllOptions() {
return this.pages.stream()
.flatMap(s -> s.getOptions().stream());
}
private Stream<ControlElement<?>> getActiveControls() {
return this.controls.stream();
}
private void renderOptionTooltip(ControlElement<?> element) {
final Dim2i dim = element.getDimensions();
final int textPadding = 3;
final int boxPadding = 3;
final int boxWidth = 200;
int boxY = dim.getOriginY();
final int boxX = dim.getLimitX() + boxPadding;
final Option<?> option = element.getOption();
final List<String> tooltip = new ArrayList<>(this.fontRendererObj.listFormattedStringToWidth(option.getTooltip(), boxWidth - (textPadding * 2)));
final OptionImpact impact = option.getImpact();
if (impact != null) {
tooltip.add(EnumChatFormatting.GRAY + I18n.format("sodium.options.performance_impact_string", impact.toDisplayString()));
}
final int boxHeight = (tooltip.size() * 12) + boxPadding;
final int boxYLimit = boxY + boxHeight;
final int boxYCutoff = this.height - 40;
// If the box is going to be cutoff on the Y-axis, move it back up the difference
if (boxYLimit > boxYCutoff) {
boxY -= boxYLimit - boxYCutoff;
}
this.drawGradientRect(boxX, boxY, boxX + boxWidth, boxY + boxHeight, 0xE0000000, 0xE0000000);
for (int i = 0; i < tooltip.size(); i++) {
this.fontRendererObj.drawString(tooltip.get(i), boxX + textPadding, boxY + textPadding + (i * 12), 0xFFFFFFFF);
}
}
private void applyChanges() {
final HashSet<OptionStorage<?>> dirtyStorages = new HashSet<>();
final EnumSet<OptionFlag> flags = EnumSet.noneOf(OptionFlag.class);
this.getAllOptions().forEach((option -> {
if (!option.hasChanged()) {
return;
}
option.applyChanges();
flags.addAll(option.getFlags());
dirtyStorages.add(option.getStorage());
}));
if (flags.contains(OptionFlag.REQUIRES_ASSET_RELOAD)) {
SodiumGameOptions.applyAtlasSettings();
this.mc.refreshResources();
} else if (flags.contains(OptionFlag.REQUIRES_RENDERER_RELOAD)) {
this.mc.renderGlobal.loadRenderers();
}
for (OptionStorage<?> storage : dirtyStorages) {
storage.save();
}
}
private void undoChanges() {
this.getAllOptions().forEach(Option::reset);
}
@Override
public void keyTyped(char typedChar, int keyCode) {
if(keyCode == Keyboard.KEY_ESCAPE && !shouldCloseOnEsc()) {
return;
} else if (keyCode == Keyboard.KEY_ESCAPE) {
onClose();
return;
}
if (keyCode == Keyboard.KEY_P && isShiftKeyDown()) {
this.mc.displayGuiScreen(new GuiCustomMenu(this.prevScreen, SodiumGameOptionPages.general(),
SodiumGameOptionPages.quality(), SodiumGameOptionPages.advanced(), SodiumGameOptionPages.performance(),
SodiumGameOptionPages.appearance()));
}
if (this.focusedElement != null) {
this.focusedElement.keyTyped(typedChar, keyCode);
}
}
public boolean shouldCloseOnEsc() {
return !this.hasPendingChanges;
}
// We can't override onGuiClosed due to StackOverflow
public void onClose() {
this.mc.displayGuiScreen(this.prevScreen);
super.onGuiClosed();
}
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
super.mouseClicked(mouseX, mouseY, mouseButton);
this.children.forEach(element -> {
if (element.mouseClicked(mouseX, mouseY, mouseButton)) {
this.focusedElement = element;
}
});
}
@Override
protected void mouseClickMove(int mouseX, int mouseY, int mouseButton, long timeSinceLastClick) {
super.mouseClickMove(mouseX, mouseY, mouseButton, timeSinceLastClick);
this.children.forEach(element -> element.mouseDragged(mouseX, mouseY, mouseButton));
}
}
@@ -8,12 +8,27 @@ public class ActionOption extends ButtonOption {
private final Supplier<String> label;
private final Runnable action;
private final BooleanSupplier available;
private final boolean clickable;
public ActionOption(String name, String tooltip, Supplier<String> label, Runnable action, BooleanSupplier available) {
this(name, tooltip, label, action, available, true);
}
private ActionOption(String name, String tooltip, Supplier<String> label, Runnable action, BooleanSupplier available, boolean clickable) {
super(name, tooltip, 100);
this.label = label;
this.action = action;
this.available = available;
this.clickable = clickable;
}
public static ActionOption label(String name, String tooltip, Supplier<String> label) {
return new ActionOption(name, tooltip, label, () -> {}, () -> true, false);
}
@Override
protected boolean clickable() {
return this.clickable;
}
@Override
@@ -40,6 +40,10 @@ public abstract class ButtonOption implements Option<Void> {
protected abstract void invoke();
protected boolean clickable() {
return true;
}
@Override
public String getName() {
return this.name;
@@ -127,12 +131,12 @@ public abstract class ButtonOption implements Option<Void> {
final String label = this.target.label();
final int width = this.font.getStringWidth(label);
this.drawString(label, this.dim.getLimitX() - width - 6, this.dim.getCenterY() - 4, this.hovered ? LABEL_HOVERED : LABEL_IDLE);
this.drawString(label, this.dim.getLimitX() - width - 6, this.dim.getCenterY() - 4, this.hovered && this.target.clickable() ? LABEL_HOVERED : LABEL_IDLE);
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
if (!this.target.isAvailable() || button != 0 || !this.dim.containsCursor(mouseX, mouseY)) return false;
if (!this.target.clickable() || !this.target.isAvailable() || button != 0 || !this.dim.containsCursor(mouseX, mouseY)) return false;
this.playClickSound();
this.target.invoke();
@@ -1,6 +1,5 @@
package me.jellysquid.mods.sodium.client.gui.options.control.element;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import me.jellysquid.mods.sodium.client.gui.options.Option;
import me.jellysquid.mods.sodium.client.gui.options.control.ControlElement;
import me.jellysquid.mods.sodium.client.gui.options.control.ControlValueFormatter;
@@ -58,20 +57,11 @@ public class SodiumControlElementFactory implements ControlElementFactory {
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
if(AngelicaConfig.enableReesesSodiumOptions) {
if (this.option.isAvailable() && this.dim.containsCursor(mouseX, mouseY) && (button == 0 || button == 1)) {
this.option.setValue(this.allowedValues[Math.floorMod(this.currentIndex() + (button == 0 ? 1 : -1), this.allowedValues.length)]);
this.playClickSound();
if (this.option.isAvailable() && this.dim.containsCursor(mouseX, mouseY) && (button == 0 || button == 1)) {
this.option.setValue(this.allowedValues[Math.floorMod(this.currentIndex() + (button == 0 ? 1 : -1), this.allowedValues.length)]);
this.playClickSound();
return true;
}
} else {
if (this.option.isAvailable() && button == 0 && this.dim.containsCursor(mouseX, mouseY)) {
this.option.setValue(this.allowedValues[(this.currentIndex() + 1) % this.allowedValues.length]);
this.playClickSound();
return true;
}
return true;
}
return false;
+178 -16
View File
@@ -17,6 +17,7 @@ import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.StateAwareTessellator;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
import com.gtnewhorizons.angelica.utils.AngelicaJar;
import cpw.mods.fml.client.registry.ClientRegistry;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.InputEvent;
@@ -29,6 +30,7 @@ import net.coderbot.iris.celeritas.IrisCeleritasShaderProvider;
import net.coderbot.iris.compat.dh.DHCompat;
import net.coderbot.iris.config.IrisConfig;
import net.coderbot.iris.gbuffer_overrides.matching.InputAvailability;
import net.coderbot.iris.gl.program.RetainedPrograms;
import net.coderbot.iris.gl.shader.StandardMacros;
import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
@@ -38,6 +40,7 @@ import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.pipeline.transform.ShaderTransformer;
import net.coderbot.iris.pipeline.transform.TransformPatcher;
import net.coderbot.iris.shaderpack.OptionalBoolean;
import net.coderbot.iris.shaderpack.IdMap;
import net.coderbot.iris.shaderpack.ProgramSet;
import net.coderbot.iris.shaderpack.ShaderPack;
import net.coderbot.iris.shaderpack.StringPair;
@@ -67,19 +70,30 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.lang.ref.SoftReference;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemLoopException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.zip.ZipException;
@@ -107,6 +121,7 @@ public class Iris {
@Getter
private static String currentPackName;
private static String currentPackSettings;
private static String currentPackState;
@Getter
private static boolean initialized;
@Getter
@@ -288,6 +303,10 @@ public class Iris {
IrisShaderProviderHolder.setProvider(new IrisCeleritasShaderProvider());
// The pack loads below, before mod preInit would otherwise set up the cache
final Path angelicaJar = AngelicaJar.location();
AngelicaJar.configureShaderDiskCache(angelicaJar != null ? angelicaJar.toFile() : null);
// Warm up the threadpool so shader transformations are faster when we need them
ShaderTransformExecutor.warmup();
@@ -485,9 +504,7 @@ public class Iris {
}
}
@SuppressWarnings("unchecked")
final Map<String, String> changedConfigs = tryReadConfigProperties(shaderPackConfigTxt).map(properties -> (Map<String, String>) (Map<?, ?>) properties)
.orElse(new HashMap<>());
final Map<String, String> changedConfigs = readChangedConfigs(shaderPackConfigTxt);
changedConfigs.putAll(shaderPackOptionQueue);
clearShaderPackOptionQueue();
@@ -499,7 +516,12 @@ public class Iris {
try {
final Iterable<StringPair> environmentDefines = StandardMacros.createStandardEnvironmentDefines();
currentPack = new ShaderPack(shaderPackPath, changedConfigs, environmentDefines);
final String packState = describeSettings(name, shaderPackRoot, toProperties(changedConfigs), environmentDefines, false);
final ShaderPack prepared = takePreparedPack(name, packState);
currentPack = prepared != null ? prepared : new ShaderPack(shaderPackPath, changedConfigs, environmentDefines);
currentPackState = packState;
RetainedPrograms.useConfiguration(packState);
currentPack.activate();
final MutableOptionValues changedConfigsValues = currentPack.getShaderPackOptions().getOptionValues().mutableCopy();
@@ -509,7 +531,7 @@ public class Iris {
changedConfigsValues.getStringValues().forEach(configsToSave::setProperty);
tryUpdateConfigPropertiesFile(shaderPackConfigTxt, configsToSave);
currentPackSettings = describeSettings(name, shaderPackRoot, configsToSave, environmentDefines);
currentPackSettings = irisConfig.shouldSaveCompiledShaders() ? describeSettings(name, shaderPackRoot, configsToSave, environmentDefines, true) : null;
} catch (Exception e) {
logger.error("Failed to load the shaderpack \"{}\"!", name);
logger.error("", e);
@@ -526,10 +548,83 @@ public class Iris {
return true;
}
private record PreparedPack(String settings, ShaderPack pack) {}
private static volatile SoftReference<PreparedPack> preparedPack;
@Nullable
public static Runnable prepareSelectedShaderPack() {
if (irisConfig == null || irisConfig.areShadersEnabled() || currentPack != null) return null;
final Optional<String> name = irisConfig.getShaderPackName();
if (name.isEmpty()) return null;
final List<StringPair> environmentDefines = new ArrayList<>();
StandardMacros.createStandardEnvironmentDefines().forEach(environmentDefines::add);
return () -> {
try {
final PreparedPack prepared = readPreparedPack(name.get(), environmentDefines);
if (prepared != null) preparedPack = new SoftReference<>(prepared);
} catch (Exception e) {
logger.debug("Could not read shaderpack \"{}\" ahead of time", name.get(), e);
}
};
}
@Nullable
private static PreparedPack readPreparedPack(String name, List<StringPair> environmentDefines) throws IOException {
final Path shaderPackRoot = getShaderpacksDirectory().resolve(name);
final Map<String, String> changedConfigs = readChangedConfigs(getShaderpacksDirectory().resolve(name + ".txt"));
final String settings = describeSettings(name, shaderPackRoot, toProperties(changedConfigs), environmentDefines, false);
if (settings == null) return null;
if (Files.isDirectory(shaderPackRoot)) {
return new PreparedPack(settings, parsePack(shaderPackRoot.resolve("shaders"), changedConfigs, environmentDefines));
}
try (FileSystem zipSystem = FileSystems.newFileSystem(shaderPackRoot, Iris.class.getClassLoader())) {
final Optional<Path> shadersDirectory = findShadersDirectory(zipSystem);
if (shadersDirectory.isEmpty()) return null;
return new PreparedPack(settings, parsePack(shadersDirectory.get(), changedConfigs, environmentDefines));
}
}
private static ShaderPack parsePack(Path shadersDirectory, Map<String, String> changedConfigs, List<StringPair> environmentDefines) throws IOException {
final ShaderPack pack = new ShaderPack(shadersDirectory, changedConfigs, environmentDefines);
// Most worlds open in the overworld; other dimensions build their program sets on demand as usual.
// Considered having tracking of what dim a user is in but with it being so fast now, not sure if
// it's worth saving the 50-80ms "time waste" on first load, especially when we're already pre-loading at game init.
pack.getProgramSet("Overworld");
return pack;
}
@Nullable
private static ShaderPack takePreparedPack(String name, @Nullable String packState) {
final SoftReference<PreparedPack> reference = preparedPack;
preparedPack = null;
final PreparedPack prepared = reference != null ? reference.get() : null;
if (prepared == null || !prepared.settings().equals(packState)) return null;
logger.info("Reusing shaderpack \"{}\" read earlier, nothing it was read from has changed", name);
return prepared.pack();
}
private static Map<String, String> readChangedConfigs(Path shaderPackConfigTxt) {
@SuppressWarnings("unchecked")
final Map<String, String> changedConfigs = tryReadConfigProperties(shaderPackConfigTxt).map(properties -> (Map<String, String>) (Map<?, ?>) properties)
.orElse(new HashMap<>());
return new HashMap<>(changedConfigs);
}
private static Properties toProperties(Map<String, String> values) {
final Properties properties = new Properties();
properties.putAll(values);
return properties;
}
private static Optional<Path> loadExternalZipShaderpack(Path shaderpackPath) throws IOException {
final FileSystem zipSystem = FileSystems.newFileSystem(shaderpackPath, Iris.class.getClassLoader());
zipFileSystem = zipSystem;
return findShadersDirectory(zipSystem);
}
private static Optional<Path> findShadersDirectory(FileSystem zipSystem) throws IOException {
// Should only be one root directory for a zip shaderpack
final Path root = zipSystem.getRootDirectories().iterator().next();
@@ -552,6 +647,7 @@ public class Iris {
private static void setShadersDisabled() {
currentPack = null;
currentPackState = null;
currentPackSettings = null;
ProgramBinaryCache.usePack(null);
fallback = false;
@@ -710,7 +806,11 @@ public class Iris {
*/
private static void destroyEverything() {
awaitProgramSetWarmup();
if (currentPack != null && currentPackState != null) {
preparedPack = new SoftReference<>(new PreparedPack(currentPackState, currentPack));
}
currentPack = null;
currentPackState = null;
currentPackSettings = null;
ProgramBinaryCache.usePack(null);
@@ -809,6 +909,8 @@ public class Iris {
} catch (Exception e) {
everyFolderCompiled = false;
logger.error("Failed to compile shader folder '{}' for saving", folder, e);
} finally {
RetainedPrograms.afterPipelineBuilt();
}
}
loopFinished = true;
@@ -818,8 +920,65 @@ public class Iris {
}
}
private record HashedFile(long size, long modified, String hash) {}
private static final Map<Path, HashedFile> contentHashes = new ConcurrentHashMap<>();
private static String contentHash(Path file, BasicFileAttributes attributes) throws IOException {
final long size = attributes.size();
final long modified = attributes.lastModifiedTime().toMillis();
final HashedFile known = contentHashes.get(file);
if (known != null && known.size() == size && known.modified() == modified) return known.hash();
final String hash;
try {
hash = Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-256").digest(Files.readAllBytes(file)));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
contentHashes.put(file, new HashedFile(size, modified, hash));
return hash;
}
private record VisitedFolder(Path path, @Nullable Object key) {}
private static Map<String, String> describeFolder(Path root, VisitedFolder folder, List<VisitedFolder> ancestors, boolean byContent) {
final Map<String, String> files = new HashMap<>();
final List<Supplier<Map<String, String>>> subfolders = new ArrayList<>();
final List<VisitedFolder> lineage = new ArrayList<>(ancestors);
lineage.add(folder);
try (DirectoryStream<Path> entries = Files.newDirectoryStream(folder.path())) {
for (Path entry : entries) {
BasicFileAttributes attributes;
try {
attributes = Files.readAttributes(entry, BasicFileAttributes.class);
} catch (IOException e) {
attributes = Files.readAttributes(entry, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
}
if (attributes.isDirectory()) {
final VisitedFolder subfolder = new VisitedFolder(entry, attributes.fileKey());
for (VisitedFolder ancestor : lineage) {
final boolean same = subfolder.key() != null && ancestor.key() != null
? subfolder.key().equals(ancestor.key())
: Files.isSameFile(ancestor.path(), entry);
if (same) throw new FileSystemLoopException(entry.toString());
}
subfolders.add(() -> describeFolder(root, subfolder, lineage, byContent));
} else if (attributes.isRegularFile()) {
files.put(root.relativize(entry).toString(),
byContent ? contentHash(entry, attributes) : attributes.size() + "|" + attributes.lastModifiedTime().toMillis());
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
for (Map<String, String> nested : AngelicaWorkers.invokeAll(subfolders)) {
files.putAll(nested);
}
return files;
}
@Nullable
private static String describeSettings(String packName, Path packRoot, Properties options, Iterable<StringPair> environmentDefines) {
private static String describeSettings(String packName, Path packRoot, Properties options, Iterable<StringPair> environmentDefines, boolean byContent) {
final StringBuilder settings = new StringBuilder(packName).append('\n');
if (Files.isRegularFile(packRoot)) {
try {
@@ -828,18 +987,15 @@ public class Iris {
return null;
}
} else if (Files.isDirectory(packRoot)) {
try (Stream<Path> files = Files.walk(packRoot)) {
files.filter(Files::isRegularFile).sorted().forEach(file -> {
try {
settings.append(packRoot.relativize(file)).append('|').append(Files.size(file)).append('|')
.append(Files.getLastModifiedTime(file).toMillis()).append('\n');
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
final TreeMap<String, String> files = new TreeMap<>();
try {
// One stat per file; this runs on the render thread when shaders turn on, so folders are read in parallel
final Object rootKey = Files.readAttributes(packRoot, BasicFileAttributes.class).fileKey();
files.putAll(describeFolder(packRoot, new VisitedFolder(packRoot, rootKey), List.of(), byContent));
} catch (IOException | UncheckedIOException e) {
return null;
}
files.forEach((file, state) -> settings.append(file).append('|').append(state).append('\n'));
} else {
return null;
}
@@ -848,6 +1004,7 @@ public class Iris {
for (StringPair define : environmentDefines) {
settings.append('#').append(define.getKey()).append(' ').append(define.getValue()).append('\n');
}
settings.append("modernFallbackMcVersion=").append(IdMap.modernFallbackMcVersion()).append('\n');
return settings.toString();
}
@@ -870,7 +1027,12 @@ public class Iris {
ShaderTransformExecutor.prepare();
PerFrameUniformBlockHarvester.harvest(programs);
long startTime = System.nanoTime();
WorldRenderingPipeline pipeline = new DeferredWorldRenderingPipeline(programs);
final WorldRenderingPipeline pipeline;
try {
pipeline = new DeferredWorldRenderingPipeline(programs);
} finally {
RetainedPrograms.afterPipelineBuilt();
}
long endTime = System.nanoTime();
logger.info("[Load #{}] Total shaderpack load time for '{}' in dimension '{}': {} ms", shaderPackLoadId, currentPackName, dimensionName, String.format("%.1f", (endTime - startTime) / 1_000_000.0));
return pipeline;
@@ -10,6 +10,7 @@ public abstract class GlResource {
}
public final void destroy() {
if (!isValid) return;
destroyInternal();
isValid = false;
}
@@ -6,6 +6,7 @@ import net.coderbot.iris.gl.GlResource;
import net.coderbot.iris.gl.blending.DepthColorStorage;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.shaderpack.FilledIndirectPointer;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector2f;
import org.joml.Vector3i;
import org.lwjgl.BufferUtils;
@@ -27,9 +28,14 @@ public final class ComputeProgram extends GlResource {
private float cachedHeight;
private Vector3i cachedWorkGroups;
ComputeProgram(int program, ProgramUniforms uniforms, ProgramSamplers samplers, ProgramImages images) {
@Nullable
private final RetainedPrograms.Sources sources;
ComputeProgram(int program, @Nullable RetainedPrograms.Sources sources, ProgramUniforms uniforms, ProgramSamplers samplers, ProgramImages images) {
super(program);
this.sources = sources;
localSizeBuffer = BufferUtils.createIntBuffer(3);
RenderSystem.getProgramiv(program, GL43.GL_COMPUTE_WORK_GROUP_SIZE, localSizeBuffer);
this.uniforms = uniforms;
@@ -93,7 +99,11 @@ public final class ComputeProgram extends GlResource {
@Override
public void destroyInternal() {
DepthColorStorage.unregisterOwnedProgram(getGlId());
GLStateManager.glDeleteProgram(getGlId());
if (sources != null) {
RetainedPrograms.retain(sources, getGlId());
} else {
GLStateManager.glDeleteProgram(getGlId());
}
}
/**
@@ -5,6 +5,7 @@ import net.coderbot.iris.gl.GlResource;
import net.coderbot.iris.gl.blending.DepthColorStorage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
@@ -25,9 +26,14 @@ public final class Program extends GlResource {
@Getter
private final ProgramImages images;
Program(int program, ProgramUniforms uniforms, ProgramSamplers samplers, ProgramImages images) {
@Nullable
private final RetainedPrograms.Sources sources;
Program(int program, @Nullable RetainedPrograms.Sources sources, ProgramUniforms uniforms, ProgramSamplers samplers, ProgramImages images) {
super(program);
this.sources = sources;
this.uniforms = uniforms;
this.samplers = samplers;
this.images = images;
@@ -63,7 +69,11 @@ public final class Program extends GlResource {
@Override
public void destroyInternal() {
DepthColorStorage.unregisterOwnedProgram(getGlId());
GLStateManager.glDeleteProgram(getGlId());
if (sources != null) {
RetainedPrograms.retain(sources, getGlId());
} else {
GLStateManager.glDeleteProgram(getGlId());
}
}
public int getProgramId() {
Loaded 100 of 144 files, more files were not shown because too many files have changed in this diff. Show more