Skip to content

Commit

Permalink
Add shader source line annotations (#2691)
Browse files Browse the repository at this point in the history
  • Loading branch information
douira authored Jan 2, 2025
1 parent 1b0f7b9 commit 3186b1b
Show file tree
Hide file tree
Showing 3 changed files with 86 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, 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,87 @@
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) {
}

public static ParsedShader parseShader(String src, ShaderConstants constants) {
var parser = new ShaderParser();
parser.parseShader("_root", src);
parser.prependDefineStrings(constants);

return String.join("\n", lines);
return parser.finish();
}

public static List<String> parseShader(String src) {
List<String> builder = new LinkedList<>();
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 +91,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 3186b1b

Please sign in to comment.