mirror of
https://github.com/panda3d/panda3d.git
synced 2025-09-28 07:48:37 -04:00
display: Add support for asynchronous screenshot download
This commit is contained in:
parent
d5fed54a0c
commit
cfd18bb16f
@ -2713,7 +2713,7 @@ class ShowBase(DirectObject.DirectObject):
|
|||||||
|
|
||||||
def screenshot(self, namePrefix = 'screenshot',
|
def screenshot(self, namePrefix = 'screenshot',
|
||||||
defaultFilename = 1, source = None,
|
defaultFilename = 1, source = None,
|
||||||
imageComment=""):
|
imageComment="", blocking=True):
|
||||||
""" Captures a screenshot from the main window or from the
|
""" Captures a screenshot from the main window or from the
|
||||||
specified window or Texture and writes it to a filename in the
|
specified window or Texture and writes it to a filename in the
|
||||||
current directory (or to a specified directory).
|
current directory (or to a specified directory).
|
||||||
@ -2735,6 +2735,13 @@ class ShowBase(DirectObject.DirectObject):
|
|||||||
generated by makeCubeMap(), namePrefix should contain the hash
|
generated by makeCubeMap(), namePrefix should contain the hash
|
||||||
mark ('#') character.
|
mark ('#') character.
|
||||||
|
|
||||||
|
Normally, this call will block until the screenshot is fully
|
||||||
|
written. To write the screenshot in a background thread
|
||||||
|
instead, pass blocking = False. In this case, the return value
|
||||||
|
is a future that can be awaited.
|
||||||
|
|
||||||
|
A "screenshot" event will be sent once the screenshot is saved.
|
||||||
|
|
||||||
:returns: The filename if successful, or None if there is a problem.
|
:returns: The filename if successful, or None if there is a problem.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -2751,8 +2758,12 @@ class ShowBase(DirectObject.DirectObject):
|
|||||||
saved = source.write(filename, 0, 0, 1, 0)
|
saved = source.write(filename, 0, 0, 1, 0)
|
||||||
else:
|
else:
|
||||||
saved = source.write(filename)
|
saved = source.write(filename)
|
||||||
else:
|
elif blocking:
|
||||||
saved = source.saveScreenshot(filename, imageComment)
|
saved = source.saveScreenshot(filename, imageComment)
|
||||||
|
else:
|
||||||
|
request = source.saveAsyncScreenshot(filename, imageComment)
|
||||||
|
request.addDoneCallback(lambda fut, filename=filename: messenger.send('screenshot', [filename]))
|
||||||
|
return request
|
||||||
|
|
||||||
if saved:
|
if saved:
|
||||||
# Announce to anybody that a screenshot has been taken
|
# Announce to anybody that a screenshot has been taken
|
||||||
|
@ -27,6 +27,7 @@ set(P3DISPLAY_HEADERS
|
|||||||
windowHandle.I windowHandle.h
|
windowHandle.I windowHandle.h
|
||||||
windowProperties.I windowProperties.h
|
windowProperties.I windowProperties.h
|
||||||
renderBuffer.h
|
renderBuffer.h
|
||||||
|
screenshotRequest.I screenshotRequest.h
|
||||||
stereoDisplayRegion.I stereoDisplayRegion.h
|
stereoDisplayRegion.I stereoDisplayRegion.h
|
||||||
displaySearchParameters.h
|
displaySearchParameters.h
|
||||||
displayInformation.h
|
displayInformation.h
|
||||||
@ -61,6 +62,7 @@ set(P3DISPLAY_SOURCES
|
|||||||
parasiteBuffer.cxx
|
parasiteBuffer.cxx
|
||||||
windowHandle.cxx
|
windowHandle.cxx
|
||||||
windowProperties.cxx
|
windowProperties.cxx
|
||||||
|
screenshotRequest.cxx
|
||||||
stereoDisplayRegion.cxx
|
stereoDisplayRegion.cxx
|
||||||
subprocessWindow.cxx
|
subprocessWindow.cxx
|
||||||
touchInfo.cxx
|
touchInfo.cxx
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include "nativeWindowHandle.h"
|
#include "nativeWindowHandle.h"
|
||||||
#include "parasiteBuffer.h"
|
#include "parasiteBuffer.h"
|
||||||
#include "pandaSystem.h"
|
#include "pandaSystem.h"
|
||||||
|
#include "screenshotRequest.h"
|
||||||
#include "stereoDisplayRegion.h"
|
#include "stereoDisplayRegion.h"
|
||||||
#include "subprocessWindow.h"
|
#include "subprocessWindow.h"
|
||||||
#include "windowHandle.h"
|
#include "windowHandle.h"
|
||||||
@ -534,6 +535,7 @@ init_libdisplay() {
|
|||||||
MouseAndKeyboard::init_type();
|
MouseAndKeyboard::init_type();
|
||||||
NativeWindowHandle::init_type();
|
NativeWindowHandle::init_type();
|
||||||
ParasiteBuffer::init_type();
|
ParasiteBuffer::init_type();
|
||||||
|
ScreenshotRequest::init_type();
|
||||||
StandardMunger::init_type();
|
StandardMunger::init_type();
|
||||||
StereoDisplayRegion::init_type();
|
StereoDisplayRegion::init_type();
|
||||||
#ifdef SUPPORT_SUBPROCESS_WINDOW
|
#ifdef SUPPORT_SUBPROCESS_WINDOW
|
||||||
|
@ -1419,6 +1419,8 @@ cull_and_draw_together(GraphicsEngine::Windows wlist,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (win->begin_frame(GraphicsOutput::FM_render, current_thread)) {
|
if (win->begin_frame(GraphicsOutput::FM_render, current_thread)) {
|
||||||
|
win->copy_async_screenshot();
|
||||||
|
|
||||||
if (win->is_any_clear_active()) {
|
if (win->is_any_clear_active()) {
|
||||||
GraphicsStateGuardian *gsg = win->get_gsg();
|
GraphicsStateGuardian *gsg = win->get_gsg();
|
||||||
PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
|
PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
|
||||||
@ -1720,6 +1722,9 @@ draw_bins(const GraphicsEngine::Windows &wlist, Thread *current_thread) {
|
|||||||
// a current context for PStatGPUTimer to work.
|
// a current context for PStatGPUTimer to work.
|
||||||
{
|
{
|
||||||
PStatGPUTimer timer(gsg, win->get_draw_window_pcollector(), current_thread);
|
PStatGPUTimer timer(gsg, win->get_draw_window_pcollector(), current_thread);
|
||||||
|
|
||||||
|
win->copy_async_screenshot();
|
||||||
|
|
||||||
if (win->is_any_clear_active()) {
|
if (win->is_any_clear_active()) {
|
||||||
PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
|
PStatGPUTimer timer(gsg, win->get_clear_window_pcollector(), current_thread);
|
||||||
win->get_gsg()->push_group_marker("Clear");
|
win->get_gsg()->push_group_marker("Clear");
|
||||||
|
@ -976,6 +976,43 @@ make_cube_map(const string &name, int size, NodePath &camera_rig,
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like save_screenshot, but performs both the texture transfer and the saving
|
||||||
|
* to disk in the background. Returns a future that can be awaited.
|
||||||
|
*
|
||||||
|
* This captures the frame that was last submitted by the App stage to the
|
||||||
|
* render_frame() call. This may not be the latest frame shown on the screen
|
||||||
|
* if the multi-threaded pipeline is used, in which case the request may take
|
||||||
|
* several frames extra to complete.
|
||||||
|
*/
|
||||||
|
PT(ScreenshotRequest) GraphicsOutput::
|
||||||
|
save_async_screenshot(const Filename &filename, const std::string &image_comment) {
|
||||||
|
PT(ScreenshotRequest) request = get_async_screenshot();
|
||||||
|
request->add_output_file(filename, image_comment);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to obtain a new Texture object containing the previously rendered frame.
|
||||||
|
* Unlike get_screenshot, this works asynchronously, meaning that the contents
|
||||||
|
* are transferred in the background. Returns a future that can be awaited.
|
||||||
|
*
|
||||||
|
* This captures the frame that was last submitted by the App stage to the
|
||||||
|
* render_frame() call. This may not be the latest frame shown on the screen
|
||||||
|
* if the multi-threaded pipeline is used, in which case the request may take
|
||||||
|
* several frames extra to complete.
|
||||||
|
*/
|
||||||
|
PT(ScreenshotRequest) GraphicsOutput::
|
||||||
|
get_async_screenshot() {
|
||||||
|
Thread *current_thread = Thread::get_current_thread();
|
||||||
|
CDWriter cdata(_cycler, current_thread);
|
||||||
|
if (cdata->_screenshot_request == nullptr) {
|
||||||
|
PT(Texture) texture = new Texture("screenshot of " + get_name());
|
||||||
|
cdata->_screenshot_request = new ScreenshotRequest(texture);
|
||||||
|
}
|
||||||
|
return cdata->_screenshot_request;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a PandaNode containing a square polygon. The dimensions are
|
* 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
|
* (-1,0,-1) to (1,0,1). The texture coordinates are such that the texture of
|
||||||
@ -1468,6 +1505,56 @@ copy_to_textures() {
|
|||||||
return okflag;
|
return okflag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do the necessary copies for the get_async_screenshot request.
|
||||||
|
*/
|
||||||
|
void GraphicsOutput::
|
||||||
|
copy_async_screenshot() {
|
||||||
|
Thread *current_thread = Thread::get_current_thread();
|
||||||
|
PT(ScreenshotRequest) request;
|
||||||
|
{
|
||||||
|
CDWriter cdata(_cycler, current_thread);
|
||||||
|
if (cdata->_screenshot_request == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
request = std::move(cdata->_screenshot_request);
|
||||||
|
cdata->_screenshot_request.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure it is cleared from upstream stages as well.
|
||||||
|
OPEN_ITERATE_UPSTREAM_ONLY(_cycler, current_thread) {
|
||||||
|
CDStageWriter cdata(_cycler, pipeline_stage, current_thread);
|
||||||
|
if (cdata->_screenshot_request == request) {
|
||||||
|
cdata->_screenshot_request.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CLOSE_ITERATE_UPSTREAM_ONLY(_cycler);
|
||||||
|
|
||||||
|
PStatTimer timer(_copy_texture_pcollector);
|
||||||
|
|
||||||
|
RenderBuffer buffer = _gsg->get_render_buffer(get_draw_buffer_type(),
|
||||||
|
get_fb_properties());
|
||||||
|
DisplayRegion *dr = _overlay_display_region;
|
||||||
|
|
||||||
|
Texture *texture = request->get_result();
|
||||||
|
|
||||||
|
if (_fb_properties.is_stereo()) {
|
||||||
|
// We've got two texture views to copy.
|
||||||
|
texture->set_num_views(2);
|
||||||
|
|
||||||
|
RenderBuffer left(_gsg, buffer._buffer_type & ~RenderBuffer::T_right);
|
||||||
|
RenderBuffer right(_gsg, buffer._buffer_type & ~RenderBuffer::T_left);
|
||||||
|
|
||||||
|
_gsg->framebuffer_copy_to_ram(texture, 0, _target_tex_page,
|
||||||
|
dr, left, request);
|
||||||
|
_gsg->framebuffer_copy_to_ram(texture, 1, _target_tex_page,
|
||||||
|
dr, right, request);
|
||||||
|
} else {
|
||||||
|
_gsg->framebuffer_copy_to_ram(texture, 0, _target_tex_page,
|
||||||
|
dr, buffer, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a GeomVertexData for a texture card.
|
* Generates a GeomVertexData for a texture card.
|
||||||
*/
|
*/
|
||||||
@ -1653,7 +1740,8 @@ CData(const GraphicsOutput::CData ©) :
|
|||||||
_active(copy._active),
|
_active(copy._active),
|
||||||
_one_shot_frame(copy._one_shot_frame),
|
_one_shot_frame(copy._one_shot_frame),
|
||||||
_active_display_regions(copy._active_display_regions),
|
_active_display_regions(copy._active_display_regions),
|
||||||
_active_display_regions_stale(copy._active_display_regions_stale)
|
_active_display_regions_stale(copy._active_display_regions_stale),
|
||||||
|
_screenshot_request(copy._screenshot_request)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
#include "pipelineCycler.h"
|
#include "pipelineCycler.h"
|
||||||
#include "updateSeq.h"
|
#include "updateSeq.h"
|
||||||
#include "asyncFuture.h"
|
#include "asyncFuture.h"
|
||||||
|
#include "screenshotRequest.h"
|
||||||
|
|
||||||
class PNMImage;
|
class PNMImage;
|
||||||
class GraphicsEngine;
|
class GraphicsEngine;
|
||||||
@ -239,6 +240,9 @@ PUBLISHED:
|
|||||||
const Filename &filename, const std::string &image_comment = "");
|
const Filename &filename, const std::string &image_comment = "");
|
||||||
INLINE bool get_screenshot(PNMImage &image);
|
INLINE bool get_screenshot(PNMImage &image);
|
||||||
INLINE PT(Texture) get_screenshot();
|
INLINE PT(Texture) get_screenshot();
|
||||||
|
PT(ScreenshotRequest) save_async_screenshot(const Filename &filename,
|
||||||
|
const std::string &image_comment = "");
|
||||||
|
PT(ScreenshotRequest) get_async_screenshot();
|
||||||
|
|
||||||
NodePath get_texture_card();
|
NodePath get_texture_card();
|
||||||
|
|
||||||
@ -298,6 +302,7 @@ protected:
|
|||||||
void prepare_for_deletion();
|
void prepare_for_deletion();
|
||||||
void promote_to_copy_texture();
|
void promote_to_copy_texture();
|
||||||
bool copy_to_textures();
|
bool copy_to_textures();
|
||||||
|
void copy_async_screenshot();
|
||||||
|
|
||||||
INLINE void begin_frame_spam(FrameMode mode);
|
INLINE void begin_frame_spam(FrameMode mode);
|
||||||
INLINE void end_frame_spam(FrameMode mode);
|
INLINE void end_frame_spam(FrameMode mode);
|
||||||
@ -392,6 +397,8 @@ protected:
|
|||||||
int _one_shot_frame;
|
int _one_shot_frame;
|
||||||
ActiveDisplayRegions _active_display_regions;
|
ActiveDisplayRegions _active_display_regions;
|
||||||
bool _active_display_regions_stale;
|
bool _active_display_regions_stale;
|
||||||
|
|
||||||
|
PT(ScreenshotRequest) _screenshot_request;
|
||||||
};
|
};
|
||||||
PipelineCycler<CData> _cycler;
|
PipelineCycler<CData> _cycler;
|
||||||
typedef CycleDataLockedReader<CData> CDLockedReader;
|
typedef CycleDataLockedReader<CData> CDLockedReader;
|
||||||
|
@ -3037,11 +3037,15 @@ framebuffer_copy_to_texture(Texture *, int, int, const DisplayRegion *,
|
|||||||
* into system memory, not texture memory. Returns true on success, false on
|
* into system memory, not texture memory. Returns true on success, false on
|
||||||
* failure.
|
* failure.
|
||||||
*
|
*
|
||||||
|
* If a future is given, the operation may be scheduled to occur in the
|
||||||
|
* background, in which case the texture will be passed as the result of the
|
||||||
|
* future when the operation is complete.
|
||||||
|
*
|
||||||
* This completely redefines the ram image of the indicated texture.
|
* This completely redefines the ram image of the indicated texture.
|
||||||
*/
|
*/
|
||||||
bool GraphicsStateGuardian::
|
bool GraphicsStateGuardian::
|
||||||
framebuffer_copy_to_ram(Texture *, int, int, const DisplayRegion *,
|
framebuffer_copy_to_ram(Texture *, int, int, const DisplayRegion *,
|
||||||
const RenderBuffer &) {
|
const RenderBuffer &, ScreenshotRequest *) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,7 +426,8 @@ public:
|
|||||||
virtual bool framebuffer_copy_to_texture
|
virtual bool framebuffer_copy_to_texture
|
||||||
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
|
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
|
||||||
virtual bool framebuffer_copy_to_ram
|
virtual bool framebuffer_copy_to_ram
|
||||||
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
|
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb,
|
||||||
|
ScreenshotRequest *request = nullptr);
|
||||||
|
|
||||||
virtual void bind_light(PointLight *light_obj, const NodePath &light,
|
virtual void bind_light(PointLight *light_obj, const NodePath &light,
|
||||||
int light_id);
|
int light_id);
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "parasiteBuffer.cxx"
|
#include "parasiteBuffer.cxx"
|
||||||
#include "standardMunger.cxx"
|
#include "standardMunger.cxx"
|
||||||
#include "touchInfo.cxx"
|
#include "touchInfo.cxx"
|
||||||
|
#include "screenshotRequest.cxx"
|
||||||
#include "stereoDisplayRegion.cxx"
|
#include "stereoDisplayRegion.cxx"
|
||||||
#include "subprocessWindow.cxx"
|
#include "subprocessWindow.cxx"
|
||||||
#ifdef IS_OSX
|
#ifdef IS_OSX
|
||||||
|
39
panda/src/display/screenshotRequest.I
Normal file
39
panda/src/display/screenshotRequest.I
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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 screenshotRequest.I
|
||||||
|
* @author rdb
|
||||||
|
* @date 2022-12-26
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
INLINE ScreenshotRequest::
|
||||||
|
ScreenshotRequest(Texture *tex) :
|
||||||
|
_frame_number(ClockObject::get_global_clock()->get_frame_count()) {
|
||||||
|
_result = tex;
|
||||||
|
_result_ref = tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the frame number in which the request originated.
|
||||||
|
*/
|
||||||
|
INLINE int ScreenshotRequest::
|
||||||
|
get_frame_number() const {
|
||||||
|
return _frame_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the resulting texture. Can always be called.
|
||||||
|
*/
|
||||||
|
INLINE Texture *ScreenshotRequest::
|
||||||
|
get_result() const {
|
||||||
|
return (Texture *)_result;
|
||||||
|
}
|
104
panda/src/display/screenshotRequest.cxx
Normal file
104
panda/src/display/screenshotRequest.cxx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* 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 screenshotRequest.cxx
|
||||||
|
* @author rdb
|
||||||
|
* @date 2022-12-26
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "screenshotRequest.h"
|
||||||
|
#include "lightMutexHolder.h"
|
||||||
|
#include "pnmImage.h"
|
||||||
|
#include "texture.h"
|
||||||
|
|
||||||
|
TypeHandle ScreenshotRequest::_type_handle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void ScreenshotRequest::
|
||||||
|
set_view_data(int view, const void *ptr) {
|
||||||
|
const int z = 0;
|
||||||
|
|
||||||
|
Texture *tex = get_result();
|
||||||
|
PTA_uchar new_image = tex->modify_ram_image();
|
||||||
|
unsigned char *image_ptr = new_image.p();
|
||||||
|
size_t image_size = tex->get_ram_image_size();
|
||||||
|
if (z >= 0 || view > 0) {
|
||||||
|
image_size = tex->get_expected_ram_page_size();
|
||||||
|
if (z >= 0) {
|
||||||
|
image_ptr += z * image_size;
|
||||||
|
}
|
||||||
|
if (view > 0) {
|
||||||
|
image_ptr += (view * tex->get_z_size()) * image_size;
|
||||||
|
nassertd(view < tex->get_num_views()) {
|
||||||
|
if (set_future_state(FS_cancelled)) {
|
||||||
|
notify_done(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memcpy(image_ptr, ptr, image_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void ScreenshotRequest::
|
||||||
|
finish() {
|
||||||
|
Texture *tex = get_result();
|
||||||
|
|
||||||
|
++_got_num_views;
|
||||||
|
if (_got_num_views < tex->get_num_views()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
LightMutexHolder holder(_lock);
|
||||||
|
if (!_output_files.empty()) {
|
||||||
|
PNMImage image;
|
||||||
|
tex->store(image);
|
||||||
|
|
||||||
|
for (const auto &item : _output_files) {
|
||||||
|
image.set_comment(item.second);
|
||||||
|
image.write(item.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncFuture::set_result(tex);
|
||||||
|
_output_files.clear();
|
||||||
|
|
||||||
|
if (!set_future_state(FS_finished)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_done(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a filename to write the screenshot to when it is available. If the
|
||||||
|
* request is already done, performs the write synchronously.
|
||||||
|
*/
|
||||||
|
void ScreenshotRequest::
|
||||||
|
add_output_file(const Filename &filename, const std::string &image_comment) {
|
||||||
|
if (!done()) {
|
||||||
|
LightMutexHolder holder(_lock);
|
||||||
|
if (!done()) {
|
||||||
|
_output_files[filename] = image_comment;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Was already done, write it right away.
|
||||||
|
Texture *tex = get_result();
|
||||||
|
PNMImage image;
|
||||||
|
tex->store(image);
|
||||||
|
image.set_comment(image_comment);
|
||||||
|
image.write(filename);
|
||||||
|
}
|
71
panda/src/display/screenshotRequest.h
Normal file
71
panda/src/display/screenshotRequest.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* 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 screenshotRequest.h
|
||||||
|
* @author rdb
|
||||||
|
* @date 2022-12-26
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SCREENSHOTREQUEST_H
|
||||||
|
#define SCREENSHOTREQUEST_H
|
||||||
|
|
||||||
|
#include "pandabase.h"
|
||||||
|
|
||||||
|
#include "asyncFuture.h"
|
||||||
|
#include "filename.h"
|
||||||
|
#include "lightMutex.h"
|
||||||
|
#include "pmap.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class representing an asynchronous request to save a screenshot.
|
||||||
|
*/
|
||||||
|
class EXPCL_PANDA_PGRAPH ScreenshotRequest : public AsyncFuture {
|
||||||
|
public:
|
||||||
|
INLINE ScreenshotRequest(Texture *tex);
|
||||||
|
|
||||||
|
INLINE int get_frame_number() const;
|
||||||
|
INLINE Texture *get_result() const;
|
||||||
|
|
||||||
|
void set_view_data(int view, const void *ptr);
|
||||||
|
void finish();
|
||||||
|
|
||||||
|
PUBLISHED:
|
||||||
|
void add_output_file(const Filename &filename,
|
||||||
|
const std::string &image_comment = "");
|
||||||
|
|
||||||
|
private:
|
||||||
|
// It's possible to call save_screenshot multiple times in the same frame, so
|
||||||
|
// rather than have to store a vector of request objects, we just allow
|
||||||
|
// storing multiple filenames to handle this corner case.
|
||||||
|
LightMutex _lock;
|
||||||
|
pmap<Filename, std::string> _output_files;
|
||||||
|
int _got_num_views = 0;
|
||||||
|
|
||||||
|
int _frame_number = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static TypeHandle get_class_type() {
|
||||||
|
return _type_handle;
|
||||||
|
}
|
||||||
|
static void init_type() {
|
||||||
|
AsyncFuture::init_type();
|
||||||
|
register_type(_type_handle, "ScreenshotRequest",
|
||||||
|
AsyncFuture::get_class_type());
|
||||||
|
}
|
||||||
|
virtual TypeHandle get_type() const {
|
||||||
|
return get_class_type();
|
||||||
|
}
|
||||||
|
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static TypeHandle _type_handle;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "screenshotRequest.I"
|
||||||
|
|
||||||
|
#endif
|
@ -1988,8 +1988,17 @@ framebuffer_copy_to_texture(Texture *tex, int view, int z,
|
|||||||
*/
|
*/
|
||||||
bool DXGraphicsStateGuardian9::
|
bool DXGraphicsStateGuardian9::
|
||||||
framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
||||||
const DisplayRegion *dr, const RenderBuffer &rb) {
|
const DisplayRegion *dr, const RenderBuffer &rb,
|
||||||
return do_framebuffer_copy_to_ram(tex, view, z, dr, rb, false);
|
ScreenshotRequest *request) {
|
||||||
|
bool success = do_framebuffer_copy_to_ram(tex, view, z, dr, rb, false);
|
||||||
|
if (request != nullptr) {
|
||||||
|
if (success) {
|
||||||
|
request->finish();
|
||||||
|
} else {
|
||||||
|
request->cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -127,7 +127,8 @@ public:
|
|||||||
const RenderBuffer &rb);
|
const RenderBuffer &rb);
|
||||||
virtual bool framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
virtual bool framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
||||||
const DisplayRegion *dr,
|
const DisplayRegion *dr,
|
||||||
const RenderBuffer &rb);
|
const RenderBuffer &rb,
|
||||||
|
ScreenshotRequest *request);
|
||||||
bool do_framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
bool do_framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
||||||
const DisplayRegion *dr,
|
const DisplayRegion *dr,
|
||||||
const RenderBuffer &rb,
|
const RenderBuffer &rb,
|
||||||
|
@ -177,6 +177,7 @@ typedef char GLchar;
|
|||||||
#define GL_READ_ONLY 0x88B8
|
#define GL_READ_ONLY 0x88B8
|
||||||
#define GL_WRITE_ONLY 0x88B9
|
#define GL_WRITE_ONLY 0x88B9
|
||||||
#define GL_READ_WRITE 0x88BA
|
#define GL_READ_WRITE 0x88BA
|
||||||
|
#define GL_PIXEL_PACK_BUFFER 0x88EB
|
||||||
#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF
|
#define GL_MAX_ARRAY_TEXTURE_LAYERS 0x88FF
|
||||||
#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35
|
#define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35
|
||||||
#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36
|
#define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36
|
||||||
@ -253,6 +254,12 @@ typedef char GLchar;
|
|||||||
#define GL_UNSIGNED_INT_IMAGE_3D 0x9064
|
#define GL_UNSIGNED_INT_IMAGE_3D 0x9064
|
||||||
#define GL_UNSIGNED_INT_IMAGE_CUBE 0x9066
|
#define GL_UNSIGNED_INT_IMAGE_CUBE 0x9066
|
||||||
#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY 0x9069
|
#define GL_UNSIGNED_INT_IMAGE_2D_ARRAY 0x9069
|
||||||
|
#define GL_SYNC_GPU_COMMANDS_COMPLETE 0x9117
|
||||||
|
#define GL_UNSIGNALED 0x9118
|
||||||
|
#define GL_SIGNALED 0x9119
|
||||||
|
#define GL_ALREADY_SIGNALED 0x911A
|
||||||
|
#define GL_TIMEOUT_EXPIRED 0x911B
|
||||||
|
#define GL_CONDITION_SATISFIED 0x911C
|
||||||
#define GL_COMPUTE_SHADER 0x91B9
|
#define GL_COMPUTE_SHADER 0x91B9
|
||||||
#define GL_FRAMEBUFFER_DEFAULT_WIDTH 0x9310
|
#define GL_FRAMEBUFFER_DEFAULT_WIDTH 0x9310
|
||||||
#define GL_FRAMEBUFFER_DEFAULT_HEIGHT 0x9311
|
#define GL_FRAMEBUFFER_DEFAULT_HEIGHT 0x9311
|
||||||
|
@ -93,6 +93,8 @@ PStatCollector CLP(GraphicsStateGuardian)::_texture_update_pcollector("Draw:Upda
|
|||||||
PStatCollector CLP(GraphicsStateGuardian)::_fbo_bind_pcollector("Draw:Bind FBO");
|
PStatCollector CLP(GraphicsStateGuardian)::_fbo_bind_pcollector("Draw:Bind FBO");
|
||||||
PStatCollector CLP(GraphicsStateGuardian)::_check_error_pcollector("Draw:Check errors");
|
PStatCollector CLP(GraphicsStateGuardian)::_check_error_pcollector("Draw:Check errors");
|
||||||
PStatCollector CLP(GraphicsStateGuardian)::_check_residency_pcollector("*:PStats:Check residency");
|
PStatCollector CLP(GraphicsStateGuardian)::_check_residency_pcollector("*:PStats:Check residency");
|
||||||
|
PStatCollector CLP(GraphicsStateGuardian)::_wait_fence_pcollector("Wait:Fence");
|
||||||
|
PStatCollector CLP(GraphicsStateGuardian)::_copy_texture_finish_pcollector("Draw:Copy texture:Finish");
|
||||||
|
|
||||||
#if defined(HAVE_CG) && !defined(OPENGLES)
|
#if defined(HAVE_CG) && !defined(OPENGLES)
|
||||||
AtomicAdjust::Integer CLP(GraphicsStateGuardian)::_num_gsgs_with_cg_contexts = 0;
|
AtomicAdjust::Integer CLP(GraphicsStateGuardian)::_num_gsgs_with_cg_contexts = 0;
|
||||||
@ -164,6 +166,10 @@ null_glPolygonOffsetClamp(GLfloat factor, GLfloat units, GLfloat clamp) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void APIENTRY
|
||||||
|
null_glMemoryBarrier(GLbitfield barriers) {
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef OPENGLES_1
|
#ifndef OPENGLES_1
|
||||||
// We have a default shader that will be applied when there isn't any shader
|
// We have a default shader that will be applied when there isn't any shader
|
||||||
// applied (e.g. if it failed to compile). We need this because OpenGL ES
|
// applied (e.g. if it failed to compile). We need this because OpenGL ES
|
||||||
@ -507,7 +513,9 @@ int CLP(GraphicsStateGuardian)::get_driver_shader_version_minor() { return _gl_s
|
|||||||
CLP(GraphicsStateGuardian)::
|
CLP(GraphicsStateGuardian)::
|
||||||
CLP(GraphicsStateGuardian)(GraphicsEngine *engine, GraphicsPipe *pipe) :
|
CLP(GraphicsStateGuardian)(GraphicsEngine *engine, GraphicsPipe *pipe) :
|
||||||
GraphicsStateGuardian(gl_coordinate_system, engine, pipe),
|
GraphicsStateGuardian(gl_coordinate_system, engine, pipe),
|
||||||
_renderbuffer_residency(get_prepared_objects()->get_name(), "renderbuffer")
|
_renderbuffer_residency(get_prepared_objects()->get_name(), "renderbuffer"),
|
||||||
|
_active_ppbuffer_memory_pcollector("Graphics memory:" + get_prepared_objects()->get_name() + ":Active:ppbuffer"),
|
||||||
|
_inactive_ppbuffer_memory_pcollector("Graphics memory:" + get_prepared_objects()->get_name() + ":Inactive:ppbuffer")
|
||||||
{
|
{
|
||||||
_error_count = 0;
|
_error_count = 0;
|
||||||
_last_error_check = -1.0;
|
_last_error_check = -1.0;
|
||||||
@ -1685,13 +1693,18 @@ reset() {
|
|||||||
if (is_at_least_gles_version(3, 0)) {
|
if (is_at_least_gles_version(3, 0)) {
|
||||||
_glMapBufferRange = (PFNGLMAPBUFFERRANGEEXTPROC)
|
_glMapBufferRange = (PFNGLMAPBUFFERRANGEEXTPROC)
|
||||||
get_extension_func("glMapBufferRange");
|
get_extension_func("glMapBufferRange");
|
||||||
|
_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)
|
||||||
} else if (has_extension("GL_EXT_map_buffer_range")) {
|
get_extension_func("glUnmapBuffer");
|
||||||
|
}
|
||||||
|
else if (has_extension("GL_EXT_map_buffer_range")) {
|
||||||
_glMapBufferRange = (PFNGLMAPBUFFERRANGEEXTPROC)
|
_glMapBufferRange = (PFNGLMAPBUFFERRANGEEXTPROC)
|
||||||
get_extension_func("glMapBufferRangeEXT");
|
get_extension_func("glMapBufferRangeEXT");
|
||||||
|
_glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)
|
||||||
} else {
|
get_extension_func("glUnmapBufferOES");
|
||||||
|
}
|
||||||
|
else {
|
||||||
_glMapBufferRange = nullptr;
|
_glMapBufferRange = nullptr;
|
||||||
|
_glUnmapBuffer = nullptr;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// Check for various advanced buffer management features.
|
// Check for various advanced buffer management features.
|
||||||
@ -2891,6 +2904,28 @@ reset() {
|
|||||||
is_at_least_gl_version(3, 3) || has_extension("GL_ARB_blend_func_extended");
|
is_at_least_gl_version(3, 3) || has_extension("GL_ARB_blend_func_extended");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef OPENGLES
|
||||||
|
if (is_at_least_gl_version(3, 2) || has_extension("GL_ARB_sync")) {
|
||||||
|
_glFenceSync = (PFNGLFENCESYNCPROC)get_extension_func("glFenceSync");
|
||||||
|
_glDeleteSync = (PFNGLDELETESYNCPROC)get_extension_func("glDeleteSync");
|
||||||
|
_glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC)get_extension_func("glClientWaitSync");
|
||||||
|
_glGetSynciv = (PFNGLGETSYNCIVPROC)get_extension_func("glGetSynciv");
|
||||||
|
}
|
||||||
|
#elif !defined(OPENGLES_1)
|
||||||
|
if (is_at_least_gles_version(3, 0)) {
|
||||||
|
_glFenceSync = (PFNGLFENCESYNCPROC)get_extension_func("glFenceSync");
|
||||||
|
_glDeleteSync = (PFNGLDELETESYNCPROC)get_extension_func("glDeleteSync");
|
||||||
|
_glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC)get_extension_func("glClientWaitSync");
|
||||||
|
_glGetSynciv = (PFNGLGETSYNCIVPROC)get_extension_func("glGetSynciv");
|
||||||
|
}
|
||||||
|
else if (has_extension("GL_APPLE_sync")) {
|
||||||
|
_glFenceSync = (PFNGLFENCESYNCPROC)get_extension_func("glFenceSyncAPPLE");
|
||||||
|
_glDeleteSync = (PFNGLDELETESYNCPROC)get_extension_func("glDeleteSyncAPPLE");
|
||||||
|
_glClientWaitSync = (PFNGLCLIENTWAITSYNCPROC)get_extension_func("glClientWaitSyncAPPLE");
|
||||||
|
_glGetSynciv = (PFNGLGETSYNCIVPROC)get_extension_func("glGetSyncivAPPLE");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef OPENGLES
|
#ifdef OPENGLES
|
||||||
_edge_clamp = GL_CLAMP_TO_EDGE;
|
_edge_clamp = GL_CLAMP_TO_EDGE;
|
||||||
#else
|
#else
|
||||||
@ -3137,7 +3172,7 @@ reset() {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
_glBindImageTexture = nullptr;
|
_glBindImageTexture = nullptr;
|
||||||
_glMemoryBarrier = nullptr;
|
_glMemoryBarrier = null_glMemoryBarrier;
|
||||||
}
|
}
|
||||||
#endif // !OPENGLES_1
|
#endif // !OPENGLES_1
|
||||||
|
|
||||||
@ -4162,6 +4197,10 @@ begin_frame(Thread *current_thread) {
|
|||||||
_primitive_batches_display_list_pcollector.clear_level();
|
_primitive_batches_display_list_pcollector.clear_level();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!_async_ram_copies.empty()) {
|
||||||
|
finish_async_framebuffer_ram_copies();
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(DO_PSTATS) && !defined(OPENGLES)
|
#if defined(DO_PSTATS) && !defined(OPENGLES)
|
||||||
int frame_number = ClockObject::get_global_clock()->get_frame_count(current_thread);
|
int frame_number = ClockObject::get_global_clock()->get_frame_count(current_thread);
|
||||||
if (_current_frame_timing == nullptr ||
|
if (_current_frame_timing == nullptr ||
|
||||||
@ -4350,6 +4389,38 @@ end_frame(Thread *current_thread) {
|
|||||||
}
|
}
|
||||||
#endif // OPENGLES
|
#endif // OPENGLES
|
||||||
|
|
||||||
|
#ifndef OPENGLES_1
|
||||||
|
if (!_deleted_buffers.empty()) {
|
||||||
|
GLuint *indices = (GLuint *)alloca(sizeof(GLuint *) * _deleted_buffers.size());
|
||||||
|
size_t num_indices = 0;
|
||||||
|
DeletedBuffers::iterator it = _deleted_buffers.begin();
|
||||||
|
while (it != _deleted_buffers.end()) {
|
||||||
|
DeletedBuffer &buffer = *it;
|
||||||
|
if (!_supports_buffer_storage && buffer._mapped_pointer != nullptr) {
|
||||||
|
_glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer._index);
|
||||||
|
_glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||||
|
buffer._mapped_pointer = nullptr;
|
||||||
|
}
|
||||||
|
if (++buffer._age > 2) {
|
||||||
|
indices[num_indices++] = buffer._index;
|
||||||
|
it = _deleted_buffers.erase(it);
|
||||||
|
_inactive_ppbuffer_memory_pcollector.sub_level(buffer._size);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!_supports_buffer_storage) {
|
||||||
|
_glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||||
|
}
|
||||||
|
if (num_indices > 0) {
|
||||||
|
_glDeleteBuffers(num_indices, indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_active_ppbuffer_memory_pcollector.flush_level();
|
||||||
|
_inactive_ppbuffer_memory_pcollector.flush_level();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
if (_check_errors || (_supports_debug && gl_debug)) {
|
if (_check_errors || (_supports_debug && gl_debug)) {
|
||||||
report_my_gl_errors();
|
report_my_gl_errors();
|
||||||
@ -6573,6 +6644,72 @@ record_deleted_display_list(GLuint index) {
|
|||||||
_deleted_display_lists.push_back(index);
|
_deleted_display_lists.push_back(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef OPENGLES_1
|
||||||
|
/**
|
||||||
|
* Creates a new buffer for client access. It is bound when this returns.
|
||||||
|
* If persistent mapping is possible, mapped_ptr will be filled in with a
|
||||||
|
* pointer to the mapped data.
|
||||||
|
*/
|
||||||
|
void CLP(GraphicsStateGuardian)::
|
||||||
|
bind_new_client_buffer(GLuint &index, void *&mapped_ptr, GLenum target, size_t size) {
|
||||||
|
_active_ppbuffer_memory_pcollector.add_level(size);
|
||||||
|
|
||||||
|
{
|
||||||
|
// Start at the end, because removing near the end is cheaper.
|
||||||
|
LightMutexHolder holder(_lock);
|
||||||
|
size_t i = _deleted_buffers.size();
|
||||||
|
while (i > 1) {
|
||||||
|
--i;
|
||||||
|
DeletedBuffer &buffer = _deleted_buffers[i];
|
||||||
|
if (buffer._size == size) {
|
||||||
|
index = buffer._index;
|
||||||
|
mapped_ptr = buffer._mapped_pointer;
|
||||||
|
_glBindBuffer(target, buffer._index);
|
||||||
|
if (!_supports_buffer_storage && mapped_ptr != nullptr) {
|
||||||
|
// Need to unmap it before we can use it.
|
||||||
|
_glUnmapBuffer(target);
|
||||||
|
mapped_ptr = nullptr;
|
||||||
|
}
|
||||||
|
_deleted_buffers.erase(_deleted_buffers.begin() + i);
|
||||||
|
_inactive_ppbuffer_memory_pcollector.sub_level(size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_glGenBuffers(1, &index);
|
||||||
|
_glBindBuffer(target, index);
|
||||||
|
#ifndef OPENGLES
|
||||||
|
if (_supports_buffer_storage) {
|
||||||
|
// Map persistently, we already use fences to synchronize access anyway.
|
||||||
|
_glBufferStorage(target, size, nullptr, GL_MAP_READ_BIT |
|
||||||
|
GL_CLIENT_STORAGE_BIT | GL_MAP_PERSISTENT_BIT);
|
||||||
|
mapped_ptr = _glMapBufferRange(target, 0, size,
|
||||||
|
GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
//XXX does it matter what usage hint we pass here? None seem to fit well.
|
||||||
|
_glBufferData(target, size, nullptr, GL_DYNAMIC_DRAW);
|
||||||
|
mapped_ptr = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the given buffer, as returned by bind_new_client_buffer, is no
|
||||||
|
* longer needed.
|
||||||
|
*/
|
||||||
|
void CLP(GraphicsStateGuardian)::
|
||||||
|
release_client_buffer(GLuint index, void *mapped_ptr, size_t size) {
|
||||||
|
// This may be called from any thread, so we can't make OpenGL calls here
|
||||||
|
// (like unmapping the buffer).
|
||||||
|
LightMutexHolder holder(_lock);
|
||||||
|
_deleted_buffers.push_back({index, 0, mapped_ptr, size});
|
||||||
|
_active_ppbuffer_memory_pcollector.sub_level(size);
|
||||||
|
_inactive_ppbuffer_memory_pcollector.add_level(size);
|
||||||
|
}
|
||||||
|
#endif // !OPENGLES_1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new retained-mode representation of the given data, and returns a
|
* Creates a new retained-mode representation of the given data, and returns a
|
||||||
* newly-allocated VertexBufferContext pointer to reference it. It is the
|
* newly-allocated VertexBufferContext pointer to reference it. It is the
|
||||||
@ -7567,7 +7704,6 @@ framebuffer_copy_to_texture(Texture *tex, int view, int z,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy the pixels within the indicated display region from the framebuffer
|
* Copy the pixels within the indicated display region from the framebuffer
|
||||||
* into system memory, not texture memory. Returns true on success, false on
|
* into system memory, not texture memory. Returns true on success, false on
|
||||||
@ -7577,7 +7713,8 @@ framebuffer_copy_to_texture(Texture *tex, int view, int z,
|
|||||||
*/
|
*/
|
||||||
bool CLP(GraphicsStateGuardian)::
|
bool CLP(GraphicsStateGuardian)::
|
||||||
framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
||||||
const DisplayRegion *dr, const RenderBuffer &rb) {
|
const DisplayRegion *dr, const RenderBuffer &rb,
|
||||||
|
ScreenshotRequest *request) {
|
||||||
nassertr(tex != nullptr && dr != nullptr, false);
|
nassertr(tex != nullptr && dr != nullptr, false);
|
||||||
set_read_buffer(rb._buffer_type);
|
set_read_buffer(rb._buffer_type);
|
||||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||||
@ -7868,12 +8005,23 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
|||||||
}
|
}
|
||||||
#endif // NDEBUG
|
#endif // NDEBUG
|
||||||
|
|
||||||
unsigned char *image_ptr = tex->modify_ram_image();
|
size_t image_size = tex->get_expected_ram_page_size();
|
||||||
size_t image_size = tex->get_ram_image_size();
|
unsigned char *image_ptr = nullptr;
|
||||||
if (z >= 0 || view > 0) {
|
#ifndef OPENGLES_1
|
||||||
image_size = tex->get_expected_ram_page_size();
|
GLuint pbo = 0;
|
||||||
|
void *mapped_ptr = nullptr;
|
||||||
|
if (request != nullptr) {
|
||||||
|
nassertr(z <= 0, false);
|
||||||
|
image_size *= tex->get_z_size();
|
||||||
|
bind_new_client_buffer(pbo, mapped_ptr, GL_PIXEL_PACK_BUFFER, image_size);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
image_ptr = tex->modify_ram_image();
|
||||||
if (z >= 0) {
|
if (z >= 0) {
|
||||||
image_ptr += z * image_size;
|
image_ptr += z * image_size;
|
||||||
|
} else {
|
||||||
|
image_size = tex->get_ram_image_size();
|
||||||
}
|
}
|
||||||
if (view > 0) {
|
if (view > 0) {
|
||||||
image_ptr += (view * tex->get_z_size()) * image_size;
|
image_ptr += (view * tex->get_z_size()) * image_size;
|
||||||
@ -7884,9 +8032,22 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
|||||||
glReadPixels(xo, yo, w, h, external_format,
|
glReadPixels(xo, yo, w, h, external_format,
|
||||||
get_component_type(component_type), image_ptr);
|
get_component_type(component_type), image_ptr);
|
||||||
|
|
||||||
// We may have to reverse the byte ordering of the image if GL didn't do it
|
#ifndef OPENGLES_1
|
||||||
// for us.
|
if (request != nullptr) {
|
||||||
|
_glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||||
|
#ifndef OPENGLES
|
||||||
|
if (_supports_buffer_storage) {
|
||||||
|
_glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
GLsync fence = _glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
|
_async_ram_copies.push_back({request, pbo, fence, external_format,
|
||||||
|
view, mapped_ptr, image_size});
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
if (external_format == GL_RGBA || external_format == GL_RGB) {
|
if (external_format == GL_RGBA || external_format == GL_RGB) {
|
||||||
|
// We may have to reverse the byte ordering of the image if GL didn't do it
|
||||||
|
// for us.
|
||||||
PTA_uchar new_image;
|
PTA_uchar new_image;
|
||||||
const unsigned char *result =
|
const unsigned char *result =
|
||||||
fix_component_ordering(new_image, image_ptr, image_size,
|
fix_component_ordering(new_image, image_ptr, image_size,
|
||||||
@ -7896,10 +8057,114 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef OPENGLES_1
|
||||||
|
if (request != nullptr) {
|
||||||
|
request->finish();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
report_my_gl_errors();
|
report_my_gl_errors();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finishes all asynchronous framebuffer-copy-to-ram operations.
|
||||||
|
*/
|
||||||
|
void CLP(GraphicsStateGuardian)::
|
||||||
|
finish_async_framebuffer_ram_copies(bool force) {
|
||||||
|
#ifndef OPENGLES_1
|
||||||
|
if (_async_ram_copies.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//XXX having a fixed number of threads is not a great idea. We ought to have
|
||||||
|
// a common thread pool that is sized based on the available number of CPUs.
|
||||||
|
#ifdef HAVE_THREADS
|
||||||
|
AsyncTaskManager *task_mgr = AsyncTaskManager::get_global_ptr();
|
||||||
|
static AsyncTaskChain *chain = task_mgr->make_task_chain("texture_download", 2, TP_low);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PStatTimer timer(_copy_texture_finish_pcollector);
|
||||||
|
|
||||||
|
if (force) {
|
||||||
|
// Just wait for the last fence, the rest must be complete too then.
|
||||||
|
PStatTimer timer(_wait_fence_pcollector);
|
||||||
|
GLsync fence = _async_ram_copies.back()._fence;
|
||||||
|
_glClientWaitSync(fence, 0, (GLuint64)-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!_async_ram_copies.empty()) {
|
||||||
|
AsyncRamCopy © = _async_ram_copies.front();
|
||||||
|
if (!force) {
|
||||||
|
GLenum result = _glClientWaitSync(copy._fence, 0, 0);
|
||||||
|
if (result != GL_ALREADY_SIGNALED && result != GL_CONDITION_SATISFIED) {
|
||||||
|
// Not yet done. The rest must not yet be done then, either.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_glDeleteSync(copy._fence);
|
||||||
|
|
||||||
|
GLuint pbo = copy._pbo;
|
||||||
|
int view = copy._view;
|
||||||
|
PT(ScreenshotRequest) request = std::move(copy._request);
|
||||||
|
GLuint external_format = copy._external_format;
|
||||||
|
void *mapped_ptr = copy._mapped_pointer;
|
||||||
|
size_t size = copy._size;
|
||||||
|
|
||||||
|
if (mapped_ptr == nullptr) {
|
||||||
|
_glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
|
||||||
|
#ifdef OPENGLES
|
||||||
|
// There is neither glMapBuffer nor persistent mapping in OpenGL ES
|
||||||
|
mapped_ptr = _glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, size, GL_MAP_READ_BIT);
|
||||||
|
#else
|
||||||
|
// If we get here in desktop GL, we must not have persistent mapping
|
||||||
|
nassertv(!_supports_buffer_storage);
|
||||||
|
mapped_ptr = _glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the memcpy in the background, since it can be slow.
|
||||||
|
auto func = [=](AsyncTask *task) {
|
||||||
|
const unsigned char *result = (unsigned char *)mapped_ptr;
|
||||||
|
PTA_uchar new_image;
|
||||||
|
if (external_format == GL_RGBA || external_format == GL_RGB) {
|
||||||
|
// We may have to reverse the byte ordering of the image if GL didn't do
|
||||||
|
// it for us.
|
||||||
|
result = fix_component_ordering(new_image, result, size,
|
||||||
|
external_format, request->get_result());
|
||||||
|
}
|
||||||
|
request->set_view_data(view, result);
|
||||||
|
|
||||||
|
// Finishing can take a long time, release the client buffer first so it
|
||||||
|
// can be reused for the next screenshot.
|
||||||
|
this->release_client_buffer(pbo, mapped_ptr, size);
|
||||||
|
request->finish();
|
||||||
|
return AsyncTask::DS_done;
|
||||||
|
};
|
||||||
|
#ifdef HAVE_THREADS
|
||||||
|
// We assign a sort value based on the originating frame number, so that
|
||||||
|
// earlier frames will be processed before subsequent frames, but we don't
|
||||||
|
// make it unique for every frame, which would kill concurrency.
|
||||||
|
int frame_number = request->get_frame_number();
|
||||||
|
chain->add(std::move(func), "screenshot", frame_number >> 3, -(frame_number & ((1 << 3) - 1)));
|
||||||
|
#else
|
||||||
|
func(nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_async_ram_copies.pop_front();
|
||||||
|
|
||||||
|
// If there is 1 remaining, save it for next frame. This helps prevent an
|
||||||
|
// inconsistent frame rate when the number of fetched frames alternates
|
||||||
|
// between 0 and 2, which can settle into a stable feedback loop.
|
||||||
|
if (!force && _async_ram_copies.size() == 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef SUPPORT_FIXED_FUNCTION
|
#ifdef SUPPORT_FIXED_FUNCTION
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -142,6 +142,7 @@ typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSPROC) (GLsizei n, const GLuint *a
|
|||||||
typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays);
|
typedef void (APIENTRYP PFNGLGENVERTEXARRAYSPROC) (GLsizei n, GLuint *arrays);
|
||||||
typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha);
|
typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha);
|
||||||
typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
|
typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
|
||||||
|
typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target);
|
||||||
|
|
||||||
#ifndef OPENGLES_1
|
#ifndef OPENGLES_1
|
||||||
// GLSL shader functions
|
// GLSL shader functions
|
||||||
@ -231,6 +232,13 @@ typedef void (APIENTRYP PFNGLBUFFERSTORAGEPROC) (GLenum target, GLsizeiptr size,
|
|||||||
typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREPROC) (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format);
|
typedef void (APIENTRYP PFNGLBINDIMAGETEXTUREPROC) (GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, GLenum format);
|
||||||
typedef void (APIENTRYP PFNGLCLEARTEXIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, const void *data);
|
typedef void (APIENTRYP PFNGLCLEARTEXIMAGEPROC) (GLuint texture, GLint level, GLenum format, GLenum type, const void *data);
|
||||||
typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data);
|
typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC) (GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data);
|
||||||
|
typedef GLsync (APIENTRYP PFNGLFENCESYNCPROC) (GLenum condition, GLbitfield flags);
|
||||||
|
typedef GLboolean (APIENTRYP PFNGLISSYNCPROC) (GLsync sync);
|
||||||
|
typedef void (APIENTRYP PFNGLDELETESYNCPROC) (GLsync sync);
|
||||||
|
typedef GLenum (APIENTRYP PFNGLCLIENTWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout);
|
||||||
|
typedef void (APIENTRYP PFNGLWAITSYNCPROC) (GLsync sync, GLbitfield flags, GLuint64 timeout);
|
||||||
|
typedef void (APIENTRYP PFNGLGETINTEGER64VPROC) (GLenum pname, GLint64 *data);
|
||||||
|
typedef void (APIENTRYP PFNGLGETSYNCIVPROC) (GLsync sync, GLenum pname, GLsizei bufSize, GLsizei *length, GLint *values);
|
||||||
#endif // OPENGLES_1
|
#endif // OPENGLES_1
|
||||||
#ifndef OPENGLES
|
#ifndef OPENGLES
|
||||||
typedef void (APIENTRYP PFNGLBINDTEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures);
|
typedef void (APIENTRYP PFNGLBINDTEXTURESPROC) (GLuint first, GLsizei count, const GLuint *textures);
|
||||||
@ -253,7 +261,6 @@ typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64PROC) (GLuint index, GLuint64EXT
|
|||||||
typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VPROC) (GLuint index, const GLuint64EXT *v);
|
typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VPROC) (GLuint index, const GLuint64EXT *v);
|
||||||
typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VPROC) (GLuint index, GLenum pname, GLuint64EXT *params);
|
typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VPROC) (GLuint index, GLenum pname, GLuint64EXT *params);
|
||||||
typedef void *(APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access);
|
typedef void *(APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access);
|
||||||
typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target);
|
|
||||||
typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, void *data);
|
typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, void *data);
|
||||||
#endif // OPENGLES
|
#endif // OPENGLES
|
||||||
#endif // __EDG__
|
#endif // __EDG__
|
||||||
@ -353,6 +360,11 @@ public:
|
|||||||
virtual ShaderContext *prepare_shader(Shader *shader);
|
virtual ShaderContext *prepare_shader(Shader *shader);
|
||||||
virtual void release_shader(ShaderContext *sc);
|
virtual void release_shader(ShaderContext *sc);
|
||||||
|
|
||||||
|
#ifndef OPENGLES_1
|
||||||
|
void bind_new_client_buffer(GLuint &index, void *&mapped_ptr, GLenum target, size_t size);
|
||||||
|
void release_client_buffer(GLuint index, void *mapped_ptr, size_t size);
|
||||||
|
#endif
|
||||||
|
|
||||||
void record_deleted_display_list(GLuint index);
|
void record_deleted_display_list(GLuint index);
|
||||||
|
|
||||||
virtual VertexBufferContext *prepare_vertex_buffer(GeomVertexArrayData *data);
|
virtual VertexBufferContext *prepare_vertex_buffer(GeomVertexArrayData *data);
|
||||||
@ -403,7 +415,9 @@ public:
|
|||||||
virtual bool framebuffer_copy_to_texture
|
virtual bool framebuffer_copy_to_texture
|
||||||
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
|
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
|
||||||
virtual bool framebuffer_copy_to_ram
|
virtual bool framebuffer_copy_to_ram
|
||||||
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
|
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb,
|
||||||
|
ScreenshotRequest *request);
|
||||||
|
void finish_async_framebuffer_ram_copies(bool force = false);
|
||||||
|
|
||||||
#ifdef SUPPORT_FIXED_FUNCTION
|
#ifdef SUPPORT_FIXED_FUNCTION
|
||||||
void apply_fog(Fog *fog);
|
void apply_fog(Fog *fog);
|
||||||
@ -880,6 +894,7 @@ public:
|
|||||||
|
|
||||||
#ifdef OPENGLES
|
#ifdef OPENGLES
|
||||||
PFNGLMAPBUFFERRANGEEXTPROC _glMapBufferRange;
|
PFNGLMAPBUFFERRANGEEXTPROC _glMapBufferRange;
|
||||||
|
PFNGLUNMAPBUFFEROESPROC _glUnmapBuffer;
|
||||||
#else
|
#else
|
||||||
PFNGLMAPBUFFERRANGEPROC _glMapBufferRange;
|
PFNGLMAPBUFFERRANGEPROC _glMapBufferRange;
|
||||||
#endif
|
#endif
|
||||||
@ -891,6 +906,8 @@ public:
|
|||||||
|
|
||||||
bool _supports_buffer_storage;
|
bool _supports_buffer_storage;
|
||||||
PFNGLBUFFERSTORAGEPROC _glBufferStorage;
|
PFNGLBUFFERSTORAGEPROC _glBufferStorage;
|
||||||
|
#else
|
||||||
|
static const bool _supports_buffer_storage = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool _supports_blend_equation_separate;
|
bool _supports_blend_equation_separate;
|
||||||
@ -1088,6 +1105,13 @@ public:
|
|||||||
PFNGLSHADERSTORAGEBLOCKBINDINGPROC _glShaderStorageBlockBinding;
|
PFNGLSHADERSTORAGEBLOCKBINDINGPROC _glShaderStorageBlockBinding;
|
||||||
#endif // !OPENGLES
|
#endif // !OPENGLES
|
||||||
|
|
||||||
|
#ifndef OPENGLES_1
|
||||||
|
PFNGLFENCESYNCPROC _glFenceSync;
|
||||||
|
PFNGLDELETESYNCPROC _glDeleteSync;
|
||||||
|
PFNGLCLIENTWAITSYNCPROC _glClientWaitSync;
|
||||||
|
PFNGLGETSYNCIVPROC _glGetSynciv;
|
||||||
|
#endif
|
||||||
|
|
||||||
GLenum _edge_clamp;
|
GLenum _edge_clamp;
|
||||||
GLenum _border_clamp;
|
GLenum _border_clamp;
|
||||||
GLenum _mirror_repeat;
|
GLenum _mirror_repeat;
|
||||||
@ -1109,6 +1133,17 @@ public:
|
|||||||
DeletedNames _deleted_display_lists;
|
DeletedNames _deleted_display_lists;
|
||||||
DeletedNames _deleted_queries;
|
DeletedNames _deleted_queries;
|
||||||
|
|
||||||
|
#ifndef OPENGLES_1
|
||||||
|
struct DeletedBuffer {
|
||||||
|
GLuint _index;
|
||||||
|
int _age;
|
||||||
|
void *_mapped_pointer;
|
||||||
|
size_t _size;
|
||||||
|
};
|
||||||
|
typedef pvector<DeletedBuffer> DeletedBuffers;
|
||||||
|
DeletedBuffers _deleted_buffers;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef OPENGLES_1
|
#ifndef OPENGLES_1
|
||||||
// Stores textures for which memory bariers should be issued.
|
// Stores textures for which memory bariers should be issued.
|
||||||
typedef pset<TextureContext*> TextureSet;
|
typedef pset<TextureContext*> TextureSet;
|
||||||
@ -1165,8 +1200,22 @@ public:
|
|||||||
FrameTiming *_current_frame_timing = nullptr;
|
FrameTiming *_current_frame_timing = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct AsyncRamCopy {
|
||||||
|
PT(ScreenshotRequest) _request;
|
||||||
|
GLuint _pbo;
|
||||||
|
GLsync _fence;
|
||||||
|
GLuint _external_format;
|
||||||
|
int _view;
|
||||||
|
void *_mapped_pointer;
|
||||||
|
size_t _size;
|
||||||
|
};
|
||||||
|
pdeque<AsyncRamCopy> _async_ram_copies;
|
||||||
|
|
||||||
BufferResidencyTracker _renderbuffer_residency;
|
BufferResidencyTracker _renderbuffer_residency;
|
||||||
|
|
||||||
|
PStatCollector _active_ppbuffer_memory_pcollector;
|
||||||
|
PStatCollector _inactive_ppbuffer_memory_pcollector;
|
||||||
|
|
||||||
static PStatCollector _load_display_list_pcollector;
|
static PStatCollector _load_display_list_pcollector;
|
||||||
static PStatCollector _primitive_batches_display_list_pcollector;
|
static PStatCollector _primitive_batches_display_list_pcollector;
|
||||||
static PStatCollector _vertices_display_list_pcollector;
|
static PStatCollector _vertices_display_list_pcollector;
|
||||||
@ -1177,6 +1226,8 @@ public:
|
|||||||
static PStatCollector _fbo_bind_pcollector;
|
static PStatCollector _fbo_bind_pcollector;
|
||||||
static PStatCollector _check_error_pcollector;
|
static PStatCollector _check_error_pcollector;
|
||||||
static PStatCollector _check_residency_pcollector;
|
static PStatCollector _check_residency_pcollector;
|
||||||
|
static PStatCollector _wait_fence_pcollector;
|
||||||
|
static PStatCollector _copy_texture_finish_pcollector;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual TypeHandle get_type() const {
|
virtual TypeHandle get_type() const {
|
||||||
|
@ -28,6 +28,7 @@ class RenderBuffer;
|
|||||||
class GraphicsWindow;
|
class GraphicsWindow;
|
||||||
class NodePath;
|
class NodePath;
|
||||||
class GraphicsOutputBase;
|
class GraphicsOutputBase;
|
||||||
|
class ScreenshotRequest;
|
||||||
|
|
||||||
class VertexBufferContext;
|
class VertexBufferContext;
|
||||||
class IndexBufferContext;
|
class IndexBufferContext;
|
||||||
@ -220,7 +221,8 @@ public:
|
|||||||
virtual bool framebuffer_copy_to_texture
|
virtual bool framebuffer_copy_to_texture
|
||||||
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb)=0;
|
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb)=0;
|
||||||
virtual bool framebuffer_copy_to_ram
|
virtual bool framebuffer_copy_to_ram
|
||||||
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb)=0;
|
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb,
|
||||||
|
ScreenshotRequest *request = nullptr)=0;
|
||||||
|
|
||||||
virtual CoordinateSystem get_internal_coordinate_system() const=0;
|
virtual CoordinateSystem get_internal_coordinate_system() const=0;
|
||||||
|
|
||||||
|
@ -1393,8 +1393,8 @@ framebuffer_copy_to_texture(Texture *tex, int view, int z,
|
|||||||
*/
|
*/
|
||||||
bool TinyGraphicsStateGuardian::
|
bool TinyGraphicsStateGuardian::
|
||||||
framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
||||||
const DisplayRegion *dr,
|
const DisplayRegion *dr, const RenderBuffer &rb,
|
||||||
const RenderBuffer &rb) {
|
ScreenshotRequest *request) {
|
||||||
nassertr(tex != nullptr && dr != nullptr, false);
|
nassertr(tex != nullptr && dr != nullptr, false);
|
||||||
|
|
||||||
int xo, yo, w, h;
|
int xo, yo, w, h;
|
||||||
@ -1465,6 +1465,9 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
|
|||||||
fo += _c->zb->linesize / PSZB;
|
fo += _c->zb->linesize / PSZB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request != nullptr) {
|
||||||
|
request->finish();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,8 @@ public:
|
|||||||
virtual bool framebuffer_copy_to_texture
|
virtual bool framebuffer_copy_to_texture
|
||||||
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
|
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
|
||||||
virtual bool framebuffer_copy_to_ram
|
virtual bool framebuffer_copy_to_ram
|
||||||
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
|
(Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb,
|
||||||
|
ScreenshotRequest *request);
|
||||||
|
|
||||||
virtual void set_state_and_transform(const RenderState *state,
|
virtual void set_state_and_transform(const RenderState *state,
|
||||||
const TransformState *transform);
|
const TransformState *transform);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user