Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
feb41ee
initial commit for render state data extraction pull request
Ramixin Mar 1, 2026
dab345e
Fixed EntityRendererMixin being a common mixin rather than client
Ramixin Mar 1, 2026
a66635a
Added game test for RenderStateDataExtractionRegistry
Ramixin Mar 1, 2026
b61fef4
26.1-snapshot-11 (#5240)
modmuss50 Mar 3, 2026
9451711
Bump version
modmuss50 Mar 3, 2026
1bd9c6c
Bump version
modmuss50 Mar 3, 2026
17d6d79
Fix opaque water by not overriding existing ChunkSectionLayer for flu…
modmuss50 Mar 5, 2026
b24f50f
Bump version
modmuss50 Mar 5, 2026
1e4130d
Pivoted to entity renderers
Ramixin Mar 8, 2026
5f74a07
Pivoted to events instead of registration (proof of concept)
Ramixin Mar 8, 2026
aac8b1d
Cleaned up
Ramixin Mar 8, 2026
fa98c24
House cleaning
Ramixin Mar 8, 2026
273e35e
initial commit for render state data extraction pull request
Ramixin Mar 1, 2026
01cf6f4
Fixed EntityRendererMixin being a common mixin rather than client
Ramixin Mar 1, 2026
f34be3f
Added game test for RenderStateDataExtractionRegistry
Ramixin Mar 1, 2026
e55b6c5
Pivoted to entity renderers
Ramixin Mar 8, 2026
6aafd40
Pivoted to events instead of registration (proof of concept)
Ramixin Mar 8, 2026
13b1974
Fixed conflict
Ramixin Mar 9, 2026
2cb10e4
House cleaning
Ramixin Mar 8, 2026
fb27347
Merge remote-tracking branch 'origin/renderStateDataExtraction' into …
Ramixin Mar 9, 2026
bd3d302
Fixed silent conflict
Ramixin Mar 9, 2026
14b21be
Add global data attachments (#5231)
DennisOchulor Mar 10, 2026
3042174
Merge remote-tracking branch 'origin/renderStateDataExtraction' into …
Ramixin Mar 10, 2026
ffb05a0
attempt to remedy merge blunder
Ramixin Mar 11, 2026
259635a
removed leftover classes from cleanup
Ramixin Mar 11, 2026
caf9215
added back changes to classes that were undone
Ramixin Mar 11, 2026
1612e9f
ran spotless
Ramixin Mar 11, 2026
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
@@ -0,0 +1,23 @@
/*
* 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;

public interface FabricEntityRenderer {
default void addExtractor(RenderStateDataExtractor extractor) {
throw new UnsupportedOperationException("Implemented via mixin");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.world.entity.Entity;

public abstract class RenderStateDataExtractor {

Check failure on line 23 in fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/RenderStateDataExtractor.java

View workflow job for this annotation

GitHub Actions / build

blank line after {

public RenderStateDataExtractor(EntityRendererProvider.Context context) {

Check failure on line 25 in fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/RenderStateDataExtractor.java

View workflow job for this annotation

GitHub Actions / build

blank line before }

Check failure on line 25 in fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/RenderStateDataExtractor.java

View workflow job for this annotation

GitHub Actions / build

blank line after {

}

public abstract void extract(Entity entity, EntityRenderState state);

Check failure on line 29 in fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/RenderStateDataExtractor.java

View workflow job for this annotation

GitHub Actions / build

blank line before }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package net.fabricmc.fabric.api.client.rendering.v1;

import java.util.List;
import java.util.function.Function;

import org.jetbrains.annotations.ApiStatus;

import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.world.entity.EntityType;

import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;

import org.jspecify.annotations.Nullable;

Check failure on line 15 in fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/RenderStateExtractionCallback.java

View workflow job for this annotation

GitHub Actions / build

Wrong order for 'org.jspecify.annotations.Nullable' import.

public interface RenderStateExtractionCallback {

Check failure on line 17 in fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/RenderStateExtractionCallback.java

View workflow job for this annotation

GitHub Actions / build

blank line after {

Event<RenderStateExtractionCallback> EVENT = EventFactory.createArrayBacked(
RenderStateExtractionCallback.class, listeners -> ctx -> {
for (RenderStateExtractionCallback callback : listeners) {
callback.onRenderStateExtraction(ctx);

Check failure on line 22 in fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/RenderStateExtractionCallback.java

View workflow job for this annotation

GitHub Actions / build

missing blank line before block at same indentation level
for (Function<EntityRendererProvider.Context, RenderStateDataExtractor> factory : ctx.factories()) {
ctx.renderer().addExtractor(factory.apply(ctx.rendererContext()));
}
}
});

void onRenderStateExtraction(RenderStateExtractionCallback.Context ctx);

@ApiStatus.NonExtendable
interface Context {

Check failure on line 32 in fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/RenderStateExtractionCallback.java

View workflow job for this annotation

GitHub Actions / build

blank line after {

@Nullable EntityType<?> type();

EntityRenderer<?, ?> renderer();

EntityRendererProvider.Context rendererContext();

void add(Function<EntityRendererProvider.Context, RenderStateDataExtractor> extractorFactory);

List<Function<EntityRendererProvider.Context, RenderStateDataExtractor>> factories();

Check failure on line 42 in fabric-rendering-v1/src/client/java/net/fabricmc/fabric/api/client/rendering/v1/RenderStateExtractionCallback.java

View workflow job for this annotation

GitHub Actions / build

blank line before }

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.fabricmc.fabric.impl.client.rendering;

import java.util.ArrayList;
import java.util.function.Function;

import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.world.entity.EntityType;

import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataExtractor;
import net.fabricmc.fabric.api.client.rendering.v1.RenderStateExtractionCallback;

import org.jspecify.annotations.Nullable;

Check failure on line 13 in fabric-rendering-v1/src/client/java/net/fabricmc/fabric/impl/client/rendering/RenderStateExtractionCallbackContextImpl.java

View workflow job for this annotation

GitHub Actions / build

Wrong order for 'org.jspecify.annotations.Nullable' import.

public record RenderStateExtractionCallbackContextImpl(@Nullable EntityType<?> type, EntityRenderer<?, ?> renderer, EntityRendererProvider.Context rendererContext, ArrayList<Function<EntityRendererProvider.Context, RenderStateDataExtractor>> factories) implements RenderStateExtractionCallback.Context {
@Override
public void add(Function<EntityRendererProvider.Context, RenderStateDataExtractor> extractorFactory) {
factories.add(extractorFactory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 java.util.ArrayList;
import java.util.List;

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.callback.CallbackInfo;

import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.world.entity.Entity;

import net.fabricmc.fabric.api.client.rendering.v1.FabricEntityRenderer;
import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataExtractor;

@Mixin(EntityRenderer.class)
public abstract class EntityRendererMixin<T extends Entity, S extends EntityRenderState> implements FabricEntityRenderer {
@Unique
private final List<RenderStateDataExtractor> renderStateExtractors = new ArrayList<>();

@Inject(method = "extractRenderState", at = @At("TAIL"))
private void runRenderStateExtractors(T entity, S state, float partialTicks, CallbackInfo ci) {
for (RenderStateDataExtractor extractor : renderStateExtractors) {
extractor.extract(entity, state);
}
}

@Override
public void addExtractor(RenderStateDataExtractor extractor) {
renderStateExtractors.add(extractor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@

package net.fabricmc.fabric.mixin.client.rendering;

import java.util.ArrayList;
import java.util.Map;

import com.google.common.collect.ImmutableMap;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
Expand All @@ -34,12 +36,15 @@
import net.minecraft.client.renderer.entity.EntityRenderers;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.player.AvatarRenderer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;

import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityRenderLayerRegistrationCallback;
import net.fabricmc.fabric.api.client.rendering.v1.RenderStateExtractionCallback;
import net.fabricmc.fabric.impl.client.rendering.EntityRendererRegistryImpl;
import net.fabricmc.fabric.impl.client.rendering.RegistrationHelperImpl;
import net.fabricmc.fabric.impl.client.rendering.RenderStateExtractionCallbackContextImpl;

@Mixin(EntityRenderers.class)
public abstract class EntityRenderersMixin {
Expand Down Expand Up @@ -77,4 +82,20 @@ private static AvatarRenderer createAvatarRenderer(EntityRendererProvider.Contex

return entityRenderer;
}

@WrapOperation(method = "lambda$createEntityRenderers$0", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/EntityRendererProvider;create(Lnet/minecraft/client/renderer/entity/EntityRendererProvider$Context;)Lnet/minecraft/client/renderer/entity/EntityRenderer;"))
private static <T extends Entity> EntityRenderer<T, ?> applyExtractorsToRenderer(EntityRendererProvider<T> instance, EntityRendererProvider.Context context, Operation<EntityRenderer<T, ?>> original, @Local(argsOnly = true) EntityType<T> entityType) {
EntityRenderer<T, ?> entityRenderer = original.call(instance, context);
RenderStateExtractionCallbackContextImpl ctx = new RenderStateExtractionCallbackContextImpl(entityType, entityRenderer, context, new ArrayList<>());
RenderStateExtractionCallback.EVENT.invoker().onRenderStateExtraction(ctx);
return entityRenderer;
}

@WrapOperation(method = "createAvatarRenderers", at = @At(value = "NEW", target = "(Lnet/minecraft/client/renderer/entity/EntityRendererProvider$Context;Z)Lnet/minecraft/client/renderer/entity/player/AvatarRenderer;"))
private static AvatarRenderer<?> applyExtractorsToAvatarRenderer(EntityRendererProvider.Context context, boolean slimSteve, Operation<AvatarRenderer<?>> original) {
AvatarRenderer<?> entityRenderer = original.call(context, slimSteve);
RenderStateExtractionCallbackContextImpl ctx = new RenderStateExtractionCallbackContextImpl(null, entityRenderer, context, new ArrayList<>());
RenderStateExtractionCallback.EVENT.invoker().onRenderStateExtraction(ctx);
return entityRenderer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ transitive-inject-interface com/mojang/blaze3d/pipeline/RenderPipeline$Snippet n
transitive-inject-interface com/mojang/blaze3d/pipeline/RenderPipeline$Builder net/fabricmc/fabric/api/client/rendering/v1/FabricRenderPipeline$Builder
transitive-inject-interface net/minecraft/client/model/Model net/fabricmc/fabric/api/client/rendering/v1/FabricModel<TS;>
transitive-inject-interface net/minecraft/client/renderer/entity/state/EntityRenderState net/fabricmc/fabric/api/client/rendering/v1/FabricRenderState
transitive-inject-interface net/minecraft/client/renderer/entity/EntityRenderer net/fabricmc/fabric/api/client/rendering/v1/FabricEntityRenderer
transitive-inject-interface net/minecraft/client/renderer/state/MapRenderState net/fabricmc/fabric/api/client/rendering/v1/FabricRenderState
transitive-inject-interface net/minecraft/client/renderer/state/MapRenderState$MapDecorationRenderState net/fabricmc/fabric/api/client/rendering/v1/FabricRenderState
transitive-inject-interface net/minecraft/client/renderer/item/ItemStackRenderState net/fabricmc/fabric/api/client/rendering/v1/FabricRenderState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"DebugOptionsScreenOptionListMixin",
"DrawAccessor",
"EntityRenderDispatcherMixin",
"EntityRendererMixin",
"EntityRenderersMixin",
"GameRendererMixin",
"GuiAccessor",
Expand Down
3 changes: 2 additions & 1 deletion fabric-rendering-v1/src/testmod/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"net.fabricmc.fabric.test.rendering.client.LevelRenderEventsTests",
"net.fabricmc.fabric.test.rendering.client.gui.PictureInPictureRendererTest",
"net.fabricmc.fabric.test.rendering.client.gui.PictureInPictureRendererTestWithNewGuiRenderer",
"net.fabricmc.fabric.test.rendering.client.gui.GuiRendererNonQuadsTest"
"net.fabricmc.fabric.test.rendering.client.gui.GuiRendererNonQuadsTest",
"net.fabricmc.fabric.test.rendering.client.RenderStateDataExtractionTest"
],
"fabric-client-gametest": [
"net.fabricmc.fabric.test.rendering.client.HudTests",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* 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.vertex.PoseStack;

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

import net.fabricmc.fabric.api.client.rendering.v1.ModelLayerRegistry;
import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataExtractor;
import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataKey;

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

import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.world.entity.Entity;

import net.minecraft.world.entity.LivingEntity;

import org.jspecify.annotations.NonNull;

import net.minecraft.client.model.Model;
import net.minecraft.client.model.geom.EntityModelSet;
import net.minecraft.client.model.geom.ModelLayerLocation;
import net.minecraft.client.model.geom.ModelPart;
import net.minecraft.client.model.geom.PartPose;
import net.minecraft.client.model.geom.builders.CubeDeformation;
import net.minecraft.client.model.geom.builders.CubeListBuilder;
import net.minecraft.client.model.geom.builders.LayerDefinition;
import net.minecraft.client.model.geom.builders.MeshDefinition;
import net.minecraft.client.model.geom.builders.PartDefinition;
import net.minecraft.client.model.player.PlayerModel;
import net.minecraft.client.renderer.SubmitNodeCollector;
import net.minecraft.client.renderer.entity.LivingEntityRenderer;
import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.client.renderer.entity.player.AvatarRenderer;
import net.minecraft.client.renderer.entity.state.AvatarRenderState;
import net.minecraft.client.renderer.rendertype.RenderTypes;
import net.minecraft.resources.Identifier;

import net.fabricmc.api.ClientModInitializer;

/**
* Renders a small red cube in front of the player's chest when in f5. The cube's
* transparency is determined by the player's health percentage.
*
* <p>
* Tests:
* <ul>
* <li>{@link RenderStateExtractionCallback}</li>
* </ul>
*
* <p>
* Assumes the following work as intended:
* <ul>
* <li>{@link ModelLayerRegistry}</li>
* <li>{@link RenderStateDataKey}</li>
* <li>{@link LivingEntityRenderLayerRegistrationCallback}</li>
* </ul>
*/
public class RenderStateDataExtractionTest implements ClientModInitializer {
public static final ModelLayerLocation TEST_MODEL_LOCATION = new ModelLayerLocation(Identifier.fromNamespaceAndPath("fabric", "test_rse_model"), "test_rse_model");

public static final RenderStateDataKey<Float> PLAYER_HEALTH_PERCENTAGE = RenderStateDataKey.create(() -> "Test RSE: Player Health Percentage");

@Override
public void onInitializeClient() {
ModelLayerRegistry.registerModelLayer(TEST_MODEL_LOCATION, TestModel::createLayer);

LivingEntityRenderLayerRegistrationCallback.EVENT.register((_, entityRenderer, registrationHelper, context) -> {
if (entityRenderer instanceof AvatarRenderer<?> avatarRenderer) {
registrationHelper.register(new TestRenderLayer(avatarRenderer, context.getModelSet()));
}
});

RenderStateExtractionCallback.EVENT.register(context -> {
if (context.renderer() instanceof AvatarRenderer<?>) {
context.add(TestExtractor::new);
}
});
}

public static class TestRenderLayer extends RenderLayer<AvatarRenderState, PlayerModel> {
private final TestModel model;

public TestRenderLayer(RenderLayerParent<AvatarRenderState, PlayerModel> renderer, final EntityModelSet modelSet) {
super(renderer);
this.model = new TestModel(modelSet.bakeLayer(TEST_MODEL_LOCATION));
}

@Override
public void submit(@NonNull PoseStack poseStack, @NonNull SubmitNodeCollector submitNodeCollector, int lightCoords, AvatarRenderState state, float yRot, float xRot) {
int overlayCoords = LivingEntityRenderer.getOverlayCoords(state, 0.0F);
float healthPercentage = state.getDataOrDefault(PLAYER_HEALTH_PERCENTAGE, 0f);
int tint = (int) (healthPercentage * 255) << 24 | 0x00FF0000;
submitNodeCollector.submitModel(model, state, poseStack, RenderTypes.entityTranslucent(Identifier.fromNamespaceAndPath("minecraft", "textures/block/diamond_block.png")), lightCoords, overlayCoords, tint, null, state.outlineColor, null);
}
}

public static class TestModel extends Model<AvatarRenderState> {
public TestModel(ModelPart root) {
super(root, RenderTypes::entityTranslucent);
}

public static LayerDefinition createLayer() {
MeshDefinition mesh = PlayerModel.createMesh(CubeDeformation.NONE, false);
PartDefinition root = mesh.getRoot().clearRecursively();
PartDefinition head = root.getChild("body");

CubeListBuilder cube = CubeListBuilder.create().addBox(-4, 2, -12, 8, 8, 8);
head.addOrReplaceChild("fabric:test_rse_model", cube, PartPose.ZERO);
return LayerDefinition.create(mesh, 16, 16);
}
}

public static class TestExtractor extends RenderStateDataExtractor {

public TestExtractor(EntityRendererProvider.Context context) {
super(context);
}

@Override
public void extract(Entity entity, EntityRenderState state) {
LivingEntity livingEntity = (LivingEntity) entity;
state.setData(PLAYER_HEALTH_PERCENTAGE, livingEntity.getHealth() / livingEntity.getMaxHealth());
}
}
}
Loading