mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-02-25 19:29:47 +01:00
Merge branch '1.18/shader-sanity' into 1.18/dev
This commit is contained in:
commit
1ad64c595c
134 changed files with 1874 additions and 1984 deletions
|
@ -1,13 +1,16 @@
|
|||
package com.jozufozu.flywheel;
|
||||
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.OptifineHandler;
|
||||
import com.jozufozu.flywheel.core.Contexts;
|
||||
import com.jozufozu.flywheel.core.Materials;
|
||||
import com.jozufozu.flywheel.core.PartialModel;
|
||||
import com.jozufozu.flywheel.core.StitchedSprite;
|
||||
import com.jozufozu.flywheel.core.compile.ProgramCompiler;
|
||||
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
|
||||
import com.jozufozu.flywheel.mixin.PausedPartialTickAccessor;
|
||||
import com.jozufozu.flywheel.vanilla.VanillaInstances;
|
||||
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.CrashReportCallables;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
|
@ -15,20 +18,21 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
|||
public class FlywheelClient {
|
||||
|
||||
public static void clientInit() {
|
||||
CrashReportCallables.registerCrashCallable("Flywheel Backend", () ->
|
||||
Backend.getInstance().getBackendDescriptor());
|
||||
CrashReportCallables.registerCrashCallable("Flywheel Backend", Backend::getBackendDescriptor);
|
||||
|
||||
OptifineHandler.init();
|
||||
Backend.init();
|
||||
IEventBus modEventBus = FMLJavaModLoadingContext.get()
|
||||
.getModEventBus();
|
||||
|
||||
modEventBus.addListener(Contexts::flwInit);
|
||||
modEventBus.addListener(Materials::flwInit);
|
||||
modEventBus.addListener(PartialModel::onModelRegistry);
|
||||
modEventBus.addListener(PartialModel::onModelBake);
|
||||
modEventBus.addListener(StitchedSprite::onTextureStitchPre);
|
||||
modEventBus.addListener(StitchedSprite::onTextureStitchPost);
|
||||
|
||||
MinecraftForge.EVENT_BUS.<ReloadRenderersEvent>addListener(ProgramCompiler::invalidateAll);
|
||||
|
||||
VanillaInstances.init();
|
||||
|
||||
// https://github.com/Jozufozu/Flywheel/issues/69
|
||||
|
|
|
@ -32,6 +32,8 @@ public interface VertexType {
|
|||
*/
|
||||
VertexList createReader(ByteBuffer buffer, int vertexCount);
|
||||
|
||||
String getShaderHeader();
|
||||
|
||||
default int getStride() {
|
||||
return getLayout().getStride();
|
||||
}
|
||||
|
|
|
@ -1,25 +1,15 @@
|
|||
package com.jozufozu.flywheel.backend;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GLCapabilities;
|
||||
|
||||
import com.jozufozu.flywheel.api.FlywheelWorld;
|
||||
import com.jozufozu.flywheel.api.InstanceData;
|
||||
import com.jozufozu.flywheel.api.struct.StructType;
|
||||
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
|
||||
import com.jozufozu.flywheel.config.FlwConfig;
|
||||
import com.jozufozu.flywheel.config.FlwEngine;
|
||||
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
|
||||
import com.jozufozu.flywheel.core.shader.ProgramSpec;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
@ -29,101 +19,39 @@ import net.minecraft.world.level.LevelAccessor;
|
|||
public class Backend {
|
||||
public static final Logger LOGGER = LogManager.getLogger(Backend.class);
|
||||
|
||||
protected static final Backend INSTANCE = new Backend();
|
||||
public static Backend getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
private static FlwEngine engine;
|
||||
|
||||
private FlwEngine engine;
|
||||
public static GlCompat compat;
|
||||
|
||||
public GLCapabilities capabilities;
|
||||
public GlCompat compat;
|
||||
|
||||
public final Loader loader;
|
||||
private final List<ShaderContext<?>> contexts = new ArrayList<>();
|
||||
private final Map<ResourceLocation, StructType<?>> materialRegistry = new HashMap<>();
|
||||
private final Map<ResourceLocation, ProgramSpec> programSpecRegistry = new HashMap<>();
|
||||
|
||||
protected Backend() {
|
||||
loader = new Loader(this);
|
||||
|
||||
OptifineHandler.init();
|
||||
}
|
||||
private static final Loader loader = new Loader();
|
||||
|
||||
/**
|
||||
* Get a string describing the Flywheel backend. When there are eventually multiple backends
|
||||
* (Meshlet, MDI, GL31 Draw Instanced are planned), this will name which one is in use.
|
||||
*/
|
||||
public String getBackendDescriptor() {
|
||||
public static String getBackendDescriptor() {
|
||||
return engine == null ? "" : engine.getProperName();
|
||||
}
|
||||
|
||||
public FlwEngine getEngine() {
|
||||
public static FlwEngine getEngine() {
|
||||
return engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a shader program.
|
||||
*/
|
||||
public ProgramSpec register(ProgramSpec spec) {
|
||||
ResourceLocation name = spec.name;
|
||||
if (programSpecRegistry.containsKey(name)) {
|
||||
throw new IllegalStateException("Program spec '" + name + "' already registered.");
|
||||
}
|
||||
programSpecRegistry.put(name, spec);
|
||||
return spec;
|
||||
@Nullable
|
||||
public static ProgramSpec getSpec(ResourceLocation name) {
|
||||
return loader.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a shader context.
|
||||
*/
|
||||
public <C extends ShaderContext<?>> C register(C spec) {
|
||||
contexts.add(spec);
|
||||
return spec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an instancing material.
|
||||
*/
|
||||
public <D extends InstanceData> StructType<D> register(ResourceLocation name, StructType<D> spec) {
|
||||
if (materialRegistry.containsKey(name)) {
|
||||
throw new IllegalStateException("Material spec '" + name + "' already registered.");
|
||||
}
|
||||
materialRegistry.put(name, spec);
|
||||
|
||||
LOGGER.debug("registered material '" + name + "' with instance size " + spec.getLayout().getStride());
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
public ProgramSpec getSpec(ResourceLocation name) {
|
||||
return programSpecRegistry.get(name);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
public static void refresh() {
|
||||
OptifineHandler.refresh();
|
||||
|
||||
capabilities = GL.createCapabilities();
|
||||
|
||||
compat = new GlCompat(capabilities);
|
||||
compat = new GlCompat();
|
||||
|
||||
engine = chooseEngine(compat);
|
||||
}
|
||||
|
||||
public Collection<StructType<?>> allMaterials() {
|
||||
return materialRegistry.values();
|
||||
}
|
||||
|
||||
public Collection<ProgramSpec> allPrograms() {
|
||||
return programSpecRegistry.values();
|
||||
}
|
||||
|
||||
public Collection<ShaderContext<?>> allContexts() {
|
||||
return contexts;
|
||||
}
|
||||
|
||||
public static boolean isOn() {
|
||||
return getInstance().engine != FlwEngine.OFF;
|
||||
return engine != FlwEngine.OFF;
|
||||
}
|
||||
|
||||
public static boolean canUseInstancing(@Nullable Level world) {
|
||||
|
@ -151,20 +79,6 @@ public class Backend {
|
|||
RenderWork.enqueue(Minecraft.getInstance().levelRenderer::allChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL USE ONLY
|
||||
*/
|
||||
void _clearContexts() {
|
||||
GameStateRegistry.clear();
|
||||
programSpecRegistry.clear();
|
||||
contexts.forEach(ShaderContext::delete);
|
||||
contexts.clear();
|
||||
materialRegistry.clear();
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
}
|
||||
|
||||
private static FlwEngine chooseEngine(GlCompat compat) {
|
||||
FlwEngine preferredChoice = FlwConfig.get()
|
||||
.getEngine();
|
||||
|
@ -178,4 +92,12 @@ public class Backend {
|
|||
|
||||
return canUseEngine ? preferredChoice : FlwEngine.OFF;
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
// noop
|
||||
}
|
||||
|
||||
private Backend() {
|
||||
throw new UnsupportedOperationException("Backend is a static class!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.jozufozu.flywheel.core.shader.gamestate.GameStateProvider;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class GameStateRegistry {
|
||||
|
||||
private static final Map<ResourceLocation, GameStateProvider> registeredStateProviders = new HashMap<>();
|
||||
|
||||
static void clear() {
|
||||
registeredStateProviders.clear();
|
||||
}
|
||||
|
||||
public static GameStateProvider getStateProvider(ResourceLocation location) {
|
||||
GameStateProvider out = registeredStateProviders.get(location);
|
||||
|
||||
if (out == null) {
|
||||
throw new IllegalArgumentException("State provider '" + location + "' does not exist.");
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static void register(GameStateProvider context) {
|
||||
if (registeredStateProviders.containsKey(context.getID())) {
|
||||
throw new IllegalStateException("Duplicate game state provider: " + context.getID());
|
||||
}
|
||||
|
||||
registeredStateProviders.put(context.getID(), context);
|
||||
}
|
||||
}
|
|
@ -1,16 +1,20 @@
|
|||
package com.jozufozu.flywheel.backend;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
|
||||
import com.jozufozu.flywheel.backend.source.Resolver;
|
||||
import com.jozufozu.flywheel.backend.source.ShaderLoadingException;
|
||||
import com.jozufozu.flywheel.backend.source.ShaderSources;
|
||||
import com.jozufozu.flywheel.core.GameStateRegistry;
|
||||
import com.jozufozu.flywheel.core.crumbling.CrumblingRenderer;
|
||||
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
|
||||
import com.jozufozu.flywheel.core.shader.ProgramSpec;
|
||||
import com.jozufozu.flywheel.core.source.Resolver;
|
||||
import com.jozufozu.flywheel.core.source.ShaderSources;
|
||||
import com.jozufozu.flywheel.event.GatherContextEvent;
|
||||
import com.jozufozu.flywheel.util.ResourceUtil;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
|
@ -38,14 +42,11 @@ public class Loader implements ResourceManagerReloadListener {
|
|||
public static final String PROGRAM_DIR = "flywheel/programs/";
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
|
||||
private final Backend backend;
|
||||
private boolean shouldCrash;
|
||||
private final Map<ResourceLocation, ProgramSpec> programSpecRegistry = new HashMap<>();
|
||||
|
||||
private boolean firstLoad = true;
|
||||
|
||||
public Loader(Backend backend) {
|
||||
this.backend = backend;
|
||||
|
||||
Loader() {
|
||||
// Can be null when running datagenerators due to the unfortunate time we call this
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
if (minecraft != null) {
|
||||
|
@ -56,36 +57,28 @@ public class Loader implements ResourceManagerReloadListener {
|
|||
}
|
||||
}
|
||||
|
||||
public void notifyError() {
|
||||
shouldCrash = true;
|
||||
@Nullable
|
||||
public ProgramSpec get(ResourceLocation name) {
|
||||
return programSpecRegistry.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResourceManagerReload(ResourceManager manager) {
|
||||
backend.refresh();
|
||||
Backend.refresh();
|
||||
|
||||
shouldCrash = false;
|
||||
backend._clearContexts();
|
||||
GameStateRegistry._clear();
|
||||
|
||||
Resolver.INSTANCE.invalidate();
|
||||
ModLoader.get()
|
||||
.postEvent(new GatherContextEvent(backend, firstLoad));
|
||||
.postEvent(new GatherContextEvent(firstLoad));
|
||||
|
||||
ShaderSources sources = new ShaderSources(manager);
|
||||
|
||||
loadProgramSpecs(manager);
|
||||
|
||||
Resolver.INSTANCE.resolve(sources);
|
||||
Resolver.INSTANCE.run(sources);
|
||||
|
||||
for (ShaderContext<?> context : backend.allContexts()) {
|
||||
context.load();
|
||||
}
|
||||
|
||||
if (shouldCrash) {
|
||||
throw new ShaderLoadingException("Could not load all shaders, see log for details");
|
||||
}
|
||||
|
||||
Backend.LOGGER.info("Loaded all shader programs.");
|
||||
Backend.LOGGER.info("Loaded all shader sources.");
|
||||
|
||||
ClientLevel world = Minecraft.getInstance().level;
|
||||
if (Backend.canUseInstancing(world)) {
|
||||
|
@ -116,10 +109,21 @@ public class Loader implements ResourceManagerReloadListener {
|
|||
|
||||
spec.setName(specName);
|
||||
|
||||
backend.register(spec);
|
||||
register(spec);
|
||||
} catch (Exception e) {
|
||||
Backend.LOGGER.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a shader program.
|
||||
*/
|
||||
private void register(ProgramSpec spec) {
|
||||
ResourceLocation name = spec.name;
|
||||
if (programSpecRegistry.containsKey(name)) {
|
||||
throw new IllegalStateException("Program spec '" + name + "' already registered.");
|
||||
}
|
||||
programSpecRegistry.put(name, spec);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public enum RenderLayer {
|
|||
;
|
||||
|
||||
@Nullable
|
||||
public static RenderLayer fromRenderType(RenderType type) {
|
||||
public static RenderLayer getPrimaryLayer(RenderType type) {
|
||||
if (type == RenderType.solid()) {
|
||||
return SOLID;
|
||||
} else if (type == RenderType.cutoutMipped()) {
|
||||
|
@ -49,4 +49,17 @@ public enum RenderLayer {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static RenderLayer getLayer(RenderType type) {
|
||||
if (type == RenderType.solid()) {
|
||||
return SOLID;
|
||||
} else if (type == RenderType.cutoutMipped() || type == RenderType.cutout()) {
|
||||
return CUTOUT;
|
||||
} else if (type == RenderType.translucent()) {
|
||||
return TRANSPARENT;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public interface ShaderContext<P extends GlProgram> {
|
||||
|
||||
default P getProgram(ResourceLocation loc) {
|
||||
return this.getProgramSupplier(loc)
|
||||
.get();
|
||||
}
|
||||
|
||||
Supplier<P> getProgramSupplier(ResourceLocation loc);
|
||||
|
||||
/**
|
||||
* Load all programs associated with this context. This might be just one, if the context is very specialized.
|
||||
*/
|
||||
void load();
|
||||
|
||||
void delete();
|
||||
}
|
|
@ -26,15 +26,15 @@ public enum GlNumericType {
|
|||
|
||||
private static final GlNumericType[] VALUES = values();
|
||||
private static final Map<String, GlNumericType> NAME_LOOKUP = Arrays.stream(VALUES)
|
||||
.collect(Collectors.toMap(GlNumericType::getDisplayName, type -> type));
|
||||
.collect(Collectors.toMap(GlNumericType::getTypeName, type -> type));
|
||||
|
||||
private final int byteWidth;
|
||||
private final String displayName;
|
||||
private final String typeName;
|
||||
private final int glEnum;
|
||||
|
||||
GlNumericType(int bytes, String name, int glEnum) {
|
||||
this.byteWidth = bytes;
|
||||
this.displayName = name;
|
||||
this.typeName = name;
|
||||
this.glEnum = glEnum;
|
||||
}
|
||||
|
||||
|
@ -42,8 +42,8 @@ public enum GlNumericType {
|
|||
return this.byteWidth;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return this.displayName;
|
||||
public String getTypeName() {
|
||||
return this.typeName;
|
||||
}
|
||||
|
||||
public int getGlEnum() {
|
||||
|
@ -64,4 +64,9 @@ public enum GlNumericType {
|
|||
public static GlNumericType byName(String name) {
|
||||
return name == null ? null : NAME_LOOKUP.get(name.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return typeName;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,8 +39,8 @@ public class GlVertexArray extends GlObject {
|
|||
int offset = 0;
|
||||
for (LayoutItem spec : type.getLayoutItems()) {
|
||||
spec.vertexAttribPointer(type.getStride(), startIndex, offset);
|
||||
startIndex += spec.getAttributeCount();
|
||||
offset += spec.getSize();
|
||||
startIndex += spec.attributeCount();
|
||||
offset += spec.size();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ public abstract class GlBuffer extends GlObject {
|
|||
* @return A buffer that will be persistent if the driver supports it.
|
||||
*/
|
||||
public static GlBuffer requestPersistent(GlBufferType type) {
|
||||
if (Backend.getInstance().compat.bufferStorageSupported()) {
|
||||
if (Backend.compat.bufferStorageSupported()) {
|
||||
return new PersistentGlBuffer(type);
|
||||
} else {
|
||||
return new MappedGlBuffer(type);
|
||||
|
|
|
@ -46,7 +46,7 @@ public class PersistentGlBuffer extends GlBuffer implements Mappable {
|
|||
|
||||
fence.clear();
|
||||
|
||||
Backend.getInstance().compat.bufferStorage.bufferStorage(type, size, flags);
|
||||
Backend.compat.bufferStorage.bufferStorage(type, size, flags);
|
||||
|
||||
ByteBuffer byteBuffer = GL30.glMapBufferRange(type.glEnum, 0, size, flags);
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
|
|||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
|
@ -83,4 +85,13 @@ public abstract class GlProgram extends GlObject {
|
|||
public String toString() {
|
||||
return "program " + name;
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory interface to create a {@link GlProgram}.
|
||||
*/
|
||||
public interface Factory<P extends GlProgram> {
|
||||
|
||||
@Nonnull
|
||||
P create(ResourceLocation name, int handle);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ package com.jozufozu.flywheel.backend.gl.shader;
|
|||
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.gl.GlObject;
|
||||
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
|
||||
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
@ -13,7 +13,7 @@ public class GlShader extends GlObject {
|
|||
public final ResourceLocation name;
|
||||
public final ShaderType type;
|
||||
|
||||
public GlShader(ResourceLocation name, ShaderType type, CharSequence source) {
|
||||
public GlShader(ResourceLocation name, ShaderType type, String source) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
int handle = GL20.glCreateShader(type.glEnum);
|
||||
|
@ -23,14 +23,12 @@ public class GlShader extends GlObject {
|
|||
|
||||
String log = GL20.glGetShaderInfoLog(handle);
|
||||
|
||||
if (!log.isEmpty()) {
|
||||
Backend.LOGGER.error("Shader compilation log for " + name + ": " + log);
|
||||
Backend.LOGGER.error(source);
|
||||
}
|
||||
//Backend.log.debug(shader.printSource());
|
||||
// if (!log.isEmpty()) {
|
||||
// env.printShaderInfoLog(source, log, this.name);
|
||||
// }
|
||||
|
||||
if (GL20.glGetShaderi(handle, GL20.GL_COMPILE_STATUS) != GL20.GL_TRUE) {
|
||||
throw new RuntimeException("Could not compile " + name + ". See log for details.");
|
||||
throw new ShaderLoadingException("Could not compile " + name + ". See log for details.");
|
||||
}
|
||||
|
||||
setHandle(handle);
|
||||
|
@ -40,4 +38,5 @@ public class GlShader extends GlObject {
|
|||
protected void deleteInternal(int handle) {
|
||||
GL20.glDeleteShader(handle);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,4 +16,8 @@ public enum ShaderType {
|
|||
this.define = define;
|
||||
this.glEnum = glEnum;
|
||||
}
|
||||
|
||||
public String getDefineStatement() {
|
||||
return "#define " + define + "\n";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import java.nio.ByteBuffer;
|
|||
import java.util.Arrays;
|
||||
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL20C;
|
||||
import org.lwjgl.opengl.GLCapabilities;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
@ -23,11 +24,11 @@ public class GlCompat {
|
|||
public final BufferStorage bufferStorage;
|
||||
public final boolean amd;
|
||||
|
||||
public GlCompat(GLCapabilities caps) {
|
||||
public GlCompat() {
|
||||
GLCapabilities caps = GL.createCapabilities();
|
||||
instancedArrays = getLatest(InstancedArrays.class, caps);
|
||||
bufferStorage = getLatest(BufferStorage.class, caps);
|
||||
|
||||
|
||||
if (Util.getPlatform() == Util.OS.WINDOWS) {
|
||||
String vendor = GL20C.glGetString(GL20C.GL_VENDOR);
|
||||
// vendor string I got was "ATI Technologies Inc."
|
||||
|
|
|
@ -39,8 +39,7 @@ public class InstanceWorld {
|
|||
this.taskEngine = new ParallelTaskEngine("Flywheel " + world.dimension().location());
|
||||
this.taskEngine.startWorkers();
|
||||
|
||||
FlwEngine engine = Backend.getInstance()
|
||||
.getEngine();
|
||||
FlwEngine engine = Backend.getEngine();
|
||||
|
||||
switch (engine) {
|
||||
case INSTANCING -> {
|
||||
|
|
|
@ -151,11 +151,17 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
|
|||
|
||||
final StructWriter<D> writer = instancedType.getWriter(mapped);
|
||||
|
||||
boolean sequential = true;
|
||||
for (int i = 0; i < size; i++) {
|
||||
final D element = data.get(i);
|
||||
if (element.checkDirtyAndClear()) {
|
||||
if (!sequential) {
|
||||
writer.seek(i);
|
||||
}
|
||||
writer.write(element);
|
||||
sequential = true;
|
||||
} else {
|
||||
sequential = false;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
@ -192,7 +198,7 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
|
|||
vao.bindAttributes(attributeBaseIndex, instanceFormat);
|
||||
|
||||
for (int i = 0; i < instanceFormat.getAttributeCount(); i++) {
|
||||
Backend.getInstance().compat.instancedArrays.vertexAttribDivisor(attributeBaseIndex + i, 1);
|
||||
Backend.compat.instancedArrays.vertexAttribDivisor(attributeBaseIndex + i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
package com.jozufozu.flywheel.backend.instancing.instancing;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.jozufozu.flywheel.api.InstanceData;
|
||||
import com.jozufozu.flywheel.api.Instancer;
|
||||
import com.jozufozu.flywheel.api.Material;
|
||||
import com.jozufozu.flywheel.api.struct.Instanced;
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.RenderWork;
|
||||
import com.jozufozu.flywheel.backend.model.ImmediateAllocator;
|
||||
import com.jozufozu.flywheel.backend.model.ModelAllocator;
|
||||
import com.jozufozu.flywheel.backend.model.ModelPool;
|
||||
import com.jozufozu.flywheel.core.Formats;
|
||||
import com.jozufozu.flywheel.core.model.Model;
|
||||
|
||||
/**
|
||||
|
@ -23,24 +20,14 @@ import com.jozufozu.flywheel.core.model.Model;
|
|||
*/
|
||||
public class InstancedMaterial<D extends InstanceData> implements Material<D> {
|
||||
|
||||
final ModelAllocator allocator;
|
||||
protected final Cache<Object, GPUInstancer<D>> models;
|
||||
protected final ModelAllocator allocator;
|
||||
protected final Map<Object, GPUInstancer<D>> models = new HashMap<>();
|
||||
protected final Instanced<D> type;
|
||||
protected final List<GPUInstancer<D>> uninitialized = new ArrayList<>();
|
||||
|
||||
public InstancedMaterial(Instanced<D> type) {
|
||||
public InstancedMaterial(Instanced<D> type, ModelAllocator allocator) {
|
||||
this.type = type;
|
||||
|
||||
if (Backend.getInstance().compat.onAMDWindows()) {
|
||||
allocator = ImmediateAllocator.INSTANCE;
|
||||
} else {
|
||||
allocator = new ModelPool(Formats.POS_TEX_NORMAL, 64);
|
||||
}
|
||||
this.models = CacheBuilder.newBuilder()
|
||||
.removalListener(notification -> {
|
||||
GPUInstancer<?> instancer = (GPUInstancer<?>) notification.getValue();
|
||||
RenderWork.enqueue(instancer::delete);
|
||||
})
|
||||
.build();
|
||||
this.allocator = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,32 +39,33 @@ public class InstancedMaterial<D extends InstanceData> implements Material<D> {
|
|||
*/
|
||||
@Override
|
||||
public Instancer<D> model(Object key, Supplier<Model> modelSupplier) {
|
||||
try {
|
||||
return models.get(key, () -> new GPUInstancer<>(type, modelSupplier.get(), allocator));
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("error creating instancer", e);
|
||||
}
|
||||
return models.computeIfAbsent(key, $ -> {
|
||||
GPUInstancer<D> instancer = new GPUInstancer<>(type, modelSupplier.get(), allocator);
|
||||
uninitialized.add(instancer);
|
||||
return instancer;
|
||||
});
|
||||
}
|
||||
|
||||
public boolean nothingToRender() {
|
||||
return models.size() > 0 && models.asMap()
|
||||
.values()
|
||||
return models.size() > 0 && models.values()
|
||||
.stream()
|
||||
.allMatch(GPUInstancer::isEmpty);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
models.invalidateAll();
|
||||
if (allocator instanceof ModelPool pool) pool.delete();
|
||||
models.values().forEach(GPUInstancer::delete);
|
||||
models.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all instance data without freeing resources.
|
||||
*/
|
||||
public void clear() {
|
||||
models.asMap()
|
||||
.values()
|
||||
models.values()
|
||||
.forEach(GPUInstancer::clear);
|
||||
}
|
||||
|
||||
public Collection<GPUInstancer<D>> getAllInstancers() {
|
||||
return models.values();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.jozufozu.flywheel.backend.instancing.instancing;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -8,7 +7,13 @@ import com.jozufozu.flywheel.api.InstanceData;
|
|||
import com.jozufozu.flywheel.api.MaterialGroup;
|
||||
import com.jozufozu.flywheel.api.struct.Instanced;
|
||||
import com.jozufozu.flywheel.api.struct.StructType;
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.RenderLayer;
|
||||
import com.jozufozu.flywheel.backend.model.FallbackAllocator;
|
||||
import com.jozufozu.flywheel.backend.model.ModelAllocator;
|
||||
import com.jozufozu.flywheel.backend.model.ModelPool;
|
||||
import com.jozufozu.flywheel.core.Formats;
|
||||
import com.jozufozu.flywheel.core.compile.ProgramContext;
|
||||
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
||||
import com.jozufozu.flywheel.util.Textures;
|
||||
import com.mojang.math.Matrix4f;
|
||||
|
@ -27,49 +32,55 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
|
|||
protected final RenderType type;
|
||||
|
||||
private final Map<Instanced<? extends InstanceData>, InstancedMaterial<?>> materials = new HashMap<>();
|
||||
private final ModelAllocator allocator;
|
||||
|
||||
public InstancedMaterialGroup(InstancingEngine<P> owner, RenderType type) {
|
||||
this.owner = owner;
|
||||
this.type = type;
|
||||
if (Backend.compat.onAMDWindows()) {
|
||||
this.allocator = FallbackAllocator.INSTANCE;
|
||||
} else {
|
||||
this.allocator = new ModelPool(Formats.POS_TEX_NORMAL, 2048);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <D extends InstanceData> InstancedMaterial<D> material(StructType<D> type) {
|
||||
if (type instanceof Instanced<D> instanced) {
|
||||
return (InstancedMaterial<D>) materials.computeIfAbsent(instanced, InstancedMaterial::new);
|
||||
return (InstancedMaterial<D>) materials.computeIfAbsent(instanced, t -> new InstancedMaterial<>(t, allocator));
|
||||
} else {
|
||||
throw new ClassCastException("Cannot use type '" + type + "' with GPU instancing.");
|
||||
}
|
||||
}
|
||||
|
||||
public void render(Matrix4f viewProjection, double camX, double camY, double camZ) {
|
||||
public void render(Matrix4f viewProjection, double camX, double camY, double camZ, RenderLayer layer) {
|
||||
type.setupRenderState();
|
||||
Textures.bindActiveTextures();
|
||||
renderAll(viewProjection, camX, camY, camZ);
|
||||
renderAll(viewProjection, camX, camY, camZ, layer);
|
||||
type.clearRenderState();
|
||||
}
|
||||
|
||||
protected void renderAll(Matrix4f viewProjection, double camX, double camY, double camZ) {
|
||||
for (Map.Entry<Instanced<? extends InstanceData>, InstancedMaterial<?>> entry : materials.entrySet()) {
|
||||
InstancedMaterial<?> material = entry.getValue();
|
||||
if (material.nothingToRender()) continue;
|
||||
|
||||
Collection<? extends GPUInstancer<?>> instancers = material.models.asMap()
|
||||
.values();
|
||||
|
||||
protected void renderAll(Matrix4f viewProjection, double camX, double camY, double camZ, RenderLayer layer) {
|
||||
// initialize all uninitialized instancers...
|
||||
for (GPUInstancer<?> gpuInstancer : instancers) {
|
||||
gpuInstancer.init();
|
||||
for (InstancedMaterial<?> material : materials.values()) {
|
||||
for (GPUInstancer<?> instancer : material.uninitialized) {
|
||||
instancer.init();
|
||||
}
|
||||
material.uninitialized.clear();
|
||||
}
|
||||
|
||||
if (material.allocator instanceof ModelPool pool) {
|
||||
if (allocator instanceof ModelPool pool) {
|
||||
// ...and then flush the model arena in case anything was marked for upload
|
||||
pool.flush();
|
||||
}
|
||||
|
||||
P program = owner.getProgram(entry.getKey()
|
||||
.getProgramSpec()).get();
|
||||
for (Map.Entry<Instanced<? extends InstanceData>, InstancedMaterial<?>> entry : materials.entrySet()) {
|
||||
InstancedMaterial<?> material = entry.getValue();
|
||||
if (material.nothingToRender()) continue;
|
||||
|
||||
P program = owner.context.getProgram(ProgramContext.create(entry.getKey()
|
||||
.getProgramSpec(), Formats.POS_TEX_NORMAL, layer));
|
||||
|
||||
program.bind();
|
||||
program.uploadViewProjection(viewProjection);
|
||||
|
@ -77,7 +88,7 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
|
|||
|
||||
setup(program);
|
||||
|
||||
for (GPUInstancer<?> instancer : instancers) {
|
||||
for (GPUInstancer<?> instancer : material.getAllInstancers()) {
|
||||
instancer.render();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package com.jozufozu.flywheel.backend.instancing.instancing;
|
|||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -12,7 +11,7 @@ import com.jozufozu.flywheel.api.MaterialGroup;
|
|||
import com.jozufozu.flywheel.backend.RenderLayer;
|
||||
import com.jozufozu.flywheel.backend.instancing.Engine;
|
||||
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
|
||||
import com.jozufozu.flywheel.core.WorldContext;
|
||||
import com.jozufozu.flywheel.core.compile.ProgramCompiler;
|
||||
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
||||
import com.jozufozu.flywheel.event.RenderLayerEvent;
|
||||
import com.jozufozu.flywheel.util.WeakHashSet;
|
||||
|
@ -22,7 +21,6 @@ import net.minecraft.client.Camera;
|
|||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public class InstancingEngine<P extends WorldProgram> implements Engine {
|
||||
|
@ -31,7 +29,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
|
|||
|
||||
protected BlockPos originCoordinate = BlockPos.ZERO;
|
||||
|
||||
protected final WorldContext<P> context;
|
||||
protected final ProgramCompiler<P> context;
|
||||
protected final GroupFactory<P> groupFactory;
|
||||
protected final boolean ignoreOriginCoordinate;
|
||||
|
||||
|
@ -39,15 +37,11 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
|
|||
|
||||
private final WeakHashSet<OriginShiftListener> listeners;
|
||||
|
||||
public InstancingEngine(WorldContext<P> context, TaskEngine taskEngine) {
|
||||
this(context, InstancedMaterialGroup::new, false);
|
||||
}
|
||||
|
||||
public static <P extends WorldProgram> Builder<P> builder(WorldContext<P> context) {
|
||||
public static <P extends WorldProgram> Builder<P> builder(ProgramCompiler<P> context) {
|
||||
return new Builder<>(context);
|
||||
}
|
||||
|
||||
public InstancingEngine(WorldContext<P> context, GroupFactory<P> groupFactory, boolean ignoreOriginCoordinate) {
|
||||
public InstancingEngine(ProgramCompiler<P> context, GroupFactory<P> groupFactory, boolean ignoreOriginCoordinate) {
|
||||
this.context = context;
|
||||
this.ignoreOriginCoordinate = ignoreOriginCoordinate;
|
||||
|
||||
|
@ -95,7 +89,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
|
|||
viewProjection = event.viewProjection;
|
||||
}
|
||||
|
||||
getGroupsToRender(event.getLayer()).forEach(group -> group.render(viewProjection, camX, camY, camZ));
|
||||
getGroupsToRender(event.getLayer()).forEach(group -> group.render(viewProjection, camX, camY, camZ, event.getLayer()));
|
||||
}
|
||||
|
||||
private Stream<InstancedMaterialGroup<P>> getGroupsToRender(@Nullable RenderLayer layer) {
|
||||
|
@ -120,10 +114,6 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
|
|||
}
|
||||
}
|
||||
|
||||
public Supplier<P> getProgram(ResourceLocation name) {
|
||||
return context.getProgramSupplier(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3i getOriginCoordinate() {
|
||||
return originCoordinate;
|
||||
|
@ -171,11 +161,11 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
|
|||
}
|
||||
|
||||
public static class Builder<P extends WorldProgram> {
|
||||
protected final WorldContext<P> context;
|
||||
protected final ProgramCompiler<P> context;
|
||||
protected GroupFactory<P> groupFactory = InstancedMaterialGroup::new;
|
||||
protected boolean ignoreOriginCoordinate;
|
||||
|
||||
public Builder(WorldContext<P> context) {
|
||||
public Builder(ProgramCompiler<P> context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,8 @@ package com.jozufozu.flywheel.backend.model;
|
|||
|
||||
import com.jozufozu.flywheel.core.model.Model;
|
||||
|
||||
public class ImmediateAllocator implements ModelAllocator {
|
||||
|
||||
public static final ImmediateAllocator INSTANCE = new ImmediateAllocator();
|
||||
public enum FallbackAllocator implements ModelAllocator {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public BufferedModel alloc(Model model, Callback allocationCallback) {
|
|
@ -1,87 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.jozufozu.flywheel.backend.source.ShaderLoadingException;
|
||||
import com.jozufozu.flywheel.backend.source.SourceFile;
|
||||
import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
|
||||
import com.jozufozu.flywheel.backend.source.parse.Variable;
|
||||
import com.jozufozu.flywheel.backend.source.span.Span;
|
||||
|
||||
public class InstancingProgramMetaData {
|
||||
|
||||
public final SourceFile file;
|
||||
public final ShaderFunction vertexMain;
|
||||
public final ShaderFunction fragmentMain;
|
||||
public final Span interpolantName;
|
||||
public final Span vertexName;
|
||||
public final Span instanceName;
|
||||
public final ShaderStruct interpolant;
|
||||
public final ShaderStruct vertex;
|
||||
public final ShaderStruct instance;
|
||||
|
||||
public InstancingProgramMetaData(SourceFile file) {
|
||||
this.file = file;
|
||||
|
||||
Optional<ShaderFunction> vertexFunc = file.findFunction("vertex");
|
||||
Optional<ShaderFunction> fragmentFunc = file.findFunction("fragment");
|
||||
|
||||
if (fragmentFunc.isEmpty()) {
|
||||
ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
|
||||
}
|
||||
if (vertexFunc.isEmpty()) {
|
||||
ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
|
||||
}
|
||||
|
||||
if (fragmentFunc.isEmpty() || vertexFunc.isEmpty()) {
|
||||
throw new ShaderLoadingException();
|
||||
}
|
||||
|
||||
fragmentMain = fragmentFunc.get();
|
||||
vertexMain = vertexFunc.get();
|
||||
ImmutableList<Variable> parameters = fragmentMain.getParameters();
|
||||
ImmutableList<Variable> vertexParams = vertexMain.getParameters();
|
||||
|
||||
if (parameters.size() != 1) {
|
||||
ErrorReporter.generateSpanError(fragmentMain.getArgs(), "instancing requires fragment function to have 1 argument");
|
||||
}
|
||||
|
||||
if (vertexParams.size() != 2) {
|
||||
ErrorReporter.generateSpanError(vertexMain.getArgs(), "instancing requires vertex function to have 2 arguments");
|
||||
throw new ShaderLoadingException();
|
||||
}
|
||||
|
||||
interpolantName = vertexMain.getType();
|
||||
vertexName = vertexParams.get(0)
|
||||
.typeName();
|
||||
instanceName = vertexParams.get(1)
|
||||
.typeName();
|
||||
|
||||
Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName);
|
||||
Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName);
|
||||
Optional<ShaderStruct> maybeInstance = file.findStruct(instanceName);
|
||||
|
||||
if (maybeVertex.isEmpty()) {
|
||||
ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined");
|
||||
}
|
||||
|
||||
if (maybeInterpolant.isEmpty()) {
|
||||
ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
|
||||
}
|
||||
|
||||
if (maybeInstance.isEmpty()) {
|
||||
ErrorReporter.generateMissingStruct(file, instanceName, "struct not defined");
|
||||
}
|
||||
|
||||
if (maybeVertex.isEmpty() || maybeInterpolant.isEmpty() || maybeInstance.isEmpty()) {
|
||||
throw new ShaderLoadingException();
|
||||
}
|
||||
|
||||
interpolant = maybeInterpolant.get();
|
||||
vertex = maybeVertex.get();
|
||||
instance = maybeInstance.get();
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
|
||||
import com.jozufozu.flywheel.backend.source.SourceFile;
|
||||
|
||||
public class InstancingTemplate extends Template<InstancingProgramMetaData> {
|
||||
|
||||
public static final InstancingTemplate INSTANCE = new InstancingTemplate();
|
||||
|
||||
public InstancingTemplate() {
|
||||
super(InstancingProgramMetaData::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file) {
|
||||
if (type == ShaderType.VERTEX) {
|
||||
vertexFooter(builder, file);
|
||||
} else if (type == ShaderType.FRAGMENT) {
|
||||
fragmentFooter(builder, file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ShaderInput> getShaderInputs(SourceFile file) {
|
||||
InstancingProgramMetaData data = getMetadata(file);
|
||||
|
||||
List<ShaderInput> inputs = new ArrayList<>(ShaderInput.fromStruct(data.vertex, "a_v_"));
|
||||
inputs.addAll(ShaderInput.fromStruct(data.instance, "a_i_"));
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
public void vertexFooter(StringBuilder template, SourceFile file) {
|
||||
InstancingProgramMetaData data = getMetadata(file);
|
||||
|
||||
Template.prefixFields(template, data.vertex, "in", "a_v_");
|
||||
Template.prefixFields(template, data.instance, "in", "a_i_");
|
||||
Template.prefixFields(template, data.interpolant, "out", "v2f_");
|
||||
|
||||
template.append("void main() {\n");
|
||||
template.append(data.vertexName)
|
||||
.append(" v;\n");
|
||||
Template.assignFields(template, data.vertex, "v.", "a_v_");
|
||||
|
||||
template.append(data.instanceName)
|
||||
.append(" i;\n");
|
||||
Template.assignFields(template, data.instance, "i.", "a_i_");
|
||||
|
||||
template.append(data.interpolantName)
|
||||
.append(" o = ")
|
||||
.append(data.vertexMain.call("v", "i"))
|
||||
.append(";\n");
|
||||
|
||||
Template.assignFields(template, data.interpolant, "v2f_", "o.");
|
||||
|
||||
template.append('}');
|
||||
}
|
||||
|
||||
public void fragmentFooter(StringBuilder template, SourceFile file) {
|
||||
InstancingProgramMetaData data = getMetadata(file);
|
||||
|
||||
Template.prefixFields(template, data.interpolant, "in", "v2f_");
|
||||
|
||||
template.append("void main() {\n");
|
||||
template.append(data.interpolantName)
|
||||
.append(" o;\n");
|
||||
Template.assignFields(template, data.interpolant, "o.", "v2f_");
|
||||
|
||||
template.append(data.fragmentMain.call("o"))
|
||||
.append(";\n");
|
||||
|
||||
template.append('}');
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.jozufozu.flywheel.backend.source.SourceFile;
|
||||
import com.jozufozu.flywheel.backend.source.error.ErrorReporter;
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
|
||||
import com.jozufozu.flywheel.backend.source.parse.Variable;
|
||||
import com.jozufozu.flywheel.backend.source.span.Span;
|
||||
|
||||
public class OneShotProgramMetaData {
|
||||
|
||||
public final SourceFile file;
|
||||
public final ShaderFunction vertexMain;
|
||||
public final Span interpolantName;
|
||||
public final Span vertexName;
|
||||
public final ShaderStruct interpolant;
|
||||
public final ShaderStruct vertex;
|
||||
public final ShaderFunction fragmentMain;
|
||||
|
||||
public OneShotProgramMetaData(SourceFile file) {
|
||||
this.file = file;
|
||||
|
||||
Optional<ShaderFunction> maybeVertexMain = file.findFunction("vertex");
|
||||
Optional<ShaderFunction> maybeFragmentMain = file.findFunction("fragment");
|
||||
|
||||
if (maybeVertexMain.isEmpty()) {
|
||||
ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
|
||||
}
|
||||
|
||||
if (maybeFragmentMain.isEmpty()) {
|
||||
ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
|
||||
}
|
||||
|
||||
if (maybeVertexMain.isEmpty() || maybeFragmentMain.isEmpty()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
vertexMain = maybeVertexMain.get();
|
||||
fragmentMain = maybeFragmentMain.get();
|
||||
ImmutableList<Variable> fragmentParameters = fragmentMain.getParameters();
|
||||
ImmutableList<Variable> vertexParameters = vertexMain.getParameters();
|
||||
|
||||
if (vertexParameters.size() != 1) {
|
||||
ErrorReporter.generateSpanError(vertexMain.getArgs(), "a basic model requires vertex function to have one argument");
|
||||
}
|
||||
|
||||
if (fragmentParameters.size() != 1) {
|
||||
ErrorReporter.generateSpanError(fragmentMain.getArgs(), "fragment function must have exactly one argument");
|
||||
}
|
||||
|
||||
if (vertexParameters.size() != 1 || fragmentParameters.size() != 1) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
interpolantName = vertexMain.getType();
|
||||
vertexName = vertexParameters.get(0)
|
||||
.typeName();
|
||||
|
||||
Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName);
|
||||
Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName);
|
||||
|
||||
if (maybeVertex.isEmpty())
|
||||
ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined");
|
||||
|
||||
if (maybeInterpolant.isEmpty())
|
||||
ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
|
||||
|
||||
if (maybeVertex.isEmpty() || maybeInterpolant.isEmpty()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
interpolant = maybeInterpolant.get();
|
||||
vertex = maybeVertex.get();
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
|
||||
import com.jozufozu.flywheel.backend.source.SourceFile;
|
||||
|
||||
public class OneShotTemplate extends Template<OneShotProgramMetaData> {
|
||||
|
||||
public static final OneShotTemplate INSTANCE = new OneShotTemplate();
|
||||
|
||||
public OneShotTemplate() {
|
||||
super(OneShotProgramMetaData::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file) {
|
||||
if (type == ShaderType.VERTEX) {
|
||||
vertexFooter(builder, file);
|
||||
} else if (type == ShaderType.FRAGMENT) {
|
||||
fragmentFooter(builder, file);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ShaderInput> getShaderInputs(SourceFile file) {
|
||||
OneShotProgramMetaData data = getMetadata(file);
|
||||
|
||||
return ShaderInput.fromStruct(data.vertex, "a_v_");
|
||||
}
|
||||
|
||||
public void vertexFooter(StringBuilder template, SourceFile file) {
|
||||
OneShotProgramMetaData data = getMetadata(file);
|
||||
|
||||
Template.prefixFields(template, data.vertex, "in", "a_v_");
|
||||
Template.prefixFields(template, data.interpolant, "out", "v2f_");
|
||||
|
||||
template.append("void main() {\n");
|
||||
template.append(data.vertexName)
|
||||
.append(" v;\n");
|
||||
Template.assignFields(template, data.vertex, "v.", "a_v_");
|
||||
|
||||
template.append(data.interpolantName)
|
||||
.append(" o = ")
|
||||
.append(data.vertexMain.call("v"))
|
||||
.append(";\n");
|
||||
|
||||
Template.assignFields(template, data.interpolant, "v2f_", "o.");
|
||||
|
||||
template.append('}');
|
||||
}
|
||||
|
||||
public void fragmentFooter(StringBuilder template, SourceFile file) {
|
||||
OneShotProgramMetaData data = getMetadata(file);
|
||||
|
||||
Template.prefixFields(template, data.interpolant, "in", "v2f_");
|
||||
|
||||
template.append("void main() {\n");
|
||||
template.append(data.interpolant.name)
|
||||
.append(" o;\n");
|
||||
Template.assignFields(template, data.interpolant, "o.", "v2f_");
|
||||
|
||||
template.append(data.fragmentMain.call("o"))
|
||||
.append(";\n");
|
||||
|
||||
template.append('}');
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
|
||||
public class Shader {
|
||||
|
||||
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
|
||||
import com.jozufozu.flywheel.backend.source.parse.StructField;
|
||||
|
||||
/**
|
||||
* A single input to a shader.
|
||||
*/
|
||||
public class ShaderInput {
|
||||
public final CharSequence name;
|
||||
public final int attribCount;
|
||||
|
||||
public ShaderInput(CharSequence name, int attribCount) {
|
||||
this.name = name;
|
||||
this.attribCount = attribCount;
|
||||
}
|
||||
|
||||
public ShaderInput withPrefix(CharSequence prefix) {
|
||||
return new ShaderInput(prefix.toString() + name, attribCount);
|
||||
}
|
||||
|
||||
public static Collection<ShaderInput> fromStruct(ShaderStruct struct, String prefix) {
|
||||
return struct.getFields()
|
||||
.stream()
|
||||
.map(ShaderInput::from)
|
||||
.map(a -> a.withPrefix(prefix))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static ShaderInput from(StructField field) {
|
||||
int attributeCount = TypeHelper.getAttributeCount(field.type);
|
||||
|
||||
return new ShaderInput(field.name, attributeCount);
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
|
||||
import com.jozufozu.flywheel.core.shader.ContextAwareProgram;
|
||||
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
||||
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
|
||||
|
||||
/**
|
||||
* The main interface for compiling usable shaders from program specs.
|
||||
* @param <P> the type of the program that this pipeline compiles.
|
||||
*/
|
||||
public interface ShaderPipeline<P extends WorldProgram> {
|
||||
|
||||
ContextAwareProgram<P> compile(ProgramSpec spec);
|
||||
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
|
||||
import com.jozufozu.flywheel.backend.source.SourceFile;
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
|
||||
import com.jozufozu.flywheel.backend.source.parse.StructField;
|
||||
|
||||
/**
|
||||
* A class that generates glsl glue code given a SourceFile.
|
||||
*
|
||||
* <p>
|
||||
* Shader files are written somewhat abstractly. Subclasses of Template handle those abstractions, using SourceFile
|
||||
* metadata to generate shader code that OpenGL can use to call into our shader programs.
|
||||
* </p>
|
||||
* @param <D> Holds metadata, generates errors.
|
||||
*/
|
||||
public abstract class Template<D> {
|
||||
|
||||
private final Map<SourceFile, D> metadata = new HashMap<>();
|
||||
|
||||
private final Function<SourceFile, D> reader;
|
||||
|
||||
protected Template(Function<SourceFile, D> reader) {
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the necessary glue code here.
|
||||
*
|
||||
* <p>
|
||||
* See {@link InstancingTemplate} and {@link OneShotTemplate} for examples.
|
||||
* </p>
|
||||
* @param builder The builder to generate the source into.
|
||||
* @param type The shader stage glue code is needed for.
|
||||
* @param file The SourceFile with user written code.
|
||||
*/
|
||||
public abstract void generateTemplateSource(StringBuilder builder, ShaderType type, SourceFile file);
|
||||
|
||||
public abstract Collection<ShaderInput> getShaderInputs(SourceFile file);
|
||||
|
||||
public D getMetadata(SourceFile file) {
|
||||
// lazily read files, cache results
|
||||
return metadata.computeIfAbsent(file, reader);
|
||||
}
|
||||
|
||||
public GLSLVersion getVersion() {
|
||||
return GLSLVersion.V150;
|
||||
}
|
||||
|
||||
public static void prefixFields(StringBuilder builder, ShaderStruct struct, String qualifier, String prefix) {
|
||||
ImmutableList<StructField> fields = struct.getFields();
|
||||
|
||||
for (StructField field : fields) {
|
||||
builder.append(qualifier)
|
||||
.append(' ')
|
||||
.append(field.type)
|
||||
.append(' ')
|
||||
.append(prefix)
|
||||
.append(field.name)
|
||||
.append(";\n");
|
||||
}
|
||||
}
|
||||
|
||||
public static void assignFields(StringBuilder builder, ShaderStruct struct, String prefix1, String prefix2) {
|
||||
ImmutableList<StructField> fields = struct.getFields();
|
||||
|
||||
for (StructField field : fields) {
|
||||
builder.append(prefix1)
|
||||
.append(field.name)
|
||||
.append(" = ")
|
||||
.append(prefix2)
|
||||
.append(field.name)
|
||||
.append(";\n");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
|
||||
import com.jozufozu.flywheel.backend.source.FileResolution;
|
||||
import com.jozufozu.flywheel.backend.source.SourceFile;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class WorldShader {
|
||||
|
||||
public final ResourceLocation name;
|
||||
public final Template<?> template;
|
||||
public final CharSequence header;
|
||||
|
||||
public SourceFile mainFile;
|
||||
|
||||
private CharSequence source;
|
||||
private StringBuilder defines;
|
||||
|
||||
public WorldShader(ResourceLocation name, Template<?> template, FileResolution header) {
|
||||
this.name = name;
|
||||
this.template = template;
|
||||
this.header = Optional.ofNullable(header.getFile())
|
||||
.map(SourceFile::generateFinalSource)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public WorldShader setDefines(List<String> defs) {
|
||||
defines = new StringBuilder();
|
||||
|
||||
for (String def : defs) {
|
||||
defines.append("#define ")
|
||||
.append(def)
|
||||
.append('\n');
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public WorldShader setMainSource(SourceFile file) {
|
||||
if (mainFile == file) return this;
|
||||
|
||||
mainFile = file;
|
||||
source = file.generateFinalSource();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public GlShader compile(ShaderType type) {
|
||||
|
||||
StringBuilder finalSource = new StringBuilder();
|
||||
|
||||
finalSource.append("#version ")
|
||||
.append(template.getVersion())
|
||||
.append('\n')
|
||||
.append("#extension GL_ARB_conservative_depth : enable\n")
|
||||
.append("#define ")
|
||||
.append(type.define) // special case shader type declaration
|
||||
.append('\n')
|
||||
.append(defines != null ? defines : "")
|
||||
.append(header)
|
||||
.append('\n')
|
||||
.append(source)
|
||||
.append('\n');
|
||||
|
||||
template.generateTemplateSource(finalSource, type, mainFile);
|
||||
|
||||
return new GlShader(name, type, finalSource);
|
||||
}
|
||||
|
||||
public ProtoProgram createProgram() {
|
||||
return new ProtoProgram(this);
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
|
||||
import com.jozufozu.flywheel.backend.source.FileResolution;
|
||||
import com.jozufozu.flywheel.backend.source.SourceFile;
|
||||
import com.jozufozu.flywheel.core.shader.ContextAwareProgram;
|
||||
import com.jozufozu.flywheel.core.shader.ExtensibleGlProgram;
|
||||
import com.jozufozu.flywheel.core.shader.GameStateProgram;
|
||||
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
||||
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
|
||||
import com.jozufozu.flywheel.core.shader.spec.ProgramState;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class WorldShaderPipeline<P extends WorldProgram> implements ShaderPipeline<P> {
|
||||
|
||||
private final ExtensibleGlProgram.Factory<P> factory;
|
||||
|
||||
private final Template<?> template;
|
||||
private final FileResolution header;
|
||||
|
||||
public WorldShaderPipeline(ExtensibleGlProgram.Factory<P> factory, Template<?> template, FileResolution header) {
|
||||
this.factory = factory;
|
||||
this.template = template;
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
public ContextAwareProgram<P> compile(ProgramSpec spec) {
|
||||
|
||||
SourceFile file = spec.getSource().getFile();
|
||||
|
||||
return compile(spec.name, file, spec.getStates());
|
||||
}
|
||||
|
||||
public ContextAwareProgram<P> compile(ResourceLocation name, SourceFile file, List<ProgramState> variants) {
|
||||
WorldShader shader = new WorldShader(name, template, header)
|
||||
.setMainSource(file);
|
||||
|
||||
GameStateProgram.Builder<P> builder = GameStateProgram.builder(compile(shader, null));
|
||||
|
||||
for (ProgramState variant : variants) {
|
||||
builder.withVariant(variant.context(), compile(shader, variant));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private P compile(WorldShader shader, @Nullable ProgramState variant) {
|
||||
|
||||
if (variant != null) {
|
||||
shader.setDefines(variant.defines());
|
||||
}
|
||||
|
||||
ProtoProgram program = shader.createProgram()
|
||||
.compilePart(ShaderType.VERTEX)
|
||||
.compilePart(ShaderType.FRAGMENT)
|
||||
.link()
|
||||
.deleteLinkedShaders();
|
||||
|
||||
return factory.create(shader.name, program.program);
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.source;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/**
|
||||
* Manages deferred file resolution.
|
||||
*
|
||||
* <p>
|
||||
* Interns all referenced files in all sources, duplicating the final lookups and allowing for more dev-friendly
|
||||
* error reporting.
|
||||
* </p><p>
|
||||
* See {@link FileResolution} for more information.
|
||||
* </p>
|
||||
*/
|
||||
public class Resolver {
|
||||
|
||||
public static final Resolver INSTANCE = new Resolver();
|
||||
|
||||
private final Map<ResourceLocation, FileResolution> resolutions = new HashMap<>();
|
||||
|
||||
public FileResolution findShader(ResourceLocation fileLoc) {
|
||||
return resolutions.computeIfAbsent(fileLoc, FileResolution::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try and resolve all referenced source files, printing errors if any aren't found.
|
||||
*/
|
||||
public void resolve(SourceFinder sources) {
|
||||
for (FileResolution resolution : resolutions.values()) {
|
||||
resolution.resolve(sources);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all FileResolutions.
|
||||
*
|
||||
* <p>
|
||||
* Called on resource reload.
|
||||
* </p>
|
||||
*/
|
||||
public void invalidate() {
|
||||
resolutions.values().forEach(FileResolution::invalidate);
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.source;
|
||||
|
||||
public class ShaderLoadingException extends RuntimeException {
|
||||
public ShaderLoadingException() {
|
||||
}
|
||||
|
||||
public ShaderLoadingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ShaderLoadingException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ShaderLoadingException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ShaderLoadingException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.source.error;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.backend.source.SourceFile;
|
||||
import com.jozufozu.flywheel.backend.source.SourceLines;
|
||||
import com.jozufozu.flywheel.backend.source.error.lines.ErrorLine;
|
||||
import com.jozufozu.flywheel.backend.source.error.lines.FileLine;
|
||||
import com.jozufozu.flywheel.backend.source.error.lines.HeaderLine;
|
||||
import com.jozufozu.flywheel.backend.source.error.lines.SourceLine;
|
||||
import com.jozufozu.flywheel.backend.source.error.lines.SpanHighlightLine;
|
||||
import com.jozufozu.flywheel.backend.source.span.Span;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
|
||||
public class ErrorBuilder {
|
||||
|
||||
private final List<ErrorLine> lines = new ArrayList<>();
|
||||
|
||||
private final Level level;
|
||||
|
||||
public ErrorBuilder(Level level, CharSequence msg) {
|
||||
this.level = level;
|
||||
|
||||
lines.add(new HeaderLine(level.toString(), msg));
|
||||
}
|
||||
|
||||
public static ErrorBuilder error(CharSequence msg) {
|
||||
return new ErrorBuilder(Level.ERROR, msg);
|
||||
}
|
||||
|
||||
public static ErrorBuilder warn(CharSequence msg) {
|
||||
return new ErrorBuilder(Level.WARN, msg);
|
||||
}
|
||||
|
||||
public ErrorBuilder pointAtFile(SourceFile file) {
|
||||
lines.add(new FileLine(file.name.toString()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ErrorBuilder hintIncludeFor(@Nullable Span span, CharSequence msg) {
|
||||
if (span == null) return this;
|
||||
|
||||
String builder = "add " + span.getSourceFile()
|
||||
.importStatement() + ' ' + msg;
|
||||
|
||||
lines.add(new HeaderLine("hint", builder));
|
||||
|
||||
return this.pointAtFile(span.getSourceFile())
|
||||
.pointAt(span, 1);
|
||||
}
|
||||
|
||||
public ErrorBuilder pointAt(Span span, int ctxLines) {
|
||||
|
||||
if (span.lines() == 1) {
|
||||
SourceLines lines = span.getSourceFile().lines;
|
||||
|
||||
int spanLine = span.firstLine();
|
||||
|
||||
int firstLine = Math.max(0, spanLine - ctxLines);
|
||||
int lastLine = Math.min(lines.getLineCount(), spanLine + ctxLines);
|
||||
|
||||
int firstCol = span.getStart()
|
||||
.col();
|
||||
int lastCol = span.getEnd()
|
||||
.col();
|
||||
|
||||
for (int i = firstLine; i <= lastLine; i++) {
|
||||
CharSequence line = lines.getLine(i);
|
||||
|
||||
this.lines.add(SourceLine.numbered(i + 1, line.toString()));
|
||||
|
||||
if (i == spanLine) {
|
||||
this.lines.add(new SpanHighlightLine(firstCol, lastCol));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CharSequence build() {
|
||||
|
||||
int maxLength = -1;
|
||||
for (ErrorLine line : lines) {
|
||||
int length = line.neededMargin();
|
||||
|
||||
if (length > maxLength) maxLength = length;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (ErrorLine line : lines) {
|
||||
int length = line.neededMargin();
|
||||
|
||||
builder.append(FlwUtil.repeatChar(' ', maxLength - length))
|
||||
.append(line.build())
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package com.jozufozu.flywheel.backend.source.parse;
|
||||
|
||||
import com.jozufozu.flywheel.backend.source.span.Span;
|
||||
|
||||
public class Variable extends AbstractShaderElement {
|
||||
|
||||
private final Span type;
|
||||
private final Span name;
|
||||
|
||||
public Variable(Span self, Span type, Span name) {
|
||||
super(self);
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Span typeName() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Span getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type + " " + name;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,12 @@
|
|||
package com.jozufozu.flywheel.core;
|
||||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.GameStateRegistry;
|
||||
import com.jozufozu.flywheel.backend.pipeline.InstancingTemplate;
|
||||
import com.jozufozu.flywheel.backend.pipeline.ShaderPipeline;
|
||||
import com.jozufozu.flywheel.backend.pipeline.WorldShaderPipeline;
|
||||
import com.jozufozu.flywheel.backend.source.FileResolution;
|
||||
import com.jozufozu.flywheel.backend.source.Resolver;
|
||||
import com.jozufozu.flywheel.core.compile.ProgramCompiler;
|
||||
import com.jozufozu.flywheel.core.crumbling.CrumblingProgram;
|
||||
import com.jozufozu.flywheel.core.shader.NormalDebugStateProvider;
|
||||
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
||||
import com.jozufozu.flywheel.core.shader.gamestate.NormalDebugStateProvider;
|
||||
import com.jozufozu.flywheel.core.source.FileResolution;
|
||||
import com.jozufozu.flywheel.core.source.Resolver;
|
||||
import com.jozufozu.flywheel.event.GatherContextEvent;
|
||||
import com.jozufozu.flywheel.util.ResourceUtil;
|
||||
|
||||
|
@ -21,22 +17,17 @@ import net.minecraftforge.api.distmarker.OnlyIn;
|
|||
@OnlyIn(Dist.CLIENT)
|
||||
public class Contexts {
|
||||
|
||||
public static WorldContext<WorldProgram> WORLD;
|
||||
public static WorldContext<CrumblingProgram> CRUMBLING;
|
||||
public static ProgramCompiler<WorldProgram> WORLD;
|
||||
public static ProgramCompiler<CrumblingProgram> CRUMBLING;
|
||||
|
||||
public static void flwInit(GatherContextEvent event) {
|
||||
Backend backend = event.getBackend();
|
||||
|
||||
GameStateRegistry.register(NormalDebugStateProvider.INSTANCE);
|
||||
|
||||
FileResolution crumblingBuiltins = Resolver.INSTANCE.findShader(ResourceUtil.subPath(Names.CRUMBLING, ".glsl"));
|
||||
FileResolution worldBuiltins = Resolver.INSTANCE.findShader(ResourceUtil.subPath(Names.WORLD, ".glsl"));
|
||||
FileResolution worldBuiltins = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.WORLD, ".glsl"));
|
||||
FileResolution crumblingBuiltins = Resolver.INSTANCE.get(ResourceUtil.subPath(Names.CRUMBLING, ".glsl"));
|
||||
|
||||
ShaderPipeline<CrumblingProgram> crumblingPipeline = new WorldShaderPipeline<>(CrumblingProgram::new, InstancingTemplate.INSTANCE, crumblingBuiltins);
|
||||
ShaderPipeline<WorldProgram> worldPipeline = new WorldShaderPipeline<>(WorldProgram::new, InstancingTemplate.INSTANCE, worldBuiltins);
|
||||
|
||||
CRUMBLING = backend.register(WorldContext.builder(backend, Names.CRUMBLING).build(crumblingPipeline));
|
||||
WORLD = backend.register(WorldContext.builder(backend, Names.WORLD).build(worldPipeline));
|
||||
WORLD = ProgramCompiler.create(Templates.INSTANCING, WorldProgram::new, worldBuiltins);
|
||||
CRUMBLING = ProgramCompiler.create(Templates.INSTANCING, CrumblingProgram::new, crumblingBuiltins);
|
||||
}
|
||||
|
||||
public static class Names {
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package com.jozufozu.flywheel.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.List;
|
||||
|
||||
import com.jozufozu.flywheel.core.shader.GameStateProvider;
|
||||
import com.jozufozu.flywheel.core.shader.ShaderConstants;
|
||||
import com.jozufozu.flywheel.core.shader.StateSnapshot;
|
||||
|
||||
public class GameStateRegistry {
|
||||
|
||||
private static final List<GameStateProvider> PROVIDERS = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Registers a game state provider.
|
||||
* @param provider The provider to register.
|
||||
*/
|
||||
public static void register(GameStateProvider provider) {
|
||||
PROVIDERS.add(provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a snapshot of the current game state, storing it in a bit set.
|
||||
* @return An object that represents the current game state.
|
||||
*/
|
||||
public static StateSnapshot takeSnapshot() {
|
||||
BitSet bitSet = new BitSet(PROVIDERS.size());
|
||||
|
||||
for (int i = 0, listSize = PROVIDERS.size(); i < listSize; i++) {
|
||||
if (PROVIDERS.get(i).isTrue()) {
|
||||
bitSet.set(i);
|
||||
}
|
||||
}
|
||||
return new StateSnapshot(bitSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the given snapshot, gathers shader constants to be injected during shader compilation.
|
||||
* @param snapshot The snapshot to use.
|
||||
* @return A list of shader constants.
|
||||
*/
|
||||
public static ShaderConstants getShaderConstants(StateSnapshot snapshot) {
|
||||
BitSet ctx = snapshot.ctx();
|
||||
ShaderConstants shaderConstants = new ShaderConstants();
|
||||
|
||||
for (int i = 0, listSize = PROVIDERS.size(); i < listSize; i++) {
|
||||
if (ctx.get(i)) {
|
||||
PROVIDERS.get(i).alterConstants(shaderConstants);
|
||||
}
|
||||
}
|
||||
return shaderConstants;
|
||||
}
|
||||
|
||||
public static void _clear() {
|
||||
PROVIDERS.clear();
|
||||
}
|
||||
}
|
|
@ -2,12 +2,10 @@ package com.jozufozu.flywheel.core;
|
|||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.api.struct.StructType;
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.core.materials.model.ModelData;
|
||||
import com.jozufozu.flywheel.core.materials.model.ModelType;
|
||||
import com.jozufozu.flywheel.core.materials.oriented.OrientedData;
|
||||
import com.jozufozu.flywheel.core.materials.oriented.OrientedType;
|
||||
import com.jozufozu.flywheel.event.GatherContextEvent;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
|
@ -19,14 +17,9 @@ public class Materials {
|
|||
public static final StructType<OrientedData> ORIENTED = new OrientedType();
|
||||
public static final StructType<ModelData> TRANSFORMED = new ModelType();
|
||||
|
||||
public static void flwInit(GatherContextEvent event) {
|
||||
Backend backend = event.getBackend();
|
||||
backend.register(Names.ORIENTED, ORIENTED);
|
||||
backend.register(Names.MODEL, TRANSFORMED);
|
||||
}
|
||||
|
||||
public static class Names {
|
||||
public static final ResourceLocation MODEL = Flywheel.rl("model");
|
||||
public static final ResourceLocation ORIENTED = Flywheel.rl("oriented");
|
||||
public static final ResourceLocation PASSTHRU = Flywheel.rl("passthru");
|
||||
}
|
||||
}
|
||||
|
|
14
src/main/java/com/jozufozu/flywheel/core/Templates.java
Normal file
14
src/main/java/com/jozufozu/flywheel/core/Templates.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
package com.jozufozu.flywheel.core;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
|
||||
import com.jozufozu.flywheel.core.compile.FragmentTemplateData;
|
||||
import com.jozufozu.flywheel.core.compile.InstancingTemplateData;
|
||||
import com.jozufozu.flywheel.core.compile.OneShotTemplateData;
|
||||
import com.jozufozu.flywheel.core.compile.Template;
|
||||
|
||||
public class Templates {
|
||||
|
||||
public static final Template<InstancingTemplateData> INSTANCING = new Template<>(GLSLVersion.V330, InstancingTemplateData::new);
|
||||
public static final Template<OneShotTemplateData> ONE_SHOT = new Template<>(GLSLVersion.V150, OneShotTemplateData::new);
|
||||
public static final Template<FragmentTemplateData> FRAGMENT = new Template<>(GLSLVersion.V150, FragmentTemplateData::new);
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package com.jozufozu.flywheel.core;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.jozufozu.flywheel.api.struct.Instanced;
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.ShaderContext;
|
||||
import com.jozufozu.flywheel.backend.pipeline.ShaderPipeline;
|
||||
import com.jozufozu.flywheel.core.shader.ContextAwareProgram;
|
||||
import com.jozufozu.flywheel.core.shader.WorldProgram;
|
||||
import com.jozufozu.flywheel.core.shader.spec.ProgramSpec;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class WorldContext<P extends WorldProgram> implements ShaderContext<P> {
|
||||
public final Backend backend;
|
||||
protected final Map<ResourceLocation, ContextAwareProgram<P>> programs = new HashMap<>();
|
||||
protected final ResourceLocation name;
|
||||
protected final Supplier<Stream<ResourceLocation>> specStream;
|
||||
|
||||
public final ShaderPipeline<P> pipeline;
|
||||
|
||||
public WorldContext(Backend backend, ResourceLocation name, Supplier<Stream<ResourceLocation>> specStream, ShaderPipeline<P> pipeline) {
|
||||
this.backend = backend;
|
||||
this.name = name;
|
||||
this.specStream = specStream;
|
||||
this.pipeline = pipeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
|
||||
Backend.LOGGER.info("Loading context '{}'", name);
|
||||
|
||||
specStream.get()
|
||||
.map(backend::getSpec)
|
||||
.forEach(this::loadSpec);
|
||||
}
|
||||
|
||||
private void loadSpec(ProgramSpec spec) {
|
||||
|
||||
try {
|
||||
programs.put(spec.name, pipeline.compile(spec));
|
||||
|
||||
Backend.LOGGER.debug("Loaded program {}", spec.name);
|
||||
} catch (Exception e) {
|
||||
Backend.LOGGER.error("Error loading program {}", spec.name);
|
||||
Backend.LOGGER.error("", e);
|
||||
backend.loader.notifyError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Supplier<P> getProgramSupplier(ResourceLocation spec) {
|
||||
return programs.get(spec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
programs.values()
|
||||
.forEach(ContextAwareProgram::delete);
|
||||
programs.clear();
|
||||
}
|
||||
|
||||
public static Builder builder(Backend backend, ResourceLocation name) {
|
||||
return new Builder(backend, name);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final Backend backend;
|
||||
private final ResourceLocation name;
|
||||
private Supplier<Stream<ResourceLocation>> specStream;
|
||||
|
||||
public Builder(Backend backend, ResourceLocation name) {
|
||||
this.backend = backend;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Builder setSpecStream(Supplier<Stream<ResourceLocation>> specStream) {
|
||||
this.specStream = specStream;
|
||||
return this;
|
||||
}
|
||||
|
||||
public <P extends WorldProgram> WorldContext<P> build(ShaderPipeline<P> pipeline) {
|
||||
if (specStream == null) {
|
||||
specStream = () -> backend.allMaterials()
|
||||
.stream()
|
||||
.map(t -> t instanceof Instanced<?> i ? i : null)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Instanced::getProgramSpec);
|
||||
}
|
||||
return new WorldContext<>(backend, name, specStream, pipeline);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,25 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TypeHelper {
|
||||
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
|
||||
|
||||
public class CompileUtil {
|
||||
|
||||
public static final Pattern vecType = Pattern.compile("^[biud]?vec([234])$");
|
||||
public static final Pattern matType = Pattern.compile("^mat([234])(?:x([234]))?$");
|
||||
|
||||
protected static String generateHeader(GLSLVersion version, ShaderType type) {
|
||||
return "#version "
|
||||
+ version
|
||||
+ '\n'
|
||||
+ "#extension GL_ARB_explicit_attrib_location : enable\n"
|
||||
+ "#extension GL_ARB_conservative_depth : enable\n"
|
||||
+ type.getDefineStatement();
|
||||
}
|
||||
|
||||
public static int getElementCount(String type) {
|
||||
Matcher vec = vecType.matcher(type);
|
||||
if (vec.find()) return Integer.parseInt(vec.group(1));
|
|
@ -0,0 +1,102 @@
|
|||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
|
||||
import com.jozufozu.flywheel.core.shader.ShaderConstants;
|
||||
import com.jozufozu.flywheel.core.shader.StateSnapshot;
|
||||
import com.jozufozu.flywheel.core.source.FileIndexImpl;
|
||||
import com.jozufozu.flywheel.core.source.FileResolution;
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
|
||||
public class FragmentCompiler extends Memoizer<FragmentCompiler.Context, GlShader> {
|
||||
private final FileResolution header;
|
||||
private final Template<FragmentTemplateData> fragment;
|
||||
|
||||
public FragmentCompiler(Template<FragmentTemplateData> fragment, FileResolution header) {
|
||||
this.header = header;
|
||||
this.fragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GlShader _create(Context key) {
|
||||
SourceFile fragmentFile = key.file;
|
||||
FragmentTemplateData appliedTemplate = fragment.apply(fragmentFile);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append(CompileUtil.generateHeader(fragment.getVersion(), ShaderType.FRAGMENT));
|
||||
|
||||
key.getShaderConstants().writeInto(builder);
|
||||
|
||||
FileIndexImpl index = new FileIndexImpl();
|
||||
|
||||
header.getFile().generateFinalSource(index, builder);
|
||||
fragmentFile.generateFinalSource(index, builder);
|
||||
|
||||
builder.append(appliedTemplate.generateFooter());
|
||||
|
||||
return new GlShader(fragmentFile.name, ShaderType.FRAGMENT, builder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _destroy(GlShader value) {
|
||||
value.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the conditions under which a shader is compiled.
|
||||
*/
|
||||
public static final class Context {
|
||||
/**
|
||||
* The file to compile.
|
||||
*/
|
||||
private final SourceFile file;
|
||||
|
||||
/**
|
||||
* The shader constants to apply.
|
||||
*/
|
||||
private final StateSnapshot ctx;
|
||||
|
||||
/**
|
||||
* Alpha threshold below which fragments are discarded.
|
||||
*/
|
||||
private final float alphaDiscard;
|
||||
|
||||
public Context(SourceFile file, StateSnapshot ctx, float alphaDiscard) {
|
||||
this.file = file;
|
||||
this.ctx = ctx;
|
||||
this.alphaDiscard = alphaDiscard;
|
||||
}
|
||||
|
||||
public ShaderConstants getShaderConstants() {
|
||||
ShaderConstants shaderConstants = ctx.getShaderConstants();
|
||||
|
||||
if (alphaDiscard > 0) {
|
||||
shaderConstants.define("ALPHA_DISCARD", alphaDiscard);
|
||||
}
|
||||
|
||||
return shaderConstants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||
var that = (Context) obj;
|
||||
return this.file == that.file && Objects.equals(this.ctx, that.ctx) && Float.floatToIntBits(this.alphaDiscard) == Float.floatToIntBits(that.alphaDiscard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(file, ctx, alphaDiscard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Context[" + "file=" + file + ", " + "ctx=" + ctx + ", " + "alphaDiscard=" + alphaDiscard + ']';
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
public interface FragmentData {
|
||||
/**
|
||||
* Generate the necessary glue code here.
|
||||
*/
|
||||
String generateFooter();
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
|
||||
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
|
||||
import com.jozufozu.flywheel.core.source.parse.ShaderStruct;
|
||||
import com.jozufozu.flywheel.core.source.parse.StructField;
|
||||
import com.jozufozu.flywheel.core.source.parse.Variable;
|
||||
import com.jozufozu.flywheel.core.source.span.Span;
|
||||
|
||||
public class FragmentTemplateData implements FragmentData {
|
||||
public final SourceFile file;
|
||||
public final Span interpolantName;
|
||||
public final ShaderStruct interpolant;
|
||||
public final ShaderFunction fragmentMain;
|
||||
|
||||
public FragmentTemplateData(SourceFile file) {
|
||||
this.file = file;
|
||||
|
||||
Optional<ShaderFunction> maybeFragmentMain = file.findFunction("fragment");
|
||||
|
||||
if (maybeFragmentMain.isEmpty()) {
|
||||
ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
fragmentMain = maybeFragmentMain.get();
|
||||
ImmutableList<Variable> fragmentParameters = fragmentMain.getParameters();
|
||||
|
||||
|
||||
if (fragmentParameters.size() != 1) {
|
||||
ErrorReporter.generateSpanError(fragmentMain.getArgs(), "fragment function must have exactly one argument");
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
interpolantName = fragmentMain.getParameters().get(0).type;
|
||||
|
||||
Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName);
|
||||
|
||||
if (maybeInterpolant.isEmpty()) {
|
||||
ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
|
||||
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
interpolant = maybeInterpolant.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateFooter() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
prefixFields(builder, interpolant, "in", "v2f_");
|
||||
|
||||
builder.append(String.format("""
|
||||
void main() {
|
||||
Fragment o;
|
||||
o.color = v2f_color;
|
||||
o.texCoords = v2f_texCoords;
|
||||
o.light = v2f_light;
|
||||
o.diffuse = v2f_diffuse;
|
||||
|
||||
vec4 color = %s;
|
||||
FLWFinalizeColor(color);
|
||||
}
|
||||
""",
|
||||
fragmentMain.call("o")
|
||||
));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static void prefixFields(StringBuilder builder, ShaderStruct struct, String qualifier, String prefix) {
|
||||
ImmutableList<StructField> fields = struct.getFields();
|
||||
|
||||
for (StructField field : fields) {
|
||||
builder.append(qualifier)
|
||||
.append(' ')
|
||||
.append(field.type)
|
||||
.append(' ')
|
||||
.append(prefix)
|
||||
.append(field.name)
|
||||
.append(";\n");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.jozufozu.flywheel.api.vertex.VertexType;
|
||||
import com.jozufozu.flywheel.core.source.FileIndex;
|
||||
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
|
||||
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
|
||||
import com.jozufozu.flywheel.core.source.parse.ShaderStruct;
|
||||
import com.jozufozu.flywheel.core.source.parse.StructField;
|
||||
import com.jozufozu.flywheel.core.source.parse.Variable;
|
||||
import com.jozufozu.flywheel.core.source.span.Span;
|
||||
|
||||
public class InstancingTemplateData implements VertexData {
|
||||
|
||||
public final SourceFile file;
|
||||
public final ShaderFunction vertexMain;
|
||||
public final Span vertexName;
|
||||
public final Span instanceName;
|
||||
public final ShaderStruct instance;
|
||||
|
||||
public InstancingTemplateData(SourceFile file) {
|
||||
this.file = file;
|
||||
|
||||
Optional<ShaderFunction> vertexFunc = file.findFunction("vertex");
|
||||
|
||||
if (vertexFunc.isEmpty()) {
|
||||
ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
|
||||
throw new ShaderLoadingException();
|
||||
}
|
||||
|
||||
vertexMain = vertexFunc.get();
|
||||
ImmutableList<Variable> vertexParams = vertexMain.getParameters();
|
||||
|
||||
if (vertexParams.size() != 2) {
|
||||
ErrorReporter.generateSpanError(vertexMain.getArgs(), "instancing requires vertex function to have 2 arguments");
|
||||
throw new ShaderLoadingException();
|
||||
}
|
||||
|
||||
Variable vertexParam = vertexParams.get(0);
|
||||
vertexName = vertexParam.type;
|
||||
|
||||
boolean namedVertex = vertexParam.type
|
||||
.toString()
|
||||
.equals("Vertex");
|
||||
|
||||
|
||||
if (!(namedVertex && vertexParam.qualifier == Variable.Qualifier.INOUT)) {
|
||||
ErrorReporter.generateSpanError(vertexParam.qualifierSpan, "first parameter must be inout Vertex");
|
||||
throw new ShaderLoadingException();
|
||||
}
|
||||
|
||||
instanceName = vertexParams.get(1).type;
|
||||
|
||||
Optional<ShaderStruct> maybeInstance = file.findStruct(instanceName);
|
||||
|
||||
if (maybeInstance.isEmpty()) {
|
||||
ErrorReporter.generateMissingStruct(file, instanceName, "struct not defined");
|
||||
throw new ShaderLoadingException();
|
||||
}
|
||||
|
||||
instance = maybeInstance.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateFooter(FileIndex shader, VertexType vertexType) {
|
||||
ImmutableList<StructField> fields = instance.getFields();
|
||||
|
||||
int attributeBinding = vertexType.getLayout()
|
||||
.getAttributeCount();
|
||||
|
||||
StringBuilder template = new StringBuilder();
|
||||
|
||||
for (StructField field : fields) {
|
||||
template.append("layout(location = ")
|
||||
.append(attributeBinding)
|
||||
.append(") in")
|
||||
.append(' ')
|
||||
.append(field.type)
|
||||
.append(' ')
|
||||
.append("a_i_")
|
||||
.append(field.name)
|
||||
.append(";\n");
|
||||
attributeBinding += CompileUtil.getAttributeCount(field.type);
|
||||
}
|
||||
template.append(String.format("""
|
||||
out vec4 v2f_color;
|
||||
out vec2 v2f_texCoords;
|
||||
out vec2 v2f_light;
|
||||
out float v2f_diffuse;
|
||||
|
||||
void main() {
|
||||
Vertex v = FLWCreateVertex();
|
||||
%s i;
|
||||
%s
|
||||
vertex(v, i);
|
||||
gl_Position = FLWVertex(v);
|
||||
v.normal = normalize(v.normal);
|
||||
|
||||
v2f_color = v.color;
|
||||
v2f_texCoords = v.texCoords;
|
||||
v2f_light = v.light;
|
||||
v2f_diffuse = diffuse(v.normal);
|
||||
#if defined(DEBUG_NORMAL)
|
||||
v2f_color = vec4(v.normal, 1.);
|
||||
#endif
|
||||
}
|
||||
""",
|
||||
instanceName,
|
||||
assignFields(instance, "i.", "a_i_")
|
||||
));
|
||||
|
||||
return template.toString();
|
||||
}
|
||||
|
||||
public static StringBuilder assignFields(ShaderStruct struct, String prefix1, String prefix2) {
|
||||
ImmutableList<StructField> fields = struct.getFields();
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (StructField field : fields) {
|
||||
builder.append(prefix1)
|
||||
.append(field.name)
|
||||
.append(" = ")
|
||||
.append(prefix2)
|
||||
.append(field.name)
|
||||
.append(";\n");
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
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);
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.jozufozu.flywheel.api.vertex.VertexType;
|
||||
import com.jozufozu.flywheel.core.source.FileIndex;
|
||||
import com.jozufozu.flywheel.core.source.ShaderLoadingException;
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
|
||||
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
|
||||
import com.jozufozu.flywheel.core.source.parse.Variable;
|
||||
|
||||
public class OneShotTemplateData implements VertexData {
|
||||
|
||||
public final SourceFile file;
|
||||
public final ShaderFunction vertexMain;
|
||||
|
||||
public OneShotTemplateData(SourceFile file) {
|
||||
this.file = file;
|
||||
|
||||
Optional<ShaderFunction> maybeVertexMain = file.findFunction("vertex");
|
||||
|
||||
if (maybeVertexMain.isEmpty()) {
|
||||
ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
vertexMain = maybeVertexMain.get();
|
||||
ImmutableList<Variable> vertexParameters = vertexMain.getParameters();
|
||||
|
||||
if (vertexParameters.size() != 1) {
|
||||
ErrorReporter.generateSpanError(vertexMain.getArgs(), "a basic model requires vertex function to have one argument");
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
Variable vertexParam = vertexMain.getParameters().get(0);
|
||||
|
||||
boolean namedVertex = vertexParam.type
|
||||
.toString()
|
||||
.equals("Vertex");
|
||||
|
||||
if (!(namedVertex && vertexParam.qualifier == Variable.Qualifier.INOUT)) {
|
||||
ErrorReporter.generateSpanError(vertexParam.qualifierSpan, "first parameter must be inout Vertex");
|
||||
throw new ShaderLoadingException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateFooter(FileIndex file, VertexType vertexType) {
|
||||
return """
|
||||
out vec4 v2f_color;
|
||||
out vec2 v2f_texCoords;
|
||||
out vec2 v2f_light;
|
||||
out float v2f_diffuse;
|
||||
|
||||
void main() {
|
||||
Vertex v = FLWCreateVertex();
|
||||
vertex(v);
|
||||
gl_Position = FLWVertex(v);
|
||||
v.normal = normalize(v.normal);
|
||||
|
||||
v2f_color = v.color;
|
||||
v2f_texCoords = v.texCoords;
|
||||
v2f_light = v.light;
|
||||
v2f_diffuse = diffuse(v.normal);
|
||||
#if defined(DEBUG_NORMAL)
|
||||
v2f_color = vec4(v.normal, 1.);
|
||||
#endif
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
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.glBindAttribLocation;
|
||||
import static org.lwjgl.opengl.GL20.glCreateProgram;
|
||||
import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
|
||||
import static org.lwjgl.opengl.GL20.glGetProgrami;
|
||||
|
@ -12,45 +11,33 @@ import static org.lwjgl.opengl.GL20.glLinkProgram;
|
|||
import java.util.List;
|
||||
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
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 it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class ProtoProgram {
|
||||
public class ProgramAssembler {
|
||||
public final int program;
|
||||
public final WorldShader parent;
|
||||
private final ResourceLocation name;
|
||||
|
||||
private int attributeIndex;
|
||||
private final List<GlShader> shaders = new ObjectArrayList<>();
|
||||
|
||||
private final List<GlShader> shaders;
|
||||
|
||||
public ProtoProgram(WorldShader parent) {
|
||||
this.parent = parent;
|
||||
public ProgramAssembler(ResourceLocation name) {
|
||||
this.name = name;
|
||||
this.program = glCreateProgram();
|
||||
shaders = new ObjectArrayList<>();
|
||||
}
|
||||
|
||||
public ProtoProgram compilePart(ShaderType type) {
|
||||
GlShader shader = parent.compile(type);
|
||||
attachShader(shader);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Links the attached shaders to this program.
|
||||
*/
|
||||
public ProtoProgram link() {
|
||||
|
||||
parent.template.getShaderInputs(parent.mainFile)
|
||||
.forEach(this::addAttribute);
|
||||
|
||||
public ProgramAssembler link() {
|
||||
glLinkProgram(this.program);
|
||||
|
||||
String log = glGetProgramInfoLog(this.program);
|
||||
|
||||
if (!log.isEmpty()) {
|
||||
Backend.LOGGER.debug("Program link log for " + parent.name + ": " + log);
|
||||
Backend.LOGGER.debug("Program link log for " + name + ": " + log);
|
||||
}
|
||||
|
||||
int result = glGetProgrami(this.program, GL_LINK_STATUS);
|
||||
|
@ -62,19 +49,18 @@ public class ProtoProgram {
|
|||
return this;
|
||||
}
|
||||
|
||||
public ProtoProgram deleteLinkedShaders() {
|
||||
public ProgramAssembler deleteLinkedShaders() {
|
||||
shaders.forEach(GlShader::delete);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void attachShader(GlShader glShader) {
|
||||
public ProgramAssembler attachShader(GlShader glShader) {
|
||||
shaders.add(glShader);
|
||||
glAttachShader(this.program, glShader.handle());
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addAttribute(ShaderInput shaderInput) {
|
||||
glBindAttribLocation(this.program, attributeIndex, shaderInput.name);
|
||||
attributeIndex += shaderInput.attribCount;
|
||||
public <P extends GlProgram> P build(GlProgram.Factory<P> factory) {
|
||||
return factory.create(name, program);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
import com.jozufozu.flywheel.core.Templates;
|
||||
import com.jozufozu.flywheel.core.source.FileResolution;
|
||||
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
|
||||
|
||||
/**
|
||||
* 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>
|
||||
*/
|
||||
public class ProgramCompiler<P extends GlProgram> extends Memoizer<ProgramContext, P> {
|
||||
|
||||
private static final List<ProgramCompiler<?>> ALL_COMPILERS = new ArrayList<>();
|
||||
|
||||
private final GlProgram.Factory<P> factory;
|
||||
private final VertexCompiler vertexCompiler;
|
||||
private final FragmentCompiler fragmentCompiler;
|
||||
|
||||
public ProgramCompiler(GlProgram.Factory<P> factory, VertexCompiler vertexCompiler, FragmentCompiler fragmentCompiler) {
|
||||
this.factory = factory;
|
||||
this.vertexCompiler = vertexCompiler;
|
||||
this.fragmentCompiler = fragmentCompiler;
|
||||
|
||||
ALL_COMPILERS.add(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a program compiler using this template.
|
||||
* @param template The vertex template to use.
|
||||
* @param factory A factory to add meaning to compiled programs.
|
||||
* @param header The header file to use for the program.
|
||||
* @param <P> The type of program to compile.
|
||||
* @return A program compiler.
|
||||
*/
|
||||
public static <T extends VertexData, P extends GlProgram> ProgramCompiler<P> create(Template<T> template, GlProgram.Factory<P> factory, FileResolution header) {
|
||||
return new ProgramCompiler<>(factory, new VertexCompiler(template, header), new FragmentCompiler(Templates.FRAGMENT, header));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 P getProgram(ProgramContext ctx) {
|
||||
return super.get(ctx);
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
super.invalidate();
|
||||
vertexCompiler.invalidate();
|
||||
fragmentCompiler.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected P _create(ProgramContext ctx) {
|
||||
return new ProgramAssembler(ctx.spec.name)
|
||||
.attachShader(vertexCompiler.get(new VertexCompiler.Context(ctx.spec.getVertexFile(), ctx.ctx, ctx.vertexType)))
|
||||
.attachShader(fragmentCompiler.get(new FragmentCompiler.Context(ctx.spec.getFragmentFile(), ctx.ctx, ctx.alphaDiscard)))
|
||||
.link()
|
||||
.build(this.factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _destroy(P value) {
|
||||
value.delete();
|
||||
}
|
||||
|
||||
public static void invalidateAll(ReloadRenderersEvent event) {
|
||||
ALL_COMPILERS.forEach(ProgramCompiler::invalidate);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.api.vertex.VertexType;
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.RenderLayer;
|
||||
import com.jozufozu.flywheel.core.GameStateRegistry;
|
||||
import com.jozufozu.flywheel.core.shader.ProgramSpec;
|
||||
import com.jozufozu.flywheel.core.shader.StateSnapshot;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/**
|
||||
* Represents the entire context of a program's usage.
|
||||
*/
|
||||
public final class ProgramContext {
|
||||
/**
|
||||
* Creates a compilation context for the given program, vertex type and render layer.
|
||||
*
|
||||
* @param programName The name of the program to use.
|
||||
* @param vertexType The vertex type to use.
|
||||
* @param layer If cutout, the alpha discard threshold is 0.1, otherwise 0.
|
||||
* @return A compilation context.
|
||||
*/
|
||||
public static ProgramContext create(ResourceLocation programName, VertexType vertexType, @Nullable RenderLayer layer) {
|
||||
ProgramSpec spec = Backend.getSpec(programName);
|
||||
|
||||
if (spec == null) {
|
||||
throw new NullPointerException("Cannot compile shader because '" + programName + "' is not recognized.");
|
||||
}
|
||||
|
||||
return new ProgramContext(spec, getAlphaDiscard(layer), vertexType, GameStateRegistry.takeSnapshot());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the alpha discard threshold for the given render layer.
|
||||
*
|
||||
* @param layer The render layer to get the alpha discard threshold for.
|
||||
* @return The alpha discard threshold.
|
||||
*/
|
||||
public static float getAlphaDiscard(@Nullable RenderLayer layer) {
|
||||
return layer == RenderLayer.CUTOUT ? 0.1f : 0f;
|
||||
}
|
||||
|
||||
public final ProgramSpec spec;
|
||||
public final float alphaDiscard;
|
||||
public final VertexType vertexType;
|
||||
public final StateSnapshot ctx;
|
||||
|
||||
/**
|
||||
* @param spec The program to use.
|
||||
* @param alphaDiscard Alpha threshold below which pixels are discarded.
|
||||
* @param vertexType The vertexType the program should be adapted for.
|
||||
* @param ctx A snapshot of the game state.
|
||||
*/
|
||||
public ProgramContext(ProgramSpec spec, float alphaDiscard, VertexType vertexType, StateSnapshot ctx) {
|
||||
this.spec = spec;
|
||||
this.alphaDiscard = alphaDiscard;
|
||||
this.vertexType = vertexType;
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
var that = (ProgramContext) o;
|
||||
return spec == that.spec && vertexType == that.vertexType && ctx.equals(that.ctx) && Float.floatToIntBits(alphaDiscard) == Float.floatToIntBits(that.alphaDiscard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(spec, alphaDiscard, vertexType, ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProgramContext{" + "spec=" + spec + ", alphaDiscard=" + alphaDiscard + ", vertexType=" + vertexType + ", ctx=" + ctx + '}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.GLSLVersion;
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
|
||||
/**
|
||||
* A class that generates glsl glue code given a SourceFile.
|
||||
*
|
||||
* <p>
|
||||
* Shader files are written somewhat abstractly. Subclasses of Template handle those abstractions, using SourceFile
|
||||
* metadata to generate shader code that OpenGL can use to call into our shader programs.
|
||||
* </p>
|
||||
*/
|
||||
public class Template<T> extends Memoizer<SourceFile, T> {
|
||||
|
||||
private final Function<SourceFile, T> reader;
|
||||
private final GLSLVersion glslVersion;
|
||||
|
||||
public Template(GLSLVersion glslVersion, Function<SourceFile, T> reader) {
|
||||
this.reader = reader;
|
||||
this.glslVersion = glslVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the given SourceFile is valid for this Template and return the metadata.
|
||||
* @param file The SourceFile to apply this Template to.
|
||||
* @return The applied template metadata.
|
||||
*/
|
||||
public T apply(SourceFile file) {
|
||||
// lazily read files, cache results
|
||||
return super.get(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The GLSL version this template requires.
|
||||
*/
|
||||
public GLSLVersion getVersion() {
|
||||
return glslVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T _create(SourceFile key) {
|
||||
return reader.apply(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _destroy(T value) {
|
||||
// noop
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.jozufozu.flywheel.api.vertex.VertexType;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlShader;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.ShaderType;
|
||||
import com.jozufozu.flywheel.core.shader.StateSnapshot;
|
||||
import com.jozufozu.flywheel.core.source.FileIndexImpl;
|
||||
import com.jozufozu.flywheel.core.source.FileResolution;
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
|
||||
public class VertexCompiler extends Memoizer<VertexCompiler.Context, GlShader> {
|
||||
private final Template<? extends VertexData> template;
|
||||
private final FileResolution header;
|
||||
|
||||
public VertexCompiler(Template<? extends VertexData> template, FileResolution header) {
|
||||
this.template = template;
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GlShader _create(Context key) {
|
||||
StringBuilder finalSource = new StringBuilder();
|
||||
|
||||
finalSource.append(CompileUtil.generateHeader(template.getVersion(), ShaderType.VERTEX));
|
||||
|
||||
key.ctx.getShaderConstants().writeInto(finalSource);
|
||||
|
||||
finalSource.append("""
|
||||
struct Vertex {
|
||||
vec3 pos;
|
||||
vec4 color;
|
||||
vec2 texCoords;
|
||||
vec2 light;
|
||||
vec3 normal;
|
||||
};
|
||||
""");
|
||||
finalSource.append(key.vertexType.getShaderHeader());
|
||||
|
||||
FileIndexImpl index = new FileIndexImpl();
|
||||
|
||||
header.getFile().generateFinalSource(index, finalSource);
|
||||
|
||||
key.file.generateFinalSource(index, finalSource);
|
||||
|
||||
VertexData appliedTemplate = template.apply(key.file);
|
||||
finalSource.append(appliedTemplate.generateFooter(index, key.vertexType));
|
||||
|
||||
return new GlShader(key.file.name, ShaderType.VERTEX, finalSource.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void _destroy(GlShader value) {
|
||||
value.delete();
|
||||
}
|
||||
|
||||
public static class Context {
|
||||
/**
|
||||
* The file to compile.
|
||||
*/
|
||||
private final SourceFile file;
|
||||
|
||||
/**
|
||||
* The shader constants to apply.
|
||||
*/
|
||||
private final StateSnapshot ctx;
|
||||
|
||||
/**
|
||||
* The vertex type to use.
|
||||
*/
|
||||
private final VertexType vertexType;
|
||||
|
||||
public Context(SourceFile file, StateSnapshot ctx, VertexType vertexType) {
|
||||
this.file = file;
|
||||
this.ctx = ctx;
|
||||
this.vertexType = vertexType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
var that = (Context) o;
|
||||
return file == that.file && vertexType == that.vertexType && ctx.equals(that.ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(file, ctx, vertexType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
import com.jozufozu.flywheel.api.vertex.VertexType;
|
||||
import com.jozufozu.flywheel.core.source.FileIndex;
|
||||
|
||||
public interface VertexData {
|
||||
/**
|
||||
* Generate the necessary glue code here.
|
||||
* @param file The SourceFile with user written code.
|
||||
*/
|
||||
String generateFooter(FileIndex file, VertexType vertexType);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.source;
|
||||
package com.jozufozu.flywheel.core.compile;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.jozufozu.flywheel.core.crumbling;
|
||||
|
||||
import com.jozufozu.flywheel.backend.RenderLayer;
|
||||
import com.jozufozu.flywheel.backend.instancing.instancing.InstancedMaterialGroup;
|
||||
import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine;
|
||||
import com.jozufozu.flywheel.util.Textures;
|
||||
|
@ -18,7 +19,7 @@ public class CrumblingGroup<P extends CrumblingProgram> extends InstancedMateria
|
|||
}
|
||||
|
||||
@Override
|
||||
public void render(Matrix4f viewProjection, double camX, double camY, double camZ) {
|
||||
public void render(Matrix4f viewProjection, double camX, double camY, double camZ, RenderLayer layer) {
|
||||
type.setupRenderState();
|
||||
|
||||
int renderTex = RenderSystem.getShaderTexture(0);
|
||||
|
@ -35,7 +36,7 @@ public class CrumblingGroup<P extends CrumblingProgram> extends InstancedMateria
|
|||
RenderSystem.setShaderTexture(4, breakingTex);
|
||||
|
||||
Textures.bindActiveTextures();
|
||||
renderAll(viewProjection, camX, camY, camZ);
|
||||
renderAll(viewProjection, camX, camY, camZ, layer);
|
||||
|
||||
CrumblingRenderer._currentLayer.clearRenderState();
|
||||
}
|
||||
|
|
|
@ -27,8 +27,8 @@ public class BufferLayout {
|
|||
|
||||
int numAttributes = 0, stride = 0;
|
||||
for (LayoutItem spec : allAttributes) {
|
||||
numAttributes += spec.getAttributeCount();
|
||||
stride += spec.getSize();
|
||||
numAttributes += spec.attributeCount();
|
||||
stride += spec.size();
|
||||
}
|
||||
this.numAttributes = numAttributes;
|
||||
this.stride = stride;
|
||||
|
|
|
@ -19,6 +19,6 @@ public class CommonItems {
|
|||
public static final PrimitiveItem LIGHT_SHORT = new PrimitiveItem(GlNumericType.USHORT, 2, true);
|
||||
|
||||
public static final PrimitiveItem NORMALIZED_BYTE = new PrimitiveItem(GlNumericType.BYTE, 1, true);
|
||||
public static final LayoutItem PADDING_BYTE = new PaddingItem(1);
|
||||
public static final LayoutItem PADDING_BYTE = new Padding(1);
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ public interface LayoutItem {
|
|||
|
||||
void vertexAttribPointer(int stride, int index, int offset);
|
||||
|
||||
int getSize();
|
||||
int size();
|
||||
|
||||
int getAttributeCount();
|
||||
int attributeCount();
|
||||
}
|
||||
|
|
|
@ -26,12 +26,13 @@ public enum MatrixItems implements LayoutItem {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
public int size() {
|
||||
return GlNumericType.FLOAT.getByteWidth() * rows * cols;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeCount() {
|
||||
public int attributeCount() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package com.jozufozu.flywheel.core.layout;
|
||||
|
||||
record PaddingItem(int bytes) implements LayoutItem {
|
||||
record Padding(int bytes) implements LayoutItem {
|
||||
|
||||
@Override
|
||||
public void vertexAttribPointer(int stride, int index, int offset) {
|
||||
|
@ -8,12 +8,13 @@ record PaddingItem(int bytes) implements LayoutItem {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
public int size() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeCount() {
|
||||
public int attributeCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -30,12 +30,13 @@ public class PrimitiveItem implements LayoutItem {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeCount() {
|
||||
public int attributeCount() {
|
||||
return attributeCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.jozufozu.flywheel.core.materials;
|
||||
|
||||
import com.jozufozu.flywheel.api.InstanceData;
|
||||
import com.jozufozu.flywheel.util.Color;
|
||||
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
|
||||
|
@ -33,6 +34,15 @@ public abstract class BasicData extends InstanceData implements FlatLit<BasicDat
|
|||
return LightTexture.pack(this.blockLight, this.skyLight);
|
||||
}
|
||||
|
||||
public BasicData setColor(Color color) {
|
||||
this.r = (byte) color.getRed();
|
||||
this.g = (byte) color.getGreen();
|
||||
this.b = (byte) color.getBlue();
|
||||
this.a = (byte) color.getAlpha();
|
||||
markDirty();
|
||||
return this;
|
||||
}
|
||||
|
||||
public BasicData setColor(int color) {
|
||||
return setColor(color, false);
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
|
||||
/**
|
||||
* Encapsulates any number of shader programs for use in similar contexts.
|
||||
* Allows the implementor to choose which shader program to use based on arbitrary state.
|
||||
*
|
||||
* @param <P>
|
||||
*/
|
||||
public interface ContextAwareProgram<P extends GlProgram> extends Supplier<P> {
|
||||
|
||||
/**
|
||||
* Get the shader program most suited for the current game state.
|
||||
*
|
||||
* @return The one true program.
|
||||
*/
|
||||
P get();
|
||||
|
||||
/**
|
||||
* Delete all shader programs encapsulated by your implementation.
|
||||
*/
|
||||
void delete();
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import com.jozufozu.flywheel.backend.ShaderContext;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
import com.jozufozu.flywheel.core.shader.extension.ExtensionInstance;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/**
|
||||
* A shader program that be arbitrarily "extended". This class can take in any number of program extensions, and
|
||||
* will initialize them and then call their {@link ExtensionInstance#bind() bind} function every subsequent time this
|
||||
* program is bound. An "extension" is something that interacts with the shader program in a way that is invisible to
|
||||
* the caller using the program. This is used by some programs to implement the different fog modes. Other uses might
|
||||
* include binding extra textures to allow for blocks to have normal maps, for example. As the extensions are
|
||||
* per-program, this also allows for same extra specialization within a
|
||||
* {@link ShaderContext ShaderContext}.
|
||||
*/
|
||||
public class ExtensibleGlProgram extends GlProgram {
|
||||
|
||||
protected final List<ExtensionInstance> extensions = new ArrayList<>();
|
||||
|
||||
public ExtensibleGlProgram(ResourceLocation name, int handle) {
|
||||
super(name, handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind() {
|
||||
super.bind();
|
||||
|
||||
extensions.forEach(ExtensionInstance::bind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("program ")
|
||||
.append(name)
|
||||
.append('[');
|
||||
|
||||
for (ExtensionInstance extension : extensions) {
|
||||
builder.append(extension)
|
||||
.append('+');
|
||||
}
|
||||
|
||||
builder.append(']');
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A factory interface to create {@link GlProgram}s parameterized by a list of extensions. This doesn't necessarily
|
||||
* have to return an {@link ExtensibleGlProgram} if implementors want more flexibility for whatever reason.
|
||||
*/
|
||||
public interface Factory<P extends GlProgram> {
|
||||
|
||||
@Nonnull
|
||||
P create(ResourceLocation name, int handle);
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
import com.jozufozu.flywheel.core.shader.spec.GameStateCondition;
|
||||
import com.jozufozu.flywheel.util.Pair;
|
||||
|
||||
public class GameStateProgram<P extends GlProgram> implements ContextAwareProgram<P> {
|
||||
|
||||
private final List<Pair<GameStateCondition, P>> variants;
|
||||
private final P fallback;
|
||||
|
||||
protected GameStateProgram(List<Pair<GameStateCondition, P>> variants, P fallback) {
|
||||
this.variants = variants;
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public P get() {
|
||||
for (Pair<GameStateCondition, P> variant : variants) {
|
||||
if (variant.first()
|
||||
.isMet()) return variant.second();
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
for (Pair<GameStateCondition, P> variant : variants) {
|
||||
variant.second()
|
||||
.delete();
|
||||
}
|
||||
|
||||
fallback.delete();
|
||||
}
|
||||
|
||||
public static <P extends GlProgram> Builder<P> builder(P fallback) {
|
||||
return new Builder<>(fallback);
|
||||
}
|
||||
|
||||
public static class Builder<P extends GlProgram> {
|
||||
private final P fallback;
|
||||
private final List<Pair<GameStateCondition, P>> variants = new ArrayList<>();
|
||||
|
||||
public Builder(P fallback) {
|
||||
this.fallback = fallback;
|
||||
}
|
||||
|
||||
public Builder<P> withVariant(GameStateCondition condition, P program) {
|
||||
variants.add(Pair.of(condition, program));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ContextAwareProgram<P> build() {
|
||||
return new GameStateProgram<>(ImmutableList.copyOf(variants), fallback);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.jozufozu.flywheel.core.shader;
|
||||
|
||||
/**
|
||||
* An object that provides a view of the current game state for shader compilation.
|
||||
*/
|
||||
public interface GameStateProvider {
|
||||
|
||||
/**
|
||||
* Get the status of this game state provider.
|
||||
* @return Returning {@code true} will cause #alterConstants to be called before compiling a shader.
|
||||
*/
|
||||
boolean isTrue();
|
||||
|
||||
/**
|
||||
* Alter the constants for shader compilation.
|
||||
* @param constants The shader constants.
|
||||
*/
|
||||
void alterConstants(ShaderConstants constants);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.jozufozu.flywheel.core.shader;
|
||||
|
||||
import com.jozufozu.flywheel.config.FlwConfig;
|
||||
|
||||
public enum NormalDebugStateProvider implements GameStateProvider {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public boolean isTrue() {
|
||||
return FlwConfig.get()
|
||||
.debugNormals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void alterConstants(ShaderConstants constants) {
|
||||
constants.define("DEBUG_NORMAL");
|
||||
}
|
||||
}
|
|
@ -1,11 +1,8 @@
|
|||
package com.jozufozu.flywheel.core.shader.spec;
|
||||
package com.jozufozu.flywheel.core.shader;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.jozufozu.flywheel.backend.source.FileResolution;
|
||||
import com.jozufozu.flywheel.backend.source.Resolver;
|
||||
import com.jozufozu.flywheel.backend.source.SourceFile;
|
||||
import com.jozufozu.flywheel.core.source.FileResolution;
|
||||
import com.jozufozu.flywheel.core.source.Resolver;
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
|
||||
|
@ -26,39 +23,46 @@ import net.minecraft.resources.ResourceLocation;
|
|||
*/
|
||||
public class ProgramSpec {
|
||||
|
||||
// TODO: Block model style inheritance?
|
||||
public static final Codec<ProgramSpec> CODEC = RecordCodecBuilder.create(instance -> instance.group(
|
||||
ResourceLocation.CODEC.fieldOf("source")
|
||||
ResourceLocation.CODEC.fieldOf("vertex")
|
||||
.forGetter(ProgramSpec::getSourceLoc),
|
||||
ProgramState.CODEC.listOf()
|
||||
.optionalFieldOf("states", Collections.emptyList())
|
||||
.forGetter(ProgramSpec::getStates))
|
||||
ResourceLocation.CODEC.fieldOf("fragment")
|
||||
.forGetter(ProgramSpec::getFragmentLoc))
|
||||
.apply(instance, ProgramSpec::new));
|
||||
|
||||
public ResourceLocation name;
|
||||
public final FileResolution source;
|
||||
public final FileResolution vertex;
|
||||
public final FileResolution fragment;
|
||||
|
||||
public final List<ProgramState> states;
|
||||
|
||||
public ProgramSpec(ResourceLocation source, List<ProgramState> states) {
|
||||
this.source = Resolver.INSTANCE.findShader(source);
|
||||
this.states = states;
|
||||
public ProgramSpec(ResourceLocation vertex, ResourceLocation fragment) {
|
||||
this.vertex = Resolver.INSTANCE.get(vertex);
|
||||
this.fragment = Resolver.INSTANCE.get(fragment);
|
||||
}
|
||||
|
||||
public void setName(ResourceLocation name) {
|
||||
this.name = name;
|
||||
this.vertex.addSpec(name);
|
||||
this.fragment.addSpec(name);
|
||||
}
|
||||
|
||||
public ResourceLocation getSourceLoc() {
|
||||
return source.getFileLoc();
|
||||
return vertex.getFileLoc();
|
||||
}
|
||||
|
||||
public FileResolution getSource() {
|
||||
return source;
|
||||
public ResourceLocation getFragmentLoc() {
|
||||
return fragment.getFileLoc();
|
||||
}
|
||||
|
||||
public List<ProgramState> getStates() {
|
||||
return states;
|
||||
public SourceFile getVertexFile() {
|
||||
return vertex.getFile();
|
||||
}
|
||||
|
||||
public SourceFile getFragmentFile() {
|
||||
return fragment.getFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package com.jozufozu.flywheel.core.shader;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A class for manipulating a list of {@code #define} directives.
|
||||
*
|
||||
* <p>Based loosely on code by jellysquid3.
|
||||
*/
|
||||
public class ShaderConstants {
|
||||
|
||||
private final Map<String, String> definitions = new HashMap<>();
|
||||
|
||||
public ShaderConstants define(String def) {
|
||||
definitions.put(def, "");
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShaderConstants define(String def, String value) {
|
||||
definitions.put(def, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShaderConstants define(String def, float value) {
|
||||
definitions.put(def, Float.toString(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShaderConstants defineAll(List<String> defines) {
|
||||
for (String def : defines) {
|
||||
definitions.put(def, "");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public String build() {
|
||||
final StringBuilder acc = new StringBuilder();
|
||||
writeInto(acc);
|
||||
return acc.toString();
|
||||
}
|
||||
|
||||
public void writeInto(final StringBuilder acc) {
|
||||
for (Map.Entry<String, String> e : definitions.entrySet()) {
|
||||
acc.append("#define ")
|
||||
.append(e.getKey());
|
||||
if (e.getValue().length() > 0) {
|
||||
acc.append(' ')
|
||||
.append(e.getValue());
|
||||
}
|
||||
acc.append('\n');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.jozufozu.flywheel.core.shader;
|
||||
|
||||
import java.util.BitSet;
|
||||
|
||||
import com.jozufozu.flywheel.core.GameStateRegistry;
|
||||
|
||||
public record StateSnapshot(BitSet ctx) {
|
||||
// TODO: is this needed?
|
||||
|
||||
public ShaderConstants getShaderConstants() {
|
||||
return GameStateRegistry.getShaderConstants(this);
|
||||
}
|
||||
}
|
|
@ -1,16 +1,11 @@
|
|||
package com.jozufozu.flywheel.core.shader.extension;
|
||||
package com.jozufozu.flywheel.core.shader;
|
||||
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class WorldFog implements ExtensionInstance {
|
||||
|
||||
public static final ResourceLocation NAME = Flywheel.rl("fog");
|
||||
public class WorldFog {
|
||||
|
||||
private final int uFogColor;
|
||||
private final int uFogRange;
|
||||
|
@ -20,14 +15,8 @@ public class WorldFog implements ExtensionInstance {
|
|||
this.uFogRange = program.getUniformLocation("uFogRange");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind() {
|
||||
GL20.glUniform2f(uFogRange, RenderSystem.getShaderFogStart(), RenderSystem.getShaderFogEnd());
|
||||
GL20.glUniform4fv(uFogColor, RenderSystem.getShaderFogColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation name() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import static org.lwjgl.opengl.GL20.glUniform1f;
|
|||
import static org.lwjgl.opengl.GL20.glUniform2f;
|
||||
import static org.lwjgl.opengl.GL20.glUniform3f;
|
||||
|
||||
import com.jozufozu.flywheel.core.shader.extension.WorldFog;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
import com.jozufozu.flywheel.util.AnimationTickHolder;
|
||||
import com.mojang.blaze3d.platform.Window;
|
||||
import com.mojang.math.Matrix4f;
|
||||
|
@ -12,11 +12,12 @@ import com.mojang.math.Matrix4f;
|
|||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class WorldProgram extends ExtensibleGlProgram {
|
||||
public class WorldProgram extends GlProgram {
|
||||
protected final int uTime = getUniformLocation("uTime");
|
||||
protected final int uViewProjection = getUniformLocation("uViewProjection");
|
||||
protected final int uCameraPos = getUniformLocation("uCameraPos");
|
||||
protected final int uWindowSize = getUniformLocation("uWindowSize");
|
||||
private final WorldFog fog;
|
||||
|
||||
protected int uBlockAtlas;
|
||||
protected int uLightMap;
|
||||
|
@ -24,7 +25,7 @@ public class WorldProgram extends ExtensibleGlProgram {
|
|||
public WorldProgram(ResourceLocation name, int handle) {
|
||||
super(name, handle);
|
||||
|
||||
this.extensions.add(new WorldFog(this));
|
||||
fog = new WorldFog(this);
|
||||
|
||||
super.bind();
|
||||
registerSamplers();
|
||||
|
@ -67,7 +68,7 @@ public class WorldProgram extends ExtensibleGlProgram {
|
|||
@Override
|
||||
public void bind() {
|
||||
super.bind();
|
||||
|
||||
fog.bind();
|
||||
uploadWindowSize();
|
||||
uploadTime(AnimationTickHolder.getRenderTime());
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader.extension;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public interface ExtensionInstance {
|
||||
|
||||
/**
|
||||
* Bind the extra program state. It is recommended to grab the state information from global variables.
|
||||
*/
|
||||
void bind();
|
||||
|
||||
ResourceLocation name();
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader.extension;
|
||||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class UnitExtensionInstance implements ExtensionInstance {
|
||||
|
||||
public static final ResourceLocation NAME = Flywheel.rl("unit");
|
||||
|
||||
public UnitExtensionInstance(GlProgram program) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation name() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader.gamestate;
|
||||
|
||||
import com.jozufozu.flywheel.backend.GameStateRegistry;
|
||||
import com.mojang.serialization.Codec;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public interface GameStateProvider {
|
||||
|
||||
Codec<GameStateProvider> CODEC = ResourceLocation.CODEC.xmap(GameStateRegistry::getStateProvider, GameStateProvider::getID);
|
||||
|
||||
ResourceLocation getID();
|
||||
|
||||
Object getValue();
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader.gamestate;
|
||||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.config.FlwConfig;
|
||||
import com.jozufozu.flywheel.core.shader.spec.BooleanStateProvider;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class NormalDebugStateProvider implements BooleanStateProvider {
|
||||
|
||||
public static final NormalDebugStateProvider INSTANCE = new NormalDebugStateProvider();
|
||||
public static final ResourceLocation NAME = Flywheel.rl("normal_debug");
|
||||
|
||||
protected NormalDebugStateProvider() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrue() {
|
||||
return FlwConfig.get()
|
||||
.debugNormals();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getID() {
|
||||
return NAME;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
|
||||
package com.jozufozu.flywheel.backend.pipeline;
|
||||
package com.jozufozu.flywheel.core.shader;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader.spec;
|
||||
|
||||
import com.jozufozu.flywheel.core.shader.gamestate.GameStateProvider;
|
||||
import com.mojang.serialization.Codec;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class BooleanGameStateCondition implements GameStateCondition {
|
||||
|
||||
public static final Codec<BooleanGameStateCondition> BOOLEAN_SUGAR = GameStateProvider.CODEC.xmap(gameContext -> {
|
||||
if (gameContext instanceof BooleanStateProvider) {
|
||||
return new BooleanGameStateCondition(((BooleanStateProvider) gameContext));
|
||||
}
|
||||
|
||||
return null;
|
||||
}, GameStateCondition::getStateProvider);
|
||||
protected final BooleanStateProvider context;
|
||||
|
||||
public BooleanGameStateCondition(BooleanStateProvider context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getID() {
|
||||
return context.getID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameStateProvider getStateProvider() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMet() {
|
||||
return context.isTrue();
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader.spec;
|
||||
|
||||
import com.jozufozu.flywheel.core.shader.gamestate.GameStateProvider;
|
||||
|
||||
public interface BooleanStateProvider extends GameStateProvider {
|
||||
|
||||
boolean isTrue();
|
||||
|
||||
@Override
|
||||
default Boolean getValue() {
|
||||
return isTrue();
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader.spec;
|
||||
|
||||
import com.jozufozu.flywheel.core.shader.gamestate.GameStateProvider;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public interface GameStateCondition {
|
||||
|
||||
ResourceLocation getID();
|
||||
|
||||
GameStateProvider getStateProvider();
|
||||
|
||||
boolean isMet();
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader.spec;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.jozufozu.flywheel.util.CodecUtil;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
|
||||
public record ProgramState(GameStateCondition context, List<String> defines) {
|
||||
|
||||
// TODO: Use Codec.dispatch
|
||||
private static final Codec<GameStateCondition> WHEN = Codec.either(BooleanGameStateCondition.BOOLEAN_SUGAR, SpecificValueCondition.CODEC)
|
||||
.flatXmap(either -> either.map(DataResult::success, DataResult::success), any -> {
|
||||
if (any instanceof BooleanGameStateCondition) {
|
||||
return DataResult.success(Either.left((BooleanGameStateCondition) any));
|
||||
}
|
||||
|
||||
if (any instanceof SpecificValueCondition) {
|
||||
return DataResult.success(Either.right((SpecificValueCondition) any));
|
||||
}
|
||||
|
||||
return DataResult.error("unknown context condition");
|
||||
});
|
||||
|
||||
public static final Codec<ProgramState> CODEC = RecordCodecBuilder.create(state -> state.group(WHEN.fieldOf("when")
|
||||
.forGetter(ProgramState::context), CodecUtil.oneOrMore(Codec.STRING)
|
||||
.optionalFieldOf("define", Collections.emptyList())
|
||||
.forGetter(ProgramState::defines))
|
||||
.apply(state, ProgramState::new));
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package com.jozufozu.flywheel.core.shader.spec;
|
||||
|
||||
import com.jozufozu.flywheel.core.shader.gamestate.GameStateProvider;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class SpecificValueCondition implements GameStateCondition {
|
||||
|
||||
public static final Codec<SpecificValueCondition> CODEC = RecordCodecBuilder.create(condition -> condition.group(GameStateProvider.CODEC.fieldOf("provider")
|
||||
.forGetter(SpecificValueCondition::getStateProvider), Codec.STRING.fieldOf("value")
|
||||
.forGetter(SpecificValueCondition::getValue))
|
||||
.apply(condition, SpecificValueCondition::new));
|
||||
|
||||
private final String required;
|
||||
private final GameStateProvider context;
|
||||
|
||||
public SpecificValueCondition(GameStateProvider context, String required) {
|
||||
this.required = required;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getID() {
|
||||
return context.getID();
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return required;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameStateProvider getStateProvider() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMet() {
|
||||
return required.equals(context.getValue()
|
||||
.toString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package com.jozufozu.flywheel.core.source;
|
||||
|
||||
import com.jozufozu.flywheel.core.source.span.Span;
|
||||
|
||||
public interface FileIndex {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
int getFileID(SourceFile sourceFile);
|
||||
|
||||
SourceFile getFile(int fileID);
|
||||
|
||||
default Span getLineSpan(int fileId, int lineNo) {
|
||||
return getFile(fileId).getLineSpanNoWhitespace(lineNo);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package com.jozufozu.flywheel.core.source;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.core.source.error.ErrorBuilder;
|
||||
import com.jozufozu.flywheel.core.source.error.ErrorReporter;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class FileIndexImpl implements FileIndex {
|
||||
public final List<SourceFile> files = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Override
|
||||
public int getFileID(SourceFile sourceFile) {
|
||||
int i = files.indexOf(sourceFile);
|
||||
if (i != -1) {
|
||||
return i;
|
||||
}
|
||||
|
||||
int size = files.size();
|
||||
files.add(sourceFile);
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceFile getFile(int fileId) {
|
||||
return files.get(fileId);
|
||||
}
|
||||
|
||||
|
||||
public void printShaderInfoLog(String source, String log, ResourceLocation name) {
|
||||
List<String> lines = log.lines()
|
||||
.toList();
|
||||
|
||||
boolean needsSourceDump = false;
|
||||
|
||||
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');
|
||||
needsSourceDump = true;
|
||||
}
|
||||
}
|
||||
Backend.LOGGER.error("Errors compiling '" + name + "': \n" + errors);
|
||||
if (needsSourceDump) {
|
||||
// TODO: generated code gets its own "file"
|
||||
ErrorReporter.printLines(source);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ErrorBuilder parseCompilerError(String line) {
|
||||
try {
|
||||
ErrorBuilder error = ErrorBuilder.fromLogLine(this, line);
|
||||
if (error != null) {
|
||||
return error;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
package com.jozufozu.flywheel.backend.source;
|
||||
package com.jozufozu.flywheel.core.source;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.source.error.ErrorBuilder;
|
||||
import com.jozufozu.flywheel.backend.source.span.Span;
|
||||
import com.jozufozu.flywheel.core.source.error.ErrorBuilder;
|
||||
import com.jozufozu.flywheel.core.source.span.Span;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
@ -23,13 +22,13 @@ import net.minecraft.resources.ResourceLocation;
|
|||
public class FileResolution {
|
||||
|
||||
/**
|
||||
* Spans that have references that resolved to this.
|
||||
* Extra info about where this resolution is required. Includes ProgramSpecs and shader Spans.
|
||||
*/
|
||||
private final List<Span> foundSpans = new ArrayList<>();
|
||||
private final List<Consumer<ErrorBuilder>> extraCrashInfoProviders = new ArrayList<>();
|
||||
private final ResourceLocation fileLoc;
|
||||
private SourceFile file;
|
||||
|
||||
public FileResolution(ResourceLocation fileLoc) {
|
||||
FileResolution(ResourceLocation fileLoc) {
|
||||
this.fileLoc = fileLoc;
|
||||
}
|
||||
|
||||
|
@ -37,7 +36,10 @@ public class FileResolution {
|
|||
return fileLoc;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
@ -51,34 +53,44 @@ public class FileResolution {
|
|||
* @param span A span where this file is referenced.
|
||||
*/
|
||||
public FileResolution addSpan(Span span) {
|
||||
foundSpans.add(span);
|
||||
extraCrashInfoProviders.add(builder -> builder.pointAtFile(span.getSourceFile())
|
||||
.pointAt(span, 1));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void addSpec(ResourceLocation name) {
|
||||
extraCrashInfoProviders.add(builder -> builder.extra("needed by spec: " + name + ".json"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if this file actually resolves to something.
|
||||
*
|
||||
* <p>
|
||||
* Called after all files are loaded. If we can't find the file here, it doesn't exist.
|
||||
* </p>
|
||||
*
|
||||
* @return True if this file is resolved.
|
||||
*/
|
||||
void resolve(SourceFinder sources) {
|
||||
|
||||
try {
|
||||
boolean resolve(SourceFinder sources) {
|
||||
file = sources.findSource(fileLoc);
|
||||
} catch (RuntimeException error) {
|
||||
|
||||
if (file == null) {
|
||||
ErrorBuilder builder = ErrorBuilder.error(String.format("could not find source for file %s", fileLoc));
|
||||
// print the location of all places where this file was referenced
|
||||
for (Span span : foundSpans) {
|
||||
builder.pointAtFile(span.getSourceFile())
|
||||
.pointAt(span, 2);
|
||||
for (Consumer<ErrorBuilder> consumer : extraCrashInfoProviders) {
|
||||
consumer.accept(builder);
|
||||
}
|
||||
Backend.LOGGER.error(builder.build());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let the GC do its thing
|
||||
extraCrashInfoProviders.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void invalidate() {
|
||||
foundSpans.clear();
|
||||
extraCrashInfoProviders.clear();
|
||||
file = null;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
package com.jozufozu.flywheel.backend.source;
|
||||
package com.jozufozu.flywheel.core.source;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.MultimapBuilder;
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
|
||||
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
|
||||
import com.jozufozu.flywheel.core.source.parse.ShaderStruct;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.jozufozu.flywheel.core.source;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/**
|
||||
* Manages deferred file resolution.
|
||||
*
|
||||
* <p>
|
||||
* Interns all file names in shader sources and program specs, deduplicating the final lookups and allowing for more
|
||||
* dev-friendly error reporting.
|
||||
* </p>
|
||||
*
|
||||
* @see FileResolution
|
||||
*/
|
||||
public class Resolver {
|
||||
|
||||
public static final Resolver INSTANCE = new Resolver();
|
||||
|
||||
private final Map<ResourceLocation, FileResolution> resolutions = new HashMap<>();
|
||||
private boolean hasRun = false;
|
||||
|
||||
public FileResolution get(ResourceLocation file) {
|
||||
if (!hasRun) {
|
||||
return resolutions.computeIfAbsent(file, FileResolution::new);
|
||||
} else {
|
||||
// Lock the map after resolution has run.
|
||||
FileResolution fileResolution = resolutions.get(file);
|
||||
|
||||
// ...so crash immediately if the file isn't found.
|
||||
if (fileResolution == null) {
|
||||
throw new RuntimeException("could not find source for file: " + file);
|
||||
}
|
||||
|
||||
return fileResolution;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try and resolve all referenced source files, printing errors if any aren't found.
|
||||
*/
|
||||
public void run(SourceFinder sources) {
|
||||
boolean needsCrash = false;
|
||||
for (FileResolution resolution : resolutions.values()) {
|
||||
if (!resolution.resolve(sources)) {
|
||||
needsCrash = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsCrash) {
|
||||
throw new ShaderLoadingException("Failed to resolve all source files, see log for details");
|
||||
}
|
||||
|
||||
hasRun = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all FileResolutions.
|
||||
*
|
||||
* <p>
|
||||
* Called on resource reload.
|
||||
* </p>
|
||||
*/
|
||||
public void invalidate() {
|
||||
resolutions.values().forEach(FileResolution::invalidate);
|
||||
hasRun = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.jozufozu.flywheel.core.source;
|
||||
|
||||
public class ShaderLoadingException extends RuntimeException {
|
||||
|
||||
public ShaderLoadingException() {
|
||||
}
|
||||
|
||||
public ShaderLoadingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.backend.source;
|
||||
package com.jozufozu.flywheel.core.source;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -6,6 +6,8 @@ import java.util.Collection;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.jozufozu.flywheel.util.ResourceUtil;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
|
@ -51,13 +53,9 @@ public class ShaderSources implements SourceFinder {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public SourceFile findSource(ResourceLocation name) {
|
||||
SourceFile source = shaderSources.get(name);
|
||||
|
||||
if (source == null) {
|
||||
throw new ShaderLoadingException(String.format("shader '%s' does not exist", name));
|
||||
}
|
||||
|
||||
return source;
|
||||
return shaderSources.get(name);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.backend.source;
|
||||
package com.jozufozu.flywheel.core.source;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
@ -10,12 +10,12 @@ import java.util.regex.Pattern;
|
|||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.jozufozu.flywheel.backend.source.parse.Import;
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
|
||||
import com.jozufozu.flywheel.backend.source.span.ErrorSpan;
|
||||
import com.jozufozu.flywheel.backend.source.span.Span;
|
||||
import com.jozufozu.flywheel.backend.source.span.StringSpan;
|
||||
import com.jozufozu.flywheel.core.source.parse.Import;
|
||||
import com.jozufozu.flywheel.core.source.parse.ShaderFunction;
|
||||
import com.jozufozu.flywheel.core.source.parse.ShaderStruct;
|
||||
import com.jozufozu.flywheel.core.source.span.ErrorSpan;
|
||||
import com.jozufozu.flywheel.core.source.span.Span;
|
||||
import com.jozufozu.flywheel.core.source.span.StringSpan;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
@ -74,6 +74,23 @@ public class SourceFile {
|
|||
this.elided = elideSource(source, elisions).toString();
|
||||
}
|
||||
|
||||
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) {
|
||||
int begin = lines.getLineStart(line);
|
||||
int end = begin + lines.getLine(line).length();
|
||||
|
||||
while (begin < end && Character.isWhitespace(source.charAt(begin))) {
|
||||
begin++;
|
||||
}
|
||||
|
||||
return new StringSpan(this, lines.getCharPos(begin), lines.getCharPos(end));
|
||||
}
|
||||
|
||||
/**
|
||||
* Search this file and recursively search all imports to find a struct definition matching the given name.
|
||||
*
|
||||
|
@ -120,30 +137,23 @@ public class SourceFile {
|
|||
return "#use " + '"' + name + '"';
|
||||
}
|
||||
|
||||
public CharSequence generateFinalSource() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
generateFinalSource(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public void generateFinalSource(StringBuilder source) {
|
||||
public void generateFinalSource(FileIndex env, StringBuilder source) {
|
||||
for (Import include : imports) {
|
||||
SourceFile file = include.getFile();
|
||||
|
||||
if (file != null) file.generateFinalSource(source);
|
||||
if (file != null) file.generateFinalSource(env, source);
|
||||
}
|
||||
|
||||
source.append("#line ")
|
||||
.append(0)
|
||||
.append(' ')
|
||||
.append(env.getFileID(this))
|
||||
.append('\n');
|
||||
source.append(elided);
|
||||
}
|
||||
|
||||
public String printSource() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
builder.append("Source for shader '")
|
||||
.append(name)
|
||||
.append("':\n")
|
||||
.append(lines.printLinesWithNumbers());
|
||||
|
||||
return builder.toString();
|
||||
return "Source for shader '" + name + "':\n" + lines.printLinesWithNumbers();
|
||||
}
|
||||
|
||||
private static CharSequence elideSource(String source, List<Span> elisions) {
|
||||
|
@ -257,4 +267,8 @@ public class SourceFile {
|
|||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name.toString();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
package com.jozufozu.flywheel.backend.source;
|
||||
package com.jozufozu.flywheel.core.source;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
|
@ -8,5 +10,6 @@ import net.minecraft.resources.ResourceLocation;
|
|||
@FunctionalInterface
|
||||
public interface SourceFinder {
|
||||
|
||||
@Nullable
|
||||
SourceFile findSource(ResourceLocation name);
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
package com.jozufozu.flywheel.backend.source;
|
||||
package com.jozufozu.flywheel.core.source;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.jozufozu.flywheel.backend.source.span.CharPos;
|
||||
import com.jozufozu.flywheel.core.source.span.CharPos;
|
||||
import com.jozufozu.flywheel.util.StringUtil;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
|
@ -29,6 +29,48 @@ public class SourceLines {
|
|||
this.lines = getLines(source, lineStarts);
|
||||
}
|
||||
|
||||
public int getLineCount() {
|
||||
return lines.size();
|
||||
}
|
||||
|
||||
public String getLine(int lineNo) {
|
||||
return lines.get(lineNo);
|
||||
}
|
||||
|
||||
public int getLineStart(int lineNo) {
|
||||
|
||||
return lineStarts.getInt(lineNo);
|
||||
}
|
||||
|
||||
public CharPos getCharPos(int charPos) {
|
||||
int lineNo = 0;
|
||||
for (; lineNo < lineStarts.size(); lineNo++) {
|
||||
int ls = lineStarts.getInt(lineNo);
|
||||
|
||||
if (charPos < ls) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lineNo -= 1;
|
||||
|
||||
int lineStart = lineStarts.getInt(lineNo);
|
||||
|
||||
return new CharPos(charPos, lineNo, charPos - lineStart);
|
||||
}
|
||||
|
||||
public String printLinesWithNumbers() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0, linesSize = lines.size(); i < linesSize; i++) {
|
||||
builder.append(String.format("%1$4s: ", i + 1))
|
||||
.append(lines.get(i))
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the source for line breaks, recording the position of the first character of each line.
|
||||
* @param source
|
||||
|
@ -57,41 +99,4 @@ public class SourceLines {
|
|||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public CharPos getCharPos(int charPos) {
|
||||
int lineNo = 0;
|
||||
for (; lineNo < lineStarts.size(); lineNo++) {
|
||||
int ls = lineStarts.getInt(lineNo);
|
||||
|
||||
if (charPos < ls) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lineNo -= 1;
|
||||
|
||||
int lineStart = lineStarts.getInt(lineNo);
|
||||
|
||||
return new CharPos(charPos, lineNo, charPos - lineStart);
|
||||
}
|
||||
|
||||
public int getLineCount() {
|
||||
return lines.size();
|
||||
}
|
||||
|
||||
public String getLine(int lineNo) {
|
||||
return lines.get(lineNo);
|
||||
}
|
||||
|
||||
public String printLinesWithNumbers() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0, linesSize = lines.size(); i < linesSize; i++) {
|
||||
builder.append(String.format("%1$4s: ", i + 1))
|
||||
.append(lines.get(i))
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package com.jozufozu.flywheel.core.source.error;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.jozufozu.flywheel.core.source.FileIndex;
|
||||
import com.jozufozu.flywheel.core.source.SourceFile;
|
||||
import com.jozufozu.flywheel.core.source.SourceLines;
|
||||
import com.jozufozu.flywheel.core.source.error.lines.ErrorLine;
|
||||
import com.jozufozu.flywheel.core.source.error.lines.FileLine;
|
||||
import com.jozufozu.flywheel.core.source.error.lines.HeaderLine;
|
||||
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.TextLine;
|
||||
import com.jozufozu.flywheel.core.source.span.Span;
|
||||
import com.jozufozu.flywheel.util.FlwUtil;
|
||||
|
||||
public class ErrorBuilder {
|
||||
|
||||
private static final Pattern ERROR_LINE = Pattern.compile("(\\d+)\\((\\d+)\\) : (.*)");
|
||||
|
||||
private final List<ErrorLine> lines = new ArrayList<>();
|
||||
|
||||
public ErrorBuilder() {
|
||||
|
||||
}
|
||||
|
||||
public static ErrorBuilder error(CharSequence msg) {
|
||||
return new ErrorBuilder()
|
||||
.header(ErrorLevel.ERROR, msg);
|
||||
}
|
||||
|
||||
public static ErrorBuilder compError(CharSequence msg) {
|
||||
return new ErrorBuilder()
|
||||
.extra(msg);
|
||||
}
|
||||
|
||||
public static ErrorBuilder warn(CharSequence msg) {
|
||||
return new ErrorBuilder()
|
||||
.header(ErrorLevel.WARN, msg);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ErrorBuilder fromLogLine(FileIndex env, String s) {
|
||||
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));
|
||||
return ErrorBuilder.compError(msg)
|
||||
.pointAtFile(span.getSourceFile())
|
||||
.pointAt(span, 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public ErrorBuilder header(ErrorLevel level, CharSequence msg) {
|
||||
lines.add(new HeaderLine(level.toString(), msg));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ErrorBuilder extra(CharSequence msg) {
|
||||
lines.add(new TextLine(msg.toString()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ErrorBuilder pointAtFile(SourceFile file) {
|
||||
lines.add(new FileLine(file.name.toString()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ErrorBuilder hintIncludeFor(@Nullable Span span, CharSequence msg) {
|
||||
if (span == null) return this;
|
||||
SourceFile sourceFile = span.getSourceFile();
|
||||
|
||||
String builder = "add " + sourceFile.importStatement() + ' ' + msg + "\n defined here:";
|
||||
|
||||
header(ErrorLevel.HINT, builder);
|
||||
|
||||
return this.pointAtFile(sourceFile)
|
||||
.pointAt(span, 0);
|
||||
}
|
||||
|
||||
public ErrorBuilder pointAt(Span span, int ctxLines) {
|
||||
|
||||
if (span.lines() == 1) {
|
||||
SourceLines lines = span.getSourceFile().lines;
|
||||
|
||||
int spanLine = span.firstLine();
|
||||
|
||||
int firstLine = Math.max(0, spanLine - ctxLines);
|
||||
int lastLine = Math.min(lines.getLineCount(), spanLine + ctxLines);
|
||||
|
||||
int firstCol = span.getStart()
|
||||
.col();
|
||||
int lastCol = span.getEnd()
|
||||
.col();
|
||||
|
||||
for (int i = firstLine; i <= lastLine; i++) {
|
||||
CharSequence line = lines.getLine(i);
|
||||
|
||||
this.lines.add(SourceLine.numbered(i + 1, line.toString()));
|
||||
|
||||
if (i == spanLine) {
|
||||
this.lines.add(new SpanHighlightLine(firstCol, lastCol));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public CharSequence build() {
|
||||
|
||||
int maxLength = -1;
|
||||
for (ErrorLine line : lines) {
|
||||
int length = line.neededMargin();
|
||||
|
||||
if (length > maxLength) maxLength = length;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append('\n');
|
||||
for (ErrorLine line : lines) {
|
||||
int length = line.neededMargin();
|
||||
|
||||
builder.append(FlwUtil.repeatChar(' ', maxLength - length))
|
||||
.append(line.build())
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
package com.jozufozu.flywheel.backend.source.error;
|
||||
package com.jozufozu.flywheel.core.source.error;
|
||||
|
||||
public enum Level {
|
||||
public enum ErrorLevel {
|
||||
WARN("warn"),
|
||||
ERROR("error"),
|
||||
HINT("hint"),
|
||||
;
|
||||
|
||||
private final String error;
|
||||
|
||||
Level(String error) {
|
||||
ErrorLevel(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
|
@ -1,12 +1,15 @@
|
|||
package com.jozufozu.flywheel.backend.source.error;
|
||||
package com.jozufozu.flywheel.core.source.error;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.jozufozu.flywheel.Flywheel;
|
||||
import com.jozufozu.flywheel.backend.Backend;
|
||||
import com.jozufozu.flywheel.backend.source.SourceFile;
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
|
||||
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
|
||||
import com.jozufozu.flywheel.backend.source.span.Span;
|
||||
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.util.FlwUtil;
|
||||
|
||||
public class ErrorReporter {
|
||||
|
||||
|
@ -42,7 +45,7 @@ public class ErrorReporter {
|
|||
|
||||
ErrorBuilder error = ErrorBuilder.error(msg)
|
||||
.pointAtFile(file)
|
||||
.pointAt(vertexName, 2)
|
||||
.pointAt(vertexName, 1)
|
||||
.hintIncludeFor(span.orElse(null), hint);
|
||||
|
||||
Backend.LOGGER.error(error.build());
|
||||
|
@ -64,4 +67,29 @@ public class ErrorReporter {
|
|||
|
||||
Backend.LOGGER.error(error.build());
|
||||
}
|
||||
|
||||
public static void printLines(CharSequence source) {
|
||||
String string = source.toString();
|
||||
|
||||
List<String> lines = string.lines()
|
||||
.toList();
|
||||
|
||||
int size = lines.size();
|
||||
|
||||
int maxWidth = FlwUtil.numDigits(size) + 1;
|
||||
|
||||
StringBuilder builder = new StringBuilder().append('\n');
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
|
||||
builder.append(i)
|
||||
.append(FlwUtil.repeatChar(' ', maxWidth - FlwUtil.numDigits(i)))
|
||||
.append("| ")
|
||||
.append(lines.get(i))
|
||||
.append('\n');
|
||||
}
|
||||
|
||||
Flywheel.LOGGER.error(builder.toString());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.backend.source.error.lines;
|
||||
package com.jozufozu.flywheel.core.source.error.lines;
|
||||
|
||||
public enum Divider {
|
||||
BAR(" | "),
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.backend.source.error.lines;
|
||||
package com.jozufozu.flywheel.core.source.error.lines;
|
||||
|
||||
public interface ErrorLine {
|
||||
|
||||
|
@ -14,6 +14,10 @@ public interface ErrorLine {
|
|||
return left() + divider() + right();
|
||||
}
|
||||
|
||||
String left();
|
||||
String right();
|
||||
default String left() {
|
||||
return "";
|
||||
}
|
||||
default String right() {
|
||||
return "";
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.backend.source.error.lines;
|
||||
package com.jozufozu.flywheel.core.source.error.lines;
|
||||
|
||||
public record FileLine(String fileName) implements ErrorLine {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.backend.source.error.lines;
|
||||
package com.jozufozu.flywheel.core.source.error.lines;
|
||||
|
||||
public record HeaderLine(String level, CharSequence message) implements ErrorLine {
|
||||
|
||||
|
@ -11,14 +11,4 @@ public record HeaderLine(String level, CharSequence message) implements ErrorLin
|
|||
public String build() {
|
||||
return level + ": " + message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String left() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String right() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package com.jozufozu.flywheel.backend.source.error.lines;
|
||||
package com.jozufozu.flywheel.core.source.error.lines;
|
||||
|
||||
public record SourceLine(String number, String line) implements ErrorLine {
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue