From 68be3c6dfee6dcd4fc620a98cc1d766585fd1b4b Mon Sep 17 00:00:00 2001 From: rdb Date: Mon, 15 May 2017 16:58:56 +0200 Subject: [PATCH] More efficient light handling; sort lights by highest priority first --- panda/src/display/graphicsStateGuardian.cxx | 135 ++++++---------- panda/src/pgraph/lightAttrib.I | 49 ++++-- panda/src/pgraph/lightAttrib.cxx | 147 +++++++----------- panda/src/pgraph/lightAttrib.h | 24 ++- .../tinydisplay/tinyGraphicsStateGuardian.cxx | 54 +++---- 5 files changed, 179 insertions(+), 230 deletions(-) diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx index 294db16e6e..70dfcc80ac 100644 --- a/panda/src/display/graphicsStateGuardian.cxx +++ b/panda/src/display/graphicsStateGuardian.cxx @@ -1092,23 +1092,12 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name, const LightAttrib *target_light = (const LightAttrib *) _target_rs->get_attrib_def(LightAttrib::get_class_slot()); - int num_on_lights = target_light->get_num_on_lights(); - if (num_on_lights == 0) { + if (!target_light->has_any_on_light()) { // There are no lights at all. This means, to follow the fixed- // function model, we pretend there is an all-white ambient light. t.set_row(3, LVecBase4(1, 1, 1, 1)); } else { - for (int li = 0; li < num_on_lights; li++) { - NodePath light = target_light->get_on_light(li); - nassertr(!light.is_empty(), &LMatrix4::zeros_mat()); - Light *light_obj = light.node()->as_light(); - nassertr(light_obj != (Light *)NULL, &LMatrix4::zeros_mat()); - - if (light_obj->is_ambient_light()) { - cur_ambient_light += light_obj->get_color(); - } - } - t.set_row(3, cur_ambient_light); + t.set_row(3, target_light->get_ambient_contribution()); } return &t; } @@ -1374,28 +1363,20 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name, const LightAttrib *target_light; _target_rs->get_attrib_def(target_light); - // We want to ignore ambient lights. To that effect, iterate through the - // list of lights. In the future, we will improve this system, by also - // filtering down to the number of lights specified by the shader. - int i = 0; - - int num_on_lights = target_light->get_num_on_lights(); - for (int li = 0; li < num_on_lights; li++) { - NodePath light = target_light->get_on_light(li); + // We don't count ambient lights, which would be pretty silly to handle + // via this mechanism. + size_t num_lights = target_light->get_num_non_ambient_lights(); + if (index >= 0 && (size_t)index < num_lights) { + NodePath light = target_light->get_on_light((size_t)index); nassertr(!light.is_empty(), &LMatrix4::ident_mat()); Light *light_obj = light.node()->as_light(); - nassertr(light_obj != (Light *)NULL, &LMatrix4::ident_mat()); + nassertr(light_obj != nullptr, &LMatrix4::ident_mat()); - if (!light_obj->is_ambient_light()) { - if (i++ == index) { - return fetch_specified_member(light, name, t); - } - } - } + return fetch_specified_member(light, name, t); - // Apply the default OpenGL lights otherwise. - // Special exception for light 0, which defaults to white. - if (index == 0) { + } else if (index == 0) { + // Apply the default OpenGL lights otherwise. + // Special exception for light 0, which defaults to white. string basename = name->get_basename(); if (basename == "color" || basename == "diffuse") { t.set_row(3, _light_color_scale); @@ -1776,35 +1757,28 @@ fetch_specified_texture(Shader::ShaderTexSpec &spec, SamplerState &sampler, const LightAttrib *target_light; _target_rs->get_attrib_def(target_light); - // We want to ignore ambient lights. To that effect, iterate through - // the list of lights. In the future, we will improve this system, by - // also filtering down to the number of lights specified by the shader. - int i = 0; - - int num_on_lights = target_light->get_num_on_lights(); - for (int li = 0; li < num_on_lights; li++) { - NodePath light = target_light->get_on_light(li); - nassertr(!light.is_empty(), NULL); + // We don't count ambient lights, which would be pretty silly to handle + // via this mechanism. + size_t num_lights = target_light->get_num_non_ambient_lights(); + if (spec._stage >= 0 && (size_t)spec._stage < num_lights) { + NodePath light = target_light->get_on_light((size_t)spec._stage); + nassertr(!light.is_empty(), nullptr); Light *light_obj = light.node()->as_light(); - nassertr(light_obj != (Light *)NULL, NULL); + nassertr(light_obj != nullptr, nullptr); - if (!light_obj->is_ambient_light()) { - if (i++ == spec._stage) { - PT(Texture) tex = get_shadow_map(light); - if (tex != (Texture *)NULL) { - sampler = tex->get_default_sampler(); - } - return tex; - } + PT(Texture) tex = get_shadow_map(light); + if (tex != nullptr) { + sampler = tex->get_default_sampler(); } + return tex; + } else { + // There is no such light assigned. Bind a dummy shadow map. + PT(Texture) tex = get_dummy_shadow_map((Texture::TextureType)spec._desired_type); + if (tex != nullptr) { + sampler = tex->get_default_sampler(); + } + return tex; } - - // There is no such light assigned. Bind a dummy shadow map. - PT(Texture) tex = get_dummy_shadow_map((Texture::TextureType)spec._desired_type); - if (tex != nullptr) { - sampler = tex->get_default_sampler(); - } - return tex; } break; @@ -2645,7 +2619,7 @@ do_issue_light() { int i; int num_enabled = 0; - int num_on_lights = 0; + bool any_on_lights = false; const LightAttrib *target_light; _target_rs->get_attrib_def(target_light); @@ -2654,18 +2628,16 @@ do_issue_light() { display_cat.spam() << "do_issue_light: " << target_light << "\n"; } - if (target_light != (LightAttrib *)NULL) { - CPT(LightAttrib) new_light = target_light->filter_to_max(_max_lights); - if (display_cat.is_spam()) { - new_light->write(display_cat.spam(false), 2); - } - - num_on_lights = new_light->get_num_on_lights(); - for (int li = 0; li < num_on_lights; li++) { - NodePath light = new_light->get_on_light(li); + if (target_light != nullptr) { + // LightAttrib guarantees that the on lights are sorted, and that + // non-ambient lights come before ambient lights. + any_on_lights = target_light->has_any_on_light(); + size_t filtered_lights = min((size_t)_max_lights, target_light->get_num_non_ambient_lights()); + for (size_t li = 0; li < filtered_lights; ++li) { + NodePath light = target_light->get_on_light(li); nassertv(!light.is_empty()); Light *light_obj = light.node()->as_light(); - nassertv(light_obj != (Light *)NULL); + nassertv(light_obj != nullptr); // Lighting should be enabled before we apply any lights. if (!_lighting_enabled) { @@ -2673,23 +2645,16 @@ do_issue_light() { _lighting_enabled = true; } - if (light_obj->is_ambient_light()) { - // Ambient lights don't require specific light ids; simply add in the - // ambient contribution to the current total - cur_ambient_light += light_obj->get_color(); - - } else { - const LColor &color = light_obj->get_color(); - // Don't bother binding the light if it has no color to contribute. - if (color[0] != 0.0 || color[1] != 0.0 || color[2] != 0.0) { - enable_light(num_enabled, true); - if (num_enabled == 0) { - begin_bind_lights(); - } - - light_obj->bind(this, light, num_enabled); - num_enabled++; + const LColor &color = light_obj->get_color(); + // Don't bother binding the light if it has no color to contribute. + if (color[0] != 0.0 || color[1] != 0.0 || color[2] != 0.0) { + enable_light(num_enabled, true); + if (num_enabled == 0) { + begin_bind_lights(); } + + light_obj->bind(this, light, num_enabled); + num_enabled++; } } } @@ -2700,7 +2665,7 @@ do_issue_light() { _num_lights_enabled = num_enabled; // If no lights were set, disable lighting - if (num_on_lights == 0) { + if (!any_on_lights) { if (_color_scale_via_lighting && (_has_material_force_color || _light_color_scale != LVecBase4(1.0f, 1.0f, 1.0f, 1.0f))) { // Unless we need lighting anyway to apply a color or color scale. if (!_lighting_enabled) { @@ -2717,7 +2682,7 @@ do_issue_light() { } } else { - set_ambient_light(cur_ambient_light); + set_ambient_light(target_light->get_ambient_contribution()); } if (num_enabled != 0) { diff --git a/panda/src/pgraph/lightAttrib.I b/panda/src/pgraph/lightAttrib.I index 5281acc1a3..e876055642 100644 --- a/panda/src/pgraph/lightAttrib.I +++ b/panda/src/pgraph/lightAttrib.I @@ -15,8 +15,7 @@ * Use LightAttrib::make() to construct a new LightAttrib object. */ INLINE LightAttrib:: -LightAttrib() { - _off_all_lights = false; +LightAttrib() : _off_all_lights(false), _num_non_ambient_lights(0) { } /** @@ -27,25 +26,37 @@ INLINE LightAttrib:: LightAttrib(const LightAttrib ©) : _on_lights(copy._on_lights), _off_lights(copy._off_lights), - _off_all_lights(copy._off_all_lights) + _off_all_lights(copy._off_all_lights), + _sort_seq(UpdateSeq::old()) { } /** * Returns the number of lights that are turned on by the attribute. */ -INLINE int LightAttrib:: +INLINE size_t LightAttrib:: get_num_on_lights() const { - return _on_lights.size(); + check_sorted(); + return _sorted_on_lights.size(); +} + +/** + * Returns the number of non-ambient lights that are turned on by this + * attribute. + */ +INLINE size_t LightAttrib:: +get_num_non_ambient_lights() const { + check_sorted(); + return _num_non_ambient_lights; } /** * Returns the nth light turned on by the attribute, sorted in render order. */ INLINE NodePath LightAttrib:: -get_on_light(int n) const { - nassertr(n >= 0 && n < (int)_on_lights.size(), NodePath::fail()); - return _on_lights[n]; +get_on_light(size_t n) const { + nassertr(n < _sorted_on_lights.size(), NodePath::fail()); + return _sorted_on_lights[n]; } /** @@ -57,10 +68,18 @@ has_on_light(const NodePath &light) const { return _on_lights.find(light) != _on_lights.end(); } +/** + * Returns true if any light is turned on by the attrib, false otherwise. + */ +INLINE bool LightAttrib:: +has_any_on_light() const { + return !_on_lights.empty(); +} + /** * Returns the number of lights that are turned off by the attribute. */ -INLINE int LightAttrib:: +INLINE size_t LightAttrib:: get_num_off_lights() const { return _off_lights.size(); } @@ -70,8 +89,8 @@ get_num_off_lights() const { * (pointer) order. */ INLINE NodePath LightAttrib:: -get_off_light(int n) const { - nassertr(n >= 0 && n < (int)_off_lights.size(), NodePath::fail()); +get_off_light(size_t n) const { + nassertr(n < _off_lights.size(), NodePath::fail()); return _off_lights[n]; } @@ -104,13 +123,11 @@ is_identity() const { } /** - * Confirms whether the _filtered table is still valid. It may become invalid - * if someone calls Light::set_priority(). - * - * If the table is invalid, transparently empties it before returning. + * Makes sure that the on lights are still sorted by priority. It may become + * invalid if someone calls Light::set_priority(). */ INLINE void LightAttrib:: -check_filtered() const { +check_sorted() const { if (_sort_seq != Light::get_sort_seq()) { ((LightAttrib *)this)->sort_on_lights(); } diff --git a/panda/src/pgraph/lightAttrib.cxx b/panda/src/pgraph/lightAttrib.cxx index b80eb3b428..8bb66bcec2 100644 --- a/panda/src/pgraph/lightAttrib.cxx +++ b/panda/src/pgraph/lightAttrib.cxx @@ -29,7 +29,7 @@ int LightAttrib::_attrib_slot; CPT(RenderAttrib) LightAttrib::_all_off_attrib; TypeHandle LightAttrib::_type_handle; -// This STL Function object is used in filter_to_max(), below, to sort a list +// This STL Function object is used in sort_on_lights(), below, to sort a list // of Lights in reverse order by priority. In the case of two lights with // equal priority, the class priority is compared. class CompareLightPriorities { @@ -430,80 +430,6 @@ remove_off_light(const NodePath &light) const { return return_new(attrib); } -/** - * Returns a new LightAttrib, very much like this one, but with the number of - * on_lights reduced to be no more than max_lights. The number of off_lights - * in the new LightAttrib is undefined. - * - * The number of AmbientLights is not included in the count. All - * AmbientLights in the original attrib are always included in the result, - * regardless of the value of max_lights. - */ -CPT(LightAttrib) LightAttrib:: -filter_to_max(int max_lights) const { - if (max_lights < 0 || (int)_on_lights.size() <= max_lights) { - // Trivial case: this LightAttrib qualifies. - return this; - } - - // Since check_filtered() will clear the _filtered list if we are out of - // date, we should call it first. - check_filtered(); - - Filtered::const_iterator fi; - fi = _filtered.find(max_lights); - if (fi != _filtered.end()) { - // Easy case: we have already computed this for this particular - // LightAttrib. - return (*fi).second; - } - - // Harder case: we have to compute it now. We must choose the n lights with - // the highest priority in our list of lights. - Lights priority_lights, ambient_lights; - - // Separate the list of lights into ambient lights and other lights. - Lights::const_iterator li; - for (li = _on_lights.begin(); li != _on_lights.end(); ++li) { - const NodePath &np = (*li); - nassertr(!np.is_empty() && np.node()->as_light() != (Light *)NULL, this); - if (np.node()->is_ambient_light()) { - ambient_lights.push_back(np); - } else { - priority_lights.push_back(np); - } - } - - // This sort function uses the STL function object defined above. - sort(priority_lights.begin(), priority_lights.end(), - CompareLightPriorities()); - - // Now lop off all of the lights after the first max_lights. - if ((int)priority_lights.size() > max_lights) { - priority_lights.erase(priority_lights.begin() + max_lights, - priority_lights.end()); - } - - // Put the ambient lights back into the list. - for (li = ambient_lights.begin(); li != ambient_lights.end(); ++li) { - priority_lights.push_back(*li); - } - - // And re-sort the ov_set into its proper order. - priority_lights.sort(); - - // Now create a new attrib reflecting these lights. - PT(LightAttrib) attrib = new LightAttrib; - attrib->_on_lights.swap(priority_lights); - - CPT(RenderAttrib) new_attrib = return_new(attrib); - - // Finally, record this newly-created attrib in the map for next time. - CPT(LightAttrib) light_attrib = (const LightAttrib *)new_attrib.p(); - ((LightAttrib *)this)->_filtered[max_lights] = light_attrib; - return light_attrib; -} - /** * Returns the most important light (that is, the light with the highest * priority) in the LightAttrib, excluding any ambient lights. Returns an @@ -511,22 +437,35 @@ filter_to_max(int max_lights) const { */ NodePath LightAttrib:: get_most_important_light() const { - NodePath best; + check_sorted(); - CompareLightPriorities compare; + if (_num_non_ambient_lights > 0) { + return _sorted_on_lights[0]; + } else { + return NodePath(); + } +} + +/** + * Returns the total contribution of all the ambient lights. + */ +LColor LightAttrib:: +get_ambient_contribution() const { + check_sorted(); + + LVecBase4 total(0); Lights::const_iterator li; - for (li = _on_lights.begin(); li != _on_lights.end(); ++li) { + li = _sorted_on_lights.begin() + _num_non_ambient_lights; + for (; li != _sorted_on_lights.end(); ++li) { const NodePath &np = (*li); - nassertr(!np.is_empty() && np.node()->as_light() != (Light *)NULL, NodePath()); - if (!np.node()->is_ambient_light()) { - if (best.is_empty() || compare(np, best)) { - best = np; - } - } + Light *light = np.node()->as_light(); + nassertd(light != nullptr && light->is_ambient_light()) continue; + + total += light->get_color(); } - return best; + return total; } /** @@ -869,13 +808,42 @@ get_auto_shader_attrib_impl(const RenderState *state) const { } /** - * This is patterned after TextureAttrib::sort_on_stages(), but since lights - * don't actually require sorting, this only empties the _filtered map. + * Makes sure the lights are sorted in order of priority. Also counts the + * number of non-ambient lights. */ void LightAttrib:: sort_on_lights() { _sort_seq = Light::get_sort_seq(); - _filtered.clear(); + + // Separate the list of lights into ambient lights and other lights. + _sorted_on_lights.clear(); + OrderedLights ambient_lights; + + Lights::const_iterator li; + for (li = _on_lights.begin(); li != _on_lights.end(); ++li) { + const NodePath &np = (*li); + nassertd(!np.is_empty() && np.node()->as_light() != nullptr) continue; + + if (!np.node()->is_ambient_light()) { + _sorted_on_lights.push_back(np); + } else { + ambient_lights.push_back(np); + } + } + + // Remember how many lights were non-ambient lights, which makes it easier + // to traverse through the list of non-ambient lights. + _num_non_ambient_lights = _sorted_on_lights.size(); + + // This sort function uses the STL function object defined above. + sort(_sorted_on_lights.begin(), _sorted_on_lights.end(), + CompareLightPriorities()); + + // Now insert the ambient lights back at the end. We don't really care + // about their relative priorities, because their contribution will simply + // be summed up in the end anyway. + _sorted_on_lights.insert(_sorted_on_lights.end(), + ambient_lights.begin(), ambient_lights.end()); } /** @@ -1085,4 +1053,7 @@ fillin(DatagramIterator &scan, BamReader *manager) { aux->_num_on_lights = scan.get_uint16(); manager->read_pointers(scan, aux->_num_on_lights); } + + _sorted_on_lights.clear(); + _sort_seq = UpdateSeq::old(); } diff --git a/panda/src/pgraph/lightAttrib.h b/panda/src/pgraph/lightAttrib.h index 1053dac3fe..1158531bc0 100644 --- a/panda/src/pgraph/lightAttrib.h +++ b/panda/src/pgraph/lightAttrib.h @@ -67,13 +67,15 @@ PUBLISHED: static CPT(RenderAttrib) make(); static CPT(RenderAttrib) make_all_off(); - INLINE int get_num_on_lights() const; - INLINE NodePath get_on_light(int n) const; + INLINE size_t get_num_on_lights() const; + INLINE size_t get_num_non_ambient_lights() const; + INLINE NodePath get_on_light(size_t n) const; MAKE_SEQ(get_on_lights, get_num_on_lights, get_on_light); INLINE bool has_on_light(const NodePath &light) const; + INLINE bool has_any_on_light() const; - INLINE int get_num_off_lights() const; - INLINE NodePath get_off_light(int n) const; + INLINE size_t get_num_off_lights() const; + INLINE NodePath get_off_light(size_t n) const; MAKE_SEQ(get_off_lights, get_num_off_lights, get_off_light); INLINE bool has_off_light(const NodePath &light) const; INLINE bool has_all_off() const; @@ -85,8 +87,11 @@ PUBLISHED: CPT(RenderAttrib) add_off_light(const NodePath &light) const; CPT(RenderAttrib) remove_off_light(const NodePath &light) const; - CPT(LightAttrib) filter_to_max(int max_lights) const; NodePath get_most_important_light() const; + LColor get_ambient_contribution() const; + + MAKE_SEQ_PROPERTY(on_lights, get_num_on_lights, get_on_light); + MAKE_SEQ_PROPERTY(off_lights, get_num_off_lights, get_off_light); public: virtual void output(ostream &out) const; @@ -100,7 +105,7 @@ protected: virtual CPT(RenderAttrib) get_auto_shader_attrib_impl(const RenderState *state) const; private: - INLINE void check_filtered() const; + INLINE void check_sorted() const; void sort_on_lights(); private: @@ -108,8 +113,11 @@ private: Lights _on_lights, _off_lights; bool _off_all_lights; - typedef pmap< int, CPT(LightAttrib) > Filtered; - Filtered _filtered; + // These are sorted in descending order of priority, with the ambient lights + // sorted last. + typedef pvector OrderedLights; + OrderedLights _sorted_on_lights; + size_t _num_non_ambient_lights; UpdateSeq _sort_seq; diff --git a/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx b/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx index db600e14de..f757b35a22 100644 --- a/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx +++ b/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx @@ -1734,11 +1734,7 @@ release_texture(TextureContext *tc) { */ void TinyGraphicsStateGuardian:: do_issue_light() { - // Initialize the current ambient light total and newly enabled light list - LColor cur_ambient_light(0.0f, 0.0f, 0.0f, 0.0f); - int num_enabled = 0; - int num_on_lights = 0; const LightAttrib *target_light = DCAST(LightAttrib, _target_rs->get_attrib_def(LightAttrib::get_class_slot())); if (display_cat.is_spam()) { @@ -1750,43 +1746,35 @@ do_issue_light() { clear_light_state(); // Now, assign new lights. - if (target_light != (LightAttrib *)NULL) { - CPT(LightAttrib) new_light = target_light->filter_to_max(_max_lights); - if (display_cat.is_spam()) { - new_light->write(display_cat.spam(false), 2); - } - - num_on_lights = new_light->get_num_on_lights(); - for (int li = 0; li < num_on_lights; li++) { - NodePath light = new_light->get_on_light(li); - nassertv(!light.is_empty()); - Light *light_obj = light.node()->as_light(); - nassertv(light_obj != (Light *)NULL); - + if (target_light != nullptr) { + if (target_light->has_any_on_light()) { _lighting_enabled = true; _c->lighting_enabled = true; + } - if (light_obj->get_type() == AmbientLight::get_class_type()) { - // Accumulate all of the ambient lights together into one. - cur_ambient_light += light_obj->get_color(); + size_t filtered_lights = min((size_t)_max_lights, target_light->get_num_non_ambient_lights()); + for (size_t li = 0; li < filtered_lights; ++li) { + NodePath light = target_light->get_on_light(li); + nassertv(!light.is_empty()); + Light *light_obj = light.node()->as_light(); + nassertv(light_obj != nullptr); - } else { - // Other kinds of lights each get their own GLLight object. - light_obj->bind(this, light, num_enabled); - num_enabled++; + // Other kinds of lights each get their own GLLight object. + light_obj->bind(this, light, num_enabled); + num_enabled++; - // Handle the diffuse color here, since all lights have this property. - GLLight *gl_light = _c->first_light; - nassertv(gl_light != NULL); - const LColor &diffuse = light_obj->get_color(); - gl_light->diffuse.v[0] = diffuse[0]; - gl_light->diffuse.v[1] = diffuse[1]; - gl_light->diffuse.v[2] = diffuse[2]; - gl_light->diffuse.v[3] = diffuse[3]; - } + // Handle the diffuse color here, since all lights have this property. + GLLight *gl_light = _c->first_light; + nassertv(gl_light != NULL); + const LColor &diffuse = light_obj->get_color(); + gl_light->diffuse.v[0] = diffuse[0]; + gl_light->diffuse.v[1] = diffuse[1]; + gl_light->diffuse.v[2] = diffuse[2]; + gl_light->diffuse.v[3] = diffuse[3]; } } + LColor cur_ambient_light = target_light->get_ambient_contribution(); _c->ambient_light_model.v[0] = cur_ambient_light[0]; _c->ambient_light_model.v[1] = cur_ambient_light[1]; _c->ambient_light_model.v[2] = cur_ambient_light[2];