Created
August 17, 2025 00:09
-
-
Save WinterAlexander/b16f2a92e6838c2eeead6d3521a94eff to your computer and use it in GitHub Desktop.
IncludeShaderProgram loader for libGDX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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