mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
159f7ff8cc | ||
|
|
0d959c2b90 | ||
|
|
ce2462cad3 | ||
|
|
5457df5369 | ||
|
|
acedba4eac | ||
|
|
21a054a4db | ||
|
|
d056bf6fa7 | ||
|
|
5ed1c92590 |
No files matched your search
@@ -148,6 +148,17 @@ fun Test.configureAngelicaJava8() {
|
||||
}
|
||||
}
|
||||
|
||||
if (isMacOs && providers.gradleProperty("clientJvmVendor").isPresent) {
|
||||
for (javaVersion in listOf(17, 21, 25)) {
|
||||
tasks.named<JavaExec>("runClient$javaVersion") {
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
vendor = JvmVendorSpec.matching(providers.gradleProperty("clientJvmVendor").get())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform { excludeTags = setOf("gl-core") }
|
||||
configureAngelicaJava8()
|
||||
|
||||
@@ -62,6 +62,7 @@ public final class SystemProperties {
|
||||
public static final long FLYBY_TIME_OF_DAY = Math.floorMod(Long.getLong("angelica.flyby.timeOfDay", 6000L), 24000L);
|
||||
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
|
||||
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
|
||||
public static final Integer FLYBY_DIMENSION = Integer.getInteger("angelica.flyby.dimension");
|
||||
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
|
||||
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
|
||||
public static final FlybyCamera FLYBY_CAMERA = parseEnum("angelica.flyby.camera", FlybyCamera.FIRST_PERSON, FlybyCamera.FIRST_PERSON, FlybyCamera.class);
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
@@ -456,12 +457,7 @@ public class CompatShaderTransformer {
|
||||
continue;
|
||||
}
|
||||
|
||||
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
|
||||
declarators.add(decl.typeless_declaration());
|
||||
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) {
|
||||
declarators.addAll(list.typeless_declaration());
|
||||
}
|
||||
for (GLSLParser.Typeless_declarationContext d : declarators) {
|
||||
for (GLSLParser.Typeless_declarationContext d : ShaderIndex.declarators((GLSLParser.Init_declarator_listContext) decl.getParent())) {
|
||||
if (d.IDENTIFIER() == null) continue;
|
||||
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(d.array_specifier());
|
||||
final String arraySuffix = typeArray + arraySuffix(d.array_specifier());
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.antlr.v4.runtime.BailErrorStrategy;
|
||||
import org.antlr.v4.runtime.BufferedTokenStream;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CommonToken;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.DefaultErrorStrategy;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.WritableToken;
|
||||
import org.antlr.v4.runtime.atn.PredictionMode;
|
||||
import org.antlr.v4.runtime.misc.ParseCancellationException;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.taumc.glsl.grammar.GLSLPreParser;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -131,72 +135,35 @@ public class GlslTransformUtils {
|
||||
public record QuietParse(GLSLParser.Translation_unitContext full, GLSLPreParser.Translation_unitContext pre) {}
|
||||
|
||||
public static GLSLParser.Translation_unitContext parseFullQuiet(String source) {
|
||||
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
|
||||
lexer.removeErrorListeners();
|
||||
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
|
||||
parser.removeErrorListeners();
|
||||
parser.setBuildParseTree(true);
|
||||
return parser.translation_unit();
|
||||
}
|
||||
|
||||
public static GLSLPreParser.Translation_unitContext parsePreQuiet(String source) {
|
||||
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
|
||||
lexer.removeErrorListeners();
|
||||
final GLSLPreParser preParser = new GLSLPreParser(new CommonTokenStream(lexer, GLSLLexer.DIRECTIVES));
|
||||
preParser.removeErrorListeners();
|
||||
preParser.setBuildParseTree(true);
|
||||
return preParser.translation_unit();
|
||||
return parseQuiet(new GLSLParser(new CommonTokenStream(quietLexer(source))), GLSLParser::translation_unit);
|
||||
}
|
||||
|
||||
public static QuietParse parseBothQuiet(String source) {
|
||||
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
|
||||
lexer.removeErrorListeners();
|
||||
final GLSLPreParser preParser = new GLSLPreParser(new BufferedTokenStream(lexer));
|
||||
preParser.removeErrorListeners();
|
||||
preParser.setBuildParseTree(true);
|
||||
final GLSLPreParser.Translation_unitContext pre = preParser.translation_unit();
|
||||
final GLSLLexer lexer = quietLexer(source);
|
||||
final GLSLPreParser.Translation_unitContext pre = parseQuiet(new GLSLPreParser(new BufferedTokenStream(lexer)), GLSLPreParser::translation_unit);
|
||||
lexer.reset();
|
||||
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
|
||||
parser.removeErrorListeners();
|
||||
parser.setBuildParseTree(true);
|
||||
return new QuietParse(parser.translation_unit(), pre);
|
||||
return new QuietParse(parseQuiet(new GLSLParser(new CommonTokenStream(lexer)), GLSLParser::translation_unit), pre);
|
||||
}
|
||||
|
||||
public static Set<String> identifiersInDirectiveText(String source) {
|
||||
private static <P extends Parser, T> T parseQuiet(P parser, Function<P, T> rule) {
|
||||
parser.removeErrorListeners();
|
||||
parser.setErrorHandler(new BailErrorStrategy());
|
||||
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
|
||||
try {
|
||||
return rule.apply(parser);
|
||||
} catch (ParseCancellationException e) {
|
||||
// Rewinds the token stream, which keeps every token SLL already pulled from the lexer.
|
||||
parser.reset();
|
||||
parser.setErrorHandler(new DefaultErrorStrategy());
|
||||
parser.getInterpreter().setPredictionMode(PredictionMode.LL);
|
||||
return rule.apply(parser);
|
||||
}
|
||||
}
|
||||
|
||||
public static GLSLLexer quietLexer(String source) {
|
||||
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
|
||||
lexer.removeErrorListeners();
|
||||
final BufferedTokenStream tokens = new BufferedTokenStream(lexer);
|
||||
tokens.fill();
|
||||
|
||||
final Set<String> names = new HashSet<>();
|
||||
for (Token token : tokens.getTokens()) {
|
||||
switch (token.getType()) {
|
||||
case GLSLLexer.PROGRAM_TEXT, GLSLLexer.MACRO_TEXT, GLSLLexer.CONSTANT_EXPRESSION -> collectIdentifiers(token.getText(), names);
|
||||
default -> { }
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private static void collectIdentifiers(String text, Set<String> out) {
|
||||
if (text == null) return;
|
||||
final int len = text.length();
|
||||
int i = 0;
|
||||
while (i < len) {
|
||||
final char c = text.charAt(i);
|
||||
if (c != '_' && !Character.isLetter(c)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
int end = i + 1;
|
||||
while (end < len) {
|
||||
final char n = text.charAt(end);
|
||||
if (n != '_' && !Character.isLetterOrDigit(n)) break;
|
||||
end++;
|
||||
}
|
||||
out.add(text.substring(i, end));
|
||||
i = end;
|
||||
}
|
||||
return lexer;
|
||||
}
|
||||
|
||||
public static String getFormattedShader(ParseTree tree, String header) {
|
||||
|
||||
@@ -276,6 +276,12 @@ public abstract class RenderBackend {
|
||||
public void voxelizeRegion(long pass, boolean rebindVertexBuffer, float x, float y, float z, int rangeBase, int rangeCount, int vertexTotal) {}
|
||||
public void endVoxelizationBatch(long pass) {}
|
||||
|
||||
public void setVertexWriteReplay(int graphicsProgram, VertexWriteReplaySetup setup) {}
|
||||
|
||||
public boolean supportsComputeImageAtomics() { return true; }
|
||||
|
||||
public boolean supportsComputeImageAtomics(int width, int height, int depth) { return supportsComputeImageAtomics(); }
|
||||
|
||||
public abstract void enable(int cap);
|
||||
public abstract void enablei(int cap, int index);
|
||||
public abstract void disable(int cap);
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
/**
|
||||
* How to replay a graphics program's vertex-stage image writes.
|
||||
*
|
||||
* @param inputLocations per input, the declared location or -1 for whatever the graphics program linked
|
||||
* @param images per image the replay uses, in binding order: the written ones, then the read-only ones
|
||||
* @param writtenImageCount how many of {@code images} the replay writes
|
||||
* @param samplerNames samplers the graphics program may not have, because only the stripped writes read them
|
||||
* @param samplerUnits per sampler name, the texture unit it reads at each draw, or -1 to use samplerTextures
|
||||
* @param samplerTextures per sampler name, its texture when samplerUnits is -1
|
||||
* @param samplerObjects per sampler name, the GL sampler object to sample it with when samplerUnits is -1, or 0
|
||||
* @param vertexBufferBinding storage buffer binding of the first vertex buffer the replay program declares
|
||||
* @param vertexBufferCount how many vertex buffers it declares, at consecutive bindings
|
||||
* @param indexBufferBinding storage buffer binding of its index buffer
|
||||
*/
|
||||
public record VertexWriteReplaySetup(int replayProgram, String[] inputNames, int[] inputLocations,
|
||||
IntSupplier[] images, int writtenImageCount,
|
||||
String[] samplerNames, int[] samplerUnits, IntSupplier[] samplerTextures, int[] samplerObjects,
|
||||
int vertexBufferBinding, int vertexBufferCount, int indexBufferBinding) {
|
||||
}
|
||||
+90
-136
@@ -4,17 +4,11 @@ import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.taumc.glsl.grammar.GLSLPreParser;
|
||||
import org.taumc.glsl.grammar.GLSLParserBaseListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
@@ -99,7 +93,7 @@ public final class GlslVulkanPreprocess {
|
||||
}
|
||||
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
final Metadata meta = collectEdits(source, root, glShaderType, debugName, separateReadOnlyImages, edits);
|
||||
final Metadata meta = collectEdits(new ShaderIndex(root, source), glShaderType, debugName, separateReadOnlyImages, edits);
|
||||
|
||||
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs());
|
||||
synchronized (CACHE) {
|
||||
@@ -111,129 +105,100 @@ public final class GlslVulkanPreprocess {
|
||||
|
||||
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless) {}
|
||||
|
||||
public static Metadata collectEdits(String source, GLSLParser.Translation_unitContext root, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
|
||||
public static Metadata collectEdits(ShaderIndex index, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
|
||||
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
|
||||
final Set<String> bools = new HashSet<>();
|
||||
final Set<String> explicitInputs = new HashSet<>();
|
||||
final Set<String> readOnlyImages = new HashSet<>();
|
||||
final boolean[] needsSamplerless = { false };
|
||||
boolean needsSamplerless = false;
|
||||
|
||||
final int[] maxExplicitLoc = { -1 };
|
||||
int maxExplicitLoc = -1;
|
||||
final List<Integer> unlocatedVsInputStarts = new ArrayList<>();
|
||||
|
||||
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
|
||||
@Override
|
||||
public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) {
|
||||
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
|
||||
if (single == null || single.fully_specified_type() == null) return;
|
||||
for (GLSLParser.DeclarationContext declaration : index.globalDeclarations()) {
|
||||
final GLSLParser.Init_declarator_listContext list = declaration.init_declarator_list();
|
||||
if (list == null) continue;
|
||||
final GLSLParser.Single_declarationContext single = list.single_declaration();
|
||||
if (single == null || single.fully_specified_type() == null) continue;
|
||||
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
final GLSLParser.Type_qualifierContext tq = fst.type_qualifier();
|
||||
if (tq == null) return;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
final GLSLParser.Type_qualifierContext tq = fst.type_qualifier();
|
||||
if (tq == null) continue;
|
||||
|
||||
boolean hasUniform = false, hasIn = false, hasLocation = false, hasReadonly = false;
|
||||
int locValue = -1;
|
||||
int bindingValue = -1;
|
||||
for (GLSLParser.Single_type_qualifierContext stq : tq.single_type_qualifier()) {
|
||||
if (stq.storage_qualifier() != null) {
|
||||
final String s = stq.storage_qualifier().getText();
|
||||
if ("uniform".equals(s)) hasUniform = true;
|
||||
else if ("in".equals(s)) hasIn = true;
|
||||
else if ("readonly".equals(s)) hasReadonly = true;
|
||||
} else if (stq.layout_qualifier() != null) {
|
||||
for (GLSLParser.Layout_qualifier_idContext id : stq.layout_qualifier().layout_qualifier_id_list().layout_qualifier_id()) {
|
||||
if (id.IDENTIFIER() == null) continue;
|
||||
final String qualifier = id.IDENTIFIER().getText();
|
||||
if ("location".equals(qualifier)) {
|
||||
hasLocation = true;
|
||||
if (id.constant_expression() != null) {
|
||||
try { locValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
} else if ("binding".equals(qualifier) && id.constant_expression() != null) {
|
||||
try { bindingValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
|
||||
boolean hasUniform = false, hasIn = false, hasLocation = false, hasReadonly = false;
|
||||
int locValue = -1;
|
||||
int bindingValue = -1;
|
||||
for (GLSLParser.Single_type_qualifierContext stq : tq.single_type_qualifier()) {
|
||||
if (stq.storage_qualifier() != null) {
|
||||
final String s = stq.storage_qualifier().getText();
|
||||
if ("uniform".equals(s)) hasUniform = true;
|
||||
else if ("in".equals(s)) hasIn = true;
|
||||
else if ("readonly".equals(s)) hasReadonly = true;
|
||||
} else if (stq.layout_qualifier() != null) {
|
||||
for (GLSLParser.Layout_qualifier_idContext id : stq.layout_qualifier().layout_qualifier_id_list().layout_qualifier_id()) {
|
||||
if (id.IDENTIFIER() == null) continue;
|
||||
final String qualifier = id.IDENTIFIER().getText();
|
||||
if ("location".equals(qualifier)) {
|
||||
hasLocation = true;
|
||||
if (id.constant_expression() != null) {
|
||||
try { locValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
} else if ("binding".equals(qualifier) && id.constant_expression() != null) {
|
||||
try { bindingValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final boolean boolDecl = hasUniform && isBool(fst);
|
||||
final boolean explicitInputDecl = isVertex && hasIn && hasLocation;
|
||||
final boolean boolDecl = hasUniform && isBool(fst);
|
||||
final boolean explicitInputDecl = isVertex && hasIn && hasLocation;
|
||||
final List<GLSLParser.Typeless_declarationContext> declarators = ShaderIndex.declarators(list);
|
||||
for (GLSLParser.Typeless_declarationContext td : declarators) {
|
||||
handleDeclarator(td, hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
|
||||
}
|
||||
|
||||
if (single.typeless_declaration() != null) {
|
||||
handleDeclarator(single.typeless_declaration(), hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
|
||||
}
|
||||
for (GLSLParser.Typeless_declarationContext td : ctx.typeless_declaration()) {
|
||||
handleDeclarator(td, hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
|
||||
}
|
||||
|
||||
if (separateReadOnlyImages && hasUniform && fst.type_specifier() != null) {
|
||||
final String typeText = fst.type_specifier().getText();
|
||||
if (separateTextureType(typeText) != null) {
|
||||
needsSamplerless[0] = true;
|
||||
} else if (hasReadonly) {
|
||||
final String asTexture = separateTextureTypeForImage(typeText);
|
||||
if (asTexture != null) {
|
||||
needsSamplerless[0] = true;
|
||||
collectDeclaredNames(single, ctx, readOnlyImages);
|
||||
final String binding = bindingValue >= 0 ? "layout(binding = " + bindingValue + ") " : "";
|
||||
edits.add(new Edit(startIdx(fst), stopIdx(fst), binding + "uniform " + asTexture));
|
||||
if (separateReadOnlyImages && hasUniform && fst.type_specifier() != null) {
|
||||
final String typeText = fst.type_specifier().getText();
|
||||
if (separateTextureType(typeText) != null) {
|
||||
needsSamplerless = true;
|
||||
} else if (hasReadonly) {
|
||||
final String asTexture = separateTextureTypeForImage(typeText);
|
||||
if (asTexture != null) {
|
||||
needsSamplerless = true;
|
||||
for (GLSLParser.Typeless_declarationContext td : declarators) {
|
||||
if (td.IDENTIFIER() != null) readOnlyImages.add(td.IDENTIFIER().getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isVertex && hasIn) {
|
||||
if (hasLocation && locValue >= 0) {
|
||||
if (locValue > maxExplicitLoc[0]) maxExplicitLoc[0] = locValue;
|
||||
} else if (!hasLocation) {
|
||||
unlocatedVsInputStarts.add(ctx.getStart().getStartIndex());
|
||||
final String binding = bindingValue >= 0 ? "layout(binding = " + bindingValue + ") " : "";
|
||||
edits.add(new Edit(startIdx(fst), stopIdx(fst), binding + "uniform " + asTexture));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterPostfix_expression(GLSLParser.Postfix_expressionContext ctx) {
|
||||
if (readOnlyImages.isEmpty() || ctx.LEFT_PAREN() == null || ctx.RIGHT_PAREN() == null) return;
|
||||
final GLSLParser.Function_call_parametersContext params = ctx.function_call_parameters();
|
||||
if (params == null || params.assignment_expression() == null || params.assignment_expression().isEmpty()) return;
|
||||
if (!readOnlyImages.contains(params.assignment_expression(0).getText())) return;
|
||||
|
||||
final ParseTree callee = ctx.getChild(0);
|
||||
if (!(callee instanceof ParserRuleContext calleeCtx)) return;
|
||||
final String replacement = switch (calleeCtx.getText()) {
|
||||
case "imageLoad" -> "texelFetch";
|
||||
case "imageSize" -> "textureSize";
|
||||
default -> null;
|
||||
};
|
||||
if (replacement == null) return;
|
||||
|
||||
edits.add(new Edit(startIdx(calleeCtx), stopIdx(calleeCtx), replacement));
|
||||
final int rparen = ctx.RIGHT_PAREN().getSymbol().getStartIndex();
|
||||
edits.add(new Edit(rparen, rparen - 1, ", 0"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTerminal(TerminalNode node) {
|
||||
final Token tok = node.getSymbol();
|
||||
if (tok.getType() != GLSLLexer.IDENTIFIER) return;
|
||||
final String text = tok.getText();
|
||||
switch (text) {
|
||||
case "sampler" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), SAMPLER_RENAMED));
|
||||
case "gl_VertexID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_VertexIndex"));
|
||||
case "gl_InstanceID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_InstanceIndex"));
|
||||
default -> {
|
||||
if (TARGET_RESERVED.contains(text)) edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), RESERVED_RENAMED_PREFIX + text));
|
||||
}
|
||||
if (isVertex && hasIn) {
|
||||
if (hasLocation && locValue >= 0) {
|
||||
if (locValue > maxExplicitLoc) maxExplicitLoc = locValue;
|
||||
} else if (!hasLocation) {
|
||||
unlocatedVsInputStarts.add(list.getStart().getStartIndex());
|
||||
}
|
||||
}
|
||||
}, root);
|
||||
|
||||
if (needsSamplerless[0] && source != null) {
|
||||
final int at = versionDirectiveEnd(source);
|
||||
if (at >= 0) edits.add(new Edit(at, at - 1, "\n" + SAMPLERLESS_EXTENSION));
|
||||
}
|
||||
|
||||
if (maxExplicitLoc[0] >= 0 && !unlocatedVsInputStarts.isEmpty()) {
|
||||
int next = maxExplicitLoc[0] + 1;
|
||||
if (!readOnlyImages.isEmpty()) {
|
||||
rewriteReadOnlyImageCalls(index.callsTo("imageLoad"), "texelFetch", readOnlyImages, edits);
|
||||
rewriteReadOnlyImageCalls(index.callsTo("imageSize"), "textureSize", readOnlyImages, edits);
|
||||
}
|
||||
if (needsSamplerless) {
|
||||
final int versionEnd = index.directives().versionEnd();
|
||||
if (versionEnd >= 0) edits.add(new Edit(versionEnd, versionEnd - 1, "\n" + SAMPLERLESS_EXTENSION));
|
||||
}
|
||||
|
||||
rename(index, "sampler", SAMPLER_RENAMED, edits);
|
||||
rename(index, "gl_VertexID", "gl_VertexIndex", edits);
|
||||
rename(index, "gl_InstanceID", "gl_InstanceIndex", edits);
|
||||
for (String reserved : TARGET_RESERVED) rename(index, reserved, RESERVED_RENAMED_PREFIX + reserved, edits);
|
||||
|
||||
if (maxExplicitLoc >= 0 && !unlocatedVsInputStarts.isEmpty()) {
|
||||
int next = maxExplicitLoc + 1;
|
||||
int dropped = 0;
|
||||
for (int start : unlocatedVsInputStarts) {
|
||||
if (next >= MAX_VS_INPUT_LOCATIONS) { dropped++; continue; }
|
||||
@@ -245,22 +210,31 @@ public final class GlslVulkanPreprocess {
|
||||
}
|
||||
}
|
||||
|
||||
return new Metadata(bools, explicitInputs, needsSamplerless[0]);
|
||||
return new Metadata(bools, explicitInputs, needsSamplerless);
|
||||
}
|
||||
|
||||
private static void rename(ShaderIndex index, String name, String replacement, List<Edit> edits) {
|
||||
for (Token tok : index.occurrences(name)) edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), replacement));
|
||||
}
|
||||
|
||||
private static void rewriteReadOnlyImageCalls(List<GLSLParser.Postfix_expressionContext> calls, String replacement, Set<String> readOnlyImages, List<Edit> edits) {
|
||||
for (GLSLParser.Postfix_expressionContext call : calls) {
|
||||
if (call.RIGHT_PAREN() == null) continue;
|
||||
final GLSLParser.Function_call_parametersContext params = call.function_call_parameters();
|
||||
if (params == null || params.assignment_expression().isEmpty()) continue;
|
||||
if (!readOnlyImages.contains(params.assignment_expression(0).getText())) continue;
|
||||
|
||||
final ParserRuleContext callee = (ParserRuleContext) call.getChild(0);
|
||||
edits.add(new Edit(startIdx(callee), stopIdx(callee), replacement));
|
||||
final int rparen = call.RIGHT_PAREN().getSymbol().getStartIndex();
|
||||
edits.add(new Edit(rparen, rparen - 1, ", 0"));
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearCache() {
|
||||
synchronized (CACHE) { CACHE.clear(); }
|
||||
}
|
||||
|
||||
private static void collectDeclaredNames(GLSLParser.Single_declarationContext single, GLSLParser.Init_declarator_listContext ctx, Set<String> out) {
|
||||
if (single.typeless_declaration() != null && single.typeless_declaration().IDENTIFIER() != null) {
|
||||
out.add(single.typeless_declaration().IDENTIFIER().getText());
|
||||
}
|
||||
for (GLSLParser.Typeless_declarationContext td : ctx.typeless_declaration()) {
|
||||
if (td.IDENTIFIER() != null) out.add(td.IDENTIFIER().getText());
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String separateTextureTypeForImage(String typeText) {
|
||||
final String dim = switch (typeText) {
|
||||
case "image1D", "iimage1D", "uimage1D" -> "1D";
|
||||
@@ -281,26 +255,6 @@ public final class GlslVulkanPreprocess {
|
||||
};
|
||||
}
|
||||
|
||||
private static int versionDirectiveEnd(String source) {
|
||||
final GLSLPreParser.Translation_unitContext pre;
|
||||
try {
|
||||
pre = GlslTransformUtils.parsePreQuiet(source);
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
final GLSLPreParser.Version_directiveContext ctx = firstVersionDirective(pre);
|
||||
return ctx == null || ctx.getStop() == null ? -1 : ctx.getStop().getStopIndex() + 1;
|
||||
}
|
||||
|
||||
private static GLSLPreParser.@Nullable Version_directiveContext firstVersionDirective(ParseTree node) {
|
||||
if (node instanceof GLSLPreParser.Version_directiveContext v) return v;
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
final GLSLPreParser.Version_directiveContext found = firstVersionDirective(node.getChild(i));
|
||||
if (found != null) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void handleDeclarator(GLSLParser.Typeless_declarationContext td, boolean hasUniform, boolean asBool, boolean asInput, Set<String> bools, Set<String> inputs, List<Edit> edits) {
|
||||
if (td.IDENTIFIER() == null) return;
|
||||
final String name = td.IDENTIFIER().getText();
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.gtnewhorizons.angelica.glsm.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* What the source passes look up in a parsed shader.
|
||||
*/
|
||||
public final class ShaderIndex {
|
||||
|
||||
public record Directives(Set<String> identifiers, boolean hasConditionalOrMacro, int versionEnd) {
|
||||
private static final Directives NONE = new Directives(Set.of(), false, -1);
|
||||
}
|
||||
|
||||
private final GLSLParser.Translation_unitContext root;
|
||||
private final String source;
|
||||
private final Map<String, List<Token>> identifiers = new HashMap<>();
|
||||
private final Map<String, List<GLSLParser.Postfix_expressionContext>> calls = new HashMap<>();
|
||||
private final List<GLSLParser.DeclarationContext> globalDeclarations = new ArrayList<>();
|
||||
private @Nullable Directives directives;
|
||||
|
||||
public ShaderIndex(GLSLParser.Translation_unitContext root, String source) {
|
||||
this.root = root;
|
||||
this.source = source;
|
||||
collect(root);
|
||||
for (GLSLParser.External_declarationContext external : root.external_declaration()) {
|
||||
if (external.declaration() != null) globalDeclarations.add(external.declaration());
|
||||
}
|
||||
}
|
||||
|
||||
private void collect(ParseTree node) {
|
||||
if (node instanceof TerminalNode terminal) {
|
||||
final Token token = terminal.getSymbol();
|
||||
if (token.getType() == GLSLLexer.IDENTIFIER) identifiers.computeIfAbsent(token.getText(), k -> new ArrayList<>(2)).add(token);
|
||||
return;
|
||||
}
|
||||
if (node instanceof GLSLParser.Postfix_expressionContext p && p.LEFT_PAREN() != null) {
|
||||
calls.computeIfAbsent(calleeName(p), k -> new ArrayList<>(2)).add(p);
|
||||
}
|
||||
for (int i = 0, n = node.getChildCount(); i < n; i++) collect(node.getChild(i));
|
||||
}
|
||||
|
||||
public GLSLParser.Translation_unitContext root() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public String source() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public List<Token> occurrences(String name) {
|
||||
return identifiers.getOrDefault(name, List.of());
|
||||
}
|
||||
|
||||
public List<GLSLParser.Postfix_expressionContext> callsTo(String name) {
|
||||
return calls.getOrDefault(name, List.of());
|
||||
}
|
||||
|
||||
public List<GLSLParser.DeclarationContext> globalDeclarations() {
|
||||
return globalDeclarations;
|
||||
}
|
||||
|
||||
/** Lexes the source on first use. */
|
||||
public Directives directives() {
|
||||
if (directives == null) directives = scanDirectives(source);
|
||||
return directives;
|
||||
}
|
||||
|
||||
private static Directives scanDirectives(String source) {
|
||||
if (source.indexOf('#') < 0) return Directives.NONE;
|
||||
final GLSLLexer lexer = GlslTransformUtils.quietLexer(source);
|
||||
final Set<String> names = new HashSet<>();
|
||||
boolean conditionalOrMacro = false;
|
||||
int versionEnd = -1;
|
||||
boolean inVersion = false;
|
||||
for (Token token = lexer.nextToken(); token.getType() != Token.EOF; token = lexer.nextToken()) {
|
||||
final int type = token.getType();
|
||||
if (type == GLSLLexer.VERSION_DIRECTIVE && versionEnd < 0) {
|
||||
inVersion = true;
|
||||
versionEnd = token.getStopIndex() + 1;
|
||||
continue;
|
||||
}
|
||||
if (inVersion && (type == GLSLLexer.NUMBER || type == GLSLLexer.PROFILE)) {
|
||||
versionEnd = token.getStopIndex() + 1;
|
||||
continue;
|
||||
}
|
||||
inVersion = false;
|
||||
switch (type) {
|
||||
case GLSLLexer.PROGRAM_TEXT, GLSLLexer.MACRO_TEXT, GLSLLexer.CONSTANT_EXPRESSION -> collectIdentifiers(token.getText(), names);
|
||||
case GLSLLexer.IF_DIRECTIVE, GLSLLexer.IFDEF_DIRECTIVE, GLSLLexer.IFNDEF_DIRECTIVE, GLSLLexer.DEFINE_DIRECTIVE -> conditionalOrMacro = true;
|
||||
default -> { }
|
||||
}
|
||||
}
|
||||
return new Directives(names, conditionalOrMacro, versionEnd);
|
||||
}
|
||||
|
||||
private static void collectIdentifiers(String text, Set<String> out) {
|
||||
if (text == null) return;
|
||||
final int len = text.length();
|
||||
int i = 0;
|
||||
while (i < len) {
|
||||
final char c = text.charAt(i);
|
||||
if (c != '_' && !Character.isLetter(c)) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
int end = i + 1;
|
||||
while (end < len) {
|
||||
final char n = text.charAt(end);
|
||||
if (n != '_' && !Character.isLetterOrDigit(n)) break;
|
||||
end++;
|
||||
}
|
||||
out.add(text.substring(i, end));
|
||||
i = end;
|
||||
}
|
||||
}
|
||||
|
||||
public static String calleeName(GLSLParser.Postfix_expressionContext call) {
|
||||
final ParseTree callee = call.getChild(0);
|
||||
return callee instanceof ParserRuleContext ? callee.getText() : "";
|
||||
}
|
||||
|
||||
public static List<GLSLParser.Typeless_declarationContext> declarators(GLSLParser.Init_declarator_listContext list) {
|
||||
final GLSLParser.Single_declarationContext single = list.single_declaration();
|
||||
final List<GLSLParser.Typeless_declarationContext> rest = list.typeless_declaration();
|
||||
if (single == null || single.typeless_declaration() == null) return rest;
|
||||
final List<GLSLParser.Typeless_declarationContext> all = new ArrayList<>(rest.size() + 1);
|
||||
all.add(single.typeless_declaration());
|
||||
all.addAll(rest);
|
||||
return all;
|
||||
}
|
||||
|
||||
public static boolean hasStorageQualifier(@Nullable GLSLParser.Type_qualifierContext qualifier, String keyword) {
|
||||
if (qualifier == null) return false;
|
||||
for (GLSLParser.Single_type_qualifierContext q : qualifier.single_type_qualifier()) {
|
||||
if (q.storage_qualifier() != null && keyword.equals(q.storage_qualifier().getText())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+2
-19
@@ -47,14 +47,6 @@ public final class SpirvShaderTranslator {
|
||||
private SpirvShaderTranslator() {}
|
||||
|
||||
public static @Nullable String glslToGlslEs(String source, int glShaderType, String debugName) {
|
||||
return glslToGlslEsImpl(source, glShaderType, debugName, false);
|
||||
}
|
||||
|
||||
static @Nullable String glslToGlslEsRawForTest(String source, int glShaderType, String debugName) {
|
||||
return glslToGlslEsImpl(source, glShaderType, debugName, true);
|
||||
}
|
||||
|
||||
private static @Nullable String glslToGlslEsImpl(String source, int glShaderType, String debugName, boolean rawOutput) {
|
||||
final int shaderKind = shaderKindForGlType(glShaderType);
|
||||
if (shaderKind < 0) {
|
||||
LOGGER.warn("Unknown GL shader type 0x{} for '{}' - skipping SPIR-V translation", Integer.toHexString(glShaderType), debugName);
|
||||
@@ -74,7 +66,7 @@ public final class SpirvShaderTranslator {
|
||||
try {
|
||||
final String emitted = emitGlslEs(cr.spirv(), debugName, glShaderType, pre.explicitVsInputs());
|
||||
if (emitted == null) return null;
|
||||
return rawOutput ? emitted : postProcessEsOutput(emitted, pre.boolUniforms());
|
||||
return postProcessEsOutput(emitted, pre.boolUniforms());
|
||||
} finally {
|
||||
memFree(cr.spirv());
|
||||
}
|
||||
@@ -163,16 +155,7 @@ public final class SpirvShaderTranslator {
|
||||
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
|
||||
if (single == null || single.typeless_declaration() == null) return;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
if (fst == null || fst.type_qualifier() == null) return;
|
||||
|
||||
boolean isUniform = false;
|
||||
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
|
||||
if (stq.storage_qualifier() != null && "uniform".equals(stq.storage_qualifier().getText())) {
|
||||
isUniform = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isUniform) return;
|
||||
if (fst == null || !ShaderIndex.hasStorageQualifier(fst.type_qualifier(), "uniform")) return;
|
||||
|
||||
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
|
||||
final GLSLParser.Type_specifier_nonarrayContext ts = fst.type_specifier().type_specifier_nonarray();
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
# length - Units: blocks (straight), blocks per leg (circuit), degrees (pan), ticks (static).
|
||||
# extra - passed through to gradle; -Dangelica.* reaches the client (e.g. -Dangelica.flyby.timeOfDay=1500).
|
||||
# -Dangelica.flyby.camera=FIRST_PERSON|THIRD_PERSON_BACK|THIRD_PERSON_FRONT (default FIRST_PERSON).
|
||||
# -Dangelica.flyby.dimension=<id> moves the player there first (1 = End), after world saving is off.
|
||||
# -PclientJvmVendor=adoptium (macOS) picks the JVM vendor for runClient17/21/25.
|
||||
#
|
||||
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
|
||||
# FLYBY_PACING - UNCAPPED | CONFIGURED. Passed through when set.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# End crystals next to the arrival platform. Use with -Dangelica.flyby.dimension=1, route pan.
|
||||
summon EnderCrystal ~ ~2 ~12 {ForgeData:{flyby:1b}}
|
||||
summon EnderCrystal ~8 ~2 ~8 {ForgeData:{flyby:1b}}
|
||||
summon EnderCrystal ~-8 ~4 ~10 {ForgeData:{flyby:1b}}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compute.VertexWriteReplayer;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compute.VoxelizationDispatcher;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.CaptureGate;
|
||||
@@ -10,6 +11,7 @@ import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendOptions;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.MainThreadPump;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VertexWriteReplaySetup;
|
||||
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
|
||||
import org.lwjglx.Lwjgl3ifyEventLoop;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
@@ -164,6 +166,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public long nextSeq() { return TransferThread.nextSeq(); }
|
||||
});
|
||||
private final PipelineApplier pipelineApplier = new PipelineApplier(frameManager, resourceManager, shaderManager, pipelineStore, fboClearTracker, persistentSync, samplerBinder, storageTextureBinder, storageBufferBinder);
|
||||
private final VertexWriteReplayer vertexWriteReplayer = new VertexWriteReplayer(shaderManager, resourceManager, pipelineApplier, computeBinder, SDLGPURenderBackend::s);
|
||||
private final AttachmentClear attachmentClear = new AttachmentClear(frameManager, pipelineStore, shaderManager);
|
||||
private final DepthStencilReadback depthStencilReadback = new DepthStencilReadback(frameManager, resourceManager, pipelineStore, shaderManager, fboClearTracker);
|
||||
private final DrawDispatch drawDispatch = new DrawDispatch(device, frameManager, resourceManager, pipelineApplier, this::enqueuePreCopied);
|
||||
@@ -301,6 +304,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
});
|
||||
pipelineApplier.setDeferredUploadSink(this::enqueuePreCopied);
|
||||
frameManager.setRenderPassEndHook(vertexWriteReplayer::flush);
|
||||
resourceManager.setTextureReplaceHook(this::flushVertexWritesBeforeTextureChange);
|
||||
frameManager.setPreRenderPassHook(() -> {
|
||||
final ContextState st = s();
|
||||
pipelineApplier.flushUniformBlocks(st);
|
||||
@@ -1207,9 +1212,11 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (st.rasterizerDiscard) return;
|
||||
final FrameState f = frameManager.frame();
|
||||
if (!f.frameActive) { f.droppedDrawsThisFrame++; return; }
|
||||
prepareVertexWrites(st);
|
||||
if (mode == GL11.GL_TRIANGLE_FAN && count >= 3) {
|
||||
if (!drawDispatch.drawTriangleFanAsTriangleList(st, first, count)) { f.droppedDrawsThisFrame++; return; }
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysFan", mode, count, 0, first, 0);
|
||||
captureVertexWrites(st, first, count, 1, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
|
||||
@@ -1218,6 +1225,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (!pipelineApplier.applyPipelineAndState(st, f)) { f.droppedDrawsThisFrame++; return; }
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arrays", mode, count, 0, first, 0);
|
||||
SDL_DrawGPUPrimitives(f.renderPass, count, 1, first, 0);
|
||||
captureVertexWrites(st, first, count, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Override public void drawElements(int mode, int count, int type, long indices) {
|
||||
@@ -1227,6 +1235,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elements", mode, count, type, firstIndex, 0);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, 1, firstIndex, 0);
|
||||
captureVertexWrites(st, 0, count, 1, type, indices, 0);
|
||||
}
|
||||
|
||||
@Override public void drawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
|
||||
@@ -1236,6 +1245,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsInstanced", mode, count, type, firstIndex, 0);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, primcount, firstIndex, 0);
|
||||
captureVertexWrites(st, 0, count, primcount, type, indices, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1246,10 +1256,12 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsBaseVertex", mode, count, type, firstIndex, baseVertex);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, 1, firstIndex, baseVertex);
|
||||
captureVertexWrites(st, 0, count, 1, type, indices, baseVertex);
|
||||
}
|
||||
|
||||
private long prepareIndexedDrawBind(ContextState st, int mode, int type, String opName) {
|
||||
if (st.rasterizerDiscard) return 0;
|
||||
prepareVertexWrites(st);
|
||||
if (!drawDispatch.prepareIndexedDraw(st, mode, type)) return 0;
|
||||
final int ebo = st.currentVao.elementBuffer;
|
||||
final long eboHandle = resourceManager.getBufferHandle(ebo);
|
||||
@@ -1278,6 +1290,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int baseVertex = MemoryUtil.memGetInt(pBaseVertex + offset);
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "multiElementsBaseVertex", mode, count, type, firstIndex, baseVertex);
|
||||
drawDispatch.issueIndexedDraw(st, rp, ebo, type, count, 1, firstIndex, baseVertex);
|
||||
captureVertexWrites(st, 0, count, 1, type, indices, baseVertex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1304,6 +1317,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsIndirect", mode, drawcount, type, (int) indirect, 0);
|
||||
SDL_DrawGPUIndexedPrimitivesIndirect(rp, indirectHandle, (int) indirect, drawcount);
|
||||
if (st.boundProgramObj != null && st.boundProgramObj.vertexWriteReplay != null) vertexWriteReplayer.noteIndirectDraw();
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
}
|
||||
@@ -1418,6 +1432,11 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
resourceManager.deleteTexture(texture);
|
||||
}
|
||||
|
||||
/** Captured vertex-write replays name textures by GL id, which a delete or reallocation would make mean something else. */
|
||||
private void flushVertexWritesBeforeTextureChange() {
|
||||
if (vertexWriteReplayer.hasPending()) frameManager.endRenderPassIfActive(FrameManager.PASS_END_COMPUTE);
|
||||
}
|
||||
|
||||
|
||||
@Override public void bindTexture(int target, int texture) {
|
||||
if (isProxyTarget(target)) return;
|
||||
@@ -4271,6 +4290,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public void memoryBarrier(int barriers) {
|
||||
// Graphics storage bindings are read-only on SDL_GPU; RW is compute-pass only
|
||||
frameManager.endCopyPassIfActive();
|
||||
// Replayed vertex image writes land when the render pass ends, and a barrier promises them to the draws after it
|
||||
if ((barriers & (GL42.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL42.GL_TEXTURE_FETCH_BARRIER_BIT)) != 0 && vertexWriteReplayer.hasPending()) {
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COMPUTE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void copyImageSubData(int srcName, int srcTarget, int srcLevel, int srcX, int srcY, int srcZ,
|
||||
@@ -4417,18 +4440,45 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
voxelizationDispatcher.endBatch(pass);
|
||||
}
|
||||
|
||||
@Override public void setVertexWriteReplay(int graphicsProgram, VertexWriteReplaySetup setup) {
|
||||
vertexWriteReplayer.setReplay(graphicsProgram, setup);
|
||||
}
|
||||
|
||||
@Override public boolean supportsComputeImageAtomics() {
|
||||
return device.supportsSpirv() || !device.supportsMsl();
|
||||
}
|
||||
|
||||
@Override public boolean supportsComputeImageAtomics(int width, int height, int depth) {
|
||||
return supportsComputeImageAtomics() || device.supportsImageAtomics(width, height, depth);
|
||||
}
|
||||
|
||||
private void prepareVertexWrites(ContextState st) {
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog != null && prog.vertexWriteReplay != null) vertexWriteReplayer.ensureImageUsage(prog.vertexWriteReplay);
|
||||
}
|
||||
|
||||
private void captureVertexWrites(ContextState st, int first, int count, int instances, int indexType, long indexOffset, int baseVertex) {
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog != null && prog.vertexWriteReplay != null) {
|
||||
vertexWriteReplayer.capture(st, first, count, instances, indexType, indexOffset, baseVertex);
|
||||
if (!frameManager.isRenderPassActive()) vertexWriteReplayer.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
|
||||
if (count <= 0 || primcount <= 0) return;
|
||||
final ContextState st = s();
|
||||
if (st.rasterizerDiscard) return;
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
prepareVertexWrites(st);
|
||||
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
|
||||
pipelineApplier.ensureDrawRenderPass(st);
|
||||
if (!frameManager.isRenderPassActive()) return;
|
||||
if (!pipelineApplier.applyPipelineAndState(st)) return;
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysInstanced", mode, count, 0, first, 0);
|
||||
SDL_DrawGPUPrimitives(frameManager.getRenderPass(), count, primcount, first, 0);
|
||||
captureVertexWrites(st, first, count, primcount, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+94
-19
@@ -45,6 +45,27 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
private final ShaderManager shaderManager;
|
||||
private final SamplerBinder samplerBinder;
|
||||
private boolean droppedDispatchWarned;
|
||||
private ShaderManager.ProgramObject lastUniformProgram;
|
||||
private long lastUniformCb;
|
||||
private long[] samplerOverrides;
|
||||
private long[] rwBufferOverrides;
|
||||
private ByteBuffer[] uniformBlockOverrides;
|
||||
|
||||
public void setSamplerOverrides(long[] byUnit) {
|
||||
samplerOverrides = byUnit;
|
||||
}
|
||||
|
||||
public void setReadWriteStorageBufferOverrides(long[] byGlSlot) {
|
||||
rwBufferOverrides = byGlSlot;
|
||||
}
|
||||
|
||||
public void setUniformBlockOverrides(ByteBuffer[] byIndex) {
|
||||
uniformBlockOverrides = byIndex;
|
||||
}
|
||||
|
||||
public long samplerHandle(int samplerObject, int glTexId) {
|
||||
return samplerBinder.getSampler(samplerObject, glTexId);
|
||||
}
|
||||
|
||||
public ComputeBinder(FrameManager frameManager, ResourceManager resourceManager, ShaderManager shaderManager, SamplerBinder samplerBinder) {
|
||||
this.frameManager = frameManager;
|
||||
@@ -153,6 +174,43 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
|
||||
@Override
|
||||
public long beginBatchedComputeDispatch(ContextState st, boolean cycleRwBuffers) {
|
||||
return beginComputePass(st, cycleRwBuffers, true);
|
||||
}
|
||||
|
||||
public long beginComputePassWithoutStorageBuffers(ContextState st) {
|
||||
return beginComputePass(st, false, false);
|
||||
}
|
||||
|
||||
public boolean bindRoStorageBufferHandles(long pass, ContextState st, long[] handlesByGlSlot) {
|
||||
if (pass == 0) return false;
|
||||
final ShaderManager.ProgramObject prog = shaderManager.getProgram(st.boundProgram);
|
||||
if (prog == null || !prog.linked) return false;
|
||||
final int[] slots = prog.computeBindingMap.roSsboGlSlots();
|
||||
if (slots.length == 0) return true;
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
final PointerBuffer ptrs = stack.mallocPointer(slots.length);
|
||||
for (int i = 0; i < slots.length; i++) {
|
||||
long handle = slots[i] < handlesByGlSlot.length ? handlesByGlSlot[slots[i]] : 0;
|
||||
if (handle == 0) handle = resourceManager.getOrCreateFallbackStorageBuffer();
|
||||
if (handle == 0) return false;
|
||||
ptrs.put(i, handle);
|
||||
}
|
||||
SDL_BindGPUComputeStorageBuffers(pass, 0, ptrs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean rebindSamplers(long pass, ContextState st) {
|
||||
if (pass == 0) return false;
|
||||
final ShaderManager.ProgramObject prog = shaderManager.getProgram(st.boundProgram);
|
||||
if (prog == null || !prog.linked) return false;
|
||||
final ShaderManager.ComputeBindingMap map = prog.computeBindingMap;
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
return bindComputeSamplers(st, prog, pass, map.samplerGlSlots(), map.samplerNames(), stack);
|
||||
}
|
||||
}
|
||||
|
||||
private long beginComputePass(ContextState st, boolean cycleRwBuffers, boolean bindRoBuffers) {
|
||||
final int programId = st.boundProgram;
|
||||
if (programId == 0) return 0;
|
||||
final ShaderManager.ProgramObject prog = shaderManager.getProgram(programId);
|
||||
@@ -196,7 +254,7 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
|
||||
final boolean ok = bindComputeSamplers(st, prog, pass, samplerSlots, map.samplerNames(), stack)
|
||||
&& bindRoStorageTextures(st, prog, pass, map, stack)
|
||||
&& bindRoStorageBuffers(st, pass, roBufSlots, stack);
|
||||
&& (!bindRoBuffers || bindRoStorageBuffers(st, pass, roBufSlots, stack));
|
||||
if (!ok) {
|
||||
SDL_EndGPUComputePass(pass);
|
||||
frameManager.noteComputePassEnded();
|
||||
@@ -298,7 +356,7 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
private SDL_GPUStorageBufferReadWriteBinding.Buffer buildRwBufferBindings(ContextState st, int[] slots, MemoryStack stack, boolean cycle) {
|
||||
final SDL_GPUStorageBufferReadWriteBinding.Buffer buf = SDL_GPUStorageBufferReadWriteBinding.calloc(slots.length, stack);
|
||||
for (int i = 0; i < slots.length; i++) {
|
||||
final long handle = resolveStorageBufHandle(st, slots[i]);
|
||||
final long handle = rwBufferOverrides != null && slots[i] < rwBufferOverrides.length ? rwBufferOverrides[slots[i]] : resolveStorageBufHandle(st, slots[i]);
|
||||
|
||||
if (handle == 0) { warnMissingBinding(st, 1, "RW storage buffer slot", slots[i], true); return null; }
|
||||
buf.get(i).buffer(handle).cycle(cycle);
|
||||
@@ -319,7 +377,8 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
if (texHandle == 0) { warnMissingBinding(st, 2, "sampler texture unit", unit, true); return false; }
|
||||
warnMissingBinding(st, 2, "sampler texture unit", unit, false);
|
||||
}
|
||||
long sampHandle = samplerBinder.getSamplerForUnit(st, unit, texGlId);
|
||||
final long override = samplerOverrides != null && unit >= 0 && unit < samplerOverrides.length ? samplerOverrides[unit] : 0;
|
||||
long sampHandle = override != 0 ? override : samplerBinder.getSamplerForUnit(st, unit, texGlId);
|
||||
if (sampHandle == 0) {
|
||||
sampHandle = resourceManager.getOrCreateDefaultSampler();
|
||||
if (sampHandle == 0) { warnMissingBinding(st, 3, "sampler unit", unit, true); return false; }
|
||||
@@ -374,6 +433,28 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
return true;
|
||||
}
|
||||
|
||||
public ByteBuffer uniformBlockView(ContextState st, ShaderManager.ProgramObject prog, int i) {
|
||||
final ShaderManager.ComputeBindingMap map = prog.computeBindingMap;
|
||||
final int[] uboSlots = map.uboGlSlots();
|
||||
final int[] declaredSizes = map.uboSizes();
|
||||
if (i >= uboSlots.length) return null;
|
||||
final int slot = uboSlots[i];
|
||||
final int bufGlId = st.boundUboByIndex[slot];
|
||||
if (bufGlId == 0) return null;
|
||||
final ByteBuffer shadow = resourceManager.getUboShadow(bufGlId);
|
||||
if (shadow == null) return null;
|
||||
final int offset = st.uboRangeOffset[slot];
|
||||
if (offset < 0 || offset >= shadow.capacity()) return null;
|
||||
int size = (i < declaredSizes.length && declaredSizes[i] > 0) ? declaredSizes[i] : shadow.capacity() - offset;
|
||||
final int rangeSize = st.uboRangeSize[slot];
|
||||
if (rangeSize > 0) size = Math.min(size, rangeSize);
|
||||
size = Math.min(size, shadow.capacity() - offset);
|
||||
if (size <= 0) return null;
|
||||
final ByteBuffer view = frameManager.getUboPushView(bufGlId, shadow);
|
||||
view.position(offset).limit(offset + size);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void pushComputeUboData(ContextState st, long cb, ShaderManager.ProgramObject prog, int[] uboSlots) {
|
||||
if (prog.lastComputeUboHash == null || prog.lastComputeUboHash.length != uboSlots.length) {
|
||||
prog.lastComputeUboHash = new long[uboSlots.length];
|
||||
@@ -381,16 +462,18 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
prog.lastComputeFrame = 0;
|
||||
}
|
||||
final long curFrame = frameManager.getFrameNumber();
|
||||
final boolean cacheValid = cb == prog.lastComputeCb && curFrame == prog.lastComputeFrame;
|
||||
final boolean cacheValid = cb == prog.lastComputeCb && curFrame == prog.lastComputeFrame
|
||||
&& lastUniformProgram == prog && lastUniformCb == cb;
|
||||
final long[] cache = prog.lastComputeUboHash;
|
||||
if (!cacheValid) {
|
||||
for (int i = 0; i < cache.length; i++) cache[i] = 0L;
|
||||
prog.lastComputeCb = cb;
|
||||
prog.lastComputeFrame = curFrame;
|
||||
}
|
||||
lastUniformProgram = prog;
|
||||
lastUniformCb = cb;
|
||||
final ShaderManager.ComputeBindingMap map = prog.computeBindingMap;
|
||||
final boolean[] isDefault = map.uboIsDefaultBlock();
|
||||
final int[] declaredSizes = map.uboSizes();
|
||||
final UniformStaging us = st.uniformStaging(prog);
|
||||
for (int i = 0; i < uboSlots.length; i++) {
|
||||
final ByteBuffer view;
|
||||
@@ -398,21 +481,13 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
if (us.fsUniformBuf == null || prog.fragmentUboSize <= 0) continue;
|
||||
view = us.fsUniformBuf;
|
||||
view.position(0).limit(prog.fragmentUboSize);
|
||||
} else if (uniformBlockOverrides != null && i < uniformBlockOverrides.length && uniformBlockOverrides[i] != null) {
|
||||
view = uniformBlockOverrides[i];
|
||||
view.position(0);
|
||||
if (!view.hasRemaining()) continue;
|
||||
} else {
|
||||
final int slot = uboSlots[i];
|
||||
final int bufGlId = st.boundUboByIndex[slot];
|
||||
if (bufGlId == 0) continue;
|
||||
final ByteBuffer shadow = resourceManager.getUboShadow(bufGlId);
|
||||
if (shadow == null) continue;
|
||||
final int offset = st.uboRangeOffset[slot];
|
||||
if (offset < 0 || offset >= shadow.capacity()) continue;
|
||||
int size = (i < declaredSizes.length && declaredSizes[i] > 0) ? declaredSizes[i] : shadow.capacity() - offset;
|
||||
final int rangeSize = st.uboRangeSize[slot];
|
||||
if (rangeSize > 0) size = Math.min(size, rangeSize);
|
||||
size = Math.min(size, shadow.capacity() - offset);
|
||||
if (size <= 0) continue;
|
||||
view = frameManager.getUboPushView(bufGlId, shadow);
|
||||
view.position(offset).limit(offset + size);
|
||||
view = uniformBlockView(st, prog, i);
|
||||
if (view == null) continue;
|
||||
}
|
||||
final int pos = view.position();
|
||||
final int len = view.remaining();
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.compute;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.cross.CrossCompileCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.msl.MslCrossCompile;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDL_GPUComputePipelineCreateInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUStorageTextureReadWriteBinding;
|
||||
import org.lwjgl.sdl.SDL_GPUTextureCreateInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUTextureRegion;
|
||||
import org.lwjgl.sdl.SDL_GPUTextureTransferInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUTransferBufferCreateInfo;
|
||||
import org.lwjgl.system.JNI;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.system.macosx.ObjCRuntime;
|
||||
import org.lwjgl.util.shaderc.Shaderc;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
|
||||
public final class ImageAtomicsProbe {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
|
||||
private static final String SOURCE = "angelica:sdlgpu/atomic_probe.csh";
|
||||
public static final int MTL_LANGUAGE_VERSION_3_1 = (3 << 16) + 1;
|
||||
private static final int GROUPS = 64;
|
||||
|
||||
private final long device;
|
||||
private long pipeline;
|
||||
|
||||
public ImageAtomicsProbe(long device) {
|
||||
this.device = device;
|
||||
final SpirvCompiler.Result compiled = SpirvCompiler.compile(ShaderLoader.getShaderSource(SOURCE), Shaderc.shaderc_compute_shader, "atomic_probe", SpirvCompiler.Options.vulkanForced460Core(), false);
|
||||
if (compiled.spirv() == null) {
|
||||
LOG.warn("Metal image atomics: probe pipeline failed: {}", compiled.error());
|
||||
return;
|
||||
}
|
||||
CrossCompileCache.Output msl = null;
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(compiled.spirv());
|
||||
msl = MslCrossCompile.compileAtVersion(compiled.spirv(), GL43.GL_COMPUTE_SHADER, MslCrossCompile.SPVC_MSL_3_1);
|
||||
pipeline = SDL_CreateGPUComputePipeline(device, SDL_GPUComputePipelineCreateInfo.calloc(stack)
|
||||
.code(msl.code())
|
||||
.entrypoint(stack.ASCII(msl.entrypoint()))
|
||||
.format(SDL_GPU_SHADERFORMAT_MSL)
|
||||
.num_readwrite_storage_textures(1)
|
||||
.threadcount_x(InvocationDispatch.WORKGROUP_SIZE).threadcount_y(1).threadcount_z(1));
|
||||
if (pipeline == 0) LOG.warn("Metal image atomics: probe pipeline failed: {}", SDLError.SDL_GetError());
|
||||
} catch (RuntimeException e) {
|
||||
LOG.warn("Metal image atomics: probe pipeline failed", e);
|
||||
} finally {
|
||||
if (msl != null) MemoryUtil.memFree(msl.code());
|
||||
MemoryUtil.memFree(compiled.spirv());
|
||||
}
|
||||
}
|
||||
|
||||
public static int defaultMslVersion() {
|
||||
try {
|
||||
final long msgSend = ObjCRuntime.getLibrary().getFunctionAddress("objc_msgSend");
|
||||
final long cls = ObjCRuntime.objc_getClass("MTLCompileOptions");
|
||||
if (msgSend == 0 || cls == 0) return 0;
|
||||
final long options = JNI.invokePPP(cls, ObjCRuntime.sel_getUid("new"), msgSend);
|
||||
if (options == 0) return 0;
|
||||
final long version = JNI.invokePPP(options, ObjCRuntime.sel_getUid("languageVersion"), msgSend);
|
||||
JNI.invokePPV(options, ObjCRuntime.sel_getUid("release"), msgSend);
|
||||
return (int) version;
|
||||
} catch (RuntimeException | LinkageError e) {
|
||||
LOG.warn("Could not read the default Metal language version", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean run(int width, int height) {
|
||||
if (pipeline == 0) return false;
|
||||
long texture = 0, upload = 0, download = 0, cb = 0;
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
texture = SDL_CreateGPUTexture(device, SDL_GPUTextureCreateInfo.calloc(stack)
|
||||
.type(SDL_GPU_TEXTURETYPE_2D)
|
||||
.format(SDL_GPU_TEXTUREFORMAT_R32_INT)
|
||||
.usage(SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE)
|
||||
.width(width).height(height).layer_count_or_depth(1).num_levels(1)
|
||||
.sample_count(SDL_GPU_SAMPLECOUNT_1));
|
||||
if (texture == 0) return failSdl(width, height, "texture");
|
||||
|
||||
upload = SDL_CreateGPUTransferBuffer(device, SDL_GPUTransferBufferCreateInfo.calloc(stack).usage(SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD).size(Integer.BYTES));
|
||||
download = SDL_CreateGPUTransferBuffer(device, SDL_GPUTransferBufferCreateInfo.calloc(stack).usage(SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD).size(Integer.BYTES));
|
||||
if (upload == 0 || download == 0) return failSdl(width, height, "transfer buffer");
|
||||
final long zero = nSDL_MapGPUTransferBuffer(device, upload, false);
|
||||
if (zero == 0) return failSdl(width, height, "map");
|
||||
MemoryUtil.memPutInt(zero, 0);
|
||||
SDL_UnmapGPUTransferBuffer(device, upload);
|
||||
|
||||
cb = SDL_AcquireGPUCommandBuffer(device);
|
||||
if (cb == 0) return failSdl(width, height, "command buffer");
|
||||
final SDL_GPUTextureRegion region = SDL_GPUTextureRegion.calloc(stack).texture(texture).w(1).h(1).d(1);
|
||||
long copy = SDL_BeginGPUCopyPass(cb);
|
||||
SDL_UploadToGPUTexture(copy, SDL_GPUTextureTransferInfo.calloc(stack).transfer_buffer(upload), region, false);
|
||||
SDL_EndGPUCopyPass(copy);
|
||||
|
||||
final SDL_GPUStorageTextureReadWriteBinding.Buffer binding = SDL_GPUStorageTextureReadWriteBinding.calloc(1, stack);
|
||||
binding.get(0).texture(texture);
|
||||
final long pass = SDL_BeginGPUComputePass(cb, binding, null);
|
||||
if (pass == 0) return failSdl(width, height, "compute pass");
|
||||
SDL_BindGPUComputePipeline(pass, pipeline);
|
||||
SDL_DispatchGPUCompute(pass, GROUPS, 1, 1);
|
||||
SDL_EndGPUComputePass(pass);
|
||||
|
||||
copy = SDL_BeginGPUCopyPass(cb);
|
||||
SDL_DownloadFromGPUTexture(copy, region, SDL_GPUTextureTransferInfo.calloc(stack).transfer_buffer(download));
|
||||
SDL_EndGPUCopyPass(copy);
|
||||
final boolean submitted = SDL_SubmitGPUCommandBuffer(cb);
|
||||
cb = 0;
|
||||
if (!submitted) return failSdl(width, height, "submit");
|
||||
SDL_WaitForGPUIdle(device);
|
||||
|
||||
final long result = nSDL_MapGPUTransferBuffer(device, download, false);
|
||||
if (result == 0) return failSdl(width, height, "map");
|
||||
final int adds = MemoryUtil.memGetInt(result);
|
||||
SDL_UnmapGPUTransferBuffer(device, download);
|
||||
|
||||
final int invocations = GROUPS * InvocationDispatch.WORKGROUP_SIZE;
|
||||
if (adds != invocations) LOG.info("Metal image atomics on {}x{}: {} invocations added {}", width, height, invocations, adds);
|
||||
return adds == invocations;
|
||||
} catch (RuntimeException e) {
|
||||
return fail(width, height, e.toString());
|
||||
} finally {
|
||||
if (cb != 0) SDL_CancelGPUCommandBuffer(cb);
|
||||
if (download != 0) SDL_ReleaseGPUTransferBuffer(device, download);
|
||||
if (upload != 0) SDL_ReleaseGPUTransferBuffer(device, upload);
|
||||
if (texture != 0) SDL_ReleaseGPUTexture(device, texture);
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (pipeline != 0) SDL_ReleaseGPUComputePipeline(device, pipeline);
|
||||
pipeline = 0;
|
||||
}
|
||||
|
||||
private static boolean failSdl(int width, int height, String what) {
|
||||
return fail(width, height, what + " failed: " + SDLError.SDL_GetError());
|
||||
}
|
||||
|
||||
private static boolean fail(int width, int height, String reason) {
|
||||
LOG.warn("Metal image atomics on {}x{}: {}", width, height, reason);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.compute;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
|
||||
public final class InvocationDispatch {
|
||||
static final int MAX_GROUPS_PER_DISPATCH = 65535;
|
||||
static final int WORKGROUP_SIZE = 64;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface IntUniformWriter {
|
||||
void write(ContextState st, int location, int value);
|
||||
}
|
||||
|
||||
private InvocationDispatch() {}
|
||||
|
||||
static void dispatch(ComputeDispatchSink sink, ContextState st, long pass, long invocations, int invocationBaseLocation, IntUniformWriter writer) {
|
||||
final long groups = (invocations + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
|
||||
for (long g0 = 0; g0 < groups; g0 += MAX_GROUPS_PER_DISPATCH) {
|
||||
if (invocationBaseLocation >= 0) writer.write(st, invocationBaseLocation, (int) (g0 * WORKGROUP_SIZE));
|
||||
sink.pushPendingComputeUniforms(st);
|
||||
sink.dispatchInBatch(pass, (int) Math.min(MAX_GROUPS_PER_DISPATCH, groups - g0), 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+635
@@ -0,0 +1,635 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.compute;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VertexWriteReplaySetup;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.opengl.GL41;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.IntSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Performs the image writes of a vertex shader that SDL-GPU strips (graphics stages may only read storage images).
|
||||
* Every draw made with a program that has a {@link Replay} is captured: its vertex and index buffers, attribute layout,
|
||||
* draw range, the uniform values the replay reads and the textures it samples. When the render pass ends, the replay
|
||||
* compute shader re-runs the vertex shader for each captured vertex and performs the writes.
|
||||
*/
|
||||
public final class VertexWriteReplayer {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
|
||||
private static final int MAX_CAPTURES = 1 << 16;
|
||||
private static final int[] COMPONENT_SIZE = { 4, 2, 1, 1, 2, 2, 4, 4, 4, 4, 4, 4 };
|
||||
private static final int FIRST_PACKED_TYPE = 9;
|
||||
private static final int PACKED_10F_11F_11F = 11;
|
||||
|
||||
private static final int ENABLED = 0x2000;
|
||||
private static final int BGRA = 0x4000;
|
||||
private static final int NO_ELEMENT = 0x8000;
|
||||
private static final int MAX_DIVISOR = 0xFFFF;
|
||||
private static final int UNCHANGED = -1;
|
||||
private static final int TEXTURE_UNITS = 32;
|
||||
private static final int SAMPLER_OBJECT_OF_DRAW = -1;
|
||||
|
||||
public static final class Replay {
|
||||
final int replayProgram;
|
||||
final VertexWriteReplaySetup setup;
|
||||
|
||||
private boolean resolved;
|
||||
private boolean broken;
|
||||
int[] inputLocations;
|
||||
int[] uniformSources;
|
||||
int[] uniformTargets;
|
||||
int[] samplerUnits;
|
||||
IntSupplier[] samplerSources;
|
||||
int[] samplerObjects;
|
||||
int[] packStorageSlots;
|
||||
int[] packRwStorageSlots;
|
||||
int[] uniformBlockIndices;
|
||||
int uniformBlockCount;
|
||||
int locAttr, locAttrDefault, locDraw, locIndex, locRestart, locInvocationBase;
|
||||
long[] lastHashes;
|
||||
boolean lastHashesApplied;
|
||||
|
||||
public Replay(VertexWriteReplaySetup setup) {
|
||||
this.replayProgram = setup.replayProgram();
|
||||
this.setup = setup;
|
||||
}
|
||||
|
||||
IntSupplier[] images() {
|
||||
return setup.images();
|
||||
}
|
||||
|
||||
boolean resolve(ShaderManager.ProgramObject graphics, ShaderManager.ProgramObject replay) {
|
||||
if (resolved) return !broken;
|
||||
resolved = true;
|
||||
|
||||
final String[] inputNames = setup.inputNames();
|
||||
inputLocations = new int[inputNames.length];
|
||||
for (int i = 0; i < inputNames.length; i++) {
|
||||
int loc = graphics.resolvedAttribLocations.getInt(inputNames[i]);
|
||||
if (loc < 0) loc = graphics.attribLocationBindings.getInt(inputNames[i]);
|
||||
if (loc < 0) loc = setup.inputLocations()[i];
|
||||
inputLocations[i] = loc;
|
||||
}
|
||||
|
||||
final IntArrayList sources = new IntArrayList();
|
||||
final IntArrayList targets = new IntArrayList();
|
||||
final List<String> samplerNames = new ArrayList<>();
|
||||
for (int target = 0; target < replay.uniformSlotCount; target++) {
|
||||
final String name = replay.locationName[target];
|
||||
if (name == null || name.startsWith("_vg_")) continue;
|
||||
if (replay.locationKind[target] == ShaderManager.ProgramObject.LOCATION_KIND_SAMPLER) {
|
||||
samplerNames.add(name);
|
||||
continue;
|
||||
}
|
||||
if (replay.locationKind[target] != ShaderManager.ProgramObject.LOCATION_KIND_PLAIN) continue;
|
||||
final int source = graphics.nameToLocation.getInt(name);
|
||||
if (source < 0) continue;
|
||||
sources.add(source);
|
||||
targets.add(target);
|
||||
}
|
||||
uniformSources = sources.toIntArray();
|
||||
uniformTargets = targets.toIntArray();
|
||||
lastHashes = new long[uniformSources.length];
|
||||
resolveSamplers(graphics, replay, samplerNames);
|
||||
|
||||
final IntArrayList storage = new IntArrayList();
|
||||
for (int slot : replay.computeBindingMap.roSsboGlSlots()) {
|
||||
final boolean vertexBuffer = slot >= setup.vertexBufferBinding() && slot < setup.vertexBufferBinding() + setup.vertexBufferCount();
|
||||
if (!vertexBuffer && slot != setup.indexBufferBinding()) storage.add(slot);
|
||||
}
|
||||
packStorageSlots = storage.toIntArray();
|
||||
packRwStorageSlots = replay.computeBindingMap.rwSsboGlSlots().clone();
|
||||
final IntArrayList blocks = new IntArrayList();
|
||||
final boolean[] isDefault = replay.computeBindingMap.uboIsDefaultBlock();
|
||||
for (int i = 0; i < replay.computeBindingMap.uboGlSlots().length; i++) {
|
||||
if (i >= isDefault.length || !isDefault[i]) blocks.add(i);
|
||||
}
|
||||
uniformBlockIndices = blocks.toIntArray();
|
||||
uniformBlockCount = replay.computeBindingMap.uboGlSlots().length;
|
||||
|
||||
locAttr = replay.nameToLocation.getInt("_vg_attr");
|
||||
locAttrDefault = replay.nameToLocation.getInt("_vg_attrDefault");
|
||||
locDraw = replay.nameToLocation.getInt("_vg_draw");
|
||||
locIndex = replay.nameToLocation.getInt("_vg_index");
|
||||
locRestart = replay.nameToLocation.getInt("_vg_restart");
|
||||
locInvocationBase = replay.nameToLocation.getInt("_vg_invocationBase");
|
||||
if (locDraw < 0 || locInvocationBase < 0) {
|
||||
broken = true;
|
||||
LOG.warn("Vertex write replay program {} is missing its dispatch uniforms; its writes are dropped", replayProgram);
|
||||
}
|
||||
return !broken;
|
||||
}
|
||||
|
||||
private void resolveSamplers(ShaderManager.ProgramObject graphics, ShaderManager.ProgramObject replay, List<String> names) {
|
||||
final IntArrayList units = new IntArrayList();
|
||||
final List<IntSupplier> sources = new ArrayList<>();
|
||||
final IntArrayList objects = new IntArrayList();
|
||||
final IntArrayList ownTextureIndices = new IntArrayList();
|
||||
final List<String> ownTextureNames = new ArrayList<>();
|
||||
final List<String> pipelineNames = Arrays.asList(setup.samplerNames());
|
||||
for (String name : names) {
|
||||
int unit = graphics.samplerTextureUnits.getInt(name);
|
||||
if (unit < 0) {
|
||||
final int i = pipelineNames.indexOf(name);
|
||||
if (i < 0) continue;
|
||||
if (setup.samplerTextures()[i] != null && setup.samplerUnits()[i] < 0) {
|
||||
ownTextureNames.add(name);
|
||||
ownTextureIndices.add(i);
|
||||
continue;
|
||||
}
|
||||
unit = setup.samplerUnits()[i];
|
||||
if (unit < 0) continue;
|
||||
}
|
||||
replay.samplerTextureUnits.put(name, unit);
|
||||
if (!units.contains(unit)) {
|
||||
units.add(unit);
|
||||
sources.add(null);
|
||||
objects.add(SAMPLER_OBJECT_OF_DRAW);
|
||||
}
|
||||
}
|
||||
int free = TEXTURE_UNITS - 1;
|
||||
for (int k = 0; k < ownTextureNames.size(); k++) {
|
||||
while (free >= 0 && units.contains(free)) free--;
|
||||
if (free < 0) break;
|
||||
final int i = ownTextureIndices.getInt(k);
|
||||
replay.samplerTextureUnits.put(ownTextureNames.get(k), free);
|
||||
units.add(free);
|
||||
sources.add(setup.samplerTextures()[i]);
|
||||
objects.add(setup.samplerObjects()[i]);
|
||||
}
|
||||
samplerUnits = units.toIntArray();
|
||||
samplerSources = sources.toArray(new IntSupplier[0]);
|
||||
samplerObjects = objects.toIntArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Capture {
|
||||
Replay replay;
|
||||
long[] buffers = new long[0];
|
||||
int bufferCount;
|
||||
long indexBuffer;
|
||||
float[] attr = new float[0];
|
||||
float[] attrDefault = new float[0];
|
||||
int[] samplerTextures = new int[0];
|
||||
long[] samplerHandles = new long[0];
|
||||
int[] imageTextures = new int[0];
|
||||
long[] packStorage = new long[0];
|
||||
long[] packRwStorage = new long[0];
|
||||
ByteBuffer[] uniformBlocks = new ByteBuffer[0];
|
||||
int first, count, instances, baseVertex, indexSize, indexOffset, indexCount;
|
||||
boolean restart;
|
||||
int restartIndex;
|
||||
}
|
||||
|
||||
private final ShaderManager shaderManager;
|
||||
private final ResourceManager resourceManager;
|
||||
private final PipelineApplier pipelineApplier;
|
||||
private final ComputeBinder computeBinder;
|
||||
private final Supplier<ContextState> state;
|
||||
|
||||
private final ArrayList<Capture> captures = new ArrayList<>();
|
||||
private int pending;
|
||||
private float[] uniformValues = new float[4096];
|
||||
private int[] uniformLengths = new int[256];
|
||||
private int uniformValueCount;
|
||||
private int uniformCount;
|
||||
private boolean flushing;
|
||||
private final long[] handlesByGlSlot = new long[ContextState.MAX_INDEXED_BUFFERS];
|
||||
private final long[] rwHandlesByGlSlot = new long[ContextState.MAX_INDEXED_BUFFERS];
|
||||
private final long[] samplerOverrides = new long[TEXTURE_UNITS];
|
||||
private final int[] savedImages = new int[ContextState.MAX_IMAGE_UNITS];
|
||||
private final int[] savedLevels = new int[ContextState.MAX_IMAGE_UNITS];
|
||||
private final int[] savedAccess = new int[ContextState.MAX_IMAGE_UNITS];
|
||||
private final int[] savedTextures = new int[TEXTURE_UNITS];
|
||||
private int uniformCursor;
|
||||
private int valueCursor;
|
||||
private final InvocationDispatch.IntUniformWriter invocationBaseWriter = (st, location, value) -> putIntVector(st, location, 1, value, 0, 0, 0);
|
||||
private final Set<String> warned = new HashSet<>();
|
||||
|
||||
public VertexWriteReplayer(ShaderManager shaderManager, ResourceManager resourceManager, PipelineApplier pipelineApplier, ComputeBinder computeBinder, Supplier<ContextState> state) {
|
||||
this.shaderManager = shaderManager;
|
||||
this.resourceManager = resourceManager;
|
||||
this.pipelineApplier = pipelineApplier;
|
||||
this.computeBinder = computeBinder;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public void setReplay(int graphicsProgram, VertexWriteReplaySetup setup) {
|
||||
final ShaderManager.ProgramObject prog = shaderManager.getProgram(graphicsProgram);
|
||||
if (prog == null) return;
|
||||
if (setup == null) {
|
||||
prog.vertexWriteReplay = null;
|
||||
return;
|
||||
}
|
||||
if (setup.images().length > ContextState.MAX_IMAGE_UNITS) {
|
||||
warnOnce("images:" + setup.replayProgram(), "Vertex write replay program " + setup.replayProgram() + " uses " + setup.images().length + " images, more than the " + ContextState.MAX_IMAGE_UNITS + " image units; its writes are dropped");
|
||||
prog.vertexWriteReplay = null;
|
||||
return;
|
||||
}
|
||||
prog.vertexWriteReplay = new Replay(setup);
|
||||
}
|
||||
|
||||
public void ensureImageUsage(Replay replay) {
|
||||
final IntSupplier[] images = replay.images();
|
||||
final int written = replay.setup.writtenImageCount();
|
||||
for (int i = 0; i < images.length; i++) {
|
||||
final int texture = images[i].getAsInt();
|
||||
if (texture != 0) resourceManager.ensureTextureUsage(texture, SDLGPURenderBackend.imageUsageForAccess(imageAccess(i, written)));
|
||||
}
|
||||
}
|
||||
|
||||
public void capture(ContextState st, int first, int count, int instances, int indexType, long indexOffset, int baseVertex) {
|
||||
final ShaderManager.ProgramObject graphics = st.boundProgramObj;
|
||||
final Replay replay = graphics.vertexWriteReplay;
|
||||
final ShaderManager.ProgramObject twin = shaderManager.getProgram(replay.replayProgram);
|
||||
if (twin == null || !twin.linked || twin.sdlComputePipeline == 0) return;
|
||||
if (!replay.resolve(graphics, twin)) return;
|
||||
if (count <= 0 || instances <= 0) return;
|
||||
if (pending == MAX_CAPTURES) {
|
||||
warnOnce("captures", "More than " + MAX_CAPTURES + " draws to replay vertex image writes for in one render pass; the rest are dropped");
|
||||
return;
|
||||
}
|
||||
|
||||
final IntSupplier[] images = replay.images();
|
||||
final Capture c = acquire();
|
||||
c.replay = replay;
|
||||
if (c.buffers.length != replay.setup.vertexBufferCount()) c.buffers = new long[replay.setup.vertexBufferCount()];
|
||||
c.bufferCount = 0;
|
||||
if (!captureInputs(st, replay, c)) return;
|
||||
|
||||
if (indexType != 0) {
|
||||
c.indexBuffer = resourceManager.getBufferHandle(st.currentVao.elementBuffer);
|
||||
c.indexSize = indexType == GL11.GL_UNSIGNED_BYTE ? 1 : indexType == GL11.GL_UNSIGNED_SHORT ? 2 : 4;
|
||||
c.indexOffset = (int) indexOffset;
|
||||
if (c.indexBuffer == 0) return;
|
||||
final long indexBytes = resourceManager.getBufferSize(st.currentVao.elementBuffer);
|
||||
c.indexCount = indexBytes <= 0 ? -1 : (int) Math.min(Math.max(0L, indexBytes - indexOffset) / c.indexSize, Integer.MAX_VALUE);
|
||||
c.restart = st.primitiveRestartEnabled && indexType != GL11.GL_UNSIGNED_BYTE;
|
||||
c.restartIndex = indexType == GL11.GL_UNSIGNED_SHORT ? st.primitiveRestartSentinel & 0xFFFF : st.primitiveRestartSentinel;
|
||||
} else {
|
||||
c.indexBuffer = 0;
|
||||
c.indexSize = 0;
|
||||
c.indexOffset = 0;
|
||||
c.indexCount = 0;
|
||||
c.restart = false;
|
||||
}
|
||||
c.first = first;
|
||||
c.count = count;
|
||||
c.instances = instances;
|
||||
c.baseVertex = baseVertex;
|
||||
|
||||
if (c.samplerTextures.length != replay.samplerUnits.length) {
|
||||
c.samplerTextures = new int[replay.samplerUnits.length];
|
||||
c.samplerHandles = new long[replay.samplerUnits.length];
|
||||
}
|
||||
for (int i = 0; i < replay.samplerUnits.length; i++) {
|
||||
final int unit = replay.samplerUnits[i];
|
||||
final IntSupplier source = replay.samplerSources[i];
|
||||
c.samplerTextures[i] = source != null ? source.getAsInt() : st.boundTextures[unit];
|
||||
final int samplerObject = replay.samplerObjects[i] != SAMPLER_OBJECT_OF_DRAW ? replay.samplerObjects[i] : st.boundSamplerObjects[unit];
|
||||
c.samplerHandles[i] = computeBinder.samplerHandle(samplerObject, c.samplerTextures[i]);
|
||||
}
|
||||
if (c.imageTextures.length != images.length) c.imageTextures = new int[images.length];
|
||||
for (int i = 0; i < images.length; i++) c.imageTextures[i] = images[i].getAsInt();
|
||||
if (c.packStorage.length != replay.packStorageSlots.length) c.packStorage = new long[replay.packStorageSlots.length];
|
||||
for (int i = 0; i < replay.packStorageSlots.length; i++) {
|
||||
c.packStorage[i] = resourceManager.getBufferHandle(st.boundSsboByIndex[replay.packStorageSlots[i]]);
|
||||
}
|
||||
if (c.packRwStorage.length != replay.packRwStorageSlots.length) c.packRwStorage = new long[replay.packRwStorageSlots.length];
|
||||
for (int i = 0; i < replay.packRwStorageSlots.length; i++) {
|
||||
c.packRwStorage[i] = resourceManager.getBufferHandle(st.boundSsboByIndex[replay.packRwStorageSlots[i]]);
|
||||
}
|
||||
captureUniformBlocks(st, twin, replay, c);
|
||||
|
||||
captureUniforms(st, graphics, replay);
|
||||
pending++;
|
||||
}
|
||||
|
||||
private boolean captureInputs(ContextState st, Replay replay, Capture c) {
|
||||
final int n = replay.inputLocations.length;
|
||||
if (c.attr.length < n * 4) {
|
||||
c.attr = new float[n * 4];
|
||||
c.attrDefault = new float[n * 4];
|
||||
}
|
||||
final ContextState.VAOState vao = st.currentVao;
|
||||
for (int k = 0; k < n; k++) {
|
||||
final int loc = replay.inputLocations[k];
|
||||
int x = 0, y = 0, z = 0, w = 0;
|
||||
if (loc >= 0 && loc < ContextState.MAX_VERTEX_ATTRIBS) {
|
||||
System.arraycopy(st.attribDefaults, loc * 4, c.attrDefault, k * 4, 4);
|
||||
if (vao.attribEnabled[loc]) {
|
||||
final int type = componentType(vao.attribType[loc]);
|
||||
final int binding = vao.attribBinding[loc];
|
||||
final int buffer = vao.bindingBuffer[binding];
|
||||
final long handle = type >= 0 ? resourceManager.getBufferHandle(buffer) : 0;
|
||||
if (type < 0) warnOnce("type:" + vao.attribType[loc], "Vertex write replay cannot read vertex attribute type 0x" + Integer.toHexString(vao.attribType[loc]) + "; that input reads its default value");
|
||||
if (handle != 0) {
|
||||
final int slot = bufferSlot(c, handle);
|
||||
if (slot < 0) {
|
||||
warnOnce("buffers", "A draw reads more than " + c.buffers.length + " vertex buffers; its vertex image writes are dropped");
|
||||
return false;
|
||||
}
|
||||
final boolean bgra = vao.attribSize[loc] == GL12.GL_BGRA;
|
||||
final int size = type == PACKED_10F_11F_11F ? 3 : bgra ? 4 : vao.attribSize[loc];
|
||||
final int elementBytes = type >= FIRST_PACKED_TYPE ? 4 : size * COMPONENT_SIZE[type];
|
||||
final int stride = vao.bindingStride[binding] != 0 ? vao.bindingStride[binding] : elementBytes;
|
||||
final long offset = vao.bindingOffset[binding] + vao.attribRelativeOffset[loc];
|
||||
final int divisor = Math.min(vao.bindingDivisor[binding], MAX_DIVISOR);
|
||||
final long bufferSize = resourceManager.getBufferSize(buffer);
|
||||
final boolean noElement = bufferSize > 0 && bufferSize < offset + elementBytes;
|
||||
x = slot | type << 4 | size << 8 | (vao.attribNormalized[loc] ? 0x800 : 0) | (vao.attribIsInteger[loc] ? 0x1000 : 0)
|
||||
| ENABLED | (bgra ? BGRA : 0) | (noElement ? NO_ELEMENT : 0) | divisor << 16;
|
||||
y = (int) offset;
|
||||
z = stride;
|
||||
w = lastElement(bufferSize, offset, elementBytes, stride);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.attrDefault[k * 4] = 0f;
|
||||
c.attrDefault[k * 4 + 1] = 0f;
|
||||
c.attrDefault[k * 4 + 2] = 0f;
|
||||
c.attrDefault[k * 4 + 3] = 1f;
|
||||
}
|
||||
c.attr[k * 4] = Float.intBitsToFloat(x);
|
||||
c.attr[k * 4 + 1] = Float.intBitsToFloat(y);
|
||||
c.attr[k * 4 + 2] = Float.intBitsToFloat(z);
|
||||
c.attr[k * 4 + 3] = Float.intBitsToFloat(w);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int lastElement(long bufferSize, long offset, int elementBytes, int stride) {
|
||||
if (bufferSize <= 0) return -1;
|
||||
final long room = bufferSize - offset - elementBytes;
|
||||
if (room < 0 || stride <= 0) return 0;
|
||||
return (int) Math.min(room / stride, 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
private static int bufferSlot(Capture c, long handle) {
|
||||
for (int i = 0; i < c.bufferCount; i++) {
|
||||
if (c.buffers[i] == handle) return i;
|
||||
}
|
||||
if (c.bufferCount == c.buffers.length) return -1;
|
||||
c.buffers[c.bufferCount] = handle;
|
||||
return c.bufferCount++;
|
||||
}
|
||||
|
||||
static int componentType(int glType) {
|
||||
return switch (glType) {
|
||||
case GL11.GL_FLOAT -> 0;
|
||||
case GL30.GL_HALF_FLOAT -> 1;
|
||||
case GL11.GL_BYTE -> 2;
|
||||
case GL11.GL_UNSIGNED_BYTE -> 3;
|
||||
case GL11.GL_SHORT -> 4;
|
||||
case GL11.GL_UNSIGNED_SHORT -> 5;
|
||||
case GL11.GL_INT -> 6;
|
||||
case GL11.GL_UNSIGNED_INT -> 7;
|
||||
case GL41.GL_FIXED -> 8;
|
||||
case GL33.GL_INT_2_10_10_10_REV -> 9;
|
||||
case GL12.GL_UNSIGNED_INT_2_10_10_10_REV -> 10;
|
||||
case GL30.GL_UNSIGNED_INT_10F_11F_11F_REV -> PACKED_10F_11F_11F;
|
||||
default -> -1;
|
||||
};
|
||||
}
|
||||
|
||||
private void captureUniformBlocks(ContextState st, ShaderManager.ProgramObject twin, Replay replay, Capture c) {
|
||||
if (c.uniformBlocks.length != replay.uniformBlockCount) c.uniformBlocks = new ByteBuffer[replay.uniformBlockCount];
|
||||
for (int index : replay.uniformBlockIndices) {
|
||||
final ByteBuffer view = computeBinder.uniformBlockView(st, twin, index);
|
||||
if (view == null) {
|
||||
if (c.uniformBlocks[index] != null) c.uniformBlocks[index].limit(0);
|
||||
continue;
|
||||
}
|
||||
ByteBuffer copy = c.uniformBlocks[index];
|
||||
if (copy == null || copy.capacity() < view.remaining()) copy = c.uniformBlocks[index] = ByteBuffer.allocateDirect(view.remaining()).order(ByteOrder.nativeOrder());
|
||||
copy.clear();
|
||||
copy.put(view);
|
||||
copy.flip();
|
||||
}
|
||||
}
|
||||
|
||||
private void captureUniforms(ContextState st, ShaderManager.ProgramObject graphics, Replay replay) {
|
||||
final UniformStaging us = st.uniformStaging(graphics);
|
||||
final long[] hashes = us.uniformValueHashBySlot;
|
||||
for (int u = 0; u < replay.uniformSources.length; u++) {
|
||||
final int source = replay.uniformSources[u];
|
||||
final float[] value = us.uniformDataBySlot != null && source < us.uniformDataBySlot.length ? us.uniformDataBySlot[source] : null;
|
||||
final long hash = hashes != null && source < hashes.length ? hashes[source] : 0L;
|
||||
if (uniformCount == uniformLengths.length) uniformLengths = Arrays.copyOf(uniformLengths, uniformLengths.length * 2);
|
||||
if (value != null && replay.lastHashesApplied && hash == replay.lastHashes[u]) {
|
||||
uniformLengths[uniformCount++] = UNCHANGED;
|
||||
continue;
|
||||
}
|
||||
replay.lastHashes[u] = hash;
|
||||
final int len = value == null ? 0 : value.length;
|
||||
uniformLengths[uniformCount++] = len;
|
||||
if (len == 0) continue;
|
||||
if (uniformValueCount + len > uniformValues.length) uniformValues = Arrays.copyOf(uniformValues, Math.max(uniformValues.length * 2, uniformValueCount + len));
|
||||
System.arraycopy(value, 0, uniformValues, uniformValueCount, len);
|
||||
uniformValueCount += len;
|
||||
}
|
||||
replay.lastHashesApplied = true;
|
||||
}
|
||||
|
||||
public boolean hasPending() {
|
||||
return pending > 0;
|
||||
}
|
||||
|
||||
public void noteIndirectDraw() {
|
||||
warnOnce("indirect", "An indirect draw used a program whose vertex shader writes images; those writes are dropped");
|
||||
}
|
||||
|
||||
private Capture acquire() {
|
||||
if (pending == captures.size()) captures.add(new Capture());
|
||||
return captures.get(pending);
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
if (pending == 0 || flushing) return;
|
||||
flushing = true;
|
||||
final ContextState st = state.get();
|
||||
final int savedProgram = st.boundProgram;
|
||||
final ShaderManager.ProgramObject savedProgramObj = st.boundProgramObj;
|
||||
try {
|
||||
uniformCursor = 0;
|
||||
valueCursor = 0;
|
||||
int start = 0;
|
||||
while (start < pending) {
|
||||
final Capture first = captures.get(start);
|
||||
final Replay replay = first.replay;
|
||||
int end = start;
|
||||
while (end < pending && captures.get(end).replay == replay && Arrays.equals(captures.get(end).imageTextures, first.imageTextures)
|
||||
&& Arrays.equals(captures.get(end).packRwStorage, first.packRwStorage)) end++;
|
||||
replayRun(st, replay, start, end);
|
||||
start = end;
|
||||
}
|
||||
} finally {
|
||||
st.boundProgram = savedProgram;
|
||||
st.boundProgramObj = savedProgramObj;
|
||||
for (int i = 0; i < pending; i++) captures.get(i).replay = null;
|
||||
pending = 0;
|
||||
uniformCount = 0;
|
||||
uniformValueCount = 0;
|
||||
flushing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void replayRun(ContextState st, Replay replay, int start, int end) {
|
||||
final ShaderManager.ProgramObject twin = shaderManager.getProgram(replay.replayProgram);
|
||||
if (twin == null || !twin.linked || twin.sdlComputePipeline == 0) {
|
||||
skipUniforms(replay, start, end);
|
||||
return;
|
||||
}
|
||||
|
||||
final Capture first = captures.get(start);
|
||||
final int[] imageTextures = first.imageTextures;
|
||||
final int imageCount = imageTextures.length;
|
||||
final int written = replay.setup.writtenImageCount();
|
||||
System.arraycopy(st.boundStorageTextureByUnit, 0, savedImages, 0, imageCount);
|
||||
System.arraycopy(st.boundStorageTextureLevel, 0, savedLevels, 0, imageCount);
|
||||
System.arraycopy(st.boundStorageTextureAccess, 0, savedAccess, 0, imageCount);
|
||||
for (int i = 0; i < replay.samplerUnits.length; i++) savedTextures[i] = st.boundTextures[replay.samplerUnits[i]];
|
||||
computeBinder.setSamplerOverrides(samplerOverrides);
|
||||
Arrays.fill(rwHandlesByGlSlot, 0L);
|
||||
for (int i = 0; i < replay.packRwStorageSlots.length; i++) rwHandlesByGlSlot[replay.packRwStorageSlots[i]] = first.packRwStorage[i];
|
||||
computeBinder.setReadWriteStorageBufferOverrides(rwHandlesByGlSlot);
|
||||
|
||||
try {
|
||||
for (int i = 0; i < imageCount; i++) {
|
||||
st.boundStorageTextureByUnit[i] = imageTextures[i];
|
||||
st.boundStorageTextureLevel[i] = 0;
|
||||
st.boundStorageTextureAccess[i] = imageAccess(i, written);
|
||||
}
|
||||
applySamplerTextures(st, replay, captures.get(start));
|
||||
|
||||
st.boundProgram = replay.replayProgram;
|
||||
st.boundProgramObj = twin;
|
||||
final long pass = computeBinder.beginComputePassWithoutStorageBuffers(st);
|
||||
if (pass == 0) {
|
||||
for (int i = start; i < end; i++) applyUniforms(st, replay);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (int i = start; i < end; i++) {
|
||||
final Capture c = captures.get(i);
|
||||
if (i > start && applySamplerTextures(st, replay, c)) computeBinder.rebindSamplers(pass, st);
|
||||
dispatch(st, pass, replay, c);
|
||||
}
|
||||
} finally {
|
||||
computeBinder.endBatchedComputeDispatch(pass);
|
||||
}
|
||||
} finally {
|
||||
computeBinder.setReadWriteStorageBufferOverrides(null);
|
||||
computeBinder.setUniformBlockOverrides(null);
|
||||
System.arraycopy(savedImages, 0, st.boundStorageTextureByUnit, 0, imageCount);
|
||||
System.arraycopy(savedLevels, 0, st.boundStorageTextureLevel, 0, imageCount);
|
||||
System.arraycopy(savedAccess, 0, st.boundStorageTextureAccess, 0, imageCount);
|
||||
for (int i = 0; i < replay.samplerUnits.length; i++) {
|
||||
st.boundTextures[replay.samplerUnits[i]] = savedTextures[i];
|
||||
samplerOverrides[replay.samplerUnits[i]] = 0L;
|
||||
}
|
||||
computeBinder.setSamplerOverrides(null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean applySamplerTextures(ContextState st, Replay replay, Capture c) {
|
||||
boolean changed = false;
|
||||
for (int i = 0; i < replay.samplerUnits.length; i++) {
|
||||
final int unit = replay.samplerUnits[i];
|
||||
if (st.boundTextures[unit] != c.samplerTextures[i] || samplerOverrides[unit] != c.samplerHandles[i]) {
|
||||
st.boundTextures[unit] = c.samplerTextures[i];
|
||||
samplerOverrides[unit] = c.samplerHandles[i];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
private void skipUniforms(Replay replay, int start, int end) {
|
||||
replay.lastHashesApplied = false;
|
||||
for (int i = start; i < end; i++) {
|
||||
for (int u = 0; u < replay.uniformSources.length; u++) valueCursor += Math.max(0, uniformLengths[uniformCursor++]);
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatch(ContextState st, long pass, Replay replay, Capture c) {
|
||||
Arrays.fill(handlesByGlSlot, 0L);
|
||||
for (int i = 0; i < replay.packStorageSlots.length; i++) handlesByGlSlot[replay.packStorageSlots[i]] = c.packStorage[i];
|
||||
System.arraycopy(c.buffers, 0, handlesByGlSlot, replay.setup.vertexBufferBinding(), c.bufferCount);
|
||||
handlesByGlSlot[replay.setup.indexBufferBinding()] = c.indexBuffer;
|
||||
final boolean bound = computeBinder.bindRoStorageBufferHandles(pass, st, handlesByGlSlot);
|
||||
|
||||
applyUniforms(st, replay);
|
||||
if (!bound) return;
|
||||
|
||||
final int inputs = replay.inputLocations.length;
|
||||
putFloats(st, replay.locAttr, c.attr, inputs * 4);
|
||||
putFloats(st, replay.locAttrDefault, c.attrDefault, inputs * 4);
|
||||
putIntVector(st, replay.locDraw, 4, c.first, c.count, c.instances, c.baseVertex);
|
||||
putIntVector(st, replay.locIndex, 3, c.indexSize, c.indexOffset, c.indexCount, 0);
|
||||
putIntVector(st, replay.locRestart, 2, c.restart ? 1 : 0, c.restartIndex, 0, 0);
|
||||
computeBinder.setUniformBlockOverrides(c.uniformBlocks);
|
||||
|
||||
InvocationDispatch.dispatch(computeBinder, st, pass, (long) c.count * c.instances, replay.locInvocationBase, invocationBaseWriter);
|
||||
}
|
||||
|
||||
private void putFloats(ContextState st, int location, float[] source, int len) {
|
||||
if (location < 0 || len == 0) return;
|
||||
final float[] value = pipelineApplier.reuseOrAlloc(st, location, len);
|
||||
if (value == null) return;
|
||||
System.arraycopy(source, 0, value, 0, len);
|
||||
pipelineApplier.putUniform(st, location, value);
|
||||
}
|
||||
|
||||
private void applyUniforms(ContextState st, Replay replay) {
|
||||
for (int u = 0; u < replay.uniformSources.length; u++) {
|
||||
final int len = uniformLengths[uniformCursor++];
|
||||
if (len <= 0) continue;
|
||||
final float[] value = pipelineApplier.reuseOrAlloc(st, replay.uniformTargets[u], len);
|
||||
if (value != null) {
|
||||
System.arraycopy(uniformValues, valueCursor, value, 0, len);
|
||||
pipelineApplier.putUniform(st, replay.uniformTargets[u], value);
|
||||
}
|
||||
valueCursor += len;
|
||||
}
|
||||
}
|
||||
|
||||
private static int imageAccess(int index, int writtenImageCount) {
|
||||
return index < writtenImageCount ? GL15.GL_READ_WRITE : GL15.GL_READ_ONLY;
|
||||
}
|
||||
|
||||
private void putIntVector(ContextState st, int location, int count, int x, int y, int z, int w) {
|
||||
if (location < 0) return;
|
||||
final float[] value = pipelineApplier.reuseOrAlloc(st, location, count);
|
||||
if (value == null) return;
|
||||
value[0] = Float.intBitsToFloat(x);
|
||||
if (count > 1) value[1] = Float.intBitsToFloat(y);
|
||||
if (count > 2) value[2] = Float.intBitsToFloat(z);
|
||||
if (count > 3) value[3] = Float.intBitsToFloat(w);
|
||||
pipelineApplier.putUniform(st, location, value);
|
||||
}
|
||||
|
||||
private void warnOnce(String key, String message) {
|
||||
if (warned.add(key)) LOG.warn(message);
|
||||
}
|
||||
}
|
||||
+2
-8
@@ -4,8 +4,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
|
||||
public final class VoxelizationDispatcher {
|
||||
static final int MAX_GROUPS_PER_DISPATCH = 65535;
|
||||
private static final int WORKGROUP_SIZE = 64;
|
||||
private static final InvocationDispatch.IntUniformWriter UNIFORM_1I = (st, location, value) -> GLStateManager.glUniform1i(location, value);
|
||||
|
||||
private final ComputeDispatchSink computeBinder;
|
||||
|
||||
@@ -25,12 +24,7 @@ public final class VoxelizationDispatcher {
|
||||
if (locBase >= 0) GLStateManager.glUniform1i(locBase, rangeBase);
|
||||
if (locCount >= 0) GLStateManager.glUniform1i(locCount, rangeCount);
|
||||
if (locTotal >= 0) GLStateManager.glUniform1i(locTotal, vertexTotal);
|
||||
final int groups = (vertexTotal + WORKGROUP_SIZE - 1) / WORKGROUP_SIZE;
|
||||
for (int g0 = 0; g0 < groups; g0 += MAX_GROUPS_PER_DISPATCH) {
|
||||
if (locInvBase >= 0) GLStateManager.glUniform1i(locInvBase, g0 * WORKGROUP_SIZE);
|
||||
computeBinder.pushPendingComputeUniforms(st);
|
||||
computeBinder.dispatchInBatch(pass, Math.min(MAX_GROUPS_PER_DISPATCH, groups - g0), 1, 1);
|
||||
}
|
||||
InvocationDispatch.dispatch(computeBinder, st, pass, vertexTotal, locInvBase, UNIFORM_1I);
|
||||
}
|
||||
|
||||
public void endBatch(long pass) {
|
||||
|
||||
@@ -5,7 +5,9 @@ import com.gtnewhorizons.angelica.glsm.backend.BackendOptions;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.MoltenVK;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compute.ImageAtomicsProbe;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.DebugMessageRelay;
|
||||
import it.unimi.dsi.fastutil.longs.Long2BooleanOpenHashMap;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
@@ -49,6 +51,9 @@ public final class Device {
|
||||
private String driverName;
|
||||
private boolean fencePollEnabled;
|
||||
private boolean fenceQueryInverted;
|
||||
private int defaultMslVersion;
|
||||
private ImageAtomicsProbe imageAtomicsProbe;
|
||||
private final Long2BooleanOpenHashMap imageAtomicsBySize = new Long2BooleanOpenHashMap();
|
||||
private final FenceReleaser fenceReleaser = new FenceReleaser(fence -> device == 0 || FenceWait.isSignaled(this, fence), fence -> { if (device != 0) SDL_ReleaseGPUFence(device, fence); });
|
||||
private String deviceName;
|
||||
private String driverVersion;
|
||||
@@ -153,10 +158,36 @@ public final class Device {
|
||||
}
|
||||
|
||||
probeFences(ver);
|
||||
readMetalLanguageVersion();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void readMetalLanguageVersion() {
|
||||
if (!isMetal(driverName)) return;
|
||||
defaultMslVersion = ImageAtomicsProbe.defaultMslVersion();
|
||||
if (metalTextureAtomics()) {
|
||||
LOG.info("Metal image atomics: MSL 3.1 available, checked per image size");
|
||||
} else {
|
||||
LOG.info("Metal image atomics: unavailable, this Java runtime compiles Metal shaders as MSL {}.{} and they need 3.1. A runtime built with the macOS 14 SDK or newer enables them.", defaultMslVersion >>> 16, defaultMslVersion & 0xFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean metalTextureAtomics() {
|
||||
return defaultMslVersion >= ImageAtomicsProbe.MTL_LANGUAGE_VERSION_3_1;
|
||||
}
|
||||
|
||||
public boolean supportsImageAtomics(int width, int height, int depth) {
|
||||
if (!metalTextureAtomics() || width <= 0 || height <= 0 || depth > 1) return false;
|
||||
final long key = (long) width << 32 | height;
|
||||
if (imageAtomicsBySize.containsKey(key)) return imageAtomicsBySize.get(key);
|
||||
if (imageAtomicsProbe == null) imageAtomicsProbe = new ImageAtomicsProbe(device);
|
||||
final boolean ok = imageAtomicsProbe.run(width, height);
|
||||
if (ok) LOG.info("Metal image atomics on {}x{}: ok", width, height);
|
||||
imageAtomicsBySize.put(key, ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
private void probeFences(int ver) {
|
||||
fencePollEnabled = FenceWait.pollEnabled(driverName, ver);
|
||||
final boolean expectInverted = isMetal(driverName) && ver == SDLVersion.SDL_VERSIONNUM(3, 4, 14);
|
||||
@@ -256,11 +287,15 @@ public final class Device {
|
||||
claimed = false;
|
||||
}
|
||||
fenceReleaser.releaseAll();
|
||||
if (imageAtomicsProbe != null) imageAtomicsProbe.release();
|
||||
imageAtomicsProbe = null;
|
||||
SDL_DestroyGPUDevice(device);
|
||||
device = 0;
|
||||
driverName = null;
|
||||
fencePollEnabled = false;
|
||||
fenceQueryInverted = false;
|
||||
defaultMslVersion = 0;
|
||||
imageAtomicsBySize.clear();
|
||||
deviceName = null;
|
||||
driverVersion = null;
|
||||
driverInfo = null;
|
||||
|
||||
@@ -381,11 +381,14 @@ public final class FrameManager {
|
||||
|
||||
|
||||
private volatile Runnable preRenderPassHook;
|
||||
private volatile Runnable renderPassEndHook;
|
||||
private volatile Runnable beforeSubmit;
|
||||
private volatile Runnable afterPresent;
|
||||
|
||||
public void setPreRenderPassHook(Runnable hook) { this.preRenderPassHook = hook; }
|
||||
|
||||
public void setRenderPassEndHook(Runnable hook) { this.renderPassEndHook = hook; }
|
||||
|
||||
public void noteComputePassBegun() {
|
||||
final FrameState f = frame();
|
||||
f.computePassesThisFrame++;
|
||||
@@ -472,6 +475,8 @@ public final class FrameManager {
|
||||
f.currentColorTarget = 0;
|
||||
f.currentDepthTarget = 0;
|
||||
f.activeLayoutHash = 0;
|
||||
final Runnable hook = renderPassEndHook;
|
||||
if (hook != null) hook.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -248,6 +248,7 @@ public final class ResourceManager {
|
||||
}
|
||||
|
||||
private long createTextureCore(int glId, int glTarget, int sdlFormat, int glFormat, int width, int height, int depth, int levels, int usage) {
|
||||
if (getTextureHandle(glId) != 0) beforeTextureReplaced();
|
||||
usage = reconcileUsageAndWarn(glId, usage);
|
||||
final int sdlType = mapTextureType(glTarget);
|
||||
if (!SDL_GPUTextureSupportsFormat(device.getDevice(), sdlFormat, sdlType, usage)) {
|
||||
@@ -309,7 +310,19 @@ public final class ResourceManager {
|
||||
return sdlFormat;
|
||||
}
|
||||
|
||||
private Runnable textureReplaceHook;
|
||||
|
||||
public void setTextureReplaceHook(Runnable hook) {
|
||||
textureReplaceHook = hook;
|
||||
}
|
||||
|
||||
private void beforeTextureReplaced() {
|
||||
final Runnable hook = textureReplaceHook;
|
||||
if (hook != null) hook.run();
|
||||
}
|
||||
|
||||
public void deleteTexture(int glId) {
|
||||
beforeTextureReplaced();
|
||||
final long handle;
|
||||
wLock.lock();
|
||||
try {
|
||||
@@ -340,6 +353,7 @@ public final class ResourceManager {
|
||||
}
|
||||
|
||||
public void releaseTextureHandleForRealloc(int glId) {
|
||||
beforeTextureReplaced();
|
||||
final long handle;
|
||||
wLock.lock();
|
||||
try {
|
||||
|
||||
@@ -43,8 +43,13 @@ public final class SamplerBinder {
|
||||
}
|
||||
|
||||
public long getSamplerForUnit(ContextState st, int glUnit, int glTexId) {
|
||||
if (glUnit >= 0 && glUnit < st.boundSamplerObjects.length && st.boundSamplerObjects[glUnit] != 0) {
|
||||
final TextureSamplerState ss = resourceManager.getSamplerObject(st.boundSamplerObjects[glUnit]);
|
||||
final int samplerObject = glUnit >= 0 && glUnit < st.boundSamplerObjects.length ? st.boundSamplerObjects[glUnit] : 0;
|
||||
return getSampler(samplerObject, glTexId);
|
||||
}
|
||||
|
||||
public long getSampler(int samplerObject, int glTexId) {
|
||||
if (samplerObject != 0) {
|
||||
final TextureSamplerState ss = resourceManager.getSamplerObject(samplerObject);
|
||||
if (ss != null) return getOrCreateSdlSampler(ss);
|
||||
}
|
||||
if (glTexId != 0) return getSamplerForTexture(glTexId);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.taumc.glsl.grammar.GLSLParserBaseListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -14,22 +12,15 @@ public final class ClipZRemap {
|
||||
private ClipZRemap() {}
|
||||
|
||||
public static void collectEdits(GLSLParser.Translation_unitContext root, List<Edit> edits) {
|
||||
final int[] mainCloseBraceIdx = { -1 };
|
||||
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
|
||||
@Override
|
||||
public void enterFunction_definition(GLSLParser.Function_definitionContext ctx) {
|
||||
if (mainCloseBraceIdx[0] >= 0) return;
|
||||
final GLSLParser.Function_prototypeContext proto = ctx.function_prototype();
|
||||
if (proto == null || proto.IDENTIFIER() == null) return;
|
||||
if (!"main".equals(proto.IDENTIFIER().getText())) return;
|
||||
final GLSLParser.Compound_statement_no_new_scopeContext body = ctx.compound_statement_no_new_scope();
|
||||
if (body == null || body.RIGHT_BRACE() == null) return;
|
||||
mainCloseBraceIdx[0] = body.RIGHT_BRACE().getSymbol().getStartIndex();
|
||||
}
|
||||
}, root);
|
||||
|
||||
if (mainCloseBraceIdx[0] < 0) return;
|
||||
final int idx = mainCloseBraceIdx[0];
|
||||
edits.add(new Edit(idx, idx - 1, INJECTION_STD));
|
||||
for (GLSLParser.External_declarationContext external : root.external_declaration()) {
|
||||
final GLSLParser.Function_definitionContext function = external.function_definition();
|
||||
if (function == null || function.function_prototype().IDENTIFIER() == null) continue;
|
||||
if (!"main".equals(function.function_prototype().IDENTIFIER().getText())) continue;
|
||||
final GLSLParser.Compound_statement_no_new_scopeContext body = function.compound_statement_no_new_scope();
|
||||
if (body == null || body.RIGHT_BRACE() == null) return;
|
||||
final int idx = body.RIGHT_BRACE().getSymbol().getStartIndex();
|
||||
edits.add(new Edit(idx, idx - 1, INJECTION_STD));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.startIdx;
|
||||
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.stopIdx;
|
||||
|
||||
/**
|
||||
* Moves {@code dFdx}/{@code dFdy}/{@code fwidth} calls in front of the {@code if} blocks around them, so every section of
|
||||
* the 2x2 quad computes them. Packs take derivatives inside branches that only part of a quad enters, which GLSL
|
||||
* leaves undefined. ACO writes {@code src - src[lane0]} in the active lanes only, then reads lane 1/2 with
|
||||
* fetch-inactive, so a neighbor that skipped the branch hands back a stale register, often NaN!
|
||||
* This was observed on Euphoria Patches and this aims to fix this wherever possible on Vulkan.
|
||||
*/
|
||||
public final class DerivativeHoisting {
|
||||
|
||||
private static final String PREFIX = "angelica_derivative_";
|
||||
|
||||
private static final Set<String> DERIVATIVES = Set.of(
|
||||
"dFdx", "dFdy", "fwidth",
|
||||
"dFdxFine", "dFdyFine", "fwidthFine",
|
||||
"dFdxCoarse", "dFdyCoarse", "fwidthCoarse");
|
||||
|
||||
private static final Map<String, Set<Integer>> BUILTIN_OUT_PARAMS = Map.of(
|
||||
"modf", Set.of(1),
|
||||
"frexp", Set.of(1),
|
||||
"uaddCarry", Set.of(2),
|
||||
"usubBorrow", Set.of(2),
|
||||
"umulExtended", Set.of(2, 3),
|
||||
"imulExtended", Set.of(2, 3));
|
||||
|
||||
private static final String UNKNOWN = "*";
|
||||
|
||||
private DerivativeHoisting() {}
|
||||
|
||||
private record Declared(String type, boolean global) {}
|
||||
|
||||
private record Operand(String name, @Nullable String swizzle) {}
|
||||
|
||||
private record DerivativeCall(GLSLParser.Postfix_expressionContext call, String callee) {}
|
||||
|
||||
private record Effects(Set<String> declared, Set<String> written, Set<String> called) {
|
||||
Effects() {
|
||||
this(new HashSet<>(), new HashSet<>(), new HashSet<>());
|
||||
}
|
||||
}
|
||||
|
||||
public static void collectEdits(ShaderIndex index, List<Edit> edits) {
|
||||
final List<DerivativeCall> calls = new ArrayList<>();
|
||||
for (String callee : DERIVATIVES) {
|
||||
for (GLSLParser.Postfix_expressionContext call : index.callsTo(callee)) calls.add(new DerivativeCall(call, callee));
|
||||
}
|
||||
if (calls.isEmpty()) return;
|
||||
calls.sort(Comparator.comparingInt(c -> startIdx(c.call())));
|
||||
|
||||
final String source = index.source();
|
||||
final GLSLParser.Translation_unitContext root = index.root();
|
||||
final Functions functions = new Functions(index);
|
||||
final int earlierEditCount = edits.size();
|
||||
final Map<GLSLParser.Selection_statementContext, Effects> ifEffects = new HashMap<>();
|
||||
final Map<GLSLParser.Selection_statementContext, StringBuilder> declarations = new LinkedHashMap<>();
|
||||
final Map<String, String> hoisted = new HashMap<>();
|
||||
boolean checkedDirectives = false;
|
||||
|
||||
for (DerivativeCall derivative : calls) {
|
||||
final GLSLParser.Postfix_expressionContext call = derivative.call();
|
||||
final GLSLParser.Function_call_parametersContext params = call.function_call_parameters();
|
||||
if (params == null || params.assignment_expression().size() != 1) continue;
|
||||
final GLSLParser.Assignment_expressionContext arg = params.assignment_expression(0);
|
||||
final Operand operand = operand(arg);
|
||||
if (operand == null) continue;
|
||||
|
||||
final Declared declared = declaredType(call, operand.name(), root);
|
||||
if (declared == null) continue;
|
||||
final String type = resultType(declared.type(), operand.swizzle());
|
||||
if (type == null) continue;
|
||||
|
||||
final GLSLParser.Selection_statementContext target = outermostSafeIf(call, operand.name(), declared.global(), functions, ifEffects);
|
||||
if (target == null) continue;
|
||||
if (!checkedDirectives) {
|
||||
if (index.directives().hasConditionalOrMacro()) return;
|
||||
checkedDirectives = true;
|
||||
}
|
||||
|
||||
final String callee = derivative.callee();
|
||||
final String argText = sliceWithEdits(source, startIdx(arg), stopIdx(arg), edits.subList(0, earlierEditCount));
|
||||
final String key = startIdx(target) + "|" + callee + "|" + argText;
|
||||
String variable = hoisted.get(key);
|
||||
if (variable == null) {
|
||||
variable = PREFIX + hoisted.size();
|
||||
hoisted.put(key, variable);
|
||||
declarations.computeIfAbsent(target, k -> new StringBuilder())
|
||||
.append(type).append(' ').append(variable).append(" = ").append(callee).append('(').append(argText).append(");\n")
|
||||
.append(indentBefore(source, startIdx(target)));
|
||||
}
|
||||
edits.add(new Edit(startIdx(call), stopIdx(call), variable));
|
||||
}
|
||||
|
||||
for (Map.Entry<GLSLParser.Selection_statementContext, StringBuilder> e : declarations.entrySet()) {
|
||||
final int at = startIdx(e.getKey());
|
||||
edits.add(new Edit(at, at - 1, e.getValue().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Functions {
|
||||
final Map<String, Set<Integer>> outParams = new HashMap<>();
|
||||
final Set<String> names = new HashSet<>();
|
||||
final Map<GLSLParser.Postfix_expressionContext, String> calleeNames = new HashMap<>();
|
||||
private final List<GLSLParser.Function_definitionContext> definitions = new ArrayList<>();
|
||||
private @Nullable Map<String, Set<String>> globalWrites;
|
||||
|
||||
Functions(ShaderIndex index) {
|
||||
for (GLSLParser.External_declarationContext external : index.root().external_declaration()) {
|
||||
final GLSLParser.Function_prototypeContext prototype;
|
||||
if (external.function_definition() != null) {
|
||||
definitions.add(external.function_definition());
|
||||
prototype = external.function_definition().function_prototype();
|
||||
} else if (external.declaration() != null) {
|
||||
prototype = external.declaration().function_prototype();
|
||||
} else {
|
||||
prototype = null;
|
||||
}
|
||||
if (prototype == null || prototype.IDENTIFIER() == null) continue;
|
||||
final String name = prototype.IDENTIFIER().getText();
|
||||
names.add(name);
|
||||
final List<GLSLParser.Parameter_declarationContext> params = parameters(prototype);
|
||||
for (int i = 0; i < params.size(); i++) {
|
||||
final GLSLParser.Type_qualifierContext q = params.get(i).type_qualifier();
|
||||
if (ShaderIndex.hasStorageQualifier(q, "out") || ShaderIndex.hasStorageQualifier(q, "inout")) {
|
||||
outParams.computeIfAbsent(name, k -> new HashSet<>()).add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (String name : names) addCalls(index, name);
|
||||
for (String name : BUILTIN_OUT_PARAMS.keySet()) addCalls(index, name);
|
||||
}
|
||||
|
||||
private void addCalls(ShaderIndex index, String name) {
|
||||
for (GLSLParser.Postfix_expressionContext call : index.callsTo(name)) calleeNames.put(call, name);
|
||||
}
|
||||
|
||||
Set<String> globalWrites(String function) {
|
||||
if (globalWrites == null) globalWrites = summarize();
|
||||
return globalWrites.getOrDefault(function, Set.of());
|
||||
}
|
||||
|
||||
private Map<String, Set<String>> summarize() {
|
||||
final Map<String, Set<String>> writes = new HashMap<>();
|
||||
final Map<String, Set<String>> callees = new HashMap<>();
|
||||
for (GLSLParser.Function_definitionContext definition : definitions) {
|
||||
final String name = definition.function_prototype().IDENTIFIER().getText();
|
||||
final Effects effects = new Effects();
|
||||
collectEffects(definition.compound_statement_no_new_scope(), this, effects);
|
||||
for (GLSLParser.Parameter_declarationContext param : parameters(definition.function_prototype())) {
|
||||
final String parameter = parameterName(param);
|
||||
if (parameter != null) effects.written().remove(parameter);
|
||||
}
|
||||
writes.computeIfAbsent(name, k -> new HashSet<>()).addAll(effects.written());
|
||||
callees.computeIfAbsent(name, k -> new HashSet<>()).addAll(effects.called());
|
||||
}
|
||||
boolean changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (Map.Entry<String, Set<String>> e : callees.entrySet()) {
|
||||
final Set<String> into = writes.get(e.getKey());
|
||||
for (String callee : e.getValue()) {
|
||||
final Set<String> from = writes.get(callee);
|
||||
if (from != null) changed |= into.addAll(from);
|
||||
}
|
||||
}
|
||||
}
|
||||
return writes;
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable Operand operand(GLSLParser.Assignment_expressionContext arg) {
|
||||
if (arg.assignment_operator() != null) return null;
|
||||
final GLSLParser.Constant_expressionContext c = arg.constant_expression();
|
||||
if (c == null || c.QUESTION() != null) return null;
|
||||
final GLSLParser.Unary_expressionContext u = c.binary_expression().unary_expression();
|
||||
if (u == null || u.postfix_expression() == null) return null;
|
||||
final GLSLParser.Postfix_expressionContext p = u.postfix_expression();
|
||||
|
||||
final String name = variableName(p);
|
||||
if (name != null) return new Operand(name, null);
|
||||
if (p.DOT() == null || p.field_selection() == null || p.field_selection().variable_identifier() == null) return null;
|
||||
final String base = variableName(p.postfix_expression());
|
||||
return base == null ? null : new Operand(base, p.field_selection().variable_identifier().getText());
|
||||
}
|
||||
|
||||
private static @Nullable String variableName(@Nullable GLSLParser.Postfix_expressionContext p) {
|
||||
if (p == null || p.primary_expression() == null || p.primary_expression().variable_identifier() == null) return null;
|
||||
return p.primary_expression().variable_identifier().getText();
|
||||
}
|
||||
|
||||
private static @Nullable String resultType(String declaredType, @Nullable String swizzle) {
|
||||
final int size = switch (declaredType) {
|
||||
case "float" -> 1;
|
||||
case "vec2" -> 2;
|
||||
case "vec3" -> 3;
|
||||
case "vec4" -> 4;
|
||||
default -> 0;
|
||||
};
|
||||
if (size == 0) return null;
|
||||
if (swizzle == null) return declaredType;
|
||||
if (size == 1 || swizzle.isEmpty() || swizzle.length() > 4) return null;
|
||||
for (String set : new String[] { "xyzw", "rgba", "stpq" }) {
|
||||
boolean all = true;
|
||||
for (int i = 0; i < swizzle.length(); i++) {
|
||||
final int component = set.indexOf(swizzle.charAt(i));
|
||||
if (component < 0 || component >= size) { all = false; break; }
|
||||
}
|
||||
if (all) return swizzle.length() == 1 ? "float" : "vec" + swizzle.length();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable Declared declaredType(ParserRuleContext from, String name, GLSLParser.Translation_unitContext root) {
|
||||
ParseTree child = from;
|
||||
for (ParserRuleContext p = from.getParent(); p != null; child = p, p = p.getParent()) {
|
||||
if (p instanceof GLSLParser.Statement_listContext list) {
|
||||
for (int i = list.children.indexOf(child) - 1; i >= 0; i--) {
|
||||
final String type = typeOf(list.getChild(i), name);
|
||||
if (type != null) return new Declared(type, false);
|
||||
}
|
||||
} else if (p instanceof GLSLParser.Iteration_statementContext loop && loop.for_init_statement() != null && child != loop.for_init_statement()) {
|
||||
final String type = typeOf(loop.for_init_statement(), name);
|
||||
if (type != null) return new Declared(type, false);
|
||||
} else if (p instanceof GLSLParser.Function_definitionContext function) {
|
||||
for (GLSLParser.Parameter_declarationContext param : parameters(function.function_prototype())) {
|
||||
if (name.equals(parameterName(param))) {
|
||||
final GLSLParser.Parameter_declaratorContext d = param.parameter_declarator();
|
||||
return new Declared(d.array_specifier() == null ? baseType(d.type_specifier()) : "", false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (GLSLParser.External_declarationContext external : root.external_declaration()) {
|
||||
if (external.declaration() == null) continue;
|
||||
final String type = typeOf(external.declaration(), name);
|
||||
if (type != null) return new Declared(type, true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable String typeOf(ParseTree node, String name) {
|
||||
final GLSLParser.Init_declarator_listContext list = firstDeclaratorList(node);
|
||||
if (list == null || list.single_declaration() == null) return null;
|
||||
for (GLSLParser.Typeless_declarationContext d : ShaderIndex.declarators(list)) {
|
||||
if (d.IDENTIFIER() == null || !name.equals(d.IDENTIFIER().getText())) continue;
|
||||
if (d.array_specifier() != null) return "";
|
||||
return baseType(list.single_declaration().fully_specified_type().type_specifier());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static GLSLParser.@Nullable Init_declarator_listContext firstDeclaratorList(ParseTree node) {
|
||||
ParseTree n = node;
|
||||
while (n != null) {
|
||||
if (n instanceof GLSLParser.Init_declarator_listContext list) return list;
|
||||
if (n instanceof GLSLParser.Simple_statementContext s) n = s.declaration_statement();
|
||||
else if (n instanceof GLSLParser.StatementContext s) n = s.simple_statement();
|
||||
else if (n instanceof GLSLParser.For_init_statementContext f) n = f.declaration_statement();
|
||||
else if (n instanceof GLSLParser.Declaration_statementContext d) n = d.declaration();
|
||||
else if (n instanceof GLSLParser.DeclarationContext d) n = d.init_declarator_list();
|
||||
else return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String baseType(GLSLParser.Type_specifierContext type) {
|
||||
if (type == null || type.array_specifier() != null || type.type_specifier_nonarray() == null) return "";
|
||||
return type.type_specifier_nonarray().getText();
|
||||
}
|
||||
|
||||
private static List<GLSLParser.Parameter_declarationContext> parameters(GLSLParser.Function_prototypeContext prototype) {
|
||||
final GLSLParser.Function_parametersContext params = prototype.function_parameters();
|
||||
return params == null ? List.of() : params.parameter_declaration();
|
||||
}
|
||||
|
||||
private static @Nullable String parameterName(GLSLParser.Parameter_declarationContext param) {
|
||||
final GLSLParser.Parameter_declaratorContext d = param.parameter_declarator();
|
||||
return d == null || d.IDENTIFIER() == null ? null : d.IDENTIFIER().getText();
|
||||
}
|
||||
|
||||
private static GLSLParser.@Nullable Selection_statementContext outermostSafeIf(ParserRuleContext call, String name, boolean global, Functions functions,
|
||||
Map<GLSLParser.Selection_statementContext, Effects> ifEffects) {
|
||||
final List<GLSLParser.Selection_statementContext> enclosing = new ArrayList<>();
|
||||
for (ParserRuleContext p = call.getParent(); p != null; p = p.getParent()) {
|
||||
if (p instanceof GLSLParser.Selection_rest_statementContext rest) {
|
||||
enclosing.add((GLSLParser.Selection_statementContext) rest.getParent());
|
||||
} else if (p instanceof GLSLParser.Iteration_statementContext || p instanceof GLSLParser.Switch_statementContext
|
||||
|| p instanceof GLSLParser.Function_definitionContext) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (int i = enclosing.size() - 1; i >= 0; i--) {
|
||||
final GLSLParser.Selection_statementContext candidate = enclosing.get(i);
|
||||
if (!inStatementList(candidate)) continue;
|
||||
Effects effects = ifEffects.get(candidate);
|
||||
if (effects == null) {
|
||||
effects = new Effects();
|
||||
collectEffects(candidate, functions, effects);
|
||||
ifEffects.put(candidate, effects);
|
||||
}
|
||||
if (effects.declared().contains(name) || writes(effects, name, global, functions)) continue;
|
||||
return candidate;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean inStatementList(GLSLParser.Selection_statementContext s) {
|
||||
return s.getParent() instanceof GLSLParser.Simple_statementContext simple
|
||||
&& simple.getParent() instanceof GLSLParser.StatementContext statement
|
||||
&& statement.getParent() instanceof GLSLParser.Statement_listContext list
|
||||
&& !(list.getParent() instanceof GLSLParser.Switch_statementContext);
|
||||
}
|
||||
|
||||
private static boolean writes(Effects effects, String name, boolean global, Functions functions) {
|
||||
if (effects.written().contains(name) || effects.written().contains(UNKNOWN)) return true;
|
||||
if (!global) return false;
|
||||
for (String callee : effects.called()) {
|
||||
final Set<String> w = functions.globalWrites(callee);
|
||||
if (w.contains(name) || w.contains(UNKNOWN)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void collectEffects(ParseTree node, Functions functions, Effects effects) {
|
||||
final Set<String> written = effects.written();
|
||||
if (node instanceof GLSLParser.Typeless_declarationContext d && d.IDENTIFIER() != null) {
|
||||
effects.declared().add(d.IDENTIFIER().getText());
|
||||
} else if (node instanceof GLSLParser.ConditionContext c && c.IDENTIFIER() != null) {
|
||||
effects.declared().add(c.IDENTIFIER().getText());
|
||||
} else if (node instanceof GLSLParser.Assignment_expressionContext a && a.assignment_operator() != null) {
|
||||
written.add(lvalueName(a.unary_expression()));
|
||||
} else if (node instanceof GLSLParser.Unary_expressionContext u && (u.INC_OP() != null || u.DEC_OP() != null)) {
|
||||
written.add(lvalueName(u.unary_expression()));
|
||||
} else if (node instanceof GLSLParser.Postfix_expressionContext p) {
|
||||
if (p.INC_OP() != null || p.DEC_OP() != null) written.add(lvalueName(p.postfix_expression()));
|
||||
final String callee = functions.calleeNames.get(p);
|
||||
if (callee != null) {
|
||||
if (functions.names.contains(callee)) effects.called().add(callee);
|
||||
Set<Integer> positions = functions.outParams.get(callee);
|
||||
if (positions == null) positions = BUILTIN_OUT_PARAMS.get(callee);
|
||||
if (positions != null && p.function_call_parameters() != null) {
|
||||
final List<GLSLParser.Assignment_expressionContext> args = p.function_call_parameters().assignment_expression();
|
||||
for (int i : positions) {
|
||||
if (i < args.size()) written.add(lvalueName(args.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < node.getChildCount(); i++) {
|
||||
collectEffects(node.getChild(i), functions, effects);
|
||||
}
|
||||
}
|
||||
|
||||
private static String lvalueName(@Nullable ParserRuleContext lvalue) {
|
||||
if (lvalue == null) return UNKNOWN;
|
||||
final Token start = lvalue.getStart();
|
||||
return start != null && start.getType() == GLSLLexer.IDENTIFIER ? start.getText() : UNKNOWN;
|
||||
}
|
||||
|
||||
private static String indentBefore(String source, int at) {
|
||||
int lineStart = at;
|
||||
while (lineStart > 0 && (source.charAt(lineStart - 1) == ' ' || source.charAt(lineStart - 1) == '\t')) lineStart--;
|
||||
return source.substring(lineStart, at);
|
||||
}
|
||||
|
||||
private static String sliceWithEdits(String source, int start, int stop, List<Edit> edits) {
|
||||
final List<Edit> inside = new ArrayList<>();
|
||||
for (Edit e : edits) {
|
||||
if (e.startIdx() >= start && e.startIdx() <= stop && e.stopIdx() <= stop) {
|
||||
inside.add(new Edit(e.startIdx() - start, e.stopIdx() - start, e.replacement()));
|
||||
}
|
||||
}
|
||||
return GlslVulkanPreprocess.applyEdits(source.substring(start, stop + 1), inside);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
@@ -112,11 +113,8 @@ public final class LogicOpLowering {
|
||||
final int vecSize = floatVecSize(ts.type_specifier_nonarray().getText());
|
||||
final int typeArrayLen = ts.array_specifier() != null ? arrayLen(ts.array_specifier(), program) : 0;
|
||||
|
||||
final List<GLSLParser.Typeless_declarationContext> names = new ArrayList<>();
|
||||
if (single.typeless_declaration() != null) names.add(single.typeless_declaration());
|
||||
names.addAll(idl.typeless_declaration());
|
||||
int nextLocation = location;
|
||||
for (GLSLParser.Typeless_declarationContext td : names) {
|
||||
for (GLSLParser.Typeless_declarationContext td : ShaderIndex.declarators(idl)) {
|
||||
final int len = td.array_specifier() != null ? arrayLen(td.array_specifier(), program) : typeArrayLen;
|
||||
outputs.add(new Output(td.IDENTIFIER().getText(), nextLocation, len, vecSize));
|
||||
if (nextLocation >= 0) nextLocation += Math.max(len, 1);
|
||||
|
||||
+13
-45
@@ -3,13 +3,9 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock.Member;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.UniformType;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.taumc.glsl.grammar.GLSLParserBaseListener;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
@@ -28,13 +24,13 @@ public final class PerFrameBlockInjector {
|
||||
|
||||
private PerFrameBlockInjector() {}
|
||||
|
||||
public static void collectEdits(GLSLParser.Translation_unitContext root, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass, List<Edit> out) {
|
||||
public static void collectEdits(ShaderIndex index, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass, List<Edit> out) {
|
||||
final PerFrameUniformBlock[] blocks = new PerFrameUniformBlock[ShaderManager.BLOCK_COUNT];
|
||||
blocks[ShaderManager.BLOCK_PER_FRAME] = normalize(perFrame);
|
||||
blocks[ShaderManager.BLOCK_PER_PASS] = normalize(perPass);
|
||||
if (blocks[0] == null && blocks[1] == null) return;
|
||||
|
||||
final int insertAt = firstDeclarationStart(root);
|
||||
final int insertAt = firstDeclarationStart(index.root());
|
||||
if (insertAt < 0) return;
|
||||
|
||||
final Map<String, String> memberTypes = new LinkedHashMap<>();
|
||||
@@ -51,9 +47,12 @@ public final class PerFrameBlockInjector {
|
||||
final List<Edit> stripEdits = new ArrayList<>();
|
||||
final IntArrayList stripBlocks = new IntArrayList();
|
||||
final Set<String> shadowed = new HashSet<>();
|
||||
classifyDeclarations(root, memberTypes, memberBlock, stripEdits, stripBlocks, shadowed);
|
||||
classifyDeclarations(index, memberTypes, memberBlock, stripEdits, stripBlocks, shadowed);
|
||||
|
||||
final Set<String> referenced = referencedMembers(root, memberTypes.keySet(), shadowed);
|
||||
final Set<String> referenced = new HashSet<>();
|
||||
for (String name : memberTypes.keySet()) {
|
||||
if (!shadowed.contains(name) && !index.occurrences(name).isEmpty()) referenced.add(name);
|
||||
}
|
||||
final boolean[] injected = new boolean[blocks.length];
|
||||
boolean anyInjected = false;
|
||||
for (int b = 0; b < blocks.length; b++) {
|
||||
@@ -108,11 +107,8 @@ public final class PerFrameBlockInjector {
|
||||
return (decls == null || decls.isEmpty()) ? -1 : decls.get(0).getStart().getStartIndex();
|
||||
}
|
||||
|
||||
private static void classifyDeclarations(GLSLParser.Translation_unitContext root, Map<String, String> memberTypes, Object2IntOpenHashMap<String> memberBlock, List<Edit> stripEdits, IntArrayList stripBlocks, Set<String> shadowed) {
|
||||
for (GLSLParser.External_declarationContext ed : root.external_declaration()) {
|
||||
final GLSLParser.DeclarationContext ctx = ed.declaration();
|
||||
if (ctx == null) continue;
|
||||
|
||||
private static void classifyDeclarations(ShaderIndex index, Map<String, String> memberTypes, Object2IntOpenHashMap<String> memberBlock, List<Edit> stripEdits, IntArrayList stripBlocks, Set<String> shadowed) {
|
||||
for (GLSLParser.DeclarationContext ctx : index.globalDeclarations()) {
|
||||
final List<String> declared = declaredNames(ctx);
|
||||
if (declared.isEmpty()) continue;
|
||||
|
||||
@@ -141,16 +137,7 @@ public final class PerFrameBlockInjector {
|
||||
final GLSLParser.Single_declarationContext single = idl.single_declaration();
|
||||
if (single == null || single.fully_specified_type() == null) return false;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
if (fst.type_qualifier() == null) return false;
|
||||
|
||||
boolean hasUniform = false;
|
||||
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
|
||||
if (stq.storage_qualifier() != null && "uniform".equals(stq.storage_qualifier().getText())) {
|
||||
hasUniform = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasUniform) return false;
|
||||
if (!ShaderIndex.hasStorageQualifier(fst.type_qualifier(), "uniform")) return false;
|
||||
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return false;
|
||||
final String declaredType = fst.type_specifier().type_specifier_nonarray().getText();
|
||||
|
||||
@@ -160,30 +147,11 @@ public final class PerFrameBlockInjector {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Set<String> referencedMembers(GLSLParser.Translation_unitContext root, Set<String> memberNames, Set<String> shadowed) {
|
||||
final Set<String> referenced = new HashSet<>();
|
||||
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
|
||||
@Override
|
||||
public void visitTerminal(TerminalNode node) {
|
||||
final Token tok = node.getSymbol();
|
||||
if (tok.getType() != GLSLLexer.IDENTIFIER) return;
|
||||
final String name = tok.getText();
|
||||
if (memberNames.contains(name) && !shadowed.contains(name)) referenced.add(name);
|
||||
}
|
||||
}, root);
|
||||
return referenced;
|
||||
}
|
||||
|
||||
private static List<String> declaredNames(GLSLParser.DeclarationContext ctx) {
|
||||
final GLSLParser.Init_declarator_listContext idl = ctx.init_declarator_list();
|
||||
if (idl == null) return List.of();
|
||||
final GLSLParser.Single_declarationContext single = idl.single_declaration();
|
||||
if (single == null) return List.of();
|
||||
if (idl == null || idl.single_declaration() == null) return List.of();
|
||||
final List<String> out = new ArrayList<>(2);
|
||||
if (single.typeless_declaration() != null && single.typeless_declaration().IDENTIFIER() != null) {
|
||||
out.add(single.typeless_declaration().IDENTIFIER().getText());
|
||||
}
|
||||
for (GLSLParser.Typeless_declarationContext td : idl.typeless_declaration()) {
|
||||
for (GLSLParser.Typeless_declarationContext td : ShaderIndex.declarators(idl)) {
|
||||
if (td.IDENTIFIER() != null) out.add(td.IDENTIFIER().getText());
|
||||
}
|
||||
return out;
|
||||
|
||||
+54
-70
@@ -1,20 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
import org.taumc.glsl.grammar.GLSLParserBaseListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@@ -24,79 +19,68 @@ public final class SamplerStripper {
|
||||
|
||||
private SamplerStripper() {}
|
||||
|
||||
public static void collectEdits(GLSLParser.Translation_unitContext root, String source, List<Edit> edits) {
|
||||
record SamplerDecl(String name, int start, int stop) {}
|
||||
private record SamplerDecl(List<String> names, int start, int stop) {}
|
||||
|
||||
public static void collectEdits(ShaderIndex index, List<Edit> edits) {
|
||||
final List<SamplerDecl> samplers = new ArrayList<>();
|
||||
|
||||
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
|
||||
@Override
|
||||
public void enterDeclaration(GLSLParser.DeclarationContext ctx) {
|
||||
final GLSLParser.Init_declarator_listContext idl = ctx.init_declarator_list();
|
||||
if (idl == null) return;
|
||||
final GLSLParser.Single_declarationContext single = idl.single_declaration();
|
||||
if (single == null || single.fully_specified_type() == null) return;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
if (fst.type_qualifier() == null) return;
|
||||
|
||||
boolean hasUniform = false;
|
||||
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
|
||||
if (stq.storage_qualifier() != null && "uniform".equals(stq.storage_qualifier().getText())) {
|
||||
hasUniform = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasUniform) return;
|
||||
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
|
||||
|
||||
final String typeText = fst.type_specifier().type_specifier_nonarray().getText();
|
||||
if (!isSamplerType(typeText)) return;
|
||||
|
||||
final int start = ctx.getStart().getStartIndex();
|
||||
final int stop = ctx.getStop().getStopIndex();
|
||||
if (single.typeless_declaration() != null && single.typeless_declaration().IDENTIFIER() != null) {
|
||||
samplers.add(new SamplerDecl(single.typeless_declaration().IDENTIFIER().getText(), start, stop));
|
||||
}
|
||||
for (GLSLParser.Typeless_declarationContext td : idl.typeless_declaration()) {
|
||||
if (td.IDENTIFIER() != null) {
|
||||
samplers.add(new SamplerDecl(td.IDENTIFIER().getText(), start, stop));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, root);
|
||||
|
||||
for (GLSLParser.DeclarationContext ctx : index.globalDeclarations()) {
|
||||
final SamplerDecl decl = samplerDeclaration(ctx);
|
||||
if (decl != null) samplers.add(decl);
|
||||
}
|
||||
if (samplers.isEmpty()) return;
|
||||
|
||||
final Map<String, Integer> refCounts = new HashMap<>();
|
||||
for (SamplerDecl s : samplers) refCounts.put(s.name(), 0);
|
||||
|
||||
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
|
||||
@Override
|
||||
public void visitTerminal(TerminalNode node) {
|
||||
final Token tok = node.getSymbol();
|
||||
if (tok.getType() != GLSLLexer.IDENTIFIER) return;
|
||||
final String name = tok.getText();
|
||||
final Integer prior = refCounts.get(name);
|
||||
if (prior == null) return;
|
||||
final int pos = tok.getStartIndex();
|
||||
for (SamplerDecl s : samplers) {
|
||||
if (pos >= s.start() && pos <= s.stop()) return;
|
||||
}
|
||||
refCounts.put(name, prior + 1);
|
||||
}
|
||||
}, root);
|
||||
|
||||
final Set<String> directiveRefs = GlslTransformUtils.identifiersInDirectiveText(source);
|
||||
|
||||
Set<String> directiveRefs = null;
|
||||
for (SamplerDecl s : samplers) {
|
||||
if (refCounts.getOrDefault(s.name(), 0) != 0) continue;
|
||||
if (directiveRefs.contains(s.name())) {
|
||||
LOGGER.debug("Keeping sampler {}: referenced only inside a preprocessor block", s.name());
|
||||
if (!allUnreferenced(s, samplers, index)) continue;
|
||||
if (directiveRefs == null) directiveRefs = index.directives().identifiers();
|
||||
if (anyIn(s, directiveRefs)) {
|
||||
LOGGER.debug("Keeping sampler {}: referenced only inside a preprocessor block", s.names());
|
||||
continue;
|
||||
}
|
||||
edits.add(new Edit(s.start(), s.stop(), ""));
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable SamplerDecl samplerDeclaration(GLSLParser.DeclarationContext ctx) {
|
||||
final GLSLParser.Init_declarator_listContext idl = ctx.init_declarator_list();
|
||||
if (idl == null) return null;
|
||||
final GLSLParser.Single_declarationContext single = idl.single_declaration();
|
||||
if (single == null || single.fully_specified_type() == null) return null;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
if (!ShaderIndex.hasStorageQualifier(fst.type_qualifier(), "uniform")) return null;
|
||||
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return null;
|
||||
if (!isSamplerType(fst.type_specifier().type_specifier_nonarray().getText())) return null;
|
||||
|
||||
final List<String> names = new ArrayList<>(1);
|
||||
for (GLSLParser.Typeless_declarationContext td : ShaderIndex.declarators(idl)) {
|
||||
if (td.IDENTIFIER() != null) names.add(td.IDENTIFIER().getText());
|
||||
}
|
||||
return names.isEmpty() ? null : new SamplerDecl(names, ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex());
|
||||
}
|
||||
|
||||
private static boolean insideAny(List<SamplerDecl> samplers, int pos) {
|
||||
for (SamplerDecl s : samplers) {
|
||||
if (pos >= s.start() && pos <= s.stop()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean allUnreferenced(SamplerDecl s, List<SamplerDecl> samplers, ShaderIndex index) {
|
||||
for (String name : s.names()) {
|
||||
for (Token tok : index.occurrences(name)) {
|
||||
if (!insideAny(samplers, tok.getStartIndex())) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean anyIn(SamplerDecl s, Set<String> names) {
|
||||
for (String name : s.names()) {
|
||||
if (names.contains(name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isSamplerType(String t) {
|
||||
return t.startsWith("sampler") || t.startsWith("isampler") || t.startsWith("usampler");
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderCacheIO;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import com.gtnewhorizons.angelica.glsm.threading.AngelicaWorkers;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compute.VertexWriteReplayer;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.cross.CrossCompileCache;
|
||||
@@ -315,40 +317,40 @@ public final class ShaderManager {
|
||||
LOG.warn("glsl-transformation-lib parse failed for '{}': {}", debugName, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
if (isGraphics(glShaderType)) {
|
||||
PerFrameBlockInjector.collectEdits(root, perFrame, perPass, edits);
|
||||
}
|
||||
final GlslVulkanPreprocess.Metadata meta = GlslVulkanPreprocess.collectEdits(transformedSource, root, glShaderType, debugName, true, edits);
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER) {
|
||||
ClipZRemap.collectEdits(root, edits);
|
||||
}
|
||||
SamplerStripper.collectEdits(root, transformedSource, edits);
|
||||
final String s = edits.isEmpty() ? transformedSource : GlslVulkanPreprocess.applyEdits(transformedSource, edits);
|
||||
return new PrewarmTransformResult(s, meta.boolUniforms());
|
||||
return transform(transformedSource, 0, root, glShaderType, debugName, perFrame, perPass);
|
||||
}
|
||||
|
||||
public static PrewarmTransformResult applyPrewarmTransformsFull(String finalSource, GLSLParser.Translation_unitContext bodyTree, int headerLen, int glShaderType, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {
|
||||
final String header = finalSource.substring(0, headerLen);
|
||||
final String body = finalSource.substring(headerLen);
|
||||
return transform(finalSource, headerLen, bodyTree, glShaderType, "prewarm", perFrame, perPass);
|
||||
}
|
||||
|
||||
private static PrewarmTransformResult transform(String source, int headerLen, GLSLParser.Translation_unitContext tree, int glShaderType, String debugName, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {
|
||||
final String body = source.substring(headerLen);
|
||||
final ShaderIndex index = new ShaderIndex(tree, body);
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
if (isGraphics(glShaderType)) {
|
||||
PerFrameBlockInjector.collectEdits(bodyTree, perFrame, perPass, edits);
|
||||
PerFrameBlockInjector.collectEdits(index, perFrame, perPass, edits);
|
||||
}
|
||||
final GlslVulkanPreprocess.Metadata meta = GlslVulkanPreprocess.collectEdits(null, bodyTree, glShaderType, "prewarm", true, edits);
|
||||
final GlslVulkanPreprocess.Metadata meta = GlslVulkanPreprocess.collectEdits(index, glShaderType, debugName, true, edits);
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER) {
|
||||
ClipZRemap.collectEdits(bodyTree, edits);
|
||||
ClipZRemap.collectEdits(tree, edits);
|
||||
}
|
||||
SamplerStripper.collectEdits(bodyTree, body, edits);
|
||||
String outHeader = header;
|
||||
SamplerStripper.collectEdits(index, edits);
|
||||
if (glShaderType == GL20.GL_FRAGMENT_SHADER) {
|
||||
DerivativeHoisting.collectEdits(index, edits);
|
||||
}
|
||||
if (headerLen == 0) {
|
||||
return new PrewarmTransformResult(GlslVulkanPreprocess.applyEdits(source, edits), meta.boolUniforms());
|
||||
}
|
||||
if (edits.isEmpty() && !meta.needsSamplerless()) {
|
||||
return new PrewarmTransformResult(source, meta.boolUniforms());
|
||||
}
|
||||
String header = source.substring(0, headerLen);
|
||||
if (meta.needsSamplerless()) {
|
||||
final int nl = header.indexOf('\n');
|
||||
outHeader = header.substring(0, nl) + "\n" + GlslVulkanPreprocess.SAMPLERLESS_EXTENSION + header.substring(nl);
|
||||
header = header.substring(0, nl) + "\n" + GlslVulkanPreprocess.SAMPLERLESS_EXTENSION + header.substring(nl);
|
||||
}
|
||||
if (edits.isEmpty() && outHeader == header) {
|
||||
return new PrewarmTransformResult(finalSource, meta.boolUniforms());
|
||||
}
|
||||
return new PrewarmTransformResult(outHeader + GlslVulkanPreprocess.applyEdits(body, edits), meta.boolUniforms());
|
||||
return new PrewarmTransformResult(header + GlslVulkanPreprocess.applyEdits(body, edits), meta.boolUniforms());
|
||||
}
|
||||
|
||||
public static void prewarmSpirv(String transformedSource, int glShaderType) {
|
||||
@@ -1161,7 +1163,7 @@ public final class ShaderManager {
|
||||
return new CrossCompiled(SDL_GPU_SHADERFORMAT_SPIRV, spirv, "main", false);
|
||||
}
|
||||
if (device.supportsMsl()) {
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, glShaderType);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, glShaderType, device.metalTextureAtomics());
|
||||
return new CrossCompiled(SDL_GPU_SHADERFORMAT_MSL, out.code(), out.entrypoint(), true);
|
||||
}
|
||||
if (device.supportsDxbc()) {
|
||||
@@ -2078,6 +2080,7 @@ public final class ShaderManager {
|
||||
public int vertexShader;
|
||||
public int vertexUboSize;
|
||||
public long sdlComputePipeline;
|
||||
public VertexWriteReplayer.Replay vertexWriteReplay;
|
||||
public long sdlFragmentShader;
|
||||
public long sdlVertexShader;
|
||||
public long lastComputeCb;
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeWalker;
|
||||
@@ -60,16 +61,7 @@ public final class UscaledRetype {
|
||||
final GLSLParser.Single_declarationContext single = idl.single_declaration();
|
||||
if (single == null || single.fully_specified_type() == null) return;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
if (fst.type_qualifier() == null) return;
|
||||
|
||||
boolean isIn = false;
|
||||
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
|
||||
if (stq.storage_qualifier() != null && "in".equals(stq.storage_qualifier().getText())) {
|
||||
isIn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isIn) return;
|
||||
if (!ShaderIndex.hasStorageQualifier(fst.type_qualifier(), "in")) return;
|
||||
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
|
||||
if (single.typeless_declaration() == null || single.typeless_declaration().IDENTIFIER() == null) return;
|
||||
|
||||
|
||||
+17
-6
@@ -150,18 +150,30 @@ public final class CrossCompileUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean usesImageAtomics(ByteBuffer spirv) {
|
||||
final IntBuffer w = spirv.asIntBuffer();
|
||||
for (int i = SPIRV_HEADER_WORDS; i < w.limit(); i = nextInstruction(w, i)) {
|
||||
if ((w.get(i) & 0xFFFF) == Spv.SpvOpImageTexelPointer) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static final int SPIRV_HEADER_WORDS = 5; // magic, version, generator, bound, schema
|
||||
|
||||
private static int nextInstruction(IntBuffer w, int i) {
|
||||
final int len = w.get(i) >>> 16;
|
||||
return len == 0 ? w.limit() : i + len;
|
||||
}
|
||||
|
||||
public static int[] parseLocalSize(ByteBuffer spirv) {
|
||||
final IntBuffer w = spirv.asIntBuffer();
|
||||
final int wordCount = w.limit();
|
||||
int i = 5; // 5-word SPIR-V header (magic, version, generator, bound, schema)
|
||||
int lx = 1, ly = 1, lz = 1;
|
||||
boolean foundLiteral = false;
|
||||
boolean sawExecutionModeId = false;
|
||||
while (i < wordCount) {
|
||||
for (int i = SPIRV_HEADER_WORDS; i < w.limit(); i = nextInstruction(w, i)) {
|
||||
final int word0 = w.get(i);
|
||||
final int opcode = word0 & 0xFFFF;
|
||||
final int instrLen = (word0 >>> 16) & 0xFFFF;
|
||||
if (instrLen == 0) break;
|
||||
final int instrLen = word0 >>> 16;
|
||||
if (opcode == 16 && instrLen >= 6) { // SpvOpExecutionMode
|
||||
final int mode = w.get(i + 2);
|
||||
if (mode == 17) { // SpvExecutionModeLocalSize
|
||||
@@ -173,7 +185,6 @@ public final class CrossCompileUtil {
|
||||
} else if (opcode == 331) { // SpvOpExecutionModeId
|
||||
sawExecutionModeId = true;
|
||||
}
|
||||
i += instrLen;
|
||||
}
|
||||
if (!foundLiteral && sawExecutionModeId) {
|
||||
LOG.warn("SPIR-V uses OpExecutionModeId (spec-constant LocalSizeId); falling back to {{1,1,1}} - not supported by parseLocalSize");
|
||||
|
||||
+10
-5
@@ -26,7 +26,11 @@ public final class MslCrossCompile {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
private static final String BACKEND = "MSL";
|
||||
|
||||
private static final CrossCompileCache CACHE = new CrossCompileCache("msl", CrossCompileUtil::spvcId, MslCrossCompile::crossCompileUncached);
|
||||
private static final int SPVC_MSL_2_2 = mslVersion(2, 2, 0);
|
||||
public static final int SPVC_MSL_3_1 = mslVersion(3, 1, 0);
|
||||
|
||||
private static final CrossCompileCache CACHE = new CrossCompileCache("msl", CrossCompileUtil::spvcId, (spirv, glShaderType) -> compileAtVersion(spirv, glShaderType, SPVC_MSL_2_2));
|
||||
private static final CrossCompileCache CACHE_3_1 = new CrossCompileCache("msl31", CrossCompileUtil::spvcId, (spirv, glShaderType) -> compileAtVersion(spirv, glShaderType, SPVC_MSL_3_1));
|
||||
|
||||
@FunctionalInterface
|
||||
private interface BufferSlotFn { public int slot(int set, int binding); }
|
||||
@@ -36,8 +40,9 @@ public final class MslCrossCompile {
|
||||
|
||||
private MslCrossCompile() {}
|
||||
|
||||
public static Output compile(ByteBuffer spirv, int glShaderType) {
|
||||
return CACHE.compile(spirv, glShaderType);
|
||||
public static Output compile(ByteBuffer spirv, int glShaderType, boolean textureAtomics) {
|
||||
final boolean atomics = textureAtomics && glShaderType == GL43.GL_COMPUTE_SHADER && CrossCompileUtil.usesImageAtomics(spirv);
|
||||
return (atomics ? CACHE_3_1 : CACHE).compile(spirv, glShaderType);
|
||||
}
|
||||
|
||||
private static int executionModelFor(int glShaderType) {
|
||||
@@ -47,7 +52,7 @@ public final class MslCrossCompile {
|
||||
throw new UnsupportedOperationException("Unsupported shader type for MSL cross-compile: 0x" + Integer.toHexString(glShaderType));
|
||||
}
|
||||
|
||||
private static Output crossCompileUncached(ByteBuffer spirv, int glShaderType) {
|
||||
public static Output compileAtVersion(ByteBuffer spirv, int glShaderType, int version) {
|
||||
final int executionModel = executionModelFor(glShaderType);
|
||||
final int dumpId = SystemProperties.dumpShaders() ? CrossCompileUtil.SHADER_DUMP_COUNTER.getAndIncrement() : -1;
|
||||
if (dumpId >= 0) CrossCompileUtil.dumpSpirv(spirv, dumpId, glShaderType);
|
||||
@@ -77,7 +82,7 @@ public final class MslCrossCompile {
|
||||
throw CrossCompileUtil.spvcError(ctx, BACKEND, "create_compiler_options");
|
||||
}
|
||||
final long opts = pOpts.get(0);
|
||||
Spvc.spvc_compiler_options_set_uint(opts, Spvc.SPVC_COMPILER_OPTION_MSL_VERSION, mslVersion(2, 2, 0));
|
||||
Spvc.spvc_compiler_options_set_uint(opts, Spvc.SPVC_COMPILER_OPTION_MSL_VERSION, version);
|
||||
Spvc.spvc_compiler_options_set_uint(opts, Spvc.SPVC_COMPILER_OPTION_MSL_PLATFORM, Spvc.SPVC_MSL_PLATFORM_MACOS);
|
||||
if (Spvc.spvc_compiler_install_compiler_options(compiler, opts) != Spvc.SPVC_SUCCESS) {
|
||||
throw CrossCompileUtil.spvcError(ctx, BACKEND, "install_compiler_options");
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#version 460 core
|
||||
layout(local_size_x = 64) in;
|
||||
layout(binding = 0, r32i) uniform iimage2D counter;
|
||||
|
||||
void main() {
|
||||
imageAtomicAdd(counter, ivec2(0), 1);
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.compute;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GetGPUDeviceDriver;
|
||||
|
||||
class ImageAtomicsGpuTest {
|
||||
|
||||
private static SdlTestRig rig;
|
||||
private static ImageAtomicsProbe probe;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() throws Exception {
|
||||
rig = SdlTestRig.acquireRealDevice();
|
||||
assumeTrue("metal".equalsIgnoreCase(SDL_GetGPUDeviceDriver(rig.sdlHandle)), "native Metal only");
|
||||
assumeTrue(ImageAtomicsProbe.defaultMslVersion() >= ImageAtomicsProbe.MTL_LANGUAGE_VERSION_3_1, "this process compiles Metal source below MSL 3.1");
|
||||
probe = new ImageAtomicsProbe(rig.sdlHandle);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
if (probe != null) probe.release();
|
||||
SdlTestRig.releaseRealDevice();
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomicsCountEveryInvocationOnAnImageTheSizeOfEuphoriasEndCrystalMap() {
|
||||
assertTrue(probe.run(40, 10));
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -77,9 +77,9 @@ class VoxelizationDispatcherTest {
|
||||
void dispatchRegion_splitsAboveMaxGroups() {
|
||||
final RecordingSink sink = new RecordingSink();
|
||||
final VoxelizationDispatcher d = new VoxelizationDispatcher(sink);
|
||||
region(d, 1, VoxelizationDispatcher.MAX_GROUPS_PER_DISPATCH * 64 + 1, new ContextState());
|
||||
region(d, 1, InvocationDispatch.MAX_GROUPS_PER_DISPATCH * 64 + 1, new ContextState());
|
||||
assertEquals(2, sink.dispatches.size());
|
||||
assertEquals(VoxelizationDispatcher.MAX_GROUPS_PER_DISPATCH, sink.dispatches.get(0)[0]);
|
||||
assertEquals(InvocationDispatch.MAX_GROUPS_PER_DISPATCH, sink.dispatches.get(0)[0]);
|
||||
assertEquals(1, sink.dispatches.get(1)[0]);
|
||||
assertEquals(2, sink.uniformPushCount);
|
||||
}
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import it.unimi.dsi.fastutil.longs.Long2BooleanOpenHashMap;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
class DeviceImageAtomicsGpuTest {
|
||||
|
||||
private Device device;
|
||||
|
||||
@BeforeAll
|
||||
static void initSdl() {
|
||||
SdlTestRig.initSdlVideo();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (device != null) device.destroyDevice();
|
||||
}
|
||||
|
||||
private Long2BooleanOpenHashMap verdicts() {
|
||||
return Reflect.get(device, "imageAtomicsBySize");
|
||||
}
|
||||
|
||||
@Test
|
||||
void verdictsAreCachedPerSizeAndDroppedWithTheDevice() {
|
||||
device = new Device();
|
||||
assumeTrue(device.createDevice(), "no SDL GPU device on this machine");
|
||||
assumeTrue(device.metalTextureAtomics(), "needs native Metal compiling at MSL 3.1 or newer");
|
||||
|
||||
assertFalse(device.supportsImageAtomics(40, 10, 2));
|
||||
assertFalse(device.supportsImageAtomics(0, 10, 1));
|
||||
assertFalse(device.supportsImageAtomics(40, -1, 1));
|
||||
assertEquals(0, verdicts().size());
|
||||
|
||||
assertNull(Reflect.get(device, "imageAtomicsProbe"));
|
||||
|
||||
assertTrue(device.supportsImageAtomics(40, 10, 1));
|
||||
assertFalse(device.supportsImageAtomics(64, 64, 1));
|
||||
assertEquals(2, verdicts().size());
|
||||
final Object probe = Reflect.get(device, "imageAtomicsProbe");
|
||||
assertTrue(device.supportsImageAtomics(40, 10, 1));
|
||||
assertFalse(device.supportsImageAtomics(64, 64, 1));
|
||||
assertEquals(2, verdicts().size());
|
||||
assertEquals(probe, Reflect.get(device, "imageAtomicsProbe"));
|
||||
|
||||
device.destroyDevice();
|
||||
assertFalse(device.metalTextureAtomics());
|
||||
assertFalse(device.supportsImageAtomics(40, 10, 1));
|
||||
assertEquals(0, verdicts().size());
|
||||
assertNull(Reflect.get(device, "imageAtomicsProbe"));
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.util.shaderc.Shaderc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.system.MemoryUtil.memFree;
|
||||
|
||||
class DerivativeHoistingTest {
|
||||
|
||||
private static String hoist(String source) {
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
DerivativeHoisting.collectEdits(new ShaderIndex(GlslTransformUtils.parseFullQuiet(source), source), edits);
|
||||
return GlslVulkanPreprocess.applyEdits(source, edits);
|
||||
}
|
||||
|
||||
private static void assertCompiles(String source) {
|
||||
final SpirvCompiler.Result r = SpirvCompiler.compile(source, Shaderc.shaderc_fragment_shader, "derivatives.frag", SpirvCompiler.Options.vulkanForced460Core(), false);
|
||||
assertNotNull(r.spirv(), () -> "compile failed: " + r.error() + "\n" + source);
|
||||
memFree(r.spirv());
|
||||
}
|
||||
|
||||
@Test
|
||||
void directionalBlocklightDerivativesLeaveTheLightmapBranch() {
|
||||
final String out = hoist("""
|
||||
#version 420 core
|
||||
in vec2 lmCoord;
|
||||
in vec3 viewIn;
|
||||
out vec4 color;
|
||||
float light(vec3 viewPos) {
|
||||
vec2 oldLightmap = lmCoord;
|
||||
float result = oldLightmap.x;
|
||||
if (oldLightmap.x > 0.035) {
|
||||
vec2 dFdBlock = vec2(dFdx(oldLightmap.x), dFdy(oldLightmap.x));
|
||||
vec3 dir = vec3(0.0);
|
||||
if (length(dFdBlock) < 1e-6) {
|
||||
dir = vec3(1.0);
|
||||
} else {
|
||||
dir = dFdx(viewPos) * dFdBlock.x + dFdy(viewPos) * dFdBlock.y;
|
||||
}
|
||||
result *= dot(normalize(dir), vec3(0.0, 1.0, 0.0));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
void main() {
|
||||
color = vec4(light(viewIn));
|
||||
}
|
||||
""");
|
||||
|
||||
final int branch = out.indexOf("if (oldLightmap.x > 0.035)");
|
||||
assertTrue(branch > 0, out);
|
||||
assertFalse(out.substring(branch).contains("dFdx("), out);
|
||||
assertFalse(out.substring(branch).contains("dFdy("), out);
|
||||
final String hoisted = out.substring(out.indexOf("float result"), branch);
|
||||
assertTrue(hoisted.contains("float angelica_derivative_0 = dFdx(oldLightmap.x);"), out);
|
||||
assertTrue(hoisted.contains("float angelica_derivative_1 = dFdy(oldLightmap.x);"), out);
|
||||
assertTrue(hoisted.contains("vec3 angelica_derivative_2 = dFdx(viewPos);"), out);
|
||||
assertTrue(hoisted.contains("vec3 angelica_derivative_3 = dFdy(viewPos);"), out);
|
||||
assertCompiles(out);
|
||||
}
|
||||
|
||||
@Test
|
||||
void variableWrittenInsideTheBranchStaysPut() {
|
||||
final String source = """
|
||||
#version 420 core
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
void bump(inout vec2 p) { p += 1.0; }
|
||||
void main() {
|
||||
vec2 a = uv;
|
||||
vec2 b = uv;
|
||||
if (uv.x > 0.5) {
|
||||
a *= 2.0;
|
||||
bump(b);
|
||||
color = vec4(dFdx(a), dFdy(b));
|
||||
}
|
||||
}
|
||||
""";
|
||||
assertEquals(source, hoist(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writesHiddenInsideDirectivesBlockTheMove() {
|
||||
final String source = """
|
||||
#version 420 core
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
void main() {
|
||||
vec2 p = uv;
|
||||
if (true) {
|
||||
#if 1
|
||||
p *= 2.0;
|
||||
#endif
|
||||
color = vec4(dFdx(p), 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
""";
|
||||
assertEquals(source, hoist(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parenthesizedBuiltinOutArgumentCountsAsAWrite() {
|
||||
final String source = """
|
||||
#version 420 core
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
void main() {
|
||||
vec2 p = uv;
|
||||
if (true) {
|
||||
modf(uv * 2.0, (p));
|
||||
color = vec4(dFdx(p), 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
""";
|
||||
assertEquals(source, hoist(source));
|
||||
}
|
||||
|
||||
@Test
|
||||
void globalsStayOnlyWhenACalleeWritesThem() {
|
||||
final String out = hoist("""
|
||||
#version 420 core
|
||||
in vec4 colorIn;
|
||||
out vec4 color;
|
||||
vec4 glColor = colorIn;
|
||||
vec2 nudged = vec2(0.0);
|
||||
float square(float x) { return x * x; }
|
||||
void nudge() { nudged += 1.0; }
|
||||
void touch() { nudge(); }
|
||||
void main() {
|
||||
if (glColor.a > 0.5) {
|
||||
touch();
|
||||
color = vec4(dFdx(glColor.a) + square(1.0), dFdy(nudged), 1.0);
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
final int branch = out.indexOf("if (glColor.a > 0.5)");
|
||||
assertTrue(out.substring(0, branch).contains("float angelica_derivative_0 = dFdx(glColor.a);"), out);
|
||||
assertTrue(out.substring(branch).contains("dFdy(nudged)"), out);
|
||||
assertCompiles(out);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loopsAreNotCrossed() {
|
||||
final String out = hoist("""
|
||||
#version 420 core
|
||||
in vec2 uv;
|
||||
out vec4 color;
|
||||
void main() {
|
||||
color = vec4(0.0);
|
||||
if (uv.y > 0.5) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
vec2 p = uv * float(i);
|
||||
if (p.x > 1.0) {
|
||||
color.xy += dFdx(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
final int loop = out.indexOf("for (");
|
||||
final int inner = out.indexOf("if (p.x > 1.0)");
|
||||
final int hoisted = out.indexOf("vec2 angelica_derivative_0 = dFdx(p);");
|
||||
assertTrue(loop < hoisted && hoisted < inner, out);
|
||||
assertCompiles(out);
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
|
||||
@@ -30,19 +31,19 @@ final class ShaderTransformChain {
|
||||
}
|
||||
|
||||
static String clipZ(String source) {
|
||||
return edit(source, ClipZRemap::collectEdits);
|
||||
return edit(source, (index, edits) -> ClipZRemap.collectEdits(index.root(), edits));
|
||||
}
|
||||
|
||||
static String stripUnused(String source) {
|
||||
if (!source.contains("sampler")) return source;
|
||||
return edit(source, (root, edits) -> SamplerStripper.collectEdits(root, source, edits));
|
||||
return edit(source, SamplerStripper::collectEdits);
|
||||
}
|
||||
|
||||
static String inject(String source, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {
|
||||
return edit(source, (root, edits) -> PerFrameBlockInjector.collectEdits(root, perFrame, perPass, edits));
|
||||
return edit(source, (index, edits) -> PerFrameBlockInjector.collectEdits(index, perFrame, perPass, edits));
|
||||
}
|
||||
|
||||
private static String edit(String source, BiConsumer<GLSLParser.Translation_unitContext, List<Edit>> collect) {
|
||||
private static String edit(String source, BiConsumer<ShaderIndex, List<Edit>> collect) {
|
||||
final GLSLParser.Translation_unitContext root;
|
||||
try {
|
||||
root = GlslTransformUtils.parseFullQuiet(source);
|
||||
@@ -50,7 +51,7 @@ final class ShaderTransformChain {
|
||||
return source;
|
||||
}
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
collect.accept(root, edits);
|
||||
collect.accept(new ShaderIndex(root, source), edits);
|
||||
return edits.isEmpty() ? source : GlslVulkanPreprocess.applyEdits(source, edits);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -57,7 +57,7 @@ class FragmentStorageTextureSlotTest {
|
||||
final ByteBuffer spirv = r.spirv();
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, false);
|
||||
try {
|
||||
final String msl = StandardCharsets.UTF_8.decode(out.code().duplicate()).toString();
|
||||
final boolean[] seen = new boolean[samplerCount];
|
||||
@@ -136,7 +136,7 @@ class FragmentStorageTextureSlotTest {
|
||||
final ByteBuffer spirv = Reflect.get(obj, "spirv");
|
||||
final ShaderManager.StageReflection refl = Reflect.get(obj, "reflection");
|
||||
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, false);
|
||||
try {
|
||||
final String msl = StandardCharsets.UTF_8.decode(out.code().duplicate()).toString();
|
||||
final int samplerCount = refl.counts().numSamplers();
|
||||
|
||||
+102
-12
@@ -8,6 +8,7 @@ import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.SpirvTestShaders;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.cross.CrossCompileCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.cross.CrossCompileUtil;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@@ -52,7 +53,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(SpirvTestShaders.VERTEX_GLSL, Shaderc.shaderc_vertex_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.length() > 0, "MSL output non-empty");
|
||||
@@ -75,7 +76,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(SpirvTestShaders.FRAGMENT_GLSL, Shaderc.shaderc_fragment_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.contains("fragment "), "MSL should contain fragment stage qualifier:\n" + msl);
|
||||
@@ -114,7 +115,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(SpirvTestShaders.LDS_MULTI_MIP_GLSL, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.contains("kernel "), "Compute MSL should contain kernel stage qualifier:\n" + msl);
|
||||
@@ -146,7 +147,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(glsl, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.contains("kernel "), "compute MSL should contain kernel stage qualifier:\n" + msl);
|
||||
@@ -167,7 +168,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(CHUNK_CULL_GLSL, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.contains("kernel "), "Compute MSL should contain kernel stage qualifier:\n" + msl);
|
||||
@@ -202,7 +203,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(VERTEX_WRITES_IMAGE_GLSL, Shaderc.shaderc_vertex_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertFalse(msl.contains("vertex void main0"),
|
||||
@@ -236,7 +237,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(pre.rewrittenSource(), Shaderc.shaderc_fragment_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertTrue(msl.contains("angelica_renamed_try"), "the local must keep a renamed identifier:\n" + msl);
|
||||
@@ -255,7 +256,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(SpirvTestShaders.VERTEX_GLSL, Shaderc.shaderc_vertex_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false);
|
||||
MemoryUtil.memFree(out.code());
|
||||
} finally {
|
||||
MemoryUtil.memFree(spirv);
|
||||
@@ -381,7 +382,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(glsl, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
|
||||
try {
|
||||
final String msl = decode(out.code());
|
||||
assertUniqueBufferSlots(msl);
|
||||
@@ -436,7 +437,7 @@ class MslCrossCompileTest {
|
||||
final ByteBuffer spirv = compile(glsl, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
|
||||
try {
|
||||
assertTrue(decode(out.code()).contains("kernel "), name + " should produce compute MSL");
|
||||
} finally {
|
||||
@@ -456,10 +457,10 @@ class MslCrossCompileTest {
|
||||
try {
|
||||
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final byte[] heap = ShaderCacheIO.toHeap(spirv);
|
||||
MemoryUtil.memFree(MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER).code());
|
||||
MemoryUtil.memFree(MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false).code());
|
||||
Reflect.<Map<?, ?>>get(cache(), "cache").clear();
|
||||
ShaderDiskCache.putBlob(cache().diskKey(heap, GL20.GL_VERTEX_SHADER), "poison_entry", "poison".getBytes(StandardCharsets.UTF_8));
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false);
|
||||
try {
|
||||
assertEquals("poison_entry", out.entrypoint());
|
||||
assertEquals("poison", new String(ShaderCacheIO.toHeap(out.code()), StandardCharsets.UTF_8));
|
||||
@@ -471,6 +472,95 @@ class MslCrossCompileTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String IMAGE_ATOMIC_GLSL = """
|
||||
#version 460 core
|
||||
layout(local_size_x = 64) in;
|
||||
layout(binding = 0, r32i) uniform iimage2D counter;
|
||||
void main() {
|
||||
imageAtomicAdd(counter, ivec2(0, 0), 1);
|
||||
}
|
||||
""";
|
||||
|
||||
@Test
|
||||
void imageAtomicsUseTheAuxBufferWithoutTextureAtomics() {
|
||||
assertTrue(computeMsl(IMAGE_ATOMIC_GLSL, false).contains("counter_atomic"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void imageAtomicsCompileToTextureAtomicsWhenAvailable() {
|
||||
final String msl = computeMsl(IMAGE_ATOMIC_GLSL, true);
|
||||
assertFalse(msl.contains("counter_atomic"), msl);
|
||||
assertTrue(msl.contains("counter.atomic_fetch_add("), msl);
|
||||
}
|
||||
|
||||
@Test
|
||||
void textureAtomicsDoNotChangeShadersWithoutImageAtomics() {
|
||||
final String base = computeMsl(CHUNK_CULL_GLSL, false);
|
||||
Reflect.<Map<?, ?>>get(cache(), "cache").clear();
|
||||
assertEquals(base, computeMsl(CHUNK_CULL_GLSL, true));
|
||||
}
|
||||
|
||||
private static final String FRAGMENT_IMAGE_ATOMIC_GLSL = """
|
||||
#version 460 core
|
||||
layout(binding = 0, r32i) uniform iimage2D counter;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = vec4(imageAtomicAdd(counter, ivec2(0, 0), 1));
|
||||
}
|
||||
""";
|
||||
|
||||
@Test
|
||||
void textureAtomicsLeaveRasterStagesAtMsl22() {
|
||||
final ByteBuffer spirv = compile(FRAGMENT_IMAGE_ATOMIC_GLSL, Shaderc.shaderc_fragment_shader);
|
||||
try {
|
||||
assertTrue(CrossCompileUtil.usesImageAtomics(spirv));
|
||||
final String base = fragmentMsl(spirv, false);
|
||||
Reflect.<Map<?, ?>>get(cache(), "cache").clear();
|
||||
final String atomics = fragmentMsl(spirv, true);
|
||||
assertEquals(base, atomics);
|
||||
assertFalse(atomics.contains("counter.atomic_fetch_add("), atomics);
|
||||
} finally {
|
||||
MemoryUtil.memFree(spirv);
|
||||
}
|
||||
}
|
||||
|
||||
private static String fragmentMsl(ByteBuffer spirv, boolean textureAtomics) {
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, textureAtomics);
|
||||
try {
|
||||
return decode(out.code());
|
||||
} finally {
|
||||
MemoryUtil.memFree(out.code());
|
||||
}
|
||||
}
|
||||
|
||||
private static String computeMsl(String glsl, boolean textureAtomics) {
|
||||
final ByteBuffer spirv = compile(glsl, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
|
||||
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, textureAtomics);
|
||||
try {
|
||||
return decode(out.code());
|
||||
} finally {
|
||||
MemoryUtil.memFree(out.code());
|
||||
}
|
||||
} finally {
|
||||
MemoryUtil.memFree(spirv);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyShadersWithImageAtomicsAreDetected() {
|
||||
final ByteBuffer atomic = compile(IMAGE_ATOMIC_GLSL, Shaderc.shaderc_compute_shader);
|
||||
final ByteBuffer plain = compile(CHUNK_CULL_GLSL, Shaderc.shaderc_compute_shader);
|
||||
try {
|
||||
assertTrue(CrossCompileUtil.usesImageAtomics(atomic));
|
||||
assertFalse(CrossCompileUtil.usesImageAtomics(plain));
|
||||
} finally {
|
||||
MemoryUtil.memFree(atomic);
|
||||
MemoryUtil.memFree(plain);
|
||||
}
|
||||
}
|
||||
|
||||
private static String decode(ByteBuffer buf) {
|
||||
final ByteBuffer slice = buf.duplicate();
|
||||
int len = slice.remaining();
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
|
||||
public final class EnderDragonBeams {
|
||||
|
||||
private static final NamespacedId CRYSTAL_BEAM = new NamespacedId("minecraft", "end_crystal_beam");
|
||||
private static final NamespacedId DEATH_RAYS = new NamespacedId("minecraft", "dragon_death_rays");
|
||||
|
||||
private EnderDragonBeams() {}
|
||||
|
||||
public static void beginCrystalBeam() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
|
||||
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(CRYSTAL_BEAM);
|
||||
}
|
||||
|
||||
public static void endCrystalBeam() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
|
||||
}
|
||||
|
||||
public static void beginDeathRays() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.LIGHTNING);
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
|
||||
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(DEATH_RAYS);
|
||||
}
|
||||
|
||||
public static void endDeathRays() {
|
||||
if (!AngelicaConfig.enableIris) return;
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
|
||||
GbufferPrograms.teardownSpecialRenderCondition();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.gtnewhorizons.angelica.api;
|
||||
|
||||
public interface EyePassRenderer {
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package com.gtnewhorizons.angelica.client.font;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
|
||||
import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
|
||||
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
@@ -393,14 +392,33 @@ public class BatchingFontRenderer {
|
||||
pushQuadIdx();
|
||||
}
|
||||
|
||||
private static final float[] MAX_AA_REACH = {0f, 6f, 8f};
|
||||
|
||||
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz, boolean flipV) {
|
||||
ensureCapacity();
|
||||
|
||||
final float tBu0 = uStart;
|
||||
final float tBu1 = uStart + uSz;
|
||||
final float tBv0 = vStart;
|
||||
final float tBv1 = vStart + vSz;
|
||||
|
||||
if (FontConfig.fontAAMode > 0 && w > 0 && h > 0) {
|
||||
final float margin = MAX_AA_REACH[FontConfig.fontAAMode] * preprocessAAStrength() + 0.5f;
|
||||
final float marginU = margin * (uSz / w);
|
||||
final float marginV = margin * (vSz / h);
|
||||
itOff *= (h + 2 * margin) / h;
|
||||
x -= margin; w += 2 * margin;
|
||||
y -= margin; h += 2 * margin;
|
||||
uStart -= marginU; uSz += 2 * marginU;
|
||||
vStart -= marginV; vSz += 2 * marginV;
|
||||
}
|
||||
|
||||
float vTop = flipV ? vStart + vSz : vStart;
|
||||
float vBot = flipV ? vStart : vStart + vSz;
|
||||
pushVtx(x + itOff, y, rgba, uStart, vTop, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x - itOff, y + h, rgba, uStart, vBot, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vTop, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vBot, uStart, uStart + uSz, vStart, vStart + vSz);
|
||||
pushVtx(x + itOff, y, rgba, uStart, vTop, tBu0, tBu1, tBv0, tBv1);
|
||||
pushVtx(x - itOff, y + h, rgba, uStart, vBot, tBu0, tBu1, tBv0, tBv1);
|
||||
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vTop, tBu0, tBu1, tBv0, tBv1);
|
||||
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vBot, tBu0, tBu1, tBv0, tBv1);
|
||||
pushQuadIdx();
|
||||
}
|
||||
|
||||
@@ -859,6 +877,10 @@ public class BatchingFontRenderer {
|
||||
truncateBatchToWatermark();
|
||||
}
|
||||
|
||||
private static float preprocessAAStrength() {
|
||||
return FontConfig.fontAAStrength / 120.f;
|
||||
}
|
||||
|
||||
private void setupFontDrawState() {
|
||||
GLStateManager.enableTexture();
|
||||
GLStateManager.enableAlphaTest();
|
||||
@@ -873,7 +895,7 @@ public class BatchingFontRenderer {
|
||||
}
|
||||
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
|
||||
fontAAStrengthLast = FontConfig.fontAAStrength;
|
||||
GLStateManager.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
|
||||
GLStateManager.glUniform1f(AAStrength, preprocessAAStrength());
|
||||
}
|
||||
if (FontConfig.fontBrightness != fontBrightnessLast) {
|
||||
fontBrightnessLast = FontConfig.fontBrightness;
|
||||
@@ -1429,16 +1451,7 @@ public class BatchingFontRenderer {
|
||||
|
||||
if (chr == ColorCodeUtils.ESCAPED_AMPERSAND) { chr = '&'; }
|
||||
|
||||
if (FontConfig.enableCustomFont && FontConfig.enableGlyphReplacements) {
|
||||
final char replacement = GlyphReplacements.getReplacementGlyph(chr);
|
||||
if (replacement != 0) {
|
||||
if (FontProviderCustom.getPrimary().isGlyphAvailable(replacement)
|
||||
|| FontProviderCustom.getFallback().isGlyphAvailable(replacement)
|
||||
) {
|
||||
chr = replacement;
|
||||
}
|
||||
}
|
||||
}
|
||||
chr = FontStrategist.replaceCustomGlyph(this, chr, unicodeFlag);
|
||||
|
||||
// ASCII space, NBSP, NNBSP, decided before obfuscation as vanilla does.
|
||||
final boolean whitespace = chr == ' ' || chr == '\u00A0' || chr == '\u202F';
|
||||
@@ -1451,7 +1464,8 @@ public class BatchingFontRenderer {
|
||||
chr = fontProvider.getRandomReplacement(chr);
|
||||
}
|
||||
|
||||
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2);
|
||||
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2)
|
||||
+ fontProvider.getBaselineShift() * glyphScaleY;
|
||||
float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY * fontProvider.getYScaleMultiplier();
|
||||
|
||||
visibleCharIndex++;
|
||||
@@ -1623,7 +1637,9 @@ public class BatchingFontRenderer {
|
||||
return 4 * this.getWhitespaceScale();
|
||||
}
|
||||
|
||||
FontProvider fp = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, underlying.getUnicodeFlag());
|
||||
final boolean unicodeFlag = underlying.getUnicodeFlag();
|
||||
chr = FontStrategist.replaceCustomGlyph(this, chr, unicodeFlag);
|
||||
FontProvider fp = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, unicodeFlag);
|
||||
|
||||
return fp.getXAdvance(chr) * this.getGlyphScaleX();
|
||||
}
|
||||
|
||||
@@ -20,4 +20,7 @@ public interface FontProvider {
|
||||
float getShadowOffset();
|
||||
int getTexture(char chr);
|
||||
float getYScaleMultiplier();
|
||||
|
||||
/** Vertical offset in font pixels, applied after the glyph is centered on the line. */
|
||||
default float getBaselineShift() { return 0.0f; }
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import it.unimi.dsi.fastutil.chars.CharArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.IReloadableResourceManager;
|
||||
import net.minecraft.client.resources.IResource;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* For modern Minecraft's hand-drawn font sheets.
|
||||
*/
|
||||
public final class FontProviderBitmap implements FontProvider {
|
||||
|
||||
private static final ResourceLocation DEFINITION = new ResourceLocation("angelica", "font/modern.json");
|
||||
private static final int DEFAULT_HEIGHT = 8;
|
||||
private static final FontProviderBitmap[] NONE = new FontProviderBitmap[0];
|
||||
|
||||
private static volatile FontProviderBitmap[] loaded;
|
||||
|
||||
public static void registerReloadListener() {
|
||||
((IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager()).registerReloadListener(manager -> loaded = null);
|
||||
}
|
||||
|
||||
private final ResourceLocation sheet;
|
||||
private final int columns;
|
||||
private final float uSize;
|
||||
private final float vSize;
|
||||
private final float glyphW;
|
||||
private final float yScale;
|
||||
private final float baselineShift;
|
||||
private final char[][] slotPages = new char[256][];
|
||||
private final int[] advances;
|
||||
private final Int2ObjectOpenHashMap<char[]> charsByAdvance = new Int2ObjectOpenHashMap<>();
|
||||
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
|
||||
|
||||
public static FontProviderBitmap find(char chr) {
|
||||
for (FontProviderBitmap provider : providers()) {
|
||||
if (provider.slot(chr) >= 0) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static FontProviderBitmap[] providers() {
|
||||
FontProviderBitmap[] local = loaded;
|
||||
if (local == null) {
|
||||
final IResourceManager resources = Minecraft.getMinecraft().getResourceManager();
|
||||
if (!resources.getResourceDomains().contains(DEFINITION.getResourceDomain())) {
|
||||
return NONE;
|
||||
}
|
||||
synchronized (FontProviderBitmap.class) {
|
||||
local = loaded;
|
||||
if (local == null) {
|
||||
local = load(resources);
|
||||
loaded = local;
|
||||
}
|
||||
}
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
||||
private static FontProviderBitmap[] load(IResourceManager resources) {
|
||||
final List<FontProviderBitmap> providers = new ArrayList<>();
|
||||
try (Reader reader = new InputStreamReader(resources.getResource(DEFINITION).getInputStream(), StandardCharsets.UTF_8)) {
|
||||
final JsonArray definitions = new JsonParser().parse(reader).getAsJsonObject().getAsJsonArray("providers");
|
||||
for (JsonElement element : definitions) {
|
||||
final JsonObject definition = element.getAsJsonObject();
|
||||
if (!"bitmap".equals(definition.get("type").getAsString())) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
providers.add(new FontProviderBitmap(definition, resources));
|
||||
} catch (IOException | RuntimeException e) {
|
||||
FontStrategist.LOGGER.warn("Skipping modern font sheet {}", definition.get("file"), e);
|
||||
}
|
||||
}
|
||||
} catch (IOException | RuntimeException e) {
|
||||
FontStrategist.LOGGER.error("Could not read modern font definition {}", DEFINITION, e);
|
||||
}
|
||||
return providers.toArray(NONE);
|
||||
}
|
||||
|
||||
private static ResourceLocation resolveSheet(IResourceManager resources, ResourceLocation sheet) {
|
||||
final ResourceLocation bundled = new ResourceLocation("angelica", sheet.getResourcePath());
|
||||
try {
|
||||
final int vanillaCopies = Minecraft.getMinecraft().mcDefaultResourcePack.resourceExists(sheet) ? 1 : 0;
|
||||
final List<IResource> copies = resources.getAllResources(sheet);
|
||||
for (IResource copy : copies) {
|
||||
copy.getInputStream().close();
|
||||
}
|
||||
return copies.size() > vanillaCopies ? sheet : bundled;
|
||||
} catch (IOException e) {
|
||||
return bundled;
|
||||
}
|
||||
}
|
||||
|
||||
private FontProviderBitmap(JsonObject definition, IResourceManager resources) throws IOException {
|
||||
final ResourceLocation file = new ResourceLocation(definition.get("file").getAsString());
|
||||
this.sheet = resolveSheet(resources, new ResourceLocation(file.getResourceDomain(), "textures/" + file.getResourcePath()));
|
||||
final int height = definition.has("height") ? definition.get("height").getAsInt() : DEFAULT_HEIGHT;
|
||||
final int ascent = definition.get("ascent").getAsInt();
|
||||
final JsonArray rows = definition.getAsJsonArray("chars");
|
||||
|
||||
final BufferedImage image;
|
||||
try (InputStream in = resources.getResource(this.sheet).getInputStream()) {
|
||||
image = ImageIO.read(in);
|
||||
}
|
||||
final String firstRow = rows.get(0).getAsString();
|
||||
this.columns = firstRow.codePointCount(0, firstRow.length());
|
||||
final int cellWidth = image.getWidth() / this.columns;
|
||||
final int cellHeight = image.getHeight() / rows.size();
|
||||
final float scale = (float) height / cellHeight;
|
||||
|
||||
this.uSize = (float) cellWidth / image.getWidth();
|
||||
this.vSize = (float) cellHeight / image.getHeight();
|
||||
this.glyphW = cellWidth * scale + 1.0f;
|
||||
this.yScale = (float) height / DEFAULT_HEIGHT;
|
||||
this.baselineShift = 3.0f + height / 2.0f - ascent;
|
||||
this.advances = new int[this.columns * rows.size()];
|
||||
|
||||
final Int2ObjectOpenHashMap<CharArrayList> byAdvance = new Int2ObjectOpenHashMap<>();
|
||||
for (int row = 0; row < rows.size(); row++) {
|
||||
final int[] codepoints = rows.get(row).getAsString().codePoints().toArray();
|
||||
for (int column = 0; column < codepoints.length; column++) {
|
||||
final int codepoint = codepoints[column];
|
||||
if (codepoint == 0 || codepoint > Character.MAX_VALUE) {
|
||||
continue;
|
||||
}
|
||||
final char chr = (char) codepoint;
|
||||
final int slot = row * this.columns + column;
|
||||
final int ink = inkWidth(image, column * cellWidth, row * cellHeight, cellWidth, cellHeight);
|
||||
this.advances[slot] = (int) (0.5f + ink * scale) + 1;
|
||||
|
||||
char[] page = this.slotPages[chr >>> 8];
|
||||
if (page == null) {
|
||||
page = this.slotPages[chr >>> 8] = new char[256];
|
||||
}
|
||||
page[chr & 0xFF] = (char) (slot + 1);
|
||||
byAdvance.computeIfAbsent(this.advances[slot], k -> new CharArrayList()).add(chr);
|
||||
}
|
||||
}
|
||||
byAdvance.int2ObjectEntrySet().forEach(e -> this.charsByAdvance.put(e.getIntKey(), e.getValue().toCharArray()));
|
||||
}
|
||||
|
||||
private static int inkWidth(BufferedImage image, int cellX, int cellY, int cellWidth, int cellHeight) {
|
||||
for (int x = cellWidth - 1; x >= 0; x--) {
|
||||
for (int y = 0; y < cellHeight; y++) {
|
||||
if ((image.getRGB(cellX + x, cellY + y) >>> 24) != 0) {
|
||||
return x + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int slot(char chr) {
|
||||
final char[] page = this.slotPages[chr >>> 8];
|
||||
return page == null ? -1 : page[chr & 0xFF] - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
return slot(chr) >= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getRandomReplacement(char chr) {
|
||||
final int slot = slot(chr);
|
||||
if (slot < 0) {
|
||||
return chr;
|
||||
}
|
||||
final char[] sameWidth = this.charsByAdvance.get(this.advances[slot]);
|
||||
return sameWidth[this.fontRandom.nextInt(sameWidth.length)];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUStart(char chr) {
|
||||
return (slot(chr) % this.columns) * this.uSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVStart(char chr) {
|
||||
return (float) (slot(chr) / this.columns) * this.vSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getXAdvance(char chr) {
|
||||
return this.advances[slot(chr)];
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getGlyphW(char chr) {
|
||||
return this.glyphW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUSize(char chr) {
|
||||
return this.uSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getVSize(char chr) {
|
||||
return this.vSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShadowOffset() {
|
||||
return FontConfig.fontShadowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTexture(char chr) {
|
||||
return FontStrategist.getIntFromResourceLocation(this.sheet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getYScaleMultiplier() {
|
||||
return this.yScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBaselineShift() {
|
||||
return this.baselineShift;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
|
||||
@@ -166,8 +167,12 @@ public class FontStrategist {
|
||||
if (fp.isGlyphAvailable(chr)) { return fp; }
|
||||
fp = FontProviderCustom.getFallback();
|
||||
if (fp.isGlyphAvailable(chr)) { return fp; }
|
||||
fp = findModernGlyph(me, chr, forceUnicode);
|
||||
if (fp != null) { return fp; }
|
||||
return FontProviderUnicode.get();
|
||||
} else {
|
||||
final FontProvider modern = findModernGlyph(me, chr, forceUnicode);
|
||||
if (modern != null) { return modern; }
|
||||
if (!forceUnicode && FontProviderMC.get(false).isGlyphAvailable(chr)) {
|
||||
return FontProviderMC.get(false);
|
||||
} else {
|
||||
@@ -176,6 +181,23 @@ public class FontStrategist {
|
||||
}
|
||||
}
|
||||
|
||||
public static char replaceCustomGlyph(BatchingFontRenderer me, char chr, boolean forceUnicode) {
|
||||
if (!FontConfig.enableGlyphReplacements || !(FontConfig.enableCustomFont || FontConfig.enableModernFont)) { return chr; }
|
||||
final char original = GlyphReplacements.getReplacementGlyph(chr);
|
||||
if (original == 0) { return chr; }
|
||||
if (FontConfig.enableCustomFont
|
||||
&& (FontProviderCustom.getPrimary().isGlyphAvailable(original) || FontProviderCustom.getFallback().isGlyphAvailable(original))) {
|
||||
return original;
|
||||
}
|
||||
return findModernGlyph(me, original, forceUnicode) != null ? original : chr;
|
||||
}
|
||||
|
||||
// The splash font renderer can only bind its own ascii texture.
|
||||
private static FontProvider findModernGlyph(BatchingFontRenderer me, char chr, boolean forceUnicode) {
|
||||
if (!FontConfig.enableModernFont || forceUnicode || me.isSplash) { return null; }
|
||||
return FontProviderBitmap.find(chr);
|
||||
}
|
||||
|
||||
public static void reloadCustomFontProviders() {
|
||||
FontProviderCustom.getPrimary().setFont(null);
|
||||
FontProviderCustom.getFallback().setFont(null);
|
||||
|
||||
@@ -2,30 +2,49 @@ package com.gtnewhorizons.angelica.client.rendering;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Used to store the block bounds fields in a thread-safe manner, as instance fields don't work correctly
|
||||
* on multiple threads.
|
||||
*/
|
||||
public class ThreadedBlockData {
|
||||
public static final Thread MAIN_THREAD = Thread.currentThread();
|
||||
public static Thread serverThread;
|
||||
public final class ThreadedBlockData {
|
||||
|
||||
public static final Thread MAIN_THREAD = Thread.currentThread();
|
||||
private static Thread SERVER_THREAD;
|
||||
private static final AtomicInteger CURRENT_SERVER_THREAD_ID = new AtomicInteger();
|
||||
|
||||
private final int serverThreadId = Thread.currentThread() == SERVER_THREAD ? CURRENT_SERVER_THREAD_ID.get() : -1;
|
||||
public double minX, minY, minZ, maxX, maxY, maxZ;
|
||||
public final Thread owner = Thread.currentThread();
|
||||
|
||||
public ThreadedBlockData() {}
|
||||
|
||||
public ThreadedBlockData(ThreadedBlockData other) {
|
||||
this.minX = other.minX;
|
||||
this.minY = other.minY;
|
||||
this.minY = other.minY;
|
||||
this.minZ = other.minZ;
|
||||
this.maxX = other.maxX;
|
||||
this.maxY = other.maxY;
|
||||
this.maxZ = other.maxZ;
|
||||
}
|
||||
|
||||
public boolean isCurrent() {
|
||||
return this.serverThreadId == CURRENT_SERVER_THREAD_ID.get();
|
||||
}
|
||||
|
||||
public static ThreadedBlockData get(Block block) {
|
||||
return ((Getter)block).angelica$getThreadData();
|
||||
return ((Getter) block).angelica$getThreadData();
|
||||
}
|
||||
|
||||
public static void setServerThread(Thread t) {
|
||||
ThreadedBlockData.SERVER_THREAD = t;
|
||||
if (t != null) {
|
||||
CURRENT_SERVER_THREAD_ID.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
public static Thread getServerThread() {
|
||||
return SERVER_THREAD;
|
||||
}
|
||||
|
||||
public interface Getter {
|
||||
|
||||
@@ -8,6 +8,10 @@ public class FontConfig {
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean enableCustomFont;
|
||||
|
||||
@Config.Comment({"Draw text with modern Minecraft's font sheets"})
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean enableModernFont;
|
||||
|
||||
@Config.Comment("Name of the primary custom font. Best not to set it from here.")
|
||||
@Config.DefaultString("(none)")
|
||||
public static String customFontNamePrimary;
|
||||
|
||||
@@ -32,6 +32,7 @@ import net.minecraft.world.World;
|
||||
import net.minecraft.world.WorldServer;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.IChunkProvider;
|
||||
import net.minecraftforge.common.DimensionManager;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.Display;
|
||||
@@ -75,6 +76,9 @@ public final class FlybyRunner {
|
||||
private boolean jfr;
|
||||
private long recordingId;
|
||||
private final AtomicReference<FlybyRequest> pendingRequest = new AtomicReference<>();
|
||||
private final AtomicReference<String> pendingDimensionPlayer = new AtomicReference<>();
|
||||
private boolean dimensionRequested;
|
||||
private volatile boolean dimensionAbandoned;
|
||||
private FlybyRequest activeRequest;
|
||||
private String[] sceneCommands = NO_COMMANDS;
|
||||
private volatile boolean sceneClearRequested;
|
||||
@@ -197,6 +201,8 @@ public final class FlybyRunner {
|
||||
this.waitForTracy = false;
|
||||
this.waitForFocus = false;
|
||||
this.exitWhenDone = false;
|
||||
this.dimensionRequested = false;
|
||||
this.dimensionAbandoned = false;
|
||||
this.jfr = false;
|
||||
this.armed = false;
|
||||
this.fixedOrigin = null;
|
||||
@@ -263,6 +269,7 @@ public final class FlybyRunner {
|
||||
if (this.waitTicks++ % 100 == 0) LOGGER.info("Flyby waiting for window focus");
|
||||
return;
|
||||
}
|
||||
if (this.awaitingDimension(mc, player)) return;
|
||||
this.begin(mc, player);
|
||||
}
|
||||
case PREPARING -> {
|
||||
@@ -331,6 +338,22 @@ public final class FlybyRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean awaitingDimension(Minecraft mc, EntityClientPlayerMP player) {
|
||||
final Integer dimension = SystemProperties.FLYBY_DIMENSION;
|
||||
if (!this.startedFromProperties || dimension == null || player.dimension == dimension || this.dimensionAbandoned) return false;
|
||||
if (mc.getIntegratedServer() == null || !DimensionManager.isDimensionRegistered(dimension)) {
|
||||
LOGGER.warn("Flyby: {}, staying in dimension {}", mc.getIntegratedServer() == null ? "not singleplayer" : "dimension " + dimension + " is not registered", player.dimension);
|
||||
this.dimensionAbandoned = true;
|
||||
return false;
|
||||
}
|
||||
if (!this.dimensionRequested) {
|
||||
this.dimensionRequested = true;
|
||||
LOGGER.info("Flyby: moving from dimension {} to {}", player.dimension, dimension);
|
||||
this.pendingDimensionPlayer.set(player.getCommandSenderName());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void overridePauseOnLostFocus(Minecraft mc) {
|
||||
if (this.pauseOnLostFocusOverridden) return;
|
||||
this.pauseOnLostFocusSaved = mc.gameSettings.pauseOnLostFocus;
|
||||
@@ -600,11 +623,23 @@ public final class FlybyRunner {
|
||||
public void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
if (event.phase != TickEvent.Phase.END) return;
|
||||
final boolean clear = this.sceneClearRequested;
|
||||
if (!clear && this.pendingRequest.get() == null) return;
|
||||
if (!clear && this.pendingRequest.get() == null && this.pendingDimensionPlayer.get() == null) return;
|
||||
|
||||
final MinecraftServer server = MinecraftServer.getServer();
|
||||
if (server == null) return;
|
||||
|
||||
final String traveller = this.pendingDimensionPlayer.getAndSet(null);
|
||||
if (traveller != null) {
|
||||
this.discardWorldChanges(server);
|
||||
final EntityPlayerMP player = server.getConfigurationManager().func_152612_a(traveller);
|
||||
if (player == null) {
|
||||
LOGGER.warn("Flyby: no server player named '{}', staying in the current dimension", traveller);
|
||||
this.dimensionAbandoned = true;
|
||||
} else {
|
||||
server.getConfigurationManager().transferPlayerToDimension(player, SystemProperties.FLYBY_DIMENSION);
|
||||
}
|
||||
}
|
||||
|
||||
if (clear) {
|
||||
this.sceneClearRequested = false;
|
||||
this.clearScene(server);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.helpers;
|
||||
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.FORMATTING_CHAR;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.EyePassRenderer;
|
||||
import net.minecraft.client.renderer.entity.RenderDragon;
|
||||
import net.minecraft.client.renderer.entity.RenderEnderman;
|
||||
import net.minecraft.client.renderer.entity.RenderSpider;
|
||||
@@ -15,7 +16,8 @@ public final class RendererLivingEntityHelper {
|
||||
private RendererLivingEntityHelper() {}
|
||||
|
||||
public static boolean hasEyePass(Object renderer) {
|
||||
return renderer instanceof RenderSpider || renderer instanceof RenderEnderman || renderer instanceof RenderDragon;
|
||||
return renderer instanceof RenderSpider || renderer instanceof RenderEnderman || renderer instanceof RenderDragon
|
||||
|| renderer instanceof EyePassRenderer;
|
||||
}
|
||||
|
||||
public static String getUpsideDownName(EntityLivingBase entity) {
|
||||
|
||||
+13
@@ -165,11 +165,19 @@ public class DarkModeFontTransform {
|
||||
RecolorTarget.includeMethodCall(
|
||||
new MethodInfo("micdoodle8.mods.galacticraft.core.client.gui.element.GuiElementCheckbox func_146112_a(Lnet/minecraft/client/Minecraft;II)V # drawButton"),
|
||||
new MethodInfo("net.minecraft.client.gui.FontRenderer func_85187_a(Ljava/lang/String;IIIZ)I # drawString")
|
||||
),
|
||||
RecolorTarget.includeMethodCall(
|
||||
new MethodInfo("micdoodle8.mods.galacticraft.core.client.gui.container.GuiAirLockController func_73863_a(IIF)V # drawScreen"),
|
||||
new MethodInfo("net.minecraft.client.gui.FontRenderer func_78276_b(Ljava/lang/String;III)I # drawString")
|
||||
),
|
||||
// Steve's Factory Manager
|
||||
RecolorTarget.includeMethodCall(
|
||||
new MethodInfo("vswe.stevesfactory.interfaces.GuiBase drawString(Ljava/lang/String;IIFI)V"),
|
||||
new MethodInfo("net.minecraft.client.gui.FontRenderer func_78276_b(Ljava/lang/String;III)I # drawString")
|
||||
),
|
||||
RecolorTarget.includeMethodCall(
|
||||
new MethodInfo("vswe.stevesfactory.interfaces.GuiBase drawSplitString(Ljava/lang/String;IIIFI)V"),
|
||||
new MethodInfo("net.minecraft.client.gui.FontRenderer func_78279_b(Ljava/lang/String;IIII)V # drawSplitString")
|
||||
),
|
||||
// Steve's Carts 2
|
||||
RecolorTarget.includeMethodCall(
|
||||
@@ -252,6 +260,11 @@ public class DarkModeFontTransform {
|
||||
new MethodInfo("com.cleanroommc.modularui.widgets.RichTextWidget draw(Lcom/cleanroommc/modularui/screen/viewport/ModularGuiContext;Lcom/cleanroommc/modularui/theme/WidgetThemeEntry;)V"),
|
||||
new MethodInfo("com.cleanroommc.modularui.drawable.text.RichText drawAtZeroPadded(Lcom/cleanroommc/modularui/screen/viewport/GuiContext;Lcom/cleanroommc/modularui/widget/sizer/Area;Lcom/cleanroommc/modularui/theme/WidgetTheme;)V")
|
||||
),
|
||||
// Hardcore Ender Expansion
|
||||
RecolorTarget.includeMethodCall(
|
||||
new MethodInfo("chylex.hee.gui.GuiTransportBeacon func_73863_a(IIF)V # drawScreen"),
|
||||
new MethodInfo("net.minecraft.client.gui.FontRenderer func_78276_b(Ljava/lang/String;III)I # drawString")
|
||||
),
|
||||
//endregion
|
||||
//region Exclude Methods
|
||||
// Applied Energistics 2
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.google.common.base.Objects;
|
||||
import com.gtnewhorizon.gtnhlib.client.model.loading.ModelRegistry;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
|
||||
import com.gtnewhorizons.angelica.client.font.DarkModeUtils;
|
||||
import com.gtnewhorizons.angelica.client.font.FontProviderBitmap;
|
||||
import com.gtnewhorizons.angelica.commands.AngelicaCommand;
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.common.BlockError;
|
||||
@@ -206,6 +207,7 @@ public final class ClientProxy extends CommonProxy {
|
||||
ItemRenderListManager.registerReloadListener();
|
||||
BlockRenderListManager.registerReloadListener();
|
||||
}
|
||||
FontProviderBitmap.registerReloadListener();
|
||||
|
||||
// Debug tooling
|
||||
if (SystemProperties.debugTooling() || Tracy.ENABLED) {
|
||||
|
||||
@@ -98,6 +98,10 @@ public class CloudRenderer implements IResourceManagerReloadListener {
|
||||
return GalaxySpaceClouds.supports(world, handler);
|
||||
}
|
||||
|
||||
public void invalidateWorld() {
|
||||
this.world = null;
|
||||
}
|
||||
|
||||
public void checkSettings() {
|
||||
if (world != mc.theWorld) {
|
||||
clearLayers();
|
||||
|
||||
+2
@@ -6,6 +6,7 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import com.gtnewhorizons.angelica.compat.bop.FogBiomeCache;
|
||||
import com.gtnewhorizons.angelica.render.CloudRenderer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
|
||||
@@ -175,6 +176,7 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
|
||||
GpuCulling.onWorldUnload();
|
||||
FfpExtendedAttribs.reset();
|
||||
FogBiomeCache.invalidate();
|
||||
CloudRenderer.getCloudRenderer().invalidateWorld();
|
||||
super.unloadWorld();
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
// Custom images and SSBOs
|
||||
private final Set<GlImage> customImages;
|
||||
private final VertexWriteReplays vertexWriteReplays = new VertexWriteReplays();
|
||||
private final GlImage[] imagesToClear;
|
||||
@Nullable
|
||||
private final ShaderStorageBufferHolder ssboHolder;
|
||||
@@ -1181,6 +1182,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
ProgramBuilder builder = beginBuilder(source.getName(), transformed);
|
||||
wireGbufferProgram(builder, availability, shadow);
|
||||
final Program program = builder.build();
|
||||
vertexWriteReplays.attach(source.getName(), program, transformed, (samplers, images) -> wireGbufferResources(samplers, images, availability, shadow));
|
||||
|
||||
final Pass pass = createPassInner(program, source.getDirectives(), shadow, id);
|
||||
this.customUniforms.mapholderToPass(builder, pass);
|
||||
@@ -1194,17 +1196,20 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
private void wireGbufferProgram(ProgramBuilder builder, InputAvailability availability, boolean shadow) {
|
||||
CommonUniforms.addDynamicUniforms(builder, FogMode.PER_VERTEX);
|
||||
this.customUniforms.assignTo(builder);
|
||||
wireGbufferResources(builder, builder, availability, shadow);
|
||||
}
|
||||
|
||||
private void wireGbufferResources(SamplerHolder samplers, ImageHolder images, InputAvailability availability, boolean shadow) {
|
||||
final Supplier<ImmutableSet<Integer>> flipped = shadow ? flippedShadowGbuffers : flippedGbuffers;
|
||||
|
||||
TextureStage textureStage = TextureStage.GBUFFERS_AND_SHADOW;
|
||||
|
||||
ProgramSamplers.CustomTextureSamplerInterceptor customTextureSamplerInterceptor =
|
||||
ProgramSamplers.customTextureSamplerInterceptor(builder,
|
||||
ProgramSamplers.customTextureSamplerInterceptor(samplers,
|
||||
customTextureManager.getCustomTextureIdMap(textureStage));
|
||||
|
||||
IrisSamplers.addRenderTargetSamplers(customTextureSamplerInterceptor, flipped, renderTargets, false, this);
|
||||
IrisImages.addRenderTargetImages(builder, flipped, renderTargets);
|
||||
IrisImages.addRenderTargetImages(images, flipped, renderTargets);
|
||||
|
||||
if (!shouldBindPBR) {
|
||||
shouldBindPBR = IrisSamplers.hasPBRSamplers(customTextureSamplerInterceptor);
|
||||
@@ -1220,7 +1225,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
IrisSamplers.addCustomImages(customTextureSamplerInterceptor, customImages);
|
||||
IrisSamplers.addCustomTextures(customTextureSamplerInterceptor, customTextureManager.getIrisCustomTextures());
|
||||
IrisImages.addCustomImages(builder, customImages);
|
||||
IrisImages.addCustomImages(images, customImages);
|
||||
|
||||
if (IrisSamplers.hasShadowSamplers(customTextureSamplerInterceptor)) {
|
||||
if (!shadow) {
|
||||
@@ -1229,7 +1234,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
|
||||
if (shadowRenderTargets != null) {
|
||||
recordSamplerUsage(IrisSamplers.addShadowSamplers(customTextureSamplerInterceptor, shadowRenderTargets, null, true));
|
||||
IrisImages.addShadowColorImages(builder, shadowRenderTargets, null);
|
||||
IrisImages.addShadowColorImages(images, shadowRenderTargets, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1527,6 +1532,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
final ProgramBuilder builder = beginBuilder(sourceName + kind.variantSuffix, transformed);
|
||||
wireGbufferProgram(builder, availability, shadow);
|
||||
final Program variant = builder.build();
|
||||
vertexWriteReplays.attach(sourceName + kind.variantSuffix, variant, transformed, (samplers, images) -> wireGbufferResources(samplers, images, availability, shadow));
|
||||
this.customUniforms.mapholderToPass(builder, variant);
|
||||
return variant;
|
||||
} catch (Exception e) {
|
||||
@@ -1606,6 +1612,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
}
|
||||
}
|
||||
}
|
||||
vertexWriteReplays.destroy();
|
||||
if (shadowVoxelizationCompute != null) {
|
||||
shadowVoxelizationCompute.destroy();
|
||||
shadowVoxelizationCompute = null;
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
package net.coderbot.iris.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VertexWriteReplaySetup;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.InternalTextureFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureType;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.gl.image.ImageHolder;
|
||||
import net.coderbot.iris.gl.program.ComputeProgram;
|
||||
import net.coderbot.iris.gl.program.Program;
|
||||
import net.coderbot.iris.gl.program.ProgramBuilder;
|
||||
import net.coderbot.iris.gl.sampler.GlSampler;
|
||||
import net.coderbot.iris.gl.sampler.SamplerHolder;
|
||||
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
|
||||
import net.coderbot.iris.pipeline.transform.PatchShaderType;
|
||||
import net.coderbot.iris.pipeline.transform.RwImageStoreExtractor;
|
||||
import net.coderbot.iris.pipeline.transform.RwImageStoreExtractor.VertexInput;
|
||||
import net.coderbot.iris.samplers.IrisSamplers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
/**
|
||||
* SDL-GPU only allows image writes from compute, so a gbuffers/shadow vertex shader that writes images gets a compute
|
||||
* twin that the backend runs over every draw made with the program.
|
||||
*/
|
||||
final class VertexWriteReplays {
|
||||
|
||||
private final List<ComputeProgram> twins = new ArrayList<>();
|
||||
private final IntArrayList attachedPrograms = new IntArrayList();
|
||||
|
||||
void attach(String name, Program program, Map<PatchShaderType, String> transformed, BiConsumer<SamplerHolder, ImageHolder> wireResources) {
|
||||
BackendManager.RENDER_BACKEND.setVertexWriteReplay(program.getProgramId(), null);
|
||||
final String source = transformed.get(PatchShaderType.COMPUTE);
|
||||
if (RwImageStoreExtractor.parseSentinel(source) != RwImageStoreExtractor.RwExtractMode.VERTEX_REPLAY) return;
|
||||
|
||||
final ComputeProgram twin;
|
||||
try {
|
||||
twin = ProgramBuilder.beginCompute(name + "_vertex_replay", source, IrisSamplers.WORLD_RESERVED_TEXTURE_UNITS).buildCompute();
|
||||
} catch (RuntimeException e) {
|
||||
Iris.logger.error("Vertex image write replay for {} failed to build; its image writes are dropped", name, e);
|
||||
return;
|
||||
}
|
||||
|
||||
final List<String> writtenImages = RwImageStoreExtractor.parseVertexReplayImages(source);
|
||||
final List<String> images = new ArrayList<>(writtenImages);
|
||||
images.addAll(RwImageStoreExtractor.parseVertexReplayReadImages(source));
|
||||
final List<String> samplers = new ArrayList<>();
|
||||
for (String sampler : RwImageStoreExtractor.parseVertexReplaySamplers(source)) {
|
||||
if (RenderSystem.getUniformLocation(twin.getProgramId(), sampler) != -1) samplers.add(sampler);
|
||||
}
|
||||
final ResourceCollector resources = new ResourceCollector(images, samplers);
|
||||
wireResources.accept(resources, resources);
|
||||
for (int i = 0; i < resources.images.length; i++) {
|
||||
if (resources.images[i] == null) {
|
||||
Iris.logger.warn("Vertex image writes of {} use image {}, which the pipeline does not provide; they are dropped", name, resources.imageNames.get(i));
|
||||
twin.destroy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final List<VertexInput> inputs = RwImageStoreExtractor.parseVertexReplayInputs(source);
|
||||
final String[] inputNames = new String[inputs.size()];
|
||||
final int[] inputLocations = new int[inputs.size()];
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
inputNames[i] = inputs.get(i).name();
|
||||
inputLocations[i] = inputs.get(i).location();
|
||||
}
|
||||
BackendManager.RENDER_BACKEND.setVertexWriteReplay(program.getProgramId(), new VertexWriteReplaySetup(twin.getProgramId(),
|
||||
inputNames, inputLocations, resources.images, writtenImages.size(),
|
||||
resources.samplerNames.toArray(new String[0]), resources.samplerUnits, resources.samplerTextures, resources.samplerObjects,
|
||||
RwImageStoreExtractor.VG_REPLAY_VERTEX_BUFFER_BINDING, RwImageStoreExtractor.VG_REPLAY_VERTEX_BUFFER_COUNT, RwImageStoreExtractor.VG_REPLAY_INDEX_BUFFER_BINDING));
|
||||
attachedPrograms.add(program.getProgramId());
|
||||
twins.add(twin);
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
for (int i = 0; i < attachedPrograms.size(); i++) BackendManager.RENDER_BACKEND.setVertexWriteReplay(attachedPrograms.getInt(i), null);
|
||||
attachedPrograms.clear();
|
||||
for (ComputeProgram twin : twins) twin.destroy();
|
||||
twins.clear();
|
||||
}
|
||||
|
||||
private static final class ResourceCollector implements SamplerHolder, ImageHolder {
|
||||
final List<String> imageNames;
|
||||
final IntSupplier[] images;
|
||||
final List<String> samplerNames;
|
||||
final int[] samplerUnits;
|
||||
final IntSupplier[] samplerTextures;
|
||||
final int[] samplerObjects;
|
||||
|
||||
ResourceCollector(List<String> imageNames, List<String> samplerNames) {
|
||||
this.imageNames = imageNames;
|
||||
this.images = new IntSupplier[imageNames.size()];
|
||||
this.samplerNames = samplerNames;
|
||||
this.samplerUnits = new int[samplerNames.size()];
|
||||
Arrays.fill(samplerUnits, -1);
|
||||
this.samplerTextures = new IntSupplier[samplerNames.size()];
|
||||
this.samplerObjects = new int[samplerNames.size()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasImage(String name) {
|
||||
return imageNames.contains(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTextureImage(IntSupplier textureID, InternalTextureFormat internalFormat, String name) {
|
||||
final int index = imageNames.indexOf(name);
|
||||
if (index >= 0 && images[index] == null) images[index] = textureID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSampler(String name) {
|
||||
return samplerNames.contains(name) || imageNames.contains(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addExternalSampler(int textureUnit, String... names) {
|
||||
for (String name : names) {
|
||||
final int index = samplerNames.indexOf(name);
|
||||
if (index >= 0 && unassigned(index)) samplerUnits[index] = textureUnit;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
|
||||
return addDynamicSampler(type, texture, notifier, sampler, names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addDynamicSampler(TextureType type, IntSupplier texture, GlSampler sampler, String... names) {
|
||||
return addDynamicSampler(type, texture, null, sampler, names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
|
||||
boolean used = false;
|
||||
for (String name : names) {
|
||||
final int index = samplerNames.indexOf(name);
|
||||
if (index < 0) continue;
|
||||
used = true;
|
||||
if (unassigned(index)) {
|
||||
samplerTextures[index] = texture;
|
||||
samplerObjects[index] = sampler != null ? sampler.getId() : 0;
|
||||
}
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
private boolean unassigned(int index) {
|
||||
return samplerUnits[index] < 0 && samplerTextures[index] == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package net.coderbot.iris.pipeline.transform;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import net.coderbot.iris.gl.program.ComputeProgram;
|
||||
import net.coderbot.iris.postprocess.FullScreenQuadRenderer;
|
||||
import net.coderbot.iris.uniforms.custom.CustomUniforms;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
@@ -16,6 +17,7 @@ public final class PreRasterComputeDispatcher {
|
||||
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
compute.use();
|
||||
uniforms.push(compute);
|
||||
if (mode == RwImageStoreExtractor.RwExtractMode.COMPOSITE_VSH) FullScreenQuadRenderer.uploadCompositeMatrices();
|
||||
int targetSizeLoc = cachedTargetSizeLoc;
|
||||
if (mode == RwImageStoreExtractor.RwExtractMode.COMPOSITE_FSH) {
|
||||
if (targetSizeLoc == LOC_UNRESOLVED) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import net.coderbot.iris.gl.image.ImageInformation;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.InternalTextureFormat;
|
||||
@@ -7,6 +8,7 @@ import org.antlr.v4.runtime.CommonToken;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.taumc.glsl.ShaderParser;
|
||||
import org.taumc.glsl.Transformer;
|
||||
import org.taumc.glsl.grammar.GLSLLexer;
|
||||
@@ -22,26 +24,30 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public final class RwImageStoreExtractor {
|
||||
|
||||
public enum RwExtractMode { CHUNK, COMPOSITE_VSH, COMPOSITE_FSH }
|
||||
public enum RwExtractMode { CHUNK, COMPOSITE_VSH, COMPOSITE_FSH, VERTEX_REPLAY }
|
||||
|
||||
public static RwExtractMode parseSentinel(String computeSource) {
|
||||
if (computeSource == null) return null;
|
||||
if (computeSource.startsWith("// _vg_mode: chunk")) return RwExtractMode.CHUNK;
|
||||
if (computeSource.startsWith("// _vg_mode: composite-vsh")) return RwExtractMode.COMPOSITE_VSH;
|
||||
if (computeSource.startsWith("// _vg_mode: composite-fsh")) return RwExtractMode.COMPOSITE_FSH;
|
||||
if (computeSource.startsWith("// _vg_mode: vertex-replay")) return RwExtractMode.VERTEX_REPLAY;
|
||||
return null;
|
||||
}
|
||||
|
||||
public record VertexInput(String name, String glslType, int location) {}
|
||||
|
||||
public record AttributeSlot(String name, String glslType, int byteOffset, String unpackExpr, boolean liveBranch) {
|
||||
public AttributeSlot(String name, String glslType, int byteOffset, String unpackExpr) {
|
||||
this(name, glslType, byteOffset, unpackExpr, true);
|
||||
}
|
||||
}
|
||||
|
||||
public record Result(String strippedSource, String computeSource, Set<String> writtenImages, RwExtractMode mode) {}
|
||||
public record Result(String strippedSource, String computeSource, Set<String> writtenImages, RwExtractMode mode, Set<String> atomicImages) {}
|
||||
|
||||
public record ImageDecl(String name, String glslType, String layoutFormat) {}
|
||||
|
||||
@@ -53,6 +59,9 @@ public final class RwImageStoreExtractor {
|
||||
|
||||
public static final int VG_VBUF_SSBO_BINDING = 9;
|
||||
public static final int VG_RANGES_SSBO_BINDING = 10;
|
||||
public static final int VG_REPLAY_VERTEX_BUFFER_BINDING = 9;
|
||||
public static final int VG_REPLAY_VERTEX_BUFFER_COUNT = 4;
|
||||
public static final int VG_REPLAY_INDEX_BUFFER_BINDING = VG_REPLAY_VERTEX_BUFFER_BINDING + VG_REPLAY_VERTEX_BUFFER_COUNT;
|
||||
private static final int STRIDE_BYTES = 48;
|
||||
private static final int STRIDE_UINTS = STRIDE_BYTES / 4;
|
||||
|
||||
@@ -62,8 +71,30 @@ public final class RwImageStoreExtractor {
|
||||
return activeCustomImages;
|
||||
}
|
||||
|
||||
private static volatile Set<String> atomicCustomImages = Set.of();
|
||||
|
||||
public static void setActiveCustomImages(Map<String, ImageInformation> images) {
|
||||
activeCustomImages = (images == null) ? Map.of() : Map.copyOf(images);
|
||||
final TreeSet<String> atomic = new TreeSet<>();
|
||||
if (!activeCustomImages.isEmpty() && !BackendManager.RENDER_BACKEND.supportsComputeImageAtomics()) {
|
||||
for (ImageInformation image : activeCustomImages.values()) {
|
||||
if (image.isRelative()) continue;
|
||||
if (BackendManager.RENDER_BACKEND.supportsComputeImageAtomics(image.width(), image.height(), image.depth())) atomic.add(image.name());
|
||||
}
|
||||
}
|
||||
if (!atomic.equals(atomicCustomImages)) {
|
||||
atomicCustomImages = atomic;
|
||||
TransformPatcher.clearCache();
|
||||
ShaderTransformer.clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean supportsAtomics(String image) {
|
||||
return atomicCustomImages.contains(image) || BackendManager.RENDER_BACKEND.supportsComputeImageAtomics();
|
||||
}
|
||||
|
||||
static String atomicCustomImagesKey() {
|
||||
return String.join(",", atomicCustomImages);
|
||||
}
|
||||
|
||||
private static final Set<String> RW_CALLS = Set.of(
|
||||
@@ -73,7 +104,11 @@ public final class RwImageStoreExtractor {
|
||||
);
|
||||
|
||||
public static Result tryExtract(String source, PatchShaderType stage, String programName) {
|
||||
if (source == null) return null;
|
||||
return tryExtract(source, stage, programName, null);
|
||||
}
|
||||
|
||||
public static Result tryExtract(String source, PatchShaderType stage, String programName, RwExtractMode vertexMode) {
|
||||
if (source == null || !mayWriteImages(source)) return null;
|
||||
if (stage != PatchShaderType.VERTEX && stage != PatchShaderType.FRAGMENT) return null;
|
||||
|
||||
final ShaderParser.ParsedShader parsed = ShaderParser.parseShader(source);
|
||||
@@ -94,17 +129,188 @@ public final class RwImageStoreExtractor {
|
||||
final Map<String, String> chunkAttrAstTypes = (stage == PatchShaderType.VERTEX) ? collectChunkAttrAstTypes(decls) : Map.of();
|
||||
final Set<String> chunkAttrsUsed = (stage == PatchShaderType.VERTEX) ? collectChunkAttrsUsed(decls, root, source) : Set.of();
|
||||
|
||||
final RwExtractMode mode = (stage == PatchShaderType.VERTEX) ? (chunkAttrsUsed.isEmpty() ? RwExtractMode.COMPOSITE_VSH : RwExtractMode.CHUNK) : RwExtractMode.COMPOSITE_FSH;
|
||||
final RwExtractMode mode = (stage == PatchShaderType.VERTEX)
|
||||
? (vertexMode != null ? vertexMode : chunkAttrsUsed.isEmpty() ? RwExtractMode.COMPOSITE_VSH : RwExtractMode.CHUNK)
|
||||
: RwExtractMode.COMPOSITE_FSH;
|
||||
|
||||
final List<AttributeSlot> attrs = (mode == RwExtractMode.CHUNK) ? collectVertexAttributes(chunkAttrsUsed, chunkAttrAstTypes) : List.of();
|
||||
final Set<String> quadInputs = (mode == RwExtractMode.COMPOSITE_VSH) ? declaredQuadInputs(decls) : Set.of();
|
||||
final boolean definesFtransform = definesFunction(root, "iris_ftransform");
|
||||
|
||||
final String stripped = buildRasterOutputViaSourceSlicing(source, decls, exprs, writtenImages, declared, mode);
|
||||
|
||||
final Transformer tc = new Transformer(root);
|
||||
applyComputeMutations(tc, writtenImages, declared, chunkAttrsUsed, mode, nonWriteonlyImages);
|
||||
final String compute = buildComputeOutput(root, mode, writtenImages, nonWriteonlyImages, declared, attrs);
|
||||
applyComputeMutations(tc, writtenImages, declared, mode == RwExtractMode.COMPOSITE_VSH ? quadInputs : chunkAttrsUsed, mode, nonWriteonlyImages);
|
||||
final String compute = buildComputeOutput(root, mode, writtenImages, nonWriteonlyImages, declared, attrs, quadInputs, definesFtransform, null);
|
||||
|
||||
return new Result(stripped, compute, writtenImages, mode);
|
||||
return new Result(stripped, compute, writtenImages, mode, atomicImages(root, writtenImages));
|
||||
}
|
||||
|
||||
private static boolean mayWriteImages(String source) {
|
||||
return source.contains("imageStore") || source.contains("imageAtomic");
|
||||
}
|
||||
|
||||
public static Result tryExtractVertexReplay(String source) {
|
||||
if (source == null || !mayWriteImages(source)) return null;
|
||||
|
||||
final ShaderParser.ParsedShader parsed = ShaderParser.parseShader(source);
|
||||
final GLSLParser.Translation_unitContext root = parsed.full();
|
||||
|
||||
final List<GLSLParser.Single_declarationContext> decls = new ArrayList<>();
|
||||
final List<GLSLParser.Postfix_expressionContext> exprs = new ArrayList<>();
|
||||
GlslAstHelpers.collectInto(root,
|
||||
new List[] { decls, exprs },
|
||||
new Class<?>[] { GLSLParser.Single_declarationContext.class, GLSLParser.Postfix_expressionContext.class });
|
||||
|
||||
final Map<String, ImageDecl> declared = collectImageDecls(decls);
|
||||
final Set<String> writtenImages = new LinkedHashSet<>();
|
||||
final Set<String> nonWriteonlyImages = new LinkedHashSet<>();
|
||||
classifyImageCalls(exprs, declared, writtenImages, nonWriteonlyImages);
|
||||
if (writtenImages.isEmpty()) return null;
|
||||
|
||||
final String stripped = buildRasterOutputViaSourceSlicing(source, decls, exprs, writtenImages, declared, RwExtractMode.VERTEX_REPLAY);
|
||||
final List<VertexInput> inputs = collectVertexInputs(decls);
|
||||
if (inputs == null) return new Result(stripped, null, writtenImages, RwExtractMode.VERTEX_REPLAY, Set.of());
|
||||
|
||||
final List<String> readImages = readOnlyImages(exprs, declared, writtenImages);
|
||||
final Set<String> inputNames = new LinkedHashSet<>();
|
||||
for (VertexInput input : inputs) inputNames.add(input.name());
|
||||
final Transformer tc = new Transformer(root);
|
||||
applyComputeMutations(tc, writtenImages, declared, inputNames, RwExtractMode.VERTEX_REPLAY, nonWriteonlyImages);
|
||||
for (String name : readImages) tc.removeVariable(name);
|
||||
final ReplayLayout layout = new ReplayLayout(inputs, readImages, replayHeader(inputs, writtenImages, readImages, collectSamplerNames(decls)));
|
||||
final String compute = buildComputeOutput(root, RwExtractMode.VERTEX_REPLAY, writtenImages, nonWriteonlyImages, declared, List.of(), Set.of(), false, layout);
|
||||
|
||||
return new Result(stripped, compute, writtenImages, RwExtractMode.VERTEX_REPLAY, atomicImages(root, writtenImages));
|
||||
}
|
||||
|
||||
private record ReplayLayout(List<VertexInput> inputs, List<String> readImages, String header) {}
|
||||
|
||||
private static final String REPLAY_INPUTS_HEADER = "// _vg_inputs:";
|
||||
private static final String REPLAY_IMAGES_HEADER = "// _vg_images:";
|
||||
private static final String REPLAY_READ_IMAGES_HEADER = "// _vg_read_images:";
|
||||
private static final String REPLAY_SAMPLERS_HEADER = "// _vg_samplers:";
|
||||
|
||||
private static String replayHeader(List<VertexInput> inputs, Set<String> writtenImages, List<String> readImages, List<String> samplers) {
|
||||
final StringBuilder out = new StringBuilder(REPLAY_INPUTS_HEADER);
|
||||
for (VertexInput input : inputs) out.append(' ').append(input.name()).append(':').append(input.glslType()).append(':').append(input.location());
|
||||
out.append('\n').append(REPLAY_IMAGES_HEADER);
|
||||
for (String image : sortedImages(writtenImages)) out.append(' ').append(image);
|
||||
out.append('\n').append(REPLAY_READ_IMAGES_HEADER);
|
||||
for (String image : readImages) out.append(' ').append(image);
|
||||
out.append('\n').append(REPLAY_SAMPLERS_HEADER);
|
||||
for (String sampler : samplers) out.append(' ').append(sampler);
|
||||
return out.append('\n').toString();
|
||||
}
|
||||
|
||||
/** Images the shader reads but never writes, sorted; the replay binds them after the written ones. */
|
||||
private static List<String> readOnlyImages(List<GLSLParser.Postfix_expressionContext> exprs, Map<String, ImageDecl> declared, Set<String> writtenImages) {
|
||||
final Set<String> read = new LinkedHashSet<>();
|
||||
for (var expr : exprs) {
|
||||
if (GlslAstHelpers.extractCallName(expr) == null) continue;
|
||||
final String firstArg = GlslAstHelpers.firstArgIdentifier(expr);
|
||||
if (declared.containsKey(firstArg) && !writtenImages.contains(firstArg)) read.add(firstArg);
|
||||
}
|
||||
final List<String> sorted = new ArrayList<>(read);
|
||||
Collections.sort(sorted);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/** The images a vertex-replay compute source only reads, in the order of their bindings after the written ones. */
|
||||
public static List<String> parseVertexReplayReadImages(String computeSource) {
|
||||
return headerTokens(computeSource, REPLAY_READ_IMAGES_HEADER);
|
||||
}
|
||||
|
||||
/** The samplers a vertex-replay compute source declares. */
|
||||
public static List<String> parseVertexReplaySamplers(String computeSource) {
|
||||
return headerTokens(computeSource, REPLAY_SAMPLERS_HEADER);
|
||||
}
|
||||
|
||||
/** The inputs of a vertex-replay compute source, in the order of its {@code _vg_attr} slots. */
|
||||
public static List<VertexInput> parseVertexReplayInputs(String computeSource) {
|
||||
final List<VertexInput> out = new ArrayList<>();
|
||||
for (String token : headerTokens(computeSource, REPLAY_INPUTS_HEADER)) {
|
||||
final String[] parts = token.split(":");
|
||||
if (parts.length != 3) continue;
|
||||
out.add(new VertexInput(parts[0], parts[1], Integer.parseInt(parts[2])));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** The images a vertex-replay compute source writes, in the order of their bindings. */
|
||||
public static List<String> parseVertexReplayImages(String computeSource) {
|
||||
return headerTokens(computeSource, REPLAY_IMAGES_HEADER);
|
||||
}
|
||||
|
||||
private static List<String> headerTokens(String computeSource, String header) {
|
||||
if (computeSource == null) return List.of();
|
||||
final int start = computeSource.indexOf(header);
|
||||
if (start < 0) return List.of();
|
||||
final int end = computeSource.indexOf('\n', start);
|
||||
final String line = computeSource.substring(start + header.length(), end < 0 ? computeSource.length() : end).trim();
|
||||
if (line.isEmpty()) return List.of();
|
||||
return List.of(line.split(" "));
|
||||
}
|
||||
|
||||
private static final Set<String> REPLAYABLE_INPUT_TYPES = Set.of(
|
||||
"float", "vec2", "vec3", "vec4", "int", "ivec2", "ivec3", "ivec4", "uint", "uvec2", "uvec3", "uvec4");
|
||||
|
||||
/** Global {@code in}/{@code attribute} declarations, or null if one of them cannot be fetched per component. */
|
||||
private static List<VertexInput> collectVertexInputs(List<GLSLParser.Single_declarationContext> decls) {
|
||||
final List<VertexInput> out = new ArrayList<>();
|
||||
for (var decl : decls) {
|
||||
final var fst = decl.fully_specified_type();
|
||||
if (fst == null || fst.type_qualifier() == null) continue;
|
||||
if (GlslAstHelpers.enclosingOfType(decl, GLSLParser.Function_definitionContext.class) != null) continue;
|
||||
boolean isInput = false;
|
||||
int location = -1;
|
||||
for (var sq : fst.type_qualifier().single_type_qualifier()) {
|
||||
if (sq.storage_qualifier() != null) {
|
||||
final String storage = sq.storage_qualifier().getText();
|
||||
if (storage.equals("in") || storage.equals("attribute")) isInput = true;
|
||||
}
|
||||
final var lq = sq.layout_qualifier();
|
||||
if (lq == null || lq.layout_qualifier_id_list() == null) continue;
|
||||
for (var id : lq.layout_qualifier_id_list().layout_qualifier_id()) {
|
||||
if (id.IDENTIFIER() == null || !"location".equals(id.IDENTIFIER().getText())) continue;
|
||||
try {
|
||||
location = Integer.parseInt(id.getText().substring(id.getText().indexOf('=') + 1).trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isInput) continue;
|
||||
final String type = fst.type_specifier().getText();
|
||||
if (!REPLAYABLE_INPUT_TYPES.contains(type)) return null;
|
||||
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
|
||||
declarators.add(decl.typeless_declaration());
|
||||
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) declarators.addAll(list.typeless_declaration());
|
||||
for (var typeless : declarators) {
|
||||
if (typeless == null || typeless.IDENTIFIER() == null || typeless.array_specifier() != null) return null;
|
||||
out.add(new VertexInput(typeless.IDENTIFIER().getText(), type, location));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Global sampler uniforms, which the replay may read even after the graphics program stops reading them. */
|
||||
private static List<String> collectSamplerNames(List<GLSLParser.Single_declarationContext> decls) {
|
||||
final List<String> out = new ArrayList<>();
|
||||
for (var decl : decls) {
|
||||
final var fst = decl.fully_specified_type();
|
||||
if (fst == null || fst.type_specifier() == null) continue;
|
||||
if (GlslAstHelpers.enclosingOfType(decl, GLSLParser.Function_definitionContext.class) != null) continue;
|
||||
final String type = fst.type_specifier().getText();
|
||||
if (!type.startsWith("sampler") && !type.startsWith("isampler") && !type.startsWith("usampler")) continue;
|
||||
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
|
||||
declarators.add(decl.typeless_declaration());
|
||||
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) declarators.addAll(list.typeless_declaration());
|
||||
for (var typeless : declarators) {
|
||||
if (typeless != null && typeless.IDENTIFIER() != null) out.add(typeless.IDENTIFIER().getText());
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Map<String, ImageDecl> collectImageDecls(List<GLSLParser.Single_declarationContext> decls) {
|
||||
@@ -270,13 +476,22 @@ public final class RwImageStoreExtractor {
|
||||
private static void applyComputeMutations(Transformer tc, Set<String> writtenImages, Map<String, ImageDecl> declared, Set<String> chunkAttrsUsed, RwExtractMode mode, Set<String> nonWriteonlyImages) {
|
||||
hoistNonConstGlobalInitializers(tc);
|
||||
renameMainToVgBody(tc);
|
||||
rewriteImageAtomicsToNonAtomic(tc, declared, writtenImages, nonWriteonlyImages);
|
||||
if (mode != RwExtractMode.VERTEX_REPLAY) rewriteImageAtomicsToNonAtomic(tc, declared, writtenImages, nonWriteonlyImages);
|
||||
for (String name : writtenImages) tc.removeVariable(name);
|
||||
if (mode == RwExtractMode.CHUNK) {
|
||||
if (mode == RwExtractMode.CHUNK || mode == RwExtractMode.VERTEX_REPLAY) {
|
||||
for (String name : chunkAttrsUsed) tc.removeVariable(name);
|
||||
convertInterfaceBlocks(tc);
|
||||
if (mode == RwExtractMode.VERTEX_REPLAY) {
|
||||
stripInterfaceLayoutQualifiers(tc);
|
||||
removeQualifierOnlyDeclarations(tc);
|
||||
}
|
||||
stripStorageQualifiers(tc, Set.of("out", "varying"));
|
||||
} else if (mode == RwExtractMode.COMPOSITE_VSH) {
|
||||
for (String name : chunkAttrsUsed) tc.removeVariable(name);
|
||||
tc.renameFunctionCall("ftransform", "iris_ftransform");
|
||||
convertInterfaceBlocks(tc);
|
||||
stripInterfaceLayoutQualifiers(tc);
|
||||
removeQualifierOnlyDeclarations(tc);
|
||||
stripStorageQualifiers(tc, Set.of("in", "attribute", "out", "varying"));
|
||||
} else if (mode == RwExtractMode.COMPOSITE_FSH) {
|
||||
stripFragOutputs(tc);
|
||||
@@ -311,6 +526,17 @@ public final class RwImageStoreExtractor {
|
||||
});
|
||||
}
|
||||
|
||||
private static Set<String> atomicImages(GLSLParser.Translation_unitContext computeRoot, Set<String> writtenImages) {
|
||||
final Set<String> out = new LinkedHashSet<>();
|
||||
for (var expr : GlslAstHelpers.collectAll(computeRoot, GLSLParser.Postfix_expressionContext.class)) {
|
||||
final String fname = GlslAstHelpers.extractCallName(expr);
|
||||
if (fname == null || !ATOMIC_CALLS.contains(fname)) continue;
|
||||
final String image = GlslAstHelpers.firstArgIdentifier(expr);
|
||||
if (writtenImages.contains(image)) out.add(image);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static boolean isCallTopLevelStatement(GLSLParser.Postfix_expressionContext expr) {
|
||||
final GLSLParser.StatementContext stmt = GlslAstHelpers.enclosingOfType(expr, GLSLParser.StatementContext.class);
|
||||
if (stmt == null) return false;
|
||||
@@ -409,6 +635,7 @@ public final class RwImageStoreExtractor {
|
||||
t.mutateTree(tree -> {
|
||||
for (var sq : GlslAstHelpers.collectAll(tree, GLSLParser.Storage_qualifierContext.class)) {
|
||||
if (!kill.contains(sq.getText())) continue;
|
||||
if (GlslAstHelpers.enclosingOfType(sq, GLSLParser.Parameter_declarationContext.class) != null) continue;
|
||||
blankLeadingToken(sq);
|
||||
}
|
||||
});
|
||||
@@ -428,6 +655,91 @@ public final class RwImageStoreExtractor {
|
||||
|
||||
private static final Set<String> AUXILIARY_STORAGE_QUALIFIERS = Set.of("centroid", "sample", "patch");
|
||||
|
||||
private static final Set<String> INTERFACE_STORAGE_QUALIFIERS = Set.of("in", "out", "attribute", "varying");
|
||||
|
||||
private static void removeQualifierOnlyDeclarations(Transformer t) {
|
||||
t.mutateTree(tree -> {
|
||||
if (tree.children == null) return;
|
||||
tree.children.removeIf(child -> child instanceof GLSLParser.External_declarationContext ed
|
||||
&& ed.declaration() != null && isQualifierOnly(ed.declaration()));
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isQualifierOnly(GLSLParser.DeclarationContext decl) {
|
||||
if (decl.LEFT_BRACE() != null) return false;
|
||||
if (decl.init_declarator_list() == null) return decl.type_qualifier() != null && qualifiesStageInterface(decl.type_qualifier());
|
||||
final var single = decl.init_declarator_list().single_declaration();
|
||||
return single != null && single.typeless_declaration() == null && decl.init_declarator_list().COMMA().isEmpty()
|
||||
&& single.fully_specified_type() != null && single.fully_specified_type().type_qualifier() != null
|
||||
&& qualifiesStageInterface(single.fully_specified_type().type_qualifier());
|
||||
}
|
||||
|
||||
private static boolean qualifiesStageInterface(GLSLParser.Type_qualifierContext tq) {
|
||||
for (var sq : tq.single_type_qualifier()) {
|
||||
if (sq.invariant_qualifier() != null || sq.precise_qualifier() != null) return true;
|
||||
if (sq.storage_qualifier() != null && INTERFACE_STORAGE_QUALIFIERS.contains(sq.storage_qualifier().getText())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void convertInterfaceBlocks(Transformer t) {
|
||||
t.mutateTree(tree -> {
|
||||
if (tree.children == null) return;
|
||||
tree.children.removeIf(child -> child instanceof GLSLParser.External_declarationContext ed
|
||||
&& isInterfaceBlock(ed.declaration()) && "gl_PerVertex".equals(ed.declaration().IDENTIFIER(0).getText()));
|
||||
for (var child : tree.children) {
|
||||
if (!(child instanceof GLSLParser.External_declarationContext ed) || !isInterfaceBlock(ed.declaration())) continue;
|
||||
final GLSLParser.DeclarationContext decl = ed.declaration();
|
||||
final String blockName = decl.IDENTIFIER(0).getText();
|
||||
blankAllTerminals(decl.type_qualifier());
|
||||
if (decl.IDENTIFIER().size() > 1) {
|
||||
setFirstTerminal(decl.type_qualifier(), "struct");
|
||||
setText(decl.RIGHT_BRACE(), "}; " + blockName);
|
||||
} else {
|
||||
setText(decl.IDENTIFIER(0), "");
|
||||
setText(decl.LEFT_BRACE(), "");
|
||||
setText(decl.RIGHT_BRACE(), "");
|
||||
setText(decl.SEMICOLON(), "");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean isInterfaceBlock(GLSLParser.DeclarationContext decl) {
|
||||
return decl != null && decl.LEFT_BRACE() != null && decl.type_qualifier() != null && !decl.IDENTIFIER().isEmpty()
|
||||
&& qualifiesStageInterface(decl.type_qualifier());
|
||||
}
|
||||
|
||||
private static void setText(TerminalNode node, String text) {
|
||||
if (node != null && node.getSymbol() instanceof CommonToken ct) ct.setText(text);
|
||||
}
|
||||
|
||||
private static void setFirstTerminal(ParseTree node, String text) {
|
||||
if (node instanceof TerminalNode tn) {
|
||||
setText(tn, text);
|
||||
return;
|
||||
}
|
||||
if (node.getChildCount() > 0) setFirstTerminal(node.getChild(0), text);
|
||||
}
|
||||
|
||||
private static void stripInterfaceLayoutQualifiers(Transformer t) {
|
||||
t.mutateTree(tree -> {
|
||||
for (var tq : GlslAstHelpers.collectAll(tree, GLSLParser.Type_qualifierContext.class)) {
|
||||
boolean isInterface = false;
|
||||
for (var sq : tq.single_type_qualifier()) {
|
||||
if (sq.storage_qualifier() != null && INTERFACE_STORAGE_QUALIFIERS.contains(sq.storage_qualifier().getText())) {
|
||||
isInterface = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isInterface) continue;
|
||||
for (var sq : tq.single_type_qualifier()) {
|
||||
if (sq.layout_qualifier() != null) blankAllTerminals(sq.layout_qualifier());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void blankLeadingToken(ParserRuleContext ctx) {
|
||||
if (ctx.getChildCount() == 0) return;
|
||||
if (!(ctx.getChild(0) instanceof TerminalNode tn)) return;
|
||||
@@ -537,19 +849,42 @@ public final class RwImageStoreExtractor {
|
||||
});
|
||||
}
|
||||
|
||||
private static String buildComputeOutput(GLSLParser.Translation_unitContext computeRoot, RwExtractMode mode, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared, List<AttributeSlot> attrs) {
|
||||
private static final Set<String> QUAD_INPUTS = Set.of("iris_Vertex", "iris_MultiTexCoord0");
|
||||
|
||||
private static Set<String> declaredQuadInputs(List<GLSLParser.Single_declarationContext> decls) {
|
||||
final Set<String> out = new LinkedHashSet<>();
|
||||
for (var decl : decls) {
|
||||
if (decl.typeless_declaration() == null || decl.typeless_declaration().IDENTIFIER() == null) continue;
|
||||
if (GlslAstHelpers.enclosingOfType(decl, GLSLParser.Function_definitionContext.class) != null) continue;
|
||||
final String name = decl.typeless_declaration().IDENTIFIER().getText();
|
||||
if (QUAD_INPUTS.contains(name)) out.add(name);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static boolean definesFunction(GLSLParser.Translation_unitContext root, String name) {
|
||||
for (var fdef : GlslAstHelpers.collectAll(root, GLSLParser.Function_definitionContext.class)) {
|
||||
final var id = fdef.function_prototype().IDENTIFIER();
|
||||
if (id != null && name.equals(id.getText())) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String buildComputeOutput(GLSLParser.Translation_unitContext computeRoot, RwExtractMode mode, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared, List<AttributeSlot> attrs, Set<String> quadInputs, boolean definesFtransform, ReplayLayout replay) {
|
||||
final StringBuilder out = new StringBuilder(8192);
|
||||
out.append("// _vg_mode: ").append(modeTag(mode)).append('\n');
|
||||
if (replay != null) out.append(replay.header());
|
||||
out.append("#version 460 core\n");
|
||||
switch (mode) {
|
||||
case CHUNK -> emitChunkPrelude(out, writtenImages, nonWriteonlyImages, declared, attrs);
|
||||
case COMPOSITE_VSH -> emitCompositeVshPrelude(out, writtenImages, nonWriteonlyImages, declared);
|
||||
case COMPOSITE_VSH -> emitCompositeVshPrelude(out, writtenImages, nonWriteonlyImages, declared, quadInputs, definesFtransform);
|
||||
case COMPOSITE_FSH -> emitCompositeFshPrelude(out, writtenImages, nonWriteonlyImages, declared);
|
||||
case VERTEX_REPLAY -> emitVertexReplayPrelude(out, writtenImages, nonWriteonlyImages, declared, replay);
|
||||
}
|
||||
out.append('\n');
|
||||
out.append(GlslTransformUtils.getFormattedShader(computeRoot, ""));
|
||||
out.append('\n');
|
||||
emitDispatchMain(out, mode, attrs);
|
||||
emitDispatchMain(out, mode, attrs, replay != null ? replay.inputs() : List.of(), quadInputs);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
@@ -558,19 +893,26 @@ public final class RwImageStoreExtractor {
|
||||
case CHUNK -> "chunk";
|
||||
case COMPOSITE_VSH -> "composite-vsh";
|
||||
case COMPOSITE_FSH -> "composite-fsh";
|
||||
case VERTEX_REPLAY -> "vertex-replay";
|
||||
};
|
||||
}
|
||||
|
||||
private static void emitImageDecls(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared) {
|
||||
private static List<String> sortedImages(Set<String> writtenImages) {
|
||||
final List<String> sorted = new ArrayList<>(writtenImages);
|
||||
Collections.sort(sorted);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private static void emitImageDecls(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared) {
|
||||
final List<String> sorted = sortedImages(writtenImages);
|
||||
final Map<String, ImageInformation> infos = activeCustomImages;
|
||||
for (int i = 0; i < sorted.size(); i++) {
|
||||
final String name = sorted.get(i);
|
||||
final ImageDecl d = declared.get(name);
|
||||
if (d == null) continue;
|
||||
final InternalTextureFormat fmt = (infos != null && infos.get(name) != null) ? infos.get(name).internalTextureFormat() : null;
|
||||
final String layoutInner = (fmt != null) ? "binding = " + i + ", " + fmt.name().toLowerCase(Locale.ROOT) : "binding = " + i;
|
||||
final String format = fmt != null ? fmt.name().toLowerCase(Locale.ROOT) : d.layoutFormat();
|
||||
final String layoutInner = (format != null) ? "binding = " + i + ", " + format : "binding = " + i;
|
||||
final String qualifier = nonWriteonlyImages.contains(name) ? "" : "writeonly ";
|
||||
out.append("layout(").append(layoutInner).append(") ").append(qualifier).append("uniform ").append(d.glslType()).append(' ').append(name).append(";\n");
|
||||
}
|
||||
@@ -604,7 +946,7 @@ public final class RwImageStoreExtractor {
|
||||
out.append("void _vert_init() {}\n");
|
||||
}
|
||||
|
||||
private static void emitCompositeVshPrelude(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared) {
|
||||
private static void emitCompositeVshPrelude(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared, Set<String> quadInputs, boolean definesFtransform) {
|
||||
out.append("layout(local_size_x = 64) in;\n\n");
|
||||
emitImageDecls(out, writtenImages, nonWriteonlyImages, declared);
|
||||
out.append("\nconst vec4 _vg_quad[4] = vec4[4](\n");
|
||||
@@ -614,10 +956,67 @@ public final class RwImageStoreExtractor {
|
||||
out.append(" vec4(0.0, 0.0, 0.0, 1.0));\n");
|
||||
out.append("vec4 _vg_gl_vertex;\n");
|
||||
out.append("int _vg_gl_vertex_id;\n");
|
||||
out.append("vec4 _vg_sink_pos;\n");
|
||||
out.append("float _vg_sink_psize;\n");
|
||||
out.append("float _vg_sink_clip[8];\n");
|
||||
out.append("#define gl_Vertex _vg_gl_vertex\n");
|
||||
out.append("#define gl_VertexID _vg_gl_vertex_id\n");
|
||||
out.append("uniform mat4 gl_ModelViewProjectionMatrix;\n");
|
||||
out.append("vec4 iris_ftransform() { return gl_ModelViewProjectionMatrix * gl_Vertex; }\n");
|
||||
out.append("#define gl_Position _vg_sink_pos\n");
|
||||
out.append("#define gl_PointSize _vg_sink_psize\n");
|
||||
out.append("#define gl_ClipDistance _vg_sink_clip\n");
|
||||
for (String input : quadInputs) out.append("vec4 ").append(input).append(";\n");
|
||||
if (!definesFtransform) {
|
||||
out.append("uniform mat4 gl_ModelViewProjectionMatrix;\n");
|
||||
out.append("vec4 iris_ftransform() { return gl_ModelViewProjectionMatrix * gl_Vertex; }\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitVertexReplayPrelude(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared, ReplayLayout replay) {
|
||||
final List<VertexInput> inputs = replay.inputs();
|
||||
out.append("layout(local_size_x = 64) in;\n\n");
|
||||
emitImageDecls(out, writtenImages, nonWriteonlyImages, declared);
|
||||
final Map<String, ImageInformation> infos = activeCustomImages;
|
||||
for (int i = 0; i < replay.readImages().size(); i++) {
|
||||
final ImageDecl d = declared.get(replay.readImages().get(i));
|
||||
final InternalTextureFormat fmt = infos.get(d.name()) != null ? infos.get(d.name()).internalTextureFormat() : null;
|
||||
final String format = fmt != null ? fmt.name().toLowerCase(Locale.ROOT) : d.layoutFormat();
|
||||
out.append("layout(binding = ").append(writtenImages.size() + i).append(format != null ? ", " + format : "").append(") readonly uniform ")
|
||||
.append(d.glslType()).append(' ').append(d.name()).append(";\n");
|
||||
}
|
||||
out.append('\n');
|
||||
for (int i = 0; i < VG_REPLAY_VERTEX_BUFFER_COUNT; i++) {
|
||||
out.append("layout(std430, binding = ").append(VG_REPLAY_VERTEX_BUFFER_BINDING + i)
|
||||
.append(") readonly buffer _VgVertices").append(i).append(" { uint d[]; } _vg_vertices").append(i).append(";\n");
|
||||
}
|
||||
out.append("layout(std430, binding = ").append(VG_REPLAY_INDEX_BUFFER_BINDING).append(") readonly buffer _VgIndices { uint d[]; } _vg_indices;\n\n");
|
||||
final int slots = Math.max(1, inputs.size());
|
||||
out.append("uniform uvec4 _vg_attr[").append(slots).append("];\n");
|
||||
out.append("uniform vec4 _vg_attrDefault[").append(slots).append("];\n");
|
||||
out.append("uniform ivec4 _vg_draw;\n");
|
||||
out.append("uniform ivec3 _vg_index;\n");
|
||||
out.append("uniform uvec2 _vg_restart;\n");
|
||||
out.append("uniform int _vg_invocationBase;\n\n");
|
||||
out.append("vec4 _vg_sink_pos;\n");
|
||||
out.append("float _vg_sink_psize;\n");
|
||||
out.append("float _vg_sink_clip[8];\n");
|
||||
out.append("int _vg_vertex_id;\n");
|
||||
out.append("int _vg_instance_id;\n");
|
||||
out.append("#define gl_Position _vg_sink_pos\n");
|
||||
out.append("#define gl_PointSize _vg_sink_psize\n");
|
||||
out.append("#define gl_ClipDistance _vg_sink_clip\n");
|
||||
out.append("#define gl_VertexID _vg_vertex_id\n");
|
||||
out.append("#define gl_VertexIndex _vg_vertex_id\n");
|
||||
out.append("#define gl_InstanceID _vg_instance_id\n");
|
||||
out.append("#define gl_InstanceIndex _vg_instance_id\n");
|
||||
for (String suffix : new String[] { "", "ARB" }) {
|
||||
out.append("#define gl_BaseVertex").append(suffix).append(" _vg_draw.w\n");
|
||||
out.append("#define gl_BaseInstance").append(suffix).append(" 0\n");
|
||||
out.append("#define gl_DrawID").append(suffix).append(" 0\n");
|
||||
}
|
||||
out.append('\n');
|
||||
for (VertexInput input : inputs) out.append(input.glslType()).append(' ').append(input.name()).append(";\n");
|
||||
out.append('\n');
|
||||
out.append(ShaderLoader.getShaderSource("angelica:include/vertex_replay_fetch.glsl"));
|
||||
}
|
||||
|
||||
private static void emitCompositeFshPrelude(StringBuilder out, Set<String> writtenImages, Set<String> nonWriteonlyImages, Map<String, ImageDecl> declared) {
|
||||
@@ -643,7 +1042,7 @@ public final class RwImageStoreExtractor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void emitDispatchMain(StringBuilder out, RwExtractMode mode, List<AttributeSlot> attrs) {
|
||||
private static void emitDispatchMain(StringBuilder out, RwExtractMode mode, List<AttributeSlot> attrs, List<VertexInput> inputs, Set<String> quadInputs) {
|
||||
out.append("void main() {\n");
|
||||
switch (mode) {
|
||||
case CHUNK -> {
|
||||
@@ -665,6 +1064,7 @@ public final class RwImageStoreExtractor {
|
||||
out.append(" uint vid = gl_GlobalInvocationID.x;\n");
|
||||
out.append(" if (vid >= 4u) return;\n");
|
||||
out.append(" _vg_gl_vertex = _vg_quad[vid];\n");
|
||||
for (String input : quadInputs) out.append(" ").append(input).append(" = _vg_quad[vid];\n");
|
||||
out.append(" _vg_gl_vertex_id = int(vid);\n");
|
||||
out.append(" _vg_body();\n");
|
||||
}
|
||||
@@ -674,10 +1074,37 @@ public final class RwImageStoreExtractor {
|
||||
out.append(" _vg_gl_fragcoord_raw = vec4(vec2(px) + 0.5, 0.0, 1.0);\n");
|
||||
out.append(" _vg_body();\n");
|
||||
}
|
||||
case VERTEX_REPLAY -> {
|
||||
out.append(" uint _vg_g = uint(_vg_invocationBase) + gl_GlobalInvocationID.x;\n");
|
||||
out.append(" uint _vg_count = uint(_vg_draw.y);\n");
|
||||
out.append(" if (_vg_count == 0u || _vg_g >= _vg_count * uint(_vg_draw.z)) return;\n");
|
||||
out.append(" uint _vg_v = _vg_g % _vg_count;\n");
|
||||
out.append(" uint _vg_i = _vg_g / _vg_count;\n");
|
||||
out.append(" uint _vg_vertex = uint(_vg_draw.x) + _vg_v;\n");
|
||||
out.append(" if (_vg_index.x != 0) {\n");
|
||||
out.append(" uint _vg_index_value = _vg_v < uint(_vg_index.z) ? _vg_read(4u, uint(_vg_index.y) + _vg_v * uint(_vg_index.x), uint(_vg_index.x)) : 0u;\n");
|
||||
out.append(" if (_vg_restart.x != 0u && _vg_index_value == _vg_restart.y) return;\n");
|
||||
out.append(" _vg_vertex = _vg_index_value + uint(_vg_draw.w);\n");
|
||||
out.append(" }\n");
|
||||
out.append(" _vg_vertex_id = int(_vg_vertex);\n");
|
||||
out.append(" _vg_instance_id = int(_vg_i);\n");
|
||||
for (int k = 0; k < inputs.size(); k++) {
|
||||
final VertexInput input = inputs.get(k);
|
||||
out.append(" ").append(input.name()).append(" = ").append(input.glslType()).append('(')
|
||||
.append(fetchFunctionFor(input.glslType())).append('(').append(k).append("u, _vg_vertex, _vg_i));\n");
|
||||
}
|
||||
out.append(" _vg_body();\n");
|
||||
}
|
||||
}
|
||||
out.append("}\n");
|
||||
}
|
||||
|
||||
private static String fetchFunctionFor(String glslType) {
|
||||
if (glslType.startsWith("u")) return "_vg_uint";
|
||||
if (glslType.startsWith("i")) return "_vg_int";
|
||||
return "_vg_float";
|
||||
}
|
||||
|
||||
private static List<AttributeSlot> collectVertexAttributes(Set<String> chunkAttrsUsed, Map<String, String> chunkAttrAstTypes) {
|
||||
final List<AttributeSlot> out = new ArrayList<>();
|
||||
for (String name : chunkAttrsUsed) {
|
||||
|
||||
@@ -35,6 +35,7 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -237,7 +238,8 @@ public class ShaderTransformer {
|
||||
final ShaderDiskCache.Key k = ShaderDiskCache.key("iris-transform").str(patch.name());
|
||||
parameters.appendDiskKey(k);
|
||||
k.i(RenderSystem.getMaxGlslVersion()).b(RenderSystem.supportsSSBO()).b(RenderSystem.supportsImageLoadStore())
|
||||
.b(RenderSystem.isGLES()).b(BackendManager.RENDER_BACKEND.isSDLGPU()).i(maxSupportedHoistVersion);
|
||||
.b(RenderSystem.isGLES()).b(BackendManager.RENDER_BACKEND.isSDLGPU()).b(BackendManager.RENDER_BACKEND.supportsComputeImageAtomics()).str(RwImageStoreExtractor.atomicCustomImagesKey())
|
||||
.i(maxSupportedHoistVersion);
|
||||
if (enabledRequirements == null) {
|
||||
k.i(-1);
|
||||
} else {
|
||||
@@ -559,18 +561,23 @@ public class ShaderTransformer {
|
||||
artifactsOut.put(shaderType, new StageArtifact(treeHolder[0], formattedShader.length() - printedBody.length()));
|
||||
}
|
||||
}
|
||||
if (BackendManager.RENDER_BACKEND.isSDLGPU()) extractRwImageStores(result, patchType, artifactsOut);
|
||||
if (BackendManager.RENDER_BACKEND.isSDLGPU()) extractRwImageStores(result, patchType, artifactsOut, RwImageStoreExtractor::supportsAtomics);
|
||||
watch.stop();
|
||||
Iris.logger.info("[Load #{}] Transformed shader for {} in {}", Iris.getShaderPackLoadId(), patchType.name(), watch);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void extractRwImageStores(EnumMap<PatchShaderType, String> result, Patch patchType, EnumMap<PatchShaderType, StageArtifact> artifactsOut) {
|
||||
static void extractRwImageStores(EnumMap<PatchShaderType, String> result, Patch patchType, EnumMap<PatchShaderType, StageArtifact> artifactsOut, Predicate<String> imageAtomics) {
|
||||
if (result.containsKey(PatchShaderType.COMPUTE)) return;
|
||||
if (patchType == Patch.ATTRIBUTES) {
|
||||
extractVertexReplay(result, artifactsOut, imageAtomics);
|
||||
return;
|
||||
}
|
||||
|
||||
final String vsh = result.get(PatchShaderType.VERTEX);
|
||||
final String fsh = result.get(PatchShaderType.FRAGMENT);
|
||||
final RwImageStoreExtractor.Result vshResult = (vsh != null) ? RwImageStoreExtractor.tryExtract(vsh, PatchShaderType.VERTEX, patchType.name()) : null;
|
||||
final RwImageStoreExtractor.RwExtractMode vertexMode = patchType == Patch.COMPOSITE ? RwImageStoreExtractor.RwExtractMode.COMPOSITE_VSH : null;
|
||||
final RwImageStoreExtractor.Result vshResult = (vsh != null) ? RwImageStoreExtractor.tryExtract(vsh, PatchShaderType.VERTEX, patchType.name(), vertexMode) : null;
|
||||
final RwImageStoreExtractor.Result fshResult = (fsh != null) ? RwImageStoreExtractor.tryExtract(fsh, PatchShaderType.FRAGMENT, patchType.name()) : null;
|
||||
|
||||
if (vshResult != null && fshResult != null) {
|
||||
@@ -581,14 +588,46 @@ public class ShaderTransformer {
|
||||
final PatchShaderType stage = vshResult != null ? PatchShaderType.VERTEX : PatchShaderType.FRAGMENT;
|
||||
result.put(stage, extracted.strippedSource());
|
||||
if (artifactsOut != null) artifactsOut.remove(stage);
|
||||
if (patchType == Patch.COMPOSITE || patchType == Patch.CELERITAS_TERRAIN) {
|
||||
if (patchType != Patch.COMPOSITE && patchType != Patch.CELERITAS_TERRAIN) {
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped image writes from {} (written={})", patchType.name(), extracted.writtenImages());
|
||||
} else if (needsUnsupportedAtomics(extracted, imageAtomics)) {
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped image writes from {}, they need image atomics this backend cannot run (written={})", patchType.name(), extracted.writtenImages());
|
||||
} else {
|
||||
result.put(PatchShaderType.COMPUTE, extracted.computeSource());
|
||||
Iris.logger.info("[RwImageStoreExtractor] Extracted compute pre-pass for {} (mode={}, written={})", patchType.name(), extracted.mode(), extracted.writtenImages());
|
||||
} else {
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped image writes from {} (written={})", patchType.name(), extracted.writtenImages());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needsUnsupportedAtomics(RwImageStoreExtractor.Result extracted, Predicate<String> imageAtomics) {
|
||||
return !extracted.atomicImages().stream().allMatch(imageAtomics);
|
||||
}
|
||||
|
||||
private static void extractVertexReplay(EnumMap<PatchShaderType, String> result, EnumMap<PatchShaderType, StageArtifact> artifactsOut, Predicate<String> imageAtomics) {
|
||||
final String fsh = result.get(PatchShaderType.FRAGMENT);
|
||||
final RwImageStoreExtractor.Result fshResult = (fsh != null) ? RwImageStoreExtractor.tryExtract(fsh, PatchShaderType.FRAGMENT, Patch.ATTRIBUTES.name()) : null;
|
||||
if (fshResult != null) {
|
||||
result.put(PatchShaderType.FRAGMENT, fshResult.strippedSource());
|
||||
if (artifactsOut != null) artifactsOut.remove(PatchShaderType.FRAGMENT);
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped fragment image writes from ATTRIBUTES (written={})", fshResult.writtenImages());
|
||||
}
|
||||
|
||||
final String vsh = result.get(PatchShaderType.VERTEX);
|
||||
final RwImageStoreExtractor.Result vshResult = (vsh != null) ? RwImageStoreExtractor.tryExtractVertexReplay(vsh) : null;
|
||||
if (vshResult == null) return;
|
||||
result.put(PatchShaderType.VERTEX, vshResult.strippedSource());
|
||||
if (artifactsOut != null) artifactsOut.remove(PatchShaderType.VERTEX);
|
||||
if (vshResult.computeSource() == null) {
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped vertex image writes from ATTRIBUTES, an input cannot be replayed (written={})", vshResult.writtenImages());
|
||||
return;
|
||||
}
|
||||
if (needsUnsupportedAtomics(vshResult, imageAtomics)) {
|
||||
Iris.logger.info("[RwImageStoreExtractor] Dropped vertex image writes from ATTRIBUTES, they need image atomics this backend cannot run (written={})", vshResult.writtenImages());
|
||||
return;
|
||||
}
|
||||
result.put(PatchShaderType.COMPUTE, vshResult.computeSource());
|
||||
Iris.logger.info("[RwImageStoreExtractor] Extracted vertex replay for ATTRIBUTES (written={})", vshResult.writtenImages());
|
||||
}
|
||||
|
||||
private static void doTransform(Transformer transformer, Patch patchType, Parameters parameters, int versionInt) {
|
||||
switch (patchType) {
|
||||
case CELERITAS_TERRAIN:
|
||||
@@ -717,12 +756,6 @@ public class ShaderTransformer {
|
||||
}
|
||||
}
|
||||
|
||||
public static void addIfNotExistsType(Transformer transformer, String name, String type) {
|
||||
if (!transformer.hasVariable(name)) {
|
||||
transformer.injectVariable(type + " " + name + ";");
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CeleritasHeader {
|
||||
static final String VALUE = computeCeleritasHeader();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
{
|
||||
"providers": [
|
||||
{
|
||||
"type": "bitmap",
|
||||
"file": "minecraft:font/nonlatin_european.png",
|
||||
"ascent": 7,
|
||||
"chars": [
|
||||
"\u00a1\u2030\u00ad\u00b7\u20b4\u2260\u00bf\u00d7\u00d8\u00de\u04bb\u00f0\u00f8\u00fe\u0391\u0392",
|
||||
"\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3",
|
||||
"\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba",
|
||||
"\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u0402",
|
||||
"\u0405\u0406\u0408\u0409\u040a\u040b\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u041a",
|
||||
"\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a",
|
||||
"\u042b\u042c\u042d\u042e\u042f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u043a\u043b",
|
||||
"\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b",
|
||||
"\u044c\u044d\u044e\u044f\u0454\u0455\u0456\u0458\u0459\u045a\u2013\u2014\u2018\u2019\u201c\u201d",
|
||||
"\u201e\u2026\u204a\u2190\u2191\u2192\u2193\u21c4\uff0b\u018f\u0259\u025b\u026a\u04ae\u04af\u04e8",
|
||||
"\u04e9\u02bb\u02cc\u037e\u0138\u1e9e\u00df\u20bd\u20ac\u0462\u0463\u0474\u0475\u04c0\u0472\u0473",
|
||||
"\u2070\u00b9\u00b3\u2074\u2075\u2076\u2077\u2078\u2079\u207a\u207b\u207c\u207d\u207e\u2071\u2122",
|
||||
"\u0294\u0295\u29c8\u2694\u2620\u049a\u049b\u0492\u0493\u04b0\u04b1\u04d8\u04d9\u0496\u0497\u04a2",
|
||||
"\u04a3\u04ba\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05dd",
|
||||
"\u05e0\u05df\u05e1\u05e2\u05e4\u05e3\u05e6\u05e5\u05e7\u05e8\u00a2\u00a4\u00a5\u00a9\u00ae\u00b5",
|
||||
"\u00b6\u00bc\u00bd\u00be\u0387\u2010\u201a\u2020\u2021\u2022\u2031\u2032\u2033\u2034\u2035\u2036",
|
||||
"\u2037\u2039\u203a\u203b\u203c\u203d\u2042\u2048\u2049\u204b\u204e\u204f\u2051\u2052\u2057\u2117",
|
||||
"\u2212\u2213\u221e\u2600\u2601\u2608\u0404\u2632\u2635\u263d\u2640\u2642\u26a5\u2660\u2663\u2665",
|
||||
"\u2666\u2669\u266a\u266b\u266c\u266d\u266e\u266f\u2680\u2681\u2682\u2683\u2684\u2685\u02ac\u26a1",
|
||||
"\u26cf\u2714\u2744\u274c\u2764\u2b50\u2e18\u2e2e\u2e35\u2e38\u2e41\u2e4b\u295d\u1614\u0190\u07c8",
|
||||
"\u03db\u3125\u2c6f\u15fa\u0186\u15e1\u018e\u2132\u2141\ua7b0\ua780\u0500\ua779\u1d1a\u27d8\u2229",
|
||||
"\u0245\u2144\u0250\u0254\u01dd\u025f\u1d77\u0265\u1d09\u027e\u029e\ua781\u026f\u0279\u0287\u028c",
|
||||
"\u028d\u028e\u0531\u0532\u0533\u0534\u0536\u0537\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540",
|
||||
"\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054b\u054c\u054d\u054e\u054f\u0550\u0551",
|
||||
"\u0552\u0553\u0554\u0555\u0556\u0559\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a",
|
||||
"\u056b\u056c\u056d\u056e\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a",
|
||||
"\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582\u0583\u0584\u0585\u0586\u0587\u05e9\u05ea\u0538",
|
||||
"\u055a\u055b\u055c\u055d\u055e\u055f\u0560\u0588\u058f\u00af\u017f\u01b7\u0292\u01f7\u01bf\u021c",
|
||||
"\u021d\u0224\u0225\u02d9\ua75a\ua75b\u2011\u214b\u23cf\u23e9\u23ea\u23ed\u23ee\u23ef\u23f4\u23f5",
|
||||
"\u23f6\u23f7\u23f8\u23f9\u23fa\u23fb\u23fc\u23fd\u2b58\u25b2\u25b6\u25bc\u25c0\u25cf\u25e6\u25d8",
|
||||
"\u2693\u26e8\u0132\u0133\u01c9\ua728\ua729\ua739\ua73b\ufb00\ufb01\ufb02\ufb03\ufb05\ufffd\u0535",
|
||||
"\u054a\u16a0\u16a2\u16a3\u16a4\u16a5\u16a6\u16a7\u16a8\u16a9\u16aa\u16ab\u16ac\u16ad\u16ae\u16af",
|
||||
"\u16b0\u16b1\u16b2\u16b3\u16b4\u16b6\u16b7\u16b8\u16b9\u16ba\u16bb\u16bc\u16bd\u16be\u16bf\u16c0",
|
||||
"\u16c1\u16c2\u16c3\u16c4\u16c5\u16c6\u16c7\u16c8\u16c9\u16ca\u16cb\u16cc\u16cd\u16ce\u16cf\u16d0",
|
||||
"\u16d1\u16d2\u16d3\u16d4\u16d5\u16d6\u16d7\u16d8\u16d9\u16da\u16db\u16dc\u16dd\u16de\u16df\u16e0",
|
||||
"\u16e1\u16e2\u16e3\u16e4\u16e5\u16e6\u16e7\u16e8\u16e9\u16ea\u16eb\u16ec\u16ed\u16ee\u16ef\u16f0",
|
||||
"\u16f1\u16f2\u16f3\u16f4\u16f5\u16f6\u16f7\u16f8\u263a\u263b\u00a6\u2639\u05da\u05f3\u05f4\u05f0",
|
||||
"\u05f1\u05f2\u05be\u05c3\u05c6\u00b4\u00a8\u1d00\u0299\u1d04\u1d05\u1d07\ua730\u0262\u029c\u1d0a",
|
||||
"\u1d0b\u029f\u1d0d\u0274\u1d0f\u1d18\ua7af\u0280\ua731\u1d1b\u1d1c\u1d20\u1d21\u028f\u1d22\u00a7",
|
||||
"\u0271\u0273\u0272\u0288\u0256\u0261\u02a1\u0255\u0291\u0278\u029d\u02a2\u027b\u0281\u0266\u028b",
|
||||
"\u0270\u026c\u026e\u0298\u01c0\u01c3\u01c2\u01c1\u0253\u0257\u1d91\u0284\u0260\u029b\u0267\u026b",
|
||||
"\u0268\u0289\u028a\u0258\u0275\u0264\u025c\u025e\u0251\u0252\u025a\u025d\u0181\u0189\u0191\u01a9",
|
||||
"\u01b2\u10a0\u10a1\u10a2\u10a3\u10a4\u10a5\u10a6\u10a7\u10a8\u10a9\u10aa\u10ab\u10ac\u10ad\u10ae",
|
||||
"\u10af\u10b0\u10b1\u10b2\u10b3\u10b4\u10b5\u10b6\u10b7\u10b8\u10b9\u10ba\u10bb\u10bc\u10bd\u10be",
|
||||
"\u10bf\u10c0\u10c1\u10c2\u10c3\u10c4\u10c5\u10c7\u10cd\u10d0\u10d1\u10d2\u10d3\u10d4\u10d5\u10d6",
|
||||
"\u10d7\u10d8\u10d9\u10da\u10db\u10dc\u10dd\u10de\u10df\u10e0\u10e1\u10e2\u10e3\u10e4\u10e5\u10e6",
|
||||
"\u10e7\u10e8\u10e9\u10ea\u10eb\u10ec\u10ed\u10ee\u10ef\u10f0\u10f1\u10f2\u10f3\u10f4\u10f5\u10f6",
|
||||
"\u10f7\u10f8\u10f9\u10fa\u10fb\u10fc\u10fd\u10fe\u10ff\ufb4a\ufb2b\ufb4e\ufb44\ufb3b\ufb1f\ufb1d",
|
||||
"\ufb4b\ufb35\ufb4c\ufb31\ua727\ua726\u027a\u2c71\u02a0\u0297\u0296\u026d\u0277\u027f\u0285\u0286",
|
||||
"\u0293\u029a\u20aa\u20be\u058a\u2d00\u2d01\u2d02\u2d03\u2d04\u2d05\u2d06\u2d21\u2d07\u2d08\u2d09",
|
||||
"\u2d0a\u2d0b\u2d0c\u2d22\u2d0d\u2d0e\u2d0f\u2d10\u2d11\u2d12\u2d23\u2d13\u2d14\u2d15\u2d16\u2d17",
|
||||
"\u2d18\u2d19\u2d1a\u2d1b\u2d1c\u2d1d\u2d1e\u2d24\u2d1f\u2d20\u2d25\u215b\u215c\u215d\u215e\u2153",
|
||||
"\u2154\u2709\u2602\u2614\u2604\u26c4\u2603\u231b\u231a\u2690\u270e\u2763\u2664\u2667\u2661\u2662",
|
||||
"\u26c8\u2630\u2631\u2633\u2634\u2636\u2637\u2194\u21d2\u21cf\u21d4\u21f5\u2200\u2203\u2204\u2209",
|
||||
"\u220b\u220c\u2282\u2283\u2284\u2285\u2227\u2228\u22bb\u22bc\u22bd\u2225\u2262\u22c6\u2211\u22a4",
|
||||
"\u22a5\u22a2\u22a8\u2254\u2201\u2234\u2235\u221b\u221c\u2202\u22c3\u2286\u2287\u25a1\u25b3\u25b7",
|
||||
"\u25bd\u25c1\u25c6\u25c7\u25cb\u25ce\u2606\u2605\u2718\u2080\u2081\u2082\u2083\u2084\u2085\u2086",
|
||||
"\u2087\u2088\u2089\u208a\u208b\u208c\u208d\u208e\u222b\u222e\u221d\u2300\u2302\u2318\u3012\u027c",
|
||||
"\u0184\u0185\u1e9f\u023d\u019a\u019b\u0220\u019e\u019f\u01a7\u01a8\u01aa\u01b8\u01b9\u01bb\u01bc",
|
||||
"\u01bd\u01be\u0221\u0234\u0235\u0236\u023a\u2c65\u023b\u023c\u0246\u0247\u023e\u2c66\u0241\u0242",
|
||||
"\u0243\u0244\u0248\u0249\u024a\u024b\u024c\u024d\u024e\u024f\u1e9c\u1e9d\u1efc\u1efd\u1efe\u1eff",
|
||||
"\ua7a8\ua7a9\ud800\udf30\ud800\udf31\ud800\udf32\ud800\udf33\ud800\udf34\ud800\udf35\ud800\udf36\ud800\udf37\ud800\udf38\ud800\udf39\ud800\udf3a\ud800\udf3b\ud800\udf3c\ud800\udf3d",
|
||||
"\ud800\udf3e\ud800\udf3f\ud800\udf40\ud800\udf41\ud800\udf42\ud800\udf43\ud800\udf44\ud800\udf45\ud800\udf46\ud800\udf47\ud800\udf48\ud800\udf49\ud800\udf4a\ud83c\udf27\ud83d\udd25\ud83c\udf0a",
|
||||
"\u2150\u2151\u2155\u2156\u2157\u2159\u215a\u215f\u2189\ud83d\udde1\ud83c\udff9\ud83e\ude93\ud83d\udd31\ud83c\udfa3\ud83e\uddea\u2697",
|
||||
"\u2bea\u2beb\u2c6d\ud83d\udee1\u2702\ud83c\udf56\ud83e\udea3\ud83d\udd14\u23f3\u2691\u20a0\u20a1\u20a2\u20a3\u20a4\u20a5",
|
||||
"\u20a6\u20a9\u20ab\u20ad\u20ae\u20b0\u20b1\u20b2\u20b3\u20b5\u20b6\u20b7\u20b8\u20b9\u20ba\u20bb",
|
||||
"\u20bc\u20bf\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "bitmap",
|
||||
"file": "minecraft:font/accented.png",
|
||||
"height": 12,
|
||||
"ascent": 10,
|
||||
"chars": [
|
||||
"\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c6\u00c7\u00c8\u00c9\u00ca\u00cb\u00cc\u00cd\u00ce\u00cf",
|
||||
"\u00d0\u00d1\u00d2\u00d3\u00d4\u00d5\u00d6\u00d9\u00da\u00db\u00dc\u00dd\u00e0\u00e1\u00e2\u00e3",
|
||||
"\u00e4\u00e5\u00e6\u00e7\u00ec\u00ed\u00ee\u00ef\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f9\u00fa",
|
||||
"\u00fb\u00fc\u00fd\u00ff\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010a\u010b",
|
||||
"\u010c\u010d\u010e\u010f\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011a\u011b",
|
||||
"\u011c\u011d\u1e20\u1e21\u011e\u011f\u0120\u0121\u0122\u0123\u0124\u0125\u0126\u0127\u0128\u0129",
|
||||
"\u012a\u012b\u012c\u012d\u012e\u012f\u0130\u0131\u0134\u0135\u0136\u0137\u0139\u013a\u013b\u013c",
|
||||
"\u013d\u013e\u013f\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u014a\u014b\u014c\u014d",
|
||||
"\u014e\u014f\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015a\u015b\u015c\u015d",
|
||||
"\u015e\u015f\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016a\u016b\u016c\u016d",
|
||||
"\u016e\u016f\u0170\u0171\u0172\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017a\u017b\u017c\u017d",
|
||||
"\u017e\u01fc\u01fd\u01fe\u01ff\u0218\u0219\u021a\u021b\u0386\u0388\u0389\u038a\u038c\u038e\u038f",
|
||||
"\u0390\u03aa\u03ab\u03ac\u03ad\u03ae\u03af\u03b0\u03ca\u03cb\u03cc\u03cd\u03ce\u0400\u0401\u0403",
|
||||
"\u0407\u040c\u040d\u040e\u0419\u0439\u0450\u0451\u0452\u0453\u0457\u045b\u045c\u045d\u045e\u045f",
|
||||
"\u0490\u0491\u1e02\u1e03\u1e0a\u1e0b\u1e1e\u1e1f\u1e22\u1e23\u1e30\u1e31\u1e40\u1e41\u1e56\u1e57",
|
||||
"\u1e60\u1e61\u1e6a\u1e6b\u1e80\u1e81\u1e82\u1e83\u1e84\u1e85\u1ef2\u1ef3\u00e8\u00e9\u00ea\u00eb",
|
||||
"\u0149\u01e7\u01eb\u040f\u1e0d\u1e25\u1e5b\u1e6d\u1e92\u1eca\u1ecb\u1ecc\u1ecd\u1ee4\u1ee5\u2116",
|
||||
"\u0207\u0194\u0263\u0283\u2047\u01f1\u01f2\u01f3\u01c4\u01c5\u01c6\u01c7\u01c8\u01ca\u01cb\u01cc",
|
||||
"\u2139\u1d6b\ua732\ua733\ua734\ua735\ua736\ua737\ua738\ua73a\ua73c\ua73d\ua74e\ua74f\ua760\ua761",
|
||||
"\ufb04\ufb06\u16a1\u16b5\u01a0\u01a1\u01af\u01b0\u1eae\u1eaf\u1ea4\u1ea5\u1ebe\u1ebf\u1ed1\u1eda",
|
||||
"\u1edb\u1ee8\u1ee9\u1eb0\u1eb1\u1ea6\u1ea7\u1ec0\u1ec1\u1ed3\u1edc\u1edd\u1eea\u1eeb\u1ea2\u1ea3",
|
||||
"\u1eb2\u1eb3\u1ea8\u1ea9\u1eba\u1ebb\u1ed5\u1ede\u1ec2\u1ec3\u1ec8\u1ec9\u1ece\u1ecf\u1ed4\u1edf",
|
||||
"\u1ee6\u1ee7\u1eec\u1eed\u1ef6\u1ef7\u1ea0\u1ea1\u1eb6\u1eb7\u1eac\u1ead\u1eb8\u1eb9\u1ec6\u1ec7",
|
||||
"\u1ed8\u1ed9\u1ee2\u1ee3\u1ef0\u1ef1\u1ef4\u1ef5\u1ed0\u0195\u1eaa\u1eab\u1ed6\u1ed7\u1eef\u261e",
|
||||
"\u261c\u262e\u1eb4\u1eb5\u1ebc\u1ebd\u1ec4\u1ec5\u1ed2\u1ee0\u1ee1\u1eee\u1ef8\u1ef9\u0498\u0499",
|
||||
"\u04a0\u04a1\u04aa\u04ab\u01f6\u26a0\u24ea\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468",
|
||||
"\u2469\u246a\u246b\u246c\u246d\u246e\u246f\u2470\u2471\u2472\u2473\u24b6\u24b7\u24b8\u24b9\u24ba",
|
||||
"\u24bb\u24bc\u24bd\u24be\u24bf\u24c0\u24c1\u24c2\u24c3\u24c4\u24c5\u24c6\u24c7\u24c8\u24c9\u24ca",
|
||||
"\u24cb\u24cc\u24cd\u24ce\u24cf\u24d0\u24d1\u24d2\u24d3\u24d4\u24d5\u24d6\u24d7\u24d8\u24d9\u24da",
|
||||
"\u24db\u24dc\u24dd\u24de\u24df\u24e0\u24e1\u24e2\u24e3\u24e4\u24e5\u24e6\u24e7\u24e8\u24e9\u0327",
|
||||
"\u0282\u0290\u0276\u01cd\u01ce\u01de\u01df\u01fa\u01fb\u0202\u0203\u0226\u0227\u01e0\u01e1\u1e00",
|
||||
"\u1e01\u0200\u0201\u1e06\u1e07\u1e04\u1e05\u1d6c\u1e08\u1e09\u1e10\u1e11\u1e12\u1e13\u1e0e\u1e0f",
|
||||
"\u1e0c\u1d6d\u1e14\u1e15\u1e16\u1e17\u1e18\u1e19\u1e1c\u1e1d\u0228\u0229\u1e1a\u1e1b\u0204\u0205",
|
||||
"\u0206\u1d6e\u01f4\u01f5\u01e6\u1e26\u1e27\u1e28\u1e29\u1e2a\u1e2b\u021e\u021f\u1e24\u1e96\u1e2e",
|
||||
"\u1e2f\u020a\u020b\u01cf\u01d0\u0208\u0209\u1e2c\u1e2d\u01f0\u0237\u01e8\u01e9\u1e32\u1e33\u1e34",
|
||||
"\u1e35\u1e3a\u1e3b\u1e3c\u1e3d\u1e36\u1e37\u1e38\u1e39\u2c62\u1e3e\u1e3f\u1e42\u1e43\u1d6f\u1e44",
|
||||
"\u1e45\u1e46\u1e47\u1e4a\u1e4b\u01f8\u01f9\u1e48\u1e49\u1d70\u01ec\u01ed\u022c\u022d\u1e4c\u1e4d",
|
||||
"\u1e4e\u1e4f\u1e50\u1e51\u1e52\u1e53\u020e\u020f\u022a\u022b\u01d1\u01d2\u022e\u022f\u0230\u0231",
|
||||
"\u020c\u020d\u01ea\u1e54\u1e55\u1d71\u0212\u0213\u1e58\u1e59\u1e5c\u1e5d\u1e5e\u1e5f\u0210\u0211",
|
||||
"\u1e5a\u1d73\u1d72\u1e64\u1e65\u1e66\u1e67\u1e62\u1e63\u1e68\u1e69\u1d74\u1e70\u1e71\u1e6e\u1e6f",
|
||||
"\u1e6c\u1e97\u1d75\u1e72\u1e73\u1e76\u1e77\u1e78\u1e79\u1e7a\u1e7b\u01d3\u01d4\u01d5\u01d6\u01d7",
|
||||
"\u01d8\u01d9\u01da\u01db\u01dc\u1e74\u1e75\u0214\u0215\u0216\u1e7e\u1e7f\u1e7c\u1e7d\u1e86\u1e87",
|
||||
"\u1e88\u1e89\u1e98\u1e8c\u1e8d\u1e8a\u1e8b\u0232\u0233\u1e8e\u1e8f\u1e99\u1e94\u1e95\u1e90\u1e91",
|
||||
"\u1e93\u1d76\u01ee\u01ef\u1e9b\ua73e\ua73f\u01e2\u01e3\u1d7a\u1efb\u1d02\u1d14\uab63\u0238\u02a3",
|
||||
"\u02a5\u02a4\u02a9\u02aa\u02ab\u0239\u02a8\u02a6\u02a7\uab50\uab51\u20a7\u1efa\ufb2e\ufb2f\u0180",
|
||||
"\u0182\u0183\u0187\u0188\u018a\u018b\u018c\u0193\u01e4\u01e5\u0197\u0196\u0269\u0198\u0199\u019d",
|
||||
"\u01a4\u01a5\u027d\u01a6\u01ac\u01ad\u01ab\u01ae\u0217\u01b1\u019c\u01b3\u01b4\u01b5\u01b6\u01a2",
|
||||
"\u01a3\u0222\u0223\u02ad\u02ae\u02af\ufb14\ufb15\ufb17\ufb16\ufb13\u04d0\u04d1\u04d2\u04d3\u04f6",
|
||||
"\u04f7\u0494\u0495\u04d6\u04d7\u04bc\u04bd\u04be\u04bf\u04da\u04db\u04dc\u04dd\u04c1\u04c2\u04de",
|
||||
"\u04df\u04e2\u04e3\u04e4\u04e5\u04e6\u04e7\u04ea\u04eb\u04f0\u04f1\u04ee\u04ef\u04f2\u04f3\u04f4",
|
||||
"\u04f5\u04f8\u04f9\u04ec\u04ed\u0476\u0477\u04d4\u04fa\u0502\ua682\ua680\ua688\u052a\u052c\ua684",
|
||||
"\u0504\u0510\u04e0\u0506\u048a\u04c3\u049e\u049c\u051e\u051a\u04c5\u052e\u0512\u0520\u0508\u0514",
|
||||
"\u04cd\u04c9\u0528\u04c7\u04a4\u0522\u050a\u04a8\u0524\u04a6\u048e\u0516\u050c\ua690\u04ac\ua68a",
|
||||
"\ua68c\u050e\u04b2\u04fc\u04fe\u0526\ua694\u04b4\ua68e\u04b6\u04cb\u04b8\ua692\ua696\ua686\u048c",
|
||||
"\u0518\u051c\u04d5\u04fb\u0503\ua683\ua681\ua689\u052b\u052d\ua685\u0505\u0511\u04e1\u0507\u048b",
|
||||
"\u04c4\u049f\u049d\u051f\u051b\u04c6\u052f\u0513\u0521\u0509\u0515\u04ce\u04ca\u0529\u04c8\u04a5",
|
||||
"\u0523\u050b\u04a9\u0525\u04a7\u048f\u0517\u050d\ua691\u04ad\ua68b\ua68d\u050f\u04b3\u04fd\u04ff",
|
||||
"\u0527\ua695\u04b5\ua68f\u04b7\u04cc\u04b9\ua693\ua697\ua687\u048d\u0519\u051d\u1f08\u1f00\u1f09",
|
||||
"\u1f01\u1f0a\u1f02\u1f0b\u1f03\u1f0c\u1f04\u1f0d\u1f05\u1f0e\u1f06\u1f0f\u1f07\u1fba\u1f70\u1fb8",
|
||||
"\u1fb0\u1fb9\u1fb1\u1fbb\u1f71\u1f88\u1f80\u1f89\u1f81\u1f8a\u1f82\u1f8b\u1f83\u1f8c\u1f84\u1f8d",
|
||||
"\u1f85\u1f8e\u1f86\u1f8f\u1f87\u1fbc\u1fb4\u1fb6\u1fb7\u1fb2\u1fb3\u1f18\u1f10\u1f19\u1f11\u1f1a",
|
||||
"\u1f12\u1f1b\u1f13\u1f1c\u1f14\u1f1d\u1f15\u1fc8\u1fc9\u1f72\u1f73\u1f28\u1f20\u1fca\u1f74\u1f29",
|
||||
"\u1f21\u1f2a\u1f22\u1f2b\u1f23\u1f2c\u1f24\u1f2d\u1f25\u1f2e\u1f26\u1f2f\u1f27\u1f98\u1f90\u1f99",
|
||||
"\u1f91\u1f9a\u1f92\u1f9b\u1f93\u1f9c\u1f94\u1f9d\u1f95\u1f9e\u1f96\u1f9f\u1f97\u1fcb\u1f75\u1fcc",
|
||||
"\u1fc3\u1fc2\u1fc4\u1fc6\u1fc7\u1fda\u1f76\u1fdb\u1f77\u1f38\u1f30\u1f39\u1f31\u1f3a\u1f32\u1f3b",
|
||||
"\u1f33\u1f3c\u1f34\u1f3d\u1f35\u1f3e\u1f36\u1f3f\u1f37\u1fd8\u1fd0\u1fd9\u1fd1\u1fd2\u1fd3\u1fd6",
|
||||
"\u1fd7\u1ff8\u1f78\u1ff9\u1f79\u1f48\u1f40\u1f49\u1f41\u1f4a\u1f42\u1f4b\u1f43\u1f4c\u1f44\u1f4d",
|
||||
"\u1f45\u1fec\u1fe4\u1fe5\u1fea\u1f7a\u1feb\u1f7b\u1f59\u1f51\u1f5b\u1f53\u1f5d\u1f55\u1f5f\u1f57",
|
||||
"\u1fe8\u1fe0\u1fe9\u1fe1\u03d3\u03d4\u1fe2\u1fe3\u1fe7\u1f50\u1f52\u1f54\u1fe6\u1f56\u1ffa\u1f7c",
|
||||
"\u1ffb\u1f7d\u1f68\u1f60\u1f69\u1f61\u1f6a\u1f62\u1f6b\u1f63\u1f6c\u1f64\u1f6d\u1f65\u1f6e\u1f66",
|
||||
"\u1f6f\u1f67\u1fa8\u1fa0\u1fa9\u1fa1\u1faa\u1fa2\u1fab\u1fa3\u1fac\u1fa4\u1fad\u1fa5\u1fae\u1fa6",
|
||||
"\u1faf\u1fa7\u1ffc\u1ff3\u1ff2\u1ff4\u1ff6\u1ff7\u262f\u2610\u2611\u2612\u018d\u01ba\u2c7e\u023f",
|
||||
"\u2c7f\u0240\u1d80\ua7c4\ua794\u1d81\u1d82\u1d83\ua795\u1d84\u1d85\u1d86\u1d87\u1d88\u1d89\u1d8a",
|
||||
"\u1d8b\u1d8c\u1d8d\ua7c6\u1d8e\u1d8f\u1d90\u1d92\u1d93\u1d94\u1d95\u1d96\u1d97\u1d98\u1d99\u1d9a",
|
||||
"\u1e9a\u2152\u2158\u20a8\u20af\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "bitmap",
|
||||
"file": "minecraft:font/ascii.png",
|
||||
"ascent": 7,
|
||||
"chars": [
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000",
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000",
|
||||
"\u0020\u0021\u0022\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002a\u002b\u002c\u002d\u002e\u002f",
|
||||
"\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003a\u003b\u003c\u003d\u003e\u003f",
|
||||
"\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u004c\u004d\u004e\u004f",
|
||||
"\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005a\u005b\u005c\u005d\u005e\u005f",
|
||||
"\u0060\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006a\u006b\u006c\u006d\u006e\u006f",
|
||||
"\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007a\u007b\u007c\u007d\u007e\u0000",
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000",
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00a3\u0000\u0000\u0192",
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u00aa\u00ba\u0000\u0000\u00ac\u0000\u0000\u0000\u00ab\u00bb",
|
||||
"\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510",
|
||||
"\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567",
|
||||
"\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580",
|
||||
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u2205\u2208\u0000",
|
||||
"\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u0000\u221a\u207f\u00b2\u25a0\u0000"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Taken mostly from mesa
|
||||
|
||||
uint _vg_word(uint slot, uint i) {
|
||||
switch (slot) {
|
||||
case 0u: return _vg_vertices0.d[i];
|
||||
case 1u: return _vg_vertices1.d[i];
|
||||
case 2u: return _vg_vertices2.d[i];
|
||||
case 3u: return _vg_vertices3.d[i];
|
||||
default: return _vg_indices.d[i];
|
||||
}
|
||||
}
|
||||
|
||||
uint _vg_read(uint slot, uint at, uint size) {
|
||||
uint shift = (at & 3u) * 8u;
|
||||
uint value = _vg_word(slot, at >> 2) >> shift;
|
||||
if (shift + size * 8u > 32u) value |= _vg_word(slot, (at >> 2) + 1u) << (32u - shift);
|
||||
return size == 4u ? value : value & ((1u << (size * 8u)) - 1u);
|
||||
}
|
||||
|
||||
const uint _VG_COMPONENT_SIZE[12] = uint[12](4u, 2u, 1u, 1u, 2u, 2u, 4u, 4u, 4u, 4u, 4u, 4u);
|
||||
|
||||
bool _vg_enabled(uint a) {
|
||||
return (_vg_attr[a].x & 0x2000u) != 0u;
|
||||
}
|
||||
|
||||
uint _vg_element(uint a, uint vertex, uint instance) {
|
||||
uvec4 desc = _vg_attr[a];
|
||||
uint divisor = desc.x >> 16;
|
||||
uint element = divisor == 0u ? vertex : instance / divisor;
|
||||
return element > desc.w ? 0u : element;
|
||||
}
|
||||
|
||||
uvec4 _vg_raw(uint a, uint element) {
|
||||
uvec4 desc = _vg_attr[a];
|
||||
uint type = (desc.x >> 4) & 15u;
|
||||
uint at = desc.y + element * desc.z;
|
||||
bool none = (desc.x & 0x8000u) != 0u;
|
||||
if (type >= 9u) {
|
||||
uint packed = none ? 0u : _vg_read(desc.x & 7u, at, 4u);
|
||||
if (type == 9u) return uvec4(ivec4(int(packed << 22), int(packed << 12), int(packed << 2), int(packed)) >> ivec4(22, 22, 22, 30));
|
||||
if (type == 10u) return (uvec4(packed) >> uvec4(0u, 10u, 20u, 30u)) & uvec4(1023u, 1023u, 1023u, 3u);
|
||||
return uvec4(packed, 0u, 0u, 1u);
|
||||
}
|
||||
uint count = (desc.x >> 8) & 7u;
|
||||
uint size = _VG_COMPONENT_SIZE[type];
|
||||
uvec4 raw = uvec4(0u, 0u, 0u, 1u);
|
||||
for (uint c = 0u; c < count; c++) {
|
||||
uint v = none ? 0u : _vg_read(desc.x & 7u, at + c * size, size);
|
||||
if (type == 2u) v = uint(int(v << 24) >> 24);
|
||||
else if (type == 4u) v = uint(int(v << 16) >> 16);
|
||||
raw[c] = v;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
vec4 _vg_float(uint a, uint vertex, uint instance) {
|
||||
if (!_vg_enabled(a)) return _vg_attrDefault[a];
|
||||
uvec4 desc = _vg_attr[a];
|
||||
uint type = (desc.x >> 4) & 15u;
|
||||
uint count = (desc.x >> 8) & 7u;
|
||||
bool normalized = (desc.x & 0x800u) != 0u;
|
||||
uvec4 raw = _vg_raw(a, _vg_element(a, vertex, instance));
|
||||
vec4 v = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
if (type == 9u) {
|
||||
vec4 c = vec4(ivec4(raw));
|
||||
v = normalized ? max(c / vec4(511.0, 511.0, 511.0, 1.0), vec4(-1.0)) : c;
|
||||
} else if (type == 10u) {
|
||||
vec4 c = vec4(raw);
|
||||
v = normalized ? c / vec4(1023.0, 1023.0, 1023.0, 3.0) : c;
|
||||
} else if (type == 11u) {
|
||||
uint p = raw.x;
|
||||
v = vec4(unpackHalf2x16((p & 0x7ffu) << 4).x, unpackHalf2x16((p & 0x3ff800u) >> 7).x,
|
||||
unpackHalf2x16((p & 0xffc00000u) >> 17).x, 1.0);
|
||||
} else {
|
||||
for (uint c = 0u; c < count; c++) {
|
||||
uint r = raw[c];
|
||||
switch (type) {
|
||||
case 0u: v[c] = uintBitsToFloat(r); break;
|
||||
case 1u: v[c] = unpackHalf2x16(r).x; break;
|
||||
case 2u: v[c] = normalized ? max(float(int(r)) / 127.0, -1.0) : float(int(r)); break;
|
||||
case 3u: v[c] = normalized ? float(r) / 255.0 : float(r); break;
|
||||
case 4u: v[c] = normalized ? max(float(int(r)) / 32767.0, -1.0) : float(int(r)); break;
|
||||
case 5u: v[c] = normalized ? float(r) / 65535.0 : float(r); break;
|
||||
case 6u: v[c] = normalized ? max(float(int(r)) / 2147483647.0, -1.0) : float(int(r)); break;
|
||||
case 7u: v[c] = normalized ? float(r) / 4294967295.0 : float(r); break;
|
||||
default: v[c] = float(int(r)) / 65536.0; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (desc.x & 0x4000u) != 0u ? v.zyxw : v;
|
||||
}
|
||||
|
||||
ivec4 _vg_int(uint a, uint vertex, uint instance) {
|
||||
if (!_vg_enabled(a)) return ivec4(_vg_attrDefault[a]);
|
||||
if ((_vg_attr[a].x & 0x1000u) == 0u) return ivec4(_vg_float(a, vertex, instance));
|
||||
return ivec4(_vg_raw(a, _vg_element(a, vertex, instance)));
|
||||
}
|
||||
|
||||
uvec4 _vg_uint(uint a, uint vertex, uint instance) {
|
||||
if (!_vg_enabled(a)) return uvec4(_vg_attrDefault[a]);
|
||||
if ((_vg_attr[a].x & 0x1000u) == 0u) return uvec4(_vg_float(a, vertex, instance));
|
||||
return _vg_raw(a, _vg_element(a, vertex, instance));
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 795 B |
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
+2
-2
@@ -19,11 +19,11 @@ public class MixinMinecraftServer {
|
||||
|
||||
@Surround(method = "run", remap = false)
|
||||
private void angelica$setServerThread() {
|
||||
ThreadedBlockData.serverThread = Thread.currentThread();
|
||||
ThreadedBlockData.setServerThread(Thread.currentThread());
|
||||
}
|
||||
|
||||
@Surround.Finally
|
||||
private void angelica$clearServerThread() {
|
||||
ThreadedBlockData.serverThread = null;
|
||||
ThreadedBlockData.setServerThread(null);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class MixinBlock implements ThreadedBlockData.Getter {
|
||||
private ThreadedBlockData angelica$serverData;
|
||||
|
||||
@Override
|
||||
public ThreadedBlockData angelica$getThreadData() {
|
||||
public final ThreadedBlockData angelica$getThreadData() {
|
||||
Thread t = Thread.currentThread();
|
||||
|
||||
if (t == ThreadedBlockData.MAIN_THREAD) {
|
||||
@@ -30,9 +30,9 @@ public class MixinBlock implements ThreadedBlockData.Getter {
|
||||
return data != null ? data : (angelica$mainData = createThreadedBlockData());
|
||||
}
|
||||
|
||||
if (t == ThreadedBlockData.serverThread) {
|
||||
if (t == ThreadedBlockData.getServerThread()) {
|
||||
ThreadedBlockData data = angelica$serverData;
|
||||
return (data == null || data.owner != t) ? (angelica$serverData = createThreadedBlockData()) : data;
|
||||
return (data == null || !data.isCurrent()) ? (angelica$serverData = createThreadedBlockData()) : data;
|
||||
}
|
||||
|
||||
ThreadedBlockData data = angelica$threadData.get();
|
||||
|
||||
+11
-39
@@ -2,18 +2,13 @@ package com.gtnewhorizons.angelica.mixins.early.shaders;
|
||||
|
||||
import com.gtnewhorizons.angelica.experimental.surround.Surround;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
|
||||
import net.coderbot.iris.layer.GbufferPrograms;
|
||||
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import com.gtnewhorizons.angelica.api.EnderDragonBeams;
|
||||
import net.minecraft.client.renderer.entity.RenderDragon;
|
||||
import net.minecraft.entity.boss.EntityDragon;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Mixin allows devs to target the ender crystal beams and dragon death rays as separate entities.
|
||||
@@ -21,43 +16,24 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
*/
|
||||
@Mixin(RenderDragon.class)
|
||||
public abstract class MixinRenderDragon {
|
||||
@Unique
|
||||
private static final NamespacedId END_CRYSTAL_BEAM = new NamespacedId("minecraft", "end_crystal_beam");
|
||||
|
||||
@Unique
|
||||
private static final NamespacedId DRAGON_DEATH_RAY = new NamespacedId("minecraft", "dragon_death_rays");
|
||||
|
||||
@Unique
|
||||
private boolean angelica$beamScope;
|
||||
|
||||
@Unique
|
||||
private int angelica$depthPassReplay = 0;
|
||||
|
||||
@Invoker("renderEquippedItems")
|
||||
protected abstract void angelica$invokeRenderEquippedItems(EntityDragon dragon, float partialTicks);
|
||||
|
||||
@Inject(
|
||||
@Surround(
|
||||
id = "crystalBeam",
|
||||
method = "doRender(Lnet/minecraft/entity/boss/EntityDragon;DDDFF)V",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glPushMatrix()V", ordinal = 0, shift = At.Shift.AFTER, remap = false)
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I")
|
||||
)
|
||||
private void iris$setBeamEntityId(EntityDragon dragon, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) {
|
||||
// Only set the ID if the dragon is being healed by a crystal
|
||||
if (dragon.healingEnderCrystal != null) {
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
|
||||
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(END_CRYSTAL_BEAM);
|
||||
angelica$beamScope = true;
|
||||
}
|
||||
private void iris$beginCrystalBeam() {
|
||||
EnderDragonBeams.beginCrystalBeam();
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/boss/EntityDragon;DDDFF)V",
|
||||
at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glPopMatrix()V", ordinal = 0, shift = At.Shift.BEFORE, remap = false)
|
||||
)
|
||||
private void iris$restoreEntityId(EntityDragon dragon, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) {
|
||||
if (angelica$beamScope) {
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
|
||||
angelica$beamScope = false;
|
||||
}
|
||||
@Surround.Finally("crystalBeam")
|
||||
private void iris$endCrystalBeam() {
|
||||
EnderDragonBeams.endCrystalBeam();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,9 +48,7 @@ public abstract class MixinRenderDragon {
|
||||
@Surround.Carry
|
||||
final boolean deathBeams = angelica$depthPassReplay == 0 && dragon.deathTicks > 0;
|
||||
if (deathBeams) {
|
||||
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.LIGHTNING);
|
||||
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
|
||||
CapturedRenderingState.INSTANCE.setCurrentNamedEntity(DRAGON_DEATH_RAY);
|
||||
EnderDragonBeams.beginDeathRays();
|
||||
|
||||
angelica$depthPassReplay++;
|
||||
GLStateManager.glColorMask(false, false, false, false);
|
||||
@@ -114,8 +88,6 @@ public abstract class MixinRenderDragon {
|
||||
|
||||
@Surround.Finally("deathBeams")
|
||||
private void angelica$endDeathBeamsLighting(@Surround.Carry boolean deathBeams) {
|
||||
if (!deathBeams) return;
|
||||
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
|
||||
GbufferPrograms.teardownSpecialRenderCondition();
|
||||
if (deathBeams) EnderDragonBeams.endDeathRays();
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CompositeVertexWriteExtractionTest {
|
||||
|
||||
// Shaped like Euphoria Patches' End prepare pass
|
||||
static final String PREPARE_VERTEX = """
|
||||
#version 130
|
||||
#extension GL_ARB_shader_image_load_store : enable
|
||||
uniform float frameTime;
|
||||
layout(r32i) uniform iimage2D endcrystal_img;
|
||||
varying vec2 texCoord;
|
||||
void main() {
|
||||
texCoord = gl_MultiTexCoord0.xy;
|
||||
gl_Position = ftransform();
|
||||
vec4 position0 = ftransform();
|
||||
if (position0.x < 0.0 && position0.y > 0.0) {
|
||||
for (int index = 0; index < 20; index++) {
|
||||
int state = imageLoad(endcrystal_img, ivec2(index, 8)).r - max(1, int(10000.0 * frameTime));
|
||||
int previousWeight = imageAtomicCompSwap(endcrystal_img, ivec2(index, 8), 0, -1);
|
||||
imageStore(endcrystal_img, ivec2(index, 5), ivec4(state + previousWeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
static final String PREPARE_FRAGMENT = """
|
||||
#version 130
|
||||
varying vec2 texCoord;
|
||||
void main() { gl_FragData[0] = vec4(texCoord, 0.0, 1.0); }
|
||||
""";
|
||||
|
||||
@BeforeAll
|
||||
static void initShaderTransformer() throws Exception {
|
||||
Reflect.setStatic(RenderSystem.class, "maxGlslVersion", 460);
|
||||
ShaderTransformer.init();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void clearCaches() {
|
||||
TransformPatcher.clearCache();
|
||||
ShaderTransformer.clearCache();
|
||||
}
|
||||
|
||||
@Test
|
||||
void compositeVertexWritesRunOverTheQuadNotTerrain() {
|
||||
final Map<PatchShaderType, String> transformed = TransformPatcher.patchComposite(PREPARE_VERTEX, null, PREPARE_FRAGMENT);
|
||||
final EnumMap<PatchShaderType, String> result = new EnumMap<>(transformed);
|
||||
result.remove(PatchShaderType.COMPUTE);
|
||||
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, null, image -> true);
|
||||
|
||||
final String compute = result.get(PatchShaderType.COMPUTE);
|
||||
assertEquals(RwImageStoreExtractor.RwExtractMode.COMPOSITE_VSH, RwImageStoreExtractor.parseSentinel(compute));
|
||||
assertTrue(compute.contains("iris_Vertex = _vg_quad[vid];"), compute);
|
||||
assertTrue(compute.contains("iris_MultiTexCoord0 = _vg_quad[vid];"), compute);
|
||||
assertFalse(compute.contains("location"), "a location on a former interface variable does not compile in compute");
|
||||
assertEquals(1, compute.split("vec4 iris_ftransform", -1).length - 1, "the transform's own iris_ftransform is the only one");
|
||||
assertTrue(compute.contains("#define gl_Position _vg_sink_pos"), compute);
|
||||
}
|
||||
|
||||
@Test
|
||||
void keptAtomicDropsThePrePassWithoutBackendSupport() {
|
||||
final EnumMap<PatchShaderType, String> result = new EnumMap<>(TransformPatcher.patchComposite(PREPARE_VERTEX, null, PREPARE_FRAGMENT));
|
||||
result.remove(PatchShaderType.COMPUTE);
|
||||
|
||||
// The CompSwap's result is used, so it stays a real atomic, which native Metal cannot bind
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, null, image -> false);
|
||||
|
||||
assertFalse(result.containsKey(PatchShaderType.COMPUTE));
|
||||
assertFalse(result.get(PatchShaderType.VERTEX).contains("imageAtomic"), result.get(PatchShaderType.VERTEX));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.InternalTextureFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.PixelFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.PixelType;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureType;
|
||||
import net.coderbot.iris.gl.image.ImageInformation;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.AdditionalAnswers.delegatesTo;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
class CustomImageAtomicsTest {
|
||||
|
||||
private RenderBackend original;
|
||||
private RenderBackend backend;
|
||||
|
||||
@BeforeEach
|
||||
void installBackend() {
|
||||
original = BackendManager.RENDER_BACKEND;
|
||||
backend = mock(RenderBackend.class, delegatesTo(original));
|
||||
doAnswer(call -> call.<Integer>getArgument(0) == 40 && call.<Integer>getArgument(1) == 10 && call.<Integer>getArgument(2) == 1).when(backend).supportsComputeImageAtomics(anyInt(), anyInt(), anyInt());
|
||||
Reflect.setStaticFinal(BackendManager.class, "RENDER_BACKEND", RenderBackend.class, backend);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void restoreBackend() {
|
||||
Reflect.setStaticFinal(BackendManager.class, "RENDER_BACKEND", RenderBackend.class, original);
|
||||
RwImageStoreExtractor.setActiveCustomImages(null);
|
||||
}
|
||||
|
||||
private static ImageInformation image(String name, int width, int height, boolean relative) {
|
||||
return new ImageInformation(name, name + "_sampler", TextureType.TEXTURE_2D, PixelFormat.RED_INTEGER, InternalTextureFormat.R32I, PixelType.INT, width, height, 1, false, relative, relative ? 1.0f : 0.0f, relative ? 1.0f : 0.0f);
|
||||
}
|
||||
|
||||
private static Map<String, ImageInformation> images() {
|
||||
return Map.of("small_img", image("small_img", 40, 10, false), "large_img", image("large_img", 64, 64, false), "screen_img", image("screen_img", 40, 10, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyImagesTheBackendAcceptsBySizeKeepAtomics() {
|
||||
doReturn(false).when(backend).supportsComputeImageAtomics();
|
||||
RwImageStoreExtractor.setActiveCustomImages(images());
|
||||
|
||||
assertTrue(RwImageStoreExtractor.supportsAtomics("small_img"));
|
||||
assertFalse(RwImageStoreExtractor.supportsAtomics("large_img"));
|
||||
assertFalse(RwImageStoreExtractor.supportsAtomics("screen_img"));
|
||||
assertFalse(RwImageStoreExtractor.supportsAtomics("colortex0"));
|
||||
assertEquals("small_img", RwImageStoreExtractor.atomicCustomImagesKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void backendWithImageAtomicsEverywhereNeedsNoPerImageList() {
|
||||
doReturn(true).when(backend).supportsComputeImageAtomics();
|
||||
RwImageStoreExtractor.setActiveCustomImages(images());
|
||||
|
||||
assertTrue(RwImageStoreExtractor.supportsAtomics("small_img"));
|
||||
assertTrue(RwImageStoreExtractor.supportsAtomics("large_img"));
|
||||
assertTrue(RwImageStoreExtractor.supportsAtomics("screen_img"));
|
||||
assertTrue(RwImageStoreExtractor.supportsAtomics("colortex0"));
|
||||
assertEquals("", RwImageStoreExtractor.atomicCustomImagesKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearingTheImagesClearsTheList() {
|
||||
doReturn(false).when(backend).supportsComputeImageAtomics();
|
||||
RwImageStoreExtractor.setActiveCustomImages(images());
|
||||
RwImageStoreExtractor.setActiveCustomImages(null);
|
||||
|
||||
assertFalse(RwImageStoreExtractor.supportsAtomics("small_img"));
|
||||
assertEquals("", RwImageStoreExtractor.atomicCustomImagesKey());
|
||||
}
|
||||
}
|
||||
@@ -246,7 +246,7 @@ class ShaderTransformerTest {
|
||||
|
||||
final EnumMap<PatchShaderType, String> result = new EnumMap<>(out);
|
||||
final String fragmentBefore = result.get(PatchShaderType.FRAGMENT);
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, artifacts);
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, artifacts, image -> true);
|
||||
|
||||
assertNotNull(result.get(PatchShaderType.COMPUTE), "fixture must actually exercise the extraction");
|
||||
assertFalse(fragmentBefore.equals(result.get(PatchShaderType.FRAGMENT)), "fixture must actually rewrite the fragment stage");
|
||||
@@ -263,7 +263,7 @@ class ShaderTransformerTest {
|
||||
assertNotNull(out);
|
||||
|
||||
final EnumMap<PatchShaderType, String> result = new EnumMap<>(out);
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, artifacts);
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.COMPOSITE, artifacts, image -> true);
|
||||
|
||||
assertFalse(artifacts.isEmpty(), "fixture must leave at least one artifact to check");
|
||||
for (Map.Entry<PatchShaderType, ShaderTransformer.StageArtifact> e : artifacts.entrySet()) {
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
package net.coderbot.iris.pipeline.transform;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import net.coderbot.iris.gbuffer_overrides.matching.InputAvailability;
|
||||
import net.coderbot.iris.pipeline.transform.RwImageStoreExtractor.VertexInput;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class VertexReplayExtractionTest {
|
||||
|
||||
// Shaped like Euphoria Patches' end crystal voxelization in its shadow program
|
||||
private static final String CRYSTAL_VERTEX = """
|
||||
#version 130
|
||||
#extension GL_ARB_shader_image_load_store : enable
|
||||
uniform int entityId;
|
||||
uniform mat4 shadowModelViewInverse;
|
||||
uniform mat4 shadowProjectionInverse;
|
||||
attribute vec4 mc_Entity;
|
||||
layout(r32i) uniform iimage2D endcrystal_img;
|
||||
varying vec2 texCoord;
|
||||
void UpdateEndCrystalMap(vec3 pos) {
|
||||
int packedPos = int(pos.x + 512.5);
|
||||
for (int k = 0; k < 20; k++) {
|
||||
int registeredPos = imageAtomicCompSwap(endcrystal_img, ivec2(k, 0), 0, packedPos);
|
||||
if (registeredPos == 0 || registeredPos == packedPos) {
|
||||
imageAtomicAdd(endcrystal_img, ivec2(k, 1), int(10.0 * pos.x));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void main() {
|
||||
texCoord = gl_MultiTexCoord0.xy;
|
||||
vec3 normal = normalize(gl_NormalMatrix * gl_Normal);
|
||||
vec4 position = shadowModelViewInverse * shadowProjectionInverse * ftransform();
|
||||
if (entityId == 50000 && abs(normal.y) > 0.5 && mc_Entity.x < 0.0) UpdateEndCrystalMap(position.xyz);
|
||||
gl_Position = ftransform();
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String FRAGMENT = """
|
||||
#version 130
|
||||
uniform sampler2D tex;
|
||||
varying vec2 texCoord;
|
||||
void main() { gl_FragData[0] = texture2D(tex, texCoord); }
|
||||
""";
|
||||
|
||||
private static final InputAvailability TEX_LM = InputAvailability.of(true, true);
|
||||
|
||||
@BeforeAll
|
||||
static void initShaderTransformer() throws Exception {
|
||||
Reflect.setStatic(RenderSystem.class, "maxGlslVersion", 460);
|
||||
ShaderTransformer.init();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void clearCaches() {
|
||||
TransformPatcher.clearCache();
|
||||
ShaderTransformer.clearCache();
|
||||
}
|
||||
|
||||
private static RwImageStoreExtractor.Result replayOf(Map<PatchShaderType, String> transformed) {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay(transformed.get(PatchShaderType.VERTEX));
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.computeSource(), "every input of an Iris gbuffers vertex shader is replayable");
|
||||
return result;
|
||||
}
|
||||
|
||||
private static VertexInput input(List<VertexInput> inputs, String name) {
|
||||
return inputs.stream().filter(i -> i.name().equals(name)).findFirst()
|
||||
.orElseThrow(() -> new AssertionError(name + " missing from " + inputs));
|
||||
}
|
||||
|
||||
@Test
|
||||
void replayKeepsWritesAtomicAndRasterDropsThem() {
|
||||
final RwImageStoreExtractor.Result result = replayOf(TransformPatcher.patchAttributes(CRYSTAL_VERTEX, null, FRAGMENT, TEX_LM));
|
||||
|
||||
assertEquals(RwImageStoreExtractor.RwExtractMode.VERTEX_REPLAY, RwImageStoreExtractor.parseSentinel(result.computeSource()));
|
||||
assertFalse(result.strippedSource().contains("imageAtomic"), result.strippedSource());
|
||||
assertFalse(result.strippedSource().contains("endcrystal_img"), result.strippedSource());
|
||||
// Many vertices of one crystal add into the same texels at once
|
||||
assertTrue(result.computeSource().contains("imageAtomicAdd"), result.computeSource());
|
||||
assertFalse(result.computeSource().contains("_vg_prev"), result.computeSource());
|
||||
assertTrue(result.computeSource().contains("layout(binding = 0, r32i) uniform iimage2D endcrystal_img;"), result.computeSource());
|
||||
assertEquals(List.of("endcrystal_img"), RwImageStoreExtractor.parseVertexReplayImages(result.computeSource()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void replayFetchesEveryDeclaredInput() {
|
||||
final RwImageStoreExtractor.Result result = replayOf(TransformPatcher.patchAttributes(CRYSTAL_VERTEX, null, FRAGMENT, TEX_LM));
|
||||
final List<VertexInput> inputs = RwImageStoreExtractor.parseVertexReplayInputs(result.computeSource());
|
||||
|
||||
assertEquals("vec4", input(inputs, "mc_Entity").glslType());
|
||||
for (int k = 0; k < inputs.size(); k++) {
|
||||
final VertexInput in = inputs.get(k);
|
||||
assertTrue(result.computeSource().contains(in.name() + " = " + in.glslType() + "("), "no fetch for " + in.name());
|
||||
assertTrue(result.computeSource().contains("(" + k + "u, _vg_vertex, _vg_i)"), "input " + in.name() + " is not fetched from slot " + k);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void instancedVariantReadsEntityIdFromItsInstanceAttribute() {
|
||||
final RwImageStoreExtractor.Result result = replayOf(TransformPatcher.patchAttributesInstanced(CRYSTAL_VERTEX, null, null, null, FRAGMENT, TEX_LM, false, Instancing.TEMPLATE));
|
||||
final List<VertexInput> inputs = RwImageStoreExtractor.parseVertexReplayInputs(result.computeSource());
|
||||
|
||||
final VertexInput entity = input(inputs, "iris_Entity");
|
||||
assertEquals(InstancedAttribs.LOC_ENTITY, entity.location());
|
||||
assertEquals("uvec4", entity.glslType());
|
||||
assertTrue(result.computeSource().contains("iris_Entity = uvec4(_vg_uint("), result.computeSource());
|
||||
assertFalse(result.computeSource().contains("location"), "a location on a former interface variable does not compile in compute");
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyDeclaratorOfAnInputListIsFetched() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
in vec3 position, normal;
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(normal.xy), uvec4(1u));
|
||||
gl_Position = vec4(position, 1.0);
|
||||
}
|
||||
""");
|
||||
assertNotNull(result.computeSource());
|
||||
final List<VertexInput> inputs = RwImageStoreExtractor.parseVertexReplayInputs(result.computeSource());
|
||||
assertEquals(List.of("position", "normal"), inputs.stream().map(VertexInput::name).toList());
|
||||
assertFalse(result.computeSource().contains("in vec3"), "a stage input does not compile in compute\n" + result.computeSource());
|
||||
assertTrue(result.computeSource().contains("normal = vec3(_vg_float(1u, _vg_vertex, _vg_i));"), result.computeSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
void samplerOnlyTheWriteReadsIsListedForThePipeline() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
in vec4 iris_Vertex;
|
||||
uniform sampler2D noisetex;
|
||||
layout(rgba8) uniform image2D marks;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(0), textureLod(noisetex, iris_Vertex.xy, 0.0));
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
assertFalse(result.strippedSource().contains("textureLod"), result.strippedSource());
|
||||
assertEquals(List.of("noisetex"), RwImageStoreExtractor.parseVertexReplaySamplers(result.computeSource()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readOnlyImagesBindAfterTheWrittenOnes() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
in vec4 iris_Vertex;
|
||||
layout(rgba8) uniform image2D source;
|
||||
layout(rgba8) uniform image2D target;
|
||||
void main() {
|
||||
imageStore(target, ivec2(0), imageLoad(source, ivec2(iris_Vertex.xy)));
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
final String compute = result.computeSource();
|
||||
assertEquals(List.of("target"), RwImageStoreExtractor.parseVertexReplayImages(compute));
|
||||
assertEquals(List.of("source"), RwImageStoreExtractor.parseVertexReplayReadImages(compute));
|
||||
assertTrue(compute.contains("layout(binding = 0, rgba8) writeonly uniform image2D target;"), compute);
|
||||
assertTrue(compute.contains("layout(binding = 1, rgba8) readonly uniform image2D source;"), compute);
|
||||
assertEquals(1, compute.split("image2D source", -1).length - 1, compute);
|
||||
}
|
||||
|
||||
@Test
|
||||
void outParametersOfHelpersStayOutParameters() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
in vec4 iris_Vertex;
|
||||
out vec2 texCoord;
|
||||
layout(r32f) uniform image2D marks;
|
||||
void fill(out float value) { value = iris_Vertex.x; }
|
||||
void main() {
|
||||
float v = 0.0;
|
||||
fill(v);
|
||||
imageStore(marks, ivec2(0), vec4(v));
|
||||
texCoord = iris_Vertex.xy;
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
final String compute = result.computeSource().replaceAll("\\s+", " ");
|
||||
assertTrue(compute.contains("void fill ( out float value )"), compute);
|
||||
assertFalse(compute.contains("out vec2 texCoord"), compute);
|
||||
}
|
||||
|
||||
@Test
|
||||
void outputBlocksBecomeGlobalsAndLayoutDefaultsStay() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
layout(std430) buffer;
|
||||
in vec4 iris_Vertex;
|
||||
out gl_PerVertex { vec4 gl_Position; };
|
||||
out Outputs { flat int id; vec2 uv; } v;
|
||||
out Extra { float fade; };
|
||||
buffer Marks { uint counts[]; };
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
void main() {
|
||||
v.uv = iris_Vertex.xy;
|
||||
fade = iris_Vertex.z;
|
||||
imageStore(marks, ivec2(v.uv), uvec4(counts[0] + uint(fade)));
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
assertNotNull(result);
|
||||
final String compute = result.computeSource().replaceAll("\\s+", " ");
|
||||
assertTrue(compute.contains("layout ( std430 ) buffer ;"), compute);
|
||||
assertFalse(compute.contains("gl_PerVertex"), compute);
|
||||
assertTrue(compute.contains("struct Outputs { int id ; vec2 uv ; }; Outputs v ;"), compute);
|
||||
assertTrue(compute.contains("float fade ;"), compute);
|
||||
assertFalse(compute.contains("Extra"), compute);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveRestartMarkersAreNotReplayedAsVertices() {
|
||||
final String compute = replayOf(TransformPatcher.patchAttributes(CRYSTAL_VERTEX, null, FRAGMENT, TEX_LM)).computeSource();
|
||||
final int skip = compute.indexOf("if (_vg_restart.x != 0u && _vg_index_value == _vg_restart.y) return;");
|
||||
assertTrue(skip >= 0, compute);
|
||||
assertTrue(skip < compute.indexOf("_vg_vertex = _vg_index_value + uint(_vg_draw.w);"), compute);
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomicReplayIsDroppedWithoutBackendSupport() {
|
||||
final EnumMap<PatchShaderType, String> atomic = new EnumMap<>(PatchShaderType.class);
|
||||
atomic.put(PatchShaderType.VERTEX, TransformPatcher.patchAttributes(CRYSTAL_VERTEX, null, FRAGMENT, TEX_LM).get(PatchShaderType.VERTEX));
|
||||
ShaderTransformer.extractRwImageStores(atomic, Patch.ATTRIBUTES, null, image -> false);
|
||||
assertFalse(atomic.containsKey(PatchShaderType.COMPUTE));
|
||||
assertFalse(atomic.get(PatchShaderType.VERTEX).contains("imageAtomic"), atomic.get(PatchShaderType.VERTEX));
|
||||
|
||||
final EnumMap<PatchShaderType, String> plain = new EnumMap<>(PatchShaderType.class);
|
||||
plain.put(PatchShaderType.VERTEX, """
|
||||
#version 460 core
|
||||
in vec4 iris_Vertex;
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(0), uvec4(1u));
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
ShaderTransformer.extractRwImageStores(plain, Patch.ATTRIBUTES, null, image -> false);
|
||||
assertTrue(plain.containsKey(PatchShaderType.COMPUTE), "plain stores need no atomics, so the replay stays");
|
||||
}
|
||||
|
||||
@Test
|
||||
void atomicReplayFollowsTheImageItWrites() {
|
||||
final String vertex = TransformPatcher.patchAttributes(CRYSTAL_VERTEX, null, FRAGMENT, TEX_LM).get(PatchShaderType.VERTEX);
|
||||
|
||||
final EnumMap<PatchShaderType, String> supported = new EnumMap<>(PatchShaderType.class);
|
||||
supported.put(PatchShaderType.VERTEX, vertex);
|
||||
ShaderTransformer.extractRwImageStores(supported, Patch.ATTRIBUTES, null, "endcrystal_img"::equals);
|
||||
assertTrue(supported.containsKey(PatchShaderType.COMPUTE));
|
||||
|
||||
final EnumMap<PatchShaderType, String> unsupported = new EnumMap<>(PatchShaderType.class);
|
||||
unsupported.put(PatchShaderType.VERTEX, vertex);
|
||||
ShaderTransformer.extractRwImageStores(unsupported, Patch.ATTRIBUTES, null, "voxel_img"::equals);
|
||||
assertFalse(unsupported.containsKey(PatchShaderType.COMPUTE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void matrixInputIsNotReplayed() {
|
||||
final RwImageStoreExtractor.Result result = RwImageStoreExtractor.tryExtractVertexReplay("""
|
||||
#version 460 core
|
||||
in mat4 instanceMatrix;
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(0), uvec4(1u));
|
||||
gl_Position = instanceMatrix[0];
|
||||
}
|
||||
""");
|
||||
assertNotNull(result);
|
||||
assertNull(result.computeSource());
|
||||
assertFalse(result.strippedSource().contains("imageStore"), result.strippedSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
void attributesProgramKeepsVertexReplayAndDropsFragmentWrites() {
|
||||
final EnumMap<PatchShaderType, String> result = new EnumMap<>(PatchShaderType.class);
|
||||
result.put(PatchShaderType.VERTEX, """
|
||||
#version 460 core
|
||||
in vec4 iris_Vertex;
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(0), uvec4(1u));
|
||||
gl_Position = iris_Vertex;
|
||||
}
|
||||
""");
|
||||
result.put(PatchShaderType.FRAGMENT, """
|
||||
#version 460 core
|
||||
layout(r32ui) uniform uimage2D marks;
|
||||
out vec4 color;
|
||||
void main() {
|
||||
imageStore(marks, ivec2(1), uvec4(2u));
|
||||
color = vec4(1.0);
|
||||
}
|
||||
""");
|
||||
|
||||
ShaderTransformer.extractRwImageStores(result, Patch.ATTRIBUTES, null, image -> true);
|
||||
|
||||
assertNotNull(result.get(PatchShaderType.COMPUTE));
|
||||
assertFalse(result.get(PatchShaderType.VERTEX).contains("imageStore"));
|
||||
assertFalse(result.get(PatchShaderType.FRAGMENT).contains("imageStore"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user