Skip to content

Conversation

@amadornes
Copy link
Contributor

@amadornes amadornes commented Dec 28, 2021

Minecraft's render engine supports the creation of custom render types, which allow the user to specify the vertex format and render state (face culling, depth sorting, shaders, etc) for certain parts of their geometry. However, natively these only work on dynamic geometry (entity/blockentity renderers), and not static elements like terrain.

This PR adds the necessary hooks to extend the default set of render types supported for static geometry rendering (solid, cutout_mipped, cutout, translucent, tripwire).

Motivation

The RenderType system is incredibly powerful, but it's currently limited to just dynamic geometry, which requires a considerable amount of CPU time and GPU bandwidth to gather and upload every single frame. This means that any effect involving static geometry that requires a custom render state or shader (which is not all that uncommon) is creating an unnecessary load on both the CPU and GPU.

Implementation

As suggested by @OrionDevelopment, the code from Forge's existing TierSortingRegistry was generified and repurposed to provide a method through which developers can register their own RenderType instances and customize their ordering relative to both Minecraft's built in render types as well as other mods'. The tier sorting registry has been migrated to this generified registry as well.

Due to the way Minecraft schedules its rendering, the render types have been split into three phases: solid, translucent and tripwire. Solid render types are rendered before entities, translucent render types are rendered after them, and tripwire render types are rendered after the dynamic buffers have been drawn. This is reflected in the events fired during client initialization to register custom render types: StaticRenderTypeRegisterEvent.Solid, StaticRenderTypeRegisterEvent.Translucent and StaticRenderTypeRegisterEvent.Tripwire. These events are fired on the mod bus as they are used to register mod-owned render types.

Along with these events, a RenderTypeRegisterEvent has been added for any mod that may need to register custom render types. This event provides a convenient and consistent place for this to take place, and ensures correct initialization of all required resources has taken place by the time it is fired. This event is also fired on the mod bus as it is used to register mod-owned render types.

The render calls for each phase are delegated from LevelRenderer to the LevelRendererAdapter class, which may decide (based on a Forge client config option) to run vanilla's default code path, or visit each of the vanilla + custom render types for that phase in the configured order.

An additional set of interfaces and classes has been created to allow the creation of custom render types which receive a callback before rendering every chunk, allowing developers to compute and upload their own sets of uniforms to their shaders, or otherwise update the render state before the draw call is issued.

Forge's MultiLayerModel has been updated to support user-registered static render types, and the public field listing the old supported render types has been deprecated and marked for removal.

Lastly, a test mod is provided (mod id layer_render_type_test) which adds a block with a custom render type. This render type's shader makes the block's geometry shake, and its color changes depending on the camera's orientation relative to the faces.

Demo

Here is a short demo gif of the block in the test mod as the camera moves around:

gif

Performance and memory implications

The overhead of this system is minimal, with a baseline cost of 3 foreach loops iterating over a pre-computed empty list.

The cost of additional render layers is down to each layer's specific settings. The main factors will be:

  • Memory usage increase of at least RenderType#bufferSize() for every chunk (negligible under vanilla standards of <= 1MB)
  • Added CPU cost of capturing and rendering the additional geometry (may be considerably optimized by deviating from the current way Forge handles render types in baked models, but this would be another PR)
  • Minor CPU hit during startup while the render types are registered and sorted

In conclusion, the actual cost if unused is negligible, but as with everything, it must be used in moderation (at least until the aforementioned changes to baked models are discussed and implemented).

Maintainability

This PR moves the maintenance tasks related to MultiLayerModel (keeping an updated list of vanilla-supported static render types) to the LevelRenderPhaseManager class. MultiLayerModel now relies on this, and as such it does not need to be kept updated anymore.

The mipmap fixes in LevelRenderer have been moved to ForgeRenderTypes.MipmapFixTextureShard and are now applied by replacing vanilla's default mipmap texture render state shard, which is a more idiomatic approach and ensures that the fix is applied to any render types using the mipmapped blocks texture.

Additional maintenance tasks may involve adding/re-ordering hooks in LevelRenderer in the case that vanilla adds new static render types. These changes are unlikely and should not result in any changes to the core API.

Additional notes

During the testing of this pull request, I found that in a Forge dev environment with test mods enabled, reloading resources triggers an invalid GL state which changes the colors of everything in the game and prevents text from rendering. This has been tested with and without this PR's changes, and the effect is the same, so the problem is expected to be in one of the other tests. This has not been tested in an environment without tests because if it works in a mod dev environment (which it does), it should work in an empty Forge dev environment.

If you want, I can open an issue about this.


Special thanks to @OrionDevelopment for all the help, feedback and code provided throughout the development of this PR.
They provided a significant amount of code, but the git authorship has been lost in refactoring and cleanup.

@sciwhiz12 sciwhiz12 added 1.18 Feature This request implements a new feature. Rendering This request deals with rendering. These must be treated with care, and tend to progress slowly. Triage This request requires the active attention of the Triage Team. Requires labelling or reviews. labels Dec 28, 2021
@amadornes amadornes marked this pull request as draft December 28, 2021 12:21
@amadornes
Copy link
Contributor Author

Currently working with Orion on some changes to try to clean up the patches.

@amadornes amadornes force-pushed the feature_custom_static_render_types branch from 5f98643 to fcabe90 Compare December 28, 2021 17:52
@amadornes amadornes marked this pull request as ready for review December 28, 2021 18:25
@amadornes
Copy link
Contributor Author

I have updated the PR description with all the changes Orion and I have made throughout the day.

The patches have been reduced to a minimal footprint and several quality of life improvements have been made, including the addition of a new event for registering custom render types and hooks for uploading custom uniforms to a render type's shader on a per-chunk basis.

@sciwhiz12 sciwhiz12 requested a review from gigaherz December 28, 2021 20:56
Due to having two people working on this, the PR ended up having slightly varying code styles. We tried to minimize this earlier, but upon further inspection, some things still slipped through.
This commit also removes the explicit profiling calls added during render dispatch, as vanilla seems to already do something similar.
Other minor tweaks and cleanups, as well as slightly improved documentation are also included.
@sciwhiz12
Copy link
Contributor

Noting here: @OrionDevelopment made substantial contributions to this PR as noted by the author in the PR description, and therefore cannot triage, review, and assign this PR on grounds of conflict of interest.

@LatvianModder
Copy link
Member

My new mod which uses shaders/RenderTypes in static geometry could really use this PR 😛 https://www.curseforge.com/minecraft/mc-mods/literal-sky-block

Copy link
Contributor

@sciwhiz12 sciwhiz12 left a comment

Choose a reason for hiding this comment

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

While I don't fully comprehend rendering, I've gone ahead and looked at this PR, mostly from formatting and documentation angles. I'll leave the bulk of the actual rendering review to @gigaherz. 👀

Aside from the formatting and whatnot, I do have one notable concern in that the test mod seems to only provide a test for a custom solid render type (as I note in one of my review comments). Ideally, it should provide tests for all three render type 'groups', to help look for possible regressions or oversights now and in the future.
In particular, custom translucent render types need testing because of their special interactions with regards to other render types.


Please update the license headers of the files added or modified by this PR to reflect the current year.

/**
* Replaced by {@link LevelRenderPhaseManager#getAllKnownTypes()}
*/
@Deprecated(forRemoval = true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add since = "1.18.1" to this deprecation annotation.

builder.add(Pair.of(layer.getValue(), deserializationContext.deserialize(GsonHelper.getAsJsonObject(layersObject, layerName), BlockModel.class)));
}
var renderTypes = LevelRenderPhaseManager.getInstance().getAllKnownTypes();
for (var key : layersObject.keySet()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Formatting: braces on newlines:

Suggested change
for (var key : layersObject.keySet()) {
for (var key : layersObject.keySet())
{

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 little off topic but why doesnt this repo have an .editorconfig file?

Copy link
Contributor

Choose a reason for hiding this comment

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

Because style is a weird and wacky place - patches and code have different styles, and no matter what we provide some people will choose to override it with their own preference anyway, which makes the whole thing moot.

@@ -0,0 +1,54 @@
package net.minecraftforge.client.renderer;
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing license header.

Comment on lines +13 to +15
* Create using {@link ComplexRenderTypeBuilder}.
*
* @see ComplexRenderTypeBuilder
Copy link
Contributor

Choose a reason for hiding this comment

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

This should likely include a @see tag for ComplexRenderType#builder().

@@ -0,0 +1,37 @@
package net.minecraftforge.client.renderer;
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing license header.

final double cameraY,
final double cameraZ,
final Matrix4f projectionMatrix
) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Formatting: braces on newlines:

Suggested change
) {
)
{

final boolean sortOnUpload,
final CompositeState compositeState,
final IBeforeChunkRenderCallback beforeChunkRenderCallback
) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Formatting: braces on newlines:

Suggested change
) {
)
{

* Adapter class used to dispatch draw calls for vanilla and user-provided static
* render types during level rendering.
*/
public final class LevelRendererAdapter
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't really an adapter class, but more of a hooks class (in the same vein of ForgeHooksClient).

public net.minecraft.client.particle.ParticleEngine m_107381_(Lnet/minecraft/core/particles/ParticleType;Lnet/minecraft/client/particle/ParticleProvider;)V # register
public net.minecraft.client.particle.ParticleEngine$SpriteParticleRegistration
public net.minecraft.client.renderer.GameRenderer m_109128_(Lnet/minecraft/resources/ResourceLocation;)V # loadEffect
public net.minecraft.client.renderer.LevelRenderer m_172993_(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/PoseStack;DDDLcom/mojang/math/Matrix4f;)V
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to have the mapped name of the method (renderChunkLayer) in a comment after the declaration.

As a thought, is it possible and viable to avoid this AT (and opening up renderChunkLayer to everyone) by passing in a method reference to it to the LevelRendererAdapter hooks?

this.f_109413_.m_83945_(this.f_109461_.m_91385_());
profilerfiller.m_6182_("translucent");
+ if (!net.minecraftforge.client.renderer.LevelRendererAdapter.getInstance().onRenderTranslucent(this, p_109600_, p_109604_, p_109607_)) {
this.m_172993_(RenderType.m_110466_(), p_109600_, d0, d1, d2, p_109607_);
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at the code in renderChunkLayer shows some hardcoded checks for RenderType.translucent(). Do these checks substantially affect render types which render alongside the vanilla translucent render type?

We may want to expand the test mod to include tests for these three render type 'groups', rather than only registering a custom solid render type.


public static List<RenderType> m_110506_() {
- return ImmutableList.of(m_110451_(), m_110457_(), m_110463_(), m_110466_(), m_110503_());
+ return net.minecraftforge.client.renderer.LevelRenderPhaseManager.getInstance().getKnownTypes();
Copy link
Contributor

Choose a reason for hiding this comment

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

In my opinion this is missing an accompanying patch in RenderBuffers: The RenderBuffers#fixedBuffers map which is used to construct the RenderBuffers#bufferSource needs to contain all chunk layers because requesting a buffer from said buffer source for a certain RenderType after using a different RenderType that isn't in the map of fixed buffers will cause the current data in the buffer to be uploaded to the GPU because "non-fixed" RenderTypes use a shared buffer.

While this is not an issue for the static chunk rendering, which uses the ChunkBufferBuilderPack to get its buffers, I would still expect a RenderType, which has a pre-allocated buffer and can be used in static chunk rendering, to be useable in a dynamic context (i.e. EntityRenderer or BlockEntityRenderer) without causing an upload to the GPU every time I switch the RenderType in use to render the next thing in the dynamic renderer (which kills performance when a few instances of the related Entity or BlockEntity are present).

@sciwhiz12 sciwhiz12 added Needs Update This request requires an update from the author to confirm whether the request is relevant. Needs Rebase This PR requires a rebase to implement upstream changes (usually to fix merge conflicts). labels May 26, 2022
@sciwhiz12
Copy link
Contributor

Hello! @amadornes, please leave a comment whether you wish to continue this PR and update it to the latest active version of 1.19.2 (or leave an explanation why this PR's changes are not applicable to 1.19.2). If we do not hear back from you after some time, this PR will be closed for inactivity.

If anyone else wishes to continue the work of this PR, please create a PR with a link to this PR for reference.
Thank you for your contributions. 👋

@sciwhiz12 sciwhiz12 added the Stale This request hasn't had an update from the author in a long time. label Aug 27, 2022
@amadornes
Copy link
Contributor Author

I'll be porting this 1.19, yep. Just haven't had the time for it 😅

I'll close this and open a new PR targetting 1.19 directly once it's ready. I could keep the same one, but it's going to be redone from scratch due to the all changes introduced in this area during the client cleanup/refactor, so I may as well keep them separate and just reference this PR in the other one.

@amadornes amadornes closed this Aug 27, 2022
@autoforge autoforge bot removed the Triage This request requires the active attention of the Triage Team. Requires labelling or reviews. label Feb 14, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.18 Feature This request implements a new feature. Needs Rebase This PR requires a rebase to implement upstream changes (usually to fix merge conflicts). Needs Update This request requires an update from the author to confirm whether the request is relevant. Rendering This request deals with rendering. These must be treated with care, and tend to progress slowly. Stale This request hasn't had an update from the author in a long time.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants