Blender V5.0
eevee_lightprobe_sphere.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6#include "eevee_instance.hh"
7
8namespace blender::eevee {
9
10/* -------------------------------------------------------------------- */
13
15{
16 return instance_.scene->eevee.gi_cubemap_resolution / 2;
17}
18
20{
21 if (!instance_.is_viewport()) {
22 /* TODO(jbakker): should we check on the subtype as well? Now it also populates even when
23 * there are other light probes in the scene. */
24 update_probes_next_sample_ = DEG_id_type_any_exists(instance_.depsgraph, ID_LP);
25 }
26 update_probes_this_sample_ = update_probes_next_sample_;
27
28 do_display_draw_ = false;
29}
30
32{
33 LightProbeModule &light_probes = instance_.light_probes;
34 SphereProbeData &world_data = *static_cast<SphereProbeData *>(&light_probes.world_sphere_);
35 {
36 gpu::Shader *shader = instance_.shaders.static_shader_get(SPHERE_PROBE_REMAP);
37
38 PassSimple &pass = remap_ps_;
39 pass.init();
40 pass.specialize_constant(shader, "extract_sh", &extract_sh_);
41 pass.specialize_constant(shader, "extract_sun", &extract_sh_);
42 pass.shader_set(shader);
43 pass.bind_texture("cubemap_tx", &cubemap_tx_);
44 pass.bind_texture("atlas_tx", &probes_tx_);
45 pass.bind_image("atlas_img", &probes_tx_);
46 pass.bind_ssbo("out_sh", &tmp_spherical_harmonics_);
47 pass.bind_ssbo("out_sun", &tmp_sunlight_);
48 pass.push_constant("probe_coord_packed", reinterpret_cast<int4 *>(&probe_sampling_coord_));
49 pass.push_constant("write_coord_packed", reinterpret_cast<int4 *>(&probe_write_coord_));
50 pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_data.atlas_coord));
51 pass.bind_resources(instance_.uniform_data);
52 pass.dispatch(&dispatch_probe_pack_);
53 }
54 {
55 PassSimple &pass = convolve_ps_;
56 pass.init();
57 pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_CONVOLVE));
58 pass.bind_texture("cubemap_tx", &cubemap_tx_);
59 pass.bind_texture("in_atlas_mip_tx", &convolve_input_);
60 pass.bind_image("out_atlas_mip_img", &convolve_output_);
61 pass.push_constant("probe_coord_packed", reinterpret_cast<int4 *>(&probe_sampling_coord_));
62 pass.push_constant("write_coord_packed", reinterpret_cast<int4 *>(&probe_write_coord_));
63 pass.push_constant("read_coord_packed", reinterpret_cast<int4 *>(&probe_read_coord_));
64 pass.push_constant("read_lod", &convolve_lod_);
66 pass.dispatch(&dispatch_probe_convolve_);
67 }
68 {
69 PassSimple &pass = sum_sh_ps_;
70 pass.init();
71 pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_IRRADIANCE));
72 pass.push_constant("probe_remap_dispatch_size", &dispatch_probe_pack_);
73 pass.bind_ssbo("in_sh", &tmp_spherical_harmonics_);
74 pass.bind_ssbo("out_sh", &spherical_harmonics_);
76 pass.dispatch(1);
77 }
78 {
79 PassSimple &pass = sum_sun_ps_;
80 pass.init();
81 pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_SUNLIGHT));
82 pass.push_constant("probe_remap_dispatch_size", &dispatch_probe_pack_);
83 pass.bind_ssbo("in_sun", &tmp_sunlight_);
84 pass.bind_ssbo("sunlight_buf", &instance_.world.sunlight);
86 pass.dispatch(1);
88 }
89 {
90 PassSimple &pass = select_ps_;
91 pass.init();
92 pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_SELECT));
93 pass.push_constant("lightprobe_sphere_count", &lightprobe_sphere_count_);
94 pass.bind_ssbo("lightprobe_sphere_buf", &data_buf_);
95 instance_.volume_probes.bind_resources(pass);
96 instance_.sampling.bind_resources(pass);
97 pass.bind_resources(instance_.uniform_data);
98 pass.dispatch(&dispatch_probe_select_);
100 }
101}
102
103bool SphereProbeModule::ensure_atlas()
104{
105 /* Make sure the atlas is always initialized even if there is nothing to render to it to fulfill
106 * the resource bindings. */
108
109 if (probes_tx_.ensure_2d_array(gpu::TextureFormat::SFLOAT_16_16_16_16,
111 instance_.light_probes.sphere_layer_count(),
112 usage,
113 nullptr,
115 {
116 probes_tx_.ensure_mip_views();
117 /* TODO(fclem): Clearing means that we need to render all probes again.
118 * If existing data exists, copy it using `CopyImageSubData`. */
120 /* Avoid undefined pixel data. Clear all mips. */
121 float4 data(0.0f);
123 }
124 GPU_texture_mipmap_mode(probes_tx_, true, true);
125 return true;
126 }
127 return false;
128}
129
131{
132 const bool atlas_resized = ensure_atlas();
133 if (atlas_resized) {
134 instance_.light_probes.world_sphere_.do_render = true;
135 }
136 const bool world_updated = instance_.light_probes.world_sphere_.do_render;
137 /* Detect if we need to render probe objects. */
138 update_probes_next_sample_ = false;
139 for (SphereProbe &probe : instance_.light_probes.sphere_map_.values()) {
140 if (atlas_resized || world_updated) {
141 /* Last minute tagging. */
142 probe.do_render = true;
143 }
144 if (probe.do_render) {
145 /* Tag the next redraw to warm up the probe pipeline.
146 * Keep doing this until there is no update.
147 * This avoids stuttering when moving a light-probe. */
148 update_probes_next_sample_ = true;
149 }
150 }
151
152 if (instance_.is_viewport()) {
153 /* When reflection probes are synced the sampling must be reset.
154 *
155 * This fixes issues when using a single non-projected sample. Without resetting the
156 * previous rendered viewport will be drawn and reflection probes will not be updated.
157 * #Instance::render_sample */
158 if (instance_.do_lightprobe_sphere_sync()) {
159 instance_.sampling.reset();
160 }
161 /* If we cannot render probes this redraw make sure we request another redraw. */
162 if (update_probes_next_sample_ && (instance_.do_lightprobe_sphere_sync() == false)) {
164 }
165 }
166}
167
168void SphereProbeModule::ensure_cubemap_render_target(int resolution)
169{
171 cubemap_tx_.ensure_cube(gpu::TextureFormat::SFLOAT_16_16_16_16, resolution, usage);
172 /* TODO(fclem): deallocate it. */
173}
174
175SphereProbeModule::UpdateInfo SphereProbeModule::update_info_from_probe(SphereProbe &probe)
176{
177 SphereProbeModule::UpdateInfo info = {};
178 info.atlas_coord = probe.atlas_coord;
179 info.cube_target_extent = probe.atlas_coord.area_extent() / 2;
180 info.clipping_distances = probe.clipping_distances;
181 info.probe_pos = probe.location;
182 info.do_render = probe.do_render;
183
184 probe.do_render = false;
185 probe.use_for_render = true;
186
187 ensure_cubemap_render_target(info.cube_target_extent);
188 return info;
189}
190
191std::optional<SphereProbeModule::UpdateInfo> SphereProbeModule::world_update_info_pop()
192{
193 SphereProbe &world_probe = instance_.light_probes.world_sphere_;
194 if (world_probe.do_render) {
195 return update_info_from_probe(world_probe);
196 }
197 return std::nullopt;
198}
199
200std::optional<SphereProbeModule::UpdateInfo> SphereProbeModule::probe_update_info_pop()
201{
202 if (!instance_.do_lightprobe_sphere_sync()) {
203 /* Do not update probes during this sample as we did not sync the draw::Passes. */
204 return std::nullopt;
205 }
206
207 for (SphereProbe &probe : instance_.light_probes.sphere_map_.values()) {
208 if (!probe.do_render) {
209 continue;
210 }
211 return update_info_from_probe(probe);
212 }
213
214 return std::nullopt;
215}
216
217void SphereProbeModule::remap_to_octahedral_projection(const SphereProbeAtlasCoord &atlas_coord,
218 bool extract_spherical_harmonics)
219{
220 /* Update shader parameters that change per dispatch. */
221 probe_sampling_coord_ = atlas_coord.as_sampling_coord();
222 probe_write_coord_ = atlas_coord.as_write_coord(0);
223 int resolution = probe_write_coord_.extent;
224 dispatch_probe_pack_ = int3(
226 extract_sh_ = extract_spherical_harmonics;
227 instance_.manager->submit(remap_ps_);
228
229 /* Populate the mip levels */
230 for (auto i : IndexRange(SPHERE_PROBE_MIPMAP_LEVELS - 1)) {
231 convolve_lod_ = i;
232 convolve_input_ = probes_tx_.mip_view(i);
233 convolve_output_ = probes_tx_.mip_view(i + 1);
234 probe_read_coord_ = atlas_coord.as_write_coord(i);
235 probe_write_coord_ = atlas_coord.as_write_coord(i + 1);
236 int out_mip_res = probe_write_coord_.extent;
237 dispatch_probe_convolve_ = int3(
239 instance_.manager->submit(convolve_ps_);
240 }
241
242 if (extract_spherical_harmonics) {
243 instance_.manager->submit(sum_sh_ps_);
244 instance_.manager->submit(sum_sun_ps_);
245 /* All volume probe that needs to composite the world probe need to be updated. */
246 instance_.volume_probes.update_world_irradiance();
247 }
248
249 /* Sync with atlas usage for shading. */
251}
252
254{
255 Vector<SphereProbe *> probe_active;
256 for (auto &probe : instance_.light_probes.sphere_map_.values()) {
257 /* Last slot is reserved for the world probe. */
258 if (lightprobe_sphere_count_ >= SPHERE_PROBE_MAX - 1) {
259 break;
260 }
261 if (!probe.use_for_render) {
262 continue;
263 }
264 /* TODO(fclem): Culling. */
265 probe_active.append(&probe);
266 }
267
268 /* Stable sorting of probes. */
269 std::sort(
270 probe_active.begin(), probe_active.end(), [](const SphereProbe *a, const SphereProbe *b) {
271 if (a->volume != b->volume) {
272 /* Smallest first. */
273 return a->volume < b->volume;
274 }
275 /* Volumes are identical. Any arbitrary criteria can be used to sort them.
276 * Use position to avoid unstable result caused by depsgraph non deterministic eval
277 * order. This could also become a priority parameter. */
278 float3 _a = a->location;
279 float3 _b = b->location;
280 if (_a.x != _b.x) {
281 return _a.x < _b.x;
282 }
283 if (_a.y != _b.y) {
284 return _a.y < _b.y;
285 }
286 if (_a.z != _b.z) {
287 return _a.z < _b.z;
288 }
289 /* Fallback to memory address, since there's no good alternative. */
290 return a < b;
291 });
292
293 /* Push all sorted data to the UBO. */
294 int probe_id = 0;
295 for (auto &probe : probe_active) {
296 data_buf_[probe_id++] = *probe;
297 }
298 /* Add world probe at the end. */
299 data_buf_[probe_id++] = instance_.light_probes.world_sphere_;
300 /* Tag the end of the array. */
301 if (probe_id < SPHERE_PROBE_MAX) {
302 data_buf_[probe_id].atlas_coord.layer = -1;
303 }
304 data_buf_.push_update();
305
306 lightprobe_sphere_count_ = probe_id;
307 dispatch_probe_select_.x = divide_ceil_u(lightprobe_sphere_count_,
309 instance_.manager->submit(select_ps_);
310
311 sync_display(probe_active);
312}
313
314void SphereProbeModule::sync_display(Vector<SphereProbe *> &probe_active)
315{
316 do_display_draw_ = false;
317 if (!instance_.draw_overlays) {
318 return;
319 }
320
321 int display_index = 0;
322 for (int i : probe_active.index_range()) {
323 if (probe_active[i]->viewport_display) {
324 SphereProbeDisplayData &sph_data = display_data_buf_.get_or_resize(display_index++);
325 sph_data.probe_index = i;
326 sph_data.display_size = probe_active[i]->viewport_display_size;
327 }
328 }
329
330 if (display_index == 0) {
331 return;
332 }
333 do_display_draw_ = true;
334 display_data_buf_.resize(display_index);
335 display_data_buf_.push_update();
336}
337
339{
340 if (!do_display_draw_) {
341 return;
342 }
343
344 viewport_display_ps_.init();
345 viewport_display_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
347 instance_.film.depth.test_state | DRW_STATE_CULL_BACK);
348 viewport_display_ps_.framebuffer_set(&view_fb);
349 viewport_display_ps_.shader_set(instance_.shaders.static_shader_get(DISPLAY_PROBE_SPHERE));
350 viewport_display_ps_.bind_resources(*this);
351 viewport_display_ps_.bind_ssbo("display_data_buf", display_data_buf_);
352 viewport_display_ps_.draw_procedural(GPU_PRIM_TRIS, 1, display_data_buf_.size() * 6);
353
354 instance_.manager->submit(viewport_display_ps_, view);
355}
356
358
359} // namespace blender::eevee
MINLINE uint divide_ceil_u(uint a, uint b)
bool DEG_id_type_any_exists(const Depsgraph *depsgraph, short id_type)
@ ID_LP
static AppView * view
@ GPU_PRIM_TRIS
@ GPU_BARRIER_SHADER_STORAGE
Definition GPU_state.hh:48
@ GPU_BARRIER_TEXTURE_FETCH
Definition GPU_state.hh:37
@ GPU_BARRIER_UNIFORM
Definition GPU_state.hh:54
void GPU_memory_barrier(GPUBarrier barrier)
Definition gpu_state.cc:326
void GPU_texture_clear(blender::gpu::Texture *texture, eGPUDataFormat data_format, const void *data)
@ GPU_DATA_FLOAT
void GPU_texture_mipmap_mode(blender::gpu::Texture *texture, bool use_mipmap, bool use_filter)
eGPUTextureUsage
@ GPU_TEXTURE_USAGE_SHADER_READ
@ GPU_TEXTURE_USAGE_SHADER_WRITE
@ GPU_TEXTURE_USAGE_ATTACHMENT
BMesh const char void * data
IndexRange index_range() const
void append(const T &value)
bool ensure_2d_array(blender::gpu::TextureFormat format, int2 extent, int layers, eGPUTextureUsage usage=GPU_TEXTURE_USAGE_GENERAL, const float *data=nullptr, int mip_len=1)
bool ensure_mip_views(bool cube_as_array=false)
bool ensure_cube(blender::gpu::TextureFormat format, int extent, eGPUTextureUsage usage=GPU_TEXTURE_USAGE_GENERAL, float *data=nullptr, int mip_len=1)
gpu::Texture * mip_view(int miplvl)
void bind_resources(U &resources)
Definition draw_pass.hh:449
void shader_set(gpu::Shader *shader)
void bind_texture(const char *name, gpu::Texture *texture, GPUSamplerState state=sampler_auto)
void specialize_constant(gpu::Shader *shader, const char *name, const float &data)
void bind_image(const char *name, gpu::Texture *image)
void dispatch(int group_len)
void barrier(GPUBarrier type)
void push_constant(const char *name, const float &data)
void bind_ssbo(const char *name, gpu::StorageBuf *buffer)
LightProbeModule light_probes
void viewport_draw(View &view, gpu::FrameBuffer *view_fb)
void DRW_viewport_request_redraw()
@ DRW_STATE_WRITE_DEPTH
Definition draw_state.hh:29
@ DRW_STATE_CLIP_CONTROL_UNIT_RANGE
Definition draw_state.hh:68
@ DRW_STATE_WRITE_COLOR
Definition draw_state.hh:30
@ DRW_STATE_CULL_BACK
Definition draw_state.hh:43
#define SPHERE_PROBE_REMAP_GROUP_SIZE
#define SPHERE_PROBE_ATLAS_RES
#define SPHERE_PROBE_SELECT_GROUP_SIZE
#define SPHERE_PROBE_MAX
#define SPHERE_PROBE_MIPMAP_LEVELS
#define SPHERE_PROBE_GROUP_SIZE
detail::Pass< command::DrawCommandBuf > PassSimple
VecBase< T, Size > divide_ceil(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
VecBase< int32_t, 4 > int4
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< int32_t, 3 > int3
VecBase< float, 3 > float3
i
Definition text_draw.cc:230