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