mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3389b952d0 | ||
|
|
b49d37d827 | ||
|
|
12857006c6 | ||
|
|
38af64f10c | ||
|
|
10922edca3 | ||
|
|
2a8c8756d3 | ||
|
|
23e36f2b76 | ||
|
|
fcf8ad064a | ||
|
|
6dfb0312b1 |
No files matched your search
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
# usage: companion-version.sh <name> <catalog-key>; TAG is <name>-v<version>[-N] or unset
|
||||
set -euo pipefail
|
||||
|
||||
name="${1:?name required}"
|
||||
key="${2:?catalog key required}"
|
||||
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
|
||||
base="$(sed -nE "s/^$key = \"(.*)\"\$/\\1/p" gradle/libs.versions.toml)"
|
||||
[[ -n "$base" ]] || { echo "$key not found" >&2; exit 1; }
|
||||
|
||||
tag="${TAG:-}"
|
||||
if [[ -z "$tag" ]]; then
|
||||
echo "$base"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
version="${tag#"$name"-v}"
|
||||
if [[ "$version" == "$tag" || ( "$version" != "$base" && ! "$version" =~ ^"$base"-[1-9][0-9]*$ ) ]]; then
|
||||
echo "bad tag '$tag': want $name-v$base[-N]" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$version"
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# usage: release-companion-jar.sh <jar> <notes>; TAG is <name>-v<version>
|
||||
set -euo pipefail
|
||||
|
||||
: "${TAG:?TAG required}"
|
||||
jar="${1:?jar required}"
|
||||
notes="${2:?notes required}"
|
||||
|
||||
gh release create "$TAG" "$jar" --latest=false --title "angelica-${TAG%%-v*} ${TAG#*-v}" --notes "$notes"
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Build angelica-moltenvk
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "moltenvk-v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: Publish angelica-moltenvk.jar to the GTNH maven
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
- uses: gradle/actions/setup-gradle@v4
|
||||
- name: Compute version
|
||||
shell: bash
|
||||
env:
|
||||
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
|
||||
run: |
|
||||
version="$(bash .github/scripts/companion-version.sh moltenvk lwjgl3Release)"
|
||||
echo "MOLTENVK_VERSION=$version" >> "$GITHUB_ENV"
|
||||
- name: Build angelica-moltenvk.jar
|
||||
run: ./gradlew -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:distJar
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: angelica-moltenvk
|
||||
path: moltenvk/build/libs/angelica-moltenvk-*.jar
|
||||
if-no-files-found: error
|
||||
- name: Publish to GTNH maven
|
||||
if: github.ref_type == 'tag' || inputs.publish
|
||||
env:
|
||||
MAVEN_USER: ${{ secrets.MAVEN_USER }}
|
||||
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
run: ./gradlew -PpublishMoltenVK=true -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:publish
|
||||
- name: Create GitHub release
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: bash .github/scripts/release-companion-jar.sh moltenvk/build/libs/angelica-moltenvk-*.jar "MoltenVK for the SDL GPU Vulkan driver on macOS."
|
||||
@@ -12,6 +12,9 @@ on:
|
||||
required: false
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
@@ -66,8 +69,15 @@ jobs:
|
||||
cp "$d"/* "$dest/"
|
||||
done
|
||||
find tracy-client/src/main/resources/natives -type f
|
||||
- name: Compute version
|
||||
shell: bash
|
||||
env:
|
||||
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
|
||||
run: |
|
||||
version="$(bash .github/scripts/companion-version.sh tracy tracy)"
|
||||
echo "TRACY_JAR_VERSION=$version" >> "$GITHUB_ENV"
|
||||
- name: Build angelica-tracy.jar
|
||||
run: ./gradlew :tracy-client:distJar
|
||||
run: ./gradlew -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:distJar
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: angelica-tracy
|
||||
@@ -78,4 +88,10 @@ jobs:
|
||||
env:
|
||||
MAVEN_USER: ${{ secrets.MAVEN_USER }}
|
||||
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
run: ./gradlew -PpublishTracy=true :tracy-client:publish
|
||||
run: ./gradlew -PpublishTracy=true -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:publish
|
||||
- name: Create GitHub release
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: bash .github/scripts/release-companion-jar.sh tracy-client/build/libs/angelica-tracy-*.jar "Tracy profiler backend for Angelica."
|
||||
@@ -3,7 +3,7 @@ name: Release tagged build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ '*', '!tracy-v*' ]
|
||||
tags: [ '*', '!tracy-v*', '!moltenvk-v*' ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -6,6 +6,8 @@ plugins {
|
||||
id("com.gtnewhorizons.gtnhconvention")
|
||||
}
|
||||
|
||||
apply(from = "gradle/mod-version.gradle.kts")
|
||||
|
||||
val lwjglDebug = false
|
||||
val gpuHud = false
|
||||
val renderdoc = false
|
||||
|
||||
@@ -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"
|
||||
))
|
||||
);
|
||||
|
||||
|
||||
+6
-6
@@ -3,7 +3,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLTypes;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -28,7 +28,7 @@ public record TexImage2DCmd(
|
||||
int format,
|
||||
int type,
|
||||
@Nullable ByteBuffer pixels,
|
||||
@Nullable PixelUnpackState unpack
|
||||
@Nullable PixelStoreState unpack
|
||||
) implements DisplayListCommand {
|
||||
|
||||
public TexImage2DCmd {
|
||||
@@ -37,19 +37,19 @@ public record TexImage2DCmd(
|
||||
}
|
||||
}
|
||||
|
||||
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
|
||||
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelStoreState unpack) {
|
||||
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
|
||||
}
|
||||
|
||||
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
|
||||
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelStoreState unpack) {
|
||||
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
|
||||
}
|
||||
|
||||
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
|
||||
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelStoreState unpack) {
|
||||
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
|
||||
}
|
||||
|
||||
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
|
||||
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelStoreState unpack) {
|
||||
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -3,7 +3,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLTypes;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -28,7 +28,7 @@ public record TexSubImage2DCmd(
|
||||
int format,
|
||||
int type,
|
||||
@Nullable ByteBuffer pixels,
|
||||
@Nullable PixelUnpackState unpack
|
||||
@Nullable PixelStoreState unpack
|
||||
) implements DisplayListCommand {
|
||||
|
||||
public TexSubImage2DCmd {
|
||||
@@ -37,19 +37,19 @@ public record TexSubImage2DCmd(
|
||||
}
|
||||
}
|
||||
|
||||
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
|
||||
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelStoreState unpack) {
|
||||
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
|
||||
}
|
||||
|
||||
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
|
||||
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelStoreState unpack) {
|
||||
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
|
||||
}
|
||||
|
||||
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
|
||||
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelStoreState unpack) {
|
||||
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
|
||||
}
|
||||
|
||||
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
|
||||
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelStoreState unpack) {
|
||||
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
|
||||
}
|
||||
|
||||
|
||||
@@ -626,6 +626,7 @@ public class GLSMRedirector {
|
||||
.add("glGenRenderbuffers")
|
||||
.add("glRenderbufferStorage")
|
||||
.add("glFramebufferRenderbuffer")
|
||||
.add("glGenerateMipmap")
|
||||
);
|
||||
methodRedirects.put(EXTFramebufferObject, RedirectMap.newMap()
|
||||
.add("glBindFramebufferEXT", "glBindFramebuffer")
|
||||
@@ -638,6 +639,7 @@ public class GLSMRedirector {
|
||||
.add("glGenRenderbuffersEXT", "glGenRenderbuffers")
|
||||
.add("glRenderbufferStorageEXT", "glRenderbufferStorage")
|
||||
.add("glFramebufferRenderbufferEXT", "glFramebufferRenderbuffer")
|
||||
.add("glGenerateMipmapEXT", "glGenerateMipmap")
|
||||
);
|
||||
methodRedirects.put(ARBOcclusionQuery, RedirectMap.newMap()
|
||||
.add("glGenQueriesARB", "glGenQueries")
|
||||
|
||||
@@ -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;
|
||||
|
||||
+737
@@ -0,0 +1,737 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VertexKey;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
@GLCoreTest
|
||||
public class GLSM_LegacyStackQueries_GLTest {
|
||||
|
||||
private static final int[] SINGLE_VALUE_QUERIES = {
|
||||
GL11.GL_ATTRIB_STACK_DEPTH,
|
||||
GL11.GL_CLIENT_ATTRIB_STACK_DEPTH,
|
||||
GL11.GL_TEXTURE_STACK_DEPTH,
|
||||
GL11.GL_MAX_ATTRIB_STACK_DEPTH,
|
||||
GL11.GL_MAX_CLIENT_ATTRIB_STACK_DEPTH,
|
||||
GL11.GL_MAX_MODELVIEW_STACK_DEPTH,
|
||||
GL11.GL_MAX_PROJECTION_STACK_DEPTH,
|
||||
GL11.GL_MAX_TEXTURE_STACK_DEPTH,
|
||||
GL13.GL_MAX_TEXTURE_UNITS,
|
||||
GL11.GL_MAX_LIGHTS,
|
||||
GL11.GL_STENCIL_BITS,
|
||||
GL11.GL_DEPTH_BITS,
|
||||
};
|
||||
|
||||
private static boolean positionArrayEnabled() {
|
||||
final VAOManager.Attrib position = GLStateManager.ctx().vaos.attrib(Usage.POSITION.getAttributeLocation());
|
||||
return position != null && position.enabled;
|
||||
}
|
||||
|
||||
private static boolean colorArrayEnabled() {
|
||||
return (GLStateManager.ctx().vaos.getVertexFlags() & VertexFlags.COLOR_BIT) != 0;
|
||||
}
|
||||
|
||||
private static void drainErrors() {
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
}
|
||||
|
||||
private static void drainClientAttribStack() {
|
||||
while (GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH) > 0) {
|
||||
GLStateManager.glPopClientAttrib();
|
||||
}
|
||||
}
|
||||
|
||||
private static void drainAttribStack() {
|
||||
while (GLStateManager.getAttribDepth() > 0) {
|
||||
GLStateManager.glPopAttrib();
|
||||
}
|
||||
}
|
||||
|
||||
private static int pushToLimit(int depthPname, int maxPname) {
|
||||
final int max = GLStateManager.glGetInteger(maxPname);
|
||||
for (int i = 0; i < max + 4; i++) {
|
||||
GLStateManager.glPushMatrix();
|
||||
}
|
||||
return GLStateManager.glGetInteger(depthPname);
|
||||
}
|
||||
|
||||
private static void popToBase(int depthPname) {
|
||||
for (int i = 0; i < 128 && GLStateManager.glGetInteger(depthPname) > 1; i++) {
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void attribDepthTracksPushAndPop() {
|
||||
final int base = GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH);
|
||||
try {
|
||||
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
assertEquals(base + 1, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
|
||||
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
|
||||
assertEquals(base + 2, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
|
||||
GLStateManager.glPopAttrib();
|
||||
assertEquals(base + 1, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
|
||||
GLStateManager.glPopAttrib();
|
||||
assertEquals(base, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
|
||||
assertEquals(GLStateManager.MAX_ATTRIB_STACK_DEPTH, GLStateManager.glGetInteger(GL11.GL_MAX_ATTRIB_STACK_DEPTH));
|
||||
} finally {
|
||||
while (GLStateManager.getAttribDepth() > base) {
|
||||
GLStateManager.glPopAttrib();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void clientAttribDepthTracksPushAndPop() {
|
||||
try {
|
||||
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
|
||||
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
|
||||
assertEquals(2, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
|
||||
GLStateManager.glPopClientAttrib();
|
||||
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
|
||||
GLStateManager.glPopClientAttrib();
|
||||
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
|
||||
GLStateManager.glPopClientAttrib();
|
||||
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
|
||||
|
||||
final int max = GLStateManager.glGetInteger(GL11.GL_MAX_CLIENT_ATTRIB_STACK_DEPTH);
|
||||
for (int i = 0; i < max + 2; i++) {
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
|
||||
}
|
||||
assertEquals(max, GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH));
|
||||
} finally {
|
||||
drainClientAttribStack();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void textureStackDepthIsPerActiveUnit() {
|
||||
try {
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glPushMatrix();
|
||||
assertEquals(2, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
assertEquals(2, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
|
||||
GLStateManager.glPopMatrix();
|
||||
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
|
||||
} finally {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
popToBase(GL11.GL_TEXTURE_STACK_DEPTH);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxModelviewStackDepthIsExactlyReachable() {
|
||||
try {
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH));
|
||||
assertEquals(GLStateManager.glGetInteger(GL11.GL_MAX_MODELVIEW_STACK_DEPTH), pushToLimit(GL11.GL_MODELVIEW_STACK_DEPTH, GL11.GL_MAX_MODELVIEW_STACK_DEPTH));
|
||||
} finally {
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
popToBase(GL11.GL_MODELVIEW_STACK_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxProjectionStackDepthIsExactlyReachable() {
|
||||
try {
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_PROJECTION_STACK_DEPTH));
|
||||
assertEquals(GLStateManager.glGetInteger(GL11.GL_MAX_PROJECTION_STACK_DEPTH), pushToLimit(GL11.GL_PROJECTION_STACK_DEPTH, GL11.GL_MAX_PROJECTION_STACK_DEPTH));
|
||||
} finally {
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
popToBase(GL11.GL_PROJECTION_STACK_DEPTH);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxTextureStackDepthIsExactlyReachable() {
|
||||
try {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
|
||||
assertEquals(GLStateManager.glGetInteger(GL11.GL_MAX_TEXTURE_STACK_DEPTH), pushToLimit(GL11.GL_TEXTURE_STACK_DEPTH, GL11.GL_MAX_TEXTURE_STACK_DEPTH));
|
||||
} finally {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
popToBase(GL11.GL_TEXTURE_STACK_DEPTH);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void constantQueriesReportEmulatedLimits() {
|
||||
assertEquals(Math.min(VertexKey.MAX_UNITS, GLStateManager.MAX_TEXTURE_UNITS), GLStateManager.glGetInteger(GL13.GL_MAX_TEXTURE_UNITS));
|
||||
assertEquals(8, GLStateManager.glGetInteger(GL11.GL_MAX_LIGHTS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stencilAndDepthBitsMatchDefaultFramebuffer() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
final int savedDraw = glCtx.drawFramebuffer;
|
||||
final int savedRead = glCtx.readFramebuffer;
|
||||
try {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
drainErrors();
|
||||
assertEquals(
|
||||
GL30.glGetFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE),
|
||||
GLStateManager.glGetInteger(GL11.GL_STENCIL_BITS));
|
||||
assertEquals(
|
||||
GL30.glGetFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE),
|
||||
GLStateManager.glGetInteger(GL11.GL_DEPTH_BITS));
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, savedDraw);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, savedRead);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void stencilAndDepthBitsFollowBoundDrawFramebuffer() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
final int savedDraw = glCtx.drawFramebuffer;
|
||||
final int savedRead = glCtx.readFramebuffer;
|
||||
final int fbo = GLStateManager.glGenFramebuffers();
|
||||
final int depthOnly = GLStateManager.glGenRenderbuffers();
|
||||
final int depthStencil = GLStateManager.glGenRenderbuffers();
|
||||
try {
|
||||
drainErrors();
|
||||
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthOnly);
|
||||
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL14.GL_DEPTH_COMPONENT24, 16, 16);
|
||||
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthStencil);
|
||||
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, 16, 16);
|
||||
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, 0);
|
||||
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_STENCIL_BITS));
|
||||
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_DEPTH_BITS));
|
||||
|
||||
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, depthOnly);
|
||||
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_STENCIL_BITS));
|
||||
final int depthOnlyBits = GL30.glGetFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE);
|
||||
assertTrue(depthOnlyBits >= 24, "driver depth size " + depthOnlyBits);
|
||||
assertEquals(depthOnlyBits, GLStateManager.glGetInteger(GL11.GL_DEPTH_BITS));
|
||||
|
||||
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_RENDERBUFFER, depthStencil);
|
||||
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depthStencil);
|
||||
assertEquals(8, GLStateManager.glGetInteger(GL11.GL_STENCIL_BITS));
|
||||
assertEquals(24, GLStateManager.glGetInteger(GL11.GL_DEPTH_BITS));
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, savedDraw);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, savedRead);
|
||||
GLStateManager.glDeleteFramebuffers(fbo);
|
||||
GLStateManager.glDeleteRenderbuffers(depthOnly);
|
||||
GLStateManager.glDeleteRenderbuffers(depthStencil);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void intBufferOverloadMatchesAndDriverIsNotAsked() {
|
||||
final int base = GLStateManager.getAttribDepth();
|
||||
final IntBuffer buf = BufferUtils.createIntBuffer(16);
|
||||
try {
|
||||
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
|
||||
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
|
||||
drainErrors();
|
||||
GL11.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH);
|
||||
assumeTrue(GL11.glGetError() != GL11.GL_NO_ERROR, "driver accepts GL_ATTRIB_STACK_DEPTH, so a clean error state proves nothing");
|
||||
for (final int pname : SINGLE_VALUE_QUERIES) {
|
||||
final int direct = GLStateManager.glGetInteger(pname);
|
||||
buf.put(0, -12345);
|
||||
GLStateManager.glGetInteger(pname, buf);
|
||||
assertEquals(direct, buf.get(0), "IntBuffer overload for 0x" + Integer.toHexString(pname));
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "driver error after querying 0x" + Integer.toHexString(pname));
|
||||
}
|
||||
} finally {
|
||||
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
drainClientAttribStack();
|
||||
while (GLStateManager.getAttribDepth() > base) {
|
||||
GLStateManager.glPopAttrib();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void attribDepthUnchangedWhileCompilingDisplayList() {
|
||||
final int base = GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH);
|
||||
final int list = GLStateManager.glGenLists(1);
|
||||
try {
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
assertEquals(base, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
|
||||
GLStateManager.glPopAttrib();
|
||||
assertEquals(base, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
|
||||
GLStateManager.glEndList();
|
||||
assertEquals(base, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
|
||||
} finally {
|
||||
if (DisplayListManager.isRecording()) {
|
||||
GLStateManager.glEndList();
|
||||
}
|
||||
GLStateManager.glDeleteLists(list, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void popAttribOnEmptyStackIsIgnored() {
|
||||
drainAttribStack();
|
||||
drainErrors();
|
||||
assertEquals(0, GLStateManager.getAttribDepth());
|
||||
assertDoesNotThrow(GLStateManager::glPopAttrib);
|
||||
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
|
||||
assertDoesNotThrow(GLStateManager::glPopAttrib);
|
||||
assertEquals(0, GLStateManager.getAttribDepth());
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pixelStoreBitRestoresPixelStoreAndPboBindings() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
final int unpackBuffer = GLStateManager.glGenBuffers();
|
||||
final int packBuffer = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 0);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
|
||||
final PixelStoreState unpackBefore = glCtx.pixelUnpackState;
|
||||
final PixelStoreState packBefore = glCtx.pixelPackState;
|
||||
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 2);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 7);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, unpackBuffer);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, packBuffer);
|
||||
assertEquals(1, glCtx.pixelUnpackState.alignment());
|
||||
assertEquals(2, glCtx.pixelPackState.alignment());
|
||||
assertEquals(7, glCtx.pixelPackState.rowLength());
|
||||
assertEquals(2, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
|
||||
GLStateManager.glPopClientAttrib();
|
||||
|
||||
assertSame(unpackBefore, glCtx.pixelUnpackState);
|
||||
assertSame(packBefore, glCtx.pixelPackState);
|
||||
assertEquals(4, GLStateManager.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
|
||||
assertEquals(0, GLStateManager.glGetInteger(GL21.GL_PIXEL_UNPACK_BUFFER_BINDING));
|
||||
assertEquals(0, GLStateManager.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
|
||||
assertEquals(4, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
|
||||
assertEquals(4, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
|
||||
assertEquals(0, GL11.glGetInteger(GL11.GL_PACK_ROW_LENGTH));
|
||||
assertEquals(0, GL11.glGetInteger(GL21.GL_PIXEL_UNPACK_BUFFER_BINDING));
|
||||
assertEquals(0, GL11.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
drainClientAttribStack();
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 0);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
|
||||
GLStateManager.glDeleteBuffers(unpackBuffer);
|
||||
GLStateManager.glDeleteBuffers(packBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void pixelStoreIsNotRestoredWithoutPixelStoreBit() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
final int unpackBuffer = GLStateManager.glGenBuffers();
|
||||
final int packBuffer = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 2);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, unpackBuffer);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, packBuffer);
|
||||
GLStateManager.glPopClientAttrib();
|
||||
|
||||
assertEquals(1, glCtx.pixelUnpackState.alignment());
|
||||
assertEquals(2, glCtx.pixelPackState.alignment());
|
||||
assertEquals(unpackBuffer, GLStateManager.glGetInteger(GL21.GL_PIXEL_UNPACK_BUFFER_BINDING));
|
||||
assertEquals(packBuffer, GLStateManager.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
|
||||
assertEquals(1, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
|
||||
assertEquals(2, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
|
||||
assertEquals(unpackBuffer, GL11.glGetInteger(GL21.GL_PIXEL_UNPACK_BUFFER_BINDING));
|
||||
assertEquals(packBuffer, GL11.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
|
||||
} finally {
|
||||
drainClientAttribStack();
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
|
||||
GLStateManager.glDeleteBuffers(unpackBuffer);
|
||||
GLStateManager.glDeleteBuffers(packBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void pixelStoreWithReturnsSameInstanceForUnchangedValue() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
try {
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 9);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 11);
|
||||
final PixelStoreState unpack = glCtx.pixelUnpackState;
|
||||
final PixelStoreState pack = glCtx.pixelPackState;
|
||||
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 9);
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, unpack.alignment());
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SWAP_BYTES, 0);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 11);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, pack.alignment());
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_LSB_FIRST, 0);
|
||||
|
||||
assertSame(unpack, glCtx.pixelUnpackState);
|
||||
assertSame(pack, glCtx.pixelPackState);
|
||||
assertSame(unpack, unpack.with(GL11.GL_UNPACK_ROW_LENGTH, 9));
|
||||
assertSame(pack, pack.with(GL11.GL_PACK_ROW_LENGTH, 11));
|
||||
} finally {
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void packAndUnpackRestoreToTheirOwnPriorValues() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
try {
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 2);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 8);
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 3);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 5);
|
||||
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 6);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 7);
|
||||
assertEquals(1, glCtx.pixelUnpackState.alignment());
|
||||
assertEquals(4, glCtx.pixelPackState.alignment());
|
||||
assertEquals(6, glCtx.pixelUnpackState.skipRows());
|
||||
assertEquals(7, glCtx.pixelPackState.skipRows());
|
||||
assertEquals(6, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
|
||||
assertEquals(7, GL11.glGetInteger(GL11.GL_PACK_SKIP_ROWS));
|
||||
GLStateManager.glPopClientAttrib();
|
||||
|
||||
assertEquals(2, glCtx.pixelUnpackState.alignment());
|
||||
assertEquals(8, glCtx.pixelPackState.alignment());
|
||||
assertEquals(3, glCtx.pixelUnpackState.skipRows());
|
||||
assertEquals(5, glCtx.pixelPackState.skipRows());
|
||||
assertEquals(2, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
|
||||
assertEquals(8, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
|
||||
assertEquals(3, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
|
||||
assertEquals(5, GL11.glGetInteger(GL11.GL_PACK_SKIP_ROWS));
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
drainClientAttribStack();
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
|
||||
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
|
||||
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void vertexArrayEnableRestoredOnlyWithVertexArrayBit() {
|
||||
final int savedVao = GLStateManager.getBoundVAO();
|
||||
final int vao = GLStateManager.glGenVertexArrays();
|
||||
final IntBuffer enabled = BufferUtils.createIntBuffer(16);
|
||||
final int position = Usage.POSITION.getAttributeLocation();
|
||||
try {
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
drainErrors();
|
||||
assertFalse(positionArrayEnabled());
|
||||
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
|
||||
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
assertTrue(positionArrayEnabled());
|
||||
GLStateManager.glPopClientAttrib();
|
||||
assertFalse(positionArrayEnabled());
|
||||
GL20.glGetVertexAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED, enabled);
|
||||
assertEquals(GL11.GL_FALSE, enabled.get(0));
|
||||
|
||||
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
|
||||
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
GLStateManager.glPopClientAttrib();
|
||||
assertTrue(positionArrayEnabled());
|
||||
GL20.glGetVertexAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED, enabled);
|
||||
assertEquals(GL11.GL_TRUE, enabled.get(0));
|
||||
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
|
||||
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
GLStateManager.glPopClientAttrib();
|
||||
assertTrue(positionArrayEnabled());
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
drainClientAttribStack();
|
||||
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
|
||||
GLStateManager.glBindVertexArray(savedVao);
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void vertexArrayStateRestoredOnlyWithVertexArrayBit() {
|
||||
final int savedVao = GLStateManager.getBoundVAO();
|
||||
final int vao = GLStateManager.glGenVertexArrays();
|
||||
try {
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
final int flagsBefore = GLStateManager.ctx().vaos.getVertexFlags();
|
||||
assertFalse(colorArrayEnabled());
|
||||
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
|
||||
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
|
||||
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
|
||||
assertTrue(colorArrayEnabled());
|
||||
GLStateManager.glPopClientAttrib();
|
||||
assertFalse(colorArrayEnabled());
|
||||
assertEquals(flagsBefore, GLStateManager.ctx().vaos.getVertexFlags());
|
||||
assertEquals(0, GLStateManager.getClientActiveTextureUnit());
|
||||
|
||||
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
|
||||
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
|
||||
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glPopClientAttrib();
|
||||
assertTrue(colorArrayEnabled());
|
||||
assertEquals(1, GLStateManager.getClientActiveTextureUnit());
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
drainClientAttribStack();
|
||||
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
|
||||
GLStateManager.glBindVertexArray(savedVao);
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void intLightGettersConvertLikeMesa() {
|
||||
final FloatBuffer savedDiffuse = BufferUtils.createFloatBuffer(16);
|
||||
final FloatBuffer savedPosition = BufferUtils.createFloatBuffer(16);
|
||||
final FloatBuffer savedDirection = BufferUtils.createFloatBuffer(16);
|
||||
final FloatBuffer savedCutoff = BufferUtils.createFloatBuffer(16);
|
||||
final FloatBuffer savedAttenuation = BufferUtils.createFloatBuffer(16);
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, savedDiffuse);
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_POSITION, savedPosition);
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_SPOT_DIRECTION, savedDirection);
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_SPOT_CUTOFF, savedCutoff);
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_LINEAR_ATTENUATION, savedAttenuation);
|
||||
final IntBuffer ints = BufferUtils.createIntBuffer(16);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPushMatrix();
|
||||
try {
|
||||
GLStateManager.glLoadIdentity();
|
||||
final FloatBuffer values = BufferUtils.createFloatBuffer(4);
|
||||
values.put(0, 0.25f).put(1, 0.5f).put(2, 1.0f).put(3, -0.5f);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, values);
|
||||
values.put(0, 3.9f).put(1, -2.7f).put(2, 0.5f).put(3, 1.0f);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_POSITION, values);
|
||||
values.put(0, 2.5f).put(1, -1.5f).put(2, -3.9f).put(3, 0.0f);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_SPOT_DIRECTION, values);
|
||||
GLStateManager.glLightf(GL11.GL_LIGHT1, GL11.GL_SPOT_CUTOFF, 45.75f);
|
||||
GLStateManager.glLightf(GL11.GL_LIGHT1, GL11.GL_LINEAR_ATTENUATION, 2.5f);
|
||||
drainErrors();
|
||||
|
||||
ints.position(2);
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, ints);
|
||||
assertEquals(2, ints.position());
|
||||
assertEquals(536870911, ints.get(2));
|
||||
assertEquals(1073741823, ints.get(3));
|
||||
assertEquals(2147483647, ints.get(4));
|
||||
assertEquals(-1073741823, ints.get(5));
|
||||
ints.position(0);
|
||||
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_POSITION, ints);
|
||||
assertEquals(3, ints.get(0));
|
||||
assertEquals(-2, ints.get(1));
|
||||
assertEquals(0, ints.get(2));
|
||||
assertEquals(1, ints.get(3));
|
||||
|
||||
ints.put(3, -777);
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_SPOT_DIRECTION, ints);
|
||||
assertEquals(2, ints.get(0));
|
||||
assertEquals(-1, ints.get(1));
|
||||
assertEquals(-3, ints.get(2));
|
||||
assertEquals(-777, ints.get(3));
|
||||
|
||||
ints.put(1, -777);
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_SPOT_CUTOFF, ints);
|
||||
assertEquals(45, ints.get(0));
|
||||
assertEquals(-777, ints.get(1));
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_LINEAR_ATTENUATION, ints);
|
||||
assertEquals(2, ints.get(0));
|
||||
|
||||
ints.put(0, -777);
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_EMISSION, ints);
|
||||
assertEquals(-777, ints.get(0));
|
||||
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_SHININESS, ints);
|
||||
assertEquals(-777, ints.get(0));
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
GLStateManager.glLoadIdentity();
|
||||
savedDiffuse.limit(4);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, savedDiffuse);
|
||||
savedPosition.limit(4);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_POSITION, savedPosition);
|
||||
savedDirection.limit(4);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_SPOT_DIRECTION, savedDirection);
|
||||
GLStateManager.glLightf(GL11.GL_LIGHT1, GL11.GL_SPOT_CUTOFF, savedCutoff.get(0));
|
||||
GLStateManager.glLightf(GL11.GL_LIGHT1, GL11.GL_LINEAR_ATTENUATION, savedAttenuation.get(0));
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void intMaterialGettersConvertLikeMesa() {
|
||||
final FloatBuffer savedSpecular = BufferUtils.createFloatBuffer(16);
|
||||
final FloatBuffer savedShininess = BufferUtils.createFloatBuffer(16);
|
||||
final FloatBuffer savedIndexes = BufferUtils.createFloatBuffer(16);
|
||||
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, savedSpecular);
|
||||
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SHININESS, savedShininess);
|
||||
GLStateManager.glGetMaterial(GL11.GL_BACK, GL11.GL_COLOR_INDEXES, savedIndexes);
|
||||
final IntBuffer ints = BufferUtils.createIntBuffer(16);
|
||||
try {
|
||||
final FloatBuffer values = BufferUtils.createFloatBuffer(4);
|
||||
values.put(0, 0.5f).put(1, 0.75f).put(2, 1.0f).put(3, 0.0f);
|
||||
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, values);
|
||||
GLStateManager.glMaterialf(GL11.GL_FRONT, GL11.GL_SHININESS, 12.6f);
|
||||
values.put(0, 2.5f).put(1, -2.5f).put(2, 7.4f).put(3, 0.0f);
|
||||
GLStateManager.glMaterial(GL11.GL_BACK, GL11.GL_COLOR_INDEXES, values);
|
||||
drainErrors();
|
||||
|
||||
ints.position(1);
|
||||
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, ints);
|
||||
assertEquals(1, ints.position());
|
||||
assertEquals(1073741823, ints.get(1));
|
||||
assertEquals(1610612735, ints.get(2));
|
||||
assertEquals(2147483647, ints.get(3));
|
||||
assertEquals(0, ints.get(4));
|
||||
ints.position(0);
|
||||
|
||||
ints.put(1, -777);
|
||||
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SHININESS, ints);
|
||||
assertEquals(13, ints.get(0));
|
||||
assertEquals(-777, ints.get(1));
|
||||
|
||||
ints.put(3, -777);
|
||||
GLStateManager.glGetMaterial(GL11.GL_BACK, GL11.GL_COLOR_INDEXES, ints);
|
||||
assertEquals(3, ints.get(0));
|
||||
assertEquals(-3, ints.get(1));
|
||||
assertEquals(7, ints.get(2));
|
||||
assertEquals(-777, ints.get(3));
|
||||
|
||||
ints.put(0, -777);
|
||||
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_POSITION, ints);
|
||||
assertEquals(-777, ints.get(0));
|
||||
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SPOT_CUTOFF, ints);
|
||||
assertEquals(-777, ints.get(0));
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
savedSpecular.limit(4);
|
||||
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, savedSpecular);
|
||||
GLStateManager.glMaterialf(GL11.GL_FRONT, GL11.GL_SHININESS, savedShininess.get(0));
|
||||
savedIndexes.limit(4);
|
||||
GLStateManager.glMaterial(GL11.GL_BACK, GL11.GL_COLOR_INDEXES, savedIndexes);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentTextureCoordsFollowActiveUnit() {
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(16);
|
||||
final float[][] saved = new float[4][4];
|
||||
try {
|
||||
for (int unit = 0; unit < 4; unit++) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
GLStateManager.glGetFloat(GL11.GL_CURRENT_TEXTURE_COORDS, buf);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
saved[unit][i] = buf.get(i);
|
||||
}
|
||||
}
|
||||
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.25f, 0.5f);
|
||||
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE1, 240.0f, 16.0f);
|
||||
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE2, 0.75f, 0.125f);
|
||||
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE3, 0.375f, 0.625f);
|
||||
drainErrors();
|
||||
|
||||
final float[][] expected = { { 0.25f, 0.5f }, { 240.0f, 16.0f }, { 0.75f, 0.125f }, { 0.375f, 0.625f } };
|
||||
for (int unit = 0; unit < 4; unit++) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
GLStateManager.glGetFloat(GL11.GL_CURRENT_TEXTURE_COORDS, buf);
|
||||
assertEquals(expected[unit][0], buf.get(0), "s of unit " + unit);
|
||||
assertEquals(expected[unit][1], buf.get(1), "t of unit " + unit);
|
||||
assertEquals(0.0f, buf.get(2), "r of unit " + unit);
|
||||
assertEquals(1.0f, buf.get(3), "q of unit " + unit);
|
||||
}
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
for (int unit = 0; unit < 4; unit++) {
|
||||
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0 + unit, saved[unit][0], saved[unit][1]);
|
||||
}
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void currentTextureCoordsOfUntrackedUnitAreInitialValue() {
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(16);
|
||||
final int unit = GLStateManager.ctx().ffp.currentTexCoords.length + 1;
|
||||
try {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.25f, 0.5f);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
drainErrors();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
buf.put(i, -7.0f);
|
||||
}
|
||||
GLStateManager.glGetFloat(GL11.GL_CURRENT_TEXTURE_COORDS, buf);
|
||||
assertEquals(0.0f, buf.get(0));
|
||||
assertEquals(0.0f, buf.get(1));
|
||||
assertEquals(0.0f, buf.get(2));
|
||||
assertEquals(1.0f, buf.get(3));
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class BackendStartGuardTest {
|
||||
|
||||
private static File marker(Path dir) {
|
||||
return dir.resolve("angelica").resolve("sdlgpu-starting").toFile();
|
||||
}
|
||||
|
||||
@Test
|
||||
void firstStartCreatesTheMarkerAndFinishRemovesIt(@TempDir Path dir) {
|
||||
final File marker = marker(dir);
|
||||
assertFalse(BackendStartGuard.begin(marker, true, false));
|
||||
assertFalse(BackendStartGuard.tripped());
|
||||
assertTrue(marker.isFile());
|
||||
BackendStartGuard.finish();
|
||||
assertFalse(marker.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftoverMarkerTripsOnceAndIsRemoved(@TempDir Path dir) {
|
||||
final File marker = marker(dir);
|
||||
assertFalse(BackendStartGuard.begin(marker, true, false));
|
||||
assertTrue(BackendStartGuard.begin(marker, true, false));
|
||||
assertTrue(BackendStartGuard.tripped());
|
||||
assertFalse(marker.exists());
|
||||
BackendStartGuard.finish();
|
||||
assertFalse(marker.exists());
|
||||
assertFalse(BackendStartGuard.begin(marker, true, false));
|
||||
assertFalse(BackendStartGuard.tripped());
|
||||
BackendStartGuard.finish();
|
||||
}
|
||||
|
||||
@Test
|
||||
void openGlConfigWritesNoMarkerAndClearsAStaleOne(@TempDir Path dir) throws IOException {
|
||||
final File marker = marker(dir);
|
||||
assertFalse(BackendStartGuard.begin(marker, false, false));
|
||||
assertFalse(marker.exists());
|
||||
assertTrue(marker.getParentFile().mkdirs());
|
||||
assertTrue(marker.createNewFile());
|
||||
assertFalse(BackendStartGuard.begin(marker, false, false));
|
||||
assertFalse(BackendStartGuard.tripped());
|
||||
assertFalse(marker.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void forcedFlagNeverTripsOrWrites(@TempDir Path dir) throws IOException {
|
||||
final File marker = marker(dir);
|
||||
assertFalse(BackendStartGuard.begin(marker, true, true));
|
||||
assertFalse(marker.exists());
|
||||
assertTrue(marker.getParentFile().mkdirs());
|
||||
assertTrue(marker.createNewFile());
|
||||
assertFalse(BackendStartGuard.begin(marker, true, true));
|
||||
assertFalse(BackendStartGuard.tripped());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class MoltenVKTest {
|
||||
|
||||
private static final String PRESENT_RESOURCE = "/com/gtnewhorizons/angelica/glsm/backend/MoltenVK.class";
|
||||
private static final String ABSENT_RESOURCE = "/macos/arm64/org/lwjgl/vulkan/absent.dylib";
|
||||
|
||||
@Test
|
||||
void detectOrderIsEnvThenJarThenSystem(@TempDir Path dir) throws IOException {
|
||||
final String[] system = { Files.createFile(dir.resolve("system.dylib")).toString() };
|
||||
final String[] none = { dir.resolve("missing.dylib").toString() };
|
||||
assertEquals(MoltenVK.Source.NONE, MoltenVK.detect(false, "/env/lib.dylib", true, system));
|
||||
assertEquals(MoltenVK.Source.ENV, MoltenVK.detect(true, "/env/lib.dylib", true, system));
|
||||
assertEquals(MoltenVK.Source.JAR, MoltenVK.detect(true, "", true, system));
|
||||
assertEquals(MoltenVK.Source.SYSTEM, MoltenVK.detect(true, null, false, system));
|
||||
assertEquals(MoltenVK.Source.NONE, MoltenVK.detect(true, null, false, none));
|
||||
assertEquals(1, dir.toFile().list().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarIsExtractedOnceAndNotRewritten(@TempDir Path dir) throws IOException {
|
||||
final File out = dir.resolve("natives").toFile();
|
||||
final String first = MoltenVK.resolve(MoltenVK.Source.JAR, null, PRESENT_RESOURCE, out, "arm64", new String[0]);
|
||||
final File extracted = new File(out, "libMoltenVK-arm64.dylib");
|
||||
assertEquals(extracted.getAbsolutePath(), first);
|
||||
assertTrue(extracted.length() > 0);
|
||||
assertTrue(extracted.setLastModified(1_000_000L));
|
||||
assertEquals(first, MoltenVK.resolve(MoltenVK.Source.JAR, null, PRESENT_RESOURCE, out, "arm64", new String[0]));
|
||||
assertEquals(1_000_000L, extracted.lastModified());
|
||||
assertEquals(1, out.list().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
void changedFileIsRewritten(@TempDir Path dir) throws IOException {
|
||||
final File extracted = new File(dir.toFile(), "libMoltenVK-arm64.dylib");
|
||||
Files.write(extracted.toPath(), new byte[] { 1, 2, 3 });
|
||||
MoltenVK.resolve(MoltenVK.Source.JAR, null, PRESENT_RESOURCE, dir.toFile(), "arm64", new String[0]);
|
||||
assertTrue(extracted.length() > 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void failedExtractionFallsBackToTheSystemInstall(@TempDir Path dir) throws IOException {
|
||||
final Path system = Files.createFile(dir.resolve("system.dylib"));
|
||||
final File notADirectory = Files.createFile(dir.resolve("blocker")).toFile();
|
||||
assertEquals(system.toString(), MoltenVK.resolve(MoltenVK.Source.JAR, null, PRESENT_RESOURCE, notADirectory, "arm64", new String[] { system.toString() }));
|
||||
assertNull(MoltenVK.resolve(MoltenVK.Source.JAR, null, ABSENT_RESOURCE, dir.toFile(), "arm64", new String[0]));
|
||||
}
|
||||
|
||||
@Test
|
||||
void envAndSystemAndNoneResolveWithoutExtracting(@TempDir Path dir) throws IOException {
|
||||
final Path system = Files.createFile(dir.resolve("system.dylib"));
|
||||
final File out = dir.resolve("natives").toFile();
|
||||
assertEquals("/env/lib.dylib", MoltenVK.resolve(MoltenVK.Source.ENV, "/env/lib.dylib", PRESENT_RESOURCE, out, "arm64", new String[0]));
|
||||
assertEquals(system.toString(), MoltenVK.resolve(MoltenVK.Source.SYSTEM, null, PRESENT_RESOURCE, out, "arm64", new String[] { system.toString() }));
|
||||
assertNull(MoltenVK.resolve(MoltenVK.Source.NONE, null, PRESENT_RESOURCE, out, "arm64", new String[] { system.toString() }));
|
||||
assertFalse(out.exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void archNames() {
|
||||
assertEquals("arm64", MoltenVK.archName("aarch64"));
|
||||
assertEquals("x64", MoltenVK.archName("x86_64"));
|
||||
}
|
||||
}
|
||||
@@ -252,6 +252,32 @@ class GLSMRedirectorTest {
|
||||
assertTrue(unmappedSeen().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void arbGenerateMipmapAliasIsRedirected() {
|
||||
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/ARBFramebufferObject", "glGenerateMipmap", "(I)V");
|
||||
|
||||
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
|
||||
|
||||
assertTrue(changed);
|
||||
assertEquals(GLSM, firstCall(cn).owner);
|
||||
assertEquals("glGenerateMipmap", firstCall(cn).name);
|
||||
assertEquals("(I)V", firstCall(cn).desc);
|
||||
assertTrue(unmappedSeen().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extGenerateMipmapAliasIsRedirected() {
|
||||
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/EXTFramebufferObject", "glGenerateMipmapEXT", "(I)V");
|
||||
|
||||
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
|
||||
|
||||
assertTrue(changed);
|
||||
assertEquals(GLSM, firstCall(cn).owner);
|
||||
assertEquals("glGenerateMipmap", firstCall(cn).name);
|
||||
assertEquals("(I)V", firstCall(cn).desc);
|
||||
assertTrue(unmappedSeen().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void newlyMappedReadPixelsIsRedirected() {
|
||||
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GL11", "glReadPixels", "(IIIIIILjava/nio/IntBuffer;)V");
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PixelStoreStateTest {
|
||||
|
||||
@Test
|
||||
void defaults() {
|
||||
assertTrue(PixelStoreState.DEFAULT.isDefault());
|
||||
assertEquals(4, PixelStoreState.DEFAULT.alignment());
|
||||
assertEquals(0, PixelStoreState.DEFAULT.rowLength());
|
||||
assertFalse(PixelStoreState.DEFAULT.swapBytes());
|
||||
assertFalse(PixelStoreState.DEFAULT.lsbFirst());
|
||||
}
|
||||
|
||||
@Test
|
||||
void withUpdatesUnpackParams() {
|
||||
PixelStoreState s = PixelStoreState.DEFAULT
|
||||
.with(GL11.GL_UNPACK_ALIGNMENT, 1)
|
||||
.with(GL11.GL_UNPACK_ROW_LENGTH, 64)
|
||||
.with(GL11.GL_UNPACK_SKIP_ROWS, 2)
|
||||
.with(GL11.GL_UNPACK_SKIP_PIXELS, 3)
|
||||
.with(GL12.GL_UNPACK_IMAGE_HEIGHT, 32)
|
||||
.with(GL12.GL_UNPACK_SKIP_IMAGES, 1)
|
||||
.with(GL11.GL_UNPACK_SWAP_BYTES, 1)
|
||||
.with(GL11.GL_UNPACK_LSB_FIRST, 1);
|
||||
assertEquals(new PixelStoreState(1, 64, 2, 3, 32, 1, true, true), s);
|
||||
assertFalse(s.isDefault());
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidValuesIgnored() {
|
||||
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 3));
|
||||
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 0));
|
||||
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_ROW_LENGTH, -1));
|
||||
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_SKIP_ROWS, -5));
|
||||
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL12.GL_UNPACK_IMAGE_HEIGHT, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void booleansAcceptAnyNonzero() {
|
||||
assertTrue(PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 2).swapBytes());
|
||||
assertTrue(PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_LSB_FIRST, -1).lsbFirst());
|
||||
PixelStoreState on = PixelStoreState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 1);
|
||||
assertFalse(on.with(GL11.GL_UNPACK_SWAP_BYTES, 0).swapBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void packPnamesSetTheSameFields() {
|
||||
PixelStoreState s = PixelStoreState.DEFAULT
|
||||
.with(GL11.GL_PACK_ALIGNMENT, 1)
|
||||
.with(GL11.GL_PACK_ROW_LENGTH, 64)
|
||||
.with(GL11.GL_PACK_SKIP_ROWS, 2)
|
||||
.with(GL11.GL_PACK_SKIP_PIXELS, 3)
|
||||
.with(GL12.GL_PACK_IMAGE_HEIGHT, 32)
|
||||
.with(GL12.GL_PACK_SKIP_IMAGES, 1)
|
||||
.with(GL11.GL_PACK_SWAP_BYTES, 1)
|
||||
.with(GL11.GL_PACK_LSB_FIRST, 1);
|
||||
assertEquals(new PixelStoreState(1, 64, 2, 3, 32, 1, true, true), s);
|
||||
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_PACK_ALIGNMENT, 3));
|
||||
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(GL11.GL_PACK_ROW_LENGTH, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unknownPnamesIgnored() {
|
||||
assertSame(PixelStoreState.DEFAULT, PixelStoreState.DEFAULT.with(0x1234, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unchangedValueReturnsSameInstance() {
|
||||
final PixelStoreState s = new PixelStoreState(1, 64, 2, 3, 32, 1, true, true);
|
||||
assertSame(s, s.with(GL11.GL_UNPACK_ALIGNMENT, 1));
|
||||
assertSame(s, s.with(GL11.GL_PACK_ALIGNMENT, 1));
|
||||
assertSame(s, s.with(GL11.GL_UNPACK_ROW_LENGTH, 64));
|
||||
assertSame(s, s.with(GL11.GL_PACK_ROW_LENGTH, 64));
|
||||
assertSame(s, s.with(GL11.GL_UNPACK_SKIP_ROWS, 2));
|
||||
assertSame(s, s.with(GL11.GL_PACK_SKIP_ROWS, 2));
|
||||
assertSame(s, s.with(GL11.GL_UNPACK_SKIP_PIXELS, 3));
|
||||
assertSame(s, s.with(GL11.GL_PACK_SKIP_PIXELS, 3));
|
||||
assertSame(s, s.with(GL12.GL_UNPACK_IMAGE_HEIGHT, 32));
|
||||
assertSame(s, s.with(GL12.GL_PACK_IMAGE_HEIGHT, 32));
|
||||
assertSame(s, s.with(GL12.GL_UNPACK_SKIP_IMAGES, 1));
|
||||
assertSame(s, s.with(GL12.GL_PACK_SKIP_IMAGES, 1));
|
||||
assertSame(s, s.with(GL11.GL_UNPACK_SWAP_BYTES, 5));
|
||||
assertSame(s, s.with(GL11.GL_PACK_SWAP_BYTES, 5));
|
||||
assertSame(s, s.with(GL11.GL_UNPACK_LSB_FIRST, 1));
|
||||
assertSame(s, s.with(GL11.GL_PACK_LSB_FIRST, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isPackSeparatesTheFamilies() {
|
||||
final int[] pack = {
|
||||
GL11.GL_PACK_ALIGNMENT, GL11.GL_PACK_ROW_LENGTH, GL11.GL_PACK_SKIP_ROWS, GL11.GL_PACK_SKIP_PIXELS,
|
||||
GL12.GL_PACK_IMAGE_HEIGHT, GL12.GL_PACK_SKIP_IMAGES, GL11.GL_PACK_SWAP_BYTES, GL11.GL_PACK_LSB_FIRST,
|
||||
};
|
||||
final int[] unpack = {
|
||||
GL11.GL_UNPACK_ALIGNMENT, GL11.GL_UNPACK_ROW_LENGTH, GL11.GL_UNPACK_SKIP_ROWS, GL11.GL_UNPACK_SKIP_PIXELS,
|
||||
GL12.GL_UNPACK_IMAGE_HEIGHT, GL12.GL_UNPACK_SKIP_IMAGES, GL11.GL_UNPACK_SWAP_BYTES, GL11.GL_UNPACK_LSB_FIRST,
|
||||
};
|
||||
for (final int pname : pack) {
|
||||
assertTrue(PixelStoreState.isPack(pname));
|
||||
}
|
||||
for (final int pname : unpack) {
|
||||
assertFalse(PixelStoreState.isPack(pname));
|
||||
}
|
||||
assertFalse(PixelStoreState.isPack(0x1234));
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PixelUnpackStateTest {
|
||||
|
||||
@Test
|
||||
void defaults() {
|
||||
assertTrue(PixelUnpackState.DEFAULT.isDefault());
|
||||
assertEquals(4, PixelUnpackState.DEFAULT.alignment());
|
||||
assertEquals(0, PixelUnpackState.DEFAULT.rowLength());
|
||||
assertFalse(PixelUnpackState.DEFAULT.swapBytes());
|
||||
assertFalse(PixelUnpackState.DEFAULT.lsbFirst());
|
||||
}
|
||||
|
||||
@Test
|
||||
void withUpdatesUnpackParams() {
|
||||
PixelUnpackState s = PixelUnpackState.DEFAULT
|
||||
.with(GL11.GL_UNPACK_ALIGNMENT, 1)
|
||||
.with(GL11.GL_UNPACK_ROW_LENGTH, 64)
|
||||
.with(GL11.GL_UNPACK_SKIP_ROWS, 2)
|
||||
.with(GL11.GL_UNPACK_SKIP_PIXELS, 3)
|
||||
.with(GL12.GL_UNPACK_IMAGE_HEIGHT, 32)
|
||||
.with(GL12.GL_UNPACK_SKIP_IMAGES, 1)
|
||||
.with(GL11.GL_UNPACK_SWAP_BYTES, 1)
|
||||
.with(GL11.GL_UNPACK_LSB_FIRST, 1);
|
||||
assertEquals(new PixelUnpackState(1, 64, 2, 3, 32, 1, true, true), s);
|
||||
assertFalse(s.isDefault());
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidValuesIgnored() {
|
||||
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 3));
|
||||
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 0));
|
||||
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ROW_LENGTH, -1));
|
||||
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SKIP_ROWS, -5));
|
||||
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL12.GL_UNPACK_IMAGE_HEIGHT, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void booleansAcceptAnyNonzero() {
|
||||
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 2).swapBytes());
|
||||
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_LSB_FIRST, -1).lsbFirst());
|
||||
PixelUnpackState on = PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 1);
|
||||
assertFalse(on.with(GL11.GL_UNPACK_SWAP_BYTES, 0).swapBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void packAndUnknownPnamesIgnored() {
|
||||
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ALIGNMENT, 1));
|
||||
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ROW_LENGTH, 16));
|
||||
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(0x1234, 1));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@
|
||||
# LOCAL to test local config updates.
|
||||
gtnh.settings.blowdryerTag = 0.2.2
|
||||
|
||||
# See mod-version-gradle.kts
|
||||
gtnh.modules.gitVersion = false
|
||||
|
||||
# Human-readable mod name, available for mcmod.info population.
|
||||
modName = Angelica
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ launchwrapper = "1.12"
|
||||
# End Frozen
|
||||
|
||||
lwjgl3 = "3.4.2-SNAPSHOT"
|
||||
# Must equal the LWJGL release shipped by lwjgl3ify. Do not bump without bumping lwjgl3ify.
|
||||
lwjgl3Release = "3.4.2"
|
||||
lwjgl3ify = "3.0.31"
|
||||
|
||||
celeritas = "2.5.12-GTNH"
|
||||
@@ -75,6 +77,7 @@ lwjgl3-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl3" }
|
||||
lwjgl3-sdl = { module = "org.lwjgl:lwjgl-sdl", version.ref = "lwjgl3" }
|
||||
lwjgl3-spvc = { module = "org.lwjgl:lwjgl-spvc", version.ref = "lwjgl3" }
|
||||
lwjgl3-shaderc = { module = "org.lwjgl:lwjgl-shaderc", version.ref = "lwjgl3" }
|
||||
lwjgl3-vulkan = { module = "org.lwjgl:lwjgl-vulkan", version.ref = "lwjgl3Release" }
|
||||
|
||||
# Consumed with an explicit :dev / :universal / :downgraded-8 classifier at the call site.
|
||||
gtnhlib = { module = "com.github.GTNewHorizons:GTNHLib", version.ref = "gtnhlib" }
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Replaces GTNHGradle's GitVersionModule (gtnh.modules.gitVersion = false): same version format, but only
|
||||
// x.y.z[-suffix] tags count, so additional tags (moltenvk-v*, tracy-v*) on master never become the mod version.
|
||||
|
||||
fun git(vararg args: String): String? = try {
|
||||
val exec = providers.exec {
|
||||
commandLine("git", *args)
|
||||
workingDir = rootDir
|
||||
isIgnoreExitValue = true
|
||||
}
|
||||
if (exec.result.get().exitValue == 0) exec.standardOutput.asText.get().trim() else null
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
fun gitVersion(): String {
|
||||
val described = git("describe", "--tags", "--long", "--first-parent", "--abbrev=7", "--match=[0-9]*.[0-9]*.[0-9]*", "HEAD")
|
||||
val match = described?.let { Regex("(.*)-([0-9]+)-g[0-9a-f]+").matchEntire(it) }
|
||||
val tag = match?.groupValues?.get(1)
|
||||
val distance = match?.groupValues?.get(2)?.toInt() ?: 0
|
||||
val hash = git("rev-parse", "HEAD")?.take(10)?.ifEmpty { null }
|
||||
val base = tag ?: hash
|
||||
if (base == null) {
|
||||
logger.error("No git repository found; set the VERSION environment variable to version this build.")
|
||||
return "NO-GIT-TAG-SET"
|
||||
}
|
||||
val dirty = !git("status", "--porcelain").isNullOrEmpty()
|
||||
val branch = (git("branch", "--show-current")?.ifEmpty { null } ?: System.getenv("GIT_BRANCH") ?: "git")
|
||||
.removePrefix("origin/")
|
||||
.replace(Regex("[^a-zA-Z0-9-]+"), "-")
|
||||
return when {
|
||||
distance > 0 -> "$base-$branch.$distance+$hash${if (dirty) "-dirty" else ""}"
|
||||
dirty -> "$base-$branch+$hash-dirty"
|
||||
else -> base
|
||||
}
|
||||
}
|
||||
|
||||
extra["modVersion"] = providers.environmentVariable("VERSION").orNull ?: gitVersion()
|
||||
@@ -0,0 +1,112 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
version = rootProject.version
|
||||
|
||||
java {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile>().configureEach {
|
||||
options.release.set(8)
|
||||
}
|
||||
|
||||
val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation", String::class.java)
|
||||
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
|
||||
|
||||
configurations {
|
||||
listOf("apiElements", "runtimeElements").forEach { name ->
|
||||
named(name) {
|
||||
attributes {
|
||||
attribute(rfgObfAttr, "mcp")
|
||||
attribute(rfgTransformedAttr, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val moltenvkVersion = (findProperty("moltenvkVersion") as String?) ?: libs.versions.lwjgl3Release.get()
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "Forge"
|
||||
url = uri("https://maven.minecraftforge.net/")
|
||||
}
|
||||
maven {
|
||||
name = "Forge artifact-only"
|
||||
url = uri("https://maven.minecraftforge.net/")
|
||||
metadataSources { artifact() }
|
||||
content { includeModule("net.minecraftforge", "forge") }
|
||||
}
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
val moltenvkNatives by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("net.minecraftforge:forge:1.7.10-10.13.4.1614-1.7.10:universal") { isTransitive = false }
|
||||
|
||||
listOf("natives-macos", "natives-macos-arm64").forEach { nativesClassifier ->
|
||||
moltenvkNatives(libs.lwjgl3.vulkan) {
|
||||
artifact { classifier = nativesClassifier }
|
||||
isTransitive = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val extractMoltenVK = tasks.register<Sync>("extractMoltenVK") {
|
||||
val outDir = layout.buildDirectory.dir("generated/resources/moltenvk")
|
||||
from(provider { moltenvkNatives.files.map { zipTree(it) } }) {
|
||||
include("macos/**/libMoltenVK.dylib")
|
||||
}
|
||||
includeEmptyDirs = false
|
||||
into(outDir)
|
||||
doLast {
|
||||
listOf("macos/x64", "macos/arm64").forEach { dir ->
|
||||
val rel = "$dir/org/lwjgl/vulkan/libMoltenVK.dylib"
|
||||
if (!outDir.get().file(rel).asFile.isFile) {
|
||||
throw GradleException("MoltenVK dylib missing after extraction: $rel")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sourceSets["main"].resources.srcDir(extractMoltenVK)
|
||||
|
||||
val distJar = tasks.register<Jar>("distJar") {
|
||||
archiveBaseName.set("angelica-moltenvk")
|
||||
archiveVersion.set(moltenvkVersion)
|
||||
from(sourceSets["main"].output)
|
||||
manifest {
|
||||
attributes(
|
||||
"FMLCorePlugin" to "com.gtnewhorizons.angelica.moltenvk.loading.MoltenVKCorePlugin"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
groupId = "com.gtnewhorizons.angelica"
|
||||
artifactId = "angelica-moltenvk"
|
||||
version = moltenvkVersion
|
||||
artifact(distJar)
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
if (hasProperty("publishMoltenVK") && System.getenv("MAVEN_USER") != null) {
|
||||
maven {
|
||||
name = "GTNHMaven"
|
||||
url = uri(rootProject.findProperty("mavenPublishUrl")?.toString() ?: "https://nexus.gtnewhorizons.com/repository/releases/")
|
||||
credentials {
|
||||
username = System.getenv("MAVEN_USER")
|
||||
password = System.getenv("MAVEN_PASSWORD")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.gtnewhorizons.angelica.moltenvk.loading;
|
||||
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@IFMLLoadingPlugin.MCVersion("1.7.10")
|
||||
public class MoltenVKCorePlugin implements IFMLLoadingPlugin {
|
||||
|
||||
@Override
|
||||
public String[] getASMTransformerClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModContainerClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSetupClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectData(Map<String, Object> data) {}
|
||||
|
||||
@Override
|
||||
public String getAccessTransformerClass() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -9,6 +9,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;
|
||||
}
|
||||
|
||||
+206
-135
@@ -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=");
|
||||
|
||||
+2
-2
@@ -698,8 +698,8 @@ public final class PipelineApplier {
|
||||
final int offset = st.uboRangeOffset[binding];
|
||||
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
|
||||
if (pm != null) {
|
||||
if (offset < 0 || offset + size > pm.staging.capacity()) return 0L;
|
||||
return MemoryUtil.memAddress0(pm.staging) + offset;
|
||||
if (!pm.covers(offset, size)) return 0L;
|
||||
return MemoryUtil.memAddress0(pm.staging) + pm.stagingIndex(offset);
|
||||
}
|
||||
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
|
||||
if (shadow != null) {
|
||||
|
||||
+4
-7
@@ -8,16 +8,13 @@ public final class ByteRegionCopy {
|
||||
private ByteRegionCopy() {}
|
||||
|
||||
public static void copyByteRegion(ByteBuffer src, int srcOff, ByteBuffer dst, int dstOff, int len) {
|
||||
if ((srcOff | dstOff | len) < 0 || srcOff + (long) len > src.capacity() || dstOff + (long) len > dst.capacity()) {
|
||||
throw new IllegalStateException("copyByteRegion out of bounds: len=" + len + " srcOff=" + srcOff + " src.capacity=" + src.capacity() + " dstOff=" + dstOff + " dst.capacity=" + dst.capacity());
|
||||
}
|
||||
if (src.isDirect() && dst.isDirect()) {
|
||||
MemoryUtil.memCopy(MemoryUtil.memAddress(src, srcOff), MemoryUtil.memAddress(dst, dstOff), len);
|
||||
return;
|
||||
}
|
||||
if (src.hasArray() && dst.hasArray()) {
|
||||
System.arraycopy(src.array(), src.arrayOffset() + srcOff, dst.array(), dst.arrayOffset() + dstOff, len);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
dst.put(dstOff + i, src.get(srcOff + i));
|
||||
}
|
||||
dst.put(dstOff, src, srcOff, len);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class MappedRange {
|
||||
public int glId;
|
||||
public ByteBuffer staging;
|
||||
public long offset;
|
||||
public long length;
|
||||
public boolean invalidate;
|
||||
public int accessFlags;
|
||||
}
|
||||
+35
-21
@@ -33,11 +33,14 @@ public final class PersistentBufferSync {
|
||||
this.sink = sink;
|
||||
}
|
||||
|
||||
public void onPersistentBufferWrite(int glId, long offset, long size) {
|
||||
public boolean onPersistentBufferWrite(int glId, long offset, long size) {
|
||||
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
|
||||
if (pm != null && pm.markDirty(offset, size)) {
|
||||
if (pm == null || size == 0) return true;
|
||||
if (offset < 0 || size < 0 || offset > pm.length || size > pm.length - offset) return false;
|
||||
if (pm.markDirty(offset, size)) {
|
||||
resourceManager.trackPersistentDirty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void uploadDirtyPersistentRegion() { processDirtyPersistentRegions(false); }
|
||||
@@ -59,30 +62,41 @@ public final class PersistentBufferSync {
|
||||
for (int i = 0; i < n; i++) {
|
||||
final PersistentMapping pm = vals.get(i);
|
||||
if (!pm.isDirty()) continue;
|
||||
final long gpuHandle = resourceManager.getBufferHandle(keys.getInt(i));
|
||||
if (gpuHandle == 0) continue;
|
||||
final long claimed = pm.claimDirty();
|
||||
if (PersistentMapping.isClean(claimed)) continue;
|
||||
final long off = PersistentMapping.rangeOffset(claimed);
|
||||
final long size = PersistentMapping.rangeSize(claimed);
|
||||
if (Tracy.ENABLED) frameManager.notePersistentDrain();
|
||||
if (defer) {
|
||||
final long seq = sink.nextSeq();
|
||||
pm.lastEnqueuedSeq = seq;
|
||||
sink.enqueue(TransferThread.StagingReadUpload.acquire(pm.staging, off, size, gpuHandle, off, seq, false));
|
||||
enqueued++;
|
||||
} else {
|
||||
pm.staging.position((int) off).limit((int) (off + size));
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), pm.staging, gpuHandle, off, false);
|
||||
pm.staging.clear();
|
||||
final int glId = keys.getInt(i);
|
||||
long copyPass = 0L;
|
||||
if (!defer) {
|
||||
if (resourceManager.getBufferHandle(glId) == 0) continue;
|
||||
copyPass = frameManager.ensureCopyPass();
|
||||
}
|
||||
resourceManager.beginPersistentDrain();
|
||||
try {
|
||||
if (resourceManager.getPersistentMapping(glId) != pm) continue;
|
||||
final long gpuHandle = resourceManager.getBufferHandle(glId);
|
||||
if (gpuHandle == 0) continue;
|
||||
final long claimed = pm.claimDirty();
|
||||
if (PersistentMapping.isClean(claimed)) continue;
|
||||
resourceManager.clearPersistentDirty();
|
||||
final long off = PersistentMapping.rangeOffset(claimed);
|
||||
final long size = PersistentMapping.rangeSize(claimed);
|
||||
if (Tracy.ENABLED) frameManager.notePersistentDrain();
|
||||
if (defer) {
|
||||
final long seq = sink.nextSeq();
|
||||
pm.lastEnqueuedSeq = seq;
|
||||
sink.enqueue(TransferThread.StagingReadUpload.acquire(pm.staging, off, size, gpuHandle, pm.offset + off, seq, false));
|
||||
enqueued++;
|
||||
} else {
|
||||
resourceManager.uploadRangeToBuffer(copyPass, pm.staging, off, size, gpuHandle, pm.offset + off, false);
|
||||
}
|
||||
} finally {
|
||||
resourceManager.endPersistentDrain();
|
||||
}
|
||||
resourceManager.clearPersistentDirty();
|
||||
}
|
||||
return enqueued;
|
||||
}
|
||||
|
||||
public static void mirrorPersistentCopy(ByteBuffer srcStaging, long srcOffset, ByteBuffer dstStaging, long dstOffset, long size) {
|
||||
ByteRegionCopy.copyByteRegion(srcStaging, (int) srcOffset, dstStaging, (int) dstOffset, (int) size);
|
||||
public static void mirrorPersistentCopy(PersistentMapping src, long readOffset, PersistentMapping dst, long writeOffset, long size) {
|
||||
if (!src.covers(readOffset, size) || !dst.covers(writeOffset, size)) return;
|
||||
ByteRegionCopy.copyByteRegion(src.staging, (int) src.stagingIndex(readOffset), dst.staging, (int) dst.stagingIndex(writeOffset), (int) size);
|
||||
}
|
||||
|
||||
public void mirrorEboShadow(int glId, ByteBuffer src, int dstOffset, int len) {
|
||||
|
||||
+6
@@ -24,6 +24,12 @@ public final class PersistentMapping {
|
||||
public static long rangeSize(long packed) { return (packed & 0xFFFFFFFFL) - (packed >>> 32); }
|
||||
static long packRange(long min, long max) { return (min << 32) | (max & 0xFFFFFFFFL); }
|
||||
|
||||
public long stagingIndex(long bufferOffset) { return bufferOffset - offset; }
|
||||
|
||||
public boolean covers(long bufferOffset, long size) {
|
||||
return size >= 0 && bufferOffset >= offset && bufferOffset - offset <= length && size <= length - (bufferOffset - offset);
|
||||
}
|
||||
|
||||
public boolean isDirty() { return dirtyRange.get() != CLEAN; }
|
||||
|
||||
public long claimDirty() { return dirtyRange.getAndSet(CLEAN); }
|
||||
|
||||
+193
-44
@@ -31,6 +31,7 @@ import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@@ -96,6 +97,9 @@ public final class ResourceManager {
|
||||
|
||||
private final Int2ObjectOpenHashMap<ContextState.VAOState> vaoStates = new Int2ObjectOpenHashMap<>();
|
||||
private final Int2ObjectOpenHashMap<PersistentMapping> persistentMappings = new Int2ObjectOpenHashMap<>();
|
||||
private final Int2ObjectOpenHashMap<MappedRange> plainMappings = new Int2ObjectOpenHashMap<>();
|
||||
private MappedRange[] plainMappingPool = new MappedRange[4];
|
||||
private int plainMappingPoolCount;
|
||||
private final Int2ObjectOpenHashMap<ByteBuffer> uboShadows = new Int2ObjectOpenHashMap<>();
|
||||
private final Int2ObjectOpenHashMap<ByteBuffer> eboShadows = new Int2ObjectOpenHashMap<>();
|
||||
private final IntOpenHashSet eboShadowWanted = new IntOpenHashSet();
|
||||
@@ -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) {}
|
||||
|
||||
+9
-5
@@ -329,11 +329,15 @@ public final class TransferThread {
|
||||
final ByteBuffer mapped = SDL_MapGPUTransferBuffer(device.getDevice(), xfer, true, mapSize);
|
||||
if (mapped != null) {
|
||||
final ByteBuffer src = sr.stagingBuffer;
|
||||
final int prevPos = src.position();
|
||||
final int prevLim = src.limit();
|
||||
src.limit((int) (sr.srcOffset + size)).position((int) sr.srcOffset);
|
||||
mapped.put(src);
|
||||
src.limit(prevLim).position(prevPos);
|
||||
if (sr.srcOffset < 0 || sr.srcOffset + size > src.capacity() || size > mapped.capacity()) {
|
||||
LOG.error("StagingReadUpload out of bounds: srcOffset={} size={} src.capacity={} mapped.capacity={}; upload dropped", sr.srcOffset, size, src.capacity(), mapped.capacity());
|
||||
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
|
||||
resourceManager.returnTransferBufferThreadSafe(xfer, size);
|
||||
retireSeq(sr.seq);
|
||||
StagingReadUpload.release(sr);
|
||||
return;
|
||||
}
|
||||
MemoryUtil.memCopy(MemoryUtil.memAddress0(src) + sr.srcOffset, MemoryUtil.memAddress0(mapped), size);
|
||||
}
|
||||
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
|
||||
recordBufferUpload(xfer, size, sr.dstGpuBuffer, sr.dstOffset, sr.seq, sr.cycle);
|
||||
|
||||
+123
-30
@@ -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();
|
||||
|
||||
+3
-1
@@ -102,6 +102,7 @@ final class ShaderStageSerializer {
|
||||
out.writeInt(r.counts().numStorageBuffers());
|
||||
out.writeInt(r.counts().numStorageTextures());
|
||||
writeStrList(out, r.samplerNames());
|
||||
writeStrList(out, r.unusedSamplerNames());
|
||||
writeStrList(out, r.extraUniformNames());
|
||||
writeStrList(out, r.storageImageNames());
|
||||
out.writeInt(r.uboSize());
|
||||
@@ -139,6 +140,7 @@ final class ShaderStageSerializer {
|
||||
private static StageReflection readReflection(ByteBuffer in) {
|
||||
final ResourceCounts counts = new ResourceCounts(in.getInt(), in.getInt(), in.getInt(), in.getInt());
|
||||
final List<String> samplerNames = readStrList(in);
|
||||
final List<String> unusedSamplerNames = readStrList(in);
|
||||
final List<String> extraNames = readStrList(in);
|
||||
final List<String> storageImageNames = readStrList(in);
|
||||
final int uboSize = in.getInt();
|
||||
@@ -156,7 +158,7 @@ final class ShaderStageSerializer {
|
||||
for (int b = 0; b < blockCount; b++) {
|
||||
blocks[b] = new BlockReflection(in.getInt(), in.getInt(), readList(in, ShaderStageSerializer::readMember), in.get() != 0);
|
||||
}
|
||||
return new StageReflection(counts, samplerNames, extraNames, storageImageNames, uboSize, uboMembers, vsInputs, vsOutputs, fsInputs,
|
||||
return new StageReflection(counts, samplerNames, unusedSamplerNames, extraNames, storageImageNames, uboSize, uboMembers, vsInputs, vsOutputs, fsInputs,
|
||||
maxOutputLocation, roBuf, rwBuf, roTex, rwTex, blocks);
|
||||
}
|
||||
|
||||
|
||||
+15
-11
@@ -1,32 +1,36 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.system.Platform;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.device.Device.driverCandidates;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.device.Device.metalNeedsNewerSdl;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.device.Device.resolveDriverName;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLVersion.SDL_VERSIONNUM;
|
||||
|
||||
class DriverSelectionTest {
|
||||
|
||||
@Test
|
||||
void windowsWithoutUserHintForcesVulkan() {
|
||||
assertEquals("vulkan", resolveDriverName("", true));
|
||||
void autoUsesThePlatformOrder() {
|
||||
assertArrayEquals(new String[] { "metal", "vulkan" }, driverCandidates("", Platform.MACOSX));
|
||||
assertArrayEquals(new String[] { "vulkan", "direct3d12" }, driverCandidates("", Platform.WINDOWS));
|
||||
assertArrayEquals(new String[] { "vulkan" }, driverCandidates("", Platform.LINUX));
|
||||
}
|
||||
|
||||
@Test
|
||||
void userHintWinsOnWindows() {
|
||||
assertNull(resolveDriverName("direct3d12", true));
|
||||
assertNull(resolveDriverName("vulkan", true));
|
||||
void requestedDriverGoesFirstWithoutDuplicates() {
|
||||
assertArrayEquals(new String[] { "vulkan", "metal" }, driverCandidates("vulkan", Platform.MACOSX));
|
||||
assertArrayEquals(new String[] { "direct3d12", "vulkan" }, driverCandidates("direct3d12", Platform.WINDOWS));
|
||||
assertArrayEquals(new String[] { "vulkan" }, driverCandidates("vulkan", Platform.LINUX));
|
||||
assertArrayEquals(new String[] { "Vulkan", "direct3d12" }, driverCandidates("Vulkan", Platform.WINDOWS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonWindowsIsUntouched() {
|
||||
assertNull(resolveDriverName("", false));
|
||||
assertNull(resolveDriverName("direct3d12", false));
|
||||
void unsupportedDriverStillFallsBack() {
|
||||
assertArrayEquals(new String[] { "metal", "vulkan", "direct3d12" }, driverCandidates("metal", Platform.WINDOWS));
|
||||
assertArrayEquals(new String[] { "bogus", "vulkan" }, driverCandidates("bogus", Platform.LINUX));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+393
@@ -0,0 +1,393 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.opengl.GL44;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlBufferMappingLifetimeTest {
|
||||
|
||||
private static final int SIZE = 4096;
|
||||
private static final int PERSISTENT_STORAGE = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT | GL44.GL_CLIENT_STORAGE_BIT;
|
||||
private static final int PERSISTENT_MAP = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT;
|
||||
private static final int PLAIN_MAP = GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
}
|
||||
|
||||
private static void fill(ByteBuffer buf, int seed) {
|
||||
for (int i = 0; i < buf.capacity(); i++) buf.put(i, (byte) (seed * 37 + i * 7 + 1));
|
||||
}
|
||||
|
||||
private static void assertPattern(ByteBuffer actual, int seed) {
|
||||
for (int i = 0; i < actual.capacity(); i++) {
|
||||
assertEquals((byte) (seed * 37 + i * 7 + 1), actual.get(i), "byte " + i + " of pattern " + seed);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer readBack(int target, int size) {
|
||||
final ByteBuffer out = MemoryUtil.memAlloc(size);
|
||||
GLStateManager.glGetBufferSubData(target, 0, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static ByteBuffer mapPersistent(int target, int id) {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferStorage(target, SIZE, PERSISTENT_STORAGE);
|
||||
return GLStateManager.glMapBufferRange(target, 0, SIZE, PERSISTENT_MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bufferDataOnPersistentlyMappedBufferIsRefusedAndMappingSurvives() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
final ByteBuffer mapped = mapPersistent(target, id);
|
||||
assertNotNull(mapped);
|
||||
GLStateManager.glBufferData(target, SIZE * 2L, GL15.GL_STATIC_DRAW);
|
||||
fill(mapped, 1);
|
||||
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
|
||||
out = readBack(target, SIZE);
|
||||
assertPattern(out, 1);
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistentMapWithoutPersistentStorageReturnsNull() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferData(target, (long) SIZE, GL15.GL_STATIC_DRAW);
|
||||
assertNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PERSISTENT_MAP));
|
||||
} finally {
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondPersistentMapReturnsNullAndFirstStillRoundTrips() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
final ByteBuffer first = mapPersistent(target, id);
|
||||
assertNotNull(first);
|
||||
assertNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PERSISTENT_MAP));
|
||||
fill(first, 2);
|
||||
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
|
||||
out = readBack(target, SIZE);
|
||||
assertPattern(out, 2);
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoBuffersMappedNonPersistentlyKeepTheirOwnData() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int a = GLStateManager.glGenBuffers();
|
||||
final int b = GLStateManager.glGenBuffers();
|
||||
ByteBuffer outA = null;
|
||||
ByteBuffer outB = null;
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, a);
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
GLStateManager.glBindBuffer(target, b);
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
|
||||
GLStateManager.glBindBuffer(target, a);
|
||||
final ByteBuffer mapA = GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP);
|
||||
assertNotNull(mapA);
|
||||
GLStateManager.glBindBuffer(target, b);
|
||||
final ByteBuffer mapB = GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP);
|
||||
assertNotNull(mapB);
|
||||
fill(mapA, 3);
|
||||
fill(mapB, 4);
|
||||
|
||||
GLStateManager.glBindBuffer(target, a);
|
||||
assertTrue(GLStateManager.glUnmapBuffer(target));
|
||||
GLStateManager.glBindBuffer(target, b);
|
||||
assertTrue(GLStateManager.glUnmapBuffer(target));
|
||||
|
||||
GLStateManager.glBindBuffer(target, a);
|
||||
outA = readBack(target, SIZE);
|
||||
assertPattern(outA, 3);
|
||||
GLStateManager.glBindBuffer(target, b);
|
||||
outB = readBack(target, SIZE);
|
||||
assertPattern(outB, 4);
|
||||
} finally {
|
||||
if (outA != null) MemoryUtil.memFree(outA);
|
||||
if (outB != null) MemoryUtil.memFree(outB);
|
||||
GLStateManager.glDeleteBuffers(a);
|
||||
GLStateManager.glDeleteBuffers(b);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void unmapOfUnmappedBufferReturnsFalseAndLeavesOtherMappingIntact() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int mappedId = GLStateManager.glGenBuffers();
|
||||
final int idle = GLStateManager.glGenBuffers();
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, mappedId);
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
final ByteBuffer map = GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP);
|
||||
assertNotNull(map);
|
||||
fill(map, 5);
|
||||
|
||||
GLStateManager.glBindBuffer(target, idle);
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
assertFalse(GLStateManager.glUnmapBuffer(target));
|
||||
|
||||
GLStateManager.glBindBuffer(target, mappedId);
|
||||
assertTrue(GLStateManager.glUnmapBuffer(target));
|
||||
out = readBack(target, SIZE);
|
||||
assertPattern(out, 5);
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
GLStateManager.glDeleteBuffers(mappedId);
|
||||
GLStateManager.glDeleteBuffers(idle);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void outOfRangeUniformSubDataIsIgnored() {
|
||||
final int ubo = GLStateManager.glGenBuffers();
|
||||
final ByteBuffer data = MemoryUtil.memAlloc(256);
|
||||
final ByteBuffer big = MemoryUtil.memAlloc(64);
|
||||
try {
|
||||
fill(data, 6);
|
||||
fill(big, 7);
|
||||
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, ubo);
|
||||
GLStateManager.glBufferData(GL31.GL_UNIFORM_BUFFER, data, GL15.GL_DYNAMIC_DRAW);
|
||||
assertDoesNotThrow(() -> {
|
||||
big.clear();
|
||||
GLStateManager.glBufferSubData(GL31.GL_UNIFORM_BUFFER, 1024, big);
|
||||
big.clear();
|
||||
GLStateManager.glBufferSubData(GL31.GL_UNIFORM_BUFFER, 224, big);
|
||||
big.clear();
|
||||
GLStateManager.glBufferSubData(GL31.GL_UNIFORM_BUFFER, -8, big);
|
||||
});
|
||||
} finally {
|
||||
MemoryUtil.memFree(data);
|
||||
MemoryUtil.memFree(big);
|
||||
GLStateManager.glDeleteBuffers(ubo);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void outOfRangeCopyBufferSubDataIsIgnored() {
|
||||
final int src = GLStateManager.glGenBuffers();
|
||||
final int dst = GLStateManager.glGenBuffers();
|
||||
final ByteBuffer data = MemoryUtil.memAlloc(256);
|
||||
ByteBuffer outDst = null;
|
||||
try {
|
||||
fill(data, 6);
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_READ_BUFFER, src);
|
||||
GLStateManager.glBufferData(GL31.GL_COPY_READ_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, dst);
|
||||
data.clear();
|
||||
GLStateManager.glBufferData(GL31.GL_COPY_WRITE_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 200, 0, 128);
|
||||
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0, 200, 128);
|
||||
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, -4, 0, 16);
|
||||
outDst = readBack(GL31.GL_COPY_WRITE_BUFFER, 256);
|
||||
assertPattern(outDst, 6);
|
||||
} finally {
|
||||
if (outDst != null) MemoryUtil.memFree(outDst);
|
||||
MemoryUtil.memFree(data);
|
||||
GLStateManager.glDeleteBuffers(src);
|
||||
GLStateManager.glDeleteBuffers(dst);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void outOfRangeClearBufferSubDataIsIgnored() {
|
||||
final int clearStart = 64;
|
||||
final int clearEnd = 96;
|
||||
final int ssbo = GLStateManager.glGenBuffers();
|
||||
final ByteBuffer data = MemoryUtil.memAlloc(256);
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
fill(data, 8);
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, ssbo);
|
||||
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, data, GL15.GL_DYNAMIC_DRAW);
|
||||
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, clearStart, clearEnd - clearStart, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
|
||||
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, 200, 128, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
|
||||
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, -4, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
|
||||
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, 1024, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
out = readBack(GL43.GL_SHADER_STORAGE_BUFFER, 256);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
final boolean cleared = i >= clearStart && i < clearEnd;
|
||||
assertEquals(cleared ? 0 : data.get(i), out.get(i), cleared ? "in-range clear did not zero byte " + i : "out-of-range clear touched byte " + i);
|
||||
}
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
MemoryUtil.memFree(data);
|
||||
GLStateManager.glDeleteBuffers(ssbo);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondBufferStorageIsRefused() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferStorage(target, SIZE, PERSISTENT_STORAGE);
|
||||
GLStateManager.glBufferStorage(target, SIZE * 2L, PERSISTENT_STORAGE);
|
||||
assertEquals(GL11.GL_TRUE, GLStateManager.glGetBufferParameteri(target, GL44.GL_BUFFER_IMMUTABLE_STORAGE));
|
||||
assertEquals(SIZE, GLStateManager.glGetBufferParameteri(target, GL15.GL_BUFFER_SIZE));
|
||||
} finally {
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedBufferDataOnImmutableStorageIsRefused() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferStorage(target, SIZE, PERSISTENT_STORAGE);
|
||||
GLStateManager.glNamedBufferData(id, SIZE * 2L, GL15.GL_STATIC_DRAW);
|
||||
assertEquals(SIZE, GLStateManager.glGetBufferParameteri(target, GL15.GL_BUFFER_SIZE));
|
||||
} finally {
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void bufferStorageWithNullDataMarksBufferImmutable() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferStorage(target, (ByteBuffer) null, PERSISTENT_STORAGE);
|
||||
assertEquals(GL11.GL_TRUE, GLStateManager.glGetBufferParameteri(target, GL44.GL_BUFFER_IMMUTABLE_STORAGE));
|
||||
} finally {
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertBufferDataUnmapsPlainMapping(int target) {
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
assertNotNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP));
|
||||
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
|
||||
assertNotNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP));
|
||||
assertTrue(GLStateManager.glUnmapBuffer(target));
|
||||
} finally {
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void bufferDataOnMappedArrayBufferUnmapsIt() {
|
||||
assertBufferDataUnmapsPlainMapping(GL15.GL_ARRAY_BUFFER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bufferDataOnMappedUniformBufferUnmapsIt() {
|
||||
assertBufferDataUnmapsPlainMapping(GL31.GL_UNIFORM_BUFFER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void outOfRangeFlushOfPersistentMappingIsIgnored() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
final ByteBuffer mapped = mapPersistent(target, id);
|
||||
assertNotNull(mapped);
|
||||
GLStateManager.glFlushMappedBufferRange(target, SIZE, 64);
|
||||
GLStateManager.glFlushMappedBufferRange(target, SIZE - 16, 64);
|
||||
GLStateManager.glFlushMappedBufferRange(target, -8, 16);
|
||||
fill(mapped, 9);
|
||||
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
|
||||
out = readBack(target, SIZE);
|
||||
assertPattern(out, 9);
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistentMappingAtNonZeroOffsetRoundTrips() {
|
||||
final int target = GL15.GL_ARRAY_BUFFER;
|
||||
final int id = GLStateManager.glGenBuffers();
|
||||
final ByteBuffer out = MemoryUtil.memAlloc(SIZE);
|
||||
try {
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferStorage(target, SIZE * 2L, PERSISTENT_STORAGE);
|
||||
final ByteBuffer mapped = GLStateManager.glMapBufferRange(target, SIZE, SIZE, PERSISTENT_MAP);
|
||||
assertNotNull(mapped);
|
||||
fill(mapped, 10);
|
||||
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
|
||||
GLStateManager.glGetBufferSubData(target, SIZE, out);
|
||||
assertPattern(out, 10);
|
||||
} finally {
|
||||
MemoryUtil.memFree(out);
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyFromPersistentMappingShorterThanBufferReadsGpuContents() {
|
||||
final int src = GLStateManager.glGenBuffers();
|
||||
final int dst = GLStateManager.glGenBuffers();
|
||||
final ByteBuffer data = MemoryUtil.memAlloc(SIZE * 2);
|
||||
ByteBuffer out = null;
|
||||
try {
|
||||
fill(data, 11);
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_READ_BUFFER, src);
|
||||
GLStateManager.glBufferStorage(GL31.GL_COPY_READ_BUFFER, data, PERSISTENT_STORAGE);
|
||||
assertNotNull(GLStateManager.glMapBufferRange(GL31.GL_COPY_READ_BUFFER, 0, SIZE, PERSISTENT_MAP));
|
||||
GLStateManager.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, dst);
|
||||
GLStateManager.glBufferData(GL31.GL_COPY_WRITE_BUFFER, SIZE, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, SIZE, 0, 256);
|
||||
out = readBack(GL31.GL_COPY_WRITE_BUFFER, 256);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
assertEquals(data.get(SIZE + i), out.get(i), "byte " + i);
|
||||
}
|
||||
} finally {
|
||||
if (out != null) MemoryUtil.memFree(out);
|
||||
MemoryUtil.memFree(data);
|
||||
GLStateManager.glDeleteBuffers(src);
|
||||
GLStateManager.glDeleteBuffers(dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPULWJGLService;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@@ -35,7 +35,7 @@ class GlsmSdlLWJGLServiceRoutingTest {
|
||||
}
|
||||
|
||||
private static int glsmUnpackAlignment() {
|
||||
final PixelUnpackState state = Reflect.get(glsmContext(), "pixelUnpackState");
|
||||
final PixelStoreState state = Reflect.get(glsmContext(), "pixelUnpackState");
|
||||
return state.alignment();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@@ -45,6 +47,30 @@ class GlsmSdlReadbackTest {
|
||||
GLStateManager.glDeleteTextures(texture);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getBufferSubDataSeesSameFrameStorageBufferWrite() {
|
||||
final int size = 64;
|
||||
final int ssbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, ssbo);
|
||||
|
||||
final ByteBuffer initial = BufferUtils.createByteBuffer(size);
|
||||
final ByteBuffer update = BufferUtils.createByteBuffer(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
initial.put(i, (byte) 0x11);
|
||||
update.put(i, (byte) (i + 1));
|
||||
}
|
||||
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, initial, GL15.GL_DYNAMIC_DRAW);
|
||||
GLStateManager.glBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, 0, update);
|
||||
|
||||
final ByteBuffer dst = BufferUtils.createByteBuffer(size);
|
||||
GLStateManager.glGetBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, 0, dst);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertEquals((byte) (i + 1), dst.get(i), "byte " + i + " should hold the same-frame sub-data write");
|
||||
}
|
||||
GLStateManager.glDeleteBuffers(ssbo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readPixelsSeesPriorDraw() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ class AbsoluteCopyRegionTest {
|
||||
for (int i = 0; i < 32; i++) src.put(i, (byte) i);
|
||||
src.position(7).limit(24);
|
||||
dst.position(9).limit(28);
|
||||
PersistentBufferSync.mirrorPersistentCopy(src, 4, dst, 16, 8);
|
||||
PersistentBufferSync.mirrorPersistentCopy(new PersistentMapping(src, 0L, 32L, 0), 4, new PersistentMapping(dst, 0L, 32L, 0), 16, 8);
|
||||
for (int i = 0; i < 8; i++) assertEquals((byte) (4 + i), dst.get(16 + i));
|
||||
assertEquals(7, src.position());
|
||||
assertEquals(24, src.limit());
|
||||
|
||||
+13
-11
@@ -40,7 +40,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(64);
|
||||
try {
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
|
||||
|
||||
assertFalse(rm.hasDirtyPersistentRegions(), "a mapping with no writes is not dirty");
|
||||
@@ -70,7 +70,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(64);
|
||||
try {
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
|
||||
|
||||
sync.onPersistentBufferWrite(1, 0, 16);
|
||||
@@ -95,7 +95,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
try {
|
||||
final int atStart = rm.getMappingsVersion();
|
||||
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
final int afterPut = rm.getMappingsVersion();
|
||||
assertNotEquals(atStart, afterPut, "adding a mapping must move the version");
|
||||
|
||||
@@ -107,7 +107,8 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
sync.enqueueDirtyPersistentRegions();
|
||||
assertEquals(afterPut, rm.getMappingsVersion(), "dirtying and draining is not a mapping change");
|
||||
|
||||
rm.swapPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.removePersistentMapping(1);
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
final int afterSwap = rm.getMappingsVersion();
|
||||
assertNotEquals(afterPut, afterSwap, "swapping a mapping must move the version");
|
||||
|
||||
@@ -129,7 +130,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(256);
|
||||
try {
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 256, 0);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
@@ -156,13 +157,14 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
final ByteBuffer first = MemoryUtil.memCalloc(64);
|
||||
final ByteBuffer second = MemoryUtil.memCalloc(64);
|
||||
try {
|
||||
rm.putPersistentMapping(1, new PersistentMapping(first, 0L, first.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(first, 0L, first.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
|
||||
sync.onPersistentBufferWrite(1, 0, 16);
|
||||
sync.enqueueDirtyPersistentRegions();
|
||||
assertEquals(1, sink.enqueued);
|
||||
|
||||
rm.swapPersistentMapping(1, new PersistentMapping(second, 0L, second.capacity(), 0));
|
||||
rm.removePersistentMapping(1);
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(second, 0L, second.capacity(), 0));
|
||||
sync.onPersistentBufferWrite(1, 0, 16);
|
||||
sync.enqueueDirtyPersistentRegions();
|
||||
assertEquals(2, sink.enqueued, "the drain must see the replacement, not a stale snapshot");
|
||||
@@ -184,7 +186,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(4096);
|
||||
try {
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 4096, 0);
|
||||
|
||||
final int writers = 6;
|
||||
@@ -235,7 +237,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(64);
|
||||
try {
|
||||
final PersistentMapping pm = new PersistentMapping(staging, 0L, staging.capacity(), 0);
|
||||
rm.putPersistentMapping(1, pm);
|
||||
rm.putPersistentMappingIfAbsent(1, pm);
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
|
||||
|
||||
final long[] stampAtEnqueue = {-1L};
|
||||
@@ -267,7 +269,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
final int seedCount = 100;
|
||||
for (int i = 0; i < seedCount; i++) {
|
||||
final ByteBuffer b = MemoryUtil.memAlloc(64);
|
||||
rm.putPersistentMapping(i, new PersistentMapping(b, 0L, b.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(i, new PersistentMapping(b, 0L, b.capacity(), 0));
|
||||
}
|
||||
|
||||
final AtomicBoolean stop = new AtomicBoolean(false);
|
||||
@@ -279,7 +281,7 @@ class PersistentBufferSyncDirtyGateTest {
|
||||
int next = seedCount;
|
||||
while (!stop.get()) {
|
||||
final ByteBuffer b = MemoryUtil.memAlloc(64);
|
||||
rm.putPersistentMapping(next, new PersistentMapping(b, 0L, b.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(next, new PersistentMapping(b, 0L, b.capacity(), 0));
|
||||
rm.removePersistentMapping(next - 50);
|
||||
next++;
|
||||
if (next > seedCount + 10000) next = seedCount;
|
||||
|
||||
+2
-2
@@ -37,13 +37,13 @@ class PersistentDrainBoundaryTest {
|
||||
final ResourceManager rm;
|
||||
final PersistentBufferSync sync;
|
||||
final RecordingSink sink = new RecordingSink();
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(4096);
|
||||
final ByteBuffer staging = MemoryUtil.memCalloc(16384);
|
||||
|
||||
Fixture() {
|
||||
final SdlTestRig rig = SdlTestRig.create();
|
||||
rm = rig.resourceManager;
|
||||
sync = new PersistentBufferSync(rig.frameManager, rm, sink);
|
||||
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
|
||||
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, staging.capacity(), 0);
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -18,14 +18,14 @@ class ReleasePersistentStagingTest {
|
||||
@Test
|
||||
void nullPmAndNullStagingNoOp() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
rm.releasePersistentStaging(null);
|
||||
rm.releasePersistentStaging(new PersistentMapping(null, 0L, 0L, 0));
|
||||
rm.releasePersistentStaging(null, 7, "unmap");
|
||||
rm.releasePersistentStaging(new PersistentMapping(null, 0L, 0L, 0), 7, "unmap");
|
||||
}
|
||||
|
||||
@Test
|
||||
void noTransferThreadFreesInline() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
rm.releasePersistentStaging(new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0));
|
||||
rm.releasePersistentStaging(new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0), 7, "unmap");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -39,7 +39,7 @@ class ReleasePersistentStagingTest {
|
||||
try {
|
||||
final PersistentMapping pm = new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0);
|
||||
pm.lastEnqueuedSeq = 0L;
|
||||
rm.releasePersistentStaging(pm);
|
||||
rm.releasePersistentStaging(pm, 7, "unmap");
|
||||
assertEquals(0, SdlReflect.pendingFreeCount(tt));
|
||||
} finally { shutdown(tt); }
|
||||
}
|
||||
@@ -55,7 +55,7 @@ class ReleasePersistentStagingTest {
|
||||
try {
|
||||
final PersistentMapping pm = new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0);
|
||||
pm.lastEnqueuedSeq = 50L;
|
||||
rm.releasePersistentStaging(pm);
|
||||
rm.releasePersistentStaging(pm, 7, "unmap");
|
||||
assertEquals(1, SdlReflect.pendingFreeCount(tt));
|
||||
} finally { shutdown(tt); }
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class ReleasePersistentStagingTest {
|
||||
void deleteBufferReleasesPersistentStaging() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
SdlReflect.recordBuffer(rm, 7, 0xDEADBEEFL, 64, 0);
|
||||
rm.putPersistentMapping(7, new PersistentMapping(MemoryUtil.memCalloc(64), 0L, 64L, 0));
|
||||
rm.putPersistentMappingIfAbsent(7, new PersistentMapping(MemoryUtil.memCalloc(64), 0L, 64L, 0));
|
||||
|
||||
rm.deleteBuffer(7);
|
||||
|
||||
@@ -84,7 +84,7 @@ class ReleasePersistentStagingTest {
|
||||
final PersistentMapping pm = new PersistentMapping(staging, 0L, staging.capacity(), 0);
|
||||
pm.lastEnqueuedSeq = 99L;
|
||||
SdlReflect.recordBuffer(rm, 7, 0xDEADBEEFL, 64, 0);
|
||||
rm.putPersistentMapping(7, pm);
|
||||
rm.putPersistentMappingIfAbsent(7, pm);
|
||||
|
||||
rm.deleteBuffer(7);
|
||||
|
||||
|
||||
+97
@@ -3,7 +3,9 @@ package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -159,4 +161,99 @@ class ResourceManagerConcurrencyTest {
|
||||
|
||||
assertTrue(created.get() >= threads, "each thread should have created at least one FBO");
|
||||
}
|
||||
|
||||
@Test
|
||||
void putPlainMappingIfAbsent_oneWinnerAcrossThreads() throws Exception {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
final int glId = 9001;
|
||||
final int n = 8;
|
||||
final AtomicInteger winners = new AtomicInteger();
|
||||
|
||||
race(n, slot -> {
|
||||
final ByteBuffer staging = MemoryUtil.memAlloc(16);
|
||||
if (rm.putPlainMappingIfAbsent(glId, staging, 0L, 16L, false, slot)) winners.incrementAndGet();
|
||||
else MemoryUtil.memFree(staging);
|
||||
});
|
||||
|
||||
assertEquals(1, winners.get(), "exactly one thread may map the buffer");
|
||||
final ByteBuffer staging = rm.takePlainStaging(glId);
|
||||
assertTrue(staging != null, "the winning mapping must still be registered");
|
||||
MemoryUtil.memFree(staging);
|
||||
assertNull(rm.takePlainStaging(glId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void plainMappingTakenFromAnotherThread() throws Exception {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
final int glId = 9002;
|
||||
final ByteBuffer staging = MemoryUtil.memAlloc(32);
|
||||
try {
|
||||
assertTrue(rm.putPlainMappingIfAbsent(glId, staging, 8L, 32L, true, 0x2A));
|
||||
final MappedRange peeked = new MappedRange();
|
||||
final MappedRange taken = new MappedRange();
|
||||
final AtomicBoolean peekedOk = new AtomicBoolean();
|
||||
final AtomicBoolean takenOk = new AtomicBoolean();
|
||||
final AtomicBoolean secondTake = new AtomicBoolean(true);
|
||||
|
||||
race(1, slot -> {
|
||||
peekedOk.set(rm.peekPlainMapping(glId, peeked));
|
||||
takenOk.set(rm.takePlainMapping(glId, taken));
|
||||
secondTake.set(rm.takePlainMapping(glId, new MappedRange()));
|
||||
});
|
||||
|
||||
assertTrue(peekedOk.get(), "peek from another thread must see the mapping");
|
||||
assertSame(staging, peeked.staging);
|
||||
assertTrue(takenOk.get(), "take from another thread must succeed");
|
||||
assertSame(staging, taken.staging);
|
||||
assertEquals(glId, taken.glId);
|
||||
assertEquals(8L, taken.offset);
|
||||
assertEquals(32L, taken.length);
|
||||
assertTrue(taken.invalidate);
|
||||
assertEquals(0x2A, taken.accessFlags);
|
||||
assertFalse(secondTake.get(), "a taken mapping must be gone");
|
||||
assertFalse(rm.peekPlainMapping(glId, new MappedRange()));
|
||||
} finally {
|
||||
MemoryUtil.memFree(staging);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteBufferFromAnotherThreadDropsPlainMapping() throws Exception {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
final int glId = 9003;
|
||||
assertTrue(rm.putPlainMappingIfAbsent(glId, MemoryUtil.memAlloc(16), 0L, 16L, false, 0));
|
||||
|
||||
race(1, slot -> rm.deleteBuffer(glId));
|
||||
|
||||
assertFalse(rm.peekPlainMapping(glId, new MappedRange()));
|
||||
assertFalse(rm.takePlainMapping(glId, new MappedRange()));
|
||||
final ByteBuffer again = MemoryUtil.memAlloc(16);
|
||||
assertTrue(rm.putPlainMappingIfAbsent(glId, again, 0L, 16L, false, 0), "a deleted buffer's id must be mappable again");
|
||||
MemoryUtil.memFree(rm.takePlainStaging(glId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void persistentAndPlainMappingsExcludeEachOther() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
final int plainFirst = 9004;
|
||||
final int persistentFirst = 9005;
|
||||
final ByteBuffer plainStaging = MemoryUtil.memAlloc(16);
|
||||
final ByteBuffer persistentStaging = MemoryUtil.memAlloc(16);
|
||||
final ByteBuffer refused = MemoryUtil.memAlloc(16);
|
||||
try {
|
||||
assertTrue(rm.putPlainMappingIfAbsent(plainFirst, plainStaging, 0L, 16L, false, 0));
|
||||
assertFalse(rm.putPersistentMappingIfAbsent(plainFirst, new PersistentMapping(refused, 0L, 16L, 0)), "persistent map of a plain-mapped buffer must be refused");
|
||||
assertNull(rm.getPersistentMapping(plainFirst));
|
||||
|
||||
assertTrue(rm.putPersistentMappingIfAbsent(persistentFirst, new PersistentMapping(persistentStaging, 0L, 16L, 0)));
|
||||
assertFalse(rm.putPlainMappingIfAbsent(persistentFirst, refused, 0L, 16L, false, 0), "plain map of a persistently mapped buffer must be refused");
|
||||
assertFalse(rm.peekPlainMapping(persistentFirst, new MappedRange()));
|
||||
} finally {
|
||||
rm.takePlainStaging(plainFirst);
|
||||
rm.removePersistentMapping(persistentFirst);
|
||||
MemoryUtil.memFree(plainStaging);
|
||||
MemoryUtil.memFree(persistentStaging);
|
||||
MemoryUtil.memFree(refused);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ class AttribLocationPatchTest {
|
||||
inputs[i] = new ShaderManager.VsInput(names[i], i, i, i < VEC_SIZES.length ? VEC_SIZES[i] : 4, i < BASE_TYPES.length ? BASE_TYPES[i] : Spvc.SPVC_BASETYPE_FP32);
|
||||
}
|
||||
final ShaderManager.StageReflection e = ShaderManager.StageReflection.EMPTY;
|
||||
return new ShaderManager.StageReflection(e.counts(), e.samplerNames(), e.extraUniformNames(),
|
||||
return new ShaderManager.StageReflection(e.counts(), e.samplerNames(), e.unusedSamplerNames(), e.extraUniformNames(),
|
||||
e.storageImageNames(), e.uboSize(), e.uboMembers(), List.of(inputs), e.vsOutputs(), e.fsInputs(),
|
||||
e.maxOutputLocation(), e.numReadonlyStorageBuffers(), e.numReadwriteStorageBuffers(),
|
||||
e.numReadonlyStorageTextures(), e.numReadwriteStorageTextures(), e.blocks());
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ class AttribLocationReflectionTest {
|
||||
|
||||
private static StageReflection reflectionWith(VsInput... inputs) {
|
||||
return new StageReflection(
|
||||
ShaderManager.ResourceCounts.EMPTY, List.of(), List.of(), List.of(), 0, List.of(),
|
||||
ShaderManager.ResourceCounts.EMPTY, List.of(), List.of(), List.of(), List.of(), 0, List.of(),
|
||||
List.of(inputs), List.of(), List.of(), -1, 0, 0, 0, 0, ShaderManager.BlockReflection.emptyBlocks());
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ class StageDiskCacheTest {
|
||||
|
||||
private static byte[] poison(String marker) {
|
||||
final ShaderManager.StageReflection reflection = new ShaderManager.StageReflection(
|
||||
ShaderManager.ResourceCounts.EMPTY, List.of(marker), List.of(), List.of(), 0, List.of(), List.of(), List.of(), List.of(),
|
||||
ShaderManager.ResourceCounts.EMPTY, List.of(marker), List.of(), List.of(), List.of(), 0, List.of(), List.of(), List.of(), List.of(),
|
||||
-1, 0, 0, 0, 0, ShaderManager.BlockReflection.emptyBlocks());
|
||||
return ShaderStageSerializer.encode(POISON_SPIRV, reflection, ShaderManager.GraphicsBindingMap.EMPTY, Set.of("poison_bool"), "poison_source");
|
||||
}
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.util.shaderc.Shaderc;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
class UnusedSamplerReflectionTest {
|
||||
|
||||
private static final String FRAGMENT = """
|
||||
#version 330 core
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D normals;
|
||||
uniform sampler2D specular;
|
||||
in vec2 uv;
|
||||
out vec4 fragColor;
|
||||
float highlight(float d) {
|
||||
float specular = d * d;
|
||||
return specular;
|
||||
}
|
||||
void main() {
|
||||
fragColor = texture(tex, uv) * highlight(uv.x);
|
||||
}
|
||||
""";
|
||||
|
||||
@Test
|
||||
void samplersOnlyShadowedOrDeclaredAreUnused() {
|
||||
final SpirvCompiler.Result r = SpirvCompiler.compile(FRAGMENT, Shaderc.shaderc_fragment_shader, "test", SpirvCompiler.Options.vulkanRelaxed());
|
||||
if (r.spirv() == null) fail("Compilation failed: " + r.error());
|
||||
final ByteBuffer spirv = r.spirv();
|
||||
try {
|
||||
final ShaderManager.StageReflection refl = ShaderManager.reflectStage(spirv, false);
|
||||
assertEquals(List.of("normals", "specular"), refl.unusedSamplerNames().stream().sorted().toList(),
|
||||
"declared samplers: " + refl.samplerNames());
|
||||
} finally {
|
||||
MemoryUtil.memFree(spirv);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
-26
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+72
-15
@@ -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();
|
||||
|
||||
+4
-14
@@ -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;
|
||||
|
||||
@@ -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
Reference in new issue
Block a user