diff --git a/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/ColorResolverRegistry.java b/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/ColorResolverRegistry.java
new file mode 100644
index 0000000000..4bdddc86ec
--- /dev/null
+++ b/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/ColorResolverRegistry.java
@@ -0,0 +1,81 @@
+/*
+ * 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.util.Set;
+
+import org.jetbrains.annotations.UnmodifiableView;
+
+import net.minecraft.client.color.world.BiomeColors;
+import net.minecraft.world.BlockRenderView;
+import net.minecraft.world.biome.ColorResolver;
+
+import net.fabricmc.fabric.impl.client.rendering.ColorResolverRegistryImpl;
+
+/**
+ * The registry for custom {@link ColorResolver}s. Custom resolvers must be registered during client initialization for
+ * them to be usable in {@link BlockRenderView#getColor}. Calling this method may throw an exception if the passed
+ * resolver is not registered with this class. Vanilla resolvers found in {@link BiomeColors} are automatically
+ * registered.
+ *
+ *
Other mods may also require custom resolvers to be registered if they provide additional functionality related to
+ * color resolvers.
+ */
+public final class ColorResolverRegistry {
+ private ColorResolverRegistry() {
+ }
+
+ /**
+ * Registers a custom {@link ColorResolver} for use in {@link BlockRenderView#getColor}. This method should be
+ * called during client initialization.
+ *
+ * @param resolver the resolver to register
+ */
+ public static void register(ColorResolver resolver) {
+ ColorResolverRegistryImpl.register(resolver);
+ }
+
+ /**
+ * Gets a view of all registered {@link ColorResolver}s, including all vanilla resolvers.
+ *
+ * @return a view of all registered resolvers
+ */
+ @UnmodifiableView
+ public static Set getAllResolvers() {
+ return ColorResolverRegistryImpl.getAllResolvers();
+ }
+
+ /**
+ * Gets a view of all registered {@link ColorResolver}s, not including vanilla resolvers.
+ *
+ * @return a view of all registered custom resolvers
+ */
+ @UnmodifiableView
+ public static Set getCustomResolvers() {
+ return ColorResolverRegistryImpl.getCustomResolvers();
+ }
+
+ /**
+ * Checks whether the given {@link ColorResolver} is registered. Vanilla resolvers are always registered.
+ *
+ * @param resolver the resolver
+ * @return whether the given resolver is registered
+ */
+ public static boolean isRegistered(ColorResolver resolver) {
+ return getAllResolvers().contains(resolver);
+ }
+}
diff --git a/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/ColorResolverRegistryImpl.java b/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/ColorResolverRegistryImpl.java
new file mode 100644
index 0000000000..e49c231d68
--- /dev/null
+++ b/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/ColorResolverRegistryImpl.java
@@ -0,0 +1,74 @@
+/*
+ * 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.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Function;
+
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
+import org.jetbrains.annotations.UnmodifiableView;
+
+import net.minecraft.client.color.world.BiomeColors;
+import net.minecraft.client.world.BiomeColorCache;
+import net.minecraft.world.biome.ColorResolver;
+
+public final class ColorResolverRegistryImpl {
+ // Includes vanilla resolvers
+ private static final Set ALL_RESOLVERS = new HashSet<>();
+ // Does not include vanilla resolvers
+ private static final Set CUSTOM_RESOLVERS = new HashSet<>();
+ private static final Set ALL_RESOLVERS_VIEW = Collections.unmodifiableSet(ALL_RESOLVERS);
+ private static final Set CUSTOM_RESOLVERS_VIEW = Collections.unmodifiableSet(CUSTOM_RESOLVERS);
+
+ static {
+ ALL_RESOLVERS.add(BiomeColors.GRASS_COLOR);
+ ALL_RESOLVERS.add(BiomeColors.FOLIAGE_COLOR);
+ ALL_RESOLVERS.add(BiomeColors.WATER_COLOR);
+ }
+
+ private ColorResolverRegistryImpl() {
+ }
+
+ public static void register(ColorResolver resolver) {
+ ALL_RESOLVERS.add(resolver);
+ CUSTOM_RESOLVERS.add(resolver);
+ }
+
+ @UnmodifiableView
+ public static Set getAllResolvers() {
+ return ALL_RESOLVERS_VIEW;
+ }
+
+ @UnmodifiableView
+ public static Set getCustomResolvers() {
+ return CUSTOM_RESOLVERS_VIEW;
+ }
+
+ public static Reference2ReferenceMap createCustomCacheMap(Function cacheFactory) {
+ Reference2ReferenceOpenHashMap map = new Reference2ReferenceOpenHashMap<>();
+
+ for (ColorResolver resolver : CUSTOM_RESOLVERS) {
+ map.put(resolver, cacheFactory.apply(resolver));
+ }
+
+ map.trim();
+ return map;
+ }
+}
diff --git a/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/ClientWorldMixin.java b/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/ClientWorldMixin.java
new file mode 100644
index 0000000000..8249e6c3f5
--- /dev/null
+++ b/fabric-rendering-v1/src/client/java/net/fabricmc/fabric/mixin/client/rendering/ClientWorldMixin.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.mixin.client.rendering;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+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.callback.CallbackInfo;
+
+import net.minecraft.client.world.BiomeColorCache;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.ChunkPos;
+import net.minecraft.world.biome.ColorResolver;
+
+import net.fabricmc.fabric.impl.client.rendering.ColorResolverRegistryImpl;
+
+@Mixin(ClientWorld.class)
+public abstract class ClientWorldMixin {
+ // Do not use the vanilla map because it is an Object2ObjectArrayMap. Array maps have O(n) retrievals compared to
+ // hash maps' O(1) retrievals. If many custom ColorResolvers are registered, this may have a non-negligible
+ // performance impact.
+ @Unique
+ private final Reference2ReferenceMap customColorCache = ColorResolverRegistryImpl.createCustomCacheMap(resolver -> new BiomeColorCache(pos -> calculateColor(pos, resolver)));
+
+ @Shadow
+ public abstract int calculateColor(BlockPos pos, ColorResolver colorResolver);
+
+ @Inject(method = "resetChunkColor(Lnet/minecraft/util/math/ChunkPos;)V", at = @At("RETURN"))
+ private void onResetChunkColor(ChunkPos chunkPos, CallbackInfo ci) {
+ for (BiomeColorCache cache : customColorCache.values()) {
+ cache.reset(chunkPos.x, chunkPos.z);
+ }
+ }
+
+ @Inject(method = "reloadColor()V", at = @At("RETURN"))
+ private void onReloadColor(CallbackInfo ci) {
+ for (BiomeColorCache cache : customColorCache.values()) {
+ cache.reset();
+ }
+ }
+
+ @ModifyExpressionValue(method = "getColor(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/world/biome/ColorResolver;)I", at = @At(value = "INVOKE", target = "it/unimi/dsi/fastutil/objects/Object2ObjectArrayMap.get(Ljava/lang/Object;)Ljava/lang/Object;"))
+ private Object modifyNullCache(/* BiomeColorCache */ Object cache, BlockPos pos, ColorResolver resolver) {
+ if (cache == null) {
+ cache = customColorCache.get(resolver);
+
+ if (cache == null) {
+ throw new UnsupportedOperationException("ClientWorld.getColor called with unregistered ColorResolver " + resolver);
+ }
+ }
+
+ return cache;
+ }
+}
diff --git a/fabric-rendering-v1/src/client/resources/fabric-rendering-v1.mixins.json b/fabric-rendering-v1/src/client/resources/fabric-rendering-v1.mixins.json
index fd802ffc65..0ec5218d1c 100644
--- a/fabric-rendering-v1/src/client/resources/fabric-rendering-v1.mixins.json
+++ b/fabric-rendering-v1/src/client/resources/fabric-rendering-v1.mixins.json
@@ -9,6 +9,7 @@
"BlockEntityRendererFactoriesMixin",
"BuiltinModelItemRendererMixin",
"CapeFeatureRendererMixin",
+ "ClientWorldMixin",
"DimensionEffectsAccessor",
"EntityModelLayersAccessor",
"EntityModelsMixin",
diff --git a/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/CustomColorResolverTestInit.java b/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/CustomColorResolverTestInit.java
new file mode 100644
index 0000000000..926740eed3
--- /dev/null
+++ b/fabric-rendering-v1/src/testmod/java/net/fabricmc/fabric/test/rendering/CustomColorResolverTestInit.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import net.minecraft.block.AbstractBlock;
+import net.minecraft.block.Block;
+import net.minecraft.item.BlockItem;
+import net.minecraft.item.Item;
+import net.minecraft.registry.Registries;
+import net.minecraft.registry.Registry;
+import net.minecraft.util.Identifier;
+
+import net.fabricmc.api.ModInitializer;
+
+public class CustomColorResolverTestInit implements ModInitializer {
+ public static final Block CUSTOM_COLOR_BLOCK = new Block(AbstractBlock.Settings.create());
+ public static final Item CUSTOM_COLOR_BLOCK_ITEM = new BlockItem(CUSTOM_COLOR_BLOCK, new Item.Settings());
+
+ @Override
+ public void onInitialize() {
+ Registry.register(Registries.BLOCK, new Identifier("fabric-rendering-v1-testmod", "custom_color_block"), CUSTOM_COLOR_BLOCK);
+ Registry.register(Registries.ITEM, new Identifier("fabric-rendering-v1-testmod", "custom_color_block"), CUSTOM_COLOR_BLOCK_ITEM);
+ }
+}
diff --git a/fabric-rendering-v1/src/testmod/resources/fabric.mod.json b/fabric-rendering-v1/src/testmod/resources/fabric.mod.json
index 3c5f3fe0a6..57107b39f9 100644
--- a/fabric-rendering-v1/src/testmod/resources/fabric.mod.json
+++ b/fabric-rendering-v1/src/testmod/resources/fabric.mod.json
@@ -8,11 +8,13 @@
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.rendering.CustomAtlasSourcesTestInit",
+ "net.fabricmc.fabric.test.rendering.CustomColorResolverTestInit",
"net.fabricmc.fabric.test.rendering.TooltipComponentTestInit"
],
"client": [
"net.fabricmc.fabric.test.rendering.client.ArmorRenderingTests",
"net.fabricmc.fabric.test.rendering.client.CustomAtlasSourcesTest",
+ "net.fabricmc.fabric.test.rendering.client.CustomColorResolverTest",
"net.fabricmc.fabric.test.rendering.client.DimensionalRenderingTest",
"net.fabricmc.fabric.test.rendering.client.FeatureRendererTest",
"net.fabricmc.fabric.test.rendering.client.HudAndShaderTest",
diff --git a/fabric-rendering-v1/src/testmodClient/java/net/fabricmc/fabric/test/rendering/client/CustomColorResolverTest.java b/fabric-rendering-v1/src/testmodClient/java/net/fabricmc/fabric/test/rendering/client/CustomColorResolverTest.java
new file mode 100644
index 0000000000..3d7cd88b10
--- /dev/null
+++ b/fabric-rendering-v1/src/testmodClient/java/net/fabricmc/fabric/test/rendering/client/CustomColorResolverTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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 net.minecraft.world.biome.ColorResolver;
+
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
+import net.fabricmc.fabric.api.client.rendering.v1.ColorResolverRegistry;
+import net.fabricmc.fabric.test.rendering.CustomColorResolverTestInit;
+
+public class CustomColorResolverTest implements ClientModInitializer {
+ public static final ColorResolver TEST_COLOR_RESOLVER = (biome, x, z) -> {
+ if (biome.hasPrecipitation()) {
+ return 0xFFFF00FF;
+ } else {
+ return 0xFFFFFF00;
+ }
+ };
+
+ @Override
+ public void onInitializeClient() {
+ ColorResolverRegistry.register(TEST_COLOR_RESOLVER);
+
+ ColorProviderRegistry.BLOCK.register((state, world, pos, tintIndex) -> {
+ if (world != null && pos != null) {
+ return world.getColor(pos, TEST_COLOR_RESOLVER);
+ } else {
+ return -1;
+ }
+ }, CustomColorResolverTestInit.CUSTOM_COLOR_BLOCK);
+ }
+}
diff --git a/fabric-rendering-v1/src/testmodClient/resources/assets/fabric-rendering-v1-testmod/blockstates/custom_color_block.json b/fabric-rendering-v1/src/testmodClient/resources/assets/fabric-rendering-v1-testmod/blockstates/custom_color_block.json
new file mode 100644
index 0000000000..2b12403da0
--- /dev/null
+++ b/fabric-rendering-v1/src/testmodClient/resources/assets/fabric-rendering-v1-testmod/blockstates/custom_color_block.json
@@ -0,0 +1,5 @@
+{
+ "variants": {
+ "": { "model": "fabric-rendering-v1-testmod:block/custom_color_block" }
+ }
+}
diff --git a/fabric-rendering-v1/src/testmodClient/resources/assets/fabric-rendering-v1-testmod/models/block/custom_color_block.json b/fabric-rendering-v1/src/testmodClient/resources/assets/fabric-rendering-v1-testmod/models/block/custom_color_block.json
new file mode 100644
index 0000000000..62703835e6
--- /dev/null
+++ b/fabric-rendering-v1/src/testmodClient/resources/assets/fabric-rendering-v1-testmod/models/block/custom_color_block.json
@@ -0,0 +1,20 @@
+{
+ "parent": "block/block",
+ "textures": {
+ "all": "fabric-rendering-v1-testmod:block/blank",
+ "particle": "#all"
+ },
+ "elements": [
+ { "from": [ 0, 0, 0 ],
+ "to": [ 16, 16, 16 ],
+ "faces": {
+ "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "cullface": "down" },
+ "up": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "tintindex": 0, "cullface": "up" },
+ "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "cullface": "north" },
+ "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "cullface": "south" },
+ "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "cullface": "west" },
+ "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#all", "cullface": "east" }
+ }
+ }
+ ]
+}
diff --git a/fabric-rendering-v1/src/testmodClient/resources/assets/fabric-rendering-v1-testmod/textures/block/blank.png b/fabric-rendering-v1/src/testmodClient/resources/assets/fabric-rendering-v1-testmod/textures/block/blank.png
new file mode 100644
index 0000000000..3869a61f64
Binary files /dev/null and b/fabric-rendering-v1/src/testmodClient/resources/assets/fabric-rendering-v1-testmod/textures/block/blank.png differ