Skip to content

Commit

Permalink
add line directive insertion to ShaderParser,
Browse files Browse the repository at this point in the history
use assigned source ids to remap the sources in shader compilation logs,
print merged shader sources to the log file in dev environments
  • Loading branch information
douira committed Oct 18, 2024
1 parent dd25399 commit c796f13
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL20C;

import java.util.Arrays;

/**
* A compiled OpenGL shader object.
*/
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,112 @@
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;
import java.util.LinkedList;
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<String> lines = parseShader(src);
lines.addAll(1, constants.getDefineStrings());
public record ParsedShader(String src, String[] includeIds) {
private static final Pattern LINE_REF_PATTERN = Pattern.compile("(?<source>\\d+):(?<line>\\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<String> parseShader(String src) {
List<String> 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<String> includeIds = new Object2IntArrayMap<>();
private final List<String> 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 <(?<namespace>.*):(?<path>.*)>");

private static List<String> resolveImport(String line) {
private ResourceLocation parseImport(String line) {
Matcher matcher = IMPORT_PATTERN.matcher(line);

if (!matcher.matches()) {
Expand All @@ -48,9 +116,20 @@ private static List<String> 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);
}
}

0 comments on commit c796f13

Please sign in to comment.