Просмотр исходного кода

Beginnings of Vulkan rendering back-end

rdb 10 лет назад
Родитель
Сommit
18fdcc2dbb

+ 20 - 2
makepanda/makepanda.py

@@ -71,8 +71,9 @@ if "MACOSX_DEPLOYMENT_TARGET" in os.environ:
     OSXTARGET=os.environ["MACOSX_DEPLOYMENT_TARGET"]
 
 PkgListSet(["PYTHON", "DIRECT",                        # Python support
-  "GL", "GLES", "GLES2"] + DXVERSIONS + ["TINYDISPLAY", "NVIDIACG", # 3D graphics
-  "EGL",                                               # OpenGL (ES) integration
+  "GL", "VULKAN"] + DXVERSIONS + ["TINYDISPLAY",       # 3D graphics
+  "EGL", "GLES", "GLES2",                              # OpenGL (ES) integration
+  "NVIDIACG",                                          # Shaders
   "EIGEN",                                             # Linear algebra acceleration
   "OPENAL", "FMODEX",                                  # Audio playback
   "VORBIS", "FFMPEG", "SWSCALE", "SWRESAMPLE",         # Audio decoding
@@ -461,6 +462,7 @@ SdkLocateWindows(WINDOWS_SDK)
 SdkLocatePhysX()
 SdkLocateSpeedTree()
 SdkLocateAndroid()
+SdkLocateVulkan()
 
 SdkAutoDisableDirectX()
 SdkAutoDisableMaya()
@@ -541,6 +543,9 @@ if (COMPILER == "MSVC"):
                     DefSymbol(pkg, "_UNICODE", "")
             elif (pkg[:2]=="DX"):
                 IncDirectory(pkg, SDK[pkg]      + "/include")
+            elif pkg == "VULKAN":
+                IncDirectory(pkg, SDK[pkg] + "/Include")
+                LibName(pkg, SDK[pkg] + "/Source/lib/vulkan-1.lib")
             elif GetThirdpartyDir() is not None:
                 IncDirectory(pkg, GetThirdpartyDir() + pkg.lower() + "/include")
     for pkg in DXVERSIONS:
@@ -2992,6 +2997,7 @@ else:
     CopyAllHeaders('panda/src/x11display')
     CopyAllHeaders('panda/src/glxdisplay')
 CopyAllHeaders('panda/src/egldisplay')
+CopyAllHeaders('panda/src/vulkandisplay')
 CopyAllHeaders('panda/metalibs/pandagl')
 CopyAllHeaders('panda/metalibs/pandagles')
 CopyAllHeaders('panda/metalibs/pandagles2')
@@ -4567,6 +4573,18 @@ if (PkgSkip("EGL")==0 and PkgSkip("GLES2")==0 and PkgSkip("X11")==0 and not RUNT
   TargetAdd('libpandagles2.dll', input=COMMON_PANDA_LIBS)
   TargetAdd('libpandagles2.dll', opts=['MODULE', 'GLES2', 'EGL', 'X11', 'XRANDR', 'XF86DGA', 'XCURSOR'])
 
+#
+# DIRECTORY: panda/src/vulkandisplay/
+#
+
+if (GetTarget() == 'windows' and PkgSkip("GL")==0 and not RUNTIME):
+  OPTS=['DIR:panda/src/vulkandisplay', 'DIR:panda/src/vulkandisplay', 'BUILDING:VULKANDISPLAY', 'VULKAN']
+  TargetAdd('p3vulkandisplay_composite1.obj', opts=OPTS, input='p3vulkandisplay_composite1.cxx')
+  TargetAdd('libp3vulkandisplay.dll', input='p3vulkandisplay_composite1.obj')
+  TargetAdd('libp3vulkandisplay.dll', input='libp3windisplay.dll')
+  TargetAdd('libp3vulkandisplay.dll', input=COMMON_PANDA_LIBS)
+  TargetAdd('libp3vulkandisplay.dll', opts=['MODULE', 'VULKAN'])
+
 #
 # DIRECTORY: panda/src/ode/
 #

+ 11 - 0
makepanda/makepandacore.py

@@ -2179,6 +2179,17 @@ def SdkLocateAndroid():
     LibName("ALWAYS", stl_lib)
     CopyFile(os.path.join(GetOutputDir(), 'libs', abi, 'libgnustl_shared.so'), stl_lib)
 
+def SdkLocateVulkan():
+    if GetHost() != 'windows' or GetTarget() != 'windows':
+        return
+
+    path = 'C:/VulkanSDK'
+    if os.path.isdir(path):
+        # Use the latest version.
+        versions = os.listdir(path)
+        versions.sort()
+        SDK["VULKAN"] = path + '/' + versions[-1]
+
 ########################################################################
 ##
 ## SDK Auto-Disables

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

@@ -519,7 +519,7 @@ get_scene() const {
  * call Texture::prepare().
  */
 TextureContext *GraphicsStateGuardian::
-prepare_texture(Texture *) {
+prepare_texture(Texture *, int) {
   return (TextureContext *)NULL;
 }
 

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

@@ -285,7 +285,7 @@ PUBLISHED:
   MAKE_PROPERTY(scene, get_scene, set_scene);
 
 public:
-  virtual TextureContext *prepare_texture(Texture *tex);
+  virtual TextureContext *prepare_texture(Texture *tex, int view);
   virtual bool update_texture(TextureContext *tc, bool force);
   virtual void release_texture(TextureContext *tc);
   virtual bool extract_texture_data(Texture *tex);

+ 8 - 0
panda/src/pandabase/pandasymbols.h

@@ -277,6 +277,14 @@
   #define EXPTP_VRPN IMPORT_TEMPL
 #endif
 
+#ifdef BUILDING_VULKANDISPLAY
+  #define EXPCL_VULKANDISPLAY EXPORT_CLASS
+  #define EXPTP_VULKANDISPLAY EXPORT_TEMPL
+#else
+  #define EXPCL_VULKANDISPLAY IMPORT_CLASS
+  #define EXPTP_VULKANDISPLAY IMPORT_TEMPL
+#endif
+
 #if (defined(WIN32_VC) || defined(WIN64_VC)) && !defined(CPPPARSER)
 #define INLINE_LINMATH __forceinline
 #define INLINE_MATHUTIL __forceinline

+ 143 - 0
panda/src/vulkandisplay/config_vulkandisplay.cxx

@@ -0,0 +1,143 @@
+/**
+ * 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 config_vulkandisplay.cxx
+ * @author rdb
+ * @date 2016-02-16
+ */
+
+#include "config_vulkandisplay.h"
+#include "vulkanGraphicsPipe.h"
+#include "vulkanGraphicsStateGuardian.h"
+#include "vulkanGraphicsWindow.h"
+#include "graphicsPipeSelection.h"
+#include "dconfig.h"
+#include "pandaSystem.h"
+
+Configure(config_vulkandisplay);
+NotifyCategoryDef(vulkandisplay, "display");
+
+ConfigureFn(config_vulkandisplay) {
+  init_libvulkandisplay();
+}
+
+#define VK_ERROR_INVALID_SHADER_NV -1000012000
+
+/**
+ * Initializes the library.  This must be called at least once before any of
+ * the functions or classes in this library can be used.  Normally it will be
+ * called by the static initializers and need not be called explicitly, but
+ * special cases exist.
+ */
+void
+init_libvulkandisplay() {
+  static bool initialized = false;
+  if (initialized) {
+    return;
+  }
+  initialized = true;
+
+  VulkanGraphicsPipe::init_type();
+  VulkanGraphicsStateGuardian::init_type();
+  VulkanGraphicsWindow::init_type();
+
+  GraphicsPipeSelection *selection = GraphicsPipeSelection::get_global_ptr();
+  selection->add_pipe_type(VulkanGraphicsPipe::get_class_type(),
+                           VulkanGraphicsPipe::pipe_constructor);
+
+  PandaSystem *ps = PandaSystem::get_global_ptr();
+  ps->set_system_tag("Vulkan", "native_window_system", "Win");
+}
+
+/**
+ * Returns the TypeHandle index of the recommended graphics pipe type defined
+ * by this module.  Which is VulkanGraphicsPipe of course, duh.
+ */
+int
+get_pipe_type_p3vulkandisplay() {
+  return VulkanGraphicsPipe::get_class_type().get_index();
+}
+
+/**
+ * Returns a string describing a VkResult code, or NULL if it is unrecognized.
+ */
+static const char *const string_result(VkResult result) {
+  switch (result) {
+  case VK_SUCCESS:
+    return "Success.";
+  case VK_NOT_READY:
+    return "Fence or query has not yet completed.";
+  case VK_TIMEOUT:
+    return "Wait operation has not completed in the specified time.";
+  case VK_EVENT_SET:
+    return "Event is signalled.";
+  case VK_EVENT_RESET:
+    return "Event is unsignalled.";
+  case VK_INCOMPLETE:
+    return "Return array too small for result.";
+  case VK_ERROR_OUT_OF_HOST_MEMORY:
+    return "Out of host memory.";
+  case VK_ERROR_OUT_OF_DEVICE_MEMORY:
+    return "Out of device memory.";
+  case VK_ERROR_INITIALIZATION_FAILED:
+    return "Initialization failed.";
+  case VK_ERROR_DEVICE_LOST:
+    return "Device lost.";
+  case VK_ERROR_MEMORY_MAP_FAILED:
+    return "Mapping of a memory object has failed.";
+  case VK_ERROR_LAYER_NOT_PRESENT:
+    return "Specified layer does not exist.";
+  case VK_ERROR_EXTENSION_NOT_PRESENT:
+    return "Specified extension does not exist.";
+  case VK_ERROR_FEATURE_NOT_PRESENT:
+    return "Requested feature not available.";
+  case VK_ERROR_INCOMPATIBLE_DRIVER:
+    return "Incompatible Vulkan driver.";
+  case VK_ERROR_TOO_MANY_OBJECTS:
+    return "Exhausted object limit.";
+  case VK_ERROR_FORMAT_NOT_SUPPORTED:
+    return "Requested format is not supported.";
+  case VK_ERROR_SURFACE_LOST_KHR:
+    return "Surface lost.";
+  case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
+    return "Native window is in use.";
+  case VK_SUBOPTIMAL_KHR:
+    return "Suboptimal.";
+  case VK_ERROR_OUT_OF_DATE_KHR:
+    return "Out of date.";
+  case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR:
+    return "Incompatible display.";
+  case VK_ERROR_VALIDATION_FAILED_EXT:
+    return "Validation failed.";
+  case VK_ERROR_INVALID_SHADER_NV:
+    return "Shader(s) failed to compile or link.";
+  default:
+    break;
+  }
+  return NULL;
+}
+
+/**
+ * Formats a Vulkan error message.
+ */
+void vulkan_error(VkResult result, const char *message) {
+  const char *const str = string_result(result);
+  if (str == NULL) {
+    // Unrecognized code.  Display error number.
+    if (result < 0) {
+      vulkandisplay_cat.error()
+        << message << ": Unknown error " << result << "\n";
+    } else {
+      vulkandisplay_cat.error()
+        << message << ": Unknown result " << result << "\n";
+    }
+  } else {
+    vulkandisplay_cat.error()
+      << message << ": " << str << "\n";
+  }
+}

+ 30 - 0
panda/src/vulkandisplay/config_vulkandisplay.h

@@ -0,0 +1,30 @@
+/**
+ * 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 config_vulkandisplay.h
+ * @author rdb
+ * @date 2016-02-16
+ */
+
+#ifndef CONFIG_VULKANDISPLAY_H
+#define CONFIG_VULKANDISPLAY_H
+
+#include "pandabase.h"
+#include "notifyCategoryProxy.h"
+
+NotifyCategoryDecl(vulkandisplay, EXPCL_VULKANDISPLAY, EXPTP_VULKANDISPLAY);
+
+extern EXPCL_VULKANDISPLAY void init_libvulkandisplay();
+extern "C" EXPCL_VULKANDISPLAY int get_pipe_type_p3vulkandisplay();
+
+#define VK_USE_PLATFORM_WIN32_KHR
+#include <vulkan/vulkan.h>
+
+void vulkan_error(VkResult result, const char *message);
+
+#endif

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

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

+ 12 - 0
panda/src/vulkandisplay/vulkanGraphicsPipe.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 vulkanGraphicsPipe.I
+ * @author rdb
+ * @date 2016-02-16
+ */

+ 266 - 0
panda/src/vulkandisplay/vulkanGraphicsPipe.cxx

@@ -0,0 +1,266 @@
+/**
+ * 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 vulkanGraphicsPipe.cxx
+ * @author rdb
+ * @date 2016-02-16
+ */
+
+#include "vulkanGraphicsPipe.h"
+#include "vulkanGraphicsWindow.h"
+#include "pandaVersion.h"
+
+/**
+ * Callback called by the VK_EXT_debug_report extension whenever one of the
+ * validation layers has something to report.
+ */
+static VkBool32 debug_callback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT, uint64_t, size_t, int32_t, const char *prefix, const char *message, void *) {
+  NotifySeverity severity;
+  if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
+    severity = NS_error;
+  } else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
+    severity = NS_warning;
+  } else if (flags & (VK_DEBUG_REPORT_INFORMATION_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)) {
+    severity = NS_info;
+  } else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT) {
+    severity = NS_debug;
+  } else {
+    severity = NS_spam;
+  }
+
+  vulkandisplay_cat.out(severity) << message << "\n";
+
+  // Return true here to fail validation, causing an error to be returned from
+  // the validated function.
+  return VK_FALSE;
+}
+
+TypeHandle VulkanGraphicsPipe::_type_handle;
+
+/**
+ * Creates a Vulkan instance and picks a GPU to use.
+ */
+VulkanGraphicsPipe::
+VulkanGraphicsPipe() {
+  _is_valid = false;
+
+  const char *const layers[] = {
+    "VK_LAYER_LUNARG_standard_validation",
+  };
+
+  const char *extensions[] = {
+    "VK_EXT_debug_report",
+    "VK_KHR_surface",
+    "VK_KHR_win32_surface"
+  };
+
+  VkApplicationInfo app_info;
+  app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+  app_info.pNext = NULL;
+  app_info.pApplicationName = NULL;
+  app_info.applicationVersion = 0;
+  app_info.pEngineName = "Panda3D";
+  app_info.engineVersion = PANDA_NUMERIC_VERSION;
+  app_info.apiVersion = VK_API_VERSION;
+
+  VkInstanceCreateInfo inst_info;
+  inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+  inst_info.pNext = NULL;
+  inst_info.pApplicationInfo = &app_info;
+  inst_info.enabledLayerCount = 1;
+  inst_info.ppEnabledLayerNames = layers;
+  inst_info.enabledExtensionCount = 3;
+  inst_info.ppEnabledExtensionNames = extensions;
+
+  VkResult err = vkCreateInstance(&inst_info, NULL, &_instance);
+  if (err == VK_ERROR_INCOMPATIBLE_DRIVER) {
+    vulkandisplay_cat.error()
+      << "Cannot find a compatible Vulkan installable client driver (ICD).\n";
+    return;
+
+  } else if (err == VK_ERROR_EXTENSION_NOT_PRESENT) {
+    vulkandisplay_cat.error()
+      << "Cannot find a specified extension library.\n";
+    return;
+
+  } else if (err) {
+    vulkan_error(err, "Failure to create Vulkan instance");
+    return;
+  }
+
+  // Set a debug callback.
+  PFN_vkCreateDebugReportCallbackEXT pvkCreateDebugReportCallback =
+    (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(_instance, "vkCreateDebugReportCallbackEXT");
+
+  if (pvkCreateDebugReportCallback) {
+    VkDebugReportCallbackCreateInfoEXT cb_info;
+    cb_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
+    cb_info.pNext = NULL;
+    cb_info.flags = 0;
+    cb_info.pfnCallback = &debug_callback;
+    cb_info.pUserData = NULL;
+
+    // Tell the extension which severities to report, based on the enabled
+    // notify categories.
+    if (vulkandisplay_cat.is_debug()) {
+      cb_info.flags |= VK_DEBUG_REPORT_DEBUG_BIT_EXT;
+    }
+    if (vulkandisplay_cat.is_info()) {
+      cb_info.flags |= VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
+                       VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
+    }
+    if (vulkandisplay_cat.is_warning()) {
+      cb_info.flags |= VK_DEBUG_REPORT_WARNING_BIT_EXT;
+    }
+    if (vulkandisplay_cat.is_error()) {
+      cb_info.flags |= VK_DEBUG_REPORT_ERROR_BIT_EXT;
+    }
+
+    VkDebugReportCallbackEXT callback;
+    err = pvkCreateDebugReportCallback(_instance, &cb_info, NULL, &callback);
+    if (err) {
+      vulkan_error(err, "Failed to create debug report callback");
+    }
+  } else {
+    vulkandisplay_cat.warning()
+      << "Cannot find vkCreateDebugReportCallbackEXT function.  Debug "
+         "reporting will not be enabled.\n";
+  }
+
+  // Enumerate the available GPUs.
+  uint32_t gpu_count;
+  err = vkEnumeratePhysicalDevices(_instance, &gpu_count, NULL);
+  nassertv(!err && gpu_count > 0);
+
+  VkPhysicalDevice *physical_devices =
+    (VkPhysicalDevice *)alloca(sizeof(VkPhysicalDevice) * gpu_count);
+
+  err = vkEnumeratePhysicalDevices(_instance, &gpu_count, physical_devices);
+  if (err) {
+    vulkan_error(err, "Failed to enumerate GPUs");
+    return;
+  }
+
+  // Just pick the first GPU for now.
+  _gpu = physical_devices[0];
+
+  // Query memory properties.
+  vkGetPhysicalDeviceMemoryProperties(_gpu, &_memory_properties);
+
+  // Query queue information.  Currently unused, but keeps validator happy.
+  uint32_t num_families;
+  vkGetPhysicalDeviceQueueFamilyProperties(_gpu, &num_families, NULL);
+  _queue_families.resize(num_families);
+  vkGetPhysicalDeviceQueueFamilyProperties(_gpu, &num_families, &_queue_families[0]);
+
+  _is_valid = true;
+}
+
+/**
+ * Cleans up the Vulkan instance.
+ */
+VulkanGraphicsPipe::
+~VulkanGraphicsPipe() {
+}
+
+/**
+ * Finds the index of the memory type that fits the given requirements.
+ */
+bool VulkanGraphicsPipe::
+find_memory_type(uint32_t &type_index, uint32_t type_bits, VkFlags required_flags) const {
+  for (int i = 0; type_bits > 0; ++i) {
+    if ((type_bits & 1) == 1) {
+      // Type is available.  Does it match the required properties?
+      if ((_memory_properties.memoryTypes[i].propertyFlags & required_flags) == required_flags) {
+        type_index = i;
+        return true;
+      }
+    }
+    type_bits >>= 1;
+  }
+
+  // Not found.
+  return false;
+}
+
+/**
+ * Returns the name of the rendering interface associated with this
+ * GraphicsPipe.  This is used to present to the user to allow him/her to
+ * choose between several possible GraphicsPipes available on a particular
+ * platform, so the name should be meaningful and unique for a given platform.
+ */
+string VulkanGraphicsPipe::
+get_interface_name() const {
+  return "Vulkan";
+}
+
+/**
+ * This function is passed to the GraphicsPipeSelection object to allow the
+ * user to make a default VulkanGraphicsPipe.
+ */
+PT(GraphicsPipe) VulkanGraphicsPipe::
+pipe_constructor() {
+  return new VulkanGraphicsPipe;
+}
+
+/**
+ * Creates a new window or buffer on the pipe, if possible.  This routine is
+ * only called from GraphicsEngine::make_output.
+ */
+PT(GraphicsOutput) VulkanGraphicsPipe::
+make_output(const string &name,
+            const FrameBufferProperties &fb_prop,
+            const WindowProperties &win_prop,
+            int flags,
+            GraphicsEngine *engine,
+            GraphicsStateGuardian *gsg,
+            GraphicsOutput *host,
+            int retry,
+            bool &precertify) {
+
+  if (!_is_valid) {
+    return NULL;
+  }
+
+  VulkanGraphicsStateGuardian *vkgsg = 0;
+  if (gsg != 0) {
+    DCAST_INTO_R(vkgsg, gsg, NULL);
+  }
+
+  // First thing to try: a VulkanGraphicsWindow
+
+  if (retry == 0) {
+    if ((flags & BF_require_parasite) != 0 ||
+        (flags & BF_refuse_window) != 0 ||
+        (flags & BF_resizeable) != 0 ||
+        (flags & BF_size_track_host) != 0 ||
+        (flags & BF_rtt_cumulative) != 0 ||
+        (flags & BF_can_bind_color) != 0 ||
+        (flags & BF_can_bind_every) != 0 ||
+        (flags & BF_can_bind_layered) != 0) {
+      return NULL;
+    }
+    return new VulkanGraphicsWindow(engine, this, name, fb_prop, win_prop,
+                                    flags, gsg, host);
+  }
+
+
+  // Nothing else left to try.
+  return NULL;
+}
+
+/**
+ * This is called when make_output() is used to create a
+ * CallbackGraphicsWindow.  If the GraphicsPipe can construct a GSG that's not
+ * associated with any particular window object, do so now, assuming the
+ * correct graphics context has been set up externally.
+ */
+PT(GraphicsStateGuardian) VulkanGraphicsPipe::
+make_callback_gsg(GraphicsEngine *engine) {
+  return new VulkanGraphicsStateGuardian(engine, this, NULL);
+}

+ 82 - 0
panda/src/vulkandisplay/vulkanGraphicsPipe.h

@@ -0,0 +1,82 @@
+/**
+ * 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 vulkanGraphicsPipe.h
+ * @author rdb
+ * @date 2016-02-16
+ */
+
+#ifndef VULKANGRAPHICSPIPE_H
+#define VULKANGRAPHICSPIPE_H
+
+#include "config_vulkandisplay.h"
+#include "graphicsOutput.h"
+
+#include <vulkan/vulkan.h>
+
+#ifdef _WIN32
+#include "winGraphicsPipe.h"
+typedef WinGraphicsPipe BaseGraphicsPipe;
+#else
+#include "graphicsPipe.h"
+typedef GraphicsPipe BaseGraphicsPipe;
+#endif
+
+/**
+ * This graphics pipe represents the interface for creating Vulkan graphics
+ * windows, and manages a single Vulkan instance and device (for now).
+ */
+class EXPCL_VULKANDISPLAY VulkanGraphicsPipe : public BaseGraphicsPipe {
+public:
+  VulkanGraphicsPipe();
+  virtual ~VulkanGraphicsPipe();
+
+  bool find_memory_type(uint32_t &type_index, uint32_t type_bits, VkFlags required_flags) const;
+
+  virtual string get_interface_name() const;
+  static PT(GraphicsPipe) pipe_constructor();
+
+protected:
+  virtual PT(GraphicsOutput) make_output(const string &name,
+                                         const FrameBufferProperties &fb_prop,
+                                         const WindowProperties &win_prop,
+                                         int flags,
+                                         GraphicsEngine *engine,
+                                         GraphicsStateGuardian *gsg,
+                                         GraphicsOutput *host,
+                                         int retry,
+                                         bool &precertify);
+  virtual PT(GraphicsStateGuardian) make_callback_gsg(GraphicsEngine *engine);
+
+public:
+  VkInstance _instance;
+  VkPhysicalDevice _gpu;
+  VkPhysicalDeviceMemoryProperties _memory_properties;
+  pvector<VkQueueFamilyProperties> _queue_families;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    BaseGraphicsPipe::init_type();
+    register_type(_type_handle, "VulkanGraphicsPipe",
+                  BaseGraphicsPipe::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 "vulkanGraphicsPipe.I"
+
+#endif

+ 12 - 0
panda/src/vulkandisplay/vulkanGraphicsStateGuardian.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 vulkanGraphicsStateGuardian.I
+ * @author rdb
+ * @date 2016-02-16
+ */

+ 274 - 0
panda/src/vulkandisplay/vulkanGraphicsStateGuardian.cxx

@@ -0,0 +1,274 @@
+/**
+ * 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 vulkanGraphicsStateGuardian.cxx
+ * @author rdb
+ * @date 2016-02-16
+ */
+
+#include "vulkanGraphicsStateGuardian.h"
+#include "standardMunger.h"
+
+TypeHandle VulkanGraphicsStateGuardian::_type_handle;
+
+/**
+ *
+ */
+VulkanGraphicsStateGuardian::
+VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
+                            VulkanGraphicsStateGuardian *share_with) :
+  GraphicsStateGuardian(CS_default, engine, pipe),
+  _device(VK_NULL_HANDLE),
+  _queue(VK_NULL_HANDLE),
+  _cmd_pool(VK_NULL_HANDLE),
+  _cmd(VK_NULL_HANDLE)
+{
+  const char *const layers[] = {
+    "VK_LAYER_LUNARG_standard_validation",
+  };
+
+  const char *extensions[] = {
+    "VK_KHR_swapchain",
+  };
+
+  const float queue_priorities[1] = {0.0f};
+  VkDeviceQueueCreateInfo queue_info;
+  queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+  queue_info.pNext = NULL;
+  queue_info.flags = 0;
+  queue_info.queueFamilyIndex = 0;
+  queue_info.queueCount = 1;
+  queue_info.pQueuePriorities = queue_priorities;
+
+  VkDeviceCreateInfo device_info;
+  device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+  device_info.pNext = NULL;
+  device_info.flags = 0;
+  device_info.queueCreateInfoCount = 1;
+  device_info.pQueueCreateInfos = &queue_info;
+  device_info.enabledLayerCount = 1;
+  device_info.ppEnabledLayerNames = layers;
+  device_info.enabledExtensionCount = 1;
+  device_info.ppEnabledExtensionNames = extensions;
+  device_info.pEnabledFeatures = NULL;
+
+  VkResult
+  err = vkCreateDevice(pipe->_gpu, &device_info, NULL, &_device);
+  if (err) {
+    vulkan_error(err, "Failed to create device");
+    return;
+  }
+
+  vkGetDeviceQueue(_device, 0, 0, &_queue);
+
+  // Create a command pool to allocate command buffers from.
+  VkCommandPoolCreateInfo pool_info;
+  pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+  pool_info.pNext = NULL;
+  pool_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
+  pool_info.queueFamilyIndex = 0;
+
+  err = vkCreateCommandPool(_device, &pool_info, NULL, &_cmd_pool);
+  if (err) {
+    vulkan_error(err, "Failed to create command pool");
+    return;
+  }
+
+  _needs_reset = false;
+}
+
+/**
+ *
+ */
+VulkanGraphicsStateGuardian::
+~VulkanGraphicsStateGuardian() {
+}
+
+/**
+ * Creates a new GeomMunger object to munge vertices appropriate to this GSG
+ * for the indicated state.
+ */
+PT(GeomMunger) VulkanGraphicsStateGuardian::
+make_geom_munger(const RenderState *state, Thread *current_thread) {
+  PT(StandardMunger) munger = new StandardMunger(this, state, 1, GeomEnums::NT_packed_dabc, GeomEnums::C_color);
+  return GeomMunger::register_munger(munger, current_thread);
+}
+
+/**
+ * Clears the framebuffer within the current DisplayRegion, according to the
+ * flags indicated by the given DrawableRegion object.
+ *
+ * This does not set the DisplayRegion first.  You should call
+ * prepare_display_region() to specify the region you wish the clear operation
+ * to apply to.
+ */
+void VulkanGraphicsStateGuardian::
+clear(DrawableRegion *clearable) {
+  nassertv(_cmd != VK_NULL_HANDLE);
+  nassertv(clearable->is_any_clear_active());
+
+  VkClearAttachment attachments[2];
+  int ai = 0;
+
+  if (clearable->get_clear_color_active()) {
+    LColor color = clearable->get_clear_color();
+    VkClearAttachment &attachment = attachments[ai++];
+    attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+    attachment.colorAttachment = 0;
+    attachment.clearValue.color.float32[0] = color[0];
+    attachment.clearValue.color.float32[1] = color[1];
+    attachment.clearValue.color.float32[2] = color[2];
+    attachment.clearValue.color.float32[3] = color[3];
+  }
+
+  if (clearable->get_clear_depth_active() ||
+      clearable->get_clear_stencil_active()) {
+    VkClearAttachment &attachment = attachments[ai++];
+    attachment.aspectMask = 0;
+    if (clearable->get_clear_depth_active()) {
+      attachment.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT;
+    }
+    if (clearable->get_clear_stencil_active()) {
+      attachment.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
+    }
+    attachment.colorAttachment = 0; // unused
+    attachment.clearValue.depthStencil.depth = clearable->get_clear_depth();
+    attachment.clearValue.depthStencil.stencil = clearable->get_clear_stencil();
+  }
+
+  // Collect the rectangles we want to clear.
+  VkClearRect *rects = (VkClearRect *)alloca(sizeof(VkClearRect) * _viewports.size());
+  for (size_t i = 0; i < _viewports.size(); ++i) {
+    rects[i].rect = _viewports[i];
+    rects[i].baseArrayLayer = 0;
+    rects[i].layerCount = 1;
+  }
+
+  vkCmdClearAttachments(_cmd, ai, attachments, _viewports.size(), rects);
+}
+
+/**
+ * Prepare a display region for rendering (set up scissor region and viewport)
+ */
+void VulkanGraphicsStateGuardian::
+prepare_display_region(DisplayRegionPipelineReader *dr) {
+  nassertv(dr != (DisplayRegionPipelineReader *)NULL);
+  GraphicsStateGuardian::prepare_display_region(dr);
+
+  int count = dr->get_num_regions();
+  VkViewport *viewports = (VkViewport *)alloca(sizeof(VkViewport) * count);
+  _viewports.resize(count);
+
+  for (int i = 0; i < count; ++i) {
+    int x, y, w, h;
+    dr->get_region_pixels(i, x, y, w, h);
+    viewports[i].x = x;
+    viewports[i].y = y;
+    viewports[i].width = w;
+    viewports[i].height = h;
+
+    // Also save this in the _viewports array for later use.
+    _viewports[i].offset.x = x;
+    _viewports[i].offset.y = y;
+    _viewports[i].extent.width = w;
+    _viewports[i].extent.height = h;
+  }
+
+  vkCmdSetViewport(_cmd, 0, count, viewports);
+  vkCmdSetScissor(_cmd, 0, count, &_viewports[0]);
+}
+
+/**
+ * Called before each frame is rendered, to allow the GSG a chance to do any
+ * internal cleanup before beginning the frame.
+ *
+ * The return value is true if successful (in which case the frame will be
+ * drawn and end_frame() will be called later), or false if unsuccessful (in
+ * which case nothing will be drawn and end_frame() will not be called).
+ */
+bool VulkanGraphicsStateGuardian::
+begin_frame(Thread *current_thread) {
+  if (!GraphicsStateGuardian::begin_frame(current_thread)) {
+    return false;
+  }
+
+  // Create a command buffer.
+  VkCommandBufferAllocateInfo alloc_info;
+  alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+  alloc_info.pNext = NULL;
+  alloc_info.commandPool = _cmd_pool;
+  alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+  alloc_info.commandBufferCount = 1;
+
+  VkResult err;
+  err = vkAllocateCommandBuffers(_device, &alloc_info, &_cmd);
+  nassertr(!err, false);
+  nassertr(_cmd != VK_NULL_HANDLE, false);
+
+  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(_cmd, &begin_info);
+  if (err) {
+    vulkan_error(err, "Can't begin command buffer");
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * Called between begin_frame() and end_frame() to mark the beginning of
+ * drawing commands for a "scene" (usually a particular DisplayRegion) within
+ * a frame.  All 3-D drawing commands, except the clear operation, must be
+ * enclosed within begin_scene() .. end_scene(). This must be called in the
+ * draw thread.
+ *
+ * The return value is true if successful (in which case the scene will be
+ * drawn and end_scene() will be called later), or false if unsuccessful (in
+ * which case nothing will be drawn and end_scene() will not be called).
+ */
+bool VulkanGraphicsStateGuardian::
+begin_scene() {
+  return GraphicsStateGuardian::begin_scene();
+}
+
+/**
+ * Called between begin_frame() and end_frame() to mark the end of drawing
+ * commands for a "scene" (usually a particular DisplayRegion) within a frame.
+ * All 3-D drawing commands, except the clear operation, must be enclosed
+ * within begin_scene() .. end_scene().
+ */
+void VulkanGraphicsStateGuardian::
+end_scene() {
+  GraphicsStateGuardian::end_scene();
+}
+
+/**
+ * Called after each frame is rendered, to allow the GSG a chance to do any
+ * internal cleanup after rendering the frame, and before the window flips.
+ */
+void VulkanGraphicsStateGuardian::
+end_frame(Thread *current_thread) {
+  GraphicsStateGuardian::end_frame(current_thread);
+
+  nassertv(_cmd != VK_NULL_HANDLE);
+  vkEndCommandBuffer(_cmd);
+
+  //TODO: delete command buffer, schedule for deletion, or recycle.
+}
+
+/**
+ * Resets all internal state as if the gsg were newly created.
+ */
+void VulkanGraphicsStateGuardian::
+reset() {
+}

+ 75 - 0
panda/src/vulkandisplay/vulkanGraphicsStateGuardian.h

@@ -0,0 +1,75 @@
+/**
+ * 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 vulkanGraphicsStateGuardian.h
+ * @author rdb
+ * @date 2016-02-16
+ */
+
+#ifndef VULKANGRAPHICSSTATEGUARDIAN_H
+#define VULKANGRAPHICSSTATEGUARDIAN_H
+
+#include "config_vulkandisplay.h"
+
+#include <vulkan/vulkan.h>
+
+/**
+ * Manages a Vulkan device, and manages sending render commands to this
+ * device.
+ */
+class VulkanGraphicsStateGuardian : public GraphicsStateGuardian {
+public:
+  VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
+                              VulkanGraphicsStateGuardian *share_with);
+  virtual ~VulkanGraphicsStateGuardian();
+
+  virtual PT(GeomMunger) make_geom_munger(const RenderState *state,
+                                          Thread *current_thread);
+
+  virtual void clear(DrawableRegion *clearable);
+  virtual void prepare_display_region(DisplayRegionPipelineReader *dr);
+
+  virtual bool begin_frame(Thread *current_thread);
+  virtual bool begin_scene();
+  virtual void end_scene();
+  virtual void end_frame(Thread *current_thread);
+
+  virtual void reset();
+
+private:
+  VkDevice _device;
+  VkQueue _queue;
+  VkCommandPool _cmd_pool;
+  VkCommandBuffer _cmd;
+  pvector<VkRect2D> _viewports;
+
+  friend class VulkanGraphicsWindow;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    GraphicsStateGuardian::init_type();
+    register_type(_type_handle, "VulkanGraphicsStateGuardian",
+                  GraphicsStateGuardian::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;
+
+  friend class wglGraphicsBuffer;
+};
+
+#include "vulkanGraphicsStateGuardian.I"
+
+#endif

+ 12 - 0
panda/src/vulkandisplay/vulkanGraphicsWindow.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 vulkanGraphicsWindow.I
+ * @author rdb
+ * @date 2016-02-16
+ */

+ 551 - 0
panda/src/vulkandisplay/vulkanGraphicsWindow.cxx

@@ -0,0 +1,551 @@
+/**
+ * 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 vulkanGraphicsWindow.cxx
+ * @author rdb
+ * @date 2016-02-16
+ */
+
+#include "vulkanGraphicsWindow.h"
+#include "vulkanGraphicsStateGuardian.h"
+
+TypeHandle VulkanGraphicsWindow::_type_handle;
+
+/**
+ *
+ */
+VulkanGraphicsWindow::
+VulkanGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
+                  const string &name,
+                  const FrameBufferProperties &fb_prop,
+                  const WindowProperties &win_prop,
+                  int flags,
+                  GraphicsStateGuardian *gsg,
+                  GraphicsOutput *host) :
+  BaseGraphicsWindow(engine, pipe, name, fb_prop, win_prop, flags, gsg, host),
+  _surface(VK_NULL_HANDLE),
+  _swapchain(VK_NULL_HANDLE),
+  _render_pass(VK_NULL_HANDLE),
+  _present_complete(VK_NULL_HANDLE),
+  _image_index(0)
+{
+}
+
+/**
+ *
+ */
+VulkanGraphicsWindow::
+~VulkanGraphicsWindow() {
+}
+
+/**
+ * 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 VulkanGraphicsWindow::
+begin_frame(FrameMode mode, Thread *current_thread) {
+  begin_frame_spam(mode);
+  if (_gsg == (GraphicsStateGuardian *)NULL) {
+    return false;
+  }
+
+  if (!get_unexposed_draw() && !_got_expose_event) {
+    if (vulkandisplay_cat.is_spam()) {
+      vulkandisplay_cat.spam()
+        << "Not drawing " << this << ": unexposed.\n";
+    }
+    return false;
+  }
+
+  if (vulkandisplay_cat.is_spam()) {
+    vulkandisplay_cat.spam()
+      << "Drawing " << this << ": exposed.\n";
+  }
+
+  if (mode != FM_render) {
+    return true;
+  }
+
+  VulkanGraphicsStateGuardian *vkgsg;
+  DCAST_INTO_R(vkgsg, _gsg, false);
+  //vkgsg->reset_if_new();
+
+  // Instruct the GSG that we are commencing a new frame.  This will cause it
+  // to create a command buffer.
+  _gsg->set_current_properties(&get_fb_properties());
+  if (!_gsg->begin_frame(current_thread)) {
+    return false;
+  }
+
+  VkSemaphoreCreateInfo semaphore_info;
+  semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+  semaphore_info.pNext = NULL;
+  semaphore_info.flags = 0;
+
+  VkResult err;
+  err = vkCreateSemaphore(vkgsg->_device, &semaphore_info,
+                          NULL, &_present_complete);
+  nassertr(err == 0, false);
+
+  err = vkAcquireNextImageKHR(vkgsg->_device, _swapchain, UINT64_MAX,
+                              _present_complete, (VkFence)0, &_image_index);
+
+  /*if (mode == FM_render) {
+    clear_cube_map_selection();
+  }*/
+
+  // Now that we have a command buffer, start our render pass.
+  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];
+  clears[1].depthStencil.depth = get_clear_depth();
+  clears[1].depthStencil.stencil = get_clear_stencil();
+
+  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 = _framebuffers[_image_index];
+  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 = 2;
+  begin_info.pClearValues = clears;
+
+  vkCmdBeginRenderPass(cmd, &begin_info, VK_SUBPASS_CONTENTS_INLINE);
+
+  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 VulkanGraphicsWindow::
+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);
+
+    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();
+  }
+}
+
+/**
+ * This function will be called within the draw thread after end_frame() has
+ * been called on all windows, to initiate the exchange of the front and back
+ * buffers.
+ *
+ * This should instruct the window to prepare for the flip at the next video
+ * sync, but it should not wait.
+ *
+ * We have the two separate functions, begin_flip() and end_flip(), to make it
+ * easier to flip all of the windows at the same time.
+ */
+void VulkanGraphicsWindow::
+begin_flip() {
+}
+
+/**
+ * This function will be called within the draw thread after end_frame() has
+ * been called on all windows, to initiate the exchange of the front and back
+ * buffers.
+ *
+ * This should instruct the window to prepare for the flip when command, but
+ * will not actually flip
+ *
+ * We have the two separate functions, begin_flip() and end_flip(), to make it
+ * easier to flip all of the windows at the same time.
+ */
+void VulkanGraphicsWindow::
+ready_flip() {
+}
+
+/**
+ * This function will be called within the draw thread after begin_flip() has
+ * been called on all windows, to finish the exchange of the front and back
+ * buffers.
+ *
+ * This should cause the window to wait for the flip, if necessary.
+ */
+void VulkanGraphicsWindow::
+end_flip() {
+  VulkanGraphicsStateGuardian *vkgsg;
+  DCAST_INTO_V(vkgsg, _gsg);
+  VkDevice device = vkgsg->_device;
+  VkQueue queue = vkgsg->_queue;
+
+  nassertv(_present_complete != VK_NULL_HANDLE);
+
+  // 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 = 1;
+  submit_info.pCommandBuffers = &vkgsg->_cmd;
+  submit_info.signalSemaphoreCount = 0;
+  submit_info.pSignalSemaphores = NULL;
+
+  VkResult err;
+  err = vkQueueSubmit(queue, 1, &submit_info, 0);
+  if (err) {
+    vulkan_error(err, "Error submitting queue");
+    return;
+  }
+
+  VkResult results[1];
+  VkPresentInfoKHR present;
+  present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+  present.pNext = NULL;
+  present.waitSemaphoreCount = 1;
+  present.pWaitSemaphores = &_present_complete;
+  present.swapchainCount = 1;
+  present.pSwapchains = &_swapchain;
+  present.pImageIndices = &_image_index;
+  present.pResults = results;
+
+  err = vkQueuePresentKHR(queue, &present);
+  if (err == VK_ERROR_OUT_OF_DATE_KHR) {
+    cerr << "out of date.\n";
+
+  } else if (err == VK_SUBOPTIMAL_KHR) {
+    cerr << "suboptimal.\n";
+
+  } else if (err != VK_SUCCESS) {
+    vulkan_error(err, "Error presenting queue");
+    return;
+  }
+
+  nassertv(vkQueueWaitIdle(queue) == VK_SUCCESS);
+  assert(err == VK_SUCCESS);
+
+  vkDestroySemaphore(vkgsg->_device, _present_complete, NULL);
+  _present_complete = VK_NULL_HANDLE;
+}
+
+/**
+ * Closes the window right now.  Called from the window thread.
+ */
+void VulkanGraphicsWindow::
+close_window() {
+  _gsg.clear();
+  BaseGraphicsWindow::close_window();
+}
+
+/**
+ * Opens the window right now.  Called from the window thread.  Returns true
+ * if the window is successfully opened, or false if there was a problem.
+ */
+bool VulkanGraphicsWindow::
+open_window() {
+  VulkanGraphicsPipe *vkpipe;
+  DCAST_INTO_R(vkpipe, _pipe, false);
+
+  if (!BaseGraphicsWindow::open_window()) {
+    return false;
+  }
+
+  // Create a surface using the WSI extension.
+  VkWin32SurfaceCreateInfoKHR surface_info;
+  surface_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
+  surface_info.pNext = NULL;
+  surface_info.flags = 0;
+  surface_info.hinstance = GetModuleHandle(NULL);
+  surface_info.hwnd = _hWnd;
+
+  VkResult err = vkCreateWin32SurfaceKHR(vkpipe->_instance, &surface_info, NULL, &_surface);
+  if (err) {
+    vulkan_error(err, "Failed to create Win32 surface");
+    return false;
+  }
+
+  // Make sure we have a GSG, which manages a VkDevice.
+  VulkanGraphicsStateGuardian *vkgsg;
+  if (_gsg == NULL) {
+    // There is no old gsg.  Create a new one.
+    vkgsg = new VulkanGraphicsStateGuardian(_engine, vkpipe, NULL);
+    _gsg = vkgsg;
+  }
+
+  VkDevice device = vkgsg->_device;
+
+  // Get the supported presentation modes for this surface.
+  uint32_t num_present_modes = 0;
+  err = vkGetPhysicalDeviceSurfacePresentModesKHR(vkpipe->_gpu, _surface, &num_present_modes, NULL);
+  VkPresentModeKHR *present_modes = (VkPresentModeKHR *)
+    alloca(sizeof(VkPresentModeKHR) * num_present_modes);
+  err = vkGetPhysicalDeviceSurfacePresentModesKHR(vkpipe->_gpu, _surface, &num_present_modes, present_modes);
+
+  VkSwapchainCreateInfoKHR swapchain_info;
+  swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+  swapchain_info.pNext = NULL;
+  swapchain_info.surface = _surface;
+  swapchain_info.minImageCount = 1 + _fb_properties.get_back_buffers();
+  swapchain_info.imageFormat = VK_FORMAT_B8G8R8A8_UNORM;
+  swapchain_info.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
+  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.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+  swapchain_info.queueFamilyIndexCount = 0;
+  swapchain_info.pQueueFamilyIndices = NULL;
+  swapchain_info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+  swapchain_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+  swapchain_info.presentMode = VK_PRESENT_MODE_FIFO_KHR;
+  swapchain_info.clipped = true;
+  swapchain_info.oldSwapchain = NULL;
+
+  // Choose a present mode.  Use FIFO mode as fallback, which is always
+  // available.
+  for (size_t i = 0; i < num_present_modes; ++i) {
+    if (present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
+      // This is the lowest-latency non-tearing mode, so we'll take this.
+      swapchain_info.presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
+      break;
+    }
+    if (present_modes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR) {
+      // This is the fastest present mode, though it tears, so we'll use this
+      // if mailbox mode isn't available.
+      swapchain_info.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
+    }
+  }
+
+  err = vkCreateSwapchainKHR(device, &swapchain_info, NULL, &_swapchain);
+  if (err) {
+    vulkan_error(err, "Failed to create swap chain");
+    return false;
+  }
+
+  // Get the images in the swap chain, which may be more than requested.
+  uint32_t num_images;
+  vkGetSwapchainImagesKHR(device, _swapchain, &num_images, NULL);
+  _image_views.resize(num_images);
+  _framebuffers.resize(num_images);
+  _fb_properties.set_back_buffers(num_images - 1);
+
+  VkImage *images = (VkImage *)alloca(sizeof(VkImage) * num_images);
+  vkGetSwapchainImagesKHR(device, _swapchain, &num_images, images);
+
+  // Now create an image view for each image.
+  for (uint32_t i = 0; i < num_images; ++i) {
+    VkImageViewCreateInfo view_info;
+    view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+    view_info.pNext = NULL;
+    view_info.flags = 0;
+    view_info.image = images[i];
+    view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+    view_info.format = swapchain_info.imageFormat;
+    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 = VK_IMAGE_ASPECT_COLOR_BIT;
+    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, &_image_views[i]);
+    if (err) {
+      vulkan_error(err, "Failed to create image view for swapchain");
+      return false;
+    }
+  }
+
+  // Now create a depth image.
+  VkImageCreateInfo depth_img_info;
+  depth_img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+  depth_img_info.pNext = NULL;
+  depth_img_info.flags = 0;
+  depth_img_info.imageType = VK_IMAGE_TYPE_2D;
+  depth_img_info.format = VK_FORMAT_D16_UNORM;
+  depth_img_info.extent.width = _size[0];
+  depth_img_info.extent.height = _size[1];
+  depth_img_info.extent.depth = 1;
+  depth_img_info.mipLevels = 1;
+  depth_img_info.arrayLayers = 1;
+  depth_img_info.samples = VK_SAMPLE_COUNT_1_BIT;
+  depth_img_info.tiling = VK_IMAGE_TILING_OPTIMAL;
+  depth_img_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+  depth_img_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+  depth_img_info.queueFamilyIndexCount = 0;
+  depth_img_info.pQueueFamilyIndices = NULL;
+  depth_img_info.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+  VkImage depth_image;
+  err = vkCreateImage(device, &depth_img_info, NULL, &depth_image);
+  if (err) {
+    vulkan_error(err, "Failed to create depth image");
+    return false;
+  }
+
+  // Get the memory requirements, and find an appropriate heap to alloc in.
+  VkMemoryRequirements mem_reqs;
+  vkGetImageMemoryRequirements(device, depth_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 depth_mem;
+  err = vkAllocateMemory(device, &alloc_info, NULL, &depth_mem);
+  if (err) {
+    vulkan_error(err, "Failed to allocate memory for depth image");
+    return false;
+  }
+
+  // Bind memory to image.
+  err = vkBindImageMemory(device, depth_image, depth_mem, 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 = depth_image;
+  view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+  view_info.format = depth_img_info.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 = VK_IMAGE_ASPECT_DEPTH_BIT;
+  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, &_depth_stencil_view);
+  if (err) {
+    vulkan_error(err, "Failed to create image view for depth/stencil");
+    return false;
+  }
+
+  // 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 = swapchain_info.imageFormat;
+  attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
+  attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+  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_img_info.format;
+  attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
+  attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+  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;
+
+  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_reference;
+  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 = 2;
+  pass_info.pAttachments = attachments;
+  pass_info.subpassCount = 1;
+  pass_info.pSubpasses = &subpass;
+  pass_info.dependencyCount = 0;
+  pass_info.pDependencies = NULL;
+
+  err = vkCreateRenderPass(device, &pass_info, NULL, &_render_pass);
+  if (err) {
+    vulkan_error(err, "Failed to create render pass");
+    return false;
+  }
+
+  // Now finally create a framebuffer for each link in the swap chain.
+  VkImageView attach_views[2];
+  attach_views[1] = _depth_stencil_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 = 2;
+  fb_info.pAttachments = attach_views;
+  fb_info.width = _size[0];
+  fb_info.height = _size[1];
+  fb_info.layers = 1;
+
+  for (uint32_t i = 0; i < num_images; ++i) {
+    attach_views[0] = _image_views[i];
+    err = vkCreateFramebuffer(device, &fb_info, NULL, &_framebuffers[i]);
+    if (err) {
+      vulkan_error(err, "Failed to create framebuffer");
+      return false;
+    }
+  }
+
+  return true;
+}

+ 88 - 0
panda/src/vulkandisplay/vulkanGraphicsWindow.h

@@ -0,0 +1,88 @@
+/**
+ * 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 vulkanGraphicsWindow.h
+ * @author rdb
+ * @date 2016-02-16
+ */
+
+#ifndef VULKANGRAPHICSWINDOW_H
+#define VULKANGRAPHICSWINDOW_H
+
+#include "config_vulkandisplay.h"
+#include "winGraphicsWindow.h"
+
+#include <vulkan/vulkan.h>
+
+#ifdef _WIN32
+#include "winGraphicsWindow.h"
+typedef WinGraphicsWindow BaseGraphicsWindow;
+#else
+#include "graphicsWindow.h"
+typedef GraphicsWindow BaseGraphicsWindow;
+#endif
+
+/**
+ * A single graphics window for rendering to using the Vulkan API.  It manages
+ * a VkSurface for the OS-specific base window implementation, as well as a
+ * swapchain.
+ */
+class EXPCL_VULKANDISPLAY VulkanGraphicsWindow : public BaseGraphicsWindow {
+public:
+  VulkanGraphicsWindow(GraphicsEngine *engine, GraphicsPipe *pipe,
+                       const string &name,
+                       const FrameBufferProperties &fb_prop,
+                       const WindowProperties &win_prop,
+                       int flags,
+                       GraphicsStateGuardian *gsg,
+                       GraphicsOutput *host);
+  virtual ~VulkanGraphicsWindow();
+
+  virtual bool begin_frame(FrameMode mode, Thread *current_thread);
+  virtual void end_frame(FrameMode mode, Thread *current_thread);
+
+  virtual void begin_flip();
+  virtual void ready_flip();
+  virtual void end_flip();
+
+protected:
+  virtual void close_window();
+  virtual bool open_window();
+
+private:
+  VkSurfaceKHR _surface;
+  VkSwapchainKHR _swapchain;
+  VkRenderPass _render_pass;
+  VkSemaphore _present_complete;
+
+  pvector<VkImageView> _image_views;
+  VkImageView _depth_stencil_view;
+  pvector<VkFramebuffer> _framebuffers;
+  uint32_t _image_index;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    BaseGraphicsWindow::init_type();
+    register_type(_type_handle, "VulkanGraphicsWindow",
+                  BaseGraphicsWindow::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 "vulkanGraphicsWindow.I"
+
+#endif