Panda3D
Loading...
Searching...
No Matches
pipeline.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 pipeline.cxx
10 * @author drose
11 * @date 2002-02-21
12 */
13
14#include "pipeline.h"
16#include "configVariableInt.h"
17#include "config_pipeline.h"
18
19Pipeline *Pipeline::_render_pipeline = nullptr;
20
21/**
22 *
23 */
24Pipeline::
25Pipeline(const std::string &name, int num_stages) :
26 Namable(name),
27#ifdef THREADED_PIPELINE
28 _num_stages(num_stages),
29 _cycle_lock("Pipeline cycle"),
30 _lock("Pipeline"),
31 _next_cycle_seq(1)
32#else
33 _num_stages(1)
34#endif
35{
36#ifdef THREADED_PIPELINE
37 // We maintain all of the cyclers in the world on one of two linked
38 // lists. Cyclers that are "clean", which is to say, they have the
39 // same value across all pipeline stages, are stored on the _clean
40 // list. Cyclers that are "dirty", which have different values
41 // across some pipeline stages, are stored instead on the _dirty
42 // list. Cyclers can move themselves from clean to dirty by calling
43 // add_dirty_cycler(), and cyclers get moved from dirty to clean
44 // during cycle().
45
46 // To visit each cycler once requires traversing both lists.
47 _clean.make_head();
48 _dirty.make_head();
49
50 // We also store the total count of all cyclers, clean and dirty, in
51 // _num_cyclers; and the count of only dirty cyclers in _num_dirty_cyclers.
52 _num_cyclers = 0;
53 _num_dirty_cyclers = 0;
54
55 // This flag is true only during the call to cycle().
56 _cycling = false;
57
58#else
59 if (num_stages != 1) {
60 pipeline_cat.warning()
61 << "Requested " << num_stages
62 << " pipeline stages but multithreaded render pipelines not enabled in build.\n";
63 }
64#endif // THREADED_PIPELINE
65
66 nassertv(num_stages >= 1);
67}
68
69/**
70 *
71 */
72Pipeline::
73~Pipeline() {
74#ifdef THREADED_PIPELINE
75 nassertv(_num_cyclers == 0);
76 nassertv(_num_dirty_cyclers == 0);
77 _clean.clear_head();
78 _dirty.clear_head();
79 nassertv(!_cycling);
80#endif // THREADED_PIPELINE
81}
82
83/**
84 * Flows all the pipeline data down to the next stage.
85 */
87cycle() {
88#ifdef THREADED_PIPELINE
89 if (pipeline_cat.is_debug()) {
90 pipeline_cat.debug()
91 << "Beginning the pipeline cycle\n";
92 }
93
94 pvector< PT(CycleData) > saved_cdatas;
95 {
96 ReMutexHolder cycle_holder(_cycle_lock);
97 unsigned int prev_seq, next_seq;
98 PipelineCyclerLinks prev_dirty;
99 {
100 // We can't hold the lock protecting the linked lists during the cycling
101 // itself, since it could cause a deadlock.
102 MutexHolder holder(_lock);
103 if (_num_stages == 1) {
104 // No need to cycle if there's only one stage.
105 nassertv(_dirty._next == &_dirty);
106 return;
107 }
108
109 nassertv(!_cycling);
110 _cycling = true;
111
112 // Increment the cycle sequence number, which is used by this method to
113 // communicate with remove_cycler() about the status of dirty cyclers.
114 prev_seq = next_seq = _next_cycle_seq;
115 if (++next_seq == 0) {
116 // Skip 0, which is a reserved number used to indicate a clean cycler.
117 ++next_seq;
118 }
119 _next_cycle_seq = next_seq;
120
121 // Move the dirty list to prev_dirty, for processing.
122 prev_dirty.make_head();
123 prev_dirty.take_list(_dirty);
124
125 saved_cdatas.reserve(_num_dirty_cyclers);
126 _num_dirty_cyclers = 0;
127 }
128
129 // This is duplicated for different number of stages, as an optimization.
130 switch (_num_stages) {
131 case 2:
132 while (prev_dirty._next != &prev_dirty) {
133 PipelineCyclerLinks *link = prev_dirty._next;
134 while (link != &prev_dirty) {
135 PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)link;
136
137 if (!cycler->_lock.try_lock()) {
138 // No big deal, just move on to the next one for now, and we'll
139 // come back around to it. It's important not to block here in
140 // order to prevent one cycler from deadlocking another.
141 if (link->_prev != &prev_dirty || link->_next != &prev_dirty) {
142 link = cycler->_next;
143 continue;
144 } else {
145 // Well, we are the last cycler left, so we might as well wait.
146 // This is necessary to trigger the deadlock detection code.
147 cycler->_lock.lock();
148 }
149 }
150
151 MutexHolder holder(_lock);
152 cycler->remove_from_list();
153
154 // We save the result of cycle(), so that we can defer the side-
155 // effects that might occur when CycleDatas destruct, at least until
156 // the end of this loop.
157 saved_cdatas.push_back(cycler->cycle_2());
158
159 // cycle_2() won't leave a cycler dirty. Add it to the clean list.
160 nassertd(!cycler->_dirty) break;
161 cycler->insert_before(&_clean);
162#ifdef DEBUG_THREADS
163 inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
164#endif
165 cycler->_lock.unlock();
166 break;
167 }
168 }
169 break;
170
171 case 3:
172 while (prev_dirty._next != &prev_dirty) {
173 PipelineCyclerLinks *link = prev_dirty._next;
174 while (link != &prev_dirty) {
175 PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)link;
176
177 if (!cycler->_lock.try_lock()) {
178 // No big deal, just move on to the next one for now, and we'll
179 // come back around to it. It's important not to block here in
180 // order to prevent one cycler from deadlocking another.
181 if (link->_prev != &prev_dirty || link->_next != &prev_dirty) {
182 link = cycler->_next;
183 continue;
184 } else {
185 // Well, we are the last cycler left, so we might as well wait.
186 // This is necessary to trigger the deadlock detection code.
187 cycler->_lock.lock();
188 }
189 }
190
191 MutexHolder holder(_lock);
192 cycler->remove_from_list();
193
194 saved_cdatas.push_back(cycler->cycle_3());
195
196 if (cycler->_dirty) {
197 // The cycler is still dirty. Add it back to the dirty list.
198 nassertd(cycler->_dirty == prev_seq) break;
199 cycler->insert_before(&_dirty);
200 cycler->_dirty = next_seq;
201 ++_num_dirty_cyclers;
202 } else {
203 // The cycler is now clean. Add it back to the clean list.
204 cycler->insert_before(&_clean);
205#ifdef DEBUG_THREADS
206 inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
207#endif
208 }
209 cycler->_lock.unlock();
210 break;
211 }
212 }
213 break;
214
215 default:
216 while (prev_dirty._next != &prev_dirty) {
217 PipelineCyclerLinks *link = prev_dirty._next;
218 while (link != &prev_dirty) {
219 PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)link;
220
221 if (!cycler->_lock.try_lock()) {
222 // No big deal, just move on to the next one for now, and we'll
223 // come back around to it. It's important not to block here in
224 // order to prevent one cycler from deadlocking another.
225 if (link->_prev != &prev_dirty || link->_next != &prev_dirty) {
226 link = cycler->_next;
227 continue;
228 } else {
229 // Well, we are the last cycler left, so we might as well wait.
230 // This is necessary to trigger the deadlock detection code.
231 cycler->_lock.lock();
232 }
233 }
234
235 MutexHolder holder(_lock);
236 cycler->remove_from_list();
237
238 saved_cdatas.push_back(cycler->cycle());
239
240 if (cycler->_dirty) {
241 // The cycler is still dirty. Add it back to the dirty list.
242 nassertd(cycler->_dirty == prev_seq) break;
243 cycler->insert_before(&_dirty);
244 cycler->_dirty = next_seq;
245 ++_num_dirty_cyclers;
246 } else {
247 // The cycler is now clean. Add it back to the clean list.
248 cycler->insert_before(&_clean);
249#ifdef DEBUG_THREADS
250 inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
251#endif
252 }
253 cycler->_lock.unlock();
254 break;
255 }
256 }
257 break;
258 }
259
260 // Now we're ready for the next frame.
261 prev_dirty.clear_head();
262 _cycling = false;
263 }
264
265 // And now it's safe to let the CycleData pointers in saved_cdatas destruct,
266 // which may cause cascading deletes, and which will in turn cause
267 // PipelineCyclers to remove themselves from (or add themselves to) the
268 // _dirty list.
269 saved_cdatas.clear();
270
271 if (pipeline_cat.is_debug()) {
272 pipeline_cat.debug()
273 << "Finished the pipeline cycle\n";
274 }
275
276#endif // THREADED_PIPELINE
277}
278
279/**
280 * Specifies the number of stages required for the pipeline.
281 */
283set_num_stages(int num_stages) {
284 nassertv(num_stages >= 1);
285#ifdef THREADED_PIPELINE
286 // Make sure it's not currently cycling.
287 ReMutexHolder cycle_holder(_cycle_lock);
288 MutexHolder holder(_lock);
289 if (num_stages != _num_stages) {
290
291 // We need to lock every PipelineCycler object attached to this pipeline
292 // before we can adjust the number of stages.
293 PipelineCyclerLinks *links;
294 for (links = _clean._next; links != &_clean; links = links->_next) {
295 PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)links;
296 cycler->_lock.lock();
297 }
298 for (links = _dirty._next; links != &_dirty; links = links->_next) {
299 PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)links;
300 cycler->_lock.lock();
301 }
302
303 _num_stages = num_stages;
304
305 for (links = _clean._next; links != &_clean; links = links->_next) {
306 PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)links;
307 cycler->set_num_stages(num_stages);
308 }
309 for (links = _dirty._next; links != &_dirty; links = links->_next) {
310 PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)links;
311 cycler->set_num_stages(num_stages);
312 }
313
314 // Now release them all.
315 int count = 0;
316 for (links = _clean._next; links != &_clean; links = links->_next) {
317 PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)links;
318 cycler->_lock.unlock();
319 ++count;
320 }
321 for (links = _dirty._next; links != &_dirty; links = links->_next) {
322 PipelineCyclerTrueImpl *cycler = (PipelineCyclerTrueImpl *)links;
323 cycler->_lock.unlock();
324 ++count;
325 }
326 nassertv(count == _num_cyclers);
327 }
328
329#else // THREADED_PIPELINE
330 if (num_stages != 1) {
331 pipeline_cat.warning()
332 << "Requested " << num_stages
333 << " pipeline stages but multithreaded render pipelines not enabled in build.\n";
334 }
335 _num_stages = 1;
336#endif // THREADED_PIPELINE
337}
338
339#ifdef THREADED_PIPELINE
340/**
341 * Adds the indicated cycler to the list of cyclers associated with the
342 * pipeline. This method only exists when true pipelining is configured on.
343 */
344void Pipeline::
345add_cycler(PipelineCyclerTrueImpl *cycler) {
346 // It's safe to add it to the list while cycling, since the _clean list is
347 // not touched during the cycle loop.
348 MutexHolder holder(_lock);
349 nassertv(!cycler->_dirty);
350
351 cycler->insert_before(&_clean);
352 ++_num_cyclers;
353
354#ifdef DEBUG_THREADS
355 inc_cycler_type(_all_cycler_types, cycler->get_parent_type(), 1);
356#endif
357}
358#endif // THREADED_PIPELINE
359
360#ifdef THREADED_PIPELINE
361/**
362 * Adds the indicated cycler to the list of cyclers associated with the
363 * pipeline. This method only exists when true pipelining is configured on.
364 *
365 * If the dirty flag is true, it will be marked as dirty in addition, as though
366 * add_dirty_cycler() were called immediately afterward.
367 */
368void Pipeline::
369add_cycler(PipelineCyclerTrueImpl *cycler, bool dirty) {
370 // It's safe to add it to the list while cycling, since the _clean list is
371 // not touched during the cycle loop.
372 MutexHolder holder(_lock);
373 nassertv(!cycler->_dirty);
374
375 if (!dirty) {
376 cycler->insert_before(&_clean);
377 }
378 else {
379 nassertv(_num_stages != 1);
380 cycler->insert_before(&_dirty);
381 cycler->_dirty = _next_cycle_seq;
382 ++_num_dirty_cyclers;
383
384#ifdef DEBUG_THREADS
385 inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), 1);
386#endif
387 }
388 ++_num_cyclers;
389
390#ifdef DEBUG_THREADS
391 inc_cycler_type(_all_cycler_types, cycler->get_parent_type(), 1);
392#endif
393}
394#endif // THREADED_PIPELINE
395
396#ifdef THREADED_PIPELINE
397/**
398 * Marks the indicated cycler as "dirty", meaning it will need to be cycled
399 * next frame. This both adds it to the "dirty" set and also sets the "dirty"
400 * flag within the cycler. This method only exists when true pipelining is
401 * configured on.
402 */
403void Pipeline::
404add_dirty_cycler(PipelineCyclerTrueImpl *cycler) {
405 nassertv(cycler->_lock.debug_is_locked());
406
407 // It's safe to add it to the list while cycling, since it's not currently
408 // on the dirty list.
409 MutexHolder holder(_lock);
410 nassertv(!cycler->_dirty);
411 nassertv(_num_stages != 1);
412
413 // Remove it from the "clean" list and add it to the "dirty" list.
414 cycler->remove_from_list();
415 cycler->insert_before(&_dirty);
416 cycler->_dirty = _next_cycle_seq;
417 ++_num_dirty_cyclers;
418
419#ifdef DEBUG_THREADS
420 inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), 1);
421#endif
422}
423#endif // THREADED_PIPELINE
424
425#ifdef THREADED_PIPELINE
426/**
427 * Removes the indicated cycler from the list of cyclers associated with the
428 * pipeline. This method only exists when true pipelining is configured on.
429 */
430void Pipeline::
431remove_cycler(PipelineCyclerTrueImpl *cycler) {
432 nassertv(cycler->_lock.debug_is_locked());
433
434 MutexHolder holder(_lock);
435
436 // If it's dirty, it may currently be processed by cycle(), so we need to be
437 // careful not to cause a race condition. It's safe for us to remove it
438 // during cycle only if it's 0 (clean) or _next_cycle_seq (scheduled for the
439 // next cycle, so not owned by the current one).
440 while (cycler->_dirty != 0 && cycler->_dirty != _next_cycle_seq) {
441 if (_cycle_lock.try_lock()) {
442 // OK, great, we got the lock, so it finished cycling already.
443 nassertv(!_cycling);
444
445 --_num_cyclers;
446 cycler->remove_from_list();
447
448 cycler->_dirty = false;
449 --_num_dirty_cyclers;
450
451 #ifdef DEBUG_THREADS
452 inc_cycler_type(_all_cycler_types, cycler->get_parent_type(), -1);
453 inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
454 #endif
455
456 _cycle_lock.unlock();
457 return;
458 } else {
459 // It's possibly currently being cycled. We will wait for the cycler
460 // to be done with it, so that we can safely remove it.
461 _lock.unlock();
462 cycler->_lock.unlock();
464 cycler->_lock.lock();
465 _lock.lock();
466 }
467 }
468
469 // It's not being owned by a cycle operation, so it's fair game.
470 --_num_cyclers;
471 cycler->remove_from_list();
472
473#ifdef DEBUG_THREADS
474 inc_cycler_type(_all_cycler_types, cycler->get_parent_type(), -1);
475#endif
476
477 if (cycler->_dirty) {
478 cycler->_dirty = 0;
479 --_num_dirty_cyclers;
480#ifdef DEBUG_THREADS
481 inc_cycler_type(_dirty_cycler_types, cycler->get_parent_type(), -1);
482#endif
483 }
484}
485#endif // THREADED_PIPELINE
486
487#if defined(THREADED_PIPELINE) && defined(DEBUG_THREADS)
488/**
489 * Walks through the list of all the different PipelineCycler types in the
490 * universe. For each one, calls the indicated callback function with the
491 * TypeHandle of the respective type (actually, the result of
492 * cycler::get_parent_type()) and the count of pipeline cyclers of that type.
493 * Mainly used for PStats reporting.
494 */
495void Pipeline::
496iterate_all_cycler_types(CallbackFunc *func, void *data) const {
497 // Make sure it's not currently cycling.
498 ReMutexHolder cycle_holder(_cycle_lock);
499 MutexHolder holder(_lock);
500 TypeCount::const_iterator ci;
501 for (ci = _all_cycler_types.begin(); ci != _all_cycler_types.end(); ++ci) {
502 func((*ci).first, (*ci).second, data);
503 }
504}
505#endif // THREADED_PIPELINE && DEBUG_THREADS
506
507#if defined(THREADED_PIPELINE) && defined(DEBUG_THREADS)
508/**
509 * Walks through the list of all the different PipelineCycler types, for only
510 * the dirty PipelineCyclers. See also iterate_all_cycler_types().
511 */
512void Pipeline::
513iterate_dirty_cycler_types(CallbackFunc *func, void *data) const {
514 // Make sure it's not currently cycling.
515 ReMutexHolder cycle_holder(_cycle_lock);
516 MutexHolder holder(_lock);
517 TypeCount::const_iterator ci;
518 for (ci = _dirty_cycler_types.begin(); ci != _dirty_cycler_types.end(); ++ci) {
519 func((*ci).first, (*ci).second, data);
520 }
521}
522#endif // THREADED_PIPELINE && DEBUG_THREADS
523
524/**
525 *
526 */
527void Pipeline::
528make_render_pipeline() {
529 ConfigVariableInt pipeline_stages
530 ("pipeline-stages", 1,
531 PRC_DESC("The initial number of stages in the render pipeline. This is "
532 "only meaningful if threaded pipelining is compiled into "
533 "Panda. In most cases, you should not set this at all anyway, "
534 "since the pipeline can automatically grow stages as needed, "
535 "but it will not remove stages automatically, and having more "
536 "pipeline stages than your application requires will incur "
537 "additional runtime overhead."));
538
539 nassertv(_render_pipeline == nullptr);
540 _render_pipeline = new Pipeline("render", pipeline_stages);
541}
542
543#if defined(THREADED_PIPELINE) && defined(DEBUG_THREADS)
544/**
545 * Increments (or decrements, according to added) the value for TypeHandle in
546 * the indicated TypeCount map. This is used in DEBUG_THREADS mode to track
547 * the types of PipelineCyclers that are coming and going, mainly for PStats
548 * reporting.
549 *
550 * It is assumed the lock is held during this call.
551 */
552void Pipeline::
553inc_cycler_type(TypeCount &count, TypeHandle type, int addend) {
554 TypeCount::iterator ci = count.find(type);
555 if (ci == count.end()) {
556 ci = count.insert(TypeCount::value_type(type, 0)).first;
557 }
558 (*ci).second += addend;
559 nassertv((*ci).second >= 0);
560}
561#endif // THREADED_PIPELINE && DEBUG_THREADS
This is a convenience class to specialize ConfigVariable as an integer type.
A single page of data maintained by a PipelineCycler.
Definition cycleData.h:50
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition mutexHolder.h:25
A base class for all things which can have a name.
Definition namable.h:26
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
void set_num_stages(int num_stages)
Specifies the number of stages required for the pipeline.
Definition pipeline.cxx:283
Similar to MutexHolder, but for a reentrant mutex.
static void force_yield()
Suspends the current thread for the rest of the current epoch.
Definition thread.I:201
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
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.