diff --git a/build.gradle b/build.gradle index 89a8ad90..a8f49a72 100644 --- a/build.gradle +++ b/build.gradle @@ -16,57 +16,59 @@ repositories { dependencies {} -tasks.register("createModule") { - if (!project.hasProperty('id')) { - throw new GradleException("Id property is required. Use -Pid= to specify the version.") - } - String id = project.property("id") - if (id.matches("[A-Z]+")) { - throw new GradleException("Id cannot be all uppercase.") - } - String underlinedId = id.replace('-', '_') - String mod_id = "anvillib_$underlinedId" - String bigHumpName = id.split('-').collect { it.substring(0, 1).toUpperCase() + it.substring(1) }.join() - String mod_name = "AnvilLib-$bigHumpName" - String class_name = "AnvilLib$bigHumpName" - File buildGradle = file("module.$id/build.gradle") - File gradleProperties = file("module.$id/gradle.properties") - File mainClass = file("module.$id/src/main/java/dev/anvilcraft/lib/v2/$underlinedId/${class_name}.java") - File packageInfo = file("module.$id/src/main/java/dev/anvilcraft/lib/v2/$underlinedId/package-info.java") - File modInfoPath = file("module.$id/src/main/resources/META-INF/") - File mixinConfig = file("module.$id/src/main/resources/${mod_id}.mixins.json") - buildGradle.parentFile.mkdirs() - mainClass.parentFile.mkdirs() - modInfoPath.mkdirs() - buildGradle.createNewFile() - gradleProperties.createNewFile() - mainClass.createNewFile() - mixinConfig.createNewFile() - copy { - from file("module.main/src/main/resources/META-INF/neoforge.mods.toml") - into modInfoPath - } - copy { - from file("module.main/src/main/resources/icon.png") - into file("module.$id/src/main/resources/") - } +class CreateModuleTask extends DefaultTask { + @TaskAction + void onCreateModule() { + if (!project.hasProperty('id')) { + throw new GradleException("Id property is required. Use -Pid= to specify the version.") + } + String id = project.property("id") + if (id.matches("[A-Z]+")) { + throw new GradleException("Id cannot be all uppercase.") + } + String underlinedId = id.replace('-', '_') + String mod_id = "anvillib_$underlinedId" + String bigHumpName = id.split('-').collect { it.substring(0, 1).toUpperCase() + it.substring(1) }.join() + String mod_name = "AnvilLib-$bigHumpName" + String class_name = "AnvilLib$bigHumpName" + File buildGradle = file("module.$id/build.gradle") + File gradleProperties = file("module.$id/gradle.properties") + File mainClass = file("module.$id/src/main/java/dev/anvilcraft/lib/v2/$underlinedId/${class_name}.java") + File packageInfo = file("module.$id/src/main/java/dev/anvilcraft/lib/v2/$underlinedId/package-info.java") + File modInfoPath = file("module.$id/src/main/resources/META-INF/") + File mixinConfig = file("module.$id/src/main/resources/${mod_id}.mixins.json") + buildGradle.parentFile.mkdirs() + mainClass.parentFile.mkdirs() + modInfoPath.mkdirs() + buildGradle.createNewFile() + gradleProperties.createNewFile() + mainClass.createNewFile() + mixinConfig.createNewFile() + copy { + from file("module.main/src/main/resources/META-INF/neoforge.mods.toml") + into modInfoPath + } + copy { + from file("module.main/src/main/resources/icon.png") + into file("module.$id/src/main/resources/") + } - buildGradle.write( - """ + buildGradle.write( + """ dependencies { } """.stripIndent().trim() - ) - gradleProperties.write( - """ + ) + gradleProperties.write( + """ ## Mod Properties mod_id=$mod_id mod_name=$mod_name mod_description= """.stripIndent().trim() - ) - mainClass.write( - """ + ) + mainClass.write( + """ package dev.anvilcraft.lib.v2.$underlinedId; import net.minecraft.resources.Identifier; @@ -82,17 +84,17 @@ tasks.register("createModule") { } } """.stripIndent().trim() - ) - packageInfo.write( - """ + ) + packageInfo.write( + """ @NullMarked package dev.anvilcraft.lib.v2.$underlinedId; import org.jspecify.annotations.NullMarked; """.stripIndent().trim() - ) - mixinConfig.write( - """ + ) + mixinConfig.write( + """ { "required": true, "minVersion": "0.8", @@ -108,9 +110,12 @@ tasks.register("createModule") { } } """.stripIndent().trim() - ) + ) + } } +tasks.register("createModule", CreateModuleTask) + subprojects { if (it.name == "renderdoc-loader-internal") return if (it.name == "anvillib-sync-processor-neoforge-26.1") return diff --git a/module.gradle b/module.gradle index 50d86ccc..0b0feb50 100644 --- a/module.gradle +++ b/module.gradle @@ -52,6 +52,10 @@ neoForge { client() systemProperty('neoforge.enabledGameTestNamespaces', project.mod_id) systemProperty("neoforge.disableGlValidation", "true") + jvmArgument("-XX:+UseParallelGC") + jvmArgument("-XX:+UnlockDiagnosticVMOptions") + jvmArgument("-XX:+DebugNonSafepoints") + programArgument("--renderDebugLabels") } } 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..deada1d6 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALRComputePipelines.java @@ -0,0 +1,59 @@ +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("SPD_MAX_MIP_LEVELS", 5) // minium required value in spec of GL_MAX_IMAGE_UNITS is 8, spd use one for input tex, one for mid tex + .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") + .withShaderStorage("rw_internal_global_atomic") + .withReadWriteImage("rw_input_downsample_src_mid_mip") + .withArrayOfImage("rw_input_downsample_src_mips", true, true, 13) + .build(); + + public static final ALRComputePipeline FFX_SPD_DOWNSAMPLE_PASS_BINDLESS = ALRComputePipeline.builder() + .withName(AnvilLibRendering.location("ffx_spd_downsample_pass")) + .withShader(AnvilLibRendering.location("compute/ffx_spd_downsample_pass_bindless.csh")) + .withDefines( + ShaderDefines.builder() +// .define("SPD_MAX_MIP_LEVELS", 4) // minium required value in spec of GL_MAX_IMAGE_UNITS is 8, spd use one for input tex + .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") + .withShaderStorage("rw_internal_global_atomic") + .withReadWriteImage("rw_input_downsample_src_mid_mip") + .withBindlessArrayOfImage("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/ALRDebugEntries.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALRDebugEntries.java new file mode 100644 index 00000000..ec65232e --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALRDebugEntries.java @@ -0,0 +1,16 @@ +package dev.anvilcraft.lib.v2.rendering; + +import dev.anvilcraft.lib.v2.rendering.debug.OcclusionCullingDebugEntry; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.RegisterDebugEntriesEvent; + +@EventBusSubscriber +public class ALRDebugEntries { + + + @SubscribeEvent + public static void on(RegisterDebugEntriesEvent event) { + event.register(OcclusionCullingDebugEntry.LOCATION, new OcclusionCullingDebugEntry()); + } +} 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..e9a9dc94 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALROptimizations.java @@ -0,0 +1,20 @@ +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; + + 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..dc1b8b2b --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/ALROptions.java @@ -0,0 +1,27 @@ +package dev.anvilcraft.lib.v2.rendering; + +import org.jspecify.annotations.Nullable; + +@SuppressWarnings("SameParameterValue") +public class ALROptions { + public static final boolean SPD_OPTION_WAVE_INTEROP_LDS = getPropertyBoolean("alrSpdOptionUseWaveInteropLds"); + public static final boolean OCCLUSION_QUERY_USE_FRUSTUM_PRE_PASS = getPropertyBoolean("alrOcclusionQueryUseFrustumPrePass", false); + public static final String OCCLUSION_CULLING_FORCE_IMPL = getProperty("alrOcclusionCullingForceImplementation", null); + + private static String getProperty(String key, @Nullable String defaultValue) { + return System.getProperty(key, defaultValue); + } + + private static boolean getPropertyBoolean(String key, boolean defaultValue) { + String prop = System.getProperty(key); + if (prop == null) { + return defaultValue; + } + return !"false".equals(prop); + } + + 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..f7937c77 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(false) + .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/AnvilLibRendering.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/AnvilLibRendering.java index e1433937..59fd1515 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/AnvilLibRendering.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/AnvilLibRendering.java @@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import net.minecraft.resources.Identifier; import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.EventPriority; import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; @@ -17,6 +18,7 @@ import net.neoforged.neoforge.client.event.RegisterPictureInPictureRenderersEvent; import net.neoforged.neoforge.client.event.RenderFrameEvent; import net.neoforged.neoforge.client.event.RenderLevelStageEvent; +import net.neoforged.neoforge.client.event.SubmitCustomGeometryEvent; import org.jetbrains.annotations.ApiStatus; @Slf4j @@ -40,6 +42,12 @@ public static void on(RenderFrameEvent.Pre event) { CachedBlockEntityRenderingPipeline.getInstance().runTasks(); } ALRPostEffects.getBloomPostEffect().beginFrame(); + ALROptimizations.getOcclusionCuller().beginRenderingFrame(); + } + + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void on(SubmitCustomGeometryEvent event) { + ALROptimizations.getOcclusionCuller().processFeatures(event.getLevelRenderState().cameraRenderState); } @SubscribeEvent 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/debug/OcclusionCullingDebugEntry.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/debug/OcclusionCullingDebugEntry.java new file mode 100644 index 00000000..faa7250e --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/debug/OcclusionCullingDebugEntry.java @@ -0,0 +1,69 @@ +package dev.anvilcraft.lib.v2.rendering.debug; + +import dev.anvilcraft.lib.v2.rendering.ALROptimizations; +import dev.anvilcraft.lib.v2.rendering.AnvilLibRendering; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.CullingStatistics; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionCuller; +import net.minecraft.client.gui.components.debug.DebugEntryCategory; +import net.minecraft.client.gui.components.debug.DebugScreenDisplayer; +import net.minecraft.client.gui.components.debug.DebugScreenEntry; +import net.minecraft.resources.Identifier; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.chunk.LevelChunk; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +public class OcclusionCullingDebugEntry implements DebugScreenEntry { + public static final Identifier LOCATION = AnvilLibRendering.location("occlusion_culling"); + + @Override + public void display( + @NonNull DebugScreenDisplayer displayer, + @Nullable Level serverOrClientLevel, + @Nullable LevelChunk clientChunk, + @Nullable LevelChunk serverChunk + ) { + OcclusionCuller occlusionCuller = ALROptimizations.getOcclusionCuller(); + CullingStatistics statistics = occlusionCuller.collectStatistics(); + + if (statistics == null) { + displayer.addToGroup( + LOCATION, + List.of( + "[AnvilLib] OcclusionCulling " + occlusionCuller.getClass().getSimpleName(), + "Debug info unavailable." + ) + ); + return; + } + + String e1 = "[AnvilLib] OcclusionCulling " + occlusionCuller.getClass().getSimpleName(); + if (statistics.message() != null) { + e1 += ": " + statistics.message(); + } + displayer.addToGroup( + LOCATION, + List.of( + e1, + "Total: " + statistics.total(), + "FrustumPrePass: " + statistics.frustumPrePass(), + "CameraInside: " + statistics.cameraInside(), + "Culled: " + statistics.culled(), + "Rendered: " + statistics.rendered(), + "Features: " + statistics.features() + ) + ); + } + + @Override + public boolean isAllowed(boolean reducedDebugInfo) { + return true; + } + + @Override + public DebugEntryCategory category() { + return DebugEntryCategory.SCREEN_TEXT; + } +} 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..0ead9e08 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,17 +1,42 @@ 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 dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.ALRComputePipeline; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.bindless.BindlessTexturingSupport; +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 { - ALRComputeProgramInstance alrCompileComputeShader(ALRComputeProgramInstanceKey instanceKey); + ALRComputeProgramInstance alrCompileComputePipeline(ALRComputePipeline pipeline, ALRComputeProgramInstanceKey instanceKey); 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 + ); + + int alrGetUniformLocation(ALRComputeProgramInstance program, ALRComputePipeline owner, String name); + + BindlessTexturingSupport alrGetBindlessTexturingSupport(); } 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..d74bf84b 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,40 @@ 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.pipeline.ALRComputePipeline; 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 dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.bindless.BindlessTexturingSupport; +import org.jspecify.annotations.Nullable; + +import java.util.function.Supplier; public interface ALRGpuDeviceExtension { - ALRComputeProgramInstance alrCompileComputeShader(ALRComputeProgramInstanceKey instanceKey); + ALRComputeProgramInstance alrCompileComputePipeline(ALRComputePipeline pipeline,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 + ); + + int alrGetUniformLocation(ALRComputeProgramInstance program, ALRComputePipeline owner, String name); + + BindlessTexturingSupport alrGetBindlessTexturingSupport(); } 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..ed428dc7 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/ALRHICapabilities.java @@ -0,0 +1,14 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d; + +import com.mojang.blaze3d.systems.RenderSystem; + +public record ALRHICapabilities( + boolean compute, + boolean bindlessTexturing, + int maxImageUnit +) { + + 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/NamedUniformAccess.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/NamedUniformAccess.java new file mode 100644 index 00000000..050ffde2 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/NamedUniformAccess.java @@ -0,0 +1,7 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d; + +public interface NamedUniformAccess { + int INVALID_UNIFORM_LOCATION = -1; + + int getUniformLocation(String name, ALRGpuDeviceBackendExtension device); +} 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..b2952530 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 @@ -5,8 +5,10 @@ import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.MemoryBarrierFlag; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.ComputeBindingLayout; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.TextureBinding; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ShaderResourceType; import java.util.List; +import java.util.function.Supplier; public class ALRComputePass implements AutoCloseable { private ALRComputePipeline pipeline; @@ -16,11 +18,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,15 +35,23 @@ public void dispatchWorkgroups( int groupCountY, int groupCountZ ) { - this.backend.pushDebugGroup("Compute " + pipeline.identifier()); - this.backend.dispatchWorkgroups(groupCountX, groupCountY, groupCountZ); - this.backend.popDebugGroup(); + this.backend.pushDebugGroup(() -> "ALRComputePass " + pipeline.identifier()); + try { + this.backend.dispatchWorkgroups(groupCountX, groupCountY, groupCountZ); + } finally { + this.backend.popDebugGroup(); + } } public void dispatchWorkgroupsIndirect( GpuBufferSlice buffer ) { - this.backend.dispatchWorkgroupsIndirect(buffer); + this.backend.pushDebugGroup(() -> "ALRComputePass " + pipeline.identifier()); + try { + this.backend.dispatchWorkgroupsIndirect(buffer); + } finally { + this.backend.popDebugGroup(); + } } @Override @@ -54,16 +64,12 @@ public void setPipeline(ALRComputePipeline pipeline) { this.backend.setPipeline(pipeline); } - @SuppressWarnings({"unchecked", "rawtypes"}) - public void bindAll(List elements) { - int bindingPoint = 0; - for (ComputeBindingLayout binding : pipeline.bindings()) { - this.bind(bindingPoint++, binding, elements.get(bindingPoint - 1)); - } + public int bind(int bindingPointStart, ComputeBindingLayout layout, T resource) { + return layout.applyOrdered(bindingPointStart, resource, this); } - public void bind(int bindingPoint, ComputeBindingLayout layout, T resource) { - layout.apply(bindingPoint, resource, this); + public void bindArrayOfTexture(int bindingPoint, TextureBinding.SamplerAndTexture resource) { + this.backend.bindTexture(bindingPoint, resource); } public void bindTexture(int bindingPoint, TextureBinding.SamplerAndTexture resource) { @@ -85,4 +91,8 @@ public void bindShaderStorage(int bindingPoint, GpuBufferSlice resource) { public void bindAtomicCounter(int bindingPoint, GpuBufferSlice resource) { this.backend.bindAtomicCounter(bindingPoint, resource); } + + public void bindBindlessImageArray(List textures, boolean read, boolean write) { + this.backend.bindBindlessImageArray(textures, read, write); + } } 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..65dc29d3 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,19 @@ 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.List; +import java.util.function.Supplier; + +@ApiStatus.Internal public interface ALRComputePassBackend { void setPipeline(ALRComputePipeline pipeline); - void pushDebugGroup(String name); + void pushDebugGroup(Supplier name); void popDebugGroup(); @@ -29,5 +33,8 @@ public interface ALRComputePassBackend { void bindAtomicCounter(int bindingPoint, GpuBufferSlice resource); + void bindBindlessImageArray(List textures, boolean read, boolean write); + void close(); + } 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..f2f19390 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 @@ -1,24 +1,56 @@ package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline; +import com.google.common.collect.ImmutableList; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.BindlessImageArrayBinding; 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; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.UniformBlockBinding; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ShaderResourceType; import net.minecraft.client.renderer.ShaderDefines; import net.minecraft.resources.Identifier; +import org.jspecify.annotations.Nullable; import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; -public record ALRComputePipeline( - Identifier identifier, - List> bindings, - Identifier shaderLocation, - ShaderDefines defines -) { +public final class ALRComputePipeline { + private final Identifier identifier; + private final List> bindings; + private final Identifier shaderLocation; + private final ShaderDefines defines; + + private final Map>> bindingsByType = + new EnumMap<>(ShaderResourceType.class); + + private final Map> bindingsByName = new HashMap<>(); + + public ALRComputePipeline( + Identifier identifier, + List> bindings, + Identifier shaderLocation, + ShaderDefines defines + ) { + this.identifier = identifier; + this.bindings = bindings; + this.shaderLocation = shaderLocation; + this.defines = defines; + + for (ComputeBindingLayout binding : bindings) { + bindingsByType.computeIfAbsent( + binding.type(), + _ -> new ArrayList<>() + ).add(binding); + bindingsByName.put(binding.name(), binding); + } + } public static Builder builder() { return new Builder(); @@ -28,17 +60,86 @@ public static Builder builder(ALRComputePipeline pipeline) { return builder().withPipeline(pipeline); } + public Identifier identifier() { + return identifier; + } + + public List> bindings() { + return bindings; + } + + @Nullable + public List> getBinding(ShaderResourceType type) { + return this.bindingsByType.get(type); + } + + @Nullable + @SuppressWarnings("unchecked") + public ComputeBindingLayout getBinding(int bindingPointStart, ShaderResourceType type) { + List> ts = (List>) (Object) this.bindingsByType.get(type); + + if (ts == null) { + return null; + } + + return ts.get(bindingPointStart); + } + + public Identifier shaderLocation() { + return shaderLocation; + } + + public ShaderDefines defines() { + return defines; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (ALRComputePipeline) obj; + return Objects.equals(this.identifier, that.identifier) && + Objects.equals(this.bindings, that.bindings) && + Objects.equals(this.shaderLocation, that.shaderLocation) && + Objects.equals(this.defines, that.defines); + } + + @Override + public int hashCode() { + return Objects.hash(identifier, bindings, shaderLocation, defines); + } + + @Override + public String toString() { + return "ALRComputePipeline[" + + "identifier=" + identifier + ", " + + "bindings=" + bindings + ", " + + "shaderLocation=" + shaderLocation + ", " + + "defines=" + defines + ']'; + } + + @Nullable + public ShaderResourceType getBindingType(String name) { + ComputeBindingLayout computeBindingLayout = this.bindingsByName.get(name); + if (computeBindingLayout == null) { + return null; + } + return computeBindingLayout.type(); + } + + public static class Builder { private final List> bindings = new ArrayList<>(); private Identifier name; private Identifier shaderLocation; private ShaderDefines defines = ShaderDefines.EMPTY; - public Builder withPipeline(ALRComputePipeline pipeline) { + /// name will not copy from template pipeline + public Builder withPipeline(ALRComputePipeline pipelineTemplate) { this.bindings.clear(); - this.bindings.addAll(pipeline.bindings()); - this.shaderLocation = pipeline.shaderLocation(); - this.defines = pipeline.defines(); + this.bindings.addAll(pipelineTemplate.bindings()); + this.shaderLocation = pipelineTemplate.shaderLocation(); + this.defines = pipelineTemplate.defines(); return this; } @@ -86,6 +187,15 @@ public Builder withReadWriteImage(String name) { return this.withImage(name, true, true); } + /// an array of image2D, not image2DArray + public Builder withArrayOfImage(String name, boolean read, boolean write, int size) { + return this.withBinding(new ImageArrayBinding(name, read, write, size)); + } + + public Builder withBindlessArrayOfImage(String name, boolean read, boolean write, int size) { + return this.withBinding(new BindlessImageArrayBinding(name, read, write, size)); + } + public Builder withUniformBlock(String name) { return this.withBinding(new UniformBlockBinding(name)); } @@ -101,7 +211,7 @@ public Builder withAtomicCounter(String name) { public ALRComputePipeline build() { return new ALRComputePipeline( Objects.requireNonNull(this.name, "name"), - this.bindings, + ImmutableList.copyOf(this.bindings), Objects.requireNonNull(this.shaderLocation, "shaderLocation"), this.defines ); diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/AtomicCounterBinding.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/AtomicCounterBinding.java index de8c0e78..bd8f90d4 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/AtomicCounterBinding.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/AtomicCounterBinding.java @@ -13,7 +13,8 @@ public ShaderResourceType type() { } @Override - public void apply(int bindingPoint, GpuBufferSlice resource, ALRComputePass computePass) { - computePass.bindAtomicCounter(bindingPoint, resource); + public int applyOrdered(int bindingPointStart, GpuBufferSlice resource, ALRComputePass computePass) { + computePass.bindAtomicCounter(bindingPointStart, resource); + return 1; } } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/BindlessImageArrayBinding.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/BindlessImageArrayBinding.java new file mode 100644 index 00000000..738e0780 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/BindlessImageArrayBinding.java @@ -0,0 +1,35 @@ +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 BindlessImageArrayBinding( + String name, + boolean read, + boolean write, + int size +) implements ComputeBindingLayout> { + + public BindlessImageArrayBinding { + if (!read && !write) { + throw new IllegalArgumentException("BindlessImageArrayBinding does not allow both read and write are false"); + } + } + + @Override + public ShaderResourceType type() { + return ShaderResourceType.TEXTURE_OR_IMAGE; + } + + /// Just assume iterating over the list passed in is ordered. + @Override + public int applyOrdered(int bindingPointStart, List resource, ALRComputePass computePass) { + Preconditions.checkArgument(this.size >= resource.size(), "resource must not have elements more than this.size"); + computePass.bindBindlessImageArray(resource, read, write); + return 0; + } +} 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..a1b44753 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 @@ -8,5 +8,6 @@ public interface ComputeBindingLayout { String name(); - void apply(int bindingPoint, T resource, ALRComputePass computePass); + /// @return binding point incremental + int applyOrdered(int bindingPointStart, T resource, ALRComputePass computePass); } 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..ec7972d9 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ImageArrayBinding.java @@ -0,0 +1,40 @@ +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.TEXTURE_OR_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 int applyOrdered(int bindingPointStart, List resource, ALRComputePass computePass) { + int increment = 0; + Preconditions.checkArgument(this.size >= resource.size(), "resource must not have elements more than this.size"); + for (GpuTexture texture : resource) { + computePass.bindImage(bindingPointStart + increment++, texture, read, write); + } + return increment; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ImageBinding.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ImageBinding.java index cbb1be70..c139c6c9 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ImageBinding.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ImageBinding.java @@ -18,11 +18,12 @@ public record ImageBinding( @Override public ShaderResourceType type() { - return ShaderResourceType.IMAGE; + return ShaderResourceType.TEXTURE_OR_IMAGE; } @Override - public void apply(int bindingPoint, GpuTexture resource, ALRComputePass computePass) { - computePass.bindImage(bindingPoint, resource, read, write); + public int applyOrdered(int bindingPointStart, GpuTexture resource, ALRComputePass computePass) { + computePass.bindImage(bindingPointStart, resource, read, write); + return 1; } } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ShaderStorageBinding.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ShaderStorageBinding.java index 85cdd54f..fe81873a 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ShaderStorageBinding.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/ShaderStorageBinding.java @@ -14,7 +14,8 @@ public ShaderResourceType type() { } @Override - public void apply(int bindingPoint, GpuBufferSlice resource, ALRComputePass computePass) { - computePass.bindShaderStorage(bindingPoint, resource); + public int applyOrdered(int bindingPointStart, GpuBufferSlice resource, ALRComputePass computePass) { + computePass.bindShaderStorage(bindingPointStart, resource); + return 1; } } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/TextureBinding.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/TextureBinding.java index b3496008..6f488731 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/TextureBinding.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/TextureBinding.java @@ -10,12 +10,13 @@ public record TextureBinding( ) implements ComputeBindingLayout { @Override public ShaderResourceType type() { - return ShaderResourceType.TEXTURE; + return ShaderResourceType.TEXTURE_OR_IMAGE; } @Override - public void apply(int bindingPoint, SamplerAndTexture resource, ALRComputePass pass) { - pass.bindTexture(bindingPoint, resource); + public int applyOrdered(int bindingPointStart, SamplerAndTexture resource, ALRComputePass pass) { + pass.bindTexture(bindingPointStart, resource); + return 1; } public record SamplerAndTexture( diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/UniformBlockBinding.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/UniformBlockBinding.java index 1420888c..83998108 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/UniformBlockBinding.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/compute/pipeline/bindings/UniformBlockBinding.java @@ -13,7 +13,8 @@ public ShaderResourceType type() { } @Override - public void apply(int bindingPoint, GpuBufferSlice resource, ALRComputePass computePass) { - computePass.bindUniformBlock(bindingPoint, resource); + public int applyOrdered(int bindingPointStart, GpuBufferSlice resource, ALRComputePass computePass) { + computePass.bindUniformBlock(bindingPointStart, resource); + return 1; } } 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..5ed11716 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 @@ -16,6 +16,8 @@ import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.TextureBinding; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeProgramInstance; import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader.ALRComputeShaderManager; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.ExtendedGpuTexture; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl.GlExtendedTextureConstants; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.jetbrains.annotations.ApiStatus; @@ -26,6 +28,9 @@ import org.lwjgl.opengl.GL33; import org.lwjgl.opengl.GL46; +import java.util.List; +import java.util.function.Supplier; + @ApiStatus.Internal public class GlComputePassBackend implements ALRComputePassBackend { private final ALRGpuDeviceBackendExtension backendExtension; @@ -51,8 +56,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 @@ -105,6 +110,11 @@ public void bindAtomicCounter(int bindingPoint, GpuBufferSlice resource) { this.atomicCounterBindings.put(bindingPoint, resource); } + @Override + public void bindBindlessImageArray(List textures, boolean read, boolean write) { + //TODO: implement with ARB_bindless_texture + } + @Override public void close() { @@ -112,7 +122,9 @@ public void close() { private void setupState() { ALRComputeProgramInstance program = ALRComputeShaderManager.INSTANCE.getShader(pipeline); - if (program == null || program == ALRComputeProgramInstance.INVALID) return; + if (program == null || program == ALRComputeProgramInstance.INVALID) { + throw new IllegalStateException("dispatching compute using a invalid program"); + } GL46.glUseProgram(program.id()); for (Int2ObjectMap.Entry entry : this.textureBindings.int2ObjectEntrySet()) { @@ -154,15 +166,27 @@ private void setupImage(int bindingPoint, ImageState state) { } } - ARBShaderImageLoadStore.glBindImageTexture( - bindingPoint, - ((GlTexture) state.resource()).glId(), - 0, - false, - 0, - access, - GlConst.toGlInternalId(state.resource().getFormat()) - ); + if (state.resource() instanceof ExtendedGpuTexture ext) { + ARBShaderImageLoadStore.glBindImageTexture( + bindingPoint, + ((GlTexture) state.resource()).glId(), + 0, + false, + 0, + access, + GlExtendedTextureConstants.toGlInternalId(ext.getActualFormat()) + ); + } else { + ARBShaderImageLoadStore.glBindImageTexture( + bindingPoint, + ((GlTexture) state.resource()).glId(), + 0, + false, + 0, + access, + GlConst.toGlInternalId(state.resource().getFormat()) + ); + } } private void setupUniformBlock(int bindingPoint, GpuBufferSlice resource) { 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..41a21b80 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 @@ -1,15 +1,29 @@ package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.shader; import dev.anvilcraft.lib.v2.rendering.AnvilLibRendering; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceBackendExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.NamedUniformAccess; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.ALRComputePipeline; import net.minecraft.client.renderer.ShaderDefines; +import org.jetbrains.annotations.ApiStatus; -public record ALRComputeProgramInstance(int id, ALRComputeProgramInstanceKey key) { +@ApiStatus.Internal +public record ALRComputeProgramInstance( + int id, + ALRComputeProgramInstanceKey key, + ALRComputePipeline owner +) implements NamedUniformAccess { public static final ALRComputeProgramInstance INVALID = new ALRComputeProgramInstance( 0, new ALRComputeProgramInstanceKey( AnvilLibRendering.location("compute/invalid"), ShaderDefines.builder().build() - ) + ), + null ); + @Override + public int getUniformLocation(String name, ALRGpuDeviceBackendExtension device) { + return device.alrGetUniformLocation(this, owner, name); + } } 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..b14221b6 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; @@ -23,9 +24,11 @@ import java.io.IOException; import java.io.Reader; import java.util.HashMap; +import java.util.List; import java.util.Map; @Slf4j +@ApiStatus.Internal public class ALRComputeShaderManager extends SimplePreparableReloadListener { public static final ALRComputeShaderManager INSTANCE = new ALRComputeShaderManager(); @@ -49,7 +52,8 @@ public class ALRComputeShaderManager extends SimplePreparableReloadListener processed = preprocessor.process(source); + sources.put(it.getKey(), String.join("", processed)); } catch (IOException ex) { log.error("Failed to load compute shader source at {}", it.getKey(), ex); } @@ -60,7 +64,6 @@ public class ALRComputeShaderManager extends SimplePreparableReloadListener handle); +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/bindless/TextureHandle.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/bindless/TextureHandle.java new file mode 100644 index 00000000..e8abb184 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/bindless/TextureHandle.java @@ -0,0 +1,5 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.bindless; + +public interface TextureHandle { + boolean isTexture(); +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/bindless/TextureResidentScope.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/bindless/TextureResidentScope.java new file mode 100644 index 00000000..1fe2dc79 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/bindless/TextureResidentScope.java @@ -0,0 +1,12 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.bindless; + +/// try-with-resources style api for {@link BindlessTexturingSupport#alrTextureHandleMakeResident(TextureHandle, boolean, boolean)} +/// and {@link BindlessTexturingSupport#alrTextureHandleDeleteResident(TextureHandle)} +public interface TextureResidentScope extends AutoCloseable { + + /// Called by GpuDevice + void onCreate(); + + @Override + void close(); +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/GlExtendedTexture.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/GlExtendedTexture.java new file mode 100644 index 00000000..763132b3 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/GlExtendedTexture.java @@ -0,0 +1,39 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl; + +import com.mojang.blaze3d.opengl.GlTexture; +import com.mojang.blaze3d.textures.TextureFormat; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ExtendedTextureFormat; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.ExtendedGpuTexture; +import lombok.Getter; + +/// A simple texture implementation with extended formats +public class GlExtendedTexture extends GlTexture implements ExtendedGpuTexture { + + @Getter + private final ExtendedTextureFormat actualFormat; + + public GlExtendedTexture( + @Usage int usage, + String label, + ExtendedTextureFormat format, + int width, + int height, + int depthOrLayers, + int mipLevels, + int id + ) { + // use RGBA8 here hopefully make most of blaze3d code work as they don't care pixel size of actual format + // as we are not using this as a framebuffer attachment + super( + usage, + label, + TextureFormat.RGBA8, + width, + height, + depthOrLayers, + mipLevels, + id + ); + this.actualFormat = format; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/GlExtendedTextureConstants.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/GlExtendedTextureConstants.java new file mode 100644 index 00000000..6fbd3bec --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/GlExtendedTextureConstants.java @@ -0,0 +1,34 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl; + +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ExtendedTextureFormat; +import org.lwjgl.opengl.GL46; + +public class GlExtendedTextureConstants { + public static int toGlConst(ExtendedTextureFormat textureFormat){ + return switch (textureFormat) { + case R32F -> 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/extension/blaze3d/texture/gl/bindless/GlBindlessTexturingSupport.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/bindless/GlBindlessTexturingSupport.java new file mode 100644 index 00000000..0936f989 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/bindless/GlBindlessTexturingSupport.java @@ -0,0 +1,151 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl.bindless; + +import com.mojang.blaze3d.opengl.GlConst; +import com.mojang.blaze3d.opengl.GlSampler; +import com.mojang.blaze3d.opengl.GlTexture; +import com.mojang.blaze3d.textures.GpuSampler; +import com.mojang.blaze3d.textures.GpuTexture; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceBackendExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.NamedUniformAccess; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.ExtendedGpuTexture; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.bindless.BindlessTexturingSupport; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.bindless.TextureHandle; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.bindless.TextureResidentScope; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl.GlExtendedTextureConstants; +import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; +import org.lwjgl.opengl.ARBBindlessTexture; +import org.lwjgl.opengl.GL46; + +import java.util.List; + +public class GlBindlessTexturingSupport implements BindlessTexturingSupport { + + private final Object2ReferenceMap textureHandleCache = new Object2ReferenceLinkedOpenHashMap<>(); + private final Object2ReferenceMap imageHandleCache = new Object2ReferenceLinkedOpenHashMap<>(); + private final ALRGpuDeviceBackendExtension backendExtension; + + public GlBindlessTexturingSupport(ALRGpuDeviceBackendExtension backendExtension) { + this.backendExtension = backendExtension; + } + + @Override + public TextureHandle alrCreateTextureHandle(GpuTexture texture, GpuSampler sampler) { + GlTexture glTexture = (GlTexture) texture; + GlSampler glSampler = (GlSampler) sampler; + + TextureCacheKey cacheKey = new TextureCacheKey(texture, sampler); + GlTextureHandle handle = textureHandleCache.get(cacheKey); + if (handle == null) { + long handleId = ARBBindlessTexture.glGetTextureSamplerHandleARB(glTexture.glId(), glSampler.getId()); + handle = new GlTextureHandle(handleId, true); + textureHandleCache.put(cacheKey, handle); + } + + return handle; + } + + @Override + public TextureHandle alrCreateImageHandle(GpuTexture texture, int level, boolean layered, int layer) { + GlTexture glTexture = (GlTexture) texture; + + ImageCacheKey cacheKey = new ImageCacheKey(texture, level, layered, layer); + GlTextureHandle handle = this.imageHandleCache.get(cacheKey); + + int format = GlConst.toGlInternalId(glTexture.getFormat()); + + if (glTexture instanceof ExtendedGpuTexture ext) { + format = GlExtendedTextureConstants.toGlConst(ext.getActualFormat()); + } + + if (handle == null) { + long handleId = ARBBindlessTexture.glGetImageHandleARB(glTexture.glId(), level, layered, layer, format); + handle = new GlTextureHandle(handleId, false); + imageHandleCache.put(cacheKey, handle); + } + + return handle; + } + + @Override + public TextureResidentScope alrTextureHandleCreateResidentScope(TextureHandle handle, boolean write, boolean read) { + GlTextureResidentScope scope = new GlTextureResidentScope(this, handle, write, read); + scope.onCreate(); + return scope; + } + + @Override + public void alrTextureHandleMakeResident(TextureHandle handle, boolean write, boolean read) { + if (handle.isTexture()) { + ARBBindlessTexture.glMakeTextureHandleResidentARB(handleId(handle)); + return; + } + int access = 0; + + if (write && read) { + access |= GL46.GL_READ_WRITE; + } else { + if (write) { + access |= GL46.GL_WRITE_ONLY; + } + if (read) { + access |= GL46.GL_READ_ONLY; + } + } + + if (access == 0) { + throw new IllegalArgumentException( + "alrTextureHandleMakeResident does not allow write == false && read == false" + ); + } + + ARBBindlessTexture.glMakeImageHandleResidentARB(handleId(handle), access); + } + + @Override + public void alrTextureHandleDeleteResident(TextureHandle handle) { + if (handle.isTexture()) { + ARBBindlessTexture.glMakeTextureHandleNonResidentARB(handleId(handle)); + return; + } + + ARBBindlessTexture.glMakeImageHandleNonResidentARB(handleId(handle)); + } + + @Override + public void alrBindTextureHandle(NamedUniformAccess namedUniformAccess, String name, TextureHandle handle) { + int uniformLocation = namedUniformAccess.getUniformLocation(name, this.backendExtension); + ARBBindlessTexture.glUniformHandleui64ARB(uniformLocation, handleId(handle)); + } + + @Override + public void alrBindTextureHandleMultiple( + NamedUniformAccess namedUniformAccess, + String name, + List handle + ) { + int uniformLocation = namedUniformAccess.getUniformLocation(name, this.backendExtension); + + long[] handles = new long[handle.size()]; + int i = 0; + for (TextureHandle textureHandle : handle) { + handles[i++] = handleId(textureHandle); + } + + ARBBindlessTexture.glUniformHandleui64vARB(uniformLocation, handles); + } + + private static long handleId(TextureHandle handle) { + return ((GlTextureHandle) (handle)).handleId(); + } + + private record TextureCacheKey( + GpuTexture texture, GpuSampler sampler + ) { + } + + private record ImageCacheKey( + GpuTexture texture, int level, boolean layered, int layer + ) { + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/bindless/GlTextureHandle.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/bindless/GlTextureHandle.java new file mode 100644 index 00000000..b23d9b71 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/bindless/GlTextureHandle.java @@ -0,0 +1,6 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl.bindless; + +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.bindless.TextureHandle; + +public record GlTextureHandle(long handleId, boolean isTexture) implements TextureHandle { +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/bindless/GlTextureResidentScope.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/bindless/GlTextureResidentScope.java new file mode 100644 index 00000000..19d1b481 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/extension/blaze3d/texture/gl/bindless/GlTextureResidentScope.java @@ -0,0 +1,33 @@ +package dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl.bindless; + +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.bindless.TextureHandle; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.bindless.TextureResidentScope; + +public class GlTextureResidentScope implements TextureResidentScope { + private final GlBindlessTexturingSupport owner; + private final TextureHandle handle; + private final boolean write; + private final boolean read; + + public GlTextureResidentScope( + GlBindlessTexturingSupport owner, + TextureHandle handle, + boolean write, + boolean read + ) { + this.owner = owner; + this.handle = handle; + this.write = write; + this.read = read; + } + + @Override + public void onCreate() { + this.owner.alrTextureHandleMakeResident(this.handle, this.write, this.read); + } + + @Override + public void close() { + this.owner.alrTextureHandleDeleteResident(this.handle); + } +} 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/layout/BufferWriter.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/layout/BufferWriter.java index ab77d5c8..dd5e1cfc 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/layout/BufferWriter.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/layout/BufferWriter.java @@ -30,11 +30,15 @@ public interface BufferWriter { void putFloat(float object); - ByteBuffer intoBuffer(); + default ByteBuffer intoBuffer() { + return this.intoBuffer(true); + } + + ByteBuffer intoBuffer(boolean flip); void putStructArray(int index, E object, BufferObjectLayoutDefinition definition); - default void putStructArray(E[] objects, BufferObjectLayoutDefinition definition) { + default void putStructArray(E[] objects, BufferObjectLayoutDefinition definition) { for (int i = 0; i < objects.length; i++) { putStructArray(i, objects[i], definition); } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/layout/std140/Std140Writer.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/layout/std140/Std140Writer.java index 568864bb..c34f2487 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/layout/std140/Std140Writer.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/layout/std140/Std140Writer.java @@ -85,8 +85,11 @@ public void putMat4f(Matrix4f vec) { this.buffer.position(this.buffer.position() + 64); } - public ByteBuffer intoBuffer() { - return this.buffer.flip(); + public ByteBuffer intoBuffer(boolean flip) { + if (flip) { + return this.buffer.flip(); + } + return this.buffer; } @Override diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/layout/std430/Std430Writer.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/layout/std430/Std430Writer.java index 63ef1369..fc4ac21b 100644 --- a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/layout/std430/Std430Writer.java +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/layout/std430/Std430Writer.java @@ -85,8 +85,11 @@ public void putMat4f(Matrix4f vec) { this.buffer.position(this.buffer.position() + 64); } - public ByteBuffer intoBuffer() { - return this.buffer.flip(); + public ByteBuffer intoBuffer(boolean flip) { + if (flip) { + return this.buffer.flip(); + } + return this.buffer; } @Override 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..70aa8b80 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,9 +1,9 @@ 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; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.layout.BufferWriter; import net.minecraft.client.renderer.DynamicUniformStorage; import org.jspecify.annotations.NonNull; import org.lwjgl.system.MemoryStack; @@ -33,14 +33,22 @@ public void upload(CommandEncoder commandEncoder, GpuBufferSlice dest) { @Override @SuppressWarnings("unchecked") public void write(@NonNull ByteBuffer buffer) { - getDefinition().write(buffer, (T) this, this.layout); + if (this.usage == ShaderBufferObjectUsage.SSBO) { + throw new IllegalStateException(); + } + BufferWriter builder = layout.createWriter(buffer); + getDefinition().writeInto(builder, (T) this); } + @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..84b36228 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/foundation/buffers/ubo/FullTransformsUbo.java @@ -0,0 +1,48 @@ +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 CameraViewMat; +/// 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::getCameraViewMat).build(), + BufferObjectLayoutEntry.ofMat4f().forGetter(FullTransformsUbo::getModelViewMat).build() + ); + + public static final int SIZE = DEFINITION.size(BufferLayout.STD140); + + private Matrix4f projMat; + private Matrix4f cameraViewMat; + private Matrix4f modelViewMat; + + public FullTransformsUbo() { + super(BufferLayout.STD140, ShaderBufferObjectUsage.UBO); + this.projMat = new Matrix4f(); + this.modelViewMat = new Matrix4f(); + this.cameraViewMat = 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..d52457a4 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,14 @@ 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.lwjgl.opengl.GL46; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -15,7 +17,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(Minecraft.class) -@ApiStatus.Internal public class MinecraftMixin { @Shadow @@ -27,10 +28,20 @@ public class MinecraftMixin { at = @At("RETURN") ) private void onCreateInstance(GameConfig gameConfig, CallbackInfo ci) { + ALRComputeCapabilities.init(); ALRPostEffects.createPostEffects(); + ALROptimizations.create(); CachedBlockEntityRenderingPipeline.create(); } + @Inject( + method = "renderFrame", + at = @At("HEAD") + ) + private void onBeforeRenderFrame(boolean advanceGameTime, CallbackInfo ci) { + ALROptimizations.getOcclusionCuller().beforeExtract(); + } + // @Inject( // method = "resizeGui", // at = @At("RETURN") 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/accessors/CustomFeatureRendererStorageAccess.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/CustomFeatureRendererStorageAccess.java new file mode 100644 index 00000000..0e6f22d3 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/CustomFeatureRendererStorageAccess.java @@ -0,0 +1,20 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.accessors; + +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.CustomFeatureRenderer; +import net.minecraft.client.renderer.rendertype.RenderType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; +import java.util.Map; + +@Mixin(CustomFeatureRenderer.Storage.class) +public interface CustomFeatureRendererStorageAccess { + + @Accessor("solidCustomGeometrySubmits") + Map> alrGetSolidCustomGeometrySubmits(); + + @Accessor("translucentCustomGeometrySubmits") + Map> alrGetTranslucentCustomGeometrySubmits(); +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/ModelFeatureRendererStorageAccess.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/ModelFeatureRendererStorageAccess.java new file mode 100644 index 00000000..eb4a4436 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/ModelFeatureRendererStorageAccess.java @@ -0,0 +1,20 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.accessors; + +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import net.minecraft.client.renderer.rendertype.RenderType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; +import java.util.Map; + +@Mixin(ModelFeatureRenderer.Storage.class) +public interface ModelFeatureRendererStorageAccess { + + @Accessor("solidModelSubmits") + Map>> alrGetSolidModelSubmits(); + + @Accessor("translucentModelSubmits") + List> alrGetTranslucentModelSubmits(); +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/ModelPartFeatureRendererStorageAccess.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/ModelPartFeatureRendererStorageAccess.java new file mode 100644 index 00000000..f7400568 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/ModelPartFeatureRendererStorageAccess.java @@ -0,0 +1,20 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.accessors; + +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.ModelPartFeatureRenderer; +import net.minecraft.client.renderer.rendertype.RenderType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; +import java.util.Map; + +@Mixin(ModelPartFeatureRenderer.Storage.class) +public interface ModelPartFeatureRendererStorageAccess { + + @Accessor("solidModelPartSubmits") + Map> alrGetSolidModelPartSubmits(); + + @Accessor("translucentModelPartSubmits") + Map> alrGetTranslucentModelPartSubmits(); +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/NameTagFeatureRendererStorageAccess.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/NameTagFeatureRendererStorageAccess.java new file mode 100644 index 00000000..413a6590 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/NameTagFeatureRendererStorageAccess.java @@ -0,0 +1,18 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.accessors; + +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.NameTagFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(NameTagFeatureRenderer.Storage.class) +public interface NameTagFeatureRendererStorageAccess { + + @Accessor("nameTagSubmitsSeethrough") + List alrGetNameTagSubmitsSeethrough(); + + @Accessor("nameTagSubmitsNormal") + List alrGetNameTagSubmitsNormal(); +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/SubmitNodeCollectionAccess.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/SubmitNodeCollectionAccess.java new file mode 100644 index 00000000..0d03137f --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/accessors/SubmitNodeCollectionAccess.java @@ -0,0 +1,12 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.accessors; + +import net.minecraft.client.renderer.SubmitNodeCollection; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(SubmitNodeCollection.class) +public interface SubmitNodeCollectionAccess { + + @Accessor("wasUsed") + void alrSetWasUsed(boolean value); +} 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..58dabb36 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,28 +2,34 @@ 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.pipeline.ALRComputePipeline; 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 dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.bindless.BindlessTexturingSupport; +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 private GpuDeviceBackend backend; @Override - public ALRComputeProgramInstance alrCompileComputeShader(ALRComputeProgramInstanceKey instanceKey) { - return alrBackend().alrCompileComputeShader(instanceKey); + public ALRComputeProgramInstance alrCompileComputePipeline(ALRComputePipeline pipeline, ALRComputeProgramInstanceKey instanceKey) { + return alrBackend().alrCompileComputePipeline(pipeline, instanceKey); } @Override @@ -31,6 +37,51 @@ 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 + ); + } + + @Override + public int alrGetUniformLocation(ALRComputeProgramInstance program, ALRComputePipeline owner, String name) { + return alrBackend().alrGetUniformLocation(program, owner, name); + } + + @Override + public BindlessTexturingSupport alrGetBindlessTexturingSupport() { + return alrBackend().alrGetBindlessTexturingSupport(); + } + @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..e64680e6 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,41 @@ 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.preprocessor.GlslPreprocessor; +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.pipeline.ALRComputePipeline; 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.bindless.BindlessTexturingSupport; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl.GlExtendedTexture; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl.GlExtendedTextureConstants; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.texture.gl.bindless.GlBindlessTexturingSupport; +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,19 +45,32 @@ public abstract class GlDeviceMixin implements ALRGpuDeviceBackendExtension { @Shadow public abstract GlDebugLabel debugLabels(); + @Shadow + @Final + private GlDebugLabel debugLabels; + + @Unique + private ALRHICapabilities alr$capabilities = null; + @Unique + private BindlessTexturingSupport alr$bindlessTexturingImpl; + @Override public void alrDestroyComputeShader(ALRComputeProgramInstance instance) { GL46.glDeleteProgram(instance.id()); } @Override - public ALRComputeProgramInstance alrCompileComputeShader(ALRComputeProgramInstanceKey instanceKey) { + public ALRComputeProgramInstance alrCompileComputePipeline( + ALRComputePipeline pipeline, + ALRComputeProgramInstanceKey instanceKey + ) { String source = ALRComputeShaderManager.INSTANCE.getSource(instanceKey.location()); int shaderId = GL46.glCreateShader(ARBComputeShader.GL_COMPUTE_SHADER); - GL46.glShaderSource(shaderId, source); + GL46.glShaderSource(shaderId, GlslPreprocessor.injectDefines(source, instanceKey.defines())); GL46.glCompileShader(shaderId); if (GL46.glGetShaderi(shaderId, GL46.GL_COMPILE_STATUS) == 0) { String infoLog = GL46.glGetShaderInfoLog(shaderId); + GL46.glDeleteShader(shaderId); LOGGER.error("Could not compile COMPUTE shader {}: {}", instanceKey.location(), infoLog); return ALRComputeProgramInstance.INVALID; } @@ -45,23 +78,143 @@ public ALRComputeProgramInstance alrCompileComputeShader(ALRComputeProgramInstan GL46.glAttachShader(program, shaderId); GL46.glLinkProgram(program); if (GL46.glGetProgrami(program, GL46.GL_LINK_STATUS) == 0) { - String infoLog = GL46.glGetProgramInfoLog(shaderId); + String infoLog = GL46.glGetProgramInfoLog(program); LOGGER.error("Could not link COMPUTE shader {}: {}", instanceKey.location(), infoLog); + GL46.glDeleteShader(shaderId); + GL46.glDeleteProgram(program); return ALRComputeProgramInstance.INVALID; } GL46.glDeleteShader(shaderId); - ALRComputeProgramInstance instance = new ALRComputeProgramInstance(program, instanceKey); + ALRComputeProgramInstance instance = new ALRComputeProgramInstance(program, instanceKey, pipeline); ((ALRDebugLabelExtension) this.debugLabels()).alrApplyLabel(instance); return instance; } @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, + capabilities.GL_ARB_bindless_texture, + GL46.glGetInteger(GL46.GL_MAX_IMAGE_UNITS) + ); + } + 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; + } + } + + @Override + public int alrGetUniformLocation(ALRComputeProgramInstance program, ALRComputePipeline owner, String name) { + return switch (owner.getBindingType(name)) { + case TEXTURE_OR_IMAGE -> GlStateManager._glGetUniformLocation(program.id(), name); + case null -> -1; + default -> throw new IllegalStateException("Shader resource other than texture or image is not allowed to getLocation"); + }; + } + + @Override + public BindlessTexturingSupport alrGetBindlessTexturingSupport() { + if (alrhiCreateCapabilities().bindlessTexturing() && alr$bindlessTexturingImpl == null) { + this.alr$bindlessTexturingImpl = new GlBindlessTexturingSupport(this); + } + return this.alr$bindlessTexturingImpl; + } } diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlProgramMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlProgramMixin.java new file mode 100644 index 00000000..1ea86e47 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/blaze3d/gl/GlProgramMixin.java @@ -0,0 +1,28 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.blaze3d.gl; + +import com.mojang.blaze3d.opengl.GlProgram; +import com.mojang.blaze3d.opengl.Uniform; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceBackendExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.NamedUniformAccess; +import org.jspecify.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +@Mixin(GlProgram.class) +public abstract class GlProgramMixin implements NamedUniformAccess { + @Shadow + public abstract @Nullable Uniform getUniform(String name); + + @Override + public int getUniformLocation(String name, ALRGpuDeviceBackendExtension device) { + Uniform uniform = getUniform(name); + if (uniform == null) { + return INVALID_UNIFORM_LOCATION; + } + return switch (uniform) { + case Uniform.Sampler(int location, _) -> location; + case Uniform.Ubo(int blockBinding) -> blockBinding; + case Uniform.Utb(int location, _, _, _) -> location; + }; + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/BlockFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/BlockFeatureRendererMixin.java new file mode 100644 index 00000000..244e76dd --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/BlockFeatureRendererMixin.java @@ -0,0 +1,48 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.BlockFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(BlockFeatureRenderer.class) +public abstract class BlockFeatureRendererMixin { + @ModifyExpressionValue( + method = "renderMovingBlockSubmits", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/SubmitNodeCollection;getMovingBlockSubmits()Ljava/util/List;" + ) + ) + private List anvillib$filterMovingBlockSubmits(List submits) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } + + @ModifyExpressionValue( + method = "renderBlockModelSubmits", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/SubmitNodeCollection;getBlockModelSubmits()Ljava/util/List;" + ) + ) + private List anvillib$filterBlockModelSubmits(List submits) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } + + @ModifyExpressionValue( + method = "renderBreakingBlockModelSubmits", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/SubmitNodeCollection;getBreakingBlockModelSubmits()Ljava/util/List;" + ) + ) + private List anvillib$filterBreakingBlockModelSubmits( + List submits + ) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/CustomFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/CustomFeatureRendererMixin.java new file mode 100644 index 00000000..1ccbde4b --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/CustomFeatureRendererMixin.java @@ -0,0 +1,23 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyReceiver; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.CustomFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(CustomFeatureRenderer.class) +public abstract class CustomFeatureRendererMixin { + @ModifyReceiver( + method = {"renderSolid", "renderTranslucent"}, + at = @At(value = "INVOKE", target = "Ljava/util/List;iterator()Ljava/util/Iterator;") + ) + private List anvillib$filterCustomGeometrySubmits( + List submits + ) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ExtendedBlockFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ExtendedBlockFeatureRendererMixin.java new file mode 100644 index 00000000..95c91204 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ExtendedBlockFeatureRendererMixin.java @@ -0,0 +1,26 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.neoforged.neoforge.client.submit.ExtendedBlockFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(ExtendedBlockFeatureRenderer.class) +public abstract class ExtendedBlockFeatureRendererMixin { + @ModifyExpressionValue( + method = "renderMultiLayerBlockModelSubmits", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/SubmitNodeCollection;getMultiLayerBlockModelSubmits()Ljava/util/List;" + ) + ) + private static List anvillib$filterMultiLayerBlockModelSubmits( + List submits + ) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/FlameFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/FlameFeatureRendererMixin.java new file mode 100644 index 00000000..aa7cbda1 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/FlameFeatureRendererMixin.java @@ -0,0 +1,24 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.FlameFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(FlameFeatureRenderer.class) +public abstract class FlameFeatureRendererMixin { + @ModifyExpressionValue( + method = "renderSolid", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/SubmitNodeCollection;getFlameSubmits()Ljava/util/List;" + ) + ) + private List anvillib$filterFlameSubmits(List submits) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ItemFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ItemFeatureRendererMixin.java new file mode 100644 index 00000000..cd5bdda7 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ItemFeatureRendererMixin.java @@ -0,0 +1,24 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.ItemFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(ItemFeatureRenderer.class) +public abstract class ItemFeatureRendererMixin { + @ModifyExpressionValue( + method = {"renderSolid", "renderTranslucent"}, + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/SubmitNodeCollection;getItemSubmits()Ljava/util/List;" + ) + ) + private List anvillib$filterItemSubmits(List submits) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/LeashFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/LeashFeatureRendererMixin.java new file mode 100644 index 00000000..6c222cd7 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/LeashFeatureRendererMixin.java @@ -0,0 +1,24 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.LeashFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(LeashFeatureRenderer.class) +public abstract class LeashFeatureRendererMixin { + @ModifyExpressionValue( + method = "renderSolid", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/SubmitNodeCollection;getLeashSubmits()Ljava/util/List;" + ) + ) + private static List anvillib$filterLeashSubmits(List submits) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ModelFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ModelFeatureRendererMixin.java new file mode 100644 index 00000000..bc08025e --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ModelFeatureRendererMixin.java @@ -0,0 +1,31 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyReceiver; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(ModelFeatureRenderer.class) +public abstract class ModelFeatureRendererMixin { + @ModifyReceiver( + method = "renderTranslucents", + at = @At(value = "INVOKE", target = "Ljava/util/List;iterator()Ljava/util/Iterator;") + ) + private List> anvillib$filterTranslucentModelSubmits( + List> submits + ) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } + + @ModifyReceiver( + method = "renderBatch", + at = @At(value = "INVOKE", target = "Ljava/util/List;iterator()Ljava/util/Iterator;") + ) + private List> anvillib$filterModelSubmits(List> submits) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ModelPartFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ModelPartFeatureRendererMixin.java new file mode 100644 index 00000000..97b316b6 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ModelPartFeatureRendererMixin.java @@ -0,0 +1,21 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyReceiver; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.ModelPartFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(ModelPartFeatureRenderer.class) +public abstract class ModelPartFeatureRendererMixin { + @ModifyReceiver( + method = "render", + at = @At(value = "INVOKE", target = "Ljava/util/List;iterator()Ljava/util/Iterator;") + ) + private List anvillib$filterModelPartSubmits(List submits) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/NameTagFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/NameTagFeatureRendererMixin.java new file mode 100644 index 00000000..f460b062 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/NameTagFeatureRendererMixin.java @@ -0,0 +1,22 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyReceiver; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.NameTagFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(NameTagFeatureRenderer.class) +public abstract class NameTagFeatureRendererMixin { + @ModifyReceiver( + method = "renderTranslucent", + at = @At(value = "INVOKE", target = "Ljava/util/List;iterator()Ljava/util/Iterator;"), + expect = 2 + ) + private List anvillib$filterNameTagSubmits(List submits) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ParticleFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ParticleFeatureRendererMixin.java new file mode 100644 index 00000000..e33d0f31 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ParticleFeatureRendererMixin.java @@ -0,0 +1,26 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.feature.ParticleFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(ParticleFeatureRenderer.class) +public abstract class ParticleFeatureRendererMixin { + @ModifyExpressionValue( + method = "render", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/SubmitNodeCollection;getParticleGroupRenderers()Ljava/util/List;" + ) + ) + private List anvillib$filterParticleGroupRenderers( + List submits + ) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ShadowFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ShadowFeatureRendererMixin.java new file mode 100644 index 00000000..6253e5f8 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/ShadowFeatureRendererMixin.java @@ -0,0 +1,24 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.ShadowFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(ShadowFeatureRenderer.class) +public abstract class ShadowFeatureRendererMixin { + @ModifyExpressionValue( + method = "renderTranslucent", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/SubmitNodeCollection;getShadowSubmits()Ljava/util/List;" + ) + ) + private List anvillib$filterShadowSubmits(List submits) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/TextFeatureRendererMixin.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/TextFeatureRendererMixin.java new file mode 100644 index 00000000..e0d50dce --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/mixins/features/TextFeatureRendererMixin.java @@ -0,0 +1,24 @@ +package dev.anvilcraft.lib.v2.rendering.mixins.features; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionFeatureRendererHelper; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.feature.TextFeatureRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.List; + +@Mixin(TextFeatureRenderer.class) +public abstract class TextFeatureRendererMixin { + @ModifyExpressionValue( + method = "renderTranslucent", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/SubmitNodeCollection;getTextSubmits()Ljava/util/List;" + ) + ) + private List anvillib$filterTextSubmits(List submits) { + return OcclusionFeatureRendererHelper.filterVisibleFeatures(submits); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/CullingStatistics.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/CullingStatistics.java new file mode 100644 index 00000000..af5be3b0 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/CullingStatistics.java @@ -0,0 +1,15 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion; + +import java.util.List; + +public record CullingStatistics( + int total, + int frustumPrePass, + int cameraInside, + int culled, + int rendered, + int features, + List message +) { + +} 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..b9be5c0f --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionCuller.java @@ -0,0 +1,69 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion; + +import com.mojang.blaze3d.systems.GpuDevice; +import dev.anvilcraft.lib.v2.rendering.ALROptions; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.noop.NoOpOcclusionCuller; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +public interface OcclusionCuller extends AutoCloseable{ + + void onResize(int newWidth, int newHeight); + + /// called on a frame before all features are extracted + void beforeExtract(); + + /// called on a frame before rendering start + void beginRenderingFrame(); + + /// OcclusionCuller relies on OcclusionKey to distinguish features between frames, + /// so the key param is also required to be the same object between frames. + void submitFeatureKey(OcclusionKey key, List feature); + + /// called on all features submitted and solid/cutout terrain rendered + void processFeatures(CameraRenderState camera); + + boolean shouldDraw(Object feature); + + boolean isEmpty(); + + default OcclusionSubmitNodeStorage wrapSubmitNodeStorage(SubmitNodeCollector original) { + return new OcclusionSubmitNodeStorage(this, original); + } + + @Nullable + CullingStatistics collectStatistics(); + + @SuppressWarnings("ConstantValue") + static OcclusionCuller createInstance(GpuDevice device) { + OcclusionCuller instance; + + String forcedImpl = ALROptions.OCCLUSION_CULLING_FORCE_IMPL; + if (forcedImpl != null) { + try { + instance = OcclusionMethod.valueOf(forcedImpl).createInstance(device); + } catch (IllegalArgumentException _) { + instance = null; + } + if (instance != null) { + return 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 new NoOpOcclusionCuller(); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionFeatureRendererHelper.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionFeatureRendererHelper.java new file mode 100644 index 00000000..36775f50 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionFeatureRendererHelper.java @@ -0,0 +1,26 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion; + +import dev.anvilcraft.lib.v2.rendering.ALROptimizations; + +import java.util.ArrayList; +import java.util.List; + +public class OcclusionFeatureRendererHelper { + private OcclusionFeatureRendererHelper() { + } + + public static List filterVisibleFeatures(List features) { + OcclusionCuller culler = ALROptimizations.getOcclusionCuller(); + if (culler == null || culler.isEmpty()) { + return features; + } + + List visibleFeatures = new ArrayList<>(features.size()); + for (T feature : features) { + if (culler.shouldDraw(feature)) { + visibleFeatures.add(feature); + } + } + return visibleFeatures; + } +} 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..d69d27ee --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionMethod.java @@ -0,0 +1,49 @@ +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.noop.NoOpOcclusionCuller; +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); + } + }, NO_OP { + @Override + public boolean isSupported() { + return true; + } + + @Override + public @NonNull OcclusionCuller createInstance(GpuDevice device) { + return new NoOpOcclusionCuller(); + } + }; + + 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/OcclusionSubmitNodeCollection.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionSubmitNodeCollection.java new file mode 100644 index 00000000..0e78b3c7 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionSubmitNodeCollection.java @@ -0,0 +1,179 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion; + +import dev.anvilcraft.lib.v2.rendering.mixins.accessors.CustomFeatureRendererStorageAccess; +import dev.anvilcraft.lib.v2.rendering.mixins.accessors.ModelFeatureRendererStorageAccess; +import dev.anvilcraft.lib.v2.rendering.mixins.accessors.ModelPartFeatureRendererStorageAccess; +import dev.anvilcraft.lib.v2.rendering.mixins.accessors.NameTagFeatureRendererStorageAccess; +import dev.anvilcraft.lib.v2.rendering.mixins.accessors.SubmitNodeCollectionAccess; +import net.minecraft.client.renderer.SubmitNodeCollection; +import net.minecraft.client.renderer.SubmitNodeStorage; +import net.minecraft.client.renderer.rendertype.RenderType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class OcclusionSubmitNodeCollection extends SubmitNodeCollection { + private final OcclusionCuller culler; + private final SubmitNodeCollection original; + + private OcclusionKey currentKey = null; + + public OcclusionSubmitNodeCollection( + SubmitNodeStorage submitNodeStorage, + OcclusionCuller culler, + SubmitNodeCollection original + ) { + super(submitNodeStorage); + this.culler = culler; + this.original = original; + } + + public void beginOcclusionRecord(OcclusionKey occlusionKey) { + if (this.currentKey != null){ + throw new IllegalStateException("occlusion record begun without terminating previous record."); + } + this.clear(); + this.endFrame(); + this.currentKey = occlusionKey; + } + + public void endOcclusionRecord() { + if (this.wasUsed()) { + this.submitFeatureToCuller(); + } + this.currentKey = null; + } + + private void submitFeatureToCuller() { + List collectedFeatures = new ArrayList<>(); + if (this.currentKey == null) { + return; + } + + this.submitFeatureList( + this.getShadowSubmits(), + this.original.getShadowSubmits(), + collectedFeatures + ); + this.submitFeatureList( + this.getFlameSubmits(), + this.original.getFlameSubmits(), + collectedFeatures + ); + this.submitFeatureList( + this.getTextSubmits(), + this.original.getTextSubmits(), + collectedFeatures + ); + this.submitFeatureList( + this.getLeashSubmits(), + this.original.getLeashSubmits(), + collectedFeatures + ); + this.submitFeatureList( + this.getMovingBlockSubmits(), + this.original.getMovingBlockSubmits(), + collectedFeatures + ); + this.submitFeatureList( + this.getBlockModelSubmits(), + this.original.getBlockModelSubmits(), + collectedFeatures + ); + this.submitFeatureList( + this.getMultiLayerBlockModelSubmits(), + this.original.getMultiLayerBlockModelSubmits(), + collectedFeatures + ); + this.submitFeatureList( + this.getBreakingBlockModelSubmits(), + this.original.getBreakingBlockModelSubmits(), + collectedFeatures + ); + this.submitFeatureList(this.getItemSubmits(), this.original.getItemSubmits(), collectedFeatures); + this.submitFeatureList( + this.getParticleGroupRenderers(), + this.original.getParticleGroupRenderers(), + collectedFeatures + ); + + NameTagFeatureRendererStorageAccess nameTagSubmits = (NameTagFeatureRendererStorageAccess) this.getNameTagSubmits(); + NameTagFeatureRendererStorageAccess originalNameTagSubmits = (NameTagFeatureRendererStorageAccess) this.original.getNameTagSubmits(); + + this.submitFeatureList( + nameTagSubmits.alrGetNameTagSubmitsNormal(), + originalNameTagSubmits.alrGetNameTagSubmitsNormal(), + collectedFeatures + ); + this.submitFeatureList( + nameTagSubmits.alrGetNameTagSubmitsSeethrough(), + originalNameTagSubmits.alrGetNameTagSubmitsSeethrough(), + collectedFeatures + ); + + ModelFeatureRendererStorageAccess modelSubmits = (ModelFeatureRendererStorageAccess) this.getModelSubmits(); + ModelFeatureRendererStorageAccess originalModelSubmits = (ModelFeatureRendererStorageAccess) this.original.getModelSubmits(); + + this.submitFeatureMap( + modelSubmits.alrGetSolidModelSubmits(), + originalModelSubmits.alrGetSolidModelSubmits(), + collectedFeatures + ); + this.submitFeatureList( + modelSubmits.alrGetTranslucentModelSubmits(), + originalModelSubmits.alrGetTranslucentModelSubmits(), + collectedFeatures + ); + + ModelPartFeatureRendererStorageAccess modelPartSubmits = (ModelPartFeatureRendererStorageAccess) this.getModelPartSubmits(); + ModelPartFeatureRendererStorageAccess originalModelPartSubmits = (ModelPartFeatureRendererStorageAccess) this.original.getModelPartSubmits(); + + this.submitFeatureMap( + modelPartSubmits.alrGetSolidModelPartSubmits(), + originalModelPartSubmits.alrGetSolidModelPartSubmits(), + collectedFeatures + ); + this.submitFeatureMap( + modelPartSubmits.alrGetTranslucentModelPartSubmits(), + originalModelPartSubmits.alrGetTranslucentModelPartSubmits(), + collectedFeatures + ); + + + CustomFeatureRendererStorageAccess customGeometrySubmits = (CustomFeatureRendererStorageAccess) this.getCustomGeometrySubmits(); + CustomFeatureRendererStorageAccess originalCustomGeometrySubmits = (CustomFeatureRendererStorageAccess) this.original.getCustomGeometrySubmits(); + + this.submitFeatureMap( + customGeometrySubmits.alrGetTranslucentCustomGeometrySubmits(), + originalCustomGeometrySubmits.alrGetTranslucentCustomGeometrySubmits(), + collectedFeatures + ); + this.submitFeatureMap( + customGeometrySubmits.alrGetSolidCustomGeometrySubmits(), + originalCustomGeometrySubmits.alrGetSolidCustomGeometrySubmits(), + collectedFeatures + ); + + ((SubmitNodeCollectionAccess) this.original).alrSetWasUsed(true); + + this.culler.submitFeatureKey(this.currentKey, collectedFeatures); + } + + private void submitFeatureList(List features, List target, List collectedFeatures) { + collectedFeatures.addAll(features); + target.addAll(features); + } + + private void submitFeatureMap( + Map> src, + Map> dst, + List collectedFeatures + ) { + for (Map.Entry> entry : src.entrySet()) { + dst.computeIfAbsent(entry.getKey(), _ -> new ArrayList<>()).addAll(entry.getValue()); + collectedFeatures.addAll(entry.getValue()); + } + } + +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionSubmitNodeStorage.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionSubmitNodeStorage.java new file mode 100644 index 00000000..22d8dc53 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/OcclusionSubmitNodeStorage.java @@ -0,0 +1,46 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion; + +import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap; +import net.minecraft.client.renderer.SubmitNodeCollection; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.SubmitNodeStorage; +import org.jspecify.annotations.NonNull; + +public class OcclusionSubmitNodeStorage extends SubmitNodeStorage { + private final Int2ObjectAVLTreeMap submitsPerOrder = new Int2ObjectAVLTreeMap<>(); + private final OcclusionCuller culler; + private final SubmitNodeCollector original; + + public OcclusionSubmitNodeStorage(OcclusionCuller culler, SubmitNodeCollector original) { + this.culler = culler; + this.original = original; + } + + @Override + @NonNull + public SubmitNodeCollection order(int order) { + SubmitNodeCollection originalCollection = (SubmitNodeCollection) original.order(order); + return this.submitsPerOrder.computeIfAbsent( + order, + _ -> new OcclusionSubmitNodeCollection(this, this.culler, originalCollection) + ); + } + + public void beginOcclusionRecord(OcclusionKey occlusionKey) { + this.beginOcclusionRecord(occlusionKey, 0); + } + + public void endOcclusionRecord() { + this.endOcclusionRecord(0); + } + + public void beginOcclusionRecord(OcclusionKey occlusionKey, int order) { + SubmitNodeCollection collection = this.order(order); + ((OcclusionSubmitNodeCollection) collection).beginOcclusionRecord(occlusionKey); + } + + public void endOcclusionRecord(int order) { + SubmitNodeCollection collection = this.order(order); + ((OcclusionSubmitNodeCollection) collection).endOcclusionRecord(); + } +} 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..4fb3527c --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/HierarchicalZOcclusionCuller.java @@ -0,0 +1,126 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz; + +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.optimization.occlusion.CullingStatistics; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionCuller; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionKey; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz.converter.DepthTexConverter; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz.spd.SinglePassDownsampler; +import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import lombok.Getter; +import lombok.Setter; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class HierarchicalZOcclusionCuller implements OcclusionCuller { + + + private final Minecraft minecraft; + private final ALRGpuDeviceExtension gpuDeviceExtension; + private final GpuDevice gpuDevice; + + private final SinglePassDownsampler downsampler; + private final DepthTexConverter depthTexConverter; + + private final Reference2ObjectMap keyAssociations = new Reference2ObjectLinkedOpenHashMap<>(); + + private final List occlusionKeys = new ArrayList<>(); + + /// update interval for z-buffer mipmap + /// + /// interval = 0 -> update every frame + /// + @Setter + @Getter + private int mipmapUpdateInterval = 0; + + private int mipmapUpdateCd = mipmapUpdateInterval; + + public HierarchicalZOcclusionCuller(ALRGpuDeviceExtension device) { + this.minecraft = Minecraft.getInstance(); + this.gpuDeviceExtension = device; + this.gpuDevice = (GpuDevice) device; + + RenderTarget mainRenderTarget = this.minecraft.getMainRenderTarget(); + + this.downsampler = new SinglePassDownsampler( + minecraft, + gpuDeviceExtension, + gpuDevice, + mainRenderTarget + ); + this.depthTexConverter = new DepthTexConverter( + gpuDevice, + gpuDeviceExtension, + mainRenderTarget, + this.downsampler.getPaddedWidth(), + this.downsampler.getPaddedHeight() + ); + } + + @Override + public void onResize(int width, int height) { + this.downsampler.onResize(width, height); + this.depthTexConverter.onResize( + width, + height, + this.downsampler.getPaddedWidth(), + this.downsampler.getPaddedHeight() + ); + } + + @Override + public void beforeExtract() { + } + + @Override + public void beginRenderingFrame() { + this.keyAssociations.clear(); + } + + @Override + public void submitFeatureKey(OcclusionKey key, List feature) { + for (Object o : feature) { + this.keyAssociations.put(o, key); + } + } + + @Override + public void processFeatures(CameraRenderState camera) { + RenderTarget mainRenderTarget = this.minecraft.getMainRenderTarget(); + CommandEncoder commandEncoder = this.gpuDevice.createCommandEncoder(); + GpuTexture texture = this.depthTexConverter.runConvert( + commandEncoder, + mainRenderTarget.getDepthTexture() + ); + this.downsampler.spdDispatch(commandEncoder, texture); + } + + @Override + public boolean isEmpty() { + return this.keyAssociations.isEmpty(); + } + + @Override + public @Nullable CullingStatistics collectStatistics() { + return null; + } + + @Override + public boolean shouldDraw(Object feature) { + return true; + } + + @Override + public void close() throws Exception { + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/converter/ConvertDepthParamsUbo.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/converter/ConvertDepthParamsUbo.java new file mode 100644 index 00000000..386ec727 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/converter/ConvertDepthParamsUbo.java @@ -0,0 +1,51 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz.converter; + +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.Vector2f; + +/// ```glsl +/// layout(std140, binding = 0) uniform ConvertParam { +/// int uSrcWidth; +/// int uSrcHeight; +/// int uPaddedWidth; +/// int uPaddedHeight; +/// float uPadValue; +/// }; +/// ``` +@Getter +@Setter +@ApiStatus.Internal +public class ConvertDepthParamsUbo extends BufferObject { + + public static final BufferObjectLayoutDefinition DEFINITION = BufferObjectLayoutDefinition.create( + BufferObjectLayoutEntry.ofInt().forGetter(ConvertDepthParamsUbo::getSrcWidth).build(), + BufferObjectLayoutEntry.ofInt().forGetter(ConvertDepthParamsUbo::getSrcHeight).build(), + BufferObjectLayoutEntry.ofInt().forGetter(ConvertDepthParamsUbo::getPaddedWidth).build(), + BufferObjectLayoutEntry.ofInt().forGetter(ConvertDepthParamsUbo::getPaddedHeight).build(), + BufferObjectLayoutEntry.ofFloat().forGetter(ConvertDepthParamsUbo::getPadValue).build() + ); + + public static final int SIZE = DEFINITION.size(BufferLayout.STD140); + + private int srcWidth; + private int srcHeight; + private int paddedWidth; + private int paddedHeight; + 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/converter/DepthTexConverter.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/converter/DepthTexConverter.java new file mode 100644 index 00000000..bd8d60f3 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/converter/DepthTexConverter.java @@ -0,0 +1,131 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz.converter; + +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.AddressMode; +import com.mojang.blaze3d.textures.FilterMode; +import com.mojang.blaze3d.textures.GpuSampler; +import com.mojang.blaze3d.textures.GpuTexture; +import dev.anvilcraft.lib.v2.rendering.ALRComputePipelines; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRCommandEncoderExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ExtendedTextureFormat; +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.ALRComputePipeline; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.compute.pipeline.bindings.TextureBinding; +import lombok.Getter; +import net.minecraft.util.Mth; + +import java.util.List; +import java.util.OptionalDouble; + +/// Accepts a DEPTH_32F texture and convert it to R32F format texture with padding +public class DepthTexConverter { + + private final GpuDevice gpuDevice; + private final ALRGpuDeviceExtension gpuDeviceExtension; + + private final ConvertDepthParamsUbo convertParams = new ConvertDepthParamsUbo(); + + private final GpuBuffer convertParamsBuffer; + private final GpuSampler convertSampler; + + private int framebufferWidth; + private int framebufferHeight; + + private int paddedWidth; + private int paddedHeight; + + private int dispatchDimensionX; + private int dispatchDimensionY; + + @Getter + private GpuTexture output; + + public DepthTexConverter( + GpuDevice gpuDevice, + ALRGpuDeviceExtension gpuDeviceExtension, + RenderTarget mainRenderTarget, + int paddedWidth, + int paddedHeight + ) { + this.gpuDevice = gpuDevice; + this.gpuDeviceExtension = gpuDeviceExtension; + + this.convertParamsBuffer = gpuDevice.createBuffer( + () -> "SPD Depth Convert Params", + GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_UNIFORM, + ConvertDepthParamsUbo.SIZE + ); + + this.convertSampler = gpuDevice.createSampler( + AddressMode.CLAMP_TO_EDGE, + AddressMode.CLAMP_TO_EDGE, + FilterMode.LINEAR, + FilterMode.LINEAR, + 1, + OptionalDouble.empty() + ); + + this.onResize(mainRenderTarget.width, mainRenderTarget.height, paddedWidth, paddedHeight); + } + + public void onResize(int width, int height, int paddedWidth, int paddedHeight) { + this.framebufferWidth = width; + this.framebufferHeight = height; + + this.paddedWidth = paddedWidth; + this.paddedHeight = paddedHeight; + + this.dispatchDimensionX = Mth.ceil(paddedWidth / 32f); + this.dispatchDimensionY = Mth.ceil(paddedHeight / 32f); + + this.depthConvertSetup(this.gpuDevice.createCommandEncoder()); + + if (this.output != null) { + this.output.close(); + } + + this.output = this.gpuDeviceExtension.alrCreateExtendedTexture( + "HierarchicalZ Converted Depth Image", + GpuTexture.USAGE_COPY_SRC | GpuTexture.USAGE_COPY_DST | GpuTexture.USAGE_TEXTURE_BINDING, + ExtendedTextureFormat.R32F, + this.paddedWidth, + this.paddedHeight, + 1, + 1 + ); + } + + public GpuTexture runConvert(CommandEncoder commandEncoder, GpuTexture source) { + ALRCommandEncoderExtension commandEncoderExtension = (ALRCommandEncoderExtension) commandEncoder; + + try (ALRComputePass pass = commandEncoderExtension.alrCreateComputePass()) { + pass.setPipeline(ALRComputePipelines.DEPTH_CONVERT); + pass.bindUniformBlock(0, convertParamsBuffer.slice()); + pass.bindTexture(1, new TextureBinding.SamplerAndTexture(convertSampler, source)); + pass.bindImage(2, this.output, false, true); + + pass.dispatchWorkgroups(dispatchDimensionX, dispatchDimensionY, 1); + pass.memoryBarrier( + MemoryBarrierFlag.SHADER_IMAGE_ACCESS_BARRIER, + MemoryBarrierFlag.TEXTURE_UPDATE_BARRIER + ); + } + + return this.output; + } + + private void depthConvertSetup(CommandEncoder commandEncoder) { + this.convertParams.setSrcWidth(this.framebufferWidth); + this.convertParams.setSrcHeight(this.framebufferHeight); + this.convertParams.setPaddedWidth(this.paddedWidth); + this.convertParams.setPaddedHeight(this.paddedHeight); + this.convertParams.setPadValue(1); + + this.convertParams.upload(commandEncoder, this.convertParamsBuffer.slice()); + } +} diff --git a/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/spd/MipLayer.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/spd/MipLayer.java new file mode 100644 index 00000000..344d25e9 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/spd/MipLayer.java @@ -0,0 +1,25 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz.spd; + +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/spd/SPDConstantBuffer.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/spd/SPDConstantBuffer.java new file mode 100644 index 00000000..d2b61278 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/spd/SPDConstantBuffer.java @@ -0,0 +1,47 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz.spd; + +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/hiz/spd/SinglePassDownsampler.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/spd/SinglePassDownsampler.java new file mode 100644 index 00000000..a8be9196 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/hiz/spd/SinglePassDownsampler.java @@ -0,0 +1,225 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.hiz.spd; + +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.AddressMode; +import com.mojang.blaze3d.textures.FilterMode; +import com.mojang.blaze3d.textures.GpuSampler; +import com.mojang.blaze3d.textures.GpuTexture; +import dev.anvilcraft.lib.v2.rendering.ALRComputePipelines; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRCommandEncoderExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ALRGpuDeviceExtension; +import dev.anvilcraft.lib.v2.rendering.extension.blaze3d.ExtendedTextureFormat; +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.bindings.TextureBinding; +import dev.anvilcraft.lib.v2.rendering.foundation.buffers.GpuBufferConstants; +import dev.anvilcraft.lib.v2.rendering.util.MemoryAccess; +import lombok.Getter; +import net.minecraft.client.Minecraft; +import net.minecraft.util.Mth; +import org.joml.Vector2f; +import org.lwjgl.system.MemoryStack; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.OptionalDouble; + +/// ## FidelityFX Single Pass Downsampler 2.2 +/// FidelityFX SPD downsamples the input texture with a user-defined 2x2 kernel using LDS for intermediate storage. Optionally, wave operations can be used to share data between threads as additional optimization. +/// +/// A single thread group downsamples a 64x64 tile of the input texture to 1x1. Afterwards, a global atomic counter is incremented. The thread group will then check the global atomic - if it's equal to the number of tiles, it processes the last tile as before, computing the last 6 MIP levels of the input texture. +/// +/// The last tile consists of the 1x1 output from all thread groups. +/// +/// This way it is possible to reduce a 4096x4096 texture to 1x1 in a single dispatch call. +public class SinglePassDownsampler { + /// 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 GpuBuffer spdParamsBuffer; + private final GpuBuffer spdGlobalAtomicCounterBuffer; + + private final GpuSampler inputSampler; + + private int framebufferWidth; + private int framebufferHeight; + + @Getter + private int paddedWidth; + @Getter + 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 SinglePassDownsampler( + Minecraft minecraft, + ALRGpuDeviceExtension gpuDeviceExtension, + GpuDevice gpuDevice, + RenderTarget mainRenderTarget + ) { + this.minecraft = minecraft; + this.gpuDeviceExtension = gpuDeviceExtension; + this.gpuDevice = gpuDevice; + + this.spdParamsBuffer = gpuDevice.createBuffer( + () -> "SPD Constant Buffer", + GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_UNIFORM, + SPDConstantBuffer.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.inputSampler = gpuDevice.createSampler( + AddressMode.CLAMP_TO_EDGE, + AddressMode.CLAMP_TO_EDGE, + FilterMode.LINEAR, + FilterMode.LINEAR, + 1, + OptionalDouble.empty() + ); + + + this.onResize(mainRenderTarget.width, mainRenderTarget.height); + + this.clearAtomicCounter(); + } + + 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); + } + + /// 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 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); + } + } + + public void spdDispatch(CommandEncoder commandEncoder, GpuTexture source) { + + } + + public void spdDispathBindless(CommandEncoder commandEncoder, GpuTexture source) { + ALRCommandEncoderExtension commandEncoderExtension = (ALRCommandEncoderExtension) commandEncoder; + List textures = new ArrayList<>(13); + GpuTexture midTex; + textures.add(source); + textures.addAll(Arrays.asList(mipTextures)); + if (textures.size() < 7) { + midTex = source; + } else { + midTex = textures.get(6); + } + while (textures.size() < 13) { + textures.add(source); + } + try (ALRComputePass pass = commandEncoderExtension.alrCreateComputePass()) { + pass.setPipeline(ALRComputePipelines.FFX_SPD_DOWNSAMPLE_PASS); + pass.bindUniformBlock(0, spdParamsBuffer.slice()); + pass.bindTexture(0, new TextureBinding.SamplerAndTexture(this.inputSampler, source)); + pass.bindShaderStorage(0, spdGlobalAtomicCounterBuffer.slice()); + pass.bindImage(0, midTex, true, true); + pass.bindBindlessImageArray(textures, true, true); + + pass.dispatchWorkgroups(dispatchDimensionX, dispatchDimensionY, 1); + + pass.memoryBarrier( + MemoryBarrierFlag.SHADER_IMAGE_ACCESS_BARRIER, + MemoryBarrierFlag.BUFFER_UPDATE_BARRIER, + MemoryBarrierFlag.SHADER_STORAGE_BARRIER + ); + } + } + + 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/noop/NoOpOcclusionCuller.java b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/noop/NoOpOcclusionCuller.java new file mode 100644 index 00000000..e29a12fc --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/noop/NoOpOcclusionCuller.java @@ -0,0 +1,56 @@ +package dev.anvilcraft.lib.v2.rendering.optimization.occlusion.noop; + +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.CullingStatistics; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionCuller; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionKey; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import org.jspecify.annotations.Nullable; + +import java.util.List; + +public class NoOpOcclusionCuller implements OcclusionCuller { + @Override + public void onResize(int newWidth, int newHeight) { + + } + + @Override + public void beforeExtract() { + + } + + @Override + public void beginRenderingFrame() { + + } + + @Override + public void submitFeatureKey(OcclusionKey key, List feature) { + + } + + @Override + public void processFeatures(CameraRenderState camera) { + + } + + @Override + public boolean shouldDraw(Object feature) { + return true; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public @Nullable CullingStatistics collectStatistics() { + return null; + } + + @Override + public void close() throws Exception { + + } +} 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..40bf7ee6 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/FrameState.java @@ -0,0 +1,182 @@ +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.ALROptions; +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.CullingStatistics; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionKey; +import it.unimi.dsi.fastutil.objects.Reference2LongLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2LongMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +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 org.jspecify.annotations.Nullable; + +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 Reference2ObjectMap keyAssociations = new Reference2ObjectLinkedOpenHashMap<>(); + private final List cameraInside = new ArrayList<>(); + private final List frustumCulled = new ArrayList<>(); + private final GpuQueryOcclusionCuller owner; + + private int total = 0; + private int culled = 0; + private int features = 0; + + public FrameState(GpuQueryOcclusionCuller owner) { + this.owner = owner; + } + + public void addKey(OcclusionKey key, Object feature) { + this.keyAssociations.put(feature, key); + features++; + GpuQueryObject gpuSamplesQuery = this.keySamplesMap.get(key); + if (gpuSamplesQuery == null) { + this.keySamplesMap.put(key, owner.acquireQuery()); + total++; + } + } + + public boolean shouldDraw(OcclusionKey key) { + if (cameraInside.contains(key)) return true; + if (ALROptions.OCCLUSION_QUERY_USE_FRUSTUM_PRE_PASS && frustumCulled.contains(key)) return false; + return results.getOrDefault(key, 1) > 0; + } + + public OcclusionKey getKey(Object feature) { + return this.keyAssociations.get(feature); + } + + public void fetchResults() { + for (Map.Entry entry : keySamplesMap.entrySet()) { + long value = entry.getValue().getValue(); + if (value <= 0) { + this.culled++; + } + results.put(entry.getKey(), value); + } + } + + @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()) { + OcclusionKey key = entry.getKey(); + if (key.getBoundingBox().contains(camera.pos)) { + cameraInside.add(key); + continue; + } + if (ALROptions.OCCLUSION_QUERY_USE_FRUSTUM_PRE_PASS && !camera.cullFrustum.isVisible(key.getBoundingBox())) { + this.frustumCulled.add(key); + } + QueryInstance query = this.owner.acquireInstance(); + query.prepareTransform(key, entry.getValue(), camera); + queries.add(query); + } + + FullTransformsUbo[] transforms = new FullTransformsUbo[queries.size()]; + for (int i = 0; i < queries.size(); i++) { + if (!queries.get(i).isCameraInside()) { + transforms[i] = queries.get(i).transformsUbo(); + } + } + + DynamicUniformStorage dynamicStorage = this.owner.getTransformsDynamicStorage(); + + GpuBufferSlice[] gpuBufferSlices = dynamicStorage.writeUniforms(transforms); + for (int i = 0; i < queries.size(); i++) { + if (!queries.get(i).isCameraInside()) { + 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) { + if (query.isCameraInside()) continue; + 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); + } + } + + public boolean isEmpty() { + return this.keyAssociations.isEmpty(); + } + + public CullingStatistics createStatistics() { + int rendered = total - culled; + return new CullingStatistics( + total, + this.frustumCulled.size(), + this.cameraInside.size(), + culled, + rendered, + features, + null + ); + } +} 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..9dae917e --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/GpuQueryOcclusionCuller.java @@ -0,0 +1,152 @@ +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.CullingStatistics; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionCuller; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionKey; +import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import lombok.Getter; +import net.minecraft.client.renderer.DynamicUniformStorage; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/// ### 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 +/// - Especially, if camera is inside a bounding box, the corresponding feature will always being drawn +/// - 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 beforeExtract() { + + } + + @Override + public void beginRenderingFrame() { + 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, List feature) { + for (Object o : feature) { + this.currentFrameState.addKey(key, o); + } + + } + + @Override + public void processFeatures(CameraRenderState camera) { + this.currentFrameState.runQueries(camera); + } + + @Override + public boolean shouldDraw(Object feature) { + if (previousFrameState == null) { + return true; + } + OcclusionKey key = this.currentFrameState.getKey(feature); + return this.previousFrameState.shouldDraw(key); + } + + @Override + public boolean isEmpty() { + if (this.previousFrameState == null) { + return true; + } + return this.previousFrameState.isEmpty(); + } + + @Override + public @Nullable CullingStatistics collectStatistics() { + if (this.previousFrameState == null) { + return null; + } + return this.previousFrameState.createStatistics(); + } + + 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); + } + + @Override + public void close() throws Exception { + + } +} 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..9d44c969 --- /dev/null +++ b/module.rendering/src/main/java/dev/anvilcraft/lib/v2/rendering/optimization/occlusion/query/QueryInstance.java @@ -0,0 +1,245 @@ +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 lombok.Getter; +import lombok.Setter; +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; + + @Setter + @Getter + private boolean cameraInside = 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 cameraViewMat = this.transformsUbo.getCameraViewMat(); + cameraViewMat.set(camera.viewRotationMatrix); + + Matrix4f modelViewMat = this.transformsUbo.getModelViewMat(); + modelViewMat.identity(); + + 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. + modelViewMat.translate(min) + .scale( + max.x - min.x, + max.y - min.y, + max.z - min.z + ); + } + + @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/anvillib_rendering.mixins.json b/module.rendering/src/main/resources/anvillib_rendering.mixins.json index f49602ff..7fed162a 100644 --- a/module.rendering/src/main/resources/anvillib_rendering.mixins.json +++ b/module.rendering/src/main/resources/anvillib_rendering.mixins.json @@ -13,6 +13,11 @@ "ItemStackRenderStateMixin", "MinecraftMixin", "RenderTypeMixin", + "accessors.CustomFeatureRendererStorageAccess", + "accessors.ModelFeatureRendererStorageAccess", + "accessors.ModelPartFeatureRendererStorageAccess", + "accessors.NameTagFeatureRendererStorageAccess", + "accessors.SubmitNodeCollectionAccess", "blaze3d.CommandEncoderMixin", "blaze3d.GpuDeviceMixin", "blaze3d.gl.DirectStateAccessMixin$Emulated", @@ -20,7 +25,20 @@ "blaze3d.gl.GlDebugLabelMixin$Core", "blaze3d.gl.GlDebugLabelMixin$Ext", "blaze3d.gl.GlDebugLabelMixin$Self", - "blaze3d.gl.GlDeviceMixin" + "blaze3d.gl.GlDeviceMixin", + "blaze3d.gl.GlProgramMixin", + "features.BlockFeatureRendererMixin", + "features.CustomFeatureRendererMixin", + "features.ExtendedBlockFeatureRendererMixin", + "features.FlameFeatureRendererMixin", + "features.ItemFeatureRendererMixin", + "features.LeashFeatureRendererMixin", + "features.ModelFeatureRendererMixin", + "features.ModelPartFeatureRendererMixin", + "features.NameTagFeatureRendererMixin", + "features.ParticleFeatureRendererMixin", + "features.ShadowFeatureRendererMixin", + "features.TextFeatureRendererMixin" ], "injectors": { "defaultRequire": 1 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..f8256feb --- /dev/null +++ b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/compute/depth_convert.csh @@ -0,0 +1,32 @@ +#version 460 core + +layout(local_size_x = 32, local_size_y = 32, local_size_z = 1) in; + +layout(std140, binding = 0) uniform ConvertParam { + int uSrcWidth; + int uSrcHeight; + int uPaddedWidth; + int uPaddedHeight; + float uPadValue; +}; + +layout(binding = 1) uniform sampler2D Input; + +layout(binding = 2, r32f) writeonly uniform image2D Output; + +void main() { + ivec2 idx = ivec2(gl_GlobalInvocationID.xy); + + if (idx.x >= uPaddedWidth || idx.y >= uPaddedHeight) { + return; + } + + vec4 result; + + if (idx.x >= uSrcWidth || idx.y >= uSrcHeight) { + 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..5774f1d9 --- /dev/null +++ b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/compute/ffx_spd_downsample_pass.csh @@ -0,0 +1,682 @@ +#version 460 core + +// 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 + +#ifndef SPD_MAX_MIP_LEVELS +#define SPD_MAX_MIP_LEVELS 12 +#endif + +#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 = 0) uniform sampler2D r_input_downsample_src; + +// UAV declarations +// replace huge binding slot in original shader 2000 to 2 +layout (/*set = 0, */binding = 0, 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 = 1, 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 = 2, 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/compute/ffx_spd_downsample_pass_bindless.csh b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/compute/ffx_spd_downsample_pass_bindless.csh new file mode 100644 index 00000000..688d137b --- /dev/null +++ b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/compute/ffx_spd_downsample_pass_bindless.csh @@ -0,0 +1,682 @@ +#version 460 core + +// 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 + +#ifndef SPD_MAX_MIP_LEVELS +#define SPD_MAX_MIP_LEVELS 12 +#endif + +#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 = 0) uniform sampler2D r_input_downsample_src; + +// UAV declarations +// replace huge binding slot in original shader 2000 to 2 +layout (/*set = 0, */binding = 0, 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 = 1, 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, */bindless_image, 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..9476bce2 --- /dev/null +++ b/module.rendering/src/main/resources/assets/anvillib_rendering/shaders/core/occlusion_query.vsh @@ -0,0 +1,13 @@ +#version 330 + +layout(std140) uniform Transforms { + mat4 ProjMat; + mat4 CameraViewMat; + mat4 ModelViewMat; +}; + +in vec3 Position; + +void main() { + gl_Position = ProjMat * CameraViewMat * 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/generated/resources/assets/anvillib_test/blockstates/test_occlusion.json b/module.test/src/generated/resources/assets/anvillib_test/blockstates/test_occlusion.json new file mode 100644 index 00000000..5a5d8888 --- /dev/null +++ b/module.test/src/generated/resources/assets/anvillib_test/blockstates/test_occlusion.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "minecraft:block/diamond_block" + } + } +} \ No newline at end of file diff --git a/module.test/src/generated/resources/assets/anvillib_test/items/test_occlusion.json b/module.test/src/generated/resources/assets/anvillib_test/items/test_occlusion.json new file mode 100644 index 00000000..05528085 --- /dev/null +++ b/module.test/src/generated/resources/assets/anvillib_test/items/test_occlusion.json @@ -0,0 +1,6 @@ +{ + "model": { + "type": "minecraft:model", + "model": "minecraft:block/diamond_block" + } +} \ No newline at end of file diff --git a/module.test/src/generated/resources/assets/anvillib_test/lang/en_ud.json b/module.test/src/generated/resources/assets/anvillib_test/lang/en_ud.json index 6243d07a..0b0ba086 100644 --- a/module.test/src/generated/resources/assets/anvillib_test/lang/en_ud.json +++ b/module.test/src/generated/resources/assets/anvillib_test/lang/en_ud.json @@ -29,9 +29,11 @@ "anvillib_test.configuration.title": "uoᴉʇɐɹnᵷᴉɟuoƆ ʇsǝ⟘ qᴉꞁꞁᴉʌuⱯ", "block.anvillib_test.test_bloom": "ɯooꞁᗺ ʇsǝ⟘", "block.anvillib_test.test_cached_rendering": "ᵷuᴉɹǝpuǝᴚ pǝɥɔɐƆ ʇsǝ⟘", + "block.anvillib_test.test_occlusion": "uoᴉsnꞁɔɔO ʇsǝ⟘", "item.anvillib_test.test_bloom": "ɯooꞁᗺ ʇsǝ⟘", "item.anvillib_test.test_cached_rendering": "ᵷuᴉɹǝpuǝᴚ pǝɥɔɐƆ ʇsǝ⟘", "item.anvillib_test.test_item": "ɯǝʇI ʇsǝ⟘", + "item.anvillib_test.test_occlusion": "uoᴉsnꞁɔɔO ʇsǝ⟘", "itemGroup.anvillib_test.test_tab": "qɐ⟘ ʇsǝ⟘", "key.anvillib_test.wheel_hold": "pꞁoH ꞁǝǝɥM", "key.anvillib_test.wheel_hold_annular": "(ɹɐꞁnuuⱯ) pꞁoH ꞁǝǝɥM", diff --git a/module.test/src/generated/resources/assets/anvillib_test/lang/en_us.json b/module.test/src/generated/resources/assets/anvillib_test/lang/en_us.json index 9ff5b80e..768fe73d 100644 --- a/module.test/src/generated/resources/assets/anvillib_test/lang/en_us.json +++ b/module.test/src/generated/resources/assets/anvillib_test/lang/en_us.json @@ -29,9 +29,11 @@ "anvillib_test.configuration.title": "Anvillib Test Configuration", "block.anvillib_test.test_bloom": "Test Bloom", "block.anvillib_test.test_cached_rendering": "Test Cached Rendering", + "block.anvillib_test.test_occlusion": "Test Occlusion", "item.anvillib_test.test_bloom": "Test Bloom", "item.anvillib_test.test_cached_rendering": "Test Cached Rendering", "item.anvillib_test.test_item": "Test Item", + "item.anvillib_test.test_occlusion": "Test Occlusion", "itemGroup.anvillib_test.test_tab": "Test Tab", "key.anvillib_test.wheel_hold": "Wheel Hold", "key.anvillib_test.wheel_hold_annular": "Wheel Hold (Annular)", diff --git a/module.test/src/generated/resources/assets/minecraft/models/block/diamond_block.json b/module.test/src/generated/resources/assets/minecraft/models/block/diamond_block.json new file mode 100644 index 00000000..a021068b --- /dev/null +++ b/module.test/src/generated/resources/assets/minecraft/models/block/diamond_block.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "minecraft:block/diamond_block" + } +} \ No newline at end of file diff --git a/module.test/src/generated/resources/data/anvillib_test/loot_table/blocks/test_occlusion.json b/module.test/src/generated/resources/data/anvillib_test/loot_table/blocks/test_occlusion.json new file mode 100644 index 00000000..6bc9121d --- /dev/null +++ b/module.test/src/generated/resources/data/anvillib_test/loot_table/blocks/test_occlusion.json @@ -0,0 +1,21 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:survives_explosion" + } + ], + "entries": [ + { + "type": "minecraft:item", + "name": "anvillib_test:test_occlusion" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "anvillib_test:blocks/test_occlusion" +} \ No newline at end of file diff --git a/module.test/src/main/java/dev/anvilcraft/lib/v2/test/all/TestBlocks.java b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/all/TestBlocks.java index 11e7ee57..3f513f94 100644 --- a/module.test/src/main/java/dev/anvilcraft/lib/v2/test/all/TestBlocks.java +++ b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/all/TestBlocks.java @@ -6,6 +6,7 @@ import dev.anvilcraft.lib.v2.test.AnvilLibTest; import dev.anvilcraft.lib.v2.test.block.TestBloomBlock; import dev.anvilcraft.lib.v2.test.block.TestCachedRenderingBlock; +import dev.anvilcraft.lib.v2.test.block.TestOcclusionBlock; import net.minecraft.client.data.models.BlockModelGenerators; import net.minecraft.client.data.models.blockstates.MultiVariantGenerator; import net.minecraft.client.data.models.model.ModelTemplates; @@ -32,6 +33,17 @@ public class TestBlocks { .simpleItem() .register(); + public static final BlockEntry TEST_OCCLUSION = AnvilLibTest.REGISTRUM.block("test_occlusion", TestOcclusionBlock::new) + .properties(p -> p.noOcclusion().noCollision()) + .blockstate(() -> (ctx, provider) -> provider.blockStateOutput.accept( + BlockModelGenerators.createSimpleBlock( + ctx.get(), + BlockModelGenerators.plainVariant(TexturedModel.CUBE.create(Blocks.DIAMOND_BLOCK, provider.modelOutput)) + ) + )) + .simpleItem() + .register(); + public static final BlockEntry TEST_CACHED_RENDERING = AnvilLibTest.REGISTRUM .block("test_cached_rendering", TestCachedRenderingBlock::new) .properties(p -> p.noOcclusion().noCollision()) diff --git a/module.test/src/main/java/dev/anvilcraft/lib/v2/test/all/TestTiles.java b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/all/TestTiles.java index 2e80b5da..f26c916e 100644 --- a/module.test/src/main/java/dev/anvilcraft/lib/v2/test/all/TestTiles.java +++ b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/all/TestTiles.java @@ -4,7 +4,9 @@ import dev.anvilcraft.lib.v2.test.AnvilLibTest; import dev.anvilcraft.lib.v2.test.block.tile.TestBloomTile; import dev.anvilcraft.lib.v2.test.block.tile.TestCachedRenderingTile; +import dev.anvilcraft.lib.v2.test.block.tile.TestOcclusionTile; import dev.anvilcraft.lib.v2.test.client.tesr.TestBloomTESR; +import dev.anvilcraft.lib.v2.test.client.tesr.TestOcclusionTESR; public class TestTiles { public static final BlockEntityEntry TEST_BLOOM = AnvilLibTest.REGISTRUM @@ -13,6 +15,12 @@ public class TestTiles { .renderer(() -> TestBloomTESR::new) .register(); + public static final BlockEntityEntry TEST_OCCLUSION = AnvilLibTest.REGISTRUM + .blockEntity("test_occlusion", TestOcclusionTile::new) + .validBlock(TestBlocks.TEST_OCCLUSION) + .renderer(() -> TestOcclusionTESR::new) + .register(); + public static final BlockEntityEntry TEST_CACHED_RENDERING = AnvilLibTest.REGISTRUM .blockEntity("test_cached_rendering", TestCachedRenderingTile::new) .validBlock(TestBlocks.TEST_CACHED_RENDERING) diff --git a/module.test/src/main/java/dev/anvilcraft/lib/v2/test/block/TestOcclusionBlock.java b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/block/TestOcclusionBlock.java new file mode 100644 index 00000000..b639abfe --- /dev/null +++ b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/block/TestOcclusionBlock.java @@ -0,0 +1,34 @@ +package dev.anvilcraft.lib.v2.test.block; + +import com.mojang.serialization.MapCodec; +import dev.anvilcraft.lib.v2.test.all.TestTiles; +import dev.anvilcraft.lib.v2.test.block.tile.TestOcclusionTile; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import org.jspecify.annotations.Nullable; + +public class TestOcclusionBlock extends BaseEntityBlock { + public TestOcclusionBlock(BlockBehaviour.Properties properties) { + super(properties); + } + + @Override + protected MapCodec codec() { + return simpleCodec(TestBloomBlock::new); + } + + @Override + protected RenderShape getRenderShape(BlockState state) { + return RenderShape.INVISIBLE; + } + + @Override + public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return new TestOcclusionTile(TestTiles.TEST_OCCLUSION.get(),blockPos, blockState); + } +} + diff --git a/module.test/src/main/java/dev/anvilcraft/lib/v2/test/block/tile/TestOcclusionTile.java b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/block/tile/TestOcclusionTile.java new file mode 100644 index 00000000..5f4c8a2b --- /dev/null +++ b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/block/tile/TestOcclusionTile.java @@ -0,0 +1,23 @@ +package dev.anvilcraft.lib.v2.test.block.tile; + +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionKey; +import lombok.Getter; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; + +public class TestOcclusionTile extends BlockEntity { + + @Getter + private final OcclusionKey occlusionKey; + + public TestOcclusionTile(BlockEntityType type, BlockPos worldPosition, BlockState blockState) { + super(type, worldPosition, blockState); + this.occlusionKey = new OcclusionKey( + () -> "TestOcclusionTile@" + worldPosition.toShortString(), + new AABB(worldPosition) + ); + } +} 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..878c0356 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(); @@ -115,14 +115,10 @@ public float[] add(float[] input, float f) { addParam.upload(commandEncoder, addParamUBO.slice()); try (ALRComputePass pass = commandEncoderExtension.alrCreateComputePass()) { pass.setPipeline(TestPipelines.ADD); - pass.bindAll( - List.of( - addInputSSBO.slice(), - addOutputSSBO.slice(), - addParamUBO.slice(), - addOutputCounter.slice() - ) - ); + pass.bindShaderStorage(0, addInputSSBO.slice()); + pass.bindShaderStorage(1, addOutputSSBO.slice()); + pass.bindUniformBlock(2, addParamUBO.slice()); + pass.bindAtomicCounter(3, addOutputCounter.slice()); pass.dispatchWorkgroups(Math.ceilDiv(input.length, 16), 1, 1); pass.memoryBarrier( MemoryBarrierFlag.SHADER_STORAGE_BARRIER, @@ -144,7 +140,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; @@ -177,13 +173,9 @@ public GpuTextureView computeBlur() { try (ALRComputePass pass = commandEncoderExtension.alrCreateComputePass()) { pass.setPipeline(TestPipelines.BLUR); - pass.bindAll( - List.of( - blurParamUBO.slice(), - new TextureBinding.SamplerAndTexture(theSampler, colorTexture), - outputTexture - ) - ); + pass.bindShaderStorage(0, addInputSSBO.slice()); + pass.bindTexture(1, new TextureBinding.SamplerAndTexture(theSampler, colorTexture)); + pass.bindImage(2, outputTexture, false, true); pass.dispatchWorkgroups(Math.ceilDiv(width, 16), Math.ceilDiv(height, 16), 1); pass.memoryBarrier( diff --git a/module.test/src/main/java/dev/anvilcraft/lib/v2/test/client/tesr/TestOcclusionTESR.java b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/client/tesr/TestOcclusionTESR.java new file mode 100644 index 00000000..ed5aba5e --- /dev/null +++ b/module.test/src/main/java/dev/anvilcraft/lib/v2/test/client/tesr/TestOcclusionTESR.java @@ -0,0 +1,86 @@ +package dev.anvilcraft.lib.v2.test.client.tesr; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.logging.annotations.MethodsReturnNonnullByDefault; +import dev.anvilcraft.lib.v2.rendering.ALROptimizations; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionCuller; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionKey; +import dev.anvilcraft.lib.v2.rendering.optimization.occlusion.OcclusionSubmitNodeStorage; +import dev.anvilcraft.lib.v2.test.block.tile.TestOcclusionTile; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; +import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import net.minecraft.client.renderer.item.ItemStackRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.util.LightCoordsUtil; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.Items; +import net.minecraft.world.phys.Vec3; +import org.jspecify.annotations.Nullable; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public class TestOcclusionTESR implements BlockEntityRenderer { + + public TestOcclusionTESR(BlockEntityRendererProvider.Context ignored) { + } + + + @Override + public RenderState createRenderState() { + return new RenderState(); + } + + @Override + public void extractRenderState( + TestOcclusionTile blockEntity, + RenderState state, + float partialTicks, + Vec3 cameraPosition, + ModelFeatureRenderer.@Nullable CrumblingOverlay breakProgress + ) { + BlockEntityRenderer.super.extractRenderState(blockEntity, state, partialTicks, cameraPosition, breakProgress); + state.occlusionKey = blockEntity.getOcclusionKey(); + } + + @Override + public void submit( + RenderState renderState, + PoseStack poseStack, + SubmitNodeCollector submitNodeCollector, + CameraRenderState cameraRenderState + ) { + OcclusionCuller occlusionCuller = ALROptimizations.getOcclusionCuller(); + OcclusionSubmitNodeStorage wrapped = occlusionCuller.wrapSubmitNodeStorage(submitNodeCollector); + Minecraft minecraft = Minecraft.getInstance(); + + wrapped.beginOcclusionRecord(renderState.occlusionKey); + + poseStack.pushPose(); + poseStack.translate(0.5, 0.5, 0.5); + + ItemStackRenderState isrs = new ItemStackRenderState(); + minecraft.getItemModelResolver().updateForTopItem( + isrs, + Items.CARROT.getDefaultInstance(), + ItemDisplayContext.FIXED, + minecraft.level, + minecraft.player, + 42 + ); + isrs.submit(poseStack, wrapped, LightCoordsUtil.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, 0); + poseStack.popPose(); + wrapped.endOcclusionRecord(); + } + + + public static class RenderState extends BlockEntityRenderState { + private OcclusionKey occlusionKey; + } +}