diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALRComputePipelines.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALRComputePipelines.java new file mode 100644 index 00000000..a37c9458 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALRComputePipelines.java @@ -0,0 +1,40 @@ +package dev.anvilcraft.lib.v2.rendering; + +import dev.anvilcraft.lib.v2.rendering.event.RegisterComputePipelinesEvent; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.ALRComputePipeline; +import net.minecraft.client.renderer.ShaderDefines; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; + +@EventBusSubscriber(Dist.CLIENT) +public class ALRComputePipelines { + public static final ALRComputePipeline FFX_SPD_DOWNSAMPLE_PASS = ALRComputePipeline.builder() + .withName(AnvilLibRendering.location("ffx_spd_downsample_pass")) + .withShader(AnvilLibRendering.location("compute/ffx_spd_downsample_pass.csh")) + .withDefines( + ShaderDefines.builder() + .define("FFX_SPD_OPTION_DOWNSAMPLE_FILTER", "2") // use max for HZB + .define("FFX_SPD_OPTION_WAVE_INTEROP_LDS", ALROptions.SPD_OPTION_WAVE_INTEROP_LDS ? 0 : 1) // weird inverted + .build() + ) + .withUniformBlock("cbFSR1") + .withTexture("r_input_downsample_src") + .withReadWriteImage("rw_input_downsample_src_mid_mip") + .withImageArray("rw_input_downsample_src_mips", true, true, 13) + .build(); + + public static final ALRComputePipeline DEPTH_CONVERT = ALRComputePipeline.builder() + .withName(AnvilLibRendering.location("depth_convert")) + .withShader(AnvilLibRendering.location("compute/depth_convert.csh")) + .withUniformBlock("ConvertParam") + .withTexture("Input") + .withWriteOnlyImage("Output") + .build(); + + @SubscribeEvent + public static void on(RegisterComputePipelinesEvent event) { + event.registerPipeline(FFX_SPD_DOWNSAMPLE_PASS); + event.registerPipeline(DEPTH_CONVERT); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALROptimizations.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALROptimizations.java new file mode 100644 index 00000000..77561dd1 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALROptimizations.java @@ -0,0 +1,21 @@ +package dev.anvilcraft.lib.v2.rendering; + +import com.mojang.blaze3d.systems.RenderSystem; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionCuller; +import org.jspecify.annotations.Nullable; + +public class ALROptimizations { + private static OcclusionCuller occlusionCuller; + + @Nullable + public static OcclusionCuller getOcclusionCuller() { + if (occlusionCuller == null) { + occlusionCuller = OcclusionCuller.createInstance(RenderSystem.getDevice()); + } + return occlusionCuller; + } + + public static void create() { + occlusionCuller = OcclusionCuller.createInstance(RenderSystem.getDevice()); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALROptions.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALROptions.java new file mode 100644 index 00000000..f6c44f59 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALROptions.java @@ -0,0 +1,11 @@ +package dev.anvilcraft.lib.v2.rendering; + +public class ALROptions { + public static final boolean SPD_OPTION_WAVE_INTEROP_LDS = getPropertyBoolean("alrSpdOptionUseWaveInteropLds"); + + + private static boolean getPropertyBoolean(String key) { + String prop = System.getProperty(key); + return !"false".equals(prop); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALRPipelines.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALRPipelines.java index c1324c99..6332d51e 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALRPipelines.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALRPipelines.java @@ -2,17 +2,22 @@ import com.mojang.blaze3d.pipeline.BlendFunction; import com.mojang.blaze3d.pipeline.ColorTargetState; +import com.mojang.blaze3d.pipeline.DepthStencilState; import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.platform.CompareOp; import com.mojang.blaze3d.shaders.UniformType; import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.blaze3d.vertex.VertexFormatElement; +import dev.anvilcraft.lib.v2.rendering.bloom.TransformsUbo; import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.client.event.RegisterRenderPipelinesEvent; import org.jetbrains.annotations.ApiStatus; +import java.util.Optional; + @EventBusSubscriber(Dist.CLIENT) public class ALRPipelines { public static final RenderPipeline.Snippet POST_PASS = RenderPipeline.builder() @@ -76,6 +81,17 @@ public class ALRPipelines { .withCull(false) .build(); + public static final RenderPipeline OCCLUSION_QUERY = RenderPipeline.builder() + .withLocation(AnvilLibRendering.location("occlusion_query")) + .withVertexFormat(DefaultVertexFormat.POSITION, VertexFormat.Mode.QUADS) + .withUniform("Transforms", UniformType.UNIFORM_BUFFER) + .withVertexShader(AnvilLibRendering.location("core/occlusion_query")) + .withFragmentShader(AnvilLibRendering.location("core/occlusion_query")) + .withColorTargetState(new ColorTargetState(Optional.empty(), ColorTargetState.WRITE_NONE)) + .withDepthStencilState(new DepthStencilState(CompareOp.LESS_THAN_OR_EQUAL, false)) + .withCull(true) + .build(); + @ApiStatus.Internal @SubscribeEvent @@ -86,5 +102,7 @@ public static void on(RegisterRenderPipelinesEvent event) { event.registerPipeline(UPSAMPLE); event.registerPipeline(SDF_GRAPHICS); + + event.registerPipeline(OCCLUSION_QUERY); } } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/BloomParametersUbo.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/BloomParametersUbo.java index 47497b31..fdc7fcad 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/BloomParametersUbo.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/BloomParametersUbo.java @@ -7,9 +7,11 @@ import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.ShaderBufferObjectUsage; import lombok.Getter; import lombok.Setter; +import org.jetbrains.annotations.ApiStatus; @Setter @Getter +@ApiStatus.Internal public class BloomParametersUbo extends BufferObject { public static final BufferObjectLayoutDefinition DEFINITION = BufferObjectLayoutDefinition.create( diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/BloomPipelineParametersUbo.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/BloomPipelineParametersUbo.java index 17be00c3..39e4a53c 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/BloomPipelineParametersUbo.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/BloomPipelineParametersUbo.java @@ -7,10 +7,12 @@ import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.ShaderBufferObjectUsage; import lombok.Getter; import lombok.Setter; +import org.jetbrains.annotations.ApiStatus; import org.joml.Vector2f; @Getter @Setter +@ApiStatus.Internal public class BloomPipelineParametersUbo extends BufferObject { public static final BufferObjectLayoutDefinition DEFINITION = BufferObjectLayoutDefinition.create( diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/TransformsUbo.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/TransformsUbo.java index ed9203bb..fdb6b0aa 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/TransformsUbo.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/bloom/TransformsUbo.java @@ -7,10 +7,12 @@ import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.ShaderBufferObjectUsage; import lombok.Getter; import lombok.Setter; +import org.jetbrains.annotations.ApiStatus; import org.joml.Matrix4f; @Setter @Getter +@ApiStatus.Internal public class TransformsUbo extends BufferObject { public static final BufferObjectLayoutDefinition DEFINITION = BufferObjectLayoutDefinition.create( diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/blur/BlurParametersUbo.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/blur/BlurParametersUbo.java index bffa5227..7027eaf6 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/blur/BlurParametersUbo.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/blur/BlurParametersUbo.java @@ -7,10 +7,12 @@ import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.ShaderBufferObjectUsage; import lombok.Getter; import lombok.Setter; +import org.jetbrains.annotations.ApiStatus; import org.joml.Vector2f; @Setter @Getter +@ApiStatus.Internal public class BlurParametersUbo extends BufferObject { public static final BufferObjectLayoutDefinition DEFINITION = BufferObjectLayoutDefinition.create( diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/cachedber/pipeline/CachedRenderingChunk.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/cachedber/pipeline/CachedRenderingChunk.java index 9d24a053..7a5bba01 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/cachedber/pipeline/CachedRenderingChunk.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/cachedber/pipeline/CachedRenderingChunk.java @@ -27,6 +27,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.ApiStatus; import org.joml.Matrix4fStack; import org.joml.Vector3f; import org.joml.Vector4f; @@ -44,6 +45,7 @@ /** * @author ZhuRuoLing */ +@ApiStatus.Internal public class CachedRenderingChunk implements VertexBufferHost { @Getter private final ChunkPos chunkPos; diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRCommandEncoderBackendExtension.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRCommandEncoderBackendExtension.java index d66777a8..78ac120e 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRCommandEncoderBackendExtension.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRCommandEncoderBackendExtension.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.rendering.extension.blaze3d; +import org.jetbrains.annotations.ApiStatus; + import com.mojang.blaze3d.buffers.GpuBufferSlice; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.ALRComputePass; import org.jetbrains.annotations.ApiStatus; diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRGpuDeviceBackendExtension.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRGpuDeviceBackendExtension.java index b8d8a7c8..3b336575 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRGpuDeviceBackendExtension.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRGpuDeviceBackendExtension.java @@ -1,9 +1,14 @@ package dev.anvilcraft.lib.v2.rendering.extension.blaze3d; -import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.ALRComputePass; +import com.mojang.blaze3d.textures.GpuTexture; +import org.jetbrains.annotations.ApiStatus; + import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeProgramInstance; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeProgramInstanceKey; -import org.jetbrains.annotations.ApiStatus; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.query.GpuQueryObject; +import org.jspecify.annotations.Nullable; + +import java.util.function.Supplier; @ApiStatus.Internal public interface ALRGpuDeviceBackendExtension { @@ -11,7 +16,21 @@ public interface ALRGpuDeviceBackendExtension { void alrDestroyComputeShader(ALRComputeProgramInstance instance); - void alrPushDebugGroup(String name); + void alrPushDebugGroup(Supplier name); void alrPopDebugGroup(); + + GpuQueryObject alrCreateSamplesQuery(); + + ALRHICapabilities alrhiCreateCapabilities(); + + GpuTexture alrCreateExtendedTexture( + @Nullable String label, + @GpuTexture.Usage int usage, + ExtendedTextureFormat format, + int width, + int height, + int depthOrLayers, + int mipLevels + ); } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRGpuDeviceExtension.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRGpuDeviceExtension.java index 184600c1..26709ec2 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRGpuDeviceExtension.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRGpuDeviceExtension.java @@ -1,10 +1,34 @@ package dev.anvilcraft.lib.v2.rendering.extension.blaze3d; +import com.mojang.blaze3d.textures.GpuTexture; +import com.mojang.blaze3d.textures.TextureFormat; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeProgramInstance; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeProgramInstanceKey; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.query.GpuQueryObject; +import org.jspecify.annotations.Nullable; + +import java.util.function.Supplier; public interface ALRGpuDeviceExtension { ALRComputeProgramInstance alrCompileComputeShader(ALRComputeProgramInstanceKey instanceKey); void alrDestroyComputeShader(ALRComputeProgramInstance instance); + + GpuQueryObject alrCreateSamplesQuery(); + + void alrPushDebugGroup(Supplier message); + + void alrPopDebugGroup(); + + ALRHICapabilities alrhiCreateCapabilities(); + + GpuTexture alrCreateExtendedTexture( + @Nullable String label, + @GpuTexture.Usage int usage, + ExtendedTextureFormat format, + int width, + int height, + int depthOrLayers, + int mipLevels + ); } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRHICapabilities.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRHICapabilities.java new file mode 100644 index 00000000..61a2814d --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRHICapabilities.java @@ -0,0 +1,12 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d; + +import com.mojang.blaze3d.systems.RenderSystem; + +public record ALRHICapabilities( + boolean compute +) { + + public static ALRHICapabilities getInstance() { + return ((ALRGpuDeviceExtension) RenderSystem.getDevice()).alrhiCreateCapabilities(); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ExtendedTextureFormat.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ExtendedTextureFormat.java new file mode 100644 index 00000000..0d576b0e --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ExtendedTextureFormat.java @@ -0,0 +1,26 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d; + +import net.neoforged.neoforge.internal.NonExhaustiveEnum; + +@SuppressWarnings("UnstableApiUsage") +@NonExhaustiveEnum(reason = "Additional texture formats may be added") +public enum ExtendedTextureFormat { + /// Single-channel 32-bit float, GL_R32F. Depth/Hi-Z pipelines. + R32F(4), + /// Four-channel 16-bit float, GL_RGBA16F. Common HDR color target. + RGBA16F(8), + /// Four-channel 32-bit float, GL_RGBA32F + RGBA32F(16), + /// 10-bit RGB with 2-bit alpha, GL_RGB10_A2 + RGB10_A2(4); + + private final int pixelSize; + + ExtendedTextureFormat(int pixelSize) { + this.pixelSize = pixelSize; + } + + public int pixelSize() { + return this.pixelSize; + } +} \ No newline at end of file diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/ALRComputeCapabilities.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/ALRComputeCapabilities.java index ab178600..49f34c9f 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/ALRComputeCapabilities.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/ALRComputeCapabilities.java @@ -1,12 +1,12 @@ package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute; -import org.lwjgl.opengl.GL; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRHICapabilities; public class ALRComputeCapabilities { private static boolean COMPUTE_SUPPORTED; public static void init() { - COMPUTE_SUPPORTED = GL.getCapabilities().GL_ARB_compute_shader; + COMPUTE_SUPPORTED = ALRHICapabilities.getInstance().compute(); } public static boolean isComputeSupported() { diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePass.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePass.java index db71a687..a3def33e 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePass.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePass.java @@ -7,6 +7,7 @@ import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.TextureBinding; import java.util.List; +import java.util.function.Supplier; public class ALRComputePass implements AutoCloseable { private ALRComputePipeline pipeline; @@ -16,11 +17,11 @@ public ALRComputePass(ALRComputePassBackend backend) { this.backend = backend; } - public void pushDebugGroup(String name) { + public void pushDebugGroup(Supplier name) { this.backend.pushDebugGroup(name); } - public void popDebugGroup(String name) { + public void popDebugGroup() { this.backend.popDebugGroup(); } @@ -33,7 +34,7 @@ public void dispatchWorkgroups( int groupCountY, int groupCountZ ) { - this.backend.pushDebugGroup("Compute " + pipeline.identifier()); + this.backend.pushDebugGroup(() -> "ALRComputePass " + pipeline.identifier()); this.backend.dispatchWorkgroups(groupCountX, groupCountY, groupCountZ); this.backend.popDebugGroup(); } @@ -58,12 +59,12 @@ public void setPipeline(ALRComputePipeline pipeline) { public void bindAll(List elements) { int bindingPoint = 0; for (ComputeBindingLayout binding : pipeline.bindings()) { - this.bind(bindingPoint++, binding, elements.get(bindingPoint - 1)); + bindingPoint += this.bind(bindingPoint, binding, elements.get(bindingPoint - 1)); } } - public void bind(int bindingPoint, ComputeBindingLayout layout, T resource) { - layout.apply(bindingPoint, resource, this); + public int bind(int bindingPointStart, ComputeBindingLayout layout, T resource) { + return layout.applyOrdered(bindingPointStart, resource, this); } public void bindTexture(int bindingPoint, TextureBinding.SamplerAndTexture resource) { diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePassBackend.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePassBackend.java index ec56de85..f5547aff 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePassBackend.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePassBackend.java @@ -1,15 +1,18 @@ package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline; -import com.mojang.blaze3d.buffers.GpuBuffer; import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.textures.GpuTexture; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.MemoryBarrierFlag; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.TextureBinding; +import org.jetbrains.annotations.ApiStatus; +import java.util.function.Supplier; + +@ApiStatus.Internal public interface ALRComputePassBackend { void setPipeline(ALRComputePipeline pipeline); - void pushDebugGroup(String name); + void pushDebugGroup(Supplier name); void popDebugGroup(); diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePipeline.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePipeline.java index 7adab954..8944a2ca 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePipeline.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/ALRComputePipeline.java @@ -2,6 +2,7 @@ import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.ComputeBindingLayout; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.AtomicCounterBinding; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.ImageArrayBinding; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.ImageBinding; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.ShaderStorageBinding; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.TextureBinding; @@ -86,6 +87,11 @@ public Builder withReadWriteImage(String name) { return this.withImage(name, true, true); } + /// an array of image2D, not image2DArray + public Builder withImageArray(String name, boolean read, boolean write, int size) { + return this.withBinding(new ImageArrayBinding(name, read, write, size)); + } + public Builder withUniformBlock(String name) { return this.withBinding(new UniformBlockBinding(name)); } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ComputeBindingLayout.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ComputeBindingLayout.java index b9bbb054..06f90630 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ComputeBindingLayout.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ComputeBindingLayout.java @@ -9,4 +9,10 @@ public interface ComputeBindingLayout { String name(); void apply(int bindingPoint, T resource, ALRComputePass computePass); + + /// @return binding point incremental + default int applyOrdered(int bindingPointStart, T resource, ALRComputePass computePass) { + this.apply(bindingPointStart, resource, computePass); + return 1; + } } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ImageArrayBinding.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ImageArrayBinding.java new file mode 100644 index 00000000..2fac784e --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ImageArrayBinding.java @@ -0,0 +1,38 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings; + +import com.google.common.base.Preconditions; +import com.mojang.blaze3d.textures.GpuTexture; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.ALRComputePass; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ShaderResourceType; + +import java.util.List; + +public record ImageArrayBinding ( + String name, + boolean read, + boolean write, + int size +) implements ComputeBindingLayout> { + + public ImageArrayBinding { + if (!read && !write) { + throw new IllegalArgumentException("ImageResource does not allow both read and write are false"); + } + } + + @Override + public ShaderResourceType type() { + return ShaderResourceType.IMAGE; + } + + /// Just assume iterating over the list passed in is ordered. + /// + /// `resource.size <= this.size` is allowed, but `resource` must not have elements more than `this.size` + @Override + public void apply(int bindingPoint, List resource, ALRComputePass computePass) { + Preconditions.checkElementIndex(size, resource.size(), "resource must not have elements more than this.size"); + for (GpuTexture texture : resource) { + computePass.bindImage(bindingPoint++, texture, read, write); + } + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/gl/GlComputePassBackend.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/gl/GlComputePassBackend.java index ae86f694..7bdecd8c 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/gl/GlComputePassBackend.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/gl/GlComputePassBackend.java @@ -26,6 +26,8 @@ import org.lwjgl.opengl.GL33; import org.lwjgl.opengl.GL46; +import java.util.function.Supplier; + @ApiStatus.Internal public class GlComputePassBackend implements ALRComputePassBackend { private final ALRGpuDeviceBackendExtension backendExtension; @@ -51,8 +53,8 @@ public void setPipeline(ALRComputePipeline pipeline) { } @Override - public void pushDebugGroup(String name) { - this.backendExtension.alrPushDebugGroup(name); + public void pushDebugGroup(Supplier message) { + this.backendExtension.alrPushDebugGroup(message); } @Override diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeProgramInstance.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeProgramInstance.java index ed61ac87..40701ed3 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeProgramInstance.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeProgramInstance.java @@ -2,7 +2,9 @@ import dev.anvilcraft.lib.v2.rendering.AnvilLibRendering; import net.minecraft.client.renderer.ShaderDefines; +import org.jetbrains.annotations.ApiStatus; +@ApiStatus.Internal public record ALRComputeProgramInstance(int id, ALRComputeProgramInstanceKey key) { public static final ALRComputeProgramInstance INVALID = new ALRComputeProgramInstance( 0, diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeProgramInstanceKey.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeProgramInstanceKey.java index af5810b0..0685ca6b 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeProgramInstanceKey.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeProgramInstanceKey.java @@ -2,6 +2,8 @@ import net.minecraft.client.renderer.ShaderDefines; import net.minecraft.resources.Identifier; +import org.jetbrains.annotations.ApiStatus; +@ApiStatus.Internal public record ALRComputeProgramInstanceKey(Identifier location, ShaderDefines defines) { } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeShaderManager.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeShaderManager.java index 88584987..22faeebc 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeShaderManager.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/shader/ALRComputeShaderManager.java @@ -16,6 +16,7 @@ import net.minecraft.util.profiling.ProfilerFiller; import net.neoforged.fml.ModLoader; import org.apache.commons.io.IOUtils; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.UnknownNullability; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -26,6 +27,7 @@ import java.util.Map; @Slf4j +@ApiStatus.Internal public class ALRComputeShaderManager extends SimplePreparableReloadListener { public static final ALRComputeShaderManager INSTANCE = new ALRComputeShaderManager(); @@ -60,7 +62,6 @@ public class ALRComputeShaderManager extends SimplePreparableReloadListener GL46.GL_R32F; + case RGBA16F -> GL46.GL_RGBA16F; + case RGBA32F -> GL46.GL_RGBA32F; + case RGB10_A2 -> GL46.GL_RGB10_A2; + }; + } + + public static int toGlInternalId(ExtendedTextureFormat format) { + return toGlConst(format); + } + + public static int toGlExternalId(ExtendedTextureFormat format) { + return switch (format) { + case R32F -> GL46.GL_RED; + case RGBA16F, RGBA32F, RGB10_A2 -> GL46.GL_RGBA; + }; + } + + public static int toGlType(ExtendedTextureFormat format) { + return switch (format) { + case R32F, RGBA32F -> GL46.GL_FLOAT; + case RGBA16F -> GL46.GL_HALF_FLOAT; + case RGB10_A2 -> GL46.GL_UNSIGNED_INT_2_10_10_10_REV; + }; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/GpuReusableResource.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/GpuReusableResource.java new file mode 100644 index 00000000..4e729cfe --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/GpuReusableResource.java @@ -0,0 +1,13 @@ +package dev.anvilcraft.lib.v2.rendering.foundation; + +public interface GpuReusableResource extends AutoCloseable { + + void acquire(); + + void release(); + + boolean isAcquired(); + + @Override + void close(); +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/GpuReusableResourcePool.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/GpuReusableResourcePool.java new file mode 100644 index 00000000..003e0f1e --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/GpuReusableResourcePool.java @@ -0,0 +1,43 @@ +package dev.anvilcraft.lib.v2.rendering.foundation; + +public abstract class GpuReusableResourcePool extends LoopResetPool { + + public GpuReusableResourcePool(int size, C context) { + super(size, context); + } + + @Override + public void release(T query) { + query.release(); + } + + @Override + public void destroy(T query) { + query.close(); + } + + @Override + public void onAcquire(T query) { + query.acquire(); + } + + @Override + public boolean isAvailable(T query) { + return !query.isAcquired(); + } + + @Override + public T fail(boolean createInstanceIfAllAcquired) { + int index = 0; + + if (createInstanceIfAllAcquired) { + index = this.size; + expand(); + } + + T query = this.get(index); + query.acquire(); + + return query; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/LoopResetPool.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/LoopResetPool.java new file mode 100644 index 00000000..29689b8b --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/LoopResetPool.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) Argon4W + * SPDX-License-Identifier: MIT + */ +package dev.anvilcraft.lib.v2.rendering.foundation; + +import org.jspecify.annotations.Nullable; + +/// @author Argon4W +public abstract class LoopResetPool extends SimpleResetPool { + + public LoopResetPool(int size, C context) { + super(size, context); + } + + @Override + @Nullable + @SuppressWarnings("unchecked") + public T acquire(boolean createInstanceIfAllAcquired) { + for (int i = 0; i < size; i++) { + T t = (T) pool[i]; + + if (this.isAvailable(t)) { + this.onAcquire(t); + return t; + } + } + + return this.fail(createInstanceIfAllAcquired); + } +} \ No newline at end of file diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/SimpleResetPool.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/SimpleResetPool.java new file mode 100644 index 00000000..28a27be8 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/SimpleResetPool.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) Argon4W + * SPDX-License-Identifier: MIT + */ +package dev.anvilcraft.lib.v2.rendering.foundation; + +import lombok.Getter; +import org.jspecify.annotations.Nullable; + +import java.util.Arrays; + +/// @author Argon4W +public abstract class SimpleResetPool { + + @Getter protected final C context; + + @Getter protected Object[] pool; + @Getter protected int cursor; + protected int size; + + public SimpleResetPool(int size, C context) { + this.size = size; + this.pool = new Object[size]; + this.context = context; + + this.cursor = 0; + + for (int i = 0; i < this.size; i++) { + this.pool[i] = createInstance(this.context, i); + } + } + + public abstract void onAcquire (T t); + + protected abstract T createInstance (C context, int i); + + @Nullable + protected abstract T fail (boolean createInstanceIfAllAcquired); + + protected abstract void release (T t); + + protected abstract void destroy (T t); + + protected abstract boolean isAvailable (T t); + + @Nullable + public T acquire() { + return this.acquire(true); + } + + @SuppressWarnings("unchecked") + @Nullable + public T acquire(boolean createInstanceIfAllAcquired) { + if (this.cursor < this.size) { + T t = (T) this.pool[this.cursor ++]; + + if (this.isAvailable(t)) { + this.onAcquire(t); + return t; + } + } + + return this.fail(createInstanceIfAllAcquired); + } + + @SuppressWarnings("unchecked") + public T get(int index) { + return (T) this.pool[index]; + } + + protected void expand() { + int old = this.size; + + this.size = old * 2; + this.pool = Arrays.copyOf(this.pool, this.size); + + for (int i = old; i < size; i ++) { + this.pool[i] = this.createInstance(context, i); + } + } + + @SuppressWarnings("unchecked") + public void releaseAll() { + for (int i = 0; i < this.cursor; i++) { + release((T) this.pool[i]); + } + + cursor = 0; + } + + @SuppressWarnings("unchecked") + public void destroyAll() { + for (int i = 0; i < this.size; i++) { + this.destroy((T) this.pool[i]); + } + } +} \ No newline at end of file diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/object/BufferObject.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/object/BufferObject.java index a0c1ff2a..5476ee83 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/object/BufferObject.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/object/BufferObject.java @@ -1,6 +1,5 @@ package dev.anvilcraft.lib.v2.rendering.foundation.buffers.object; -import com.mojang.blaze3d.buffers.GpuBuffer; import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.systems.CommandEncoder; import dev.anvilcraft.lib.v2.rendering.foundation.buffers.layout.BufferLayout; @@ -33,14 +32,21 @@ public void upload(CommandEncoder commandEncoder, GpuBufferSlice dest) { @Override @SuppressWarnings("unchecked") public void write(@NonNull ByteBuffer buffer) { + if (this.usage == ShaderBufferObjectUsage.SSBO) { + throw new IllegalStateException(); + } getDefinition().write(buffer, (T) this, this.layout); } + @Deprecated(forRemoval = true) public DynamicUniformStorage createDynamicStorage(String label) { + if (this.usage == ShaderBufferObjectUsage.SSBO) { + throw new IllegalStateException(); + } return new DynamicUniformStorage<>( label, getDefinition().size(this.layout), - GpuBuffer.USAGE_UNIFORM | GpuBuffer.USAGE_COPY_DST + 16 ); } } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/object/BufferObjectLayoutEntry.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/object/BufferObjectLayoutEntry.java index c44fad34..b0e671f8 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/object/BufferObjectLayoutEntry.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/object/BufferObjectLayoutEntry.java @@ -11,6 +11,8 @@ import org.joml.Vector4f; import org.joml.Vector4i; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Function; public record BufferObjectLayoutEntry(BufferObjectLayoutEntryType type, Function getter) { @@ -70,6 +72,7 @@ public static Builder ofMat4f() { public static class Builder { private final BufferObjectLayoutEntryType type; private Function getter; + private BiConsumer setter; public Builder(BufferObjectLayoutEntryType type) { this.type = type; @@ -80,6 +83,11 @@ public Builder forGetter(Function getter) { return this; } + public Builder forSetter(BiConsumer setter) { + this.setter = setter; + return this; + } + public BufferObjectLayoutEntry build() { return new BufferObjectLayoutEntry<>(type, getter); } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/ubo/FullTransformsUbo.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/ubo/FullTransformsUbo.java new file mode 100644 index 00000000..37d434ac --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/ubo/FullTransformsUbo.java @@ -0,0 +1,44 @@ +package dev.anvilcraft.lib.v2.rendering.foundation.buffers.ubo; + +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.layout.BufferLayout; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObject; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObjectLayoutDefinition; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObjectLayoutEntry; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.ShaderBufferObjectUsage; +import lombok.Getter; +import lombok.Setter; +import org.jetbrains.annotations.ApiStatus; +import org.joml.Matrix4f; + +/// ```glsl +/// layout(std140) uniform Transforms { +/// mat4 ProjMat; +/// mat4 ModelViewMat; +/// }; +/// ``` +@Setter +@Getter +@ApiStatus.Internal +public class FullTransformsUbo extends BufferObject { + + public static final BufferObjectLayoutDefinition DEFINITION = BufferObjectLayoutDefinition.create( + BufferObjectLayoutEntry.ofMat4f().forGetter(FullTransformsUbo::getProjMat).build(), + BufferObjectLayoutEntry.ofMat4f().forGetter(FullTransformsUbo::getModelViewMat).build() + ); + + public static final int SIZE = DEFINITION.size(BufferLayout.STD140); + + private Matrix4f projMat; + private Matrix4f modelViewMat; + + public FullTransformsUbo() { + super(BufferLayout.STD140, ShaderBufferObjectUsage.UBO); + this.projMat = new Matrix4f(); + this.modelViewMat = new Matrix4f(); + } + + @Override + protected BufferObjectLayoutDefinition getDefinition() { + return DEFINITION; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/ubo/IntSizeUbo.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/ubo/IntSizeUbo.java new file mode 100644 index 00000000..d96fbfdd --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/ubo/IntSizeUbo.java @@ -0,0 +1,41 @@ +package dev.anvilcraft.lib.v2.rendering.foundation.buffers.ubo; + +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.layout.BufferLayout; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObject; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObjectLayoutDefinition; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObjectLayoutEntry; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.ShaderBufferObjectUsage; +import lombok.Getter; +import lombok.Setter; +import org.jetbrains.annotations.ApiStatus; + +/// ```glsl +/// layout(std140, binding = 0) uniform SizeParam { +/// int uWidth; +/// int uHeight; +/// }; +/// ``` +@Getter +@Setter +@ApiStatus.Internal +public class IntSizeUbo extends BufferObject { + + public static final BufferObjectLayoutDefinition DEFINITION = BufferObjectLayoutDefinition.create( + BufferObjectLayoutEntry.ofInt().forGetter(IntSizeUbo::getHeight).build(), + BufferObjectLayoutEntry.ofInt().forGetter(IntSizeUbo::getWidth).build() + ); + + public static final int SIZE = DEFINITION.size(BufferLayout.STD140); + + private int width; + private int height; + + protected IntSizeUbo() { + super(BufferLayout.STD140, ShaderBufferObjectUsage.UBO); + } + + @Override + protected BufferObjectLayoutDefinition getDefinition() { + return DEFINITION; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/renderer/BlockStatePipRenderer.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/renderer/BlockStatePipRenderer.java index 0b5f773d..0e41f791 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/renderer/BlockStatePipRenderer.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/renderer/BlockStatePipRenderer.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.rendering.gui.renderer; +import org.jetbrains.annotations.ApiStatus; + import com.mojang.blaze3d.platform.Lighting; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/renderer/StructurePipRenderer.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/renderer/StructurePipRenderer.java index 030e87c3..0dfc68fc 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/renderer/StructurePipRenderer.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/renderer/StructurePipRenderer.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.rendering.gui.renderer; +import org.jetbrains.annotations.ApiStatus; + import com.mojang.blaze3d.buffers.GpuBuffer; import com.mojang.blaze3d.buffers.GpuBufferSlice; import com.mojang.blaze3d.systems.CommandEncoder; diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/state/BlockStatePipRenderingState.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/state/BlockStatePipRenderingState.java index 8cc8a1a2..ef5564b7 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/state/BlockStatePipRenderingState.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/state/BlockStatePipRenderingState.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.rendering.gui.state; +import org.jetbrains.annotations.ApiStatus; + import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.gui.navigation.ScreenRectangle; import net.minecraft.client.renderer.block.BlockAndTintGetter; diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/state/DynamicTextureBlitRenderState.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/state/DynamicTextureBlitRenderState.java index bf0f4f0f..167323b1 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/state/DynamicTextureBlitRenderState.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/gui/state/DynamicTextureBlitRenderState.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.rendering.gui.state; +import org.jetbrains.annotations.ApiStatus; + import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.vertex.VertexConsumer; import net.minecraft.client.gui.navigation.ScreenRectangle; diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/ALRIntegrationCompatMixinPlugin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/ALRIntegrationCompatMixinPlugin.java index 5b4cf91f..e6490eb3 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/ALRIntegrationCompatMixinPlugin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/ALRIntegrationCompatMixinPlugin.java @@ -1,10 +1,7 @@ package dev.anvilcraft.lib.v2.rendering.integration.mixins; import com.google.common.collect.ImmutableMap; -import dev.anvilcraft.lib.v2.rendering.integration.IrisSupport; import net.neoforged.fml.loading.FMLLoader; -import net.neoforged.fml.loading.LoadingModList; -import org.jetbrains.annotations.ApiStatus; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; @@ -12,7 +9,6 @@ import java.util.List; import java.util.Set; -@ApiStatus.Internal public class ALRIntegrationCompatMixinPlugin implements IMixinConfigPlugin { private ImmutableMap mixinConditions; diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/CachedBlockEntityRenderingPipelineMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/CachedBlockEntityRenderingPipelineMixin.java index 0b686d66..f16e1ffd 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/CachedBlockEntityRenderingPipelineMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/CachedBlockEntityRenderingPipelineMixin.java @@ -2,7 +2,6 @@ import dev.anvilcraft.lib.v2.rendering.cachedber.pipeline.CachedBlockEntityRenderingPipeline; import dev.anvilcraft.lib.v2.rendering.integration.IrisSupport; -import org.jetbrains.annotations.ApiStatus; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; @@ -11,7 +10,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(CachedBlockEntityRenderingPipeline.class) -@ApiStatus.Internal public abstract class CachedBlockEntityRenderingPipelineMixin { @Shadow diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/RebuildTaskMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/RebuildTaskMixin.java index 58378ed3..0dddaeaa 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/RebuildTaskMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/integration/mixins/RebuildTaskMixin.java @@ -3,14 +3,12 @@ import dev.anvilcraft.lib.v2.rendering.cachedber.pipeline.RebuildTask; import dev.anvilcraft.lib.v2.rendering.integration.IrisSupport; import net.irisshaders.iris.vertices.ImmediateState; -import org.jetbrains.annotations.ApiStatus; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(RebuildTask.class) -@ApiStatus.Internal public class RebuildTaskMixin { @Inject( method = "run", diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GameRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GameRendererMixin.java index f255774d..b315c7ba 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GameRendererMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GameRendererMixin.java @@ -3,14 +3,12 @@ import dev.anvilcraft.lib.v2.rendering.event.MainTargetResizeEvent; import net.minecraft.client.renderer.GameRenderer; import net.neoforged.fml.ModLoader; -import org.jetbrains.annotations.ApiStatus; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(GameRenderer.class) -@ApiStatus.Internal public class GameRendererMixin { @Inject( diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GuiGraphicsExtractorMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GuiGraphicsExtractorMixin.java index 7cd927ef..16b4ac87 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GuiGraphicsExtractorMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GuiGraphicsExtractorMixin.java @@ -14,7 +14,6 @@ import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; -import org.jetbrains.annotations.ApiStatus; import org.joml.Matrix3x2f; import org.joml.Matrix3x2fStack; import org.jspecify.annotations.Nullable; @@ -24,7 +23,6 @@ @SuppressWarnings("AddedMixinMembersNamePattern") @Mixin(GuiGraphicsExtractor.class) -@ApiStatus.Internal public class GuiGraphicsExtractorMixin implements GuiGraphicsExtractorExtension { @Shadow diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GuiRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GuiRendererMixin.java index 251356b7..ba33ef8a 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GuiRendererMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/GuiRendererMixin.java @@ -17,7 +17,6 @@ import net.minecraft.client.renderer.state.gui.GuiItemRenderState; import net.minecraft.client.renderer.state.gui.GuiRenderState; import net.minecraft.util.ARGB; -import org.jetbrains.annotations.ApiStatus; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -30,7 +29,6 @@ import java.util.Map; @Mixin(GuiRenderer.class) -@ApiStatus.Internal public class GuiRendererMixin { @Unique private GuiElementRenderState anvillib$renderState = null; diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/ItemStackRenderStateMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/ItemStackRenderStateMixin.java index bf6e5736..23c7bf18 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/ItemStackRenderStateMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/ItemStackRenderStateMixin.java @@ -2,12 +2,10 @@ import dev.anvilcraft.lib.v2.rendering.internal.ItemStackRenderStateInternals; import net.minecraft.client.renderer.item.ItemStackRenderState; -import org.jetbrains.annotations.ApiStatus; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @Mixin(ItemStackRenderState.class) -@ApiStatus.Internal public class ItemStackRenderStateMixin implements ItemStackRenderStateInternals.Extension { @Unique private float anvillib_rendering$alpha = 1f; diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/MinecraftMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/MinecraftMixin.java index 80237e09..b7fd77bd 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/MinecraftMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/MinecraftMixin.java @@ -1,12 +1,13 @@ package dev.anvilcraft.lib.v2.rendering.mixins; import com.mojang.blaze3d.platform.Window; +import dev.anvilcraft.lib.v2.rendering.ALROptimizations; import dev.anvilcraft.lib.v2.rendering.ALRPostEffects; import dev.anvilcraft.lib.v2.rendering.cachedber.pipeline.CachedBlockEntityRenderingPipeline; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.ALRComputeCapabilities; import net.minecraft.client.Minecraft; import net.minecraft.client.main.GameConfig; import net.minecraft.client.multiplayer.ClientLevel; -import org.jetbrains.annotations.ApiStatus; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -15,7 +16,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(Minecraft.class) -@ApiStatus.Internal public class MinecraftMixin { @Shadow @@ -28,7 +28,9 @@ public class MinecraftMixin { ) private void onCreateInstance(GameConfig gameConfig, CallbackInfo ci) { ALRPostEffects.createPostEffects(); + ALROptimizations.create(); CachedBlockEntityRenderingPipeline.create(); + ALRComputeCapabilities.init(); } // @Inject( diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/RenderTypeMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/RenderTypeMixin.java index 77f6f38c..d3ec2464 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/RenderTypeMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/RenderTypeMixin.java @@ -2,12 +2,10 @@ import dev.anvilcraft.lib.v2.rendering.extension.ALRRenderTypeExtension; import net.minecraft.client.renderer.rendertype.RenderType; -import org.jetbrains.annotations.ApiStatus; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @Mixin(RenderType.class) -@ApiStatus.Internal public class RenderTypeMixin implements ALRRenderTypeExtension { @Unique diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/CommandEncoderMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/CommandEncoderMixin.java index 12f97ab9..35ba5328 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/CommandEncoderMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/CommandEncoderMixin.java @@ -7,13 +7,11 @@ import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRCommandEncoderExtension; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.MemoryBarrierFlag; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.ALRComputePass; -import org.jetbrains.annotations.ApiStatus; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @Mixin(CommandEncoder.class) -@ApiStatus.Internal public class CommandEncoderMixin implements ALRCommandEncoderExtension { @Shadow @Final diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/GpuDeviceMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/GpuDeviceMixin.java index 3bcd260c..8e7709b3 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/GpuDeviceMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/GpuDeviceMixin.java @@ -2,20 +2,24 @@ import com.mojang.blaze3d.systems.GpuDevice; import com.mojang.blaze3d.systems.GpuDeviceBackend; +import com.mojang.blaze3d.textures.GpuTexture; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceBackendExtension; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceExtension; -import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.ALRComputePass; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRHICapabilities; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ExtendedTextureFormat; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeProgramInstance; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeProgramInstanceKey; -import org.jetbrains.annotations.ApiStatus; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.query.GpuQueryObject; +import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; +import java.util.function.Supplier; + @SuppressWarnings("AddedMixinMembersNamePattern") @Mixin(GpuDevice.class) -@ApiStatus.Internal public class GpuDeviceMixin implements ALRGpuDeviceExtension { @Shadow @Final @@ -31,6 +35,41 @@ public void alrDestroyComputeShader(ALRComputeProgramInstance instance) { alrBackend().alrDestroyComputeShader(instance); } + @Override + public GpuQueryObject alrCreateSamplesQuery() { + return alrBackend().alrCreateSamplesQuery(); + } + + @Override + public void alrPushDebugGroup(Supplier message) { + alrBackend().alrPushDebugGroup(message); + } + + @Override + public void alrPopDebugGroup() { + alrBackend().alrPopDebugGroup(); + } + + @Override + public ALRHICapabilities alrhiCreateCapabilities() { + return alrBackend().alrhiCreateCapabilities(); + } + + @Override + public GpuTexture alrCreateExtendedTexture( + @Nullable String label, + @GpuTexture.Usage int usage, + ExtendedTextureFormat format, + int width, + int height, + int depthOrLayers, + int mipLevels + ){ + return alrBackend().alrCreateExtendedTexture( + label, usage, format, width, height, depthOrLayers, mipLevels + ); + } + @Unique private ALRGpuDeviceBackendExtension alrBackend() { return ((ALRGpuDeviceBackendExtension) this.backend); diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/DirectStateAccessMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/DirectStateAccessMixin.java index b576a1f6..2ac5bc20 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/DirectStateAccessMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/DirectStateAccessMixin.java @@ -1,14 +1,12 @@ package dev.anvilcraft.lib.v2.rendering.mixins.blaze3d.gl; import dev.anvilcraft.lib.v2.rendering.foundation.buffers.GpuBufferConstants; -import org.jetbrains.annotations.ApiStatus; import org.lwjgl.opengl.ARBShaderStorageBufferObject; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -@ApiStatus.Internal public class DirectStateAccessMixin { @Mixin(targets = "com.mojang.blaze3d.opengl.DirectStateAccess$Emulated") diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlCommandEncoderMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlCommandEncoderMixin.java index ef2d02f1..093efda8 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlCommandEncoderMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlCommandEncoderMixin.java @@ -9,7 +9,6 @@ import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.MemoryBarrierFlag; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.ALRComputePass; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.gl.GlComputePassBackend; -import org.jetbrains.annotations.ApiStatus; import org.lwjgl.opengl.ARBComputeShader; import org.lwjgl.opengl.ARBShaderImageLoadStore; import org.lwjgl.opengl.GL46; @@ -18,7 +17,6 @@ import org.spongepowered.asm.mixin.Shadow; @Mixin(targets = "com.mojang.blaze3d.opengl.GlCommandEncoder") -@ApiStatus.Internal public class GlCommandEncoderMixin implements ALRCommandEncoderBackendExtension { @Shadow @Final diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlDebugLabelMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlDebugLabelMixin.java index a35136fe..6c5c833d 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlDebugLabelMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlDebugLabelMixin.java @@ -4,7 +4,6 @@ import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.ALRDebugLabelExtension; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeProgramInstance; import net.minecraft.util.StringUtil; -import org.jetbrains.annotations.ApiStatus; import org.lwjgl.opengl.EXTDebugLabel; import org.lwjgl.opengl.GL46; import org.lwjgl.opengl.KHRDebug; @@ -12,7 +11,6 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -@ApiStatus.Internal public class GlDebugLabelMixin { @Mixin(GlDebugLabel.class) public static class Self implements ALRDebugLabelExtension { diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlDeviceMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlDeviceMixin.java index 59d7a87b..bbcd7361 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlDeviceMixin.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlDeviceMixin.java @@ -1,21 +1,37 @@ package dev.anvilcraft.lib.v2.rendering.mixins.blaze3d.gl; +import com.mojang.blaze3d.GpuOutOfMemoryException; +import com.mojang.blaze3d.opengl.GlConst; import com.mojang.blaze3d.opengl.GlDebugLabel; +import com.mojang.blaze3d.opengl.GlStateManager; +import com.mojang.blaze3d.opengl.GlTexture; +import com.mojang.blaze3d.textures.GpuTexture; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceBackendExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRHICapabilities; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ExtendedTextureFormat; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.ALRDebugLabelExtension; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeProgramInstance; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeProgramInstanceKey; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeShaderManager; -import org.jetbrains.annotations.ApiStatus; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.query.GpuQueryObject; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.query.gl.GlSamplesQuery; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl.GlExtendedTexture; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl.GlExtendedTextureConstants; +import org.jspecify.annotations.Nullable; import org.lwjgl.opengl.ARBComputeShader; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL46; +import org.lwjgl.opengl.GLCapabilities; import org.slf4j.Logger; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; + +import java.util.function.Supplier; @Mixin(targets = "com.mojang.blaze3d.opengl.GlDevice") -@ApiStatus.Internal public abstract class GlDeviceMixin implements ALRGpuDeviceBackendExtension { @Shadow @@ -25,6 +41,12 @@ public abstract class GlDeviceMixin implements ALRGpuDeviceBackendExtension { @Shadow public abstract GlDebugLabel debugLabels(); + @Shadow + @Final + private GlDebugLabel debugLabels; + @Unique + private ALRHICapabilities alr$capabilities = null; + @Override public void alrDestroyComputeShader(ALRComputeProgramInstance instance) { GL46.glDeleteProgram(instance.id()); @@ -56,12 +78,102 @@ public ALRComputeProgramInstance alrCompileComputeShader(ALRComputeProgramInstan } @Override - public void alrPushDebugGroup(String name) { - this.debugLabels().pushDebugGroup(() -> name); + public void alrPushDebugGroup(Supplier message) { + this.debugLabels().pushDebugGroup(message); } @Override public void alrPopDebugGroup() { this.debugLabels().popDebugGroup(); } + + @Override + public GpuQueryObject alrCreateSamplesQuery() { + return new GlSamplesQuery(); + } + + @Override + public ALRHICapabilities alrhiCreateCapabilities() { + if (this.alr$capabilities == null) { + GLCapabilities capabilities = GL.getCapabilities(); + this.alr$capabilities = new ALRHICapabilities( + capabilities.GL_ARB_compute_shader + ); + } + return alr$capabilities; + } + + @Override + public GpuTexture alrCreateExtendedTexture( + @Nullable String label, + int usage, + ExtendedTextureFormat format, + int width, + int height, + int depthOrLayers, + int mipLevels + ) { + GlStateManager.clearGlErrors(); + int id = GlStateManager._genTexture(); + if (label == null) { + label = String.valueOf(id); + } + + boolean isCubemap = (usage & 16) != 0; + int target; + if (isCubemap) { + GL11.glBindTexture(34067, id); + target = 34067; + } else { + GlStateManager._bindTexture(id); + target = 3553; + } + + GlStateManager._texParameter(target, 33085, mipLevels - 1); + GlStateManager._texParameter(target, 33082, 0); + GlStateManager._texParameter(target, 33083, mipLevels - 1); + + if (isCubemap) { + for (int cubeTarget : GlConst.CUBEMAP_TARGETS) { + for (int i = 0; i < mipLevels; i++) { + GlStateManager._texImage2D( + cubeTarget, + i, + GlExtendedTextureConstants.toGlInternalId(format), + width >> i, + height >> i, + 0, + GlExtendedTextureConstants.toGlExternalId(format), + GlExtendedTextureConstants.toGlType(format), + null + ); + } + } + } else { + for (int i = 0; i < mipLevels; i++) { + GlStateManager._texImage2D( + target, + i, + GlExtendedTextureConstants.toGlInternalId(format), + width >> i, + height >> i, + 0, + GlExtendedTextureConstants.toGlExternalId(format), + GlExtendedTextureConstants.toGlType(format), + null + ); + } + } + + int error = GlStateManager._getError(); + if (error == 1285) { + throw new GpuOutOfMemoryException("Could not allocate texture of " + width + "x" + height + " for " + label); + } else if (error != 0) { + throw new IllegalStateException("OpenGL error " + error); + } else { + GlExtendedTexture texture = new GlExtendedTexture(usage, label, format, width, height, depthOrLayers, mipLevels, id); + this.debugLabels.applyLabel(texture); + return texture; + } + } } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionCuller.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionCuller.java new file mode 100644 index 00000000..c707ae41 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionCuller.java @@ -0,0 +1,36 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion; + +import com.mojang.blaze3d.systems.GpuDevice; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import org.jspecify.annotations.Nullable; + +public interface OcclusionCuller { + void onResize(int newWidth, int newHeight); + + void beginFrame(); + + void submitFeatureKey(OcclusionKey key, Object feature); + + void processFeatures(CameraRenderState camera); + + boolean shouldDraw(OcclusionKey key, Object feature); + + @SuppressWarnings("ConstantValue") + @Nullable + static OcclusionCuller createInstance(GpuDevice device) { + OcclusionCuller instance; + if (OcclusionMethod.HIERARCHICAL_Z.isSupported() + && (instance = OcclusionMethod.HIERARCHICAL_Z.createInstance(device)) != null + ) { + return instance; + } + + if (OcclusionMethod.GPU_QUERY.isSupported() + && (instance = OcclusionMethod.GPU_QUERY.createInstance(device)) != null + ) { + return instance; + } + + return null; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionKey.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionKey.java new file mode 100644 index 00000000..7f297372 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionKey.java @@ -0,0 +1,45 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion; + +import lombok.Getter; +import lombok.Setter; +import net.minecraft.world.phys.AABB; + +import java.util.function.Supplier; + +/// Stable handle for associating one logical render feature with occlusion +/// state across frames. Reuse the same instance for the feature and update +/// its bounding box as needed; identity, not bounding-box equality, defines +/// the key. +public class OcclusionKey { + + @Getter + @Setter + private AABB boundingBox; + + @Getter + private final Supplier name; + + public OcclusionKey(AABB boundingBox) { + this.boundingBox = boundingBox; + this.name = OcclusionKey::defaultName; + } + + public OcclusionKey(Supplier name, AABB boundingBox) { + this.name = name; + this.boundingBox = boundingBox; + } + + @Override + public final boolean equals(Object obj) { + return this == obj; + } + + @Override + public final int hashCode() { + return System.identityHashCode(this); + } + + private static String defaultName() { + return "OcclusionKey"; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionMethod.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionMethod.java new file mode 100644 index 00000000..4b70a9f0 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionMethod.java @@ -0,0 +1,39 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion; + +import com.mojang.blaze3d.systems.GpuDevice; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.ALRComputeCapabilities; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz.HierarchicalZOcclusionCuller; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.query.GpuQueryOcclusionCuller; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +public enum OcclusionMethod { + GPU_QUERY { + @Override + public boolean isSupported() { + return true; + } + + @Override + public @NonNull OcclusionCuller createInstance(GpuDevice device) { + return new GpuQueryOcclusionCuller((ALRGpuDeviceExtension) device); + } + }, HIERARCHICAL_Z { + @Override + public boolean isSupported() { + return ALRComputeCapabilities.isComputeSupported(); + } + + @Override + public @NonNull OcclusionCuller createInstance(GpuDevice device) { + return new HierarchicalZOcclusionCuller((ALRGpuDeviceExtension) device); + } + }; + + public abstract boolean isSupported(); + + @Nullable + public abstract OcclusionCuller createInstance(GpuDevice device); + +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/ConvertDepthParamsUbo.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/ConvertDepthParamsUbo.java new file mode 100644 index 00000000..961069e6 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/ConvertDepthParamsUbo.java @@ -0,0 +1,44 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz; + +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.layout.BufferLayout; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObject; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObjectLayoutDefinition; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObjectLayoutEntry; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.ShaderBufferObjectUsage; +import lombok.Getter; +import lombok.Setter; +import org.jetbrains.annotations.ApiStatus; + +/// ```glsl +/// layout(std140, binding = 0) uniform ConvertParam { +/// int uWidth; +/// int uHeight; +/// float uPadValue; +/// }; +/// ``` +@Getter +@Setter +@ApiStatus.Internal +public class ConvertDepthParamsUbo extends BufferObject { + + public static final BufferObjectLayoutDefinition DEFINITION = BufferObjectLayoutDefinition.create( + BufferObjectLayoutEntry.ofInt().forGetter(ConvertDepthParamsUbo::getWidth).build(), + BufferObjectLayoutEntry.ofInt().forGetter(ConvertDepthParamsUbo::getHeight).build(), + BufferObjectLayoutEntry.ofFloat().forGetter(ConvertDepthParamsUbo::getPadValue).build() + ); + + public static final int SIZE = DEFINITION.size(BufferLayout.STD140); + + private int width; + private int height; + private float padValue = 1f; + + protected ConvertDepthParamsUbo() { + super(BufferLayout.STD140, ShaderBufferObjectUsage.UBO); + } + + @Override + protected BufferObjectLayoutDefinition getDefinition() { + return DEFINITION; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/HierarchicalZOcclusionCuller.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/HierarchicalZOcclusionCuller.java new file mode 100644 index 00000000..d1890c01 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/HierarchicalZOcclusionCuller.java @@ -0,0 +1,190 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz; + +import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.systems.CommandEncoder; +import com.mojang.blaze3d.systems.GpuDevice; +import com.mojang.blaze3d.textures.GpuTexture; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ExtendedTextureFormat; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.GpuBufferConstants; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionCuller; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionKey; +import dev.anvilcraft.lib.v2.rendering.util.MemoryAccess; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.util.Mth; +import org.joml.Vector2f; +import org.lwjgl.system.MemoryStack; + +import java.nio.ByteBuffer; + +public class HierarchicalZOcclusionCuller implements OcclusionCuller { + /// uint * 6 + public static final int SPD_GLOBAL_ATOMIC_COUNTER_SIZE = 4 * 6; + + private final Minecraft minecraft; + private final ALRGpuDeviceExtension gpuDeviceExtension; + private final GpuDevice gpuDevice; + + private final SPDConstantBuffer spdParams = new SPDConstantBuffer(); + private final ConvertDepthParamsUbo convertParams = new ConvertDepthParamsUbo(); + + private final GpuBuffer spdParamsBuffer; + private final GpuBuffer convertParamsBuffer; + private final GpuBuffer spdGlobalAtomicCounterBuffer; + + private int framebufferWidth; + private int framebufferHeight; + private int paddedWidth; + private int paddedHeight; + + private int dispatchDimensionX; + private int dispatchDimensionY; + + /// mip layer count, excluding input layer (mip 0) + private int mipLayerCount = 0; + private GpuTexture[] mipTextures; + private MipLayer[] mipLayers; + + public HierarchicalZOcclusionCuller(ALRGpuDeviceExtension device) { + this.minecraft = Minecraft.getInstance(); + this.gpuDeviceExtension = device; + this.gpuDevice = (GpuDevice) device; + + RenderTarget mainRenderTarget = this.minecraft.getMainRenderTarget(); + + this.spdParamsBuffer = gpuDevice.createBuffer( + () -> "SPD Constant Buffer", + GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_UNIFORM, + SPDConstantBuffer.SIZE + ); + + this.convertParamsBuffer = gpuDevice.createBuffer( + () -> "SPD Depth Convert Params", + GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_UNIFORM, + ConvertDepthParamsUbo.SIZE + ); + + this.spdGlobalAtomicCounterBuffer = gpuDevice.createBuffer( + () -> "SPD Global Atomic Counter", + GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_MAP_READ | GpuBuffer.USAGE_MAP_WRITE | GpuBufferConstants.USAGE_SHADER_STORAGE, + SPD_GLOBAL_ATOMIC_COUNTER_SIZE + ); + + this.onResize(mainRenderTarget.width, mainRenderTarget.height); + } + + @Override + public void onResize(int width, int height) { + this.framebufferWidth = width; + this.framebufferHeight = height; + + this.paddedWidth = Math.ceilDiv(width, 64) * 64; + this.paddedHeight = Math.ceilDiv(height, 64) * 64; + + this.mipLayerCount = Math.min( + Mth.floor( + Mth.log2( + Math.max( + this.paddedWidth, + this.paddedHeight + ) + ) + ), + 12 + ); + + this.dispatchDimensionX = Mth.ceil(paddedWidth / 64f); + this.dispatchDimensionY = Mth.ceil(paddedHeight / 64f); + + this.deleteTextures(); + + int slotCount = mipLayerCount + 1; + this.mipTextures = new GpuTexture[slotCount]; + this.mipLayers = new MipLayer[slotCount]; + + for (int i = 0; i < slotCount; i++) { + int mipW = Math.max(1, this.paddedWidth >> i); + int mipH = Math.max(1, this.paddedHeight >> i); + + MipLayer mipLayer = new MipLayer(); + mipLayer.setWidth(mipW); + mipLayer.setHeight(mipH); + + GpuTexture texture = this.gpuDeviceExtension.alrCreateExtendedTexture( + "HierarchicalZ Mip Chain Image #" + i, + GpuTexture.USAGE_COPY_SRC | GpuTexture.USAGE_COPY_DST | GpuTexture.USAGE_TEXTURE_BINDING, + ExtendedTextureFormat.R32F, + mipW, + mipH, + 1, + 1 + ); + + this.mipTextures[i] = texture; + this.mipLayers[i] = mipLayer; + } + + CommandEncoder commandEncoder = gpuDevice.createCommandEncoder(); + this.ffxSpdSetup(commandEncoder); + this.depthConvertSetup(commandEncoder); + this.clearAtomicCounter(); + } + + /// Setup required constant values for SPD (CPU). + private void ffxSpdSetup(CommandEncoder commandEncoder) { + this.spdParams.setMips(this.mipLayerCount); + this.spdParams.setNumWorkGroups(this.dispatchDimensionX * this.dispatchDimensionY); + this.spdParams.setWorkGroupOffset(new Vector2f(0, 0)); + this.spdParams.setInvInputSize(new Vector2f(1.0f / this.paddedWidth, 1.0f / this.paddedHeight)); + + this.spdParams.upload(commandEncoder, this.spdParamsBuffer.slice()); + } + + private void depthConvertSetup(CommandEncoder commandEncoder) { + this.convertParams.setWidth(this.framebufferWidth); + this.convertParams.setHeight(this.framebufferHeight); + this.convertParams.setPadValue(1); + + this.convertParams.upload(commandEncoder, this.convertParamsBuffer.slice()); + } + + private void clearAtomicCounter() { + CommandEncoder commandEncoder = gpuDevice.createCommandEncoder(); + + try (MemoryStack memoryStack = MemoryStack.stackPush()) { + ByteBuffer buffer = memoryStack.malloc(SPD_GLOBAL_ATOMIC_COUNTER_SIZE); + MemoryAccess.memset(MemoryAccess.memAddress(buffer), SPD_GLOBAL_ATOMIC_COUNTER_SIZE, (byte) 0); + commandEncoder.writeToBuffer(spdGlobalAtomicCounterBuffer.slice(), buffer); + } + } + + @Override + public void beginFrame() { + + } + + @Override + public void submitFeatureKey(OcclusionKey key, Object feature) { + + } + + @Override + public void processFeatures(CameraRenderState camera) { + + } + + @Override + public boolean shouldDraw(OcclusionKey key, Object feature) { + return true; + } + + private void deleteTextures() { + if (mipTextures != null) { + for (GpuTexture mipTexture : mipTextures) { + mipTexture.close(); + } + } + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/MipLayer.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/MipLayer.java new file mode 100644 index 00000000..5dc59b2d --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/MipLayer.java @@ -0,0 +1,25 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz; + +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.layout.BufferLayout; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObject; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObjectLayoutDefinition; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.ShaderBufferObjectUsage; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class MipLayer extends BufferObject { + + private int width; + private int height; + + protected MipLayer() { + super(BufferLayout.STD430, ShaderBufferObjectUsage.SSBO); + } + + @Override + protected BufferObjectLayoutDefinition getDefinition() { + return null; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/SPDConstantBuffer.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/SPDConstantBuffer.java new file mode 100644 index 00000000..271c278f --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/SPDConstantBuffer.java @@ -0,0 +1,47 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz; + +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.layout.BufferLayout; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObject; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObjectLayoutDefinition; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.BufferObjectLayoutEntry; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.object.ShaderBufferObjectUsage; +import lombok.Getter; +import lombok.Setter; +import org.joml.Vector2f; + +@Getter +@Setter +public class SPDConstantBuffer extends BufferObject { + + public static final BufferObjectLayoutDefinition DEFINITION = BufferObjectLayoutDefinition.create( + BufferObjectLayoutEntry.ofInt().forGetter(SPDConstantBuffer::getMips).build(), + BufferObjectLayoutEntry.ofInt().forGetter(SPDConstantBuffer::getNumWorkGroups).build(), + BufferObjectLayoutEntry.ofVec2f().forGetter(SPDConstantBuffer::getWorkGroupOffset).build(), + BufferObjectLayoutEntry.ofVec2f().forGetter(SPDConstantBuffer::getInvInputSize).build() + ); + + public static final int SIZE = DEFINITION.size(BufferLayout.STD140); + + /// The total number of mip levels SPD generates for each input texture slice. + private int mips = 13; + + /// number of thread groups per slice + private int numWorkGroups; + + /// The offset of the first 64x64 input tile in work-group coordinates, normally `(left / 64, top / 64)` for a + /// downsampled subregion. + private Vector2f workGroupOffset; + + /// The input texture size is `size = (width, height)`. This field stores + /// `invInputSize = (1.0 / size.x, 1.0 / size.y)` for normalized UV conversion when linear sampling is enabled. + private Vector2f invInputSize; + + protected SPDConstantBuffer() { + super(BufferLayout.STD140, ShaderBufferObjectUsage.UBO); + } + + @Override + protected BufferObjectLayoutDefinition getDefinition() { + return DEFINITION; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/FrameState.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/FrameState.java new file mode 100644 index 00000000..86dca0cb --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/FrameState.java @@ -0,0 +1,127 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.query; + +import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.systems.CommandEncoder; +import com.mojang.blaze3d.systems.GpuDevice; +import com.mojang.blaze3d.systems.RenderPass; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.VertexFormat; +import dev.anvilcraft.lib.v2.rendering.ALRPipelines; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.query.GpuQueryObject; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.ubo.FullTransformsUbo; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionKey; +import it.unimi.dsi.fastutil.objects.Reference2LongLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2LongMap; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.DynamicUniformStorage; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import org.jetbrains.annotations.ApiStatus; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.OptionalDouble; +import java.util.OptionalInt; + +@ApiStatus.Internal +public class FrameState implements AutoCloseable { + private final Map keySamplesMap = new IdentityHashMap<>(); + private final Reference2LongMap results = new Reference2LongLinkedOpenHashMap<>(); + private final GpuQueryOcclusionCuller owner; + + public FrameState(GpuQueryOcclusionCuller owner) { + this.owner = owner; + } + + public void addKey(OcclusionKey key) { + GpuQueryObject gpuSamplesQuery = this.keySamplesMap.get(key); + if (gpuSamplesQuery == null) { + this.keySamplesMap.put(key, owner.acquireQuery()); + } + } + + public boolean shouldDraw(OcclusionKey key) { + return results.getOrDefault(key, 1) > 0; + } + + public void fetchResults() { + for (Map.Entry entry : keySamplesMap.entrySet()) { + results.put(entry.getKey(), entry.getValue().getValue()); + } + } + + @SuppressWarnings("DataFlowIssue") + public void runQueries(CameraRenderState camera) { + CommandEncoder commandEncoder = this.owner.getCommandEncoder(); + RenderTarget target = Minecraft.getInstance().getMainRenderTarget(); + + RenderSystem.AutoStorageIndexBuffer sequentialBuffer = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS); + GpuBuffer buffer = sequentialBuffer.getBuffer(6 * 6); + VertexFormat.IndexType type = sequentialBuffer.type(); + + GpuDevice device = RenderSystem.getDevice(); + + ALRGpuDeviceExtension deviceExtension = (ALRGpuDeviceExtension) device; + + deviceExtension.alrPushDebugGroup(() -> "Gpu Occlusion Query Draw"); + + List queries = new ArrayList<>(); + + for (Map.Entry entry : keySamplesMap.entrySet()) { + QueryInstance query = this.owner.acquireInstance(); + query.prepareTransform(entry.getKey(), entry.getValue(), camera); + queries.add(query); + } + + FullTransformsUbo[] transforms = new FullTransformsUbo[queries.size()]; + for (int i = 0; i < queries.size(); i++) { + transforms[i] = queries.get(i).transformsUbo(); + } + + DynamicUniformStorage dynamicStorage = this.owner.getTransformsDynamicStorage(); + + GpuBufferSlice[] gpuBufferSlices = dynamicStorage.writeUniforms(transforms); + for (int i = 0; i < queries.size(); i++) { + queries.get(i).uniform(gpuBufferSlices[i]); + } + + try (RenderPass renderPass = commandEncoder.createRenderPass( + () -> "Gpu Occlusion Query Draw Batch", + target.getColorTextureView(), + OptionalInt.empty(), + target.getDepthTextureView(), + OptionalDouble.empty() + )) { + renderPass.setPipeline(ALRPipelines.OCCLUSION_QUERY); + + for (QueryInstance query : queries) { + renderPass.setUniform("Transforms", query.uniform()); + renderPass.setVertexBuffer(0, query.vertexBuffer()); + renderPass.setIndexBuffer(buffer, type); + + query.queryObject().begin(); + renderPass.drawIndexed(0, 0, 6 * 6, 1); + query.queryObject().end(); + } + } + + for (QueryInstance query : queries) { + this.owner.releaseInstance(query); + } + + dynamicStorage.endFrame(); + + deviceExtension.alrPopDebugGroup(); + } + + @Override + public void close() { + for (GpuQueryObject value : keySamplesMap.values()) { + owner.releaseQuery(value); + } + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/GpuQueryOcclusionCuller.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/GpuQueryOcclusionCuller.java new file mode 100644 index 00000000..ed4f6ad0 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/GpuQueryOcclusionCuller.java @@ -0,0 +1,113 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.query; + +import com.mojang.blaze3d.systems.CommandEncoder; +import com.mojang.blaze3d.systems.GpuDevice; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.query.GpuQueryObject; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.ubo.FullTransformsUbo; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionCuller; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionKey; +import lombok.Getter; +import net.minecraft.client.renderer.DynamicUniformStorage; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import org.jetbrains.annotations.ApiStatus; + +/// ### How does this work +/// In a single frame query happens in order described below. +/// - Frame begin +/// - Read back query result in previous frame +/// - Minecraft collects features to draw in current frame +/// - Culler collects OcclusionKey for features requested to cull +/// - Minecraft draws solid terrain +/// - Culler draws bounding box of each OcclusionKey and submits query about whether any samples passed in each draw event +/// - Minecraft draws features +/// - Ask culler whether a feature should be drawn, answers are based on the previous frame's query results (one-frame latency) +/// - ... +/// - Frame ends +@ApiStatus.Internal +public class GpuQueryOcclusionCuller implements OcclusionCuller { + @Getter + private final CommandEncoder commandEncoder; + private final GpuSampleQueryPool sampleQueryPool; + private final QueryInstancePool queryInstancePool; + private final ALRGpuDeviceExtension extension; + @Getter + private final DynamicUniformStorage transformsDynamicStorage; + + private FrameState previousFrameState = null; + private FrameState currentFrameState = null; + + public GpuQueryOcclusionCuller(ALRGpuDeviceExtension extension) { + this.extension = extension; + this.sampleQueryPool = new GpuSampleQueryPool(extension); + GpuDevice gpuDevice = (GpuDevice) extension; + + this.commandEncoder = gpuDevice.createCommandEncoder(); + + this.queryInstancePool = new QueryInstancePool( + new QueryInstance.CreationContext( + commandEncoder, + gpuDevice + ) + ); + + this.transformsDynamicStorage = new DynamicUniformStorage<>( + "Gpu Query Occlusion Transforms Dynamic Uniform", + FullTransformsUbo.SIZE, + 512 + ); + } + + @Override + public void onResize(int newWidth, int newHeight) { + // Query-based culling has no size-dependent resources. + } + + @Override + public void beginFrame() { + if (this.currentFrameState == null) { + this.currentFrameState = new FrameState(this); + return; + } + if (this.previousFrameState != null) { + this.previousFrameState.close(); + } + this.previousFrameState = this.currentFrameState; + this.previousFrameState.fetchResults(); + this.currentFrameState = new FrameState(this); + } + + @Override + public void submitFeatureKey(OcclusionKey key, Object feature) { + this.currentFrameState.addKey(key); + } + + @Override + public void processFeatures(CameraRenderState camera) { + this.currentFrameState.runQueries(camera); + } + + @Override + public boolean shouldDraw(OcclusionKey key, Object feature) { + if (previousFrameState == null) { + return true; + } + return this.previousFrameState.shouldDraw(key); + } + + public GpuQueryObject acquireQuery() { + return this.sampleQueryPool.acquire(); + } + + public void releaseQuery(GpuQueryObject query) { + this.sampleQueryPool.release(query); + } + + public QueryInstance acquireInstance() { + return this.queryInstancePool.acquire(); + } + + public void releaseInstance(QueryInstance queryInstance) { + this.queryInstancePool.release(queryInstance); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/GpuSampleQueryPool.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/GpuSampleQueryPool.java new file mode 100644 index 00000000..42ff4c08 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/GpuSampleQueryPool.java @@ -0,0 +1,20 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.query; + +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.query.GpuQueryObject; +import dev.anvilcraft.lib.v2.rendering.foundation.GpuReusableResourcePool; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NonNull; + +@ApiStatus.Internal +public class GpuSampleQueryPool extends GpuReusableResourcePool { + public GpuSampleQueryPool(ALRGpuDeviceExtension context) { + super(16, context); + } + + @Override + @NonNull + protected GpuQueryObject createInstance(@NonNull ALRGpuDeviceExtension context, int i) { + return context.alrCreateSamplesQuery(); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/QueryInstance.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/QueryInstance.java new file mode 100644 index 00000000..1f20db11 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/QueryInstance.java @@ -0,0 +1,239 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.query; + +import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import com.mojang.blaze3d.systems.CommandEncoder; +import com.mojang.blaze3d.systems.GpuDevice; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.MeshData; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexFormat; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.query.GpuQueryObject; +import dev.anvilcraft.lib.v2.rendering.foundation.GpuReusableResource; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.ubo.FullTransformsUbo; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionKey; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.ApiStatus; +import org.joml.Matrix4f; +import org.joml.Vector3f; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +@ApiStatus.Internal +public final class QueryInstance implements GpuReusableResource { + + /// use single unit box and model view matrix instead + /// ((3 float * 4) for each quad * 6) for cube * 1 + public static final int DEFAULT_VERTEX_BUFFER_SIZE = 3 * 4 * 4 * 6; + + /// Shared between QueryInstance instances + private final GpuBuffer vertexBuffer; + + private final FullTransformsUbo transformsUbo; + + @Nullable + private OcclusionKey key; + + @Nullable + private GpuQueryObject queryObject; + @Nullable + private GpuBufferSlice uniform; + + private boolean closed = false; + private boolean acquired = false; + + public QueryInstance( + GpuBuffer vertexBuffer, + FullTransformsUbo transformsUbo + ) { + this.vertexBuffer = vertexBuffer; + this.transformsUbo = transformsUbo; + } + + void prepareTransform(OcclusionKey key, GpuQueryObject queryObject, CameraRenderState camera) { + this.key = key; + this.queryObject = queryObject; + + this.transformsUbo.getProjMat().set(camera.projectionMatrix); + + Matrix4f modelViewMat = this.transformsUbo.getModelViewMat(); + modelViewMat.set(camera.viewRotationMatrix); + + Vec3 cameraPos = camera.pos; + modelViewMat.translate( + (float) -cameraPos.x, + (float) -cameraPos.y, + (float) -cameraPos.z + ); + + AABB boundingBox = key.getBoundingBox().inflate(0.1); + + Vector3f min = new Vector3f( + (float) boundingBox.minX, + (float) boundingBox.minY, + (float) boundingBox.minZ + ); + + Vector3f max = new Vector3f( + (float) boundingBox.maxX, + (float) boundingBox.maxY, + (float) boundingBox.maxZ + ); + + // ChatGPT can make mistakes. Check important info. + Matrix4f transformation = new Matrix4f() + .translate(min) + .scale( + max.x - min.x, + max.y - min.y, + max.z - min.z + ); + + modelViewMat.mul(transformation); + } + + @Override + public void acquire() { + this.acquired = true; + } + + @Override + public void release() { + this.acquired = false; + this.key = null; + this.queryObject = null; + this.uniform = null; + } + + @Override + public boolean isAcquired() { + return this.acquired; + } + + @Override + public void close() { + if (!closed) { + // nothing to close if we handle all lifecycle logic correct + this.closed = true; + } + } + + public GpuBuffer vertexBuffer() { + return vertexBuffer; + } + + public FullTransformsUbo transformsUbo() { + return transformsUbo; + } + + @Nullable + public OcclusionKey key() { + return key; + } + + @Nullable + public GpuQueryObject queryObject() { + return queryObject; + } + + @Nullable + public GpuBufferSlice uniform() { + return uniform; + } + + public void uniform(GpuBufferSlice uniform) { + this.uniform = uniform; + } + + public static QueryInstance newInstance(CreationContext context, int index) { + return new QueryInstance( + context.getVertexBuffer(), + new FullTransformsUbo() + ); + } + + public record CreationContext( + CommandEncoder commandEncoder, + GpuDevice device + ) { + /// should keep alive when game running + private static GpuBuffer vertexBuffer; + + @NonNull + public GpuBuffer getVertexBuffer() { + if (vertexBuffer == null) { + prepareMesh(this); + } + return vertexBuffer; + } + + private static void prepareMesh(CreationContext context) { + float x0 = 0; + float y0 = 0; + float z0 = 0; + float x1 = 1; + float y1 = 1; + float z1 = 1; + + BufferBuilder bufferBuilder = Tesselator.getInstance() + .begin( + VertexFormat.Mode.QUADS, + DefaultVertexFormat.POSITION + ); + + // z+ + bufferBuilder.addVertex(x0, y0, z1); + bufferBuilder.addVertex(x1, y0, z1); + bufferBuilder.addVertex(x1, y1, z1); + bufferBuilder.addVertex(x0, y1, z1); + + // z- + bufferBuilder.addVertex(x0, y0, z0); + bufferBuilder.addVertex(x0, y1, z0); + bufferBuilder.addVertex(x1, y1, z0); + bufferBuilder.addVertex(x1, y0, z0); + + // x+ + bufferBuilder.addVertex(x1, y0, z0); + bufferBuilder.addVertex(x1, y0, z1); + bufferBuilder.addVertex(x1, y1, z1); + bufferBuilder.addVertex(x1, y1, z0); + + // x- + bufferBuilder.addVertex(x0, y0, z0); + bufferBuilder.addVertex(x0, y1, z0); + bufferBuilder.addVertex(x0, y1, z1); + bufferBuilder.addVertex(x0, y0, z1); + + // y+ + bufferBuilder.addVertex(x0, y1, z0); + bufferBuilder.addVertex(x0, y1, z1); + bufferBuilder.addVertex(x1, y1, z1); + bufferBuilder.addVertex(x1, y1, z0); + + // y- + bufferBuilder.addVertex(x0, y0, z0); + bufferBuilder.addVertex(x1, y0, z0); + bufferBuilder.addVertex(x1, y0, z1); + bufferBuilder.addVertex(x0, y0, z1); + + MeshData orThrow = bufferBuilder.buildOrThrow(); + + if (vertexBuffer == null) { + vertexBuffer = context.device.createBuffer( + () -> "Gpu Occlusion Query Vertex Buffer", + GpuBuffer.USAGE_VERTEX | GpuBuffer.USAGE_COPY_DST, + DEFAULT_VERTEX_BUFFER_SIZE + ); + } + + context.commandEncoder.writeToBuffer(vertexBuffer.slice(), orThrow.vertexBuffer()); + + orThrow.close(); + } + + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/QueryInstancePool.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/QueryInstancePool.java new file mode 100644 index 00000000..60259d2a --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/QueryInstancePool.java @@ -0,0 +1,16 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.query; + +import dev.anvilcraft.lib.v2.rendering.foundation.GpuReusableResourcePool; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class QueryInstancePool extends GpuReusableResourcePool { + public QueryInstancePool(QueryInstance.CreationContext context) { + super(2, context); + } + + @Override + protected QueryInstance createInstance(QueryInstance.CreationContext context, int i) { + return QueryInstance.newInstance(context, i); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/util/MemoryAccess.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/util/MemoryAccess.java new file mode 100644 index 00000000..54289c19 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/util/MemoryAccess.java @@ -0,0 +1,123 @@ +package dev.anvilcraft.lib.v2.rendering.util; + +import org.lwjgl.system.Pointer; +import sun.misc.Unsafe; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.function.LongPredicate; + +import static org.lwjgl.system.Pointer.BITS32; +import static org.lwjgl.system.jni.JNINativeInterface.NewDirectByteBuffer; + +/// @author IMS212 +@SuppressWarnings("removal") +public class MemoryAccess { + private static final Unsafe UNSAFE = getUnsafe(); + private static final boolean BITS32 = Pointer.BITS32; + + private static final long ADDRESS = getAddressOffset(); + + private static Unsafe getUnsafe() { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (Unsafe) f.get(null); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + public static long memAddress(ByteBuffer buffer) { + return buffer.position() + UNSAFE.getLong(buffer, ADDRESS); + } + + public static void memset(long address, long size, byte value) { + UNSAFE.setMemory(address, size, value); + } + + public static void putInt(long address, int value) { + UNSAFE.putInt(address, value); + } + + public static void putFloat(long address, float value) { + UNSAFE.putFloat(address, value); + } + + public static void putLong(long address, long value) { + UNSAFE.putLong(address, value); + } + + public static void putShort(long address, short value) { + UNSAFE.putShort(address, value); + } + + public static void putByte(long address, byte b) { + UNSAFE.putByte(address, b); + } + + public static int getInt(long address) { + return UNSAFE.getInt(address); + } + + public static float getFloat(long address) { + return UNSAFE.getFloat(address); + } + + public static long getLong(long address) { + return UNSAFE.getLong(address); + } + + public static short getShort(long address) { + return UNSAFE.getShort(address); + } + + public static byte getByte(long address) { + return UNSAFE.getByte(address); + } + + public static void putAddress(long address, long value) { + if (BITS32) { + UNSAFE.putInt(address, (int) value); + } else { + UNSAFE.putLong(address, value); + } + } + + public static long getAddress(long address) { + if (BITS32) { + return UNSAFE.getInt(address) & 0xFFFF_FFFFL; + } else { + return UNSAFE.getLong(address); + } + } + + private static long getFieldOffset(Class containerType, Class fieldType, LongPredicate predicate) { + Class c = containerType; + while (c != Object.class) { + Field[] fields = c.getDeclaredFields(); + for (Field field : fields) { + if (!field.getType().isAssignableFrom(fieldType) || Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) { + continue; + } + + long offset = UNSAFE.objectFieldOffset(field); + if (predicate.test(offset)) { + return offset; + } + } + c = c.getSuperclass(); + } + throw new UnsupportedOperationException("Failed to find field offset in class."); + } + + private static long getAddressOffset() { + long MAGIC_ADDRESS = 0xDEADBEEF8BADF00DL & (BITS32 ? 0xFFFF_FFFFL : 0xFFFF_FFFF_FFFF_FFFFL); + + ByteBuffer bb = Objects.requireNonNull(NewDirectByteBuffer(MAGIC_ADDRESS, 0)); + + return getFieldOffset(bb.getClass(), long.class, offset -> UNSAFE.getLong(bb, offset) == MAGIC_ADDRESS); + } +} \ No newline at end of file diff --git a/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/compute/depth_convert.csh b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/compute/depth_convert.csh new file mode 100644 index 00000000..572c39ff --- /dev/null +++ b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/compute/depth_convert.csh @@ -0,0 +1,26 @@ +#version 460 core + +layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in; + +layout(std140, binding = 0) uniform ConvertParam { + int uWidth; + int uHeight; + float uPadValue; +}; + +layout(binding = 1) uniform sampler2D Input; + +layout(binding = 2, r32f) writeonly uniform image2D Output; + +void main() { + ivec2 idx = ivec2(gl_GlobalInvocationID.xy); + + vec4 result; + + if (idx.x >= uWidth || idx.y >= uHeight) { + result = vec4(uPadValue, 0, 0, 1); + } else { + result = texelFetch(Input, idx, 0); + } + imageStore(Output, idx, vec4(result.r, 0.0, 0.0, 1.0)); +} \ No newline at end of file diff --git a/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/compute/ffx_spd_downsample_pass.csh b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/compute/ffx_spd_downsample_pass.csh new file mode 100644 index 00000000..a371e4a6 --- /dev/null +++ b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/compute/ffx_spd_downsample_pass.csh @@ -0,0 +1,680 @@ +// This file is part of the FidelityFX SDK. +// +// Copyright (C) 2024 Advanced Micro Devices, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// SPD pass +// SRV 0 : SPD_InputDownsampleSrc : r_input_downsample_src +// UAV 0 : SPD_InternalGlobalAtomic : rw_internal_global_atomic +// UAV 1 : SPD_InputDownsampleSrcMidMip : rw_input_downsample_src_mid_mip +// UAV 2 : SPD_InputDownsampleSrcMips : rw_input_downsample_src_mips +// CB 0 : cbSPD + +#version 450 + +#define SPD_MAX_MIP_LEVELS 12 + +#ifndef FFX_SPD_OPTION_DOWNSAMPLE_FILTER +#define FFX_SPD_OPTION_DOWNSAMPLE_FILTER 0 +#endif + +#ifndef FFX_SPD_OPTION_LINEAR_SAMPLE +#define FFX_SPD_OPTION_LINEAR_SAMPLE 0 +#endif + +#ifndef FFX_SPD_OPTION_WAVE_INTEROP_LDS +#define FFX_SPD_OPTION_WAVE_INTEROP_LDS 0 +#endif + +#if FFX_SPD_OPTION_LINEAR_SAMPLE +#define SPD_LINEAR_SAMPLER 1 +#endif + +#if FFX_SPD_OPTION_WAVE_INTEROP_LDS +#define FFX_SPD_NO_WAVE_OPERATIONS 1 +#else +#extension GL_KHR_shader_subgroup_quad : require +#endif + + +layout (set = 0, binding = 0, std140) uniform cbFSR1_t +{ + uint mips; + uint numWorkGroups; + uvec2 workGroupOffset; + vec2 invInputSize; // Only used for linear sampling mode +} cbFSR1; + +uint Mips() +{ + return cbFSR1.mips; +} + +uint NumWorkGroups() +{ + return cbFSR1.numWorkGroups; +} + +uvec2 WorkGroupOffset() +{ + return cbFSR1.workGroupOffset; +} + +vec2 InvInputSize() +{ + return cbFSR1.invInputSize; +} + +// separate texture and sampler objects are unavailable in opengl +//layout (set = 0, binding = 1000) uniform sampler s_LinearClamp; +//// SRVs +//layout (set = 0, binding = 0) uniform texture2DArray r_input_downsample_src; +// in our usage case, those arrays only have one element, so replace it with a sampler2D +layout (set = 0, binding = 1) uniform sampler2D r_input_downsample_src; + +// UAV declarations +// replace huge binding slot in original shader 2000 to 2 +layout (set = 0, binding = 2, std430) coherent buffer rw_internal_global_atomic_t +{ + uint counter[6]; +} rw_internal_global_atomic; + +// replace huge binding slot in original shader 2001 to 3 +// bind mip map 6 to this uniform +// change format from rgba32f to r32f because we are handling depth texture +layout (set = 0, binding = 3, r32f) coherent uniform image2D rw_input_downsample_src_mid_mip; + +// replace huge binding slot in original shader 2002 to 4 +// change format from rgba32f to r32f because we are handling depth texture +layout (set = 0, binding = 4, r32f) uniform image2D rw_input_downsample_src_mips[SPD_MAX_MIP_LEVELS + 1]; + +/// Compute an SRGB value from a linear value. +/// +/// @param [in] value The value to convert to SRGB from linear. +/// +/// @returns +/// A value in SRGB space. +/// +/// @ingroup GPUCore +float ffxSrgbFromLinear(float value) +{ + vec3 j = vec3(0.0031308 * 12.92, 12.92, 1.0 / 2.4); + vec2 k = vec2(1.055, -0.055); + // wrong clamp order? original: + // return clamp(j.x, value * j.y, pow(value, j.z) * k.x + k.y); + return clamp(pow(value, j.z) * k.x + k.y, j.x, value * j.y); +} + +/// A helper function performing a remap 64x1 to 8x8 remapping which is necessary for 2D wave reductions. +/// +/// The 64-wide lane indices to 8x8 remapping is performed as follows: +/// +/// 00 01 08 09 10 11 18 19 +/// 02 03 0a 0b 12 13 1a 1b +/// 04 05 0c 0d 14 15 1c 1d +/// 06 07 0e 0f 16 17 1e 1f +/// 20 21 28 29 30 31 38 39 +/// 22 23 2a 2b 32 33 3a 3b +/// 24 25 2c 2d 34 35 3c 3d +/// 26 27 2e 2f 36 37 3e 3f +/// +/// @param [in] a The input 1D coordinate to remap. +/// +/// @returns +/// The remapped 2D coordinates. +/// +/// @ingroup GPUCore +uvec2 ffxRemapForWaveReduction(uint a) +{ + return uvec2(((a >> 2u) & 6u) | (a & 1u), ((a >> 3u) & 4u) | ((a >> 1u) & 3u)); +} + +// removed slice because we are using image2D/sampler2D +vec4 SampleSrcImage(ivec2 uv) +{ + vec2 textureCoord = vec2(uv) * InvInputSize() + InvInputSize(); + // vec4 result = textureLod(sampler2DArray(r_input_downsample_src, s_LinearClamp), vec3(textureCoord, slice), 0); + vec4 result = textureLod(r_input_downsample_src, textureCoord, 0); + // remove srgb convert because minecraft use linear rgb8 unorm + // return vec4(ffxSrgbFromLinear(result.x), ffxSrgbFromLinear(result.y), ffxSrgbFromLinear(result.z), result.w); + return result; +} + +vec4 LoadSrcImage(ivec2 uv) +{ + return imageLoad(rw_input_downsample_src_mips[0], uv); +} + +void StoreSrcMip(vec4 value, ivec2 uv, uint mip) +{ + imageStore(rw_input_downsample_src_mips[mip], uv, value); +} + +vec4 LoadMidMip(ivec2 uv) +{ + return imageLoad(rw_input_downsample_src_mid_mip, uv); +} + +void StoreMidMip(vec4 value, ivec2 uv) +{ + imageStore(rw_input_downsample_src_mid_mip, uv, value); +} + +void IncreaseAtomicCounter(uint slice, inout uint counter) +{ + counter = atomicAdd(rw_internal_global_atomic.counter[slice], 1); +} + +void ResetAtomicCounter(uint slice) +{ + rw_internal_global_atomic.counter[slice] = 0; +} + +shared uint spdCounter; + +void SpdIncreaseAtomicCounter(uint slice) +{ + IncreaseAtomicCounter(slice, spdCounter); +} + +uint SpdGetAtomicCounter() +{ + return spdCounter; +} + +void SpdResetAtomicCounter(uint slice) +{ + ResetAtomicCounter(slice); +} + +shared float spdIntermediateR[16][16]; +shared float spdIntermediateG[16][16]; +shared float spdIntermediateB[16][16]; +shared float spdIntermediateA[16][16]; + +vec4 SpdLoadSourceImage(ivec2 tex) +{ + #if defined SPD_LINEAR_SAMPLER + return SampleSrcImage(tex); + #else + return LoadSrcImage(tex); + #endif // SPD_LINEAR_SAMPLER +} + +vec4 SpdLoad(ivec2 tex) +{ + return LoadMidMip(tex); +} + +void SpdStore(ivec2 pix, vec4 outValue, uint mip) +{ + if (mip == 5) + StoreMidMip(outValue, pix); + else + StoreSrcMip(outValue, pix, mip + 1); +} + +vec4 SpdLoadIntermediate(uint x, uint y) +{ + return vec4(spdIntermediateR[x][y], spdIntermediateG[x][y], spdIntermediateB[x][y], spdIntermediateA[x][y]); +} + +void SpdStoreIntermediate(uint x, uint y, vec4 value) +{ + spdIntermediateR[x][y] = value.x; + spdIntermediateG[x][y] = value.y; + spdIntermediateB[x][y] = value.z; + spdIntermediateA[x][y] = value.w; +} + +vec4 SpdReduce4(vec4 v0, vec4 v1, vec4 v2, vec4 v3) +{ + #if FFX_SPD_OPTION_DOWNSAMPLE_FILTER == 1 + return min(min(v0, v1), min(v2, v3)); + #elif FFX_SPD_OPTION_DOWNSAMPLE_FILTER == 2 + return max(max(v0, v1), max(v2, v3)); + #else + return (v0 + v1 + v2 + v3) * 0.25; + #endif +} + +void ffxSpdWorkgroupShuffleBarrier() +{ + groupMemoryBarrier(); + barrier(); +} + +// Only last active workgroup should proceed +bool SpdExitWorkgroup(uint numWorkGroups, uint localInvocationIndex) +{ + // global atomic counter + if (localInvocationIndex == 0) + { + SpdIncreaseAtomicCounter(0); + } + + ffxSpdWorkgroupShuffleBarrier(); + return (SpdGetAtomicCounter() != (numWorkGroups - 1)); +} + +// User defined: vec4 SpdReduce4(vec4 v0, vec4 v1, vec4 v2, vec4 v3); +vec4 SpdReduceQuad(vec4 v) +{ + #if !defined(FFX_SPD_NO_WAVE_OPERATIONS) + vec4 v0 = v; + vec4 v1 = subgroupQuadSwapHorizontal(v); + vec4 v2 = subgroupQuadSwapVertical(v); + vec4 v3 = subgroupQuadSwapDiagonal(v); + return SpdReduce4(v0, v1, v2, v3); + #endif + return v; +} + +vec4 SpdReduceIntermediate(uvec2 i0, uvec2 i1, uvec2 i2, uvec2 i3) +{ + vec4 v0 = SpdLoadIntermediate(i0.x, i0.y); + vec4 v1 = SpdLoadIntermediate(i1.x, i1.y); + vec4 v2 = SpdLoadIntermediate(i2.x, i2.y); + vec4 v3 = SpdLoadIntermediate(i3.x, i3.y); + return SpdReduce4(v0, v1, v2, v3); +} + +vec4 SpdReduceLoad4(uvec2 i0, uvec2 i1, uvec2 i2, uvec2 i3) +{ + vec4 v0 = SpdLoad(ivec2(i0)); + vec4 v1 = SpdLoad(ivec2(i1)); + vec4 v2 = SpdLoad(ivec2(i2)); + vec4 v3 = SpdLoad(ivec2(i3)); + return SpdReduce4(v0, v1, v2, v3); +} + +vec4 SpdReduceLoad4(uvec2 base) +{ + return SpdReduceLoad4(base + uvec2(0, 0), base + uvec2(0, 1), base + uvec2(1, 0), base + uvec2(1, 1)); +} + +vec4 SpdReduceLoadSourceImage4(uvec2 i0, uvec2 i1, uvec2 i2, uvec2 i3) +{ + vec4 v0 = SpdLoadSourceImage(ivec2(i0)); + vec4 v1 = SpdLoadSourceImage(ivec2(i1)); + vec4 v2 = SpdLoadSourceImage(ivec2(i2)); + vec4 v3 = SpdLoadSourceImage(ivec2(i3)); + return SpdReduce4(v0, v1, v2, v3); +} + +vec4 SpdReduceLoadSourceImage(uvec2 base) +{ + #if defined(SPD_LINEAR_SAMPLER) + return SpdLoadSourceImage(ivec2(base)); + #else + return SpdReduceLoadSourceImage4(base + uvec2(0, 0), base + uvec2(0, 1), base + uvec2(1, 0), base + uvec2(1, 1)); + #endif +} + +void SpdDownsampleMips_0_1_Intrinsics(uint x, uint y, uvec2 workGroupID, uint localInvocationIndex, uint mip) +{ + vec4 v[4]; + + ivec2 tex = ivec2(workGroupID.xy * 64) + ivec2(x * 2, y * 2); + ivec2 pix = ivec2(workGroupID.xy * 32) + ivec2(x, y); + v[0] = SpdReduceLoadSourceImage(tex); + SpdStore(pix, v[0], 0); + + tex = ivec2(workGroupID.xy * 64) + ivec2(x * 2 + 32, y * 2); + pix = ivec2(workGroupID.xy * 32) + ivec2(x + 16, y); + v[1] = SpdReduceLoadSourceImage(tex); + SpdStore(pix, v[1], 0); + + tex = ivec2(workGroupID.xy * 64) + ivec2(x * 2, y * 2 + 32); + pix = ivec2(workGroupID.xy * 32) + ivec2(x, y + 16); + v[2] = SpdReduceLoadSourceImage(tex); + SpdStore(pix, v[2], 0); + + tex = ivec2(workGroupID.xy * 64) + ivec2(x * 2 + 32, y * 2 + 32); + pix = ivec2(workGroupID.xy * 32) + ivec2(x + 16, y + 16); + v[3] = SpdReduceLoadSourceImage(tex); + SpdStore(pix, v[3], 0); + + if (mip <= 1) + return; + + v[0] = SpdReduceQuad(v[0]); + v[1] = SpdReduceQuad(v[1]); + v[2] = SpdReduceQuad(v[2]); + v[3] = SpdReduceQuad(v[3]); + + if ((localInvocationIndex % 4) == 0) + { + SpdStore(ivec2(workGroupID.xy * 16) + ivec2(x / 2, y / 2), v[0], 1); + SpdStoreIntermediate(x / 2, y / 2, v[0]); + + SpdStore(ivec2(workGroupID.xy * 16) + ivec2(x / 2 + 8, y / 2), v[1], 1); + SpdStoreIntermediate(x / 2 + 8, y / 2, v[1]); + + SpdStore(ivec2(workGroupID.xy * 16) + ivec2(x / 2, y / 2 + 8), v[2], 1); + SpdStoreIntermediate(x / 2, y / 2 + 8, v[2]); + + SpdStore(ivec2(workGroupID.xy * 16) + ivec2(x / 2 + 8, y / 2 + 8), v[3], 1); + SpdStoreIntermediate(x / 2 + 8, y / 2 + 8, v[3]); + } +} + +void SpdDownsampleMips_0_1_LDS(uint x, uint y, uvec2 workGroupID, uint localInvocationIndex, uint mip) +{ + vec4 v[4]; + + ivec2 tex = ivec2(workGroupID.xy * 64) + ivec2(x * 2, y * 2); + ivec2 pix = ivec2(workGroupID.xy * 32) + ivec2(x, y); + v[0] = SpdReduceLoadSourceImage(tex); + SpdStore(pix, v[0], 0); + + tex = ivec2(workGroupID.xy * 64) + ivec2(x * 2 + 32, y * 2); + pix = ivec2(workGroupID.xy * 32) + ivec2(x + 16, y); + v[1] = SpdReduceLoadSourceImage(tex); + SpdStore(pix, v[1], 0); + + tex = ivec2(workGroupID.xy * 64) + ivec2(x * 2, y * 2 + 32); + pix = ivec2(workGroupID.xy * 32) + ivec2(x, y + 16); + v[2] = SpdReduceLoadSourceImage(tex); + SpdStore(pix, v[2], 0); + + tex = ivec2(workGroupID.xy * 64) + ivec2(x * 2 + 32, y * 2 + 32); + pix = ivec2(workGroupID.xy * 32) + ivec2(x + 16, y + 16); + v[3] = SpdReduceLoadSourceImage(tex); + SpdStore(pix, v[3], 0); + + if (mip <= 1) + return; + + for (uint i = 0; i < 4; i++) + { + SpdStoreIntermediate(x, y, v[i]); + ffxSpdWorkgroupShuffleBarrier(); + if (localInvocationIndex < 64) + { + v[i] = SpdReduceIntermediate(uvec2(x * 2 + 0, y * 2 + 0), uvec2(x * 2 + 1, y * 2 + 0), uvec2(x * 2 + 0, y * 2 + 1), uvec2(x * 2 + 1, y * 2 + 1)); + SpdStore(ivec2(workGroupID.xy * 16) + ivec2(x + (i % 2) * 8, y + (i / 2) * 8), v[i], 1); + } + ffxSpdWorkgroupShuffleBarrier(); + } + + if (localInvocationIndex < 64) + { + SpdStoreIntermediate(x + 0, y + 0, v[0]); + SpdStoreIntermediate(x + 8, y + 0, v[1]); + SpdStoreIntermediate(x + 0, y + 8, v[2]); + SpdStoreIntermediate(x + 8, y + 8, v[3]); + } +} + +void SpdDownsampleMips_0_1(uint x, uint y, uvec2 workGroupID, uint localInvocationIndex, uint mip) +{ + #if defined(FFX_SPD_NO_WAVE_OPERATIONS) + SpdDownsampleMips_0_1_LDS(x, y, workGroupID, localInvocationIndex, mip); + #else + SpdDownsampleMips_0_1_Intrinsics(x, y, workGroupID, localInvocationIndex, mip); + #endif +} + +void SpdDownsampleMip_2(uint x, uint y, uvec2 workGroupID, uint localInvocationIndex, uint mip) +{ + #if defined(FFX_SPD_NO_WAVE_OPERATIONS) + if (localInvocationIndex < 64) + { + vec4 v = SpdReduceIntermediate(uvec2(x * 2 + 0, y * 2 + 0), uvec2(x * 2 + 1, y * 2 + 0), uvec2(x * 2 + 0, y * 2 + 1), uvec2(x * 2 + 1, y * 2 + 1)); + SpdStore(ivec2(workGroupID.xy * 8) + ivec2(x, y), v, mip); + // store to LDS, try to reduce bank conflicts + // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 x + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 + // ... + // x 0 x 0 x 0 x 0 x 0 x 0 x 0 x 0 + SpdStoreIntermediate(x * 2 + y % 2, y * 2, v); + } + #else + vec4 v = SpdLoadIntermediate(x, y); + v = SpdReduceQuad(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStore(ivec2(workGroupID.xy * 8) + ivec2(x / 2, y / 2), v, mip); + SpdStoreIntermediate(x + (y / 2) % 2, y, v); + } + #endif +} + +void SpdDownsampleMip_3(uint x, uint y, uvec2 workGroupID, uint localInvocationIndex, uint mip) +{ + #if defined(FFX_SPD_NO_WAVE_OPERATIONS) + if (localInvocationIndex < 16) + { + // x 0 x 0 + // 0 0 0 0 + // 0 x 0 x + // 0 0 0 0 + vec4 v = SpdReduceIntermediate(uvec2(x * 4 + 0 + 0, y * 4 + 0), uvec2(x * 4 + 2 + 0, y * 4 + 0), uvec2(x * 4 + 0 + 1, y * 4 + 2), uvec2(x * 4 + 2 + 1, y * 4 + 2)); + SpdStore(ivec2(workGroupID.xy * 4) + ivec2(x, y), v, mip); + // store to LDS + // x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 0 + // ... + // 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x 0 + // ... + // 0 0 0 x 0 0 0 x 0 0 0 x 0 0 0 x + // ... + SpdStoreIntermediate(x * 4 + y, y * 4, v); + } + #else + if (localInvocationIndex < 64) + { + vec4 v = SpdLoadIntermediate(x * 2 + y % 2, y * 2); + v = SpdReduceQuad(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStore(ivec2(workGroupID.xy * 4) + ivec2(x / 2, y / 2), v, mip); + SpdStoreIntermediate(x * 2 + y / 2, y * 2, v); + } + } + #endif +} + +void SpdDownsampleMip_4(uint x, uint y, uvec2 workGroupID, uint localInvocationIndex, uint mip) +{ + #if defined(FFX_SPD_NO_WAVE_OPERATIONS) + if (localInvocationIndex < 4) + { + // x 0 0 0 x 0 0 0 + // ... + // 0 x 0 0 0 x 0 0 + vec4 v = SpdReduceIntermediate(uvec2(x * 8 + 0 + 0 + y * 2, y * 8 + 0), + uvec2(x * 8 + 4 + 0 + y * 2, y * 8 + 0), + uvec2(x * 8 + 0 + 1 + y * 2, y * 8 + 4), + uvec2(x * 8 + 4 + 1 + y * 2, y * 8 + 4)); + SpdStore(ivec2(workGroupID.xy * 2) + ivec2(x, y), v, mip); + // store to LDS + // x x x x 0 ... + // 0 ... + SpdStoreIntermediate(x + y * 2, 0, v); + } + #else + if (localInvocationIndex < 16) + { + vec4 v = SpdLoadIntermediate(x * 4 + y, y * 4); + v = SpdReduceQuad(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStore(ivec2(workGroupID.xy * 2) + ivec2(x / 2, y / 2), v, mip); + SpdStoreIntermediate(x / 2 + y, 0, v); + } + } + #endif +} + +void SpdDownsampleMip_5(uvec2 workGroupID, uint localInvocationIndex, uint mip) +{ + #if defined(FFX_SPD_NO_WAVE_OPERATIONS) + if (localInvocationIndex < 1) + { + // x x x x 0 ... + // 0 ... + vec4 v = SpdReduceIntermediate(uvec2(0, 0), uvec2(1, 0), uvec2(2, 0), uvec2(3, 0)); + SpdStore(ivec2(workGroupID.xy), v, mip); + } + #else + if (localInvocationIndex < 4) + { + vec4 v = SpdLoadIntermediate(localInvocationIndex, 0); + v = SpdReduceQuad(v); + // quad index 0 stores result + if (localInvocationIndex % 4 == 0) + { + SpdStore(ivec2(workGroupID.xy), v, mip); + } + } + #endif +} + +void SpdDownsampleMips_6_7(uint x, uint y, uint mips) +{ + ivec2 tex = ivec2(x * 4 + 0, y * 4 + 0); + ivec2 pix = ivec2(x * 2 + 0, y * 2 + 0); + vec4 v0 = SpdReduceLoad4(tex); + SpdStore(pix, v0, 6); + + tex = ivec2(x * 4 + 2, y * 4 + 0); + pix = ivec2(x * 2 + 1, y * 2 + 0); + vec4 v1 = SpdReduceLoad4(tex); + SpdStore(pix, v1, 6); + + tex = ivec2(x * 4 + 0, y * 4 + 2); + pix = ivec2(x * 2 + 0, y * 2 + 1); + vec4 v2 = SpdReduceLoad4(tex); + SpdStore(pix, v2, 6); + + tex = ivec2(x * 4 + 2, y * 4 + 2); + pix = ivec2(x * 2 + 1, y * 2 + 1); + vec4 v3 = SpdReduceLoad4(tex); + SpdStore(pix, v3, 6); + + if (mips <= 7) + return; + // no barrier needed, working on values only from the same thread + + vec4 v = SpdReduce4(v0, v1, v2, v3); + SpdStore(ivec2(x, y), v, 7); + SpdStoreIntermediate(x, y, v); +} + +void SpdDownsampleNextFour(uint x, uint y, uvec2 workGroupID, uint localInvocationIndex, uint baseMip, uint mips) +{ + if (mips <= baseMip) + return; + ffxSpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_2(x, y, workGroupID, localInvocationIndex, baseMip); + + if (mips <= baseMip + 1) + return; + ffxSpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_3(x, y, workGroupID, localInvocationIndex, baseMip + 1); + + if (mips <= baseMip + 2) + return; + ffxSpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_4(x, y, workGroupID, localInvocationIndex, baseMip + 2); + + if (mips <= baseMip + 3) + return; + ffxSpdWorkgroupShuffleBarrier(); + SpdDownsampleMip_5(workGroupID, localInvocationIndex, baseMip + 3); +} + +/// Downsamples a 64x64 tile based on the work group id. +/// If after downsampling it's the last active thread group, computes the remaining MIP levels. +/// +/// @param [in] workGroupID index of the work group / thread group +/// @param [in] localInvocationIndex index of the thread within the thread group in 1D +/// @param [in] mips the number of total MIP levels to compute for the input texture +/// @param [in] numWorkGroups the total number of dispatched work groups / thread groups for this slice +/// @param [in] slice the slice of the input texture +/// +/// @ingroup FfxGPUSpd +void SpdDownsample(uvec2 workGroupID, uint localInvocationIndex, uint mips, uint numWorkGroups) +{ + // compute MIP level 0 and 1 + uvec2 sub_xy = ffxRemapForWaveReduction(localInvocationIndex % 64); + uint x = sub_xy.x + 8 * ((localInvocationIndex >> 6) % 2); + uint y = sub_xy.y + 8 * (localInvocationIndex >> 7); + SpdDownsampleMips_0_1(x, y, workGroupID, localInvocationIndex, mips); + + // compute MIP level 2, 3, 4, 5 + SpdDownsampleNextFour(x, y, workGroupID, localInvocationIndex, 2, mips); + + if (mips <= 6) + return; + + // increase the global atomic counter for the given slice and check if it's the last remaining thread group: + // terminate if not, continue if yes. + if (SpdExitWorkgroup(numWorkGroups, localInvocationIndex)) + return; + + // reset the global atomic counter back to 0 for the next spd dispatch + SpdResetAtomicCounter(0); + + // After mip 5 there is only a single workgroup left that downsamples the remaining up to 64x64 texels. + // compute MIP level 6 and 7 + SpdDownsampleMips_6_7(x, y, mips); + + // compute MIP level 8, 9, 10, 11 + SpdDownsampleNextFour(x, y, uvec2(0, 0), localInvocationIndex, 8, mips); +} + +/// Downsamples a 64x64 tile based on the work group id and work group offset. +/// If after downsampling it's the last active thread group, computes the remaining MIP levels. +/// +/// @param [in] workGroupID index of the work group / thread group +/// @param [in] localInvocationIndex index of the thread within the thread group in 1D +/// @param [in] mips the number of total MIP levels to compute for the input texture +/// @param [in] numWorkGroups the total number of dispatched work groups / thread groups for this slice +/// @param [in] slice the slice of the input texture +/// @param [in] workGroupOffset the work group offset. it's (0,0) in case the entire input texture is downsampled. +/// +/// @ingroup FfxGPUSpd +void SpdDownsample(uvec2 workGroupID, uint localInvocationIndex, uint mips, uint numWorkGroups, uvec2 workGroupOffset) +{ + SpdDownsample(workGroupID + workGroupOffset, localInvocationIndex, mips, numWorkGroups); +} + +void DOWNSAMPLE(uint localThreadId, uvec3 workGroupId) +{ + SpdDownsample(workGroupId.xy, localThreadId, Mips(), NumWorkGroups(), WorkGroupOffset()); +} + +layout (local_size_x = 256, local_size_y = 1, local_size_z = 1) in; +void main() +{ + DOWNSAMPLE(gl_LocalInvocationIndex, gl_WorkGroupID.xyz); +} diff --git a/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/core/occlusion_query.fsh b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/core/occlusion_query.fsh new file mode 100644 index 00000000..de9b92f1 --- /dev/null +++ b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/core/occlusion_query.fsh @@ -0,0 +1,7 @@ +#version 330 + +out vec4 fragColor; + +void main() { + fragColor = vec4(1.0, 1.0, 1.0, 1.0); +} diff --git a/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/core/occlusion_query.vsh b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/core/occlusion_query.vsh new file mode 100644 index 00000000..ce45a8af --- /dev/null +++ b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/core/occlusion_query.vsh @@ -0,0 +1,12 @@ +#version 330 + +layout(std140) uniform Transforms { + mat4 ProjMat; + mat4 ModelViewMat; +}; + +in vec3 Position; + +void main() { + gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0); +} diff --git a/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/include/tonemappers.glsl b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/include/tonemappers.glsl new file mode 100644 index 00000000..d7869d50 --- /dev/null +++ b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/include/tonemappers.glsl @@ -0,0 +1,159 @@ +// This file is part of the FidelityFX SDK. +// +// Copyright (C) 2024 Advanced Micro Devices, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +//-------------------------------------------------------------------------------------- +// Timothy Lottes tone mapper +//-------------------------------------------------------------------------------------- +// General tonemapping operator, build 'b' term. +float ColToneB(float hdrMax, float contrast, float shoulder, float midIn, float midOut) +{ + return + -((-pow(midIn, contrast) + (midOut * (pow(hdrMax, contrast * shoulder) * pow(midIn, contrast) - + pow(hdrMax, contrast) * pow(midIn, contrast * shoulder) * midOut)) / + (pow(hdrMax, contrast * shoulder) * midOut - pow(midIn, contrast * shoulder) * midOut)) / + (pow(midIn, contrast * shoulder) * midOut)); +} + +// General tonemapping operator, build 'c' term. +float ColToneC(float hdrMax, float contrast, float shoulder, float midIn, float midOut) +{ + return (pow(hdrMax, contrast * shoulder) * pow(midIn, contrast) - pow(hdrMax, contrast) * pow(midIn, contrast * shoulder) * midOut) / + (pow(hdrMax, contrast * shoulder) * midOut - pow(midIn, contrast * shoulder) * midOut); +} + +// General tonemapping operator, p := {contrast, shoulder, b, c}. +float ColTone(float x, vec4 p) +{ + float z = pow(x, p.r); + return z / (pow(z, p.g) * p.b + p.a); +} + +vec3 TimothyTonemapper(vec3 color) +{ + const float hdrMax = 16.0; // How much HDR range before clipping. HDR modes likely need this pushed up to say 25.0. + const float contrast = 2.0; // Use as a baseline to tune the amount of contrast the tonemapper has. + const float shoulder = 1.0; // Likely don't need to mess with this factor, unless matching existing tonemapper is not working well. + const float midIn = 0.18; // Most games will have a {0.0 to 1.0} range for LDR so midIn should be 0.18. + const float midOut = 0.18; // Use for LDR. For HDR10 10:10:10:2 use maybe 0.18 / 25.0 to start. For scRGB, this needs recalculating. + + float b = ColToneB(hdrMax, contrast, shoulder, midIn, midOut); + float c = ColToneC(hdrMax, contrast, shoulder, midIn, midOut); + + const float eps = 1e-6; + float peak = max(color.r, max(color.g, color.b)); + peak = max(eps, peak); + + vec3 ratio = color / peak; + peak = ColTone(peak, vec4(contrast, shoulder, b, c)); + + // Probably want these pre-computed and passed over as constants. + const float crosstalk = 4.0; // Controls amount of channel crosstalk. + const float saturation = contrast; // Full tonal range saturation control. + const float crossSaturation = contrast * 16.0; // Crosstalk saturation. + const float white = 1.0; + + // Wrap crosstalk in transform. + ratio = pow(abs(ratio), vec3(saturation / crossSaturation)); + ratio = mix(ratio, vec3(white), vec3(pow(peak, crosstalk))); + ratio = pow(abs(ratio), vec3(crossSaturation)); + + // Then apply ratio to peak. + color = peak * ratio; + return color; +} + +//-------------------------------------------------------------------------------------- +// The tone mapper used in HDRToneMappingCS11 +//-------------------------------------------------------------------------------------- +vec3 DX11DSK(vec3 color) +{ + const float middleGray = 0.72; + const float lumWhite = 1.5; + + // Tone mapping. + color *= middleGray; + color *= (1.0 + color / lumWhite); + color /= (1.0 + color); + + return color; +} + +//-------------------------------------------------------------------------------------- +// Reinhard +//-------------------------------------------------------------------------------------- +vec3 Reinhard(vec3 color) +{ + return color / (1.0 + color); +} + +//-------------------------------------------------------------------------------------- +// Hable's filmic +//-------------------------------------------------------------------------------------- +vec3 Uncharted2TonemapOp(vec3 x) +{ + const float A = 0.15; + const float B = 0.50; + const float C = 0.10; + const float D = 0.20; + const float E = 0.02; + const float F = 0.30; + + return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F; +} + +vec3 Uncharted2Tonemap(vec3 color) +{ + const float W = 11.2; + return Uncharted2TonemapOp(2.0 * color) / Uncharted2TonemapOp(vec3(W)); +} + +//-------------------------------------------------------------------------------------- +// https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/ +//-------------------------------------------------------------------------------------- +vec3 ACESFilm(vec3 x) +{ + const float a = 2.51; + const float b = 0.03; + const float c = 2.43; + const float d = 0.59; + const float e = 0.14; + return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0); +} + +//-------------------------------------------------------------------------------------- +// Default switch +//-------------------------------------------------------------------------------------- +vec3 Tonemap(vec3 color, float exposure, int tonemapper) +{ + color *= exposure; + + switch (tonemapper) + { + case 0: return TimothyTonemapper(color); + case 1: return DX11DSK(color); + case 2: return Reinhard(color); + case 3: return Uncharted2Tonemap(color); + case 4: return ACESFilm(color); + case 5: return color; + default: return vec3(1.0); + } +} diff --git a/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/LazySyncBytecodeInjector.java b/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/LazySyncBytecodeInjector.java index c0f5fb7f..1b02cbe6 100644 --- a/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/LazySyncBytecodeInjector.java +++ b/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/LazySyncBytecodeInjector.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.sync.transform; +import org.jetbrains.annotations.ApiStatus; + import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.ApiStatus; import org.objectweb.asm.Opcodes; diff --git a/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/LazySyncTargetIndex.java b/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/LazySyncTargetIndex.java index 66eb7136..598fcd23 100644 --- a/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/LazySyncTargetIndex.java +++ b/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/LazySyncTargetIndex.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.sync.transform; +import org.jetbrains.annotations.ApiStatus; + import lombok.extern.slf4j.Slf4j; import net.neoforged.fml.loading.FMLLoader; import net.neoforged.fml.loading.moddiscovery.ModFileInfo; diff --git a/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncBytecodeInjector.java b/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncBytecodeInjector.java index dd223e1b..051ab618 100644 --- a/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncBytecodeInjector.java +++ b/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncBytecodeInjector.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.sync.transform; +import org.jetbrains.annotations.ApiStatus; + import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.ApiStatus; import org.objectweb.asm.Opcodes; diff --git a/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncClassProcessor.java b/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncClassProcessor.java index 0076127a..8e90c728 100644 --- a/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncClassProcessor.java +++ b/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncClassProcessor.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.sync.transform; +import org.jetbrains.annotations.ApiStatus; + import lombok.extern.slf4j.Slf4j; import net.neoforged.neoforgespi.transformation.ClassProcessor; import net.neoforged.neoforgespi.transformation.ProcessorName; diff --git a/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncTargetIndex.java b/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncTargetIndex.java index 5587e836..43cbaaea 100644 --- a/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncTargetIndex.java +++ b/module.sync/processor/src/main/java/dev/anvilcraft/lib/v2/sync/transform/SyncTargetIndex.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.sync.transform; +import org.jetbrains.annotations.ApiStatus; + import lombok.extern.slf4j.Slf4j; import net.neoforged.fml.loading.FMLLoader; import net.neoforged.fml.loading.moddiscovery.ModFileInfo; diff --git a/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/LazySyncPayload.java b/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/LazySyncPayload.java index 2bf7c0d5..c1622568 100644 --- a/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/LazySyncPayload.java +++ b/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/LazySyncPayload.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.sync.network.payload; +import org.jetbrains.annotations.ApiStatus; + import dev.anvilcraft.lib.v2.network.packet.IInsensitiveBiPacket; import dev.anvilcraft.lib.v2.network.packet.IPacket; import dev.anvilcraft.lib.v2.sync.AnvilLibSync; diff --git a/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/SyncConfigurationPayload.java b/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/SyncConfigurationPayload.java index 4ac65ad4..6a3894c2 100644 --- a/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/SyncConfigurationPayload.java +++ b/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/SyncConfigurationPayload.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.sync.network.payload; +import org.jetbrains.annotations.ApiStatus; + import dev.anvilcraft.lib.v2.network.packet.IClientboundPacket; import dev.anvilcraft.lib.v2.network.packet.IPacket; import dev.anvilcraft.lib.v2.sync.AnvilLibSync; diff --git a/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/SyncPayload.java b/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/SyncPayload.java index 28af73ab..df46274b 100644 --- a/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/SyncPayload.java +++ b/module.sync/src/main/java/dev/anvilcraft/lib/v2/sync/network/payload/SyncPayload.java @@ -1,5 +1,7 @@ package dev.anvilcraft.lib.v2.sync.network.payload; +import org.jetbrains.annotations.ApiStatus; + import dev.anvilcraft.lib.v2.network.packet.IInsensitiveBiPacket; import dev.anvilcraft.lib.v2.network.packet.IPacket; import dev.anvilcraft.lib.v2.sync.AnvilLibSync; diff --git a/module.test/src/main/java/dev/anvilcraft/lib/v2/test/client/compute/ComputeSupport.java b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/client/compute/ComputeSupport.java index 47a92e29..1a8b6614 100644 --- a/module.test/src/main/java/dev/anvilcraft/lib/v2/test/client/compute/ComputeSupport.java +++ b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/client/compute/ComputeSupport.java @@ -31,7 +31,7 @@ import java.util.OptionalDouble; public class ComputeSupport { - public static final ComputeSupport INSTANCE = new ComputeSupport(); + public static final ComputeSupport INSTANCE = ALRComputeCapabilities.isComputeSupported() ? new ComputeSupport() : null; public static final float[] UNSUPPORTED = {}; @Getter private final GpuDevice device = RenderSystem.getDevice(); @@ -144,7 +144,7 @@ public float[] add(float[] input, float f) { ByteBuffer counterData = mappedCounterBuffer.data(); int anInt = counterData.getInt(); - if (anInt != input.length){ + if (anInt != input.length) { System.out.printf("Compute counter does not match with input size: %d/%d%n", anInt, input.length); } return result;