mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 10:54:24 -04:00

This is an optimization, which will skip begin_frame/end_frame for a buffer that isn't going to have anything rendered to it. Affects the RenderPipeline.
1671 lines
53 KiB
C++
1671 lines
53 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file graphicsOutput.cxx
|
|
* @author drose
|
|
* @date 2004-02-06
|
|
*/
|
|
|
|
#include "graphicsOutput.h"
|
|
#include "graphicsPipe.h"
|
|
#include "graphicsEngine.h"
|
|
#include "graphicsWindow.h"
|
|
#include "config_display.h"
|
|
#include "lightMutexHolder.h"
|
|
#include "renderBuffer.h"
|
|
#include "indirectLess.h"
|
|
#include "pStatTimer.h"
|
|
#include "configVariableBool.h"
|
|
#include "camera.h"
|
|
#include "displayRegion.h"
|
|
#include "lens.h"
|
|
#include "perspectiveLens.h"
|
|
#include "pointerTo.h"
|
|
#include "compassEffect.h"
|
|
#include "geom.h"
|
|
#include "geomNode.h"
|
|
#include "geomTristrips.h"
|
|
#include "geomVertexWriter.h"
|
|
#include "throw_event.h"
|
|
#include "config_gobj.h"
|
|
|
|
using std::string;
|
|
|
|
TypeHandle GraphicsOutput::_type_handle;
|
|
|
|
PStatCollector GraphicsOutput::_make_current_pcollector("Draw:Make current");
|
|
PStatCollector GraphicsOutput::_copy_texture_pcollector("Draw:Copy texture");
|
|
PStatCollector GraphicsOutput::_cull_pcollector("Cull");
|
|
PStatCollector GraphicsOutput::_draw_pcollector("Draw");
|
|
|
|
struct CubeFaceDef {
|
|
CubeFaceDef(const char *name, const LPoint3 &look_at, const LVector3 &up) :
|
|
_name(name), _look_at(look_at), _up(up) { }
|
|
|
|
const char *_name;
|
|
LPoint3 _look_at;
|
|
LVector3 _up;
|
|
};
|
|
|
|
static CubeFaceDef cube_faces[6] = {
|
|
CubeFaceDef("positive_x", LPoint3(1, 0, 0), LVector3(0, -1, 0)),
|
|
CubeFaceDef("negative_x", LPoint3(-1, 0, 0), LVector3(0, -1, 0)),
|
|
CubeFaceDef("positive_y", LPoint3(0, 1, 0), LVector3(0, 0, 1)),
|
|
CubeFaceDef("negative_y", LPoint3(0, -1, 0), LVector3(0, 0, -1)),
|
|
CubeFaceDef("positive_z", LPoint3(0, 0, 1), LVector3(0, -1, 0)),
|
|
CubeFaceDef("negative_z", LPoint3(0, 0, -1), LVector3(0, -1, 0))
|
|
};
|
|
|
|
/**
|
|
* Normally, the GraphicsOutput constructor is not called directly; these are
|
|
* created instead via the GraphicsEngine::make_window() function.
|
|
*/
|
|
GraphicsOutput::
|
|
GraphicsOutput(GraphicsEngine *engine, GraphicsPipe *pipe,
|
|
const string &name,
|
|
const FrameBufferProperties &fb_prop,
|
|
const WindowProperties &win_prop,
|
|
int flags,
|
|
GraphicsStateGuardian *gsg,
|
|
GraphicsOutput *host,
|
|
bool default_stereo_flags) :
|
|
_lock("GraphicsOutput"),
|
|
_cull_window_pcollector(_cull_pcollector, name),
|
|
_draw_window_pcollector(_draw_pcollector, name),
|
|
_clear_window_pcollector(_draw_window_pcollector, "Clear"),
|
|
_size(0, 0)
|
|
{
|
|
#ifdef DO_MEMORY_USAGE
|
|
MemoryUsage::update_type(this, this);
|
|
#endif
|
|
_engine = engine;
|
|
_pipe = pipe;
|
|
_gsg = gsg;
|
|
_host = host;
|
|
_fb_properties = fb_prop;
|
|
_name = name;
|
|
_creation_flags = flags;
|
|
_has_size = win_prop.has_size();
|
|
_is_nonzero_size = false;
|
|
if (_has_size) {
|
|
_size = win_prop.get_size();
|
|
_is_nonzero_size = (_size[0] > 0 && _size[1] > 0);
|
|
}
|
|
if (_creation_flags & GraphicsPipe::BF_size_track_host) {
|
|
// If we're tracking the host size, we assume we'll be nonzero eventually.
|
|
_is_nonzero_size = true;
|
|
}
|
|
|
|
_is_valid = false;
|
|
_flip_ready = false;
|
|
_target_tex_page = -1;
|
|
_prev_page_dr = nullptr;
|
|
_sort = 0;
|
|
_child_sort = 0;
|
|
_got_child_sort = false;
|
|
_internal_sort_index = 0;
|
|
_inverted = window_inverted;
|
|
_swap_eyes = swap_eyes;
|
|
_red_blue_stereo = false;
|
|
_left_eye_color_mask = 0x0f;
|
|
_right_eye_color_mask = 0x0f;
|
|
_side_by_side_stereo = false;
|
|
_sbs_left_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
|
|
_sbs_right_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
|
|
_delete_flag = false;
|
|
|
|
if (_fb_properties.is_single_buffered()) {
|
|
_draw_buffer_type = RenderBuffer::T_front;
|
|
} else {
|
|
_draw_buffer_type = RenderBuffer::T_back;
|
|
}
|
|
|
|
if (default_stereo_flags) {
|
|
// Check the config variables to see if we should make this a "stereo"
|
|
// buffer or window.
|
|
_red_blue_stereo = red_blue_stereo && !fb_prop.is_stereo();
|
|
if (_red_blue_stereo) {
|
|
_left_eye_color_mask = parse_color_mask(red_blue_stereo_colors.get_word(0));
|
|
_right_eye_color_mask = parse_color_mask(red_blue_stereo_colors.get_word(1));
|
|
}
|
|
_side_by_side_stereo = side_by_side_stereo && !fb_prop.is_stereo();
|
|
if (_side_by_side_stereo) {
|
|
_sbs_left_dimensions.set(sbs_left_dimensions[0], sbs_left_dimensions[1],
|
|
sbs_left_dimensions[2], sbs_left_dimensions[3]);
|
|
_sbs_right_dimensions.set(sbs_right_dimensions[0], sbs_right_dimensions[1],
|
|
sbs_right_dimensions[2], sbs_right_dimensions[3]);
|
|
}
|
|
}
|
|
|
|
// We start out with one DisplayRegion that covers the whole window, which
|
|
// we may use internally for full-window operations like clear() and
|
|
// get_screenshot().
|
|
_overlay_display_region = make_mono_display_region(0.0f, 1.0f, 0.0f, 1.0f);
|
|
_overlay_display_region->set_active(false);
|
|
_overlay_display_region->set_scissor_enabled(false);
|
|
|
|
// Make sure the "active" flag is set true for pipeline stage 0.
|
|
{
|
|
CDWriter cdata(_cycler, true);
|
|
cdata->_active = true;
|
|
}
|
|
|
|
// By default, each new GraphicsOutput is set up to clear color and depth.
|
|
set_clear_color_active(true);
|
|
set_clear_depth_active(true);
|
|
set_clear_stencil_active(true);
|
|
set_clear_color(background_color.get_value());
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
GraphicsOutput::
|
|
~GraphicsOutput() {
|
|
// The window should be closed by the time we destruct.
|
|
nassertv(!is_valid());
|
|
|
|
// We shouldn't have a GraphicsPipe pointer anymore.
|
|
nassertv(_pipe == nullptr);
|
|
|
|
// We don't have to destruct our child display regions explicitly, since
|
|
// they are all reference-counted and will go away when their pointers do.
|
|
// However, we do need to zero out their pointers to us.
|
|
TotalDisplayRegions::iterator dri;
|
|
for (dri = _total_display_regions.begin();
|
|
dri != _total_display_regions.end();
|
|
++dri) {
|
|
(*dri)->_window = nullptr;
|
|
}
|
|
|
|
_total_display_regions.clear();
|
|
_overlay_display_region = nullptr;
|
|
}
|
|
|
|
/**
|
|
* If the GraphicsOutput is currently rendering to a texture, then all
|
|
* textures are dissociated from the GraphicsOuput.
|
|
*/
|
|
void GraphicsOutput::
|
|
clear_render_textures() {
|
|
CDWriter cdata(_cycler, true);
|
|
cdata->_textures.clear();
|
|
++(cdata->_textures_seq);
|
|
throw_event("render-texture-targets-changed");
|
|
}
|
|
|
|
/**
|
|
* Creates a new Texture object, suitable for rendering the contents of this
|
|
* buffer into, and appends it to the list of render textures.
|
|
*
|
|
* If tex is not NULL, it is the texture that will be set up for rendering
|
|
* into; otherwise, a new Texture object will be created, in which case you
|
|
* may call get_texture() to retrieve the new texture pointer.
|
|
*
|
|
* You can specify a bitplane to attach the texture to. the legal choices
|
|
* are:
|
|
*
|
|
* - RTP_depth
|
|
* - RTP_depth_stencil
|
|
* - RTP_color
|
|
* - RTP_aux_rgba_0
|
|
* - RTP_aux_rgba_1
|
|
* - RTP_aux_rgba_2
|
|
* - RTP_aux_rgba_3
|
|
*
|
|
* If you do not specify a bitplane to attach the texture to, this routine
|
|
* will use a default based on the texture's format:
|
|
*
|
|
* - F_depth_component attaches to RTP_depth
|
|
* - F_depth_stencil attaches to RTP_depth_stencil
|
|
* - all other formats attach to RTP_color.
|
|
*
|
|
* The texture's format will be changed to match the format of the bitplane to
|
|
* which it is attached. For example, if you pass in an F_rgba texture and
|
|
* order that it be attached to RTP_depth_stencil, it will turn into an
|
|
* F_depth_stencil texture.
|
|
*
|
|
* Also see make_texture_buffer(), which is a higher-level interface for
|
|
* preparing render-to-a-texture mode.
|
|
*/
|
|
void GraphicsOutput::
|
|
add_render_texture(Texture *tex, RenderTextureMode mode,
|
|
RenderTexturePlane plane) {
|
|
|
|
if (mode == RTM_none) {
|
|
return;
|
|
}
|
|
LightMutexHolder holder(_lock);
|
|
|
|
// Create texture if necessary.
|
|
if (tex == nullptr) {
|
|
tex = new Texture(get_name());
|
|
tex->set_wrap_u(SamplerState::WM_clamp);
|
|
tex->set_wrap_v(SamplerState::WM_clamp);
|
|
} else {
|
|
tex->clear_ram_image();
|
|
}
|
|
|
|
// Set it to have no compression by default. You can restore compression
|
|
// later if you really, really want it; but this freaks out some drivers,
|
|
// and presumably it's a mistake if you have compression enabled for a
|
|
// rendered texture.
|
|
tex->set_compression(Texture::CM_off);
|
|
|
|
// Choose a default bitplane.
|
|
if (plane == RTP_COUNT) {
|
|
switch (tex->get_format()) {
|
|
case Texture::F_depth_stencil:
|
|
plane = RTP_depth_stencil;
|
|
break;
|
|
|
|
case Texture::F_depth_component:
|
|
case Texture::F_depth_component16:
|
|
case Texture::F_depth_component24:
|
|
case Texture::F_depth_component32:
|
|
plane = RTP_depth;
|
|
break;
|
|
|
|
default:
|
|
plane = RTP_color;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Set the texture's format to match the bitplane. (And validate the
|
|
// bitplane, while we're at it).
|
|
|
|
if (plane == RTP_depth) {
|
|
_fb_properties.setup_depth_texture(tex);
|
|
tex->set_match_framebuffer_format(true);
|
|
|
|
} else if (plane == RTP_depth_stencil) {
|
|
tex->set_format(Texture::F_depth_stencil);
|
|
if (_fb_properties.get_float_depth()) {
|
|
tex->set_component_type(Texture::T_float);
|
|
} else {
|
|
tex->set_component_type(Texture::T_unsigned_int_24_8);
|
|
}
|
|
tex->set_match_framebuffer_format(true);
|
|
|
|
} else if (plane == RTP_color ||
|
|
plane == RTP_aux_rgba_0 ||
|
|
plane == RTP_aux_rgba_1 ||
|
|
plane == RTP_aux_rgba_2 ||
|
|
plane == RTP_aux_rgba_3) {
|
|
_fb_properties.setup_color_texture(tex);
|
|
tex->set_match_framebuffer_format(true);
|
|
|
|
} else if (plane == RTP_aux_hrgba_0 ||
|
|
plane == RTP_aux_hrgba_1 ||
|
|
plane == RTP_aux_hrgba_2 ||
|
|
plane == RTP_aux_hrgba_3) {
|
|
tex->set_format(Texture::F_rgba16);
|
|
tex->set_match_framebuffer_format(true);
|
|
|
|
} else if (plane == RTP_aux_float_0 ||
|
|
plane == RTP_aux_float_1 ||
|
|
plane == RTP_aux_float_2 ||
|
|
plane == RTP_aux_float_3) {
|
|
tex->set_format(Texture::F_rgba32);
|
|
tex->set_component_type(Texture::T_float);
|
|
tex->set_match_framebuffer_format(true);
|
|
|
|
} else {
|
|
display_cat.error() <<
|
|
"add_render_texture: invalid bitplane specified.\n";
|
|
return;
|
|
}
|
|
|
|
// Go ahead and tell the texture our anticipated size, even if it might be
|
|
// inaccurate (particularly if this is a GraphicsWindow, which has system-
|
|
// imposed restrictions on size).
|
|
tex->set_size_padded(get_x_size(), get_y_size(), tex->get_z_size());
|
|
|
|
if (_fb_properties.is_stereo() && plane == RTP_color) {
|
|
if (tex->get_num_views() < 2) {
|
|
tex->set_num_views(2);
|
|
}
|
|
}
|
|
|
|
if (!support_render_texture || !get_supports_render_texture()) {
|
|
// Binding is not supported or it is disabled, so just fall back to copy
|
|
// instead.
|
|
if (mode == RTM_bind_or_copy) {
|
|
mode = RTM_copy_texture;
|
|
} else if (mode == RTM_bind_layered) {
|
|
// We can't fallback to copy, because that doesn't work for layered
|
|
// textures. The best thing we can do is raise an error message.
|
|
display_cat.error() <<
|
|
"add_render_texture: RTM_bind_layered was requested but "
|
|
"render-to-texture is not supported or has been disabled!\n";
|
|
}
|
|
}
|
|
|
|
if (mode == RTM_bind_layered && _gsg != nullptr && !_gsg->get_supports_geometry_shaders()) {
|
|
// Layered FBOs require a geometry shader to write to any but the first
|
|
// layer.
|
|
display_cat.warning() <<
|
|
"add_render_texture: RTM_bind_layered was requested but "
|
|
"geometry shaders are not supported!\n";
|
|
}
|
|
|
|
if (mode == RTM_bind_or_copy || mode == RTM_bind_layered) {
|
|
// If we're still planning on binding, indicate it in texture properly.
|
|
tex->set_render_to_texture(true);
|
|
}
|
|
|
|
CDWriter cdata(_cycler, true);
|
|
RenderTexture result;
|
|
result._texture = tex;
|
|
result._plane = plane;
|
|
result._rtm_mode = mode;
|
|
cdata->_textures.push_back(result);
|
|
++(cdata->_textures_seq);
|
|
|
|
throw_event("render-texture-targets-changed");
|
|
}
|
|
|
|
/**
|
|
* This is a deprecated interface that made sense back when GraphicsOutputs
|
|
* could only render into one texture at a time. From now on, use
|
|
* clear_render_textures and add_render_texture instead.
|
|
*/
|
|
void GraphicsOutput::
|
|
setup_render_texture(Texture *tex, bool allow_bind, bool to_ram) {
|
|
display_cat.warning() <<
|
|
"Using deprecated setup_render_texture interface.\n";
|
|
clear_render_textures();
|
|
if (to_ram) {
|
|
add_render_texture(tex, RTM_copy_ram);
|
|
} else if (allow_bind) {
|
|
add_render_texture(tex, RTM_bind_or_copy);
|
|
} else {
|
|
add_render_texture(tex, RTM_copy_texture);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the active flag associated with the GraphicsOutput. If the
|
|
* GraphicsOutput is marked inactive, nothing is rendered.
|
|
*/
|
|
void GraphicsOutput::
|
|
set_active(bool active) {
|
|
CDLockedReader cdata(_cycler);
|
|
if (cdata->_active != active) {
|
|
CDWriter cdataw(((GraphicsOutput *)this)->_cycler, cdata, true);
|
|
cdataw->_active = active;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if the window is ready to be rendered into, false otherwise.
|
|
*/
|
|
bool GraphicsOutput::
|
|
is_active() const {
|
|
if (!is_valid()) {
|
|
return false;
|
|
}
|
|
|
|
CDLockedReader cdata(_cycler);
|
|
if (!cdata->_active) {
|
|
return false;
|
|
}
|
|
|
|
if (cdata->_one_shot_frame != -1) {
|
|
// If one_shot is in effect, then we are active only for the one indicated
|
|
// frame.
|
|
if (cdata->_one_shot_frame != ClockObject::get_global_clock()->get_frame_count()) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If the window has a clear value set, it is active.
|
|
if (is_any_clear_active()) {
|
|
return true;
|
|
}
|
|
|
|
// If we triggered a copy operation, it is also active.
|
|
if (_trigger_copy) {
|
|
return true;
|
|
}
|
|
|
|
// The window is active if at least one display region is active.
|
|
if (cdata->_active_display_regions_stale) {
|
|
CDWriter cdataw(((GraphicsOutput *)this)->_cycler, cdata, false);
|
|
((GraphicsOutput *)this)->do_determine_display_regions(cdataw);
|
|
}
|
|
|
|
return !cdata->_active_display_regions.empty();
|
|
}
|
|
|
|
/**
|
|
* Changes the current setting of the one-shot flag. When this is true, the
|
|
* GraphicsOutput will render the current frame and then automatically set
|
|
* itself inactive. This is particularly useful for buffers that are created
|
|
* for the purposes of render-to-texture, for static textures that don't need
|
|
* to be continually re-rendered once they have been rendered the first time.
|
|
*
|
|
* Setting the buffer inactive is not the same thing as destroying it. You
|
|
* are still responsible for passing this buffer to
|
|
* GraphicsEngine::remove_window() when you no longer need the texture, in
|
|
* order to clean up fully. (However, you should not call remove_window() on
|
|
* this buffer while the texture is still needed, because depending on the
|
|
* render-to-texture mechanism in use, this may invalidate the texture
|
|
* contents.)
|
|
*/
|
|
void GraphicsOutput::
|
|
set_one_shot(bool one_shot) {
|
|
CDWriter cdata(_cycler, true);
|
|
if (one_shot) {
|
|
cdata->_one_shot_frame = ClockObject::get_global_clock()->get_frame_count();
|
|
} else {
|
|
cdata->_one_shot_frame = -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the current setting of the one-shot flag. When this is true, the
|
|
* GraphicsOutput will automatically set itself inactive after the next frame.
|
|
*/
|
|
bool GraphicsOutput::
|
|
get_one_shot() const {
|
|
CDReader cdata(_cycler);
|
|
return (cdata->_one_shot_frame == ClockObject::get_global_clock()->get_frame_count());
|
|
}
|
|
|
|
/**
|
|
* Changes the current setting of the inverted flag. When this is true, the
|
|
* scene is rendered into the window upside-down and backwards, that is,
|
|
* inverted as if viewed through a mirror placed on the floor.
|
|
*
|
|
* This is primarily intended to support DirectX (and a few buggy OpenGL
|
|
* graphics drivers) that perform a framebuffer-to-texture copy upside-down
|
|
* from the usual OpenGL (and Panda) convention. Panda will automatically set
|
|
* this flag for offscreen buffers on hardware that is known to do this, to
|
|
* compensate when rendering offscreen into a texture.
|
|
*/
|
|
void GraphicsOutput::
|
|
set_inverted(bool inverted) {
|
|
if (_inverted != inverted) {
|
|
_inverted = inverted;
|
|
|
|
if (get_y_size() != 0) {
|
|
// All of our DisplayRegions need to recompute their pixel positions
|
|
// now.
|
|
TotalDisplayRegions::iterator dri;
|
|
for (dri = _total_display_regions.begin();
|
|
dri != _total_display_regions.end();
|
|
++dri) {
|
|
(*dri)->compute_pixels(get_x_size(), get_y_size());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enables side-by-side stereo mode on this particular window. When side-by-
|
|
* side stereo mode is in effect, DisplayRegions that have the "left" channel
|
|
* set will render on the part of the window specified by sbs_left_dimensions
|
|
* (typically the left half: (0, 0.5, 0, 1)), while DisplayRegions that have
|
|
* the "right" channel set will render on the part of the window specified by
|
|
* sbs_right_dimensions (typically the right half: (0.5, 1, 0, 1)).
|
|
*
|
|
* This is commonly used in a dual-monitor mode, where a window is opened that
|
|
* spans two monitors, and each monitor represents a different eye.
|
|
*/
|
|
void GraphicsOutput::
|
|
set_side_by_side_stereo(bool side_by_side_stereo) {
|
|
LVecBase4 left, right;
|
|
left.set(sbs_left_dimensions[0], sbs_left_dimensions[1],
|
|
sbs_left_dimensions[2], sbs_left_dimensions[3]);
|
|
right.set(sbs_right_dimensions[0], sbs_right_dimensions[1],
|
|
sbs_right_dimensions[2], sbs_right_dimensions[3]);
|
|
set_side_by_side_stereo(side_by_side_stereo, left, right);
|
|
}
|
|
|
|
/**
|
|
* Enables side-by-side stereo mode on this particular window. When side-by-
|
|
* side stereo mode is in effect, DisplayRegions that have the "left" channel
|
|
* set will render on the part of the window specified by sbs_left_dimensions
|
|
* (typically the left half: (0, 0.5, 0, 1)), while DisplayRegions that have
|
|
* the "right" channel set will render on the part of the window specified by
|
|
* sbs_right_dimensions (typically the right half: (0.5, 1, 0, 1)).
|
|
*
|
|
* This is commonly used in a dual-monitor mode, where a window is opened that
|
|
* spans two monitors, and each monitor represents a different eye.
|
|
*/
|
|
void GraphicsOutput::
|
|
set_side_by_side_stereo(bool side_by_side_stereo,
|
|
const LVecBase4 &sbs_left_dimensions,
|
|
const LVecBase4 &sbs_right_dimensions) {
|
|
_side_by_side_stereo = side_by_side_stereo;
|
|
if (_side_by_side_stereo) {
|
|
_sbs_left_dimensions = sbs_left_dimensions;
|
|
_sbs_right_dimensions = sbs_right_dimensions;
|
|
} else {
|
|
_sbs_left_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
|
|
_sbs_right_dimensions.set(0.0f, 1.0f, 0.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the current setting of the delete flag. When this is true, the
|
|
* GraphicsOutput will automatically be removed before the beginning of the
|
|
* next frame by the GraphicsEngine.
|
|
*/
|
|
bool GraphicsOutput::
|
|
get_delete_flag() const {
|
|
// We only delete the window or buffer automatically when it is no longer
|
|
// associated with a texture.
|
|
for (int i = 0; i < (int)_hold_textures.size(); i++) {
|
|
if (_hold_textures[i].is_valid_pointer()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return _delete_flag;
|
|
}
|
|
|
|
/**
|
|
* Adjusts the sorting order of this particular GraphicsOutput, relative to
|
|
* other GraphicsOutputs.
|
|
*/
|
|
void GraphicsOutput::
|
|
set_sort(int sort) {
|
|
if (_sort != sort) {
|
|
if (_gsg != nullptr &&
|
|
_gsg->get_engine() != nullptr) {
|
|
_gsg->get_engine()->set_window_sort(this, sort);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new DisplayRegion that covers the indicated sub-rectangle within
|
|
* the window. The range on all parameters is 0..1.
|
|
*
|
|
* If is_stereo() is true for this window, and default-stereo-camera is
|
|
* configured true, this actually makes a StereoDisplayRegion. Call
|
|
* make_mono_display_region() or make_stereo_display_region() if you want to
|
|
* insist on one or the other.
|
|
*/
|
|
DisplayRegion *GraphicsOutput::
|
|
make_display_region(const LVecBase4 &dimensions) {
|
|
if (is_stereo() && default_stereo_camera) {
|
|
return make_stereo_display_region(dimensions);
|
|
} else {
|
|
return make_mono_display_region(dimensions);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new DisplayRegion that covers the indicated sub-rectangle within
|
|
* the window. The range on all parameters is 0..1.
|
|
*
|
|
* This generally returns a mono DisplayRegion, even if is_stereo() is true.
|
|
* However, if side-by-side stereo is enabled, this will return a
|
|
* StereoDisplayRegion whose two eyes are both set to SC_mono. (This is
|
|
* necessary because in side-by-side stereo mode, it is necessary to draw even
|
|
* mono DisplayRegions twice).
|
|
*/
|
|
DisplayRegion *GraphicsOutput::
|
|
make_mono_display_region(const LVecBase4 &dimensions) {
|
|
if (_side_by_side_stereo) {
|
|
StereoDisplayRegion *dr = make_stereo_display_region(dimensions);
|
|
dr->get_left_eye()->set_stereo_channel(Lens::SC_mono);
|
|
dr->get_right_eye()->set_stereo_channel(Lens::SC_mono);
|
|
return dr;
|
|
}
|
|
|
|
return new DisplayRegion(this, dimensions);
|
|
}
|
|
|
|
/**
|
|
* Creates a new DisplayRegion that covers the indicated sub-rectangle within
|
|
* the window. The range on all parameters is 0..1.
|
|
*
|
|
* This always returns a stereo DisplayRegion, even if is_stereo() is false.
|
|
*/
|
|
StereoDisplayRegion *GraphicsOutput::
|
|
make_stereo_display_region(const LVecBase4 &dimensions) {
|
|
PT(DisplayRegion) left, right;
|
|
|
|
if (_side_by_side_stereo) {
|
|
// On a side-by-side stereo window, each eye gets the corresponding
|
|
// dimensions of its own sub-region.
|
|
PN_stdfloat left_l = _sbs_left_dimensions[0];
|
|
PN_stdfloat left_b = _sbs_left_dimensions[2];
|
|
PN_stdfloat left_w = _sbs_left_dimensions[1] - _sbs_left_dimensions[0];
|
|
PN_stdfloat left_h = _sbs_left_dimensions[3] - _sbs_left_dimensions[2];
|
|
LVecBase4 left_dimensions(dimensions[0] * left_w + left_l,
|
|
dimensions[1] * left_w + left_l,
|
|
dimensions[2] * left_h + left_b,
|
|
dimensions[3] * left_h + left_b);
|
|
left = new DisplayRegion(this, left_dimensions);
|
|
|
|
PN_stdfloat right_l = _sbs_right_dimensions[0];
|
|
PN_stdfloat right_b = _sbs_right_dimensions[2];
|
|
PN_stdfloat right_w = _sbs_right_dimensions[1] - _sbs_right_dimensions[0];
|
|
PN_stdfloat right_h = _sbs_right_dimensions[3] - _sbs_right_dimensions[2];
|
|
LVecBase4 right_dimensions(dimensions[0] * right_w + right_l,
|
|
dimensions[1] * right_w + right_l,
|
|
dimensions[2] * right_h + right_b,
|
|
dimensions[3] * right_h + right_b);
|
|
right = new DisplayRegion(this, right_dimensions);
|
|
|
|
if (_swap_eyes) {
|
|
DisplayRegion *t = left;
|
|
left = right;
|
|
right = t;
|
|
}
|
|
|
|
} else {
|
|
// Not a side-by-side stereo window; thus, both the left and right eyes
|
|
// are the same region: the region specified.
|
|
left = new DisplayRegion(this, dimensions);
|
|
right = new DisplayRegion(this, dimensions);
|
|
|
|
// In this case, we assume that the two eyes will share the same depth
|
|
// buffer, which means the right eye should clear the depth buffer by
|
|
// default.
|
|
if (get_clear_depth_active()) {
|
|
right->set_clear_depth_active(true);
|
|
}
|
|
if (get_clear_stencil_active()) {
|
|
right->set_clear_stencil_active(true);
|
|
}
|
|
}
|
|
|
|
PT(StereoDisplayRegion) stereo = new StereoDisplayRegion(this, dimensions,
|
|
left, right);
|
|
|
|
return stereo;
|
|
}
|
|
|
|
/**
|
|
* Removes the indicated DisplayRegion from the window, and destructs it if
|
|
* there are no other references.
|
|
*
|
|
* Returns true if the DisplayRegion is found and removed, false if it was not
|
|
* a part of the window.
|
|
*/
|
|
bool GraphicsOutput::
|
|
remove_display_region(DisplayRegion *display_region) {
|
|
LightMutexHolder holder(_lock);
|
|
|
|
nassertr(display_region != _overlay_display_region, false);
|
|
|
|
if (display_region->is_stereo()) {
|
|
StereoDisplayRegion *sdr;
|
|
DCAST_INTO_R(sdr, display_region, false);
|
|
do_remove_display_region(sdr->get_left_eye());
|
|
do_remove_display_region(sdr->get_right_eye());
|
|
}
|
|
|
|
return do_remove_display_region(display_region);
|
|
}
|
|
|
|
/**
|
|
* Removes all display regions from the window, except the default one that is
|
|
* created with the window.
|
|
*/
|
|
void GraphicsOutput::
|
|
remove_all_display_regions() {
|
|
LightMutexHolder holder(_lock);
|
|
|
|
TotalDisplayRegions::iterator dri;
|
|
for (dri = _total_display_regions.begin();
|
|
dri != _total_display_regions.end();
|
|
++dri) {
|
|
DisplayRegion *display_region = (*dri);
|
|
if (display_region != _overlay_display_region) {
|
|
// Let's aggressively clean up the display region too.
|
|
display_region->cleanup();
|
|
display_region->_window = nullptr;
|
|
}
|
|
}
|
|
_total_display_regions.clear();
|
|
_total_display_regions.push_back(_overlay_display_region);
|
|
|
|
OPEN_ITERATE_ALL_STAGES(_cycler) {
|
|
CDStageWriter cdata(_cycler, pipeline_stage);
|
|
cdata->_active_display_regions_stale = true;
|
|
}
|
|
CLOSE_ITERATE_ALL_STAGES(_cycler);
|
|
}
|
|
|
|
/**
|
|
* Replaces the special "overlay" DisplayRegion that is created for each
|
|
* window or buffer. See get_overlay_display_region(). This must be a new
|
|
* DisplayRegion that has already been created for this window, for instance
|
|
* via a call to make_mono_display_region(). You are responsible for ensuring
|
|
* that the new DisplayRegion covers the entire window. The previous overlay
|
|
* display region is not automatically removed; you must explicitly call
|
|
* remove_display_region() on it after replacing it with this method, if you
|
|
* wish it to be removed.
|
|
*
|
|
* Normally, there is no reason to change the overlay DisplayRegion, so this
|
|
* method should be used only in very unusual circumstances.
|
|
*/
|
|
void GraphicsOutput::
|
|
set_overlay_display_region(DisplayRegion *display_region) {
|
|
nassertv(display_region->get_window() == this);
|
|
_overlay_display_region = display_region;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of DisplayRegions that have been created within the
|
|
* window, active or otherwise.
|
|
*/
|
|
int GraphicsOutput::
|
|
get_num_display_regions() const {
|
|
LightMutexHolder holder(_lock);
|
|
return _total_display_regions.size();
|
|
}
|
|
|
|
/**
|
|
* Returns the nth DisplayRegion of those that have been created within the
|
|
* window. This may return NULL if n is out of bounds; particularly likely if
|
|
* the number of display regions has changed since the last call to
|
|
* get_num_display_regions().
|
|
*/
|
|
PT(DisplayRegion) GraphicsOutput::
|
|
get_display_region(int n) const {
|
|
determine_display_regions();
|
|
PT(DisplayRegion) result;
|
|
{
|
|
LightMutexHolder holder(_lock);
|
|
if (n >= 0 && n < (int)_total_display_regions.size()) {
|
|
result = _total_display_regions[n];
|
|
} else {
|
|
result = nullptr;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of active DisplayRegions that have been created within
|
|
* the window.
|
|
*/
|
|
int GraphicsOutput::
|
|
get_num_active_display_regions() const {
|
|
determine_display_regions();
|
|
CDReader cdata(_cycler);
|
|
return cdata->_active_display_regions.size();
|
|
}
|
|
|
|
/**
|
|
* Returns the nth active DisplayRegion of those that have been created within
|
|
* the window. This may return NULL if n is out of bounds; particularly
|
|
* likely if the number of display regions has changed since the last call to
|
|
* get_num_active_display_regions().
|
|
*/
|
|
PT(DisplayRegion) GraphicsOutput::
|
|
get_active_display_region(int n) const {
|
|
determine_display_regions();
|
|
|
|
CDReader cdata(_cycler);
|
|
if (n >= 0 && n < (int)cdata->_active_display_regions.size()) {
|
|
return cdata->_active_display_regions[n];
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Creates and returns an offscreen buffer for rendering into, the result of
|
|
* which will be a texture suitable for applying to geometry within the scene
|
|
* rendered into this window.
|
|
*
|
|
* If tex is not NULL, it is the texture that will be set up for rendering
|
|
* into; otherwise, a new Texture object will be created. In either case, the
|
|
* target texture can be retrieved from the return value with
|
|
* buffer->get_texture() (assuming the return value is not NULL).
|
|
*
|
|
* If to_ram is true, the buffer will be set up to download its contents to
|
|
* the system RAM memory associated with the Texture object, instead of
|
|
* keeping it strictly within texture memory; this is much slower, but it
|
|
* allows using the texture with any GSG.
|
|
*
|
|
* This will attempt to be smart about maximizing render performance while
|
|
* minimizing framebuffer waste. It might return a GraphicsBuffer set to
|
|
* render directly into a texture, if possible; or it might return a
|
|
* ParasiteBuffer that renders into this window. The return value is NULL if
|
|
* the buffer could not be created for some reason.
|
|
*
|
|
* When you are done using the buffer, you should remove it with a call to
|
|
* GraphicsEngine::remove_window().
|
|
*/
|
|
GraphicsOutput *GraphicsOutput::
|
|
make_texture_buffer(const string &name, int x_size, int y_size,
|
|
Texture *tex, bool to_ram, FrameBufferProperties *fbp) {
|
|
|
|
FrameBufferProperties props;
|
|
props.set_rgb_color(1);
|
|
props.set_color_bits(1);
|
|
props.set_alpha_bits(1);
|
|
props.set_depth_bits(1);
|
|
|
|
if (fbp == nullptr) {
|
|
fbp = &props;
|
|
}
|
|
|
|
int flags = GraphicsPipe::BF_refuse_window;
|
|
if (textures_power_2 != ATS_none) {
|
|
flags |= GraphicsPipe::BF_size_power_2;
|
|
}
|
|
if (tex != nullptr &&
|
|
tex->get_texture_type() == Texture::TT_cube_map) {
|
|
flags |= GraphicsPipe::BF_size_square;
|
|
}
|
|
|
|
GraphicsOutput *buffer = get_gsg()->get_engine()->
|
|
make_output(get_gsg()->get_pipe(),
|
|
name, get_child_sort(),
|
|
*fbp, WindowProperties::size(x_size, y_size),
|
|
flags, get_gsg(), get_host());
|
|
|
|
if (buffer != nullptr) {
|
|
if (buffer->get_gsg() == nullptr ||
|
|
buffer->get_gsg()->get_prepared_objects() != get_gsg()->get_prepared_objects()) {
|
|
// If the newly-created buffer doesn't share texture objects with the
|
|
// current GSG, then we will have to force the texture copy to go
|
|
// through RAM.
|
|
to_ram = true;
|
|
}
|
|
|
|
buffer->add_render_texture(tex, to_ram ? RTM_copy_ram : RTM_bind_or_copy);
|
|
return buffer;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* This is similar to make_texture_buffer() in that it allocates a separate
|
|
* buffer suitable for rendering to a texture that can be assigned to geometry
|
|
* in this window, but in this case, the buffer is set up to render the six
|
|
* faces of a cube map.
|
|
*
|
|
* The buffer is automatically set up with six display regions and six
|
|
* cameras, each of which are assigned the indicated draw_mask and parented to
|
|
* the given camera_rig node (which you should then put in your scene to
|
|
* render the cube map from the appropriate point of view).
|
|
*
|
|
* You may take the texture associated with the buffer and apply it to
|
|
* geometry, particularly with TexGenAttrib::M_world_cube_map also in effect,
|
|
* to apply a reflection of everything seen by the camera rig.
|
|
*/
|
|
GraphicsOutput *GraphicsOutput::
|
|
make_cube_map(const string &name, int size, NodePath &camera_rig,
|
|
DrawMask camera_mask, bool to_ram, FrameBufferProperties *fbp) {
|
|
if (!to_ram) {
|
|
// Check the limits imposed by the GSG. (However, if we're rendering the
|
|
// texture to RAM only, these limits may be irrelevant.)
|
|
GraphicsStateGuardian *gsg = get_gsg();
|
|
int max_dimension = gsg->get_max_cube_map_dimension();
|
|
if (max_dimension == 0 || !gsg->get_supports_cube_map()) {
|
|
// The GSG doesn't support cube mapping; too bad for you.
|
|
display_cat.warning()
|
|
<< "Cannot make dynamic cube map; GSG does not support cube maps.\n";
|
|
return nullptr;
|
|
}
|
|
if (max_dimension > 0) {
|
|
size = std::min(max_dimension, size);
|
|
}
|
|
}
|
|
|
|
// Usually, we want the whole camera_rig to keep itself unrotated with
|
|
// respect to the world coordinate space, so the user can apply
|
|
// TexGenAttrib::M_world_cube_map to the objects on which the cube map
|
|
// texture is applied. If for some reason the user doesn't want this
|
|
// behavior, he can take this effect off again.
|
|
camera_rig.node()->set_effect(CompassEffect::make(NodePath()));
|
|
|
|
PT(Texture) tex = new Texture(name);
|
|
tex->setup_cube_map();
|
|
tex->set_wrap_u(SamplerState::WM_clamp);
|
|
tex->set_wrap_v(SamplerState::WM_clamp);
|
|
GraphicsOutput *buffer;
|
|
|
|
buffer = make_texture_buffer(name, size, size, tex, to_ram, fbp);
|
|
|
|
// We don't need to clear the overall buffer; instead, we'll clear each
|
|
// display region.
|
|
buffer->set_clear_color_active(false);
|
|
buffer->set_clear_depth_active(false);
|
|
buffer->set_clear_stencil_active(false);
|
|
|
|
PT(Lens) lens = new PerspectiveLens(90, 90);
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
PT(Camera) camera = new Camera(cube_faces[i]._name);
|
|
camera->set_lens(lens);
|
|
camera->set_camera_mask(camera_mask);
|
|
NodePath camera_np = camera_rig.attach_new_node(camera);
|
|
camera_np.look_at(cube_faces[i]._look_at, cube_faces[i]._up);
|
|
|
|
DisplayRegion *dr;
|
|
dr = buffer->make_display_region();
|
|
|
|
dr->set_target_tex_page(i);
|
|
dr->copy_clear_settings(*this);
|
|
dr->set_camera(camera_np);
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/**
|
|
* Returns a PandaNode containing a square polygon. The dimensions are
|
|
* (-1,0,-1) to (1,0,1). The texture coordinates are such that the texture of
|
|
* this GraphicsOutput is aligned properly to the polygon. The GraphicsOutput
|
|
* promises to surgically update the Geom inside the PandaNode if necessary to
|
|
* maintain this invariant.
|
|
*
|
|
* Each invocation of this function returns a freshly- allocated PandaNode.
|
|
* You can therefore safely modify the RenderAttribs of the PandaNode. The
|
|
* PandaNode is initially textured with the texture of this GraphicOutput.
|
|
*/
|
|
NodePath GraphicsOutput::
|
|
get_texture_card() {
|
|
if (_texture_card == nullptr) {
|
|
PT(GeomVertexData) vdata = create_texture_card_vdata(get_x_size(), get_y_size());
|
|
PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_static);
|
|
strip->set_shade_model(Geom::SM_uniform);
|
|
strip->add_next_vertices(4);
|
|
strip->close_primitive();
|
|
PT(Geom) geom = new Geom(vdata);
|
|
geom->add_primitive(strip);
|
|
_texture_card = new GeomNode("texture card");
|
|
_texture_card->add_geom(geom);
|
|
}
|
|
|
|
NodePath path("texture card");
|
|
path.node()->add_child(_texture_card);
|
|
|
|
// The texture card, by default, is textured with the first render-to-
|
|
// texture output texture. Depth and stencil textures are ignored. The
|
|
// user can freely alter the card's texture attrib.
|
|
CDReader cdata(_cycler);
|
|
RenderTextures::const_iterator ri;
|
|
for (ri = cdata->_textures.begin(); ri != cdata->_textures.end(); ++ri) {
|
|
Texture *texture = (*ri)._texture;
|
|
if ((texture->get_format() != Texture::F_depth_stencil)) {
|
|
path.set_texture(texture, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
/**
|
|
* Will attempt to use the depth buffer of the input graphics_output. The
|
|
* buffer sizes must be exactly the same.
|
|
*/
|
|
bool GraphicsOutput::
|
|
share_depth_buffer(GraphicsOutput *graphics_output) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Discontinue sharing the depth buffer.
|
|
*/
|
|
void GraphicsOutput::
|
|
unshare_depth_buffer() {
|
|
}
|
|
|
|
/**
|
|
* Returns true if this particular GraphicsOutput can render directly into a
|
|
* texture, or false if it must always copy-to-texture at the end of each
|
|
* frame to achieve this effect.
|
|
*/
|
|
bool GraphicsOutput::
|
|
get_supports_render_texture() const {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns true if a frame has been rendered and needs to be flipped, false
|
|
* otherwise.
|
|
*/
|
|
bool GraphicsOutput::
|
|
flip_ready() const {
|
|
return _flip_ready;
|
|
}
|
|
|
|
/**
|
|
* This is normally called only from within make_texture_buffer(). When
|
|
* called on a ParasiteBuffer, it returns the host of that buffer; but when
|
|
* called on some other buffer, it returns the buffer itself.
|
|
*/
|
|
GraphicsOutput *GraphicsOutput::
|
|
get_host() {
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* This is called by the GraphicsEngine to request that the window (or
|
|
* whatever) open itself or, in general, make itself valid, at the next call
|
|
* to process_events().
|
|
*/
|
|
void GraphicsOutput::
|
|
request_open() {
|
|
}
|
|
|
|
/**
|
|
* This is called by the GraphicsEngine to request that the window (or
|
|
* whatever) close itself or, in general, make itself invalid, at the next
|
|
* call to process_events(). By that time we promise the gsg pointer will be
|
|
* cleared.
|
|
*/
|
|
void GraphicsOutput::
|
|
request_close() {
|
|
}
|
|
|
|
/**
|
|
* This is called by the GraphicsEngine to insist that the output be closed
|
|
* immediately. This is only called from the window thread.
|
|
*/
|
|
void GraphicsOutput::
|
|
set_close_now() {
|
|
}
|
|
|
|
/**
|
|
* Resets the window framebuffer from its derived children. Does nothing
|
|
* here.
|
|
*/
|
|
void GraphicsOutput::
|
|
reset_window(bool swapchain) {
|
|
display_cat.info()
|
|
<< "Resetting " << get_type() << "\n";
|
|
}
|
|
|
|
/**
|
|
* Sets the window's _pipe pointer to NULL; this is generally called only as a
|
|
* precursor to deleting the window.
|
|
*/
|
|
void GraphicsOutput::
|
|
clear_pipe() {
|
|
_pipe = nullptr;
|
|
}
|
|
|
|
/**
|
|
* Changes the x_size and y_size, then recalculates structures that depend on
|
|
* size. The recalculation currently includes: - compute_pixels on all the
|
|
* graphics regions. - updating the texture card, if one is present.
|
|
*/
|
|
void GraphicsOutput::
|
|
set_size_and_recalc(int x, int y) {
|
|
_size.set(x, y);
|
|
_has_size = true;
|
|
|
|
_is_nonzero_size = (x > 0 && y > 0);
|
|
|
|
int fb_x_size = get_fb_x_size();
|
|
int fb_y_size = get_fb_y_size();
|
|
|
|
TotalDisplayRegions::iterator dri;
|
|
for (dri = _total_display_regions.begin();
|
|
dri != _total_display_regions.end();
|
|
++dri) {
|
|
(*dri)->compute_pixels_all_stages(fb_x_size, fb_y_size);
|
|
}
|
|
|
|
if (_texture_card != nullptr && _texture_card->get_num_geoms() > 0) {
|
|
_texture_card->modify_geom(0)->set_vertex_data(create_texture_card_vdata(x, y));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clears the entire framebuffer before rendering, according to the settings
|
|
* of get_color_clear_active() and get_depth_clear_active() (inherited from
|
|
* DrawableRegion).
|
|
*
|
|
* This function is called only within the draw thread.
|
|
*/
|
|
void GraphicsOutput::
|
|
clear(Thread *current_thread) {
|
|
if (is_any_clear_active()) {
|
|
if (display_cat.is_spam()) {
|
|
display_cat.spam()
|
|
<< "clear(): " << get_type() << " "
|
|
<< get_name() << " " << (void *)this << "\n";
|
|
}
|
|
|
|
nassertv(_gsg != nullptr);
|
|
|
|
DisplayRegionPipelineReader dr_reader(_overlay_display_region, current_thread);
|
|
_gsg->prepare_display_region(&dr_reader);
|
|
_gsg->clear(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function will be called within the draw thread before beginning
|
|
* rendering for a given frame. It should do whatever setup is required, and
|
|
* return true if the frame should be rendered, or false if it should be
|
|
* skipped.
|
|
*/
|
|
bool GraphicsOutput::
|
|
begin_frame(FrameMode mode, Thread *current_thread) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* This function will be called within the draw thread after rendering is
|
|
* completed for a given frame. It should do whatever finalization is
|
|
* required.
|
|
*/
|
|
void GraphicsOutput::
|
|
end_frame(FrameMode mode, Thread *current_thread) {
|
|
}
|
|
|
|
/**
|
|
* Called by the GraphicsEngine when the window is about to change to another
|
|
* DisplayRegion. This exists mainly to provide a callback for switching the
|
|
* cube map face, if we are rendering to the different faces of a cube map.
|
|
*/
|
|
void GraphicsOutput::
|
|
change_scenes(DisplayRegionPipelineReader *new_dr) {
|
|
int new_target_tex_page = new_dr->get_target_tex_page();
|
|
|
|
if (new_target_tex_page != -1 && new_target_tex_page != _target_tex_page) {
|
|
|
|
if (new_target_tex_page == -1) {
|
|
new_target_tex_page = 0;
|
|
}
|
|
int old_target_tex_page = _target_tex_page;
|
|
DisplayRegion *old_page_dr = _prev_page_dr;
|
|
_target_tex_page = new_target_tex_page;
|
|
_prev_page_dr = new_dr->get_object();
|
|
|
|
CDReader cdata(_cycler);
|
|
RenderTextures::const_iterator ri;
|
|
for (ri = cdata->_textures.begin(); ri != cdata->_textures.end(); ++ri) {
|
|
RenderTextureMode rtm_mode = (*ri)._rtm_mode;
|
|
RenderTexturePlane plane = (*ri)._plane;
|
|
Texture *texture = (*ri)._texture;
|
|
if (rtm_mode != RTM_none) {
|
|
if (rtm_mode == RTM_bind_or_copy || rtm_mode == RTM_bind_layered) {
|
|
// In render-to-texture mode, switch the rendering backend to the
|
|
// new page, so that the subsequent frame will be rendered to the
|
|
// correct page.
|
|
select_target_tex_page(_target_tex_page);
|
|
|
|
} else if (old_target_tex_page != -1) {
|
|
// In copy-to-texture mode, copy the just-rendered framebuffer to
|
|
// the old texture page.
|
|
|
|
nassertv(old_page_dr != nullptr);
|
|
if (display_cat.is_debug()) {
|
|
display_cat.debug()
|
|
<< "Copying texture for " << get_name() << " at scene change.\n";
|
|
display_cat.debug()
|
|
<< "target_tex_page = " << old_target_tex_page << "\n";
|
|
}
|
|
RenderBuffer buffer = _gsg->get_render_buffer(get_draw_buffer_type(),
|
|
get_fb_properties());
|
|
|
|
if (plane == RTP_color && _fb_properties.is_stereo()) {
|
|
// We've got two texture views to copy.
|
|
RenderBuffer left(_gsg, buffer._buffer_type & ~RenderBuffer::T_right);
|
|
RenderBuffer right(_gsg, buffer._buffer_type & ~RenderBuffer::T_left);
|
|
|
|
if (rtm_mode == RTM_copy_ram) {
|
|
_gsg->framebuffer_copy_to_ram(texture, 0, old_target_tex_page,
|
|
old_page_dr, left);
|
|
_gsg->framebuffer_copy_to_ram(texture, 1, old_target_tex_page,
|
|
old_page_dr, right);
|
|
} else {
|
|
_gsg->framebuffer_copy_to_texture(texture, 0, old_target_tex_page,
|
|
old_page_dr, left);
|
|
_gsg->framebuffer_copy_to_texture(texture, 1, old_target_tex_page,
|
|
old_page_dr, right);
|
|
}
|
|
} else {
|
|
if (rtm_mode == RTM_copy_ram) {
|
|
_gsg->framebuffer_copy_to_ram(texture, 0, old_target_tex_page,
|
|
old_page_dr, buffer);
|
|
} else {
|
|
_gsg->framebuffer_copy_to_texture(texture, 0, old_target_tex_page,
|
|
old_page_dr, buffer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called internally when the window is in render-to-a-texture mode and we are
|
|
* in the process of rendering the six faces of a cube map, or any other
|
|
* multi-page texture. This should do whatever needs to be done to switch the
|
|
* buffer to the indicated page.
|
|
*/
|
|
void GraphicsOutput::
|
|
select_target_tex_page(int) {
|
|
}
|
|
|
|
/**
|
|
* This function will be called within the draw thread after end_frame() has
|
|
* been called on all windows, to initiate the exchange of the front and back
|
|
* buffers.
|
|
*
|
|
* This should instruct the window to prepare for the flip at the next video
|
|
* sync, but it should not wait.
|
|
*
|
|
* We have the two separate functions, begin_flip() and end_flip(), to make it
|
|
* easier to flip all of the windows at the same time.
|
|
*/
|
|
void GraphicsOutput::
|
|
begin_flip() {
|
|
}
|
|
|
|
/**
|
|
* This function will be called within the draw thread after end_frame() has
|
|
* been called on all windows, to initiate the exchange of the front and back
|
|
* buffers.
|
|
*
|
|
* This should instruct the window to prepare for the flip when it is command
|
|
* but not actually flip
|
|
*
|
|
*/
|
|
void GraphicsOutput::
|
|
ready_flip() {
|
|
}
|
|
|
|
/**
|
|
* This function will be called within the draw thread after begin_flip() has
|
|
* been called on all windows, to finish the exchange of the front and back
|
|
* buffers.
|
|
*
|
|
* This should cause the window to wait for the flip, if necessary.
|
|
*/
|
|
void GraphicsOutput::
|
|
end_flip() {
|
|
_flip_ready = false;
|
|
}
|
|
|
|
/**
|
|
* Do whatever processing in the window thread is appropriate for this output
|
|
* object each frame.
|
|
*
|
|
* This function is called only within the window thread.
|
|
*/
|
|
void GraphicsOutput::
|
|
process_events() {
|
|
}
|
|
|
|
/**
|
|
* Called internally when the pixel factor changes.
|
|
*/
|
|
void GraphicsOutput::
|
|
pixel_factor_changed() {
|
|
if (_has_size) {
|
|
set_size_and_recalc(get_x_size(), get_y_size());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the delete flag, and do the usual cleanup activities associated with
|
|
* that.
|
|
*/
|
|
void GraphicsOutput::
|
|
prepare_for_deletion() {
|
|
CDWriter cdata(_cycler, true);
|
|
cdata->_active = false;
|
|
|
|
// If we were rendering directly to texture, we can't delete the buffer
|
|
// until all the textures are gone too.
|
|
RenderTextures::iterator ri;
|
|
for (ri = cdata->_textures.begin(); ri != cdata->_textures.end(); ++ri) {
|
|
if ((*ri)._rtm_mode == RTM_bind_or_copy || (*ri)._rtm_mode == RTM_bind_layered) {
|
|
_hold_textures.push_back((*ri)._texture);
|
|
}
|
|
}
|
|
cdata->_textures.clear();
|
|
|
|
_delete_flag = true;
|
|
|
|
// We have to be sure to remove all of the display regions immediately, so
|
|
// that circular reference counts can be cleared up (each display region
|
|
// keeps a pointer to a CullResult, which can hold all sorts of pointers).
|
|
remove_all_display_regions();
|
|
}
|
|
|
|
/**
|
|
* If any textures are marked RTM_bind_or_copy, change them to
|
|
* RTM_copy_texture. This does not change textures that are set to
|
|
* RTM_bind_layered, as layered framebuffers aren't supported with
|
|
* RTM_copy_texture.
|
|
*/
|
|
void GraphicsOutput::
|
|
promote_to_copy_texture() {
|
|
CDLockedReader cdata(_cycler);
|
|
RenderTextures::const_iterator ri;
|
|
|
|
bool any_bind = false;
|
|
for (ri = cdata->_textures.begin(); ri != cdata->_textures.end(); ++ri) {
|
|
if ((*ri)._rtm_mode == RTM_bind_or_copy) {
|
|
any_bind = true;
|
|
break;
|
|
}
|
|
}
|
|
if (any_bind) {
|
|
CDWriter cdataw(((GraphicsOutput *)this)->_cycler, cdata, true);
|
|
RenderTextures::iterator ri;
|
|
for (ri = cdataw->_textures.begin(); ri != cdataw->_textures.end(); ++ri) {
|
|
if ((*ri)._rtm_mode == RTM_bind_or_copy) {
|
|
(*ri)._rtm_mode = RTM_copy_texture;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* For all textures marked RTM_copy_texture, RTM_copy_ram,
|
|
* RTM_triggered_copy_texture, or RTM_triggered_copy_ram, do the necessary
|
|
* copies.
|
|
*
|
|
* Returns true if all copies are successful, false otherwise.
|
|
*/
|
|
bool GraphicsOutput::
|
|
copy_to_textures() {
|
|
bool okflag = true;
|
|
|
|
CDReader cdata(_cycler);
|
|
RenderTextures::const_iterator ri;
|
|
for (ri = cdata->_textures.begin(); ri != cdata->_textures.end(); ++ri) {
|
|
RenderTextureMode rtm_mode = (*ri)._rtm_mode;
|
|
if ((rtm_mode == RTM_none) || (rtm_mode == RTM_bind_or_copy)) {
|
|
continue;
|
|
}
|
|
|
|
Texture *texture = (*ri)._texture;
|
|
PStatTimer timer(_copy_texture_pcollector);
|
|
|
|
if ((rtm_mode == RTM_copy_texture)||
|
|
(rtm_mode == RTM_copy_ram)||
|
|
((rtm_mode == RTM_triggered_copy_texture)&&(_trigger_copy))||
|
|
((rtm_mode == RTM_triggered_copy_ram)&&(_trigger_copy))) {
|
|
if (display_cat.is_debug()) {
|
|
display_cat.debug()
|
|
<< "Copying texture for " << get_name() << " at frame end.\n";
|
|
display_cat.debug()
|
|
<< "target_tex_page = " << _target_tex_page << "\n";
|
|
}
|
|
RenderTexturePlane plane = (*ri)._plane;
|
|
RenderBuffer buffer(_gsg, DrawableRegion::get_renderbuffer_type(plane));
|
|
if (plane == RTP_color) {
|
|
buffer = _gsg->get_render_buffer(get_draw_buffer_type(),
|
|
get_fb_properties());
|
|
}
|
|
|
|
bool copied = false;
|
|
DisplayRegion *dr = _overlay_display_region;
|
|
if (_prev_page_dr != nullptr) {
|
|
dr = _prev_page_dr;
|
|
}
|
|
|
|
if (plane == RTP_color && _fb_properties.is_stereo()) {
|
|
// We've got two texture views to copy.
|
|
RenderBuffer left(_gsg, buffer._buffer_type & ~RenderBuffer::T_right);
|
|
RenderBuffer right(_gsg, buffer._buffer_type & ~RenderBuffer::T_left);
|
|
|
|
if ((rtm_mode == RTM_copy_ram)||(rtm_mode == RTM_triggered_copy_ram)) {
|
|
copied = _gsg->framebuffer_copy_to_ram(texture, 0, _target_tex_page,
|
|
dr, left);
|
|
copied = _gsg->framebuffer_copy_to_ram(texture, 1, _target_tex_page,
|
|
dr, right) && copied;
|
|
} else {
|
|
copied = _gsg->framebuffer_copy_to_texture(texture, 0, _target_tex_page,
|
|
dr, left);
|
|
copied = _gsg->framebuffer_copy_to_texture(texture, 1, _target_tex_page,
|
|
dr, right) && copied;
|
|
}
|
|
} else {
|
|
if ((rtm_mode == RTM_copy_ram)||(rtm_mode == RTM_triggered_copy_ram)) {
|
|
copied = _gsg->framebuffer_copy_to_ram(texture, 0, _target_tex_page,
|
|
dr, buffer);
|
|
} else {
|
|
copied = _gsg->framebuffer_copy_to_texture(texture, 0, _target_tex_page,
|
|
dr, buffer);
|
|
}
|
|
}
|
|
if (!copied) {
|
|
okflag = false;
|
|
}
|
|
}
|
|
}
|
|
if (_trigger_copy != nullptr) {
|
|
_trigger_copy->set_result(nullptr);
|
|
_trigger_copy = nullptr;
|
|
}
|
|
|
|
return okflag;
|
|
}
|
|
|
|
/**
|
|
* Generates a GeomVertexData for a texture card.
|
|
*/
|
|
PT(GeomVertexData) GraphicsOutput::
|
|
create_texture_card_vdata(int x, int y) {
|
|
PN_stdfloat xhi = 1.0;
|
|
PN_stdfloat yhi = 1.0;
|
|
|
|
if (Texture::get_textures_power_2() != ATS_none) {
|
|
int xru = Texture::up_to_power_2(x);
|
|
int yru = Texture::up_to_power_2(y);
|
|
xhi = (x * 1.0f) / xru;
|
|
yhi = (y * 1.0f) / yru;
|
|
}
|
|
|
|
CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3n3t2();
|
|
|
|
PT(GeomVertexData) vdata = new GeomVertexData
|
|
("card", format, Geom::UH_static);
|
|
|
|
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
|
|
GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
|
|
GeomVertexWriter normal(vdata, InternalName::get_normal());
|
|
|
|
vertex.add_data3(LVertex::rfu(-1.0f, 0.0f, 1.0f));
|
|
vertex.add_data3(LVertex::rfu(-1.0f, 0.0f, -1.0f));
|
|
vertex.add_data3(LVertex::rfu( 1.0f, 0.0f, 1.0f));
|
|
vertex.add_data3(LVertex::rfu( 1.0f, 0.0f, -1.0f));
|
|
|
|
texcoord.add_data2( 0.0f, yhi);
|
|
texcoord.add_data2( 0.0f, 0.0f);
|
|
texcoord.add_data2( xhi, yhi);
|
|
texcoord.add_data2( xhi, 0.0f);
|
|
|
|
normal.add_data3(LVector3::back());
|
|
normal.add_data3(LVector3::back());
|
|
normal.add_data3(LVector3::back());
|
|
normal.add_data3(LVector3::back());
|
|
|
|
return vdata;
|
|
}
|
|
|
|
/**
|
|
* Called by the DisplayRegion constructor to add the new DisplayRegion to the
|
|
* list.
|
|
*/
|
|
DisplayRegion *GraphicsOutput::
|
|
add_display_region(DisplayRegion *display_region) {
|
|
LightMutexHolder holder(_lock);
|
|
CDWriter cdata(_cycler, true);
|
|
cdata->_active_display_regions_stale = true;
|
|
|
|
_total_display_regions.push_back(display_region);
|
|
|
|
return display_region;
|
|
}
|
|
|
|
/**
|
|
* Internal implementation of remove_display_region. Assumes the lock is
|
|
* already held.
|
|
*/
|
|
bool GraphicsOutput::
|
|
do_remove_display_region(DisplayRegion *display_region) {
|
|
nassertr(display_region != _overlay_display_region, false);
|
|
|
|
PT(DisplayRegion) drp = display_region;
|
|
TotalDisplayRegions::iterator dri =
|
|
find(_total_display_regions.begin(), _total_display_regions.end(), drp);
|
|
if (dri != _total_display_regions.end()) {
|
|
// Let's aggressively clean up the display region too.
|
|
display_region->cleanup();
|
|
display_region->_window = nullptr;
|
|
_total_display_regions.erase(dri);
|
|
|
|
OPEN_ITERATE_ALL_STAGES(_cycler) {
|
|
CDStageWriter cdata(_cycler, pipeline_stage);
|
|
cdata->_active_display_regions_stale = true;
|
|
}
|
|
CLOSE_ITERATE_ALL_STAGES(_cycler);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Re-sorts the list of active DisplayRegions within the window.
|
|
*/
|
|
void GraphicsOutput::
|
|
do_determine_display_regions(GraphicsOutput::CData *cdata) {
|
|
cdata->_active_display_regions_stale = false;
|
|
|
|
cdata->_active_display_regions.clear();
|
|
cdata->_active_display_regions.reserve(_total_display_regions.size());
|
|
|
|
int index = 0;
|
|
TotalDisplayRegions::const_iterator dri;
|
|
for (dri = _total_display_regions.begin();
|
|
dri != _total_display_regions.end();
|
|
++dri) {
|
|
DisplayRegion *display_region = (*dri);
|
|
if (display_region->is_active()) {
|
|
cdata->_active_display_regions.push_back(display_region);
|
|
display_region->set_active_index(index);
|
|
++index;
|
|
} else {
|
|
display_region->set_active_index(-1);
|
|
}
|
|
}
|
|
|
|
std::stable_sort(cdata->_active_display_regions.begin(),
|
|
cdata->_active_display_regions.end(),
|
|
IndirectLess<DisplayRegion>());
|
|
}
|
|
|
|
/**
|
|
* Parses one of the keywords in the red-blue-stereo-colors Config.prc
|
|
* variable, and returns the corresponding bitmask.
|
|
*
|
|
* These bitmask values are taken from ColorWriteAttrib.
|
|
*/
|
|
unsigned int GraphicsOutput::
|
|
parse_color_mask(const string &word) {
|
|
unsigned int result = 0;
|
|
vector_string components;
|
|
tokenize(word, components, "|");
|
|
|
|
vector_string::const_iterator ci;
|
|
for (ci = components.begin(); ci != components.end(); ++ci) {
|
|
string w = downcase(*ci);
|
|
if (w == "red" || w == "r") {
|
|
result |= 0x001;
|
|
|
|
} else if (w == "green" || w == "g") {
|
|
result |= 0x002;
|
|
|
|
} else if (w == "blue" || w == "b") {
|
|
result |= 0x004;
|
|
|
|
} else if (w == "yellow" || w == "y") {
|
|
result |= 0x003;
|
|
|
|
} else if (w == "magenta" || w == "m") {
|
|
result |= 0x005;
|
|
|
|
} else if (w == "cyan" || w == "c") {
|
|
result |= 0x006;
|
|
|
|
} else if (w == "alpha" || w == "a") {
|
|
result |= 0x008;
|
|
|
|
} else if (w == "off") {
|
|
|
|
} else {
|
|
display_cat.warning()
|
|
<< "Invalid color in red-blue-stereo-colors: " << (*ci) << "\n";
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
GraphicsOutput::CData::
|
|
CData() {
|
|
// The default is *not* active, so the entire pipeline stage is initially
|
|
// populated with inactive outputs. Pipeline stage 0 is set to active in
|
|
// the constructor.
|
|
_active = false;
|
|
_one_shot_frame = -1;
|
|
_active_display_regions_stale = false;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
GraphicsOutput::CData::
|
|
CData(const GraphicsOutput::CData ©) :
|
|
_textures(copy._textures),
|
|
_active(copy._active),
|
|
_one_shot_frame(copy._one_shot_frame),
|
|
_active_display_regions(copy._active_display_regions),
|
|
_active_display_regions_stale(copy._active_display_regions_stale)
|
|
{
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
CycleData *GraphicsOutput::CData::
|
|
make_copy() const {
|
|
return new CData(*this);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
std::ostream &
|
|
operator << (std::ostream &out, GraphicsOutput::FrameMode fm) {
|
|
switch (fm) {
|
|
case GraphicsOutput::FM_render:
|
|
return out << "render";
|
|
case GraphicsOutput::FM_parasite:
|
|
return out << "parasite";
|
|
case GraphicsOutput::FM_refresh:
|
|
return out << "refresh";
|
|
}
|
|
|
|
return out << "(**invalid GraphicsOutput::FrameMode(" << (int)fm << ")**)";
|
|
}
|