Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;

import net.fabricmc.fabric.api.client.rendering.v1.BlockColorRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.BlockTintsFactory;

import org.joml.Vector3f;
import org.jspecify.annotations.Nullable;

Expand Down Expand Up @@ -201,6 +205,19 @@ private int computeTintColor(final BlockAndTintGetter level, final BlockState st
if (!tintSourcesInitialized) {
configureTintCache(state);
tintSourcesInitialized = true;

if (this.tintSources.isEmpty()) {
Comment thread
marchermans marked this conversation as resolved.
Outdated
final BlockTintsFactory factory = BlockColorRegistry.factoryFor(state);
if (factory != null) {
factory.collect(state, level, pos, this.computedTintValues);
}

if (!this.computedTintValues.isEmpty()) {
for (int i = 0; i < this.computedTintValues.size(); i++) {
this.tintSources.add(null);
}
}
}
}

if (tintIndex >= tintSources.size()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@

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

import net.minecraft.world.level.block.state.BlockState;

import org.jspecify.annotations.Nullable;

/**
* The registry for {@link BlockTintSource}s.
*/
Expand All @@ -44,4 +48,25 @@ private BlockColorRegistry() {
public static void register(List<BlockTintSource> layers, Block... blocks) {
BlockColorRegistryImpl.register(layers, blocks);
}

/**
* Register a block tint factory for one or more blocks. Overriding existing registration is allowed.
*
* @param factory The factory which allows dynamic tinting.
* @param blocks The blocks which should be colored using the given factory.
*/
public static void register(BlockTintsFactory factory, Block... blocks) {
BlockColorRegistryImpl.register(factory, blocks);
}

/**
* Retrieves the current {@link BlockTintsFactory factory}, or {@code null} if no factory exists,
* for the given {@link BlockState block state}.
*
* @param blockState The block state to look up.
* @return The factory.
*/
public static @Nullable BlockTintsFactory factoryFor(BlockState blockState) {
Comment thread
marchermans marked this conversation as resolved.
Outdated
return BlockColorRegistryImpl.factoryFor(blockState);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package net.fabricmc.fabric.api.client.rendering.v1;

import it.unimi.dsi.fastutil.ints.IntList;

import net.minecraft.client.renderer.block.BlockAndTintGetter;
import net.minecraft.client.resources.model.geometry.BakedQuad;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;

/**
* This factory takes over the collection of tint colors in a model renderer.
* <p>
* If this factory provides any tints in the tintValues collections of its {@link #collect(BlockState, BlockAndTintGetter, BlockPos, IntList)}
* method then the default vanilla behaviour of iterating the registered {@link net.minecraft.client.color.block.BlockTintSource block tint sources}
* is skipped.
* </p>
* <p>
* This factory is only invoked if no {@link net.minecraft.client.color.block.BlockTintSource tint source} has been registered for the {@link BlockState block state}.
* </p>
*/
@FunctionalInterface
public interface BlockTintsFactory
{
/**
* Invoked to collect the dynamic tint values for the given block state.
* <p>
* The tint applied to a given {@link net.minecraft.client.resources.model.geometry.BakedQuad quad}
* is then determined based on the index stored in {@link BakedQuad.MaterialInfo#tintIndex()} by looking
* them up in the tint values list after this collect method is called.
* </p>
* <p>
* The resulting tints might be cached for this state, level and position, while the
* given position and model are rendered, but may not be stored beyond that time window,
* especially not beyond any given frame being rendered.
* </p>
*
* @param state The state for which the tints are retrieved.
* @param level The level in which they are retrieved.
* @param pos The position inside the level for which they are retrieved.
* @param tintValues The target collection in which to store the tint values for the given index.
Comment thread
marchermans marked this conversation as resolved.
*/
void collect(BlockState state, BlockAndTintGetter level, BlockPos pos, IntList tintValues);
Comment thread
marchermans marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
import java.util.List;
import java.util.Map;

import net.fabricmc.fabric.api.client.rendering.v1.BlockTintsFactory;

import net.minecraft.world.level.block.state.BlockState;

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

import net.minecraft.client.color.block.BlockColors;
Expand All @@ -32,6 +37,8 @@ public final class BlockColorRegistryImpl {
@Nullable
private static Map<Block, List<BlockTintSource>> map = new IdentityHashMap<>();

private static Map<Block, BlockTintsFactory> factories = new IdentityHashMap<>();

public static void initialize(BlockColors blockColors) {
if (BlockColorRegistryImpl.blockColors != null) {
return;
Expand All @@ -52,4 +59,17 @@ public static void register(List<BlockTintSource> layers, Block... blocks) {
}
}
}

public static void register(final BlockTintsFactory factory, final Block[] blocks)
{
for (final Block block : blocks)
{
factories.put(block, factory);
}
}

public static @Nullable BlockTintsFactory factoryFor(final BlockState blockState)
{
return factories.get(blockState.getBlock());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package net.fabricmc.fabric.mixin.client.rendering;

import it.unimi.dsi.fastutil.ints.IntList;

import net.fabricmc.fabric.api.client.rendering.v1.BlockColorRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.BlockTintsFactory;

import net.minecraft.client.color.block.BlockTintSource;
import net.minecraft.client.renderer.block.BlockAndTintGetter;
import net.minecraft.client.renderer.block.ModelBlockRenderer;

import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;

import org.jspecify.annotations.Nullable;
import org.objectweb.asm.Opcodes;
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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.util.List;

@Mixin(ModelBlockRenderer.class)
public abstract class ModelBlockRendererMixin
{

@Shadow
@Final
private IntList computedTintValues;
@Shadow
@Final
private List<@Nullable BlockTintSource> tintSources;

@Inject(
method = "computeTintColor(Lnet/minecraft/client/renderer/block/BlockAndTintGetter;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/core/BlockPos;I)I",
at = @At(
value = "FIELD",
target = "Lnet/minecraft/client/renderer/block/ModelBlockRenderer;tintSourcesInitialized:Z",
opcode = Opcodes.PUTFIELD,
shift = At.Shift.AFTER
)

)
private void injectFactoryTintCacheLoading(
final BlockAndTintGetter level,
final BlockState state,
final BlockPos pos,
final int tintIndex,
final CallbackInfoReturnable<Integer> cir) {
if (this.tintSources.isEmpty()) {
final BlockTintsFactory factory = BlockColorRegistry.factoryFor(state);
if (factory != null) {
factory.collect(state, level, pos, this.computedTintValues);
}

if (!this.computedTintValues.isEmpty()) {
for (int i = 0; i < this.computedTintValues.size(); i++) {
this.tintSources.add(null);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"LevelRendererMixin",
"LivingEntityRendererAccessor",
"LivingEntityRendererMixin",
"ModelBlockRendererMixin",
"ModelLayersAccessor",
"ModelMixin",
"ModelPartAccessor",
Expand Down