Merge branch '1.20.1/dev' into 1.21.1/dev

# Conflicts:
#	common/src/api/java/dev/engine_room/flywheel/api/Flywheel.java
#	common/src/main/java/dev/engine_room/flywheel/impl/event/RenderContextImpl.java
#	common/src/main/java/dev/engine_room/flywheel/impl/mixin/LevelRendererMixin.java
#	fabric/src/lib/java/dev/engine_room/flywheel/lib/model/baked/MeshEmitter.java
#	fabric/src/main/java/dev/engine_room/flywheel/impl/FabricFlwConfig.java
#	forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java
#	neoforge/src/main/java/dev/engine_room/flywheel/impl/NeoForgeFlwConfig.java
This commit is contained in:
Jozufozu 2025-02-26 22:08:43 -08:00
commit 7adc132a0a
85 changed files with 1628 additions and 1014 deletions

View file

@ -1,14 +1,11 @@
package dev.engine_room.flywheel.api;
import net.minecraft.resources.ResourceLocation;
public final class Flywheel {
/**
* The mod ID and resource namespace of Flywheel.
*/
public static final String ID = "flywheel";
private Flywheel() {
}
public static ResourceLocation rl(String path) {
return ResourceLocation.fromNamespaceAndPath(ID, path);
}
}

View file

@ -5,10 +5,8 @@ import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Range;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.client.Camera;
@ -22,14 +20,13 @@ public interface Engine {
/**
* Create a visualization context that will be used to create visuals of the given type.
*
* @param visualType The type of visual.
* @return A new visualization context.
*/
VisualizationContext createVisualizationContext(VisualType visualType);
VisualizationContext createVisualizationContext();
/**
* Create a plan that will start execution after the start of the level render and
* finish execution before {@link #setupRender} is called.
* finish execution before {@link #render} is called.
*
* @return A new plan.
*/
@ -60,32 +57,20 @@ public interface Engine {
void onLightUpdate(SectionPos sectionPos, LightLayer layer);
/**
* Set up rendering for the current level render.
* Render all instances necessary for the given visual type.
*
* <p>This method is guaranteed to be called after
* {@linkplain #createFramePlan() the frame plan} has finished execution and before
* {@link #render} and {@link #renderCrumbling} are called. This method is guaranteed to
* be called on the render thread.
* {@link #renderCrumbling} are called. This method is guaranteed to be called on the render thread.
*
* @param context The context for the current level render.
*/
void setupRender(RenderContext context);
/**
* Render all instances necessary for the given visual type.
*
* <p>This method is guaranteed to be called after {@link #setupRender} for the current
* level render. This method is guaranteed to be called on the render thread.
*
* @param context The context for the current level render.
* @param visualType The type of visual.
*/
void render(RenderContext context, VisualType visualType);
void render(RenderContext context);
/**
* Render the given instances as a crumbling overlay.
*
* <p>This method is guaranteed to be called after {@link #setupRender} for the current
* <p>This method is guaranteed to be called after {@link #render} for the current
* level render. This method is guaranteed to be called on the render thread.
*
* @param context The context for the current level render.

View file

@ -1,4 +1,4 @@
package dev.engine_room.flywheel.api;
package dev.engine_room.flywheel.api.backend;
import org.joml.Matrix4fc;

View file

@ -1,10 +1,81 @@
package dev.engine_room.flywheel.api.material;
public enum Transparency {
/**
* No blending. Used for solid and cutout geometry.
*/
OPAQUE,
/**
* Additive blending.
*
* <p>Each fragment blends color and alpha with the following equation:
* <pre>
* {@code
* out = src + dst
* }
* </pre>
*/
ADDITIVE,
/**
* Lightning transparency.
*
* <p>Each fragment blends color and alpha with the following equation:
* <pre>
* {@code
* out = src * alpha_src + dst
* }
* </pre>
*/
LIGHTNING,
/**
* Glint transparency. Used for the enchantment effect.
*
* <p>Each fragment blends with the following equations:
* <pre>
* {@code
* color_out = color_src^2 + color_dst
* alpha_out = alpha_dst
* }
* </pre>
*/
GLINT,
/**
* Crumbling transparency. Used for the block breaking overlay.
*
* <p>Each fragment blends with the following equations:
* <pre>
* {@code
* color_out = 2 * color_src * color_dst
* alpha_out = alpha_src
* }
* </pre>
*/
CRUMBLING,
/**
* Translucent transparency.
*
* <p>Each fragment blends with the following equations:
* <pre>
* {@code
* color_out = color_src * alpha_src + color_dst * (1 - alpha_src)
* alpha_out = alpha_src + alpha_dst * (1 - alpha_src)
* }
* </pre>
*/
TRANSLUCENT,
/**
* If supported by the backend, this mode will use OIT that approximates {@code TRANSLUCENT} transparency.
*
* <p>If a backend does not support OIT, it must treat this the same as {@code TRANSLUCENT}.
*
* <p>It is recommended to use this option when possible, though for cases where blend modes are used as an
* overlay against solid geometry the order dependent modes are preferred.
*/
ORDER_INDEPENDENT,
}

View file

@ -1,7 +0,0 @@
package dev.engine_room.flywheel.api.visualization;
public enum VisualType {
BLOCK_ENTITY,
ENTITY,
EFFECT;
}

View file

@ -5,7 +5,7 @@ import java.util.SortedSet;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.backend.RenderContext;
import dev.engine_room.flywheel.api.internal.FlwApiLink;
import dev.engine_room.flywheel.api.visual.Effect;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
@ -47,14 +47,31 @@ public interface VisualizationManager {
@ApiStatus.NonExtendable
interface RenderDispatcher {
/**
* Prepare visuals for render.
*
* <p>Guaranteed to be called before {@link #afterEntities} and {@link #beforeCrumbling}.
* <br>Guaranteed to be called after the render thread has processed all light updates.
* <br>The caller is otherwise free to choose an invocation site, but it is recommended to call
* this as early as possible to give the VisualizationManager time to process things off-thread.
*/
void onStartLevelRender(RenderContext ctx);
void afterBlockEntities(RenderContext ctx);
/**
* Render instances.
*
* <p>Guaranteed to be called after {@link #onStartLevelRender} and before {@link #beforeCrumbling}.
* <br>The caller is otherwise free to choose an invocation site, but it is recommended to call
* this between rendering entities and block entities.
*/
void afterEntities(RenderContext ctx);
/**
* Render crumbling block entities.
*
* <p>Guaranteed to be called after {@link #onStartLevelRender} and {@link #afterEntities}
* @param destructionProgress The destruction progress map from {@link net.minecraft.client.renderer.LevelRenderer LevelRenderer}.
*/
void beforeCrumbling(RenderContext ctx, Long2ObjectMap<SortedSet<BlockDestructionProgress>> destructionProgress);
void afterParticles(RenderContext ctx);
}
}

View file

@ -1,14 +1,15 @@
package dev.engine_room.flywheel.backend;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.compile.InstancingPrograms;
import dev.engine_room.flywheel.backend.engine.EngineImpl;
import dev.engine_room.flywheel.backend.engine.indirect.IndirectDrawManager;
import dev.engine_room.flywheel.backend.engine.instancing.InstancedDrawManager;
import dev.engine_room.flywheel.backend.gl.Driver;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.lib.backend.SimpleBackend;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import dev.engine_room.flywheel.lib.util.ShadersModHelper;
public final class Backends {
@ -19,16 +20,25 @@ public final class Backends {
.engineFactory(level -> new EngineImpl(level, new InstancedDrawManager(InstancingPrograms.get()), 256))
.priority(500)
.supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHelper.isShaderPackInUse())
.register(Flywheel.rl("instancing"));
.register(ResourceUtil.rl("instancing"));
/**
* Use Compute shaders to cull instances.
*/
public static final Backend INDIRECT = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(level, new IndirectDrawManager(IndirectPrograms.get()), 256))
.priority(1000)
.priority(() -> {
// Read from GlCompat in these provider because loading GlCompat
// at the same time the backends are registered causes GlCapabilities to be null.
if (GlCompat.DRIVER == Driver.INTEL) {
// Intel has very poor performance with indirect rendering, and on top of that has graphics bugs
return 1;
} else {
return 1000;
}
})
.supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHelper.isShaderPackInUse())
.register(Flywheel.rl("indirect"));
.register(ResourceUtil.rl("indirect"));
private Backends() {
}

View file

@ -2,11 +2,11 @@ package dev.engine_room.flywheel.backend;
import java.util.List;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.layout.FloatRepr;
import dev.engine_room.flywheel.api.layout.Layout;
import dev.engine_room.flywheel.api.layout.LayoutBuilder;
import dev.engine_room.flywheel.backend.gl.array.VertexAttribute;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import dev.engine_room.flywheel.lib.vertex.FullVertexView;
import dev.engine_room.flywheel.lib.vertex.VertexView;
import net.minecraft.resources.ResourceLocation;
@ -24,7 +24,7 @@ public final class InternalVertex {
public static final List<VertexAttribute> ATTRIBUTES = LayoutAttributes.attributes(LAYOUT);
public static final int STRIDE = LAYOUT.byteSize();
public static final ResourceLocation LAYOUT_SHADER = Flywheel.rl("internal/vertex_input.vert");
public static final ResourceLocation LAYOUT_SHADER = ResourceUtil.rl("internal/vertex_input.vert");
private InternalVertex() {
}

View file

@ -0,0 +1,52 @@
package dev.engine_room.flywheel.backend;
import java.io.IOException;
import org.jetbrains.annotations.UnknownNullability;
import org.lwjgl.opengl.GL32;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.engine_room.flywheel.backend.gl.GlTextureUnit;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
public class NoiseTextures {
public static final ResourceLocation NOISE_TEXTURE = ResourceUtil.rl("textures/flywheel/noise/blue.png");
@UnknownNullability
public static DynamicTexture BLUE_NOISE;
public static void reload(ResourceManager manager) {
if (BLUE_NOISE != null) {
BLUE_NOISE.close();
BLUE_NOISE = null;
}
var optional = manager.getResource(NOISE_TEXTURE);
if (optional.isEmpty()) {
return;
}
try (var is = optional.get()
.open()) {
var image = NativeImage.read(NativeImage.Format.LUMINANCE, is);
BLUE_NOISE = new DynamicTexture(image);
GlTextureUnit.T0.makeActive();
BLUE_NOISE.bind();
NoiseTextures.BLUE_NOISE.setFilter(true, false);
RenderSystem.texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_S, GL32.GL_REPEAT);
RenderSystem.texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_T, GL32.GL_REPEAT);
RenderSystem.bindTexture(0);
} catch (IOException e) {
}
}
}

View file

@ -10,4 +10,8 @@ public class Samplers {
public static final GlTextureUnit INSTANCE_BUFFER = GlTextureUnit.T4;
public static final GlTextureUnit LIGHT_LUT = GlTextureUnit.T5;
public static final GlTextureUnit LIGHT_SECTIONS = GlTextureUnit.T6;
public static final GlTextureUnit DEPTH_RANGE = GlTextureUnit.T7;
public static final GlTextureUnit COEFFICIENTS = GlTextureUnit.T8;
public static final GlTextureUnit NOISE = GlTextureUnit.T9;
}

View file

@ -8,13 +8,14 @@ import org.slf4j.LoggerFactory;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.backend.glsl.ShaderSources;
import dev.engine_room.flywheel.backend.glsl.SourceComponent;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
public final class FlwPrograms {
public static final Logger LOGGER = LoggerFactory.getLogger(Flywheel.ID + "/backend/shaders");
private static final ResourceLocation COMPONENTS_HEADER_FRAG = Flywheel.rl("internal/components_header.frag");
private static final ResourceLocation COMPONENTS_HEADER_FRAG = ResourceUtil.rl("internal/components_header.frag");
public static ShaderSources SOURCES;

View file

@ -6,7 +6,6 @@ import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.backend.compile.component.InstanceStructComponent;
@ -25,13 +24,12 @@ import dev.engine_room.flywheel.lib.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
public class IndirectPrograms extends AtomicReferenceCounted {
private static final ResourceLocation CULL_SHADER_API_IMPL = Flywheel.rl("internal/indirect/cull_api_impl.glsl");
private static final ResourceLocation CULL_SHADER_MAIN = Flywheel.rl("internal/indirect/cull.glsl");
private static final ResourceLocation APPLY_SHADER_MAIN = Flywheel.rl("internal/indirect/apply.glsl");
private static final ResourceLocation SCATTER_SHADER_MAIN = Flywheel.rl("internal/indirect/scatter.glsl");
private static final ResourceLocation DOWNSAMPLE_FIRST = Flywheel.rl("internal/indirect/downsample_first.glsl");
private static final ResourceLocation DOWNSAMPLE_SECOND = Flywheel.rl("internal/indirect/downsample_second.glsl");
public static final List<ResourceLocation> UTIL_SHADERS = List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN, DOWNSAMPLE_FIRST, DOWNSAMPLE_SECOND);
private static final ResourceLocation CULL_SHADER_API_IMPL = ResourceUtil.rl("internal/indirect/cull_api_impl.glsl");
private static final ResourceLocation CULL_SHADER_MAIN = ResourceUtil.rl("internal/indirect/cull.glsl");
private static final ResourceLocation APPLY_SHADER_MAIN = ResourceUtil.rl("internal/indirect/apply.glsl");
private static final ResourceLocation SCATTER_SHADER_MAIN = ResourceUtil.rl("internal/indirect/scatter.glsl");
private static final ResourceLocation DOWNSAMPLE_FIRST = ResourceUtil.rl("internal/indirect/downsample_first.glsl");
private static final ResourceLocation DOWNSAMPLE_SECOND = ResourceUtil.rl("internal/indirect/downsample_second.glsl");
private static final Compile<InstanceType<?>> CULL = new Compile<>();
private static final Compile<ResourceLocation> UTIL = new Compile<>();
@ -45,11 +43,13 @@ public class IndirectPrograms extends AtomicReferenceCounted {
private final PipelineCompiler pipeline;
private final CompilationHarness<InstanceType<?>> culling;
private final CompilationHarness<ResourceLocation> utils;
private final OitPrograms oitPrograms;
private IndirectPrograms(PipelineCompiler pipeline, CompilationHarness<InstanceType<?>> culling, CompilationHarness<ResourceLocation> utils) {
private IndirectPrograms(PipelineCompiler pipeline, CompilationHarness<InstanceType<?>> culling, CompilationHarness<ResourceLocation> utils, OitPrograms oitPrograms) {
this.pipeline = pipeline;
this.culling = culling;
this.utils = utils;
this.oitPrograms = oitPrograms;
}
private static List<String> getExtensions(GlslVersion glslVersion) {
@ -59,9 +59,11 @@ public class IndirectPrograms extends AtomicReferenceCounted {
}
if (glslVersion.compareTo(GlslVersion.V420) < 0) {
extensions.add("GL_ARB_shading_language_420pack");
extensions.add("GL_ARB_shader_image_load_store");
}
if (glslVersion.compareTo(GlslVersion.V430) < 0) {
extensions.add("GL_ARB_shader_storage_buffer_object");
extensions.add("GL_ARB_shader_image_size");
}
if (glslVersion.compareTo(GlslVersion.V460) < 0) {
extensions.add("GL_ARB_shader_draw_parameters");
@ -88,8 +90,9 @@ public class IndirectPrograms extends AtomicReferenceCounted {
var pipelineCompiler = PipelineCompiler.create(sources, Pipelines.INDIRECT, vertexComponents, fragmentComponents, EXTENSIONS);
var cullingCompiler = createCullingCompiler(sources);
var utilCompiler = createUtilCompiler(sources);
var fullscreenCompiler = OitPrograms.createFullscreenCompiler(sources);
IndirectPrograms newInstance = new IndirectPrograms(pipelineCompiler, cullingCompiler, utilCompiler);
IndirectPrograms newInstance = new IndirectPrograms(pipelineCompiler, cullingCompiler, utilCompiler, fullscreenCompiler);
setInstance(newInstance);
}
@ -148,8 +151,8 @@ public class IndirectPrograms extends AtomicReferenceCounted {
setInstance(null);
}
public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader, Material material) {
return pipeline.get(instanceType, contextShader, material);
public GlProgram getIndirectProgram(InstanceType<?> instanceType, ContextShader contextShader, Material material, PipelineCompiler.OitMode oit) {
return pipeline.get(instanceType, contextShader, material, oit);
}
public GlProgram getCullingProgram(InstanceType<?> instanceType) {
@ -172,10 +175,15 @@ public class IndirectPrograms extends AtomicReferenceCounted {
return utils.get(DOWNSAMPLE_SECOND);
}
public OitPrograms oitPrograms() {
return oitPrograms;
}
@Override
protected void _delete() {
pipeline.delete();
culling.delete();
utils.delete();
oitPrograms.delete();
}
}

View file

@ -23,8 +23,11 @@ public class InstancingPrograms extends AtomicReferenceCounted {
private final PipelineCompiler pipeline;
private InstancingPrograms(PipelineCompiler pipeline) {
private final OitPrograms oitPrograms;
private InstancingPrograms(PipelineCompiler pipeline, OitPrograms oitPrograms) {
this.pipeline = pipeline;
this.oitPrograms = oitPrograms;
}
private static List<String> getExtensions(GlslVersion glslVersion) {
@ -41,7 +44,8 @@ public class InstancingPrograms extends AtomicReferenceCounted {
}
var pipelineCompiler = PipelineCompiler.create(sources, Pipelines.INSTANCING, vertexComponents, fragmentComponents, EXTENSIONS);
InstancingPrograms newInstance = new InstancingPrograms(pipelineCompiler);
var fullscreen = OitPrograms.createFullscreenCompiler(sources);
InstancingPrograms newInstance = new InstancingPrograms(pipelineCompiler, fullscreen);
setInstance(newInstance);
}
@ -69,12 +73,17 @@ public class InstancingPrograms extends AtomicReferenceCounted {
setInstance(null);
}
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, Material material) {
return pipeline.get(instanceType, contextShader, material);
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, Material material, PipelineCompiler.OitMode mode) {
return pipeline.get(instanceType, contextShader, material, mode);
}
public OitPrograms oitPrograms() {
return oitPrograms;
}
@Override
protected void _delete() {
pipeline.delete();
oitPrograms.delete();
}
}

View file

@ -0,0 +1,67 @@
package dev.engine_room.flywheel.backend.compile;
import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.compile.core.CompilationHarness;
import dev.engine_room.flywheel.backend.compile.core.Compile;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.backend.gl.GlTextureUnit;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.backend.gl.shader.ShaderType;
import dev.engine_room.flywheel.backend.glsl.GlslVersion;
import dev.engine_room.flywheel.backend.glsl.ShaderSources;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
public class OitPrograms {
private static final ResourceLocation FULLSCREEN = ResourceUtil.rl("internal/fullscreen.vert");
static final ResourceLocation OIT_COMPOSITE = ResourceUtil.rl("internal/oit_composite.frag");
static final ResourceLocation OIT_DEPTH = ResourceUtil.rl("internal/oit_depth.frag");
private static final Compile<ResourceLocation> COMPILE = new Compile<>();
private final CompilationHarness<ResourceLocation> harness;
public OitPrograms(CompilationHarness<ResourceLocation> harness) {
this.harness = harness;
}
public static OitPrograms createFullscreenCompiler(ShaderSources sources) {
var harness = COMPILE.program()
.link(COMPILE.shader(GlCompat.MAX_GLSL_VERSION, ShaderType.VERTEX)
.nameMapper($ -> "fullscreen/fullscreen")
.withResource(FULLSCREEN))
.link(COMPILE.shader(GlCompat.MAX_GLSL_VERSION, ShaderType.FRAGMENT)
.nameMapper(rl -> "fullscreen/" + ResourceUtil.toDebugFileNameNoExtension(rl))
.onCompile((rl, compilation) -> {
if (GlCompat.MAX_GLSL_VERSION.compareTo(GlslVersion.V400) < 0) {
// Need to define FMA for the wavelet calculations
compilation.define("fma(a, b, c) ((a) * (b) + (c))");
}
})
.withResource(s -> s))
.postLink((key, program) -> {
program.bind();
Uniforms.setUniformBlockBindings(program);
program.setSamplerBinding("_flw_accumulate", GlTextureUnit.T0);
program.setSamplerBinding("_flw_depthRange", Samplers.DEPTH_RANGE);
program.setSamplerBinding("_flw_coefficients", Samplers.COEFFICIENTS);
GlProgram.unbind();
})
.harness("fullscreen", sources);
return new OitPrograms(harness);
}
public GlProgram getOitCompositeProgram() {
return harness.get(OitPrograms.OIT_COMPOSITE);
}
public GlProgram getOitDepthProgram() {
return harness.get(OitPrograms.OIT_DEPTH);
}
public void delete() {
harness.delete();
}
}

View file

@ -6,7 +6,6 @@ import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.LightShader;
import dev.engine_room.flywheel.api.material.Material;
@ -24,6 +23,7 @@ import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.backend.gl.shader.ShaderType;
import dev.engine_room.flywheel.backend.glsl.GlslVersion;
import dev.engine_room.flywheel.backend.glsl.ShaderSources;
import dev.engine_room.flywheel.backend.glsl.SourceComponent;
import dev.engine_room.flywheel.backend.glsl.generate.FnSignature;
@ -40,8 +40,8 @@ public final class PipelineCompiler {
private static UberShaderComponent FOG;
private static UberShaderComponent CUTOUT;
private static final ResourceLocation API_IMPL_VERT = Flywheel.rl("internal/api_impl.vert");
private static final ResourceLocation API_IMPL_FRAG = Flywheel.rl("internal/api_impl.frag");
private static final ResourceLocation API_IMPL_VERT = ResourceUtil.rl("internal/api_impl.vert");
private static final ResourceLocation API_IMPL_FRAG = ResourceUtil.rl("internal/api_impl.frag");
private final CompilationHarness<PipelineProgramKey> harness;
@ -50,7 +50,7 @@ public final class PipelineCompiler {
ALL.add(this);
}
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, Material material) {
public GlProgram get(InstanceType<?> instanceType, ContextShader contextShader, Material material, OitMode oit) {
var light = material.light();
var cutout = material.cutout();
var shaders = material.shaders();
@ -66,7 +66,7 @@ public final class PipelineCompiler {
MaterialShaderIndices.cutoutSources()
.index(cutout.source());
return harness.get(new PipelineProgramKey(instanceType, contextShader, light, shaders, cutout != CutoutShaders.OFF, FrameUniforms.debugOn()));
return harness.get(new PipelineProgramKey(instanceType, contextShader, light, shaders, cutout != CutoutShaders.OFF, FrameUniforms.debugOn(), oit));
}
public void delete() {
@ -96,6 +96,12 @@ public final class PipelineCompiler {
return "pipeline/" + pipeline.compilerMarker() + "/" + instance + "/" + material + "_" + context + debug;
})
.requireExtensions(extensions)
.onCompile((rl, compilation) -> {
if (GlCompat.MAX_GLSL_VERSION.compareTo(GlslVersion.V400) < 0 && !extensions.contains("GL_ARB_gpu_shader5")) {
// Only define fma if it wouldn't be declared by gpu shader 5
compilation.define("fma(a, b, c) ((a) * (b) + (c))");
}
})
.onCompile((key, comp) -> key.contextShader()
.onCompile(comp))
.onCompile((key, comp) -> BackendConfig.INSTANCE.lightSmoothness()
@ -128,10 +134,17 @@ public final class PipelineCompiler {
.source());
var debug = key.debugEnabled() ? "_debug" : "";
var cutout = key.useCutout() ? "_cutout" : "";
return "pipeline/" + pipeline.compilerMarker() + "/frag/" + material + "/" + light + "_" + context + cutout + debug;
var oit = key.oit().name;
return "pipeline/" + pipeline.compilerMarker() + "/frag/" + material + "/" + light + "_" + context + cutout + debug + oit;
})
.requireExtensions(extensions)
.enableExtension("GL_ARB_conservative_depth")
.onCompile((rl, compilation) -> {
if (GlCompat.MAX_GLSL_VERSION.compareTo(GlslVersion.V400) < 0 && !extensions.contains("GL_ARB_gpu_shader5")) {
// Only define fma if it wouldn't be declared by gpu shader 5
compilation.define("fma(a, b, c) ((a) * (b) + (c))");
}
})
.onCompile((key, comp) -> key.contextShader()
.onCompile(comp))
.onCompile((key, comp) -> BackendConfig.INSTANCE.lightSmoothness()
@ -146,6 +159,12 @@ public final class PipelineCompiler {
comp.define("_FLW_USE_DISCARD");
}
})
.onCompile((key, comp) -> {
if (key.oit() != OitMode.OFF) {
comp.define("_FLW_OIT");
comp.define(key.oit().define);
}
})
.withResource(API_IMPL_FRAG)
.withResource(key -> key.materialShaders()
.fragmentSource())
@ -171,6 +190,9 @@ public final class PipelineCompiler {
program.setSamplerBinding("flw_diffuseTex", Samplers.DIFFUSE);
program.setSamplerBinding("flw_overlayTex", Samplers.OVERLAY);
program.setSamplerBinding("flw_lightTex", Samplers.LIGHT);
program.setSamplerBinding("_flw_depthRange", Samplers.DEPTH_RANGE);
program.setSamplerBinding("_flw_coefficients", Samplers.COEFFICIENTS);
program.setSamplerBinding("_flw_blueNoise", Samplers.NOISE);
pipeline.onLink()
.accept(program);
key.contextShader()
@ -184,7 +206,7 @@ public final class PipelineCompiler {
}
public static void createFogComponent() {
FOG = UberShaderComponent.builder(Flywheel.rl("fog"))
FOG = UberShaderComponent.builder(ResourceUtil.rl("fog"))
.materialSources(MaterialShaderIndices.fogSources()
.all())
.adapt(FnSignature.create()
@ -197,7 +219,7 @@ public final class PipelineCompiler {
}
private static void createCutoutComponent() {
CUTOUT = UberShaderComponent.builder(Flywheel.rl("cutout"))
CUTOUT = UberShaderComponent.builder(ResourceUtil.rl("cutout"))
.materialSources(MaterialShaderIndices.cutoutSources()
.all())
.adapt(FnSignature.create()
@ -217,6 +239,23 @@ public final class PipelineCompiler {
* @param light The light shader to use.
*/
public record PipelineProgramKey(InstanceType<?> instanceType, ContextShader contextShader, LightShader light,
MaterialShaders materialShaders, boolean useCutout, boolean debugEnabled) {
MaterialShaders materialShaders, boolean useCutout, boolean debugEnabled,
OitMode oit) {
}
public enum OitMode {
OFF("", ""),
DEPTH_RANGE("_FLW_DEPTH_RANGE", "_depth_range"),
GENERATE_COEFFICIENTS("_FLW_COLLECT_COEFFS", "_generate_coefficients"),
EVALUATE("_FLW_EVALUATE", "_resolve"),
;
public final String define;
public final String name;
OitMode(String define, String name) {
this.define = define;
this.name = name;
}
}
}

View file

@ -1,15 +1,15 @@
package dev.engine_room.flywheel.backend.compile;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.compile.component.BufferTextureInstanceComponent;
import dev.engine_room.flywheel.backend.compile.component.SsboInstanceComponent;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
public final class Pipelines {
public static final Pipeline INSTANCING = Pipeline.builder()
.compilerMarker("instancing")
.vertexMain(Flywheel.rl("internal/instancing/main.vert"))
.fragmentMain(Flywheel.rl("internal/instancing/main.frag"))
.vertexMain(ResourceUtil.rl("internal/instancing/main.vert"))
.fragmentMain(ResourceUtil.rl("internal/instancing/main.frag"))
.assembler(BufferTextureInstanceComponent::new)
.onLink(program -> {
program.setSamplerBinding("_flw_instances", Samplers.INSTANCE_BUFFER);
@ -20,8 +20,8 @@ public final class Pipelines {
public static final Pipeline INDIRECT = Pipeline.builder()
.compilerMarker("indirect")
.vertexMain(Flywheel.rl("internal/indirect/main.vert"))
.fragmentMain(Flywheel.rl("internal/indirect/main.frag"))
.vertexMain(ResourceUtil.rl("internal/indirect/main.vert"))
.fragmentMain(ResourceUtil.rl("internal/indirect/main.frag"))
.assembler(SsboInstanceComponent::new)
.onLink($ -> {
})

View file

@ -2,7 +2,6 @@ package dev.engine_room.flywheel.backend.compile.component;
import java.util.ArrayList;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.layout.Layout;
import dev.engine_room.flywheel.backend.glsl.generate.FnSignature;
@ -11,6 +10,7 @@ import dev.engine_room.flywheel.backend.glsl.generate.GlslBuilder;
import dev.engine_room.flywheel.backend.glsl.generate.GlslExpr;
import dev.engine_room.flywheel.backend.glsl.generate.GlslStmt;
import dev.engine_room.flywheel.lib.math.MoreMath;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
public class BufferTextureInstanceComponent extends InstanceAssemblerComponent {
private static final String[] SWIZZLE_SELECTORS = { "x", "y", "z", "w" };
@ -21,7 +21,7 @@ public class BufferTextureInstanceComponent extends InstanceAssemblerComponent {
@Override
public String name() {
return Flywheel.rl("buffer_texture_instance_assembler").toString();
return ResourceUtil.rl("buffer_texture_instance_assembler").toString();
}
@Override

View file

@ -3,12 +3,12 @@ package dev.engine_room.flywheel.backend.compile.component;
import java.util.Collection;
import java.util.Collections;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.layout.Layout;
import dev.engine_room.flywheel.backend.compile.LayoutInterpreter;
import dev.engine_room.flywheel.backend.glsl.SourceComponent;
import dev.engine_room.flywheel.backend.glsl.generate.GlslBuilder;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
public class InstanceStructComponent implements SourceComponent {
private static final String STRUCT_NAME = "FlwInstance";
@ -21,7 +21,7 @@ public class InstanceStructComponent implements SourceComponent {
@Override
public String name() {
return Flywheel.rl("instance_struct").toString();
return ResourceUtil.rl("instance_struct").toString();
}
@Override

View file

@ -2,7 +2,6 @@ package dev.engine_room.flywheel.backend.compile.component;
import java.util.ArrayList;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.layout.Layout;
import dev.engine_room.flywheel.backend.engine.indirect.BufferBindings;
@ -12,6 +11,7 @@ import dev.engine_room.flywheel.backend.glsl.generate.GlslBuilder;
import dev.engine_room.flywheel.backend.glsl.generate.GlslExpr;
import dev.engine_room.flywheel.backend.glsl.generate.GlslStmt;
import dev.engine_room.flywheel.lib.math.MoreMath;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
public class SsboInstanceComponent extends InstanceAssemblerComponent {
public SsboInstanceComponent(InstanceType<?> type) {
@ -20,7 +20,7 @@ public class SsboInstanceComponent extends InstanceAssemblerComponent {
@Override
public String name() {
return Flywheel.rl("ssbo_instance_assembler").toString();
return ResourceUtil.rl("ssbo_instance_assembler").toString();
}
@Override

View file

@ -3,8 +3,8 @@ package dev.engine_room.flywheel.backend.compile.component;
import java.util.Collection;
import java.util.Map;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.backend.glsl.SourceComponent;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
public final class StringSubstitutionComponent implements SourceComponent {
private final SourceComponent source;
@ -42,7 +42,7 @@ public final class StringSubstitutionComponent implements SourceComponent {
@Override
public String name() {
return Flywheel.rl("string_substitution").toString() + " / " + source.name();
return ResourceUtil.rl("string_substitution").toString() + " / " + source.name();
}
@Override

View file

@ -10,7 +10,6 @@ import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.backend.glsl.ShaderSources;
import dev.engine_room.flywheel.backend.glsl.SourceComponent;
import dev.engine_room.flywheel.backend.glsl.SourceFile;
@ -19,6 +18,7 @@ import dev.engine_room.flywheel.backend.glsl.generate.GlslBlock;
import dev.engine_room.flywheel.backend.glsl.generate.GlslBuilder;
import dev.engine_room.flywheel.backend.glsl.generate.GlslExpr;
import dev.engine_room.flywheel.backend.glsl.generate.GlslSwitch;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
public class UberShaderComponent implements SourceComponent {
@ -40,7 +40,7 @@ public class UberShaderComponent implements SourceComponent {
@Override
public String name() {
return Flywheel.rl("uber_shader").toString() + " / " + name;
return ResourceUtil.rl("uber_shader").toString() + " / " + name;
}
@Override

View file

@ -8,18 +8,18 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.backend.glsl.SourceFile;
import dev.engine_room.flywheel.backend.glsl.SourceLines;
import dev.engine_room.flywheel.backend.glsl.error.ConsoleColors;
import dev.engine_room.flywheel.backend.glsl.error.ErrorBuilder;
import dev.engine_room.flywheel.backend.glsl.error.ErrorLevel;
import dev.engine_room.flywheel.backend.glsl.span.Span;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import dev.engine_room.flywheel.lib.util.StringUtil;
import net.minecraft.resources.ResourceLocation;
public class FailedCompilation {
public static final ResourceLocation GENERATED_SOURCE_NAME = Flywheel.rl("generated_source");
public static final ResourceLocation GENERATED_SOURCE_NAME = ResourceUtil.rl("generated_source");
private static final Pattern PATTERN_ONE = Pattern.compile("(\\d+)\\((\\d+)\\) : (.*)");
private static final Pattern PATTERN_TWO = Pattern.compile("(\\w+): (\\d+):(\\d+):(?: '(.+?)' :)?(.*)");
private final List<SourceFile> files;

View file

@ -12,13 +12,12 @@ import org.jetbrains.annotations.Nullable;
import com.mojang.datafixers.util.Pair;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.backend.RenderContext;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.FlwBackend;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
@ -39,12 +38,12 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
/**
* A list of instancers that have not yet been initialized.
* <br>
* All new instancers land here before having resources allocated in {@link #flush}.
* All new instancers land here before having resources allocated in {@link #render}.
*/
protected final Queue<UninitializedInstancer<N, ?>> initializationQueue = new ConcurrentLinkedQueue<>();
public <I extends Instance> AbstractInstancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) {
return getInstancer(new InstancerKey<>(environment, type, model, visualType, bias));
public <I extends Instance> AbstractInstancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, int bias) {
return getInstancer(new InstancerKey<>(environment, type, model, bias));
}
@SuppressWarnings("unchecked")
@ -57,7 +56,7 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
return ForEachPlan.of(() -> new ArrayList<>(instancers.values()), AbstractInstancer::parallelUpdate);
}
public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
public void render(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
// Thread safety: flush is called from the render thread after all visual updates have been made,
// so there are no:tm: threads we could be racing with.
for (var init : initializationQueue) {
@ -76,8 +75,6 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
.forEach(AbstractInstancer::clear);
}
public abstract void render(VisualType visualType);
public abstract void renderCrumbling(List<Engine.CrumblingBlock> crumblingBlocks);
protected abstract <I extends Instance> N create(InstancerKey<I> type);

View file

@ -4,8 +4,8 @@ import java.util.List;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.backend.RenderContext;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.Instancer;
@ -13,7 +13,6 @@ import dev.engine_room.flywheel.api.instance.InstancerProvider;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.visualization.VisualEmbedding;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import dev.engine_room.flywheel.backend.FlwBackend;
import dev.engine_room.flywheel.backend.compile.core.ShaderException;
@ -47,8 +46,8 @@ public class EngineImpl implements Engine {
}
@Override
public VisualizationContext createVisualizationContext(VisualType visualType) {
return new VisualizationContextImpl(visualType);
public VisualizationContext createVisualizationContext() {
return new VisualizationContextImpl();
}
@Override
@ -90,23 +89,13 @@ public class EngineImpl implements Engine {
}
@Override
public void setupRender(RenderContext context) {
public void render(RenderContext context) {
try (var state = GlStateTracker.getRestoreState()) {
// Process the render queue for font updates
RenderSystem.replayQueue();
Uniforms.update(context);
environmentStorage.flush();
drawManager.flush(lightStorage, environmentStorage);
} catch (ShaderException e) {
FlwBackend.LOGGER.error("Falling back", e);
triggerFallback();
}
}
@Override
public void render(RenderContext context, VisualType visualType) {
try (var state = GlStateTracker.getRestoreState()) {
drawManager.render(visualType);
drawManager.render(lightStorage, environmentStorage);
} catch (ShaderException e) {
FlwBackend.LOGGER.error("Falling back", e);
triggerFallback();
@ -134,8 +123,8 @@ public class EngineImpl implements Engine {
drawManager.triggerFallback();
}
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) {
return drawManager.getInstancer(environment, type, model, visualType, bias);
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, int bias) {
return drawManager.getInstancer(environment, type, model, bias);
}
public EnvironmentStorage environmentStorage() {
@ -148,11 +137,9 @@ public class EngineImpl implements Engine {
private class VisualizationContextImpl implements VisualizationContext {
private final InstancerProviderImpl instancerProvider;
private final VisualType visualType;
public VisualizationContextImpl(VisualType visualType) {
instancerProvider = new InstancerProviderImpl(EngineImpl.this, visualType);
this.visualType = visualType;
public VisualizationContextImpl() {
instancerProvider = new InstancerProviderImpl(EngineImpl.this);
}
@Override
@ -167,7 +154,7 @@ public class EngineImpl implements Engine {
@Override
public VisualEmbedding createEmbedding(Vec3i renderOrigin) {
var out = new EmbeddedEnvironment(EngineImpl.this, visualType, renderOrigin);
var out = new EmbeddedEnvironment(EngineImpl.this, renderOrigin);
environmentStorage.track(out);
return out;
}

View file

@ -3,9 +3,7 @@ package dev.engine_room.flywheel.backend.engine;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
public record InstancerKey<I extends Instance>(Environment environment, InstanceType<I> type, Model model,
VisualType visualType, int bias) {
public record InstancerKey<I extends Instance>(Environment environment, InstanceType<I> type, Model model, int bias) {
}

View file

@ -5,12 +5,11 @@ import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.Instancer;
import dev.engine_room.flywheel.api.instance.InstancerProvider;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.engine.embed.GlobalEnvironment;
public record InstancerProviderImpl(EngineImpl engine, VisualType visualType) implements InstancerProvider {
public record InstancerProviderImpl(EngineImpl engine) implements InstancerProvider {
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, int bias) {
return engine.instancer(GlobalEnvironment.INSTANCE, type, model, visualType, bias);
return engine.instancer(GlobalEnvironment.INSTANCE, type, model, bias);
}
}

View file

@ -45,6 +45,17 @@ public final class MaterialRenderState {
setupWriteMask(material.writeMask());
}
public static void setupOit(Material material) {
setupTexture(material);
setupBackfaceCulling(material.backfaceCulling());
setupPolygonOffset(material.polygonOffset());
setupDepthTest(material.depthTest());
WriteMask mask = material.writeMask();
boolean writeColor = mask.color();
RenderSystem.colorMask(writeColor, writeColor, writeColor, writeColor);
}
private static void setupTexture(Material material) {
Samplers.DIFFUSE.makeActive();
AbstractTexture texture = Minecraft.getInstance()

View file

@ -12,7 +12,6 @@ import dev.engine_room.flywheel.api.instance.Instancer;
import dev.engine_room.flywheel.api.instance.InstancerProvider;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualEmbedding;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.engine.EngineImpl;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
@ -21,7 +20,6 @@ import net.minecraft.core.Vec3i;
public class EmbeddedEnvironment implements VisualEmbedding, Environment {
private final EngineImpl engine;
private final VisualType visualType;
private final Vec3i renderOrigin;
@Nullable
private final EmbeddedEnvironment parent;
@ -36,9 +34,8 @@ public class EmbeddedEnvironment implements VisualEmbedding, Environment {
private boolean deleted = false;
public EmbeddedEnvironment(EngineImpl engine, VisualType visualType, Vec3i renderOrigin, @Nullable EmbeddedEnvironment parent) {
public EmbeddedEnvironment(EngineImpl engine, Vec3i renderOrigin, @Nullable EmbeddedEnvironment parent) {
this.engine = engine;
this.visualType = visualType;
this.renderOrigin = renderOrigin;
this.parent = parent;
@ -46,13 +43,13 @@ public class EmbeddedEnvironment implements VisualEmbedding, Environment {
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model, int bias) {
// Kinda cursed usage of anonymous classes here, but it does the job.
return engine.instancer(EmbeddedEnvironment.this, type, model, visualType, bias);
return engine.instancer(EmbeddedEnvironment.this, type, model, bias);
}
};
}
public EmbeddedEnvironment(EngineImpl engine, VisualType visualType, Vec3i renderOrigin) {
this(engine, visualType, renderOrigin, null);
public EmbeddedEnvironment(EngineImpl engine, Vec3i renderOrigin) {
this(engine, renderOrigin, null);
}
@Override
@ -73,7 +70,7 @@ public class EmbeddedEnvironment implements VisualEmbedding, Environment {
@Override
public VisualEmbedding createEmbedding(Vec3i renderOrigin) {
var out = new EmbeddedEnvironment(engine, visualType, renderOrigin, this);
var out = new EmbeddedEnvironment(engine, renderOrigin, this);
engine.environmentStorage()
.track(out);
return out;

View file

@ -8,17 +8,16 @@ import static org.lwjgl.opengl.GL43.glDispatchCompute;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.material.Transparency;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.compile.PipelineCompiler;
import dev.engine_room.flywheel.backend.engine.InstancerKey;
import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.engine.MeshPool;
@ -28,8 +27,7 @@ import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import dev.engine_room.flywheel.lib.math.MoreMath;
public class IndirectCullingGroup<I extends Instance> {
private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::visualType)
.thenComparing(IndirectDraw::isEmbedded)
private static final Comparator<IndirectDraw> DRAW_COMPARATOR = Comparator.comparing(IndirectDraw::isEmbedded)
.thenComparing(IndirectDraw::bias)
.thenComparing(IndirectDraw::indexOfMeshInModel)
.thenComparing(IndirectDraw::material, MaterialRenderState.COMPARATOR);
@ -39,7 +37,8 @@ public class IndirectCullingGroup<I extends Instance> {
private final IndirectBuffers buffers;
private final List<IndirectInstancer<I>> instancers = new ArrayList<>();
private final List<IndirectDraw> indirectDraws = new ArrayList<>();
private final Map<VisualType, List<MultiDraw>> multiDraws = new EnumMap<>(VisualType.class);
private final List<MultiDraw> multiDraws = new ArrayList<>();
private final List<MultiDraw> oitDraws = new ArrayList<>();
private final IndirectPrograms programs;
private final GlProgram cullProgram;
@ -58,7 +57,7 @@ public class IndirectCullingGroup<I extends Instance> {
cullProgram = programs.getCullingProgram(instanceType);
}
public void flushInstancers() {
public boolean flushInstancers() {
instanceCountThisFrame = 0;
int modelIndex = 0;
for (var iterator = instancers.iterator(); iterator.hasNext(); ) {
@ -80,13 +79,17 @@ public class IndirectCullingGroup<I extends Instance> {
if (indirectDraws.removeIf(IndirectDraw::deleted)) {
needsDrawSort = true;
}
var out = indirectDraws.isEmpty();
if (out) {
delete();
}
return out;
}
public void upload(StagingBuffer stagingBuffer) {
if (nothingToDo()) {
return;
}
buffers.updateCounts(instanceCountThisFrame, instancers.size(), indirectDraws.size());
// Upload only instances that have changed.
@ -108,10 +111,6 @@ public class IndirectCullingGroup<I extends Instance> {
}
public void dispatchCull() {
if (nothingToDo()) {
return;
}
Uniforms.bindAll();
cullProgram.bind();
@ -120,24 +119,17 @@ public class IndirectCullingGroup<I extends Instance> {
}
public void dispatchApply() {
if (nothingToDo()) {
return;
}
buffers.bindForApply();
glDispatchCompute(GlCompat.getComputeGroupCount(indirectDraws.size()), 1, 1);
}
private boolean nothingToDo() {
return indirectDraws.isEmpty() || instanceCountThisFrame == 0;
}
private boolean nothingToDo(VisualType visualType) {
return nothingToDo() || !multiDraws.containsKey(visualType);
public boolean hasOitDraws() {
return !oitDraws.isEmpty();
}
private void sortDraws() {
multiDraws.clear();
oitDraws.clear();
// sort by visual type, then material
indirectDraws.sort(DRAW_COMPARATOR);
@ -146,28 +138,21 @@ public class IndirectCullingGroup<I extends Instance> {
// if the next draw call has a different VisualType or Material, start a new MultiDraw
if (i == indirectDraws.size() - 1 || incompatibleDraws(draw1, indirectDraws.get(i + 1))) {
multiDraws.computeIfAbsent(draw1.visualType(), s -> new ArrayList<>())
.add(new MultiDraw(draw1.material(), draw1.isEmbedded(), start, i + 1));
var dst = draw1.material()
.transparency() == Transparency.ORDER_INDEPENDENT ? oitDraws : multiDraws;
dst.add(new MultiDraw(draw1.material(), draw1.isEmbedded(), start, i + 1));
start = i + 1;
}
}
}
private boolean incompatibleDraws(IndirectDraw draw1, IndirectDraw draw2) {
if (draw1.visualType() != draw2.visualType()) {
return true;
}
if (draw1.isEmbedded() != draw2.isEmbedded()) {
return true;
}
return !MaterialRenderState.materialEquals(draw1.material(), draw2.material());
}
public boolean hasVisualType(VisualType visualType) {
return multiDraws.containsKey(visualType);
}
public void add(IndirectInstancer<I> instancer, InstancerKey<I> key, MeshPool meshPool) {
instancer.mapping = buffers.objectStorage.createMapping();
instancer.update(instancers.size(), -1);
@ -180,7 +165,7 @@ public class IndirectCullingGroup<I extends Instance> {
var entry = meshes.get(i);
MeshPool.PooledMesh mesh = meshPool.alloc(entry.mesh());
var draw = new IndirectDraw(instancer, entry.material(), mesh, key.visualType(), key.bias(), i);
var draw = new IndirectDraw(instancer, entry.material(), mesh, key.bias(), i);
indirectDraws.add(draw);
instancer.addDraw(draw);
}
@ -188,8 +173,8 @@ public class IndirectCullingGroup<I extends Instance> {
needsDrawSort = true;
}
public void submit(VisualType visualType) {
if (nothingToDo(visualType)) {
public void submitSolid() {
if (multiDraws.isEmpty()) {
return;
}
@ -199,8 +184,8 @@ public class IndirectCullingGroup<I extends Instance> {
GlProgram lastProgram = null;
for (var multiDraw : multiDraws.get(visualType)) {
var drawProgram = programs.getIndirectProgram(instanceType, multiDraw.embedded ? ContextShader.EMBEDDED : ContextShader.DEFAULT, multiDraw.material);
for (var multiDraw : multiDraws) {
var drawProgram = programs.getIndirectProgram(instanceType, multiDraw.embedded ? ContextShader.EMBEDDED : ContextShader.DEFAULT, multiDraw.material, PipelineCompiler.OitMode.OFF);
if (drawProgram != lastProgram) {
lastProgram = drawProgram;
@ -214,8 +199,36 @@ public class IndirectCullingGroup<I extends Instance> {
}
}
public void submitTransparent(PipelineCompiler.OitMode oit) {
if (oitDraws.isEmpty()) {
return;
}
buffers.bindForDraw();
drawBarrier();
GlProgram lastProgram = null;
for (var multiDraw : oitDraws) {
var drawProgram = programs.getIndirectProgram(instanceType, multiDraw.embedded ? ContextShader.EMBEDDED : ContextShader.DEFAULT, multiDraw.material, oit);
if (drawProgram != lastProgram) {
lastProgram = drawProgram;
// Don't need to do this unless the program changes.
drawProgram.bind();
drawProgram.setFloat("_flw_blueNoiseFactor", 0.07f);
}
MaterialRenderState.setupOit(multiDraw.material);
multiDraw.submit(drawProgram);
}
}
public void bindForCrumbling(Material material) {
var program = programs.getIndirectProgram(instanceType, ContextShader.CRUMBLING, material);
var program = programs.getIndirectProgram(instanceType, ContextShader.CRUMBLING, material, PipelineCompiler.OitMode.OFF);
program.bind();
@ -273,16 +286,6 @@ public class IndirectCullingGroup<I extends Instance> {
buffers.delete();
}
public boolean checkEmptyAndDelete() {
var out = indirectDraws.isEmpty();
if (out) {
delete();
}
return out;
}
private record MultiDraw(Material material, boolean embedded, int start, int end) {
private void submit(GlProgram drawProgram) {
GlCompat.safeMultiDrawElementsIndirect(drawProgram, GL_TRIANGLES, GL_UNSIGNED_INT, this.start, this.end, IndirectBuffers.DRAW_COMMAND_STRIDE);

View file

@ -3,7 +3,6 @@ package dev.engine_room.flywheel.backend.engine.indirect;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.engine.MaterialEncoder;
import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.embed.EmbeddedEnvironment;
@ -12,7 +11,6 @@ public class IndirectDraw {
private final IndirectInstancer<?> instancer;
private final Material material;
private final MeshPool.PooledMesh mesh;
private final VisualType visualType;
private final int bias;
private final int indexOfMeshInModel;
@ -20,11 +18,10 @@ public class IndirectDraw {
private final int packedMaterialProperties;
private boolean deleted;
public IndirectDraw(IndirectInstancer<?> instancer, Material material, MeshPool.PooledMesh mesh, VisualType visualType, int bias, int indexOfMeshInModel) {
public IndirectDraw(IndirectInstancer<?> instancer, Material material, MeshPool.PooledMesh mesh, int bias, int indexOfMeshInModel) {
this.instancer = instancer;
this.material = material;
this.mesh = mesh;
this.visualType = visualType;
this.bias = bias;
this.indexOfMeshInModel = indexOfMeshInModel;
@ -50,10 +47,6 @@ public class IndirectDraw {
return mesh;
}
public VisualType visualType() {
return visualType;
}
public int bias() {
return bias;
}

View file

@ -15,9 +15,9 @@ import java.util.Map;
import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.compile.PipelineCompiler;
import dev.engine_room.flywheel.backend.engine.AbstractInstancer;
import dev.engine_room.flywheel.backend.engine.CommonCrumbling;
import dev.engine_room.flywheel.backend.engine.DrawManager;
@ -49,7 +49,7 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
private final DepthPyramid depthPyramid;
private boolean needsBarrier = false;
private final OitFramebuffer oitFramebuffer;
public IndirectDrawManager(IndirectPrograms programs) {
this.programs = programs;
@ -65,6 +65,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
matrixBuffer = new MatrixBuffer();
depthPyramid = new DepthPyramid(programs);
oitFramebuffer = new OitFramebuffer(programs.oitPrograms());
}
@Override
@ -79,51 +81,15 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
group.add((IndirectInstancer<I>) instancer, key, meshPool);
}
public boolean hasVisualType(VisualType visualType) {
for (var group : cullingGroups.values()) {
if (group.hasVisualType(visualType)) {
return true;
}
}
return false;
}
public void render(VisualType visualType) {
if (!hasVisualType(visualType)) {
return;
}
TextureBinder.bindLightAndOverlay();
vertexArray.bindForDraw();
lightBuffers.bind();
matrixBuffer.bind();
Uniforms.bindAll();
if (needsBarrier) {
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
needsBarrier = false;
}
for (var group : cullingGroups.values()) {
group.submit(visualType);
}
MaterialRenderState.reset();
TextureBinder.resetLightAndOverlay();
}
@Override
public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
super.flush(lightStorage, environmentStorage);
for (var group : cullingGroups.values()) {
group.flushInstancers();
}
public void render(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
super.render(lightStorage, environmentStorage);
// Flush instance counts, page mappings, and prune empty groups.
cullingGroups.values()
.removeIf(IndirectCullingGroup::checkEmptyAndDelete);
.removeIf(IndirectCullingGroup::flushInstancers);
// Instancers may have been emptied in the above call, now remove them here.
instancers.values()
.removeIf(instancer -> instancer.instanceCount() == 0);
@ -131,6 +97,12 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
stagingBuffer.reclaim();
// Genuinely nothing to do, we can just early out.
// Still process the mesh pool and reclaim fenced staging regions though.
if (cullingGroups.isEmpty()) {
return;
}
lightBuffers.flush(stagingBuffer, lightStorage);
matrixBuffer.flush(stagingBuffer, environmentStorage);
@ -165,7 +137,59 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
group.dispatchApply();
}
needsBarrier = true;
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
TextureBinder.bindLightAndOverlay();
vertexArray.bindForDraw();
lightBuffers.bind();
matrixBuffer.bind();
Uniforms.bindAll();
for (var group : cullingGroups.values()) {
group.submitSolid();
}
// Let's avoid invoking the oit chain if we don't have anything to do
boolean useOit = false;
for (var group : cullingGroups.values()) {
if (group.hasOitDraws()) {
useOit = true;
break;
}
}
if (useOit) {
oitFramebuffer.prepare();
oitFramebuffer.depthRange();
for (var group : cullingGroups.values()) {
group.submitTransparent(PipelineCompiler.OitMode.DEPTH_RANGE);
}
oitFramebuffer.renderTransmittance();
for (var group : cullingGroups.values()) {
group.submitTransparent(PipelineCompiler.OitMode.GENERATE_COEFFICIENTS);
}
oitFramebuffer.renderDepthFromTransmittance();
// Need to bind this again because we just drew a full screen quad for OIT.
vertexArray.bindForDraw();
oitFramebuffer.accumulate();
for (var group : cullingGroups.values()) {
group.submitTransparent(PipelineCompiler.OitMode.EVALUATE);
}
oitFramebuffer.composite();
}
MaterialRenderState.reset();
TextureBinder.resetLightAndOverlay();
}
@Override
@ -189,6 +213,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
lightBuffers.delete();
matrixBuffer.delete();
oitFramebuffer.delete();
}
public void renderCrumbling(List<Engine.CrumblingBlock> crumblingBlocks) {

View file

@ -141,12 +141,8 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
// This is safe because only one bit position changes at a time.
parent.fullPages.set(pageNo);
}
if (isEmpty(currentValue)) {
// Value we just saw was zero, so since we added something we are now mergeable!
if (isMergeable(newValue)) {
parent.mergeablePages.set(pageNo);
} else if (Integer.bitCount(currentValue) == 16) {
// We just filled the 17th instance, so we are no longer mergeable.
parent.mergeablePages.clear(pageNo);
}
parent.instanceCount.incrementAndGet();
@ -199,11 +195,7 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
if (valid.compareAndSet(currentValue, newValue)) {
parent.validityChanged.set(pageNo);
if (isEmpty(newValue)) {
// If we decremented to zero then we're no longer mergeable.
parent.mergeablePages.clear(pageNo);
} else if (Integer.bitCount(newValue) == 16) {
// If we decremented to 16 then we're now mergeable.
if (isMergeable(newValue)) {
parent.mergeablePages.set(pageNo);
}
// Set full page last so that other threads don't race to set the other bitsets.
@ -223,6 +215,13 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
// Fill the holes in this page with instances from the other page.
int valid = this.valid.get();
if (isFull(valid)) {
// We got filled after being marked mergeable, nothing to do
parent.mergeablePages.clear(pageNo);
return;
}
int otherValid = other.valid.get();
for (int i = 0; i < ObjectStorage.PAGE_SIZE; i++) {

View file

@ -0,0 +1,319 @@
package dev.engine_room.flywheel.backend.engine.indirect;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL46;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import dev.engine_room.flywheel.backend.NoiseTextures;
import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.compile.OitPrograms;
import dev.engine_room.flywheel.backend.gl.GlCompat;
import dev.engine_room.flywheel.backend.gl.GlTextureUnit;
import net.minecraft.client.Minecraft;
public class OitFramebuffer {
public static final float[] CLEAR_TO_ZERO = {0, 0, 0, 0};
public static final int[] DEPTH_RANGE_DRAW_BUFFERS = {GL46.GL_COLOR_ATTACHMENT0};
public static final int[] RENDER_TRANSMITTANCE_DRAW_BUFFERS = {GL46.GL_COLOR_ATTACHMENT1, GL46.GL_COLOR_ATTACHMENT2, GL46.GL_COLOR_ATTACHMENT3, GL46.GL_COLOR_ATTACHMENT4};
public static final int[] ACCUMULATE_DRAW_BUFFERS = {GL46.GL_COLOR_ATTACHMENT5};
public static final int[] DEPTH_ONLY_DRAW_BUFFERS = {};
private final OitPrograms programs;
private final int vao;
public int fbo = -1;
public int depthBounds = -1;
public int coefficients = -1;
public int accumulate = -1;
private int lastWidth = -1;
private int lastHeight = -1;
public OitFramebuffer(OitPrograms programs) {
this.programs = programs;
if (GlCompat.SUPPORTS_DSA) {
vao = GL46.glCreateVertexArrays();
} else {
vao = GL32.glGenVertexArrays();
}
}
/**
* Set up the framebuffer.
*/
public void prepare() {
RenderTarget renderTarget;
if (Minecraft.useShaderTransparency()) {
renderTarget = Minecraft.getInstance().levelRenderer.getItemEntityTarget();
renderTarget.copyDepthFrom(Minecraft.getInstance()
.getMainRenderTarget());
} else {
renderTarget = Minecraft.getInstance()
.getMainRenderTarget();
}
maybeResizeFBO(renderTarget.width, renderTarget.height);
Samplers.COEFFICIENTS.makeActive();
// Bind zero to render system to make sure we clear their internal state
RenderSystem.bindTexture(0);
GL32.glBindTexture(GL32.GL_TEXTURE_2D_ARRAY, coefficients);
Samplers.DEPTH_RANGE.makeActive();
RenderSystem.bindTexture(depthBounds);
Samplers.NOISE.makeActive();
NoiseTextures.BLUE_NOISE.bind();
GlStateManager._glBindFramebuffer(GL32.GL_FRAMEBUFFER, fbo);
GL32.glFramebufferTexture(GL32.GL_FRAMEBUFFER, GL32.GL_DEPTH_ATTACHMENT, renderTarget.getDepthTextureId(), 0);
}
/**
* Render out the min and max depth per fragment.
*/
public void depthRange() {
// No depth writes, but we'll still use the depth test.
RenderSystem.depthMask(false);
RenderSystem.colorMask(true, true, true, true);
RenderSystem.enableBlend();
RenderSystem.blendFunc(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE);
RenderSystem.blendEquation(GL32.GL_MAX);
var far = Minecraft.getInstance().gameRenderer.getDepthFar();
if (GlCompat.SUPPORTS_DSA) {
GL46.glNamedFramebufferDrawBuffers(fbo, DEPTH_RANGE_DRAW_BUFFERS);
GL46.glClearNamedFramebufferfv(fbo, GL46.GL_COLOR, 0, new float[]{-far, -far, 0, 0});
} else {
GL32.glDrawBuffers(DEPTH_RANGE_DRAW_BUFFERS);
RenderSystem.clearColor(-far, -far, 0, 0);
RenderSystem.clear(GL32.GL_COLOR_BUFFER_BIT, false);
}
}
/**
* Generate the coefficients to the transmittance function.
*/
public void renderTransmittance() {
// No depth writes, but we'll still use the depth test
RenderSystem.depthMask(false);
RenderSystem.colorMask(true, true, true, true);
RenderSystem.enableBlend();
RenderSystem.blendFunc(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE);
RenderSystem.blendEquation(GL32.GL_FUNC_ADD);
if (GlCompat.SUPPORTS_DSA) {
GL46.glNamedFramebufferDrawBuffers(fbo, RENDER_TRANSMITTANCE_DRAW_BUFFERS);
GL46.glClearNamedFramebufferfv(fbo, GL46.GL_COLOR, 0, CLEAR_TO_ZERO);
GL46.glClearNamedFramebufferfv(fbo, GL46.GL_COLOR, 1, CLEAR_TO_ZERO);
GL46.glClearNamedFramebufferfv(fbo, GL46.GL_COLOR, 2, CLEAR_TO_ZERO);
GL46.glClearNamedFramebufferfv(fbo, GL46.GL_COLOR, 3, CLEAR_TO_ZERO);
} else {
GL32.glDrawBuffers(RENDER_TRANSMITTANCE_DRAW_BUFFERS);
RenderSystem.clearColor(0, 0, 0, 0);
RenderSystem.clear(GL32.GL_COLOR_BUFFER_BIT, false);
}
}
/**
* If any fragment has its transmittance fall off to zero, search the transmittance
* function to determine at what depth that occurs and write out to the depth buffer.
*/
public void renderDepthFromTransmittance() {
// Only write to depth, not color.
RenderSystem.depthMask(true);
RenderSystem.colorMask(false, false, false, false);
RenderSystem.disableBlend();
RenderSystem.depthFunc(GL32.GL_ALWAYS);
if (GlCompat.SUPPORTS_DSA) {
GL46.glNamedFramebufferDrawBuffers(fbo, DEPTH_ONLY_DRAW_BUFFERS);
} else {
GL32.glDrawBuffers(DEPTH_ONLY_DRAW_BUFFERS);
}
programs.getOitDepthProgram()
.bind();
drawFullscreenQuad();
}
/**
* Sample the transmittance function and accumulate.
*/
public void accumulate() {
// No depth writes, but we'll still use the depth test
RenderSystem.depthMask(false);
RenderSystem.colorMask(true, true, true, true);
RenderSystem.enableBlend();
RenderSystem.blendFunc(GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE);
RenderSystem.blendEquation(GL32.GL_FUNC_ADD);
if (GlCompat.SUPPORTS_DSA) {
GL46.glNamedFramebufferDrawBuffers(fbo, ACCUMULATE_DRAW_BUFFERS);
GL46.glClearNamedFramebufferfv(fbo, GL46.GL_COLOR, 0, CLEAR_TO_ZERO);
} else {
GL32.glDrawBuffers(ACCUMULATE_DRAW_BUFFERS);
RenderSystem.clearColor(0, 0, 0, 0);
RenderSystem.clear(GL32.GL_COLOR_BUFFER_BIT, false);
}
}
/**
* Composite the accumulated luminance onto the main framebuffer.
*/
public void composite() {
if (Minecraft.useShaderTransparency()) {
Minecraft.getInstance().levelRenderer.getItemEntityTarget()
.bindWrite(false);
} else {
Minecraft.getInstance()
.getMainRenderTarget()
.bindWrite(false);
}
// The composite shader writes out the closest depth to gl_FragDepth.
// depthMask = true: OIT stuff renders on top of other transparent stuff.
// depthMask = false: other transparent stuff renders on top of OIT stuff.
// If Neo gets wavelet OIT we can use their hooks to be correct with everything.
RenderSystem.depthMask(true);
RenderSystem.colorMask(true, true, true, true);
RenderSystem.enableBlend();
// We rely on the blend func to achieve:
// final color = (1 - transmittance_total) * sum(color_f * alpha_f * transmittance_f) / sum(alpha_f * transmittance_f)
// + color_dst * transmittance_total
//
// Though note that the alpha value we emit in the fragment shader is actually (1. - transmittance_total).
// The extra inversion step is so we can have a sane alpha value written out for the fabulous blit shader to consume.
RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
RenderSystem.blendEquation(GL32.GL_FUNC_ADD);
RenderSystem.depthFunc(GL32.GL_ALWAYS);
GlTextureUnit.T0.makeActive();
RenderSystem.bindTexture(accumulate);
programs.getOitCompositeProgram()
.bind();
drawFullscreenQuad();
Minecraft.getInstance()
.getMainRenderTarget()
.bindWrite(false);
}
public void delete() {
deleteTextures();
GL32.glDeleteVertexArrays(vao);
}
private void drawFullscreenQuad() {
// Empty VAO, the actual full screen triangle is generated in the vertex shader
GlStateManager._glBindVertexArray(vao);
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, 3);
}
private void deleteTextures() {
if (depthBounds != -1) {
GL32.glDeleteTextures(depthBounds);
}
if (coefficients != -1) {
GL32.glDeleteTextures(coefficients);
}
if (accumulate != -1) {
GL32.glDeleteTextures(accumulate);
}
if (fbo != -1) {
GL32.glDeleteFramebuffers(fbo);
}
// We sometimes get the same texture ID back when creating new textures,
// so bind zero to clear the GlStateManager
Samplers.COEFFICIENTS.makeActive();
RenderSystem.bindTexture(0);
Samplers.DEPTH_RANGE.makeActive();
RenderSystem.bindTexture(0);
}
private void maybeResizeFBO(int width, int height) {
if (lastWidth == width && lastHeight == height) {
return;
}
lastWidth = width;
lastHeight = height;
deleteTextures();
if (GlCompat.SUPPORTS_DSA) {
fbo = GL46.glCreateFramebuffers();
depthBounds = GL46.glCreateTextures(GL46.GL_TEXTURE_2D);
coefficients = GL46.glCreateTextures(GL46.GL_TEXTURE_2D_ARRAY);
accumulate = GL46.glCreateTextures(GL46.GL_TEXTURE_2D);
GL46.glTextureStorage2D(depthBounds, 1, GL32.GL_RG32F, width, height);
GL46.glTextureStorage3D(coefficients, 1, GL32.GL_RGBA16F, width, height, 4);
GL46.glTextureStorage2D(accumulate, 1, GL32.GL_RGBA16F, width, height);
GL46.glNamedFramebufferTexture(fbo, GL32.GL_COLOR_ATTACHMENT0, depthBounds, 0);
GL46.glNamedFramebufferTextureLayer(fbo, GL32.GL_COLOR_ATTACHMENT1, coefficients, 0, 0);
GL46.glNamedFramebufferTextureLayer(fbo, GL32.GL_COLOR_ATTACHMENT2, coefficients, 0, 1);
GL46.glNamedFramebufferTextureLayer(fbo, GL32.GL_COLOR_ATTACHMENT3, coefficients, 0, 2);
GL46.glNamedFramebufferTextureLayer(fbo, GL32.GL_COLOR_ATTACHMENT4, coefficients, 0, 3);
GL46.glNamedFramebufferTexture(fbo, GL32.GL_COLOR_ATTACHMENT5, accumulate, 0);
} else {
fbo = GL46.glGenFramebuffers();
depthBounds = GL32.glGenTextures();
coefficients = GL32.glGenTextures();
accumulate = GL32.glGenTextures();
GlTextureUnit.T0.makeActive();
RenderSystem.bindTexture(0);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, depthBounds);
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RG32F, width, height, 0, GL46.GL_RGBA, GL46.GL_BYTE, 0);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_S, GL32.GL_CLAMP_TO_EDGE);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_T, GL32.GL_CLAMP_TO_EDGE);
GL32.glBindTexture(GL32.GL_TEXTURE_2D_ARRAY, coefficients);
GL32.glTexImage3D(GL32.GL_TEXTURE_2D_ARRAY, 0, GL32.GL_RGBA16F, width, height, 4, 0, GL46.GL_RGBA, GL46.GL_BYTE, 0);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D_ARRAY, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D_ARRAY, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D_ARRAY, GL32.GL_TEXTURE_WRAP_S, GL32.GL_CLAMP_TO_EDGE);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D_ARRAY, GL32.GL_TEXTURE_WRAP_T, GL32.GL_CLAMP_TO_EDGE);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, accumulate);
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16F, width, height, 0, GL46.GL_RGBA, GL46.GL_BYTE, 0);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_S, GL32.GL_CLAMP_TO_EDGE);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_T, GL32.GL_CLAMP_TO_EDGE);
GlStateManager._glBindFramebuffer(GL32.GL_FRAMEBUFFER, fbo);
GL46.glFramebufferTexture(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, depthBounds, 0);
GL46.glFramebufferTextureLayer(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT1, coefficients, 0, 0);
GL46.glFramebufferTextureLayer(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT2, coefficients, 0, 1);
GL46.glFramebufferTextureLayer(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT3, coefficients, 0, 2);
GL46.glFramebufferTextureLayer(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT4, coefficients, 0, 3);
GL46.glFramebufferTexture(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT5, accumulate, 0);
}
}
}

View file

@ -10,6 +10,7 @@ import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.gl.GlFence;
import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer;
import dev.engine_room.flywheel.backend.gl.buffer.GlBufferUsage;
import dev.engine_room.flywheel.lib.memory.FlwMemoryTracker;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
import it.unimi.dsi.fastutil.PriorityQueue;
@ -22,6 +23,8 @@ public class StagingBuffer {
private static final int STORAGE_FLAGS = GL45C.GL_MAP_PERSISTENT_BIT | GL45C.GL_MAP_WRITE_BIT | GL45C.GL_CLIENT_STORAGE_BIT;
private static final int MAP_FLAGS = GL45C.GL_MAP_PERSISTENT_BIT | GL45C.GL_MAP_WRITE_BIT | GL45C.GL_MAP_FLUSH_EXPLICIT_BIT | GL45C.GL_MAP_INVALIDATE_BUFFER_BIT;
private static final int SSBO_ALIGNMENT = GL45.glGetInteger(GL45.GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT);
private final int vbo;
private final long map;
private final long capacity;
@ -30,7 +33,7 @@ public class StagingBuffer {
private final OverflowStagingBuffer overflow = new OverflowStagingBuffer();
private final TransferList transfers = new TransferList();
private final PriorityQueue<FencedRegion> fencedRegions = new ObjectArrayFIFOQueue<>();
private final GlBuffer scatterBuffer = new GlBuffer();
private final GlBuffer scatterBuffer = new GlBuffer(GlBufferUsage.STREAM_COPY);
private final ScatterList scatterList = new ScatterList();
/**
@ -252,7 +255,6 @@ public class StagingBuffer {
.bind();
// These bindings don't change between dstVbos.
GL45.glBindBufferBase(GL45C.GL_SHADER_STORAGE_BUFFER, 0, scatterBuffer.handle());
GL45.glBindBufferBase(GL45C.GL_SHADER_STORAGE_BUFFER, 1, vbo);
int dstVbo;
@ -274,7 +276,25 @@ public class StagingBuffer {
}
private void dispatchScatter(int dstVbo) {
scatterBuffer.upload(scatterList.ptr(), scatterList.usedBytes());
var scatterSize = scatterList.usedBytes();
// If there's enough space in the staging buffer still, lets write the scatter in it directly.
long alignedPos = pos + SSBO_ALIGNMENT - 1 - (pos + SSBO_ALIGNMENT - 1) % SSBO_ALIGNMENT;
long remaining = capacity - alignedPos;
if (scatterSize <= remaining && scatterSize <= totalAvailable) {
MemoryUtil.memCopy(scatterList.ptr(), map + alignedPos, scatterSize);
GL45.glBindBufferRange(GL45C.GL_SHADER_STORAGE_BUFFER, 0, vbo, alignedPos, scatterSize);
long alignmentCost = alignedPos - pos;
usedCapacity += scatterSize + alignmentCost;
totalAvailable -= scatterSize + alignmentCost;
pos += scatterSize + alignmentCost;
} else {
scatterBuffer.upload(scatterList.ptr(), scatterSize);
GL45.glBindBufferBase(GL45C.GL_SHADER_STORAGE_BUFFER, 0, scatterBuffer.handle());
}
GL45.glBindBufferBase(GL45C.GL_SHADER_STORAGE_BUFFER, 2, dstVbo);

View file

@ -1,16 +1,17 @@
package dev.engine_room.flywheel.backend.engine.instancing;
import java.util.EnumMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.material.Material;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.api.material.Transparency;
import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.compile.InstancingPrograms;
import dev.engine_room.flywheel.backend.compile.PipelineCompiler;
import dev.engine_room.flywheel.backend.engine.AbstractInstancer;
import dev.engine_room.flywheel.backend.engine.CommonCrumbling;
import dev.engine_room.flywheel.backend.engine.DrawManager;
@ -22,6 +23,7 @@ import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.TextureBinder;
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
import dev.engine_room.flywheel.backend.engine.indirect.OitFramebuffer;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.TextureBuffer;
import dev.engine_room.flywheel.backend.gl.array.GlVertexArray;
@ -31,10 +33,16 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.ModelBakery;
public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
/**
* The set of draw calls to make for each {@link VisualType}.
*/
private final Map<VisualType, InstancedRenderStage> stages = new EnumMap<>(VisualType.class);
private static final Comparator<InstancedDraw> DRAW_COMPARATOR = Comparator.comparing(InstancedDraw::bias)
.thenComparing(InstancedDraw::indexOfMeshInModel)
.thenComparing(InstancedDraw::material, MaterialRenderState.COMPARATOR);
private final List<InstancedDraw> allDraws = new ArrayList<>();
private boolean needSort = false;
private final List<InstancedDraw> draws = new ArrayList<>();
private final List<InstancedDraw> oitDraws = new ArrayList<>();
private final InstancingPrograms programs;
/**
* A map of vertex types to their mesh pools.
@ -44,6 +52,8 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
private final TextureBuffer instanceTexture;
private final InstancedLight light;
private final OitFramebuffer oitFramebuffer;
public InstancedDrawManager(InstancingPrograms programs) {
programs.acquire();
this.programs = programs;
@ -54,11 +64,14 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
light = new InstancedLight();
meshPool.bind(vao);
oitFramebuffer = new OitFramebuffer(programs.oitPrograms());
}
@Override
public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
super.flush(lightStorage, environmentStorage);
public void render(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
super.render(lightStorage, environmentStorage);
this.instancers.values()
.removeIf(instancer -> {
@ -71,21 +84,32 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
}
});
for (InstancedRenderStage stage : stages.values()) {
// Remove the draw calls for any instancers we deleted.
stage.flush();
needSort |= allDraws.removeIf(InstancedDraw::deleted);
if (needSort) {
allDraws.sort(DRAW_COMPARATOR);
draws.clear();
oitDraws.clear();
for (var draw : allDraws) {
if (draw.material()
.transparency() == Transparency.ORDER_INDEPENDENT) {
oitDraws.add(draw);
} else {
draws.add(draw);
}
}
needSort = false;
}
meshPool.flush();
light.flush(lightStorage);
}
@Override
public void render(VisualType visualType) {
var stage = stages.get(visualType);
if (stage == null || stage.isEmpty()) {
if (allDraws.isEmpty()) {
return;
}
@ -94,20 +118,92 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
TextureBinder.bindLightAndOverlay();
light.bind();
stage.draw(instanceTexture, programs);
submitDraws();
if (!oitDraws.isEmpty()) {
oitFramebuffer.prepare();
oitFramebuffer.depthRange();
submitOitDraws(PipelineCompiler.OitMode.DEPTH_RANGE);
oitFramebuffer.renderTransmittance();
submitOitDraws(PipelineCompiler.OitMode.GENERATE_COEFFICIENTS);
oitFramebuffer.renderDepthFromTransmittance();
// Need to bind this again because we just drew a full screen quad for OIT.
vao.bindForDraw();
oitFramebuffer.accumulate();
submitOitDraws(PipelineCompiler.OitMode.EVALUATE);
oitFramebuffer.composite();
}
MaterialRenderState.reset();
TextureBinder.resetLightAndOverlay();
}
private void submitDraws() {
for (var drawCall : draws) {
var material = drawCall.material();
var groupKey = drawCall.groupKey;
var environment = groupKey.environment();
var program = programs.get(groupKey.instanceType(), environment.contextShader(), material, PipelineCompiler.OitMode.OFF);
program.bind();
environment.setupDraw(program);
uploadMaterialUniform(program, material);
program.setUInt("_flw_vertexOffset", drawCall.mesh()
.baseVertex());
MaterialRenderState.setup(material);
Samplers.INSTANCE_BUFFER.makeActive();
drawCall.render(instanceTexture);
}
}
private void submitOitDraws(PipelineCompiler.OitMode mode) {
for (var drawCall : oitDraws) {
var material = drawCall.material();
var groupKey = drawCall.groupKey;
var environment = groupKey.environment();
var program = programs.get(groupKey.instanceType(), environment.contextShader(), material, mode);
program.bind();
environment.setupDraw(program);
uploadMaterialUniform(program, material);
program.setUInt("_flw_vertexOffset", drawCall.mesh()
.baseVertex());
MaterialRenderState.setupOit(material);
Samplers.INSTANCE_BUFFER.makeActive();
drawCall.render(instanceTexture);
}
}
@Override
public void delete() {
instancers.values()
.forEach(InstancedInstancer::delete);
stages.values()
.forEach(InstancedRenderStage::delete);
stages.clear();
allDraws.forEach(InstancedDraw::delete);
allDraws.clear();
draws.clear();
oitDraws.clear();
meshPool.delete();
instanceTexture.delete();
@ -116,6 +212,8 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
light.delete();
oitFramebuffer.delete();
super.delete();
}
@ -128,8 +226,6 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
protected <I extends Instance> void initialize(InstancerKey<I> key, InstancedInstancer<?> instancer) {
instancer.init();
InstancedRenderStage stage = stages.computeIfAbsent(key.visualType(), $ -> new InstancedRenderStage());
var meshes = key.model()
.meshes();
for (int i = 0; i < meshes.size(); i++) {
@ -139,7 +235,8 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
GroupKey<?> groupKey = new GroupKey<>(key.type(), key.environment());
InstancedDraw instancedDraw = new InstancedDraw(instancer, mesh, groupKey, entry.material(), key.bias(), i);
stage.put(groupKey, instancedDraw);
allDraws.add(instancedDraw);
needSort = true;
instancer.addDrawCall(instancedDraw);
}
}
@ -182,7 +279,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
for (InstancedDraw draw : instancer.draws()) {
CommonCrumbling.applyCrumblingProperties(crumblingMaterial, draw.material());
var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING, crumblingMaterial);
var program = programs.get(shader.instanceType(), ContextShader.CRUMBLING, crumblingMaterial, PipelineCompiler.OitMode.OFF);
program.bind();
program.setInt("_flw_baseInstance", index);
uploadMaterialUniform(program, crumblingMaterial);

View file

@ -1,106 +0,0 @@
package dev.engine_room.flywheel.backend.engine.instancing;
import static dev.engine_room.flywheel.backend.engine.instancing.InstancedDrawManager.uploadMaterialUniform;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.compile.InstancingPrograms;
import dev.engine_room.flywheel.backend.engine.GroupKey;
import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.gl.TextureBuffer;
public class InstancedRenderStage {
private static final Comparator<InstancedDraw> DRAW_COMPARATOR = Comparator.comparing(InstancedDraw::bias)
.thenComparing(InstancedDraw::indexOfMeshInModel)
.thenComparing(InstancedDraw::material, MaterialRenderState.COMPARATOR);
private final Map<GroupKey<?>, DrawGroup> groups = new HashMap<>();
public InstancedRenderStage() {
}
public void delete() {
groups.values()
.forEach(DrawGroup::delete);
groups.clear();
}
public void put(GroupKey<?> groupKey, InstancedDraw instancedDraw) {
groups.computeIfAbsent(groupKey, $ -> new DrawGroup())
.put(instancedDraw);
}
public boolean isEmpty() {
return groups.isEmpty();
}
public void flush() {
groups.values()
.forEach(DrawGroup::flush);
groups.values()
.removeIf(DrawGroup::isEmpty);
}
public void draw(TextureBuffer instanceTexture, InstancingPrograms programs) {
for (var entry : groups.entrySet()) {
var shader = entry.getKey();
var drawCalls = entry.getValue();
var environment = shader.environment();
for (var drawCall : drawCalls.draws) {
var material = drawCall.material();
var program = programs.get(shader.instanceType(), environment.contextShader(), material);
program.bind();
environment.setupDraw(program);
uploadMaterialUniform(program, material);
program.setUInt("_flw_vertexOffset", drawCall.mesh()
.baseVertex());
MaterialRenderState.setup(material);
Samplers.INSTANCE_BUFFER.makeActive();
drawCall.render(instanceTexture);
}
}
}
public static class DrawGroup {
private final List<InstancedDraw> draws = new ArrayList<>();
private boolean needSort = false;
public void put(InstancedDraw instancedDraw) {
draws.add(instancedDraw);
needSort = true;
}
public void delete() {
draws.forEach(InstancedDraw::delete);
draws.clear();
}
public void flush() {
needSort |= draws.removeIf(InstancedDraw::deleted);
if (needSort) {
draws.sort(DRAW_COMPARATOR);
needSort = false;
}
}
public boolean isEmpty() {
return draws.isEmpty();
}
}
}

View file

@ -6,13 +6,14 @@ import org.joml.Vector2f;
import org.joml.Vector3f;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.backend.RenderContext;
import dev.engine_room.flywheel.api.visualization.VisualizationManager;
import dev.engine_room.flywheel.backend.engine.indirect.DepthPyramid;
import dev.engine_room.flywheel.backend.mixin.LevelRendererAccessor;
import net.minecraft.Util;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.Level;
@ -116,6 +117,9 @@ public final class FrameUniforms extends UniformWriter {
ptr = writeInt(ptr, debugMode);
// OIT noise factor
ptr = writeFloat(ptr, 0.07f);
firstWrite = false;
BUFFER.markDirty();
}
@ -195,7 +199,7 @@ public final class FrameUniforms extends UniformWriter {
int pyramidHeight = DepthPyramid.mip0Size(mainRenderTarget.height);
int pyramidDepth = DepthPyramid.getImageMipLevels(pyramidWidth, pyramidHeight);
ptr = writeFloat(ptr, 0.05F); // zNear
ptr = writeFloat(ptr, GameRenderer.PROJECTION_Z_NEAR); // zNear
ptr = writeFloat(ptr, mc.gameRenderer.getDepthFar()); // zFar
ptr = writeFloat(ptr, PROJECTION.m00()); // P00
ptr = writeFloat(ptr, PROJECTION.m11()); // P11

View file

@ -2,7 +2,7 @@ package dev.engine_room.flywheel.backend.engine.uniform;
import org.joml.Vector3f;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.backend.RenderContext;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;

View file

@ -2,7 +2,7 @@ package dev.engine_room.flywheel.backend.engine.uniform;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.backend.RenderContext;
import dev.engine_room.flywheel.backend.FlwBackendXplat;
import dev.engine_room.flywheel.backend.mixin.AbstractClientPlayerAccessor;
import net.minecraft.client.Minecraft;

View file

@ -1,6 +1,6 @@
package dev.engine_room.flywheel.backend.engine.uniform;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.backend.RenderContext;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
public final class Uniforms {

View file

@ -1,7 +1,5 @@
package dev.engine_room.flywheel.backend.gl;
import java.nio.ByteBuffer;
import org.jetbrains.annotations.UnknownNullability;
import org.lwjgl.PointerBuffer;
import org.lwjgl.opengl.GL;
@ -13,6 +11,7 @@ import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.opengl.KHRShaderSubgroup;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.backend.FlwBackend;
import dev.engine_room.flywheel.backend.compile.core.Compilation;
@ -40,6 +39,8 @@ public final class GlCompat {
public static final boolean ALLOW_DSA = true;
public static final GlslVersion MAX_GLSL_VERSION = maxGlslVersion();
public static final boolean SUPPORTS_DSA = ALLOW_DSA && isDsaSupported();
public static final boolean SUPPORTS_INSTANCING = isInstancingSupported();
public static final boolean SUPPORTS_INDIRECT = isIndirectSupported();
@ -67,10 +68,11 @@ public final class GlCompat {
*/
public static void safeShaderSource(int glId, CharSequence source) {
try (MemoryStack stack = MemoryStack.stackPush()) {
final ByteBuffer sourceBuffer = stack.UTF8(source, true);
var sourceBuffer = MemoryUtil.memUTF8(source, true);
final PointerBuffer pointers = stack.mallocPointer(1);
pointers.put(sourceBuffer);
GL20C.nglShaderSource(glId, 1, pointers.address0(), 0);
MemoryUtil.memFree(sourceBuffer);
}
}
@ -161,8 +163,15 @@ public final class GlCompat {
&& CAPABILITIES.GL_ARB_multi_draw_indirect
&& CAPABILITIES.GL_ARB_shader_draw_parameters
&& CAPABILITIES.GL_ARB_shader_storage_buffer_object
&& CAPABILITIES.GL_ARB_shading_language_420pack
&& CAPABILITIES.GL_ARB_vertex_attrib_binding;
&& CAPABILITIES.GL_ARB_shading_language_420pack && CAPABILITIES.GL_ARB_vertex_attrib_binding && CAPABILITIES.GL_ARB_shader_image_load_store && CAPABILITIES.GL_ARB_shader_image_size;
}
private static boolean isDsaSupported() {
if (CAPABILITIES == null) {
return false;
}
return CAPABILITIES.GL_ARB_direct_state_access;
}
/**

View file

@ -1,11 +1,10 @@
package dev.engine_room.flywheel.backend.glsl.parse;
package dev.engine_room.flywheel.backend.glsl;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableList;
import dev.engine_room.flywheel.backend.glsl.SourceLines;
import dev.engine_room.flywheel.backend.glsl.span.Span;
public record Import(Span self, Span file) {

View file

@ -10,13 +10,8 @@ import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.mojang.datafixers.util.Pair;
import dev.engine_room.flywheel.backend.glsl.parse.Import;
import dev.engine_room.flywheel.backend.glsl.parse.ShaderField;
import dev.engine_room.flywheel.backend.glsl.parse.ShaderFunction;
import dev.engine_room.flywheel.backend.glsl.parse.ShaderStruct;
import dev.engine_room.flywheel.backend.glsl.span.Span;
import dev.engine_room.flywheel.backend.glsl.span.StringSpan;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
@ -35,39 +30,25 @@ public class SourceFile implements SourceComponent {
public final SourceLines source;
/**
* Function lookup by name.
*/
public final ImmutableMap<String, ShaderFunction> functions;
/**
* Struct lookup by name.
*/
public final ImmutableMap<String, ShaderStruct> structs;
/**
* Includes ordered as defined in the source.
*/
public final ImmutableList<Import> imports;
public final ImmutableMap<String, ShaderField> fields;
public final List<SourceFile> included;
public final String finalSource;
private SourceFile(ResourceLocation name, SourceLines source, ImmutableMap<String, ShaderFunction> functions, ImmutableMap<String, ShaderStruct> structs, ImmutableList<Import> imports, ImmutableMap<String, ShaderField> fields, List<SourceFile> included, String finalSource) {
private SourceFile(ResourceLocation name, SourceLines source, ImmutableList<Import> imports, List<SourceFile> included, String finalSource) {
this.name = name;
this.source = source;
this.functions = functions;
this.structs = structs;
this.imports = imports;
this.fields = fields;
this.included = included;
this.finalSource = finalSource;
}
public static LoadResult empty(ResourceLocation name) {
return new LoadResult.Success(new SourceFile(name, new SourceLines(name, ""), ImmutableMap.of(), ImmutableMap.of(), ImmutableList.of(), ImmutableMap.of(), ImmutableList.of(), ""));
return new LoadResult.Success(new SourceFile(name, new SourceLines(name, ""), ImmutableList.of(), ImmutableList.of(), ""));
}
public static LoadResult parse(Function<ResourceLocation, LoadResult> sourceFinder, ResourceLocation name, String stringSource) {
@ -106,12 +87,8 @@ public class SourceFile implements SourceComponent {
return new LoadResult.Failure(new LoadError.IncludeError(name, failures));
}
var functions = ShaderFunction.parseFunctions(source);
var structs = ShaderStruct.parseStructs(source);
var fields = ShaderField.parseFields(source);
var finalSource = generateFinalSource(imports, source);
return new LoadResult.Success(new SourceFile(name, source, functions, structs, imports, fields, included, finalSource));
return new LoadResult.Success(new SourceFile(name, source, imports, included, finalSource));
}
@Override
@ -129,13 +106,6 @@ public class SourceFile implements SourceComponent {
return name.toString();
}
public Span getLineSpan(int lineNo) {
int begin = source.lineStartIndex(lineNo);
int end = begin + source.lineString(lineNo)
.length();
return new StringSpan(source, begin, end);
}
public Span getLineSpanNoWhitespace(int line) {
int begin = source.lineStartIndex(line);
int end = begin + source.lineString(line)
@ -166,56 +136,6 @@ public class SourceFile implements SourceComponent {
return new StringSpan(source, begin, end);
}
/**
* Search this file and recursively search all imports to find a struct definition matching the given name.
*
* @param name The name of the struct to find.
* @return null if no definition matches the name.
*/
@Nullable
public ShaderStruct findStruct(String name) {
ShaderStruct struct = structs.get(name);
if (struct != null) {
return struct;
}
for (var include : included) {
var external = include.findStruct(name);
if (external != null) {
return external;
}
}
return null;
}
/**
* Search this file and recursively search all imports to find a function definition matching the given name.
*
* @param name The name of the function to find.
* @return null if no definition matches the name.
*/
@Nullable
public ShaderFunction findFunction(String name) {
ShaderFunction function = functions.get(name);
if (function != null) {
return function;
}
for (var include : included) {
var external = include.findFunction(name);
if (external != null) {
return external;
}
}
return null;
}
@Override
public String toString() {
return name.toString();

View file

@ -1,69 +0,0 @@
package dev.engine_room.flywheel.backend.glsl.parse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableMap;
import dev.engine_room.flywheel.backend.glsl.SourceLines;
import dev.engine_room.flywheel.backend.glsl.span.Span;
public class ShaderField {
public static final Pattern PATTERN = Pattern.compile("layout\\s*\\(location\\s*=\\s*(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)");
public final Span self;
public final Span location;
public final Span qualifierSpan;
@Nullable
public final Qualifier qualifier;
public final Span type;
public final Span name;
public ShaderField(Span self, Span location, Span qualifier, Span type, Span name) {
this.self = self;
this.location = location;
this.qualifierSpan = qualifier;
this.qualifier = Qualifier.fromSpan(qualifier);
this.type = type;
this.name = name;
}
/**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/
public static ImmutableMap<String, ShaderField> parseFields(SourceLines source) {
Matcher matcher = PATTERN.matcher(source);
ImmutableMap.Builder<String, ShaderField> fields = ImmutableMap.builder();
while (matcher.find()) {
Span self = Span.fromMatcher(source, matcher);
Span location = Span.fromMatcher(source, matcher, 1);
Span decoration = Span.fromMatcher(source, matcher, 2);
Span type = Span.fromMatcher(source, matcher, 3);
Span name = Span.fromMatcher(source, matcher, 4);
fields.put(location.get(), new ShaderField(self, location, decoration, type, name));
}
return fields.build();
}
public enum Qualifier {
IN,
OUT,
FLAT,
;
@Nullable
public static Qualifier fromSpan(Span span) {
return switch (span.toString()) {
case "in" -> IN;
case "out" -> OUT;
case "flat" -> FLAT;
default -> null;
};
}
}
}

View file

@ -1,133 +0,0 @@
package dev.engine_room.flywheel.backend.glsl.parse;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import dev.engine_room.flywheel.backend.glsl.SourceLines;
import dev.engine_room.flywheel.backend.glsl.span.ErrorSpan;
import dev.engine_room.flywheel.backend.glsl.span.Span;
import dev.engine_room.flywheel.backend.glsl.span.StringSpan;
public class ShaderFunction {
// https://regexr.com/60n3d
public static final Pattern PATTERN = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{");
public static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:(inout|in|out) )?(\\w+)\\s+(\\w+)");
public static final Pattern ASSIGNMENT_PATTERN = Pattern.compile("(\\w+)\\s*=");
public final Span self;
public final Span type;
public final Span name;
public final Span args;
public final Span body;
public final ImmutableList<ShaderVariable> parameters;
public ShaderFunction(Span self, Span type, Span name, Span args, Span body) {
this.self = self;
this.type = type;
this.name = name;
this.args = args;
this.body = body;
this.parameters = parseArguments();
}
/**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/
public static ImmutableMap<String, ShaderFunction> parseFunctions(SourceLines source) {
Matcher matcher = PATTERN.matcher(source);
Map<String, ShaderFunction> functions = new HashMap<>();
while (matcher.find()) {
Span type = Span.fromMatcher(source, matcher, 1);
Span name = Span.fromMatcher(source, matcher, 2);
Span args = Span.fromMatcher(source, matcher, 3);
int blockStart = matcher.end();
int blockEnd = findEndOfBlock(source, blockStart);
Span self;
Span body;
if (blockEnd > blockStart) {
self = new StringSpan(source, matcher.start(), blockEnd + 1);
body = new StringSpan(source, blockStart, blockEnd);
} else {
self = new ErrorSpan(source, matcher.start(), matcher.end());
body = new ErrorSpan(source, blockStart);
}
ShaderFunction function = new ShaderFunction(self, type, name, args, body);
functions.put(name.get(), function);
}
return ImmutableMap.copyOf(functions);
}
/**
* Given the position of an opening brace, scans through the source for a paired closing brace.
*/
private static int findEndOfBlock(CharSequence source, int start) {
int blockDepth = 0;
for (int i = start + 1; i < source.length(); i++) {
char ch = source.charAt(i);
if (ch == '{') {
blockDepth++;
} else if (ch == '}') {
blockDepth--;
}
if (blockDepth < 0) {
return i;
}
}
return -1;
}
public String call(String... args) {
return name + "(" + String.join(", ", args) + ")";
}
public Span getParameterType(int index) {
return parameters.get(index).type;
}
protected ImmutableList<ShaderVariable> parseArguments() {
if (args.isErr() || args.isEmpty()) return ImmutableList.of();
Matcher arguments = ARGUMENT_PATTERN.matcher(args.get());
ImmutableList.Builder<ShaderVariable> builder = ImmutableList.builder();
while (arguments.find()) {
Span self = Span.fromMatcher(args, arguments);
Span qualifier = Span.fromMatcher(args, arguments, 1);
Span type = Span.fromMatcher(args, arguments, 2);
Span name = Span.fromMatcher(args, arguments, 3);
builder.add(new ShaderVariable(self, qualifier, type, name));
}
return builder.build();
}
@Override
public String toString() {
String p = parameters.stream()
.map(variable -> variable.type)
.map(Span::get)
.collect(Collectors.joining(", "));
return type + " " + name + "(" + p + ")";
}
}

View file

@ -1,84 +0,0 @@
package dev.engine_room.flywheel.backend.glsl.parse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import dev.engine_room.flywheel.backend.glsl.SourceLines;
import dev.engine_room.flywheel.backend.glsl.span.Span;
public class ShaderStruct {
// https://regexr.com/61rpe
public static final Pattern PATTERN = Pattern.compile("struct\\s+([\\w_]*)\\s*\\{(.*?)}\\s*([\\w_]*)?\\s*;\\s", Pattern.DOTALL);
public final Span self;
public final Span name;
public final Span body;
public final Span variableName;
public final ImmutableList<StructField> fields;
public final ImmutableMap<String, Span> fields2Types;
public ShaderStruct(Span self, Span name, Span body, Span variableName) {
this.self = self;
this.name = name;
this.body = body;
this.variableName = variableName;
this.fields = parseFields();
this.fields2Types = createTypeLookup();
}
/**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/
public static ImmutableMap<String, ShaderStruct> parseStructs(SourceLines source) {
Matcher matcher = PATTERN.matcher(source);
ImmutableMap.Builder<String, ShaderStruct> structs = ImmutableMap.builder();
while (matcher.find()) {
Span self = Span.fromMatcher(source, matcher);
Span name = Span.fromMatcher(source, matcher, 1);
Span body = Span.fromMatcher(source, matcher, 2);
Span variableName = Span.fromMatcher(source, matcher, 3);
ShaderStruct shaderStruct = new ShaderStruct(self, name, body, variableName);
structs.put(name.get(), shaderStruct);
}
return structs.build();
}
private ImmutableList<StructField> parseFields() {
Matcher matcher = StructField.PATTERN.matcher(body);
ImmutableList.Builder<StructField> fields = ImmutableList.builder();
while (matcher.find()) {
Span field = Span.fromMatcher(body, matcher);
Span type = Span.fromMatcher(body, matcher, 1);
Span name = Span.fromMatcher(body, matcher, 2);
fields.add(new StructField(field, type, name));
}
return fields.build();
}
private ImmutableMap<String, Span> createTypeLookup() {
ImmutableMap.Builder<String, Span> lookup = ImmutableMap.builder();
for (StructField field : fields) {
lookup.put(field.name.get(), field.type);
}
return lookup.build();
}
@Override
public String toString() {
return "struct " + name;
}
}

View file

@ -1,47 +0,0 @@
package dev.engine_room.flywheel.backend.glsl.parse;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.backend.glsl.span.Span;
public class ShaderVariable {
public final Span self;
public final Span qualifierSpan;
@Nullable
public final Qualifier qualifier;
public final Span type;
public final Span name;
public ShaderVariable(Span self, Span qualifier, Span type, Span name) {
this.self = self;
this.qualifierSpan = qualifier;
this.qualifier = Qualifier.fromSpan(qualifierSpan);
this.type = type;
this.name = name;
}
@Override
public String toString() {
return type + " " + name;
}
public enum Qualifier {
NONE,
IN,
OUT,
INOUT;
@Nullable
public static Qualifier fromSpan(Span s) {
String span = s.toString();
return switch (span) {
case "" -> NONE;
case "in" -> IN;
case "inout" -> INOUT;
case "out" -> OUT;
default -> null;
};
}
}
}

View file

@ -1,24 +0,0 @@
package dev.engine_room.flywheel.backend.glsl.parse;
import java.util.regex.Pattern;
import dev.engine_room.flywheel.backend.glsl.span.Span;
public class StructField {
public static final Pattern PATTERN = Pattern.compile("(\\S+)\\s*(\\S+);");
public final Span self;
public final Span type;
public final Span name;
public StructField(Span self, Span type, Span name) {
this.self = self;
this.type = type;
this.name = name;
}
@Override
public String toString() {
return type + " " + name;
}
}

View file

@ -1,6 +1,8 @@
#include "flywheel:internal/packed_material.glsl"
#include "flywheel:internal/diffuse.glsl"
#include "flywheel:internal/colorizer.glsl"
#include "flywheel:internal/wavelet.glsl"
#include "flywheel:internal/depth.glsl"
// optimize discard usage
#if defined(GL_ARB_conservative_depth) && defined(_FLW_USE_DISCARD)
@ -17,8 +19,67 @@ in vec2 _flw_crumblingTexCoord;
flat in uvec2 _flw_ids;
#endif
#ifdef _FLW_OIT
uniform sampler2D _flw_depthRange;
uniform sampler2DArray _flw_coefficients;
uniform sampler2D _flw_blueNoise;
float tented_blue_noise(float normalizedDepth) {
float tentIn = abs(normalizedDepth * 2. - 1);
float tentIn2 = tentIn * tentIn;
float tentIn4 = tentIn2 * tentIn2;
float tent = 1 - (tentIn2 * tentIn4);
float b = texture(_flw_blueNoise, gl_FragCoord.xy / vec2(64)).r;
return b * tent;
}
float linear_depth() {
return linearize_depth(gl_FragCoord.z, _flw_cullData.znear, _flw_cullData.zfar);
}
float depth() {
float linearDepth = linear_depth();
vec2 depthRange = texelFetch(_flw_depthRange, ivec2(gl_FragCoord.xy), 0).rg;
float delta = depthRange.x + depthRange.y;
float depth = (linearDepth + depthRange.x) / delta;
return depth - tented_blue_noise(depth) * _flw_oitNoise;
}
#ifdef _FLW_DEPTH_RANGE
out vec2 _flw_depthRange_out;
#endif
#ifdef _FLW_COLLECT_COEFFS
out vec4 _flw_coeffs0;
out vec4 _flw_coeffs1;
out vec4 _flw_coeffs2;
out vec4 _flw_coeffs3;
#endif
#ifdef _FLW_EVALUATE
out vec4 _flw_accumulate;
#endif
#else
out vec4 _flw_outputColor;
#endif
float _flw_diffuseFactor() {
if (flw_material.cardinalLightingMode == 2u) {
return diffuseFromLightDirections(flw_vertexNormal);
@ -99,5 +160,47 @@ void _flw_main() {
}
#endif
_flw_outputColor = flw_fogFilter(color);
color = flw_fogFilter(color);
#ifdef _FLW_OIT
#ifdef _FLW_DEPTH_RANGE
float linearDepth = linear_depth();
// Pad the depth by some unbalanced epsilons because minecraft has a lot of single-quad tranparency.
// The unbalance means our fragment will be considered closer to the screen in the normalization,
// which helps prevent unnecessary noise as it'll be closer to the edge of our tent function.
_flw_depthRange_out = vec2(-linearDepth + 1e-5, linearDepth + 1e-2);
#endif
#ifdef _FLW_COLLECT_COEFFS
vec4[4] result;
result[0] = vec4(0.);
result[1] = vec4(0.);
result[2] = vec4(0.);
result[3] = vec4(0.);
add_transmittance(result, 1. - color.a, depth());
_flw_coeffs0 = result[0];
_flw_coeffs1 = result[1];
_flw_coeffs2 = result[2];
_flw_coeffs3 = result[3];
#endif
#ifdef _FLW_EVALUATE
float transmittance = signal_corrected_transmittance(_flw_coefficients, depth(), 1. - color.a);
_flw_accumulate = vec4(color.rgb * color.a, color.a) * transmittance;
#endif
#else
_flw_outputColor = color;
#endif
}

View file

@ -0,0 +1,9 @@
float linearize_depth(float d, float zNear, float zFar) {
float z_n = 2.0 * d - 1.0;
return 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear));
}
float delinearize_depth(float linearDepth, float zNear, float zFar) {
float z_n = (2.0 * zNear * zFar / linearDepth) - (zFar + zNear);
return 0.5 * (z_n / (zNear - zFar) + 1.0);
}

View file

@ -0,0 +1,4 @@
void main() {
vec2 vertices[3] = vec2[3](vec2(-1, -1), vec2(3, -1), vec2(-1, 3));
gl_Position = vec4(vertices[gl_VertexID], 0, 1);
}

View file

@ -1,12 +1,12 @@
const uint _FLW_BLOCKS_PER_SECTION = 18 * 18 * 18;
const uint _FLW_BLOCKS_PER_SECTION = 18u * 18u * 18u;
const uint _FLW_LIGHT_SIZE_BYTES = _FLW_BLOCKS_PER_SECTION;
const uint _FLW_SOLID_SIZE_BYTES = ((_FLW_BLOCKS_PER_SECTION + 31) / 32) * 4;
const uint _FLW_SOLID_SIZE_BYTES = ((_FLW_BLOCKS_PER_SECTION + 31u) / 32u) * 4u;
const uint _FLW_LIGHT_START_BYTES = _FLW_SOLID_SIZE_BYTES;
const uint _FLW_LIGHT_SECTION_SIZE_BYTES = _FLW_SOLID_SIZE_BYTES + _FLW_LIGHT_SIZE_BYTES;
const uint _FLW_SOLID_START_INTS = 0;
const uint _FLW_LIGHT_START_INTS = _FLW_SOLID_SIZE_BYTES / 4;
const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4;
const uint _FLW_SOLID_START_INTS = 0u;
const uint _FLW_LIGHT_START_INTS = _FLW_SOLID_SIZE_BYTES / 4u;
const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4u;
const uint _FLW_COMPLETELY_SOLID = 0x7FFFFFFu;
const float _FLW_EPSILON = 1e-5;
@ -29,39 +29,39 @@ bool _flw_nextLut(uint base, int coord, out uint next) {
// The base coordinate.
int start = int(_flw_indexLut(base));
// The width of the coordinate span.
uint size = _flw_indexLut(base + 1);
uint size = _flw_indexLut(base + 1u);
// Index of the coordinate in the span.
int i = coord - start;
if (i < 0 || i >= size) {
if (i < 0 || i >= int(size)) {
// We missed.
return true;
}
next = _flw_indexLut(base + 2 + i);
next = _flw_indexLut(base + 2u + uint(i));
return false;
}
bool _flw_chunkCoordToSectionIndex(ivec3 sectionPos, out uint index) {
uint first;
if (_flw_nextLut(0, sectionPos.y, first) || first == 0) {
if (_flw_nextLut(0u, sectionPos.y, first) || first == 0u) {
return true;
}
uint second;
if (_flw_nextLut(first, sectionPos.x, second) || second == 0) {
if (_flw_nextLut(first, sectionPos.x, second) || second == 0u) {
return true;
}
uint sectionIndex;
if (_flw_nextLut(second, sectionPos.z, sectionIndex) || sectionIndex == 0) {
if (_flw_nextLut(second, sectionPos.z, sectionIndex) || sectionIndex == 0u) {
return true;
}
// The index is written as 1-based so we can properly detect missing sections.
index = sectionIndex - 1;
index = sectionIndex - 1u;
return false;
}
@ -87,7 +87,7 @@ bool _flw_isSolid(uint sectionOffset, uvec3 blockInSectionPos) {
uint word = _flw_indexLight(sectionOffset + _FLW_SOLID_START_INTS + uintOffset);
return (word & (1u << bitInWordOffset)) != 0;
return (word & (1u << bitInWordOffset)) != 0u;
}
bool flw_lightFetch(ivec3 blockPos, out vec2 lightCoord) {
@ -98,7 +98,7 @@ bool flw_lightFetch(ivec3 blockPos, out vec2 lightCoord) {
// The offset of the section in the light buffer.
uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS;
uvec3 blockInSectionPos = (blockPos & 0xF) + 1;
uvec3 blockInSectionPos = uvec3((blockPos & 0xF) + 1);
lightCoord = vec2(_flw_lightAt(sectionOffset, blockInSectionPos)) * _FLW_LIGHT_NORMALIZER;
return true;
@ -106,7 +106,7 @@ bool flw_lightFetch(ivec3 blockPos, out vec2 lightCoord) {
uint _flw_fetchSolid3x3x3(uint sectionOffset, ivec3 blockInSectionPos) {
uint ret = 0;
uint ret = 0u;
// The formatter does NOT like these macros
// @formatter:off

View file

@ -0,0 +1,25 @@
#include "flywheel:internal/wavelet.glsl"
#include "flywheel:internal/depth.glsl"
#include "flywheel:internal/uniforms/frame.glsl"
out vec4 frag;
uniform sampler2D _flw_accumulate;
uniform sampler2D _flw_depthRange;
uniform sampler2DArray _flw_coefficients;
void main() {
vec4 texel = texelFetch(_flw_accumulate, ivec2(gl_FragCoord.xy), 0);
if (texel.a < 1e-5) {
discard;
}
float total_transmittance = total_transmittance(_flw_coefficients);
frag = vec4(texel.rgb / texel.a, 1. - total_transmittance);
float minDepth = -texelFetch(_flw_depthRange, ivec2(gl_FragCoord.xy), 0).r;
gl_FragDepth = delinearize_depth(minDepth, _flw_cullData.znear, _flw_cullData.zfar);
}

View file

@ -0,0 +1,56 @@
#include "flywheel:internal/uniforms/frame.glsl"
#include "flywheel:internal/wavelet.glsl"
#include "flywheel:internal/depth.glsl"
uniform sampler2D _flw_depthRange;
uniform sampler2DArray _flw_coefficients;
float eye_depth_from_normalized_transparency_depth(float tDepth) {
vec2 depthRange = texelFetch(_flw_depthRange, ivec2(gl_FragCoord.xy), 0).rg;
float delta = depthRange.x + depthRange.y;
return tDepth * delta - depthRange.x;
}
void main() {
float threshold = 0.0001;
//
// If transmittance an infinite depth is above the threshold, it doesn't ever become
// zero, so we can bail out.
//
float transmittance_at_far_depth = total_transmittance(_flw_coefficients);
if (transmittance_at_far_depth > threshold) {
discard;
}
float normalized_depth_at_zero_transmittance = 1.0;
float sample_depth = 0.5;
float delta = 0.25;
//
// Quick & Dirty way to binary search through the transmittance function
// looking for a value that's below the threshold.
//
int steps = 6;
for (int i = 0; i < steps; ++i) {
float transmittance = transmittance(_flw_coefficients, sample_depth);
if (transmittance <= threshold) {
normalized_depth_at_zero_transmittance = sample_depth;
sample_depth -= delta;
} else {
sample_depth += delta;
}
delta *= 0.5;
}
//
// Searching inside the transparency depth bounds, so have to transform that to
// a world-space linear-depth and that into a device depth we can output into
// the currently bound depth buffer.
//
float eyeDepth = eye_depth_from_normalized_transparency_depth(normalized_depth_at_zero_transmittance);
gl_FragDepth = delinearize_depth(eyeDepth, _flw_cullData.znear, _flw_cullData.zfar);
}

View file

@ -62,6 +62,8 @@ layout(std140) uniform _FlwFrameUniforms {
uint flw_cameraInBlock;
uint _flw_debugMode;
float _flw_oitNoise;
};
#define flw_renderOrigin (_flw_renderOrigin.xyz)

View file

@ -0,0 +1,185 @@
#define TRANSPARENCY_WAVELET_RANK 3
#define TRANSPARENCY_WAVELET_COEFFICIENT_COUNT 16
// -------------------------------------------------------------------------
// WRITING
// -------------------------------------------------------------------------
void add_to_index(inout vec4[4] coefficients, int index, float addend) {
coefficients[index >> 2][index & 3] = addend;
}
void add_absorbance(inout vec4[4] coefficients, float signal, float depth) {
depth *= float(TRANSPARENCY_WAVELET_COEFFICIENT_COUNT-1) / TRANSPARENCY_WAVELET_COEFFICIENT_COUNT;
int index = clamp(int(floor(depth * TRANSPARENCY_WAVELET_COEFFICIENT_COUNT)), 0, TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1);
index += TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1;
for (int i = 0; i < (TRANSPARENCY_WAVELET_RANK+1); ++i) {
int power = TRANSPARENCY_WAVELET_RANK - i;
int new_index = (index - 1) >> 1;
float k = float((new_index + 1) & ((1 << power) - 1));
int wavelet_sign = ((index & 1) << 1) - 1;
float wavelet_phase = ((index + 1) & 1) * exp2(-power);
float addend = fma(fma(-exp2(-power), k, depth), wavelet_sign, wavelet_phase) * exp2(power * 0.5) * signal;
add_to_index(coefficients, new_index, addend);
index = new_index;
}
float addend = fma(signal, -depth, signal);
add_to_index(coefficients, TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1, addend);
}
void add_transmittance(inout vec4[4] coefficients, float transmittance, float depth) {
float absorbance = -log(max(transmittance, 0.00001));// transforming the signal from multiplicative transmittance to additive absorbance
add_absorbance(coefficients, absorbance, depth);
}
// -------------------------------------------------------------------------
// READING
// -------------------------------------------------------------------------
// TODO: maybe we could reduce the number of texel fetches below?
float get_coefficients(in sampler2DArray coefficients, int index) {
return texelFetch(coefficients, ivec3(gl_FragCoord.xy, index >> 2), 0)[index & 3];
}
/// Compute the total absorbance, as if at infinite depth.
float total_absorbance(in sampler2DArray coefficients) {
float scale_coefficient = get_coefficients(coefficients, TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1);
if (scale_coefficient == 0) {
return 0;
}
int index_b = TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1;
index_b += TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1;
float b = scale_coefficient;
for (int i = 0; i < (TRANSPARENCY_WAVELET_RANK+1); ++i) {
int power = TRANSPARENCY_WAVELET_RANK - i;
int new_index_b = (index_b - 1) >> 1;
int wavelet_sign_b = ((index_b & 1) << 1) - 1;
float coeff_b = get_coefficients(coefficients, new_index_b);
b -= exp2(float(power) * 0.5) * coeff_b * wavelet_sign_b;
index_b = new_index_b;
}
return b;
}
/// Compute the absorbance at a given normalized depth.
float absorbance(in sampler2DArray coefficients, float depth) {
float scale_coefficient = get_coefficients(coefficients, TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1);
if (scale_coefficient == 0) {
return 0;
}
depth *= float(TRANSPARENCY_WAVELET_COEFFICIENT_COUNT-1) / TRANSPARENCY_WAVELET_COEFFICIENT_COUNT;
float coefficient_depth = depth * TRANSPARENCY_WAVELET_COEFFICIENT_COUNT;
int index_b = clamp(int(floor(coefficient_depth)), 0, TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1);
bool sample_a = index_b >= 1;
int index_a = sample_a ? (index_b - 1) : index_b;
index_b += TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1;
index_a += TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1;
float b = scale_coefficient;
float a = sample_a ? scale_coefficient : 0;
for (int i = 0; i < (TRANSPARENCY_WAVELET_RANK+1); ++i) {
int power = TRANSPARENCY_WAVELET_RANK - i;
int new_index_b = (index_b - 1) >> 1;
int wavelet_sign_b = ((index_b & 1) << 1) - 1;
float coeff_b = get_coefficients(coefficients, new_index_b);
b -= exp2(float(power) * 0.5) * coeff_b * wavelet_sign_b;
index_b = new_index_b;
if (sample_a) {
int new_index_a = (index_a - 1) >> 1;
int wavelet_sign_a = ((index_a & 1) << 1) - 1;
float coeff_a = (new_index_a == new_index_b) ? coeff_b : get_coefficients(coefficients, new_index_a);
a -= exp2(float(power) * 0.5) * coeff_a * wavelet_sign_a;
index_a = new_index_a;
}
}
float t = coefficient_depth >= TRANSPARENCY_WAVELET_COEFFICIENT_COUNT ? 1.0 : fract(coefficient_depth);
return mix(a, b, t);
}
/// Compute the absorbance at a given normalized depth,
/// correcting for self-occlusion by undoing the previously recorded absorbance event.
float signal_corrected_absorbance(in sampler2DArray coefficients, float depth, float signal) {
float scale_coefficient = get_coefficients(coefficients, TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1);
if (scale_coefficient == 0) {
return 0;
}
depth *= float(TRANSPARENCY_WAVELET_COEFFICIENT_COUNT-1) / TRANSPARENCY_WAVELET_COEFFICIENT_COUNT;
float scale_coefficient_addend = fma(signal, -depth, signal);
scale_coefficient -= scale_coefficient_addend;
float coefficient_depth = depth * TRANSPARENCY_WAVELET_COEFFICIENT_COUNT;
int index_b = clamp(int(floor(coefficient_depth)), 0, TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1);
bool sample_a = index_b >= 1;
int index_a = sample_a ? (index_b - 1) : index_b;
index_b += TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1;
index_a += TRANSPARENCY_WAVELET_COEFFICIENT_COUNT - 1;
float b = scale_coefficient;
float a = sample_a ? scale_coefficient : 0;
for (int i = 0; i < (TRANSPARENCY_WAVELET_RANK+1); ++i) {
int power = TRANSPARENCY_WAVELET_RANK - i;
int new_index_b = (index_b - 1) >> 1;
int wavelet_sign_b = ((index_b & 1) << 1) - 1;
float coeff_b = get_coefficients(coefficients, new_index_b);
float wavelet_phase_b = ((index_b + 1) & 1) * exp2(-power);
float k = float((new_index_b + 1) & ((1 << power) - 1));
float addend = fma(fma(-exp2(-power), k, depth), wavelet_sign_b, wavelet_phase_b) * exp2(power * 0.5) * signal;
coeff_b -= addend;
b -= exp2(float(power) * 0.5) * coeff_b * wavelet_sign_b;
index_b = new_index_b;
if (sample_a) {
int new_index_a = (index_a - 1) >> 1;
int wavelet_sign_a = ((index_a & 1) << 1) - 1;
float coeff_a = (new_index_a == new_index_b) ? coeff_b : get_coefficients(coefficients, new_index_a);// No addend here on purpose, the original signal didn't contribute to this coefficient
a -= exp2(float(power) * 0.5) * coeff_a * wavelet_sign_a;
index_a = new_index_a;
}
}
float t = coefficient_depth >= TRANSPARENCY_WAVELET_COEFFICIENT_COUNT ? 1.0 : fract(coefficient_depth);
return mix(a, b, t);
}
// Helpers below to deal directly in transmittance.
#define ABSORBANCE_TO_TRANSMITTANCE(a) clamp(exp(-(a)), 0., 1.)
float total_transmittance(in sampler2DArray coefficients) {
return ABSORBANCE_TO_TRANSMITTANCE(total_absorbance(coefficients));
}
float transmittance(in sampler2DArray coefficients, float depth) {
return ABSORBANCE_TO_TRANSMITTANCE(absorbance(coefficients, depth));
}
float signal_corrected_transmittance(in sampler2DArray coefficients, float depth, float signal) {
return ABSORBANCE_TO_TRANSMITTANCE(signal_corrected_absorbance(coefficients, depth, signal));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -3,6 +3,9 @@ package dev.engine_room.flywheel.lib.backend;
import java.util.Objects;
import java.util.function.BooleanSupplier;
import java.util.function.Function;
import java.util.function.IntSupplier;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.Engine;
@ -11,12 +14,12 @@ import net.minecraft.world.level.LevelAccessor;
public final class SimpleBackend implements Backend {
private final Function<LevelAccessor, Engine> engineFactory;
private final int priority;
private final IntSupplier priority;
private final BooleanSupplier isSupported;
public SimpleBackend(int priority, Function<LevelAccessor, Engine> engineFactory, BooleanSupplier isSupported) {
this.priority = priority;
public SimpleBackend(Function<LevelAccessor, Engine> engineFactory, IntSupplier priority, BooleanSupplier isSupported) {
this.engineFactory = engineFactory;
this.priority = priority;
this.isSupported = isSupported;
}
@ -31,7 +34,7 @@ public final class SimpleBackend implements Backend {
@Override
public int priority() {
return priority;
return priority.getAsInt();
}
@Override
@ -40,8 +43,10 @@ public final class SimpleBackend implements Backend {
}
public static final class Builder {
@Nullable
private Function<LevelAccessor, Engine> engineFactory;
private int priority = 0;
private IntSupplier priority = () -> 0;
@Nullable
private BooleanSupplier isSupported;
public Builder engineFactory(Function<LevelAccessor, Engine> engineFactory) {
@ -50,6 +55,10 @@ public final class SimpleBackend implements Backend {
}
public Builder priority(int priority) {
return priority(() -> priority);
}
public Builder priority(IntSupplier priority) {
this.priority = priority;
return this;
}
@ -63,7 +72,7 @@ public final class SimpleBackend implements Backend {
Objects.requireNonNull(engineFactory);
Objects.requireNonNull(isSupported);
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(priority, engineFactory, isSupported));
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(engineFactory, priority, isSupported));
}
}
}

View file

@ -2,12 +2,12 @@ package dev.engine_room.flywheel.lib.instance;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.layout.FloatRepr;
import dev.engine_room.flywheel.api.layout.IntegerRepr;
import dev.engine_room.flywheel.api.layout.LayoutBuilder;
import dev.engine_room.flywheel.lib.util.ExtraMemoryOps;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
public final class InstanceTypes {
public static final InstanceType<TransformedInstance> TRANSFORMED = SimpleInstanceType.builder(TransformedInstance::new)
@ -26,8 +26,8 @@ public final class InstanceTypes {
ExtraMemoryOps.put2x16(ptr + 8, instance.light);
ExtraMemoryOps.putMatrix4f(ptr + 12, instance.pose);
})
.vertexShader(Flywheel.rl("instance/transformed.vert"))
.cullShader(Flywheel.rl("instance/cull/transformed.glsl"))
.vertexShader(ResourceUtil.rl("instance/transformed.vert"))
.cullShader(ResourceUtil.rl("instance/cull/transformed.glsl"))
.build();
public static final InstanceType<PosedInstance> POSED = SimpleInstanceType.builder(PosedInstance::new)
@ -48,8 +48,8 @@ public final class InstanceTypes {
ExtraMemoryOps.putMatrix4f(ptr + 12, instance.pose);
ExtraMemoryOps.putMatrix3f(ptr + 76, instance.normal);
})
.vertexShader(Flywheel.rl("instance/posed.vert"))
.cullShader(Flywheel.rl("instance/cull/posed.glsl"))
.vertexShader(ResourceUtil.rl("instance/posed.vert"))
.cullShader(ResourceUtil.rl("instance/cull/posed.glsl"))
.build();
public static final InstanceType<OrientedInstance> ORIENTED = SimpleInstanceType.builder(OrientedInstance::new)
@ -76,8 +76,8 @@ public final class InstanceTypes {
MemoryUtil.memPutFloat(ptr + 32, instance.pivotZ);
ExtraMemoryOps.putQuaternionf(ptr + 36, instance.rotation);
})
.vertexShader(Flywheel.rl("instance/oriented.vert"))
.cullShader(Flywheel.rl("instance/cull/oriented.glsl"))
.vertexShader(ResourceUtil.rl("instance/oriented.vert"))
.cullShader(ResourceUtil.rl("instance/cull/oriented.glsl"))
.build();
public static final InstanceType<ShadowInstance> SHADOW = SimpleInstanceType.builder(ShadowInstance::new)
@ -99,8 +99,8 @@ public final class InstanceTypes {
MemoryUtil.memPutFloat(ptr + 28, instance.alpha);
MemoryUtil.memPutFloat(ptr + 32, instance.radius);
})
.vertexShader(Flywheel.rl("instance/shadow.vert"))
.cullShader(Flywheel.rl("instance/cull/shadow.glsl"))
.vertexShader(ResourceUtil.rl("instance/shadow.vert"))
.cullShader(ResourceUtil.rl("instance/cull/shadow.glsl"))
.build();
private InstanceTypes() {

View file

@ -1,25 +1,25 @@
package dev.engine_room.flywheel.lib.material;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.material.CutoutShader;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
public final class CutoutShaders {
/**
* Do not discard any fragments based on alpha.
*/
public static final CutoutShader OFF = new SimpleCutoutShader(Flywheel.rl("cutout/off.glsl"));
public static final CutoutShader OFF = new SimpleCutoutShader(ResourceUtil.rl("cutout/off.glsl"));
/**
* Discard fragments with alpha close to or equal to zero.
*/
public static final CutoutShader EPSILON = new SimpleCutoutShader(Flywheel.rl("cutout/epsilon.glsl"));
public static final CutoutShader EPSILON = new SimpleCutoutShader(ResourceUtil.rl("cutout/epsilon.glsl"));
/**
* Discard fragments with alpha less than to 0.1.
*/
public static final CutoutShader ONE_TENTH = new SimpleCutoutShader(Flywheel.rl("cutout/one_tenth.glsl"));
public static final CutoutShader ONE_TENTH = new SimpleCutoutShader(ResourceUtil.rl("cutout/one_tenth.glsl"));
/**
* Discard fragments with alpha less than to 0.5.
*/
public static final CutoutShader HALF = new SimpleCutoutShader(Flywheel.rl("cutout/half.glsl"));
public static final CutoutShader HALF = new SimpleCutoutShader(ResourceUtil.rl("cutout/half.glsl"));
private CutoutShaders() {
}

View file

@ -1,12 +1,12 @@
package dev.engine_room.flywheel.lib.material;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.material.FogShader;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
public final class FogShaders {
public static final FogShader NONE = new SimpleFogShader(Flywheel.rl("fog/none.glsl"));
public static final FogShader LINEAR = new SimpleFogShader(Flywheel.rl("fog/linear.glsl"));
public static final FogShader LINEAR_FADE = new SimpleFogShader(Flywheel.rl("fog/linear_fade.glsl"));
public static final FogShader NONE = new SimpleFogShader(ResourceUtil.rl("fog/none.glsl"));
public static final FogShader LINEAR = new SimpleFogShader(ResourceUtil.rl("fog/linear.glsl"));
public static final FogShader LINEAR_FADE = new SimpleFogShader(ResourceUtil.rl("fog/linear_fade.glsl"));
private FogShaders() {
}

View file

@ -1,12 +1,12 @@
package dev.engine_room.flywheel.lib.material;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.material.LightShader;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
public final class LightShaders {
public static final LightShader SMOOTH_WHEN_EMBEDDED = new SimpleLightShader(Flywheel.rl("light/smooth_when_embedded.glsl"));
public static final LightShader SMOOTH = new SimpleLightShader(Flywheel.rl("light/smooth.glsl"));
public static final LightShader FLAT = new SimpleLightShader(Flywheel.rl("light/flat.glsl"));
public static final LightShader SMOOTH_WHEN_EMBEDDED = new SimpleLightShader(ResourceUtil.rl("light/smooth_when_embedded.glsl"));
public static final LightShader SMOOTH = new SimpleLightShader(ResourceUtil.rl("light/smooth.glsl"));
public static final LightShader FLAT = new SimpleLightShader(ResourceUtil.rl("light/flat.glsl"));
private LightShaders() {
}

View file

@ -32,20 +32,20 @@ public final class Materials {
.build();
public static final Material TRANSLUCENT_BLOCK = SimpleMaterial.builder()
.transparency(Transparency.TRANSLUCENT)
.transparency(Transparency.ORDER_INDEPENDENT)
.build();
public static final Material TRANSLUCENT_UNSHADED_BLOCK = SimpleMaterial.builder()
.transparency(Transparency.TRANSLUCENT)
.transparency(Transparency.ORDER_INDEPENDENT)
.diffuse(false)
.build();
public static final Material TRIPWIRE_BLOCK = SimpleMaterial.builder()
.cutout(CutoutShaders.ONE_TENTH)
.transparency(Transparency.TRANSLUCENT)
.transparency(Transparency.ORDER_INDEPENDENT)
.build();
public static final Material TRIPWIRE_UNSHADED_BLOCK = SimpleMaterial.builder()
.cutout(CutoutShaders.ONE_TENTH)
.transparency(Transparency.TRANSLUCENT)
.transparency(Transparency.ORDER_INDEPENDENT)
.diffuse(false)
.build();

View file

@ -1,17 +1,17 @@
package dev.engine_room.flywheel.lib.material;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.material.MaterialShaders;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
public final class StandardMaterialShaders {
public static final MaterialShaders DEFAULT = new SimpleMaterialShaders(
Flywheel.rl("material/default.vert"), Flywheel.rl("material/default.frag"));
ResourceUtil.rl("material/default.vert"), ResourceUtil.rl("material/default.frag"));
public static final MaterialShaders WIREFRAME = new SimpleMaterialShaders(Flywheel.rl("material/wireframe.vert"), Flywheel.rl("material/wireframe.frag"));
public static final MaterialShaders WIREFRAME = new SimpleMaterialShaders(ResourceUtil.rl("material/wireframe.vert"), ResourceUtil.rl("material/wireframe.frag"));
public static final MaterialShaders LINE = new SimpleMaterialShaders(Flywheel.rl("material/lines.vert"), Flywheel.rl("material/lines.frag"));
public static final MaterialShaders LINE = new SimpleMaterialShaders(ResourceUtil.rl("material/lines.vert"), ResourceUtil.rl("material/lines.frag"));
public static final MaterialShaders GLINT = new SimpleMaterialShaders(Flywheel.rl("material/glint.vert"), Flywheel.rl("material/default.frag"));
public static final MaterialShaders GLINT = new SimpleMaterialShaders(ResourceUtil.rl("material/glint.vert"), ResourceUtil.rl("material/default.frag"));
private StandardMaterialShaders() {
}

View file

@ -0,0 +1,24 @@
package dev.engine_room.flywheel.lib.model;
import java.util.Collections;
import java.util.List;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import dev.engine_room.flywheel.api.model.Model;
public final class EmptyModel implements Model {
private static final Vector4fc BOUNDING_SPHERE = new Vector4f(0, 0, 0, 0);
public static final EmptyModel INSTANCE = new EmptyModel();
@Override
public List<ConfiguredMesh> meshes() {
return Collections.emptyList();
}
@Override
public Vector4fc boundingSphere() {
return BOUNDING_SPHERE;
}
}

View file

@ -1,7 +1,6 @@
package dev.engine_room.flywheel.lib.model;
import org.jetbrains.annotations.UnknownNullability;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import org.lwjgl.system.MemoryUtil;
@ -32,14 +31,37 @@ public final class LineModelBuilder {
private MemoryBlock data;
private int vertexCount = 0;
public LineModelBuilder(int segmentCount) {
public LineModelBuilder() {
}
public LineModelBuilder(int initialSegmentCount) {
ensureCapacity(initialSegmentCount);
}
public void ensureCapacity(int segmentCount) {
if (segmentCount < 0) {
throw new IllegalArgumentException("Segment count must be greater than or equal to 0");
} else if (segmentCount == 0) {
return;
}
if (data == null) {
vertexView = new FullVertexView();
data = MemoryBlock.mallocTracked(segmentCount * 4 * vertexView.stride());
vertexView.ptr(data.ptr());
vertexCount = 0;
} else {
long requiredCapacity = (vertexCount + segmentCount * 4) * vertexView.stride();
if (requiredCapacity > data.size()) {
data = data.realloc(requiredCapacity);
vertexView.ptr(data.ptr());
}
}
}
public LineModelBuilder line(float x1, float y1, float z1, float x2, float y2, float z2) {
ensureCapacity(vertexCount + 4);
ensureCapacity(1);
// We'll use the normal to figure out the orientation of the line in the vertex shader.
float dx = x2 - x1;
@ -79,8 +101,19 @@ public final class LineModelBuilder {
}
public Model build() {
vertexView.vertexCount(vertexCount);
if (vertexCount == 0) {
return EmptyModel.INSTANCE;
}
long requiredCapacity = vertexCount * vertexView.stride();
if (data.size() > requiredCapacity) {
data = data.realloc(requiredCapacity);
vertexView.ptr(data.ptr());
}
vertexView.nativeMemoryOwner(data);
vertexView.vertexCount(vertexCount);
var boundingSphere = ModelUtil.computeBoundingSphere(vertexView);
boundingSphere.w += 0.1f; // make the bounding sphere a little bigger to account for line width
@ -94,17 +127,6 @@ public final class LineModelBuilder {
return model;
}
private void ensureCapacity(int vertexCount) {
if (data == null) {
vertexView = new FullVertexView();
data = MemoryBlock.mallocTracked(vertexCount * vertexView.stride());
vertexView.ptr(data.ptr());
} else if (vertexCount * vertexView.stride() > data.size()) {
data = data.realloc(vertexCount * vertexView.stride());
vertexView.ptr(data.ptr());
}
}
private static class LineMesh implements Mesh {
private static final IndexSequence INDEX_SEQUENCE = (ptr, count) -> {
int numVertices = 2 * count / 3;
@ -124,9 +146,9 @@ public final class LineModelBuilder {
}
};
private final VertexList vertexList;
private final Vector4f boundingSphere;
private final Vector4fc boundingSphere;
public LineMesh(VertexList vertexList, Vector4f boundingSphere) {
public LineMesh(VertexList vertexList, Vector4fc boundingSphere) {
this.vertexList = vertexList;
this.boundingSphere = boundingSphere;
}

View file

@ -15,6 +15,10 @@ public final class ResourceUtil {
private ResourceUtil() {
}
public static ResourceLocation rl(String path) {
return ResourceLocation.fromNamespaceAndPath(Flywheel.ID, path);
}
/**
* Same as {@link ResourceLocation#parse(String)}, but defaults to Flywheel namespace.
*/

View file

@ -2,10 +2,10 @@ package dev.engine_room.flywheel.impl;
import java.util.ArrayList;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.impl.visualization.VisualizationManagerImpl;
import dev.engine_room.flywheel.lib.backend.SimpleBackend;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.resources.ResourceLocation;
@ -15,7 +15,7 @@ public final class BackendManagerImpl {
throw new UnsupportedOperationException("Cannot create engine when backend is off.");
})
.supported(() -> true)
.register(Flywheel.rl("off"));
.register(ResourceUtil.rl("off"));
private static Backend backend = OFF_BACKEND;

View file

@ -4,6 +4,8 @@ import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.backend.BackendConfig;
public interface FlwConfig {
String DEFAULT_BACKEND_STR = "DEFAULT";
FlwConfig INSTANCE = FlwImplXplat.INSTANCE.getConfig();
Backend backend();

View file

@ -3,7 +3,7 @@ package dev.engine_room.flywheel.impl.event;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.backend.RenderContext;
import net.minecraft.client.Camera;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;

View file

@ -9,8 +9,6 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.At.Shift;
import org.spongepowered.asm.mixin.injection.Group;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@ -83,16 +81,6 @@ abstract class LevelRendererMixin {
}
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/OutlineBufferSource;endOutlineBatch()V", ordinal = 0))
private void flywheel$afterBlockEntities(CallbackInfo ci) {
if (flywheel$renderContext != null) {
VisualizationManager manager = VisualizationManager.get(level);
if (manager != null) {
manager.renderDispatcher().afterBlockEntities(flywheel$renderContext);
}
}
}
@Inject(method = "renderLevel", at = @At(value = "INVOKE_STRING", target = "Lnet/minecraft/util/profiling/ProfilerFiller;popPush(Ljava/lang/String;)V", args = "ldc=destroyProgress"))
private void flywheel$beforeRenderCrumbling(CallbackInfo ci) {
if (flywheel$renderContext != null) {
@ -103,28 +91,6 @@ abstract class LevelRendererMixin {
}
}
@Group(name = "afterParticles", min = 2, max = 3)
@Inject(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/ParticleEngine;render(Lnet/minecraft/client/renderer/LightTexture;Lnet/minecraft/client/Camera;F)V", shift = Shift.AFTER))
private void flywheel$afterParticles$fabric(CallbackInfo ci) {
if (flywheel$renderContext != null) {
VisualizationManager manager = VisualizationManager.get(level);
if (manager != null) {
manager.renderDispatcher().afterParticles(flywheel$renderContext);
}
}
}
@Group(name = "afterParticles")
@Inject(method = "renderLevel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/ParticleEngine;render(Lnet/minecraft/client/renderer/LightTexture;Lnet/minecraft/client/Camera;FLnet/minecraft/client/renderer/culling/Frustum;Ljava/util/function/Predicate;)V", shift = Shift.AFTER))
private void flywheel$afterParticles$neoforge(CallbackInfo ci) {
if (flywheel$renderContext != null) {
VisualizationManager manager = VisualizationManager.get(level);
if (manager != null) {
manager.renderDispatcher().afterParticles(flywheel$renderContext);
}
}
}
@Inject(method = "renderEntity", at = @At("HEAD"), cancellable = true)
private void flywheel$decideNotToRenderEntity(Entity entity, double camX, double camY, double camZ, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, CallbackInfo ci) {
if (VisualizationManager.supportsVisualization(entity.level()) && VisualizationHelper.skipVanillaRender(entity)) {

View file

@ -9,16 +9,15 @@ import org.jetbrains.annotations.Nullable;
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import dev.engine_room.flywheel.api.RenderContext;
import dev.engine_room.flywheel.api.backend.BackendManager;
import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.backend.RenderContext;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.visual.DynamicVisual;
import dev.engine_room.flywheel.api.visual.Effect;
import dev.engine_room.flywheel.api.visual.TickableVisual;
import dev.engine_room.flywheel.api.visualization.VisualManager;
import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.api.visualization.VisualizationLevel;
import dev.engine_room.flywheel.api.visualization.VisualizationManager;
import dev.engine_room.flywheel.impl.FlwConfig;
@ -74,17 +73,16 @@ public class VisualizationManagerImpl implements VisualizationManager {
private final Plan<RenderContext> framePlan;
private final Plan<TickableVisual.Context> tickPlan;
private boolean canEngineRender;
private VisualizationManagerImpl(LevelAccessor level) {
taskExecutor = FlwTaskExecutor.get();
engine = BackendManager.currentBackend()
.createEngine(level);
frameLimiter = createUpdateLimiter();
var blockEntitiesStorage = new BlockEntityStorage(engine.createVisualizationContext(VisualType.BLOCK_ENTITY));
var entitiesStorage = new EntityStorage(engine.createVisualizationContext(VisualType.ENTITY));
var effectsStorage = new EffectStorage(engine.createVisualizationContext(VisualType.EFFECT));
var visualizationContext = engine.createVisualizationContext();
var blockEntitiesStorage = new BlockEntityStorage(visualizationContext);
var entitiesStorage = new EntityStorage(visualizationContext);
var effectsStorage = new EffectStorage(visualizationContext);
blockEntities = new VisualManagerImpl<>(blockEntitiesStorage);
entities = new VisualManagerImpl<>(entitiesStorage);
@ -241,25 +239,16 @@ public class VisualizationManagerImpl implements VisualizationManager {
frameFlag.lower();
frameLimiter.tick();
canEngineRender = false;
framePlan.execute(taskExecutor, context);
}
private void ensureCanRender(RenderContext context) {
taskExecutor.syncUntil(frameFlag::isRaised);
if (!canEngineRender) {
engine.setupRender(context);
canEngineRender = true;
}
}
/**
* Draw all visuals of the given type.
*/
private void render(RenderContext context, VisualType visualType) {
ensureCanRender(context);
engine.render(context, visualType);
private void render(RenderContext context) {
taskExecutor.syncUntil(frameFlag::isRaised);
engine.render(context);
}
private void renderCrumbling(RenderContext context, Long2ObjectMap<SortedSet<BlockDestructionProgress>> destructionProgress) {
@ -267,8 +256,6 @@ public class VisualizationManagerImpl implements VisualizationManager {
return;
}
ensureCanRender(context);
List<Engine.CrumblingBlock> crumblingBlocks = new ArrayList<>();
for (var entry : destructionProgress.long2ObjectEntrySet()) {
@ -337,25 +324,15 @@ public class VisualizationManagerImpl implements VisualizationManager {
beginFrame(ctx);
}
@Override
public void afterBlockEntities(RenderContext ctx) {
render(ctx, VisualType.BLOCK_ENTITY);
}
@Override
public void afterEntities(RenderContext ctx) {
render(ctx, VisualType.ENTITY);
render(ctx);
}
@Override
public void beforeCrumbling(RenderContext ctx, Long2ObjectMap<SortedSet<BlockDestructionProgress>> destructionProgress) {
renderCrumbling(ctx, destructionProgress);
}
@Override
public void afterParticles(RenderContext ctx) {
render(ctx, VisualType.EFFECT);
}
}
private record CrumblingBlockImpl(BlockPos pos, int progress, List<Instance> instances) implements Engine.CrumblingBlock {

View file

@ -6,13 +6,13 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.List;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import net.minecraft.resources.ResourceLocation;
public class TestBase {
public static final ResourceLocation FLW_A = Flywheel.rl("a.glsl");
public static final ResourceLocation FLW_B = Flywheel.rl("b.glsl");
public static final ResourceLocation FLW_C = Flywheel.rl("c.glsl");
public static final ResourceLocation FLW_A = ResourceUtil.rl("a.glsl");
public static final ResourceLocation FLW_B = ResourceUtil.rl("b.glsl");
public static final ResourceLocation FLW_C = ResourceUtil.rl("c.glsl");
public static <T> T assertSingletonList(List<T> list) {
assertEquals(1, list.size());

View file

@ -7,8 +7,6 @@ import org.junit.jupiter.api.Test;
import com.google.common.collect.ImmutableList;
import dev.engine_room.flywheel.backend.glsl.parse.Import;
public class TestShaderSourceLoading extends TestBase {
@Test
void testSimpleFind() {

View file

@ -1,6 +1,7 @@
package dev.engine_room.flywheel.backend.compile;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import dev.engine_room.flywheel.backend.NoiseTextures;
import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
@ -8,7 +9,7 @@ import net.minecraft.server.packs.resources.ResourceManager;
public final class FlwProgramsReloader implements SimpleSynchronousResourceReloadListener {
public static final FlwProgramsReloader INSTANCE = new FlwProgramsReloader();
public static final ResourceLocation ID = Flywheel.rl("programs");
public static final ResourceLocation ID = ResourceUtil.rl("programs");
private FlwProgramsReloader() {
}
@ -16,6 +17,7 @@ public final class FlwProgramsReloader implements SimpleSynchronousResourceReloa
@Override
public void onResourceManagerReload(ResourceManager manager) {
FlwPrograms.reload(manager);
NoiseTextures.reload(manager);
}
@Override

View file

@ -4,7 +4,7 @@ import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.lib.util.ResourceUtil;
import net.fabricmc.fabric.api.resource.ResourceReloadListenerKeys;
import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener;
import net.minecraft.client.Minecraft;
@ -32,7 +32,7 @@ public final class PartialModelEventHandler {
public static final class ReloadListener implements SimpleSynchronousResourceReloadListener {
public static final ReloadListener INSTANCE = new ReloadListener();
public static final ResourceLocation ID = Flywheel.rl("partial_models");
public static final ResourceLocation ID = ResourceUtil.rl("partial_models");
public static final List<ResourceLocation> DEPENDENCIES = List.of(ResourceReloadListenerKeys.MODELS);
private ReloadListener() {

View file

@ -38,8 +38,8 @@ public class FabricFlwConfig implements FlwConfig {
private final File file;
// Don't actually default to off, we'll find the true default in #load
public Backend backend = BackendManager.offBackend();
public boolean useDefaultBackend = true;
public boolean limitUpdates = LIMIT_UPDATES_DEFAULT;
public int workerThreads = WORKER_THREADS_DEFAULT;
@ -51,6 +51,10 @@ public class FabricFlwConfig implements FlwConfig {
@Override
public Backend backend() {
if (useDefaultBackend) {
return BackendManager.defaultBackend();
}
return backend;
}
@ -70,10 +74,6 @@ public class FabricFlwConfig implements FlwConfig {
}
public void load() {
// Grab the default backend here because this object is constructed
// very early in flywheel loading and not all backends may be registered
backend = BackendManager.defaultBackend();
if (file.exists()) {
try (FileReader reader = new FileReader(file)) {
fromJson(JsonParser.parseReader(reader));
@ -96,7 +96,8 @@ public class FabricFlwConfig implements FlwConfig {
public void fromJson(JsonElement json) {
if (!(json instanceof JsonObject object)) {
FlwImpl.CONFIG_LOGGER.warn("Config JSON must be an object");
backend = BackendManager.defaultBackend();
backend = BackendManager.offBackend();
useDefaultBackend = true;
limitUpdates = LIMIT_UPDATES_DEFAULT;
workerThreads = WORKER_THREADS_DEFAULT;
return;
@ -114,8 +115,15 @@ public class FabricFlwConfig implements FlwConfig {
if (backendJson instanceof JsonPrimitive primitive && primitive.isString()) {
var value = primitive.getAsString();
if (value.equals(DEFAULT_BACKEND_STR)) {
backend = BackendManager.offBackend();
useDefaultBackend = true;
return;
}
try {
this.backend = Backend.REGISTRY.getOrThrow(ResourceLocation.parse(value));
useDefaultBackend = false;
return;
} catch (ResourceLocationException e) {
msg = "'backend' value '" + value + "' is not a valid resource location";
@ -133,7 +141,8 @@ public class FabricFlwConfig implements FlwConfig {
if (msg != null) {
FlwImpl.CONFIG_LOGGER.warn(msg);
}
backend = BackendManager.defaultBackend();
backend = BackendManager.offBackend();
useDefaultBackend = true;
}
private void readLimitUpdates(JsonObject object) {
@ -181,7 +190,7 @@ public class FabricFlwConfig implements FlwConfig {
public JsonObject toJson() {
JsonObject object = new JsonObject();
object.addProperty("backend", Backend.REGISTRY.getIdOrThrow(backend).toString());
object.addProperty("backend", useDefaultBackend ? DEFAULT_BACKEND_STR : Backend.REGISTRY.getIdOrThrow(backend).toString());
object.addProperty("limitUpdates", limitUpdates);
object.addProperty("workerThreads", workerThreads);
object.add("flw_backends", backendConfig.toJson());

View file

@ -38,10 +38,26 @@ public final class FlwCommands {
context.getSource().sendFeedback(Component.translatable("command.flywheel.backend.get", idStr));
return Command.SINGLE_SUCCESS;
})
.then(ClientCommandManager.literal("DEFAULT")
.executes(context -> {
FabricFlwConfig.INSTANCE.backend = BackendManager.offBackend();
FabricFlwConfig.INSTANCE.useDefaultBackend = true;
FabricFlwConfig.INSTANCE.save();
// Reload renderers so we can report the actual backend.
Minecraft.getInstance().levelRenderer.allChanged();
Backend actualBackend = BackendManager.currentBackend();
String actualIdStr = Backend.REGISTRY.getIdOrThrow(actualBackend)
.toString();
context.getSource().sendFeedback(Component.translatable("command.flywheel.backend.set", actualIdStr));
return Command.SINGLE_SUCCESS;
}))
.then(ClientCommandManager.argument("id", BackendArgument.INSTANCE)
.executes(context -> {
Backend requestedBackend = context.getArgument("id", Backend.class);
FabricFlwConfig.INSTANCE.backend = requestedBackend;
FabricFlwConfig.INSTANCE.useDefaultBackend = false;
FabricFlwConfig.INSTANCE.save();
// Reload renderers so we can report the actual backend.

View file

@ -7,6 +7,7 @@ import dev.engine_room.flywheel.api.event.EndClientResourceReloadCallback;
import dev.engine_room.flywheel.api.event.ReloadLevelRendererCallback;
import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.impl.mixin.fabric.ArgumentTypeInfosAccessor;
import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler;
import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler;
import dev.engine_room.flywheel.lib.util.RendererReloadCache;
@ -16,7 +17,6 @@ import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallba
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin;
import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
@ -60,9 +60,12 @@ public final class FlywheelFabric implements ClientModInitializer {
EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) ->
BackendManagerImpl.onEndClientResourceReload(error.isPresent()));
ArgumentTypeRegistry.registerArgumentType(Flywheel.rl("backend"), BackendArgument.class, BackendArgument.INFO);
ArgumentTypeRegistry.registerArgumentType(Flywheel.rl("debug_mode"), DebugModeArgument.class, DebugModeArgument.INFO);
ArgumentTypeRegistry.registerArgumentType(Flywheel.rl("light_smoothness"), LightSmoothnessArgument.class, LightSmoothnessArgument.INFO);
// We can't use ArgumentTypeRegistry from Fabric API here as it also registers to BuiltInRegistries.COMMAND_ARGUMENT_TYPE.
// We can't register anything to BuiltInRegistries.COMMAND_ARGUMENT_TYPE because it is a synced registry but
// Flywheel is a client-side only mod.
ArgumentTypeInfosAccessor.getBY_CLASS().put(BackendArgument.class, BackendArgument.INFO);
ArgumentTypeInfosAccessor.getBY_CLASS().put(DebugModeArgument.class, DebugModeArgument.INFO);
ArgumentTypeInfosAccessor.getBY_CLASS().put(LightSmoothnessArgument.class, LightSmoothnessArgument.INFO);
}
private static void setupLib() {

View file

@ -0,0 +1,17 @@
package dev.engine_room.flywheel.impl.mixin.fabric;
import java.util.Map;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
@Mixin(ArgumentTypeInfos.class)
public interface ArgumentTypeInfosAccessor {
@Accessor("BY_CLASS")
static Map<Class<?>, ArgumentTypeInfo<?, ?>> getBY_CLASS() {
throw new AssertionError();
}
}

View file

@ -5,6 +5,7 @@
"compatibilityLevel": "JAVA_21",
"refmap": "flywheel.refmap.json",
"client": [
"ArgumentTypeInfosAccessor",
"DebugScreenOverlayMixin",
"MinecraftMixin",
"SystemReportMixin"

View file

@ -1,5 +1,6 @@
package dev.engine_room.flywheel.backend.compile;
import dev.engine_room.flywheel.backend.NoiseTextures;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.ResourceManagerReloadListener;
@ -12,5 +13,6 @@ public final class FlwProgramsReloader implements ResourceManagerReloadListener
@Override
public void onResourceManagerReload(ResourceManager manager) {
FlwPrograms.reload(manager);
NoiseTextures.reload(manager);
}
}

View file

@ -38,6 +38,19 @@ public final class FlwCommands {
sendMessage(context.getSource(), Component.translatable("command.flywheel.backend.get", idStr));
return Command.SINGLE_SUCCESS;
})
.then(Commands.literal("DEFAULT")
.executes(context -> {
backendValue.set(FlwConfig.DEFAULT_BACKEND_STR);
// Reload renderers so we can report the actual backend.
Minecraft.getInstance().levelRenderer.allChanged();
Backend actualBackend = BackendManager.currentBackend();
String actualIdStr = Backend.REGISTRY.getIdOrThrow(actualBackend)
.toString();
sendMessage(context.getSource(), Component.translatable("command.flywheel.backend.set", actualIdStr));
return Command.SINGLE_SUCCESS;
}))
.then(Commands.argument("id", BackendArgument.INSTANCE)
.executes(context -> {
Backend requestedBackend = context.getArgument("id", Backend.class);

View file

@ -16,7 +16,6 @@ import dev.engine_room.flywheel.lib.util.RendererReloadCache;
import dev.engine_room.flywheel.lib.util.ResourceReloadHolder;
import net.minecraft.client.Minecraft;
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
import net.minecraft.core.registries.Registries;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.CrashReportCallables;
@ -30,7 +29,6 @@ import net.neoforged.neoforge.event.entity.EntityJoinLevelEvent;
import net.neoforged.neoforge.event.entity.EntityLeaveLevelEvent;
import net.neoforged.neoforge.event.level.LevelEvent;
import net.neoforged.neoforge.event.tick.LevelTickEvent;
import net.neoforged.neoforge.registries.RegisterEvent;
@Mod(value = Flywheel.ID, dist = Dist.CLIENT)
public final class FlywheelNeoForge {
@ -83,17 +81,12 @@ public final class FlywheelNeoForge {
modEventBus.addListener((EndClientResourceReloadEvent e) -> BackendManagerImpl.onEndClientResourceReload(e.error().isPresent()));
modEventBus.addListener((FMLCommonSetupEvent e) -> {
// We can't register anything to Registries.COMMAND_ARGUMENT_TYPE because it is a synced registry but
// Flywheel is a client-side only mod.
ArgumentTypeInfos.registerByClass(BackendArgument.class, BackendArgument.INFO);
ArgumentTypeInfos.registerByClass(DebugModeArgument.class, DebugModeArgument.INFO);
ArgumentTypeInfos.registerByClass(LightSmoothnessArgument.class, LightSmoothnessArgument.INFO);
});
modEventBus.addListener((RegisterEvent e) -> {
if (e.getRegistryKey().equals(Registries.COMMAND_ARGUMENT_TYPE)) {
e.register(Registries.COMMAND_ARGUMENT_TYPE, Flywheel.rl("backend"), () -> BackendArgument.INFO);
e.register(Registries.COMMAND_ARGUMENT_TYPE, Flywheel.rl("debug_mode"), () -> DebugModeArgument.INFO);
e.register(Registries.COMMAND_ARGUMENT_TYPE, Flywheel.rl("light_smoothness"), () -> LightSmoothnessArgument.INFO);
}
});
}
private static void registerLibEventListeners(IEventBus gameEventBus, IEventBus modEventBus) {

View file

@ -29,20 +29,24 @@ public class NeoForgeFlwConfig implements FlwConfig {
public Backend backend() {
Backend backend = parseBackend(client.backend.get());
if (backend == null) {
client.backend.set(DEFAULT_BACKEND_STR);
backend = BackendManager.defaultBackend();
client.backend.set(Backend.REGISTRY.getIdOrThrow(backend).toString());
}
return backend;
}
@Nullable
private static Backend parseBackend(String idStr) {
private static Backend parseBackend(String value) {
if (value.equals(DEFAULT_BACKEND_STR)) {
return BackendManager.defaultBackend();
}
ResourceLocation backendId;
try {
backendId = ResourceLocation.parse(idStr);
backendId = ResourceLocation.parse(value);
} catch (ResourceLocationException e) {
FlwImpl.CONFIG_LOGGER.warn("'backend' value '{}' is not a valid resource location", idStr);
FlwImpl.CONFIG_LOGGER.warn("'backend' value '{}' is not a valid resource location", value);
return null;
}
@ -82,8 +86,8 @@ public class NeoForgeFlwConfig implements FlwConfig {
public final NeoForgeBackendConfig backendConfig;
private ClientConfig(ModConfigSpec.Builder builder) {
backend = builder.comment("Select the backend to use.")
.define("backend", () -> Backend.REGISTRY.getIdOrThrow(BackendManager.defaultBackend()).toString(), o -> o != null && String.class.isAssignableFrom(o.getClass()));
backend = builder.comment("Select the backend to use. Set to \"DEFAULT\" to let Flywheel decide.")
.define("backend", DEFAULT_BACKEND_STR);
limitUpdates = builder.comment("Enable or disable instance update limiting with distance.")
.define("limitUpdates", true);
@ -103,7 +107,7 @@ public class NeoForgeFlwConfig implements FlwConfig {
public final ModConfigSpec.EnumValue<LightSmoothness> lightSmoothness;
public NeoForgeBackendConfig(ModConfigSpec.Builder builder) {
lightSmoothness = builder.comment("How smooth flywheel's shader-based lighting should be. May have a large performance impact.")
lightSmoothness = builder.comment("How smooth Flywheel's shader-based lighting should be. May have a large performance impact.")
.defineEnum("lightSmoothness", LightSmoothness.SMOOTH);
}