Add registry for core shaders in custom namespaces (#2901)

* Add registry for core shaders in custom namespaces

* Apply suggestions from code review

Co-authored-by: modmuss50 <modmuss50@gmail.com>

* Use Identifier.NAMESPACE_SEPARATOR instead of ":"

* Remove fabric_ prefixes from mixins

* Move test rendering to lower-right corner for test screenshots

---------

Co-authored-by: modmuss50 <modmuss50@gmail.com>
(cherry picked from commit ad177755a6)
This commit is contained in:
Juuz 2023-03-15 11:15:40 +02:00 committed by modmuss50
parent 09a3510c69
commit 8f87821716
13 changed files with 417 additions and 2 deletions

View file

@ -0,0 +1,73 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.client.rendering.v1;
import java.io.IOException;
import java.util.function.Consumer;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.client.gl.ShaderProgram;
import net.minecraft.client.render.VertexFormat;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Called when core shaders ({@linkplain ShaderProgram shader programs} loaded from {@code assets/<namespace>/shaders/core})
* are loaded to register custom modded shaders.
*
* <p>Fabric API also modifies the {@code #moj_import} feature in core shaders to accept
* arbitrary namespaces for shaders loaded using the {@code <filename.glsl>} syntax.
* For example, {@code #moj_import <my_mod:test.glsl>} would import the shader from
* {@code assets/my_mod/shaders/include/test.glsl}.
*/
@FunctionalInterface
public interface CoreShaderRegistrationCallback {
Event<CoreShaderRegistrationCallback> EVENT = EventFactory.createArrayBacked(CoreShaderRegistrationCallback.class, callbacks -> context -> {
for (CoreShaderRegistrationCallback callback : callbacks) {
callback.registerShaders(context);
}
});
/**
* Registers core shaders using the registration context.
*
* @param context the registration context
*/
void registerShaders(RegistrationContext context) throws IOException;
/**
* A context object used to create and register core shader programs.
*
* <p>This is not meant for implementation by users of the API.
*/
@ApiStatus.NonExtendable
interface RegistrationContext {
/**
* Creates and registers a core shader program.
*
* <p>The program is loaded from {@code assets/<namespace>/shaders/core/<path>.json}.
*
* @param id the program ID
* @param vertexFormat the vertex format used by the shader
* @param loadCallback a callback that is called when the shader program has been successfully loaded
*/
void register(Identifier id, VertexFormat vertexFormat, Consumer<ShaderProgram> loadCallback) throws IOException;
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.client.rendering;
import java.io.IOException;
import net.minecraft.client.gl.ShaderProgram;
import net.minecraft.client.render.VertexFormat;
import net.minecraft.resource.ResourceFactory;
import net.minecraft.util.Identifier;
public final class FabricShaderProgram extends ShaderProgram {
public FabricShaderProgram(ResourceFactory factory, Identifier name, VertexFormat format) throws IOException {
super(factory, name.toString(), format);
}
/**
* Rewrites the input string containing an identifier
* with the namespace of the id in the front instead of in the middle.
*
* <p>Example: {@code shaders/core/my_mod:xyz} -> {@code my_mod:shaders/core/xyz}
*
* @param input the raw input string
* @param containedId the ID contained within the input string
* @return the corrected full ID string
*/
public static String rewriteAsId(String input, String containedId) {
Identifier contained = new Identifier(containedId);
return contained.getNamespace() + Identifier.NAMESPACE_SEPARATOR + input.replace(containedId, contained.getPath());
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.client.rendering.shader;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import com.mojang.datafixers.util.Pair;
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;
import net.minecraft.client.gl.ShaderProgram;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.resource.ResourceFactory;
import net.fabricmc.fabric.api.client.rendering.v1.CoreShaderRegistrationCallback;
import net.fabricmc.fabric.impl.client.rendering.FabricShaderProgram;
/**
* Implements custom core shader registration (CoreShaderRegistrationCallback).
*/
@Mixin(GameRenderer.class)
abstract class GameRendererMixin {
@Inject(
method = "loadPrograms",
at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z", remap = false, shift = At.Shift.AFTER),
slice = @Slice(from = @At(value = "NEW", target = "net/minecraft/client/gl/ShaderProgram", ordinal = 0)),
locals = LocalCapture.CAPTURE_FAILHARD
)
private void registerShaders(ResourceFactory factory, CallbackInfo info, List<?> shaderStages, List<Pair<ShaderProgram, Consumer<ShaderProgram>>> programs) throws IOException {
CoreShaderRegistrationCallback.RegistrationContext context = (id, vertexFormat, loadCallback) -> {
ShaderProgram program = new FabricShaderProgram(factory, id, vertexFormat);
programs.add(Pair.of(program, loadCallback));
};
CoreShaderRegistrationCallback.EVENT.invoker().registerShaders(context);
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.client.rendering.shader;
import org.spongepowered.asm.mixin.Mixin;
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.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.client.rendering.FabricShaderProgram;
/**
* Lets modded shaders {@code #moj_import} shaders from any namespace with the
* {@code <>} syntax.
*/
@Mixin(targets = "net.minecraft.client.gl.ShaderProgram$1")
abstract class ShaderProgramImportProcessorMixin {
@Unique
private String capturedImport;
@Inject(method = "loadImport", at = @At("HEAD"))
private void captureImport(boolean inline, String name, CallbackInfoReturnable<String> info) {
capturedImport = name;
}
@ModifyVariable(method = "loadImport", at = @At("STORE"), ordinal = 0, argsOnly = true)
private String modifyImportId(String id, boolean inline) {
if (!inline && capturedImport.contains(String.valueOf(Identifier.NAMESPACE_SEPARATOR))) {
return FabricShaderProgram.rewriteAsId(id, capturedImport);
}
return id;
}
@Inject(method = "loadImport", at = @At("RETURN"))
private void uncaptureImport(boolean inline, String name, CallbackInfoReturnable<String> info) {
capturedImport = null;
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.client.rendering.shader;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import net.minecraft.client.gl.ShaderProgram;
import net.minecraft.client.gl.ShaderStage;
import net.minecraft.resource.ResourceFactory;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.client.rendering.FabricShaderProgram;
@Mixin(ShaderProgram.class)
abstract class ShaderProgramMixin {
@Shadow
@Final
private String name;
// Allow loading FabricShaderPrograms from arbitrary namespaces.
@ModifyArg(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Identifier;<init>(Ljava/lang/String;)V"), allow = 1)
private String modifyProgramId(String id) {
if ((Object) this instanceof FabricShaderProgram) {
return FabricShaderProgram.rewriteAsId(id, name);
}
return id;
}
// Allow loading shader stages from arbitrary namespaces.
@ModifyVariable(method = "loadShader", at = @At("STORE"), ordinal = 1)
private static String modifyStageId(String id, ResourceFactory factory, ShaderStage.Type type, String name) {
if (name.contains(String.valueOf(Identifier.NAMESPACE_SEPARATOR))) {
return FabricShaderProgram.rewriteAsId(id, name);
}
return id;
}
}

View file

@ -16,7 +16,10 @@
"BlockEntityRendererFactoriesMixin",
"EntityRenderersMixin",
"ScreenMixin",
"DimensionEffectsAccessor"
"DimensionEffectsAccessor",
"shader.GameRendererMixin",
"shader.ShaderProgramImportProcessorMixin",
"shader.ShaderProgramMixin"
],
"injectors": {
"defaultRequire": 1

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.rendering.client;
import com.mojang.blaze3d.systems.RenderSystem;
import org.joml.Matrix4f;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gl.ShaderProgram;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.BufferRenderer;
import net.minecraft.client.render.Tessellator;
import net.minecraft.client.render.VertexFormat;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.util.Window;
import net.minecraft.util.Identifier;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.rendering.v1.CoreShaderRegistrationCallback;
import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback;
/**
* Tests {@link HudRenderCallback} and {@link CoreShaderRegistrationCallback} by drawing a green rectangle
* in the lower-right corner of the screen.
*/
public class HudAndShaderTest implements ClientModInitializer {
private static ShaderProgram testShader;
@Override
public void onInitializeClient() {
CoreShaderRegistrationCallback.EVENT.register(context -> {
// Register a custom shader taking POSITION vertices.
Identifier id = new Identifier("fabric-rendering-v1-testmod", "test");
context.register(id, VertexFormats.POSITION, program -> testShader = program);
});
HudRenderCallback.EVENT.register((matrices, tickDelta) -> {
MinecraftClient client = MinecraftClient.getInstance();
Window window = client.getWindow();
int x = window.getScaledWidth() - 15;
int y = window.getScaledHeight() - 15;
RenderSystem.setShader(() -> testShader);
RenderSystem.setShaderColor(0f, 1f, 0f, 1f);
Matrix4f positionMatrix = matrices.peek().getPositionMatrix();
BufferBuilder buffer = Tessellator.getInstance().getBuffer();
buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION);
buffer.vertex(positionMatrix, x, y, 50).next();
buffer.vertex(positionMatrix, x, y + 10, 50).next();
buffer.vertex(positionMatrix, x + 10, y + 10, 50).next();
buffer.vertex(positionMatrix, x + 10, y, 50).next();
BufferRenderer.drawWithGlobalProgram(buffer.end());
// Reset shader color
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
});
}
}

View file

@ -0,0 +1,10 @@
#version 150
#moj_import <fabric-rendering-v1-testmod:test_include.glsl>
uniform vec4 ColorModulator;
out vec4 fragColor;
void main() {
fragColor = applyColor(vec4(1.0, 1.0, 1.0, 1.0), ColorModulator);
}

View file

@ -0,0 +1,18 @@
{
"blend": {
"func": "add",
"srcrgb": "srcalpha",
"dstrgb": "1-srcalpha"
},
"vertex": "fabric-rendering-v1-testmod:test",
"fragment": "fabric-rendering-v1-testmod:test",
"attributes": [
],
"samplers": [
],
"uniforms": [
{ "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
{ "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
{ "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }
]
}

View file

@ -0,0 +1,12 @@
#version 150
#moj_import "test_local_import.glsl"
in vec3 Position;
uniform mat4 ProjMat;
uniform mat4 ModelViewMat;
void main() {
gl_Position = applyMatrix(ProjMat, applyMatrix(ModelViewMat, vec4(Position, 1.0)));
}

View file

@ -0,0 +1,6 @@
#version 150
// Just a simple function to test importing custom files.
vec4 applyMatrix(mat4 matrix, vec4 vector) {
return matrix * vector;
}

View file

@ -0,0 +1,6 @@
#version 150
// Just a simple function to test importing custom files.
vec4 applyColor(vec4 color, vec4 colorModulator) {
return color * colorModulator;
}

View file

@ -17,7 +17,8 @@
"net.fabricmc.fabric.test.rendering.client.ArmorRenderingTests",
"net.fabricmc.fabric.test.rendering.client.FeatureRendererTest",
"net.fabricmc.fabric.test.rendering.client.TooltipComponentTests",
"net.fabricmc.fabric.test.rendering.client.DimensionalRenderingTest"
"net.fabricmc.fabric.test.rendering.client.DimensionalRenderingTest",
"net.fabricmc.fabric.test.rendering.client.HudAndShaderTest"
]
}
}