/** * PANDA 3D SOFTWARE * Copyright (c) Carnegie Mellon University. All rights reserved. * * All use of this software is subject to the terms of the revised BSD * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * * @file renderEffects.cxx * @author drose * @date 2002-03-14 */ #include "renderEffects.h" #include "billboardEffect.h" #include "decalEffect.h" #include "compassEffect.h" #include "polylightEffect.h" #include "showBoundsEffect.h" #include "config_pgraph.h" #include "bamReader.h" #include "bamWriter.h" #include "datagramIterator.h" #include "indent.h" #include "compareTo.h" #include "lightReMutexHolder.h" #include "lightMutexHolder.h" #include "thread.h" #include LightReMutex *RenderEffects::_states_lock = nullptr; RenderEffects::States *RenderEffects::_states = nullptr; CPT(RenderEffects) RenderEffects::_empty_state; TypeHandle RenderEffects::_type_handle; /** * Actually, this could be a private constructor, since no one inherits from * RenderEffects, but gcc gives us a spurious warning if all constructors are * private. */ RenderEffects:: RenderEffects() : _lock("RenderEffects") { if (_states == nullptr) { init_states(); } _saved_entry = _states->end(); _flags = 0; } /** * The destructor is responsible for removing the RenderEffects from the * global set if it is there. */ RenderEffects:: ~RenderEffects() { // Remove the deleted RenderEffects object from the global pool. LightReMutexHolder holder(*_states_lock); // unref() should have cleared this. nassertv(_saved_entry == _states->end()); } /** * Returns true if all of the effects in this set can safely be transformed, * and therefore the complete set can be transformed, by calling xform(). */ bool RenderEffects:: safe_to_transform() const { Effects::const_iterator ai; for (ai = _effects.begin(); ai != _effects.end(); ++ai) { const Effect &effect = (*ai); if (!effect._effect->safe_to_transform()) { return false; } } return true; } /** * Preprocesses the accumulated transform that is about to be applied to (or * through) this node due to a flatten operation. The returned value will be * used instead. */ CPT(TransformState) RenderEffects:: prepare_flatten_transform(const TransformState *net_transform) const { CPT(TransformState) result = net_transform; Effects::const_iterator ai; for (ai = _effects.begin(); ai != _effects.end(); ++ai) { const Effect &effect = (*ai); result = effect._effect->prepare_flatten_transform(result); } return result; } /** * Returns true if all of the effects in this set can safely be shared with a * sibling node that has the exact same set of effects, or false if this would * be bad for any of the effects. */ bool RenderEffects:: safe_to_combine() const { Effects::const_iterator ai; for (ai = _effects.begin(); ai != _effects.end(); ++ai) { const Effect &effect = (*ai); if (!effect._effect->safe_to_combine()) { return false; } } return true; } /** * Returns a new RenderEffects transformed by the indicated matrix. */ CPT(RenderEffects) RenderEffects:: xform(const LMatrix4 &mat) const { if (is_empty()) { return this; } RenderEffects *new_state = new RenderEffects; std::back_insert_iterator result = std::back_inserter(new_state->_effects); Effects::const_iterator ai; for (ai = _effects.begin(); ai != _effects.end(); ++ai) { const Effect &effect = (*ai); Effect new_effect(effect); new_effect._effect = effect._effect->xform(mat); *result = new_effect; ++result; } return return_new(new_state); } /** * Provides an arbitrary ordering among all unique RenderEffects, so we can * store the essentially different ones in a big set and throw away the rest. * * This method is not needed outside of the RenderEffects class because all * equivalent RenderEffects objects are guaranteed to share the same pointer; * thus, a pointer comparison is always sufficient. */ bool RenderEffects:: operator < (const RenderEffects &other) const { // We must compare all the properties of the effects, not just the type; // thus, we compare them one at a time using compare_to(). return lexicographical_compare(_effects.begin(), _effects.end(), other._effects.begin(), other._effects.end(), CompareTo()); } /** * Searches for an effect with the indicated type in the state, and returns * its index if it is found, or -1 if it is not. */ int RenderEffects:: find_effect(TypeHandle type) const { Effects::const_iterator ai = _effects.find(Effect(type)); if (ai == _effects.end()) { return -1; } return ai - _effects.begin(); } /** * Returns a RenderEffects with no effects set. */ CPT(RenderEffects) RenderEffects:: make_empty() { // The empty state is asked for so often, we make it a special case and // store a pointer forever once we find it the first time. if (_empty_state == nullptr) { RenderEffects *state = new RenderEffects; _empty_state = return_new(state); } return _empty_state; } /** * Returns a RenderEffects with one effect set. */ CPT(RenderEffects) RenderEffects:: make(const RenderEffect *effect) { RenderEffects *state = new RenderEffects; state->_effects.reserve(1); state->_effects.insert(Effect(effect)); return return_new(state); } /** * Returns a RenderEffects with two effects set. */ CPT(RenderEffects) RenderEffects:: make(const RenderEffect *effect1, const RenderEffect *effect2) { RenderEffects *state = new RenderEffects; state->_effects.reserve(2); state->_effects.push_back(Effect(effect1)); state->_effects.push_back(Effect(effect2)); state->_effects.sort(); return return_new(state); } /** * Returns a RenderEffects with three effects set. */ CPT(RenderEffects) RenderEffects:: make(const RenderEffect *effect1, const RenderEffect *effect2, const RenderEffect *effect3) { RenderEffects *state = new RenderEffects; state->_effects.reserve(2); state->_effects.push_back(Effect(effect1)); state->_effects.push_back(Effect(effect2)); state->_effects.push_back(Effect(effect3)); state->_effects.sort(); return return_new(state); } /** * Returns a RenderEffects with four effects set. */ CPT(RenderEffects) RenderEffects:: make(const RenderEffect *effect1, const RenderEffect *effect2, const RenderEffect *effect3, const RenderEffect *effect4) { RenderEffects *state = new RenderEffects; state->_effects.reserve(2); state->_effects.push_back(Effect(effect1)); state->_effects.push_back(Effect(effect2)); state->_effects.push_back(Effect(effect3)); state->_effects.push_back(Effect(effect4)); state->_effects.sort(); return return_new(state); } /** * Returns a new RenderEffects object that represents the same as the source * state, with the new RenderEffect added. If there is already a RenderEffect * with the same type, it is replaced. */ CPT(RenderEffects) RenderEffects:: add_effect(const RenderEffect *effect) const { RenderEffects *new_state = new RenderEffects; std::back_insert_iterator result = std::back_inserter(new_state->_effects); Effect new_effect(effect); Effects::const_iterator ai = _effects.begin(); while (ai != _effects.end() && (*ai) < new_effect) { *result = *ai; ++ai; ++result; } *result = new_effect; ++result; if (ai != _effects.end() && !(new_effect < (*ai))) { // At this point we know: !((*ai) < new_effect) && !(new_effect < (*ai)) // which means (*ai) == new_effect--so we should leave it out, to avoid // duplicating effects in the set. ++ai; } while (ai != _effects.end()) { *result = *ai; ++ai; ++result; } return return_new(new_state); } /** * Returns a new RenderEffects object that represents the same as the source * state, with the indicated RenderEffect removed. */ CPT(RenderEffects) RenderEffects:: remove_effect(TypeHandle type) const { RenderEffects *new_state = new RenderEffects; std::back_insert_iterator result = std::back_inserter(new_state->_effects); Effects::const_iterator ai = _effects.begin(); while (ai != _effects.end()) { if ((*ai)._type != type) { *result = *ai; ++result; } ++ai; } return return_new(new_state); } /** * Looks for a RenderEffect of the indicated type in the state, and returns it * if it is found, or NULL if it is not. */ const RenderEffect *RenderEffects:: get_effect(TypeHandle type) const { Effects::const_iterator ai; ai = _effects.find(Effect(type)); if (ai != _effects.end()) { return (*ai)._effect; } return nullptr; } /** * This method overrides ReferenceCount::unref() to check whether the * remaining reference count is entirely in the cache, and if so, it checks * for and breaks a cycle in the cache involving this object. This is * designed to prevent leaks from cyclical references within the cache. * * Note that this is not a virtual method, and cannot be because * ReferenceCount itself declares no virtual methods (it avoids the overhead * of a virtual function pointer). But this doesn't matter, because * PT(TransformState) is a template class, and will call the appropriate * method even though it is non-virtual. */ bool RenderEffects:: unref() const { LightReMutexHolder holder(*_states_lock); if (ReferenceCount::unref()) { // The reference count is still nonzero. return true; } // The reference count has just reached zero. Make sure the object is // removed from the global object pool, before anyone else finds it and // tries to ref it. ((RenderEffects *)this)->release_new(); return false; } /** * */ void RenderEffects:: output(std::ostream &out) const { out << "E:"; if (_effects.empty()) { out << "(empty)"; } else { Effects::const_iterator ai = _effects.begin(); out << "(" << (*ai)._type; ++ai; while (ai != _effects.end()) { out << " " << (*ai)._type; ++ai; } out << ")"; } } /** * */ void RenderEffects:: write(std::ostream &out, int indent_level) const { indent(out, indent_level) << _effects.size() << " effects:\n"; Effects::const_iterator ai; for (ai = _effects.begin(); ai != _effects.end(); ++ai) { const Effect &effect = (*ai); effect._effect->write(out, indent_level + 2); } } /** * Returns the total number of unique RenderEffects objects allocated in the * world. This will go up and down during normal operations. */ int RenderEffects:: get_num_states() { if (_states == nullptr) { return 0; } LightReMutexHolder holder(*_states_lock); return _states->size(); } /** * Lists all of the RenderEffects in the cache to the output stream, one per * line. This can be quite a lot of output if the cache is large, so be * prepared. */ void RenderEffects:: list_states(std::ostream &out) { out << _states->size() << " states:\n"; States::const_iterator si; for (si = _states->begin(); si != _states->end(); ++si) { const RenderEffects *state = (*si); state->write(out, 2); } } /** * Ensures that the cache is still stored in sorted order. Returns true if * so, false if there is a problem (which implies someone has modified one of * the supposedly-const RenderEffects objects). */ bool RenderEffects:: validate_states() { if (_states->empty()) { return true; } LightReMutexHolder holder(*_states_lock); States::const_iterator si = _states->begin(); States::const_iterator snext = si; ++snext; while (snext != _states->end()) { if (!(*(*si) < *(*snext))) { pgraph_cat.error() << "RenderEffects out of order!\n"; (*si)->write(pgraph_cat.error(false), 2); (*snext)->write(pgraph_cat.error(false), 2); return false; } if ((*(*snext) < *(*si))) { pgraph_cat.error() << "RenderEffects::operator < not defined properly!\n"; pgraph_cat.error(false) << "a < b: " << (*(*si) < *(*snext)) << "\n"; pgraph_cat.error(false) << "b < a: " << (*(*snext) < *(*si)) << "\n"; (*si)->write(pgraph_cat.error(false), 2); (*snext)->write(pgraph_cat.error(false), 2); return false; } si = snext; ++snext; } return true; } /** * Calls cull_callback() on all effects. You may check has_cull_callback() * first to see if any effects define this method to do anything useful. */ void RenderEffects:: cull_callback(CullTraverser *trav, CullTraverserData &data, CPT(TransformState) &node_transform, CPT(RenderState) &node_state) const { Effects::const_iterator ei; for (ei = _effects.begin(); ei != _effects.end(); ++ei) { (*ei)._effect->cull_callback(trav, data, node_transform, node_state); } } /** * Calls adjust_transform() on all effects. You may check * has_adjust_transform() first to see if any effects define this method to do * anything useful. * * The order in which the individual effects are applied is not defined, so if * more than one effect applies a change to the transform on any particular * node, you might get indeterminate results. */ void RenderEffects:: adjust_transform(CPT(TransformState) &net_transform, CPT(TransformState) &node_transform, const PandaNode *node) const { Effects::const_iterator ei; for (ei = _effects.begin(); ei != _effects.end(); ++ei) { (*ei)._effect->adjust_transform(net_transform, node_transform, node); } } /** * Make sure the global _states map is allocated. This only has to be done * once. We could make this map static, but then we run into problems if * anyone creates a RenderEffects object at static init time; it also seems to * cause problems when the Panda shared library is unloaded at application * exit time. */ void RenderEffects:: init_states() { _states = new States; // TODO: we should have a global Panda mutex to allow us to safely create // _states_lock without a startup race condition. For the meantime, this is // OK because we guarantee that this method is called at static init time, // presumably when there is still only one thread in the world. _states_lock = new LightReMutex("RenderEffects::_states_lock"); nassertv(Thread::get_current_thread() == Thread::get_main_thread()); } /** * This function is used to share a common RenderEffects pointer for all * equivalent RenderEffects objects. * * See the similar logic in RenderEffect. The idea is to create a new * RenderEffects object and pass it through this function, which will share * the pointer with a previously-created RenderEffects object if it is * equivalent. */ CPT(RenderEffects) RenderEffects:: return_new(RenderEffects *state) { nassertr(state != nullptr, state); #ifndef NDEBUG if (!state_cache) { return state; } #endif #ifndef NDEBUG if (paranoid_const) { nassertr(validate_states(), state); } #endif LightReMutexHolder holder(*_states_lock); // This should be a newly allocated pointer, not one that was used for // anything else. nassertr(state->_saved_entry == _states->end(), state); // Save the state in a local PointerTo so that it will be freed at the end // of this function if no one else uses it. CPT(RenderEffects) pt_state = state; std::pair result = _states->insert(state); if (result.second) { // The state was inserted; save the iterator and return the input state. state->_saved_entry = result.first; nassertr(_states->find(state) == state->_saved_entry, pt_state); return pt_state; } // The state was not inserted; there must be an equivalent one already in // the set. Return that one. return *(result.first); } /** * This inverse of return_new, this releases this object from the global * RenderEffects table. * * You must already be holding _states_lock before you call this method. */ void RenderEffects:: release_new() { nassertv(_states_lock->debug_is_locked()); if (_saved_entry != _states->end()) { nassertv(_states->find(this) == _saved_entry); _states->erase(_saved_entry); _saved_entry = _states->end(); } } /** * This is the private implementation of has_decal(). */ void RenderEffects:: determine_decal() { LightMutexHolder holder(_lock); if ((_flags & F_checked_decal) != 0) { // Someone else checked it first. return; } const RenderEffect *effect = get_effect(DecalEffect::get_class_type()); if (effect != nullptr) { _flags |= F_has_decal; } _flags |= F_checked_decal; } /** * This is the private implementation of has_show_bounds(). */ void RenderEffects:: determine_show_bounds() { LightMutexHolder holder(_lock); if ((_flags & F_checked_show_bounds) != 0) { // Someone else checked it first. return; } const RenderEffect *effect = get_effect(ShowBoundsEffect::get_class_type()); if (effect != nullptr) { _flags |= F_has_show_bounds; const ShowBoundsEffect *sba = DCAST(ShowBoundsEffect, effect); if (sba->get_tight()) { _flags |= F_has_show_tight_bounds; } } _flags |= F_checked_show_bounds; } /** * This is the private implementation of has_cull_callback(). */ void RenderEffects:: determine_cull_callback() { LightMutexHolder holder(_lock); if ((_flags & F_checked_cull_callback) != 0) { // Someone else checked it first. return; } _flags |= F_checked_cull_callback; Effects::const_iterator ei; for (ei = _effects.begin(); ei != _effects.end(); ++ei) { if ((*ei)._effect->has_cull_callback()) { _flags |= F_has_cull_callback; return; } } } /** * This is the private implementation of has_adjust_transform(). */ void RenderEffects:: determine_adjust_transform() { LightMutexHolder holder(_lock); if ((_flags & F_checked_adjust_transform) != 0) { // Someone else checked it first. return; } _flags |= F_checked_adjust_transform; Effects::const_iterator ei; for (ei = _effects.begin(); ei != _effects.end(); ++ei) { if ((*ei)._effect->has_adjust_transform()) { _flags |= F_has_adjust_transform; return; } } } /** * Tells the BamReader how to create objects of type RenderEffects. */ void RenderEffects:: register_with_read_factory() { BamReader::get_factory()->register_factory(get_class_type(), make_from_bam); } /** * Writes the contents of this object to the datagram for shipping out to a * Bam file. */ void RenderEffects:: write_datagram(BamWriter *manager, Datagram &dg) { TypedWritable::write_datagram(manager, dg); int num_effects = _effects.size(); nassertv(num_effects == (int)(uint16_t)num_effects); dg.add_uint16(num_effects); Effects::const_iterator ai; for (ai = _effects.begin(); ai != _effects.end(); ++ai) { const Effect &effect = (*ai); manager->write_pointer(dg, effect._effect); } } /** * Receives an array of pointers, one for each time manager->read_pointer() * was called in fillin(). Returns the number of pointers processed. */ int RenderEffects:: complete_pointers(TypedWritable **p_list, BamReader *manager) { int pi = TypedWritable::complete_pointers(p_list, manager); // Get the effect pointers. size_t i = 0; while (i < _effects.size()) { Effect &effect = _effects[i]; effect._effect = DCAST(RenderEffect, p_list[pi++]); if (effect._effect == nullptr) { // Remove this bogus RenderEffect pointer (it must have been from an // unwritable class). _effects.erase(_effects.begin() + i); } else { // Keep this good pointer, and increment. effect._type = effect._effect->get_type(); ++i; } } // Now make sure the array is properly sorted. (It won't necessarily // preserve its correct sort after being read from bam, because the sort is // based on TypeHandle indices, which can change from session to session.) _effects.sort(); nassertr(_saved_entry == _states->end(), pi); return pi; } /** * Some objects require all of their nested pointers to have been completed * before the objects themselves can be completed. If this is the case, * override this method to return true, and be careful with circular * references (which would make the object unreadable from a bam file). */ bool RenderEffects:: require_fully_complete() const { // Since we sort _states based on each RenderEffects' operator < method, // which in turn compares based on each nested RenderEffect object's // compare_to() method, some of which depend on the RenderEffect's pointers // having already been completed (e.g. CharacterJointEffect), we therefore // require each of out our nested RenderEffect objects to have been // completed before we can be completed. return true; } /** * Called immediately after complete_pointers(), this gives the object a * chance to adjust its own pointer if desired. Most objects don't change * pointers after completion, but some need to. * * Once this function has been called, the old pointer will no longer be * accessed. */ TypedWritable *RenderEffects:: change_this(TypedWritable *old_ptr, BamReader *manager) { // First, uniquify the pointer. RenderEffects *state = DCAST(RenderEffects, old_ptr); CPT(RenderEffects) pointer = return_new(state); // But now we have a problem, since we have to hold the reference count and // there's no way to return a TypedWritable while still holding the // reference count! We work around this by explicitly upping the count, and // also setting a finalize() callback to down it later. if (pointer == state) { pointer->ref(); manager->register_finalize(state); } // We have to cast the pointer back to non-const, because the bam reader // expects that. return (RenderEffects *)pointer.p(); } /** * Called by the BamReader to perform any final actions needed for setting up * the object after all objects have been read and all pointers have been * completed. */ void RenderEffects:: finalize(BamReader *) { // Unref the pointer that we explicitly reffed in change_this(). unref(); // We should never get back to zero after unreffing our own count, because // we expect to have been stored in a pointer somewhere. If we do get to // zero, it's a memory leak; the way to avoid this is to call unref_delete() // above instead of unref(), but this is dangerous to do from within a // virtual function. nassertv(get_ref_count() != 0); } /** * This function is called by the BamReader's factory when a new object of * type RenderEffects is encountered in the Bam file. It should create the * RenderEffects and extract its information from the file. */ TypedWritable *RenderEffects:: make_from_bam(const FactoryParams ¶ms) { RenderEffects *state = new RenderEffects; DatagramIterator scan; BamReader *manager; parse_params(params, scan, manager); state->fillin(scan, manager); manager->register_change_this(change_this, state); return state; } /** * This internal function is called by make_from_bam to read in all of the * relevant data from the BamFile for the new RenderEffects. */ void RenderEffects:: fillin(DatagramIterator &scan, BamReader *manager) { TypedWritable::fillin(scan, manager); int num_effects = scan.get_uint16(); // Push back a NULL pointer for each effect for now, until we get the actual // list of pointers later in complete_pointers(). _effects.reserve(num_effects); for (int i = 0; i < num_effects; i++) { manager->read_pointer(scan); _effects.push_back(Effect()); } nassertv(_saved_entry == _states->end()); }