mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-14 17:24:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d8cbf9c2f | ||
|
|
096dd01446 | ||
|
|
22c9479946 | ||
|
|
b2f597a73a | ||
|
|
553dec5b6d | ||
|
|
bc8116006c | ||
|
|
bf9492dffb | ||
|
|
69aed42b89 | ||
|
|
ffe54f61b9 | ||
|
|
c86e1de9f6 | ||
|
|
6af401c834 |
No files matched your search
@@ -2,7 +2,8 @@
|
||||
# Builds the libTracyClient shared library for the tracy-client module.
|
||||
set -euo pipefail
|
||||
|
||||
TRACY_VERSION="${TRACY_VERSION:-v0.13.1}"
|
||||
TRACY_VERSION="${TRACY_VERSION:-v0.14.0}"
|
||||
TRACY_VERSION_BARE="${TRACY_VERSION#v}"
|
||||
OUT_DIR="${OUT_DIR:-dist/tracy-natives}"
|
||||
TRACY_REPO="https://github.com/wolfpld/tracy"
|
||||
|
||||
@@ -16,7 +17,7 @@ case "$(uname -m)" in
|
||||
arm64|aarch64) PLAT_ARCH="arm64" ;;
|
||||
*) PLAT_ARCH="x64" ;;
|
||||
esac
|
||||
RES_DIR="${RES_DIR:-$REPO_ROOT/tracy-client/src/main/resources/natives/tracy/$PLAT_OS-$PLAT_ARCH}"
|
||||
RES_DIR="${RES_DIR:-$REPO_ROOT/tracy-client/src/main/resources/natives/tracy/$TRACY_VERSION_BARE/$PLAT_OS-$PLAT_ARCH}"
|
||||
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
@@ -33,7 +34,8 @@ fi
|
||||
BUILD_DIR="$WORK_DIR/build"
|
||||
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
|
||||
|
||||
cmake -S "$SRC_DIR" -B "$BUILD_DIR" \
|
||||
cmake -S "$REPO_ROOT/tracy-client/src/main/native" -B "$BUILD_DIR" \
|
||||
-DTRACY_SRC_DIR="$SRC_DIR" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DTRACY_STATIC=OFF \
|
||||
|
||||
@@ -6,10 +6,6 @@ on:
|
||||
- "tracy-v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tracy-version:
|
||||
description: Tracy git tag to build
|
||||
required: false
|
||||
default: v0.13.1
|
||||
publish:
|
||||
description: Publish angelica-tracy.jar to the GTNH maven
|
||||
type: boolean
|
||||
@@ -20,25 +16,25 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [linux-x64, windows-x64, macos-x64, macos-arm64]
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
platform: linux-x64
|
||||
- os: windows-2025
|
||||
platform: windows-x64
|
||||
- os: macos-15-intel
|
||||
platform: macos-x64
|
||||
- os: macos-15
|
||||
platform: macos-arm64
|
||||
- platform: linux-x64
|
||||
os: ubuntu-22.04
|
||||
- platform: windows-x64
|
||||
os: windows-2025
|
||||
- platform: macos-x64
|
||||
os: macos-15-intel
|
||||
- platform: macos-arm64
|
||||
os: macos-15
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Build libTracyClient
|
||||
shell: bash
|
||||
env:
|
||||
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
|
||||
OUT_DIR: dist/tracy-natives
|
||||
run: |
|
||||
export TRACY_VERSION="${RAW_VERSION#tracy-}"
|
||||
export TRACY_VERSION="v$(sed -nE 's/^tracy = "(.*)"$/\1/p' gradle/libs.versions.toml)"
|
||||
bash .github/scripts/build-tracy-natives.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -56,14 +52,6 @@ jobs:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
- uses: gradle/actions/setup-gradle@v4
|
||||
- name: Check tracy version matches the version catalog
|
||||
shell: bash
|
||||
env:
|
||||
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
|
||||
run: |
|
||||
ver="${RAW_VERSION#tracy-}"
|
||||
grep -q "^tracy = \"${ver#v}\"$" gradle/libs.versions.toml \
|
||||
|| { echo "libs.versions.toml tracy does not match $ver" >&2; exit 1; }
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: tracy-natives-*
|
||||
@@ -71,8 +59,9 @@ jobs:
|
||||
- name: Stage natives into tracy-client resources
|
||||
shell: bash
|
||||
run: |
|
||||
version="$(sed -nE 's/^tracy = "(.*)"$/\1/p' gradle/libs.versions.toml)"
|
||||
for d in staging/tracy-natives-*; do
|
||||
dest="tracy-client/src/main/resources/natives/tracy/${d#staging/tracy-natives-}"
|
||||
dest="tracy-client/src/main/resources/natives/tracy/$version/${d#staging/tracy-natives-}"
|
||||
mkdir -p "$dest"
|
||||
cp "$d"/* "$dest/"
|
||||
done
|
||||
|
||||
@@ -43,3 +43,5 @@ tracy-client/src/main/resources/natives/
|
||||
tracy-client/angelica/
|
||||
dist/tracy-natives/
|
||||
*.tracy
|
||||
|
||||
tracy-client/angelica/
|
||||
@@ -52,8 +52,8 @@ configurations.configureEach {
|
||||
}
|
||||
|
||||
DependencyHandlerScope.of(dependencies).apply {
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
|
||||
|
||||
runtimeOnlyNonPublishable(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
@@ -161,6 +161,8 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
|
||||
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
|
||||
compileOnly("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:OpenBlocks:1.12.19-GTNH:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Chisel:2.17.31-GTNH:dev")
|
||||
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:CosmeticArmorReworked:1.0.6-GTNH:dev")
|
||||
@@ -202,6 +204,7 @@ afterEvaluate {
|
||||
|
||||
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
|
||||
testCompileOnly(testFixtures(project(":glsm")))
|
||||
testImplementation("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
|
||||
testRuntimeOnly(libs.fastutil)
|
||||
testRuntimeOnly(libs.joml)
|
||||
|
||||
@@ -58,7 +58,7 @@ public final class SystemProperties {
|
||||
public static final boolean DEBUG_F3_DETAIL = Boolean.getBoolean("angelica.debug.f3Detail");
|
||||
public static final boolean ENABLE_TEST_BLOCKS = Boolean.getBoolean("angelica.debug.testBlocks");
|
||||
public static final boolean DUMP_CLASS = Boolean.getBoolean("angelica.debug.dumpClass");
|
||||
public static final boolean DUMP_SHADERS = Boolean.getBoolean("angelica.debug.dumpShaders") || isDeobf();
|
||||
private static final boolean DUMP_SHADERS_PROP = Boolean.getBoolean("angelica.debug.dumpShaders");
|
||||
public static final boolean REDIRECTOR_LOGSPAM = Boolean.getBoolean("angelica.debug.redirectorLogspam");
|
||||
public static final boolean DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debug.displayLists");
|
||||
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
|
||||
@@ -68,8 +68,12 @@ public final class SystemProperties {
|
||||
// Set by us, read by celeritas
|
||||
public static final String KEY_CELERITAS_ENABLE_GL_DEBUG = "celeritas.enableGLDebug";
|
||||
|
||||
public static boolean dumpShaders() {
|
||||
return DUMP_SHADERS_PROP || isDeobf();
|
||||
}
|
||||
|
||||
public static Path shaderDumpDir(String phase) {
|
||||
return DUMP_SHADERS ? Paths.get(SHADER_DUMP_ROOT, phase) : null;
|
||||
return dumpShaders() ? Paths.get(SHADER_DUMP_ROOT, phase) : null;
|
||||
}
|
||||
|
||||
public enum UnmappedGLMode {
|
||||
@@ -118,7 +122,7 @@ public final class SystemProperties {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDeobf() {
|
||||
public static boolean isDeobf() {
|
||||
return Launch.blackboard != null && Boolean.TRUE.equals(Launch.blackboard.get("fml.deobfuscatedEnvironment"));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
|
||||
@@ -181,6 +182,9 @@ class GLContextState {
|
||||
public int clientAttribStackPointer = 0;
|
||||
public final boolean[] restoreUnitChanged = new boolean[GLStateManager.MAX_TEXTURE_UNITS];
|
||||
public boolean restoreDepthChanged, restoreBlendChanged, restoreColorMaskChanged, restoreClearColorChanged, restoreDrawBufferChanged, restoreLogicOpChanged, restoreStencilChanged, restoreViewportChanged, restoreLineChanged, restorePointChanged, restorePolygonChanged, restoreActiveUnitChanged;
|
||||
public final BlendState vanillaBlendBefore = new BlendState();
|
||||
public final BlendState vanillaBlendAfter = new BlendState();
|
||||
public boolean vanillaBlendEnabledBefore;
|
||||
public int drawFramebuffer = 0;
|
||||
public int readFramebuffer = 0;
|
||||
public int texGenGeneration;
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VertexKey;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.DeferredAlphaHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
|
||||
@@ -44,6 +45,8 @@ import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
|
||||
@@ -1301,12 +1304,16 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final BooleanStateStack blend = ctx().blendMode;
|
||||
final boolean wasEnabled = blend.isEffectivelyEnabled();
|
||||
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
|
||||
if (bh != null && bh.isBlendLocked()) {
|
||||
blend.beforeModify();
|
||||
bh.deferBlendModeToggle(true);
|
||||
return;
|
||||
} else {
|
||||
blend.enable();
|
||||
}
|
||||
ctx().blendMode.enable();
|
||||
if (!wasEnabled && blend.isEffectivelyEnabled()) postVanillaBlendChange();
|
||||
}
|
||||
|
||||
public static void disableBlend() {
|
||||
@@ -1317,12 +1324,80 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final BooleanStateStack blend = ctx().blendMode;
|
||||
final boolean wasEnabled = blend.isEffectivelyEnabled();
|
||||
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
|
||||
if (bh != null && bh.isBlendLocked()) {
|
||||
blend.beforeModify();
|
||||
bh.deferBlendModeToggle(false);
|
||||
return;
|
||||
} else {
|
||||
blend.disable();
|
||||
}
|
||||
ctx().blendMode.disable();
|
||||
if (wasEnabled && !blend.isEffectivelyEnabled()) postVanillaBlendChange();
|
||||
}
|
||||
|
||||
private static void postVanillaBlendChange() {
|
||||
if (GLSMHooks.VANILLA_BLEND_CHANGE.hasListeners()) {
|
||||
GLSMHooks.VANILLA_BLEND_CHANGE.post(GLSMHooks.vanillaBlendChangeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean snapshotVanillaBlendFunc() {
|
||||
if (!GLSMHooks.VANILLA_BLEND_CHANGE.hasListeners()) return false;
|
||||
final GLContextState glCtx = ctx();
|
||||
glCtx.blendState.readEffective(glCtx.vanillaBlendBefore);
|
||||
glCtx.vanillaBlendEnabledBefore = glCtx.blendMode.isEffectivelyEnabled();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void postVanillaBlendChangeIfMoved(boolean snapshotted) {
|
||||
if (!snapshotted) return;
|
||||
final GLContextState glCtx = ctx();
|
||||
final BlendState before = glCtx.vanillaBlendBefore;
|
||||
final BlendState after = glCtx.blendState.readEffective(glCtx.vanillaBlendAfter);
|
||||
if (glCtx.blendMode.isEffectivelyEnabled() != glCtx.vanillaBlendEnabledBefore
|
||||
|| after.getSrcRgb() != before.getSrcRgb()
|
||||
|| after.getDstRgb() != before.getDstRgb()
|
||||
|| after.getSrcAlpha() != before.getSrcAlpha()
|
||||
|| after.getDstAlpha() != before.getDstAlpha()) {
|
||||
postVanillaBlendChange();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isEffectiveBlendEnabled() {
|
||||
return ctx().blendMode.isEffectivelyEnabled();
|
||||
}
|
||||
|
||||
public static BlendState getEffectiveBlendState(BlendState out) {
|
||||
return ctx().blendState.readEffective(out);
|
||||
}
|
||||
|
||||
public static boolean isEffectiveDepthMaskEnabled() {
|
||||
return ctx().depthState.isEffectiveMaskEnabled();
|
||||
}
|
||||
|
||||
public static boolean isEffectiveAlphaTestEnabled() {
|
||||
return ctx().alphaTest.isEffectivelyEnabled();
|
||||
}
|
||||
|
||||
public static AlphaState getEffectiveAlphaState(AlphaState out) {
|
||||
return ctx().alphaState.readEffective(out);
|
||||
}
|
||||
|
||||
private static int foreignDrawDepth;
|
||||
|
||||
public static void beginForeignDraw() {
|
||||
foreignDrawDepth++;
|
||||
}
|
||||
|
||||
public static void endForeignDraw() {
|
||||
if (foreignDrawDepth > 0 && --foreignDrawDepth == 0 && GLSMHooks.FOREIGN_DRAW_END.hasListeners()) {
|
||||
GLSMHooks.FOREIGN_DRAW_END.post(GLSMHooks.foreignDrawEndEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isForeignDraw() {
|
||||
return foreignDrawDepth > 0;
|
||||
}
|
||||
|
||||
public static void enableScissorTest() {
|
||||
@@ -1356,9 +1431,11 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final boolean snapshotted = snapshotVanillaBlendFunc();
|
||||
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
|
||||
if (bh != null && bh.isBlendLocked()) {
|
||||
bh.deferBlendFunc(srcFactor, dstFactor, srcFactor, dstFactor);
|
||||
postVanillaBlendChangeIfMoved(snapshotted);
|
||||
return;
|
||||
}
|
||||
final boolean caching = isCachingEnabled();
|
||||
@@ -1372,6 +1449,7 @@ public class GLStateManager {
|
||||
GLSMHooks.blendFuncChangeEvent.dstAlpha = GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
|
||||
}
|
||||
postVanillaBlendChangeIfMoved(snapshotted);
|
||||
return;
|
||||
}
|
||||
final boolean bypass = !caching;
|
||||
@@ -1390,6 +1468,7 @@ public class GLStateManager {
|
||||
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
|
||||
}
|
||||
}
|
||||
postVanillaBlendChangeIfMoved(snapshotted);
|
||||
}
|
||||
|
||||
public static void glBlendEquation(int mode) {
|
||||
@@ -1429,9 +1508,11 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final boolean snapshotted = snapshotVanillaBlendFunc();
|
||||
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
|
||||
if (bh != null && bh.isBlendLocked()) {
|
||||
bh.deferBlendFunc(srcRgb, dstRgb, srcAlpha, dstAlpha);
|
||||
postVanillaBlendChangeIfMoved(snapshotted);
|
||||
return;
|
||||
}
|
||||
if (GLSMConfig.hudCacheOverride && dstAlpha != GL11.GL_ONE_MINUS_SRC_ALPHA) {
|
||||
@@ -1451,6 +1532,7 @@ public class GLStateManager {
|
||||
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
|
||||
}
|
||||
}
|
||||
postVanillaBlendChangeIfMoved(snapshotted);
|
||||
}
|
||||
|
||||
public static void glBlendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
@@ -1745,6 +1827,7 @@ public class GLStateManager {
|
||||
}
|
||||
final DeferredAlphaHandler ah = GLSMHooks.alphaHandler;
|
||||
if (ah != null && ah.isAlphaTestLocked()) {
|
||||
glCtx.alphaTest.beforeModify();
|
||||
ah.deferAlphaTestToggle(true);
|
||||
return;
|
||||
}
|
||||
@@ -1766,6 +1849,7 @@ public class GLStateManager {
|
||||
}
|
||||
final DeferredAlphaHandler ah = GLSMHooks.alphaHandler;
|
||||
if (ah != null && ah.isAlphaTestLocked()) {
|
||||
glCtx.alphaTest.beforeModify();
|
||||
ah.deferAlphaTestToggle(false);
|
||||
return;
|
||||
}
|
||||
@@ -3057,9 +3141,15 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (light >= VertexKey.FFP_LIGHT_COUNT && (warnedFfpLightMask & (1 << light)) == 0 && ShaderManager.getInstance().isEnabled()) {
|
||||
warnedFfpLightMask |= (1 << light);
|
||||
LOGGER.warn("GL_LIGHT{} enabled, but FFP shader emulation implements only GL_LIGHT0..{}", light, VertexKey.FFP_LIGHT_COUNT - 1, new Throwable("call site"));
|
||||
}
|
||||
ctx().lightStates[light].enable();
|
||||
}
|
||||
|
||||
private static int warnedFfpLightMask = 0;
|
||||
|
||||
public static void enableColorMaterial() {
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
@@ -3525,6 +3615,9 @@ public class GLStateManager {
|
||||
final int mask = glCtx.attribs.popInt();
|
||||
glCtx.attribDepth--;
|
||||
|
||||
// Snapshot before the stacks pop, so applyRestoredState can tell whether vanilla blend actually moved.
|
||||
final boolean blendSnapshotted = (mask & (GL11.GL_COLOR_BUFFER_BIT | GL11.GL_ENABLE_BIT)) != 0 && snapshotVanillaBlendFunc();
|
||||
|
||||
// First: restore BooleanStateStack states that were actually modified (fast path)
|
||||
// These use lazy copy-on-write with global depth tracking
|
||||
final List<IStateStack<?>> modified = glCtx.modifiedAtDepth[glCtx.attribDepth];
|
||||
@@ -3543,7 +3636,12 @@ public class GLStateManager {
|
||||
|
||||
// Third: apply restored state to the GL driver. BooleanStateStacks already issue GL calls via setEnabled(); non-boolean stacks are pure data
|
||||
// containers, so we must explicitly drive GL here.
|
||||
applyRestoredState(mask);
|
||||
applyRestoredState(mask, blendSnapshotted);
|
||||
}
|
||||
|
||||
private static boolean isDepthColorOverridden() {
|
||||
final DeferredDepthColorHandler dch = GLSMHooks.depthColorHandler;
|
||||
return dch != null && dch.isOverrideHeld();
|
||||
}
|
||||
|
||||
private static void captureRestoreChanges(int mask) {
|
||||
@@ -3632,20 +3730,25 @@ public class GLStateManager {
|
||||
/**
|
||||
* After popping GLSM stacks, apply restored state to the GL driver.
|
||||
*/
|
||||
private static void applyRestoredState(int mask) {
|
||||
private static void applyRestoredState(int mask, boolean blendSnapshotted) {
|
||||
final GLContextState glCtx = ctx();
|
||||
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0 && glCtx.restoreDepthChanged) {
|
||||
RENDER_BACKEND.depthFunc(glCtx.depthState.getFunc());
|
||||
RENDER_BACKEND.depthMask(glCtx.depthState.isEnabled());
|
||||
if (!isDepthColorOverridden()) {
|
||||
RENDER_BACKEND.depthMask(glCtx.depthState.isEnabled());
|
||||
}
|
||||
RENDER_BACKEND.clearDepth(glCtx.depthState.getClearValue());
|
||||
}
|
||||
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
|
||||
if (glCtx.restoreBlendChanged) {
|
||||
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
|
||||
final DeferredBlendHandler restoreBlendHandler = GLSMHooks.blendHandler;
|
||||
if (restoreBlendHandler == null || !restoreBlendHandler.isOverrideHeld()) {
|
||||
RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha());
|
||||
}
|
||||
RENDER_BACKEND.blendEquationSeparate(glCtx.blendState.getEquationRgb(), glCtx.blendState.getEquationAlpha());
|
||||
RENDER_BACKEND.blendColor(glCtx.blendState.getBlendColorR(), glCtx.blendState.getBlendColorG(), glCtx.blendState.getBlendColorB(), glCtx.blendState.getBlendColorA());
|
||||
}
|
||||
if (glCtx.restoreColorMaskChanged) {
|
||||
if (glCtx.restoreColorMaskChanged && !isDepthColorOverridden()) {
|
||||
RENDER_BACKEND.colorMask(glCtx.colorMask.red, glCtx.colorMask.green, glCtx.colorMask.blue, glCtx.colorMask.alpha);
|
||||
}
|
||||
if (glCtx.restoreClearColorChanged) {
|
||||
@@ -3730,6 +3833,8 @@ public class GLStateManager {
|
||||
glCtx.dirtyTexCoordAttrib = true;
|
||||
}
|
||||
}
|
||||
|
||||
postVanillaBlendChangeIfMoved(blendSnapshotted);
|
||||
}
|
||||
|
||||
public static void glClear(int mask) {
|
||||
@@ -4918,16 +5023,22 @@ public class GLStateManager {
|
||||
final ShaderManager ffp = ShaderManager.getInstance();
|
||||
if (program == 0 && ffp.isEnabled()) {
|
||||
final int prev = glCtx.activeProgram;
|
||||
final boolean caching = isCachingEnabled();
|
||||
if (prev != 0) glCtx.programGeneration++;
|
||||
final boolean changed = prev != 0;
|
||||
if (changed) glCtx.programGeneration++;
|
||||
glCtx.activeProgram = 0; // Track that FFP was requested
|
||||
if (GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
|
||||
if (changed && GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
|
||||
GLSMHooks.programChangeEvent.previousProgram = prev;
|
||||
GLSMHooks.programChangeEvent.newProgram = 0;
|
||||
GLSMHooks.programChangeEvent.postBind = false;
|
||||
GLSMHooks.PROGRAM_CHANGE.post(GLSMHooks.programChangeEvent);
|
||||
}
|
||||
ffp.activate();
|
||||
if (changed && GLSMHooks.PROGRAM_CHANGE.hasListeners()) {
|
||||
GLSMHooks.programChangeEvent.previousProgram = prev;
|
||||
GLSMHooks.programChangeEvent.newProgram = 0;
|
||||
GLSMHooks.programChangeEvent.postBind = true;
|
||||
GLSMHooks.PROGRAM_CHANGE.post(GLSMHooks.programChangeEvent);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
public final class FFPVertexLighting {
|
||||
|
||||
private static final Vector3f scratchNormal = new Vector3f();
|
||||
private static final Vector3f scratchLightVec = new Vector3f();
|
||||
private static final Vector4f scratchEyePos = new Vector4f();
|
||||
private static final Matrix3f scratchNormalMat = new Matrix3f();
|
||||
private static final Vector3f scratchVec = new Vector3f();
|
||||
|
||||
private FFPVertexLighting() {}
|
||||
|
||||
public static boolean modulatesVertexColor(Vector3f destFactor) {
|
||||
destFactor.set(1.0f);
|
||||
|
||||
if (!GLStateManager.getLightingState().isEnabled()) return false;
|
||||
if (!GLStateManager.getColorMaterial().isEnabled()) return false;
|
||||
if (VertexKey.encodeColorMaterialMode(GLStateManager.getColorMaterialParameter().getValue()) != VertexKey.CM_AMBIENT_AND_DIFFUSE) return false;
|
||||
|
||||
final MaterialState material = GLStateManager.getFrontMaterial();
|
||||
if (material.emission.x != 0.0f || material.emission.y != 0.0f || material.emission.z != 0.0f)
|
||||
return false;
|
||||
|
||||
final boolean materialHasSpecular = material.shininess > 0.0f && (material.specular.x != 0.0f || material.specular.y != 0.0f || material.specular.z != 0.0f);
|
||||
|
||||
final Vector4f lightModelAmbient = GLStateManager.getLightModel().ambient;
|
||||
float r = lightModelAmbient.x;
|
||||
float g = lightModelAmbient.y;
|
||||
float b = lightModelAmbient.z;
|
||||
|
||||
final var lightEnables = GLStateManager.getLightStates();
|
||||
final var lightData = GLStateManager.getLightDataStates();
|
||||
boolean normalReady = false;
|
||||
|
||||
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
|
||||
if (!lightEnables[i].isEnabled()) continue;
|
||||
final LightState light = lightData[i];
|
||||
if (materialHasSpecular && (light.specular.x != 0.0f || light.specular.y != 0.0f || light.specular.z != 0.0f))
|
||||
return false;
|
||||
if (!normalReady) {
|
||||
transformCurrentNormal();
|
||||
normalReady = true;
|
||||
}
|
||||
final float nDotVP = Math.max(scratchNormal.dot(lightDirection(light)), 0.0f);
|
||||
r += light.ambient.x + nDotVP * light.diffuse.x;
|
||||
g += light.ambient.y + nDotVP * light.diffuse.y;
|
||||
b += light.ambient.z + nDotVP * light.diffuse.z;
|
||||
}
|
||||
|
||||
if (r == 1.0f && g == 1.0f && b == 1.0f) return false;
|
||||
if (!Float.isFinite(r + g + b)) return false;
|
||||
destFactor.set(r, g, b);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void transformCurrentNormal() {
|
||||
GLStateManager.getModelViewMatrix().normal(scratchNormalMat);
|
||||
scratchNormal.set(ShaderManager.getCurrentNormal());
|
||||
scratchNormalMat.transform(scratchNormal);
|
||||
if (GLStateManager.getNormalizeState().isEnabled()) {
|
||||
scratchNormal.normalize();
|
||||
} else if (GLStateManager.getRescaleNormalState().isEnabled()) {
|
||||
scratchNormal.mul(Uniforms.rescaleFactor(scratchNormalMat, scratchVec));
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector3f lightDirection(LightState light) {
|
||||
if (light.position.w == 0.0f) {
|
||||
scratchLightVec.set(light.position.x, light.position.y, light.position.z);
|
||||
} else {
|
||||
scratchEyePos.set(0.0f, 0.0f, 0.0f, 1.0f).mul(GLStateManager.getModelViewMatrix());
|
||||
scratchLightVec.set(light.position.x - scratchEyePos.x, light.position.y - scratchEyePos.y, light.position.z - scratchEyePos.z);
|
||||
}
|
||||
return scratchLightVec.normalize();
|
||||
}
|
||||
}
|
||||
@@ -232,8 +232,12 @@ public class Uniforms {
|
||||
private void stageNormalMatrix() {
|
||||
GLStateManager.getModelViewMatrix().normal(normalMatrix);
|
||||
putMat3(FFPUniformBlock.NORMAL_MATRIX, normalMatrix);
|
||||
final float scale = 1.0f / normalMatrix.getColumn(0, tempVec3).length();
|
||||
putFloat(FFPUniformBlock.NORMAL_SCALE, scale);
|
||||
putFloat(FFPUniformBlock.NORMAL_SCALE, rescaleFactor(normalMatrix, tempVec3));
|
||||
}
|
||||
|
||||
static float rescaleFactor(Matrix3f normalMatrix, Vector3f scratch) {
|
||||
final float f = normalMatrix.getColumn(2, scratch).lengthSquared();
|
||||
return f > 1.0e-12f ? 1.0f / (float) Math.sqrt(f) : 1.0f;
|
||||
}
|
||||
|
||||
private void stageTextureMatrices() {
|
||||
|
||||
@@ -20,38 +20,36 @@ public final class VertexKey {
|
||||
|
||||
public static final int MAX_UNITS = 4;
|
||||
|
||||
public static final int FFP_LIGHT_COUNT = 2;
|
||||
|
||||
private final long packed;
|
||||
|
||||
private static final int BIT_LIGHTING = 0;
|
||||
private static final int BIT_LIGHT0 = 1;
|
||||
private static final int BIT_LIGHT1 = 2;
|
||||
private static final int BIT_LIGHT0_DIR = 3;
|
||||
private static final int BIT_LIGHT1_DIR = 4;
|
||||
private static final int BIT_COLOR_MATERIAL = 5;
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6;
|
||||
private static final int BIT_FOG = 7;
|
||||
private static final int BIT_FOG_DIST_MODE = 8; // 2 bits: 8-9
|
||||
private static final int BIT_NORMALIZE = 10;
|
||||
private static final int BIT_RESCALE_NORMAL = 11;
|
||||
// Per-unit texcoord enable: bits 12-15 (unit 0..3). Unit 1 doubles as the lightmap flag.
|
||||
private static final int BIT_UNIT_TEX_BASE = 12;
|
||||
private static final int BIT_HAS_VERTEX_COLOR = 16;
|
||||
private static final int BIT_HAS_VERTEX_NORMAL = 17;
|
||||
private static final int BIT_HAS_VERTEX_TEX = 18;
|
||||
private static final int BIT_HAS_VERTEX_LIGHTMAP = 19;
|
||||
private static final int BIT_COLOR_MAT_MODE = 20; // 3 bits: 20-22
|
||||
// TexGen modes (unit 0 only, for now): 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR).
|
||||
private static final int BIT_TEXGEN_S = 23; // 3 bits: 23-25
|
||||
private static final int BIT_TEXGEN_T = 26; // 3 bits: 26-28
|
||||
private static final int BIT_TEXGEN_R = 29; // 3 bits: 29-31
|
||||
private static final int BIT_TEXGEN_Q = 32; // 3 bits: 32-34
|
||||
private static final int BIT_CLIP_PLANES = 35;
|
||||
private static final int BIT_WIDE_LINE = 36;
|
||||
// Per-unit texmat enable: bits 37-40 (unit 0..3). Unit 1 is the lightmap matrix.
|
||||
private static final int BIT_UNIT_TEXMAT_BASE = 37;
|
||||
private static final int BIT_UNIT23_UV_FROM_UNIT0 = 41;
|
||||
private static final int BIT_LINE_STIPPLE = 42;
|
||||
private static final int BIT_INSTANCED = 44;
|
||||
private static final int BIT_LIGHT_BASE = BIT_LIGHTING + 1;
|
||||
private static final int BIT_LIGHT_DIR_BASE = BIT_LIGHT_BASE + FFP_LIGHT_COUNT;
|
||||
private static final int BIT_COLOR_MATERIAL = BIT_LIGHT_DIR_BASE + FFP_LIGHT_COUNT;
|
||||
private static final int BIT_SEPARATE_SPECULAR = BIT_COLOR_MATERIAL + 1;
|
||||
private static final int BIT_FOG = BIT_SEPARATE_SPECULAR + 1;
|
||||
private static final int BIT_FOG_DIST_MODE = BIT_FOG + 1;
|
||||
private static final int BIT_NORMALIZE = BIT_FOG_DIST_MODE + 2;
|
||||
private static final int BIT_RESCALE_NORMAL = BIT_NORMALIZE + 1;
|
||||
private static final int BIT_UNIT_TEX_BASE = BIT_RESCALE_NORMAL + 1;
|
||||
private static final int BIT_HAS_VERTEX_COLOR = BIT_UNIT_TEX_BASE + MAX_UNITS;
|
||||
private static final int BIT_HAS_VERTEX_NORMAL = BIT_HAS_VERTEX_COLOR + 1;
|
||||
private static final int BIT_HAS_VERTEX_TEX = BIT_HAS_VERTEX_NORMAL + 1;
|
||||
private static final int BIT_HAS_VERTEX_LIGHTMAP = BIT_HAS_VERTEX_TEX + 1;
|
||||
private static final int BIT_COLOR_MAT_MODE = BIT_HAS_VERTEX_LIGHTMAP + 1;
|
||||
// TexGen modes are unit 0 only, for now.
|
||||
private static final int BIT_TEXGEN_S = BIT_COLOR_MAT_MODE + 3;
|
||||
private static final int BIT_TEXGEN_T = BIT_TEXGEN_S + 3;
|
||||
private static final int BIT_TEXGEN_R = BIT_TEXGEN_T + 3;
|
||||
private static final int BIT_TEXGEN_Q = BIT_TEXGEN_R + 3;
|
||||
private static final int BIT_CLIP_PLANES = BIT_TEXGEN_Q + 3;
|
||||
private static final int BIT_WIDE_LINE = BIT_CLIP_PLANES + 1;
|
||||
private static final int BIT_UNIT_TEXMAT_BASE = BIT_WIDE_LINE + 1;
|
||||
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
|
||||
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
|
||||
private static final int BIT_INSTANCED = BIT_LINE_STIPPLE + 1;
|
||||
|
||||
public static final int TG_NONE = 0;
|
||||
public static final int TG_OBJ_LINEAR = 1;
|
||||
@@ -70,10 +68,8 @@ public final class VertexKey {
|
||||
public long pack() { return packed; }
|
||||
|
||||
public boolean lightingEnabled() { return bit(BIT_LIGHTING); }
|
||||
public boolean light0Enabled() { return bit(BIT_LIGHT0); }
|
||||
public boolean light1Enabled() { return bit(BIT_LIGHT1); }
|
||||
public boolean light0Directional() { return bit(BIT_LIGHT0_DIR); }
|
||||
public boolean light1Directional() { return bit(BIT_LIGHT1_DIR); }
|
||||
public boolean lightEnabled(int light) { return bit(BIT_LIGHT_BASE + light); }
|
||||
public boolean lightDirectional(int light) { return bit(BIT_LIGHT_DIR_BASE + light); }
|
||||
public boolean colorMaterialEnabled() { return bit(BIT_COLOR_MATERIAL); }
|
||||
public boolean separateSpecular() { return bit(BIT_SEPARATE_SPECULAR); }
|
||||
public boolean fogEnabled() { return bit(BIT_FOG); }
|
||||
@@ -136,17 +132,12 @@ public final class VertexKey {
|
||||
if (lighting) {
|
||||
bits |= (1L << BIT_LIGHTING);
|
||||
|
||||
if (GLStateManager.getLightStates()[0].isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHT0);
|
||||
for (int i = 0; i < FFP_LIGHT_COUNT; i++) {
|
||||
if (!GLStateManager.getLightStates()[i].isEnabled()) continue;
|
||||
bits |= (1L << (BIT_LIGHT_BASE + i));
|
||||
// position.w == 0 means directional
|
||||
if (GLStateManager.getLightDataStates()[0].position.w == 0.0f) {
|
||||
bits |= (1L << BIT_LIGHT0_DIR);
|
||||
}
|
||||
}
|
||||
if (GLStateManager.getLightStates()[1].isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHT1);
|
||||
if (GLStateManager.getLightDataStates()[1].position.w == 0.0f) {
|
||||
bits |= (1L << BIT_LIGHT1_DIR);
|
||||
if (GLStateManager.getLightDataStates()[i].position.w == 0.0f) {
|
||||
bits |= (1L << (BIT_LIGHT_DIR_BASE + i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +256,7 @@ public final class VertexKey {
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%b]",
|
||||
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
|
||||
packed, lightingEnabled(), lightEnabled(0), lightEnabled(1),
|
||||
colorMaterialEnabled(), fogEnabled(),
|
||||
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0,
|
||||
unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
|
||||
|
||||
@@ -182,11 +182,10 @@ public final class VertexShaderGenerator {
|
||||
}
|
||||
|
||||
// Per-light accumulation
|
||||
if (key.light0Enabled()) {
|
||||
emitLightContribution(sb, key, 0);
|
||||
}
|
||||
if (key.light1Enabled()) {
|
||||
emitLightContribution(sb, key, 1);
|
||||
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
|
||||
if (key.lightEnabled(i)) {
|
||||
emitLightContribution(sb, key, i);
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(" v_Color = vec4(clamp(color0, 0.0, 1.0), outAlpha);\n");
|
||||
@@ -198,7 +197,7 @@ public final class VertexShaderGenerator {
|
||||
|
||||
private static void emitLightContribution(StringBuilder sb, VertexKey key, int lightIndex) {
|
||||
final String li = String.valueOf(lightIndex);
|
||||
final boolean directional = (lightIndex == 0) ? key.light0Directional() : key.light1Directional();
|
||||
final boolean directional = key.lightDirectional(lightIndex);
|
||||
final String posUniform = "u_Light" + li + "Position";
|
||||
|
||||
sb.append(" { // Light ").append(li).append('\n');
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface DeferredBlendHandler {
|
||||
boolean isBlendLocked();
|
||||
boolean isOverrideHeld();
|
||||
void deferBlendModeToggle(boolean enabled);
|
||||
void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha);
|
||||
void flushDeferredBlend();
|
||||
|
||||
+1
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface DeferredDepthColorHandler {
|
||||
boolean isDepthColorLocked();
|
||||
boolean isOverrideHeld();
|
||||
void deferDepthEnable(boolean enabled);
|
||||
void deferColorMask(boolean r, boolean g, boolean b, boolean a);
|
||||
}
|
||||
@@ -6,6 +6,14 @@ public final class GLSMConfig {
|
||||
public static float lastBrightnessX;
|
||||
public static float lastBrightnessY;
|
||||
|
||||
public static int packBrightness(float x, float y) {
|
||||
return ((int) y << 16) | ((int) x & 0xFFFF);
|
||||
}
|
||||
|
||||
public static int packedLastBrightness() {
|
||||
return packBrightness(lastBrightnessX, lastBrightnessY);
|
||||
}
|
||||
|
||||
public static boolean hudCacheOverride;
|
||||
|
||||
public static volatile boolean expandVertexFormats;
|
||||
|
||||
@@ -25,6 +25,8 @@ public final class GLSMHooks {
|
||||
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
|
||||
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
|
||||
public static final EventBus<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
|
||||
public static final EventBus<ForeignDrawEndEvent> FOREIGN_DRAW_END = EventBus.create(ForeignDrawEndEvent.class);
|
||||
public static final EventBus<VanillaBlendChangeEvent> VANILLA_BLEND_CHANGE = EventBus.create(VanillaBlendChangeEvent.class);
|
||||
public static final EventBus<LoadingCheckpointEvent> LOADING_CHECKPOINT = EventBus.create(LoadingCheckpointEvent.class);
|
||||
|
||||
// Reusable event instances
|
||||
@@ -38,6 +40,8 @@ public final class GLSMHooks {
|
||||
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
|
||||
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
|
||||
public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent();
|
||||
public static final ForeignDrawEndEvent foreignDrawEndEvent = new ForeignDrawEndEvent();
|
||||
public static final VanillaBlendChangeEvent vanillaBlendChangeEvent = new VanillaBlendChangeEvent();
|
||||
public static final LoadingCheckpointEvent loadingCheckpointEvent = new LoadingCheckpointEvent();
|
||||
|
||||
private GLSMHooks() {}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface VanillaBooleanLayer {
|
||||
|
||||
boolean isOverrideHeld();
|
||||
boolean getVanilla();
|
||||
void setVanilla(boolean enabled);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface VanillaStateLayer<T> {
|
||||
|
||||
boolean isOverrideHeld();
|
||||
void readVanilla(T into);
|
||||
void writeVanilla(T from);
|
||||
|
||||
static boolean isHeld(VanillaStateLayer<?> layer) {
|
||||
return layer != null && layer.isOverrideHeld();
|
||||
}
|
||||
|
||||
static <T> void capture(VanillaStateLayer<T> layer, T slot) {
|
||||
if (isHeld(layer)) layer.readVanilla(slot);
|
||||
}
|
||||
|
||||
static <T> boolean restore(VanillaStateLayer<T> layer, T saved) {
|
||||
if (!isHeld(layer)) return false;
|
||||
layer.writeVanilla(saved);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class ForeignDrawEndEvent extends MutableEvent {
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class VanillaBlendChangeEvent extends MutableEvent {
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.gtnewhorizons.angelica.glsm.profiling;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class SectionRegistry {
|
||||
|
||||
public interface Emitter {
|
||||
long enter(int category, String text);
|
||||
void leave(long nativeId);
|
||||
void setup(int category, String name);
|
||||
boolean isConnected();
|
||||
}
|
||||
|
||||
private static final class Category {
|
||||
final int id;
|
||||
final String name;
|
||||
|
||||
Category(int id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Section {
|
||||
final long token;
|
||||
final int category;
|
||||
final String text;
|
||||
long nativeId;
|
||||
|
||||
Section(long token, int category, String text, long nativeId) {
|
||||
this.token = token;
|
||||
this.category = category;
|
||||
this.text = text;
|
||||
this.nativeId = nativeId;
|
||||
}
|
||||
}
|
||||
|
||||
private final Emitter emitter;
|
||||
private final List<Category> categories = new ArrayList<>();
|
||||
private final List<Section> open = new ArrayList<>();
|
||||
private long nextToken;
|
||||
private boolean connected;
|
||||
|
||||
public SectionRegistry(Emitter emitter) {
|
||||
this.emitter = emitter;
|
||||
}
|
||||
|
||||
public synchronized void registerCategory(int category, String name) {
|
||||
categories.add(new Category(category, name));
|
||||
}
|
||||
|
||||
public synchronized long enter(int category, String text) {
|
||||
syncConnection();
|
||||
final long token = ++nextToken;
|
||||
open.add(new Section(token, category, text, emitter.enter(category, text)));
|
||||
return token;
|
||||
}
|
||||
|
||||
public synchronized void leave(long token) {
|
||||
for (int i = 0; i < open.size(); i++) {
|
||||
final Section section = open.get(i);
|
||||
if (section.token == token) {
|
||||
open.remove(i);
|
||||
if (section.nativeId != 0L) emitter.leave(section.nativeId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void poll() {
|
||||
syncConnection();
|
||||
}
|
||||
|
||||
private void syncConnection() {
|
||||
final boolean now = emitter.isConnected();
|
||||
if (now == connected) return;
|
||||
connected = now;
|
||||
if (now) replay();
|
||||
}
|
||||
|
||||
private void replay() {
|
||||
for (int i = 0; i < categories.size(); i++) {
|
||||
final Category category = categories.get(i);
|
||||
emitter.setup(category.id, category.name);
|
||||
}
|
||||
for (int i = 0; i < open.size(); i++) {
|
||||
final Section section = open.get(i);
|
||||
section.nativeId = emitter.enter(section.category, section.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,18 @@ public final class Tracy {
|
||||
public static final int COLOR_IRIS = 0x9E862E; // gold
|
||||
public static final int COLOR_FFP = 0x7A4E9E; // purple
|
||||
|
||||
public static final int SECTION_WORLD = 1;
|
||||
public static final int SECTION_SHADERS = 2;
|
||||
public static final int SECTION_BENCHMARK = 3;
|
||||
public static final int SECTION_UI = 4;
|
||||
|
||||
private static final SectionRegistry SECTIONS = new SectionRegistry(new SectionRegistry.Emitter() {
|
||||
@Override public long enter(int category, String text) { return BACKEND.sectionEnter(category, text); }
|
||||
@Override public void leave(long nativeId) { BACKEND.sectionLeave(nativeId); }
|
||||
@Override public void setup(int category, String name) { BACKEND.sectionSetup(category, name); }
|
||||
@Override public boolean isConnected() { return BACKEND.isConnected(); }
|
||||
});
|
||||
|
||||
private static final ThreadLocal<ZoneStack> STACK = new ThreadLocal<>() {
|
||||
@Override
|
||||
protected ZoneStack initialValue() {
|
||||
@@ -62,6 +74,10 @@ public final class Tracy {
|
||||
FINE_ZONES = ENABLED && SystemProperties.TRACY_FINE_ZONES;
|
||||
if (ENABLED) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(Tracy::shutdown, "Tracy-Shutdown"));
|
||||
SECTIONS.registerCategory(SECTION_WORLD, "World");
|
||||
SECTIONS.registerCategory(SECTION_SHADERS, "Shaders");
|
||||
SECTIONS.registerCategory(SECTION_BENCHMARK, "Benchmark");
|
||||
SECTIONS.registerCategory(SECTION_UI, "UI");
|
||||
LOGGER.info("Tracy profiling enabled");
|
||||
}
|
||||
}
|
||||
@@ -95,6 +111,7 @@ public final class Tracy {
|
||||
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but no TracyBackend service present");
|
||||
return null;
|
||||
}
|
||||
if (!implementsCurrentAbi(found)) return null;
|
||||
final TracyBackend backend = found;
|
||||
final CountDownLatch done = new CountDownLatch(1);
|
||||
final AtomicReference<Boolean> result = new AtomicReference<>();
|
||||
@@ -120,10 +137,28 @@ public final class Tracy {
|
||||
return backend;
|
||||
}
|
||||
|
||||
private static final String ABI_SENTINEL = "sectionEnter";
|
||||
|
||||
private static boolean implementsCurrentAbi(TracyBackend backend) {
|
||||
try {
|
||||
backend.getClass().getMethod(ABI_SENTINEL, int.class, String.class);
|
||||
return true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
LOGGER.warn("Tracy backend {} was built against an older TracyBackend interface; profiling disabled", backend.getClass().getName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private record InitRunner(TracyBackend backend, CountDownLatch done, AtomicReference<Boolean> result) implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
final boolean ok = backend.init();
|
||||
boolean ok;
|
||||
try {
|
||||
ok = backend.init();
|
||||
} catch (LinkageError e) {
|
||||
LOGGER.warn("Tracy backend {} failed to link; profiling disabled: {}", backend.getClass().getName(), e);
|
||||
ok = false;
|
||||
}
|
||||
if (!result.compareAndSet(null, ok) && ok) {
|
||||
backend.shutdown();
|
||||
}
|
||||
@@ -168,7 +203,7 @@ public final class Tracy {
|
||||
final String name = stack.poppedName();
|
||||
if (name != null && WARNED_LEAKS.add(name)) {
|
||||
LOGGER.warn("Tracy: unbalanced profiler section '{}' left open (leaked by its caller); closed at next root", name);
|
||||
BACKEND.message("unbalanced profiler section '" + name + "' closed at root");
|
||||
BACKEND.message("unbalanced profiler section '" + name + "' closed at root", TracyBackend.SEVERITY_WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,6 +309,7 @@ public final class Tracy {
|
||||
threadNameRefreshed = true;
|
||||
BACKEND.setCurrentThreadName(Thread.currentThread().getName());
|
||||
}
|
||||
SECTIONS.poll();
|
||||
BACKEND.frameMark();
|
||||
}
|
||||
|
||||
@@ -330,8 +366,22 @@ public final class Tracy {
|
||||
}
|
||||
|
||||
public static void message(String text) {
|
||||
message(text, TracyBackend.SEVERITY_INFO);
|
||||
}
|
||||
|
||||
public static void message(String text, int severity) {
|
||||
if (!ENABLED || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
|
||||
BACKEND.message(text);
|
||||
BACKEND.message(text, severity);
|
||||
}
|
||||
|
||||
public static long sectionEnter(int category, String text) {
|
||||
if (!ENABLED || SHUTTING_DOWN) return 0L;
|
||||
return SECTIONS.enter(category, text);
|
||||
}
|
||||
|
||||
public static void sectionLeave(long token) {
|
||||
if (token == 0L || !ENABLED || SHUTTING_DOWN) return;
|
||||
SECTIONS.leave(token);
|
||||
}
|
||||
|
||||
public static boolean isConnected() {
|
||||
|
||||
@@ -5,6 +5,13 @@ public interface TracyBackend {
|
||||
int PLOT_FORMAT_MEMORY = 1;
|
||||
int PLOT_FORMAT_PERCENTAGE = 2;
|
||||
|
||||
int SEVERITY_TRACE = 0;
|
||||
int SEVERITY_DEBUG = 1;
|
||||
int SEVERITY_INFO = 2;
|
||||
int SEVERITY_WARNING = 3;
|
||||
int SEVERITY_ERROR = 4;
|
||||
int SEVERITY_FATAL = 5;
|
||||
|
||||
boolean init();
|
||||
|
||||
long internSrcLoc(String name, int color);
|
||||
@@ -24,7 +31,14 @@ public interface TracyBackend {
|
||||
|
||||
void plotInt(long namePtr, long value);
|
||||
void plot(long namePtr, double value);
|
||||
void message(String text);
|
||||
|
||||
void message(String text, int severity);
|
||||
default void message(String text) { message(text, SEVERITY_INFO); }
|
||||
|
||||
long sectionEnter(int category, String text);
|
||||
void sectionLeave(long id);
|
||||
void sectionSetup(int category, String name);
|
||||
|
||||
void setCurrentThreadName(String name);
|
||||
boolean isConnected();
|
||||
void shutdown();
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.glsm.profiling;
|
||||
|
||||
public final class ZoneStack {
|
||||
public static final long EMPTY = -1L;
|
||||
static final int MAX_DEPTH = 512;
|
||||
public static final int MAX_DEPTH = 512;
|
||||
|
||||
private long[] stack = new long[64];
|
||||
private boolean[] gpu = new boolean[64];
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
|
||||
import lombok.Setter;
|
||||
|
||||
public class AlphaStateStack extends AlphaState implements IStateStack<AlphaState> {
|
||||
|
||||
@@ -10,6 +12,8 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
|
||||
|
||||
protected int pointer;
|
||||
|
||||
@Setter private VanillaStateLayer<AlphaState> vanillaLayer;
|
||||
|
||||
public AlphaStateStack() {
|
||||
stack = new AlphaState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
@@ -22,7 +26,7 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -31,10 +35,19 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
final AlphaState saved = stack[--pointer];
|
||||
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
|
||||
set(saved);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AlphaState readEffective(AlphaState out) {
|
||||
out.set(this);
|
||||
VanillaStateLayer.capture(vanillaLayer, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import lombok.Setter;
|
||||
|
||||
public class BlendStateStack extends BlendState implements IStateStack<BlendStateStack> {
|
||||
|
||||
@@ -10,6 +12,8 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
|
||||
|
||||
protected int pointer;
|
||||
|
||||
@Setter private VanillaStateLayer<BlendState> vanillaLayer;
|
||||
|
||||
public BlendStateStack() {
|
||||
stack = new BlendState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
@@ -22,7 +26,7 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -31,10 +35,21 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
final BlendState saved = stack[--pointer];
|
||||
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
|
||||
setExceptFunc(saved);
|
||||
} else {
|
||||
set(saved);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public BlendState readEffective(BlendState out) {
|
||||
out.set(this);
|
||||
VanillaStateLayer.capture(vanillaLayer, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BooleanState;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* A stack for boolean GL state with lazy copy-on-write optimization.
|
||||
@@ -14,6 +16,7 @@ import com.gtnewhorizons.angelica.glsm.states.BooleanState;
|
||||
public class BooleanStateStack extends BooleanState implements IStateStack<BooleanStateStack> {
|
||||
|
||||
protected final boolean[] stack;
|
||||
@Setter private VanillaBooleanLayer vanillaLayer;
|
||||
|
||||
/**
|
||||
* The depth at which state has been saved. Compared against GLStateManager.getAttribDepth()
|
||||
@@ -56,7 +59,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
|
||||
if (savedDepth >= stack.length) {
|
||||
throw new IllegalStateException("Stack overflow size " + (savedDepth + 1) + " reached");
|
||||
}
|
||||
stack[savedDepth++] = enabled;
|
||||
stack[savedDepth++] = vanillaValue();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -65,12 +68,26 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
|
||||
if (savedDepth == 0) {
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
final boolean oldValue = stack[--savedDepth];
|
||||
// Call setEnabledDirect to avoid triggering beforeModify during restore
|
||||
setEnabledDirect(oldValue);
|
||||
restore(stack[--savedDepth]);
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isEffectivelyEnabled() {
|
||||
return (vanillaLayer != null && vanillaLayer.isOverrideHeld()) ? vanillaLayer.getVanilla() : enabled;
|
||||
}
|
||||
|
||||
private boolean vanillaValue() {
|
||||
return isEffectivelyEnabled();
|
||||
}
|
||||
|
||||
private void restore(boolean value) {
|
||||
if (vanillaLayer != null && vanillaLayer.isOverrideHeld()) {
|
||||
vanillaLayer.setVanilla(value);
|
||||
} else {
|
||||
setEnabledDirect(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return savedDepth == 0;
|
||||
@@ -92,8 +109,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
|
||||
public BooleanStateStack popDepth() {
|
||||
// We're only called if we were modified, so savedDepth should match
|
||||
if (savedDepth > 0) {
|
||||
final boolean oldValue = stack[--savedDepth];
|
||||
setEnabledDirect(oldValue);
|
||||
restore(stack[--savedDepth]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -106,7 +122,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
|
||||
public void beforeModify() {
|
||||
final int globalDepth = GLStateManager.getAttribDepth();
|
||||
if (savedDepth < globalDepth) {
|
||||
stack[savedDepth++] = enabled;
|
||||
stack[savedDepth++] = vanillaValue();
|
||||
GLStateManager.registerModifiedState(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
|
||||
import lombok.Setter;
|
||||
|
||||
public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskStack> {
|
||||
|
||||
@@ -10,6 +12,8 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
|
||||
|
||||
protected int pointer;
|
||||
|
||||
@Setter private VanillaStateLayer<ColorMask> vanillaLayer;
|
||||
|
||||
public ColorMaskStack() {
|
||||
stack = new ColorMask[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
@@ -22,7 +26,7 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -31,7 +35,10 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
final ColorMask saved = stack[--pointer];
|
||||
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
|
||||
set(saved);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.DepthState;
|
||||
import lombok.Setter;
|
||||
|
||||
public class DepthStateStack extends DepthState implements IStateStack<DepthStateStack> {
|
||||
|
||||
@@ -10,6 +12,10 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
|
||||
|
||||
protected int pointer;
|
||||
|
||||
@Setter private VanillaStateLayer<DepthState> vanillaLayer;
|
||||
|
||||
private final DepthState effective = new DepthState();
|
||||
|
||||
public DepthStateStack() {
|
||||
stack = new DepthState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
@@ -22,7 +28,7 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
|
||||
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
|
||||
}
|
||||
|
||||
stack[pointer++].set(this);
|
||||
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -31,10 +37,21 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
|
||||
throw new IllegalStateException("Stack underflow");
|
||||
}
|
||||
|
||||
set(stack[--pointer]);
|
||||
final DepthState saved = stack[--pointer];
|
||||
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
|
||||
setExceptMask(saved);
|
||||
} else {
|
||||
set(saved);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isEffectiveMaskEnabled() {
|
||||
if (!VanillaStateLayer.isHeld(vanillaLayer)) return isEnabled();
|
||||
vanillaLayer.readVanilla(effective);
|
||||
return effective.isEnabled();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return pointer == 0;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,15 @@ public class BlendState implements ISettableState<BlendState> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setExceptFunc(BlendState state) {
|
||||
final int sRgb = srcRgb, dRgb = dstRgb, sAlpha = srcAlpha, dAlpha = dstAlpha;
|
||||
set(state);
|
||||
this.srcRgb = sRgb;
|
||||
this.dstRgb = dRgb;
|
||||
this.srcAlpha = sAlpha;
|
||||
this.dstAlpha = dAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameAs(Object state) {
|
||||
if (this == state) return true;
|
||||
|
||||
@@ -18,6 +18,12 @@ public class DepthState implements ISettableState<DepthState> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setExceptMask(DepthState state) {
|
||||
final boolean mask = enabled;
|
||||
set(state);
|
||||
this.enabled = mask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameAs(Object state) {
|
||||
if (this == state) return true;
|
||||
|
||||
+1
-1
@@ -108,7 +108,7 @@ public class TessellatorStreamingDrawer {
|
||||
|
||||
final int defaultBrightness;
|
||||
if (!tess.hasBrightness) {
|
||||
defaultBrightness = ((int) GLSMConfig.lastBrightnessY << 16) | ((int) GLSMConfig.lastBrightnessX & 0xFFFF);
|
||||
defaultBrightness = GLSMConfig.packedLastBrightness();
|
||||
} else {
|
||||
defaultBrightness = 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.joml.Vector3f;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.COLOR_BIT;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@GLCoreTest
|
||||
class FFPVertexLightingGLTest {
|
||||
|
||||
private static final int POSITION_LOC = VertexFormatElement.Usage.POSITION.getAttributeLocation();
|
||||
private static final int COLOR_LOC = VertexFormatElement.Usage.COLOR.getAttributeLocation();
|
||||
private static final int STRIDE = 16;
|
||||
|
||||
private static final int VERTEX_COLOR = 0xFF808080;
|
||||
private static final float VERTEX_CHANNEL = 0x80 / 255.0f;
|
||||
|
||||
private static final Vector3f IDENTITY_FACTOR = new Vector3f(1.0f, 1.0f, 1.0f);
|
||||
|
||||
private final Vector3f factor = new Vector3f();
|
||||
|
||||
private int vao;
|
||||
private int vbo;
|
||||
|
||||
@BeforeEach
|
||||
void setUpState() {
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glViewport(0, 0, 800, 600);
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
standardItemLighting();
|
||||
|
||||
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glMaterialf(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 0.0f);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
GLStateManager.glDisable(GL11.GL_NORMALIZE);
|
||||
GLStateManager.glDisable(GL12.GL_RESCALE_NORMAL);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glDisable(GL11.GL_LIGHTING);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHT0);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHT1);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHT2);
|
||||
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
if (sm.isActive()) sm.deactivate();
|
||||
sm.disable();
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
if (vbo != 0) {
|
||||
GLStateManager.glDeleteBuffers(vbo);
|
||||
vbo = 0;
|
||||
}
|
||||
if (vao != 0) {
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
vao = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void standardItemLighting() {
|
||||
GLStateManager.glEnable(GL11.GL_LIGHTING);
|
||||
GLStateManager.glEnable(GL11.GL_LIGHT0);
|
||||
GLStateManager.glEnable(GL11.GL_LIGHT1);
|
||||
GLStateManager.glEnable(GL11.GL_COLOR_MATERIAL);
|
||||
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
|
||||
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, floats(0.4f, 0.4f, 0.4f, 1.0f));
|
||||
light(GL11.GL_LIGHT0, 0.2f, 1.0f, -0.7f);
|
||||
light(GL11.GL_LIGHT1, -0.2f, 1.0f, 0.7f);
|
||||
}
|
||||
|
||||
private static void light(int light, float x, float y, float z) {
|
||||
GLStateManager.glLight(light, GL11.GL_POSITION, floats(x, y, z, 0.0f));
|
||||
GLStateManager.glLight(light, GL11.GL_DIFFUSE, floats(0.6f, 0.6f, 0.6f, 1.0f));
|
||||
GLStateManager.glLight(light, GL11.GL_AMBIENT, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glLight(light, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
|
||||
private static String gateState() {
|
||||
final var material = GLStateManager.getFrontMaterial();
|
||||
return "lighting=" + GLStateManager.getLightingState().isEnabled()
|
||||
+ " colorMaterial=" + GLStateManager.getColorMaterial().isEnabled()
|
||||
+ " cmParam=0x" + Integer.toHexString(GLStateManager.getColorMaterialParameter().getValue())
|
||||
+ " emission=" + material.emission
|
||||
+ " specular=" + material.specular
|
||||
+ " shininess=" + material.shininess
|
||||
+ " lmAmbient=" + GLStateManager.getLightModel().ambient
|
||||
+ " light0=" + GLStateManager.getLightStates()[0].isEnabled()
|
||||
+ " light1=" + GLStateManager.getLightStates()[1].isEnabled();
|
||||
}
|
||||
|
||||
private static FloatBuffer floats(float... values) {
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(values.length);
|
||||
buf.put(values).flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
private void buildTriangle() {
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
final ByteBuffer data = BufferUtils.createByteBuffer(3 * STRIDE);
|
||||
putVertex(data, -0.9f, -0.9f);
|
||||
putVertex(data, 0.9f, -0.9f);
|
||||
putVertex(data, 0.0f, 0.9f);
|
||||
data.flip();
|
||||
|
||||
vbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glEnableVertexAttribArray(POSITION_LOC);
|
||||
GLStateManager.glVertexAttribPointer(POSITION_LOC, 3, GL11.GL_FLOAT, false, STRIDE, 0L);
|
||||
GLStateManager.glEnableVertexAttribArray(COLOR_LOC);
|
||||
GLStateManager.glVertexAttribPointer(COLOR_LOC, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 12L);
|
||||
VAOManager.setCurrentVertexFlags(COLOR_BIT);
|
||||
}
|
||||
|
||||
private static void putVertex(ByteBuffer buf, float x, float y) {
|
||||
final int base = buf.position();
|
||||
buf.putFloat(base, x).putFloat(base + 4, y).putFloat(base + 8, 0.0f);
|
||||
buf.putInt(base + 12, VERTEX_COLOR);
|
||||
buf.position(base + STRIDE);
|
||||
}
|
||||
|
||||
private float[] drawAndReadCenter() {
|
||||
buildTriangle();
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
sm.enable();
|
||||
sm.activate();
|
||||
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "FFP draw must not raise a GL error");
|
||||
|
||||
final ByteBuffer pixel = BufferUtils.createByteBuffer(4);
|
||||
GL11.glReadPixels(400, 200, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixel);
|
||||
return new float[] { (pixel.get(0) & 0xFF) / 255.0f, (pixel.get(1) & 0xFF) / 255.0f, (pixel.get(2) & 0xFF) / 255.0f };
|
||||
}
|
||||
|
||||
private void assertSolverMatchesShader(String context) {
|
||||
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), context + ": expected a representable factor");
|
||||
final float[] actual = drawAndReadCenter();
|
||||
final float[] expected = { Math.min(1.0f, VERTEX_CHANNEL * factor.x), Math.min(1.0f, VERTEX_CHANNEL * factor.y), Math.min(1.0f, VERTEX_CHANNEL * factor.z) };
|
||||
for (int c = 0; c < 3; c++) {
|
||||
assertEquals(expected[c], actual[c], 2.0f / 255.0f, context + ": channel " + c + " solver=" + expected[c] + " shader=" + actual[c] + " factor=" + factor);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void faceOnNormalMatchesGeneratedShader() {
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
assertSolverMatchesShader("face-on normal");
|
||||
}
|
||||
|
||||
@Test
|
||||
void signNormalMatchesGeneratedShader() {
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, -0.010416667f);
|
||||
assertSolverMatchesShader("sign normal");
|
||||
assertTrue(factor.x < 0.45f, "a 0.0104-long normal must stay at the 0.4 light model ambient floor rather than pick up 0.6 per light, got " + factor.x);
|
||||
}
|
||||
|
||||
@Test
|
||||
void tiltedNormalMatchesGeneratedShader() {
|
||||
GLStateManager.glNormal3f(0.3f, 0.8f, 0.5f);
|
||||
assertSolverMatchesShader("tilted normal");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rescaleNormalMatchesGeneratedShader() {
|
||||
GLStateManager.glEnable(GL12.GL_RESCALE_NORMAL);
|
||||
GLStateManager.glScalef(1.0f, 2.0f, 1.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
|
||||
assertSolverMatchesShader("rescale normal");
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalizeMatchesGeneratedShader() {
|
||||
GLStateManager.glEnable(GL11.GL_NORMALIZE);
|
||||
GLStateManager.glScalef(1.0f, 2.0f, 1.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
|
||||
assertSolverMatchesShader("normalize");
|
||||
assertTrue(factor.x > 1.2f, "normalize restores the unit normal, which must pick up more diffuse than rescale's 0.5-long one, got " + factor.x);
|
||||
}
|
||||
|
||||
@Test
|
||||
void materialEmissionIsNotRepresentable() {
|
||||
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION, floats(0.1f, 0.1f, 0.1f, 1.0f));
|
||||
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "emission is additive, so the lit colour is not vertexColor * factor");
|
||||
assertEquals(IDENTITY_FACTOR, factor, "a rejected solve must leave an identity factor");
|
||||
}
|
||||
|
||||
@Test
|
||||
void specularContributionIsNotRepresentable() {
|
||||
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_SPECULAR, floats(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
GLStateManager.glMaterialf(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 16.0f);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, floats(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "specular is additive, so the lit colour is not vertexColor * factor");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonAmbientAndDiffuseColorMaterialIsNotRepresentable() {
|
||||
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION);
|
||||
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "only AMBIENT_AND_DIFFUSE makes the lit colour scale with the vertex colour");
|
||||
}
|
||||
|
||||
@Test
|
||||
void lightsBeyondTheFfpCountAreIgnored() {
|
||||
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), FFPVertexLightingGLTest::gateState);
|
||||
final Vector3f twoLights = new Vector3f(factor);
|
||||
|
||||
light(GL11.GL_LIGHT2, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glEnable(GL11.GL_LIGHT2);
|
||||
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), FFPVertexLightingGLTest::gateState);
|
||||
|
||||
assertEquals(twoLights, factor, "VertexShaderGenerator emits only GL_LIGHT0/GL_LIGHT1, so text must not see GL_LIGHT2 either");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class UniformsRescaleTest {
|
||||
|
||||
private static float rescaleFactorFor(float sx, float sy, float sz) {
|
||||
final Matrix3f normalMatrix = new Matrix3f();
|
||||
new Matrix4f().rotateY(0.7f).scale(sx, sy, sz).normal(normalMatrix);
|
||||
return Uniforms.rescaleFactor(normalMatrix, new Vector3f());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rescaleFactorUsesTheInverseModelviewBottomRow() {
|
||||
assertEquals(8.0f, rescaleFactorFor(4.0f, 2.0f, 8.0f), 1.0e-5f, "wrong normal matrix column");
|
||||
}
|
||||
|
||||
@Test
|
||||
void uniformScaleIsRowIndependent() {
|
||||
assertEquals(2.0f, rescaleFactorFor(2.0f, 2.0f, 2.0f), 1.0e-5f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void singularMatrixFallsBackToUnitScale() {
|
||||
assertEquals(1.0f, rescaleFactorFor(1.0f, 1.0f, 0.0f), 1.0e-5f, "a degenerate normal matrix must not divide by zero");
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package com.gtnewhorizons.angelica.glsm.profiling;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class SectionRegistryTest {
|
||||
|
||||
private static final class RecordingEmitter implements SectionRegistry.Emitter {
|
||||
final List<String> setups = new ArrayList<>();
|
||||
final List<String> enters = new ArrayList<>();
|
||||
final List<Long> leaves = new ArrayList<>();
|
||||
long nextId;
|
||||
boolean connected;
|
||||
|
||||
@Override
|
||||
public long enter(int category, String text) {
|
||||
enters.add(category + ":" + text);
|
||||
return connected ? ++nextId : 0L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leave(long nativeId) {
|
||||
leaves.add(nativeId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setup(int category, String name) {
|
||||
setups.add(category + ":" + name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
}
|
||||
|
||||
private final RecordingEmitter emitter = new RecordingEmitter();
|
||||
private final SectionRegistry registry = new SectionRegistry(emitter);
|
||||
|
||||
private void reconnect() {
|
||||
emitter.connected = false;
|
||||
registry.poll();
|
||||
emitter.connected = true;
|
||||
registry.poll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void tokensOutliveEveryReplayAndLeaveUsesTheNewestId() {
|
||||
final long openedWhileDisconnected = registry.enter(1, "world");
|
||||
assertNotEquals(0L, openedWhileDisconnected, "token must be valid while disconnected");
|
||||
assertEquals(0L, emitter.nextId, "no native id while disconnected");
|
||||
|
||||
emitter.connected = true;
|
||||
registry.poll();
|
||||
final long openedWhileConnected = registry.enter(2, "shaders");
|
||||
assertNotEquals(openedWhileDisconnected, openedWhileConnected);
|
||||
|
||||
reconnect();
|
||||
emitter.leaves.clear();
|
||||
registry.leave(openedWhileDisconnected);
|
||||
registry.leave(openedWhileConnected);
|
||||
|
||||
assertEquals(List.of(3L, 4L), emitter.leaves, "leave must use the post-reconnect ids");
|
||||
}
|
||||
|
||||
@Test
|
||||
void aSectionEnteredOnTheConnectingFrameIsNotEnteredTwice() {
|
||||
emitter.connected = true;
|
||||
registry.enter(2, "shaders pack / Overworld");
|
||||
registry.poll();
|
||||
|
||||
assertEquals(List.of("2:shaders pack / Overworld"), emitter.enters, "must not re-enter a section already live on this connection");
|
||||
}
|
||||
|
||||
@Test
|
||||
void reconnectReplaysEveryCategoryAndEveryOpenSection() {
|
||||
registry.registerCategory(1, "World");
|
||||
registry.registerCategory(4, "UI");
|
||||
emitter.connected = true;
|
||||
registry.poll();
|
||||
registry.enter(1, "world");
|
||||
registry.enter(4, "pause");
|
||||
emitter.setups.clear();
|
||||
emitter.enters.clear();
|
||||
|
||||
reconnect();
|
||||
|
||||
assertEquals(List.of("1:World", "4:UI"), emitter.setups, "all categories re-registered");
|
||||
assertEquals(List.of("1:world", "4:pause"), emitter.enters, "all open sections re-entered");
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftSectionsAreNotResurrectedByAReconnect() {
|
||||
emitter.connected = true;
|
||||
registry.poll();
|
||||
final long token = registry.enter(1, "transient");
|
||||
registry.leave(token);
|
||||
emitter.enters.clear();
|
||||
emitter.leaves.clear();
|
||||
|
||||
reconnect();
|
||||
registry.leave(token);
|
||||
|
||||
assertTrue(emitter.enters.isEmpty(), "closed sections must not be re-entered");
|
||||
assertTrue(emitter.leaves.isEmpty(), "closed tokens must not be left twice");
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ eventbus = "7.0-beta.15"
|
||||
glslTransformLib = "0.2.0-33.g3ec381e"
|
||||
antlr4Runtime = "4.13.2"
|
||||
jcpp = "1.4.14"
|
||||
tracy = "0.13.1"
|
||||
tracy = "0.14.0"
|
||||
retroFuturaBootstrap = "1.1.0"
|
||||
|
||||
lombok = "1.18.46"
|
||||
|
||||
@@ -88,7 +88,6 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.lwjgl.sdl.SDLGPU;
|
||||
import org.lwjgl.sdl.SDLSurface;
|
||||
import org.lwjgl.sdl.SDL_GPUBlitInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUBufferLocation;
|
||||
import org.lwjgl.sdl.SDL_GPUTextureLocation;
|
||||
@@ -111,7 +110,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
private final Device device = SDLGPUGate.device();
|
||||
private final FrameManager frameManager = new FrameManager(device);
|
||||
private Presenter presenter;
|
||||
private volatile Presenter presenter;
|
||||
private final ResourceManager resourceManager = new ResourceManager(device, frameManager);
|
||||
private final Image3DClear image3DClear = new Image3DClear();
|
||||
private final ShaderManager shaderManager = new ShaderManager(device);
|
||||
@@ -155,7 +154,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
private volatile boolean shutdown;
|
||||
|
||||
private OffscreenTarget splashTarget;
|
||||
private volatile OffscreenTarget splashTarget;
|
||||
private volatile boolean splashTeardownPending;
|
||||
|
||||
private static final ThreadRegistry<ContextState> registeredStates = new ThreadRegistry<>(new ContextState[0]);
|
||||
private static final ThreadLocal<ContextState> tlState = new ThreadLocal<>();
|
||||
@@ -241,16 +241,12 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
});
|
||||
GLSMHooks.LOADING_CHECKPOINT
|
||||
.addListener(event -> {
|
||||
if (Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
if (!GLStateManager.isSplashComplete()) {
|
||||
SplashDispatcher.tryDispatch(this, splashTarget);
|
||||
} else if (splashTarget != null) {
|
||||
SplashDispatcher.signalFrameReady();
|
||||
SplashDispatcher.tryDispatch(this, splashTarget);
|
||||
splashTarget.destroy(resourceManager);
|
||||
splashTarget = null;
|
||||
SplashDispatcher.reset();
|
||||
if (!GLStateManager.isSplashComplete()) {
|
||||
if (Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
SplashDispatcher.tryDispatch(this, splashTarget, false);
|
||||
}
|
||||
} else if (splashTarget != null) {
|
||||
splashTeardownPending = true;
|
||||
}
|
||||
if (event.requiresSync) {
|
||||
frameManager.requestSyncFlushOnAllRegisteredFrames();
|
||||
@@ -411,12 +407,12 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public boolean wantsDisplayUpdateGateTiming() { return false; }
|
||||
|
||||
@Override public long lastFrameGateNanos() {
|
||||
if (presenter != null && presenter.isEngaged()) return frameManager.windowFrame().lastFrameGateNanos;
|
||||
if (presenter != null) return frameManager.windowFrame().lastFrameGateNanos;
|
||||
return frameManager.lastFrameGateNanos();
|
||||
}
|
||||
|
||||
@Override public long lastFrameGateEndNanos() {
|
||||
if (presenter != null && presenter.isEngaged()) return frameManager.windowFrame().lastFrameGateEndNanos;
|
||||
if (presenter != null) return frameManager.windowFrame().lastFrameGateEndNanos;
|
||||
return frameManager.lastFrameGateEndNanos();
|
||||
}
|
||||
|
||||
@@ -520,8 +516,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public boolean handleMakeCurrent(Object drawable) {
|
||||
if (!isSDLManagedDrawable(drawable)) return false;
|
||||
Lwjgl3GLCapabilitiesShim.installOnCurrentThread(advertisedCapabilities());
|
||||
if (splashTarget != null && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
s().boundFboId = splashTarget.fboId();
|
||||
final OffscreenTarget splash = splashTarget;
|
||||
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
s().boundFboId = splash.fboId();
|
||||
frameManager.frame().swapchainUnavailable = true;
|
||||
}
|
||||
return true;
|
||||
@@ -542,7 +539,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public boolean handleSwapBuffers() {
|
||||
if (splashTarget != null && splashTarget.isFor(s()) && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
final OffscreenTarget splash = splashTarget;
|
||||
if (splash != null && splash.isFor(s()) && Thread.currentThread() != GLStateManager.getMainThread()) {
|
||||
endFrameUploadFlush();
|
||||
frameManager.endFrame();
|
||||
SplashDispatcher.signalFrameReady((int) s().viewportW, (int) s().viewportH);
|
||||
@@ -560,13 +558,24 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long drawnSize = SplashDispatcher.getDrawnSize();
|
||||
final int srcW = Math.max(1, Math.min((int) (drawnSize >> 32), target.width()));
|
||||
final int srcH = Math.max(1, Math.min((int) drawnSize, target.height()));
|
||||
final FrameState f = frameManager.frame();
|
||||
frameManager.presentBlit(f, target.colorTexture(), srcW, srcH, SDLSurface.SDL_FLIP_VERTICAL);
|
||||
f.presentedThisFrame = false;
|
||||
frameManager.presentSplash(target.colorTexture(), srcW, srcH);
|
||||
}
|
||||
|
||||
private void finishSplash() {
|
||||
splashTeardownPending = false;
|
||||
final OffscreenTarget target = splashTarget;
|
||||
if (target == null) return;
|
||||
SplashDispatcher.signalFrameReady();
|
||||
SplashDispatcher.tryDispatch(this, target, true);
|
||||
if (presenter != null) presenter.drain();
|
||||
splashTarget = null;
|
||||
target.destroy(resourceManager);
|
||||
SplashDispatcher.reset();
|
||||
}
|
||||
|
||||
@Override public void onFrameBegin() {
|
||||
if (shutdown) return;
|
||||
if (splashTeardownPending) finishSplash();
|
||||
if (GLStateManager.isSplashComplete()) applyPendingVSync();
|
||||
frameManager.beginFrame();
|
||||
beginFrameInit();
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_VERTICAL;
|
||||
import static org.lwjgl.system.MemoryStack.*;
|
||||
|
||||
/**
|
||||
@@ -61,7 +62,6 @@ public final class FrameManager {
|
||||
public boolean fbo0UsedThisFrame;
|
||||
public boolean presentedThisFrame;
|
||||
public boolean swapchainUnavailable;
|
||||
public boolean windowThreadChecked;
|
||||
public long renderPassGeneration;
|
||||
|
||||
public boolean wantFenceOnNextSubmit;
|
||||
@@ -464,6 +464,20 @@ public final class FrameManager {
|
||||
}
|
||||
}
|
||||
|
||||
private volatile boolean acquireThreadReported;
|
||||
|
||||
private void checkAcquireOnWindowThread() {
|
||||
if (acquireThreadReported) return;
|
||||
final Thread self = Thread.currentThread();
|
||||
final Thread window = device.getWindowThread();
|
||||
if (window == null || window == self) return;
|
||||
acquireThreadReported = true;
|
||||
|
||||
final String message = "Swapchain acquired on '" + self.getName() + "', window created on '" + window.getName() + "'";
|
||||
if (SystemProperties.isDeobf() && !SystemProperties.DISABLE_SDL_PRESENTER_THREAD) throw new IllegalStateException(message);
|
||||
LOG.warn(message);
|
||||
}
|
||||
|
||||
public long ensureCopyPass() {
|
||||
final FrameState f = frame();
|
||||
if (f.copyPass != 0) {
|
||||
@@ -863,7 +877,7 @@ public final class FrameManager {
|
||||
return frame().lastFrameGateEndNanos;
|
||||
}
|
||||
|
||||
private Presenter presenter;
|
||||
private volatile Presenter presenter;
|
||||
|
||||
public void setPresenter(Presenter presenter) {
|
||||
this.presenter = presenter;
|
||||
@@ -877,18 +891,24 @@ public final class FrameManager {
|
||||
|
||||
public void presentFinalTarget() {
|
||||
if (!finalTarget.hasContent()) return;
|
||||
final long texture = finalTarget.colorTexture();
|
||||
final int width = finalTarget.width();
|
||||
final int height = finalTarget.height();
|
||||
present(finalTarget.colorTexture(), finalTarget.width(), finalTarget.height(), SDL_FLIP_NONE, false);
|
||||
}
|
||||
|
||||
public void presentSplash(long srcTexture, int srcW, int srcH) {
|
||||
present(srcTexture, srcW, srcH, SDL_FLIP_VERTICAL, true);
|
||||
}
|
||||
|
||||
private void present(long srcTexture, int srcW, int srcH, int flipMode, boolean splash) {
|
||||
final FrameState f = frame();
|
||||
if (f.presentedThisFrame) return;
|
||||
final Presenter p = presenter;
|
||||
if (p != null && p.isEngaged()) {
|
||||
final FrameState f = frame();
|
||||
if (f.presentedThisFrame) return;
|
||||
if (p != null) {
|
||||
f.presentedThisFrame = true;
|
||||
p.requestPresent(texture, width, height);
|
||||
p.requestPresent(srcTexture, srcW, srcH, flipMode);
|
||||
} else {
|
||||
presentBlit(frame(), texture, width, height, SDL_FLIP_NONE);
|
||||
presentBlit(f, srcTexture, srcW, srcH, flipMode);
|
||||
}
|
||||
if (splash) f.presentedThisFrame = false;
|
||||
}
|
||||
|
||||
private final AtomicLong windowAcquireWaitNanos = new AtomicLong();
|
||||
@@ -915,8 +935,8 @@ public final class FrameManager {
|
||||
else f.presentSkipsThisFrame++;
|
||||
}
|
||||
|
||||
public void presentOnWindowThread(long srcTexture, int srcW, int srcH) {
|
||||
presentBlit(windowFrame, srcTexture, srcW, srcH, SDL_FLIP_NONE);
|
||||
public void presentOnWindowThread(long srcTexture, int srcW, int srcH, int flipMode) {
|
||||
presentBlit(windowFrame, srcTexture, srcW, srcH, flipMode);
|
||||
windowFrame.presentedThisFrame = false;
|
||||
windowFrame.swapchainUnavailable = false;
|
||||
}
|
||||
@@ -925,16 +945,7 @@ public final class FrameManager {
|
||||
if (srcTexture == 0 || srcW <= 0 || srcH <= 0) return false;
|
||||
if (f.presentedThisFrame || f.swapchainUnavailable) return false;
|
||||
|
||||
if (!f.windowThreadChecked) {
|
||||
f.windowThreadChecked = true;
|
||||
final Thread self = Thread.currentThread();
|
||||
final Thread windowThread = device.getWindowThread();
|
||||
if (windowThread != null && windowThread != self) {
|
||||
LOG.warn("Swapchain acquired on '{}' but the SDL window was created on '{}'; SDL requires acquire on the window-creating thread", self.getName(), windowThread.getName());
|
||||
} else {
|
||||
LOG.info("First swapchain acquire on thread={}", self.getName());
|
||||
}
|
||||
}
|
||||
checkAcquireOnWindowThread();
|
||||
|
||||
final long window = device.getClaimedWindow();
|
||||
if (window == 0) return false;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.frame;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
@@ -16,17 +14,13 @@ public final class Presenter {
|
||||
this.windowThreadExecutor = windowThreadExecutor;
|
||||
}
|
||||
|
||||
public boolean isEngaged() {
|
||||
return windowThreadExecutor != null && GLStateManager.isSplashComplete();
|
||||
}
|
||||
|
||||
public void requestPresent(long srcTexture, int srcW, int srcH) {
|
||||
public void requestPresent(long srcTexture, int srcW, int srcH, int flipMode) {
|
||||
pending.acquireUninterruptibly();
|
||||
boolean submitted = false;
|
||||
try {
|
||||
windowThreadExecutor.execute(() -> {
|
||||
try {
|
||||
frameManager.presentOnWindowThread(srcTexture, srcW, srcH);
|
||||
frameManager.presentOnWindowThread(srcTexture, srcW, srcH, flipMode);
|
||||
} finally {
|
||||
pending.release();
|
||||
}
|
||||
|
||||
@@ -57,7 +57,6 @@ final class SdlFramePlots {
|
||||
private static final long PLOT_PIPELINE_KEY_RECOMPUTES = Tracy.plotHandle("sdl.pipelineKeyRecomputes");
|
||||
private static final long PLOT_PIPELINE_FAST_PATH_HITS = Tracy.plotHandle("sdl.pipelineFastPathHits");
|
||||
private static final long PLOT_MID_FRAME_SUBMITS = Tracy.plotHandle("sdl.midFrameSubmits");
|
||||
private static final long PLOT_GATE_US = Tracy.plotHandle("sdl.gateUs");
|
||||
|
||||
private static final int[] contextCounters = new int[SDLGPURenderBackend.CTR_COUNT];
|
||||
|
||||
@@ -110,6 +109,5 @@ final class SdlFramePlots {
|
||||
Tracy.plotInt(PLOT_EMPTY_FRAMES, f.emptyFramesThisFrame);
|
||||
Tracy.plotInt(PLOT_DROPPED_DRAWS, f.droppedDrawsThisFrame);
|
||||
Tracy.plotInt(PLOT_MID_FRAME_SUBMITS, f.midFrameSubmitsThisFrame);
|
||||
Tracy.plotInt(PLOT_GATE_US, f.lastFrameGateNanos / 1000);
|
||||
}
|
||||
}
|
||||
@@ -681,7 +681,7 @@ public final class ShaderManager {
|
||||
return null;
|
||||
}
|
||||
LOG.info("Vertex attribute conversion: program {} converted {}", program, attribs);
|
||||
if (SystemProperties.DUMP_SHADERS) dumpVariantSource(program, src);
|
||||
if (SystemProperties.dumpShaders()) dumpVariantSource(program, src);
|
||||
return v;
|
||||
} finally {
|
||||
memFree(r.spirv());
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ public final class DxbcCrossCompile {
|
||||
return new Output(copy, cached.entrypoint);
|
||||
}
|
||||
|
||||
final int dumpId = SystemProperties.DUMP_SHADERS ? CrossCompileUtil.SHADER_DUMP_COUNTER.getAndIncrement() : -1;
|
||||
final int dumpId = SystemProperties.dumpShaders() ? CrossCompileUtil.SHADER_DUMP_COUNTER.getAndIncrement() : -1;
|
||||
if (dumpId >= 0) CrossCompileUtil.dumpSpirv(spirv, dumpId, glShaderType);
|
||||
|
||||
final String hlsl = compileToHlsl(spirv, glShaderType);
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ public final class MslCrossCompile {
|
||||
return new Output(copy, cached.entrypoint);
|
||||
}
|
||||
|
||||
final int dumpId = SystemProperties.DUMP_SHADERS ? CrossCompileUtil.SHADER_DUMP_COUNTER.getAndIncrement() : -1;
|
||||
final int dumpId = SystemProperties.dumpShaders() ? CrossCompileUtil.SHADER_DUMP_COUNTER.getAndIncrement() : -1;
|
||||
if (dumpId >= 0) CrossCompileUtil.dumpSpirv(spirv, dumpId, glShaderType);
|
||||
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
|
||||
+2
-2
@@ -33,10 +33,10 @@ public final class SplashDispatcher {
|
||||
return (long) w << 32 | (h & 0xffffffffL);
|
||||
}
|
||||
|
||||
public static void tryDispatch(SDLGPURenderBackend backend, OffscreenTarget target) {
|
||||
public static void tryDispatch(SDLGPURenderBackend backend, OffscreenTarget target, boolean force) {
|
||||
if (!frameReady || target == null) return;
|
||||
final long now = System.nanoTime();
|
||||
if (now - lastBlitNanos < MIN_INTERVAL_NS) return;
|
||||
if (!force && now - lastBlitNanos < MIN_INTERVAL_NS) return;
|
||||
lastBlitNanos = now;
|
||||
frameReady = false;
|
||||
backend.dispatchSplashBlit(target);
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.concurrent.Executor;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
|
||||
|
||||
@Timeout(10)
|
||||
class PresenterTest {
|
||||
@@ -43,10 +44,10 @@ class PresenterTest {
|
||||
final DeferredExecutor executor = new DeferredExecutor();
|
||||
final Presenter p = presenter(executor);
|
||||
|
||||
p.requestPresent(1L, 10, 10);
|
||||
p.requestPresent(1L, 10, 10, SDL_FLIP_NONE);
|
||||
assertEquals(1, executor.tasks.size());
|
||||
|
||||
final Thread second = new Thread(() -> p.requestPresent(2L, 10, 10));
|
||||
final Thread second = new Thread(() -> p.requestPresent(2L, 10, 10, SDL_FLIP_NONE));
|
||||
second.start();
|
||||
awaitWaiting(second);
|
||||
assertEquals(1, executor.tasks.size(), "second present must not enqueue while the first is in flight");
|
||||
@@ -61,7 +62,7 @@ class PresenterTest {
|
||||
final DeferredExecutor executor = new DeferredExecutor();
|
||||
final Presenter p = presenter(executor);
|
||||
|
||||
p.requestPresent(1L, 10, 10);
|
||||
p.requestPresent(1L, 10, 10, SDL_FLIP_NONE);
|
||||
|
||||
final Thread drainer = new Thread(p::drain);
|
||||
drainer.start();
|
||||
@@ -74,8 +75,8 @@ class PresenterTest {
|
||||
@Test
|
||||
void anInlineExecutorReleasesTheSlotImmediately() {
|
||||
final Presenter p = new Presenter(new FrameManager(new Device()), Runnable::run);
|
||||
p.requestPresent(1L, 10, 10);
|
||||
p.requestPresent(2L, 10, 10);
|
||||
p.requestPresent(1L, 10, 10, SDL_FLIP_NONE);
|
||||
p.requestPresent(2L, 10, 10, SDL_FLIP_NONE);
|
||||
p.drain();
|
||||
}
|
||||
|
||||
@@ -87,7 +88,7 @@ class PresenterTest {
|
||||
fm.frame();
|
||||
final int before = fm.registeredFrameCount();
|
||||
|
||||
p.requestPresent(1L, 10, 10);
|
||||
p.requestPresent(1L, 10, 10, SDL_FLIP_NONE);
|
||||
|
||||
final Thread windowThread = new Thread(executor::runNext, "window-thread");
|
||||
windowThread.start();
|
||||
@@ -95,4 +96,18 @@ class PresenterTest {
|
||||
|
||||
assertEquals(before, fm.registeredFrameCount(), "the window thread must stay out of the frame registry");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theSplashPresentGoesThroughTheWindowThreadToo() {
|
||||
final DeferredExecutor executor = new DeferredExecutor();
|
||||
final FrameManager fm = new FrameManager(new Device());
|
||||
fm.setPresenter(new Presenter(fm, executor));
|
||||
fm.frame();
|
||||
final int before = fm.registeredFrameCount();
|
||||
|
||||
fm.presentSplash(1L, 10, 10);
|
||||
|
||||
assertEquals(1, executor.tasks.size(), "the splash blit must be enqueued on the window thread");
|
||||
assertEquals(before, fm.registeredFrameCount(), "the splash present must not touch the calling thread's frame");
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.TestPaths;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.SamplerLookup;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.util.shaderc.Shaderc;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.system.MemoryUtil.memFree;
|
||||
|
||||
class FontFilterVertexSamplerTest {
|
||||
|
||||
private static final String FONT_FILTER_VSH = TestPaths.readString("src/main/resources/assets/angelica/shaders/fontFilter.vsh");
|
||||
|
||||
private static String runSdlPipeline(String raw) {
|
||||
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(raw, GL20.GL_VERTEX_SHADER, "fontFilter.vsh", true);
|
||||
String src = pre != null ? pre.rewrittenSource() : raw;
|
||||
src = ClipZRemap.injectGLToVulkanClipZ(src);
|
||||
src = SamplerStripper.stripUnused(src);
|
||||
return src;
|
||||
}
|
||||
|
||||
private static ShaderManager.StageReflection reflect(String source) {
|
||||
final SpirvCompiler.Result r = SpirvCompiler.compile(source, Shaderc.shaderc_vertex_shader,
|
||||
"fontFilter.vsh", SpirvCompiler.Options.vulkanForced460Core());
|
||||
assertNotNull(r.spirv(), () -> "fontFilter.vsh failed to compile for Vulkan: " + r.error());
|
||||
final ByteBuffer spirv = r.spirv();
|
||||
try {
|
||||
return ShaderManager.reflectStage(spirv, true);
|
||||
} finally {
|
||||
memFree(spirv);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void samplerSurvivesStrippingAndReflectsOnTheVertexStage() {
|
||||
final String transformed = runSdlPipeline(FONT_FILTER_VSH);
|
||||
assertTrue(transformed.contains("uniform sampler2D lightmap"), "a reference inside a runtime if must keep the declaration; only preprocessor #if hides it");
|
||||
|
||||
final List<String> samplers = reflect(transformed).samplerNames();
|
||||
assertEquals(List.of("lightmap"), samplers, "the vertex stage must expose exactly the lightmap sampler");
|
||||
}
|
||||
|
||||
@Test
|
||||
void looseVec3UniformReflectsWithAStd140Offset() {
|
||||
final List<ShaderManager.UboMember> members = reflect(runSdlPipeline(FONT_FILTER_VSH)).uboMembers();
|
||||
|
||||
final ShaderManager.UboMember lightmap = members.stream()
|
||||
.filter(m -> m.name().endsWith("u_Lightmap"))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("u_Lightmap missing from the default uniform block: " + members));
|
||||
|
||||
assertEquals(3, lightmap.vectorSize(), "u_Lightmap must reflect as a 3-wide vector");
|
||||
assertEquals(1, lightmap.columns(), "u_Lightmap is a vector, not a matrix");
|
||||
assertEquals(0, lightmap.arrayStride(), "a scalar member takes Std140Writer's contiguous path");
|
||||
assertEquals(0, lightmap.offset() % 16, "std140 aligns a vec3 to 16 bytes");
|
||||
}
|
||||
|
||||
@Test
|
||||
void samplerNamedLightmapResolvesToTextureUnitOne() {
|
||||
final List<String> samplers = reflect(runSdlPipeline(FONT_FILTER_VSH)).samplerNames();
|
||||
final Object2IntOpenHashMap<String> noExplicitUnits = new Object2IntOpenHashMap<>();
|
||||
noExplicitUnits.defaultReturnValue(-1);
|
||||
|
||||
assertEquals(1, SamplerLookup.getSamplerTextureUnit(samplers, noExplicitUnits, 0), "even without a glUniform1i the SDL fallback must put a sampler named 'lightmap' on unit 1");
|
||||
}
|
||||
}
|
||||
@@ -4,45 +4,61 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_CHAR;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.GRADIENT_PAYLOAD;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_LENGTH;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_PAYLOAD;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FFPVertexLighting;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
|
||||
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import lombok.Setter;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.gl.program.Program;
|
||||
import net.coderbot.iris.gl.program.ProgramBuilder;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
|
||||
import net.coderbot.iris.pipeline.PipelineManager;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutByte;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memRealloc;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_CHAR;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.GRADIENT_PAYLOAD;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_LENGTH;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_PAYLOAD;
|
||||
|
||||
/**
|
||||
* A batching replacement for {@code FontRenderer}
|
||||
@@ -67,6 +83,8 @@ public class BatchingFontRenderer {
|
||||
private final int AAStrength;
|
||||
private final int alphaTestRefLocation;
|
||||
private final int mvpMatrixLocation;
|
||||
private final int lightmapLocation;
|
||||
private final int lightmapSamplerLocation;
|
||||
private final int fontShaderId;
|
||||
|
||||
final boolean isSGA;
|
||||
@@ -79,6 +97,7 @@ public class BatchingFontRenderer {
|
||||
private static class FontAAShader {
|
||||
|
||||
private static Program fontShader = null;
|
||||
|
||||
public static Program getProgram() {
|
||||
if (fontShader == null) {
|
||||
final String vsh = ShaderLoader.getShaderSource("angelica:fontFilter.vsh");
|
||||
@@ -111,6 +130,8 @@ public class BatchingFontRenderer {
|
||||
AAStrength = GLStateManager.glGetUniformLocation(fontShaderId, "strength");
|
||||
alphaTestRefLocation = GLStateManager.glGetUniformLocation(fontShaderId, "alphaTestRef");
|
||||
mvpMatrixLocation = GLStateManager.glGetUniformLocation(fontShaderId, "u_MVPMatrix");
|
||||
lightmapLocation = GLStateManager.glGetUniformLocation(fontShaderId, "u_Lightmap");
|
||||
lightmapSamplerLocation = GLStateManager.glGetUniformLocation(fontShaderId, "lightmap");
|
||||
if (ebo == null) {
|
||||
ebo = new IndexBuffer();
|
||||
vbo = GLStateManager.glGenBuffers();
|
||||
@@ -128,6 +149,9 @@ public class BatchingFontRenderer {
|
||||
// [v, v, t, t, c, c, c, c, tb, tb, tb, tb]
|
||||
// v, t and tb are floats, c is bytes; 36 bytes total
|
||||
private static final int VERTEX_SIZE = 36;
|
||||
|
||||
/** {@code OpenGlHelper.lightmapTexUnit} */
|
||||
private static final int LIGHTMAP_TEX_UNIT = 1;
|
||||
private static int rawCapacity = INITIAL_BATCH_SIZE * VERTEX_SIZE;
|
||||
private static ByteBuffer vertexData = memAlloc(rawCapacity);
|
||||
private static long vertexDataAddress = memAddress0(vertexData);
|
||||
@@ -138,6 +162,7 @@ public class BatchingFontRenderer {
|
||||
private static int fontVAO = 0;
|
||||
private static int vbo;
|
||||
private static IndexBuffer ebo;
|
||||
private static int lightmapSamplerProgram;
|
||||
|
||||
private static final int RING_CAPACITY = 2 * 1024 * 1024;
|
||||
private static PersistentStreamingBuffer streamRing;
|
||||
@@ -158,11 +183,65 @@ public class BatchingFontRenderer {
|
||||
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
|
||||
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
|
||||
private final BlendState blendStateBefore = new BlendState();
|
||||
private static final BlendState deferredBlendStateBefore = new BlendState();
|
||||
|
||||
private float lightmapU = 0.0f;
|
||||
private float lightmapV = 0.0f;
|
||||
private boolean lightmapActive = false;
|
||||
private int lightmapTextureId = 0;
|
||||
|
||||
private final Vector3f lightingFactor = new Vector3f(1.0f, 1.0f, 1.0f);
|
||||
private boolean lightingFactorActive = false;
|
||||
|
||||
private static final Vector4f scratchLightmapUv = new Vector4f();
|
||||
|
||||
private int applyLighting(int argb) {
|
||||
if (!lightingFactorActive) return argb;
|
||||
final int red = Math.max(0, Math.min(255, (int) (((argb >> 16) & 0xFF) * lightingFactor.x + 0.5f)));
|
||||
final int green = Math.max(0, Math.min(255, (int) (((argb >> 8) & 0xFF) * lightingFactor.y + 0.5f)));
|
||||
final int blue = Math.max(0, Math.min(255, (int) ((argb & 0xFF) * lightingFactor.z + 0.5f)));
|
||||
return (argb & 0xFF000000) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
private void captureLightmapState() {
|
||||
final int unitBinding = GLStateManager.getTextures().getTextureUnitBindings(LIGHTMAP_TEX_UNIT).getBinding();
|
||||
|
||||
final boolean active = GLStateManager.getProjectionMatrix().m33() == 0.0f && unitBinding != 0;
|
||||
|
||||
float u = 0.0f;
|
||||
float v = 0.0f;
|
||||
int texture = 0;
|
||||
if (active) {
|
||||
scratchLightmapUv.set(GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY, 0.0f, 1.0f).mul(GLStateManager.getTextures().getTextureUnitMatrix(LIGHTMAP_TEX_UNIT));
|
||||
u = scratchLightmapUv.x;
|
||||
v = scratchLightmapUv.y;
|
||||
texture = unitBinding;
|
||||
}
|
||||
|
||||
if (active == lightmapActive && u == lightmapU && v == lightmapV && texture == lightmapTextureId) {
|
||||
return;
|
||||
}
|
||||
sealBatchSegment();
|
||||
lightmapActive = active;
|
||||
lightmapU = u;
|
||||
lightmapV = v;
|
||||
lightmapTextureId = texture;
|
||||
}
|
||||
|
||||
private static final class TextSegment {
|
||||
final Matrix4f mvp = new Matrix4f();
|
||||
final Matrix4f modelView = new Matrix4f();
|
||||
BatchingFontRenderer owner;
|
||||
int cmdStart;
|
||||
int cmdEnd;
|
||||
float lightmapU;
|
||||
float lightmapV;
|
||||
boolean lightmapActive;
|
||||
int lightmapTexture;
|
||||
int packedLight;
|
||||
float normalX, normalY, normalZ;
|
||||
int blockEntityId;
|
||||
}
|
||||
|
||||
// 16-bit EBO index range
|
||||
@@ -174,7 +253,6 @@ public class BatchingFontRenderer {
|
||||
private int deferredCmdWatermark;
|
||||
private static final ObjectArrayList<TextSegment> deferredSegments = ObjectArrayList.wrap(new TextSegment[16], 0);
|
||||
private static final ObjectArrayList<TextSegment> deferredSegmentPool = ObjectArrayList.wrap(new TextSegment[16], 0);
|
||||
private boolean deferCurrentBatch;
|
||||
|
||||
private final Matrix4f batchProj = new Matrix4f();
|
||||
private final Matrix4f batchModelView = new Matrix4f();
|
||||
@@ -183,7 +261,6 @@ public class BatchingFontRenderer {
|
||||
private int batchSealedEnd;
|
||||
private static BatchingFontRenderer arenaOwner;
|
||||
|
||||
|
||||
private void allocateBuffers() {
|
||||
populateEBO(rawCapacity / VERTEX_SIZE);
|
||||
}
|
||||
@@ -255,6 +332,7 @@ public class BatchingFontRenderer {
|
||||
|
||||
private void pushVtx(float x, float y, int rgba, float u, float v, float uMin, float uMax, float vMin, float vMax) {
|
||||
final long ptr = vertexDataAddress + vertexDataPos;
|
||||
rgba = applyLighting(rgba);
|
||||
|
||||
// v, v
|
||||
memPutFloat(ptr, x);
|
||||
@@ -406,6 +484,10 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveModelView(Matrix4f dest) {
|
||||
dest.set(batchMatrixValid ? batchModelView : GLStateManager.getModelViewMatrix());
|
||||
}
|
||||
|
||||
private void sealBatchSegment() {
|
||||
final int end = batchCommands.size();
|
||||
if (end == batchSealedEnd) return;
|
||||
@@ -414,6 +496,17 @@ public class BatchingFontRenderer {
|
||||
segment.cmdStart = batchSealedEnd;
|
||||
segment.cmdEnd = end;
|
||||
resolveMvp(segment.mvp);
|
||||
resolveModelView(segment.modelView);
|
||||
segment.lightmapActive = lightmapActive;
|
||||
segment.lightmapU = lightmapU;
|
||||
segment.lightmapV = lightmapV;
|
||||
segment.lightmapTexture = lightmapTextureId;
|
||||
segment.packedLight = GLSMConfig.packedLastBrightness();
|
||||
final Vector3f normal = ShaderManager.getCurrentNormal();
|
||||
segment.normalX = normal.x;
|
||||
segment.normalY = normal.y;
|
||||
segment.normalZ = normal.z;
|
||||
segment.blockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
|
||||
batchSegments.add(segment);
|
||||
batchSealedEnd = end;
|
||||
}
|
||||
@@ -434,27 +527,29 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
batchDepth--;
|
||||
if (batchDepth == 0) {
|
||||
// We finished any nested batches
|
||||
flushBatch();
|
||||
// We finished any nested batches.
|
||||
if (shouldDeferNow()) {
|
||||
deferBatch();
|
||||
} else {
|
||||
flushBatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldDeferNow() {
|
||||
if (!GLStateManager.isMainThread()) return false;
|
||||
return TesrBatchRenderer.INSTANCE.hasPendingGeometry() || ModelPartBatcher.INSTANCE.isActive();
|
||||
}
|
||||
|
||||
public void beginDeferredBatch() {
|
||||
deferCurrentBatch = batchDepth == 0 && TesrBatchRenderer.INSTANCE.hasActivePass() || ModelPartBatcher.INSTANCE.isActive();
|
||||
if (deferCurrentBatch && deferredIdxPos / 6 > QUAD_BUDGET / 2) {
|
||||
if (batchDepth == 0 && shouldDeferNow() && deferredIdxPos / 6 > QUAD_BUDGET / 2) {
|
||||
flushDeferredText();
|
||||
}
|
||||
beginBatch();
|
||||
}
|
||||
|
||||
public void endDeferredBatch() {
|
||||
if (deferCurrentBatch && batchDepth == 1) {
|
||||
deferCurrentBatch = false;
|
||||
batchDepth = 0;
|
||||
deferBatch();
|
||||
} else {
|
||||
endBatch();
|
||||
}
|
||||
endBatch();
|
||||
}
|
||||
|
||||
private void deferBatch() {
|
||||
@@ -479,34 +574,123 @@ public class BatchingFontRenderer {
|
||||
public static void flushDeferredText() {
|
||||
if (deferredSegments.isEmpty()) return;
|
||||
|
||||
if (deferredSegments.getFirst().owner.shouldDrawThroughPipeline()) {
|
||||
flushDeferredThroughPipeline();
|
||||
return;
|
||||
}
|
||||
|
||||
GLStateManager.beginForeignDraw();
|
||||
try {
|
||||
flushDeferredTextInner();
|
||||
} finally {
|
||||
GLStateManager.endForeignDraw();
|
||||
}
|
||||
}
|
||||
|
||||
private static void flushDeferredThroughPipeline() {
|
||||
final BatchingFontRenderer first = deferredSegments.getFirst().owner;
|
||||
final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
|
||||
final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled();
|
||||
final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled();
|
||||
GLStateManager.getEffectiveBlendState(deferredBlendStateBefore);
|
||||
final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled();
|
||||
final int prevBlockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
|
||||
boolean textureChanged = false;
|
||||
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(first.blendSrcRGB, first.blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glDepthMask(false);
|
||||
|
||||
final Boolean prevTranslucency = GbufferPrograms.beginTranslucencyDeclaration(Boolean.TRUE);
|
||||
GbufferPrograms.setBlockEntityDefaults();
|
||||
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPushMatrix();
|
||||
try {
|
||||
for (final TextSegment segment : deferredSegments) {
|
||||
final BatchingFontRenderer owner = segment.owner;
|
||||
if (segment.cmdStart == segment.cmdEnd) continue;
|
||||
|
||||
GLStateManager.setModelViewMatrix(segment.modelView);
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(segment.blockEntityId);
|
||||
|
||||
final FontDrawCmd[] cmdsData = owner.batchCommands.elements();
|
||||
Arrays.sort(cmdsData, segment.cmdStart, segment.cmdEnd, FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
|
||||
textureChanged |= emitRangeThroughPipeline(owner, cmdsData, segment.cmdStart, segment.cmdEnd,
|
||||
segment.packedLight, segment.normalX, segment.normalY, segment.normalZ);
|
||||
}
|
||||
} finally {
|
||||
GLStateManager.glPopMatrix();
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
|
||||
GbufferPrograms.endTranslucencyDeclaration(prevTranslucency);
|
||||
|
||||
GLStateManager.glDepthMask(depthMaskBefore);
|
||||
if (isTextureEnabledBefore) {
|
||||
GLStateManager.enableTexture();
|
||||
} else {
|
||||
GLStateManager.disableTexture();
|
||||
}
|
||||
if (isBlendEnabledBefore) {
|
||||
GLStateManager.enableBlend();
|
||||
} else {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
GLStateManager.tryBlendFuncSeparate(deferredBlendStateBefore.getSrcRgb(), deferredBlendStateBefore.getDstRgb(),
|
||||
deferredBlendStateBefore.getSrcAlpha(), deferredBlendStateBefore.getDstAlpha());
|
||||
if (isAlphaTestEnabledBefore) {
|
||||
GLStateManager.enableAlphaTest();
|
||||
} else {
|
||||
GLStateManager.disableAlphaTest();
|
||||
}
|
||||
if (textureChanged) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore);
|
||||
}
|
||||
discardDeferredText();
|
||||
}
|
||||
}
|
||||
|
||||
private static void flushDeferredTextInner() {
|
||||
uploadVertexData(deferredVertexPos);
|
||||
|
||||
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
|
||||
final boolean isBlendEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_BLEND);
|
||||
final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled();
|
||||
final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled();
|
||||
GLStateManager.getEffectiveBlendState(deferredBlendStateBefore);
|
||||
final int shadeModelBefore = GLStateManager.glGetInteger(GL11.GL_SHADE_MODEL);
|
||||
final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
final int lightmapBindingBefore = boundLightmapTexture();
|
||||
|
||||
final boolean depthTestBefore = GLStateManager.getDepthTest().isEnabled();
|
||||
final boolean depthMaskBefore = GLStateManager.getDepthState().isEnabled();
|
||||
final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled();
|
||||
|
||||
deferredSegments.get(0).owner.setupFontDrawState();
|
||||
GLStateManager.glDepthMask(false);
|
||||
flushLastTexture = DUMMY_RESOURCE_LOCATION;
|
||||
flushTextureChanged = false;
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer mvpBuf = stack.mallocFloat(16);
|
||||
for (int i = 0, n = deferredSegments.size(); i < n; i++) {
|
||||
final TextSegment segment = deferredSegments.get(i);
|
||||
mvpBuf.clear();
|
||||
segment.mvp.get(mvpBuf);
|
||||
GLStateManager.glUniformMatrix4(segment.owner.mvpMatrixLocation, false, mvpBuf);
|
||||
drawCommands(segment.owner.batchCommands.elements(), segment.cmdStart, segment.cmdEnd, segment.owner);
|
||||
resetFlushLightmap();
|
||||
try {
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer mvpBuf = stack.mallocFloat(16);
|
||||
for (final TextSegment segment : deferredSegments) {
|
||||
mvpBuf.clear();
|
||||
segment.mvp.get(mvpBuf);
|
||||
GLStateManager.glUniformMatrix4(segment.owner.mvpMatrixLocation, false, mvpBuf);
|
||||
uploadLightmap(segment.owner.lightmapLocation, segment.lightmapActive, segment.lightmapU,
|
||||
segment.lightmapV, segment.lightmapTexture);
|
||||
drawCommands(segment.owner.batchCommands.elements(), segment.cmdStart, segment.cmdEnd, segment.owner);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
restoreFontDrawState(prevProgram, isTextureEnabledBefore, isBlendEnabledBefore, boundTextureBefore,
|
||||
lightmapBindingBefore, isAlphaTestEnabledBefore, deferredBlendStateBefore, shadeModelBefore);
|
||||
restoreDepth(depthTestBefore, depthMaskBefore);
|
||||
discardDeferredText();
|
||||
}
|
||||
restoreFontDrawState(prevProgram, isTextureEnabledBefore, isBlendEnabledBefore, boundTextureBefore);
|
||||
restoreDepth(depthTestBefore, depthMaskBefore);
|
||||
|
||||
discardDeferredText();
|
||||
}
|
||||
|
||||
public static void discardDeferredText() {
|
||||
@@ -517,6 +701,7 @@ public class BatchingFontRenderer {
|
||||
segment.owner.recycleBatchSegments();
|
||||
segment.owner.batchSealedEnd = 0;
|
||||
segment.owner.batchMatrixValid = false;
|
||||
segment.owner.resetLightmapState();
|
||||
segment.owner = null;
|
||||
}
|
||||
deferredSegmentPool.addAll(deferredSegments);
|
||||
@@ -544,18 +729,47 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
|
||||
private static final Matrix4f scratchMvp = new Matrix4f();
|
||||
private static final Matrix4f scratchModelView = new Matrix4f();
|
||||
private int fontAAModeLast = -1;
|
||||
private int fontAAStrengthLast = -1;
|
||||
|
||||
private void flushBatch() {
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
flushBatchInner();
|
||||
if (shouldDrawThroughPipeline()) {
|
||||
flushBatchThroughPipeline();
|
||||
} else {
|
||||
GLStateManager.beginForeignDraw();
|
||||
try {
|
||||
flushBatchInner();
|
||||
} finally {
|
||||
GLStateManager.endForeignDraw();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldDrawThroughPipeline() {
|
||||
if (FontConfig.fontAAMode != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GLStateManager.isMainThread()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final PipelineManager pipelines = Iris.getPipelineManagerNullable();
|
||||
if (pipelines == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !Tessellator.instance.isDrawing
|
||||
&& pipelines.getPipelineNullable() instanceof DeferredWorldRenderingPipeline drp
|
||||
&& drp.shouldOverrideShaders();
|
||||
}
|
||||
|
||||
private void flushBatchInner() {
|
||||
final int cmdCount = batchCommands.size();
|
||||
final int segmentCount = batchSegments.size();
|
||||
@@ -576,14 +790,19 @@ public class BatchingFontRenderer {
|
||||
Arrays.sort(cmds, batchSealedEnd, cmdCount, FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
|
||||
final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
|
||||
final boolean isBlendEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_BLEND);
|
||||
final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled();
|
||||
final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled();
|
||||
GLStateManager.getEffectiveBlendState(blendStateBefore);
|
||||
final int shadeModelBefore = GLStateManager.glGetInteger(GL11.GL_SHADE_MODEL);
|
||||
final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
final int lightmapBindingBefore = boundLightmapTexture();
|
||||
final boolean depthTestBefore = GLStateManager.getDepthTest().isEnabled();
|
||||
final boolean depthMaskBefore = GLStateManager.getDepthState().isEnabled();
|
||||
final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled();
|
||||
|
||||
setupFontDrawState();
|
||||
flushLastTexture = DUMMY_RESOURCE_LOCATION;
|
||||
flushTextureChanged = false;
|
||||
resetFlushLightmap();
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final FloatBuffer mvpBuf = stack.mallocFloat(16);
|
||||
for (int i = 0; i < segmentCount; i++) {
|
||||
@@ -591,6 +810,7 @@ public class BatchingFontRenderer {
|
||||
mvpBuf.clear();
|
||||
segment.mvp.get(mvpBuf);
|
||||
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
|
||||
uploadLightmap(lightmapLocation, segment.lightmapActive, segment.lightmapU, segment.lightmapV, segment.lightmapTexture);
|
||||
drawCommands(cmds, segment.cmdStart, segment.cmdEnd, this);
|
||||
}
|
||||
if (batchSealedEnd < cmdCount) {
|
||||
@@ -598,10 +818,12 @@ public class BatchingFontRenderer {
|
||||
resolveMvp(scratchMvp);
|
||||
scratchMvp.get(mvpBuf);
|
||||
GLStateManager.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
|
||||
uploadLightmap(lightmapLocation, lightmapActive, lightmapU, lightmapV, lightmapTextureId);
|
||||
drawCommands(cmds, batchSealedEnd, cmdCount, this);
|
||||
}
|
||||
}
|
||||
restoreFontDrawState(prevProgram, isTextureEnabledBefore, isBlendEnabledBefore, boundTextureBefore);
|
||||
restoreFontDrawState(prevProgram, isTextureEnabledBefore, isBlendEnabledBefore, boundTextureBefore,
|
||||
lightmapBindingBefore, isAlphaTestEnabledBefore, blendStateBefore, shadeModelBefore);
|
||||
restoreDepth(depthTestBefore, depthMaskBefore);
|
||||
|
||||
truncateBatchToWatermark();
|
||||
@@ -624,6 +846,10 @@ public class BatchingFontRenderer {
|
||||
GLStateManager.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
|
||||
}
|
||||
GLStateManager.glUniform1f(alphaTestRefLocation, GLStateManager.getAlphaState().getReference());
|
||||
if (lightmapSamplerProgram != fontShaderId) {
|
||||
GLStateManager.glUniform1i(lightmapSamplerLocation, LIGHTMAP_TEX_UNIT);
|
||||
lightmapSamplerProgram = fontShaderId;
|
||||
}
|
||||
|
||||
if (fontVAO == 0) {
|
||||
fontVAO = GLStateManager.glGenVertexArrays();
|
||||
@@ -654,6 +880,38 @@ public class BatchingFontRenderer {
|
||||
private static ResourceLocation flushLastTexture;
|
||||
private static boolean flushTextureChanged;
|
||||
|
||||
private static float flushLastLightmapU;
|
||||
private static float flushLastLightmapV;
|
||||
private static boolean flushLastLightmapActive;
|
||||
|
||||
private static void resetFlushLightmap() {
|
||||
flushLastLightmapU = Float.NaN;
|
||||
flushLastLightmapV = Float.NaN;
|
||||
flushLastLightmapActive = false;
|
||||
}
|
||||
|
||||
private static int boundLightmapTexture() {
|
||||
return GLStateManager.getTextures().getTextureUnitBindings(LIGHTMAP_TEX_UNIT).getBinding();
|
||||
}
|
||||
|
||||
private static void bindLightmapTexture(int texture) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + LIGHTMAP_TEX_UNIT);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
|
||||
private static void uploadLightmap(int uniformLocation, boolean active, float u, float v, int texture) {
|
||||
if (u != flushLastLightmapU || v != flushLastLightmapV || active != flushLastLightmapActive) {
|
||||
GLStateManager.glUniform3f(uniformLocation, u, v, active ? 1.0f : 0.0f);
|
||||
flushLastLightmapU = u;
|
||||
flushLastLightmapV = v;
|
||||
flushLastLightmapActive = active;
|
||||
}
|
||||
if (active && texture != 0 && boundLightmapTexture() != texture) {
|
||||
bindLightmapTexture(texture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawCommands(FontDrawCmd[] cmdsData, int from, int to, BatchingFontRenderer owner) {
|
||||
for (int i = from; i < to; i++) {
|
||||
final FontDrawCmd cmd = cmdsData[i];
|
||||
@@ -673,7 +931,9 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void restoreFontDrawState(int prevProgram, boolean textureEnabledBefore, boolean blendEnabledBefore, int boundTextureBefore) {
|
||||
private static void restoreFontDrawState(int prevProgram, boolean textureEnabledBefore, boolean blendEnabledBefore,
|
||||
int boundTextureBefore, int lightmapBindingBefore, boolean alphaTestEnabledBefore, BlendState blendBefore,
|
||||
int shadeModelBefore) {
|
||||
GLStateManager.glUseProgram(prevProgram);
|
||||
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
@@ -682,19 +942,177 @@ public class BatchingFontRenderer {
|
||||
if (textureEnabledBefore) {
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
if (!blendEnabledBefore) {
|
||||
if (blendEnabledBefore) {
|
||||
GLStateManager.enableBlend();
|
||||
} else {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
GLStateManager.tryBlendFuncSeparate(blendBefore.getSrcRgb(), blendBefore.getDstRgb(),
|
||||
blendBefore.getSrcAlpha(), blendBefore.getDstAlpha());
|
||||
if (alphaTestEnabledBefore) {
|
||||
GLStateManager.enableAlphaTest();
|
||||
} else {
|
||||
GLStateManager.disableAlphaTest();
|
||||
}
|
||||
GLStateManager.glShadeModel(shadeModelBefore);
|
||||
if (flushTextureChanged) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore);
|
||||
}
|
||||
if (boundLightmapTexture() != lightmapBindingBefore) {
|
||||
bindLightmapTexture(lightmapBindingBefore);
|
||||
}
|
||||
}
|
||||
|
||||
private void flushBatchThroughPipeline() {
|
||||
final int cmdCount = batchCommands.size();
|
||||
final int segmentCount = batchSegments.size();
|
||||
if (segmentCount == 0 && (vertexDataPos == deferredVertexPos || cmdCount == deferredCmdWatermark)) {
|
||||
truncateBatchToWatermark();
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
|
||||
final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled();
|
||||
final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled();
|
||||
GLStateManager.getEffectiveBlendState(blendStateBefore);
|
||||
final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
final int prevBlockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
|
||||
boolean textureChanged = false;
|
||||
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
|
||||
final Boolean prevTranslucency = GbufferPrograms.beginTranslucencyDeclaration(Boolean.TRUE);
|
||||
GbufferPrograms.setBlockEntityDefaults();
|
||||
|
||||
final FontDrawCmd[] cmdsData = batchCommands.elements();
|
||||
for (int i = 0; i < segmentCount; i++) {
|
||||
final TextSegment segment = batchSegments.get(i);
|
||||
Arrays.sort(cmdsData, segment.cmdStart, segment.cmdEnd, FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
}
|
||||
Arrays.sort(cmdsData, batchSealedEnd, cmdCount, FontDrawCmd.DRAW_ORDER_COMPARATOR);
|
||||
|
||||
// The unsealed tail still belongs to the modelview live at flush time; capture it before the segments move it.
|
||||
resolveModelView(scratchModelView);
|
||||
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPushMatrix();
|
||||
try {
|
||||
for (int i = 0; i < segmentCount; i++) {
|
||||
final TextSegment segment = batchSegments.get(i);
|
||||
if (segment.cmdStart == segment.cmdEnd) continue;
|
||||
GLStateManager.setModelViewMatrix(segment.modelView);
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(segment.blockEntityId);
|
||||
textureChanged |= emitRangeThroughPipeline(this, cmdsData, segment.cmdStart, segment.cmdEnd,
|
||||
segment.packedLight, segment.normalX, segment.normalY, segment.normalZ);
|
||||
}
|
||||
if (batchSealedEnd < cmdCount) {
|
||||
GLStateManager.setModelViewMatrix(scratchModelView);
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
|
||||
final Vector3f normal = ShaderManager.getCurrentNormal();
|
||||
textureChanged |= emitRangeThroughPipeline(this, cmdsData, batchSealedEnd, cmdCount,
|
||||
GLSMConfig.packedLastBrightness(), normal.x, normal.y, normal.z);
|
||||
}
|
||||
} finally {
|
||||
GLStateManager.glPopMatrix();
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
|
||||
GbufferPrograms.endTranslucencyDeclaration(prevTranslucency);
|
||||
|
||||
if (isTextureEnabledBefore) {
|
||||
GLStateManager.enableTexture();
|
||||
} else {
|
||||
GLStateManager.disableTexture();
|
||||
}
|
||||
if (isBlendEnabledBefore) {
|
||||
GLStateManager.enableBlend();
|
||||
} else {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
GLStateManager.tryBlendFuncSeparate(blendStateBefore.getSrcRgb(), blendStateBefore.getDstRgb(),
|
||||
blendStateBefore.getSrcAlpha(), blendStateBefore.getDstAlpha());
|
||||
if (isAlphaTestEnabledBefore) {
|
||||
GLStateManager.enableAlphaTest();
|
||||
} else {
|
||||
GLStateManager.disableAlphaTest();
|
||||
}
|
||||
if (textureChanged) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore);
|
||||
}
|
||||
|
||||
truncateBatchToWatermark();
|
||||
}
|
||||
}
|
||||
|
||||
/** Replays one sealed command range through the Tessellator so Iris sees it as ordinary geometry. */
|
||||
private static boolean emitRangeThroughPipeline(BatchingFontRenderer owner, FontDrawCmd[] cmdsData, int from, int to,
|
||||
int packedLight, float normalX, float normalY, float normalZ) {
|
||||
final Tessellator tessellator = Tessellator.instance;
|
||||
ResourceLocation lastTexture = DUMMY_RESOURCE_LOCATION;
|
||||
boolean textureChanged = false;
|
||||
boolean drawing = false;
|
||||
try {
|
||||
for (int i = from; i < to; i++) {
|
||||
final FontDrawCmd cmd = cmdsData[i];
|
||||
if (!Objects.equals(lastTexture, cmd.texture)) {
|
||||
if (drawing) {
|
||||
tessellator.draw();
|
||||
drawing = false;
|
||||
}
|
||||
if (cmd.texture == null) {
|
||||
GLStateManager.disableTexture();
|
||||
} else {
|
||||
GLStateManager.enableTexture();
|
||||
((FontRendererAccessor) owner.underlying).angelica$bindTexture(cmd.texture);
|
||||
textureChanged = true;
|
||||
}
|
||||
lastTexture = cmd.texture;
|
||||
}
|
||||
if (!drawing) {
|
||||
tessellator.startDrawingQuads();
|
||||
tessellator.setBrightness(packedLight);
|
||||
tessellator.setNormal(normalX, normalY, normalZ);
|
||||
drawing = true;
|
||||
}
|
||||
owner.emitCommand(tessellator, cmd);
|
||||
}
|
||||
} finally {
|
||||
if (drawing) {
|
||||
tessellator.draw();
|
||||
}
|
||||
}
|
||||
return textureChanged;
|
||||
}
|
||||
|
||||
private void emitCommand(Tessellator tessellator, FontDrawCmd cmd) {
|
||||
final int firstVertex = cmd.startVtx / 6 * 4;
|
||||
final int quadCount = cmd.idxCount / 6;
|
||||
|
||||
for (int q = 0; q < quadCount; q++) {
|
||||
final long base = vertexDataAddress + (long) (firstVertex + q * 4) * VERTEX_SIZE;
|
||||
emitVertex(tessellator, base);
|
||||
emitVertex(tessellator, base + VERTEX_SIZE);
|
||||
emitVertex(tessellator, base + 3L * VERTEX_SIZE);
|
||||
emitVertex(tessellator, base + 2L * VERTEX_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
private void emitVertex(Tessellator tessellator, long ptr) {
|
||||
tessellator.setColorRGBA(
|
||||
memGetByte(ptr + 16) & 0xFF,
|
||||
memGetByte(ptr + 17) & 0xFF,
|
||||
memGetByte(ptr + 18) & 0xFF,
|
||||
memGetByte(ptr + 19) & 0xFF);
|
||||
tessellator.addVertexWithUV(memGetFloat(ptr), memGetFloat(ptr + 4), 0.0D,
|
||||
memGetFloat(ptr + 8), memGetFloat(ptr + 12));
|
||||
}
|
||||
|
||||
private static void restoreDepth(boolean testBefore, boolean maskBefore) {
|
||||
if (testBefore != GLStateManager.getDepthTest().isEnabled()) {
|
||||
if (testBefore) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
|
||||
}
|
||||
if (maskBefore != GLStateManager.getDepthState().isEnabled()) {
|
||||
if (maskBefore != GLStateManager.isEffectiveDepthMaskEnabled()) {
|
||||
GLStateManager.glDepthMask(maskBefore);
|
||||
}
|
||||
}
|
||||
@@ -709,11 +1127,19 @@ public class BatchingFontRenderer {
|
||||
batchMatrixValid = false;
|
||||
vertexDataPos = deferredVertexPos;
|
||||
idxWriterIndex = deferredIdxPos;
|
||||
resetLightmapState();
|
||||
if (arenaOwner == this) {
|
||||
arenaOwner = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void resetLightmapState() {
|
||||
lightmapActive = false;
|
||||
lightmapU = 0.0f;
|
||||
lightmapV = 0.0f;
|
||||
lightmapTextureId = 0;
|
||||
}
|
||||
|
||||
// === Actual text mesh generation
|
||||
|
||||
public static boolean charInRange(char what, char fromInclusive, char toInclusive) {
|
||||
@@ -744,7 +1170,7 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
|
||||
private static int hsvToRgb(float hue, float sat, float val) {
|
||||
int h = (int)(hue / 60f) % 6;
|
||||
int h = (int) (hue / 60f) % 6;
|
||||
float f = hue / 60f - h;
|
||||
float p = val * (1 - sat);
|
||||
float q = val * (1 - f * sat);
|
||||
@@ -758,11 +1184,12 @@ public class BatchingFontRenderer {
|
||||
case 4: r=t; g=p; b=val; break;
|
||||
default: r=val; g=p; b=q; break;
|
||||
}
|
||||
return ((int)(r*255) << 16) | ((int)(g*255) << 8) | (int)(b*255);
|
||||
return ((int) (r * 255) << 16) | ((int) (g * 255) << 8) | (int) (b * 255);
|
||||
}
|
||||
|
||||
private static final int RAINBOW_LUT_SIZE = 24;
|
||||
private static final int[] RAINBOW_LUT = new int[RAINBOW_LUT_SIZE];
|
||||
|
||||
static {
|
||||
for (int i = 0; i < RAINBOW_LUT_SIZE; i++) {
|
||||
RAINBOW_LUT[i] = hsvToRgb(i * 15f, 1f, 1f);
|
||||
@@ -797,7 +1224,7 @@ public class BatchingFontRenderer {
|
||||
private static final float WAVE_FREQUENCY = 0.5f;
|
||||
|
||||
public float drawString(final float anchorX, final float anchorY, final int color, final boolean enableShadow,
|
||||
final boolean unicodeFlag, final CharSequence string, int stringOffset, int stringLength) {
|
||||
final boolean unicodeFlag, final CharSequence string, int stringOffset, int stringLength) {
|
||||
// noinspection SizeReplaceableByIsEmpty
|
||||
if (string == null || string.length() == 0) {
|
||||
return anchorX + (enableShadow ? 1.0f : 0.0f);
|
||||
@@ -808,6 +1235,8 @@ public class BatchingFontRenderer {
|
||||
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
|
||||
|
||||
this.beginBatch();
|
||||
this.captureLightmapState();
|
||||
this.lightingFactorActive = FFPVertexLighting.modulatesVertexColor(this.lightingFactor);
|
||||
float curX = anchorX;
|
||||
try {
|
||||
final int totalStringLength = string.length();
|
||||
@@ -884,86 +1313,86 @@ public class BatchingFontRenderer {
|
||||
charIdx += SECTION_X_PAYLOAD;
|
||||
}
|
||||
} else {
|
||||
final boolean is09 = charInRange(fmtCode, '0', '9');
|
||||
final boolean isAF = charInRange(fmtCode, 'a', 'f');
|
||||
if (is09 || isAF) {
|
||||
curRandom = false;
|
||||
curBold = false;
|
||||
curStrikethrough = false;
|
||||
curUnderline = false;
|
||||
curItalic = false;
|
||||
curRainbow = false;
|
||||
curGradient = false;
|
||||
// wave/dinnerbone NOT reset — they're positional effects, independent of color
|
||||
|
||||
final int colorIdx = is09 ? (fmtCode - '0') : (fmtCode - 'a' + 10);
|
||||
final int rgb = this.colorCode[colorIdx];
|
||||
curColor = (curColor & 0xFF000000) | (rgb & 0x00FFFFFF);
|
||||
final int shadowRgb = this.colorCode[colorIdx + 16];
|
||||
curShadowColor = (curShadowColor & 0xFF000000) | (shadowRgb & 0x00FFFFFF);
|
||||
} else if (fmtCode == 'k') {
|
||||
curRandom = true;
|
||||
} else if (fmtCode == 'l') {
|
||||
curBold = true;
|
||||
} else if (fmtCode == 'm') {
|
||||
curStrikethrough = true;
|
||||
strikethroughStartX = curX - 1.0f;
|
||||
strikethroughEndX = strikethroughStartX;
|
||||
} else if (fmtCode == 'n') {
|
||||
curUnderline = true;
|
||||
underlineStartX = curX - 1.0f;
|
||||
underlineEndX = underlineStartX;
|
||||
} else if (fmtCode == 'o') {
|
||||
curItalic = true;
|
||||
} else if (fmtCode == 'q' && AngelicaConfig.enableRainbow) {
|
||||
curRainbow = true;
|
||||
curGradient = false;
|
||||
rainbowCharIndex = 0;
|
||||
} else if (fmtCode == 'z' && AngelicaConfig.enableWaveText) {
|
||||
curWave = !curWave;
|
||||
} else if (fmtCode == 'v' && AngelicaConfig.enableDinnerboneText) {
|
||||
curDinnerbone = !curDinnerbone;
|
||||
} else if (fmtCode == 'u' && AngelicaConfig.enableDropShadow) {
|
||||
int customRgb = (charIdx + SECTION_X_LENGTH < stringEnd)
|
||||
? ColorCodeUtils.parseSectionXAt(string, charIdx + 1)
|
||||
: -1;
|
||||
if (customRgb != -1) {
|
||||
curShadow = true;
|
||||
curShadowCustomColor = true;
|
||||
curShadowColorOverride = customRgb;
|
||||
charIdx += SECTION_X_LENGTH;
|
||||
} else {
|
||||
curShadow = !curShadow;
|
||||
if (!curShadow) curShadowCustomColor = false;
|
||||
}
|
||||
} else if (fmtCode == 'g' && AngelicaConfig.enableGradients && charIdx + GRADIENT_PAYLOAD < stringEnd) {
|
||||
int color1 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1);
|
||||
int color2 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1 + SECTION_X_LENGTH);
|
||||
if (color1 != -1 && color2 != -1) {
|
||||
curGradient = true;
|
||||
final boolean is09 = charInRange(fmtCode, '0', '9');
|
||||
final boolean isAF = charInRange(fmtCode, 'a', 'f');
|
||||
if (is09 || isAF) {
|
||||
curRandom = false;
|
||||
curBold = false;
|
||||
curStrikethrough = false;
|
||||
curUnderline = false;
|
||||
curItalic = false;
|
||||
curRainbow = false;
|
||||
gradientStartRgb = color1;
|
||||
gradientEndRgb = color2;
|
||||
gradientCharIndex = 0;
|
||||
gradientTotalChars = countVisibleChars(string, charIdx + 1 + GRADIENT_PAYLOAD, stringEnd);
|
||||
gradientStep = gradientTotalChars > 1 ? 1f / (gradientTotalChars - 1) : 0f;
|
||||
charIdx += GRADIENT_PAYLOAD;
|
||||
curGradient = false;
|
||||
// wave/dinnerbone NOT reset — they're positional effects, independent of color
|
||||
|
||||
final int colorIdx = is09 ? (fmtCode - '0') : (fmtCode - 'a' + 10);
|
||||
final int rgb = this.colorCode[colorIdx];
|
||||
curColor = (curColor & 0xFF000000) | (rgb & 0x00FFFFFF);
|
||||
final int shadowRgb = this.colorCode[colorIdx + 16];
|
||||
curShadowColor = (curShadowColor & 0xFF000000) | (shadowRgb & 0x00FFFFFF);
|
||||
} else if (fmtCode == 'k') {
|
||||
curRandom = true;
|
||||
} else if (fmtCode == 'l') {
|
||||
curBold = true;
|
||||
} else if (fmtCode == 'm') {
|
||||
curStrikethrough = true;
|
||||
strikethroughStartX = curX - 1.0f;
|
||||
strikethroughEndX = strikethroughStartX;
|
||||
} else if (fmtCode == 'n') {
|
||||
curUnderline = true;
|
||||
underlineStartX = curX - 1.0f;
|
||||
underlineEndX = underlineStartX;
|
||||
} else if (fmtCode == 'o') {
|
||||
curItalic = true;
|
||||
} else if (fmtCode == 'q' && AngelicaConfig.enableRainbow) {
|
||||
curRainbow = true;
|
||||
curGradient = false;
|
||||
rainbowCharIndex = 0;
|
||||
} else if (fmtCode == 'z' && AngelicaConfig.enableWaveText) {
|
||||
curWave = !curWave;
|
||||
} else if (fmtCode == 'v' && AngelicaConfig.enableDinnerboneText) {
|
||||
curDinnerbone = !curDinnerbone;
|
||||
} else if (fmtCode == 'u' && AngelicaConfig.enableDropShadow) {
|
||||
int customRgb = (charIdx + SECTION_X_LENGTH < stringEnd)
|
||||
? ColorCodeUtils.parseSectionXAt(string, charIdx + 1)
|
||||
: -1;
|
||||
if (customRgb != -1) {
|
||||
curShadow = true;
|
||||
curShadowCustomColor = true;
|
||||
curShadowColorOverride = customRgb;
|
||||
charIdx += SECTION_X_LENGTH;
|
||||
} else {
|
||||
curShadow = !curShadow;
|
||||
if (!curShadow) curShadowCustomColor = false;
|
||||
}
|
||||
} else if (fmtCode == 'g' && AngelicaConfig.enableGradients && charIdx + GRADIENT_PAYLOAD < stringEnd) {
|
||||
int color1 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1);
|
||||
int color2 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1 + SECTION_X_LENGTH);
|
||||
if (color1 != -1 && color2 != -1) {
|
||||
curGradient = true;
|
||||
curRainbow = false;
|
||||
gradientStartRgb = color1;
|
||||
gradientEndRgb = color2;
|
||||
gradientCharIndex = 0;
|
||||
gradientTotalChars = countVisibleChars(string, charIdx + 1 + GRADIENT_PAYLOAD, stringEnd);
|
||||
gradientStep = gradientTotalChars > 1 ? 1f / (gradientTotalChars - 1) : 0f;
|
||||
charIdx += GRADIENT_PAYLOAD;
|
||||
}
|
||||
} else if (fmtCode == 'r') {
|
||||
curRandom = false;
|
||||
curBold = false;
|
||||
curStrikethrough = false;
|
||||
curUnderline = false;
|
||||
curItalic = false;
|
||||
curRainbow = false;
|
||||
curWave = false;
|
||||
curDinnerbone = false;
|
||||
curGradient = false;
|
||||
curShadow = false;
|
||||
curShadowCustomColor = false;
|
||||
curColor = color;
|
||||
curShadowColor = shadowColor;
|
||||
}
|
||||
} else if (fmtCode == 'r') {
|
||||
curRandom = false;
|
||||
curBold = false;
|
||||
curStrikethrough = false;
|
||||
curUnderline = false;
|
||||
curItalic = false;
|
||||
curRainbow = false;
|
||||
curWave = false;
|
||||
curDinnerbone = false;
|
||||
curGradient = false;
|
||||
curShadow = false;
|
||||
curShadowCustomColor = false;
|
||||
curColor = color;
|
||||
curShadowColor = shadowColor;
|
||||
}
|
||||
} // close else block for non-§x codes
|
||||
|
||||
continue;
|
||||
@@ -1007,9 +1436,9 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
if (curGradient && gradientTotalChars > 0) {
|
||||
float t = Math.min(gradientCharIndex * gradientStep, 1f);
|
||||
int gr = (int)((gradientStartRgb >> 16 & 0xFF) * (1-t) + (gradientEndRgb >> 16 & 0xFF) * t);
|
||||
int gg = (int)((gradientStartRgb >> 8 & 0xFF) * (1-t) + (gradientEndRgb >> 8 & 0xFF) * t);
|
||||
int gb = (int)((gradientStartRgb & 0xFF) * (1-t) + (gradientEndRgb & 0xFF) * t);
|
||||
int gr = (int) ((gradientStartRgb >> 16 & 0xFF) * (1 - t) + (gradientEndRgb >> 16 & 0xFF) * t);
|
||||
int gg = (int) ((gradientStartRgb >> 8 & 0xFF) * (1 - t) + (gradientEndRgb >> 8 & 0xFF) * t);
|
||||
int gb = (int) ((gradientStartRgb & 0xFF) * (1 - t) + (gradientEndRgb & 0xFF) * t);
|
||||
int rgbEffect = (gr << 16) | (gg << 8) | gb;
|
||||
curColor = (curColor & 0xFF000000) | rgbEffect;
|
||||
curShadowColor = (curShadowColor & 0xFF000000) | ((rgbEffect & 0xFCFCFC) >> 2);
|
||||
@@ -1032,7 +1461,7 @@ public class BatchingFontRenderer {
|
||||
// Wave: Y offset via sine wave
|
||||
float renderY = heightNorth;
|
||||
if (curWave) {
|
||||
float time = HUDCaching.renderingCacheOverride ? 0f : (float)((System.nanoTime() & 0xFFFFFFFFFFFFL) * WAVE_TIME_SCALE);
|
||||
float time = HUDCaching.renderingCacheOverride ? 0f : (float) ((System.nanoTime() & 0xFFFFFFFFFFFFL) * WAVE_TIME_SCALE);
|
||||
renderY += (float) Math.sin(visibleCharIndex * WAVE_FREQUENCY + time) * AngelicaConfig.waveAmplitude;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import net.coderbot.batchedentityrendering.impl.BlendingStateHolder;
|
||||
import net.coderbot.batchedentityrendering.impl.TransparencyType;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.layer.PassOverride;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
@@ -68,11 +69,9 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
return TRANSLUCENT;
|
||||
}
|
||||
|
||||
private static MultiPhaseParameters.Builder tesrMaterialPhases(ResourceLocation texture, TesrMaterial material) {
|
||||
private static MultiPhaseParameters.Builder tesrMaterialPhases(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) {
|
||||
final MultiPhaseParameters.Builder b = MultiPhaseParameters.builder();
|
||||
if (texture != null) {
|
||||
b.texture(new RenderPhase.Texture(texture, false, false));
|
||||
}
|
||||
b.texture(texture != null ? new RenderPhase.Texture(texture, false, false) : NO_TEXTURE);
|
||||
if (material.isNoCull()) {
|
||||
b.cull(DISABLE_CULLING);
|
||||
}
|
||||
@@ -85,19 +84,16 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
if (material.isDepthOnly()) {
|
||||
b.writeMaskState(DEPTH_MASK);
|
||||
}
|
||||
if (material.cutoutAlpha() > 0) {
|
||||
b.alpha(new RenderPhase.Alpha(material.cutoutAlpha()));
|
||||
}
|
||||
if (material.isDepthEqual()) {
|
||||
b.depthTest(EQUAL_DEPTH_TEST);
|
||||
}
|
||||
b.alpha(material.cutoutAlpha() > 0 ? new RenderPhase.Alpha(material.cutoutAlpha()) : ZERO_ALPHA);
|
||||
b.depthTest(material.isDepthEqual() ? EQUAL_DEPTH_TEST : LEQUAL_DEPTH_TEST);
|
||||
switch (material.transparency()) {
|
||||
case TRANSLUCENT -> b.transparency(TRANSLUCENT_TRANSPARENCY);
|
||||
case ADDITIVE -> b.transparency(ADDITIVE_TRANSPARENCY);
|
||||
case ADDITIVE_ALPHA -> b.transparency(LIGHTNING_TRANSPARENCY);
|
||||
case GLINT -> b.transparency(GLINT_TRANSPARENCY);
|
||||
case OPAQUE -> {}
|
||||
case OPAQUE -> b.transparency(NO_TRANSPARENCY);
|
||||
}
|
||||
boolean shaderPhaseTaken = true;
|
||||
switch (material.special()) {
|
||||
case GLINT -> b.shader(SPECIAL_GLINT);
|
||||
case BEACON_BEAM -> b.shader(SPECIAL_BEACON_BEAM);
|
||||
@@ -105,9 +101,15 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
final TesrShader shader = material.shader();
|
||||
if (shader != null) {
|
||||
b.shader(new RenderPhase.Shader("angelica_tesr_shader_" + shader.name(), shader.bind(), shader.release()));
|
||||
} else {
|
||||
shaderPhaseTaken = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shaderPhaseTaken && pass != PassOverride.NONE) {
|
||||
b.shader(new RenderPhase.Shader("angelica_tesr_pass" + pass.nameSuffix(), pass::apply, pass::clear));
|
||||
}
|
||||
b.layering(polygonOffset(offsetFactor, offsetUnits));
|
||||
return b;
|
||||
}
|
||||
|
||||
@@ -119,19 +121,21 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
GbufferPrograms::teardownSpecialRenderCondition);
|
||||
|
||||
public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material) {
|
||||
final MultiPhaseParameters.Builder b = tesrMaterialPhases(texture, material).shadeModel(SMOOTH_SHADE_MODEL).lightmap(ENABLE_LIGHTMAP);
|
||||
if (material.transparency() == TesrMaterial.Transparency.TRANSLUCENT) {
|
||||
b.target(TRANSLUCENT_TARGET);
|
||||
}
|
||||
return tesr(texture, material, PassOverride.NONE, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) {
|
||||
final MultiPhaseParameters.Builder b = tesrMaterialPhases(texture, material, pass, offsetFactor, offsetUnits).shadeModel(SMOOTH_SHADE_MODEL);
|
||||
final boolean sortsTranslucent = material.transparency() != TesrMaterial.Transparency.OPAQUE;
|
||||
return of("angelica_tesr_" + material.transparency().name().toLowerCase(Locale.ROOT),
|
||||
return of("angelica_tesr_" + material.transparency().name().toLowerCase(Locale.ROOT) + pass.nameSuffix()
|
||||
+ (offsetFactor == 0.0f && offsetUnits == 0.0f ? "" : "_offset" + offsetFactor + "_" + offsetUnits),
|
||||
BatchVertexFormats.POSITION_COLOR_TEXTURE_LIGHTF_NORMAL, GL11.GL_QUADS, 65536, true, sortsTranslucent, b.build(false));
|
||||
}
|
||||
|
||||
public static RenderLayer tesrNoPass(ResourceLocation texture, TesrMaterial material) {
|
||||
return of("angelica_tesr_nopass_" + material.transparency().name().toLowerCase(Locale.ROOT),
|
||||
BatchVertexFormats.POSITION_COLOR_TEXTURE_LIGHTF_NORMAL, GL11.GL_QUADS, 65536, true, false,
|
||||
tesrMaterialPhases(texture, material).build(false));
|
||||
tesrMaterialPhases(texture, material, PassOverride.NONE, 0.0f, 0.0f).build(false));
|
||||
}
|
||||
|
||||
public static RenderLayer getOutline(ResourceLocation texture, RenderPhase.Cull cull) {
|
||||
@@ -154,7 +158,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
|
||||
@Override
|
||||
public TransparencyType getTransparencyType() {
|
||||
return phases.transparency.getTransparencyType();
|
||||
return phases.transparency == null ? TransparencyType.OPAQUE : phases.transparency.getTransparencyType();
|
||||
}
|
||||
|
||||
private MultiPhase(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent, MultiPhaseParameters phases) {
|
||||
@@ -170,7 +174,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
}
|
||||
});
|
||||
this.phases = phases;
|
||||
this.affectedOutline = phases.outlineMode == RenderLayer.OutlineMode.AFFECTS_OUTLINE ? phases.texture.getId().map((arg2) -> {
|
||||
this.affectedOutline = phases.outlineMode == RenderLayer.OutlineMode.AFFECTS_OUTLINE && phases.texture != null ? phases.texture.getId().map((arg2) -> {
|
||||
return getOutline(arg2, phases.cull);
|
||||
}) : Optional.empty();
|
||||
this.hash = Objects.hash(new Object[]{super.hashCode(), phases});
|
||||
@@ -187,7 +191,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTextureId() {
|
||||
return phases.texture.getId().orElse(null);
|
||||
return phases.texture == null ? null : phases.texture.getId().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -223,7 +227,9 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
if (arg == arg2) {
|
||||
return true;
|
||||
} else {
|
||||
return arg != null && arg2 != null ? Objects.equals(arg.phases, arg2.phases) : false;
|
||||
return arg != null && arg2 != null && arg.name.equals(arg2.name)
|
||||
&& arg.getVertexFormat() == arg2.getVertexFormat() && arg.getDrawMode() == arg2.getDrawMode()
|
||||
&& Objects.equals(arg.phases, arg2.phases);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,7 +269,13 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
this.writeMaskState = writeMaskState;
|
||||
this.shader = shader;
|
||||
this.outlineMode = outlineMode;
|
||||
this.phases = ImmutableList.of(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering, this.target, this.texturing, this.writeMaskState, this.shader);
|
||||
final ImmutableList.Builder<RenderPhase> declared = ImmutableList.builder();
|
||||
for (RenderPhase phase : new RenderPhase[] { this.texture, this.transparency, this.diffuseLighting,
|
||||
this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering,
|
||||
this.target, this.texturing, this.writeMaskState, this.shader }) {
|
||||
if (phase != null) declared.add(phase);
|
||||
}
|
||||
this.phases = declared.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -308,22 +320,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
private RenderPhase.WriteMaskState writeMaskState;
|
||||
private RenderPhase.Shader shader;
|
||||
|
||||
private Builder() {
|
||||
this.texture = RenderPhase.NO_TEXTURE ;
|
||||
this.transparency = RenderPhase.NO_TRANSPARENCY;
|
||||
this.diffuseLighting = RenderPhase.DISABLE_DIFFUSE_LIGHTING;
|
||||
this.shadeModel = RenderPhase.SHADE_MODEL;
|
||||
this.alpha = RenderPhase.ZERO_ALPHA;
|
||||
this.depthTest = RenderPhase.LEQUAL_DEPTH_TEST;
|
||||
this.cull = RenderPhase.ENABLE_CULLING;
|
||||
this.lightmap = RenderPhase.DISABLE_LIGHTMAP;
|
||||
this.fog = RenderPhase.FOG;
|
||||
this.layering = RenderPhase.NO_LAYERING;
|
||||
this.target = RenderPhase.MAIN_TARGET;
|
||||
this.texturing = RenderPhase.DEFAULT_TEXTURING;
|
||||
this.writeMaskState = RenderPhase.ALL_MASK;
|
||||
this.shader = RenderPhase.NO_SHADER;
|
||||
}
|
||||
private Builder() {}
|
||||
|
||||
public Builder texture(RenderPhase.Texture texture) {
|
||||
this.texture = texture;
|
||||
@@ -335,6 +332,10 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
|
||||
return this;
|
||||
}
|
||||
|
||||
public void layering(Layering layering) {
|
||||
this.layering = layering;
|
||||
}
|
||||
|
||||
public Builder shadeModel(RenderPhase.ShadeModel shadeModel) {
|
||||
this.shadeModel = shadeModel;
|
||||
return this;
|
||||
|
||||
@@ -9,7 +9,9 @@ import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
@@ -77,6 +79,7 @@ public abstract class RenderPhase {
|
||||
protected static final WriteMaskState COLOR_MASK;
|
||||
protected static final WriteMaskState DEPTH_MASK;
|
||||
protected static final Layering NO_LAYERING;
|
||||
|
||||
protected static final Fog NO_FOG;
|
||||
protected static final Fog FOG;
|
||||
protected static final Fog BLACK_FOG;
|
||||
@@ -85,6 +88,22 @@ public abstract class RenderPhase {
|
||||
protected static final Target TRANSLUCENT_TARGET;
|
||||
protected static final Shader NO_SHADER = new Shader("no_shader", () -> {}, () -> {});
|
||||
|
||||
private static final Map<Long, Layering> POLYGON_OFFSET_LAYERINGS = new ConcurrentHashMap<>();
|
||||
|
||||
public static Layering polygonOffset(float factor, float units) {
|
||||
if (factor == 0.0f && units == 0.0f) {
|
||||
return NO_LAYERING;
|
||||
}
|
||||
final long key = ((long) Float.floatToIntBits(factor) << 32) | (Float.floatToIntBits(units) & 0xFFFFFFFFL);
|
||||
return POLYGON_OFFSET_LAYERINGS.computeIfAbsent(key, k -> new Layering("polygon_offset_" + factor + "_" + units, () -> {
|
||||
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(factor, units);
|
||||
}, () -> {
|
||||
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(0.0f, 0.0f);
|
||||
}));
|
||||
}
|
||||
|
||||
public RenderPhase(String name, Runnable beginAction, Runnable endAction) {
|
||||
this.name = name;
|
||||
this.beginAction = beginAction;
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.config;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.config.Config;
|
||||
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
|
||||
import net.coderbot.iris.shadows.ShadowGraphGate;
|
||||
|
||||
@Config(modid = "angelica", filename = "angelica-modules")
|
||||
public class AngelicaConfig {
|
||||
@@ -347,6 +348,20 @@ public class AngelicaConfig {
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean shadowSkipInMeshTileEntities;
|
||||
|
||||
@Config.Comment({
|
||||
"How much a celestial body can move before the shadow map can update.",
|
||||
"",
|
||||
"Note this option is disabled when a shader pack utilizes voxelization features."
|
||||
})
|
||||
@Config.DefaultFloat(ShadowGraphGate.DEFAULT_ANGLE_DELTA_DEGREES)
|
||||
@Config.RangeFloat(min = 0f, max = 0.1f)
|
||||
public static float shadowGraphAngleDelta;
|
||||
|
||||
@Config.Comment("Adjusts the rate the shadow map rebuilds when when a celestial body is near the horizon. Rate is sinusoidal")
|
||||
@Config.DefaultFloat(ShadowGraphGate.DEFAULT_HORIZON_SCALE)
|
||||
@Config.RangeFloat(min = 0f, max = 1.0f)
|
||||
public static float shadowGraphHorizonScale;
|
||||
|
||||
@Config.Comment("ASM transformer exclusion narrowing for mod compatibility. Disable per-mod if narrowing causes class loading issues.")
|
||||
public static TransformerCompat transformerCompat = new TransformerCompat();
|
||||
|
||||
|
||||
@@ -56,7 +56,9 @@ public class FontConfig {
|
||||
@Config.RangeFloat(min = -2F, max = 2F)
|
||||
public static float glyphSpacing;
|
||||
|
||||
@Config.Comment("Controls font antialiasing. 0 = none, 1 = 4x MSAA, 2 = 16x MSAA.")
|
||||
@Config.Comment({
|
||||
"Controls font antialiasing. 0 = none, 1 = 4x MSAA, 2 = 16x MSAA.",
|
||||
"Turning off AA lets shaders pick up the text."})
|
||||
@Config.DefaultInt(2)
|
||||
@Config.RangeInt(min = 0, max = 2)
|
||||
public static int fontAAMode;
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.debug.flyby;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.rendering.FramePacer;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -57,6 +58,10 @@ public final class FlybyRunner {
|
||||
private long plotTurning;
|
||||
private int lastPhase = -1;
|
||||
|
||||
private long warmupSection;
|
||||
private long runSection;
|
||||
private long legSection;
|
||||
|
||||
private long[] frameTimesNs = NO_FRAMES;
|
||||
private int frameCount;
|
||||
private boolean framesTruncated;
|
||||
@@ -180,9 +185,13 @@ public final class FlybyRunner {
|
||||
this.buildPath();
|
||||
this.freezeTimeAndWeather(mc);
|
||||
|
||||
this.state = this.warmupTicks > 0 ? State.WARMUP : State.RUNNING;
|
||||
this.tick = 0;
|
||||
if (this.state == State.RUNNING) this.beginMeasuring();
|
||||
if (this.warmupTicks > 0) {
|
||||
this.state = State.WARMUP;
|
||||
this.warmupSection = Tracy.sectionEnter(Tracy.SECTION_BENCHMARK, "flyby warmup");
|
||||
} else {
|
||||
this.beginMeasuring();
|
||||
}
|
||||
|
||||
LOGGER.info("Flyby {} starting at {} {} {} yaw {}", this.route.id(),
|
||||
this.originX, this.originY, this.originZ, this.originYaw);
|
||||
@@ -252,12 +261,18 @@ public final class FlybyRunner {
|
||||
final int phase = (leg << 1) | turning;
|
||||
if (phase != this.lastPhase) {
|
||||
this.lastPhase = phase;
|
||||
Tracy.message("flyby " + (turning != 0 ? "turn" : "leg") + " " + leg);
|
||||
final String label = "flyby " + (turning != 0 ? "turn" : "leg") + " " + leg;
|
||||
Tracy.message(label);
|
||||
Tracy.sectionLeave(this.legSection);
|
||||
this.legSection = Tracy.sectionEnter(Tracy.SECTION_BENCHMARK, label);
|
||||
}
|
||||
}
|
||||
|
||||
private void beginMeasuring() {
|
||||
this.state = State.RUNNING;
|
||||
Tracy.sectionLeave(this.warmupSection);
|
||||
this.warmupSection = 0L;
|
||||
if (Tracy.ENABLED) this.runSection = Tracy.sectionEnter(Tracy.SECTION_BENCHMARK, "flyby " + this.route.id());
|
||||
this.plotLeg = Tracy.plotHandle("flyby.leg");
|
||||
this.plotTurning = Tracy.plotHandle("flyby.turning");
|
||||
this.lastPhase = -1;
|
||||
@@ -265,11 +280,10 @@ public final class FlybyRunner {
|
||||
this.frameCount = 0;
|
||||
this.lastFrameNs = 0L;
|
||||
this.runStartNs = System.nanoTime();
|
||||
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
|
||||
+ " sdlgpu=" + SystemProperties.USE_SDL_GPU);
|
||||
FramePacer.beginStats();
|
||||
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 + " sdlgpu=" + SystemProperties.USE_SDL_GPU);
|
||||
}
|
||||
}
|
||||
|
||||
private void holdPosition(EntityClientPlayerMP player) {
|
||||
@@ -364,10 +378,15 @@ public final class FlybyRunner {
|
||||
this.state = State.SETTLE;
|
||||
this.tick = 0;
|
||||
final long elapsedNs = System.nanoTime() - this.runStartNs;
|
||||
Tracy.message("flyby end route=" + this.route.id() + " frames=" + this.frameCount);
|
||||
Tracy.sectionLeave(this.legSection);
|
||||
this.legSection = 0L;
|
||||
Tracy.sectionLeave(this.runSection);
|
||||
this.runSection = 0L;
|
||||
if (Tracy.ENABLED) Tracy.message("flyby end route=" + this.route.id() + " frames=" + this.frameCount);
|
||||
|
||||
final String summary = this.summarise(elapsedNs, player);
|
||||
LOGGER.info(summary);
|
||||
LOGGER.info(FramePacer.endStats());
|
||||
if (mc.thePlayer != null) {
|
||||
mc.thePlayer.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + summary));
|
||||
}
|
||||
@@ -422,6 +441,13 @@ public final class FlybyRunner {
|
||||
if (this.isActive()) {
|
||||
LOGGER.info("Flyby cancelled");
|
||||
this.state = State.DONE;
|
||||
Tracy.sectionLeave(this.warmupSection);
|
||||
this.warmupSection = 0L;
|
||||
Tracy.sectionLeave(this.legSection);
|
||||
this.legSection = 0L;
|
||||
Tracy.sectionLeave(this.runSection);
|
||||
this.runSection = 0L;
|
||||
FramePacer.endStats();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.gtnewhorizons.angelica.helpers;
|
||||
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_CHAR;
|
||||
|
||||
import net.minecraft.entity.EntityLiving;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
public final class RendererLivingEntityHelper {
|
||||
|
||||
private RendererLivingEntityHelper() {}
|
||||
|
||||
public static String getUpsideDownName(EntityLivingBase entity) {
|
||||
if (entity instanceof EntityLiving living) {
|
||||
return living.hasCustomNameTag() ? entity.getCommandSenderName() : "";
|
||||
}
|
||||
|
||||
if (entity instanceof EntityPlayer player) {
|
||||
return player.getHideCape() ? "" : entity.getCommandSenderName();
|
||||
}
|
||||
|
||||
return entity.getCommandSenderName();
|
||||
}
|
||||
|
||||
public static String stripFormattingCodes(String entityName) {
|
||||
if (entityName == null || entityName.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return entityName.indexOf(FORMATTING_CHAR) < 0
|
||||
? entityName
|
||||
: EnumChatFormatting.getTextWithoutFormattingCodes(entityName);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@ import com.gtnewhorizons.angelica.glsm.hooks.ShaderTransformPostProcessor;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderType;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ShaderWorkSubmitter;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import net.coderbot.iris.Iris;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -38,7 +41,20 @@ public class IrisGLSMBridge {
|
||||
private static Runnable fogDensityListener = null;
|
||||
private static Runnable colorModulatorListener = null;
|
||||
|
||||
private static boolean inputsDeferred = false;
|
||||
|
||||
private static boolean blendDeferred = false;
|
||||
|
||||
private static boolean programRestoreDeferred = false;
|
||||
|
||||
private static final Int2IntOpenHashMap programLastUpdatedFrame = new Int2IntOpenHashMap();
|
||||
|
||||
private static void refreshBlendCondition() {
|
||||
if (Iris.getPipelineManager().getPipelineNullable() instanceof DeferredWorldRenderingPipeline drp) {
|
||||
drp.onVanillaBlendChanged();
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
programLastUpdatedFrame.defaultReturnValue(-1);
|
||||
StateUpdateNotifiers.alphaFuncNotifier = listener -> alphaFuncListener = listener;
|
||||
@@ -72,6 +88,44 @@ public class IrisGLSMBridge {
|
||||
};
|
||||
}
|
||||
|
||||
private static VanillaBooleanLayer gated(VanillaBooleanLayer layer) {
|
||||
return new VanillaBooleanLayer() {
|
||||
@Override
|
||||
public boolean isOverrideHeld() {
|
||||
return Iris.enabled && layer.isOverrideHeld();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getVanilla() {
|
||||
return layer.getVanilla();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVanilla(boolean enabled) {
|
||||
layer.setVanilla(enabled);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static <T> VanillaStateLayer<T> gated(VanillaStateLayer<T> layer) {
|
||||
return new VanillaStateLayer<>() {
|
||||
@Override
|
||||
public boolean isOverrideHeld() {
|
||||
return Iris.enabled && layer.isOverrideHeld();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readVanilla(T into) {
|
||||
layer.readVanilla(into);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeVanilla(T from) {
|
||||
layer.writeVanilla(from);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void register() {
|
||||
GLSMConfig.expandVertexFormats = Iris.enabled;
|
||||
IrisSamplers.initRenderer();
|
||||
@@ -88,6 +142,11 @@ public class IrisGLSMBridge {
|
||||
return Iris.enabled && BlendModeStorage.isBlendLocked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideHeld() {
|
||||
return Iris.enabled && BlendModeStorage.isOverrideHeld();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deferBlendModeToggle(boolean enabled) {
|
||||
BlendModeStorage.deferBlendModeToggle(enabled);
|
||||
@@ -104,6 +163,13 @@ public class IrisGLSMBridge {
|
||||
}
|
||||
};
|
||||
|
||||
GLStateManager.getBlendMode().setVanillaLayer(gated(BlendModeStorage.ENABLE_LAYER));
|
||||
GLStateManager.getBlendState().setVanillaLayer(gated(BlendModeStorage.FUNC_LAYER));
|
||||
GLStateManager.getAlphaTest().setVanillaLayer(gated(AlphaTestStorage.ENABLE_LAYER));
|
||||
GLStateManager.getAlphaState().setVanillaLayer(gated(AlphaTestStorage.FUNC_LAYER));
|
||||
GLStateManager.getDepthState().setVanillaLayer(gated(DepthColorStorage.DEPTH_LAYER));
|
||||
GLStateManager.getColorMask().setVanillaLayer(gated(DepthColorStorage.COLOR_LAYER));
|
||||
|
||||
GLSMHooks.alphaHandler = new DeferredAlphaHandler() {
|
||||
@Override
|
||||
public boolean isAlphaTestLocked() {
|
||||
@@ -127,6 +193,11 @@ public class IrisGLSMBridge {
|
||||
return Iris.enabled && DepthColorStorage.isDepthColorLocked();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverrideHeld() {
|
||||
return Iris.enabled && DepthColorStorage.isOverrideHeld();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deferDepthEnable(boolean enabled) {
|
||||
DepthColorStorage.deferDepthEnable(enabled);
|
||||
@@ -190,9 +261,41 @@ public class IrisGLSMBridge {
|
||||
StateTracker.INSTANCE.lightmapSampler = event.enabled;
|
||||
updatePipeline = true;
|
||||
}
|
||||
if (updatePipeline) {
|
||||
if (!updatePipeline) return;
|
||||
|
||||
if (GLStateManager.isForeignDraw()) {
|
||||
inputsDeferred = true;
|
||||
return;
|
||||
}
|
||||
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setInputs(StateTracker.INSTANCE.getInputs()));
|
||||
});
|
||||
|
||||
GLSMHooks.VANILLA_BLEND_CHANGE.addListener(event -> {
|
||||
if (!Iris.enabled) return;
|
||||
if (GLStateManager.isForeignDraw()) {
|
||||
blendDeferred = true;
|
||||
return;
|
||||
}
|
||||
refreshBlendCondition();
|
||||
});
|
||||
|
||||
GLSMHooks.FOREIGN_DRAW_END.addListener(event -> {
|
||||
if (!Iris.enabled) return;
|
||||
if (inputsDeferred) {
|
||||
inputsDeferred = false;
|
||||
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setInputs(StateTracker.INSTANCE.getInputs()));
|
||||
}
|
||||
if (blendDeferred) {
|
||||
blendDeferred = false;
|
||||
refreshBlendCondition();
|
||||
}
|
||||
if (programRestoreDeferred) {
|
||||
programRestoreDeferred = false;
|
||||
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
|
||||
if (pipeline instanceof DeferredWorldRenderingPipeline drp && drp.shouldOverrideShaders()) {
|
||||
drp.restorePassAfterModProgram(GLStateManager.getActiveProgram());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
GLSMHooks.PROGRAM_CHANGE.addListener(event -> {
|
||||
@@ -208,15 +311,29 @@ public class IrisGLSMBridge {
|
||||
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
|
||||
if (!(pipeline instanceof DeferredWorldRenderingPipeline drp)) return;
|
||||
if (!drp.shouldOverrideShaders()) return;
|
||||
if (drp.getActivePassProgramId() == -1) return;
|
||||
DepthColorStorage.unlockDepthColor();
|
||||
|
||||
if (DepthColorStorage.isOwnedProgram(event.newProgram)) {
|
||||
DepthColorStorage.unlockDepthColor();
|
||||
} else {
|
||||
if (event.newProgram != 0 && !DepthColorStorage.isOwnedProgram(event.newProgram)) {
|
||||
drp.onModProgramOverride();
|
||||
}
|
||||
});
|
||||
|
||||
GLSMHooks.PROGRAM_CHANGE.addListener(event -> {
|
||||
if (!Iris.enabled) return;
|
||||
if (!event.postBind) return;
|
||||
|
||||
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
|
||||
if (!(pipeline instanceof DeferredWorldRenderingPipeline drp)) return;
|
||||
if (!drp.shouldOverrideShaders()) return;
|
||||
|
||||
if (GLStateManager.isForeignDraw()) {
|
||||
programRestoreDeferred = true;
|
||||
return;
|
||||
}
|
||||
|
||||
drp.restorePassAfterModProgram(event.newProgram);
|
||||
});
|
||||
|
||||
GLSMHooks.PROGRAM_CHANGE.addListener(event -> {
|
||||
if (!Iris.enabled) return;
|
||||
if (!event.postBind) return;
|
||||
|
||||
+13
-5
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.loading.fml.transformers;
|
||||
import com.gtnewhorizons.angelica.loading.AngelicaClientTweaker;
|
||||
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
|
||||
import com.gtnewhorizons.angelica.loading.shared.transformers.CeleritasBlockTransform;
|
||||
import com.gtnewhorizons.angelica.loading.shared.transformers.TileEntityMarkerTransform;
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
@@ -12,10 +13,12 @@ import org.objectweb.asm.tree.ClassNode;
|
||||
public class CeleritasBlockTransformer implements IClassTransformer {
|
||||
|
||||
private final CeleritasBlockTransform inner;
|
||||
private final TileEntityMarkerTransform tileEntities;
|
||||
private final String[] exclusions;
|
||||
|
||||
public CeleritasBlockTransformer() {
|
||||
this.inner = new CeleritasBlockTransform(AngelicaClientTweaker.isObfEnv());
|
||||
this.tileEntities = new TileEntityMarkerTransform(AngelicaClientTweaker.isObfEnv());
|
||||
this.exclusions = inner.getTransformerExclusions();
|
||||
}
|
||||
|
||||
@@ -31,14 +34,19 @@ public class CeleritasBlockTransformer implements IClassTransformer {
|
||||
}
|
||||
|
||||
final ClassReader cr = new ClassReader(basicClass);
|
||||
inner.trackBlockSubclasses(cr.getClassName(), cr.getSuperName());
|
||||
final String className = cr.getClassName();
|
||||
inner.trackBlockSubclasses(className, cr.getSuperName());
|
||||
tileEntities.track(className, cr.getSuperName());
|
||||
|
||||
if (!inner.shouldTransform(basicClass)) {
|
||||
return basicClass;
|
||||
final int markers = tileEntities.markersFor(className, basicClass);
|
||||
final byte[] marked = markers == 0 ? basicClass : tileEntities.addMarkers(basicClass, markers);
|
||||
|
||||
if (!inner.shouldTransform(marked)) {
|
||||
return marked;
|
||||
}
|
||||
|
||||
final ClassNode cn = new ClassNode();
|
||||
cr.accept(cn, 0);
|
||||
(markers == 0 ? cr : new ClassReader(marked)).accept(cn, 0);
|
||||
final boolean changed = inner.transformClassNode(transformedName, cn);
|
||||
if (changed) {
|
||||
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
@@ -47,7 +55,7 @@ public class CeleritasBlockTransformer implements IClassTransformer {
|
||||
AngelicaClassDump.dumpClass(transformedName, basicClass, bytes, this);
|
||||
return bytes;
|
||||
}
|
||||
return basicClass;
|
||||
return marked;
|
||||
}
|
||||
|
||||
}
|
||||
+24
-3
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.loading.rfb.transformers;
|
||||
|
||||
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
|
||||
import com.gtnewhorizons.angelica.loading.shared.transformers.CeleritasBlockTransform;
|
||||
import com.gtnewhorizons.angelica.loading.shared.transformers.TileEntityMarkerTransform;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassHeaderMetadata;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
|
||||
@@ -9,15 +10,18 @@ import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
|
||||
import org.intellij.lang.annotations.Pattern;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public class RFBCeleritasBlockTransformer implements RfbClassTransformer {
|
||||
|
||||
private final CeleritasBlockTransform inner;
|
||||
private final TileEntityMarkerTransform tileEntities;
|
||||
|
||||
public RFBCeleritasBlockTransformer(boolean isObf) {
|
||||
inner = new CeleritasBlockTransform(isObf);
|
||||
tileEntities = new TileEntityMarkerTransform(isObf);
|
||||
}
|
||||
|
||||
@Pattern("[a-z0-9-]+")
|
||||
@@ -54,15 +58,32 @@ public class RFBCeleritasBlockTransformer implements RfbClassTransformer {
|
||||
return false;
|
||||
}
|
||||
|
||||
inner.trackBlockSubclasses(metadata.binaryThisName(), metadata.binarySuperName());
|
||||
return inner.shouldTransform(classNode.getOriginalBytes());
|
||||
final String thisName = metadata.binaryThisName();
|
||||
final String superName = metadata.binarySuperName();
|
||||
final byte[] originalBytes = classNode.getOriginalBytes();
|
||||
|
||||
inner.trackBlockSubclasses(thisName, superName);
|
||||
if (context == RfbClassTransformer.Context.LCL_WITH_TRANSFORMS) {
|
||||
tileEntities.track(thisName, superName);
|
||||
if (tileEntities.markersFor(thisName, originalBytes) != 0) return true;
|
||||
}
|
||||
return inner.shouldTransform(originalBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transformClassIfNeeded(@NotNull ExtensibleClassLoader classLoader,
|
||||
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest,
|
||||
@NotNull String className, @NotNull ClassNodeHandle classNode) {
|
||||
final boolean changed = inner.transformClassNode(className, classNode.getNode());
|
||||
final ClassNode node = classNode.getNode();
|
||||
boolean changed = false;
|
||||
if (context == RfbClassTransformer.Context.LCL_WITH_TRANSFORMS) {
|
||||
final int markers = tileEntities.markersFor(node.name, classNode.getOriginalBytes());
|
||||
if (markers != 0) {
|
||||
tileEntities.addMarkers(node, markers);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
changed |= inner.transformClassNode(className, node);
|
||||
if (changed) {
|
||||
classNode.computeMaxs();
|
||||
AngelicaClassDump.dumpRFBClass(className, classNode, this);
|
||||
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package com.gtnewhorizons.angelica.loading.shared.transformers;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Marks TileEntity subclasses that declare getDescriptionPacket, shouldRenderInPass or getDistanceFrom with a
|
||||
* corresponding interface, so runtime code can test with instanceof instead of reflection.
|
||||
*/
|
||||
public final class TileEntityMarkerTransform {
|
||||
|
||||
public static final int MARK_DESCRIPTION_PACKET = 1 << 0;
|
||||
public static final int MARK_SHOULD_RENDER_IN_PASS = 1 << 1;
|
||||
public static final int MARK_GET_DISTANCE_FROM = 1 << 2;
|
||||
|
||||
private static final String SENDS_DESCRIPTION_PACKET = "com/gtnewhorizons/angelica/mixins/interfaces/SendsDescriptionPacket";
|
||||
private static final String OVERRIDES_SHOULD_RENDER_IN_PASS = "com/gtnewhorizons/angelica/mixins/interfaces/OverridesShouldRenderInPass";
|
||||
private static final String OVERRIDES_GET_DISTANCE_FROM = "com/gtnewhorizons/angelica/mixins/interfaces/OverridesGetDistanceFrom";
|
||||
|
||||
private static final String TILE_ENTITY = "net/minecraft/tileentity/TileEntity";
|
||||
private static final int SCAN_FLAGS = ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
|
||||
|
||||
private record Marker(int bit, String method, String desc, String iface) {}
|
||||
|
||||
private final Marker[] markers;
|
||||
private final ClassConstantPoolParser prefilter;
|
||||
private final Set<String> tileEntities = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
public TileEntityMarkerTransform(boolean isObf) {
|
||||
this.markers = new Marker[] {
|
||||
new Marker(MARK_DESCRIPTION_PACKET, isObf ? "func_145844_m" : "getDescriptionPacket", "()Lnet/minecraft/network/Packet;", SENDS_DESCRIPTION_PACKET),
|
||||
new Marker(MARK_SHOULD_RENDER_IN_PASS, "shouldRenderInPass", "(I)Z", OVERRIDES_SHOULD_RENDER_IN_PASS),
|
||||
new Marker(MARK_GET_DISTANCE_FROM, isObf ? "func_145835_a" : "getDistanceFrom", "(DDD)D", OVERRIDES_GET_DISTANCE_FROM) };
|
||||
|
||||
final String[] methods = new String[markers.length];
|
||||
for (int i = 0; i < markers.length; i++) methods[i] = markers[i].method();
|
||||
this.prefilter = new ClassConstantPoolParser(methods);
|
||||
|
||||
}
|
||||
|
||||
private boolean isTileEntity(String className) {
|
||||
return className != null && (className.startsWith(TILE_ENTITY) || tileEntities.contains(className));
|
||||
}
|
||||
|
||||
public void track(String className, String superClassName) {
|
||||
if (className != null && isTileEntity(superClassName)) tileEntities.add(className);
|
||||
}
|
||||
|
||||
public int markersFor(String className, byte[] classBytes) {
|
||||
if (TILE_ENTITY.equals(className) || !isTileEntity(className) || !prefilter.find(classBytes)) return 0;
|
||||
|
||||
final MarkerScanner scanner = new MarkerScanner();
|
||||
new ClassReader(classBytes).accept(scanner, SCAN_FLAGS);
|
||||
return scanner.declared & ~scanner.alreadyPresent;
|
||||
}
|
||||
|
||||
public byte[] addMarkers(byte[] classBytes, int markers) {
|
||||
final ClassReader cr = new ClassReader(classBytes);
|
||||
final ClassWriter cw = new ClassWriter(cr, 0);
|
||||
cr.accept(new ClassVisitor(Opcodes.ASM5, cw) {
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String sig, String superName, String[] interfaces) {
|
||||
final String[] added = interfacesFor(markers);
|
||||
if (interfaces == null) {
|
||||
super.visit(version, access, name, sig, superName, added);
|
||||
return;
|
||||
}
|
||||
final String[] merged = Arrays.copyOf(interfaces, interfaces.length + added.length);
|
||||
System.arraycopy(added, 0, merged, interfaces.length, added.length);
|
||||
super.visit(version, access, name, sig, superName, merged);
|
||||
}
|
||||
}, 0);
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
public void addMarkers(ClassNode cn, int markers) {
|
||||
Collections.addAll(cn.interfaces, interfacesFor(markers));
|
||||
}
|
||||
|
||||
private int bitOf(int access, String name, String desc) {
|
||||
if ((access & Opcodes.ACC_PUBLIC) == 0 || (access & Opcodes.ACC_STATIC) != 0) return 0;
|
||||
for (Marker m : markers) if (m.method().equals(name) && m.desc().equals(desc)) return m.bit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int bitOf(String interfaceName) {
|
||||
for (Marker m : markers) if (m.iface().equals(interfaceName)) return m.bit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
private String[] interfacesFor(int wanted) {
|
||||
final String[] names = new String[Integer.bitCount(wanted)];
|
||||
int next = 0;
|
||||
for (Marker m : markers) if ((wanted & m.bit()) != 0) names[next++] = m.iface();
|
||||
return names;
|
||||
}
|
||||
|
||||
private final class MarkerScanner extends ClassVisitor {
|
||||
|
||||
private int declared;
|
||||
private int alreadyPresent;
|
||||
|
||||
MarkerScanner() {
|
||||
super(Opcodes.ASM5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String sig, String superName, String[] interfaces) {
|
||||
if (interfaces == null) return;
|
||||
for (String iface : interfaces) alreadyPresent |= bitOf(iface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] exceptions) {
|
||||
declared |= bitOf(access, name, desc);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,12 @@ public enum Mixins implements IMixins {
|
||||
)
|
||||
),
|
||||
|
||||
STARMINER_RENDERER_LIVING_ENTITY_OPTIMIZATION(new MixinBuilder()
|
||||
.setPhase(Phase.LATE)
|
||||
.addRequiredMod(TargetedMod.STARMINER)
|
||||
.addClientMixins("client.starminer.MixinTransformClientHelper")
|
||||
),
|
||||
|
||||
ANGELICA_ENTITY_OVERLAYS(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> AngelicaConfig.entityOverlayFixes)
|
||||
@@ -377,6 +383,11 @@ public enum Mixins implements IMixins {
|
||||
, "celeritas.biome_blending.MixinBlockLiquid"
|
||||
, "celeritas.threading.MixinForgeHooksClient"
|
||||
, "celeritas.terrain.MixinChunk"
|
||||
, "celeritas.terrain.MixinWorldClient_WorkerAccess"
|
||||
, "celeritas.terrain.MixinWorld_WorkerMutationGuard"
|
||||
, "celeritas.terrain.MixinTileEntity_AwaitingDescriptor"
|
||||
, "celeritas.terrain.MixinNetHandlerPlayClient_DescriptorRepair"
|
||||
, "celeritas.terrain.MixinWorld_AwaitingDescriptor"
|
||||
, "celeritas.terrain.MixinRenderRegion"
|
||||
, "celeritas.terrain.MixinSectionRenderDataStorage"
|
||||
, "celeritas.terrain.MixinDefaultChunkRenderer"
|
||||
@@ -406,11 +417,13 @@ public enum Mixins implements IMixins {
|
||||
.addClientMixins(
|
||||
"shaders.MixinDroppedItemGlintEdges"
|
||||
, "shaders.MixinHeldItemGlintEdges"
|
||||
, "shaders.MixinEffectRenderer_Particles"
|
||||
, "shaders.MixinEntityPickupFX"
|
||||
, "shaders.MixinEntityRenderer"
|
||||
, "shaders.MixinGuiIngameForge"
|
||||
, "shaders.MixinFramebuffer"
|
||||
, "shaders.MixinItem"
|
||||
, "shaders.MixinItemRenderer_ItemId"
|
||||
, "shaders.MixinLocale"
|
||||
, "shaders.MixinRender"
|
||||
, "shaders.MixinRenderBiped"
|
||||
@@ -733,6 +746,24 @@ public enum Mixins implements IMixins {
|
||||
.setApplyIf(() -> AngelicaConfig.enableIris)
|
||||
.addClientMixins("client.etfuturum.MixinLayerBetterElytra")
|
||||
),
|
||||
ET_FUTURUM_BEACON_BEAM(new MixinBuilder("Iris Et Futurum Beacon Beam")
|
||||
.setPhase(Phase.LATE)
|
||||
.addRequiredMod(TargetedMod.ET_FUTURUM_REQUIEM)
|
||||
.setApplyIf(() -> AngelicaConfig.enableIris)
|
||||
.addClientMixins("client.etfuturum.MixinTileEntityNewBeaconRenderer")
|
||||
),
|
||||
OPENBLOCKS_TROPHY_ENTITY_GBUFFER(new MixinBuilder("Render OpenBlocks trophies with the entity gbuffer programs")
|
||||
.setPhase(Phase.LATE)
|
||||
.addRequiredMod(TargetedMod.OPENBLOCKS)
|
||||
.setApplyIf(() -> AngelicaConfig.enableIris)
|
||||
.addClientMixins("client.openblocks.MixinTileEntityTrophyRenderer")
|
||||
),
|
||||
DRAGONAPI_PARTICLE_SPLIT(new MixinBuilder("Split DragonAPI's replacement particle renderer between the particle gbuffer programs")
|
||||
.setPhase(Phase.LATE)
|
||||
.addRequiredMod(TargetedMod.DRAGON_API)
|
||||
.setApplyIf(() -> AngelicaConfig.enableIris)
|
||||
.addClientMixins("client.dragonapi.MixinThrottleableEffectRenderer")
|
||||
),
|
||||
MCPATCHER_FORGE(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> AngelicaConfig.enableMCPatcherForgeFeatures)
|
||||
|
||||
@@ -23,7 +23,9 @@ public enum TargetedMod implements ITargetMod {
|
||||
NTM_SPACE("com.hbm.dim.SolarSystem"),
|
||||
IC2("ic2.core.coremod.IC2core", "IC2"),
|
||||
NATURA(null, "Natura"),
|
||||
OPENBLOCKS(null, "OpenBlocks"),
|
||||
SECURITYCRAFT(null, "securitycraft"),
|
||||
STARMINER("jp.mc.ancientred.starminer.core.SMPlugin", "modJ_StarMiner"),
|
||||
SUPERNOVA("com.mitchej123.supernova.core.SupernovaCore", "supernova"),
|
||||
THAUMCRAFT(null, "Thaumcraft"),
|
||||
TINKERS_CONSTRUCT(null, "TConstruct"),
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
public interface AwaitingDescriptorTE {
|
||||
|
||||
boolean angelica$isAwaitingDescriptor();
|
||||
|
||||
void angelica$setAwaitingDescriptor(boolean awaiting);
|
||||
|
||||
int angelica$getDescriptorWaitTicks();
|
||||
|
||||
void angelica$setDescriptorWaitTicks(int ticks);
|
||||
}
|
||||
-2
@@ -6,6 +6,4 @@ public interface ITileEntityBoundingBoxCache {
|
||||
AxisAlignedBB angelica$getCachedRenderBoundingBox();
|
||||
|
||||
byte angelica$boundsClass();
|
||||
|
||||
byte angelica$passClass();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
public interface ItemRendererAccessor {
|
||||
|
||||
ItemStack angelica$getItemToRender();
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
public interface OverridesGetDistanceFrom {}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
public interface OverridesShouldRenderInPass {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
public interface SendsDescriptionPacket {}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.profiling;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import net.minecraft.client.gui.GuiMainMenu;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.gui.GuiIngameMenu;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class TracyUiSections {
|
||||
|
||||
private static final Map<Class<?>, String> LABELS = new HashMap<>();
|
||||
private static Class<?> current = TracyUiSections.class;
|
||||
private static long section;
|
||||
|
||||
private TracyUiSections() {}
|
||||
|
||||
public static void poll(GuiScreen screen) {
|
||||
if (!Tracy.ENABLED) return;
|
||||
final Class<?> screenClass = screen == null ? null : screen.getClass();
|
||||
if (screenClass == current) return;
|
||||
current = screenClass;
|
||||
Tracy.sectionLeave(section);
|
||||
section = Tracy.sectionEnter(Tracy.SECTION_UI, label(screenClass));
|
||||
}
|
||||
|
||||
private static String label(Class<?> screenClass) {
|
||||
if (screenClass == null) return "in world";
|
||||
String label = LABELS.get(screenClass);
|
||||
if (label == null) {
|
||||
if (screenClass == GuiMainMenu.class) label = "main menu";
|
||||
else if (screenClass == GuiIngameMenu.class) label = "pause";
|
||||
else label = screenClass.getSimpleName();
|
||||
LABELS.put(screenClass, label);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,7 @@ 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.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
|
||||
import com.gtnewhorizons.angelica.iris.IrisGLSMBridge;
|
||||
import com.gtnewhorizons.angelica.loading.AngelicaClientTweaker;
|
||||
@@ -216,9 +217,15 @@ public final class ClientProxy extends CommonProxy {
|
||||
}
|
||||
}
|
||||
|
||||
private long worldSection;
|
||||
|
||||
@SubscribeEvent
|
||||
public void worldLoad(WorldEvent.Load event) {
|
||||
if (!event.world.isRemote) return;
|
||||
if (Tracy.ENABLED) {
|
||||
Tracy.sectionLeave(this.worldSection);
|
||||
this.worldSection = Tracy.sectionEnter(Tracy.SECTION_WORLD, "world " + event.world.provider.getDimensionName() + " (dim " + event.world.provider.dimensionId + ")");
|
||||
}
|
||||
if (GLStateManager.isRunningSplash()) {
|
||||
GLStateManager.setRunningSplash(false);
|
||||
LOGGER.info("World loaded - Enabling GLSM Cache");
|
||||
@@ -238,6 +245,8 @@ public final class ClientProxy extends CommonProxy {
|
||||
@SubscribeEvent
|
||||
public void onWorldUnload(WorldEvent.Unload event) {
|
||||
if (!event.world.isRemote) return;
|
||||
Tracy.sectionLeave(this.worldSection);
|
||||
this.worldSection = 0L;
|
||||
DynamicLights.get().removeAllLightSources();
|
||||
GpuCulling.onWorldUnload();
|
||||
}
|
||||
|
||||
@@ -73,6 +73,9 @@ public final class SelectionBoxRenderer {
|
||||
public static void draw(AxisAlignedBB aabb, int color) {
|
||||
if (program == null || vao == null) return;
|
||||
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
|
||||
final float minX = (float) aabb.minX, minY = (float) aabb.minY, minZ = (float) aabb.minZ;
|
||||
final float extX = (float) (aabb.maxX - aabb.minX);
|
||||
final float extY = (float) (aabb.maxY - aabb.minY);
|
||||
|
||||
@@ -123,11 +123,11 @@ public class DeferredEntityOverlay {
|
||||
GbufferPrograms.beginEntities();
|
||||
try {
|
||||
for (DeferredEntry entry : deferred) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntity(EntityIdHelper.getEntityId(entry.entity));
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(EntityIdHelper.getEntityId(entry.entity), 0);
|
||||
renderOverlay(entry);
|
||||
}
|
||||
} finally {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntity(-1);
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(-1, 0);
|
||||
GbufferPrograms.endEntities();
|
||||
}
|
||||
recycle();
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.TracyBackend;
|
||||
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
@@ -25,16 +26,21 @@ public final class FramePacer {
|
||||
private static final Tracy.ZoneId Z_PACER_IDLE = Tracy.zoneId("pacerIdleWork", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_PACER_SLEEP = Tracy.zoneId("pacerSleep", Tracy.COLOR_SWAP);
|
||||
private static final long P_FRAME_GATE_US = Tracy.plotHandle("frameGateUs");
|
||||
private static final long P_SLACK_US = Tracy.plotHandle("pacer.slackUs");
|
||||
private static final long P_SPIN_US = Tracy.plotHandle("pacer.spinUs");
|
||||
private static final long P_CEILING_HZ = Tracy.plotHandle("pacer.ceilingHz");
|
||||
private static final long P_GATE_ACTIVE = Tracy.plotHandle("pacer.gateActive");
|
||||
|
||||
private static long deadline;
|
||||
private static long lastFrameTime;
|
||||
|
||||
private static boolean warnedUncapped;
|
||||
private static boolean pacedLastFrame;
|
||||
private static boolean statsActive;
|
||||
|
||||
private static VSyncMode lastVSyncMode = VSyncMode.ON;
|
||||
private static int lastCapHz;
|
||||
private static int lastRefreshHz;
|
||||
private static int lastCeilingHz;
|
||||
|
||||
private static IdleWork idleWork;
|
||||
private static PacerSleeper sleeper = newSleeper();
|
||||
@@ -47,7 +53,6 @@ public final class FramePacer {
|
||||
private static int loggedRefreshHz;
|
||||
private static int loggedCapHz;
|
||||
|
||||
private static boolean gateBlocking;
|
||||
private static long lastSeenGateEnd;
|
||||
|
||||
private FramePacer() {}
|
||||
@@ -60,6 +65,35 @@ public final class FramePacer {
|
||||
idleWork = work;
|
||||
}
|
||||
|
||||
public static boolean pacedLastFrame() {
|
||||
return pacedLastFrame;
|
||||
}
|
||||
|
||||
public static void beginStats() {
|
||||
sleeper.resetStats();
|
||||
statsActive = true;
|
||||
}
|
||||
|
||||
public static String endStats() {
|
||||
statsActive = false;
|
||||
final String summary = sleeper.summary(configLine());
|
||||
Tracy.message(summary);
|
||||
return summary;
|
||||
}
|
||||
|
||||
static boolean statsActive() {
|
||||
return statsActive;
|
||||
}
|
||||
|
||||
static String configLine(VSyncMode mode, int refreshHz, int capHz, int ceilingHz) {
|
||||
return "cfg=[" + mode + " refresh=" + refreshHz + "Hz cap=" + capHz + " ceiling=" + ceilingHz + "Hz]";
|
||||
}
|
||||
|
||||
private static String configLine() {
|
||||
return configLine(lastVSyncMode, lastRefreshHz, lastCapHz,
|
||||
pacingCeilingHz(lastVSyncMode, lastCapHz, lastRefreshHz, gateActive()));
|
||||
}
|
||||
|
||||
public static int plausibleRefreshHz(int hz) {
|
||||
return hz >= MIN_PLAUSIBLE_REFRESH_HZ && hz <= MAX_PLAUSIBLE_REFRESH_HZ ? hz : 0;
|
||||
}
|
||||
@@ -155,11 +189,6 @@ public final class FramePacer {
|
||||
}
|
||||
|
||||
public static long pace(int ceilingHz, Runnable renderAheadWait) {
|
||||
if (ceilingHz != lastCeilingHz) {
|
||||
lastCeilingHz = ceilingHz;
|
||||
deadline = 0L;
|
||||
}
|
||||
|
||||
long now = System.nanoTime();
|
||||
long phaseEnd;
|
||||
|
||||
@@ -167,6 +196,9 @@ public final class FramePacer {
|
||||
final long thresholdNanos = gateBlockThresholdNanos(targetNanos);
|
||||
long frameGateNanos = 0L;
|
||||
|
||||
pacedLastFrame = ceilingHz > 0;
|
||||
Tracy.plotInt(P_CEILING_HZ, ceilingHz);
|
||||
|
||||
if (ceilingHz > 0) {
|
||||
frameGateNanos = GLStateManager.lastFrameGateNanos();
|
||||
final long gateEnd = GLStateManager.lastFrameGateEndNanos();
|
||||
@@ -192,8 +224,11 @@ public final class FramePacer {
|
||||
now = phaseEnd;
|
||||
}
|
||||
|
||||
if (now < deadline) {
|
||||
final long preSleep = now;
|
||||
final long slackNanos = deadline - now;
|
||||
Tracy.plotInt(P_SLACK_US, slackNanos / 1000);
|
||||
|
||||
final boolean slept = now < deadline;
|
||||
if (slept) {
|
||||
Tracy.beginZone(Z_PACER_SLEEP);
|
||||
try {
|
||||
now = sleeper.sleepUntil(deadline, now);
|
||||
@@ -201,6 +236,10 @@ public final class FramePacer {
|
||||
Tracy.endZone();
|
||||
}
|
||||
}
|
||||
if (Tracy.ENABLED || statsActive) {
|
||||
sleeper.noteFrame(slackNanos, slept ? now - deadline : 0L, slept);
|
||||
Tracy.plotInt(P_SPIN_US, sleeper.lastSpinNanos / 1000);
|
||||
}
|
||||
} else {
|
||||
deadline = 0L;
|
||||
}
|
||||
@@ -215,15 +254,18 @@ public final class FramePacer {
|
||||
final long waitNanos = phaseEnd - now;
|
||||
now = phaseEnd;
|
||||
if (ceilingHz > 0 && waitNanos > thresholdNanos) deadline = now;
|
||||
gateBlocking = observeGate(ceilingHz > 0 ? Math.max(frameGateNanos, waitNanos) : 0L, thresholdNanos);
|
||||
final boolean gateBlocking = observeGate(ceilingHz > 0 ? Math.max(frameGateNanos, waitNanos) : 0L, thresholdNanos);
|
||||
Tracy.plotInt(P_GATE_ACTIVE, gateBlocking ? 1 : 0);
|
||||
|
||||
GLStateManager.pumpDisplayMessages();
|
||||
|
||||
if (pacingStatusChanged(gateBlocking, lastVSyncMode, lastRefreshHz, lastCapHz)) {
|
||||
final boolean blocking = settledBlocking;
|
||||
final String line = pacingLine(lastVSyncMode, lastRefreshHz, lastCapHz, blocking);
|
||||
if (vsyncNotHonoured(lastVSyncMode, blocking)) AngelicaMod.LOGGER.warn(line);
|
||||
final boolean notHonoured = vsyncNotHonoured(lastVSyncMode, blocking);
|
||||
if (notHonoured) AngelicaMod.LOGGER.warn(line);
|
||||
else AngelicaMod.LOGGER.info(line);
|
||||
Tracy.message(line, notHonoured ? TracyBackend.SEVERITY_WARNING : TracyBackend.SEVERITY_INFO);
|
||||
}
|
||||
|
||||
final long period = lastFrameTime == 0L ? 0L : now - lastFrameTime;
|
||||
|
||||
@@ -5,31 +5,50 @@ import java.util.function.LongSupplier;
|
||||
|
||||
final class PacerSleeper {
|
||||
|
||||
private static final long SPIN_FLOOR_NANOS = 100_000L;
|
||||
private static final long MIN_PARK_NANOS = 1_000_000L;
|
||||
private static final long MAX_OVERSHOOT_NANOS = 4_000_000L;
|
||||
private static final int SLOTS = 10;
|
||||
static final int SLOTS = 10;
|
||||
|
||||
static final long MIN_SPIN_FLOOR_NANOS = 20_000L;
|
||||
private static final long MAX_SPIN_FLOOR_NANOS = 500_000L;
|
||||
|
||||
private final LongSupplier clock;
|
||||
private final LongConsumer parker;
|
||||
private final boolean spinWait;
|
||||
private final long[] overshoots = new long[SLOTS];
|
||||
private long overshootSum;
|
||||
private long spinFloorNanos = MIN_SPIN_FLOOR_NANOS;
|
||||
private int offset;
|
||||
|
||||
long lastSpinNanos;
|
||||
int lastParks;
|
||||
|
||||
private long frames;
|
||||
private long slackSum;
|
||||
private long parkTotal;
|
||||
private long lateFrames;
|
||||
private long lateMax;
|
||||
|
||||
PacerSleeper(LongSupplier clock, LongConsumer parker, boolean spinWait) {
|
||||
this.clock = clock;
|
||||
this.parker = parker;
|
||||
this.spinWait = spinWait;
|
||||
}
|
||||
|
||||
long spinFloorNanos() {
|
||||
return spinFloorNanos;
|
||||
}
|
||||
|
||||
long sleepUntil(long deadlineNanos, long now) {
|
||||
resetLastFrame();
|
||||
|
||||
long spinFloor = spinFloorNanos;
|
||||
while (true) {
|
||||
final long remaining = deadlineNanos - now;
|
||||
if (remaining <= SPIN_FLOOR_NANOS) break;
|
||||
if (remaining <= spinFloor) break;
|
||||
if (Thread.currentThread().isInterrupted()) return clock.getAsLong();
|
||||
|
||||
long request = remaining - avgOvershootNanos() - SPIN_FLOOR_NANOS;
|
||||
long request = remaining - avgOvershootNanos() - spinFloor;
|
||||
if (request <= 0L) {
|
||||
if (remaining <= MIN_PARK_NANOS) break;
|
||||
request = MIN_PARK_NANOS;
|
||||
@@ -39,17 +58,51 @@ final class PacerSleeper {
|
||||
parker.accept(request);
|
||||
now = clock.getAsLong();
|
||||
record(Math.min(Math.max(0L, now - before - request), MAX_OVERSHOOT_NANOS));
|
||||
spinFloor = spinFloorNanos;
|
||||
lastParks++;
|
||||
}
|
||||
|
||||
if (spinWait) {
|
||||
final long spinStart = now;
|
||||
while (now < deadlineNanos) {
|
||||
Thread.yield();
|
||||
if (deadlineNanos - now > spinFloor) Thread.yield();
|
||||
else Thread.onSpinWait();
|
||||
now = clock.getAsLong();
|
||||
}
|
||||
lastSpinNanos = now - spinStart;
|
||||
}
|
||||
return now;
|
||||
}
|
||||
|
||||
void noteFrame(long slackNanos, long wakeLateNanos, boolean slept) {
|
||||
if (!slept) resetLastFrame();
|
||||
|
||||
frames++;
|
||||
slackSum += slackNanos;
|
||||
parkTotal += lastParks;
|
||||
if (slackNanos < 0L && -slackNanos > lateMax) lateMax = -slackNanos;
|
||||
if (slackNanos < 0L || wakeLateNanos > 0L) lateFrames++;
|
||||
}
|
||||
|
||||
private void resetLastFrame() {
|
||||
lastSpinNanos = 0L;
|
||||
lastParks = 0;
|
||||
}
|
||||
|
||||
void resetStats() {
|
||||
frames = 0L;
|
||||
slackSum = 0L;
|
||||
parkTotal = 0L;
|
||||
lateFrames = 0L;
|
||||
lateMax = 0L;
|
||||
}
|
||||
|
||||
String summary(String config) {
|
||||
if (frames == 0L) return "pacer: " + config + " no paced frames";
|
||||
return "pacer: " + config + " frames=" + frames + " parks=" + parkTotal + " slackUs avg=" + slackSum / frames / 1000L + " floorUs=" + spinFloorNanos / 1000L
|
||||
+ " overshootUs est=" + avgOvershootNanos() / 1000L + " lateFrames=" + lateFrames + " lateMaxUs=" + lateMax / 1000L;
|
||||
}
|
||||
|
||||
long avgOvershootNanos() {
|
||||
return overshootSum / SLOTS;
|
||||
}
|
||||
@@ -58,5 +111,12 @@ final class PacerSleeper {
|
||||
overshootSum += nanos - overshoots[offset];
|
||||
overshoots[offset] = nanos;
|
||||
offset = (offset + 1) % SLOTS;
|
||||
|
||||
long max = 0L;
|
||||
for (final long o : overshoots) {
|
||||
if (o > max) max = o;
|
||||
}
|
||||
final long jitter = max - avgOvershootNanos();
|
||||
spinFloorNanos = Math.max(MIN_SPIN_FLOOR_NANOS, Math.min(MAX_SPIN_FLOOR_NANOS, jitter));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
import com.gtnewhorizons.angelica.client.rendering.DeferredDrawBatcher;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.minecraft.client.particle.EntityCloudFX;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.particle.EntityFireworkOverlayFX;
|
||||
import net.minecraft.client.particle.EntityFireworkSparkFX;
|
||||
import net.minecraft.client.particle.EntityFireworkStarterFX;
|
||||
import net.minecraft.client.particle.EntitySpellParticleFX;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevel;
|
||||
|
||||
/**
|
||||
* Splits a particle run between {@code gbuffers_particles} and {@code gbuffers_particles_translucent}.
|
||||
*/
|
||||
public final class ParticleRunSplitter {
|
||||
|
||||
private static boolean runTranslucent;
|
||||
|
||||
private ParticleRunSplitter() {}
|
||||
|
||||
public static void beginRun() {
|
||||
runTranslucent = false;
|
||||
GbufferPrograms.setTranslucencyDeclaration(Boolean.FALSE);
|
||||
}
|
||||
|
||||
public static void splitIfNeeded(EntityFX particle, Tessellator tessellator, float partialTicks) {
|
||||
final boolean translucent = isTranslucent(particle);
|
||||
if (translucent == runTranslucent) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean batcherWasActive = DeferredDrawBatcher.isActive();
|
||||
if (batcherWasActive) {
|
||||
DeferredDrawBatcher.exitAndFlush();
|
||||
}
|
||||
tessellator.draw();
|
||||
|
||||
runTranslucent = translucent;
|
||||
GbufferPrograms.setTranslucencyDeclaration(translucent);
|
||||
|
||||
tessellator.startDrawingQuads();
|
||||
tessellator.setBrightness(particle.getBrightnessForRender(partialTicks));
|
||||
if (batcherWasActive) {
|
||||
DeferredDrawBatcher.enter();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isTranslucent(EntityFX particle) {
|
||||
if (particle.particleIcon instanceof TextureAtlasSprite sprite) {
|
||||
return ((SpriteExtension) sprite).celeritas$getTransparencyLevel() == SpriteTransparencyLevel.TRANSLUCENT;
|
||||
}
|
||||
|
||||
return particle instanceof EntityFireworkSparkFX
|
||||
|| particle instanceof EntityFireworkOverlayFX
|
||||
|| particle instanceof EntityFireworkStarterFX
|
||||
|| particle instanceof EntitySpellParticleFX
|
||||
|| particle instanceof EntityCloudFX;
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,7 @@ public final class PlayerReflectionCapture {
|
||||
private static final Matrix4f pendingModelView = new Matrix4f();
|
||||
private static int pendingSkin;
|
||||
private static int pendingEntityId;
|
||||
private static int pendingItemId;
|
||||
|
||||
private static int vbo;
|
||||
private static int vao;
|
||||
@@ -134,6 +135,7 @@ public final class PlayerReflectionCapture {
|
||||
pendingModelView.set(GLStateManager.getModelViewMatrix());
|
||||
pendingSkin = GLStateManager.getBoundTextureForServerState(0);
|
||||
pendingEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity();
|
||||
pendingItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem();
|
||||
pending = true;
|
||||
}
|
||||
|
||||
@@ -328,9 +330,10 @@ public final class PlayerReflectionCapture {
|
||||
final boolean prevDepthWrite = GLStateManager.getDepthState().isEnabled();
|
||||
|
||||
final int prevEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity();
|
||||
final int prevItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem();
|
||||
savedModelView.set(GLStateManager.getModelViewMatrix());
|
||||
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntity(pendingEntityId);
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(pendingEntityId, pendingItemId);
|
||||
GLStateManager.setModelViewMatrix(pendingModelView);
|
||||
if (maskable) {
|
||||
GLStateManager.glColorMask(false, false, false, false);
|
||||
@@ -344,7 +347,7 @@ public final class PlayerReflectionCapture {
|
||||
GLStateManager.glColorMask(pr, pg, pb, pa);
|
||||
}
|
||||
GLStateManager.setModelViewMatrix(savedModelView);
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntity(prevEntityId);
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(prevEntityId, prevItemId);
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, prevTexture);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
|
||||
public class RenderThreadContext {
|
||||
|
||||
@@ -15,10 +15,13 @@ public class RenderThreadContext {
|
||||
currentWorldSlice.remove();
|
||||
}
|
||||
|
||||
public static TileEntity getSnapshotTE(int x, int y, int z) {
|
||||
final WorldSlice slice = currentWorldSlice.get();
|
||||
if (slice == null) return null;
|
||||
return slice.getTileEntity(x, y, z);
|
||||
public static WorldSlice get() {
|
||||
return currentWorldSlice.get();
|
||||
}
|
||||
|
||||
public static WorldSlice workerSlice() {
|
||||
if (TessellatorManager.isOnMainThread()) return null;
|
||||
return currentWorldSlice.get();
|
||||
}
|
||||
|
||||
public static boolean hasWorldSlice() {
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
/// Despite not in the `api` package, this interface is used by GT5uNH and GTNHLib, possibly others. Be careful what you
|
||||
/// change!
|
||||
@SuppressWarnings("unused")
|
||||
public interface StateAwareTessellator {
|
||||
/**
|
||||
* True if the vertex originated from a RenderBlocks method call with the enableAO flag set.
|
||||
*/
|
||||
|
||||
/// True if the vertex originated from a RenderBlocks method call with the enableAO flag set.
|
||||
int RENDERED_WITH_VANILLA_AO = 0x1;
|
||||
int NO_DIRECTIONAL_SHADING = 0x2;
|
||||
|
||||
void angelica$setAppliedAo(boolean flag);
|
||||
void angelica$setNoDirectionalShading(boolean flag);
|
||||
|
||||
/**
|
||||
* Sets whether we're doing terrain meshing as part of celeritas -- collects additional information.
|
||||
*
|
||||
* Enables per-vertex AO state collection into {@link #angelica$getVertexStates()}.
|
||||
*/
|
||||
/// Sets whether we're doing terrain meshing as part of celeritas -- collects additional information.
|
||||
///
|
||||
/// Enables per-vertex AO state collection into [#angelica$getVertexStates()].
|
||||
void angelica$setCeleritasMeshing(boolean active);
|
||||
|
||||
int[] angelica$getVertexStates();
|
||||
|
||||
-82
@@ -1,7 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ByteOpenHashMap;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
|
||||
@@ -17,25 +16,12 @@ public final class TileEntityRenderBoundsRegistry {
|
||||
public static final byte INFINITE = 2;
|
||||
public static final byte DYNAMIC = 3;
|
||||
|
||||
public static final byte PASS0_ONLY = 1;
|
||||
public static final byte PASS_OVERRIDES = 2;
|
||||
|
||||
private static final byte NOT_OVERRIDDEN = 1;
|
||||
private static final byte OVERRIDDEN = 2;
|
||||
|
||||
private static final Reference2ByteOpenHashMap<Class<? extends TileEntity>> classRegistry = new Reference2ByteOpenHashMap<>();
|
||||
private static final Reference2ByteOpenHashMap<Class<? extends TileEntity>> passClassRegistry = new Reference2ByteOpenHashMap<>();
|
||||
private static final Reference2ByteOpenHashMap<Class<? extends TileEntity>> distanceOverrideRegistry = new Reference2ByteOpenHashMap<>();
|
||||
|
||||
private static final Set<String> dynamicClassNames = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private static final String[] SHOULD_RENDER_IN_PASS_NAMES = { "shouldRenderInPass" };
|
||||
private static String[] distanceFromNames;
|
||||
|
||||
static {
|
||||
classRegistry.defaultReturnValue(UNKNOWN);
|
||||
passClassRegistry.defaultReturnValue(UNKNOWN);
|
||||
distanceOverrideRegistry.defaultReturnValue(UNKNOWN);
|
||||
}
|
||||
|
||||
private TileEntityRenderBoundsRegistry() {}
|
||||
@@ -73,72 +59,4 @@ public final class TileEntityRenderBoundsRegistry {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean overridesMethod(Class<?> clazz, Class<?> base, String[] candidateNames, Class<?>... params) {
|
||||
for (String name : candidateNames) {
|
||||
try {
|
||||
return clazz.getMethod(name, params).getDeclaringClass() != base;
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
} catch (Throwable t) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String[] distanceFromNames() {
|
||||
String[] names = distanceFromNames;
|
||||
if (names == null) {
|
||||
String resolved;
|
||||
try {
|
||||
resolved = Boolean.TRUE.equals(Launch.blackboard.get("fml.deobfuscatedEnvironment")) ? "getDistanceFrom" : "func_145835_a";
|
||||
} catch (Throwable t) {
|
||||
resolved = "getDistanceFrom";
|
||||
}
|
||||
distanceFromNames = names = new String[] { resolved };
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
public static byte classifyPass(TileEntity te) {
|
||||
final Class<? extends TileEntity> clazz = te.getClass();
|
||||
synchronized (passClassRegistry) {
|
||||
final byte cached = passClassRegistry.getByte(clazz);
|
||||
if (cached != UNKNOWN) return cached;
|
||||
}
|
||||
|
||||
final byte result = overridesMethod(clazz, TileEntity.class, SHOULD_RENDER_IN_PASS_NAMES, int.class)
|
||||
? PASS_OVERRIDES : PASS0_ONLY;
|
||||
|
||||
synchronized (passClassRegistry) {
|
||||
passClassRegistry.put(clazz, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean overridesGetDistanceFrom(Class<? extends TileEntity> clazz) {
|
||||
synchronized (distanceOverrideRegistry) {
|
||||
final byte cached = distanceOverrideRegistry.getByte(clazz);
|
||||
if (cached != UNKNOWN) return cached == OVERRIDDEN;
|
||||
}
|
||||
|
||||
final boolean overrides = overridesMethod(clazz, TileEntity.class, distanceFromNames(), double.class, double.class, double.class);
|
||||
|
||||
synchronized (distanceOverrideRegistry) {
|
||||
distanceOverrideRegistry.put(clazz, overrides ? OVERRIDDEN : NOT_OVERRIDDEN);
|
||||
}
|
||||
return overrides;
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
synchronized (classRegistry) {
|
||||
classRegistry.clear();
|
||||
}
|
||||
synchronized (passClassRegistry) {
|
||||
passClassRegistry.clear();
|
||||
}
|
||||
synchronized (distanceOverrideRegistry) {
|
||||
distanceOverrideRegistry.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.gtnewhorizons.angelica.rendering;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSource;
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IRenderingRegistryExt;
|
||||
|
||||
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler;
|
||||
import cpw.mods.fml.client.registry.RenderingRegistry;
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import cpw.mods.fml.common.ModContainer;
|
||||
import cpw.mods.fml.common.registry.GameRegistry;
|
||||
import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier;
|
||||
|
||||
public final class WorkerWorldAccess {
|
||||
|
||||
private static final String REPORT_URL = "https://github.com/GTNewHorizons/angelica/issues";
|
||||
private static final int INITIAL_SLOTS = 64;
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
private static final LoggedRenderTypeErrors READS = new LoggedRenderTypeErrors();
|
||||
private static final LoggedRenderTypeErrors WRITES = new LoggedRenderTypeErrors();
|
||||
|
||||
private WorkerWorldAccess() {}
|
||||
|
||||
public static void readOutsideSlice(String worldMethod, int x, int y, int z, Block renderingBlock) {
|
||||
if (!READS.shouldLog(renderingBlock)) return;
|
||||
LOGGER.warn(describe(worldMethod, "read outside the chunk slice", x, y, z, renderingBlock), new Throwable());
|
||||
}
|
||||
|
||||
public static void blockedWrite(String worldMethod, int x, int y, int z, Block renderingBlock) {
|
||||
if (!WRITES.shouldLog(renderingBlock)) return;
|
||||
LOGGER.error(describe(worldMethod, "ignored, it would corrupt world data", x, y, z, renderingBlock), new Throwable());
|
||||
}
|
||||
|
||||
private static String describe(String worldMethod, String outcome, int x, int y, int z, Block block) {
|
||||
final StringBuilder sb = new StringBuilder(224);
|
||||
sb.append("Off-thread World.").append(worldMethod).append(' ').append(outcome).append(" at ").append(x).append(',').append(y).append(',').append(z);
|
||||
if (block != null) {
|
||||
sb.append(". Rendering ").append(nameOf(block));
|
||||
final ISimpleBlockRenderingHandler isbrh = isbrhOf(block);
|
||||
if (isbrh != null) {
|
||||
sb.append(" via ").append(isbrh.getClass().getName());
|
||||
final String owner = modIdOf(isbrh.getClass());
|
||||
if (owner != null) sb.append(" from ").append(owner);
|
||||
}
|
||||
}
|
||||
return sb.append(". Please report with this trace: ").append(REPORT_URL).toString();
|
||||
}
|
||||
|
||||
private static String nameOf(Block block) {
|
||||
try {
|
||||
final UniqueIdentifier id = GameRegistry.findUniqueIdentifierFor(block);
|
||||
if (id != null) return id.modId + ':' + id.name;
|
||||
} catch (Throwable ignored) {}
|
||||
return block.getClass().getName();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private static ISimpleBlockRenderingHandler isbrhOf(Block block) {
|
||||
try {
|
||||
return ((IRenderingRegistryExt) RenderingRegistry.instance()).getISBRH(block.getRenderType());
|
||||
} catch (Throwable ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String modIdOf(Class<?> clazz) {
|
||||
try {
|
||||
final CodeSource source = clazz.getProtectionDomain() == null ? null : clazz.getProtectionDomain().getCodeSource();
|
||||
if (source == null || source.getLocation() == null) return null;
|
||||
final File jar = new File(jarFileUrl(source.getLocation()).toURI());
|
||||
for (ModContainer mod : Loader.instance().getModList()) {
|
||||
if (jar.equals(mod.getSource())) return mod.getModId();
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static URL jarFileUrl(URL location) throws MalformedURLException {
|
||||
if (!"jar".equals(location.getProtocol())) return location;
|
||||
final String nested = location.getPath();
|
||||
final int separator = nested.indexOf("!/");
|
||||
return new URL(separator < 0 ? nested : nested.substring(0, separator));
|
||||
}
|
||||
|
||||
private static final class LoggedRenderTypeErrors {
|
||||
|
||||
private volatile boolean[] logged = new boolean[INITIAL_SLOTS];
|
||||
|
||||
boolean shouldLog(Block block) {
|
||||
final int renderType = block == null ? -1 : block.getRenderType();
|
||||
final int slot = renderType < 0 ? 0 : renderType + 1;
|
||||
final boolean[] current = this.logged;
|
||||
return (slot >= current.length || !current[slot]) && markLogged(slot);
|
||||
}
|
||||
|
||||
private synchronized boolean markLogged(int slot) {
|
||||
boolean[] current = this.logged;
|
||||
if (slot < current.length) {
|
||||
if (current[slot]) return false;
|
||||
current[slot] = true;
|
||||
return true;
|
||||
}
|
||||
current = Arrays.copyOf(current, Math.max(slot + 1, current.length * 2));
|
||||
current[slot] = true;
|
||||
this.logged = current;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
-10
@@ -1,5 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import static com.gtnewhorizons.angelica.rendering.StateAwareTessellator.NO_DIRECTIONAL_SHADING;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.ExtQuadLightData;
|
||||
import com.gtnewhorizons.angelica.api.TintComputer;
|
||||
import com.gtnewhorizons.angelica.api.TintRegistry;
|
||||
@@ -28,6 +30,7 @@ import org.embeddedt.embeddium.impl.model.light.data.QuadLightData;
|
||||
import org.embeddedt.embeddium.impl.model.light.flat.FlatLightPipeline;
|
||||
import org.embeddedt.embeddium.impl.model.light.smooth.SmoothLightPipeline;
|
||||
import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFacing;
|
||||
import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFlags;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.ChunkColorWriter;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers;
|
||||
@@ -43,6 +46,9 @@ import java.util.List;
|
||||
public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
public static final int NUM_PASSES = 2;
|
||||
|
||||
// Yoinked from Celeritas
|
||||
private static final int[] REVERSED_QUAD_ORDER = { 0, 3, 2, 1 };
|
||||
|
||||
private final TextureMapExtension textureAtlas;
|
||||
private final ChunkVertexEncoder.Vertex[] vertices = ChunkVertexEncoder.Vertex.uninitializedQuad();
|
||||
@Getter
|
||||
@@ -62,6 +68,8 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
private boolean lightPipelineReady = false;
|
||||
private int originX, originY, originZ;
|
||||
private float cachedSkylightSubtracted;
|
||||
private final float[] fluidQuadPos = new float[12];
|
||||
private ModelQuadFacing fluidQuadLightFace;
|
||||
|
||||
public AngelicaChunkBuildContext(RenderPassConfiguration<?> renderPassConfiguration, WorldClient world) {
|
||||
super(renderPassConfiguration);
|
||||
@@ -160,6 +168,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
boolean hasCachedMaterial = false;
|
||||
TextureAtlasSprite lastSprite = null;
|
||||
Material lastMaterial = null;
|
||||
this.fluidQuadLightFace = null;
|
||||
|
||||
for (int quadIdx = 0; quadIdx < numQuads; quadIdx++) {
|
||||
float uSum = 0, vSum = 0;
|
||||
@@ -201,11 +210,8 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
// Otherwise, we need to use the block's lightmap values as they are as the ISBRH may have
|
||||
// specified its own lightmaps for visual purposes (e.g. fullbright)
|
||||
if (useAoCalculation && (quadState & StateAwareTessellator.RENDERED_WITH_VANILLA_AO) != 0) {
|
||||
quadView.setup(trueNormal, blockX, blockY, blockZ);
|
||||
final ModelQuadFacing lightFace = quadView.getLightFace();
|
||||
final LightPipeline pipeline = blockAllowsSmoothLighting ? smoothLightPipeline : flatLightPipeline;
|
||||
final ModelQuadFacing cullFace = quadView.getCullFace();
|
||||
pipeline.calculate(quadView, worldX, worldY, worldZ, quadLightData, cullFace, lightFace, shouldApplyDiffuse, true);
|
||||
calculateQuadLight(trueNormal, blockX, blockY, blockZ, worldX, worldY, worldZ, isFluid,
|
||||
blockAllowsSmoothLighting, shouldApplyDiffuse && (quadState & NO_DIRECTIONAL_SHADING) == 0);
|
||||
|
||||
for (int vIdx = 0; vIdx < 4; vIdx++) {
|
||||
final var vertex = vertices[vIdx];
|
||||
@@ -227,11 +233,8 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
}
|
||||
|
||||
if (useAoCalculation && isFluid) {
|
||||
quadView.setup(trueNormal, blockX, blockY, blockZ);
|
||||
final ModelQuadFacing lightFace = quadView.getLightFace();
|
||||
final LightPipeline pipeline = blockAllowsSmoothLighting ? smoothLightPipeline : flatLightPipeline;
|
||||
final ModelQuadFacing cullFace = quadView.getCullFace();
|
||||
pipeline.calculate(quadView, worldX, worldY, worldZ, quadLightData, cullFace, lightFace, shouldApplyDiffuse, true);
|
||||
calculateQuadLight(trueNormal, blockX, blockY, blockZ, worldX, worldY, worldZ, true,
|
||||
blockAllowsSmoothLighting, shouldApplyDiffuse);
|
||||
for (int vIdx = 0; vIdx < 4; vIdx++) {
|
||||
vertices[vIdx].light = quadLightData.lm[vIdx];
|
||||
}
|
||||
@@ -274,6 +277,42 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
|
||||
}
|
||||
}
|
||||
|
||||
private void calculateQuadLight(int trueNormal, int blockX, int blockY, int blockZ, int worldX, int worldY,
|
||||
int worldZ, boolean isFluid, boolean blockAllowsSmoothLighting,
|
||||
boolean shouldApplyDiffuse) {
|
||||
final boolean isFlippedFluidCopy = isFluid && fluidQuadLightFace != null && isReversedFluidQuad();
|
||||
|
||||
quadView.setup(trueNormal, blockX, blockY, blockZ, isFluid ? ModelQuadFlags.IS_VANILLA_SHADED : 0,
|
||||
isFlippedFluidCopy ? fluidQuadLightFace : null);
|
||||
|
||||
final LightPipeline pipeline = blockAllowsSmoothLighting ? smoothLightPipeline : flatLightPipeline;
|
||||
pipeline.calculate(quadView, worldX, worldY, worldZ, quadLightData, quadView.getCullFace(),
|
||||
quadView.getLightFace(), shouldApplyDiffuse, true);
|
||||
|
||||
if (isFluid && !isFlippedFluidCopy) {
|
||||
fluidQuadLightFace = quadView.getLightFace();
|
||||
for (int vIdx = 0; vIdx < 4; vIdx++) {
|
||||
final var vertex = vertices[vIdx];
|
||||
fluidQuadPos[vIdx * 3] = vertex.x;
|
||||
fluidQuadPos[vIdx * 3 + 1] = vertex.y;
|
||||
fluidQuadPos[vIdx * 3 + 2] = vertex.z;
|
||||
}
|
||||
} else if (!isFluid) {
|
||||
fluidQuadLightFace = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isReversedFluidQuad() {
|
||||
for (int vIdx = 0; vIdx < 4; vIdx++) {
|
||||
final int src = REVERSED_QUAD_ORDER[vIdx] * 3;
|
||||
final var vertex = vertices[vIdx];
|
||||
if (vertex.x != fluidQuadPos[src] || vertex.y != fluidQuadPos[src + 1] || vertex.z != fluidQuadPos[src + 2]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void applyBlockLightTint(int x, int y, int z, ChunkVertexEncoder.Vertex[] vertices) {
|
||||
if (!hasColoredLight) return;
|
||||
final ExtQuadLightData ext = (ExtQuadLightData) quadLightData;
|
||||
|
||||
+7
-1
@@ -4,11 +4,13 @@ import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.rendering.AngelicaRenderQueue;
|
||||
import com.gtnewhorizons.angelica.rendering.StateAwareTessellator;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.OverridesGetDistanceFrom;
|
||||
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.iris.BlockRenderContext;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.iris.ContextAwareChunkVertexEncoder;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
|
||||
import com.prupe.mcpatcher.mal.block.RenderBlocksUtils;
|
||||
import it.unimi.dsi.fastutil.floats.FloatArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
|
||||
@@ -111,6 +113,8 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
|
||||
final BlockPos blockPos = new BlockPos(minX, minY, minZ);
|
||||
final IBlockAccess region = getBlockAccess();
|
||||
final WorldSlice reportingSlice = region instanceof WorldSlice slice ? slice : null;
|
||||
if (reportingSlice != null) reportingSlice.setRenderingBlock(null);
|
||||
final SmoothBiomeColorCache biomeColorCache = getBiomeColorCache();
|
||||
|
||||
onEnterExecute();
|
||||
@@ -155,6 +159,8 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
continue;
|
||||
}
|
||||
|
||||
if (reportingSlice != null) reportingSlice.setRenderingBlock(block);
|
||||
|
||||
final int meta = region.getBlockMetadata(x, y, z);
|
||||
|
||||
if (block.hasTileEntity(meta)) {
|
||||
@@ -185,7 +191,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
renderData.culledBlockEntities.add(tileEntity);
|
||||
AngelicaBuiltRenderSectionData.packSectionLocalBounds(culledBounds, aabb, minX, minY, minZ);
|
||||
double teDistSq;
|
||||
if (TileEntityRenderBoundsRegistry.overridesGetDistanceFrom(tileEntity.getClass())) {
|
||||
if (tileEntity instanceof OverridesGetDistanceFrom) {
|
||||
teDistSq = Double.POSITIVE_INFINITY;
|
||||
} else {
|
||||
try {
|
||||
|
||||
+2
-1
@@ -45,6 +45,7 @@ import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightWorldRenderer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.ITileEntityBoundingBoxCache;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.OverridesShouldRenderInPass;
|
||||
import com.gtnewhorizons.angelica.profiling.RenderClassTimings;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderingState;
|
||||
@@ -405,7 +406,7 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
|
||||
try {
|
||||
for (int i = 0; i < frameTEs.size(); i++) {
|
||||
final TileEntity te = frameTEs.get(i);
|
||||
if (((ITileEntityBoundingBoxCache) te).angelica$passClass() == TileEntityRenderBoundsRegistry.PASS0_ONLY) {
|
||||
if (!(te instanceof OverridesShouldRenderInPass)) {
|
||||
dispatchTE(te, partialTicks);
|
||||
count++;
|
||||
continue;
|
||||
|
||||
+8
-2
@@ -13,18 +13,24 @@ public class VertexArrayQuadView implements ModelQuadView {
|
||||
private int blockX, blockY, blockZ;
|
||||
private ModelQuadFacing lightFace;
|
||||
private int flags;
|
||||
private int extraFlags;
|
||||
|
||||
public VertexArrayQuadView(ChunkVertexEncoder.Vertex[] vertices) {
|
||||
this.vertices = vertices;
|
||||
}
|
||||
|
||||
public void setup(int trueNormal, int blockX, int blockY, int blockZ) {
|
||||
setup(trueNormal, blockX, blockY, blockZ, 0, null);
|
||||
}
|
||||
|
||||
public void setup(int trueNormal, int blockX, int blockY, int blockZ, int extraFlags, ModelQuadFacing forcedLightFace) {
|
||||
this.trueNormal = trueNormal;
|
||||
this.blockX = blockX;
|
||||
this.blockY = blockY;
|
||||
this.blockZ = blockZ;
|
||||
this.lightFace = null; // Reset cached lightFace
|
||||
this.lightFace = forcedLightFace;
|
||||
this.flags = 0;
|
||||
this.extraFlags = extraFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,7 +71,7 @@ public class VertexArrayQuadView implements ModelQuadView {
|
||||
@Override
|
||||
public int getFlags() {
|
||||
if (flags == 0) {
|
||||
flags = ModelQuadFlags.getQuadFlags(this, getLightFace());
|
||||
flags = ModelQuadFlags.getQuadFlags(this, getLightFace(), extraFlags);
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
+12
-1
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas.light;
|
||||
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
|
||||
import lombok.Setter;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
@@ -9,6 +10,16 @@ import org.embeddedt.embeddium.impl.model.light.data.LightDataAccess;
|
||||
public class LightDataCache extends LightDataAccess {
|
||||
private IBlockAccess world;
|
||||
|
||||
/**
|
||||
* Doing this to force packs like Steadfast's water to work properly.
|
||||
*/
|
||||
private static int skyLight(IBlockAccess world, Block block, int x, int y, int z, int neighborMaxed) {
|
||||
if (world instanceof WorldSlice slice && block.getMaterial().isLiquid()) {
|
||||
return slice.getRawSkyLight(x, y, z);
|
||||
}
|
||||
return neighborMaxed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int compute(int x, int y, int z) {
|
||||
final IBlockAccess world = this.world;
|
||||
@@ -26,7 +37,7 @@ public class LightDataCache extends LightDataAccess {
|
||||
} else {
|
||||
final int packed = world.getLightBrightnessForSkyBlocks(x, y, z, 0);
|
||||
bl = (packed >> 4) & 0xF;
|
||||
sl = (packed >> 20) & 0xF;
|
||||
sl = skyLight(world, block, x, y, z, (packed >> 20) & 0xF);
|
||||
}
|
||||
|
||||
// AO value - blocks that emit light don't contribute to AO
|
||||
|
||||
@@ -104,6 +104,7 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
|
||||
// The chunk origin of this slice
|
||||
private ChunkSectionPos origin;
|
||||
private StructureBoundingBox volume;
|
||||
private Block renderingBlock;
|
||||
|
||||
private final SmoothBiomeColorCache biomeColorCache;
|
||||
|
||||
@@ -275,6 +276,17 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
|
||||
return skyBrightness << 20 | blockBrightness << 4;
|
||||
}
|
||||
|
||||
public int getRawSkyLight(int x, int y, int z) {
|
||||
final int minHeight = ModStatus.isCubicChunksLoaded ? CubicChunksAPI.getMinHeight(this.world) : 0;
|
||||
final int maxHeight = ModStatus.isCubicChunksLoaded ? CubicChunksAPI.getMaxHeight(this.world) : 256;
|
||||
|
||||
if (y < minHeight || y >= maxHeight || x < -30_000_000 || z < -30_000_000 || x >= 30_000_000 || z >= 30_000_000) {
|
||||
return 15;
|
||||
}
|
||||
|
||||
return this.getLightLevel(EnumSkyBlock.Sky, x, y, z);
|
||||
}
|
||||
|
||||
private int getSkyBlockTypeBrightness(EnumSkyBlock skyBlock, int x, int y, int z) {
|
||||
if (this.getBlock(x, y, z).getUseNeighborBrightness()) {
|
||||
int yp = this.getLightLevel(skyBlock, x, y + 1, z);
|
||||
@@ -530,6 +542,18 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
|
||||
return y << TABLE_BITS << TABLE_BITS | z << TABLE_BITS | x;
|
||||
}
|
||||
|
||||
public boolean containsBlock(int x, int y, int z) {
|
||||
return blockBoxContains(this.volume, x, y, z);
|
||||
}
|
||||
|
||||
public void setRenderingBlock(Block block) {
|
||||
this.renderingBlock = block;
|
||||
}
|
||||
|
||||
public Block getRenderingBlock() {
|
||||
return this.renderingBlock;
|
||||
}
|
||||
|
||||
private static boolean blockBoxContains(StructureBoundingBox box, int x, int y, int z) {
|
||||
return x >= box.minX && x <= box.maxX &&
|
||||
y >= box.minY && y <= box.maxY &&
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
|
||||
import com.gtnewhorizons.angelica.profiling.BailClassCounts;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
@@ -24,6 +25,7 @@ import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
|
||||
import net.coderbot.batchedentityrendering.impl.TransparencyType;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.layer.PassOverride;
|
||||
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import net.minecraft.client.model.ModelBox;
|
||||
@@ -105,22 +107,34 @@ public final class ModelPartBatcher {
|
||||
private static final class LayerKey {
|
||||
ResourceLocation texture;
|
||||
TesrMaterial material;
|
||||
PassOverride pass;
|
||||
float offsetFactor;
|
||||
float offsetUnits;
|
||||
|
||||
LayerKey set(ResourceLocation texture, TesrMaterial material) {
|
||||
LayerKey set(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) {
|
||||
this.texture = texture;
|
||||
this.material = material;
|
||||
this.pass = pass;
|
||||
this.offsetFactor = offsetFactor;
|
||||
this.offsetUnits = offsetUnits;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof LayerKey other)) return false;
|
||||
return Objects.equals(texture, other.texture) && Objects.equals(material, other.material);
|
||||
return Objects.equals(texture, other.texture) && Objects.equals(material, other.material)
|
||||
&& Objects.equals(pass, other.pass)
|
||||
&& Float.floatToIntBits(offsetFactor) == Float.floatToIntBits(other.offsetFactor)
|
||||
&& Float.floatToIntBits(offsetUnits) == Float.floatToIntBits(other.offsetUnits);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(texture) * 31 + Objects.hashCode(material);
|
||||
int h = (Objects.hashCode(texture) * 31 + Objects.hashCode(material)) * 31 + Objects.hashCode(pass);
|
||||
h = h * 31 + Float.floatToIntBits(offsetFactor);
|
||||
h = h * 31 + Float.floatToIntBits(offsetUnits);
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +142,9 @@ public final class ModelPartBatcher {
|
||||
private final LayerKey scratchKey = new LayerKey();
|
||||
private ResourceLocation lastLayerTexture;
|
||||
private TesrMaterial lastLayerMaterial;
|
||||
private PassOverride lastLayerPass;
|
||||
private float lastLayerOffsetFactor;
|
||||
private float lastLayerOffsetUnits;
|
||||
private RenderLayer lastLayer;
|
||||
|
||||
private static ResourceLocation lastBoundLocation;
|
||||
@@ -177,7 +194,9 @@ public final class ModelPartBatcher {
|
||||
if (instancedThisCycle) {
|
||||
TesrBatchRenderer.INSTANCE.instancedRenderer.endFrame();
|
||||
}
|
||||
BatchingFontRenderer.flushDeferredText();
|
||||
if (!TesrBatchRenderer.INSTANCE.hasPendingGeometry()) {
|
||||
BatchingFontRenderer.flushDeferredText();
|
||||
}
|
||||
return;
|
||||
}
|
||||
final boolean inEntityLoop = mode == Mode.ENTITIES && Iris.enabled && GbufferPrograms.isEntityLoopActive();
|
||||
@@ -193,6 +212,9 @@ public final class ModelPartBatcher {
|
||||
if (instancedThisCycle) {
|
||||
TesrBatchRenderer.INSTANCE.instancedRenderer.endFrame();
|
||||
}
|
||||
if (!TesrBatchRenderer.INSTANCE.hasPendingGeometry()) {
|
||||
BatchingFontRenderer.flushDeferredText();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
@@ -278,11 +300,19 @@ public final class ModelPartBatcher {
|
||||
}
|
||||
}
|
||||
}
|
||||
final RenderLayer layer = layerFor(texture, material);
|
||||
final int entityId = mode == Mode.ENTITIES ? Math.max(0, CapturedRenderingState.INSTANCE.getCurrentRenderedEntity()) : CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
|
||||
final PassOverride pass = shaderPipeline != null ? PassOverride.capture() : PassOverride.NONE;
|
||||
final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
final PolygonState polygon = GLStateManager.getPolygonState();
|
||||
final float offsetFactor = offset ? polygon.getOffsetFactor() : 0.0f;
|
||||
final float offsetUnits = offset ? polygon.getOffsetUnits() : 0.0f;
|
||||
final RenderLayer layer = layerFor(texture, material, pass, offsetFactor, offsetUnits);
|
||||
// Entities nested inside a TESR (mob spawner, OpenBlocks trophy) run in the block entity pass but carry an entity id
|
||||
final int entityId = mode == Mode.ENTITIES || pass.isEntityPhase()
|
||||
? Math.max(0, CapturedRenderingState.INSTANCE.getCurrentRenderedEntity())
|
||||
: CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
|
||||
final Color4 color = GLStateManager.getColor();
|
||||
final int colorABGR = ColorABGR.pack(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
|
||||
final int packedLight = ((int) GLSMConfig.lastBrightnessY << 16) | ((int) GLSMConfig.lastBrightnessX & 0xFFFF);
|
||||
final int packedLight = GLSMConfig.packedLastBrightness();
|
||||
final int mvGen = GLStateManager.getMvGeneration();
|
||||
if (!mvCaptured || mvGen != lastMvGeneration) {
|
||||
modelView.set(GLStateManager.getModelViewMatrix());
|
||||
@@ -349,17 +379,21 @@ public final class ModelPartBatcher {
|
||||
return entry;
|
||||
}
|
||||
|
||||
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material) {
|
||||
if (texture == lastLayerTexture && material == lastLayerMaterial) {
|
||||
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) {
|
||||
if (texture == lastLayerTexture && material == lastLayerMaterial && pass.equals(lastLayerPass)
|
||||
&& offsetFactor == lastLayerOffsetFactor && offsetUnits == lastLayerOffsetUnits) {
|
||||
return lastLayer;
|
||||
}
|
||||
RenderLayer layer = layers.get(scratchKey.set(texture, material));
|
||||
RenderLayer layer = layers.get(scratchKey.set(texture, material, pass, offsetFactor, offsetUnits));
|
||||
if (layer == null) {
|
||||
layer = RenderLayer.tesr(texture, material);
|
||||
layers.put(new LayerKey().set(texture, material), layer);
|
||||
layer = RenderLayer.tesr(texture, material, pass, offsetFactor, offsetUnits);
|
||||
layers.put(new LayerKey().set(texture, material, pass, offsetFactor, offsetUnits), layer);
|
||||
}
|
||||
lastLayerTexture = texture;
|
||||
lastLayerMaterial = material;
|
||||
lastLayerPass = pass;
|
||||
lastLayerOffsetFactor = offsetFactor;
|
||||
lastLayerOffsetUnits = offsetUnits;
|
||||
lastLayer = layer;
|
||||
return layer;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
|
||||
@@ -19,6 +20,7 @@ import net.coderbot.batchedentityrendering.impl.SegmentedBufferBuilder;
|
||||
import net.coderbot.batchedentityrendering.impl.TransparencyType;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.layer.PassOverride;
|
||||
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
|
||||
import net.coderbot.iris.pipeline.FixedFunctionWorldRenderingPipeline;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPhase;
|
||||
@@ -129,13 +131,23 @@ public final class TesrBatchRenderer {
|
||||
return activePass >= 0;
|
||||
}
|
||||
|
||||
public boolean hasPendingGeometry() {
|
||||
return activePass >= 0 || deferredFlushPending;
|
||||
}
|
||||
|
||||
public void queue(TemplateBuffer template, ResourceLocation texture, TesrMaterial material) {
|
||||
modelView.set(GLStateManager.getModelViewMatrix());
|
||||
final int packedLight = currentPackedLight(material);
|
||||
final int colorABGR = resolveColor(material);
|
||||
if (activePass >= 0) {
|
||||
final RenderLayer layer = layerFor(texture, material);
|
||||
final int blockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
|
||||
final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
final PolygonState polygon = GLStateManager.getPolygonState();
|
||||
final PassOverride pass = PassOverride.capture();
|
||||
final RenderLayer layer = layerFor(texture, material, pass,
|
||||
offset ? polygon.getOffsetFactor() : 0.0f, offset ? polygon.getOffsetUnits() : 0.0f);
|
||||
final int blockEntityId = pass.isEntityPhase()
|
||||
? Math.max(0, CapturedRenderingState.INSTANCE.getCurrentRenderedEntity())
|
||||
: CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
|
||||
retained[activePass].queue(template, layer, material, modelView, packedLight, colorABGR, blockEntityId, captureTextureMatrix(), false);
|
||||
} else {
|
||||
drawImmediate(template, texture, material, packedLight, colorABGR);
|
||||
@@ -159,8 +171,11 @@ public final class TesrBatchRenderer {
|
||||
TesrMaterial.SpecialRender special;
|
||||
TesrShader shader;
|
||||
boolean noPass;
|
||||
PassOverride pass;
|
||||
float offsetFactor;
|
||||
float offsetUnits;
|
||||
|
||||
LayerKey set(ResourceLocation texture, TesrMaterial.Transparency transparency, boolean noCull, boolean unlit, boolean noDepthWrite, boolean depthOnly, float cutoutAlpha, boolean depthEqual, TesrMaterial.SpecialRender special, TesrShader shader, boolean noPass) {
|
||||
LayerKey set(ResourceLocation texture, TesrMaterial.Transparency transparency, boolean noCull, boolean unlit, boolean noDepthWrite, boolean depthOnly, float cutoutAlpha, boolean depthEqual, TesrMaterial.SpecialRender special, TesrShader shader, boolean noPass, PassOverride pass, float offsetFactor, float offsetUnits) {
|
||||
this.texture = texture;
|
||||
this.transparency = transparency;
|
||||
this.noCull = noCull;
|
||||
@@ -172,11 +187,14 @@ public final class TesrBatchRenderer {
|
||||
this.special = special;
|
||||
this.shader = shader;
|
||||
this.noPass = noPass;
|
||||
this.pass = pass;
|
||||
this.offsetFactor = offsetFactor;
|
||||
this.offsetUnits = offsetUnits;
|
||||
return this;
|
||||
}
|
||||
|
||||
LayerKey copy() {
|
||||
return new LayerKey().set(texture, transparency, noCull, unlit, noDepthWrite, depthOnly, cutoutAlpha, depthEqual, special, shader, noPass);
|
||||
return new LayerKey().set(texture, transparency, noCull, unlit, noDepthWrite, depthOnly, cutoutAlpha, depthEqual, special, shader, noPass, pass, offsetFactor, offsetUnits);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -186,7 +204,10 @@ public final class TesrBatchRenderer {
|
||||
&& noCull == other.noCull && unlit == other.unlit && noDepthWrite == other.noDepthWrite
|
||||
&& depthOnly == other.depthOnly
|
||||
&& Float.floatToIntBits(cutoutAlpha) == Float.floatToIntBits(other.cutoutAlpha)
|
||||
&& depthEqual == other.depthEqual && special == other.special && shader == other.shader && noPass == other.noPass;
|
||||
&& depthEqual == other.depthEqual && special == other.special && shader == other.shader && noPass == other.noPass
|
||||
&& Objects.equals(pass, other.pass)
|
||||
&& Float.floatToIntBits(offsetFactor) == Float.floatToIntBits(other.offsetFactor)
|
||||
&& Float.floatToIntBits(offsetUnits) == Float.floatToIntBits(other.offsetUnits);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -202,6 +223,9 @@ public final class TesrBatchRenderer {
|
||||
h = h * 31 + Objects.hashCode(special);
|
||||
h = h * 31 + System.identityHashCode(shader);
|
||||
h = h * 31 + (noPass ? 1 : 0);
|
||||
h = h * 31 + Objects.hashCode(pass);
|
||||
h = h * 31 + Float.floatToIntBits(offsetFactor);
|
||||
h = h * 31 + Float.floatToIntBits(offsetUnits);
|
||||
return h;
|
||||
}
|
||||
}
|
||||
@@ -210,18 +234,25 @@ public final class TesrBatchRenderer {
|
||||
private final LayerKey scratchKey = new LayerKey();
|
||||
private ResourceLocation lastLayerTexture;
|
||||
private TesrMaterial lastLayerMaterial;
|
||||
private PassOverride lastLayerPass;
|
||||
private float lastLayerOffsetFactor;
|
||||
private float lastLayerOffsetUnits;
|
||||
private RenderLayer lastLayer;
|
||||
private ResourceLocation lastImmediateTexture;
|
||||
private TesrMaterial lastImmediateMaterial;
|
||||
private RenderLayer lastImmediateLayer;
|
||||
|
||||
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material) {
|
||||
if (texture == lastLayerTexture && material == lastLayerMaterial) {
|
||||
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) {
|
||||
if (texture == lastLayerTexture && material == lastLayerMaterial && pass.equals(lastLayerPass)
|
||||
&& offsetFactor == lastLayerOffsetFactor && offsetUnits == lastLayerOffsetUnits) {
|
||||
return lastLayer;
|
||||
}
|
||||
final RenderLayer layer = layerLookup(texture, material, false);
|
||||
final RenderLayer layer = layerLookup(texture, material, false, pass, offsetFactor, offsetUnits);
|
||||
lastLayerTexture = texture;
|
||||
lastLayerMaterial = material;
|
||||
lastLayerPass = pass;
|
||||
lastLayerOffsetFactor = offsetFactor;
|
||||
lastLayerOffsetUnits = offsetUnits;
|
||||
lastLayer = layer;
|
||||
return layer;
|
||||
}
|
||||
@@ -230,18 +261,18 @@ public final class TesrBatchRenderer {
|
||||
if (texture == lastImmediateTexture && material == lastImmediateMaterial) {
|
||||
return lastImmediateLayer;
|
||||
}
|
||||
final RenderLayer layer = layerLookup(texture, material, true);
|
||||
final RenderLayer layer = layerLookup(texture, material, true, PassOverride.NONE, 0.0f, 0.0f);
|
||||
lastImmediateTexture = texture;
|
||||
lastImmediateMaterial = material;
|
||||
lastImmediateLayer = layer;
|
||||
return layer;
|
||||
}
|
||||
|
||||
private RenderLayer layerLookup(ResourceLocation texture, TesrMaterial material, boolean noPass) {
|
||||
final LayerKey key = scratchKey.set(texture, material.transparency(), material.isNoCull(), material.isUnlit(), material.isNoDepthWrite(), material.isDepthOnly(), material.cutoutAlpha(), material.isDepthEqual(), material.special(), material.shader(), noPass);
|
||||
private RenderLayer layerLookup(ResourceLocation texture, TesrMaterial material, boolean noPass, PassOverride pass, float offsetFactor, float offsetUnits) {
|
||||
final LayerKey key = scratchKey.set(texture, material.transparency(), material.isNoCull(), material.isUnlit(), material.isNoDepthWrite(), material.isDepthOnly(), material.cutoutAlpha(), material.isDepthEqual(), material.special(), material.shader(), noPass, pass, offsetFactor, offsetUnits);
|
||||
RenderLayer layer = layers.get(key);
|
||||
if (layer == null) {
|
||||
layer = noPass ? RenderLayer.tesrNoPass(texture, material) : RenderLayer.tesr(texture, material);
|
||||
layer = noPass ? RenderLayer.tesrNoPass(texture, material) : RenderLayer.tesr(texture, material, pass, offsetFactor, offsetUnits);
|
||||
layers.put(key.copy(), layer);
|
||||
}
|
||||
return layer;
|
||||
@@ -249,9 +280,9 @@ public final class TesrBatchRenderer {
|
||||
|
||||
private static int currentPackedLight(TesrMaterial material) {
|
||||
if (material.hasLightmap()) {
|
||||
return ((int) material.lightmapY() << 16) | ((int) material.lightmapX() & 0xFFFF);
|
||||
return GLSMConfig.packBrightness(material.lightmapX(), material.lightmapY());
|
||||
}
|
||||
return ((int) GLSMConfig.lastBrightnessY << 16) | ((int) GLSMConfig.lastBrightnessX & 0xFFFF);
|
||||
return GLSMConfig.packedLastBrightness();
|
||||
}
|
||||
|
||||
private static int resolveColor(TesrMaterial material) {
|
||||
@@ -307,6 +338,7 @@ public final class TesrBatchRenderer {
|
||||
try {
|
||||
final EntityRenderer entityRenderer = Minecraft.getMinecraft().entityRenderer;
|
||||
final boolean savedDepthMask = GLStateManager.getDepthState().isEnabled();
|
||||
final boolean savedDepthTest = GLStateManager.getDepthTest().isEnabled();
|
||||
final boolean savedBlend = GLStateManager.getBlendMode().isEnabled();
|
||||
final int savedSrcRgb = GLStateManager.getBlendState().getSrcRgb();
|
||||
final int savedDstRgb = GLStateManager.getBlendState().getDstRgb();
|
||||
@@ -329,11 +361,12 @@ public final class TesrBatchRenderer {
|
||||
bufferSource.endBatch(hook);
|
||||
instancedRenderer.endFrame();
|
||||
AngelicaBufferSource.rebindPass();
|
||||
BatchingFontRenderer.flushDeferredText();
|
||||
BatchingFontRenderer.flushDeferredText();
|
||||
} finally {
|
||||
if (wrap) GbufferPrograms.endBlockEntities();
|
||||
entityRenderer.disableLightmap(0);
|
||||
GLStateManager.glDepthMask(savedDepthMask);
|
||||
if (savedDepthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
|
||||
if (savedBlend) GLStateManager.enableBlend(); else GLStateManager.disableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(savedSrcRgb, savedDstRgb, savedSrcAlpha, savedDstAlpha);
|
||||
if (savedAlphaTest) GLStateManager.enableAlphaTest(); else GLStateManager.disableAlphaTest();
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.gtnewhorizons.angelica.utils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.AwaitingDescriptorTE;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.SendsDescriptionPacket;
|
||||
|
||||
public final class AwaitingDescriptor {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
private static final int WAIT_TICKS = 100;
|
||||
|
||||
private static final Set<Class<?>> NEVER_SENT = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
private AwaitingDescriptor() {}
|
||||
|
||||
public static void begin(TileEntity te) {
|
||||
if (!(te instanceof SendsDescriptionPacket) || NEVER_SENT.contains(te.getClass())) return;
|
||||
|
||||
final AwaitingDescriptorTE tile = (AwaitingDescriptorTE) te;
|
||||
tile.angelica$setAwaitingDescriptor(true);
|
||||
tile.angelica$setDescriptorWaitTicks(WAIT_TICKS);
|
||||
}
|
||||
|
||||
public static void end(AwaitingDescriptorTE tile) {
|
||||
tile.angelica$setAwaitingDescriptor(false);
|
||||
tile.angelica$setDescriptorWaitTicks(0);
|
||||
}
|
||||
|
||||
public static boolean defersUpdate(TileEntity te) {
|
||||
final AwaitingDescriptorTE tile = (AwaitingDescriptorTE) te;
|
||||
final int waiting = tile.angelica$getDescriptorWaitTicks();
|
||||
if (waiting <= 0) return false;
|
||||
|
||||
tile.angelica$setDescriptorWaitTicks(waiting - 1);
|
||||
if (waiting == 1) stopWaitingOn(te.getClass());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void stopWaitingOn(Class<?> cls) {
|
||||
if (NEVER_SENT.add(cls)) {
|
||||
LOGGER.warn("{} declares getDescriptionPacket() but never sent one", cls.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,7 +202,8 @@ public class ConcurrentTileEntityMap extends HashMap<ChunkPosition, TileEntity>
|
||||
if (!isRenderWorkerThread()) return delegate.containsKey(key);
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return delegate.containsKey(key);
|
||||
final TileEntity te = delegate.get(key);
|
||||
return te != null && !te.isInvalid();
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ public class TileLoader {
|
||||
logger.severe("beforeChange was not called, invoking directly");
|
||||
changeHandler.beforeChange();
|
||||
}
|
||||
long currentSize = Long.MIN_VALUE;
|
||||
for (TileLoader loader : loaders) {
|
||||
if (loader.isForThisMap(mapName)) {
|
||||
if (loader.baseTextureMap == null) {
|
||||
@@ -105,10 +106,13 @@ public class TileLoader {
|
||||
loader.baseTexturesByName.putAll(map);
|
||||
}
|
||||
if (!loader.tilesToRegister.isEmpty()) {
|
||||
if (currentSize == Long.MIN_VALUE) currentSize = getTextureSize(map.values());
|
||||
loader.subLogger
|
||||
.fine("adding icons to %s (%d remaining)", mapName, loader.tilesToRegister.size(), mapName);
|
||||
while (!loader.tilesToRegister.isEmpty() && loader.registerOneIcon(textureMap, mapName, map)) {
|
||||
// nothing
|
||||
while (!loader.tilesToRegister.isEmpty()) {
|
||||
long updatedSize = loader.registerOneIcon(textureMap, mapName, map, currentSize);
|
||||
if (updatedSize < 0) break;
|
||||
currentSize = updatedSize;
|
||||
}
|
||||
loader.subLogger.fine(
|
||||
"done adding icons to %s (%d remaining)",
|
||||
@@ -275,42 +279,44 @@ public class TileLoader {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean registerOneIcon(TextureMap textureMap, String mapName, Map<String, TextureAtlasSprite> map) {
|
||||
protected long registerOneIcon(TextureMap textureMap, String mapName, Map<String, TextureAtlasSprite> map,
|
||||
long currentSize) {
|
||||
ResourceLocation resource = tilesToRegister.iterator()
|
||||
.next();
|
||||
String name = resource.toString();
|
||||
if (registerDefaultIcon(name)) {
|
||||
tilesToRegister.remove(resource);
|
||||
return true;
|
||||
return currentSize;
|
||||
}
|
||||
BufferedImage image = tileImages.get(resource);
|
||||
if (image == null) {
|
||||
subLogger.error("tile for %s unexpectedly missing", resource);
|
||||
tilesToRegister.remove(resource);
|
||||
return true;
|
||||
return currentSize;
|
||||
}
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
long currentSize = getTextureSize(map.values());
|
||||
long newSize = 4 * width * width;
|
||||
if (newSize + currentSize > MAX_TILESHEET_SIZE) {
|
||||
float sizeMB = (float) currentSize / 1048576.0f;
|
||||
if (currentSize <= 0) {
|
||||
subLogger.error("%s too big for any tilesheet (%.1fMB), dropping", name, sizeMB);
|
||||
tilesToRegister.remove(resource);
|
||||
return true;
|
||||
return currentSize;
|
||||
} else {
|
||||
subLogger.warning("%s nearly full (%.1fMB), will start a new tilesheet", mapName, sizeMB);
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
TextureAtlasSprite previous = map.get(name);
|
||||
TextureAtlasSprite icon = registerIconToMap(textureMap, resource, name);
|
||||
map.put(name, icon);
|
||||
currentSize += getTextureSize(icon) - getTextureSize(previous);
|
||||
iconMap.put(name, icon);
|
||||
String extra = (width == height ? "" : ", " + (height / width) + " frames");
|
||||
subLogger.finer("%s -> %s icon %dx%d%s", name, mapName, width, width, extra);
|
||||
tilesToRegister.remove(resource);
|
||||
return true;
|
||||
return currentSize;
|
||||
}
|
||||
|
||||
protected TextureAtlasSprite registerIconToMap(TextureMap textureMap, ResourceLocation resource, String name) {
|
||||
|
||||
@@ -11,8 +11,6 @@ import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.client.settings.GameSettings;
|
||||
import net.minecraft.util.MathHelper;
|
||||
|
||||
import org.lwjgl.opengl.Display;
|
||||
|
||||
import com.cardinalstar.cubicchunks.api.compat.CubicChunksVideoSettings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
@@ -21,7 +19,6 @@ import com.gtnewhorizons.angelica.rendering.celeritas.MultiDrawModeResolver;
|
||||
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
|
||||
import com.gtnewhorizons.angelica.config.GpuCullingMode;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import cpw.mods.fml.common.Optional.Method;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
|
||||
import jss.notfine.core.Settings;
|
||||
@@ -48,6 +45,7 @@ import me.jellysquid.mods.sodium.client.gui.options.storage.MinecraftOptionsStor
|
||||
import me.jellysquid.mods.sodium.client.gui.options.storage.SodiumOptionsStorage;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.gui.option.IrisVideoSettings;
|
||||
import net.coderbot.iris.pipeline.ShadowRenderer;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.occlusion.AsyncOcclusionMode;
|
||||
|
||||
public class SodiumGameOptionPages {
|
||||
@@ -519,6 +517,36 @@ public class SodiumGameOptionPages {
|
||||
|
||||
.build());
|
||||
|
||||
if (Iris.enabled) {
|
||||
final OptionImpl<AngelicaConfig, Integer> shadowGraphAngleDelta =
|
||||
OptionImpl.createBuilder(int.class, angelicaOpts)
|
||||
.setName(I18n.format("options.angelica.shadowGraphAngleDelta"))
|
||||
.setTooltip(I18n.format("options.angelica.shadowGraphAngleDelta.tooltip"))
|
||||
.setControl(o -> new SliderControl(o, 0, 100, 1, ControlValueFormatter.shadowGraphAngleDelta()))
|
||||
.setImpact(OptionImpact.MEDIUM)
|
||||
.setBinding((opts, value) -> AngelicaConfig.shadowGraphAngleDelta = value * 0.001f,
|
||||
opts -> Math.round(AngelicaConfig.shadowGraphAngleDelta * 1000.0f))
|
||||
.build();
|
||||
shadowGraphAngleDelta.iris$dynamicallyEnable(ShadowRenderer::shadowGraphGateIsNotBypassed);
|
||||
|
||||
final OptionImpl<AngelicaConfig, Integer> shadowGraphHorizonScale =
|
||||
OptionImpl.createBuilder(int.class, angelicaOpts)
|
||||
.setName(I18n.format("options.angelica.shadowGraphHorizonScale"))
|
||||
.setTooltip(I18n.format("options.angelica.shadowGraphHorizonScale.tooltip"))
|
||||
.setControl(o -> new SliderControl(o, 0, 100, 1, ControlValueFormatter.shadowGraphHorizonScale()))
|
||||
.setImpact(OptionImpact.LOW)
|
||||
.setBinding((opts, value) -> AngelicaConfig.shadowGraphHorizonScale = value * 0.01f,
|
||||
opts -> Math.round(AngelicaConfig.shadowGraphHorizonScale * 100.0f))
|
||||
.build();
|
||||
shadowGraphHorizonScale.iris$dynamicallyEnable(
|
||||
() -> ShadowRenderer.shadowGraphGateIsNotBypassed() && shadowGraphAngleDelta.getValue() != 0);
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(shadowGraphAngleDelta)
|
||||
.add(shadowGraphHorizonScale)
|
||||
.build());
|
||||
}
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
|
||||
.setName(I18n.format("options.angelica.hudCaching"))
|
||||
|
||||
+12
@@ -2,6 +2,8 @@ package me.jellysquid.mods.sodium.client.gui.options.control;
|
||||
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public interface ControlValueFormatter {
|
||||
static ControlValueFormatter guiScale() {
|
||||
return (v) -> (v == 0) ? I18n.format("options.guiScale.auto") : I18n.format(v + "x");
|
||||
@@ -47,4 +49,14 @@ public interface ControlValueFormatter {
|
||||
static ControlValueFormatter number() {
|
||||
return String::valueOf;
|
||||
}
|
||||
|
||||
static ControlValueFormatter shadowGraphAngleDelta() {
|
||||
return (v) -> v == 0
|
||||
? I18n.format("options.angelica.shadowGraphAngleDelta.always")
|
||||
: String.format(Locale.ROOT, "%.3f°", v * 0.001f);
|
||||
}
|
||||
|
||||
static ControlValueFormatter shadowGraphHorizonScale() {
|
||||
return (v) -> v == 100 ? I18n.format("options.angelica.shadowGraphHorizonScale.off") : v + "%";
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.coderbot.batchedentityrendering.impl.ordering.SimpleRenderOrderManager;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPhase;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -159,15 +161,17 @@ public class AngelicaBufferSource implements Groupable {
|
||||
|
||||
private void clearCurrentId() {
|
||||
if (!anyIdSet) return;
|
||||
if (idKind == GroupIdKind.ENTITY) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntity(-1);
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0f, 0f, 0f, 0f);
|
||||
} else {
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
|
||||
}
|
||||
// A pass can set either kind: nested entities inside a TESR write the entity id during a block entity pass
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(-1, 0);
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0f, 0f, 0f, 0f);
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
|
||||
anyIdSet = false;
|
||||
}
|
||||
|
||||
private GroupIdKind effectiveIdKind() {
|
||||
return GbufferPrograms.getCurrentPhase() == WorldRenderingPhase.ENTITIES ? GroupIdKind.ENTITY : idKind;
|
||||
}
|
||||
|
||||
private void drawLayer(RenderLayer layer, LayerDrawHook hook) {
|
||||
final List<BufferSegment> segments = typeToSegment.get(layer);
|
||||
final boolean hasDynamic = segments != null && !segments.isEmpty();
|
||||
@@ -184,7 +188,7 @@ public class AngelicaBufferSource implements Groupable {
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
SegmentedBufferBuilder.LayerBuffer bound = null;
|
||||
final boolean entityKind = idKind == GroupIdKind.ENTITY;
|
||||
final boolean entityKind = effectiveIdKind() == GroupIdKind.ENTITY;
|
||||
int currentId = Integer.MIN_VALUE;
|
||||
int currentColor = 0;
|
||||
boolean colorSet = false;
|
||||
@@ -271,8 +275,8 @@ public class AngelicaBufferSource implements Groupable {
|
||||
|
||||
public void applyIdAndRebind(int id) {
|
||||
anyIdSet = true;
|
||||
if (idKind == GroupIdKind.ENTITY) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntity(id);
|
||||
if (effectiveIdKind() == GroupIdKind.ENTITY) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(id, 0);
|
||||
rebindPass();
|
||||
} else {
|
||||
setBlockEntityAndRebind(id);
|
||||
@@ -281,8 +285,8 @@ public class AngelicaBufferSource implements Groupable {
|
||||
|
||||
public void applyIdNoRebind(int id) {
|
||||
anyIdSet = true;
|
||||
if (idKind == GroupIdKind.ENTITY) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntity(id);
|
||||
if (effectiveIdKind() == GroupIdKind.ENTITY) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(id, 0);
|
||||
} else {
|
||||
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(id);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -919,6 +920,11 @@ public class Iris {
|
||||
return pipelineManager;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PipelineManager getPipelineManagerNullable() {
|
||||
return pipelineManager;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Optional<ShaderPack> getCurrentPack() {
|
||||
return Optional.ofNullable(currentPack);
|
||||
|
||||
@@ -69,13 +69,17 @@ public class CeleritasTerrainPipeline {
|
||||
IntFunction<ProgramImages> createTerrainImages,
|
||||
IntFunction<ProgramImages> createShadowImages,
|
||||
CustomUniforms customUniforms,
|
||||
Optional<ProgramSource> terrainSource,
|
||||
Optional<ProgramSource> terrainSolidSource,
|
||||
Optional<ProgramSource> terrainCutoutSource,
|
||||
Optional<ProgramSource> translucentSource,
|
||||
Optional<ProgramSource> shadowSource,
|
||||
Optional<ProgramSource> shadowSolidSource,
|
||||
Optional<ProgramSource> shadowCutoutSource,
|
||||
Optional<ProgramSource> shadowTranslucentSource,
|
||||
CompletableFuture<Map<PatchShaderType, String>> terrainFuture,
|
||||
CompletableFuture<Map<PatchShaderType, String>> terrainSolidFuture,
|
||||
CompletableFuture<Map<PatchShaderType, String>> terrainCutoutFuture,
|
||||
CompletableFuture<Map<PatchShaderType, String>> translucentFuture,
|
||||
CompletableFuture<Map<PatchShaderType, String>> shadowFuture,
|
||||
CompletableFuture<Map<PatchShaderType, String>> shadowSolidFuture,
|
||||
CompletableFuture<Map<PatchShaderType, String>> shadowCutoutFuture,
|
||||
CompletableFuture<Map<PatchShaderType, String>> shadowTranslucentFuture,
|
||||
RenderTargets renderTargets,
|
||||
ImmutableSet<Integer> flippedAfterPrepare,
|
||||
@@ -90,11 +94,12 @@ public class CeleritasTerrainPipeline {
|
||||
|
||||
// Build program source map (local - only needed during construction)
|
||||
final EnumMap<IrisTerrainPass, Optional<ProgramSource>> gbufferProgramSource = new EnumMap<>(IrisTerrainPass.class);
|
||||
gbufferProgramSource.put(IrisTerrainPass.GBUFFER_SOLID, terrainSource);
|
||||
gbufferProgramSource.put(IrisTerrainPass.GBUFFER_CUTOUT, terrainSource);
|
||||
gbufferProgramSource.put(IrisTerrainPass.GBUFFER_TRANSLUCENT, translucentSource.isPresent() ? translucentSource : terrainSource);
|
||||
gbufferProgramSource.put(IrisTerrainPass.SHADOW, shadowSource);
|
||||
gbufferProgramSource.put(IrisTerrainPass.SHADOW_TRANSLUCENT, shadowTranslucentSource.isPresent() ? shadowTranslucentSource : shadowSource);
|
||||
gbufferProgramSource.put(IrisTerrainPass.GBUFFER_SOLID, terrainSolidSource);
|
||||
gbufferProgramSource.put(IrisTerrainPass.GBUFFER_CUTOUT, terrainCutoutSource);
|
||||
gbufferProgramSource.put(IrisTerrainPass.GBUFFER_TRANSLUCENT, translucentSource);
|
||||
gbufferProgramSource.put(IrisTerrainPass.SHADOW, shadowSolidSource);
|
||||
gbufferProgramSource.put(IrisTerrainPass.SHADOW_CUTOUT, shadowCutoutSource);
|
||||
gbufferProgramSource.put(IrisTerrainPass.SHADOW_TRANSLUCENT, shadowTranslucentSource);
|
||||
|
||||
// Initialize PassInfo, framebuffers, blend modes, and alpha in single pass
|
||||
for (IrisTerrainPass pass : IrisTerrainPass.VALUES) {
|
||||
@@ -103,10 +108,12 @@ public class CeleritasTerrainPipeline {
|
||||
|
||||
// Set up framebuffer, blend mode, and buffer blend overrides
|
||||
final ProgramId programId = switch (pass) {
|
||||
case GBUFFER_SOLID -> ProgramId.TerrainSolid;
|
||||
case GBUFFER_CUTOUT -> ProgramId.TerrainCutout;
|
||||
case GBUFFER_TRANSLUCENT -> ProgramId.Water;
|
||||
case SHADOW_TRANSLUCENT -> ProgramId.ShadowWater;
|
||||
case SHADOW, SHADOW_CUTOUT -> ProgramId.Shadow;
|
||||
default -> ProgramId.Terrain;
|
||||
case SHADOW -> ProgramId.ShadowSolid;
|
||||
case SHADOW_CUTOUT -> ProgramId.ShadowCutout;
|
||||
};
|
||||
|
||||
if (pass.isShadow()) {
|
||||
@@ -146,10 +153,12 @@ public class CeleritasTerrainPipeline {
|
||||
}
|
||||
|
||||
// Process and apply shader sources
|
||||
processShaderFuture(terrainFuture, terrainSource, passInfoMap.get(IrisTerrainPass.GBUFFER_SOLID), passInfoMap.get(IrisTerrainPass.GBUFFER_CUTOUT));
|
||||
processShaderFuture(terrainSolidFuture, terrainSolidSource, passInfoMap.get(IrisTerrainPass.GBUFFER_SOLID));
|
||||
processShaderFuture(terrainCutoutFuture, terrainCutoutSource, passInfoMap.get(IrisTerrainPass.GBUFFER_CUTOUT));
|
||||
processShaderFuture(translucentFuture, translucentSource, passInfoMap.get(IrisTerrainPass.GBUFFER_TRANSLUCENT));
|
||||
processShaderFuture(shadowFuture, shadowSource, passInfoMap.get(IrisTerrainPass.SHADOW), passInfoMap.get(IrisTerrainPass.SHADOW_CUTOUT));
|
||||
processShaderFuture(shadowTranslucentFuture, shadowTranslucentSource.isPresent() ? shadowTranslucentSource : shadowSource, passInfoMap.get(IrisTerrainPass.SHADOW_TRANSLUCENT));
|
||||
processShaderFuture(shadowSolidFuture, shadowSolidSource, passInfoMap.get(IrisTerrainPass.SHADOW));
|
||||
processShaderFuture(shadowCutoutFuture, shadowCutoutSource, passInfoMap.get(IrisTerrainPass.SHADOW_CUTOUT));
|
||||
processShaderFuture(shadowTranslucentFuture, shadowTranslucentSource, passInfoMap.get(IrisTerrainPass.SHADOW_TRANSLUCENT));
|
||||
}
|
||||
|
||||
private void processShaderFuture(@Nullable CompletableFuture<Map<PatchShaderType, String>> future, Optional<ProgramSource> source, PassInfo... targets) {
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package net.coderbot.iris.fantastic;
|
||||
|
||||
// TODO: RenderType
|
||||
//public class IrisParticleRenderTypes {
|
||||
// public static final ParticleRenderType OPAQUE_TERRAIN = new ParticleRenderType() {
|
||||
// public void begin(BufferBuilder bufferBuilder, TextureManager textureManager) {
|
||||
// GL11.glDisable(GL11.GL_BLEND);
|
||||
// GL11.glDepthMask(true);
|
||||
// GL11.glAlphaFunc(GL11.GL_GREATER, 0.1F);
|
||||
// textureManager.bind(TextureAtlas.LOCATION_BLOCKS);
|
||||
// bufferBuilder.begin(7, DefaultVertexFormat.PARTICLE);
|
||||
// }
|
||||
//
|
||||
// public void end(Tesselator tesselator) {
|
||||
// tesselator.end();
|
||||
// }
|
||||
//
|
||||
// public String toString() {
|
||||
// return "OPAQUE_TERRAIN_SHEET";
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
@@ -12,6 +12,8 @@ public enum RenderCondition {
|
||||
BEACON_BEAM,
|
||||
ENTITIES,
|
||||
ENTITIES_TRANSLUCENT,
|
||||
PARTICLES,
|
||||
PARTICLES_TRANSLUCENT,
|
||||
GLINT,
|
||||
ENTITY_EYES,
|
||||
HAND_OPAQUE,
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package net.coderbot.iris.gbuffer_overrides.matching;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Decides whether a draw is blending.
|
||||
*/
|
||||
public final class TranslucentBlendMatcher {
|
||||
|
||||
/** Render thread only. */
|
||||
private static final BlendState scratch = new BlendState();
|
||||
|
||||
private TranslucentBlendMatcher() {}
|
||||
|
||||
public static boolean matchesCurrentState() {
|
||||
if (!GLStateManager.isEffectiveBlendEnabled()) {
|
||||
return false;
|
||||
}
|
||||
GLStateManager.getEffectiveBlendState(scratch);
|
||||
return scratch.getSrcRgb() == GL11.GL_SRC_ALPHA && scratch.getDstRgb() == GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
}
|
||||
}
|
||||
@@ -3,57 +3,114 @@ package net.coderbot.iris.gl.blending;
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaTestFunction;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
|
||||
import lombok.Getter;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public class AlphaTestStorage {
|
||||
private static boolean originalAlphaTestEnable;
|
||||
private static AlphaTest originalAlphaTest;
|
||||
@Getter
|
||||
private static boolean vanillaEnabled;
|
||||
private static int vanillaFunction = GL11.GL_ALWAYS;
|
||||
private static float vanillaReference;
|
||||
|
||||
@Getter
|
||||
private static boolean alphaTestLocked;
|
||||
|
||||
private static boolean overrideHeld;
|
||||
|
||||
public static void overrideAlphaTest(AlphaTest override) {
|
||||
if (!alphaTestLocked) {
|
||||
if (!overrideHeld) {
|
||||
final AlphaState alphaState = GLStateManager.getAlphaState();
|
||||
|
||||
// Only save the previous state if the alpha test wasn't already locked
|
||||
originalAlphaTestEnable = GLStateManager.getAlphaTest().isEnabled();
|
||||
originalAlphaTest = new AlphaTest(AlphaTestFunction.fromGlId(alphaState.getFunction()).orElse(AlphaTestFunction.ALWAYS), alphaState.getReference());
|
||||
}
|
||||
// Only save the previous state if the alpha test wasn't already overridden
|
||||
vanillaEnabled = GLStateManager.getAlphaTest().isEnabled();
|
||||
vanillaFunction = alphaState.getFunction();
|
||||
vanillaReference = alphaState.getReference();
|
||||
}
|
||||
|
||||
alphaTestLocked = false;
|
||||
overrideHeld = true;
|
||||
|
||||
if (override == null) {
|
||||
GLStateManager.disableAlphaTest();
|
||||
} else {
|
||||
if (override == null) {
|
||||
apply(false, GL11.GL_ALWAYS, 0.0f);
|
||||
} else {
|
||||
apply(true, override.getFunction().getGlId(), override.getReference());
|
||||
}
|
||||
}
|
||||
|
||||
private static void apply(boolean enabled, int function, float reference) {
|
||||
alphaTestLocked = false;
|
||||
try {
|
||||
if (enabled) {
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(function, reference);
|
||||
} else {
|
||||
GLStateManager.disableAlphaTest();
|
||||
}
|
||||
} finally {
|
||||
alphaTestLocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void deferAlphaTestToggle(boolean enabled) {
|
||||
vanillaEnabled = enabled;
|
||||
}
|
||||
|
||||
public static void deferAlphaFunc(int function, float reference) {
|
||||
vanillaFunction = function;
|
||||
vanillaReference = reference;
|
||||
}
|
||||
|
||||
public static void restoreAlphaTest() {
|
||||
if (!overrideHeld) {
|
||||
return;
|
||||
}
|
||||
|
||||
alphaTestLocked = false;
|
||||
|
||||
if (vanillaEnabled) {
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.glAlphaFunc(override.getFunction().getGlId(), override.getReference());
|
||||
}
|
||||
|
||||
alphaTestLocked = true;
|
||||
}
|
||||
|
||||
public static void deferAlphaTestToggle(boolean enabled) {
|
||||
originalAlphaTestEnable = enabled;
|
||||
}
|
||||
|
||||
public static void deferAlphaFunc(int function, float reference) {
|
||||
originalAlphaTest = new AlphaTest(AlphaTestFunction.fromGlId(function).get(), reference);
|
||||
}
|
||||
|
||||
public static void restoreAlphaTest() {
|
||||
if (!alphaTestLocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
alphaTestLocked = false;
|
||||
|
||||
if (originalAlphaTestEnable) {
|
||||
GLStateManager.enableAlphaTest();
|
||||
} else {
|
||||
} else {
|
||||
GLStateManager.disableAlphaTest();
|
||||
}
|
||||
}
|
||||
|
||||
GLStateManager.glAlphaFunc(originalAlphaTest.getFunction().getGlId(), originalAlphaTest.getReference());
|
||||
}
|
||||
GLStateManager.glAlphaFunc(vanillaFunction, vanillaReference);
|
||||
overrideHeld = false;
|
||||
}
|
||||
|
||||
public static final VanillaBooleanLayer ENABLE_LAYER = new VanillaBooleanLayer() {
|
||||
@Override
|
||||
public boolean isOverrideHeld() {
|
||||
return overrideHeld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getVanilla() {
|
||||
return vanillaEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVanilla(boolean enabled) {
|
||||
vanillaEnabled = enabled;
|
||||
}
|
||||
};
|
||||
|
||||
public static final VanillaStateLayer<AlphaState> FUNC_LAYER = new VanillaStateLayer<>() {
|
||||
@Override
|
||||
public boolean isOverrideHeld() {
|
||||
return overrideHeld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readVanilla(AlphaState into) {
|
||||
into.setFunction(vanillaFunction);
|
||||
into.setReference(vanillaReference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeVanilla(AlphaState from) {
|
||||
vanillaFunction = from.getFunction();
|
||||
vanillaReference = from.getReference();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,126 +2,187 @@ package net.coderbot.iris.gl.blending;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import lombok.Getter;
|
||||
|
||||
public class BlendModeStorage {
|
||||
private static final int MAX_DRAW_BUFFERS = 8;
|
||||
private static final int MAX_DRAW_BUFFERS = 8;
|
||||
|
||||
private static boolean originalBlendEnable;
|
||||
private static final BlendState originalBlend = new BlendState();
|
||||
@Getter private static boolean blendLocked;
|
||||
@Getter private static boolean hasDeferredChanges;
|
||||
private static final int[] bufferOverrideIndices = new int[MAX_DRAW_BUFFERS];
|
||||
private static final boolean[] bufferOverrideIsDisable = new boolean[MAX_DRAW_BUFFERS];
|
||||
private static final BlendState[] bufferOverrideStates = new BlendState[MAX_DRAW_BUFFERS];
|
||||
private static int bufferOverrideCount;
|
||||
private static boolean vanillaBlendEnable;
|
||||
private static final BlendState vanillaBlend = new BlendState();
|
||||
|
||||
static {
|
||||
for (int i = 0; i < MAX_DRAW_BUFFERS; i++) {
|
||||
bufferOverrideStates[i] = new BlendState();
|
||||
}
|
||||
}
|
||||
@Getter
|
||||
private static boolean blendLocked;
|
||||
@Getter
|
||||
private static boolean overrideHeld;
|
||||
|
||||
private static boolean hasGlobalOverride;
|
||||
|
||||
private static boolean hasDeferredChanges;
|
||||
private static final int[] bufferOverrideIndices = new int[MAX_DRAW_BUFFERS];
|
||||
private static final boolean[] bufferOverrideIsDisable = new boolean[MAX_DRAW_BUFFERS];
|
||||
private static final BlendState[] bufferOverrideStates = new BlendState[MAX_DRAW_BUFFERS];
|
||||
private static int bufferOverrideCount;
|
||||
private static boolean bufferOverridesDirty;
|
||||
|
||||
static {
|
||||
for (int i = 0; i < MAX_DRAW_BUFFERS; i++) {
|
||||
bufferOverrideStates[i] = new BlendState();
|
||||
}
|
||||
}
|
||||
|
||||
public static void overrideBlend(BlendState override) {
|
||||
if (!blendLocked) {
|
||||
// Only save the previous state if the blend mode wasn't already locked
|
||||
originalBlendEnable = GLStateManager.getBlendMode().isEnabled();
|
||||
originalBlend.set(GLStateManager.getBlendState());
|
||||
}
|
||||
captureVanilla();
|
||||
|
||||
bufferOverrideCount = 0;
|
||||
hasDeferredChanges = false;
|
||||
blendLocked = false;
|
||||
bufferOverrideCount = 0;
|
||||
bufferOverridesDirty = false;
|
||||
hasDeferredChanges = false;
|
||||
overrideHeld = true;
|
||||
hasGlobalOverride = true;
|
||||
|
||||
if (override == null) {
|
||||
GLStateManager.disableBlend();
|
||||
} else {
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha());
|
||||
}
|
||||
blendLocked = false;
|
||||
try {
|
||||
if (override == null) {
|
||||
GLStateManager.disableBlend();
|
||||
} else {
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha());
|
||||
}
|
||||
} finally {
|
||||
blendLocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
blendLocked = true;
|
||||
}
|
||||
public static void overrideBufferBlend(int index, BlendState override) {
|
||||
captureVanilla();
|
||||
|
||||
public static void overrideBufferBlend(int index, BlendState override) {
|
||||
if (!blendLocked) {
|
||||
// Only save the previous state if the blend mode wasn't already locked
|
||||
originalBlendEnable = GLStateManager.getBlendMode().isEnabled();
|
||||
originalBlend.set(GLStateManager.getBlendState());
|
||||
}
|
||||
if (override == null) {
|
||||
RenderSystem.disableBufferBlend(index);
|
||||
} else {
|
||||
RenderSystem.enableBufferBlend(index);
|
||||
RenderSystem.blendFuncSeparatei(index, override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha());
|
||||
}
|
||||
|
||||
if (override == null) {
|
||||
RenderSystem.disableBufferBlend(index);
|
||||
} else {
|
||||
RenderSystem.enableBufferBlend(index);
|
||||
RenderSystem.blendFuncSeparatei(index, override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha());
|
||||
}
|
||||
if (bufferOverrideCount < MAX_DRAW_BUFFERS) {
|
||||
final int slot = bufferOverrideCount++;
|
||||
bufferOverrideIndices[slot] = index;
|
||||
bufferOverrideIsDisable[slot] = (override == null);
|
||||
if (override != null) {
|
||||
bufferOverrideStates[slot].setAll(override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha());
|
||||
}
|
||||
}
|
||||
overrideHeld = true;
|
||||
blendLocked = true;
|
||||
}
|
||||
|
||||
if (bufferOverrideCount < MAX_DRAW_BUFFERS) {
|
||||
final int slot = bufferOverrideCount++;
|
||||
bufferOverrideIndices[slot] = index;
|
||||
bufferOverrideIsDisable[slot] = (override == null);
|
||||
if (override != null) {
|
||||
bufferOverrideStates[slot].setAll(override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha());
|
||||
}
|
||||
}
|
||||
blendLocked = true;
|
||||
}
|
||||
private static void captureVanilla() {
|
||||
if (!overrideHeld) {
|
||||
vanillaBlendEnable = GLStateManager.getBlendMode().isEnabled();
|
||||
vanillaBlend.set(GLStateManager.getBlendState());
|
||||
}
|
||||
}
|
||||
|
||||
public static void deferBlendModeToggle(boolean enabled) {
|
||||
originalBlendEnable = enabled;
|
||||
hasDeferredChanges = true;
|
||||
}
|
||||
public static void deferBlendModeToggle(boolean enabled) {
|
||||
vanillaBlendEnable = enabled;
|
||||
hasDeferredChanges = true;
|
||||
}
|
||||
|
||||
public static void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
|
||||
originalBlend.setAll(srcRgb, dstRgb, srcAlpha, dstAlpha);
|
||||
hasDeferredChanges = true;
|
||||
}
|
||||
public static void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
|
||||
vanillaBlend.setAll(srcRgb, dstRgb, srcAlpha, dstAlpha);
|
||||
hasDeferredChanges = true;
|
||||
}
|
||||
|
||||
public static void flushDeferredBlend() {
|
||||
if (!blendLocked || !hasDeferredChanges) return;
|
||||
hasDeferredChanges = false;
|
||||
blendLocked = false;
|
||||
public static void flushDeferredBlend() {
|
||||
if (!overrideHeld) return;
|
||||
|
||||
if (originalBlendEnable) {
|
||||
GLStateManager.enableBlend();
|
||||
} else {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
GLStateManager.tryBlendFuncSeparate(
|
||||
originalBlend.getSrcRgb(), originalBlend.getDstRgb(),
|
||||
originalBlend.getSrcAlpha(), originalBlend.getDstAlpha());
|
||||
if (hasDeferredChanges && !hasGlobalOverride) {
|
||||
hasDeferredChanges = false;
|
||||
applyVanilla();
|
||||
bufferOverridesDirty = true;
|
||||
}
|
||||
|
||||
blendLocked = true;
|
||||
if (bufferOverridesDirty) {
|
||||
bufferOverridesDirty = false;
|
||||
reassertBufferOverrides();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < bufferOverrideCount; i++) {
|
||||
final int idx = bufferOverrideIndices[i];
|
||||
if (bufferOverrideIsDisable[i]) {
|
||||
RenderSystem.disableBufferBlend(idx);
|
||||
} else {
|
||||
final BlendState s = bufferOverrideStates[i];
|
||||
RenderSystem.enableBufferBlend(idx);
|
||||
RenderSystem.blendFuncSeparatei(idx, s.getSrcRgb(), s.getDstRgb(), s.getSrcAlpha(), s.getDstAlpha());
|
||||
}
|
||||
}
|
||||
}
|
||||
private static void reassertBufferOverrides() {
|
||||
for (int i = 0; i < bufferOverrideCount; i++) {
|
||||
final int idx = bufferOverrideIndices[i];
|
||||
if (bufferOverrideIsDisable[i]) {
|
||||
RenderSystem.disableBufferBlend(idx);
|
||||
} else {
|
||||
final BlendState s = bufferOverrideStates[i];
|
||||
RenderSystem.enableBufferBlend(idx);
|
||||
RenderSystem.blendFuncSeparatei(idx, s.getSrcRgb(), s.getDstRgb(), s.getSrcAlpha(), s.getDstAlpha());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void restoreBlend() {
|
||||
if (!blendLocked) {
|
||||
return;
|
||||
}
|
||||
public static void restoreBlend() {
|
||||
if (!overrideHeld) {
|
||||
return;
|
||||
}
|
||||
|
||||
blendLocked = false;
|
||||
hasDeferredChanges = false;
|
||||
bufferOverrideCount = 0;
|
||||
hasGlobalOverride = false;
|
||||
hasDeferredChanges = false;
|
||||
bufferOverrideCount = 0;
|
||||
bufferOverridesDirty = false;
|
||||
blendLocked = false;
|
||||
|
||||
if (originalBlendEnable) {
|
||||
GLStateManager.enableBlend();
|
||||
} else {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
applyVanilla();
|
||||
overrideHeld = false;
|
||||
}
|
||||
|
||||
GLStateManager.tryBlendFuncSeparate(originalBlend.getSrcRgb(), originalBlend.getDstRgb(), originalBlend.getSrcAlpha(), originalBlend.getDstAlpha());
|
||||
}
|
||||
private static void applyVanilla() {
|
||||
final boolean wasLocked = blendLocked;
|
||||
blendLocked = false;
|
||||
try {
|
||||
if (vanillaBlendEnable) {
|
||||
GLStateManager.enableBlend();
|
||||
} else {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
GLStateManager.tryBlendFuncSeparate(vanillaBlend.getSrcRgb(), vanillaBlend.getDstRgb(), vanillaBlend.getSrcAlpha(), vanillaBlend.getDstAlpha());
|
||||
} finally {
|
||||
blendLocked = wasLocked;
|
||||
}
|
||||
}
|
||||
|
||||
public static final VanillaBooleanLayer ENABLE_LAYER = new VanillaBooleanLayer() {
|
||||
@Override
|
||||
public boolean isOverrideHeld() {
|
||||
return overrideHeld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getVanilla() {
|
||||
return vanillaBlendEnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVanilla(boolean enabled) {
|
||||
deferBlendModeToggle(enabled);
|
||||
}
|
||||
};
|
||||
|
||||
public static final VanillaStateLayer<BlendState> FUNC_LAYER = new VanillaStateLayer<>() {
|
||||
@Override
|
||||
public boolean isOverrideHeld() {
|
||||
return overrideHeld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readVanilla(BlendState into) {
|
||||
into.setAll(vanillaBlend.getSrcRgb(), vanillaBlend.getDstRgb(), vanillaBlend.getSrcAlpha(), vanillaBlend.getDstAlpha());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeVanilla(BlendState from) {
|
||||
deferBlendFunc(from.getSrcRgb(), from.getDstRgb(), from.getSrcAlpha(), from.getDstAlpha());
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -3,64 +3,105 @@ package net.coderbot.iris.gl.blending;
|
||||
import com.gtnewhorizons.angelica.glsm.states.DepthState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import lombok.Getter;
|
||||
|
||||
public class DepthColorStorage {
|
||||
private static boolean originalDepthEnable;
|
||||
private static net.coderbot.iris.gl.blending.ColorMask originalColor;
|
||||
@Getter
|
||||
private static boolean vanillaDepthEnable;
|
||||
private static final ColorMask vanillaColor = new ColorMask();
|
||||
|
||||
@Getter
|
||||
private static boolean depthColorLocked;
|
||||
|
||||
private static final IntOpenHashSet ownedPrograms = new IntOpenHashSet();
|
||||
@Getter
|
||||
private static boolean overrideHeld;
|
||||
|
||||
public static void registerOwnedProgram(int programId) {
|
||||
ownedPrograms.add(programId);
|
||||
}
|
||||
private static final IntOpenHashSet ownedPrograms = new IntOpenHashSet();
|
||||
|
||||
public static void unregisterOwnedProgram(int programId) {
|
||||
ownedPrograms.remove(programId);
|
||||
}
|
||||
public static void registerOwnedProgram(int programId) {
|
||||
ownedPrograms.add(programId);
|
||||
}
|
||||
|
||||
public static boolean isOwnedProgram(int programId) {
|
||||
return ownedPrograms.contains(programId);
|
||||
}
|
||||
public static void unregisterOwnedProgram(int programId) {
|
||||
ownedPrograms.remove(programId);
|
||||
}
|
||||
|
||||
public static boolean isOwnedProgram(int programId) {
|
||||
return ownedPrograms.contains(programId);
|
||||
}
|
||||
|
||||
public static void disableDepthColor() {
|
||||
if (!depthColorLocked) {
|
||||
// Only save the previous state if the depth and color mask wasn't already locked
|
||||
final ColorMask colorMask = GLStateManager.getColorMask();
|
||||
final DepthState depthState = GLStateManager.getDepthState();
|
||||
if (!overrideHeld) {
|
||||
final ColorMask colorMask = GLStateManager.getColorMask();
|
||||
final DepthState depthState = GLStateManager.getDepthState();
|
||||
|
||||
originalDepthEnable = depthState.isEnabled();
|
||||
originalColor = new net.coderbot.iris.gl.blending.ColorMask(colorMask.red, colorMask.green, colorMask.blue, colorMask.alpha);
|
||||
}
|
||||
vanillaDepthEnable = depthState.isEnabled();
|
||||
vanillaColor.set(colorMask);
|
||||
}
|
||||
|
||||
depthColorLocked = false;
|
||||
overrideHeld = true;
|
||||
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glColorMask(false, false, false, false);
|
||||
depthColorLocked = false;
|
||||
try {
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glColorMask(false, false, false, false);
|
||||
} finally {
|
||||
depthColorLocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
depthColorLocked = true;
|
||||
}
|
||||
public static void deferDepthEnable(boolean enabled) {
|
||||
vanillaDepthEnable = enabled;
|
||||
}
|
||||
|
||||
public static void deferDepthEnable(boolean enabled) {
|
||||
originalDepthEnable = enabled;
|
||||
}
|
||||
public static void deferColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
|
||||
vanillaColor.setAll(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
public static void deferColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
|
||||
originalColor = new net.coderbot.iris.gl.blending.ColorMask(red, green, blue, alpha);
|
||||
}
|
||||
public static void unlockDepthColor() {
|
||||
if (!overrideHeld) {
|
||||
return;
|
||||
}
|
||||
|
||||
public static void unlockDepthColor() {
|
||||
if (!depthColorLocked) {
|
||||
return;
|
||||
}
|
||||
depthColorLocked = false;
|
||||
|
||||
depthColorLocked = false;
|
||||
GLStateManager.glDepthMask(vanillaDepthEnable);
|
||||
GLStateManager.glColorMask(vanillaColor.red, vanillaColor.green, vanillaColor.blue, vanillaColor.alpha);
|
||||
overrideHeld = false;
|
||||
}
|
||||
|
||||
GLStateManager.glDepthMask(originalDepthEnable);
|
||||
public static final VanillaStateLayer<DepthState> DEPTH_LAYER = new VanillaStateLayer<>() {
|
||||
@Override
|
||||
public boolean isOverrideHeld() {
|
||||
return overrideHeld;
|
||||
}
|
||||
|
||||
GLStateManager.glColorMask(originalColor.isRedMasked(), originalColor.isGreenMasked(), originalColor.isBlueMasked(), originalColor.isAlphaMasked());
|
||||
}
|
||||
@Override
|
||||
public void readVanilla(DepthState into) {
|
||||
into.setEnabled(vanillaDepthEnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeVanilla(DepthState from) {
|
||||
vanillaDepthEnable = from.isEnabled();
|
||||
}
|
||||
};
|
||||
|
||||
public static final VanillaStateLayer<ColorMask> COLOR_LAYER = new VanillaStateLayer<>() {
|
||||
@Override
|
||||
public boolean isOverrideHeld() {
|
||||
return overrideHeld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readVanilla(ColorMask into) {
|
||||
into.set(vanillaColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeVanilla(ColorMask from) {
|
||||
vanillaColor.set(from);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -36,7 +36,6 @@ public class ShaderPackSelectionList extends IrisGuiSlot {
|
||||
@Getter
|
||||
private ShaderPackEntry applied = null;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
private ShaderPackEntry selected = null;
|
||||
|
||||
@@ -203,6 +202,11 @@ public class ShaderPackSelectionList extends IrisGuiSlot {
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelected(ShaderPackEntry selected) {
|
||||
this.selected = selected;
|
||||
this.screen.refreshScreenSwitchButton();
|
||||
}
|
||||
|
||||
public void select(String name) {
|
||||
for (BaseEntry entry : this.entries) {
|
||||
if (entry instanceof ShaderPackEntry shaderPackEntry && name.equals(shaderPackEntry.getPackName())) {
|
||||
|
||||
@@ -39,8 +39,10 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
@@ -85,6 +87,8 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
private boolean guiHidden = false;
|
||||
private boolean dirty = false;
|
||||
|
||||
private final Map<String, Boolean> packOptionAvailability = new HashMap<>();
|
||||
|
||||
public ShaderPackScreen(GuiScreen parent) {
|
||||
this.title = I18n.format("options.iris.shaderPackSelection.title");
|
||||
|
||||
@@ -250,19 +254,7 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
I18n.format("options.iris.openShaderPackFolder"), button -> this.openShaderPackFolder()));
|
||||
|
||||
this.screenSwitchButton = new IrisButton(topCenter + 78, this.height - 51, 152, 20,
|
||||
I18n.format("options.iris.shaderPackList"), button -> {
|
||||
this.optionMenuOpen = !this.optionMenuOpen;
|
||||
|
||||
// UX: Apply changes before switching screens to avoid unintuitive behavior
|
||||
//
|
||||
// Not doing this leads to unintuitive behavior, since selecting a pack in the
|
||||
// list (but not applying) would open the settings for the previous pack, rather
|
||||
// than opening the settings for the selected (but not applied) pack.
|
||||
this.applyChanges();
|
||||
|
||||
initGui();
|
||||
}
|
||||
);
|
||||
I18n.format("options.iris.shaderPackList"), button -> this.toggleOptionMenu());
|
||||
this.buttonList.add(this.screenSwitchButton);
|
||||
|
||||
refreshScreenSwitchButton();
|
||||
@@ -299,6 +291,17 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updateScreen() {
|
||||
super.updateScreen();
|
||||
|
||||
if (this.notificationDialogTimer > 0) {
|
||||
this.notificationDialogTimer--;
|
||||
} else if (this.notificationDialog != null) {
|
||||
this.notificationDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse is clicked.
|
||||
*/
|
||||
@@ -346,10 +349,52 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches between the pack list and the pack settings, applying the pending pack first.
|
||||
* <p>
|
||||
* UX: Apply changes before switching screens to avoid unintuitive behavior. Not doing this leads to
|
||||
* unintuitive behavior, since selecting a pack in the list (but not applying) would open the settings
|
||||
* for the previous pack, rather than opening the settings for the selected (but not applied) pack.
|
||||
*/
|
||||
private void toggleOptionMenu() {
|
||||
final boolean opening = !this.optionMenuOpen;
|
||||
this.optionMenuOpen = opening;
|
||||
|
||||
this.applyChanges();
|
||||
|
||||
// The pack is only parsed by applyChanges, so this is the first point where an option-less
|
||||
// pack can be recognized. Stay on the list instead of opening an empty settings screen.
|
||||
if (opening && !currentPackHasOptions()) {
|
||||
this.optionMenuOpen = false;
|
||||
displayNotification(I18n.format("options.iris.shaderPackOptions.noOptions", Iris.getCurrentPackName()));
|
||||
}
|
||||
|
||||
initGui();
|
||||
}
|
||||
|
||||
private boolean canOpenOptionMenu() {
|
||||
if (!this.shaderPackList.getTopButtonRow().shadersEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ShaderPackEntry highlighted = this.shaderPackList.getSelected();
|
||||
final String name = highlighted != null ? highlighted.getPackName() : Iris.getCurrentPackName();
|
||||
|
||||
return name == null || this.packOptionAvailability.getOrDefault(name, Boolean.TRUE);
|
||||
}
|
||||
|
||||
private boolean currentPackHasOptions() {
|
||||
return Iris.getCurrentPack().map(p -> p.getMenuContainer().hasMenuElements()).orElse(false);
|
||||
}
|
||||
|
||||
public void refreshForChangedPack() {
|
||||
if (Iris.getCurrentPack().isPresent()) {
|
||||
final ShaderPack currentPack = Iris.getCurrentPack().get();
|
||||
|
||||
if (Iris.getCurrentPackName() != null) {
|
||||
this.packOptionAvailability.put(Iris.getCurrentPackName(), currentPack.getMenuContainer().hasMenuElements());
|
||||
}
|
||||
|
||||
this.navigation = new NavigationController();
|
||||
|
||||
if (this.shaderOptionList != null) {
|
||||
@@ -364,11 +409,10 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
}
|
||||
|
||||
public void refreshScreenSwitchButton() {
|
||||
if (this.screenSwitchButton != null) {
|
||||
if (this.screenSwitchButton != null && this.shaderPackList != null) {
|
||||
this.screenSwitchButton.displayString = optionMenuOpen ? I18n.format("options.iris.shaderPackList") : I18n.format("options.iris.shaderPackSettings");
|
||||
// Disable the settings switch when shaders are off or the selected pack exposes no options
|
||||
this.screenSwitchButton.enabled = optionMenuOpen || (shaderPackList.getTopButtonRow().shadersEnabled
|
||||
&& Iris.getCurrentPack().map(p -> !p.getMenuContainer().mainScreen.elements.isEmpty()).orElse(true));
|
||||
// Disable the settings switch when shaders are off or the highlighted pack exposes no options
|
||||
this.screenSwitchButton.enabled = optionMenuOpen || canOpenOptionMenu();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,12 +479,8 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
|
||||
return;
|
||||
}
|
||||
if (keyCode == Keyboard.KEY_TAB) {
|
||||
final boolean canOpenOptions = shaderPackList.getTopButtonRow().shadersEnabled
|
||||
&& Iris.getCurrentPack().map(p -> !p.getMenuContainer().mainScreen.elements.isEmpty()).orElse(false);
|
||||
if (this.optionMenuOpen || canOpenOptions) {
|
||||
this.optionMenuOpen = !this.optionMenuOpen;
|
||||
this.applyChanges();
|
||||
this.initGui();
|
||||
if (this.optionMenuOpen || canOpenOptionMenu()) {
|
||||
toggleOptionMenu();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user