Browse Source

Support for asynchronous texture readback via OpenGL PBOs

rdb 6 years ago
parent
commit
1241524877

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

@@ -1196,6 +1196,10 @@ dispatch_compute(const LVecBase3i &work_groups, const ShaderAttrib *sattr, Graph
   string draw_name = gsg->get_threading_model().get_draw_name();
   string draw_name = gsg->get_threading_model().get_draw_name();
   if (draw_name.empty()) {
   if (draw_name.empty()) {
     // A single-threaded environment.  No problem.
     // A single-threaded environment.  No problem.
+    Thread *current_thread = Thread::get_current_thread();
+    PreparedGraphicsObjects *pgo = gsg->get_prepared_objects();
+    pgo->extract_pending_now(gsg, current_thread);
+    pgo->release_pending_now(gsg, current_thread);
     gsg->push_group_marker(std::string("Compute ") + shader->get_filename(Shader::ST_compute).get_basename());
     gsg->push_group_marker(std::string("Compute ") + shader->get_filename(Shader::ST_compute).get_basename());
     gsg->set_state_and_transform(state, TransformState::make_identity());
     gsg->set_state_and_transform(state, TransformState::make_identity());
     gsg->dispatch_compute(work_groups[0], work_groups[1], work_groups[2]);
     gsg->dispatch_compute(work_groups[0], work_groups[1], work_groups[2]);
@@ -2759,14 +2763,19 @@ thread_main() {
       break;
       break;
 
 
     case TS_do_compute:
     case TS_do_compute:
-      nassertd(_gsg != nullptr && _state != nullptr) break;
       {
       {
+        GraphicsStateGuardian *gsg = _gsg;
+        nassertd(gsg != nullptr && _state != nullptr) break;
+
         const ShaderAttrib *sattr;
         const ShaderAttrib *sattr;
         _state->get_attrib(sattr);
         _state->get_attrib(sattr);
-        _gsg->push_group_marker(std::string("Compute ") + sattr->get_shader()->get_filename(Shader::ST_compute).get_basename());
-        _gsg->set_state_and_transform(_state, TransformState::make_identity());
-        _gsg->dispatch_compute(_work_groups[0], _work_groups[1], _work_groups[2]);
-        _gsg->pop_group_marker();
+        PreparedGraphicsObjects *pgo = gsg->get_prepared_objects();
+        pgo->extract_pending_now(gsg, current_thread);
+        pgo->release_pending_now(gsg, current_thread);
+        gsg->push_group_marker(std::string("Compute ") + sattr->get_shader()->get_filename(Shader::ST_compute).get_basename());
+        gsg->set_state_and_transform(_state, TransformState::make_identity());
+        gsg->dispatch_compute(_work_groups[0], _work_groups[1], _work_groups[2]);
+        gsg->pop_group_marker();
       }
       }
       break;
       break;
 
 

+ 18 - 0
panda/src/display/graphicsStateGuardian.cxx

@@ -590,6 +590,24 @@ extract_texture_data(Texture *) {
   return false;
   return false;
 }
 }
 
 
+/**
+ * Schedules an asynchronous extraction into a staging buffer, which is
+ * returned.  The operation may also be done immediately, in which case a
+ * null pointer is returned.
+ *
+ * This function should not be called directly to extract a texture.  Instead,
+ * call Texture::extract().
+ *
+ * The default implementation just extracts the textures synchronously.
+ */
+TransferBufferContext *GraphicsStateGuardian::
+async_extract_textures(const pvector<PT(Texture)> &textures) {
+  for (Texture *texture : textures) {
+    extract_texture_data(texture);
+  }
+  return nullptr;
+}
+
 /**
 /**
  * Creates whatever structures the GSG requires to represent the sampler
  * Creates whatever structures the GSG requires to represent the sampler
  * internally, and returns a newly-allocated SamplerContext object with this
  * internally, and returns a newly-allocated SamplerContext object with this

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

@@ -294,6 +294,8 @@ public:
   virtual bool update_texture(TextureContext *tc, bool force);
   virtual bool update_texture(TextureContext *tc, bool force);
   virtual void release_texture(TextureContext *tc);
   virtual void release_texture(TextureContext *tc);
   virtual bool extract_texture_data(Texture *tex);
   virtual bool extract_texture_data(Texture *tex);
+  virtual TransferBufferContext *
+    async_extract_textures(const pvector<PT(Texture)> &textures);
 
 
   virtual SamplerContext *prepare_sampler(const SamplerState &sampler);
   virtual SamplerContext *prepare_sampler(const SamplerState &sampler);
   virtual void release_sampler(SamplerContext *sc);
   virtual void release_sampler(SamplerContext *sc);

+ 298 - 97
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -2649,6 +2649,27 @@ reset() {
   }
   }
 #endif
 #endif
 
 
+#ifndef OPENGLES
+  _supports_fence_sync = false;
+  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");
+    _glGetSynciv = (PFNGLGETSYNCIVPROC)
+      get_extension_func("glGetSynciv");
+
+    if (_glFenceSync != nullptr && _glDeleteSync != nullptr &&
+        _glGetSynciv != nullptr) {
+      _supports_fence_sync = true;
+    } else {
+      GLCAT.warning()
+        << "Sync objects advertised as supported by OpenGL runtime, but "
+           "could not get pointer to extension functions.\n";
+    }
+  }
+#endif
+
 #ifdef OPENGLES_1
 #ifdef OPENGLES_1
   // In OpenGL ES 1, blending is supported via extensions.
   // In OpenGL ES 1, blending is supported via extensions.
   if (has_extension("GL_OES_blend_subtract")) {
   if (has_extension("GL_OES_blend_subtract")) {
@@ -6017,6 +6038,114 @@ extract_texture_data(Texture *tex) {
   return success;
   return success;
 }
 }
 
 
+#ifndef OPENGLES
+/**
+ * Schedules an asynchronous extraction into a staging buffer, which is
+ * returned.  The operation may also be done immediately, in which case a
+ * null pointer is returned.
+ *
+ * This function should not be called directly to extract a texture.  Instead,
+ * call Texture::extract().
+ */
+TransferBufferContext *CLP(GraphicsStateGuardian)::
+async_extract_textures(const pvector<PT(Texture)> &textures) {
+  if (textures.empty()) {
+    return nullptr;
+  }
+
+  bool needs_barrier = false;
+
+  if (GLCAT.is_debug()) {
+    GLCAT.debug() << "Asynchronously extracting " << textures.size() << " textures\n";
+  }
+
+  CLP(PixelPackBufferContext) *pubc = new CLP(PixelPackBufferContext)(this);
+
+  size_t buffer_size = 0;
+
+  for (Texture *tex : textures) {
+    CLP(PixelPackBufferContext)::ExtractTexture et;
+    et._texture = tex;
+    et._offset = (buffer_size + 255) & ~255; // Align to 256 bytes
+    et._num_views = tex->get_num_views();
+
+    // Prepare view 0; we assume that all views have the same parameters.
+    TextureContext *tc = tex->prepare_now(0, get_prepared_objects(), this);
+    nassertd(tc != nullptr) continue;
+    CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
+
+    // This call also binds the texture, which is necessary for the call below
+    // this one to work.
+    if (!extract_texture_parameters(gtc, et._width, et._height, et._depth,
+                                    et._sampler, et._type, et._format,
+                                    et._compression)) {
+      continue;
+    }
+
+    et._page_size = get_extracted_texture_page_size(tex, gtc->_target, et._type, et._compression, 0);
+
+    if (gtc->needs_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT)) {
+      needs_barrier = true;
+    }
+
+    size_t image_size = et._page_size * et._num_views * et._depth;
+    buffer_size = et._offset + image_size;
+
+    pubc->_textures.push_back(std::move(et));
+  }
+
+  // Make sure any incoherent writes to the texture have been synced.
+  if (needs_barrier) {
+    issue_memory_barrier(GL_TEXTURE_UPDATE_BARRIER_BIT);
+  }
+
+  // Create the PBO.
+  _glGenBuffers(1, &pubc->_index);
+  _glBindBuffer(GL_PIXEL_PACK_BUFFER, pubc->_index);
+
+  if (_supports_buffer_storage) {
+    if (GLCAT.is_spam()) {
+      GLCAT.spam() << "glBufferStorage(GL_PIXEL_PACK_BUFFER, "
+                   << buffer_size << ", nullptr, GL_CLIENT_STORAGE_BIT)\n";
+    }
+    _glBufferStorage(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_MAP_READ_BIT | GL_CLIENT_STORAGE_BIT);
+  }
+
+  // Now that we have created the buffer, read each texture into the PBO.
+  for (CLP(PixelPackBufferContext)::ExtractTexture &et : pubc->_textures) {
+    TextureContext *tc = et._texture->prepare_now(0, get_prepared_objects(), this);
+    nassertd(tc != nullptr) continue;
+    CLP(TextureContext) *gtc = DCAST(CLP(TextureContext), tc);
+
+    if (GLCAT.is_spam()) {
+      GLCAT.spam()
+        << "glBindTexture(0x" << hex << gtc->_target << dec << ", " << gtc->_index << "): " << *et._texture << "\n";
+    }
+    glBindTexture(gtc->_target, gtc->_index);
+
+    extract_texture_image((void *)et._offset, et._page_size, et._texture,
+                          gtc->_target, et._type, et._compression, 0);
+  }
+
+  _glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+
+  // Issue a fence so that we know when the extraction is done.
+  if (_supports_fence_sync) {
+    if (GLCAT.is_spam()) {
+      GLCAT.spam() << "glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE)\n";
+    }
+    pubc->_sync = _glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+  }
+
+  // Flush this operation so that we know it starts right away.  Otherwise, we
+  // have no guarantee that the fence will be signalled eventually.
+  gl_flush();
+
+  report_my_gl_errors();
+  return pubc;
+}
+#endif  // OPENGLES
+
 #ifndef OPENGLES_1
 #ifndef OPENGLES_1
 /**
 /**
  * Creates whatever structures the GSG requires to represent the sampler state
  * Creates whatever structures the GSG requires to represent the sampler state
@@ -13714,6 +13843,11 @@ bool CLP(GraphicsStateGuardian)::
 do_extract_texture_data(CLP(TextureContext) *gtc) {
 do_extract_texture_data(CLP(TextureContext) *gtc) {
   report_my_gl_errors();
   report_my_gl_errors();
 
 
+#ifdef OPENGLES
+  nassert_raise("OpenGL ES does not support extracting texture data");
+  return false;
+#endif
+
   GLenum target = gtc->_target;
   GLenum target = gtc->_target;
   if (target == GL_NONE) {
   if (target == GL_NONE) {
     return false;
     return false;
@@ -13726,12 +13860,15 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
   }
   }
 #endif
 #endif
 
 
-  Texture *tex = gtc->get_texture();
+  int width, height, depth;
+  SamplerState sampler;
+  Texture::ComponentType type = Texture::T_unsigned_byte;
+  Texture::Format format = Texture::F_rgb;
+  Texture::CompressionMode compression = Texture::CM_off;
 
 
-  glBindTexture(target, gtc->_index);
-  if (GLCAT.is_spam()) {
-    GLCAT.spam()
-      << "glBindTexture(0x" << hex << target << dec << ", " << gtc->_index << "): " << *tex << "\n";
+  // The below call also binds the texture.
+  if (!extract_texture_parameters(gtc, width, height, depth, sampler, type, format, compression)) {
+    return false;
   }
   }
 
 
 #ifndef OPENGLES
 #ifndef OPENGLES
@@ -13740,18 +13877,96 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
   }
   }
 #endif
 #endif
 
 
-  GLint wrap_u, wrap_v, wrap_w;
-  GLint minfilter, magfilter;
+  // We don't want to call setup_texture() again; that resets too much.
+  // Instead, we'll just set the individual components.
+  Texture *tex = gtc->get_texture();
+  tex->set_x_size(width);
+  tex->set_y_size(height);
+  tex->set_z_size(depth);
+  tex->set_component_type(type);
+  tex->set_format(format);
+
+#ifdef OPENGLES
+  if (true) {
+#else
+  if (target != GL_TEXTURE_BUFFER) {
+#endif
+    tex->set_default_sampler(sampler);
+  }
 
 
 #ifndef OPENGLES
 #ifndef OPENGLES
-  GLfloat border_color[4];
+  size_t page_size = get_extracted_texture_page_size(tex, gtc->_target, type, compression, 0);
+#else
+  size_t page_size = 0;
 #endif
 #endif
+  nassertr(page_size > 0, false);
+  PTA_uchar image = PTA_uchar::empty_array(page_size * depth);
+
+  if (!extract_texture_image(image.p(), page_size, tex, target, type, compression, 0)) {
+    return false;
+  }
+
+  tex->set_ram_image(image, compression, page_size);
+
+  if (gtc->_uses_mipmaps) {
+    // Also get the mipmap levels.
+    GLint num_expected_levels = tex->get_expected_num_mipmap_levels();
+    GLint highest_level = num_expected_levels;
+
+    if (_supports_texture_max_level) {
+      glGetTexParameteriv(target, GL_TEXTURE_MAX_LEVEL, &highest_level);
+      highest_level = min(highest_level, num_expected_levels);
+    }
+    for (int n = 1; n <= highest_level; ++n) {
+#ifndef OPENGLES
+      size_t page_size = get_extracted_texture_page_size(tex, gtc->_target, type, compression, n);
+#else
+      size_t page_size = 0;
+#endif
+      if (!extract_texture_image(image, page_size, tex, target, type, compression, n)) {
+        return false;
+      }
+      tex->set_ram_mipmap_image(n, image, page_size);
+    }
+  }
+
+  return true;
+}
+
+/**
+ * Called from extract_texture_data(), this queries the properties of a texture
+ * to be extracted.
+ */
+bool CLP(GraphicsStateGuardian)::
+extract_texture_parameters(CLP(TextureContext) *gtc, int &width, int &height,
+                           int &depth, SamplerState &sampler,
+                           Texture::ComponentType &type, Texture::Format &format,
+                           Texture::CompressionMode &compression) {
+
+  GLenum target = gtc->_target;
+  if (target == GL_NONE) {
+    return false;
+  }
+
+  glBindTexture(target, gtc->_index);
+  if (GLCAT.is_spam()) {
+    GLCAT.spam()
+      << "glBindTexture(0x" << hex << target << dec << ", " << gtc->_index
+      << "): " << *gtc->get_texture() << "\n";
+  }
 
 
 #ifdef OPENGLES
 #ifdef OPENGLES
   if (true) {
   if (true) {
 #else
 #else
   if (target != GL_TEXTURE_BUFFER) {
   if (target != GL_TEXTURE_BUFFER) {
 #endif
 #endif
+    GLint wrap_u, wrap_v, wrap_w;
+    GLint minfilter, magfilter;
+
+#ifndef OPENGLES
+    GLfloat border_color[4];
+#endif
+
     glGetTexParameteriv(target, GL_TEXTURE_WRAP_S, &wrap_u);
     glGetTexParameteriv(target, GL_TEXTURE_WRAP_S, &wrap_u);
     glGetTexParameteriv(target, GL_TEXTURE_WRAP_T, &wrap_v);
     glGetTexParameteriv(target, GL_TEXTURE_WRAP_T, &wrap_v);
     wrap_w = GL_REPEAT;
     wrap_w = GL_REPEAT;
@@ -13766,6 +13981,17 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
 #ifndef OPENGLES
 #ifndef OPENGLES
     glGetTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, border_color);
     glGetTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, border_color);
 #endif
 #endif
+
+    sampler.set_wrap_u(get_panda_wrap_mode(wrap_u));
+    sampler.set_wrap_v(get_panda_wrap_mode(wrap_v));
+    sampler.set_wrap_w(get_panda_wrap_mode(wrap_w));
+    sampler.set_minfilter(get_panda_filter_type(minfilter));
+    //sampler.set_magfilter(get_panda_filter_type(magfilter));
+
+#ifndef OPENGLES
+    sampler.set_border_color(LColor(border_color[0], border_color[1],
+                                    border_color[2], border_color[3]));
+#endif
   }
   }
 
 
   GLenum page_target = target;
   GLenum page_target = target;
@@ -13774,7 +14000,9 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
     page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
     page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
   }
   }
 
 
-  GLint width = gtc->_width, height = gtc->_height, depth = gtc->_depth;
+  width = gtc->_width;
+  height = gtc->_height;
+  depth = gtc->_depth;
 #ifndef OPENGLES
 #ifndef OPENGLES
   glGetTexLevelParameteriv(page_target, 0, GL_TEXTURE_WIDTH, &width);
   glGetTexLevelParameteriv(page_target, 0, GL_TEXTURE_WIDTH, &width);
   if (target != GL_TEXTURE_1D) {
   if (target != GL_TEXTURE_1D) {
@@ -13792,7 +14020,7 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
   clear_my_gl_errors();
   clear_my_gl_errors();
   if (width <= 0 || height <= 0 || depth <= 0) {
   if (width <= 0 || height <= 0 || depth <= 0) {
     GLCAT.error()
     GLCAT.error()
-      << "No texture data for " << tex->get_name() << "\n";
+      << "No texture data for " << gtc->get_texture()->get_name() << "\n";
     return false;
     return false;
   }
   }
 
 
@@ -13812,16 +14040,12 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
   GLenum error_code = gl_get_error();
   GLenum error_code = gl_get_error();
   if (error_code != GL_NO_ERROR) {
   if (error_code != GL_NO_ERROR) {
     GLCAT.error()
     GLCAT.error()
-      << "Unable to query texture parameters for " << tex->get_name()
+      << "Unable to query texture parameters for " << gtc->get_texture()->get_name()
       << " : " << get_error_string(error_code) << "\n";
       << " : " << get_error_string(error_code) << "\n";
 
 
     return false;
     return false;
   }
   }
 
 
-  Texture::ComponentType type = Texture::T_unsigned_byte;
-  Texture::Format format = Texture::F_rgb;
-  Texture::CompressionMode compression = Texture::CM_off;
-
   switch (internal_format) {
   switch (internal_format) {
 #ifndef OPENGLES
 #ifndef OPENGLES
   case GL_COLOR_INDEX:
   case GL_COLOR_INDEX:
@@ -14225,75 +14449,76 @@ do_extract_texture_data(CLP(TextureContext) *gtc) {
 #endif
 #endif
   default:
   default:
     GLCAT.warning()
     GLCAT.warning()
-      << "Unhandled internal format for " << tex->get_name()
+      << "Unhandled internal format for " << gtc->get_texture()->get_name()
       << " : " << hex << "0x" << internal_format << dec << "\n";
       << " : " << hex << "0x" << internal_format << dec << "\n";
     return false;
     return false;
   }
   }
 
 
-  // We don't want to call setup_texture() again; that resets too much.
-  // Instead, we'll just set the individual components.
-  tex->set_x_size(width);
-  tex->set_y_size(height);
-  tex->set_z_size(depth);
-  tex->set_component_type(type);
-  tex->set_format(format);
-
-#ifdef OPENGLES
-  if (true) {
-#else
-  if (target != GL_TEXTURE_BUFFER) {
-#endif
-    tex->set_wrap_u(get_panda_wrap_mode(wrap_u));
-    tex->set_wrap_v(get_panda_wrap_mode(wrap_v));
-    tex->set_wrap_w(get_panda_wrap_mode(wrap_w));
-    tex->set_minfilter(get_panda_filter_type(minfilter));
-    //tex->set_magfilter(get_panda_filter_type(magfilter));
+  return true;
+}
 
 
+/**
+ * Called from extract_texture_data(), this returns the size of a texture to
+ * be extracted.
+ *
+ * Requires the texture to be bound to the given target.
+ */
 #ifndef OPENGLES
 #ifndef OPENGLES
-    tex->set_border_color(LColor(border_color[0], border_color[1],
-                                 border_color[2], border_color[3]));
-#endif
-  }
-
-  PTA_uchar image;
-  size_t page_size = 0;
+size_t CLP(GraphicsStateGuardian)::
+get_extracted_texture_page_size(Texture *tex, GLenum target,
+                                Texture::ComponentType type,
+                                Texture::CompressionMode compression, int n) {
 
 
-  if (!extract_texture_image(image, page_size, tex, target, page_target,
-                             type, compression, 0)) {
-    return false;
-  }
+  if (target == GL_TEXTURE_CUBE_MAP) {
+    // A cube map, compressed or uncompressed.  This we must extract one page
+    // at a time.
 
 
-  tex->set_ram_image(image, compression, page_size);
+    // If the cube map is compressed, we assume that all the compressed pages
+    // are exactly the same size.  OpenGL doesn't make this assumption, but it
+    // happens to be true for all currently extant compression schemes, and it
+    // makes things simpler for us.  (It also makes things much simpler for
+    // the graphics hardware, so it's likely to continue to be true for a
+    // while at least.)
 
 
-  if (gtc->_uses_mipmaps) {
-    // Also get the mipmap levels.
-    GLint num_expected_levels = tex->get_expected_num_mipmap_levels();
-    GLint highest_level = num_expected_levels;
+    size_t page_size = tex->get_expected_ram_mipmap_page_size(n);
 
 
-    if (_supports_texture_max_level) {
-      glGetTexParameteriv(target, GL_TEXTURE_MAX_LEVEL, &highest_level);
-      highest_level = min(highest_level, num_expected_levels);
-    }
-    for (int n = 1; n <= highest_level; ++n) {
-      if (!extract_texture_image(image, page_size, tex, target, page_target,
-                                 type, compression, n)) {
-        return false;
-      }
-      tex->set_ram_mipmap_image(n, image, page_size);
+    if (compression != Texture::CM_off) {
+      GLint image_size;
+      glGetTexLevelParameteriv(GL_TEXTURE_CUBE_MAP_POSITIVE_X, n,
+                               GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &image_size);
+      nassertr(image_size <= (int)page_size, 0);
+      return image_size;
     }
     }
-  }
 
 
-  return true;
+    return page_size;
+
+  } else if (target == GL_TEXTURE_BUFFER) {
+    // In the case of a buffer texture, we need to get it from the buffer.
+    return tex->get_expected_ram_mipmap_page_size(n);
+
+  } else if (compression == Texture::CM_off) {
+    // An uncompressed 1-d, 2-d, or 3-d texture.
+    return tex->get_expected_ram_mipmap_page_size(n);
+
+  } else {
+    // A compressed 1-d, 2-d, or 3-d texture.
+    GLint image_size;
+    glGetTexLevelParameteriv(target, n, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &image_size);
+    return image_size / tex->get_z_size();
+  }
 }
 }
+#endif
 
 
 /**
 /**
  * Called from extract_texture_data(), this gets just the image array for a
  * Called from extract_texture_data(), this gets just the image array for a
  * particular mipmap level (or for the base image).
  * particular mipmap level (or for the base image).
+ *
+ * Note that the target pointer may actually be an offset, in case that there
+ * is a PBO bound.
  */
  */
 bool CLP(GraphicsStateGuardian)::
 bool CLP(GraphicsStateGuardian)::
-extract_texture_image(PTA_uchar &image, size_t &page_size,
-                      Texture *tex, GLenum target, GLenum page_target,
-                      Texture::ComponentType type,
+extract_texture_image(void *pixels, size_t page_size, Texture *tex,
+                      GLenum target, Texture::ComponentType type,
                       Texture::CompressionMode compression, int n) {
                       Texture::CompressionMode compression, int n) {
 #ifdef OPENGLES  // Extracting texture data unsupported in OpenGL ES.
 #ifdef OPENGLES  // Extracting texture data unsupported in OpenGL ES.
   nassert_raise("OpenGL ES does not support extracting texture data");
   nassert_raise("OpenGL ES does not support extracting texture data");
@@ -14307,59 +14532,35 @@ extract_texture_image(PTA_uchar &image, size_t &page_size,
   if (target == GL_TEXTURE_CUBE_MAP) {
   if (target == GL_TEXTURE_CUBE_MAP) {
     // A cube map, compressed or uncompressed.  This we must extract one page
     // A cube map, compressed or uncompressed.  This we must extract one page
     // at a time.
     // at a time.
-
-    // If the cube map is compressed, we assume that all the compressed pages
-    // are exactly the same size.  OpenGL doesn't make this assumption, but it
-    // happens to be true for all currently extant compression schemes, and it
-    // makes things simpler for us.  (It also makes things much simpler for
-    // the graphics hardware, so it's likely to continue to be true for a
-    // while at least.)
-
     GLenum external_format = get_external_image_format(tex);
     GLenum external_format = get_external_image_format(tex);
     GLenum pixel_type = get_component_type(type);
     GLenum pixel_type = get_component_type(type);
-    page_size = tex->get_expected_ram_mipmap_page_size(n);
-
-    if (compression != Texture::CM_off) {
-      GLint image_size;
-      glGetTexLevelParameteriv(page_target, n,
-                                  GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &image_size);
-      nassertr(image_size <= (int)page_size, false);
-      page_size = image_size;
-    }
-
-    image = PTA_uchar::empty_array(page_size * 6);
 
 
     for (int z = 0; z < 6; ++z) {
     for (int z = 0; z < 6; ++z) {
-      page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + z;
+      GLenum page_target = GL_TEXTURE_CUBE_MAP_POSITIVE_X + z;
 
 
       if (compression == Texture::CM_off) {
       if (compression == Texture::CM_off) {
         glGetTexImage(page_target, n, external_format, pixel_type,
         glGetTexImage(page_target, n, external_format, pixel_type,
-                         image.p() + z * page_size);
+                      (char *)pixels + z * page_size);
       } else {
       } else {
-        _glGetCompressedTexImage(page_target, 0, image.p() + z * page_size);
+        _glGetCompressedTexImage(page_target, 0, (char *)pixels + z * page_size);
       }
       }
     }
     }
 
 
 #ifndef OPENGLES
 #ifndef OPENGLES
   } else if (target == GL_TEXTURE_BUFFER) {
   } else if (target == GL_TEXTURE_BUFFER) {
     // In the case of a buffer texture, we need to get it from the buffer.
     // In the case of a buffer texture, we need to get it from the buffer.
-    image = PTA_uchar::empty_array(tex->get_expected_ram_mipmap_image_size(n));
-    _glGetBufferSubData(target, 0, image.size(), image.p());
+    _glGetBufferSubData(target, 0, page_size, pixels);
 #endif
 #endif
 
 
   } else if (compression == Texture::CM_off) {
   } else if (compression == Texture::CM_off) {
     // An uncompressed 1-d, 2-d, or 3-d texture.
     // An uncompressed 1-d, 2-d, or 3-d texture.
-    image = PTA_uchar::empty_array(tex->get_expected_ram_mipmap_image_size(n));
     GLenum external_format = get_external_image_format(tex);
     GLenum external_format = get_external_image_format(tex);
     GLenum pixel_type = get_component_type(type);
     GLenum pixel_type = get_component_type(type);
-    glGetTexImage(target, n, external_format, pixel_type, image.p());
+    glGetTexImage(target, n, external_format, pixel_type, pixels);
 
 
   } else {
   } else {
     // A compressed 1-d, 2-d, or 3-d texture.
     // A compressed 1-d, 2-d, or 3-d texture.
-    GLint image_size;
-    glGetTexLevelParameteriv(target, n, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &image_size);
-    page_size = image_size / tex->get_z_size();
-    image = PTA_uchar::empty_array(image_size);
+    size_t image_size = page_size * tex->get_z_size();
 
 
     // Some drivers (ATI!) seem to try to overstuff more bytes in the array
     // Some drivers (ATI!) seem to try to overstuff more bytes in the array
     // than they asked us to allocate (that is, more bytes than
     // than they asked us to allocate (that is, more bytes than
@@ -14385,7 +14586,7 @@ extract_texture_image(PTA_uchar &image, size_t &page_size,
       memset(buffer + image_size, token, extra_space);
       memset(buffer + image_size, token, extra_space);
 #endif
 #endif
       _glGetCompressedTexImage(target, n, buffer);
       _glGetCompressedTexImage(target, n, buffer);
-      memcpy(image.p(), buffer, image_size);
+      memcpy(pixels, buffer, image_size);
 #ifndef NDEBUG
 #ifndef NDEBUG
       int count = extra_space;
       int count = extra_space;
       while (count > 0 && buffer[image_size + count - 1] == token) {
       while (count > 0 && buffer[image_size + count - 1] == token) {
@@ -14404,7 +14605,7 @@ extract_texture_image(PTA_uchar &image, size_t &page_size,
       nassertr(count != extra_space, true)
       nassertr(count != extra_space, true)
 #endif  // NDEBUG
 #endif  // NDEBUG
     } else {
     } else {
-      _glGetCompressedTexImage(target, n, image.p());
+      _glGetCompressedTexImage(target, n, pixels);
     }
     }
   }
   }
 
 

+ 22 - 3
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -334,6 +334,10 @@ public:
   virtual bool update_texture(TextureContext *tc, bool force);
   virtual bool update_texture(TextureContext *tc, bool force);
   virtual void release_texture(TextureContext *tc);
   virtual void release_texture(TextureContext *tc);
   virtual bool extract_texture_data(Texture *tex);
   virtual bool extract_texture_data(Texture *tex);
+#ifndef OPENGLES
+  virtual TransferBufferContext *
+    async_extract_textures(const pvector<PT(Texture)> &textures);
+#endif
 
 
 #ifndef OPENGLES_1
 #ifndef OPENGLES_1
   virtual SamplerContext *prepare_sampler(const SamplerState &sampler);
   virtual SamplerContext *prepare_sampler(const SamplerState &sampler);
@@ -617,9 +621,19 @@ protected:
   size_t get_texture_memory_size(CLP(TextureContext) *gtc);
   size_t get_texture_memory_size(CLP(TextureContext) *gtc);
   void check_nonresident_texture(BufferContextChain &chain);
   void check_nonresident_texture(BufferContextChain &chain);
   bool do_extract_texture_data(CLP(TextureContext) *gtc);
   bool do_extract_texture_data(CLP(TextureContext) *gtc);
-  bool extract_texture_image(PTA_uchar &image, size_t &page_size,
-           Texture *tex, GLenum target, GLenum page_target,
-           Texture::ComponentType type,
+  bool extract_texture_parameters(CLP(TextureContext) *gtc,
+                                  int &width, int &height, int &depth,
+                                  SamplerState &sampler,
+                                  Texture::ComponentType &type,
+                                  Texture::Format &format,
+                                  Texture::CompressionMode &compression);
+#ifndef OPENGLES
+  size_t get_extracted_texture_page_size(
+           Texture *tex, GLenum target, Texture::ComponentType type,
+           Texture::CompressionMode compression, int n);
+#endif
+  bool extract_texture_image(void *pixels, size_t page_size,
+           Texture *tex, GLenum target, Texture::ComponentType type,
            Texture::CompressionMode compression, int n);
            Texture::CompressionMode compression, int n);
 
 
 #ifdef SUPPORT_FIXED_FUNCTION
 #ifdef SUPPORT_FIXED_FUNCTION
@@ -958,6 +972,11 @@ public:
   PFNGLGETQUERYOBJECTUI64VPROC _glGetQueryObjectui64v;
   PFNGLGETQUERYOBJECTUI64VPROC _glGetQueryObjectui64v;
 
 
   PFNGLGETINTEGER64VPROC _glGetInteger64v;
   PFNGLGETINTEGER64VPROC _glGetInteger64v;
+
+  bool _supports_fence_sync;
+  PFNGLFENCESYNCPROC _glFenceSync;
+  PFNGLDELETESYNCPROC _glDeleteSync;
+  PFNGLGETSYNCIVPROC _glGetSynciv;
 #endif
 #endif
 
 
   PFNGLACTIVESTENCILFACEEXTPROC _glActiveStencilFaceEXT;
   PFNGLACTIVESTENCILFACEEXTPROC _glActiveStencilFaceEXT;

+ 20 - 0
panda/src/glstuff/glPixelPackBufferContext_src.I

@@ -0,0 +1,20 @@
+/**
+ * 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 glPixelPackBufferContext_src.I
+ * @author rdb
+ * @date 2019-10-02
+ */
+
+/**
+ *
+ */
+INLINE CLP(PixelPackBufferContext)::
+CLP(PixelPackBufferContext)(CLP(GraphicsStateGuardian) *glgsg) :
+  _glgsg(glgsg) {
+}

+ 115 - 0
panda/src/glstuff/glPixelPackBufferContext_src.cxx

@@ -0,0 +1,115 @@
+/**
+ * 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 glPixelPackBufferContext_src.cxx
+ * @author rdb
+ * @date 2019-10-02
+ */
+
+#ifndef OPENGLES
+
+TypeHandle CLP(PixelPackBufferContext)::_type_handle;
+
+/**
+ * Deletes the buffer.
+ */
+CLP(PixelPackBufferContext)::
+~CLP(PixelPackBufferContext)() {
+  if (_index != 0) {
+    _glgsg->_glDeleteBuffers(1, &_index);
+    _index = 0;
+  }
+}
+
+/**
+ * Returns true if the extraction has been performed.  This may return false
+ * positives if sync objects are not supported.
+ */
+bool CLP(PixelPackBufferContext)::
+is_transfer_done() const {
+#ifndef OPENGLES
+  if (_sync == 0) {
+    return true;
+  }
+  GLint status = GL_UNSIGNALED;
+  _glgsg->_glGetSynciv(_sync, GL_SYNC_STATUS, 1, nullptr, &status);
+  return (status == GL_SIGNALED);
+#else
+  return true;
+#endif
+}
+
+/**
+ * Finishes the extraction process.
+ */
+void CLP(PixelPackBufferContext)::
+finish_transfer() {
+  nassertv(_index != 0);
+
+  _glgsg->_glBindBuffer(GL_PIXEL_PACK_BUFFER, _index);
+
+  void *data = _glgsg->_glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
+
+  for (ExtractTexture &et : _textures) {
+    et._texture->set_x_size(et._width);
+    et._texture->set_y_size(et._height);
+    et._texture->set_z_size(et._depth);
+    et._texture->set_component_type(et._type);
+    et._texture->set_format(et._format);
+
+    unsigned char *begin = (unsigned char *)data + et._offset;
+    unsigned char *end = (unsigned char *)begin + (et._page_size * et._depth);
+    et._texture->set_ram_image(PTA_uchar(begin, end, Texture::get_class_type()),
+                               et._compression, et._page_size);
+
+    //PTA_uchar image = PTA_uchar::empty_array(et._page_size * et._depth);
+    //_glgsg->_glGetBufferSubData(GL_PIXEL_PACK_BUFFER, et._offset, image.size(), image.p());
+    //et._texture->set_ram_image(std::move(image), et._compression, et._page_size);
+  }
+
+  _glgsg->_glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
+
+  notify_done();
+
+  _glgsg->_glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+
+  // Delete the fence, which is no longer needed.
+  if (_sync != 0) {
+    _glgsg->_glDeleteSync(_sync);
+    _sync = 0;
+  }
+
+  _textures.clear();
+}
+
+/**
+ * Evicts the page from the LRU.  Called internally when the LRU determines
+ * that it is full.  May also be called externally when necessary to
+ * explicitly evict the page.
+ *
+ * It is legal for this method to either evict the page as requested, do
+ * nothing (in which case the eviction will be requested again at the next
+ * epoch), or requeue itself on the tail of the queue (in which case the
+ * eviction will be requested again much later).
+ */
+/*void CLP(PixelPackBufferContext)::
+evict_lru() {
+  if (!_textures.empty()) {
+    finish_extraction();
+  }
+
+  dequeue_lru();
+
+  // Free the buffer.
+  _glgsg->_glDeleteBuffers(1, &_index);
+
+  update_data_size_bytes(0);
+  mark_unloaded();
+}*/
+
+#endif  // !OPENGLES

+ 71 - 0
panda/src/glstuff/glPixelPackBufferContext_src.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 glPixelPackBufferContext_src.h
+ * @author rdb
+ * @date 2019-10-02
+ */
+
+#include "pandabase.h"
+#include "deletedChain.h"
+#include "transferBufferContext.h"
+
+#ifndef OPENGLES
+
+/**
+ * This class manages a series of texture extractions that are performed in a
+ * single batch.
+ */
+class EXPCL_GL CLP(PixelPackBufferContext) final : public TransferBufferContext {
+public:
+  INLINE CLP(PixelPackBufferContext)(CLP(GraphicsStateGuardian) *glgsg);
+  virtual ~CLP(PixelPackBufferContext)();
+
+  virtual bool is_transfer_done() const override;
+  virtual void finish_transfer() override;
+
+  CLP(GraphicsStateGuardian) *const _glgsg;
+  GLuint _index = 0;
+  GLsync _sync = 0;
+
+  struct ExtractTexture {
+    PT(Texture) _texture;
+    uintptr_t _offset = 0;
+    size_t _page_size = 0;
+    int _width, _height, _depth, _num_views;
+    SamplerState _sampler;
+    Texture::ComponentType _type = Texture::T_unsigned_byte;
+    Texture::Format _format = Texture::F_rgb;
+    Texture::CompressionMode _compression = Texture::CM_off;
+  };
+  pvector<ExtractTexture> _textures;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TransferBufferContext::init_type();
+    register_type(_type_handle, CLASSPREFIX_QUOTED "PixelPackBufferContext",
+                  TransferBufferContext::get_class_type());
+  }
+  virtual TypeHandle get_type() const override {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() override {
+    init_type();
+    return get_class_type();
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "glPixelPackBufferContext_src.I"
+
+#endif  // !OPENGLES

+ 1 - 0
panda/src/glstuff/glstuff_src.cxx

@@ -25,6 +25,7 @@
 #include "glOcclusionQueryContext_src.cxx"
 #include "glOcclusionQueryContext_src.cxx"
 #include "glTimerQueryContext_src.cxx"
 #include "glTimerQueryContext_src.cxx"
 #include "glLatencyQueryContext_src.cxx"
 #include "glLatencyQueryContext_src.cxx"
+#include "glPixelPackBufferContext_src.cxx"
 #include "glGeomContext_src.cxx"
 #include "glGeomContext_src.cxx"
 #include "glGeomMunger_src.cxx"
 #include "glGeomMunger_src.cxx"
 #include "glShaderContext_src.cxx"
 #include "glShaderContext_src.cxx"

+ 1 - 0
panda/src/glstuff/glstuff_src.h

@@ -37,6 +37,7 @@
 #include "glOcclusionQueryContext_src.h"
 #include "glOcclusionQueryContext_src.h"
 #include "glTimerQueryContext_src.h"
 #include "glTimerQueryContext_src.h"
 #include "glLatencyQueryContext_src.h"
 #include "glLatencyQueryContext_src.h"
+#include "glPixelPackBufferContext_src.h"
 #include "glGeomContext_src.h"
 #include "glGeomContext_src.h"
 #include "glGeomMunger_src.h"
 #include "glGeomMunger_src.h"
 #include "glShaderContext_src.h"
 #include "glShaderContext_src.h"

+ 3 - 1
panda/src/gobj/config_gobj.cxx

@@ -44,11 +44,12 @@
 #include "queryContext.h"
 #include "queryContext.h"
 #include "sliderTable.h"
 #include "sliderTable.h"
 #include "texture.h"
 #include "texture.h"
+#include "textureContext.h"
 #include "texturePoolFilter.h"
 #include "texturePoolFilter.h"
 #include "textureReloadRequest.h"
 #include "textureReloadRequest.h"
 #include "textureStage.h"
 #include "textureStage.h"
-#include "textureContext.h"
 #include "timerQueryContext.h"
 #include "timerQueryContext.h"
+#include "transferBufferContext.h"
 #include "samplerContext.h"
 #include "samplerContext.h"
 #include "samplerState.h"
 #include "samplerState.h"
 #include "shader.h"
 #include "shader.h"
@@ -594,6 +595,7 @@ ConfigureFn(config_gobj) {
   TextureReloadRequest::init_type();
   TextureReloadRequest::init_type();
   TextureStage::init_type();
   TextureStage::init_type();
   TimerQueryContext::init_type();
   TimerQueryContext::init_type();
+  TransferBufferContext::init_type();
   TransformBlend::init_type();
   TransformBlend::init_type();
   TransformBlendTable::init_type();
   TransformBlendTable::init_type();
   TransformTable::init_type();
   TransformTable::init_type();

+ 1 - 0
panda/src/gobj/p3gobj_composite2.cxx

@@ -21,6 +21,7 @@
 #include "textureStage.cxx"
 #include "textureStage.cxx"
 #include "textureStagePool.cxx"
 #include "textureStagePool.cxx"
 #include "timerQueryContext.cxx"
 #include "timerQueryContext.cxx"
+#include "transferBufferContext.cxx"
 #include "transformBlend.cxx"
 #include "transformBlend.cxx"
 #include "transformBlendTable.cxx"
 #include "transformBlendTable.cxx"
 #include "transformTable.cxx"
 #include "transformTable.cxx"

+ 69 - 8
panda/src/gobj/preparedGraphicsObjects.cxx

@@ -13,6 +13,7 @@
 
 
 #include "preparedGraphicsObjects.h"
 #include "preparedGraphicsObjects.h"
 #include "textureContext.h"
 #include "textureContext.h"
+#include "transferBufferContext.h"
 #include "vertexBufferContext.h"
 #include "vertexBufferContext.h"
 #include "indexBufferContext.h"
 #include "indexBufferContext.h"
 #include "texture.h"
 #include "texture.h"
@@ -214,6 +215,20 @@ enqueue_texture_future(Texture *tex) {
   return fut;
   return fut;
 }
 }
 
 
+/**
+ * Indicates that a texture would like to be put on the list to be extracted
+ * when the GSG is next ready to do this (presumably at the next frame).
+ */
+PT(AsyncFuture) PreparedGraphicsObjects::
+extract_texture(Texture *tex) {
+  ReMutexHolder holder(_lock);
+  if (_extracted == nullptr) {
+    _extracted = new AsyncFuture;
+  }
+  _extracted_textures.push_back(tex);
+  return _extracted;
+}
+
 /**
 /**
  * Returns true if the texture has been queued on this GSG, false otherwise.
  * Returns true if the texture has been queued on this GSG, false otherwise.
  */
  */
@@ -1499,18 +1514,49 @@ cancel() {
 }
 }
 
 
 /**
 /**
- * This is called by the GraphicsStateGuardian to indicate that it is about to
- * begin processing of the frame.
- *
- * Any texture contexts that were previously passed to release_texture() are
- * actually passed to the GSG to be freed at this point; textures that were
- * previously passed to prepare_texture are actually loaded.
+ * Actually processes the queue of pending extractions.  Usually called by
+ * begin_frame.
  */
  */
 void PreparedGraphicsObjects::
 void PreparedGraphicsObjects::
-begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
+extract_pending_now(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
+  ReMutexHolder holder(_lock, current_thread);
+
+  // First, check if any extractions in progress were done.
+  while (!_transfer_buffers.empty()) {
+    TransferBufferContext *tbc = _transfer_buffers.front();
+    if (tbc->is_transfer_done()) {
+      tbc->finish_transfer();
+      delete tbc;
+      _transfer_buffers.pop_front();
+    } else {
+      // If the first pending one isn't done, the rest isn't either.
+      break;
+    }
+  }
+
+  // Now, process any pending new extractions.
+  if (!_extracted_textures.empty()) {
+    TransferBufferContext *tbc = gsg->async_extract_textures(_extracted_textures);
+    if (tbc != nullptr) {
+      tbc->_future = std::move(_extracted);
+      _transfer_buffers.push_back(tbc);
+    } else {
+      // It was performed synchronously.
+      _extracted->set_result(nullptr);
+    }
+    _extracted.clear();
+    _extracted_textures.clear();
+  }
+}
+
+/**
+ * Release all the textures, geoms, and buffers awaiting release.
+ * Usually called by begin_frame.
+ */
+void PreparedGraphicsObjects::
+release_pending_now(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
   ReMutexHolder holder(_lock, current_thread);
   ReMutexHolder holder(_lock, current_thread);
 
 
-  // First, release all the textures, geoms, and buffers awaiting release.
   if (!_released_textures.empty()) {
   if (!_released_textures.empty()) {
     Textures::iterator tci;
     Textures::iterator tci;
     for (tci = _released_textures.begin();
     for (tci = _released_textures.begin();
@@ -1574,6 +1620,21 @@ begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
   }
   }
 
 
   _released_index_buffers.clear();
   _released_index_buffers.clear();
+}
+
+/**
+ * This is called by the GraphicsStateGuardian to indicate that it is about to
+ * begin processing of the frame.
+ *
+ * Any texture contexts that were previously passed to release_texture() are
+ * actually passed to the GSG to be freed at this point; textures that were
+ * previously passed to prepare_texture are actually loaded.
+ */
+void PreparedGraphicsObjects::
+begin_frame(GraphicsStateGuardianBase *gsg, Thread *current_thread) {
+  ReMutexHolder holder(_lock, current_thread);
+  extract_pending_now(gsg, current_thread);
+  release_pending_now(gsg, current_thread);
 
 
   // Reset the residency trackers.
   // Reset the residency trackers.
   _texture_residency.begin_frame(current_thread);
   _texture_residency.begin_frame(current_thread);

+ 14 - 1
panda/src/gobj/preparedGraphicsObjects.h

@@ -30,8 +30,10 @@
 #include "bufferResidencyTracker.h"
 #include "bufferResidencyTracker.h"
 #include "adaptiveLru.h"
 #include "adaptiveLru.h"
 #include "asyncFuture.h"
 #include "asyncFuture.h"
+#include "savedContext.h"
 
 
 class TextureContext;
 class TextureContext;
+class TransferBufferContext;
 class SamplerContext;
 class SamplerContext;
 class GeomContext;
 class GeomContext;
 class ShaderContext;
 class ShaderContext;
@@ -39,7 +41,6 @@ class VertexBufferContext;
 class IndexBufferContext;
 class IndexBufferContext;
 class BufferContext;
 class BufferContext;
 class GraphicsStateGuardianBase;
 class GraphicsStateGuardianBase;
-class SavedContext;
 
 
 /**
 /**
  * A table of objects that are saved within the graphics context for reference
  * A table of objects that are saved within the graphics context for reference
@@ -210,6 +211,11 @@ public:
   //PT(EnqueuedObject) enqueue_index_buffer_future(GeomPrimitive *data);
   //PT(EnqueuedObject) enqueue_index_buffer_future(GeomPrimitive *data);
   //PT(EnqueuedObject) enqueue_shader_buffer_future(ShaderBuffer *data);
   //PT(EnqueuedObject) enqueue_shader_buffer_future(ShaderBuffer *data);
 
 
+  PT(AsyncFuture) extract_texture(Texture *tex);
+
+  void extract_pending_now(GraphicsStateGuardianBase *gsg, Thread *current_thread);
+  void release_pending_now(GraphicsStateGuardianBase *gsg, Thread *current_thread);
+
   void begin_frame(GraphicsStateGuardianBase *gsg,
   void begin_frame(GraphicsStateGuardianBase *gsg,
                    Thread *current_thread);
                    Thread *current_thread);
   void end_frame(Thread *current_thread);
   void end_frame(Thread *current_thread);
@@ -278,6 +284,13 @@ private:
   Buffers _prepared_shader_buffers, _released_shader_buffers;
   Buffers _prepared_shader_buffers, _released_shader_buffers;
   EnqueuedShaderBuffers _enqueued_shader_buffers;
   EnqueuedShaderBuffers _enqueued_shader_buffers;
 
 
+  pvector<PT(Texture)> _extracted_textures;
+  pdeque<TransferBufferContext *> _transfer_buffers;
+
+  // This is the future used to signal the next batch of extractions.  The
+  // moment we perform such an extraction, we replace this with a new object.
+  PT(AsyncFuture) _extracted;
+
   BufferCache _vertex_buffer_cache;
   BufferCache _vertex_buffer_cache;
   BufferCacheLRU _vertex_buffer_cache_lru;
   BufferCacheLRU _vertex_buffer_cache_lru;
   size_t _vertex_buffer_cache_size;
   size_t _vertex_buffer_cache_size;

+ 22 - 0
panda/src/gobj/texture.cxx

@@ -1618,6 +1618,28 @@ release_all() {
   return num_freed;
   return num_freed;
 }
 }
 
 
+/**
+ * Schedules the texture for download from texture memory.  In anticipation,
+ * the current RAM image is cleared.
+ *
+ * It is an error to call this if the texture has not yet been prepared on the
+ * GPU.  In that case, the future is cancelled.
+ */
+PT(AsyncFuture) Texture::
+extract(PreparedGraphicsObjects *prepared_objects) {
+  MutexHolder holder(_lock);
+  PreparedViews::iterator pvi;
+  pvi = _prepared_views.find(prepared_objects);
+  if (pvi == _prepared_views.end()) {
+    PT(AsyncFuture) fut = new AsyncFuture;
+    fut->cancel();
+    return fut;
+  }
+
+  clear_ram_image();
+  return prepared_objects->extract_texture(this);
+}
+
 /**
 /**
  * Not to be confused with write(Filename), this method simply describes the
  * Not to be confused with write(Filename), this method simply describes the
  * texture properties.
  * texture properties.

+ 2 - 0
panda/src/gobj/texture.h

@@ -530,6 +530,8 @@ PUBLISHED:
   bool get_active(PreparedGraphicsObjects *prepared_objects) const;
   bool get_active(PreparedGraphicsObjects *prepared_objects) const;
   bool get_resident(PreparedGraphicsObjects *prepared_objects) const;
   bool get_resident(PreparedGraphicsObjects *prepared_objects) const;
 
 
+  PT(AsyncFuture) extract(PreparedGraphicsObjects *prepared_objects);
+
   bool release(PreparedGraphicsObjects *prepared_objects);
   bool release(PreparedGraphicsObjects *prepared_objects);
   int release_all();
   int release_all();
 
 

+ 20 - 0
panda/src/gobj/transferBufferContext.I

@@ -0,0 +1,20 @@
+/**
+ * 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 transferBufferContext.I
+ * @author rdb
+ * @date 2019-10-03
+ */
+
+/**
+ * Called by an implementation when the extraction has finished.
+ */
+INLINE void TransferBufferContext::
+notify_done() {
+  _future->set_result(nullptr);
+}

+ 16 - 0
panda/src/gobj/transferBufferContext.cxx

@@ -0,0 +1,16 @@
+/**
+ * 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 transferBufferContext.I
+ * @author rdb
+ * @date 2019-10-03
+ */
+
+#include "transferBufferContext.h"
+
+TypeHandle TransferBufferContext::_type_handle;

+ 58 - 0
panda/src/gobj/transferBufferContext.h

@@ -0,0 +1,58 @@
+/**
+ * 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 transferBufferContext.h
+ * @author rdb
+ * @date 2019-10-03
+ */
+
+#ifndef TRANSFERBUFFERCONTEXT_H
+#define TRANSFERBUFFERCONTEXT_H
+
+#include "pandabase.h"
+#include "savedContext.h"
+
+/**
+ * This is a staging buffer used for downloading textures from the GPU.
+ * It does not inherit from BufferContext because this type of buffer is
+ * ephemeral; it does not make much sense to put it on an LRU chain.
+ */
+class EXPCL_PANDA_GOBJ TransferBufferContext : public SavedContext {
+public:
+  virtual bool is_transfer_done() const=0;
+  virtual void finish_transfer()=0;
+
+protected:
+  INLINE void notify_done();
+
+private:
+  PT(AsyncFuture) _future;
+
+  friend class PreparedGraphicsObjects;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    SavedContext::init_type();
+    register_type(_type_handle, "TransferBufferContext",
+                  SavedContext::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 "transferBufferContext.I"
+
+#endif

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

@@ -53,6 +53,7 @@ class SceneSetup;
 class PreparedGraphicsObjects;
 class PreparedGraphicsObjects;
 class GraphicsOutput;
 class GraphicsOutput;
 class Texture;
 class Texture;
+class TransferBufferContext;
 class TextureContext;
 class TextureContext;
 class SamplerContext;
 class SamplerContext;
 class SamplerState;
 class SamplerState;
@@ -148,6 +149,8 @@ public:
   virtual bool update_texture(TextureContext *tc, bool force)=0;
   virtual bool update_texture(TextureContext *tc, bool force)=0;
   virtual void release_texture(TextureContext *tc)=0;
   virtual void release_texture(TextureContext *tc)=0;
   virtual bool extract_texture_data(Texture *tex)=0;
   virtual bool extract_texture_data(Texture *tex)=0;
+  virtual TransferBufferContext *
+    async_extract_textures(const pvector<PT(Texture)> &textures)=0;
 
 
   virtual SamplerContext *prepare_sampler(const SamplerState &sampler)=0;
   virtual SamplerContext *prepare_sampler(const SamplerState &sampler)=0;
   virtual void release_sampler(SamplerContext *sc)=0;
   virtual void release_sampler(SamplerContext *sc)=0;