aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/viewer/sk_app
diff options
context:
space:
mode:
Diffstat (limited to 'tools/viewer/sk_app')
-rw-r--r--tools/viewer/sk_app/Application.h24
-rw-r--r--tools/viewer/sk_app/VulkanWindowContext.cpp571
-rw-r--r--tools/viewer/sk_app/VulkanWindowContext.h120
-rw-r--r--tools/viewer/sk_app/Window.cpp78
-rw-r--r--tools/viewer/sk_app/Window.h151
-rw-r--r--tools/viewer/sk_app/WindowContext.h36
-rw-r--r--tools/viewer/sk_app/android/VulkanWindowContext_android.cpp47
-rw-r--r--tools/viewer/sk_app/android/VulkanWindowContext_android.h26
-rw-r--r--tools/viewer/sk_app/android/Window_android.cpp183
-rw-r--r--tools/viewer/sk_app/android/Window_android.h52
-rw-r--r--tools/viewer/sk_app/android/main_android.cpp69
-rw-r--r--tools/viewer/sk_app/win/VulkanWindowContext_win.cpp62
-rw-r--r--tools/viewer/sk_app/win/VulkanWindowContext_win.h28
-rw-r--r--tools/viewer/sk_app/win/Window_win.cpp285
-rw-r--r--tools/viewer/sk_app/win/Window_win.h37
-rw-r--r--tools/viewer/sk_app/win/main_win.cpp87
16 files changed, 1856 insertions, 0 deletions
diff --git a/tools/viewer/sk_app/Application.h b/tools/viewer/sk_app/Application.h
new file mode 100644
index 0000000000..235ff09ed4
--- /dev/null
+++ b/tools/viewer/sk_app/Application.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 Application_DEFINED
+#define Application_DEFINED
+
+namespace sk_app {
+
+class Application {
+public:
+ static Application* Create(int argc, char** argv, void* platformData);
+
+ virtual ~Application() {}
+
+ virtual void onIdle(double ms) = 0;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/viewer/sk_app/VulkanWindowContext.cpp b/tools/viewer/sk_app/VulkanWindowContext.cpp
new file mode 100644
index 0000000000..8f5f4209a4
--- /dev/null
+++ b/tools/viewer/sk_app/VulkanWindowContext.cpp
@@ -0,0 +1,571 @@
+
+/*
+ * 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 "VulkanWindowContext.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
+
+#define GET_PROC(F) f ## F = (PFN_vk ## F) vkGetInstanceProcAddr(instance, "vk" #F)
+#define GET_DEV_PROC(F) f ## F = (PFN_vk ## F) vkGetDeviceProcAddr(device, "vk" #F)
+
+namespace sk_app {
+
+VulkanWindowContext::VulkanWindowContext(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 VulkanWindowContext::initializeContext(void* platformData) {
+
+ fBackendContext.reset(GrVkBackendContext::Create(&fPresentQueueIndex, canPresent));
+ if (!(fBackendContext->fExtensions & kKHR_surface_GrVkExtensionFlag) ||
+ !(fBackendContext->fExtensions & kKHR_swapchain_GrVkExtensionFlag)) {
+ fBackendContext.reset(nullptr);
+ return;
+ }
+
+ VkInstance instance = fBackendContext->fInstance;
+ VkDevice device = fBackendContext->fDevice;
+ GET_PROC(DestroySurfaceKHR);
+ GET_PROC(GetPhysicalDeviceSurfaceSupportKHR);
+ GET_PROC(GetPhysicalDeviceSurfaceCapabilitiesKHR);
+ GET_PROC(GetPhysicalDeviceSurfaceFormatsKHR);
+ GET_PROC(GetPhysicalDeviceSurfacePresentModesKHR);
+ GET_DEV_PROC(CreateSwapchainKHR);
+ GET_DEV_PROC(DestroySwapchainKHR);
+ GET_DEV_PROC(GetSwapchainImagesKHR);
+ GET_DEV_PROC(AcquireNextImageKHR);
+ GET_DEV_PROC(QueuePresentKHR);
+
+ fContext = GrContext::Create(kVulkan_GrBackend, (GrBackendContext) fBackendContext.get());
+
+ fSurface = createVkSurface(instance, platformData);
+ if (VK_NULL_HANDLE == fSurface) {
+ fBackendContext.reset(nullptr);
+ return;
+ }
+
+ VkBool32 supported;
+ VkResult res = fGetPhysicalDeviceSurfaceSupportKHR(fBackendContext->fPhysicalDevice,
+ fPresentQueueIndex, fSurface,
+ &supported);
+ if (VK_SUCCESS != res) {
+ this->destroyContext();
+ return;
+ }
+
+ if (!this->createSwapchain(-1, -1)) {
+ this->destroyContext();
+ return;
+ }
+
+ // create presentQueue
+ vkGetDeviceQueue(fBackendContext->fDevice, fPresentQueueIndex, 0, &fPresentQueue);
+}
+
+bool VulkanWindowContext::createSwapchain(uint32_t width, uint32_t height) {
+ // check for capabilities
+ VkSurfaceCapabilitiesKHR caps;
+ VkResult res = fGetPhysicalDeviceSurfaceCapabilitiesKHR(fBackendContext->fPhysicalDevice,
+ fSurface, &caps);
+ if (VK_SUCCESS != res) {
+ return false;
+ }
+
+ uint32_t surfaceFormatCount;
+ res = fGetPhysicalDeviceSurfaceFormatsKHR(fBackendContext->fPhysicalDevice, fSurface,
+ &surfaceFormatCount, nullptr);
+ if (VK_SUCCESS != res) {
+ return false;
+ }
+
+ SkAutoMalloc surfaceFormatAlloc(surfaceFormatCount * sizeof(VkSurfaceFormatKHR));
+ VkSurfaceFormatKHR* surfaceFormats = (VkSurfaceFormatKHR*)surfaceFormatAlloc.get();
+ res = fGetPhysicalDeviceSurfaceFormatsKHR(fBackendContext->fPhysicalDevice, fSurface,
+ &surfaceFormatCount, surfaceFormats);
+ if (VK_SUCCESS != res) {
+ return false;
+ }
+
+ uint32_t presentModeCount;
+ res = fGetPhysicalDeviceSurfacePresentModesKHR(fBackendContext->fPhysicalDevice, fSurface,
+ &presentModeCount, nullptr);
+ if (VK_SUCCESS != res) {
+ return false;
+ }
+
+ SkAutoMalloc presentModeAlloc(presentModeCount * sizeof(VkPresentModeKHR));
+ VkPresentModeKHR* presentModes = (VkPresentModeKHR*)presentModeAlloc.get();
+ res = fGetPhysicalDeviceSurfacePresentModesKHR(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;
+
+ // Pick our surface format -- for now, the first one
+ VkFormat surfaceFormat = surfaceFormats[0].format;
+ VkColorSpaceKHR colorSpace = surfaceFormats[0].colorSpace;
+
+ // If mailbox mode is available, use it, as it is the lowest-latency non-
+ // tearing mode. If not, fall back to FIFO which is always available.
+ VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR;
+ for (uint32_t i = 0; i < presentModeCount; ++i) {
+ // use mailbox
+ if (VK_PRESENT_MODE_MAILBOX_KHR == presentModes[i]) {
+ mode = presentModes[i];
+ break;
+ }
+ }
+
+ VkSwapchainCreateInfoKHR swapchainCreateInfo;
+ memset(&swapchainCreateInfo, 0, sizeof(VkSwapchainCreateInfoKHR));
+ swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+ swapchainCreateInfo.surface = fSurface;
+ swapchainCreateInfo.minImageCount = imageCount;
+ swapchainCreateInfo.imageFormat = surfaceFormat;
+ swapchainCreateInfo.imageColorSpace = colorSpace;
+ swapchainCreateInfo.imageExtent = extent;
+ swapchainCreateInfo.imageArrayLayers = 1;
+ swapchainCreateInfo.imageUsage = usageFlags;
+
+ uint32_t queueFamilies[] = { fBackendContext->fGraphicsQueueIndex, fPresentQueueIndex };
+ if (fBackendContext->fGraphicsQueueIndex != 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 = fCreateSwapchainKHR(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();
+
+ fDestroySwapchainKHR(fBackendContext->fDevice, swapchainCreateInfo.oldSwapchain, nullptr);
+ }
+
+ this->createBuffers(swapchainCreateInfo.imageFormat);
+
+ return true;
+}
+
+void VulkanWindowContext::createBuffers(VkFormat format) {
+ GrVkFormatToPixelConfig(format, &fPixelConfig);
+
+ fGetSwapchainImagesKHR(fBackendContext->fDevice, fSwapchain, &fImageCount, nullptr);
+ SkASSERT(fImageCount);
+ fImages = new VkImage[fImageCount];
+ fGetSwapchainImagesKHR(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 = VK_NULL_HANDLE;
+ info.fImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+ info.fImageTiling = VK_IMAGE_TILING_OPTIMAL;
+ info.fFormat = format;
+ 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->fGraphicsQueueIndex;
+ 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 VulkanWindowContext::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;
+}
+
+VulkanWindowContext::~VulkanWindowContext() {
+ this->destroyContext();
+}
+
+void VulkanWindowContext::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) {
+ fDestroySwapchainKHR(fBackendContext->fDevice, fSwapchain, nullptr);
+ fSwapchain = VK_NULL_HANDLE;
+ }
+
+ if (VK_NULL_HANDLE != fSurface) {
+ fDestroySurfaceKHR(fBackendContext->fInstance, fSurface, nullptr);
+ fSurface = VK_NULL_HANDLE;
+ }
+
+ delete fContext;
+
+ fBackendContext.reset(nullptr);
+}
+
+VulkanWindowContext::BackbufferInfo* VulkanWindowContext::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* VulkanWindowContext::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 = fAcquireNextImageKHR(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*
+ // maybe use attach somehow? but need a Window
+ return nullptr;
+ }
+ if (VK_ERROR_OUT_OF_DATE_KHR == res) {
+ // tear swapchain down and try again
+ if (!this->createSwapchain(0, 0)) {
+ return nullptr;
+ }
+
+ // acquire the image
+ res = fAcquireNextImageKHR(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->fGraphicsQueueIndex, // 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]));
+
+ VkPipelineStageFlags waitDstStageFlags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ // 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 = &waitDstStageFlags;
+ 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 VulkanWindowContext::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->fGraphicsQueueIndex, // 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
+ };
+
+ fQueuePresentKHR(fPresentQueue, &presentInfo);
+
+}
+
+} //namespace sk_app
diff --git a/tools/viewer/sk_app/VulkanWindowContext.h b/tools/viewer/sk_app/VulkanWindowContext.h
new file mode 100644
index 0000000000..48f8ed536b
--- /dev/null
+++ b/tools/viewer/sk_app/VulkanWindowContext.h
@@ -0,0 +1,120 @@
+
+/*
+ * 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 VulkanWindowContext_DEFINED
+#define VulkanWindowContext_DEFINED
+
+#ifdef SK_VULKAN
+
+#include "vk/GrVkBackendContext.h"
+#include "WindowContext.h"
+
+class SkSurface;
+class GrContext;
+
+namespace sk_app {
+
+class VulkanWindowContext : public WindowContext {
+public:
+ ~VulkanWindowContext() override;
+
+ // each platform will have to implement these in its CPP file
+ static VkSurfaceKHR createVkSurface(VkInstance, void* platformData);
+ static bool canPresent(VkInstance, VkPhysicalDevice, uint32_t queueFamilyIndex);
+
+ static VulkanWindowContext* Create(void* platformData, int msaaSampleCount) {
+ VulkanWindowContext* ctx = new VulkanWindowContext(platformData, msaaSampleCount);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+ }
+
+ SkSurface* getBackbufferSurface() override;
+ void swapBuffers() override;
+
+ bool makeCurrent() override { return true; }
+
+ bool isValid() override { return SkToBool(fBackendContext.get()); }
+
+ void resize(uint32_t w, uint32_t h) override {
+ this->createSwapchain(w, h);
+ }
+
+ GrBackendContext getBackendContext() override {
+ return (GrBackendContext) fBackendContext.get();
+ }
+
+private:
+ VulkanWindowContext();
+ VulkanWindowContext(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(VkFormat format);
+ void destroyBuffers();
+
+ SkAutoTUnref<const GrVkBackendContext> fBackendContext;
+
+ // simple wrapper class that exists only to initialize a pointer to NULL
+ template <typename FNPTR_TYPE> class VkPtr {
+ public:
+ VkPtr() : fPtr(NULL) {}
+ VkPtr operator=(FNPTR_TYPE ptr) { fPtr = ptr; return *this; }
+ operator FNPTR_TYPE() const { return fPtr; }
+ private:
+ FNPTR_TYPE fPtr;
+ };
+
+ // WSI interface functions
+ VkPtr<PFN_vkDestroySurfaceKHR> fDestroySurfaceKHR;
+ VkPtr<PFN_vkGetPhysicalDeviceSurfaceSupportKHR> fGetPhysicalDeviceSurfaceSupportKHR;
+ VkPtr<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR> fGetPhysicalDeviceSurfaceCapabilitiesKHR;
+ VkPtr<PFN_vkGetPhysicalDeviceSurfaceFormatsKHR> fGetPhysicalDeviceSurfaceFormatsKHR;
+ VkPtr<PFN_vkGetPhysicalDeviceSurfacePresentModesKHR> fGetPhysicalDeviceSurfacePresentModesKHR;
+
+ VkPtr<PFN_vkCreateSwapchainKHR> fCreateSwapchainKHR;
+ VkPtr<PFN_vkDestroySwapchainKHR> fDestroySwapchainKHR;
+ VkPtr<PFN_vkGetSwapchainImagesKHR> fGetSwapchainImagesKHR;
+ VkPtr<PFN_vkAcquireNextImageKHR> fAcquireNextImageKHR;
+ VkPtr<PFN_vkQueuePresentKHR> fQueuePresentKHR;
+ VkPtr<PFN_vkCreateSharedSwapchainsKHR> fCreateSharedSwapchainsKHR;
+
+ 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;
+};
+
+} // namespace sk_app
+
+#endif // SK_VULKAN
+
+#endif
diff --git a/tools/viewer/sk_app/Window.cpp b/tools/viewer/sk_app/Window.cpp
new file mode 100644
index 0000000000..a7fd6b0cf6
--- /dev/null
+++ b/tools/viewer/sk_app/Window.cpp
@@ -0,0 +1,78 @@
+/*
+* 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 "VulkanWindowContext.h"
+
+namespace sk_app {
+
+static bool default_char_func(SkUnichar c, uint32_t modifiers, void* userData) {
+ return false;
+}
+
+static bool default_key_func(Window::Key key, Window::InputState state, uint32_t modifiers,
+ void* userData) {
+ return false;
+}
+
+static bool default_mouse_func(int x, int y, Window::InputState state, uint32_t modifiers,
+ void* userData) {
+ return false;
+}
+
+static void default_paint_func(SkCanvas*, void* userData) {}
+
+Window::Window() : fCharFunc(default_char_func)
+ , fKeyFunc(default_key_func)
+ , fMouseFunc(default_mouse_func)
+ , fPaintFunc(default_paint_func) {
+}
+
+void Window::detach() {
+ delete fWindowContext;
+ fWindowContext = nullptr;
+}
+
+bool Window::onChar(SkUnichar c, uint32_t modifiers) {
+ return fCharFunc(c, modifiers, fCharUserData);
+}
+
+bool Window::onKey(Key key, InputState state, uint32_t modifiers) {
+ return fKeyFunc(key, state, modifiers, fKeyUserData);
+}
+
+bool Window::onMouse(int x, int y, InputState state, uint32_t modifiers) {
+ return fMouseFunc(x, y, state, modifiers, fMouseUserData);
+}
+
+void Window::onPaint() {
+ SkSurface* backbuffer = fWindowContext->getBackbufferSurface();
+ if (backbuffer) {
+ // draw into the canvas of this surface
+ SkCanvas* canvas = backbuffer->getCanvas();
+
+ fPaintFunc(canvas, fPaintUserData);
+
+ canvas->flush();
+
+ fWindowContext->swapBuffers();
+ } else {
+ // try recreating testcontext
+ }
+
+}
+
+void Window::onResize(uint32_t w, uint32_t h) {
+ fWidth = w;
+ fHeight = h;
+ fWindowContext->resize(w, h);
+}
+
+} // namespace sk_app
diff --git a/tools/viewer/sk_app/Window.h b/tools/viewer/sk_app/Window.h
new file mode 100644
index 0000000000..3dd558e75a
--- /dev/null
+++ b/tools/viewer/sk_app/Window.h
@@ -0,0 +1,151 @@
+/*
+* 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
+
+#include "SkTypes.h"
+#include "SkRect.h"
+
+class SkCanvas;
+
+namespace sk_app {
+
+class WindowContext;
+
+class Window {
+public:
+ static Window* CreateNativeWindow(void* platformData);
+
+ virtual ~Window() {};
+
+ virtual void setTitle(const char*) = 0;
+ virtual void show() = 0;
+ virtual void inval() = 0;
+
+ virtual bool scaleContentToFit() const { return false; }
+ virtual bool supportsContentRect() const { return false; }
+ virtual SkRect getContentRect() { return SkRect::MakeEmpty(); }
+
+ enum BackEndType {
+ kNativeGL_BackendType,
+ kVulkan_BackendType
+ };
+
+ virtual bool attach(BackEndType attachType, int msaaSampleCount) = 0;
+ void detach();
+
+ // input handling
+ enum Key {
+ kNONE_Key, //corresponds to android's UNKNOWN
+
+ kLeftSoftKey_Key,
+ kRightSoftKey_Key,
+
+ kHome_Key, //!< the home key - added to match android
+ kBack_Key, //!< (CLR)
+ kSend_Key, //!< the green (talk) key
+ kEnd_Key, //!< the red key
+
+ k0_Key,
+ k1_Key,
+ k2_Key,
+ k3_Key,
+ k4_Key,
+ k5_Key,
+ k6_Key,
+ k7_Key,
+ k8_Key,
+ k9_Key,
+ kStar_Key, //!< the * key
+ kHash_Key, //!< the # key
+
+ kUp_Key,
+ kDown_Key,
+ kLeft_Key,
+ kRight_Key,
+
+ kOK_Key, //!< the center key
+
+ kVolUp_Key, //!< volume up - match android
+ kVolDown_Key, //!< volume down - same
+ kPower_Key, //!< power button - same
+ kCamera_Key, //!< camera - same
+
+ kLast_Key = kCamera_Key
+ };
+ static const int kKeyCount = kLast_Key + 1;
+
+ enum ModifierKeys {
+ kShift_ModifierKey = 1 << 0,
+ kControl_ModifierKey = 1 << 1,
+ kOption_ModifierKey = 1 << 2, // same as ALT
+ kCommand_ModifierKey = 1 << 3,
+ kFirstPress_ModifierKey = 1 << 4,
+ };
+
+ enum InputState {
+ kDown_InputState,
+ kUp_InputState,
+ kMove_InputState // only valid for mouse
+ };
+
+ // return value of 'true' means 'I have handled this event'
+ typedef bool(*OnCharFunc)(SkUnichar c, uint32_t modifiers, void* userData);
+ typedef bool(*OnKeyFunc)(Key key, InputState state, uint32_t modifiers, void* userData);
+ typedef bool(*OnMouseFunc)(int x, int y, InputState state, uint32_t modifiers, void* userData);
+ typedef void(*OnPaintFunc)(SkCanvas*, void* userData);
+
+ void registerCharFunc(OnCharFunc func, void* userData) {
+ fCharFunc = func;
+ fCharUserData = 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;
+ }
+
+ bool onChar(SkUnichar c, uint32_t modifiers);
+ bool onKey(Key key, InputState state, uint32_t modifiers);
+ bool onMouse(int x, int y, InputState state, uint32_t modifiers);
+ void onPaint();
+ void onResize(uint32_t width, uint32_t height);
+
+ uint32_t width() { return fWidth; }
+ uint32_t height() { return fHeight; }
+
+protected:
+ Window();
+
+ uint32_t fWidth;
+ uint32_t fHeight;
+
+ OnCharFunc fCharFunc;
+ void* fCharUserData;
+ OnKeyFunc fKeyFunc;
+ void* fKeyUserData;
+ OnMouseFunc fMouseFunc;
+ void* fMouseUserData;
+ OnPaintFunc fPaintFunc;
+ void* fPaintUserData;
+
+ WindowContext* fWindowContext;
+};
+
+} // namespace sk_app
+#endif
diff --git a/tools/viewer/sk_app/WindowContext.h b/tools/viewer/sk_app/WindowContext.h
new file mode 100644
index 0000000000..5811bedd32
--- /dev/null
+++ b/tools/viewer/sk_app/WindowContext.h
@@ -0,0 +1,36 @@
+/*
+ * 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 WindowContext_DEFINED
+#define WindowContext_DEFINED
+
+#include "GrTypes.h"
+
+class SkSurface;
+
+namespace sk_app {
+
+// TODO: fill this out with an interface
+class WindowContext {
+public:
+ virtual ~WindowContext() {}
+
+ virtual SkSurface* getBackbufferSurface() = 0;
+
+ virtual void swapBuffers() = 0;
+
+ virtual bool makeCurrent() = 0;
+
+ virtual bool isValid() = 0;
+
+ virtual void resize(uint32_t w, uint32_t h) = 0;
+
+ virtual GrBackendContext getBackendContext() = 0;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/viewer/sk_app/android/VulkanWindowContext_android.cpp b/tools/viewer/sk_app/android/VulkanWindowContext_android.cpp
new file mode 100644
index 0000000000..1ef994aa3f
--- /dev/null
+++ b/tools/viewer/sk_app/android/VulkanWindowContext_android.cpp
@@ -0,0 +1,47 @@
+
+/*
+ * 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 "VulkanWindowContext_android.h"
+
+#include "vk/GrVkInterface.h"
+#include "vk/GrVkUtil.h"
+
+namespace sk_app {
+
+VkSurfaceKHR VulkanWindowContext::createVkSurface(VkInstance instance, void* platformData) {
+ static PFN_vkCreateAndroidSurfaceKHR createAndroidSurfaceKHR = nullptr;
+ if (!createAndroidSurfaceKHR) {
+ createAndroidSurfaceKHR = (PFN_vkCreateAndroidSurfaceKHR)vkGetInstanceProcAddr(instance,
+ "vkCreateAndroidSurfaceKHR");
+ }
+
+ if (!platformData) {
+ return VK_NULL_HANDLE;
+ }
+ ContextPlatformData_android* androidPlatformData =
+ reinterpret_cast<ContextPlatformData_android*>(platformData);
+ VkSurfaceKHR surface;
+
+ VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
+ memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR));
+ surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
+ surfaceCreateInfo.pNext = nullptr;
+ surfaceCreateInfo.flags = 0;
+ surfaceCreateInfo.window = androidPlatformData->fNativeWindow;
+
+ VkResult res = createAndroidSurfaceKHR(instance, &surfaceCreateInfo,
+ nullptr, &surface);
+ return (VK_SUCCESS == res) ? surface : VK_NULL_HANDLE;
+}
+
+bool VulkanWindowContext::canPresent(VkInstance instance, VkPhysicalDevice physDev,
+ uint32_t queueFamilyIndex) {
+ return true;
+}
+
+} // namespace sk_app
diff --git a/tools/viewer/sk_app/android/VulkanWindowContext_android.h b/tools/viewer/sk_app/android/VulkanWindowContext_android.h
new file mode 100644
index 0000000000..dd53e13b69
--- /dev/null
+++ b/tools/viewer/sk_app/android/VulkanWindowContext_android.h
@@ -0,0 +1,26 @@
+
+/*
+ * 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_ANDROID_DEFINED
+#define VULKANTESTCONTEXT_ANDROID_DEFINED
+
+#ifdef SK_VULKAN
+
+#include "../VulkanWindowContext.h"
+
+struct ANativeWindow;
+
+namespace sk_app {
+
+struct ContextPlatformData_android {
+ ANativeWindow* fNativeWindow;
+};
+
+}
+#endif // SK_VULKAN
+
+#endif
diff --git a/tools/viewer/sk_app/android/Window_android.cpp b/tools/viewer/sk_app/android/Window_android.cpp
new file mode 100644
index 0000000000..94be02c933
--- /dev/null
+++ b/tools/viewer/sk_app/android/Window_android.cpp
@@ -0,0 +1,183 @@
+/*
+* 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_android.h"
+
+#include "VulkanWindowContext_android.h"
+
+namespace sk_app {
+
+Window* Window::CreateNativeWindow(void* platformData) {
+ Window_android* window = new Window_android();
+ if (!window->init((android_app*)platformData)) {
+ delete window;
+ return nullptr;
+ }
+ return window;
+}
+
+static void handle_cmd(struct android_app* app, int32_t cmd);
+static int32_t handle_input(struct android_app* app, AInputEvent* event);
+
+bool Window_android::init(android_app* app) {
+ SkASSERT(app);
+ mApp = app;
+ mApp->userData = this;
+ mApp->onAppCmd = handle_cmd;
+ mApp->onInputEvent = handle_input;
+ return true;
+}
+
+void Window_android::setTitle(const char* title) {
+ //todo
+ SkDebugf("Title: %s", title);
+}
+
+bool Window_android::attach(BackEndType attachType, int msaaSampleCount) {
+ if (kVulkan_BackendType != attachType) {
+ return false;
+ }
+
+ mSampleCount = msaaSampleCount;
+
+ // We delay the creation of fTestContext until Android informs us that
+ // the native window is ready to use.
+ return true;
+}
+
+void Window_android::initDisplay(ANativeWindow* window) {
+ SkASSERT(window);
+ ContextPlatformData_android platformData;
+ platformData.fNativeWindow = window;
+ fWindowContext = VulkanWindowContext::Create((void*)&platformData, mSampleCount);
+}
+
+static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) {
+ if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) {
+ SkDebugf("Failure writing android_app cmd: %s\n", strerror(errno));
+ }
+}
+
+void Window_android::inval() {
+ android_app_write_cmd(mApp, APP_CMD_INVAL_WINDOW);
+}
+
+void Window_android::paintIfNeeded() {
+ if (mApp->window || !mContentRect.isEmpty()) {
+ this->onPaint();
+ }
+}
+
+/**
+ * Process the next main command.
+ */
+static void handle_cmd(struct android_app* app, int32_t cmd) {
+ Window_android* window = (Window_android*)app->userData;
+ switch (cmd) {
+ case APP_CMD_INIT_WINDOW:
+ // The window is being shown, get it ready.
+ SkASSERT(app->window);
+ window->initDisplay(app->window);
+ window->paintIfNeeded();
+ break;
+ case APP_CMD_WINDOW_RESIZED: {
+ int width = ANativeWindow_getWidth(app->window);
+ int height = ANativeWindow_getHeight(app->window);
+ window->onResize(width, height);
+ break;
+ }
+ case APP_CMD_CONTENT_RECT_CHANGED:
+ window->setContentRect(app->contentRect.left, app->contentRect.top,
+ app->contentRect.right, app->contentRect.bottom);
+ window->paintIfNeeded();
+ break;
+ case APP_CMD_TERM_WINDOW:
+ // The window is being hidden or closed, clean it up.
+ window->detach();
+ break;
+ case APP_CMD_INVAL_WINDOW:
+ window->paintIfNeeded();
+ break;
+ }
+}
+
+static Window::Key get_key(int32_t keycode) {
+ static const struct {
+ int32_t fAndroidKey;
+ Window::Key fWindowKey;
+ } gPair[] = {
+ { AKEYCODE_BACK, Window::kBack_Key },
+ { AKEYCODE_VOLUME_UP, Window::kLeft_Key },
+ { AKEYCODE_VOLUME_DOWN, Window::kRight_Key }
+ };
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
+ if (gPair[i].fAndroidKey == keycode) {
+ return gPair[i].fWindowKey;
+ }
+ }
+ return Window::kNONE_Key;
+}
+
+static Window::InputState get_action(int32_t action) {
+ static const struct {
+ int32_t fAndroidAction;
+ Window::InputState fInputState;
+ } gPair[] = {
+ { AKEY_STATE_DOWN, Window::kDown_InputState },
+ { AKEY_STATE_UP, Window::kUp_InputState },
+ };
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
+ if (gPair[i].fAndroidAction == action) {
+ return gPair[i].fInputState;
+ }
+ }
+ return Window::kMove_InputState;
+}
+
+static int32_t get_key_modifiers(AInputEvent* event) {
+ static const struct {
+ int32_t fAndroidState;
+ int32_t fWindowModifier;
+ } gPair[] = {
+ { AMETA_SHIFT_ON, Window::kShift_ModifierKey },
+ { AMETA_CTRL_ON, Window::kControl_ModifierKey },
+ };
+
+ int32_t metaState = AKeyEvent_getMetaState(event);
+ int32_t modifiers = 0;
+
+ if (AKeyEvent_getRepeatCount(event) == 0) {
+ modifiers |= Window::kFirstPress_ModifierKey;
+ }
+
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
+ if (gPair[i].fAndroidState == metaState) {
+ modifiers |= gPair[i].fWindowModifier;
+ }
+ }
+ return modifiers;
+}
+
+/**
+ * Process the next input event.
+ */
+static int32_t handle_input(struct android_app* app, AInputEvent* event) {
+ Window_android* window = (Window_android*)app->userData;
+ switch(AInputEvent_getType(event)) {
+ case AINPUT_EVENT_TYPE_MOTION:
+ break;
+ case AINPUT_EVENT_TYPE_KEY:
+ Window::Key key = get_key(AKeyEvent_getKeyCode(event));
+ Window::InputState state = get_action(AKeyEvent_getAction(event));
+ int32_t mod = get_key_modifiers(event);
+ window->onKey(key, state, mod);
+ return true; // eat all key events
+ }
+ return 0;
+}
+
+} // namespace sk_app
diff --git a/tools/viewer/sk_app/android/Window_android.h b/tools/viewer/sk_app/android/Window_android.h
new file mode 100644
index 0000000000..d41f0a54c2
--- /dev/null
+++ b/tools/viewer/sk_app/android/Window_android.h
@@ -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.
+*/
+
+#ifndef Window_android_DEFINED
+#define Window_android_DEFINED
+
+#include "../Window.h"
+#include <android_native_app_glue.h>
+
+namespace sk_app {
+
+enum {
+ /**
+ * Leave plenty of space between this item and the ones defined in the glue layer
+ */
+ APP_CMD_INVAL_WINDOW = 64,
+};
+
+class Window_android : public Window {
+public:
+ Window_android() : Window() {}
+ ~Window_android() override {}
+
+ bool init(android_app* app_state);
+ void initDisplay(ANativeWindow* window);
+
+ void setTitle(const char*) override;
+ void show() override {}
+
+ bool attach(BackEndType attachType, int msaaSampleCount, bool deepColor) override;
+ void inval() override;
+
+ void paintIfNeeded();
+
+ bool scaleContentToFit() const override { return true; }
+ bool supportsContentRect() const override { return true; }
+ SkRect getContentRect() override { return mContentRect; }
+ void setContentRect(int l, int t, int r, int b) { mContentRect.set(l,t,r,b); }
+
+private:
+ android_app* mApp = nullptr;
+ SkRect mContentRect;
+ int mSampleCount = 0;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/viewer/sk_app/android/main_android.cpp b/tools/viewer/sk_app/android/main_android.cpp
new file mode 100644
index 0000000000..9334f0ccd3
--- /dev/null
+++ b/tools/viewer/sk_app/android/main_android.cpp
@@ -0,0 +1,69 @@
+/*
+* 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 <jni.h>
+#include <errno.h>
+
+#include <android_native_app_glue.h>
+
+#include "../Application.h"
+#include "Timer.h"
+
+static double now_ms() { return SkTime::GetNSecs() * 1e-6; }
+
+using sk_app::Application;
+
+/**
+ * This is the main entry point of a native application that is using
+ * android_native_app_glue. It runs in its own thread, with its own
+ * event loop for receiving input events and doing other things.
+ */
+void android_main(struct android_app* state) {
+ // Make sure glue isn't stripped.
+ app_dummy();
+
+ static const char* gCmdLine[] = {
+ "viewer",
+ "--skps",
+ "/data/local/tmp/skp",
+ };
+
+ std::unique_ptr<Application> vkApp(Application::Create(SK_ARRAY_COUNT(gCmdLine),
+ const_cast<char**>(gCmdLine),
+ state));
+
+ double currentTime = 0.0;
+ double previousTime = 0.0;
+
+ // loop waiting for stuff to do.
+ while (1) {
+ // Read all pending events.
+ int ident;
+ int events;
+ struct android_poll_source* source;
+
+ // block forever waiting for events.
+ while ((ident=ALooper_pollAll(-1, NULL, &events,
+ (void**)&source)) >= 0) {
+
+ // Process this event.
+ if (source != NULL) {
+ source->process(state, source);
+ }
+
+ // Check if we are exiting.
+ if (state->destroyRequested != 0) {
+ return;
+ }
+
+ previousTime = currentTime;
+ currentTime = now_ms();
+ vkApp->onIdle(currentTime - previousTime);
+ }
+ }
+}
+//END_INCLUDE(all)
diff --git a/tools/viewer/sk_app/win/VulkanWindowContext_win.cpp b/tools/viewer/sk_app/win/VulkanWindowContext_win.cpp
new file mode 100644
index 0000000000..05f3bddd31
--- /dev/null
+++ b/tools/viewer/sk_app/win/VulkanWindowContext_win.cpp
@@ -0,0 +1,62 @@
+
+/*
+ * 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 "VulkanWindowContext_win.h"
+
+#include "vk/GrVkInterface.h"
+#include "vk/GrVkUtil.h"
+
+namespace sk_app {
+
+// Platform dependant call
+VkSurfaceKHR VulkanWindowContext::createVkSurface(VkInstance instance, void* platformData) {
+ static PFN_vkCreateWin32SurfaceKHR createWin32SurfaceKHR = nullptr;
+ if (!createWin32SurfaceKHR) {
+ createWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR) vkGetInstanceProcAddr(instance,
+ "vkCreateWin32SurfaceKHR");
+ }
+
+ if (!platformData) {
+ return VK_NULL_HANDLE;
+ }
+ 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 = createWin32SurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
+ if (VK_SUCCESS != res) {
+ return VK_NULL_HANDLE;
+ }
+
+ return surface;
+}
+
+// Platform dependant call
+bool VulkanWindowContext::canPresent(VkInstance instance, VkPhysicalDevice physDev,
+ uint32_t queueFamilyIndex) {
+ static PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR
+ getPhysicalDeviceWin32PresentationSupportKHR = nullptr;
+ if (!getPhysicalDeviceWin32PresentationSupportKHR) {
+ getPhysicalDeviceWin32PresentationSupportKHR =
+ (PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR) vkGetInstanceProcAddr(instance,
+ "vkGetPhysicalDeviceWin32PresentationSupportKHR");
+ }
+
+ VkBool32 check = getPhysicalDeviceWin32PresentationSupportKHR(physDev, queueFamilyIndex);
+ return (VK_FALSE != check);
+}
+
+} // namespace sk_app
diff --git a/tools/viewer/sk_app/win/VulkanWindowContext_win.h b/tools/viewer/sk_app/win/VulkanWindowContext_win.h
new file mode 100644
index 0000000000..e0b5a156f9
--- /dev/null
+++ b/tools/viewer/sk_app/win/VulkanWindowContext_win.h
@@ -0,0 +1,28 @@
+
+/*
+ * 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 "../VulkanWindowContext.h"
+
+namespace sk_app {
+
+// for Windows
+struct ContextPlatformData_win {
+ HINSTANCE fHInstance;
+ HWND fHWnd;
+};
+
+}
+
+#endif // SK_VULKAN
+
+#endif
diff --git a/tools/viewer/sk_app/win/Window_win.cpp b/tools/viewer/sk_app/win/Window_win.cpp
new file mode 100644
index 0000000000..1dd07a6c9c
--- /dev/null
+++ b/tools/viewer/sk_app/win/Window_win.cpp
@@ -0,0 +1,285 @@
+/*
+* 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 "SkUtils.h"
+#include "VulkanWindowContext_win.h"
+
+namespace sk_app {
+
+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;
+}
+
+static Window::Key get_key(WPARAM vk) {
+ static const struct {
+ WPARAM fVK;
+ Window::Key fKey;
+ } gPair[] = {
+ { VK_BACK, Window::kBack_Key },
+ { VK_CLEAR, Window::kBack_Key },
+ { VK_RETURN, Window::kOK_Key },
+ { VK_UP, Window::kUp_Key },
+ { VK_DOWN, Window::kDown_Key },
+ { VK_LEFT, Window::kLeft_Key },
+ { VK_RIGHT, Window::kRight_Key }
+ };
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
+ if (gPair[i].fVK == vk) {
+ return gPair[i].fKey;
+ }
+ }
+ return Window::kNONE_Key;
+}
+
+static uint32_t get_modifiers(UINT message, WPARAM wParam, LPARAM lParam) {
+ uint32_t modifiers = 0;
+
+ switch (message) {
+ case WM_UNICHAR:
+ case WM_CHAR:
+ if (0 == (lParam & (1 << 30))) {
+ modifiers |= Window::kFirstPress_ModifierKey;
+ }
+ if (lParam & (1 << 29)) {
+ modifiers |= Window::kOption_ModifierKey;
+ }
+ break;
+
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ if (0 == (lParam & (1 << 30))) {
+ modifiers |= Window::kFirstPress_ModifierKey;
+ }
+ if (lParam & (1 << 29)) {
+ modifiers |= Window::kOption_ModifierKey;
+ }
+ break;
+
+ case WM_KEYUP:
+ case WM_SYSKEYUP:
+ if (lParam & (1 << 29)) {
+ modifiers |= Window::kOption_ModifierKey;
+ }
+ break;
+
+ case WM_LBUTTONDOWN:
+ case WM_LBUTTONUP:
+ case WM_MOUSEMOVE:
+ if (wParam & MK_CONTROL) {
+ modifiers |= Window::kControl_ModifierKey;
+ }
+ if (wParam & MK_SHIFT) {
+ modifiers |= Window::kShift_ModifierKey;
+ }
+ }
+
+ return modifiers;
+}
+
+LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ PAINTSTRUCT ps;
+ HDC hdc;
+
+ Window_win* window = (Window_win*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
+
+ bool eventHandled = false;
+
+ switch (message) {
+ case WM_PAINT:
+ hdc = BeginPaint(hWnd, &ps);
+ window->onPaint();
+ EndPaint(hWnd, &ps);
+ eventHandled = true;
+ break;
+
+ case WM_CLOSE:
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ eventHandled = true;
+ break;
+
+ case WM_ACTIVATE:
+ // disable/enable rendering here, depending on wParam != WA_INACTIVE
+ break;
+
+ case WM_SIZE:
+ window->onResize(LOWORD(lParam), HIWORD(lParam));
+ eventHandled = true;
+ break;
+
+ case WM_UNICHAR:
+ eventHandled = window->onChar((SkUnichar)wParam,
+ get_modifiers(message, wParam, lParam));
+ break;
+
+ case WM_CHAR: {
+ const uint16_t* c = reinterpret_cast<uint16_t*>(&wParam);
+ eventHandled = window->onChar(SkUTF16_NextUnichar(&c),
+ get_modifiers(message, wParam, lParam));
+ } break;
+
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN:
+ eventHandled = window->onKey(get_key(wParam), Window::kDown_InputState,
+ get_modifiers(message, wParam, lParam));
+ break;
+
+ case WM_KEYUP:
+ case WM_SYSKEYUP:
+ eventHandled = window->onKey(get_key(wParam), Window::kUp_InputState,
+ get_modifiers(message, wParam, lParam));
+ break;
+
+ case WM_LBUTTONDOWN:
+ case WM_LBUTTONUP: {
+ 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;
+ //}
+
+ Window::InputState istate = ((wParam & MK_LBUTTON) != 0) ? Window::kDown_InputState
+ : Window::kUp_InputState;
+
+ eventHandled = window->onMouse(xPos, yPos, istate,
+ get_modifiers(message, wParam, lParam));
+ } break;
+
+ case WM_MOUSEMOVE:
+ // only track if left button is down
+ if ((wParam & MK_LBUTTON) != 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;
+ //}
+
+ eventHandled = window->onMouse(xPos, yPos, Window::kMove_InputState,
+ get_modifiers(message, wParam, lParam));
+ }
+ break;
+
+ default:
+ return DefWindowProc(hWnd, message, wParam, lParam);
+ }
+
+ return eventHandled ? 0 : 1;
+}
+
+void Window_win::setTitle(const char* title) {
+ SetWindowTextA(fHWnd, title);
+}
+
+void Window_win::show() {
+ ShowWindow(fHWnd, SW_SHOW);
+}
+
+
+bool Window_win::attach(BackEndType attachType, int msaaSampleCount) {
+ if (kVulkan_BackendType != attachType) {
+ return false;
+ }
+
+ ContextPlatformData_win platformData;
+ platformData.fHInstance = fHInstance;
+ platformData.fHWnd = fHWnd;
+
+ fWindowContext = VulkanWindowContext::Create((void*)&platformData, msaaSampleCount);
+
+ return (SkToBool(fWindowContext));
+}
+
+void Window_win::inval() {
+ InvalidateRect(fHWnd, nullptr, false);
+}
+
+} // namespace sk_app
diff --git a/tools/viewer/sk_app/win/Window_win.h b/tools/viewer/sk_app/win/Window_win.h
new file mode 100644
index 0000000000..e295212f31
--- /dev/null
+++ b/tools/viewer/sk_app/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"
+
+namespace sk_app {
+
+class Window_win : public Window {
+public:
+ Window_win() : Window() {}
+ ~Window_win() override {}
+
+ bool init(HINSTANCE instance);
+
+ void setTitle(const char*) override;
+ void show() override;
+
+ bool attach(BackEndType attachType, int msaaSampleCount) override;
+
+ void inval() override;
+
+private:
+ HINSTANCE fHInstance;
+ HWND fHWnd;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/viewer/sk_app/win/main_win.cpp b/tools/viewer/sk_app/win/main_win.cpp
new file mode 100644
index 0000000000..ba1629c837
--- /dev/null
+++ b/tools/viewer/sk_app/win/main_win.cpp
@@ -0,0 +1,87 @@
+/*
+* 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 "Timer.h"
+#include "Window_win.h"
+#include "../Application.h"
+
+using sk_app::Application;
+
+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
+}
+
+static double now_ms() { return SkTime::GetNSecs() * 1e-6; }
+
+// 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 };
+
+ double currentTime = 0.0;
+ double previousTime = 0.0;
+
+ // Main message loop
+ while (WM_QUIT != msg.message) {
+ if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ } else {
+ previousTime = currentTime;
+ currentTime = now_ms();
+ app->onIdle(currentTime - previousTime);
+ }
+ }
+
+ delete app;
+
+ return (int)msg.wParam;
+}