Compare commits

...
13 Commits
Author SHA1 Message Date
Jason Mitchell 996fb54cb0 Add a weather API for mods and instance their weather (#2364)
* Mods can supply their own weather columns, drawn alongside vanilla rain and snow
* Mod weather renders instanced with and without shader packs
* NotFine leaf settings apply to Biomes O' Plenty leaves
* Flyby can start in a named biome and fly in survival
2026-11-13 15:03:24 -08:00
Eldrinn Elantey d1e7838cf9 Render fluids stored by LiquidLogged in the chunk mesher (#2356)
* Render fluids stored by LiquidLogged in the chunk mesher

* Update dependencies.gradle.kts
2026-11-13 14:05:32 -08:00
Alexdoru a12d2bb4b0 fix world client leak in MixinRenderBlockConnectedTexturesEthereal (#2360) 2026-11-11 12:33:01 -08:00
Eldrinn Elantey 60d1a3de3c Restore FluidLogged fluid rendering (#2354)
* Restore FluidLogged fluid rendering

* Check region type before reading logged fluid
2026-11-08 18:39:33 -08:00
DarkShadow44 367f9d9161 Fix compatibility with LOTR mod (#2353) 2026-11-08 14:56:07 -08:00
Jason Mitchell d69fd20a8b Tag profiler captures with the Angelica version (#2351)
* RenderDoc, Tracy, async-profiler, spark
2026-11-07 22:49:01 -08:00
Jason Mitchell 9493d630a0 Emulate clip distances on devices without them (#2348) 2026-11-07 13:42:32 -08:00
Jason Mitchell aee989d23b Fix flat item icons on drawers and barrels with shader packs (#2347) 2026-11-07 12:59:19 -08:00
Jason Mitchell 3876218405 Fix GLES crashing at startup and rendering black (#2346) 2026-11-06 21:30:30 -08:00
Armisael5andEclipse-Sol a6d014a9eb Fix falling block texture (#2336)
Co-authored-by: Eclipse-Sol <[email protected]>
2026-11-06 15:25:15 -05:00
Eldrinn Elantey 52444510d0 Move the tilde glyph down in the modern ascii font sheet (#2343) 2026-11-06 09:05:22 -08:00
DarkXFast 8840c2109f Fix replayed "glDrawElements" display lists losing their normals and texcoords (#2328) 2026-11-02 08:40:12 -08:00
Alexdoru e64a4e7214 Fix world client leak in PositionEntries$PlayerPosition (#2327) 2026-11-02 08:35:41 -08:00
123 changed files with 3904 additions and 766 deletions

No files matched your search

+5 -2
View File
@@ -26,7 +26,7 @@ jobs:
- name: Install a software Vulkan driver
run: |
sudo apt-get update -y
sudo apt-get install -y xvfb mesa-vulkan-drivers
sudo apt-get install -y xvfb mesa-vulkan-drivers libgl1-mesa-dri
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
@@ -36,7 +36,7 @@ jobs:
25
- uses: gradle/actions/setup-gradle@v5
- name: Run SDL-GPU tests
run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest
run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest :lwjgl3-backend:glesTest
- name: Check LWJGL did not extract natives to the shared temp dir
run: test ! -e "/tmp/lwjgl_$USER"
- name: Attach SDL-GPU test results
@@ -48,3 +48,6 @@ jobs:
sdl-gpu/build/test-results/
sdl-gpu/build/reports/tests/
sdl-gpu/hs_err_pid*.log
lwjgl3-backend/build/test-results/
lwjgl3-backend/build/reports/tests/
lwjgl3-backend/hs_err_pid*.log
+3
View File
@@ -186,6 +186,8 @@ DependencyHandlerScope.of(dependencies).apply {
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
compileOnly("com.github.GTNewHorizons:LiquidLogged:0.2.0:dev") { isTransitive = false }
compileOnly("com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.5-alpha:dev") { isTransitive = false }
}
@@ -210,6 +212,7 @@ afterEvaluate {
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
testCompileOnly(testFixtures(project(":glsm")))
testImplementation("mega:fluidlogged-mc1.7.10:0.1.2")
testImplementation("com.github.GTNewHorizons:LiquidLogged:0.2.0:dev") { isTransitive = false }
testRuntimeOnly(libs.fastutil)
testRuntimeOnly(libs.joml)
+17
View File
@@ -67,6 +67,22 @@ artifacts {
}
}
// LWJGL2-API stubs for test JVMs that strip the lwjgl-2 jars (glsmSdlTest, glesTest).
val lwjgl2StubsSourceSet = sourceSets.create("lwjgl2Stubs") {
java.srcDir("src/lwjgl2Stubs/java")
}
val lwjgl2Stubs by configurations.creating {
isCanBeConsumed = true
isCanBeResolved = false
}
artifacts {
add(lwjgl2Stubs.name, lwjgl2StubsSourceSet.java.classesDirectory) {
builtBy(tasks.named("lwjgl2StubsClasses"))
}
}
repositories {
maven {
name = "Mojang"
@@ -111,6 +127,7 @@ val spirvTestRuntime by configurations.creating {
dependencies {
// MC compile stubs
compileOnly(sourceSets["stubs"].output)
"lwjgl2StubsCompileOnly"(libs.lwjgl3.opengl)
// LWJGL
compileOnly(libs.lwjgl2)
@@ -1,5 +1,7 @@
package org.lwjgl.opengl;
import java.lang.reflect.Field;
public class ContextCapabilities {
public boolean OpenGL30 = true;
@@ -21,11 +23,28 @@ public class ContextCapabilities {
public boolean GL_ARB_sampler_objects = true;
public boolean GL_ARB_shader_image_load_store = true;
public boolean GL_ARB_shader_storage_buffer_object = true;
public boolean GL_ARB_stencil_texturing = false;
public boolean GL_ARB_tessellation_shader = false;
public boolean GL_ARB_texture_storage = false;
public boolean GL_EXT_direct_state_access = false;
public boolean GL_EXT_gpu_shader4 = true;
public boolean GL_EXT_shader_image_load_store = true;
public boolean GL_NVX_gpu_memory_info = false;
public ContextCapabilities() {
if ("lwjgl3".equals(System.getProperty("glsm.stubs.capabilities"))) copyFromCurrentContext();
}
private void copyFromCurrentContext() {
final GLCapabilities caps = GL.getCapabilities();
for (Field field : ContextCapabilities.class.getFields()) {
try {
field.setBoolean(this, GLCapabilities.class.getField(field.getName()).getBoolean(caps));
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("LWJGL3 GLCapabilities has no " + field.getName(), e);
}
}
}
}
@@ -0,0 +1,13 @@
package org.lwjgl.opengl;
public final class GLContext {
private static ContextCapabilities capabilities;
private GLContext() {}
public static ContextCapabilities getCapabilities() {
if (capabilities == null) capabilities = new ContextCapabilities();
return capabilities;
}
}
@@ -21,6 +21,7 @@ public final class SystemProperties {
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
public static final boolean EMULATE_CLIP_DISTANCES = Boolean.getBoolean("angelica.emulateClipDistances");
// Redirector
public static final UnmappedGLMode UNMAPPED_GL = parseEnum("angelica.unmappedGL", UnmappedGLMode.WARN, UnmappedGLMode.WARN, UnmappedGLMode.class);
@@ -65,6 +66,8 @@ public final class SystemProperties {
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
public static final Integer FLYBY_DIMENSION = Integer.getInteger("angelica.flyby.dimension");
public static final String FLYBY_BIOME = System.getProperty("angelica.flyby.biome", "");
public static final boolean FLYBY_SURVIVAL = Boolean.getBoolean("angelica.flyby.survival");
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
public static final FlybyCamera FLYBY_CAMERA = parseEnum("angelica.flyby.camera", FlybyCamera.FIRST_PERSON, FlybyCamera.FIRST_PERSON, FlybyCamera.class);
@@ -26,12 +26,20 @@ public final class CaptureGate {
public static final boolean TOOL_ATTACHED = detectCaptureTool();
public static boolean markersThisFrame = true;
public static boolean tracyConnected;
private static boolean toolAttached = TOOL_ATTACHED;
private CaptureGate() {}
public static void refresh() {
tracyConnected = Tracy.isConnected();
markersThisFrame = gateValue(FORCED, TOOL_ATTACHED, tracyConnected);
markersThisFrame = gateValue(FORCED, toolAttached, tracyConnected);
}
static void probeGlTool() {
if (RenderSystem.hasExtension("GL_EXT_debug_tool")) {
toolAttached = true;
LOGGER.info("Capture tool detected via GL_EXT_debug_tool; instrumentation enabled");
}
}
static boolean gateValue(boolean forced, boolean toolAttached, boolean tracyConnected) {
@@ -39,7 +47,7 @@ public final class CaptureGate {
}
public static boolean enabledAtStartup() {
return FORCED || TOOL_ATTACHED;
return FORCED || toolAttached;
}
private static boolean detectCaptureTool() {
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
public final class ClipPlaneBooleanState extends BooleanStateStack {
@@ -13,12 +14,22 @@ public final class ClipPlaneBooleanState extends BooleanStateStack {
this.index = index;
}
@Override
protected boolean forwardsToBackend() {
return !RenderSystem.isClipDistanceEmulated();
}
@Override
protected void onEnabledChanged() {
final int before = owner.clipPlaneEnabledMask;
if (isEnabled()) {
owner.clipPlaneEnabledMask |= (1 << index);
} else {
owner.clipPlaneEnabledMask &= ~(1 << index);
}
owner.clipPlaneGeneration++;
if ((before == 0) != (owner.clipPlaneEnabledMask == 0) && RenderSystem.isClipDistanceEmulated() && GLSMHooks.CLIP_PLANES_IN_USE.hasListeners()) {
GLSMHooks.CLIP_PLANES_IN_USE.post(GLSMHooks.clipPlanesInUseEvent);
}
}
}
@@ -249,7 +249,7 @@ public class CompatUniformManager {
final int cpGen = glCtx.clipPlaneGeneration;
if (cpGen != pu.lastClipPlaneGen) {
pu.lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
uploadClipPlanes(locs, glCtx.clipPlaneEnabledMask);
}
}
@@ -468,16 +468,14 @@ public class CompatUniformManager {
RENDER_BACKEND.uniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
}
private static void uploadClipPlanes(int[] locs) {
private static void uploadClipPlanes(int[] locs, int enabledMask) {
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLSMRasterizer.anyClipPlaneEnabled() ? 1 : 0);
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], enabledMask != 0 ? 1 : 0);
}
if (locs[LOC_CLIP_PLANES] != -1) {
final ClipPlaneState cps = GLSMRasterizer.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
cps.putEffectivePlanes(enabledMask, clipPlaneBuf);
clipPlaneBuf.flip();
RENDER_BACKEND.uniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
}
@@ -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;
}
@@ -88,8 +88,11 @@ public final class GLSMRasterizer {
static void probePointSizeRange(GLContextState glCtx) {
final FloatBuffer range = glCtx.queryScratch;
range.clear();
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
final float smooth = range.get(1);
float smooth = 0.0f;
if (!RenderSystem.isGLES()) {
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
smooth = range.get(1);
}
RENDER_BACKEND.getFloat(GL12.GL_ALIASED_POINT_SIZE_RANGE, range);
maxPointSize = Math.max(smooth, range.get(1));
}
@@ -506,6 +509,14 @@ public final class GLSMRasterizer {
return glCtx.clipPlaneEnabledMask != 0;
}
public static boolean clipEmulationActive() {
return RenderSystem.isClipDistanceEmulated() && anyClipPlaneEnabled();
}
public static boolean clipEmulationActive(GLContextState glCtx) {
return RenderSystem.isClipDistanceEmulated() && anyClipPlaneEnabled(glCtx);
}
public static void glSampleCoverage(float value, boolean invert) {
if (DisplayListManager.isRecording()) {
throw DisplayListManager.unsupportedInList("glSampleCoverage");
@@ -458,6 +458,7 @@ public class GLStateManager {
static void init(Runnable initCallback) {
final GLContextState glCtx = ctx();
RenderSystem.initRenderer();
CaptureGate.probeGlTool();
GLSMFramebuffer.queryDefaultDrawBuffer();
@@ -63,7 +63,7 @@ public class RenderSystem {
private static volatile boolean isGLES;
private static volatile int glesVersion;
private static volatile boolean glesDetected;
private static volatile boolean hasClipCullDistance;
private static boolean clipDistanceEmulated;
private static volatile boolean isLTW;
private static volatile boolean ltwDetected;
@@ -132,7 +132,12 @@ public class RenderSystem {
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
supportsStencilTexturing = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_stencil_texturing;
supportsTextureStorage = isGLES ? glesVersion >= 300 : GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_texture_storage;
hasClipCullDistance = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
final boolean esClipExt = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
final int maxClipDistances = esClipExt ? RENDER_BACKEND.getInteger(GL30.GL_MAX_CLIP_DISTANCES) : 0;
clipDistanceEmulated = SystemProperties.EMULATE_CLIP_DISTANCES || maxClipDistances < GLStateManager.MAX_CLIP_PLANES;
if (clipDistanceEmulated) {
GLStateManager.LOGGER.info("Clip distances emulated (GL_MAX_CLIP_DISTANCES={}, forced={})", maxClipDistances, SystemProperties.EMULATE_CLIP_DISTANCES);
}
// Cache maximum image units
if (supportsImageLoadStore) {
@@ -434,11 +439,11 @@ public class RenderSystem {
return supportsImageLoadStore;
}
public static boolean hasClipCullDistance() {
return hasClipCullDistance;
public static boolean isClipDistanceEmulated() {
return clipDistanceEmulated;
}
private static boolean hasExtension(String name) {
static boolean hasExtension(String name) {
final int count = GLSMQueries.glGetInteger(GL30.GL_NUM_EXTENSIONS);
for (int i = 0; i < count; i++) {
if (name.equals(GLSMQueries.glGetStringi(GL11.GL_EXTENSIONS, i))) return true;
@@ -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");
}
}
@@ -108,11 +108,13 @@ public final class FFPUniformBlock {
public static final int FOG_PARAMS = vec4("u_FogParams");
public static final int FOG_COLOR = vec4("u_FogColor");
// Rain and snow, for Instancing.WEATHER. 0 = (translate.xyz, 1/radius),
// 1 = (cameraFrac.xy, partialTicks, age), 2 = (rainScroll, snowScroll, rainStrength, unused).
// Weather columns, for Instancing.WEATHER. 0 = (translate.xyz, 1/radius),
// 1 = (cameraFrac.xy, partialTicks, age), 2 = (vanillaTick, providerTick, rainStrength, providerAge),
// 3 = (provider 1/radius, unused, unused, unused).
public static final int WEATHER_PARAMS_0 = vec4("u_WeatherParams0");
public static final int WEATHER_PARAMS_1 = vec4("u_WeatherParams1");
public static final int WEATHER_PARAMS_2 = vec4("u_WeatherParams2");
public static final int WEATHER_PARAMS_3 = vec4("u_WeatherParams3");
public static final int SIZE;
public static final String GLSL_DECL;
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
import com.gtnewhorizons.angelica.glsm.GLSMLighting;
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
import com.gtnewhorizons.angelica.glsm.states.LightState;
@@ -15,12 +16,75 @@ public final class FFPVertexLighting {
private static final Vector4f scratchEyePos = new Vector4f();
private static final Matrix3f scratchNormalMat = new Matrix3f();
private static final Vector3f scratchVec = new Vector3f();
private static final Vector3f scratchFactor = new Vector3f();
private static final Vector3f base = new Vector3f();
private static final Vector3f[] lightDirs = new Vector3f[VertexKey.FFP_LIGHT_COUNT];
private static final Vector3f[] lightDiffuse = new Vector3f[VertexKey.FFP_LIGHT_COUNT];
private static int litCount;
private static boolean normalize;
private static float rescale;
static {
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
lightDirs[i] = new Vector3f();
lightDiffuse[i] = new Vector3f();
}
}
private FFPVertexLighting() {}
public static boolean modulatesVertexColor(Vector3f destFactor) {
destFactor.set(1.0f);
if (!prepare(false)) return false;
factor(ShaderManager.getCurrentNormal(), scratchFactor);
final float r = scratchFactor.x;
final float g = scratchFactor.y;
final float b = scratchFactor.z;
if (r == 1.0f && g == 1.0f && b == 1.0f) return false;
if (!Float.isFinite(r + g + b)) return false;
destFactor.set(r, g, b);
return true;
}
public static boolean bakeColors(int[] rawBuffer, int vertexCount, boolean hasNormals, boolean hasColor) {
if (vertexCount <= 0 || !prepare(true)) return false;
if (!hasNormals) factor(ShaderManager.getCurrentNormal(), scratchFactor);
float cr = 0.0f, cg = 0.0f, cb = 0.0f;
int alphaBits = 0;
if (!hasColor) {
final var color = GLSMCurrentAttribs.getColor();
cr = clamp01(color.getRed());
cg = clamp01(color.getGreen());
cb = clamp01(color.getBlue());
alphaBits = toByte(clamp01(color.getAlpha())) << 24;
}
boolean haveNormal = false;
int lastNormal = 0;
for (int v = 0, i = 0; v < vertexCount; v++, i += 8) {
if (hasNormals) {
final int packed = rawBuffer[i + 6];
if (!haveNormal || packed != lastNormal) {
factor((byte) packed / 127.0f, (byte) (packed >> 8) / 127.0f, (byte) (packed >> 16) / 127.0f, scratchFactor);
lastNormal = packed;
haveNormal = true;
}
}
if (hasColor) {
final int packed = rawBuffer[i + 5];
cr = (packed & 0xFF) / 255.0f;
cg = ((packed >>> 8) & 0xFF) / 255.0f;
cb = ((packed >>> 16) & 0xFF) / 255.0f;
alphaBits = packed & 0xFF000000;
}
rawBuffer[i + 5] = alphaBits | (toByte(clamp01(cb * scratchFactor.z)) << 16) | (toByte(clamp01(cg * scratchFactor.y)) << 8) | toByte(clamp01(cr * scratchFactor.x));
}
return true;
}
private static boolean prepare(boolean directionalOnly) {
if (!GLSMLighting.getLightingState().isEnabled()) return false;
if (!GLSMLighting.getColorMaterial().isEnabled()) return false;
if (VertexKey.encodeColorMaterialMode(GLSMLighting.getColorMaterialParameter().getValue()) != VertexKey.CM_AMBIENT_AND_DIFFUSE) return false;
@@ -32,44 +96,58 @@ public final class FFPVertexLighting {
final boolean materialHasSpecular = material.shininess > 0.0f && (material.specular.x != 0.0f || material.specular.y != 0.0f || material.specular.z != 0.0f);
final Vector4f lightModelAmbient = GLSMLighting.getLightModel().ambient;
float r = lightModelAmbient.x;
float g = lightModelAmbient.y;
float b = lightModelAmbient.z;
base.set(lightModelAmbient.x, lightModelAmbient.y, lightModelAmbient.z);
final var lightEnables = GLSMLighting.getLightStates();
final var lightData = GLSMLighting.getLightDataStates();
boolean normalReady = false;
litCount = 0;
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
if (!lightEnables[i].isEnabled()) continue;
final LightState light = lightData[i];
if (materialHasSpecular && (light.specular.x != 0.0f || light.specular.y != 0.0f || light.specular.z != 0.0f))
return false;
if (!normalReady) {
transformCurrentNormal();
normalReady = true;
}
final float nDotVP = Math.max(scratchNormal.dot(lightDirection(light)), 0.0f);
r += light.ambient.x + nDotVP * light.diffuse.x;
g += light.ambient.y + nDotVP * light.diffuse.y;
b += light.ambient.z + nDotVP * light.diffuse.z;
if (directionalOnly && light.position.w != 0.0f) return false;
base.add(light.ambient.x, light.ambient.y, light.ambient.z);
lightDirs[litCount].set(lightDirection(light));
lightDiffuse[litCount].set(light.diffuse.x, light.diffuse.y, light.diffuse.z);
litCount++;
}
if (r == 1.0f && g == 1.0f && b == 1.0f) return false;
if (!Float.isFinite(r + g + b)) return false;
destFactor.set(r, g, b);
if (litCount > 0) {
GLSMMatrices.getModelViewMatrix().normal(scratchNormalMat);
normalize = GLSMLighting.getNormalizeState().isEnabled();
rescale = !normalize && GLSMLighting.getRescaleNormalState().isEnabled() ? Uniforms.rescaleFactor(scratchNormalMat, scratchVec) : 1.0f;
}
return true;
}
private static void transformCurrentNormal() {
GLSMMatrices.getModelViewMatrix().normal(scratchNormalMat);
scratchNormal.set(ShaderManager.getCurrentNormal());
private static void factor(Vector3f normal, Vector3f dest) {
factor(normal.x, normal.y, normal.z, dest);
}
private static void factor(float nx, float ny, float nz, Vector3f dest) {
dest.set(base);
if (litCount == 0) return;
scratchNormal.set(nx, ny, nz);
scratchNormalMat.transform(scratchNormal);
if (GLSMLighting.getNormalizeState().isEnabled()) {
if (normalize) {
scratchNormal.normalize();
} else if (GLSMLighting.getRescaleNormalState().isEnabled()) {
scratchNormal.mul(Uniforms.rescaleFactor(scratchNormalMat, scratchVec));
} else {
scratchNormal.mul(rescale);
}
for (int i = 0; i < litCount; i++) {
final float nDotVP = Math.max(scratchNormal.dot(lightDirs[i]), 0.0f);
dest.fma(nDotVP, lightDiffuse[i]);
}
}
private static float clamp01(float value) {
return Math.min(Math.max(value, 0.0f), 1.0f);
}
private static int toByte(float unit) {
return (int) (unit * 255.0f + 0.5f);
}
private static Vector3f lightDirection(LightState light) {
@@ -3,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");
}
@@ -39,17 +39,16 @@ public final class InstancedGlslHelpers {
vec2 {p}wColumn = {span}.xy;
float {p}wBottom = {span}.z;
float {p}wTop = {span}.w;
vec2 {p}wLightmap = {params}.xy;
float {p}wHash = {params}.z;
bool {p}wSnow = {params}.w > 0.5;
float {p}wLightV = floor({params}.x / 65536.0);
vec2 {p}wLightmap = vec2({params}.x - {p}wLightV * 65536.0, {p}wLightV);
bool {p}wProvider = {params}.w > 0.5;
vec3 {p}wTranslate = {p}WeatherParams0.xyz;
float {p}wInvRadius = {p}WeatherParams0.w;
float {p}wInvRadius = {p}wProvider ? {p}WeatherParams3.x : {p}WeatherParams0.w;
vec2 {p}wCameraFrac = {p}WeatherParams1.xy;
float {p}wPartialTicks = {p}WeatherParams1.z;
float {p}wAge = {p}WeatherParams1.w;
float {p}wRainScroll = {p}WeatherParams2.x;
float {p}wSnowScroll = {p}WeatherParams2.y;
float {p}wStrength = {p}WeatherParams2.z;
float {p}wAge = {p}wProvider ? {p}WeatherParams2.w : {p}WeatherParams1.w;
float {p}wTick = {p}wProvider ? {p}WeatherParams2.y : {p}WeatherParams2.x;
float {p}wStrength = {p}wProvider ? 1.0 : {p}WeatherParams2.z;
vec2 {p}wHalfWidth = vec2(-{p}wColumn.y, {p}wColumn.x) * (0.5 / sqrt(dot({p}wColumn, {p}wColumn)));
vec2 {p}wCornerXZ = {p}wColumn + vec2(0.5) + ({corner}.x * 2.0 - 1.0) * {p}wHalfWidth;
@@ -58,22 +57,13 @@ public final class InstancedGlslHelpers {
float {p}wDistance = length({p}wColumn + vec2(0.5) - {p}wCameraFrac) * {p}wInvRadius;
float {p}wFade = 1.0 - {p}wDistance * {p}wDistance;
float {p}wScroll;
vec2 {p}wDrift;
float {p}wAlpha;
if ({p}wSnow) {
{p}wScroll = ({p}wSnowScroll + {p}wPartialTicks) / 512.0;
{p}wDrift = vec2({jitter}.x + {p}wAge * 0.01 * {jitter}.y, {jitter}.z + {p}wAge * {jitter}.w * 0.001);
{p}wAlpha = {p}wFade * 0.3 + 0.5;
} else {
{p}wScroll = (mod({p}wRainScroll + {p}wHash, 32.0) + {p}wPartialTicks) / 32.0 * (3.0 + {jitter}.x);
{p}wDrift = vec2(0.0);
{p}wAlpha = {p}wFade * 0.5 + 0.5;
}
float {p}wScroll = (mod({p}wTick + {params}.y, {layer}.x) + {p}wPartialTicks) / {layer}.x * {params}.z;
vec2 {p}wDrift = vec2({jitter}.x + {p}wAge * {layer}.y * {jitter}.y, {jitter}.z + {p}wAge * {layer}.z * {jitter}.w);
float {p}wAlpha = {p}wFade * {layer}.w + 0.5;
{tex0} = vec4({corner}.x + {p}wDrift.x, {p}wCornerY * 0.25 + {p}wScroll + {p}wDrift.y, 0.0, 1.0);
{tex1} = vec4({p}wLightmap, 0.0, 1.0);
{color} = vec4(1.0, 1.0, 1.0, {p}wAlpha * {p}wStrength);""";
{color} = floor(clamp(vec4({colorAttr}.rgb, {p}wAlpha * {colorAttr}.a * {p}wStrength), 0.0, 1.0) * 255.0) / 255.0;""";
public static String weatherPrologue(String uniformPrefix, String attribPrefix, String corner,
String vertex, String color, String tex0, String tex1, String sep) {
@@ -82,6 +72,8 @@ public final class InstancedGlslHelpers {
.replace("{span}", attribPrefix + "InstColumnSpan")
.replace("{jitter}", attribPrefix + "InstJitter")
.replace("{params}", attribPrefix + "InstWeatherParams")
.replace("{layer}", attribPrefix + "InstWeatherLayer")
.replace("{colorAttr}", attribPrefix + "InstWeatherColor")
.replace("{corner}", corner)
.replace("{vertex}", vertex)
.replace("{color}", color)
@@ -125,6 +117,8 @@ public final class InstancedGlslHelpers {
decls.add(decl(WeatherInstancedAttribs.LOC_COLUMN_SPAN, "vec4", prefix + "InstColumnSpan"));
decls.add(decl(WeatherInstancedAttribs.LOC_JITTER, "vec4", prefix + "InstJitter"));
decls.add(decl(WeatherInstancedAttribs.LOC_PARAMS, "vec4", prefix + "InstWeatherParams"));
decls.add(decl(WeatherInstancedAttribs.LOC_COLOR, "vec4", prefix + "InstWeatherColor"));
decls.add(decl(WeatherInstancedAttribs.LOC_LAYER, "vec4", prefix + "InstWeatherLayer"));
}
}
return decls.toArray(String[]::new);
@@ -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));
@@ -394,8 +391,9 @@ public class Uniforms {
WeatherParams.translateZ, WeatherParams.invRadius);
putVec4(FFPUniformBlock.WEATHER_PARAMS_1, WeatherParams.cameraFracX, WeatherParams.cameraFracZ,
WeatherParams.partialTicks, WeatherParams.age);
putVec4(FFPUniformBlock.WEATHER_PARAMS_2, WeatherParams.rainScroll, WeatherParams.snowScroll,
WeatherParams.rainStrength, 0.0f);
putVec4(FFPUniformBlock.WEATHER_PARAMS_2, WeatherParams.vanillaTick, WeatherParams.providerTick,
WeatherParams.rainStrength, WeatherParams.providerAge);
putVec4(FFPUniformBlock.WEATHER_PARAMS_3, WeatherParams.providerInvRadius, 0.0f, 0.0f, 0.0f);
}
private void putVec4(int offset, Vector4f v) {
@@ -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);
}
}
@@ -3,31 +3,76 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
/**
* One rain or snow column per instance.
* One weather column per instance.
*/
public final class WeatherInstancedAttribs {
public static final int LOC_COLUMN_SPAN = ParticleInstancedAttribs.LOC_CENTER_HALF;
public static final int LOC_JITTER = ParticleInstancedAttribs.LOC_UV;
public static final int LOC_PARAMS = InstancedAttribs.LOC_COLOR;
public static final int LOC_COLOR = InstancedAttribs.LOC_ROW2;
public static final int LOC_LAYER = InstancedAttribs.LOC_OVERLAY;
public static final int OFFSET_COLUMN_SPAN = 0;
public static final int OFFSET_JITTER = 16;
public static final int OFFSET_PARAMS = 32;
public static final int STRIDE = 48;
public static final int OFFSET_COLOR = 48;
public static final int OFFSET_LAYER = 64;
public static final int STRIDE = 80;
public static final int RAIN_PERIOD = 32;
public static final float RAIN_SCROLL_BASE = 3.0F;
public static final float RAIN_FADE = 0.5F;
public static final int SNOW_PERIOD = 512;
public static final float SNOW_U_DRIFT = 0.01F;
public static final float SNOW_V_DRIFT = 0.001F;
public static final float SNOW_FADE = 0.3F;
public static final float SOURCE_VANILLA = 0.0F;
public static final float SOURCE_PROVIDER = 1.0F;
public static void putColumn(ByteBuffer data, int index, float x, float z, float bottom, float top, float uBase, float uDrift,
float vBase, float vDrift, float brightness, int scrollOffset, float scrollScale, float source, float r, float g, float b,
float a, int period, float uDriftScale, float vDriftScale, float fadeScale) {
final int base = index * STRIDE;
data.putFloat(base + OFFSET_COLUMN_SPAN, x);
data.putFloat(base + OFFSET_COLUMN_SPAN + 4, z);
data.putFloat(base + OFFSET_COLUMN_SPAN + 8, bottom);
data.putFloat(base + OFFSET_COLUMN_SPAN + 12, top);
data.putFloat(base + OFFSET_JITTER, uBase);
data.putFloat(base + OFFSET_JITTER + 4, uDrift);
data.putFloat(base + OFFSET_JITTER + 8, vBase);
data.putFloat(base + OFFSET_JITTER + 12, vDrift);
data.putFloat(base + OFFSET_PARAMS, brightness);
data.putFloat(base + OFFSET_PARAMS + 4, scrollOffset & (period - 1));
data.putFloat(base + OFFSET_PARAMS + 8, scrollScale);
data.putFloat(base + OFFSET_PARAMS + 12, source);
data.putFloat(base + OFFSET_COLOR, r);
data.putFloat(base + OFFSET_COLOR + 4, g);
data.putFloat(base + OFFSET_COLOR + 8, b);
data.putFloat(base + OFFSET_COLOR + 12, a);
data.putFloat(base + OFFSET_LAYER, period);
data.putFloat(base + OFFSET_LAYER + 4, uDriftScale);
data.putFloat(base + OFFSET_LAYER + 8, vDriftScale);
data.putFloat(base + OFFSET_LAYER + 12, fadeScale);
}
public static void pointInstanceAttribs(long base) {
GLSMVertexInput.glVertexAttribPointer(LOC_COLUMN_SPAN, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_COLUMN_SPAN);
GLSMVertexInput.glVertexAttribPointer(LOC_JITTER, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_JITTER);
GLSMVertexInput.glVertexAttribPointer(LOC_PARAMS, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_PARAMS);
GLSMVertexInput.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_COLOR);
GLSMVertexInput.glVertexAttribPointer(LOC_LAYER, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LAYER);
}
static void enableInstanceArrays() {
InstancedAttribs.enableInstanced(LOC_COLUMN_SPAN);
InstancedAttribs.enableInstanced(LOC_JITTER);
InstancedAttribs.enableInstanced(LOC_PARAMS);
InstancedAttribs.enableInstanced(LOC_COLOR);
InstancedAttribs.enableInstanced(LOC_LAYER);
}
private WeatherInstancedAttribs() {}
@@ -1,7 +1,7 @@
package com.gtnewhorizons.angelica.glsm.ffp;
/**
* The per-frame half of the rain and snow instancing, set once per weather draw.
* The per-frame half of the weather column instancing, set once per frame.
*/
public final class WeatherParams {
@@ -15,9 +15,14 @@ public final class WeatherParams {
public static float partialTicks;
public static float age;
public static float rainScroll;
public static float snowScroll;
public static float vanillaTick;
public static float providerTick;
public static float rainStrength;
public static float providerAge;
public static float providerInvRadius;
public static final int TICK_WRAP = 1024;
public static int generation;
@@ -25,7 +30,7 @@ public final class WeatherParams {
public static void set(float translateX, float translateY, float translateZ, float invRadius,
float cameraFracX, float cameraFracZ, float partialTicks, float age,
float rainScroll, float snowScroll, float rainStrength) {
int vanillaTick, float rainStrength, int providerTick, float providerAge, float providerInvRadius) {
WeatherParams.translateX = translateX;
WeatherParams.translateY = translateY;
WeatherParams.translateZ = translateZ;
@@ -34,9 +39,11 @@ public final class WeatherParams {
WeatherParams.cameraFracZ = cameraFracZ;
WeatherParams.partialTicks = partialTicks;
WeatherParams.age = age;
WeatherParams.rainScroll = rainScroll;
WeatherParams.snowScroll = snowScroll;
WeatherParams.vanillaTick = vanillaTick & (TICK_WRAP - 1);
WeatherParams.providerTick = providerTick & (TICK_WRAP - 1);
WeatherParams.rainStrength = rainStrength;
WeatherParams.providerAge = providerAge;
WeatherParams.providerInvRadius = providerInvRadius;
generation++;
}
}
@@ -11,7 +11,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* The four corners every rain and snow column is expanded from. x picks the end of the billboard, y picks
* The four corners every weather column is expanded from. x picks the end of the billboard, y picks
* the bottom or top edge.
*/
public final class WeatherQuadMesh {
@@ -49,14 +49,6 @@ public final class WeatherQuadMesh {
return vao;
}
public static void bindInstanceBuffer(int bufferId) {
GLSMVertexInput.glBindVertexArray(vao());
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
WeatherInstancedAttribs.pointInstanceAttribs(0L);
GLSMVertexInput.glBindVertexArray(0);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
public static void delete() {
if (vbo != 0) {
GLSMBuffers.glDeleteBuffers(vbo);
@@ -6,6 +6,7 @@ import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLSMTextureLifetime;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
@@ -16,11 +17,18 @@ public final class FrameHooks {
private static boolean gameLoopStarted;
private static long frameCounter;
private static boolean frameGroupActive;
private static String framePrefix = "frame:";
private static String buildLabel = "";
public static void bootstrapFirstFrame() {
DisplayListManager.abortIfLeaked();
if (!gameLoopStarted) {
gameLoopStarted = true;
final GLSMInitConfig config = GLStateManager.getInitConfig();
if (config != null && !config.getBuildLabel().isEmpty()) {
buildLabel = config.getBuildLabel();
framePrefix = buildLabel + " frame:";
}
RENDER_BACKEND.onFrameBegin();
pushFrameGroup();
}
@@ -48,8 +56,10 @@ public final class FrameHooks {
}
private static void pushFrameGroup() {
final boolean wasConnected = CaptureGate.tracyConnected;
CaptureGate.refresh();
GLDebug.pushGroup("frame:", frameCounter);
if (CaptureGate.tracyConnected && !wasConnected && !buildLabel.isEmpty()) Tracy.message(buildLabel);
GLDebug.pushGroup(framePrefix, frameCounter);
frameGroupActive = true;
}
@@ -31,6 +31,7 @@ public final class GLSMHooks {
public static final EventBus<ProgramDeleteEvent> PROGRAM_DELETE = EventBus.create(ProgramDeleteEvent.class);
public static final EventBus<BlendFuncChangeEvent> BLEND_FUNC_CHANGE = EventBus.create(BlendFuncChangeEvent.class);
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
public static final EventBus<ClipPlanesInUseEvent> CLIP_PLANES_IN_USE = EventBus.create(ClipPlanesInUseEvent.class);
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
public static final EventBus<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
@@ -46,6 +47,7 @@ public final class GLSMHooks {
public static final ProgramDeleteEvent programDeleteEvent = new ProgramDeleteEvent();
public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent();
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
public static final ClipPlanesInUseEvent clipPlanesInUseEvent = new ClipPlanesInUseEvent();
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent();
@@ -17,6 +17,7 @@ public final class GLSMInitConfig {
@Getter private final Runnable postInitCallback;
private final boolean dsaEnabled;
@Getter private final boolean noErrorChecks;
@Getter private final String buildLabel;
private GLSMInitConfig(Builder builder) {
this.lwjglDebug = builder.lwjglDebug;
@@ -27,6 +28,7 @@ public final class GLSMInitConfig {
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
this.dsaEnabled = builder.dsaEnabled;
this.buildLabel = builder.buildLabel;
}
public boolean isDSAEnabled() {
@@ -46,6 +48,7 @@ public final class GLSMInitConfig {
private Runnable postInitCallback = null;
private boolean dsaEnabled = true;
private boolean noErrorChecks;
private String buildLabel = "";
private Builder() {}
@@ -85,6 +88,11 @@ public final class GLSMInitConfig {
return this;
}
public Builder buildLabel(String buildLabel) {
this.buildLabel = buildLabel == null ? "" : buildLabel;
return this;
}
public GLSMInitConfig build() {
return new GLSMInitConfig(this);
}
@@ -0,0 +1,5 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ClipPlanesInUseEvent extends MutableEvent {}
@@ -1,9 +1,12 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL15;
public final class IndexedDrawBatch {
@@ -34,17 +37,29 @@ public final class IndexedDrawBatch {
GLSMVertexInput.glBindVertexArray(v);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, sharedVBO);
final int stride = layout.stride();
int vertexFlags = 0;
for (int i = 0; i < layout.locations.length; i++) {
final int loc = layout.locations[i];
GLSMVertexInput.glVertexAttribPointer(loc, layout.sizes[i], layout.types[i], layout.normalized[i], stride, layout.offset(i));
GLSMVertexInput.glEnableVertexAttribArray(loc);
vertexFlags |= vertexFlag(loc);
}
// glVertexAttribPointer doesn't set these, and the FFP shader only reads the inputs they name. Saved with the VAO.
VAOManager.setCurrentVertexFlags(vertexFlags);
GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEBO);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
vao = v;
vaoCtx = glCtx;
}
private static int vertexFlag(int location) {
if (location == Usage.COLOR.getAttributeLocation()) return VertexFlags.COLOR_BIT;
if (location == Usage.NORMAL.getAttributeLocation()) return VertexFlags.NORMAL_BIT;
if (location == Usage.PRIMARY_UV.getAttributeLocation()) return VertexFlags.TEXTURE_BIT;
if (location == Usage.SECONDARY_UV.getAttributeLocation()) return VertexFlags.BRIGHTNESS_BIT;
return 0;
}
public void delete() {
if (deleted) return;
deleted = true;
@@ -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"},
@@ -15,8 +15,10 @@ import org.taumc.glsl.grammar.GLSLParser;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class GlslVulkanPreprocess {
@@ -56,7 +58,9 @@ public final class GlslVulkanPreprocess {
private GlslVulkanPreprocess() {}
private record CacheKey(String source, int glShaderType, boolean separateReadOnlyImages) {}
public record Result(String rewrittenSource, Set<String> boolUniforms, Set<String> explicitVsInputs) {}
public record Result(String rewrittenSource, Set<String> boolUniforms, Set<String> explicitVsInputs, Map<String, UniformBlock> uniformBlocks) {}
public record UniformBlock(String qualifierText, @Nullable String instanceName) {}
/** Replace {@code [startIdx, stopIdx]} with {@code replacement}. */
public record Edit(int startIdx, int stopIdx, String replacement) {}
@@ -109,7 +113,7 @@ public final class GlslVulkanPreprocess {
final List<Edit> edits = new ArrayList<>();
final Metadata meta = collectEdits(new ShaderIndex(root, source), glShaderType, debugName, separateReadOnlyImages, edits);
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs());
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs(), meta.uniformBlocks());
synchronized (CACHE) {
CACHE.putAndMoveToFirst(key, out);
while (CACHE.size() > CACHE_MAX) CACHE.removeLast();
@@ -117,11 +121,12 @@ public final class GlslVulkanPreprocess {
return out;
}
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless) {}
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless, Map<String, UniformBlock> uniformBlocks) {}
public static Metadata collectEdits(ShaderIndex index, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
final Set<String> bools = new HashSet<>();
Map<String, UniformBlock> uniformBlocks = Map.of();
final Set<String> explicitInputs = new HashSet<>();
final Set<String> readOnlyImages = new HashSet<>();
boolean needsSamplerless = false;
@@ -130,6 +135,14 @@ public final class GlslVulkanPreprocess {
final List<Integer> unlocatedVsInputStarts = new ArrayList<>();
for (GLSLParser.DeclarationContext declaration : index.globalDeclarations()) {
if (declaration.LEFT_BRACE() != null && ShaderIndex.hasStorageQualifier(declaration.type_qualifier(), "uniform")) {
final GLSLParser.Type_qualifierContext blockQualifier = declaration.type_qualifier();
final String qualifierText = index.source().substring(startIdx(blockQualifier), stopIdx(blockQualifier) + 1);
final String instanceName = declaration.IDENTIFIER().size() > 1 ? declaration.IDENTIFIER(1).getText() : null;
if (uniformBlocks.isEmpty()) uniformBlocks = new HashMap<>();
uniformBlocks.put(declaration.IDENTIFIER(0).getText(), new UniformBlock(qualifierText, instanceName));
continue;
}
final GLSLParser.Init_declarator_listContext list = declaration.init_declarator_list();
if (list == null) continue;
final GLSLParser.Single_declarationContext single = list.single_declaration();
@@ -223,7 +236,7 @@ public final class GlslVulkanPreprocess {
}
}
return new Metadata(bools, explicitInputs, needsSamplerless);
return new Metadata(bools, explicitInputs, needsSamplerless, uniformBlocks);
}
public static boolean renamesIdentifier(String name) {
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.UniformBlock;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;
@@ -26,8 +27,10 @@ import org.taumc.glsl.grammar.GLSLParserBaseListener;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.startIdx;
@@ -65,13 +68,13 @@ public final class SpirvShaderTranslator {
try {
final String emitted = emitGlslEs(cr.spirv(), debugName, glShaderType, pre.explicitVsInputs());
if (emitted == null) return null;
return postProcessEsOutput(emitted, pre.boolUniforms());
return postProcessEsOutput(emitted, pre.boolUniforms(), pre.uniformBlocks());
} finally {
memFree(cr.spirv());
}
}
private static @Nullable String postProcessEsOutput(String glslEs, Set<String> boolUniformNames) {
private static @Nullable String postProcessEsOutput(String glslEs, Set<String> boolUniformNames, Map<String, UniformBlock> uniformBlocks) {
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(glslEs);
@@ -80,21 +83,42 @@ public final class SpirvShaderTranslator {
return null;
}
final DefaultBlockInfo block = findDefaultBlock(root);
final List<Edit> edits = new ArrayList<>();
final BlockFinder finder = new BlockFinder(glslEs, uniformBlocks);
ParseTreeWalker.DEFAULT.walk(finder, root);
if (block != null) {
edits.add(new Edit(startIdx(block.structExternalDecl), stopIdx(block.structExternalDecl), buildLooseUniforms(block.members, boolUniformNames)));
final List<Edit> edits = new ArrayList<>();
BlockInfo defaultBlock = null;
final Map<String, Set<String>> bareMembersByVar = new HashMap<>();
for (BlockInfo block : finder.blocks) {
final String structReplacement;
if (block.source == null) {
defaultBlock = block;
structReplacement = buildLooseUniforms(block.members, boolUniformNames);
bareMembersByVar.put(block.varName, block.memberNames());
} else if (block.source.instanceName() == null) {
structReplacement = block.source.qualifierText() + " " + block.structName + " " + block.body + ";";
bareMembersByVar.put(block.varName, block.memberNames());
} else {
structReplacement = block.source.qualifierText() + " " + block.structName + " " + block.body + " " + block.declaratorText + ";";
}
edits.add(new Edit(startIdx(block.structExternalDecl), stopIdx(block.structExternalDecl), structReplacement));
edits.add(new Edit(startIdx(block.uniformExternalDecl), stopIdx(block.uniformExternalDecl), ""));
}
final UseSiteRewriter rewriter = new UseSiteRewriter(block, boolUniformNames, edits);
final Set<String> defaultMembers = defaultBlock == null ? Set.of() : bareMembersByVar.get(defaultBlock.varName);
final UseSiteRewriter rewriter = new UseSiteRewriter(defaultBlock, defaultMembers, boolUniformNames, bareMembersByVar, edits);
ParseTreeWalker.DEFAULT.walk(rewriter, root);
return GlslVulkanPreprocess.applyEdits(glslEs, edits);
}
private record DefaultBlockInfo(ParserRuleContext structExternalDecl, ParserRuleContext uniformExternalDecl, String blockVarName, List<MemberInfo> members) {}
private record BlockInfo(String structName, @Nullable UniformBlock source, ParserRuleContext structExternalDecl, String body, List<MemberInfo> members, ParserRuleContext uniformExternalDecl, String varName, String declaratorText) {
Set<String> memberNames() {
final Set<String> names = new HashSet<>();
for (MemberInfo m : members) names.add(m.name);
return names;
}
}
private record MemberInfo(String typePrefix, String name) {}
@@ -111,31 +135,38 @@ public final class SpirvShaderTranslator {
return sb.toString();
}
private static @Nullable DefaultBlockInfo findDefaultBlock(GLSLParser.Translation_unitContext root) {
final DefaultBlockFinder finder = new DefaultBlockFinder();
ParseTreeWalker.DEFAULT.walk(finder, root);
if (finder.structExternalDecl == null || finder.uniformExternalDecl == null) return null;
return new DefaultBlockInfo(finder.structExternalDecl, finder.uniformExternalDecl, finder.blockVarName, finder.members);
private static boolean isDefaultBlockStruct(String structName) {
return structName.startsWith("_RESERVED_IDENTIFIER_FIXUP_") && structName.endsWith("DefaultUniformBlock");
}
private static final class DefaultBlockFinder extends GLSLParserBaseListener {
String structName;
ParserRuleContext structExternalDecl;
ParserRuleContext uniformExternalDecl;
String blockVarName;
final List<MemberInfo> members = new ArrayList<>();
private static final class BlockFinder extends GLSLParserBaseListener {
private record PendingStruct(@Nullable UniformBlock source, ParserRuleContext externalDecl, String body, List<MemberInfo> members) {}
private final String glslEs;
private final Map<String, UniformBlock> sourceBlocks;
private final Map<String, PendingStruct> pending = new HashMap<>();
final List<BlockInfo> blocks = new ArrayList<>();
BlockFinder(String glslEs, Map<String, UniformBlock> sourceBlocks) {
this.glslEs = glslEs;
this.sourceBlocks = sourceBlocks;
}
@Override
public void enterStruct_specifier(GLSLParser.Struct_specifierContext ctx) {
if (structExternalDecl != null) return;
final TerminalNode id = ctx.IDENTIFIER();
if (id == null) return;
final String idText = id.getText();
if (!idText.startsWith("_RESERVED_IDENTIFIER_FIXUP_") || !idText.endsWith("DefaultUniformBlock")) return;
final UniformBlock source = sourceBlocks.get(idText);
if (source == null && !isDefaultBlockStruct(idText)) return;
structName = idText;
structExternalDecl = findExternalDeclaration(ctx);
final List<MemberInfo> members = source != null && source.instanceName() != null ? List.of() : collectMembers(ctx);
final String body = glslEs.substring(ctx.LEFT_BRACE().getSymbol().getStartIndex(), ctx.RIGHT_BRACE().getSymbol().getStopIndex() + 1);
pending.putIfAbsent(idText, new PendingStruct(source, findExternalDeclaration(ctx), body, members));
}
private static List<MemberInfo> collectMembers(GLSLParser.Struct_specifierContext ctx) {
final List<MemberInfo> members = new ArrayList<>();
for (GLSLParser.Struct_declarationContext sd : ctx.struct_declaration_list().struct_declaration()) {
final StringBuilder typePrefix = new StringBuilder();
if (sd.type_qualifier() != null) typePrefix.append(textWithSpaces(sd.type_qualifier())).append(' ');
@@ -146,11 +177,12 @@ public final class SpirvShaderTranslator {
members.add(new MemberInfo(typePrefix + arraySuffix, decl.IDENTIFIER().getText()));
}
}
return members;
}
@Override
public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) {
if (structName == null || uniformExternalDecl != null) return;
if (pending.isEmpty()) return;
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
if (single == null || single.typeless_declaration() == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
@@ -159,10 +191,14 @@ public final class SpirvShaderTranslator {
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
final GLSLParser.Type_specifier_nonarrayContext ts = fst.type_specifier().type_specifier_nonarray();
if (ts.type_name() == null) return;
if (!structName.equals(ts.type_name().getText())) return;
final String structName = ts.type_name().getText();
final PendingStruct struct = pending.remove(structName);
if (struct == null) return;
blockVarName = single.typeless_declaration().IDENTIFIER().getText();
uniformExternalDecl = findExternalDeclaration(ctx);
final GLSLParser.Typeless_declarationContext declarator = single.typeless_declaration();
final String declaratorText = glslEs.substring(startIdx(declarator), stopIdx(declarator) + 1);
blocks.add(new BlockInfo(structName, struct.source, struct.externalDecl, struct.body, struct.members,
findExternalDeclaration(ctx), declarator.IDENTIFIER().getText(), declaratorText));
}
private static String textWithSpaces(ParseTree tree) {
@@ -192,17 +228,18 @@ public final class SpirvShaderTranslator {
}
private static final class UseSiteRewriter extends GLSLParserBaseListener {
private final @Nullable DefaultBlockInfo block;
private final @Nullable BlockInfo block;
private final Set<String> boolNames;
private final Map<String, Set<String>> bareMembersByVar;
private final List<Edit> edits;
private final Set<String> memberNames;
UseSiteRewriter(@Nullable DefaultBlockInfo block, Set<String> boolNames, List<Edit> edits) {
UseSiteRewriter(@Nullable BlockInfo block, Set<String> memberNames, Set<String> boolNames, Map<String, Set<String>> bareMembersByVar, List<Edit> edits) {
this.block = block;
this.memberNames = memberNames;
this.boolNames = boolNames;
this.bareMembersByVar = bareMembersByVar;
this.edits = edits;
this.memberNames = new HashSet<>();
if (block != null) for (MemberInfo m : block.members) memberNames.add(m.name);
}
@Override
@@ -240,11 +277,11 @@ public final class SpirvShaderTranslator {
}
}
if (block != null && ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) {
final ParseTree inner = ctx.getChild(0);
if (inner.getText().equals(block.blockVarName)) {
if (ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) {
final Set<String> bareMembers = bareMembersByVar.get(ctx.getChild(0).getText());
if (bareMembers != null) {
final String member = ctx.field_selection().getText();
if (memberNames.contains(member) && !coveredByAncestorBoolCompare(ctx)) {
if (bareMembers.contains(member) && !coveredByAncestorBoolCompare(ctx)) {
edits.add(new Edit(startIdx(ctx), stopIdx(ctx), member));
}
}
@@ -254,8 +291,8 @@ public final class SpirvShaderTranslator {
private @Nullable String resolveBoolOperand(ParseTree operand) {
final String text = operand.getText();
if (boolNames.contains(text)) return text;
if (block != null && text.startsWith(block.blockVarName + ".")) {
final String member = text.substring(block.blockVarName.length() + 1);
if (block != null && text.startsWith(block.varName + ".")) {
final String member = text.substring(block.varName.length() + 1);
if (boolNames.contains(member) && memberNames.contains(member)) return member;
}
return null;
@@ -23,8 +23,12 @@ public class BooleanState implements ISettableState<BooleanState> {
this.ffpStateOnly = ffpStateOnly;
}
protected boolean forwardsToBackend() {
return !ffpStateOnly;
}
public void applyToBackend() {
if (ffpStateOnly) return;
if (!forwardsToBackend()) return;
if (enabled) {
RENDER_BACKEND.enable(glCap);
} else {
@@ -51,7 +55,7 @@ public class BooleanState implements ISettableState<BooleanState> {
stateUnknown = false;
this.enabled = enabled;
onEnabledChanged();
if (!ffpStateOnly) {
if (forwardsToBackend()) {
if (enabled) {
RENDER_BACKEND.enable(this.glCap);
} else {
@@ -27,10 +27,16 @@ public class ClipPlaneState {
dest[3] = tempPlane.w;
}
/** Writes the 4-component eye-space plane equation into {@code buf} at its current position. */
public void putEyePlane(int index, FloatBuffer buf) {
final float[] p = eyePlanes[index];
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
// Vulkan and clip emulation have no per-distance enable: a disabled plane must read as distance 0.
public void putEffectivePlanes(int enabledMask, FloatBuffer buf) {
for (int i = 0; i < eyePlanes.length; i++) {
if ((enabledMask & (1 << i)) != 0) {
final float[] p = eyePlanes[i];
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
} else {
buf.put(0f).put(0f).put(0f).put(0f);
}
}
}
public void getEyePlane(int index, DoubleBuffer buf) {
@@ -0,0 +1,92 @@
package com.gtnewhorizons.angelica.glsm.texture;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
/** Emulates GL proxy textures for backends that lack them. */
public final class ProxyTexture {
private final int maxTextureSize;
private final int max3DTextureSize;
private final int maxCubeMapTextureSize;
private final int maxArrayTextureLayers;
private int target;
private int width;
private int height;
private int depth;
private int internalFormat;
public ProxyTexture(int maxTextureSize, int max3DTextureSize, int maxCubeMapTextureSize, int maxArrayTextureLayers) {
this.maxTextureSize = maxTextureSize;
this.max3DTextureSize = max3DTextureSize;
this.maxCubeMapTextureSize = maxCubeMapTextureSize;
this.maxArrayTextureLayers = maxArrayTextureLayers;
}
public void record(int target, int level, int internalFormat, int width, int height, int depth, boolean formatSupported) {
final boolean layersInHeight = layersInHeight(target);
final boolean layersInDepth = layersInDepth(target);
final int dimLimit = dimensionLimit(target);
final int w = levelZeroEquivalentSize(width, level);
final int h = layersInHeight ? height : levelZeroEquivalentSize(height, level);
final int d = layersInDepth ? depth : levelZeroEquivalentSize(depth, level);
final int hLimit = layersInHeight ? maxArrayTextureLayers : dimLimit;
final int dLimit = layersInDepth ? maxArrayTextureLayers : dimLimit;
final boolean fits = w >= 1 && w <= dimLimit && h >= 1 && h <= hLimit && d >= 1 && d <= dLimit && formatSupported;
if (fits) {
accept(target, w, h, d, internalFormat);
} else {
reject(target);
}
}
public void reject(int target) {
accept(target, 0, 0, 0, 0);
}
private void accept(int target, int width, int height, int depth, int internalFormat) {
this.target = target;
this.width = width;
this.height = height;
this.depth = depth;
this.internalFormat = internalFormat;
}
public int levelParameter(int target, int level, int pname) {
if (this.target != target) return 0;
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(width, level);
case GL11.GL_TEXTURE_HEIGHT -> layersInHeight(target) ? height : TextureInfo.mipLevelSize(height, level);
case GL12.GL_TEXTURE_DEPTH -> layersInDepth(target) ? depth : TextureInfo.mipLevelSize(depth, level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> internalFormat;
default -> 0;
};
}
public int dimensionLimit(int target) {
return switch (target) {
case GL12.GL_PROXY_TEXTURE_3D -> max3DTextureSize;
case GL13.GL_PROXY_TEXTURE_CUBE_MAP, GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> maxCubeMapTextureSize;
default -> maxTextureSize;
};
}
public static int levelZeroEquivalentSize(int size, int level) {
if (size <= 0 || level < 0 || level >= Integer.SIZE - 1) return 0;
final long scaled = (long) size << level;
return scaled > Integer.MAX_VALUE ? 0 : (int) scaled;
}
private static boolean layersInHeight(int target) {
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY;
}
private static boolean layersInDepth(int target) {
return target == GL30.GL_PROXY_TEXTURE_2D_ARRAY || target == GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY || target == GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY;
}
}
@@ -15,6 +15,12 @@ public class TextureInfo {
private static final int UNSET_INT = Integer.MIN_VALUE;
private static final float UNSET_FLOAT = Float.NEGATIVE_INFINITY;
public static int mipLevelSize(int base, int level) {
if (base <= 0 || level < 0) return 0;
if (level >= Integer.SIZE - 1) return 1;
return Math.max(1, base >> level);
}
@Getter protected final int id;
@Getter @Setter protected int target = GL11.GL_TEXTURE_2D;
protected int internalFormat = -1;
@@ -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);
}
}
}
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.PointerBuffer;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.shaderc.Shaderc;
@@ -55,6 +56,30 @@ class FFPUniformBlockSpirvLayoutTest {
MemoryUtil.memFree(result.spirv());
}
@ParameterizedTest(name = "emulated={0}")
@ValueSource(booleans = {false, true})
void clipDistanceCapabilityFollowsEmulation(boolean emulated) {
long vbits = bit("BIT_CLIP_PLANES");
if (emulated) vbits |= bit("BIT_CLIP_EMULATED");
final String vs = VertexShaderGenerator.generate(VertexKey.fromPacked(vbits));
assertEquals(!emulated, declaresClipDistance(vs, Shaderc.shaderc_vertex_shader), "vertex ClipDistance capability");
}
private static boolean declaresClipDistance(String glsl, int shaderKind) {
final SpirvCompiler.Result result = SpirvCompiler.compile(glsl, shaderKind, "clip-emulation-test", SpirvCompiler.Options.vulkanForced460Core());
assertNotNull(result.spirv(), () -> "SPIR-V compile failed: " + result.error() + "\n" + glsl);
try {
final IntBuffer words = result.spirv().asIntBuffer();
for (int i = 5; i < words.limit(); i += Math.max(1, words.get(i) >>> 16)) {
final int word = words.get(i);
if ((word & 0xFFFF) == 17 && words.get(i + 1) == 32) return true;
}
return false;
} finally {
MemoryUtil.memFree(result.spirv());
}
}
private static VertexKey cubeKey(boolean lightmapUnit, boolean unit0TextureMatrix, boolean texGen) {
long bits = bit("BIT_HAS_VERTEX_COLOR") | bit("BIT_HAS_VERTEX_NORMAL") | bit("BIT_HAS_VERTEX_TEX") | bit("BIT_UNIT_TEX_BASE");
if (lightmapUnit) bits |= 1L << (position("BIT_UNIT_TEX_BASE") + 1);
@@ -27,10 +27,12 @@ import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.stream.Stream;
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.COLOR_BIT;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.floats;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -82,6 +84,7 @@ class FFPVertexLightingGLTest {
GLSMEnable.glDisable(GL11.GL_LIGHT1);
GLSMEnable.glDisable(GL11.GL_LIGHT2);
GLSMEnable.glDisable(GL11.GL_COLOR_MATERIAL);
GLSMCurrentAttribs.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
GLSMVertexInput.glBindVertexArray(0);
@@ -126,7 +129,7 @@ class FFPVertexLightingGLTest {
+ " light1=" + GLSMLighting.getLightStates()[1].isEnabled();
}
private void buildTriangle() {
private void buildTriangle(boolean withColor) {
vao = GLSMVertexInput.glGenVertexArrays();
GLSMVertexInput.glBindVertexArray(vao);
@@ -141,9 +144,13 @@ class FFPVertexLightingGLTest {
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
GLSMVertexInput.glEnableVertexAttribArray(POSITION_LOC);
GLSMVertexInput.glVertexAttribPointer(POSITION_LOC, 3, GL11.GL_FLOAT, false, STRIDE, 0L);
GLSMVertexInput.glEnableVertexAttribArray(COLOR_LOC);
GLSMVertexInput.glVertexAttribPointer(COLOR_LOC, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 12L);
VAOManager.setCurrentVertexFlags(COLOR_BIT);
if (withColor) {
GLSMVertexInput.glEnableVertexAttribArray(COLOR_LOC);
GLSMVertexInput.glVertexAttribPointer(COLOR_LOC, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 12L);
VAOManager.setCurrentVertexFlags(COLOR_BIT);
} else {
VAOManager.setCurrentVertexFlags(0);
}
}
private static void putVertex(ByteBuffer buf, float x, float y) {
@@ -154,7 +161,11 @@ class FFPVertexLightingGLTest {
}
private float[] drawAndReadCenter() {
buildTriangle();
return drawAndReadCenter(true);
}
private float[] drawAndReadCenter(boolean withColor) {
buildTriangle(withColor);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
@@ -233,6 +244,64 @@ class FFPVertexLightingGLTest {
state.run();
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), reason);
assertEquals(IDENTITY_FACTOR, factor, "a rejected solve must leave an identity factor");
assertBakeRejected(reason);
}
private static void assertBakeRejected(String reason) {
final int[] raw = new int[8];
Arrays.fill(raw, 0x5A3C1E0F);
final int[] before = raw.clone();
assertFalse(FFPVertexLighting.bakeColors(raw, 1, true, true), reason + ": bake must be rejected");
assertArrayEquals(before, raw, reason + ": a rejected bake must leave the buffer untouched");
}
@Test
void positionalLightIsNotBaked() {
GLSMLighting.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.0f, 0.0f, 1.0f, 1.0f));
assertBakeRejected("a positional light has a per-vertex direction");
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), "text keeps the object-origin approximation for positional lights");
}
static Stream<Arguments> bakeCases() {
return Stream.of(
Arguments.of("flattened face-on", -1.0e-4f, 0.0f, 0.0f, 1.0f, true, true),
Arguments.of("flattened top", -1.0e-4f, 0.0f, 1.0f, 0.0f, true, true),
Arguments.of("flattened side", -1.0e-4f, 1.0f, 0.0f, 0.0f, true, true),
Arguments.of("unflattened tilted", 1.0f, 0.3f, 0.8f, 0.5f, true, true),
Arguments.of("current color", -1.0e-4f, 0.0f, 1.0f, 0.0f, true, false),
Arguments.of("current normal", -1.0e-4f, 0.0f, 1.0f, 0.0f, false, true));
}
private static int packNormal(float x, float y, float z) {
return ((byte) (x * 127.0f) & 0xFF) | (((byte) (y * 127.0f) & 0xFF) << 8) | (((byte) (z * 127.0f) & 0xFF) << 16);
}
private static float unpackNormal(int packed, int shift) {
return (byte) (packed >> shift) / 127.0f;
}
@ParameterizedTest(name = "{0}")
@MethodSource("bakeCases")
void bakedColorMatchesGeneratedShader(String name, float zScale, float nx, float ny, float nz, boolean hasNormals, boolean hasColor) {
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
GLSMMatrices.glLoadIdentity();
GLSMMatrices.glScalef(1.0f, 1.0f, zScale);
standardItemLighting();
if (!hasColor) GLSMCurrentAttribs.glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
final int packedNormal = packNormal(nx, ny, nz);
final int[] raw = new int[8];
raw[5] = VERTEX_COLOR;
if (hasNormals) raw[6] = packedNormal;
GLSMCurrentAttribs.glNormal3f(unpackNormal(packedNormal, 0), unpackNormal(packedNormal, 8), unpackNormal(packedNormal, 16));
assertTrue(FFPVertexLighting.bakeColors(raw, 1, hasNormals, hasColor), name + ": expected a bakeable state, " + gateState());
final float[] actual = drawAndReadCenter(hasColor);
for (int c = 0; c < 3; c++) {
final int baked = (raw[5] >>> (c * 8)) & 0xFF;
final int shader = Math.round(actual[c] * 255.0f);
assertEquals(baked, shader, name + ": channel " + c + " baked=" + baked + " shader=" + shader);
}
}
@Test
@@ -3,21 +3,21 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
import com.gtnewhorizons.angelica.glsm.GLSMDepthStencilAlpha;
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
import com.gtnewhorizons.angelica.glsm.GLSMFog;
import com.gtnewhorizons.angelica.glsm.GLSMLighting;
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
import com.gtnewhorizons.angelica.glsm.GLSMTexEnvGen;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.nio.DoubleBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -32,12 +32,6 @@ class FfpUniformBlockDedupTest {
return GL30.glGetInteger(GL31.GL_UNIFORM_BUFFER_START, FFPUniformBlock.BINDING_POINT);
}
private static DoubleBuffer doubles(double... values) {
final DoubleBuffer buf = BufferUtils.createDoubleBuffer(values.length);
buf.put(values).flip();
return buf;
}
private static void resetState() {
GLSMMatrices.glMatrixMode(GL11.GL_TEXTURE);
GLSMMatrices.glLoadIdentity();
@@ -58,7 +52,8 @@ class FfpUniformBlockDedupTest {
GLSMLighting.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(1.0f, 1.0f, 1.0f, 1.0f));
GLSMLighting.glMaterialf(GL11.GL_FRONT, GL11.GL_SHININESS, 0.0f);
GLSMTexEnvGen.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(1.0f, 0.0f, 0.0f, 0.0f));
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 0.0, 0.0, 0.0));
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
ClipTestSupport.setPlane(0, 0.0, 0.0, 0.0, 0.0);
GLSMRasterizer.glLineStipple(1, (short) 0xFFFF);
GLSMRasterizer.glViewport(0, 0, 854, 480);
}
@@ -149,7 +144,10 @@ class FfpUniformBlockDedupTest {
addCategory(tests, "currentTexCoord", () -> GLSMCurrentAttribs.glTexCoord2f(0.125f, 0.375f));
addCategory(tests, "lightmapCoord", () -> GLStateManager.ctx().lastBrightnessX = 96.0f);
addCategory(tests, "texGenPlane", () -> GLSMTexEnvGen.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(0.5f, 0.0f, 0.0f, 0.25f)));
addCategory(tests, "clipPlane", () -> GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 1.0, 0.0, 7.0)));
addCategory(tests, "clipPlane", () -> {
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
ClipTestSupport.setPlane(0, 0.0, 1.0, 0.0, 7.0);
});
addCategory(tests, "alphaRef", () -> GLSMDepthStencilAlpha.glAlphaFunc(GL11.GL_GREATER, 0.35f));
addCategory(tests, "fogColor", () -> GLSMFog.glFog(GL11.GL_FOG_COLOR, floats(0.1f, 0.2f, 0.3f, 0.4f)));
addCategory(tests, "fogDensity", () -> GLSMFog.glFogf(GL11.GL_FOG_DENSITY, 0.75f));
@@ -100,6 +100,7 @@ class InstancedFFPDrawGLTest {
CubeParityFixture.disableDirectionalLight();
CubeParityFixture.deleteResources();
ParticleParityFixture.deleteResources();
WeatherParityFixture.deleteResources();
if (templateVbo != 0) { GLSMBuffers.glDeleteBuffers(templateVbo); templateVbo = 0; }
if (instanceVbo != 0) { GLSMBuffers.glDeleteBuffers(instanceVbo); instanceVbo = 0; }
if (vao != 0) { GLSMVertexInput.glDeleteVertexArrays(vao); vao = 0; }
@@ -196,6 +197,27 @@ class InstancedFFPDrawGLTest {
ParticleParityFixture.assertPixelParity(reference, instanced, PARITY_SIZE, 0xFF000000, pixel -> "0x" + Integer.toHexString(pixel));
}
@Test
void weatherColumnsMatchTessellatedReference() {
GLSMRasterizer.glViewport(0, 0, PARITY_SIZE, PARITY_SIZE);
WeatherParityFixture.setupScene();
FfpFixture.activateFfp();
final WeatherParityFixture.Spec[] specs = WeatherParityFixture.specs();
FfpFixture.clear();
WeatherParityFixture.drawReference(specs);
final int[] reference = FfpFixture.readRegion(PARITY_SIZE);
FfpFixture.clear();
WeatherParityFixture.drawInstanced(specs);
final int[] instanced = FfpFixture.readRegion(PARITY_SIZE);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "parity draws must not raise a GL error");
WeatherParityFixture.assertPixelParity(reference, instanced, PARITY_SIZE, 0xFF000000, pixel -> "0x" + Integer.toHexString(pixel));
}
@Test
void entityAttribSelectsColorPerInstance() {
GLSMRasterizer.glViewport(0, 0, INTEGER_WIDTH, INTEGER_HEIGHT);
@@ -277,6 +277,39 @@ class SpirvShaderTranslatorTest {
assertFalse(es.contains("!= 0u"), "no bool use-site should still be comparing against 0u");
}
@Test
void uniformBlock_staysABlockUnderGLES() {
final String src = """
#version 330 core
layout(std140) uniform Blk {
mat4 u_M;
vec4 u_C;
};
layout(std140) uniform Named {
vec4 u_N;
} named;
uniform float u_Loose;
struct L { vec4 c; };
uniform L u_Light;
in vec4 color;
out vec4 fragColor;
void main() {
fragColor = u_M * color * u_C * u_Loose + u_Light.c + named.u_N;
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "uniform_block.frag");
dumpOnFailure("uniform_block.frag", src, es);
assertNotNull(es);
assertTrue(Pattern.compile("layout\\s*\\(\\s*std140\\s*\\)\\s*uniform\\s+Blk\\s*\\{").matcher(es).find(), "a source uniform block must reach GLSL ES as a block, so glUniformBlockBinding finds it by name\n\n" + es);
assertFalse(Pattern.compile("uniform\\s+Blk\\s+_").matcher(es).find(), "the block must not be left as a plain struct uniform\n\n" + es);
assertFalse(Pattern.compile("_[0-9]+\\.u_M").matcher(es).find(), "an unnamed source block's members must be accessed bare\n\n" + es);
assertTrue(Pattern.compile("layout\\s*\\(\\s*std140\\s*\\)\\s*uniform\\s+Named\\s*\\{[^}]*\\}\\s*named\\s*;").matcher(es).find(), "an instance-named block keeps its instance name\n\n" + es);
assertTrue(es.contains("named.u_N"), "members of an instance-named block stay qualified\n\n" + es);
assertContainsDecl(es, "float", "u_Loose");
assertTrue(Pattern.compile("uniform\\s+L\\s+u_Light\\s*;").matcher(es).find(), "a struct uniform is not a block and must stay a plain uniform\n\n" + es);
}
@Test
void readonlyImage3D_staysAnImageUnderGLES() {
final String src = """
@@ -0,0 +1,292 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizons.angelica.glsm.GLSMBlend;
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
import com.gtnewhorizons.angelica.glsm.GLSMPrograms;
import com.gtnewhorizons.angelica.glsm.GLSMTextures;
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.function.IntFunction;
public final class WeatherParityFixture {
public record Spec(float x, float z, float bottom, float top, float uBase, float uDrift, float vBase, float vDrift,
int brightness, int scrollOffset, float scrollScale, float source,
float r, float g, float b, float a, int period, float uDriftScale, float vDriftScale, float fadeScale) {}
private static final int TICK = 2650;
private static final float PARTIAL_TICKS = 0.375f;
private static final float TRANSLATE_X = 0.37f;
private static final float TRANSLATE_Y = -62.25f;
private static final float TRANSLATE_Z = -0.61f;
private static final float INV_RADIUS = 1.0f / 6.0f;
private static final float CAMERA_FRAC_X = 0.3f;
private static final float CAMERA_FRAC_Z = 0.7f;
private static final float STRENGTH = 0.8f;
private static final int PROVIDER_TICK = 1777;
private static final float PROVIDER_INV_RADIUS = 1.0f / 9.0f;
private static final float[] CORNER_X = { 0.0f, 1.0f, 1.0f, 0.0f };
private static final float[] CORNER_Y = { 0.0f, 0.0f, 1.0f, 1.0f };
private static final int REF_STRIDE = 44;
private static int referenceVao;
private static int referenceVbo;
private static int instanceVbo;
private static int weatherTexture;
private static int lightmapTexture;
private WeatherParityFixture() {}
private static Spec rain(float x, float z, float bottom, float top, float j0, int hash, int brightness) {
return new Spec(x, z, bottom, top, 0f, 0f, 0f, 0f, brightness, hash, WeatherInstancedAttribs.RAIN_SCROLL_BASE + j0, WeatherInstancedAttribs.SOURCE_VANILLA,
1f, 1f, 1f, 1f, WeatherInstancedAttribs.RAIN_PERIOD, 0f, 0f, WeatherInstancedAttribs.RAIN_FADE);
}
private static Spec snow(float x, float z, float bottom, float top, float j0, float j1, float j2, float j3, int brightness) {
return new Spec(x, z, bottom, top, j0, j1, j2, j3, brightness, 0, 1f, WeatherInstancedAttribs.SOURCE_VANILLA,
1f, 1f, 1f, 1f, WeatherInstancedAttribs.SNOW_PERIOD, WeatherInstancedAttribs.SNOW_U_DRIFT, WeatherInstancedAttribs.SNOW_V_DRIFT,
WeatherInstancedAttribs.SNOW_FADE);
}
private static Spec provider(float x, float z, float bottom, float top, float uBase, float uDrift, float vBase, float vDrift,
int brightness, int scrollOffset, float scrollScale, float r, float g, float b, float a,
int period, float uDriftScale, float vDriftScale, float fadeScale) {
return new Spec(x, z, bottom, top, uBase, uDrift, vBase, vDrift, brightness, scrollOffset, scrollScale, WeatherInstancedAttribs.SOURCE_PROVIDER, r, g, b, a,
period, uDriftScale, vDriftScale, fadeScale);
}
public static Spec[] specs() {
return new Spec[] {
rain(-4f, -3f, 58f, 66f, 0.31f, 5, 0x00F00030),
rain(2f, -4f, 60f, 67f, 0.77f, 19, 0x00A000D0),
rain(-2f, 3f, 57f, 65f, 0.05f, 62, 0x0060F0A0),
rain(4f, 2f, 59f, 64f, 0.58f, 11, 0x00D01090),
rain(1f, 4f, 56f, 66f, 0.93f, 24, 0x0080C0F0),
snow(-5f, 1f, 58f, 66f, 0.12f, 0.47f, 0.83f, 0.29f, 0x00E02070),
snow(3f, -2f, 60f, 68f, 0.66f, 0.91f, 0.18f, 0.74f, 0x00B0B0B0),
snow(-1f, -5f, 57f, 63f, 0.39f, 0.25f, 0.52f, 0.96f, 0x0040E0C0),
snow(5f, 4f, 59f, 67f, 0.84f, 0.63f, 0.07f, 0.41f, 0x00F0F0F0),
snow(-3f, 5f, 55f, 65f, 0.22f, 0.78f, 0.35f, 0.58f, 0x00109050),
provider(-5f, -4f, 58f, 66f, 0.14f, 0.52f, 0.71f, 0.33f, 0x00C06040, 0, 1f, 1f, 1f, 1f, 1f, 512, 0.03f, 0.001f, 0.3f),
provider(4f, -3f, 59f, 67f, 0.61f, 0.27f, 0.09f, 0.88f, 0x00F020B0, 0, 1f, 1f, 1f, 1f, 1f, 512, 0.03f, 0.001f, 0.3f),
provider(-4f, 2f, 57f, 65f, 0.42f, 0.36f, 0.64f, 0.19f, 0x0090D070, 0, 0f, 0.12f, 0.27f, 0.18f, 2.0f, 512, 0.03f, 0.003f, 0f),
provider(2f, 5f, 60f, 66f, 0.83f, 0.74f, 0.25f, 0.57f, 0x0050A0E0, 0, 0f, 0.29f, 0.14f, 0.21f, 2.0f, 512, 0.03f, 0.003f, 0f),
provider(-2f, -5f, 56f, 64f, 0.07f, 0.45f, 0.93f, 0.62f, 0x00B0F040, 0, -1f, 0.83f, 0.92f, 0.88f, 1f, 1024, 0.001f, 0.001f, 0.3f),
provider(5f, 1f, 58f, 68f, 0.55f, 0.16f, 0.38f, 0.81f, 0x007070F0, 0, -1f, 0.97f, 0.86f, 0.81f, 1f, 1024, 0.001f, 0.001f, 0.3f),
provider(-5f, 4f, 59f, 66f, 0f, 0.68f, 0f, 0.23f, 0x00E0E050, 0, -1f, 1f, 1f, 1f, 0.35f, 512, 0f, 0.01f, 0.3f),
provider(3f, 3f, 57f, 67f, 0f, 0.49f, 0f, 0.91f, 0x0030C0A0, 0, -1f, 1f, 1f, 1f, 0.82f, 512, 0f, 0.01f, 0.3f),
provider(0f, -4f, 58f, 65f, 0.26f, 0.12f, 0.67f, 0.44f, 0x00A0A0A0, 21, 3.46f, 1f, 1f, 1f, 1f, 32, 0f, 0f, 0.3f),
provider(1f, 3f, 60f, 66f, 0.89f, 0.31f, 0.15f, 0.76f, 0x00F0A040, 7, 3.81f, 1f, 1f, 1f, 1f, 32, 0f, 0f, 0.3f),
provider(-3f, 0f, 56f, 67f, 0.33f, 0.85f, 0.48f, 0.13f, 0x00D0D0D0, 0, 1f, 1f, 1f, 1f, 0.24f, 512, 0.01f, 0.01f, 0.3f),
provider(0f, 5f, 58f, 64f, 0.72f, 0.04f, 0.59f, 0.97f, 0x0010F0F0, 0, 1f, 1f, 1f, 1f, 0.67f, 512, 0.01f, 0.01f, 0.3f),
};
}
private static float mod(float x, float y) {
return x - y * (float) Math.floor(x / y);
}
public static void setupScene() {
GLSMEnable.glDisable(GL11.GL_FOG);
GLSMEnable.glDisable(GL11.GL_ALPHA_TEST);
GLSMEnable.glDisable(GL11.GL_DEPTH_TEST);
GLSMEnable.glDisable(GL11.GL_CULL_FACE);
GLSMEnable.glDisable(GL11.GL_LIGHTING);
GLSMEnable.glEnable(GL11.GL_BLEND);
GLSMBlend.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLSMMatrices.glMatrixMode(GL11.GL_PROJECTION);
GLSMMatrices.glLoadIdentity();
GLSMMatrices.glOrtho(-8.0, 8.0, -8.0, 8.0, -64.0, 64.0);
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
GLSMMatrices.glLoadIdentity();
GLSMMatrices.glRotatef(30.0f, 1.0f, 0.0f, 0.0f);
GLSMMatrices.glRotatef(35.0f, 0.0f, 1.0f, 0.0f);
lightmapTexture = createTexture(GL13.GL_TEXTURE1, 16, GL11.GL_LINEAR, GL11.GL_CLAMP);
GLSMMatrices.glMatrixMode(GL11.GL_TEXTURE);
GLSMMatrices.glLoadIdentity();
GLSMMatrices.glScalef(0.00390625f, 0.00390625f, 0.00390625f);
GLSMMatrices.glTranslatef(8.0f, 8.0f, 8.0f);
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
weatherTexture = createTexture(GL13.GL_TEXTURE0, 4, GL11.GL_NEAREST, GL11.GL_REPEAT);
GLSMMatrices.glMatrixMode(GL11.GL_TEXTURE);
GLSMMatrices.glLoadIdentity();
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
}
private static int createTexture(int unit, int size, int filter, int wrap) {
final int id = GLSMTextures.glGenTextures();
GLSMTextures.glActiveTexture(unit);
GLSMTextures.glBindTexture(GL11.GL_TEXTURE_2D, id);
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter);
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filter);
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, wrap);
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, wrap);
final ByteBuffer texels = BufferUtils.createByteBuffer(size * size * 4);
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
texels.put((byte) (40 + x * (200 / size)));
texels.put((byte) (40 + y * (200 / size)));
texels.put((byte) (255 - (x + y) * (100 / size)));
texels.put((byte) 255);
}
}
texels.flip();
GLSMTextures.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, size, size, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
GLSMEnable.glEnable(GL11.GL_TEXTURE_2D);
return id;
}
private static void putVertex(ByteBuffer data, float px, float py, float pz, float r, float g, float b, float a,
float u, float v, float lmX, float lmY) {
final int base = data.position();
data.putFloat(base, px);
data.putFloat(base + 4, py);
data.putFloat(base + 8, pz);
data.putFloat(base + 12, r);
data.putFloat(base + 16, g);
data.putFloat(base + 20, b);
data.putFloat(base + 24, a);
data.putFloat(base + 28, u);
data.putFloat(base + 32, v);
data.putFloat(base + 36, lmX);
data.putFloat(base + 40, lmY);
data.position(base + REF_STRIDE);
}
private static float tessellatorByte(float channel) {
final int value = (int) (channel * 255.0F);
return Math.max(0, Math.min(255, value)) / 255.0f;
}
private static void addColumn(ByteBuffer data, Spec s) {
final boolean provider = s.source() == WeatherInstancedAttribs.SOURCE_PROVIDER;
final int tick = provider ? PROVIDER_TICK : TICK;
final float age = tick + PARTIAL_TICKS;
final float invRadius = provider ? PROVIDER_INV_RADIUS : INV_RADIUS;
final float strength = provider ? 1.0f : STRENGTH;
final float halfScale = 0.5f / (float) Math.sqrt(s.x() * s.x() + s.z() * s.z());
final float halfX = -s.z() * halfScale;
final float halfZ = s.x() * halfScale;
final float dx = s.x() + 0.5f - CAMERA_FRAC_X;
final float dz = s.z() + 0.5f - CAMERA_FRAC_Z;
final float distance = (float) Math.sqrt(dx * dx + dz * dz) * invRadius;
final float fade = 1.0f - distance * distance;
final float scroll = (mod((float) tick + s.scrollOffset(), s.period()) + PARTIAL_TICKS) / s.period() * s.scrollScale();
final float driftU = s.uBase() + age * s.uDriftScale() * s.uDrift();
final float driftV = s.vBase() + age * s.vDriftScale() * s.vDrift();
final float alpha = fade * s.fadeScale() + 0.5f;
final float r = tessellatorByte(s.r());
final float g = tessellatorByte(s.g());
final float b = tessellatorByte(s.b());
final float a = tessellatorByte(alpha * s.a() * strength);
final float lmX = s.brightness() & 0xFFFF;
final float lmY = s.brightness() >> 16 & 0xFFFF;
for (int i = 0; i < 4; i++) {
final float side = CORNER_X[i] * 2.0f - 1.0f;
final float cornerX = s.x() + 0.5f + side * halfX;
final float cornerZ = s.z() + 0.5f + side * halfZ;
final float cornerY = s.bottom() * (1.0f - CORNER_Y[i]) + s.top() * CORNER_Y[i];
putVertex(data, cornerX + TRANSLATE_X, cornerY + TRANSLATE_Y, cornerZ + TRANSLATE_Z, r, g, b, a,
CORNER_X[i] + driftU, cornerY * 0.25f + scroll + driftV, lmX, lmY);
}
}
private static ByteBuffer buildReferenceData(Spec[] specs) {
final ByteBuffer data = BufferUtils.createByteBuffer(specs.length * 4 * REF_STRIDE);
for (Spec s : specs) {
addColumn(data, s);
}
data.flip();
return data;
}
public static void drawReference(Spec[] specs) {
final ByteBuffer data = buildReferenceData(specs);
if (referenceVao == 0) referenceVao = GLSMVertexInput.glGenVertexArrays();
GLSMVertexInput.glBindVertexArray(referenceVao);
if (referenceVbo == 0) referenceVbo = GLSMBuffers.glGenBuffers();
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, referenceVbo);
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, REF_STRIDE, 0);
FfpFixture.attrib(1, 4, GL11.GL_FLOAT, false, REF_STRIDE, 12);
FfpFixture.attrib(2, 2, GL11.GL_FLOAT, false, REF_STRIDE, 28);
FfpFixture.attrib(3, 2, GL11.GL_FLOAT, false, REF_STRIDE, 36);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setCurrentVertexFlags(VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT | VertexFlags.BRIGHTNESS_BIT);
GLSMDraws.glDrawArrays(GL11.GL_QUADS, 0, specs.length * 4);
GLSMVertexInput.glBindVertexArray(0);
}
public static void drawInstanced(Spec[] specs) {
final ByteBuffer records = BufferUtils.createByteBuffer(specs.length * WeatherInstancedAttribs.STRIDE);
for (int i = 0; i < specs.length; i++) {
final Spec s = specs[i];
WeatherInstancedAttribs.putColumn(records, i, s.x(), s.z(), s.bottom(), s.top(), s.uBase(), s.uDrift(), s.vBase(), s.vDrift(),
s.brightness(), s.scrollOffset(), s.scrollScale(), s.source(), s.r(), s.g(), s.b(), s.a(),
s.period(), s.uDriftScale(), s.vDriftScale(), s.fadeScale());
}
WeatherParams.set(TRANSLATE_X, TRANSLATE_Y, TRANSLATE_Z, INV_RADIUS, CAMERA_FRAC_X, CAMERA_FRAC_Z,
PARTIAL_TICKS, TICK + PARTIAL_TICKS, TICK, STRENGTH, PROVIDER_TICK, PROVIDER_TICK + PARTIAL_TICKS, PROVIDER_INV_RADIUS);
GLSMVertexInput.glBindVertexArray(WeatherQuadMesh.vao());
if (instanceVbo == 0) instanceVbo = GLSMBuffers.glGenBuffers();
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, records, GL15.GL_STREAM_DRAW);
WeatherInstancedAttribs.pointInstanceAttribs(0L);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setCurrentVertexFlags(WeatherQuadMesh.VERTEX_FLAGS);
GLSMPrograms.setFfpInstancing(Instancing.WEATHER);
GLSMDraws.glDrawArraysInstanced(GL11.GL_TRIANGLE_FAN, 0, WeatherQuadMesh.VERTEX_COUNT, specs.length);
GLSMPrograms.setFfpInstancing(Instancing.NONE);
GLSMVertexInput.glBindVertexArray(0);
}
public static void assertPixelParity(int[] reference, int[] instanced, int size, int background, IntFunction<String> describe) {
FfpFixture.assertPixelParity("instanced weather columns", reference, instanced, size, background, 16, 0, describe);
}
public static void deleteResources() {
if (instanceVbo != 0) {
GLSMBuffers.glDeleteBuffers(instanceVbo);
instanceVbo = 0;
}
if (referenceVbo != 0) {
GLSMBuffers.glDeleteBuffers(referenceVbo);
referenceVbo = 0;
}
if (referenceVao != 0) {
GLSMVertexInput.glDeleteVertexArrays(referenceVao);
referenceVao = 0;
}
if (weatherTexture != 0) {
GLSMTextures.glDeleteTextures(weatherTexture);
weatherTexture = 0;
}
if (lightmapTexture != 0) {
GLSMTextures.glDeleteTextures(lightmapTexture);
lightmapTexture = 0;
}
WeatherQuadMesh.delete();
}
}
@@ -0,0 +1,37 @@
package com.gtnewhorizons.angelica.glsm.testutil;
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.DoubleBuffer;
public final class ClipTestSupport {
private ClipTestSupport() {}
public static void setPlane(int index, double a, double b, double c, double d) {
final DoubleBuffer eq = BufferUtils.createDoubleBuffer(4);
eq.put(a).put(b).put(c).put(d).flip();
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0 + index, eq);
}
public static Emulation emulation(boolean emulated) {
return new Emulation(emulated);
}
public static final class Emulation implements AutoCloseable {
private final boolean previous;
private Emulation(boolean emulated) {
previous = Reflect.getStatic(RenderSystem.class, "clipDistanceEmulated");
Reflect.setStatic(RenderSystem.class, "clipDistanceEmulated", emulated);
}
@Override
public void close() {
Reflect.setStatic(RenderSystem.class, "clipDistanceEmulated", previous);
}
}
}
+1
View File
@@ -36,6 +36,7 @@ tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configur
for (name in forwardedRunProperties) {
jvmArgs("-D$name=${System.getProperty(name)}")
}
jvmArgs("-Dangelica.buildVersion=${project.version}")
}
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
+76
View File
@@ -1,3 +1,5 @@
import java.time.Duration
plugins {
`java-library`
`maven-publish`
@@ -75,6 +77,80 @@ dependencies {
compileOnly(libs.annotations)
}
val glesTag = "gles"
val glsmRedirectAgent: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
val glsmLwjgl2Stubs: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
glsmRedirectAgent(project(":glsm", "redirectAgent"))
glsmLwjgl2Stubs(project(":glsm", "lwjgl2Stubs"))
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testImplementation(testFixtures(project(":glsm")))
testImplementation(project(":glsm")) {
capabilities { requireCapability("${project.group}:glsm-stubs") }
}
listOf(libs.lwjgl3.sdl, libs.lwjgl3.shaderc, libs.lwjgl3.spvc).forEach {
testImplementation(it)
testRuntimeOnly(it) { artifact { classifier = lwjglNatives } }
}
testRuntimeOnly(libs.junit.platform.launcher)
testRuntimeOnly(libs.log4j.core)
testRuntimeOnly(libs.fastutil)
testRuntimeOnly(libs.joml)
testRuntimeOnly(libs.commons.lang3)
testRuntimeOnly(libs.guava)
testRuntimeOnly(libs.celeritas.common) { isTransitive = false }
testRuntimeOnly(libs.asm.tree.test)
}
tasks.test {
useJUnitPlatform {
excludeTags(glesTag)
}
}
val unpackLwjgl3Natives = registerLwjgl3NativesUnpack()
val glesTest by tasks.registering(Test::class) {
description = "Boots GLSM on the LWJGL3 GL backend in a real OpenGL ES 3.2 context."
group = "verification"
testClassesDirs = sourceSets["test"].output.classesDirs
classpath = glsmLwjgl2Stubs.plus(
sourceSets["test"].runtimeClasspath.filter { file ->
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
}
)
useJUnitPlatform {
includeTags(glesTag)
}
systemProperty("glsm.stubs.capabilities", "lwjgl3")
javaAgent(glsmRedirectAgent)
loadLwjgl3NativesFrom(unpackLwjgl3Natives)
outputs.cacheIf { false }
timeout = Duration.ofMinutes(5)
systemProperty("junit.jupiter.execution.timeout.default", "60s")
systemProperty("junit.jupiter.execution.timeout.threaddump.enabled", "true")
testLogging {
events("passed", "skipped", "failed")
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
mustRunAfter(tasks.test)
}
if (!providers.environmentVariable("CI").isPresent) {
tasks.check { dependsOn(glesTest) }
}
tasks.named<JavaCompile>("compileJava") {
val classesDir = destinationDirectory
doLast { injectLwjgl3Aware(classesDir.get().asFile) }
@@ -10,6 +10,9 @@ import com.gtnewhorizons.angelica.glsm.GLScreenCapture;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.jetbrains.annotations.Nullable;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
import org.lwjgl.PointerBuffer;
@@ -48,6 +51,7 @@ import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDLEvents;
import org.lwjgl.sdl.SDLTimer;
import org.lwjgl.sdl.SDLVideo;
import org.lwjgl.system.FunctionProvider;
import org.lwjgl.system.JNI;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
@@ -97,14 +101,45 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|| normalizedVendor.startsWith("advanced micro devices");
}
}
final FunctionProvider functions = GL.getFunctionProvider();
if (RenderSystem.isGLES()) {
pfnClearDepthf = GL.getFunctionProvider().getFunctionAddress("glClearDepthf");
pfnDepthRangef = GL.getFunctionProvider().getFunctionAddress("glDepthRangef");
pfnClearDepthf = functions.getFunctionAddress("glClearDepthf");
pfnDepthRangef = functions.getFunctionAddress("glDepthRangef");
proxyTexture = new ProxyTexture(GL11C.glGetInteger(GL11C.GL_MAX_TEXTURE_SIZE), GL11C.glGetInteger(GL12C.GL_MAX_3D_TEXTURE_SIZE),
GL11C.glGetInteger(GL13C.GL_MAX_CUBE_MAP_TEXTURE_SIZE), GL11C.glGetInteger(GL30C.GL_MAX_ARRAY_TEXTURE_LAYERS));
}
pfnGenSamplers = requireFunction(functions, "glGenSamplers");
pfnDeleteSamplers = requireFunction(functions, "glDeleteSamplers");
pfnBindSampler = requireFunction(functions, "glBindSampler");
pfnSamplerParameteri = requireFunction(functions, "glSamplerParameteri");
pfnSamplerParameterf = requireFunction(functions, "glSamplerParameterf");
pfnIsSampler = requireFunction(functions, "glIsSampler");
pfnVertexAttribDivisor = requireFunction(functions, "glVertexAttribDivisor");
}
private static long requireFunction(FunctionProvider functions, String name) {
final long address = functions.getFunctionAddress(name);
if (address == 0L) throw new IllegalStateException(name + " is not exported by the GL driver");
return address;
}
private long pfnClearDepthf;
private long pfnDepthRangef;
private @Nullable ProxyTexture proxyTexture;
// GL33C slots stay unloaded on an ES 3.2 context: LWJGL builds desktop caps only up to the reported 3.2.
private long pfnGenSamplers;
private long pfnDeleteSamplers;
private long pfnBindSampler;
private long pfnSamplerParameteri;
private long pfnSamplerParameterf;
private long pfnIsSampler;
private long pfnVertexAttribDivisor;
private boolean recordProxy(int target, int level, int internalFormat, int width, int height, int depth) {
if (proxyTexture == null || !TextureInfoCache.isProxyTarget(target)) return false;
proxyTexture.record(target, level, internalFormat, width, height, depth, true);
return true;
}
@Override
public void shutdown() {
@@ -253,7 +288,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
public void finish() {GL11C.glFinish();}
private static boolean capAllowed(int cap) {
return GLESCaps.isCapAllowed(cap, RenderSystem.isGLES(), RenderSystem.hasClipCullDistance());
return GLESCaps.isCapAllowed(cap, RenderSystem.isGLES());
}
@Override
@@ -565,26 +600,31 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
}
@Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, DoubleBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
}
@Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, FloatBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
}
@Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
}
@Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixelBufferOffset) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixelBufferOffset);
}
@@ -605,16 +645,19 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void texImage1D(int target, int level, int internalFormat, int width, int border, int format, int type, ByteBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, 1, 1)) return;
GL11C.glTexImage1D(target, level, internalFormat, width, border, format, type, pixels);
}
@Override
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, depth)) return;
GL12C.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels);
}
@Override
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, depth)) return;
GL12C.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels);
}
@@ -686,6 +729,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public int getTexLevelParameteri(int target, int level, int pname) {
if (proxyTexture != null && TextureInfoCache.isProxyTarget(target)) return proxyTexture.levelParameter(target, level, pname);
return GL11C.glGetTexLevelParameteri(target, level, pname);
}
@@ -706,27 +750,33 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public int genSamplers() {
return GL33C.glGenSamplers();
try (MemoryStack stack = MemoryStack.stackPush()) {
final IntBuffer ids = stack.mallocInt(1);
JNI.callPV(1, MemoryUtil.memAddress(ids), pfnGenSamplers);
return ids.get(0);
}
}
@Override
public void deleteSamplers(int sampler) {
GL33C.glDeleteSamplers(sampler);
try (MemoryStack stack = MemoryStack.stackPush()) {
JNI.callPV(1, MemoryUtil.memAddress(stack.ints(sampler)), pfnDeleteSamplers);
}
}
@Override
public void bindSampler(int unit, int sampler) {
GL33C.glBindSampler(unit, sampler);
JNI.callV(unit, sampler, pfnBindSampler);
}
@Override
public void samplerParameteri(int sampler, int pname, int param) {
GL33C.glSamplerParameteri(sampler, pname, param);
JNI.callV(sampler, pname, param, pfnSamplerParameteri);
}
@Override
public void samplerParameterf(int sampler, int pname, float param) {
GL33C.glSamplerParameterf(sampler, pname, param);
JNI.callV(sampler, pname, param, pfnSamplerParameterf);
}
@Override
@@ -1371,7 +1421,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override public boolean isFramebuffer(int framebuffer) { return GL30C.glIsFramebuffer(framebuffer); }
@Override public boolean isRenderbuffer(int renderbuffer) { return GL30C.glIsRenderbuffer(renderbuffer); }
@Override public boolean isSampler(int sampler) { return GL33C.glIsSampler(sampler); }
@Override public boolean isSampler(int sampler) { return JNI.callZ(sampler, pfnIsSampler); }
@Override public boolean isQuery(int query) { return GL15C.glIsQuery(query); }
@Override
@@ -1441,7 +1491,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void vertexAttribDivisor(int index, int divisor) {
GL33C.glVertexAttribDivisor(index, divisor);
JNI.callV(index, divisor, pfnVertexAttribDivisor);
}
@Override
@@ -0,0 +1,234 @@
package com.gtnewhorizons.angelica.lwjgl3;
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
import com.gtnewhorizons.angelica.glsm.GLSMBlend;
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
import com.gtnewhorizons.angelica.glsm.GLSMFramebuffer;
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
import com.gtnewhorizons.angelica.glsm.GLSMTextures;
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.opengl.GL12C;
import org.lwjgl.opengl.GL20C;
import org.lwjgl.opengl.GL30C;
import org.lwjgl.opengl.GL33C;
import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDLInit;
import org.lwjgl.sdl.SDLVideo;
import org.lwjgl.system.Configuration;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("gles")
@DisabledOnOs(OS.MAC)
class GlesSmokeTest {
private static final int SIZE = 64;
private static int fbo;
@BeforeAll
static void boot() {
Configuration.OPENGL_EXPLICIT_INIT.set(true);
assertTrue(SDLInit.SDL_Init(SDLInit.SDL_INIT_VIDEO), () -> "SDL_Init failed: " + SDLError.SDL_GetError());
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_PROFILE_MASK, SDLVideo.SDL_GL_CONTEXT_PROFILE_ES);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_DEPTH_SIZE, 24);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_STENCIL_SIZE, 8);
final long window = SDLVideo.SDL_CreateWindow("glesSmoke", SIZE, SIZE, SDLVideo.SDL_WINDOW_OPENGL | SDLVideo.SDL_WINDOW_HIDDEN);
assertNotEquals(0L, window, () -> "SDL_CreateWindow failed: " + SDLError.SDL_GetError());
final long context = SDLVideo.SDL_GL_CreateContext(window);
assertNotEquals(0L, context, () -> "SDL_GL_CreateContext failed: " + SDLError.SDL_GetError());
assertTrue(SDLVideo.SDL_GL_MakeCurrent(window, context), () -> "SDL_GL_MakeCurrent failed: " + SDLError.SDL_GetError());
GL.create(SDLVideo::SDL_GL_GetProcAddress);
GL.createCapabilities();
// LWJGL's GL_CONTEXT_PROFILE_MASK probe in createCapabilities leaves INVALID_ENUM on an ES context.
while (GL11C.glGetError() != GL11C.GL_NO_ERROR) {}
final String version = GL11C.glGetString(GL11C.GL_VERSION);
assertTrue(version != null && version.startsWith("OpenGL ES 3."), () -> "not an ES 3.x context: " + version);
assertTrue(BackendManager.RENDER_BACKEND instanceof Lwjgl3GLRenderBackend, () -> "selected backend is " + BackendManager.RENDER_BACKEND.getName());
GLStateManager.initialize(GLSMInitConfig.builder().displaySize(SIZE, SIZE).build());
GLStateManager.setRunningSplash(false);
GLStateManager.markSplashComplete("glesSmoke");
assertTrue(RenderSystem.isGLES());
assertTrue(RenderSystem.supportsSamplerObjects());
fbo = createTarget();
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void samplerLifecycle() {
final int sampler = RenderSystem.genSampler();
assertNotEquals(0, sampler);
RenderSystem.samplerParameteri(sampler, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_LINEAR);
RenderSystem.samplerParameterf(sampler, GL12C.GL_TEXTURE_MAX_LOD, 4.0f);
RenderSystem.bindSamplerToUnit(0, sampler);
assertEquals(sampler, GL11C.glGetInteger(GL33C.GL_SAMPLER_BINDING));
assertTrue(BackendManager.RENDER_BACKEND.isSampler(sampler));
RenderSystem.destroySampler(sampler);
assertFalse(BackendManager.RENDER_BACKEND.isSampler(sampler));
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void vertexAttribDivisor() {
final int vao = GLSMVertexInput.glGenVertexArrays();
GLSMVertexInput.glBindVertexArray(vao);
GLSMVertexInput.glVertexAttribDivisor(5, 1);
assertEquals(1, GL20C.glGetVertexAttribi(5, GL33C.GL_VERTEX_ATTRIB_ARRAY_DIVISOR));
GLSMVertexInput.glBindVertexArray(0);
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void ffpQuadRenders() {
bindClearedTarget();
GLSMDraws.glBegin(GL11C.GL_QUADS);
GLSMCurrentAttribs.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
GLSMDraws.glVertex3f(-1.0f, -1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, -1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, 1.0f, 0.0f);
GLSMDraws.glVertex3f(-1.0f, 1.0f, 0.0f);
GLSMDraws.glEnd();
assertEquals(0xFF00FF00, readCenterPixel());
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void ffpTexturedQuadRenders() {
final int texel = 0xFF2060A0;
final int texture = GLSMTextures.glGenTextures();
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, texture);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST);
final IntBuffer pixels = MemoryUtil.memAllocInt(16);
try {
for (int i = 0; i < 16; i++) pixels.put(i, texel);
GLSMTextures.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, GL11C.GL_RGBA, 4, 4, 0, GL12C.GL_BGRA, GL12C.GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
} finally {
MemoryUtil.memFree(pixels);
}
bindClearedTarget();
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, texture);
GLSMEnable.glEnable(GL11C.GL_TEXTURE_2D);
GLSMDraws.glBegin(GL11C.GL_QUADS);
GLSMCurrentAttribs.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLSMCurrentAttribs.glTexCoord2f(0.0f, 0.0f);
GLSMDraws.glVertex3f(-1.0f, -1.0f, 0.0f);
GLSMCurrentAttribs.glTexCoord2f(1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, -1.0f, 0.0f);
GLSMCurrentAttribs.glTexCoord2f(1.0f, 1.0f);
GLSMDraws.glVertex3f(1.0f, 1.0f, 0.0f);
GLSMCurrentAttribs.glTexCoord2f(0.0f, 1.0f);
GLSMDraws.glVertex3f(-1.0f, 1.0f, 0.0f);
GLSMDraws.glEnd();
GLSMEnable.glDisable(GL11C.GL_TEXTURE_2D);
assertEquals(texel, readCenterPixel());
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@ParameterizedTest(name = "emulated={0}")
@ValueSource(booleans = {false, true})
void clipPlaneKeepsOnlyTheEnabledHalfSpace(boolean emulated) {
try (var ignored = ClipTestSupport.emulation(emulated)) {
try {
bindClearedTarget();
ClipTestSupport.setPlane(1, 0.0, 0.0, 0.0, -1.0);
ClipTestSupport.setPlane(0, 1.0, 0.0, 0.0, 0.0);
GLSMEnable.glEnable(GL30C.GL_CLIP_DISTANCE0);
GLSMDraws.glBegin(GL11C.GL_QUADS);
GLSMCurrentAttribs.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
GLSMDraws.glVertex3f(-1.0f, -1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, -1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, 1.0f, 0.0f);
GLSMDraws.glVertex3f(-1.0f, 1.0f, 0.0f);
GLSMDraws.glEnd();
assertEquals(0xFF000000, readPixel(SIZE / 4, SIZE / 2), "x < 0 must be clipped by plane 0");
assertEquals(0xFF00FF00, readPixel(3 * SIZE / 4, SIZE / 2), "x >= 0 must survive; plane 1 set but not enabled must not clip");
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
} finally {
GLSMEnable.glDisable(GL30C.GL_CLIP_DISTANCE0);
}
}
}
@Test
void proxyTextureProbeFindsTheMaxTextureSize() {
final int maxSize = Integer.highestOneBit(GL11C.glGetInteger(GL11C.GL_MAX_TEXTURE_SIZE));
int found = -1;
for (int size = maxSize * 2; size > 0 && found < 0; size >>= 1) {
GLSMTextures.glTexImage2D(GL11C.GL_PROXY_TEXTURE_2D, 0, GL11C.GL_RGBA, size, size, 0, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, (ByteBuffer) null);
if (GLSMTextures.glGetTexLevelParameteri(GL11C.GL_PROXY_TEXTURE_2D, 0, GL11C.GL_TEXTURE_WIDTH) != 0) found = size;
}
assertEquals(maxSize, found);
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
private static void bindClearedTarget() {
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, fbo);
GLSMRasterizer.glViewport(0, 0, SIZE, SIZE);
GLSMBlend.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLSMFramebuffer.glClear(GL11C.GL_COLOR_BUFFER_BIT | GL11C.GL_DEPTH_BUFFER_BIT);
}
private static int readCenterPixel() {
return readPixel(SIZE / 2, SIZE / 2);
}
private static int readPixel(int x, int y) {
final ByteBuffer pixel = MemoryUtil.memAlloc(4);
try {
GLSMFramebuffer.glReadPixels(x, y, 1, 1, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, pixel);
return (pixel.get(3) & 0xFF) << 24 | (pixel.get(0) & 0xFF) << 16 | (pixel.get(1) & 0xFF) << 8 | pixel.get(2) & 0xFF;
} finally {
MemoryUtil.memFree(pixel);
}
}
private static int createTarget() {
final int color = GLSMTextures.glGenTextures();
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, color);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST);
GLSMTextures.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, GL11C.GL_RGBA8, SIZE, SIZE, 0, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, (ByteBuffer) null);
final int depth = GLSMFramebuffer.glGenRenderbuffers();
GLSMFramebuffer.glBindRenderbuffer(GL30C.GL_RENDERBUFFER, depth);
GLSMFramebuffer.glRenderbufferStorage(GL30C.GL_RENDERBUFFER, GL30C.GL_DEPTH24_STENCIL8, SIZE, SIZE);
final int target = GLSMFramebuffer.glGenFramebuffers();
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, target);
GLSMFramebuffer.glFramebufferTexture2D(GL30C.GL_FRAMEBUFFER, GL30C.GL_COLOR_ATTACHMENT0, GL11C.GL_TEXTURE_2D, color, 0);
GLSMFramebuffer.glFramebufferRenderbuffer(GL30C.GL_FRAMEBUFFER, GL30C.GL_DEPTH_STENCIL_ATTACHMENT, GL30C.GL_RENDERBUFFER, depth);
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, 0);
return target;
}
}
+2
View File
@@ -11,6 +11,8 @@
# extra - passed through to gradle; -Dangelica.* reaches the client (e.g. -Dangelica.flyby.timeOfDay=1500).
# -Dangelica.flyby.camera=FIRST_PERSON|THIRD_PERSON_BACK|THIRD_PERSON_FRONT (default FIRST_PERSON).
# -Dangelica.flyby.dimension=<id> moves the player there first (1 = End), after world saving is off.
# -Dangelica.flyby.biome=<name> pins the origin to the 44x44-block area of that biome nearest to 0,0 (BiomeGenBase.biomeName), searched in the target dimension.
# -Dangelica.flyby.survival=true runs the flight in survival with flying and no damage, restoring the original game mode afterwards.
# -PclientJvmVendor=adoptium (macOS) picks the JVM vendor for runClient17/21/25.
#
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
+8
View File
@@ -0,0 +1,8 @@
# Clip-plane scene for -Dangelica.debug.testBlocks=true. ~ is the flight start, yaw 0 faces south (+z).
# Each angelica:test_clip_plane TESR keeps only the lower half of a gold cube (plane 0) and leaves a reject-all plane 1 disabled.
gamerule doMobSpawning false
gamerule doDaylightCycle false
time set 6000
fill ~-2 ~-1 ~4 ~2 ~0 ~4 angelica:test_clip_plane
+7 -1
View File
@@ -106,7 +106,13 @@ val glsmSdlAgent = sourceSets.create("glsmSdlAgent") {
java.srcDir("src/glsmSdlAgent/java")
}
val glsmLwjgl2Stubs: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
glsmLwjgl2Stubs(project(":glsm", "lwjgl2Stubs"))
"glsmSdlAgentCompileOnly"(project(":glsm"))
"glsmSdlAgentCompileOnly"(testFixtures(project(":glsm")))
"glsmSdlAgentCompileOnly"(libs.asm.tree.test)
@@ -148,7 +154,7 @@ val glsmSdlTest by tasks.registering(Test::class) {
description = "Runs the headless GLSM-on-SDL FFP rig in its own JVM, with the SDL GPU gate engaged."
group = "verification"
testClassesDirs = sourceSets["test"].output.classesDirs
classpath = glsmSdlStubs.output.classesDirs.plus(
classpath = glsmSdlStubs.output.classesDirs.plus(glsmLwjgl2Stubs).plus(
sourceSets["test"].runtimeClasspath.filter { file ->
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
}
@@ -1,12 +0,0 @@
package org.lwjgl.opengl;
public final class GLContext {
private static final ContextCapabilities CAPABILITIES = new ContextCapabilities();
private GLContext() {}
public static ContextCapabilities getCapabilities() {
return CAPABILITIES;
}
}
@@ -20,6 +20,7 @@ import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
import com.gtnewhorizons.angelica.sdlgpu.compute.ComputeBinder;
@@ -113,6 +114,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import org.lwjgl.util.spvc.Spvc;
import org.lwjgl.sdl.SDLGPU;
import org.lwjgl.sdl.SDL_GPUBlitInfo;
import org.lwjgl.sdl.SDLTimer;
@@ -1507,52 +1509,8 @@ public class SDLGPURenderBackend extends RenderBackend {
return TextureInfoCache.isProxyTarget(target);
}
private static boolean proxyLayersInHeight(int target) {
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY;
}
private static boolean proxyLayersInDepth(int target) {
return isProxyArrayTarget(target) && !proxyLayersInHeight(target);
}
private static boolean isProxyArrayTarget(int target) {
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY || target == GL30.GL_PROXY_TEXTURE_2D_ARRAY || target == GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY || target == GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY;
}
public static int proxyLevelSize(int base, int level) {
return PixelOps.mipLevelSize(base, level);
}
static int proxyDimensionLimit(int target) {
return switch (target) {
case GL12.GL_PROXY_TEXTURE_3D -> MAX_3D_TEXTURE_SIZE;
case GL13.GL_PROXY_TEXTURE_CUBE_MAP, GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> MAX_CUBE_MAP_TEXTURE_SIZE;
default -> MAX_TEXTURE_SIZE;
};
}
static int levelZeroEquivalentSize(int size, int level) {
if (size <= 0 || level < 0 || level >= Integer.SIZE - 1) return 0;
final long scaled = (long) size << level;
return scaled > Integer.MAX_VALUE ? 0 : (int) scaled;
}
private void recordProxyTexImage(ContextState st, int target, int level, int internalFormat, int width, int height, int depth) {
final boolean layersInHeight = proxyLayersInHeight(target);
final boolean layersInDepth = proxyLayersInDepth(target);
final int dimLimit = proxyDimensionLimit(target);
final int w = levelZeroEquivalentSize(width, level);
final int h = layersInHeight ? height : levelZeroEquivalentSize(height, level);
final int d = layersInDepth ? depth : levelZeroEquivalentSize(depth, level);
final int hLimit = layersInHeight ? MAX_ARRAY_TEXTURE_LAYERS : dimLimit;
final int dLimit = layersInDepth ? MAX_ARRAY_TEXTURE_LAYERS : dimLimit;
final boolean fits = w >= 1 && w <= dimLimit && h >= 1 && h <= hLimit && d >= 1 && d <= dLimit
&& resourceManager.isMappableTextureFormat(internalFormat);
if (fits) {
st.proxyTexture.accept(target, w, h, d, internalFormat);
} else {
st.proxyTexture.reject(target);
}
st.proxyTexture.record(target, level, internalFormat, width, height, depth, resourceManager.isMappableTextureFormat(internalFormat));
}
private static int boundTextureFor(ContextState st, int target) {
@@ -1816,23 +1774,13 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public int getTexLevelParameteri(int target, int level, int pname) {
final ContextState cs = s();
if (isProxyTarget(target)) {
final ContextState.ProxyTextureState proxy = cs.proxyTexture;
if (proxy.target != target) return 0;
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> proxyLevelSize(proxy.width, level);
case GL11.GL_TEXTURE_HEIGHT -> proxyLayersInHeight(target) ? proxy.height : proxyLevelSize(proxy.height, level);
case GL12.GL_TEXTURE_DEPTH -> proxyLayersInDepth(target) ? proxy.depth : proxyLevelSize(proxy.depth, level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> proxy.internalFormat;
default -> 0;
};
}
if (isProxyTarget(target)) return cs.proxyTexture.levelParameter(target, level, pname);
final int glId = cs.boundTextures[cs.activeTextureUnit];
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null) return 0;
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(meta.width(), level);
case GL11.GL_TEXTURE_HEIGHT -> TextureInfo.mipLevelSize(meta.height(), level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
final int stored = meta.glFormat();
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
@@ -1987,8 +1935,8 @@ public class SDLGPURenderBackend extends RenderBackend {
if (meta != null) {
fbo.colorFormats[colorIdx] = meta.sdlFormat();
if (colorIdx == 0) {
fbo.width = PixelOps.mipLevelSize(meta.width(), level);
fbo.height = PixelOps.mipLevelSize(meta.height(), level);
fbo.width = TextureInfo.mipLevelSize(meta.width(), level);
fbo.height = TextureInfo.mipLevelSize(meta.height(), level);
}
}
fbo.colorAttachmentCount = Math.max(fbo.colorAttachmentCount, colorIdx + 1);
@@ -2216,8 +2164,8 @@ public class SDLGPURenderBackend extends RenderBackend {
out.level = fbo.colorLevels[fbo.readBufferIndex];
out.sdlFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
out.glBaseFormat = meta != null ? PixelPack.glBaseFormat(meta.glFormat()) : GL11.GL_RGBA;
out.width = meta != null ? PixelOps.mipLevelSize(meta.width(), out.level) : fbo.width;
out.height = meta != null ? PixelOps.mipLevelSize(meta.height(), out.level) : fbo.height;
out.width = meta != null ? TextureInfo.mipLevelSize(meta.width(), out.level) : fbo.width;
out.height = meta != null ? TextureInfo.mipLevelSize(meta.height(), out.level) : fbo.height;
return out;
}
@@ -2283,8 +2231,8 @@ public class SDLGPURenderBackend extends RenderBackend {
final int glId = boundTextureFor(cs, target);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null || level < 0 || level >= meta.levels()) return;
final int width = PixelOps.mipLevelSize(meta.width(), level);
final int height = PixelOps.mipLevelSize(meta.height(), level);
final int width = TextureInfo.mipLevelSize(meta.width(), level);
final int height = TextureInfo.mipLevelSize(meta.height(), level);
if (width <= 0 || height <= 0) return;
checkReadbackCapacity(format, type, width, height, loadPackState(cs), capacity);
final int kind = readbackKind(format);
@@ -2600,17 +2548,23 @@ public class SDLGPURenderBackend extends RenderBackend {
if (length != null && length.remaining() > 0) length.put(0, 0);
return;
}
if (s == null) return;
final byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
final int n = Math.min(bytes.length, out.remaining());
out.put(bytes, 0, n);
final byte[] bytes = (s == null ? "" : s).getBytes(StandardCharsets.UTF_8);
final int pos = out.position();
final int max = out.remaining();
final int n = Math.max(0, Math.min(bytes.length, max - 1));
for (int i = 0; i < n; i++) out.put(pos + i, bytes[i]);
if (max > 0) out.put(pos + n, (byte) 0);
if (length != null && length.remaining() > 0) length.put(0, n);
}
private static int glStringLength(String s) {
return s == null || s.isEmpty() ? 0 : s.getBytes(StandardCharsets.UTF_8).length + 1;
}
@Override public int getShaderi(int shader, int pname) {
return switch (pname) {
case GL20.GL_COMPILE_STATUS -> shaderManager.isCompiled(shader) ? 1 : 0;
case GL20.GL_SHADER_TYPE -> shaderManager.getShaderType(shader);
case GL20.GL_INFO_LOG_LENGTH -> shaderManager.getShaderInfoLog(shader).length();
case GL20.GL_INFO_LOG_LENGTH -> glStringLength(shaderManager.getShaderInfoLog(shader));
case GL20.GL_SHADER_SOURCE_LENGTH -> glStringLength(shaderManager.getShaderSource(shader));
default -> 0;
};
}
@@ -2618,7 +2572,7 @@ public class SDLGPURenderBackend extends RenderBackend {
return switch (pname) {
case GL20.GL_LINK_STATUS -> shaderManager.isLinked(program) ? 1 : 0;
case GL20.GL_VALIDATE_STATUS -> shaderManager.isLinked(program) ? 1 : 0;
case GL20.GL_INFO_LOG_LENGTH -> shaderManager.getProgramInfoLog(program).length();
case GL20.GL_INFO_LOG_LENGTH -> glStringLength(shaderManager.getProgramInfoLog(program));
case GL20.GL_ATTACHED_SHADERS -> shaderManager.getAttachedShaderCount(program);
case GL20.GL_ACTIVE_UNIFORMS -> {
final var entries = getOrBuildUniformEntries(program);
@@ -2658,6 +2612,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final int loc = e.getValue();
int glType;
int arrayLen = 1;
boolean array = false;
if (prog.allSamplerNames.contains(name)) {
glType = GL20.GL_SAMPLER_2D;
} else {
@@ -2666,6 +2621,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (umi != null) {
glType = FormatMap.mapMemberInfoToGlType(umi);
arrayLen = Math.max(1, umi.arrayLen());
array = umi.arrayStride() > 0;
} else {
glType = GL20.GL_FLOAT_VEC4;
}
@@ -2673,7 +2629,7 @@ public class SDLGPURenderBackend extends RenderBackend {
glType = FormatMap.remapUnsignedIntToBool(glType);
}
}
result.add(new UniformEntry(name, glType, arrayLen));
result.add(new UniformEntry(array ? name + "[0]" : name, glType, arrayLen));
}
result.sort(Comparator.comparing(UniformEntry::name));
final List<UniformEntry> immutable = Collections.unmodifiableList(result);
@@ -2732,6 +2688,78 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public int getUniformLocation(int program, ByteBuffer name) {
return shaderManager.getUniformLocation(program, MemoryUtil.memASCII(name));
}
@Override public void getUniformfv(int program, int location, FloatBuffer params) {
final Readback rb = readback(program, location);
if (rb == null) return;
final int pos = params.position();
final int count = Math.min(rb.count(), params.remaining());
for (int i = 0; i < count; i++) {
final float raw = rb.raw(i);
final int bits = Float.floatToRawIntBits(raw);
params.put(pos + i, switch (rb.type()) {
case FLOAT -> raw;
case INT -> (float) bits;
case UINT -> (float) (bits & 0xFFFFFFFFL);
case BOOL -> bits != 0 ? 1f : 0f;
case OPAQUE -> rb.opaqueValue();
});
}
}
@Override public void getUniformiv(int program, int location, IntBuffer params) {
final Readback rb = readback(program, location);
if (rb == null) return;
final int pos = params.position();
final int count = Math.min(rb.count(), params.remaining());
for (int i = 0; i < count; i++) {
final float raw = rb.raw(i);
final int bits = Float.floatToRawIntBits(raw);
params.put(pos + i, switch (rb.type()) {
case FLOAT -> roundHalfAwayFromZero(raw);
case BOOL -> bits != 0 ? 1 : 0;
case INT, UINT -> bits;
case OPAQUE -> rb.opaqueValue();
});
}
}
private enum ReadType { FLOAT, INT, UINT, BOOL, OPAQUE }
private record Readback(float[] storage, int start, int count, ReadType type, int opaqueValue) {
float raw(int i) {
return storage != null && start + i < storage.length ? storage[start + i] : 0f;
}
}
private Readback readback(int program, int location) {
final ShaderManager.ProgramObject prog = shaderManager.getProgram(program);
if (prog == null || !prog.linked || location < 0 || location >= prog.uniformSlotCount) return null;
final int kind = prog.locationKind[location];
if (kind != ShaderManager.ProgramObject.LOCATION_KIND_PLAIN) {
return new Readback(null, 0, 1, ReadType.OPAQUE, opaqueUnit(prog, location, kind));
}
final ShaderManager.UniformMemberInfo info = prog.memberInfo(location);
if (info == null) return null;
final int perElement = info.vectorSize() * info.columns();
final int index = prog.arrayIndexBySlot[location];
final int base = location - index;
final float[] storage = s().uniformStaging(prog).uniformDataBySlot[base];
return new Readback(storage, index * perElement, perElement, readType(prog, base, info), 0);
}
private static ReadType readType(ShaderManager.ProgramObject prog, int base, ShaderManager.UniformMemberInfo info) {
if (info.baseType() == Spvc.SPVC_BASETYPE_FP32) return ReadType.FLOAT;
if (info.baseType() != Spvc.SPVC_BASETYPE_UINT32) return ReadType.INT;
final String name = prog.locationName[base];
return name != null && prog.boolUniforms.contains(name) ? ReadType.BOOL : ReadType.UINT;
}
private static int roundHalfAwayFromZero(float f) {
final double r = Math.floor(Math.abs((double) f) + 0.5);
return (int) (f < 0 ? -r : r);
}
private static int opaqueUnit(ShaderManager.ProgramObject prog, int location, int kind) {
final String name = prog.locationName[location];
if (name == null) return 0;
final int unit = kind == ShaderManager.ProgramObject.LOCATION_KIND_SAMPLER
? prog.samplerTextureUnits.getInt(name)
: prog.imageTextureUnits.getInt(name);
return Math.max(unit, 0);
}
@Override public void getShaderSource(int shader, IntBuffer length, ByteBuffer source) {
writeUtf8(shaderManager.getShaderSource(shader), length, source);
}
@@ -4166,8 +4194,8 @@ public class SDLGPURenderBackend extends RenderBackend {
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
if (meta == null) return 0;
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(meta.width(), level);
case GL11.GL_TEXTURE_HEIGHT -> TextureInfo.mipLevelSize(meta.height(), level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
final int stored = meta.glFormat();
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
@@ -4184,6 +4212,7 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS -> 32;
case GL20.GL_MAX_VERTEX_ATTRIBS -> 16;
case GL20.GL_MAX_DRAW_BUFFERS -> 8;
case GL30.GL_MAX_CLIP_DISTANCES -> device.hasClipDistance() ? GLStateManager.MAX_CLIP_PLANES : 0;
case GL30.GL_MAX_COLOR_ATTACHMENTS -> 8;
case GL15.GL_ARRAY_BUFFER_BINDING -> cs.boundArrayBuffer;
case GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING -> cs.currentVao.elementBuffer;
@@ -4246,10 +4275,10 @@ public class SDLGPURenderBackend extends RenderBackend {
}
static final int MAX_UNIFORM_COMPONENTS_PER_STAGE = 4096 / Float.BYTES;
static final int MAX_ARRAY_TEXTURE_LAYERS = 256;
static final int MAX_TEXTURE_SIZE = 16384;
static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
static final int MAX_3D_TEXTURE_SIZE = 2048;
public static final int MAX_ARRAY_TEXTURE_LAYERS = 256;
public static final int MAX_TEXTURE_SIZE = 16384;
public static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
public static final int MAX_3D_TEXTURE_SIZE = 2048;
/** D3D12 and Metal both cap anisotropy at 16, and every desktop Vulkan driver reports 16. */
static final float MAX_ANISOTROPY = 16.0f;
@@ -4412,9 +4441,9 @@ public class SDLGPURenderBackend extends RenderBackend {
if (texHandle == 0 || frameManager.getCommandBuffer() == 0) return;
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
if (meta == null || level >= meta.levels()) return;
final int mipW = PixelOps.mipLevelSize(meta.width(), level);
final int mipH = PixelOps.mipLevelSize(meta.height(), level);
final int mipD = PixelOps.mipLevelSize(meta.depth(), level);
final int mipW = TextureInfo.mipLevelSize(meta.width(), level);
final int mipH = TextureInfo.mipLevelSize(meta.height(), level);
final int mipD = TextureInfo.mipLevelSize(meta.depth(), level);
final int bpp = PixelOps.sdlFormatTexelBytes(meta.sdlFormat());
if (mipW <= 0 || mipH <= 0 || bpp <= 0) return;
@@ -45,6 +45,7 @@ public final class Device {
private long device;
private boolean claimed;
private boolean clipDistance = true;
private int supportedShaderFormats;
private String driverName;
private boolean fencePollEnabled;
@@ -116,6 +117,10 @@ public final class Device {
}
public boolean createDevice() {
return createDevice(!SystemProperties.EMULATE_CLIP_DISTANCES);
}
boolean createDevice(boolean requestClipDistance) {
if (device != 0) return true;
installLogCallback();
@@ -130,9 +135,9 @@ public final class Device {
}
final boolean gpuDebug = SystemProperties.LWJGL_DEBUG || SystemProperties.SDL_GPU_DEBUG;
device = createGPUDevice(gpuDebug, candidates);
device = createGPUDevice(gpuDebug, candidates, requestClipDistance);
if (device == 0 && VideoDriverRecovery.shouldRetryOnX11(SDL_GetCurrentVideoDriver(), vulkanLoaderAvailable(), System.getenv("DISPLAY"))) {
device = VideoDriverRecovery.retryUnderX11(() -> createGPUDevice(gpuDebug, candidates));
device = VideoDriverRecovery.retryUnderX11(() -> createGPUDevice(gpuDebug, candidates, requestClipDistance));
}
if (device == 0) {
logDeviceCreationFailure(REQUESTED_SHADER_FORMATS);
@@ -206,13 +211,22 @@ public final class Device {
}
}
private long createGPUDevice(boolean gpuDebug, String[] candidates) {
private long createGPUDevice(boolean gpuDebug, String[] candidates, boolean requestClipDistance) {
final boolean macos = Platform.get() == Platform.MACOSX;
for (final String driver : candidates) {
final boolean macVulkan = macos && isVulkan(driver);
if (macVulkan) useMoltenVk();
final long dev = SDL_CreateGPUDevice(REQUESTED_SHADER_FORMATS, gpuDebug, driver);
if (dev != 0) return dev;
long dev = createWithProperties(gpuDebug, driver, requestClipDistance);
boolean clip = requestClipDistance;
if (dev == 0 && clip && isVulkan(driver)) {
LOG.info("SDL GPU Vulkan device creation failed with shaderClipDistance ({}); the device may lack it, retrying without it, clip distances will be emulated", SDLError.SDL_GetError());
dev = createWithProperties(gpuDebug, driver, false);
clip = false;
}
if (dev != 0) {
clipDistance = clip;
return dev;
}
LOG.warn("Failed to create a '{}' SDL GPU device: {}", driver, SDLError.SDL_GetError());
if (macVulkan && MoltenVK.locate() == null) {
LOG.warn("Vulkan on macOS needs MoltenVK. Add {} to the mods folder or install MoltenVK.", MoltenVK.JAR_NAME);
@@ -221,6 +235,22 @@ public final class Device {
return 0;
}
private static long createWithProperties(boolean gpuDebug, String driver, boolean clipDistance) {
final int props = SDLProperties.SDL_CreateProperties();
try {
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_SPIRV) != 0);
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_DXBC) != 0);
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_DXIL) != 0);
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_MSL) != 0);
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, gpuDebug);
SDLProperties.SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, driver);
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, clipDistance);
return SDL_CreateGPUDeviceWithProperties(props);
} finally {
SDLProperties.SDL_DestroyProperties(props);
}
}
public void claimWindow(long window) {
if (device == 0) {
throw new IllegalStateException("SDL GPU device not created");
@@ -298,6 +328,7 @@ public final class Device {
driverVersion = null;
driverInfo = null;
supportedShaderFormats = 0;
clipDistance = true;
}
private void logWindowDiagnostics(long window) {
@@ -351,6 +382,10 @@ public final class Device {
return device;
}
public boolean hasClipDistance() {
return clipDistance;
}
public String getDriverName() {
return driverName;
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.sdlgpu.frame;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import com.gtnewhorizons.angelica.sdlgpu.resource.ColorSource;
@@ -10,6 +11,7 @@ import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpVariant;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.longs.Long2FloatOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
@@ -67,27 +69,8 @@ public final class ContextState {
public final PackState packState = new PackState();
public final ColorSource readColor = new ColorSource();
public static final class ProxyTextureState {
public int target;
public int width;
public int height;
public int depth;
public int internalFormat;
public void accept(int target, int width, int height, int depth, int internalFormat) {
this.target = target;
this.width = width;
this.height = height;
this.depth = depth;
this.internalFormat = internalFormat;
}
public void reject(int target) {
accept(target, 0, 0, 0, 0);
}
}
public final ProxyTextureState proxyTexture = new ProxyTextureState();
public final ProxyTexture proxyTexture = new ProxyTexture(SDLGPURenderBackend.MAX_TEXTURE_SIZE, SDLGPURenderBackend.MAX_3D_TEXTURE_SIZE,
SDLGPURenderBackend.MAX_CUBE_MAP_TEXTURE_SIZE, SDLGPURenderBackend.MAX_ARRAY_TEXTURE_LAYERS);
public static final class VAOState {
public final boolean[] attribEnabled = new boolean[MAX_VERTEX_ATTRIBS];
@@ -201,6 +184,7 @@ public final class ContextState {
private final Reference2ObjectOpenHashMap<ShaderManager.ProgramObject, UniformStaging> uniformStagingByProgram = new Reference2ObjectOpenHashMap<>();
private ShaderManager.ProgramObject stagingProg;
private UniformStaging currentUniformStaging;
public final Int2ObjectOpenHashMap<float[]> uniformArrayScratch = new Int2ObjectOpenHashMap<>();
public UniformStaging uniformStaging(ShaderManager.ProgramObject prog) {
if (prog == stagingProg) return currentUniformStaging;
@@ -1,10 +1,10 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureOps;
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpFormats;
@@ -121,13 +121,13 @@ public final class LogicOpEmulator {
if (fbo == null) return frameManager.getFbo0Width();
final int db = drawBuffer(fbo, loc);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]);
return meta != null ? PixelOps.mipLevelSize(meta.width(), fbo.colorLevels[db]) : fbo.width;
return meta != null ? TextureInfo.mipLevelSize(meta.width(), fbo.colorLevels[db]) : fbo.width;
}
private int targetHeight(FboState fbo, int loc) {
if (fbo == null) return frameManager.getFbo0Height();
final int db = drawBuffer(fbo, loc);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]);
return meta != null ? PixelOps.mipLevelSize(meta.height(), fbo.colorLevels[db]) : fbo.height;
return meta != null ? TextureInfo.mipLevelSize(meta.height(), fbo.colorLevels[db]) : fbo.height;
}
}
@@ -754,16 +754,32 @@ public final class PipelineApplier {
public float[] reuseOrAlloc(ContextState st, int location, int length) {
final ShaderManager.ProgramObject prog = st.boundProgramObj;
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return null;
if (prog.floatLimitBySlot[location] < 0) return arrayScratch(st, length);
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
if (existing != null && existing.length == length) return existing;
return new float[length];
}
private static float[] arrayScratch(ContextState st, int length) {
float[] scratch = st.uniformArrayScratch.get(length);
if (scratch == null) {
scratch = new float[length];
st.uniformArrayScratch.put(length, scratch);
}
return scratch;
}
public void putUniformFv(ContextState st, int location, FloatBuffer value) {
final ShaderManager.ProgramObject prog = st.boundProgramObj;
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return;
final int n = value.remaining();
final int pos = value.position();
if (prog.floatLimitBySlot[location] < 0) {
final float[] v = arrayScratch(st, n);
value.get(pos, v);
putUniform(st, location, v);
return;
}
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
if (existing != null && existing.length == n) {
if (contentEquals(existing, value, pos, n)) {
@@ -824,6 +840,7 @@ public final class PipelineApplier {
if (Tracy.ENABLED) frameManager.noteUniformBlockFlush(b, size);
}
private static final long ARRAY_HASH_SEED = 0x9E3779B97F4A7C15L;
private static final boolean VERIFY_PER_FRAME_BLOCK = SystemProperties.SDL_VERIFY_PER_FRAME_UNIFORM_BLOCK;
private final HashSet<String> perFrameBlockDriftWarned = new HashSet<>();
@@ -837,6 +854,12 @@ public final class PipelineApplier {
public void putUniform(ContextState st, int location, float[] data) {
final ShaderManager.ProgramObject prog = st.boundProgramObj;
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return;
final int limit = prog.floatLimitBySlot[location];
if (limit < 0) {
putArrayElement(st, prog, st.uniformStaging(prog), location - prog.arrayIndexBySlot[location], location, data);
return;
}
if (data.length > limit) return;
final UniformStaging us = st.uniformStaging(prog);
final int len = data.length;
final long valueKey;
@@ -884,6 +907,62 @@ public final class PipelineApplier {
}
return;
}
writeStages(st, prog, us, location, data);
}
private void putArrayElement(ContextState st, ShaderManager.ProgramObject prog, UniformStaging us, int base, int location, float[] data) {
final ShaderManager.UniformMemberInfo baseInfo = prog.memberInfo(base);
if (baseInfo == null) return;
final int len = prog.arrayLenBySlot[location];
final int index = prog.arrayIndexBySlot[location];
final int count = Math.min(elementCount(baseInfo, data), len - index);
if (count <= 0) return;
final int floatsPerElement = baseInfo.vectorSize() * baseInfo.columns();
if (Tracy.ENABLED) st.slotWrites++;
final int start = index * floatsPerElement;
final int floats = count * floatsPerElement;
float[] storage = us.uniformDataBySlot[base];
final boolean fresh = storage == null;
long storageHash;
if (fresh) {
storage = us.uniformDataBySlot[base] = new float[len * floatsPerElement];
storageHash = ARRAY_HASH_SEED;
} else {
storageHash = us.uniformValueHashBySlot[base];
}
boolean changed = fresh;
for (int i = 0; i < floats; i++) {
final int oldBits = Float.floatToRawIntBits(storage[start + i]);
final int newBits = Float.floatToRawIntBits(data[i]);
if (oldBits == newBits) continue;
storageHash ^= arrayHashTerm(start + i, oldBits) ^ arrayHashTerm(start + i, newBits);
storage[start + i] = data[i];
changed = true;
}
if (!changed) {
if (Tracy.ENABLED) st.slotWritesElided++;
return;
}
us.uniformValueHashBySlot[base] = storageHash;
for (int b = 0; b < ShaderManager.BLOCK_COUNT; b++) {
final ShaderManager.UniformMemberInfo info = prog.blockInfoBySlot[b][location];
if (info == null) continue;
final ContextState.UniformBlockState block = st.uniformBlocks[b];
if (prog.blockSize[b] > block.size) block.size = prog.blockSize[b];
final FloatBuffer blockFb = block.staging(prog.blockSize[b]);
final int hashSlot = block.hashSlot(prog.blockInfoBySlot[b][base].offset());
if (hashSlot >= 0) block.setHash(hashSlot, storageHash);
writeMember(st, prog, blockFb, prog.blockSize[b], info, data, location, false);
block.dirty = true;
if (VERIFY_PER_FRAME_BLOCK && b == ShaderManager.BLOCK_PER_FRAME && block.flushedThisFrame) {
notePerFrameBlockDrift(prog, base);
}
return;
}
writeStages(st, prog, us, location, data);
}
private void writeStages(ContextState st, ShaderManager.ProgramObject prog, UniformStaging us, int location, float[] data) {
final ShaderManager.UniformMemberInfo vs = prog.vsInfoBySlot[location];
if (vs != null && us.vsUniformFb != null) {
writeMember(st, prog, us.vsUniformFb, prog.vertexUboSize, vs, data, location, true);
@@ -896,6 +975,10 @@ public final class PipelineApplier {
}
}
private static long arrayHashTerm(int index, int bits) {
return bits == 0 ? 0L : Hashing.fmix64(ARRAY_HASH_SEED, Hashing.packHiLo(index, bits));
}
public void storeMatrix(ContextState st, int location, boolean transpose, FloatBuffer value, int size) {
if (location < 0) return;
final int n = value.remaining();
@@ -914,16 +997,19 @@ public final class PipelineApplier {
putUniform(st, location, out);
}
private void writeMember(ContextState st, ShaderManager.ProgramObject prog, FloatBuffer ubo, int uboSize, ShaderManager.UniformMemberInfo info, float[] data, int loc, boolean isVs) {
private static int elementCount(ShaderManager.UniformMemberInfo info, float[] data) {
final int floatsPerElement = info.vectorSize() * info.columns();
if (floatsPerElement <= 0) return;
final int srcCount = data.length / floatsPerElement;
if (srcCount <= 0) return;
final int dataBytes = srcCount * floatsPerElement * 4;
return floatsPerElement <= 0 ? 0 : data.length / floatsPerElement;
}
private void writeMember(ContextState st, ShaderManager.ProgramObject prog, FloatBuffer ubo, int uboSize, ShaderManager.UniformMemberInfo info, float[] data, int loc, boolean isVs) {
final int count = Math.min(elementCount(info, data), info.arrayLen());
if (count <= 0) return;
final int dataBytes = count * info.vectorSize() * info.columns() * 4;
final int avail = uboSize - info.offset();
if (dataBytes > avail) {
throw new IllegalStateException("Uniform overflows UBO: prog=" + st.boundProgram + " loc=" + loc + " name='" + prog.locationToName.getOrDefault(loc, "?") + "' dataBytes=" + dataBytes + " availFromOffset=" + avail + " stage=" + (isVs ? "VS" : "FS"));
}
Std140Writer.write(ubo, info, data, srcCount);
Std140Writer.write(ubo, info, data, count);
}
}
@@ -14,12 +14,6 @@ import static org.lwjgl.sdl.SDLGPU.*;
public final class PixelOps {
private PixelOps() {}
public static int mipLevelSize(int base, int level) {
if (base <= 0 || level < 0) return 0;
if (level >= Integer.SIZE - 1) return 1;
return Math.max(1, base >> level);
}
public static int defaultMipLevels(int width, int height) {
final int max = Math.max(1, Math.max(width, height));
return Math.max(1, 32 - Integer.numberOfLeadingZeros(max));
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.device.Submits;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
@@ -156,11 +157,11 @@ public final class TextureOps {
}
public static int levelWidth(ResourceManager.TextureMeta meta, int level) {
return meta != null ? PixelOps.mipLevelSize(meta.width(), level) : 0;
return meta != null ? TextureInfo.mipLevelSize(meta.width(), level) : 0;
}
public static int levelHeight(ResourceManager.TextureMeta meta, int level) {
return meta != null ? PixelOps.mipLevelSize(meta.height(), level) : 0;
return meta != null ? TextureInfo.mipLevelSize(meta.height(), level) : 0;
}
private void copyFromDefaultFramebuffer(ContextState st, int destGlId, long destTex, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
@@ -777,7 +777,7 @@ public final class ShaderManager {
else prog.fragmentUboSize = refl.uboSize();
}
for (UboMember m : refl.uboMembers()) {
final int loc = ensureLocation(prog, m.name());
final int loc = ensureLocation(prog, m.name(), m.arrayLen());
final UniformMemberInfo info = new UniformMemberInfo(
m.offset(), m.size(), m.arrayStride(), isVertex,
m.vectorSize(), m.columns(), m.baseType(), m.arrayLen()
@@ -795,7 +795,7 @@ public final class ShaderManager {
prog.blockSize[b] = Math.max(prog.blockSize[b], block.size());
prog.blockBinding[b] = block.binding();
for (UboMember m : block.members()) {
final int loc = ensureLocation(prog, m.name());
final int loc = ensureLocation(prog, m.name(), m.arrayLen());
if (prog.blockMemberInfo[b].containsKey(loc)) continue;
prog.blockMemberInfo[b].put(loc, new UniformMemberInfo(
m.offset(), m.size(), m.arrayStride(), false,
@@ -809,14 +809,34 @@ public final class ShaderManager {
}
private static int ensureLocation(ProgramObject prog, String name) {
return ensureLocation(prog, name, 1);
}
private static int ensureLocation(ProgramObject prog, String name, int arrayLen) {
final int existing = prog.nameToLocation.getInt(name);
if (existing != -1) return existing;
final int loc = prog.nextUniformLocation++;
if (existing != -1) {
final int registered = Math.max(1, prog.arrayLenByLocation.get(existing));
if (registered == arrayLen) return existing;
retireLocations(prog, existing, registered);
}
final int loc = prog.nextUniformLocation;
prog.nextUniformLocation += arrayLen;
prog.nameToLocation.put(name, loc);
prog.locationToName.put(loc, name);
for (int i = 1; i < arrayLen; i++) prog.locationToName.put(loc + i, name + "[" + i + "]");
if (arrayLen > 1) prog.arrayLenByLocation.put(loc, arrayLen);
return loc;
}
private static void retireLocations(ProgramObject prog, int base, int len) {
prog.arrayLenByLocation.remove(base);
for (int i = 0; i < len; i++) prog.locationToName.remove(base + i);
prog.locationToMemberInfo.remove(base);
prog.vertexMemberInfo.remove(base);
prog.fragmentMemberInfo.remove(base);
for (int b = 0; b < BLOCK_COUNT; b++) prog.blockMemberInfo[b].remove(base);
}
private static void applyVaryingMatch(ByteBuffer fragmentSpirv, StageReflection vs, StageReflection fs) {
if (vs.vsOutputs().isEmpty() || fs.fsInputs().isEmpty() || fragmentSpirv == null) return;
final HashMap<String, Integer> vsOutLocs = new HashMap<>(vs.vsOutputs().size() * 2);
@@ -1268,7 +1288,13 @@ public final class ShaderManager {
public int getUniformLocation(int program, String name) {
final ProgramObject prog = programObjects.get(program);
if (prog == null || !prog.linked) return -1;
return prog.nameToLocation.getInt(name);
final int exact = prog.nameToLocation.getInt(name);
if (exact != -1) return exact;
final int index = UniformNames.arrayIndex(name);
if (index < 0) return -1;
final int base = prog.nameToLocation.getInt(name.substring(0, name.lastIndexOf('[')));
if (base < 0 || base >= prog.uniformSlotCount || index >= prog.arrayLenBySlot[base]) return -1;
return base + index;
}
/**
@@ -1645,7 +1671,12 @@ public final class ShaderManager {
public record UniformMemberInfo(
int offset, int size, int arrayStride, boolean isVertex,
int vectorSize, int columns, int baseType, int arrayLen
) {}
) {
public UniformMemberInfo element(int i) {
return new UniformMemberInfo(offset + i * arrayStride, size - i * arrayStride, arrayStride, isVertex,
vectorSize, columns, baseType, arrayLen - i);
}
}
public record UboMember(
String name, int offset, int size, int arrayStride,
@@ -2069,11 +2100,24 @@ public final class ShaderManager {
return m != null ? m : blockMemberInfo[BLOCK_PER_PASS].get(loc);
}
public UniformMemberInfo memberInfo(int slot) {
for (int b = 0; b < BLOCK_COUNT; b++) {
final UniformMemberInfo info = blockInfoBySlot[b][slot];
if (info != null) return info;
}
final UniformMemberInfo vs = vsInfoBySlot[slot];
return vs != null ? vs : fsInfoBySlot[slot];
}
private static final UniformMemberInfo[] EMPTY_INFO = new UniformMemberInfo[0];
public UniformMemberInfo[] vsInfoBySlot;
public UniformMemberInfo[] fsInfoBySlot;
public int uniformSlotCount;
public final Int2IntOpenHashMap arrayLenByLocation = new Int2IntOpenHashMap();
public int[] arrayIndexBySlot = EMPTY_UNITS;
public int[] arrayLenBySlot = EMPTY_UNITS;
public int[] floatLimitBySlot = EMPTY_UNITS;
public final int[] vertexInputBaseType = new int[16];
public final int[] vertexInputVecSize = new int[16];
public final String[] vertexInputName = new String[16];
@@ -2200,24 +2244,51 @@ public final class ShaderManager {
locationKind[loc] = LOCATION_KIND_PLAIN;
}
}
arrayIndexBySlot = new int[n];
arrayLenBySlot = new int[n];
Arrays.fill(arrayLenBySlot, 1);
for (var it = arrayLenByLocation.int2IntEntrySet().fastIterator(); it.hasNext(); ) {
final var e = it.next();
final int base = e.getIntKey();
final int len = e.getIntValue();
for (int i = 0; i < len; i++) {
arrayLenBySlot[base + i] = len;
arrayIndexBySlot[base + i] = i;
}
}
for (int b = 0; b < BLOCK_COUNT; b++) {
blockInfoBySlot[b] = new UniformMemberInfo[n];
for (var it = blockMemberInfo[b].int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
final var e = it.next();
final int k = e.getIntKey();
if (k >= 0 && k < n) blockInfoBySlot[b][k] = e.getValue();
fillSlots(blockInfoBySlot[b], e.getIntKey(), e.getValue());
}
}
for (var it = vertexMemberInfo.int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
final var e = it.next();
final int k = e.getIntKey();
if (k >= 0 && k < n) vsInfoBySlot[k] = e.getValue();
fillSlots(vsInfoBySlot, e.getIntKey(), e.getValue());
}
for (var it = fragmentMemberInfo.int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
final var e = it.next();
final int k = e.getIntKey();
if (k >= 0 && k < n) fsInfoBySlot[k] = e.getValue();
fillSlots(fsInfoBySlot, e.getIntKey(), e.getValue());
}
floatLimitBySlot = new int[n];
for (int slot = 0; slot < n; slot++) {
if (arrayLenBySlot[slot] > 1) {
floatLimitBySlot[slot] = -1;
continue;
}
final UniformMemberInfo info = memberInfo(slot);
final int floats = info == null || info.arrayStride() > 0 ? 0 : info.vectorSize() * info.columns();
floatLimitBySlot[slot] = floats > 0 ? floats : Integer.MAX_VALUE;
}
}
private void fillSlots(UniformMemberInfo[] bySlot, int base, UniformMemberInfo info) {
final int n = bySlot.length;
if (base < 0 || base >= n) return;
bySlot[base] = info;
final int len = arrayLenBySlot[base];
for (int i = 1; i < len; i++) bySlot[base + i] = info.element(i);
}
}
}
@@ -0,0 +1,25 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
public final class UniformNames {
private UniformNames() {}
public static int arrayIndex(String name) {
final int len = name.length();
if (len == 0 || name.charAt(len - 1) != ']') return -1;
int i = len - 1;
while (i > 0 && isDigit(name.charAt(i - 1))) i--;
if (i == 0 || name.charAt(i - 1) != '[') return -1;
if (name.charAt(i) == '0' && i + 1 != len - 1) return -1;
long index = 0;
for (int c = i; c < len - 1; c++) {
index = index * 10 + (name.charAt(c) - '0');
if (index > Integer.MAX_VALUE) return Integer.MAX_VALUE;
}
return (int) index;
}
private static boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -47,11 +48,11 @@ class ProxyTextureLimitsTest {
assertEquals(2048, SDLGPURenderBackend.deviceLimit(GL12.GL_MAX_3D_TEXTURE_SIZE));
assertEquals(16384, SDLGPURenderBackend.deviceLimit(GL13.GL_MAX_CUBE_MAP_TEXTURE_SIZE));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_2D));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_1D));
assertEquals(2048, SDLGPURenderBackend.proxyDimensionLimit(GL12.GL_PROXY_TEXTURE_3D));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY));
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL11.GL_PROXY_TEXTURE_2D));
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL11.GL_PROXY_TEXTURE_1D));
assertEquals(2048, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL12.GL_PROXY_TEXTURE_3D));
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP));
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY));
}
@Test
@@ -99,13 +100,13 @@ class ProxyTextureLimitsTest {
@Test
void levelZeroEquivalentSizeRejectsOverflowAndNonsense() {
assertEquals(4, SDLGPURenderBackend.levelZeroEquivalentSize(4, 0));
assertEquals(32, SDLGPURenderBackend.levelZeroEquivalentSize(4, 3));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, -1));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(0, 2));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 31));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 30));
assertEquals(1 << 30, SDLGPURenderBackend.levelZeroEquivalentSize(1, 30));
assertEquals(4, ProxyTexture.levelZeroEquivalentSize(4, 0));
assertEquals(32, ProxyTexture.levelZeroEquivalentSize(4, 3));
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, -1));
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(0, 2));
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, 31));
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, 30));
assertEquals(1 << 30, ProxyTexture.levelZeroEquivalentSize(1, 30));
}
@Test
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
@@ -81,7 +82,7 @@ class ProxyTextureTargetTest {
@ParameterizedTest(name = "{0}")
@MethodSource("levelSizeCases")
void levelSizeMatchesExpectation(String label, int base, int level, int expected) {
assertEquals(expected, SDLGPURenderBackend.proxyLevelSize(base, level), label);
assertEquals(expected, TextureInfo.mipLevelSize(base, level), label);
}
@Test
@@ -2,11 +2,13 @@ package com.gtnewhorizons.angelica.sdlgpu.device;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -46,4 +48,12 @@ class DeviceLifecycleGpuTest {
device.destroyDevice();
assertEquals(0L, device.getDevice());
}
@Test
void createsVulkanDeviceWithoutClipDistance() {
device = new Device();
assertTrue(device.createDevice(false), "no SDL GPU device on this machine");
Assumptions.assumeTrue(device.isVulkan(), "test needs the vulkan driver, got " + device.getDriverName());
assertFalse(device.hasClipDistance());
}
}
@@ -10,6 +10,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.CubeParityFixture;
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.ffp.ParticleParityFixture;
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
@@ -127,6 +128,30 @@ class GlsmSdlFfpTest {
assertTexelEverywhere("two units, glCallLists");
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void disabledClipPlaneDoesNotClip(boolean emulated) {
try (var ignored = ClipTestSupport.emulation(emulated)) {
bindBaseTexture();
try {
ClipTestSupport.setPlane(1, 0.0, 0.0, 0.0, -1.0);
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE1);
ClipTestSupport.setPlane(0, 1.0, 0.0, 0.0, 0.0);
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
quadUnit0Only();
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
final int left = pixelAt(pixels, SIZE, SIZE / 4, SIZE / 2);
final int right = pixelAt(pixels, SIZE, 3 * SIZE / 4, SIZE / 2);
assertTrue(left != TEXEL, () -> "left quarter must be clipped by enabled plane 0: " + describe(left));
assertEquals(TEXEL, right, () -> "right quarter must survive; disabled plane 1 must not clip: " + describe(right));
} finally {
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE1);
}
}
}
@Test
void litTexturedQuadIsShaded() {
bindBaseTexture();
@@ -0,0 +1,246 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
import com.gtnewhorizons.angelica.glsm.GLSMPrograms;
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlUniformArrayTest {
private static final int COUNT = 8;
private static final int[][] A = {
{10, 20, 30}, {40, 50, 60}, {70, 80, 90}, {100, 110, 120},
{130, 140, 150}, {160, 170, 180}, {190, 200, 210}, {220, 230, 240}
};
private static final int[] X = {250, 5, 15};
private static final int[][] B = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {11, 12, 13}};
private int program;
private int vao;
private int arr;
private int single;
private int pick;
@BeforeEach
void setUp() {
program = GlTestSupport.program(GlsmSdlHeadlessRig.FULLSCREEN_VERTEX, """
#version 330 core
uniform vec4 arr[8];
uniform vec4 single;
uniform ivec4 iarr[2];
uniform vec4 one[1];
uniform int pick;
out vec4 color;
void main() { color = pick == -2 ? vec4(iarr[1]) / 255.0 : pick == -3 ? one[0] : pick < 0 ? single : arr[pick]; }
""");
vao = GLSMVertexInput.glGenVertexArrays();
GLSMVertexInput.glBindVertexArray(vao);
GLSMPrograms.glUseProgram(program);
arr = loc("arr");
single = loc("single");
pick = loc("pick");
}
@AfterEach
void tearDown() {
GLSMPrograms.glUseProgram(0);
GLSMVertexInput.glBindVertexArray(0);
GLSMVertexInput.glDeleteVertexArrays(vao);
GLSMPrograms.glDeleteProgram(program);
}
private int loc(String name) {
return GLSMPrograms.glGetUniformLocation(program, name);
}
private static int argb(int[] rgb) {
return 0xFF000000 | (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
}
private static float[] vec(int[] rgb) {
return new float[] {rgb[0] / 255f, rgb[1] / 255f, rgb[2] / 255f, 1f};
}
private static FloatBuffer pack(int[]... colors) {
final FloatBuffer buf = MemoryUtil.memAllocFloat(colors.length * 4);
for (int[] c : colors) buf.put(vec(c));
buf.flip();
return buf;
}
private void writeAt(int location, int[]... colors) {
final FloatBuffer buf = pack(colors);
try {
GLSMPrograms.glUniform4(location, buf);
} finally {
MemoryUtil.memFree(buf);
}
}
private void writeElement(int location, int[] rgb) {
final float[] v = vec(rgb);
GLSMPrograms.glUniform4f(location, v[0], v[1], v[2], v[3]);
}
private void writeAll() {
writeAt(arr, A);
}
private int drawPick(int index) {
GlsmSdlHeadlessRig.beginFrame();
GlsmSdlHeadlessRig.bindTarget();
GLSMPrograms.glUniform1i(pick, index);
GLSMDraws.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
return GlsmSdlHeadlessRig.readTarget(GlsmSdlHeadlessRig.SIZE / 2, GlsmSdlHeadlessRig.SIZE / 2, 1, 1)[0];
}
private void assertPick(int index, int[] expected, String label) {
final int got = drawPick(index);
assertEquals(Integer.toHexString(argb(expected)), Integer.toHexString(got), label + ": " + GlsmSdlHeadlessRig.describe(got));
}
private float[] readUniform(int location) {
final FloatBuffer buf = MemoryUtil.memAllocFloat(4);
try {
GLSMPrograms.glGetUniform(program, location, buf);
final float[] out = new float[4];
buf.get(out);
return out;
} finally {
MemoryUtil.memFree(buf);
}
}
@Test
void arrayElementLocations() {
assertNotEquals(-1, arr, "arr");
assertNotEquals(-1, single, "single");
assertEquals(arr, loc("arr[0]"), "arr[0]");
assertEquals(arr + 3, loc("arr[3]"), "arr[3]");
assertEquals(arr + 7, loc("arr[7]"), "arr[7]");
assertEquals(-1, loc("arr[8]"), "arr[8]");
assertEquals(-1, loc("arr[03]"), "arr[03]");
assertEquals(-1, loc("arr[-1]"), "arr[-1]");
assertEquals(arr, loc("arr[]"), "arr[] is index 0, as Mesa parses it");
assertEquals(single, loc("single[0]"), "single[0]");
assertEquals(-1, loc("single[1]"), "single[1]");
}
@Test
void elementWriteLandsInTheArray() {
writeAll();
final int element3 = loc("arr[3]");
writeElement(element3, X);
assertPick(3, X, "pick=3 shows the element write");
assertPick(2, A[2], "pick=2 keeps the bulk write");
assertArrayEquals(vec(X), readUniform(element3), 0f, "glGetUniform arr[3]");
assertArrayEquals(vec(A[2]), readUniform(loc("arr[2]")), 0f, "glGetUniform arr[2]");
}
@Test
void elementWriteClampsToTheArrayEnd() {
writeAll();
writeAt(loc("arr[6]"), B);
assertPick(6, B[0], "pick=6");
assertPick(7, B[1], "pick=7");
assertPick(5, A[5], "pick=5");
}
@Test
void countBeyondTheDeclarationFollowsMesa() {
writeElement(single, A[0]);
writeAt(single, B);
assertPick(-1, A[0], "count 2 on a non-array is ignored");
assertArrayEquals(vec(A[0]), readUniform(single), 0f, "glGetUniform single keeps its value");
writeAt(loc("one"), B);
assertPick(-3, B[0], "count 2 on a size-1 array clamps");
}
@Test
void fullWriteAfterElementWriteIsNotElided() {
writeAll();
writeElement(loc("arr[3]"), X);
writeAll();
assertPick(3, A[3], "pick=3 after the repeated full write");
}
@Test
void intElementReadsBackAsInts() {
final int element1 = loc("iarr[1]");
assertEquals(loc("iarr") + 1, element1, "iarr[1]");
GLSMPrograms.glUniform4i(loc("iarr"), 1, 2, 3, 4);
GLSMPrograms.glUniform4i(element1, X[0], X[1], X[2], 255);
assertPick(-2, X, "pick=-2 shows iarr[1]");
assertArrayEquals(new int[] {X[0], X[1], X[2], 255}, readIntUniform(element1), "glGetUniformiv iarr[1]");
assertArrayEquals(new int[] {1, 2, 3, 4}, readIntUniform(loc("iarr")), "glGetUniformiv iarr[0]");
}
private int[] readIntUniform(int location) {
final IntBuffer buf = MemoryUtil.memAllocInt(4);
try {
GLSMPrograms.glGetUniform(program, location, buf);
final int[] out = new int[4];
buf.get(out);
return out;
} finally {
MemoryUtil.memFree(buf);
}
}
@Test
void activeUniformReportsArrayNameWithIndexZero() {
final Map<String, Integer> sizes = activeUniformSizes();
assertEquals(COUNT, sizes.getOrDefault("arr[0]", -1), "size of arr[0] in " + sizes);
assertEquals(1, sizes.getOrDefault("single", -1), "size of single in " + sizes);
assertEquals(1, sizes.getOrDefault("one[0]", -1), "size of one[0] in " + sizes);
}
private Map<String, Integer> activeUniformSizes() {
final int active = GLSMPrograms.glGetProgrami(program, GL20.GL_ACTIVE_UNIFORMS);
final Map<String, Integer> sizes = new HashMap<>();
final IntBuffer length = MemoryUtil.memAllocInt(1);
final IntBuffer size = MemoryUtil.memAllocInt(1);
final IntBuffer type = MemoryUtil.memAllocInt(1);
final ByteBuffer name = MemoryUtil.memAlloc(128);
try {
for (int i = 0; i < active; i++) {
GLSMPrograms.glGetActiveUniform(program, i, length, size, type, name);
final byte[] bytes = new byte[length.get(0)];
for (int c = 0; c < bytes.length; c++) bytes[c] = name.get(c);
sizes.put(new String(bytes, java.nio.charset.StandardCharsets.US_ASCII), size.get(0));
}
} finally {
MemoryUtil.memFree(name);
MemoryUtil.memFree(type);
MemoryUtil.memFree(size);
MemoryUtil.memFree(length);
}
return sizes;
}
}
@@ -0,0 +1,21 @@
package com.gtnewhorizons.angelica.api.weather;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.entity.EntityLivingBase;
public interface WeatherColumnProvider {
// False suppresses vanilla rain and snow in this dimension.
boolean rendersVanillaWeather();
// Called each instanced weather frame before columnRevision and collectColumns; returns the tick counter that drives scroll and drift.
int beginFrame(WorldClient world, EntityLivingBase view, float partialTicks);
// Must change whenever any collected column changes; columns are re-collected only then or when the dimension or the player's block X/Z changes.
int columnRevision();
// Finite positive block distance d0 in alpha = ((1 - (d / d0)^2) * fadeScale + 0.5) * a.
float fadeRadius();
void collectColumns(WeatherColumnSink sink);
}
@@ -0,0 +1,12 @@
package com.gtnewhorizons.angelica.api.weather;
import net.minecraft.util.ResourceLocation;
public interface WeatherColumnSink {
// scrollPeriod is a power of two no greater than 1024.
void beginLayer(ResourceLocation texture, int scrollPeriod, float uDriftScale, float vDriftScale, float fadeScale);
// x and z are world block coordinates; brightness is a packed lightmap coordinate below 2^24.
void column(int x, int z, float bottom, float top, float uBase, float uDrift, float vBase, float vDrift, int brightness, int scrollOffset, float scrollScale, float r, float g, float b, float a);
}
@@ -0,0 +1,4 @@
@API(owner = "angelica", provides = "angelica|weather", apiVersion = "1")
package com.gtnewhorizons.angelica.api.weather;
import cpw.mods.fml.common.API;
@@ -28,6 +28,7 @@ import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
import com.gtnewhorizons.angelica.rendering.tesr.BatchDrawDefaults;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import com.gtnewhorizons.angelica.rendering.tesr.FlattenedItemLighting;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@@ -1194,6 +1195,7 @@ public class BatchingFontRenderer {
final Tessellator tessellator = Tessellator.instance;
int lastTexture = INVALID_TEXTURE;
boolean drawing = false;
FlattenedItemLighting.setPreLitText(true);
try {
for (int i = from; i < to; i++) {
final FontDrawCmd cmd = cmdsData[i];
@@ -1219,8 +1221,12 @@ public class BatchingFontRenderer {
owner.emitCommand(tessellator, cmd);
}
} finally {
if (drawing) {
tessellator.draw();
try {
if (drawing) {
tessellator.draw();
}
} finally {
FlattenedItemLighting.setPreLitText(false);
}
}
}
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.common;
import net.minecraft.block.BlockContainer;
import net.minecraft.block.material.Material;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
public class BlockClipPlaneTest extends BlockContainer {
public BlockClipPlaneTest() {
super(Material.iron);
setHardness(0.7f);
}
@Override
public TileEntity createNewTileEntity(World world, int meta) {
return new TileEntityClipPlaneTest();
}
@Override
public boolean isOpaqueCube() {
return false;
}
@Override
public boolean renderAsNormalBlock() {
return false;
}
@Override
public int getRenderType() {
return -1;
}
}
@@ -0,0 +1,11 @@
package com.gtnewhorizons.angelica.common;
import net.minecraft.tileentity.TileEntity;
public class TileEntityClipPlaneTest extends TileEntity {
@Override
public boolean canUpdate() {
return false;
}
}
@@ -30,6 +30,7 @@ public class ModStatus {
public static boolean isBaublesLoaded;
public static boolean isCosmeticArmorReworkedLoaded;
public static boolean isFluidLoggedLoaded;
public static boolean isLiquidLoggedLoaded;
public static boolean isCubicChunksLoaded;
public static boolean isBOPLoaded;
public static boolean isEtFuturumLoaded;
@@ -51,6 +52,7 @@ public class ModStatus {
isBaublesLoaded = Loader.isModLoaded("Baubles");
isCosmeticArmorReworkedLoaded = Loader.isModLoaded("cosmeticarmorreworked");
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
isLiquidLoggedLoaded = Loader.isModLoaded("liquidlogged");
isCubicChunksLoaded = Loader.isModLoaded("cubicchunks");
isEtFuturumLoaded = Loader.isModLoaded("etfuturum");
@@ -0,0 +1,77 @@
package com.gtnewhorizons.angelica.compat.spark;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.io.InputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public final class SparkKnownSources {
private static final Logger LOGGER = LogManager.getLogger("Angelica");
private static final String PLUGIN = "me/lucko/spark/forge/plugin/Forge1710SparkPlugin";
private static final String SOURCE_METADATA = "me.lucko.spark.common.sampler.source.SourceMetadata";
private static MethodHandle constructor;
private static boolean failed;
private SparkKnownSources() {}
public static boolean mixinApplies() {
try (InputStream stream = SparkKnownSources.class.getResourceAsStream("/" + PLUGIN + ".class")) {
if (stream == null) {
return false;
}
final boolean[] declared = new boolean[1];
new ClassReader(stream).accept(new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if ("getKnownSources".equals(name) && "()Ljava/util/Collection;".equals(desc)) {
declared[0] = true;
}
return null;
}
}, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
if (declared[0]) {
LOGGER.info("spark already reports mod sources; skipping");
return false;
}
return true;
} catch (Throwable t) {
LOGGER.warn("Could not inspect spark plugin class; not adding mod sources", t);
return false;
}
}
public static Collection<Object> gather(ClassLoader loader) {
if (failed) {
return Collections.emptyList();
}
try {
if (constructor == null) constructor = MethodHandles.publicLookup().findConstructor(Class.forName(SOURCE_METADATA, false, loader),
MethodType.methodType(void.class, String.class, String.class, String.class, String.class));
final List<ModContainer> mods = Loader.instance().getActiveModList();
final List<Object> sources = new ArrayList<>(mods.size());
for (int i = 0; i < mods.size(); i++) {
final ModContainer mod = mods.get(i);
sources.add(constructor.invoke(mod.getModId(), mod.getVersion(), (String) null, (String) null));
}
return sources;
} catch (Throwable t) {
failed = true;
LOGGER.warn("Could not build spark mod sources; profiles will omit mod versions", t);
return Collections.emptyList();
}
}
}
@@ -14,6 +14,7 @@ import com.gtnewhorizons.angelica.rendering.FpsReducer;
import com.gtnewhorizons.angelica.rendering.FramePacer;
import com.gtnewhorizons.angelica.rendering.IsbrhTestRenderer;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import com.prupe.mcpatcher.mal.biome.BiomeAPI;
import com.sun.management.OperatingSystemMXBean;
import com.sun.management.ThreadMXBean;
import cpw.mods.fml.client.FMLClientHandler;
@@ -24,6 +25,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityClientPlayerMP;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiSelectWorld;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.server.MinecraftServer;
@@ -34,8 +36,12 @@ import net.minecraft.util.IChatComponent;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ScreenShotHelper;
import net.minecraft.world.MinecraftException;
import net.minecraft.world.Teleporter;
import net.minecraft.world.World;
import net.minecraft.world.WorldSettings;
import net.minecraft.world.WorldServer;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.biome.WorldChunkManager;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.storage.ThreadedFileIOBase;
@@ -66,6 +72,8 @@ public final class FlybyRunner {
private enum State { IDLE, WAITING, PREPARING, WARMUP, RUNNING, SETTLE, EXITING, MENU, DONE }
private static final int BIOME_WINDOW = 11;
private static final int BIOME_SEARCH_CELLS = 512;
private static final int EXIT_TICKS = 20;
private static final int SETTLE_TICKS = 40;
private static final int MENU_SETTLE_TICKS = 20;
@@ -93,6 +101,16 @@ public final class FlybyRunner {
private final AtomicReference<String> pendingDimensionPlayer = new AtomicReference<>();
private boolean dimensionRequested;
private volatile boolean dimensionAbandoned;
private final AtomicReference<String> pendingBiomePlayer = new AtomicReference<>();
private boolean biomeRequested;
private volatile boolean biomeAbandoned;
private volatile FlybyOrigin biomeOrigin;
private BiomeGenBase[] biomeScratch;
private volatile boolean survivalRestoreRequested;
private boolean survivalApplied;
private String survivalPlayerName;
private String survivalWorld;
private WorldSettings.GameType survivalRestoreType;
private FlybyRequest activeRequest;
private String[] sceneCommands = NO_COMMANDS;
private volatile boolean sceneClearRequested;
@@ -229,6 +247,10 @@ public final class FlybyRunner {
this.exitWhenDone = false;
this.dimensionRequested = false;
this.dimensionAbandoned = false;
this.biomeRequested = false;
this.biomeAbandoned = false;
this.biomeOrigin = null;
if (SystemProperties.FLYBY_SURVIVAL) this.survivalRestoreRequested = true;
this.jfr = false;
this.armed = false;
this.fixedOrigin = null;
@@ -305,6 +327,7 @@ public final class FlybyRunner {
return;
}
if (this.awaitingDimension(mc, player)) return;
if (this.awaitingBiome(mc, player)) return;
this.begin(mc, player);
}
case PREPARING -> {
@@ -390,6 +413,26 @@ public final class FlybyRunner {
return true;
}
private boolean awaitingBiome(Minecraft mc, EntityClientPlayerMP player) {
final String biome = SystemProperties.FLYBY_BIOME;
if (!this.startedFromProperties || biome.isEmpty() || this.biomeAbandoned) return false;
final FlybyOrigin found = this.biomeOrigin;
if (found != null) {
this.fixedOrigin = this.fixedOrigin == null ? found : new FlybyOrigin(found.x(), found.z(), this.fixedOrigin.yaw());
return false;
}
if (mc.getIntegratedServer() == null) {
LOGGER.warn("Flyby: not singleplayer, ignoring biome '{}'", biome);
this.biomeAbandoned = true;
return false;
}
if (!this.biomeRequested) {
this.biomeRequested = true;
this.pendingBiomePlayer.set(player.getCommandSenderName());
}
return true;
}
private void overridePauseOnLostFocus(Minecraft mc) {
if (this.pauseOnLostFocusOverridden) return;
this.pauseOnLostFocusSaved = mc.gameSettings.pauseOnLostFocus;
@@ -657,7 +700,7 @@ public final class FlybyRunner {
public void onServerTick(TickEvent.ServerTickEvent event) {
if (event.phase != TickEvent.Phase.END) return;
final boolean clear = this.sceneClearRequested;
if (!clear && this.pendingRequest.get() == null && this.pendingDimensionPlayer.get() == null && this.sceneDeferred.isEmpty()) return;
if (!clear && this.pendingRequest.get() == null && this.pendingDimensionPlayer.get() == null && this.pendingBiomePlayer.get() == null && !this.survivalRestoreRequested && this.sceneDeferred.isEmpty()) return;
final MinecraftServer server = MinecraftServer.getServer();
if (server == null) return;
@@ -670,10 +713,23 @@ public final class FlybyRunner {
LOGGER.warn("Flyby: no server player named '{}', staying in the current dimension", traveller);
this.dimensionAbandoned = true;
} else {
server.getConfigurationManager().transferPlayerToDimension(player, SystemProperties.FLYBY_DIMENSION);
final int dimension = SystemProperties.FLYBY_DIMENSION;
final WorldServer target = server.worldServerForDimension(dimension);
server.getConfigurationManager().transferPlayerToDimension(player, dimension,
target.provider.hasNoSky ? target.getDefaultTeleporter() : new SurfaceTeleporter(target));
}
}
final String seeker = this.pendingBiomePlayer.getAndSet(null);
if (seeker != null) {
this.findBiome(server, seeker);
}
if (this.survivalRestoreRequested) {
this.survivalRestoreRequested = false;
this.restoreGameType(server);
}
if (clear) {
this.sceneClearRequested = false;
this.clearScene(server);
@@ -688,11 +744,104 @@ public final class FlybyRunner {
final double feetY = this.flightFeetY(server, request);
request.feetY().complete(feetY);
this.applyFreeze(server);
if (SystemProperties.FLYBY_SURVIVAL) {
if (this.survivalApplied) {
LOGGER.warn("Flyby: game mode restore pending for world '{}', flying without survival", this.survivalWorld);
} else {
this.applySurvival(server, request.playerName());
}
}
if (request.scene()) {
this.runScene(server, request, feetY);
}
}
private void applySurvival(MinecraftServer server, String playerName) {
final EntityPlayerMP player = server.getConfigurationManager().func_152612_a(playerName);
if (player == null) {
LOGGER.warn("Flyby: no server player named '{}', staying in the current game mode", playerName);
return;
}
this.survivalRestoreType = player.theItemInWorldManager.getGameType();
player.setGameType(WorldSettings.GameType.SURVIVAL);
player.capabilities.allowFlying = player.capabilities.isFlying = player.capabilities.disableDamage = true;
player.sendPlayerAbilities();
this.survivalApplied = true;
this.survivalPlayerName = playerName;
this.survivalWorld = server.getFolderName();
}
private void restoreGameType(MinecraftServer server) {
if (!this.survivalApplied) return;
final EntityPlayerMP player = server.getFolderName().equals(this.survivalWorld)
? server.getConfigurationManager().func_152612_a(this.survivalPlayerName) : null;
if (player == null) {
this.survivalRestoreRequested = true;
return;
}
player.setGameType(this.survivalRestoreType);
this.survivalApplied = false;
}
private void findBiome(MinecraftServer server, String playerName) {
final String name = SystemProperties.FLYBY_BIOME;
final EntityPlayerMP player = server.getConfigurationManager().func_152612_a(playerName);
if (player == null) {
LOGGER.warn("Flyby: no server player named '{}', ignoring biome '{}'", playerName, name);
this.biomeAbandoned = true;
return;
}
final BiomeGenBase target = BiomeAPI.findBiomeByName(name);
if (target == null) {
LOGGER.error("Flyby: unknown biome '{}'", name);
this.biomeAbandoned = true;
return;
}
final FlybyOrigin found = this.scanForBiome(player.worldObj.getWorldChunkManager(), target);
this.biomeScratch = null;
if (found == null) {
LOGGER.error("Flyby: biome '{}' not found within {} blocks", name, BIOME_SEARCH_CELLS * 4);
this.biomeAbandoned = true;
return;
}
LOGGER.info("Flyby: biome '{}' found at {} {}", name, found.x(), found.z());
this.biomeOrigin = found;
}
private FlybyOrigin scanForBiome(WorldChunkManager manager, BiomeGenBase target) {
FlybyOrigin found = this.scanStrip(manager, target, 0, 0, 0, 0);
for (int r = 1; found == null && r <= BIOME_SEARCH_CELLS; r++) {
found = this.scanStrip(manager, target, -r, -r, r, -r);
if (found == null) found = this.scanStrip(manager, target, -r, r, r, r);
if (found == null) found = this.scanStrip(manager, target, -r, 1 - r, -r, r - 1);
if (found == null) found = this.scanStrip(manager, target, r, 1 - r, r, r - 1);
}
return found;
}
private FlybyOrigin scanStrip(WorldChunkManager manager, BiomeGenBase target, int minX, int minZ, int maxX, int maxZ) {
final int half = BIOME_WINDOW / 2;
final int width = maxX - minX + BIOME_WINDOW;
this.biomeScratch = manager.getBiomesForGeneration(this.biomeScratch, minX - half, minZ - half, width, maxZ - minZ + BIOME_WINDOW);
for (int cz = minZ; cz <= maxZ; cz++) {
for (int cx = minX; cx <= maxX; cx++) {
if (this.windowMatches(target, width, minX - half, minZ - half, cx, cz)) return new FlybyOrigin(cx * 4 + 2, cz * 4 + 2, 0.0F);
}
}
return null;
}
private boolean windowMatches(BiomeGenBase target, int width, int originX, int originZ, int cx, int cz) {
final int half = BIOME_WINDOW / 2;
for (int dz = -half; dz <= half; dz++) {
final int row = (cz + dz - originZ) * width - originX + cx;
for (int dx = -half; dx <= half; dx++) {
if (this.biomeScratch[row + dx] != target) return false;
}
}
return true;
}
private void discardWorldChanges(MinecraftServer server) {
if (worldChangesDiscarded) return;
@@ -864,6 +1013,7 @@ public final class FlybyRunner {
private void finish(Minecraft mc, EntityClientPlayerMP player) {
this.state = State.SETTLE;
if (SystemProperties.FLYBY_SURVIVAL) this.survivalRestoreRequested = true;
this.tick = 0;
final long elapsedNs = System.nanoTime() - this.runStartNs;
Tracy.sectionLeave(this.legSection);
@@ -1140,6 +1290,7 @@ public final class FlybyRunner {
this.sceneClearRequested = this.sceneCommands.length > 0;
this.restorePacing(mc);
}
if (SystemProperties.FLYBY_SURVIVAL) this.survivalRestoreRequested = true;
}
public boolean startedFromProperties() {
@@ -1147,4 +1298,20 @@ public final class FlybyRunner {
}
private record FlybyRequest(double[] pathX, double[] pathZ, double originX, double originZ, double parkedFeetY, String playerName, boolean sampleTerrain, boolean scene, CompletableFuture<Double> feetY) {}
private static final class SurfaceTeleporter extends Teleporter {
private final WorldServer world;
SurfaceTeleporter(WorldServer world) {
super(world);
this.world = world;
}
@Override
public void placeInPortal(Entity entity, double x, double y, double z, float yaw) {
final int top = this.world.getTopSolidOrLiquidBlock(MathHelper.floor_double(entity.posX), MathHelper.floor_double(entity.posZ));
entity.setLocationAndAngles(entity.posX, top > 0 ? top : entity.posY, entity.posZ, entity.rotationYaw, entity.rotationPitch);
entity.motionX = entity.motionY = entity.motionZ = 0.0;
}
}
}
@@ -34,9 +34,6 @@ public final class PositionEntries {
}
}
private WorldProvider cachedProvider;
private String dimensionName = "";
@Override
public void display(DebugScreenDisplayer displayer, DebugContext context) {
final EntityClientPlayerMP player = context.player();
@@ -52,10 +49,7 @@ public final class PositionEntries {
facing.value(DIRECTION_NAMES[heading]).text(" (").text(TOWARDS[heading]).text(") (");
facing.value(MathHelper.wrapAngleTo180_float(player.rotationYaw), 1).text(SEPARATOR).value(MathHelper.wrapAngleTo180_float(player.rotationPitch), 1).text(')');
final WorldProvider provider = context.world().provider;
if (provider != cachedProvider) {
cachedProvider = provider;
dimensionName = provider.getDimensionName();
}
final String dimensionName = provider.getDimensionName();
displayer.fact(DebugGroup.POSITION, "Dimension").value(dimensionName).text(" (").value(provider.dimensionId).text(')');
}
}
@@ -274,6 +274,13 @@ public class IrisGLSMBridge {
refreshBlendCondition();
});
GLSMHooks.CLIP_PLANES_IN_USE.addListener(event -> {
if (!Iris.enabled) return;
if (Iris.getPipelineManager().getPipelineNullable() instanceof DeferredWorldRenderingPipeline drp) {
drp.onClipPlanesInUseChanged();
}
});
GLSMHooks.FOREIGN_DRAW_END.addListener(event -> {
if (!Iris.enabled) return;
if (programRestoreDeferred) {
@@ -336,12 +343,13 @@ public class IrisGLSMBridge {
WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
if (pipeline instanceof DeferredWorldRenderingPipeline drp) {
DeferredWorldRenderingPipeline.Pass activePass = drp.getActivePassProgram();
if (activePass != null && activePass.getProgram() != null && activePass.getProgram().getProgramId() == event.newProgram) {
final Program bound = activePass != null ? activePass.boundProgram() : null;
if (bound != null && bound.getProgramId() == event.newProgram) {
final int frame = SystemTimeUniforms.COUNTER.getAsInt();
if (programLastUpdatedFrame.get(event.newProgram) != frame
|| !ProgramUniforms.isActiveProgramBound()
|| ProgramUniforms.activeHasDeferredUploads()) {
activePass.getProgram().getUniforms().update();
bound.getUniforms().update();
programLastUpdatedFrame.put(event.newProgram, frame);
}
}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.mixins;
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
import com.gtnewhorizon.gtnhmixins.builders.MixinBuilder;
import com.gtnewhorizons.angelica.api.BlockLightProvider;
import com.gtnewhorizons.angelica.compat.spark.SparkKnownSources;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.CompatConfig;
import com.gtnewhorizons.angelica.config.SystemProperties;
@@ -641,6 +642,13 @@ public enum Mixins implements IMixins {
)
),
SPARK_MOD_SOURCES(new MixinBuilder("Mod versions in spark 1.7.10 profiles")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.SPARK)
.setApplyIf(SparkKnownSources::mixinApplies)
.addClientMixins("spark.MixinForge1710SparkPlugin")
),
ANGELICA_TEXTURE(new MixinBuilder()
.setPhase(Phase.EARLY)
.addClientMixins(
@@ -892,6 +900,18 @@ public enum Mixins implements IMixins {
"MixinBlockTFMagicLeaves"
))
),
NOTFINE_BIOMES_O_PLENTY_COMPAT(new MixinBuilder()
.setPhase(Phase.LATE)
.setApplyIf(() -> AngelicaConfig.enableNotFineFeatures)
.addRequiredMod(TargetedMod.BIOMES_O_PLENTY)
.addClientMixins(addPrefix("notfine.leaves.biomesoplenty.",
"MixinBlockBOPLeaves",
"MixinBlockBOPColorizedLeaves",
"MixinBlockBOPAppleLeaves",
"MixinBlockBOPPersimmonLeaves",
"MixinColorizedLeavesRenderer"
))
),
ET_FUTURUM_ELYTRA_CAPE(new MixinBuilder("Set custom item ID for elytra with cape texture")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.ET_FUTURUM_REQUIEM)
@@ -32,6 +32,7 @@ public enum TargetedMod implements ITargetMod {
OPENBLOCKS(null, "OpenBlocks"),
RAILCRAFT(null, "Railcraft"),
SECURITYCRAFT(null, "securitycraft"),
SPARK(null, "spark"),
STARMINER("jp.mc.ancientred.starminer.core.SMPlugin", "modJ_StarMiner"),
SUPERNOVA("com.mitchej123.supernova.core.SupernovaCore", "supernova"),
THAUMCRAFT(null, "Thaumcraft"),
@@ -39,6 +39,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");
}
}
@@ -134,29 +134,12 @@ public final class WeatherColumns {
return height[slotOf(x, z)];
}
public int hashAt(int x, int z) {
return hash[slotOf(x, z)];
}
public float temperatureAt(int x, int z, int y) {
final int slot = slotOf(x, z);
if (y <= 64) return temperatureBelow65[slot];
return temperatureAtReference[slot] - (y - TEMPERATURE_REFERENCE_Y) * TEMPERATURE_PER_BLOCK;
}
public int lightAt(WorldClient world, int x, int z, int y) {
final int slot = slotOf(x, z);
if (lightY[slot] != y) {
lightY[slot] = y;
light[slot] = world.getLightBrightnessForSkyBlocks(x, y, z, 0);
}
return light[slot];
}
public float jitterAt(int x, int z, int index) {
return jitter[slotOf(x, z) * 4 + index];
}
private int slotOf(int x, int z) {
return Math.floorMod(x, size) * size + Math.floorMod(z, size);
}
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica.render;
import com.gtnewhorizons.angelica.api.weather.WeatherColumnProvider;
import com.gtnewhorizons.angelica.api.weather.WeatherColumnSink;
import com.gtnewhorizons.angelica.compat.dragonapi.DragonAPICompat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.SystemProperties;
@@ -27,27 +29,30 @@ import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.client.particle.EntityRainFX;
import net.minecraft.client.particle.EntitySmokeFX;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.biome.WorldChunkManager;
import net.minecraftforge.client.IRenderHandler;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Random;
/**
* Replaces EntityRenderer.renderRainSnow.
*
* <p>The whole grid is one instanced draw per precipitation type. Everything that varies per frame
* <p>The whole grid is one instanced draw per layer: vanilla rain, vanilla snow, and each provider layer. Everything that varies per frame
* (billboard axis, distance falloff, texture scroll, snow drift) happens in the vertex shader, and the
* instance buffer is only rebuilt when the camera changes block or {@link WeatherColumns} actually
* resamples something. Per-column data comes from that cache rather than being recomputed every frame.
* {@link WeatherColumnProvider} columns are collected from the provider instead, and re-collected only when its revision,
* the dimension, or the player's block X/Z changes.
*/
public final class WeatherRenderer {
@@ -63,17 +68,18 @@ public final class WeatherRenderer {
private static final long P_REBUILDS = Tracy.plotHandle("weather.rebuilds");
private static final long P_PARTICLE_MICROS = Tracy.plotHandle("weather.particleMicros");
private static final long P_PARTICLES_SPAWNED = Tracy.plotHandle("weather.particlesSpawned");
private static final long P_PROVIDER_COLUMNS = Tracy.plotHandle("weather.providerColumns");
private static final long P_PROVIDER_DRAWS = Tracy.plotHandle("weather.providerDraws");
private static final long P_PROVIDER_REBUILDS = Tracy.plotHandle("weather.providerRebuilds");
private static final long P_LEGACY_HANDLER = Tracy.plotHandle("weather.legacyHandler");
private static final WeatherColumns columns = new WeatherColumns();
private static float[] axisX;
private static float[] axisZ;
private static int instanceBuffer;
private static ByteBuffer instanceData;
private static int instanceCapacity;
private static int instanceCount;
private static int rainInstances;
private static final WeatherBatch VANILLA = new WeatherBatch();
private static final WeatherBatch PROVIDER = new WeatherBatch();
private static int builtRevision = Integer.MIN_VALUE;
private static int builtX = Integer.MIN_VALUE;
@@ -84,6 +90,32 @@ public final class WeatherRenderer {
private static int builtCameraY = Integer.MIN_VALUE;
private static boolean rebuiltThisFrame;
private static int builtProviderRevision = Integer.MIN_VALUE;
private static int builtProviderDimension = Integer.MIN_VALUE;
private static int builtProviderX = Integer.MIN_VALUE;
private static int builtProviderZ = Integer.MIN_VALUE;
private static boolean providerRebuiltThisFrame;
private static int frameQuads;
private static int frameDrawCalls;
private static int providerColumns;
private static int providerDraws;
private static boolean legacyHandler;
private static int entityBlockX;
private static int entityBlockY;
private static int entityBlockZ;
private static double cameraX;
private static double cameraY;
private static double cameraZ;
private static int cameraBlockY;
private static double viewPosX;
private static double viewPosZ;
private static int updateCount;
private static int radius;
private static float rainStrength;
private static float partialTicks;
private static final int PARTICLE_RADIUS = 10;
private static final Random particleRandom = new Random();
private static int rainSoundCounter;
@@ -181,180 +213,180 @@ public final class WeatherRenderer {
}
public static boolean render(float partialTicks) {
final Minecraft mc = Minecraft.getMinecraft();
final WorldClient world = mc.theWorld;
final long startNanos = System.nanoTime();
if (!AngelicaConfig.enableInstancedWeather) return false;
if (world.provider.getWeatherRenderer() != null) {
Tracy.plot(P_STRENGTH, 0.0);
report(0, 0, startNanos);
return false;
}
if (DragonAPICompat.forcingSeasonalSnow) {
report(0, 0, startNanos);
return false;
}
final float rainStrength = world.getRainStrength(partialTicks);
Tracy.plot(P_STRENGTH, rainStrength);
frameQuads = 0;
frameDrawCalls = 0;
providerColumns = 0;
providerDraws = 0;
legacyHandler = false;
final boolean handled = renderFrame(partialTicks);
report(startNanos);
return handled;
}
final int radius = SettingsManager.downfallDistance;
if (rainStrength <= 0.0F || radius <= 0) {
report(0, 0, startNanos);
private static boolean renderFrame(float partialTicks) {
final Minecraft mc = Minecraft.getMinecraft();
final WorldClient world = mc.theWorld;
final IRenderHandler handler = world.provider.getWeatherRenderer();
final WeatherColumnProvider provider;
if (handler == null) {
if (DragonAPICompat.forcingSeasonalSnow) return false;
provider = null;
} else if (handler instanceof WeatherColumnProvider columnProvider) {
provider = columnProvider;
} else {
return useLegacyHandler();
}
rainStrength = provider == null || provider.rendersVanillaWeather() ? world.getRainStrength(partialTicks) : 0.0F;
radius = SettingsManager.downfallDistance;
final boolean vanillaActive = rainStrength > 0.0F && radius > 0;
if (provider == null && !vanillaActive) {
Tracy.plot(P_STRENGTH, rainStrength);
return true;
}
buildAxisTable();
final EntityLivingBase view = mc.renderViewEntity;
final int entityBlockX = MathHelper.floor_double(view.posX);
final int entityBlockY = MathHelper.floor_double(view.posY);
final int entityBlockZ = MathHelper.floor_double(view.posZ);
final double cameraX = view.lastTickPosX + (view.posX - view.lastTickPosX) * partialTicks;
final double cameraY = view.lastTickPosY + (view.posY - view.lastTickPosY) * partialTicks;
final double cameraZ = view.lastTickPosZ + (view.posZ - view.lastTickPosZ) * partialTicks;
final int cameraBlockY = MathHelper.floor_double(cameraY);
final int updateCount = ((EntityRendererAccessor) mc.entityRenderer).getRendererUpdateCount();
columns.update(world, entityBlockX, entityBlockZ, radius, updateCount);
WeatherRenderer.partialTicks = partialTicks;
entityBlockX = MathHelper.floor_double(view.posX);
entityBlockY = MathHelper.floor_double(view.posY);
entityBlockZ = MathHelper.floor_double(view.posZ);
cameraX = view.lastTickPosX + (view.posX - view.lastTickPosX) * partialTicks;
cameraY = view.lastTickPosY + (view.posY - view.lastTickPosY) * partialTicks;
cameraZ = view.lastTickPosZ + (view.posZ - view.lastTickPosZ) * partialTicks;
cameraBlockY = MathHelper.floor_double(cameraY);
viewPosX = view.posX;
viewPosZ = view.posZ;
updateCount = ((EntityRendererAccessor) mc.entityRenderer).getRendererUpdateCount();
final DeferredWorldRenderingPipeline deferred = deferredPipeline();
mc.entityRenderer.enableLightmap(partialTicks);
GLSMRasterizer.disableCull();
GLSMCurrentAttribs.glNormal3f(0.0F, 1.0F, 0.0F);
GLSMBlend.enableBlend();
GLSMBlend.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLSMDepthStencilAlpha.glAlphaFunc(GL11.GL_GREATER, 0.1F);
GLSMCurrentAttribs.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
if (deferred != null && !deferred.supportsInstancing(Instancing.WEATHER)) {
renderTessellator(mc, world, view, partialTicks, rainStrength, radius, updateCount,
entityBlockX, entityBlockY, entityBlockZ, cameraX, cameraY, cameraZ, cameraBlockY, startNanos);
return true;
final boolean instanced;
if (deferred == null) {
instanced = true;
} else if (deferred.supportsInstancing(Instancing.WEATHER)) {
deferred.rebindCurrentPass();
instanced = deferred.hasInstancedVariant(Instancing.WEATHER);
} else {
instanced = false;
}
if (provider != null && !instanced) {
exitState(mc, partialTicks);
return useLegacyHandler();
}
Tracy.plot(P_STRENGTH, rainStrength);
if (vanillaActive) {
buildAxisTable();
columns.update(world, entityBlockX, entityBlockZ, radius, updateCount);
if (instanced) {
final boolean rebuild = SystemProperties.WEATHER_REBUILD_ALWAYS
|| updateCount - builtUpdateCount >= REBUILD_TICKS
|| columns.revision() != builtRevision
|| entityBlockX != builtX || entityBlockY != builtY || entityBlockZ != builtZ
|| cameraBlockY != builtCameraY
|| radius != builtRadius;
if (rebuild) {
VANILLA.begin(entityBlockX, entityBlockZ, WeatherInstancedAttribs.SOURCE_VANILLA);
VANILLA.beginLayer(RAIN_TEXTURE, WeatherInstancedAttribs.RAIN_PERIOD, 0.0F, 0.0F, WeatherInstancedAttribs.RAIN_FADE);
writeColumns(world, true);
VANILLA.beginLayer(SNOW_TEXTURE, WeatherInstancedAttribs.SNOW_PERIOD, WeatherInstancedAttribs.SNOW_U_DRIFT, WeatherInstancedAttribs.SNOW_V_DRIFT, WeatherInstancedAttribs.SNOW_FADE);
writeColumns(world, false);
VANILLA.upload();
builtCameraY = cameraBlockY;
builtRevision = columns.revision();
builtX = entityBlockX;
builtY = entityBlockY;
builtZ = entityBlockZ;
builtRadius = radius;
builtUpdateCount = updateCount;
}
rebuiltThisFrame = rebuild;
} else {
renderTessellator(mc, world);
}
}
renderInstanced(mc, world, view, deferred, partialTicks, rainStrength, radius, updateCount,
entityBlockX, entityBlockY, entityBlockZ, cameraX, cameraY, cameraZ, cameraBlockY, startNanos);
int providerTick = 0;
float providerAge = 0.0F;
float providerInvRadius = 0.0F;
if (provider != null) {
providerTick = provider.beginFrame(world, view, partialTicks);
final int revision = provider.columnRevision();
final float fadeRadius = provider.fadeRadius();
if (!Float.isFinite(fadeRadius) || fadeRadius <= 0.0F) {
throw new IllegalArgumentException("Weather fade radius must be finite and positive");
}
final int dimension = world.provider.dimensionId;
if (SystemProperties.WEATHER_REBUILD_ALWAYS
|| revision != builtProviderRevision || dimension != builtProviderDimension
|| entityBlockX != builtProviderX || entityBlockZ != builtProviderZ) {
PROVIDER.begin(entityBlockX, entityBlockZ, WeatherInstancedAttribs.SOURCE_PROVIDER);
provider.collectColumns(PROVIDER);
PROVIDER.upload();
builtProviderRevision = revision;
builtProviderDimension = dimension;
builtProviderX = entityBlockX;
builtProviderZ = entityBlockZ;
providerRebuiltThisFrame = true;
}
providerAge = providerTick + partialTicks;
providerInvRadius = 1.0F / fadeRadius;
}
if (instanced && (vanillaActive && VANILLA.count > 0 || provider != null && PROVIDER.count > 0)) {
WeatherParams.set((float) (entityBlockX - cameraX), (float) -cameraY, (float) (entityBlockZ - cameraZ),
radius > 0 ? 1.0F / radius : 0.0F,
(float) (viewPosX - entityBlockX), (float) (viewPosZ - entityBlockZ), partialTicks,
updateCount + partialTicks,
updateCount, rainStrength, providerTick, providerAge, providerInvRadius);
GLSMVertexInput.glBindVertexArray(WeatherQuadMesh.vao());
if (deferred == null) GLSMPrograms.setFfpInstancing(Instancing.WEATHER);
if (vanillaActive) {
frameDrawCalls += VANILLA.draw(mc, deferred);
frameQuads += VANILLA.count;
}
if (provider != null) {
providerDraws = PROVIDER.draw(mc, deferred);
providerColumns = PROVIDER.count;
}
if (deferred == null) GLSMPrograms.setFfpInstancing(Instancing.NONE);
GLSMVertexInput.glBindVertexArray(0);
if (deferred != null) deferred.rebindCurrentPass();
}
exitState(mc, partialTicks);
return true;
}
private static boolean useLegacyHandler() {
Tracy.plot(P_STRENGTH, 0.0);
legacyHandler = true;
return false;
}
private static void exitState(Minecraft mc, float partialTicks) {
GLSMRasterizer.enableCull();
GLSMBlend.disableBlend();
GLSMDepthStencilAlpha.glAlphaFunc(GL11.GL_GREATER, 0.1F);
mc.entityRenderer.disableLightmap(partialTicks);
}
private static DeferredWorldRenderingPipeline deferredPipeline() {
if (!AngelicaConfig.enableIris || !Iris.enabled) return null;
return Iris.getPipelineManager().getPipelineNullable() instanceof DeferredWorldRenderingPipeline pipeline
? pipeline : null;
}
private static void renderInstanced(Minecraft mc, WorldClient world, EntityLivingBase view,
DeferredWorldRenderingPipeline deferred, float partialTicks, float rainStrength, int radius,
int updateCount, int entityBlockX, int entityBlockY, int entityBlockZ, double cameraX,
double cameraY, double cameraZ, int cameraBlockY, long startNanos) {
final boolean rebuild = SystemProperties.WEATHER_REBUILD_ALWAYS
|| updateCount - builtUpdateCount >= REBUILD_TICKS
|| columns.revision() != builtRevision
|| entityBlockX != builtX || entityBlockY != builtY || entityBlockZ != builtZ
|| cameraBlockY != builtCameraY
|| radius != builtRadius;
if (rebuild) {
rebuildInstances(world, radius, entityBlockX, entityBlockY, entityBlockZ, cameraBlockY);
builtCameraY = cameraBlockY;
builtRevision = columns.revision();
builtX = entityBlockX;
builtY = entityBlockY;
builtZ = entityBlockZ;
builtRadius = radius;
builtUpdateCount = updateCount;
}
rebuiltThisFrame = rebuild;
if (instanceCount == 0) {
report(0, 0, startNanos);
return;
}
if (deferred != null) {
deferred.rebindCurrentPass();
if (!deferred.hasInstancedVariant(Instancing.WEATHER)) {
renderTessellator(mc, world, view, partialTicks, rainStrength, radius, updateCount,
entityBlockX, entityBlockY, entityBlockZ, cameraX, cameraY, cameraZ, cameraBlockY, startNanos);
return;
}
}
WeatherParams.set((float) (entityBlockX - cameraX), (float) -cameraY, (float) (entityBlockZ - cameraZ),
1.0F / radius,
(float) (view.posX - entityBlockX), (float) (view.posZ - entityBlockZ), partialTicks,
updateCount + partialTicks,
updateCount & 31, updateCount & 511, rainStrength);
mc.entityRenderer.enableLightmap(partialTicks);
GLSMRasterizer.disableCull();
GLSMCurrentAttribs.glNormal3f(0.0F, 1.0F, 0.0F);
GLSMBlend.enableBlend();
OpenGlHelper.glBlendFunc(770, 771, 1, 0);
GLSMDepthStencilAlpha.glAlphaFunc(GL11.GL_GREATER, 0.1F);
GLSMCurrentAttribs.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
int drawCalls = 0;
drawCalls += drawRange(mc, deferred, RAIN_TEXTURE, 0, rainInstances);
drawCalls += drawRange(mc, deferred, SNOW_TEXTURE, rainInstances, instanceCount - rainInstances);
GLSMRasterizer.enableCull();
GLSMBlend.disableBlend();
GLSMDepthStencilAlpha.glAlphaFunc(GL11.GL_GREATER, 0.1F);
mc.entityRenderer.disableLightmap(partialTicks);
report(instanceCount, drawCalls, startNanos);
}
private static int drawRange(Minecraft mc, DeferredWorldRenderingPipeline deferred,
ResourceLocation texture, int first, int count) {
if (count <= 0) return 0;
mc.getTextureManager().bindTexture(texture);
if (deferred != null) {
deferred.rebindCurrentPass();
if (!deferred.hasInstancedVariant(Instancing.WEATHER)) return 0;
deferred.bindInstancedVariant(Instancing.WEATHER);
}
GLSMVertexInput.glBindVertexArray(WeatherQuadMesh.vao());
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceBuffer);
WeatherInstancedAttribs.pointInstanceAttribs((long) first * WeatherInstancedAttribs.STRIDE);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setCurrentVertexFlags(WeatherQuadMesh.VERTEX_FLAGS);
if (deferred == null) GLSMPrograms.setFfpInstancing(Instancing.WEATHER);
GLSMDraws.glDrawArraysInstanced(GL11.GL_TRIANGLE_FAN, 0, WeatherQuadMesh.VERTEX_COUNT, count);
GLSMPrograms.setFfpInstancing(Instancing.NONE);
GLSMVertexInput.glBindVertexArray(0);
if (deferred != null) deferred.rebindCurrentPass();
return 1;
}
private static void rebuildInstances(WorldClient world, int radius, int entityBlockX, int entityBlockY,
int entityBlockZ, int cameraBlockY) {
final int slots = (radius * 2 + 1) * (radius * 2 + 1);
final int stride = WeatherInstancedAttribs.STRIDE;
if (instanceCapacity < slots) {
instanceCapacity = slots;
instanceData = ByteBuffer.allocateDirect(slots * stride).order(ByteOrder.nativeOrder());
if (instanceBuffer == 0) instanceBuffer = GLSMBuffers.glGenBuffers();
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceBuffer);
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, (long) slots * stride, GL15.GL_DYNAMIC_DRAW);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
WeatherQuadMesh.bindInstanceBuffer(instanceBuffer);
}
int count = writeColumns(world, radius, entityBlockX, entityBlockY, entityBlockZ, cameraBlockY, true, 0);
rainInstances = count;
count = writeColumns(world, radius, entityBlockX, entityBlockY, entityBlockZ, cameraBlockY, false, count);
instanceCount = count;
if (count == 0) return;
instanceData.position(0);
instanceData.limit(count * stride);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceBuffer);
GLSMBuffers.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0L, instanceData);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
instanceData.clear();
}
private static int writeColumns(WorldClient world, int radius, int entityBlockX, int entityBlockY,
int entityBlockZ, int cameraBlockY, boolean wantRain, int count) {
final int stride = WeatherInstancedAttribs.STRIDE;
private static void writeColumns(WorldClient world, boolean wantRain) {
final WorldChunkManager chunkManager = world.getWorldChunkManager();
for (int z = entityBlockZ - radius; z <= entityBlockZ + radius; z++) {
for (int x = entityBlockX - radius; x <= entityBlockX + radius; x++) {
@@ -371,68 +403,48 @@ public final class WeatherRenderer {
if (rain != wantRain) continue;
final int lightY = Math.max(precipitationHeight, cameraBlockY);
int brightness = columns.lightAtSlot(world, slot, x, z, lightY);
if (!rain) brightness = (brightness * 3 + 15728880) / 4;
final int light = columns.lightAtSlot(world, slot, x, z, lightY);
final int base = count * stride;
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_COLUMN_SPAN, x - entityBlockX);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_COLUMN_SPAN + 4, z - entityBlockZ);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_COLUMN_SPAN + 8, bottom);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_COLUMN_SPAN + 12, top);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_JITTER, columns.jitterAtSlot(slot, 0));
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_JITTER + 4, columns.jitterAtSlot(slot, 1));
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_JITTER + 8, columns.jitterAtSlot(slot, 2));
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_JITTER + 12, columns.jitterAtSlot(slot, 3));
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_PARAMS, brightness & 0xFFFF);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_PARAMS + 4, brightness >> 16 & 0xFFFF);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_PARAMS + 8, columns.hashAtSlot(slot) & 31);
instanceData.putFloat(base + WeatherInstancedAttribs.OFFSET_PARAMS + 12, rain ? 0.0F : 1.0F);
count++;
final float j0 = columns.jitterAtSlot(slot, 0);
VANILLA.put(x, z, bottom, top,
rain ? 0.0F : j0, rain ? 0.0F : columns.jitterAtSlot(slot, 1),
rain ? 0.0F : columns.jitterAtSlot(slot, 2), rain ? 0.0F : columns.jitterAtSlot(slot, 3),
rain ? light : snowBrightness(light), rain ? columns.hashAtSlot(slot) : 0,
rain ? WeatherInstancedAttribs.RAIN_SCROLL_BASE + j0 : 1.0F,
1.0F, 1.0F, 1.0F, 1.0F);
}
}
return count;
}
private static void renderTessellator(Minecraft mc, WorldClient world, EntityLivingBase view,
float partialTicks, float rainStrength, int radius, int updateCount, int entityBlockX,
int entityBlockY, int entityBlockZ, double cameraX, double cameraY, double cameraZ,
int cameraBlockY, long startNanos) {
mc.entityRenderer.enableLightmap(partialTicks);
private static void renderTessellator(Minecraft mc, WorldClient world) {
final float age = updateCount + partialTicks;
GLSMRasterizer.disableCull();
GLSMCurrentAttribs.glNormal3f(0.0F, 1.0F, 0.0F);
GLSMBlend.enableBlend();
OpenGlHelper.glBlendFunc(770, 771, 1, 0);
GLSMDepthStencilAlpha.glAlphaFunc(GL11.GL_GREATER, 0.1F);
GLSMCurrentAttribs.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
final Tessellator tessellator = Tessellator.instance;
final WorldChunkManager chunkManager = world.getWorldChunkManager();
int bound = -1;
int quads = 0;
int drawCalls = 0;
for (int z = entityBlockZ - radius; z <= entityBlockZ + radius; z++) {
for (int x = entityBlockX - radius; x <= entityBlockX + radius; x++) {
if (!columns.precipitates(x, z)) continue;
final int slot = columns.slotAt(x, z);
if (!columns.precipitatesAtSlot(slot)) continue;
final int precipitationHeight = columns.heightAt(x, z);
final int precipitationHeight = columns.heightAtSlot(slot);
final int bottom = Math.max(entityBlockY - radius, precipitationHeight);
final int top = Math.max(entityBlockY + radius, precipitationHeight);
if (bottom == top) continue;
final int lightY = Math.max(precipitationHeight, cameraBlockY);
final float temperature = columns.temperatureAt(x, z, bottom);
final boolean rain = world.getWorldChunkManager()
.getTemperatureAtHeight(temperature, precipitationHeight) >= 0.15F;
final int light = columns.lightAtSlot(world, slot, x, z, lightY);
final float temperature = columns.temperatureAtSlot(slot, bottom);
final boolean rain = chunkManager.getTemperatureAtHeight(temperature, precipitationHeight) >= 0.15F;
final int axis = (z - entityBlockZ + 16) * 32 + x - entityBlockX + 16;
final float halfX = axisX[axis] * 0.5F;
final float halfZ = axisZ[axis] * 0.5F;
final double offsetX = (x + 0.5F) - view.posX;
final double offsetZ = (z + 0.5F) - view.posZ;
final double offsetX = (x + 0.5F) - viewPosX;
final double offsetZ = (z + 0.5F) - viewPosZ;
final float distance = MathHelper.sqrt_double(offsetX * offsetX + offsetZ * offsetZ) / radius;
if (rain) {
@@ -445,13 +457,12 @@ public final class WeatherRenderer {
mc.getTextureManager().bindTexture(RAIN_TEXTURE);
tessellator.startDrawingQuads();
}
final float scroll = ((updateCount + columns.hashAt(x, z) & 31) + partialTicks) / 32.0F
* (3.0F + columns.jitterAt(x, z, 0));
tessellator.setBrightness(columns.lightAt(world, x, z, lightY));
final float scroll = ((updateCount + columns.hashAtSlot(slot) & WeatherInstancedAttribs.RAIN_PERIOD - 1) + partialTicks)
/ WeatherInstancedAttribs.RAIN_PERIOD * (WeatherInstancedAttribs.RAIN_SCROLL_BASE + columns.jitterAtSlot(slot, 0));
tessellator.setBrightness(light);
tessellator.setColorRGBA_F(1.0F, 1.0F, 1.0F,
((1.0F - distance * distance) * 0.5F + 0.5F) * rainStrength);
emit(tessellator, x, z, bottom, top, halfX, halfZ, scroll, 0.0F, 0.0F,
cameraX, cameraY, cameraZ);
((1.0F - distance * distance) * WeatherInstancedAttribs.RAIN_FADE + 0.5F) * rainStrength);
emit(tessellator, x, z, bottom, top, halfX, halfZ, scroll, 0.0F, 0.0F);
} else {
if (bound != 1) {
if (bound == 0) {
@@ -462,14 +473,14 @@ public final class WeatherRenderer {
mc.getTextureManager().bindTexture(SNOW_TEXTURE);
tessellator.startDrawingQuads();
}
final float scroll = ((updateCount & 511) + partialTicks) / 512.0F;
final float driftU = columns.jitterAt(x, z, 0) + age * 0.01F * columns.jitterAt(x, z, 1);
final float driftV = columns.jitterAt(x, z, 2) + age * columns.jitterAt(x, z, 3) * 0.001F;
tessellator.setBrightness((columns.lightAt(world, x, z, lightY) * 3 + 15728880) / 4);
final float scroll = ((updateCount & WeatherInstancedAttribs.SNOW_PERIOD - 1) + partialTicks)
/ WeatherInstancedAttribs.SNOW_PERIOD;
final float driftU = columns.jitterAtSlot(slot, 0) + age * WeatherInstancedAttribs.SNOW_U_DRIFT * columns.jitterAtSlot(slot, 1);
final float driftV = columns.jitterAtSlot(slot, 2) + age * columns.jitterAtSlot(slot, 3) * WeatherInstancedAttribs.SNOW_V_DRIFT;
tessellator.setBrightness(snowBrightness(light));
tessellator.setColorRGBA_F(1.0F, 1.0F, 1.0F,
((1.0F - distance * distance) * 0.3F + 0.5F) * rainStrength);
emit(tessellator, x, z, bottom, top, halfX, halfZ, scroll, driftU, driftV,
cameraX, cameraY, cameraZ);
((1.0F - distance * distance) * WeatherInstancedAttribs.SNOW_FADE + 0.5F) * rainStrength);
emit(tessellator, x, z, bottom, top, halfX, halfZ, scroll, driftU, driftV);
}
quads++;
}
@@ -480,16 +491,16 @@ public final class WeatherRenderer {
drawCalls++;
}
GLSMRasterizer.enableCull();
GLSMBlend.disableBlend();
GLSMDepthStencilAlpha.glAlphaFunc(GL11.GL_GREATER, 0.1F);
mc.entityRenderer.disableLightmap(partialTicks);
frameQuads += quads;
frameDrawCalls += drawCalls;
}
report(quads, drawCalls, startNanos);
private static int snowBrightness(int light) {
return (light * 3 + 15728880) / 4;
}
private static void emit(Tessellator tessellator, int x, int z, int bottom, int top, float halfX,
float halfZ, float scroll, float driftU, float driftV, double cameraX, double cameraY, double cameraZ) {
float halfZ, float scroll, float driftU, float driftV) {
tessellator.setTranslation(-cameraX, -cameraY, -cameraZ);
tessellator.addVertexWithUV(x - halfX + 0.5D, bottom, z - halfZ + 0.5D, driftU, bottom / 4.0F + scroll + driftV);
tessellator.addVertexWithUV(x + halfX + 0.5D, bottom, z + halfZ + 0.5D, 1.0F + driftU, bottom / 4.0F + scroll + driftV);
@@ -498,12 +509,17 @@ public final class WeatherRenderer {
tessellator.setTranslation(0.0D, 0.0D, 0.0D);
}
private static void report(int quads, int drawCalls, long startNanos) {
Tracy.plotInt(P_QUADS, quads);
Tracy.plotInt(P_DRAW_CALLS, drawCalls);
private static void report(long startNanos) {
Tracy.plotInt(P_QUADS, frameQuads);
Tracy.plotInt(P_DRAW_CALLS, frameDrawCalls);
Tracy.plotInt(P_REBUILDS, rebuiltThisFrame ? 1 : 0);
Tracy.plotInt(P_PROVIDER_COLUMNS, providerColumns);
Tracy.plotInt(P_PROVIDER_DRAWS, providerDraws);
Tracy.plotInt(P_PROVIDER_REBUILDS, providerRebuiltThisFrame ? 1 : 0);
Tracy.plotInt(P_LEGACY_HANDLER, legacyHandler ? 1 : 0);
Tracy.plot(P_MICROS, (System.nanoTime() - startNanos) / 1000.0);
rebuiltThisFrame = false;
providerRebuiltThisFrame = false;
}
private static void buildAxisTable() {
@@ -522,14 +538,145 @@ public final class WeatherRenderer {
}
public static void clear() {
VANILLA.release();
PROVIDER.release();
WeatherQuadMesh.delete();
if (instanceBuffer != 0) {
GLSMBuffers.glDeleteBuffers(instanceBuffer);
instanceBuffer = 0;
builtRevision = Integer.MIN_VALUE;
builtX = Integer.MIN_VALUE;
builtY = Integer.MIN_VALUE;
builtZ = Integer.MIN_VALUE;
builtRadius = -1;
builtUpdateCount = Integer.MIN_VALUE;
builtCameraY = Integer.MIN_VALUE;
builtProviderRevision = Integer.MIN_VALUE;
builtProviderDimension = Integer.MIN_VALUE;
builtProviderX = Integer.MIN_VALUE;
builtProviderZ = Integer.MIN_VALUE;
}
private static final class WeatherBatch implements WeatherColumnSink {
private static final int STRIDE = WeatherInstancedAttribs.STRIDE;
private static final int MIN_CAPACITY = 256;
private ByteBuffer data;
private int capacity;
private int count;
private ResourceLocation[] textures = new ResourceLocation[4];
private int[] firsts = new int[4];
private int layers;
private int originX;
private int originZ;
private float source;
private int period;
private float uDriftScale;
private float vDriftScale;
private float fadeScale;
private int buffer;
private int bufferCapacity;
void begin(int originX, int originZ, float source) {
this.originX = originX;
this.originZ = originZ;
this.source = source;
Arrays.fill(textures, 0, layers, null);
layers = 0;
count = 0;
}
void upload() {
if (count == 0) return;
if (buffer == 0) buffer = GLSMBuffers.glGenBuffers();
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, buffer);
if (bufferCapacity < count) {
bufferCapacity = capacity;
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, (long) bufferCapacity * STRIDE, GL15.GL_DYNAMIC_DRAW);
}
data.position(0);
data.limit(count * STRIDE);
GLSMBuffers.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0L, data);
data.clear();
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
int draw(Minecraft mc, DeferredWorldRenderingPipeline deferred) {
int draws = 0;
for (int layer = 0; layer < layers; layer++) {
final int first = firsts[layer];
final int layerCount = (layer + 1 < layers ? firsts[layer + 1] : count) - first;
if (layerCount == 0) continue;
mc.getTextureManager().bindTexture(textures[layer]);
if (deferred != null) deferred.bindInstancedVariant(Instancing.WEATHER);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, buffer);
WeatherInstancedAttribs.pointInstanceAttribs((long) first * STRIDE);
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setCurrentVertexFlags(WeatherQuadMesh.VERTEX_FLAGS);
GLSMDraws.glDrawArraysInstanced(GL11.GL_TRIANGLE_FAN, 0, WeatherQuadMesh.VERTEX_COUNT, layerCount);
draws++;
}
return draws;
}
void release() {
begin(0, 0, 0.0F);
data = null;
capacity = 0;
if (buffer != 0) {
GLSMBuffers.glDeleteBuffers(buffer);
buffer = 0;
}
bufferCapacity = 0;
}
@Override
public void beginLayer(ResourceLocation texture, int scrollPeriod, float uDriftScale, float vDriftScale, float fadeScale) {
if (texture == null) throw new IllegalArgumentException("Weather layer texture must not be null");
if (scrollPeriod < 1 || scrollPeriod > WeatherParams.TICK_WRAP || (scrollPeriod & scrollPeriod - 1) != 0) {
throw new IllegalArgumentException("Weather layer scroll period must be a power of two between 1 and " + WeatherParams.TICK_WRAP);
}
if (!Float.isFinite(uDriftScale) || !Float.isFinite(vDriftScale) || !Float.isFinite(fadeScale)) {
throw new IllegalArgumentException("Weather layer scales must be finite");
}
if (layers == textures.length) {
textures = Arrays.copyOf(textures, layers * 2);
firsts = Arrays.copyOf(firsts, layers * 2);
}
textures[layers] = texture;
firsts[layers] = count;
layers++;
period = scrollPeriod;
this.uDriftScale = uDriftScale;
this.vDriftScale = vDriftScale;
this.fadeScale = fadeScale;
}
@Override
public void column(int x, int z, float bottom, float top, float uBase, float uDrift, float vBase, float vDrift,
int brightness, int scrollOffset, float scrollScale, float r, float g, float b, float a) {
if (layers == 0) throw new IllegalStateException("Weather column written before beginLayer");
if (brightness < 0 || brightness >= 1 << 24) {
throw new IllegalArgumentException("Weather column brightness must be a packed lightmap coordinate below 2^24");
}
put(x, z, bottom, top, uBase, uDrift, vBase, vDrift, brightness, scrollOffset, scrollScale, r, g, b, a);
}
void put(int x, int z, float bottom, float top, float uBase, float uDrift, float vBase, float vDrift, int brightness, int scrollOffset, float scrollScale, float r, float g, float b, float a) {
if (count == capacity) grow();
WeatherInstancedAttribs.putColumn(data, count, x - originX, z - originZ, bottom, top, uBase, uDrift, vBase, vDrift, brightness, scrollOffset, scrollScale, source, r, g, b, a, period, uDriftScale, vDriftScale, fadeScale);
count++;
}
private void grow() {
final int grownCapacity = Math.max(MIN_CAPACITY, capacity * 2);
final ByteBuffer grown = ByteBuffer.allocateDirect(grownCapacity * STRIDE).order(ByteOrder.nativeOrder());
if (count > 0) {
data.position(0);
data.limit(count * STRIDE);
grown.put(data);
grown.clear();
}
data = grown;
capacity = grownCapacity;
}
instanceData = null;
instanceCapacity = 0;
instanceCount = 0;
rainInstances = 0;
}
}
@@ -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();
}
}
@@ -14,12 +14,16 @@ import com.gtnewhorizons.angelica.rendering.celeritas.iris.ContextAwareChunkVert
import com.gtnewhorizons.angelica.rendering.celeritas.threading.RenderPassHelper;
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
import com.gtnewhorizons.angelica.rendering.tesr.SectionBuiltBlockEntity;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.utils.NaturalTextureUtils;
import com.gtnewhorizons.liquidlogged.api.LiquidLoggedAPI;
import com.gtnewhorizons.liquidlogged.api.LiquidLoggedRenderAPI;
import com.prupe.mcpatcher.mal.block.RenderBlocksUtils;
import it.unimi.dsi.fastutil.floats.FloatArrayList;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
import mega.fluidlogged.api.FLBlockAccess;
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
@@ -39,6 +43,7 @@ import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.ReportedException;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.fluids.Fluid;
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildContext;
@@ -254,39 +259,13 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
final boolean canRenderOffThread = !handoff || !isDeferred(deferredMask, x, y, z);
// Check for shader pack override
final BlockRenderLayer override = blockTypeIds != null ? blockTypeIds.get(block) : null;
int deferredPasses = 0;
Material deferredOverride = null;
renderBlockPasses(block, meta, x, y, z, canRenderOffThread, blockTypeIds, deferredBlocks, tessellator, renderBlocks, buffers, buildContext, blockRenderContext, minX, minY, minZ, teMap, currentTick);
for (int pass = 0; pass < AngelicaChunkBuildContext.NUM_PASSES; pass++) {
final boolean canRender;
Material materialOverride = null;
if (override != null) {
// Shader pack override controls both pass and material
canRender = (pass == override.toVanillaPass());
if (canRender) {
materialOverride = buffers.getRenderPassConfiguration().getMaterialForRenderType(override);
}
} else {
// Normal block rendering
canRender = !canRenderOffThread || block.canRenderInPass(pass);
}
if (canRender) {
final boolean isShaderPackOverride = materialOverride != null;
if (!canRenderOffThread) {
deferredPasses |= 1 << pass;
deferredOverride = materialOverride;
continue;
}
renderBlock(block, meta, x, y, z, pass, tessellator, renderBlocks, buffers, buildContext, blockRenderContext, minX, minY, minZ, materialOverride, isShaderPackOverride, teMap, currentTick);
}
}
if (deferredPasses != 0) {
deferredBlocks.add(new DeferredBlock(x, y, z, block, meta, deferredPasses, deferredOverride));
// FluidLogged stores the fluid apart from the block, and this loop only sees the block
final Block loggedFluid = getLoggedFluidBlock(region, x, y, z);
if (loggedFluid != null) {
if (reportingSlice != null) reportingSlice.setRenderingBlock(loggedFluid);
renderBlockPasses(loggedFluid, 0, x, y, z, canRenderOffThread, blockTypeIds, deferredBlocks, tessellator, renderBlocks, buffers, buildContext, blockRenderContext, minX, minY, minZ, teMap, currentTick);
}
if (block.isOpaqueCube()) {
@@ -345,7 +324,11 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
final Block block = region.getBlock(minX + x, minY + y, minZ + z);
if (block != Blocks.air && !canRenderOffThread(block)) {
if (block == Blocks.air) {
continue;
}
final Block loggedFluid = getLoggedFluidBlock(region, minX + x, minY + y, minZ + z);
if (!canRenderOffThread(block) || (loggedFluid != null && !canRenderOffThread(loggedFluid))) {
final int bit = (y << 8) | (z << 4) | x;
mask[bit >>> 6] |= 1L << bit;
any = true;
@@ -361,6 +344,18 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
return (mask[bit >>> 6] & (1L << bit)) != 0;
}
private static Block getLoggedFluidBlock(IBlockAccess region, int x, int y, int z) {
final Fluid fluid;
if (ModStatus.isLiquidLoggedLoaded) {
fluid = LiquidLoggedAPI.getFluid(region, x, y, z);
} else if (ModStatus.isFluidLoggedLoaded && region instanceof FLBlockAccess fluidAccess) {
fluid = fluidAccess.fl$getFluid(x, y, z);
} else {
return null;
}
return fluid != null ? fluid.getBlock() : null;
}
ChunkJobResult<ChunkBuildOutput> captureDeferred(DeferredSectionMesh m) {
final long start = System.nanoTime();
@@ -497,6 +492,43 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
return new ChunkBuildOutput(this.render, renderData, meshes, this.buildTime);
}
private void renderBlockPasses(Block block, int meta, int x, int y, int z, boolean canRenderOffThread, Map<Block, BlockRenderLayer> blockTypeIds, List<DeferredBlock> deferredBlocks, Tessellator tessellator, RenderBlocks renderBlocks, ChunkBuildBuffers buffers, AngelicaChunkBuildContext buildContext, BlockRenderContext blockRenderContext, int originX, int originY, int originZ, NbtConditionalIdMap<Block> teMap, long currentTick) {
// Check for shader pack override
final BlockRenderLayer override = blockTypeIds != null ? blockTypeIds.get(block) : null;
int deferredPasses = 0;
Material deferredOverride = null;
for (int pass = 0; pass < AngelicaChunkBuildContext.NUM_PASSES; pass++) {
final boolean canRender;
Material materialOverride = null;
if (override != null) {
// Shader pack override controls both pass and material
canRender = (pass == override.toVanillaPass());
if (canRender) {
materialOverride = buffers.getRenderPassConfiguration().getMaterialForRenderType(override);
}
} else {
// Normal block rendering
canRender = !canRenderOffThread || block.canRenderInPass(pass);
}
if (canRender) {
final boolean isShaderPackOverride = materialOverride != null;
if (!canRenderOffThread) {
deferredPasses |= 1 << pass;
deferredOverride = materialOverride;
continue;
}
renderBlock(block, meta, x, y, z, pass, tessellator, renderBlocks, buffers, buildContext, blockRenderContext, originX, originY, originZ, materialOverride, isShaderPackOverride, teMap, currentTick);
}
}
if (deferredPasses != 0) {
deferredBlocks.add(new DeferredBlock(x, y, z, block, meta, deferredPasses, deferredOverride));
}
}
protected void renderBlock(Block block, int metadata, int x, int y, int z, int pass, Tessellator tessellator, RenderBlocks renderBlocks, ChunkBuildBuffers buffers, AngelicaChunkBuildContext buildContext, BlockRenderContext blockRenderContext, int originX, int originY, int originZ, Material materialOverride, boolean isShaderPackOverride, NbtConditionalIdMap<Block> teMap, long currentTick) {
final Material passMaterial = resolvePassMaterial(block, pass, buffers, materialOverride);
final int teBlockId = resolveTeBlockId(teMap, renderBlocks.blockAccess, block, metadata, x, y, z, buffers, pass, materialOverride, currentTick);
@@ -587,7 +619,14 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
if (applyNaturalTex) { renderBlocks.uvRotateTop = NaturalTextureUtils.getTopRotation(x, y, z); }
tessellator.startDrawingQuads();
renderBlocks.renderBlockByRenderType(block, x, y, z);
// A block that differs from the cell's own block is the fluid stored in it
final boolean storedFluid = ModStatus.isLiquidLoggedLoaded && renderBlocks.blockAccess.getBlock(x, y, z) != block;
if (storedFluid) LiquidLoggedRenderAPI.beginStoredFluid();
try {
renderBlocks.renderBlockByRenderType(block, x, y, z);
} finally {
if (storedFluid) LiquidLoggedRenderAPI.endStoredFluid();
}
if (applyNaturalTex) { renderBlocks.uvRotateTop = 0; } // Reset
}
@@ -32,6 +32,7 @@ import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ChunkRenderContext;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSection;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSectionCache;
import com.gtnewhorizons.liquidlogged.api.LiquidLoggedBlockAccess;
import cpw.mods.fml.common.Optional;
import mega.fluidlogged.api.FLBlockAccess;
@@ -45,8 +46,11 @@ import mega.fluidlogged.api.FLBlockAccess;
*
* Object pooling should be used to avoid huge allocations as this class contains many large arrays.
*/
@Optional.Interface(iface = "mega.fluidlogged.api.FLBlockAccess", modid = "fluidlogged")
public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
@Optional.InterfaceList({
@Optional.Interface(iface = "mega.fluidlogged.api.FLBlockAccess", modid = "fluidlogged"),
@Optional.Interface(iface = "com.gtnewhorizons.liquidlogged.api.LiquidLoggedBlockAccess", modid = "liquidlogged")
})
public class WorldSlice implements IBlockAccessExtended, FLBlockAccess, LiquidLoggedBlockAccess {
private static final EnumSkyBlock[] LIGHT_TYPES = EnumSkyBlock.values();
@@ -179,7 +183,7 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
this.lightArrays = new NibbleArray[SECTION_TABLE_ARRAY_SIZE][LIGHT_TYPES.length];
this.sectionLightData = new SectionLightData[SECTION_TABLE_ARRAY_SIZE];
if (ModStatus.isFluidLoggedLoaded) {
if (ModStatus.isFluidLoggedLoaded || ModStatus.isLiquidLoggedLoaded) {
this.fluidArrays = new Fluid[SECTION_TABLE_ARRAY_SIZE][];
} else {
this.fluidArrays = null;
@@ -193,7 +197,7 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
Arrays.fill(this.blockArrays[i], Blocks.air);
this.metadataArrays[i] = new int[SECTION_BLOCK_COUNT];
if (ModStatus.isFluidLoggedLoaded && this.fluidArrays != null) {
if (this.fluidArrays != null) {
this.fluidArrays[i] = new Fluid[SECTION_BLOCK_COUNT];
}
}
@@ -239,7 +243,7 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
this.unpackBlockData(this.blockArrays[idx], this.metadataArrays[idx], section, context.getVolume());
if (ModStatus.isFluidLoggedLoaded && this.fluidArrays != null) {
if (this.fluidArrays != null) {
this.unpackFluidLoggedData(this.fluidArrays[idx], section, context.getVolume());
}
@@ -457,6 +461,12 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
return this.fluidArrays[getLocalSectionIndex(relX >> 4, relY >> 4, relZ >> 4)][getLocalBlockIndex(relX & 15, relY & 15, relZ & 15)];
}
// LiquidLogged API support
public @Nullable Fluid liquidlogged$getFluid(int x, int y, int z) {
return fl$getFluid(x, y, z);
}
public void fl$setFluid(int x, int y, int z, @Nullable Fluid fluid) {
throw new UnsupportedOperationException("Cannot set fluids in a world slice. WorldSlice is read-only");
}
@@ -9,6 +9,8 @@ import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import com.gtnewhorizons.angelica.mixins.interfaces.IChunkTileEntityMapHolder;
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
import com.gtnewhorizons.liquidlogged.api.LiquidLoggedAPI;
import com.gtnewhorizons.liquidlogged.api.SectionFluidsView;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import mega.fluidlogged.internal.mixin.hook.FLSubChunk;
@@ -41,6 +43,9 @@ public class ClonedChunkSection {
private SectionLightData sectionLightData;
private final Short2ObjectOpenHashMap<TileEntity> tileEntities;
// Fluids stored in blocks by LiquidLogged, indexed y << 8 | z << 4 | x; null when the section holds none
private Fluid[] liquidLoggedFluids;
private long lastUsedTimestamp = Long.MAX_VALUE;
int refs;
@@ -79,6 +84,10 @@ public class ClonedChunkSection {
this.data.copyFrom(chunk, section);
}
if (ModStatus.isLiquidLoggedLoaded) {
copyLiquidLoggedFluids(section);
}
copyBlockEntities(chunk, pos);
fillBiomeData(chunk);
@@ -130,7 +139,22 @@ public class ClonedChunkSection {
return data.getExtBlockMetadata(x, y, z);
}
private void copyLiquidLoggedFluids(ExtendedBlockStorage section) {
final SectionFluidsView view = LiquidLoggedAPI.getSection(section);
if (view == null || view.isEmpty()) {
this.liquidLoggedFluids = null;
return;
}
if (this.liquidLoggedFluids == null) {
this.liquidLoggedFluids = new Fluid[SECTION_BLOCK_LENGTH * SECTION_BLOCK_LENGTH * SECTION_BLOCK_LENGTH];
}
view.copyTo(this.liquidLoggedFluids);
}
public Fluid getFluid(int x, int y, int z) {
if (ModStatus.isLiquidLoggedLoaded) {
return this.liquidLoggedFluids != null ? this.liquidLoggedFluids[y << 8 | z << 4 | x] : null;
}
if (ModStatus.isFluidLoggedLoaded) {
return ((FLSubChunk) data).fl$getFluid(x, y, z);
}
@@ -17,6 +17,7 @@ import com.gtnewhorizons.angelica.rendering.tesr.EntityMaterials;
import com.gtnewhorizons.angelica.rendering.tesr.GlintCapture;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import com.gtnewhorizons.angelica.rendering.tesr.FlattenedItemLighting;
import com.gtnewhorizons.angelica.rendering.tesr.TemplateBuffer;
import com.gtnewhorizons.angelica.utils.AnimationsRenderUtils;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
@@ -26,6 +27,8 @@ import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.item.ItemStack;
import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.VERTEX_SIZE;
public final class DroppedItemInstancer {
public static final long SKIP = -2L;
@@ -93,7 +96,8 @@ public final class DroppedItemInstancer {
private static boolean glintEligible(ItemStack stack) {
return stack != null && stack.getItem() != null && ModelPartBatcher.INSTANCE.isActive() && !GLStateManager.isRecordingDisplayList()
&& !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(Tessellator.instance);
&& !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(Tessellator.instance)
&& !FlattenedItemLighting.applies();
}
static TesrMaterial material(ItemStack stack, boolean unfilteredAtlas) {
@@ -219,6 +223,29 @@ public final class DroppedItemInstancer {
return material != null ? batchBlock(material, rb, block, meta, brightness) : GLSMDraws.drawCalls;
}
public static boolean drawFlattenedBlock(RenderBlocks rb, Block block, int meta, float brightness) {
final BlockMesh mesh = blockMesh(rb, block, meta, brightness);
final TemplateBuffer template = mesh.template();
if (template == null) return false;
mesh.sprites().markUsed();
final Tessellator tess = Tessellator.instance;
final int ints = template.vertexCount * VERTEX_SIZE;
tess.startDrawing(template.drawMode);
if (tess.rawBuffer == null || tess.rawBufferSize < ints) {
tess.rawBufferSize = Math.max(0x10000, ints);
tess.rawBuffer = new int[tess.rawBufferSize];
}
System.arraycopy(template.data, 0, tess.rawBuffer, 0, ints);
tess.vertexCount = template.vertexCount;
tess.rawBufferIndex = ints;
tess.hasTexture = true;
tess.hasNormals = true;
tess.hasColor = false;
tess.hasBrightness = false;
tess.draw();
return true;
}
private static long itemBlock(ItemStack stack, RenderBlocks rb, Block block, int meta, float brightness, boolean unfilteredAtlas) {
final TesrMaterial material = material(stack, unfilteredAtlas);
if (material == null) {
@@ -236,12 +263,7 @@ public final class DroppedItemInstancer {
static long batchBlock(TesrMaterial material, RenderBlocks rb, Block block, int meta, float brightness) {
final long drawsBefore = GLSMDraws.drawCalls;
if (BatchEligibility.batchingAllowed()) {
BlockMesh mesh = lookupBlock(block, meta);
if (mesh == null) {
mesh = captureBlock(rb, block, meta, brightness);
} else if (mesh.template() != null) {
GLSMCurrentAttribs.glColor4f(mesh.red(), mesh.green(), mesh.blue(), mesh.alpha());
}
final BlockMesh mesh = blockMesh(rb, block, meta, brightness);
if (mesh.template() == null) {
fallback++;
bail(BailReason.TEMPLATE);
@@ -338,6 +360,13 @@ public final class DroppedItemInstancer {
return mesh;
}
private static BlockMesh blockMesh(RenderBlocks rb, Block block, int meta, float brightness) {
final BlockMesh hit = lookupBlock(block, meta);
if (hit == null) return captureBlock(rb, block, meta, brightness);
if (hit.template() != null) GLSMCurrentAttribs.glColor4f(hit.red(), hit.green(), hit.blue(), hit.alpha());
return hit;
}
private static BlockMesh lookupBlock(Block block, int meta) {
blockKey.block = block;
blockKey.meta = meta;
@@ -0,0 +1,39 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.glsm.GLSMLighting;
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.coderbot.iris.shadows.ShadowRenderingState;
import org.joml.Matrix4fc;
public final class FlattenedItemLighting {
static final float FLATNESS_CUTOFF = 0.05f;
private static boolean preLitText;
private FlattenedItemLighting() {}
public static void setPreLitText(boolean preLit) {
preLitText = preLit;
}
static boolean isFlattened(Matrix4fc m) {
final float ax = m.m00(), ay = m.m01(), az = m.m02();
final float bx = m.m10(), by = m.m11(), bz = m.m12();
final float cx = m.m20(), cy = m.m21(), cz = m.m22();
final float bcx = by * cz - bz * cy, bcy = bz * cx - bx * cz, bcz = bx * cy - by * cx;
final float cax = cy * az - cz * ay, cay = cz * ax - cx * az, caz = cx * ay - cy * ax;
final float abx = ay * bz - az * by, aby = az * bx - ax * bz, abz = ax * by - ay * bx;
final float det = ax * bcx + ay * bcy + az * bcz;
final float frob2 = ax * ax + ay * ay + az * az + bx * bx + by * by + bz * bz + cx * cx + cy * cy + cz * cz;
final float adj2 = bcx * bcx + bcy * bcy + bcz * bcz + cax * cax + cay * cay + caz * caz + abx * abx + aby * aby + abz * abz;
if (!(adj2 > 0f) || det == 0f) return false;
return Math.abs(det) * (float) Math.sqrt(frob2) < FLATNESS_CUTOFF * adj2;
}
public static boolean applies() {
return GLStateManager.isMainThread() && !preLitText && TesrBatchRenderer.INSTANCE.inScope() && GLSMLighting.getLightingState().isEnabled() && !GLStateManager.isRecordingDisplayList()
&& TesrShaderPrograms.packActive() && !ShadowRenderingState.areShadowsCurrentlyBeingRendered() && isFlattened(GLSMMatrices.getModelViewMatrix());
}
}
@@ -23,6 +23,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
import com.gtnewhorizons.angelica.glsm.ffp.FFPVertexLighting;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
@@ -850,6 +851,7 @@ public final class ModelPartBatcher {
private enum TessellatorRoute { DRAW_NOW, DRAW_NOW_EXPECTED, QUEUE }
public static int drawTessellator(Tessellator tess) {
if (FlattenedItemLighting.applies() && FFPVertexLighting.bakeColors(tess.rawBuffer, tess.vertexCount, tess.hasNormals, tess.hasColor)) tess.hasColor = true;
switch (INSTANCE.routeTessellatorDraw(tess)) {
case QUEUE -> {
tess.isDrawing = false;
@@ -95,6 +95,10 @@ public final class TesrBatchRenderer {
if (scopeDepth++ == 0) currentScope = ++scopeCounter;
}
public boolean inScope() {
return scopeDepth != 0;
}
public void endScope() {
if (--scopeDepth == 0) currentScope = 0;
}
@@ -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);
}
}
}
@@ -41,6 +41,7 @@ class CoreTransformHelper {
transformer.injectVariable("uniform vec4 iris_WeatherParams0;");
transformer.injectVariable("uniform vec4 iris_WeatherParams1;");
transformer.injectVariable("uniform vec4 iris_WeatherParams2;");
transformer.injectVariable("uniform vec4 iris_WeatherParams3;");
transformer.injectVariable("vec4 iris_Vertex;");
transformer.injectVariable("vec4 iris_Color;");
transformer.injectVariable("vec4 iris_MultiTexCoord0;");
@@ -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);
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff. Show more