|
|
|
@@ -26,9 +26,11 @@ import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.EnumMap;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Objects;
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
import java.util.function.BooleanSupplier;
|
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
@@ -38,33 +40,40 @@ public class ShaderTransformer {
|
|
|
|
|
private static final Pattern inOutVaryingPattern = Pattern.compile("(?m)^(\\s*(?:(?:flat|smooth|noperspective)\\s+)?)(in|out)(\\s+)");
|
|
|
|
|
private static final Pattern inPattern = Pattern.compile("(?m)^(\\s*(?:(?:flat|smooth|noperspective)\\s+)?)(in)(\\s+)");
|
|
|
|
|
private static final Pattern outPattern = Pattern.compile("(?m)^(\\s*(?:(?:flat|smooth|noperspective)\\s+)?)(out)(\\s+)");
|
|
|
|
|
private static final Pattern texturePattern = Pattern.compile("\\btexture\\s*\\(|(\\btexture\\b)");
|
|
|
|
|
private static final Pattern unsignedSuffixPattern = Pattern.compile("(\\b(?:\\d+|0[xX][0-9a-fA-F]+))[uU]\\b");
|
|
|
|
|
|
|
|
|
|
private static final int CACHE_SIZE = 100;
|
|
|
|
|
private static final Object2ObjectLinkedOpenHashMap<TransformKey<?>, Map<PatchShaderType, String>> shaderTransformationCache = new Object2ObjectLinkedOpenHashMap<>();
|
|
|
|
|
private static final boolean useCache = true;
|
|
|
|
|
|
|
|
|
|
// Track logged negotiations to avoid spam - cleared on shader pack reload
|
|
|
|
|
private static final Set<String> loggedNegotiations = new HashSet<>();
|
|
|
|
|
|
|
|
|
|
public static void clearCache() {
|
|
|
|
|
synchronized (shaderTransformationCache) {
|
|
|
|
|
shaderTransformationCache.clear();
|
|
|
|
|
}
|
|
|
|
|
loggedNegotiations.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* These are words which need to be renamed by iris if a shader uses them, regardless o the GLSL version.
|
|
|
|
|
* The words will get caught and renamed to iris_renamed_$WORD
|
|
|
|
|
*/
|
|
|
|
|
private static final List<String> fullReservedWords = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This does the same thing as fullReservedWords, but based on a maximum GLSL version. As an example
|
|
|
|
|
* if something was register to 400 here, it would get applied on any version below 400.
|
|
|
|
|
*/
|
|
|
|
|
private static final Map<Integer, List<String>> versionedReservedWords = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
static {
|
|
|
|
|
// texture seems to be reserved by some drivers but not others, however this is not actually reserved by the GLSL spec
|
|
|
|
|
fullReservedWords.add("texture");
|
|
|
|
|
private static String replaceTexture(String input) {
|
|
|
|
|
final var matcher = texturePattern.matcher(input);
|
|
|
|
|
final StringBuilder builder = new StringBuilder();
|
|
|
|
|
while (matcher.find()) {
|
|
|
|
|
if (matcher.group(1) != null) {
|
|
|
|
|
matcher.appendReplacement(builder, "iris_renamed_texture");
|
|
|
|
|
} else {
|
|
|
|
|
matcher.appendReplacement(builder, Matcher.quoteReplacement(matcher.group(0)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
matcher.appendTail(builder);
|
|
|
|
|
return builder.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static {
|
|
|
|
|
// sample was added as a keyword in GLSL 400, many shaders use it
|
|
|
|
|
versionedReservedWords.put(400, List.of("sample"));
|
|
|
|
|
}
|
|
|
|
@@ -72,14 +81,160 @@ public class ShaderTransformer {
|
|
|
|
|
private record VersionRequirement(String keyword, int minVersion, BooleanSupplier supported) {}
|
|
|
|
|
|
|
|
|
|
// Sorted descending by minVersion for early exit in getRequiredVersion
|
|
|
|
|
|
|
|
|
|
private static final VersionRequirement[] VERSION_REQUIREMENTS = {
|
|
|
|
|
new VersionRequirement("std430", 430, RenderSystem::supportsSSBO),
|
|
|
|
|
new VersionRequirement("iimage", 420, RenderSystem::supportsImageLoadStore),
|
|
|
|
|
new VersionRequirement("uimage", 420, RenderSystem::supportsImageLoadStore),
|
|
|
|
|
new VersionRequirement("imageLoad", 420, RenderSystem::supportsImageLoadStore),
|
|
|
|
|
new VersionRequirement("imageStore", 420, RenderSystem::supportsImageLoadStore)
|
|
|
|
|
new VersionRequirement("imageStore", 420, RenderSystem::supportsImageLoadStore),
|
|
|
|
|
|
|
|
|
|
new VersionRequirement("uint", 130, () -> RenderSystem.getMaxGlslVersion() >= 130),
|
|
|
|
|
new VersionRequirement("uvec2", 130, () -> RenderSystem.getMaxGlslVersion() >= 130),
|
|
|
|
|
new VersionRequirement("uvec3", 130, () -> RenderSystem.getMaxGlslVersion() >= 130),
|
|
|
|
|
new VersionRequirement("uvec4", 130, () -> RenderSystem.getMaxGlslVersion() >= 130),
|
|
|
|
|
new VersionRequirement("flat", 130, () -> RenderSystem.getMaxGlslVersion() >= 130),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private record DowngradeRule(
|
|
|
|
|
int fromVersion,
|
|
|
|
|
int toVersion,
|
|
|
|
|
BooleanSupplier supported,
|
|
|
|
|
List<String> extensions,
|
|
|
|
|
Map<String, String> defines,
|
|
|
|
|
List<String> preprocessorDefines,
|
|
|
|
|
boolean convertStorageQualifiers
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
private static final DowngradeRule[] DOWNGRADE_RULES = {
|
|
|
|
|
new DowngradeRule(130, 120,
|
|
|
|
|
RenderSystem::supportsGpuShader4,
|
|
|
|
|
List.of("GL_EXT_gpu_shader4"),
|
|
|
|
|
Map.of("texture", "texture2D"),
|
|
|
|
|
List.of(
|
|
|
|
|
// Preprocessor defines
|
|
|
|
|
"#define uint int",
|
|
|
|
|
"#define uvec2 ivec2",
|
|
|
|
|
"#define uvec3 ivec3",
|
|
|
|
|
"#define uvec4 ivec4",
|
|
|
|
|
"#define isnan(x) ((x) != (x))",
|
|
|
|
|
"#define isinf(x) ((x) == (1.0/0.0) || (x) == (-1.0/0.0))",
|
|
|
|
|
"#define trunc(x) (sign(x) * floor(abs(x)))",
|
|
|
|
|
"#define round(x) (floor((x) + 0.5))",
|
|
|
|
|
"#define texelFetch texelFetch2D",
|
|
|
|
|
"#define textureSize textureSize2D",
|
|
|
|
|
"#define modf iris_modf",
|
|
|
|
|
// Function definitions
|
|
|
|
|
"GLSL_FUNC:float iris_modf(float x, out float i) { i = sign(x) * floor(abs(x)); return x - i; }",
|
|
|
|
|
"GLSL_FUNC:vec2 iris_modf(vec2 x, out vec2 i) { i = sign(x) * floor(abs(x)); return x - i; }",
|
|
|
|
|
"GLSL_FUNC:vec3 iris_modf(vec3 x, out vec3 i) { i = sign(x) * floor(abs(x)); return x - i; }",
|
|
|
|
|
"GLSL_FUNC:vec4 iris_modf(vec4 x, out vec4 i) { i = sign(x) * floor(abs(x)); return x - i; }",
|
|
|
|
|
"GLSL_FUNC:vec2 mix(vec2 a, vec2 b, bvec2 sel) { return vec2(sel.x ? b.x : a.x, sel.y ? b.y : a.y); }",
|
|
|
|
|
"GLSL_FUNC:vec3 mix(vec3 a, vec3 b, bvec3 sel) { return vec3(sel.x ? b.x : a.x, sel.y ? b.y : a.y, sel.z ? b.z : a.z); }",
|
|
|
|
|
"GLSL_FUNC:vec4 mix(vec4 a, vec4 b, bvec4 sel) { return vec4(sel.x ? b.x : a.x, sel.y ? b.y : a.y, sel.z ? b.z : a.z, sel.w ? b.w : a.w); }",
|
|
|
|
|
"GLSL_FUNC:bool any(bool b) { return b; }",
|
|
|
|
|
"GLSL_FUNC:bool all(bool b) { return b; }"
|
|
|
|
|
),
|
|
|
|
|
true
|
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
record NegotiationResult(
|
|
|
|
|
int targetVersion,
|
|
|
|
|
String profile,
|
|
|
|
|
List<String> extensions,
|
|
|
|
|
Map<String, String> defines,
|
|
|
|
|
List<String> preprocessorDefines,
|
|
|
|
|
boolean convertStorageQualifiers,
|
|
|
|
|
String error
|
|
|
|
|
) {
|
|
|
|
|
static NegotiationResult success(int targetVersion, String profile, List<String> extensions, Map<String, String> defines, List<String> preprocessorDefines, boolean convertStorageQualifiers) {
|
|
|
|
|
return new NegotiationResult(targetVersion, profile, extensions, defines, preprocessorDefines, convertStorageQualifiers, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static NegotiationResult error(String message) {
|
|
|
|
|
return new NegotiationResult(-1, "", List.of(), Map.of(), List.of(), false, message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static NegotiationResult noop(int version, String profile) {
|
|
|
|
|
return new NegotiationResult(version, profile, List.of(), Map.of(), List.of(), false, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
boolean isError() { return error != null; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static int getStageMinimumVersion(PatchShaderType stage) {
|
|
|
|
|
return switch (stage) {
|
|
|
|
|
case COMPUTE -> 330;
|
|
|
|
|
case TESS_CONTROL, TESS_EVAL -> 400;
|
|
|
|
|
case GEOMETRY -> 150;
|
|
|
|
|
default -> 110;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static NegotiationResult negotiateVersion(int effectiveVersion, PatchShaderType stage) {
|
|
|
|
|
final int maxGlsl = RenderSystem.getMaxGlslVersion();
|
|
|
|
|
|
|
|
|
|
if (effectiveVersion <= maxGlsl) {
|
|
|
|
|
if (effectiveVersion <= 120 && RenderSystem.supportsGpuShader4()) {
|
|
|
|
|
final String profile = "";
|
|
|
|
|
for (DowngradeRule rule : DOWNGRADE_RULES) {
|
|
|
|
|
if (rule.fromVersion == 130 && rule.toVersion == 120) {
|
|
|
|
|
final List<String> definesOnly = rule.preprocessorDefines.stream().filter(d -> !d.startsWith("GLSL_FUNC:")).toList();
|
|
|
|
|
return NegotiationResult.success(effectiveVersion, profile, rule.extensions, rule.defines, definesOnly, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return NegotiationResult.success(effectiveVersion, profile, List.of("GL_EXT_gpu_shader4"), Map.of(), List.of(), true);
|
|
|
|
|
}
|
|
|
|
|
return NegotiationResult.noop(effectiveVersion, effectiveVersion >= 150 ? "compatibility" : "");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final int stageMin = getStageMinimumVersion(stage);
|
|
|
|
|
if (maxGlsl < stageMin) {
|
|
|
|
|
return NegotiationResult.error("Hardware GLSL " + maxGlsl + " below stage minimum " + stageMin + " for " + stage.name());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Walk downgrade rules from effectiveVersion toward maxGlsl
|
|
|
|
|
final List<String> accExtensions = new ArrayList<>();
|
|
|
|
|
final Map<String, String> accDefines = new HashMap<>();
|
|
|
|
|
final List<String> accPreprocessorDefines = new ArrayList<>();
|
|
|
|
|
boolean accConvertQualifiers = false;
|
|
|
|
|
int currentVersion = effectiveVersion;
|
|
|
|
|
|
|
|
|
|
while (currentVersion > maxGlsl) {
|
|
|
|
|
DowngradeRule matched = null;
|
|
|
|
|
for (DowngradeRule rule : DOWNGRADE_RULES) {
|
|
|
|
|
if (rule.fromVersion == currentVersion) {
|
|
|
|
|
matched = rule;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (matched == null) {
|
|
|
|
|
return NegotiationResult.error("No downgrade rule from GLSL " + currentVersion + " (hardware max: " + maxGlsl + ", shader requires: " + effectiveVersion + ")");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify rule's required extensions are supported
|
|
|
|
|
if (!matched.supported.getAsBoolean()) {
|
|
|
|
|
return NegotiationResult.error("Downgrade from " + matched.fromVersion + " to " + matched.toVersion + " requires extensions " + matched.extensions + " which are not supported");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
accExtensions.addAll(matched.extensions);
|
|
|
|
|
accDefines.putAll(matched.defines);
|
|
|
|
|
accPreprocessorDefines.addAll(matched.preprocessorDefines);
|
|
|
|
|
accConvertQualifiers |= matched.convertStorageQualifiers;
|
|
|
|
|
currentVersion = matched.toVersion;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (currentVersion < stageMin) {
|
|
|
|
|
return NegotiationResult.error("Downgrade reached GLSL " + currentVersion + " which is below stage minimum " + stageMin + " for " + stage.name());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final String profile = currentVersion >= 150 ? "compatibility" : "";
|
|
|
|
|
return NegotiationResult.success(currentVersion, profile, accExtensions, accDefines, accPreprocessorDefines, accConvertQualifiers);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Pattern hoistPattern;
|
|
|
|
|
private static Object2IntMap<String> keywordToVersion;
|
|
|
|
|
private static int maxSupportedHoistVersion;
|
|
|
|
@@ -93,7 +248,7 @@ public class ShaderTransformer {
|
|
|
|
|
if (req.supported.getAsBoolean()) {
|
|
|
|
|
if (!patternBuilder.isEmpty()) patternBuilder.append('|');
|
|
|
|
|
|
|
|
|
|
patternBuilder.append(Pattern.quote(req.keyword));
|
|
|
|
|
patternBuilder.append("\\b").append(Pattern.quote(req.keyword)).append("\\b");
|
|
|
|
|
versionMap.put(req.keyword, req.minVersion);
|
|
|
|
|
maxVersion = Math.max(maxVersion, req.minVersion);
|
|
|
|
|
}
|
|
|
|
@@ -207,12 +362,12 @@ public class ShaderTransformer {
|
|
|
|
|
} else {
|
|
|
|
|
Map<PatchShaderType, String> result;
|
|
|
|
|
|
|
|
|
|
var patchType = parameters.patch;
|
|
|
|
|
final var patchType = parameters.patch;
|
|
|
|
|
|
|
|
|
|
EnumMap<PatchShaderType, String> inputs = new EnumMap<>(PatchShaderType.class);
|
|
|
|
|
inputs.put(PatchShaderType.COMPUTE, compute);
|
|
|
|
|
|
|
|
|
|
var key = new TransformKey<>(patchType, inputs, parameters);
|
|
|
|
|
final var key = new TransformKey<>(patchType, inputs, parameters);
|
|
|
|
|
|
|
|
|
|
synchronized (shaderTransformationCache) {
|
|
|
|
|
result = shaderTransformationCache.getAndMoveToLast(key);
|
|
|
|
@@ -223,7 +378,7 @@ public class ShaderTransformer {
|
|
|
|
|
parameters.type = null;
|
|
|
|
|
synchronized (shaderTransformationCache) {
|
|
|
|
|
// Double-check in case another thread added it while we were transforming
|
|
|
|
|
Map<PatchShaderType, String> existing = shaderTransformationCache.getAndMoveToLast(key);
|
|
|
|
|
final Map<PatchShaderType, String> existing = shaderTransformationCache.getAndMoveToLast(key);
|
|
|
|
|
if (existing != null) {
|
|
|
|
|
return existing;
|
|
|
|
|
}
|
|
|
|
@@ -239,13 +394,13 @@ public class ShaderTransformer {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static <P extends Parameters> Map<PatchShaderType, String> transformComputeInternal(String compute, Patch patchType, P parameters) {
|
|
|
|
|
EnumMap<PatchShaderType, String> result = new EnumMap<>(PatchShaderType.class);
|
|
|
|
|
final EnumMap<PatchShaderType, String> result = new EnumMap<>(PatchShaderType.class);
|
|
|
|
|
|
|
|
|
|
Stopwatch watch = Stopwatch.createStarted();
|
|
|
|
|
final Stopwatch watch = Stopwatch.createStarted();
|
|
|
|
|
|
|
|
|
|
parameters.type = ShaderType.COMPUTE;
|
|
|
|
|
|
|
|
|
|
Matcher matcher = versionPattern.matcher(compute);
|
|
|
|
|
final Matcher matcher = versionPattern.matcher(compute);
|
|
|
|
|
if (!matcher.find()) {
|
|
|
|
|
throw new IllegalArgumentException("No #version directive found in compute shader source code!");
|
|
|
|
|
}
|
|
|
|
@@ -254,7 +409,7 @@ public class ShaderTransformer {
|
|
|
|
|
int versionInt = Integer.parseInt(versionString);
|
|
|
|
|
|
|
|
|
|
// Check if shader uses features requiring a higher GLSL version
|
|
|
|
|
int requiredVersion = getRequiredVersion(compute, versionInt);
|
|
|
|
|
final int requiredVersion = getRequiredVersion(compute, versionInt);
|
|
|
|
|
if (requiredVersion > versionInt) {
|
|
|
|
|
Iris.logger.debug("Compute shader requires GLSL {} for detected features, hoisting from {}", requiredVersion, versionInt);
|
|
|
|
|
versionInt = requiredVersion;
|
|
|
|
@@ -266,33 +421,40 @@ public class ShaderTransformer {
|
|
|
|
|
versionString = "330";
|
|
|
|
|
versionInt = 330;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Negotiate version downgrade if needed
|
|
|
|
|
final NegotiationResult negotiation = negotiateVersion(versionInt, PatchShaderType.COMPUTE);
|
|
|
|
|
if (negotiation.isError()) {
|
|
|
|
|
throw new RuntimeException("Compute shader version negotiation failed: " + negotiation.error());
|
|
|
|
|
}
|
|
|
|
|
if (negotiation.targetVersion() != versionInt) {
|
|
|
|
|
Iris.logger.debug("Negotiated compute shader from GLSL {} to {}", versionInt, negotiation.targetVersion());
|
|
|
|
|
versionInt = negotiation.targetVersion();
|
|
|
|
|
versionString = String.valueOf(versionInt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String profileString = "#version " + versionString + " core\n";
|
|
|
|
|
|
|
|
|
|
// Rename reserved words
|
|
|
|
|
String input = compute;
|
|
|
|
|
for (String reservedWord : fullReservedWords) {
|
|
|
|
|
String newName = "iris_renamed_" + reservedWord;
|
|
|
|
|
input = input.replaceAll("\\b" + reservedWord + "\\b", newName);
|
|
|
|
|
}
|
|
|
|
|
String input = replaceTexture(compute);
|
|
|
|
|
for (int version : versionedReservedWords.keySet()) {
|
|
|
|
|
if (versionInt < version) {
|
|
|
|
|
for (String reservedWord : versionedReservedWords.get(version)) {
|
|
|
|
|
String newName = "iris_renamed_" + reservedWord;
|
|
|
|
|
final String newName = "iris_renamed_" + reservedWord;
|
|
|
|
|
input = input.replaceAll("\\b" + reservedWord + "\\b", newName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var parsedShader = ShaderParser.parseShader(input);
|
|
|
|
|
var transformer = new Transformer(parsedShader.full());
|
|
|
|
|
final var parsedShader = ShaderParser.parseShader(input);
|
|
|
|
|
final var transformer = new Transformer(parsedShader.full());
|
|
|
|
|
|
|
|
|
|
doTransform(transformer, patchType, parameters, "core", versionInt);
|
|
|
|
|
|
|
|
|
|
// Extract extensions
|
|
|
|
|
var extensions = versionPattern.matcher(getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
|
|
|
|
String header = profileString + (extensions.isEmpty() ? "" : "\n" + extensions);
|
|
|
|
|
final var extensions = versionPattern.matcher(getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
|
|
|
|
|
|
|
|
|
final String finalHeader = header;
|
|
|
|
|
final String finalHeader = profileString + (extensions.isEmpty() ? "" : "\n" + extensions);
|
|
|
|
|
final StringBuilder formattedShaderBuilder = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
transformer.mutateTree(tree -> {
|
|
|
|
@@ -313,13 +475,13 @@ public class ShaderTransformer {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static <P extends Parameters> Map<PatchShaderType, String> transformInternal(EnumMap<PatchShaderType, String> inputs, Patch patchType, P parameters) {
|
|
|
|
|
EnumMap<PatchShaderType, String> result = new EnumMap<>(PatchShaderType.class);
|
|
|
|
|
EnumMap<PatchShaderType, Transformer> types = new EnumMap<>(PatchShaderType.class);
|
|
|
|
|
EnumMap<PatchShaderType, String> prepatched = new EnumMap<>(PatchShaderType.class);
|
|
|
|
|
List<Transformer> textureLodExtensionPatches = new ArrayList<>();
|
|
|
|
|
List<Transformer> hacky120Patches = new ArrayList<>();
|
|
|
|
|
final EnumMap<PatchShaderType, String> result = new EnumMap<>(PatchShaderType.class);
|
|
|
|
|
final EnumMap<PatchShaderType, Transformer> types = new EnumMap<>(PatchShaderType.class);
|
|
|
|
|
final EnumMap<PatchShaderType, String> prepatched = new EnumMap<>(PatchShaderType.class);
|
|
|
|
|
final EnumMap<PatchShaderType, NegotiationResult> negotiations = new EnumMap<>(PatchShaderType.class);
|
|
|
|
|
final EnumMap<PatchShaderType, Boolean> needsTextureLodExtension = new EnumMap<>(PatchShaderType.class);
|
|
|
|
|
|
|
|
|
|
Stopwatch watch = Stopwatch.createStarted();
|
|
|
|
|
final Stopwatch watch = Stopwatch.createStarted();
|
|
|
|
|
|
|
|
|
|
for (PatchShaderType type : PatchShaderType.VALUES) {
|
|
|
|
|
parameters.type = type.glShaderType;
|
|
|
|
@@ -329,7 +491,7 @@ public class ShaderTransformer {
|
|
|
|
|
|
|
|
|
|
String input = inputs.get(type);
|
|
|
|
|
|
|
|
|
|
Matcher matcher = versionPattern.matcher(input);
|
|
|
|
|
final Matcher matcher = versionPattern.matcher(input);
|
|
|
|
|
if (!matcher.find()) {
|
|
|
|
|
throw new IllegalArgumentException("No #version directive found in source code!");
|
|
|
|
|
}
|
|
|
|
@@ -342,41 +504,56 @@ public class ShaderTransformer {
|
|
|
|
|
String profile = "";
|
|
|
|
|
int versionInt = Integer.parseInt(versionString);
|
|
|
|
|
|
|
|
|
|
// Check if shader uses features requiring a higher GLSL version
|
|
|
|
|
int requiredVersion = getRequiredVersion(input, versionInt);
|
|
|
|
|
// Include celeritas header in scan — it's injected post-negotiation but contains uint/uvec3
|
|
|
|
|
final String scanSource = (patchType == Patch.CELERITAS_TERRAIN && type == PatchShaderType.VERTEX) ? input + computeCeleritasHeader() : input;
|
|
|
|
|
final int requiredVersion = getRequiredVersion(scanSource, versionInt);
|
|
|
|
|
if (requiredVersion > versionInt) {
|
|
|
|
|
Iris.logger.debug("Shader requires GLSL {} for detected features, hoisting from {}", requiredVersion, versionInt);
|
|
|
|
|
versionInt = requiredVersion;
|
|
|
|
|
versionString = String.valueOf(versionInt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (versionInt >= 150) {
|
|
|
|
|
profile = matcher.group(2);
|
|
|
|
|
if (profile == null) {
|
|
|
|
|
profile = "compatibility";
|
|
|
|
|
}
|
|
|
|
|
// Negotiate version downgrade if needed
|
|
|
|
|
final NegotiationResult negotiation = negotiateVersion(versionInt, type);
|
|
|
|
|
if (negotiation.isError()) {
|
|
|
|
|
throw new RuntimeException("Shader version negotiation failed for " + type.name() + ": " + negotiation.error());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String profileString = "#version " + versionString + " " + profile + "\n";
|
|
|
|
|
if (negotiation.targetVersion() != versionInt) {
|
|
|
|
|
String negotiationKey = type.name() + "_" + versionInt + "_" + negotiation.targetVersion();
|
|
|
|
|
if (loggedNegotiations.add(negotiationKey)) {
|
|
|
|
|
Iris.logger.info("Negotiated {} shader from GLSL {} to {} (extensions: {}, polyfills: {})", type.name(), versionInt, negotiation.targetVersion(), negotiation.extensions(), negotiation.preprocessorDefines().size());
|
|
|
|
|
}
|
|
|
|
|
versionInt = negotiation.targetVersion();
|
|
|
|
|
versionString = String.valueOf(versionInt);
|
|
|
|
|
profile = negotiation.profile();
|
|
|
|
|
} else {
|
|
|
|
|
if (versionInt >= 150) {
|
|
|
|
|
profile = matcher.group(2);
|
|
|
|
|
if (profile == null) {
|
|
|
|
|
profile = "compatibility";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
negotiations.put(type, negotiation);
|
|
|
|
|
|
|
|
|
|
final String profileString = "#version " + versionString + (profile.isEmpty() ? "" : " " + profile) + "\n";
|
|
|
|
|
|
|
|
|
|
input = replaceTexture(input);
|
|
|
|
|
// The primary reason we rename words here using regex, is because if the words cause invalid
|
|
|
|
|
// GLSL, regardless of the version being used, it will cause glsl-transformation-lib to fail
|
|
|
|
|
// so we need to rename them prior to passing the shader input to glsl-transformation-lib.
|
|
|
|
|
for (String reservedWord : fullReservedWords) {
|
|
|
|
|
String newName = "iris_renamed_" + reservedWord;
|
|
|
|
|
input = input.replaceAll("\\b" + reservedWord + "\\b", newName);
|
|
|
|
|
}
|
|
|
|
|
for (int version : versionedReservedWords.keySet()) {
|
|
|
|
|
if (versionInt < version) {
|
|
|
|
|
for (String reservedWord : versionedReservedWords.get(version)) {
|
|
|
|
|
String newName = "iris_renamed_" + reservedWord;
|
|
|
|
|
final String newName = "iris_renamed_" + reservedWord;
|
|
|
|
|
input = input.replaceAll("\\b" + reservedWord + "\\b", newName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var parsedShader = ShaderParser.parseShader(input);
|
|
|
|
|
var transformer = new Transformer(parsedShader.full());
|
|
|
|
|
final var parsedShader = ShaderParser.parseShader(input);
|
|
|
|
|
final var transformer = new Transformer(parsedShader.full());
|
|
|
|
|
|
|
|
|
|
if (parameters.type == ShaderType.VERTEX || parameters.type == ShaderType.FRAGMENT) {
|
|
|
|
|
upgradeStorageQualifiers(transformer, parameters);
|
|
|
|
@@ -386,28 +563,25 @@ public class ShaderTransformer {
|
|
|
|
|
|
|
|
|
|
// Check if we need to patch in texture LOD extension enabling
|
|
|
|
|
if (versionInt <= 120 && (transformer.containsCall("texture2DLod") || transformer.containsCall("texture3DLod") || transformer.containsCall("texture2DGradARB"))) {
|
|
|
|
|
textureLodExtensionPatches.add(transformer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if we need to patch in some hacky GLSL 120 compat
|
|
|
|
|
if (versionInt <= 120) {
|
|
|
|
|
hacky120Patches.add(transformer);
|
|
|
|
|
needsTextureLodExtension.put(type, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extract extensions from the pre-parsed content (version + extensions before main code)
|
|
|
|
|
// This preserves #extension directives that the shader pack declares
|
|
|
|
|
var extensions = versionPattern.matcher(getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
|
|
|
|
final var extensions = versionPattern.matcher(getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
|
|
|
|
|
|
|
|
|
|
types.put(type, transformer);
|
|
|
|
|
prepatched.put(type, profileString + (extensions.isEmpty() ? "" : "\n" + extensions));
|
|
|
|
|
}
|
|
|
|
|
CompatibilityTransformer.transformGrouped(types, parameters);
|
|
|
|
|
for (var entry : types.entrySet()) {
|
|
|
|
|
final PatchShaderType shaderType = entry.getKey();
|
|
|
|
|
final Transformer transformer = entry.getValue();
|
|
|
|
|
String header = prepatched.get(entry.getKey());
|
|
|
|
|
String header = prepatched.get(shaderType);
|
|
|
|
|
final NegotiationResult negotiation = negotiations.get(shaderType);
|
|
|
|
|
|
|
|
|
|
// For Celeritas terrain vertex shaders, inject chunk_vertex.glsl header
|
|
|
|
|
if (patchType == Patch.CELERITAS_TERRAIN && entry.getKey() == PatchShaderType.VERTEX) {
|
|
|
|
|
if (patchType == Patch.CELERITAS_TERRAIN && shaderType == PatchShaderType.VERTEX) {
|
|
|
|
|
header += computeCeleritasHeader();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -424,39 +598,38 @@ public class ShaderTransformer {
|
|
|
|
|
formattedShader = formattedShader.replace("iris_renamed_texture", "texture");
|
|
|
|
|
formattedShader = formattedShader.replace("iris_renamed_sample", "sample");
|
|
|
|
|
|
|
|
|
|
// Please don't mind the entire rest of this loop basically, we're doing awful fragile regex on the transformed
|
|
|
|
|
// shader output to do things that I can't figure out with AST because I'm bad at it
|
|
|
|
|
|
|
|
|
|
if (textureLodExtensionPatches.contains(transformer)) {
|
|
|
|
|
String[] parts = formattedShader.split("\n", 2);
|
|
|
|
|
// Inject texture LOD extension if needed
|
|
|
|
|
if (needsTextureLodExtension.containsKey(shaderType)) {
|
|
|
|
|
final String[] parts = formattedShader.split("\n", 2);
|
|
|
|
|
parts[1] = "#extension GL_ARB_shader_texture_lod : require\n" + parts[1];
|
|
|
|
|
formattedShader = parts[0] + "\n" + parts[1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hacky120Patches.contains(transformer)) {
|
|
|
|
|
// Forcibly enable GL_EXT_gpu_shader4, it has a lot of compatibility backports with GLSL 130+
|
|
|
|
|
// and seems more or less universally supported by hardware/drivers
|
|
|
|
|
String[] parts = formattedShader.split("\n", 2);
|
|
|
|
|
parts[1] = "#extension GL_EXT_gpu_shader4 : require\n" + parts[1];
|
|
|
|
|
formattedShader = parts[0] + "\n" + parts[1];
|
|
|
|
|
if (negotiation != null && (!negotiation.extensions().isEmpty() || !negotiation.preprocessorDefines().isEmpty())) {
|
|
|
|
|
formattedShader = injectGlslPreamble(formattedShader, negotiation.extensions(), negotiation.preprocessorDefines());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GLSL 120 compatibility for in/out specifiers:
|
|
|
|
|
// - Vertex shaders: "in" = vertex attribute input -> "attribute", "out" = to fragment -> "varying"
|
|
|
|
|
// - Fragment shaders: "in" = from vertex -> "varying", "out" = color output -> handled elsewhere
|
|
|
|
|
if (entry.getKey() == PatchShaderType.VERTEX) {
|
|
|
|
|
// In vertex shaders, "in" declarations are vertex attributes, not varyings
|
|
|
|
|
Matcher inMatcher = inPattern.matcher(formattedShader);
|
|
|
|
|
formattedShader = inMatcher.replaceAll("$1attribute$3");
|
|
|
|
|
Matcher outMatcher = outPattern.matcher(formattedShader);
|
|
|
|
|
formattedShader = outMatcher.replaceAll("$1varying$3");
|
|
|
|
|
} else {
|
|
|
|
|
// In fragment (and geometry) shaders, both in/out become varying
|
|
|
|
|
Matcher inOutVaryingMatcher = inOutVaryingPattern.matcher(formattedShader);
|
|
|
|
|
formattedShader = inOutVaryingMatcher.replaceAll("$1varying$3");
|
|
|
|
|
if (negotiation != null && !negotiation.defines().isEmpty()) {
|
|
|
|
|
for (var defineEntry : negotiation.defines().entrySet()) {
|
|
|
|
|
formattedShader = formattedShader.replaceAll("\\b" + Pattern.quote(defineEntry.getKey()) + "\\b", Matcher.quoteReplacement(defineEntry.getValue()));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.put(entry.getKey(), formattedShader);
|
|
|
|
|
// Convert storage qualifiers for downgraded shaders or native 120 shaders
|
|
|
|
|
if (negotiation != null && (negotiation.convertStorageQualifiers() || negotiation.targetVersion() <= 120)) {
|
|
|
|
|
if (shaderType == PatchShaderType.VERTEX) {
|
|
|
|
|
final Matcher inMatcher = inPattern.matcher(formattedShader);
|
|
|
|
|
formattedShader = inMatcher.replaceAll("$1attribute$3");
|
|
|
|
|
final Matcher outMatcher = outPattern.matcher(formattedShader);
|
|
|
|
|
formattedShader = outMatcher.replaceAll("$1varying$3");
|
|
|
|
|
} else {
|
|
|
|
|
final Matcher inOutVaryingMatcher = inOutVaryingPattern.matcher(formattedShader);
|
|
|
|
|
formattedShader = inOutVaryingMatcher.replaceAll("$1varying$3");
|
|
|
|
|
}
|
|
|
|
|
formattedShader = unsignedSuffixPattern.matcher(formattedShader).replaceAll("$1");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.put(shaderType, formattedShader);
|
|
|
|
|
}
|
|
|
|
|
watch.stop();
|
|
|
|
|
Iris.logger.info("[Load #{}] Transformed shader for {} in {}", Iris.getShaderPackLoadId(), patchType.name(), watch);
|
|
|
|
@@ -491,13 +664,6 @@ public class ShaderTransformer {
|
|
|
|
|
transformer.renameFunctionCall("ftransform", "iris_ftransform");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static void replaceGlMultiTexCoordBounded(Transformer transformer, int min, int max) {
|
|
|
|
|
for (int i = min; i <= max; i++) {
|
|
|
|
|
transformer.replaceExpression("gl_MultiTexCoord" + i, "vec4(0.0, 0.0, 0.0, 1.0)");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void patchMultiTexCoord3(Transformer transformer, Parameters parameters) {
|
|
|
|
|
if (parameters.type == ShaderType.VERTEX && transformer.hasVariable("gl_MultiTexCoord3") && !transformer.hasVariable("mc_midTexCoord")) {
|
|
|
|
|
transformer.rename("gl_MultiTexCoord3", "mc_midTexCoord");
|
|
|
|
@@ -506,7 +672,7 @@ public class ShaderTransformer {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void replaceMidTexCoord(Transformer transformer, float textureScale) {
|
|
|
|
|
int type = transformer.findType("mc_midTexCoord");
|
|
|
|
|
final int type = transformer.findType("mc_midTexCoord");
|
|
|
|
|
if (type != 0) {
|
|
|
|
|
transformer.removeVariable("mc_midTexCoord");
|
|
|
|
|
}
|
|
|
|
@@ -590,6 +756,62 @@ public class ShaderTransformer {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String injectGlslPreamble(String shader, List<String> extensions, List<String> preprocessorDefines) {
|
|
|
|
|
final StringBuilder extensionBlock = new StringBuilder();
|
|
|
|
|
final StringBuilder defineBlock = new StringBuilder();
|
|
|
|
|
final StringBuilder functionBlock = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
for (String ext : extensions) {
|
|
|
|
|
extensionBlock.append("#extension ").append(ext).append(" : require\n");
|
|
|
|
|
}
|
|
|
|
|
for (String define : preprocessorDefines) {
|
|
|
|
|
if (define.startsWith("GLSL_FUNC:")) {
|
|
|
|
|
functionBlock.append(define.substring(10)).append("\n");
|
|
|
|
|
} else {
|
|
|
|
|
defineBlock.append(define).append("\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find where preprocessor section ends and code begins
|
|
|
|
|
final String[] lines = shader.split("\n");
|
|
|
|
|
final StringBuilder shaderResult = new StringBuilder();
|
|
|
|
|
boolean extensionsInjected = false, definesInjected = false, functionsInjected = false;
|
|
|
|
|
|
|
|
|
|
for (String line : lines) {
|
|
|
|
|
final String trimmed = line.trim();
|
|
|
|
|
final boolean isPreprocessor = trimmed.startsWith("#");
|
|
|
|
|
final boolean isExtension = trimmed.startsWith("#extension");
|
|
|
|
|
final boolean isVersion = trimmed.startsWith("#version");
|
|
|
|
|
|
|
|
|
|
// Inject our extensions right after #version
|
|
|
|
|
if (isVersion) {
|
|
|
|
|
shaderResult.append(line).append("\n");
|
|
|
|
|
shaderResult.append(extensionBlock);
|
|
|
|
|
extensionsInjected = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Inject our defines after the last #extension line (before other preprocessor or code)
|
|
|
|
|
if (!definesInjected && extensionsInjected && !isExtension && !trimmed.isEmpty()) {
|
|
|
|
|
shaderResult.append(defineBlock);
|
|
|
|
|
definesInjected = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Inject our functions after all preprocessor lines
|
|
|
|
|
if (!functionsInjected && definesInjected && !isPreprocessor && !trimmed.isEmpty()) {
|
|
|
|
|
shaderResult.append(functionBlock);
|
|
|
|
|
functionsInjected = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shaderResult.append(line).append("\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!definesInjected) shaderResult.append(defineBlock);
|
|
|
|
|
if (!functionsInjected) shaderResult.append(functionBlock);
|
|
|
|
|
|
|
|
|
|
return shaderResult.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static String getFormattedShader(ParseTree tree, String string) {
|
|
|
|
|
final StringBuilder sb = new StringBuilder(string + "\n");
|
|
|
|
|
final String[] tabHolder = {""};
|
|
|
|
|