// Filename: graphicsEngine.cxx // Created by: drose (24Feb02) // //////////////////////////////////////////////////////////////////// // // 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." // //////////////////////////////////////////////////////////////////// #include "graphicsEngine.h" #include "graphicsPipe.h" #include "parasiteBuffer.h" #include "config_gobj.h" #include "config_display.h" #include "pipeline.h" #include "drawCullHandler.h" #include "binCullHandler.h" #include "cullResult.h" #include "cullTraverser.h" #include "clockObject.h" #include "pStatTimer.h" #include "pStatClient.h" #include "pStatCollector.h" #include "mutexHolder.h" #include "lightReMutexHolder.h" #include "cullFaceAttrib.h" #include "string_utils.h" #include "geomCacheManager.h" #include "renderState.h" #include "transformState.h" #include "thread.h" #include "pipeline.h" #include "throw_event.h" #include "bamCache.h" #include "cullableObject.h" #include "geomVertexArrayData.h" #include "vertexDataSaveFile.h" #include "vertexDataBook.h" #include "vertexDataPage.h" #include "config_pgraph.h" #include "displayRegionCullCallbackData.h" #include "displayRegionDrawCallbackData.h" #if defined(WIN32) #define WINDOWS_LEAN_AND_MEAN #include #include #undef WINDOWS_LEAN_AND_MEAN #else #include #endif PT(GraphicsEngine) GraphicsEngine::_global_ptr; PStatCollector GraphicsEngine::_wait_pcollector("Wait:Thread sync"); PStatCollector GraphicsEngine::_cycle_pcollector("App:Cycle"); PStatCollector GraphicsEngine::_app_pcollector("App:Show code:General"); PStatCollector GraphicsEngine::_render_frame_pcollector("App:render_frame"); PStatCollector GraphicsEngine::_do_frame_pcollector("*:do_frame"); PStatCollector GraphicsEngine::_yield_pcollector("App:Yield"); PStatCollector GraphicsEngine::_cull_pcollector("Cull"); PStatCollector GraphicsEngine::_cull_setup_pcollector("Cull:Setup"); PStatCollector GraphicsEngine::_cull_sort_pcollector("Cull:Sort"); PStatCollector GraphicsEngine::_draw_pcollector("Draw"); PStatCollector GraphicsEngine::_sync_pcollector("Draw:Sync"); PStatCollector GraphicsEngine::_flip_pcollector("Wait:Flip"); PStatCollector GraphicsEngine::_flip_begin_pcollector("Wait:Flip:Begin"); PStatCollector GraphicsEngine::_flip_end_pcollector("Wait:Flip:End"); PStatCollector GraphicsEngine::_transform_states_pcollector("TransformStates"); PStatCollector GraphicsEngine::_transform_states_unused_pcollector("TransformStates:Unused"); PStatCollector GraphicsEngine::_render_states_pcollector("RenderStates"); PStatCollector GraphicsEngine::_render_states_unused_pcollector("RenderStates:Unused"); PStatCollector GraphicsEngine::_cyclers_pcollector("PipelineCyclers"); PStatCollector GraphicsEngine::_dirty_cyclers_pcollector("Dirty PipelineCyclers"); PStatCollector GraphicsEngine::_delete_pcollector("App:Delete"); PStatCollector GraphicsEngine::_sw_sprites_pcollector("SW Sprites"); PStatCollector GraphicsEngine::_vertex_data_small_pcollector("Vertex Data:Small"); PStatCollector GraphicsEngine::_vertex_data_independent_pcollector("Vertex Data:Independent"); PStatCollector GraphicsEngine::_vertex_data_pending_pcollector("Vertex Data:Pending"); PStatCollector GraphicsEngine::_vertex_data_resident_pcollector("Vertex Data:Resident"); PStatCollector GraphicsEngine::_vertex_data_compressed_pcollector("Vertex Data:Compressed"); PStatCollector GraphicsEngine::_vertex_data_unused_disk_pcollector("Vertex Data:Disk:Unused"); PStatCollector GraphicsEngine::_vertex_data_used_disk_pcollector("Vertex Data:Disk:Used"); // These are counted independently by the collision system; we // redefine them here so we can reset them at each frame. PStatCollector GraphicsEngine::_cnode_volume_pcollector("Collision Volumes:CollisionNode"); PStatCollector GraphicsEngine::_gnode_volume_pcollector("Collision Volumes:GeomNode"); PStatCollector GraphicsEngine::_geom_volume_pcollector("Collision Volumes:Geom"); PStatCollector GraphicsEngine::_node_volume_pcollector("Collision Volumes:PandaNode"); PStatCollector GraphicsEngine::_volume_pcollector("Collision Volumes:CollisionSolid"); PStatCollector GraphicsEngine::_test_pcollector("Collision Tests:CollisionSolid"); PStatCollector GraphicsEngine::_volume_polygon_pcollector("Collision Volumes:CollisionPolygon"); PStatCollector GraphicsEngine::_test_polygon_pcollector("Collision Tests:CollisionPolygon"); PStatCollector GraphicsEngine::_volume_plane_pcollector("Collision Volumes:CollisionPlane"); PStatCollector GraphicsEngine::_test_plane_pcollector("Collision Tests:CollisionPlane"); PStatCollector GraphicsEngine::_volume_sphere_pcollector("Collision Volumes:CollisionSphere"); PStatCollector GraphicsEngine::_test_sphere_pcollector("Collision Tests:CollisionSphere"); PStatCollector GraphicsEngine::_volume_tube_pcollector("Collision Volumes:CollisionTube"); PStatCollector GraphicsEngine::_test_tube_pcollector("Collision Tests:CollisionTube"); PStatCollector GraphicsEngine::_volume_inv_sphere_pcollector("Collision Volumes:CollisionInvSphere"); PStatCollector GraphicsEngine::_test_inv_sphere_pcollector("Collision Tests:CollisionInvSphere"); PStatCollector GraphicsEngine::_volume_geom_pcollector("Collision Volumes:CollisionGeom"); PStatCollector GraphicsEngine::_test_geom_pcollector("Collision Tests:CollisionGeom"); PStatCollector GraphicsEngine::_occlusion_untested_pcollector("Occlusion results:Not tested"); PStatCollector GraphicsEngine::_occlusion_passed_pcollector("Occlusion results:Visible"); PStatCollector GraphicsEngine::_occlusion_failed_pcollector("Occlusion results:Occluded"); PStatCollector GraphicsEngine::_occlusion_tests_pcollector("Occlusion tests"); //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::Constructor // Access: Published // Description: Creates a new GraphicsEngine object. The Pipeline is // normally left to default to NULL, which indicates the // global render pipeline, but it may be any Pipeline // you choose. //////////////////////////////////////////////////////////////////// GraphicsEngine:: GraphicsEngine(Pipeline *pipeline) : _pipeline(pipeline), _app("app"), _lock("GraphicsEngine::_lock"), _loaded_textures_lock("GraphicsEngine::_loaded_textures_lock") { if (_pipeline == (Pipeline *)NULL) { _pipeline = Pipeline::get_render_pipeline(); } _windows_sorted = true; _window_sort_index = 0; _needs_open_windows = false; set_threading_model(GraphicsThreadingModel(threading_model)); if (!_threading_model.is_default()) { display_cat.info() << "Using threading model " << _threading_model << "\n"; } _auto_flip = auto_flip; _portal_enabled = false; _flip_state = FS_flip; _singular_warning_last_frame = false; _singular_warning_this_frame = false; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::Destructor // Access: Published // Description: Gracefully cleans up the graphics engine and its // related threads and windows. //////////////////////////////////////////////////////////////////// GraphicsEngine:: ~GraphicsEngine() { #ifdef DO_PSTATS if (_app_pcollector.is_started()) { _app_pcollector.stop(); } #endif remove_all_windows(); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::set_threading_model // Access: Published // Description: Specifies how future objects created via make_gsg(), // make_buffer(), and make_window() will be threaded. // This does not affect any already-created objects. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: set_threading_model(const GraphicsThreadingModel &threading_model) { if (!Thread::is_threading_supported()) { if (!threading_model.is_single_threaded()) { display_cat.warning() << "Threading model " << threading_model << " requested but threading is not available. Ignoring.\n"; return; } } #ifndef THREADED_PIPELINE if (!threading_model.is_single_threaded()) { display_cat.warning() << "Threading model " << threading_model << " requested but multithreaded render pipelines not enabled in build.\n"; if (!allow_nonpipeline_threads) { display_cat.warning() << "Ignoring requested threading model.\n"; return; } display_cat.warning() << "Danger! Creating requested render threads anyway!\n"; } #endif // THREADED_PIPELINE MutexHolder holder(_lock); _threading_model = threading_model; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::get_threading_model // Access: Published // Description: Returns the threading model that will be applied to // future objects. See set_threading_model(). //////////////////////////////////////////////////////////////////// GraphicsThreadingModel GraphicsEngine:: get_threading_model() const { GraphicsThreadingModel result; { MutexHolder holder(_lock); result = _threading_model; } return result; } // THIS IS THE OLD CODE FOR make_gsg // PT(GraphicsStateGuardian) gsg = pipe->make_gsg(properties, share_with); //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::make_output // Access: Published // Description: Creates a new window (or buffer) and returns it. // The GraphicsEngine becomes the owner of the window, // it will persist at least until remove_window() is // called later. // // If a null pointer is supplied for the gsg, then this // routine will create a new gsg. // // This routine is only called from the app thread. //////////////////////////////////////////////////////////////////// GraphicsOutput *GraphicsEngine:: make_output(GraphicsPipe *pipe, const string &name, int sort, const FrameBufferProperties &fb_prop, const WindowProperties &win_prop, int flags, GraphicsStateGuardian *gsg, GraphicsOutput *host) { // The code here is tricky because the gsg that is passed in // might be in the uninitialized state. As a result, // pipe::make_output may not be able to tell which DirectX // capabilities or OpenGL extensions are supported and which // are not. Worse yet, it can't query the API, because that // can only be done from the draw thread, and this is the app // thread. // // So here's the workaround: this routine calls pipe::make_output, // which returns a "non-certified" window. That means that // the pipe doesn't promise that the draw thread will actually // succeed in initializing the window. This routine then calls // open_windows, which attempts to initialize the window. // // If open_windows fails to initialize the window, then // this routine will ask pipe::make_output to try again, this // time using a different set of OpenGL extensions or DirectX // capabilities. This is what the "retry" parameter to // pipe::make_output is for - it specifies, in an abstract // manner, which set of capabilties/extensions to try. // // The only problem with this design is that it requires the // engine to call open_windows, which is slow. To make // things faster, the pipe can choose to "precertify" // its creations. If it chooses to do so, this is a guarantee // that the windows it returns will not fail in open_windows. // However, most graphics pipes will only precertify if you // pass them an already-initialized gsg. Long story short, // if you want make_output to be fast, use an // already-initialized gsg. // Simplify the input parameters. int x_size = 0, y_size = 0; if (win_prop.has_size()) { x_size = win_prop.get_x_size(); y_size = win_prop.get_y_size(); } if ((x_size == 0)&&(y_size == 0)) { flags |= GraphicsPipe::BF_size_track_host; } if (host != 0) { host = host->get_host(); } // If a gsg or host was supplied, and either is not yet initialized, // then call open_windows to get both ready. If that fails, // give up on using the supplied gsg and host. if (host == (GraphicsOutput *)NULL) { if (gsg != (GraphicsStateGuardian*)NULL) { if ((!gsg->is_valid())||(gsg->needs_reset())) { open_windows(); } if ((!gsg->is_valid())||(gsg->needs_reset())) { gsg = NULL; } } } else { if ((host->get_gsg()==0)|| (!host->is_valid())|| (!host->get_gsg()->is_valid())|| (host->get_gsg()->needs_reset())) { open_windows(); } if ((host->get_gsg()==0)|| (!host->is_valid())|| (!host->get_gsg()->is_valid())|| (host->get_gsg()->needs_reset())) { host = NULL; gsg = NULL; } else { gsg = host->get_gsg(); } } // Sanity check everything. GraphicsThreadingModel threading_model = get_threading_model(); nassertr(pipe != (GraphicsPipe *)NULL, NULL); if (gsg != (GraphicsStateGuardian *)NULL) { nassertr(pipe == gsg->get_pipe(), NULL); nassertr(this == gsg->get_engine(), NULL); nassertr(threading_model.get_draw_name() == gsg->get_threading_model().get_draw_name(), NULL); } // Determine if a parasite buffer meets the user's specs. bool can_use_parasite = false; if ((host != 0)&& ((flags&GraphicsPipe::BF_require_window)==0)&& ((flags&GraphicsPipe::BF_refuse_parasite)==0)&& ((flags&GraphicsPipe::BF_can_bind_color)==0)&& ((flags&GraphicsPipe::BF_can_bind_every)==0)&& ((flags&GraphicsPipe::BF_rtt_cumulative)==0)) { if ((flags&GraphicsPipe::BF_fb_props_optional) || (host->get_fb_properties().subsumes(fb_prop))) { can_use_parasite = true; } } // If parasite buffers are preferred, then try a parasite first. // Even if prefer-parasite-buffer is set, parasites are not preferred // if the host window is too small, or if the host window does not // have the requested properties. if ((prefer_parasite_buffer) && (can_use_parasite) && (x_size <= host->get_x_size())&& (y_size <= host->get_y_size())&& (host->get_fb_properties().subsumes(fb_prop))) { ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags); buffer->_sort = sort; do_add_window(buffer, threading_model); do_add_gsg(host->get_gsg(), pipe, threading_model); return buffer; } // If force-parasite-buffer is set, we create a parasite buffer even // if it's less than ideal. You might set this if you really don't // trust your graphics driver's support for offscreen buffers. if (force_parasite_buffer && can_use_parasite) { ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags); buffer->_sort = sort; do_add_window(buffer, threading_model); do_add_gsg(host->get_gsg(), pipe, threading_model); return buffer; } // Ask the pipe to create a window. for (int retry=0; retry<10; retry++) { bool precertify = false; PT(GraphicsOutput) window = pipe->make_output(name, fb_prop, win_prop, flags, this, gsg, host, retry, precertify); if (window != (GraphicsOutput *)NULL) { window->_sort = sort; if ((precertify) && (gsg != 0) && (window->get_gsg()==gsg)) { do_add_window(window, threading_model); do_add_gsg(window->get_gsg(), pipe, threading_model); return window; } do_add_window(window, threading_model); open_windows(); if (window->is_valid()) { do_add_gsg(window->get_gsg(), pipe, threading_model); if (window->get_fb_properties().subsumes(fb_prop)) { return window; } else { if (flags & GraphicsPipe::BF_fb_props_optional) { display_cat.warning() << "FrameBufferProperties available less than requested.\n"; return window; } display_cat.error() << "Could not get requested FrameBufferProperties; abandoning window.\n"; display_cat.error(false) << " requested: " << fb_prop << "\n" << " got: " << window->get_fb_properties() << "\n"; } } else { display_cat.info() << window->get_type() << " wouldn't open; abandoning.\n"; } // No good; delete the window and keep trying. bool removed = remove_window(window); nassertr(removed, NULL); } } // Parasite buffers were not preferred, but the pipe could not // create a window to the user's specs. Try a parasite as a // last hope. if (can_use_parasite) { ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags); buffer->_sort = sort; do_add_window(buffer, threading_model); do_add_gsg(host->get_gsg(), pipe, threading_model); return buffer; } // Could not create a window to the user's specs. return NULL; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::remove_window // Access: Published // Description: Removes the indicated window or offscreen buffer from // the set of windows that will be processed when // render_frame() is called. This also closes the // window if it is open, and removes the window from its // GraphicsPipe, allowing the window to be destructed if // there are no other references to it. (However, the // window may not be actually closed until next frame, // if it is controlled by a sub-thread.) // // The return value is true if the window was removed, // false if it was not found. // // Unlike remove_all_windows(), this function does not // terminate any of the threads that may have been // started to service this window; they are left running // (since you might open a new window later on these // threads). If your intention is to clean up before // shutting down, it is better to call // remove_all_windows() then to call remove_window() one // at a time. //////////////////////////////////////////////////////////////////// bool GraphicsEngine:: remove_window(GraphicsOutput *window) { nassertr(window != NULL, false); Thread *current_thread = Thread::get_current_thread(); // First, make sure we know what this window is. PT(GraphicsOutput) ptwin = window; size_t count; { MutexHolder holder(_lock, current_thread); if (!_windows_sorted) { do_resort_windows(); } count = _windows.erase(ptwin); } if (count == 0) { // Never heard of this window. Do nothing. return false; } do_remove_window(window, current_thread); GraphicsStateGuardian *gsg = window->get_gsg(); if (gsg != (GraphicsStateGuardian *)NULL) { PreparedGraphicsObjects *pgo = gsg->get_prepared_objects(); if (pgo != (PreparedGraphicsObjects *)NULL) { // Check to see if any other still-active windows share this // context. bool any_common = false; { MutexHolder holder(_lock, current_thread); Windows::iterator wi; for (wi = _windows.begin(); wi != _windows.end() && !any_common; ++wi) { GraphicsStateGuardian *gsg2 = (*wi)->get_gsg(); if (gsg2 != (GraphicsStateGuardian *)NULL && gsg2->get_prepared_objects() == pgo) { any_common = true; } } } if (!any_common) { // If no windows still use this context, release all textures, // etc. We do this in case there is a floating pointer // somewhere keeping the GSG from destructing when its window // goes away. A leaked GSG pointer is bad enough, but there's // no reason we also need to keep around all of the objects // allocated on graphics memory. pgo->release_all(); } } } nassertr(count == 1, true); return true; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::remove_all_windows // Access: Published // Description: Removes and closes all windows from the engine. This // also cleans up and terminates any threads that have // been started to service those windows. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: remove_all_windows() { Thread *current_thread = Thread::get_current_thread(); // Let's move the _windows vector into a local copy first, and walk // through that local copy, just in case someone we call during the // loop attempts to modify _windows. I don't know what code would // be doing this, but it appeared to be happening, and this worked // around it. Windows old_windows; old_windows.swap(_windows); Windows::iterator wi; for (wi = old_windows.begin(); wi != old_windows.end(); ++wi) { GraphicsOutput *win = (*wi); nassertv(win != NULL); do_remove_window(win, current_thread); GraphicsStateGuardian *gsg = win->get_gsg(); if (gsg != (GraphicsStateGuardian *)NULL) { gsg->release_all(); } } _app.do_close(this, current_thread); _app.do_pending(this, current_thread); terminate_threads(current_thread); // It seems a safe assumption that we're about to exit the // application or otherwise shut down Panda. Although it's a bit of // a hack, since it's not really related to removing windows, this // would nevertheless be a fine time to ensure the model cache (if // any) has been flushed to disk. BamCache *cache = BamCache::get_global_ptr(); cache->flush_index(); // And, hey, let's stop the vertex paging threads, if any. VertexDataPage::stop_threads(); AsyncTaskManager::get_global_ptr()->stop_threads(); #ifdef DO_PSTATS PStatClient::get_global_pstats()->disconnect(); #endif // Well, and why not clean up all threads here? Thread::prepare_for_exit(); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::reset_all_windows // Access: Published // Description: Resets the framebuffer of the current window. This // is currently used by DirectX 8 only. It calls a // reset_window function on each active window to // release/create old/new framebuffer //////////////////////////////////////////////////////////////////// void GraphicsEngine:: reset_all_windows(bool swapchain) { Windows::iterator wi; for (wi = _windows.begin(); wi != _windows.end(); ++wi) { GraphicsOutput *win = (*wi); // if (win->is_active()) win->reset_window(swapchain); } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::is_empty // Access: Published // Description: Returns true if there are no windows or buffers // managed by the engine, false if there is at least // one. //////////////////////////////////////////////////////////////////// bool GraphicsEngine:: is_empty() const { return _windows.empty(); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::get_num_windows // Access: Published // Description: Returns the number of windows (or buffers) managed by // the engine. //////////////////////////////////////////////////////////////////// int GraphicsEngine:: get_num_windows() const { return _windows.size(); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::get_window // Access: Published // Description: Returns the nth window or buffers managed by the // engine, in sorted order. //////////////////////////////////////////////////////////////////// GraphicsOutput *GraphicsEngine:: get_window(int n) const { nassertr(n >= 0 && n < (int)_windows.size(), NULL); if (!_windows_sorted) { ((GraphicsEngine *)this)->do_resort_windows(); } return _windows[n]; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::render_frame // Access: Published // Description: Renders the next frame in all the registered windows, // and flips all of the frame buffers. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: render_frame() { Thread *current_thread = Thread::get_current_thread(); // Since this gets called every frame, we should take advantage of // the opportunity to flush the cache if necessary. BamCache *cache = BamCache::get_global_ptr(); cache->consider_flush_index(); // Anything that happens outside of GraphicsEngine::render_frame() // is deemed to be App. #ifdef DO_PSTATS _render_frame_pcollector.start(); if (_app_pcollector.is_started()) { _app_pcollector.stop(); } #endif if (_needs_open_windows) { // Make sure our buffers and windows are fully realized before we // render a frame. We do this particularly to realize our // offscreen buffers, so that we don't render a frame before the // offscreen buffers are ready (which might result in a frame // going by without some textures having been rendered). open_windows(); } ClockObject *global_clock = ClockObject::get_global_clock(); if (display_cat.is_spam()) { display_cat.spam() << "render_frame() - frame " << global_clock->get_frame_count() << "\n"; } { MutexHolder holder(_lock, current_thread); if (!_windows_sorted) { do_resort_windows(); } if (sync_flip && _flip_state != FS_flip) { do_flip_frame(current_thread); } // Are any of the windows ready to be deleted? Windows new_windows; new_windows.reserve(_windows.size()); Windows::iterator wi; for (wi = _windows.begin(); wi != _windows.end(); ++wi) { GraphicsOutput *win = (*wi); nassertv(win != NULL); if (win->get_delete_flag()) { do_remove_window(win, current_thread); } else { new_windows.push_back(win); // Let's calculate each scene's bounding volume here in App, // before we cycle the pipeline. The cull traversal will // calculate it anyway, but if we calculate it in App first // before it gets calculated in the Cull thread, it will be more // likely to stick for subsequent frames, so we won't have to // recompute it each frame. int num_drs = win->get_num_active_display_regions(); for (int i = 0; i < num_drs; ++i) { DisplayRegion *dr = win->get_active_display_region(i); if (dr != (DisplayRegion *)NULL) { NodePath camera_np = dr->get_camera(current_thread); if (!camera_np.is_empty()) { Camera *camera = DCAST(Camera, camera_np.node()); NodePath scene = camera->get_scene(); if (scene.is_empty()) { scene = camera_np.get_top(current_thread); } if (!scene.is_empty()) { scene.get_bounds(current_thread); } } } } } } _windows.swap(new_windows); // Go ahead and release any textures' ram images for textures that // were drawn in the previous frame. { MutexHolder holder2(_loaded_textures_lock); LoadedTextures::iterator lti; for (lti = _loaded_textures.begin(); lti != _loaded_textures.end(); ++lti) { LoadedTexture < = (*lti); if (lt._tex->get_image_modified() == lt._image_modified) { lt._tex->texture_uploaded(); } } _loaded_textures.clear(); } // Now it's time to do any drawing from the main frame--after all of // the App code has executed, but before we begin the next frame. _app.do_frame(this, current_thread); // Grab each thread's mutex again after all windows have flipped, // and wait for the thread to finish. { PStatTimer timer(_wait_pcollector, current_thread); Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_cv_mutex.acquire(); while (thread->_thread_state != TS_wait) { thread->_cv_done.wait(); } } } #if defined(THREADED_PIPELINE) && defined(DO_PSTATS) _cyclers_pcollector.set_level(_pipeline->get_num_cyclers()); _dirty_cyclers_pcollector.set_level(_pipeline->get_num_dirty_cyclers()); #ifdef DEBUG_THREADS if (PStatClient::is_connected()) { _pipeline->iterate_all_cycler_types(pstats_count_cycler_type, this); _pipeline->iterate_dirty_cycler_types(pstats_count_dirty_cycler_type, this); } #endif // DEBUG_THREADS #endif // THREADED_PIPELINE && DO_PSTATS GeomCacheManager::flush_level(); CullTraverser::flush_level(); RenderState::flush_level(); TransformState::flush_level(); CullableObject::flush_level(); // Now cycle the pipeline and officially begin the next frame. #ifdef THREADED_PIPELINE { PStatTimer timer(_cycle_pcollector, current_thread); _pipeline->cycle(); } #endif // THREADED_PIPELINE global_clock->tick(current_thread); if (global_clock->check_errors(current_thread)) { throw_event("clock_error"); } #ifdef DO_PSTATS PStatClient::main_tick(); // Reset our pcollectors that track data across the frame. CullTraverser::_nodes_pcollector.clear_level(); CullTraverser::_geom_nodes_pcollector.clear_level(); CullTraverser::_geoms_pcollector.clear_level(); GeomCacheManager::_geom_cache_active_pcollector.clear_level(); GeomCacheManager::_geom_cache_record_pcollector.clear_level(); GeomCacheManager::_geom_cache_erase_pcollector.clear_level(); GeomCacheManager::_geom_cache_evict_pcollector.clear_level(); GraphicsStateGuardian::init_frame_pstats(); _transform_states_pcollector.set_level(TransformState::get_num_states()); _render_states_pcollector.set_level(RenderState::get_num_states()); if (pstats_unused_states) { _transform_states_unused_pcollector.set_level(TransformState::get_num_unused_states()); _render_states_unused_pcollector.set_level(RenderState::get_num_unused_states()); } _sw_sprites_pcollector.clear_level(); _cnode_volume_pcollector.clear_level(); _gnode_volume_pcollector.clear_level(); _geom_volume_pcollector.clear_level(); _node_volume_pcollector.clear_level(); _volume_pcollector.clear_level(); _test_pcollector.clear_level(); _volume_polygon_pcollector.clear_level(); _test_polygon_pcollector.clear_level(); _volume_plane_pcollector.clear_level(); _test_plane_pcollector.clear_level(); _volume_sphere_pcollector.clear_level(); _test_sphere_pcollector.clear_level(); _volume_tube_pcollector.clear_level(); _test_tube_pcollector.clear_level(); _volume_inv_sphere_pcollector.clear_level(); _test_inv_sphere_pcollector.clear_level(); _volume_geom_pcollector.clear_level(); _test_geom_pcollector.clear_level(); _occlusion_untested_pcollector.clear_level(); _occlusion_passed_pcollector.clear_level(); _occlusion_failed_pcollector.clear_level(); _occlusion_tests_pcollector.clear_level(); if (PStatClient::is_connected()) { size_t small_buf = GeomVertexArrayData::get_small_lru()->get_total_size(); size_t independent = GeomVertexArrayData::get_independent_lru()->get_total_size(); size_t resident = VertexDataPage::get_global_lru(VertexDataPage::RC_resident)->get_total_size(); size_t compressed = VertexDataPage::get_global_lru(VertexDataPage::RC_compressed)->get_total_size(); size_t pending = VertexDataPage::get_pending_lru()->get_total_size(); VertexDataSaveFile *save_file = VertexDataPage::get_save_file(); size_t total_disk = save_file->get_total_file_size(); size_t used_disk = save_file->get_used_file_size(); _vertex_data_small_pcollector.set_level(small_buf); _vertex_data_independent_pcollector.set_level(independent); _vertex_data_pending_pcollector.set_level(pending); _vertex_data_resident_pcollector.set_level(resident); _vertex_data_compressed_pcollector.set_level(compressed); _vertex_data_unused_disk_pcollector.set_level(total_disk - used_disk); _vertex_data_used_disk_pcollector.set_level(used_disk); } #endif // DO_PSTATS GeomVertexArrayData::lru_epoch(); // Now signal all of our threads to begin their next frame. Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; if (thread->_thread_state == TS_wait) { thread->_thread_state = TS_do_frame; thread->_cv_start.notify(); } thread->_cv_mutex.release(); } // Some threads may still be drawing, so indicate that we have to // wait for those threads before we can flip. _flip_state = _auto_flip ? FS_flip : FS_draw; } // Now the lock is released. if (yield_timeslice) { // Nap for a moment to yield the timeslice, to be polite to other // running applications. PStatTimer timer(_yield_pcollector, current_thread); Thread::force_yield(); } else if (!Thread::is_true_threads()) { PStatTimer timer(_yield_pcollector, current_thread); Thread::consider_yield(); } // Anything that happens outside of GraphicsEngine::render_frame() // is deemed to be App. _app_pcollector.start(); _render_frame_pcollector.stop(); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::open_windows // Access: Published // Description: Fully opens (or closes) any windows that have // recently been requested open or closed, without // rendering any frames. It is not necessary to call // this explicitly, since windows will be automatically // opened or closed when the next frame is rendered, but // you may call this if you want your windows now // without seeing a frame go by. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: open_windows() { Thread *current_thread = Thread::get_current_thread(); MutexHolder holder(_lock, current_thread); if (!_windows_sorted) { do_resort_windows(); } // We do it twice, to allow both cull and draw to process the // window. for (int i = 0; i < 2; ++i) { _app.do_windows(this, current_thread); _app.do_pending(this, current_thread); PStatTimer timer(_wait_pcollector, current_thread); Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_cv_mutex.acquire(); while (thread->_thread_state != TS_wait) { thread->_cv_done.wait(); } thread->_thread_state = TS_do_windows; thread->_cv_start.notify(); thread->_cv_mutex.release(); } } _needs_open_windows = false; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::sync_frame // Access: Published // Description: Waits for all the threads that started drawing their // last frame to finish drawing. The windows are not // yet flipped when this returns; see also flip_frame(). // It is not usually necessary to call this explicitly, // unless you need to see the previous frame right away. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: sync_frame() { Thread *current_thread = Thread::get_current_thread(); MutexHolder holder(_lock, current_thread); if (_flip_state == FS_draw) { do_sync_frame(current_thread); } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::ready_flip // Access: Published // Description: Waits for all the threads that started drawing their // last frame to finish drawing. Returns when all threads have // actually finished drawing, as opposed to 'sync_frame' // we seems to return once all draw calls have been submitted. // Calling 'flip_frame' after this function should immediately // cause a buffer flip. This function will only work in // opengl right now, for all other graphics pipelines it will // simply return immediately. In opengl it's a bit of a hack: // it will attempt to read a single pixel from the frame buffer to // force the graphics card to finish drawing before it returns //////////////////////////////////////////////////////////////////// void GraphicsEngine:: ready_flip() { Thread *current_thread = Thread::get_current_thread(); MutexHolder holder(_lock, current_thread); if (_flip_state == FS_draw) { do_ready_flip(current_thread); } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::flip_frame // Access: Published // Description: Waits for all the threads that started drawing their // last frame to finish drawing, and then flips all the // windows. It is not usually necessary to call this // explicitly, unless you need to see the previous frame // right away. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: flip_frame() { Thread *current_thread = Thread::get_current_thread(); MutexHolder holder(_lock, current_thread); if (_flip_state != FS_flip) { do_flip_frame(current_thread); } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::extract_texture_data // Access: Published // Description: Asks the indicated GraphicsStateGuardian to retrieve // the texture memory image of the indicated texture and // store it in the texture's ram_image field. The image // can then be written to disk via Texture::write(), or // otherwise manipulated on the CPU. // // This is useful for retrieving the contents of a // texture that has been somehow generated on the // graphics card, instead of having been loaded the // normal way via Texture::read() or Texture::load(). // It is particularly useful for getting the data // associated with a compressed texture image. // // Since this requires a round-trip to the draw thread, // it may require waiting for the current thread to // finish rendering if it is called in a multithreaded // environment. However, you can call this several // consecutive times on different textures for little // additional cost. // // If the texture has not yet been loaded to the GSG in // question, it will be loaded immediately. // // The return value is true if the operation is // successful, false otherwise. //////////////////////////////////////////////////////////////////// bool GraphicsEngine:: extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg) { MutexHolder holder(_lock); string draw_name = gsg->get_threading_model().get_draw_name(); if (draw_name.empty()) { // A single-threaded environment. No problem. return gsg->extract_texture_data(tex); } else { // A multi-threaded environment. We have to wait until the draw // thread has finished its current task. WindowRenderer *wr = get_window_renderer(draw_name, 0); RenderThread *thread = (RenderThread *)wr; MutexHolder holder2(thread->_cv_mutex); while (thread->_thread_state != TS_wait) { thread->_cv_done.wait(); } // OK, now the draw thread is idle. That's really good enough for // our purposes; we don't *actually* need to make the draw thread // do the work--it's sufficient that it's not doing anything else // while we access the GSG. return gsg->extract_texture_data(tex); } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::get_global_ptr // Access: Published, Static // Description: //////////////////////////////////////////////////////////////////// GraphicsEngine *GraphicsEngine:: get_global_ptr() { if (_global_ptr == NULL) { _global_ptr = new GraphicsEngine; PandaNode::set_scene_root_func(&scene_root_func); } return _global_ptr; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::texture_uploaded // Access: Public // Description: This method is called by the GraphicsStateGuardian // after a texture has been successfully uploaded to // graphics memory. It is intended as a callback so the // texture can release its RAM image, if _keep_ram_image // is false. // // Normally, this is not called directly except by the // GraphicsStateGuardian. It will be called in the draw // thread. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: texture_uploaded(Texture *tex) { MutexHolder holder(_loaded_textures_lock); // We defer this until the end of the frame; multiple GSG's might be // rendering the texture within the same frame, and we don't want to // dump the texture image until they've all had a chance at it. _loaded_textures.push_back(LoadedTexture()); LoadedTexture < = _loaded_textures.back(); lt._tex = tex; lt._image_modified = tex->get_image_modified(); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::do_cull // Access: Public, Static // Description: Fires off a cull traversal using the indicated camera. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: do_cull(CullHandler *cull_handler, SceneSetup *scene_setup, GraphicsStateGuardian *gsg, Thread *current_thread) { DisplayRegion *dr = scene_setup->get_display_region(); PStatTimer timer(dr->get_cull_region_pcollector(), current_thread); CullTraverser *trav = dr->get_cull_traverser(); trav->set_cull_handler(cull_handler); trav->set_scene(scene_setup, gsg, dr->get_incomplete_render()); trav->set_view_frustum(NULL); if (view_frustum_cull) { // If we're to be performing view-frustum culling, determine the // bounding volume associated with the current viewing frustum. // First, we have to get the current viewing frustum, which comes // from the lens. PT(BoundingVolume) bv = scene_setup->get_lens()->make_bounds(); if (bv != (BoundingVolume *)NULL && bv->is_of_type(GeometricBoundingVolume::get_class_type())) { // Transform it into the appropriate coordinate space. PT(GeometricBoundingVolume) local_frustum; local_frustum = DCAST(GeometricBoundingVolume, bv->make_copy()); NodePath scene_parent = scene_setup->get_scene_root().get_parent(current_thread); CPT(TransformState) cull_center_transform = scene_setup->get_cull_center().get_transform(scene_parent, current_thread); local_frustum->xform(cull_center_transform->get_mat()); trav->set_view_frustum(local_frustum); } } trav->traverse(scene_setup->get_scene_root()); trav->end_traverse(); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::scene_root_func // Access: Private, Static // Description: This function is added to PandaNode::scene_root_func // to implement PandaNode::is_scene_root(). //////////////////////////////////////////////////////////////////// bool GraphicsEngine:: scene_root_func(const PandaNode *node) { return _global_ptr->is_scene_root(node); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::is_scene_root // Access: Private // Description: Returns true if the indicated node is known to be // the render root of some active DisplayRegion // associated with this GraphicsEngine, false otherwise. //////////////////////////////////////////////////////////////////// bool GraphicsEngine:: is_scene_root(const PandaNode *node) { Thread *current_thread = Thread::get_current_thread(); Windows::const_iterator wi; for (wi = _windows.begin(); wi != _windows.end(); ++wi) { GraphicsOutput *win = (*wi); if (win->is_active() && win->get_gsg()->is_active()) { int num_display_regions = win->get_num_active_display_regions(); for (int i = 0; i < num_display_regions; i++) { DisplayRegion *dr = win->get_active_display_region(i); if (dr != (DisplayRegion *)NULL) { NodePath camera = dr->get_camera(); if (camera.is_empty()) { continue; } Camera *camera_node; DCAST_INTO_R(camera_node, camera.node(), false); if (!camera_node->is_active()) { continue; } NodePath scene_root = camera_node->get_scene(); if (scene_root.is_empty()) { // If there's no explicit scene specified, use whatever scene the // camera is parented within. This is the normal and preferred // case; the use of an explicit scene is now deprecated. scene_root = camera.get_top(current_thread); } if (scene_root.node() == node) { return true; } } } } } return false; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::set_window_sort // Access: Private // Description: Changes the sort value of a particular window (or // buffer) on the GraphicsEngine. This requires // securing the mutex. // // Users shouldn't call this directly; use // GraphicsOutput::set_sort() instead. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: set_window_sort(GraphicsOutput *window, int sort) { MutexHolder holder(_lock); window->_sort = sort; _windows_sorted = false; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::cull_and_draw_together // Access: Private // Description: This is called in the cull+draw thread by individual // RenderThread objects during the frame rendering. It // culls the geometry and immediately draws it, without // first collecting it into bins. This is used when the // threading model begins with the "-" character. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: cull_and_draw_together(const GraphicsEngine::Windows &wlist, Thread *current_thread) { PStatTimer timer(_cull_pcollector, current_thread); Windows::const_iterator wi; for (wi = wlist.begin(); wi != wlist.end(); ++wi) { GraphicsOutput *win = (*wi); if (win->is_active() && win->get_gsg()->is_active()) { if (win->flip_ready()) { { PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread); win->begin_flip(); } { PStatTimer timer(GraphicsEngine::_flip_end_pcollector, current_thread); win->end_flip(); } } if (win->begin_frame(GraphicsOutput::FM_render, current_thread)) { win->clear(current_thread); int num_display_regions = win->get_num_active_display_regions(); for (int i = 0; i < num_display_regions; i++) { DisplayRegion *dr = win->get_active_display_region(i); if (dr != (DisplayRegion *)NULL) { cull_and_draw_together(win, dr, current_thread); } } win->end_frame(GraphicsOutput::FM_render, current_thread); if (_auto_flip) { if (win->flip_ready()) { { PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread); win->begin_flip(); } { PStatTimer timer(GraphicsEngine::_flip_end_pcollector, current_thread); win->end_flip(); } } } } } } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::cull_and_draw_together // Access: Private // Description: Called only from within the inner loop in // cull_and_draw_together(), above. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr, Thread *current_thread) { GraphicsStateGuardian *gsg = win->get_gsg(); nassertv(gsg != (GraphicsStateGuardian *)NULL); DisplayRegionPipelineReader *dr_reader = new DisplayRegionPipelineReader(dr, current_thread); win->change_scenes(dr_reader); gsg->prepare_display_region(dr_reader); if (dr_reader->is_any_clear_active()) { gsg->clear(dr); } PT(SceneSetup) scene_setup = setup_scene(gsg, dr_reader); if (scene_setup == (SceneSetup *)NULL) { // Never mind. } else if (dr_reader->get_object()->is_stereo()) { // Don't draw stereo DisplayRegions directly. } else if (!gsg->set_scene(scene_setup)) { // The scene or lens is inappropriate somehow. display_cat.error() << gsg->get_type() << " cannot render scene with specified lens.\n"; } else { DrawCullHandler cull_handler(gsg); if (gsg->begin_scene()) { delete dr_reader; dr_reader = NULL; CallbackObject *cbobj = dr->get_cull_callback(); if (cbobj != (CallbackObject *)NULL) { // Issue the cull callback on this DisplayRegion. DisplayRegionCullCallbackData cbdata(&cull_handler, scene_setup); cbobj->do_callback(&cbdata); // The callback has taken care of the culling. } else { // Perform the cull normally. do_cull(&cull_handler, scene_setup, gsg, current_thread); } gsg->end_scene(); } } if (dr_reader != (DisplayRegionPipelineReader *)NULL) { delete dr_reader; } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::cull_to_bins // Access: Private // Description: This is called in the cull thread by individual // RenderThread objects during the frame rendering. It // collects the geometry into bins in preparation for // drawing. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: cull_to_bins(const GraphicsEngine::Windows &wlist, Thread *current_thread) { PStatTimer timer(_cull_pcollector, current_thread); _singular_warning_last_frame = _singular_warning_this_frame; _singular_warning_this_frame = false; // Keep track of the cameras we have already used in this thread to // render DisplayRegions. typedef pmap AlreadyCulled; AlreadyCulled already_culled; Windows::const_iterator wi; for (wi = wlist.begin(); wi != wlist.end(); ++wi) { GraphicsOutput *win = (*wi); if (win->is_active() && win->get_gsg()->is_active()) { PStatTimer timer(win->get_cull_window_pcollector(), current_thread); int num_display_regions = win->get_num_active_display_regions(); for (int i = 0; i < num_display_regions; ++i) { DisplayRegion *dr = win->get_active_display_region(i); if (dr != (DisplayRegion *)NULL) { DisplayRegionPipelineReader *dr_reader = new DisplayRegionPipelineReader(dr, current_thread); NodePath camera = dr_reader->get_camera(); AlreadyCulled::iterator aci = already_culled.insert(AlreadyCulled::value_type(camera, (DisplayRegion *)NULL)).first; if ((*aci).second == NULL) { // We have not used this camera already in this thread. // Perform the cull operation. delete dr_reader; dr_reader = NULL; (*aci).second = dr; cull_to_bins(win, dr, current_thread); } else { // We have already culled a scene using this camera in // this thread, and now we're being asked to cull another // scene using the same camera. (Maybe this represents // two different DisplayRegions for the left and right // channels of a stereo image.) Of course, the cull // result will be the same, so just use the result from // the other DisplayRegion. DisplayRegion *other_dr = (*aci).second; dr->set_cull_result(other_dr->get_cull_result(current_thread), setup_scene(win->get_gsg(), dr_reader), current_thread); } if (dr_reader != (DisplayRegionPipelineReader *)NULL) { delete dr_reader; } } } } } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::cull_to_bins // Access: Private // Description: Called only within the inner loop of cull_to_bins(), // above. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: cull_to_bins(GraphicsOutput *win, DisplayRegion *dr, Thread *current_thread) { GraphicsStateGuardian *gsg = win->get_gsg(); nassertv(gsg != (GraphicsStateGuardian *)NULL); PT(CullResult) cull_result; PT(SceneSetup) scene_setup; { PStatTimer timer(_cull_setup_pcollector, current_thread); DisplayRegionPipelineReader dr_reader(dr, current_thread); scene_setup = setup_scene(gsg, &dr_reader); cull_result = dr->get_cull_result(current_thread); if (cull_result != (CullResult *)NULL) { cull_result = cull_result->make_next(); } else { // This DisplayRegion has no cull results; draw it. cull_result = new CullResult(gsg, dr->get_draw_region_pcollector()); } } if (scene_setup != (SceneSetup *)NULL) { BinCullHandler cull_handler(cull_result); CallbackObject *cbobj = dr->get_cull_callback(); if (cbobj != (CallbackObject *)NULL) { // Issue the cull callback on this DisplayRegion. DisplayRegionCullCallbackData cbdata(&cull_handler, scene_setup); cbobj->do_callback(&cbdata); // The callback has taken care of the culling. } else { // Perform the cull normally. do_cull(&cull_handler, scene_setup, gsg, current_thread); } PStatTimer timer(_cull_sort_pcollector, current_thread); cull_result->finish_cull(scene_setup, current_thread); } // Save the results for next frame. dr->set_cull_result(cull_result, scene_setup, current_thread); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::draw_bins // Access: Private // Description: This is called in the draw thread by individual // RenderThread objects during the frame rendering. It // issues the graphics commands to draw the objects that // have been collected into bins by a previous call to // cull_to_bins(). //////////////////////////////////////////////////////////////////// void GraphicsEngine:: draw_bins(const GraphicsEngine::Windows &wlist, Thread *current_thread) { nassertv(wlist.verify_list()); size_t wlist_size = wlist.size(); for (size_t wi = 0; wi < wlist_size; ++wi) { GraphicsOutput *win = wlist[wi]; if (win->is_active() && win->get_gsg()->is_active()) { if (win->flip_ready()) { { PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread); win->begin_flip(); } { PStatTimer timer(GraphicsEngine::_flip_end_pcollector, current_thread); win->end_flip(); } } PStatTimer timer(win->get_draw_window_pcollector(), current_thread); if (win->begin_frame(GraphicsOutput::FM_render, current_thread)) { win->clear(current_thread); if (display_cat.is_spam()) { display_cat.spam() << "Drawing window " << win->get_name() << "\n"; } int num_display_regions = win->get_num_active_display_regions(); for (int i = 0; i < num_display_regions; ++i) { DisplayRegion *dr = win->get_active_display_region(i); if (dr != (DisplayRegion *)NULL) { draw_bins(win, dr, current_thread); } } win->end_frame(GraphicsOutput::FM_render, current_thread); if (_auto_flip) { if (win->flip_ready()) { { PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread); win->begin_flip(); } { PStatTimer timer(GraphicsEngine::_flip_end_pcollector, current_thread); win->end_flip(); } } } } else { if (display_cat.is_spam()) { display_cat.spam() << "Not drawing window " << win->get_name() << "\n"; } } } else { if (display_cat.is_spam()) { display_cat.spam() << "Window " << win->get_name() << " is inactive\n"; } } } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::draw_bins // Access: Private // Description: This variant on draw_bins() is only called from // draw_bins(), above. It draws the cull result for a // particular DisplayRegion. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: draw_bins(GraphicsOutput *win, DisplayRegion *dr, Thread *current_thread) { GraphicsStateGuardian *gsg = win->get_gsg(); nassertv(gsg != (GraphicsStateGuardian *)NULL); PT(CullResult) cull_result = dr->get_cull_result(current_thread); PT(SceneSetup) scene_setup = dr->get_scene_setup(current_thread); do_draw(cull_result, scene_setup, win, dr, current_thread); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::make_contexts // Access: Private // Description: Called in the draw thread, this calls make_context() // on each window on the list to guarantee its gsg and // graphics context both get created. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: make_contexts(const GraphicsEngine::Windows &wlist, Thread *current_thread) { Windows::const_iterator wi; for (wi = wlist.begin(); wi != wlist.end(); ++wi) { GraphicsOutput *win = (*wi); if (win->begin_frame(GraphicsOutput::FM_refresh, current_thread)) { win->end_frame(GraphicsOutput::FM_refresh, current_thread); } } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::process_events // Access: Private // Description: This is called by the RenderThread object to process // all the windows events (resize, etc.) for the given // list of windows. This is run in the window thread. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: process_events(const GraphicsEngine::Windows &wlist, Thread *current_thread) { // We're not using a vector iterator here, since it's possible that // the window list changes in an event, which would invalidate the // iterator and cause a crash. for (size_t i = 0; i < wlist.size(); ++i) { wlist[i]->process_events(); } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::flip_windows // Access: Private // Description: This is called by the RenderThread object to flip the // buffers for all of the non-single-buffered windows in // the given list. This is run in the draw thread. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: flip_windows(const GraphicsEngine::Windows &wlist, Thread *current_thread) { Windows::const_iterator wi; for (wi = wlist.begin(); wi != wlist.end(); ++wi) { GraphicsOutput *win = (*wi); if (win->flip_ready()) { PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread); win->begin_flip(); } } for (wi = wlist.begin(); wi != wlist.end(); ++wi) { GraphicsOutput *win = (*wi); if (win->flip_ready()) { PStatTimer timer(GraphicsEngine::_flip_end_pcollector, current_thread); win->end_flip(); } } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::ready_flip_windows // Access: Private // Description: This is called by the RenderThread object to flip the // buffers for all of the non-single-buffered windows in // the given list. This is run in the draw thread. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: ready_flip_windows(const GraphicsEngine::Windows &wlist, Thread *current_thread) { Windows::const_iterator wi; for (wi = wlist.begin(); wi != wlist.end(); ++wi) { GraphicsOutput *win = (*wi); if (win->flip_ready()) { PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread); win->ready_flip(); } } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::do_sync_frame // Access: Private // Description: The implementation of sync_frame(). We assume _lock // is already held before this method is called. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: do_sync_frame(Thread *current_thread) { nassertv(_lock.debug_is_locked()); // Statistics PStatTimer timer(_sync_pcollector, current_thread); nassertv(_flip_state == FS_draw); // Wait for all the threads to finish their current frame. Grabbing // and releasing the mutex should achieve that. Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_cv_mutex.acquire(); thread->_cv_mutex.release(); } _flip_state = FS_sync; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::do_ready_flip // Access: Private // Description: Wait until all draw calls have finished drawing and // the frame is ready to flip //////////////////////////////////////////////////////////////////// void GraphicsEngine:: do_ready_flip(Thread *current_thread) { nassertv(_lock.debug_is_locked()); // Statistics PStatTimer timer(_sync_pcollector, current_thread); nassertv(_flip_state == FS_draw); // Wait for all the threads to finish their current frame. Grabbing // and releasing the mutex should achieve that. Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_cv_mutex.acquire(); thread->_cv_mutex.release(); } _app.do_ready_flip(this,current_thread); _flip_state = FS_sync; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::do_flip_frame // Access: Private // Description: The implementation of flip_frame(). We assume _lock // is already held before this method is called. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: do_flip_frame(Thread *current_thread) { nassertv(_lock.debug_is_locked()); // Statistics PStatTimer timer(_flip_pcollector, current_thread); nassertv(_flip_state == FS_draw || _flip_state == FS_sync); // First, wait for all the threads to finish their current frame, if // necessary. Grabbing the mutex (and waiting for TS_wait) should // achieve that. { PStatTimer timer(_wait_pcollector, current_thread); Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_cv_mutex.acquire(); while (thread->_thread_state != TS_wait) { thread->_cv_done.wait(); } } } // Now signal all of our threads to flip the windows. _app.do_flip(this, current_thread); { Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; nassertv(thread->_thread_state == TS_wait); thread->_thread_state = TS_do_flip; thread->_cv_start.notify(); thread->_cv_mutex.release(); } } _flip_state = FS_flip; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::setup_scene // Access: Private // Description: Returns a new SceneSetup object appropriate for // rendering the scene from the indicated camera, or // NULL if the scene should not be rendered for some // reason. //////////////////////////////////////////////////////////////////// PT(SceneSetup) GraphicsEngine:: setup_scene(GraphicsStateGuardian *gsg, DisplayRegionPipelineReader *dr) { Thread *current_thread = dr->get_current_thread(); PStatTimer timer(_cull_setup_pcollector, current_thread); GraphicsOutput *window = dr->get_window(); // The window pointer shouldn't be NULL, since we presumably got to // this particular DisplayRegion by walking through a list on a // window. nassertr(window != (GraphicsOutput *)NULL, NULL); NodePath camera = dr->get_camera(); if (camera.is_empty()) { // No camera, no draw. return NULL; } Camera *camera_node; DCAST_INTO_R(camera_node, camera.node(), NULL); if (!camera_node->is_active()) { // Camera inactive, no draw. return NULL; } camera_node->cleanup_aux_scene_data(current_thread); int lens_index = dr->get_lens_index(); Lens *lens = camera_node->get_lens(lens_index); if (lens == (Lens *)NULL || !camera_node->get_lens_active(lens_index)) { // No lens, no draw. return NULL; } NodePath scene_root = camera_node->get_scene(); if (scene_root.is_empty()) { // If there's no explicit scene specified, use whatever scene the // camera is parented within. This is the normal and preferred // case; the use of an explicit scene is now deprecated. scene_root = camera.get_top(current_thread); } PT(SceneSetup) scene_setup = new SceneSetup; // We will need both the camera transform (the net transform to the // camera from the scene) and the world transform (the camera // transform inverse, or the net transform to the scene from the // camera). These are actually defined from the parent of the // scene_root, because the scene_root's own transform is immediately // applied to these during rendering. (Normally, the parent of the // scene_root is the empty NodePath, although it need not be.) NodePath scene_parent = scene_root.get_parent(current_thread); CPT(TransformState) camera_transform = camera.get_transform(scene_parent, current_thread); CPT(TransformState) world_transform = scene_parent.get_transform(camera, current_thread); if (camera_transform->is_invalid()) { // There must be a singular transform over the scene. if (!_singular_warning_last_frame) { display_cat.warning() << "Scene " << scene_root << " has net scale (" << scene_root.get_scale(NodePath()) << "); cannot render.\n"; _singular_warning_this_frame = true; } return NULL; } if (world_transform->is_invalid()) { // There must be a singular transform over the camera. if (!_singular_warning_last_frame) { display_cat.warning() << "Camera " << camera << " has net scale (" << camera.get_scale(NodePath()) << "); cannot render.\n"; } _singular_warning_this_frame = true; return NULL; } CPT(RenderState) initial_state = camera_node->get_initial_state(); if (window->get_inverted()) { // If the window is to be inverted, we must set the inverted flag // on the SceneSetup object, so that the GSG will be able to // invert the projection matrix at the last minute. scene_setup->set_inverted(true); // This also means we need to globally invert the sense of polygon // vertex ordering. initial_state = initial_state->compose(get_invert_polygon_state()); } scene_setup->set_display_region(dr->get_object()); scene_setup->set_viewport_size(dr->get_pixel_width(), dr->get_pixel_height()); scene_setup->set_scene_root(scene_root); scene_setup->set_camera_path(camera); scene_setup->set_camera_node(camera_node); scene_setup->set_lens(lens); scene_setup->set_initial_state(initial_state); scene_setup->set_camera_transform(camera_transform); scene_setup->set_world_transform(world_transform); return scene_setup; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::do_draw // Access: Private // Description: Draws the previously-culled scene. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: do_draw(CullResult *cull_result, SceneSetup *scene_setup, GraphicsOutput *win, DisplayRegion *dr, Thread *current_thread) { // Statistics PStatTimer timer(dr->get_draw_region_pcollector(), current_thread); GraphicsStateGuardian *gsg = win->get_gsg(); CallbackObject *cbobj; { DisplayRegionPipelineReader dr_reader(dr, current_thread); win->change_scenes(&dr_reader); gsg->prepare_display_region(&dr_reader); if (dr_reader.is_any_clear_active()) { gsg->clear(dr_reader.get_object()); } cbobj = dr_reader.get_draw_callback(); } if (cbobj != (CallbackObject *)NULL) { // Issue the draw callback on this DisplayRegion. // Set the GSG to the initial state. gsg->clear_before_callback(); gsg->set_state_and_transform(RenderState::make_empty(), TransformState::make_identity()); DisplayRegionDrawCallbackData cbdata(cull_result, scene_setup); cbobj->do_callback(&cbdata); // We don't trust the state the callback may have left us in. gsg->clear_state_and_transform(); // The callback has taken care of the drawing. return; } if (cull_result == NULL || scene_setup == NULL) { // Nothing to see here. } else if (dr->is_stereo()) { // We don't actually draw the stereo DisplayRegions. These are // just placeholders; we draw the individual left and right eyes // instead. (We might still clear the stereo DisplayRegions, // though, since it's probably faster to clear right and left // channels in one pass, than to clear them in two separate // passes.) } else if (!gsg->set_scene(scene_setup)) { // The scene or lens is inappropriate somehow. display_cat.error() << gsg->get_type() << " cannot render scene with specified lens.\n"; } else { if (gsg->begin_scene()) { cull_result->draw(current_thread); gsg->end_scene(); } } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::do_add_window // Access: Private // Description: An internal function called by make_window() and // make_buffer() and similar functions to add the // newly-created GraphicsOutput object to the engine's // list of windows, and to request that the window be // opened. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: do_add_window(GraphicsOutput *window, const GraphicsThreadingModel &threading_model) { nassertv(window != NULL); MutexHolder holder(_lock); nassertv(window->get_engine() == this); // We have a special counter that is unique per window that allows // us to assure that recently-added windows end up on the end of the // list. window->_internal_sort_index = _window_sort_index; ++_window_sort_index; _windows_sorted = false; _windows.push_back(window); WindowRenderer *cull = get_window_renderer(threading_model.get_cull_name(), threading_model.get_cull_stage()); WindowRenderer *draw = get_window_renderer(threading_model.get_draw_name(), threading_model.get_draw_stage()); if (threading_model.get_cull_sorting()) { cull->add_window(cull->_cull, window); draw->add_window(draw->_draw, window); } else { cull->add_window(cull->_cdraw, window); } // Ask the pipe which thread it prefers to run its windowing // commands in (the "window thread"). This is the thread that // handles the commands to open, resize, etc. the window. X // requires this to be done in the app thread (along with all the // other windows, since X is strictly single-threaded), but Windows // requires this to be done in draw (because once an OpenGL context // has been bound in a given thread, it cannot subsequently be bound // in any other thread, and we have to bind a context in // open_window()). switch (window->get_pipe()->get_preferred_window_thread()) { case GraphicsPipe::PWT_app: _app.add_window(_app._window, window); break; case GraphicsPipe::PWT_draw: draw->add_window(draw->_window, window); break; } if (display_cat.is_debug()) { display_cat.debug() << "Created " << window->get_type() << " " << (void *)window << "\n"; } window->request_open(); _needs_open_windows = true; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::do_add_gsg // Access: Private // Description: An internal function called by make_output to add // the newly-created gsg object to the engine's // list of gsg's. It also adjusts various config // variables based on the gsg's capabilities. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe, const GraphicsThreadingModel &threading_model) { MutexHolder holder(_lock); nassertv(gsg->get_pipe() == pipe && gsg->get_engine() == this); gsg->_threading_model = threading_model; if (!_default_loader.is_null()) { gsg->set_loader(_default_loader); } auto_adjust_capabilities(gsg); WindowRenderer *draw = get_window_renderer(threading_model.get_draw_name(), threading_model.get_draw_stage()); draw->add_gsg(gsg); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::do_remove_window // Access: Private // Description: An internal function called by remove_window() and // remove_all_windows() to actually remove the indicated // window from all relevant structures, except the // _windows list itself. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: do_remove_window(GraphicsOutput *window, Thread *current_thread) { nassertv(window != NULL); PT(GraphicsPipe) pipe = window->get_pipe(); window->clear_pipe(); if (!_windows_sorted) { do_resort_windows(); } // Now remove the window from all threads that know about it. _app.remove_window(window); Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->remove_window(window); } // If the window happened to be controlled by the app thread, we // might as well close it now rather than waiting for next frame. _app.do_pending(this, current_thread); if (display_cat.is_debug()) { display_cat.debug() << "Removed " << window->get_type() << " " << (void *)window << "\n"; } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::do_resort_windows // Access: Private // Description: Resorts all of the Windows lists. This may need to // be done if one or more of the windows' sort // properties has changed. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: do_resort_windows() { _windows_sorted = true; _app.resort_windows(); Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->resort_windows(); } _windows.sort(); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::auto_adjust_capabilities // Access: Private // Description: Video card capability flags are stored on a // per-gsg basis. However, there are a few cases // where panda needs to know not the capabilities // of an individual GSG, but rather, the // collective capabilities of all the GSGs. // // Non-power-of-two (NPOT) texture support is the // classic example. Panda makes a single global // decision to either create NPOT textures, or not. // Therefore, it doesn't need to know whether one GSG // supports NPOT textures. It needs to know whether ALL // the GSGs support NPOT textures. // // The purpose of this routine is to maintain global // capability flags that summarize the collective // capabilities of the computer as a whole. // // These global capability flags are initialized from // config variables. Then, they can be auto-reconfigured // using built-in heuristic mechanisms if the user so // desires. Whether auto-reconfiguration is enabled or // not, the configured values are checked against // the actual capabilities of the machine and error // messages will be printed if there is a mismatch. // //////////////////////////////////////////////////////////////////// void GraphicsEngine:: auto_adjust_capabilities(GraphicsStateGuardian *gsg) { // The rule we use when auto-reconfiguring is as follows. The // global capabilities must initially be set to conservative // values. When the first GSG comes into existence, its // capabilities will be checked, and the global capabilities // may be elevated to more aggressive values. // // At first glance, this might seem backward, and it might seem // better to do it the other way: start with all global capabilities // aggressively set, and then disable capabilities when you discover // a gsg that doesn't support them. // // However, that approach doesn't work, because once a global // capability is enabled, there is no going back. If // textures_power_2 has ever been set to 'none', there may be NPOT // textures already floating about the system. Ie, it's too late: // you can't turn these global capability flags off, once they've // been turned on. // // That's why we have to start with conservative settings, and then // elevate those settings to more aggressive values later when // we're fairly sure it's OK to do so. // // For each global capability, we must: // 1. Make sure the initial setting is conservative. // 2. Possibly elevate to a more aggressive value. // 3. Check that we haven't over-elevated. // if (textures_auto_power_2 && (textures_power_2 == ATS_none)) { display_cat.error() << "Invalid panda config file: if you set the config-variable\n" << "textures_auto_power_2 to true, you must set the config-variable" << "textures_power_2 to 'up' or 'down'.\n"; textures_power_2 = ATS_down; // Not a fix. Just suppresses further error messages. } if (textures_auto_power_2 && !Texture::have_textures_power_2()) { if (gsg->get_supports_tex_non_pow2()) { Texture::set_textures_power_2(ATS_none); } else { Texture::set_textures_power_2(textures_power_2); } } if ((Texture::get_textures_power_2() == ATS_none) && (!gsg->get_supports_tex_non_pow2())) { // Overaggressive configuration detected display_cat.error() << "The 'textures_power_2' configuration is set to 'none', meaning \n" << "that non-power-of-two texture support is required, but the video \n" << "driver I'm trying to use does not support non-power-of-two textures.\n"; if (textures_power_2 != ATS_none) { display_cat.error() << "The 'none' did not come from the config file. In other words,\n" << "the variable 'textures_power_2' was altered procedurally.\n"; if (textures_auto_power_2) { display_cat.error() << "It is possible that it was set by panda's automatic mechanisms,\n" << "which are currently enabled, because 'textures_auto_power_2' is\n" << "true. Panda's automatic mechanisms assume that if one\n" << "window supports non-power-of-two textures, then they all will.\n" << "This assumption works for most games, but not all.\n" << "In particular, it can fail if the game creates multiple windows\n" << "on multiple displays with different video cards.\n"; } } } if (shader_auto_utilization && (shader_utilization != SUT_none)) { display_cat.error() << "Invalid panda config file: if you set the config-variable\n" << "shader_auto_utilization to true, you must set the config-variable" << "shader_utilization to 'none'.\n"; shader_utilization = SUT_none; // Not a fix. Just suppresses further error messages. } if (shader_auto_utilization && !Shader::have_shader_utilization()) { if (gsg->get_supports_basic_shaders()) { Shader::set_shader_utilization(SUT_basic); } else { Shader::set_shader_utilization(SUT_none); } } if ((Shader::get_shader_utilization() != SUT_none) && (!gsg->get_supports_basic_shaders())) { // Overaggressive configuration detected display_cat.error() << "The 'shader_utilization' config variable is set, meaning\n" << "that panda may try to generate shaders. However, the video \n" << "driver I'm trying to use does not support shaders.\n"; if (shader_utilization == SUT_none) { display_cat.error() << "The 'shader_utilization' setting did not come from the config\n" << "file. In other words, it was altered procedurally.\n"; if (shader_auto_utilization) { display_cat.error() << "It is possible that it was set by panda's automatic mechanisms,\n" << "which are currently enabled, because 'shader_auto_utilization' is\n" << "true. Panda's automatic mechanisms assume that if one\n" << "window supports shaders, then they all will.\n" << "This assumption works for most games, but not all.\n" << "In particular, it can fail if the game creates multiple windows\n" << "on multiple displays with different video cards.\n"; } } } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::terminate_threads // Access: Private // Description: Signals our child threads to terminate and waits for // them to clean up. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: terminate_threads(Thread *current_thread) { MutexHolder holder(_lock, current_thread); // We spend almost our entire time in this method just waiting for // threads. Time it appropriately. PStatTimer timer(_wait_pcollector, current_thread); // First, wait for all the threads to finish their current frame. // Grabbing the mutex should achieve that. Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_cv_mutex.acquire(); } // Now tell them to close their windows and terminate. for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_thread_state = TS_terminate; thread->_cv_start.notify(); thread->_cv_mutex.release(); } // Finally, wait for them all to finish cleaning up. for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->join(); } _threads.clear(); } #ifdef DO_PSTATS //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::pstats_count_cycler_type // Access: Private, Static // Description: A callback function for // Pipeline::iterate_all_cycler_types() to report the // cycler types to PStats. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: pstats_count_cycler_type(TypeHandle type, int count, void *data) { GraphicsEngine *self = (GraphicsEngine *)data; CyclerTypeCounters::iterator ci = self->_all_cycler_types.find(type); if (ci == self->_all_cycler_types.end()) { PStatCollector collector(_cyclers_pcollector, type.get_name()); ci = self->_all_cycler_types.insert(CyclerTypeCounters::value_type(type, collector)).first; } (*ci).second.set_level(count); } #endif // DO_PSTATS #ifdef DO_PSTATS //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::pstats_count_dirty_cycler_type // Access: Private, Static // Description: A callback function for // Pipeline::iterate_dirty_cycler_types() to report the // cycler types to PStats. //////////////////////////////////////////////////////////////////// void GraphicsEngine:: pstats_count_dirty_cycler_type(TypeHandle type, int count, void *data) { GraphicsEngine *self = (GraphicsEngine *)data; CyclerTypeCounters::iterator ci = self->_dirty_cycler_types.find(type); if (ci == self->_dirty_cycler_types.end()) { PStatCollector collector(_dirty_cyclers_pcollector, type.get_name()); ci = self->_dirty_cycler_types.insert(CyclerTypeCounters::value_type(type, collector)).first; } (*ci).second.set_level(count); } #endif // DO_PSTATS //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::get_invert_polygon_state // Access: Protected, Static // Description: Returns a RenderState for inverting the sense of // polygon vertex ordering: if the scene graph specifies // a clockwise ordering, this changes it to // counterclockwise, and vice-versa. //////////////////////////////////////////////////////////////////// const RenderState *GraphicsEngine:: get_invert_polygon_state() { // Once someone asks for this pointer, we hold its reference count // and never free it. static CPT(RenderState) state = (const RenderState *)NULL; if (state == (const RenderState *)NULL) { state = RenderState::make(CullFaceAttrib::make_reverse()); } return state; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::get_window_renderer // Access: Private // Description: Returns the WindowRenderer with the given name. // Creates a new RenderThread if there is no such thread // already. The pipeline_stage number specifies the // pipeline stage that will be assigned to the thread // (unless was previously given a higher stage). // // You must already be holding the lock before calling // this method. //////////////////////////////////////////////////////////////////// GraphicsEngine::WindowRenderer *GraphicsEngine:: get_window_renderer(const string &name, int pipeline_stage) { nassertr(_lock.debug_is_locked(), NULL); if (name.empty()) { return &_app; } Threads::iterator ti = _threads.find(name); if (ti != _threads.end()) { return (*ti).second.p(); } PT(RenderThread) thread = new RenderThread(name, this); thread->set_min_pipeline_stage(pipeline_stage); _pipeline->set_min_stages(pipeline_stage + 1); bool started = thread->start(TP_normal, true); nassertr(started, thread.p()); _threads[name] = thread; nassertr(thread->get_pipeline_stage() < _pipeline->get_num_stages(), thread.p()); return thread.p(); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// GraphicsEngine::WindowRenderer:: WindowRenderer(const string &name) : _wl_lock(string("GraphicsEngine::WindowRenderer::_wl_lock ") + name) { } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::add_gsg // Access: Public // Description: Adds a new GSG to the _gsg list, if it is not already // there. //////////////////////////////////////////////////////////////////// void GraphicsEngine::WindowRenderer:: add_gsg(GraphicsStateGuardian *gsg) { LightReMutexHolder holder(_wl_lock); _gsgs.insert(gsg); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::add_window // Access: Public // Description: Adds a new window to the indicated list, which should // be a member of the WindowRenderer. //////////////////////////////////////////////////////////////////// void GraphicsEngine::WindowRenderer:: add_window(Windows &wlist, GraphicsOutput *window) { LightReMutexHolder holder(_wl_lock); wlist.insert(window); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::remove_window // Access: Public // Description: Immediately removes the indicated window from all // lists. If the window is currently open and is // already on the _window list, moves it to the _pending_close // list for later closure. //////////////////////////////////////////////////////////////////// void GraphicsEngine::WindowRenderer:: remove_window(GraphicsOutput *window) { nassertv(window != NULL); LightReMutexHolder holder(_wl_lock); PT(GraphicsOutput) ptwin = window; _cull.erase(ptwin); _cdraw.erase(ptwin); _draw.erase(ptwin); Windows::iterator wi; wi = _window.find(ptwin); if (wi != _window.end()) { // The window is on our _window list, meaning its open/close // operations (among other window ops) are serviced by this // thread. // Make sure the window isn't about to request itself open. ptwin->request_close(); // If the window is already open, move it to the _pending_close list so // it can be closed later. We can't close it immediately, because // we might not have been called from the subthread. if (ptwin->is_valid()) { _pending_close.push_back(ptwin); } _window.erase(wi); } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::resort_windows // Access: Public // Description: Resorts all the lists of windows, assuming they may // have become unsorted. //////////////////////////////////////////////////////////////////// void GraphicsEngine::WindowRenderer:: resort_windows() { LightReMutexHolder holder(_wl_lock); _cull.sort(); _cdraw.sort(); _draw.sort(); _window.sort(); if (display_cat.is_debug()) { display_cat.debug() << "Windows resorted:"; Windows::const_iterator wi; for (wi = _window.begin(); wi != _window.end(); ++wi) { GraphicsOutput *win = (*wi); display_cat.debug(false) << " " << win->get_name() << "(" << win->get_sort() << ")"; } display_cat.debug(false) << "\n"; for (wi = _draw.begin(); wi != _draw.end(); ++wi) { GraphicsOutput *win = (*wi); display_cat.debug(false) << " " << win->get_name() << "(" << win->get_sort() << ")"; } display_cat.debug(false) << "\n"; } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::do_frame // Access: Public // Description: Executes one stage of the pipeline for the current // thread: calls cull on all windows that are on the // cull list for this thread, draw on all the windows on // the draw list, etc. //////////////////////////////////////////////////////////////////// void GraphicsEngine::WindowRenderer:: do_frame(GraphicsEngine *engine, Thread *current_thread) { PStatTimer timer(engine->_do_frame_pcollector, current_thread); LightReMutexHolder holder(_wl_lock); engine->cull_to_bins(_cull, current_thread); engine->cull_and_draw_together(_cdraw, current_thread); engine->draw_bins(_draw, current_thread); engine->process_events(_window, current_thread); // If any GSG's on the list have no more outstanding pointers, clean // them up. (We are in the draw thread for all of these GSG's.) if (any_done_gsgs()) { GSGs new_gsgs; GSGs::iterator gi; for (gi = _gsgs.begin(); gi != _gsgs.end(); ++gi) { GraphicsStateGuardian *gsg = (*gi); if (gsg->get_ref_count() == 1) { // This one has no outstanding pointers; clean it up. GraphicsPipe *pipe = gsg->get_pipe(); engine->close_gsg(pipe, gsg); } else { // This one is ok; preserve it. new_gsgs.insert(gsg); } } _gsgs.swap(new_gsgs); } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::do_windows // Access: Public // Description: Attempts to fully open or close any windows or // buffers associated with this thread, but does not // otherwise perform any rendering. (Normally, this // step is handled during do_frame(); call this method // only if you want these things to open immediately.) //////////////////////////////////////////////////////////////////// void GraphicsEngine::WindowRenderer:: do_windows(GraphicsEngine *engine, Thread *current_thread) { LightReMutexHolder holder(_wl_lock); engine->process_events(_window, current_thread); engine->make_contexts(_cdraw, current_thread); engine->make_contexts(_draw, current_thread); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::do_flip // Access: Public // Description: Flips the windows as appropriate for the current // thread. //////////////////////////////////////////////////////////////////// void GraphicsEngine::WindowRenderer:: do_flip(GraphicsEngine *engine, Thread *current_thread) { LightReMutexHolder holder(_wl_lock); engine->flip_windows(_cdraw, current_thread); engine->flip_windows(_draw, current_thread); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::do_ready_flip // Access: Public // Description: Prepares windows for flipping by waiting until all draw // calls are finished //////////////////////////////////////////////////////////////////// void GraphicsEngine::WindowRenderer:: do_ready_flip(GraphicsEngine *engine, Thread *current_thread) { LightReMutexHolder holder(_wl_lock); engine->ready_flip_windows(_cdraw, current_thread); engine->ready_flip_windows(_draw, current_thread); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::do_close // Access: Public // Description: Closes all the windows on the _window list. //////////////////////////////////////////////////////////////////// void GraphicsEngine::WindowRenderer:: do_close(GraphicsEngine *engine, Thread *current_thread) { LightReMutexHolder holder(_wl_lock); Windows::iterator wi; for (wi = _window.begin(); wi != _window.end(); ++wi) { GraphicsOutput *win = (*wi); win->set_close_now(); } // Also close all of the GSG's. GSGs new_gsgs; GSGs::iterator gi; for (gi = _gsgs.begin(); gi != _gsgs.end(); ++gi) { GraphicsStateGuardian *gsg = (*gi); if (gsg->get_ref_count() == 1) { // This one has no outstanding pointers; clean it up. GraphicsPipe *pipe = gsg->get_pipe(); engine->close_gsg(pipe, gsg); } else { // This one is ok; preserve it. new_gsgs.insert(gsg); } } _gsgs.swap(new_gsgs); } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::do_pending // Access: Public // Description: Actually closes any windows that were recently // removed from the WindowRenderer. //////////////////////////////////////////////////////////////////// void GraphicsEngine::WindowRenderer:: do_pending(GraphicsEngine *engine, Thread *current_thread) { LightReMutexHolder holder(_wl_lock); if (!_pending_close.empty()) { if (display_cat.is_debug()) { display_cat.debug() << "_pending_close.size() = " << _pending_close.size() << "\n"; } // Close any windows that were pending closure. Carefully protect // against recursive entry to this function by swapping the vector // to a local copy first. Windows::iterator wi; Windows pending_close; _pending_close.swap(pending_close); for (wi = pending_close.begin(); wi != pending_close.end(); ++wi) { GraphicsOutput *win = (*wi); win->set_close_now(); } } } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::WindowRenderer::any_done_gsgs // Access: Public // Description: Returns true if any of the GSG's on this thread's // draw list are done (they have no outstanding pointers // other than this one), or false if all of them are // still good. //////////////////////////////////////////////////////////////////// bool GraphicsEngine::WindowRenderer:: any_done_gsgs() const { GSGs::const_iterator gi; for (gi = _gsgs.begin(); gi != _gsgs.end(); ++gi) { if ((*gi)->get_ref_count() == 1) { return true; } } return false; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::RenderThread::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// GraphicsEngine::RenderThread:: RenderThread(const string &name, GraphicsEngine *engine) : Thread(name, "Main"), WindowRenderer(name), _engine(engine), _cv_mutex(string("GraphicsEngine::RenderThread ") + name), _cv_start(_cv_mutex), _cv_done(_cv_mutex) { _thread_state = TS_wait; } //////////////////////////////////////////////////////////////////// // Function: GraphicsEngine::RenderThread::thread_main // Access: Public, Virtual // Description: The main loop for a particular render thread. The // thread will process whatever cull or draw windows it // has assigned to it. //////////////////////////////////////////////////////////////////// void GraphicsEngine::RenderThread:: thread_main() { Thread *current_thread = Thread::get_current_thread(); MutexHolder holder(_cv_mutex); while (true) { nassertv(_cv_mutex.debug_is_locked()); switch (_thread_state) { case TS_wait: break; case TS_do_frame: do_pending(_engine, current_thread); do_frame(_engine, current_thread); break; case TS_do_flip: do_flip(_engine, current_thread); break; case TS_do_release: do_pending(_engine, current_thread); break; case TS_do_windows: do_windows(_engine, current_thread); do_pending(_engine, current_thread); break; case TS_terminate: do_pending(_engine, current_thread); do_close(_engine, current_thread); _thread_state = TS_done; _cv_done.notify(); return; case TS_done: // Shouldn't be possible to get here. nassertv(false); return; } _thread_state = TS_wait; _cv_done.notify(); { PStatTimer timer(_wait_pcollector, current_thread); _cv_start.wait(); } } }