From b7f3cf34e6d1b033cca90c03f62c08a486682c33 Mon Sep 17 00:00:00 2001
From: bluebear94 <uruwi@protonmail.com>
Date: Sun, 26 Nov 2023 07:53:58 -0500
Subject: [PATCH] Add registry for core shaders in custom namespaces (#3416)

* 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>

* Reorder imports to comply with checkstyle

* Rename some classes

* Fix compilation error in test mod

---------

Co-authored-by: Juuz <6596629+Juuxel@users.noreply.github.com>
Co-authored-by: modmuss50 <modmuss50@gmail.com>
---
 .../v1/CoreShaderRegistrationCallback.java    | 73 +++++++++++++++++++
 .../impl/client/rendering/FabricShader.java   | 45 ++++++++++++
 .../rendering/shader/GameRendererMixin.java   | 56 ++++++++++++++
 .../shader/ShaderImportProcessorMixin.java    | 57 +++++++++++++++
 .../client/rendering/shader/ShaderMixin.java  | 58 +++++++++++++++
 .../resources/fabric-rendering-v1.mixins.json |  5 +-
 .../rendering/client/HudAndShaderTest.java    | 71 ++++++++++++++++++
 .../shaders/core/test.fsh                     | 10 +++
 .../shaders/core/test.json                    | 18 +++++
 .../shaders/core/test.vsh                     | 12 +++
 .../shaders/core/test_local_import.glsl       |  6 ++
 .../shaders/include/test_include.glsl         |  6 ++
 .../src/testmod/resources/fabric.mod.json     |  3 +-
 13 files changed, 418 insertions(+), 2 deletions(-)
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/CoreShaderRegistrationCallback.java
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/FabricShader.java
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/GameRendererMixin.java
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/ShaderImportProcessorMixin.java
 create mode 100644 fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/ShaderMixin.java
 create mode 100644 fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/client/HudAndShaderTest.java
 create mode 100644 fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.fsh
 create mode 100644 fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.json
 create mode 100644 fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.vsh
 create mode 100644 fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test_local_import.glsl
 create mode 100644 fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/include/test_include.glsl

diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/CoreShaderRegistrationCallback.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/CoreShaderRegistrationCallback.java
new file mode 100644
index 000000000..f945cdff3
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/api/client/rendering/v1/CoreShaderRegistrationCallback.java
@@ -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;
+	}
+}
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/FabricShader.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/FabricShader.java
new file mode 100644
index 000000000..075819011
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/impl/client/rendering/FabricShader.java
@@ -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());
+	}
+}
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/GameRendererMixin.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/GameRendererMixin.java
new file mode 100644
index 000000000..dc48bf171
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/GameRendererMixin.java
@@ -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);
+	}
+}
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/ShaderImportProcessorMixin.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/ShaderImportProcessorMixin.java
new file mode 100644
index 000000000..f1f3ba542
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/ShaderImportProcessorMixin.java
@@ -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;
+	}
+}
diff --git a/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/ShaderMixin.java b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/ShaderMixin.java
new file mode 100644
index 000000000..bd24c3e3d
--- /dev/null
+++ b/fabric-rendering-v1/src/main/java/net/fabricmc/fabric/mixin/client/rendering/shader/ShaderMixin.java
@@ -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;
+	}
+}
diff --git a/fabric-rendering-v1/src/main/resources/fabric-rendering-v1.mixins.json b/fabric-rendering-v1/src/main/resources/fabric-rendering-v1.mixins.json
index 3c1adc3f5..8fba29b3b 100644
--- a/fabric-rendering-v1/src/main/resources/fabric-rendering-v1.mixins.json
+++ b/fabric-rendering-v1/src/main/resources/fabric-rendering-v1.mixins.json
@@ -16,7 +16,10 @@
     "MixinBlockEntityRenderers",
     "MixinEntityRenderers",
     "ScreenMixin",
-    "DimensionEffectsAccessor"
+    "DimensionEffectsAccessor",
+    "shader.GameRendererMixin",
+    "shader.ShaderImportProcessorMixin",
+    "shader.ShaderMixin"
   ],
   "injectors": {
     "defaultRequire": 1
diff --git a/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/client/HudAndShaderTest.java b/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/client/HudAndShaderTest.java
new file mode 100644
index 000000000..063a39ea9
--- /dev/null
+++ b/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/client/HudAndShaderTest.java
@@ -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);
+		});
+	}
+}
diff --git a/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.fsh b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.fsh
new file mode 100644
index 000000000..a7a042f21
--- /dev/null
+++ b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.fsh
@@ -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);
+}
diff --git a/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.json b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.json
new file mode 100644
index 000000000..7ec3e9ae5
--- /dev/null
+++ b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.json
@@ -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 ] }
+  ]
+}
diff --git a/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.vsh b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.vsh
new file mode 100644
index 000000000..1113ad901
--- /dev/null
+++ b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test.vsh
@@ -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)));
+}
diff --git a/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test_local_import.glsl b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test_local_import.glsl
new file mode 100644
index 000000000..7914d421d
--- /dev/null
+++ b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/core/test_local_import.glsl
@@ -0,0 +1,6 @@
+#version 150
+
+// Just a simple function to test importing custom files.
+vec4 applyMatrix(mat4 matrix, vec4 vector) {
+    return matrix * vector;
+}
diff --git a/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/include/test_include.glsl b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/include/test_include.glsl
new file mode 100644
index 000000000..04e1d0884
--- /dev/null
+++ b/fabric-rendering-v1/src/testmod/resources/assets/fabric-rendering-v1-testmod/shaders/include/test_include.glsl
@@ -0,0 +1,6 @@
+#version 150
+
+// Just a simple function to test importing custom files.
+vec4 applyColor(vec4 color, vec4 colorModulator) {
+    return color * colorModulator;
+}
diff --git a/fabric-rendering-v1/src/testmod/resources/fabric.mod.json b/fabric-rendering-v1/src/testmod/resources/fabric.mod.json
index 243377b6d..d48096656 100644
--- a/fabric-rendering-v1/src/testmod/resources/fabric.mod.json
+++ b/fabric-rendering-v1/src/testmod/resources/fabric.mod.json
@@ -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"
     ]
   }
 }