-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
BlockRenderContext requires RenderType #284
Comments
We can remove this parameter for 1.20.x on Fabric as it's not used and we do intend to keep binary compatibility with Sodium 0.5.8 as closely as possible. However, it will probably get added back in a future version of MC to keep the Forge & Fabric branches more in sync. That said, as mentioned on the developer page we prefer that people don't mixin to add custom functionality, as it means we can never change any of these functions without breaking some mod. Can you explain in more detail what you are trying to accomplish? |
Rendering Snow Layers inside of other blocks. |
If there's a way to maintain Sodium's structure while also supporting Embeddium's structure, that would be preferable. |
Quick update, Treetrain1 found an API you guys have! |
We're going to fix the method signature to match Sodium's regardless of my comment about mixins, as it's a valid parity issue, and we intend to maintain binary compatibility with mods targeting Sodium 0.5.8 where possible. (This of course won't apply to Sodium 0.6+.)
I assume this is what Treetrain1 found but our |
Alright, thank you! |
Is this helpful? I realized the API is not too intuitive without having already studied the vanilla meshing code in detail 😅, so I'll probably add a similar example somewhere in the codebase for other modders. public static void onChunkMesh(ChunkMeshEvent event) {
LevelChunk chunk = event.getWorld().getChunk(event.getSectionOrigin().x(), event.getSectionOrigin().z());
LevelChunkSection section = chunk.getSection(chunk.getSectionIndexFromSectionY(event.getSectionOrigin().y()));
// Never schedule a mesh appender for empty sections (unless you need to render geometry in them).
// Embeddium normally greatly speeds up initial world loads by not scheduling a rebuild task for an empty
// section (since there is nothing to build), but the task is still created if you register a mesh appender
// because mods may wish to render custom geometry in otherwise empty sections.
// Since we only want to render a snowlogged effect on actual blocks, we don't need to bother with empty
// sections.
if(section != null && !section.hasOnlyAir()) {
BlockState snowState = Blocks.SNOW.defaultBlockState().setValue(SnowLayerBlock.LAYERS, 7);
event.addMeshAppender(ctx -> {
// This part of the code runs on the meshing thread, not the main thread
BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
var vertexConsumer = ctx.vertexConsumerProvider().apply(RenderType.solid());
PoseStack stack = new PoseStack();
RandomSource random = RandomSource.create();
// Even though we have a snapshot of the world, we still use the same coordinates
int baseX = ctx.sectionOrigin().minBlockX();
int baseY = ctx.sectionOrigin().minBlockY();
int baseZ = ctx.sectionOrigin().minBlockZ();
for(int y = 0; y < 16; y++) {
for(int z = 0; z < 16; z++) {
for(int x = 0; x < 16; x++) {
cursor.set(baseX + x, baseY + y, baseZ + z);
// Access blocks through the context's getter, not the section itself, as the latter
// is not safe to use on a meshing thread
BlockState selfState = ctx.blockRenderView().getBlockState(cursor);
if(!selfState.isAir()) {
// Check if the block underneath is netherite block
cursor.move(0, -1, 0);
if(ctx.blockRenderView().getBlockState(cursor).is(Blocks.NETHERITE_BLOCK)) {
// Snowlog the original block
cursor.move(0, 1, 0);
stack.pushPose();
stack.translate(x, y, z);
// FIXME horrible hack to hide z-fighting of the sides of the snow
float scaleFactor = 0.99f;
float translateFactor = (1 - scaleFactor) / 2;
stack.translate(translateFactor, translateFactor, translateFactor);
stack.scale(scaleFactor, scaleFactor, scaleFactor);
// Render the snow blockstate at the given position
Minecraft.getInstance().getBlockRenderer().renderBatched(snowState, cursor, ctx.blockRenderView(), stack, vertexConsumer, true, random);
// Reset the PoseStack for the next use
stack.popPose();
}
}
}
}
}
});
}
} |
I’m not entirely sure to be honest |
In any case once I get to my PC, how should I use this? |
The idea of the API is that you do your extra rendering using vanilla functions, not Embeddium functions (as the latter are intended for internal use). We really don't want mods needing to do much special-casing for Embeddium, beyond some safer equivalent of mixing into the chunk renderer. For instance, in the example above I call the vanilla block model renderer to add the snow layer. Aside from the use of the API hook to get the vertex consumer for the chunk section, there's nothing Embeddium-specific about that rendering logic - it uses existing vanilla infrastructure. This example doesn't use the
You can register an event handler with |
For the record, I am just about to release 0.3.18 with the method signature change (writing the changelog as we speak), so your existing Sodium mixin will probably work anyway. The API is intended as our long-term solution, as we're not able to retain compatibility with Sodium after they release 0.6, and we're trying to wean mods off of using mixins where we can. In short - if you don't want to look into this right now, it may not be required in the short term. |
Ah, that makes sense. I’ll try to add that in! Thank you! |
I'll try to keep this concise to minimize potential drama from the answer and also to be respectful of your time, as the full answer would be rather long. 😆 We (Nolij and myself, the current two maintainers of Embeddium) have had some disagreements with CaffeineMC in the past over various concerns we had with Sodium's project management (such as breaking changes which were not strictly necessary and were very likely to break mods). The main problem at this point, though, is that as of Sodium 0.6, the license was changed to a new, more restrictive one that is not open-source (it explicitly prevents competing forks from being published using the code). Since we obviously compete with Sodium, we can't pull anything from newer versions. We personally do not want to use versions of Sodium under that new license, and we still think our approach to mod compatibility is ultimately more stable for modders, so we're continuing to maintain Embeddium as an independent project, under the original open-source license. For now, we're trying our best to retain compatibility with anything that depended on Sodium 0.5.8 (hence the method signature revert). In the future, we'll inevitably diverge since the projects will go in different directions. |
Just curious how anyone’s expected to maintain compat with Sodium if they’re not open source 😭 |
Everyone else can read the code (and even incorporate portions of it) as long as they don't write a mod deemed to be "competing" under the license. The license's terms are targeted directly at competing projects like ours, not any arbitrary software project. For reference the terms of the license can be found here; it doesn't really give a strict definition of what constitutes competing, so I will not comment on that myself either. |
Ah |
Oh, really quick, by the way! |
I may be missing something but at first glance it looks like you could use |
FrozenBlock/WilderWild@b7444fd |
Hadn't thought of that. |
Dang, the wind idea worked too! Thank you so much!!! |
That mixin actually doesn't seem to function with Embeddium in its current state, not sure why. Anyway, from trying it in vanilla, it seems you just want to override the texture/color that's used for fluid rendering; nothing else gets changed. Since the mesoglea block can only ever contains water fluid states, you should be able to just register an override for the water fluid handler using Fabric's fluid API and conditionally return different textures. (This should work with Sodium/Embeddium out of the box.) Here is an example, which makes fluids rendered on top of netherite blocks use the diamond block texture. It should be easy to adjust this to detect the block at the given position being mesoglea. The reason for doing it "weirdly" with the resource listener is to make it properly compose with other mods that also try to wrap water rendering. To be completely transparent, I'm not 100% sure if this is the proper way to wrap water rendering, but the Fabric Discord should be able to confirm if you have questions. The documentation does suggest that wrapping vanilla fluids is an intended use case. ResourceManagerHelper.get(PackType.CLIENT_RESOURCES).registerReloadListener(new SimpleSynchronousResourceReloadListener() {
@Override
public ResourceLocation getFabricId() {
return new ResourceLocation("embeddium", "fluid_example");
}
FluidRenderHandler customWaterHandler;
@Override
public void onResourceManagerReload(ResourceManager resourceManager) {
if(customWaterHandler == null) {
FluidRenderHandler originalHandler = FluidRenderHandlerRegistry.INSTANCE.get(Fluids.WATER);
// Assert that the original handler exists now, otherwise the crash will happen later inside our
// handler
Objects.requireNonNull(originalHandler);
customWaterHandler = new FluidRenderHandler() {
final TextureAtlasSprite[] diamondWaterSprites = new TextureAtlasSprite[3];
private boolean isSingleTexture(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos) {
return view != null && pos != null && view.getBlockState(pos.below()).is(Blocks.NETHERITE_BLOCK);
}
@Override
public TextureAtlasSprite[] getFluidSprites(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos, FluidState state) {
if(isSingleTexture(view, pos)) {
return diamondWaterSprites;
} else {
return originalHandler.getFluidSprites(view, pos, state);
}
}
@Override
public void reloadTextures(TextureAtlas textureAtlas) {
originalHandler.reloadTextures(textureAtlas);
var diamondBlock = textureAtlas.getSprite(new ResourceLocation("block/diamond_block"));
diamondWaterSprites[0] = diamondBlock; // still
diamondWaterSprites[1] = diamondBlock; // flowing
diamondWaterSprites[2] = diamondBlock; // overlay
}
// Delegate all other methods to the original
@Override
public void renderFluid(BlockPos pos, BlockAndTintGetter world, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) {
originalHandler.renderFluid(pos, world, vertexConsumer, blockState, fluidState);
}
@Override
public int getFluidColor(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos, FluidState state) {
return isSingleTexture(view, pos) ? 0xFFFFFFFF : originalHandler.getFluidColor(view, pos, state);
}
};
FluidRenderHandlerRegistry.INSTANCE.register(Fluids.WATER, Fluids.FLOWING_WATER, customWaterHandler);
}
// Load textures for our handler
customWaterHandler.reloadTextures(Minecraft.getInstance().getModelManager().getAtlas(TextureAtlas.LOCATION_BLOCKS));
}
}); |
Thank you very much!! |
Let me check 1.20.6, I tested that example on 1.20.1 and Fabric has changed how fluid handler registration is done internally since then. |
This revised example works on 1.20.6. FluidRenderHandler originalHandler = FluidRenderHandlerRegistry.INSTANCE.get(Fluids.WATER);
// Assert that the original handler exists now, otherwise the crash will happen later inside our
// handler
Objects.requireNonNull(originalHandler);
var customWaterHandler = new FluidRenderHandler() {
final TextureAtlasSprite[] diamondWaterSprites = new TextureAtlasSprite[3];
private boolean isSingleTexture(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos) {
return view != null && pos != null && view.getBlockState(pos.below()).is(Blocks.NETHERITE_BLOCK);
}
@Override
public TextureAtlasSprite[] getFluidSprites(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos, FluidState state) {
if(isSingleTexture(view, pos)) {
return diamondWaterSprites;
} else {
return originalHandler.getFluidSprites(view, pos, state);
}
}
@Override
public void reloadTextures(TextureAtlas textureAtlas) {
originalHandler.reloadTextures(textureAtlas);
var diamondBlock = textureAtlas.getSprite(new ResourceLocation("block/diamond_block"));
diamondWaterSprites[0] = diamondBlock; // still
diamondWaterSprites[1] = diamondBlock; // flowing
diamondWaterSprites[2] = diamondBlock; // overlay
}
// Delegate all other methods to the original
@Override
public int getFluidColor(@Nullable BlockAndTintGetter view, @Nullable BlockPos pos, FluidState state) {
return isSingleTexture(view, pos) ? 0xFFFFFFFF : originalHandler.getFluidColor(view, pos, state);
}
};
FluidRenderHandlerRegistry.INSTANCE.register(Fluids.WATER, Fluids.FLOWING_WATER, customWaterHandler); |
I would really prefer not to have such an invasive mixin in the fluid renderer, because the mixin will inevitably break if I ever have to refactor it.
To solve that, I think you would either have to make another texture that's scaled the same way as the vanilla water fluid textures (so that it doesn't look zoomed in), or you could just conditionally take over fluid rendering and render it yourself in |
I've updated it for Sodium's from 1.19 through now, but it's understandable!
I think that renderFluid one is my best bet. I made a method for this exact situation in Vanilla for the library WW uses! |
Double check that Sodium actually respects the |
Will do! |
For a very specific use case, possibly so. It’s to make the bottom side of Water render as the solid-colored overlay. |
What triggers that? When I look up at the underside of ocean water with just WW installed I still see the regular still texture. |
I meant the bottom of the block, not the underside of the surface. |
Would you be able to try that scenario without WW |
On that note though, I’m not really sure how to easily do this using renderFluid without pretty much overwriting all water rendering |
Oh, now I see what you mean; the difference is extremely subtle to my eyes though (as the ripple is nowhere near as obvious as it is on the bottom of the glass).
The mixin is easy for you to write, but it's very much not easy for me when players complain about it being broken down the line. 😉 That problem doesn't exist for vanilla mixins because the code is obviously not changing until another Minecraft update, but cross-mod mixins tend to be rather unstable. This would be the kind of niche visual change where if you don't want to have the responsibility of replacing the water renderer using I'll keep thinking and let you know if I do come up with something though. |
I feared it was too niche for that 😭 |
Going to close this as the original issue was fixed in 0.3.18, but feel free to reach out if you need anything else! |
Thank you very much!!! |
Bug Description
Hello!
I am the lead developer of Wilder Wild.
Upon looking at me.jellysquid.mods.sodium.client.render.chunk.compile.pipeline.BlockRenderContext, I found that the class differs from that of Sodium's.
This parity is crucial as I am importing both Embeddium and Sodium to support custom Liquid and Snowlogging rendering, and Snowlogging specifically requires a mixin that calls the update method in this class. However, since they share the same exact file paths, Embeddium overrides Sodium's source, ensuring one of the two mods will be guaranteed to crash with Wilder Wild.
Reproduction Steps
Compare Sodium's and Embeddium's sources.
Log File
latest.log
The text was updated successfully, but these errors were encountered: