Merge branch '1.18/ubershaders' into 1.18/next

# Conflicts:
#	src/main/java/com/jozufozu/flywheel/api/material/Material.java
#	src/main/java/com/jozufozu/flywheel/api/vertex/VertexType.java
#	src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectEngine.java
#	src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java
#	src/main/java/com/jozufozu/flywheel/core/material/SimpleMaterial.java
#	src/main/java/com/jozufozu/flywheel/core/structs/oriented/OrientedType.java
#	src/main/java/com/jozufozu/flywheel/core/structs/transformed/TransformedType.java
#	src/main/java/com/jozufozu/flywheel/core/uniform/ViewProvider.java
#	src/main/java/com/jozufozu/flywheel/util/FlwUtil.java
This commit is contained in:
Jozufozu 2023-03-28 16:27:18 -07:00
commit e31575de3b
127 changed files with 2896 additions and 2231 deletions

View file

@ -7,14 +7,12 @@ import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.RenderWork; import com.jozufozu.flywheel.backend.RenderWork;
import com.jozufozu.flywheel.backend.ShadersModHandler; import com.jozufozu.flywheel.backend.ShadersModHandler;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.instancing.PipelineCompiler;
import com.jozufozu.flywheel.backend.instancing.batching.DrawBuffer; import com.jozufozu.flywheel.backend.instancing.batching.DrawBuffer;
import com.jozufozu.flywheel.config.BackendTypeArgument; import com.jozufozu.flywheel.config.BackendTypeArgument;
import com.jozufozu.flywheel.config.FlwCommands; import com.jozufozu.flywheel.config.FlwCommands;
import com.jozufozu.flywheel.config.FlwConfig; import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.core.BackendTypes; import com.jozufozu.flywheel.core.BackendTypes;
import com.jozufozu.flywheel.core.Components; import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.DebugRender;
import com.jozufozu.flywheel.core.PartialModel; import com.jozufozu.flywheel.core.PartialModel;
import com.jozufozu.flywheel.core.QuadConverter; import com.jozufozu.flywheel.core.QuadConverter;
import com.jozufozu.flywheel.core.StitchedSprite; import com.jozufozu.flywheel.core.StitchedSprite;
@ -81,7 +79,6 @@ public class Flywheel {
forgeEventBus.addListener(FlwCommands::registerClientCommands); forgeEventBus.addListener(FlwCommands::registerClientCommands);
forgeEventBus.addListener(EventPriority.HIGHEST, QuadConverter::onReloadRenderers); forgeEventBus.addListener(EventPriority.HIGHEST, QuadConverter::onReloadRenderers);
forgeEventBus.addListener(PipelineCompiler::onReloadRenderers);
forgeEventBus.addListener(Models::onReloadRenderers); forgeEventBus.addListener(Models::onReloadRenderers);
forgeEventBus.addListener(DrawBuffer::onReloadRenderers); forgeEventBus.addListener(DrawBuffer::onReloadRenderers);
@ -108,7 +105,6 @@ public class Flywheel {
// forgeEventBus.addListener(ExampleEffect::onReload); // forgeEventBus.addListener(ExampleEffect::onReload);
Components.init(); Components.init();
DebugRender.init();
VanillaInstances.init(); VanillaInstances.init();

View file

@ -0,0 +1,13 @@
package com.jozufozu.flywheel.api.context;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.resources.ResourceLocation;
public interface Context {
void onProgramLink(GlProgram program);
ResourceLocation vertexShader();
ResourceLocation fragmentShader();
}

View file

@ -1,16 +0,0 @@
package com.jozufozu.flywheel.api.context;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.SourceFile;
public record ContextShader(GlProgram.Factory factory, FileResolution vertexShader, FileResolution fragmentShader) {
public SourceFile getVertexShader() {
return vertexShader.getFile();
}
public SourceFile getFragmentShader() {
return fragmentShader.getFile();
}
}

View file

@ -1,15 +1,15 @@
package com.jozufozu.flywheel.api.material; package com.jozufozu.flywheel.api.material;
import com.jozufozu.flywheel.api.vertex.MutableVertexList; import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import com.jozufozu.flywheel.core.source.FileResolution;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
public interface Material { public interface Material {
FileResolution getVertexShader(); ResourceLocation vertexShader();
FileResolution getFragmentShader(); ResourceLocation fragmentShader();
void setup(); void setup();

View file

@ -0,0 +1,28 @@
package com.jozufozu.flywheel.api.pipeline;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.source.ShaderSources;
import net.minecraft.resources.ResourceLocation;
public interface Pipeline {
GLSLVersion glslVersion();
ResourceLocation vertexShader();
ResourceLocation fragmentShader();
/**
* Generate the source component necessary to convert a packed {@link StructType} into its shader representation.
*
* @return A source component defining functions that unpack a representation of the given struct type.
*/
SourceComponent assemble(InstanceAssemblerContext context);
record InstanceAssemblerContext(ShaderSources sources, VertexType vertexType, StructType<?> structType) {
}
}

View file

@ -1,27 +0,0 @@
package com.jozufozu.flywheel.api.pipeline;
import java.util.function.BiFunction;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.source.FileResolution;
public record PipelineShader(GLSLVersion glslVersion, FileResolution vertex, FileResolution fragment,
InstanceAssemblerFactory factory) {
/**
* Generate the source component necessary to convert a packed {@link StructType} into its shader representation.
*
* @param structType The struct type to convert.
* @return A source component defining functions that unpack a representation of the given struct type.
*/
public SourceComponent assemble(VertexType vertexType, StructType<?> structType) {
return factory.apply(vertexType, structType);
}
public interface InstanceAssemblerFactory extends BiFunction<VertexType, StructType<?>, SourceComponent> {
}
}

View file

@ -3,9 +3,9 @@ package com.jozufozu.flywheel.api.struct;
import com.jozufozu.flywheel.api.instancer.InstancedPart; import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.vertex.MutableVertexList; import com.jozufozu.flywheel.api.vertex.MutableVertexList;
import com.jozufozu.flywheel.core.layout.BufferLayout; import com.jozufozu.flywheel.core.layout.BufferLayout;
import com.jozufozu.flywheel.core.source.FileResolution;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.resources.ResourceLocation;
/** /**
* A StructType contains metadata for a specific instance struct that Flywheel can interface with. * A StructType contains metadata for a specific instance struct that Flywheel can interface with.
@ -25,7 +25,7 @@ public interface StructType<S extends InstancedPart> {
StructWriter<S> getWriter(); StructWriter<S> getWriter();
FileResolution getInstanceShader(); ResourceLocation instanceShader();
VertexTransformer<S> getVertexTransformer(); VertexTransformer<S> getVertexTransformer();

View file

@ -0,0 +1,29 @@
package com.jozufozu.flywheel.api.uniform;
import net.minecraft.resources.ResourceLocation;
public interface ShaderUniforms {
Provider activate(long ptr);
ResourceLocation uniformShader();
int byteSize();
interface Provider {
/**
* Delete this provider.<p>
* <p>
* Do not free the ptr passed to {@link #activate(long)}.<br>
* Clean up other resources, and unsubscribe from events.
*/
void delete();
/**
* Poll the provider for changes.
*
* @return {@code true} if the provider updated its backing store.
*/
boolean poll();
}
}

View file

@ -1,22 +0,0 @@
package com.jozufozu.flywheel.api.uniform;
import com.jozufozu.flywheel.core.source.FileResolution;
public abstract class UniformProvider {
protected long ptr;
protected Notifier notifier;
public abstract int getActualByteSize();
public void updatePtr(long ptr, Notifier notifier) {
this.ptr = ptr;
this.notifier = notifier;
}
public abstract FileResolution getUniformShader();
public interface Notifier {
void signalChanged();
}
}

View file

@ -1,7 +1,8 @@
package com.jozufozu.flywheel.api.vertex; package com.jozufozu.flywheel.api.vertex;
import com.jozufozu.flywheel.core.layout.BufferLayout; import com.jozufozu.flywheel.core.layout.BufferLayout;
import com.jozufozu.flywheel.core.source.FileResolution;
import net.minecraft.resources.ResourceLocation;
/** /**
* A vertex type containing metadata about a specific vertex layout. * A vertex type containing metadata about a specific vertex layout.
@ -13,5 +14,13 @@ public interface VertexType extends VertexListProvider {
*/ */
BufferLayout getLayout(); BufferLayout getLayout();
FileResolution getLayoutShader(); ResourceLocation layoutShader();
default int getStride() {
return getLayout().getStride();
}
default int byteOffset(int vertexIndex) {
return getStride() * vertexIndex;
}
} }

View file

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

View file

@ -1,6 +1,9 @@
package com.jozufozu.flywheel.backend; package com.jozufozu.flywheel.backend;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.backend.instancing.Engine; import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.core.pipeline.SimplePipeline;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
@ -17,4 +20,6 @@ public interface BackendType {
BackendType findFallback(); BackendType findFallback();
boolean supported(); boolean supported();
@Nullable SimplePipeline pipelineShader();
} }

View file

@ -1,23 +1,9 @@
package com.jozufozu.flywheel.backend; package com.jozufozu.flywheel.backend;
import java.util.List;
import com.jozufozu.flywheel.api.context.ContextShader;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.pipeline.PipelineShader;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher; import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.instancing.PipelineCompiler; import com.jozufozu.flywheel.backend.instancing.compile.FlwCompiler;
import com.jozufozu.flywheel.backend.instancing.indirect.ComputeCullerCompiler;
import com.jozufozu.flywheel.core.ComponentRegistry;
import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.compile.ShaderCompilationException;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.source.ShaderSources; import com.jozufozu.flywheel.core.source.ShaderSources;
import com.jozufozu.flywheel.core.source.error.ErrorReporter; import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.util.StringUtil;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
@ -37,11 +23,12 @@ public class Loader implements ResourceManagerReloadListener {
Loader() { Loader() {
// Can be null when running datagenerators due to the unfortunate time we call this // Can be null when running datagenerators due to the unfortunate time we call this
Minecraft minecraft = Minecraft.getInstance(); Minecraft minecraft = Minecraft.getInstance();
if (minecraft != null) { if (minecraft == null) {
ResourceManager manager = minecraft.getResourceManager(); return;
if (manager instanceof ReloadableResourceManager) {
((ReloadableResourceManager) manager).registerReloadListener(this);
} }
if (minecraft.getResourceManager() instanceof ReloadableResourceManager reloadable) {
reloadable.registerReloadListener(this);
} }
} }
@ -52,62 +39,11 @@ public class Loader implements ResourceManagerReloadListener {
var errorReporter = new ErrorReporter(); var errorReporter = new ErrorReporter();
ShaderSources sources = new ShaderSources(errorReporter, manager); ShaderSources sources = new ShaderSources(errorReporter, manager);
Backend.LOGGER.info("Loaded all shader sources in " + sources.getLoadTime()); if (FlwCompiler.INSTANCE != null) {
FlwCompiler.INSTANCE.delete();
FileResolution.run(errorReporter, sources);
if (errorReporter.hasErrored()) {
throw errorReporter.dump();
} }
sources.postResolve(); FlwCompiler.INSTANCE = new FlwCompiler(sources);
Backend.LOGGER.info("Successfully resolved all source files.");
FileResolution.checkAll(errorReporter);
Backend.LOGGER.info("All shaders passed checks.");
long compileStart = System.nanoTime();
int programCounter = 0;
boolean crash = false;
for (Material material : ComponentRegistry.materials) {
for (StructType<?> structType : ComponentRegistry.structTypes) {
for (VertexType vertexType : ComponentRegistry.vertexTypes) {
for (ContextShader contextShader : ComponentRegistry.contextShaders) {
for (PipelineShader pipelineShader : List.of(Components.INSTANCED_ARRAYS, Components.INDIRECT)) {
var ctx = new PipelineCompiler.Context(vertexType, material, structType, contextShader, pipelineShader);
try {
PipelineCompiler.INSTANCE.getProgram(ctx);
} catch (ShaderCompilationException e) {
Backend.LOGGER.error(e.errors);
crash = true;
}
programCounter++;
}
}
}
}
}
for (StructType<?> structType : ComponentRegistry.structTypes) {
try {
ComputeCullerCompiler.INSTANCE.get(structType);
} catch (ShaderCompilationException e) {
Backend.LOGGER.error(e.errors);
crash = true;
}
programCounter++;
}
long compileEnd = System.nanoTime();
Backend.LOGGER.info("Compiled " + programCounter + " programs in " + StringUtil.formatTime(compileEnd - compileStart));
if (crash) {
throw new ShaderLoadingException("Compilation failed");
}
ClientLevel level = Minecraft.getInstance().level; ClientLevel level = Minecraft.getInstance().level;
if (Backend.canUseInstancing(level)) { if (Backend.canUseInstancing(level)) {

View file

@ -3,8 +3,11 @@ package com.jozufozu.flywheel.backend;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.backend.instancing.Engine; import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.core.BackendTypes; import com.jozufozu.flywheel.core.BackendTypes;
import com.jozufozu.flywheel.core.pipeline.SimplePipeline;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
@ -17,14 +20,16 @@ public class SimpleBackendType implements BackendType {
private final Supplier<Engine> engineSupplier; private final Supplier<Engine> engineSupplier;
private final Supplier<BackendType> fallback; private final Supplier<BackendType> fallback;
private final BooleanSupplier isSupported; private final BooleanSupplier isSupported;
private final SimplePipeline pipelineShader;
public SimpleBackendType(String properName, String shortName, Component engineMessage, Supplier<Engine> engineSupplier, Supplier<BackendType> fallback, BooleanSupplier isSupported) { public SimpleBackendType(String properName, String shortName, Component engineMessage, Supplier<Engine> engineSupplier, Supplier<BackendType> fallback, BooleanSupplier isSupported, @Nullable SimplePipeline pipelineShader) {
this.properName = properName; this.properName = properName;
this.shortName = shortName; this.shortName = shortName;
this.engineMessage = engineMessage; this.engineMessage = engineMessage;
this.engineSupplier = engineSupplier; this.engineSupplier = engineSupplier;
this.fallback = fallback; this.fallback = fallback;
this.isSupported = isSupported; this.isSupported = isSupported;
this.pipelineShader = pipelineShader;
} }
public static Builder builder() { public static Builder builder() {
@ -66,6 +71,11 @@ public class SimpleBackendType implements BackendType {
return isSupported.getAsBoolean(); return isSupported.getAsBoolean();
} }
@Override
public @Nullable SimplePipeline pipelineShader() {
return pipelineShader;
}
public static class Builder { public static class Builder {
private String properName; private String properName;
private String shortName; private String shortName;
@ -73,6 +83,7 @@ public class SimpleBackendType implements BackendType {
private Supplier<Engine> engineSupplier; private Supplier<Engine> engineSupplier;
private Supplier<BackendType> fallback; private Supplier<BackendType> fallback;
private BooleanSupplier isSupported; private BooleanSupplier isSupported;
private SimplePipeline pipelineShader;
public Builder properName(String properName) { public Builder properName(String properName) {
this.properName = properName; this.properName = properName;
@ -104,8 +115,13 @@ public class SimpleBackendType implements BackendType {
return this; return this;
} }
public Builder pipelineShader(SimplePipeline pipelineShader) {
this.pipelineShader = pipelineShader;
return this;
}
public BackendType register() { public BackendType register() {
return BackendTypes.register(new SimpleBackendType(properName, shortName, engineMessage, engineSupplier, fallback, isSupported)); return BackendTypes.register(new SimpleBackendType(properName, shortName, engineMessage, engineSupplier, fallback, isSupported, pipelineShader));
} }
} }
} }

View file

@ -1,28 +1,22 @@
package com.jozufozu.flywheel.backend.gl.shader; package com.jozufozu.flywheel.backend.gl.shader;
import static org.lwjgl.opengl.GL20.glDeleteProgram; import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.backend.Backend; import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlObject; import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.core.uniform.UniformBuffer;
import com.mojang.blaze3d.shaders.ProgramManager; import com.mojang.blaze3d.shaders.ProgramManager;
import net.minecraft.resources.ResourceLocation;
public class GlProgram extends GlObject { public class GlProgram extends GlObject {
public final ResourceLocation name; public GlProgram(int handle) {
public GlProgram(ResourceLocation name, int handle) {
this.name = name;
setHandle(handle); setHandle(handle);
} }
public void bind() { public void bind() {
// TODO: bind textures?
UniformBuffer.getInstance()
.sync();
ProgramManager.glUseProgram(handle()); ProgramManager.glUseProgram(handle());
} }
@ -40,7 +34,7 @@ public class GlProgram extends GlObject {
int index = glGetUniformLocation(this.handle(), uniform); int index = glGetUniformLocation(this.handle(), uniform);
if (index < 0) { if (index < 0) {
Backend.LOGGER.debug("No active uniform '{}' exists in program '{}'. Could be unused.", uniform, this.name); Backend.LOGGER.debug("No active uniform '{}' exists. Could be unused.", uniform);
} }
return index; return index;
@ -51,17 +45,13 @@ public class GlProgram extends GlObject {
* *
* @param name The name of the sampler uniform. * @param name The name of the sampler uniform.
* @param binding The index of the texture unit. * @param binding The index of the texture unit.
* @return The sampler uniform's index.
* @throws NullPointerException If no uniform exists with the given name.
*/ */
public int setSamplerBinding(String name, int binding) { public void setSamplerBinding(String name, int binding) {
int samplerUniform = getUniformLocation(name); int samplerUniform = getUniformLocation(name);
if (samplerUniform >= 0) { if (samplerUniform >= 0) {
glUniform1i(samplerUniform, binding); glUniform1i(samplerUniform, binding);
} }
return samplerUniform;
} }
@Override @Override
@ -69,17 +59,4 @@ public class GlProgram extends GlObject {
glDeleteProgram(handle); glDeleteProgram(handle);
} }
@Override
public String toString() {
return "program " + name;
}
/**
* A factory interface to create a {@link GlProgram}.
*/
public interface Factory {
@NotNull
GlProgram create(ResourceLocation name, int handle);
}
} }

View file

@ -1,38 +1,17 @@
package com.jozufozu.flywheel.backend.gl.shader; package com.jozufozu.flywheel.backend.gl.shader;
import java.io.File;
import java.io.FileWriter;
import java.util.List;
import java.util.stream.Collectors;
import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GlObject; import com.jozufozu.flywheel.backend.gl.GlObject;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.core.compile.ShaderCompilationException;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
public class GlShader extends GlObject { public class GlShader extends GlObject {
public final ShaderType type; public final ShaderType type;
private final List<ResourceLocation> parts; private final String name;
public GlShader(String source, ShaderType type, List<ResourceLocation> parts) throws ShaderCompilationException { public GlShader(int handle, ShaderType type, String name) {
this.parts = parts;
this.type = type; this.type = type;
int handle = GL20.glCreateShader(type.glEnum); this.name = name;
GlCompat.safeShaderSource(handle, source);
GL20.glCompileShader(handle);
dumpSource(source, type);
if (GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) != GL20.GL_TRUE) {
throw new ShaderCompilationException("Could not compile " + getName(), handle);
}
setHandle(handle); setHandle(handle);
} }
@ -42,26 +21,9 @@ public class GlShader extends GlObject {
GL20.glDeleteShader(handle); GL20.glDeleteShader(handle);
} }
public String getName() { @Override
return parts.stream() public String toString() {
.map(ResourceLocation::toString) return "GlShader{" + type.name + handle() + " " + name + "}";
.map(s -> s.replaceAll("/", "_")
.replaceAll(":", "\\$"))
.collect(Collectors.joining(";"));
} }
private void dumpSource(String source, ShaderType type) {
if (!Backend.DUMP_SHADER_SOURCE) {
return;
}
File dir = new File(Minecraft.getInstance().gameDirectory, "flywheel_sources");
dir.mkdirs();
File file = new File(dir, type.getFileName(getName()));
try (FileWriter writer = new FileWriter(file)) {
writer.write(source);
} catch (Exception e) {
e.printStackTrace();
}
}
} }

View file

@ -1,178 +0,0 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.LinkedHashSet;
import java.util.List;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.context.ContextShader;
import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.pipeline.PipelineShader;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.compile.CompileUtil;
import com.jozufozu.flywheel.core.compile.Memoizer;
import com.jozufozu.flywheel.core.compile.ProgramAssembler;
import com.jozufozu.flywheel.core.compile.ShaderCompilationException;
import com.jozufozu.flywheel.core.source.CompilationContext;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import net.minecraft.resources.ResourceLocation;
/**
* A caching compiler.
*
* <p>
* This class is responsible for compiling programs on the fly. An instance of this class will keep a cache of
* compiled programs, and will only compile a program if it is not already in the cache.
* </p>
* <p>
* A ProgramCompiler is also responsible for deleting programs and shaders on renderer reload.
* </p>
*/
public class PipelineCompiler extends Memoizer<PipelineCompiler.Context, GlProgram> {
public static final PipelineCompiler INSTANCE = new PipelineCompiler();
private final ShaderCompiler shaderCompiler;
private PipelineCompiler() {
this.shaderCompiler = new ShaderCompiler();
}
/**
* Get or compile a spec to the given vertex type, accounting for all game state conditions specified by the spec.
*
* @param ctx The context of compilation.
* @return A compiled GlProgram.
*/
public GlProgram getProgram(PipelineCompiler.Context ctx) {
return super.get(ctx);
}
@Override
public void invalidate() {
super.invalidate();
shaderCompiler.invalidate();
}
@Override
protected GlProgram _create(PipelineCompiler.Context ctx) {
var glslVersion = ctx.pipelineShader()
.glslVersion();
var vertex = new ShaderCompiler.Context(glslVersion, ShaderType.VERTEX, ctx.getVertexComponents());
var fragment = new ShaderCompiler.Context(glslVersion, ShaderType.FRAGMENT, ctx.getFragmentComponents());
return new ProgramAssembler(ctx.structType.getInstanceShader()
.getFileLoc()).attachShader(shaderCompiler.get(vertex))
.attachShader(shaderCompiler.get(fragment))
.link()
.build(ctx.contextShader.factory());
}
@Override
protected void _destroy(GlProgram value) {
value.delete();
}
public static void onReloadRenderers(ReloadRenderersEvent event) {
INSTANCE.invalidate();
}
/**
* Represents the entire context of a program's usage.
*
* @param vertexType The vertexType the program should be adapted for.
* @param material The material shader to use. TODO: Flatten materials
* @param structType The instance shader to use.
* @param contextShader The context shader to use.
*/
public record Context(VertexType vertexType, Material material, StructType<?> structType,
ContextShader contextShader, PipelineShader pipelineShader) {
ImmutableList<SourceComponent> getVertexComponents() {
var layout = vertexType.getLayoutShader()
.getFile();
var instanceAssembly = pipelineShader.assemble(vertexType, structType);
var instance = structType.getInstanceShader()
.getFile();
var material = this.material.getVertexShader()
.getFile();
var context = contextShader.getVertexShader();
var pipeline = pipelineShader.vertex()
.getFile();
return ImmutableList.of(layout, instanceAssembly, instance, material, context, pipeline);
}
ImmutableList<SourceComponent> getFragmentComponents() {
var material = this.material.getFragmentShader()
.getFile();
var context = contextShader.getFragmentShader();
var pipeline = pipelineShader.fragment()
.getFile();
return ImmutableList.of(material, context, pipeline);
}
}
/**
* Handles compilation and deletion of vertex shaders.
*/
public static class ShaderCompiler extends Memoizer<ShaderCompiler.Context, GlShader> {
private ShaderCompiler() {
}
@Override
protected GlShader _create(Context key) {
StringBuilder finalSource = new StringBuilder(key.generateHeader());
finalSource.append("#extension GL_ARB_explicit_attrib_location : enable\n");
finalSource.append("#extension GL_ARB_conservative_depth : enable\n");
var ctx = new CompilationContext();
var names = ImmutableList.<ResourceLocation>builder();
var included = new LinkedHashSet<SourceComponent>(); // linked to preserve order
for (var component : key.sourceComponents) {
included.addAll(component.included());
names.add(component.name());
}
for (var include : included) {
finalSource.append(include.source(ctx));
}
for (var component : key.sourceComponents) {
finalSource.append(component.source(ctx));
}
try {
return new GlShader(finalSource.toString(), key.shaderType, names.build());
} catch (ShaderCompilationException e) {
throw e.withErrorLog(ctx);
}
}
@Override
protected void _destroy(GlShader value) {
value.delete();
}
/**
* @param glslVersion The GLSL version to use.
* @param sourceComponents A list of shader components to stitch together, in order.
*/
public record Context(GLSLVersion glslVersion, ShaderType shaderType, List<SourceComponent> sourceComponents) {
public String generateHeader() {
return CompileUtil.generateHeader(glslVersion, shaderType);
}
}
}
}

View file

@ -0,0 +1,150 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.util.StringUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
/**
* Builder style class for compiling shaders.
* <p>
* Keeps track of the source files and components used to compile a shader,
* and interprets/pretty prints any errors that occur.
*/
public class Compilation {
private final List<SourceFile> files = new ArrayList<>();
private final List<ResourceLocation> componentNames = new ArrayList<>();
private final StringBuilder generatedSource;
private final StringBuilder fullSource;
private final GLSLVersion glslVersion;
private final ShaderType shaderType;
private int generatedLines = 0;
public Compilation(GLSLVersion glslVersion, ShaderType shaderType) {
this.generatedSource = new StringBuilder();
this.fullSource = new StringBuilder(CompileUtil.generateHeader(glslVersion, shaderType));
this.glslVersion = glslVersion;
this.shaderType = shaderType;
}
@NotNull
public CompilationResult compile() {
int handle = GL20.glCreateShader(shaderType.glEnum);
var source = fullSource.toString();
GlCompat.safeShaderSource(handle, source);
GL20.glCompileShader(handle);
var shaderName = buildShaderName();
dumpSource(source, shaderType.getFileName(shaderName));
if (compiledSuccessfully(handle)) {
return CompilationResult.success(new GlShader(handle, shaderType, shaderName));
}
var errorLog = GL20.glGetShaderInfoLog(handle);
GL20.glDeleteShader(handle);
return CompilationResult.failure(new FailedCompilation(shaderName, files, generatedSource.toString(), errorLog));
}
public void enableExtension(String ext) {
fullSource.append("#extension ")
.append(ext)
.append(" : enable\n");
}
public void addComponentName(ResourceLocation name) {
componentNames.add(name);
}
public void appendComponent(SourceComponent component) {
var source = component.source();
if (component instanceof SourceFile file) {
fullSource.append(sourceHeader(file));
} else {
fullSource.append(generatedHeader(source, component.name()
.toString()));
}
fullSource.append(source)
.append('\n');
}
private String sourceHeader(SourceFile sourceFile) {
return "#line " + 0 + ' ' + getOrCreateFileID(sourceFile) + " // " + sourceFile.name + '\n';
}
private String generatedHeader(String generatedCode, String comment) {
generatedSource.append(generatedCode);
int lines = StringUtil.countLines(generatedCode);
// all generated code is put in file 0,
var out = "#line " + generatedLines + ' ' + 0;
generatedLines += lines;
return out + " // (generated) " + comment + '\n';
}
/**
* Returns an arbitrary file ID for use this compilation context, or generates one if missing.
*
* @param sourceFile The file to retrieve the ID for.
* @return A file ID unique to the given sourceFile.
*/
private int getOrCreateFileID(SourceFile sourceFile) {
int i = files.indexOf(sourceFile);
if (i != -1) {
return i + 1;
}
files.add(sourceFile);
return files.size();
}
@NotNull
private String buildShaderName() {
var components = componentNames.stream()
.map(ResourceLocation::toString)
.map(s -> s.replaceAll("/", "_")
.replaceAll(":", "\\$"))
.collect(Collectors.joining(";"));
return shaderType.name + glslVersion + ';' + components;
}
private static void dumpSource(String source, String fileName) {
if (!Backend.DUMP_SHADER_SOURCE) {
return;
}
File dir = new File(Minecraft.getInstance().gameDirectory, "flywheel_sources");
dir.mkdirs();
File file = new File(dir, fileName);
try (FileWriter writer = new FileWriter(file)) {
writer.write(source);
} catch (Exception e) {
Backend.LOGGER.error("Could not dump source.", e);
}
}
private static boolean compiledSuccessfully(int handle) {
return GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) == GL20.GL_TRUE;
}
}

View file

@ -0,0 +1,29 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
public sealed interface CompilationResult {
@Nullable
default GlShader unwrap() {
if (this instanceof Success s) {
return s.shader();
}
return null;
}
record Success(GlShader shader) implements CompilationResult {
}
record Failure(FailedCompilation failure) implements CompilationResult {
}
static CompilationResult success(GlShader program) {
return new Success(program);
}
static CompilationResult failure(FailedCompilation failure) {
return new Failure(failure);
}
}

View file

@ -1,4 +1,10 @@
package com.jozufozu.flywheel.core.compile; package com.jozufozu.flywheel.backend.instancing.compile;
import static org.lwjgl.opengl.GL20.GL_LINK_STATUS;
import static org.lwjgl.opengl.GL20.GL_TRUE;
import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
import static org.lwjgl.opengl.GL20.glGetProgrami;
import static org.lwjgl.opengl.GL20.glLinkProgram;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -7,6 +13,7 @@ import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GLSLVersion; import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType; import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.core.source.SourceFile; import com.jozufozu.flywheel.core.source.SourceFile;
@ -17,9 +24,7 @@ public class CompileUtil {
public static final Pattern matType = Pattern.compile("^mat([234])(?:x([234]))?$"); public static final Pattern matType = Pattern.compile("^mat([234])(?:x([234]))?$");
public static String generateHeader(GLSLVersion version, ShaderType type) { public static String generateHeader(GLSLVersion version, ShaderType type) {
return version.getVersionLine() return version.getVersionLine() + type.getDefineStatement() + '\n';
+ type.getDefineStatement()
+ '\n';
} }
public static int getElementCount(String type) { public static int getElementCount(String type) {
@ -59,4 +64,25 @@ public class CompileUtil {
.map(SourceFile::toString) .map(SourceFile::toString)
.collect(Collectors.joining(" -> ")); .collect(Collectors.joining(" -> "));
} }
/**
* Check the program info log for errors.
*
* @param handle The handle of the program to check.
*/
public static void checkLinkLog(int handle) {
glLinkProgram(handle);
String log = glGetProgramInfoLog(handle);
if (!log.isEmpty()) {
Backend.LOGGER.debug("Program link log: " + log);
}
int result = glGetProgrami(handle, GL_LINK_STATUS);
if (result != GL_TRUE) {
throw new RuntimeException("Shader program linking failed, see log for details");
}
}
} }

View file

@ -0,0 +1,6 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import com.jozufozu.flywheel.api.struct.StructType;
public record CullingContext(StructType<?> structType) {
}

View file

@ -0,0 +1,38 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.core.ComponentRegistry;
public class CullingContextSet {
static CullingContextSet create() {
var builder = new CullingContextSet();
for (StructType<?> structType : ComponentRegistry.structTypes) {
builder.add(structType);
}
return builder;
}
private final List<CullingContext> contexts = new ArrayList<>();
private final List<CullingContext> contextView = Collections.unmodifiableList(contexts);
CullingContextSet() {
}
public List<CullingContext> all() {
return contextView;
}
public int size() {
return contexts.size();
}
private void add(StructType<?> structType) {
var ctx = new CullingContext(structType);
contexts.add(ctx);
}
}

View file

@ -0,0 +1,83 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.SourceLines;
import com.jozufozu.flywheel.core.source.error.ErrorBuilder;
import com.jozufozu.flywheel.core.source.span.Span;
import com.jozufozu.flywheel.util.ConsoleColors;
import com.jozufozu.flywheel.util.StringUtil;
public class FailedCompilation {
private static final Pattern ERROR_LINE = Pattern.compile("(\\d+)\\((\\d+)\\) : (.*)");
private final List<SourceFile> files;
private final SourceLines generatedSource;
private final String errorLog;
private final String shaderName;
public FailedCompilation(String shaderName, List<SourceFile> files, String generatedSource, String errorLog) {
this.shaderName = shaderName;
this.files = files;
this.generatedSource = new SourceLines(generatedSource);
this.errorLog = errorLog;
}
public String getMessage() {
return ConsoleColors.RED_BOLD_BRIGHT + "Failed to compile " + shaderName + ":\n" + errorString();
}
public String errorString() {
return errorStream().map(ErrorBuilder::build)
.collect(Collectors.joining("\n"));
}
@NotNull
private Stream<ErrorBuilder> errorStream() {
return errorLog.lines()
.map(this::interpretErrorLine);
}
private ErrorBuilder interpretErrorLine(String s) {
Matcher matcher = ERROR_LINE.matcher(s);
if (matcher.find()) {
int fileId = Integer.parseInt(matcher.group(1));
int lineNo = Integer.parseInt(matcher.group(2));
var msg = StringUtil.trimPrefix(matcher.group(3), "error")
.stripLeading();
if (fileId == 0) {
return interpretGeneratedError(lineNo, msg);
} else {
return interpretSourceError(fileId, lineNo, msg);
}
}
return ErrorBuilder.create()
.error(s);
}
private ErrorBuilder interpretSourceError(int fileId, int lineNo, String msg) {
var sourceFile = files.get(fileId - 1);
Span span = sourceFile.getLineSpanNoWhitespace(lineNo);
return ErrorBuilder.create()
.error(msg)
.pointAtFile(sourceFile)
.pointAt(span, 1);
}
private ErrorBuilder interpretGeneratedError(int lineNo, String msg) {
return ErrorBuilder.create()
.error(msg)
.pointAtFile("[in generated source]")
.pointAtLine(generatedSource, lineNo, 1)
.note("This generally indicates a bug in Flywheel, not your shader code.");
}
}

View file

@ -0,0 +1,207 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import static org.lwjgl.opengl.GL20.glAttachShader;
import static org.lwjgl.opengl.GL20.glCreateProgram;
import static org.lwjgl.opengl.GL20.glLinkProgram;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.pipeline.Pipeline;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.uniform.ShaderUniforms;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.backend.instancing.indirect.IndirectComponent;
import com.jozufozu.flywheel.core.ComponentRegistry;
import com.jozufozu.flywheel.core.Pipelines;
import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.context.SimpleContext;
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 {
public static FlwCompiler INSTANCE;
final long compileStart = System.nanoTime();
private final ShaderSources sources;
private final UniformComponent uniformComponent;
private final MaterialAdapterComponent vertexMaterialComponent;
private final MaterialAdapterComponent fragmentMaterialComponent;
private final PipelineContextSet pipelineContexts;
private final CullingContextSet cullingContexts;
final ShaderCompiler shaderCompiler;
final Map<PipelineContext, GlProgram> pipelinePrograms = new HashMap<>();
final Map<StructType<?>, GlProgram> cullingPrograms = new HashMap<>();
final List<FailedCompilation> errors = new ArrayList<>();
public FlwCompiler(ShaderSources sources) {
this.shaderCompiler = ShaderCompiler.builder()
.errorConsumer(errors::add)
.build();
this.sources = sources;
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.uniformComponent = UniformComponent.builder(Flywheel.rl("uniforms"))
.sources(ComponentRegistry.getAllUniformProviders()
.stream()
.map(ShaderUniforms::uniformShader)
.toList())
.build(sources);
this.pipelineContexts = PipelineContextSet.create();
this.cullingContexts = CullingContextSet.create();
doCompilation();
finish();
}
private void doCompilation() {
for (var ctx : pipelineContexts.all()) {
compilePipelineContext(ctx);
}
for (var ctx : cullingContexts.all()) {
compileComputeCuller(ctx);
}
}
private void finish() {
long compileEnd = System.nanoTime();
int programCount = pipelineContexts.size() + ComponentRegistry.structTypes.size();
int shaderCount = shaderCompiler.shaderCount();
int errorCount = errors.size();
var elapsed = StringUtil.formatTime(compileEnd - compileStart);
Backend.LOGGER.info("Compiled " + programCount + " programs and " + shaderCount + " shaders in " + elapsed + " with " + errorCount + " errors.");
if (errorCount > 0) {
var details = errors.stream()
.map(FailedCompilation::getMessage)
.collect(Collectors.joining("\n"));
throw new ShaderLoadingException("Compilation failed.\n" + details);
}
}
public void delete() {
pipelinePrograms.values()
.forEach(GlProgram::delete);
cullingPrograms.values()
.forEach(GlProgram::delete);
shaderCompiler.delete();
}
public GlProgram getPipelineProgram(VertexType vertexType, StructType<?> structType, SimpleContext contextShader, SimplePipeline pipelineShader) {
return pipelinePrograms.get(new PipelineContext(vertexType, structType, contextShader, pipelineShader));
}
public GlProgram getCullingProgram(StructType<?> structType) {
return cullingPrograms.get(structType);
}
private void compilePipelineContext(PipelineContext ctx) {
var glslVersion = ctx.pipelineShader()
.glslVersion();
var vertex = shaderCompiler.compile(glslVersion, ShaderType.VERTEX, getVertexComponents(ctx));
var fragment = shaderCompiler.compile(glslVersion, ShaderType.FRAGMENT, getFragmentComponents(ctx));
if (vertex == null || fragment == null) {
return;
}
var glProgram = link(vertex.handle(), fragment.handle());
ctx.contextShader()
.onProgramLink(glProgram);
pipelinePrograms.put(ctx, glProgram);
}
private void compileComputeCuller(CullingContext ctx) {
var computeComponents = getComputeComponents(ctx.structType());
var result = shaderCompiler.compile(GLSLVersion.V460, ShaderType.COMPUTE, computeComponents);
if (result == null) {
return;
}
cullingPrograms.put(ctx.structType(), link(result.handle()));
}
private GlProgram link(int... shaders) {
var handle = glCreateProgram();
for (var shader : shaders) {
glAttachShader(handle, shader);
}
glLinkProgram(handle);
CompileUtil.checkLinkLog(handle);
return new GlProgram(handle);
}
private ImmutableList<SourceComponent> getVertexComponents(PipelineContext ctx) {
var instanceAssembly = ctx.pipelineShader()
.assemble(new Pipeline.InstanceAssemblerContext(sources, ctx.vertexType(), ctx.structType()));
var layout = sources.find(ctx.vertexType()
.layoutShader());
var instance = sources.find(ctx.structType()
.instanceShader());
var context = sources.find(ctx.contextShader()
.vertexShader());
var pipeline = sources.find(ctx.pipelineShader()
.vertexShader());
return ImmutableList.of(uniformComponent, vertexMaterialComponent, instanceAssembly, layout, instance, context, pipeline);
}
private ImmutableList<SourceComponent> getFragmentComponents(PipelineContext ctx) {
var context = sources.find(ctx.contextShader()
.fragmentShader());
var pipeline = sources.find(ctx.pipelineShader()
.fragmentShader());
return ImmutableList.of(uniformComponent, fragmentMaterialComponent, context, pipeline);
}
private ImmutableList<SourceComponent> getComputeComponents(StructType<?> structType) {
var instanceAssembly = new IndirectComponent(sources, structType);
var instance = sources.find(structType.instanceShader());
var pipeline = sources.find(Pipelines.Files.INDIRECT_CULL);
return ImmutableList.of(uniformComponent, instanceAssembly, instance, pipeline);
}
}

View file

@ -0,0 +1,21 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.util.function.Consumer;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.core.SourceComponent;
/**
* A component of a ShaderCompiler, responsible for expanding root sources into the complete set of included sources.
*/
public interface Includer {
/**
* Expand the given root sources into the complete set of included sources.
* <p> Each unique source will be seen exactly once.
*
* @param rootSources The root sources to expand.
* @param out A consumer to which all sources should be passed in the order they should be included.
*/
void expand(ImmutableList<SourceComponent> rootSources, Consumer<SourceComponent> out);
}

View file

@ -0,0 +1,173 @@
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.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 class MaterialAdapterComponent implements SourceComponent {
// TODO: material id handling in pipeline shader
private final ResourceLocation name;
private final GlslExpr switchArg;
private final List<AdaptedFn> functionsToAdapt;
private final List<StringSubstitutionSourceComponent> adaptedComponents;
public MaterialAdapterComponent(ResourceLocation name, GlslExpr switchArg, List<AdaptedFn> functionsToAdapt, List<StringSubstitutionSourceComponent> adaptedComponents) {
this.name = name;
this.switchArg = switchArg;
this.functionsToAdapt = functionsToAdapt;
this.adaptedComponents = adaptedComponents;
}
public static Builder builder(ResourceLocation name) {
return new Builder(name);
}
@Override
public ResourceLocation name() {
return name;
}
@Override
public Collection<? extends SourceComponent> included() {
return adaptedComponents;
}
@Override
public String source() {
var builder = new GlslBuilder();
for (var adaptedFunction : functionsToAdapt) {
builder.function()
.signature(adaptedFunction.signature())
.body(body -> generateAdapter(body, adaptedFunction));
}
return builder.build();
}
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();
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);
}
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);
}
private record AdaptedFn(FnSignature signature, @Nullable GlslExpr defaultReturn) {
}
public static class Builder {
private final ResourceLocation name;
private final List<ResourceLocation> materialSources = new ArrayList<>();
private final List<AdaptedFn> adaptedFunctions = new ArrayList<>();
private GlslExpr switchArg;
public Builder(ResourceLocation name) {
this.name = name;
}
public Builder materialSources(List<ResourceLocation> sources) {
this.materialSources.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.<StringSubstitutionSourceComponent>builder();
for (var rl : materialSources) {
var sourceFile = sources.find(rl);
var adapterMap = createAdapterMap(adaptedFunctions, getSuffix(rl));
transformed.add(new StringSubstitutionSourceComponent(sourceFile, adapterMap));
}
return new MaterialAdapterComponent(name, switchArg, adaptedFunctions, transformed.build());
}
}
@NotNull
private static HashMap<String, String> createAdapterMap(List<AdaptedFn> adaptedFunctions, String suffix) {
HashMap<String, String> out = new HashMap<>();
for (var adapted : adaptedFunctions) {
var fnName = adapted.signature()
.name();
out.put(fnName, fnName + suffix);
}
return out;
}
@NotNull
private static String getSuffix(ResourceLocation rl) {
return '_' + ResourceUtil.toSafeString(rl);
}
}

View file

@ -0,0 +1,17 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.pipeline.Pipeline;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
/**
* Represents the entire context of a program's usage.
*
* @param vertexType The vertexType the program should be adapted for.
* @param structType The instance shader to use.
* @param contextShader The context shader to use.
*/
public record PipelineContext(VertexType vertexType, StructType<?> structType, Context contextShader,
Pipeline pipelineShader) {
}

View file

@ -0,0 +1,48 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.core.BackendTypes;
import com.jozufozu.flywheel.core.ComponentRegistry;
import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.context.SimpleContext;
import com.jozufozu.flywheel.core.pipeline.SimplePipeline;
public class PipelineContextSet {
static PipelineContextSet create() {
var builder = new PipelineContextSet();
for (SimplePipeline pipelineShader : BackendTypes.availablePipelineShaders()) {
for (StructType<?> structType : ComponentRegistry.structTypes) {
for (VertexType vertexType : ComponentRegistry.vertexTypes) {
builder.add(vertexType, structType, Components.WORLD, pipelineShader);
}
}
}
return builder;
}
private final List<PipelineContext> contexts = new ArrayList<>();
private final List<PipelineContext> contextView = Collections.unmodifiableList(contexts);
PipelineContextSet() {
}
public List<PipelineContext> all() {
return contextView;
}
public int size() {
return contexts.size();
}
private void add(VertexType vertexType, StructType<?> structType, SimpleContext world, SimplePipeline pipelineShader) {
var ctx = new PipelineContext(vertexType, structType, world, pipelineShader);
contexts.add(ctx);
}
}

View file

@ -0,0 +1,33 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Consumer;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.core.SourceComponent;
public class RecursiveIncluder implements Includer {
public static final RecursiveIncluder INSTANCE = new RecursiveIncluder();
private RecursiveIncluder() {
}
@Override
public void expand(ImmutableList<SourceComponent> rootSources, Consumer<SourceComponent> out) {
var included = new LinkedHashSet<SourceComponent>(); // use hash set to deduplicate. linked to preserve order
for (var component : rootSources) {
recursiveDepthFirstInclude(included, component);
included.add(component);
}
included.forEach(out);
}
private static void recursiveDepthFirstInclude(Set<SourceComponent> included, SourceComponent component) {
for (var include : component.included()) {
recursiveDepthFirstInclude(included, include);
}
included.addAll(component.included());
}
}

View file

@ -0,0 +1,113 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.util.FlwUtil;
public class ShaderCompiler {
private final Map<ShaderKey, CompilationResult> shaderCache = new HashMap<>();
private final Consumer<FailedCompilation> errorConsumer;
private final CompilationFactory factory;
private final Includer includer;
public ShaderCompiler(Consumer<FailedCompilation> errorConsumer, CompilationFactory factory, Includer includer) {
this.errorConsumer = errorConsumer;
this.factory = factory;
this.includer = includer;
}
public int shaderCount() {
return shaderCache.size();
}
@Nullable
public GlShader compile(GLSLVersion glslVersion, ShaderType shaderType, ImmutableList<SourceComponent> sourceComponents) {
var key = new ShaderKey(glslVersion, shaderType, sourceComponents);
var cached = shaderCache.get(key);
if (cached != null) {
return cached.unwrap();
}
CompilationResult out = compileUncached(factory.create(glslVersion, shaderType), sourceComponents);
shaderCache.put(key, out);
return unwrapAndReportError(out);
}
public void delete() {
shaderCache.values()
.stream()
.map(CompilationResult::unwrap)
.filter(Objects::nonNull)
.forEach(GlShader::delete);
}
@Nullable
private GlShader unwrapAndReportError(CompilationResult result) {
if (result instanceof CompilationResult.Success s) {
return s.shader();
} else if (result instanceof CompilationResult.Failure f) {
errorConsumer.accept(f.failure());
}
return null;
}
@NotNull
private CompilationResult compileUncached(Compilation ctx, ImmutableList<SourceComponent> sourceComponents) {
ctx.enableExtension("GL_ARB_explicit_attrib_location");
ctx.enableExtension("GL_ARB_conservative_depth");
includer.expand(sourceComponents, ctx::appendComponent);
return ctx.compile();
}
private record ShaderKey(GLSLVersion glslVersion, ShaderType shaderType,
ImmutableList<SourceComponent> sourceComponents) {
}
public static Builder builder() {
return new Builder();
}
@FunctionalInterface
public interface CompilationFactory {
Compilation create(GLSLVersion version, ShaderType shaderType);
}
public static class Builder {
private Consumer<FailedCompilation> errorConsumer = FlwUtil::noop;
private CompilationFactory factory = Compilation::new;
private Includer includer = RecursiveIncluder.INSTANCE;
public Builder errorConsumer(Consumer<FailedCompilation> errorConsumer) {
this.errorConsumer = errorConsumer;
return this;
}
public Builder compilationFactory(CompilationFactory factory) {
this.factory = factory;
return this;
}
public Builder includer(Includer includer) {
this.includer = includer;
return this;
}
public ShaderCompiler build() {
return new ShaderCompiler(errorConsumer, factory, includer);
}
}
}

View file

@ -0,0 +1,54 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.util.Collection;
import java.util.Map;
import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
public final class StringSubstitutionSourceComponent implements SourceComponent {
private final SourceComponent source;
private final Map<String, String> replacements;
private final String sourceString;
public StringSubstitutionSourceComponent(SourceComponent source, String find, String replace) {
this(source, Map.of(find, replace));
}
public StringSubstitutionSourceComponent(SourceComponent source, Map<String, String> replacements) {
this.source = source;
this.replacements = replacements;
this.sourceString = source.source();
}
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 = sourceString;
for (var entry : replacements.entrySet()) {
source = source.replace(entry.getKey(), entry.getValue());
}
return source;
}
@Override
public ResourceLocation name() {
return ResourceUtil.subPath(source.name(), "_string_substitution");
}
@Override
public Collection<? extends SourceComponent> included() {
return source.included();
}
}

View file

@ -0,0 +1,76 @@
package com.jozufozu.flywheel.backend.instancing.compile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.source.ShaderSources;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.generate.GlslBuilder;
import net.minecraft.resources.ResourceLocation;
public class UniformComponent implements SourceComponent {
private final ResourceLocation name;
private final ImmutableList<SourceFile> uniformShaders;
public static Builder builder(ResourceLocation uniforms) {
return new Builder(uniforms);
}
private UniformComponent(ResourceLocation name, ImmutableList<SourceFile> uniformShaders) {
this.name = name;
this.uniformShaders = uniformShaders;
}
@Override
public Collection<? extends SourceComponent> included() {
return uniformShaders;
}
@Override
public String source() {
var builder = new GlslBuilder();
builder.uniformBlock()
.layout("std140")
.binding(0)
.name("FLWUniforms")
.member("flywheel_uniforms", "flywheel");
return builder.build();
}
@Override
public ResourceLocation name() {
return name;
}
public static class Builder {
private final ResourceLocation name;
private final List<ResourceLocation> uniformShaders = new ArrayList<>();
public Builder(ResourceLocation name) {
this.name = name;
}
public Builder sources(List<ResourceLocation> sources) {
this.uniformShaders.addAll(sources);
return this;
}
public UniformComponent build(ShaderSources sources) {
var out = ImmutableList.<SourceFile>builder();
for (var fileResolution : uniformShaders) {
out.add(sources.find(fileResolution));
}
return new UniformComponent(name, out.build());
}
}
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.instancing.compile;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

@ -1,74 +0,0 @@
package com.jozufozu.flywheel.backend.instancing.indirect;
import java.util.LinkedHashSet;
import java.util.List;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.compile.CompileUtil;
import com.jozufozu.flywheel.core.compile.Memoizer;
import com.jozufozu.flywheel.core.compile.ProgramAssembler;
import com.jozufozu.flywheel.core.compile.ShaderCompilationException;
import com.jozufozu.flywheel.core.source.CompilationContext;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import net.minecraft.resources.ResourceLocation;
public class ComputeCullerCompiler extends Memoizer<StructType<?>, GlProgram> {
public static final ComputeCullerCompiler INSTANCE = new ComputeCullerCompiler();
private ComputeCullerCompiler() {
}
@Override
protected GlProgram _create(StructType<?> structType) {
var location = structType.getInstanceShader();
var finalSource = new StringBuilder();
CompilationContext context = new CompilationContext();
var components = List.of(new IndirectComponent(structType.getLayout().layoutItems), location.getFile(), Components.Pipeline.INDIRECT_CULL.getFile());
var names = ImmutableList.<ResourceLocation>builder();
var included = new LinkedHashSet<SourceComponent>(); // linked to preserve order
for (var component : components) {
included.addAll(component.included());
names.add(component.name());
}
finalSource.append(CompileUtil.generateHeader(GLSLVersion.V460, ShaderType.COMPUTE));
for (var include : included) {
finalSource.append(include.source(context));
}
for (var component : components) {
finalSource.append(component.source(context));
}
try {
var fileLoc = location.getFileLoc();
var shader = new GlShader(finalSource.toString(), ShaderType.COMPUTE, ImmutableList.of(fileLoc));
return new ProgramAssembler(fileLoc).attachShader(shader)
.link()
.build(GlProgram::new);
} catch (ShaderCompilationException e) {
throw e.withErrorLog(context);
}
}
@Override
protected void _destroy(GlProgram value) {
value.delete();
}
public static void invalidateAll(ReloadRenderersEvent ignored) {
INSTANCE.invalidate();
}
}

View file

@ -1,22 +1,7 @@
package com.jozufozu.flywheel.backend.instancing.indirect; package com.jozufozu.flywheel.backend.instancing.indirect;
import static org.lwjgl.opengl.GL45.glCreateBuffers; import static org.lwjgl.opengl.GL45.glCreateBuffers;
import static org.lwjgl.opengl.GL46.GL_DRAW_INDIRECT_BUFFER; import static org.lwjgl.opengl.GL46.*;
import static org.lwjgl.opengl.GL46.GL_DYNAMIC_STORAGE_BIT;
import static org.lwjgl.opengl.GL46.GL_MAP_FLUSH_EXPLICIT_BIT;
import static org.lwjgl.opengl.GL46.GL_MAP_PERSISTENT_BIT;
import static org.lwjgl.opengl.GL46.GL_MAP_WRITE_BIT;
import static org.lwjgl.opengl.GL46.GL_SHADER_STORAGE_BUFFER;
import static org.lwjgl.opengl.GL46.glBindBuffer;
import static org.lwjgl.opengl.GL46.glCopyNamedBufferSubData;
import static org.lwjgl.opengl.GL46.glDeleteBuffers;
import static org.lwjgl.opengl.GL46.glFlushMappedNamedBufferRange;
import static org.lwjgl.opengl.GL46.glNamedBufferStorage;
import static org.lwjgl.opengl.GL46.nglBindBuffersRange;
import static org.lwjgl.opengl.GL46.nglCreateBuffers;
import static org.lwjgl.opengl.GL46.nglDeleteBuffers;
import static org.lwjgl.opengl.GL46.nglMapNamedBufferRange;
import static org.lwjgl.opengl.GL46.nglNamedBufferSubData;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import org.lwjgl.system.Pointer; import org.lwjgl.system.Pointer;
@ -30,7 +15,7 @@ public class IndirectBuffers {
public static final long PTR_SIZE = Pointer.POINTER_SIZE; public static final long PTR_SIZE = Pointer.POINTER_SIZE;
// DRAW COMMAND // DRAW COMMAND
public static final long DRAW_COMMAND_STRIDE = 36; public static final long DRAW_COMMAND_STRIDE = 44;
public static final long DRAW_COMMAND_OFFSET = 0; public static final long DRAW_COMMAND_OFFSET = 0;
// BITS // BITS
@ -180,15 +165,16 @@ public class IndirectBuffers {
FlwMemoryTracker._freeGPUMemory(maxDrawCount * DRAW_COMMAND_STRIDE); FlwMemoryTracker._freeGPUMemory(maxDrawCount * DRAW_COMMAND_STRIDE);
} }
public void bindAll() { public void bindForCompute() {
bindN(BUFFER_COUNT); multiBind(BUFFER_COUNT);
} }
public void bindObjectAndTarget() { public void bindForDraw() {
bindN(2); multiBind(BUFFER_COUNT);
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, draw);
} }
private void bindN(int bufferCount) { private void multiBind(int bufferCount) {
if (bufferCount > BUFFER_COUNT) { if (bufferCount > BUFFER_COUNT) {
throw new IllegalArgumentException("Can't bind more than " + BUFFER_COUNT + " buffers"); throw new IllegalArgumentException("Can't bind more than " + BUFFER_COUNT + " buffers");
} }
@ -197,10 +183,6 @@ public class IndirectBuffers {
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, bufferCount, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, bufferCount, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
} }
void bindIndirectBuffer() {
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, draw);
}
void flushBatchIDs(long length) { void flushBatchIDs(long length) {
glFlushMappedNamedBufferRange(batch, 0, length); glFlushMappedNamedBufferRange(batch, 0, length);
} }

View file

@ -2,13 +2,18 @@ package com.jozufozu.flywheel.backend.instancing.indirect;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.core.Components; import com.jozufozu.flywheel.api.pipeline.Pipeline;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.core.Pipelines;
import com.jozufozu.flywheel.core.SourceComponent; import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.layout.LayoutItem; import com.jozufozu.flywheel.core.layout.LayoutItem;
import com.jozufozu.flywheel.core.source.CompilationContext; 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.GlslBuilder;
import com.jozufozu.flywheel.core.source.generate.GlslExpr; import com.jozufozu.flywheel.core.source.generate.GlslExpr;
@ -18,16 +23,24 @@ public class IndirectComponent implements SourceComponent {
private static final String UNPACK_ARG = "p"; private static final String UNPACK_ARG = "p";
private static final GlslExpr.Variable UNPACKING_VARIABLE = GlslExpr.variable(UNPACK_ARG); 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 List<LayoutItem> layoutItems;
private final ImmutableList<SourceFile> included;
public IndirectComponent(List<LayoutItem> layoutItems) { public IndirectComponent(Pipeline.InstanceAssemblerContext ctx) {
this.layoutItems = layoutItems; this(ctx.sources(), ctx.structType());
}
public IndirectComponent(ShaderSources sources, StructType<?> structType) {
this.layoutItems = structType.getLayout().layoutItems;
included = ImmutableList.of(sources.find(Pipelines.Files.UTIL_TYPES));
} }
@Override @Override
public Collection<? extends SourceComponent> included() { public Collection<? extends SourceComponent> included() {
return List.of(Components.UTIL_TYPES.getFile()); return included;
} }
@Override @Override
@ -36,22 +49,20 @@ public class IndirectComponent implements SourceComponent {
} }
@Override @Override
public String source(CompilationContext ctx) { public String source() {
var generated = generateIndirect("IndirectStruct"); return generateIndirect();
return ctx.generatedHeader(generated, name().toString()) + generated;
} }
public String generateIndirect(String structName) { public String generateIndirect() {
var builder = new GlslBuilder(); var builder = new GlslBuilder();
final var packedStructName = structName + "_packed"; builder.define("FlwInstance", STRUCT_NAME);
builder.define("FlwInstance", structName); builder.define("FlwPackedInstance", PACKED_STRUCT_NAME);
builder.define("FlwPackedInstance", packedStructName);
var packed = builder.struct(); var packed = builder.struct();
builder.blankLine(); builder.blankLine();
var instance = builder.struct(); var instance = builder.struct();
packed.setName(packedStructName); packed.setName(PACKED_STRUCT_NAME);
instance.setName(structName); instance.setName(STRUCT_NAME);
for (var field : layoutItems) { for (var field : layoutItems) {
field.addPackedToStruct(packed); field.addPackedToStruct(packed);
@ -60,18 +71,21 @@ public class IndirectComponent implements SourceComponent {
builder.blankLine(); builder.blankLine();
var func = builder.function() builder.function()
.returnType(structName) .signature(FnSignature.create()
.returnType(STRUCT_NAME)
.name("flw_unpackInstance") .name("flw_unpackInstance")
.argumentIn(packedStructName, UNPACK_ARG); .arg(PACKED_STRUCT_NAME, UNPACK_ARG)
.build())
var args = layoutItems.stream() .body(this::generateUnpackingBody);
.map(layoutItem -> layoutItem.unpackField(UNPACKING_VARIABLE))
.map(GlslExpr::minPrint)
.collect(Collectors.joining(", "));
func.statement("return " + structName + "(" + args + ");");
return builder.build(); 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

@ -3,31 +3,22 @@ package com.jozufozu.flywheel.backend.instancing.indirect;
import static org.lwjgl.opengl.GL42.GL_COMMAND_BARRIER_BIT; import static org.lwjgl.opengl.GL42.GL_COMMAND_BARRIER_BIT;
import static org.lwjgl.opengl.GL42.glMemoryBarrier; import static org.lwjgl.opengl.GL42.glMemoryBarrier;
import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BARRIER_BIT; import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BARRIER_BIT;
import static org.lwjgl.opengl.GL46.glBindVertexArray; import static org.lwjgl.opengl.GL46.*;
import static org.lwjgl.opengl.GL46.glCreateVertexArrays;
import static org.lwjgl.opengl.GL46.glDeleteVertexArrays;
import static org.lwjgl.opengl.GL46.glDispatchCompute;
import static org.lwjgl.opengl.GL46.glEnableVertexArrayAttrib;
import static org.lwjgl.opengl.GL46.glVertexArrayElementBuffer;
import static org.lwjgl.opengl.GL46.glVertexArrayVertexBuffer;
import com.jozufozu.flywheel.api.RenderStage; import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.instancer.InstancedPart; import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType; import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.struct.StructWriter;
import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram; import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.instancing.PipelineCompiler; import com.jozufozu.flywheel.backend.instancing.compile.FlwCompiler;
import com.jozufozu.flywheel.core.Components; import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.Materials; import com.jozufozu.flywheel.core.Pipelines;
import com.jozufozu.flywheel.core.QuadConverter; import com.jozufozu.flywheel.core.QuadConverter;
import com.jozufozu.flywheel.core.uniform.UniformBuffer;
public class IndirectCullingGroup<T extends InstancedPart> { public class IndirectCullingGroup<T extends InstancedPart> {
private static final int BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT; private static final int BARRIER_BITS = GL_SHADER_STORAGE_BARRIER_BIT | GL_COMMAND_BARRIER_BIT;
final StructWriter<T> storageBufferWriter;
final GlProgram compute; final GlProgram compute;
final GlProgram draw; final GlProgram draw;
private final VertexType vertexType; private final VertexType vertexType;
@ -48,7 +39,6 @@ public class IndirectCullingGroup<T extends InstancedPart> {
IndirectCullingGroup(StructType<T> structType, VertexType vertexType) { IndirectCullingGroup(StructType<T> structType, VertexType vertexType) {
this.vertexType = vertexType; this.vertexType = vertexType;
storageBufferWriter = structType.getWriter();
objectStride = structType.getLayout() objectStride = structType.getLayout()
.getStride(); .getStride();
@ -65,8 +55,8 @@ public class IndirectCullingGroup<T extends InstancedPart> {
.quads2Tris(2048).glBuffer; .quads2Tris(2048).glBuffer;
setupVertexArray(); setupVertexArray();
compute = ComputeCullerCompiler.INSTANCE.get(structType); compute = FlwCompiler.INSTANCE.getCullingProgram(structType);
draw = PipelineCompiler.INSTANCE.get(new PipelineCompiler.Context(vertexType, Materials.SHULKER, structType, Components.WORLD, Components.INDIRECT)); draw = FlwCompiler.INSTANCE.getPipelineProgram(vertexType, structType, Components.WORLD, Pipelines.INDIRECT);
} }
private void setupVertexArray() { private void setupVertexArray() {
@ -116,11 +106,8 @@ public class IndirectCullingGroup<T extends InstancedPart> {
uploadInstanceData(); uploadInstanceData();
uploadIndirectCommands(); uploadIndirectCommands();
UniformBuffer.getInstance()
.sync();
compute.bind(); compute.bind();
buffers.bindAll(); buffers.bindForCompute();
var groupCount = (instanceCountThisFrame + 31) >> 5; // ceil(instanceCount / 32) var groupCount = (instanceCountThisFrame + 31) >> 5; // ceil(instanceCount / 32)
glDispatchCompute(groupCount, 1, 1); glDispatchCompute(groupCount, 1, 1);
@ -134,11 +121,7 @@ public class IndirectCullingGroup<T extends InstancedPart> {
draw.bind(); draw.bind();
glBindVertexArray(vertexArray); glBindVertexArray(vertexArray);
buffers.bindObjectAndTarget(); buffers.bindForDraw();
buffers.bindIndirectBuffer();
UniformBuffer.getInstance()
.sync();
memoryBarrier(); memoryBarrier();

View file

@ -5,6 +5,7 @@ import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.RenderStage; import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.instancer.InstancedPart; import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.core.ComponentRegistry;
public final class IndirectDraw<T extends InstancedPart> { public final class IndirectDraw<T extends InstancedPart> {
final IndirectInstancer<T> instancer; final IndirectInstancer<T> instancer;
@ -13,6 +14,9 @@ public final class IndirectDraw<T extends InstancedPart> {
final RenderStage stage; final RenderStage stage;
int baseInstance = -1; int baseInstance = -1;
final int vertexMaterialID;
final int fragmentMaterialID;
boolean needsFullWrite = true; boolean needsFullWrite = true;
IndirectDraw(IndirectInstancer<T> instancer, Material material, RenderStage stage, IndirectMeshPool.BufferedMesh mesh) { IndirectDraw(IndirectInstancer<T> instancer, Material material, RenderStage stage, IndirectMeshPool.BufferedMesh mesh) {
@ -20,6 +24,9 @@ public final class IndirectDraw<T extends InstancedPart> {
this.material = material; this.material = material;
this.stage = stage; this.stage = stage;
this.mesh = mesh; this.mesh = mesh;
this.vertexMaterialID = ComponentRegistry.materials.getVertexID(material);
this.fragmentMaterialID = ComponentRegistry.materials.getFragmentID(material);
} }
public void prepare(int baseInstance) { public void prepare(int baseInstance) {
@ -51,5 +58,8 @@ public final class IndirectDraw<T extends InstancedPart> {
MemoryUtil.memPutInt(ptr + 16, baseInstance); // baseInstance MemoryUtil.memPutInt(ptr + 16, baseInstance); // baseInstance
boundingSphere.getToAddress(ptr + 20); // boundingSphere boundingSphere.getToAddress(ptr + 20); // boundingSphere
MemoryUtil.memPutInt(ptr + 36, vertexMaterialID); // vertexMaterialID
MemoryUtil.memPutInt(ptr + 40, fragmentMaterialID); // fragmentMaterialID
} }
} }

View file

@ -10,6 +10,7 @@ import java.util.List;
import com.jozufozu.flywheel.api.RenderStage; import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.instancer.InstancedPart; import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.util.Textures;
public class IndirectDrawSet<T extends InstancedPart> { public class IndirectDrawSet<T extends InstancedPart> {
@ -37,6 +38,7 @@ public class IndirectDrawSet<T extends InstancedPart> {
var material = batch.material; var material = batch.material;
material.setup(); material.setup();
Textures.bindActiveTextures();
glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, i * stride, 1, stride); glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, i * stride, 1, stride);
material.clear(); material.clear();
} }

View file

@ -7,7 +7,6 @@ import java.util.Set;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
import com.jozufozu.flywheel.api.RenderStage; import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.context.ContextShader;
import com.jozufozu.flywheel.api.instancer.InstancedPart; import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.instancer.Instancer; import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.struct.StructType; import com.jozufozu.flywheel.api.struct.StructType;
@ -19,6 +18,7 @@ import com.jozufozu.flywheel.backend.instancing.TaskExecutor;
import com.jozufozu.flywheel.core.RenderContext; import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.core.model.Model; import com.jozufozu.flywheel.core.model.Model;
import com.jozufozu.flywheel.util.FlwUtil; import com.jozufozu.flywheel.util.FlwUtil;
import com.jozufozu.flywheel.core.context.SimpleContext;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Camera; import net.minecraft.client.Camera;
@ -37,12 +37,12 @@ public class IndirectEngine implements Engine {
*/ */
private final Set<InstanceManager<?>> instanceManagers = FlwUtil.createWeakHashSet(); private final Set<InstanceManager<?>> instanceManagers = FlwUtil.createWeakHashSet();
protected final ContextShader context; protected final SimpleContext context;
protected final int sqrMaxOriginDistance; protected final int sqrMaxOriginDistance;
protected BlockPos originCoordinate = BlockPos.ZERO; protected BlockPos originCoordinate = BlockPos.ZERO;
public IndirectEngine(ContextShader context, int sqrMaxOriginDistance) { public IndirectEngine(SimpleContext context, int sqrMaxOriginDistance) {
this.context = context; this.context = context;
this.sqrMaxOriginDistance = sqrMaxOriginDistance; this.sqrMaxOriginDistance = sqrMaxOriginDistance;
} }

View file

@ -3,12 +3,13 @@ package com.jozufozu.flywheel.backend.instancing.instancing;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.pipeline.Pipeline;
import com.jozufozu.flywheel.core.SourceComponent; import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.layout.LayoutItem; import com.jozufozu.flywheel.core.layout.LayoutItem;
import com.jozufozu.flywheel.core.source.CompilationContext; 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.GlslBuilder;
import com.jozufozu.flywheel.core.source.generate.GlslExpr; import com.jozufozu.flywheel.core.source.generate.GlslExpr;
@ -16,13 +17,17 @@ import net.minecraft.resources.ResourceLocation;
public class InstancedArraysComponent implements SourceComponent { public class InstancedArraysComponent implements SourceComponent {
private static final String ATTRIBUTE_SUFFIX = "_vertex_in"; private static final String ATTRIBUTE_SUFFIX = "_vertex_in";
private static final String STRUCT_NAME = "Instance";
private final List<LayoutItem> layoutItems; private final List<LayoutItem> layoutItems;
private final int baseIndex; private final int baseIndex;
public InstancedArraysComponent(List<LayoutItem> layoutItems, int baseIndex) { public InstancedArraysComponent(Pipeline.InstanceAssemblerContext ctx) {
this.layoutItems = layoutItems; this.layoutItems = ctx.structType()
this.baseIndex = baseIndex; .getLayout().layoutItems;
this.baseIndex = ctx.vertexType()
.getLayout()
.getAttributeCount();
} }
@Override @Override
@ -30,20 +35,15 @@ public class InstancedArraysComponent implements SourceComponent {
return Collections.emptyList(); return Collections.emptyList();
} }
@Override
public String source(CompilationContext ctx) {
var generated = generateInstancedArrays("Instance");
return ctx.generatedHeader(generated, name().toString()) + generated;
}
@Override @Override
public ResourceLocation name() { public ResourceLocation name() {
return Flywheel.rl("generated_instanced_arrays"); return Flywheel.rl("generated_instanced_arrays");
} }
public String generateInstancedArrays(String structName) { @Override
public String source() {
var builder = new GlslBuilder(); var builder = new GlslBuilder();
builder.define("FlwInstance", structName); builder.define("FlwInstance", STRUCT_NAME);
int i = baseIndex; int i = baseIndex;
for (var field : layoutItems) { for (var field : layoutItems) {
@ -60,7 +60,7 @@ public class InstancedArraysComponent implements SourceComponent {
builder.blankLine(); builder.blankLine();
var structBuilder = builder.struct(); var structBuilder = builder.struct();
structBuilder.setName(structName); structBuilder.setName(STRUCT_NAME);
for (var field : layoutItems) { for (var field : layoutItems) {
field.addToStruct(structBuilder); field.addToStruct(structBuilder);
@ -68,18 +68,18 @@ public class InstancedArraysComponent implements SourceComponent {
builder.blankLine(); builder.blankLine();
var func = builder.function() // unpacking function
.returnType(structName) builder.function()
.name("flw_unpackInstance"); .signature(FnSignature.of(STRUCT_NAME, "flw_unpackInstance"))
.body(this::generateUnpackingBody);
var args = layoutItems.stream()
.map(it -> new GlslExpr.Variable(it.name() + ATTRIBUTE_SUFFIX))
.map(GlslExpr::minPrint)
.collect(Collectors.joining(", "));
func.statement("return " + structName + "(" + args + ");");
return builder.build(); 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

@ -7,7 +7,6 @@ import java.util.Set;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
import com.jozufozu.flywheel.api.RenderStage; import com.jozufozu.flywheel.api.RenderStage;
import com.jozufozu.flywheel.api.context.ContextShader;
import com.jozufozu.flywheel.api.instancer.InstancedPart; import com.jozufozu.flywheel.api.instancer.InstancedPart;
import com.jozufozu.flywheel.api.instancer.Instancer; import com.jozufozu.flywheel.api.instancer.Instancer;
import com.jozufozu.flywheel.api.struct.StructType; import com.jozufozu.flywheel.api.struct.StructType;
@ -15,12 +14,13 @@ import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.gl.GlTextureUnit; import com.jozufozu.flywheel.backend.gl.GlTextureUnit;
import com.jozufozu.flywheel.backend.instancing.Engine; import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.InstanceManager; import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.PipelineCompiler;
import com.jozufozu.flywheel.backend.instancing.TaskExecutor; import com.jozufozu.flywheel.backend.instancing.TaskExecutor;
import com.jozufozu.flywheel.core.Components; import com.jozufozu.flywheel.backend.instancing.compile.FlwCompiler;
import com.jozufozu.flywheel.core.ComponentRegistry;
import com.jozufozu.flywheel.core.Pipelines;
import com.jozufozu.flywheel.core.RenderContext; import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.core.context.SimpleContext;
import com.jozufozu.flywheel.core.model.Model; import com.jozufozu.flywheel.core.model.Model;
import com.jozufozu.flywheel.core.uniform.UniformBuffer;
import com.jozufozu.flywheel.util.FlwUtil; import com.jozufozu.flywheel.util.FlwUtil;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
@ -40,12 +40,12 @@ public class InstancingEngine implements Engine {
*/ */
private final Set<InstanceManager<?>> instanceManagers = FlwUtil.createWeakHashSet(); private final Set<InstanceManager<?>> instanceManagers = FlwUtil.createWeakHashSet();
protected final ContextShader context; protected final SimpleContext context;
protected final int sqrMaxOriginDistance; protected final int sqrMaxOriginDistance;
protected BlockPos originCoordinate = BlockPos.ZERO; protected BlockPos originCoordinate = BlockPos.ZERO;
public InstancingEngine(ContextShader context, int sqrMaxOriginDistance) { public InstancingEngine(SimpleContext context, int sqrMaxOriginDistance) {
this.context = context; this.context = context;
this.sqrMaxOriginDistance = sqrMaxOriginDistance; this.sqrMaxOriginDistance = sqrMaxOriginDistance;
} }
@ -116,12 +116,13 @@ public class InstancingEngine implements Engine {
var structType = desc.instance(); var structType = desc.instance();
var material = desc.material(); var material = desc.material();
var ctx = new PipelineCompiler.Context(vertexType, material, structType, context, Components.INSTANCED_ARRAYS); var program = FlwCompiler.INSTANCE.getPipelineProgram(vertexType, structType, context, Pipelines.INSTANCED_ARRAYS);
program.bind();
PipelineCompiler.INSTANCE.getProgram(ctx) var uniformLocation = program.getUniformLocation("_flw_materialID_instancing");
.bind(); var vertexID = ComponentRegistry.materials.getVertexID(material);
UniformBuffer.getInstance() var fragmentID = ComponentRegistry.materials.getFragmentID(material);
.sync(); GL32.glUniform2ui(uniformLocation, vertexID, fragmentID);
} }
@Override @Override

View file

@ -8,7 +8,7 @@ import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.BackendType; import com.jozufozu.flywheel.backend.BackendType;
import com.jozufozu.flywheel.backend.SimpleBackendType; import com.jozufozu.flywheel.backend.SimpleBackendType;
import com.jozufozu.flywheel.core.BackendTypes; import com.jozufozu.flywheel.core.BackendTypes;
import com.jozufozu.flywheel.core.uniform.FrustumProvider; import com.jozufozu.flywheel.core.uniform.FlwShaderUniforms;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType;
@ -111,17 +111,17 @@ public class FlwCommands {
commandBuilder.command.then(Commands.literal("debugFrustum") commandBuilder.command.then(Commands.literal("debugFrustum")
.then(Commands.literal("pause") .then(Commands.literal("pause")
.executes(context -> { .executes(context -> {
FrustumProvider.PAUSED = true; FlwShaderUniforms.FRUSTUM_PAUSED = true;
return 1; return 1;
})) }))
.then(Commands.literal("unpause") .then(Commands.literal("unpause")
.executes(context -> { .executes(context -> {
FrustumProvider.PAUSED = false; FlwShaderUniforms.FRUSTUM_PAUSED = false;
return 1; return 1;
})) }))
.then(Commands.literal("capture") .then(Commands.literal("capture")
.executes(context -> { .executes(context -> {
FrustumProvider.CAPTURE = true; FlwShaderUniforms.FRUSTUM_CAPTURE = true;
return 1; return 1;
}))); })));

View file

@ -4,6 +4,8 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -14,6 +16,7 @@ import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.backend.instancing.batching.BatchingEngine; import com.jozufozu.flywheel.backend.instancing.batching.BatchingEngine;
import com.jozufozu.flywheel.backend.instancing.indirect.IndirectEngine; import com.jozufozu.flywheel.backend.instancing.indirect.IndirectEngine;
import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine; import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine;
import com.jozufozu.flywheel.core.pipeline.SimplePipeline;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.TextComponent; import net.minecraft.network.chat.TextComponent;
@ -55,6 +58,7 @@ public class BackendTypes {
.fallback(() -> BackendTypes.BATCHING) .fallback(() -> BackendTypes.BATCHING)
.supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.getInstance() .supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.getInstance()
.instancedArraysSupported()) .instancedArraysSupported())
.pipelineShader(Pipelines.INSTANCED_ARRAYS)
.register(); .register();
/** /**
@ -68,6 +72,7 @@ public class BackendTypes {
.fallback(() -> BackendTypes.INSTANCING) .fallback(() -> BackendTypes.INSTANCING)
.supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.getInstance() .supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.getInstance()
.supportsIndirect()) .supportsIndirect())
.pipelineShader(Pipelines.INDIRECT)
.register(); .register();
public static BackendType register(BackendType type) { public static BackendType register(BackendType type) {
@ -94,4 +99,12 @@ public class BackendTypes {
} }
public static Collection<SimplePipeline> availablePipelineShaders() {
return BACKEND_TYPES.values()
.stream()
.filter(BackendType::supported)
.map(BackendType::pipelineShader)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
} }

View file

@ -3,31 +3,34 @@ package com.jozufozu.flywheel.core;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.api.struct.StructType; import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.api.uniform.UniformProvider; import com.jozufozu.flywheel.api.uniform.ShaderUniforms;
import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.api.context.ContextShader;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
public class ComponentRegistry { public class ComponentRegistry {
private static final Registry<UniformProvider> uniformProviders = new Registry<>(); private static final Registry<ShaderUniforms> uniformProviders = new Registry<>();
public static final Set<Material> materials = new HashSet<>(); public static final MaterialRegistry materials = new MaterialRegistry();
public static final Set<StructType<?>> structTypes = new HashSet<>(); public static final Set<StructType<?>> structTypes = new HashSet<>();
public static final Set<VertexType> vertexTypes = new HashSet<>(); public static final Set<VertexType> vertexTypes = new HashSet<>();
public static final Set<ContextShader> contextShaders = new HashSet<>(); public static final Set<Context> contextShaders = new HashSet<>();
// TODO: fill out the rest of the registry // TODO: fill out the rest of the registry
public static <T extends Material> T register(T material) { public static <T extends Material> T register(T material) {
materials.add(material); return materials.add(material);
return material;
} }
public static <T extends StructType<?>> T register(T type) { public static <T extends StructType<?>> T register(T type) {
@ -40,31 +43,90 @@ public class ComponentRegistry {
return vertexType; return vertexType;
} }
public static ContextShader register(ContextShader contextShader) { public static <T extends Context> T register(T contextShader) {
contextShaders.add(contextShader); contextShaders.add(contextShader);
return contextShader; return contextShader;
} }
public static <T extends UniformProvider> T register(T provider) { public static <T extends ShaderUniforms> T register(T provider) {
return uniformProviders.register(provider.getUniformShader() return uniformProviders.register(provider.uniformShader(), provider);
.getFileLoc(), provider);
} }
public static Collection<UniformProvider> getAllUniformProviders() { public static Collection<ShaderUniforms> getAllUniformProviders() {
return Collections.unmodifiableCollection(uniformProviders.objects); return Collections.unmodifiableCollection(uniformProviders.objects);
} }
@Nullable
public static ShaderUniforms getUniformProvider(ResourceLocation loc) {
return uniformProviders.get(loc);
}
private static class Registry<T> { private static class Registry<T> {
private final Set<ResourceLocation> files = new HashSet<>(); private final Map<ResourceLocation, T> files = new HashMap<>();
private final List<T> objects = new ArrayList<>(); private final List<T> objects = new ArrayList<>();
public <O extends T> O register(ResourceLocation loc, O object) { public <O extends T> O register(ResourceLocation loc, O object) {
if (files.contains(loc)) { if (files.containsKey(loc)) {
throw new IllegalArgumentException("Shader file already registered: " + loc); throw new IllegalArgumentException("Shader file already registered: " + loc);
} }
files.add(loc); files.put(loc, object);
objects.add(object); objects.add(object);
return object; return object;
} }
@Nullable
public T get(ResourceLocation loc) {
return files.get(loc);
}
}
public static class MaterialRegistry {
private final Set<Material> materials = new HashSet<>();
private final MaterialSources vertexSources = new MaterialSources();
private final MaterialSources fragmentSources = new MaterialSources();
public <T extends Material> T add(T material) {
materials.add(material);
vertexSources.register(material.vertexShader());
fragmentSources.register(material.fragmentShader());
return material;
}
/**
* @return a list of vertex shader sources where the index in the list is the shader's ID.
*/
public List<ResourceLocation> vertexSources() {
return vertexSources.sourceView;
}
/**
* @return a list of fragment shader sources where the index in the list is the shader's ID.
*/
public List<ResourceLocation> fragmentSources() {
return fragmentSources.sourceView;
}
public int getVertexID(Material material) {
return vertexSources.orderedSources.indexOf(material.vertexShader());
}
public int getFragmentID(Material material) {
return fragmentSources.orderedSources.indexOf(material.fragmentShader());
}
private static class MaterialSources {
private final Set<ResourceLocation> registered = new HashSet<>();
private final List<ResourceLocation> orderedSources = new ArrayList<>();
private final List<ResourceLocation> sourceView = Collections.unmodifiableList(orderedSources);
public void register(ResourceLocation vertexShader) {
if (registered.add(vertexShader)) {
orderedSources.add(vertexShader);
}
}
}
} }
} }

View file

@ -1,22 +1,9 @@
package com.jozufozu.flywheel.core; package com.jozufozu.flywheel.core;
import java.util.function.BiConsumer;
import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.context.ContextShader; import com.jozufozu.flywheel.core.context.SimpleContext;
import com.jozufozu.flywheel.api.pipeline.PipelineShader;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.instancing.indirect.IndirectComponent;
import com.jozufozu.flywheel.backend.instancing.instancing.InstancedArraysComponent;
import com.jozufozu.flywheel.core.crumbling.CrumblingProgram;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.SourceChecks;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.structs.StructTypes; import com.jozufozu.flywheel.core.structs.StructTypes;
import com.jozufozu.flywheel.core.uniform.FogProvider; import com.jozufozu.flywheel.core.uniform.FlwShaderUniforms;
import com.jozufozu.flywheel.core.uniform.FrustumProvider;
import com.jozufozu.flywheel.core.uniform.ViewProvider;
import com.jozufozu.flywheel.core.vertex.Formats; import com.jozufozu.flywheel.core.vertex.Formats;
import com.jozufozu.flywheel.util.ResourceUtil; import com.jozufozu.flywheel.util.ResourceUtil;
@ -25,107 +12,32 @@ import net.minecraft.resources.ResourceLocation;
public class Components { public class Components {
public static final ViewProvider VIEW_PROVIDER = ComponentRegistry.register(new ViewProvider()); public static final FlwShaderUniforms UNIFORM_PROVIDER = ComponentRegistry.register(new FlwShaderUniforms());
public static final FogProvider FOG_PROVIDER = ComponentRegistry.register(new FogProvider()); public static final SimpleContext WORLD = ComponentRegistry.register(new SimpleContext(Files.WORLD_VERTEX, Files.WORLD_FRAGMENT));
public static final FrustumProvider FRUSTUM_PROVIDER = ComponentRegistry.register(new FrustumProvider()); public static final SimpleContext CRUMBLING = ComponentRegistry.register(new SimpleContext(Files.WORLD_VERTEX, Files.CRUMBLING_FRAGMENT));
public static final ContextShader WORLD = ComponentRegistry.register(new ContextShader(WorldProgram::new, Files.WORLD_VERTEX, Files.WORLD_FRAGMENT));
public static final ContextShader CRUMBLING = ComponentRegistry.register(new ContextShader(CrumblingProgram::new, Files.WORLD_VERTEX, Files.CRUMBLING_FRAGMENT));
public static final PipelineShader INSTANCED_ARRAYS = new PipelineShader(GLSLVersion.V420, Pipeline.INSTANCED_ARRAYS_DRAW, Pipeline.DRAW_FRAGMENT, (vertexType, structType) -> new InstancedArraysComponent(structType.getLayout().layoutItems, vertexType.getLayout()
.getAttributeCount()));
public static final PipelineShader INDIRECT = new PipelineShader(GLSLVersion.V460, Pipeline.INDIRECT_DRAW, Pipeline.DRAW_FRAGMENT, (vertexType, structType) -> new IndirectComponent(structType.getLayout().layoutItems));
public static final FileResolution UTIL_TYPES = FileResolution.get(Flywheel.rl("util/types.glsl"));
public static void init() { public static void init() {
Files.init();
Formats.init(); Formats.init();
StructTypes.init(); StructTypes.init();
Materials.init(); Materials.init();
} Pipelines.init();
public static class Pipeline {
public static final FileResolution DRAW_FRAGMENT = pipeline("pipeline/draw.frag");
public static final FileResolution INSTANCED_ARRAYS_DRAW = pipeline("pipeline/instanced_arrays_draw.vert");
public static final FileResolution INDIRECT_DRAW = pipeline("pipeline/indirect_draw.vert");
public static final FileResolution INDIRECT_CULL = pipeline("pipeline/indirect_cull.glsl");
private static FileResolution pipeline(String name) {
return FileResolution.get(Flywheel.rl(name))
.validateWith(Checks.PIPELINE);
}
} }
public static class Files { public static class Files {
public static final FileResolution VIEW_UNIFORMS = uniform(Flywheel.rl("uniform/view.glsl")); public static final ResourceLocation UNIFORMS = Flywheel.rl("uniform/flywheel.glsl");
public static final FileResolution FOG_UNIFORMS = uniform(Flywheel.rl("uniform/fog.glsl")); public static final ResourceLocation BLOCK_LAYOUT = ResourceUtil.subPath(Names.BLOCK, ".vert");
public static final FileResolution FRUSTUM_UNIFORMS = uniform(Flywheel.rl("uniform/frustum.glsl")); public static final ResourceLocation POS_TEX_NORMAL_LAYOUT = ResourceUtil.subPath(Names.POS_TEX_NORMAL, ".vert");
public static final FileResolution BLOCK_LAYOUT = layoutVertex(ResourceUtil.subPath(Names.BLOCK, ".vert")); public static final ResourceLocation TRANSFORMED = ResourceUtil.subPath(Names.TRANSFORMED, ".vert");
public static final FileResolution POS_TEX_NORMAL_LAYOUT = layoutVertex(ResourceUtil.subPath(Names.POS_TEX_NORMAL, ".vert")); public static final ResourceLocation ORIENTED = ResourceUtil.subPath(Names.ORIENTED, ".vert");
public static final FileResolution TRANSFORMED = instanceVertex(ResourceUtil.subPath(Names.TRANSFORMED, ".vert")); public static final ResourceLocation DEFAULT_VERTEX = ResourceUtil.subPath(Names.DEFAULT, ".vert");
public static final FileResolution ORIENTED = instanceVertex(ResourceUtil.subPath(Names.ORIENTED, ".vert")); public static final ResourceLocation SHADED_VERTEX = ResourceUtil.subPath(Names.SHADED, ".vert");
public static final FileResolution DEFAULT_VERTEX = materialVertex(ResourceUtil.subPath(Names.DEFAULT, ".vert")); public static final ResourceLocation DEFAULT_FRAGMENT = ResourceUtil.subPath(Names.DEFAULT, ".frag");
public static final FileResolution SHADED_VERTEX = materialVertex(ResourceUtil.subPath(Names.SHADED, ".vert")); public static final ResourceLocation CUTOUT_FRAGMENT = ResourceUtil.subPath(Names.CUTOUT, ".frag");
public static final FileResolution DEFAULT_FRAGMENT = materialFragment(ResourceUtil.subPath(Names.DEFAULT, ".frag")); public static final ResourceLocation WORLD_VERTEX = ResourceUtil.subPath(Names.WORLD, ".vert");
public static final FileResolution CUTOUT_FRAGMENT = materialFragment(ResourceUtil.subPath(Names.CUTOUT, ".frag")); public static final ResourceLocation WORLD_FRAGMENT = ResourceUtil.subPath(Names.WORLD, ".frag");
public static final FileResolution WORLD_VERTEX = contextVertex(ResourceUtil.subPath(Names.WORLD, ".vert")); public static final ResourceLocation CRUMBLING_VERTEX = ResourceUtil.subPath(Names.CRUMBLING, ".vert");
public static final FileResolution WORLD_FRAGMENT = contextFragment(ResourceUtil.subPath(Names.WORLD, ".frag")); public static final ResourceLocation CRUMBLING_FRAGMENT = ResourceUtil.subPath(Names.CRUMBLING, ".frag");
public static final FileResolution CRUMBLING_VERTEX = contextVertex(ResourceUtil.subPath(Names.CRUMBLING, ".vert"));
public static final FileResolution CRUMBLING_FRAGMENT = contextFragment(ResourceUtil.subPath(Names.CRUMBLING, ".frag"));
private static FileResolution compute(ResourceLocation rl) {
return FileResolution.get(rl);
}
private static FileResolution uniform(ResourceLocation location) {
return FileResolution.get(location);
}
private static FileResolution layoutVertex(ResourceLocation location) {
return FileResolution.get(location)
.validateWith(Checks.LAYOUT_VERTEX);
}
private static FileResolution instanceVertex(ResourceLocation location) {
return FileResolution.get(location); // .validateWith(Checks.INSTANCE_VERTEX);
}
private static FileResolution materialVertex(ResourceLocation location) {
return FileResolution.get(location)
.validateWith(Checks.MATERIAL_VERTEX);
}
private static FileResolution materialFragment(ResourceLocation location) {
return FileResolution.get(location)
.validateWith(Checks.MATERIAL_FRAGMENT);
}
private static FileResolution contextVertex(ResourceLocation location) {
return FileResolution.get(location)
.validateWith(Checks.CONTEXT_VERTEX);
}
private static FileResolution contextFragment(ResourceLocation location) {
return FileResolution.get(location)
.validateWith(Checks.CONTEXT_FRAGMENT);
}
public static void init() {
// noop, just in case
}
}
public static class Checks {
public static final BiConsumer<ErrorReporter, SourceFile> LAYOUT_VERTEX = SourceChecks.checkFunctionArity("flw_layoutVertex", 0);
public static final BiConsumer<ErrorReporter, SourceFile> INSTANCE_VERTEX = SourceChecks.checkFunctionParameterTypeExists("flw_instanceVertex", 1, 0);
public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_VERTEX = SourceChecks.checkFunctionArity("flw_materialVertex", 0);
public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_FRAGMENT = SourceChecks.checkFunctionArity("flw_materialFragment", 0);
public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_VERTEX = SourceChecks.checkFunctionArity("flw_contextVertex", 0);
public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_FRAGMENT = SourceChecks.checkFunctionArity("flw_contextFragment", 0)
.andThen(SourceChecks.checkFunctionArity("flw_initFragment", 0));
public static final BiConsumer<ErrorReporter, SourceFile> PIPELINE = SourceChecks.checkFunctionArity("main", 0);
} }
public static class Names { public static class Names {

View file

@ -1,104 +0,0 @@
package com.jozufozu.flywheel.core;
import org.lwjgl.opengl.GL46;
import org.lwjgl.system.MemoryStack;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.core.compile.DebugCompiler;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.util.Lazy;
import com.jozufozu.flywheel.util.joml.FrustumIntersection;
import com.mojang.blaze3d.systems.RenderSystem;
public class DebugRender {
private static final Lazy<GlProgram> SHADER = Lazy.of(() -> DebugCompiler.INSTANCE.get(new DebugCompiler.Context(Files.VERTEX, Files.FRAGMENT)));
private static final Lazy<Frustum> FRUSTUM_VBO = Lazy.of(Frustum::new);
public static void init() {
Files.init();
}
public static void updateFrustum(FrustumIntersection culler) {
FRUSTUM_VBO.get()
.upload(culler);
}
public static void drawFrustum() {
if (!FRUSTUM_VBO.isInitialized()) {
return;
}
RenderSystem.disableCull();
RenderSystem.enableBlend();
RenderSystem.defaultBlendFunc();
try (var ignored = GlStateTracker.getRestoreState()) {
SHADER.get()
.bind();
FRUSTUM_VBO.get()
.draw();
}
}
public static class Files {
public static final FileResolution VERTEX = FileResolution.get(Flywheel.rl("debug/debug.vert"));
public static final FileResolution FRAGMENT = FileResolution.get(Flywheel.rl("debug/debug.frag"));
public static void init() {
}
}
// FIXME: This never worked (and the thing it was meant to debug is already fixed),
// but it should be a quick turnaround
private static class Frustum {
private static final int[] indices = new int[]{
0, 2, 3, 0, 3, 1,
2, 6, 7, 2, 7, 3,
6, 4, 5, 6, 5, 7,
4, 0, 1, 4, 1, 5,
0, 4, 6, 0, 6, 2,
1, 5, 7, 1, 7, 3,
};
private static final int elementCount = indices.length;
private static final int indicesSize = elementCount * 4;
private static final int verticesSize = 3 * 8 * 4;
private final int buffer;
private final int vao;
public Frustum() {
// holy moly DSA is nice
buffer = GL46.glCreateBuffers();
GL46.glNamedBufferStorage(buffer, verticesSize + indicesSize, GL46.GL_DYNAMIC_STORAGE_BIT);
GL46.glNamedBufferSubData(buffer, 0, indices);
vao = GL46.glCreateVertexArrays();
GL46.glEnableVertexArrayAttrib(vao, 0);
GL46.glVertexArrayElementBuffer(vao, buffer);
GL46.glVertexArrayVertexBuffer(vao, 0, buffer, indicesSize, 3 * 4);
GL46.glVertexArrayAttribFormat(vao, 0, 3, GL46.GL_FLOAT, false, 0);
}
public void upload(FrustumIntersection culler) {
try (var stack = MemoryStack.stackPush()) {
var buf = stack.malloc(3 * 8 * 4);
culler.getCorners(buf);
GL46.glNamedBufferSubData(buffer, indicesSize, buf);
}
}
public void draw() {
GL46.glEnableVertexArrayAttrib(vao, 0);
GL46.glVertexArrayElementBuffer(vao, buffer);
GL46.glBindVertexArray(vao);
GL46.glDrawElements(GL46.GL_TRIANGLES, elementCount, GL46.GL_UNSIGNED_INT, 0);
}
}
}

View file

@ -0,0 +1,36 @@
package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.instancing.indirect.IndirectComponent;
import com.jozufozu.flywheel.backend.instancing.instancing.InstancedArraysComponent;
import com.jozufozu.flywheel.core.pipeline.SimplePipeline;
import net.minecraft.resources.ResourceLocation;
public class Pipelines {
public static final SimplePipeline INSTANCED_ARRAYS = SimplePipeline.builder()
.glslVersion(GLSLVersion.V420)
.vertex(Files.INSTANCED_ARRAYS_DRAW)
.fragment(Files.DRAW_FRAGMENT)
.assemblerFactory(InstancedArraysComponent::new)
.build();
public static final SimplePipeline INDIRECT = SimplePipeline.builder()
.glslVersion(GLSLVersion.V460)
.vertex(Files.INDIRECT_DRAW)
.fragment(Files.DRAW_FRAGMENT)
.assemblerFactory(IndirectComponent::new)
.build();
public static void init() {
// noop
}
public static class Files {
public static final ResourceLocation DRAW_FRAGMENT = Flywheel.rl("pipeline/draw.frag");
public static final ResourceLocation INSTANCED_ARRAYS_DRAW = Flywheel.rl("pipeline/instanced_arrays_draw.vert");
public static final ResourceLocation INDIRECT_DRAW = Flywheel.rl("pipeline/indirect_draw.vert");
public static final ResourceLocation INDIRECT_CULL = Flywheel.rl("pipeline/indirect_cull.glsl");
public static final ResourceLocation UTIL_TYPES = Flywheel.rl("util/types.glsl");
}
}

View file

@ -2,14 +2,12 @@ package com.jozufozu.flywheel.core;
import java.util.Collection; import java.util.Collection;
import com.jozufozu.flywheel.core.source.CompilationContext;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
public interface SourceComponent { public interface SourceComponent {
Collection<? extends SourceComponent> included(); Collection<? extends SourceComponent> included();
String source(CompilationContext ctx); String source();
ResourceLocation name(); ResourceLocation name();
} }

View file

@ -1,27 +0,0 @@
package com.jozufozu.flywheel.core;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.resources.ResourceLocation;
public class WorldProgram extends GlProgram {
// TODO: sampler registry?
protected int diffuseTex;
protected int overlayTex;
protected int lightTex;
public WorldProgram(ResourceLocation name, int handle) {
super(name, handle);
bind();
registerSamplers();
unbind();
}
protected void registerSamplers() {
diffuseTex = setSamplerBinding("flw_diffuseTex", 0);
overlayTex = setSamplerBinding("flw_overlayTex", 1);
lightTex = setSamplerBinding("flw_lightTex", 2);
}
}

View file

@ -1,95 +0,0 @@
package com.jozufozu.flywheel.core.compile;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
import com.jozufozu.flywheel.core.source.CompilationContext;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
/**
* Simple shader compiler that pulls no excessive tricks.<p>
* Useful for writing experimental shaders or
*/
public class DebugCompiler extends Memoizer<DebugCompiler.Context, GlProgram> {
public static final DebugCompiler INSTANCE = new DebugCompiler();
private final ShaderCompiler shaderCompiler;
private DebugCompiler() {
this.shaderCompiler = new ShaderCompiler();
}
@Override
public void invalidate() {
super.invalidate();
shaderCompiler.invalidate();
}
@Override
protected GlProgram _create(DebugCompiler.Context ctx) {
return new ProgramAssembler(ctx.vertex.getFileLoc())
.attachShader(shaderCompiler.vertex(ctx.vertex))
.attachShader(shaderCompiler.fragment(ctx.fragment))
.link()
.build(GlProgram::new);
}
@Override
protected void _destroy(GlProgram value) {
value.delete();
}
public static void invalidateAll(ReloadRenderersEvent ignored) {
INSTANCE.invalidate();
}
public record Context(FileResolution vertex, FileResolution fragment) {
}
/**
* Handles compilation and deletion of vertex shaders.
*/
private static class ShaderCompiler extends Memoizer<ShaderCompiler.Context, GlShader> {
public GlShader vertex(FileResolution source) {
return get(new Context(source, ShaderType.VERTEX));
}
public GlShader fragment(FileResolution source) {
return get(new Context(source, ShaderType.FRAGMENT));
}
@Override
protected GlShader _create(Context ctx) {
var index = new CompilationContext();
StringBuilder source = new StringBuilder(CompileUtil.generateHeader(GLSLVersion.V420, ctx.type));
var file = ctx.source.getFile();
for (var include : file.flattenedImports) {
source.append(include.source(index));
}
source.append(file.source(index));
try {
return new GlShader(source.toString(), ctx.type, ImmutableList.of(ctx.source.getFileLoc()));
} catch (ShaderCompilationException e) {
throw e.withErrorLog(index);
}
}
@Override
protected void _destroy(GlShader value) {
value.delete();
}
public record Context(FileResolution source, ShaderType type) {
}
}
}

View file

@ -1,22 +0,0 @@
package com.jozufozu.flywheel.core.compile;
import java.util.HashMap;
import java.util.Map;
public abstract class Memoizer<K, V> {
private final Map<K, V> map = new HashMap<>();
public V get(K key) {
return map.computeIfAbsent(key, this::_create);
}
public void invalidate() {
map.values().forEach(this::_destroy);
map.clear();
}
protected abstract V _create(K key);
protected abstract void _destroy(V value);
}

View file

@ -1,55 +0,0 @@
package com.jozufozu.flywheel.core.compile;
import static org.lwjgl.opengl.GL11.GL_TRUE;
import static org.lwjgl.opengl.GL20.GL_LINK_STATUS;
import static org.lwjgl.opengl.GL20.glAttachShader;
import static org.lwjgl.opengl.GL20.glCreateProgram;
import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
import static org.lwjgl.opengl.GL20.glGetProgrami;
import static org.lwjgl.opengl.GL20.glLinkProgram;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
import net.minecraft.resources.ResourceLocation;
public class ProgramAssembler {
public final int program;
private final ResourceLocation name;
public ProgramAssembler(ResourceLocation name) {
this.name = name;
this.program = glCreateProgram();
}
/**
* Links the attached shaders to this program.
*/
public ProgramAssembler link() {
glLinkProgram(this.program);
String log = glGetProgramInfoLog(this.program);
if (!log.isEmpty()) {
Backend.LOGGER.debug("Program link log for " + name + ": " + log);
}
int result = glGetProgrami(this.program, GL_LINK_STATUS);
if (result != GL_TRUE) {
throw new RuntimeException("Shader program linking failed, see log for details");
}
return this;
}
public ProgramAssembler attachShader(GlShader glShader) {
glAttachShader(this.program, glShader.handle());
return this;
}
public GlProgram build(GlProgram.Factory factory) {
return factory.create(name, program);
}
}

View file

@ -1,32 +0,0 @@
package com.jozufozu.flywheel.core.compile;
import org.lwjgl.opengl.GL20;
import com.jozufozu.flywheel.core.source.CompilationContext;
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
public class ShaderCompilationException extends ShaderLoadingException {
private final int shaderHandle;
public String errors = "";
public ShaderCompilationException(String message, int shaderHandle) {
super(message);
this.shaderHandle = shaderHandle;
}
public ShaderLoadingException withErrorLog(CompilationContext ctx) {
if (this.shaderHandle == -1)
return this;
this.errors = ctx.parseErrors(GL20.glGetShaderInfoLog(this.shaderHandle));
return this;
}
@Override
public String getMessage() {
return super.getMessage() + '\n' + this.errors;
}
}

View file

@ -0,0 +1,27 @@
package com.jozufozu.flywheel.core.context;
import com.jozufozu.flywheel.api.context.Context;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.resources.ResourceLocation;
public record SimpleContext(ResourceLocation vertexShader, ResourceLocation fragmentShader) implements Context {
@Override
public void onProgramLink(GlProgram program) {
program.bind();
program.setSamplerBinding("flw_diffuseTex", 0);
program.setSamplerBinding("flw_overlayTex", 1);
program.setSamplerBinding("flw_lightTex", 2);
GlProgram.unbind();
}
@Override
public ResourceLocation vertexShader() {
return vertexShader;
}
@Override
public ResourceLocation fragmentShader() {
return fragmentShader;
}
}

View file

@ -1,16 +0,0 @@
package com.jozufozu.flywheel.core.crumbling;
import com.jozufozu.flywheel.core.WorldProgram;
import net.minecraft.resources.ResourceLocation;
public class CrumblingProgram extends WorldProgram {
public CrumblingProgram(ResourceLocation name, int handle) {
super(name, handle);
}
@Override
protected void registerSamplers() {
diffuseTex = setSamplerBinding("flw_diffuseTex", 0);
}
}

View file

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

View file

@ -3,20 +3,20 @@ package com.jozufozu.flywheel.core.material;
import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.core.ComponentRegistry; import com.jozufozu.flywheel.core.ComponentRegistry;
import com.jozufozu.flywheel.core.Components; import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.source.FileResolution;
import net.minecraft.client.renderer.RenderStateShard; import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
public class SimpleMaterial implements Material { public class SimpleMaterial implements Material {
protected final FileResolution vertexShader; protected final ResourceLocation vertexShader;
protected final FileResolution fragmentShader; protected final ResourceLocation fragmentShader;
protected final Runnable setup; protected final Runnable setup;
protected final Runnable clear; protected final Runnable clear;
protected final RenderType batchingRenderType; protected final RenderType batchingRenderType;
protected final VertexTransformer vertexTransformer; protected final VertexTransformer vertexTransformer;
public SimpleMaterial(FileResolution vertexShader, FileResolution fragmentShader, Runnable setup, Runnable clear, RenderType batchingRenderType, VertexTransformer vertexTransformer) { public SimpleMaterial(ResourceLocation vertexShader, ResourceLocation fragmentShader, Runnable setup, Runnable clear, RenderType batchingRenderType, VertexTransformer vertexTransformer) {
this.vertexShader = vertexShader; this.vertexShader = vertexShader;
this.fragmentShader = fragmentShader; this.fragmentShader = fragmentShader;
this.setup = setup; this.setup = setup;
@ -30,12 +30,12 @@ public class SimpleMaterial implements Material {
} }
@Override @Override
public FileResolution getVertexShader() { public ResourceLocation vertexShader() {
return vertexShader; return vertexShader;
} }
@Override @Override
public FileResolution getFragmentShader() { public ResourceLocation fragmentShader() {
return fragmentShader; return fragmentShader;
} }
@ -60,22 +60,23 @@ public class SimpleMaterial implements Material {
} }
public static class Builder { public static class Builder {
protected FileResolution vertexShader = Components.Files.DEFAULT_VERTEX; protected ResourceLocation vertexShader = Components.Files.DEFAULT_VERTEX;
protected FileResolution fragmentShader = Components.Files.DEFAULT_FRAGMENT; protected ResourceLocation fragmentShader = Components.Files.DEFAULT_FRAGMENT;
protected Runnable setup = () -> {}; protected Runnable setup = () -> {};
protected Runnable clear = () -> {}; protected Runnable clear = () -> {};
protected RenderType batchingRenderType = RenderType.solid(); protected RenderType batchingRenderType = RenderType.solid();
protected VertexTransformer vertexTransformer = (vertexList, level) -> {}; protected VertexTransformer vertexTransformer = (vertexList, level) -> {
};
public Builder() { public Builder() {
} }
public Builder vertexShader(FileResolution vertexShader) { public Builder vertexShader(ResourceLocation vertexShader) {
this.vertexShader = vertexShader; this.vertexShader = vertexShader;
return this; return this;
} }
public Builder fragmentShader(FileResolution fragmentShader) { public Builder fragmentShader(ResourceLocation fragmentShader) {
this.fragmentShader = fragmentShader; this.fragmentShader = fragmentShader;
return this; return this;
} }

View file

@ -0,0 +1,87 @@
package com.jozufozu.flywheel.core.pipeline;
import com.jozufozu.flywheel.api.pipeline.Pipeline;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
import com.jozufozu.flywheel.core.SourceComponent;
import net.minecraft.resources.ResourceLocation;
public final class SimplePipeline implements Pipeline {
private final GLSLVersion glslVersion;
private final ResourceLocation vertex;
private final ResourceLocation fragment;
private final InstanceAssemblerFactory factory;
public SimplePipeline(GLSLVersion glslVersion, ResourceLocation vertex, ResourceLocation fragment, InstanceAssemblerFactory factory) {
this.glslVersion = glslVersion;
this.vertex = vertex;
this.fragment = fragment;
this.factory = factory;
}
/**
* Generate the source component necessary to convert a packed {@link StructType} into its shader representation.
*
* @return A source component defining functions that unpack a representation of the given struct type.
*/
@Override
public SourceComponent assemble(InstanceAssemblerContext context) {
return factory.apply(context);
}
@Override
public GLSLVersion glslVersion() {
return glslVersion;
}
@Override
public ResourceLocation vertexShader() {
return vertex;
}
@Override
public ResourceLocation fragmentShader() {
return fragment;
}
@FunctionalInterface
public interface InstanceAssemblerFactory {
SourceComponent apply(InstanceAssemblerContext context);
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private GLSLVersion glslVersion;
private ResourceLocation vertex;
private ResourceLocation fragment;
private InstanceAssemblerFactory factory;
public Builder glslVersion(GLSLVersion glslVersion) {
this.glslVersion = glslVersion;
return this;
}
public Builder vertex(ResourceLocation vertex) {
this.vertex = vertex;
return this;
}
public Builder fragment(ResourceLocation fragment) {
this.fragment = fragment;
return this;
}
public Builder assemblerFactory(InstanceAssemblerFactory factory) {
this.factory = factory;
return this;
}
public SimplePipeline build() {
return new SimplePipeline(glslVersion, vertex, fragment, factory);
}
}
}

View file

@ -1,95 +0,0 @@
package com.jozufozu.flywheel.core.source;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.core.source.error.ErrorBuilder;
import com.jozufozu.flywheel.core.source.span.Span;
public class CompilationContext {
public final List<SourceFile> files = new ArrayList<>();
private String generatedSource = "";
private int generatedLines = 0;
String sourceHeader(SourceFile sourceFile) {
return "#line " + 0 + ' ' + getOrCreateFileID(sourceFile) + " // " + sourceFile.name + '\n';
}
public String generatedHeader(String generatedCode, @Nullable String comment) {
generatedSource += generatedCode;
int lines = generatedCode.split("\n").length;
var out = "#line " + generatedLines + ' ' + 0;
generatedLines += lines;
if (comment != null) {
out += " // " + comment;
}
return out + '\n';
}
public boolean contains(SourceFile sourceFile) {
return files.contains(sourceFile);
}
/**
* Returns an arbitrary file ID for use this compilation context, or generates one if missing.
*
* @param sourceFile The file to retrieve the ID for.
* @return A file ID unique to the given sourceFile.
*/
private int getOrCreateFileID(SourceFile sourceFile) {
int i = files.indexOf(sourceFile);
if (i != -1) {
return i + 1;
}
files.add(sourceFile);
return files.size();
}
public Span getLineSpan(int fileId, int lineNo) {
if (fileId == 0) {
// TODO: Valid spans for generated code.
return null;
}
return getFile(fileId).getLineSpanNoWhitespace(lineNo);
}
private SourceFile getFile(int fileId) {
return files.get(fileId - 1);
}
public String parseErrors(String log) {
List<String> lines = log.lines()
.toList();
StringBuilder errors = new StringBuilder();
for (String line : lines) {
ErrorBuilder builder = parseCompilerError(line);
if (builder != null) {
errors.append(builder.build());
} else {
errors.append(line).append('\n');
}
}
return errors.toString();
}
@Nullable
private ErrorBuilder parseCompilerError(String line) {
try {
return ErrorBuilder.fromLogLine(this, line);
} catch (Exception ignored) {
}
return null;
}
}

View file

@ -1,186 +0,0 @@
package com.jozufozu.flywheel.core.source;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import com.jozufozu.flywheel.core.source.error.ErrorBuilder;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.source.span.Span;
import net.minecraft.resources.ResourceLocation;
/**
* A reference to a source file that might not be loaded when the owning object is created.
*
* <p>
* FileResolutions are used primarily while parsing import statements. {@link FileResolution#file} is initially
* null, but will be populated later on, after <em>all</em> SourceFiles are loaded (assuming
* {@link FileResolution#fileLoc} references an actual file).
* </p>
*/
public class FileResolution {
private static final Map<ResourceLocation, FileResolution> ALL = new HashMap<>();
private static final Map<ResourceLocation, FileResolution> WEAK = new HashMap<>();
private static boolean tooLate = false;
/**
* Extra info about where this resolution is required. Includes shader Spans.
*/
private final List<Span> neededAt = new ArrayList<>();
private final List<BiConsumer<ErrorReporter, SourceFile>> checks = new ArrayList<>();
private final ResourceLocation fileLoc;
private final boolean weak;
private SourceFile file;
private FileResolution(ResourceLocation fileLoc, boolean weak) {
this.fileLoc = fileLoc;
this.weak = weak;
}
public static FileResolution get(ResourceLocation file) {
if (!tooLate) {
return ALL.computeIfAbsent(file, loc -> new FileResolution(loc, false));
} else {
// Lock the map after resolution has run.
FileResolution fileResolution = ALL.get(file);
// ...so crash immediately if the file isn't found.
if (fileResolution == null) {
throw new ShaderLoadingException("could not find source for file: " + file);
}
return fileResolution;
}
}
/**
* Weak resolutions don't persist through resource reloads.<p>
* This should be used inside parsing code.
*
* @param file The location of the file to resolve.
* @return A weak resolution for the given file.
*/
public static FileResolution weak(ResourceLocation file) {
FileResolution fileResolution = ALL.get(file);
if (fileResolution != null) {
return fileResolution;
}
// never too late for weak resolutions.
return WEAK.computeIfAbsent(file, loc -> new FileResolution(loc, true));
}
/**
* Try and resolve all referenced source files, printing errors if any aren't found.
*/
public static void run(ErrorReporter errorReporter, SourceFinder sources) {
for (FileResolution resolution : ALL.values()) {
resolution.resolve(errorReporter, sources);
}
for (FileResolution resolution : WEAK.values()) {
resolution.resolve(errorReporter, sources);
}
WEAK.clear();
tooLate = true;
}
public static void checkAll(ErrorReporter errorReporter) {
for (FileResolution resolution : ALL.values()) {
resolution.runChecks(errorReporter);
}
}
private void resolve(ErrorReporter errorReporter, SourceFinder sources) {
file = sources.findSource(fileLoc);
if (file == null) {
reportMissing(errorReporter);
}
// Let the GC do its thing
neededAt.clear();
}
private void reportMissing(ErrorReporter errorReporter) {
ErrorBuilder builder = errorReporter.error(String.format("could not find source for file %s", fileLoc));
for (Span location : neededAt) {
builder.pointAtFile(location.getSourceFile())
.pointAt(location);
}
}
private void runChecks(ErrorReporter errorReporter) {
for (var check : checks) {
check.accept(errorReporter, file);
}
}
public ResourceLocation getFileLoc() {
return fileLoc;
}
/**
* Non-null if this file is resolved because there would have been a crash otherwise.
* @return The file that this resolution resolves to.
*/
public SourceFile getFile() {
return file;
}
public boolean isWeak() {
return weak;
}
/**
* Store the given span so this resolution can know all the places that reference the file.
*
* <p>
* Used for error reporting.
* </p>
* @param span A span where this file is referenced.
*/
public FileResolution addSpan(Span span) {
neededAt.add(span);
return this;
}
public FileResolution validateWith(BiConsumer<ErrorReporter, SourceFile> check) {
checks.add(check);
return this;
}
@Override
public String toString() {
return "FileResolution[" + fileLoc + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FileResolution that = (FileResolution) o;
return fileLoc.equals(that.fileLoc);
}
@Override
public int hashCode() {
// FileResolutions are interned and therefore can be hashed based on object identity.
// Overriding this to make it explicit.
return System.identityHashCode(this);
}
}

View file

@ -5,4 +5,8 @@ public class ShaderLoadingException extends RuntimeException {
public ShaderLoadingException(String message) { public ShaderLoadingException(String message) {
super(message); super(message);
} }
public ShaderLoadingException(String message, Throwable cause) {
super(message, cause);
}
} }

View file

@ -1,13 +1,14 @@
package com.jozufozu.flywheel.core.source; package com.jozufozu.flywheel.core.source;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.jozufozu.flywheel.core.source.error.ErrorReporter; import com.jozufozu.flywheel.core.source.error.ErrorReporter;
@ -15,72 +16,74 @@ import com.jozufozu.flywheel.util.ResourceUtil;
import com.jozufozu.flywheel.util.StringUtil; import com.jozufozu.flywheel.util.StringUtil;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.ResourceManager;
/** /**
* The main object for loading and parsing source files. * The main object for loading and parsing source files.
*/ */
public class ShaderSources implements SourceFinder { public class ShaderSources {
public static final String SHADER_DIR = "flywheel/"; public static final String SHADER_DIR = "flywheel/";
public static final ArrayList<String> EXTENSIONS = Lists.newArrayList(".vert", ".vsh", ".frag", ".fsh", ".glsl"); public static final ArrayList<String> EXTENSIONS = Lists.newArrayList(".vert", ".vsh", ".frag", ".fsh", ".glsl");
private final Map<ResourceLocation, SourceFile> shaderSources = new HashMap<>(); private final Map<ResourceLocation, SourceFile> cache = new HashMap<>();
public final Index index; /**
* Tracks where we are in the mutual recursion to detect circular imports.
*/
private final Deque<ResourceLocation> findStack = new ArrayDeque<>();
public final long loadTimeNs; private final ResourceManager manager;
public final long indexTimeNs; private final ErrorReporter errorReporter;
public final long totalTimeNs;
public ShaderSources(ErrorReporter errorReporter, ResourceManager manager) { public ShaderSources(ErrorReporter errorReporter, ResourceManager manager) {
long loadStart = System.nanoTime(); this.errorReporter = errorReporter;
this.manager = manager;
for (ResourceLocation location : getValidShaderFiles(manager)) {
try (Resource resource = manager.getResource(location)) {
String source = StringUtil.readToString(resource.getInputStream());
ResourceLocation name = ResourceUtil.removePrefixUnchecked(location, SHADER_DIR);
shaderSources.put(name, new SourceFile(errorReporter, this, name, source));
} catch (IOException e) {
//
}
}
long loadEnd = System.nanoTime();
long indexStart = System.nanoTime();
index = new Index(shaderSources);
long indexEnd = System.nanoTime();
loadTimeNs = loadEnd - loadStart;
indexTimeNs = indexEnd - indexStart;
totalTimeNs = indexEnd - loadStart;
} }
public void postResolve() { @Nonnull
for (SourceFile file : shaderSources.values()) { public SourceFile find(ResourceLocation location) {
file.postResolve(); pushFindStack(location);
// Can't use computeIfAbsent because mutual recursion causes ConcurrentModificationExceptions
var out = cache.get(location);
if (out == null) {
out = load(location);
cache.put(location, out);
}
popFindStack();
return out;
}
@Nonnull
private SourceFile load(ResourceLocation loc) {
try {
var resource = manager.getResource(ResourceUtil.prefixed(SHADER_DIR, loc));
var sourceString = StringUtil.readToString(resource.getInputStream());
return new SourceFile(this, loc, sourceString);
} catch (IOException ioException) {
throw new ShaderLoadingException("Could not load shader " + loc, ioException);
} }
} }
@Override private void generateRecursiveImportException(ResourceLocation location) {
@Nullable findStack.add(location);
public SourceFile findSource(ResourceLocation name) { String path = findStack.stream()
return shaderSources.get(name); .dropWhile(l -> !l.equals(location))
.map(ResourceLocation::toString)
.collect(Collectors.joining(" -> "));
findStack.clear();
throw new ShaderLoadingException("recursive import: " + path);
} }
@NotNull private void pushFindStack(ResourceLocation location) {
private static Collection<ResourceLocation> getValidShaderFiles(ResourceManager manager) { if (findStack.contains(location)) {
return manager.listResources(SHADER_DIR, s -> { generateRecursiveImportException(location);
for (String ext : EXTENSIONS) {
if (s.endsWith(ext)) return true;
} }
return false; findStack.add(location);
});
} }
public String getLoadTime() { private void popFindStack() {
return StringUtil.formatTime(totalTimeNs); findStack.pop();
} }
} }

View file

@ -12,6 +12,17 @@ import com.jozufozu.flywheel.core.source.parse.ShaderVariable;
public class SourceChecks { public class SourceChecks {
// TODO: recycle to be invoked by the shader compiler
public static final BiConsumer<ErrorReporter, SourceFile> LAYOUT_VERTEX = checkFunctionArity("flw_layoutVertex", 0);
public static final BiConsumer<ErrorReporter, SourceFile> INSTANCE_VERTEX = checkFunctionParameterTypeExists("flw_instanceVertex", 1, 0);
public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_VERTEX = checkFunctionArity("flw_materialVertex", 0);
public static final BiConsumer<ErrorReporter, SourceFile> MATERIAL_FRAGMENT = checkFunctionArity("flw_materialFragment", 0);
public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_VERTEX = checkFunctionArity("flw_contextVertex", 0);
public static final BiConsumer<ErrorReporter, SourceFile> CONTEXT_FRAGMENT = checkFunctionArity("flw_contextFragment", 0).andThen(checkFunctionArity("flw_initFragment", 0));
public static final BiConsumer<ErrorReporter, SourceFile> PIPELINE = checkFunctionArity("main", 0);
public static BiConsumer<ErrorReporter, SourceFile> checkFunctionArity(String name, int arity) { public static BiConsumer<ErrorReporter, SourceFile> checkFunctionArity(String name, int arity) {
return (errorReporter, file) -> checkFunctionArity(errorReporter, file, name, arity); return (errorReporter, file) -> checkFunctionArity(errorReporter, file, name, arity);
} }

View file

@ -1,20 +1,11 @@
package com.jozufozu.flywheel.core.source; package com.jozufozu.flywheel.core.source;
import java.util.ArrayList; import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.core.SourceComponent; import com.jozufozu.flywheel.core.SourceComponent;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.source.parse.Import; import com.jozufozu.flywheel.core.source.parse.Import;
import com.jozufozu.flywheel.core.source.parse.ShaderField; import com.jozufozu.flywheel.core.source.parse.ShaderField;
import com.jozufozu.flywheel.core.source.parse.ShaderFunction; import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
@ -37,9 +28,8 @@ public class SourceFile implements SourceComponent {
public final ResourceLocation name; public final ResourceLocation name;
public final ShaderSources parent; public final ShaderSources parent;
public final String source;
public final SourceLines lines; public final SourceLines source;
/** /**
* Function lookup by name. * Function lookup by name.
@ -57,30 +47,42 @@ public class SourceFile implements SourceComponent {
public final ImmutableList<Import> imports; public final ImmutableList<Import> imports;
public final ImmutableMap<String, ShaderField> fields; public final ImmutableMap<String, ShaderField> fields;
// POST-RESOLUTION public final List<SourceFile> included;
public List<SourceFile> flattenedImports;
public SourceFile(ErrorReporter errorReporter, ShaderSources parent, ResourceLocation name, String source) { public SourceFile(ShaderSources sourceFinder, ResourceLocation name, String source) {
this.parent = parent; this.parent = sourceFinder;
this.name = name; this.name = name;
this.source = source;
this.lines = new SourceLines(source); this.source = new SourceLines(source);
this.imports = parseImports(errorReporter); this.imports = parseImports(source);
this.functions = parseFunctions(); this.functions = parseFunctions(source);
this.structs = parseStructs(); this.structs = parseStructs(source);
this.fields = parseFields(); this.fields = parseFields(source);
this.included = imports.stream()
.map(i -> i.file)
.map(Span::toString)
.distinct()
.<SourceFile>mapMulti((file, sink) -> {
try {
var loc = new ResourceLocation(file);
var sourceFile = sourceFinder.find(loc);
sink.accept(sourceFile);
} catch (Exception ignored) {
}
})
.toList();
} }
@Override @Override
public Collection<? extends SourceComponent> included() { public Collection<? extends SourceComponent> included() {
return flattenedImports; return included;
} }
@Override @Override
public String source(CompilationContext ctx) { public String source() {
return ctx.sourceHeader(this) + this.elideImports(); return this.genFinalSource();
} }
@Override @Override
@ -88,52 +90,23 @@ public class SourceFile implements SourceComponent {
return name; return name;
} }
public void postResolve() { public Span getLineSpan(int lineNo) {
this.flattenImports(); int begin = source.lineStartIndex(lineNo);
} int end = begin + source.lineString(lineNo)
.length();
private void flattenImports() { return new StringSpan(this, source.getCharPos(begin), source.getCharPos(end));
// somebody #used us and got resolved before we did
if (this.flattenedImports != null) {
return;
}
if (this.imports.isEmpty()) {
this.flattenedImports = Collections.emptyList();
return;
}
ArrayList<SourceFile> flat = new ArrayList<>(this.imports.size());
for (Import include : this.imports) {
SourceFile file = include.resolution.getFile();
file.flattenImports();
flat.addAll(file.flattenedImports);
flat.add(file);
}
this.flattenedImports = flat.stream()
.distinct()
.toList();
}
public Span getLineSpan(int line) {
int begin = lines.getLineStart(line);
int end = begin + lines.getLine(line).length();
return new StringSpan(this, lines.getCharPos(begin), lines.getCharPos(end));
} }
public Span getLineSpanNoWhitespace(int line) { public Span getLineSpanNoWhitespace(int line) {
int begin = lines.getLineStart(line); int begin = source.lineStartIndex(line);
int end = begin + lines.getLine(line).length(); int end = begin + source.lineString(line)
.length();
while (begin < end && Character.isWhitespace(source.charAt(begin))) { while (begin < end && Character.isWhitespace(source.charAt(begin))) {
begin++; begin++;
} }
return new StringSpan(this, lines.getCharPos(begin), lines.getCharPos(end)); return new StringSpan(this, source.getCharPos(begin), source.getCharPos(end));
} }
/** /**
@ -142,12 +115,14 @@ public class SourceFile implements SourceComponent {
* @param name The name of the struct to find. * @param name The name of the struct to find.
* @return null if no definition matches the name. * @return null if no definition matches the name.
*/ */
public Optional<ShaderStruct> findStruct(String name) { public Optional<ShaderStruct> findStructByName(String name) {
ShaderStruct struct = structs.get(name); ShaderStruct struct = structs.get(name);
if (struct != null) return Optional.of(struct); if (struct != null) {
return Optional.of(struct);
}
for (var include : flattenedImports) { for (var include : included) {
var external = include.structs.get(name); var external = include.structs.get(name);
if (external != null) { if (external != null) {
@ -169,7 +144,7 @@ public class SourceFile implements SourceComponent {
if (local != null) return Optional.of(local); if (local != null) return Optional.of(local);
for (var include : flattenedImports) { for (var include : included) {
var external = include.functions.get(name); var external = include.functions.get(name);
if (external != null) { if (external != null) {
@ -185,10 +160,10 @@ public class SourceFile implements SourceComponent {
} }
public String printSource() { public String printSource() {
return "Source for shader '" + name + "':\n" + lines.printLinesWithNumbers(); return "Source for shader '" + name + "':\n" + source.printLinesWithNumbers();
} }
private CharSequence elideImports() { private String genFinalSource() {
StringBuilder out = new StringBuilder(); StringBuilder out = new StringBuilder();
int lastEnd = 0; int lastEnd = 0;
@ -203,13 +178,50 @@ public class SourceFile implements SourceComponent {
out.append(this.source, lastEnd, this.source.length()); out.append(this.source, lastEnd, this.source.length());
return out; return out.toString();
} }
@Override
public String toString() {
return name.toString();
}
@Override
public boolean equals(Object o) {
// SourceFiles are only equal by reference.
return this == o;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
/**
* Scan the source for {@code #use "..."} directives.
* Records the contents of the directive into an {@link Import} object, and marks the directive for elision.
*/
private ImmutableList<Import> parseImports(String source) {
Matcher uses = Import.PATTERN.matcher(source);
var imports = ImmutableList.<Import>builder();
while (uses.find()) {
Span use = Span.fromMatcher(this, uses);
Span file = Span.fromMatcher(this, uses, 1);
imports.add(new Import(use, file));
}
return imports.build();
}
/** /**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function. * Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/ */
private ImmutableMap<String, ShaderFunction> parseFunctions() { private ImmutableMap<String, ShaderFunction> parseFunctions(String source) {
Matcher matcher = ShaderFunction.PATTERN.matcher(source); Matcher matcher = ShaderFunction.PATTERN.matcher(source);
Map<String, ShaderFunction> functions = new HashMap<>(); Map<String, ShaderFunction> functions = new HashMap<>();
@ -220,7 +232,7 @@ public class SourceFile implements SourceComponent {
Span args = Span.fromMatcher(this, matcher, 3); Span args = Span.fromMatcher(this, matcher, 3);
int blockStart = matcher.end(); int blockStart = matcher.end();
int blockEnd = findEndOfBlock(blockStart); int blockEnd = findEndOfBlock(source, blockStart);
Span self; Span self;
Span body; Span body;
@ -243,7 +255,7 @@ public class SourceFile implements SourceComponent {
/** /**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function. * Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/ */
private ImmutableMap<String, ShaderStruct> parseStructs() { private ImmutableMap<String, ShaderStruct> parseStructs(String source) {
Matcher matcher = ShaderStruct.PATTERN.matcher(source); Matcher matcher = ShaderStruct.PATTERN.matcher(source);
ImmutableMap.Builder<String, ShaderStruct> structs = ImmutableMap.builder(); ImmutableMap.Builder<String, ShaderStruct> structs = ImmutableMap.builder();
@ -251,8 +263,9 @@ public class SourceFile implements SourceComponent {
Span self = Span.fromMatcher(this, matcher); Span self = Span.fromMatcher(this, matcher);
Span name = Span.fromMatcher(this, matcher, 1); Span name = Span.fromMatcher(this, matcher, 1);
Span body = Span.fromMatcher(this, matcher, 2); Span body = Span.fromMatcher(this, matcher, 2);
Span variableName = Span.fromMatcher(this, matcher, 3);
ShaderStruct shaderStruct = new ShaderStruct(self, name, body); ShaderStruct shaderStruct = new ShaderStruct(self, name, body, variableName);
structs.put(name.get(), shaderStruct); structs.put(name.get(), shaderStruct);
} }
@ -263,7 +276,7 @@ public class SourceFile implements SourceComponent {
/** /**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function. * Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/ */
private ImmutableMap<String, ShaderField> parseFields() { private ImmutableMap<String, ShaderField> parseFields(String source) {
Matcher matcher = ShaderField.PATTERN.matcher(source); Matcher matcher = ShaderField.PATTERN.matcher(source);
ImmutableMap.Builder<String, ShaderField> fields = ImmutableMap.builder(); ImmutableMap.Builder<String, ShaderField> fields = ImmutableMap.builder();
@ -280,36 +293,10 @@ public class SourceFile implements SourceComponent {
return fields.build(); return fields.build();
} }
/**
* Scan the source for {@code #use "..."} directives.
* Records the contents of the directive into an {@link Import} object, and marks the directive for elision.
*/
private ImmutableList<Import> parseImports(ErrorReporter errorReporter) {
Matcher uses = Import.PATTERN.matcher(source);
Set<String> importedFiles = new HashSet<>();
var imports = ImmutableList.<Import>builder();
while (uses.find()) {
Span use = Span.fromMatcher(this, uses);
Span file = Span.fromMatcher(this, uses, 1);
String fileName = file.get();
if (importedFiles.add(fileName)) {
var checked = Import.create(errorReporter, use, file);
if (checked != null) {
imports.add(checked);
}
}
}
return imports.build();
}
/** /**
* Given the position of an opening brace, scans through the source for a paired closing brace. * Given the position of an opening brace, scans through the source for a paired closing brace.
*/ */
private int findEndOfBlock(int start) { private static int findEndOfBlock(String source, int start) {
int blockDepth = 0; int blockDepth = 0;
for (int i = start + 1; i < source.length(); i++) { for (int i = start + 1; i < source.length(); i++) {
char ch = source.charAt(i); char ch = source.charAt(i);
@ -324,20 +311,4 @@ public class SourceFile implements SourceComponent {
return -1; return -1;
} }
@Override
public String toString() {
return name.toString();
}
@Override
public boolean equals(Object o) {
// SourceFiles are only equal by reference.
return this == o;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
} }

View file

@ -1,15 +0,0 @@
package com.jozufozu.flywheel.core.source;
import org.jetbrains.annotations.Nullable;
import net.minecraft.resources.ResourceLocation;
/**
* A minimal source file lookup function.
*/
@FunctionalInterface
public interface SourceFinder {
@Nullable
SourceFile findSource(ResourceLocation name);
}

View file

@ -3,14 +3,16 @@ package com.jozufozu.flywheel.core.source;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.jetbrains.annotations.NotNull;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.core.source.span.CharPos; import com.jozufozu.flywheel.core.source.span.CharPos;
import com.jozufozu.flywheel.util.StringUtil;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntLists;
public class SourceLines { public class SourceLines implements CharSequence {
private static final Pattern newLine = Pattern.compile("(\\r\\n|\\r|\\n)"); private static final Pattern newLine = Pattern.compile("(\\r\\n|\\r|\\n)");
@ -23,22 +25,23 @@ public class SourceLines {
* 0-indexed line lookup * 0-indexed line lookup
*/ */
private final ImmutableList<String> lines; private final ImmutableList<String> lines;
public final String raw;
public SourceLines(String source) { public SourceLines(String raw) {
this.lineStarts = createLineLookup(source); this.raw = raw;
this.lines = getLines(source, lineStarts); this.lineStarts = createLineLookup(raw);
this.lines = getLines(raw, lineStarts);
} }
public int getLineCount() { public int count() {
return lines.size(); return lines.size();
} }
public String getLine(int lineNo) { public String lineString(int lineNo) {
return lines.get(lineNo); return lines.get(lineNo);
} }
public int getLineStart(int lineNo) { public int lineStartIndex(int lineNo) {
return lineStarts.getInt(lineNo); return lineStarts.getInt(lineNo);
} }
@ -73,9 +76,12 @@ public class SourceLines {
/** /**
* Scan the source for line breaks, recording the position of the first character of each line. * Scan the source for line breaks, recording the position of the first character of each line.
* @param source
*/ */
private static IntList createLineLookup(String source) { private static IntList createLineLookup(String source) {
if (source.isEmpty()) {
return IntLists.emptyList();
}
IntList l = new IntArrayList(); IntList l = new IntArrayList();
l.add(0); // first line is always at position 0 l.add(0); // first line is always at position 0
@ -84,6 +90,7 @@ public class SourceLines {
while (matcher.find()) { while (matcher.find()) {
l.add(matcher.end()); l.add(matcher.end());
} }
return l; return l;
} }
@ -94,9 +101,53 @@ public class SourceLines {
int start = lines.getInt(i - 1); int start = lines.getInt(i - 1);
int end = lines.getInt(i); int end = lines.getInt(i);
builder.add(StringUtil.trimEnd(source.substring(start, end))); builder.add(source.substring(start, end)
.stripTrailing());
} }
return builder.build(); return builder.build();
} }
@Override
public String toString() {
return raw;
}
@NotNull
@Override
public CharSequence subSequence(int start, int end) {
return raw.subSequence(start, end);
}
public char charAt(int i) {
return raw.charAt(i);
}
public int length() {
return raw.length();
}
public int lineStartCol(int spanLine) {
return 0;
}
public int lineWidth(int spanLine) {
return lines.get(spanLine)
.length();
}
public int lineStartColTrimmed(final int line) {
final var lineString = lineString(line);
final int end = lineString.length();
int col = 0;
while (col < end && Character.isWhitespace(charAt(col))) {
col++;
}
return col;
}
public int lineStartPosTrimmed(final int line) {
return lineStartIndex(line) + lineStartColTrimmed(line);
}
} }

View file

@ -2,12 +2,9 @@ package com.jozufozu.flywheel.core.source.error;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.core.source.CompilationContext;
import com.jozufozu.flywheel.core.source.SourceFile; import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.SourceLines; import com.jozufozu.flywheel.core.source.SourceLines;
import com.jozufozu.flywheel.core.source.error.lines.ErrorLine; import com.jozufozu.flywheel.core.source.error.lines.ErrorLine;
@ -17,72 +14,61 @@ import com.jozufozu.flywheel.core.source.error.lines.SourceLine;
import com.jozufozu.flywheel.core.source.error.lines.SpanHighlightLine; import com.jozufozu.flywheel.core.source.error.lines.SpanHighlightLine;
import com.jozufozu.flywheel.core.source.error.lines.TextLine; import com.jozufozu.flywheel.core.source.error.lines.TextLine;
import com.jozufozu.flywheel.core.source.span.Span; import com.jozufozu.flywheel.core.source.span.Span;
import com.jozufozu.flywheel.util.FlwUtil; import com.jozufozu.flywheel.util.ConsoleColors;
import com.jozufozu.flywheel.util.StringUtil;
public class ErrorBuilder { public class ErrorBuilder {
private static final Pattern ERROR_LINE = Pattern.compile("(\\d+)\\((\\d+)\\) : (.*)");
private final List<ErrorLine> lines = new ArrayList<>(); private final List<ErrorLine> lines = new ArrayList<>();
public ErrorBuilder() { private ErrorBuilder() {
} }
public static ErrorBuilder error(CharSequence msg) { public static ErrorBuilder create() {
return new ErrorBuilder() return new ErrorBuilder();
.header(ErrorLevel.ERROR, msg);
} }
public static ErrorBuilder compError(CharSequence msg) { public ErrorBuilder error(String msg) {
return new ErrorBuilder() return header(ErrorLevel.ERROR, msg);
.extra(msg);
} }
public static ErrorBuilder warn(CharSequence msg) { public ErrorBuilder warn(String msg) {
return new ErrorBuilder() return header(ErrorLevel.WARN, msg);
.header(ErrorLevel.WARN, msg);
} }
@Nullable public ErrorBuilder hint(String msg) {
public static ErrorBuilder fromLogLine(CompilationContext env, String s) { return header(ErrorLevel.HINT, msg);
Matcher matcher = ERROR_LINE.matcher(s);
if (matcher.find()) {
String fileId = matcher.group(1);
String lineNo = matcher.group(2);
String msg = matcher.group(3);
Span span = env.getLineSpan(Integer.parseInt(fileId), Integer.parseInt(lineNo));
if (span == null) {
return ErrorBuilder.compError("Error in generated code");
} }
return ErrorBuilder.compError(msg) public ErrorBuilder note(String msg) {
.pointAtFile(span.getSourceFile()) return header(ErrorLevel.NOTE, msg);
.pointAt(span, 1);
} else {
return null;
}
} }
public ErrorBuilder header(ErrorLevel level, CharSequence msg) { public ErrorBuilder header(ErrorLevel level, String msg) {
lines.add(new HeaderLine(level.toString(), msg)); lines.add(new HeaderLine(level.toString(), msg));
return this; return this;
} }
public ErrorBuilder extra(CharSequence msg) { public ErrorBuilder extra(String msg) {
lines.add(new TextLine(msg.toString())); lines.add(new TextLine(msg));
return this; return this;
} }
public ErrorBuilder pointAtFile(SourceFile file) { public ErrorBuilder pointAtFile(SourceFile file) {
lines.add(new FileLine(file.name.toString())); return pointAtFile(file.name.toString());
}
public ErrorBuilder pointAtFile(String file) {
lines.add(new FileLine(file));
return this;
}
public ErrorBuilder hintIncludeFor(@Nullable Span span, String msg) {
if (span == null) {
return this; return this;
} }
public ErrorBuilder hintIncludeFor(@Nullable Span span, CharSequence msg) {
if (span == null) return this;
SourceFile sourceFile = span.getSourceFile(); SourceFile sourceFile = span.getSourceFile();
String builder = "add " + sourceFile.importStatement() + ' ' + msg + "\n defined here:"; String builder = "add " + sourceFile.importStatement() + ' ' + msg + "\n defined here:";
@ -98,22 +84,32 @@ public class ErrorBuilder {
} }
public ErrorBuilder pointAt(Span span, int ctxLines) { public ErrorBuilder pointAt(Span span, int ctxLines) {
if (span.lines() == 1) { if (span.lines() == 1) {
SourceLines lines = span.getSourceFile().lines; SourceLines lines = span.getSourceFile().source;
int spanLine = span.firstLine(); int spanLine = span.firstLine();
int firstLine = Math.max(0, spanLine - ctxLines);
int lastLine = Math.min(lines.getLineCount(), spanLine + ctxLines);
int firstCol = span.getStart() int firstCol = span.getStart()
.col(); .col();
int lastCol = span.getEnd() int lastCol = span.getEnd()
.col(); .col();
pointAtLine(lines, spanLine, ctxLines, firstCol, lastCol);
}
return this;
}
public ErrorBuilder pointAtLine(SourceLines lines, int spanLine, int ctxLines) {
return pointAtLine(lines, spanLine, ctxLines, lines.lineStartColTrimmed(spanLine), lines.lineWidth(spanLine));
}
public ErrorBuilder pointAtLine(SourceLines lines, int spanLine, int ctxLines, int firstCol, int lastCol) {
int firstLine = Math.max(0, spanLine - ctxLines);
int lastLine = Math.min(lines.count(), spanLine + ctxLines);
for (int i = firstLine; i <= lastLine; i++) { for (int i = firstLine; i <= lastLine; i++) {
CharSequence line = lines.getLine(i); CharSequence line = lines.lineString(i);
this.lines.add(SourceLine.numbered(i + 1, line.toString())); this.lines.add(SourceLine.numbered(i + 1, line.toString()));
@ -121,29 +117,30 @@ public class ErrorBuilder {
this.lines.add(new SpanHighlightLine(firstCol, lastCol)); this.lines.add(new SpanHighlightLine(firstCol, lastCol));
} }
} }
}
return this; return this;
} }
public String build() { public String build() {
int maxMargin = -1;
int maxLength = -1;
for (ErrorLine line : lines) { for (ErrorLine line : lines) {
int length = line.neededMargin(); int neededMargin = line.neededMargin();
if (length > maxLength) { if (neededMargin > maxMargin) {
maxLength = length; maxMargin = neededMargin;
} }
} }
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append('\n');
for (ErrorLine line : lines) { for (ErrorLine line : lines) {
int length = line.neededMargin(); int neededMargin = line.neededMargin();
builder.append(FlwUtil.repeatChar(' ', maxLength - length)) if (neededMargin >= 0) {
.append(line.build()) builder.append(StringUtil.repeatChar(' ', maxMargin - neededMargin));
}
builder.append(line.build())
.append(ConsoleColors.RESET)
.append('\n'); .append('\n');
} }

View file

@ -1,9 +1,12 @@
package com.jozufozu.flywheel.core.source.error; package com.jozufozu.flywheel.core.source.error;
import com.jozufozu.flywheel.util.ConsoleColors;
public enum ErrorLevel { public enum ErrorLevel {
WARN("warn"), WARN(ConsoleColors.YELLOW + "warn"),
ERROR("error"), ERROR(ConsoleColors.RED + "error"),
HINT("hint"), HINT(ConsoleColors.WHITE_BRIGHT + "hint"),
NOTE(ConsoleColors.WHITE_BRIGHT + "note"),
; ;
private final String error; private final String error;

View file

@ -2,17 +2,14 @@ package com.jozufozu.flywheel.core.source.error;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.core.source.ShaderLoadingException; import com.jozufozu.flywheel.core.source.ShaderLoadingException;
import com.jozufozu.flywheel.core.source.SourceFile; import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
import com.jozufozu.flywheel.core.source.parse.ShaderStruct;
import com.jozufozu.flywheel.core.source.span.Span; import com.jozufozu.flywheel.core.source.span.Span;
import com.jozufozu.flywheel.util.FlwUtil; import com.jozufozu.flywheel.util.FlwUtil;
import com.jozufozu.flywheel.util.StringUtil;
public class ErrorReporter { public class ErrorReporter {
@ -23,15 +20,15 @@ public class ErrorReporter {
} }
public void generateMissingStruct(SourceFile file, Span vertexName, CharSequence msg, CharSequence hint) { public void generateMissingStruct(SourceFile file, Span vertexName, CharSequence msg, CharSequence hint) {
Optional<Span> span = file.parent.index.getStructDefinitionsMatching(vertexName) // Optional<Span> span = file.parent.index.getStructDefinitionsMatching(vertexName)
.stream() // .stream()
.findFirst() // .findFirst()
.map(ShaderStruct::getName); // .map(ShaderStruct::getName);
//
this.error(msg) // this.error(msg)
.pointAtFile(file) // .pointAtFile(file)
.pointAt(vertexName, 1) // .pointAt(vertexName, 1)
.hintIncludeFor(span.orElse(null), hint); // .hintIncludeFor(span.orElse(null), hint);
} }
public void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg) { public void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg) {
@ -39,14 +36,14 @@ public class ErrorReporter {
} }
public void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg, CharSequence hint) { public void generateMissingFunction(SourceFile file, CharSequence functionName, CharSequence msg, CharSequence hint) {
Optional<Span> span = file.parent.index.getFunctionDefinitionsMatching(functionName) // Optional<Span> span = file.parent.index.getFunctionDefinitionsMatching(functionName)
.stream() // .stream()
.findFirst() // .findFirst()
.map(ShaderFunction::getName); // .map(ShaderFunction::getName);
//
this.error(msg) // this.error(msg)
.pointAtFile(file) // .pointAtFile(file)
.hintIncludeFor(span.orElse(null), hint); // .hintIncludeFor(span.orElse(null), hint);
} }
public ErrorBuilder generateFunctionArgumentCountError(String name, int requiredArguments, Span span) { public ErrorBuilder generateFunctionArgumentCountError(String name, int requiredArguments, Span span) {
@ -64,18 +61,17 @@ public class ErrorReporter {
public ErrorBuilder generateSpanError(Span span, String message) { public ErrorBuilder generateSpanError(Span span, String message) {
SourceFile file = span.getSourceFile(); SourceFile file = span.getSourceFile();
return error(message) return error(message).pointAtFile(file)
.pointAtFile(file)
.pointAt(span, 2); .pointAt(span, 2);
} }
public ErrorBuilder generateFileError(SourceFile file, String message) { public ErrorBuilder generateFileError(SourceFile file, String message) {
return error(message) return error(message).pointAtFile(file);
.pointAtFile(file);
} }
public ErrorBuilder error(CharSequence msg) { public ErrorBuilder error(String msg) {
var out = ErrorBuilder.error(msg); var out = ErrorBuilder.create()
.error(msg);
reportedErrors.add(out); reportedErrors.add(out);
return out; return out;
} }
@ -92,9 +88,7 @@ public class ErrorReporter {
return new ShaderLoadingException(allErrors); return new ShaderLoadingException(allErrors);
} }
public static void printLines(CharSequence source) { public static void printLines(String string) {
String string = source.toString();
List<String> lines = string.lines() List<String> lines = string.lines()
.toList(); .toList();
@ -107,7 +101,7 @@ public class ErrorReporter {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
builder.append(i) builder.append(i)
.append(FlwUtil.repeatChar(' ', maxWidth - FlwUtil.numDigits(i))) .append(StringUtil.repeatChar(' ', maxWidth - FlwUtil.numDigits(i)))
.append("| ") .append("| ")
.append(lines.get(i)) .append(lines.get(i))
.append('\n'); .append('\n');

View file

@ -2,7 +2,7 @@ package com.jozufozu.flywheel.core.source.error.lines;
public enum Divider { public enum Divider {
BAR(" | "), BAR(" | "),
ARROW("-->"), ARROW("-> "),
; ;
private final String s; private final String s;

View file

@ -4,7 +4,7 @@ public record FileLine(String fileName) implements ErrorLine {
@Override @Override
public String left() { public String left() {
return "--"; return "-";
} }
@Override @Override

View file

@ -4,7 +4,7 @@ public record HeaderLine(String level, CharSequence message) implements ErrorLin
@Override @Override
public int neededMargin() { public int neededMargin() {
return 0; return -1;
} }
@Override @Override

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

@ -4,29 +4,34 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.jozufozu.flywheel.util.Pair;
public class GlslBuilder { public class GlslBuilder {
private final List<Declaration> elements = new ArrayList<>();
private final List<SourceElement> elements = new ArrayList<>();
public void define(String name, String value) { public void define(String name, String value) {
add(new Define(name, value)); add(new Define(name, value));
} }
public StructBuilder struct() { public void undef(String key) {
return add(new StructBuilder()); add(new Undef(key));
} }
public FunctionBuilder function() { public GlslStruct struct() {
return add(new FunctionBuilder()); return add(new GlslStruct());
} }
public VertexInputBuilder vertexInput() { public GlslFn function() {
return add(new VertexInputBuilder()); return add(new GlslFn());
} }
public <T extends SourceElement> T add(T element) { public GlslVertexInput vertexInput() {
return add(new GlslVertexInput());
}
public GlslUniformBlock uniformBlock() {
return add(new GlslUniformBlock());
}
public <T extends Declaration> T add(T element) {
elements.add(element); elements.add(element);
return element; return element;
} }
@ -35,17 +40,21 @@ public class GlslBuilder {
elements.add(Separators.BLANK_LINE); elements.add(Separators.BLANK_LINE);
} }
public void _addRaw(String sourceString) {
elements.add(() -> sourceString);
}
public String build() { public String build() {
return elements.stream() return elements.stream()
.map(SourceElement::build) .map(Declaration::prettyPrint)
.collect(Collectors.joining("\n")); .collect(Collectors.joining("\n"));
} }
public interface SourceElement { public interface Declaration {
String build(); String prettyPrint();
} }
public enum Separators implements SourceElement { public enum Separators implements Declaration {
BLANK_LINE(""), BLANK_LINE(""),
; ;
@ -54,125 +63,25 @@ public class GlslBuilder {
Separators(String separator) { Separators(String separator) {
this.separator = separator; this.separator = separator;
} }
@Override @Override
public String build() { public String prettyPrint() {
return separator; return separator;
} }
} }
public record Define(String name, String value) implements SourceElement { public record Define(String name, String value) implements Declaration {
@Override @Override
public String build() { public String prettyPrint() {
return "#define " + name + " " + value; return "#define " + name + " " + value;
} }
} }
public static class VertexInputBuilder implements SourceElement { public record Undef(String name) implements Declaration {
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 @Override
public String build() { public String prettyPrint() {
return "layout(location = " + binding + ") in " + type + " " + name + ";"; return "#undef " + name;
} }
} }
public static class StructBuilder implements SourceElement {
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 -> '\t' + p.first() + ' ' + p.second() + ';')
.collect(Collectors.joining("\n"));
}
public String build() {
return """
struct %s {
%s
};
""".formatted(name, buildFields());
}
}
public static class FunctionBuilder implements SourceElement {
private final List<Pair<String, String>> arguments = new ArrayList<>();
private final List<String> body = new ArrayList<>();
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 statement(String statement) {
this.body.add(statement);
return this;
}
public String build() {
return """
%s %s(%s) {
%s
}
""".formatted(returnType, name, buildArguments(), buildBody());
}
private String buildBody() {
return body.stream()
.map(s -> '\t' + s)
.collect(Collectors.joining("\n"));
}
private String buildArguments() {
return arguments.stream()
.map(p -> p.first() + ' ' + p.second())
.collect(Collectors.joining(", "));
}
}
} }

View file

@ -1,8 +1,12 @@
package com.jozufozu.flywheel.core.source.generate; package com.jozufozu.flywheel.core.source.generate;
import java.util.Collection;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors;
public sealed interface GlslExpr { import com.google.common.collect.ImmutableList;
public interface GlslExpr {
/** /**
* Create a glsl variable with the given name. * Create a glsl variable with the given name.
@ -14,7 +18,21 @@ public sealed interface GlslExpr {
return new Variable(name); return new Variable(name);
} }
String minPrint(); static FunctionCall call(String functionName, Collection<? extends GlslExpr> args) {
return new FunctionCall(functionName, args);
}
static FunctionCall0 call(String functionName) {
return new FunctionCall0(functionName);
}
static GlslExpr literal(int expr) {
return new IntLiteral(expr);
}
static GlslExpr literal(boolean expr) {
return new BoolLiteral(expr);
}
/** /**
* Call a one-parameter function with the given name on this expression. * Call a one-parameter function with the given name on this expression.
@ -56,31 +74,66 @@ public sealed interface GlslExpr {
return f.apply(this); return f.apply(this);
} }
String prettyPrint();
record Variable(String name) implements GlslExpr { record Variable(String name) implements GlslExpr {
@Override @Override
public String minPrint() { public String prettyPrint() {
return name; return name;
} }
} }
record FunctionCall(String name, GlslExpr target) implements GlslExpr { record FunctionCall(String name, Collection<? extends GlslExpr> args) implements GlslExpr {
@Override public FunctionCall(String name, GlslExpr target) {
public String minPrint() { this(name, ImmutableList.of(target));
return name + "(" + target.minPrint() + ")";
} }
@Override
public String prettyPrint() {
var args = this.args.stream()
.map(GlslExpr::prettyPrint)
.collect(Collectors.joining(","));
return name + "(" + args + ")";
}
}
record FunctionCall0(String name) implements GlslExpr {
@Override
public String prettyPrint() {
return name + "()";
}
} }
record Swizzle(GlslExpr target, String selection) implements GlslExpr { record Swizzle(GlslExpr target, String selection) implements GlslExpr {
@Override @Override
public String minPrint() { public String prettyPrint() {
return target.minPrint() + "." + selection; return target.prettyPrint() + "." + selection;
} }
} }
record Access(GlslExpr target, String argName) implements GlslExpr { record Access(GlslExpr target, String argName) implements GlslExpr {
@Override @Override
public String minPrint() { public String prettyPrint() {
return target.minPrint() + "." + argName; return target.prettyPrint() + "." + argName;
}
}
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

@ -0,0 +1,31 @@
package com.jozufozu.flywheel.core.source.generate;
public interface GlslStmt {
GlslStmt BREAK = () -> "break;";
GlslStmt CONTINUE = () -> "continue;";
GlslStmt RETURN = () -> "return;";
static GlslStmt eval(GlslExpr expr) {
return new Eval(expr);
}
static GlslStmt ret(GlslExpr value) {
return new Return(value);
}
String prettyPrint();
record Eval(GlslExpr expr) implements GlslStmt {
@Override
public String prettyPrint() {
return expr.prettyPrint() + ";";
}
}
record Return(GlslExpr expr) implements GlslStmt {
@Override
public String prettyPrint() {
return "return " + expr.prettyPrint() + ";";
}
}
}

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(), formatCases());
}
@NotNull
private String formatCases() {
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,52 @@
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;
import com.jozufozu.flywheel.util.StringUtil;
public class GlslUniformBlock implements GlslBuilder.Declaration {
private String qualifier;
private int binding;
private String name;
private final List<Pair<String, String>> members = new ArrayList<>();
@Override
public String prettyPrint() {
return """
layout(%s, binding = %d) uniform %s {
%s
};
""".formatted(qualifier, binding, name, StringUtil.indent(formatMembers(), 4));
}
private String formatMembers() {
return members.stream()
.map(p -> p.first() + " " + p.second() + ";")
.collect(Collectors.joining("\n"));
}
public GlslUniformBlock layout(String qualifier) {
this.qualifier = qualifier;
return this;
}
public GlslUniformBlock binding(int i) {
binding = i;
return this;
}
public GlslUniformBlock name(String name) {
this.name = name;
return this;
}
public GlslUniformBlock member(String typeName, String variableName) {
members.add(Pair.of(typeName, variableName));
return this;
}
}

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,5 +1,5 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.core.compile; package com.jozufozu.flywheel.core.source.generate;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -1,13 +0,0 @@
package com.jozufozu.flywheel.core.source.parse;
import com.jozufozu.flywheel.core.source.span.Span;
public abstract class AbstractShaderElement {
public final Span self;
public AbstractShaderElement(Span self) {
this.self = self;
}
}

View file

@ -1,52 +1,19 @@
package com.jozufozu.flywheel.core.source.parse; package com.jozufozu.flywheel.core.source.parse;
import java.util.Optional;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.core.source.SourceFile;
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
import com.jozufozu.flywheel.core.source.span.Span; import com.jozufozu.flywheel.core.source.span.Span;
import net.minecraft.ResourceLocationException; public class Import {
import net.minecraft.resources.ResourceLocation;
public class Import extends AbstractShaderElement {
public static final Pattern PATTERN = Pattern.compile("^\\s*#\\s*use\\s+\"(.*)\"", Pattern.MULTILINE); public static final Pattern PATTERN = Pattern.compile("^\\s*#\\s*use\\s+\"(.*)\"", Pattern.MULTILINE);
public final FileResolution resolution; public final Span self;
public final Span file;
protected Import(Span self, FileResolution resolution, Span file) { public Import(Span self, Span file) {
super(self); this.self = self;
this.resolution = resolution.addSpan(file); this.file = file;
} }
@Nullable
public static Import create(ErrorReporter errorReporter, Span self, Span file) {
ResourceLocation fileLocation;
try {
fileLocation = new ResourceLocation(file.get());
} catch (ResourceLocationException e) {
errorReporter.generateSpanError(file, "malformed source location");
return null;
}
return new Import(self, FileResolution.weak(fileLocation), file);
}
public Optional<SourceFile> getOptional() {
return Optional.ofNullable(resolution.getFile());
}
@Nullable
public SourceFile getFile() {
return resolution.getFile();
}
public ResourceLocation getFileLoc() {
return resolution.getFileLoc();
}
} }

View file

@ -6,16 +6,17 @@ import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.core.source.span.Span; import com.jozufozu.flywheel.core.source.span.Span;
public class ShaderField extends AbstractShaderElement { public class ShaderField {
public static final Pattern PATTERN = Pattern.compile("layout\\s*\\(location\\s*=\\s*(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)"); public static final Pattern PATTERN = Pattern.compile("layout\\s*\\(location\\s*=\\s*(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)");
public final Span location; public final Span location;
public final @Nullable Decoration decoration; public final @Nullable Decoration decoration;
public final Span type; public final Span type;
public final Span name; public final Span name;
public final Span self;
public ShaderField(Span self, Span location, Span inOut, Span type, Span name) { public ShaderField(Span self, Span location, Span inOut, Span type, Span name) {
super(self); this.self = self;
this.location = location; this.location = location;
this.decoration = Decoration.fromSpan(inOut); this.decoration = Decoration.fromSpan(inOut);

View file

@ -7,13 +7,14 @@ import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.core.source.span.Span; import com.jozufozu.flywheel.core.source.span.Span;
public class ShaderFunction extends AbstractShaderElement { public class ShaderFunction {
// https://regexr.com/60n3d // https://regexr.com/60n3d
public static final Pattern PATTERN = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{"); public static final Pattern PATTERN = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{");
public static final Pattern argument = Pattern.compile("(?:(inout|in|out) )?(\\w+)\\s+(\\w+)"); public static final Pattern argument = Pattern.compile("(?:(inout|in|out) )?(\\w+)\\s+(\\w+)");
public static final Pattern assignment = Pattern.compile("(\\w+)\\s*="); public static final Pattern assignment = Pattern.compile("(\\w+)\\s*=");
public final Span self;
private final Span type; private final Span type;
private final Span name; private final Span name;
@ -23,7 +24,7 @@ public class ShaderFunction extends AbstractShaderElement {
private final ImmutableList<ShaderVariable> parameters; private final ImmutableList<ShaderVariable> parameters;
public ShaderFunction(Span self, Span type, Span name, Span args, Span body) { public ShaderFunction(Span self, Span type, Span name, Span args, Span body) {
super(self); this.self = self;
this.type = type; this.type = type;
this.name = name; this.name = name;
this.args = args; this.args = args;

View file

@ -7,21 +7,24 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.core.source.span.Span; import com.jozufozu.flywheel.core.source.span.Span;
public class ShaderStruct extends AbstractShaderElement { public class ShaderStruct {
// https://regexr.com/61rpe // https://regexr.com/61rpe
public static final Pattern PATTERN = Pattern.compile("struct\\s+([\\w\\d]*)\\s*\\{([\\w\\d\\s,;]*)}\\s*;\\s"); public static final Pattern PATTERN = Pattern.compile("struct\\s+([\\w_]*)\\s*\\{(.*?)}\\s*([\\w_]*)?\\s*;\\s", Pattern.DOTALL);
public final Span name; public final Span name;
public final Span body; public final Span body;
public final Span self;
public final Span variableName;
private final ImmutableList<StructField> fields; private final ImmutableList<StructField> fields;
private final ImmutableMap<String, Span> fields2Types; private final ImmutableMap<String, Span> fields2Types;
public ShaderStruct(Span self, Span name, Span body) { public ShaderStruct(Span self, Span name, Span body, Span variableName) {
super(self); this.self = self;
this.name = name; this.name = name;
this.body = body; this.body = body;
this.variableName = variableName;
this.fields = parseFields(); this.fields = parseFields();
this.fields2Types = createTypeLookup(); this.fields2Types = createTypeLookup();
} }

View file

@ -2,15 +2,16 @@ package com.jozufozu.flywheel.core.source.parse;
import com.jozufozu.flywheel.core.source.span.Span; import com.jozufozu.flywheel.core.source.span.Span;
public class ShaderVariable extends AbstractShaderElement { public class ShaderVariable {
public final Span qualifierSpan; public final Span qualifierSpan;
public final Span type; public final Span type;
public final Span name; public final Span name;
public final Qualifier qualifier; public final Qualifier qualifier;
public final Span self;
public ShaderVariable(Span self, Span qualifier, Span type, Span name) { public ShaderVariable(Span self, Span qualifier, Span type, Span name) {
super(self); this.self = self;
this.qualifierSpan = qualifier; this.qualifierSpan = qualifier;
this.type = type; this.type = type;
this.name = name; this.name = name;

View file

@ -4,14 +4,15 @@ import java.util.regex.Pattern;
import com.jozufozu.flywheel.core.source.span.Span; import com.jozufozu.flywheel.core.source.span.Span;
public class StructField extends AbstractShaderElement { public class StructField {
public static final Pattern fieldPattern = Pattern.compile("(\\S+)\\s*(\\S+);"); public static final Pattern fieldPattern = Pattern.compile("(\\S+)\\s*(\\S+);");
public final Span self;
public Span type; public Span type;
public Span name; public Span name;
public StructField(Span self, Span type, Span name) { public StructField(Span self, Span type, Span name) {
super(self); this.self = self;
this.type = type; this.type = type;
this.name = name; this.name = name;
} }

View file

@ -23,7 +23,7 @@ public abstract class Span implements CharSequence, Comparable<Span> {
protected final CharPos end; protected final CharPos end;
public Span(SourceFile in, int start, int end) { public Span(SourceFile in, int start, int end) {
this(in, in.lines.getCharPos(start), in.lines.getCharPos(end)); this(in, in.source.getCharPos(start), in.source.getCharPos(end));
} }
public Span(SourceFile in, CharPos start, CharPos end) { public Span(SourceFile in, CharPos start, CharPos end) {
@ -131,7 +131,7 @@ public abstract class Span implements CharSequence, Comparable<Span> {
if (isErr()) { if (isErr()) {
return Optional.empty(); return Optional.empty();
} }
return in.findStruct(this.toString()); return in.findStructByName(this.toString());
} }
public Optional<ShaderFunction> findFunction() { public Optional<ShaderFunction> findFunction() {

View file

@ -19,8 +19,7 @@ public class StringSpan extends Span {
@Override @Override
public String get() { public String get() {
return in.source return in.source.raw.substring(start.pos(), end.pos());
.substring(start.pos(), end.pos());
} }
@Override @Override

View file

@ -5,7 +5,6 @@ import com.jozufozu.flywheel.api.struct.StructWriter;
import com.jozufozu.flywheel.core.Components; import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.layout.BufferLayout; import com.jozufozu.flywheel.core.layout.BufferLayout;
import com.jozufozu.flywheel.core.layout.CommonItems; import com.jozufozu.flywheel.core.layout.CommonItems;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.util.RenderMath; import com.jozufozu.flywheel.util.RenderMath;
import com.mojang.math.Matrix3f; import com.mojang.math.Matrix3f;
import com.mojang.math.Matrix4f; import com.mojang.math.Matrix4f;
@ -13,6 +12,8 @@ import com.mojang.math.Quaternion;
import com.mojang.math.Vector3f; import com.mojang.math.Vector3f;
import com.mojang.math.Vector4f; import com.mojang.math.Vector4f;
import net.minecraft.resources.ResourceLocation;
public class OrientedType implements StructType<OrientedPart> { public class OrientedType implements StructType<OrientedPart> {
public static final BufferLayout FORMAT = BufferLayout.builder() public static final BufferLayout FORMAT = BufferLayout.builder()
@ -39,7 +40,7 @@ public class OrientedType implements StructType<OrientedPart> {
} }
@Override @Override
public FileResolution getInstanceShader() { public ResourceLocation instanceShader() {
return Components.Files.ORIENTED; return Components.Files.ORIENTED;
} }

View file

@ -5,11 +5,12 @@ import com.jozufozu.flywheel.api.struct.StructWriter;
import com.jozufozu.flywheel.core.Components; import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.layout.BufferLayout; import com.jozufozu.flywheel.core.layout.BufferLayout;
import com.jozufozu.flywheel.core.layout.CommonItems; import com.jozufozu.flywheel.core.layout.CommonItems;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.util.RenderMath; import com.jozufozu.flywheel.util.RenderMath;
import com.mojang.math.Vector3f; import com.mojang.math.Vector3f;
import com.mojang.math.Vector4f; import com.mojang.math.Vector4f;
import net.minecraft.resources.ResourceLocation;
public class TransformedType implements StructType<TransformedPart> { public class TransformedType implements StructType<TransformedPart> {
public static final BufferLayout FORMAT = BufferLayout.builder() public static final BufferLayout FORMAT = BufferLayout.builder()
@ -35,7 +36,7 @@ public class TransformedType implements StructType<TransformedPart> {
} }
@Override @Override
public FileResolution getInstanceShader() { public ResourceLocation instanceShader() {
return Components.Files.TRANSFORMED; return Components.Files.TRANSFORMED;
} }

View file

@ -0,0 +1,138 @@
package com.jozufozu.flywheel.core.uniform;
import java.util.function.Consumer;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.uniform.ShaderUniforms;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.util.MatrixUtil;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.core.Vec3i;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.MinecraftForge;
public class FlwShaderUniforms implements ShaderUniforms {
public static final int SIZE = 224;
public static boolean FRUSTUM_PAUSED = false;
public static boolean FRUSTUM_CAPTURE = false;
public static boolean FOG_UPDATE = true;
@Override
public int byteSize() {
return SIZE;
}
@Override
public ResourceLocation uniformShader() {
return Components.Files.UNIFORMS;
}
@Override
public Provider activate(long ptr) {
return new Active(ptr);
}
public static class Active implements Provider, Consumer<BeginFrameEvent> {
private final long ptr;
private boolean dirty;
public Active(long ptr) {
this.ptr = ptr;
MinecraftForge.EVENT_BUS.addListener(this);
}
@Override
public void delete() {
MinecraftForge.EVENT_BUS.unregister(this);
}
@Override
public boolean poll() {
boolean updated = maybeUpdateFog();
updated |= dirty;
dirty = false;
return updated;
}
@Override
public void accept(BeginFrameEvent event) {
if (ptr == MemoryUtil.NULL) {
return;
}
RenderContext context = event.getContext();
Vec3i originCoordinate = InstancedRenderDispatcher.getOriginCoordinate(context.level());
Vec3 camera = context.camera()
.getPosition();
var camX = (float) (camera.x - originCoordinate.getX());
var camY = (float) (camera.y - originCoordinate.getY());
var camZ = (float) (camera.z - originCoordinate.getZ());
// don't want to mutate viewProjection
var vp = context.viewProjection()
.copy();
vp.multiplyWithTranslation(-camX, -camY, -camZ);
MatrixUtil.writeUnsafe(vp, ptr + 32);
MemoryUtil.memPutFloat(ptr + 96, camX);
MemoryUtil.memPutFloat(ptr + 100, camY);
MemoryUtil.memPutFloat(ptr + 104, camZ);
MemoryUtil.memPutFloat(ptr + 108, 0f); // vec4 alignment
MemoryUtil.memPutInt(ptr + 112, getConstantAmbientLightFlag(context));
updateFrustum(context, camX, camY, camZ);
dirty = true;
}
private static int getConstantAmbientLightFlag(RenderContext context) {
var constantAmbientLight = context.level()
.effects()
.constantAmbientLight();
return constantAmbientLight ? 1 : 0;
}
private boolean maybeUpdateFog() {
if (!FOG_UPDATE || ptr == MemoryUtil.NULL) {
return false;
}
var color = RenderSystem.getShaderFogColor();
MemoryUtil.memPutFloat(ptr, color[0]);
MemoryUtil.memPutFloat(ptr + 4, color[1]);
MemoryUtil.memPutFloat(ptr + 8, color[2]);
MemoryUtil.memPutFloat(ptr + 12, color[3]);
MemoryUtil.memPutFloat(ptr + 16, RenderSystem.getShaderFogStart());
MemoryUtil.memPutFloat(ptr + 20, RenderSystem.getShaderFogEnd());
MemoryUtil.memPutInt(ptr + 24, RenderSystem.getShaderFogShape()
.getIndex());
FOG_UPDATE = false;
return true;
}
private void updateFrustum(RenderContext context, float camX, float camY, float camZ) {
if (FRUSTUM_PAUSED && !FRUSTUM_CAPTURE) {
return;
}
var shiftedCuller = RenderContext.createCuller(context.viewProjection(), -camX, -camY, -camZ);
shiftedCuller.getJozuPackedPlanes(ptr + 128);
FRUSTUM_CAPTURE = false;
}
}
}

View file

@ -1,39 +0,0 @@
package com.jozufozu.flywheel.core.uniform;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.uniform.UniformProvider;
import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.mojang.blaze3d.systems.RenderSystem;
public class FogProvider extends UniformProvider {
@Override
public int getActualByteSize() {
return 16 + 8 + 4;
}
public void update() {
if (ptr == MemoryUtil.NULL) {
return;
}
var color = RenderSystem.getShaderFogColor();
MemoryUtil.memPutFloat(ptr, color[0]);
MemoryUtil.memPutFloat(ptr + 4, color[1]);
MemoryUtil.memPutFloat(ptr + 8, color[2]);
MemoryUtil.memPutFloat(ptr + 12, color[3]);
MemoryUtil.memPutFloat(ptr + 16, RenderSystem.getShaderFogStart());
MemoryUtil.memPutFloat(ptr + 20, RenderSystem.getShaderFogEnd());
MemoryUtil.memPutInt(ptr + 24, RenderSystem.getShaderFogShape().getIndex());
notifier.signalChanged();
}
@Override
public FileResolution getUniformShader() {
return Components.Files.FOG_UNIFORMS;
}
}

View file

@ -1,59 +0,0 @@
package com.jozufozu.flywheel.core.uniform;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.uniform.UniformProvider;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.MinecraftForge;
public class FrustumProvider extends UniformProvider {
public static boolean PAUSED = false;
public static boolean CAPTURE = false;
public FrustumProvider() {
MinecraftForge.EVENT_BUS.addListener(this::beginFrame);
}
@Override
public int getActualByteSize() {
return 96;
}
@Override
public FileResolution getUniformShader() {
return Components.Files.FRUSTUM_UNIFORMS;
}
public void beginFrame(BeginFrameEvent event) {
update(event.getContext());
}
public void update(RenderContext context) {
if (ptr == MemoryUtil.NULL || (PAUSED && !CAPTURE)) {
return;
}
Vec3i originCoordinate = InstancedRenderDispatcher.getOriginCoordinate(context.level());
Vec3 camera = context.camera()
.getPosition();
var camX = (float) (camera.x - originCoordinate.getX());
var camY = (float) (camera.y - originCoordinate.getY());
var camZ = (float) (camera.z - originCoordinate.getZ());
var shiftedCuller = RenderContext.createCuller(context.viewProjection(), -camX, -camY, -camZ);
shiftedCuller.getJozuPackedPlanes(ptr);
notifier.signalChanged();
CAPTURE = false;
}
}

View file

@ -1,17 +1,17 @@
package com.jozufozu.flywheel.core.uniform; package com.jozufozu.flywheel.core.uniform;
import java.util.BitSet;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.lwjgl.opengl.GL32; import org.lwjgl.opengl.GL32;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.api.uniform.UniformProvider; import com.jozufozu.flywheel.api.uniform.ShaderUniforms;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer; import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType; import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.memory.MemoryBlock; import com.jozufozu.flywheel.backend.memory.MemoryBlock;
import com.jozufozu.flywheel.core.ComponentRegistry; import com.jozufozu.flywheel.core.ComponentRegistry;
import com.jozufozu.flywheel.util.FlwUtil;
import com.jozufozu.flywheel.util.RenderMath; import com.jozufozu.flywheel.util.RenderMath;
public class UniformBuffer { public class UniformBuffer {
@ -22,7 +22,7 @@ public class UniformBuffer {
private static final boolean PO2_ALIGNMENT = RenderMath.isPowerOf2(OFFSET_ALIGNMENT); private static final boolean PO2_ALIGNMENT = RenderMath.isPowerOf2(OFFSET_ALIGNMENT);
private static UniformBuffer instance; private static UniformBuffer instance;
private final List<Allocated> allocatedProviders; private final ProviderSet providerSet;
public static UniformBuffer getInstance() { public static UniformBuffer getInstance() {
if (instance == null) { if (instance == null) {
@ -32,51 +32,18 @@ public class UniformBuffer {
} }
private final GlBuffer buffer; private final GlBuffer buffer;
private final MemoryBlock data;
private final BitSet changedBytes;
private UniformBuffer() { private UniformBuffer() {
buffer = new GlBuffer(GlBufferType.UNIFORM_BUFFER); buffer = new GlBuffer(GlBufferType.UNIFORM_BUFFER);
providerSet = new ProviderSet(ComponentRegistry.getAllUniformProviders());
Collection<UniformProvider> providers = ComponentRegistry.getAllUniformProviders();
var builder = ImmutableList.<Allocated>builder();
int totalBytes = 0;
int index = 0;
for (UniformProvider provider : providers) {
int size = alignPo2(provider.getActualByteSize(), 16);
builder.add(new Allocated(provider, totalBytes, size, index));
totalBytes = alignUniformBuffer(totalBytes + size);
index++;
}
allocatedProviders = builder.build();
data = MemoryBlock.mallocTracked(totalBytes);
changedBytes = new BitSet(totalBytes);
for (Allocated p : allocatedProviders) {
p.updatePtr(data);
}
} }
public void sync() { public void sync() {
if (changedBytes.isEmpty()) { if (providerSet.pollUpdates()) {
return; buffer.upload(providerSet.data);
} }
// TODO: upload only changed bytes GL32.glBindBufferRange(GL32.GL_UNIFORM_BUFFER, 0, buffer.handle(), 0, providerSet.data.size());
changedBytes.clear();
buffer.upload(data);
int handle = buffer.handle();
for (Allocated p : allocatedProviders) {
GL32.glBindBufferRange(GL32.GL_UNIFORM_BUFFER, p.index, handle, p.offset, p.size);
}
} }
// https://stackoverflow.com/questions/3407012/rounding-up-to-the-nearest-multiple-of-a-number // https://stackoverflow.com/questions/3407012/rounding-up-to-the-nearest-multiple-of-a-number
@ -88,46 +55,61 @@ public class UniformBuffer {
} }
} }
private static int alignPo2(int numToRound, int alignment) { private static class LiveProvider {
return (numToRound + alignment - 1) & -alignment; private final ShaderUniforms provider;
}
private class Allocated implements UniformProvider.Notifier {
private final UniformProvider provider;
private final int offset; private final int offset;
private final int size; private final int size;
private final int index; private ShaderUniforms.Provider activeProvider;
private Allocated(UniformProvider provider, int offset, int size, int index) { private LiveProvider(ShaderUniforms provider, int offset, int size) {
this.provider = provider; this.provider = provider;
this.offset = offset; this.offset = offset;
this.size = size; this.size = size;
this.index = index;
}
@Override
public void signalChanged() {
changedBytes.set(offset, offset + size);
} }
private void updatePtr(MemoryBlock bufferBase) { private void updatePtr(MemoryBlock bufferBase) {
provider.updatePtr(bufferBase.ptr() + offset, this); if (activeProvider != null) {
activeProvider.delete();
}
activeProvider = provider.activate(bufferBase.ptr() + offset);
} }
public UniformProvider provider() { public boolean maybePoll() {
return provider; return activeProvider != null && activeProvider.poll();
}
} }
public int offset() { private static class ProviderSet {
return offset; private final List<LiveProvider> allocatedProviders;
private final MemoryBlock data;
private ProviderSet(final Collection<ShaderUniforms> providers) {
var builder = ImmutableList.<LiveProvider>builder();
int totalBytes = 0;
for (ShaderUniforms provider : providers) {
int size = FlwUtil.align16(provider.byteSize());
builder.add(new LiveProvider(provider, totalBytes, size));
totalBytes += size;
} }
public int size() { allocatedProviders = builder.build();
return size;
data = MemoryBlock.mallocTracked(totalBytes);
for (LiveProvider p : allocatedProviders) {
p.updatePtr(data);
}
} }
public int index() { public boolean pollUpdates() {
return index; boolean changed = false;
for (LiveProvider p : allocatedProviders) {
changed |= p.maybePoll();
}
return changed;
} }
} }
} }

View file

@ -1,68 +0,0 @@
package com.jozufozu.flywheel.core.uniform;
import org.lwjgl.system.MemoryUtil;
import com.jozufozu.flywheel.api.uniform.UniformProvider;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.core.Components;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.core.source.FileResolution;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.util.MatrixUtil;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.common.MinecraftForge;
public class ViewProvider extends UniformProvider {
public ViewProvider() {
MinecraftForge.EVENT_BUS.addListener(this::beginFrame);
}
public void beginFrame(BeginFrameEvent event) {
update(event.getContext());
}
@Override
public int getActualByteSize() {
return 4 * 16 + 16 + 4;
}
public void update(RenderContext context) {
if (ptr == MemoryUtil.NULL) {
return;
}
ClientLevel level = context.level();
int constantAmbientLight = level.effects()
.constantAmbientLight() ? 1 : 0;
Vec3i originCoordinate = InstancedRenderDispatcher.getOriginCoordinate(level);
Vec3 camera = context.camera()
.getPosition();
var camX = (float) (camera.x - originCoordinate.getX());
var camY = (float) (camera.y - originCoordinate.getY());
var camZ = (float) (camera.z - originCoordinate.getZ());
// don't want to mutate viewProjection
var vp = context.viewProjection().copy();
vp.multiplyWithTranslation(-camX, -camY, -camZ);
MatrixUtil.writeUnsafe(vp, ptr);
MemoryUtil.memPutFloat(ptr + 64, camX);
MemoryUtil.memPutFloat(ptr + 68, camY);
MemoryUtil.memPutFloat(ptr + 72, camZ);
MemoryUtil.memPutInt(ptr + 76, constantAmbientLight);
notifier.signalChanged();
}
@Override
public FileResolution getUniformShader() {
return Components.Files.VIEW_UNIFORMS;
}
}

Some files were not shown because too many files have changed in this diff Show more