Răsfoiți Sursa

Vulkan: texture barrier management, copy-to-texture, copy-to-ram, many more things

rdb 9 ani în urmă
părinte
comite
9896a2e19f

+ 24 - 7
panda/src/display/graphicsStateGuardian.cxx

@@ -557,9 +557,16 @@ release_texture(TextureContext *) {
  * This method should only be called by the GraphicsEngine.  Do not call it
  * directly; call GraphicsEngine::extract_texture_data() instead.
  *
- * This method will be called in the draw thread to download the texture
- * memory's image into its ram_image value.  It returns true on success, false
- * otherwise.
+ * Please note that this may be a very expensive operation as it stalls the
+ * graphics pipeline while waiting for the rendered results to become
+ * available.  The graphics implementation may choose to defer writing the ram
+ * image until the next end_frame() call.
+ *
+ * This method will be called in the draw thread between begin_frame() and
+ * end_frame() to download the texture memory's image into its ram_image
+ * value.  It may not be called between begin_scene() and end_scene().
+ *
+ * @return true on success, false otherwise
  */
 bool GraphicsStateGuardian::
 extract_texture_data(Texture *) {
@@ -2635,7 +2642,11 @@ do_issue_light() {
  * Copy the pixels within the indicated display region from the framebuffer
  * into texture memory.
  *
- * If z > -1, it is the cube map index into which to copy.
+ * This should be called between begin_frame() and end_frame(), but not be
+ * called between begin_scene() and end_scene().
+ *
+ * @param z if z > -1, it is the cube map index into which to copy
+ * @return true on success, false on failure
  */
 bool GraphicsStateGuardian::
 framebuffer_copy_to_texture(Texture *, int, int, const DisplayRegion *,
@@ -2643,13 +2654,19 @@ framebuffer_copy_to_texture(Texture *, int, int, const DisplayRegion *,
   return false;
 }
 
-
 /**
  * Copy the pixels within the indicated display region from the framebuffer
- * into system memory, not texture memory.  Returns true on success, false on
- * failure.
+ * into system memory, not texture memory.  Please note that this may be a
+ * very expensive operation as it stalls the graphics pipeline while waiting
+ * for the rendered results to become available.  The graphics implementation
+ * may choose to defer writing the ram image until the next end_frame() call.
  *
  * This completely redefines the ram image of the indicated texture.
+ *
+ * This should be called between begin_frame() and end_frame(), but not be
+ * called between begin_scene() and end_scene().
+ *
+ * @return true on success, false on failure
  */
 bool GraphicsStateGuardian::
 framebuffer_copy_to_ram(Texture *, int, int, const DisplayRegion *,

+ 2 - 2
panda/src/gobj/shader.cxx

@@ -2797,8 +2797,8 @@ spirv_analyze_shader(const string &data) {
   ShaderType shader_type = ST_none;
 
   while (words < end) {
-    uint16_t wcount = words[0] >> 16;
-    SpvOp opcode = (SpvOp)(words[0] & 0xffff);
+    uint16_t wcount = words[0] >> SpvWordCountShift;
+    SpvOp opcode = (SpvOp)(words[0] & SpvOpCodeMask);
 
     switch (opcode) {
     case SpvOpMemoryModel:

+ 1 - 0
panda/src/vulkandisplay/p3vulkandisplay_composite1.cxx

@@ -1,4 +1,5 @@
 #include "config_vulkandisplay.cxx"
+#include "vulkanGraphicsBuffer.cxx"
 #include "vulkanGraphicsPipe.cxx"
 #include "vulkanGraphicsStateGuardian.cxx"
 #include "vulkanGraphicsWindow.cxx"

+ 12 - 0
panda/src/vulkandisplay/vulkanGraphicsBuffer.I

@@ -0,0 +1,12 @@
+/**
+ * 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 vulkanGraphicsBuffer.I
+ * @author rdb
+ * @date 2016-04-13
+ */

+ 602 - 0
panda/src/vulkandisplay/vulkanGraphicsBuffer.cxx

@@ -0,0 +1,602 @@
+/**
+ * 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 vulkanGraphicsBuffer.cxx
+ * @author rdb
+ * @date 2016-04-13
+ */
+
+#include "vulkanGraphicsBuffer.h"
+#include "vulkanGraphicsStateGuardian.h"
+
+TypeHandle VulkanGraphicsBuffer::_type_handle;
+
+/**
+ *
+ */
+VulkanGraphicsBuffer::
+VulkanGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
+                     const string &name,
+                     const FrameBufferProperties &fb_prop,
+                     const WindowProperties &win_prop,
+                     int flags,
+                     GraphicsStateGuardian *gsg,
+                     GraphicsOutput *host) :
+  GraphicsBuffer(engine, pipe, name, fb_prop, win_prop, flags, gsg, host) {
+}
+
+/**
+ *
+ */
+VulkanGraphicsBuffer::
+~VulkanGraphicsBuffer() {
+}
+
+/**
+ * Clears the entire framebuffer before rendering, according to the settings
+ * of get_color_clear_active() and get_depth_clear_active() (inherited from
+ * DrawableRegion).
+ *
+ * This function is called only within the draw thread.
+ */
+void GraphicsOutput::
+clear(Thread *current_thread) {
+  // We do the clear in begin_frame(), and the validation layers don't like it
+  // if an extra clear is being done at the beginning of a frame.  That's why
+  // this is empty for now.  Need a cleaner solution for this.
+}
+
+/**
+ * This function will be called within the draw thread before beginning
+ * rendering for a given frame.  It should do whatever setup is required, and
+ * return true if the frame should be rendered, or false if it should be
+ * skipped.
+ */
+bool VulkanGraphicsBuffer::
+begin_frame(FrameMode mode, Thread *current_thread) {
+  begin_frame_spam(mode);
+  if (_gsg == (GraphicsStateGuardian *)NULL) {
+    return false;
+  }
+
+  if (mode != FM_render) {
+    return true;
+  }
+
+  VulkanGraphicsStateGuardian *vkgsg;
+  DCAST_INTO_R(vkgsg, _gsg, false);
+  //vkgsg->reset_if_new();
+
+  if (_current_clear_mask != _clear_mask) {
+    // The clear flags have changed.  Recreate the render pass.  Note that the
+    // clear flags don't factor into render pass compatibility, so we don't
+    // need to recreate the framebuffer.
+    vkQueueWaitIdle(vkgsg->_queue);
+    setup_render_pass();
+  }
+
+  if (_framebuffer_size != _size) {
+    // Uh-oh, the window must have resized.  Recreate the framebuffer.
+    // Before destroying the old, make sure the queue is no longer rendering
+    // anything to it.
+    vkQueueWaitIdle(vkgsg->_queue);
+    destroy_framebuffer();
+    if (!create_framebuffer()) {
+      return false;
+    }
+  }
+
+  // Instruct the GSG that we are commencing a new frame.  This will cause it
+  // to create a command buffer.
+  vkgsg->set_current_properties(&get_fb_properties());
+  if (!vkgsg->begin_frame(current_thread)) {
+    return false;
+  }
+
+  /*if (mode == FM_render) {
+    clear_cube_map_selection();
+  }*/
+
+  // Now that we have a command buffer, start our render pass.  First
+  // transition the swapchain images into the valid state for rendering into.
+  VkCommandBuffer cmd = vkgsg->_cmd;
+
+  VkClearValue clears[2];
+  LColor clear_color = get_clear_color();
+  clears[0].color.float32[0] = clear_color[0];
+  clears[0].color.float32[1] = clear_color[1];
+  clears[0].color.float32[2] = clear_color[2];
+  clears[0].color.float32[3] = clear_color[3];
+
+  VkRenderPassBeginInfo begin_info;
+  begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+  begin_info.pNext = NULL;
+  begin_info.renderPass = _render_pass;
+  begin_info.framebuffer = _framebuffer;
+  begin_info.renderArea.offset.x = 0;
+  begin_info.renderArea.offset.y = 0;
+  begin_info.renderArea.extent.width = _size[0];
+  begin_info.renderArea.extent.height = _size[1];
+  begin_info.clearValueCount = 1;
+  begin_info.pClearValues = clears;
+
+  VkImageMemoryBarrier *barriers = (VkImageMemoryBarrier *)
+    alloca(sizeof(VkImageMemoryBarrier) * _attachments.size());
+
+  for (size_t i = 0; i < _attachments.size(); ++i) {
+    const Attachment &attach = _attachments[i];
+    barriers[i].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+    barriers[i].pNext = NULL;
+    barriers[i].srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
+    barriers[i].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    barriers[i].oldLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+    barriers[i].newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+    barriers[i].srcQueueFamilyIndex = vkgsg->_graphics_queue_family_index;
+    barriers[i].dstQueueFamilyIndex = vkgsg->_graphics_queue_family_index;
+    barriers[i].image = attach._image;
+    barriers[i].subresourceRange.aspectMask = attach._aspect;
+    barriers[i].subresourceRange.baseMipLevel = 0;
+    barriers[i].subresourceRange.levelCount = 1;
+    barriers[i].subresourceRange.baseArrayLayer = 0;
+    barriers[i].subresourceRange.layerCount = 1;
+  }
+
+  vkCmdBeginRenderPass(cmd, &begin_info, VK_SUBPASS_CONTENTS_INLINE);
+  vkgsg->_render_pass = _render_pass;
+
+  return true;
+}
+
+/**
+ * This function will be called within the draw thread after rendering is
+ * completed for a given frame.  It should do whatever finalization is
+ * required.
+ */
+void VulkanGraphicsBuffer::
+end_frame(FrameMode mode, Thread *current_thread) {
+  end_frame_spam(mode);
+
+  if (mode == FM_render) {
+    VulkanGraphicsStateGuardian *vkgsg;
+    DCAST_INTO_V(vkgsg, _gsg);
+    VkCommandBuffer cmd = vkgsg->_cmd;
+    nassertv(cmd != VK_NULL_HANDLE);
+
+    vkCmdEndRenderPass(cmd);
+    vkgsg->_render_pass = VK_NULL_HANDLE;
+
+    if (mode == FM_render) {
+      copy_to_textures();
+    }
+
+    // Note: this will close the command buffer.
+    _gsg->end_frame(current_thread);
+
+    trigger_flip();
+    clear_cube_map_selection();
+  }
+}
+
+/**
+ * Closes the buffer right now.  Called from the window thread.
+ */
+void VulkanGraphicsBuffer::
+close_buffer() {
+  if (!_gsg.is_null()) {
+    VulkanGraphicsStateGuardian *vkgsg;
+    DCAST_INTO_V(vkgsg, _gsg);
+
+    // Wait until the queue is done with any commands that might use the swap
+    // chain, then destroy it.
+    vkQueueWaitIdle(vkgsg->_queue);
+    destroy_framebuffer();
+
+    if (_render_pass != VK_NULL_HANDLE) {
+      vkDestroyRenderPass(vkgsg->_device, _render_pass, NULL);
+      _render_pass = VK_NULL_HANDLE;
+    }
+
+    _gsg.clear();
+  }
+}
+
+/**
+ * Opens the buffer right now.  Called from the window thread.  Returns true
+ * if the buffer is successfully opened, or false if there was a problem.
+ */
+bool VulkanGraphicsBuffer::
+open_buffer() {
+  VulkanGraphicsPipe *vkpipe;
+  DCAST_INTO_R(vkpipe, _pipe, false);
+
+  // Choose a suitable color format.  Sorted in order of preferability,
+  // preferring lower bpps over higher bpps, and preferring formats that pack
+  // bits in fewer channels (because if the user only requests red bits, they
+  // probably would prefer to maximize the amount of red bits rather than to
+  // have bits in other channels that they don't end up using)
+  static const struct {
+    int rgb, r, g, b, a;
+    bool has_float;
+    VkFormat format;
+  } formats[] = {
+    { 8,  8,  0,  0,  0, false, VK_FORMAT_R8_UNORM},
+    {16,  8,  8,  0,  0, false, VK_FORMAT_R8G8_UNORM},
+    {16,  5,  6,  5,  0, false, VK_FORMAT_R5G6B5_UNORM_PACK16},
+    {15,  5,  5,  5,  1, false, VK_FORMAT_A1R5G5B5_UNORM_PACK16},
+    {16, 16,  0,  0,  0,  true, VK_FORMAT_R16_SFLOAT},
+    {24,  8,  8,  8,  8, false, VK_FORMAT_R8G8B8A8_UNORM},
+    {32, 16, 16,  0,  0, false, VK_FORMAT_R16G16_SFLOAT},
+    {30, 10, 10, 10,  2, false, VK_FORMAT_A2B10G10R10_UNORM_PACK32},
+    {48, 16, 16, 16, 16,  true, VK_FORMAT_R16G16B16A16_SFLOAT},
+    {32, 32,  0,  0,  0,  true, VK_FORMAT_R32_SFLOAT},
+    {64, 32, 32,  0,  0,  true, VK_FORMAT_R32G32_SFLOAT},
+    {96, 32, 32, 32, 32,  true, VK_FORMAT_R32G32B32A32_SFLOAT},
+    {0}
+  };
+
+  if (_fb_properties.get_srgb_color()) {
+    // This the only sRGB format.  Deal with it.
+    _color_format = VK_FORMAT_R8G8B8A8_SRGB;
+    _fb_properties.set_rgba_bits(8, 8, 8, 8);
+    _fb_properties.set_float_color(false);
+
+  } else {
+    for (int i = 0; formats[i].r; ++i) {
+      if (formats[i].r >= _fb_properties.get_red_bits() &&
+          formats[i].g >= _fb_properties.get_green_bits() &&
+          formats[i].b >= _fb_properties.get_blue_bits() &&
+          formats[i].a >= _fb_properties.get_alpha_bits() &&
+          formats[i].rgb >= _fb_properties.get_color_bits() &&
+          formats[i].has_float >= _fb_properties.get_float_color()) {
+
+        // This format meets the requirements.
+        _color_format = formats[i].format;
+        _fb_properties.set_rgba_bits(formats[i].r, formats[i].g,
+                                     formats[i].b, formats[i].a);
+        break;
+      }
+    }
+  }
+
+  // Choose a suitable depth/stencil format that satisfies the requirements.
+  VkFormatProperties fmt_props;
+  bool request_depth32 = _fb_properties.get_depth_bits() > 24 ||
+                         _fb_properties.get_float_depth();
+
+  if (_fb_properties.get_depth_bits() > 0 || _fb_properties.get_stencil_bits() > 0) {
+    // Vulkan requires support for at least of one of these two formats.
+    vkGetPhysicalDeviceFormatProperties(vkpipe->_gpu, VK_FORMAT_D32_SFLOAT_S8_UINT, &fmt_props);
+    bool supports_depth32 = (fmt_props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0;
+    vkGetPhysicalDeviceFormatProperties(vkpipe->_gpu, VK_FORMAT_D24_UNORM_S8_UINT, &fmt_props);
+    bool supports_depth24 = (fmt_props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0;
+
+    if ((supports_depth32 && request_depth32) || !supports_depth24) {
+      _depth_stencil_format = VK_FORMAT_D32_SFLOAT_S8_UINT;
+      _fb_properties.set_depth_bits(32);
+    } else {
+      _depth_stencil_format = VK_FORMAT_D24_UNORM_S8_UINT;
+      _fb_properties.set_depth_bits(24);
+    }
+    _fb_properties.set_stencil_bits(8);
+
+    _depth_stencil_aspect_mask |= VK_IMAGE_ASPECT_DEPTH_BIT |
+                                  VK_IMAGE_ASPECT_STENCIL_BIT;
+
+  } else if (_fb_properties.get_depth_bits() > 0) {
+    // Vulkan requires support for at least of one of these two formats.
+    vkGetPhysicalDeviceFormatProperties(vkpipe->_gpu, VK_FORMAT_D32_SFLOAT, &fmt_props);
+    bool supports_depth32 = (fmt_props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0;
+    vkGetPhysicalDeviceFormatProperties(vkpipe->_gpu, VK_FORMAT_X8_D24_UNORM_PACK32, &fmt_props);
+    bool supports_depth24 = (fmt_props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0;
+
+    if ((supports_depth32 && request_depth32) || !supports_depth24) {
+      _depth_stencil_format = VK_FORMAT_D32_SFLOAT;
+      _fb_properties.set_depth_bits(32);
+    } else {
+      _depth_stencil_format = VK_FORMAT_X8_D24_UNORM_PACK32;
+      _fb_properties.set_depth_bits(24);
+    }
+
+    _depth_stencil_aspect_mask |= VK_IMAGE_ASPECT_DEPTH_BIT;
+
+  } else {
+    _depth_stencil_format = VK_FORMAT_UNDEFINED;
+    _depth_stencil_aspect_mask |= 0;
+  }
+
+  return setup_render_pass() && create_framebuffer();
+}
+
+/**
+ * Creates a render pass object for this buffer.  Call this whenever the
+ * format or clear parameters change.  Note that all pipeline states become
+ * invalid if the render pass is no longer compatible; however, we currently
+ * call this only when the clear flags change, which does not affect pipeline
+ * compatibility.
+ */
+bool VulkanGraphicsBuffer::
+setup_render_pass() {
+  VulkanGraphicsStateGuardian *vkgsg;
+  DCAST_INTO_R(vkgsg, _gsg, false);
+
+  if (vulkandisplay_cat.is_debug()) {
+    vulkandisplay_cat.debug()
+      << "Creating render pass for VulkanGraphicsBuffer " << this << "\n";
+  }
+
+  // Now we want to create a render pass, and for that we need to describe the
+  // framebuffer attachments as well as any subpasses we'd like to use.
+  VkAttachmentDescription attachments[2];
+  attachments[0].flags = 0;
+  attachments[0].format = _color_format;
+  attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
+  attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+  attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+  attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+  attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+  attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+  attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+  attachments[1].flags = 0;
+  attachments[1].format = _depth_stencil_format;
+  attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+  attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+  attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+  attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+  attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+  attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+  attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+  if (get_clear_color_active()) {
+    attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+  } else {
+    attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
+  }
+
+  if (get_clear_depth_active()) {
+    attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+  }
+
+  if (get_clear_stencil_active()) {
+    attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+  }
+
+  VkAttachmentReference color_reference;
+  color_reference.attachment = 0;
+  color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+  VkAttachmentReference depth_reference;
+  depth_reference.attachment = 1;
+  depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+  VkSubpassDescription subpass;
+  subpass.flags = 0;
+  subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+  subpass.inputAttachmentCount = 0;
+  subpass.pInputAttachments = NULL;
+  subpass.colorAttachmentCount = 1;
+  subpass.pColorAttachments = &color_reference;
+  subpass.pResolveAttachments = NULL;
+  subpass.pDepthStencilAttachment = _depth_stencil_format ? &depth_reference : NULL;
+  subpass.preserveAttachmentCount = 0;
+  subpass.pPreserveAttachments = NULL;
+
+  VkRenderPassCreateInfo pass_info;
+  pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+  pass_info.pNext = NULL;
+  pass_info.flags = 0;
+  pass_info.attachmentCount = _depth_stencil_format ? 2 : 1;
+  pass_info.pAttachments = attachments;
+  pass_info.subpassCount = 1;
+  pass_info.pSubpasses = &subpass;
+  pass_info.dependencyCount = 0;
+  pass_info.pDependencies = NULL;
+
+  VkRenderPass pass;
+  VkResult
+  err = vkCreateRenderPass(vkgsg->_device, &pass_info, NULL, &pass);
+  if (err) {
+    vulkan_error(err, "Failed to create render pass");
+    return false;
+  }
+
+  // Destroy the previous render pass object.
+  if (_render_pass != VK_NULL_HANDLE) {
+    // Actually, we can't destroy it, since we may now have pipeline states
+    // that reference it.  Destroying it now would also require destroying the
+    // framebuffer and clearing all of the prepared states from the GSG.
+    // Maybe we need to start reference counting render passes?
+    vulkandisplay_cat.warning() << "Leaking VkRenderPass.\n";
+    //vkDestroyRenderPass(vkgsg->_device, _render_pass, NULL);
+    //_render_pass = VK_NULL_HANDLE;
+  }
+
+  _render_pass = pass;
+  _current_clear_mask = _clear_mask;
+  return true;
+}
+
+/**
+ * Destroys an existing swapchain.  Before calling this, make sure that no
+ * commands are executing on any queue that uses this swapchain.
+ */
+void VulkanGraphicsBuffer::
+destroy_framebuffer() {
+  VulkanGraphicsStateGuardian *vkgsg;
+  DCAST_INTO_V(vkgsg, _gsg);
+  VkDevice device = vkgsg->_device;
+
+  // Make sure that the GSG's command buffer releases its resources.
+  if (vkgsg->_cmd != VK_NULL_HANDLE) {
+    vkResetCommandBuffer(vkgsg->_cmd, 0);
+  }
+
+  /*if (!_present_cmds.empty()) {
+    vkFreeCommandBuffers(device, vkgsg->_cmd_pool, _present_cmds.size(), &_present_cmds[0]);
+    _present_cmds.clear();
+  }*/
+
+  // Destroy the resources held for each attachment.
+  Attachments::iterator it;
+  for (it = _attachments.begin(); it != _attachments.end(); ++it) {
+    Attachment &attach = *it;
+
+    vkDestroyImageView(device, attach._image_view, NULL);
+    vkFreeMemory(device, attach._memory, NULL);
+  }
+  _attachments.clear();
+
+  if (_framebuffer != VK_NULL_HANDLE) {
+    vkDestroyFramebuffer(device, _framebuffer, NULL);
+    _framebuffer = VK_NULL_HANDLE;
+  }
+}
+
+/**
+ * Creates or recreates the framebuffer.
+ */
+bool VulkanGraphicsBuffer::
+create_framebuffer() {
+  VulkanGraphicsPipe *vkpipe;
+  VulkanGraphicsStateGuardian *vkgsg;
+  DCAST_INTO_R(vkpipe, _pipe, false);
+  DCAST_INTO_R(vkgsg, _gsg, false);
+  VkDevice device = vkgsg->_device;
+  VkResult err;
+
+  if (!create_attachment(_color_format, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_IMAGE_ASPECT_COLOR_BIT) ||
+      !create_attachment(_depth_stencil_format, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_IMAGE_ASPECT_DEPTH_BIT)) {
+    return false;
+  }
+
+  int num_attachments = (int)_attachments.size();
+  VkImageView *attach_views = (VkImageView *)alloca(sizeof(VkImageView) * num_attachments);
+
+  for (int i = 0; i < num_attachments; ++i) {
+    attach_views[i] = _attachments[i]._image_view;
+  }
+
+  VkFramebufferCreateInfo fb_info;
+  fb_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+  fb_info.pNext = NULL;
+  fb_info.flags = 0;
+  fb_info.renderPass = _render_pass;
+  fb_info.attachmentCount = num_attachments;
+  fb_info.pAttachments = attach_views;
+  fb_info.width = _size[0];
+  fb_info.height = _size[1];
+  fb_info.layers = 1;
+
+  err = vkCreateFramebuffer(device, &fb_info, NULL, &_framebuffer);
+  if (err) {
+    vulkan_error(err, "Failed to create framebuffer");
+    return false;
+  }
+
+  _framebuffer_size = _size;
+  return true;
+}
+
+/**
+ * Adds a new attachment to the framebuffer.
+ * @return Returns true on success.
+ */
+bool VulkanGraphicsBuffer::
+create_attachment(VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspect) {
+  VulkanGraphicsPipe *vkpipe;
+  DCAST_INTO_R(vkpipe, _pipe, false);
+
+  VulkanGraphicsStateGuardian *vkgsg;
+  DCAST_INTO_R(vkgsg, _gsg, false);
+  VkDevice device = vkgsg->_device;
+
+  Attachment attach;
+  attach._format = format;
+  attach._usage = usage;
+  attach._aspect = aspect;
+
+  VkImageCreateInfo img_info;
+  img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+  img_info.pNext = NULL;
+  img_info.flags = 0;
+  img_info.imageType = VK_IMAGE_TYPE_2D;
+  img_info.format = format;
+  img_info.extent.width = _size[0];
+  img_info.extent.height = _size[1];
+  img_info.extent.depth = 1;
+  img_info.mipLevels = 1;
+  img_info.arrayLayers = 1;
+  img_info.samples = VK_SAMPLE_COUNT_1_BIT;
+  img_info.tiling = VK_IMAGE_TILING_OPTIMAL;
+  img_info.usage = usage;
+  img_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+  img_info.queueFamilyIndexCount = 0;
+  img_info.pQueueFamilyIndices = NULL;
+  img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+
+  VkResult
+  err = vkCreateImage(device, &img_info, NULL, &attach._image);
+  if (err) {
+    vulkan_error(err, "Failed to create image");
+    return false;
+  }
+
+  // Get the memory requirements, and find an appropriate heap to alloc in.
+  VkMemoryRequirements mem_reqs;
+  vkGetImageMemoryRequirements(device, attach._image, &mem_reqs);
+
+  VkMemoryAllocateInfo alloc_info;
+  alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+  alloc_info.pNext = NULL;
+  alloc_info.allocationSize = mem_reqs.size;
+
+  if (!vkpipe->find_memory_type(alloc_info.memoryTypeIndex, mem_reqs.memoryTypeBits, 0)) {
+    vulkan_error(err, "Failed to find memory heap to allocate depth buffer");
+    return false;
+  }
+
+  VkDeviceMemory memory;
+  err = vkAllocateMemory(device, &alloc_info, NULL, &memory);
+  if (err) {
+    vulkan_error(err, "Failed to allocate memory for depth image");
+    return false;
+  }
+
+  // Bind memory to image.
+  err = vkBindImageMemory(device, attach._image, memory, 0);
+  if (err) {
+    vulkan_error(err, "Failed to bind memory to depth image");
+    return false;
+  }
+
+  VkImageViewCreateInfo view_info;
+  view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+  view_info.pNext = NULL;
+  view_info.flags = 0;
+  view_info.image = attach._image;
+  view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+  view_info.format = format;
+  view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+  view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+  view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+  view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+  view_info.subresourceRange.aspectMask = aspect;
+  view_info.subresourceRange.baseMipLevel = 0;
+  view_info.subresourceRange.levelCount = 1;
+  view_info.subresourceRange.baseArrayLayer = 0;
+  view_info.subresourceRange.layerCount = 1;
+
+  err = vkCreateImageView(device, &view_info, NULL, &attach._image_view);
+  if (err) {
+    vulkan_error(err, "Failed to create image view for attachment");
+    return false;
+  }
+
+  _attachments.push_back(attach);
+  return true;
+}

+ 91 - 0
panda/src/vulkandisplay/vulkanGraphicsBuffer.h

@@ -0,0 +1,91 @@
+/**
+ * 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 vulkanGraphicsBuffer.h
+ * @author rdb
+ * @date 2016-04-13
+ */
+
+#ifndef VULKANGRAPHICSBUFFER_H
+#define VULKANGRAPHICSBUFFER_H
+
+#include "config_vulkandisplay.h"
+#include "graphicsBuffer.h"
+
+/**
+ * An offscreen buffer using the Vulkan API.
+ */
+class EXPCL_VULKANDISPLAY VulkanGraphicsBuffer : public GraphicsBuffer {
+public:
+  VulkanGraphicsBuffer(GraphicsEngine *engine, GraphicsPipe *pipe,
+                       const string &name,
+                       const FrameBufferProperties &fb_prop,
+                       const WindowProperties &win_prop,
+                       int flags,
+                       GraphicsStateGuardian *gsg,
+                       GraphicsOutput *host);
+  virtual ~VulkanGraphicsBuffer();
+
+  //virtual void clear(Thread *current_thread);
+  virtual bool begin_frame(FrameMode mode, Thread *current_thread);
+  virtual void end_frame(FrameMode mode, Thread *current_thread);
+
+protected:
+  virtual void close_buffer();
+  virtual bool open_buffer();
+
+  bool setup_render_pass();
+
+  void destroy_framebuffer();
+  bool create_framebuffer();
+
+  bool create_attachment(VkFormat format, VkImageUsageFlags usage, VkImageAspectFlags aspect);
+
+private:
+  VkRenderPass _render_pass;
+  VkFramebuffer _framebuffer;
+  LVecBase2i _framebuffer_size;
+  int _current_clear_mask;
+
+  VkFormat _color_format;
+  VkFormat _depth_stencil_format;
+  VkImageAspectFlags _depth_stencil_aspect_mask;
+
+  struct Attachment {
+    VkImage _image;
+    VkImageView _image_view;
+    VkDeviceMemory _memory;
+    VkFormat _format;
+    VkImageUsageFlags _usage;
+    VkImageAspectFlags _aspect;
+  };
+  typedef pvector<Attachment> Attachments;
+  Attachments _attachments;
+  bool _layout_defined;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    GraphicsBuffer::init_type();
+    register_type(_type_handle, "VulkanGraphicsBuffer",
+                  GraphicsBuffer::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 "vulkanGraphicsBuffer.I"
+
+#endif

+ 301 - 52
panda/src/vulkandisplay/vulkanGraphicsStateGuardian.cxx

@@ -196,7 +196,6 @@ VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
   // Create a dummy vertex buffer.  This will be used to store default values
   // for attributes when they are not bound to a vertex buffer, as well as any
   // flat color assigned via ColorAttrib.
-  VkBuffer buffer;
   VkDeviceMemory memory;
   uint32_t palette_size = (uint32_t)min(2, vulkan_color_palette_size.get_value()) * 16;
   if (!create_buffer(palette_size, _color_vertex_buffer, memory,
@@ -250,9 +249,9 @@ VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
   _supports_occlusion_query = false;
   _supports_timer_query = false;
 
-  // Initially, we set this to false; a GSG that knows it has this property
-  // should set it to true.
-  _copy_texture_inverted = false;
+  // Set to indicate that we get an inverted result when we copy the
+  // framebuffer to a texture.
+  _copy_texture_inverted = true;
 
   // Similarly with these capabilities flags.
   _supports_multisample = true;
@@ -264,6 +263,7 @@ VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
   _supports_basic_shaders = false;
   _supports_geometry_shaders = (features.geometryShader != VK_FALSE);
   _supports_tessellation_shaders = (features.tessellationShader != VK_FALSE);
+  _supports_compute_shaders = true;
   _supports_glsl = false;
   _supports_hlsl = false;
   _supports_framebuffer_multisample = true;
@@ -283,9 +283,14 @@ VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
     Geom::GR_point |
     Geom::GR_indexed_other |
     Geom::GR_triangle_strip | Geom::GR_triangle_fan |
-    Geom::GR_line_strip;
-  //TODO: designate provoking vertex used for flat shading
-  //TODO: add flags indicating support for render modes
+    Geom::GR_line_strip |
+    Geom::GR_flat_first_vertex | //TODO: is this correct?
+    Geom::GR_strip_cut_index;
+
+  if (features.fillModeNonSolid) {
+    _supported_geom_rendering |= Geom::GR_render_mode_wireframe |
+                                 Geom::GR_render_mode_point;
+  }
 
   if (features.largePoints) {
     _supported_geom_rendering |= Geom::GR_point_uniform_size;
@@ -663,6 +668,7 @@ prepare_texture(Texture *texture, int view) {
   tc->_extent = image_info.extent;
   tc->_mipmap_begin = mipmap_begin;
   tc->_mipmap_end = mipmap_end;
+  tc->_mip_levels = image_info.mipLevels;
   tc->_array_layers = image_info.arrayLayers;
   tc->_aspect_mask = view_info.subresourceRange.aspectMask;
   tc->_generate_mipmaps = generate_mipmaps;
@@ -691,30 +697,18 @@ upload_texture(VulkanTextureContext *tc) {
   uint32_t mip_levels = tc->_mipmap_end - tc->_mipmap_begin;
   VkResult err;
 
+  //TODO: check if the image is currently in use on a different queue, and if
+  // so, use a semaphore to control order or create a new image and discard
+  // the old one.
+
   VulkanGraphicsPipe *vkpipe;
   DCAST_INTO_R(vkpipe, get_pipe(), false);
 
   // Issue a command to transition the image into a layout optimal for
   // transferring into.
-  VkImageMemoryBarrier barrier;
-  barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-  barrier.pNext = NULL;
-  barrier.srcAccessMask = 0;
-  barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
-  barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-  barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
-  barrier.srcQueueFamilyIndex = _graphics_queue_family_index;
-  barrier.dstQueueFamilyIndex = _graphics_queue_family_index;
-  barrier.image = image;
-  barrier.subresourceRange.aspectMask = tc->_aspect_mask;
-  barrier.subresourceRange.baseMipLevel = 0;
-  barrier.subresourceRange.levelCount = mip_levels;
-  barrier.subresourceRange.baseArrayLayer = 0;
-  barrier.subresourceRange.layerCount = tc->_array_layers;
-
-  vkCmdPipelineBarrier(_transfer_cmd, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
-                       VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0,
-                       0, NULL, 0, NULL, 1, &barrier);
+  tc->transition(_transfer_cmd, _graphics_queue_family_index,
+    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+    VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
 
   // Do we even have an image to upload?
   if (texture->get_ram_image().is_null()) {
@@ -727,14 +721,12 @@ upload_texture(VulkanTextureContext *tc) {
         value.float32[1] = col[1];
         value.float32[2] = col[2];
         value.float32[3] = col[3];
-        vkCmdClearColorImage(_transfer_cmd, image, VK_IMAGE_LAYOUT_GENERAL,
-                             &value, 1, &barrier.subresourceRange);
+        tc->clear_color_image(_transfer_cmd, value);
       } else {
         VkClearDepthStencilValue value;
         value.depth = col[0];
         value.stencil = 0;
-        vkCmdClearDepthStencilImage(_transfer_cmd, image, VK_IMAGE_LAYOUT_GENERAL,
-                                    &value, 1, &barrier.subresourceRange);
+        tc->clear_depth_stencil_image(_transfer_cmd, value);
       }
     }
 
@@ -956,13 +948,34 @@ release_texture(TextureContext *) {
  * This method should only be called by the GraphicsEngine.  Do not call it
  * directly; call GraphicsEngine::extract_texture_data() instead.
  *
- * This method will be called in the draw thread to download the texture
- * memory's image into its ram_image value.  It returns true on success, false
- * otherwise.
+ * Please note that this may be a very expensive operation as it stalls the
+ * graphics pipeline while waiting for the rendered results to become
+ * available.  The graphics implementation may choose to defer writing the ram
+ * image until the next end_frame() call.
+ *
+ * This method will be called in the draw thread between begin_frame() and
+ * end_frame() to download the texture memory's image into its ram_image
+ * value.  It may not be called between begin_scene() and end_scene().
+ *
+ * @return true on success, false otherwise
  */
 bool VulkanGraphicsStateGuardian::
-extract_texture_data(Texture *) {
-  return false;
+extract_texture_data(Texture *tex) {
+  bool success = true;
+
+  // If we wanted to optimize this use-case, we could allocate a single buffer
+  // to hold all texture views and copy that in one go.
+  int num_views = tex->get_num_views();
+  for (int view = 0; view < num_views; ++view) {
+    VulkanTextureContext *tc;
+    DCAST_INTO_R(tc, tex->prepare_now(view, get_prepared_objects(), this), false);
+
+    if (!do_extract_image(tc, tex, view)) {
+      success = false;
+    }
+  }
+
+  return success;
 }
 
 /**
@@ -1395,6 +1408,11 @@ set_state_and_transform(const RenderState *state,
     VulkanTextureContext *tc;
     DCAST_INTO_V(tc, texture->prepare_now(0, get_prepared_objects(), this));
     update_texture(tc, true);
+
+    tc->transition(_cmd, _graphics_queue_family_index,
+                   VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                   VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+                   VK_ACCESS_SHADER_READ_BIT);
   }
 
   VkDescriptorSet ds = get_descriptor_set(state);
@@ -1529,6 +1547,7 @@ begin_frame(Thread *current_thread) {
 
     // Make sure that the previous command buffer is done executing, so that
     // we don't update or delete resources while they're still being used.
+    // We should probably come up with a better mechanism for this.
     err = vkWaitForFences(_device, 1, &_fence, VK_TRUE, 1000000000ULL);
     if (err == VK_TIMEOUT) {
       vulkandisplay_cat.error()
@@ -1660,9 +1679,89 @@ end_frame(Thread *current_thread) {
   nassertv(_transfer_cmd != VK_NULL_HANDLE);
   vkEndCommandBuffer(_transfer_cmd);
 
+  // Issue commands to transition the staging buffers of the texture downloads
+  // to make sure that the previous copy operations are visible to host reads.
+  if (!_download_queue.empty()) {
+    size_t num_downloads = _download_queue.size();
+    VkBufferMemoryBarrier *barriers = (VkBufferMemoryBarrier *)
+      alloca(sizeof(VkBufferMemoryBarrier) * num_downloads);
+
+    for (size_t i = 0; i < num_downloads; ++i) {
+      barriers[i].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+      barriers[i].pNext = NULL;
+      barriers[i].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+      barriers[i].dstAccessMask = VK_ACCESS_HOST_READ_BIT;
+      barriers[i].srcQueueFamilyIndex = _graphics_queue_family_index;
+      barriers[i].dstQueueFamilyIndex = _graphics_queue_family_index;
+      barriers[i].buffer = _download_queue[i]._buffer;
+      barriers[i].offset = 0;
+      barriers[i].size = VK_WHOLE_SIZE;
+    }
+
+    vkCmdPipelineBarrier(_cmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0,
+                         0, NULL, (uint32_t)num_downloads, barriers, 0, NULL);
+  }
+
   nassertv(_cmd != VK_NULL_HANDLE);
   vkEndCommandBuffer(_cmd);
 
+  VkCommandBuffer cmdbufs[] = {_transfer_cmd, _cmd};
+
+  // Submit the command buffers to the queue.
+  VkSubmitInfo submit_info;
+  submit_info.pNext = NULL;
+  submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+  submit_info.waitSemaphoreCount = 0;
+  submit_info.pWaitSemaphores = NULL;
+  submit_info.pWaitDstStageMask = NULL;
+  submit_info.commandBufferCount = 2;
+  submit_info.pCommandBuffers = cmdbufs;
+  submit_info.signalSemaphoreCount = 0;
+  submit_info.pSignalSemaphores = NULL;
+
+  VkResult err;
+  err = vkQueueSubmit(_queue, 1, &submit_info, _fence);
+  if (err) {
+    vulkan_error(err, "Error submitting queue");
+    return;
+  }
+
+  // If we queued up texture downloads, wait for the queue to finish (slow!)
+  // and then copy the data from Vulkan host memory to Panda memory.
+  if (!_download_queue.empty()) {
+    {
+      PStatTimer timer(_flush_pcollector);
+      err = vkWaitForFences(_device, 1, &_fence, VK_TRUE, ~0ULL);
+    }
+    if (err) {
+      vulkan_error(err, "Failed to wait for command buffer execution");
+    }
+
+    DownloadQueue::const_iterator it;
+    for (it = _download_queue.begin(); it != _download_queue.end(); ++it) {
+      const QueuedDownload &down = *it;
+      PTA_uchar target = down._texture->modify_ram_image();
+      size_t view_size = down._texture->get_ram_view_size();
+
+      void *data;
+      err = vkMapMemory(_device, down._memory, 0, view_size, 0, &data);
+      if (err) {
+        vulkan_error(err, "Failed to map memory for RAM transfer");
+        vkDestroyBuffer(_device, down._buffer, NULL);
+        vkFreeMemory(_device, down._memory, NULL);
+        continue;
+      }
+
+      memcpy(target.p() + view_size * down._view, data, view_size);
+
+      // We won't need these any more.
+      vkUnmapMemory(_device, down._memory);
+      vkDestroyBuffer(_device, down._buffer, NULL);
+      vkFreeMemory(_device, down._memory, NULL);
+    }
+    _download_queue.clear();
+  }
+
   //TODO: delete command buffer, schedule for deletion, or recycle.
 }
 
@@ -1784,12 +1883,157 @@ reset() {
  * Copy the pixels within the indicated display region from the framebuffer
  * into texture memory.
  *
- * If z > -1, it is the cube map index into which to copy.
+ * This should be called between begin_frame() and end_frame(), but not be
+ * called between begin_scene() and end_scene().
+ *
+ * @param z if z > -1, it is the cube map index into which to copy
+ * @return true on success, false on failure
+ */
+bool VulkanGraphicsStateGuardian::
+framebuffer_copy_to_texture(Texture *tex, int view, int z,
+                            const DisplayRegion *dr, const RenderBuffer &rb) {
+
+  // You're not allowed to call this while a render pass is active.
+  nassertr(_render_pass == VK_NULL_HANDLE, false);
+
+  nassertr(_fb_color_tc != NULL, false);
+  VulkanTextureContext *fbtc = _fb_color_tc;
+
+  //TODO: proper format checking and size calculation.
+  tex->setup_2d_texture(fbtc->_extent.width, fbtc->_extent.height, Texture::T_unsigned_byte, Texture::F_rgba8);
+  VkDeviceSize buffer_size = fbtc->_extent.width * fbtc->_extent.height * 4;
+
+  VulkanTextureContext *tc;
+  DCAST_INTO_R(tc, tex->prepare_now(view, get_prepared_objects(), this), false);
+
+  nassertr(fbtc->_extent.width == tc->_extent.width &&
+           fbtc->_extent.height == tc->_extent.height &&
+           fbtc->_extent.depth == tc->_extent.depth, false);
+  nassertr(fbtc->_mip_levels == tc->_mip_levels, false);
+  nassertr(fbtc->_array_layers == tc->_array_layers, false);
+  nassertr(fbtc->_aspect_mask == tc->_aspect_mask, false);
+
+  VkImageCopy region;
+  region.srcSubresource.aspectMask = fbtc->_aspect_mask;
+  region.srcSubresource.mipLevel = 0;
+  if (z != -1) {
+    nassertr(z >= 0 && (uint32_t)z < fbtc->_array_layers, false);
+    region.srcSubresource.baseArrayLayer = z;
+    region.srcSubresource.layerCount = 1;
+  } else {
+    region.srcSubresource.baseArrayLayer = 0;
+    region.srcSubresource.layerCount = fbtc->_array_layers;
+  }
+  region.srcOffset.x = 0;
+  region.srcOffset.y = 0;
+  region.srcOffset.z = 0;
+  region.dstSubresource = region.srcSubresource;
+  region.dstOffset.x = 0;
+  region.dstOffset.y = 0;
+  region.dstOffset.z = 0;
+  region.extent = fbtc->_extent;
+
+  // Issue a command to transition the image into a layout optimal for
+  // transferring from.
+  fbtc->transition(_cmd, _graphics_queue_family_index,
+    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+    VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT);
+
+  tc->transition(_cmd, _graphics_queue_family_index,
+    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+    VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
+
+  vkCmdCopyImage(_cmd, fbtc->_image, fbtc->_layout, tc->_image, tc->_layout, 1, &region);
+  return true;
+}
+
+/**
+ * Copy the pixels within the indicated display region from the framebuffer
+ * into system memory, not texture memory.  Please note that this may be a
+ * very expensive operation as it stalls the graphics pipeline while waiting
+ * for the rendered results to become available.  The graphics implementation
+ * may choose to defer writing the ram image until the next end_frame() call.
+ *
+ * This completely redefines the ram image of the indicated texture.
+ *
+ * This should be called between begin_frame() and end_frame(), but not be
+ * called between begin_scene() and end_scene().
+ *
+ * @return true on success, false on failure
  */
 bool VulkanGraphicsStateGuardian::
-framebuffer_copy_to_texture(Texture *tex, int view, int z, const DisplayRegion *,
-                            const RenderBuffer &) {
-  return false;
+framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                        const DisplayRegion *dr, const RenderBuffer &rb) {
+
+  // Please note that this doesn't complete immediately, but instead queues it
+  // until the next end_frame().  This seems to be okay given existing usage,
+  // and it prevents having to do the equivalent of glFinish() mid-render.
+
+  // You're not allowed to call this while a render pass is active.
+  nassertr(_render_pass == VK_NULL_HANDLE, false);
+
+  nassertr(_fb_color_tc != NULL, false);
+  VulkanTextureContext *fbtc = _fb_color_tc;
+
+  //TODO: proper format checking and size calculation.
+  tex->setup_2d_texture(fbtc->_extent.width, fbtc->_extent.height, Texture::T_unsigned_byte, Texture::F_rgba8);
+
+  return do_extract_image(fbtc, tex, view);
+}
+
+/**
+ * Internal method used by extract_texture_data and framebuffer_copy_to_ram.
+ * Queues up a texture-to-RAM download.
+ */
+bool VulkanGraphicsStateGuardian::
+do_extract_image(VulkanTextureContext *tc, Texture *tex, int view, int z) {
+  VkDeviceSize buffer_size = tc->_extent.width * tc->_extent.height * 4;
+
+  // Create a temporary buffer for transferring into.
+  QueuedDownload down;
+  if (!create_buffer(buffer_size, down._buffer, down._memory,
+                     VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+                     VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
+    vulkandisplay_cat.error()
+      << "Failed to create staging buffer for framebuffer-to-RAM copy.\n";
+    return false;
+  }
+
+  // We tack this onto the existing command buffer, for now.
+  VkCommandBuffer cmd = _cmd;
+
+  VkBufferImageCopy region;
+  region.bufferOffset = 0;
+  region.bufferRowLength = 0;
+  region.bufferImageHeight = 0;
+  region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+  region.imageSubresource.mipLevel = 0;
+  if (z != -1) {
+    nassertr(z >= 0 && (uint32_t)z < tc->_array_layers, false);
+    region.imageSubresource.baseArrayLayer = z;
+    region.imageSubresource.layerCount = 1;
+  } else {
+    region.imageSubresource.baseArrayLayer = 0;
+    region.imageSubresource.layerCount = tc->_array_layers;
+  }
+  region.imageOffset.x = 0;
+  region.imageOffset.y = 0;
+  region.imageOffset.z = 0;
+  region.imageExtent = tc->_extent;
+
+  // Issue a command to transition the image into a layout optimal for
+  // transferring from.
+  tc->transition(cmd, _graphics_queue_family_index,
+    VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+    VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT);
+
+  vkCmdCopyImageToBuffer(cmd, tc->_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+                         down._buffer, 1, &region);
+
+  down._texture = tex;
+  down._view = view;
+  _download_queue.push_back(down);
+  return true;
 }
 
 /**
@@ -1921,10 +2165,10 @@ make_pipeline(const RenderState *state, const GeomVertexFormat *format,
   static PT(Shader) default_shader;
   if (default_shader.is_null()) {
     default_shader = Shader::load(Shader::SL_SPIR_V, "vert.spv", "frag.spv");
-    nassertr(default_shader, NULL);
+    nassertr(default_shader, VK_NULL_HANDLE);
 
     ShaderContext *sc = default_shader->prepare_now(get_prepared_objects(), this);
-    nassertr(sc, NULL);
+    nassertr(sc, VK_NULL_HANDLE);
     _default_sc = DCAST(VulkanShaderContext, sc);
   }
 
@@ -2140,17 +2384,22 @@ make_pipeline(const RenderState *state, const GeomVertexFormat *format,
   raster_info.depthClampEnable = VK_TRUE;
   raster_info.rasterizerDiscardEnable = VK_FALSE;
 
-  switch (render_mode->get_mode()) {
-  case RenderModeAttrib::M_filled:
-  default:
+  if (_supported_geom_rendering & Geom::GR_render_mode_wireframe) {
+    switch (render_mode->get_mode()) {
+    case RenderModeAttrib::M_filled:
+    default:
+      raster_info.polygonMode = VK_POLYGON_MODE_FILL;
+      break;
+    case RenderModeAttrib::M_wireframe:
+      raster_info.polygonMode = VK_POLYGON_MODE_LINE;
+      break;
+    case RenderModeAttrib::M_point:
+      raster_info.polygonMode = VK_POLYGON_MODE_POINT;
+      break;
+    }
+  } else {
+    // Not supported.  The geometry will have been changed at munge time.
     raster_info.polygonMode = VK_POLYGON_MODE_FILL;
-    break;
-  case RenderModeAttrib::M_wireframe:
-    raster_info.polygonMode = VK_POLYGON_MODE_LINE;
-    break;
-  case RenderModeAttrib::M_point:
-    raster_info.polygonMode = VK_POLYGON_MODE_POINT;
-    break;
   }
 
   raster_info.cullMode = (VkCullModeFlagBits)cull_face->get_effective_mode();
@@ -2412,7 +2661,7 @@ make_descriptor_set(const RenderState *state) {
   VkDescriptorImageInfo image_info;
   image_info.sampler = sc->_sampler;
   image_info.imageView = tc->_image_view;
-  image_info.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+  image_info.imageLayout = tc->_layout;
 
   VkWriteDescriptorSet write;
   write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;

+ 21 - 1
panda/src/vulkandisplay/vulkanGraphicsStateGuardian.h

@@ -105,8 +105,13 @@ public:
   virtual bool framebuffer_copy_to_texture(Texture *tex, int view, int z,
                                            const DisplayRegion *dr,
                                            const RenderBuffer &rb);
+  virtual bool framebuffer_copy_to_ram(Texture *tex, int view, int z,
+                                       const DisplayRegion *dr,
+                                       const RenderBuffer &rb);
 
 private:
+  bool do_extract_image(VulkanTextureContext *tc, Texture *tex, int view, int z=-1);
+
   bool do_draw_primitive(const GeomPrimitivePipelineReader *reader, bool force,
                          VkPrimitiveTopology topology);
 
@@ -178,7 +183,6 @@ private:
   VkCommandBuffer _cmd;
   VkCommandBuffer _transfer_cmd;
   pvector<VkRect2D> _viewports;
-  VkRenderPass _render_pass;
   VkPipelineCache _pipeline_cache;
   VkPipelineLayout _pipeline_layout;
   VkDescriptorSetLayout _descriptor_set_layout;
@@ -187,6 +191,11 @@ private:
   CPT(GeomVertexFormat) _format;
   PT(Texture) _white_texture;
 
+  // Stores current framebuffer info.
+  VkRenderPass _render_pass;
+  VulkanTextureContext *_fb_color_tc;
+  VulkanTextureContext *_fb_depth_tc;
+
   // Palette for flat colors.
   VkBuffer _color_vertex_buffer;
   int _next_palette_index;
@@ -199,6 +208,17 @@ private:
   typedef pmap<DescriptorSetKey, VkDescriptorSet> DescriptorSetMap;
   DescriptorSetMap _descriptor_set_map;
 
+  // Queued buffer-to-RAM transfer.
+  struct QueuedDownload {
+    VkBuffer _buffer;
+    VkDeviceMemory _memory;
+    PT(Texture) _texture;
+    int _view;
+  };
+  typedef pvector<QueuedDownload> DownloadQueue;
+  DownloadQueue _download_queue;
+
+  friend class VulkanGraphicsBuffer;
   friend class VulkanGraphicsWindow;
 
 public:

+ 123 - 226
panda/src/vulkandisplay/vulkanGraphicsWindow.cxx

@@ -33,11 +33,7 @@ VulkanGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
   _render_pass(VK_NULL_HANDLE),
   _present_complete(VK_NULL_HANDLE),
   _current_clear_mask(-1),
-  _depth_stencil_image(VK_NULL_HANDLE),
-  _depth_stencil_view(VK_NULL_HANDLE),
-  _depth_stencil_memory(VK_NULL_HANDLE),
-  _depth_stencil_layout_defined(false),
-  _depth_stencil_aspect_mask(0),
+  _depth_stencil_tc(NULL),
   _image_index(0)
 {
 }
@@ -89,9 +85,9 @@ begin_frame(FrameMode mode, Thread *current_thread) {
       << "Drawing " << this << ": exposed.\n";
   }
 
-  if (mode != FM_render) {
+  /*if (mode != FM_render) {
     return true;
-  }
+  }*/
 
   VulkanGraphicsStateGuardian *vkgsg;
   DCAST_INTO_R(vkgsg, _gsg, false);
@@ -123,6 +119,10 @@ begin_frame(FrameMode mode, Thread *current_thread) {
     return false;
   }
 
+  if (mode != FM_render) {
+    return true;
+  }
+
   VkSemaphoreCreateInfo semaphore_info;
   semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
   semaphore_info.pNext = NULL;
@@ -133,10 +133,12 @@ begin_frame(FrameMode mode, Thread *current_thread) {
                           NULL, &_present_complete);
   nassertr(err == 0, false);
 
-  err = vkAcquireNextImageKHR(vkgsg->_device, _swapchain, UINT64_MAX,
-                              _present_complete, (VkFence)0, &_image_index);
+  if (mode == FM_render) {
+    err = vkAcquireNextImageKHR(vkgsg->_device, _swapchain, UINT64_MAX,
+                                _present_complete, (VkFence)0, &_image_index);
 
-  nassertr(_image_index < _swap_buffers.size(), false);
+    nassertr(_image_index < _swap_buffers.size(), false);
+  }
   SwapBuffer &buffer = _swap_buffers[_image_index];
 
   /*if (mode == FM_render) {
@@ -166,98 +168,52 @@ begin_frame(FrameMode mode, Thread *current_thread) {
   begin_info.clearValueCount = 1;
   begin_info.pClearValues = clears;
 
-  VkImageMemoryBarrier barriers[2];
-  barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-  barriers[0].pNext = NULL;
-  barriers[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
-  barriers[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
-  barriers[0].oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
-  barriers[0].newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-  barriers[0].srcQueueFamilyIndex = vkgsg->_graphics_queue_family_index; //TODO support separate present queue.
-  barriers[0].dstQueueFamilyIndex = vkgsg->_graphics_queue_family_index;
-  barriers[0].image = buffer._image;
-  barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-  barriers[0].subresourceRange.baseMipLevel = 0;
-  barriers[0].subresourceRange.levelCount = 1;
-  barriers[0].subresourceRange.baseArrayLayer = 0;
-  barriers[0].subresourceRange.layerCount = 1;
-
-  if (!buffer._layout_defined) {
-    // If this is the first time we are using these images, they are still in
-    // the UNDEFINED layout.
-    if (!get_clear_color_active()) {
+  if (!get_clear_color_active()) {
+    // If we aren't clearing (which is a bad idea - please clear the window)
+    // then we need to transition it to a consistent state..
+    if (buffer._tc->_layout == VK_IMAGE_LAYOUT_UNDEFINED) {
       // If the attachment is set to LOAD, we need to clear it for the first
-      // time if we don't want the validation layer to yell at us.  This means
-      // we need to transition it to TRANSFER_DST_OPTIMAL first.
-      VkImageMemoryBarrier barrier;
-      barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-      barrier.pNext = NULL;
-      barrier.srcAccessMask = 0;
-      barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
-      barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-      barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
-      barrier.srcQueueFamilyIndex = vkgsg->_graphics_queue_family_index;
-      barrier.dstQueueFamilyIndex = vkgsg->_graphics_queue_family_index;
-      barrier.image = buffer._image;
-      barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-      barrier.subresourceRange.baseMipLevel = 0;
-      barrier.subresourceRange.levelCount = 1;
-      barrier.subresourceRange.baseArrayLayer = 0;
-      barrier.subresourceRange.layerCount = 1;
-      vkCmdPipelineBarrier(cmd, 0, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
-                           0, NULL, 0, NULL, 1, &barrier);
-
-      // Now clear the image to some arbitrary color.  We'll just pick the
+      // time if we don't want the validation layer to yell at us.
+      // We clear it to an arbitrary arbitrary color.  We'll just pick the
       // color returned by get_clear_color(), even if it is meaningless.
-      vkCmdClearColorImage(cmd, buffer._image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-                           &clears[0].color, 1, &barrier.subresourceRange);
-
-      barriers[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
-      barriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
-    } else {
-      barriers[0].srcAccessMask = 0;
-      barriers[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+      buffer._tc->clear_color_image(cmd, clears[0].color);
     }
-    buffer._layout_defined = true;
-  }
 
-  if (_depth_stencil_image == VK_NULL_HANDLE) {
-    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
-                         VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0,
-                         0, NULL, 0, NULL, 1, barriers);
+    buffer._tc->transition(cmd, vkgsg->_graphics_queue_family_index,
+                           VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+                           VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+                           VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
   } else {
+    // This transition will be made when the first subpass is started.
+    buffer._tc->_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+    buffer._tc->_access_mask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    buffer._tc->_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+  }
+
+  if (_depth_stencil_tc != NULL) {
     begin_info.clearValueCount++;
     clears[1].depthStencil.depth = get_clear_depth();
     clears[1].depthStencil.stencil = get_clear_stencil();
 
-    barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-    barriers[1].pNext = NULL;
-    barriers[1].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
-    barriers[1].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
-    barriers[1].oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
-    barriers[1].newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-    barriers[1].srcQueueFamilyIndex = vkgsg->_graphics_queue_family_index; //TODO support separate present queue.
-    barriers[1].dstQueueFamilyIndex = vkgsg->_graphics_queue_family_index;
-    barriers[1].image = _depth_stencil_image;
-    barriers[1].subresourceRange.aspectMask = _depth_stencil_aspect_mask;
-    barriers[1].subresourceRange.baseMipLevel = 0;
-    barriers[1].subresourceRange.levelCount = 1;
-    barriers[1].subresourceRange.baseArrayLayer = 0;
-    barriers[1].subresourceRange.layerCount = 1;
-
-    if (!_depth_stencil_layout_defined) {
-      barriers[1].srcAccessMask = 0;
-      barriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
-      _depth_stencil_layout_defined = true;
+    // Transition the depth-stencil image to a consistent state.
+    if (!get_clear_depth_active() || !get_clear_stencil_active()) {
+      _depth_stencil_tc->transition(cmd, vkgsg->_graphics_queue_family_index,
+        VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
+        VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
+        VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
+    } else {
+      // This transition will be made when the first subpass is started.
+      _depth_stencil_tc->_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+      _depth_stencil_tc->_access_mask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+      _depth_stencil_tc->_stage_mask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
+                                       VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
     }
-
-    vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
-                         VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0,
-                         0, NULL, 0, NULL, 2, barriers);
   }
 
   vkCmdBeginRenderPass(cmd, &begin_info, VK_SUBPASS_CONTENTS_INLINE);
   vkgsg->_render_pass = _render_pass;
+  vkgsg->_fb_color_tc = buffer._tc;
+  vkgsg->_fb_depth_tc = _depth_stencil_tc;
 
   return true;
 }
@@ -271,22 +227,28 @@ void VulkanGraphicsWindow::
 end_frame(FrameMode mode, Thread *current_thread) {
   end_frame_spam(mode);
 
+  VulkanGraphicsStateGuardian *vkgsg;
+  DCAST_INTO_V(vkgsg, _gsg);
+
   if (mode == FM_render) {
-    VulkanGraphicsStateGuardian *vkgsg;
-    DCAST_INTO_V(vkgsg, _gsg);
     VkCommandBuffer cmd = vkgsg->_cmd;
     nassertv(cmd != VK_NULL_HANDLE);
 
     vkCmdEndRenderPass(cmd);
     vkgsg->_render_pass = VK_NULL_HANDLE;
 
-    if (mode == FM_render) {
-      copy_to_textures();
-    }
+    // The driver implicitly transitioned this to the final layout.
+    _swap_buffers[_image_index]._tc->_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+    // Now we can do copy-to-texture, now that the render pass has ended.
+    copy_to_textures();
+  }
 
-    // Note: this will close the command buffer.
-    _gsg->end_frame(current_thread);
+  // Note: this will close the command buffer.
+  vkgsg->end_frame(current_thread);
 
+  if (mode == FM_render) {
+    nassertv(_present_complete != VK_NULL_HANDLE);
     trigger_flip();
     clear_cube_map_selection();
   }
@@ -335,34 +297,10 @@ end_flip() {
   DCAST_INTO_V(vkgsg, _gsg);
   VkDevice device = vkgsg->_device;
   VkQueue queue = vkgsg->_queue;
-  VkFence fence = vkgsg->_fence;
-
-  nassertv(_present_complete != VK_NULL_HANDLE);
+  VkResult err;
 
   SwapBuffer &buffer = _swap_buffers[_image_index];
-  nassertv(buffer._layout_defined);
-
-  VkCommandBuffer cmdbufs[3] = {vkgsg->_transfer_cmd, vkgsg->_cmd, _present_cmds[_image_index]};
-
-  // Submit the GSG's command buffers to the queue.
-  VkPipelineStageFlags stage_flags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
-  VkSubmitInfo submit_info;
-  submit_info.pNext = NULL;
-  submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
-  submit_info.waitSemaphoreCount = 1;
-  submit_info.pWaitSemaphores = &_present_complete;
-  submit_info.pWaitDstStageMask = &stage_flags;
-  submit_info.commandBufferCount = 3;
-  submit_info.pCommandBuffers = cmdbufs;
-  submit_info.signalSemaphoreCount = 0;
-  submit_info.pSignalSemaphores = NULL;
-
-  VkResult err;
-  err = vkQueueSubmit(queue, 1, &submit_info, fence);
-  if (err) {
-    vulkan_error(err, "Error submitting queue");
-    return;
-  }
+  nassertv(buffer._tc->_layout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
 
   VkResult results[1];
   VkPresentInfoKHR present;
@@ -389,6 +327,8 @@ end_flip() {
     return;
   }
 
+  // Should we really wait for the present to be done?  Seems like a waste of
+  // precious frame time.
   err = vkQueueWaitIdle(queue);
   assert(err == VK_SUCCESS);
 
@@ -609,7 +549,7 @@ setup_render_pass() {
   attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
   attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
   attachments[0].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-  attachments[0].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+  attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
 
   attachments[1].flags = 0;
   attachments[1].format = _depth_stencil_format;
@@ -622,6 +562,8 @@ setup_render_pass() {
   attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
 
   if (get_clear_color_active()) {
+    // We don't care about the current contents.
+    attachments[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
     attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
   } else {
     attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
@@ -635,6 +577,11 @@ setup_render_pass() {
     attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
   }
 
+  if (get_clear_depth_active() && get_clear_stencil_active()) {
+    // We don't care about the current contents.
+    attachments[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+  }
+
   VkAttachmentReference color_reference;
   color_reference.attachment = 0;
   color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
@@ -705,11 +652,6 @@ destroy_swapchain() {
     vkResetCommandBuffer(vkgsg->_cmd, 0);
   }
 
-  if (!_present_cmds.empty()) {
-    vkFreeCommandBuffers(device, vkgsg->_cmd_pool, _present_cmds.size(), &_present_cmds[0]);
-    _present_cmds.clear();
-  }
-
   // Destroy the resources held for each link in the swap chain.
   SwapBuffers::iterator it;
   for (it = _swap_buffers.begin(); it != _swap_buffers.end(); ++it) {
@@ -717,23 +659,31 @@ destroy_swapchain() {
 
     // Destroy the framebuffers that use the swapchain images.
     vkDestroyFramebuffer(device, buffer._framebuffer, NULL);
-    vkDestroyImageView(device, buffer._image_view, NULL);
+    vkDestroyImageView(device, buffer._tc->_image_view, NULL);
+
+    buffer._tc->update_data_size_bytes(0);
+    delete buffer._tc;
   }
   _swap_buffers.clear();
 
-  if (_depth_stencil_view != VK_NULL_HANDLE) {
-    vkDestroyImageView(device, _depth_stencil_view, NULL);
-    _depth_stencil_view = VK_NULL_HANDLE;
-  }
+  if (_depth_stencil_tc != NULL) {
+    if (_depth_stencil_tc->_image_view != VK_NULL_HANDLE) {
+      vkDestroyImageView(device, _depth_stencil_tc->_image_view, NULL);
+      _depth_stencil_tc->_image_view = VK_NULL_HANDLE;
+    }
 
-  if (_depth_stencil_image != VK_NULL_HANDLE) {
-    vkDestroyImage(device, _depth_stencil_image, NULL);
-    _depth_stencil_image = VK_NULL_HANDLE;
-  }
+    if (_depth_stencil_tc->_image != VK_NULL_HANDLE) {
+      vkDestroyImage(device, _depth_stencil_tc->_image, NULL);
+      _depth_stencil_tc->_image = VK_NULL_HANDLE;
+    }
+
+    if (_depth_stencil_tc->_memory != VK_NULL_HANDLE) {
+      vkFreeMemory(device, _depth_stencil_tc->_memory, NULL);
+      _depth_stencil_tc->_memory = VK_NULL_HANDLE;
+    }
 
-  if (_depth_stencil_memory != VK_NULL_HANDLE) {
-    vkFreeMemory(device, _depth_stencil_memory, NULL);
-    _depth_stencil_memory = VK_NULL_HANDLE;
+    delete _depth_stencil_tc;
+    _depth_stencil_tc = NULL;
   }
 
   // Destroy the previous swapchain.  This also destroys the swapchain images.
@@ -743,7 +693,6 @@ destroy_swapchain() {
   }
 
   _image_index = 0;
-  _depth_stencil_layout_defined = false;
 }
 
 /**
@@ -782,6 +731,8 @@ create_swapchain() {
     alloca(sizeof(VkPresentModeKHR) * num_present_modes);
   err = vkGetPhysicalDeviceSurfacePresentModesKHR(vkpipe->_gpu, _surface, &num_present_modes, present_modes);
 
+  // Note that we set the usage to include VK_IMAGE_USAGE_TRANSFER_SRC_BIT
+  // since we can at any time be asked to copy the framebuffer to a texture.
   VkSwapchainCreateInfoKHR swapchain_info;
   swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
   swapchain_info.pNext = NULL;
@@ -792,7 +743,7 @@ create_swapchain() {
   swapchain_info.imageExtent.width = _size[0];
   swapchain_info.imageExtent.height = _size[1];
   swapchain_info.imageArrayLayers = 1;
-  swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+  swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
   swapchain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
   swapchain_info.queueFamilyIndexCount = 0;
   swapchain_info.pQueueFamilyIndices = NULL;
@@ -827,7 +778,6 @@ create_swapchain() {
   vkGetSwapchainImagesKHR(device, _swapchain, &num_images, NULL);
   _swap_buffers.resize(num_images);
   _fb_properties.set_back_buffers(num_images - 1);
-  _depth_stencil_layout_defined = false;
   _image_index = 0;
 
   memset(&_swap_buffers[0], 0, sizeof(SwapBuffer) * num_images);
@@ -835,10 +785,17 @@ create_swapchain() {
   VkImage *images = (VkImage *)alloca(sizeof(VkImage) * num_images);
   vkGetSwapchainImagesKHR(device, _swapchain, &num_images, images);
 
+  PreparedGraphicsObjects *pgo = vkgsg->get_prepared_objects();
   // Now create an image view for each image.
   for (uint32_t i = 0; i < num_images; ++i) {
     SwapBuffer &buffer = _swap_buffers[i];
-    buffer._image = images[i];
+    buffer._tc = new VulkanTextureContext(pgo, images[i], swapchain_info.imageFormat);
+    buffer._tc->_aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT;
+    buffer._tc->_extent.width = swapchain_info.imageExtent.width;
+    buffer._tc->_extent.height = swapchain_info.imageExtent.height;
+    buffer._tc->_extent.depth = 1;
+    buffer._tc->_mip_levels = 1;
+    buffer._tc->_array_layers = 1;
 
     VkImageViewCreateInfo view_info;
     view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
@@ -857,7 +814,7 @@ create_swapchain() {
     view_info.subresourceRange.baseArrayLayer = 0;
     view_info.subresourceRange.layerCount = 1;
 
-    err = vkCreateImageView(device, &view_info, NULL, &buffer._image_view);
+    err = vkCreateImageView(device, &view_info, NULL, &buffer._tc->_image_view);
     if (err) {
       vulkan_error(err, "Failed to create image view for swapchain");
       return false;
@@ -865,8 +822,7 @@ create_swapchain() {
   }
 
   // Now create a depth image.
-  _depth_stencil_image = VK_NULL_HANDLE;
-  _depth_stencil_view = VK_NULL_HANDLE;
+  _depth_stencil_tc = NULL;
   bool have_ds = (_depth_stencil_format != VK_FORMAT_UNDEFINED);
 
   if (have_ds) {
@@ -889,7 +845,8 @@ create_swapchain() {
     depth_img_info.pQueueFamilyIndices = NULL;
     depth_img_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 
-    err = vkCreateImage(device, &depth_img_info, NULL, &_depth_stencil_image);
+    VkImage depth_stencil_image;
+    err = vkCreateImage(device, &depth_img_info, NULL, &depth_stencil_image);
     if (err) {
       vulkan_error(err, "Failed to create depth image");
       return false;
@@ -897,7 +854,7 @@ create_swapchain() {
 
     // Get the memory requirements, and find an appropriate heap to alloc in.
     VkMemoryRequirements mem_reqs;
-    vkGetImageMemoryRequirements(device, _depth_stencil_image, &mem_reqs);
+    vkGetImageMemoryRequirements(device, depth_stencil_image, &mem_reqs);
 
     VkMemoryAllocateInfo alloc_info;
     alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
@@ -909,14 +866,15 @@ create_swapchain() {
       return false;
     }
 
-    err = vkAllocateMemory(device, &alloc_info, NULL, &_depth_stencil_memory);
+    VkDeviceMemory depth_stencil_memory;
+    err = vkAllocateMemory(device, &alloc_info, NULL, &depth_stencil_memory);
     if (err) {
       vulkan_error(err, "Failed to allocate memory for depth image");
       return false;
     }
 
     // Bind memory to image.
-    err = vkBindImageMemory(device, _depth_stencil_image, _depth_stencil_memory, 0);
+    err = vkBindImageMemory(device, depth_stencil_image, depth_stencil_memory, 0);
     if (err) {
       vulkan_error(err, "Failed to bind memory to depth image");
       return false;
@@ -926,7 +884,7 @@ create_swapchain() {
     view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
     view_info.pNext = NULL;
     view_info.flags = 0;
-    view_info.image = _depth_stencil_image;
+    view_info.image = depth_stencil_image;
     view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
     view_info.format = depth_img_info.format;
     view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
@@ -943,16 +901,27 @@ create_swapchain() {
       view_info.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
     }
 
-    err = vkCreateImageView(device, &view_info, NULL, &_depth_stencil_view);
+    VkImageView depth_stencil_view;
+    err = vkCreateImageView(device, &view_info, NULL, &depth_stencil_view);
     if (err) {
       vulkan_error(err, "Failed to create image view for depth/stencil");
       return false;
     }
+
+    _depth_stencil_tc = new VulkanTextureContext(pgo, depth_stencil_image, view_info.format);
+    _depth_stencil_tc->_extent = depth_img_info.extent;
+    _depth_stencil_tc->_mip_levels = depth_img_info.mipLevels;
+    _depth_stencil_tc->_array_layers = depth_img_info.arrayLayers;
+    _depth_stencil_tc->_aspect_mask = _depth_stencil_aspect_mask;
+    _depth_stencil_tc->_memory = depth_stencil_memory;
+    _depth_stencil_tc->_image_view = depth_stencil_view;
   }
 
   // Now finally create a framebuffer for each link in the swap chain.
   VkImageView attach_views[2];
-  attach_views[1] = _depth_stencil_view;
+  if (have_ds) {
+    attach_views[1] = _depth_stencil_tc->_image_view;
+  }
 
   VkFramebufferCreateInfo fb_info;
   fb_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
@@ -967,7 +936,7 @@ create_swapchain() {
 
   for (uint32_t i = 0; i < num_images; ++i) {
     SwapBuffer &buffer = _swap_buffers[i];
-    attach_views[0] = buffer._image_view;
+    attach_views[0] = buffer._tc->_image_view;
     err = vkCreateFramebuffer(device, &fb_info, NULL, &buffer._framebuffer);
     if (err) {
       vulkan_error(err, "Failed to create framebuffer");
@@ -975,78 +944,6 @@ create_swapchain() {
     }
   }
 
-  // Prerecord the command buffers to transition the images into a presentable
-  // state.
-  VkCommandBufferAllocateInfo cmd_alloc;
-  cmd_alloc.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
-  cmd_alloc.pNext = NULL;
-  cmd_alloc.commandPool = vkgsg->_cmd_pool;
-  cmd_alloc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
-  cmd_alloc.commandBufferCount = num_images;
-
-  _present_cmds.resize(num_images);
-  err = vkAllocateCommandBuffers(device, &cmd_alloc, &_present_cmds[0]);
-  if (err) {
-    vulkan_error(err, "Failed to create command buffer");
-    return false;
-  }
-
-  for (uint32_t i = 0; i < num_images; ++i) {
-    SwapBuffer &buffer = _swap_buffers[i];
-    VkCommandBuffer present_cmd = _present_cmds[i];
-
-    VkCommandBufferBeginInfo begin_info;
-    begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
-    begin_info.pNext = NULL;
-    begin_info.flags = 0;
-    begin_info.pInheritanceInfo = NULL;
-
-    err = vkBeginCommandBuffer(present_cmd, &begin_info);
-    if (err) {
-      vulkan_error(err, "Can't begin command buffer");
-      return false;
-    }
-
-    VkImageMemoryBarrier barriers[2];
-    barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-    barriers[0].pNext = NULL;
-    barriers[0].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
-    barriers[0].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
-    barriers[0].oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
-    barriers[0].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
-    barriers[0].srcQueueFamilyIndex = vkgsg->_graphics_queue_family_index;
-    barriers[0].dstQueueFamilyIndex = vkgsg->_graphics_queue_family_index; //TODO support separate present queue.
-    barriers[0].image = buffer._image;
-    barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-    barriers[0].subresourceRange.baseMipLevel = 0;
-    barriers[0].subresourceRange.levelCount = 1;
-    barriers[0].subresourceRange.baseArrayLayer = 0;
-    barriers[0].subresourceRange.layerCount = 1;
-
-    if (have_ds) {
-      barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
-      barriers[1].pNext = NULL;
-      barriers[1].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
-      barriers[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
-      barriers[1].oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
-      barriers[1].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
-      barriers[1].srcQueueFamilyIndex = vkgsg->_graphics_queue_family_index;
-      barriers[1].dstQueueFamilyIndex = vkgsg->_graphics_queue_family_index; //TODO support separate present queue.
-      barriers[1].image = _depth_stencil_image;
-      barriers[1].subresourceRange.aspectMask = _depth_stencil_aspect_mask;
-      barriers[1].subresourceRange.baseMipLevel = 0;
-      barriers[1].subresourceRange.levelCount = 1;
-      barriers[1].subresourceRange.baseArrayLayer = 0;
-      barriers[1].subresourceRange.layerCount = 1;
-    }
-
-    vkCmdPipelineBarrier(present_cmd, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
-                         VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0,
-                         0, NULL, 0, NULL, 1 + (int)have_ds, barriers);
-
-    vkEndCommandBuffer(present_cmd);
-  }
-
   _swapchain_size = _size;
   return true;
 }

+ 2 - 9
panda/src/vulkandisplay/vulkanGraphicsWindow.h

@@ -71,22 +71,15 @@ private:
   VkSurfaceFormatKHR _surface_format;
 
   struct SwapBuffer {
-    VkImage _image;
-    VkImageView _image_view;
+    VulkanTextureContext *_tc;
     VkFramebuffer _framebuffer;
-    bool _layout_defined;
   };
   typedef pvector<SwapBuffer> SwapBuffers;
   SwapBuffers _swap_buffers;
   uint32_t _image_index;
 
-  pvector<VkCommandBuffer> _present_cmds;
-
-  bool _depth_stencil_layout_defined;
+  VulkanTextureContext *_depth_stencil_tc;
   VkFormat _depth_stencil_format;
-  VkImage _depth_stencil_image;
-  VkImageView _depth_stencil_view;
-  VkDeviceMemory _depth_stencil_memory;
   VkImageAspectFlags _depth_stencil_aspect_mask;
 
 public:

+ 43 - 2
panda/src/vulkandisplay/vulkanTextureContext.I

@@ -11,7 +11,6 @@
  * @date 2016-02-19
  */
 
-
 /**
  * Constructs a Texture context.  Follow this up with calls to fill in the
  * image, allocate the memory and create an image view.
@@ -21,5 +20,47 @@ VulkanTextureContext(PreparedGraphicsObjects *pgo, Texture *texture, int view) :
   TextureContext(pgo, texture, view),
   _image(VK_NULL_HANDLE),
   _memory(VK_NULL_HANDLE),
-  _image_view(VK_NULL_HANDLE) {
+  _image_view(VK_NULL_HANDLE),
+  _pack_bgr8(false),
+  _layout(VK_IMAGE_LAYOUT_UNDEFINED),
+  _access_mask(0),
+    //TODO: it is not clear to me what we should set srcStageMask to here.
+  _stage_mask(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT) {
+}
+
+/**
+ * Constructs a Texture context that's not associated with a particular
+ * texture, but tracks an already existing VkImage object.
+ */
+INLINE VulkanTextureContext::
+VulkanTextureContext(PreparedGraphicsObjects *pgo, VkImage image, VkFormat format) :
+  TextureContext(pgo, NULL, 0),
+  _format(format),
+  _image(image),
+  _memory(VK_NULL_HANDLE),
+  _image_view(VK_NULL_HANDLE),
+  _pack_bgr8(false),
+  _layout(VK_IMAGE_LAYOUT_UNDEFINED),
+  _access_mask(0),
+  //TODO: it is not clear to me what we should set srcStageMask to here.
+  _stage_mask(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT) {
+}
+
+/**
+ * Records a way that the image has been accessed.
+ */
+INLINE void VulkanTextureContext::
+access(VkPipelineStageFlags stage, VkAccessFlags access_mask) {
+  _stage_mask |= stage;
+  _access_mask |= access_mask;
+}
+
+/**
+ * Indicates that future commands aren't really interested in seeing the
+ * results of writes made before it to this image.
+ */
+INLINE void VulkanTextureContext::
+discard() {
+  _access_mask = 0;
+  _layout = VK_IMAGE_LAYOUT_UNDEFINED;
 }

+ 85 - 0
panda/src/vulkandisplay/vulkanTextureContext.cxx

@@ -14,3 +14,88 @@
 #include "vulkanTextureContext.h"
 
 TypeHandle VulkanTextureContext::_type_handle;
+
+/**
+ * Inserts commands to clear the image.
+ */
+void VulkanTextureContext::
+clear_color_image(VkCommandBuffer cmd, const VkClearColorValue &value) {
+  nassertv(_aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT);
+  nassertv(_image != VK_NULL_HANDLE);
+
+  // We're not interested in whatever was in here before.
+  discard();
+
+  transition(cmd, 0,//vkgsg->_graphics_queue_family_index,
+    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+    VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
+
+  VkImageSubresourceRange range;
+  range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+  range.baseMipLevel = 0;
+  range.levelCount = _mip_levels;
+  range.baseArrayLayer = 0;
+  range.layerCount = _array_layers;
+  vkCmdClearColorImage(cmd, _image, _layout, &value, 1, &range);
+}
+
+/**
+ * Inserts commands to clear the image.
+ */
+void VulkanTextureContext::
+clear_depth_stencil_image(VkCommandBuffer cmd, const VkClearDepthStencilValue &value) {
+  nassertv(_aspect_mask != VK_IMAGE_ASPECT_COLOR_BIT);
+  nassertv(_image != VK_NULL_HANDLE);
+
+  // We're not interested in whatever was in here before.
+  discard();
+
+  transition(cmd, 0,//vkgsg->_graphics_queue_family_index,
+    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+    VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
+
+  VkImageSubresourceRange range;
+  range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
+  range.baseMipLevel = 0;
+  range.levelCount = _mip_levels;
+  range.baseArrayLayer = 0;
+  range.layerCount = _array_layers;
+  vkCmdClearDepthStencilImage(cmd, _image, _layout, &value, 1, &range);
+}
+
+/**
+ * Issues a command to transition the image to a new layout or queue family.
+ * Does not (yet) do inter-queue synchronization.
+ */
+void VulkanTextureContext::
+transition(VkCommandBuffer cmd, uint32_t queue_family, VkImageLayout layout,
+           VkPipelineStageFlags dst_stage_mask, VkAccessFlags dst_access_mask) {
+
+  nassertv(_image != VK_NULL_HANDLE);
+  if (_layout == layout) {
+    return;
+  }
+
+  VkImageMemoryBarrier barrier;
+  barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+  barrier.pNext = NULL;
+  barrier.srcAccessMask = _access_mask;
+  barrier.dstAccessMask = dst_access_mask;
+  barrier.oldLayout = _layout;
+  barrier.newLayout = layout;
+  barrier.srcQueueFamilyIndex = 0;//_graphics_queue_family_index;
+  barrier.dstQueueFamilyIndex = 0;//_graphics_queue_family_index;
+  barrier.image = _image;
+  barrier.subresourceRange.aspectMask = _aspect_mask;
+  barrier.subresourceRange.baseMipLevel = 0;
+  barrier.subresourceRange.levelCount = _mip_levels;
+  barrier.subresourceRange.baseArrayLayer = 0;
+  barrier.subresourceRange.layerCount = _array_layers;
+
+  vkCmdPipelineBarrier(cmd, _stage_mask, dst_stage_mask, 0,
+                       0, NULL, 0, NULL, 1, &barrier);
+
+  _layout = layout;
+  _access_mask = dst_access_mask;
+  _stage_mask = dst_stage_mask;
+}

+ 15 - 0
panda/src/vulkandisplay/vulkanTextureContext.h

@@ -23,14 +23,25 @@
 class EXPCL_VULKANDISPLAY VulkanTextureContext : public TextureContext {
 public:
   INLINE VulkanTextureContext(PreparedGraphicsObjects *pgo, Texture *texture, int view);
+  INLINE VulkanTextureContext(PreparedGraphicsObjects *pgo, VkImage image, VkFormat format);
   ~VulkanTextureContext() {};
 
   ALLOC_DELETED_CHAIN(VulkanTextureContext);
 
+  INLINE void access(VkPipelineStageFlags stage_mask, VkAccessFlags access_mask);
+  INLINE void discard();
+
+  void clear_color_image(VkCommandBuffer cmd, const VkClearColorValue &value);
+  void clear_depth_stencil_image(VkCommandBuffer cmd, const VkClearDepthStencilValue &value);
+
+  void transition(VkCommandBuffer cmd, uint32_t queue_family, VkImageLayout layout,
+                  VkPipelineStageFlags dst_stage_mask, VkAccessFlags dst_access_mask);
+
 public:
   VkFormat _format;
   VkExtent3D _extent;
   int _mipmap_begin, _mipmap_end;
+  uint32_t _mip_levels;
   uint32_t _array_layers;
   VkImageAspectFlags _aspect_mask;
   bool _generate_mipmaps;
@@ -40,6 +51,10 @@ public:
   VkDeviceMemory _memory;
   VkImageView _image_view;
 
+  VkImageLayout _layout;
+  VkAccessFlags _access_mask;
+  VkPipelineStageFlags _stage_mask;
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;