Lyft shaders

- The ubershaders actually compile, but uniforms/material ids are broken
 - Convert vertex/fragment adapter components into generic builder
 - Support arbitrary adapted function signatures
 - Make an attempt at cleaning up generation code
This commit is contained in:
Jozufozu 2022-10-13 18:00:59 -07:00
parent 7a080a5538
commit ef568d22f7
20 changed files with 520 additions and 342 deletions

View file

@ -1,5 +1,6 @@
package com.jozufozu.flywheel.backend;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@ -60,6 +61,7 @@ public class Backend {
return TYPE != BackendTypes.OFF;
}
@Contract("null -> false")
public static boolean canUseInstancing(@Nullable Level level) {
return isOn() && isFlywheelLevel(level);
}

View file

@ -10,6 +10,7 @@ import java.util.stream.Collectors;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.context.ContextShader;
import com.jozufozu.flywheel.api.pipeline.Pipeline;
import com.jozufozu.flywheel.api.struct.StructType;
@ -28,6 +29,8 @@ import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.pipeline.SimplePipeline;
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.source.ShaderSources;
import com.jozufozu.flywheel.core.source.generate.FnSignature;
import com.jozufozu.flywheel.core.source.generate.GlslExpr;
import com.jozufozu.flywheel.util.StringUtil;
public class FlwCompiler {
@ -36,11 +39,10 @@ public class FlwCompiler {
final long compileStart = System.nanoTime();
private final ShaderSources sources;
private final VertexMaterialComponent vertexMaterialComponent;
private final FragmentMaterialComponent fragmentMaterialComponent;
private final MaterialAdapterComponent vertexMaterialComponent;
private final MaterialAdapterComponent fragmentMaterialComponent;
private final List<PipelineContext> pipelineContexts;
final ShaderCompiler shaderCompiler;
final Multimap<Set<UniformProvider>, PipelineContext> uniformProviderGroups = ArrayListMultimap.create();
final Map<PipelineContext, GlProgram> pipelinePrograms = new HashMap<>();
@ -50,8 +52,26 @@ public class FlwCompiler {
public FlwCompiler(ShaderSources sources) {
this.shaderCompiler = new ShaderCompiler(errors::add);
this.sources = sources;
this.vertexMaterialComponent = new VertexMaterialComponent(sources, ComponentRegistry.materials.vertexSources());
this.fragmentMaterialComponent = new FragmentMaterialComponent(sources, ComponentRegistry.materials.fragmentSources());
this.vertexMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("vertex_material_adapter"))
.materialSources(ComponentRegistry.materials.vertexSources())
.adapt(FnSignature.ofVoid("flw_materialVertex"))
.switchOn(GlslExpr.variable("flw_materialVertexID"))
.build(sources);
this.fragmentMaterialComponent = MaterialAdapterComponent.builder(Flywheel.rl("fragment_material_adapter"))
.materialSources(ComponentRegistry.materials.fragmentSources())
.adapt(FnSignature.ofVoid("flw_materialFragment"))
.adapt(FnSignature.create()
.returnType("bool")
.name("flw_discardPredicate")
.arg("vec4", "color")
.build(), GlslExpr.literal(false))
.adapt(FnSignature.create()
.returnType("vec4")
.name("flw_fogFilter")
.arg("vec4", "color")
.build(), GlslExpr.variable("color"))
.switchOn(GlslExpr.variable("flw_materialFragmentID"))
.build(sources);
this.pipelineContexts = buildPipelineSet();

View file

@ -1,29 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.util.List;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.ShaderSources;
import com.jozufozu.flywheel.core.source.generate.GlslExpr;
import net.minecraft.resources.ResourceLocation;
public class FragmentMaterialComponent extends MaterialAdapterComponent {
private static final String flw_materialFragment = "flw_materialFragment";
private static final String flw_discardPredicate = "flw_discardPredicate";
private static final String flw_fogFilter = "flw_fogFilter";
private static final List<String> adaptedFunctions = List.of(flw_materialFragment, flw_discardPredicate, flw_fogFilter);
private static final GlslExpr flw_materialFragmentID = GlslExpr.variable(flw_materialFragment + "ID");
public FragmentMaterialComponent(ShaderSources sources, List<FileResolution> sourceMaterials) {
super(sources, sourceMaterials, flw_materialFragmentID, adaptedFunctions);
}
@Override
public ResourceLocation name() {
return Flywheel.rl("fragment_material_adapter");
}
}

View file

@ -1,87 +1,172 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import javax.annotation.Nonnull;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.ShaderSources;
import com.jozufozu.flywheel.core.source.generate.FnSignature;
import com.jozufozu.flywheel.core.source.generate.GlslBlock;
import com.jozufozu.flywheel.core.source.generate.GlslBuilder;
import com.jozufozu.flywheel.core.source.generate.GlslExpr;
import com.jozufozu.flywheel.core.source.generate.GlslSwitch;
import com.jozufozu.flywheel.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
public abstract class MaterialAdapterComponent implements SourceComponent {
public class MaterialAdapterComponent implements SourceComponent {
// TODO: material id handling in pipeline shader
private final ResourceLocation name;
private final GlslExpr switchArg;
private final List<String> adaptedFunctions;
private final List<RenamedFunctionsSourceComponent> transformedMaterials;
private final List<AdaptedFn> functionsToAdapt;
private final List<RenamedFunctionsSourceComponent> adaptedComponents;
// TODO: Create builder and remove Fragment* and Vertex* classes
public MaterialAdapterComponent(ShaderSources sources, List<FileResolution> sourceMaterials, GlslExpr switchArg, List<String> adaptedFunctions) {
public MaterialAdapterComponent(ResourceLocation name, GlslExpr switchArg, List<AdaptedFn> functionsToAdapt, List<RenamedFunctionsSourceComponent> adaptedComponents) {
this.name = name;
this.switchArg = switchArg;
this.adaptedFunctions = adaptedFunctions;
this.functionsToAdapt = functionsToAdapt;
this.adaptedComponents = adaptedComponents;
}
var transformed = ImmutableList.<RenamedFunctionsSourceComponent>builder();
public static Builder builder(ResourceLocation name) {
return new Builder(name);
}
for (FileResolution fileResolution : sourceMaterials) {
var loc = fileResolution.resourceLocation();
var sourceFile = sources.find(loc);
transformed.add(new RenamedFunctionsSourceComponent(sourceFile, createAdapterMap(adaptedFunctions, loc)));
}
this.transformedMaterials = transformed.build();
@Override
public ResourceLocation name() {
return name;
}
@Override
public Collection<? extends SourceComponent> included() {
return transformedMaterials;
return adaptedComponents;
}
@Override
public String source() {
var builder = new GlslBuilder();
for (String adaptedFunction : adaptedFunctions) {
// TODO: support different function signatures
for (var adaptedFunction : functionsToAdapt) {
builder.function()
.returnType("void")
.name(adaptedFunction)
.signature(adaptedFunction.signature())
.body(body -> generateAdapter(body, adaptedFunction));
}
return builder.build();
}
private void generateAdapter(GlslBuilder.BlockBuilder body, String adaptedFunction) {
var sw = new GlslBuilder.SwitchBuilder(switchArg);
for (int i = 0; i < transformedMaterials.size(); i++) {
var variant = transformedMaterials.get(i)
.replacement(adaptedFunction);
private void generateAdapter(GlslBlock body, AdaptedFn adaptedFunction) {
var sw = GlslSwitch.on(switchArg);
var fnSignature = adaptedFunction.signature();
var fnName = fnSignature.name();
var isVoid = fnSignature.isVoid();
var fnArgs = fnSignature.createArgExpressions();
sw.case_(i, b -> b.eval(GlslExpr.call(variant))
.break_());
for (int i = 0; i < adaptedComponents.size(); i++) {
var component = adaptedComponents.get(i);
if (!component.replaces(fnName)) {
continue;
}
var adaptedCall = GlslExpr.call(component.remapFnName(fnName), fnArgs);
var block = GlslBlock.create();
if (isVoid) {
block.eval(adaptedCall)
.breakStmt();
} else {
block.ret(adaptedCall);
}
sw.intCase(i, block);
}
body.add(sw.build());
if (!isVoid) {
var defaultReturn = adaptedFunction.defaultReturn;
if (defaultReturn == null) {
throw new IllegalStateException("Function " + fnName + " is not void, but no default return value was provided");
}
sw.defaultCase(GlslBlock.create()
.ret(defaultReturn));
}
body.add(sw);
}
@NotNull
private static HashMap<String, String> createAdapterMap(List<String> adaptedFunctions, ResourceLocation loc) {
private static HashMap<String, String> createAdapterMap(List<AdaptedFn> adaptedFunctions, ResourceLocation loc) {
HashMap<String, String> out = new HashMap<>();
var suffix = '_' + ResourceUtil.toSafeString(loc);
for (String fnName : adaptedFunctions) {
for (var adapted : adaptedFunctions) {
var fnName = adapted.signature()
.name();
out.put(fnName, fnName + suffix);
}
return out;
}
private record AdaptedFn(FnSignature signature, @Nullable GlslExpr defaultReturn) {
}
public static class Builder {
private final ResourceLocation name;
private final List<FileResolution> sourceMaterials = new ArrayList<>();
private final List<AdaptedFn> adaptedFunctions = new ArrayList<>();
private GlslExpr switchArg;
public Builder(ResourceLocation name) {
this.name = name;
}
public Builder materialSources(List<FileResolution> sources) {
this.sourceMaterials.addAll(sources);
return this;
}
public Builder adapt(FnSignature function) {
adaptedFunctions.add(new AdaptedFn(function, null));
return this;
}
public Builder adapt(FnSignature function, @Nonnull GlslExpr defaultReturn) {
adaptedFunctions.add(new AdaptedFn(function, defaultReturn));
return this;
}
public Builder switchOn(GlslExpr expr) {
this.switchArg = expr;
return this;
}
public MaterialAdapterComponent build(ShaderSources sources) {
if (switchArg == null) {
throw new NullPointerException("Switch argument must be set");
}
var transformed = ImmutableList.<RenamedFunctionsSourceComponent>builder();
for (FileResolution fileResolution : sourceMaterials) {
var loc = fileResolution.resourceLocation();
var sourceFile = sources.find(loc);
transformed.add(new RenamedFunctionsSourceComponent(sourceFile, createAdapterMap(adaptedFunctions, loc)));
}
return new MaterialAdapterComponent(name, switchArg, adaptedFunctions, transformed.build());
}
}
}

View file

@ -11,24 +11,29 @@ import net.minecraft.resources.ResourceLocation;
public final class RenamedFunctionsSourceComponent implements SourceComponent {
private final SourceComponent source;
private final Map<String, String> replacements;
private final String sourceString;
public RenamedFunctionsSourceComponent(SourceComponent source, String find, String replace) {
this.source = source;
this.replacements = Map.of(find, replace);
this(source, Map.of(find, replace));
}
public RenamedFunctionsSourceComponent(SourceComponent source, Map<String, String> replacements) {
this.source = source;
this.replacements = replacements;
this.sourceString = source.source();
}
public String replacement(String name) {
public String remapFnName(String name) {
return replacements.getOrDefault(name, name);
}
public boolean replaces(String name) {
return replacements.containsKey(name) && sourceString.contains(name);
}
@Override
public String source() {
var source = this.source.source();
var source = sourceString;
for (var entry : replacements.entrySet()) {
source = source.replace(entry.getKey(), entry.getValue());

View file

@ -1,27 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.util.List;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.ShaderSources;
import com.jozufozu.flywheel.core.source.generate.GlslExpr;
import net.minecraft.resources.ResourceLocation;
public class VertexMaterialComponent extends MaterialAdapterComponent {
private static final String flw_materialVertex = "flw_materialVertex";
private static final List<String> adaptedFunctions = List.of(flw_materialVertex);
private static final GlslExpr flw_materialVertexID = GlslExpr.variable(flw_materialVertex + "ID");
public VertexMaterialComponent(ShaderSources sources, List<FileResolution> sourceMaterials) {
super(sources, sourceMaterials, flw_materialVertexID, adaptedFunctions);
}
@Override
public ResourceLocation name() {
return Flywheel.rl("vertex_material_adapter");
}
}

View file

@ -12,6 +12,8 @@ import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.layout.LayoutItem;
import com.jozufozu.flywheel.core.source.ShaderSources;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.generate.FnSignature;
import com.jozufozu.flywheel.core.source.generate.GlslBlock;
import com.jozufozu.flywheel.core.source.generate.GlslBuilder;
import com.jozufozu.flywheel.core.source.generate.GlslExpr;
@ -21,6 +23,8 @@ public class IndirectComponent implements SourceComponent {
private static final String UNPACK_ARG = "p";
private static final GlslExpr.Variable UNPACKING_VARIABLE = GlslExpr.variable(UNPACK_ARG);
private static final String STRUCT_NAME = "IndirectStruct";
private static final String PACKED_STRUCT_NAME = STRUCT_NAME + "_packed";
private final List<LayoutItem> layoutItems;
private final ImmutableList<SourceFile> included;
@ -46,20 +50,19 @@ public class IndirectComponent implements SourceComponent {
@Override
public String source() {
return generateIndirect("IndirectStruct");
return generateIndirect();
}
public String generateIndirect(String structName) {
public String generateIndirect() {
var builder = new GlslBuilder();
final var packedStructName = structName + "_packed";
builder.define("FlwInstance", structName);
builder.define("FlwPackedInstance", packedStructName);
builder.define("FlwInstance", STRUCT_NAME);
builder.define("FlwPackedInstance", PACKED_STRUCT_NAME);
var packed = builder.struct();
builder.blankLine();
var instance = builder.struct();
packed.setName(packedStructName);
instance.setName(structName);
packed.setName(PACKED_STRUCT_NAME);
instance.setName(STRUCT_NAME);
for (var field : layoutItems) {
field.addPackedToStruct(packed);
@ -69,13 +72,20 @@ public class IndirectComponent implements SourceComponent {
builder.blankLine();
builder.function()
.returnType(structName)
.name("flw_unpackInstance")
.argumentIn(packedStructName, UNPACK_ARG)
.body(b -> b.ret(GlslExpr.call(structName, layoutItems.stream()
.map(layoutItem -> layoutItem.unpackField(UNPACKING_VARIABLE))
.toList())));
.signature(FnSignature.create()
.returnType(STRUCT_NAME)
.name("flw_unpackInstance")
.arg(PACKED_STRUCT_NAME, UNPACK_ARG)
.build())
.body(this::generateUnpackingBody);
return builder.build();
}
private void generateUnpackingBody(GlslBlock b) {
var unpackedFields = layoutItems.stream()
.map(layoutItem -> layoutItem.unpackField(UNPACKING_VARIABLE))
.toList();
b.ret(GlslExpr.call(STRUCT_NAME, unpackedFields));
}
}

View file

@ -8,6 +8,8 @@ import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.pipeline.Pipeline;
import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.layout.LayoutItem;
import com.jozufozu.flywheel.core.source.generate.FnSignature;
import com.jozufozu.flywheel.core.source.generate.GlslBlock;
import com.jozufozu.flywheel.core.source.generate.GlslBuilder;
import com.jozufozu.flywheel.core.source.generate.GlslExpr;
@ -15,6 +17,7 @@ import net.minecraft.resources.ResourceLocation;
public class InstancedArraysComponent implements SourceComponent {
private static final String ATTRIBUTE_SUFFIX = "_vertex_in";
private static final String STRUCT_NAME = "Instance";
private final List<LayoutItem> layoutItems;
private final int baseIndex;
@ -32,19 +35,15 @@ public class InstancedArraysComponent implements SourceComponent {
return Collections.emptyList();
}
@Override
public String source() {
return generateInstancedArrays("Instance");
}
@Override
public ResourceLocation name() {
return Flywheel.rl("generated_instanced_arrays");
}
public String generateInstancedArrays(String structName) {
@Override
public String source() {
var builder = new GlslBuilder();
builder.define("FlwInstance", structName);
builder.define("FlwInstance", STRUCT_NAME);
int i = baseIndex;
for (var field : layoutItems) {
@ -61,7 +60,7 @@ public class InstancedArraysComponent implements SourceComponent {
builder.blankLine();
var structBuilder = builder.struct();
structBuilder.setName(structName);
structBuilder.setName(STRUCT_NAME);
for (var field : layoutItems) {
field.addToStruct(structBuilder);
@ -71,13 +70,16 @@ public class InstancedArraysComponent implements SourceComponent {
// unpacking function
builder.function()
.returnType(structName)
.name("flw_unpackInstance")
.body(b -> b.ret(GlslExpr.call(structName, layoutItems.stream()
.map(it -> new GlslExpr.Variable(it.name() + ATTRIBUTE_SUFFIX))
.toList())));
.signature(FnSignature.of(STRUCT_NAME, "flw_unpackInstance"))
.body(this::generateUnpackingBody);
return builder.build();
}
private void generateUnpackingBody(GlslBlock b) {
var fields = layoutItems.stream()
.map(it -> new GlslExpr.Variable(it.name() + ATTRIBUTE_SUFFIX))
.toList();
b.ret(GlslExpr.call(STRUCT_NAME, fields));
}
}

View file

@ -1,7 +1,7 @@
package com.jozufozu.flywheel.core.layout;
import com.jozufozu.flywheel.core.source.generate.GlslBuilder;
import com.jozufozu.flywheel.core.source.generate.GlslExpr;
import com.jozufozu.flywheel.core.source.generate.GlslStruct;
public record LayoutItem(InputType type, String name) {
public GlslExpr unpackField(GlslExpr.Variable struct) {
@ -9,11 +9,11 @@ public record LayoutItem(InputType type, String name) {
.transform(type()::unpack);
}
public void addToStruct(GlslBuilder.StructBuilder structBuilder) {
structBuilder.addField(type().typeName(), name());
public void addToStruct(GlslStruct glslStruct) {
glslStruct.addField(type().typeName(), name());
}
public void addPackedToStruct(GlslBuilder.StructBuilder packed) {
public void addPackedToStruct(GlslStruct packed) {
packed.addField(type().packedTypeName(), name());
}
}

View file

@ -0,0 +1,80 @@
package com.jozufozu.flywheel.core.source.generate;
import java.util.Collection;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.util.Pair;
public record FnSignature(String returnType, String name, ImmutableList<Pair<String, String>> args) {
public static Builder create() {
return new Builder();
}
public static FnSignature of(String returnType, String name) {
return FnSignature.create()
.returnType(returnType)
.name(name)
.build();
}
public static FnSignature ofVoid(String name) {
return new FnSignature("void", name, ImmutableList.of());
}
public Collection<? extends GlslExpr> createArgExpressions() {
return args.stream()
.map(Pair::second)
.map(GlslExpr::variable)
.collect(Collectors.toList());
}
public boolean isVoid() {
return "void".equals(returnType);
}
public String fullDeclaration() {
return returnType + ' ' + name + '(' + args.stream()
.map(p -> p.first() + ' ' + p.second())
.collect(Collectors.joining(", ")) + ')';
}
public String signatureDeclaration() {
return returnType + ' ' + name + '(' + args.stream()
.map(Pair::first)
.collect(Collectors.joining(", ")) + ')';
}
public static class Builder {
private String returnType;
private String name;
private final ImmutableList.Builder<Pair<String, String>> args = ImmutableList.builder();
public Builder returnType(String returnType) {
this.returnType = returnType;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Builder arg(String type, String name) {
args.add(Pair.of(type, name));
return this;
}
public FnSignature build() {
if (returnType == null) {
throw new IllegalStateException("returnType not set");
}
if (name == null) {
throw new IllegalStateException("name not set");
}
return new FnSignature(returnType, name, args.build());
}
}
}

View file

@ -0,0 +1,39 @@
package com.jozufozu.flywheel.core.source.generate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class GlslBlock {
private final List<GlslStmt> body = new ArrayList<>();
public static GlslBlock create() {
return new GlslBlock();
}
public GlslBlock add(GlslStmt stmt) {
body.add(stmt);
return this;
}
public GlslBlock eval(GlslExpr expr) {
return add(GlslStmt.eval(expr));
}
public GlslBlock ret(GlslExpr call) {
add(GlslStmt.ret(call));
return this;
}
public GlslBlock breakStmt() {
add(GlslStmt.BREAK);
return this;
}
public String prettyPrint() {
return body.stream()
.map(GlslStmt::prettyPrint)
.collect(Collectors.joining("\n"));
}
}

View file

@ -2,31 +2,28 @@ package com.jozufozu.flywheel.core.source.generate;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import com.jozufozu.flywheel.util.Pair;
public class GlslBuilder {
private final List<GlslRootElement> elements = new ArrayList<>();
private final List<Declaration> elements = new ArrayList<>();
public void define(String name, String value) {
add(new Define(name, value));
}
public StructBuilder struct() {
return add(new StructBuilder());
public GlslStruct struct() {
return add(new GlslStruct());
}
public FunctionBuilder function() {
return add(new FunctionBuilder());
public GlslFn function() {
return add(new GlslFn());
}
public VertexInputBuilder vertexInput() {
return add(new VertexInputBuilder());
public GlslVertexInput vertexInput() {
return add(new GlslVertexInput());
}
public <T extends GlslRootElement> T add(T element) {
public <T extends Declaration> T add(T element) {
elements.add(element);
return element;
}
@ -37,15 +34,15 @@ public class GlslBuilder {
public String build() {
return elements.stream()
.map(GlslRootElement::prettyPrint)
.map(Declaration::prettyPrint)
.collect(Collectors.joining("\n"));
}
public interface GlslRootElement {
public interface Declaration {
String prettyPrint();
}
public enum Separators implements GlslRootElement {
public enum Separators implements Declaration {
BLANK_LINE(""),
;
@ -61,169 +58,11 @@ public class GlslBuilder {
}
}
public record Define(String name, String value) implements GlslRootElement {
public record Define(String name, String value) implements Declaration {
@Override
public String prettyPrint() {
return "#define " + name + " " + value;
}
}
public static class VertexInputBuilder implements GlslRootElement {
private int binding;
private String type;
private String name;
public VertexInputBuilder binding(int binding) {
this.binding = binding;
return this;
}
public VertexInputBuilder type(String type) {
this.type = type;
return this;
}
public VertexInputBuilder name(String name) {
this.name = name;
return this;
}
@Override
public String prettyPrint() {
return "layout(location = " + binding + ") in " + type + " " + name + ";";
}
}
public static class StructBuilder implements GlslRootElement {
private final List<Pair<String, String>> fields = new ArrayList<>();
private String name;
public void setName(String name) {
this.name = name;
}
public void addField(String type, String name) {
fields.add(Pair.of(type, name));
}
private String buildFields() {
return fields.stream()
.map(p -> p.first() + ' ' + p.second() + ';')
.collect(Collectors.joining("\n"));
}
public String prettyPrint() {
return """
struct %s {
%s
};
""".formatted(name, buildFields().indent(4));
}
}
public static class FunctionBuilder implements GlslRootElement {
private final List<Pair<String, String>> arguments = new ArrayList<>();
private final BlockBuilder body = new BlockBuilder();
private String returnType;
private String name;
public FunctionBuilder returnType(String returnType) {
this.returnType = returnType;
return this;
}
public FunctionBuilder name(String name) {
this.name = name;
return this;
}
public FunctionBuilder argument(String type, String name) {
arguments.add(Pair.of(type, name));
return this;
}
public FunctionBuilder argumentIn(String type, String name) {
arguments.add(Pair.of("in " + type, name));
return this;
}
public FunctionBuilder body(Consumer<BlockBuilder> f) {
f.accept(body);
return this;
}
public String prettyPrint() {
return """
%s %s(%s) {
%s
}
""".formatted(returnType, name, buildArguments(), body.prettyPrint()
.indent(4));
}
private String buildArguments() {
return arguments.stream()
.map(p -> p.first() + ' ' + p.second())
.collect(Collectors.joining(", "));
}
}
public static class BlockBuilder implements LangItem {
private final List<GlslStmt> body = new ArrayList<>();
public BlockBuilder add(GlslStmt stmt) {
body.add(stmt);
return this;
}
public BlockBuilder eval(GlslExpr expr) {
return add(GlslStmt.eval(expr));
}
public BlockBuilder switchOn(GlslExpr expr, Consumer<SwitchBuilder> f) {
var builder = new SwitchBuilder(expr);
f.accept(builder);
return add(builder.build());
}
public void ret(GlslExpr call) {
add(GlslStmt.ret(call));
}
public void break_() {
add(GlslStmt.BREAK);
}
@Override
public String prettyPrint() {
return body.stream()
.map(GlslStmt::prettyPrint)
.collect(Collectors.joining("\n"));
}
}
public static class SwitchBuilder {
private final GlslExpr on;
private final List<Pair<GlslExpr, BlockBuilder>> cases = new ArrayList<>();
public SwitchBuilder(GlslExpr on) {
this.on = on;
}
public SwitchBuilder case_(int expr, Consumer<BlockBuilder> f) {
var builder = new BlockBuilder();
f.accept(builder);
cases.add(Pair.of(GlslExpr.literal(expr), builder));
return this;
}
public GlslStmt.Switch build() {
return new GlslStmt.Switch(on, cases);
}
}
}

View file

@ -6,7 +6,7 @@ import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
public interface GlslExpr extends LangItem {
public interface GlslExpr {
/**
* Create a glsl variable with the given name.
@ -27,7 +27,11 @@ public interface GlslExpr extends LangItem {
}
static GlslExpr literal(int expr) {
return new Literal(expr);
return new IntLiteral(expr);
}
static GlslExpr literal(boolean expr) {
return new BoolLiteral(expr);
}
/**
@ -70,6 +74,8 @@ public interface GlslExpr extends LangItem {
return f.apply(this);
}
String prettyPrint();
record Variable(String name) implements GlslExpr {
@Override
public String prettyPrint() {
@ -117,10 +123,17 @@ public interface GlslExpr extends LangItem {
}
record Literal(int value) implements GlslExpr {
record IntLiteral(int value) implements GlslExpr {
@Override
public String prettyPrint() {
return Integer.toString(value);
}
}
record BoolLiteral(boolean value) implements GlslExpr {
@Override
public String prettyPrint() {
return Boolean.toString(value);
}
}
}

View file

@ -0,0 +1,28 @@
package com.jozufozu.flywheel.core.source.generate;
import java.util.function.Consumer;
import com.jozufozu.flywheel.util.StringUtil;
public class GlslFn implements GlslBuilder.Declaration {
private final GlslBlock body = new GlslBlock();
private FnSignature signature;
public GlslFn signature(FnSignature signature) {
this.signature = signature;
return this;
}
public GlslFn body(Consumer<GlslBlock> f) {
f.accept(body);
return this;
}
public String prettyPrint() {
return """
%s {
%s
}
""".formatted(signature.fullDeclaration(), StringUtil.indent(body.prettyPrint(), 4));
}
}

View file

@ -1,11 +1,9 @@
package com.jozufozu.flywheel.core.source.generate;
import java.util.List;
import java.util.stream.Collectors;
import com.jozufozu.flywheel.util.Pair;
public interface GlslStmt extends LangItem {
public interface GlslStmt {
GlslStmt BREAK = () -> "break;";
GlslStmt CONTINUE = () -> "continue;";
GlslStmt RETURN = () -> "return;";
static GlslStmt eval(GlslExpr expr) {
return new Eval(expr);
@ -15,11 +13,7 @@ public interface GlslStmt extends LangItem {
return new Return(value);
}
static GlslStmt BREAK = () -> "break;";
static GlslStmt CONTINUE = () -> "continue;";
static GlslStmt RETURN = () -> "return;";
String prettyPrint();
record Eval(GlslExpr expr) implements GlslStmt {
@Override
@ -34,27 +28,4 @@ public interface GlslStmt extends LangItem {
return "return " + expr.prettyPrint() + ";";
}
}
record Switch(GlslExpr expr, List<Pair<GlslExpr, GlslBuilder.BlockBuilder>> body) implements GlslStmt {
@Override
public String prettyPrint() {
var cases = body.stream()
.map(Switch::prettyPrintCase)
.collect(Collectors.joining("\n"));
return """
switch (%s) {
%s
}""".formatted(expr.prettyPrint(), cases);
}
private static String prettyPrintCase(Pair<GlslExpr, GlslBuilder.BlockBuilder> p) {
var variant = p.first()
.prettyPrint();
var block = p.second()
.prettyPrint();
return """
case %s:
%s""".formatted(variant, block.indent(4));
}
}
}

View file

@ -0,0 +1,35 @@
package com.jozufozu.flywheel.core.source.generate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.jozufozu.flywheel.util.Pair;
public class GlslStruct implements GlslBuilder.Declaration {
private final List<Pair<String, String>> fields = new ArrayList<>();
private String name;
public void setName(String name) {
this.name = name;
}
public void addField(String type, String name) {
fields.add(Pair.of(type, name));
}
private String buildFields() {
return fields.stream()
.map(p -> p.first() + ' ' + p.second() + ';')
.collect(Collectors.joining("\n"));
}
public String prettyPrint() {
return """
struct %s {
%s
};
""".formatted(name, buildFields().indent(4));
}
}

View file

@ -0,0 +1,64 @@
package com.jozufozu.flywheel.core.source.generate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.util.Pair;
import com.jozufozu.flywheel.util.StringUtil;
public class GlslSwitch implements GlslStmt {
private final GlslExpr on;
private final List<Pair<GlslExpr, GlslBlock>> cases = new ArrayList<>();
private GlslBlock defaultCase = null;
private GlslSwitch(GlslExpr on) {
this.on = on;
}
public static GlslSwitch on(GlslExpr on) {
return new GlslSwitch(on);
}
public void intCase(int expr, GlslBlock block) {
cases.add(Pair.of(GlslExpr.literal(expr), block));
}
public void defaultCase(GlslBlock block) {
defaultCase = block;
}
@Override
public String prettyPrint() {
return """
switch (%s) {
%s
}""".formatted(on.prettyPrint(), getCaseStream());
}
@NotNull
private String getCaseStream() {
var cases = this.cases.stream()
.map(GlslSwitch::prettyPrintCase)
.collect(Collectors.joining("\n"));
if (defaultCase != null) {
cases += "\ndefault:\n" + StringUtil.indent(defaultCase.prettyPrint(), 4);
}
return cases;
}
private static String prettyPrintCase(Pair<GlslExpr, GlslBlock> p) {
var variant = p.first()
.prettyPrint();
var block = p.second()
.prettyPrint();
return """
case %s:
%s""".formatted(variant, StringUtil.indent(block, 4));
}
}

View file

@ -0,0 +1,28 @@
package com.jozufozu.flywheel.core.source.generate;
public class GlslVertexInput implements GlslBuilder.Declaration {
private int binding;
private String type;
private String name;
public GlslVertexInput binding(int binding) {
this.binding = binding;
return this;
}
public GlslVertexInput type(String type) {
this.type = type;
return this;
}
public GlslVertexInput name(String name) {
this.name = name;
return this;
}
@Override
public String prettyPrint() {
return "layout(location = " + binding + ") in " + type + " " + name + ";";
}
}

View file

@ -1,7 +0,0 @@
package com.jozufozu.flywheel.core.source.generate;
public interface LangItem {
String prettyPrint();
}

View file

@ -12,6 +12,7 @@ import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
@ -72,6 +73,25 @@ public class StringUtil {
return value.substring(0, len);
}
/**
* Copy of {@link String#indent(int)} with the trailing newline removed.
*/
public static String indent(String str, int n) {
if (str.isEmpty()) {
return "";
}
Stream<String> stream = str.lines();
if (n > 0) {
final String spaces = repeatChar(' ', n);
stream = stream.map(s -> spaces + s);
} else if (n == Integer.MIN_VALUE) {
stream = stream.map(String::stripLeading);
} else if (n < 0) {
throw new IllegalArgumentException("Requested indentation (" + n + ") is unsupported");
}
return stream.collect(Collectors.joining("\n"));
}
@Nonnull
public static String readToString(InputStream is) throws IOException {
ByteBuffer bytebuffer = null;