Blender V4.3
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/* -------------------------------------------------------------------- */
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
27 do_display_draw_ = false;
28}
29
31{
32 update_probes_this_sample_ = update_probes_next_sample_;
33
34 LightProbeModule &light_probes = instance_.light_probes;
35 SphereProbeData &world_data = *static_cast<SphereProbeData *>(&light_probes.world_sphere_);
36 {
38
39 PassSimple &pass = remap_ps_;
40 pass.init();
41 pass.specialize_constant(shader, "extract_sh", &extract_sh_);
42 pass.specialize_constant(shader, "extract_sun", &extract_sh_);
43 pass.shader_set(shader);
44 pass.bind_texture("cubemap_tx", &cubemap_tx_);
45 pass.bind_texture("atlas_tx", &probes_tx_);
46 pass.bind_image("atlas_img", &probes_tx_);
47 pass.bind_ssbo("out_sh", &tmp_spherical_harmonics_);
48 pass.bind_ssbo("out_sun", &tmp_sunlight_);
49 pass.push_constant("probe_coord_packed", reinterpret_cast<int4 *>(&probe_sampling_coord_));
50 pass.push_constant("write_coord_packed", reinterpret_cast<int4 *>(&probe_write_coord_));
51 pass.push_constant("world_coord_packed", reinterpret_cast<int4 *>(&world_data.atlas_coord));
52 pass.bind_resources(instance_.uniform_data);
53 pass.dispatch(&dispatch_probe_pack_);
54 }
55 {
56 PassSimple &pass = convolve_ps_;
57 pass.init();
58 pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_CONVOLVE));
59 pass.bind_texture("cubemap_tx", &cubemap_tx_);
60 pass.bind_texture("in_atlas_mip_tx", &convolve_input_);
61 pass.bind_image("out_atlas_mip_img", &convolve_output_);
62 pass.push_constant("probe_coord_packed", reinterpret_cast<int4 *>(&probe_sampling_coord_));
63 pass.push_constant("write_coord_packed", reinterpret_cast<int4 *>(&probe_write_coord_));
64 pass.push_constant("read_coord_packed", reinterpret_cast<int4 *>(&probe_read_coord_));
65 pass.push_constant("read_lod", &convolve_lod_);
66 pass.barrier(GPU_BARRIER_TEXTURE_FETCH);
67 pass.dispatch(&dispatch_probe_convolve_);
68 }
69 {
70 PassSimple &pass = sum_sh_ps_;
71 pass.init();
72 pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_IRRADIANCE));
73 pass.push_constant("probe_remap_dispatch_size", &dispatch_probe_pack_);
74 pass.bind_ssbo("in_sh", &tmp_spherical_harmonics_);
75 pass.bind_ssbo("out_sh", &spherical_harmonics_);
76 pass.barrier(GPU_BARRIER_SHADER_STORAGE);
77 pass.dispatch(1);
78 }
79 {
80 PassSimple &pass = sum_sun_ps_;
81 pass.init();
82 pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_SUNLIGHT));
83 pass.push_constant("probe_remap_dispatch_size", &dispatch_probe_pack_);
84 pass.bind_ssbo("in_sun", &tmp_sunlight_);
85 pass.bind_ssbo("sunlight_buf", &instance_.world.sunlight);
86 pass.barrier(GPU_BARRIER_SHADER_STORAGE);
87 pass.dispatch(1);
88 pass.barrier(GPU_BARRIER_UNIFORM);
89 }
90 {
91 PassSimple &pass = select_ps_;
92 pass.init();
93 pass.shader_set(instance_.shaders.static_shader_get(SPHERE_PROBE_SELECT));
94 pass.push_constant("lightprobe_sphere_count", &lightprobe_sphere_count_);
95 pass.bind_ssbo("lightprobe_sphere_buf", &data_buf_);
96 instance_.volume_probes.bind_resources(pass);
97 instance_.sampling.bind_resources(pass);
98 pass.bind_resources(instance_.uniform_data);
99 pass.dispatch(&dispatch_probe_select_);
100 pass.barrier(GPU_BARRIER_UNIFORM);
101 }
102}
103
104bool SphereProbeModule::ensure_atlas()
105{
106 /* Make sure the atlas is always initialized even if there is nothing to render to it to fulfill
107 * the resource bindings. */
109
110 if (probes_tx_.ensure_2d_array(GPU_RGBA16F,
112 instance_.light_probes.sphere_layer_count(),
113 usage,
114 nullptr,
116 {
117 probes_tx_.ensure_mip_views();
118 /* TODO(fclem): Clearing means that we need to render all probes again.
119 * If existing data exists, copy it using `CopyImageSubData`. */
120 for (auto i : IndexRange(SPHERE_PROBE_MIPMAP_LEVELS)) {
121 /* Avoid undefined pixel data. Clear all mips. */
122 float4 data(0.0f);
123 GPU_texture_clear(probes_tx_.mip_view(i), GPU_DATA_FLOAT, &data);
124 }
125 GPU_texture_mipmap_mode(probes_tx_, true, true);
126 return true;
127 }
128 return false;
129}
130
132{
133 const bool atlas_resized = ensure_atlas();
134 if (atlas_resized) {
135 instance_.light_probes.world_sphere_.do_render = true;
136 }
137 const bool world_updated = instance_.light_probes.world_sphere_.do_render;
138 /* Detect if we need to render probe objects. */
139 update_probes_next_sample_ = false;
140 for (SphereProbe &probe : instance_.light_probes.sphere_map_.values()) {
141 if (atlas_resized || world_updated) {
142 /* Last minute tagging. */
143 probe.do_render = true;
144 }
145 if (probe.do_render) {
146 /* Tag the next redraw to warm up the probe pipeline.
147 * Keep doing this until there is no update.
148 * This avoids stuttering when moving a light-probe. */
149 update_probes_next_sample_ = true;
150 }
151 }
152
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
167void SphereProbeModule::ensure_cubemap_render_target(int resolution)
168{
170 cubemap_tx_.ensure_cube(GPU_RGBA16F, resolution, usage);
171 /* TODO(fclem): deallocate it. */
172}
173
174SphereProbeModule::UpdateInfo SphereProbeModule::update_info_from_probe(SphereProbe &probe)
175{
176 SphereProbeModule::UpdateInfo info = {};
177 info.atlas_coord = probe.atlas_coord;
178 info.cube_target_extent = probe.atlas_coord.area_extent() / 2;
179 info.clipping_distances = probe.clipping_distances;
180 info.probe_pos = probe.location;
181 info.do_render = probe.do_render;
182
183 probe.do_render = false;
184 probe.use_for_render = true;
185
186 ensure_cubemap_render_target(info.cube_target_extent);
187 return info;
188}
189
190std::optional<SphereProbeModule::UpdateInfo> SphereProbeModule::world_update_info_pop()
191{
192 SphereProbe &world_probe = instance_.light_probes.world_sphere_;
193 if (world_probe.do_render) {
194 return update_info_from_probe(world_probe);
195 }
196 return std::nullopt;
197}
198
199std::optional<SphereProbeModule::UpdateInfo> SphereProbeModule::probe_update_info_pop()
200{
201 if (!instance_.do_lightprobe_sphere_sync()) {
202 /* Do not update probes during this sample as we did not sync the draw::Passes. */
203 return std::nullopt;
204 }
205
206 for (SphereProbe &probe : instance_.light_probes.sphere_map_.values()) {
207 if (!probe.do_render) {
208 continue;
209 }
210 return update_info_from_probe(probe);
211 }
212
213 return std::nullopt;
214}
215
216void SphereProbeModule::remap_to_octahedral_projection(const SphereProbeAtlasCoord &atlas_coord,
217 bool extract_spherical_harmonics)
218{
219 /* Update shader parameters that change per dispatch. */
220 probe_sampling_coord_ = atlas_coord.as_sampling_coord();
221 probe_write_coord_ = atlas_coord.as_write_coord(0);
222 int resolution = probe_write_coord_.extent;
223 dispatch_probe_pack_ = int3(
225 extract_sh_ = extract_spherical_harmonics;
226 instance_.manager->submit(remap_ps_);
227
228 /* Populate the mip levels */
229 for (auto i : IndexRange(SPHERE_PROBE_MIPMAP_LEVELS - 1)) {
230 convolve_lod_ = i;
231 convolve_input_ = probes_tx_.mip_view(i);
232 convolve_output_ = probes_tx_.mip_view(i + 1);
233 probe_read_coord_ = atlas_coord.as_write_coord(i);
234 probe_write_coord_ = atlas_coord.as_write_coord(i + 1);
235 int out_mip_res = probe_write_coord_.extent;
236 dispatch_probe_convolve_ = int3(
238 instance_.manager->submit(convolve_ps_);
239 }
240
241 if (extract_spherical_harmonics) {
242 instance_.manager->submit(sum_sh_ps_);
243 instance_.manager->submit(sum_sun_ps_);
244 /* All volume probe that needs to composite the world probe need to be updated. */
246 }
247
248 /* Sync with atlas usage for shading. */
250}
251
253{
254 Vector<SphereProbe *> probe_active;
255 for (auto &probe : instance_.light_probes.sphere_map_.values()) {
256 /* Last slot is reserved for the world probe. */
257 if (lightprobe_sphere_count_ >= SPHERE_PROBE_MAX - 1) {
258 break;
259 }
260 if (!probe.use_for_render) {
261 continue;
262 }
263 /* TODO(fclem): Culling. */
264 probe_active.append(&probe);
265 }
266
267 /* Stable sorting of probes. */
268 std::sort(
269 probe_active.begin(), probe_active.end(), [](const SphereProbe *a, const SphereProbe *b) {
270 if (a->volume != b->volume) {
271 /* Smallest first. */
272 return a->volume < b->volume;
273 }
274 /* Volumes are identical. Any arbitrary criteria can be used to sort them.
275 * Use position to avoid unstable result caused by depsgraph non deterministic eval
276 * order. This could also become a priority parameter. */
277 float3 _a = a->location;
278 float3 _b = b->location;
279 if (_a.x != _b.x) {
280 return _a.x < _b.x;
281 }
282 if (_a.y != _b.y) {
283 return _a.y < _b.y;
284 }
285 if (_a.z != _b.z) {
286 return _a.z < _b.z;
287 }
288 /* Fallback to memory address, since there's no good alternative. */
289 return a < b;
290 });
291
292 /* Push all sorted data to the UBO. */
293 int probe_id = 0;
294 for (auto &probe : probe_active) {
295 data_buf_[probe_id++] = *probe;
296 }
297 /* Add world probe at the end. */
298 data_buf_[probe_id++] = instance_.light_probes.world_sphere_;
299 /* Tag the end of the array. */
300 if (probe_id < SPHERE_PROBE_MAX) {
301 data_buf_[probe_id].atlas_coord.layer = -1;
302 }
303 data_buf_.push_update();
304
305 lightprobe_sphere_count_ = probe_id;
306 dispatch_probe_select_.x = divide_ceil_u(lightprobe_sphere_count_,
308 instance_.manager->submit(select_ps_);
309
310 sync_display(probe_active);
311}
312
313void SphereProbeModule::sync_display(Vector<SphereProbe *> &probe_active)
314{
315 do_display_draw_ = false;
316 if (!DRW_state_draw_support()) {
317 return;
318 }
319
320 int display_index = 0;
321 for (int i : probe_active.index_range()) {
322 if (probe_active[i]->viewport_display) {
323 SphereProbeDisplayData &sph_data = display_data_buf_.get_or_resize(display_index++);
324 sph_data.probe_index = i;
325 sph_data.display_size = probe_active[i]->viewport_display_size;
326 }
327 }
328
329 if (display_index == 0) {
330 return;
331 }
332 do_display_draw_ = true;
333 display_data_buf_.resize(display_index);
334 display_data_buf_.push_update();
335}
336
337void SphereProbeModule::viewport_draw(View &view, GPUFrameBuffer *view_fb)
338{
339 if (!do_display_draw_) {
340 return;
341 }
342
343 viewport_display_ps_.init();
344 viewport_display_ps_.state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH |
346 viewport_display_ps_.framebuffer_set(&view_fb);
347 viewport_display_ps_.shader_set(instance_.shaders.static_shader_get(DISPLAY_PROBE_SPHERE));
348 viewport_display_ps_.bind_resources(*this);
349 viewport_display_ps_.bind_ssbo("display_data_buf", display_data_buf_);
350 viewport_display_ps_.draw_procedural(GPU_PRIM_TRIS, 1, display_data_buf_.size() * 6);
351
352 instance_.manager->submit(viewport_display_ps_, view);
353}
354
357} // 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
@ GPU_PRIM_TRIS
void GPU_memory_barrier(eGPUBarrier barrier)
Definition gpu_state.cc:374
@ 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_texture_clear(GPUTexture *texture, eGPUDataFormat data_format, const void *data)
@ GPU_DATA_FLOAT
eGPUTextureUsage
@ GPU_TEXTURE_USAGE_SHADER_READ
@ GPU_TEXTURE_USAGE_SHADER_WRITE
@ GPU_TEXTURE_USAGE_ATTACHMENT
void GPU_texture_mipmap_mode(GPUTexture *texture, bool use_mipmap, bool use_filter)
struct GPUShader GPUShader
void append(const T &value)
void submit(PassSimple &pass, View &view)
bool ensure_mip_views(bool cube_as_array=false)
bool ensure_cube(eGPUTextureFormat format, int extent, eGPUTextureUsage usage=GPU_TEXTURE_USAGE_GENERAL, float *data=nullptr, int mip_len=1)
bool ensure_2d_array(eGPUTextureFormat format, int2 extent, int layers, eGPUTextureUsage usage=GPU_TEXTURE_USAGE_GENERAL, const float *data=nullptr, int mip_len=1)
GPUTexture * mip_view(int miplvl)
VolumeProbeModule volume_probes
bool do_lightprobe_sphere_sync() const
UniformDataModule uniform_data
LightProbeModule light_probes
void bind_resources(PassType &pass)
GPUShader * static_shader_get(eShaderType shader_type)
UniformBuffer< LightData > sunlight
local_group_size(16, 16) .push_constant(Type b
bool DRW_state_draw_support()
void DRW_viewport_request_redraw()
@ DRW_STATE_WRITE_DEPTH
Definition draw_state.hh:29
@ DRW_STATE_WRITE_COLOR
Definition draw_state.hh:30
@ DRW_STATE_DEPTH_LESS_EQUAL
Definition draw_state.hh:38
@ 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
local_group_size(SPHERE_PROBE_GROUP_SIZE, SPHERE_PROBE_GROUP_SIZE) .additional_info("eevee_shared") .push_constant(Type smooth(Type::VEC3, "P") .smooth(Type SphereProbeDisplayData
VecBase< T, Size > divide_ceil(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
VecBase< int32_t, 2 > int2
VecBase< int32_t, 3 > int3
struct SceneEEVEE eevee