Fix Complementary + Euphoria Patches ULTRA on SDL-GPU (#2195)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-10-02 22:40:06 -07:00
1 parent 37afddab5d
commit bdcb4d1882
6 files changed
+141 -4

No files matched your search

@@ -28,6 +28,15 @@ public final class GlslVulkanPreprocess {
public static final String SAMPLER_RENAMED = "angelica_sampler_renamed";
public static final String RESERVED_RENAMED_PREFIX = "angelica_renamed_";
private static final Set<String> TARGET_RESERVED = Set.of(
"try", "catch", "throw", "delete", "operator", "private", "protected", "friend", "virtual", "explicit",
"mutable", "typename", "typeid", "register", "signed", "char", "auto", "nullptr", "constexpr", "decltype",
"noexcept", "alignas", "alignof", "static_assert", "thread_local", "wchar_t", "char16_t", "char32_t",
"const_cast", "dynamic_cast", "reinterpret_cast", "static_cast", "export",
"and", "or", "xor", "bitand", "bitor", "compl", "and_eq", "or_eq", "xor_eq", "not_eq");
public static final String SAMPLERLESS_EXTENSION = "#extension GL_EXT_samplerless_texture_functions : require";
private static final int MAX_VS_INPUT_LOCATIONS = 16;
@@ -211,7 +220,9 @@ public final class GlslVulkanPreprocess {
case "sampler" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), SAMPLER_RENAMED));
case "gl_VertexID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_VertexIndex"));
case "gl_InstanceID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_InstanceIndex"));
default -> {}
default -> {
if (TARGET_RESERVED.contains(text)) edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), RESERVED_RENAMED_PREFIX + text));
}
}
}
}, root);
@@ -297,6 +297,30 @@ class SpirvShaderTranslatorTest {
assertTrue(es.contains("sampler2D"), "sampler2D type must not be touched by the rename");
}
@Test
void cppKeywordsAsIdentifiers_areRenamed() {
final String src = """
#version 330 core
in vec2 texCoord;
out vec4 fragColor;
void main() {
vec4 try = vec4(texCoord, 0.0, 1.0);
float catch = try.x;
bvec2 flipped = not(lessThan(texCoord, vec2(0.5)));
fragColor = flipped.x ? try : vec4(catch);
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "reserved.frag");
dumpOnFailure("reserved.frag", src, es);
assertNotNull(es);
assertTrue(es.contains("angelica_renamed_try"), es);
assertTrue(es.contains("angelica_renamed_catch"), es);
assertTrue(es.contains("not("), "the not() builtin must not be touched by the rename\n" + es);
assertFalse(es.contains("angelica_renamed_not"), es);
}
@Test
void uniformBool_multiDeclarator_allRestored() {
final String src = """
@@ -106,6 +106,20 @@ class PrewarmTreeHandoffEquivalenceTest {
GL20.GL_FRAGMENT_SHADER);
}
@Test
void cppKeywordLocal() {
assertHandoffEquivalent("cpp-keyword-local",
"layout(location = 0) in vec4 v_Color;\n"
+ "layout(location = 0) out vec4 fragColor;\n"
+ "vec4 trace(vec4 c) {\n"
+ " vec4 try = c * 0.5;\n"
+ " if (try.a > -0.5) return try;\n"
+ " return c;\n"
+ "}\n"
+ "void main() { fragColor = trace(v_Color); }\n",
GL20.GL_FRAGMENT_SHADER);
}
@Test
void geometryStage() {
assertHandoffEquivalent("geometry",
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu.shader.msl;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
import com.gtnewhorizons.angelica.glsm.shader.ShaderCacheIO;
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
@@ -28,6 +29,7 @@ import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -215,6 +217,38 @@ class MslCrossCompileTest {
}
}
private static final String CPP_KEYWORD_LOCAL_GLSL = """
#version 460 core
layout(location = 0) in vec4 v_Color;
layout(location = 0) out vec4 fragColor;
vec4 trace(vec4 c) {
vec4 try = c * 0.5;
if (try.a > -0.5) return try;
return c;
}
void main() { fragColor = trace(v_Color); }
""";
@Test
void cppKeywordLocalIsRenamedBeforeMsl() {
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(CPP_KEYWORD_LOCAL_GLSL, GL20.GL_FRAGMENT_SHADER, "test", true);
assertNotNull(pre);
final ByteBuffer spirv = compile(pre.rewrittenSource(), Shaderc.shaderc_fragment_shader);
try {
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_FRAGMENT_SHADER);
final MslCrossCompile.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER);
try {
final String msl = decode(out.code());
assertTrue(msl.contains("angelica_renamed_try"), "the local must keep a renamed identifier:\n" + msl);
assertFalse(Pattern.compile("\\btry\\b").matcher(msl).find(), "try is reserved in MSL:\n" + msl);
} finally {
MemoryUtil.memFree(out.code());
}
} finally {
MemoryUtil.memFree(spirv);
}
}
@Test
void crossCompileFreesNativeMemory() {
for (int i = 0; i < 200; i++) {
@@ -559,14 +559,13 @@ public class ShaderTransformer {
artifactsOut.put(shaderType, new StageArtifact(treeHolder[0], formattedShader.length() - printedBody.length()));
}
}
maybeExtractRwImageStores(result, patchType);
if (BackendManager.RENDER_BACKEND.isSDLGPU()) extractRwImageStores(result, patchType, artifactsOut);
watch.stop();
Iris.logger.info("[Load #{}] Transformed shader for {} in {}", Iris.getShaderPackLoadId(), patchType.name(), watch);
return result;
}
private static void maybeExtractRwImageStores(EnumMap<PatchShaderType, String> result, Patch patchType) {
if (!BackendManager.RENDER_BACKEND.isSDLGPU()) return;
static void extractRwImageStores(EnumMap<PatchShaderType, String> result, Patch patchType, EnumMap<PatchShaderType, StageArtifact> artifactsOut) {
if (result.containsKey(PatchShaderType.COMPUTE)) return;
final String vsh = result.get(PatchShaderType.VERTEX);
@@ -580,10 +579,12 @@ public class ShaderTransformer {
if (vshResult != null) {
result.put(PatchShaderType.VERTEX, vshResult.strippedSource());
result.put(PatchShaderType.COMPUTE, vshResult.computeSource());
if (artifactsOut != null) artifactsOut.remove(PatchShaderType.VERTEX);
Iris.logger.info("[RwImageStoreExtractor] Extracted compute pre-pass for {} (mode={}, written={})", patchType.name(), vshResult.mode(), vshResult.writtenImages());
} else if (fshResult != null) {
result.put(PatchShaderType.FRAGMENT, fshResult.strippedSource());
result.put(PatchShaderType.COMPUTE, fshResult.computeSource());
if (artifactsOut != null) artifactsOut.remove(PatchShaderType.FRAGMENT);
Iris.logger.info("[RwImageStoreExtractor] Extracted compute pre-pass for {} (mode={}, written={})", patchType.name(), fshResult.mode(), fshResult.writtenImages());
}
}
@@ -1,8 +1,11 @@
package net.coderbot.iris.pipeline.transform;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import net.coderbot.iris.pipeline.transform.parameter.TextureStageParameters;
import net.coderbot.iris.shaderpack.texture.TextureStage;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -11,6 +14,7 @@ import org.taumc.glsl.ShaderParser;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@@ -219,6 +223,55 @@ class ShaderTransformerTest {
assertTrue(out.contains("gtexture"), "the pack's own sampler name must survive off SDL-GPU\n\n" + out);
}
private static final String IMAGE_STORE_FRAGMENT = String.join("\n",
"#version 330 core",
"layout(r16ui) writeonly uniform uimage3D voxel_img;",
"in vec2 uv;",
"void main(){",
" imageStore(voxel_img, ivec3(0), uvec4(1u, 0u, 0u, 0u));",
" gl_FragColor = vec4(uv, 0.0, 1.0);",
"}",
"");
private static final String PLAIN_VERTEX = "#version 330 core\nout vec2 uv;\nvoid main(){ uv = vec2(0.5); gl_Position = vec4(0.0); }";
@Test
void extractedStageDropsItsArtifact() {
TransformPatcher.clearCache();
ShaderTransformer.clearCache();
final EnumMap<PatchShaderType, ShaderTransformer.StageArtifact> artifacts = new EnumMap<>(PatchShaderType.class);
final Map<PatchShaderType, String> out = ShaderTransformer.transform(PLAIN_VERTEX, null, null, null, IMAGE_STORE_FRAGMENT, new TextureStageParameters(Patch.COMPOSITE, TextureStage.COMPOSITE_AND_FINAL, null), artifacts);
assertNotNull(out);
assertTrue(artifacts.containsKey(PatchShaderType.VERTEX) && artifacts.containsKey(PatchShaderType.FRAGMENT), "fixture must start with both artifacts");
final EnumMap<PatchShaderType, String> result = new EnumMap<>(out);
final String fragmentBefore = result.get(PatchShaderType.FRAGMENT);
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, artifacts);
assertNotNull(result.get(PatchShaderType.COMPUTE), "fixture must actually exercise the extraction");
assertFalse(fragmentBefore.equals(result.get(PatchShaderType.FRAGMENT)), "fixture must actually rewrite the fragment stage");
assertFalse(artifacts.containsKey(PatchShaderType.FRAGMENT), "a rewritten stage must not keep the tree of its old text");
assertTrue(artifacts.containsKey(PatchShaderType.VERTEX), "an untouched stage keeps its artifact");
}
@Test
void survivingArtifactsMatchTheirSource() {
TransformPatcher.clearCache();
ShaderTransformer.clearCache();
final EnumMap<PatchShaderType, ShaderTransformer.StageArtifact> artifacts = new EnumMap<>(PatchShaderType.class);
final Map<PatchShaderType, String> out = ShaderTransformer.transform(PLAIN_VERTEX, null, null, null, IMAGE_STORE_FRAGMENT, new TextureStageParameters(Patch.COMPOSITE, TextureStage.COMPOSITE_AND_FINAL, null), artifacts);
assertNotNull(out);
final EnumMap<PatchShaderType, String> result = new EnumMap<>(out);
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, artifacts);
assertFalse(artifacts.isEmpty(), "fixture must leave at least one artifact to check");
for (Map.Entry<PatchShaderType, ShaderTransformer.StageArtifact> e : artifacts.entrySet()) {
final String body = result.get(e.getKey()).substring(e.getValue().headerLen());
assertEquals(GlslTransformUtils.getFormattedShaderRebased(e.getValue().tree(), ""), body, "artifact tree must describe the emitted " + e.getKey() + " body");
}
}
@Test
void transformIsDeterministicUnderConcurrency() throws ExecutionException, InterruptedException {
TransformPatcher.clearCache();