Panda3D
Loading...
Searching...
No Matches
internalLightManager.cxx
1/**
2 *
3 * RenderPipeline
4 *
5 * Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE.
24 *
25 */
26
27
28#include "internalLightManager.h"
29
30#include <algorithm>
31
32using std::endl;
33
34NotifyCategoryDef(lightmgr, "");
35
36
37/**
38 * @brief Constructs the light manager
39 * @details This constructs the light manager, initializing the light and shadow
40 * storage. You should set a command list and shadow manager before calling
41 * InternalLightManager::update. s
42 */
44 _shadow_update_distance = 100.0;
45 _cmd_list = nullptr;
46 _shadow_manager = nullptr;
47}
48
49/**
50 * @brief Adds a new light.
51 * @details This adds a new light to the list of lights. This will throw an
52 * error and return if the light is already attached. You may only call
53 * this after the ShadowManager was already set.
54 *
55 * While the light is attached, the light manager keeps a reference to it, so
56 * the light does not get destructed.
57 *
58 * This also setups the shadows on the light, in case shadows are enabled.
59 * While a light is attached, you can not change whether it casts shadows or not.
60 * To do so, detach the light, change the setting, and re-add the light.
61 *
62 * In case no free light slot is available, an error will be printed and no
63 * action will be performed.
64 *
65 * If no shadow manager was set, an assertion will be triggered.
66 *
67 * @param light The light to add.
68 */
70 nassertv(_shadow_manager != nullptr); // Shadow manager not set yet!
71
72 // Don't attach the light in case its already attached
73 if (light->has_slot()) {
74 lightmgr_cat.error() << "could not add light because it already is attached! "
75 << "Detach the light first, then try it again." << endl;
76 return;
77 }
78
79 // Find a free slot
80 size_t slot;
81 if (!_lights.find_slot(slot)) {
82 lightmgr_cat.error() << "Light limit of " << MAX_LIGHT_COUNT << " reached, "
83 << "all light slots used!" << endl;
84 return;
85 }
86
87 // Reference the light because we store it, to avoid it getting destructed
88 // on the python side while we still work with it. The reference will be
89 // removed when the light gets detached.
90 light->ref();
91
92 // Reserve the slot
93 light->assign_slot(slot);
94 _lights.reserve_slot(slot, light);
95
96 // Setup the shadows in case the light uses them
97 if (light->get_casts_shadows()) {
98 setup_shadows(light);
99 }
100
101 // Store the light on the gpu, to make sure the GPU directly knows about it.
102 // We could wait until the next update cycle, but then we might be one frame
103 // too late already.
104 gpu_update_light(light);
105}
106
107/**
108 * @brief Internal method to setup shadows for a light
109 * @details This method gets called by the InternalLightManager::add_light method
110 * to setup a lights shadow sources, in case shadows are enabled on that light.
111 *
112 * It finds a slot for all shadow sources of the ilhgt, and inits the shadow
113 * sources as well. If no slot could be found, an error is printed an nothing
114 * happens.
115 *
116 * @param light The light to init the shadow sources for
117 */
118void InternalLightManager::setup_shadows(RPLight* light) {
119
120 // Init the lights shadow sources, and also call update once to make sure
121 // the sources are properly initialized
122 light->init_shadow_sources();
123 light->update_shadow_sources();
124
125 // Find consecutive slots, this is important for PointLights so we can just
126 // store the first index of the source, and get the other slots by doing
127 // first_index + 1, +2 and so on.
128 size_t base_slot;
129 size_t num_sources = light->get_num_shadow_sources();
130 if (!_shadow_sources.find_consecutive_slots(base_slot, num_sources)) {
131 lightmgr_cat.error() << "Failed to find slot for shadow sources! "
132 << "Shadow-Source limit of " << MAX_SHADOW_SOURCES
133 << " reached!" << endl;
134 return;
135 }
136
137 // Init all sources
138 for (size_t i = 0; i < num_sources; ++i) {
139 ShadowSource* source = light->get_shadow_source(i);
140
141 // Set the source as dirty, so it gets updated in the beginning
142 source->set_needs_update(true);
143
144 // Assign the slot to the source. Since we got consecutive slots, we can
145 // just do base_slot + N.
146 size_t slot = base_slot + i;
147 _shadow_sources.reserve_slot(slot, source);
148 source->set_slot(slot);
149 }
150}
151
152/**
153 * @brief Removes a light
154 * @details This detaches a light. This prevents it from being rendered, and also
155 * cleans up all resources used by that light. If no reference is kept on the
156 * python side, the light will also get destructed.
157 *
158 * If the light was not previously attached with InternalLightManager::add_light,
159 * an error will be triggered and nothing happens.
160 *
161 * In case the light was set to cast shadows, all shadow sources are cleaned
162 * up, and their regions in the shadow atlas are freed.
163 *
164 * All resources used by the light in the light and shadow storage are also
165 * cleaned up, by emitting cleanup GPUCommands.
166 *
167 * If no shadow manager was set, an assertion will be triggered.
168 *
169 * @param light [description]
170 */
172 nassertv(_shadow_manager != nullptr);
173
174 if (!light->has_slot()) {
175 lightmgr_cat.error() << "Could not detach light, light was not attached!" << endl;
176 return;
177 }
178
179 // Free the lights slot in the light storage
180 _lights.free_slot(light->get_slot());
181
182 // Tell the GPU we no longer need the lights data
183 gpu_remove_light(light);
184
185 // Mark the light as detached. After this call, we can not call get_slot
186 // anymore, so its important we do this after we unregistered the light
187 // from everywhere.
188 light->remove_slot();
189
190 // Clear shadow related stuff, in case the light casts shadows
191 if (light->get_casts_shadows()) {
192
193 // Free the slots of all sources, and also unregister their regions from
194 // the shadow atlas.
195 for (size_t i = 0; i < light->get_num_shadow_sources(); ++i) {
196 ShadowSource* source = light->get_shadow_source(i);
197 if (source->has_slot()) {
198 _shadow_sources.free_slot(source->get_slot());
199 }
200 if (source->has_region()) {
201 _shadow_manager->get_atlas()->free_region(source->get_region());
202 source->clear_region();
203 }
204 }
205
206 // Remove all sources of the light by emitting a consecutive remove command
207 gpu_remove_consecutive_sources(light->get_shadow_source(0),
208 light->get_num_shadow_sources());
209
210 // Finally remove all shadow sources. This is important in case the light
211 // will be re-attached. Otherwise an assertion will get triggered.
212 light->clear_shadow_sources();
213 }
214
215 // Since we referenced the light when we stored it, we have to decrease
216 // the reference now. In case no reference was kept on the python side,
217 // the light will get destructed soon.
218 light->unref();
219}
220
221/**
222 * @brief Internal method to remove consecutive sources from the GPU.
223 * @details This emits a GPUCommand to consecutively remove shadow sources from
224 * the GPU. This is called when a light gets removed, to free the space its
225 * shadow sources took. Its not really required, because as long as the light
226 * is not used, there is no reference to the sources. However, it can't hurt to
227 * cleanup the memory.
228 *
229 * All sources starting at first_source->get_slot() until
230 * first_source->get_slot() + num_sources will get cleaned up.
231 *
232 * @param first_source First source of the light
233 * @param num_sources Amount of consecutive sources to clear
234 */
235void InternalLightManager::gpu_remove_consecutive_sources(ShadowSource *first_source,
236 size_t num_sources) {
237 nassertv(_cmd_list != nullptr); // No command list set yet
238 nassertv(first_source->has_slot()); // Source has no slot!
239 GPUCommand cmd_remove(GPUCommand::CMD_remove_sources);
240 cmd_remove.push_int(first_source->get_slot());
241 cmd_remove.push_int(num_sources);
242 _cmd_list->add_command(cmd_remove);
243}
244
245/**
246 * @brief Internal method to remove a light from the GPU.
247 * @details This emits a GPUCommand to clear a lights data. This sets the data
248 * to all zeros, marking that no light is stored anymore.
249 *
250 * This throws an assertion in case the light is not currently attached. Be
251 * sure to call this before detaching the light.
252 *
253 * @param light The light to remove, must be attached.
254 */
255void InternalLightManager::gpu_remove_light(RPLight* light) {
256 nassertv(_cmd_list != nullptr); // No command list set yet
257 nassertv(light->has_slot()); // Light has no slot!
258 GPUCommand cmd_remove(GPUCommand::CMD_remove_light);
259 cmd_remove.push_int(light->get_slot());
260 _cmd_list->add_command(cmd_remove);
261}
262
263/**
264 * @brief Updates a lights data on the GPU
265 * @details This method emits a GPUCommand to update a lights data. This can
266 * be used to initially store the lights data, or to update the data whenever
267 * the light changed.
268 *
269 * This throws an assertion in case the light is not currently attached. Be
270 * sure to call this after attaching the light.
271 *
272 * @param light The light to update
273 */
274void InternalLightManager::gpu_update_light(RPLight* light) {
275 nassertv(_cmd_list != nullptr); // No command list set yet
276 nassertv(light->has_slot()); // Light has no slot!
277 GPUCommand cmd_update(GPUCommand::CMD_store_light);
278 cmd_update.push_int(light->get_slot());
279 light->write_to_command(cmd_update);
280 light->set_needs_update(false);
281 _cmd_list->add_command(cmd_update);
282}
283
284/**
285 * @brief Updates a shadow source data on the GPU
286 * @details This emits a GPUCommand to update a given shadow source, storing all
287 * data of the source on the GPU. This can also be used to initially store a
288 * ShadowSource, since all data will be overridden.
289 *
290 * This throws an assertion if the source has no slot yet.
291 *
292 * @param source The source to update
293 */
294void InternalLightManager::gpu_update_source(ShadowSource* source) {
295 nassertv(_cmd_list != nullptr); // No command list set yet
296 nassertv(source->has_slot()); // Source has no slot!
297 GPUCommand cmd_update(GPUCommand::CMD_store_source);
298 cmd_update.push_int(source->get_slot());
299 source->write_to_command(cmd_update);
300 _cmd_list->add_command(cmd_update);
301}
302
303/**
304 * @brief Internal method to update all lights
305 * @details This is called by the main update method, and iterates over the list
306 * of lights. If a light is marked as dirty, it will recieve an update of its
307 * data and its shadow sources.
308 */
309void InternalLightManager::update_lights() {
310 for (auto iter = _lights.begin(); iter != _lights.end(); ++iter) {
311 RPLight* light = *iter;
312 if (light && light->get_needs_update()) {
313 if (light->get_casts_shadows()) {
314 light->update_shadow_sources();
315 }
316 gpu_update_light(light);
317 }
318 }
319}
320
321/**
322 * @brief Compares shadow sources by their priority
323 * @details Returns if a has a greater priority than b. This depends on the
324 * resolution of the source, and also if the source has a region or not.
325 * This method can be passed to std::sort.
326 *
327 * @param a First source
328 * @param b Second source
329 *
330 * @return true if a is more important than b, else false
331 */
332bool InternalLightManager::compare_shadow_sources(const ShadowSource* a, const ShadowSource* b) const {
333
334 // Make sure that sources which already have a region (but maybe outdated)
335 // come after sources which have no region at all.
336 if (a->has_region() != b->has_region()) {
337 return b->has_region();
338 }
339
340 // Compare sources based on their distance to the camera
341 PN_stdfloat dist_a = (_camera_pos - a->get_bounds().get_center()).length_squared();
342 PN_stdfloat dist_b = (_camera_pos - a->get_bounds().get_center()).length_squared();
343
344 // XXX: Should also compare based on source size, so that huge sources recieve
345 // more updates
346
347 return dist_b > dist_a;
348}
349
350/**
351 * @brief Internal method to update all shadow sources
352 * @details This updates all shadow sources which are marked dirty. It will sort
353 * the list of all dirty shadow sources by their resolution, take the first
354 * n entries, and update them. The amount of sources processed depends on the
355 * max_updates of the ShadowManager.
356 */
357void InternalLightManager::update_shadow_sources() {
358
359 // Find all dirty shadow sources and make a list of them
360 std::vector<ShadowSource*> sources_to_update;
361 for (auto iter = _shadow_sources.begin(); iter != _shadow_sources.end(); ++iter) {
362 ShadowSource* source = *iter;
363 if (source) {
364 const BoundingSphere& bounds = source->get_bounds();
365
366 // Check if source is in range
367 PN_stdfloat distance_to_camera = (_camera_pos - bounds.get_center()).length() - bounds.get_radius();
368 if (distance_to_camera < _shadow_update_distance) {
369 if (source->get_needs_update()) {
370 sources_to_update.push_back(source);
371 }
372 } else {
373
374 // Free regions of sources which are out of the update radius,
375 // to make space for other regions
376 if (source->has_region()) {
377 _shadow_manager->get_atlas()->free_region(source->get_region());
378 source->clear_region();
379 }
380 }
381 }
382
383 }
384
385 // Sort the sources based on their importance, so that sources with a bigger
386 // priority come first. This helps to get a better packing on the shadow atlas.
387 // However, we also need to prioritize sources which have no current region,
388 // because no shadows are worse than outdated-shadows.
389 std::sort(sources_to_update.begin(), sources_to_update.end(), [this](const ShadowSource* a, const ShadowSource* b) {
390 return this->compare_shadow_sources(a, b);
391 });
392
393 // Get a handle to the atlas, will be frequently used
394 ShadowAtlas *atlas = _shadow_manager->get_atlas();
395
396 // Free the regions of all sources which will get updated. We have to take into
397 // account that only a limited amount of sources can get updated per frame.
398 size_t update_slots = std::min(sources_to_update.size(),
399 _shadow_manager->get_num_update_slots_left());
400 for(size_t i = 0; i < update_slots; ++i) {
401 if (sources_to_update[i]->has_region()) {
402 atlas->free_region(sources_to_update[i]->get_region());
403 }
404 }
405
406 // Find an atlas spot for all regions which are supposed to get an update
407 for (size_t i = 0; i < update_slots; ++i) {
408 ShadowSource *source = sources_to_update[i];
409
410 if(!_shadow_manager->add_update(source)) {
411 // In case the ShadowManager lied about the number of updates left
412 lightmgr_cat.error() << "ShadowManager ensured update slot, but slot is taken!" << endl;
413 break;
414 }
415
416 // We have an update slot, and are guaranteed to get updated as soon
417 // as possible, so we can start getting a new atlas position.
418 size_t region_size = atlas->get_required_tiles(source->get_resolution());
419 LVecBase4i new_region = atlas->find_and_reserve_region(region_size, region_size);
420 LVecBase4 new_uv_region = atlas->region_to_uv(new_region);
421 source->set_region(new_region, new_uv_region);
422
423 // Mark the source as updated
424 source->set_needs_update(false);
425 gpu_update_source(source);
426 }
427}
428
429/**
430 * @brief Main update method
431 * @details This is the main update method of the InternalLightManager. It
432 * processes all lights and shadow sources, updates them, and notifies the
433 * GPU about it. This should be called on a per-frame basis.
434 *
435 * If the InternalLightManager was not initialized yet, an assertion is thrown.
436 */
438 nassertv(_shadow_manager != nullptr); // Not initialized yet!
439 nassertv(_cmd_list != nullptr); // Not initialized yet!
440
441 update_lights();
442 update_shadow_sources();
443}
This defines a bounding sphere, consisting of a center and a radius.
void add_command(const GPUCommand &cmd)
Pushes a GPUCommand to the command list.
Class for storing data to be transferred to the GPU.
Definition gpuCommand.h:47
void add_light(PT(RPLight) light)
Adds a new light.
void remove_light(PT(RPLight) light)
Removes a light.
void update()
Main update method.
InternalLightManager()
Constructs the light manager.
void free_slot(size_t slot)
Frees an allocated slot.
InternalContainer::iterator begin()
Returns an iterator to the begin of the container.
bool find_slot(size_t &slot) const
Finds a free slot.
bool find_consecutive_slots(size_t &slot, size_t num_consecutive) const
Finds free consecutive slots.
InternalContainer::iterator end()
Returns an iterator to the end of the container.
void reserve_slot(size_t slot, T ptr)
Reserves a slot.
RenderPipeline.
Definition rpLight.h:41
get_casts_shadows
Returns whether the light casts shadows.
Definition rpLight.h:96
void clear_shadow_sources()
Clears all shadow source.
Definition rpLight.I:60
ShadowSource * get_shadow_source(size_t index) const
Returns the n-th shadow source.
Definition rpLight.I:49
bool has_slot() const
Returns whether the light has a slot.
Definition rpLight.I:106
virtual void write_to_command(GPUCommand &cmd)
Writes the light to a GPUCommand.
Definition rpLight.cxx:60
void assign_slot(int slot)
Assigns a slot to the light.
Definition rpLight.I:140
size_t get_num_shadow_sources() const
RenderPipeline.
Definition rpLight.I:36
int get_slot() const
Returns the slot of the light.
Definition rpLight.I:118
void remove_slot()
Removes the light slot.
Definition rpLight.I:128
void set_needs_update(bool flag)
Sets whether the light needs an update.
Definition rpLight.I:80
bool get_needs_update() const
Returns whether the light needs an update.
Definition rpLight.I:94
void ref() const
Explicitly increments the reference count.
virtual bool unref() const
Explicitly decrements the reference count.
Class which manages distributing shadow maps in an atlas.
Definition shadowAtlas.h:41
LVecBase4i find_and_reserve_region(size_t tile_width, size_t tile_height)
Finds space for a map of the given size in the atlas.
void free_region(const LVecBase4i &region)
Frees a given region.
LVecBase4 region_to_uv(const LVecBase4i &region)
Converts a tile-space region to uv space.
int get_required_tiles(size_t resolution) const
Returns the amount of tiles required to store a resolution.
bool add_update(const ShadowSource *source)
Adds a new shadow update.
get_atlas
Returns a handle to the shadow atlas.
get_num_update_slots_left
Returns how many update slots are left.
RenderPipeline.
size_t get_resolution() const
Returns the resolution of the source.
int get_slot() const
Returns the slot of the shadow source.
const LVecBase4i & get_region() const
Returns the assigned region of the source in atlas space.
bool has_region() const
Returns whether the source has a valid region.
void set_slot(int slot)
Assigns the source a slot.
void set_region(const LVecBase4i &region, const LVecBase4 &region_uv)
Sets the assigned region of the source in atlas and uv space.
void write_to_command(GPUCommand &cmd) const
Writes the source to a GPUCommand.
const BoundingSphere & get_bounds() const
Returns the shadow sources bounds.
void clear_region()
Clears the assigned region of the source.
void set_needs_update(bool flag)
Sets the update flag of the source.
bool get_needs_update() const
RenderPipeline.
bool has_slot() const
Returns whether the source has a slot.