First pass at VulkanViewer

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1848833005

Review URL: https://codereview.chromium.org/1848833005
diff --git a/gyp/most.gyp b/gyp/most.gyp
index 9b49c47..6c7fc02 100644
--- a/gyp/most.gyp
+++ b/gyp/most.gyp
@@ -29,12 +29,14 @@
         'visualbench.gyp:visualbench',
         'fuzz.gyp:fuzz',
         'kilobench.gyp:kilobench',
+        'vulkanviewer.gyp:vulkanviewer',
       ],
       'conditions': [
         [ 'skia_gpu == 0', {
           'dependencies!': [
             'visualbench.gyp:visualbench',
             'kilobench.gyp:kilobench',
+            'vulkanviewer.gyp:vulkanviewer',
           ]
         }],
         [ 'skia_os != "android" and skia_os != "linux"', {
@@ -73,6 +75,11 @@
             'skiaserve.gyp:skiaserve',
           ],
         }],
+        [ 'skia_vulkan == 0 or skia_os != "win"', {
+          'dependencies!': [
+            'vulkanviewer.gyp:vulkanviewer',
+          ],
+        }],
         [ 'skia_skip_gui',
           {
             'dependencies!': [
diff --git a/gyp/vulkanviewer.gyp b/gyp/vulkanviewer.gyp
new file mode 100644
index 0000000..d58613d
--- /dev/null
+++ b/gyp/vulkanviewer.gyp
@@ -0,0 +1,46 @@
+# Copyright 2016 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+# GYP file to build performance testbench.
+#
+{
+  'includes': [
+    'apptype_console.gypi',
+  ],
+  'targets': [
+    {
+      'target_name': 'vulkanviewer',
+      'type': 'executable',
+      'includes' : [
+        'gmslides.gypi',
+      ],
+      'include_dirs': [
+        '../bench',
+        '../gm',
+        '../include/private',
+        '../src/core',
+        '../src/effects',
+        '../src/gpu',
+        '../src/images',
+        '../src/image',
+      ],
+      'sources': [
+        '../gm/gm.cpp',
+        '<!@(python find.py ../tools/vulkan "*.cpp")',
+      ],
+      'dependencies': [
+        'flags.gyp:flags',
+        'gputest.gyp:skgputest',
+        'jsoncpp.gyp:jsoncpp',
+        'skia_lib.gyp:skia_lib',
+        'tools.gyp:crash_handler',
+        'tools.gyp:proc_stats',
+        'tools.gyp:resources',
+        'tools.gyp:sk_tool_utils',
+        'tools.gyp:timer',
+        'tools.gyp:url_data_manager',
+      ],
+    },
+  ],
+}
diff --git a/src/gpu/vk/GrVkBackendContext.cpp b/src/gpu/vk/GrVkBackendContext.cpp
index 1ca56e8..0358618 100644
--- a/src/gpu/vk/GrVkBackendContext.cpp
+++ b/src/gpu/vk/GrVkBackendContext.cpp
@@ -24,7 +24,7 @@
     "VK_LAYER_LUNARG_mem_tracker",
     "VK_LAYER_LUNARG_draw_state",
     "VK_LAYER_LUNARG_swapchain",
-    "VK_LAYER_GOOGLE_unique_objects",
+    //"VK_LAYER_GOOGLE_unique_objects",
     // not included in standard_validation
     //"VK_LAYER_LUNARG_api_dump",
     //"VK_LAYER_LUNARG_vktrace",
@@ -68,6 +68,8 @@
         instanceExtensionNames.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
         extensionFlags |= kEXT_debug_report_GrVkExtensionFlag;
     }
+#endif
+
     if (extensions.hasInstanceExtension(VK_KHR_SURFACE_EXTENSION_NAME)) {
         instanceExtensionNames.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
         extensionFlags |= kKHR_surface_GrVkExtensionFlag;
@@ -92,7 +94,6 @@
         extensionFlags |= kKHR_xlib_surface_GrVkExtensionFlag;
     }
 #endif
-#endif
 
     const VkInstanceCreateInfo instance_create = {
         VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,    // sType
@@ -159,6 +160,10 @@
         }
     }
 #endif
+    if (extensions.hasDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
+        deviceExtensionNames.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+        extensionFlags |= kKHR_swapchain_GrVkExtensionFlag;
+    }
     if (extensions.hasDeviceExtension("VK_NV_glsl_shader")) {
         deviceExtensionNames.push_back("VK_NV_glsl_shader");
         extensionFlags |= kNV_glsl_shader_GrVkExtensionFlag;
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index e1e99ed..62cf580 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -95,10 +95,10 @@
         callbackCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
         callbackCreateInfo.pNext = nullptr;
         callbackCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT |
-                                   VK_DEBUG_REPORT_WARNING_BIT_EXT |
+                                   VK_DEBUG_REPORT_WARNING_BIT_EXT;// |
                                    //VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
                                    //VK_DEBUG_REPORT_DEBUG_BIT_EXT |
-                                   VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
+                                   //VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
         callbackCreateInfo.pfnCallback = &DebugReportCallback;
         callbackCreateInfo.pUserData = nullptr;
 
diff --git a/src/gpu/vk/GrVkUtil.cpp b/src/gpu/vk/GrVkUtil.cpp
index 5ab7966..a6a9d4c 100644
--- a/src/gpu/vk/GrVkUtil.cpp
+++ b/src/gpu/vk/GrVkUtil.cpp
@@ -23,6 +23,9 @@
         case kSRGBA_8888_GrPixelConfig:
             *format = VK_FORMAT_R8G8B8A8_SRGB;
             break;
+        case kSBGRA_8888_GrPixelConfig:
+            *format = VK_FORMAT_B8G8R8A8_SRGB;
+            break;
         case kRGB_565_GrPixelConfig:
             *format = VK_FORMAT_R5G6B5_UNORM_PACK16;
             break;
@@ -30,7 +33,7 @@
             *format = VK_FORMAT_R4G4B4A4_UNORM_PACK16;
             break;
         case kIndex_8_GrPixelConfig:
-            // No current rad support for this config
+            // No current vulkan support for this config
             return false;
         case kAlpha_8_GrPixelConfig:
             *format = VK_FORMAT_R8_UNORM;
@@ -40,7 +43,7 @@
             *format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK;
             break;
         case kLATC_GrPixelConfig:
-            // No current rad support for this config
+            // No current vulkan support for this config
             return false;
         case kR11_EAC_GrPixelConfig:
             *format = VK_FORMAT_EAC_R11_UNORM_BLOCK;
@@ -63,6 +66,58 @@
     return true;
 }
 
+bool GrVkFormatToPixelConfig(VkFormat format, GrPixelConfig* config) {
+    GrPixelConfig dontCare;
+    if (!config) {
+        config = &dontCare;
+    }
+
+    switch (format) {
+        case VK_FORMAT_R8G8B8A8_UNORM:
+            *config = kRGBA_8888_GrPixelConfig;
+            break;
+        case VK_FORMAT_B8G8R8A8_UNORM:
+            *config = kBGRA_8888_GrPixelConfig;
+            break;
+        case VK_FORMAT_R8G8B8A8_SRGB:
+            *config = kSRGBA_8888_GrPixelConfig;
+            break;
+        case VK_FORMAT_B8G8R8A8_SRGB:
+            *config = kSBGRA_8888_GrPixelConfig;
+            break;
+        case VK_FORMAT_R5G6B5_UNORM_PACK16:
+            *config = kRGB_565_GrPixelConfig;
+            break;
+        case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
+            *config = kRGBA_4444_GrPixelConfig;
+            break;
+        case VK_FORMAT_R8_UNORM:
+            *config = kAlpha_8_GrPixelConfig;
+            break;
+        case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK:
+            *config = kETC1_GrPixelConfig;
+            break;
+        case VK_FORMAT_EAC_R11_UNORM_BLOCK:
+            *config = kR11_EAC_GrPixelConfig;
+            break;
+        case VK_FORMAT_ASTC_12x12_UNORM_BLOCK:
+            *config = kASTC_12x12_GrPixelConfig;
+            break;
+        case VK_FORMAT_R32G32B32A32_SFLOAT:
+            *config = kRGBA_float_GrPixelConfig;
+            break;
+        case VK_FORMAT_R16G16B16A16_SFLOAT:
+            *config = kRGBA_half_GrPixelConfig;
+            break;
+        case VK_FORMAT_R16_SFLOAT:
+            *config = kAlpha_half_GrPixelConfig;
+            break;
+        default:
+            return false;
+    }
+    return true;
+}
+
 bool GrSampleCountToVkSampleCount(uint32_t samples, VkSampleCountFlagBits* vkSamples) {
     switch (samples) {
         case 0: // fall through
diff --git a/src/gpu/vk/GrVkUtil.h b/src/gpu/vk/GrVkUtil.h
index 4e03c75..c2227ec 100644
--- a/src/gpu/vk/GrVkUtil.h
+++ b/src/gpu/vk/GrVkUtil.h
@@ -30,6 +30,11 @@
  */
 bool GrPixelConfigToVkFormat(GrPixelConfig config, VkFormat* format);
 
+/**
+* Returns the GrPixelConfig for the given vulkan texture format
+*/
+bool GrVkFormatToPixelConfig(VkFormat format, GrPixelConfig* config);
+
 bool GrSampleCountToVkSampleCount(uint32_t samples, VkSampleCountFlagBits* vkSamples);
 
 #endif
diff --git a/tools/vulkan/Application.h b/tools/vulkan/Application.h
new file mode 100644
index 0000000..623496d
--- /dev/null
+++ b/tools/vulkan/Application.h
@@ -0,0 +1,20 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef Application_DEFINED
+#define Application_DEFINED
+
+class Application {
+public:
+    static Application* Create(int argc, char** argv, void* platformData);
+
+    virtual ~Application() {}
+
+    virtual void onIdle(float dt) = 0;
+};
+
+#endif
diff --git a/tools/vulkan/VulkanTestContext.cpp b/tools/vulkan/VulkanTestContext.cpp
new file mode 100644
index 0000000..4a4c219
--- /dev/null
+++ b/tools/vulkan/VulkanTestContext.cpp
@@ -0,0 +1,605 @@
+
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrContext.h"
+#include "SkSurface.h"
+#include "VulkanTestContext.h"
+
+#include "vk/GrVkInterface.h"
+#include "vk/GrVkUtil.h"
+#include "vk/GrVkTypes.h"
+
+#ifdef VK_USE_PLATFORM_WIN32_KHR
+// windows wants to define this as CreateSemaphoreA or CreateSemaphoreW
+#undef CreateSemaphore
+#endif
+
+VulkanTestContext::VulkanTestContext(void* platformData, int msaaSampleCount) 
+                                 : fSurface(VK_NULL_HANDLE)
+                                 , fSwapchain(VK_NULL_HANDLE)
+                                 , fCommandPool(VK_NULL_HANDLE)
+                                 , fBackbuffers(nullptr) {
+
+    // any config code here (particularly for msaa)?
+
+    this->initializeContext(platformData);
+}
+
+void VulkanTestContext::initializeContext(void* platformData) {
+
+    fBackendContext.reset(GrVkBackendContext::Create());
+    fBackendContext->ref();
+
+    fContext = GrContext::Create(kVulkan_GrBackend, (GrBackendContext)fBackendContext.get());
+
+    fSurface = createVkSurface(platformData);
+    if (VK_NULL_HANDLE == fSurface) {
+        fBackendContext.reset(nullptr);
+        return;
+    }
+
+    // query to get the initial queue props size
+    uint32_t queueCount;
+    GR_VK_CALL(fBackendContext->fInterface, 
+               GetPhysicalDeviceQueueFamilyProperties(fBackendContext->fPhysicalDevice, &queueCount,
+                                                      nullptr));
+    SkASSERT(queueCount >= 1);
+
+    SkAutoMalloc queuePropsAlloc(queueCount * sizeof(VkQueueFamilyProperties));
+    // now get the actual queue props
+    VkQueueFamilyProperties* queueProps = (VkQueueFamilyProperties*)queuePropsAlloc.get();
+
+    GR_VK_CALL(fBackendContext->fInterface,
+               GetPhysicalDeviceQueueFamilyProperties(fBackendContext->fPhysicalDevice, &queueCount,
+                                                      queueProps));
+
+    // iterate to find the present queue
+    fPresentQueueIndex = -1;
+    for (uint32_t i = 0; i < queueCount; i++) {
+        if ((queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && canPresent(i)) {
+            fPresentQueueIndex = i;
+            break;
+        }
+    }
+    SkASSERT(0 <= fPresentQueueIndex && fPresentQueueIndex < queueCount);
+
+    VkBool32 supported;
+    VkResult res = GR_VK_CALL(fBackendContext->fInterface,
+                              GetPhysicalDeviceSurfaceSupportKHR(fBackendContext->fPhysicalDevice,
+                                                                 fPresentQueueIndex,
+                                                                 fSurface,
+                                                                 &supported));
+    if (VK_SUCCESS != res) {
+        this->destroyContext();
+        return;
+    }
+
+    // get this info from somewhere?
+    if (!this->createSwapchain(1024, 768)) {
+        this->destroyContext();
+        return;
+    }
+
+    // create presentQueue
+    vkGetDeviceQueue(fBackendContext->fDevice, fPresentQueueIndex, 0, &fPresentQueue);
+
+
+}
+
+bool VulkanTestContext::createSwapchain(uint32_t width, uint32_t height)
+{
+    // check for capabilities
+    VkSurfaceCapabilitiesKHR caps;
+    VkResult res = GR_VK_CALL(fBackendContext->fInterface,
+                           GetPhysicalDeviceSurfaceCapabilitiesKHR(fBackendContext->fPhysicalDevice,
+                                                                   fSurface,
+                                                                   &caps));
+    if (VK_SUCCESS != res) {
+        return false;
+    }
+
+    uint32_t surfaceFormatCount;
+    res = GR_VK_CALL(fBackendContext->fInterface,
+                     GetPhysicalDeviceSurfaceFormatsKHR(fBackendContext->fPhysicalDevice,
+                                                        fSurface,
+                                                        &surfaceFormatCount,
+                                                        nullptr));
+    if (VK_SUCCESS != res) {
+        return false;
+    }
+
+    SkAutoMalloc surfaceFormatAlloc(surfaceFormatCount * sizeof(VkSurfaceFormatKHR));
+    VkSurfaceFormatKHR* surfaceFormats = (VkSurfaceFormatKHR*)surfaceFormatAlloc.get();
+    res = GR_VK_CALL(fBackendContext->fInterface,
+                     GetPhysicalDeviceSurfaceFormatsKHR(fBackendContext->fPhysicalDevice,
+                                                        fSurface,
+                                                        &surfaceFormatCount,
+                                                        surfaceFormats));
+    if (VK_SUCCESS != res) {
+        return false;
+    }
+
+    uint32_t presentModeCount;
+    res = GR_VK_CALL(fBackendContext->fInterface,
+                     GetPhysicalDeviceSurfacePresentModesKHR(fBackendContext->fPhysicalDevice,
+                                                             fSurface,
+                                                             &presentModeCount,
+                                                             nullptr));
+    if (VK_SUCCESS != res) {
+        return false;
+    }
+
+    SkAutoMalloc presentModeAlloc(presentModeCount * sizeof(VkPresentModeKHR));
+    VkPresentModeKHR* presentModes = (VkPresentModeKHR*)presentModeAlloc.get();
+    res = GR_VK_CALL(fBackendContext->fInterface,
+                     GetPhysicalDeviceSurfacePresentModesKHR(fBackendContext->fPhysicalDevice,
+                                                             fSurface,
+                                                             &presentModeCount,
+                                                             presentModes));
+    if (VK_SUCCESS != res) {
+        return false;
+    }
+
+    VkExtent2D extent = caps.currentExtent;
+    // use the hints
+    if (extent.width == (uint32_t)-1) {
+        extent.width = width;
+        extent.height = height;
+    }
+
+    // clamp width; to protect us from broken hints?
+    if (extent.width < caps.minImageExtent.width) {
+        extent.width = caps.minImageExtent.width;
+    } else if (extent.width > caps.maxImageExtent.width) {
+        extent.width = caps.maxImageExtent.width;
+    }
+    // clamp height
+    if (extent.height < caps.minImageExtent.height) {
+        extent.height = caps.minImageExtent.height;
+    } else if (extent.height > caps.maxImageExtent.height) {
+        extent.height = caps.maxImageExtent.height;
+    }
+    fWidth = (int)extent.width;
+    fHeight = (int)extent.height;
+
+    uint32_t imageCount = caps.minImageCount + 2;
+    if (caps.maxImageCount > 0 && imageCount > caps.maxImageCount) {
+        // Application must settle for fewer images than desired:
+        imageCount = caps.maxImageCount;
+    }
+
+    VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
+                                   VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+                                   VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+    SkASSERT((caps.supportedUsageFlags & usageFlags) == usageFlags);
+    SkASSERT(caps.supportedTransforms & caps.currentTransform);
+    SkASSERT(caps.supportedCompositeAlpha & (VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR |
+                                             VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR));
+    VkCompositeAlphaFlagBitsKHR composite_alpha =
+        (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) ?
+                                        VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR :
+                                        VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+
+    // FIFO is the only mode universally supported
+    VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR;
+    bool vsync = false;
+    for (uint32_t i = 0; i < presentModeCount; ++i) {
+        if ((vsync && VK_PRESENT_MODE_MAILBOX_KHR == presentModes[i]) ||
+            (!vsync && VK_PRESENT_MODE_IMMEDIATE_KHR == presentModes[i])) {
+            mode = presentModes[i];
+        }
+    }
+
+    VkSwapchainCreateInfoKHR swapchainCreateInfo;
+    memset(&swapchainCreateInfo, 0, sizeof(VkSwapchainCreateInfoKHR));
+    swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+    swapchainCreateInfo.surface = fSurface;
+    swapchainCreateInfo.minImageCount = imageCount;
+    swapchainCreateInfo.imageFormat = surfaceFormats[0].format;       // for now, use the first one
+    swapchainCreateInfo.imageColorSpace = surfaceFormats[0].colorSpace;
+    swapchainCreateInfo.imageExtent = extent;
+    swapchainCreateInfo.imageArrayLayers = 1;
+    swapchainCreateInfo.imageUsage = usageFlags;
+
+    uint32_t queueFamilies[] = { fBackendContext->fQueueFamilyIndex, fPresentQueueIndex };
+    if (fBackendContext->fQueueFamilyIndex != fPresentQueueIndex) {
+        swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
+        swapchainCreateInfo.queueFamilyIndexCount = 2;
+        swapchainCreateInfo.pQueueFamilyIndices = queueFamilies;
+    } else {
+        swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+        swapchainCreateInfo.queueFamilyIndexCount = 0;
+        swapchainCreateInfo.pQueueFamilyIndices = nullptr;
+    }
+
+    swapchainCreateInfo.preTransform = caps.currentTransform;;
+    swapchainCreateInfo.compositeAlpha = composite_alpha;
+    swapchainCreateInfo.presentMode = mode;
+    swapchainCreateInfo.clipped = true;
+    swapchainCreateInfo.oldSwapchain = fSwapchain;
+
+    res = GR_VK_CALL(fBackendContext->fInterface,
+                     CreateSwapchainKHR(fBackendContext->fDevice,
+                                        &swapchainCreateInfo, nullptr, &fSwapchain));
+    if (VK_SUCCESS != res) {
+        return false;
+    }
+
+    // destroy the old swapchain
+    if (swapchainCreateInfo.oldSwapchain != VK_NULL_HANDLE) {
+        GR_VK_CALL(fBackendContext->fInterface, DeviceWaitIdle(fBackendContext->fDevice));
+
+        this->destroyBuffers();
+
+        GR_VK_CALL(fBackendContext->fInterface, DestroySwapchainKHR(fBackendContext->fDevice,
+                                                                   swapchainCreateInfo.oldSwapchain,
+                                                                   nullptr));
+    }
+
+    GrVkFormatToPixelConfig(swapchainCreateInfo.imageFormat, &fPixelConfig);
+
+    this->createBuffers();
+
+    return true;
+}
+
+void VulkanTestContext::createBuffers() {
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface, GetSwapchainImagesKHR(fBackendContext->fDevice,
+                                                                           fSwapchain,
+                                                                           &fImageCount,
+                                                                           nullptr));
+    SkASSERT(fImageCount);
+    fImages = new VkImage[fImageCount];
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface, GetSwapchainImagesKHR(fBackendContext->fDevice,
+                                                                           fSwapchain,
+                                                                           &fImageCount,
+                                                                           fImages));
+
+    // set up initial image layouts and create surfaces
+    fImageLayouts = new VkImageLayout[fImageCount];
+    fSurfaces = new sk_sp<SkSurface>[fImageCount];
+    for (uint32_t i = 0; i < fImageCount; ++i) {
+        fImageLayouts[i] = VK_IMAGE_LAYOUT_UNDEFINED;
+
+        GrBackendRenderTargetDesc desc;
+        GrVkTextureInfo info;
+        info.fImage = fImages[i];
+        info.fAlloc = nullptr;
+        info.fImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+        info.fImageTiling = VK_IMAGE_TILING_OPTIMAL;
+        desc.fWidth = fWidth;
+        desc.fHeight = fHeight;
+        desc.fConfig = fPixelConfig;
+        desc.fOrigin = kTopLeft_GrSurfaceOrigin;
+        desc.fSampleCnt = 0;
+        desc.fStencilBits = 0;
+        desc.fRenderTargetHandle = (GrBackendObject) &info;
+        SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+        fSurfaces[i] = SkSurface::MakeFromBackendRenderTarget(fContext, desc, &props);
+    }
+
+    // create the command pool for the command buffers
+    if (VK_NULL_HANDLE == fCommandPool) {
+        VkCommandPoolCreateInfo commandPoolInfo;
+        memset(&commandPoolInfo, 0, sizeof(VkCommandPoolCreateInfo));
+        commandPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+        // this needs to be on the render queue
+        commandPoolInfo.queueFamilyIndex = fBackendContext->fQueueFamilyIndex;
+        commandPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+        GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                            CreateCommandPool(fBackendContext->fDevice, &commandPoolInfo,
+                                              nullptr, &fCommandPool));
+    }
+
+    // set up the backbuffers
+    VkSemaphoreCreateInfo semaphoreInfo;
+    memset(&semaphoreInfo, 0, sizeof(VkSemaphoreCreateInfo));
+    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+    semaphoreInfo.pNext = nullptr;
+    semaphoreInfo.flags = 0;
+    VkCommandBufferAllocateInfo commandBuffersInfo;
+    memset(&commandBuffersInfo, 0, sizeof(VkCommandBufferAllocateInfo));
+    commandBuffersInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+    commandBuffersInfo.pNext = nullptr;
+    commandBuffersInfo.commandPool = fCommandPool;
+    commandBuffersInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+    commandBuffersInfo.commandBufferCount = 2;
+    VkFenceCreateInfo fenceInfo;
+    memset(&fenceInfo, 0, sizeof(VkFenceCreateInfo));
+    fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+    fenceInfo.pNext = nullptr;
+    fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+    // we create one additional backbuffer structure here, because we want to 
+    // give the command buffers they contain a chance to finish before we cycle back
+    fBackbuffers = new BackbufferInfo[fImageCount + 1];
+    for (uint32_t i = 0; i < fImageCount + 1; ++i) {
+        fBackbuffers[i].fImageIndex = -1;
+        GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                            CreateSemaphore(fBackendContext->fDevice, &semaphoreInfo,
+                                            nullptr, &fBackbuffers[i].fAcquireSemaphore));
+        GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                            CreateSemaphore(fBackendContext->fDevice, &semaphoreInfo,
+                                            nullptr, &fBackbuffers[i].fRenderSemaphore));
+        GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                            AllocateCommandBuffers(fBackendContext->fDevice, &commandBuffersInfo,
+                                                   fBackbuffers[i].fTransitionCmdBuffers));
+        GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                            CreateFence(fBackendContext->fDevice, &fenceInfo, nullptr,
+                                        &fBackbuffers[i].fUsageFences[0]));
+        GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                            CreateFence(fBackendContext->fDevice, &fenceInfo, nullptr,
+                                        &fBackbuffers[i].fUsageFences[1]));
+    }
+    fCurrentBackbufferIndex = fImageCount;
+}
+
+void VulkanTestContext::destroyBuffers() {
+
+    if (fBackbuffers) {
+        for (uint32_t i = 0; i < fImageCount + 1; ++i) {
+            GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                                WaitForFences(fBackendContext->fDevice, 2, 
+                                              fBackbuffers[i].fUsageFences,
+                                true, UINT64_MAX));
+            fBackbuffers[i].fImageIndex = -1;
+            GR_VK_CALL(fBackendContext->fInterface,
+                       DestroySemaphore(fBackendContext->fDevice,
+                                        fBackbuffers[i].fAcquireSemaphore,
+                                        nullptr));
+            GR_VK_CALL(fBackendContext->fInterface,
+                       DestroySemaphore(fBackendContext->fDevice,
+                                        fBackbuffers[i].fRenderSemaphore,
+                                        nullptr));
+            GR_VK_CALL(fBackendContext->fInterface,
+                       FreeCommandBuffers(fBackendContext->fDevice, fCommandPool, 2,
+                                          fBackbuffers[i].fTransitionCmdBuffers));
+            GR_VK_CALL(fBackendContext->fInterface,
+                       DestroyFence(fBackendContext->fDevice, fBackbuffers[i].fUsageFences[0], 0));
+            GR_VK_CALL(fBackendContext->fInterface,
+                       DestroyFence(fBackendContext->fDevice, fBackbuffers[i].fUsageFences[1], 0));
+        }
+    }
+
+    delete[] fBackbuffers;
+    fBackbuffers = nullptr;
+
+    delete[] fSurfaces;
+    fSurfaces = nullptr;
+    delete[] fImageLayouts;
+    fImageLayouts = nullptr;
+    delete[] fImages;
+    fImages = nullptr;
+}
+
+VulkanTestContext::~VulkanTestContext() {
+    this->destroyContext();
+}
+
+void VulkanTestContext::destroyContext() {
+    if (!fBackendContext.get()) {
+        return;
+    }
+
+    GR_VK_CALL(fBackendContext->fInterface, DeviceWaitIdle(fBackendContext->fDevice));
+
+    this->destroyBuffers();
+
+    if (VK_NULL_HANDLE != fCommandPool) {
+        GR_VK_CALL(fBackendContext->fInterface, DestroyCommandPool(fBackendContext->fDevice,
+                                                                   fCommandPool, nullptr));
+        fCommandPool = VK_NULL_HANDLE;
+    }
+
+    if (VK_NULL_HANDLE != fSwapchain) {
+        GR_VK_CALL(fBackendContext->fInterface, DestroySwapchainKHR(fBackendContext->fDevice,
+                                                                    fSwapchain, nullptr));
+        fSwapchain = VK_NULL_HANDLE;
+    }
+
+    if (VK_NULL_HANDLE != fSurface) {
+        GR_VK_CALL(fBackendContext->fInterface, DestroySurfaceKHR(fBackendContext->fInstance,
+                                                                  fSurface, nullptr));
+        fSurface = VK_NULL_HANDLE;
+    }
+
+    delete fContext;
+
+    fBackendContext.reset(nullptr);
+}
+
+VulkanTestContext::BackbufferInfo* VulkanTestContext::getAvailableBackbuffer() {
+    SkASSERT(fBackbuffers);
+
+    ++fCurrentBackbufferIndex;
+    if (fCurrentBackbufferIndex > fImageCount) {
+        fCurrentBackbufferIndex = 0;
+    }
+
+    BackbufferInfo* backbuffer = fBackbuffers + fCurrentBackbufferIndex;
+
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                        WaitForFences(fBackendContext->fDevice, 2, backbuffer->fUsageFences,
+                                      true, UINT64_MAX));
+    return backbuffer;
+}
+
+SkSurface* VulkanTestContext::getBackbufferSurface() {
+    BackbufferInfo* backbuffer = this->getAvailableBackbuffer();
+    SkASSERT(backbuffer);
+
+    // reset the fence
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                        ResetFences(fBackendContext->fDevice, 2, backbuffer->fUsageFences));
+    // semaphores should be in unsignaled state
+
+    // acquire the image
+    VkResult res = GR_VK_CALL(fBackendContext->fInterface,
+                              AcquireNextImageKHR(fBackendContext->fDevice, 
+                                                  fSwapchain,
+                                                  UINT64_MAX,
+                                                  backbuffer->fAcquireSemaphore,
+                                                  VK_NULL_HANDLE,
+                                                  &backbuffer->fImageIndex));
+    if (VK_ERROR_SURFACE_LOST_KHR == res) {
+        // need to figure out how to create a new vkSurface without the platformData*
+        return nullptr;
+    }
+    if (VK_ERROR_OUT_OF_DATE_KHR == res || VK_ERROR_SURFACE_LOST_KHR == res) {
+        // tear swapchain down and try again
+        if (!this->createSwapchain(0, 0)) {
+            return nullptr;
+        }
+
+        // acquire the image
+        res = GR_VK_CALL(fBackendContext->fInterface,
+                         AcquireNextImageKHR(fBackendContext->fDevice,
+                                             fSwapchain,
+                                             UINT64_MAX,
+                                             backbuffer->fAcquireSemaphore,
+                                             VK_NULL_HANDLE,
+                                             &backbuffer->fImageIndex));
+
+        if (VK_SUCCESS != res) {
+            return nullptr;
+        }
+    }
+
+    // set up layout transfer from initial to color attachment
+    VkImageLayout layout = fImageLayouts[backbuffer->fImageIndex];
+    VkPipelineStageFlags srcStageMask = (VK_IMAGE_LAYOUT_UNDEFINED == layout) ?
+                                        VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT :
+                                        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    VkAccessFlags srcAccessMask = (VK_IMAGE_LAYOUT_UNDEFINED == layout) ? 
+                                  0 : VK_ACCESS_MEMORY_READ_BIT;
+    VkAccessFlags dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+
+    VkImageMemoryBarrier imageMemoryBarrier = {
+        VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,   // sType
+        NULL,                                     // pNext
+        srcAccessMask,                            // outputMask
+        dstAccessMask,                            // inputMask
+        layout,                                   // oldLayout
+        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // newLayout
+        fPresentQueueIndex,                       // srcQueueFamilyIndex
+        fBackendContext->fQueueFamilyIndex,       // dstQueueFamilyIndex
+        fImages[backbuffer->fImageIndex],         // image
+        { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 } // subresourceRange
+    };
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                        ResetCommandBuffer(backbuffer->fTransitionCmdBuffers[0], 0));
+    VkCommandBufferBeginInfo info;
+    memset(&info, 0, sizeof(VkCommandBufferBeginInfo));
+    info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+    info.flags = 0;
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                        BeginCommandBuffer(backbuffer->fTransitionCmdBuffers[0], &info));
+
+    GR_VK_CALL(fBackendContext->fInterface, 
+                CmdPipelineBarrier(backbuffer->fTransitionCmdBuffers[0], 
+                                    srcStageMask, dstStageMask, 0,
+                                    0, nullptr,
+                                    0, nullptr,
+                                    1, &imageMemoryBarrier));
+
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                        EndCommandBuffer(backbuffer->fTransitionCmdBuffers[0]));  
+
+    // insert the layout transfer into the queue and wait on the acquire
+    VkSubmitInfo submitInfo;
+    memset(&submitInfo, 0, sizeof(VkSubmitInfo));
+    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submitInfo.waitSemaphoreCount = 1;
+    submitInfo.pWaitSemaphores = &backbuffer->fAcquireSemaphore;
+    submitInfo.pWaitDstStageMask = 0;
+    submitInfo.commandBufferCount = 1;
+    submitInfo.pCommandBuffers = &backbuffer->fTransitionCmdBuffers[0];
+    submitInfo.signalSemaphoreCount = 0;
+    
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                        QueueSubmit(fBackendContext->fQueue, 1, &submitInfo, 
+                                    backbuffer->fUsageFences[0]));
+
+    return fSurfaces[backbuffer->fImageIndex].get();
+}
+
+
+void VulkanTestContext::swapBuffers() {
+
+    BackbufferInfo* backbuffer = fBackbuffers + fCurrentBackbufferIndex;
+
+    VkImageLayout layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+    VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+    VkAccessFlags srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    VkAccessFlags dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
+
+    VkImageMemoryBarrier imageMemoryBarrier = {
+        VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,   // sType
+        NULL,                                     // pNext
+        srcAccessMask,                            // outputMask
+        dstAccessMask,                            // inputMask
+        layout,                                   // oldLayout
+        VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,          // newLayout
+        fBackendContext->fQueueFamilyIndex,       // srcQueueFamilyIndex
+        fPresentQueueIndex,                       // dstQueueFamilyIndex
+        fImages[backbuffer->fImageIndex],         // image
+        { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 } // subresourceRange
+    };
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                        ResetCommandBuffer(backbuffer->fTransitionCmdBuffers[1], 0));
+    VkCommandBufferBeginInfo info;
+    memset(&info, 0, sizeof(VkCommandBufferBeginInfo));
+    info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+    info.flags = 0;
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                        BeginCommandBuffer(backbuffer->fTransitionCmdBuffers[1], &info));
+    GR_VK_CALL(fBackendContext->fInterface,
+                CmdPipelineBarrier(backbuffer->fTransitionCmdBuffers[1],
+                srcStageMask, dstStageMask, 0,
+                0, nullptr,
+                0, nullptr,
+                1, &imageMemoryBarrier));
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                        EndCommandBuffer(backbuffer->fTransitionCmdBuffers[1]));
+
+    fImageLayouts[backbuffer->fImageIndex] = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+    // insert the layout transfer into the queue and wait on the acquire
+    VkSubmitInfo submitInfo;
+    memset(&submitInfo, 0, sizeof(VkSubmitInfo));
+    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+    submitInfo.waitSemaphoreCount = 0;
+    submitInfo.pWaitDstStageMask = 0;
+    submitInfo.commandBufferCount = 1;
+    submitInfo.pCommandBuffers = &backbuffer->fTransitionCmdBuffers[1];
+    submitInfo.signalSemaphoreCount = 1;
+    submitInfo.pSignalSemaphores = &backbuffer->fRenderSemaphore;
+
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                        QueueSubmit(fBackendContext->fQueue, 1, &submitInfo, 
+                                    backbuffer->fUsageFences[1]));
+
+    // Submit present operation to present queue
+    const VkPresentInfoKHR presentInfo =
+    {
+        VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, // sType
+        NULL, // pNext
+        1, // waitSemaphoreCount
+        &backbuffer->fRenderSemaphore, // pWaitSemaphores
+        1, // swapchainCount
+        &fSwapchain, // pSwapchains
+        &backbuffer->fImageIndex, // pImageIndices
+        NULL // pResults
+    };
+
+    GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+                        QueuePresentKHR(fPresentQueue, &presentInfo));
+
+}
diff --git a/tools/vulkan/VulkanTestContext.h b/tools/vulkan/VulkanTestContext.h
new file mode 100644
index 0000000..c1535b9
--- /dev/null
+++ b/tools/vulkan/VulkanTestContext.h
@@ -0,0 +1,92 @@
+
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef VulkanTestContext_DEFINED
+#define VulkanTestContext_DEFINED
+
+#ifdef SK_VULKAN
+
+#include "GrTypes.h"
+#include "vk/GrVkBackendContext.h"
+
+class SkSurface;
+class GrContext;
+
+class VulkanTestContext {
+public:
+    ~VulkanTestContext();
+
+    // each platform will have to implement these in its CPP file
+    VkSurfaceKHR createVkSurface(void* platformData);
+    bool canPresent(uint32_t queueFamilyIndex);
+
+    static VulkanTestContext* Create(void* platformData, int msaaSampleCount) {
+        VulkanTestContext* ctx = new VulkanTestContext(platformData, msaaSampleCount);
+        if (!ctx->isValid()) {
+            delete ctx;
+            return nullptr;
+        }
+        return ctx;
+    }
+
+    SkSurface* getBackbufferSurface();
+    void swapBuffers();
+
+    bool makeCurrent() { return true; }
+    int getStencilBits() { return 0;  }
+    int getSampleCount() { return 0; }
+
+    bool isValid() { return SkToBool(fBackendContext.get()); }
+
+    void resize() { 
+        this->createSwapchain(0, 0); 
+    }
+
+    GrBackendContext getBackendContext() { return (GrBackendContext)fBackendContext.get(); }
+
+private:
+    VulkanTestContext();
+    VulkanTestContext(void*, int msaaSampleCount);
+    void initializeContext(void*);
+    void destroyContext();
+
+    struct BackbufferInfo {
+        uint32_t        fImageIndex;          // image this is associated with
+        VkSemaphore     fAcquireSemaphore;    // we signal on this for acquisition of image
+        VkSemaphore     fRenderSemaphore;     // we wait on this for rendering to be done
+        VkCommandBuffer fTransitionCmdBuffers[2]; // to transition layout between present and render
+        VkFence         fUsageFences[2];      // used to ensure this data is no longer used on GPU
+    };
+
+    BackbufferInfo* getAvailableBackbuffer();
+    bool createSwapchain(uint32_t width, uint32_t height);
+    void createBuffers();
+    void destroyBuffers();
+
+    SkAutoTUnref<const GrVkBackendContext> fBackendContext;
+
+    GrContext*        fContext;
+    VkSurfaceKHR      fSurface;
+    VkSwapchainKHR    fSwapchain;
+    uint32_t          fPresentQueueIndex;
+    VkQueue           fPresentQueue;
+    int               fWidth;
+    int               fHeight;
+    GrPixelConfig     fPixelConfig;
+
+    uint32_t          fImageCount;
+    VkImage*          fImages;         // images in the swapchain
+    VkImageLayout*    fImageLayouts;   // layouts of these images when not color attachment
+    sk_sp<SkSurface>* fSurfaces;       // wrapped surface for those images
+    VkCommandPool     fCommandPool;
+    BackbufferInfo*   fBackbuffers;
+    uint32_t          fCurrentBackbufferIndex;
+};
+
+#endif // SK_VULKAN
+
+#endif
diff --git a/tools/vulkan/Window.cpp b/tools/vulkan/Window.cpp
new file mode 100644
index 0000000..d0a2a76
--- /dev/null
+++ b/tools/vulkan/Window.cpp
@@ -0,0 +1,52 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "Window.h"
+
+#include "SkSurface.h"
+#include "SkCanvas.h"
+#include "VulkanTestContext.h"
+
+static bool default_key_func(int key, bool down, void* userData) {
+    return false;
+}
+
+static bool default_mouse_func(int x, int y, bool down, void* userData) {
+    return false;
+}
+
+static void default_paint_func(SkCanvas*, void* userData) {}
+
+Window::Window() : fKeyFunc(default_key_func)
+                 , fMouseFunc(default_mouse_func)
+                 , fPaintFunc(default_paint_func) {
+}
+
+void Window::detach() {
+    delete fTestContext;
+    fTestContext = nullptr;
+}
+
+void Window::onPaint() {
+    SkSurface* backbuffer = fTestContext->getBackbufferSurface();
+    if (backbuffer) {
+        // draw into the canvas of this surface
+        SkCanvas* canvas = backbuffer->getCanvas();
+
+        fPaintFunc(canvas, fPaintUserData);
+
+        canvas->flush();
+
+        fTestContext->swapBuffers();
+    }
+
+}
+
+
+void Window::onSize() {
+    fTestContext->resize();
+}
diff --git a/tools/vulkan/Window.h b/tools/vulkan/Window.h
new file mode 100644
index 0000000..2247ca1
--- /dev/null
+++ b/tools/vulkan/Window.h
@@ -0,0 +1,73 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef Window_DEFINED
+#define Window_DEFINED
+
+class SkCanvas;
+class VulkanTestContext;
+
+class Window {
+public:
+    static Window* CreateNativeWindow(void* platformData);
+
+    virtual ~Window() {};
+
+    virtual void setTitle(const char*) = 0;
+    virtual void show() = 0;
+
+    struct AttachmentInfo {
+        int fSampleCount;
+        int fStencilBits;
+    };
+
+    enum BackEndTypes {
+        kNativeGL_BackendType,
+        kVulkan_BackendType
+    };
+
+    virtual bool attach(BackEndTypes attachType, int msaaSampleCount, AttachmentInfo*) = 0;
+    void detach();
+
+    // input handling
+    typedef bool(*OnKeyFunc)(int key, bool down, void* userData);
+    typedef bool(*OnMouseFunc)(int x, int y, bool down, void* userData);
+    typedef void(*OnPaintFunc)(SkCanvas*, void* userData);
+
+    void registerKeyFunc(OnKeyFunc func, void* userData) {
+        fKeyFunc = func;
+        fKeyUserData = userData;
+    }
+
+    void registerMouseFunc(OnMouseFunc func, void* userData) {
+        fMouseFunc = func;
+        fMouseUserData = userData;
+    }
+
+    void registerPaintFunc(OnPaintFunc func, void* userData) {
+        fPaintFunc = func;
+        fPaintUserData = userData;
+    }
+
+    void onPaint();
+    void onSize();
+
+protected:
+    Window();
+
+    OnKeyFunc    fKeyFunc;
+    void*        fKeyUserData;
+    OnMouseFunc  fMouseFunc;
+    void*        fMouseUserData;
+    OnPaintFunc  fPaintFunc;
+    void*        fPaintUserData;
+
+    VulkanTestContext* fTestContext;
+};
+
+
+#endif
diff --git a/tools/vulkan/viewer/InputHandler.cpp b/tools/vulkan/viewer/InputHandler.cpp
new file mode 100644
index 0000000..0e78b94
--- /dev/null
+++ b/tools/vulkan/viewer/InputHandler.cpp
@@ -0,0 +1,48 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "InputHandler.h"
+#include <ctype.h>
+
+InputHandler::InputHandler() : fMouseDown(false), fMousePressed(false), fMouseReleased(false)
+                             , fMouseX(0), fMouseY(0) {
+    // clear key states
+    memset(fKeys, 0, sizeof(bool) * 256);
+    memset(fKeyPressed, 0, sizeof(bool) * 256);
+    memset(fKeyReleased, 0, sizeof(bool) * 256);
+}
+
+void InputHandler::onKeyDown(unsigned char key) {
+    if (!fKeys[key]) {
+        fKeys[key] = true;
+        fKeyPressed[key] = true;
+    }
+} 
+
+void InputHandler::onKeyUp(unsigned char key) {
+    if (fKeys[key]) {
+        fKeys[key] = false;
+        fKeyReleased[key] = true;
+    }
+}
+
+void InputHandler::onMouseDown(unsigned int h, unsigned int v) {
+    if (!fMouseDown) {
+        fMouseDown = true;
+        fMousePressed = true;
+    }
+
+    fMouseX = h;
+    fMouseY = v;
+}
+
+void InputHandler::onMouseUp() {
+    if (fMouseDown)  {
+        fMouseDown = false;
+        fMouseReleased = true;
+    }
+}
diff --git a/tools/vulkan/viewer/InputHandler.h b/tools/vulkan/viewer/InputHandler.h
new file mode 100644
index 0000000..05d6942
--- /dev/null
+++ b/tools/vulkan/viewer/InputHandler.h
@@ -0,0 +1,84 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef InputHandler_DEFINED
+#define InputHandler_DEFINED
+
+#include <string.h>
+
+class InputHandler
+{
+public:
+    InputHandler();
+
+    void onKeyDown(unsigned char key);                  // Handle key down event
+    void onKeyUp(unsigned char key);                    // Handle key up event
+    void onMouseDown(unsigned int h, unsigned int v);   // Handle mouse down event
+    void onMouseUp();                                   // Handle mouse up event
+
+    bool isKeyDown(unsigned char key) const { 
+        return fKeys[key]; 
+    }
+    bool isKeyUp(unsigned char key) const { 
+        return !fKeys[key]; 
+    }
+    bool isKeyPressed(unsigned char key) const {
+        return fKeyPressed[key];
+    }
+
+    bool isKeyReleased(unsigned char key) const {
+        return fKeyReleased[key];
+    }
+
+    bool isMouseDown(unsigned int* h, unsigned int* v) const {
+        if (fMouseDown)
+        {
+            *h = fMouseX; 
+            *v = fMouseY;
+            return true;
+        }
+        return false;
+    }
+
+    bool isMousePressed(unsigned int* h, unsigned int* v) const {
+        if (fMousePressed)
+        {
+            *h = fMouseX; 
+            *v = fMouseY;
+            return true;
+        }
+        return false;
+    }
+
+    bool IsMouseReleased() const {
+        return fMouseReleased;
+    }
+
+    inline void Update() {
+        memset(fKeyPressed, 0, sizeof(bool) * 256);
+        memset(fKeyReleased, 0, sizeof(bool) * 256);
+        fMousePressed = false;
+        fMouseReleased = false;
+    }
+
+private:
+    // assumes ASCII keystrokes only
+    bool fKeys[256];            
+    bool fKeyPressed[256];
+    bool fKeyReleased[256];
+
+    bool fMouseDown;
+    bool fMousePressed;
+    bool fMouseReleased;
+    unsigned int fMouseX;
+    unsigned int fMouseY;
+
+    InputHandler(const InputHandler& other);
+    InputHandler& operator=(const InputHandler& other);
+};
+
+#endif
diff --git a/tools/vulkan/viewer/VulkanViewer.cpp b/tools/vulkan/viewer/VulkanViewer.cpp
new file mode 100644
index 0000000..0af423e
--- /dev/null
+++ b/tools/vulkan/viewer/VulkanViewer.cpp
@@ -0,0 +1,94 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "VulkanViewer.h"
+
+#include "SkCanvas.h"
+#include "SkRandom.h"
+#include "SkCommonFlags.h"
+
+DEFINE_string(key, "",
+              "Space-separated key/value pairs to add to JSON identifying this builder.");
+
+Application* Application::Create(int argc, char** argv, void* platformData) {
+    return new VulkanViewer(argc, argv, platformData);
+}
+
+static bool on_key_handler(int key, bool keyDown, void* userData) {
+    VulkanViewer* vv = reinterpret_cast<VulkanViewer*>(userData);
+
+    return vv->onKey(key, keyDown);
+}
+
+static bool on_mouse_handler(int x, int y, bool mouseDown, void* userData) {
+    VulkanViewer* vv = reinterpret_cast<VulkanViewer*>(userData);
+
+    return vv->onMouse(x, y, mouseDown);
+}
+
+static void on_paint_handler(SkCanvas* canvas, void* userData) {
+    VulkanViewer* vv = reinterpret_cast<VulkanViewer*>(userData);
+
+    return vv->onPaint(canvas);
+}
+
+VulkanViewer::VulkanViewer(int argc, char** argv, void* platformData) :
+    fGMs(skiagm::GMRegistry::Head()){
+
+    fWindow = Window::CreateNativeWindow(platformData);
+    fWindow->attach(Window::kVulkan_BackendType, 0, nullptr);
+
+    // register callbacks
+    fWindow->registerKeyFunc(on_key_handler, this);
+    fWindow->registerMouseFunc(on_mouse_handler, this);
+    fWindow->registerPaintFunc(on_paint_handler, this);
+
+    fWindow->setTitle("VulkanViewer");
+    fWindow->show();
+}
+
+VulkanViewer::~VulkanViewer() {
+    fWindow->detach();
+    delete fWindow;
+}
+
+bool VulkanViewer::onKey(int key, bool keyDown) {
+    if (keyDown) {
+        fInputHandler.onKeyDown(key);
+    } else {
+        fInputHandler.onKeyUp(key);
+    }
+    return true;
+}
+
+bool VulkanViewer::onMouse(int x, int y, bool mouseDown) {
+    if (mouseDown) {
+        fInputHandler.onMouseDown(x, y);
+    } else {
+        fInputHandler.onMouseUp();
+    }
+    return true;
+}
+
+void VulkanViewer::onPaint(SkCanvas* canvas) {
+    SkAutoTDelete<skiagm::GM> gm(fGMs->factory()(nullptr));
+
+    canvas->save();
+
+    gm->draw(canvas);
+
+    canvas->restore();
+}
+
+void VulkanViewer::onIdle(float dt) {
+    if (fInputHandler.isKeyPressed('l') || fInputHandler.isKeyPressed('L')) {
+        fGMs = fGMs->next();
+    }
+    fWindow->onPaint();
+
+    fInputHandler.Update();
+}
diff --git a/tools/vulkan/viewer/VulkanViewer.h b/tools/vulkan/viewer/VulkanViewer.h
new file mode 100644
index 0000000..48ab7af
--- /dev/null
+++ b/tools/vulkan/viewer/VulkanViewer.h
@@ -0,0 +1,37 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef VulkanViewer_DEFINED
+#define VulkanViewer_DEFINED
+
+#include "../Application.h"
+#include "InputHandler.h"
+#include "../Window.h"
+#include "gm.h"
+
+class SkCanvas;
+
+class VulkanViewer : public Application {
+public:
+    VulkanViewer(int argc, char** argv, void* platformData);
+    ~VulkanViewer() override;
+
+    bool onKey(int key, bool keyDown);
+    bool onMouse(int x, int y, bool mouseDown);
+    void onPaint(SkCanvas* canvas);
+
+    void onIdle(float dt) override;
+
+private:
+    Window*      fWindow;
+    InputHandler fInputHandler;
+
+    const skiagm::GMRegistry* fGMs;
+};
+
+
+#endif
diff --git a/tools/vulkan/win/VulkanTestContext_win.cpp b/tools/vulkan/win/VulkanTestContext_win.cpp
new file mode 100644
index 0000000..a4f49e3
--- /dev/null
+++ b/tools/vulkan/win/VulkanTestContext_win.cpp
@@ -0,0 +1,47 @@
+
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "VulkanTestContext_win.h"
+
+#include "vk/GrVkInterface.h"
+#include "../../src/gpu/vk/GrVkUtil.h"
+
+// Platform dependant call
+VkSurfaceKHR VulkanTestContext::createVkSurface(void* platformData) {
+    // need better error handling here
+    SkASSERT(platformData);
+    ContextPlatformData_win* winPlatformData = 
+                                           reinterpret_cast<ContextPlatformData_win*>(platformData);
+    VkSurfaceKHR surface;
+
+    VkWin32SurfaceCreateInfoKHR surfaceCreateInfo;
+    memset(&surfaceCreateInfo, 0, sizeof(VkWin32SurfaceCreateInfoKHR));
+    surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
+    surfaceCreateInfo.pNext = nullptr;
+    surfaceCreateInfo.flags = 0;
+    surfaceCreateInfo.hinstance = winPlatformData->fHInstance;
+    surfaceCreateInfo.hwnd = winPlatformData->fHWnd;
+
+    VkResult res = GR_VK_CALL(fBackendContext->fInterface,
+                              CreateWin32SurfaceKHR(fBackendContext->fInstance, &surfaceCreateInfo,
+                              nullptr, &surface));
+    if (VK_SUCCESS != res) {
+        return VK_NULL_HANDLE;
+    }
+
+    return surface;
+}
+
+// Platform dependant call
+bool VulkanTestContext::canPresent(uint32_t queueFamilyIndex) {
+    VkBool32 check = GR_VK_CALL(fBackendContext->fInterface,
+                                GetPhysicalDeviceWin32PresentationSupportKHR(
+                                                                   fBackendContext->fPhysicalDevice,
+                                                                   queueFamilyIndex));
+    return (VK_FALSE != check);
+}
diff --git a/tools/vulkan/win/VulkanTestContext_win.h b/tools/vulkan/win/VulkanTestContext_win.h
new file mode 100644
index 0000000..59b5c2c
--- /dev/null
+++ b/tools/vulkan/win/VulkanTestContext_win.h
@@ -0,0 +1,24 @@
+
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef VULKANTESTCONTEXT_WIN_DEFINED
+#define VULKANTESTCONTEXT_WIN_DEFINED
+
+#ifdef SK_VULKAN
+
+#include <windows.h>
+#include "../VulkanTestContext.h"
+
+// for Windows
+struct ContextPlatformData_win {
+    HINSTANCE fHInstance;
+    HWND      fHWnd;
+};
+
+#endif // SK_VULKAN
+
+#endif
diff --git a/tools/vulkan/win/Window_win.cpp b/tools/vulkan/win/Window_win.cpp
new file mode 100644
index 0000000..20cf084
--- /dev/null
+++ b/tools/vulkan/win/Window_win.cpp
@@ -0,0 +1,206 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "Window_win.h"
+
+#include <tchar.h>
+#include <windows.h>
+#include <windowsx.h>
+
+#include "VulkanTestContext_win.h"
+
+Window* Window::CreateNativeWindow(void* platformData) {
+    HINSTANCE hInstance = (HINSTANCE)platformData;
+
+    Window_win* window = new Window_win();
+    if (!window->init(hInstance)) {
+        delete window;
+        return nullptr;
+    }
+
+    return window;
+}
+
+LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
+
+bool Window_win::init(HINSTANCE hInstance) {
+    fHInstance = hInstance ? hInstance : GetModuleHandle(nullptr);
+
+    WNDCLASSEX wcex;
+    // The main window class name
+    static const TCHAR gSZWindowClass[] = _T("SkiaApp");
+
+    wcex.cbSize = sizeof(WNDCLASSEX);
+
+    wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
+    wcex.lpfnWndProc = WndProc;
+    wcex.cbClsExtra = 0;
+    wcex.cbWndExtra = 0;
+    wcex.hInstance = fHInstance;
+    wcex.hIcon = LoadIcon(fHInstance, (LPCTSTR)IDI_WINLOGO);
+    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);;
+    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
+    wcex.lpszMenuName = nullptr;
+    wcex.lpszClassName = gSZWindowClass;
+    wcex.hIconSm = LoadIcon(fHInstance, (LPCTSTR)IDI_WINLOGO);;
+
+    if (!RegisterClassEx(&wcex)) {
+        return false;
+    }
+
+   /*
+    if (fullscreen)
+    {
+        DEVMODE dmScreenSettings;
+        // If full screen set the screen to maximum size of the users desktop and 32bit.
+        memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));
+        dmScreenSettings.dmSize = sizeof(dmScreenSettings);
+        dmScreenSettings.dmPelsWidth = (unsigned long)width;
+        dmScreenSettings.dmPelsHeight = (unsigned long)height;
+        dmScreenSettings.dmBitsPerPel = 32;
+        dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
+
+        // Change the display settings to full screen.
+        ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
+
+        // Set the position of the window to the top left corner.
+        posX = posY = 0;
+    } 
+    */
+ //   gIsFullscreen = fullscreen;
+
+    fHWnd = CreateWindow(gSZWindowClass, nullptr, WS_OVERLAPPEDWINDOW,
+                         CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, fHInstance, nullptr);
+    if (!fHWnd)
+    {
+        return false;
+    }
+
+    SetWindowLongPtr(fHWnd, GWLP_USERDATA, (LONG_PTR)this);
+
+    return true;
+}
+
+
+LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+    PAINTSTRUCT ps;
+    HDC hdc;
+
+    Window_win* window = (Window_win*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
+
+    switch (message)
+    {
+    case WM_PAINT:
+        hdc = BeginPaint(hWnd, &ps);
+        window->onPaint();
+        EndPaint(hWnd, &ps);
+        break;
+
+    case WM_CLOSE:
+    case WM_DESTROY:
+        PostQuitMessage(0);
+        break;
+
+    case WM_ACTIVATE:
+        // disable/enable rendering here, depending on wParam != WA_INACTIVE
+        break;
+
+    case WM_SIZE:
+        window->onSize();
+        break;
+
+    case WM_KEYDOWN:
+    case WM_SYSKEYDOWN:
+        {
+            DWORD dwMask = (1 << 29);
+            bool bAltDown = ((lParam & dwMask) != 0);
+            UINT theChar = MapVirtualKey((UINT)wParam, 2);
+            // Handle Extended ASCII only
+            if (theChar < 256) {
+                return window->onKeyboard(theChar, true, bAltDown);
+            }
+        }
+        break;
+
+    case WM_KEYUP:
+    case WM_SYSKEYUP:
+        {
+            DWORD dwMask = (1 << 29);
+            bool bAltDown = ((lParam & dwMask) != 0);
+            UINT theChar = MapVirtualKey((UINT)wParam, 2);
+            // Handle Extended ASCII only
+            if (theChar < 256) {
+                return window->onKeyboard(theChar, false, bAltDown);
+            }
+        }
+        break;
+
+    case WM_LBUTTONDOWN:
+    case WM_RBUTTONDOWN:
+    case WM_MBUTTONDOWN:
+    case WM_LBUTTONUP:
+    case WM_RBUTTONUP:
+    case WM_MBUTTONUP:
+        {
+            bool bLeftDown = ((wParam & MK_LBUTTON) != 0);
+            bool bRightDown = ((wParam & MK_RBUTTON) != 0);
+            bool bMiddleDown = ((wParam & MK_MBUTTON) != 0);
+
+            int xPos = GET_X_LPARAM(lParam);
+            int yPos = GET_Y_LPARAM(lParam);
+            //if (!gIsFullscreen)
+            //{
+            //    RECT rc = { 0, 0, 640, 480 };
+            //    AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
+            //    xPos -= rc.left;
+            //    yPos -= rc.top;
+            //}
+
+            return window->onMouse(bLeftDown, bRightDown, bMiddleDown, false, false, 0, xPos, yPos);
+        }
+    break;
+
+    default:
+        return DefWindowProc(hWnd, message, wParam, lParam);
+    }
+
+    return 0;
+}
+
+bool Window_win::onKeyboard(UINT nChar, bool bKeyDown, bool bAltDown) {
+    return fKeyFunc(nChar, bKeyDown, fKeyUserData);
+}
+
+bool Window_win::onMouse(bool bLeftButtonDown, bool bRightButtonDown, bool bMiddleButtonDown,
+                         bool bSideButton1Down, bool bSideButton2Down, int nMouseWheelDelta,
+                         int xPos, int yPos) {
+    return fMouseFunc(xPos, yPos, bLeftButtonDown, fMouseUserData);
+}
+
+void Window_win::setTitle(const char* title) {
+    SetWindowTextA(fHWnd, title);
+}
+
+void Window_win::show() {
+    ShowWindow(fHWnd, SW_SHOW);
+}
+
+
+bool Window_win::attach(BackEndTypes attachType, int msaaSampleCount, AttachmentInfo*) {
+    if (kVulkan_BackendType != attachType) {
+        return false;
+    }
+
+    ContextPlatformData_win platformData;
+    platformData.fHInstance = fHInstance;
+    platformData.fHWnd = fHWnd;
+
+    fTestContext = VulkanTestContext::Create((void*)&platformData, msaaSampleCount);
+
+    return (SkToBool(fTestContext));
+}
diff --git a/tools/vulkan/win/Window_win.h b/tools/vulkan/win/Window_win.h
new file mode 100644
index 0000000..90d4191
--- /dev/null
+++ b/tools/vulkan/win/Window_win.h
@@ -0,0 +1,37 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef Window_win_DEFINED
+#define Window_win_DEFINED
+
+#include <windows.h>
+#include "../Window.h"
+
+class Window_win : public Window {
+public:
+    Window_win() : Window() {}
+    ~Window_win() override {}
+
+    bool init(HINSTANCE instance);
+
+    void setTitle(const char*) override;
+    void show() override;
+
+    // event callbacks
+    bool onKeyboard(UINT nChar, bool bKeyDown, bool bAltDown);
+    bool onMouse(bool bLeftButtonDown, bool bRightButtonDown, bool bMiddleButtonDown,
+                 bool bSideButton1Down, bool bSideButton2Down, int nMouseWheelDelta,
+                 int xPos, int yPos);
+
+    bool attach(BackEndTypes attachType, int msaaSampleCount, AttachmentInfo*) override;
+
+private:
+    HINSTANCE fHInstance;
+    HWND      fHWnd;
+};
+
+#endif
diff --git a/tools/vulkan/win/main_win.cpp b/tools/vulkan/win/main_win.cpp
new file mode 100644
index 0000000..34e522a
--- /dev/null
+++ b/tools/vulkan/win/main_win.cpp
@@ -0,0 +1,76 @@
+/*
+* Copyright 2016 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include <windows.h>
+#include <tchar.h>
+
+#include "SkTypes.h"
+#include "Window_win.h"
+#include "../Application.h"
+
+static char* tchar_to_utf8(const TCHAR* str) {
+#ifdef _UNICODE
+    int size = WideCharToMultiByte(CP_UTF8, 0, str, wcslen(str), NULL, 0, NULL, NULL);
+    char* str8 = (char*)sk_malloc_throw(size + 1);
+    WideCharToMultiByte(CP_UTF8, 0, str, wcslen(str), str8, size, NULL, NULL);
+    str8[size] = '\0';
+    return str8;
+#else
+    return _strdup(str);
+#endif
+}
+
+// This file can work with GUI or CONSOLE subsystem types since we define _tWinMain and main().
+
+static int main_common(HINSTANCE hInstance, int show, int argc, char**argv);
+
+int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine,
+                       int nCmdShow) {
+
+    // convert from lpCmdLine to argc, argv.
+    char* argv[4096];
+    int argc = 0;
+    TCHAR exename[1024], *next;
+    int exenameLen = GetModuleFileName(NULL, exename, SK_ARRAY_COUNT(exename));
+    // we're ignoring the possibility that the exe name exceeds the exename buffer
+    (void)exenameLen;
+    argv[argc++] = tchar_to_utf8(exename);
+    TCHAR* arg = _tcstok_s(lpCmdLine, _T(" "), &next);
+    while (arg != NULL) {
+        argv[argc++] = tchar_to_utf8(arg);
+        arg = _tcstok_s(NULL, _T(" "), &next);
+    }
+    int result = main_common(hInstance, nCmdShow, argc, argv);
+    for (int i = 0; i < argc; ++i) {
+        sk_free(argv[i]);
+    }
+    return result;
+}
+
+int main(int argc, char**argv) {
+    return main_common(GetModuleHandle(NULL), SW_SHOW, argc, argv);
+}
+
+static int main_common(HINSTANCE hInstance, int show, int argc, char**argv) {
+
+    Application* app = Application::Create(argc, argv, (void*)hInstance);
+
+    MSG msg = { 0 };
+    // Main message loop
+    while (WM_QUIT != msg.message) {
+        if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
+            TranslateMessage(&msg);
+            DispatchMessage(&msg);
+        } 
+        
+        app->onIdle(0.0f);
+    }
+
+    delete app;
+
+    return (int)msg.wParam;
+}