|
@@ -0,0 +1,441 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ *
|
|
|
|
|
+ * RenderPipeline
|
|
|
|
|
+ *
|
|
|
|
|
+ * Copyright (c) 2014-2016 tobspr <[email protected]>
|
|
|
|
|
+ *
|
|
|
|
|
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
+ * of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
+ * in the Software without restriction, including without limitation the rights
|
|
|
|
|
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
+ * copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
+ * furnished to do so, subject to the following conditions:
|
|
|
|
|
+ *
|
|
|
|
|
+ * The above copyright notice and this permission notice shall be included in
|
|
|
|
|
+ * all copies or substantial portions of the Software.
|
|
|
|
|
+ *
|
|
|
|
|
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
|
+ * THE SOFTWARE.
|
|
|
|
|
+ *
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+#include "internalLightManager.h"
|
|
|
|
|
+
|
|
|
|
|
+#include <algorithm>
|
|
|
|
|
+
|
|
|
|
|
+NotifyCategoryDef(lightmgr, "");
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Constructs the light manager
|
|
|
|
|
+ * @details This constructs the light manager, initializing the light and shadow
|
|
|
|
|
+ * storage. You should set a command list and shadow manager before calling
|
|
|
|
|
+ * InternalLightManager::update. s
|
|
|
|
|
+ */
|
|
|
|
|
+InternalLightManager::InternalLightManager() {
|
|
|
|
|
+ _shadow_update_distance = 100.0;
|
|
|
|
|
+ _cmd_list = nullptr;
|
|
|
|
|
+ _shadow_manager = nullptr;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Adds a new light.
|
|
|
|
|
+ * @details This adds a new light to the list of lights. This will throw an
|
|
|
|
|
+ * error and return if the light is already attached. You may only call
|
|
|
|
|
+ * this after the ShadowManager was already set.
|
|
|
|
|
+ *
|
|
|
|
|
+ * While the light is attached, the light manager keeps a reference to it, so
|
|
|
|
|
+ * the light does not get destructed.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This also setups the shadows on the light, in case shadows are enabled.
|
|
|
|
|
+ * While a light is attached, you can not change whether it casts shadows or not.
|
|
|
|
|
+ * To do so, detach the light, change the setting, and re-add the light.
|
|
|
|
|
+ *
|
|
|
|
|
+ * In case no free light slot is available, an error will be printed and no
|
|
|
|
|
+ * action will be performed.
|
|
|
|
|
+ *
|
|
|
|
|
+ * If no shadow manager was set, an assertion will be triggered.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param light The light to add.
|
|
|
|
|
+ */
|
|
|
|
|
+void InternalLightManager::add_light(PT(RPLight) light) {
|
|
|
|
|
+ nassertv(_shadow_manager != nullptr); // Shadow manager not set yet!
|
|
|
|
|
+
|
|
|
|
|
+ // Don't attach the light in case its already attached
|
|
|
|
|
+ if (light->has_slot()) {
|
|
|
|
|
+ lightmgr_cat.error() << "could not add light because it already is attached! "
|
|
|
|
|
+ << "Detach the light first, then try it again." << endl;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Find a free slot
|
|
|
|
|
+ size_t slot;
|
|
|
|
|
+ if (!_lights.find_slot(slot)) {
|
|
|
|
|
+ lightmgr_cat.error() << "Light limit of " << MAX_LIGHT_COUNT << " reached, "
|
|
|
|
|
+ << "all light slots used!" << endl;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Reference the light because we store it, to avoid it getting destructed
|
|
|
|
|
+ // on the python side while we still work with it. The reference will be
|
|
|
|
|
+ // removed when the light gets detached.
|
|
|
|
|
+ light->ref();
|
|
|
|
|
+
|
|
|
|
|
+ // Reserve the slot
|
|
|
|
|
+ light->assign_slot(slot);
|
|
|
|
|
+ _lights.reserve_slot(slot, light);
|
|
|
|
|
+
|
|
|
|
|
+ // Setup the shadows in case the light uses them
|
|
|
|
|
+ if (light->get_casts_shadows()) {
|
|
|
|
|
+ setup_shadows(light);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Store the light on the gpu, to make sure the GPU directly knows about it.
|
|
|
|
|
+ // We could wait until the next update cycle, but then we might be one frame
|
|
|
|
|
+ // too late already.
|
|
|
|
|
+ gpu_update_light(light);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Internal method to setup shadows for a light
|
|
|
|
|
+ * @details This method gets called by the InternalLightManager::add_light method
|
|
|
|
|
+ * to setup a lights shadow sources, in case shadows are enabled on that light.
|
|
|
|
|
+ *
|
|
|
|
|
+ * It finds a slot for all shadow sources of the ilhgt, and inits the shadow
|
|
|
|
|
+ * sources as well. If no slot could be found, an error is printed an nothing
|
|
|
|
|
+ * happens.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param light The light to init the shadow sources for
|
|
|
|
|
+ */
|
|
|
|
|
+void InternalLightManager::setup_shadows(RPLight* light) {
|
|
|
|
|
+
|
|
|
|
|
+ // Init the lights shadow sources, and also call update once to make sure
|
|
|
|
|
+ // the sources are properly initialized
|
|
|
|
|
+ light->init_shadow_sources();
|
|
|
|
|
+ light->update_shadow_sources();
|
|
|
|
|
+
|
|
|
|
|
+ // Find consecutive slots, this is important for PointLights so we can just
|
|
|
|
|
+ // store the first index of the source, and get the other slots by doing
|
|
|
|
|
+ // first_index + 1, +2 and so on.
|
|
|
|
|
+ size_t base_slot;
|
|
|
|
|
+ size_t num_sources = light->get_num_shadow_sources();
|
|
|
|
|
+ if (!_shadow_sources.find_consecutive_slots(base_slot, num_sources)) {
|
|
|
|
|
+ lightmgr_cat.error() << "Failed to find slot for shadow sources! "
|
|
|
|
|
+ << "Shadow-Source limit of " << MAX_SHADOW_SOURCES
|
|
|
|
|
+ << " reached!" << endl;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Init all sources
|
|
|
|
|
+ for (int i = 0; i < num_sources; ++i) {
|
|
|
|
|
+ ShadowSource* source = light->get_shadow_source(i);
|
|
|
|
|
+
|
|
|
|
|
+ // Set the source as dirty, so it gets updated in the beginning
|
|
|
|
|
+ source->set_needs_update(true);
|
|
|
|
|
+
|
|
|
|
|
+ // Assign the slot to the source. Since we got consecutive slots, we can
|
|
|
|
|
+ // just do base_slot + N.
|
|
|
|
|
+ size_t slot = base_slot + i;
|
|
|
|
|
+ _shadow_sources.reserve_slot(slot, source);
|
|
|
|
|
+ source->set_slot(slot);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Removes a light
|
|
|
|
|
+ * @details This detaches a light. This prevents it from being rendered, and also
|
|
|
|
|
+ * cleans up all resources used by that light. If no reference is kept on the
|
|
|
|
|
+ * python side, the light will also get destructed.
|
|
|
|
|
+ *
|
|
|
|
|
+ * If the light was not previously attached with InternalLightManager::add_light,
|
|
|
|
|
+ * an error will be triggered and nothing happens.
|
|
|
|
|
+ *
|
|
|
|
|
+ * In case the light was set to cast shadows, all shadow sources are cleaned
|
|
|
|
|
+ * up, and their regions in the shadow atlas are freed.
|
|
|
|
|
+ *
|
|
|
|
|
+ * All resources used by the light in the light and shadow storage are also
|
|
|
|
|
+ * cleaned up, by emitting cleanup GPUCommands.
|
|
|
|
|
+ *
|
|
|
|
|
+ * If no shadow manager was set, an assertion will be triggered.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param light [description]
|
|
|
|
|
+ */
|
|
|
|
|
+void InternalLightManager::remove_light(PT(RPLight) light) {
|
|
|
|
|
+ nassertv(_shadow_manager != nullptr);
|
|
|
|
|
+
|
|
|
|
|
+ if (!light->has_slot()) {
|
|
|
|
|
+ lightmgr_cat.error() << "Could not detach light, light was not attached!" << endl;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Free the lights slot in the light storage
|
|
|
|
|
+ _lights.free_slot(light->get_slot());
|
|
|
|
|
+
|
|
|
|
|
+ // Tell the GPU we no longer need the lights data
|
|
|
|
|
+ gpu_remove_light(light);
|
|
|
|
|
+
|
|
|
|
|
+ // Mark the light as detached. After this call, we can not call get_slot
|
|
|
|
|
+ // anymore, so its important we do this after we unregistered the light
|
|
|
|
|
+ // from everywhere.
|
|
|
|
|
+ light->remove_slot();
|
|
|
|
|
+
|
|
|
|
|
+ // Clear shadow related stuff, in case the light casts shadows
|
|
|
|
|
+ if (light->get_casts_shadows()) {
|
|
|
|
|
+
|
|
|
|
|
+ // Free the slots of all sources, and also unregister their regions from
|
|
|
|
|
+ // the shadow atlas.
|
|
|
|
|
+ for (size_t i = 0; i < light->get_num_shadow_sources(); ++i) {
|
|
|
|
|
+ ShadowSource* source = light->get_shadow_source(i);
|
|
|
|
|
+ if (source->has_slot()) {
|
|
|
|
|
+ _shadow_sources.free_slot(source->get_slot());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (source->has_region()) {
|
|
|
|
|
+ _shadow_manager->get_atlas()->free_region(source->get_region());
|
|
|
|
|
+ source->clear_region();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Remove all sources of the light by emitting a consecutive remove command
|
|
|
|
|
+ gpu_remove_consecutive_sources(light->get_shadow_source(0),
|
|
|
|
|
+ light->get_num_shadow_sources());
|
|
|
|
|
+
|
|
|
|
|
+ // Finally remove all shadow sources. This is important in case the light
|
|
|
|
|
+ // will be re-attached. Otherwise an assertion will get triggered.
|
|
|
|
|
+ light->clear_shadow_sources();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Since we referenced the light when we stored it, we have to decrease
|
|
|
|
|
+ // the reference now. In case no reference was kept on the python side,
|
|
|
|
|
+ // the light will get destructed soon.
|
|
|
|
|
+ light->unref();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Internal method to remove consecutive sources from the GPU.
|
|
|
|
|
+ * @details This emits a GPUCommand to consecutively remove shadow sources from
|
|
|
|
|
+ * the GPU. This is called when a light gets removed, to free the space its
|
|
|
|
|
+ * shadow sources took. Its not really required, because as long as the light
|
|
|
|
|
+ * is not used, there is no reference to the sources. However, it can't hurt to
|
|
|
|
|
+ * cleanup the memory.
|
|
|
|
|
+ *
|
|
|
|
|
+ * All sources starting at first_source->get_slot() until
|
|
|
|
|
+ * first_source->get_slot() + num_sources will get cleaned up.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param first_source First source of the light
|
|
|
|
|
+ * @param num_sources Amount of consecutive sources to clear
|
|
|
|
|
+ */
|
|
|
|
|
+void InternalLightManager::gpu_remove_consecutive_sources(ShadowSource *first_source,
|
|
|
|
|
+ size_t num_sources) {
|
|
|
|
|
+ nassertv(_cmd_list != nullptr); // No command list set yet
|
|
|
|
|
+ nassertv(first_source->has_slot()); // Source has no slot!
|
|
|
|
|
+ GPUCommand cmd_remove(GPUCommand::CMD_remove_sources);
|
|
|
|
|
+ cmd_remove.push_int(first_source->get_slot());
|
|
|
|
|
+ cmd_remove.push_int(num_sources);
|
|
|
|
|
+ _cmd_list->add_command(cmd_remove);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Internal method to remove a light from the GPU.
|
|
|
|
|
+ * @details This emits a GPUCommand to clear a lights data. This sets the data
|
|
|
|
|
+ * to all zeros, marking that no light is stored anymore.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This throws an assertion in case the light is not currently attached. Be
|
|
|
|
|
+ * sure to call this before detaching the light.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param light The light to remove, must be attached.
|
|
|
|
|
+ */
|
|
|
|
|
+void InternalLightManager::gpu_remove_light(RPLight* light) {
|
|
|
|
|
+ nassertv(_cmd_list != nullptr); // No command list set yet
|
|
|
|
|
+ nassertv(light->has_slot()); // Light has no slot!
|
|
|
|
|
+ GPUCommand cmd_remove(GPUCommand::CMD_remove_light);
|
|
|
|
|
+ cmd_remove.push_int(light->get_slot());
|
|
|
|
|
+ _cmd_list->add_command(cmd_remove);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Updates a lights data on the GPU
|
|
|
|
|
+ * @details This method emits a GPUCommand to update a lights data. This can
|
|
|
|
|
+ * be used to initially store the lights data, or to update the data whenever
|
|
|
|
|
+ * the light changed.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This throws an assertion in case the light is not currently attached. Be
|
|
|
|
|
+ * sure to call this after attaching the light.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param light The light to update
|
|
|
|
|
+ */
|
|
|
|
|
+void InternalLightManager::gpu_update_light(RPLight* light) {
|
|
|
|
|
+ nassertv(_cmd_list != nullptr); // No command list set yet
|
|
|
|
|
+ nassertv(light->has_slot()); // Light has no slot!
|
|
|
|
|
+ GPUCommand cmd_update(GPUCommand::CMD_store_light);
|
|
|
|
|
+ cmd_update.push_int(light->get_slot());
|
|
|
|
|
+ light->write_to_command(cmd_update);
|
|
|
|
|
+ light->set_needs_update(false);
|
|
|
|
|
+ _cmd_list->add_command(cmd_update);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Updates a shadow source data on the GPU
|
|
|
|
|
+ * @details This emits a GPUCommand to update a given shadow source, storing all
|
|
|
|
|
+ * data of the source on the GPU. This can also be used to initially store a
|
|
|
|
|
+ * ShadowSource, since all data will be overridden.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This throws an assertion if the source has no slot yet.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param source The source to update
|
|
|
|
|
+ */
|
|
|
|
|
+void InternalLightManager::gpu_update_source(ShadowSource* source) {
|
|
|
|
|
+ nassertv(_cmd_list != nullptr); // No command list set yet
|
|
|
|
|
+ nassertv(source->has_slot()); // Source has no slot!
|
|
|
|
|
+ GPUCommand cmd_update(GPUCommand::CMD_store_source);
|
|
|
|
|
+ cmd_update.push_int(source->get_slot());
|
|
|
|
|
+ source->write_to_command(cmd_update);
|
|
|
|
|
+ _cmd_list->add_command(cmd_update);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Internal method to update all lights
|
|
|
|
|
+ * @details This is called by the main update method, and iterates over the list
|
|
|
|
|
+ * of lights. If a light is marked as dirty, it will recieve an update of its
|
|
|
|
|
+ * data and its shadow sources.
|
|
|
|
|
+ */
|
|
|
|
|
+void InternalLightManager::update_lights() {
|
|
|
|
|
+ for (auto iter = _lights.begin(); iter != _lights.end(); ++iter) {
|
|
|
|
|
+ RPLight* light = *iter;
|
|
|
|
|
+ if (light && light->get_needs_update()) {
|
|
|
|
|
+ if (light->get_casts_shadows()) {
|
|
|
|
|
+ light->update_shadow_sources();
|
|
|
|
|
+ }
|
|
|
|
|
+ gpu_update_light(light);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Compares shadow sources by their priority
|
|
|
|
|
+ * @details Returns if a has a greater priority than b. This depends on the
|
|
|
|
|
+ * resolution of the source, and also if the source has a region or not.
|
|
|
|
|
+ * This method can be passed to std::sort.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param a First source
|
|
|
|
|
+ * @param b Second source
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return true if a is more important than b, else false
|
|
|
|
|
+ */
|
|
|
|
|
+bool InternalLightManager::compare_shadow_sources(const ShadowSource* a, const ShadowSource* b) const {
|
|
|
|
|
+
|
|
|
|
|
+ // Make sure that sources which already have a region (but maybe outdated)
|
|
|
|
|
+ // come after sources which have no region at all.
|
|
|
|
|
+ if (a->has_region() != b->has_region()) {
|
|
|
|
|
+ return b->has_region();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Compare sources based on their distance to the camera
|
|
|
|
|
+ PN_stdfloat dist_a = (_camera_pos - a->get_bounds().get_center()).length_squared();
|
|
|
|
|
+ PN_stdfloat dist_b = (_camera_pos - a->get_bounds().get_center()).length_squared();
|
|
|
|
|
+
|
|
|
|
|
+ // XXX: Should also compare based on source size, so that huge sources recieve
|
|
|
|
|
+ // more updates
|
|
|
|
|
+
|
|
|
|
|
+ return dist_b > dist_a;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Internal method to update all shadow sources
|
|
|
|
|
+ * @details This updates all shadow sources which are marked dirty. It will sort
|
|
|
|
|
+ * the list of all dirty shadow sources by their resolution, take the first
|
|
|
|
|
+ * n entries, and update them. The amount of sources processed depends on the
|
|
|
|
|
+ * max_updates of the ShadowManager.
|
|
|
|
|
+ */
|
|
|
|
|
+void InternalLightManager::update_shadow_sources() {
|
|
|
|
|
+
|
|
|
|
|
+ // Find all dirty shadow sources and make a list of them
|
|
|
|
|
+ vector<ShadowSource*> sources_to_update;
|
|
|
|
|
+ for (auto iter = _shadow_sources.begin(); iter != _shadow_sources.end(); ++iter) {
|
|
|
|
|
+ ShadowSource* source = *iter;
|
|
|
|
|
+ if (source) {
|
|
|
|
|
+ const BoundingSphere& bounds = source->get_bounds();
|
|
|
|
|
+
|
|
|
|
|
+ // Check if source is in range
|
|
|
|
|
+ PN_stdfloat distance_to_camera = (_camera_pos - bounds.get_center()).length() - bounds.get_radius();
|
|
|
|
|
+ if (distance_to_camera < _shadow_update_distance) {
|
|
|
|
|
+ if (source->get_needs_update()) {
|
|
|
|
|
+ sources_to_update.push_back(source);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+
|
|
|
|
|
+ // Free regions of sources which are out of the update radius,
|
|
|
|
|
+ // to make space for other regions
|
|
|
|
|
+ if (source->has_region()) {
|
|
|
|
|
+ _shadow_manager->get_atlas()->free_region(source->get_region());
|
|
|
|
|
+ source->clear_region();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Sort the sources based on their importance, so that sources with a bigger
|
|
|
|
|
+ // priority come first. This helps to get a better packing on the shadow atlas.
|
|
|
|
|
+ // However, we also need to prioritize sources which have no current region,
|
|
|
|
|
+ // because no shadows are worse than outdated-shadows.
|
|
|
|
|
+ std::sort(sources_to_update.begin(), sources_to_update.end(), [this](const ShadowSource* a, const ShadowSource* b) {
|
|
|
|
|
+ return this->compare_shadow_sources(a, b);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Get a handle to the atlas, will be frequently used
|
|
|
|
|
+ ShadowAtlas *atlas = _shadow_manager->get_atlas();
|
|
|
|
|
+
|
|
|
|
|
+ // Free the regions of all sources which will get updated. We have to take into
|
|
|
|
|
+ // account that only a limited amount of sources can get updated per frame.
|
|
|
|
|
+ size_t update_slots = min(sources_to_update.size(),
|
|
|
|
|
+ _shadow_manager->get_num_update_slots_left());
|
|
|
|
|
+ for(size_t i = 0; i < update_slots; ++i) {
|
|
|
|
|
+ if (sources_to_update[i]->has_region()) {
|
|
|
|
|
+ atlas->free_region(sources_to_update[i]->get_region());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Find an atlas spot for all regions which are supposed to get an update
|
|
|
|
|
+ for (size_t i = 0; i < update_slots; ++i) {
|
|
|
|
|
+ ShadowSource *source = sources_to_update[i];
|
|
|
|
|
+
|
|
|
|
|
+ if(!_shadow_manager->add_update(source)) {
|
|
|
|
|
+ // In case the ShadowManager lied about the number of updates left
|
|
|
|
|
+ lightmgr_cat.error() << "ShadowManager ensured update slot, but slot is taken!" << endl;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // We have an update slot, and are guaranteed to get updated as soon
|
|
|
|
|
+ // as possible, so we can start getting a new atlas position.
|
|
|
|
|
+ size_t region_size = atlas->get_required_tiles(source->get_resolution());
|
|
|
|
|
+ LVecBase4i new_region = atlas->find_and_reserve_region(region_size, region_size);
|
|
|
|
|
+ LVecBase4 new_uv_region = atlas->region_to_uv(new_region);
|
|
|
|
|
+ source->set_region(new_region, new_uv_region);
|
|
|
|
|
+
|
|
|
|
|
+ // Mark the source as updated
|
|
|
|
|
+ source->set_needs_update(false);
|
|
|
|
|
+ gpu_update_source(source);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @brief Main update method
|
|
|
|
|
+ * @details This is the main update method of the InternalLightManager. It
|
|
|
|
|
+ * processes all lights and shadow sources, updates them, and notifies the
|
|
|
|
|
+ * GPU about it. This should be called on a per-frame basis.
|
|
|
|
|
+ *
|
|
|
|
|
+ * If the InternalLightManager was not initialized yet, an assertion is thrown.
|
|
|
|
|
+ */
|
|
|
|
|
+void InternalLightManager::update() {
|
|
|
|
|
+ nassertv(_shadow_manager != nullptr); // Not initialized yet!
|
|
|
|
|
+ nassertv(_cmd_list != nullptr); // Not initialized yet!
|
|
|
|
|
+
|
|
|
|
|
+ update_lights();
|
|
|
|
|
+ update_shadow_sources();
|
|
|
|
|
+}
|