mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-03 18:31:55 -04:00
565 lines
18 KiB
C++
565 lines
18 KiB
C++
// Filename: wglGraphicsBuffer.cxx
|
|
// Created by: drose (08Feb04)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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."
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "wglGraphicsBuffer.h"
|
|
#include "wglGraphicsPipe.h"
|
|
#include "config_wgldisplay.h"
|
|
#include "glgsg.h"
|
|
#include "pStatTimer.h"
|
|
|
|
#include <wingdi.h>
|
|
|
|
TypeHandle wglGraphicsBuffer::_type_handle;
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::Constructor
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
wglGraphicsBuffer::
|
|
wglGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
|
|
const string &name,
|
|
const FrameBufferProperties &fb_prop,
|
|
const WindowProperties &win_prop,
|
|
int flags,
|
|
GraphicsStateGuardian *gsg,
|
|
GraphicsOutput *host) :
|
|
GraphicsBuffer(engine, pipe, name, fb_prop, win_prop, flags, gsg, host)
|
|
{
|
|
_pbuffer = (HPBUFFERARB)0;
|
|
_pbuffer_dc = (HDC)0;
|
|
release_pbuffer();
|
|
|
|
// Since the pbuffer never gets flipped, we get screenshots from the
|
|
// same buffer we draw into.
|
|
_screenshot_buffer_type = _draw_buffer_type;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::Destructor
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
wglGraphicsBuffer::
|
|
~wglGraphicsBuffer() {
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::begin_frame
|
|
// Access: Public, Virtual
|
|
// Description: 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 wglGraphicsBuffer::
|
|
begin_frame(FrameMode mode, Thread *current_thread) {
|
|
|
|
begin_frame_spam(mode);
|
|
if (_gsg == (GraphicsStateGuardian *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
wglGraphicsStateGuardian *wglgsg;
|
|
DCAST_INTO_R(wglgsg, _gsg, false);
|
|
|
|
if (_fb_properties.is_single_buffered()) {
|
|
wglgsg->_wglReleaseTexImageARB(_pbuffer, WGL_FRONT_LEFT_ARB);
|
|
} else {
|
|
wglgsg->_wglReleaseTexImageARB(_pbuffer, WGL_BACK_LEFT_ARB);
|
|
}
|
|
|
|
if (!rebuild_bitplanes()) {
|
|
wglGraphicsPipe::wgl_make_current(0, 0, &_make_current_pcollector);
|
|
return false;
|
|
}
|
|
|
|
wglGraphicsPipe::wgl_make_current(_pbuffer_dc, wglgsg->get_context(_pbuffer_dc),
|
|
&_make_current_pcollector);
|
|
|
|
if (mode == FM_render) {
|
|
CDLockedReader cdata(_cycler);
|
|
for (size_t i = 0; i != cdata->_textures.size(); ++i) {
|
|
const RenderTexture &rt = cdata->_textures[i];
|
|
RenderTextureMode rtm_mode = rt._rtm_mode;
|
|
RenderTexturePlane plane = rt._plane;
|
|
if (rtm_mode == RTM_bind_or_copy && plane != RTP_color) {
|
|
CDWriter cdataw(_cycler, cdata, false);
|
|
nassertr(cdata->_textures.size() == cdataw->_textures.size(), false);
|
|
cdataw->_textures[i]._rtm_mode = RTM_copy_texture;
|
|
}
|
|
}
|
|
clear_cube_map_selection();
|
|
}
|
|
|
|
_gsg->set_current_properties(&get_fb_properties());
|
|
return _gsg->begin_frame(current_thread);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::end_frame
|
|
// Access: Public, Virtual
|
|
// Description: 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 wglGraphicsBuffer::
|
|
end_frame(FrameMode mode, Thread *current_thread) {
|
|
end_frame_spam(mode);
|
|
nassertv(_gsg != (GraphicsStateGuardian *)NULL);
|
|
|
|
if (mode == FM_render) {
|
|
copy_to_textures();
|
|
bind_texture_to_pbuffer();
|
|
}
|
|
|
|
_gsg->end_frame(current_thread);
|
|
|
|
if (mode == FM_render) {
|
|
trigger_flip();
|
|
clear_cube_map_selection();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: GraphicsOutput::bind_texture_to_pbuffer
|
|
// Access: Private
|
|
// Description: Looks for the appropriate texture,
|
|
// and binds that texture to the pbuffer.
|
|
////////////////////////////////////////////////////////////////////
|
|
void wglGraphicsBuffer::
|
|
bind_texture_to_pbuffer() {
|
|
wglGraphicsStateGuardian *wglgsg;
|
|
DCAST_INTO_V(wglgsg, _gsg);
|
|
|
|
// Find the color texture, if there is one. That one can be bound to
|
|
// the framebuffer. All others must be marked RTM_copy_to_texture.
|
|
|
|
int tex_index = -1;
|
|
CDLockedReader cdata(_cycler);
|
|
for (size_t i = 0; i != cdata->_textures.size(); ++i) {
|
|
const RenderTexture &rt = cdata->_textures[i];
|
|
RenderTexturePlane plane = rt._plane;
|
|
if (plane == RTP_color) {
|
|
tex_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tex_index >= 0) {
|
|
const RenderTexture &rt = cdata->_textures[tex_index];
|
|
Texture *tex = rt._texture;
|
|
if ((_pbuffer_bound != 0)&&(_pbuffer_bound != tex)) {
|
|
_pbuffer_bound->release(wglgsg->get_prepared_objects());
|
|
_pbuffer_bound = 0;
|
|
}
|
|
tex->set_size_padded(_x_size, _y_size);
|
|
if (tex->get_match_framebuffer_format()) {
|
|
if (_fb_properties.get_alpha_bits()) {
|
|
tex->set_format(Texture::F_rgba);
|
|
} else {
|
|
tex->set_format(Texture::F_rgb);
|
|
}
|
|
}
|
|
TextureContext *tc = tex->prepare_now(0, _gsg->get_prepared_objects(), _gsg);
|
|
nassertv(tc != (TextureContext *)NULL);
|
|
CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
|
|
GLenum target = wglgsg->get_texture_target(tex->get_texture_type());
|
|
if (target == GL_NONE) {
|
|
CDWriter cdataw(_cycler, cdata, false);
|
|
nassertv(cdata->_textures.size() == cdataw->_textures.size());
|
|
cdataw->_textures[tex_index]._rtm_mode = RTM_copy_texture;
|
|
return;
|
|
}
|
|
GLP(BindTexture)(target, gtc->_index);
|
|
if (_fb_properties.is_single_buffered()) {
|
|
wglgsg->_wglBindTexImageARB(_pbuffer, WGL_FRONT_LEFT_ARB);
|
|
} else {
|
|
wglgsg->_wglBindTexImageARB(_pbuffer, WGL_BACK_LEFT_ARB);
|
|
}
|
|
_pbuffer_bound = tex;
|
|
} else {
|
|
if (_pbuffer_bound != 0) {
|
|
_pbuffer_bound->release(wglgsg->get_prepared_objects());
|
|
_pbuffer_bound = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::select_cube_map
|
|
// Access: Public, Virtual
|
|
// Description: 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. This should
|
|
// do whatever needs to be done to switch the buffer to
|
|
// the indicated face.
|
|
////////////////////////////////////////////////////////////////////
|
|
void wglGraphicsBuffer::
|
|
select_cube_map(int cube_map_index) {
|
|
wglGraphicsStateGuardian *wglgsg;
|
|
DCAST_INTO_V(wglgsg, _gsg);
|
|
|
|
nassertv(wglgsg->_wglSetPbufferAttribARB != NULL);
|
|
|
|
static const int max_attrib_list = 64;
|
|
int iattrib_list[max_attrib_list];
|
|
int ni = 0;
|
|
|
|
iattrib_list[ni++] = WGL_CUBE_MAP_FACE_ARB;
|
|
iattrib_list[ni++] = WGL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + cube_map_index;
|
|
|
|
// Terminate the list.
|
|
nassertv(ni <= max_attrib_list);
|
|
iattrib_list[ni] = 0;
|
|
|
|
wglgsg->_wglSetPbufferAttribARB(_pbuffer, iattrib_list);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::process_events
|
|
// Access: Public, Virtual
|
|
// Description: Do whatever processing is necessary to ensure that
|
|
// the window responds to user events. Also, honor any
|
|
// requests recently made via request_properties()
|
|
//
|
|
// This function is called only within the window
|
|
// thread.
|
|
////////////////////////////////////////////////////////////////////
|
|
void wglGraphicsBuffer::
|
|
process_events() {
|
|
GraphicsBuffer::process_events();
|
|
|
|
MSG msg;
|
|
|
|
// Handle all the messages on the queue in a row. Some of these
|
|
// might be for another window, but they will get dispatched
|
|
// appropriately.
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
|
|
process_1_event();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::get_supports_render_texture
|
|
// Access: Published, Virtual
|
|
// Description: 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 wglGraphicsBuffer::
|
|
get_supports_render_texture() const {
|
|
if (_gsg == (GraphicsStateGuardian *)NULL) {
|
|
return false;
|
|
}
|
|
|
|
wglGraphicsStateGuardian *wglgsg;
|
|
DCAST_INTO_R(wglgsg, _gsg, false);
|
|
return wglgsg->get_supports_wgl_render_texture();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::close_buffer
|
|
// Access: Protected, Virtual
|
|
// Description: Closes the buffer right now. Called from the window
|
|
// thread.
|
|
////////////////////////////////////////////////////////////////////
|
|
void wglGraphicsBuffer::
|
|
close_buffer() {
|
|
if (_gsg != (GraphicsStateGuardian *)NULL) {
|
|
wglGraphicsStateGuardian *wglgsg;
|
|
DCAST_INTO_V(wglgsg, _gsg);
|
|
|
|
_gsg.clear();
|
|
}
|
|
|
|
release_pbuffer();
|
|
|
|
_is_valid = false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::open_buffer
|
|
// Access: Protected, Virtual
|
|
// Description: Opens the window right now. Called from the window
|
|
// thread. Returns true if the window is successfully
|
|
// opened, or false if there was a problem.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool wglGraphicsBuffer::
|
|
open_buffer() {
|
|
|
|
// pbuffers don't seem to work correctly in double-buffered
|
|
// mode. Besides, the back buffer is a pointless waste of space.
|
|
// So always use a single-buffered gsg.
|
|
|
|
_fb_properties.set_back_buffers(0);
|
|
_draw_buffer_type = RenderBuffer::T_front;
|
|
_screenshot_buffer_type = RenderBuffer::T_front;
|
|
|
|
// GSG creation/initialization.
|
|
|
|
wglGraphicsStateGuardian *wglgsg;
|
|
if (_gsg == 0) {
|
|
// There is no old gsg. Create a new one.
|
|
wglgsg = new wglGraphicsStateGuardian(_engine, _pipe, NULL);
|
|
wglgsg->choose_pixel_format(_fb_properties, true);
|
|
_gsg = wglgsg;
|
|
} else {
|
|
// If the old gsg has the wrong pixel format, create a
|
|
// new one that shares with the old gsg.
|
|
DCAST_INTO_R(wglgsg, _gsg, false);
|
|
if ((!wglgsg->get_fb_properties().subsumes(_fb_properties))||
|
|
(!wglgsg->get_fb_properties().is_single_buffered())||
|
|
(!wglgsg->pfnum_supports_pbuffer())) {
|
|
wglgsg = new wglGraphicsStateGuardian(_engine, _pipe, wglgsg);
|
|
wglgsg->choose_pixel_format(_fb_properties, true);
|
|
_gsg = wglgsg;
|
|
}
|
|
}
|
|
|
|
// Use the temp window to initialize the gsg.
|
|
|
|
HDC twindow_dc = wglgsg->get_twindow_dc();
|
|
if (twindow_dc == 0) {
|
|
// If we couldn't make a window, we can't get a GL context.
|
|
return false;
|
|
}
|
|
wglGraphicsPipe::wgl_make_current(twindow_dc, wglgsg->get_context(twindow_dc),
|
|
&_make_current_pcollector);
|
|
wglgsg->reset_if_new();
|
|
wglgsg->report_my_gl_errors();
|
|
if (!wglgsg->get_fb_properties().verify_hardware_software
|
|
(_fb_properties,wglgsg->get_gl_renderer())) {
|
|
return false;
|
|
}
|
|
_fb_properties = wglgsg->get_fb_properties();
|
|
|
|
// Now that we have fully made a window and used that window to
|
|
// create a rendering context, we can attempt to create a pbuffer.
|
|
// This might fail if the pbuffer extensions are not supported.
|
|
|
|
if (!rebuild_bitplanes()) {
|
|
wglGraphicsPipe::wgl_make_current(0, 0, &_make_current_pcollector);
|
|
return false;
|
|
}
|
|
|
|
_is_valid = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::release_pbuffer
|
|
// Access: Private
|
|
// Description: Destroys the pbuffer if it has been created. The
|
|
// intent is that this may allow it to be recreated
|
|
// with different options.
|
|
////////////////////////////////////////////////////////////////////
|
|
void wglGraphicsBuffer::
|
|
release_pbuffer() {
|
|
if (_gsg == 0) {
|
|
return;
|
|
}
|
|
|
|
wglGraphicsStateGuardian *wglgsg;
|
|
DCAST_INTO_V(wglgsg, _gsg);
|
|
|
|
if (_pbuffer_bound != 0) {
|
|
_pbuffer_bound->release(wglgsg->get_prepared_objects());
|
|
_pbuffer_bound = 0;
|
|
}
|
|
wglGraphicsPipe::wgl_make_current(0, 0, NULL);
|
|
if (_pbuffer_dc) {
|
|
wglgsg->_wglReleasePbufferDCARB(_pbuffer, _pbuffer_dc);
|
|
}
|
|
if (_pbuffer) {
|
|
wglgsg->_wglDestroyPbufferARB(_pbuffer);
|
|
}
|
|
_pbuffer = (HPBUFFERARB)0;
|
|
_pbuffer_dc = (HDC)0;
|
|
_pbuffer_mipmap = false;
|
|
_pbuffer_sizex = 0;
|
|
_pbuffer_sizey = 0;
|
|
_pbuffer_type = Texture::TT_2d_texture;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::rebuild_bitplanes
|
|
// Access: Private
|
|
// Description: Once the GL context has been fully realized, attempts
|
|
// to create an offscreen pbuffer if the graphics API
|
|
// supports it. Returns true if successful, false on
|
|
// failure.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool wglGraphicsBuffer::
|
|
rebuild_bitplanes() {
|
|
wglGraphicsStateGuardian *wglgsg;
|
|
DCAST_INTO_R(wglgsg, _gsg, false);
|
|
|
|
if (!wglgsg->_supports_pbuffer) {
|
|
wgldisplay_cat.info()
|
|
<< "PBuffers not supported by GL implementation.\n";
|
|
return false;
|
|
}
|
|
|
|
// Find the texture to bind to the color buffer.
|
|
Texture *bindtexture = NULL;
|
|
for (int i=0; i<count_textures(); i++) {
|
|
if ((get_rtm_mode(i) == RTM_bind_or_copy)&&
|
|
(get_texture(i)->get_format() != Texture::F_depth_stencil)) {
|
|
bindtexture = get_texture(i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we already have a pbuffer, and if it's lost, then
|
|
// force the rebuild.
|
|
|
|
if (_pbuffer_dc) {
|
|
int flag = 0;
|
|
wglgsg->_wglQueryPbufferARB(_pbuffer, WGL_PBUFFER_LOST_ARB, &flag);
|
|
if (flag != 0) {
|
|
release_pbuffer();
|
|
}
|
|
}
|
|
|
|
// Determine what pbuffer attributes are needed
|
|
// for currently-applicable textures.
|
|
|
|
if ((_host != 0)&&(_creation_flags & GraphicsPipe::BF_size_track_host)) {
|
|
if ((_host->get_x_size() != _x_size)||
|
|
(_host->get_y_size() != _y_size)) {
|
|
set_size_and_recalc(_host->get_x_size(),
|
|
_host->get_y_size());
|
|
}
|
|
}
|
|
int desired_x = _x_size;
|
|
int desired_y = _y_size;
|
|
if ((bindtexture != 0)&&(Texture::get_textures_power_2() != ATS_none)) {
|
|
desired_x = Texture::up_to_power_2(desired_x);
|
|
desired_y = Texture::up_to_power_2(desired_y);
|
|
}
|
|
bool desired_mipmap = false;
|
|
Texture::TextureType desired_type = Texture::TT_2d_texture;
|
|
if (bindtexture != 0) {
|
|
desired_mipmap = bindtexture->uses_mipmaps();
|
|
desired_type = bindtexture->get_texture_type();
|
|
}
|
|
|
|
if ((_pbuffer != 0)&&
|
|
(_pbuffer_sizex == desired_x)&&
|
|
(_pbuffer_sizey == desired_y)&&
|
|
(_pbuffer_mipmap == desired_mipmap)&&
|
|
(_pbuffer_type == desired_type)) {
|
|
// the pbuffer we already have is fine. Do not rebuild.
|
|
return true;
|
|
}
|
|
|
|
// Release the old pbuffer, if there was one.
|
|
|
|
release_pbuffer();
|
|
|
|
// Allocate the new pbuffer.
|
|
|
|
int pfnum = wglgsg->get_pfnum();
|
|
|
|
static const int max_attrib_list = 64;
|
|
int iattrib_list[max_attrib_list];
|
|
int ni = 0;
|
|
|
|
if (_fb_properties.get_alpha_bits()) {
|
|
iattrib_list[ni++] = WGL_TEXTURE_FORMAT_ARB;
|
|
iattrib_list[ni++] = WGL_TEXTURE_RGBA_ARB;
|
|
} else {
|
|
iattrib_list[ni++] = WGL_TEXTURE_FORMAT_ARB;
|
|
iattrib_list[ni++] = WGL_TEXTURE_RGB_ARB;
|
|
}
|
|
|
|
if (desired_mipmap) {
|
|
iattrib_list[ni++] = WGL_MIPMAP_TEXTURE_ARB;
|
|
iattrib_list[ni++] = 1;
|
|
}
|
|
|
|
switch (desired_type) {
|
|
case Texture::TT_cube_map:
|
|
iattrib_list[ni++] = WGL_TEXTURE_TARGET_ARB;
|
|
iattrib_list[ni++] = WGL_TEXTURE_CUBE_MAP_ARB;
|
|
break;
|
|
|
|
case Texture::TT_1d_texture:
|
|
iattrib_list[ni++] = WGL_TEXTURE_TARGET_ARB;
|
|
iattrib_list[ni++] = WGL_TEXTURE_1D_ARB;
|
|
break;
|
|
|
|
default:
|
|
iattrib_list[ni++] = WGL_TEXTURE_TARGET_ARB;
|
|
iattrib_list[ni++] = WGL_TEXTURE_2D_ARB;
|
|
}
|
|
|
|
// Terminate the list.
|
|
nassertr(ni <= max_attrib_list, false);
|
|
iattrib_list[ni] = 0;
|
|
|
|
HDC twindow_dc = wglgsg->get_twindow_dc();
|
|
if (twindow_dc == 0) {
|
|
return false;
|
|
}
|
|
|
|
wglGraphicsPipe::wgl_make_current(twindow_dc, wglgsg->get_context(twindow_dc),
|
|
&_make_current_pcollector);
|
|
|
|
_pbuffer = wglgsg->_wglCreatePbufferARB(twindow_dc, pfnum,
|
|
desired_x, desired_y, iattrib_list);
|
|
|
|
if (_pbuffer == 0) {
|
|
wgldisplay_cat.info()
|
|
<< "Attempt to create pbuffer failed.\n";
|
|
return false;
|
|
}
|
|
|
|
_pbuffer_dc = wglgsg->_wglGetPbufferDCARB(_pbuffer);
|
|
_pbuffer_mipmap = desired_mipmap;
|
|
_pbuffer_type = desired_type;
|
|
_pbuffer_sizex = desired_x;
|
|
_pbuffer_sizey = desired_y;
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: wglGraphicsBuffer::process_1_event
|
|
// Access: Private, Static
|
|
// Description: Handles one event from the message queue.
|
|
////////////////////////////////////////////////////////////////////
|
|
void wglGraphicsBuffer::
|
|
process_1_event() {
|
|
MSG msg;
|
|
|
|
if (!GetMessage(&msg, NULL, 0, 0)) {
|
|
// WM_QUIT received. We need a cleaner way to deal with this.
|
|
// DestroyAllWindows(false);
|
|
exit(msg.wParam); // this will invoke AtExitFn
|
|
}
|
|
|
|
// Translate virtual key messages
|
|
TranslateMessage(&msg);
|
|
// Call window_proc
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
|