Skip to content

Commit

Permalink
Color space support
Browse files Browse the repository at this point in the history
Co-Authored-By: IMS <ims@irisshaders.dev>
  • Loading branch information
Asek3 and IMS212 committed Nov 21, 2023
1 parent c2a0cac commit f6a9e40
Show file tree
Hide file tree
Showing 20 changed files with 437 additions and 4 deletions.
9 changes: 9 additions & 0 deletions src/main/java/net/coderbot/iris/colorspace/ColorSpace.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package net.coderbot.iris.colorspace;

public enum ColorSpace {
SRGB,
DCI_P3,
DISPLAY_P3,
REC2020,
ADOBE_RGB
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package net.coderbot.iris.colorspace;

import com.google.common.collect.ImmutableSet;
import net.coderbot.iris.gl.IrisRenderSystem;
import net.coderbot.iris.gl.program.ComputeProgram;
import net.coderbot.iris.gl.program.ProgramBuilder;
import net.coderbot.iris.gl.texture.InternalTextureFormat;
import net.coderbot.iris.shaderpack.StringPair;
import net.coderbot.iris.shaderpack.preprocessor.JcppProcessor;
import org.apache.commons.io.IOUtils;
import org.lwjgl.opengl.GL43C;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

public class ColorSpaceComputeConverter implements ColorSpaceConverter {
private int width;
private int height;
private ColorSpace colorSpace;
private ComputeProgram program;

private int target;
public ColorSpaceComputeConverter(int width, int height, ColorSpace colorSpace) {
rebuildProgram(width, height, colorSpace);
}

public void rebuildProgram(int width, int height, ColorSpace colorSpace) {
if (program != null) {
program.destroy();
program = null;
}

this.width = width;
this.height = height;
this.colorSpace = colorSpace;

String source;
try {
source = new String(IOUtils.toByteArray(Objects.requireNonNull(getClass().getResourceAsStream("/colorSpace.csh"))), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}

List<StringPair> defineList = new ArrayList<>();
defineList.add(new StringPair("COMPUTE", ""));
defineList.add(new StringPair("CURRENT_COLOR_SPACE", String.valueOf(colorSpace.ordinal())));

for (ColorSpace space : ColorSpace.values()) {
defineList.add(new StringPair(space.name(), String.valueOf(space.ordinal())));
}
source = JcppProcessor.glslPreprocessSource(source, defineList);
ProgramBuilder builder = ProgramBuilder.beginCompute("colorSpaceCompute", source, ImmutableSet.of());
builder.addTextureImage(() -> target, InternalTextureFormat.RGBA8, "readImage");
this.program = builder.buildCompute();
}

public void process(int targetImage) {
if (colorSpace == ColorSpace.SRGB) return;

this.target = targetImage;
program.use();
IrisRenderSystem.dispatchCompute(width / 8, height / 8, 1);
IrisRenderSystem.memoryBarrier(GL43C.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL43C.GL_TEXTURE_FETCH_BARRIER_BIT);
ComputeProgram.unbind();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package net.coderbot.iris.colorspace;

public interface ColorSpaceConverter {
void rebuildProgram(int width, int height, ColorSpace colorSpace);
void process(int target);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package net.coderbot.iris.colorspace;

import com.google.common.collect.ImmutableSet;
import com.mojang.blaze3d.platform.GlStateManager;
import net.coderbot.iris.gl.IrisRenderSystem;
import net.coderbot.iris.gl.framebuffer.GlFramebuffer;
import net.coderbot.iris.gl.program.Program;
import net.coderbot.iris.gl.program.ProgramBuilder;
import net.coderbot.iris.gl.uniform.UniformUpdateFrequency;
import net.coderbot.iris.postprocess.FullScreenQuadRenderer;
import net.coderbot.iris.shaderpack.StringPair;
import net.coderbot.iris.shaderpack.preprocessor.JcppProcessor;
import net.coderbot.iris.vendored.joml.Matrix4f;
import org.apache.commons.io.IOUtils;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.opengl.GL30C;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class ColorSpaceFragmentConverter implements ColorSpaceConverter {
private int width;
private int height;
private ColorSpace colorSpace;
private Program program;
private GlFramebuffer framebuffer;
private int swapTexture;

private int target;
public ColorSpaceFragmentConverter(int width, int height, ColorSpace colorSpace) {
rebuildProgram(width, height, colorSpace);
}

public void rebuildProgram(int width, int height, ColorSpace colorSpace) {
if (program != null) {
program.destroy();
program = null;
framebuffer.destroy();
framebuffer = null;
GlStateManager._deleteTexture(swapTexture);
swapTexture = 0;
}

this.width = width;
this.height = height;
this.colorSpace = colorSpace;

String vertexSource;
String source;
try {
vertexSource = new String(IOUtils.toByteArray(Objects.requireNonNull(getClass().getResourceAsStream("/colorSpace.vsh"))), StandardCharsets.UTF_8);
source = new String(IOUtils.toByteArray(Objects.requireNonNull(getClass().getResourceAsStream("/colorSpace.csh"))), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}

List<StringPair> defineList = new ArrayList<>();
defineList.add(new StringPair("CURRENT_COLOR_SPACE", String.valueOf(colorSpace.ordinal())));

for (ColorSpace space : ColorSpace.values()) {
defineList.add(new StringPair(space.name(), String.valueOf(space.ordinal())));
}
source = JcppProcessor.glslPreprocessSource(source, defineList);

ProgramBuilder builder = ProgramBuilder.begin("colorSpaceFragment", vertexSource, null, source, ImmutableSet.of());

builder.uniformJomlMatrix(UniformUpdateFrequency.ONCE, "projection", () -> new Matrix4f(2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, -1, -1, 0, 1));
builder.addDynamicSampler(() -> target, "readImage");

swapTexture = GlStateManager._genTexture();
IrisRenderSystem.texImage2D(swapTexture, GL30C.GL_TEXTURE_2D, 0, GL30C.GL_RGBA8, width, height, 0, GL30C.GL_RGBA, GL30C.GL_UNSIGNED_BYTE, null);

this.framebuffer = new GlFramebuffer();
framebuffer.addColorAttachment(0, swapTexture);
this.program = builder.build();
}

public void process(int targetImage) {
if (colorSpace == ColorSpace.SRGB) return;

this.target = targetImage;
program.use();
framebuffer.bind();
FullScreenQuadRenderer.INSTANCE.render();
Program.unbind();
framebuffer.bindAsReadBuffer();
IrisRenderSystem.copyTexSubImage2D(targetImage, GL11C.GL_TEXTURE_2D, 0, 0, 0, 0, 0, width, height);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
import me.jellysquid.mods.sodium.client.gui.options.control.SliderControl;
import me.jellysquid.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage;
import net.coderbot.iris.Iris;
import net.coderbot.iris.colorspace.ColorSpace;
import net.coderbot.iris.gui.option.IrisVideoSettings;
import net.minecraft.client.Options;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;

public class IrisSodiumOptions {
Expand All @@ -39,6 +41,29 @@ public static OptionImpl<Options, Integer> createMaxShadowDistanceSlider(Minecra
return maxShadowDistanceSlider;
}

public static OptionImpl<Options, ColorSpace> createColorSpaceButton(MinecraftOptionsStorage vanillaOpts) {
OptionImpl<Options, ColorSpace> colorSpace = OptionImpl.createBuilder(ColorSpace.class, vanillaOpts)
.setName(new TranslatableComponent("options.iris.colorSpace"))
.setTooltip(new TranslatableComponent("options.iris.colorSpace.sodium_tooltip"))
.setControl(option -> new CyclingControl<>(option, ColorSpace.class,
new Component[] { new TextComponent("SRGB"), new TextComponent("DCI_P3"), new TextComponent("Display P3"), new TextComponent("REC2020"), new TextComponent("Adobe RGB") }))
.setBinding((options, value) -> {
IrisVideoSettings.colorSpace = value;
try {
Iris.getIrisConfig().save();
} catch (IOException e) {
e.printStackTrace();
}
},
options -> IrisVideoSettings.colorSpace)
.setImpact(OptionImpact.LOW)
.setEnabled(true)
.build();


return colorSpace;
}

public static OptionImpl<Options, SupportedGraphicsMode> createLimitedVideoSettingsButton(MinecraftOptionsStorage vanillaOpts) {
return OptionImpl.createBuilder(SupportedGraphicsMode.class, vanillaOpts)
.setName(new TranslatableComponent("options.graphics"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class MixinSodiumGameOptionPages {
Option<?> candidate) {
builder.add(candidate);
builder.add(IrisSodiumOptions.createMaxShadowDistanceSlider(vanillaOpts));
builder.add(IrisSodiumOptions.createColorSpaceButton(vanillaOpts));

return builder;
}
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/net/coderbot/iris/config/IrisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.Properties;

import net.coderbot.iris.Iris;
import net.coderbot.iris.colorspace.ColorSpace;
import net.coderbot.iris.gui.option.IrisVideoSettings;

/**
Expand Down Expand Up @@ -139,9 +140,11 @@ public void load() throws IOException {
disableUpdateMessage = "true".equals(properties.getProperty("disableUpdateMessage"));
try {
IrisVideoSettings.shadowDistance = Integer.parseInt(properties.getProperty("maxShadowRenderDistance", "32"));
} catch (NumberFormatException e) {
IrisVideoSettings.colorSpace = ColorSpace.valueOf(properties.getProperty("colorSpace", "SRGB"));
} catch (IllegalArgumentException e) {
Iris.logger.error("Shadow distance setting reset; value is invalid.");
IrisVideoSettings.shadowDistance = 32;
IrisVideoSettings.colorSpace = ColorSpace.SRGB;
save();
}

Expand All @@ -164,6 +167,7 @@ public void save() throws IOException {
properties.setProperty("enableDebugOptions", enableDebugOptions ? "true" : "false");
properties.setProperty("disableUpdateMessage", disableUpdateMessage ? "true" : "false");
properties.setProperty("maxShadowRenderDistance", String.valueOf(IrisVideoSettings.shadowDistance));
properties.setProperty("colorSpace", IrisVideoSettings.colorSpace.name());
// NB: This uses ISO-8859-1 with unicode escapes as the encoding
try (OutputStream os = Files.newOutputStream(propertiesPath)) {
properties.store(os, COMMENT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ public Vector3i getWorkGroups(float width, float height) {
return cachedWorkGroups;
}

public void use() {
ProgramManager.glUseProgram(getGlId());

uniforms.update();
samplers.update();
images.update();
}

public void dispatch(float width, float height) {
ProgramManager.glUseProgram(getGlId());
uniforms.update();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.IOException;

import net.coderbot.iris.Iris;
import net.coderbot.iris.colorspace.ColorSpace;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.minecraft.client.Minecraft;
import net.minecraft.client.ProgressOption;
Expand All @@ -15,6 +16,7 @@ public class IrisVideoSettings {
// TODO: Tell the user to check in the shader options once that's supported.
private static final Component DISABLED_TOOLTIP = new TranslatableComponent("options.iris.shadowDistance.disabled");
private static final Component ENABLED_TOOLTIP = new TranslatableComponent("options.iris.shadowDistance.enabled");
public static ColorSpace colorSpace = ColorSpace.SRGB;

public static int getOverriddenShadowDistance(int base) {
return Iris.getPipelineManager().getPipeline()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.List;
import java.util.Objects;

import net.coderbot.iris.gui.option.IrisVideoSettings;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
Expand Down Expand Up @@ -51,6 +52,7 @@ public abstract class MixinDebugScreenOverlay {
Iris.getCurrentPack().ifPresent(pack -> {
messages.add("[" + Iris.MODNAME + "] " + pack.getProfileInfo());
});
messages.add("[" + Iris.MODNAME + "] Color space: " + IrisVideoSettings.colorSpace.name());
} else {
messages.add("[" + Iris.MODNAME + "] Shaders are disabled");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
import java.util.function.IntFunction;
import java.util.function.Supplier;

import net.coderbot.iris.colorspace.ColorSpace;
import net.coderbot.iris.colorspace.ColorSpaceComputeConverter;
import net.coderbot.iris.colorspace.ColorSpaceConverter;
import net.coderbot.iris.colorspace.ColorSpaceFragmentConverter;
import net.coderbot.iris.gl.IrisRenderSystem;
import net.coderbot.iris.gui.option.IrisVideoSettings;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL15C;
import org.lwjgl.opengl.GL20C;
Expand Down Expand Up @@ -126,6 +132,8 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R

private final SodiumTerrainPipeline sodiumTerrainPipeline;

private final ColorSpaceConverter colorSpaceConverter;

private final HorizonRenderer horizonRenderer = new HorizonRenderer();

private final float sunPathRotation;
Expand Down Expand Up @@ -154,6 +162,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
private int currentNormalTexture;
private int currentSpecularTexture;
private PackDirectives packDirectives;
private ColorSpace currentColorSpace;

public DeferredWorldRenderingPipeline(ProgramSet programs) {
Objects.requireNonNull(programs);
Expand Down Expand Up @@ -430,6 +439,28 @@ public DeferredWorldRenderingPipeline(ProgramSet programs) {
this.sodiumTerrainPipeline = new SodiumTerrainPipeline(this, programs, createTerrainSamplers,
shadowRenderer == null ? null : createShadowTerrainSamplers, createTerrainImages,
shadowRenderer == null ? null : createShadowTerrainImages);

if (programs.getPackDirectives().supportsColorCorrection()) {
colorSpaceConverter = new ColorSpaceConverter() {
@Override
public void rebuildProgram(int width, int height, ColorSpace colorSpace) {

}

@Override
public void process(int target) {

}
};
} else {
if (IrisRenderSystem.supportsCompute()) {
colorSpaceConverter = new ColorSpaceComputeConverter(mainTarget.width, mainTarget.height, IrisVideoSettings.colorSpace);
} else {
colorSpaceConverter = new ColorSpaceFragmentConverter(mainTarget.width, mainTarget.height, IrisVideoSettings.colorSpace);
}
}

currentColorSpace = IrisVideoSettings.colorSpace;
}

private RenderTargets getRenderTargets() {
Expand Down Expand Up @@ -939,6 +970,11 @@ private void prepareRenderTargets() {
packDirectives.getRenderTargetDirectives());
}

if (changed || IrisVideoSettings.colorSpace != currentColorSpace) {
currentColorSpace = IrisVideoSettings.colorSpace;
colorSpaceConverter.rebuildProgram(main.width, main.height, currentColorSpace);
}

final ImmutableList<ClearPass> passes;

if (renderTargets.isFullClearRequired()) {
Expand Down Expand Up @@ -1171,6 +1207,7 @@ public void finalizeLevelRendering() {

compositeRenderer.renderAll();
finalPassRenderer.renderFinalPass();
colorSpaceConverter.process(Minecraft.getInstance().getMainRenderTarget().getColorTextureId());

isRenderingFullScreenPass = false;
}
Expand Down
Loading

0 comments on commit f6a9e40

Please sign in to comment.