Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
03e63f3
feat!: Use ServiceLoader for `Renderer` registry, `RendererReadyEntry…
sylv256 Feb 4, 2026
1260f4b
feat!: Use ServiceLoader for `Renderer` registry, `RendererReadyEntry…
sylv256 Feb 4, 2026
06bca83
feat!: Use `RendererProvider` for service loading
sylv256 Feb 4, 2026
ee27791
Merge remote-tracking branch 'origin/rendering-0' into rendering-0
sylv256 Feb 4, 2026
a236245
refactor: Log whether Indigo is applicable again
sylv256 Feb 4, 2026
b53602f
refactor!: `RendererProvider#getChosenProvider` -> `#get`
sylv256 Feb 4, 2026
80ac9d2
docs(RendererProvider): "if it has not yet been found" -> "…been chosen"
sylv256 Feb 4, 2026
6da3e44
chore: run `spotlessApply`
sylv256 Feb 4, 2026
e15d217
test: add `fabric-renderer-ready` & actually test it
sylv256 Feb 4, 2026
1f7eb43
docs: Warn user about calling `RendererProvider#getRenderer` early
sylv256 Feb 5, 2026
ce336b0
Merge remote-tracking branch 'origin/26.1' into rendering-0
sylv256 Feb 6, 2026
c11e491
refactor!: Mark impl methods in `RendererProvider` `@OverrideOnly`
sylv256 Feb 6, 2026
1b3b8fa
docs(RendererProvider#getRenderer): currently registered -> to be reg…
sylv256 Feb 6, 2026
a9417e2
refactor!: Allow early `Renderer` init, nuking `RendererReadyEntrypoint`
sylv256 Feb 6, 2026
9c32272
fix: crash when `Renderer#get` was called again
sylv256 Feb 6, 2026
67e8380
fix: don't test `Renderer#get` at mod init, do it at client init
sylv256 Feb 6, 2026
adf1e2c
docs: remove incorrect asterisks
sylv256 Feb 6, 2026
5869245
refactor!: Make `RendererProvider` entrypoint instead of using `Servi…
sylv256 Feb 8, 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
3 changes: 2 additions & 1 deletion fabric-renderer-api-v1/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ testDependencies(project, [
':fabric-particles-v1',
':fabric-renderer-indigo',
':fabric-rendering-v1',
':fabric-resource-loader-v1'
':fabric-resource-loader-v1',
':fabric-lifecycle-events-v1'
])

loom {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;

import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.client.renderer.v1.mesh.MutableMesh;
import net.fabricmc.fabric.api.client.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.client.renderer.v1.render.BlockMultiBufferSource;
Expand Down Expand Up @@ -67,22 +68,14 @@ public interface Renderer {
/**
* Access to the current {@link Renderer} for creating and retrieving mesh builders
* and materials.
*
* <p><b>Warning:</b> do not call this method before {@link ModInitializer} has been invoked. Doing
* so will likely crash.
*/
static Renderer get() {
return RendererManager.getRenderer();
}

/**
* Rendering extension mods must implement {@link Renderer} and
* call this method during initialization.
*
* <p>Only one {@link Renderer} plug-in can be active in any game instance.
* If a second mod attempts to register, this method will throw an UnsupportedOperationException.
*/
static void register(Renderer renderer) {
RendererManager.registerRenderer(renderer);
}

/**
* Obtain a new {@link MutableMesh} instance to build optimized meshes and create baked models
* with enhanced features.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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.renderer.v1;

import org.jetbrains.annotations.ApiStatus;

import net.fabricmc.fabric.impl.client.renderer.RendererManager;
import net.fabricmc.loader.api.FabricLoader;

/**
* An abstraction for registering {@link Renderer} implementations.
*
* <p>Before Minecraft is initialized, implementations of {@link RendererProvider} are
* loaded via {@link FabricLoader#getEntrypointContainers(String, Class)}, after which
* {@link #getRenderer()} is called.
*
* @implSpec Renderers are expected to add a {@code fabric-renderer-api-v1:renderer_provider} entrypoint
* referencing their implementations of {@link RendererProvider}.
*/
public interface RendererProvider {
/**
* Gets the mod ID of the current, chosen {@link RendererProvider} or finds one if it has not
* yet been chosen.
*
* @return the mod ID of the current {@link RendererProvider}.
*/
static String getId() {
return RendererManager.getOrLoadRendererProvider().getProvider().getMetadata().getId();
}

/**
* Get or instantiate an implementation of {@link Renderer}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This javadoc should clarify that this method is called only if this provider is selected and that this method is guaranteed to run at the same time as ClientModInitializer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is relevant anymore.

*
* @return an instance of the {@link Renderer} to be registered.
* @implSpec This method should instantiate an implementation of {@link Renderer} the first time
* it is invoked and return that instance for any subsequent calls.
*/
@ApiStatus.OverrideOnly
Renderer getRenderer();

/**
* The higher a renderer's priority is, the more likely it is to be loaded. So, the
* {@link RendererProvider} with the highest priority is loaded.
*
* @return this renderer's priority.
* @implSpec Implementations of {@link Renderer} should use priority {@code 1000} in most cases.
* However, they may choose any priority or even change priorities based on some conditions.
* Implementors should avoid priorities of {@code 0} or below as that is Indigo's priority.
*/
@ApiStatus.OverrideOnly
default int priority() {
return 1000;
}
Comment on lines +56 to +67
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an anti pattern, and something we dont do else where. It always ends up in a mess and people will put max int to force them selves to be first. Im really no keen on this at all. Use the toposort with a before/after mod id.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this in CONTRIBUTING.md? If not I'll probably add it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be a good idea, we have used the toposorting logic in quite a few places now. Let me know if you need help to wire it up.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an example in fapi where it's used?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ParticleGroupRegistryImpl.registerOrdering
or ArrayBackedEvent.addPhaseOrdering
or ModPackResourcesSorter.addLoadOrdering

net.fabricmc.fabric.impl.base.toposort.NodeSorting is what everything uses. Don't spend ages on this if you cannot figure it out, do let me know if you arent making any progress.

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,51 @@

package net.fabricmc.fabric.impl.client.renderer;

import java.util.List;

import net.fabricmc.fabric.api.client.renderer.v1.Renderer;
import net.fabricmc.fabric.api.client.renderer.v1.RendererProvider;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;

public final class RendererManager {
private static EntrypointContainer<RendererProvider> chosenRendererProvider;
private static Renderer activeRenderer;

private RendererManager() {
}

public static Renderer getRenderer() {
if (activeRenderer == null) {
throw new UnsupportedOperationException("Attempted to retrieve active rendering plug-in before one was registered.");
if (activeRenderer != null) {
return activeRenderer;
}

activeRenderer = getOrLoadRendererProvider().getEntrypoint().getRenderer();
return activeRenderer;
}

public static void registerRenderer(Renderer renderer) {
if (renderer == null) {
throw new NullPointerException("Attempted to register a null rendering plug-in. This is not supported.");
public static EntrypointContainer<RendererProvider> getOrLoadRendererProvider() {
if (chosenRendererProvider != null) {
return chosenRendererProvider;
}

if (activeRenderer != null) {
throw new UnsupportedOperationException("Attempted to register a second rendering plug-in. Multiple rendering plug-ins are not supported.");
List<EntrypointContainer<RendererProvider>> entrypoints = FabricLoader.getInstance()
.getEntrypointContainers("fabric-renderer-api-v1:renderer_provider", RendererProvider.class);
int highestPriority = Integer.MIN_VALUE;
EntrypointContainer<RendererProvider> rendererProvider = null;

for (EntrypointContainer<RendererProvider> next : entrypoints) {
if (next.getEntrypoint().priority() > highestPriority) {
rendererProvider = next;
highestPriority = next.getEntrypoint().priority();
}
}

activeRenderer = renderer;
if (rendererProvider != null) {
chosenRendererProvider = rendererProvider;
return rendererProvider;
} else {
throw new NullPointerException("A renderer plug-in has not been provided before Minecraft has loaded. This is unsupported.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.model.loading.v1.CustomUnbakedBlockStateModel;
import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedModelDeserializer;
import net.fabricmc.fabric.api.client.renderer.v1.Renderer;
import net.fabricmc.fabric.api.client.rendering.v1.ChunkSectionLayerMap;
import net.fabricmc.fabric.test.renderer.Registration;
import net.fabricmc.fabric.test.renderer.RendererTest;
Expand All @@ -38,5 +39,11 @@ public void onInitializeClient() {
// We don't specify a material for the frame mesh,
// so it will use the default material, i.e. the one from ChunkSectionLayers.
ChunkSectionLayerMap.putBlock(Registration.FRAME_BLOCK, ChunkSectionLayer.CUTOUT);

try {
Renderer.get(); // Ensure Renderer can be initialized as early as mod init
} catch (Exception e) {
throw new RuntimeException("Renderer failed to initialize", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.renderer.v1.Renderer;
import net.fabricmc.fabric.api.util.TriState;
import net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRenderer;
import net.fabricmc.fabric.impl.client.indigo.renderer.aocalc.AoConfig;
import net.fabricmc.loader.api.FabricLoader;

public class Indigo implements ClientModInitializer {
public class Indigo {
public static final AoConfig AMBIENT_OCCLUSION_MODE;
/** Set true in dev env to confirm results match vanilla when they should. */
public static final boolean DEBUG_COMPARE_LIGHTING;
Expand All @@ -45,7 +42,7 @@ public class Indigo implements ClientModInitializer {
public static final boolean FIX_EXTERIOR_VERTEX_LIGHTING;
public static final boolean FIX_LUMINOUS_AO_SHADE;

private static final Logger LOGGER = LoggerFactory.getLogger(Indigo.class);
static final Logger LOGGER = LoggerFactory.getLogger(Indigo.class);
/** If set the default config file will be generated on startup, restoring pre 26.1 behavior. */
private static final boolean GENERATE_CONFIG_FILE = System.getProperty("fabric.indigo.generateConfigFile") != null;

Expand Down Expand Up @@ -93,12 +90,12 @@ private static TriState asTriState(@Nullable String property) {
}
}

AMBIENT_OCCLUSION_MODE = asEnum((String) properties.computeIfAbsent("ambient-occlusion-mode", (a) -> "hybrid"), AoConfig.HYBRID);
DEBUG_COMPARE_LIGHTING = asBoolean((String) properties.computeIfAbsent("debug-compare-lighting", (a) -> "auto"), false);
FIX_SMOOTH_LIGHTING_OFFSET = asBoolean((String) properties.computeIfAbsent("fix-smooth-lighting-offset", (a) -> "auto"), true);
boolean fixMeanLightCalculation = asBoolean((String) properties.computeIfAbsent("fix-mean-light-calculation", (a) -> "auto"), true);
FIX_EXTERIOR_VERTEX_LIGHTING = asBoolean((String) properties.computeIfAbsent("fix-exterior-vertex-lighting", (a) -> "auto"), true);
FIX_LUMINOUS_AO_SHADE = asBoolean((String) properties.computeIfAbsent("fix-luminous-block-ambient-occlusion", (a) -> "auto"), false);
AMBIENT_OCCLUSION_MODE = asEnum((String) properties.computeIfAbsent("ambient-occlusion-mode", (_) -> "hybrid"), AoConfig.HYBRID);
DEBUG_COMPARE_LIGHTING = asBoolean((String) properties.computeIfAbsent("debug-compare-lighting", (_) -> "auto"), false);
FIX_SMOOTH_LIGHTING_OFFSET = asBoolean((String) properties.computeIfAbsent("fix-smooth-lighting-offset", (_) -> "auto"), true);
boolean fixMeanLightCalculation = asBoolean((String) properties.computeIfAbsent("fix-mean-light-calculation", (_) -> "auto"), true);
FIX_EXTERIOR_VERTEX_LIGHTING = asBoolean((String) properties.computeIfAbsent("fix-exterior-vertex-lighting", (_) -> "auto"), true);
FIX_LUMINOUS_AO_SHADE = asBoolean((String) properties.computeIfAbsent("fix-luminous-block-ambient-occlusion", (_) -> "auto"), false);

if (fixMeanLightCalculation && !FIX_SMOOTH_LIGHTING_OFFSET) {
fixMeanLightCalculation = false;
Expand All @@ -123,14 +120,4 @@ private static TriState asTriState(@Nullable String property) {
}
}
}

@Override
public void onInitializeClient() {
if (IndigoMixinConfigPlugin.shouldApplyIndigo()) {
LOGGER.info("[Indigo] Registering Indigo renderer!");
Renderer.register(IndigoRenderer.INSTANCE);
} else {
LOGGER.info("[Indigo] Different rendering plugin detected; not applying Indigo.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,25 @@
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;

import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.fabricmc.fabric.api.client.renderer.v1.RendererProvider;

public class IndigoMixinConfigPlugin implements IMixinConfigPlugin {
/** Set by other renderers to disable loading of Indigo. */
private static final String JSON_KEY_DISABLE_INDIGO = "fabric-renderer-api-v1:contains_renderer";

private static boolean needsLoad = true;

private static boolean indigoApplicable = true;

private static void loadIfNeeded() {
public static boolean shouldApplyIndigo() {
if (needsLoad) {
for (ModContainer container : FabricLoader.getInstance().getAllMods()) {
final ModMetadata meta = container.getMetadata();
indigoApplicable = RendererProvider.getId().equals("fabric-renderer-indigo");

if (meta.containsCustomValue(JSON_KEY_DISABLE_INDIGO)) {
indigoApplicable = false;
}
if (indigoApplicable) {
Indigo.LOGGER.info("[Indigo] Applying Indigo renderer!");
} else {
Indigo.LOGGER.info("[Indigo] Different rendering plugin detected; not applying Indigo.");
}

needsLoad = false;
}
}

static boolean shouldApplyIndigo() {
loadIfNeeded();
return indigoApplicable;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,18 @@
* The Fabric default renderer implementation. Supports all features defined in the API.
*/
public class IndigoRenderer implements Renderer {
public static final IndigoRenderer INSTANCE = new IndigoRenderer();
private static IndigoRenderer instance;

private IndigoRenderer() { }
private IndigoRenderer() {
}

static IndigoRenderer getOrCreateInstance() {
if (instance == null) {
instance = new IndigoRenderer();
}

return instance;
}

@Override
public MutableMesh mutableMesh() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.indigo.renderer;

import net.fabricmc.fabric.api.client.renderer.v1.Renderer;
import net.fabricmc.fabric.api.client.renderer.v1.RendererProvider;

public class IndigoRendererProvider implements RendererProvider {
@Override
public Renderer getRenderer() {
return IndigoRenderer.getOrCreateInstance();
}

@Override
public int priority() {
return 0; // This ensures other renderers override Indigo
}
}
12 changes: 6 additions & 6 deletions fabric-renderer-indigo/src/client/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
"mixins": [
"fabric-renderer-indigo.mixins.json"
],
"entrypoints": {
"client": [
"net.fabricmc.fabric.impl.client.indigo.Indigo"
]
},
"custom": {
"fabric-api:module-lifecycle": "stable"
},
"accessWidener" : "fabric-renderer-indigo.classtweaker"
"accessWidener" : "fabric-renderer-indigo.classtweaker",
"entrypoints": {
"fabric-renderer-api-v1:renderer_provider": [
"net.fabricmc.fabric.impl.client.indigo.renderer.IndigoRendererProvider"
]
}
}
Loading