diff --git a/1_21_2_model.md b/1_21_2_model.md index 12a1825..9cb2ba2 100644 --- a/1_21_2_model.md +++ b/1_21_2_model.md @@ -1,4 +1,6 @@ -# Minecraft Shader Loading +# Minecraft's new Shader system + +## Shader Loading `ShaderLoader` loads every file in the `shaders` directory ending in `.json`, `.fsh`, `.vsh`, or `.glsl` @@ -85,3 +87,23 @@ classDiagram TextureSampler : +int width TextureSampler : +int height ``` + +## Post-process effect rendering + +Post effect rendering is now divided into two steps: +1. building a reusable frame graph +2. rendering the frame graph + +### Building the frame graph + +```mermaid +--- +title: Frame Graph structure +--- +classDiagram + Node <|-- ObjectNode + Node <|-- ResourceNode + ResourceNode "1" --* "*" FrameGraphBuilder + ObjectNode "1" --* "*" FrameGraphBuilder + FramePass "1" --* "*" FrameGraphBuilder +``` diff --git a/src/main/java/org/ladysnake/satin/api/managed/ManagedFramebuffer.java b/src/main/java/org/ladysnake/satin/api/managed/ManagedFramebuffer.java index 1ceb967..78c15a1 100644 --- a/src/main/java/org/ladysnake/satin/api/managed/ManagedFramebuffer.java +++ b/src/main/java/org/ladysnake/satin/api/managed/ManagedFramebuffer.java @@ -61,8 +61,6 @@ public interface ManagedFramebuffer { */ void clear(); - void clear(boolean swallowErrors); - /** * Gets a simple {@link RenderLayer} that is functionally identical to {@code baseLayer}, * but with a different {@link RenderPhase.Target} that binds this framebuffer. diff --git a/src/main/java/org/ladysnake/satin/api/managed/ManagedShaderEffect.java b/src/main/java/org/ladysnake/satin/api/managed/ManagedShaderEffect.java index b6beab5..1eab4d1 100644 --- a/src/main/java/org/ladysnake/satin/api/managed/ManagedShaderEffect.java +++ b/src/main/java/org/ladysnake/satin/api/managed/ManagedShaderEffect.java @@ -140,23 +140,6 @@ public interface ManagedShaderEffect extends UniformFinder { @API(status = EXPERIMENTAL, since = "1.4.0") ManagedFramebuffer getTarget(String name); - /** - * Forwards to {@link #setupDynamicUniforms(int, Runnable)} with an index of 0 - * - * @param dynamicSetBlock a block in which dynamic uniforms are set - */ - @API(status = EXPERIMENTAL, since = "1.0.0") - void setupDynamicUniforms(Runnable dynamicSetBlock); - - /** - * Runs the given block while the shader at the given index is active - * - * @param index the shader index within the group - * @param dynamicSetBlock a block in which dynamic name uniforms are set - */ - @API(status = EXPERIMENTAL, since = "1.0.0") - void setupDynamicUniforms(int index, Runnable dynamicSetBlock); - /** * Sets the value of a uniform declared in json * diff --git a/src/main/java/org/ladysnake/satin/api/managed/uniform/SamplerUniform.java b/src/main/java/org/ladysnake/satin/api/managed/uniform/SamplerUniform.java index fce8426..ca8327d 100644 --- a/src/main/java/org/ladysnake/satin/api/managed/uniform/SamplerUniform.java +++ b/src/main/java/org/ladysnake/satin/api/managed/uniform/SamplerUniform.java @@ -26,14 +26,6 @@ @API(status = MAINTAINED) public interface SamplerUniform { - /** - * Sets the value of a sampler uniform declared in json using the Opengl texture slot id (between 0 and 30). - * @param activeTexture the active texture id to be used by the sampler - * @see org.lwjgl.opengl.GL13#GL_TEXTURE0 - */ - @API(status = MAINTAINED, since = "1.4.0") - void setDirect(int activeTexture); - /** * Sets the value of a sampler uniform declared in json * diff --git a/src/main/java/org/ladysnake/satin/api/util/ShaderPrograms.java b/src/main/java/org/ladysnake/satin/api/util/ShaderPrograms.java deleted file mode 100644 index 9b07590..0000000 --- a/src/main/java/org/ladysnake/satin/api/util/ShaderPrograms.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Satin - * Copyright (C) 2019-2024 Ladysnake - * - * 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; either - * version 3 of the License, or (at your option) any later version. - * - * 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 - * Lesser 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 org.ladysnake.satin.api.util; - -import com.mojang.blaze3d.systems.RenderSystem; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gl.GlProgramManager; -import net.minecraft.client.gl.GlUniform; -import net.minecraft.util.Identifier; -import org.apiguardian.api.API; -import org.ladysnake.satin.Satin; -import org.lwjgl.opengl.GL20; - -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.util.function.IntConsumer; - -import static org.apiguardian.api.API.Status.*; - -/** - * This class consists exclusively of static methods that operate on - * OpenGL shader program objects. - */ -public final class ShaderPrograms { - private ShaderPrograms() {} - - /**A map of programs to maps of uniform names to location*/ - private static final Int2ObjectMap> uniformsCache = new Int2ObjectOpenHashMap<>(); - - /** - * Sets the currently used program. - * - *

- * If shaders are disallowed in the current game instance, this method returns immediately - * with no side effect. - * - * @param program the reference to the desired shader (0 to remove any current shader) - */ - @API(status = MAINTAINED) - public static void useShader(int program) { - if (Satin.areShadersDisabled()) { - return; - } - - GlProgramManager.useProgram(program); - } - - /** - * Sets the value of an attribute from the current shader program using the given operation. - *

- * operation will only be called if shaders are enabled and an attribute with the given name exists - * in the current program. It should call one of {@link GL20} attrib functions (eg. {@link GL20#glBindAttribLocation(int, int, ByteBuffer)}). - * - * @param program OpenGL shader program object - * @param attribName the name of the attribute field in the shader source file - * @param operation a gl operation to apply to this uniform - */ - @API(status = EXPERIMENTAL) - public static void setAttribValue(int program, String attribName, IntConsumer operation) { - if (Satin.areShadersDisabled() || program == 0) { - return; - } - - int attrib = GlUniform.getAttribLocation(program, attribName); - if (attrib != -1) { - operation.accept(attrib); - } - } - - /** - * Sets the value of a uniform from the current shader program using the given operation. - *

- * operation will only be called if shaders are enabled and a uniform with the given name exists - * in the current program. It should call one of {@link GL20} uniform functions (eg. {@link GL20#glUniform1iv(int, IntBuffer)}). - * - * @param program OpenGL shader program object - * @param uniformName the name of the uniform field in the shader source file - * @param operation a gl operation to apply to this uniform - */ - @API(status = EXPERIMENTAL) - public static void setUniformValue(int program, String uniformName, IntConsumer operation) { - if (Satin.areShadersDisabled() || program == 0) { - return; - } - - int uniform = getUniformLocation(program, uniformName); - if (uniform != -1) { - operation.accept(uniform); - } - } - - /** - * Sets the value of an int uniform from the current shader program - * - * @param program OpenGL shader program object - * @param uniformName the name of the uniform field in the shader source file - * @param value an int value for this uniform - */ - @API(status = STABLE) - public static void setUniform(int program, String uniformName, int value) { - if (Satin.areShadersDisabled() || program == 0) { - return; - } - - int uniform = getUniformLocation(program, uniformName); - if (uniform != -1) { - GL20.glUniform1i(uniform, value); - } - } - - /** - * Sets the value of a float uniform field from the current shader program - * - * @param program OpenGL shader program object - * @param uniformName the name of the uniform field in the shader source file - * @param value float value of the uniform - */ - @API(status = EXPERIMENTAL) - public static void setUniform(int program, String uniformName, float value) { - if (Satin.areShadersDisabled() || program == 0) { - return; - } - - int uniform = getUniformLocation(program, uniformName); - if (uniform != -1) { - GL20.glUniform1f(uniform, value); - } - } - - /** - * Sets the value of a mat4 uniform in the current shader - * - * @param program OpenGL shader program object - * @param uniformName the name of the uniform field in the shader source file - * @param mat4 a raw array of float values - */ - @API(status = EXPERIMENTAL) - public static void setUniform(int program, String uniformName, FloatBuffer mat4) { - if (Satin.areShadersDisabled() || program == 0) { - return; - } - - int uniform = getUniformLocation(program, uniformName); - if (uniform != -1) { - GL20.glUniformMatrix4fv(uniform, false, mat4); - } - } - - /** - * {@code getUniformLocation} returns an integer that represents the location - * of a specific uniform variable within a program object. - *

- * {@code name} must be a string that contains no white space. - * {@code name} must be an active uniform variable name in program that is not a structure, - * an array of structures, or a subcomponent of a vector or a matrix. - *

- * This function returns -1 if name does not correspond to an active - * uniform variable in {@code program} or if {@code name} starts with the - * reserved prefix "gl_". - *

- * Uniform locations obtained through this method are cached, limiting - * performance loss from consecutive calls. - * - * @param program program object to be queried - * @param uniformName string containing the name of the uniform variable - * whose location is to be queried - * @return an integer that represents the location of a specific uniform - * variable within a program object - */ - @API(status = MAINTAINED) - public static int getUniformLocation(int program, String uniformName) { - // Gets the uniform cache for the current program - Object2IntMap shaderUniformsCache = uniformsCache.get(program); - // Compute if absent - if (shaderUniformsCache == null) { - shaderUniformsCache = new Object2IntOpenHashMap<>(); - uniformsCache.put(program, shaderUniformsCache); - } - // Gets the uniform location from the cache - int uniform; - if (shaderUniformsCache.containsKey(uniformName)) { - uniform = shaderUniformsCache.getInt(uniformName); - } else { - // Compute if absent - uniform = GlUniform.getUniformLocation(program, uniformName); - shaderUniformsCache.put(uniformName, uniform); - } - return uniform; - } - - /** - * Binds any number of additional textures to be used by the current shader. - *

- * The default texture (0) is unaffected. - * Shaders can access these textures by using uniforms named "textureN" with N - * being the index of the additional texture, starting at 1. - *

- * - * Example: The call {@code bindAdditionalTextures(rl1, rl2, rl3)} will let the shader - * access those textures via the uniforms
{@code
-     * uniform sampler2D texture;   // the texture that's currently being drawn
-     * uniform sampler2D texture1;  // the texture designated by rl1
-     * uniform sampler2D texture2;  // the texture designated by rl2
-     * uniform sampler2D texture3;  // the texture designated by rl3
-     * }
- */ - @API(status = EXPERIMENTAL) - public static void bindAdditionalTextures(int program, Identifier... textures) { - for (int i = 0; i < textures.length; i++) { - Identifier texture = textures[i]; - // don't mess with the lightmap (1) nor the default texture (0) - RenderSystem.activeTexture(i + GL20.GL_TEXTURE2); - MinecraftClient.getInstance().getTextureManager().bindTexture(texture); - // start texture uniforms at 1, as 0 would be the default texture which doesn't require any special operation - setUniform(program, "texture" + (i + 1), i + 2); - } - RenderSystem.activeTexture(GL20.GL_TEXTURE0); - } -} diff --git a/src/main/java/org/ladysnake/satin/impl/CustomFormatFramebuffers.java b/src/main/java/org/ladysnake/satin/impl/CustomFormatFramebuffers.java index 430f07c..98cf8ba 100644 --- a/src/main/java/org/ladysnake/satin/impl/CustomFormatFramebuffers.java +++ b/src/main/java/org/ladysnake/satin/impl/CustomFormatFramebuffers.java @@ -37,10 +37,10 @@ public class CustomFormatFramebuffers { *

Refer to {@link SimpleFramebuffer} for the list of parameters */ @API(status = API.Status.EXPERIMENTAL) - public static Framebuffer create(int width, int height, boolean useDepth, boolean getError, TextureFormat format) { + public static Framebuffer create(int width, int height, boolean useDepth, TextureFormat format) { try { FORMAT.set(format); - return new SimpleFramebuffer(width, height, useDepth, getError); + return new SimpleFramebuffer(width, height, useDepth); } finally { FORMAT.remove(); } diff --git a/src/main/java/org/ladysnake/satin/impl/FramebufferWrapper.java b/src/main/java/org/ladysnake/satin/impl/FramebufferWrapper.java index 9f91d63..dfeca97 100644 --- a/src/main/java/org/ladysnake/satin/impl/FramebufferWrapper.java +++ b/src/main/java/org/ladysnake/satin/impl/FramebufferWrapper.java @@ -24,6 +24,7 @@ import net.minecraft.client.util.Window; import org.ladysnake.satin.Satin; import org.ladysnake.satin.api.managed.ManagedFramebuffer; +import org.ladysnake.satin.mixin.client.AccessiblePassesShaderEffect; import javax.annotation.Nullable; @@ -46,9 +47,10 @@ void findTarget(@Nullable PostEffectProcessor shaderEffect) { if (shaderEffect == null) { this.wrapped = null; } else { - this.wrapped = shaderEffect.getSecondaryTarget(this.name); + // FIXME create the target instead and add it to a FramebufferSet + this.wrapped = null; ((AccessiblePassesShaderEffect) shaderEffect).getInternalTargets().get(this.name); if (this.wrapped == null) { - Satin.LOGGER.warn("No target framebuffer found with name {} in shader {}", this.name, shaderEffect.getName()); + Satin.LOGGER.warn("No target framebuffer found with name {} in shader", this.name); } } } @@ -86,19 +88,14 @@ public void draw() { @Override public void draw(int width, int height, boolean disableBlend) { if (this.wrapped != null) { - this.wrapped.draw(width, height, disableBlend); + this.wrapped.draw(width, height); } } @Override public void clear() { - clear(MinecraftClient.IS_SYSTEM_MAC); - } - - @Override - public void clear(boolean swallowErrors) { if (this.wrapped != null) { - this.wrapped.clear(swallowErrors); + this.wrapped.clear(); } } diff --git a/src/main/java/org/ladysnake/satin/impl/ManagedSamplerUniformV2.java b/src/main/java/org/ladysnake/satin/impl/ManagedPassSamplerUniform.java similarity index 51% rename from src/main/java/org/ladysnake/satin/impl/ManagedSamplerUniformV2.java rename to src/main/java/org/ladysnake/satin/impl/ManagedPassSamplerUniform.java index 4bec8dd..b6a6866 100644 --- a/src/main/java/org/ladysnake/satin/impl/ManagedSamplerUniformV2.java +++ b/src/main/java/org/ladysnake/satin/impl/ManagedPassSamplerUniform.java @@ -18,17 +18,50 @@ package org.ladysnake.satin.impl; import net.minecraft.client.gl.Framebuffer; -import net.minecraft.client.gl.JsonEffectShaderProgram; +import net.minecraft.client.gl.PostEffectPass; +import net.minecraft.client.gl.ShaderProgram; +import net.minecraft.client.render.RenderPass; import net.minecraft.client.texture.AbstractTexture; +import net.minecraft.client.util.Handle; +import net.minecraft.util.Identifier; import org.ladysnake.satin.api.managed.uniform.SamplerUniformV2; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import java.util.function.IntSupplier; -public final class ManagedSamplerUniformV2 extends ManagedSamplerUniformBase implements SamplerUniformV2 { - public ManagedSamplerUniformV2(String name) { +/** + * A sampler uniform applying to a {@link PostEffectPass} + */ +public final class ManagedPassSamplerUniform extends ManagedSamplerUniformBase implements SamplerUniformV2, PostEffectPass.Sampler { + public ManagedPassSamplerUniform(String name) { super(name); } + @Override + public void preRender(RenderPass pass, Map> internalTargets) { + // NO-OP + } + + @Override + public void bind(ShaderProgram program, Map> internalTargets) { + program.addSamplerTexture(this.name, this.value.getAsInt()); + } + + @Override + public boolean findUniformTargets(List passes) { + List targets = new ArrayList<>(passes.size()); + boolean found = false; + for (PostEffectPass pass : passes) { + pass.addSampler(this); + found = true; + } + this.targets = targets.toArray(new SamplerAccess[0]); + this.syncCurrentValues(); + return found; + } + @Override public void set(AbstractTexture texture) { set(texture::getGlId); @@ -44,19 +77,11 @@ public void set(int textureName) { set(() -> textureName); } - @Override - protected void set(Object value) { - this.set((IntSupplier) value); - } - @Override public void set(IntSupplier value) { SamplerAccess[] targets = this.targets; - if (targets.length > 0 && this.cachedValue != value) { - for (SamplerAccess target : targets) { - ((JsonEffectShaderProgram) target).bindSampler(this.name, value); - } - this.cachedValue = value; + if (targets.length > 0 && this.value != value) { + this.value = value; } } } diff --git a/src/main/java/org/ladysnake/satin/impl/ManagedSamplerUniformBase.java b/src/main/java/org/ladysnake/satin/impl/ManagedSamplerUniformBase.java index f2d0e5a..ed789a0 100644 --- a/src/main/java/org/ladysnake/satin/impl/ManagedSamplerUniformBase.java +++ b/src/main/java/org/ladysnake/satin/impl/ManagedSamplerUniformBase.java @@ -17,55 +17,35 @@ */ package org.ladysnake.satin.impl; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; -import net.minecraft.client.gl.GlUniform; -import net.minecraft.client.gl.JsonEffectShaderProgram; -import net.minecraft.client.gl.PostEffectPass; import net.minecraft.client.gl.ShaderProgram; +import net.minecraft.client.gl.ShaderProgramDefinition; import org.ladysnake.satin.api.managed.uniform.SamplerUniform; -import java.util.ArrayList; import java.util.List; import java.util.function.IntSupplier; /** - * Mojank working in divergent branches for half a century, {@link net.minecraft.client.gl.ShaderProgram} - * is a copy of an old implementation of {@link net.minecraft.client.gl.JsonEffectShaderProgram}. - * The latter has since been updated to have the {@link net.minecraft.client.gl.JsonEffectShaderProgram#bindSampler(String, IntSupplier)} - * while the former still uses {@link net.minecraft.client.gl.ShaderProgram#addSampler(String, Object)}. + * * *

So we need to deal with both those extremely similar implementations */ public abstract class ManagedSamplerUniformBase extends ManagedUniformBase implements SamplerUniform { protected SamplerAccess[] targets = new SamplerAccess[0]; protected int[] locations = new int[0]; - protected Object cachedValue; + protected IntSupplier value; public ManagedSamplerUniformBase(String name) { super(name); } - @Override - public boolean findUniformTargets(List shaders) { - List targets = new ArrayList<>(shaders.size()); - IntList rawTargets = new IntArrayList(shaders.size()); - for (PostEffectPass shader : shaders) { - JsonEffectShaderProgram program = shader.getProgram(); - SamplerAccess access = (SamplerAccess) program; - if (access.satin$hasSampler(this.name)) { - targets.add(access); - rawTargets.add(getSamplerLoc(access)); + private int getSamplerLoc(SamplerAccess access) { + List samplerNames = access.satin$getSamplerNames(); + for (int i = 0; i < samplerNames.size(); i++) { + if (samplerNames.get(i).name().equals(this.name)) { + return access.satin$getSamplerShaderLocs().getInt(i); } } - this.targets = targets.toArray(new SamplerAccess[0]); - this.locations = rawTargets.toArray(new int[0]); - this.syncCurrentValues(); - return this.targets.length > 0; - } - - private int getSamplerLoc(SamplerAccess access) { - return access.satin$getSamplerShaderLocs().get(access.satin$getSamplerNames().indexOf(this.name)); + return -1; } @Override @@ -83,22 +63,14 @@ private boolean findUniformTarget(SamplerAccess access) { return false; } - private void syncCurrentValues() { - Object value = this.cachedValue; + protected void syncCurrentValues() { + IntSupplier value = this.value; if (value != null) { // after the first upload - this.cachedValue = null; + this.value = null; this.set(value); } } - protected abstract void set(Object value); + protected abstract void set(IntSupplier value); - @Override - public void setDirect(int activeTexture) { - int length = this.locations.length; - for (int i = 0; i < length; i++) { - this.targets[i].satin$removeSampler(this.name); - GlUniform.uniform1(this.locations[i], activeTexture); - } - } } diff --git a/src/main/java/org/ladysnake/satin/impl/ManagedSamplerUniformV1.java b/src/main/java/org/ladysnake/satin/impl/ManagedSamplerUniformV1.java index 7b5c67d..1010e16 100644 --- a/src/main/java/org/ladysnake/satin/impl/ManagedSamplerUniformV1.java +++ b/src/main/java/org/ladysnake/satin/impl/ManagedSamplerUniformV1.java @@ -18,14 +18,22 @@ package org.ladysnake.satin.impl; import net.minecraft.client.gl.Framebuffer; +import net.minecraft.client.gl.PostEffectPass; import net.minecraft.client.gl.ShaderProgram; import net.minecraft.client.texture.AbstractTexture; +import java.util.List; + public final class ManagedSamplerUniformV1 extends ManagedSamplerUniformBase { public ManagedSamplerUniformV1(String name) { super(name); } + @Override + public boolean findUniformTargets(List shaders) { + throw new UnsupportedOperationException(); + } + @Override public void set(AbstractTexture texture) { this.set((Object) texture); @@ -44,11 +52,11 @@ public void set(int textureName) { @Override protected void set(Object value) { SamplerAccess[] targets = this.targets; - if (targets.length > 0 && this.cachedValue != value) { + if (targets.length > 0 && this.value != value) { for (SamplerAccess target : targets) { - ((ShaderProgram) target).addSampler(this.name, value); + ((ShaderProgram) target).addSamplerTexture(this.name, value); } - this.cachedValue = value; + this.value = value; } } } diff --git a/src/main/java/org/ladysnake/satin/impl/ReloadableShaderEffectManager.java b/src/main/java/org/ladysnake/satin/impl/ReloadableShaderEffectManager.java index e40d5b6..412ff58 100644 --- a/src/main/java/org/ladysnake/satin/impl/ReloadableShaderEffectManager.java +++ b/src/main/java/org/ladysnake/satin/impl/ReloadableShaderEffectManager.java @@ -117,20 +117,20 @@ public void reload(ResourceFactory shaderResources) { @Override public void onResolutionChanged(int newWidth, int newHeight) { - runShaderSetup(newWidth, newHeight); + runShaderSetup(); } @Override public void onRendererReload(WorldRenderer renderer) { Window window = MinecraftClient.getInstance().getWindow(); - runShaderSetup(window.getFramebufferWidth(), window.getFramebufferHeight()); + runShaderSetup(); } - private void runShaderSetup(int newWidth, int newHeight) { + private void runShaderSetup() { if (!Satin.areShadersDisabled() && !managedShaders.isEmpty()) { for (ResettableManagedShaderBase ss : managedShaders) { if (ss.isInitialized()) { - ss.setup(newWidth, newHeight); + ss.setup(); } } } diff --git a/src/main/java/org/ladysnake/satin/impl/RenderLayerSupplier.java b/src/main/java/org/ladysnake/satin/impl/RenderLayerSupplier.java index 2e78629..dc6a142 100644 --- a/src/main/java/org/ladysnake/satin/impl/RenderLayerSupplier.java +++ b/src/main/java/org/ladysnake/satin/impl/RenderLayerSupplier.java @@ -18,6 +18,7 @@ package org.ladysnake.satin.impl; import net.minecraft.client.gl.ShaderProgram; +import net.minecraft.client.gl.ShaderProgramKey; import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.RenderPhase; import net.minecraft.client.render.VertexFormat; @@ -44,9 +45,9 @@ public static RenderLayerSupplier framebuffer(String name, Runnable setupState, return new RenderLayerSupplier(name, builder -> builder.target(target)); } - public static RenderLayerSupplier shader(String name, VertexFormat vertexFormat, Supplier shaderSupplier) { - RenderPhase shader = Helper.makeShader(shaderSupplier); - return new RenderLayerSupplier(name, vertexFormat, builder -> Helper.applyShader(builder, shader)); + public static RenderLayerSupplier shader(String name, VertexFormat vertexFormat, ShaderProgramKey shaderKey) { + RenderPhase shader = new RenderPhase.ShaderProgram(shaderKey); + return new RenderLayerSupplier(name, vertexFormat, builder -> builder.program((RenderPhase.ShaderProgram) shader)); } public RenderLayerSupplier(String name, Consumer transformer) { @@ -74,13 +75,6 @@ public RenderLayer getRenderLayer(RenderLayer baseLayer) { * Big brain move right there */ private static class Helper extends RenderPhase { - public static RenderPhase makeShader(Supplier shader) { - return new ShaderProgram(shader); - } - - public static void applyShader(RenderLayer.MultiPhaseParameters.Builder builder, RenderPhase shader) { - builder.program((ShaderProgram) shader); - } private Helper(String name, Runnable beginAction, Runnable endAction) { super(name, beginAction, endAction); diff --git a/src/main/java/org/ladysnake/satin/impl/ResettableManagedCoreShader.java b/src/main/java/org/ladysnake/satin/impl/ResettableManagedCoreShader.java index 1545413..29ec9de 100644 --- a/src/main/java/org/ladysnake/satin/impl/ResettableManagedCoreShader.java +++ b/src/main/java/org/ladysnake/satin/impl/ResettableManagedCoreShader.java @@ -18,9 +18,10 @@ package org.ladysnake.satin.impl; import com.google.common.base.Preconditions; -import net.fabricmc.fabric.impl.client.rendering.FabricShaderProgram; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.Defines; import net.minecraft.client.gl.ShaderProgram; +import net.minecraft.client.gl.ShaderProgramKey; import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.VertexFormat; import net.minecraft.resource.ResourceFactory; @@ -54,13 +55,12 @@ public ResettableManagedCoreShader(Identifier location, VertexFormat vertexForma } @Override - protected ShaderProgram parseShader(ResourceFactory resourceManager, MinecraftClient mc, Identifier location) throws IOException { - // Easiest way of getting modded shader locations to work - return new FabricShaderProgram(resourceManager, this.getLocation(), this.vertexFormat); + protected ShaderProgram parseShader(ResourceFactory resourceManager, MinecraftClient mc, Identifier location) { + return mc.getShaderLoader().getOrCreateProgram(new ShaderProgramKey(this.getLocation(), this.vertexFormat, Defines.builder().build())); } @Override - public void setup(int newWidth, int newHeight) { + public void setup() { Preconditions.checkNotNull(this.shader); for (ManagedUniformBase uniform : this.getManagedUniforms()) { setupUniform(uniform, this.shader); diff --git a/src/main/java/org/ladysnake/satin/impl/ResettableManagedShaderBase.java b/src/main/java/org/ladysnake/satin/impl/ResettableManagedShaderBase.java index 7f441f8..85b245d 100644 --- a/src/main/java/org/ladysnake/satin/impl/ResettableManagedShaderBase.java +++ b/src/main/java/org/ladysnake/satin/impl/ResettableManagedShaderBase.java @@ -71,9 +71,8 @@ public void initializeOrLog(ResourceFactory mgr) { protected void initialize(ResourceFactory resourceManager) throws IOException { this.release(); - MinecraftClient mc = MinecraftClient.getInstance(); - this.shader = parseShader(resourceManager, mc, this.location); - this.setup(mc.getWindow().getFramebufferWidth(), mc.getWindow().getFramebufferHeight()); + this.shader = parseShader(resourceManager, MinecraftClient.getInstance(), this.location); + this.setup(); } protected abstract S parseShader(ResourceFactory resourceFactory, MinecraftClient mc, Identifier location) throws IOException; @@ -174,7 +173,7 @@ public UniformMat4 findUniformMat4(String uniformName) { } @API(status = INTERNAL) - public abstract void setup(int newWidth, int newHeight); + public abstract void setup(); @Override public String toString() { diff --git a/src/main/java/org/ladysnake/satin/impl/ResettableManagedShaderEffect.java b/src/main/java/org/ladysnake/satin/impl/ResettableManagedShaderEffect.java index 0b70490..bb6475c 100644 --- a/src/main/java/org/ladysnake/satin/impl/ResettableManagedShaderEffect.java +++ b/src/main/java/org/ladysnake/satin/impl/ResettableManagedShaderEffect.java @@ -22,7 +22,6 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gl.Framebuffer; import net.minecraft.client.gl.PostEffectProcessor; -import net.minecraft.client.gl.ShaderProgram; import net.minecraft.client.render.DefaultFramebufferSet; import net.minecraft.client.texture.AbstractTexture; import net.minecraft.resource.ResourceFactory; @@ -34,8 +33,8 @@ import org.ladysnake.satin.api.managed.ManagedShaderEffect; import org.ladysnake.satin.api.managed.ShaderEffectManager; import org.ladysnake.satin.api.managed.uniform.SamplerUniformV2; -import org.ladysnake.satin.api.util.ShaderPrograms; import org.ladysnake.satin.mixin.client.AccessiblePassesShaderEffect; +import org.ladysnake.satin.mixin.client.render.GameRendererAccessor; import javax.annotation.Nullable; import java.io.IOException; @@ -60,14 +59,14 @@ public final class ResettableManagedShaderEffect extends ResettableManagedShader /**Callback to run once each time the shader effect is initialized*/ private final Consumer initCallback; private final Map managedTargets; - private final Map managedSamplers = new HashMap<>(); + private final Map managedSamplers = new HashMap<>(); /** * Creates a new shader effect.
* Users should obtain instanced of this class through {@link ShaderEffectManager} * * @param location the location of a shader effect JSON definition file - * @param initCallback code to run in {@link #setup(int, int)} + * @param initCallback code to run in {@link #setup()} * @see ReloadableShaderEffectManager#manage(Identifier) * @see ReloadableShaderEffectManager#manage(Identifier, Consumer) */ @@ -95,9 +94,8 @@ protected PostEffectProcessor parseShader(ResourceFactory resourceFactory, Minec } @Override - public void setup(int windowWidth, int windowHeight) { + public void setup() { Preconditions.checkNotNull(shader); - this.shader.setupDimensions(windowWidth, windowHeight); for (ManagedUniformBase uniform : this.getManagedUniforms()) { setupUniform(uniform, shader); @@ -120,8 +118,9 @@ public void render(float tickDelta) { RenderSystem.disableBlend(); RenderSystem.disableDepthTest(); RenderSystem.resetTextureMatrix(); - sg.render(tickDelta); - MinecraftClient.getInstance().getFramebuffer().beginWrite(true); + MinecraftClient client = MinecraftClient.getInstance(); + sg.render(client.getFramebuffer(), ((GameRendererAccessor) client.gameRenderer).getPool()); + client.getFramebuffer().beginWrite(true); RenderSystem.disableBlend(); RenderSystem.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // restore blending RenderSystem.enableDepthTest(); @@ -237,21 +236,7 @@ public void setSamplerUniform(String samplerName, int textureName) { @Override public SamplerUniformV2 findSampler(String samplerName) { - return manageUniform(this.managedSamplers, ManagedSamplerUniformV2::new, samplerName, "sampler"); - } - - public void setupDynamicUniforms(Runnable dynamicSetBlock) { - this.setupDynamicUniforms(0, dynamicSetBlock); - } - - public void setupDynamicUniforms(int index, Runnable dynamicSetBlock) { - AccessiblePassesShaderEffect sg = (AccessiblePassesShaderEffect) this.getShaderEffect(); - if (sg != null) { - ShaderProgram sm = sg.getPasses().get(index).getProgram(); - ShaderPrograms.useShader(sm.getGlRef()); - dynamicSetBlock.run(); - ShaderPrograms.useShader(0); - } + return manageUniform(this.managedSamplers, ManagedPassSamplerUniform::new, samplerName, "sampler"); } @Override @@ -270,4 +255,9 @@ protected void logInitError(IOException e) { } return this.shader; } + + @Override + protected void doRelease(PostEffectProcessor shader) { + + } } \ No newline at end of file diff --git a/src/main/java/org/ladysnake/satin/impl/SamplerAccess.java b/src/main/java/org/ladysnake/satin/impl/SamplerAccess.java index 20bb153..c3fa02e 100644 --- a/src/main/java/org/ladysnake/satin/impl/SamplerAccess.java +++ b/src/main/java/org/ladysnake/satin/impl/SamplerAccess.java @@ -17,11 +17,14 @@ */ package org.ladysnake.satin.impl; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.client.gl.ShaderProgramDefinition; + import java.util.List; public interface SamplerAccess { void satin$removeSampler(String name); boolean satin$hasSampler(String name); - List satin$getSamplerNames(); - List satin$getSamplerShaderLocs(); + List satin$getSamplerNames(); + IntList satin$getSamplerShaderLocs(); } diff --git a/src/main/java/org/ladysnake/satin/mixin/client/AccessiblePassesShaderEffect.java b/src/main/java/org/ladysnake/satin/mixin/client/AccessiblePassesShaderEffect.java index 7783cee..f63e53b 100644 --- a/src/main/java/org/ladysnake/satin/mixin/client/AccessiblePassesShaderEffect.java +++ b/src/main/java/org/ladysnake/satin/mixin/client/AccessiblePassesShaderEffect.java @@ -18,14 +18,20 @@ package org.ladysnake.satin.mixin.client; import net.minecraft.client.gl.PostEffectPass; +import net.minecraft.client.gl.PostEffectPipeline; import net.minecraft.client.gl.PostEffectProcessor; +import net.minecraft.util.Identifier; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; import java.util.List; +import java.util.Map; @Mixin(PostEffectProcessor.class) public interface AccessiblePassesShaderEffect { @Accessor List getPasses(); + + @Accessor + Map getInternalTargets(); } diff --git a/src/main/java/org/ladysnake/satin/mixin/client/gl/CoreShaderMixin.java b/src/main/java/org/ladysnake/satin/mixin/client/gl/CoreShaderMixin.java index 981bc16..ad185eb 100644 --- a/src/main/java/org/ladysnake/satin/mixin/client/gl/CoreShaderMixin.java +++ b/src/main/java/org/ladysnake/satin/mixin/client/gl/CoreShaderMixin.java @@ -17,7 +17,9 @@ */ package org.ladysnake.satin.mixin.client.gl; +import it.unimi.dsi.fastutil.ints.IntList; import net.minecraft.client.gl.ShaderProgram; +import net.minecraft.client.gl.ShaderProgramDefinition; import org.ladysnake.satin.impl.SamplerAccess; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -29,7 +31,7 @@ @Mixin(ShaderProgram.class) public abstract class CoreShaderMixin implements SamplerAccess { - @Shadow @Final private Map samplers; + @Shadow @Final private Map samplers; @Override public void satin$removeSampler(String name) { @@ -42,10 +44,10 @@ public abstract class CoreShaderMixin implements SamplerAccess { } @Override - @Accessor("samplerNames") - public abstract List satin$getSamplerNames(); + @Accessor("samplers") + public abstract List satin$getSamplerNames(); @Override - @Accessor("loadedSamplerIds") - public abstract List satin$getSamplerShaderLocs(); + @Accessor("samplerLocations") + public abstract IntList satin$getSamplerShaderLocs(); } diff --git a/src/main/java/org/ladysnake/satin/mixin/client/gl/CustomFormatPostEffectProcessorMixin.java b/src/main/java/org/ladysnake/satin/mixin/client/gl/CustomFormatPostEffectProcessorMixin.java index d3298c4..4e7680e 100644 --- a/src/main/java/org/ladysnake/satin/mixin/client/gl/CustomFormatPostEffectProcessorMixin.java +++ b/src/main/java/org/ladysnake/satin/mixin/client/gl/CustomFormatPostEffectProcessorMixin.java @@ -17,47 +17,10 @@ */ package org.ladysnake.satin.mixin.client.gl; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import net.minecraft.client.gl.PostEffectProcessor; -import net.minecraft.client.texture.TextureManager; -import net.minecraft.util.Identifier; -import net.minecraft.util.JsonHelper; -import org.ladysnake.satin.impl.CustomFormatFramebuffers; 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.Slice; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.LocalCapture; @Mixin(PostEffectProcessor.class) public class CustomFormatPostEffectProcessorMixin { - @Inject(method = "parseTarget", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gl/PostEffectProcessor;addTarget(Ljava/lang/String;II)V", ordinal = 1), locals = LocalCapture.CAPTURE_FAILSOFT) - private void satin$parseCustomTargetFormat(JsonElement jsonTarget, CallbackInfo ci, JsonObject jsonObject) { - String format = JsonHelper.getString(jsonObject, CustomFormatFramebuffers.FORMAT_KEY, null); - if (format != null) { - CustomFormatFramebuffers.prepareCustomFormat(format); - } - } - - /** - * @reason need to clean up state if an exception is thrown - */ - @Inject( - method = "parseEffect", - slice = @Slice( - from = @At(value = "CONSTANT:FIRST", args = "stringValue=targets"), - to = @At(value = "CONSTANT:FIRST", args = "stringValue=passes") - ), - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/util/InvalidHierarchicalFileException;wrap(Ljava/lang/Exception;)Lnet/minecraft/util/InvalidHierarchicalFileException;" - ), - allow = 1 - ) - private void satin$cleanupCustomTargetFormat(TextureManager textureManager, Identifier id, CallbackInfo ci) { - CustomFormatFramebuffers.clearCustomFormat(); - } } diff --git a/src/main/java/org/ladysnake/satin/mixin/client/gl/JsonEffectGlShaderMixin.java b/src/main/java/org/ladysnake/satin/mixin/client/gl/JsonEffectGlShaderMixin.java deleted file mode 100644 index da28baf..0000000 --- a/src/main/java/org/ladysnake/satin/mixin/client/gl/JsonEffectGlShaderMixin.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Satin - * Copyright (C) 2019-2024 Ladysnake - * - * 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; either - * version 3 of the License, or (at your option) any later version. - * - * 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 - * Lesser 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 org.ladysnake.satin.mixin.client.gl; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import net.minecraft.client.gl.JsonEffectShaderProgram; -import net.minecraft.client.gl.ShaderStage; -import net.minecraft.resource.ResourceFactory; -import net.minecraft.util.Identifier; -import org.ladysnake.satin.impl.SamplerAccess; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.gen.Accessor; -import org.spongepowered.asm.mixin.injection.At; - -import java.util.List; -import java.util.Map; -import java.util.function.IntSupplier; - -/** - * Minecraft does not take into account domains when parsing a shader program. - * These hooks redirect identifier instantiations to allow specifying a domain for shader files. - */ -@Mixin(JsonEffectShaderProgram.class) -public abstract class JsonEffectGlShaderMixin implements SamplerAccess { - @Shadow @Final private Map samplerBinds; - - @Override - public void satin$removeSampler(String name) { - this.samplerBinds.remove(name); - } - - @Override - public boolean satin$hasSampler(String name) { - return this.samplerBinds.containsKey(name); - } - - @Override - @Accessor("samplerNames") - public abstract List satin$getSamplerNames(); - - @Override - @Accessor("samplerLocations") - public abstract List satin$getSamplerShaderLocs(); - - /** - * Fix identifier creation to allow different namespaces - */ - @WrapOperation( - at = @At( - value = "INVOKE", - target = "net/minecraft/util/Identifier.ofVanilla (Ljava/lang/String;)Lnet/minecraft/util/Identifier;", - ordinal = 0 - ), - method = "" - ) - Identifier constructProgramIdentifier(String arg, Operation original, ResourceFactory unused, String id) { - if (!id.contains(":")) { - return original.call(arg); - } - Identifier split = Identifier.of(id); - return Identifier.of(split.getNamespace(), "shaders/program/" + split.getPath() + ".json"); - } - - @WrapOperation( - at = @At( - value = "INVOKE", - target = "net/minecraft/util/Identifier.ofVanilla (Ljava/lang/String;)Lnet/minecraft/util/Identifier;", - ordinal = 0 - ), - method = "loadEffect" - ) - private static Identifier constructProgramIdentifier(String arg, Operation original, ResourceFactory unused, ShaderStage.Type shaderType, String id) { - if (!arg.contains(":")) { - return original.call(arg); - } - Identifier split = Identifier.of(id); - return Identifier.of(split.getNamespace(), "shaders/program/" + split.getPath() + shaderType.getFileExtension()); - } -} diff --git a/src/main/java/org/ladysnake/satin/mixin/client/gl/ShaderLoaderMixin.java b/src/main/java/org/ladysnake/satin/mixin/client/gl/ShaderLoaderMixin.java new file mode 100644 index 0000000..43b90a4 --- /dev/null +++ b/src/main/java/org/ladysnake/satin/mixin/client/gl/ShaderLoaderMixin.java @@ -0,0 +1,9 @@ +package org.ladysnake.satin.mixin.client.gl; + +import net.minecraft.client.gl.ShaderLoader; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ShaderLoader.class) +public class ShaderLoaderMixin { + +} diff --git a/src/main/java/org/ladysnake/satin/mixin/client/render/GameRendererAccessor.java b/src/main/java/org/ladysnake/satin/mixin/client/render/GameRendererAccessor.java new file mode 100644 index 0000000..5954913 --- /dev/null +++ b/src/main/java/org/ladysnake/satin/mixin/client/render/GameRendererAccessor.java @@ -0,0 +1,13 @@ +package org.ladysnake.satin.mixin.client.render; + +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.util.Pool; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.injection.Inject; + +@Mixin(GameRenderer.class) +public interface GameRendererAccessor { + @Accessor + Pool getPool(); +} diff --git a/src/main/resources/mixins.satin.client.json b/src/main/resources/mixins.satin.client.json index 6eb224b..636b732 100644 --- a/src/main/resources/mixins.satin.client.json +++ b/src/main/resources/mixins.satin.client.json @@ -15,7 +15,6 @@ "gl.CustomFormatPostEffectProcessorMixin", "gl.DepthGlFramebufferMixin", "gl.GlUniformMixin", - "gl.JsonEffectGlShaderMixin", "render.RenderLayerAccessor", "render.RenderLayerMixin", "render.RenderLayerMixin$MultiPhaseParametersAccessor", @@ -26,6 +25,8 @@ "defaultRequire": 1 }, "client": [ + "gl.ShaderLoaderMixin", + "render.GameRendererAccessor", "render.WorldRendererMixin" ] }