From c9d46e70e0dda4e43e3b4f0b5fa1dfd781a329ae Mon Sep 17 00:00:00 2001 From: Jason Mitchell Date: Sun, 27 Sep 2026 09:37:40 -0700 Subject: [PATCH] Update to Celeritas 2.5.12 with translucency sorting v3 (#2151) --- gradle/libs.versions.toml | 2 +- .../angelica/sdlgpu/SDLGPURenderBackend.java | 6 +- .../sdlgpu/resource/FBOClearTracker.java | 13 ++ .../sdlgpu/glsm/GlsmSdlSplashTargetTest.java | 1 + .../client/rendering/AngelicaFogService.java | 2 + .../angelica/config/AngelicaConfig.java | 4 + .../angelica/debug/DebugKeyHandler.java | 28 +++ .../gtnewhorizons/angelica/mixins/Mixins.java | 4 +- .../RenderSectionManagerAccessor.java | 3 - .../angelica/profiling/TracyFramePlots.java | 8 +- .../angelica/proxy/ClientProxy.java | 2 + .../AngelicaChunkBuilderMeshingTask.java | 4 +- .../celeritas/AngelicaChunkRenderer.java | 23 +-- .../celeritas/AngelicaChunkTracker.java | 62 ------ .../AngelicaRenderPassConfiguration.java | 41 ++-- .../AngelicaRenderSectionManager.java | 63 ++---- .../CeleritasDebugScreenHandler.java | 31 +-- .../rendering/celeritas/CeleritasSetup.java | 4 - .../celeritas/CeleritasWorldRenderer.java | 179 ++++++++--------- .../celeritas/CubeStatusTracker.java | 7 +- .../rendering/celeritas/SectionAgeMath.java | 27 --- .../rendering/celeritas/TerrainDrawStats.java | 26 +++ .../celeritas/api/IrisShaderProvider.java | 2 - .../rendering/culling/GpuTerrainCuller.java | 5 +- .../client/gui/SodiumGameOptionPages.java | 6 + .../IrisCeleritasShaderProvider.java | 6 - .../DeferredWorldRenderingPipeline.java | 5 - .../iris/pipeline/ShadowRenderer.java | 96 +++++---- .../iris/pipeline/WorldRenderingPipeline.java | 1 - .../transform/RwImageStoreExtractor.java | 16 -- .../iris/shaderpack/PackShadowDirectives.java | 7 + ...embeddium.impl.render.chunk.fog.FogService | 1 - .../resources/assets/angelica/lang/en_US.lang | 6 +- .../shaders/blocks/block_layer_opaque.fsh | 52 +++-- .../MixinDefaultChunkShaderInterface.java | 84 -------- .../celeritas/terrain/MixinRenderGlobal.java | 12 +- .../terrain/MixinRenderRegionManager.java | 31 +++ .../terrain/MixinRenderSectionManager.java | 9 - .../celeritas/terrain/MixinShaderLoader.java | 20 ++ .../terrain/MixinSharedQuadIndexBuffer.java | 18 ++ .../celeritas/terrain/MixinWorldClient.java | 3 +- .../early/shaders/MixinEntityRenderer.java | 10 +- .../celeritas/SectionAgeMathTest.java | 58 ------ .../TerrainFragmentShaderDriftTest.java | 73 +++++++ .../celeritas/TerrainShaderLinkTest.java | 124 ++++++++++++ .../TreeSortCameraBookkeepingGLTest.java | 184 ++++++++++++++++++ .../culling/IndirectCullDrawParityTest.java | 2 - .../culling/TerrainPassPreparationTest.java | 28 +++ .../transform/RwImageStoreExtractorTest.java | 23 +-- .../data/SectionRenderDataLayoutTest.java | 4 +- 50 files changed, 870 insertions(+), 556 deletions(-) create mode 100644 src/main/java/com/gtnewhorizons/angelica/debug/DebugKeyHandler.java delete mode 100644 src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkTracker.java delete mode 100644 src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/SectionAgeMath.java delete mode 100644 src/main/resources/META-INF/services/org.embeddedt.embeddium.impl.render.chunk.fog.FogService delete mode 100644 src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinDefaultChunkShaderInterface.java create mode 100644 src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderRegionManager.java create mode 100644 src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinShaderLoader.java create mode 100644 src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinSharedQuadIndexBuffer.java delete mode 100644 src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/SectionAgeMathTest.java create mode 100644 src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainFragmentShaderDriftTest.java create mode 100644 src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainShaderLinkTest.java create mode 100644 src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TreeSortCameraBookkeepingGLTest.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5c176dc76..0ab511710 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ asmCompile = "5.0.3" lwjgl3 = "3.4.2-SNAPSHOT" lwjgl3ify = "3.0.31" -celeritas = "2.5.11-GTNH" +celeritas = "2.5.12-GTNH" gtnhlib = "0.11.36" eventbus = "7.0-beta.15" glslTransformLib = "0.2.0-33.g3ec381e" diff --git a/sdl-gpu/src/main/java/com/gtnewhorizons/angelica/sdlgpu/SDLGPURenderBackend.java b/sdl-gpu/src/main/java/com/gtnewhorizons/angelica/sdlgpu/SDLGPURenderBackend.java index 7ea4ca52e..fbabba35f 100644 --- a/sdl-gpu/src/main/java/com/gtnewhorizons/angelica/sdlgpu/SDLGPURenderBackend.java +++ b/sdl-gpu/src/main/java/com/gtnewhorizons/angelica/sdlgpu/SDLGPURenderBackend.java @@ -542,11 +542,12 @@ public class SDLGPURenderBackend extends RenderBackend { @Override public void onRenderThreadReleased(Thread t) { if (shutdown || t == GLStateManager.getMainThread()) return; + final ContextState st = tlState.get(); if (frameManager.isFrameActive()) { + if (st != null) fboClearTracker.materializeAllPendingClears(st); frameManager.endFrame(); } frameManager.releaseThreadState(); - final ContextState st = tlState.get(); if (st != null) { tlState.remove(); registeredStates.remove(st); @@ -601,6 +602,7 @@ public class SDLGPURenderBackend extends RenderBackend { @Override public boolean handleSwapBuffers() { final OffscreenTarget splash = splashTarget; if (splash != null && splash.isFor(s()) && Thread.currentThread() != GLStateManager.getMainThread()) { + if (frameManager.isFrameActive()) fboClearTracker.materializeAllPendingClears(s()); endFrameUploadFlush(); frameManager.endFrame(); SplashDispatcher.signalFrameReady((int) s().viewportW, (int) s().viewportH); @@ -728,6 +730,7 @@ public class SDLGPURenderBackend extends RenderBackend { frameManager.markFrameEmpty(f); } + if (f.frameActive) fboClearTracker.materializeAllPendingClears(s()); awaitUploadFlush(); if (SystemProperties.FFP_TRACE) ffpTrace.frameEnd(f); frameManager.endFrame(); @@ -740,6 +743,7 @@ public class SDLGPURenderBackend extends RenderBackend { @Override public void onPreSwapchainInvalidatingChange(Object change) { super.onPreSwapchainInvalidatingChange(change); if (presenter != null) presenter.drain(); + if (frameManager.isFrameActive()) fboClearTracker.materializeAllPendingClears(s()); endFrameUploadFlush(); if (frameManager.isFrameActive()) { frameManager.endFrame(); diff --git a/sdl-gpu/src/main/java/com/gtnewhorizons/angelica/sdlgpu/resource/FBOClearTracker.java b/sdl-gpu/src/main/java/com/gtnewhorizons/angelica/sdlgpu/resource/FBOClearTracker.java index 1fd8f5a0b..fe2a24a1e 100644 --- a/sdl-gpu/src/main/java/com/gtnewhorizons/angelica/sdlgpu/resource/FBOClearTracker.java +++ b/sdl-gpu/src/main/java/com/gtnewhorizons/angelica/sdlgpu/resource/FBOClearTracker.java @@ -283,6 +283,19 @@ public final class FBOClearTracker { } } + public void materializeAllPendingClears(ContextState st) { + if (st.pendingColorTextures.isEmpty() && st.pendingDepthTextures.isEmpty() && st.pendingStencilTextures.isEmpty()) return; + final LongArrayList handles = st.samplerFlushColorHandles; + handles.clear(); + handles.addAll(st.pendingColorTextures); + handles.addAll(st.pendingDepthTextures); + handles.addAll(st.pendingStencilTextures); + for (int i = 0; i < handles.size(); i++) { + materializePendingClearForTexture(st, handles.getLong(i)); + } + handles.clear(); + } + public void scrubPendingClearsForTexture(ContextState st, int glId) { final long handle = resourceManager.getTextureHandle(glId); if (handle == 0) return; diff --git a/sdl-gpu/src/test/java/com/gtnewhorizons/angelica/sdlgpu/glsm/GlsmSdlSplashTargetTest.java b/sdl-gpu/src/test/java/com/gtnewhorizons/angelica/sdlgpu/glsm/GlsmSdlSplashTargetTest.java index f0ebdbce3..8279f6933 100644 --- a/sdl-gpu/src/test/java/com/gtnewhorizons/angelica/sdlgpu/glsm/GlsmSdlSplashTargetTest.java +++ b/sdl-gpu/src/test/java/com/gtnewhorizons/angelica/sdlgpu/glsm/GlsmSdlSplashTargetTest.java @@ -30,6 +30,7 @@ class GlsmSdlSplashTargetTest { final OffscreenTarget target = createSplashTarget(backend); Reflect.set(backend, "splashTarget", target); try { + GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0); TestThreads.run("SplashTarget-Seed-Thread", () -> { Reflect.invoke(backend, "enterContext", new Class[]{boolean.class}, true); GlsmSdlHeadlessRig.solidQuad(0f, 1f, 0f); diff --git a/src/main/java/com/gtnewhorizons/angelica/client/rendering/AngelicaFogService.java b/src/main/java/com/gtnewhorizons/angelica/client/rendering/AngelicaFogService.java index b14b05bfe..b5daa5d6c 100644 --- a/src/main/java/com/gtnewhorizons/angelica/client/rendering/AngelicaFogService.java +++ b/src/main/java/com/gtnewhorizons/angelica/client/rendering/AngelicaFogService.java @@ -5,6 +5,8 @@ import org.embeddedt.embeddium.impl.render.chunk.fog.FogService; import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkFogMode; public class AngelicaFogService implements FogService { + public static final AngelicaFogService INSTANCE = new AngelicaFogService(); + private final float[] fogColorArray = new float[4]; @Override diff --git a/src/main/java/com/gtnewhorizons/angelica/config/AngelicaConfig.java b/src/main/java/com/gtnewhorizons/angelica/config/AngelicaConfig.java index 1a981e642..96df8750f 100644 --- a/src/main/java/com/gtnewhorizons/angelica/config/AngelicaConfig.java +++ b/src/main/java/com/gtnewhorizons/angelica/config/AngelicaConfig.java @@ -419,6 +419,10 @@ public class AngelicaConfig { @Config.DefaultBoolean(false) public static boolean disableF3Additions; + @Config.Comment("Shows developer counters (FFP, streaming, TESR, transfer) on the F3 screen. Toggle in game with F3+V.") + @Config.DefaultBoolean(false) + public static boolean verboseF3; + @Config.Comment("Replaces various FFP uploads with statically allocated VBO's.") @Config.DefaultBoolean(true) public static boolean replaceFFPUploads; diff --git a/src/main/java/com/gtnewhorizons/angelica/debug/DebugKeyHandler.java b/src/main/java/com/gtnewhorizons/angelica/debug/DebugKeyHandler.java new file mode 100644 index 000000000..623c3ad14 --- /dev/null +++ b/src/main/java/com/gtnewhorizons/angelica/debug/DebugKeyHandler.java @@ -0,0 +1,28 @@ +package com.gtnewhorizons.angelica.debug; + +import com.gtnewhorizon.gtnhlib.GTNHLib; +import com.gtnewhorizon.gtnhlib.config.ConfigurationManager; +import com.gtnewhorizons.angelica.config.AngelicaConfig; +import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.InputEvent; +import net.minecraft.client.Minecraft; +import net.minecraft.util.StatCollector; +import org.lwjgl.input.Keyboard; + +/** F3+V toggles {@link AngelicaConfig#verboseF3} */ +public class DebugKeyHandler { + + @SubscribeEvent + public void onKey(InputEvent.KeyInputEvent event) { + if (!Keyboard.getEventKeyState() || Keyboard.getEventKey() != Keyboard.KEY_V || !Keyboard.isKeyDown(Keyboard.KEY_F3) + || Minecraft.getMinecraft().currentScreen != null) { + return; + } + + AngelicaConfig.verboseF3 = !AngelicaConfig.verboseF3; + ConfigurationManager.save(AngelicaConfig.class); + CeleritasDebugScreenHandler.invalidate(); + GTNHLib.proxy.addDebugToChat(StatCollector.translateToLocal(AngelicaConfig.verboseF3 ? "angelica.debug.verbose.on" : "angelica.debug.verbose.off")); + } +} diff --git a/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java b/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java index a470037f3..7ee54b6db 100644 --- a/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java +++ b/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java @@ -419,7 +419,9 @@ public enum Mixins implements IMixins { , "celeritas.terrain.MixinWorld_AwaitingDescriptor" , "celeritas.terrain.MixinRenderRegion" , "celeritas.terrain.MixinSectionRenderDataStorage" - , "celeritas.terrain.MixinDefaultChunkShaderInterface" + , "celeritas.terrain.MixinSharedQuadIndexBuffer" + , "celeritas.terrain.MixinRenderRegionManager" + , "celeritas.terrain.MixinShaderLoader" ) ), diff --git a/src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/RenderSectionManagerAccessor.java b/src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/RenderSectionManagerAccessor.java index b614c83fd..c7c92b019 100644 --- a/src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/RenderSectionManagerAccessor.java +++ b/src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/RenderSectionManagerAccessor.java @@ -1,14 +1,11 @@ package com.gtnewhorizons.angelica.mixins.interfaces; -import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; -import org.embeddedt.embeddium.impl.render.chunk.RenderSection; import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkTaskOutput; import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult; import java.util.concurrent.ConcurrentLinkedDeque; public interface RenderSectionManagerAccessor { - Long2ReferenceMap angelica$getSectionByPosition(); ConcurrentLinkedDeque angelica$getAsyncSubmittedTasks(); ConcurrentLinkedDeque> angelica$getBuildResults(); } diff --git a/src/main/java/com/gtnewhorizons/angelica/profiling/TracyFramePlots.java b/src/main/java/com/gtnewhorizons/angelica/profiling/TracyFramePlots.java index 98cc439d9..edf1b794c 100644 --- a/src/main/java/com/gtnewhorizons/angelica/profiling/TracyFramePlots.java +++ b/src/main/java/com/gtnewhorizons/angelica/profiling/TracyFramePlots.java @@ -22,8 +22,6 @@ import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer; import net.coderbot.iris.pipeline.ShadowRenderer; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.WorldRenderer; -import org.embeddedt.embeddium.impl.render.chunk.SharedQuadIndexBuffer; -import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegionManager; public final class TracyFramePlots { private static final long P_ALLOC_RATE = Tracy.plotHandle("allocRate", TracyBackend.PLOT_FORMAT_MEMORY); @@ -267,9 +265,9 @@ public final class TracyFramePlots { TESR_BAILS[reason.ordinal()].plot(batcher.statBail(reason)); } - P_MESH_UPLOADED_SECTIONS.plot(RenderRegionManager.getSectionsUploaded()); - P_MESH_UPLOADED_BYTES.plot(RenderRegionManager.getBytesUploaded()); - P_DRAW_INDEX_BUFFER_GROWTHS.plot(SharedQuadIndexBuffer.getGrowths()); + P_MESH_UPLOADED_SECTIONS.plot(TerrainDrawStats.getSectionsUploaded()); + P_MESH_UPLOADED_BYTES.plot(TerrainDrawStats.getBytesUploaded()); + P_DRAW_INDEX_BUFFER_GROWTHS.plot(TerrainDrawStats.getIndexBufferGrowths()); Tracy.plotInt(P_MESH_META_SLOTS, GpuCulling.sectionMeta().getHighWaterMark()); Tracy.plotInt(P_MT_QUEUE_DEPTH, AngelicaRenderQueue.getQueueDepth()); Tracy.plotInt(P_MT_QUEUE_TASKS, AngelicaRenderQueue.getLastFrameTasksRan()); diff --git a/src/main/java/com/gtnewhorizons/angelica/proxy/ClientProxy.java b/src/main/java/com/gtnewhorizons/angelica/proxy/ClientProxy.java index d7c1a13c1..8441b6797 100644 --- a/src/main/java/com/gtnewhorizons/angelica/proxy/ClientProxy.java +++ b/src/main/java/com/gtnewhorizons/angelica/proxy/ClientProxy.java @@ -1,5 +1,6 @@ package com.gtnewhorizons.angelica.proxy; +import com.gtnewhorizons.angelica.debug.DebugKeyHandler; import com.gtnewhorizons.angelica.rendering.culling.GpuCulling; import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID; @@ -185,6 +186,7 @@ public final class ClientProxy extends CommonProxy { Zoom.init(); } FpsReducer.init(); + FMLCommonHandler.instance().bus().register(new DebugKeyHandler()); AngelicaConfig.applyGpuCullingMode(); if (AngelicaConfig.enableDynamicLights) { EntityLightConfig.init(new java.io.File(mc.mcDataDir, "config")); diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkBuilderMeshingTask.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkBuilderMeshingTask.java index a6a7410bd..73acf3f8a 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkBuilderMeshingTask.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkBuilderMeshingTask.java @@ -299,7 +299,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask meshes; try { meshes = BuiltSectionMeshParts.groupFromBuildBuffers(buffers, - (float) camera.x - minX, (float) camera.y - minY, (float) camera.z - minZ); + (float) (camera.x - minX), (float) (camera.y - minY), (float) (camera.z - minZ)); } finally { if (Tracy.ENABLED) Tracy.endZone(); } @@ -399,7 +399,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask meshes; try { - meshes = BuiltSectionMeshParts.groupFromBuildBuffers(m.buffers, (float) camera.x - minX, (float) camera.y - minY, (float) camera.z - minZ); + meshes = BuiltSectionMeshParts.groupFromBuildBuffers(m.buffers, (float) (camera.x - minX), (float) (camera.y - minY), (float) (camera.z - minZ)); } finally { if (Tracy.ENABLED) Tracy.endZone(); } diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkRenderer.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkRenderer.java index bdd599477..1cca5617c 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkRenderer.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkRenderer.java @@ -1,6 +1,7 @@ package com.gtnewhorizons.angelica.rendering.celeritas; import com.gtnewhorizons.angelica.AngelicaMod; +import com.gtnewhorizons.angelica.client.rendering.AngelicaFogService; import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.RenderSystem; import com.gtnewhorizons.angelica.glsm.backend.BackendManager; @@ -27,7 +28,6 @@ import org.embeddedt.embeddium.impl.gl.device.RenderDevice; import org.embeddedt.embeddium.impl.gl.shader.GlProgram; import org.embeddedt.embeddium.impl.gl.shader.GlShader; import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants; -import org.embeddedt.embeddium.impl.gl.shader.ShaderParser; import org.embeddedt.embeddium.impl.gl.shader.ShaderType; import org.embeddedt.embeddium.impl.gl.tessellation.GlPrimitiveType; import org.embeddedt.embeddium.impl.gl.tessellation.GlTessellation; @@ -81,7 +81,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer { private static MultiDrawMode installedBatchMode; public AngelicaChunkRenderer(RenderDevice device, RenderPassConfiguration renderPassConfiguration) { - super(device, renderPassConfiguration); + super(device, renderPassConfiguration, AngelicaFogService.INSTANCE); installBatchFactory(); this.culler = createCuller(); @@ -148,11 +148,6 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer { return null; } - private static GlShader loadShader(ShaderType type, String path, ShaderConstants constants) { - final String source = ShaderParser.parseShader(ShaderLoader.getShaderSource(path), ShaderLoader::getShaderSource, constants); - return new GlShader(type, path, source); - } - @Override protected void begin(TerrainRenderPass pass) { if (Tracy.ENABLED) Tracy.beginZone(Z_CHUNK_BEGIN); @@ -301,8 +296,8 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer { final List loadedShaders = new ArrayList<>(); try { - loadedShaders.add(loadShader(ShaderType.VERTEX, "sodium:" + path + ".vsh", constants)); - loadedShaders.add(loadShader(ShaderType.FRAGMENT, "angelica:" + path + ".fsh", constants)); + loadedShaders.add(ShaderLoader.loadShader(ShaderType.VERTEX, "sodium:" + path + ".vsh", constants)); + loadedShaders.add(ShaderLoader.loadShader(ShaderType.FRAGMENT, "angelica:" + path + ".fsh", constants)); final var builder = GlProgram.builder("sodium:chunk_shader"); loadedShaders.forEach(builder::attachShader); @@ -310,8 +305,10 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer { for (var attr : options.pass().vertexType().getVertexFormat().getAttributes()) { builder.bindAttribute(attr.getName(), i++); } - builder.bindFragmentData("fragColor", ChunkShaderBindingPoints.FRAG_COLOR); - return builder.link((shader) -> new DefaultChunkShaderInterface(shader, options)); + if (!this.enableLegacyGLPatches) { + builder.bindFragmentData("fragColor", ChunkShaderBindingPoints.FRAG_COLOR); + } + return builder.link((shader) -> new DefaultChunkShaderInterface(shader, options, this.environment)); } finally { loadedShaders.forEach(GlShader::delete); } @@ -351,12 +348,12 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer { useBlockFaceCulling, cacheParams); } - final long batchesCreatedBefore = BatchAssembler.getCachedBatchesCreated(); + final CachedBatch before = storage.getCachedMultiDrawBatch(cacheParams); Tracy.beginZone(Z_CHUNK_ASSEMBLE_REGION); try { final CachedBatch cached = super.getRegionBatch(commandList, region, storage, renderList, occlusionCamera, renderPass, useBlockFaceCulling, cacheParams); - if (BatchAssembler.getCachedBatchesCreated() != batchesCreatedBefore) TerrainDrawStats.recordRebuild(); + if (cached != before) TerrainDrawStats.recordRebuild(); final MultiDrawBatch batch = cached != null ? cached.getBatch() : null; if (batch != null && !batch.isEmpty()) TerrainDrawStats.recordBatch(batch.size()); return cached; diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkTracker.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkTracker.java deleted file mode 100644 index f2f6d9fa2..000000000 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkTracker.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Adapated from: Beddium for usage in Angelica - * - * Copyright (C) 2025 Ven, FalsePattern - * All Rights Reserved - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, only version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.gtnewhorizons.angelica.rendering.celeritas; - -import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager; -import org.embeddedt.embeddium.impl.render.chunk.map.ChunkStatus; -import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTrackerImpl; -import org.embeddedt.embeddium.impl.util.PositionUtil; - -import com.gtnewhorizons.angelica.config.AngelicaConfig; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; - -/** - * Configurable chunk tracker supporting "fast" and "aggressive" loading modes. - * Based on Beddium's SafeChunkTracker by Ven and FalsePattern. - * - * Modes (controlled by {@link AngelicaConfig#useVanillaChunkTracking}): - * - Fast (default): Only renders chunks when all 8 neighbors are loaded (better lighting/AO) - * - Aggressive: Renders chunks immediately when loaded (faster perceived loading) - * - * Mode can be switched at runtime via options menu - triggers renderer reload. - */ -public class AngelicaChunkTracker extends ChunkTrackerImpl { - - @Override - public void forEachReady(RenderSectionManager sectionManager) { - if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) { - setFastMode(AngelicaConfig.useVanillaChunkTracking); - } - - super.forEachReady(sectionManager); - } - - @Override - public void forEachEvent(RenderSectionManager sectionManager) { - if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) { - setFastMode(AngelicaConfig.useVanillaChunkTracking); - } - - super.forEachEvent(sectionManager); - } -} diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaRenderPassConfiguration.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaRenderPassConfiguration.java index 1632e29aa..07d2c22a5 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaRenderPassConfiguration.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaRenderPassConfiguration.java @@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.rendering.celeritas; import com.google.common.collect.ImmutableListMultimap; import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.StateSet; +import com.gtnewhorizons.angelica.proxy.ClientProxy; import com.gtnewhorizons.angelica.utils.SpritePadding; import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; import lombok.Getter; @@ -15,6 +16,7 @@ import org.embeddedt.embeddium.impl.render.chunk.terrain.material.parameters.Alp import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexType; import org.lwjgl.opengl.GL11; +import java.util.LinkedHashMap; import java.util.Map; public class AngelicaRenderPassConfiguration { @@ -31,25 +33,32 @@ public class AngelicaRenderPassConfiguration { .vertexType(vertexType) .primitiveType(QuadPrimitiveType.TRIANGULATED); - final int mipmapLevels = SodiumGameOptions.terrainMipmapLevels(); - - if (rgssEnabled) { - builder.extraDefine("USE_RGSS", ""); - } - if (SodiumGameOptions.usesTerrainTexelSnap()) { - builder.extraDefine("USE_TEXEL_SNAP", ""); - } - if (SodiumGameOptions.effectiveTextureFilterMode().usesAnisotropy()) { - builder.extraDefine("USE_ANISOTROPIC", ""); - builder.extraDefine("TERRAIN_GUTTER", SpritePadding.gutterFor(mipmapLevels, true) + ".0"); - } - if (mipmapLevels <= 0) { - builder.extraDefine("TERRAIN_NO_MIPS", ""); - } + samplerDefines(rgssEnabled, SodiumGameOptions.usesTerrainTexelSnap(), + SodiumGameOptions.effectiveTextureFilterMode().usesAnisotropy(), SodiumGameOptions.terrainMipmapLevels()) + .forEach(builder::extraDefine); return builder; } + /** Defines selecting the terrain fragment shader's sampling path. */ + public static Map samplerDefines(boolean rgss, boolean texelSnap, boolean anisotropic, int mipmapLevels) { + final Map defines = new LinkedHashMap<>(); + if (rgss) { + defines.put("USE_RGSS", ""); + } + if (texelSnap) { + defines.put("USE_TEXEL_SNAP", ""); + } + if (anisotropic) { + defines.put("USE_ANISOTROPIC", ""); + defines.put("TERRAIN_GUTTER", SpritePadding.gutterFor(mipmapLevels, true) + ".0"); + } + if (mipmapLevels <= 0) { + defines.put("TERRAIN_NO_MIPS", ""); + } + return defines; + } + public static RenderPassConfiguration build(ChunkVertexType vertexType) { rgssEnabled = SodiumGameOptions.effectiveTextureFilterMode().usesRgss(); @@ -69,7 +78,7 @@ public class AngelicaRenderPassConfiguration { .name("translucent") .fragmentDiscard(false) .useReverseOrder(true) - .useTranslucencySorting(true) + .useTranslucencySorting(ClientProxy.options().performance.translucencySorting) .build(); TRANSLUCENT_MATERIAL = new Material(TRANSLUCENT_PASS, AlphaCutoffParameter.ZERO, true); diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaRenderSectionManager.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaRenderSectionManager.java index 8c6f041d9..0fdaca158 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaRenderSectionManager.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaRenderSectionManager.java @@ -1,6 +1,7 @@ package com.gtnewhorizons.angelica.rendering.celeritas; import com.cardinalstar.cubicchunks.world.ICubicWorld; +import com.gtnewhorizons.angelica.client.rendering.AngelicaFogService; import com.gtnewhorizons.angelica.compat.ModStatus; import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI; import com.gtnewhorizons.angelica.glsm.profiling.Tracy; @@ -20,22 +21,21 @@ import net.minecraft.client.multiplayer.WorldClient; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.world.chunk.Chunk; import org.embeddedt.embeddium.impl.gl.device.CommandList; -import org.embeddedt.embeddium.impl.render.chunk.ChunkRenderMatrices; import org.embeddedt.embeddium.impl.render.chunk.ChunkUpdateType; import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration; import org.embeddedt.embeddium.impl.render.chunk.RenderSection; import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager; import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildOutput; import org.embeddedt.embeddium.impl.render.chunk.compile.tasks.ChunkBuilderTask; +import org.embeddedt.embeddium.impl.render.chunk.fog.FogService; import org.embeddedt.embeddium.impl.render.chunk.lists.SectionTicker; import org.embeddedt.embeddium.impl.render.chunk.occlusion.AsyncOcclusionMode; import org.embeddedt.embeddium.impl.render.chunk.sprite.GenericSectionSpriteTicker; -import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass; import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexType; -import org.embeddedt.embeddium.impl.render.viewport.CameraTransform; import org.embeddedt.embeddium.impl.render.viewport.Viewport; import org.embeddedt.embeddium.impl.util.PositionUtil; import org.jetbrains.annotations.Nullable; +import org.joml.Vector3d; import java.util.ArrayList; import java.util.Collection; @@ -58,15 +58,13 @@ public class AngelicaRenderSectionManager extends RenderSectionManager { private static final Tracy.ZoneId Z_BIOME_REBUILDS = Tracy.zoneId("biomeRebuilds", Tracy.COLOR_TERRAIN); private static final Tracy.ZoneId Z_GRAPH_SEARCH = Tracy.zoneId("graphSearch", Tracy.COLOR_TERRAIN); private static final Tracy.ZoneId Z_SECTION_UPLOAD = Tracy.zoneId("sectionUpload", Tracy.COLOR_TERRAIN); - private static final Tracy.ZoneId Z_SHADOW_GRAPH_WAIT = Tracy.zoneId("shadowGraphWait", Tracy.COLOR_TERRAIN); - - private boolean initialCameraSectionReady = false; private final WorldClient world; private final ClonedChunkSectionCache sectionCache; private final ChunkTaskProvider taskProvider; private final LongOpenHashSet biomeRebuildColumns = new LongOpenHashSet(); private final DeferredMeshScheduler deferredScheduler; + private int shadowSearchSubmissions; public AngelicaRenderSectionManager(RenderPassConfiguration configuration, WorldClient world, int renderDistance, CommandList commandList, int minSection, int maxSection, int requestedThreads, ChunkTaskProvider taskProvider) { super(configuration, () -> new AngelicaChunkBuildContext(configuration, world), AngelicaChunkRenderer::new, renderDistance, commandList, minSection, maxSection, requestedThreads, true /* hasShadowPass = true for Iris */); @@ -92,24 +90,9 @@ public class AngelicaRenderSectionManager extends RenderSectionManager { return new AngelicaRenderSectionManager(AngelicaRenderPassConfiguration.build(vertexType), world, renderDistance, commandList, minSection, maxSection, provider.threadCount(), provider); } - public void setCameraPosition(double x, double y, double z) { - this.cameraPosition.set(x, y, z); - } - @Override - public void update(Viewport positionedViewport, int frame, boolean spectator) { - if (isInShadowPass()) { - return; - } - if (!initialCameraSectionReady) { - var origin = positionedViewport.getChunkCoord(); - long key = PositionUtil.packSection(origin.x(), origin.y(), origin.z()); - if (!((RenderSectionManagerAccessor) this).angelica$getSectionByPosition().containsKey(key)) { - return; - } - initialCameraSectionReady = true; - } - super.update(positionedViewport, frame, spectator); + public FogService getFogService() { + return AngelicaFogService.INSTANCE; } @Override @@ -173,7 +156,7 @@ public class AngelicaRenderSectionManager extends RenderSectionManager { return null; } - final ChunkBuilderTask task = this.taskProvider.createRebuildTask(render, frame, this.cameraPosition, this.sectionCache); + final ChunkBuilderTask task = this.taskProvider.createRebuildTask(render, frame, new Vector3d(this.cameraPosition), this.sectionCache); if (task instanceof AngelicaChunkBuilderMeshingTask t) { final ChunkUpdateType pending = render.getPendingUpdate(); t.bindScheduler(this.deferredScheduler, pending != null && pending.isImportant()); @@ -219,9 +202,6 @@ public class AngelicaRenderSectionManager extends RenderSectionManager { @Override public void updateChunks(boolean updateImmediately) { - if (isInShadowPass()) { - return; - } this.sectionCache.cleanup(); this.flushBiomeRebuilds(); if (Tracy.ENABLED) Tracy.beginZone(Z_GRAPH_SEARCH); @@ -276,14 +256,19 @@ public class AngelicaRenderSectionManager extends RenderSectionManager { } @Override - public boolean canSubmitShadowGraphSearch() { - return super.canSubmitShadowGraphSearch() && this.shadowRenderListManager.isNeedsUpdate() - && !this.shadowRenderListManager.hasOcclusionFutureInFlight(); + protected boolean canSubmitShadowGraphSearch() { + return super.canSubmitShadowGraphSearch() && this.shadowRenderListManager.isNeedsUpdate(); } @Override - public boolean submitShadowGraphSearch(Viewport viewport, int frame) { - return super.submitShadowGraphSearch(viewport, frame); + protected void submitShadowGraphSearch(Viewport viewport, int frame) { + super.submitShadowGraphSearch(viewport, frame); + this.shadowSearchSubmissions++; + } + + /** Running count of submitted shadow searches; a change across a shadow pass means new lists are pending. */ + public int getShadowSearchSubmissions() { + return this.shadowSearchSubmissions; } public boolean isShadowGraphDirty() { @@ -314,20 +299,6 @@ public class AngelicaRenderSectionManager extends RenderSectionManager { return list; } - @Override - public void renderLayer(ChunkRenderMatrices matrices, TerrainRenderPass pass, CameraTransform occlusionCamera, CameraTransform camera) { - // Shadow pass graph update is async - must wait for it to complete before rendering - if (IrisShaderProviderHolder.isShadowPass()) { - if (Tracy.ENABLED) Tracy.beginZone(Z_SHADOW_GRAPH_WAIT); - try { - finishAllGraphUpdates(); - } finally { - if (Tracy.ENABLED) Tracy.endZone(); - } - } - super.renderLayer(matrices, pass, occlusionCamera, camera); - } - /** * Process both asyncSubmittedTasks and AngelicaRenderQueue.TASKS. */ diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasDebugScreenHandler.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasDebugScreenHandler.java index 5e6365a3e..e161f46b4 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasDebugScreenHandler.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasDebugScreenHandler.java @@ -35,6 +35,15 @@ public class CeleritasDebugScreenHandler { private List cachedRight; private long lastBuildNanos; + static boolean isVerbose() { + return AngelicaConfig.verboseF3 || SystemProperties.DEBUG_F3_DETAIL; + } + + /** Drops the cached right-hand lines so a verbose toggle shows on the next frame. */ + public static void invalidate() { + INSTANCE.cachedRight = null; + } + @SubscribeEvent(priority = EventPriority.LOW) public void onRenderGameOverlayTextEvent(RenderGameOverlayEvent.Text event) { final Minecraft mc = Minecraft.getMinecraft(); @@ -69,17 +78,17 @@ public class CeleritasDebugScreenHandler { lines.add(""); lines.add(EnumChatFormatting.GREEN + "Angelica " + Tags.VERSION + " [Celeritas Renderer] [" + RENDER_BACKEND.getName() + "]"); lines.addAll(renderer.getDebugStrings()); - final ShaderManager sm = ShaderManager.getInstance(); - if (sm != null) { - lines.add(sm.getDebugInfo()); - } - lines.add(TessellatorStreamingDrawer.getDebugInfo()); - lines.add(TesrBatchRenderer.INSTANCE.getDebugSummaryLine()); - final String transferInfo = RENDER_BACKEND.getTransferDebugInfo(); - if (transferInfo != null) { - lines.add(transferInfo); - } - if (SystemProperties.DEBUG_F3_DETAIL) { + if (isVerbose()) { + final ShaderManager sm = ShaderManager.getInstance(); + if (sm != null) { + lines.add(sm.getDebugInfo()); + } + lines.add(TessellatorStreamingDrawer.getDebugInfo()); + lines.add(TesrBatchRenderer.INSTANCE.getDebugSummaryLine()); + final String transferInfo = RENDER_BACKEND.getTransferDebugInfo(); + if (transferInfo != null) { + lines.add(transferInfo); + } lines.addAll(TesrBatchRenderer.INSTANCE.getDebugDetailStrings()); lines.add(ModelPartBatcher.INSTANCE.getDebugString()); if (Tracy.ENABLED) { diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasSetup.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasSetup.java index bbb32fae8..55d7d4149 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasSetup.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasSetup.java @@ -2,17 +2,13 @@ package com.gtnewhorizons.angelica.rendering.celeritas; import com.gtnewhorizons.angelica.config.AngelicaConfig; import com.gtnewhorizons.angelica.event.ChunkBiomeDataChangedEvent; -import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.loading.AngelicaClientTweaker; -import org.embeddedt.embeddium.impl.gl.device.GLRenderDevice; -import org.lwjgl.opengl.GL15; public class CeleritasSetup { private static boolean initialized = false; public static void ensureInitialized() { if (!initialized) { - GLRenderDevice.VANILLA_STATE_RESETTER = () -> GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); ChunkBiomeDataChangedEvent.BUS.addListener(CeleritasSetup::onChunkBiomeDataChanged); AngelicaClientTweaker.LOGGER.debug("Celeritas init"); initialized = true; diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasWorldRenderer.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasWorldRenderer.java index 3fd8e0aa0..7b717d114 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasWorldRenderer.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CeleritasWorldRenderer.java @@ -3,7 +3,6 @@ package com.gtnewhorizons.angelica.rendering.celeritas; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; -import java.util.Iterator; import java.util.List; import com.gtnewhorizons.angelica.compat.bop.FogBiomeCache; @@ -25,9 +24,8 @@ import org.embeddedt.embeddium.impl.gl.device.CommandList; import org.embeddedt.embeddium.impl.gl.device.RenderDevice; import org.embeddedt.embeddium.impl.render.chunk.ChunkRenderMatrices; import org.embeddedt.embeddium.impl.render.chunk.RenderSection; -import org.embeddedt.embeddium.impl.render.chunk.data.MinecraftBuiltRenderSectionData; -import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderList; -import org.embeddedt.embeddium.impl.render.chunk.lists.SortedRenderLists; +import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker; +import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTrackerHolder; import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkMeshFormats; import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexType; import org.embeddedt.embeddium.impl.render.terrain.SimpleWorldRenderer; @@ -41,6 +39,7 @@ import org.lwjgl.opengl.GL11; import com.gtnewhorizons.angelica.compat.ModStatus; import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI; +import com.gtnewhorizons.angelica.config.AngelicaConfig; import com.gtnewhorizons.angelica.dynamiclights.DynamicLights; import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightWorldRenderer; import com.gtnewhorizons.angelica.glsm.GLStateManager; @@ -78,7 +77,9 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer frameTEs = new ObjectArrayList<>(); private final ObjectArrayList frameOverride = new ObjectArrayList<>(); @@ -130,7 +128,7 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer renderListIterator = renderLists.iterator(); - - while (renderListIterator.hasNext()) { - final var renderList = renderListIterator.next(); - final var renderRegion = renderList.getRegion(); - final var renderSectionIterator = renderList.sectionsWithEntitiesIterator(); - - if (renderSectionIterator == null) { - continue; - } - - while (renderSectionIterator.hasNext()) { - final var renderSectionId = renderSectionIterator.nextByteAsInt(); - final var renderSection = renderRegion.getSection(renderSectionId); - - if (renderSection == null) { - continue; - } - - final var context = renderSection.getBuiltContext(); - if (context instanceof MinecraftBuiltRenderSectionData mcData) { - final var culledEntities = (List) mcData.culledBlockEntities; - if (!culledEntities.isEmpty()) { - ShadowRenderer.visibleTileEntities.add(culledEntities); - } - } - } - } - - for (var renderSection : renderSectionManager.getSectionsWithGlobalEntities()) { - final var context = renderSection.getBuiltContext(); - if (context instanceof MinecraftBuiltRenderSectionData mcData) { - final var globalEntities = (List) mcData.globalBlockEntities; - if (!globalEntities.isEmpty()) { - ShadowRenderer.globalTileEntities.add(globalEntities); - } - } - } - } - private CameraTransform cachedCameraTransform; private double cachedCamX, cachedCamY, cachedCamZ; @@ -481,6 +480,11 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer blockEntities, boolean culled, TileEntityRenderContext renderContext) { + if (renderContext.shadowCollect) { + (culled ? ShadowRenderer.visibleTileEntities : ShadowRenderer.globalTileEntities).add(blockEntities); + return blockEntities.size(); + } + if (culled) { if (section.getBuiltContext() instanceof AngelicaBuiltRenderSectionData angelicaData) { if (TeDistanceMath.distSqToSection(teCamX, teCamY, teCamZ, @@ -625,20 +629,19 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer MAX_ENTITY_CHECK_VOLUME) { - // TODO: do a frustum check instead, even large entities aren't visible if they're outside the frustum - return true; - } - return this.isBoxVisible(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); } public static class TileEntityRenderContext { + /** Collects shadow-pass block entity lists for ShadowRenderer instead of rendering. */ + final boolean shadowCollect; public float partialTicks; public int pass; + TileEntityRenderContext(boolean shadowCollect) { + this.shadowCollect = shadowCollect; + } + public TileEntityRenderContext set(float partialTicks, int pass) { this.partialTicks = partialTicks; this.pass = pass; diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CubeStatusTracker.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CubeStatusTracker.java index b6603c4fb..e00361a23 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CubeStatusTracker.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/CubeStatusTracker.java @@ -7,7 +7,8 @@ import com.cardinalstar.cubicchunks.util.HashMap3D; import com.cardinalstar.cubicchunks.util.HashSet3D; import com.gtnewhorizons.angelica.config.AngelicaConfig; -public class CubeStatusTracker implements ChunkTracker { +/** Tracks 3D cubes instead of columns; the inherited 2D state stays empty. */ +public class CubeStatusTracker extends ChunkTracker { /// Cubes that have been synced to the client and are currently loaded. private final HashSet3D loadedCubes = new HashSet3D(); @@ -28,14 +29,12 @@ public class CubeStatusTracker implements ChunkTracker { } - @Override public void setFastMode(boolean fastMode) { this.fastMode = fastMode; this.loadedCubes.forEach(this::pollCube); } - @Override public boolean isFastModeEnabled() { return fastMode; } @@ -107,7 +106,6 @@ public class CubeStatusTracker implements ChunkTracker { // do nothing } - @Override public void forEachReady(RenderSectionManager renderSectionManager) { if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) { setFastMode(AngelicaConfig.useVanillaChunkTracking); @@ -134,7 +132,6 @@ public class CubeStatusTracker implements ChunkTracker { } } - @Override public void forEachEvent(RenderSectionManager renderSectionManager) { if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) { setFastMode(AngelicaConfig.useVanillaChunkTracking); diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/SectionAgeMath.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/SectionAgeMath.java deleted file mode 100644 index fc0eab4a0..000000000 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/SectionAgeMath.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.gtnewhorizons.angelica.rendering.celeritas; - -/** Decides when the section-age uniform upload can be skipped */ -public final class SectionAgeMath { - - public static final long STEP_NANOS = 100_000_000L; - - private SectionAgeMath() {} - - public static long quantize(long timestampNanos) { - return timestampNanos - Math.floorMod(timestampNanos, STEP_NANOS); - } - - public static long[] newState() { - return new long[] { Long.MIN_VALUE, Long.MIN_VALUE, 0L }; - } - - public static boolean canSkip(long[] state, long newestLoadTime, long quantizedTimestamp) { - return state[0] == newestLoadTime && (state[2] == 1L || state[1] == quantizedTimestamp); - } - - public static void record(long[] state, long newestLoadTime, long quantizedTimestamp, boolean allSaturated) { - state[0] = newestLoadTime; - state[1] = quantizedTimestamp; - state[2] = allSaturated ? 1L : 0L; - } -} diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainDrawStats.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainDrawStats.java index 93a7577f9..38be6c47d 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainDrawStats.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainDrawStats.java @@ -14,6 +14,11 @@ public final class TerrainDrawStats { private static int cullMaxRegionCommands; private static long sectionMetaBytes; + // Running totals + private static long indexBufferGrowths; + private static long sectionsUploaded; + private static long bytesUploaded; + private TerrainDrawStats() { } @@ -44,6 +49,27 @@ public final class TerrainDrawStats { sectionMetaBytes += bytes; } + public static void recordIndexBufferGrowth() { + indexBufferGrowths++; + } + + public static void recordSectionUpload(long bytes) { + sectionsUploaded++; + bytesUploaded += bytes; + } + + public static long getIndexBufferGrowths() { + return indexBufferGrowths; + } + + public static long getSectionsUploaded() { + return sectionsUploaded; + } + + public static long getBytesUploaded() { + return bytesUploaded; + } + public static int takeBatchRebuilds() { final int n = batchRebuilds; batchRebuilds = 0; diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/api/IrisShaderProvider.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/api/IrisShaderProvider.java index 2433f25e0..789a03f09 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/api/IrisShaderProvider.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/api/IrisShaderProvider.java @@ -27,6 +27,4 @@ public interface IrisShaderProvider { /** Block render layer overrides from shader pack */ @Nullable Map getBlockTypeIds(); - - default void preSubmitShadowGraph(int frame) {} } diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/culling/GpuTerrainCuller.java b/src/main/java/com/gtnewhorizons/angelica/rendering/culling/GpuTerrainCuller.java index 5d7d8ebdc..826ba22b3 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/culling/GpuTerrainCuller.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/culling/GpuTerrainCuller.java @@ -442,8 +442,9 @@ public final class GpuTerrainCuller { } private void walkSortedGroup(ChunkRenderMatrices matrices, ChunkRenderListIterable renderLists, CameraTransform occlusionCamera, CameraTransform camera, boolean useBlockFaceCulling, boolean chained) { + final int indexPointerMask = AngelicaRenderPassConfiguration.TRANSLUCENT_PASS.isSorted() ? 0xFFFFFFFF : 0; if (chained) { - startSortedPass(0xFFFFFFFF); + startSortedPass(indexPointerMask); } else { invalidatePreparedPasses(); computeActiveThisPass = GpuCulling.mode().computeEnabled(); @@ -452,7 +453,7 @@ public final class GpuTerrainCuller { passOutputBase = 0; walkPrimitiveType = null; primaryPass.reset(0, 0); - sortedPass.reset(0xFFFFFFFF, 0); + sortedPass.reset(indexPointerMask, 0); buildPass = sortedPass; current = sortedPass; } diff --git a/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptionPages.java b/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptionPages.java index 615b45612..ba840245b 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptionPages.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptionPages.java @@ -766,6 +766,12 @@ public class SodiumGameOptionPages { .setControl(TickBoxControl::new) .setBinding((opts, value) -> AngelicaConfig.disableF3Additions = value, opts -> AngelicaConfig.disableF3Additions) .build()) + .add(OptionImpl.createBuilder(boolean.class, angelicaOpts) + .setName(I18n.format("options.angelica.verbosef3")) + .setTooltip(I18n.format("options.angelica.verbosef3.tooltip")) + .setControl(TickBoxControl::new) + .setBinding((opts, value) -> AngelicaConfig.verboseF3 = value, opts -> AngelicaConfig.verboseF3) + .build()) .build()); if (AngelicaConfig.enableZoom) { diff --git a/src/main/java/net/coderbot/iris/celeritas/IrisCeleritasShaderProvider.java b/src/main/java/net/coderbot/iris/celeritas/IrisCeleritasShaderProvider.java index 7db1ba836..78c4aa282 100644 --- a/src/main/java/net/coderbot/iris/celeritas/IrisCeleritasShaderProvider.java +++ b/src/main/java/net/coderbot/iris/celeritas/IrisCeleritasShaderProvider.java @@ -66,12 +66,6 @@ public class IrisCeleritasShaderProvider implements IrisShaderProvider { return BlockRenderingSettings.INSTANCE.getBlockTypeIds(); } - @Override - public void preSubmitShadowGraph(int frame) { - final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable(); - if (pipeline != null) pipeline.preSubmitShadowGraph(frame); - } - public void deleteShaders() { overrides.deleteShaders(); } diff --git a/src/main/java/net/coderbot/iris/pipeline/DeferredWorldRenderingPipeline.java b/src/main/java/net/coderbot/iris/pipeline/DeferredWorldRenderingPipeline.java index 2c63d9704..9842c360f 100644 --- a/src/main/java/net/coderbot/iris/pipeline/DeferredWorldRenderingPipeline.java +++ b/src/main/java/net/coderbot/iris/pipeline/DeferredWorldRenderingPipeline.java @@ -1958,11 +1958,6 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R isRenderingFullScreenPass = false; } - @Override - public void preSubmitShadowGraph(int frame) { - if (shadowRenderer != null) shadowRenderer.preSubmitGraphUpdate(frame); - } - @Override public void renderShadows(EntityRenderer levelRenderer, Camera playerCamera) { if (shouldRenderPrepareBeforeShadow) { diff --git a/src/main/java/net/coderbot/iris/pipeline/ShadowRenderer.java b/src/main/java/net/coderbot/iris/pipeline/ShadowRenderer.java index b28dea800..f1db7913e 100644 --- a/src/main/java/net/coderbot/iris/pipeline/ShadowRenderer.java +++ b/src/main/java/net/coderbot/iris/pipeline/ShadowRenderer.java @@ -81,6 +81,7 @@ public class ShadowRenderer { private static final Tracy.ZoneId Z_SHADOW_FRUSTUM = Tracy.zoneId("shadowFrustum", Tracy.COLOR_IRIS); private static final Tracy.ZoneId Z_SHADOW_VIEWPORT = Tracy.zoneId("shadowViewport", Tracy.COLOR_IRIS); private static final Tracy.ZoneId Z_SHADOW_MODEL_PARTS = Tracy.zoneId("shadowModelParts", Tracy.COLOR_IRIS); + private static final Tracy.ZoneId Z_SHADOW_GRAPH_JOIN = Tracy.zoneId("shadowGraphJoin", Tracy.COLOR_IRIS); public static final Matrix4f MODELVIEW = new Matrix4f(); public static final FloatBuffer MODELVIEW_BUFFER = BufferUtils.createFloatBuffer(16); @@ -114,6 +115,7 @@ public class ShadowRenderer { private final boolean shouldRenderEntities; private final boolean shouldRenderPlayer; private final boolean shouldRenderBlockEntities; + private final boolean shouldRenderLightBlockEntities; private final float sunPathRotation; private final List mipmapPasses = new ArrayList<>(); private final String debugStringOverall; @@ -171,10 +173,9 @@ public class ShadowRenderer { private final FrustumCaches entityFrustumCaches = new FrustumCaches(); private float lastGraphShadowAngle = Float.NaN; private float relayShadowAngle = Float.NaN; - private FrustumHolder preSubmitFrustumHolder = new FrustumHolder("preSubmit"); - private final FrustumCaches preSubmitFrustumCaches = new FrustumCaches(); - private boolean preSubmitActive; - private float preSubmittedShadowAngle = Float.NaN; + /** A shadow search was submitted by the last shadow pass; with async occlusion its lists are published by this pass's join. */ + private boolean shadowSearchPending; + private float searchedShadowAngle = Float.NaN; private float shadowAngleDelta() { final Vector4f light = celestialUniforms.getShadowLightPositionInWorldSpace(); @@ -185,25 +186,6 @@ public class ShadowRenderer { AngelicaConfig.shadowGraphHorizonScale); } - public void preSubmitGraphUpdate(int frame) { - final AngelicaRenderSectionManager rsm = CeleritasWorldRenderer.getInstance().getRenderSectionManager(); - final float currentShadowAngle = getShadowAngle(); - if (ShadowGraphGate.shouldMarkDirty(lastGraphShadowAngle, currentShadowAngle, shadowAngleDelta())) { - rsm.markShadowGraphDirty(); - lastGraphShadowAngle = currentShadowAngle; - } - if (!rsm.canSubmitShadowGraphSearch()) return; - - preSubmitFrustumHolder = createShadowFrustum(renderDistanceMultiplier, preSubmitFrustumHolder, preSubmitFrustumCaches); - if (!(preSubmitFrustumHolder.getFrustum() instanceof ViewportProvider provider)) return; - final Vector3d entityPos = Camera.INSTANCE.getEntityPos(); - preSubmitFrustumHolder.getFrustum().setPosition(entityPos.x, entityPos.y, entityPos.z); - - if (rsm.submitShadowGraphSearch(provider.sodium$createViewport(), frame)) { - preSubmittedShadowAngle = lastGraphShadowAngle; - preSubmitActive = true; - } - } private long lastRelayNanos; private final Vector3f shadowLightVectorCache = new Vector3f(); private final MemoizedBoxCuller tileEntityCuller = new MemoizedBoxCuller(); @@ -232,6 +214,7 @@ public class ShadowRenderer { this.shouldRenderEntities = shadowDirectives.shouldRenderEntities(); this.shouldRenderPlayer = shadowDirectives.shouldRenderPlayer(); this.shouldRenderBlockEntities = shadowDirectives.shouldRenderBlockEntities(); + this.shouldRenderLightBlockEntities = shadowDirectives.shouldRenderLightBlockEntities(); this.shouldRenderDH = shadowDirectives.isDhShadowEnabled().orElse(false); this.packUsesShadowtex1 = packUsesShadowtex1; @@ -797,7 +780,11 @@ public class ShadowRenderer { ); } - private void renderTileEntities(Object bufferSource, MatrixStack modelView, double cameraX, double cameraY, double cameraZ, float partialTicks, boolean hasEntityFrustum) { + private static boolean emitsLight(TileEntity tile) { + return tile.getBlockType().getLightValue(tile.getWorldObj(), tile.xCoord, tile.yCoord, tile.zCoord) > 0; + } + + private void renderTileEntities(Object bufferSource, MatrixStack modelView, double cameraX, double cameraY, double cameraZ, float partialTicks, boolean hasEntityFrustum, boolean lightsOnly) { profiler.startSection("iris_shadow_build_blockentities"); int shadowTileEntities = 0; @@ -826,6 +813,9 @@ public class ShadowRenderer { if (hasEntityFrustum && (culler.isCulled(tileEntity.xCoord - 1, tileEntity.yCoord - 1, tileEntity.zCoord - 1, tileEntity.xCoord + 1, tileEntity.yCoord + 1, tileEntity.zCoord + 1))) { continue; } + if (lightsOnly && !emitsLight(tileEntity)) { + continue; + } renderTileEntity(tileEntity, cameraX, cameraY, cameraZ, partialTicks); shadowTileEntities++; @@ -838,6 +828,9 @@ public class ShadowRenderer { if (hasEntityFrustum && (culler.isCulled(tileEntity.xCoord - 1, tileEntity.yCoord - 1, tileEntity.zCoord - 1, tileEntity.xCoord + 1, tileEntity.yCoord + 1, tileEntity.zCoord + 1))) { continue; } + if (lightsOnly && !emitsLight(tileEntity)) { + continue; + } renderTileEntity(tileEntity, cameraX, cameraY, cameraZ, partialTicks); shadowTileEntities++; @@ -864,8 +857,20 @@ public class ShadowRenderer { this.profiler = Minecraft.getMinecraft().mcProfiler; profiler.endStartSection("iris_shadows"); + + final AngelicaRenderSectionManager rsm = CeleritasWorldRenderer.getInstance().getRenderSectionManager(); + // The in-flight shadow search reads terrainFrustumHolder's frustum, which createShadowFrustum re-inits below. + if (Tracy.ENABLED) Tracy.beginZone(Z_SHADOW_GRAPH_JOIN); + try { + rsm.finishAllGraphUpdates(); + } finally { + if (Tracy.ENABLED) Tracy.endZone(); + } + ACTIVE = true; CURRENT_TARGETS = this.targets; + // ACTIVE routes clipRenderersByFrustum into the shadow path; a leak would send every main pass there. + try { // NB: We store the previous player buffers in order to be able to allow mods rendering entities in the shadow pass (Flywheel) to use the shadow buffers instead. // TODO: Render @@ -875,25 +880,27 @@ public class ShadowRenderer { visibleTileEntities.clear(); globalTileEntities.clear(); + final boolean newShadowLists = shadowSearchPending; + shadowSearchPending = false; + final float currentShadowAngle = getShadowAngle(); if (ShadowGraphGate.shouldMarkDirty(lastGraphShadowAngle, currentShadowAngle, shadowAngleDelta())) { - CeleritasWorldRenderer.getInstance().getRenderSectionManager().markShadowGraphDirty(); + rsm.markShadowGraphDirty(); lastGraphShadowAngle = currentShadowAngle; } final boolean deferActive = shouldRenderTerrain; final long frameNanos = System.nanoTime(); - final boolean consumePreSubmit = preSubmitActive; - preSubmitActive = false; + final boolean shadowGraphDirty = rsm.isShadowGraphDirty(); final boolean relayTerrain = shadowTerrainAlwaysRelays() || !targets.isTerrainSnapshotValid() - || consumePreSubmit - || CeleritasWorldRenderer.getInstance().getRenderSectionManager().isShadowGraphDirty() + || newShadowLists + || shadowGraphDirty || frameNanos - lastRelayNanos >= SHADOW_RELAY_MAX_AGE_NANOS; if (relayTerrain) { - - relayShadowAngle = consumePreSubmit && !CeleritasWorldRenderer.getInstance().getRenderSectionManager().isShadowGraphDirty() ? preSubmittedShadowAngle : currentShadowAngle; + // New lists draw at the angle they were searched at, unless the gate already wants a newer search. + relayShadowAngle = newShadowLists && !shadowGraphDirty ? searchedShadowAngle : currentShadowAngle; } activeShadowAngle = relayShadowAngle; SHADOW_TERRAIN_RELAID = relayTerrain; @@ -934,11 +941,9 @@ public class ShadowRenderer { profiler.endSection(); - // Save the main camera viewport before shadow pass overwrites it. - // clipRenderersByFrustum -> setupTerrain sets currentViewport to the shadow frustum. - // If the shadow pass throws or the main setupTerrain doesn't run after us, the shadow - // viewport would persist and corrupt entity culling in the main pass. - final Viewport savedViewport = CeleritasWorldRenderer.getInstance().getCurrentViewport(); + // clipRenderersByFrustum -> setupShadowTerrain sets currentViewport to the shadow frustum; restore the last + // terrain viewport so entity culling never sees the shadow one if the main setupTerrain doesn't run. + final Viewport savedViewport = CeleritasWorldRenderer.getInstance().getLastViewport(); // Pair setupGlState / restoreGlState with try-finally so any throw between them // (translucent terrain, entity rendering, mipmaps, etc.) still unwinds the @@ -947,7 +952,13 @@ public class ShadowRenderer { boolean setupGlStateRan = false; try { // Execute the vanilla terrain setup / culling routines using our shadow frustum. + final int shadowSearchesBefore = rsm.getShadowSearchSubmissions(); mc.renderGlobal.clipRenderersByFrustum(terrainFrustumHolder.getFrustum(), playerCamera.getPartialTicks()); + if (rsm.getShadowSearchSubmissions() != shadowSearchesBefore) { + shadowSearchPending = true; + // createShadowFrustum built the search's light vector from the current light + searchedShadowAngle = currentShadowAngle; + } // Don't forget to increment the frame counter! This variable is arbitrary and only used in terrain setup, // and if it's not incremented, the vanilla culling code will get confused and think that it's already seen @@ -983,9 +994,9 @@ public class ShadowRenderer { renderShadowEntitiesAndPlayer(levelRenderer, entityShadowFrustum, modelView, entityX, entityY, entityZ, tickDelta); } - if (shouldRenderBlockEntities) { + if (shouldRenderBlockEntities || shouldRenderLightBlockEntities) { try (GLDebug.Scope s = GLDebug.scope("shadow:block_entities")) { - renderTileEntities(null, modelView, entityX, entityY, entityZ, tickDelta, entityFrustumConstrained); + renderTileEntities(null, modelView, entityX, entityY, entityZ, tickDelta, entityFrustumConstrained, !shouldRenderBlockEntities); } } @@ -1036,9 +1047,10 @@ public class ShadowRenderer { profiler.endStartSection("iris_shadowcomp"); if (compositeRenderer != null) compositeRenderer.renderAll(); - - ACTIVE = false; - CURRENT_TARGETS = null; + } finally { + ACTIVE = false; + CURRENT_TARGETS = null; + } profiler.endSection(); profiler.endStartSection("culling"); } @@ -1062,7 +1074,7 @@ public class ShadowRenderer { } private String getTileEntitiesDebugString() { - return shouldRenderBlockEntities ? (renderedShadowTileEntities + "/" + Minecraft.getMinecraft().theWorld.loadedTileEntityList.size()) : "disabled by pack"; + return (shouldRenderBlockEntities || shouldRenderLightBlockEntities) ? (renderedShadowTileEntities + "/" + Minecraft.getMinecraft().theWorld.loadedTileEntityList.size()) : "disabled by pack"; } private static class MipmapPass { diff --git a/src/main/java/net/coderbot/iris/pipeline/WorldRenderingPipeline.java b/src/main/java/net/coderbot/iris/pipeline/WorldRenderingPipeline.java index fa7647781..fc82a08fa 100644 --- a/src/main/java/net/coderbot/iris/pipeline/WorldRenderingPipeline.java +++ b/src/main/java/net/coderbot/iris/pipeline/WorldRenderingPipeline.java @@ -22,7 +22,6 @@ import java.util.OptionalInt; public interface WorldRenderingPipeline { void beginLevelRendering(); void renderShadows(EntityRenderer levelRenderer, Camera camera); - default void preSubmitShadowGraph(int frame) {} void addDebugText(List messages); OptionalInt getForcedShadowRenderDistanceChunksForDisplay(); diff --git a/src/main/java/net/coderbot/iris/pipeline/transform/RwImageStoreExtractor.java b/src/main/java/net/coderbot/iris/pipeline/transform/RwImageStoreExtractor.java index d4333f485..35a82ce62 100644 --- a/src/main/java/net/coderbot/iris/pipeline/transform/RwImageStoreExtractor.java +++ b/src/main/java/net/coderbot/iris/pipeline/transform/RwImageStoreExtractor.java @@ -611,16 +611,6 @@ public final class RwImageStoreExtractor { final Set present = new HashSet<>(); for (AttributeSlot slot : attrs) present.add(slot.name()); - out.append("#ifdef USE_VERTEX_COMPRESSION\n"); - if (present.contains("a_PosId")) { - out.append(" _vert_position = vec3(a_PosId.xyz) * VERT_POS_SCALE + VERT_POS_OFFSET;\n"); - out.append(" _draw_id = (a_PosId.w >> 8u) & 0xFFu;\n"); - out.append(" _material_params = (a_PosId.w >> 0u) & 0xFFu;\n"); - } - if (present.contains("a_TexCoord")) out.append(" _vert_tex_diffuse_coord = a_TexCoord * VERT_TEX_SCALE;\n"); - if (present.contains("a_LightCoord")) out.append(" _vert_tex_light_coord = a_LightCoord;\n"); - if (present.contains("a_Color")) out.append(" _vert_color = a_Color;\n"); - out.append("#else\n"); if (present.contains("a_PosId")) out.append(" _vert_position = a_PosId;\n"); if (present.contains("a_TexCoord")) out.append(" _vert_tex_diffuse_coord = a_TexCoord;\n"); if (present.contains("a_Color")) out.append(" _vert_color = a_Color;\n"); @@ -630,7 +620,6 @@ public final class RwImageStoreExtractor { out.append(" _draw_id = (_vg_draw_params >> 8) & 0xFFu;\n"); out.append(" _vert_tex_light_coord = ivec2((uvec2((a_LightCoord >> 16) & 0xFFFFu) >> uvec2(0, 8)) & uvec2(0xFFu));\n"); } - out.append("#endif\n"); } private static void emitDispatchMain(StringBuilder out, RwExtractMode mode, List attrs) { @@ -788,11 +777,6 @@ public final class RwImageStoreExtractor { addAttr(m, new AttributeSlot("a_LightCoord", "uint", 24, "_vg_vbuf.data[base + 6u]")); - addAttr(m, new AttributeSlot("a_PosId", "uvec4", 0, - "uvec4(0u, 0u, 0u, 0u)", false)); - addAttr(m, new AttributeSlot("a_LightCoord", "ivec2", 24, - "ivec2(0, 0)", false)); - final LinkedHashMap> immutable = new LinkedHashMap<>(); for (var e : m.entrySet()) immutable.put(e.getKey(), Map.copyOf(e.getValue())); return Map.copyOf(immutable); diff --git a/src/main/java/net/coderbot/iris/shaderpack/PackShadowDirectives.java b/src/main/java/net/coderbot/iris/shaderpack/PackShadowDirectives.java index f7afaccd3..c6c99da40 100644 --- a/src/main/java/net/coderbot/iris/shaderpack/PackShadowDirectives.java +++ b/src/main/java/net/coderbot/iris/shaderpack/PackShadowDirectives.java @@ -29,6 +29,7 @@ public class PackShadowDirectives { private final boolean shouldRenderEntities; private final boolean shouldRenderPlayer; private final boolean shouldRenderBlockEntities; + private final boolean shouldRenderLightBlockEntities; @Getter private final ShadowCullState cullingState; private final OptionalBoolean dhShadowEnabled; private float nearPlane, farPlane; @@ -83,6 +84,7 @@ public class PackShadowDirectives { this.shouldRenderEntities = properties.getShadowEntities().orElse(true); this.shouldRenderPlayer = properties.getShadowPlayer().orElse(false); this.shouldRenderBlockEntities = properties.getShadowBlockEntities().orElse(true); + this.shouldRenderLightBlockEntities = properties.getShadowLightBlockEntities().orElse(false); this.cullingState = properties.getShadowCulling(); this.shadowEnabled = properties.getShadowEnabled(); this.dhShadowEnabled = properties.getDhShadowEnabled(); @@ -114,6 +116,7 @@ public class PackShadowDirectives { this.shouldRenderEntities = shadowDirectives.shouldRenderEntities; this.shouldRenderPlayer = shadowDirectives.shouldRenderPlayer; this.shouldRenderBlockEntities = shadowDirectives.shouldRenderBlockEntities; + this.shouldRenderLightBlockEntities = shadowDirectives.shouldRenderLightBlockEntities; this.cullingState = shadowDirectives.cullingState; this.depthSamplingSettings = shadowDirectives.depthSamplingSettings; this.colorSamplingSettings = shadowDirectives.colorSamplingSettings; @@ -185,6 +188,10 @@ public class PackShadowDirectives { return shouldRenderBlockEntities; } + public boolean shouldRenderLightBlockEntities() { + return shouldRenderLightBlockEntities; + } + public OptionalBoolean isShadowEnabled() { return shadowEnabled; } diff --git a/src/main/resources/META-INF/services/org.embeddedt.embeddium.impl.render.chunk.fog.FogService b/src/main/resources/META-INF/services/org.embeddedt.embeddium.impl.render.chunk.fog.FogService deleted file mode 100644 index 3f498cb80..000000000 --- a/src/main/resources/META-INF/services/org.embeddedt.embeddium.impl.render.chunk.fog.FogService +++ /dev/null @@ -1 +0,0 @@ -com.gtnewhorizons.angelica.client.rendering.AngelicaFogService diff --git a/src/main/resources/assets/angelica/lang/en_US.lang b/src/main/resources/assets/angelica/lang/en_US.lang index bef37ebb8..2801d7723 100644 --- a/src/main/resources/assets/angelica/lang/en_US.lang +++ b/src/main/resources/assets/angelica/lang/en_US.lang @@ -83,7 +83,7 @@ sodium.options.use_particle_culling.tooltip=If enabled, only particles which are sodium.options.animate_only_visible_textures.name=Animate Only Visible Textures sodium.options.animate_only_visible_textures.tooltip=If enabled, only animated textures determined to be visible will be updated. This can provide a significant boost to frame rates on some hardware, especially with heavier resource packs. If you experience issues with some textures not being animated, try disabling this option. sodium.options.translucency_sorting.name=Translucency Sorting -sodium.options.translucency_sorting.tooltip=If enabled, translucent blocks will be sorted correctly from back to front to provide better rendering, especially when multiple translucent blocks are in view and layered. This is a bit of an expensive operation. +sodium.options.translucency_sorting.tooltip=If enabled, translucent terrain (water, stained glass, ice) and translucent tile entities are sorted back to front so overlapping layers draw correctly. Disabling improves performance in scenes with lots of translucent blocks but causes layering artifacts. Changing this reloads chunks. sodium.options.cpu_render_ahead_limit.name=CPU Render-Ahead Limit sodium.options.cpu_render_ahead_limit.tooltip=How many frames the CPU may run ahead of the GPU. Lower values cut input latency, higher values smooth out uneven frame times. Very low or high values may create frame rate instability. 0 disables it; the SDL-GPU backend ignores it and applies its own backpressure. sodium.options.cpu_render_ahead_limit.value=%s frame(s) @@ -301,6 +301,10 @@ options.angelica.aggressiveChunkLoading=Aggressive Chunk Loading options.angelica.aggressiveChunkLoading.tooltip=Renders chunks immediately when loaded, even if neighbors aren't ready yet. Improves perceived loading speed at render distance edges. Chunks rebuild automatically when neighbors load. Useful for low render distance servers. options.angelica.disablef3=Disable F3 Additions options.angelica.disablef3.tooltip=Disables Angelica's additions to the F3 debug screen. +options.angelica.verbosef3=Verbose F3 +options.angelica.verbosef3.tooltip=Shows developer counters (fixed-function shaders, streaming, TESR batching, GPU transfer) on the F3 screen. Toggle in game with F3+V. +angelica.debug.verbose.on=Verbose F3: shown +angelica.debug.verbose.off=Verbose F3: hidden options.angelica.texteffects.rgb_colors=RGB Colors options.angelica.texteffects.rgb_colors.tooltip=Enable hex colors via %s syntax options.angelica.texteffects.gradients=Gradients diff --git a/src/main/resources/assets/angelica/shaders/blocks/block_layer_opaque.fsh b/src/main/resources/assets/angelica/shaders/blocks/block_layer_opaque.fsh index 00b2ada14..cf51ee2ae 100644 --- a/src/main/resources/assets/angelica/shaders/blocks/block_layer_opaque.fsh +++ b/src/main/resources/assets/angelica/shaders/blocks/block_layer_opaque.fsh @@ -1,11 +1,19 @@ #version 330 core +#ifdef LEGACY +#extension GL_EXT_gpu_shader4 : require +#endif + #import in vec4 v_Color; +in vec4 v_RdhFactor; +in vec2 v_QuadCoord; in vec2 v_TexCoord; +#if defined(USE_FOG) && defined(CHUNK_FADE_IN_DURATION_MS) && CHUNK_FADE_IN_DURATION_MS > 0 in float v_ChunkAgeMs; +#endif in float v_MaterialMipBias; #ifdef USE_FRAGMENT_DISCARD @@ -19,13 +27,14 @@ in float v_CylindricalFragDistance; in float v_FragDistance; #endif -uniform sampler2D u_BlockTex; +uniform sampler2D u_BlockTex; // The block texture -uniform vec4 u_FogColor; +uniform int u_FogShape; +uniform vec4 u_FogColor; // The color of the shader fog #ifdef USE_FOG_SMOOTH -uniform float u_FogStart; -uniform float u_FogEnd; +uniform float u_FogStart; // The starting position of the shader fog +uniform float u_FogEnd; // The ending position of the shader fog #endif #ifdef USE_FOG_POSTMODERN @@ -36,11 +45,11 @@ uniform float u_EnvFogEnd; #endif #if defined(USE_FOG_EXP) || defined(USE_FOG_EXP2) -uniform float u_FogDensity; +uniform float u_FogDensity; // The density of the shader fog #endif #ifndef LEGACY -out vec4 fragColor; +out vec4 fragColor; // The output fragment for the color framebuffer #else #define fragColor gl_FragColor #endif @@ -181,32 +190,51 @@ void main() { #endif vec4 m_color = v_Color; + // Apply correction factor to make the resulting color very close to what true bilinear interpolation would obtain + // min(x, y) * (1 - max(x, y)) == min(x, y) - (x * y) + float correctionWeight = min(v_QuadCoord.x, v_QuadCoord.y) - (v_QuadCoord.x * v_QuadCoord.y); + m_color += v_RdhFactor * correctionWeight; #ifdef USE_VANILLA_COLOR_FORMAT + // Apply per-vertex color. AO shade is applied ahead of time on the CPU. diffuseColor *= m_color; #else + // Apply per-vertex color diffuseColor.rgb *= m_color.rgb; + + // Apply ambient occlusion "shade" diffuseColor.rgb *= m_color.a; #endif #ifdef USE_FOG + #if defined(CHUNK_FADE_IN_DURATION_MS) && CHUNK_FADE_IN_DURATION_MS > 0 + // Make chunk fade in over a short duration diffuseColor = vec4(mix(u_FogColor.rgb, diffuseColor.rgb, (clamp(v_ChunkAgeMs, 0, CHUNK_FADE_IN_DURATION_MS) / CHUNK_FADE_IN_DURATION_MS)), diffuseColor.a); #endif -#ifdef USE_FOG_POSTMODERN +#if defined(USE_FOG_POSTMODERN) float fogValue = max(_linearFogValue(v_CylindricalFragDistance, u_RenderDistFogStart, u_RenderDistFogEnd), _linearFogValue(v_SphericalFragDistance, u_EnvFogStart, u_EnvFogEnd)); fragColor = vec4(mix(diffuseColor.rgb, u_FogColor.rgb, fogValue * u_FogColor.a), diffuseColor.a); -#elif defined(USE_FOG_EXP2) - fragColor = _exp2Fog(diffuseColor, v_FragDistance, u_FogColor, u_FogDensity); +#else // Legacy fog + float fragDistance; + if (u_FogShape == FOG_SHAPE_PLANAR) { + fragDistance = gl_FragCoord.z / gl_FragCoord.w; + } else { + fragDistance = v_FragDistance; + } +#if defined(USE_FOG_EXP2) + fragColor = _exp2Fog(diffuseColor, fragDistance, u_FogColor, u_FogDensity); #elif defined(USE_FOG_EXP) - fragColor = _expFog(diffuseColor, v_FragDistance, u_FogColor, u_FogDensity); + fragColor = _expFog(diffuseColor, fragDistance, u_FogColor, u_FogDensity); #elif defined(USE_FOG_SMOOTH) - fragColor = _linearFog(diffuseColor, v_FragDistance, u_FogColor, u_FogStart, u_FogEnd); + fragColor = _linearFog(diffuseColor, fragDistance, u_FogColor, u_FogStart, u_FogEnd); #endif -#else +#endif + +#else // No fog fragColor = diffuseColor; #endif } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinDefaultChunkShaderInterface.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinDefaultChunkShaderInterface.java deleted file mode 100644 index ca29a3345..000000000 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinDefaultChunkShaderInterface.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain; - -import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities; -import com.gtnewhorizons.angelica.rendering.celeritas.SectionAgeMath; -import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloatArray; -import org.embeddedt.embeddium.impl.render.chunk.shader.DefaultChunkShaderInterface; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; -import java.util.WeakHashMap; - -/** Skips section-age uniform uploads that cannot have changed; see {@link SectionAgeMath} */ -@Mixin(value = DefaultChunkShaderInterface.class, remap = false) -public abstract class MixinDefaultChunkShaderInterface { - - @Shadow @Final private GlUniformFloatArray uniformChunkAges; - - /** Per-region {@link SectionAgeMath} skip state. */ - @Unique private final WeakHashMap angelica$ageState = new WeakHashMap<>(); - - @Unique private FloatBuffer angelica$ageScratch; - - public void setSectionAges(long timestamp, long[] loadTimes, long newestLoadTime) { - final int n = loadTimes.length; - if (this.uniformChunkAges == null || n == 0) return; - - final long tsQ = SectionAgeMath.quantize(timestamp); - long[] state = angelica$ageState.get(loadTimes); - if (state != null && SectionAgeMath.canSkip(state, newestLoadTime, tsQ)) { - return; - } - - final boolean allSaturated = angelica$buildAndUpload(tsQ, loadTimes, n); - - if (state == null) { - state = SectionAgeMath.newState(); - angelica$ageState.put(loadTimes, state); - } - SectionAgeMath.record(state, newestLoadTime, tsQ, allSaturated); - } - - /** - * @author Angelica - * @reason reused direct buffer - */ - @Overwrite - public void setSectionAges(long timestamp, long[] loadTimes) { - final int n = loadTimes.length; - if (this.uniformChunkAges == null || n == 0) return; - angelica$buildAndUpload(timestamp, loadTimes, n); - } - - @Unique - private boolean angelica$buildAndUpload(long timestamp, long[] loadTimes, int n) { - FloatBuffer buf = angelica$ageScratch; - if (buf == null || buf.capacity() < n) { - buf = ByteBuffer.allocateDirect(n * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); - angelica$ageScratch = buf; - } - long ptr = MemoryUtilities.memAddress(buf); - boolean allSaturated = true; - for (int i = 0; i < n; i++) { - final long ageMs = (timestamp - loadTimes[i]) / 1_000_000L; - final float v; - if (ageMs >= 30000L) { - v = 30000f; - } else { - v = (float) ageMs; - allSaturated = false; - } - MemoryUtilities.memPutFloat(ptr, v); - ptr += 4; - } - buf.position(0).limit(n); - this.uniformChunkAges.set(buf); - return allSaturated; - } -} diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderGlobal.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderGlobal.java index 17122ff89..91f3ff6e4 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderGlobal.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderGlobal.java @@ -1,5 +1,6 @@ package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain; +import com.gtnewhorizons.angelica.client.rendering.AngelicaFogService; import com.gtnewhorizons.angelica.compat.mojang.Camera; import com.gtnewhorizons.angelica.compat.mojang.GameModeUtil; import com.gtnewhorizons.angelica.event.RenderChunkEvent; @@ -29,8 +30,8 @@ import net.minecraft.client.renderer.texture.TextureMap; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import org.embeddedt.embeddium.impl.gl.device.RenderDevice; -import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderFogComponent; import org.embeddedt.embeddium.impl.render.terrain.SimpleWorldRenderer; +import org.embeddedt.embeddium.impl.render.viewport.Viewport; import org.embeddedt.embeddium.impl.render.viewport.ViewportProvider; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL13; @@ -200,7 +201,7 @@ public class MixinRenderGlobal implements IRenderGlobalExt { try { final Entity viewEntity = this.mc.renderViewEntity; - final float fogDistance = ChunkShaderFogComponent.FOG_SERVICE.getFogCutoff(); + final float fogDistance = AngelicaFogService.INSTANCE.getFogCutoff(); final double camX = lerp(viewEntity.lastTickPosX, viewEntity.posX, partialTicks); final double camY = lerp(viewEntity.lastTickPosY, viewEntity.posY, partialTicks) + viewEntity.getEyeHeight(); @@ -222,7 +223,12 @@ public class MixinRenderGlobal implements IRenderGlobalExt { camX, camY, camZ, viewEntity.rotationPitch, viewEntity.rotationYaw, fogDistance ); - this.celeritas$renderer.setupTerrain(((ViewportProvider)camera).sodium$createViewport(), cameraState, this.celeritas$frame++, angelica$isSpectatorMode(), false); + final Viewport viewport = ((ViewportProvider) camera).sodium$createViewport(); + if (this.celeritas$renderer.isInShadowPass()) { + this.celeritas$renderer.setupShadowTerrain(viewport, cameraState, this.celeritas$frame++, angelica$isSpectatorMode()); + } else { + this.celeritas$renderer.setupTerrain(viewport, cameraState, this.celeritas$frame++, angelica$isSpectatorMode(), false); + } } finally { RenderDevice.exitManagedCode(); } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderRegionManager.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderRegionManager.java new file mode 100644 index 000000000..5b1f835bd --- /dev/null +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderRegionManager.java @@ -0,0 +1,31 @@ +package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain; + +import com.gtnewhorizons.angelica.rendering.celeritas.TerrainDrawStats; +import org.embeddedt.embeddium.impl.gl.device.CommandList; +import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildOutput; +import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkTaskOutput; +import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult; +import org.embeddedt.embeddium.impl.render.chunk.data.BuiltSectionMeshParts; +import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegionManager; +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; + +import java.util.Collection; + +@Mixin(value = RenderRegionManager.class, remap = false) +public abstract class MixinRenderRegionManager { + + @Inject(method = "uploadMeshes", at = @At("HEAD")) + private void angelica$onUploadMeshes(CommandList commandList, Collection> results, Runnable graphUpdateTrigger, CallbackInfo ci) { + for (var holder : results) { + if (!(holder.output() instanceof ChunkBuildOutput buildOutput)) continue; + for (BuiltSectionMeshParts mesh : buildOutput.meshes.values()) { + long bytes = mesh.vertexBuffer().getLength(); + if (mesh.indexBuffer() != null) bytes += mesh.indexBuffer().getLength(); + TerrainDrawStats.recordSectionUpload(bytes); + } + } + } +} diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderSectionManager.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderSectionManager.java index dc93a4d7d..86c1bedf9 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderSectionManager.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinRenderSectionManager.java @@ -1,8 +1,6 @@ package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain; import com.gtnewhorizons.angelica.mixins.interfaces.RenderSectionManagerAccessor; -import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; -import org.embeddedt.embeddium.impl.render.chunk.RenderSection; import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager; import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkTaskOutput; import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult; @@ -15,17 +13,10 @@ import java.util.concurrent.ConcurrentLinkedDeque; @Mixin(value = RenderSectionManager.class, remap = false) public class MixinRenderSectionManager implements RenderSectionManagerAccessor { - @Shadow @Final private Long2ReferenceMap sectionByPosition; - @Shadow @Final private ConcurrentLinkedDeque asyncSubmittedTasks; @Shadow @Final private ConcurrentLinkedDeque> buildResults; - @Override - public Long2ReferenceMap angelica$getSectionByPosition() { - return sectionByPosition; - } - @Override public ConcurrentLinkedDeque angelica$getAsyncSubmittedTasks() { return asyncSubmittedTasks; diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinShaderLoader.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinShaderLoader.java new file mode 100644 index 000000000..036035d59 --- /dev/null +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinShaderLoader.java @@ -0,0 +1,20 @@ +package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain; + +import com.gtnewhorizons.angelica.glsm.RenderSystem; +import org.embeddedt.embeddium.impl.render.shader.ShaderLoader; +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; + +@Mixin(value = ShaderLoader.class, remap = false) +public class MixinShaderLoader { + + /** The desktop GL 3.2 capability check can fail on ES 3.2; ES must never take the GLSL 1.20 path. */ + @Inject(method = "useLegacyGlsl", at = @At("HEAD"), cancellable = true) + private static void angelica$noLegacyGlslOnGles(CallbackInfoReturnable cir) { + if (RenderSystem.isGLES()) { + cir.setReturnValue(false); + } + } +} diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinSharedQuadIndexBuffer.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinSharedQuadIndexBuffer.java new file mode 100644 index 000000000..58e38c19f --- /dev/null +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinSharedQuadIndexBuffer.java @@ -0,0 +1,18 @@ +package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain; + +import com.gtnewhorizons.angelica.rendering.celeritas.TerrainDrawStats; +import org.embeddedt.embeddium.impl.gl.device.CommandList; +import org.embeddedt.embeddium.impl.render.chunk.SharedQuadIndexBuffer; +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(value = SharedQuadIndexBuffer.class, remap = false) +public abstract class MixinSharedQuadIndexBuffer { + + @Inject(method = "grow", at = @At("HEAD")) + private void angelica$onGrow(CommandList commandList, int primitiveCount, CallbackInfo ci) { + TerrainDrawStats.recordIndexBufferGrowth(); + } +} diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinWorldClient.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinWorldClient.java index 7a0a0a619..de37179a4 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinWorldClient.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/celeritas/terrain/MixinWorldClient.java @@ -9,14 +9,13 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import com.gtnewhorizons.angelica.compat.ModStatus; -import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaChunkTracker; import com.gtnewhorizons.angelica.rendering.celeritas.CubeStatusTracker; import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache; import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension; @Mixin(WorldClient.class) public class MixinWorldClient implements ChunkTrackerHolder, WorldClientExtension { - @Unique private final ChunkTracker celeritas$tracker = ModStatus.isCubicChunksLoaded ? new CubeStatusTracker() : new AngelicaChunkTracker(); + @Unique private final ChunkTracker celeritas$tracker = ModStatus.isCubicChunksLoaded ? new CubeStatusTracker() : new ChunkTracker(); @Unique private final SmoothBiomeColorCache celeritas$smoothBiomeColorCache = new SmoothBiomeColorCache((IBlockAccess) this); @Override diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java index f5eaf65fe..78566cc91 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java @@ -2,8 +2,10 @@ package com.gtnewhorizons.angelica.mixins.early.shaders; import com.gtnewhorizons.angelica.compat.mojang.Camera; import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Share; import com.llamalad7.mixinextras.sugar.ref.LocalRef; import jss.notfine.core.SettingsManager; @@ -24,10 +26,12 @@ import net.minecraft.client.renderer.EntityRenderer; import net.minecraft.client.renderer.ItemRenderer; import net.minecraft.client.renderer.RenderGlobal; import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.culling.Frustrum; import net.minecraft.client.resources.IResourceManagerReloadListener; import net.minecraft.client.settings.GameSettings; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; +import org.embeddedt.embeddium.impl.render.viewport.ViewportProvider; import org.lwjgl.opengl.GL11; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -85,7 +89,11 @@ public abstract class MixinEntityRenderer implements IResourceManagerReloadListe } @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;clipRenderersByFrustum(Lnet/minecraft/client/renderer/culling/ICamera;F)V"), method = "renderWorld(FJ)V") - private void iris$renderShadows(float partialTicks, long startTime, CallbackInfo ci, @Share("pipeline") LocalRef pipeline) { + private void iris$renderShadows(float partialTicks, long startTime, CallbackInfo ci, @Share("pipeline") LocalRef pipeline, @Local Frustrum playerFrustum) { + final CeleritasWorldRenderer renderer = CeleritasWorldRenderer.getInstanceOrNull(); + if (renderer != null) { + renderer.setShadowPassPlayerViewport(((ViewportProvider) playerFrustum).sodium$createViewport()); + } pipeline.get().renderShadows((EntityRenderer) (Object) this, Camera.INSTANCE); } diff --git a/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/SectionAgeMathTest.java b/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/SectionAgeMathTest.java deleted file mode 100644 index f0d4b935b..000000000 --- a/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/SectionAgeMathTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.gtnewhorizons.angelica.rendering.celeritas; - -import org.junit.jupiter.api.Test; - -import static com.gtnewhorizons.angelica.rendering.celeritas.SectionAgeMath.STEP_NANOS; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class SectionAgeMathTest { - - @Test - void quantizeRoundsDownToStep() { - assertEquals(0L, SectionAgeMath.quantize(0L)); - assertEquals(0L, SectionAgeMath.quantize(STEP_NANOS - 1)); - assertEquals(STEP_NANOS, SectionAgeMath.quantize(STEP_NANOS)); - assertEquals(7 * STEP_NANOS, SectionAgeMath.quantize(7 * STEP_NANOS + 42)); - - assertEquals(-STEP_NANOS, SectionAgeMath.quantize(-1L)); - assertEquals(-2 * STEP_NANOS, SectionAgeMath.quantize(-STEP_NANOS - 1)); - assertEquals(-STEP_NANOS, SectionAgeMath.quantize(-STEP_NANOS)); - } - - @Test - void freshStateNeverSkips() { - final long[] state = SectionAgeMath.newState(); - assertFalse(SectionAgeMath.canSkip(state, 0L, 0L)); - assertFalse(SectionAgeMath.canSkip(state, 123L, SectionAgeMath.quantize(456L))); - } - - @Test - void unsaturatedSkipsOnlyWithinTheSameStep() { - final long[] state = SectionAgeMath.newState(); - final long newest = 1_000L; - final long tsQ = SectionAgeMath.quantize(50 * STEP_NANOS + 3); - SectionAgeMath.record(state, newest, tsQ, false); - - assertTrue(SectionAgeMath.canSkip(state, newest, tsQ)); - assertFalse(SectionAgeMath.canSkip(state, newest, tsQ + STEP_NANOS), "next step must rebuild"); - assertFalse(SectionAgeMath.canSkip(state, newest + 1, tsQ), "newer section load must rebuild"); - } - - @Test - void saturatedSkipsAcrossStepsUntilNewestChanges() { - final long[] state = SectionAgeMath.newState(); - final long newest = 1_000L; - final long tsQ = SectionAgeMath.quantize(400 * STEP_NANOS); - SectionAgeMath.record(state, newest, tsQ, true); - - assertTrue(SectionAgeMath.canSkip(state, newest, tsQ)); - assertTrue(SectionAgeMath.canSkip(state, newest, tsQ + 10_000 * STEP_NANOS), "saturation is time-independent"); - assertFalse(SectionAgeMath.canSkip(state, newest + 1, tsQ), "a new section load ends saturation"); - - SectionAgeMath.record(state, newest + 1, tsQ + STEP_NANOS, false); - assertTrue(SectionAgeMath.canSkip(state, newest + 1, tsQ + STEP_NANOS)); - assertFalse(SectionAgeMath.canSkip(state, newest + 1, tsQ + 2 * STEP_NANOS)); - } -} diff --git a/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainFragmentShaderDriftTest.java b/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainFragmentShaderDriftTest.java new file mode 100644 index 000000000..01c916d7d --- /dev/null +++ b/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainFragmentShaderDriftTest.java @@ -0,0 +1,73 @@ +package com.gtnewhorizons.angelica.rendering.celeritas; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +class TerrainFragmentShaderDriftTest { + + private static final String UPSTREAM_FSH = "/assets/sodium/shaders/blocks/block_layer_opaque.fsh"; + private static final String ANGELICA_FSH = "/assets/angelica/shaders/blocks/block_layer_opaque.fsh"; + + private static final String REPLACED_SAMPLE = "vec4 diffuseColor = texture(u_BlockTex, v_TexCoord, v_MaterialMipBias);"; + + private static String resource(String path) throws IOException { + try (InputStream in = TerrainFragmentShaderDriftTest.class.getResourceAsStream(path)) { + assertNotNull(in, "missing resource: " + path); + return new String(in.readAllBytes(), StandardCharsets.UTF_8); + } + } + + /** Statements with whitespace collapsed, so line wrapping and indentation don't count as drift. */ + private static List normalize(String src) { + final List out = new ArrayList<>(); + final StringBuilder statement = new StringBuilder(); + for (String line : src.split("\\R")) { + final int comment = line.indexOf("//"); + if (comment >= 0) line = line.substring(0, comment); + line = line.trim(); + if (line.isEmpty()) continue; + if (line.startsWith("#")) { + out.add(line.replaceAll("\\s+", " ")); + continue; + } + for (char c : line.toCharArray()) { + statement.append(c); + if (c == ';' || c == '{' || c == '}') { + out.add(statement.toString().replaceAll("\\s+", " ").trim()); + statement.setLength(0); + } + } + statement.append(' '); + } + if (!statement.toString().isBlank()) out.add(statement.toString().replaceAll("\\s+", " ").trim()); + return out; + } + + @Test + void angelicaFragmentShaderContainsUpstream() throws IOException { + final List upstream = normalize(resource(UPSTREAM_FSH)); + final List angelica = normalize(resource(ANGELICA_FSH)); + + assertEquals(1, upstream.stream().filter(REPLACED_SAMPLE::equals).count(), "upstream diffuse sample line changed; update REPLACED_SAMPLE and Angelica's sampler selection"); + upstream.remove(REPLACED_SAMPLE); + + int a = 0; + for (int u = 0; u < upstream.size(); u++) { + final String want = upstream.get(u); + while (a < angelica.size() && !angelica.get(a).equals(want)) a++; + if (a == angelica.size()) { + fail("Angelica " + ANGELICA_FSH + " is missing upstream statement " + (u + 1) + " in order: " + want); + } + a++; + } + } +} diff --git a/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainShaderLinkTest.java b/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainShaderLinkTest.java new file mode 100644 index 000000000..d24503f6b --- /dev/null +++ b/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TerrainShaderLinkTest.java @@ -0,0 +1,124 @@ +package com.gtnewhorizons.angelica.rendering.celeritas; + +import com.gtnewhorizons.angelica.glsm.GLCoreTest; +import com.gtnewhorizons.angelica.glsm.GLStateManager; +import org.embeddedt.embeddium.impl.gl.attribute.GlVertexAttribute; +import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants; +import org.embeddedt.embeddium.impl.gl.shader.ShaderParser; +import org.embeddedt.embeddium.impl.render.chunk.compile.sorting.QuadPrimitiveType; +import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkFogMode; +import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderBindingPoints; +import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkMeshFormats; +import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexType; +import org.embeddedt.embeddium.impl.render.shader.ShaderLoader; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL20; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@GLCoreTest +class TerrainShaderLinkTest { + + private static final String VSH = "sodium:blocks/block_layer_opaque.vsh"; + private static final String FSH = "angelica:blocks/block_layer_opaque.fsh"; + + private static final int MIPPED_LEVELS = 4; + + enum Filter { + PLAIN(false, false, false), + TEXEL_SNAP(true, false, false), + RGSS(true, true, false), + ANISOTROPIC(true, false, true), + RGSS_ANISOTROPIC(true, true, true); + + final boolean texelSnap, rgss, anisotropic; + + Filter(boolean texelSnap, boolean rgss, boolean anisotropic) { + this.texelSnap = texelSnap; + this.rgss = rgss; + this.anisotropic = anisotropic; + } + } + + private static final Map VERTEX_TYPES = new LinkedHashMap<>(); + static { + VERTEX_TYPES.put("VANILLA_LIKE", ChunkMeshFormats.VANILLA_LIKE); + VERTEX_TYPES.put("COMPACT", ChunkMeshFormats.COMPACT); + } + + static Stream variants() { + final List out = new ArrayList<>(); + for (ChunkFogMode fog : ChunkFogMode.values()) { + for (String vertex : VERTEX_TYPES.keySet()) { + for (Filter filter : Filter.values()) { + for (boolean noMips : new boolean[] {false, true}) { + out.add(Arguments.of(fog, vertex, filter, noMips)); + } + } + } + } + return out.stream(); + } + + private static ShaderConstants constants(ChunkFogMode fog, ChunkVertexType vertexType, Filter filter, boolean noMips) { + final ShaderConstants.Builder b = ShaderConstants.builder(); + b.addAll(fog.getDefines()); + b.add("USE_FRAGMENT_DISCARD"); + b.addAll(AngelicaRenderPassConfiguration.samplerDefines(filter.rgss, filter.texelSnap, filter.anisotropic, noMips ? 0 : MIPPED_LEVELS)); + vertexType.getDefines().forEach(b::add); + b.addAll(QuadPrimitiveType.TRIANGULATED.getDefines()); + return b.build(); + } + + private static int compile(int type, String name, ShaderConstants constants) { + final String src = ShaderParser.parseShader(ShaderLoader.getShaderSource(name), ShaderLoader::getShaderSource, constants); + final int shader = GLStateManager.glCreateShader(type); + GLStateManager.glShaderSource(shader, src); + GLStateManager.glCompileShader(shader); + if (GLStateManager.glGetShaderi(shader, GL20.GL_COMPILE_STATUS) != GL11.GL_TRUE) { + final String log = GLStateManager.glGetShaderInfoLog(shader, 8192); + GLStateManager.glDeleteShader(shader); + throw new AssertionError(name + " failed to compile with " + constants.getDefineStrings() + ":\n" + log); + } + return shader; + } + + @ParameterizedTest(name = "fog={0} vertex={1} filter={2} noMips={3}") + @MethodSource("variants") + void links(ChunkFogMode fog, String vertex, Filter filter, boolean noMips) { + final ChunkVertexType vertexType = VERTEX_TYPES.get(vertex); + final ShaderConstants constants = constants(fog, vertexType, filter, noMips); + + final int vsh = compile(GL20.GL_VERTEX_SHADER, VSH, constants); + int fsh = 0; + int program = 0; + try { + fsh = compile(GL20.GL_FRAGMENT_SHADER, FSH, constants); + program = GLStateManager.glCreateProgram(); + GLStateManager.glAttachShader(program, vsh); + GLStateManager.glAttachShader(program, fsh); + int i = 0; + for (GlVertexAttribute attr : vertexType.getVertexFormat().getAttributes()) { + GLStateManager.glBindAttribLocation(program, i++, attr.getName()); + } + GLStateManager.glBindFragDataLocation(program, ChunkShaderBindingPoints.FRAG_COLOR, "fragColor"); + GLStateManager.glLinkProgram(program); + final int linked = program; + assertEquals(GL11.GL_TRUE, GLStateManager.glGetProgrami(program, GL20.GL_LINK_STATUS), + () -> "terrain program failed to link with " + constants.getDefineStrings() + ":\n" + GLStateManager.glGetProgramInfoLog(linked, 8192)); + } finally { + if (program != 0) GLStateManager.glDeleteProgram(program); + if (fsh != 0) GLStateManager.glDeleteShader(fsh); + GLStateManager.glDeleteShader(vsh); + } + } +} diff --git a/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TreeSortCameraBookkeepingGLTest.java b/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TreeSortCameraBookkeepingGLTest.java new file mode 100644 index 000000000..3ab836a81 --- /dev/null +++ b/src/test/java/com/gtnewhorizons/angelica/rendering/celeritas/TreeSortCameraBookkeepingGLTest.java @@ -0,0 +1,184 @@ +package com.gtnewhorizons.angelica.rendering.celeritas; + +import com.gtnewhorizons.angelica.glsm.GLCoreTest; +import org.embeddedt.embeddium.impl.gl.device.CommandList; +import org.embeddedt.embeddium.impl.gl.device.RenderDevice; +import org.embeddedt.embeddium.impl.render.chunk.ChunkRenderMatrices; +import org.embeddedt.embeddium.impl.render.chunk.ChunkRenderer; +import org.embeddedt.embeddium.impl.render.chunk.ChunkUpdateType; +import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration; +import org.embeddedt.embeddium.impl.render.chunk.RenderSection; +import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager; +import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildContext; +import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildOutput; +import org.embeddedt.embeddium.impl.render.chunk.compile.sorting.QuadPrimitiveType; +import org.embeddedt.embeddium.impl.render.chunk.compile.tasks.ChunkBuilderTask; +import org.embeddedt.embeddium.impl.render.chunk.fog.FogService; +import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderListIterable; +import org.embeddedt.embeddium.impl.render.chunk.lists.RenderListManager; +import org.embeddedt.embeddium.impl.render.chunk.occlusion.AsyncOcclusionMode; +import org.embeddedt.embeddium.impl.render.chunk.sorting.CutPlaneIndex; +import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass; +import org.embeddedt.embeddium.impl.render.chunk.terrain.material.Material; +import org.embeddedt.embeddium.impl.render.chunk.terrain.material.parameters.AlphaCutoffParameter; +import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkMeshFormats; +import org.embeddedt.embeddium.impl.render.viewport.CameraTransform; +import org.embeddedt.embeddium.impl.render.viewport.Viewport; +import org.embeddedt.embeddium.impl.render.viewport.frustum.Frustum; +import org.joml.Vector3d; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@GLCoreTest +class TreeSortCameraBookkeepingGLTest { + + private static final Frustum ALWAYS_VISIBLE = (minX, minY, minZ, maxX, maxY, maxZ) -> true; + + private static CommandList commandList; + + private TestRsm rsm; + private RenderSection section; + private int frame; + + private static final class TestRsm extends RenderSectionManager { + boolean inShadowPass; + + TestRsm(RenderPassConfiguration config, CommandList commandList) { + // -1 threads: no workers; SORT entries stay queued since updateChunks() is never called + super(config, () -> new ChunkBuildContext(config), (device, c) -> new NoopRenderer(c), 2, commandList, 0, 16, -1, true); + } + + @Override protected AsyncOcclusionMode getAsyncOcclusionMode() { return AsyncOcclusionMode.NONE; } + @Override protected boolean useFogOcclusion() { return false; } + @Override protected boolean useRasterOcclusionCulling() { return false; } + @Override protected boolean shouldUseOcclusionCulling(Viewport viewport, boolean spectator) { return false; } + @Override public FogService getFogService() { throw new UnsupportedOperationException(); } + @Override protected boolean isSectionVisuallyEmpty(int x, int y, int z) { return true; } + @Override protected ChunkBuilderTask createRebuildTask(RenderSection render, int frame) { return null; } + @Override public boolean isInShadowPass() { return this.inShadowPass; } + + Vector3d cameraPosition() { return this.cameraPosition; } + Vector3d previousCameraPosition() { return this.previousCameraPosition; } + RenderListManager terrainLists() { return this.renderListManager; } + RenderListManager shadowLists() { return this.shadowRenderListManager; } + } + + private record NoopRenderer(RenderPassConfiguration getRenderPassConfiguration) implements ChunkRenderer { + @Override + public void render(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderListIterable renderLists, TerrainRenderPass pass, CameraTransform occlusionCamera, CameraTransform camera) {} + + @Override + public void delete(CommandList commandList) {} + } + + @BeforeAll + static void setUpDevice() { + RenderDevice.enterManagedCode(); + commandList = RenderDevice.INSTANCE.createCommandList(); + } + + @AfterAll + static void tearDownDevice() { + RenderDevice.exitManagedCode(); + } + + @BeforeEach + void setUp() throws ReflectiveOperationException { + final TerrainRenderPass solid = new TerrainRenderPass("tree_sort_solid", TerrainRenderPass.PipelineState.DEFAULT, false, false, false, false, ChunkMeshFormats.VANILLA_LIKE, QuadPrimitiveType.TRIANGULATED, Map.of()); + final TerrainRenderPass translucent = new TerrainRenderPass("tree_sort_translucent", TerrainRenderPass.PipelineState.DEFAULT, true, false, true, false, ChunkMeshFormats.VANILLA_LIKE, QuadPrimitiveType.TRIANGULATED, Map.of()); + final Material solidMaterial = new Material(solid, AlphaCutoffParameter.ZERO, false); + final Material translucentMaterial = new Material(translucent, AlphaCutoffParameter.ZERO, true); + final RenderPassConfiguration config = new RenderPassConfiguration<>(Map.of(), Map.of("solid", List.of(solid), "translucent", List.of(translucent)), solidMaterial, solidMaterial, translucentMaterial); + + rsm = new TestRsm(config, commandList); + rsm.onSectionAdded(0, 0, 0); + section = rsm.getAllRenderSections().iterator().next(); + + final Field f = RenderSectionManager.class.getDeclaredField("cutPlaneIndex"); + f.setAccessible(true); + @SuppressWarnings("unchecked") + final CutPlaneIndex index = (CutPlaneIndex) f.get(rsm); + index.put(section, new float[][] { { 8f }, {}, {} }, section.getOriginX(), section.getOriginY(), section.getOriginZ()); + assertEquals(1, index.size()); + } + + @AfterEach + void tearDown() { + if (rsm != null) rsm.destroy(); + rsm = null; + } + + private static Viewport at(double x) { + return new Viewport(ALWAYS_VISIBLE, new Vector3d(x, 8.5, 8.5)); + } + + private static Viewport farShadowViewport() { + return new Viewport(ALWAYS_VISIBLE, new Vector3d(520.5, 100.5, 520.5)); + } + + private void update(double x) { + rsm.update(at(x), ++frame, false); + } + + private void shadowThenMain(double x) { + rsm.inShadowPass = true; + rsm.updateForShadowPass(at(x), farShadowViewport(), ++frame, false); + rsm.inShadowPass = false; + assertTrue(rsm.didShadowPassRunThisFrame()); + rsm.update(at(x), frame, false); + assertFalse(rsm.didShadowPassRunThisFrame()); + } + + private long queued(RenderListManager lists, ChunkUpdateType type) { + return lists.getRebuildLists().byUpdateType().get(type).stream().filter(s -> s == section).count(); + } + + private void assertSortQueuedInTerrain() { + assertEquals(ChunkUpdateType.SORT, section.getPendingUpdate()); + assertEquals(1, queued(rsm.terrainLists(), ChunkUpdateType.SORT)); + assertEquals(0, queued(rsm.shadowLists(), ChunkUpdateType.SORT)); + } + + @Test + void crossingSchedulesSort() { + update(7.5); + assertNull(section.getPendingUpdate()); + + update(8.5); + assertNotNull(rsm.previousCameraPosition()); + assertNotSame(rsm.previousCameraPosition(), rsm.cameraPosition()); + assertEquals(7.5, rsm.previousCameraPosition().x); + assertEquals(8.5, rsm.cameraPosition().x); + assertSortQueuedInTerrain(); + } + + @Test + void shadowFrameKeepsPlayerCameraBookkeeping() { + update(7.5); + + shadowThenMain(8.5); + assertEquals(new Vector3d(8.5, 8.5, 8.5), rsm.cameraPosition()); + assertEquals(new Vector3d(7.5, 8.5, 8.5), rsm.previousCameraPosition()); + assertSortQueuedInTerrain(); + + shadowThenMain(8.5); + assertNotSame(rsm.previousCameraPosition(), rsm.cameraPosition()); + assertEquals(new Vector3d(8.5, 8.5, 8.5), rsm.previousCameraPosition()); + assertEquals(new Vector3d(8.5, 8.5, 8.5), rsm.cameraPosition()); + } +} diff --git a/src/test/java/com/gtnewhorizons/angelica/rendering/culling/IndirectCullDrawParityTest.java b/src/test/java/com/gtnewhorizons/angelica/rendering/culling/IndirectCullDrawParityTest.java index 014ce175a..24ff93310 100644 --- a/src/test/java/com/gtnewhorizons/angelica/rendering/culling/IndirectCullDrawParityTest.java +++ b/src/test/java/com/gtnewhorizons/angelica/rendering/culling/IndirectCullDrawParityTest.java @@ -17,7 +17,6 @@ import org.embeddedt.embeddium.impl.gl.array.GlVertexArray; import org.embeddedt.embeddium.impl.gl.buffer.GlBufferUsage; import org.embeddedt.embeddium.impl.gl.buffer.GlMutableBuffer; import org.embeddedt.embeddium.impl.gl.device.CommandList; -import org.embeddedt.embeddium.impl.gl.device.GLRenderDevice; import org.embeddedt.embeddium.impl.gl.device.RenderDevice; import org.embeddedt.embeddium.impl.gl.tessellation.GlPrimitiveType; import org.embeddedt.embeddium.impl.gl.tessellation.GlVertexArrayTessellation; @@ -183,7 +182,6 @@ class IndirectCullDrawParityTest { @BeforeAll static void setUpDevice() { - GLRenderDevice.VANILLA_STATE_RESETTER = () -> {}; RenderDevice.enterManagedCode(); commandList = RenderDevice.INSTANCE.createCommandList(); program = buildProgram(); diff --git a/src/test/java/com/gtnewhorizons/angelica/rendering/culling/TerrainPassPreparationTest.java b/src/test/java/com/gtnewhorizons/angelica/rendering/culling/TerrainPassPreparationTest.java index 01001b723..46cfc06dc 100644 --- a/src/test/java/com/gtnewhorizons/angelica/rendering/culling/TerrainPassPreparationTest.java +++ b/src/test/java/com/gtnewhorizons/angelica/rendering/culling/TerrainPassPreparationTest.java @@ -21,6 +21,8 @@ import org.joml.Matrix4f; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.lang.reflect.Field; import java.util.Iterator; @@ -157,6 +159,23 @@ class TerrainPassPreparationTest { assertEquals(((long) base << 32) | 1L, ((long[]) field(expected, expected.getClass(), "regionRanges"))[0]); } + private int indexPointerMaskOf(String pass) { + final Object state = state(pass); + return (int) field(state, state.getClass(), "indexPointerMask"); + } + + @SuppressWarnings("unchecked") + private void useUnsortedTranslucentPass() { + storage[2].delete(); + final TerrainRenderPass pass = new TerrainRenderPass("preparation2-unsorted", null, false, true, false, false, ChunkMeshFormats.VANILLA_LIKE, QuadPrimitiveType.TRIANGULATED, Map.of()); + final Storage newStorage = new Storage(false); + passes[2] = pass; + storage[2] = newStorage; + AngelicaRenderPassConfiguration.TRANSLUCENT_PASS = pass; + final Map attached = (Map) field(region, RenderRegion.class, "sectionRenderData"); + attached.put(pass, newStorage); + } + @Test void preparesAndSelectsAllEightPassCombinations() { for (int mask = 0; mask < 8; mask++) { @@ -236,6 +255,15 @@ class TerrainPassPreparationTest { select(lists, 2); assertRange("sortedPass", 1); } + + @ParameterizedTest(name = "passMask={0}") + @ValueSource(ints = {4, 5}) // translucent alone, and chained after solid + void unsortedTranslucentPassClearsIndexPointerMask(int passMask) { + useUnsortedTranslucentPass(); + prepare(lists(passMask)); + assertEquals(0, indexPointerMaskOf("sortedPass")); + } + @Test void independentlyResetCommandStorageCannotSelectOldPreparation() { prepare(lists(7)); diff --git a/src/test/java/net/coderbot/iris/pipeline/transform/RwImageStoreExtractorTest.java b/src/test/java/net/coderbot/iris/pipeline/transform/RwImageStoreExtractorTest.java index e805f9055..721964548 100644 --- a/src/test/java/net/coderbot/iris/pipeline/transform/RwImageStoreExtractorTest.java +++ b/src/test/java/net/coderbot/iris/pipeline/transform/RwImageStoreExtractorTest.java @@ -209,11 +209,9 @@ class RwImageStoreExtractorTest { @Test void knownAttributeVariants_includesCeleritasTypings() { final var variants = RwImageStoreExtractor.CHUNK_VERTEX_ATTRS; assertNotNull(variants.get("a_PosId").get("vec3")); - assertNotNull(variants.get("a_PosId").get("uvec4")); assertNotNull(variants.get("a_Color").get("vec4")); assertNotNull(variants.get("a_TexCoord").get("vec2")); assertNotNull(variants.get("a_LightCoord").get("uint")); - assertNotNull(variants.get("a_LightCoord").get("ivec2")); assertNotNull(variants.get("mc_Entity").get("vec4")); assertNotNull(variants.get("mc_Entity").get("uint")); assertNotNull(variants.get("mc_midTexCoord").get("vec4")); @@ -289,15 +287,6 @@ class RwImageStoreExtractorTest { assertTrue(s.contains("vec4 at_midBlock;")); } - @Test void celeritas_computeContainsDeadBranchStubGlobals() { - final String s = RwImageStoreExtractor.tryExtract(CELERITAS_PATCHED_VOX_VSH, PatchShaderType.VERTEX, "CELERITAS_TERRAIN").computeSource(); - assertTrue(s.contains("uvec4 a_PosId;"), "compressed-branch stub a_PosId"); - assertTrue(s.contains("ivec2 a_LightCoord;"), "compressed-branch stub a_LightCoord"); - assertTrue(s.contains("#ifdef USE_VERTEX_COMPRESSION"), "expected #ifdef guard around dual-branch globals"); - assertTrue(s.contains("#else")); - assertTrue(s.contains("#endif")); - } - @Test void celeritas_unpackContainsCeleritasFormulas() { final String s = RwImageStoreExtractor.tryExtract(CELERITAS_PATCHED_VOX_VSH, PatchShaderType.VERTEX, "CELERITAS_TERRAIN").computeSource(); assertTrue(s.contains("a_PosId = vec3(uintBitsToFloat(_vg_vbuf.data[base + 0u])")); @@ -305,14 +294,11 @@ class RwImageStoreExtractorTest { assertTrue(s.contains("a_LightCoord = _vg_vbuf.data[base + 6u]")); assertTrue(s.contains("mc_Entity = _vg_vbuf.data[base + 10u]"), "celeritas-typed mc_Entity must be delivered as the raw packed uint"); assertTrue(s.contains("mc_midTexCoord = vec2(float(_vg_vbuf.data[base + 7u] & 0xFFFFu),"), "mc_midTexCoord is an unnormalized ushort delivered raw by the vertex stage, so the compute prelude must not rescale it"); - assertTrue(s.contains("a_PosId = uvec4(0u, 0u, 0u, 0u)")); - assertTrue(s.contains("a_LightCoord = ivec2(0, 0)")); } @Test void celeritas_computeDecodesVertexGlobals() { final String s = RwImageStoreExtractor.tryExtract(CELERITAS_PATCHED_VOX_VSH, PatchShaderType.VERTEX, "CELERITAS_TERRAIN").computeSource(); - assertTrue(s.contains("_vert_position = a_PosId;"), "uncompressed branch must decode position\n\n" + s); - assertTrue(s.contains("_vert_position = vec3(a_PosId.xyz) * VERT_POS_SCALE + VERT_POS_OFFSET;"), "compressed branch must decode position\n\n" + s); + assertTrue(s.contains("_vert_position = a_PosId;"), "vertex decode must assign position\n\n" + s); assertTrue(s.contains("_draw_id = (_vg_draw_params >> 8) & 0xFFu;"), "_get_draw_translation depends on _draw_id\n\n" + s); } @@ -551,13 +537,6 @@ class RwImageStoreExtractorTest { assertTrue(s.contains("vec2 mc_midTexCoord;")); } - @Test void celeritasIfdefAttrs_emitBothVariantsGuarded() { - final var result = RwImageStoreExtractor.tryExtract(CELERITAS_PATCHED_VOX_VSH, PatchShaderType.VERTEX, "CELERITAS_TERRAIN"); - final String s = result.computeSource(); - assertTrue(s.contains("uvec4 a_PosId;"), "compressed-branch stub a_PosId\n\n" + s); - assertTrue(s.contains("vec3 a_PosId;"), "uncompressed-branch live a_PosId\n\n" + s); - } - private static final String IMAGE_STORE_AS_BRACELESS_IF_BODY_VSH = String.join("\n", "#version 460 core", "in vec4 iris_Vertex;", diff --git a/src/test/java/org/embeddedt/embeddium/impl/render/chunk/data/SectionRenderDataLayoutTest.java b/src/test/java/org/embeddedt/embeddium/impl/render/chunk/data/SectionRenderDataLayoutTest.java index 11849b2af..05faefa53 100644 --- a/src/test/java/org/embeddedt/embeddium/impl/render/chunk/data/SectionRenderDataLayoutTest.java +++ b/src/test/java/org/embeddedt/embeddium/impl/render/chunk/data/SectionRenderDataLayoutTest.java @@ -4,7 +4,7 @@ import org.embeddedt.embeddium.impl.gl.util.VertexRange; import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFacing; import org.embeddedt.embeddium.impl.render.chunk.compile.sorting.ChunkPrimitiveType; import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion; -import org.embeddedt.embeddium.impl.render.chunk.sorting.TranslucentQuadAnalyzer; +import org.embeddedt.embeddium.impl.render.chunk.sorting.SortState; import org.junit.jupiter.api.Test; import java.nio.ByteBuffer; @@ -41,7 +41,7 @@ class SectionRenderDataLayoutTest { } @Override - public void generateSortedIndexBuffer(ByteBuffer indexBuffer, int numPrimitives, TranslucentQuadAnalyzer.SortState chunkData, float x, float y, float z) { + public void generateSortedIndexBuffer(ByteBuffer indexBuffer, int numPrimitives, SortState chunkData, float x, float y, float z) { throw new UnsupportedOperationException(); } }