2022-12-23 23:41:44 +11:00
|
|
|
/**************************************************************************/
|
|
|
|
|
/* luminance.cpp */
|
|
|
|
|
/**************************************************************************/
|
|
|
|
|
/* This file is part of: */
|
|
|
|
|
/* GODOT ENGINE */
|
|
|
|
|
/* https://godotengine.org */
|
|
|
|
|
/**************************************************************************/
|
|
|
|
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
|
|
|
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
|
|
|
|
/* */
|
|
|
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
|
|
|
/* a copy of this software and associated documentation files (the */
|
|
|
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
|
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
|
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
|
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
|
|
|
/* the following conditions: */
|
|
|
|
|
/* */
|
|
|
|
|
/* The above copyright notice and this permission notice shall be */
|
|
|
|
|
/* included in all copies or substantial portions of the Software. */
|
|
|
|
|
/* */
|
|
|
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
|
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
|
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
|
|
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
|
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
|
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
|
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
|
|
|
/**************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "luminance.h"
|
|
|
|
|
#include "../framebuffer_cache_rd.h"
|
|
|
|
|
#include "../uniform_set_cache_rd.h"
|
|
|
|
|
#include "servers/rendering/renderer_rd/storage_rd/material_storage.h"
|
|
|
|
|
|
|
|
|
|
using namespace RendererRD;
|
|
|
|
|
|
|
|
|
|
Luminance::Luminance(bool p_prefer_raster_effects) {
|
|
|
|
|
prefer_raster_effects = p_prefer_raster_effects;
|
|
|
|
|
|
|
|
|
|
if (prefer_raster_effects) {
|
|
|
|
|
Vector<String> luminance_reduce_modes;
|
|
|
|
|
luminance_reduce_modes.push_back("\n#define FIRST_PASS\n"); // LUMINANCE_REDUCE_FRAGMENT_FIRST
|
|
|
|
|
luminance_reduce_modes.push_back("\n"); // LUMINANCE_REDUCE_FRAGMENT
|
|
|
|
|
luminance_reduce_modes.push_back("\n#define FINAL_PASS\n"); // LUMINANCE_REDUCE_FRAGMENT_FINAL
|
|
|
|
|
|
|
|
|
|
luminance_reduce_raster.shader.initialize(luminance_reduce_modes);
|
|
|
|
|
luminance_reduce_raster.shader_version = luminance_reduce_raster.shader.version_create();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < LUMINANCE_REDUCE_FRAGMENT_MAX; i++) {
|
|
|
|
|
luminance_reduce_raster.pipelines[i].setup(luminance_reduce_raster.shader.version_get_shader(luminance_reduce_raster.shader_version, i), RD::RENDER_PRIMITIVE_TRIANGLES, RD::PipelineRasterizationState(), RD::PipelineMultisampleState(), RD::PipelineDepthStencilState(), RD::PipelineColorBlendState::create_disabled(), 0);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Initialize luminance_reduce
|
|
|
|
|
Vector<String> luminance_reduce_modes;
|
|
|
|
|
luminance_reduce_modes.push_back("\n#define READ_TEXTURE\n");
|
|
|
|
|
luminance_reduce_modes.push_back("\n");
|
|
|
|
|
luminance_reduce_modes.push_back("\n#define WRITE_LUMINANCE\n");
|
|
|
|
|
|
|
|
|
|
luminance_reduce.shader.initialize(luminance_reduce_modes);
|
|
|
|
|
luminance_reduce.shader_version = luminance_reduce.shader.version_create();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < LUMINANCE_REDUCE_MAX; i++) {
|
|
|
|
|
luminance_reduce.pipelines[i] = RD::get_singleton()->compute_pipeline_create(luminance_reduce.shader.version_get_shader(luminance_reduce.shader_version, i));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < LUMINANCE_REDUCE_FRAGMENT_MAX; i++) {
|
|
|
|
|
luminance_reduce_raster.pipelines[i].clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Luminance::~Luminance() {
|
|
|
|
|
if (prefer_raster_effects) {
|
|
|
|
|
luminance_reduce_raster.shader.version_free(luminance_reduce_raster.shader_version);
|
|
|
|
|
} else {
|
|
|
|
|
luminance_reduce.shader.version_free(luminance_reduce.shader_version);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Luminance::LuminanceBuffers::set_prefer_raster_effects(bool p_prefer_raster_effects) {
|
|
|
|
|
prefer_raster_effects = p_prefer_raster_effects;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Luminance::LuminanceBuffers::configure(RenderSceneBuffersRD *p_render_buffers) {
|
|
|
|
|
Size2i internal_size = p_render_buffers->get_internal_size();
|
|
|
|
|
int w = internal_size.x;
|
|
|
|
|
int h = internal_size.y;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
w = MAX(w / 8, 1);
|
|
|
|
|
h = MAX(h / 8, 1);
|
|
|
|
|
|
|
|
|
|
RD::TextureFormat tf;
|
|
|
|
|
tf.format = RD::DATA_FORMAT_R32_SFLOAT;
|
|
|
|
|
tf.width = w;
|
|
|
|
|
tf.height = h;
|
|
|
|
|
|
|
|
|
|
bool final = w == 1 && h == 1;
|
|
|
|
|
|
|
|
|
|
if (prefer_raster_effects) {
|
|
|
|
|
tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT;
|
|
|
|
|
} else {
|
|
|
|
|
tf.usage_bits = RD::TEXTURE_USAGE_STORAGE_BIT;
|
Fix uninitialized value in Tonemap
And anything that uses luminance.
The class Luminance in luminance.cpp is in charge of averaging the
luminance of all pixels.
It performs multiple passes until it reaches a 1x1 texture containing
the total average. This is standard luminance averaging on GPU.
Then the "result" of this frame and the "prev_frame_result" are averaged
together at a certain speed to mimic eye adaptation.
Then this avarege becomes the "source" for the next frame. This is done
here:
```cpp
SWAP(p_luminance_buffers->current,
p_luminance_buffers->reduce.write[p_luminance_buffers->reduce.size() -
1]);
```
So far pretty normal stuff.
**The problem is: prev_frame_result IS UNINITIALIZED**. Therefore it's
possible for prev_frame_result to contain garbage values like -5+e15
which causes the screen to stay black for a minute until eye adaptation
catches up.
Windows will always force allocations to be reset to 0, but Linux does
not do that.
However Windows just delays the bug; because it's possible for VMA to
reuse a block.
You can repro this bug by downloading Bistro, creating a camera,
selecting a default scene; and then launching Bistro.
Everything will work fine.
Until you decide to resize the window. It takes a few tries on Godot,
but eventually the screen becomes black.
If you wait around a minute, the screen will "unblack" itself back to
normal.
Even if it's not stuck in black after resize, you may notice that every
resize is inconsistent in how the eye adaptation catches up (i.e.
sometimes it flashes to white, sometimes it does not).
If you can't repro the bug, you need to try harder by doing arbitrary
resizes until it triggers.
Also, I advise to try this on Linux; since Windows' sanitization of
memory gets in the way.
There's probably multiple tickets already filled around issues that were
rooted in luminance calculations starting from uninitialized memory.
This PR sets a default value of 0, which causes the screen to always
flash white after resize. Setting a different value like 0.1 makes the
flash effect weaker. Setting it to a high value like 5.0 makes the
screen flash from dark instead.
This bug can be backported to 4.3. I don't know if it can be backported
to earlier; as the render graph makes sure the texture_clear() calls
gets issued in the right place; whereas in <= 4.2 it might be
problematic depending on when Luminance::LuminanceBuffers::configure is
being called.
2025-02-20 16:51:41 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (final) {
|
|
|
|
|
tf.usage_bits |= RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT;
|
2022-12-23 23:41:44 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RID texture = RD::get_singleton()->texture_create(tf, RD::TextureView());
|
|
|
|
|
reduce.push_back(texture);
|
|
|
|
|
|
|
|
|
|
if (final) {
|
|
|
|
|
current = RD::get_singleton()->texture_create(tf, RD::TextureView());
|
Fix uninitialized value in Tonemap
And anything that uses luminance.
The class Luminance in luminance.cpp is in charge of averaging the
luminance of all pixels.
It performs multiple passes until it reaches a 1x1 texture containing
the total average. This is standard luminance averaging on GPU.
Then the "result" of this frame and the "prev_frame_result" are averaged
together at a certain speed to mimic eye adaptation.
Then this avarege becomes the "source" for the next frame. This is done
here:
```cpp
SWAP(p_luminance_buffers->current,
p_luminance_buffers->reduce.write[p_luminance_buffers->reduce.size() -
1]);
```
So far pretty normal stuff.
**The problem is: prev_frame_result IS UNINITIALIZED**. Therefore it's
possible for prev_frame_result to contain garbage values like -5+e15
which causes the screen to stay black for a minute until eye adaptation
catches up.
Windows will always force allocations to be reset to 0, but Linux does
not do that.
However Windows just delays the bug; because it's possible for VMA to
reuse a block.
You can repro this bug by downloading Bistro, creating a camera,
selecting a default scene; and then launching Bistro.
Everything will work fine.
Until you decide to resize the window. It takes a few tries on Godot,
but eventually the screen becomes black.
If you wait around a minute, the screen will "unblack" itself back to
normal.
Even if it's not stuck in black after resize, you may notice that every
resize is inconsistent in how the eye adaptation catches up (i.e.
sometimes it flashes to white, sometimes it does not).
If you can't repro the bug, you need to try harder by doing arbitrary
resizes until it triggers.
Also, I advise to try this on Linux; since Windows' sanitization of
memory gets in the way.
There's probably multiple tickets already filled around issues that were
rooted in luminance calculations starting from uninitialized memory.
This PR sets a default value of 0, which causes the screen to always
flash white after resize. Setting a different value like 0.1 makes the
flash effect weaker. Setting it to a high value like 5.0 makes the
screen flash from dark instead.
This bug can be backported to 4.3. I don't know if it can be backported
to earlier; as the render graph makes sure the texture_clear() calls
gets issued in the right place; whereas in <= 4.2 it might be
problematic depending on when Luminance::LuminanceBuffers::configure is
being called.
2025-02-20 16:51:41 -03:00
|
|
|
RD::get_singleton()->texture_clear(current, Color(0.0, 0.0, 0.0), 0u, 1u, 0u, 1u);
|
2022-12-23 23:41:44 +11:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Luminance::LuminanceBuffers::free_data() {
|
|
|
|
|
for (int i = 0; i < reduce.size(); i++) {
|
2025-06-05 00:49:35 -07:00
|
|
|
RD::get_singleton()->free_rid(reduce[i]);
|
2022-12-23 23:41:44 +11:00
|
|
|
}
|
|
|
|
|
reduce.clear();
|
|
|
|
|
|
|
|
|
|
if (current.is_valid()) {
|
2025-06-05 00:49:35 -07:00
|
|
|
RD::get_singleton()->free_rid(current);
|
2022-12-23 23:41:44 +11:00
|
|
|
current = RID();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ref<Luminance::LuminanceBuffers> Luminance::get_luminance_buffers(Ref<RenderSceneBuffersRD> p_render_buffers) {
|
|
|
|
|
if (p_render_buffers->has_custom_data(RB_LUMINANCE_BUFFERS)) {
|
|
|
|
|
return p_render_buffers->get_custom_data(RB_LUMINANCE_BUFFERS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ref<LuminanceBuffers> buffers;
|
|
|
|
|
buffers.instantiate();
|
|
|
|
|
buffers->set_prefer_raster_effects(prefer_raster_effects);
|
|
|
|
|
buffers->configure(p_render_buffers.ptr());
|
|
|
|
|
|
|
|
|
|
p_render_buffers->set_custom_data(RB_LUMINANCE_BUFFERS, buffers);
|
|
|
|
|
|
|
|
|
|
return buffers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RID Luminance::get_current_luminance_buffer(Ref<RenderSceneBuffersRD> p_render_buffers) {
|
|
|
|
|
if (p_render_buffers->has_custom_data(RB_LUMINANCE_BUFFERS)) {
|
|
|
|
|
Ref<LuminanceBuffers> buffers = p_render_buffers->get_custom_data(RB_LUMINANCE_BUFFERS);
|
|
|
|
|
return buffers->current;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return RID();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Luminance::luminance_reduction(RID p_source_texture, const Size2i p_source_size, Ref<LuminanceBuffers> p_luminance_buffers, float p_min_luminance, float p_max_luminance, float p_adjust, bool p_set) {
|
|
|
|
|
UniformSetCacheRD *uniform_set_cache = UniformSetCacheRD::get_singleton();
|
|
|
|
|
ERR_FAIL_NULL(uniform_set_cache);
|
|
|
|
|
MaterialStorage *material_storage = MaterialStorage::get_singleton();
|
|
|
|
|
ERR_FAIL_NULL(material_storage);
|
|
|
|
|
|
|
|
|
|
// setup our uniforms
|
|
|
|
|
RID default_sampler = material_storage->sampler_rd_get_default(RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED);
|
|
|
|
|
|
|
|
|
|
if (prefer_raster_effects) {
|
|
|
|
|
LuminanceReduceRasterPushConstant push_constant;
|
|
|
|
|
memset(&push_constant, 0, sizeof(LuminanceReduceRasterPushConstant));
|
|
|
|
|
|
|
|
|
|
push_constant.max_luminance = p_max_luminance;
|
|
|
|
|
push_constant.min_luminance = p_min_luminance;
|
|
|
|
|
push_constant.exposure_adjust = p_adjust;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < p_luminance_buffers->reduce.size(); i++) {
|
|
|
|
|
push_constant.source_size[0] = i == 0 ? p_source_size.x : push_constant.dest_size[0];
|
|
|
|
|
push_constant.source_size[1] = i == 0 ? p_source_size.y : push_constant.dest_size[1];
|
|
|
|
|
push_constant.dest_size[0] = MAX(push_constant.source_size[0] / 8, 1);
|
|
|
|
|
push_constant.dest_size[1] = MAX(push_constant.source_size[1] / 8, 1);
|
|
|
|
|
|
|
|
|
|
bool final = !p_set && (push_constant.dest_size[0] == 1) && (push_constant.dest_size[1] == 1);
|
|
|
|
|
LuminanceReduceRasterMode mode = final ? LUMINANCE_REDUCE_FRAGMENT_FINAL : (i == 0 ? LUMINANCE_REDUCE_FRAGMENT_FIRST : LUMINANCE_REDUCE_FRAGMENT);
|
|
|
|
|
RID shader = luminance_reduce_raster.shader.version_get_shader(luminance_reduce_raster.shader_version, mode);
|
|
|
|
|
|
|
|
|
|
RID framebuffer = FramebufferCacheRD::get_singleton()->get_cache(p_luminance_buffers->reduce[i]);
|
|
|
|
|
|
|
|
|
|
RD::Uniform u_source_texture(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, Vector<RID>({ default_sampler, i == 0 ? p_source_texture : p_luminance_buffers->reduce[i - 1] }));
|
|
|
|
|
|
2024-10-24 16:01:00 -03:00
|
|
|
RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer);
|
2022-12-23 23:41:44 +11:00
|
|
|
RD::get_singleton()->draw_list_bind_render_pipeline(draw_list, luminance_reduce_raster.pipelines[mode].get_render_pipeline(RD::INVALID_ID, RD::get_singleton()->framebuffer_get_format(framebuffer)));
|
|
|
|
|
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uniform_set_cache->get_cache(shader, 0, u_source_texture), 0);
|
|
|
|
|
if (final) {
|
|
|
|
|
RD::Uniform u_current_texture(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, Vector<RID>({ default_sampler, p_luminance_buffers->current }));
|
|
|
|
|
RD::get_singleton()->draw_list_bind_uniform_set(draw_list, uniform_set_cache->get_cache(shader, 1, u_current_texture), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RD::get_singleton()->draw_list_set_push_constant(draw_list, &push_constant, sizeof(LuminanceReduceRasterPushConstant));
|
|
|
|
|
|
2023-08-05 16:30:47 -03:00
|
|
|
RD::get_singleton()->draw_list_draw(draw_list, false, 1u, 3u);
|
2022-12-23 23:41:44 +11:00
|
|
|
RD::get_singleton()->draw_list_end();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
LuminanceReducePushConstant push_constant;
|
|
|
|
|
memset(&push_constant, 0, sizeof(LuminanceReducePushConstant));
|
|
|
|
|
|
|
|
|
|
push_constant.source_size[0] = p_source_size.x;
|
|
|
|
|
push_constant.source_size[1] = p_source_size.y;
|
|
|
|
|
push_constant.max_luminance = p_max_luminance;
|
|
|
|
|
push_constant.min_luminance = p_min_luminance;
|
|
|
|
|
push_constant.exposure_adjust = p_adjust;
|
|
|
|
|
|
|
|
|
|
RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < p_luminance_buffers->reduce.size(); i++) {
|
|
|
|
|
RID shader;
|
|
|
|
|
|
|
|
|
|
if (i == 0) {
|
|
|
|
|
shader = luminance_reduce.shader.version_get_shader(luminance_reduce.shader_version, LUMINANCE_REDUCE_READ);
|
|
|
|
|
RD::Uniform u_source_texture(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, Vector<RID>({ default_sampler, p_source_texture }));
|
|
|
|
|
|
|
|
|
|
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, luminance_reduce.pipelines[LUMINANCE_REDUCE_READ]);
|
|
|
|
|
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_texture), 0);
|
|
|
|
|
} else {
|
|
|
|
|
RD::get_singleton()->compute_list_add_barrier(compute_list); //needs barrier, wait until previous is done
|
|
|
|
|
|
|
|
|
|
if (i == p_luminance_buffers->reduce.size() - 1 && !p_set) {
|
|
|
|
|
shader = luminance_reduce.shader.version_get_shader(luminance_reduce.shader_version, LUMINANCE_REDUCE_WRITE);
|
|
|
|
|
RD::Uniform u_current_texture(RD::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE, 0, Vector<RID>({ default_sampler, p_luminance_buffers->current }));
|
|
|
|
|
|
|
|
|
|
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, luminance_reduce.pipelines[LUMINANCE_REDUCE_WRITE]);
|
|
|
|
|
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 2, u_current_texture), 2);
|
|
|
|
|
} else {
|
|
|
|
|
shader = luminance_reduce.shader.version_get_shader(luminance_reduce.shader_version, LUMINANCE_REDUCE);
|
|
|
|
|
RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, luminance_reduce.pipelines[LUMINANCE_REDUCE]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RD::Uniform u_source_texture(RD::UNIFORM_TYPE_IMAGE, 0, p_luminance_buffers->reduce[i - 1]);
|
|
|
|
|
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 0, u_source_texture), 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RD::Uniform u_reduce_texture(RD::UNIFORM_TYPE_IMAGE, 0, p_luminance_buffers->reduce[i]);
|
|
|
|
|
RD::get_singleton()->compute_list_bind_uniform_set(compute_list, uniform_set_cache->get_cache(shader, 1, u_reduce_texture), 1);
|
|
|
|
|
|
|
|
|
|
RD::get_singleton()->compute_list_set_push_constant(compute_list, &push_constant, sizeof(LuminanceReducePushConstant));
|
|
|
|
|
|
|
|
|
|
RD::get_singleton()->compute_list_dispatch_threads(compute_list, push_constant.source_size[0], push_constant.source_size[1], 1);
|
|
|
|
|
|
|
|
|
|
push_constant.source_size[0] = MAX(push_constant.source_size[0] / 8, 1);
|
|
|
|
|
push_constant.source_size[1] = MAX(push_constant.source_size[1] / 8, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RD::get_singleton()->compute_list_end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SWAP(p_luminance_buffers->current, p_luminance_buffers->reduce.write[p_luminance_buffers->reduce.size() - 1]);
|
|
|
|
|
}
|