diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderConstants.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderConstants.java index 8a419d5d2c..c0e6aff627 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderConstants.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderConstants.java @@ -9,6 +9,10 @@ private ShaderConstants(List defines) { this.defines = defines; } + public static ShaderConstants empty() { + return new ShaderConstants(List.of()); + } + public List getDefineStrings() { return this.defines; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat4v.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat4v.java index c968f69bdb..1d961caf3f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat4v.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat4v.java @@ -1,5 +1,6 @@ package net.caffeinemc.mods.sodium.client.gl.shader.uniform; +import org.joml.Vector4fc; import org.lwjgl.opengl.GL30C; public class GlUniformFloat4v extends GlUniform { @@ -15,4 +16,12 @@ public void set(float[] value) { GL30C.glUniform4fv(this.index, value); } + + public void set(float x, float y, float z, float w) { + GL30C.glUniform4f(this.index, x, y, z, w); + } + + public void set(Vector4fc vec) { + this.set(vec.x(), vec.y(), vec.z(), vec.w()); + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java index f0cb6c37c9..98dbd809d1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java @@ -293,6 +293,14 @@ public static OptionPage performance() { .setBinding((opts, value) -> opts.performance.useEntityCulling = value, opts -> opts.performance.useEntityCulling) .build() ) + .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) + .setName(Component.translatable("sodium.options.use_single_pass_compositing.name")) + .setTooltip(Component.translatable("sodium.options.use_single_pass_compositing.tooltip")) + .setControl(TickBoxControl::new) + .setImpact(OptionImpact.HIGH) + .setBinding((opts, value) -> opts.performance.useSinglePassCompositing = value, opts -> opts.performance.useSinglePassCompositing) + .build() + ) .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) .setName(Component.translatable("sodium.options.animate_only_visible_textures.name")) .setTooltip(Component.translatable("sodium.options.animate_only_visible_textures.tooltip")) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java index ec681430b8..acfb803af3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java @@ -45,6 +45,7 @@ public static class PerformanceSettings { public boolean useFogOcclusion = true; public boolean useBlockFaceCulling = true; public boolean useNoErrorGLContext = true; + public boolean useSinglePassCompositing = true; @SerializedName("sorting_enabled_v2") // reset the older option in configs before we started hiding it public boolean sortingEnabled = true; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/CompositePass.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/CompositePass.java new file mode 100644 index 0000000000..7b7d5515e0 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/CompositePass.java @@ -0,0 +1,172 @@ +package net.caffeinemc.mods.sodium.client.render; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.VertexBuffer; +import net.caffeinemc.mods.sodium.client.gl.shader.GlProgram; +import net.caffeinemc.mods.sodium.client.gl.shader.ShaderConstants; +import net.caffeinemc.mods.sodium.client.gl.shader.ShaderLoader; +import net.caffeinemc.mods.sodium.client.gl.shader.ShaderType; +import net.caffeinemc.mods.sodium.client.gl.shader.uniform.GlUniformFloat4v; +import net.caffeinemc.mods.sodium.client.gl.shader.uniform.GlUniformInt; +import net.caffeinemc.mods.sodium.client.render.chunk.shader.ShaderBindingContext; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector4f; +import org.lwjgl.opengl.GL33C; +import org.lwjgl.opengl.GL45C; + +import java.util.EnumSet; + +public class CompositePass { + public static boolean ENABLED; + + public static boolean ENTITY_GLOW_IS_ACTIVE = false; + + private static final Vector4f VIGNETTE_COLOR = new Vector4f(); + + private static int DEFAULT_VERTEX_ARRAY; + private static GlProgram COMPOSITE_PROGRAM; + + public static void composite(@NotNull RenderTarget mainRT, + @Nullable RenderTarget entityRT, + int width, + int height) { + EnumSet blendOptions = EnumSet.noneOf(BlendOption.class); + + if (entityRT != null && isEntityGlowIsActive()) { + blendOptions.add(BlendOption.USE_ENTITY_GLOW); + } + + if (isVignetteActive()) { + blendOptions.add(BlendOption.USE_VIGNETTE); + } + + if (blendOptions.isEmpty()) { + // If no compositing is necessary, we can just blit the main render target to + // the default framebuffer and avoid using the rasterization pipeline. + mainRT.blitToScreen(width, height, true); + return; + } + + if (COMPOSITE_PROGRAM == null) { + COMPOSITE_PROGRAM = GlProgram.builder(ResourceLocation.fromNamespaceAndPath("sodium", "composite")) + .attachShader(ShaderLoader.loadShader(ShaderType.VERTEX, ResourceLocation.fromNamespaceAndPath("sodium", "composite.vsh"), ShaderConstants.empty())) + .attachShader(ShaderLoader.loadShader(ShaderType.FRAGMENT, ResourceLocation.fromNamespaceAndPath("sodium", "composite.fsh"), ShaderConstants.empty())) + .bindFragmentData("fragColor", 0) + .link(CompositeProgramInterface::new); + + DEFAULT_VERTEX_ARRAY = GL33C.glGenVertexArrays(); + } + + VertexBuffer.unbind(); + + GlStateManager._disableDepthTest(); + GlStateManager._disableBlend(); + + GlStateManager._colorMask(true, true, true, false); + GlStateManager._depthMask(false); + + GlStateManager._glUseProgram(COMPOSITE_PROGRAM.handle()); + GlStateManager._glBindVertexArray(DEFAULT_VERTEX_ARRAY); + + var uniforms = COMPOSITE_PROGRAM.getInterface(); + uniforms.mainColorRT.set(0); + uniforms.entityColorRT.set(1); + uniforms.vignetteTexture.set(2); + uniforms.vignetteColorModulator.set(VIGNETTE_COLOR); + uniforms.blendOptions.set(toBitfield(blendOptions)); + + bindTextureToUnit(0, mainRT.getColorTextureId()); + + if (entityRT != null) { + bindTextureToUnit(1, blendOptions.contains(BlendOption.USE_ENTITY_GLOW) ? entityRT.getColorTextureId() : 0 /* default texture */); + } + + bindTextureToUnit(2, getVignetteTextureId()); + + GL45C.glDrawArrays(GL45C.GL_TRIANGLES, 0, 3); + + unbindTextureFromUnit(0); + unbindTextureFromUnit(1); + unbindTextureFromUnit(2); + + GlStateManager._depthMask(true); + GlStateManager._colorMask(true, true, true, true); + + GlStateManager._enableBlend(); + GlStateManager._enableDepthTest(); + + CompositePass.ENTITY_GLOW_IS_ACTIVE = false; + } + + private static boolean isEntityGlowIsActive() { + return CompositePass.ENTITY_GLOW_IS_ACTIVE; + } + + private static boolean isVignetteActive() { + return VIGNETTE_COLOR.w() >= 0.0025f; + } + + private static int toBitfield(EnumSet set) { + int bits = 0; + + for (var value : set) { + bits = (1 << value.ordinal()); + } + + return bits; + } + + private static void unbindTextureFromUnit(int slot) { + RenderSystem.activeTexture(GL33C.GL_TEXTURE0 + slot); + RenderSystem.bindTexture(0); + } + + private static void bindTextureToUnit(int slot, int texture) { + RenderSystem.activeTexture(GL33C.GL_TEXTURE0 + slot); + RenderSystem.bindTexture(texture); + } + + public static void setVignetteColor(float[] color) { + VIGNETTE_COLOR.set(color); + } + + private static class CompositeProgramInterface { + private final GlUniformInt mainColorRT; + private final GlUniformInt entityColorRT; + + private final GlUniformInt vignetteTexture; + private final GlUniformFloat4v vignetteColorModulator; + + private final GlUniformInt blendOptions; + + public CompositeProgramInterface(ShaderBindingContext ctx) { + this.mainColorRT = ctx.bindUniform("mainColorRT", GlUniformInt::new); + this.entityColorRT = ctx.bindUniform("entityColorRT", GlUniformInt::new); + + this.vignetteTexture = ctx.bindUniform("vignetteTexture", GlUniformInt::new); + this.vignetteColorModulator = ctx.bindUniform("vignetteColorModulator", GlUniformFloat4v::new); + + this.blendOptions = ctx.bindUniform("blendOptions", GlUniformInt::new); + } + } + + private static int getVignetteTextureId() { + Minecraft minecraft = Minecraft.getInstance(); + TextureManager textureManager = minecraft.getTextureManager(); + AbstractTexture texture = textureManager.getTexture(ResourceLocation.parse("textures/misc/vignette.png")); + + return texture.getId(); + } + + private enum BlendOption { + USE_ENTITY_GLOW, + USE_VIGNETTE + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/GameRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/GameRendererMixin.java new file mode 100644 index 0000000000..537d9b4e2a --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/GameRendererMixin.java @@ -0,0 +1,37 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.compositing; + +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.Window; +import net.caffeinemc.mods.sodium.client.render.CompositePass; +import net.minecraft.client.DeltaTracker; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GameRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(GameRenderer.class) +public class GameRendererMixin { + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;clear(IZ)V", ordinal = 0, shift = At.Shift.BEFORE)) + private void preRenderGui(DeltaTracker deltaTracker, boolean renderLevel, CallbackInfo ci) { + if (!CompositePass.ENABLED) { + return; + } + + Minecraft minecraft = Minecraft.getInstance(); + Window window = minecraft.getWindow(); + + int width = window.getWidth(); + int height = window.getHeight(); + + RenderTarget mainRT = minecraft.getMainRenderTarget(); + mainRT.unbindWrite(); + + if (minecraft.level != null) { + CompositePass.composite(mainRT, minecraft.levelRenderer.entityTarget(), width, height); + } else { + mainRT.blitToScreen(width, height, true); + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/GuiMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/GuiMixin.java new file mode 100644 index 0000000000..adefa34fee --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/GuiMixin.java @@ -0,0 +1,26 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.compositing; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.blaze3d.systems.RenderSystem; +import net.caffeinemc.mods.sodium.client.render.CompositePass; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.resources.ResourceLocation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(Gui.class) +public class GuiMixin { + // We can't separately query the vignette color, so we instead hook the render function + // and look at the render state to figure out what color is used. + @WrapOperation(method = "renderVignette", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;blit(Lnet/minecraft/resources/ResourceLocation;IIIFFIIII)V")) + public void beforeBlit(GuiGraphics gui, ResourceLocation texture, int x, int y, int z, float u1, float v1, int u2, int v2, int textureWidth, int textureHeight, Operation original) { + if (CompositePass.ENABLED) { + // The blit will happen later in the final compositing pass + CompositePass.setVignetteColor(RenderSystem.getShaderColor()); + } else { + original.call(gui, texture, x, y, z, u1, v1, u2, v2, textureWidth, textureHeight); + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/LevelRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/LevelRendererMixin.java new file mode 100644 index 0000000000..16c1959567 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/LevelRendererMixin.java @@ -0,0 +1,34 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.compositing; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.caffeinemc.mods.sodium.client.render.CompositePass; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.OutlineBufferSource; +import net.minecraft.world.entity.Entity; +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(LevelRenderer.class) +public class LevelRendererMixin { + @Inject(method = "doEntityOutline", at = @At("HEAD"), cancellable = true) + public void cancelEntityOutlineComposite(CallbackInfo ci) { + // Normally, the entity outline buffer would be blurred and then composited into the + // main render target, but we want to defer this until our final compositing pass. + if (CompositePass.ENABLED) { + ci.cancel(); + } + } + + @Inject(method = "renderEntity", at = @At("HEAD")) + private void onEntityRendered(Entity entity, double d, double e, double f, float g, PoseStack poseStack, MultiBufferSource multiBufferSource, CallbackInfo ci) { + // If any entities are rendered with an outline effect, mark the entity glow render target + // as needing to be composited into the final image. Otherwise, we can skip compositing when + // there are no glowing entities. + if (multiBufferSource instanceof OutlineBufferSource) { + CompositePass.ENTITY_GLOW_IS_ACTIVE = true; + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/MainTargetMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/MainTargetMixin.java new file mode 100644 index 0000000000..484ac50a02 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/MainTargetMixin.java @@ -0,0 +1,134 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.compositing; + +import com.mojang.blaze3d.pipeline.MainTarget; +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import org.lwjgl.opengl.GL32C; +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 org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(MainTarget.class) +public abstract class MainTargetMixin extends RenderTarget { + @Unique + private int depthRenderBufferId = -1; + + private MainTargetMixin(boolean useDepthBuffer) { + super(useDepthBuffer); + } + + /** + * @author JellySquid + * @reason Delegate to the default implementation (which is then overwritten) + */ + @Overwrite + protected void createFrameBuffer(int width, int height) { + this.createBuffers(width, height, this.useDepth); + } + + // For some reason, the main render target implements custom initialization behavior, + // but resizing the render target uses the default implementation. For consistency’s sake when + // resizing, we override the default implementation too. + @Override + public void createBuffers(int width, int height, boolean checkError) { + RenderSystem.assertOnRenderThreadOrInit(); + MainTarget.Dimension dimension = this.allocateAttachments(width, height); + + this.frameBufferId = GlStateManager.glGenFramebuffers(); + + GlStateManager._glBindFramebuffer(GL32C.GL_FRAMEBUFFER, this.frameBufferId); + + attachColorBuffer(this.colorTextureId); + attachDepthBuffer(this.depthRenderBufferId); + + this.checkStatus(); + + GlStateManager._glBindFramebuffer(GL32C.GL_FRAMEBUFFER, 0); + + this.viewWidth = dimension.width; + this.viewHeight = dimension.height; + this.width = dimension.width; + this.height = dimension.height; + } + + @Unique + private static void attachColorBuffer(int colorTextureId) { + GlStateManager._bindTexture(colorTextureId); + GlStateManager._texParameter(GL32C.GL_TEXTURE_2D, 10241, 9728); + GlStateManager._texParameter(GL32C.GL_TEXTURE_2D, 10240, 9728); + GlStateManager._texParameter(GL32C.GL_TEXTURE_2D, 10242, 33071); + GlStateManager._texParameter(GL32C.GL_TEXTURE_2D, 10243, 33071); + GlStateManager._glFramebufferTexture2D(GL32C.GL_FRAMEBUFFER, GL32C.GL_COLOR_ATTACHMENT0, + GL32C.GL_TEXTURE_2D, colorTextureId, 0); + } + + @Unique + private static void attachDepthBuffer(int depthRenderBufferId) { + GL32C.glBindRenderbuffer(GL32C.GL_RENDERBUFFER, depthRenderBufferId); + GL32C.glFramebufferRenderbuffer(GL32C.GL_FRAMEBUFFER, GL32C.GL_DEPTH_ATTACHMENT, + GL32C.GL_RENDERBUFFER, depthRenderBufferId); + GL32C.glBindRenderbuffer(GL32C.GL_RENDERBUFFER, 0); + } + + @Inject(method = "allocateAttachments", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;assertOnRenderThreadOrInit()V", shift = At.Shift.AFTER)) + private void initDepthAttachment(int width, int height, CallbackInfoReturnable cir) { + this.depthRenderBufferId = GL32C.glGenRenderbuffers(); + } + + @Shadow + protected abstract MainTarget.Dimension allocateAttachments(int width, int height); + + /** + * @author JellySquid + * @reason Use RGB format instead of RGBA format + */ + @Overwrite + private boolean allocateColorAttachment(MainTarget.Dimension dimension) { + GlStateManager._getError(); // clear the existing error, if any + + // The main render target does not need alpha, since the skybox is always rendered with full + // opacity. Furthermore, when shaders attempt to read back the alpha from the color texture, + // the default alpha (1.0, fully opaque) will always be used, so it should not cause problems. + // Since we need to read from this color texture, it's not possible to replace it with a render buffer. + GlStateManager._bindTexture(this.colorTextureId); + GlStateManager._texImage2D(GL32C.GL_TEXTURE_2D, 0, GL32C.GL_RGB8, dimension.width, dimension.height, + 0, GL32C.GL_RGB, GL32C.GL_UNSIGNED_BYTE, null); + + return GlStateManager._getError() != GL32C.GL_OUT_OF_MEMORY; + } + + /** + * @author JellySquid + * @reason Replace depth texture with depth render buffer + */ + @Overwrite + private boolean allocateDepthAttachment(MainTarget.Dimension dimension) { + GlStateManager._getError(); // clear the existing error, if any + + // Depth textures are slower than depth render buffers, since it (in theory) prevents the GPU + // from applying certain optimizations. At least with Intel Xe graphics, using a render buffer seems + // to be slightly faster. But since fragment sorting needs access to the depth information, it is + // necessary to fall back to a depth texture when Fabulous mode is activated. + GL32C.glBindRenderbuffer(GL32C.GL_RENDERBUFFER, this.depthRenderBufferId); + GL32C.glRenderbufferStorage(GL32C.GL_RENDERBUFFER, GL32C.GL_DEPTH_COMPONENT32F, + dimension.width, dimension.height); + GL32C.glBindRenderbuffer(GL32C.GL_RENDERBUFFER, 0); + + return GlStateManager._getError() != GL32C.GL_OUT_OF_MEMORY; + } + + @Override + public void destroyBuffers() { + super.destroyBuffers(); + + if (this.depthRenderBufferId != -1) { + GL32C.glDeleteRenderbuffers(this.depthRenderBufferId); + this.depthRenderBufferId = -1; + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/MinecraftMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/MinecraftMixin.java new file mode 100644 index 0000000000..753652d76a --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/MinecraftMixin.java @@ -0,0 +1,34 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.compositing; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.blaze3d.pipeline.RenderTarget; +import net.caffeinemc.mods.sodium.client.SodiumClientMod; +import net.caffeinemc.mods.sodium.client.render.CompositePass; +import net.minecraft.client.Minecraft; +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(Minecraft.class) +public class MinecraftMixin { + @Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;clear(IZ)V", ordinal = 0, shift = At.Shift.BEFORE)) + private void preRunTick(boolean outOfMemory, CallbackInfo ci) { + CompositePass.ENABLED = SodiumClientMod.options().performance.useSinglePassCompositing; + } + + @WrapOperation(method = "runTick", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;clear(IZ)V", ordinal = 0)) + private void cancelClear(int clearMask, boolean checkError, Operation original) { + if (!CompositePass.ENABLED) { + original.call(clearMask, checkError); + } + } + + @WrapOperation(method = "runTick", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/pipeline/RenderTarget;blitToScreen(II)V")) + private void redirectCompositePass(RenderTarget renderTarget, int width, int height, Operation original) { + if (!CompositePass.ENABLED) { + original.call(renderTarget, width, height); + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/ScreenMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/ScreenMixin.java new file mode 100644 index 0000000000..676610c9e9 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/ScreenMixin.java @@ -0,0 +1,19 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.compositing; + +import net.caffeinemc.mods.sodium.client.render.CompositePass; +import net.minecraft.client.gui.screens.Screen; +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(Screen.class) +public class ScreenMixin { + @Inject(method = "renderBlurredBackground", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/pipeline/RenderTarget;bindWrite(Z)V", shift = At.Shift.BEFORE), cancellable = true) + private void redirectBindDefaultFramebuffer(float partialTicks, CallbackInfo ci) { + // FIXME + if (CompositePass.ENABLED) { + ci.cancel(); + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/WindowMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/WindowMixin.java new file mode 100644 index 0000000000..9152bdcac2 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/compositing/WindowMixin.java @@ -0,0 +1,19 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.compositing; + +import com.mojang.blaze3d.platform.Window; +import org.lwjgl.glfw.GLFW; +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(Window.class) +public class WindowMixin { + @Inject(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwCreateWindow(IILjava/lang/CharSequence;JJ)J", shift = At.Shift.BEFORE)) + private void preCreateWindow(CallbackInfo ci) { + GLFW.glfwWindowHint(GLFW.GLFW_RED_BITS, 8); + GLFW.glfwWindowHint(GLFW.GLFW_GREEN_BITS, 8); + GLFW.glfwWindowHint(GLFW.GLFW_BLUE_BITS, 8); + GLFW.glfwWindowHint(GLFW.GLFW_ALPHA_BITS, 0); // Alpha channel is not needed for default framebuffer + } +} diff --git a/common/src/main/resources/assets/sodium/lang/en_us.json b/common/src/main/resources/assets/sodium/lang/en_us.json index b128222a27..03737e512b 100644 --- a/common/src/main/resources/assets/sodium/lang/en_us.json +++ b/common/src/main/resources/assets/sodium/lang/en_us.json @@ -37,6 +37,8 @@ "sodium.options.use_fog_occlusion.tooltip": "If enabled, chunks which are determined to be fully hidden by fog effects will not be rendered, helping to improve performance. The improvement can be more dramatic when fog effects are heavier (such as while underwater), but it may cause undesirable visual artifacts between the sky and fog in some scenarios.", "sodium.options.use_entity_culling.name": "Use Entity Culling", "sodium.options.use_entity_culling.tooltip": "If enabled, entities which are within the camera viewport, but not inside of a visible chunk, will be skipped during rendering. This optimization uses the visibility data which already exists for chunk rendering and does not add overhead.", + "sodium.options.use_single_pass_compositing.name" : "Use Single-Pass Compositing", + "sodium.options.use_single_pass_compositing.tooltip" : "If enabled, compositing of the final output image will be done in a single render pass without additional memory copies. This can greatly improve performance on slower graphics cards, but may cause issues with mod compatibility.", "sodium.options.animate_only_visible_textures.name": "Animate Only Visible Textures", "sodium.options.animate_only_visible_textures.tooltip": "If enabled, only the animated textures which are determined to be visible in the current image will be updated. This can provide a significant performance improvement on some hardware, especially with heavier resource packs. If you experience issues with some textures not being animated, try disabling this option.", "sodium.options.cpu_render_ahead_limit.name": "CPU Render-Ahead Limit", diff --git a/common/src/main/resources/assets/sodium/shaders/composite.fsh b/common/src/main/resources/assets/sodium/shaders/composite.fsh new file mode 100644 index 0000000000..8176467c82 --- /dev/null +++ b/common/src/main/resources/assets/sodium/shaders/composite.fsh @@ -0,0 +1,44 @@ +#version 150 core + +uniform sampler2D mainColorRT; +uniform sampler2D entityColorRT; + +uniform sampler2D vignetteTexture; +uniform vec4 vignetteColorModulator; + +uniform int blendOptions; + +in vec2 v_TexCoord; + +out vec4 fragColor; + +#define readRT(rt, coord) texelFetch(rt, coord, 0) +#define readTex(tex, coord) texture(tex, coord) +#define blend(dst, src) mix(dst, src, src.a) + +// dst.rgb *= 1.0 - (src.rgb * src.a) +vec3 sampleVignette(vec2 texCoord) { + vec4 color = readTex(vignetteTexture, texCoord) * vignetteColorModulator; + color.rgb * color.a; + + return vec3(1.0) - color.rgb; +} + +const int USE_ENTITY_GLOW = 1 << 0; +const int USE_VIGNETTE = 1 << 1; + +void main() { + ivec2 pixelCoord = ivec2(gl_FragCoord.xy); + + vec4 result = readRT(mainColorRT, pixelCoord); + + if ((blendOptions & USE_ENTITY_GLOW) != 0) { + result = blend(result, readRT(entityColorRT, pixelCoord)); + } + + if ((blendOptions & USE_VIGNETTE) != 0) { + result.rgb *= sampleVignette(v_TexCoord); + } + + fragColor = result; +} \ No newline at end of file diff --git a/common/src/main/resources/assets/sodium/shaders/composite.vsh b/common/src/main/resources/assets/sodium/shaders/composite.vsh new file mode 100644 index 0000000000..536ea64246 --- /dev/null +++ b/common/src/main/resources/assets/sodium/shaders/composite.vsh @@ -0,0 +1,15 @@ +#version 150 core + +const vec2[] VERTICES = vec2[]( + vec2(-1.0f, -1.0f), + vec2( 3.0f, -1.0f), + vec2(-1.0f, 3.0f) +); + +out vec2 v_TexCoord; + +void main() { + gl_Position = vec4(VERTICES[gl_VertexID], 0.0f, 1.0f); + + v_TexCoord = (0.5f * gl_Position.xy) + vec2(0.5); +} \ No newline at end of file diff --git a/common/src/main/resources/sodium.accesswidener b/common/src/main/resources/sodium.accesswidener index 7dc0f617eb..6f1a6d88ef 100644 --- a/common/src/main/resources/sodium.accesswidener +++ b/common/src/main/resources/sodium.accesswidener @@ -19,4 +19,6 @@ accessible field com/mojang/blaze3d/vertex/PoseStack$Pose trustedNormals Z accessible field net/minecraft/world/level/GrassColor pixels [I accessible field net/minecraft/world/level/FoliageColor pixels [I -accessible method net/minecraft/client/renderer/FogRenderer getPriorityFogFunction (Lnet/minecraft/world/entity/Entity;F)Lnet/minecraft/client/renderer/FogRenderer$MobEffectFogFunction; \ No newline at end of file +accessible method net/minecraft/client/renderer/FogRenderer getPriorityFogFunction (Lnet/minecraft/world/entity/Entity;F)Lnet/minecraft/client/renderer/FogRenderer$MobEffectFogFunction; + +accessible class com/mojang/blaze3d/pipeline/MainTarget$Dimension \ No newline at end of file diff --git a/common/src/main/resources/sodium.mixins.json b/common/src/main/resources/sodium.mixins.json index e110e980ee..a3731c8e5e 100644 --- a/common/src/main/resources/sodium.mixins.json +++ b/common/src/main/resources/sodium.mixins.json @@ -41,7 +41,14 @@ "features.options.render_layers.LeavesBlockMixin", "features.options.render_layers.ItemBlockRenderTypesMixin", "features.options.weather.LevelRendererMixin", + "features.render.compositing.GameRendererMixin", + "features.render.compositing.GuiMixin", + "features.render.compositing.LevelRendererMixin", + "features.render.compositing.MainTargetMixin", + "features.render.compositing.MinecraftMixin", "features.render.compositing.RenderTargetMixin", + "features.render.compositing.ScreenMixin", + "features.render.compositing.WindowMixin", "features.render.entity.CubeMixin", "features.render.entity.ModelPartMixin", "features.render.entity.cull.EntityRendererMixin", diff --git a/neoforge/src/main/resources/META-INF/accesstransformer.cfg b/neoforge/src/main/resources/META-INF/accesstransformer.cfg index 62d286afca..30cce4b8b8 100644 --- a/neoforge/src/main/resources/META-INF/accesstransformer.cfg +++ b/neoforge/src/main/resources/META-INF/accesstransformer.cfg @@ -17,4 +17,6 @@ public com.mojang.blaze3d.vertex.PoseStack$Pose trustedNormals # trustedNormals public net.minecraft.world.level.GrassColor pixels public net.minecraft.world.level.FoliageColor pixels -public net.minecraft.client.renderer.FogRenderer getPriorityFogFunction(Lnet/minecraft/world/entity/Entity;F)Lnet/minecraft/client/renderer/FogRenderer$MobEffectFogFunction; \ No newline at end of file +public net.minecraft.client.renderer.FogRenderer getPriorityFogFunction(Lnet/minecraft/world/entity/Entity;F)Lnet/minecraft/client/renderer/FogRenderer$MobEffectFogFunction; + +public com.mojang.blaze3d.pipeline.MainTarget$Dimension \ No newline at end of file