Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
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;

Check failure on line 29 in fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AltModelBlockRendererImpl.java

View workflow job for this annotation

GitHub Actions / build

Wrong order for 'org.joml.Vector3f' import.
import org.jspecify.annotations.Nullable;

import net.minecraft.client.color.block.BlockColors;
Expand Down Expand Up @@ -184,7 +188,9 @@
}
}

private void configureTintCache(final BlockState blockState) {
private void configureTintCache(final BlockState blockState,
final BlockAndTintGetter level,
final BlockPos pos) {
List<BlockTintSource> tintSources = blockColors.getTintSources(blockState);
int tintSourceCount = tintSources.size();

Expand All @@ -194,12 +200,23 @@
for (int i = 0; i < tintSourceCount; ++i) {
computedTintValues.add(-1);
}
} else {
final BlockTintsFactory factory = BlockColorRegistry.getFactory(blockState);

Check failure on line 204 in fabric-renderer-indigo/src/client/java/net/fabricmc/fabric/impl/client/indigo/renderer/render/AltModelBlockRendererImpl.java

View workflow job for this annotation

GitHub Actions / build

missing blank line before block at same indentation level
if (factory != null) {
factory.collect(blockState, level, pos, computedTintValues);
}

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

private int computeTintColor(final BlockAndTintGetter level, final BlockState state, final BlockPos pos, final int tintIndex) {
if (!tintSourcesInitialized) {
configureTintCache(state);
configureTintCache(state, level, pos);
tintSourcesInitialized = true;
}

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 getFactory(BlockState blockState) {
return BlockColorRegistryImpl.getFactory(blockState);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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>
* <p>
* The given tint list is guaranteed to be empty.
* It is recommended to call the {@link IntList#size(int) size} method if you at the start of the method, ahead of time, how many
* tints your system will eventually register as this will pre-allocate enough memory to hold your ints.
* </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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
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.Nullable;

import net.minecraft.client.color.block.BlockColors;
Expand All @@ -32,6 +36,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 @@ -44,6 +50,15 @@ public static void initialize(BlockColors blockColors) {
}

public static void register(List<BlockTintSource> layers, Block... blocks) {
for (final Block block : blocks)
{
if (factories.containsKey(block)) {
throw new IllegalStateException("A dynamic block color factory for the block %s has already been registered and as such no static usage is allowed!".formatted(
block
));
}
}

if (blockColors != null) {
blockColors.register(layers, blocks);
} else {
Expand All @@ -52,4 +67,28 @@ public static void register(List<BlockTintSource> layers, Block... blocks) {
}
}
}

public static void register(final BlockTintsFactory factory, final Block[] blocks)
{
for (final Block block : blocks)
{
if (map != null && map.containsKey(block)) {
throw new IllegalStateException(
"A static block color provider for the block: %s has already been registered and as such no dynamic usage is allowed!".formatted(
block));
}
if (blockColors != null && !blockColors.getTintSources(block.defaultBlockState()).isEmpty()) {
throw new IllegalStateException(
"A static block color provider for the block: %s has already been registered and as such no dynamic usage is allowed!".formatted(
block));
}

factories.put(block, factory);
}
}

public static @Nullable BlockTintsFactory getFactory(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.getFactory(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
Loading