Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single-pass Compositing #2677

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ private ShaderConstants(List<String> defines) {
this.defines = defines;
}

public static ShaderConstants empty() {
return new ShaderConstants(List.of());
}

public List<String> getDefineStrings() {
return this.defines;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<float[]> {
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CompositeProgramInterface> COMPOSITE_PROGRAM;

public static void composite(@NotNull RenderTarget mainRT,
@Nullable RenderTarget entityRT,
int width,
int height) {
EnumSet<BlendOption> 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
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Void> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Loading
Loading