Panda3D
graphicsOutput.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 graphicsOutput.cxx
10 * @author drose
11 * @date 2004-02-06
12 */
13
14#include "graphicsOutput.h"
15#include "graphicsPipe.h"
16#include "graphicsEngine.h"
17#include "graphicsWindow.h"
18#include "config_display.h"
19#include "lightMutexHolder.h"
20#include "renderBuffer.h"
21#include "indirectLess.h"
22#include "pStatTimer.h"
23#include "configVariableBool.h"
24#include "camera.h"
25#include "displayRegion.h"
26#include "lens.h"
27#include "perspectiveLens.h"
28#include "pointerTo.h"
29#include "compassEffect.h"
30#include "geom.h"
31#include "geomNode.h"
32#include "geomTristrips.h"
33#include "geomVertexWriter.h"
34#include "throw_event.h"
35#include "config_gobj.h"
36
37using std::string;
38
39TypeHandle GraphicsOutput::_type_handle;
40
41PStatCollector GraphicsOutput::_make_current_pcollector("Draw:Make current");
42PStatCollector GraphicsOutput::_copy_texture_pcollector("Draw:Copy texture");
43PStatCollector GraphicsOutput::_cull_pcollector("Cull");
44PStatCollector GraphicsOutput::_draw_pcollector("Draw");
45
46struct CubeFaceDef {
47 CubeFaceDef(const char *name, const LPoint3 &look_at, const LVector3 &up) :
48 _name(name), _look_at(look_at), _up(up) { }
49
50 const char *_name;
51 LPoint3 _look_at;
52 LVector3 _up;
53};
54
55static CubeFaceDef cube_faces[6] = {
56 CubeFaceDef("positive_x", LPoint3(1, 0, 0), LVector3(0, -1, 0)),
57 CubeFaceDef("negative_x", LPoint3(-1, 0, 0), LVector3(0, -1, 0)),
58 CubeFaceDef("positive_y", LPoint3(0, 1, 0), LVector3(0, 0, 1)),
59 CubeFaceDef("negative_y", LPoint3(0, -1, 0), LVector3(0, 0, -1)),
60 CubeFaceDef("positive_z", LPoint3(0, 0, 1), LVector3(0, -1, 0)),
61 CubeFaceDef("negative_z", LPoint3(0, 0, -1), LVector3(0, -1, 0))
62};
63
64/**
65 * Normally, the GraphicsOutput constructor is not called directly; these are
66 * created instead via the GraphicsEngine::make_output() function.
67 */
68GraphicsOutput::
69GraphicsOutput(GraphicsEngine *engine, GraphicsPipe *pipe,
70 const string &name,
71 const FrameBufferProperties &fb_prop,
72 const WindowProperties &win_prop,
73 int flags,
75 GraphicsOutput *host,
76 bool default_stereo_flags) :
77 _lock("GraphicsOutput"),
78 _cull_window_pcollector(_cull_pcollector, name),
79 _draw_window_pcollector(_draw_pcollector, name),
80 _clear_window_pcollector(_draw_window_pcollector, "Clear"),
81 _size(0, 0)
82{
83#ifdef DO_MEMORY_USAGE
84 MemoryUsage::update_type(this, this);
85#endif
86 _engine = engine;
87 _pipe = pipe;
88 _gsg = gsg;
89 _host = host;
90 _fb_properties = fb_prop;
91 _name = name;
92 _creation_flags = flags;
93 _has_size = win_prop.has_size();
94 _is_nonzero_size = false;
95 if (_has_size) {
96 _size = win_prop.get_size();
97 _is_nonzero_size = (_size[0] > 0 && _size[1] > 0);
98 }
99 if (_creation_flags & GraphicsPipe::BF_size_track_host) {
100 // If we're tracking the host size, we assume we'll be nonzero eventually.
101 _is_nonzero_size = true;
102 }
103
104 _is_valid = false;
105 _flip_ready = false;
106 _target_tex_page = -1;
107 _prev_page_dr = nullptr;
108 _sort = 0;
109 _child_sort = 0;
110 _got_child_sort = false;
111 _internal_sort_index = 0;
112 _inverted = window_inverted;
113 _swap_eyes = swap_eyes;
114 _red_blue_stereo = false;
115 _left_eye_color_mask = 0x0f;
116 _right_eye_color_mask = 0x0f;
117 _side_by_side_stereo = false;
118 _sbs_left_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
119 _sbs_right_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
120 _delete_flag = false;
121
122 if (_fb_properties.is_single_buffered()) {
123 _draw_buffer_type = RenderBuffer::T_front;
124 } else {
125 _draw_buffer_type = RenderBuffer::T_back;
126 }
127
128 if (default_stereo_flags) {
129 // Check the config variables to see if we should make this a "stereo"
130 // buffer or window.
131 _red_blue_stereo = red_blue_stereo && !fb_prop.is_stereo();
132 if (_red_blue_stereo) {
133 _left_eye_color_mask = parse_color_mask(red_blue_stereo_colors.get_word(0));
134 _right_eye_color_mask = parse_color_mask(red_blue_stereo_colors.get_word(1));
135 }
136 _side_by_side_stereo = side_by_side_stereo && !fb_prop.is_stereo();
137 if (_side_by_side_stereo) {
138 _sbs_left_dimensions.set(sbs_left_dimensions[0], sbs_left_dimensions[1],
139 sbs_left_dimensions[2], sbs_left_dimensions[3]);
140 _sbs_right_dimensions.set(sbs_right_dimensions[0], sbs_right_dimensions[1],
141 sbs_right_dimensions[2], sbs_right_dimensions[3]);
142 }
143 }
144
145 // We start out with one DisplayRegion that covers the whole window, which
146 // we may use internally for full-window operations like clear() and
147 // get_screenshot().
148 _overlay_display_region = make_mono_display_region(0.0f, 1.0f, 0.0f, 1.0f);
149 _overlay_display_region->set_active(false);
150 _overlay_display_region->set_scissor_enabled(false);
151
152 // Make sure the "active" flag is set true for pipeline stage 0.
153 {
154 CDWriter cdata(_cycler, true);
155 cdata->_active = true;
156 }
157
158 // By default, each new GraphicsOutput is set up to clear color and depth.
159 set_clear_color_active(true);
160 set_clear_depth_active(true);
161 set_clear_stencil_active(true);
162 set_clear_color(background_color.get_value());
163}
164
165/**
166 *
167 */
168GraphicsOutput::
169~GraphicsOutput() {
170 // The window should be closed by the time we destruct.
171 nassertv(!is_valid());
172
173 // We shouldn't have a GraphicsPipe pointer anymore.
174 nassertv(_pipe == nullptr);
175
176 // We don't have to destruct our child display regions explicitly, since
177 // they are all reference-counted and will go away when their pointers do.
178 // However, we do need to zero out their pointers to us.
179 TotalDisplayRegions::iterator dri;
180 for (dri = _total_display_regions.begin();
181 dri != _total_display_regions.end();
182 ++dri) {
183 (*dri)->_window = nullptr;
184 }
185
186 _total_display_regions.clear();
187 _overlay_display_region = nullptr;
188}
189
190/**
191 * If the GraphicsOutput is currently rendering to a texture, then all
192 * textures are dissociated from the GraphicsOuput.
193 */
196 CDWriter cdata(_cycler, true);
197 cdata->_textures.clear();
198 ++(cdata->_textures_seq);
199 throw_event("render-texture-targets-changed");
200}
201
202/**
203 * Creates a new Texture object, suitable for rendering the contents of this
204 * buffer into, and appends it to the list of render textures.
205 *
206 * If tex is not NULL, it is the texture that will be set up for rendering
207 * into; otherwise, a new Texture object will be created, in which case you
208 * may call get_texture() to retrieve the new texture pointer.
209 *
210 * You can specify a bitplane to attach the texture to. the legal choices
211 * are:
212 *
213 * - RTP_depth
214 * - RTP_depth_stencil
215 * - RTP_color
216 * - RTP_aux_rgba_0
217 * - RTP_aux_rgba_1
218 * - RTP_aux_rgba_2
219 * - RTP_aux_rgba_3
220 *
221 * If you do not specify a bitplane to attach the texture to, this routine
222 * will use a default based on the texture's format:
223 *
224 * - F_depth_component attaches to RTP_depth
225 * - F_depth_stencil attaches to RTP_depth_stencil
226 * - all other formats attach to RTP_color.
227 *
228 * The texture's format will be changed to match the format of the bitplane to
229 * which it is attached. For example, if you pass in an F_rgba texture and
230 * order that it be attached to RTP_depth_stencil, it will turn into an
231 * F_depth_stencil texture.
232 *
233 * Also see make_texture_buffer(), which is a higher-level interface for
234 * preparing render-to-a-texture mode.
235 */
237add_render_texture(Texture *tex, RenderTextureMode mode,
238 RenderTexturePlane plane) {
239
240 if (mode == RTM_none) {
241 return;
242 }
243 LightMutexHolder holder(_lock);
244
245 // Create texture if necessary.
246 if (tex == nullptr) {
247 tex = new Texture(get_name());
248 tex->set_wrap_u(SamplerState::WM_clamp);
249 tex->set_wrap_v(SamplerState::WM_clamp);
250 } else {
251 tex->clear_ram_image();
252 }
253
254 // Set it to have no compression by default. You can restore compression
255 // later if you really, really want it; but this freaks out some drivers,
256 // and presumably it's a mistake if you have compression enabled for a
257 // rendered texture.
258 tex->set_compression(Texture::CM_off);
259
260 // Choose a default bitplane.
261 if (plane == RTP_COUNT) {
262 switch (tex->get_format()) {
263 case Texture::F_depth_stencil:
264 plane = RTP_depth_stencil;
265 break;
266
267 case Texture::F_depth_component:
268 case Texture::F_depth_component16:
269 case Texture::F_depth_component24:
270 case Texture::F_depth_component32:
271 plane = RTP_depth;
272 break;
273
274 default:
275 plane = RTP_color;
276 break;
277 }
278 }
279
280 // Set the texture's format to match the bitplane. (And validate the
281 // bitplane, while we're at it).
282
283 if (plane == RTP_depth) {
284 _fb_properties.setup_depth_texture(tex);
286
287 } else if (plane == RTP_depth_stencil) {
288 tex->set_format(Texture::F_depth_stencil);
289 if (_fb_properties.get_float_depth()) {
290 tex->set_component_type(Texture::T_float);
291 } else {
292 tex->set_component_type(Texture::T_unsigned_int_24_8);
293 }
295
296 } else if (plane == RTP_color ||
297 plane == RTP_aux_rgba_0 ||
298 plane == RTP_aux_rgba_1 ||
299 plane == RTP_aux_rgba_2 ||
300 plane == RTP_aux_rgba_3) {
301 _fb_properties.setup_color_texture(tex);
303
304 } else if (plane == RTP_aux_hrgba_0 ||
305 plane == RTP_aux_hrgba_1 ||
306 plane == RTP_aux_hrgba_2 ||
307 plane == RTP_aux_hrgba_3) {
308 tex->set_format(Texture::F_rgba16);
310
311 } else if (plane == RTP_aux_float_0 ||
312 plane == RTP_aux_float_1 ||
313 plane == RTP_aux_float_2 ||
314 plane == RTP_aux_float_3) {
315 tex->set_format(Texture::F_rgba32);
316 tex->set_component_type(Texture::T_float);
318
319 } else {
320 display_cat.error() <<
321 "add_render_texture: invalid bitplane specified.\n";
322 return;
323 }
324
325 // Go ahead and tell the texture our anticipated size, even if it might be
326 // inaccurate (particularly if this is a GraphicsWindow, which has system-
327 // imposed restrictions on size).
329
330 if (_fb_properties.is_stereo() && plane == RTP_color) {
331 if (tex->get_num_views() < 2) {
332 tex->set_num_views(2);
333 }
334 }
335
336 if (!support_render_texture || !get_supports_render_texture()) {
337 // Binding is not supported or it is disabled, so just fall back to copy
338 // instead.
339 if (mode == RTM_bind_or_copy) {
340 mode = RTM_copy_texture;
341 } else if (mode == RTM_bind_layered) {
342 // We can't fallback to copy, because that doesn't work for layered
343 // textures. The best thing we can do is raise an error message.
344 display_cat.error() <<
345 "add_render_texture: RTM_bind_layered was requested but "
346 "render-to-texture is not supported or has been disabled!\n";
347 }
348 }
349
350 if (mode == RTM_bind_layered && _gsg != nullptr && !_gsg->get_supports_geometry_shaders()) {
351 // Layered FBOs require a geometry shader to write to any but the first
352 // layer.
353 display_cat.warning() <<
354 "add_render_texture: RTM_bind_layered was requested but "
355 "geometry shaders are not supported!\n";
356 }
357
358 if (mode == RTM_bind_or_copy || mode == RTM_bind_layered) {
359 // If we're still planning on binding, indicate it in texture properly.
360 tex->set_render_to_texture(true);
361 }
362 else if ((plane == RTP_depth || plane == RTP_depth_stencil) && _fb_properties.get_depth_bits() == 0) {
363 // If we're not providing the depth buffer, we need something to copy from.
364 display_cat.error()
365 << "add_render_texture: can't copy depth from framebuffer without depth bits!\n";
366 return;
367 }
368
369 CDWriter cdata(_cycler, true);
370 RenderTexture result;
371 result._texture = tex;
372 result._plane = plane;
373 result._rtm_mode = mode;
374 cdata->_textures.push_back(result);
375 ++(cdata->_textures_seq);
376
377 throw_event("render-texture-targets-changed");
378}
379
380/**
381 * This is a deprecated interface that made sense back when GraphicsOutputs
382 * could only render into one texture at a time. From now on, use
383 * clear_render_textures and add_render_texture instead.
384 *
385 * @deprecated Use add_render_texture() instead.
386 */
388setup_render_texture(Texture *tex, bool allow_bind, bool to_ram) {
389 display_cat.warning() <<
390 "Using deprecated setup_render_texture interface.\n";
392 if (to_ram) {
393 add_render_texture(tex, RTM_copy_ram);
394 } else if (allow_bind) {
395 add_render_texture(tex, RTM_bind_or_copy);
396 } else {
397 add_render_texture(tex, RTM_copy_texture);
398 }
399}
400
401/**
402 * Sets the active flag associated with the GraphicsOutput. If the
403 * GraphicsOutput is marked inactive, nothing is rendered.
404 */
406set_active(bool active) {
407 CDLockedReader cdata(_cycler);
408 if (cdata->_active != active) {
409 CDWriter cdataw(((GraphicsOutput *)this)->_cycler, cdata, true);
410 cdataw->_active = active;
411 }
412}
413
414/**
415 * Returns true if the window is ready to be rendered into, false otherwise.
416 */
418is_active() const {
419 if (!is_valid()) {
420 return false;
421 }
422
423 CDLockedReader cdata(_cycler);
424 if (!cdata->_active) {
425 return false;
426 }
427
428 if (cdata->_one_shot_frame != -1) {
429 // If one_shot is in effect, then we are active only for the one indicated
430 // frame.
431 if (cdata->_one_shot_frame != ClockObject::get_global_clock()->get_frame_count()) {
432 return false;
433 } else {
434 return true;
435 }
436 }
437
438 // If the window has a clear value set, it is active.
439 if (is_any_clear_active()) {
440 return true;
441 }
442
443 // If we triggered a copy operation, it is also active.
444 if (_trigger_copy) {
445 return true;
446 }
447
448 // The window is active if at least one display region is active.
449 if (cdata->_active_display_regions_stale) {
450 CDWriter cdataw(((GraphicsOutput *)this)->_cycler, cdata, false);
451 ((GraphicsOutput *)this)->do_determine_display_regions(cdataw);
452 return !cdataw->_active_display_regions.empty();
453 } else {
454 return !cdata->_active_display_regions.empty();
455 }
456}
457
458/**
459 * Changes the current setting of the one-shot flag. When this is true, the
460 * GraphicsOutput will render the current frame and then automatically set
461 * itself inactive. This is particularly useful for buffers that are created
462 * for the purposes of render-to-texture, for static textures that don't need
463 * to be continually re-rendered once they have been rendered the first time.
464 *
465 * Setting the buffer inactive is not the same thing as destroying it. You
466 * are still responsible for passing this buffer to
467 * GraphicsEngine::remove_window() when you no longer need the texture, in
468 * order to clean up fully. (However, you should not call remove_window() on
469 * this buffer while the texture is still needed, because depending on the
470 * render-to-texture mechanism in use, this may invalidate the texture
471 * contents.)
472 */
474set_one_shot(bool one_shot) {
475 CDWriter cdata(_cycler, true);
476 if (one_shot) {
477 cdata->_one_shot_frame = ClockObject::get_global_clock()->get_frame_count();
478 } else {
479 cdata->_one_shot_frame = -1;
480 }
481}
482
483/**
484 * Returns the current setting of the one-shot flag. When this is true, the
485 * GraphicsOutput will automatically set itself inactive after the next frame.
486 */
488get_one_shot() const {
489 CDReader cdata(_cycler);
490 return (cdata->_one_shot_frame == ClockObject::get_global_clock()->get_frame_count());
491}
492
493/**
494 * Changes the current setting of the inverted flag. When this is true, the
495 * scene is rendered into the window upside-down and backwards, that is,
496 * inverted as if viewed through a mirror placed on the floor.
497 *
498 * This is primarily intended to support DirectX (and a few buggy OpenGL
499 * graphics drivers) that perform a framebuffer-to-texture copy upside-down
500 * from the usual OpenGL (and Panda) convention. Panda will automatically set
501 * this flag for offscreen buffers on hardware that is known to do this, to
502 * compensate when rendering offscreen into a texture.
503 */
505set_inverted(bool inverted) {
506 if (_inverted != inverted) {
507 _inverted = inverted;
508
509 if (get_y_size() != 0) {
510 // All of our DisplayRegions need to recompute their pixel positions
511 // now.
512 TotalDisplayRegions::iterator dri;
513 for (dri = _total_display_regions.begin();
514 dri != _total_display_regions.end();
515 ++dri) {
516 (*dri)->compute_pixels(get_x_size(), get_y_size());
517 }
518 }
519 }
520}
521
522/**
523 * Enables side-by-side stereo mode on this particular window. When side-by-
524 * side stereo mode is in effect, DisplayRegions that have the "left" channel
525 * set will render on the part of the window specified by sbs_left_dimensions
526 * (typically the left half: (0, 0.5, 0, 1)), while DisplayRegions that have
527 * the "right" channel set will render on the part of the window specified by
528 * sbs_right_dimensions (typically the right half: (0.5, 1, 0, 1)).
529 *
530 * This is commonly used in a dual-monitor mode, where a window is opened that
531 * spans two monitors, and each monitor represents a different eye.
532 */
534set_side_by_side_stereo(bool side_by_side_stereo) {
535 LVecBase4 left, right;
536 left.set(sbs_left_dimensions[0], sbs_left_dimensions[1],
537 sbs_left_dimensions[2], sbs_left_dimensions[3]);
538 right.set(sbs_right_dimensions[0], sbs_right_dimensions[1],
539 sbs_right_dimensions[2], sbs_right_dimensions[3]);
540 set_side_by_side_stereo(side_by_side_stereo, left, right);
541}
542
543/**
544 * Enables side-by-side stereo mode on this particular window. When side-by-
545 * side stereo mode is in effect, DisplayRegions that have the "left" channel
546 * set will render on the part of the window specified by sbs_left_dimensions
547 * (typically the left half: (0, 0.5, 0, 1)), while DisplayRegions that have
548 * the "right" channel set will render on the part of the window specified by
549 * sbs_right_dimensions (typically the right half: (0.5, 1, 0, 1)).
550 *
551 * This is commonly used in a dual-monitor mode, where a window is opened that
552 * spans two monitors, and each monitor represents a different eye.
553 */
555set_side_by_side_stereo(bool side_by_side_stereo,
556 const LVecBase4 &sbs_left_dimensions,
557 const LVecBase4 &sbs_right_dimensions) {
558 _side_by_side_stereo = side_by_side_stereo;
559 if (_side_by_side_stereo) {
560 _sbs_left_dimensions = sbs_left_dimensions;
561 _sbs_right_dimensions = sbs_right_dimensions;
562 } else {
563 _sbs_left_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
564 _sbs_right_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
565 }
566}
567
568/**
569 * Returns the current setting of the delete flag. When this is true, the
570 * GraphicsOutput will automatically be removed before the beginning of the
571 * next frame by the GraphicsEngine.
572 */
574get_delete_flag() const {
575 // We only delete the window or buffer automatically when it is no longer
576 // associated with a texture.
577 for (int i = 0; i < (int)_hold_textures.size(); i++) {
578 if (_hold_textures[i].is_valid_pointer()) {
579 return false;
580 }
581 }
582
583 return _delete_flag;
584}
585
586/**
587 * Adjusts the sorting order of this particular GraphicsOutput, relative to
588 * other GraphicsOutputs.
589 */
591set_sort(int sort) {
592 if (_sort != sort) {
593 if (_gsg != nullptr &&
594 _gsg->get_engine() != nullptr) {
595 _gsg->get_engine()->set_window_sort(this, sort);
596 }
597 }
598}
599
600/**
601 * Creates a new DisplayRegion that covers the indicated sub-rectangle within
602 * the window. The range on all parameters is 0..1.
603 *
604 * If is_stereo() is true for this window, and default-stereo-camera is
605 * configured true, this actually makes a StereoDisplayRegion. Call
606 * make_mono_display_region() or make_stereo_display_region() if you want to
607 * insist on one or the other.
608 */
610make_display_region(const LVecBase4 &dimensions) {
611 if (is_stereo() && default_stereo_camera) {
612 return make_stereo_display_region(dimensions);
613 } else {
614 return make_mono_display_region(dimensions);
615 }
616}
617
618/**
619 * Creates a new DisplayRegion that covers the indicated sub-rectangle within
620 * the window. The range on all parameters is 0..1.
621 *
622 * This generally returns a mono DisplayRegion, even if is_stereo() is true.
623 * However, if side-by-side stereo is enabled, this will return a
624 * StereoDisplayRegion whose two eyes are both set to SC_mono. (This is
625 * necessary because in side-by-side stereo mode, it is necessary to draw even
626 * mono DisplayRegions twice).
627 */
629make_mono_display_region(const LVecBase4 &dimensions) {
630 if (_side_by_side_stereo) {
632 dr->get_left_eye()->set_stereo_channel(Lens::SC_mono);
633 dr->get_right_eye()->set_stereo_channel(Lens::SC_mono);
634 return dr;
635 }
636
637 return new DisplayRegion(this, dimensions);
638}
639
640/**
641 * Creates a new DisplayRegion that covers the indicated sub-rectangle within
642 * the window. The range on all parameters is 0..1.
643 *
644 * This always returns a stereo DisplayRegion, even if is_stereo() is false.
645 */
647make_stereo_display_region(const LVecBase4 &dimensions) {
648 PT(DisplayRegion) left, right;
649
650 if (_side_by_side_stereo) {
651 // On a side-by-side stereo window, each eye gets the corresponding
652 // dimensions of its own sub-region.
653 PN_stdfloat left_l = _sbs_left_dimensions[0];
654 PN_stdfloat left_b = _sbs_left_dimensions[2];
655 PN_stdfloat left_w = _sbs_left_dimensions[1] - _sbs_left_dimensions[0];
656 PN_stdfloat left_h = _sbs_left_dimensions[3] - _sbs_left_dimensions[2];
657 LVecBase4 left_dimensions(dimensions[0] * left_w + left_l,
658 dimensions[1] * left_w + left_l,
659 dimensions[2] * left_h + left_b,
660 dimensions[3] * left_h + left_b);
661 left = new DisplayRegion(this, left_dimensions);
662
663 PN_stdfloat right_l = _sbs_right_dimensions[0];
664 PN_stdfloat right_b = _sbs_right_dimensions[2];
665 PN_stdfloat right_w = _sbs_right_dimensions[1] - _sbs_right_dimensions[0];
666 PN_stdfloat right_h = _sbs_right_dimensions[3] - _sbs_right_dimensions[2];
667 LVecBase4 right_dimensions(dimensions[0] * right_w + right_l,
668 dimensions[1] * right_w + right_l,
669 dimensions[2] * right_h + right_b,
670 dimensions[3] * right_h + right_b);
671 right = new DisplayRegion(this, right_dimensions);
672
673 if (_swap_eyes) {
674 DisplayRegion *t = left;
675 left = right;
676 right = t;
677 }
678
679 } else {
680 // Not a side-by-side stereo window; thus, both the left and right eyes
681 // are the same region: the region specified.
682 left = new DisplayRegion(this, dimensions);
683 right = new DisplayRegion(this, dimensions);
684
685 // In this case, we assume that the two eyes will share the same depth
686 // buffer, which means the right eye should clear the depth buffer by
687 // default.
689 right->set_clear_depth_active(true);
690 }
692 right->set_clear_stencil_active(true);
693 }
694 }
695
696 PT(StereoDisplayRegion) stereo = new StereoDisplayRegion(this, dimensions,
697 left, right);
698
699 return stereo;
700}
701
702/**
703 * Removes the indicated DisplayRegion from the window, and destructs it if
704 * there are no other references.
705 *
706 * Returns true if the DisplayRegion is found and removed, false if it was not
707 * a part of the window.
708 */
710remove_display_region(DisplayRegion *display_region) {
711 LightMutexHolder holder(_lock);
712
713 nassertr(display_region != _overlay_display_region, false);
714
715 if (display_region->is_stereo()) {
717 DCAST_INTO_R(sdr, display_region, false);
718 do_remove_display_region(sdr->get_left_eye());
719 do_remove_display_region(sdr->get_right_eye());
720 }
721
722 return do_remove_display_region(display_region);
723}
724
725/**
726 * Removes all display regions from the window, except the default one that is
727 * created with the window.
728 */
731 LightMutexHolder holder(_lock);
732
733 TotalDisplayRegions::iterator dri;
734 for (dri = _total_display_regions.begin();
735 dri != _total_display_regions.end();
736 ++dri) {
737 DisplayRegion *display_region = (*dri);
738 if (display_region != _overlay_display_region) {
739 // Let's aggressively clean up the display region too.
740 display_region->cleanup();
741 display_region->_window = nullptr;
742 }
743 }
744 _total_display_regions.clear();
745 _total_display_regions.push_back(_overlay_display_region);
746
747 OPEN_ITERATE_ALL_STAGES(_cycler) {
748 CDStageWriter cdata(_cycler, pipeline_stage);
749 cdata->_active_display_regions_stale = true;
750 }
751 CLOSE_ITERATE_ALL_STAGES(_cycler);
752}
753
754/**
755 * Replaces the special "overlay" DisplayRegion that is created for each
756 * window or buffer. See get_overlay_display_region(). This must be a new
757 * DisplayRegion that has already been created for this window, for instance
758 * via a call to make_mono_display_region(). You are responsible for ensuring
759 * that the new DisplayRegion covers the entire window. The previous overlay
760 * display region is not automatically removed; you must explicitly call
761 * remove_display_region() on it after replacing it with this method, if you
762 * wish it to be removed.
763 *
764 * Normally, there is no reason to change the overlay DisplayRegion, so this
765 * method should be used only in very unusual circumstances.
766 */
769 nassertv(display_region->get_window() == this);
770 _overlay_display_region = display_region;
771}
772
773/**
774 * Returns the number of DisplayRegions that have been created within the
775 * window, active or otherwise.
776 */
779 LightMutexHolder holder(_lock);
780 return _total_display_regions.size();
781}
782
783/**
784 * Returns the nth DisplayRegion of those that have been created within the
785 * window. This may return NULL if n is out of bounds; particularly likely if
786 * the number of display regions has changed since the last call to
787 * get_num_display_regions().
788 */
789PT(DisplayRegion) GraphicsOutput::
790get_display_region(int n) const {
791 determine_display_regions();
792 PT(DisplayRegion) result;
793 {
794 LightMutexHolder holder(_lock);
795 if (n >= 0 && n < (int)_total_display_regions.size()) {
796 result = _total_display_regions[n];
797 } else {
798 result = nullptr;
799 }
800 }
801 return result;
802}
803
804/**
805 * Returns the number of active DisplayRegions that have been created within
806 * the window.
807 */
808int GraphicsOutput::
809get_num_active_display_regions() const {
810 determine_display_regions();
811 CDReader cdata(_cycler);
812 return cdata->_active_display_regions.size();
813}
814
815/**
816 * Returns the nth active DisplayRegion of those that have been created within
817 * the window. This may return NULL if n is out of bounds; particularly
818 * likely if the number of display regions has changed since the last call to
819 * get_num_active_display_regions().
820 */
821PT(DisplayRegion) GraphicsOutput::
822get_active_display_region(int n) const {
823 determine_display_regions();
824
825 CDReader cdata(_cycler);
826 if (n >= 0 && n < (int)cdata->_active_display_regions.size()) {
827 return cdata->_active_display_regions[n];
828 }
829 return nullptr;
830}
831
832/**
833 * Creates and returns an offscreen buffer for rendering into, the result of
834 * which will be a texture suitable for applying to geometry within the scene
835 * rendered into this window.
836 *
837 * If you pass zero as the buffer size, the buffer will have the same size as
838 * the host window, and will automatically be resized when the host window is.
839 *
840 * If tex is not NULL, it is the texture that will be set up for rendering
841 * into; otherwise, a new Texture object will be created. In either case, the
842 * target texture can be retrieved from the return value with
843 * buffer->get_texture() (assuming the return value is not NULL).
844 *
845 * If to_ram is true, the buffer will be set up to download its contents to
846 * the system RAM memory associated with the Texture object, instead of
847 * keeping it strictly within texture memory; this is much slower, but it
848 * allows using the texture with any GSG.
849 *
850 * This will attempt to be smart about maximizing render performance while
851 * minimizing framebuffer waste. It might return a GraphicsBuffer set to
852 * render directly into a texture, if possible; or it might return a
853 * ParasiteBuffer that renders into this window. The return value is NULL if
854 * the buffer could not be created for some reason.
855 *
856 * When you are done using the buffer, you should remove it with a call to
857 * GraphicsEngine::remove_window().
858 */
859GraphicsOutput *GraphicsOutput::
860make_texture_buffer(const string &name, int x_size, int y_size,
861 Texture *tex, bool to_ram, FrameBufferProperties *fbp) {
862
864 props.set_rgb_color(1);
865 props.set_color_bits(1);
866 props.set_alpha_bits(1);
867 props.set_depth_bits(1);
868
869 if (fbp == nullptr) {
870 fbp = &props;
871 }
872
873 int flags = GraphicsPipe::BF_refuse_window;
874 if (textures_power_2 != ATS_none) {
875 flags |= GraphicsPipe::BF_size_power_2;
876 }
877 if (tex != nullptr &&
878 tex->get_texture_type() == Texture::TT_cube_map) {
879 flags |= GraphicsPipe::BF_size_square;
880 }
881
882 GraphicsOutput *buffer = get_gsg()->get_engine()->
883 make_output(get_gsg()->get_pipe(),
884 name, get_child_sort(),
885 *fbp, WindowProperties::size(x_size, y_size),
886 flags, get_gsg(), get_host());
887
888 if (buffer != nullptr) {
889 if (buffer->get_gsg() == nullptr ||
890 buffer->get_gsg()->get_prepared_objects() != get_gsg()->get_prepared_objects()) {
891 // If the newly-created buffer doesn't share texture objects with the
892 // current GSG, then we will have to force the texture copy to go
893 // through RAM.
894 to_ram = true;
895 }
896
897 buffer->add_render_texture(tex, to_ram ? RTM_copy_ram : RTM_bind_or_copy);
898 return buffer;
899 }
900
901 return nullptr;
902}
903
904/**
905 * This is similar to make_texture_buffer() in that it allocates a separate
906 * buffer suitable for rendering to a texture that can be assigned to geometry
907 * in this window, but in this case, the buffer is set up to render the six
908 * faces of a cube map.
909 *
910 * The buffer is automatically set up with six display regions and six
911 * cameras, each of which are assigned the indicated draw_mask and parented to
912 * the given camera_rig node (which you should then put in your scene to
913 * render the cube map from the appropriate point of view).
914 *
915 * You may take the texture associated with the buffer and apply it to
916 * geometry, particularly with TexGenAttrib::M_world_cube_map also in effect,
917 * to apply a reflection of everything seen by the camera rig.
918 */
920make_cube_map(const string &name, int size, NodePath &camera_rig,
921 DrawMask camera_mask, bool to_ram, FrameBufferProperties *fbp) {
922 if (!to_ram) {
923 // Check the limits imposed by the GSG. (However, if we're rendering the
924 // texture to RAM only, these limits may be irrelevant.)
926 int max_dimension = gsg->get_max_cube_map_dimension();
927 if (max_dimension == 0 || !gsg->get_supports_cube_map()) {
928 // The GSG doesn't support cube mapping; too bad for you.
929 display_cat.warning()
930 << "Cannot make dynamic cube map; GSG does not support cube maps.\n";
931 return nullptr;
932 }
933 if (max_dimension > 0) {
934 size = std::min(max_dimension, size);
935 }
936 }
937
938 // Usually, we want the whole camera_rig to keep itself unrotated with
939 // respect to the world coordinate space, so the user can apply
940 // TexGenAttrib::M_world_cube_map to the objects on which the cube map
941 // texture is applied. If for some reason the user doesn't want this
942 // behavior, he can take this effect off again.
943 camera_rig.node()->set_effect(CompassEffect::make(NodePath()));
944
945 PT(Texture) tex = new Texture(name);
946 tex->setup_cube_map();
947 tex->set_wrap_u(SamplerState::WM_clamp);
948 tex->set_wrap_v(SamplerState::WM_clamp);
949 GraphicsOutput *buffer;
950
951 buffer = make_texture_buffer(name, size, size, tex, to_ram, fbp);
952
953 // We don't need to clear the overall buffer; instead, we'll clear each
954 // display region.
955 buffer->set_clear_color_active(false);
956 buffer->set_clear_depth_active(false);
957 buffer->set_clear_stencil_active(false);
958
959 PT(Lens) lens = new PerspectiveLens(90, 90);
960
961 for (int i = 0; i < 6; i++) {
962 PT(Camera) camera = new Camera(cube_faces[i]._name);
963 camera->set_lens(lens);
964 camera->set_camera_mask(camera_mask);
965 NodePath camera_np = camera_rig.attach_new_node(camera);
966 camera_np.look_at(cube_faces[i]._look_at, cube_faces[i]._up);
967
968 DisplayRegion *dr;
969 dr = buffer->make_display_region();
970
971 dr->set_target_tex_page(i);
972 dr->copy_clear_settings(*this);
973 dr->set_camera(camera_np);
974 }
975
976 return buffer;
977}
978
979/**
980 * Returns a PandaNode containing a square polygon. The dimensions are
981 * (-1,0,-1) to (1,0,1). The texture coordinates are such that the texture of
982 * this GraphicsOutput is aligned properly to the polygon. The GraphicsOutput
983 * promises to surgically update the Geom inside the PandaNode if necessary to
984 * maintain this invariant.
985 *
986 * Each invocation of this function returns a freshly- allocated PandaNode.
987 * You can therefore safely modify the RenderAttribs of the PandaNode. The
988 * PandaNode is initially textured with the texture of this GraphicOutput.
989 */
992 if (_texture_card == nullptr) {
993 PT(GeomVertexData) vdata = create_texture_card_vdata(get_x_size(), get_y_size());
994 PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_static);
995 strip->set_shade_model(Geom::SM_uniform);
996 strip->add_next_vertices(4);
997 strip->close_primitive();
998 PT(Geom) geom = new Geom(vdata);
999 geom->add_primitive(strip);
1000 _texture_card = new GeomNode("texture card");
1001 _texture_card->add_geom(geom);
1002 }
1003
1004 NodePath path("texture card");
1005 path.node()->add_child(_texture_card);
1006
1007 // The texture card, by default, is textured with the first render-to-
1008 // texture output texture. Depth and stencil textures are ignored. The
1009 // user can freely alter the card's texture attrib.
1010 CDReader cdata(_cycler);
1011 RenderTextures::const_iterator ri;
1012 for (ri = cdata->_textures.begin(); ri != cdata->_textures.end(); ++ri) {
1013 Texture *texture = (*ri)._texture;
1014 if ((texture->get_format() != Texture::F_depth_stencil)) {
1015 path.set_texture(texture, 0);
1016 break;
1017 }
1018 }
1019
1020 return path;
1021}
1022
1023/**
1024 * Will attempt to use the depth buffer of the input graphics_output. The
1025 * buffer sizes must be exactly the same.
1026 */
1028share_depth_buffer(GraphicsOutput *graphics_output) {
1029 return false;
1030}
1031
1032/**
1033 * Discontinue sharing the depth buffer.
1034 */
1037}
1038
1039/**
1040 * Returns true if this particular GraphicsOutput can render directly into a
1041 * texture, or false if it must always copy-to-texture at the end of each
1042 * frame to achieve this effect.
1043 */
1046 return false;
1047}
1048
1049/**
1050 * Returns true if a frame has been rendered and needs to be flipped, false
1051 * otherwise.
1052 */
1054flip_ready() const {
1055 return _flip_ready;
1056}
1057
1058/**
1059 * This is normally called only from within make_texture_buffer(). When
1060 * called on a ParasiteBuffer, it returns the host of that buffer; but when
1061 * called on some other buffer, it returns the buffer itself.
1062 */
1064get_host() {
1065 return this;
1066}
1067
1068/**
1069 * This is called by the GraphicsEngine to request that the window (or
1070 * whatever) open itself or, in general, make itself valid, at the next call
1071 * to process_events().
1072 */
1074request_open() {
1075}
1076
1077/**
1078 * This is called by the GraphicsEngine to request that the window (or
1079 * whatever) close itself or, in general, make itself invalid, at the next
1080 * call to process_events(). By that time we promise the gsg pointer will be
1081 * cleared.
1082 */
1084request_close() {
1085}
1086
1087/**
1088 * This is called by the GraphicsEngine to insist that the output be closed
1089 * immediately. This is only called from the window thread.
1090 */
1092set_close_now() {
1093}
1094
1095/**
1096 * Resets the window framebuffer from its derived children. Does nothing
1097 * here.
1098 */
1100reset_window(bool swapchain) {
1101 display_cat.info()
1102 << "Resetting " << get_type() << "\n";
1103}
1104
1105/**
1106 * Sets the window's _pipe pointer to NULL; this is generally called only as a
1107 * precursor to deleting the window.
1108 */
1110clear_pipe() {
1111 _pipe = nullptr;
1112}
1113
1114/**
1115 * Changes the x_size and y_size, then recalculates structures that depend on
1116 * size. The recalculation currently includes: - compute_pixels on all the
1117 * graphics regions. - updating the texture card, if one is present.
1118 */
1120set_size_and_recalc(int x, int y) {
1121 _size.set(x, y);
1122 _has_size = true;
1123
1124 _is_nonzero_size = (x > 0 && y > 0);
1125
1126 int fb_x_size = get_fb_x_size();
1127 int fb_y_size = get_fb_y_size();
1128
1129 TotalDisplayRegions::iterator dri;
1130 for (dri = _total_display_regions.begin();
1131 dri != _total_display_regions.end();
1132 ++dri) {
1133 (*dri)->compute_pixels_all_stages(fb_x_size, fb_y_size);
1134 }
1135
1136 if (_texture_card != nullptr && _texture_card->get_num_geoms() > 0) {
1137 _texture_card->modify_geom(0)->set_vertex_data(create_texture_card_vdata(x, y));
1138 }
1139}
1140
1141/**
1142 * Clears the entire framebuffer before rendering, according to the settings
1143 * of get_color_clear_active() and get_depth_clear_active() (inherited from
1144 * DrawableRegion).
1145 *
1146 * This function is called only within the draw thread.
1147 */
1149clear(Thread *current_thread) {
1150 if (is_any_clear_active()) {
1151 if (display_cat.is_spam()) {
1152 display_cat.spam()
1153 << "clear(): " << get_type() << " "
1154 << get_name() << " " << (void *)this << "\n";
1155 }
1156
1157 nassertv(_gsg != nullptr);
1158
1159 DisplayRegionPipelineReader dr_reader(_overlay_display_region, current_thread);
1160 _gsg->prepare_display_region(&dr_reader);
1161 _gsg->clear(this);
1162 }
1163}
1164
1165/**
1166 * This function will be called within the draw thread before beginning
1167 * rendering for a given frame. It should do whatever setup is required, and
1168 * return true if the frame should be rendered, or false if it should be
1169 * skipped.
1170 */
1172begin_frame(FrameMode mode, Thread *current_thread) {
1173 return false;
1174}
1175
1176/**
1177 * This function will be called within the draw thread after rendering is
1178 * completed for a given frame. It should do whatever finalization is
1179 * required.
1180 */
1182end_frame(FrameMode mode, Thread *current_thread) {
1183}
1184
1185/**
1186 * Called by the GraphicsEngine when the window is about to change to another
1187 * DisplayRegion. This exists mainly to provide a callback for switching the
1188 * cube map face, if we are rendering to the different faces of a cube map.
1189 */
1192 int new_target_tex_page = new_dr->get_target_tex_page();
1193
1194 if (new_target_tex_page != -1 && new_target_tex_page != _target_tex_page) {
1195
1196 if (new_target_tex_page == -1) {
1197 new_target_tex_page = 0;
1198 }
1199 int old_target_tex_page = _target_tex_page;
1200 DisplayRegion *old_page_dr = _prev_page_dr;
1201 _target_tex_page = new_target_tex_page;
1202 _prev_page_dr = new_dr->get_object();
1203
1204 CDReader cdata(_cycler);
1205 RenderTextures::const_iterator ri;
1206 for (ri = cdata->_textures.begin(); ri != cdata->_textures.end(); ++ri) {
1207 RenderTextureMode rtm_mode = (*ri)._rtm_mode;
1208 RenderTexturePlane plane = (*ri)._plane;
1209 Texture *texture = (*ri)._texture;
1210 if (rtm_mode != RTM_none) {
1211 if (rtm_mode == RTM_bind_or_copy || rtm_mode == RTM_bind_layered) {
1212 // In render-to-texture mode, switch the rendering backend to the
1213 // new page, so that the subsequent frame will be rendered to the
1214 // correct page.
1215 select_target_tex_page(_target_tex_page);
1216
1217 } else if (old_target_tex_page != -1) {
1218 // In copy-to-texture mode, copy the just-rendered framebuffer to
1219 // the old texture page.
1220
1221 nassertv(old_page_dr != nullptr);
1222 if (display_cat.is_debug()) {
1223 display_cat.debug()
1224 << "Copying texture for " << get_name() << " at scene change.\n";
1225 display_cat.debug()
1226 << "target_tex_page = " << old_target_tex_page << "\n";
1227 }
1228 RenderBuffer buffer = _gsg->get_render_buffer(get_draw_buffer_type(),
1230
1231 if (plane == RTP_color && _fb_properties.is_stereo()) {
1232 // We've got two texture views to copy.
1233 RenderBuffer left(_gsg, buffer._buffer_type & ~RenderBuffer::T_right);
1234 RenderBuffer right(_gsg, buffer._buffer_type & ~RenderBuffer::T_left);
1235
1236 if (rtm_mode == RTM_copy_ram) {
1237 _gsg->framebuffer_copy_to_ram(texture, 0, old_target_tex_page,
1238 old_page_dr, left);
1239 _gsg->framebuffer_copy_to_ram(texture, 1, old_target_tex_page,
1240 old_page_dr, right);
1241 } else {
1242 _gsg->framebuffer_copy_to_texture(texture, 0, old_target_tex_page,
1243 old_page_dr, left);
1244 _gsg->framebuffer_copy_to_texture(texture, 1, old_target_tex_page,
1245 old_page_dr, right);
1246 }
1247 } else {
1248 if (rtm_mode == RTM_copy_ram) {
1249 _gsg->framebuffer_copy_to_ram(texture, 0, old_target_tex_page,
1250 old_page_dr, buffer);
1251 } else {
1252 _gsg->framebuffer_copy_to_texture(texture, 0, old_target_tex_page,
1253 old_page_dr, buffer);
1254 }
1255 }
1256 }
1257 }
1258 }
1259 }
1260}
1261
1262/**
1263 * Called internally when the window is in render-to-a-texture mode and we are
1264 * in the process of rendering the six faces of a cube map, or any other
1265 * multi-page texture. This should do whatever needs to be done to switch the
1266 * buffer to the indicated page.
1267 */
1270}
1271
1272/**
1273 * This function will be called within the draw thread after end_frame() has
1274 * been called on all windows, to initiate the exchange of the front and back
1275 * buffers.
1276 *
1277 * This should instruct the window to prepare for the flip at the next video
1278 * sync, but it should not wait.
1279 *
1280 * We have the two separate functions, begin_flip() and end_flip(), to make it
1281 * easier to flip all of the windows at the same time.
1282 */
1284begin_flip() {
1285}
1286
1287/**
1288 * This function will be called within the draw thread after end_frame() has
1289 * been called on all windows, to initiate the exchange of the front and back
1290 * buffers.
1291 *
1292 * This should instruct the window to prepare for the flip when it is command
1293 * but not actually flip
1294 *
1295 */
1297ready_flip() {
1298}
1299
1300/**
1301 * This function will be called within the draw thread after begin_flip() has
1302 * been called on all windows, to finish the exchange of the front and back
1303 * buffers.
1304 *
1305 * This should cause the window to wait for the flip, if necessary.
1306 */
1308end_flip() {
1309 _flip_ready = false;
1310}
1311
1312/**
1313 * Do whatever processing in the window thread is appropriate for this output
1314 * object each frame.
1315 *
1316 * This function is called only within the window thread.
1317 */
1320}
1321
1322/**
1323 * Called internally when the pixel factor changes.
1324 */
1325void GraphicsOutput::
1326pixel_factor_changed() {
1327 if (_has_size) {
1329 }
1330}
1331
1332/**
1333 * Set the delete flag, and do the usual cleanup activities associated with
1334 * that.
1335 */
1336void GraphicsOutput::
1337prepare_for_deletion() {
1338 CDWriter cdata(_cycler, true);
1339 cdata->_active = false;
1340
1341 // If we were rendering directly to texture, we can't delete the buffer
1342 // until all the textures are gone too.
1343 RenderTextures::iterator ri;
1344 for (ri = cdata->_textures.begin(); ri != cdata->_textures.end(); ++ri) {
1345 if ((*ri)._rtm_mode == RTM_bind_or_copy || (*ri)._rtm_mode == RTM_bind_layered) {
1346 _hold_textures.push_back((*ri)._texture);
1347 }
1348 }
1349 cdata->_textures.clear();
1350
1351 _delete_flag = true;
1352
1353 // We have to be sure to remove all of the display regions immediately, so
1354 // that circular reference counts can be cleared up (each display region
1355 // keeps a pointer to a CullResult, which can hold all sorts of pointers).
1357}
1358
1359/**
1360 * If any textures are marked RTM_bind_or_copy, change them to
1361 * RTM_copy_texture. This does not change textures that are set to
1362 * RTM_bind_layered, as layered framebuffers aren't supported with
1363 * RTM_copy_texture.
1364 */
1365void GraphicsOutput::
1366promote_to_copy_texture() {
1367 CDLockedReader cdata(_cycler);
1368 RenderTextures::const_iterator ri;
1369
1370 bool any_bind = false;
1371 for (ri = cdata->_textures.begin(); ri != cdata->_textures.end(); ++ri) {
1372 if ((*ri)._rtm_mode == RTM_bind_or_copy) {
1373 any_bind = true;
1374 break;
1375 }
1376 }
1377 if (any_bind) {
1378 CDWriter cdataw(((GraphicsOutput *)this)->_cycler, cdata, true);
1379 RenderTextures::iterator ri;
1380 for (ri = cdataw->_textures.begin(); ri != cdataw->_textures.end(); ++ri) {
1381 if ((*ri)._rtm_mode == RTM_bind_or_copy) {
1382 (*ri)._rtm_mode = RTM_copy_texture;
1383 }
1384 }
1385 }
1386}
1387
1388/**
1389 * For all textures marked RTM_copy_texture, RTM_copy_ram,
1390 * RTM_triggered_copy_texture, or RTM_triggered_copy_ram, do the necessary
1391 * copies.
1392 *
1393 * Returns true if all copies are successful, false otherwise.
1394 */
1395bool GraphicsOutput::
1396copy_to_textures() {
1397 bool okflag = true;
1398
1399 CDReader cdata(_cycler);
1400 RenderTextures::const_iterator ri;
1401 for (ri = cdata->_textures.begin(); ri != cdata->_textures.end(); ++ri) {
1402 RenderTextureMode rtm_mode = (*ri)._rtm_mode;
1403 if ((rtm_mode == RTM_none) || (rtm_mode == RTM_bind_or_copy)) {
1404 continue;
1405 }
1406
1407 Texture *texture = (*ri)._texture;
1408 PStatTimer timer(_copy_texture_pcollector);
1409
1410 if ((rtm_mode == RTM_copy_texture)||
1411 (rtm_mode == RTM_copy_ram)||
1412 ((rtm_mode == RTM_triggered_copy_texture)&&(_trigger_copy))||
1413 ((rtm_mode == RTM_triggered_copy_ram)&&(_trigger_copy))) {
1414 if (display_cat.is_debug()) {
1415 display_cat.debug()
1416 << "Copying texture for " << get_name() << " at frame end.\n";
1417 display_cat.debug()
1418 << "target_tex_page = " << _target_tex_page << "\n";
1419 }
1420 RenderTexturePlane plane = (*ri)._plane;
1422 if (plane == RTP_color) {
1423 buffer = _gsg->get_render_buffer(get_draw_buffer_type(),
1425 }
1426
1427 bool copied = false;
1428 DisplayRegion *dr = _overlay_display_region;
1429 if (_prev_page_dr != nullptr) {
1430 dr = _prev_page_dr;
1431 }
1432
1433 if (plane == RTP_color && _fb_properties.is_stereo()) {
1434 // We've got two texture views to copy.
1435 RenderBuffer left(_gsg, buffer._buffer_type & ~RenderBuffer::T_right);
1436 RenderBuffer right(_gsg, buffer._buffer_type & ~RenderBuffer::T_left);
1437
1438 if ((rtm_mode == RTM_copy_ram)||(rtm_mode == RTM_triggered_copy_ram)) {
1439 copied = _gsg->framebuffer_copy_to_ram(texture, 0, _target_tex_page,
1440 dr, left);
1441 copied = _gsg->framebuffer_copy_to_ram(texture, 1, _target_tex_page,
1442 dr, right) && copied;
1443 } else {
1444 copied = _gsg->framebuffer_copy_to_texture(texture, 0, _target_tex_page,
1445 dr, left);
1446 copied = _gsg->framebuffer_copy_to_texture(texture, 1, _target_tex_page,
1447 dr, right) && copied;
1448 }
1449 } else {
1450 if ((rtm_mode == RTM_copy_ram)||(rtm_mode == RTM_triggered_copy_ram)) {
1451 copied = _gsg->framebuffer_copy_to_ram(texture, 0, _target_tex_page,
1452 dr, buffer);
1453 } else {
1454 copied = _gsg->framebuffer_copy_to_texture(texture, 0, _target_tex_page,
1455 dr, buffer);
1456 }
1457 }
1458 if (!copied) {
1459 okflag = false;
1460 }
1461 }
1462 }
1463 if (_trigger_copy != nullptr) {
1464 _trigger_copy->set_result(nullptr);
1465 _trigger_copy = nullptr;
1466 }
1467
1468 return okflag;
1469}
1470
1471/**
1472 * Generates a GeomVertexData for a texture card.
1473 */
1474PT(GeomVertexData) GraphicsOutput::
1475create_texture_card_vdata(int x, int y) {
1476 PN_stdfloat xhi = 1.0;
1477 PN_stdfloat yhi = 1.0;
1478
1479 if (Texture::get_textures_power_2() != ATS_none) {
1480 int xru = Texture::up_to_power_2(x);
1481 int yru = Texture::up_to_power_2(y);
1482 xhi = (x * 1.0f) / xru;
1483 yhi = (y * 1.0f) / yru;
1484 }
1485
1487
1488 PT(GeomVertexData) vdata = new GeomVertexData
1489 ("card", format, Geom::UH_static);
1490
1491 GeomVertexWriter vertex(vdata, InternalName::get_vertex());
1492 GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
1493 GeomVertexWriter normal(vdata, InternalName::get_normal());
1494
1495 vertex.add_data3(LVertex::rfu(-1.0f, 0.0f, 1.0f));
1496 vertex.add_data3(LVertex::rfu(-1.0f, 0.0f, -1.0f));
1497 vertex.add_data3(LVertex::rfu( 1.0f, 0.0f, 1.0f));
1498 vertex.add_data3(LVertex::rfu( 1.0f, 0.0f, -1.0f));
1499
1500 texcoord.add_data2( 0.0f, yhi);
1501 texcoord.add_data2( 0.0f, 0.0f);
1502 texcoord.add_data2( xhi, yhi);
1503 texcoord.add_data2( xhi, 0.0f);
1504
1505 normal.add_data3(LVector3::back());
1506 normal.add_data3(LVector3::back());
1507 normal.add_data3(LVector3::back());
1508 normal.add_data3(LVector3::back());
1509
1510 return vdata;
1511}
1512
1513/**
1514 * Called by the DisplayRegion constructor to add the new DisplayRegion to the
1515 * list.
1516 */
1517DisplayRegion *GraphicsOutput::
1518add_display_region(DisplayRegion *display_region) {
1519 LightMutexHolder holder(_lock);
1520 CDWriter cdata(_cycler, true);
1521 cdata->_active_display_regions_stale = true;
1522
1523 _total_display_regions.push_back(display_region);
1524
1525 return display_region;
1526}
1527
1528/**
1529 * Internal implementation of remove_display_region. Assumes the lock is
1530 * already held.
1531 */
1532bool GraphicsOutput::
1533do_remove_display_region(DisplayRegion *display_region) {
1534 nassertr(display_region != _overlay_display_region, false);
1535
1536 PT(DisplayRegion) drp = display_region;
1537 TotalDisplayRegions::iterator dri =
1538 find(_total_display_regions.begin(), _total_display_regions.end(), drp);
1539 if (dri != _total_display_regions.end()) {
1540 // Let's aggressively clean up the display region too.
1541 display_region->cleanup();
1542 display_region->_window = nullptr;
1543 _total_display_regions.erase(dri);
1544
1545 OPEN_ITERATE_ALL_STAGES(_cycler) {
1546 CDStageWriter cdata(_cycler, pipeline_stage);
1547 cdata->_active_display_regions_stale = true;
1548 }
1549 CLOSE_ITERATE_ALL_STAGES(_cycler);
1550 return true;
1551 }
1552
1553 return false;
1554}
1555
1556/**
1557 * Re-sorts the list of active DisplayRegions within the window.
1558 */
1559void GraphicsOutput::
1560do_determine_display_regions(GraphicsOutput::CData *cdata) {
1561 cdata->_active_display_regions_stale = false;
1562
1563 cdata->_active_display_regions.clear();
1564 cdata->_active_display_regions.reserve(_total_display_regions.size());
1565
1566 int index = 0;
1567 TotalDisplayRegions::const_iterator dri;
1568 for (dri = _total_display_regions.begin();
1569 dri != _total_display_regions.end();
1570 ++dri) {
1571 DisplayRegion *display_region = (*dri);
1572 if (display_region->is_active()) {
1573 cdata->_active_display_regions.push_back(display_region);
1574 display_region->set_active_index(index);
1575 ++index;
1576 } else {
1577 display_region->set_active_index(-1);
1578 }
1579 }
1580
1581 std::stable_sort(cdata->_active_display_regions.begin(),
1582 cdata->_active_display_regions.end(),
1584}
1585
1586/**
1587 * Parses one of the keywords in the red-blue-stereo-colors Config.prc
1588 * variable, and returns the corresponding bitmask.
1589 *
1590 * These bitmask values are taken from ColorWriteAttrib.
1591 */
1592unsigned int GraphicsOutput::
1593parse_color_mask(const string &word) {
1594 unsigned int result = 0;
1595 vector_string components;
1596 tokenize(word, components, "|");
1597
1598 vector_string::const_iterator ci;
1599 for (ci = components.begin(); ci != components.end(); ++ci) {
1600 string w = downcase(*ci);
1601 if (w == "red" || w == "r") {
1602 result |= 0x001;
1603
1604 } else if (w == "green" || w == "g") {
1605 result |= 0x002;
1606
1607 } else if (w == "blue" || w == "b") {
1608 result |= 0x004;
1609
1610 } else if (w == "yellow" || w == "y") {
1611 result |= 0x003;
1612
1613 } else if (w == "magenta" || w == "m") {
1614 result |= 0x005;
1615
1616 } else if (w == "cyan" || w == "c") {
1617 result |= 0x006;
1618
1619 } else if (w == "alpha" || w == "a") {
1620 result |= 0x008;
1621
1622 } else if (w == "off") {
1623
1624 } else {
1625 display_cat.warning()
1626 << "Invalid color in red-blue-stereo-colors: " << (*ci) << "\n";
1627 }
1628 }
1629
1630 return result;
1631}
1632
1633/**
1634 *
1635 */
1636GraphicsOutput::CData::
1637CData() {
1638 // The default is *not* active, so the entire pipeline stage is initially
1639 // populated with inactive outputs. Pipeline stage 0 is set to active in
1640 // the constructor.
1641 _active = false;
1642 _one_shot_frame = -1;
1643 _active_display_regions_stale = false;
1644}
1645
1646/**
1647 *
1648 */
1649GraphicsOutput::CData::
1650CData(const GraphicsOutput::CData &copy) :
1651 _textures(copy._textures),
1652 _active(copy._active),
1653 _one_shot_frame(copy._one_shot_frame),
1654 _active_display_regions(copy._active_display_regions),
1655 _active_display_regions_stale(copy._active_display_regions_stale)
1656{
1657}
1658
1659/**
1660 *
1661 */
1662CycleData *GraphicsOutput::CData::
1663make_copy() const {
1664 return new CData(*this);
1665}
1666
1667/**
1668 *
1669 */
1670std::ostream &
1671operator << (std::ostream &out, GraphicsOutput::FrameMode fm) {
1672 switch (fm) {
1673 case GraphicsOutput::FM_render:
1674 return out << "render";
1675 case GraphicsOutput::FM_parasite:
1676 return out << "parasite";
1677 case GraphicsOutput::FM_refresh:
1678 return out << "refresh";
1679 }
1680
1681 return out << "(**invalid GraphicsOutput::FrameMode(" << (int)fm << ")**)";
1682}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A node that can be positioned around in the scene graph to represent a point of view for rendering a ...
Definition: camera.h:35
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
Definition: clockObject.h:94
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
const LColor & get_value() const
Returns the variable's value.
std::string get_word(size_t n) const
Returns the variable's nth value.
This template class calls PipelineCycler::read_unlocked(), and then provides a transparent read-only ...
This class is similar to CycleDataWriter, except it allows writing to a particular stage of the pipel...
This template class calls PipelineCycler::write() in the constructor and PipelineCycler::release_writ...
A single page of data maintained by a PipelineCycler.
Definition: cycleData.h:50
Encapsulates the data from a DisplayRegion, pre-fetched for one stage of the pipeline.
int get_target_tex_page() const
Returns the target page number associated with this particular DisplayRegion, or -1 if it is not asso...
A rectangular subregion within a window for rendering into.
Definition: displayRegion.h:57
is_stereo
Returns true if this is a StereoDisplayRegion, false otherwise.
Definition: displayRegion.h:90
set_camera
Sets the camera that is associated with this DisplayRegion.
Definition: displayRegion.h:94
set_target_tex_page
This is a special parameter that is only used when rendering the faces of a cube map or multipage and...
is_active
Returns the active flag associated with the DisplayRegion.
Definition: displayRegion.h:98
get_window
Returns the GraphicsOutput that this DisplayRegion is ultimately associated with, or NULL if no windo...
Definition: displayRegion.h:88
void cleanup()
Cleans up some pointers associated with the DisplayRegion to help reduce the chance of memory leaks d...
int get_draw_buffer_type() const
Returns the RenderBuffer into which the GSG should issue draw commands.
void set_clear_stencil_active(bool clear_stencil_active)
Toggles the flag that indicates whether the stencil buffer should be cleared every frame.
void set_clear_color_active(bool clear_color_active)
Toggles the flag that indicates whether the color buffer should be cleared every frame.
static int get_renderbuffer_type(int plane)
Returns the RenderBuffer::Type that corresponds to a RenderTexturePlane.
bool get_clear_depth_active() const
Returns the current setting of the flag that indicates whether the depth buffer should be cleared eve...
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,...
void copy_clear_settings(const DrawableRegion &copy)
Copies only the clear settings from the other drawable region.
bool get_clear_stencil_active() const
Returns the current setting of the flag that indicates whether the color buffer should be cleared eve...
void set_clear_depth_active(bool clear_depth_active)
Toggles the flag that indicates whether the depth buffer should be cleared every frame.
A container for the various kinds of properties we might ask to have on a graphics frameBuffer before...
set_color_bits
Sets the number of requested color bits as a single number that represents the sum of the individual ...
bool setup_color_texture(Texture *tex) const
Sets the texture up for render-to-texture matching these framebuffer properties.
bool setup_depth_texture(Texture *tex) const
Sets the texture up for render-to-texture matching these framebuffer properties.
A node that holds Geom objects, renderable pieces of geometry.
Definition: geomNode.h:34
Defines a series of triangle strips.
Definition: geomTristrips.h:23
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
This class defines the physical layout of the vertex data stored within a Geom.
static const GeomVertexFormat * get_v3n3t2()
Returns a standard vertex format with a 2-component texture coordinate pair, a 3-component normal,...
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
void add_data2(PN_stdfloat x, PN_stdfloat y)
Sets the write row to a particular 2-component value, and advances the write row.
void add_data3(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z)
Sets the write row to a particular 3-component value, and advances the write row.
A container for geometry primitives.
Definition: geom.h:54
This class is the main interface to controlling the render process.
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,...
virtual void process_events()
Do whatever processing in the window thread is appropriate for this output object each frame.
void clear_render_textures()
If the GraphicsOutput is currently rendering to a texture, then all textures are dissociated from the...
int get_fb_x_size() const
Returns the internal width of the window or buffer.
set_inverted
Changes 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,...
void add_render_texture(Texture *tex, RenderTextureMode mode, RenderTexturePlane bitplane=RTP_COUNT)
Creates a new Texture object, suitable for rendering the contents of this buffer into,...
set_active
Sets the active flag associated with the GraphicsOutput.
void set_side_by_side_stereo(bool side_by_side_stereo)
Enables side-by-side stereo mode on this particular window.
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...
void remove_all_display_regions()
Removes all display regions from the window, except the default one that is created with the window.
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 bool share_depth_buffer(GraphicsOutput *graphics_output)
Will attempt to use the depth buffer of the input graphics_output.
set_one_shot
Changes the current setting of the one-shot flag.
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.
void setup_render_texture(Texture *tex, bool allow_bind, bool to_ram)
This is a deprecated interface that made sense back when GraphicsOutputs could only render into one t...
const FrameBufferProperties & get_fb_properties() const
Returns the framebuffer properties of the window.
StereoDisplayRegion * make_stereo_display_region()
Creates a new DisplayRegion that covers the entire window.
DisplayRegion * make_mono_display_region()
Creates a new DisplayRegion that covers the entire window.
virtual void unshare_depth_buffer()
Discontinue sharing the depth buffer.
get_supports_render_texture
Returns true if this particular GraphicsOutput can render directly into a texture,...
NodePath get_texture_card()
Returns a PandaNode containing a square polygon.
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.
get_num_display_regions
Returns the number of DisplayRegions that have been created within the window, active or otherwise.
get_child_sort
Returns the sort value of future offscreen buffers created by make_texture_sort().
get_one_shot
Returns the current setting of the one-shot flag.
int get_fb_y_size() const
Returns the internal height of the window or buffer.
DisplayRegion * make_display_region()
Creates a new DisplayRegion that covers the entire window.
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().
void set_size_and_recalc(int x, int y)
Changes the x_size and y_size, then recalculates structures that depend on size.
bool remove_display_region(DisplayRegion *display_region)
Removes the indicated DisplayRegion from the window, and destructs it if there are no other reference...
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,...
int get_y_size() const
Returns the visible height of the window or buffer, if it is known.
get_name
Returns the name that was passed to the GraphicsOutput constructor.
void set_overlay_display_region(DisplayRegion *display_region)
Replaces the special "overlay" DisplayRegion that is created for each window or buffer.
virtual void select_target_tex_page(int page)
Called internally when the window is in render-to-a-texture mode and we are in the process of renderi...
GraphicsOutput * make_cube_map(const std::string &name, int size, NodePath &camera_rig, DrawMask camera_mask=PandaNode::get_all_camera_mask(), bool to_ram=false, FrameBufferProperties *fbp=nullptr)
This is similar to make_texture_buffer() in that it allocates a separate buffer suitable for renderin...
bool is_stereo() const
Returns Returns true if this window can render stereo DisplayRegions, either through red-blue stereo ...
virtual void request_open()
This is called by the GraphicsEngine to request that the window (or whatever) open itself or,...
int get_x_size() const
Returns the visible width of the window or buffer, if it is known.
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()...
set_sort
Adjusts the sorting order of this particular GraphicsOutput, relative to other GraphicsOutputs.
virtual void set_close_now()
This is called by the GraphicsEngine to insist that the output be closed immediately.
An object to create GraphicsOutputs that share a particular 3-D API.
Definition: graphicsPipe.h:52
Encapsulates all the communication with a particular instance of a given rendering backend.
get_max_cube_map_dimension
Returns the largest possible texture size in any one dimension for a cube map texture,...
get_supports_cube_map
Returns true if this GSG can render cube map textures.
An STL function object class, this is intended to be used on any ordered collection of pointers to cl...
Definition: indirectLess.h:25
A base class for any number of different kinds of lenses, linear and otherwise.
Definition: lens.h:41
Similar to MutexHolder, but for a light mutex.
static void update_type(ReferenceCount *ptr, TypeHandle type)
Associates the indicated type with the given pointer.
Definition: memoryUsage.I:55
NodePath is the fundamental system for disambiguating instances, and also provides a higher-level int...
Definition: nodePath.h:159
void look_at(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z)
Sets the transform on this NodePath so that it rotates to face the indicated point in space.
Definition: nodePath.I:789
void set_texture(Texture *tex, int priority=0)
Adds the indicated texture to the list of textures that will be rendered on the default texture stage...
Definition: nodePath.cxx:2938
PandaNode * node() const
Returns the referenced node of the path.
Definition: nodePath.I:227
NodePath attach_new_node(PandaNode *node, int sort=0, Thread *current_thread=Thread::get_current_thread()) const
Attaches a new node, with or without existing parents, to the scene graph below the referenced node o...
Definition: nodePath.cxx:600
A lightweight class that represents a single element that may be timed and/or counted via stats.
A lightweight class that can be used to automatically start and stop a PStatCollector around a sectio...
Definition: pStatTimer.h:30
void set_effect(const RenderEffect *effect)
Adds the indicated render effect to the scene graph on this node.
Definition: pandaNode.cxx:999
A perspective-type lens: a normal camera.
A RenderBuffer is an arbitrary subset of the various layers (depth buffer, color buffer,...
Definition: renderBuffer.h:27
This is a special DisplayRegion wrapper that actually includes a pair of DisplayRegions internally: t...
get_right_eye
Returns a pointer to the right DisplayRegion managed by this stereo object.
get_left_eye
Returns a pointer to the left DisplayRegion managed by this stereo object.
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition: texture.h:71
set_compression
Requests that this particular Texture be compressed when it is loaded into texture memory.
Definition: texture.h:413
get_format
Returns the format of the texture, which represents both the semantic meaning of the texels and,...
Definition: texture.h:370
set_match_framebuffer_format
Sets the special flag that, if true, indicates to the GSG that the Texture's format should be chosen ...
Definition: texture.h:589
set_wrap_u
This setting determines what happens when the texture is sampled with a U value outside the range 0....
Definition: texture.h:378
set_num_views
Sets the number of "views" within a texture.
Definition: texture.h:354
get_num_views
Returns the number of "views" in the texture.
Definition: texture.h:354
get_texture_type
Returns the overall interpretation of the texture.
Definition: texture.h:365
set_format
Changes the format value for the texture components.
Definition: texture.h:370
set_render_to_texture
Sets a flag on the texture that indicates whether the texture is intended to be used as a direct-rend...
Definition: texture.h:417
void setup_cube_map()
Sets the texture as an empty cube map texture with no dimensions.
Definition: texture.I:155
get_z_size
Returns the depth of the texture image in texels.
Definition: texture.h:350
static int up_to_power_2(int value)
Returns the smallest power of 2 greater than or equal to value.
Definition: texture.cxx:2009
static AutoTextureScale get_textures_power_2()
This flag returns ATS_none, ATS_up, or ATS_down and controls the scaling of textures in general.
Definition: texture.I:1863
void clear_ram_image()
Discards the current system-RAM image.
Definition: texture.I:1439
void set_size_padded(int x=1, int y=1, int z=1)
Changes the size of the texture, padding if necessary, and setting the pad region as well.
Definition: texture.cxx:1933
set_wrap_v
This setting determines what happens when the texture is sampled with a V value outside the range 0....
Definition: texture.h:382
set_component_type
Changes the data value for the texture components.
Definition: texture.h:374
A thread; that is, a lightweight process.
Definition: thread.h:46
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
A container for the various kinds of properties we might ask to have on a graphics window before we o...
static WindowProperties size(const LVecBase2i &size)
Returns a WindowProperties structure with only the size specified.
has_size
Returns true if the window size has been specified, false otherwise.
get_size
Returns size in pixels of the useful part of the window, not including decorations.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PT(DisplayRegion) GraphicsOutput
Returns the nth DisplayRegion of those that have been created within the window.
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.
string downcase(const string &s)
Returns the input string with all uppercase letters converted to lowercase.
void tokenize(const string &str, vector_string &words, const string &delimiters, bool discard_repeated_delimiters)
Chops the source string up into pieces delimited by any of the characters specified in delimiters.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.