Merge remote-tracking branch 'Engine-Room/1.20.1/oit' into 1.20.1/dev

# Conflicts:
#	common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java
#	fabric/src/backend/java/dev/engine_room/flywheel/backend/compile/FlwProgramsReloader.java
This commit is contained in:
Jozufozu 2025-02-26 21:56:45 -08:00
commit cbc524ca7a
26 changed files with 1188 additions and 199 deletions

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

@ -30,7 +30,6 @@ public class IndirectPrograms extends AtomicReferenceCounted {
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");
public static final List<ResourceLocation> UTIL_SHADERS = List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN, DOWNSAMPLE_FIRST, DOWNSAMPLE_SECOND);
private static final Compile<InstanceType<?>> CULL = new Compile<>();
private static final Compile<ResourceLocation> UTIL = new Compile<>();
@ -44,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) {
@ -58,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");
@ -87,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);
}
@ -147,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) {
@ -171,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

@ -23,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;
@ -49,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();
@ -65,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() {
@ -95,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()
@ -127,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()
@ -145,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())
@ -170,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()
@ -216,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

@ -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()
@ -135,7 +146,7 @@ public final class MaterialRenderState {
RenderSystem.enableBlend();
RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.DST_COLOR, GlStateManager.DestFactor.SRC_COLOR, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);
}
case TRANSLUCENT, ORDER_INDEPENDENT -> {
case TRANSLUCENT -> {
RenderSystem.enableBlend();
RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
}

View file

@ -13,9 +13,11 @@ import java.util.List;
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.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;
@ -36,6 +38,7 @@ public class IndirectCullingGroup<I extends Instance> {
private final List<IndirectInstancer<I>> instancers = new ArrayList<>();
private final List<IndirectDraw> indirectDraws = new ArrayList<>();
private final List<MultiDraw> multiDraws = new ArrayList<>();
private final List<MultiDraw> oitDraws = new ArrayList<>();
private final IndirectPrograms programs;
private final GlProgram cullProgram;
@ -54,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(); ) {
@ -76,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.
@ -104,10 +111,6 @@ public class IndirectCullingGroup<I extends Instance> {
}
public void dispatchCull() {
if (nothingToDo()) {
return;
}
Uniforms.bindAll();
cullProgram.bind();
@ -116,20 +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;
public boolean hasOitDraws() {
return !oitDraws.isEmpty();
}
private void sortDraws() {
multiDraws.clear();
oitDraws.clear();
// sort by visual type, then material
indirectDraws.sort(DRAW_COMPARATOR);
@ -138,7 +138,9 @@ 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.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;
}
}
@ -171,8 +173,8 @@ public class IndirectCullingGroup<I extends Instance> {
needsDrawSort = true;
}
public void submit() {
if (nothingToDo()) {
public void submitSolid() {
if (multiDraws.isEmpty()) {
return;
}
@ -183,7 +185,7 @@ public class IndirectCullingGroup<I extends Instance> {
GlProgram lastProgram = null;
for (var multiDraw : multiDraws) {
var drawProgram = programs.getIndirectProgram(instanceType, multiDraw.embedded ? ContextShader.EMBEDDED : ContextShader.DEFAULT, multiDraw.material);
var drawProgram = programs.getIndirectProgram(instanceType, multiDraw.embedded ? ContextShader.EMBEDDED : ContextShader.DEFAULT, multiDraw.material, PipelineCompiler.OitMode.OFF);
if (drawProgram != lastProgram) {
lastProgram = drawProgram;
@ -197,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();
@ -256,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

@ -17,6 +17,7 @@ import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
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;
@ -48,6 +49,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
private final DepthPyramid depthPyramid;
private final OitFramebuffer oitFramebuffer;
public IndirectDrawManager(IndirectPrograms programs) {
this.programs = programs;
programs.acquire();
@ -62,6 +65,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
matrixBuffer = new MatrixBuffer();
depthPyramid = new DepthPyramid(programs);
oitFramebuffer = new OitFramebuffer(programs.oitPrograms());
}
@Override
@ -80,13 +85,11 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
public void render(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
super.render(lightStorage, environmentStorage);
for (var group : cullingGroups.values()) {
group.flushInstancers();
}
// 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);
@ -94,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);
@ -138,7 +147,45 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
Uniforms.bindAll();
for (var group : cullingGroups.values()) {
group.submit();
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();
@ -166,6 +213,8 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
lightBuffers.delete();
matrixBuffer.delete();
oitFramebuffer.delete();
}
public void renderCrumbling(List<Engine.CrumblingBlock> crumblingBlocks) {

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

@ -1,13 +1,17 @@
package dev.engine_room.flywheel.backend.engine.instancing;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
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.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;
@ -19,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;
@ -28,7 +33,16 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.ModelBakery;
public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
private final InstancedRenderStage draws = new InstancedRenderStage();
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.
@ -38,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;
@ -48,6 +64,9 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
light = new InstancedLight();
meshPool.bind(vao);
oitFramebuffer = new OitFramebuffer(programs.oitPrograms());
}
@Override
@ -66,13 +85,31 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
});
// Remove the draw calls for any instancers we deleted.
draws.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);
if (draws.isEmpty()) {
if (allDraws.isEmpty()) {
return;
}
@ -81,18 +118,92 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
TextureBinder.bindLightAndOverlay();
light.bind();
draws.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);
draws.delete();
allDraws.forEach(InstancedDraw::delete);
allDraws.clear();
draws.clear();
oitDraws.clear();
meshPool.delete();
instanceTexture.delete();
@ -101,6 +212,8 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
light.delete();
oitFramebuffer.delete();
super.delete();
}
@ -122,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);
draws.put(groupKey, instancedDraw);
allDraws.add(instancedDraw);
needSort = true;
instancer.addDrawCall(instancedDraw);
}
}
@ -165,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

@ -13,6 +13,7 @@ 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

@ -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,6 +1,5 @@
package dev.engine_room.flywheel.backend.glsl.parse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jetbrains.annotations.Nullable;
@ -34,20 +33,21 @@ public class ShaderField {
* 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);
// 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));
// }
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();
return ImmutableMap.<String, ShaderField>builder()
.build();
}
public enum Qualifier {

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

@ -1,6 +1,7 @@
package dev.engine_room.flywheel.backend.compile;
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;
@ -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

@ -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);
}
}