Skip to content

Add option to skip subpass and tonemap directly on fragment on mobile renderer.#113853

Draft
DarioSamo wants to merge 2 commits into
godotengine:masterfrom
DarioSamo:mobile-skip-tonemapping
Draft

Add option to skip subpass and tonemap directly on fragment on mobile renderer.#113853
DarioSamo wants to merge 2 commits into
godotengine:masterfrom
DarioSamo:mobile-skip-tonemapping

Conversation

@DarioSamo

@DarioSamo DarioSamo commented Dec 10, 2025

Copy link
Copy Markdown
Contributor

This PR adds a feature to tonemap directly while drawing 3D geometry in the mobile renderer, a feature that might not be very useful for most projects as it affects the accuracy of the blending, but it can provide some extra performance for those projects that need it. This is the equivalent tonemapping process to the compatibility renderer, which also applies this adjustment directly when drawing.

Using this feature practically disables all other effects that can be applied during the tonemapping step, so it should only be used by projects that are fully aware of the consequences. On the mobile platforms this PR was tested on such as VR, it is estimated it can save around 0.5 to 1ms in total, which can be significant when targetting rates such as 90 Hz (~11 ms).

As a bonus, it also reorganizes the tonemapping code to share the implementations more effectively and it also adds support for specialization constants on Forward+'s tonemapper pass in the same way that the Mobile one does, which should provide it with some performance improvements.

Opening up as draft to make it easier for me to self-review.

@Calinou

Calinou commented Dec 10, 2025

Copy link
Copy Markdown
Member

Like in Compatibility, can this be performed automatically if no feature that requires a tonemap pass is used? In Compatibility, the tonemap pass is only used if any of these conditions are true:

  • 3D resolution scale is not 1.0.
  • Glow is enabled.
  • SSAO is enabled.
  • Screen-space AA is used (not implemented yet, but it would need the tonemap pass).
  • Adjustments are enabled.

From quick testing, it seems this PR doesn't support resolution scaling while having the new project setting enabled. If you change the 3D resolution scale after enabling Tonemap Before Blending, it will appear to have no effect. If you enable Tonemap Before Blending while having a resolution scale factor not equal to 1.0, the option won't appear to have any effect. In this case, this is also spammed in the console:

[...]
ERROR: Condition "!E" is true. Returning: TEXTURE_SAMPLES_1
   at: framebuffer_format_get_texture_samples (./servers/rendering/rendering_device.cpp:2916)
ERROR: Mismatch fragment shader output mask (1) and framebuffer color output mask (0) when binding both in render pipeline.
   at: render_pipeline_create (./servers/rendering/rendering_device.cpp:4129)
ERROR: Condition "pipeline.is_null()" is true. Returning: RID()
   at: _generate_version (./servers/rendering/renderer_rd/pipeline_cache_rd.cpp:59)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_bind_render_pipeline (./servers/rendering/rendering_device.cpp:4662)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_bind_uniform_set (./servers/rendering/rendering_device.cpp:4754)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_bind_uniform_set (./servers/rendering/rendering_device.cpp:4754)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_bind_uniform_set (./servers/rendering/rendering_device.cpp:4754)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_bind_uniform_set (./servers/rendering/rendering_device.cpp:4754)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_set_push_constant (./servers/rendering/rendering_device.cpp:4948)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_draw (./servers/rendering/rendering_device.cpp:4965)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_bind_uniform_set (./servers/rendering/rendering_device.cpp:4754)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_bind_uniform_set (./servers/rendering/rendering_device.cpp:4754)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_bind_uniform_set (./servers/rendering/rendering_device.cpp:4754)
ERROR: Immediate draw list is already inactive.
   at: draw_list_end (./servers/rendering/rendering_device.cpp:5312)
ERROR: All textures in a framebuffer should be the same size.
   at: framebuffer_create_multipass (./servers/rendering/rendering_device.cpp:3017)
ERROR: Condition "rid.is_null()" is true. Returning: rid
   at: _allocate_from_data (./servers/rendering/renderer_rd/framebuffer_cache_rd.h:160)
ERROR: Parameter "framebuffer" is null.
   at: draw_list_begin (./servers/rendering/rendering_device.cpp:4531)
ERROR: Parameter "framebuffer" is null.
   at: framebuffer_get_format (./servers/rendering/rendering_device.cpp:3077)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_bind_uniform_set (./servers/rendering/rendering_device.cpp:4754)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_bind_uniform_set (./servers/rendering/rendering_device.cpp:4754)
ERROR: Condition "!draw_list.active" is true.
   at: draw_list_bind_uniform_set (./servers/rendering/rendering_device.cpp:4754)
ERROR: Parameter "framebuffer" is null.
   at: framebuffer_get_format (./servers/rendering/rendering_device.cpp:3077)
ERROR: Condition "!E" is true. Returning: TEXTURE_SAMPLES_1
   at: framebuffer_format_get_texture_samples (./servers/rendering/rendering_device.cpp:2916)
ERROR: Mismatch fragment shader output mask (1) and framebuffer color output mask (0) when binding both in render pipeline.
   at: render_pipeline_create (./servers/rendering/rendering_device.cpp:4129)
[...]

@DarioSamo

Copy link
Copy Markdown
Contributor Author

Like in Compatibility, can this be performed automatically if no feature that requires a tonemap pass is used? In Compatibility, the tonemap pass is only used if any of these conditions are true:

Is it automatic in compatibility then? Do transparencies get weaker then when it does that? Or are the colors in srgb space instead of linear when drawn by the shader?

@clayjohn

Copy link
Copy Markdown
Member

Is it automatic in compatibility then? Do transparencies get weaker then when it does that? Or are the colors in srgb space instead of linear when drawn by the shader?

Compatibility always renders in srgb space, regardless of whether the final post process pass is used

Comment thread doc/classes/ProjectSettings.xml Outdated
@@ -3475,6 +3475,11 @@
Practically speaking, this means that the end result of the Viewport will not be clamped to the [code]0-1[/code] range and can be used in 3D rendering without color encoding adjustments. This allows 2D rendering to take advantage of effects requiring high dynamic range (e.g. 2D glow) as well as substantially improves the appearance of effects requiring highly detailed gradients.
[b]Note:[/b] This property is only read when the project starts. To toggle HDR 2D at runtime, set [member Viewport.use_hdr_2d] on the root [Viewport].
</member>
<member name="rendering/viewport/tonemap_before_blending" type="bool" setter="" getter="" default="false">
If [code]true[/code], enables [member Viewport.tonemap_before_blending] on the root Viewport. When enabled, post-processing options will be limited to tonemapping only and it'll be applied directly when drawing 3D geometry. This can improve performance at the cost of inaccurate blending and weaker transparencies. This is the same technique employed by the Compatibility renderer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
If [code]true[/code], enables [member Viewport.tonemap_before_blending] on the root Viewport. When enabled, post-processing options will be limited to tonemapping only and it'll be applied directly when drawing 3D geometry. This can improve performance at the cost of inaccurate blending and weaker transparencies. This is the same technique employed by the Compatibility renderer.
If [code]true[/code], enables [member Viewport.tonemap_before_blending] on the root Viewport. When enabled, post-processing options will be limited to tonemapping only and will be applied directly when drawing 3D geometry. This can improve performance at the cost of inaccurate blending and weaker transparencies. This is the same technique employed by the Compatibility renderer.

I'd say

Comment thread doc/classes/RenderingServer.xml Outdated
<param index="0" name="viewport" type="RID" />
<param index="1" name="enabled" type="bool" />
<description>
If [code]true[/code], the viewport's post-processing options will be limited to tonemapping only and it'll be applied directly when drawing 3D geometry. This can improve performance at the cost of inaccurate blending and weaker transparencies. This is the same technique employed by the Compatibility renderer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
If [code]true[/code], the viewport's post-processing options will be limited to tonemapping only and it'll be applied directly when drawing 3D geometry. This can improve performance at the cost of inaccurate blending and weaker transparencies. This is the same technique employed by the Compatibility renderer.
If [code]true[/code], the viewport's post-processing options will be limited to tonemapping only and will be applied directly when drawing 3D geometry. This can improve performance at the cost of inaccurate blending and weaker transparencies. This is the same technique employed by the Compatibility renderer.

Comment thread doc/classes/Viewport.xml Outdated
@@ -455,6 +455,10 @@
[b]Note:[/b] If [member scaling_3d_scale] is lower than [code]1.0[/code] (exclusive), [member texture_mipmap_bias] is used to adjust the automatic mipmap bias which is calculated internally based on the scale factor. The formula for this is [code]log2(scaling_3d_scale) + mipmap_bias[/code].
To control this property on the root viewport, set the [member ProjectSettings.rendering/textures/default_filters/texture_mipmap_bias] project setting.
</member>
<member name="tonemap_before_blending" type="bool" setter="set_tonemap_before_blending" getter="has_tonemap_before_blending" default="false">
If [code]true[/code], the viewport's post-processing options will be limited to tonemapping only and it'll be applied directly when drawing 3D geometry. This can improve performance at the cost of inaccurate blending and weaker transparencies. This is the same technique employed by the Compatibility renderer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
If [code]true[/code], the viewport's post-processing options will be limited to tonemapping only and it'll be applied directly when drawing 3D geometry. This can improve performance at the cost of inaccurate blending and weaker transparencies. This is the same technique employed by the Compatibility renderer.
If [code]true[/code], the viewport's post-processing options will be limited to tonemapping only and will be applied directly when drawing 3D geometry. This can improve performance at the cost of inaccurate blending and weaker transparencies. This is the same technique employed by the Compatibility renderer.

// SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////

// Nvidia Original FXAA 3.11 License

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This file needs to be added to the COPYRIGHT.txt file where the other references to this copyright is mentioned (if already mentioned, otherwise it needs a new entry)

// We invert luminance_multiplier for sky so that we can combine it with exposure value.
float inverse_luminance_multiplier = 1.0 / rb->get_luminance_multiplier();
float sky_luminance_multiplier = 1.0 / 2.0; // Hardcoded since sky always uses LDR in the mobile renderer
float inverse_luminance_multiplier = tonemap_before_blending ? 1.0f : 1.0 / rb->get_luminance_multiplier();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
float inverse_luminance_multiplier = tonemap_before_blending ? 1.0f : 1.0 / rb->get_luminance_multiplier();
float inverse_luminance_multiplier = tonemap_before_blending ? 1.0f : (1.0 / rb->get_luminance_multiplier());

For readability

@DarioSamo DarioSamo force-pushed the mobile-skip-tonemapping branch 2 times, most recently from d3c5c9e to 899de9a Compare December 17, 2025 17:50
@DarioSamo

Copy link
Copy Markdown
Contributor Author

I've been reworking this PR and opted to go for a different and more automatic approach. What I did actually worked for the most part, except one minor implementation quirk that I'm still working out. For anyone that still wants the older version of this PR, I'll keep it in my branch mobile-skip-tonemapping-old.

The benefits of the new approach I took are that it doesn't break blending: in most situations, it looks the same as if it was using the subpass or the separate pass to render. The trick is that it only applies automatically if no tonemapping is in use, which means the renderer can just output directly to an sRGB version of the framebuffer and get linear to sRGB conversion for free when the target is sampled.

There's only one caveat which I'm investigating how to solve at the moment, but if the scene is prone to using values that normally get clamped without applying the luminance multiplier, then some colors can get a bit lost when the fast path is used. As far as I looked, the only solution would be to change to a higher precision internal format that supports floats, that could end up negating the performance benefit of this. There also remains a possibility that the error just isn't visible for the user if the project's materials render within the 0-1 range.

@DarioSamo DarioSamo force-pushed the mobile-skip-tonemapping branch 6 times, most recently from 08c4e00 to 33fd851 Compare December 18, 2025 13:33
@DarioSamo DarioSamo force-pushed the mobile-skip-tonemapping branch from 33fd851 to ed2e2b2 Compare December 18, 2025 13:54
@allenwp

allenwp commented Jan 3, 2026

Copy link
Copy Markdown
Contributor

If you plan to continue working on this, would you mind providing a proposal that describes why this is needed instead of using the Compatibility rendering method?

My gut tells me that this is an undesirable feature because Godot already has six major rendering configurations (Compatibility, Mobile, and Forward+, each with HDR 2D enabled or disabled) plus extra complexity added with features like SMAA, etc. Adding yet another configuration that behaves quite differently in terms of what encoding is used on various buffers would be a notable bloat to the engine that would be difficult to for people to test and maintain. In the current state of Godot, it is uncommon for PR authors to do proper testing on the existing six rendering configurations, so this addition risks making things more bug/error prone.

I think this sort of feature might be better introduced as a replacement to Compatibility: when we remove Compatibility, we can consider adding this sort of feature to replace it... but without a detailed proposal, it's difficult to reason about this idea...

@clayjohn

clayjohn commented Jan 3, 2026

Copy link
Copy Markdown
Member

@allenwp this is purely a performance optimization. The compatibility renderer is often not a good choice for VR due to rendering in gamma space and other quality tradeoffs. This PR bridges the gap in performance between the mobile renderer and the compatibility renderer while maintaining the quality of the mobile renderer

@allenwp

allenwp commented Jan 3, 2026

Copy link
Copy Markdown
Contributor

Ok, thanks, that makes sense.

If this gets to a ready-for-review state, I'll revisit my thoughts about the extra complexity that this adds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants