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