diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/GlShader.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/GlShader.java index 0cc4058234..578d9f848e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/GlShader.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/GlShader.java @@ -7,6 +7,8 @@ import org.apache.logging.log4j.Logger; import org.lwjgl.opengl.GL20C; +import java.util.Arrays; + /** * A compiled OpenGL shader object. */ @@ -15,17 +17,18 @@ public class GlShader extends GlObject { private final ResourceLocation name; - public GlShader(ShaderType type, ResourceLocation name, String src) { + public GlShader(ShaderType type, ResourceLocation name, ShaderParser.ParsedShader parsedShader) { this.name = name; int handle = GL20C.glCreateShader(type.id); - ShaderWorkarounds.safeShaderSource(handle, src); + ShaderWorkarounds.safeShaderSource(handle, parsedShader.src()); GL20C.glCompileShader(handle); String log = GL20C.glGetShaderInfoLog(handle); if (!log.isEmpty()) { - LOGGER.warn("Shader compilation log for " + this.name + ": " + log); + LOGGER.warn("Shader compilation log for " + this.name + ": " + parsedShader.remapShaderErrorLog(name, log)); + LOGGER.warn("Include table: " + Arrays.toString(parsedShader.includeIds())); } int result = GlStateManager.glGetShaderi(handle, GL20C.GL_COMPILE_STATUS); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java index 03d04c0e97..4d7806e8e4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java @@ -1,13 +1,18 @@ package net.caffeinemc.mods.sodium.client.gl.shader; +import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import net.minecraft.resources.ResourceLocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class ShaderLoader { + private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-ShaderLoader"); + /** * Creates an OpenGL shader from GLSL sources. The GLSL source file should be made available on the classpath at the * path of `/assets/{namespace}/shaders/{path}`. User defines can be used to declare variables in the shader source @@ -19,7 +24,12 @@ public class ShaderLoader { * @return An OpenGL shader object compiled with the given user defines */ public static GlShader loadShader(ShaderType type, ResourceLocation name, ShaderConstants constants) { - return new GlShader(type, name, ShaderParser.parseShader(getShaderSource(name), constants)); + var parsedShader = ShaderParser.parseShader(getShaderSource(name), constants); + if (PlatformRuntimeInformation.INSTANCE.isDevelopmentEnvironment()) { + LOGGER.info("Loaded shader {} with constants {}", name, constants); + LOGGER.info(parsedShader.src()); + } + return new GlShader(type, name, parsedShader); } public static String getShaderSource(ResourceLocation name) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java index 1d0bd52569..30ce4faea7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java @@ -1,5 +1,9 @@ package net.caffeinemc.mods.sodium.client.gl.shader; +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.minecraft.resources.ResourceLocation; + import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; @@ -7,38 +11,102 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import net.minecraft.resources.ResourceLocation; public class ShaderParser { - public static String parseShader(String src, ShaderConstants constants) { - List lines = parseShader(src); - lines.addAll(1, constants.getDefineStrings()); + public record ParsedShader(String src, String[] includeIds) { + private static final Pattern LINE_REF_PATTERN = Pattern.compile("(?\\d+):(?\\d+):"); + + public String remapShaderErrorLog(ResourceLocation name, String log) { + // remap all line references with the include id table + Matcher matcher = LINE_REF_PATTERN.matcher(log); + StringBuilder builder = new StringBuilder(); + + while (matcher.find()) { + String source = matcher.group("source"); + var line = matcher.group("line"); + var id = Integer.parseInt(source); + + if (id >= this.includeIds.length) { + matcher.appendReplacement(builder, "unknown_source" + "(#" + id + "):" + line + ":"); + continue; + } + + String includeName = id == 0 ? name.toString() : this.includeIds[id]; - return String.join("\n", lines); + matcher.appendReplacement(builder, includeName + "(#" + id + "):" + line + ":"); + } + + matcher.appendTail(builder); + return builder.toString(); + } } - public static List parseShader(String src) { - List builder = new LinkedList<>(); + public static ParsedShader parseShader(String src, ShaderConstants constants) { + var parser = new ShaderParser(); + parser.parseShader("_root", src); + parser.prependDefineStrings(constants); + + return parser.finish(); + } + + private final Object2IntMap includeIds = new Object2IntArrayMap<>(); + private final List lines = new LinkedList<>(); + + private ShaderParser() { + } + + public void parseShader(String name, String src) { String line; + int lineNumber = 0; try (BufferedReader reader = new BufferedReader(new StringReader(src))) { while ((line = reader.readLine()) != null) { - if (line.startsWith("#import")) { - builder.addAll(resolveImport(line)); + lineNumber++; + if (line.startsWith("#version")) { + this.lines.add(line); + this.lines.add(lineDirectiveFor(name, lineNumber)); + } else if (line.startsWith("#import")) { + // add the original import statement as a comment for reference + this.lines.add("// START " + line); + + processImport(line); + + // reset the line directive to the current file + this.lines.add("// END " + line); + this.lines.add(lineDirectiveFor(name, lineNumber)); } else { - builder.add(line); + this.lines.add(line); } } } catch (IOException e) { throw new RuntimeException("Failed to read shader sources", e); } + } + + private String lineDirectiveFor(String name, int line) { + int idNumber; + if (!this.includeIds.containsKey(name)) { + idNumber = this.includeIds.size(); + this.includeIds.put(name, idNumber); + } else { + idNumber = this.includeIds.getInt(name); + } + return "#line " + (line + 1) + " " + idNumber; + } + + private void processImport(String line) { + ResourceLocation name = parseImport(line); - return builder; + // mark the start of the imported file + var nameString = name.toString(); + this.lines.add(lineDirectiveFor(nameString, 0)); + + parseShader(nameString, ShaderLoader.getShaderSource(name)); } private static final Pattern IMPORT_PATTERN = Pattern.compile("#import <(?.*):(?.*)>"); - private static List resolveImport(String line) { + private ResourceLocation parseImport(String line) { Matcher matcher = IMPORT_PATTERN.matcher(line); if (!matcher.matches()) { @@ -48,9 +116,20 @@ private static List resolveImport(String line) { String namespace = matcher.group("namespace"); String path = matcher.group("path"); - ResourceLocation name = ResourceLocation.fromNamespaceAndPath(namespace, path); - String source = ShaderLoader.getShaderSource(name); + return ResourceLocation.fromNamespaceAndPath(namespace, path); + } + + private void prependDefineStrings(ShaderConstants constants) { + this.lines.addAll(1, constants.getDefineStrings()); + } + + private ParsedShader finish() { + // convert include id map to a list ordered by id + var includeIds = new String[this.includeIds.size()]; + this.includeIds.forEach((name, id) -> { + includeIds[id] = name; + }); - return ShaderParser.parseShader(source); + return new ParsedShader(String.join("\n", this.lines), includeIds); } }