Bläddra i källkod

display: Add support for asynchronous screenshot download

rdb 3 år sedan
förälder
incheckning
cfd18bb16f

+ 13 - 2
direct/src/showbase/ShowBase.py

@@ -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

+ 2 - 0
panda/src/display/CMakeLists.txt

@@ -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

+ 2 - 0
panda/src/display/config_display.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

+ 5 - 0
panda/src/display/graphicsEngine.cxx

@@ -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");

+ 89 - 1
panda/src/display/graphicsOutput.cxx

@@ -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 &copy) :
   _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)
 {
 {
 }
 }
 
 

+ 7 - 0
panda/src/display/graphicsOutput.h

@@ -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;

+ 5 - 1
panda/src/display/graphicsStateGuardian.cxx

@@ -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;
 }
 }
 
 

+ 2 - 1
panda/src/display/graphicsStateGuardian.h

@@ -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);

+ 1 - 0
panda/src/display/p3display_composite2.cxx

@@ -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 - 0
panda/src/display/screenshotRequest.I

@@ -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 - 0
panda/src/display/screenshotRequest.cxx

@@ -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 - 0
panda/src/display/screenshotRequest.h

@@ -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

+ 11 - 2
panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx

@@ -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) {
-  return do_framebuffer_copy_to_ram(tex, view, z, dr, rb, false);
+                        const DisplayRegion *dr, const RenderBuffer &rb,
+                        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;
 }
 }
 
 
 /**
 /**

+ 2 - 1
panda/src/dxgsg9/dxGraphicsStateGuardian9.h

@@ -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,

+ 7 - 0
panda/src/gles2gsg/gles2gsg.h

@@ -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

+ 279 - 14
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -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");
-
-  } else if (has_extension("GL_EXT_map_buffer_range")) {
+    _glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)
+      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");
-
-  } else {
+    _glUnmapBuffer = (PFNGLUNMAPBUFFERPROC)
+      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_ram_image_size();
-  if (z >= 0 || view > 0) {
-    image_size = tex->get_expected_ram_page_size();
+  size_t image_size = tex->get_expected_ram_page_size();
+  unsigned char *image_ptr = nullptr;
+#ifndef OPENGLES_1
+  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
-  // for us.
+#ifndef OPENGLES_1
+  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 &copy = _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
 /**
 /**
  *
  *

+ 53 - 2
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -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 {

+ 3 - 1
panda/src/gsgbase/graphicsStateGuardianBase.h

@@ -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;
 
 

+ 5 - 2
panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx

@@ -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 RenderBuffer &rb) {
+                        const DisplayRegion *dr, 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;
 }
 }
 
 

+ 2 - 1
panda/src/tinydisplay/tinyGraphicsStateGuardian.h

@@ -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);