mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-08 12:35:01 +00:00
Emulate clip distances on devices without them (#2348)
This commit is contained in:
1 parent
aee989d23b
commit
9493d630a0
49 files changed
+1359
-194
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -509,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");
|
||||
|
||||
@@ -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,8 +439,8 @@ public class RenderSystem {
|
||||
return supportsImageLoadStore;
|
||||
}
|
||||
|
||||
public static boolean hasClipCullDistance() {
|
||||
return hasClipCullDistance;
|
||||
public static boolean isClipDistanceEmulated() {
|
||||
return clipDistanceEmulated;
|
||||
}
|
||||
|
||||
private static boolean hasExtension(String name) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ 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.states.TexEnvState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
@@ -79,6 +80,7 @@ public final class FragmentKey {
|
||||
private static final int GLOBAL_BITS = 14;
|
||||
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
|
||||
@@ -126,6 +128,7 @@ public final class FragmentKey {
|
||||
long global = 0;
|
||||
if (CombinedGlint.isActive()) global |= 1L << BIT_COMBINED_GLINT;
|
||||
if (CombinedGlint.replacesAlpha()) global |= 1L << BIT_GLINT_REPLACE_ALPHA;
|
||||
if (GLSMRasterizer.clipEmulationActive(glCtx)) global |= 1L << BIT_CLIP_DISCARD;
|
||||
|
||||
// Fog
|
||||
global |= activeFogMode(glCtx);
|
||||
@@ -259,6 +262,7 @@ 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 int textureSupersamplingSamples() { return 1 << (int) ((packed[0] >> BIT_TEXTURE_SUPERSAMPLING) & 0x7); }
|
||||
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
|
||||
|
||||
@@ -34,6 +34,9 @@ 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);
|
||||
@@ -49,6 +52,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");
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class ClipPlanesInUseEvent extends MutableEvent {}
|
||||
@@ -106,22 +106,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"},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -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);
|
||||
|
||||
+8
-10
@@ -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));
|
||||
|
||||
+37
@@ -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
-1
@@ -288,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
|
||||
|
||||
@@ -12,11 +12,14 @@ 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;
|
||||
@@ -153,6 +156,32 @@ class GlesSmokeTest {
|
||||
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));
|
||||
@@ -173,9 +202,13 @@ class GlesSmokeTest {
|
||||
}
|
||||
|
||||
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(SIZE / 2, SIZE / 2, 1, 1, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, pixel);
|
||||
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);
|
||||
|
||||
@@ -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
|
||||
@@ -114,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;
|
||||
@@ -2547,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;
|
||||
};
|
||||
}
|
||||
@@ -2565,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);
|
||||
@@ -2605,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 {
|
||||
@@ -2613,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;
|
||||
}
|
||||
@@ -2620,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);
|
||||
@@ -2679,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);
|
||||
}
|
||||
@@ -4131,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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -11,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;
|
||||
@@ -183,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;
|
||||
|
||||
+92
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
+10
@@ -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();
|
||||
|
||||
+246
@@ -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,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,11 @@
|
||||
package com.gtnewhorizons.angelica.common;
|
||||
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
|
||||
public class TileEntityClipPlaneTest extends TileEntity {
|
||||
|
||||
@Override
|
||||
public boolean canUpdate() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ 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.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.bettercrashes.BetterCrashesCompat;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
@@ -70,6 +71,7 @@ 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;
|
||||
@@ -86,6 +88,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 +149,7 @@ 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());
|
||||
}
|
||||
AngelicaJar.configureShaderDiskCache(event.getSourceFile());
|
||||
if (AngelicaConfig.enableIris) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.proxy;
|
||||
|
||||
import com.gtnewhorizons.angelica.common.BlockClipPlaneTest;
|
||||
import com.gtnewhorizons.angelica.common.BlockIsbrhTest;
|
||||
import com.gtnewhorizons.angelica.common.BlockTest;
|
||||
import com.gtnewhorizons.angelica.common.TileEntityClipPlaneTest;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import cpw.mods.fml.common.event.FMLInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
|
||||
@@ -14,6 +16,8 @@ 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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PendingProgramSelection;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderingState;
|
||||
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
|
||||
@@ -141,6 +142,7 @@ import java.util.OptionalInt;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -148,6 +150,8 @@ import java.util.function.Supplier;
|
||||
* Encapsulates the compiled shader program objects for the currently loaded shaderpack.
|
||||
*/
|
||||
public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, RenderTargetStateListener, TesrInstancingPipeline, PendingProgramSelection {
|
||||
private static final Tracy.ZoneId Z_CLIP_VARIANT = Tracy.zoneId("irisClipVariant", Tracy.COLOR_IRIS);
|
||||
|
||||
private final RenderTargets renderTargets;
|
||||
|
||||
@Nullable
|
||||
@@ -202,7 +206,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
private final EnumMap<Instancing, Map<Pair<String, InputAvailability>, Map<PatchShaderType, String>>> instancedAttributeTransforms = new EnumMap<>(Instancing.class);
|
||||
private final boolean[] supportsInstancing = initialInstancingSupport();
|
||||
private final Map<Pair<String, InputAvailability>, Map<PatchShaderType, String>> wideLineTransforms = new HashMap<>();
|
||||
private final Map<SharedProgramKey, WideLineProgram> wideLinePrograms = new HashMap<>();
|
||||
private final Map<WideLineKey, WideLineProgram> wideLinePrograms = new HashMap<>();
|
||||
private final boolean packWidensLines;
|
||||
private final ParityFlipState parityState = new ParityFlipState(AngelicaConfig.shaderParityFlip);
|
||||
private final Supplier<ImmutableSet<Integer>> flippedGbuffers;
|
||||
@@ -965,7 +969,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
public int getActivePassProgramId() {
|
||||
if (current == null) return -1;
|
||||
final Program p = current.getProgram();
|
||||
final Program p = current.boundProgram();
|
||||
return p != null ? p.getProgramId() : -1;
|
||||
}
|
||||
|
||||
@@ -1044,6 +1048,29 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
}
|
||||
|
||||
private boolean clipRebindPending;
|
||||
|
||||
public void onClipPlanesInUseChanged() {
|
||||
if (current == null) return;
|
||||
clipRebindPending = true;
|
||||
GLSMHooks.pendingProgramSelection = this;
|
||||
}
|
||||
|
||||
private void resolveClipRebind() {
|
||||
if (!clipRebindPending) return;
|
||||
if (DisplayListManager.isRecording()) {
|
||||
GLSMHooks.pendingProgramSelection = this;
|
||||
return;
|
||||
}
|
||||
clipRebindPending = false;
|
||||
final Pass pass = current;
|
||||
if (pass == null) return;
|
||||
final Program live = pass.liveProgram;
|
||||
if (live != null && GLSMPrograms.getActiveProgram() == live.getProgramId() && pass.select(activeInstancing, GLSMRasterizer.clipEmulationActive()) != live) {
|
||||
pass.rebindProgram();
|
||||
}
|
||||
}
|
||||
|
||||
private void clearPendingSelection() {
|
||||
if (GLSMHooks.pendingProgramSelection == this) {
|
||||
GLSMHooks.pendingProgramSelection = null;
|
||||
@@ -1053,11 +1080,10 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
private void matchPass() {
|
||||
clearPendingSelection();
|
||||
|
||||
if (!shouldOverrideShaders()) {
|
||||
return;
|
||||
if (shouldOverrideShaders()) {
|
||||
matchPass(getCondition(getPhase()));
|
||||
}
|
||||
|
||||
matchPass(getCondition(getPhase()));
|
||||
resolveClipRebind();
|
||||
}
|
||||
|
||||
private void matchPass(RenderCondition condition) {
|
||||
@@ -1139,7 +1165,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
return;
|
||||
}
|
||||
final Program program = pass.getProgram();
|
||||
final Program program = pass.select(Instancing.NONE, GLSMRasterizer.clipEmulationActive());
|
||||
final GlFramebuffer expected = isBeforeTranslucent ? pass.framebufferBeforeTranslucents : pass.framebufferAfterTranslucents;
|
||||
if (program != null && GLSMPrograms.getActiveProgram() == program.getProgramId() && GLSMFramebuffer.getDrawFramebuffer() == expected.getId()) {
|
||||
return;
|
||||
@@ -1199,7 +1225,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
if (shared != null) {
|
||||
final Pass pass = createPassInner(shared.program(), source.getDirectives(), shadow, id);
|
||||
this.customUniforms.mapPassLike(shared.firstPass(), pass);
|
||||
pass.setInstancingKey(source.getName(), availability, shadow);
|
||||
pass.setVariantSource(source, id, availability, shadow);
|
||||
return pass;
|
||||
}
|
||||
|
||||
@@ -1224,7 +1250,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
final Pass pass = createPassInner(program, source.getDirectives(), shadow, id);
|
||||
this.customUniforms.mapholderToPass(builder, pass);
|
||||
pass.setInstancingKey(source.getName(), availability, shadow);
|
||||
pass.setVariantSource(source, id, availability, shadow);
|
||||
if (shareKey != null) {
|
||||
builtPrograms.put(shareKey, new BuiltProgram(program, pass));
|
||||
}
|
||||
@@ -1432,18 +1458,24 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
private final List<BufferBlendOverride> bufferBlendOverrides;
|
||||
private final boolean shadowViewport;
|
||||
|
||||
private String instancingSourceName;
|
||||
private InputAvailability instancingAvailability;
|
||||
private boolean instancingShadow;
|
||||
private final Program[] instancedVariants = new Program[Instancing.VALUES.length];
|
||||
private final boolean[] instancedVariantAttempted = new boolean[Instancing.VALUES.length];
|
||||
private ProgramSource variantSource;
|
||||
private ProgramId variantId;
|
||||
private InputAvailability variantAvailability;
|
||||
private boolean variantShadow;
|
||||
private final Program[][] variants = new Program[Instancing.VALUES.length][2];
|
||||
private final boolean[][] variantAttempted = new boolean[Instancing.VALUES.length][2];
|
||||
@Nullable
|
||||
private WideLineProgram wideLineProgram;
|
||||
private boolean wideLineProgramAttempted;
|
||||
private Program boundProgram;
|
||||
@Nullable
|
||||
private Program liveProgram;
|
||||
private final WideLineProgram[] wideLineVariants = new WideLineProgram[2];
|
||||
private final boolean[] wideLineProgramAttempted = new boolean[2];
|
||||
|
||||
private Pass(@Nullable Program program, GlFramebuffer framebufferBeforeTranslucents, GlFramebuffer framebufferAfterTranslucents,
|
||||
@Nullable AlphaTestOverride alphaTestOverride, @Nullable BlendModeOverride blendModeOverride, @Nullable List<BufferBlendOverride> bufferBlendOverrides, boolean shadowViewport) {
|
||||
this.program = program;
|
||||
this.variants[Instancing.NONE.ordinal()][0] = program;
|
||||
this.variantAttempted[Instancing.NONE.ordinal()][0] = true;
|
||||
this.framebufferBeforeTranslucents = framebufferBeforeTranslucents;
|
||||
this.framebufferAfterTranslucents = framebufferAfterTranslucents;
|
||||
this.alphaTestOverride = alphaTestOverride;
|
||||
@@ -1457,15 +1489,22 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
|
||||
void rebindProgram() {
|
||||
if (program != null) program.use();
|
||||
final boolean suspended = GLStateManager.suspendExitCapture();
|
||||
try {
|
||||
DeferredWorldRenderingPipeline.this.customUniforms.push(this);
|
||||
bindSelected();
|
||||
} finally {
|
||||
GLStateManager.resumeExitCapture(suspended);
|
||||
}
|
||||
}
|
||||
|
||||
private void bindSelected() {
|
||||
final Program selected = select(activeInstancing, GLSMRasterizer.clipEmulationActive());
|
||||
liveProgram = selected;
|
||||
if (activeInstancing == Instancing.NONE) boundProgram = selected;
|
||||
if (selected != null) selected.use();
|
||||
DeferredWorldRenderingPipeline.this.customUniforms.push(selected == null || selected == program ? this : selected);
|
||||
}
|
||||
|
||||
private void useNow() {
|
||||
final boolean suspended = GLStateManager.suspendExitCapture();
|
||||
try {
|
||||
@@ -1511,12 +1550,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
bufferBlendOverrides.forEach(BufferBlendOverride::apply);
|
||||
}
|
||||
|
||||
if (activeInstancing != Instancing.NONE && program != null) {
|
||||
DeferredWorldRenderingPipeline.this.bindInstancedVariant(activeInstancing);
|
||||
} else {
|
||||
if (program != null) program.use();
|
||||
DeferredWorldRenderingPipeline.this.customUniforms.push(this);
|
||||
}
|
||||
bindSelected();
|
||||
}
|
||||
|
||||
public void stopUsing() {
|
||||
@@ -1541,62 +1575,113 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
return program;
|
||||
}
|
||||
|
||||
void setInstancingKey(String sourceName, InputAvailability availability, boolean shadow) {
|
||||
this.instancingSourceName = sourceName;
|
||||
this.instancingAvailability = availability;
|
||||
this.instancingShadow = shadow;
|
||||
@Nullable
|
||||
public Program boundProgram() {
|
||||
return boundProgram != null ? boundProgram : program;
|
||||
}
|
||||
|
||||
void setVariantSource(ProgramSource source, ProgramId id, InputAvailability availability, boolean shadow) {
|
||||
this.variantSource = source;
|
||||
this.variantId = id;
|
||||
this.variantAvailability = availability;
|
||||
this.variantShadow = shadow;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Program instancedVariant(Instancing kind) {
|
||||
Program select(Instancing kind, boolean clipped) {
|
||||
final int i = kind.ordinal();
|
||||
if (!instancedVariantAttempted[i]) {
|
||||
instancedVariantAttempted[i] = true;
|
||||
if (instancingSourceName != null && supportsInstancing[i]) {
|
||||
instancedVariants[i] = sharedInstancedVariant(instancingSourceName, instancingAvailability, instancingShadow, kind);
|
||||
if (!variantAttempted[i][0]) {
|
||||
variantAttempted[i][0] = true;
|
||||
if (variantSource != null && supportsInstancing[i]) {
|
||||
variants[i][0] = sharedVariant(new InstancedVariantKey(variantSource.getName(), variantAvailability, variantShadow, kind, false),
|
||||
() -> buildInstancedVariant(variantSource.getName(), variantAvailability, variantShadow, kind));
|
||||
}
|
||||
}
|
||||
return instancedVariants[i];
|
||||
final Program unclipped = variants[i][0];
|
||||
if (!clipped || unclipped == null) return unclipped;
|
||||
if (!variantAttempted[i][1]) {
|
||||
variantAttempted[i][1] = true;
|
||||
variants[i][1] = sharedVariant(new InstancedVariantKey(variantSource.getName(), variantAvailability, variantShadow, kind, true),
|
||||
() -> buildClipVariant(variantSource, variantId, variantAvailability, variantShadow, kind));
|
||||
}
|
||||
return variants[i][1] != null ? variants[i][1] : unclipped;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
WideLineProgram wideLineProgram() {
|
||||
if (!wideLineProgramAttempted) {
|
||||
wideLineProgramAttempted = true;
|
||||
if (instancingSourceName != null) {
|
||||
wideLineProgram = sharedWideLineProgram(instancingSourceName, instancingAvailability, instancingShadow);
|
||||
WideLineProgram wideLineProgram(boolean clipped) {
|
||||
final int c = clipped ? 1 : 0;
|
||||
if (!wideLineProgramAttempted[c]) {
|
||||
wideLineProgramAttempted[c] = true;
|
||||
if (variantSource != null && (!clipped || wideLineProgram(false) != null)) {
|
||||
wideLineVariants[c] = sharedWideLineProgram(variantSource, variantAvailability, variantShadow, clipped);
|
||||
}
|
||||
}
|
||||
return wideLineProgram;
|
||||
return wideLineVariants[c] != null || !clipped ? wideLineVariants[c] : wideLineVariants[0];
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (this.program != null) {
|
||||
this.program.destroy();
|
||||
}
|
||||
for (int i = 0; i < instancedVariants.length; i++) {
|
||||
if (instancedVariants[i] != null) {
|
||||
instancedVariants[i].destroy();
|
||||
instancedVariants[i] = null;
|
||||
for (int i = 0; i < variants.length; i++) {
|
||||
for (int c = 0; c < 2; c++) {
|
||||
if ((i != Instancing.NONE.ordinal() || c != 0) && variants[i][c] != null) {
|
||||
variants[i][c].destroy();
|
||||
variants[i][c] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record InstancedVariantKey(String sourceName, InputAvailability availability, boolean shadow, Instancing kind) {}
|
||||
private record InstancedVariantKey(String sourceName, InputAvailability availability, boolean shadow, Instancing kind, boolean clipped) {}
|
||||
|
||||
// Passes sharing a program also share its instanced variants
|
||||
private final Map<InstancedVariantKey, Program> instancedVariantPrograms = new HashMap<>();
|
||||
|
||||
@Nullable
|
||||
private Program sharedInstancedVariant(String sourceName, InputAvailability availability, boolean shadow, Instancing kind) {
|
||||
final InstancedVariantKey key = new InstancedVariantKey(sourceName, availability, shadow, kind);
|
||||
private Program sharedVariant(InstancedVariantKey key, Supplier<Program> build) {
|
||||
if (instancedVariantPrograms.containsKey(key)) return instancedVariantPrograms.get(key);
|
||||
final Program variant = buildInstancedVariant(sourceName, availability, shadow, kind);
|
||||
final Program variant = build.get();
|
||||
instancedVariantPrograms.put(key, variant);
|
||||
return variant;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Program buildClipVariant(ProgramSource source, ProgramId id, InputAvailability availability, boolean shadow, Instancing kind) {
|
||||
final String name = source.getName() + kind.variantSuffix + "_clipped";
|
||||
final boolean glint = GlintScrollInjector.shouldInject(id, source);
|
||||
return buildClipped(source, name, (vertex, fragment) -> {
|
||||
final Map<PatchShaderType, String> transformed = TransformPatcher.patchAttributesInstanced(vertex, null, null, null, fragment, availability, glint, kind, true);
|
||||
return transformed != null ? linkVariant(name, transformed, availability, shadow) : null;
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <T> T buildClipped(ProgramSource source, String name, BiFunction<String, String, T> build) {
|
||||
if (source.getGeometrySource().isPresent() || source.getTessControlSource().isPresent() || source.getTessEvalSource().isPresent()) {
|
||||
return null;
|
||||
}
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_CLIP_VARIANT);
|
||||
try {
|
||||
return build.apply(source.getVertexSource().orElse(null), source.getFragmentSource().orElse(null));
|
||||
} catch (Exception e) {
|
||||
Iris.logger.warn("Clip plane variant failed for {}; clip planes stay unclipped there", name, e);
|
||||
return null;
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
private Program linkVariant(String name, Map<PatchShaderType, String> transformed, InputAvailability availability, boolean shadow) {
|
||||
final ProgramBuilder builder = beginBuilder(name, transformed);
|
||||
wireGbufferProgram(builder, availability, shadow);
|
||||
final Program variant = builder.build();
|
||||
vertexWriteReplays.attach(name, variant, transformed, (samplers, images) -> wireGbufferResources(samplers, images, availability, shadow));
|
||||
this.customUniforms.mapholderToPass(builder, variant);
|
||||
return variant;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Program buildInstancedVariant(String sourceName, InputAvailability availability, boolean shadow, Instancing kind) {
|
||||
final Map<Pair<String, InputAvailability>, Map<PatchShaderType, String>> transforms = instancedAttributeTransforms.get(kind);
|
||||
@@ -1605,12 +1690,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final ProgramBuilder builder = beginBuilder(sourceName + kind.variantSuffix, transformed);
|
||||
wireGbufferProgram(builder, availability, shadow);
|
||||
final Program variant = builder.build();
|
||||
vertexWriteReplays.attach(sourceName + kind.variantSuffix, variant, transformed, (samplers, images) -> wireGbufferResources(samplers, images, availability, shadow));
|
||||
this.customUniforms.mapholderToPass(builder, variant);
|
||||
return variant;
|
||||
return linkVariant(sourceName + kind.variantSuffix, transformed, availability, shadow);
|
||||
} catch (Exception e) {
|
||||
Iris.logger.warn("{} instanced variant link failed for {}; disabling it", kind, sourceName, e);
|
||||
supportsInstancing[kind.ordinal()] = false;
|
||||
@@ -1641,50 +1721,52 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
@Override
|
||||
public boolean hasInstancedVariant(Instancing kind) {
|
||||
return current != null && current.instancedVariant(kind) != null;
|
||||
return current != null && current.select(kind, false) != null;
|
||||
}
|
||||
|
||||
public boolean hasInstancedVariant(RenderCondition condition, InputAvailability availability, Instancing kind) {
|
||||
final Pass pass = table.match(condition, availability);
|
||||
return pass != null && pass.instancedVariant(kind) != null;
|
||||
return pass != null && pass.select(kind, false) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindInstancedVariant(Instancing kind) {
|
||||
activeInstancing = kind;
|
||||
final Program variant = current.instancedVariant(kind);
|
||||
variant.use();
|
||||
this.customUniforms.push(variant);
|
||||
current.bindSelected();
|
||||
}
|
||||
|
||||
private record WideLineProgram(Program program, int lineWidthLocation, int screenSizeLocation) {}
|
||||
|
||||
private record WideLineKey(String sourceName, InputAvailability availability, boolean shadow, boolean clipped) {}
|
||||
|
||||
@Nullable
|
||||
private WideLineProgram sharedWideLineProgram(String sourceName, InputAvailability availability, boolean shadow) {
|
||||
final SharedProgramKey key = new SharedProgramKey(sourceName, availability, shadow);
|
||||
private WideLineProgram sharedWideLineProgram(ProgramSource source, InputAvailability availability, boolean shadow, boolean clipped) {
|
||||
final WideLineKey key = new WideLineKey(source.getName(), availability, shadow, clipped);
|
||||
if (wideLinePrograms.containsKey(key)) return wideLinePrograms.get(key);
|
||||
final WideLineProgram built = buildWideLineProgram(sourceName, availability, shadow);
|
||||
final WideLineProgram built = clipped ? buildClippedWideLineProgram(source, availability, shadow)
|
||||
: buildWideLineProgram(source.getName() + "_widelines", wideLineTransforms.get(Pair.of(source.getName(), availability)), availability, shadow);
|
||||
wideLinePrograms.put(key, built);
|
||||
return built;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private WideLineProgram buildWideLineProgram(String sourceName, InputAvailability availability, boolean shadow) {
|
||||
final Map<PatchShaderType, String> transformed = wideLineTransforms.get(Pair.of(sourceName, availability));
|
||||
private WideLineProgram buildClippedWideLineProgram(ProgramSource source, InputAvailability availability, boolean shadow) {
|
||||
final String name = source.getName() + "_widelines_clipped";
|
||||
return buildClipped(source, name, (vertex, fragment) ->
|
||||
buildWideLineProgram(name, TransformPatcher.patchAttributesWideLines(vertex, null, null, null, fragment, availability, true), availability, shadow));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private WideLineProgram buildWideLineProgram(String name, @Nullable Map<PatchShaderType, String> transformed, InputAvailability availability, boolean shadow) {
|
||||
if (transformed == null) {
|
||||
return null;
|
||||
}
|
||||
final String name = sourceName + "_widelines";
|
||||
try {
|
||||
final ProgramBuilder builder = beginBuilder(name, transformed);
|
||||
wireGbufferProgram(builder, availability, shadow);
|
||||
final Program program = builder.build();
|
||||
vertexWriteReplays.attach(name, program, transformed, (samplers, images) -> wireGbufferResources(samplers, images, availability, shadow));
|
||||
this.customUniforms.mapholderToPass(builder, program);
|
||||
final Program program = linkVariant(name, transformed, availability, shadow);
|
||||
final int id = program.getProgramId();
|
||||
return new WideLineProgram(program, GLSMPrograms.glGetUniformLocation(id, "iris_LineWidth"), GLSMPrograms.glGetUniformLocation(id, "iris_ScreenSize"));
|
||||
} catch (Exception e) {
|
||||
Iris.logger.warn("Wide line program link failed for {}; lines there stay 1 px wide", sourceName, e);
|
||||
Iris.logger.warn("Wide line program link failed for {}; lines there stay 1 px wide", name, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1698,8 +1780,8 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
final Pass pass = current;
|
||||
if (pass == null || pass.getProgram() == null || activeInstancing != Instancing.NONE
|
||||
|| GLSMPrograms.getActiveProgram() != pass.getProgram().getProgramId()) {
|
||||
final Program bound = pass != null ? pass.boundProgram() : null;
|
||||
if (bound == null || activeInstancing != Instancing.NONE || GLSMPrograms.getActiveProgram() != bound.getProgramId()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1709,7 +1791,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
return false;
|
||||
}
|
||||
final boolean needsWidening = shadow || !packWidensLines;
|
||||
final WideLineProgram wide = needsWidening ? linePass.wideLineProgram() : null;
|
||||
final WideLineProgram wide = needsWidening ? linePass.wideLineProgram(GLSMRasterizer.clipEmulationActive()) : null;
|
||||
if (needsWidening && wide == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -1742,11 +1824,11 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
|
||||
public void compileWideLinePrograms() {
|
||||
table.match(RenderCondition.LINES, LINE_INPUTS).wideLineProgram();
|
||||
table.match(RenderCondition.LINES, LINE_INPUTS).wideLineProgram(false);
|
||||
final Pass shadowPass = table.match(RenderCondition.SHADOW, SHADOW_INPUTS);
|
||||
if (shadowPass != null) shadowPass.wideLineProgram();
|
||||
if (shadowPass != null) shadowPass.wideLineProgram(false);
|
||||
final Pass shadowWaterPass = table.match(RenderCondition.SHADOW_TRANSLUCENT, SHADOW_INPUTS);
|
||||
if (shadowWaterPass != null) shadowWaterPass.wideLineProgram();
|
||||
if (shadowWaterPass != null) shadowWaterPass.wideLineProgram(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1851,7 +1933,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
table.forEach(pass -> {
|
||||
if (pass == null || !visited.add(pass)) return;
|
||||
for (Instancing kind : Instancing.VALUES) {
|
||||
if (kind != Instancing.NONE) pass.instancedVariant(kind);
|
||||
if (kind != Instancing.NONE) pass.select(kind, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -2667,7 +2749,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
source.getTessControlSource().orElse(null),
|
||||
source.getTessEvalSource().orElse(null),
|
||||
source.getFragmentSource().orElse(null),
|
||||
inputs)));
|
||||
inputs, false)));
|
||||
}
|
||||
|
||||
private static final ProgramId[] INSTANCED_PROGRAM_IDS = {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ClipDistanceGlsl;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderType;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.AttributeParameters;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.Parameters;
|
||||
import org.taumc.glsl.Transformer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
@@ -123,7 +126,9 @@ public class CommonTransformer {
|
||||
});
|
||||
}
|
||||
|
||||
if (parameters.patch == Patch.ATTRIBUTES && parameters.type == ShaderType.VERTEX) {
|
||||
if (parameters.patch == Patch.ATTRIBUTES && RenderSystem.isClipDistanceEmulated()) {
|
||||
injectClipDiscard(root, (AttributeParameters) parameters);
|
||||
} else if (parameters.patch == Patch.ATTRIBUTES && parameters.type == ShaderType.VERTEX) {
|
||||
root.injectVariable("uniform bool angelica_ClipPlanesEnabled;");
|
||||
root.injectVariable("uniform vec4 angelica_ClipPlane[8];");
|
||||
root.appendMain(
|
||||
@@ -144,4 +149,31 @@ public class CommonTransformer {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String CLIP_VS_BLOCK;
|
||||
static {
|
||||
final String[] distances = new String[8];
|
||||
for (int i = 0; i < distances.length; i++) {
|
||||
distances[i] = "dot(angelica_ClipPlane[" + i + "], _cp_ep)";
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder(256).append("{ vec4 _cp_ep = iris_ModelViewMatrix * iris_Vertex;\n");
|
||||
ClipDistanceGlsl.appendVsAssign(sb, distances);
|
||||
CLIP_VS_BLOCK = sb.append('}').toString();
|
||||
}
|
||||
|
||||
private static void injectClipDiscard(Transformer root, AttributeParameters parameters) {
|
||||
if (!parameters.clipDiscard) {
|
||||
return;
|
||||
}
|
||||
if (parameters.type == ShaderType.VERTEX) {
|
||||
root.injectVariable("uniform vec4 angelica_ClipPlane[8];");
|
||||
root.injectVariable(ClipDistanceGlsl.VS_OUT_DIST0);
|
||||
root.injectVariable(ClipDistanceGlsl.VS_OUT_DIST1);
|
||||
root.appendMain(CLIP_VS_BLOCK);
|
||||
} else if (parameters.type == ShaderType.FRAGMENT) {
|
||||
root.injectVariable(ClipDistanceGlsl.FS_IN_DIST0);
|
||||
root.injectVariable(ClipDistanceGlsl.FS_IN_DIST1);
|
||||
root.prependMain(ClipDistanceGlsl.FS_DISCARD);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,7 +238,7 @@ public class ShaderTransformer {
|
||||
final ShaderDiskCache.Key k = ShaderDiskCache.key("iris-transform").str(patch.name());
|
||||
parameters.appendDiskKey(k);
|
||||
k.i(RenderSystem.getMaxGlslVersion()).b(RenderSystem.supportsSSBO()).b(RenderSystem.supportsImageLoadStore())
|
||||
.b(RenderSystem.isGLES()).b(BackendManager.RENDER_BACKEND.isSDLGPU()).b(BackendManager.RENDER_BACKEND.supportsComputeImageAtomics()).str(RwImageStoreExtractor.atomicCustomImagesKey())
|
||||
.b(RenderSystem.isGLES()).b(RenderSystem.isClipDistanceEmulated()).b(BackendManager.RENDER_BACKEND.isSDLGPU()).b(BackendManager.RENDER_BACKEND.supportsComputeImageAtomics()).str(RwImageStoreExtractor.atomicCustomImagesKey())
|
||||
.i(maxSupportedHoistVersion);
|
||||
if (enabledRequirements == null) {
|
||||
k.i(-1);
|
||||
|
||||
@@ -143,11 +143,15 @@ public class TransformPatcher {
|
||||
}
|
||||
|
||||
public static Map<PatchShaderType, String> patchAttributesInstanced(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs, boolean scrollGlint, Instancing instancing) {
|
||||
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, tessControl != null, inputs, scrollGlint, instancing));
|
||||
return patchAttributesInstanced(vertex, geometry, tessControl, tessEval, fragment, inputs, scrollGlint, instancing, false);
|
||||
}
|
||||
|
||||
public static Map<PatchShaderType, String> patchAttributesWideLines(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs) {
|
||||
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, tessControl != null, inputs, false, Instancing.NONE, true));
|
||||
public static Map<PatchShaderType, String> patchAttributesInstanced(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs, boolean scrollGlint, Instancing instancing, boolean clipDiscard) {
|
||||
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, tessControl != null, inputs, scrollGlint, instancing, false, clipDiscard));
|
||||
}
|
||||
|
||||
public static Map<PatchShaderType, String> patchAttributesWideLines(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs, boolean clipDiscard) {
|
||||
return transform(vertex, geometry, tessControl, tessEval, fragment, new AttributeParameters(Patch.ATTRIBUTES, geometry != null, tessControl != null, inputs, false, Instancing.NONE, true, clipDiscard));
|
||||
}
|
||||
|
||||
public static Map<PatchShaderType, String> patchAttributes(String vertex, String geometry, String tessControl, String tessEval, String fragment, InputAvailability inputs) {
|
||||
|
||||
+8
-3
@@ -13,13 +13,14 @@ public class AttributeParameters extends Parameters {
|
||||
public final boolean scrollGlint;
|
||||
public final Instancing instancing;
|
||||
public final boolean wideLines;
|
||||
public final boolean clipDiscard;
|
||||
// WARNING: adding new fields requires updating hashCode and equals methods!
|
||||
|
||||
public AttributeParameters(Patch patch, boolean hasGeometry, boolean hasTesselation, InputAvailability inputs, boolean scrollGlint, Instancing instancing) {
|
||||
this(patch, hasGeometry, hasTesselation, inputs, scrollGlint, instancing, false);
|
||||
this(patch, hasGeometry, hasTesselation, inputs, scrollGlint, instancing, false, false);
|
||||
}
|
||||
|
||||
public AttributeParameters(Patch patch, boolean hasGeometry, boolean hasTesselation, InputAvailability inputs, boolean scrollGlint, Instancing instancing, boolean wideLines) {
|
||||
public AttributeParameters(Patch patch, boolean hasGeometry, boolean hasTesselation, InputAvailability inputs, boolean scrollGlint, Instancing instancing, boolean wideLines, boolean clipDiscard) {
|
||||
super(patch, null);
|
||||
this.hasGeometry = hasGeometry;
|
||||
this.hasTesselation = hasTesselation;
|
||||
@@ -27,6 +28,7 @@ public class AttributeParameters extends Parameters {
|
||||
this.scrollGlint = scrollGlint;
|
||||
this.instancing = instancing;
|
||||
this.wideLines = wideLines;
|
||||
this.clipDiscard = clipDiscard;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -37,7 +39,7 @@ public class AttributeParameters extends Parameters {
|
||||
@Override
|
||||
public void appendDiskKey(ShaderDiskCache.Key k) {
|
||||
super.appendDiskKey(k);
|
||||
k.b(hasGeometry).b(hasTesselation).str(inputs == null ? null : inputs.name()).b(scrollGlint).str(instancing.name()).b(wideLines);
|
||||
k.b(hasGeometry).b(hasTesselation).str(inputs == null ? null : inputs.name()).b(scrollGlint).str(instancing.name()).b(wideLines).b(clipDiscard);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,6 +51,7 @@ public class AttributeParameters extends Parameters {
|
||||
result = prime * result + (scrollGlint ? 1231 : 1237);
|
||||
result = prime * result + instancing.ordinal();
|
||||
result = prime * result + (wideLines ? 1231 : 1237);
|
||||
result = prime * result + (clipDiscard ? 1231 : 1237);
|
||||
result = prime * result + ((inputs == null) ? 0 : inputs.hashCode());
|
||||
return result;
|
||||
}
|
||||
@@ -72,6 +75,8 @@ public class AttributeParameters extends Parameters {
|
||||
return false;
|
||||
if (wideLines != other.wideLines)
|
||||
return false;
|
||||
if (clipDiscard != other.clipDiscard)
|
||||
return false;
|
||||
if (inputs == null) {
|
||||
return other.inputs == null;
|
||||
} else return inputs.equals(other.inputs);
|
||||
|
||||
@@ -99,7 +99,7 @@ class AttributeTransformerLinkGLTest {
|
||||
|
||||
@Test
|
||||
void wideLineVariantLinksWithLineUniformsLive() {
|
||||
final Map<PatchShaderType, String> result = TransformPatcher.patchAttributesWideLines(COMPAT_VERTEX, null, null, null, COMPAT_FRAGMENT, InputAvailability.NONE);
|
||||
final Map<PatchShaderType, String> result = TransformPatcher.patchAttributesWideLines(COMPAT_VERTEX, null, null, null, COMPAT_FRAGMENT, InputAvailability.NONE, false);
|
||||
final int program = link("wideLines", result);
|
||||
try {
|
||||
assertNotEquals(-1, GLSMPrograms.glGetUniformLocation(program, "iris_LineWidth"), () -> result.get(PatchShaderType.VERTEX));
|
||||
|
||||
+52
@@ -1,11 +1,13 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ClipDistanceGlsl;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.CubeInstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ParticleInstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.InputAvailability;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.AttributeParameters;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
@@ -467,4 +469,54 @@ class AttributeTransformerInstancedTest {
|
||||
assertNotNull(entityResult);
|
||||
assertHas(entityResult.get(PatchShaderType.VERTEX), "layout(location = " + ImmediateExtendedAttribHandler.LOC_ENTITY_IDS + ") in vec4 iris_Entity;");
|
||||
}
|
||||
|
||||
private static final String[] CLIP_NAMES = {"gl_ClipDistance", "angelica_ClipDist", "angelica_ClipPlane"};
|
||||
|
||||
private static Map<PatchShaderType, String> clipStages(boolean emulated, boolean clipDiscard) {
|
||||
try (var ignored = ClipTestSupport.emulation(emulated)) {
|
||||
final Map<PatchShaderType, String> result = TransformPatcher.patchAttributesInstanced(VERTEX, null, null, null, FRAGMENT, TEX_LM, false, Instancing.NONE, clipDiscard);
|
||||
assertNotNull(result);
|
||||
for (String stage : result.values()) {
|
||||
if (stage != null) ShaderParser.parseShader(stage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nativeClipDistancesWrittenByTheVertexStage() {
|
||||
final Map<PatchShaderType, String> result = clipStages(false, false);
|
||||
assertHas(result.get(PatchShaderType.VERTEX), "gl_ClipDistance[7] = dot(angelica_ClipPlane[7], _cp_ep);");
|
||||
assertLacks(result.get(PatchShaderType.VERTEX), "angelica_ClipDist0");
|
||||
assertLacks(result.get(PatchShaderType.FRAGMENT), "angelica_ClipDist0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emulatedUnclippedVariantCarriesNoClipCode() {
|
||||
final Map<PatchShaderType, String> result = clipStages(true, false);
|
||||
for (String stage : result.values()) {
|
||||
if (stage == null) continue;
|
||||
for (String name : CLIP_NAMES) assertLacks(stage, name);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void emulatedClippedVariantDiscardsFirstInFragmentMain() {
|
||||
final Map<PatchShaderType, String> result = clipStages(true, true);
|
||||
final String v = result.get(PatchShaderType.VERTEX);
|
||||
assertHas(v, "uniform vec4 angelica_ClipPlane[8];");
|
||||
assertHas(v, "out vec4 angelica_ClipDist0;");
|
||||
assertHas(v, "out vec4 angelica_ClipDist1;");
|
||||
assertHas(v, "angelica_ClipDist0 = vec4(dot(angelica_ClipPlane[0], _cp_ep), dot(angelica_ClipPlane[1], _cp_ep), dot(angelica_ClipPlane[2], _cp_ep), dot(angelica_ClipPlane[3], _cp_ep));");
|
||||
assertHas(v, "angelica_ClipDist1 = vec4(dot(angelica_ClipPlane[4], _cp_ep), dot(angelica_ClipPlane[5], _cp_ep), dot(angelica_ClipPlane[6], _cp_ep), dot(angelica_ClipPlane[7], _cp_ep));");
|
||||
assertLacks(v, "gl_ClipDistance");
|
||||
|
||||
final String f = result.get(PatchShaderType.FRAGMENT);
|
||||
assertHas(f, "in vec4 angelica_ClipDist0;");
|
||||
assertHas(f, "in vec4 angelica_ClipDist1;");
|
||||
final String body = squash(f);
|
||||
final int mainIdx = body.indexOf("voidmain(){");
|
||||
assertTrue(mainIdx >= 0, f);
|
||||
assertTrue(body.startsWith(squash(ClipDistanceGlsl.FS_DISCARD), mainIdx + "voidmain(){".length()), "discard is not the first statement of main:\n" + f);
|
||||
}
|
||||
}
|
||||
+21
@@ -1,11 +1,15 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureType;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.InputAvailability;
|
||||
import net.coderbot.iris.helpers.Tri;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.AttributeParameters;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.ComputeParameters;
|
||||
import net.coderbot.iris.pipeline.transform.parameter.TextureStageParameters;
|
||||
import net.coderbot.iris.shaderpack.texture.TextureStage;
|
||||
@@ -97,6 +101,23 @@ class ShaderTransformerDiskCacheTest {
|
||||
assertNotEquals(before, after);
|
||||
}
|
||||
|
||||
@Test
|
||||
void keyChangesWithClipEmulationAndClipDiscard() {
|
||||
final InputAvailability avail = InputAvailability.of(true, true);
|
||||
final AttributeParameters unclipped = new AttributeParameters(Patch.ATTRIBUTES, false, false, avail, false, Instancing.NONE, false, false);
|
||||
final AttributeParameters clipped = new AttributeParameters(Patch.ATTRIBUTES, false, false, avail, false, Instancing.NONE, false, true);
|
||||
final String nativeKey;
|
||||
try (var ignored = ClipTestSupport.emulation(false)) {
|
||||
nativeKey = ShaderTransformer.diskKey(Patch.ATTRIBUTES, compositeInputs(), unclipped).hex();
|
||||
}
|
||||
try (var ignored = ClipTestSupport.emulation(true)) {
|
||||
final String emulatedKey = ShaderTransformer.diskKey(Patch.ATTRIBUTES, compositeInputs(), unclipped).hex();
|
||||
final String clippedKey = ShaderTransformer.diskKey(Patch.ATTRIBUTES, compositeInputs(), clipped).hex();
|
||||
assertNotEquals(nativeKey, emulatedKey);
|
||||
assertNotEquals(emulatedKey, clippedKey);
|
||||
}
|
||||
}
|
||||
|
||||
private static Object2ObjectOpenHashMap<Tri<String, TextureType, TextureStage>, String> textureMap(int capacity) {
|
||||
final Object2ObjectOpenHashMap<Tri<String, TextureType, TextureStage>, String> m = new Object2ObjectOpenHashMap<>(capacity);
|
||||
m.put(new Tri<>("colortex0", TextureType.TEXTURE_2D, TextureStage.COMPOSITE_AND_FINAL), "a.png");
|
||||
|
||||
Reference in new issue
Block a user