Skip to content

Commit

Permalink
Early draft for single-pass compositing
Browse files Browse the repository at this point in the history
This is a very early revision of the idea. Some (most) things don't
work currently.
  • Loading branch information
jellysquid3 committed Aug 16, 2024
1 parent 9b2dbff commit 2fa6b08
Show file tree
Hide file tree
Showing 18 changed files with 571 additions and 2 deletions.
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

0 comments on commit 2fa6b08

Please sign in to comment.