Compare commits

...
21 Commits
Author SHA1 Message Date
Ruling-0 6a6b1d14e9 Depth peel translucent block entity batches and flush them before blended immediate draws 2026-11-08 13:09:00 -06:00
Ruling-0 3c6b7e0fe9 Add depth peel passes to GLSM draws 2026-11-08 13:03:07 -06:00
Ruling-0 6b9cb265f8 Let block entities opt out of the glow depth replay 2026-11-08 12:15:40 -06:00
Ruling-0 0d2e82fba5 Replay translucent-pass block entities into a glow depth so glows sort against translucent terrain 2026-11-08 12:15:40 -06:00
Ruling-0 c340a200a8 Composite terrain behind block entities under Fancy translucency 2026-11-08 12:15:37 -06:00
Ruling-0 358f495471 Draw particles, weather and clouds into composited layers 2026-11-08 12:15:37 -06:00
Ruling-0 785cd05a37 Draw translucent terrain and tile entities into composited layers 2026-11-08 12:15:37 -06:00
Ruling-0 433d08bc5f Add translucency layer targets and depth-sorted composite 2026-11-08 12:15:36 -06:00
Ruling-0 12840f8953 Add premultiplied layer blending to GLSMBlend 2026-11-08 12:15:35 -06:00
Ruling-0 3980599f7e Replace translucency sorting toggle with an Off/Fast/Fancy setting 2026-11-08 12:15:35 -06:00
Ruling-0 299e9d835d Draw translucent-pass block entities before translucent terrain 2026-11-08 12:15:32 -06:00
Ruling-0 30f0835a21 Sort block entities back to front before dispatch 2026-11-08 12:15:29 -06:00
Ruling-0 3717d2d46e Add translucent test TESR blocks and flyby scene 2026-11-08 12:15:29 -06:00
Jason Mitchell d69fd20a8b Tag profiler captures with the Angelica version (#2351)
* RenderDoc, Tracy, async-profiler, spark
2026-11-07 22:49:01 -08:00
Jason Mitchell 9493d630a0 Emulate clip distances on devices without them (#2348) 2026-11-07 13:42:32 -08:00
Jason Mitchell aee989d23b Fix flat item icons on drawers and barrels with shader packs (#2347) 2026-11-07 12:59:19 -08:00
Jason Mitchell 3876218405 Fix GLES crashing at startup and rendering black (#2346) 2026-11-06 21:30:30 -08:00
Armisael5andEclipse-Sol a6d014a9eb Fix falling block texture (#2336)
Co-authored-by: Eclipse-Sol <[email protected]>
2026-11-06 15:25:15 -05:00
Eldrinn Elantey 52444510d0 Move the tilde glyph down in the modern ascii font sheet (#2343) 2026-11-06 09:05:22 -08:00
DarkXFast 8840c2109f Fix replayed "glDrawElements" display lists losing their normals and texcoords (#2328) 2026-11-02 08:40:12 -08:00
Alexdoru e64a4e7214 Fix world client leak in PositionEntries$PlayerPosition (#2327) 2026-11-02 08:35:41 -08:00
157 changed files with 6102 additions and 761 deletions

No files matched your search

+5 -2
View File
@@ -26,7 +26,7 @@ jobs:
- name: Install a software Vulkan driver
run: |
sudo apt-get update -y
sudo apt-get install -y xvfb mesa-vulkan-drivers
sudo apt-get install -y xvfb mesa-vulkan-drivers libgl1-mesa-dri
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
@@ -36,7 +36,7 @@ jobs:
25
- uses: gradle/actions/setup-gradle@v5
- name: Run SDL-GPU tests
run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest
run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest :lwjgl3-backend:glesTest
- name: Check LWJGL did not extract natives to the shared temp dir
run: test ! -e "/tmp/lwjgl_$USER"
- name: Attach SDL-GPU test results
@@ -48,3 +48,6 @@ jobs:
sdl-gpu/build/test-results/
sdl-gpu/build/reports/tests/
sdl-gpu/hs_err_pid*.log
lwjgl3-backend/build/test-results/
lwjgl3-backend/build/reports/tests/
lwjgl3-backend/hs_err_pid*.log
+17
View File
@@ -67,6 +67,22 @@ artifacts {
}
}
// LWJGL2-API stubs for test JVMs that strip the lwjgl-2 jars (glsmSdlTest, glesTest).
val lwjgl2StubsSourceSet = sourceSets.create("lwjgl2Stubs") {
java.srcDir("src/lwjgl2Stubs/java")
}
val lwjgl2Stubs by configurations.creating {
isCanBeConsumed = true
isCanBeResolved = false
}
artifacts {
add(lwjgl2Stubs.name, lwjgl2StubsSourceSet.java.classesDirectory) {
builtBy(tasks.named("lwjgl2StubsClasses"))
}
}
repositories {
maven {
name = "Mojang"
@@ -111,6 +127,7 @@ val spirvTestRuntime by configurations.creating {
dependencies {
// MC compile stubs
compileOnly(sourceSets["stubs"].output)
"lwjgl2StubsCompileOnly"(libs.lwjgl3.opengl)
// LWJGL
compileOnly(libs.lwjgl2)
@@ -1,5 +1,7 @@
package org.lwjgl.opengl;
import java.lang.reflect.Field;
public class ContextCapabilities {
public boolean OpenGL30 = true;
@@ -21,11 +23,28 @@ public class ContextCapabilities {
public boolean GL_ARB_sampler_objects = true;
public boolean GL_ARB_shader_image_load_store = true;
public boolean GL_ARB_shader_storage_buffer_object = true;
public boolean GL_ARB_stencil_texturing = false;
public boolean GL_ARB_tessellation_shader = false;
public boolean GL_ARB_texture_storage = false;
public boolean GL_EXT_direct_state_access = false;
public boolean GL_EXT_gpu_shader4 = true;
public boolean GL_EXT_shader_image_load_store = true;
public boolean GL_NVX_gpu_memory_info = false;
public ContextCapabilities() {
if ("lwjgl3".equals(System.getProperty("glsm.stubs.capabilities"))) copyFromCurrentContext();
}
private void copyFromCurrentContext() {
final GLCapabilities caps = GL.getCapabilities();
for (Field field : ContextCapabilities.class.getFields()) {
try {
field.setBoolean(this, GLCapabilities.class.getField(field.getName()).getBoolean(caps));
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("LWJGL3 GLCapabilities has no " + field.getName(), e);
}
}
}
}
@@ -0,0 +1,13 @@
package org.lwjgl.opengl;
public final class GLContext {
private static ContextCapabilities capabilities;
private GLContext() {}
public static ContextCapabilities getCapabilities() {
if (capabilities == null) capabilities = new ContextCapabilities();
return capabilities;
}
}
@@ -21,6 +21,7 @@ public final class SystemProperties {
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
public static final boolean EMULATE_CLIP_DISTANCES = Boolean.getBoolean("angelica.emulateClipDistances");
// Redirector
public static final UnmappedGLMode UNMAPPED_GL = parseEnum("angelica.unmappedGL", UnmappedGLMode.WARN, UnmappedGLMode.WARN, UnmappedGLMode.class);
@@ -26,12 +26,20 @@ public final class CaptureGate {
public static final boolean TOOL_ATTACHED = detectCaptureTool();
public static boolean markersThisFrame = true;
public static boolean tracyConnected;
private static boolean toolAttached = TOOL_ATTACHED;
private CaptureGate() {}
public static void refresh() {
tracyConnected = Tracy.isConnected();
markersThisFrame = gateValue(FORCED, TOOL_ATTACHED, tracyConnected);
markersThisFrame = gateValue(FORCED, toolAttached, tracyConnected);
}
static void probeGlTool() {
if (RenderSystem.hasExtension("GL_EXT_debug_tool")) {
toolAttached = true;
LOGGER.info("Capture tool detected via GL_EXT_debug_tool; instrumentation enabled");
}
}
static boolean gateValue(boolean forced, boolean toolAttached, boolean tracyConnected) {
@@ -39,7 +47,7 @@ public final class CaptureGate {
}
public static boolean enabledAtStartup() {
return FORCED || TOOL_ATTACHED;
return FORCED || toolAttached;
}
private static boolean detectCaptureTool() {
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
public final class ClipPlaneBooleanState extends BooleanStateStack {
@@ -13,12 +14,22 @@ public final class ClipPlaneBooleanState extends BooleanStateStack {
this.index = index;
}
@Override
protected boolean forwardsToBackend() {
return !RenderSystem.isClipDistanceEmulated();
}
@Override
protected void onEnabledChanged() {
final int before = owner.clipPlaneEnabledMask;
if (isEnabled()) {
owner.clipPlaneEnabledMask |= (1 << index);
} else {
owner.clipPlaneEnabledMask &= ~(1 << index);
}
owner.clipPlaneGeneration++;
if ((before == 0) != (owner.clipPlaneEnabledMask == 0) && RenderSystem.isClipDistanceEmulated() && GLSMHooks.CLIP_PLANES_IN_USE.hasListeners()) {
GLSMHooks.CLIP_PLANES_IN_USE.post(GLSMHooks.clipPlanesInUseEvent);
}
}
}
@@ -249,7 +249,7 @@ public class CompatUniformManager {
final int cpGen = glCtx.clipPlaneGeneration;
if (cpGen != pu.lastClipPlaneGen) {
pu.lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
uploadClipPlanes(locs, glCtx.clipPlaneEnabledMask);
}
}
@@ -468,16 +468,14 @@ public class CompatUniformManager {
RENDER_BACKEND.uniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
}
private static void uploadClipPlanes(int[] locs) {
private static void uploadClipPlanes(int[] locs, int enabledMask) {
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLSMRasterizer.anyClipPlaneEnabled() ? 1 : 0);
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], enabledMask != 0 ? 1 : 0);
}
if (locs[LOC_CLIP_PLANES] != -1) {
final ClipPlaneState cps = GLSMRasterizer.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
cps.putEffectivePlanes(enabledMask, clipPlaneBuf);
clipPlaneBuf.flip();
RENDER_BACKEND.uniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
}
@@ -0,0 +1,132 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.stacks.ColorMaskStack;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/// A draw between [#beginTracking] and [#endTracking] is left to the caller's replay instead of repeated. A replay
/// ignores the depth mask, which a previous renderer may leak differently into the pass and the replay, and repeats
/// set GL state through the backend, never the GLSM cache.
public final class DepthEcho {
private static int framebuffer;
private static int source;
private static boolean echoed;
private static boolean tracking;
private static boolean hit;
private static int immediate;
private static boolean replaying;
private static boolean replayDepthWrite;
private DepthEcho() {}
public static void arm(int framebuffer, int source) {
DepthEcho.framebuffer = framebuffer;
DepthEcho.source = source;
}
public static void disarm() {
framebuffer = 0;
tracking = false;
hit = false;
endReplay();
}
public static boolean armed() {
return framebuffer != 0;
}
public static void beginTracking() {
tracking = framebuffer != 0;
hit = false;
}
public static boolean endTracking() {
tracking = false;
final boolean was = hit;
hit = false;
return was;
}
public static void beginImmediate() {
immediate++;
}
public static void endImmediate() {
immediate--;
}
public static void beginReplay() {
replaying = true;
}
public static void endReplay() {
replaying = false;
restoreReplayDepthMask();
}
public static boolean replaying() {
return replaying;
}
public static boolean takeEchoed() {
final boolean was = echoed;
echoed = false;
return was;
}
public static boolean skipsCall() {
return replaySkips() && !DisplayListManager.isRecording();
}
public static boolean skipsDraw() {
if (!replaying) return false;
if (replaySkips()) return true;
if (!GLSMDepthStencilAlpha.getDepthState().isEnabled()) {
RENDER_BACKEND.depthMask(true);
replayDepthWrite = true;
}
return false;
}
private static boolean replaySkips() {
return replaying && !(blendsVisibly() && GLSMFramebuffer.getDrawFramebuffer() == framebuffer);
}
public static boolean begin() {
if (replayDepthWrite) {
restoreReplayDepthMask();
return false;
}
if (framebuffer == 0 || GLSMDepthStencilAlpha.getDepthState().isEnabled() || !blendsVisibly() || GLSMFramebuffer.getDrawFramebuffer() != source) return false;
echoed = true;
if (tracking && immediate == 0 && !BatchStateGuard.isSuspended()) {
hit = true;
return false;
}
RENDER_BACKEND.bindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, framebuffer);
RENDER_BACKEND.depthMask(true);
return true;
}
public static void end() {
RENDER_BACKEND.depthMask(false);
RENDER_BACKEND.bindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, source);
}
private static void restoreReplayDepthMask() {
if (!replayDepthWrite) return;
replayDepthWrite = false;
RENDER_BACKEND.depthMask(GLSMDepthStencilAlpha.getDepthState().isEnabled());
}
private static boolean blendsVisibly() {
if (!GLSMDepthStencilAlpha.getDepthTest().isEnabled() || !GLSMBlend.getBlendMode().isEnabled()) return false;
if (GLSMDepthStencilAlpha.getDepthState().getFunc() == GL11.GL_EQUAL) return false;
final ColorMaskStack mask = GLSMBlend.getColorMask();
return mask.red || mask.green || mask.blue || mask.alpha;
}
}
@@ -0,0 +1,127 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ColorMaskStack;
import com.gtnewhorizons.angelica.glsm.stacks.DepthStateStack;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/// Per-draw state for depth peeling. Color passes skip the depth test but keep the draw's depth writes, so the target
/// ends with the nearest peeled depth and the rest pass's own test keeps only what is in front of it. Depth and rest
/// passes discard fragments that would not change the target under the draw's blend, so transparent texels do not take
/// a peel. Draws with an application program cannot run [#GLSL], so they skip the peels and draw unsorted afterwards.
public final class DepthPeel {
public static final int TEXTURE_UNIT = 15;
public static final int SCENE_TEXTURE_UNIT = 14;
private static final int PASS_DEPTH = 0;
private static final int PASS_COLOR = 1;
private static final int PASS_REST = 2;
private static final int UNBOUNDED = 4;
private static final int COVER_ALPHA = 0;
private static final int COVER_RGB = 8;
private static final int COVER_RGB_ALPHA = 16;
private static final int COVER_ALL = 24;
public static final String GLSL = "#define PEEL_PASS_COLOR " + PASS_COLOR + "\n"
+ "#define PEEL_UNBOUNDED " + UNBOUNDED + "\n"
+ "#define PEEL_COVER_MASK " + COVER_ALL + "\n"
+ "#define PEEL_COVER_RGB " + COVER_RGB + "\n"
+ "#define PEEL_COVER_RGB_ALPHA " + COVER_RGB_ALPHA + "\n"
+ "#define PEEL_COVER_ALL " + COVER_ALL + "\n"
+ "#define PEEL_SCENE_TOLERANCE 6.0e-8\n"
+ "#define PEEL_WINDOW 2.4e-7\n"
+ "#define PEEL_MIN_COVERAGE 0.004\n"
+ """
uniform sampler2D u_PeelDepth;
uniform sampler2D u_PeelScene;
uniform int u_PeelMode;
void peelTest(vec4 c) {
ivec2 texel = ivec2(gl_FragCoord.xy);
float z = gl_FragCoord.z;
if (z > texelFetch(u_PeelScene, texel, 0).r + PEEL_SCENE_TOLERANCE) discard;
float bound = texelFetch(u_PeelDepth, texel, 0).r;
if ((u_PeelMode & 3) == PEEL_PASS_COLOR) {
if (z > bound || z < bound - PEEL_WINDOW) discard;
return;
}
if ((u_PeelMode & PEEL_UNBOUNDED) == 0 && z >= bound - PEEL_WINDOW) discard;
int cover = u_PeelMode & PEEL_COVER_MASK;
float rgb = max(c.r, max(c.g, c.b));
float coverage = cover == PEEL_COVER_ALL ? 1.0 : cover == PEEL_COVER_RGB ? rgb : cover == PEEL_COVER_RGB_ALPHA ? rgb * c.a : c.a;
if (coverage < PEEL_MIN_COVERAGE) discard;
}
""";
public enum Pass { OFF, DEPTH, COLOR, REST, APPLICATION }
private static Pass pass = Pass.OFF;
private static boolean unbounded;
private static boolean overridden;
private static boolean skippedApplicationDraws;
private DepthPeel() {}
public static void setPass(Pass next, boolean unbounded) {
if (pass == Pass.OFF) skippedApplicationDraws = false;
pass = next;
DepthPeel.unbounded = unbounded;
GLSMConfig.depthPeel = next == Pass.DEPTH || next == Pass.COLOR || next == Pass.REST;
}
public static boolean active() {
return pass != Pass.OFF;
}
public static boolean skippedApplicationDraws() {
return skippedApplicationDraws;
}
public static int shaderMode() {
return switch (pass) {
case DEPTH -> PASS_DEPTH | coverage() | (unbounded ? UNBOUNDED : 0);
case COLOR -> PASS_COLOR;
default -> PASS_REST | coverage();
};
}
private static int coverage() {
if (!GLSMBlend.getBlendMode().isEnabled()) return COVER_ALL;
final BlendStateStack blend = GLStateManager.ctx().blendState;
if (blend.getDstRgb() != GL11.GL_ONE) return COVER_ALPHA;
return blend.getSrcRgb() == GL11.GL_SRC_ALPHA ? COVER_RGB_ALPHA : COVER_RGB;
}
public static boolean begin() {
if (pass == Pass.OFF) return true;
final boolean fixedFunction = GLSMPrograms.getActiveProgram() == 0;
if (pass == Pass.APPLICATION) return !fixedFunction;
if (!fixedFunction) {
skippedApplicationDraws = true;
return false;
}
if (pass == Pass.DEPTH) {
RENDER_BACKEND.colorMask(false, false, false, false);
RENDER_BACKEND.depthMask(true);
RENDER_BACKEND.depthFunc(GL11.GL_GREATER);
overridden = true;
} else if (pass == Pass.COLOR) {
RENDER_BACKEND.depthFunc(GL11.GL_ALWAYS);
overridden = true;
}
return true;
}
public static void end() {
if (!overridden) return;
overridden = false;
final ColorMaskStack mask = GLSMBlend.getColorMask();
final DepthStateStack depth = GLSMDepthStencilAlpha.getDepthState();
RENDER_BACKEND.colorMask(mask.red, mask.green, mask.blue, mask.alpha);
RENDER_BACKEND.depthMask(depth.isEnabled());
RENDER_BACKEND.depthFunc(depth.getFunc());
}
}
@@ -11,11 +11,8 @@ public final class GLESCaps {
private GLESCaps() {}
public static boolean isCapAllowed(int cap, boolean isGLES, boolean hasClipCullDistance) {
public static boolean isCapAllowed(int cap, boolean isGLES) {
if (!isGLES) return true;
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + GLStateManager.MAX_CLIP_PLANES) {
return hasClipCullDistance;
}
return switch (cap) {
case GL11.GL_LINE_SMOOTH,
GL11.GL_POLYGON_SMOOTH,
@@ -149,19 +149,46 @@ public final class GLSMBlend {
}
static void issueBlendFunc(BlendStateStack blend) {
if (GLSMConfig.hudCacheOverride) {
RENDER_BACKEND.blendFuncSeparate(blend.getSrcRgb(), blend.getDstRgb(), GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
} else {
RENDER_BACKEND.blendFuncSeparate(blend.getSrcRgb(), blend.getDstRgb(), blend.getSrcAlpha(), blend.getDstAlpha());
}
RENDER_BACKEND.blendFuncSeparate(blend.getSrcRgb(), blend.getDstRgb(), emittedSrcAlpha(blend), emittedDstAlpha(blend));
}
private static boolean addsWithoutCoverage(BlendStateStack blend) {
return blend.getDstRgb() == GL11.GL_ONE;
}
private static int emittedSrcAlpha(BlendStateStack blend) {
if (GLSMConfig.layerBlending) return addsWithoutCoverage(blend) ? GL11.GL_ZERO : GL11.GL_ONE;
return GLSMConfig.hudCacheOverride ? GL11.GL_ONE : blend.getSrcAlpha();
}
private static int emittedDstAlpha(BlendStateStack blend) {
if (GLSMConfig.layerBlending) return addsWithoutCoverage(blend) ? GL11.GL_ONE : GL11.GL_ONE_MINUS_SRC_ALPHA;
return GLSMConfig.hudCacheOverride ? GL11.GL_ONE_MINUS_SRC_ALPHA : blend.getDstAlpha();
}
public static void setHudCacheOverride(boolean enabled) {
if (GLSMConfig.hudCacheOverride == enabled) return;
GLSMConfig.hudCacheOverride = enabled;
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isOverrideHeld()) return;
reissueBlendFunc();
}
public static void setLayerBlending(boolean enabled) {
if (GLSMConfig.layerBlending == enabled) return;
GLSMConfig.layerBlending = enabled;
reissueBlendFunc();
}
public static boolean isLayerBlending() {
return GLSMConfig.layerBlending;
}
private static void reissueBlendFunc() {
final BlendStateStack blend = ctx().blendState;
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null && bh.isOverrideHeld()) {
blend.setFuncUnknownState();
return;
}
issueBlendFunc(blend);
blend.clearFuncUnknownState();
}
@@ -226,11 +253,10 @@ public final class GLSMBlend {
glCtx.blendState.clearFuncUnknownState();
issueBlendFunc(glCtx.blendState);
if (GLSMHooks.BLEND_FUNC_CHANGE.hasListeners()) {
final boolean hudCache = GLSMConfig.hudCacheOverride;
GLSMHooks.blendFuncChangeEvent.srcRgb = srcRgb;
GLSMHooks.blendFuncChangeEvent.dstRgb = dstRgb;
GLSMHooks.blendFuncChangeEvent.srcAlpha = hudCache ? GL11.GL_ONE : srcAlpha;
GLSMHooks.blendFuncChangeEvent.dstAlpha = hudCache ? GL11.GL_ONE_MINUS_SRC_ALPHA : dstAlpha;
GLSMHooks.blendFuncChangeEvent.srcAlpha = emittedSrcAlpha(glCtx.blendState);
GLSMHooks.blendFuncChangeEvent.dstAlpha = emittedDstAlpha(glCtx.blendState);
GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent);
}
}
@@ -6,6 +6,7 @@ import com.gtnewhorizons.angelica.glsm.DisplayListManager.RecordMode;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
@@ -76,6 +77,7 @@ public final class GLSMDraws {
ImmediateModeRecorder.begin(mode);
return;
}
BatchStateGuard.beforeDraw();
ImmediateModeRecorder.beginLive(mode);
}
@@ -141,6 +143,7 @@ public final class GLSMDraws {
}
public static void glDrawElements(int mode, ByteBuffer indices) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
drawCalls++;
CommandRecorder savedRecorder = null;
@@ -174,6 +177,7 @@ public final class GLSMDraws {
}
public static void glDrawElements(int mode, IntBuffer indices) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
drawCalls++;
CommandRecorder savedRecorder = null;
@@ -207,6 +211,7 @@ public final class GLSMDraws {
}
public static void glDrawElements(int mode, ShortBuffer indices) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
drawCalls++;
CommandRecorder savedRecorder = null;
@@ -240,6 +245,7 @@ public final class GLSMDraws {
}
public static void glDrawElements(int mode, int count, int type, ByteBuffer indices) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
drawCalls++;
CommandRecorder savedRecorder = null;
@@ -273,6 +279,7 @@ public final class GLSMDraws {
}
public static void glDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
final GLContextState glCtx = ctx();
drawCalls++;
@@ -329,12 +336,14 @@ public final class GLSMDraws {
}
public static void glDrawElementsBaseVertex(int mode, int count, int type, long indices, int basevertex) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
drawCalls++;
RENDER_BACKEND.drawElementsBaseVertex(mode, count, type, indices, basevertex);
}
public static void glDrawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
drawCalls++;
final GLContextState glCtx = ctx();
@@ -353,6 +362,7 @@ public final class GLSMDraws {
}
public static void glDrawArraysInstanced(int mode, int first, int count, int primcount) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
drawCalls++;
final GLContextState glCtx = ctx();
@@ -377,6 +387,7 @@ public final class GLSMDraws {
}
public static void glDrawRangeElements(int mode, int start, int end, int count, int type, long indices) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
final GLContextState glCtx = ctx();
final boolean locked = acquireDrawLock();
@@ -393,11 +404,13 @@ public final class GLSMDraws {
}
public static void glDrawRangeElementsBaseVertex(int mode, int start, int end, int count, int type, long indices, int baseVertex) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
RENDER_BACKEND.drawRangeElementsBaseVertex(mode, start, end, count, type, indices, baseVertex);
}
public static void glMultiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
final GLContextState glCtx = ctx();
final boolean locked = acquireDrawLock();
@@ -423,6 +436,7 @@ public final class GLSMDraws {
}
public static void glDrawArrays(int mode, int first, int count) {
if (DepthEcho.skipsCall()) return;
rejectExitCapture();
drawCalls++;
CommandRecorder savedRecorder = null;
@@ -521,6 +535,7 @@ public final class GLSMDraws {
public static void glCallList(int list) {
rejectExitCapture();
if (!DisplayListManager.isRecording()) BatchStateGuard.beforeDraw();
if (!GLDebug.isActive()) {
DisplayListManager.glCallList(list);
return;
@@ -30,7 +30,6 @@ public final class GLSMEnable {
// Handle clip planes dynamically (supports up to MAX_CLIP_PLANES)
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + MAX_CLIP_PLANES) {
glCtx.clipPlaneStates[cap - GL11.GL_CLIP_PLANE0].enable();
glCtx.clipPlaneGeneration++;
return;
}
@@ -119,7 +118,6 @@ public final class GLSMEnable {
// Handle clip planes dynamically (supports up to MAX_CLIP_PLANES)
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + MAX_CLIP_PLANES) {
glCtx.clipPlaneStates[cap - GL11.GL_CLIP_PLANE0].disable();
glCtx.clipPlaneGeneration++;
return;
}
@@ -64,6 +64,7 @@ public final class GLSMFramebuffer {
private static long firstClearBlockNanos;
public static void glClear(int mask) {
if (DepthEcho.replaying() && !DisplayListManager.isRecording()) return;
rejectExitCapture();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -315,6 +316,7 @@ public final class GLSMFramebuffer {
}
public static void glBlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
if (DepthEcho.replaying() && !DisplayListManager.isRecording()) return;
rejectExitCapture();
RENDER_BACKEND.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
@@ -503,8 +503,8 @@ public final class GLSMQueries {
return RENDER_BACKEND.genQueries();
}
public static void glDeleteQueries(int id) { rejectExitCapture(); RENDER_BACKEND.deleteQueries(id); }
public static void glBeginQuery(int target, int id) { rejectExitCapture(); RENDER_BACKEND.beginQuery(target, id); }
public static void glEndQuery(int target) { rejectExitCapture(); RENDER_BACKEND.endQuery(target); }
public static void glBeginQuery(int target, int id) { rejectExitCapture(); if (!DepthEcho.replaying()) RENDER_BACKEND.beginQuery(target, id); }
public static void glEndQuery(int target) { rejectExitCapture(); if (!DepthEcho.replaying()) RENDER_BACKEND.endQuery(target); }
public static void glGetQueryObjectui(int id, int pname, IntBuffer params) { rejectExitCapture(); RENDER_BACKEND.getQueryObjectui(id, pname, params); }
public static int glGetQueryObjecti(int id, int pname) { rejectExitCapture(); return RENDER_BACKEND.getQueryObjecti(id, pname); }
public static void glQueryCounter(int id, int target) { rejectExitCapture(); RENDER_BACKEND.queryCounter(id, target); }
@@ -88,8 +88,11 @@ public final class GLSMRasterizer {
static void probePointSizeRange(GLContextState glCtx) {
final FloatBuffer range = glCtx.queryScratch;
range.clear();
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
final float smooth = range.get(1);
float smooth = 0.0f;
if (!RenderSystem.isGLES()) {
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
smooth = range.get(1);
}
RENDER_BACKEND.getFloat(GL12.GL_ALIASED_POINT_SIZE_RANGE, range);
maxPointSize = Math.max(smooth, range.get(1));
}
@@ -506,6 +509,14 @@ public final class GLSMRasterizer {
return glCtx.clipPlaneEnabledMask != 0;
}
public static boolean clipEmulationActive() {
return RenderSystem.isClipDistanceEmulated() && anyClipPlaneEnabled();
}
public static boolean clipEmulationActive(GLContextState glCtx) {
return RenderSystem.isClipDistanceEmulated() && anyClipPlaneEnabled(glCtx);
}
public static void glSampleCoverage(float value, boolean invert) {
if (DisplayListManager.isRecording()) {
throw DisplayListManager.unsupportedInList("glSampleCoverage");
@@ -458,6 +458,7 @@ public class GLStateManager {
static void init(Runnable initCallback) {
final GLContextState glCtx = ctx();
RenderSystem.initRenderer();
CaptureGate.probeGlTool();
GLSMFramebuffer.queryDefaultDrawBuffer();
@@ -63,7 +63,7 @@ public class RenderSystem {
private static volatile boolean isGLES;
private static volatile int glesVersion;
private static volatile boolean glesDetected;
private static volatile boolean hasClipCullDistance;
private static boolean clipDistanceEmulated;
private static volatile boolean isLTW;
private static volatile boolean ltwDetected;
@@ -132,7 +132,12 @@ public class RenderSystem {
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
supportsStencilTexturing = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_stencil_texturing;
supportsTextureStorage = isGLES ? glesVersion >= 300 : GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_texture_storage;
hasClipCullDistance = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
final boolean esClipExt = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
final int maxClipDistances = esClipExt ? RENDER_BACKEND.getInteger(GL30.GL_MAX_CLIP_DISTANCES) : 0;
clipDistanceEmulated = SystemProperties.EMULATE_CLIP_DISTANCES || maxClipDistances < GLStateManager.MAX_CLIP_PLANES;
if (clipDistanceEmulated) {
GLStateManager.LOGGER.info("Clip distances emulated (GL_MAX_CLIP_DISTANCES={}, forced={})", maxClipDistances, SystemProperties.EMULATE_CLIP_DISTANCES);
}
// Cache maximum image units
if (supportsImageLoadStore) {
@@ -434,11 +439,11 @@ public class RenderSystem {
return supportsImageLoadStore;
}
public static boolean hasClipCullDistance() {
return hasClipCullDistance;
public static boolean isClipDistanceEmulated() {
return clipDistanceEmulated;
}
private static boolean hasExtension(String name) {
static boolean hasExtension(String name) {
final int count = GLSMQueries.glGetInteger(GL30.GL_NUM_EXTENSIONS);
for (int i = 0; i < count; i++) {
if (name.equals(GLSMQueries.glGetStringi(GL11.GL_EXTENSIONS, i))) return true;
@@ -6,6 +6,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import java.util.Arrays;
public final class StateSet {
public static final int R_DEPTH = 1;
@@ -79,28 +81,47 @@ public final class StateSet {
public static final StateSet CUTOUT = buildCutout();
public static final StateSet MASKS = buildMasks();
public static final StateSet CULL = buildCull();
public static final StateSet BLOCK_ENTITY_PASS = buildBlockEntityPass();
private static CowStateStack<?>[] batchMembers() {
return new CowStateStack<?>[] {
GLSMBlend.getBlendState(), GLSMDepthStencilAlpha.getAlphaState(), GLSMDepthStencilAlpha.getDepthState(),
GLSMBlend.getColorMask(), GLSMRasterizer.getPolygonState(),
GLSMTextures.getTextures().getTextureUnitBindings(0), GLSMTextures.getTextures().getTextureUnitBindings(1),
GLSMTextures.getActiveTextureUnitStack(),
GLSMBlend.getBlendMode(), GLSMDepthStencilAlpha.getAlphaTest(), GLSMDepthStencilAlpha.getDepthTest(),
GLSMLighting.getLightingState(), GLSMRasterizer.getCullState(), GLSMRasterizer.getPolygonOffsetFillState(),
GLSMTextures.getTextures().getTextureUnitStates(0), GLSMTextures.getTextures().getTextureUnitStates(1),
GLSMLighting.getLightStates()[0], GLSMLighting.getLightStates()[1],
GLSMLighting.getLightDataStates()[0], GLSMLighting.getLightDataStates()[1],
GLSMLighting.getColorMaterial(), GLSMLighting.getColorMaterialFace(), GLSMLighting.getColorMaterialParameter(),
GLSMLighting.getLightModel(), GLSMLighting.getFrontMaterial(), GLSMLighting.getBackMaterial(),
GLSMLighting.getNormalizeState(), GLSMLighting.getRescaleNormalState()
};
}
private static StateSet buildBatch() {
return new StateSet(0,
ids(
GLSMBlend.getBlendState(), GLSMDepthStencilAlpha.getAlphaState(), GLSMDepthStencilAlpha.getDepthState(),
GLSMBlend.getColorMask(), GLSMRasterizer.getPolygonState(),
GLSMTextures.getTextures().getTextureUnitBindings(0), GLSMTextures.getTextures().getTextureUnitBindings(1),
GLSMTextures.getActiveTextureUnitStack(),
GLSMBlend.getBlendMode(), GLSMDepthStencilAlpha.getAlphaTest(), GLSMDepthStencilAlpha.getDepthTest(),
GLSMLighting.getLightingState(), GLSMRasterizer.getCullState(), GLSMRasterizer.getPolygonOffsetFillState(),
GLSMTextures.getTextures().getTextureUnitStates(0), GLSMTextures.getTextures().getTextureUnitStates(1),
GLSMLighting.getLightStates()[0], GLSMLighting.getLightStates()[1],
GLSMLighting.getLightDataStates()[0], GLSMLighting.getLightDataStates()[1],
GLSMLighting.getColorMaterial(), GLSMLighting.getColorMaterialFace(), GLSMLighting.getColorMaterialParameter(),
GLSMLighting.getLightModel(), GLSMLighting.getFrontMaterial(), GLSMLighting.getBackMaterial(),
GLSMLighting.getNormalizeState(), GLSMLighting.getRescaleNormalState()
),
ids(batchMembers()),
R_BLEND | R_DEPTH | R_COLOR_MASK | R_POLYGON | R_TEXTURE | R_ACTIVE_UNIT,
B_FRAG_COLORBUF | B_LIGHTING,
true);
}
private static StateSet buildBlockEntityPass() {
final CowStateStack<?>[] batch = batchMembers();
final CowStateStack<?>[] all = Arrays.copyOf(batch, batch.length + 4);
all[batch.length] = GLSMCurrentAttribs.getColor();
all[batch.length + 1] = GLSMFog.getFogState();
all[batch.length + 2] = GLSMFog.getFogMode();
all[batch.length + 3] = GLSMRasterizer.getShadeModelState();
return new StateSet(0,
ids(all),
R_BLEND | R_DEPTH | R_COLOR_MASK | R_POLYGON | R_TEXTURE | R_ACTIVE_UNIT,
B_FRAG_COLORBUF | B_LIGHTING | B_CURRENT | B_FRAG_FOG,
true);
}
private static StateSet buildFont() {
return new StateSet(0,
ids(
@@ -323,42 +323,42 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
}
@Override
public void drawArrays(int mode, int first, int count) {
protected void issueDrawArrays(int mode, int first, int count) {
GL11.glDrawArrays(mode, first, count);
}
@Override
public void drawElements(int mode, int count, int type, long indices) {
protected void issueDrawElements(int mode, int count, int type, long indices) {
GL11.glDrawElements(mode, count, type, indices);
}
@Override
public void drawElements(int mode, ByteBuffer indices) {
protected void issueDrawElements(int mode, ByteBuffer indices) {
GL11.glDrawElements(mode, indices);
}
@Override
public void drawElements(int mode, IntBuffer indices) {
protected void issueDrawElements(int mode, IntBuffer indices) {
GL11.glDrawElements(mode, indices);
}
@Override
public void drawElements(int mode, ShortBuffer indices) {
protected void issueDrawElements(int mode, ShortBuffer indices) {
GL11.glDrawElements(mode, indices);
}
@Override
public void drawElements(int mode, int count, int type, ByteBuffer indices) {
protected void issueDrawElements(int mode, int count, int type, ByteBuffer indices) {
GL11.glDrawElements(mode, count, type, indices);
}
@Override
public void drawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
protected void issueDrawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
GL31.glDrawElementsInstanced(mode, count, type, indices, primcount);
}
@Override
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
protected void issueDrawArraysInstanced(int mode, int first, int count, int primcount) {
GL31.glDrawArraysInstanced(mode, first, count, primcount);
}
@@ -368,7 +368,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
}
@Override
public void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
protected void issueMultiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
GL43.glMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
}
@@ -378,12 +378,12 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
}
@Override
public void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex) {
protected void issueDrawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex) {
GL32.glDrawElementsBaseVertex(mode, count, type, indices, baseVertex);
}
@Override
public void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) {
protected void issueMultiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) {
for (int i = 0; i < drawcount; i++) {
GL32.glDrawElementsBaseVertex(mode, memGetInt(pCount + 4L * i), type, memGetAddress(pIndices + (long) i * POINTER_SIZE), memGetInt(pBaseVertex + 4L * i));
}
@@ -1715,7 +1715,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
}
@Override
public void multiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts) {
protected void issueMultiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts) {
GL14.glMultiDrawArrays(mode, firsts, counts);
}
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.DepthEcho;
import com.gtnewhorizons.angelica.glsm.DepthPeel;
import com.gtnewhorizons.angelica.glsm.GLESFormatRemap;
import com.gtnewhorizons.angelica.glsm.GLSMAttribStack;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
@@ -321,28 +323,221 @@ public abstract class RenderBackend {
public abstract void logicOp(int opcode);
public abstract void hint(int target, int mode);
public abstract void drawArrays(int mode, int first, int count);
public abstract void drawElements(int mode, int count, int type, long indices);
public void drawElements(int mode, ByteBuffer indices) {}
public void drawElements(int mode, IntBuffer indices) {}
public void drawElements(int mode, ShortBuffer indices) {}
public void drawElements(int mode, int count, int type, ByteBuffer indices) {}
public abstract void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride);
public void multiDrawArraysIndirectCount(int mode, long indirect, long drawcount, int maxdrawcount, int stride) { throw new UnsupportedOperationException(getName() + ": indirect draw-count buffers unsupported"); }
public void multiDrawElementsIndirectCount(int mode, int type, long indirect, long drawcount, int maxdrawcount, int stride) { throw new UnsupportedOperationException(getName() + ": indirect draw-count buffers unsupported"); }
public abstract void copyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size);
public abstract void drawElementsInstanced(int mode, int count, int type, long indices, int primcount);
public abstract void drawArraysInstanced(int mode, int first, int count, int primcount);
public void provokingVertex(int provokeMode) {}
public abstract void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex);
public abstract void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex);
public final void drawArrays(int mode, int first, int count) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueDrawArrays(mode, first, count);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueDrawArrays(mode, first, count);
} finally {
DepthEcho.end();
}
}
}
public final void drawElements(int mode, int count, int type, long indices) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueDrawElements(mode, count, type, indices);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueDrawElements(mode, count, type, indices);
} finally {
DepthEcho.end();
}
}
}
public final void drawElements(int mode, ByteBuffer indices) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueDrawElements(mode, indices);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueDrawElements(mode, indices);
} finally {
DepthEcho.end();
}
}
}
public final void drawElements(int mode, IntBuffer indices) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueDrawElements(mode, indices);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueDrawElements(mode, indices);
} finally {
DepthEcho.end();
}
}
}
public final void drawElements(int mode, ShortBuffer indices) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueDrawElements(mode, indices);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueDrawElements(mode, indices);
} finally {
DepthEcho.end();
}
}
}
public final void drawElements(int mode, int count, int type, ByteBuffer indices) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueDrawElements(mode, count, type, indices);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueDrawElements(mode, count, type, indices);
} finally {
DepthEcho.end();
}
}
}
public final void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
} finally {
DepthEcho.end();
}
}
}
public final void drawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueDrawElementsInstanced(mode, count, type, indices, primcount);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueDrawElementsInstanced(mode, count, type, indices, primcount);
} finally {
DepthEcho.end();
}
}
}
public final void drawArraysInstanced(int mode, int first, int count, int primcount) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueDrawArraysInstanced(mode, first, count, primcount);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueDrawArraysInstanced(mode, first, count, primcount);
} finally {
DepthEcho.end();
}
}
}
public final void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueDrawElementsBaseVertex(mode, count, type, indices, baseVertex);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueDrawElementsBaseVertex(mode, count, type, indices, baseVertex);
} finally {
DepthEcho.end();
}
}
}
public final void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueMultiDrawElementsBaseVertex(mode, pCount, type, pIndices, drawcount, pBaseVertex);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueMultiDrawElementsBaseVertex(mode, pCount, type, pIndices, drawcount, pBaseVertex);
} finally {
DepthEcho.end();
}
}
}
public final void multiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts) {
if (DepthEcho.skipsDraw() || !DepthPeel.begin()) return;
try {
issueMultiDrawArrays(mode, firsts, counts);
} finally {
DepthPeel.end();
}
if (DepthEcho.begin()) {
try {
issueMultiDrawArrays(mode, firsts, counts);
} finally {
DepthEcho.end();
}
}
}
protected abstract void issueDrawArrays(int mode, int first, int count);
protected abstract void issueDrawElements(int mode, int count, int type, long indices);
protected void issueDrawElements(int mode, ByteBuffer indices) {}
protected void issueDrawElements(int mode, IntBuffer indices) {}
protected void issueDrawElements(int mode, ShortBuffer indices) {}
protected void issueDrawElements(int mode, int count, int type, ByteBuffer indices) {}
protected abstract void issueMultiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride);
protected abstract void issueDrawElementsInstanced(int mode, int count, int type, long indices, int primcount);
protected abstract void issueDrawArraysInstanced(int mode, int first, int count, int primcount);
protected abstract void issueDrawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex);
protected abstract void issueMultiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex);
protected abstract void issueMultiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts);
public void drawRangeElements(int mode, int start, int end, int count, int type, long indices) {
drawElements(mode, count, type, indices);
}
public void drawRangeElementsBaseVertex(int mode, int start, int end, int count, int type, long indices, int baseVertex) {
drawElementsBaseVertex(mode, count, type, indices, baseVertex);
}
public abstract void multiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts);
public abstract void primitiveRestartIndex(int index);
public abstract void pointParameterf(int pname, float param);
public abstract void pointParameteri(int pname, int param);
@@ -0,0 +1,17 @@
package com.gtnewhorizons.angelica.glsm.ffp;
public final class ClipDistanceGlsl {
public static final String VS_OUT_DIST0 = "out vec4 angelica_ClipDist0;";
public static final String VS_OUT_DIST1 = "out vec4 angelica_ClipDist1;";
public static final String FS_IN_DIST0 = "in vec4 angelica_ClipDist0;";
public static final String FS_IN_DIST1 = "in vec4 angelica_ClipDist1;";
public static final String FS_DISCARD = "if (any(lessThan(angelica_ClipDist0, vec4(0.0))) || any(lessThan(angelica_ClipDist1, vec4(0.0)))) discard;\n";
private ClipDistanceGlsl() {}
public static void appendVsAssign(StringBuilder sb, String[] distances) {
sb.append(" angelica_ClipDist0 = vec4(").append(distances[0]).append(", ").append(distances[1]).append(", ").append(distances[2]).append(", ").append(distances[3]).append(");\n");
sb.append(" angelica_ClipDist1 = vec4(").append(distances[4]).append(", ").append(distances[5]).append(", ").append(distances[6]).append(", ").append(distances[7]).append(");\n");
}
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
import com.gtnewhorizons.angelica.glsm.GLSMLighting;
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
import com.gtnewhorizons.angelica.glsm.states.LightState;
@@ -15,12 +16,75 @@ public final class FFPVertexLighting {
private static final Vector4f scratchEyePos = new Vector4f();
private static final Matrix3f scratchNormalMat = new Matrix3f();
private static final Vector3f scratchVec = new Vector3f();
private static final Vector3f scratchFactor = new Vector3f();
private static final Vector3f base = new Vector3f();
private static final Vector3f[] lightDirs = new Vector3f[VertexKey.FFP_LIGHT_COUNT];
private static final Vector3f[] lightDiffuse = new Vector3f[VertexKey.FFP_LIGHT_COUNT];
private static int litCount;
private static boolean normalize;
private static float rescale;
static {
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
lightDirs[i] = new Vector3f();
lightDiffuse[i] = new Vector3f();
}
}
private FFPVertexLighting() {}
public static boolean modulatesVertexColor(Vector3f destFactor) {
destFactor.set(1.0f);
if (!prepare(false)) return false;
factor(ShaderManager.getCurrentNormal(), scratchFactor);
final float r = scratchFactor.x;
final float g = scratchFactor.y;
final float b = scratchFactor.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;
}
public static boolean bakeColors(int[] rawBuffer, int vertexCount, boolean hasNormals, boolean hasColor) {
if (vertexCount <= 0 || !prepare(true)) return false;
if (!hasNormals) factor(ShaderManager.getCurrentNormal(), scratchFactor);
float cr = 0.0f, cg = 0.0f, cb = 0.0f;
int alphaBits = 0;
if (!hasColor) {
final var color = GLSMCurrentAttribs.getColor();
cr = clamp01(color.getRed());
cg = clamp01(color.getGreen());
cb = clamp01(color.getBlue());
alphaBits = toByte(clamp01(color.getAlpha())) << 24;
}
boolean haveNormal = false;
int lastNormal = 0;
for (int v = 0, i = 0; v < vertexCount; v++, i += 8) {
if (hasNormals) {
final int packed = rawBuffer[i + 6];
if (!haveNormal || packed != lastNormal) {
factor((byte) packed / 127.0f, (byte) (packed >> 8) / 127.0f, (byte) (packed >> 16) / 127.0f, scratchFactor);
lastNormal = packed;
haveNormal = true;
}
}
if (hasColor) {
final int packed = rawBuffer[i + 5];
cr = (packed & 0xFF) / 255.0f;
cg = ((packed >>> 8) & 0xFF) / 255.0f;
cb = ((packed >>> 16) & 0xFF) / 255.0f;
alphaBits = packed & 0xFF000000;
}
rawBuffer[i + 5] = alphaBits | (toByte(clamp01(cb * scratchFactor.z)) << 16) | (toByte(clamp01(cg * scratchFactor.y)) << 8) | toByte(clamp01(cr * scratchFactor.x));
}
return true;
}
private static boolean prepare(boolean directionalOnly) {
if (!GLSMLighting.getLightingState().isEnabled()) return false;
if (!GLSMLighting.getColorMaterial().isEnabled()) return false;
if (VertexKey.encodeColorMaterialMode(GLSMLighting.getColorMaterialParameter().getValue()) != VertexKey.CM_AMBIENT_AND_DIFFUSE) return false;
@@ -32,44 +96,58 @@ public final class FFPVertexLighting {
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 = GLSMLighting.getLightModel().ambient;
float r = lightModelAmbient.x;
float g = lightModelAmbient.y;
float b = lightModelAmbient.z;
base.set(lightModelAmbient.x, lightModelAmbient.y, lightModelAmbient.z);
final var lightEnables = GLSMLighting.getLightStates();
final var lightData = GLSMLighting.getLightDataStates();
boolean normalReady = false;
litCount = 0;
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 (directionalOnly && light.position.w != 0.0f) return false;
base.add(light.ambient.x, light.ambient.y, light.ambient.z);
lightDirs[litCount].set(lightDirection(light));
lightDiffuse[litCount].set(light.diffuse.x, light.diffuse.y, light.diffuse.z);
litCount++;
}
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);
if (litCount > 0) {
GLSMMatrices.getModelViewMatrix().normal(scratchNormalMat);
normalize = GLSMLighting.getNormalizeState().isEnabled();
rescale = !normalize && GLSMLighting.getRescaleNormalState().isEnabled() ? Uniforms.rescaleFactor(scratchNormalMat, scratchVec) : 1.0f;
}
return true;
}
private static void transformCurrentNormal() {
GLSMMatrices.getModelViewMatrix().normal(scratchNormalMat);
scratchNormal.set(ShaderManager.getCurrentNormal());
private static void factor(Vector3f normal, Vector3f dest) {
factor(normal.x, normal.y, normal.z, dest);
}
private static void factor(float nx, float ny, float nz, Vector3f dest) {
dest.set(base);
if (litCount == 0) return;
scratchNormal.set(nx, ny, nz);
scratchNormalMat.transform(scratchNormal);
if (GLSMLighting.getNormalizeState().isEnabled()) {
if (normalize) {
scratchNormal.normalize();
} else if (GLSMLighting.getRescaleNormalState().isEnabled()) {
scratchNormal.mul(Uniforms.rescaleFactor(scratchNormalMat, scratchVec));
} else {
scratchNormal.mul(rescale);
}
for (int i = 0; i < litCount; i++) {
final float nDotVP = Math.max(scratchNormal.dot(lightDirs[i]), 0.0f);
dest.fma(nDotVP, lightDiffuse[i]);
}
}
private static float clamp01(float value) {
return Math.min(Math.max(value, 0.0f), 1.0f);
}
private static int toByte(float unit) {
return (int) (unit * 255.0f + 0.5f);
}
private static Vector3f lightDirection(LightState light) {
@@ -3,7 +3,9 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import java.util.Arrays;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
@@ -15,24 +17,24 @@ import org.lwjgl.opengl.GL13;
* Packed-long fragment shader permutation key for FFP emulation.
*
* Layout:
* long[0]: global bits (14) + unit 0 (47 bits at offset 14)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* long[0]: global bits (14) + unit 0 (45 bits at offset 14), depth peel (60), combined glint (61), glint alpha (62), clip discard (63)
* long[1..3]: unit 1..3 (45 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
* Per-unit packing (47 bits):
* Per-unit packing (45 bits):
* 0: enabled
* 1-3: mode (MODULATE=0..COMBINE=5)
* 4: needsEnvColor
* 5-8: combineRgb (encoded 0-7)
* 9-12: combineAlpha (encoded 0-7)
* 13-14: scaleShiftRgb
* 15-16: scaleShiftAlpha
* 17-21: arg0 RGB (source:3 + operand:2)
* 22-26: arg1 RGB
* 27-31: arg2 RGB
* 32-36: arg0 Alpha
* 37-41: arg1 Alpha
* 42-46: arg2 Alpha
* 5-7: combineRgb (encoded 0-7)
* 8-10: combineAlpha (encoded 0-7)
* 11-12: scaleShiftRgb
* 13-14: scaleShiftAlpha
* 15-19: arg0 RGB (source:3 + operand:2)
* 20-24: arg1 RGB
* 25-29: arg2 RGB
* 30-34: arg0 Alpha
* 35-39: arg1 Alpha
* 40-44: arg2 Alpha
*/
public final class FragmentKey {
public static final int TEX_ENV_MODULATE = 0;
@@ -77,8 +79,10 @@ public final class FragmentKey {
}
private static final int GLOBAL_BITS = 14;
private static final int BIT_DEPTH_PEEL = 60;
private static final int BIT_COMBINED_GLINT = 61;
private static final int BIT_GLINT_REPLACE_ALPHA = 62;
private static final int BIT_CLIP_DISCARD = 63;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
@@ -92,16 +96,16 @@ public final class FragmentKey {
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
private static final int U_NEEDS_ENV_COLOR = 4; // 1 bit
private static final int U_COMBINE_RGB = 5; // 4 bits
private static final int U_COMBINE_ALPHA = 9; // 4 bits
private static final int U_SCALE_RGB = 13; // 2 bits
private static final int U_SCALE_ALPHA = 15; // 2 bits
private static final int U_ARG0_RGB = 17; // 5 bits (source:3 + operand:2)
private static final int U_ARG1_RGB = 22; // 5 bits
private static final int U_ARG2_RGB = 27; // 5 bits
private static final int U_ARG0_ALPHA = 32; // 5 bits
private static final int U_ARG1_ALPHA = 37; // 5 bits
private static final int U_ARG2_ALPHA = 42; // 5 bits
private static final int U_COMBINE_RGB = 5; // 3 bits
private static final int U_COMBINE_ALPHA = 8; // 3 bits
private static final int U_SCALE_RGB = 11; // 2 bits
private static final int U_SCALE_ALPHA = 13; // 2 bits
private static final int U_ARG0_RGB = 15; // 5 bits (source:3 + operand:2)
private static final int U_ARG1_RGB = 20; // 5 bits
private static final int U_ARG2_RGB = 25; // 5 bits
private static final int U_ARG0_ALPHA = 30; // 5 bits
private static final int U_ARG1_ALPHA = 35; // 5 bits
private static final int U_ARG2_ALPHA = 40; // 5 bits
static final int MAX_UNITS = 4;
@@ -125,7 +129,9 @@ public final class FragmentKey {
public static int packFromState(long[] out, GLContextState glCtx) {
long global = 0;
if (CombinedGlint.isActive()) global |= 1L << BIT_COMBINED_GLINT;
if (GLSMConfig.depthPeel) global |= 1L << BIT_DEPTH_PEEL;
if (CombinedGlint.replacesAlpha()) global |= 1L << BIT_GLINT_REPLACE_ALPHA;
if (GLSMRasterizer.clipEmulationActive(glCtx)) global |= 1L << BIT_CLIP_DISCARD;
// Fog
global |= activeFogMode(glCtx);
@@ -197,8 +203,8 @@ public final class FragmentKey {
if (mode == TEX_ENV_COMBINE) {
final int combRgb = encodeCombineFunc(envState.combineRgb);
final int combAlpha = encodeCombineFunc(envState.combineAlpha);
bits |= ((long) (combRgb & 0xF)) << U_COMBINE_RGB;
bits |= ((long) (combAlpha & 0xF)) << U_COMBINE_ALPHA;
bits |= ((long) (combRgb & 0x7)) << U_COMBINE_RGB;
bits |= ((long) (combAlpha & 0x7)) << U_COMBINE_ALPHA;
bits |= ((long) (encodeScale(envState.scaleRgb) & 0x3)) << U_SCALE_RGB;
bits |= ((long) (encodeScale(envState.scaleAlpha) & 0x3)) << U_SCALE_ALPHA;
@@ -259,7 +265,9 @@ public final class FragmentKey {
public int fogMode() { return (int) (packed[0] & 0x3); }
public boolean combinedGlint() { return (packed[0] & (1L << BIT_COMBINED_GLINT)) != 0; }
public boolean clipDiscard() { return (packed[0] & (1L << BIT_CLIP_DISCARD)) != 0; }
public boolean glintReplaceAlpha() { return (packed[0] & (1L << BIT_GLINT_REPLACE_ALPHA)) != 0; }
public boolean depthPeel() { return (packed[0] & (1L << BIT_DEPTH_PEEL)) != 0; }
public int textureSupersamplingSamples() { return 1 << (int) ((packed[0] >> BIT_TEXTURE_SUPERSAMPLING) & 0x7); }
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
@@ -277,8 +285,8 @@ public final class FragmentKey {
public boolean unitEnabled(int i) { return (unitBits(i) & 1) != 0; }
public int unitMode(int i) { return (int) ((unitBits(i) >> U_MODE) & 0x7); }
public boolean unitNeedsEnvColor(int i) { return ((unitBits(i) >> U_NEEDS_ENV_COLOR) & 1) != 0; }
public int unitCombineRgb(int i) { return (int) ((unitBits(i) >> U_COMBINE_RGB) & 0xF); }
public int unitCombineAlpha(int i) { return (int) ((unitBits(i) >> U_COMBINE_ALPHA) & 0xF); }
public int unitCombineRgb(int i) { return (int) ((unitBits(i) >> U_COMBINE_RGB) & 0x7); }
public int unitCombineAlpha(int i) { return (int) ((unitBits(i) >> U_COMBINE_ALPHA) & 0x7); }
public int unitScaleShiftRgb(int i) { return (int) ((unitBits(i) >> U_SCALE_RGB) & 0x3); }
public int unitScaleShiftAlpha(int i) { return (int) ((unitBits(i) >> U_SCALE_ALPHA) & 0x3); }
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.DepthPeel;
import org.lwjgl.opengl.GL11;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
@@ -24,6 +25,7 @@ public final class FragmentShaderGenerator {
sb.append("#define GLINT_ALPHA_FUNC ").append(key.alphaTestEnabled() ? key.alphaTestFunc() : 7).append('\n');
sb.append("#define GLINT_OVERLAY ").append(key.overlayInstanced() ? 2 : key.overlayEnabled() ? 1 : 0).append('\n');
sb.append("#define GLINT_LIGHTMAP ").append(key.nrEnabledUnits() > 1 && key.unitEnabled(1) ? 1 : 0).append('\n');
if (key.depthPeel()) sb.append("#define DEPTH_PEEL 1\n");
sb.append(ShaderLoader.getShaderSource("angelica:combined_glint.frag.glsl"));
return sb.toString();
}
@@ -34,12 +36,16 @@ public final class FragmentShaderGenerator {
}
sb.append("void main() {\n");
if (key.clipDiscard()) {
sb.append(ClipDistanceGlsl.FS_DISCARD);
}
emitLineStipple(sb, key);
emitTextureSampling(sb, key);
emitTexEnvChain(sb, key);
emitSpecularAdd(sb, key);
emitAlphaTest(sb, key);
emitFog(sb, key);
emitDepthPeel(sb, key);
sb.append(" fragColor = color;\n");
sb.append("}\n");
@@ -49,6 +55,9 @@ public final class FragmentShaderGenerator {
private static void emitInputs(StringBuilder sb, FragmentKey key) {
sb.append("// Inputs from vertex shader\n");
sb.append("in vec4 v_Color;\n");
if (key.clipDiscard()) {
sb.append(ClipDistanceGlsl.FS_IN_DIST0).append('\n').append(ClipDistanceGlsl.FS_IN_DIST1).append('\n');
}
if (key.overlayInstanced()) {
sb.append("in vec4 v_Overlay;\n");
}
@@ -83,10 +92,15 @@ public final class FragmentShaderGenerator {
sb.append("uniform sampler2D u_Sampler").append(i).append(";\n");
}
}
if (key.depthPeel()) sb.append(DepthPeel.GLSL);
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
}
private static void emitDepthPeel(StringBuilder sb, FragmentKey key) {
if (key.depthPeel()) sb.append(" peelTest(color);\n");
}
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CompatShaderTransformer;
import com.gtnewhorizons.angelica.glsm.DepthPeel;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLSMPrograms;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
@@ -35,6 +36,10 @@ public class Program {
@Getter private final FragmentKey fragmentKey;
public final int[] locSampler = { -1, -1, -1, -1 };
public final int locPeelDepth;
public final int locPeelScene;
private final int locPeelMode;
private int peelMode = -1;
Program(int programId, VertexKey vertexKey, FragmentKey fragmentKey) {
this.programId = programId;
@@ -43,6 +48,19 @@ public class Program {
for (int i = 0; i < 4; i++) {
locSampler[i] = RENDER_BACKEND.getUniformLocation(programId, "u_Sampler" + i);
}
locPeelDepth = RENDER_BACKEND.getUniformLocation(programId, "u_PeelDepth");
locPeelScene = RENDER_BACKEND.getUniformLocation(programId, "u_PeelScene");
locPeelMode = RENDER_BACKEND.getUniformLocation(programId, "u_PeelMode");
}
boolean peels() {
return locPeelMode != -1;
}
void syncPeelMode(int mode) {
if (peelMode == mode) return;
RENDER_BACKEND.uniform1i(locPeelMode, mode);
peelMode = mode;
}
public void destroy() {
@@ -99,6 +117,8 @@ public class Program {
for (int i = 0; i < 4; i++) {
if (ffpProgram.locSampler[i] != -1) backend.uniform1i(ffpProgram.locSampler[i], i);
}
if (ffpProgram.locPeelDepth != -1) backend.uniform1i(ffpProgram.locPeelDepth, DepthPeel.TEXTURE_UNIT);
if (ffpProgram.locPeelScene != -1) backend.uniform1i(ffpProgram.locPeelScene, DepthPeel.SCENE_TEXTURE_UNIT);
if (previousProgram != 0) {
backend.useProgram(previousProgram);
} else {
@@ -4,6 +4,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
import com.gtnewhorizons.angelica.glsm.DepthPeel;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
@@ -188,6 +189,7 @@ public final class ShaderManager {
private void uploadUniforms(GLContextState glCtx) {
if (currentProgram != null) {
uniforms.upload(glCtx);
if (currentProgram.peels()) currentProgram.syncPeelMode(DepthPeel.shaderMode());
}
}
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.FogState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
@@ -330,9 +329,7 @@ public class Uniforms {
private void stageClipPlanes(GLContextState glCtx) {
final ClipPlaneState cps = glCtx.clipPlaneState;
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
cps.putEffectivePlanes(glCtx.clipPlaneEnabledMask, clipPlaneBuf);
final long src = memAddress0(clipPlaneBuf);
for (int i = 0; i < 32 * 4; i += 4) {
putInt(FFPUniformBlock.CLIP_PLANES + i, memGetInt(src + i));
@@ -6,6 +6,7 @@ import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import org.joml.Matrix4f;
@@ -53,6 +54,7 @@ public final class VertexKey {
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_INSTANCING = BIT_LINE_STIPPLE + 1;
private static final int BIT_CLIP_EMULATED = BIT_INSTANCING + 3;
private static final long INSTANCING_MASK = 0x7L;
static {
@@ -105,6 +107,7 @@ public final class VertexKey {
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
public boolean clipEmulated() { return bit(BIT_CLIP_EMULATED); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
public boolean lineQuads() { return bit(BIT_LINE_QUADS); }
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & INSTANCING_MASK)]; }
@@ -238,6 +241,7 @@ public final class VertexKey {
if (GLSMRasterizer.anyClipPlaneEnabled(glCtx)) {
bits |= (1L << BIT_CLIP_PLANES);
if (RenderSystem.isClipDistanceEmulated()) bits |= 1L << BIT_CLIP_EMULATED;
}
if (glCtx.lineStippleActive) {
@@ -279,6 +283,6 @@ public final class VertexKey {
@Override
public String toString() {
return String.format("FFPVertexKey[0x%012X: 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 lquads=%b inst=%s]", 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, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), lineQuads(), instancing());
return String.format("FFPVertexKey[0x%012X: 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 clipEmu=%b lquads=%b inst=%s]", 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, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), clipEmulated(), lineQuads(), instancing());
}
}
@@ -111,6 +111,9 @@ public final class VertexShaderGenerator {
if (key.lineStipple()) {
sb.append("flat out vec2 v_LineStart;\n");
}
if (key.clipEmulated()) {
sb.append(ClipDistanceGlsl.VS_OUT_DIST0).append('\n').append(ClipDistanceGlsl.VS_OUT_DIST1).append('\n');
}
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
@@ -398,10 +401,19 @@ public final class VertexShaderGenerator {
private static void emitClipDistances(StringBuilder sb, VertexKey key) {
sb.append(" // Clip distances\n");
final boolean emulated = key.clipEmulated();
final String[] distances = emulated ? new String[8] : null;
for (int i = 0; i < 8; i++) {
final String own = "dot(u_ClipPlane[" + i + "], eyePos)";
final String distance = key.lineQuads() ? acrossLine(own, "dot(u_ClipPlane[" + i + "], lineEyeOther)") : own;
sb.append(" gl_ClipDistance[").append(i).append("] = ").append(distance).append(";\n");
if (emulated) {
distances[i] = distance;
} else {
sb.append(" gl_ClipDistance[").append(i).append("] = ").append(distance).append(";\n");
}
}
if (emulated) {
ClipDistanceGlsl.appendVsAssign(sb, distances);
}
}
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
/** Drains deferred geometry before changing state that is not stored with each batch. */
public final class BatchStateGuard {
public static Runnable flush;
public static Runnable drawFlush;
private static int suspended;
private BatchStateGuard() {}
@@ -16,12 +17,20 @@ public final class BatchStateGuard {
public static boolean isSuspended() { return suspended != 0; }
public static void beforeChange() {
if (flush == null || suspended != 0 || !GLStateManager.isMainThread()
run(flush);
}
public static void beforeDraw() {
run(drawFlush);
}
private static void run(Runnable action) {
if (action == null || suspended != 0 || !GLStateManager.isMainThread()
|| DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.COMPILE) return;
final CommandRecorder recorder = DisplayListManager.isRecording() ? DisplayListManager.pauseRecording() : null;
suspend();
try {
flush.run();
action.run();
} finally {
resume();
if (recorder != null) DisplayListManager.resumeRecording(recorder);
@@ -6,6 +6,7 @@ import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLSMTextureLifetime;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
@@ -16,11 +17,18 @@ public final class FrameHooks {
private static boolean gameLoopStarted;
private static long frameCounter;
private static boolean frameGroupActive;
private static String framePrefix = "frame:";
private static String buildLabel = "";
public static void bootstrapFirstFrame() {
DisplayListManager.abortIfLeaked();
if (!gameLoopStarted) {
gameLoopStarted = true;
final GLSMInitConfig config = GLStateManager.getInitConfig();
if (config != null && !config.getBuildLabel().isEmpty()) {
buildLabel = config.getBuildLabel();
framePrefix = buildLabel + " frame:";
}
RENDER_BACKEND.onFrameBegin();
pushFrameGroup();
}
@@ -48,8 +56,10 @@ public final class FrameHooks {
}
private static void pushFrameGroup() {
final boolean wasConnected = CaptureGate.tracyConnected;
CaptureGate.refresh();
GLDebug.pushGroup("frame:", frameCounter);
if (CaptureGate.tracyConnected && !wasConnected && !buildLabel.isEmpty()) Tracy.message(buildLabel);
GLDebug.pushGroup(framePrefix, frameCounter);
frameGroupActive = true;
}
@@ -15,6 +15,8 @@ public final class GLSMConfig {
}
public static boolean hudCacheOverride;
public static boolean layerBlending;
public static boolean depthPeel;
public static boolean extendedAttribsExpected;
public static volatile boolean expandVertexFormats;
@@ -31,6 +31,7 @@ public final class GLSMHooks {
public static final EventBus<ProgramDeleteEvent> PROGRAM_DELETE = EventBus.create(ProgramDeleteEvent.class);
public static final EventBus<BlendFuncChangeEvent> BLEND_FUNC_CHANGE = EventBus.create(BlendFuncChangeEvent.class);
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
public static final EventBus<ClipPlanesInUseEvent> CLIP_PLANES_IN_USE = EventBus.create(ClipPlanesInUseEvent.class);
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);
@@ -46,6 +47,7 @@ public final class GLSMHooks {
public static final ProgramDeleteEvent programDeleteEvent = new ProgramDeleteEvent();
public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent();
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
public static final ClipPlanesInUseEvent clipPlanesInUseEvent = new ClipPlanesInUseEvent();
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent();
@@ -17,6 +17,7 @@ public final class GLSMInitConfig {
@Getter private final Runnable postInitCallback;
private final boolean dsaEnabled;
@Getter private final boolean noErrorChecks;
@Getter private final String buildLabel;
private GLSMInitConfig(Builder builder) {
this.lwjglDebug = builder.lwjglDebug;
@@ -27,6 +28,7 @@ public final class GLSMInitConfig {
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
this.dsaEnabled = builder.dsaEnabled;
this.buildLabel = builder.buildLabel;
}
public boolean isDSAEnabled() {
@@ -46,6 +48,7 @@ public final class GLSMInitConfig {
private Runnable postInitCallback = null;
private boolean dsaEnabled = true;
private boolean noErrorChecks;
private String buildLabel = "";
private Builder() {}
@@ -85,6 +88,11 @@ public final class GLSMInitConfig {
return this;
}
public Builder buildLabel(String buildLabel) {
this.buildLabel = buildLabel == null ? "" : buildLabel;
return this;
}
public GLSMInitConfig build() {
return new GLSMInitConfig(this);
}
@@ -0,0 +1,5 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ClipPlanesInUseEvent extends MutableEvent {}
@@ -1,9 +1,12 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL15;
public final class IndexedDrawBatch {
@@ -34,17 +37,29 @@ public final class IndexedDrawBatch {
GLSMVertexInput.glBindVertexArray(v);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, sharedVBO);
final int stride = layout.stride();
int vertexFlags = 0;
for (int i = 0; i < layout.locations.length; i++) {
final int loc = layout.locations[i];
GLSMVertexInput.glVertexAttribPointer(loc, layout.sizes[i], layout.types[i], layout.normalized[i], stride, layout.offset(i));
GLSMVertexInput.glEnableVertexAttribArray(loc);
vertexFlags |= vertexFlag(loc);
}
// glVertexAttribPointer doesn't set these, and the FFP shader only reads the inputs they name. Saved with the VAO.
VAOManager.setCurrentVertexFlags(vertexFlags);
GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEBO);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
vao = v;
vaoCtx = glCtx;
}
private static int vertexFlag(int location) {
if (location == Usage.COLOR.getAttributeLocation()) return VertexFlags.COLOR_BIT;
if (location == Usage.NORMAL.getAttributeLocation()) return VertexFlags.NORMAL_BIT;
if (location == Usage.PRIMARY_UV.getAttributeLocation()) return VertexFlags.TEXTURE_BIT;
if (location == Usage.SECONDARY_UV.getAttributeLocation()) return VertexFlags.BRIGHTNESS_BIT;
return 0;
}
public void delete() {
if (deleted) return;
deleted = true;
@@ -12,7 +12,8 @@ final class GLSMOwners {
"getColorLogicOpState", "getColorMask", "getEffectiveBlendState", "getEffectiveColorMask",
"getIndexLogicOpState", "getLogicOpMode", "glBlendColor", "glBlendEquation", "glBlendEquationSeparate",
"glBlendFunc", "glBlendFuncSeparate", "glBlendFuncSeparatei", "glClearColor", "glColorMask", "glLogicOp",
"isEffectiveBlendEnabled", "setHudCacheOverride", "tryBlendFuncSeparate"},
"isEffectiveBlendEnabled", "isLayerBlending", "setHudCacheOverride", "setLayerBlending",
"tryBlendFuncSeparate"},
{"com/gtnewhorizons/angelica/glsm/GLSMBuffers",
"getBoundCopyReadBuffer", "getBoundCopyWriteBuffer", "getBoundVBO", "getIndexedBufferBinding",
"glBindBuffer", "glBindBufferBase", "glBindBufferRange", "glBufferData", "glBufferStorage",
@@ -106,22 +107,22 @@ final class GLSMOwners {
"glGetIntegeri", "glGetIntegeri_v", "glGetQueryObjecti", "glGetQueryObjectui", "glGetQueryObjectui64",
"glGetString", "glGetStringi", "glIsQuery", "glQueryCounter"},
{"com/gtnewhorizons/angelica/glsm/GLSMRasterizer",
"anyClipPlaneEnabled", "disableCull", "disableScissorTest", "enableCull", "enableScissorTest",
"getAutoNormalState", "getClipPlaneState", "getClipPlaneStates", "getCullState", "getDitherState",
"getEdgeFlagState", "getHintState", "getLineSmoothState", "getLineState", "getLineStippleState",
"getMap1Color4State", "getMap1IndexState", "getMap1NormalState", "getMap1TextureCoord1State",
"getMap1TextureCoord2State", "getMap1TextureCoord3State", "getMap1TextureCoord4State",
"getMap1Vertex3State", "getMap1Vertex4State", "getMap2Color4State", "getMap2IndexState",
"getMap2NormalState", "getMap2TextureCoord1State", "getMap2TextureCoord2State", "getMap2TextureCoord3State",
"getMap2TextureCoord4State", "getMap2Vertex3State", "getMap2Vertex4State", "getMultisampleState",
"getPointSmoothState", "getPointSpriteState", "getPointState", "getPolygonOffsetFillState",
"getPolygonOffsetLineState", "getPolygonOffsetPointState", "getPolygonSmoothState", "getPolygonState",
"getPolygonStippleState", "getSampleAlphaToCoverageState", "getSampleAlphaToOneState",
"getSampleCoverageState", "getScissorState", "getScissorTest", "getShadeModelState", "getViewportState",
"glClipPlane", "glCullFace", "glFrontFace", "glGetClipPlane", "glHint", "glLineStipple", "glLineWidth",
"glPointParameter", "glPointParameterf", "glPointParameteri", "glPointSize", "glPolygonMode",
"glPolygonOffset", "glPolygonOffsetClamp", "glPolygonStipple", "glSampleCoverage", "glScissor",
"glShadeModel", "glViewport"},
"anyClipPlaneEnabled", "clipEmulationActive", "disableCull", "disableScissorTest", "enableCull",
"enableScissorTest", "getAutoNormalState", "getClipPlaneState", "getClipPlaneStates", "getCullState",
"getDitherState", "getEdgeFlagState", "getHintState", "getLineSmoothState", "getLineState",
"getLineStippleState", "getMap1Color4State", "getMap1IndexState", "getMap1NormalState",
"getMap1TextureCoord1State", "getMap1TextureCoord2State", "getMap1TextureCoord3State",
"getMap1TextureCoord4State", "getMap1Vertex3State", "getMap1Vertex4State", "getMap2Color4State",
"getMap2IndexState", "getMap2NormalState", "getMap2TextureCoord1State", "getMap2TextureCoord2State",
"getMap2TextureCoord3State", "getMap2TextureCoord4State", "getMap2Vertex3State", "getMap2Vertex4State",
"getMultisampleState", "getPointSmoothState", "getPointSpriteState", "getPointState",
"getPolygonOffsetFillState", "getPolygonOffsetLineState", "getPolygonOffsetPointState",
"getPolygonSmoothState", "getPolygonState", "getPolygonStippleState", "getSampleAlphaToCoverageState",
"getSampleAlphaToOneState", "getSampleCoverageState", "getScissorState", "getScissorTest",
"getShadeModelState", "getViewportState", "glClipPlane", "glCullFace", "glFrontFace", "glGetClipPlane",
"glHint", "glLineStipple", "glLineWidth", "glPointParameter", "glPointParameterf", "glPointParameteri",
"glPointSize", "glPolygonMode", "glPolygonOffset", "glPolygonOffsetClamp", "glPolygonStipple",
"glSampleCoverage", "glScissor", "glShadeModel", "glViewport"},
{"com/gtnewhorizons/angelica/glsm/GLSMTexEnvGen",
"glGetTexEnv", "glGetTexEnvf", "glGetTexEnvi", "glGetTexGen", "glGetTexGend", "glGetTexGenf",
"glGetTexGeni", "glTexEnv", "glTexEnvf", "glTexEnvi", "glTexGen", "glTexGend", "glTexGenf", "glTexGeni"},
@@ -15,8 +15,10 @@ import org.taumc.glsl.grammar.GLSLParser;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class GlslVulkanPreprocess {
@@ -56,7 +58,9 @@ public final class GlslVulkanPreprocess {
private GlslVulkanPreprocess() {}
private record CacheKey(String source, int glShaderType, boolean separateReadOnlyImages) {}
public record Result(String rewrittenSource, Set<String> boolUniforms, Set<String> explicitVsInputs) {}
public record Result(String rewrittenSource, Set<String> boolUniforms, Set<String> explicitVsInputs, Map<String, UniformBlock> uniformBlocks) {}
public record UniformBlock(String qualifierText, @Nullable String instanceName) {}
/** Replace {@code [startIdx, stopIdx]} with {@code replacement}. */
public record Edit(int startIdx, int stopIdx, String replacement) {}
@@ -109,7 +113,7 @@ public final class GlslVulkanPreprocess {
final List<Edit> edits = new ArrayList<>();
final Metadata meta = collectEdits(new ShaderIndex(root, source), glShaderType, debugName, separateReadOnlyImages, edits);
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs());
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs(), meta.uniformBlocks());
synchronized (CACHE) {
CACHE.putAndMoveToFirst(key, out);
while (CACHE.size() > CACHE_MAX) CACHE.removeLast();
@@ -117,11 +121,12 @@ public final class GlslVulkanPreprocess {
return out;
}
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless) {}
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless, Map<String, UniformBlock> uniformBlocks) {}
public static Metadata collectEdits(ShaderIndex index, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
final Set<String> bools = new HashSet<>();
Map<String, UniformBlock> uniformBlocks = Map.of();
final Set<String> explicitInputs = new HashSet<>();
final Set<String> readOnlyImages = new HashSet<>();
boolean needsSamplerless = false;
@@ -130,6 +135,14 @@ public final class GlslVulkanPreprocess {
final List<Integer> unlocatedVsInputStarts = new ArrayList<>();
for (GLSLParser.DeclarationContext declaration : index.globalDeclarations()) {
if (declaration.LEFT_BRACE() != null && ShaderIndex.hasStorageQualifier(declaration.type_qualifier(), "uniform")) {
final GLSLParser.Type_qualifierContext blockQualifier = declaration.type_qualifier();
final String qualifierText = index.source().substring(startIdx(blockQualifier), stopIdx(blockQualifier) + 1);
final String instanceName = declaration.IDENTIFIER().size() > 1 ? declaration.IDENTIFIER(1).getText() : null;
if (uniformBlocks.isEmpty()) uniformBlocks = new HashMap<>();
uniformBlocks.put(declaration.IDENTIFIER(0).getText(), new UniformBlock(qualifierText, instanceName));
continue;
}
final GLSLParser.Init_declarator_listContext list = declaration.init_declarator_list();
if (list == null) continue;
final GLSLParser.Single_declarationContext single = list.single_declaration();
@@ -223,7 +236,7 @@ public final class GlslVulkanPreprocess {
}
}
return new Metadata(bools, explicitInputs, needsSamplerless);
return new Metadata(bools, explicitInputs, needsSamplerless, uniformBlocks);
}
public static boolean renamesIdentifier(String name) {
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.UniformBlock;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;
@@ -26,8 +27,10 @@ import org.taumc.glsl.grammar.GLSLParserBaseListener;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.startIdx;
@@ -65,13 +68,13 @@ public final class SpirvShaderTranslator {
try {
final String emitted = emitGlslEs(cr.spirv(), debugName, glShaderType, pre.explicitVsInputs());
if (emitted == null) return null;
return postProcessEsOutput(emitted, pre.boolUniforms());
return postProcessEsOutput(emitted, pre.boolUniforms(), pre.uniformBlocks());
} finally {
memFree(cr.spirv());
}
}
private static @Nullable String postProcessEsOutput(String glslEs, Set<String> boolUniformNames) {
private static @Nullable String postProcessEsOutput(String glslEs, Set<String> boolUniformNames, Map<String, UniformBlock> uniformBlocks) {
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(glslEs);
@@ -80,21 +83,42 @@ public final class SpirvShaderTranslator {
return null;
}
final DefaultBlockInfo block = findDefaultBlock(root);
final List<Edit> edits = new ArrayList<>();
final BlockFinder finder = new BlockFinder(glslEs, uniformBlocks);
ParseTreeWalker.DEFAULT.walk(finder, root);
if (block != null) {
edits.add(new Edit(startIdx(block.structExternalDecl), stopIdx(block.structExternalDecl), buildLooseUniforms(block.members, boolUniformNames)));
final List<Edit> edits = new ArrayList<>();
BlockInfo defaultBlock = null;
final Map<String, Set<String>> bareMembersByVar = new HashMap<>();
for (BlockInfo block : finder.blocks) {
final String structReplacement;
if (block.source == null) {
defaultBlock = block;
structReplacement = buildLooseUniforms(block.members, boolUniformNames);
bareMembersByVar.put(block.varName, block.memberNames());
} else if (block.source.instanceName() == null) {
structReplacement = block.source.qualifierText() + " " + block.structName + " " + block.body + ";";
bareMembersByVar.put(block.varName, block.memberNames());
} else {
structReplacement = block.source.qualifierText() + " " + block.structName + " " + block.body + " " + block.declaratorText + ";";
}
edits.add(new Edit(startIdx(block.structExternalDecl), stopIdx(block.structExternalDecl), structReplacement));
edits.add(new Edit(startIdx(block.uniformExternalDecl), stopIdx(block.uniformExternalDecl), ""));
}
final UseSiteRewriter rewriter = new UseSiteRewriter(block, boolUniformNames, edits);
final Set<String> defaultMembers = defaultBlock == null ? Set.of() : bareMembersByVar.get(defaultBlock.varName);
final UseSiteRewriter rewriter = new UseSiteRewriter(defaultBlock, defaultMembers, boolUniformNames, bareMembersByVar, edits);
ParseTreeWalker.DEFAULT.walk(rewriter, root);
return GlslVulkanPreprocess.applyEdits(glslEs, edits);
}
private record DefaultBlockInfo(ParserRuleContext structExternalDecl, ParserRuleContext uniformExternalDecl, String blockVarName, List<MemberInfo> members) {}
private record BlockInfo(String structName, @Nullable UniformBlock source, ParserRuleContext structExternalDecl, String body, List<MemberInfo> members, ParserRuleContext uniformExternalDecl, String varName, String declaratorText) {
Set<String> memberNames() {
final Set<String> names = new HashSet<>();
for (MemberInfo m : members) names.add(m.name);
return names;
}
}
private record MemberInfo(String typePrefix, String name) {}
@@ -111,31 +135,38 @@ public final class SpirvShaderTranslator {
return sb.toString();
}
private static @Nullable DefaultBlockInfo findDefaultBlock(GLSLParser.Translation_unitContext root) {
final DefaultBlockFinder finder = new DefaultBlockFinder();
ParseTreeWalker.DEFAULT.walk(finder, root);
if (finder.structExternalDecl == null || finder.uniformExternalDecl == null) return null;
return new DefaultBlockInfo(finder.structExternalDecl, finder.uniformExternalDecl, finder.blockVarName, finder.members);
private static boolean isDefaultBlockStruct(String structName) {
return structName.startsWith("_RESERVED_IDENTIFIER_FIXUP_") && structName.endsWith("DefaultUniformBlock");
}
private static final class DefaultBlockFinder extends GLSLParserBaseListener {
String structName;
ParserRuleContext structExternalDecl;
ParserRuleContext uniformExternalDecl;
String blockVarName;
final List<MemberInfo> members = new ArrayList<>();
private static final class BlockFinder extends GLSLParserBaseListener {
private record PendingStruct(@Nullable UniformBlock source, ParserRuleContext externalDecl, String body, List<MemberInfo> members) {}
private final String glslEs;
private final Map<String, UniformBlock> sourceBlocks;
private final Map<String, PendingStruct> pending = new HashMap<>();
final List<BlockInfo> blocks = new ArrayList<>();
BlockFinder(String glslEs, Map<String, UniformBlock> sourceBlocks) {
this.glslEs = glslEs;
this.sourceBlocks = sourceBlocks;
}
@Override
public void enterStruct_specifier(GLSLParser.Struct_specifierContext ctx) {
if (structExternalDecl != null) return;
final TerminalNode id = ctx.IDENTIFIER();
if (id == null) return;
final String idText = id.getText();
if (!idText.startsWith("_RESERVED_IDENTIFIER_FIXUP_") || !idText.endsWith("DefaultUniformBlock")) return;
final UniformBlock source = sourceBlocks.get(idText);
if (source == null && !isDefaultBlockStruct(idText)) return;
structName = idText;
structExternalDecl = findExternalDeclaration(ctx);
final List<MemberInfo> members = source != null && source.instanceName() != null ? List.of() : collectMembers(ctx);
final String body = glslEs.substring(ctx.LEFT_BRACE().getSymbol().getStartIndex(), ctx.RIGHT_BRACE().getSymbol().getStopIndex() + 1);
pending.putIfAbsent(idText, new PendingStruct(source, findExternalDeclaration(ctx), body, members));
}
private static List<MemberInfo> collectMembers(GLSLParser.Struct_specifierContext ctx) {
final List<MemberInfo> members = new ArrayList<>();
for (GLSLParser.Struct_declarationContext sd : ctx.struct_declaration_list().struct_declaration()) {
final StringBuilder typePrefix = new StringBuilder();
if (sd.type_qualifier() != null) typePrefix.append(textWithSpaces(sd.type_qualifier())).append(' ');
@@ -146,11 +177,12 @@ public final class SpirvShaderTranslator {
members.add(new MemberInfo(typePrefix + arraySuffix, decl.IDENTIFIER().getText()));
}
}
return members;
}
@Override
public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) {
if (structName == null || uniformExternalDecl != null) return;
if (pending.isEmpty()) return;
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
if (single == null || single.typeless_declaration() == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
@@ -159,10 +191,14 @@ public final class SpirvShaderTranslator {
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
final GLSLParser.Type_specifier_nonarrayContext ts = fst.type_specifier().type_specifier_nonarray();
if (ts.type_name() == null) return;
if (!structName.equals(ts.type_name().getText())) return;
final String structName = ts.type_name().getText();
final PendingStruct struct = pending.remove(structName);
if (struct == null) return;
blockVarName = single.typeless_declaration().IDENTIFIER().getText();
uniformExternalDecl = findExternalDeclaration(ctx);
final GLSLParser.Typeless_declarationContext declarator = single.typeless_declaration();
final String declaratorText = glslEs.substring(startIdx(declarator), stopIdx(declarator) + 1);
blocks.add(new BlockInfo(structName, struct.source, struct.externalDecl, struct.body, struct.members,
findExternalDeclaration(ctx), declarator.IDENTIFIER().getText(), declaratorText));
}
private static String textWithSpaces(ParseTree tree) {
@@ -192,17 +228,18 @@ public final class SpirvShaderTranslator {
}
private static final class UseSiteRewriter extends GLSLParserBaseListener {
private final @Nullable DefaultBlockInfo block;
private final @Nullable BlockInfo block;
private final Set<String> boolNames;
private final Map<String, Set<String>> bareMembersByVar;
private final List<Edit> edits;
private final Set<String> memberNames;
UseSiteRewriter(@Nullable DefaultBlockInfo block, Set<String> boolNames, List<Edit> edits) {
UseSiteRewriter(@Nullable BlockInfo block, Set<String> memberNames, Set<String> boolNames, Map<String, Set<String>> bareMembersByVar, List<Edit> edits) {
this.block = block;
this.memberNames = memberNames;
this.boolNames = boolNames;
this.bareMembersByVar = bareMembersByVar;
this.edits = edits;
this.memberNames = new HashSet<>();
if (block != null) for (MemberInfo m : block.members) memberNames.add(m.name);
}
@Override
@@ -240,11 +277,11 @@ public final class SpirvShaderTranslator {
}
}
if (block != null && ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) {
final ParseTree inner = ctx.getChild(0);
if (inner.getText().equals(block.blockVarName)) {
if (ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) {
final Set<String> bareMembers = bareMembersByVar.get(ctx.getChild(0).getText());
if (bareMembers != null) {
final String member = ctx.field_selection().getText();
if (memberNames.contains(member) && !coveredByAncestorBoolCompare(ctx)) {
if (bareMembers.contains(member) && !coveredByAncestorBoolCompare(ctx)) {
edits.add(new Edit(startIdx(ctx), stopIdx(ctx), member));
}
}
@@ -254,8 +291,8 @@ public final class SpirvShaderTranslator {
private @Nullable String resolveBoolOperand(ParseTree operand) {
final String text = operand.getText();
if (boolNames.contains(text)) return text;
if (block != null && text.startsWith(block.blockVarName + ".")) {
final String member = text.substring(block.blockVarName.length() + 1);
if (block != null && text.startsWith(block.varName + ".")) {
final String member = text.substring(block.varName.length() + 1);
if (boolNames.contains(member) && memberNames.contains(member)) return member;
}
return null;
@@ -23,8 +23,12 @@ public class BooleanState implements ISettableState<BooleanState> {
this.ffpStateOnly = ffpStateOnly;
}
protected boolean forwardsToBackend() {
return !ffpStateOnly;
}
public void applyToBackend() {
if (ffpStateOnly) return;
if (!forwardsToBackend()) return;
if (enabled) {
RENDER_BACKEND.enable(glCap);
} else {
@@ -51,7 +55,7 @@ public class BooleanState implements ISettableState<BooleanState> {
stateUnknown = false;
this.enabled = enabled;
onEnabledChanged();
if (!ffpStateOnly) {
if (forwardsToBackend()) {
if (enabled) {
RENDER_BACKEND.enable(this.glCap);
} else {
@@ -27,10 +27,16 @@ public class ClipPlaneState {
dest[3] = tempPlane.w;
}
/** Writes the 4-component eye-space plane equation into {@code buf} at its current position. */
public void putEyePlane(int index, FloatBuffer buf) {
final float[] p = eyePlanes[index];
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
// Vulkan and clip emulation have no per-distance enable: a disabled plane must read as distance 0.
public void putEffectivePlanes(int enabledMask, FloatBuffer buf) {
for (int i = 0; i < eyePlanes.length; i++) {
if ((enabledMask & (1 << i)) != 0) {
final float[] p = eyePlanes[i];
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
} else {
buf.put(0f).put(0f).put(0f).put(0f);
}
}
}
public void getEyePlane(int index, DoubleBuffer buf) {
@@ -0,0 +1,92 @@
package com.gtnewhorizons.angelica.glsm.texture;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
/** Emulates GL proxy textures for backends that lack them. */
public final class ProxyTexture {
private final int maxTextureSize;
private final int max3DTextureSize;
private final int maxCubeMapTextureSize;
private final int maxArrayTextureLayers;
private int target;
private int width;
private int height;
private int depth;
private int internalFormat;
public ProxyTexture(int maxTextureSize, int max3DTextureSize, int maxCubeMapTextureSize, int maxArrayTextureLayers) {
this.maxTextureSize = maxTextureSize;
this.max3DTextureSize = max3DTextureSize;
this.maxCubeMapTextureSize = maxCubeMapTextureSize;
this.maxArrayTextureLayers = maxArrayTextureLayers;
}
public void record(int target, int level, int internalFormat, int width, int height, int depth, boolean formatSupported) {
final boolean layersInHeight = layersInHeight(target);
final boolean layersInDepth = layersInDepth(target);
final int dimLimit = dimensionLimit(target);
final int w = levelZeroEquivalentSize(width, level);
final int h = layersInHeight ? height : levelZeroEquivalentSize(height, level);
final int d = layersInDepth ? depth : levelZeroEquivalentSize(depth, level);
final int hLimit = layersInHeight ? maxArrayTextureLayers : dimLimit;
final int dLimit = layersInDepth ? maxArrayTextureLayers : dimLimit;
final boolean fits = w >= 1 && w <= dimLimit && h >= 1 && h <= hLimit && d >= 1 && d <= dLimit && formatSupported;
if (fits) {
accept(target, w, h, d, internalFormat);
} else {
reject(target);
}
}
public void reject(int target) {
accept(target, 0, 0, 0, 0);
}
private void accept(int target, int width, int height, int depth, int internalFormat) {
this.target = target;
this.width = width;
this.height = height;
this.depth = depth;
this.internalFormat = internalFormat;
}
public int levelParameter(int target, int level, int pname) {
if (this.target != target) return 0;
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(width, level);
case GL11.GL_TEXTURE_HEIGHT -> layersInHeight(target) ? height : TextureInfo.mipLevelSize(height, level);
case GL12.GL_TEXTURE_DEPTH -> layersInDepth(target) ? depth : TextureInfo.mipLevelSize(depth, level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> internalFormat;
default -> 0;
};
}
public int dimensionLimit(int target) {
return switch (target) {
case GL12.GL_PROXY_TEXTURE_3D -> max3DTextureSize;
case GL13.GL_PROXY_TEXTURE_CUBE_MAP, GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> maxCubeMapTextureSize;
default -> maxTextureSize;
};
}
public static int levelZeroEquivalentSize(int size, int level) {
if (size <= 0 || level < 0 || level >= Integer.SIZE - 1) return 0;
final long scaled = (long) size << level;
return scaled > Integer.MAX_VALUE ? 0 : (int) scaled;
}
private static boolean layersInHeight(int target) {
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY;
}
private static boolean layersInDepth(int target) {
return target == GL30.GL_PROXY_TEXTURE_2D_ARRAY || target == GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY || target == GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY;
}
}
@@ -15,6 +15,12 @@ public class TextureInfo {
private static final int UNSET_INT = Integer.MIN_VALUE;
private static final float UNSET_FLOAT = Float.NEGATIVE_INFINITY;
public static int mipLevelSize(int base, int level) {
if (base <= 0 || level < 0) return 0;
if (level >= Integer.SIZE - 1) return 1;
return Math.max(1, base >> level);
}
@Getter protected final int id;
@Getter @Setter protected int target = GL11.GL_TEXTURE_2D;
protected int internalFormat = -1;
@@ -53,4 +53,7 @@ void main() {
#if GLINT_REPLACE_ALPHA
fragColor.a = visible1 ? b.a : a.a;
#endif
#ifdef DEPTH_PEEL
peelTest(fragColor);
#endif
}
@@ -40,7 +40,7 @@ class GLESCapsTest {
};
private static boolean onGles(int cap) {
return GLESCaps.isCapAllowed(cap, true, true);
return GLESCaps.isCapAllowed(cap, true);
}
@Test
@@ -60,25 +60,10 @@ class GLESCapsTest {
@Test
void desktopContextAllowsEveryCap() {
for (int cap : DESKTOP_ONLY) {
assertTrue(GLESCaps.isCapAllowed(cap, false, false), () -> "cap 0x" + Integer.toHexString(cap) + " is legal on desktop core and the gate must never suppress it there");
assertTrue(GLESCaps.isCapAllowed(cap, false), () -> "cap 0x" + Integer.toHexString(cap) + " is legal on desktop core and the gate must never suppress it there");
}
for (int cap : VALID_ON_GLES) {
assertTrue(GLESCaps.isCapAllowed(cap, false, false));
assertTrue(GLESCaps.isCapAllowed(cap, false));
}
}
@Test
void clipPlanesFollowClipCullDistance() {
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
final int cap = GL11.GL_CLIP_PLANE0 + i;
assertTrue(GLESCaps.isCapAllowed(cap, true, true), () -> "clip plane " + cap + " works where EXT_clip_cull_distance is present and must not be suppressed");
assertFalse(GLESCaps.isCapAllowed(cap, true, false), () -> "clip plane " + cap + " is invalid on GLES without EXT_clip_cull_distance");
}
}
@Test
void capsOutsideTheClipPlaneRangeAreUnaffectedByClipCullDistance() {
assertTrue(GLESCaps.isCapAllowed(GL11.GL_BLEND, true, false));
assertTrue(GLESCaps.isCapAllowed(GL11.GL_CLIP_PLANE0 + GLStateManager.MAX_CLIP_PLANES, true, false));
}
}
@@ -0,0 +1,119 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
public class GLSM_BlendAlphaOverride_GLTest {
private static void assertDriverFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
assertEquals(srcRgb, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB));
assertEquals(dstRgb, GL11.glGetInteger(GL14.GL_BLEND_DST_RGB));
assertEquals(srcAlpha, GL11.glGetInteger(GL14.GL_BLEND_SRC_ALPHA));
assertEquals(dstAlpha, GL11.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
}
private static void assertCachedFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
assertEquals(srcRgb, GLSMQueries.glGetInteger(GL14.GL_BLEND_SRC_RGB));
assertEquals(dstRgb, GLSMQueries.glGetInteger(GL14.GL_BLEND_DST_RGB));
assertEquals(srcAlpha, GLSMQueries.glGetInteger(GL14.GL_BLEND_SRC_ALPHA));
assertEquals(dstAlpha, GLSMQueries.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
}
@AfterEach
void resetOverrides() {
GLSMHooks.blendHandler = null;
GLSMBlend.setHudCacheOverride(false);
GLSMBlend.setLayerBlending(false);
GLSMBlend.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
}
@Test
void overrideChangesTheDriverFuncButNotTheCache() {
GLSMBlend.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLSMBlend.setHudCacheOverride(true);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLSMBlend.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_SRC_ALPHA, GL11.GL_ONE);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLSMBlend.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
assertCachedFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
assertDriverFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLSMBlend.setHudCacheOverride(false);
assertCachedFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
assertDriverFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
}
@Test
void popUnderOverrideKeepsTheOverriddenAlphaOnTheDriver() {
GLSMBlend.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
final int d = GLSMAttribStack.pushState(StateSet.BLEND);
GLSMBlend.setHudCacheOverride(true);
GLSMBlend.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
GLSMAttribStack.popStateTo(d);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLSMBlend.setHudCacheOverride(false);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
}
@Test
void separateAlphaIsForcedEvenWhenDstAlphaAlreadyMatches() {
GLSMBlend.setHudCacheOverride(true);
GLSMBlend.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_ALPHA);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_ALPHA);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
}
static Stream<Arguments> layerBlendRows() {
return Stream.of(
Arguments.of("translucent over", GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA),
Arguments.of("alpha additive", GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE));
}
@ParameterizedTest(name = "{0}")
@MethodSource("layerBlendRows")
void layerBlendingAccumulatesCoverageAndKeepsAdditiveCoverageFree(String name, int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
GLSMBlend.setLayerBlending(true);
GLSMBlend.glBlendFunc(srcRgb, dstRgb);
assertCachedFunc(srcRgb, dstRgb, srcRgb, dstRgb);
assertDriverFunc(srcRgb, dstRgb, srcAlpha, dstAlpha);
GLSMBlend.setLayerBlending(false);
assertDriverFunc(srcRgb, dstRgb, srcRgb, dstRgb);
}
@Test
void togglingWhileABlendOverrideIsHeldReemitsOnTheNextUnchangedFunc() {
GLSMBlend.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLSMHooks.blendHandler = new HeldOverride();
GLSMBlend.setLayerBlending(true);
GLSMHooks.blendHandler = null;
GLSMBlend.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
}
private static final class HeldOverride implements DeferredBlendHandler {
@Override public boolean isBlendLocked() { return false; }
@Override public boolean isOverrideHeld() { return true; }
@Override public void deferBlendModeToggle(boolean enabled) {}
@Override public void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {}
@Override public void flushDeferredBlend() {}
}
}
@@ -1,83 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
public class GLSM_HudCacheBlendOverride_GLTest {
private static void assertDriverFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
assertEquals(srcRgb, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB));
assertEquals(dstRgb, GL11.glGetInteger(GL14.GL_BLEND_DST_RGB));
assertEquals(srcAlpha, GL11.glGetInteger(GL14.GL_BLEND_SRC_ALPHA));
assertEquals(dstAlpha, GL11.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
}
private static void assertCachedFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
assertEquals(srcRgb, GLSMQueries.glGetInteger(GL14.GL_BLEND_SRC_RGB));
assertEquals(dstRgb, GLSMQueries.glGetInteger(GL14.GL_BLEND_DST_RGB));
assertEquals(srcAlpha, GLSMQueries.glGetInteger(GL14.GL_BLEND_SRC_ALPHA));
assertEquals(dstAlpha, GLSMQueries.glGetInteger(GL14.GL_BLEND_DST_ALPHA));
}
@Test
void overrideChangesTheDriverFuncButNotTheCache() {
try {
GLSMBlend.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLSMBlend.setHudCacheOverride(true);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLSMBlend.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_SRC_ALPHA, GL11.GL_ONE);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLSMBlend.tryBlendFuncSeparate(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
assertCachedFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
assertDriverFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLSMBlend.setHudCacheOverride(false);
assertCachedFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
assertDriverFunc(GL11.GL_DST_COLOR, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ONE);
} finally {
GLSMBlend.setHudCacheOverride(false);
GLSMBlend.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void popUnderOverrideKeepsTheOverriddenAlphaOnTheDriver() {
try {
GLSMBlend.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
final int d = GLSMAttribStack.pushState(StateSet.BLEND);
GLSMBlend.setHudCacheOverride(true);
GLSMBlend.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
GLSMAttribStack.popStateTo(d);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLSMBlend.setHudCacheOverride(false);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
} finally {
GLSMBlend.setHudCacheOverride(false);
GLSMBlend.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
}
}
@Test
void separateAlphaIsForcedEvenWhenDstAlphaAlreadyMatches() {
try {
GLSMBlend.setHudCacheOverride(true);
GLSMBlend.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_ALPHA);
assertCachedFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE_MINUS_SRC_ALPHA);
assertDriverFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
} finally {
GLSMBlend.setHudCacheOverride(false);
GLSMBlend.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
}
}
}
@@ -167,6 +167,33 @@ public class GLSM_StateSet_GLTest {
|| GLSMBlend.getColorMask().blue || GLSMBlend.getColorMask().alpha, "cache color mask - not a HAND member, must stay toggled");
}
@Test
void blockEntityPassRestoresColorFogAndShadeModelOnTopOfBatchButNotStencil() {
GLSMCurrentAttribs.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLSMFog.disableFog();
GLSMFog.glFogf(GL11.GL_FOG_END, 64.0f);
GLSMRasterizer.glShadeModel(GL11.GL_SMOOTH);
GLSMDepthStencilAlpha.glDepthMask(true);
GLSMEnable.glDisable(GL11.GL_STENCIL_TEST);
final int d = GLSMAttribStack.pushState(StateSet.BLOCK_ENTITY_PASS);
GLSMCurrentAttribs.glColor4f(0.2f, 0.4f, 0.6f, 0.8f);
GLSMFog.enableFog();
GLSMFog.glFogf(GL11.GL_FOG_END, 8.0f);
GLSMRasterizer.glShadeModel(GL11.GL_FLAT);
GLSMDepthStencilAlpha.glDepthMask(false);
GLSMEnable.glEnable(GL11.GL_STENCIL_TEST);
GLSMAttribStack.popStateTo(d);
assertEquals(1.0f, GLSMCurrentAttribs.getColor().getRed(), "cache color red");
assertEquals(1.0f, GLSMCurrentAttribs.getColor().getAlpha(), "cache color alpha");
assertFalse(GLSMFog.getFogMode().isEnabled(), "fog enable restored by BLOCK_ENTITY_PASS pop");
assertEquals(64.0f, GLSMFog.getFogState().getEnd(), "cache fog end");
assertEquals(GL11.GL_SMOOTH, GLSMRasterizer.getShadeModelState().getValue(), "cache shade model");
assertTrue(GLSMDepthStencilAlpha.getDepthState().isEnabled(), "depth mask restored, as in BATCH");
verifyIsEnabled(GL11.GL_STENCIL_TEST, true, "Stencil test - not a BLOCK_ENTITY_PASS member, must stay toggled");
}
@Test
void masksRestoresDepthMaskAndColorMaskButNotDepthTestEnable() {
GLSMDepthStencilAlpha.glDepthMask(true);
@@ -0,0 +1,124 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLSMAttribStack;
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
class ClipPlaneGLTest {
private static final int SIZE = 64;
private static final int BACKGROUND = 0xFF000000;
private static final int QUAD = 0xFF0000FF;
@BeforeEach
void setUp() {
FfpFixture.resetFfpState();
FfpFixture.activateFfp();
GLSMRasterizer.glViewport(0, 0, SIZE, SIZE);
GLSMMatrices.glMatrixMode(GL11.GL_PROJECTION);
GLSMMatrices.glLoadIdentity();
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
GLSMMatrices.glLoadIdentity();
GLSMEnable.glDisable(GL11.GL_DEPTH_TEST);
}
@AfterEach
void tearDown() {
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0 + i);
}
FfpFixture.resetFfpState();
}
@ParameterizedTest(name = "emulated={0}")
@ValueSource(booleans = {false, true})
void disabledPlaneDoesNotClip(boolean emulated) {
try (var ignored = ClipTestSupport.emulation(emulated)) {
ClipTestSupport.setPlane(1, 0, 0, 0, -1);
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE1);
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE1);
ClipTestSupport.setPlane(0, 1, 0, 0, 0);
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
assertRightHalfOnly(drawQuad(), "plane1 disabled, plane0 enabled");
}
}
@ParameterizedTest(name = "emulated={0}")
@ValueSource(booleans = {false, true})
void attribPopRestoresClipping(boolean emulated) {
try (var ignored = ClipTestSupport.emulation(emulated)) {
ClipTestSupport.setPlane(0, 1, 0, 0, 0);
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
assertDriverClipPlane0(!emulated, "after glEnable");
GLSMAttribStack.glPushAttrib(GL11.GL_ENABLE_BIT);
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
final int[] unclipped = drawQuad();
GLSMAttribStack.glPopAttrib();
assertDriverClipPlane0(!emulated, "after glPopAttrib");
GLSMAttribStack.replayStateToBackend();
assertDriverClipPlane0(!emulated, "after replayStateToBackend");
final int[] restored = drawQuad();
for (int i = 0; i < unclipped.length; i++) {
final int px = i;
assertEquals(QUAD, unclipped[i], () -> "plane disabled inside the pushed scope, pixel " + px % SIZE + "," + px / SIZE);
}
assertRightHalfOnly(restored, "after glPopAttrib");
}
}
private static void assertDriverClipPlane0(boolean expected, String when) {
assertEquals(expected, GL11.glIsEnabled(GL11.GL_CLIP_PLANE0), () -> when + ": clip plane reaches the driver only when clip distances are native");
}
private static void assertRightHalfOnly(int[] pixels, String what) {
for (int i = 0; i < pixels.length; i++) {
final int x = i % SIZE, y = i / SIZE;
assertEquals(x < SIZE / 2 ? BACKGROUND : QUAD, pixels[i], () -> what + ": first mismatch at pixel " + x + "," + y);
}
}
private static int[] drawQuad() {
final int vao = GLSMVertexInput.glGenVertexArrays();
GLSMVertexInput.glBindVertexArray(vao);
final int vbo = GLSMBuffers.glGenBuffers();
try {
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
final FloatBuffer positions = BufferUtils.createFloatBuffer(18);
positions.put(new float[] { -1, -1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0 }).flip();
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, positions, GL15.GL_STATIC_DRAW);
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setCurrentVertexFlags(0);
FfpFixture.clear();
GLSMCurrentAttribs.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
GLSMDraws.glDrawArrays(GL11.GL_TRIANGLES, 0, 6);
GLSMVertexInput.glBindVertexArray(0);
return FfpFixture.readRegion(SIZE);
} finally {
GLSMBuffers.glDeleteBuffers(vbo);
GLSMVertexInput.glDeleteVertexArrays(vao);
}
}
}
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.shaderc.Shaderc;
@@ -55,6 +56,30 @@ class FFPUniformBlockSpirvLayoutTest {
MemoryUtil.memFree(result.spirv());
}
@ParameterizedTest(name = "emulated={0}")
@ValueSource(booleans = {false, true})
void clipDistanceCapabilityFollowsEmulation(boolean emulated) {
long vbits = bit("BIT_CLIP_PLANES");
if (emulated) vbits |= bit("BIT_CLIP_EMULATED");
final String vs = VertexShaderGenerator.generate(VertexKey.fromPacked(vbits));
assertEquals(!emulated, declaresClipDistance(vs, Shaderc.shaderc_vertex_shader), "vertex ClipDistance capability");
}
private static boolean declaresClipDistance(String glsl, int shaderKind) {
final SpirvCompiler.Result result = SpirvCompiler.compile(glsl, shaderKind, "clip-emulation-test", SpirvCompiler.Options.vulkanForced460Core());
assertNotNull(result.spirv(), () -> "SPIR-V compile failed: " + result.error() + "\n" + glsl);
try {
final IntBuffer words = result.spirv().asIntBuffer();
for (int i = 5; i < words.limit(); i += Math.max(1, words.get(i) >>> 16)) {
final int word = words.get(i);
if ((word & 0xFFFF) == 17 && words.get(i + 1) == 32) return true;
}
return false;
} finally {
MemoryUtil.memFree(result.spirv());
}
}
private static VertexKey cubeKey(boolean lightmapUnit, boolean unit0TextureMatrix, boolean texGen) {
long bits = bit("BIT_HAS_VERTEX_COLOR") | bit("BIT_HAS_VERTEX_NORMAL") | bit("BIT_HAS_VERTEX_TEX") | bit("BIT_UNIT_TEX_BASE");
if (lightmapUnit) bits |= 1L << (position("BIT_UNIT_TEX_BASE") + 1);
@@ -27,10 +27,12 @@ import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.stream.Stream;
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.COLOR_BIT;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.floats;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -82,6 +84,7 @@ class FFPVertexLightingGLTest {
GLSMEnable.glDisable(GL11.GL_LIGHT1);
GLSMEnable.glDisable(GL11.GL_LIGHT2);
GLSMEnable.glDisable(GL11.GL_COLOR_MATERIAL);
GLSMCurrentAttribs.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
GLSMVertexInput.glBindVertexArray(0);
@@ -126,7 +129,7 @@ class FFPVertexLightingGLTest {
+ " light1=" + GLSMLighting.getLightStates()[1].isEnabled();
}
private void buildTriangle() {
private void buildTriangle(boolean withColor) {
vao = GLSMVertexInput.glGenVertexArrays();
GLSMVertexInput.glBindVertexArray(vao);
@@ -141,9 +144,13 @@ class FFPVertexLightingGLTest {
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
GLSMVertexInput.glEnableVertexAttribArray(POSITION_LOC);
GLSMVertexInput.glVertexAttribPointer(POSITION_LOC, 3, GL11.GL_FLOAT, false, STRIDE, 0L);
GLSMVertexInput.glEnableVertexAttribArray(COLOR_LOC);
GLSMVertexInput.glVertexAttribPointer(COLOR_LOC, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 12L);
VAOManager.setCurrentVertexFlags(COLOR_BIT);
if (withColor) {
GLSMVertexInput.glEnableVertexAttribArray(COLOR_LOC);
GLSMVertexInput.glVertexAttribPointer(COLOR_LOC, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 12L);
VAOManager.setCurrentVertexFlags(COLOR_BIT);
} else {
VAOManager.setCurrentVertexFlags(0);
}
}
private static void putVertex(ByteBuffer buf, float x, float y) {
@@ -154,7 +161,11 @@ class FFPVertexLightingGLTest {
}
private float[] drawAndReadCenter() {
buildTriangle();
return drawAndReadCenter(true);
}
private float[] drawAndReadCenter(boolean withColor) {
buildTriangle(withColor);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
@@ -233,6 +244,64 @@ class FFPVertexLightingGLTest {
state.run();
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), reason);
assertEquals(IDENTITY_FACTOR, factor, "a rejected solve must leave an identity factor");
assertBakeRejected(reason);
}
private static void assertBakeRejected(String reason) {
final int[] raw = new int[8];
Arrays.fill(raw, 0x5A3C1E0F);
final int[] before = raw.clone();
assertFalse(FFPVertexLighting.bakeColors(raw, 1, true, true), reason + ": bake must be rejected");
assertArrayEquals(before, raw, reason + ": a rejected bake must leave the buffer untouched");
}
@Test
void positionalLightIsNotBaked() {
GLSMLighting.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.0f, 0.0f, 1.0f, 1.0f));
assertBakeRejected("a positional light has a per-vertex direction");
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), "text keeps the object-origin approximation for positional lights");
}
static Stream<Arguments> bakeCases() {
return Stream.of(
Arguments.of("flattened face-on", -1.0e-4f, 0.0f, 0.0f, 1.0f, true, true),
Arguments.of("flattened top", -1.0e-4f, 0.0f, 1.0f, 0.0f, true, true),
Arguments.of("flattened side", -1.0e-4f, 1.0f, 0.0f, 0.0f, true, true),
Arguments.of("unflattened tilted", 1.0f, 0.3f, 0.8f, 0.5f, true, true),
Arguments.of("current color", -1.0e-4f, 0.0f, 1.0f, 0.0f, true, false),
Arguments.of("current normal", -1.0e-4f, 0.0f, 1.0f, 0.0f, false, true));
}
private static int packNormal(float x, float y, float z) {
return ((byte) (x * 127.0f) & 0xFF) | (((byte) (y * 127.0f) & 0xFF) << 8) | (((byte) (z * 127.0f) & 0xFF) << 16);
}
private static float unpackNormal(int packed, int shift) {
return (byte) (packed >> shift) / 127.0f;
}
@ParameterizedTest(name = "{0}")
@MethodSource("bakeCases")
void bakedColorMatchesGeneratedShader(String name, float zScale, float nx, float ny, float nz, boolean hasNormals, boolean hasColor) {
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
GLSMMatrices.glLoadIdentity();
GLSMMatrices.glScalef(1.0f, 1.0f, zScale);
standardItemLighting();
if (!hasColor) GLSMCurrentAttribs.glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
final int packedNormal = packNormal(nx, ny, nz);
final int[] raw = new int[8];
raw[5] = VERTEX_COLOR;
if (hasNormals) raw[6] = packedNormal;
GLSMCurrentAttribs.glNormal3f(unpackNormal(packedNormal, 0), unpackNormal(packedNormal, 8), unpackNormal(packedNormal, 16));
assertTrue(FFPVertexLighting.bakeColors(raw, 1, hasNormals, hasColor), name + ": expected a bakeable state, " + gateState());
final float[] actual = drawAndReadCenter(hasColor);
for (int c = 0; c < 3; c++) {
final int baked = (raw[5] >>> (c * 8)) & 0xFF;
final int shader = Math.round(actual[c] * 255.0f);
assertEquals(baked, shader, name + ": channel " + c + " baked=" + baked + " shader=" + shader);
}
}
@Test
@@ -3,21 +3,21 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
import com.gtnewhorizons.angelica.glsm.GLSMDepthStencilAlpha;
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
import com.gtnewhorizons.angelica.glsm.GLSMFog;
import com.gtnewhorizons.angelica.glsm.GLSMLighting;
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
import com.gtnewhorizons.angelica.glsm.GLSMTexEnvGen;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.nio.DoubleBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -32,12 +32,6 @@ class FfpUniformBlockDedupTest {
return GL30.glGetInteger(GL31.GL_UNIFORM_BUFFER_START, FFPUniformBlock.BINDING_POINT);
}
private static DoubleBuffer doubles(double... values) {
final DoubleBuffer buf = BufferUtils.createDoubleBuffer(values.length);
buf.put(values).flip();
return buf;
}
private static void resetState() {
GLSMMatrices.glMatrixMode(GL11.GL_TEXTURE);
GLSMMatrices.glLoadIdentity();
@@ -58,7 +52,8 @@ class FfpUniformBlockDedupTest {
GLSMLighting.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(1.0f, 1.0f, 1.0f, 1.0f));
GLSMLighting.glMaterialf(GL11.GL_FRONT, GL11.GL_SHININESS, 0.0f);
GLSMTexEnvGen.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(1.0f, 0.0f, 0.0f, 0.0f));
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 0.0, 0.0, 0.0));
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
ClipTestSupport.setPlane(0, 0.0, 0.0, 0.0, 0.0);
GLSMRasterizer.glLineStipple(1, (short) 0xFFFF);
GLSMRasterizer.glViewport(0, 0, 854, 480);
}
@@ -149,7 +144,10 @@ class FfpUniformBlockDedupTest {
addCategory(tests, "currentTexCoord", () -> GLSMCurrentAttribs.glTexCoord2f(0.125f, 0.375f));
addCategory(tests, "lightmapCoord", () -> GLStateManager.ctx().lastBrightnessX = 96.0f);
addCategory(tests, "texGenPlane", () -> GLSMTexEnvGen.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(0.5f, 0.0f, 0.0f, 0.25f)));
addCategory(tests, "clipPlane", () -> GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 1.0, 0.0, 7.0)));
addCategory(tests, "clipPlane", () -> {
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
ClipTestSupport.setPlane(0, 0.0, 1.0, 0.0, 7.0);
});
addCategory(tests, "alphaRef", () -> GLSMDepthStencilAlpha.glAlphaFunc(GL11.GL_GREATER, 0.35f));
addCategory(tests, "fogColor", () -> GLSMFog.glFog(GL11.GL_FOG_COLOR, floats(0.1f, 0.2f, 0.3f, 0.4f)));
addCategory(tests, "fogDensity", () -> GLSMFog.glFogf(GL11.GL_FOG_DENSITY, 0.75f));
@@ -277,6 +277,39 @@ class SpirvShaderTranslatorTest {
assertFalse(es.contains("!= 0u"), "no bool use-site should still be comparing against 0u");
}
@Test
void uniformBlock_staysABlockUnderGLES() {
final String src = """
#version 330 core
layout(std140) uniform Blk {
mat4 u_M;
vec4 u_C;
};
layout(std140) uniform Named {
vec4 u_N;
} named;
uniform float u_Loose;
struct L { vec4 c; };
uniform L u_Light;
in vec4 color;
out vec4 fragColor;
void main() {
fragColor = u_M * color * u_C * u_Loose + u_Light.c + named.u_N;
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "uniform_block.frag");
dumpOnFailure("uniform_block.frag", src, es);
assertNotNull(es);
assertTrue(Pattern.compile("layout\\s*\\(\\s*std140\\s*\\)\\s*uniform\\s+Blk\\s*\\{").matcher(es).find(), "a source uniform block must reach GLSL ES as a block, so glUniformBlockBinding finds it by name\n\n" + es);
assertFalse(Pattern.compile("uniform\\s+Blk\\s+_").matcher(es).find(), "the block must not be left as a plain struct uniform\n\n" + es);
assertFalse(Pattern.compile("_[0-9]+\\.u_M").matcher(es).find(), "an unnamed source block's members must be accessed bare\n\n" + es);
assertTrue(Pattern.compile("layout\\s*\\(\\s*std140\\s*\\)\\s*uniform\\s+Named\\s*\\{[^}]*\\}\\s*named\\s*;").matcher(es).find(), "an instance-named block keeps its instance name\n\n" + es);
assertTrue(es.contains("named.u_N"), "members of an instance-named block stay qualified\n\n" + es);
assertContainsDecl(es, "float", "u_Loose");
assertTrue(Pattern.compile("uniform\\s+L\\s+u_Light\\s*;").matcher(es).find(), "a struct uniform is not a block and must stay a plain uniform\n\n" + es);
}
@Test
void readonlyImage3D_staysAnImageUnderGLES() {
final String src = """
@@ -0,0 +1,37 @@
package com.gtnewhorizons.angelica.glsm.testutil;
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.DoubleBuffer;
public final class ClipTestSupport {
private ClipTestSupport() {}
public static void setPlane(int index, double a, double b, double c, double d) {
final DoubleBuffer eq = BufferUtils.createDoubleBuffer(4);
eq.put(a).put(b).put(c).put(d).flip();
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0 + index, eq);
}
public static Emulation emulation(boolean emulated) {
return new Emulation(emulated);
}
public static final class Emulation implements AutoCloseable {
private final boolean previous;
private Emulation(boolean emulated) {
previous = Reflect.getStatic(RenderSystem.class, "clipDistanceEmulated");
Reflect.setStatic(RenderSystem.class, "clipDistanceEmulated", emulated);
}
@Override
public void close() {
Reflect.setStatic(RenderSystem.class, "clipDistanceEmulated", previous);
}
}
}
+1
View File
@@ -36,6 +36,7 @@ tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configur
for (name in forwardedRunProperties) {
jvmArgs("-D$name=${System.getProperty(name)}")
}
jvmArgs("-Dangelica.buildVersion=${project.version}")
}
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
+76
View File
@@ -1,3 +1,5 @@
import java.time.Duration
plugins {
`java-library`
`maven-publish`
@@ -75,6 +77,80 @@ dependencies {
compileOnly(libs.annotations)
}
val glesTag = "gles"
val glsmRedirectAgent: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
val glsmLwjgl2Stubs: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
glsmRedirectAgent(project(":glsm", "redirectAgent"))
glsmLwjgl2Stubs(project(":glsm", "lwjgl2Stubs"))
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testImplementation(testFixtures(project(":glsm")))
testImplementation(project(":glsm")) {
capabilities { requireCapability("${project.group}:glsm-stubs") }
}
listOf(libs.lwjgl3.sdl, libs.lwjgl3.shaderc, libs.lwjgl3.spvc).forEach {
testImplementation(it)
testRuntimeOnly(it) { artifact { classifier = lwjglNatives } }
}
testRuntimeOnly(libs.junit.platform.launcher)
testRuntimeOnly(libs.log4j.core)
testRuntimeOnly(libs.fastutil)
testRuntimeOnly(libs.joml)
testRuntimeOnly(libs.commons.lang3)
testRuntimeOnly(libs.guava)
testRuntimeOnly(libs.celeritas.common) { isTransitive = false }
testRuntimeOnly(libs.asm.tree.test)
}
tasks.test {
useJUnitPlatform {
excludeTags(glesTag)
}
}
val unpackLwjgl3Natives = registerLwjgl3NativesUnpack()
val glesTest by tasks.registering(Test::class) {
description = "Boots GLSM on the LWJGL3 GL backend in a real OpenGL ES 3.2 context."
group = "verification"
testClassesDirs = sourceSets["test"].output.classesDirs
classpath = glsmLwjgl2Stubs.plus(
sourceSets["test"].runtimeClasspath.filter { file ->
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
}
)
useJUnitPlatform {
includeTags(glesTag)
}
systemProperty("glsm.stubs.capabilities", "lwjgl3")
javaAgent(glsmRedirectAgent)
loadLwjgl3NativesFrom(unpackLwjgl3Natives)
outputs.cacheIf { false }
timeout = Duration.ofMinutes(5)
systemProperty("junit.jupiter.execution.timeout.default", "60s")
systemProperty("junit.jupiter.execution.timeout.threaddump.enabled", "true")
testLogging {
events("passed", "skipped", "failed")
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
mustRunAfter(tasks.test)
}
if (!providers.environmentVariable("CI").isPresent) {
tasks.check { dependsOn(glesTest) }
}
tasks.named<JavaCompile>("compileJava") {
val classesDir = destinationDirectory
doLast { injectLwjgl3Aware(classesDir.get().asFile) }
@@ -10,6 +10,9 @@ import com.gtnewhorizons.angelica.glsm.GLScreenCapture;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.jetbrains.annotations.Nullable;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
import org.lwjgl.PointerBuffer;
@@ -48,6 +51,7 @@ import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDLEvents;
import org.lwjgl.sdl.SDLTimer;
import org.lwjgl.sdl.SDLVideo;
import org.lwjgl.system.FunctionProvider;
import org.lwjgl.system.JNI;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
@@ -97,14 +101,45 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|| normalizedVendor.startsWith("advanced micro devices");
}
}
final FunctionProvider functions = GL.getFunctionProvider();
if (RenderSystem.isGLES()) {
pfnClearDepthf = GL.getFunctionProvider().getFunctionAddress("glClearDepthf");
pfnDepthRangef = GL.getFunctionProvider().getFunctionAddress("glDepthRangef");
pfnClearDepthf = functions.getFunctionAddress("glClearDepthf");
pfnDepthRangef = functions.getFunctionAddress("glDepthRangef");
proxyTexture = new ProxyTexture(GL11C.glGetInteger(GL11C.GL_MAX_TEXTURE_SIZE), GL11C.glGetInteger(GL12C.GL_MAX_3D_TEXTURE_SIZE),
GL11C.glGetInteger(GL13C.GL_MAX_CUBE_MAP_TEXTURE_SIZE), GL11C.glGetInteger(GL30C.GL_MAX_ARRAY_TEXTURE_LAYERS));
}
pfnGenSamplers = requireFunction(functions, "glGenSamplers");
pfnDeleteSamplers = requireFunction(functions, "glDeleteSamplers");
pfnBindSampler = requireFunction(functions, "glBindSampler");
pfnSamplerParameteri = requireFunction(functions, "glSamplerParameteri");
pfnSamplerParameterf = requireFunction(functions, "glSamplerParameterf");
pfnIsSampler = requireFunction(functions, "glIsSampler");
pfnVertexAttribDivisor = requireFunction(functions, "glVertexAttribDivisor");
}
private static long requireFunction(FunctionProvider functions, String name) {
final long address = functions.getFunctionAddress(name);
if (address == 0L) throw new IllegalStateException(name + " is not exported by the GL driver");
return address;
}
private long pfnClearDepthf;
private long pfnDepthRangef;
private @Nullable ProxyTexture proxyTexture;
// GL33C slots stay unloaded on an ES 3.2 context: LWJGL builds desktop caps only up to the reported 3.2.
private long pfnGenSamplers;
private long pfnDeleteSamplers;
private long pfnBindSampler;
private long pfnSamplerParameteri;
private long pfnSamplerParameterf;
private long pfnIsSampler;
private long pfnVertexAttribDivisor;
private boolean recordProxy(int target, int level, int internalFormat, int width, int height, int depth) {
if (proxyTexture == null || !TextureInfoCache.isProxyTarget(target)) return false;
proxyTexture.record(target, level, internalFormat, width, height, depth, true);
return true;
}
@Override
public void shutdown() {
@@ -253,7 +288,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
public void finish() {GL11C.glFinish();}
private static boolean capAllowed(int cap) {
return GLESCaps.isCapAllowed(cap, RenderSystem.isGLES(), RenderSystem.hasClipCullDistance());
return GLESCaps.isCapAllowed(cap, RenderSystem.isGLES());
}
@Override
@@ -458,42 +493,42 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
}
@Override
public void drawArrays(int mode, int first, int count) {
protected void issueDrawArrays(int mode, int first, int count) {
GL11C.glDrawArrays(mode, first, count);
}
@Override
public void drawElements(int mode, int count, int type, long indices) {
protected void issueDrawElements(int mode, int count, int type, long indices) {
GL11C.glDrawElements(mode, count, type, indices);
}
@Override
public void drawElements(int mode, ByteBuffer indices) {
protected void issueDrawElements(int mode, ByteBuffer indices) {
GL11C.glDrawElements(mode, indices);
}
@Override
public void drawElements(int mode, IntBuffer indices) {
protected void issueDrawElements(int mode, IntBuffer indices) {
GL11C.glDrawElements(mode, indices);
}
@Override
public void drawElements(int mode, ShortBuffer indices) {
protected void issueDrawElements(int mode, ShortBuffer indices) {
GL11C.glDrawElements(mode, indices);
}
@Override
public void drawElements(int mode, int count, int type, ByteBuffer indices) {
protected void issueDrawElements(int mode, int count, int type, ByteBuffer indices) {
GL11C.glDrawElements(mode, count, type, MemoryUtil.memAddress(indices));
}
@Override
public void drawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
protected void issueDrawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
GL31C.glDrawElementsInstanced(mode, count, type, indices, primcount);
}
@Override
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
protected void issueDrawArraysInstanced(int mode, int first, int count, int primcount) {
GL31C.glDrawArraysInstanced(mode, first, count, primcount);
}
@@ -504,7 +539,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
}
@Override
public void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
protected void issueMultiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
GL43C.glMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
}
@@ -514,12 +549,12 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
}
@Override
public void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex) {
protected void issueDrawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex) {
GL32C.glDrawElementsBaseVertex(mode, count, type, indices, baseVertex);
}
@Override
public void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) {
protected void issueMultiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) {
GL32C.nglMultiDrawElementsBaseVertex(mode, pCount, type, pIndices, drawcount, pBaseVertex);
}
@@ -565,26 +600,31 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
}
@Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, DoubleBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
}
@Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, FloatBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
}
@Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
}
@Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixelBufferOffset) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixelBufferOffset);
}
@@ -605,16 +645,19 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void texImage1D(int target, int level, int internalFormat, int width, int border, int format, int type, ByteBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, 1, 1)) return;
GL11C.glTexImage1D(target, level, internalFormat, width, border, format, type, pixels);
}
@Override
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, depth)) return;
GL12C.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels);
}
@Override
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, depth)) return;
GL12C.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels);
}
@@ -686,6 +729,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public int getTexLevelParameteri(int target, int level, int pname) {
if (proxyTexture != null && TextureInfoCache.isProxyTarget(target)) return proxyTexture.levelParameter(target, level, pname);
return GL11C.glGetTexLevelParameteri(target, level, pname);
}
@@ -706,27 +750,33 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public int genSamplers() {
return GL33C.glGenSamplers();
try (MemoryStack stack = MemoryStack.stackPush()) {
final IntBuffer ids = stack.mallocInt(1);
JNI.callPV(1, MemoryUtil.memAddress(ids), pfnGenSamplers);
return ids.get(0);
}
}
@Override
public void deleteSamplers(int sampler) {
GL33C.glDeleteSamplers(sampler);
try (MemoryStack stack = MemoryStack.stackPush()) {
JNI.callPV(1, MemoryUtil.memAddress(stack.ints(sampler)), pfnDeleteSamplers);
}
}
@Override
public void bindSampler(int unit, int sampler) {
GL33C.glBindSampler(unit, sampler);
JNI.callV(unit, sampler, pfnBindSampler);
}
@Override
public void samplerParameteri(int sampler, int pname, int param) {
GL33C.glSamplerParameteri(sampler, pname, param);
JNI.callV(sampler, pname, param, pfnSamplerParameteri);
}
@Override
public void samplerParameterf(int sampler, int pname, float param) {
GL33C.glSamplerParameterf(sampler, pname, param);
JNI.callV(sampler, pname, param, pfnSamplerParameterf);
}
@Override
@@ -1371,7 +1421,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override public boolean isFramebuffer(int framebuffer) { return GL30C.glIsFramebuffer(framebuffer); }
@Override public boolean isRenderbuffer(int renderbuffer) { return GL30C.glIsRenderbuffer(renderbuffer); }
@Override public boolean isSampler(int sampler) { return GL33C.glIsSampler(sampler); }
@Override public boolean isSampler(int sampler) { return JNI.callZ(sampler, pfnIsSampler); }
@Override public boolean isQuery(int query) { return GL15C.glIsQuery(query); }
@Override
@@ -1441,7 +1491,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void vertexAttribDivisor(int index, int divisor) {
GL33C.glVertexAttribDivisor(index, divisor);
JNI.callV(index, divisor, pfnVertexAttribDivisor);
}
@Override
@@ -1834,7 +1884,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
}
@Override
public void multiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts) {
protected void issueMultiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts) {
GL14C.glMultiDrawArrays(mode, firsts, counts);
}
@@ -0,0 +1,234 @@
package com.gtnewhorizons.angelica.lwjgl3;
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
import com.gtnewhorizons.angelica.glsm.GLSMBlend;
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
import com.gtnewhorizons.angelica.glsm.GLSMFramebuffer;
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
import com.gtnewhorizons.angelica.glsm.GLSMTextures;
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.opengl.GL12C;
import org.lwjgl.opengl.GL20C;
import org.lwjgl.opengl.GL30C;
import org.lwjgl.opengl.GL33C;
import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDLInit;
import org.lwjgl.sdl.SDLVideo;
import org.lwjgl.system.Configuration;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("gles")
@DisabledOnOs(OS.MAC)
class GlesSmokeTest {
private static final int SIZE = 64;
private static int fbo;
@BeforeAll
static void boot() {
Configuration.OPENGL_EXPLICIT_INIT.set(true);
assertTrue(SDLInit.SDL_Init(SDLInit.SDL_INIT_VIDEO), () -> "SDL_Init failed: " + SDLError.SDL_GetError());
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_PROFILE_MASK, SDLVideo.SDL_GL_CONTEXT_PROFILE_ES);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_DEPTH_SIZE, 24);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_STENCIL_SIZE, 8);
final long window = SDLVideo.SDL_CreateWindow("glesSmoke", SIZE, SIZE, SDLVideo.SDL_WINDOW_OPENGL | SDLVideo.SDL_WINDOW_HIDDEN);
assertNotEquals(0L, window, () -> "SDL_CreateWindow failed: " + SDLError.SDL_GetError());
final long context = SDLVideo.SDL_GL_CreateContext(window);
assertNotEquals(0L, context, () -> "SDL_GL_CreateContext failed: " + SDLError.SDL_GetError());
assertTrue(SDLVideo.SDL_GL_MakeCurrent(window, context), () -> "SDL_GL_MakeCurrent failed: " + SDLError.SDL_GetError());
GL.create(SDLVideo::SDL_GL_GetProcAddress);
GL.createCapabilities();
// LWJGL's GL_CONTEXT_PROFILE_MASK probe in createCapabilities leaves INVALID_ENUM on an ES context.
while (GL11C.glGetError() != GL11C.GL_NO_ERROR) {}
final String version = GL11C.glGetString(GL11C.GL_VERSION);
assertTrue(version != null && version.startsWith("OpenGL ES 3."), () -> "not an ES 3.x context: " + version);
assertTrue(BackendManager.RENDER_BACKEND instanceof Lwjgl3GLRenderBackend, () -> "selected backend is " + BackendManager.RENDER_BACKEND.getName());
GLStateManager.initialize(GLSMInitConfig.builder().displaySize(SIZE, SIZE).build());
GLStateManager.setRunningSplash(false);
GLStateManager.markSplashComplete("glesSmoke");
assertTrue(RenderSystem.isGLES());
assertTrue(RenderSystem.supportsSamplerObjects());
fbo = createTarget();
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void samplerLifecycle() {
final int sampler = RenderSystem.genSampler();
assertNotEquals(0, sampler);
RenderSystem.samplerParameteri(sampler, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_LINEAR);
RenderSystem.samplerParameterf(sampler, GL12C.GL_TEXTURE_MAX_LOD, 4.0f);
RenderSystem.bindSamplerToUnit(0, sampler);
assertEquals(sampler, GL11C.glGetInteger(GL33C.GL_SAMPLER_BINDING));
assertTrue(BackendManager.RENDER_BACKEND.isSampler(sampler));
RenderSystem.destroySampler(sampler);
assertFalse(BackendManager.RENDER_BACKEND.isSampler(sampler));
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void vertexAttribDivisor() {
final int vao = GLSMVertexInput.glGenVertexArrays();
GLSMVertexInput.glBindVertexArray(vao);
GLSMVertexInput.glVertexAttribDivisor(5, 1);
assertEquals(1, GL20C.glGetVertexAttribi(5, GL33C.GL_VERTEX_ATTRIB_ARRAY_DIVISOR));
GLSMVertexInput.glBindVertexArray(0);
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void ffpQuadRenders() {
bindClearedTarget();
GLSMDraws.glBegin(GL11C.GL_QUADS);
GLSMCurrentAttribs.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
GLSMDraws.glVertex3f(-1.0f, -1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, -1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, 1.0f, 0.0f);
GLSMDraws.glVertex3f(-1.0f, 1.0f, 0.0f);
GLSMDraws.glEnd();
assertEquals(0xFF00FF00, readCenterPixel());
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void ffpTexturedQuadRenders() {
final int texel = 0xFF2060A0;
final int texture = GLSMTextures.glGenTextures();
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, texture);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST);
final IntBuffer pixels = MemoryUtil.memAllocInt(16);
try {
for (int i = 0; i < 16; i++) pixels.put(i, texel);
GLSMTextures.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, GL11C.GL_RGBA, 4, 4, 0, GL12C.GL_BGRA, GL12C.GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
} finally {
MemoryUtil.memFree(pixels);
}
bindClearedTarget();
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, texture);
GLSMEnable.glEnable(GL11C.GL_TEXTURE_2D);
GLSMDraws.glBegin(GL11C.GL_QUADS);
GLSMCurrentAttribs.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLSMCurrentAttribs.glTexCoord2f(0.0f, 0.0f);
GLSMDraws.glVertex3f(-1.0f, -1.0f, 0.0f);
GLSMCurrentAttribs.glTexCoord2f(1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, -1.0f, 0.0f);
GLSMCurrentAttribs.glTexCoord2f(1.0f, 1.0f);
GLSMDraws.glVertex3f(1.0f, 1.0f, 0.0f);
GLSMCurrentAttribs.glTexCoord2f(0.0f, 1.0f);
GLSMDraws.glVertex3f(-1.0f, 1.0f, 0.0f);
GLSMDraws.glEnd();
GLSMEnable.glDisable(GL11C.GL_TEXTURE_2D);
assertEquals(texel, readCenterPixel());
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@ParameterizedTest(name = "emulated={0}")
@ValueSource(booleans = {false, true})
void clipPlaneKeepsOnlyTheEnabledHalfSpace(boolean emulated) {
try (var ignored = ClipTestSupport.emulation(emulated)) {
try {
bindClearedTarget();
ClipTestSupport.setPlane(1, 0.0, 0.0, 0.0, -1.0);
ClipTestSupport.setPlane(0, 1.0, 0.0, 0.0, 0.0);
GLSMEnable.glEnable(GL30C.GL_CLIP_DISTANCE0);
GLSMDraws.glBegin(GL11C.GL_QUADS);
GLSMCurrentAttribs.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
GLSMDraws.glVertex3f(-1.0f, -1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, -1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, 1.0f, 0.0f);
GLSMDraws.glVertex3f(-1.0f, 1.0f, 0.0f);
GLSMDraws.glEnd();
assertEquals(0xFF000000, readPixel(SIZE / 4, SIZE / 2), "x < 0 must be clipped by plane 0");
assertEquals(0xFF00FF00, readPixel(3 * SIZE / 4, SIZE / 2), "x >= 0 must survive; plane 1 set but not enabled must not clip");
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
} finally {
GLSMEnable.glDisable(GL30C.GL_CLIP_DISTANCE0);
}
}
}
@Test
void proxyTextureProbeFindsTheMaxTextureSize() {
final int maxSize = Integer.highestOneBit(GL11C.glGetInteger(GL11C.GL_MAX_TEXTURE_SIZE));
int found = -1;
for (int size = maxSize * 2; size > 0 && found < 0; size >>= 1) {
GLSMTextures.glTexImage2D(GL11C.GL_PROXY_TEXTURE_2D, 0, GL11C.GL_RGBA, size, size, 0, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, (ByteBuffer) null);
if (GLSMTextures.glGetTexLevelParameteri(GL11C.GL_PROXY_TEXTURE_2D, 0, GL11C.GL_TEXTURE_WIDTH) != 0) found = size;
}
assertEquals(maxSize, found);
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
private static void bindClearedTarget() {
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, fbo);
GLSMRasterizer.glViewport(0, 0, SIZE, SIZE);
GLSMBlend.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLSMFramebuffer.glClear(GL11C.GL_COLOR_BUFFER_BIT | GL11C.GL_DEPTH_BUFFER_BIT);
}
private static int readCenterPixel() {
return readPixel(SIZE / 2, SIZE / 2);
}
private static int readPixel(int x, int y) {
final ByteBuffer pixel = MemoryUtil.memAlloc(4);
try {
GLSMFramebuffer.glReadPixels(x, y, 1, 1, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, pixel);
return (pixel.get(3) & 0xFF) << 24 | (pixel.get(0) & 0xFF) << 16 | (pixel.get(1) & 0xFF) << 8 | pixel.get(2) & 0xFF;
} finally {
MemoryUtil.memFree(pixel);
}
}
private static int createTarget() {
final int color = GLSMTextures.glGenTextures();
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, color);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST);
GLSMTextures.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, GL11C.GL_RGBA8, SIZE, SIZE, 0, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, (ByteBuffer) null);
final int depth = GLSMFramebuffer.glGenRenderbuffers();
GLSMFramebuffer.glBindRenderbuffer(GL30C.GL_RENDERBUFFER, depth);
GLSMFramebuffer.glRenderbufferStorage(GL30C.GL_RENDERBUFFER, GL30C.GL_DEPTH24_STENCIL8, SIZE, SIZE);
final int target = GLSMFramebuffer.glGenFramebuffers();
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, target);
GLSMFramebuffer.glFramebufferTexture2D(GL30C.GL_FRAMEBUFFER, GL30C.GL_COLOR_ATTACHMENT0, GL11C.GL_TEXTURE_2D, color, 0);
GLSMFramebuffer.glFramebufferRenderbuffer(GL30C.GL_FRAMEBUFFER, GL30C.GL_DEPTH_STENCIL_ATTACHMENT, GL30C.GL_RENDERBUFFER, depth);
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, 0);
return target;
}
}
+8
View File
@@ -0,0 +1,8 @@
# Clip-plane scene for -Dangelica.debug.testBlocks=true. ~ is the flight start, yaw 0 faces south (+z).
# Each angelica:test_clip_plane TESR keeps only the lower half of a gold cube (plane 0) and leaves a reject-all plane 1 disabled.
gamerule doMobSpawning false
gamerule doDaylightCycle false
time set 6000
fill ~-2 ~-1 ~4 ~2 ~0 ~4 angelica:test_clip_plane
@@ -0,0 +1,83 @@
# Translucent TESR ordering scene for a generated superflat (windows-flyby-kit/make_world.py). Needs
# -Dangelica.debug.testBlocks=true. $x $y $z is the flight start; ground-standing level is $y-2. Start facing south (yaw 0),
# pitch 15. Blocks: test_tesr_provider (0 translucent red, 1 additive purple), test_tesr_model (0 box pane,
# 1 Tessellator pane, 2 stacked A/B/A panes), test_tesr_decal, test_tesr_glow; metadata + 8 draws in the solid pass.
gamerule doMobSpawning false
# translucent pass: three rows, materials shifted per row so every column stacks three different ones
setblock $x-2 $y $z+6 angelica:test_tesr_provider 0 replace
setblock $x-1 $y $z+6 angelica:test_tesr_provider 1 replace
setblock $x+0 $y $z+6 angelica:test_tesr_model 0 replace
setblock $x+1 $y $z+6 angelica:test_tesr_model 1 replace
setblock $x-2 $y $z+8 angelica:test_tesr_provider 1 replace
setblock $x-1 $y $z+8 angelica:test_tesr_model 0 replace
setblock $x+0 $y $z+8 angelica:test_tesr_model 1 replace
setblock $x+1 $y $z+8 angelica:test_tesr_provider 0 replace
setblock $x-2 $y $z+10 angelica:test_tesr_model 0 replace
setblock $x-1 $y $z+10 angelica:test_tesr_model 1 replace
setblock $x+0 $y $z+10 angelica:test_tesr_provider 0 replace
setblock $x+1 $y $z+10 angelica:test_tesr_provider 1 replace
# solid pass: the same rows two blocks higher
setblock $x-2 $y+2 $z+6 angelica:test_tesr_provider 8 replace
setblock $x-1 $y+2 $z+6 angelica:test_tesr_provider 9 replace
setblock $x+0 $y+2 $z+6 angelica:test_tesr_model 8 replace
setblock $x+1 $y+2 $z+6 angelica:test_tesr_model 9 replace
setblock $x-2 $y+2 $z+8 angelica:test_tesr_provider 9 replace
setblock $x-1 $y+2 $z+8 angelica:test_tesr_model 8 replace
setblock $x+0 $y+2 $z+8 angelica:test_tesr_model 9 replace
setblock $x+1 $y+2 $z+8 angelica:test_tesr_provider 8 replace
setblock $x-2 $y+2 $z+10 angelica:test_tesr_model 8 replace
setblock $x-1 $y+2 $z+10 angelica:test_tesr_model 9 replace
setblock $x+0 $y+2 $z+10 angelica:test_tesr_provider 8 replace
setblock $x+1 $y+2 $z+10 angelica:test_tesr_provider 9 replace
# one tile entity submitting translucent A, additive B, translucent A; a depth-equal decal
setblock $x-7 $y $z+8 angelica:test_tesr_model 2 replace
setblock $x-7 $y+1 $z+8 angelica:test_tesr_decal 0 replace
# light blue glass wall: glow and provider panes in front, a provider pane behind. The panes behind the wall and
# under the water show only when block entities draw before translucent terrain.
setblock $x+5 $y-1 $z+14 stained_glass 3 replace
setblock $x+5 $y $z+14 stained_glass 3 replace
setblock $x+5 $y+1 $z+14 stained_glass 3 replace
setblock $x+6 $y-1 $z+14 stained_glass 3 replace
setblock $x+6 $y $z+14 stained_glass 3 replace
setblock $x+6 $y+1 $z+14 stained_glass 3 replace
setblock $x+7 $y-1 $z+14 stained_glass 3 replace
setblock $x+7 $y $z+14 stained_glass 3 replace
setblock $x+7 $y+1 $z+14 stained_glass 3 replace
setblock $x+8 $y-1 $z+14 stained_glass 3 replace
setblock $x+8 $y $z+14 stained_glass 3 replace
setblock $x+8 $y+1 $z+14 stained_glass 3 replace
setblock $x+9 $y-1 $z+14 stained_glass 3 replace
setblock $x+9 $y $z+14 stained_glass 3 replace
setblock $x+9 $y+1 $z+14 stained_glass 3 replace
setblock $x+6 $y $z+13 angelica:test_tesr_glow 0 replace
setblock $x+8 $y $z+13 angelica:test_tesr_provider 0 replace
setblock $x+7 $y $z+15 angelica:test_tesr_provider 0 replace
# water pool with a provider pane under the surface
setblock $x-10 $y-4 $z+12 water 0 replace
setblock $x-10 $y-3 $z+12 water 0 replace
setblock $x-10 $y-4 $z+13 water 0 replace
setblock $x-10 $y-3 $z+13 water 0 replace
setblock $x-10 $y-4 $z+14 water 0 replace
setblock $x-10 $y-3 $z+14 water 0 replace
setblock $x-9 $y-4 $z+12 water 0 replace
setblock $x-9 $y-3 $z+12 water 0 replace
setblock $x-9 $y-4 $z+13 water 0 replace
setblock $x-9 $y-3 $z+13 water 0 replace
setblock $x-9 $y-4 $z+14 water 0 replace
setblock $x-9 $y-3 $z+14 water 0 replace
setblock $x-8 $y-4 $z+12 water 0 replace
setblock $x-8 $y-3 $z+12 water 0 replace
setblock $x-8 $y-4 $z+13 water 0 replace
setblock $x-8 $y-3 $z+13 water 0 replace
setblock $x-8 $y-4 $z+14 water 0 replace
setblock $x-8 $y-3 $z+14 water 0 replace
setblock $x-9 $y-4 $z+13 angelica:test_tesr_provider 0 replace
# control: a lone pane with sky behind it
setblock $x+5 $y+2 $z+8 angelica:test_tesr_provider 0 replace
@@ -0,0 +1,58 @@
# Translucency layer scene for a generated superflat (windows-flyby-kit/make_world.py). $x $y $z is the flight start;
# ground-standing level is $y-2. Start facing south (yaw 0), pitch 15. Use with -Dangelica.flyby.weather=RAIN. Torch
# flame and smoke and rain fall in front of a light blue glass wall and a water pool; one torch row stands behind the glass.
gamerule doMobSpawning false
# glass wall
setblock $x-1 $y-2 $z+9 stained_glass 3 replace
setblock $x-1 $y-1 $z+9 stained_glass 3 replace
setblock $x-1 $y+0 $z+9 stained_glass 3 replace
setblock $x+0 $y-2 $z+9 stained_glass 3 replace
setblock $x+0 $y-1 $z+9 stained_glass 3 replace
setblock $x+0 $y+0 $z+9 stained_glass 3 replace
setblock $x+1 $y-2 $z+9 stained_glass 3 replace
setblock $x+1 $y-1 $z+9 stained_glass 3 replace
setblock $x+1 $y+0 $z+9 stained_glass 3 replace
setblock $x+2 $y-2 $z+9 stained_glass 3 replace
setblock $x+2 $y-1 $z+9 stained_glass 3 replace
setblock $x+2 $y+0 $z+9 stained_glass 3 replace
setblock $x+3 $y-2 $z+9 stained_glass 3 replace
setblock $x+3 $y-1 $z+9 stained_glass 3 replace
setblock $x+3 $y+0 $z+9 stained_glass 3 replace
# torches in front of and behind the glass
setblock $x-1 $y-2 $z+7 torch 5 replace
setblock $x-1 $y-2 $z+11 torch 5 replace
setblock $x+0 $y-2 $z+7 torch 5 replace
setblock $x+0 $y-2 $z+11 torch 5 replace
setblock $x+1 $y-2 $z+7 torch 5 replace
setblock $x+1 $y-2 $z+11 torch 5 replace
setblock $x+2 $y-2 $z+7 torch 5 replace
setblock $x+2 $y-2 $z+11 torch 5 replace
setblock $x+3 $y-2 $z+7 torch 5 replace
setblock $x+3 $y-2 $z+11 torch 5 replace
# water pool with torches in front of it
setblock $x-6 $y-3 $z+7 water 0 replace
setblock $x-6 $y-3 $z+8 water 0 replace
setblock $x-6 $y-3 $z+9 water 0 replace
setblock $x-6 $y-3 $z+10 water 0 replace
setblock $x-5 $y-3 $z+7 water 0 replace
setblock $x-5 $y-3 $z+8 water 0 replace
setblock $x-5 $y-3 $z+9 water 0 replace
setblock $x-5 $y-3 $z+10 water 0 replace
setblock $x-4 $y-3 $z+7 water 0 replace
setblock $x-4 $y-3 $z+8 water 0 replace
setblock $x-4 $y-3 $z+9 water 0 replace
setblock $x-4 $y-3 $z+10 water 0 replace
setblock $x-3 $y-3 $z+7 water 0 replace
setblock $x-3 $y-3 $z+8 water 0 replace
setblock $x-3 $y-3 $z+9 water 0 replace
setblock $x-3 $y-3 $z+10 water 0 replace
setblock $x-2 $y-3 $z+7 water 0 replace
setblock $x-2 $y-3 $z+8 water 0 replace
setblock $x-2 $y-3 $z+9 water 0 replace
setblock $x-2 $y-3 $z+10 water 0 replace
setblock $x-5 $y-2 $z+6 torch 5 replace
setblock $x-3 $y-2 $z+6 torch 5 replace
+7 -1
View File
@@ -106,7 +106,13 @@ val glsmSdlAgent = sourceSets.create("glsmSdlAgent") {
java.srcDir("src/glsmSdlAgent/java")
}
val glsmLwjgl2Stubs: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
glsmLwjgl2Stubs(project(":glsm", "lwjgl2Stubs"))
"glsmSdlAgentCompileOnly"(project(":glsm"))
"glsmSdlAgentCompileOnly"(testFixtures(project(":glsm")))
"glsmSdlAgentCompileOnly"(libs.asm.tree.test)
@@ -148,7 +154,7 @@ val glsmSdlTest by tasks.registering(Test::class) {
description = "Runs the headless GLSM-on-SDL FFP rig in its own JVM, with the SDL GPU gate engaged."
group = "verification"
testClassesDirs = sourceSets["test"].output.classesDirs
classpath = glsmSdlStubs.output.classesDirs.plus(
classpath = glsmSdlStubs.output.classesDirs.plus(glsmLwjgl2Stubs).plus(
sourceSets["test"].runtimeClasspath.filter { file ->
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
}
@@ -1,12 +0,0 @@
package org.lwjgl.opengl;
public final class GLContext {
private static final ContextCapabilities CAPABILITIES = new ContextCapabilities();
private GLContext() {}
public static ContextCapabilities getCapabilities() {
return CAPABILITIES;
}
}
@@ -20,6 +20,7 @@ import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
import com.gtnewhorizons.angelica.sdlgpu.compute.ComputeBinder;
@@ -113,6 +114,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import org.lwjgl.util.spvc.Spvc;
import org.lwjgl.sdl.SDLGPU;
import org.lwjgl.sdl.SDL_GPUBlitInfo;
import org.lwjgl.sdl.SDLTimer;
@@ -1226,7 +1228,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
@Override public void drawArrays(int mode, int first, int count) {
@Override protected void issueDrawArrays(int mode, int first, int count) {
if (count <= 0) return;
final ContextState st = s();
if (st.rasterizerDiscard) return;
@@ -1248,7 +1250,7 @@ public class SDLGPURenderBackend extends RenderBackend {
captureVertexWrites(st, first, count, 1, 0, 0, 0);
}
@Override public void drawElements(int mode, int count, int type, long indices) {
@Override protected void issueDrawElements(int mode, int count, int type, long indices) {
final ContextState st = s();
final long rp = prepareIndexedDrawBind(st, mode, type, "drawElements");
if (rp == 0) return;
@@ -1258,7 +1260,7 @@ public class SDLGPURenderBackend extends RenderBackend {
captureVertexWrites(st, 0, count, 1, type, indices, 0);
}
@Override public void drawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
@Override protected void issueDrawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
final ContextState st = s();
final long rp = prepareIndexedDrawBind(st, mode, type, "drawElementsInstanced");
if (rp == 0) return;
@@ -1270,7 +1272,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex) {
@Override protected void issueDrawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex) {
final ContextState st = s();
final long rp = prepareIndexedDrawBind(st, mode, type, "drawElementsBaseVertex");
if (rp == 0) return;
@@ -1295,7 +1297,7 @@ public class SDLGPURenderBackend extends RenderBackend {
return rp;
}
@Override public void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) {
@Override protected void issueMultiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) {
if (drawcount <= 0) return;
final ContextState st = s();
final long rp = prepareIndexedDrawBind(st, mode, type, "multiDrawElementsBaseVertex");
@@ -1315,7 +1317,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
@Override public void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
@Override protected void issueMultiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
final ContextState cs = s();
if (drawcount <= 0) return;
Tracy.beginZone(Z_SDL_INDIRECT_DRAW);
@@ -1507,52 +1509,8 @@ public class SDLGPURenderBackend extends RenderBackend {
return TextureInfoCache.isProxyTarget(target);
}
private static boolean proxyLayersInHeight(int target) {
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY;
}
private static boolean proxyLayersInDepth(int target) {
return isProxyArrayTarget(target) && !proxyLayersInHeight(target);
}
private static boolean isProxyArrayTarget(int target) {
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY || target == GL30.GL_PROXY_TEXTURE_2D_ARRAY || target == GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY || target == GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY;
}
public static int proxyLevelSize(int base, int level) {
return PixelOps.mipLevelSize(base, level);
}
static int proxyDimensionLimit(int target) {
return switch (target) {
case GL12.GL_PROXY_TEXTURE_3D -> MAX_3D_TEXTURE_SIZE;
case GL13.GL_PROXY_TEXTURE_CUBE_MAP, GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> MAX_CUBE_MAP_TEXTURE_SIZE;
default -> MAX_TEXTURE_SIZE;
};
}
static int levelZeroEquivalentSize(int size, int level) {
if (size <= 0 || level < 0 || level >= Integer.SIZE - 1) return 0;
final long scaled = (long) size << level;
return scaled > Integer.MAX_VALUE ? 0 : (int) scaled;
}
private void recordProxyTexImage(ContextState st, int target, int level, int internalFormat, int width, int height, int depth) {
final boolean layersInHeight = proxyLayersInHeight(target);
final boolean layersInDepth = proxyLayersInDepth(target);
final int dimLimit = proxyDimensionLimit(target);
final int w = levelZeroEquivalentSize(width, level);
final int h = layersInHeight ? height : levelZeroEquivalentSize(height, level);
final int d = layersInDepth ? depth : levelZeroEquivalentSize(depth, level);
final int hLimit = layersInHeight ? MAX_ARRAY_TEXTURE_LAYERS : dimLimit;
final int dLimit = layersInDepth ? MAX_ARRAY_TEXTURE_LAYERS : dimLimit;
final boolean fits = w >= 1 && w <= dimLimit && h >= 1 && h <= hLimit && d >= 1 && d <= dLimit
&& resourceManager.isMappableTextureFormat(internalFormat);
if (fits) {
st.proxyTexture.accept(target, w, h, d, internalFormat);
} else {
st.proxyTexture.reject(target);
}
st.proxyTexture.record(target, level, internalFormat, width, height, depth, resourceManager.isMappableTextureFormat(internalFormat));
}
private static int boundTextureFor(ContextState st, int target) {
@@ -1816,23 +1774,13 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public int getTexLevelParameteri(int target, int level, int pname) {
final ContextState cs = s();
if (isProxyTarget(target)) {
final ContextState.ProxyTextureState proxy = cs.proxyTexture;
if (proxy.target != target) return 0;
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> proxyLevelSize(proxy.width, level);
case GL11.GL_TEXTURE_HEIGHT -> proxyLayersInHeight(target) ? proxy.height : proxyLevelSize(proxy.height, level);
case GL12.GL_TEXTURE_DEPTH -> proxyLayersInDepth(target) ? proxy.depth : proxyLevelSize(proxy.depth, level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> proxy.internalFormat;
default -> 0;
};
}
if (isProxyTarget(target)) return cs.proxyTexture.levelParameter(target, level, pname);
final int glId = cs.boundTextures[cs.activeTextureUnit];
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null) return 0;
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(meta.width(), level);
case GL11.GL_TEXTURE_HEIGHT -> TextureInfo.mipLevelSize(meta.height(), level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
final int stored = meta.glFormat();
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
@@ -1987,8 +1935,8 @@ public class SDLGPURenderBackend extends RenderBackend {
if (meta != null) {
fbo.colorFormats[colorIdx] = meta.sdlFormat();
if (colorIdx == 0) {
fbo.width = PixelOps.mipLevelSize(meta.width(), level);
fbo.height = PixelOps.mipLevelSize(meta.height(), level);
fbo.width = TextureInfo.mipLevelSize(meta.width(), level);
fbo.height = TextureInfo.mipLevelSize(meta.height(), level);
}
}
fbo.colorAttachmentCount = Math.max(fbo.colorAttachmentCount, colorIdx + 1);
@@ -2216,8 +2164,8 @@ public class SDLGPURenderBackend extends RenderBackend {
out.level = fbo.colorLevels[fbo.readBufferIndex];
out.sdlFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
out.glBaseFormat = meta != null ? PixelPack.glBaseFormat(meta.glFormat()) : GL11.GL_RGBA;
out.width = meta != null ? PixelOps.mipLevelSize(meta.width(), out.level) : fbo.width;
out.height = meta != null ? PixelOps.mipLevelSize(meta.height(), out.level) : fbo.height;
out.width = meta != null ? TextureInfo.mipLevelSize(meta.width(), out.level) : fbo.width;
out.height = meta != null ? TextureInfo.mipLevelSize(meta.height(), out.level) : fbo.height;
return out;
}
@@ -2283,8 +2231,8 @@ public class SDLGPURenderBackend extends RenderBackend {
final int glId = boundTextureFor(cs, target);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null || level < 0 || level >= meta.levels()) return;
final int width = PixelOps.mipLevelSize(meta.width(), level);
final int height = PixelOps.mipLevelSize(meta.height(), level);
final int width = TextureInfo.mipLevelSize(meta.width(), level);
final int height = TextureInfo.mipLevelSize(meta.height(), level);
if (width <= 0 || height <= 0) return;
checkReadbackCapacity(format, type, width, height, loadPackState(cs), capacity);
final int kind = readbackKind(format);
@@ -2600,17 +2548,23 @@ public class SDLGPURenderBackend extends RenderBackend {
if (length != null && length.remaining() > 0) length.put(0, 0);
return;
}
if (s == null) return;
final byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
final int n = Math.min(bytes.length, out.remaining());
out.put(bytes, 0, n);
final byte[] bytes = (s == null ? "" : s).getBytes(StandardCharsets.UTF_8);
final int pos = out.position();
final int max = out.remaining();
final int n = Math.max(0, Math.min(bytes.length, max - 1));
for (int i = 0; i < n; i++) out.put(pos + i, bytes[i]);
if (max > 0) out.put(pos + n, (byte) 0);
if (length != null && length.remaining() > 0) length.put(0, n);
}
private static int glStringLength(String s) {
return s == null || s.isEmpty() ? 0 : s.getBytes(StandardCharsets.UTF_8).length + 1;
}
@Override public int getShaderi(int shader, int pname) {
return switch (pname) {
case GL20.GL_COMPILE_STATUS -> shaderManager.isCompiled(shader) ? 1 : 0;
case GL20.GL_SHADER_TYPE -> shaderManager.getShaderType(shader);
case GL20.GL_INFO_LOG_LENGTH -> shaderManager.getShaderInfoLog(shader).length();
case GL20.GL_INFO_LOG_LENGTH -> glStringLength(shaderManager.getShaderInfoLog(shader));
case GL20.GL_SHADER_SOURCE_LENGTH -> glStringLength(shaderManager.getShaderSource(shader));
default -> 0;
};
}
@@ -2618,7 +2572,7 @@ public class SDLGPURenderBackend extends RenderBackend {
return switch (pname) {
case GL20.GL_LINK_STATUS -> shaderManager.isLinked(program) ? 1 : 0;
case GL20.GL_VALIDATE_STATUS -> shaderManager.isLinked(program) ? 1 : 0;
case GL20.GL_INFO_LOG_LENGTH -> shaderManager.getProgramInfoLog(program).length();
case GL20.GL_INFO_LOG_LENGTH -> glStringLength(shaderManager.getProgramInfoLog(program));
case GL20.GL_ATTACHED_SHADERS -> shaderManager.getAttachedShaderCount(program);
case GL20.GL_ACTIVE_UNIFORMS -> {
final var entries = getOrBuildUniformEntries(program);
@@ -2658,6 +2612,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final int loc = e.getValue();
int glType;
int arrayLen = 1;
boolean array = false;
if (prog.allSamplerNames.contains(name)) {
glType = GL20.GL_SAMPLER_2D;
} else {
@@ -2666,6 +2621,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (umi != null) {
glType = FormatMap.mapMemberInfoToGlType(umi);
arrayLen = Math.max(1, umi.arrayLen());
array = umi.arrayStride() > 0;
} else {
glType = GL20.GL_FLOAT_VEC4;
}
@@ -2673,7 +2629,7 @@ public class SDLGPURenderBackend extends RenderBackend {
glType = FormatMap.remapUnsignedIntToBool(glType);
}
}
result.add(new UniformEntry(name, glType, arrayLen));
result.add(new UniformEntry(array ? name + "[0]" : name, glType, arrayLen));
}
result.sort(Comparator.comparing(UniformEntry::name));
final List<UniformEntry> immutable = Collections.unmodifiableList(result);
@@ -2732,6 +2688,78 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public int getUniformLocation(int program, ByteBuffer name) {
return shaderManager.getUniformLocation(program, MemoryUtil.memASCII(name));
}
@Override public void getUniformfv(int program, int location, FloatBuffer params) {
final Readback rb = readback(program, location);
if (rb == null) return;
final int pos = params.position();
final int count = Math.min(rb.count(), params.remaining());
for (int i = 0; i < count; i++) {
final float raw = rb.raw(i);
final int bits = Float.floatToRawIntBits(raw);
params.put(pos + i, switch (rb.type()) {
case FLOAT -> raw;
case INT -> (float) bits;
case UINT -> (float) (bits & 0xFFFFFFFFL);
case BOOL -> bits != 0 ? 1f : 0f;
case OPAQUE -> rb.opaqueValue();
});
}
}
@Override public void getUniformiv(int program, int location, IntBuffer params) {
final Readback rb = readback(program, location);
if (rb == null) return;
final int pos = params.position();
final int count = Math.min(rb.count(), params.remaining());
for (int i = 0; i < count; i++) {
final float raw = rb.raw(i);
final int bits = Float.floatToRawIntBits(raw);
params.put(pos + i, switch (rb.type()) {
case FLOAT -> roundHalfAwayFromZero(raw);
case BOOL -> bits != 0 ? 1 : 0;
case INT, UINT -> bits;
case OPAQUE -> rb.opaqueValue();
});
}
}
private enum ReadType { FLOAT, INT, UINT, BOOL, OPAQUE }
private record Readback(float[] storage, int start, int count, ReadType type, int opaqueValue) {
float raw(int i) {
return storage != null && start + i < storage.length ? storage[start + i] : 0f;
}
}
private Readback readback(int program, int location) {
final ShaderManager.ProgramObject prog = shaderManager.getProgram(program);
if (prog == null || !prog.linked || location < 0 || location >= prog.uniformSlotCount) return null;
final int kind = prog.locationKind[location];
if (kind != ShaderManager.ProgramObject.LOCATION_KIND_PLAIN) {
return new Readback(null, 0, 1, ReadType.OPAQUE, opaqueUnit(prog, location, kind));
}
final ShaderManager.UniformMemberInfo info = prog.memberInfo(location);
if (info == null) return null;
final int perElement = info.vectorSize() * info.columns();
final int index = prog.arrayIndexBySlot[location];
final int base = location - index;
final float[] storage = s().uniformStaging(prog).uniformDataBySlot[base];
return new Readback(storage, index * perElement, perElement, readType(prog, base, info), 0);
}
private static ReadType readType(ShaderManager.ProgramObject prog, int base, ShaderManager.UniformMemberInfo info) {
if (info.baseType() == Spvc.SPVC_BASETYPE_FP32) return ReadType.FLOAT;
if (info.baseType() != Spvc.SPVC_BASETYPE_UINT32) return ReadType.INT;
final String name = prog.locationName[base];
return name != null && prog.boolUniforms.contains(name) ? ReadType.BOOL : ReadType.UINT;
}
private static int roundHalfAwayFromZero(float f) {
final double r = Math.floor(Math.abs((double) f) + 0.5);
return (int) (f < 0 ? -r : r);
}
private static int opaqueUnit(ShaderManager.ProgramObject prog, int location, int kind) {
final String name = prog.locationName[location];
if (name == null) return 0;
final int unit = kind == ShaderManager.ProgramObject.LOCATION_KIND_SAMPLER
? prog.samplerTextureUnits.getInt(name)
: prog.imageTextureUnits.getInt(name);
return Math.max(unit, 0);
}
@Override public void getShaderSource(int shader, IntBuffer length, ByteBuffer source) {
writeUtf8(shaderManager.getShaderSource(shader), length, source);
}
@@ -4166,8 +4194,8 @@ public class SDLGPURenderBackend extends RenderBackend {
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
if (meta == null) return 0;
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(meta.width(), level);
case GL11.GL_TEXTURE_HEIGHT -> TextureInfo.mipLevelSize(meta.height(), level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
final int stored = meta.glFormat();
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
@@ -4184,6 +4212,7 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS -> 32;
case GL20.GL_MAX_VERTEX_ATTRIBS -> 16;
case GL20.GL_MAX_DRAW_BUFFERS -> 8;
case GL30.GL_MAX_CLIP_DISTANCES -> device.hasClipDistance() ? GLStateManager.MAX_CLIP_PLANES : 0;
case GL30.GL_MAX_COLOR_ATTACHMENTS -> 8;
case GL15.GL_ARRAY_BUFFER_BINDING -> cs.boundArrayBuffer;
case GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING -> cs.currentVao.elementBuffer;
@@ -4246,10 +4275,10 @@ public class SDLGPURenderBackend extends RenderBackend {
}
static final int MAX_UNIFORM_COMPONENTS_PER_STAGE = 4096 / Float.BYTES;
static final int MAX_ARRAY_TEXTURE_LAYERS = 256;
static final int MAX_TEXTURE_SIZE = 16384;
static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
static final int MAX_3D_TEXTURE_SIZE = 2048;
public static final int MAX_ARRAY_TEXTURE_LAYERS = 256;
public static final int MAX_TEXTURE_SIZE = 16384;
public static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
public static final int MAX_3D_TEXTURE_SIZE = 2048;
/** D3D12 and Metal both cap anisotropy at 16, and every desktop Vulkan driver reports 16. */
static final float MAX_ANISOTROPY = 16.0f;
@@ -4412,9 +4441,9 @@ public class SDLGPURenderBackend extends RenderBackend {
if (texHandle == 0 || frameManager.getCommandBuffer() == 0) return;
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
if (meta == null || level >= meta.levels()) return;
final int mipW = PixelOps.mipLevelSize(meta.width(), level);
final int mipH = PixelOps.mipLevelSize(meta.height(), level);
final int mipD = PixelOps.mipLevelSize(meta.depth(), level);
final int mipW = TextureInfo.mipLevelSize(meta.width(), level);
final int mipH = TextureInfo.mipLevelSize(meta.height(), level);
final int mipD = TextureInfo.mipLevelSize(meta.depth(), level);
final int bpp = PixelOps.sdlFormatTexelBytes(meta.sdlFormat());
if (mipW <= 0 || mipH <= 0 || bpp <= 0) return;
@@ -4641,7 +4670,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
protected void issueDrawArraysInstanced(int mode, int first, int count, int primcount) {
if (count <= 0 || primcount <= 0) return;
final ContextState st = s();
if (st.rasterizerDiscard) return;
@@ -4659,11 +4688,11 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override
public void multiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts) {
protected void issueMultiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts) {
if (firsts == null || counts == null) return;
final int n = Math.min(firsts.remaining(), counts.remaining());
for (int i = 0; i < n; i++) {
drawArrays(mode, firsts.get(firsts.position() + i), counts.get(counts.position() + i));
issueDrawArrays(mode, firsts.get(firsts.position() + i), counts.get(counts.position() + i));
}
}
@@ -45,6 +45,7 @@ public final class Device {
private long device;
private boolean claimed;
private boolean clipDistance = true;
private int supportedShaderFormats;
private String driverName;
private boolean fencePollEnabled;
@@ -116,6 +117,10 @@ public final class Device {
}
public boolean createDevice() {
return createDevice(!SystemProperties.EMULATE_CLIP_DISTANCES);
}
boolean createDevice(boolean requestClipDistance) {
if (device != 0) return true;
installLogCallback();
@@ -130,9 +135,9 @@ public final class Device {
}
final boolean gpuDebug = SystemProperties.LWJGL_DEBUG || SystemProperties.SDL_GPU_DEBUG;
device = createGPUDevice(gpuDebug, candidates);
device = createGPUDevice(gpuDebug, candidates, requestClipDistance);
if (device == 0 && VideoDriverRecovery.shouldRetryOnX11(SDL_GetCurrentVideoDriver(), vulkanLoaderAvailable(), System.getenv("DISPLAY"))) {
device = VideoDriverRecovery.retryUnderX11(() -> createGPUDevice(gpuDebug, candidates));
device = VideoDriverRecovery.retryUnderX11(() -> createGPUDevice(gpuDebug, candidates, requestClipDistance));
}
if (device == 0) {
logDeviceCreationFailure(REQUESTED_SHADER_FORMATS);
@@ -206,13 +211,22 @@ public final class Device {
}
}
private long createGPUDevice(boolean gpuDebug, String[] candidates) {
private long createGPUDevice(boolean gpuDebug, String[] candidates, boolean requestClipDistance) {
final boolean macos = Platform.get() == Platform.MACOSX;
for (final String driver : candidates) {
final boolean macVulkan = macos && isVulkan(driver);
if (macVulkan) useMoltenVk();
final long dev = SDL_CreateGPUDevice(REQUESTED_SHADER_FORMATS, gpuDebug, driver);
if (dev != 0) return dev;
long dev = createWithProperties(gpuDebug, driver, requestClipDistance);
boolean clip = requestClipDistance;
if (dev == 0 && clip && isVulkan(driver)) {
LOG.info("SDL GPU Vulkan device creation failed with shaderClipDistance ({}); the device may lack it, retrying without it, clip distances will be emulated", SDLError.SDL_GetError());
dev = createWithProperties(gpuDebug, driver, false);
clip = false;
}
if (dev != 0) {
clipDistance = clip;
return dev;
}
LOG.warn("Failed to create a '{}' SDL GPU device: {}", driver, SDLError.SDL_GetError());
if (macVulkan && MoltenVK.locate() == null) {
LOG.warn("Vulkan on macOS needs MoltenVK. Add {} to the mods folder or install MoltenVK.", MoltenVK.JAR_NAME);
@@ -221,6 +235,22 @@ public final class Device {
return 0;
}
private static long createWithProperties(boolean gpuDebug, String driver, boolean clipDistance) {
final int props = SDLProperties.SDL_CreateProperties();
try {
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_SPIRV) != 0);
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_DXBC) != 0);
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_DXIL) != 0);
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_MSL) != 0);
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, gpuDebug);
SDLProperties.SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, driver);
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, clipDistance);
return SDL_CreateGPUDeviceWithProperties(props);
} finally {
SDLProperties.SDL_DestroyProperties(props);
}
}
public void claimWindow(long window) {
if (device == 0) {
throw new IllegalStateException("SDL GPU device not created");
@@ -298,6 +328,7 @@ public final class Device {
driverVersion = null;
driverInfo = null;
supportedShaderFormats = 0;
clipDistance = true;
}
private void logWindowDiagnostics(long window) {
@@ -351,6 +382,10 @@ public final class Device {
return device;
}
public boolean hasClipDistance() {
return clipDistance;
}
public String getDriverName() {
return driverName;
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.sdlgpu.frame;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import com.gtnewhorizons.angelica.sdlgpu.resource.ColorSource;
@@ -10,6 +11,7 @@ import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpVariant;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.Long2FloatOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
@@ -67,27 +69,8 @@ public final class ContextState {
public final PackState packState = new PackState();
public final ColorSource readColor = new ColorSource();
public static final class ProxyTextureState {
public int target;
public int width;
public int height;
public int depth;
public int internalFormat;
public void accept(int target, int width, int height, int depth, int internalFormat) {
this.target = target;
this.width = width;
this.height = height;
this.depth = depth;
this.internalFormat = internalFormat;
}
public void reject(int target) {
accept(target, 0, 0, 0, 0);
}
}
public final ProxyTextureState proxyTexture = new ProxyTextureState();
public final ProxyTexture proxyTexture = new ProxyTexture(SDLGPURenderBackend.MAX_TEXTURE_SIZE, SDLGPURenderBackend.MAX_3D_TEXTURE_SIZE,
SDLGPURenderBackend.MAX_CUBE_MAP_TEXTURE_SIZE, SDLGPURenderBackend.MAX_ARRAY_TEXTURE_LAYERS);
public static final class VAOState {
public final boolean[] attribEnabled = new boolean[MAX_VERTEX_ATTRIBS];
@@ -201,6 +184,7 @@ public final class ContextState {
private final Reference2ObjectOpenHashMap<ShaderManager.ProgramObject, UniformStaging> uniformStagingByProgram = new Reference2ObjectOpenHashMap<>();
private ShaderManager.ProgramObject stagingProg;
private UniformStaging currentUniformStaging;
public final Int2ObjectOpenHashMap<float[]> uniformArrayScratch = new Int2ObjectOpenHashMap<>();
public UniformStaging uniformStaging(ShaderManager.ProgramObject prog) {
if (prog == stagingProg) return currentUniformStaging;
@@ -1,10 +1,10 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureOps;
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpFormats;
@@ -121,13 +121,13 @@ public final class LogicOpEmulator {
if (fbo == null) return frameManager.getFbo0Width();
final int db = drawBuffer(fbo, loc);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]);
return meta != null ? PixelOps.mipLevelSize(meta.width(), fbo.colorLevels[db]) : fbo.width;
return meta != null ? TextureInfo.mipLevelSize(meta.width(), fbo.colorLevels[db]) : fbo.width;
}
private int targetHeight(FboState fbo, int loc) {
if (fbo == null) return frameManager.getFbo0Height();
final int db = drawBuffer(fbo, loc);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]);
return meta != null ? PixelOps.mipLevelSize(meta.height(), fbo.colorLevels[db]) : fbo.height;
return meta != null ? TextureInfo.mipLevelSize(meta.height(), fbo.colorLevels[db]) : fbo.height;
}
}
@@ -754,16 +754,32 @@ public final class PipelineApplier {
public float[] reuseOrAlloc(ContextState st, int location, int length) {
final ShaderManager.ProgramObject prog = st.boundProgramObj;
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return null;
if (prog.floatLimitBySlot[location] < 0) return arrayScratch(st, length);
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
if (existing != null && existing.length == length) return existing;
return new float[length];
}
private static float[] arrayScratch(ContextState st, int length) {
float[] scratch = st.uniformArrayScratch.get(length);
if (scratch == null) {
scratch = new float[length];
st.uniformArrayScratch.put(length, scratch);
}
return scratch;
}
public void putUniformFv(ContextState st, int location, FloatBuffer value) {
final ShaderManager.ProgramObject prog = st.boundProgramObj;
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return;
final int n = value.remaining();
final int pos = value.position();
if (prog.floatLimitBySlot[location] < 0) {
final float[] v = arrayScratch(st, n);
value.get(pos, v);
putUniform(st, location, v);
return;
}
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
if (existing != null && existing.length == n) {
if (contentEquals(existing, value, pos, n)) {
@@ -824,6 +840,7 @@ public final class PipelineApplier {
if (Tracy.ENABLED) frameManager.noteUniformBlockFlush(b, size);
}
private static final long ARRAY_HASH_SEED = 0x9E3779B97F4A7C15L;
private static final boolean VERIFY_PER_FRAME_BLOCK = SystemProperties.SDL_VERIFY_PER_FRAME_UNIFORM_BLOCK;
private final HashSet<String> perFrameBlockDriftWarned = new HashSet<>();
@@ -837,6 +854,12 @@ public final class PipelineApplier {
public void putUniform(ContextState st, int location, float[] data) {
final ShaderManager.ProgramObject prog = st.boundProgramObj;
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return;
final int limit = prog.floatLimitBySlot[location];
if (limit < 0) {
putArrayElement(st, prog, st.uniformStaging(prog), location - prog.arrayIndexBySlot[location], location, data);
return;
}
if (data.length > limit) return;
final UniformStaging us = st.uniformStaging(prog);
final int len = data.length;
final long valueKey;
@@ -884,6 +907,62 @@ public final class PipelineApplier {
}
return;
}
writeStages(st, prog, us, location, data);
}
private void putArrayElement(ContextState st, ShaderManager.ProgramObject prog, UniformStaging us, int base, int location, float[] data) {
final ShaderManager.UniformMemberInfo baseInfo = prog.memberInfo(base);
if (baseInfo == null) return;
final int len = prog.arrayLenBySlot[location];
final int index = prog.arrayIndexBySlot[location];
final int count = Math.min(elementCount(baseInfo, data), len - index);
if (count <= 0) return;
final int floatsPerElement = baseInfo.vectorSize() * baseInfo.columns();
if (Tracy.ENABLED) st.slotWrites++;
final int start = index * floatsPerElement;
final int floats = count * floatsPerElement;
float[] storage = us.uniformDataBySlot[base];
final boolean fresh = storage == null;
long storageHash;
if (fresh) {
storage = us.uniformDataBySlot[base] = new float[len * floatsPerElement];
storageHash = ARRAY_HASH_SEED;
} else {
storageHash = us.uniformValueHashBySlot[base];
}
boolean changed = fresh;
for (int i = 0; i < floats; i++) {
final int oldBits = Float.floatToRawIntBits(storage[start + i]);
final int newBits = Float.floatToRawIntBits(data[i]);
if (oldBits == newBits) continue;
storageHash ^= arrayHashTerm(start + i, oldBits) ^ arrayHashTerm(start + i, newBits);
storage[start + i] = data[i];
changed = true;
}
if (!changed) {
if (Tracy.ENABLED) st.slotWritesElided++;
return;
}
us.uniformValueHashBySlot[base] = storageHash;
for (int b = 0; b < ShaderManager.BLOCK_COUNT; b++) {
final ShaderManager.UniformMemberInfo info = prog.blockInfoBySlot[b][location];
if (info == null) continue;
final ContextState.UniformBlockState block = st.uniformBlocks[b];
if (prog.blockSize[b] > block.size) block.size = prog.blockSize[b];
final FloatBuffer blockFb = block.staging(prog.blockSize[b]);
final int hashSlot = block.hashSlot(prog.blockInfoBySlot[b][base].offset());
if (hashSlot >= 0) block.setHash(hashSlot, storageHash);
writeMember(st, prog, blockFb, prog.blockSize[b], info, data, location, false);
block.dirty = true;
if (VERIFY_PER_FRAME_BLOCK && b == ShaderManager.BLOCK_PER_FRAME && block.flushedThisFrame) {
notePerFrameBlockDrift(prog, base);
}
return;
}
writeStages(st, prog, us, location, data);
}
private void writeStages(ContextState st, ShaderManager.ProgramObject prog, UniformStaging us, int location, float[] data) {
final ShaderManager.UniformMemberInfo vs = prog.vsInfoBySlot[location];
if (vs != null && us.vsUniformFb != null) {
writeMember(st, prog, us.vsUniformFb, prog.vertexUboSize, vs, data, location, true);
@@ -896,6 +975,10 @@ public final class PipelineApplier {
}
}
private static long arrayHashTerm(int index, int bits) {
return bits == 0 ? 0L : Hashing.fmix64(ARRAY_HASH_SEED, Hashing.packHiLo(index, bits));
}
public void storeMatrix(ContextState st, int location, boolean transpose, FloatBuffer value, int size) {
if (location < 0) return;
final int n = value.remaining();
@@ -914,16 +997,19 @@ public final class PipelineApplier {
putUniform(st, location, out);
}
private void writeMember(ContextState st, ShaderManager.ProgramObject prog, FloatBuffer ubo, int uboSize, ShaderManager.UniformMemberInfo info, float[] data, int loc, boolean isVs) {
private static int elementCount(ShaderManager.UniformMemberInfo info, float[] data) {
final int floatsPerElement = info.vectorSize() * info.columns();
if (floatsPerElement <= 0) return;
final int srcCount = data.length / floatsPerElement;
if (srcCount <= 0) return;
final int dataBytes = srcCount * floatsPerElement * 4;
return floatsPerElement <= 0 ? 0 : data.length / floatsPerElement;
}
private void writeMember(ContextState st, ShaderManager.ProgramObject prog, FloatBuffer ubo, int uboSize, ShaderManager.UniformMemberInfo info, float[] data, int loc, boolean isVs) {
final int count = Math.min(elementCount(info, data), info.arrayLen());
if (count <= 0) return;
final int dataBytes = count * info.vectorSize() * info.columns() * 4;
final int avail = uboSize - info.offset();
if (dataBytes > avail) {
throw new IllegalStateException("Uniform overflows UBO: prog=" + st.boundProgram + " loc=" + loc + " name='" + prog.locationToName.getOrDefault(loc, "?") + "' dataBytes=" + dataBytes + " availFromOffset=" + avail + " stage=" + (isVs ? "VS" : "FS"));
}
Std140Writer.write(ubo, info, data, srcCount);
Std140Writer.write(ubo, info, data, count);
}
}
@@ -14,12 +14,6 @@ import static org.lwjgl.sdl.SDLGPU.*;
public final class PixelOps {
private PixelOps() {}
public static int mipLevelSize(int base, int level) {
if (base <= 0 || level < 0) return 0;
if (level >= Integer.SIZE - 1) return 1;
return Math.max(1, base >> level);
}
public static int defaultMipLevels(int width, int height) {
final int max = Math.max(1, Math.max(width, height));
return Math.max(1, 32 - Integer.numberOfLeadingZeros(max));
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.device.Submits;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
@@ -156,11 +157,11 @@ public final class TextureOps {
}
public static int levelWidth(ResourceManager.TextureMeta meta, int level) {
return meta != null ? PixelOps.mipLevelSize(meta.width(), level) : 0;
return meta != null ? TextureInfo.mipLevelSize(meta.width(), level) : 0;
}
public static int levelHeight(ResourceManager.TextureMeta meta, int level) {
return meta != null ? PixelOps.mipLevelSize(meta.height(), level) : 0;
return meta != null ? TextureInfo.mipLevelSize(meta.height(), level) : 0;
}
private void copyFromDefaultFramebuffer(ContextState st, int destGlId, long destTex, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
@@ -777,7 +777,7 @@ public final class ShaderManager {
else prog.fragmentUboSize = refl.uboSize();
}
for (UboMember m : refl.uboMembers()) {
final int loc = ensureLocation(prog, m.name());
final int loc = ensureLocation(prog, m.name(), m.arrayLen());
final UniformMemberInfo info = new UniformMemberInfo(
m.offset(), m.size(), m.arrayStride(), isVertex,
m.vectorSize(), m.columns(), m.baseType(), m.arrayLen()
@@ -795,7 +795,7 @@ public final class ShaderManager {
prog.blockSize[b] = Math.max(prog.blockSize[b], block.size());
prog.blockBinding[b] = block.binding();
for (UboMember m : block.members()) {
final int loc = ensureLocation(prog, m.name());
final int loc = ensureLocation(prog, m.name(), m.arrayLen());
if (prog.blockMemberInfo[b].containsKey(loc)) continue;
prog.blockMemberInfo[b].put(loc, new UniformMemberInfo(
m.offset(), m.size(), m.arrayStride(), false,
@@ -809,14 +809,34 @@ public final class ShaderManager {
}
private static int ensureLocation(ProgramObject prog, String name) {
return ensureLocation(prog, name, 1);
}
private static int ensureLocation(ProgramObject prog, String name, int arrayLen) {
final int existing = prog.nameToLocation.getInt(name);
if (existing != -1) return existing;
final int loc = prog.nextUniformLocation++;
if (existing != -1) {
final int registered = Math.max(1, prog.arrayLenByLocation.get(existing));
if (registered == arrayLen) return existing;
retireLocations(prog, existing, registered);
}
final int loc = prog.nextUniformLocation;
prog.nextUniformLocation += arrayLen;
prog.nameToLocation.put(name, loc);
prog.locationToName.put(loc, name);
for (int i = 1; i < arrayLen; i++) prog.locationToName.put(loc + i, name + "[" + i + "]");
if (arrayLen > 1) prog.arrayLenByLocation.put(loc, arrayLen);
return loc;
}
private static void retireLocations(ProgramObject prog, int base, int len) {
prog.arrayLenByLocation.remove(base);
for (int i = 0; i < len; i++) prog.locationToName.remove(base + i);
prog.locationToMemberInfo.remove(base);
prog.vertexMemberInfo.remove(base);
prog.fragmentMemberInfo.remove(base);
for (int b = 0; b < BLOCK_COUNT; b++) prog.blockMemberInfo[b].remove(base);
}
private static void applyVaryingMatch(ByteBuffer fragmentSpirv, StageReflection vs, StageReflection fs) {
if (vs.vsOutputs().isEmpty() || fs.fsInputs().isEmpty() || fragmentSpirv == null) return;
final HashMap<String, Integer> vsOutLocs = new HashMap<>(vs.vsOutputs().size() * 2);
@@ -1268,7 +1288,13 @@ public final class ShaderManager {
public int getUniformLocation(int program, String name) {
final ProgramObject prog = programObjects.get(program);
if (prog == null || !prog.linked) return -1;
return prog.nameToLocation.getInt(name);
final int exact = prog.nameToLocation.getInt(name);
if (exact != -1) return exact;
final int index = UniformNames.arrayIndex(name);
if (index < 0) return -1;
final int base = prog.nameToLocation.getInt(name.substring(0, name.lastIndexOf('[')));
if (base < 0 || base >= prog.uniformSlotCount || index >= prog.arrayLenBySlot[base]) return -1;
return base + index;
}
/**
@@ -1645,7 +1671,12 @@ public final class ShaderManager {
public record UniformMemberInfo(
int offset, int size, int arrayStride, boolean isVertex,
int vectorSize, int columns, int baseType, int arrayLen
) {}
) {
public UniformMemberInfo element(int i) {
return new UniformMemberInfo(offset + i * arrayStride, size - i * arrayStride, arrayStride, isVertex,
vectorSize, columns, baseType, arrayLen - i);
}
}
public record UboMember(
String name, int offset, int size, int arrayStride,
@@ -2069,11 +2100,24 @@ public final class ShaderManager {
return m != null ? m : blockMemberInfo[BLOCK_PER_PASS].get(loc);
}
public UniformMemberInfo memberInfo(int slot) {
for (int b = 0; b < BLOCK_COUNT; b++) {
final UniformMemberInfo info = blockInfoBySlot[b][slot];
if (info != null) return info;
}
final UniformMemberInfo vs = vsInfoBySlot[slot];
return vs != null ? vs : fsInfoBySlot[slot];
}
private static final UniformMemberInfo[] EMPTY_INFO = new UniformMemberInfo[0];
public UniformMemberInfo[] vsInfoBySlot;
public UniformMemberInfo[] fsInfoBySlot;
public int uniformSlotCount;
public final Int2IntOpenHashMap arrayLenByLocation = new Int2IntOpenHashMap();
public int[] arrayIndexBySlot = EMPTY_UNITS;
public int[] arrayLenBySlot = EMPTY_UNITS;
public int[] floatLimitBySlot = EMPTY_UNITS;
public final int[] vertexInputBaseType = new int[16];
public final int[] vertexInputVecSize = new int[16];
public final String[] vertexInputName = new String[16];
@@ -2200,24 +2244,51 @@ public final class ShaderManager {
locationKind[loc] = LOCATION_KIND_PLAIN;
}
}
arrayIndexBySlot = new int[n];
arrayLenBySlot = new int[n];
Arrays.fill(arrayLenBySlot, 1);
for (var it = arrayLenByLocation.int2IntEntrySet().fastIterator(); it.hasNext(); ) {
final var e = it.next();
final int base = e.getIntKey();
final int len = e.getIntValue();
for (int i = 0; i < len; i++) {
arrayLenBySlot[base + i] = len;
arrayIndexBySlot[base + i] = i;
}
}
for (int b = 0; b < BLOCK_COUNT; b++) {
blockInfoBySlot[b] = new UniformMemberInfo[n];
for (var it = blockMemberInfo[b].int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
final var e = it.next();
final int k = e.getIntKey();
if (k >= 0 && k < n) blockInfoBySlot[b][k] = e.getValue();
fillSlots(blockInfoBySlot[b], e.getIntKey(), e.getValue());
}
}
for (var it = vertexMemberInfo.int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
final var e = it.next();
final int k = e.getIntKey();
if (k >= 0 && k < n) vsInfoBySlot[k] = e.getValue();
fillSlots(vsInfoBySlot, e.getIntKey(), e.getValue());
}
for (var it = fragmentMemberInfo.int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
final var e = it.next();
final int k = e.getIntKey();
if (k >= 0 && k < n) fsInfoBySlot[k] = e.getValue();
fillSlots(fsInfoBySlot, e.getIntKey(), e.getValue());
}
floatLimitBySlot = new int[n];
for (int slot = 0; slot < n; slot++) {
if (arrayLenBySlot[slot] > 1) {
floatLimitBySlot[slot] = -1;
continue;
}
final UniformMemberInfo info = memberInfo(slot);
final int floats = info == null || info.arrayStride() > 0 ? 0 : info.vectorSize() * info.columns();
floatLimitBySlot[slot] = floats > 0 ? floats : Integer.MAX_VALUE;
}
}
private void fillSlots(UniformMemberInfo[] bySlot, int base, UniformMemberInfo info) {
final int n = bySlot.length;
if (base < 0 || base >= n) return;
bySlot[base] = info;
final int len = arrayLenBySlot[base];
for (int i = 1; i < len; i++) bySlot[base + i] = info.element(i);
}
}
}
@@ -0,0 +1,25 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
public final class UniformNames {
private UniformNames() {}
public static int arrayIndex(String name) {
final int len = name.length();
if (len == 0 || name.charAt(len - 1) != ']') return -1;
int i = len - 1;
while (i > 0 && isDigit(name.charAt(i - 1))) i--;
if (i == 0 || name.charAt(i - 1) != '[') return -1;
if (name.charAt(i) == '0' && i + 1 != len - 1) return -1;
long index = 0;
for (int c = i; c < len - 1; c++) {
index = index * 10 + (name.charAt(c) - '0');
if (index > Integer.MAX_VALUE) return Integer.MAX_VALUE;
}
return (int) index;
}
private static boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -47,11 +48,11 @@ class ProxyTextureLimitsTest {
assertEquals(2048, SDLGPURenderBackend.deviceLimit(GL12.GL_MAX_3D_TEXTURE_SIZE));
assertEquals(16384, SDLGPURenderBackend.deviceLimit(GL13.GL_MAX_CUBE_MAP_TEXTURE_SIZE));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_2D));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_1D));
assertEquals(2048, SDLGPURenderBackend.proxyDimensionLimit(GL12.GL_PROXY_TEXTURE_3D));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY));
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL11.GL_PROXY_TEXTURE_2D));
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL11.GL_PROXY_TEXTURE_1D));
assertEquals(2048, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL12.GL_PROXY_TEXTURE_3D));
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP));
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY));
}
@Test
@@ -99,13 +100,13 @@ class ProxyTextureLimitsTest {
@Test
void levelZeroEquivalentSizeRejectsOverflowAndNonsense() {
assertEquals(4, SDLGPURenderBackend.levelZeroEquivalentSize(4, 0));
assertEquals(32, SDLGPURenderBackend.levelZeroEquivalentSize(4, 3));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, -1));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(0, 2));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 31));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 30));
assertEquals(1 << 30, SDLGPURenderBackend.levelZeroEquivalentSize(1, 30));
assertEquals(4, ProxyTexture.levelZeroEquivalentSize(4, 0));
assertEquals(32, ProxyTexture.levelZeroEquivalentSize(4, 3));
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, -1));
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(0, 2));
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, 31));
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, 30));
assertEquals(1 << 30, ProxyTexture.levelZeroEquivalentSize(1, 30));
}
@Test
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
@@ -81,7 +82,7 @@ class ProxyTextureTargetTest {
@ParameterizedTest(name = "{0}")
@MethodSource("levelSizeCases")
void levelSizeMatchesExpectation(String label, int base, int level, int expected) {
assertEquals(expected, SDLGPURenderBackend.proxyLevelSize(base, level), label);
assertEquals(expected, TextureInfo.mipLevelSize(base, level), label);
}
@Test
@@ -2,11 +2,13 @@ package com.gtnewhorizons.angelica.sdlgpu.device;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -46,4 +48,12 @@ class DeviceLifecycleGpuTest {
device.destroyDevice();
assertEquals(0L, device.getDevice());
}
@Test
void createsVulkanDeviceWithoutClipDistance() {
device = new Device();
assertTrue(device.createDevice(false), "no SDL GPU device on this machine");
Assumptions.assumeTrue(device.isVulkan(), "test needs the vulkan driver, got " + device.getDriverName());
assertFalse(device.hasClipDistance());
}
}
@@ -10,6 +10,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.CubeParityFixture;
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.ffp.ParticleParityFixture;
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
@@ -127,6 +128,30 @@ class GlsmSdlFfpTest {
assertTexelEverywhere("two units, glCallLists");
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void disabledClipPlaneDoesNotClip(boolean emulated) {
try (var ignored = ClipTestSupport.emulation(emulated)) {
bindBaseTexture();
try {
ClipTestSupport.setPlane(1, 0.0, 0.0, 0.0, -1.0);
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE1);
ClipTestSupport.setPlane(0, 1.0, 0.0, 0.0, 0.0);
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
quadUnit0Only();
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
final int left = pixelAt(pixels, SIZE, SIZE / 4, SIZE / 2);
final int right = pixelAt(pixels, SIZE, 3 * SIZE / 4, SIZE / 2);
assertTrue(left != TEXEL, () -> "left quarter must be clipped by enabled plane 0: " + describe(left));
assertEquals(TEXEL, right, () -> "right quarter must survive; disabled plane 1 must not clip: " + describe(right));
} finally {
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE1);
}
}
}
@Test
void litTexturedQuadIsShaded() {
bindBaseTexture();
@@ -0,0 +1,246 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
import com.gtnewhorizons.angelica.glsm.GLSMPrograms;
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlUniformArrayTest {
private static final int COUNT = 8;
private static final int[][] A = {
{10, 20, 30}, {40, 50, 60}, {70, 80, 90}, {100, 110, 120},
{130, 140, 150}, {160, 170, 180}, {190, 200, 210}, {220, 230, 240}
};
private static final int[] X = {250, 5, 15};
private static final int[][] B = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {11, 12, 13}};
private int program;
private int vao;
private int arr;
private int single;
private int pick;
@BeforeEach
void setUp() {
program = GlTestSupport.program(GlsmSdlHeadlessRig.FULLSCREEN_VERTEX, """
#version 330 core
uniform vec4 arr[8];
uniform vec4 single;
uniform ivec4 iarr[2];
uniform vec4 one[1];
uniform int pick;
out vec4 color;
void main() { color = pick == -2 ? vec4(iarr[1]) / 255.0 : pick == -3 ? one[0] : pick < 0 ? single : arr[pick]; }
""");
vao = GLSMVertexInput.glGenVertexArrays();
GLSMVertexInput.glBindVertexArray(vao);
GLSMPrograms.glUseProgram(program);
arr = loc("arr");
single = loc("single");
pick = loc("pick");
}
@AfterEach
void tearDown() {
GLSMPrograms.glUseProgram(0);
GLSMVertexInput.glBindVertexArray(0);
GLSMVertexInput.glDeleteVertexArrays(vao);
GLSMPrograms.glDeleteProgram(program);
}
private int loc(String name) {
return GLSMPrograms.glGetUniformLocation(program, name);
}
private static int argb(int[] rgb) {
return 0xFF000000 | (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
}
private static float[] vec(int[] rgb) {
return new float[] {rgb[0] / 255f, rgb[1] / 255f, rgb[2] / 255f, 1f};
}
private static FloatBuffer pack(int[]... colors) {
final FloatBuffer buf = MemoryUtil.memAllocFloat(colors.length * 4);
for (int[] c : colors) buf.put(vec(c));
buf.flip();
return buf;
}
private void writeAt(int location, int[]... colors) {
final FloatBuffer buf = pack(colors);
try {
GLSMPrograms.glUniform4(location, buf);
} finally {
MemoryUtil.memFree(buf);
}
}
private void writeElement(int location, int[] rgb) {
final float[] v = vec(rgb);
GLSMPrograms.glUniform4f(location, v[0], v[1], v[2], v[3]);
}
private void writeAll() {
writeAt(arr, A);
}
private int drawPick(int index) {
GlsmSdlHeadlessRig.beginFrame();
GlsmSdlHeadlessRig.bindTarget();
GLSMPrograms.glUniform1i(pick, index);
GLSMDraws.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
return GlsmSdlHeadlessRig.readTarget(GlsmSdlHeadlessRig.SIZE / 2, GlsmSdlHeadlessRig.SIZE / 2, 1, 1)[0];
}
private void assertPick(int index, int[] expected, String label) {
final int got = drawPick(index);
assertEquals(Integer.toHexString(argb(expected)), Integer.toHexString(got), label + ": " + GlsmSdlHeadlessRig.describe(got));
}
private float[] readUniform(int location) {
final FloatBuffer buf = MemoryUtil.memAllocFloat(4);
try {
GLSMPrograms.glGetUniform(program, location, buf);
final float[] out = new float[4];
buf.get(out);
return out;
} finally {
MemoryUtil.memFree(buf);
}
}
@Test
void arrayElementLocations() {
assertNotEquals(-1, arr, "arr");
assertNotEquals(-1, single, "single");
assertEquals(arr, loc("arr[0]"), "arr[0]");
assertEquals(arr + 3, loc("arr[3]"), "arr[3]");
assertEquals(arr + 7, loc("arr[7]"), "arr[7]");
assertEquals(-1, loc("arr[8]"), "arr[8]");
assertEquals(-1, loc("arr[03]"), "arr[03]");
assertEquals(-1, loc("arr[-1]"), "arr[-1]");
assertEquals(arr, loc("arr[]"), "arr[] is index 0, as Mesa parses it");
assertEquals(single, loc("single[0]"), "single[0]");
assertEquals(-1, loc("single[1]"), "single[1]");
}
@Test
void elementWriteLandsInTheArray() {
writeAll();
final int element3 = loc("arr[3]");
writeElement(element3, X);
assertPick(3, X, "pick=3 shows the element write");
assertPick(2, A[2], "pick=2 keeps the bulk write");
assertArrayEquals(vec(X), readUniform(element3), 0f, "glGetUniform arr[3]");
assertArrayEquals(vec(A[2]), readUniform(loc("arr[2]")), 0f, "glGetUniform arr[2]");
}
@Test
void elementWriteClampsToTheArrayEnd() {
writeAll();
writeAt(loc("arr[6]"), B);
assertPick(6, B[0], "pick=6");
assertPick(7, B[1], "pick=7");
assertPick(5, A[5], "pick=5");
}
@Test
void countBeyondTheDeclarationFollowsMesa() {
writeElement(single, A[0]);
writeAt(single, B);
assertPick(-1, A[0], "count 2 on a non-array is ignored");
assertArrayEquals(vec(A[0]), readUniform(single), 0f, "glGetUniform single keeps its value");
writeAt(loc("one"), B);
assertPick(-3, B[0], "count 2 on a size-1 array clamps");
}
@Test
void fullWriteAfterElementWriteIsNotElided() {
writeAll();
writeElement(loc("arr[3]"), X);
writeAll();
assertPick(3, A[3], "pick=3 after the repeated full write");
}
@Test
void intElementReadsBackAsInts() {
final int element1 = loc("iarr[1]");
assertEquals(loc("iarr") + 1, element1, "iarr[1]");
GLSMPrograms.glUniform4i(loc("iarr"), 1, 2, 3, 4);
GLSMPrograms.glUniform4i(element1, X[0], X[1], X[2], 255);
assertPick(-2, X, "pick=-2 shows iarr[1]");
assertArrayEquals(new int[] {X[0], X[1], X[2], 255}, readIntUniform(element1), "glGetUniformiv iarr[1]");
assertArrayEquals(new int[] {1, 2, 3, 4}, readIntUniform(loc("iarr")), "glGetUniformiv iarr[0]");
}
private int[] readIntUniform(int location) {
final IntBuffer buf = MemoryUtil.memAllocInt(4);
try {
GLSMPrograms.glGetUniform(program, location, buf);
final int[] out = new int[4];
buf.get(out);
return out;
} finally {
MemoryUtil.memFree(buf);
}
}
@Test
void activeUniformReportsArrayNameWithIndexZero() {
final Map<String, Integer> sizes = activeUniformSizes();
assertEquals(COUNT, sizes.getOrDefault("arr[0]", -1), "size of arr[0] in " + sizes);
assertEquals(1, sizes.getOrDefault("single", -1), "size of single in " + sizes);
assertEquals(1, sizes.getOrDefault("one[0]", -1), "size of one[0] in " + sizes);
}
private Map<String, Integer> activeUniformSizes() {
final int active = GLSMPrograms.glGetProgrami(program, GL20.GL_ACTIVE_UNIFORMS);
final Map<String, Integer> sizes = new HashMap<>();
final IntBuffer length = MemoryUtil.memAllocInt(1);
final IntBuffer size = MemoryUtil.memAllocInt(1);
final IntBuffer type = MemoryUtil.memAllocInt(1);
final ByteBuffer name = MemoryUtil.memAlloc(128);
try {
for (int i = 0; i < active; i++) {
GLSMPrograms.glGetActiveUniform(program, i, length, size, type, name);
final byte[] bytes = new byte[length.get(0)];
for (int c = 0; c < bytes.length; c++) bytes[c] = name.get(c);
sizes.put(new String(bytes, java.nio.charset.StandardCharsets.US_ASCII), size.get(0));
}
} finally {
MemoryUtil.memFree(name);
MemoryUtil.memFree(type);
MemoryUtil.memFree(size);
MemoryUtil.memFree(length);
}
return sizes;
}
}
@@ -0,0 +1,15 @@
package com.gtnewhorizons.angelica.api.tesr;
import com.gtnewhorizons.angelica.rendering.tesr.GlowReplayExclusions;
/// Under Fancy translucency, block entities drawing glows (blended draws without depth writes) in the translucent
/// pass are rendered a second time. Renderers that advance state per call opt out here.
public final class TesrGlowDepth {
private TesrGlowDepth() {}
/// Excludes block entities of `type`, or rendered by a renderer of `type`, including subclasses.
public static void excludeFromReplay(Class<?> type) {
GlowReplayExclusions.exclude(type);
}
}
@@ -1,5 +1,5 @@
/** TESR mesh cache/batch API (Render thread only) */
@API(owner = "angelica", provides = "angelica|tesr", apiVersion = "3")
@API(owner = "angelica", provides = "angelica|tesr", apiVersion = "4")
package com.gtnewhorizons.angelica.api.tesr;
import cpw.mods.fml.common.API;
@@ -28,6 +28,7 @@ import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
import com.gtnewhorizons.angelica.rendering.tesr.BatchDrawDefaults;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import com.gtnewhorizons.angelica.rendering.tesr.FlattenedItemLighting;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@@ -1194,6 +1195,7 @@ public class BatchingFontRenderer {
final Tessellator tessellator = Tessellator.instance;
int lastTexture = INVALID_TEXTURE;
boolean drawing = false;
FlattenedItemLighting.setPreLitText(true);
try {
for (int i = from; i < to; i++) {
final FontDrawCmd cmd = cmdsData[i];
@@ -1219,8 +1221,12 @@ public class BatchingFontRenderer {
owner.emitCommand(tessellator, cmd);
}
} finally {
if (drawing) {
tessellator.draw();
try {
if (drawing) {
tessellator.draw();
}
} finally {
FlattenedItemLighting.setPreLitText(false);
}
}
}
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.common;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.material.Material;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
public class BlockClipPlaneTest extends BlockContainer {
public BlockClipPlaneTest() {
super(Material.iron);
setHardness(0.7f);
}
@Override
public TileEntity createNewTileEntity(World world, int meta) {
return new TileEntityClipPlaneTest();
}
@Override
public boolean isOpaqueCube() {
return false;
}
@Override
public boolean renderAsNormalBlock() {
return false;
}
@Override
public int getRenderType() {
return -1;
}
}
@@ -0,0 +1,45 @@
package com.gtnewhorizons.angelica.common;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import java.util.function.Supplier;
public class BlockTesrTranslucentTest extends Block {
private final Supplier<TileEntity> tile;
public BlockTesrTranslucentTest(Supplier<TileEntity> tile) {
super(Material.glass);
this.tile = tile;
setHardness(0.3f);
setLightOpacity(0);
}
@Override
public boolean isOpaqueCube() {
return false;
}
@Override
public boolean renderAsNormalBlock() {
return false;
}
@Override
public int getRenderType() {
return -1;
}
@Override
public boolean hasTileEntity(int metadata) {
return true;
}
@Override
public TileEntity createTileEntity(World world, int metadata) {
return tile.get();
}
}
@@ -0,0 +1,11 @@
package com.gtnewhorizons.angelica.common;
import net.minecraft.tileentity.TileEntity;
public class TileEntityClipPlaneTest extends TileEntity {
@Override
public boolean canUpdate() {
return false;
}
}
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.common;
import net.minecraft.tileentity.TileEntity;
public abstract class TileTesrTranslucentTest extends TileEntity {
private static final int SOLID_PASS = 8;
public int variant() {
return getBlockMetadata() & ~SOLID_PASS;
}
@Override
public boolean shouldRenderInPass(int pass) {
return pass == ((getBlockMetadata() & SOLID_PASS) != 0 ? 0 : 1);
}
@Override
public boolean canUpdate() {
return false;
}
public static class Provider extends TileTesrTranslucentTest {}
public static class Model extends TileTesrTranslucentTest {}
public static class Decal extends TileTesrTranslucentTest {}
public static class Glow extends TileTesrTranslucentTest {}
}
@@ -0,0 +1,77 @@
package com.gtnewhorizons.angelica.compat.spark;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.io.InputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public final class SparkKnownSources {
private static final Logger LOGGER = LogManager.getLogger("Angelica");
private static final String PLUGIN = "me/lucko/spark/forge/plugin/Forge1710SparkPlugin";
private static final String SOURCE_METADATA = "me.lucko.spark.common.sampler.source.SourceMetadata";
private static MethodHandle constructor;
private static boolean failed;
private SparkKnownSources() {}
public static boolean mixinApplies() {
try (InputStream stream = SparkKnownSources.class.getResourceAsStream("/" + PLUGIN + ".class")) {
if (stream == null) {
return false;
}
final boolean[] declared = new boolean[1];
new ClassReader(stream).accept(new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if ("getKnownSources".equals(name) && "()Ljava/util/Collection;".equals(desc)) {
declared[0] = true;
}
return null;
}
}, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
if (declared[0]) {
LOGGER.info("spark already reports mod sources; skipping");
return false;
}
return true;
} catch (Throwable t) {
LOGGER.warn("Could not inspect spark plugin class; not adding mod sources", t);
return false;
}
}
public static Collection<Object> gather(ClassLoader loader) {
if (failed) {
return Collections.emptyList();
}
try {
if (constructor == null) constructor = MethodHandles.publicLookup().findConstructor(Class.forName(SOURCE_METADATA, false, loader),
MethodType.methodType(void.class, String.class, String.class, String.class, String.class));
final List<ModContainer> mods = Loader.instance().getActiveModList();
final List<Object> sources = new ArrayList<>(mods.size());
for (int i = 0; i < mods.size(); i++) {
final ModContainer mod = mods.get(i);
sources.add(constructor.invoke(mod.getModId(), mod.getVersion(), (String) null, (String) null));
}
return sources;
} catch (Throwable t) {
failed = true;
LOGGER.warn("Could not build spark mod sources; profiles will omit mod versions", t);
return Collections.emptyList();
}
}
}
@@ -177,6 +177,11 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static String[] entityShadowReplayExcludedClasses;
@Config.Comment("Tile entity or renderer classes whose glows Fancy translucency records as they draw instead of rendering the tile entity a second time. For renderers that advance an animation or other state on each render.")
@Config.DefaultStringList({})
@Config.RequiresMcRestart
public static String[] glowDepthReplayExcludedClasses;
@Config.Comment("Skip the end-of-frame shader buffer copy by ping-ponging buffers. Disable if a shader pack misrenders.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
@@ -34,9 +34,6 @@ public final class PositionEntries {
}
}
private WorldProvider cachedProvider;
private String dimensionName = "";
@Override
public void display(DebugScreenDisplayer displayer, DebugContext context) {
final EntityClientPlayerMP player = context.player();
@@ -52,10 +49,7 @@ public final class PositionEntries {
facing.value(DIRECTION_NAMES[heading]).text(" (").text(TOWARDS[heading]).text(") (");
facing.value(MathHelper.wrapAngleTo180_float(player.rotationYaw), 1).text(SEPARATOR).value(MathHelper.wrapAngleTo180_float(player.rotationPitch), 1).text(')');
final WorldProvider provider = context.world().provider;
if (provider != cachedProvider) {
cachedProvider = provider;
dimensionName = provider.getDimensionName();
}
final String dimensionName = provider.getDimensionName();
displayer.fact(DebugGroup.POSITION, "Dimension").value(dimensionName).text(" (").value(provider.dimensionId).text(')');
}
}
@@ -274,6 +274,13 @@ public class IrisGLSMBridge {
refreshBlendCondition();
});
GLSMHooks.CLIP_PLANES_IN_USE.addListener(event -> {
if (!Iris.enabled) return;
if (Iris.getPipelineManager().getPipelineNullable() instanceof DeferredWorldRenderingPipeline drp) {
drp.onClipPlanesInUseChanged();
}
});
GLSMHooks.FOREIGN_DRAW_END.addListener(event -> {
if (!Iris.enabled) return;
if (programRestoreDeferred) {
@@ -336,12 +343,13 @@ public class IrisGLSMBridge {
WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
if (pipeline instanceof DeferredWorldRenderingPipeline drp) {
DeferredWorldRenderingPipeline.Pass activePass = drp.getActivePassProgram();
if (activePass != null && activePass.getProgram() != null && activePass.getProgram().getProgramId() == event.newProgram) {
final Program bound = activePass != null ? activePass.boundProgram() : null;
if (bound != null && bound.getProgramId() == event.newProgram) {
final int frame = SystemTimeUniforms.COUNTER.getAsInt();
if (programLastUpdatedFrame.get(event.newProgram) != frame
|| !ProgramUniforms.isActiveProgramBound()
|| ProgramUniforms.activeHasDeferredUploads()) {
activePass.getProgram().getUniforms().update();
bound.getUniforms().update();
programLastUpdatedFrame.put(event.newProgram, frame);
}
}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.mixins;
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
import com.gtnewhorizon.gtnhmixins.builders.MixinBuilder;
import com.gtnewhorizons.angelica.api.BlockLightProvider;
import com.gtnewhorizons.angelica.compat.spark.SparkKnownSources;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.CompatConfig;
import com.gtnewhorizons.angelica.config.SystemProperties;
@@ -300,6 +301,15 @@ public enum Mixins implements IMixins {
)
),
ANGELICA_GLOW_DEPTH_REPLAY(new MixinBuilder("Drop particles and sounds a block entity renderer starts while Fancy translucency replays it into the glow depth")
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableIris)
.addClientMixins(
"angelica.tesr.MixinEffectRenderer_GlowDepthReplay",
"angelica.tesr.MixinSoundHandler_GlowDepthReplay"
)
),
ANGELICA_TESR_PROVIDER_DISPATCH(new MixinBuilder("Route TesrMeshProvider renderers through the batched mesh cache, and observe renderers that mix model parts with their own draws")
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableTESRProviderDispatch || AngelicaConfig.enableEntityBatching)
@@ -550,6 +560,8 @@ public enum Mixins implements IMixins {
, "shaders.MixinEntityRenderer"
, "shaders.MixinGuiIngameForge"
, "shaders.MixinFramebuffer"
, "angelica.rendering.MixinEntityRenderer_TranslucencyLayers"
, "angelica.rendering.MixinFramebuffer_TranslucencyLayers"
, "shaders.MixinItem"
, "shaders.MixinItemRenderer_ItemId"
, "shaders.MixinLocale"
@@ -641,6 +653,13 @@ public enum Mixins implements IMixins {
)
),
SPARK_MOD_SOURCES(new MixinBuilder("Mod versions in spark 1.7.10 profiles")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.SPARK)
.setApplyIf(SparkKnownSources::mixinApplies)
.addClientMixins("spark.MixinForge1710SparkPlugin")
),
ANGELICA_TEXTURE(new MixinBuilder()
.setPhase(Phase.EARLY)
.addClientMixins(
@@ -32,6 +32,7 @@ public enum TargetedMod implements ITargetMod {
OPENBLOCKS(null, "OpenBlocks"),
RAILCRAFT(null, "Railcraft"),
SECURITYCRAFT(null, "securitycraft"),
SPARK(null, "spark"),
STARMINER("jp.mc.ancientred.starminer.core.SMPlugin", "modJ_StarMiner"),
SUPERNOVA("com.mitchej123.supernova.core.SupernovaCore", "supernova"),
THAUMCRAFT(null, "Thaumcraft"),
@@ -39,6 +39,8 @@ import com.gtnewhorizons.angelica.commands.AngelicaCommand;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.common.BlockIsbrhTest;
import com.gtnewhorizons.angelica.common.TileEntityClipPlaneTest;
import com.gtnewhorizons.angelica.common.TileTesrTranslucentTest;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.bettercrashes.BetterCrashesCompat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
@@ -70,9 +72,11 @@ import com.gtnewhorizons.angelica.mixins.interfaces.IGameSettingsExt;
import com.gtnewhorizons.angelica.render.CloudRenderer;
import com.gtnewhorizons.angelica.render.EmissiveTextureAutoloader;
import com.gtnewhorizons.angelica.rendering.AngelicaBlockSafetyRegistry;
import com.gtnewhorizons.angelica.rendering.ClipPlaneTestRenderer;
import com.gtnewhorizons.angelica.rendering.FpsReducer;
import com.gtnewhorizons.angelica.rendering.FramePacer;
import com.gtnewhorizons.angelica.rendering.IsbrhTestRenderer;
import com.gtnewhorizons.angelica.rendering.TesrTranslucentTestRenderers;
import com.gtnewhorizons.angelica.rendering.TextureSamplers;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasSetup;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
@@ -86,6 +90,7 @@ import com.gtnewhorizons.angelica.utils.AngelicaJar;
import com.gtnewhorizons.angelica.utils.AnimationMode;
import com.gtnewhorizons.angelica.utils.ManagedEnum;
import com.gtnewhorizons.angelica.zoom.Zoom;
import cpw.mods.fml.client.registry.ClientRegistry;
import cpw.mods.fml.client.registry.RenderingRegistry;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.event.FMLInitializationEvent;
@@ -146,6 +151,11 @@ public final class ClientProxy extends CommonProxy {
if (AngelicaConfig.enableTestBlocks) {
BlockIsbrhTest.renderId = RenderingRegistry.getNextAvailableRenderId();
RenderingRegistry.registerBlockHandler(BlockIsbrhTest.renderId, new IsbrhTestRenderer());
ClientRegistry.bindTileEntitySpecialRenderer(TileEntityClipPlaneTest.class, new ClipPlaneTestRenderer());
ClientRegistry.bindTileEntitySpecialRenderer(TileTesrTranslucentTest.Provider.class, new TesrTranslucentTestRenderers.Provider());
ClientRegistry.bindTileEntitySpecialRenderer(TileTesrTranslucentTest.Model.class, new TesrTranslucentTestRenderers.Model());
ClientRegistry.bindTileEntitySpecialRenderer(TileTesrTranslucentTest.Decal.class, new TesrTranslucentTestRenderers.Decal());
ClientRegistry.bindTileEntitySpecialRenderer(TileTesrTranslucentTest.Glow.class, new TesrTranslucentTestRenderers.Glow());
}
AngelicaJar.configureShaderDiskCache(event.getSourceFile());
if (AngelicaConfig.enableIris) {
@@ -1,12 +1,19 @@
package com.gtnewhorizons.angelica.proxy;
import com.gtnewhorizons.angelica.common.BlockClipPlaneTest;
import com.gtnewhorizons.angelica.common.BlockIsbrhTest;
import com.gtnewhorizons.angelica.common.BlockTesrTranslucentTest;
import com.gtnewhorizons.angelica.common.BlockTest;
import com.gtnewhorizons.angelica.common.TileEntityClipPlaneTest;
import com.gtnewhorizons.angelica.common.TileTesrTranslucentTest;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.registry.GameRegistry;
import net.minecraft.tileentity.TileEntity;
import java.util.function.Supplier;
public class CommonProxy {
@@ -14,9 +21,20 @@ public class CommonProxy {
if (AngelicaConfig.enableTestBlocks) {
GameRegistry.registerBlock(new BlockTest(), "test_block");
GameRegistry.registerBlock(new BlockIsbrhTest(), "test_isbrh");
GameRegistry.registerBlock(new BlockClipPlaneTest(), "test_clip_plane");
GameRegistry.registerTileEntity(TileEntityClipPlaneTest.class, "angelica:test_clip_plane");
registerTesrTest("test_tesr_provider", TileTesrTranslucentTest.Provider.class, TileTesrTranslucentTest.Provider::new);
registerTesrTest("test_tesr_model", TileTesrTranslucentTest.Model.class, TileTesrTranslucentTest.Model::new);
registerTesrTest("test_tesr_decal", TileTesrTranslucentTest.Decal.class, TileTesrTranslucentTest.Decal::new);
registerTesrTest("test_tesr_glow", TileTesrTranslucentTest.Glow.class, TileTesrTranslucentTest.Glow::new);
}
}
private static void registerTesrTest(String name, Class<? extends TileEntity> tile, Supplier<TileEntity> factory) {
GameRegistry.registerBlock(new BlockTesrTranslucentTest(factory), name);
GameRegistry.registerTileEntity(tile, "angelica:" + name);
}
public void init(FMLInitializationEvent event) {}
public void postInit(FMLPostInitializationEvent event) {}
@@ -103,8 +103,12 @@ public class PanoramaRenderer {
}
public static <T> GlProgram<T> loadProgram(String name, String vertBase, String fragBase, Function<ShaderBindingContext, T> factory) {
return loadProgram(name, vertBase, fragBase, ShaderConstants.EMPTY, factory);
}
public static <T> GlProgram<T> loadProgram(String name, String vertBase, String fragBase, ShaderConstants fragConstants, Function<ShaderBindingContext, T> factory) {
final GlShader vert = ShaderLoader.loadShader(ShaderType.VERTEX, vertBase + ".vert", ShaderConstants.EMPTY);
final GlShader frag = ShaderLoader.loadShader(ShaderType.FRAGMENT, fragBase + ".frag", ShaderConstants.EMPTY);
final GlShader frag = ShaderLoader.loadShader(ShaderType.FRAGMENT, fragBase + ".frag", fragConstants);
try {
final GlProgram<T> program = GlProgram.builder(name).attachShader(vert).attachShader(frag).link(factory);
program.bind();
@@ -0,0 +1,41 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.init.Blocks;
import net.minecraft.tileentity.TileEntity;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.DoubleBuffer;
public class ClipPlaneTestRenderer extends TileEntitySpecialRenderer {
private static final DoubleBuffer KEEP_LOWER_HALF = plane(0.0, -1.0, 0.0, 0.5);
private static final DoubleBuffer REJECT_ALL = plane(0.0, 0.0, 0.0, -1.0);
private static DoubleBuffer plane(double a, double b, double c, double d) {
final DoubleBuffer buf = BufferUtils.createDoubleBuffer(4);
buf.put(a).put(b).put(c).put(d).flip();
return buf;
}
@Override
public void renderTileEntityAt(TileEntity te, double x, double y, double z, float partialTicks) {
GLSMMatrices.glPushMatrix();
GLSMMatrices.glTranslated(x, y, z);
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE1, REJECT_ALL);
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE1);
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0, KEEP_LOWER_HALF);
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
GLSMMatrices.glTranslated(0.5, 0.5, 0.5);
bindTexture(TextureMap.locationBlocksTexture);
RenderBlocks.getInstance().renderBlockAsItem(Blocks.gold_block, 0, 1.0f);
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
GLSMMatrices.glPopMatrix();
}
}
@@ -0,0 +1,215 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
import com.gtnewhorizons.angelica.api.tesr.TesrMeshProvider;
import com.gtnewhorizons.angelica.api.tesr.TesrMeshSink;
import com.gtnewhorizons.angelica.common.TileTesrTranslucentTest;
import com.gtnewhorizons.angelica.glsm.GLSMBlend;
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
import com.gtnewhorizons.angelica.glsm.GLSMDepthStencilAlpha;
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
import com.gtnewhorizons.angelica.utils.WorkaroundUtils;
import net.minecraft.client.model.ModelBase;
import net.minecraft.client.model.ModelRenderer;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.tileentity.TileEntity;
import org.lwjgl.opengl.GL11;
public final class TesrTranslucentTestRenderers {
private static final double HALF = 0.4375;
private TesrTranslucentTestRenderers() {}
private static void pane(Tessellator t, double half) {
rect(t, -half, -half, half, half);
}
private static void rect(Tessellator t, double x0, double y0, double x1, double y1) {
t.setNormal(0f, 0f, -1f);
t.addVertexWithUV(0.5 + x0, 0.5 + y0, 0.5, 0, 1);
t.addVertexWithUV(0.5 + x0, 0.5 + y1, 0.5, 0, 0);
t.addVertexWithUV(0.5 + x1, 0.5 + y1, 0.5, 1, 0);
t.addVertexWithUV(0.5 + x1, 0.5 + y0, 0.5, 1, 1);
}
private static void drawPane(double half) {
final Tessellator t = Tessellator.instance;
t.startDrawingQuads();
pane(t, half);
t.draw();
}
private static void apply(TesrMaterial material) {
if (material.transparency() == TesrMaterial.Transparency.ADDITIVE) {
GLSMBlend.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
} else {
GLSMBlend.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
}
GLSMCurrentAttribs.glColor4f(material.colorRed(), material.colorGreen(), material.colorBlue(), material.colorAlpha());
}
private abstract static class PaneRenderer extends TileEntitySpecialRenderer {
void begin(double x, double y, double z) {
bindTexture(WorkaroundUtils.GL_TEXTURE_2D_WORKAROUND);
GLSMMatrices.glPushMatrix();
GLSMMatrices.glTranslated(x, y, z);
GLSMEnable.glDisable(GL11.GL_CULL_FACE);
GLSMEnable.glEnable(GL11.GL_BLEND);
}
static void end() {
GLSMCurrentAttribs.glColor4f(1f, 1f, 1f, 1f);
GLSMBlend.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLSMEnable.glDisable(GL11.GL_BLEND);
GLSMEnable.glEnable(GL11.GL_CULL_FACE);
GLSMMatrices.glPopMatrix();
}
}
public static final class Provider extends PaneRenderer implements TesrMeshProvider {
private static final TesrMaterial[] MATERIALS = {
TesrMaterial.builder().color(1f, 0f, 0f, 0.5f).translucent().noCull().build(),
TesrMaterial.builder().color(0.5f, 0f, 0.5f, 1f).additive().noCull().build() };
private static final Object[] KEYS = { new Object(), new Object() };
private static int kind(TileEntity te) {
return ((TileTesrTranslucentTest) te).variant() == 1 ? 1 : 0;
}
@Override
public Object angelica$meshKey(TileEntity te) {
return KEYS[kind(te)];
}
@Override
public void angelica$build(TesrMeshSink sink, TileEntity te) {
pane(sink.angelica$bucket(DefaultVertexFormat.POSITION_TEXTURE_NORMAL, WorkaroundUtils.GL_TEXTURE_2D_WORKAROUND, MATERIALS[kind(te)]), HALF);
}
@Override
public void renderTileEntityAt(TileEntity te, double x, double y, double z, float partialTicks) {
begin(x, y, z);
apply(MATERIALS[kind(te)]);
drawPane(HALF);
end();
}
}
public static final class Model extends PaneRenderer {
private static final TesrMaterial TRANSLUCENT_GREEN = TesrMaterial.builder().color(0f, 1f, 0f, 0.5f).translucent().build();
private static final TesrMaterial TRANSLUCENT_YELLOW = TesrMaterial.builder().color(1f, 1f, 0f, 0.5f).translucent().build();
private static final TesrMaterial TRANSLUCENT_MAGENTA = TesrMaterial.builder().color(1f, 0f, 1f, 0.5f).translucent().build();
private static final TesrMaterial ADDITIVE_CYAN = TesrMaterial.builder().color(0f, 0.6f, 0.6f, 1f).additive().build();
private final ModelBase model = new ModelBase() {};
private final ModelRenderer base = new ModelRenderer(model, 0, 0);
private final ModelRenderer pane = paneAt(0f);
private final ModelRenderer paneFar = paneAt(0.16f);
private final ModelRenderer paneNear = paneAt(-0.16f);
public Model() {
base.addBox(-1f, 7f, -1f, 2, 1, 2);
}
private ModelRenderer paneAt(float z) {
final ModelRenderer renderer = new ModelRenderer(model, 0, 0);
renderer.addBox(-7f, -7f, z, 14, 14, 0);
return renderer;
}
@Override
public void renderTileEntityAt(TileEntity te, double x, double y, double z, float partialTicks) {
final float scale = 1f / 16f;
bindTexture(WorkaroundUtils.GL_TEXTURE_2D_WORKAROUND);
GLSMMatrices.glPushMatrix();
GLSMMatrices.glTranslated(x + 0.5, y + 0.5, z + 0.5);
GLSMCurrentAttribs.glColor4f(1f, 1f, 1f, 1f);
base.render(scale);
GLSMEnable.glEnable(GL11.GL_BLEND);
switch (((TileTesrTranslucentTest) te).variant()) {
case 0 -> {
apply(TRANSLUCENT_GREEN);
pane.render(scale);
}
case 1 -> {
apply(TRANSLUCENT_YELLOW);
GLSMMatrices.glTranslated(-0.5, -0.5, -0.5);
drawPane(HALF);
}
default -> {
apply(TRANSLUCENT_MAGENTA);
paneFar.render(scale);
apply(ADDITIVE_CYAN);
pane.render(scale);
apply(TRANSLUCENT_GREEN);
paneNear.render(scale);
}
}
end();
}
}
public static final class Decal extends PaneRenderer implements TesrMeshProvider {
private static final TesrMaterial WRITER = TesrMaterial.builder().color(1f, 1f, 1f, 0.5f).translucent().noCull().build();
private static final TesrMaterial DECAL = TesrMaterial.builder().color(0f, 0f, 0f, 0.8f).translucent().depthEqual().noCull().build();
private static final Object KEY = new Object();
private static final double INNER = HALF / 2;
private static void writer(Tessellator t) {
rect(t, -HALF, INNER, HALF, HALF);
rect(t, -HALF, -HALF, HALF, -INNER);
rect(t, -HALF, -INNER, -INNER, INNER);
rect(t, INNER, -INNER, HALF, INNER);
pane(t, INNER);
}
@Override
public Object angelica$meshKey(TileEntity te) {
return KEY;
}
@Override
public void angelica$build(TesrMeshSink sink, TileEntity te) {
writer(sink.angelica$bucket(DefaultVertexFormat.POSITION_TEXTURE_NORMAL, WorkaroundUtils.GL_TEXTURE_2D_WORKAROUND, WRITER));
pane(sink.angelica$bucket(DefaultVertexFormat.POSITION_TEXTURE_NORMAL, WorkaroundUtils.GL_TEXTURE_2D_WORKAROUND, DECAL), INNER);
}
@Override
public void renderTileEntityAt(TileEntity te, double x, double y, double z, float partialTicks) {
begin(x, y, z);
apply(WRITER);
final Tessellator t = Tessellator.instance;
t.startDrawingQuads();
writer(t);
t.draw();
GLSMDepthStencilAlpha.glDepthFunc(GL11.GL_EQUAL);
apply(DECAL);
drawPane(INNER);
GLSMDepthStencilAlpha.glDepthFunc(GL11.GL_LEQUAL);
end();
}
}
public static final class Glow extends PaneRenderer {
private static final TesrMaterial TRANSLUCENT_ORANGE = TesrMaterial.builder().color(1f, 0.5f, 0f, 0.6f).translucent().build();
@Override
public void renderTileEntityAt(TileEntity te, double x, double y, double z, float partialTicks) {
begin(x, y, z);
apply(TRANSLUCENT_ORANGE);
GLSMDepthStencilAlpha.glDepthMask(false);
drawPane(HALF);
GLSMDepthStencilAlpha.glDepthMask(true);
end();
}
}
}
@@ -13,6 +13,7 @@ import com.gtnewhorizons.angelica.rendering.TextureSamplers;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
import com.gtnewhorizons.angelica.rendering.translucency.TranslucencyLayers;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
import com.gtnewhorizons.angelica.rendering.voxelization.SdlShadowVoxelizationSink;
@@ -196,6 +197,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
if (program != null) {
program.bind();
program.getInterface().setupState(pass);
if (pass == AngelicaRenderPassConfiguration.TRANSLUCENT_PASS) TranslucencyLayers.INSTANCE.beginTerrainSplit(program.handle());
}
}
@@ -204,6 +206,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
if (irisProgram != null) {
endIrisPass(pass);
} else {
if (pass == AngelicaRenderPassConfiguration.TRANSLUCENT_PASS) TranslucencyLayers.INSTANCE.endTerrainSplit();
super.end(pass);
}
}
Loaded 100 of 157 files, more files were not shown because too many files have changed in this diff. Show more