Skip to content

Instantly share code, notes, and snippets.

@WinterAlexander
Created August 17, 2025 00:09
Show Gist options
  • Select an option

  • Save WinterAlexander/b16f2a92e6838c2eeead6d3521a94eff to your computer and use it in GitHub Desktop.

Select an option

Save WinterAlexander/b16f2a92e6838c2eeead6d3521a94eff to your computer and use it in GitHub Desktop.
IncludeShaderProgram loader for libGDX
package com.greenfrisbee.makerking.render.loader;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.assets.loaders.FileHandleResolver;
import com.badlogic.gdx.assets.loaders.ShaderProgramLoader;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.ObjectSet;
import com.badlogic.gdx.utils.StringBuilder;
import com.winteralexander.gdx.utils.StringUtil;
import java.util.Locale;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.winteralexander.gdx.utils.math.NumberUtil.tryParseInt;
/**
* {@link ShaderProgramLoader} that processes includes directives in shaders
* <p>
* Created on 2020-05-20.
*
* @author Alexander Winter
*/
public class IncludeShaderProgramLoader extends ShaderProgramLoader {
public IncludeShaderProgramLoader(FileHandleResolver resolver) {
super(resolver);
}
@Override
public ShaderProgram loadSync(AssetManager manager,
String fileName,
FileHandle file,
ShaderProgramParameter parameter) {
String vertFileName = null, fragFileName = null;
if(parameter != null) {
if(parameter.vertexFile != null)
vertFileName = parameter.vertexFile;
if(parameter.fragmentFile != null)
fragFileName = parameter.fragmentFile;
}
FileHandle vertexFile = vertFileName == null ? file : resolve(vertFileName);
FileHandle fragmentFile = fragFileName == null ? file : resolve(fragFileName);
String vertexCode = vertexFile.readString();
String fragmentCode = vertexFile.equals(fragmentFile) ? vertexCode : fragmentFile.readString();
if(parameter != null) {
if(parameter.prependVertexCode != null)
vertexCode = parameter.prependVertexCode + vertexCode;
if(parameter.prependFragmentCode != null)
fragmentCode = parameter.prependFragmentCode + fragmentCode;
}
ObjectSet<FileHandle> alreadyProcessed = new ObjectSet<>();
vertexCode = processIncludes(vertexFile.parent(), vertexCode, alreadyProcessed);
alreadyProcessed.clear();
fragmentCode = processIncludes(fragmentFile.parent(), fragmentCode, alreadyProcessed);
ShaderProgram shaderProgram = new ShaderProgram(vertexCode, fragmentCode);
if(!shaderProgram.isCompiled())
throw new GdxRuntimeException("Failed to compile shader " + fileName + ":\n " +
improvedShaderLog(shaderProgram.getLog(), vertexCode, fragmentCode));
return shaderProgram;
}
public static String improvedShaderLog(String compileLog, String vertexSource, String fragmentSource) {
boolean isVertex = true;
Array<String> lines = new Array<>(compileLog.split(System.lineSeparator()));
Pattern amdLineNumberMatcher = Pattern.compile("^0:(\\d+)\\((\\d+)\\):");
String[] vertexSourceLines = vertexSource.split(System.lineSeparator());
String[] fragmentSourceLines = fragmentSource.split(System.lineSeparator());
for(int i = 0; i < lines.size; i++) {
String line = lines.get(i);
if(line.toLowerCase(Locale.ROOT).matches(".*fragment.*")) {
isVertex = false;
continue;
}
if(line.toLowerCase(Locale.ROOT).matches(".*vertex.*")) {
isVertex = true;
continue;
}
Matcher matcher = amdLineNumberMatcher.matcher(line);
if(!matcher.find())
continue;
int lineNumber = tryParseInt(matcher.group(1), -1);
int charNumber = tryParseInt(matcher.group(2), -1);
if(lineNumber == -1 || charNumber == -1)
continue;
String[] source = isVertex ? vertexSourceLines : fragmentSourceLines;
int start = Math.max(0, lineNumber - 10);
for(int j = start; j < Math.min(source.length, start + 20); j++) {
i++;
lines.insert(i, source[j]);
if(j + 1 == lineNumber) {
i++;
lines.insert(i, StringUtil.padLeft("^", ' ', charNumber + 2));
}
}
}
return StringUtil.join(lines, System.lineSeparator());
}
public static String processIncludes(FileHandle directory,
String code) {
return processIncludes(directory, code, new ObjectSet<>());
}
public static String processIncludes(FileHandle directory,
String code,
ObjectSet<FileHandle> alreadyProcessed) {
Scanner scanner = new Scanner(code);
StringBuilder sb = new StringBuilder();
while(scanner.hasNextLine()) {
String line = scanner.nextLine();
// process the line
if(line.startsWith("//!include")) {
FileHandle dir = directory;
String includePath = line.substring("//!include".length()).trim();
while(includePath.startsWith("../")) {
dir = dir.parent();
includePath = includePath.substring(3);
}
FileHandle includeFile = dir.child(includePath);
if(alreadyProcessed.contains(includeFile))
continue;
alreadyProcessed.add(includeFile);
String includeCode = includeFile.readString();
includeCode = processIncludes(includeFile.parent(), includeCode, alreadyProcessed);
sb.appendLine(includeCode);
} else
sb.appendLine(line);
}
scanner.close();
return sb.toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment