Panda3D
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 
32 using std::endl;
33 
34 NotifyCategoryDef(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  */
118 void 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  */
235 void 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  */
255 void 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  */
274 void 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  */
294 void 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  */
309 void 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  */
332 bool 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  */
357 void 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 }
get_num_update_slots_left
Returns how many update slots are left.
Definition: shadowManager.h:61
RenderPipeline.
Definition: shadowSource.h:51
void write_to_command(GPUCommand &cmd) const
Writes the source to a GPUCommand.
Definition: shadowSource.I:225
void set_needs_update(bool flag)
Sets the update flag of the source.
Definition: shadowSource.I:131
int get_slot() const
Returns the slot of the shadow source.
Definition: shadowSource.I:49
This defines a bounding sphere, consisting of a center and a radius.
void set_needs_update(bool flag)
Sets whether the light needs an update.
Definition: rpLight.I:80
size_t get_resolution() const
Returns the resolution of the source.
Definition: shadowSource.I:152
const BoundingSphere & get_bounds() const
Returns the shadow sources bounds.
Definition: shadowSource.I:250
void set_slot(int slot)
Assigns the source a slot.
Definition: shadowSource.I:73
size_t get_num_shadow_sources() const
RenderPipeline.
Definition: rpLight.I:36
get_casts_shadows
Returns whether the light casts shadows.
Definition: rpLight.h:96
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 source has a slot.
Definition: shadowSource.I:61
const LVecBase4i & get_region() const
Returns the assigned region of the source in atlas space.
Definition: shadowSource.I:165
bool get_needs_update() const
Returns whether the light needs an update.
Definition: rpLight.I:94
InternalContainer::iterator end()
Returns an iterator to the end of the container.
bool add_update(const ShadowSource *source)
Adds a new shadow update.
bool get_needs_update() const
RenderPipeline.
Definition: shadowSource.I:38
void reserve_slot(size_t slot, T ptr)
Reserves a slot.
void add_light(PT(RPLight) light)
Adds a new light.
void update()
Main update method.
get_atlas
Returns a handle to the shadow atlas.
Definition: shadowManager.h:64
void clear_shadow_sources()
Clears all shadow source.
Definition: rpLight.I:60
bool has_slot() const
Returns whether the light has a slot.
Definition: rpLight.I:106
void ref() const
Explicitly increments the reference count.
Class which manages distributing shadow maps in an atlas.
Definition: shadowAtlas.h:41
virtual void write_to_command(GPUCommand &cmd)
Writes the light to a GPUCommand.
Definition: rpLight.cxx:60
bool has_region() const
Returns whether the source has a valid region.
Definition: shadowSource.I:142
bool find_consecutive_slots(size_t &slot, size_t num_consecutive) const
Finds free consecutive slots.
bool find_slot(size_t &slot) const
Finds a free slot.
Class for storing data to be transferred to the GPU.
Definition: gpuCommand.h:47
void remove_slot()
Removes the light slot.
Definition: rpLight.I:128
void add_command(const GPUCommand &cmd)
Pushes a GPUCommand to the command list.
void set_region(const LVecBase4i &region, const LVecBase4 &region_uv)
Sets the assigned region of the source in atlas and uv space.
Definition: shadowSource.I:194
void remove_light(PT(RPLight) light)
Removes a light.
void assign_slot(int slot)
Assigns a slot to the light.
Definition: rpLight.I:140
int get_slot() const
Returns the slot of the light.
Definition: rpLight.I:118
RenderPipeline.
Definition: rpLight.h:41
void free_slot(size_t slot)
Frees an allocated slot.
InternalLightManager()
Constructs the light manager.
virtual bool unref() const
Explicitly decrements the reference count.
InternalContainer::iterator begin()
Returns an iterator to the begin of the container.
void clear_region()
Clears the assigned region of the source.
Definition: shadowSource.I:259