Panda3D
graphicsEngine.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file graphicsEngine.cxx
10 * @author drose
11 * @date 2002-02-24
12 */
13
14#include "graphicsEngine.h"
15#include "graphicsPipe.h"
16#include "parasiteBuffer.h"
17#include "config_gobj.h"
18#include "config_display.h"
19#include "pipeline.h"
20#include "drawCullHandler.h"
21#include "binCullHandler.h"
22#include "cullResult.h"
23#include "cullTraverser.h"
24#include "clockObject.h"
25#include "pStatTimer.h"
26#include "pStatGPUTimer.h"
27#include "pStatClient.h"
28#include "pStatCollector.h"
29#include "mutexHolder.h"
30#include "reMutexHolder.h"
31#include "lightReMutexHolder.h"
32#include "cullFaceAttrib.h"
33#include "string_utils.h"
34#include "geomCacheManager.h"
35#include "renderState.h"
36#include "transformState.h"
37#include "thread.h"
38#include "pipeline.h"
39#include "throw_event.h"
40#include "bamCache.h"
41#include "cullableObject.h"
42#include "geomVertexArrayData.h"
43#include "vertexDataSaveFile.h"
44#include "vertexDataBook.h"
45#include "vertexDataPage.h"
46#include "config_pgraph.h"
50#include "depthTestAttrib.h"
51
52#if defined(_WIN32) && defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
54#endif
55
56#if defined(WIN32)
57 #define WINDOWS_LEAN_AND_MEAN
58 #include <winsock2.h>
59 #include <wtypes.h>
60 #undef WINDOWS_LEAN_AND_MEAN
61#else
62 #include <sys/time.h>
63#endif
64
65using std::string;
66
67PT(GraphicsEngine) GraphicsEngine::_global_ptr;
68
69PStatCollector GraphicsEngine::_wait_pcollector("Wait:Thread sync");
70PStatCollector GraphicsEngine::_cycle_pcollector("App:Cycle");
71PStatCollector GraphicsEngine::_app_pcollector("App:Show code:General");
72PStatCollector GraphicsEngine::_render_frame_pcollector("App:render_frame");
73PStatCollector GraphicsEngine::_do_frame_pcollector("*:do_frame");
74PStatCollector GraphicsEngine::_yield_pcollector("App:Yield");
75PStatCollector GraphicsEngine::_cull_pcollector("Cull");
76PStatCollector GraphicsEngine::_cull_setup_pcollector("Cull:Setup");
77PStatCollector GraphicsEngine::_cull_sort_pcollector("Cull:Sort");
78PStatCollector GraphicsEngine::_draw_pcollector("Draw");
79PStatCollector GraphicsEngine::_sync_pcollector("Draw:Sync");
80PStatCollector GraphicsEngine::_flip_pcollector("Wait:Flip");
81PStatCollector GraphicsEngine::_flip_begin_pcollector("Wait:Flip:Begin");
82PStatCollector GraphicsEngine::_flip_end_pcollector("Wait:Flip:End");
83PStatCollector GraphicsEngine::_transform_states_pcollector("TransformStates");
84PStatCollector GraphicsEngine::_transform_states_unused_pcollector("TransformStates:Unused");
85PStatCollector GraphicsEngine::_render_states_pcollector("RenderStates");
86PStatCollector GraphicsEngine::_render_states_unused_pcollector("RenderStates:Unused");
87PStatCollector GraphicsEngine::_cyclers_pcollector("PipelineCyclers");
88PStatCollector GraphicsEngine::_dirty_cyclers_pcollector("Dirty PipelineCyclers");
89PStatCollector GraphicsEngine::_delete_pcollector("App:Delete");
90
91
92PStatCollector GraphicsEngine::_sw_sprites_pcollector("SW Sprites");
93PStatCollector GraphicsEngine::_vertex_data_small_pcollector("Vertex Data:Small");
94PStatCollector GraphicsEngine::_vertex_data_independent_pcollector("Vertex Data:Independent");
95PStatCollector GraphicsEngine::_vertex_data_pending_pcollector("Vertex Data:Pending");
96PStatCollector GraphicsEngine::_vertex_data_resident_pcollector("Vertex Data:Resident");
97PStatCollector GraphicsEngine::_vertex_data_compressed_pcollector("Vertex Data:Compressed");
98PStatCollector GraphicsEngine::_vertex_data_unused_disk_pcollector("Vertex Data:Disk:Unused");
99PStatCollector GraphicsEngine::_vertex_data_used_disk_pcollector("Vertex Data:Disk:Used");
100
101// These are counted independently by the collision system; we redefine them
102// here so we can reset them at each frame.
103PStatCollector GraphicsEngine::_cnode_volume_pcollector("Collision Volumes:CollisionNode");
104PStatCollector GraphicsEngine::_gnode_volume_pcollector("Collision Volumes:GeomNode");
105PStatCollector GraphicsEngine::_geom_volume_pcollector("Collision Volumes:Geom");
106PStatCollector GraphicsEngine::_node_volume_pcollector("Collision Volumes:PandaNode");
107PStatCollector GraphicsEngine::_volume_pcollector("Collision Volumes:CollisionSolid");
108PStatCollector GraphicsEngine::_test_pcollector("Collision Tests:CollisionSolid");
109PStatCollector GraphicsEngine::_volume_polygon_pcollector("Collision Volumes:CollisionPolygon");
110PStatCollector GraphicsEngine::_test_polygon_pcollector("Collision Tests:CollisionPolygon");
111PStatCollector GraphicsEngine::_volume_plane_pcollector("Collision Volumes:CollisionPlane");
112PStatCollector GraphicsEngine::_test_plane_pcollector("Collision Tests:CollisionPlane");
113PStatCollector GraphicsEngine::_volume_sphere_pcollector("Collision Volumes:CollisionSphere");
114PStatCollector GraphicsEngine::_test_sphere_pcollector("Collision Tests:CollisionSphere");
115PStatCollector GraphicsEngine::_volume_box_pcollector("Collision Volumes:CollisionBox");
116PStatCollector GraphicsEngine::_test_box_pcollector("Collision Tests:CollisionBox");
117PStatCollector GraphicsEngine::_volume_capsule_pcollector("Collision Volumes:CollisionCapsule");
118PStatCollector GraphicsEngine::_test_capsule_pcollector("Collision Tests:CollisionCapsule");
119PStatCollector GraphicsEngine::_volume_inv_sphere_pcollector("Collision Volumes:CollisionInvSphere");
120PStatCollector GraphicsEngine::_test_inv_sphere_pcollector("Collision Tests:CollisionInvSphere");
121PStatCollector GraphicsEngine::_volume_geom_pcollector("Collision Volumes:CollisionGeom");
122PStatCollector GraphicsEngine::_test_geom_pcollector("Collision Tests:CollisionGeom");
123PStatCollector GraphicsEngine::_occlusion_untested_pcollector("Occlusion results:Not tested");
124PStatCollector GraphicsEngine::_occlusion_passed_pcollector("Occlusion results:Visible");
125PStatCollector GraphicsEngine::_occlusion_failed_pcollector("Occlusion results:Occluded");
126PStatCollector GraphicsEngine::_occlusion_tests_pcollector("Occlusion tests");
127
128// This is used to keep track of which scenes we have already culled.
129struct CullKey {
131 NodePath _camera;
132 int _lens_index;
133};
134
135INLINE static bool operator < (const CullKey &a, const CullKey &b) {
136 if (a._gsg != b._gsg) {
137 return a._gsg < b._gsg;
138 }
139 if (a._camera != b._camera) {
140 return a._camera < b._camera;
141 }
142 return a._lens_index < b._lens_index;
143}
144
145/**
146 * Creates a new GraphicsEngine object. The Pipeline is normally left to
147 * default to NULL, which indicates the global render pipeline, but it may be
148 * any Pipeline you choose.
149 */
151GraphicsEngine(Pipeline *pipeline) :
152 _pipeline(pipeline),
153 _app("app"),
154 _lock("GraphicsEngine::_lock"),
155 _loaded_textures_lock("GraphicsEngine::_loaded_textures_lock")
156{
157 if (_pipeline == nullptr) {
158 _pipeline = Pipeline::get_render_pipeline();
159 }
160
161 _windows_sorted = true;
162 _window_sort_index = 0;
163
165 if (!_threading_model.is_default()) {
166 display_cat.info()
167 << "Using threading model " << _threading_model << "\n";
168 }
169 _auto_flip = auto_flip;
170 _portal_enabled = false;
171 _flip_state = FS_flip;
172
173 _singular_warning_last_frame = false;
174 _singular_warning_this_frame = false;
175}
176
177/**
178 * Gracefully cleans up the graphics engine and its related threads and
179 * windows.
180 */
183#ifdef DO_PSTATS
184 if (_app_pcollector.is_started()) {
185 _app_pcollector.stop();
186 }
187#endif
188
190}
191
192/**
193 * Specifies how future objects created via make_gsg(), make_buffer(), and
194 * make_output() will be threaded. This does not affect any already-created
195 * objects.
196 */
198set_threading_model(const GraphicsThreadingModel &threading_model) {
200 if (!threading_model.is_single_threaded()) {
201 display_cat.warning()
202 << "Threading model " << threading_model
203 << " requested but threading is not available. Ignoring.\n";
204 return;
205 }
206 }
207
208#ifndef THREADED_PIPELINE
209 if (!threading_model.is_single_threaded()) {
210 display_cat.warning()
211 << "Threading model " << threading_model
212 << " requested but multithreaded render pipelines not enabled in build.\n";
213 if (!allow_nonpipeline_threads) {
214 display_cat.warning()
215 << "Ignoring requested threading model.\n";
216 return;
217 }
218 display_cat.warning()
219 << "Danger! Creating requested render threads anyway!\n";
220 }
221#endif // THREADED_PIPELINE
222 ReMutexHolder holder(_lock);
223 _threading_model = threading_model;
224}
225
226/**
227 * Returns the threading model that will be applied to future objects. See
228 * set_threading_model().
229 */
231get_threading_model() const {
233 {
234 ReMutexHolder holder(_lock);
235 result = _threading_model;
236 }
237 return result;
238}
239
240// THIS IS THE OLD CODE FOR make_gsg PT(GraphicsStateGuardian) gsg =
241// pipe->make_gsg(properties, share_with);
242
243
244
245/**
246 * Creates a new window (or buffer) and returns it. The GraphicsEngine
247 * becomes the owner of the window, it will persist at least until
248 * remove_window() is called later.
249 *
250 * If a null pointer is supplied for the gsg, then this routine will create a
251 * new gsg.
252 *
253 * This routine is only called from the app thread.
254 */
255
258 const string &name, int sort,
259 const FrameBufferProperties &fb_prop,
260 const WindowProperties &win_prop,
261 int flags,
263 GraphicsOutput *host) {
264
265/*
266 * The code here is tricky because the gsg that is passed in might be in the
267 * uninitialized state. As a result, pipe::make_output may not be able to
268 * tell which DirectX capabilities or OpenGL extensions are supported and
269 * which are not. Worse yet, it can't query the API, because that can only be
270 * done from the draw thread, and this is the app thread. So here's the
271 * workaround: this routine calls pipe::make_output, which returns a "non-
272 * certified" window. That means that the pipe doesn't promise that the draw
273 * thread will actually succeed in initializing the window. This routine then
274 * calls open_windows, which attempts to initialize the window. If
275 * open_windows fails to initialize the window, then this routine will ask
276 * pipe::make_output to try again, this time using a different set of OpenGL
277 * extensions or DirectX capabilities. This is what the "retry" parameter to
278 * pipe::make_output is for - it specifies, in an abstract manner, which set
279 * of capabiltiesextensions to try. The only problem with this design is that
280 * it requires the engine to call open_windows, which is slow. To make things
281 * faster, the pipe can choose to "precertify" its creations. If it chooses
282 * to do so, this is a guarantee that the windows it returns will not fail in
283 * open_windows. However, most graphics pipes will only precertify if you
284 * pass them an already-initialized gsg. Long story short, if you want
285 * make_output to be fast, use an already-initialized gsg.
286 */
287
288 // Simplify the input parameters.
289
290 int x_size = 0, y_size = 0;
291 if (win_prop.has_size()) {
292 x_size = win_prop.get_x_size();
293 y_size = win_prop.get_y_size();
294 }
295 if ((x_size == 0)&&(y_size == 0)) {
296 flags |= GraphicsPipe::BF_size_track_host;
297 }
298 if (host != nullptr) {
299 host = host->get_host();
300 }
301
302 // If a gsg or host was supplied, and either is not yet initialized, then
303 // call open_windows to get both ready. If that fails, give up on using the
304 // supplied gsg and host.
305
306 if (host == nullptr) {
307 if (gsg != nullptr) {
308 if ((!gsg->is_valid())||(gsg->needs_reset())) {
309 open_windows();
310 }
311 if ((!gsg->is_valid())||(gsg->needs_reset())) {
312 gsg = nullptr;
313 }
314 }
315 } else {
316 if ((host->get_gsg()==nullptr)||
317 (!host->is_valid())||
318 (!host->get_gsg()->is_valid())||
319 (host->get_gsg()->needs_reset())) {
320 open_windows();
321 }
322 if ((host->get_gsg()==nullptr)||
323 (!host->is_valid())||
324 (!host->get_gsg()->is_valid())||
325 (host->get_gsg()->needs_reset())) {
326 host = nullptr;
327 gsg = nullptr;
328 } else {
329 gsg = host->get_gsg();
330 }
331 }
332
333 // Sanity check everything.
334
335 nassertr(pipe != nullptr, nullptr);
336 if (gsg != nullptr) {
337 nassertr(pipe == gsg->get_pipe(), nullptr);
338 nassertr(this == gsg->get_engine(), nullptr);
339 }
340
341 // Are we really asking for a callback window?
342 if ((flags & GraphicsPipe::BF_require_callback_window)!=0) {
343 PT(GraphicsStateGuardian) this_gsg = gsg;
344 if (this_gsg == nullptr) {
345 // If we don't already have a GSG, we have to ask the pipe to make a new
346 // one, unencumbered by window dressing.
347 this_gsg = pipe->make_callback_gsg(this);
348 }
349 if (this_gsg != nullptr) {
350 CallbackGraphicsWindow *window = new CallbackGraphicsWindow(this, pipe, name, fb_prop, win_prop, flags, this_gsg);
351 window->_sort = sort;
352 do_add_window(window);
353 do_add_gsg(window->get_gsg(), pipe);
354 display_cat.info() << "Created output of type CallbackGraphicsWindow\n";
355 return window;
356 }
357
358 // Couldn't make a callback window, because the pipe wouldn't make an
359 // unencumbered GSG.
360 return nullptr;
361 }
362
363 // Determine if a parasite buffer meets the user's specs.
364
365 bool can_use_parasite = false;
366 if ((host != nullptr)&&
367 ((flags&GraphicsPipe::BF_require_window)==0)&&
368 ((flags&GraphicsPipe::BF_require_callback_window)==0)&&
369 ((flags&GraphicsPipe::BF_refuse_parasite)==0)&&
370 ((flags&GraphicsPipe::BF_can_bind_color)==0)&&
371 ((flags&GraphicsPipe::BF_can_bind_every)==0)&&
372 ((flags&GraphicsPipe::BF_rtt_cumulative)==0)&&
373 ((flags&GraphicsPipe::BF_can_bind_layered)==0)) {
374 if ((flags&GraphicsPipe::BF_fb_props_optional) ||
375 (host->get_fb_properties().subsumes(fb_prop))) {
376 can_use_parasite = true;
377 }
378 }
379
380 // If parasite buffers are preferred, then try a parasite first. Even if
381 // prefer-parasite-buffer is set, parasites are not preferred if the host
382 // window is too small, or if the host window does not have the requested
383 // properties.
384
385 if ((prefer_parasite_buffer) &&
386 (can_use_parasite) &&
387 (x_size <= host->get_x_size())&&
388 (y_size <= host->get_y_size())&&
389 (host->get_fb_properties().subsumes(fb_prop))) {
390 ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags);
391 buffer->_sort = sort;
392 do_add_window(buffer);
393 do_add_gsg(host->get_gsg(), pipe);
394 display_cat.info() << "Created output of type ParasiteBuffer\n";
395 return buffer;
396 }
397
398 // If force-parasite-buffer is set, we create a parasite buffer even if it's
399 // less than ideal. You might set this if you really don't trust your
400 // graphics driver's support for offscreen buffers.
401 if (force_parasite_buffer && can_use_parasite) {
402 ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags);
403 buffer->_sort = sort;
404 do_add_window(buffer);
405 do_add_gsg(host->get_gsg(), pipe);
406 display_cat.info() << "Created output of type ParasiteBuffer\n";
407 return buffer;
408 }
409
410 // Ask the pipe to create a window.
411
412 for (int retry=0; retry<10; retry++) {
413 bool precertify = false;
414 PT(GraphicsOutput) window =
415 pipe->make_output(name, fb_prop, win_prop, flags, this, gsg, host, retry, precertify);
416 if (window != nullptr) {
417 window->_sort = sort;
418 if (precertify && gsg != nullptr && window->get_gsg() == gsg) {
419 do_add_window(window);
420 display_cat.info()
421 << "Created output of type " << window->get_type() << "\n";
422 return window;
423 }
424 do_add_window(window);
425 open_windows();
426 if (window->is_valid()) {
427 display_cat.info()
428 << "Created output of type " << window->get_type() << "\n";
429
430 if (window->get_fb_properties().subsumes(fb_prop)) {
431 return window;
432 } else {
433 if (flags & GraphicsPipe::BF_fb_props_optional) {
434 display_cat.warning()
435 << "FrameBufferProperties available less than requested.\n";
436 display_cat.warning(false)
437 << " requested: " << fb_prop << "\n"
438 << " got: " << window->get_fb_properties() << "\n";
439 return window;
440 }
441 display_cat.error()
442 << "Could not get requested FrameBufferProperties; abandoning window.\n";
443 display_cat.error(false)
444 << " requested: " << fb_prop << "\n"
445 << " got: " << window->get_fb_properties() << "\n";
446 }
447 } else {
448 display_cat.info()
449 << window->get_type() << " wouldn't open; abandoning.\n";
450 display_cat.debug(false)
451 << " requested: " << fb_prop << "\n";
452 }
453
454 // No good; delete the window and keep trying.
455 remove_window(window);
456 }
457 }
458
459 // Parasite buffers were not preferred, but the pipe could not create a
460 // window to the user's specs. Try a parasite as a last hope.
461
462 if (can_use_parasite) {
463 ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags);
464 buffer->_sort = sort;
465 do_add_window(buffer);
466 do_add_gsg(host->get_gsg(), pipe);
467 display_cat.info() << "Created output of type ParasiteBuffer\n";
468 return buffer;
469 }
470
471 // Could not create a window to the user's specs.
472
473 return nullptr;
474}
475
476/**
477 * This can be used to add a newly-created GraphicsOutput object (and its GSG)
478 * to the engine's list of windows, and requests that it be opened. This
479 * shouldn't be called by user code as make_output normally does this under
480 * the hood; it may be useful in esoteric cases in which a custom window
481 * object is used.
482 *
483 * This can be called during the rendering loop, unlike make_output(); the
484 * window will be opened before the next frame begins rendering. Because it
485 * doesn't call open_windows(), however, it's not guaranteed that the window
486 * will succeed opening even if it returns true.
487 */
489add_window(GraphicsOutput *window, int sort) {
490 nassertr(window != nullptr, false);
491 nassertr(this == window->get_engine(), false);
492
493 window->_sort = sort;
494 do_add_window(window);
495
496 display_cat.info()
497 << "Added output of type " << window->get_type() << "\n";
498
499 return true;
500}
501
502/**
503 * Removes the indicated window or offscreen buffer from the set of windows
504 * that will be processed when render_frame() is called. This also closes the
505 * window if it is open, and removes the window from its GraphicsPipe,
506 * allowing the window to be destructed if there are no other references to
507 * it. (However, the window may not be actually closed until next frame, if
508 * it is controlled by a sub-thread.)
509 *
510 * The return value is true if the window was removed, false if it was not
511 * found.
512 *
513 * Unlike remove_all_windows(), this function does not terminate any of the
514 * threads that may have been started to service this window; they are left
515 * running (since you might open a new window later on these threads). If
516 * your intention is to clean up before shutting down, it is better to call
517 * remove_all_windows() then to call remove_window() one at a time.
518 */
521 nassertr(window != nullptr, false);
522 Thread *current_thread = Thread::get_current_thread();
523
524 // First, make sure we know what this window is.
525 PT(GraphicsOutput) ptwin = window;
526 size_t count;
527 {
528 ReMutexHolder holder(_lock, current_thread);
529 if (!_windows_sorted) {
530 do_resort_windows();
531 }
532 count = _windows.erase(ptwin);
533 }
534
535 // Also check whether it is in _new_windows.
536 {
537 MutexHolder new_windows_holder(_new_windows_lock, current_thread);
538 size_t old_size = _new_windows.size();
539 _new_windows.erase(std::remove(_new_windows.begin(), _new_windows.end(), ptwin), _new_windows.end());
540 if (count == 0 && _new_windows.size() < old_size) {
541 count = 1;
542 }
543 }
544
545 if (count == 0) {
546 // Never heard of this window. Do nothing.
547 return false;
548 }
549
550 do_remove_window(window, current_thread);
551
552 GraphicsStateGuardian *gsg = window->get_gsg();
553 if (gsg != nullptr) {
555 if (pgo != nullptr) {
556 // Check to see if any other still-active windows share this context.
557 bool any_common = false;
558 {
559 ReMutexHolder holder(_lock, current_thread);
560 Windows::iterator wi;
561 for (wi = _windows.begin(); wi != _windows.end() && !any_common; ++wi) {
562 GraphicsStateGuardian *gsg2 = (*wi)->get_gsg();
563 if (gsg2 != nullptr &&
564 gsg2->get_prepared_objects() == pgo) {
565 any_common = true;
566 }
567 }
568 }
569 if (!any_common) {
570 // If no windows still use this context, release all textures, etc.
571 // We do this in case there is a floating pointer somewhere keeping
572 // the GSG from destructing when its window goes away. A leaked GSG
573 // pointer is bad enough, but there's no reason we also need to keep
574 // around all of the objects allocated on graphics memory.
575 pgo->release_all();
576 }
577 }
578 }
579
580 nassertr(count == 1, true);
581 return true;
582}
583
584/**
585 * Removes and closes all windows from the engine. This also cleans up and
586 * terminates any threads that have been started to service those windows.
587 */
590 Thread *current_thread = Thread::get_current_thread();
591
592 ReMutexHolder holder(_lock, current_thread);
593
594 // Let's move the _windows vector into a local copy first, and walk through
595 // that local copy, just in case someone we call during the loop attempts to
596 // modify _windows. I don't know what code would be doing this, but it
597 // appeared to be happening, and this worked around it.
598 Windows old_windows;
599 old_windows.swap(_windows);
600 for (GraphicsOutput *win : old_windows) {
601 nassertv(win != nullptr);
602 do_remove_window(win, current_thread);
603 GraphicsStateGuardian *gsg = win->get_gsg();
604 if (gsg != nullptr) {
605 gsg->release_all();
606 }
607 }
608
609 {
610 MutexHolder new_windows_holder(_new_windows_lock, current_thread);
611 for (GraphicsOutput *win : _new_windows) {
612 nassertv(win != nullptr);
613 do_remove_window(win, current_thread);
614 GraphicsStateGuardian *gsg = win->get_gsg();
615 if (gsg != nullptr) {
616 gsg->release_all();
617 }
618 }
619 _new_windows.clear();
620 }
621
622 _app.do_close(this, current_thread);
623 _app.do_pending(this, current_thread);
624 terminate_threads(current_thread);
625
626 // It seems a safe assumption that we're about to exit the application or
627 // otherwise shut down Panda. Although it's a bit of a hack, since it's not
628 // really related to removing windows, this would nevertheless be a fine
629 // time to ensure the model cache (if any) has been flushed to disk.
631
632 // And, hey, let's stop the vertex paging threads, if any.
634
635 // Stopping the tasks means we have to release the Python GIL while
636 // this method runs (hence it is marked BLOCKING), so that any
637 // Python tasks on other threads won't deadlock grabbing the GIL.
639
640#ifdef DO_PSTATS
641 PStatClient::get_global_pstats()->disconnect();
642#endif
643
644#if defined(_WIN32) && defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
645 // Send a message to the input message pump asking it to shut itself down.
646 // If we don't do that, the next call will deadlock.
647 WinInputDeviceManager::stop_thread();
648#endif
649
650 // Well, and why not clean up all threads here?
652}
653
654/**
655 * Resets the framebuffer of the current window. This is currently used by
656 * DirectX 8 only. It calls a reset_window function on each active window to
657 * release/create old/new framebuffer
658 */
660reset_all_windows(bool swapchain) {
661 Windows::iterator wi;
662 for (wi = _windows.begin(); wi != _windows.end(); ++wi) {
663 GraphicsOutput *win = (*wi);
664 // if (win->is_active())
665 win->reset_window(swapchain);
666 }
667}
668
669/**
670 * Returns true if there are no windows or buffers managed by the engine,
671 * false if there is at least one.
672 */
674is_empty() const {
675 return _windows.empty();
676}
677
678/**
679 * Returns the number of windows (or buffers) managed by the engine.
680 */
682get_num_windows() const {
683 return _windows.size();
684}
685
686/**
687 * Returns the nth window or buffers managed by the engine, in sorted order.
688 */
690get_window(int n) const {
691 nassertr(n >= 0 && n < (int)_windows.size(), nullptr);
692
693 if (!_windows_sorted) {
694 ((GraphicsEngine *)this)->do_resort_windows();
695 }
696 return _windows[n];
697}
698
699/**
700 * Renders the next frame in all the registered windows, and flips all of the
701 * frame buffers.
702 */
704render_frame() {
705 Thread *current_thread = Thread::get_current_thread();
706 ReMutexHolder public_holder(_public_lock);
707
708 // Since this gets called every frame, we should take advantage of the
709 // opportunity to flush the cache if necessary.
711
712 // Anything that happens outside of GraphicsEngine::render_frame() is deemed
713 // to be App.
714#ifdef DO_PSTATS
715 _render_frame_pcollector.start();
716 if (_app_pcollector.is_started()) {
717 _app_pcollector.stop();
718 }
719#endif
720
721 // Make sure our buffers and windows are fully realized before we render a
722 // frame. We do this particularly to realize our offscreen buffers, so
723 // that we don't render a frame before the offscreen buffers are ready
724 // (which might result in a frame going by without some textures having
725 // been rendered).
726 open_windows();
727
729
730 if (display_cat.is_spam()) {
731 display_cat.spam()
732 << "render_frame() - frame " << global_clock->get_frame_count() << "\n";
733 }
734
735 {
736 ReMutexHolder holder(_lock, current_thread);
737
738 if (!_windows_sorted) {
739 do_resort_windows();
740 }
741
742 if (sync_flip && _flip_state != FS_flip) {
743 do_flip_frame(current_thread);
744 }
745
746 // Are any of the windows ready to be deleted?
747 Windows new_windows;
748 new_windows.reserve(_windows.size());
749 Windows::iterator wi;
750 for (wi = _windows.begin(); wi != _windows.end(); ++wi) {
751 GraphicsOutput *win = (*wi);
752 nassertv(win != nullptr);
753 if (win->get_delete_flag()) {
754 do_remove_window(win, current_thread);
755
756 } else {
757 new_windows.push_back(win);
758
759 // Let's calculate each scene's bounding volume here in App, before we
760 // cycle the pipeline. The cull traversal will calculate it anyway,
761 // but if we calculate it in App first before it gets calculated in
762 // the Cull thread, it will be more likely to stick for subsequent
763 // frames, so we won't have to recompute it each frame.
764 int num_drs = win->get_num_active_display_regions();
765 for (int i = 0; i < num_drs; ++i) {
766 PT(DisplayRegion) dr = win->get_active_display_region(i);
767 if (dr != nullptr) {
768 NodePath camera_np = dr->get_camera(current_thread);
769 if (!camera_np.is_empty()) {
770 Camera *camera = DCAST(Camera, camera_np.node());
771 NodePath scene = camera->get_scene();
772 if (scene.is_empty()) {
773 scene = camera_np.get_top(current_thread);
774 }
775 if (!scene.is_empty()) {
776 scene.get_bounds(current_thread);
777 }
778 }
779 }
780 }
781 }
782 }
783 _windows.swap(new_windows);
784
785 // Go ahead and release any textures' ram images for textures that were
786 // drawn in the previous frame.
787 {
788 MutexHolder holder2(_loaded_textures_lock);
789 LoadedTextures::iterator lti;
790 for (lti = _loaded_textures.begin(); lti != _loaded_textures.end(); ++lti) {
791 LoadedTexture &lt = (*lti);
792 if (lt._tex->get_image_modified() == lt._image_modified) {
793 lt._tex->texture_uploaded();
794 }
795 }
796 _loaded_textures.clear();
797 }
798
799 // Now it's time to do any drawing from the main frame--after all of the
800 // App code has executed, but before we begin the next frame.
801 _app.do_frame(this, current_thread);
802
803 // Grab each thread's mutex again after all windows have flipped, and wait
804 // for the thread to finish.
805 {
806 PStatTimer timer(_wait_pcollector, current_thread);
807 Threads::const_iterator ti;
808 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
809 RenderThread *thread = (*ti).second;
810 thread->_cv_mutex.acquire();
811
812 while (thread->_thread_state != TS_wait) {
813 thread->_cv_done.wait();
814 }
815 }
816 }
817
818#if defined(THREADED_PIPELINE) && defined(DO_PSTATS)
819 _cyclers_pcollector.set_level(_pipeline->get_num_cyclers());
820 _dirty_cyclers_pcollector.set_level(_pipeline->get_num_dirty_cyclers());
821
822#ifdef DEBUG_THREADS
824 _pipeline->iterate_all_cycler_types(pstats_count_cycler_type, this);
825 _pipeline->iterate_dirty_cycler_types(pstats_count_dirty_cycler_type, this);
826 }
827#endif // DEBUG_THREADS
828
829#endif // THREADED_PIPELINE && DO_PSTATS
830
834 TransformState::flush_level();
836
837 // Now cycle the pipeline and officially begin the next frame.
838#ifdef THREADED_PIPELINE
839 {
840 PStatTimer timer(_cycle_pcollector, current_thread);
841 _pipeline->cycle();
842 }
843#endif // THREADED_PIPELINE
844
845 global_clock->tick(current_thread);
846 if (global_clock->check_errors(current_thread)) {
847 throw_event("clock_error");
848 }
849
850#ifdef DO_PSTATS
851 PStatClient::main_tick();
852
853 // Reset our pcollectors that track data across the frame.
854 CullTraverser::_nodes_pcollector.clear_level();
855 CullTraverser::_geom_nodes_pcollector.clear_level();
856 CullTraverser::_geoms_pcollector.clear_level();
857 GeomCacheManager::_geom_cache_active_pcollector.clear_level();
858 GeomCacheManager::_geom_cache_record_pcollector.clear_level();
859 GeomCacheManager::_geom_cache_erase_pcollector.clear_level();
860 GeomCacheManager::_geom_cache_evict_pcollector.clear_level();
861
862 GraphicsStateGuardian::init_frame_pstats();
863
864 _transform_states_pcollector.set_level(TransformState::get_num_states());
865 _render_states_pcollector.set_level(RenderState::get_num_states());
866 if (pstats_unused_states) {
867 _transform_states_unused_pcollector.set_level(TransformState::get_num_unused_states());
868 _render_states_unused_pcollector.set_level(RenderState::get_num_unused_states());
869 }
870
871 _sw_sprites_pcollector.clear_level();
872
873 _cnode_volume_pcollector.clear_level();
874 _gnode_volume_pcollector.clear_level();
875 _geom_volume_pcollector.clear_level();
876 _node_volume_pcollector.clear_level();
877 _volume_pcollector.clear_level();
878 _test_pcollector.clear_level();
879 _volume_polygon_pcollector.clear_level();
880 _test_polygon_pcollector.clear_level();
881 _volume_plane_pcollector.clear_level();
882 _test_plane_pcollector.clear_level();
883 _volume_sphere_pcollector.clear_level();
884 _test_sphere_pcollector.clear_level();
885 _volume_box_pcollector.clear_level();
886 _test_box_pcollector.clear_level();
887 _volume_capsule_pcollector.clear_level();
888 _test_capsule_pcollector.clear_level();
889 _volume_inv_sphere_pcollector.clear_level();
890 _test_inv_sphere_pcollector.clear_level();
891 _volume_geom_pcollector.clear_level();
892 _test_geom_pcollector.clear_level();
893 _occlusion_untested_pcollector.clear_level();
894 _occlusion_passed_pcollector.clear_level();
895 _occlusion_failed_pcollector.clear_level();
896 _occlusion_tests_pcollector.clear_level();
897
901 size_t resident = VertexDataPage::get_global_lru(VertexDataPage::RC_resident)->get_total_size();
902 size_t compressed = VertexDataPage::get_global_lru(VertexDataPage::RC_compressed)->get_total_size();
904
906 size_t total_disk = save_file->get_total_file_size();
907 size_t used_disk = save_file->get_used_file_size();
908
909 _vertex_data_small_pcollector.set_level(small_buf);
910 _vertex_data_independent_pcollector.set_level(independent);
911 _vertex_data_pending_pcollector.set_level(pending);
912 _vertex_data_resident_pcollector.set_level(resident);
913 _vertex_data_compressed_pcollector.set_level(compressed);
914 _vertex_data_unused_disk_pcollector.set_level(total_disk - used_disk);
915 _vertex_data_used_disk_pcollector.set_level(used_disk);
916 }
917
918#endif // DO_PSTATS
919
921
922 // Now signal all of our threads to begin their next frame.
923 Threads::const_iterator ti;
924 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
925 RenderThread *thread = (*ti).second;
926 if (thread->_thread_state == TS_wait) {
927 thread->_thread_state = TS_do_frame;
928 thread->_cv_start.notify();
929 }
930 thread->_cv_mutex.release();
931 }
932
933 // Some threads may still be drawing, so indicate that we have to wait for
934 // those threads before we can flip.
935 _flip_state = _auto_flip ? FS_flip : FS_draw;
936 }
937
938 // Now the lock is released.
939
940 if (yield_timeslice) {
941 // Nap for a moment to yield the timeslice, to be polite to other running
942 // applications.
943 PStatTimer timer(_yield_pcollector, current_thread);
945 } else if (!Thread::is_true_threads()) {
946 PStatTimer timer(_yield_pcollector, current_thread);
948 }
949
950 // Anything that happens outside of GraphicsEngine::render_frame() is deemed
951 // to be App.
952 _app_pcollector.start();
953 _render_frame_pcollector.stop();
954}
955
956
957/**
958 * Fully opens (or closes) any windows that have recently been requested open
959 * or closed, without rendering any frames. It is not necessary to call this
960 * explicitly, since windows will be automatically opened or closed when the
961 * next frame is rendered, but you may call this if you want your windows now
962 * without seeing a frame go by.
963 */
965open_windows() {
966 Thread *current_thread = Thread::get_current_thread();
967
968 ReMutexHolder holder(_lock, current_thread);
969
970 pvector<PT(GraphicsOutput)> new_windows;
971 {
972 MutexHolder new_windows_holder(_new_windows_lock, current_thread);
973 if (_new_windows.empty()) {
974 return;
975 }
976
977 for (auto it = _new_windows.begin(); it != _new_windows.end(); ++it) {
978 GraphicsOutput *window = *it;
979
980 WindowRenderer *cull =
981 get_window_renderer(_threading_model.get_cull_name(),
982 _threading_model.get_cull_stage());
983 WindowRenderer *draw =
984 get_window_renderer(_threading_model.get_draw_name(),
985 _threading_model.get_draw_stage());
986
987 if (_threading_model.get_cull_sorting()) {
988 cull->add_window(cull->_cull, window);
989 draw->add_window(draw->_draw, window);
990 } else {
991 cull->add_window(cull->_cdraw, window);
992 }
993
994 // Ask the pipe which thread it prefers to run its windowing commands in
995 // (the "window thread"). This is the thread that handles the commands
996 // to open, resize, etc. the window. X requires this to be done in the
997 // app thread (along with all the other windows, since X is strictly
998 // single-threaded), but Windows requires this to be done in draw
999 // (because once an OpenGL context has been bound in a given thread, it
1000 // cannot subsequently be bound in any other thread, and we have to bind
1001 // a context in open_window()).
1002
1003 switch (window->get_pipe()->get_preferred_window_thread()) {
1004 case GraphicsPipe::PWT_app:
1005 _app.add_window(_app._window, window);
1006 break;
1007
1008 case GraphicsPipe::PWT_draw:
1009 draw->add_window(draw->_window, window);
1010 break;
1011 }
1012
1013 _windows.push_back(window);
1014 }
1015
1016 // Steal the list, since remove_window() may remove from _new_windows.
1017 new_windows.swap(_new_windows);
1018 }
1019
1020 do_resort_windows();
1021
1022 // We do it twice, to allow both cull and draw to process the window.
1023 for (int i = 0; i < 2; ++i) {
1024 _app.do_windows(this, current_thread);
1025 _app.do_pending(this, current_thread);
1026
1027 PStatTimer timer(_wait_pcollector, current_thread);
1028 Threads::const_iterator ti;
1029 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
1030 RenderThread *thread = (*ti).second;
1031 thread->_cv_mutex.acquire();
1032
1033 while (thread->_thread_state != TS_wait) {
1034 thread->_cv_done.wait();
1035 }
1036
1037 thread->_thread_state = TS_do_windows;
1038 thread->_cv_start.notify();
1039 thread->_cv_mutex.release();
1040 }
1041 }
1042
1043 // Now go through the list again to check whether they opened successfully.
1044 for (auto it = new_windows.begin(); it != new_windows.end(); ++it) {
1045 GraphicsOutput *window = *it;
1046 if (window->is_valid()) {
1047 do_add_gsg(window->get_gsg(), window->get_pipe());
1048 } else {
1049 remove_window(window);
1050 }
1051 }
1052}
1053
1054/**
1055 * Waits for all the threads that started drawing their last frame to finish
1056 * drawing. The windows are not yet flipped when this returns; see also
1057 * flip_frame(). It is not usually necessary to call this explicitly, unless
1058 * you need to see the previous frame right away.
1059 */
1061sync_frame() {
1062 Thread *current_thread = Thread::get_current_thread();
1063 ReMutexHolder holder(_lock, current_thread);
1064
1065 if (_flip_state == FS_draw) {
1066 do_sync_frame(current_thread);
1067 }
1068}
1069
1070
1071/**
1072 * Waits for all the threads that started drawing their last frame to finish
1073 * drawing. Returns when all threads have actually finished drawing, as
1074 * opposed to 'sync_frame' we seems to return once all draw calls have been
1075 * submitted. Calling 'flip_frame' after this function should immediately
1076 * cause a buffer flip. This function will only work in opengl right now, for
1077 * all other graphics pipelines it will simply return immediately. In opengl
1078 * it's a bit of a hack: it will attempt to read a single pixel from the frame
1079 * buffer to force the graphics card to finish drawing before it returns
1080 */
1082ready_flip() {
1083 Thread *current_thread = Thread::get_current_thread();
1084 ReMutexHolder holder(_lock, current_thread);
1085
1086 if (_flip_state == FS_draw) {
1087 do_ready_flip(current_thread);
1088 }
1089}
1090
1091/**
1092 * Waits for all the threads that started drawing their last frame to finish
1093 * drawing, and then flips all the windows. It is not usually necessary to
1094 * call this explicitly, unless you need to see the previous frame right away.
1095 */
1097flip_frame() {
1098 Thread *current_thread = Thread::get_current_thread();
1099 ReMutexHolder holder(_lock, current_thread);
1100
1101 if (_flip_state != FS_flip) {
1102 do_flip_frame(current_thread);
1103 }
1104}
1105
1106/**
1107 * Asks the indicated GraphicsStateGuardian to retrieve the texture memory
1108 * image of the indicated texture and store it in the texture's ram_image
1109 * field. The image can then be written to disk via Texture::write(), or
1110 * otherwise manipulated on the CPU.
1111 *
1112 * This is useful for retrieving the contents of a texture that has been
1113 * somehow generated on the graphics card, instead of having been loaded the
1114 * normal way via Texture::read() or Texture::load(). It is particularly
1115 * useful for getting the data associated with a compressed texture image.
1116 *
1117 * Since this requires a round-trip to the draw thread, it may require waiting
1118 * for the current thread to finish rendering if it is called in a
1119 * multithreaded environment. However, you can call this several consecutive
1120 * times on different textures for little additional cost.
1121 *
1122 * If the texture has not yet been loaded to the GSG in question, it will be
1123 * loaded immediately.
1124 *
1125 * The return value is true if the operation is successful, false otherwise.
1126 */
1129 ReMutexHolder holder(_lock);
1130
1131 string draw_name = gsg->get_threading_model().get_draw_name();
1132 if (draw_name.empty()) {
1133 // A single-threaded environment. No problem.
1134 return gsg->extract_texture_data(tex);
1135
1136 } else {
1137 // A multi-threaded environment. We have to wait until the draw thread
1138 // has finished its current task.
1139 WindowRenderer *wr = get_window_renderer(draw_name, 0);
1140 RenderThread *thread = (RenderThread *)wr;
1141 MutexHolder cv_holder(thread->_cv_mutex);
1142
1143 while (thread->_thread_state != TS_wait) {
1144 thread->_cv_done.wait();
1145 }
1146
1147 // Temporarily set this so that it accesses data from the current thread.
1148 int pipeline_stage = Thread::get_current_pipeline_stage();
1149 int draw_pipeline_stage = thread->get_pipeline_stage();
1150 thread->set_pipeline_stage(pipeline_stage);
1151
1152 // Now that the draw thread is idle, signal it to do the extraction task.
1153 thread->_gsg = gsg;
1154 thread->_texture = tex;
1155 thread->_thread_state = TS_do_extract;
1156 thread->_cv_start.notify();
1157 thread->_cv_mutex.release();
1158 thread->_cv_mutex.acquire();
1159
1160 //XXX is this necessary, or is acquiring the mutex enough?
1161 while (thread->_thread_state != TS_wait) {
1162 thread->_cv_done.wait();
1163 }
1164
1165 thread->set_pipeline_stage(draw_pipeline_stage);
1166 thread->_gsg = nullptr;
1167 thread->_texture = nullptr;
1168 return thread->_result;
1169 }
1170}
1171
1172/**
1173 * Asks the indicated GraphicsStateGuardian to dispatch the compute shader in
1174 * the given ShaderAttrib using the given work group counts. This can act as
1175 * an interface for running a one-off compute shader, without having to store
1176 * it in the scene graph using a ComputeNode.
1177 *
1178 * Since this requires a round-trip to the draw thread, it may require waiting
1179 * for the current thread to finish rendering if it is called in a
1180 * multithreaded environment. However, you can call this several consecutive
1181 * times on different textures for little additional cost.
1182 *
1183 * The return value is true if the operation is successful, false otherwise.
1184 */
1186dispatch_compute(const LVecBase3i &work_groups, const ShaderAttrib *sattr, GraphicsStateGuardian *gsg) {
1187 const Shader *shader = sattr->get_shader();
1188 nassertv(shader != nullptr);
1189 nassertv(gsg != nullptr);
1190
1191 ReMutexHolder holder(_lock);
1192
1193 CPT(RenderState) state = RenderState::make(sattr);
1194
1195 string draw_name = gsg->get_threading_model().get_draw_name();
1196 if (draw_name.empty()) {
1197 // A single-threaded environment. No problem.
1198 gsg->push_group_marker(std::string("Compute ") + shader->get_filename(Shader::ST_compute).get_basename());
1199 gsg->set_state_and_transform(state, TransformState::make_identity());
1200 gsg->dispatch_compute(work_groups[0], work_groups[1], work_groups[2]);
1201 gsg->pop_group_marker();
1202
1203 } else {
1204 // A multi-threaded environment. We have to wait until the draw thread
1205 // has finished its current task.
1206 WindowRenderer *wr = get_window_renderer(draw_name, 0);
1207 RenderThread *thread = (RenderThread *)wr;
1208 MutexHolder cv_holder(thread->_cv_mutex);
1209
1210 while (thread->_thread_state != TS_wait) {
1211 thread->_cv_done.wait();
1212 }
1213
1214 // Temporarily set this so that it accesses data from the current thread.
1215 int pipeline_stage = Thread::get_current_pipeline_stage();
1216 int draw_pipeline_stage = thread->get_pipeline_stage();
1217 thread->set_pipeline_stage(pipeline_stage);
1218
1219 // Now that the draw thread is idle, signal it to do the compute task.
1220 thread->_gsg = gsg;
1221 thread->_state = state.p();
1222 thread->_work_groups = work_groups;
1223 thread->_thread_state = TS_do_compute;
1224 thread->_cv_start.notify();
1225 thread->_cv_mutex.release();
1226 thread->_cv_mutex.acquire();
1227
1228 //XXX is this necessary, or is acquiring the mutex enough?
1229 while (thread->_thread_state != TS_wait) {
1230 thread->_cv_done.wait();
1231 }
1232
1233 thread->set_pipeline_stage(draw_pipeline_stage);
1234 thread->_gsg = nullptr;
1235 thread->_state = nullptr;
1236 }
1237}
1238
1239/**
1240 *
1241 */
1242GraphicsEngine *GraphicsEngine::
1243get_global_ptr() {
1244 if (_global_ptr == nullptr) {
1245 _global_ptr = new GraphicsEngine;
1246 PandaNode::set_scene_root_func(&scene_root_func);
1247 }
1248 return _global_ptr;
1249}
1250
1251/**
1252 * This method is called by the GraphicsStateGuardian after a texture has been
1253 * successfully uploaded to graphics memory. It is intended as a callback so
1254 * the texture can release its RAM image, if _keep_ram_image is false.
1255 *
1256 * Normally, this is not called directly except by the GraphicsStateGuardian.
1257 * It will be called in the draw thread.
1258 */
1261 MutexHolder holder(_loaded_textures_lock);
1262 // We defer this until the end of the frame; multiple GSG's might be
1263 // rendering the texture within the same frame, and we don't want to dump
1264 // the texture image until they've all had a chance at it.
1265 _loaded_textures.push_back(LoadedTexture());
1266 LoadedTexture &lt = _loaded_textures.back();
1267 lt._tex = tex;
1268 lt._image_modified = tex->get_image_modified();
1269// Usually only called by DisplayRegion::do_cull.
1270}
1271
1272/**
1273 * Called by DisplayRegion::do_get_screenshot
1274 */
1275PT(Texture) GraphicsEngine::
1276do_get_screenshot(DisplayRegion *region, GraphicsStateGuardian *gsg) {
1277 // A multi-threaded environment. We have to wait until the draw thread
1278 // has finished its current task.
1279
1280 ReMutexHolder holder(_lock);
1281
1282 const std::string &draw_name = gsg->get_threading_model().get_draw_name();
1283 WindowRenderer *wr = get_window_renderer(draw_name, 0);
1284 RenderThread *thread = (RenderThread *)wr;
1285 MutexHolder cv_holder(thread->_cv_mutex);
1286
1287 while (thread->_thread_state != TS_wait) {
1288 thread->_cv_done.wait();
1289 }
1290
1291 // Now that the draw thread is idle, signal it to do the extraction task.
1292 thread->_region = region;
1293 thread->_thread_state = TS_do_screenshot;
1294 thread->_cv_start.notify();
1295 thread->_cv_mutex.release();
1296 thread->_cv_mutex.acquire();
1297
1298 //XXX is this necessary, or is acquiring the mutex enough?
1299 while (thread->_thread_state != TS_wait) {
1300 thread->_cv_done.wait();
1301 }
1302
1303 PT(Texture) tex = std::move(thread->_texture);
1304 thread->_region = nullptr;
1305 thread->_texture = nullptr;
1306 return tex;
1307}
1308
1309/**
1310 * Fires off a cull traversal using the indicated camera.
1311 */
1313do_cull(CullHandler *cull_handler, SceneSetup *scene_setup,
1314 GraphicsStateGuardian *gsg, Thread *current_thread) {
1315 DisplayRegion *dr = scene_setup->get_display_region();
1316 PStatTimer timer(dr->get_cull_region_pcollector(), current_thread);
1317
1318 CullTraverser *trav = dr->get_cull_traverser();
1319 trav->set_cull_handler(cull_handler);
1320 trav->set_scene(scene_setup, gsg, dr->get_incomplete_render());
1321
1322 trav->set_view_frustum(nullptr);
1323 if (view_frustum_cull) {
1324 // If we're to be performing view-frustum culling, determine the bounding
1325 // volume associated with the current viewing frustum.
1326
1327 // First, we have to get the current viewing frustum, which comes from the
1328 // lens.
1329 PT(BoundingVolume) bv = scene_setup->get_cull_bounds();
1330
1331 if (bv != nullptr && !bv->is_infinite() &&
1332 bv->as_geometric_bounding_volume() != nullptr) {
1333 // Transform it into the appropriate coordinate space.
1334 PT(GeometricBoundingVolume) local_frustum;
1335 local_frustum = bv->make_copy()->as_geometric_bounding_volume();
1336 nassertv(!local_frustum.is_null());
1337
1338 NodePath scene_parent = scene_setup->get_scene_root().get_parent(current_thread);
1339 CPT(TransformState) cull_center_transform =
1340 scene_setup->get_cull_center().get_transform(scene_parent, current_thread);
1341 local_frustum->xform(cull_center_transform->get_mat());
1342
1343 trav->set_view_frustum(local_frustum);
1344 }
1345 }
1346
1347 trav->traverse(scene_setup->get_scene_root());
1348 trav->end_traverse();
1349}
1350
1351
1352/**
1353 * This function is added to PandaNode::scene_root_func to implement
1354 * PandaNode::is_scene_root().
1355 */
1356bool GraphicsEngine::
1357scene_root_func(const PandaNode *node) {
1358 return _global_ptr->is_scene_root(node);
1359}
1360
1361/**
1362 * Returns true if the indicated node is known to be the render root of some
1363 * active DisplayRegion associated with this GraphicsEngine, false otherwise.
1364 */
1365bool GraphicsEngine::
1366is_scene_root(const PandaNode *node) {
1367 Thread *current_thread = Thread::get_current_thread();
1368
1369 Windows::const_iterator wi;
1370 for (wi = _windows.begin(); wi != _windows.end(); ++wi) {
1371 GraphicsOutput *win = (*wi);
1372 if (win->is_active() && win->get_gsg()->is_active()) {
1373 int num_display_regions = win->get_num_active_display_regions();
1374 for (int i = 0; i < num_display_regions; i++) {
1375 PT(DisplayRegion) dr = win->get_active_display_region(i);
1376 if (dr != nullptr) {
1377 NodePath camera = dr->get_camera();
1378 if (camera.is_empty()) {
1379 continue;
1380 }
1381
1382 Camera *camera_node;
1383 DCAST_INTO_R(camera_node, camera.node(), false);
1384 if (!camera_node->is_active()) {
1385 continue;
1386 }
1387
1388 NodePath scene_root = camera_node->get_scene();
1389 if (scene_root.is_empty()) {
1390 // If there's no explicit scene specified, use whatever scene the
1391 // camera is parented within. This is the normal and preferred
1392 // case; the use of an explicit scene is now deprecated.
1393 scene_root = camera.get_top(current_thread);
1394 }
1395
1396 if (scene_root.node() == node) {
1397 return true;
1398 }
1399 }
1400 }
1401 }
1402 }
1403
1404 return false;
1405}
1406
1407/**
1408 * Changes the sort value of a particular window (or buffer) on the
1409 * GraphicsEngine. This requires securing the mutex.
1410 *
1411 * Users shouldn't call this directly; use GraphicsOutput::set_sort() instead.
1412 */
1413void GraphicsEngine::
1414set_window_sort(GraphicsOutput *window, int sort) {
1415 ReMutexHolder holder(_lock);
1416 window->_sort = sort;
1417 _windows_sorted = false;
1418}
1419
1420/**
1421 * This is called in the cull+draw thread by individual RenderThread objects
1422 * during the frame rendering. It culls the geometry and immediately draws
1423 * it, without first collecting it into bins. This is used when the threading
1424 * model begins with the "-" character.
1425 */
1426void GraphicsEngine::
1427cull_and_draw_together(GraphicsEngine::Windows wlist,
1428 Thread *current_thread) {
1429 PStatTimer timer(_cull_pcollector, current_thread);
1430
1431 size_t wlist_size = wlist.size();
1432 for (size_t wi = 0; wi < wlist_size; ++wi) {
1433 GraphicsOutput *win = wlist[wi];
1434 if (win->is_active() && win->get_gsg()->is_active()) {
1435 if (win->flip_ready()) {
1436 {
1437 PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread);
1438 win->begin_flip();
1439 }
1440 {
1441 PStatTimer timer(GraphicsEngine::_flip_end_pcollector, current_thread);
1442 win->end_flip();
1443 }
1444 }
1445
1446 if (win->begin_frame(GraphicsOutput::FM_render, current_thread)) {
1447 if (win->is_any_clear_active()) {
1448 GraphicsStateGuardian *gsg = win->get_gsg();
1449 PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
1450 gsg->push_group_marker("Clear");
1451 win->clear(current_thread);
1452 gsg->pop_group_marker();
1453 }
1454
1455 int num_display_regions = win->get_num_active_display_regions();
1456 for (int i = 0; i < num_display_regions; i++) {
1457 PT(DisplayRegion) dr = win->get_active_display_region(i);
1458 if (dr != nullptr) {
1459 cull_and_draw_together(win, dr, current_thread);
1460 }
1461 }
1462 win->end_frame(GraphicsOutput::FM_render, current_thread);
1463
1464 if (_auto_flip) {
1465 if (win->flip_ready()) {
1466 {
1467 PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread);
1468 win->begin_flip();
1469 }
1470 {
1471 PStatTimer timer(GraphicsEngine::_flip_end_pcollector, current_thread);
1472 win->end_flip();
1473 }
1474 }
1475 }
1476 }
1477 }
1478 }
1479}
1480
1481/**
1482 * Called only from within the inner loop in cull_and_draw_together(), above.
1483 */
1484void GraphicsEngine::
1485cull_and_draw_together(GraphicsOutput *win, DisplayRegion *dr,
1486 Thread *current_thread) {
1487 GraphicsStateGuardian *gsg = win->get_gsg();
1488 nassertv(gsg != nullptr);
1489
1490 gsg->push_group_marker(dr->get_debug_name());
1491
1492 PT(SceneSetup) scene_setup;
1493
1494 {
1495 DisplayRegionPipelineReader dr_reader(dr, current_thread);
1496 win->change_scenes(&dr_reader);
1497 gsg->prepare_display_region(&dr_reader);
1498
1499 if (dr_reader.is_any_clear_active()) {
1500 PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
1501 gsg->clear(dr);
1502 }
1503
1504 scene_setup = setup_scene(gsg, &dr_reader);
1505 }
1506
1507 if (scene_setup == nullptr) {
1508 // Never mind.
1509
1510 } else if (dr->is_stereo()) {
1511 // Don't draw stereo DisplayRegions directly.
1512
1513 } else if (!gsg->set_scene(scene_setup)) {
1514 // The scene or lens is inappropriate somehow.
1515 display_cat.error()
1516 << gsg->get_type() << " cannot render scene with specified lens.\n";
1517
1518 } else {
1519 DrawCullHandler cull_handler(gsg);
1520 if (gsg->begin_scene()) {
1521 CallbackObject *cbobj = dr->get_cull_callback();
1522 if (cbobj != nullptr) {
1523 // Issue the cull callback on this DisplayRegion.
1524 DisplayRegionCullCallbackData cbdata(&cull_handler, scene_setup);
1525 cbobj->do_callback(&cbdata);
1526
1527 // The callback has taken care of the culling.
1528
1529 } else {
1530 // Perform the cull normally.
1531 dr->do_cull(&cull_handler, scene_setup, gsg, current_thread);
1532 }
1533
1534 gsg->end_scene();
1535 }
1536 }
1537
1538 gsg->pop_group_marker();
1539}
1540
1541/**
1542 * This is called in the cull thread by individual RenderThread objects during
1543 * the frame rendering. It collects the geometry into bins in preparation for
1544 * drawing.
1545 */
1546void GraphicsEngine::
1547cull_to_bins(GraphicsEngine::Windows wlist, Thread *current_thread) {
1548 PStatTimer timer(_cull_pcollector, current_thread);
1549
1550 _singular_warning_last_frame = _singular_warning_this_frame;
1551 _singular_warning_this_frame = false;
1552
1553 // Keep track of the cameras we have already used in this thread to render
1554 // DisplayRegions.
1555 typedef pmap<CullKey, DisplayRegion *> AlreadyCulled;
1556 AlreadyCulled already_culled;
1557
1558 size_t wlist_size = wlist.size();
1559 for (size_t wi = 0; wi < wlist_size; ++wi) {
1560 GraphicsOutput *win = wlist[wi];
1561 if (win->is_active() && win->get_gsg()->is_active()) {
1562 GraphicsStateGuardian *gsg = win->get_gsg();
1563 PStatTimer timer(win->get_cull_window_pcollector(), current_thread);
1564 int num_display_regions = win->get_num_active_display_regions();
1565 for (int i = 0; i < num_display_regions; ++i) {
1566 PT(DisplayRegion) dr = win->get_active_display_region(i);
1567 if (dr != nullptr) {
1568 PT(SceneSetup) scene_setup;
1569 PT(CullResult) cull_result;
1570 CullKey key;
1571 {
1572 PStatTimer timer(_cull_setup_pcollector, current_thread);
1573 DisplayRegionPipelineReader dr_reader(dr, current_thread);
1574 scene_setup = setup_scene(gsg, &dr_reader);
1575 if (scene_setup == nullptr) {
1576 continue;
1577 }
1578
1579 key._gsg = gsg;
1580 key._camera = dr_reader.get_camera();
1581 key._lens_index = dr_reader.get_lens_index();
1582 }
1583
1584 AlreadyCulled::iterator aci = already_culled.insert(AlreadyCulled::value_type(std::move(key), nullptr)).first;
1585 if ((*aci).second == nullptr) {
1586 // We have not used this camera already in this thread. Perform
1587 // the cull operation.
1588 cull_result = dr->get_cull_result(current_thread);
1589 if (cull_result != nullptr) {
1590 cull_result = cull_result->make_next();
1591 } else {
1592 // This DisplayRegion has no cull results; draw it.
1593 cull_result = new CullResult(gsg, dr->get_draw_region_pcollector());
1594 }
1595 (*aci).second = dr;
1596 cull_to_bins(win, gsg, dr, scene_setup, cull_result, current_thread);
1597
1598 } else {
1599 // We have already culled a scene using this camera in this
1600 // thread, and now we're being asked to cull another scene using
1601 // the same camera. (Maybe this represents two different
1602 // DisplayRegions for the left and right channels of a stereo
1603 // image.) Of course, the cull result will be the same, so just
1604 // use the result from the other DisplayRegion.
1605 DisplayRegion *other_dr = (*aci).second;
1606 cull_result = other_dr->get_cull_result(current_thread);
1607 }
1608
1609 // Save the results for next frame.
1610 dr->set_cull_result(std::move(cull_result), MOVE(scene_setup), current_thread);
1611 }
1612 }
1613 }
1614 }
1615}
1616
1617/**
1618 * Called only within the inner loop of cull_to_bins(), above.
1619 */
1620void GraphicsEngine::
1621cull_to_bins(GraphicsOutput *win, GraphicsStateGuardian *gsg,
1622 DisplayRegion *dr, SceneSetup *scene_setup,
1623 CullResult *cull_result, Thread *current_thread) {
1624
1625 BinCullHandler cull_handler(cull_result);
1626 CallbackObject *cbobj = dr->get_cull_callback();
1627 if (cbobj != nullptr) {
1628 // Issue the cull callback on this DisplayRegion.
1629 DisplayRegionCullCallbackData cbdata(&cull_handler, scene_setup);
1630 cbobj->do_callback(&cbdata);
1631
1632 // The callback has taken care of the culling.
1633
1634 } else {
1635 // Perform the cull normally.
1636 dr->do_cull(&cull_handler, scene_setup, gsg, current_thread);
1637 }
1638
1639 PStatTimer timer(_cull_sort_pcollector, current_thread);
1640 cull_result->finish_cull(scene_setup, current_thread);
1641}
1642
1643/**
1644 * This is called in the draw thread by individual RenderThread objects during
1645 * the frame rendering. It issues the graphics commands to draw the objects
1646 * that have been collected into bins by a previous call to cull_to_bins().
1647 */
1648void GraphicsEngine::
1649draw_bins(const GraphicsEngine::Windows &wlist, Thread *current_thread) {
1650 nassertv(wlist.verify_list());
1651
1652 size_t wlist_size = wlist.size();
1653 for (size_t wi = 0; wi < wlist_size; ++wi) {
1654 GraphicsOutput *win = wlist[wi];
1655
1656 if (win->is_active()) {
1657 GraphicsStateGuardian *gsg = win->get_gsg();
1658
1659 GraphicsOutput *host = win->get_host();
1660 if (host->flip_ready()) {
1661 {
1662 // We can't use a PStatGPUTimer before begin_frame, so when using
1663 // GPU timing, it is advisable to set auto-flip to #t.
1664 PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread);
1665 host->begin_flip();
1666 }
1667 {
1668 PStatTimer timer(GraphicsEngine::_flip_end_pcollector, current_thread);
1669 host->end_flip();
1670 }
1671 }
1672
1673 if (win->begin_frame(GraphicsOutput::FM_render, current_thread)) {
1674 // We have to place this collector inside begin_frame, because we need
1675 // a current context for PStatGPUTimer to work.
1676 {
1677 PStatGPUTimer timer(gsg, win->get_draw_window_pcollector(), current_thread);
1678 if (win->is_any_clear_active()) {
1679 PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
1680 win->get_gsg()->push_group_marker("Clear");
1681 win->clear(current_thread);
1682 win->get_gsg()->pop_group_marker();
1683 }
1684
1685 if (display_cat.is_spam()) {
1686 display_cat.spam()
1687 << "Drawing window " << win->get_name() << "\n";
1688 }
1689 int num_display_regions = win->get_num_active_display_regions();
1690 for (int i = 0; i < num_display_regions; ++i) {
1691 PT(DisplayRegion) dr = win->get_active_display_region(i);
1692 if (dr != nullptr) {
1693 do_draw(win, gsg, dr, current_thread);
1694 }
1695 }
1696 }
1697 win->end_frame(GraphicsOutput::FM_render, current_thread);
1698
1699 if (_auto_flip) {
1700#ifdef DO_PSTATS
1701 // This is a good time to perform a latency query.
1702 if (gsg->get_timer_queries_active()) {
1703 gsg->issue_timer_query(GraphicsStateGuardian::_command_latency_pcollector.get_index());
1704 }
1705#endif
1706
1707 if (win->flip_ready()) {
1708 {
1709 // begin_flip doesn't do anything interesting, let's not waste
1710 // two timer queries on that.
1711 PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread);
1712 win->begin_flip();
1713 }
1714 {
1715 PStatGPUTimer timer(gsg, GraphicsEngine::_flip_end_pcollector, current_thread);
1716 win->end_flip();
1717 }
1718 }
1719 }
1720
1721 } else {
1722 if (display_cat.is_spam()) {
1723 display_cat.spam()
1724 << "Not drawing window " << win->get_name() << "\n";
1725 }
1726 }
1727
1728 } else {
1729 if (display_cat.is_spam()) {
1730 display_cat.spam()
1731 << "Window " << win->get_name() << " is inactive\n";
1732 }
1733 }
1734 }
1735}
1736
1737/**
1738 * Called in the draw thread, this calls make_context() on each window on the
1739 * list to guarantee its gsg and graphics context both get created.
1740 */
1741void GraphicsEngine::
1742make_contexts(const GraphicsEngine::Windows &wlist, Thread *current_thread) {
1743 Windows::const_iterator wi;
1744 for (wi = wlist.begin(); wi != wlist.end(); ++wi) {
1745 GraphicsOutput *win = (*wi);
1746 if (win->begin_frame(GraphicsOutput::FM_refresh, current_thread)) {
1747 win->end_frame(GraphicsOutput::FM_refresh, current_thread);
1748 }
1749 }
1750}
1751
1752/**
1753 * This is called by the RenderThread object to process all the windows events
1754 * (resize, etc.) for the given list of windows. This is run in the window
1755 * thread.
1756 */
1757void GraphicsEngine::
1758process_events(const GraphicsEngine::Windows &wlist, Thread *current_thread) {
1759 // We're not using a vector iterator here, since it's possible that the
1760 // window list changes in an event, which would invalidate the iterator and
1761 // cause a crash.
1762 for (size_t i = 0; i < wlist.size(); ++i) {
1763 wlist[i]->process_events();
1764 }
1765}
1766
1767/**
1768 * This is called by the RenderThread object to flip the buffers for all of
1769 * the non-single-buffered windows in the given list. This is run in the draw
1770 * thread.
1771 */
1772void GraphicsEngine::
1773flip_windows(const GraphicsEngine::Windows &wlist, Thread *current_thread) {
1774 size_t num_windows = wlist.size();
1775 size_t warray_size = num_windows * sizeof(GraphicsOutput *);
1776 size_t warray_count = 0;
1777 GraphicsOutput **warray = (GraphicsOutput **)alloca(warray_size);
1778
1779 size_t i;
1780 for (i = 0; i < num_windows; ++i) {
1781 GraphicsOutput *win = wlist[i];
1782 if (win->flip_ready()) {
1783 nassertv(warray_count < num_windows);
1784 warray[warray_count] = win;
1785 ++warray_count;
1786
1787 PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread);
1788 win->begin_flip();
1789 }
1790 }
1791
1792 for (i = 0; i < warray_count; ++i) {
1793 GraphicsOutput *win = warray[i];
1794 PStatTimer timer(GraphicsEngine::_flip_end_pcollector, current_thread);
1795 win->end_flip();
1796 }
1797}
1798
1799/**
1800 * This is called by the RenderThread object to flip the buffers for all of
1801 * the non-single-buffered windows in the given list. This is run in the draw
1802 * thread.
1803 */
1804void GraphicsEngine::
1805ready_flip_windows(const GraphicsEngine::Windows &wlist, Thread *current_thread) {
1806 Windows::const_iterator wi;
1807 for (wi = wlist.begin(); wi != wlist.end(); ++wi) {
1808 GraphicsOutput *win = (*wi);
1809 if (win->flip_ready()) {
1810 PStatTimer timer(GraphicsEngine::_flip_begin_pcollector, current_thread);
1811 win->ready_flip();
1812 }
1813 }
1814}
1815
1816/**
1817 * The implementation of sync_frame(). We assume _lock is already held before
1818 * this method is called.
1819 */
1820void GraphicsEngine::
1821do_sync_frame(Thread *current_thread) {
1822 nassertv(_lock.debug_is_locked());
1823
1824 // Statistics
1825 PStatTimer timer(_sync_pcollector, current_thread);
1826
1827 nassertv(_flip_state == FS_draw);
1828
1829 // Wait for all the threads to finish their current frame. Grabbing and
1830 // releasing the mutex should achieve that.
1831 Threads::const_iterator ti;
1832 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
1833 RenderThread *thread = (*ti).second;
1834 thread->_cv_mutex.acquire();
1835 thread->_cv_mutex.release();
1836 }
1837
1838 _flip_state = FS_sync;
1839}
1840
1841/**
1842 * Wait until all draw calls have finished drawing and the frame is ready to
1843 * flip
1844 */
1845void GraphicsEngine::
1846do_ready_flip(Thread *current_thread) {
1847 nassertv(_lock.debug_is_locked());
1848
1849 // Statistics
1850 PStatTimer timer(_sync_pcollector, current_thread);
1851
1852 nassertv(_flip_state == FS_draw);
1853
1854 // Wait for all the threads to finish their current frame. Grabbing and
1855 // releasing the mutex should achieve that.
1856 Threads::const_iterator ti;
1857 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
1858 RenderThread *thread = (*ti).second;
1859 thread->_cv_mutex.acquire();
1860 thread->_cv_mutex.release();
1861 }
1862 _app.do_ready_flip(this,current_thread);
1863 _flip_state = FS_sync;
1864}
1865
1866/**
1867 * The implementation of flip_frame(). We assume _lock is already held before
1868 * this method is called.
1869 */
1870void GraphicsEngine::
1871do_flip_frame(Thread *current_thread) {
1872 nassertv(_lock.debug_is_locked());
1873
1874 // Statistics
1875 PStatTimer timer(_flip_pcollector, current_thread);
1876
1877 nassertv(_flip_state == FS_draw || _flip_state == FS_sync);
1878
1879 // First, wait for all the threads to finish their current frame, if
1880 // necessary. Grabbing the mutex (and waiting for TS_wait) should achieve
1881 // that.
1882 {
1883 PStatTimer timer(_wait_pcollector, current_thread);
1884 Threads::const_iterator ti;
1885 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
1886 RenderThread *thread = (*ti).second;
1887 thread->_cv_mutex.acquire();
1888
1889 while (thread->_thread_state != TS_wait) {
1890 thread->_cv_done.wait();
1891 }
1892 }
1893 }
1894
1895 // Now signal all of our threads to flip the windows.
1896 _app.do_flip(this, current_thread);
1897
1898 {
1899 Threads::const_iterator ti;
1900 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
1901 RenderThread *thread = (*ti).second;
1902 nassertv(thread->_thread_state == TS_wait);
1903 thread->_thread_state = TS_do_flip;
1904 thread->_cv_start.notify();
1905 thread->_cv_mutex.release();
1906 }
1907 }
1908
1909 _flip_state = FS_flip;
1910}
1911
1912/**
1913 * Returns a new SceneSetup object appropriate for rendering the scene from
1914 * the indicated camera, or NULL if the scene should not be rendered for some
1915 * reason.
1916 */
1917PT(SceneSetup) GraphicsEngine::
1919 Thread *current_thread = dr->get_current_thread();
1920 PStatTimer timer(_cull_setup_pcollector, current_thread);
1921
1922 GraphicsOutput *window = dr->get_window();
1923 // The window pointer shouldn't be NULL, since we presumably got to this
1924 // particular DisplayRegion by walking through a list on a window.
1925 nassertr(window != nullptr, nullptr);
1926
1927 NodePath camera = dr->get_camera();
1928 if (camera.is_empty()) {
1929 // No camera, no draw.
1930 return nullptr;
1931 }
1932
1933 Camera *camera_node;
1934 DCAST_INTO_R(camera_node, camera.node(), nullptr);
1935
1936 if (!camera_node->is_active()) {
1937 // Camera inactive, no draw.
1938 return nullptr;
1939 }
1940 camera_node->cleanup_aux_scene_data(current_thread);
1941
1942 int lens_index = dr->get_lens_index();
1943 Lens *lens = camera_node->get_lens(lens_index);
1944 if (lens == nullptr || !camera_node->get_lens_active(lens_index)) {
1945 // No lens, no draw.
1946 return nullptr;
1947 }
1948
1949 NodePath scene_root = camera_node->get_scene();
1950 if (scene_root.is_empty()) {
1951 // If there's no explicit scene specified, use whatever scene the camera
1952 // is parented within. This is the normal and preferred case; the use of
1953 // an explicit scene is now deprecated.
1954 scene_root = camera.get_top(current_thread);
1955 }
1956
1957 PT(SceneSetup) scene_setup = new SceneSetup;
1958
1959 // We will need both the camera transform (the net transform to the camera
1960 // from the scene) and the world transform (the camera transform inverse, or
1961 // the net transform to the scene from the camera). These are actually
1962 // defined from the parent of the scene_root, because the scene_root's own
1963 // transform is immediately applied to these during rendering. (Normally,
1964 // the parent of the scene_root is the empty NodePath, although it need not
1965 // be.)
1966 NodePath scene_parent = scene_root.get_parent(current_thread);
1967 CPT(TransformState) camera_transform = camera.get_transform(scene_parent, current_thread);
1968 CPT(TransformState) world_transform = scene_parent.get_transform(camera, current_thread);
1969
1970 if (camera_transform->is_invalid()) {
1971 // There must be a singular transform over the scene.
1972 if (!_singular_warning_last_frame) {
1973 display_cat.warning()
1974 << "Scene " << scene_root << " has net scale ("
1975 << scene_root.get_scale(NodePath()) << "); cannot render.\n";
1976 _singular_warning_this_frame = true;
1977 }
1978 return nullptr;
1979 }
1980
1981 if (world_transform->is_invalid()) {
1982 // There must be a singular transform over the camera.
1983 if (!_singular_warning_last_frame) {
1984 display_cat.warning()
1985 << "Camera " << camera << " has net scale ("
1986 << camera.get_scale(NodePath()) << "); cannot render.\n";
1987 }
1988 _singular_warning_this_frame = true;
1989 return nullptr;
1990 }
1991
1992 CPT(RenderState) initial_state = camera_node->get_initial_state();
1993
1994 if (window->get_inverted()) {
1995 // If the window is to be inverted, we must set the inverted flag on the
1996 // SceneSetup object, so that the GSG will be able to invert the
1997 // projection matrix at the last minute.
1998 scene_setup->set_inverted(true);
1999
2000 // This also means we need to globally invert the sense of polygon vertex
2001 // ordering.
2002 initial_state = initial_state->compose(get_invert_polygon_state());
2003 }
2004
2005 scene_setup->set_display_region(dr->get_object());
2006 scene_setup->set_viewport_size(dr->get_pixel_width(), dr->get_pixel_height());
2007 scene_setup->set_scene_root(scene_root);
2008 scene_setup->set_camera_path(camera);
2009 scene_setup->set_camera_node(camera_node);
2010 scene_setup->set_lens(lens);
2011 scene_setup->set_initial_state(initial_state);
2012 scene_setup->set_camera_transform(camera_transform);
2013 scene_setup->set_world_transform(world_transform);
2014
2015 CPT(TransformState) cs_transform = gsg->get_cs_transform_for(lens->get_coordinate_system());
2016 scene_setup->set_cs_transform(cs_transform);
2017
2018 CPT(TransformState) cs_world_transform = cs_transform->compose(world_transform);
2019 scene_setup->set_cs_world_transform(cs_world_transform);
2020
2021 return scene_setup;
2022}
2023
2024/**
2025 * Draws the previously-culled scene.
2026 */
2027void GraphicsEngine::
2028do_draw(GraphicsOutput *win, GraphicsStateGuardian *gsg, DisplayRegion *dr, Thread *current_thread) {
2029 // Statistics
2030 PStatGPUTimer timer(gsg, dr->get_draw_region_pcollector(), current_thread);
2031
2032 gsg->push_group_marker(dr->get_debug_name());
2033
2034 PT(CullResult) cull_result;
2035 PT(SceneSetup) scene_setup;
2036 {
2037 DisplayRegion::CDCullReader cdata(dr->_cycler_cull, current_thread);
2038 cull_result = cdata->_cull_result;
2039 scene_setup = cdata->_scene_setup;
2040 }
2041
2042 CallbackObject *cbobj;
2043
2044 {
2045 DisplayRegionPipelineReader dr_reader(dr, current_thread);
2046 win->change_scenes(&dr_reader);
2047 gsg->prepare_display_region(&dr_reader);
2048 if (dr_reader.is_any_clear_active()) {
2049 PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
2050 gsg->clear(dr_reader.get_object());
2051 }
2052
2053 cbobj = dr_reader.get_draw_callback();
2054 }
2055
2056 if (cbobj != nullptr) {
2057 // Issue the draw callback on this DisplayRegion.
2058
2059 // Set the GSG to the initial state. We disable depth testing since that
2060 // is the default OpenGL state, and some libraries (eg. Kivy) expect that.
2061 static CPT(RenderState) state = RenderState::make(
2062 DepthTestAttrib::make(DepthTestAttrib::M_none));
2063 gsg->clear_before_callback();
2064 gsg->set_state_and_transform(state, TransformState::make_identity());
2065
2066 DisplayRegionDrawCallbackData cbdata(cull_result, scene_setup);
2067 cbobj->do_callback(&cbdata);
2068
2069 // We don't trust the state the callback may have left us in.
2071
2072 } else if (cull_result == nullptr || scene_setup == nullptr) {
2073 // Nothing to see here.
2074
2075 } else if (dr->is_stereo()) {
2076 // We don't actually draw the stereo DisplayRegions. These are just
2077 // placeholders; we draw the individual left and right eyes instead. (We
2078 // might still clear the stereo DisplayRegions, though, since it's
2079 // probably faster to clear right and left channels in one pass, than to
2080 // clear them in two separate passes.)
2081
2082 } else if (!gsg->set_scene(scene_setup)) {
2083 // The scene or lens is inappropriate somehow.
2084 display_cat.error()
2085 << gsg->get_type() << " cannot render scene with specified lens.\n";
2086
2087 } else {
2088 if (gsg->begin_scene()) {
2089 cull_result->draw(current_thread);
2090 gsg->end_scene();
2091 }
2092 }
2093
2094 gsg->pop_group_marker();
2095}
2096
2097/**
2098 * An internal function called by make_output() and make_buffer() and similar
2099 * functions to add the newly-created GraphicsOutput object to the engine's
2100 * list of windows, and to request that the window be opened.
2101 */
2102void GraphicsEngine::
2103do_add_window(GraphicsOutput *window) {
2104 nassertv(window != nullptr);
2105
2106 MutexHolder holder(_new_windows_lock);
2107 nassertv(window->get_engine() == this);
2108
2109 // We have a special counter that is unique per window that allows us to
2110 // assure that recently-added windows end up on the end of the list.
2111 window->_internal_sort_index = _window_sort_index;
2112 ++_window_sort_index;
2113
2114 if (display_cat.is_debug()) {
2115 display_cat.debug()
2116 << "Created " << window->get_type() << " " << (void *)window << "\n";
2117 }
2118
2119 window->request_open();
2120 _new_windows.push_back(window);
2121}
2122
2123/**
2124 * An internal function called by make_output to add the newly-created gsg
2125 * object to the engine's list of gsg's. It also adjusts various config
2126 * variables based on the gsg's capabilities.
2127 */
2128void GraphicsEngine::
2129do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe) {
2130 nassertv(gsg != nullptr);
2131
2132 ReMutexHolder holder(_lock);
2133 nassertv(gsg->get_pipe() == pipe && gsg->get_engine() == this);
2134 gsg->_threading_model = _threading_model;
2135 if (!_default_loader.is_null()) {
2136 gsg->set_loader(_default_loader);
2137 }
2138
2139 auto_adjust_capabilities(gsg);
2140
2141 WindowRenderer *draw =
2142 get_window_renderer(_threading_model.get_draw_name(),
2143 _threading_model.get_draw_stage());
2144
2145 draw->add_gsg(gsg);
2146}
2147
2148/**
2149 * An internal function called by remove_window() and remove_all_windows() to
2150 * actually remove the indicated window from all relevant structures, except
2151 * the _windows list itself.
2152 */
2153void GraphicsEngine::
2154do_remove_window(GraphicsOutput *window, Thread *current_thread) {
2155 nassertv(window != nullptr);
2156 PT(GraphicsPipe) pipe = window->get_pipe();
2157 window->clear_pipe();
2158
2159 if (!_windows_sorted) {
2160 do_resort_windows();
2161 }
2162
2163 // Now remove the window from all threads that know about it.
2164 _app.remove_window(window);
2165 Threads::const_iterator ti;
2166 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
2167 RenderThread *thread = (*ti).second;
2168 thread->remove_window(window);
2169 }
2170
2171 // If the window happened to be controlled by the app thread, we might as
2172 // well close it now rather than waiting for next frame.
2173 _app.do_pending(this, current_thread);
2174
2175 if (display_cat.is_debug()) {
2176 display_cat.debug()
2177 << "Removed " << window->get_type() << " " << (void *)window << "\n";
2178 }
2179}
2180
2181/**
2182 * Resorts all of the Windows lists. This may need to be done if one or more
2183 * of the windows' sort properties has changed.
2184 */
2185void GraphicsEngine::
2186do_resort_windows() {
2187 _windows_sorted = true;
2188
2189 _app.resort_windows();
2190 Threads::const_iterator ti;
2191 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
2192 RenderThread *thread = (*ti).second;
2193 thread->resort_windows();
2194 }
2195
2196 _windows.sort();
2197}
2198
2199/**
2200 * Video card capability flags are stored on a per-gsg basis. However, there
2201 * are a few cases where panda needs to know not the capabilities of an
2202 * individual GSG, but rather, the collective capabilities of all the GSGs.
2203 *
2204 * Non-power-of-two (NPOT) texture support is the classic example. Panda
2205 * makes a single global decision to either create NPOT textures, or not.
2206 * Therefore, it doesn't need to know whether one GSG supports NPOT textures.
2207 * It needs to know whether ALL the GSGs support NPOT textures.
2208 *
2209 * The purpose of this routine is to maintain global capability flags that
2210 * summarize the collective capabilities of the computer as a whole.
2211 *
2212 * These global capability flags are initialized from config variables. Then,
2213 * they can be auto-reconfigured using built-in heuristic mechanisms if the
2214 * user so desires. Whether auto-reconfiguration is enabled or not, the
2215 * configured values are checked against the actual capabilities of the
2216 * machine and error messages will be printed if there is a mismatch.
2217 *
2218 */
2219void GraphicsEngine::
2220auto_adjust_capabilities(GraphicsStateGuardian *gsg) {
2221
2222/*
2223 * The rule we use when auto-reconfiguring is as follows. The global
2224 * capabilities must initially be set to conservative values. When the first
2225 * GSG comes into existence, its capabilities will be checked, and the global
2226 * capabilities may be elevated to more aggressive values. At first glance,
2227 * this might seem backward, and it might seem better to do it the other way:
2228 * start with all global capabilities aggressively set, and then disable
2229 * capabilities when you discover a gsg that doesn't support them. However,
2230 * that approach doesn't work, because once a global capability is enabled,
2231 * there is no going back. If textures_power_2 has ever been set to 'none',
2232 * there may be NPOT textures already floating about the system. Ie, it's too
2233 * late: you can't turn these global capability flags off, once they've been
2234 * turned on. That's why we have to start with conservative settings, and
2235 * then elevate those settings to more aggressive values later when we're
2236 * fairly sure it's OK to do so. For each global capability, we must: 1. Make
2237 * sure the initial setting is conservative. 2. Possibly elevate to a more
2238 * aggressive value. 3. Check that we haven't over-elevated.
2239 */
2240
2241 if (textures_auto_power_2 && (textures_power_2 == ATS_none)) {
2242 display_cat.error()
2243 << "Invalid panda config file: if you set the config-variable\n"
2244 << "textures_auto_power_2 to true, you must set the config-variable"
2245 << "textures_power_2 to 'up' or 'down'.\n";
2246 textures_power_2 = ATS_down; // Not a fix. Just suppresses further error messages.
2247 }
2248
2249 if (textures_auto_power_2 && !Texture::has_textures_power_2()) {
2250 if (gsg->get_supports_tex_non_pow2()) {
2252 } else {
2253 Texture::set_textures_power_2(textures_power_2);
2254 }
2255 }
2256
2257 if ((Texture::get_textures_power_2() == ATS_none) &&
2258 (!gsg->get_supports_tex_non_pow2())) {
2259
2260 // Overaggressive configuration detected
2261
2262 display_cat.error()
2263 << "The 'textures_power_2' configuration is set to 'none', meaning \n"
2264 << "that non-power-of-two texture support is required, but the video \n"
2265 << "driver I'm trying to use does not support non-power-of-two textures.\n";
2266
2267 if (textures_power_2 != ATS_none) {
2268 display_cat.error()
2269 << "The 'none' did not come from the config file. In other words,\n"
2270 << "the variable 'textures_power_2' was altered procedurally.\n";
2271
2272 if (textures_auto_power_2) {
2273 display_cat.error()
2274 << "It is possible that it was set by panda's automatic mechanisms,\n"
2275 << "which are currently enabled, because 'textures_auto_power_2' is\n"
2276 << "true. Panda's automatic mechanisms assume that if one\n"
2277 << "window supports non-power-of-two textures, then they all will.\n"
2278 << "This assumption works for most games, but not all.\n"
2279 << "In particular, it can fail if the game creates multiple windows\n"
2280 << "on multiple displays with different video cards.\n";
2281 }
2282 }
2283 }
2284}
2285
2286/**
2287 * Signals our child threads to terminate and waits for them to clean up.
2288 */
2289void GraphicsEngine::
2290terminate_threads(Thread *current_thread) {
2291 ReMutexHolder holder(_lock, current_thread);
2292
2293 // We spend almost our entire time in this method just waiting for threads.
2294 // Time it appropriately.
2295 PStatTimer timer(_wait_pcollector, current_thread);
2296
2297 // First, wait for all the threads to finish their current frame. Grabbing
2298 // the mutex should achieve that.
2299 Threads::const_iterator ti;
2300 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
2301 RenderThread *thread = (*ti).second;
2302 thread->_cv_mutex.acquire();
2303 }
2304
2305 // Now tell them to close their windows and terminate.
2306 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
2307 RenderThread *thread = (*ti).second;
2308 thread->_thread_state = TS_terminate;
2309 thread->_cv_start.notify();
2310 thread->_cv_mutex.release();
2311 }
2312
2313 // Finally, wait for them all to finish cleaning up.
2314 for (ti = _threads.begin(); ti != _threads.end(); ++ti) {
2315 RenderThread *thread = (*ti).second;
2316 thread->join();
2317 }
2318
2319 _threads.clear();
2320}
2321
2322
2323#ifdef DO_PSTATS
2324/**
2325 * A callback function for Pipeline::iterate_all_cycler_types() to report the
2326 * cycler types to PStats.
2327 */
2328void GraphicsEngine::
2329pstats_count_cycler_type(TypeHandle type, int count, void *data) {
2330 GraphicsEngine *self = (GraphicsEngine *)data;
2331 CyclerTypeCounters::iterator ci = self->_all_cycler_types.find(type);
2332 if (ci == self->_all_cycler_types.end()) {
2333 PStatCollector collector(_cyclers_pcollector, type.get_name());
2334 ci = self->_all_cycler_types.insert(CyclerTypeCounters::value_type(type, collector)).first;
2335 }
2336 (*ci).second.set_level(count);
2337}
2338#endif // DO_PSTATS
2339
2340#ifdef DO_PSTATS
2341/**
2342 * A callback function for Pipeline::iterate_dirty_cycler_types() to report
2343 * the cycler types to PStats.
2344 */
2345void GraphicsEngine::
2346pstats_count_dirty_cycler_type(TypeHandle type, int count, void *data) {
2347 GraphicsEngine *self = (GraphicsEngine *)data;
2348 CyclerTypeCounters::iterator ci = self->_dirty_cycler_types.find(type);
2349 if (ci == self->_dirty_cycler_types.end()) {
2350 PStatCollector collector(_dirty_cyclers_pcollector, type.get_name());
2351 ci = self->_dirty_cycler_types.insert(CyclerTypeCounters::value_type(type, collector)).first;
2352 }
2353 (*ci).second.set_level(count);
2354}
2355#endif // DO_PSTATS
2356
2357/**
2358 * Returns a RenderState for inverting the sense of polygon vertex ordering:
2359 * if the scene graph specifies a clockwise ordering, this changes it to
2360 * counterclockwise, and vice-versa.
2361 */
2362const RenderState *GraphicsEngine::
2363get_invert_polygon_state() {
2364 // Once someone asks for this pointer, we hold its reference count and never
2365 // free it.
2366 static CPT(RenderState) state = nullptr;
2367 if (state == nullptr) {
2368 state = RenderState::make(CullFaceAttrib::make_reverse());
2369 }
2370
2371 return state;
2372}
2373
2374/**
2375 * Returns the WindowRenderer with the given name. Creates a new RenderThread
2376 * if there is no such thread already. The pipeline_stage number specifies
2377 * the pipeline stage that will be assigned to the thread (unless was
2378 * previously given a higher stage).
2379 *
2380 * You must already be holding the lock before calling this method.
2381 */
2382GraphicsEngine::WindowRenderer *GraphicsEngine::
2383get_window_renderer(const string &name, int pipeline_stage) {
2384 nassertr(_lock.debug_is_locked(), nullptr);
2385
2386 if (name.empty()) {
2387 return &_app;
2388 }
2389
2390 Threads::iterator ti = _threads.find(name);
2391 if (ti != _threads.end()) {
2392 return (*ti).second.p();
2393 }
2394
2395 PT(RenderThread) thread = new RenderThread(name, this);
2396 thread->set_min_pipeline_stage(pipeline_stage);
2397 _pipeline->set_min_stages(pipeline_stage + 1);
2398
2399 bool started = thread->start(TP_normal, true);
2400 nassertr(started, thread.p());
2401 _threads[name] = thread;
2402
2403 nassertr(thread->get_pipeline_stage() < _pipeline->get_num_stages(), thread.p());
2404
2405 return thread.p();
2406}
2407
2408/**
2409 *
2410 */
2411GraphicsEngine::WindowRenderer::
2412WindowRenderer(const string &name) :
2413 _wl_lock(string("GraphicsEngine::WindowRenderer::_wl_lock ") + name)
2414{
2415}
2416
2417/**
2418 * Adds a new GSG to the _gsg list, if it is not already there.
2419 */
2420void GraphicsEngine::WindowRenderer::
2421add_gsg(GraphicsStateGuardian *gsg) {
2422 LightReMutexHolder holder(_wl_lock);
2423 _gsgs.insert(gsg);
2424}
2425
2426/**
2427 * Adds a new window to the indicated list, which should be a member of the
2428 * WindowRenderer.
2429 */
2430void GraphicsEngine::WindowRenderer::
2431add_window(Windows &wlist, GraphicsOutput *window) {
2432 LightReMutexHolder holder(_wl_lock);
2433 wlist.insert(window);
2434}
2435
2436/**
2437 * Immediately removes the indicated window from all lists. If the window is
2438 * currently open and is already on the _window list, moves it to the
2439 * _pending_close list for later closure.
2440 */
2441void GraphicsEngine::WindowRenderer::
2442remove_window(GraphicsOutput *window) {
2443 nassertv(window != nullptr);
2444 LightReMutexHolder holder(_wl_lock);
2445 PT(GraphicsOutput) ptwin = window;
2446
2447 _cull.erase(ptwin);
2448 _cdraw.erase(ptwin);
2449 _draw.erase(ptwin);
2450
2451 Windows::iterator wi;
2452
2453 wi = _window.find(ptwin);
2454 if (wi != _window.end()) {
2455 // The window is on our _window list, meaning its openclose operations
2456 // (among other window ops) are serviced by this thread.
2457
2458 // Make sure the window isn't about to request itself open.
2459 ptwin->request_close();
2460
2461 // If the window is already open, move it to the _pending_close list so it
2462 // can be closed later. We can't close it immediately, because we might
2463 // not have been called from the subthread.
2464 if (ptwin->is_valid()) {
2465 _pending_close.push_back(ptwin);
2466 }
2467
2468 _window.erase(wi);
2469 }
2470}
2471
2472/**
2473 * Resorts all the lists of windows, assuming they may have become unsorted.
2474 */
2475void GraphicsEngine::WindowRenderer::
2476resort_windows() {
2477 LightReMutexHolder holder(_wl_lock);
2478
2479 _cull.sort();
2480 _cdraw.sort();
2481 _draw.sort();
2482 _window.sort();
2483
2484 if (display_cat.is_debug()) {
2485 display_cat.debug()
2486 << "Windows resorted:";
2487 Windows::const_iterator wi;
2488 for (wi = _window.begin(); wi != _window.end(); ++wi) {
2489 GraphicsOutput *win = (*wi);
2490 display_cat.debug(false)
2491 << " " << win->get_name() << "(" << win->get_sort() << ")";
2492 }
2493 display_cat.debug(false)
2494 << "\n";
2495
2496 for (wi = _draw.begin(); wi != _draw.end(); ++wi) {
2497 GraphicsOutput *win = (*wi);
2498 display_cat.debug(false)
2499 << " " << win->get_name() << "(" << win->get_sort() << ")";
2500 }
2501 display_cat.debug(false)
2502 << "\n";
2503 }
2504}
2505
2506/**
2507 * Executes one stage of the pipeline for the current thread: calls cull on
2508 * all windows that are on the cull list for this thread, draw on all the
2509 * windows on the draw list, etc.
2510 */
2511void GraphicsEngine::WindowRenderer::
2512do_frame(GraphicsEngine *engine, Thread *current_thread) {
2513 PStatTimer timer(engine->_do_frame_pcollector, current_thread);
2514 LightReMutexHolder holder(_wl_lock);
2515
2516 engine->cull_to_bins(_cull, current_thread);
2517 engine->cull_and_draw_together(_cdraw, current_thread);
2518 engine->draw_bins(_draw, current_thread);
2519 engine->process_events(_window, current_thread);
2520
2521 // If any GSG's on the list have no more outstanding pointers, clean them
2522 // up. (We are in the draw thread for all of these GSG's.)
2523 if (any_done_gsgs()) {
2524 GSGs new_gsgs;
2525 GSGs::iterator gi;
2526 for (gi = _gsgs.begin(); gi != _gsgs.end(); ++gi) {
2527 GraphicsStateGuardian *gsg = (*gi);
2528 if (gsg->get_ref_count() == 1) {
2529 // This one has no outstanding pointers; clean it up.
2530 GraphicsPipe *pipe = gsg->get_pipe();
2531 engine->close_gsg(pipe, gsg);
2532 } else {
2533 // This one is ok; preserve it.
2534 new_gsgs.insert(gsg);
2535 }
2536 }
2537
2538 _gsgs.swap(new_gsgs);
2539 }
2540}
2541
2542/**
2543 * Attempts to fully open or close any windows or buffers associated with this
2544 * thread, but does not otherwise perform any rendering. (Normally, this step
2545 * is handled during do_frame(); call this method only if you want these
2546 * things to open immediately.)
2547 */
2548void GraphicsEngine::WindowRenderer::
2549do_windows(GraphicsEngine *engine, Thread *current_thread) {
2550 LightReMutexHolder holder(_wl_lock);
2551
2552 engine->process_events(_window, current_thread);
2553
2554 engine->make_contexts(_cdraw, current_thread);
2555 engine->make_contexts(_draw, current_thread);
2556}
2557
2558/**
2559 * Flips the windows as appropriate for the current thread.
2560 */
2561void GraphicsEngine::WindowRenderer::
2562do_flip(GraphicsEngine *engine, Thread *current_thread) {
2563 LightReMutexHolder holder(_wl_lock);
2564 engine->flip_windows(_cdraw, current_thread);
2565 engine->flip_windows(_draw, current_thread);
2566}
2567
2568/**
2569 * Prepares windows for flipping by waiting until all draw calls are finished
2570 */
2571void GraphicsEngine::WindowRenderer::
2572do_ready_flip(GraphicsEngine *engine, Thread *current_thread) {
2573 LightReMutexHolder holder(_wl_lock);
2574 engine->ready_flip_windows(_cdraw, current_thread);
2575 engine->ready_flip_windows(_draw, current_thread);
2576}
2577
2578
2579/**
2580 * Closes all the windows on the _window list.
2581 */
2582void GraphicsEngine::WindowRenderer::
2583do_close(GraphicsEngine *engine, Thread *current_thread) {
2584 LightReMutexHolder holder(_wl_lock);
2585 Windows::iterator wi;
2586 for (wi = _window.begin(); wi != _window.end(); ++wi) {
2587 GraphicsOutput *win = (*wi);
2588 win->set_close_now();
2589 }
2590
2591 // Also close all of the GSG's.
2592 GSGs new_gsgs;
2593 GSGs::iterator gi;
2594 for (gi = _gsgs.begin(); gi != _gsgs.end(); ++gi) {
2595 GraphicsStateGuardian *gsg = (*gi);
2596 if (gsg->get_ref_count() == 1) {
2597 // This one has no outstanding pointers; clean it up.
2598 GraphicsPipe *pipe = gsg->get_pipe();
2599 engine->close_gsg(pipe, gsg);
2600 } else {
2601 // This one is ok; preserve it.
2602 new_gsgs.insert(gsg);
2603 }
2604 }
2605
2606 _gsgs.swap(new_gsgs);
2607}
2608
2609/**
2610 * Actually closes any windows that were recently removed from the
2611 * WindowRenderer.
2612 */
2613void GraphicsEngine::WindowRenderer::
2614do_pending(GraphicsEngine *engine, Thread *current_thread) {
2615 LightReMutexHolder holder(_wl_lock);
2616
2617 if (!_pending_close.empty()) {
2618 if (display_cat.is_debug()) {
2619 display_cat.debug()
2620 << "_pending_close.size() = " << _pending_close.size() << "\n";
2621 }
2622
2623 // Close any windows that were pending closure. Carefully protect against
2624 // recursive entry to this function by swapping the vector to a local copy
2625 // first.
2626 Windows::iterator wi;
2627 Windows pending_close;
2628 _pending_close.swap(pending_close);
2629 for (wi = pending_close.begin(); wi != pending_close.end(); ++wi) {
2630 GraphicsOutput *win = (*wi);
2631 win->set_close_now();
2632 }
2633 }
2634}
2635
2636/**
2637 * Returns true if any of the GSG's on this thread's draw list are done (they
2638 * have no outstanding pointers other than this one), or false if all of them
2639 * are still good.
2640 */
2641bool GraphicsEngine::WindowRenderer::
2642any_done_gsgs() const {
2643 GSGs::const_iterator gi;
2644 for (gi = _gsgs.begin(); gi != _gsgs.end(); ++gi) {
2645 if ((*gi)->get_ref_count() == 1) {
2646 return true;
2647 }
2648 }
2649
2650 return false;
2651}
2652
2653/**
2654 *
2655 */
2656GraphicsEngine::RenderThread::
2657RenderThread(const string &name, GraphicsEngine *engine) :
2658 Thread(name, "Main"),
2659 WindowRenderer(name),
2660 _engine(engine),
2661 _cv_mutex(string("GraphicsEngine::RenderThread ") + name),
2662 _cv_start(_cv_mutex),
2663 _cv_done(_cv_mutex)
2664{
2665 _thread_state = TS_wait;
2666}
2667
2668/**
2669 * The main loop for a particular render thread. The thread will process
2670 * whatever cull or draw windows it has assigned to it.
2671 */
2672void GraphicsEngine::RenderThread::
2673thread_main() {
2674 Thread *current_thread = Thread::get_current_thread();
2675
2676 MutexHolder holder(_cv_mutex);
2677 while (true) {
2678 nassertv(_cv_mutex.debug_is_locked());
2679
2680 switch (_thread_state) {
2681 case TS_wait:
2682 break;
2683
2684 case TS_do_frame:
2685 do_pending(_engine, current_thread);
2686 do_frame(_engine, current_thread);
2687 break;
2688
2689 case TS_do_flip:
2690 do_flip(_engine, current_thread);
2691 break;
2692
2693 case TS_do_release:
2694 do_pending(_engine, current_thread);
2695 break;
2696
2697 case TS_do_windows:
2698 do_windows(_engine, current_thread);
2699 do_pending(_engine, current_thread);
2700 break;
2701
2702 case TS_do_compute:
2703 nassertd(_gsg != nullptr && _state != nullptr) break;
2704 {
2705 const ShaderAttrib *sattr;
2706 _state->get_attrib(sattr);
2707 _gsg->push_group_marker(std::string("Compute ") + sattr->get_shader()->get_filename(Shader::ST_compute).get_basename());
2708 _gsg->set_state_and_transform(_state, TransformState::make_identity());
2709 _gsg->dispatch_compute(_work_groups[0], _work_groups[1], _work_groups[2]);
2710 _gsg->pop_group_marker();
2711 }
2712 break;
2713
2714 case TS_do_extract:
2715 nassertd(_gsg != nullptr && _texture != nullptr) break;
2716 _result = _gsg->extract_texture_data(_texture);
2717 break;
2718
2719 case TS_do_screenshot:
2720 nassertd(_region != nullptr) break;
2721 _texture = _region->get_screenshot();
2722 break;
2723
2724 case TS_terminate:
2725 do_pending(_engine, current_thread);
2726 do_close(_engine, current_thread);
2727 _thread_state = TS_done;
2728 _cv_done.notify();
2729 return;
2730
2731 case TS_done:
2732 // Shouldn't be possible to get here.
2733 nassertv(false);
2734 return;
2735 }
2736
2737 _thread_state = TS_wait;
2738 _cv_done.notify();
2739
2740 {
2741 PStatTimer timer(_wait_pcollector, current_thread);
2742 _cv_start.wait();
2743 }
2744 }
2745}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void stop_threads()
Stops any threads that are currently running.
static AsyncTaskManager * get_global_ptr()
Returns a pointer to the global AsyncTaskManager.
static void consider_flush_global_index()
If there is a global BamCache object, calls consider_flush_index() on it.
Definition: bamCache.I:234
static void flush_global_index()
If there is a global BamCache object, calls flush_index() on it.
Definition: bamCache.I:244
This CullHandler sends all of the geoms it receives into a CullResult object, for binning (and later ...
This is an abstract class for any volume in any sense which can be said to define the locality of ref...
This special window object doesn't represent a window in its own right, but instead hooks into some t...
This is a generic object that can be assigned to a callback at various points in the rendering proces...
virtual void do_callback(CallbackData *cbdata)
This method called when the callback is triggered; it *replaces* the original function.
A node that can be positioned around in the scene graph to represent a point of view for rendering a ...
Definition: camera.h:35
is_active
Returns the current setting of the active flag on the camera.
Definition: camera.h:50
get_scene
Returns the scene that will be rendered by the camera.
Definition: camera.h:54
int cleanup_aux_scene_data(Thread *current_thread=Thread::get_current_thread())
Walks through the list of currently-assigned AuxSceneData objects and releases any that are past thei...
Definition: camera.cxx:214
A ClockObject keeps track of elapsed real time and discrete time.
Definition: clockObject.h:58
void tick(Thread *current_thread=Thread::get_current_thread())
Instructs the clock that a new frame has just begun.
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
Definition: clockObject.h:94
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
bool check_errors(Thread *current_thread)
Returns true if a clock error was detected since the last time check_errors() was called.
Definition: clockObject.I:203
This defines the abstract interface for an object that receives Geoms identified by the CullTraverser...
Definition: cullHandler.h:28
This stores the result of a BinCullHandler traversal: an ordered collection of CullBins,...
Definition: cullResult.h:44
void draw(Thread *current_thread)
Asks all the bins to draw themselves in the correct order.
Definition: cullResult.cxx:290
void finish_cull(SceneSetup *scene_setup, Thread *current_thread)
Called after all the geoms have been added, this indicates that the cull process is finished for this...
Definition: cullResult.cxx:268
This object performs a depth-first traversal of the scene graph, with optional view-frustum culling,...
Definition: cullTraverser.h:45
void set_view_frustum(GeometricBoundingVolume *view_frustum)
Specifies the bounding volume that corresponds to the view frustum.
virtual void end_traverse()
Should be called when the traverser has finished traversing its scene, this gives it a chance to do a...
virtual void set_scene(SceneSetup *scene_setup, GraphicsStateGuardianBase *gsg, bool dr_incomplete_render)
Sets the SceneSetup object that indicates the initial camera position, etc.
void set_cull_handler(CullHandler *cull_handler)
Specifies the object that will receive the culled Geoms.
static void flush_level()
Flushes the PStatCollectors used during traversal.
void traverse(const NodePath &root)
Begins the traversal from the indicated node.
static void flush_level()
Flushes the PStatCollectors used during traversal.
This template class calls PipelineCycler::read_unlocked(), and then provides a transparent read-only ...
This specialization on CallbackData is passed when the callback is initiated from the cull traversal,...
This specialization on CallbackData is passed when the callback is initiated from the draw traversal,...
Encapsulates the data from a DisplayRegion, pre-fetched for one stage of the pipeline.
GraphicsOutput * get_window() const
Returns the GraphicsOutput that this DisplayRegion is ultimately associated with, or NULL if no windo...
int get_lens_index() const
Gets the index into a lens_node lens array.
NodePath get_camera() const
Returns the camera associated with this DisplayRegion, or an empty NodePath if no camera is associate...
int get_pixel_width(int i=0) const
Returns the width of the DisplayRegion in pixels.
int get_pixel_height(int i=0) const
Returns the height of the DisplayRegion in pixels.
A rectangular subregion within a window for rendering into.
Definition: displayRegion.h:57
is_stereo
Returns true if this is a StereoDisplayRegion, false otherwise.
Definition: displayRegion.h:90
get_cull_traverser
Returns the CullTraverser that will be used to draw the contents of this DisplayRegion.
void set_cull_result(PT(CullResult) cull_result, PT(SceneSetup) scene_setup, Thread *current_thread)
Stores the result of performing a cull operation on this DisplayRegion.
get_incomplete_render
Returns the incomplete_render flag.
CullResult * get_cull_result(Thread *current_thread) const
Returns the CullResult value that was stored on this DisplayRegion, presumably by the last successful...
PStatCollector & get_draw_region_pcollector()
Returns a PStatCollector for timing the draw operation for just this DisplayRegion.
PStatCollector & get_cull_region_pcollector()
Returns a PStatCollector for timing the cull operation for just this DisplayRegion.
const std::string & get_debug_name() const
Returns a unique name used for debugging.
get_cull_callback
Returns the CallbackObject set by set_cull_callback().
This special kind of CullHandler immediately draws its contents as soon as it receives them.
virtual bool is_any_clear_active() const
Returns true if any of the clear types (so far there are just color or depth) have been set active,...
std::string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:367
A container for the various kinds of properties we might ask to have on a graphics frameBuffer before...
bool subsumes(const FrameBufferProperties &other) const
Returns true if this set of properties makes strictly greater or equal demands of the framebuffer tha...
static void flush_level()
Flushes the PStatCollectors used during traversal.
static SimpleLru * get_small_lru()
Returns a pointer to the global LRU object that manages the GeomVertexArrayData's that are deemed too...
static SimpleLru * get_independent_lru()
Returns a pointer to the global LRU object that manages the GeomVertexArrayData's that have not (yet)...
static void lru_epoch()
Marks that an epoch has passed in each LRU.
This is another abstract class, for a general class of bounding volumes that actually enclose points ...
This class is the main interface to controlling the render process.
get_threading_model
Returns the threading model that will be applied to future objects.
get_window
Returns the nth window or buffers managed by the engine, in sorted order.
void reset_all_windows(bool swapchain)
Resets the framebuffer of the current window.
void remove_all_windows()
Removes and closes all windows from the engine.
bool add_window(GraphicsOutput *window, int sort)
This can be used to add a newly-created GraphicsOutput object (and its GSG) to the engine's list of w...
void ready_flip()
Waits for all the threads that started drawing their last frame to finish drawing.
void open_windows()
Fully opens (or closes) any windows that have recently been requested open or closed,...
void flip_frame()
Waits for all the threads that started drawing their last frame to finish drawing,...
bool extract_texture_data(Texture *tex, GraphicsStateGuardian *gsg)
Asks the indicated GraphicsStateGuardian to retrieve the texture memory image of the indicated textur...
bool is_empty() const
Returns true if there are no windows or buffers managed by the engine, false if there is at least one...
bool remove_window(GraphicsOutput *window)
Removes the indicated window or offscreen buffer from the set of windows that will be processed when ...
set_threading_model
Specifies how future objects created via make_gsg(), make_buffer(), and make_output() will be threade...
GraphicsOutput * make_output(GraphicsPipe *pipe, const std::string &name, int sort, const FrameBufferProperties &fb_prop, const WindowProperties &win_prop, int flags, GraphicsStateGuardian *gsg=nullptr, GraphicsOutput *host=nullptr)
Creates a new window (or buffer) and returns it.
void render_frame()
Renders the next frame in all the registered windows, and flips all of the frame buffers.
~GraphicsEngine()
Gracefully cleans up the graphics engine and its related threads and windows.
GraphicsEngine(Pipeline *pipeline=nullptr)
Creates a new GraphicsEngine object.
void texture_uploaded(Texture *tex)
This method is called by the GraphicsStateGuardian after a texture has been successfully uploaded to ...
void sync_frame()
Waits for all the threads that started drawing their last frame to finish drawing.
get_num_windows
Returns the number of windows (or buffers) managed by the engine.
void dispatch_compute(const LVecBase3i &work_groups, const ShaderAttrib *sattr, GraphicsStateGuardian *gsg)
Asks the indicated GraphicsStateGuardian to dispatch the compute shader in the given ShaderAttrib usi...
static void do_cull(CullHandler *cull_handler, SceneSetup *scene_setup, GraphicsStateGuardian *gsg, Thread *current_thread)
Fires off a cull traversal using the indicated camera.
This is a base class for the various different classes that represent the result of a frame of render...
virtual void ready_flip()
This function will be called within the draw thread after end_frame() has been called on all windows,...
get_inverted
Returns the current setting of the inverted flag.
get_pipe
Returns the GraphicsPipe that this window is associated with.
virtual void begin_flip()
This function will be called within the draw thread after end_frame() has been called on all windows,...
is_active
Returns true if the window is ready to be rendered into, false otherwise.
get_gsg
Returns the GSG that is associated with this window.
virtual void clear_pipe()
Sets the window's _pipe pointer to NULL; this is generally called only as a precursor to deleting the...
PStatCollector & get_draw_window_pcollector()
Returns a PStatCollector for timing the draw operation for just this GraphicsOutput.
PStatCollector & get_cull_window_pcollector()
Returns a PStatCollector for timing the cull operation for just this GraphicsOutput.
virtual void end_frame(FrameMode mode, Thread *current_thread)
This function will be called within the draw thread after rendering is completed for a given frame.
virtual void end_flip()
This function will be called within the draw thread after begin_flip() has been called on all windows...
bool get_delete_flag() const
Returns the current setting of the delete flag.
void change_scenes(DisplayRegionPipelineReader *new_dr)
Called by the GraphicsEngine when the window is about to change to another DisplayRegion.
const FrameBufferProperties & get_fb_properties() const
Returns the framebuffer properties of the window.
virtual bool begin_frame(FrameMode mode, Thread *current_thread)
This function will be called within the draw thread before beginning rendering for a given frame.
virtual void reset_window(bool swapchain)
Resets the window framebuffer from its derived children.
virtual GraphicsOutput * get_host()
This is normally called only from within make_texture_buffer().
get_engine
Returns the graphics engine that created this output.
bool is_valid() const
Returns true if the output is fully created and ready for rendering, false otherwise.
virtual void request_close()
This is called by the GraphicsEngine to request that the window (or whatever) close itself or,...
get_sort
Returns the sorting order of this particular GraphicsOutput.
get_name
Returns the name that was passed to the GraphicsOutput constructor.
PStatCollector & get_clear_window_pcollector()
Returns a PStatCollector for timing the clear operation for just this GraphicsOutput.
virtual void request_open()
This is called by the GraphicsEngine to request that the window (or whatever) open itself or,...
virtual bool flip_ready() const
Returns true if a frame has been rendered and needs to be flipped, false otherwise.
virtual void clear(Thread *current_thread)
Clears the entire framebuffer before rendering, according to the settings of get_color_clear_active()...
virtual void set_close_now()
This is called by the GraphicsEngine to insist that the output be closed immediately.
An object to create GraphicsOutputs that share a particular 3-D API.
Definition: graphicsPipe.h:52
get_gsg
Returns the nth GSG in the universe.
Encapsulates all the communication with a particular instance of a given rendering backend.
virtual void clear_before_callback()
Resets any non-standard graphics state that might give a callback apoplexy.
is_valid
Returns true if the GSG has been correctly initialized within a graphics context, false if there has ...
get_pipe
Returns the graphics pipe on which this GSG was created.
void release_all()
Releases all prepared objects.
set_scene
Sets the SceneSetup object that indicates the initial camera position, etc.
GraphicsEngine * get_engine() const
Returns the graphics engine that created this GSG.
bool needs_reset() const
Returns true if the gsg is marked as needing a reset.
get_supports_tex_non_pow2
Returns true if this GSG can handle non power of two sized textures.
virtual void clear_state_and_transform()
Forgets the current graphics state and current transform, so that the next call to set_state_and_tran...
set_loader
Sets the Loader object that will be used by this GSG to load textures when necessary,...
virtual void prepare_display_region(DisplayRegionPipelineReader *dr)
Makes the specified DisplayRegion current.
get_prepared_objects
Returns the set of texture and geom objects that have been prepared with this GSG (and possibly other...
virtual bool extract_texture_data(Texture *tex)
This method should only be called by the GraphicsEngine.
virtual void end_scene()
Called between begin_frame() and end_frame() to mark the end of drawing commands for a "scene" (usual...
get_timer_queries_active
Returns true if timer queries are currently enabled on this GSG.
virtual void set_state_and_transform(const RenderState *state, const TransformState *transform)
Simultaneously resets the render state and the transform state.
virtual void clear(DrawableRegion *clearable)
Clears the framebuffer within the current DisplayRegion, according to the flags indicated by the give...
const GraphicsThreadingModel & get_threading_model() const
Returns the threading model that was used to create this GSG.
virtual void dispatch_compute(int size_x, int size_y, int size_z)
Dispatches a currently bound compute shader using the given work group counts.
virtual bool begin_scene()
Called between begin_frame() and end_frame() to mark the beginning of drawing commands for a "scene" ...
This represents the user's specification of how a particular frame is handled by the various threads.
int get_cull_stage() const
Returns the pipeline stage from which the cull thread should access data.
bool get_cull_sorting() const
Returns true if the model involves a separate cull pass, or false if culling happens implicitly,...
const std::string & get_cull_name() const
Returns the name of the thread that will handle culling in this model.
bool is_default() const
Returns true if the threading model is the default, cull-then-draw single- threaded model,...
const std::string & get_draw_name() const
Returns the name of the thread that will handle sending the actual graphics primitives to the graphic...
int get_draw_stage() const
Returns the pipeline stage from which the draw thread should access data.
Lens * get_lens(int index=0) const
Returns a pointer to the particular Lens associated with this LensNode, or NULL if there is not yet a...
Definition: lensNode.I:47
bool get_lens_active(int index) const
Returns the active flag for the nth lens.
Definition: lensNode.I:60
A base class for any number of different kinds of lenses, linear and otherwise.
Definition: lens.h:41
get_coordinate_system
Returns the coordinate system that all 3-d computations are performed within for this Lens.
Definition: lens.h:74
Similar to MutexHolder, but for a light reentrant mutex.
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:25
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:159
LVecBase3 get_scale() const
Retrieves the scale component of the transform.
Definition: nodePath.cxx:1195
bool is_empty() const
Returns true if the NodePath contains no nodes.
Definition: nodePath.I:188
PandaNode * node() const
Returns the referenced node of the path.
Definition: nodePath.I:227
NodePath get_top(Thread *current_thread=Thread::get_current_thread()) const
Returns a singleton NodePath that represents the top of the path, or empty NodePath if this path is e...
Definition: nodePath.cxx:209
get_parent
Returns the NodePath to the parent of the referenced node: that is, this NodePath,...
Definition: nodePath.h:242
const TransformState * get_transform(Thread *current_thread=Thread::get_current_thread()) const
Returns the complete transform object set on this node.
Definition: nodePath.cxx:795
static void disconnect()
Closes the connection previously established.
Definition: pStatClient.h:290
static bool is_connected()
Returns true if the client believes it is connected to a working PStatServer, false otherwise.
Definition: pStatClient.h:291
A lightweight class that represents a single element that may be timed and/or counted via stats.
This is a special type of PStatTimer that also uses a timer query on the GSG to measure how long a ta...
Definition: pStatGPUTimer.h:36
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:30
A basic node of the scene graph or data graph.
Definition: pandaNode.h:65
static void set_scene_root_func(SceneRootFunc *func)
This is used by the GraphicsEngine to hook in a pointer to the scene_root_func(), the function to det...
Definition: pandaNode.cxx:3566
This is a special GraphicsOutput type that acts a lot like a GraphicsBuffer, effectively allowing ren...
This class manages a staged pipeline of data, for instance the render pipeline, so that each stage of...
Definition: pipeline.h:38
void cycle()
Flows all the pipeline data down to the next stage.
Definition: pipeline.cxx:87
int get_num_stages() const
Returns the number of stages required for the pipeline.
Definition: pipeline.I:37
static Pipeline * get_render_pipeline()
Returns a pointer to the global render pipeline.
Definition: pipeline.I:18
void set_min_stages(int min_stages)
Ensures that at least the indicated number of stages are in the pipeline.
Definition: pipeline.I:29
A table of objects that are saved within the graphics context for reference by handle later.
void release_all()
Releases all prepared objects of all kinds at once.
bool debug_is_locked() const
Returns true if the current thread has locked the ReMutex, false otherwise.
Similar to MutexHolder, but for a reentrant mutex.
Definition: reMutexHolder.h:25
get_ref_count
Returns the current reference count.
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
Definition: renderState.h:47
static int get_num_states()
Returns the total number of unique RenderState objects allocated in the world.
static void flush_level()
Flushes the PStatCollectors used during traversal.
Definition: renderState.I:479
static int get_num_unused_states()
Returns the total number of RenderState objects that have been allocated but have no references outsi...
This object holds the camera position, etc., and other general setup information for rendering a part...
Definition: sceneSetup.h:32
void set_world_transform(const TransformState *world_transform)
Specifies the position of the starting node relative to the camera.
Definition: sceneSetup.I:223
void set_viewport_size(int width, int height)
Specifies the size of the viewport (display region), in pixels.
Definition: sceneSetup.I:50
DisplayRegion * get_display_region() const
Returns the display region for the scene.
Definition: sceneSetup.I:42
void set_cs_world_transform(const TransformState *cs_world_transform)
Specifies the position from the starting node relative to the camera, in the GSG's internal coordinat...
Definition: sceneSetup.I:259
const NodePath & get_scene_root() const
Returns the root node of the scene.
Definition: sceneSetup.I:83
void set_scene_root(const NodePath &scene_root)
Specifies the root node of the scene.
Definition: sceneSetup.I:75
void set_cs_transform(const TransformState *cs_transform)
Specifies the transform from the camera's coordinate system to the GSG's internal coordinate system.
Definition: sceneSetup.I:241
void set_lens(const Lens *lens)
Indicates the particular Lens used for rendering.
Definition: sceneSetup.I:123
void set_camera_path(const NodePath &camera_path)
Specifies the NodePath to the camera.
Definition: sceneSetup.I:91
const NodePath & get_cull_center() const
Returns the point from which the culling operations will be performed.
Definition: sceneSetup.I:161
void set_display_region(DisplayRegion *display_region)
Specifies the display region for the scene.
Definition: sceneSetup.I:34
void set_camera_transform(const TransformState *camera_transform)
Specifies the position of the camera relative to the starting node.
Definition: sceneSetup.I:206
void set_inverted(bool inverted)
Changes the current setting of the inverted flag.
Definition: sceneSetup.I:141
void set_camera_node(Camera *camera_node)
Specifies the camera used to render the scene.
Definition: sceneSetup.I:107
Definition: shader.h:49
Filename get_filename(ShaderType type=ST_none) const
Return the Shader's filename for the given shader type.
Definition: shader.I:20
size_t get_total_size() const
Returns the total size of all objects currently active on the LRU.
Definition: simpleLru.I:18
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition: texture.h:71
get_image_modified
Returns a sequence number which is guaranteed to change at least every time the texture image data (i...
Definition: texture.h:525
static AutoTextureScale get_textures_power_2()
This flag returns ATS_none, ATS_up, or ATS_down and controls the scaling of textures in general.
Definition: texture.I:1863
static void set_textures_power_2(AutoTextureScale scale)
Set this flag to ATS_none, ATS_up, ATS_down, or ATS_pad to control the scaling of textures in general...
Definition: texture.I:1852
static bool has_textures_power_2()
If true, then get_textures_power_2 has been set using set_textures_power_2.
Definition: texture.I:1877
A thread; that is, a lightweight process.
Definition: thread.h:46
is_threading_supported
Returns true if threading support has been compiled in and enabled, or false if no threading is avail...
Definition: thread.h:112
get_current_pipeline_stage
Returns the integer pipeline stage associated with the current thread.
Definition: thread.h:110
static void consider_yield()
Possibly suspends the current thread for the rest of the current epoch, if it has run for enough this...
Definition: thread.I:212
is_true_threads
Returns true if a real threading library is available that supports actual OS-implemented threads,...
Definition: thread.h:113
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition: thread.h:109
static void prepare_for_exit()
Should be called by the main thread just before exiting the program, this blocks until any remaining ...
Definition: thread.I:283
static void force_yield()
Suspends the current thread for the rest of the current epoch.
Definition: thread.I:201
Indicates a coordinate-system transform on vertices.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
get_name
Returns the name of the type.
Definition: typeHandle.h:136
static SimpleLru * get_pending_lru()
Returns a pointer to the global LRU object that manages the VertexDataPage's that are pending process...
static void stop_threads()
Call this to stop the paging threads, if they were started.
static SimpleLru * get_global_lru(RamClass rclass)
Returns a pointer to the global LRU object that manages the VertexDataPage's with the indicated RamCl...
get_save_file
Returns the global VertexDataSaveFile that will be used to save vertex data buffers to disk when nece...
A temporary file to hold the vertex data that has been evicted from memory and written to disk.
size_t get_total_file_size() const
Returns the amount of space consumed by the save file, including unused portions.
size_t get_used_file_size() const
Returns the amount of space within the save file that is currently in use.
A container for the various kinds of properties we might ask to have on a graphics window before we o...
int get_y_size() const
Returns size in pixels in the y dimension of the useful part of the window, not including decorations...
int get_x_size() const
Returns size in pixels in the x dimension of the useful part of the window, not including decorations...
has_size
Returns true if the window size has been specified, false otherwise.
void reserve(size_type_0 n)
Informs the vector of a planned change in size; ensures that the capacity of the vector is greater th...
iterator_0 begin()
Returns the iterator that marks the first element in the ordered vector.
void push_back(const value_type_0 &key)
Adds the new element to the end of the vector without regard for proper sorting.
void swap(ordered_vector< Key, Compare, Vector > &other)
Exchanges the contents of this vector and the other vector, in constant time (e.g....
size_type_0 size() const
Returns the number of elements in the ordered vector.
bool empty() const
Returns true if the ordered vector is empty, false otherwise.
iterator_0 end()
Returns the iterator that marks the end of the ordered vector.
void sort()
Maps to sort_unique().
bool verify_list() const
Maps to verify_list_unique().
This is our own Panda specialization on the default STL map.
Definition: pmap.h:49
This is our own Panda specialization on the default STL vector.
Definition: pvector.h:42
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PT(Texture) GraphicsEngine
Called by DisplayRegion::do_get_screenshot.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.