Unravel Engine C++ Reference
Loading...
Searching...
No Matches
auto_exposure_pass.cpp
Go to the documentation of this file.
5#include <graphics/texture.h>
6#include <algorithm>
7
8namespace unravel
9{
10
12{
13 auto& am = ctx.get_cached<asset_manager>();
14
15 auto cs_histogram = am.get_asset<gfx::shader>("engine:/data/shaders/exposure/cs_luminance_histogram.sc");
16 auto cs_average = am.get_asset<gfx::shader>("engine:/data/shaders/exposure/cs_histogram_average.sc");
17
18 if(!cs_histogram || !cs_average)
19 {
20 return false;
21 }
22
23 histogram_program_.program = std::make_shared<gpu_program>(cs_histogram);
24 histogram_program_.cache_uniforms();
25
26 average_program_.program = std::make_shared<gpu_program>(cs_average);
27 average_program_.cache_uniforms();
28
29 histogram_buffer_ = bgfx::createDynamicIndexBuffer(histogram_bins,
30 BGFX_BUFFER_COMPUTE_READ_WRITE | BGFX_BUFFER_INDEX32);
31
32 return histogram_program_.program->is_valid() &&
33 average_program_.program->is_valid() &&
34 bgfx::isValid(histogram_buffer_);
35}
36
38{
39 if(bgfx::isValid(histogram_buffer_))
40 {
41 bgfx::destroy(histogram_buffer_);
42 histogram_buffer_ = BGFX_INVALID_HANDLE;
43 }
44
45
46 return 0;
47}
48
49void auto_exposure_pass::ensure_resources(gfx::render_view& rview)
50{
51 auto& exposure_tex = rview.tex_get_or_emplace("AUTO_EXPOSURE");
52 if(gfx::needs_recreate(exposure_tex, {1, 1}))
53 {
54 // Two-step init to dodge a bgfx GL-backend incompatibility: when
55 // BGFX_TEXTURE_COMPUTE_WRITE is set, bgfx creates the texture via glTexStorage2D
56 // (immutable storage), but the same code path then tries to upload any initial
57 // `_mem` payload via glTexImage2D -- which is GL_INVALID_OPERATION on immutable
58 // storage. So we create the texture with no initial data, then push the seed
59 // value through update_texture_2d (which routes to glTexSubImage2D, the API
60 // that *is* allowed on immutable storage). Reproduces for any R32F + COMPUTE_WRITE
61 // texture regardless of dimensions; size doesn't matter.
62 exposure_tex.reset();
63 exposure_tex = std::make_shared<gfx::texture>(1,
64 1,
65 false,
66 1,
67 gfx::texture_format::R32F,
68 BGFX_TEXTURE_COMPUTE_WRITE);
69
70 // Seed the texel to 1.0 so any reader sampling AUTO_EXPOSURE before the very first
71 // run_average() (e.g. tonemapping on frame 0) sees a sane multiplier instead of
72 // whatever was in freshly-allocated storage. The AUTO_EXPOSURE_SNAP flag below
73 // makes run_average force-converge to the measured value on its first dispatch,
74 // so the seed only matters for that single-frame window.
75 const float initial_exposure = 1.0f;
76 const gfx::memory_view* initial_pixel = gfx::copy(&initial_exposure, sizeof(initial_exposure));
77 gfx::update_texture_2d(exposure_tex->native_handle(), 0, 0, 0, 0, 1, 1, initial_pixel);
78
79 rview.data_get_or_emplace("AUTO_EXPOSURE_SNAP", 1u) = 1u;
80 }
81}
82
84{
85 return rview.tex_safe_get("AUTO_EXPOSURE");
86}
87
88void auto_exposure_pass::run_histogram(gfx::render_view& rview, const settings& config, const gfx::frame_buffer::ptr& input)
89{
90 const auto input_size = input->get_size();
91
92 // Meter from a subsampled grid rather than the full-resolution buffer. The grid
93 // is capped to max_metering_dim on the long edge (aspect preserved), which keeps
94 // the histogram statistically equivalent while cutting texture bandwidth and the
95 // number of atomic increments dramatically at high resolutions.
96 const uint32_t longest = std::max(input_size.width, input_size.height);
97 const float scale = (longest > max_metering_dim) ? (float(max_metering_dim) / float(longest)) : 1.0f;
98 const uint32_t meter_width = std::max(1u, uint32_t(float(input_size.width) * scale));
99 const uint32_t meter_height = std::max(1u, uint32_t(float(input_size.height) * scale));
100
101 gfx::render_pass pass("Auto Exposure/Histogram");
102
103 histogram_program_.program->begin();
104
105 gfx::set_texture(histogram_program_.s_hdr_input, 0, input->get_texture());
106
107 gfx::set_buffer(1, histogram_buffer_, bgfx::Access::ReadWrite);
108
109 float log_range = max_log_lum - min_log_lum;
110 float inv_log_range = 1.0f / log_range;
111 float params[4] = {min_log_lum, inv_log_range, float(meter_width), float(meter_height)};
112 gfx::set_uniform(histogram_program_.u_histogram_params, params);
113
114 float metering_params[4] = {float(static_cast<int>(config.metering_mode)), config.metering_area, 0.0f, 0.0f};
115 gfx::set_uniform(histogram_program_.u_metering_params, metering_params);
116
117 uint32_t groups_x = (meter_width + 15) / 16;
118 uint32_t groups_y = (meter_height + 15) / 16;
119 bgfx::dispatch(pass.id, histogram_program_.program->native_handle(), groups_x, groups_y, 1);
120
121 histogram_program_.program->end();
122}
123
124void auto_exposure_pass::run_average(gfx::render_view& rview, const settings& config, float dt)
125{
126 auto exposure_tex = rview.tex_get("AUTO_EXPOSURE");
127 if(!exposure_tex)
128 {
129 return;
130 }
131
132 gfx::render_pass pass("Auto Exposure/Average");
133
134 average_program_.program->begin();
135
136 gfx::set_buffer(0, histogram_buffer_, bgfx::Access::ReadWrite);
137
138 gfx::set_image(1, exposure_tex->native_handle(), 0, bgfx::Access::ReadWrite, gfx::texture_format::R32F);
139
140 float log_range = max_log_lum - min_log_lum;
141 float params0[4] = {min_log_lum, log_range, config.low_percentile, config.high_percentile};
142 gfx::set_uniform(average_program_.u_average_params0, params0);
143
144 float params1[4] = {config.min_ev, config.max_ev, config.compensation, 0.0f};
145 gfx::set_uniform(average_program_.u_average_params1, params1);
146
147 float effective_dt = dt;
148 auto& snap = rview.data_get_or_emplace("AUTO_EXPOSURE_SNAP", 0u);
149 if(snap != 0u)
150 {
151 effective_dt = 100.0f;
152 snap = 0u;
153 }
154
155 float params2[4] = {effective_dt, config.adaptation_speed_up, config.adaptation_speed_down, 0.0f};
156 gfx::set_uniform(average_program_.u_average_params2, params2);
157
158 bgfx::dispatch(pass.id, average_program_.program->native_handle(), 1, 1, 1);
159
160 average_program_.program->end();
161}
162
164{
165 APP_SCOPE_PERF("Rendering/Auto Exposure Pass");
166
167 ensure_resources(rview);
168
169 run_histogram(rview, params.config, params.input);
170 run_average(rview, params.config, params.delta_time);
171}
172
174{
175 rview.tex_remove("AUTO_EXPOSURE");
176 rview.data_get_or_emplace("AUTO_EXPOSURE_SNAP", 1u) = 1u;
177}
178
179} // namespace unravel
void tex_remove(const hpp::string_view &id)
auto tex_get(const hpp::string_view &id) const -> const texture::ptr &
auto tex_safe_get(const hpp::string_view &id) const -> const texture::ptr &
auto tex_get_or_emplace(const hpp::string_view &id) -> texture::ptr &
auto data_get_or_emplace(const hpp::string_view &id, uint32_t default_val=0) -> uint32_t &
Manages assets, including loading, unloading, and storage.
auto get_asset(const std::string &key, load_flags flags=load_flags::standard, load_mode mode=load_mode::immediate) -> asset_handle< T >
Gets an asset by its key.
auto init(rtti::context &ctx) -> bool
void run(gfx::render_view &rview, const run_params &params)
Runs histogram + temporal average to produce a 1x1 adapted exposure texture.
void release_resources(gfx::render_view &rview)
auto get_exposure_texture(gfx::render_view &rview) const -> gfx::texture::ptr
void update_texture_2d(texture_handle _handle, uint16_t _layer, uint8_t _mip, uint16_t _x, uint16_t _y, uint16_t _width, uint16_t _height, const memory_view *_mem, uint16_t _pitch)
Definition graphics.cpp:719
void set_buffer(uint8_t _stage, index_buffer_handle _handle, access _access)
auto needs_recreate(const gfx::frame_buffer::ptr &fbo, const usize32_t &size) -> bool
const memory_view * copy(const void *_data, uint32_t _size)
Definition graphics.cpp:460
void set_image(uint8_t _stage, texture_handle _handle, uint8_t _mip, access _access, texture_format _format)
void set_uniform(uniform_handle _handle, const void *_value, uint16_t _num)
Definition graphics.cpp:977
bgfx::Memory memory_view
Definition graphics.h:23
void set_texture(uint8_t _stage, uniform_handle _sampler, texture_handle _handle, uint32_t _flags)
std::vector< float > scale
#define APP_SCOPE_PERF(name_literal)
Create a scoped performance timer that records to the timeline profiler. Only accepts string literals...
Definition profiler.h:675