Skip to content

Commit

Permalink
Add registry for core shaders in custom namespaces (#3416)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* 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 <[email protected]>

* Reorder imports to comply with checkstyle

* Rename some classes

* Fix compilation error in test mod

---------

Co-authored-by: Juuz <[email protected]>
Co-authored-by: modmuss50 <[email protected]>
  • Loading branch information
3 people authored Nov 26, 2023
1 parent 0dc92ab commit b7f3cf3
Show file tree
Hide file tree
Showing 13 changed files with 418 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -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.render.Shader;
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 Shader 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<Shader> loadCallback) throws IOException;
}
}
Original file line number Diff line number Diff line change
@@ -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.render.Shader;
import net.minecraft.client.render.VertexFormat;
import net.minecraft.resource.ResourceFactory;
import net.minecraft.util.Identifier;

public final class FabricShader extends Shader {
public FabricShader(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());
}
}
Original file line number Diff line number Diff line change
@@ -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.render.GameRenderer;
import net.minecraft.client.render.Shader;
import net.minecraft.resource.ResourceManager;

import net.fabricmc.fabric.api.client.rendering.v1.CoreShaderRegistrationCallback;
import net.fabricmc.fabric.impl.client.rendering.FabricShader;

/**
* Implements custom core shader registration (CoreShaderRegistrationCallback).
*/
@Mixin(GameRenderer.class)
abstract class GameRendererMixin {
@Inject(
method = "loadShaders",
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/render/Shader", ordinal = 0)),
locals = LocalCapture.CAPTURE_FAILHARD
)
private void registerShaders(ResourceManager factory, CallbackInfo info, List<?> shaderStages, List<Pair<Shader, Consumer<Shader>>> programs) throws IOException {
CoreShaderRegistrationCallback.RegistrationContext context = (id, vertexFormat, loadCallback) -> {
Shader program = new FabricShader(factory, id, vertexFormat);
programs.add(Pair.of(program, loadCallback));
};
CoreShaderRegistrationCallback.EVENT.invoker().registerShaders(context);
}
}
Original file line number Diff line number Diff line change
@@ -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.FabricShader;

/**
* Lets modded shaders {@code #moj_import} shaders from any namespace with the
* {@code <>} syntax.
*/
@Mixin(targets = "net.minecraft.client.render.Shader$1")
abstract class ShaderImportProcessorMixin {
@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 FabricShader.rewriteAsId(id, capturedImport);
}

return id;
}

@Inject(method = "loadImport", at = @At("RETURN"))
private void uncaptureImport(boolean inline, String name, CallbackInfoReturnable<String> info) {
capturedImport = null;
}
}
Original file line number Diff line number Diff line change
@@ -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.Program;
import net.minecraft.client.render.Shader;
import net.minecraft.resource.ResourceFactory;
import net.minecraft.util.Identifier;

import net.fabricmc.fabric.impl.client.rendering.FabricShader;

@Mixin(Shader.class)
abstract class ShaderMixin {
@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 FabricShader) {
return FabricShader.rewriteAsId(id, name);
}

return id;
}

// Allow loading shader stages from arbitrary namespaces.
@ModifyVariable(method = "loadProgram", at = @At("STORE"), ordinal = 1)
private static String modifyStageId(String id, ResourceFactory factory, Program.Type type, String name) {
if (name.contains(String.valueOf(Identifier.NAMESPACE_SEPARATOR))) {
return FabricShader.rewriteAsId(id, name);
}

return id;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
"MixinBlockEntityRenderers",
"MixinEntityRenderers",
"ScreenMixin",
"DimensionEffectsAccessor"
"DimensionEffectsAccessor",
"shader.GameRendererMixin",
"shader.ShaderImportProcessorMixin",
"shader.ShaderMixin"
],
"injectors": {
"defaultRequire": 1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* 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 net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.BufferBuilder;
import net.minecraft.client.render.BufferRenderer;
import net.minecraft.client.render.Shader;
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.minecraft.util.math.Matrix4f;

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 Shader 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();
buffer.end();
BufferRenderer.draw(buffer);
// Reset shader color
RenderSystem.setShaderColor(1f, 1f, 1f, 1f);
});
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Loading

0 comments on commit b7f3cf3

Please sign in to comment.