aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/sk_app
diff options
context:
space:
mode:
authorGravatar Brian Osman <brianosman@google.com>2017-11-21 13:18:02 -0500
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-11-21 18:37:19 +0000
commiteff04b5ec287e0fee0d44207c10d2d11f7eade8a (patch)
treeea2cf00ea329c81611536aaa9a9f1eca47c67e9a /tools/sk_app
parent2aa09dbe8aced37aa6bb285e62df45deb0e81650 (diff)
Remove SampleApp and convert HelloWorld to sk_app
There is still a large amount of views code that could be trimmed down, but which is used to implement samples (in viewer). Seemed simpler to remove some of this code in pieces. Bug: skia: Change-Id: Ia3415060d03c8de604a154e3dc38379b754daab6 Reviewed-on: https://skia-review.googlesource.com/72801 Commit-Queue: Brian Osman <brianosman@google.com> Reviewed-by: Jim Van Verth <jvanverth@google.com> Reviewed-by: Brian Salomon <bsalomon@google.com>
Diffstat (limited to 'tools/sk_app')
-rw-r--r--tools/sk_app/Application.h24
-rw-r--r--tools/sk_app/CommandSet.cpp161
-rw-r--r--tools/sk_app/CommandSet.h117
-rw-r--r--tools/sk_app/DisplayParams.h29
-rw-r--r--tools/sk_app/GLWindowContext.cpp108
-rw-r--r--tools/sk_app/GLWindowContext.h59
-rw-r--r--tools/sk_app/RasterWindowContext.h28
-rw-r--r--tools/sk_app/VulkanWindowContext.cpp621
-rw-r--r--tools/sk_app/VulkanWindowContext.h122
-rw-r--r--tools/sk_app/Window.cpp173
-rw-r--r--tools/sk_app/Window.h239
-rw-r--r--tools/sk_app/WindowContext.h76
-rw-r--r--tools/sk_app/android/GLWindowContext_android.cpp170
-rw-r--r--tools/sk_app/android/RasterWindowContext_android.cpp108
-rw-r--r--tools/sk_app/android/VulkanWindowContext_android.cpp58
-rw-r--r--tools/sk_app/android/WindowContextFactory_android.h32
-rw-r--r--tools/sk_app/android/Window_android.cpp85
-rw-r--r--tools/sk_app/android/Window_android.h43
-rw-r--r--tools/sk_app/android/main_android.cpp65
-rw-r--r--tools/sk_app/android/surface_glue_android.cpp278
-rw-r--r--tools/sk_app/android/surface_glue_android.h79
-rw-r--r--tools/sk_app/ios/GLWindowContext_ios.cpp109
-rw-r--r--tools/sk_app/ios/RasterWindowContext_ios.cpp136
-rw-r--r--tools/sk_app/ios/WindowContextFactory_ios.h38
-rw-r--r--tools/sk_app/ios/Window_ios.cpp277
-rw-r--r--tools/sk_app/ios/Window_ios.h64
-rw-r--r--tools/sk_app/ios/main_ios.cpp58
-rw-r--r--tools/sk_app/mac/GLWindowContext_mac.cpp109
-rw-r--r--tools/sk_app/mac/RasterWindowContext_mac.cpp136
-rw-r--r--tools/sk_app/mac/WindowContextFactory_mac.h38
-rw-r--r--tools/sk_app/mac/Window_mac.cpp277
-rw-r--r--tools/sk_app/mac/Window_mac.h64
-rw-r--r--tools/sk_app/mac/main_mac.cpp58
-rw-r--r--tools/sk_app/unix/GLWindowContext_unix.cpp149
-rw-r--r--tools/sk_app/unix/RasterWindowContext_unix.cpp104
-rw-r--r--tools/sk_app/unix/VulkanWindowContext_unix.cpp86
-rw-r--r--tools/sk_app/unix/WindowContextFactory_unix.h42
-rw-r--r--tools/sk_app/unix/Window_unix.cpp387
-rw-r--r--tools/sk_app/unix/Window_unix.h95
-rw-r--r--tools/sk_app/unix/main_unix.cpp94
-rw-r--r--tools/sk_app/win/ANGLEWindowContext_win.cpp177
-rw-r--r--tools/sk_app/win/GLWindowContext_win.cpp141
-rw-r--r--tools/sk_app/win/RasterWindowContext_win.cpp100
-rw-r--r--tools/sk_app/win/VulkanWindowContext_win.cpp79
-rw-r--r--tools/sk_app/win/WindowContextFactory_win.h33
-rw-r--r--tools/sk_app/win/Window_win.cpp393
-rw-r--r--tools/sk_app/win/Window_win.h44
-rw-r--r--tools/sk_app/win/main_win.cpp80
48 files changed, 6043 insertions, 0 deletions
diff --git a/tools/sk_app/Application.h b/tools/sk_app/Application.h
new file mode 100644
index 0000000000..df9a20d358
--- /dev/null
+++ b/tools/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() = 0;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/CommandSet.cpp b/tools/sk_app/CommandSet.cpp
new file mode 100644
index 0000000000..d0154d6e61
--- /dev/null
+++ b/tools/sk_app/CommandSet.cpp
@@ -0,0 +1,161 @@
+/*
+* 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 "CommandSet.h"
+
+#include "SkCanvas.h"
+#include "SkTSort.h"
+
+namespace sk_app {
+
+CommandSet::CommandSet()
+ : fHelpMode(kNone_HelpMode) {
+ this->addCommand('h', "Overlays", "Show help screen", [this]() {
+ switch (this->fHelpMode) {
+ case kNone_HelpMode:
+ this->fHelpMode = kGrouped_HelpMode;
+ break;
+ case kGrouped_HelpMode:
+ this->fHelpMode = kAlphabetical_HelpMode;
+ break;
+ case kAlphabetical_HelpMode:
+ this->fHelpMode = kNone_HelpMode;
+ break;
+ }
+ fWindow->inval();
+ });
+}
+
+void CommandSet::attach(Window* window) {
+ fWindow = window;
+}
+
+bool CommandSet::onKey(Window::Key key, Window::InputState state, uint32_t modifiers) {
+ if (Window::kDown_InputState == state) {
+ for (Command& cmd : fCommands) {
+ if (Command::kKey_CommandType == cmd.fType && key == cmd.fKey) {
+ cmd.fFunction();
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool CommandSet::onChar(SkUnichar c, uint32_t modifiers) {
+ for (Command& cmd : fCommands) {
+ if (Command::kChar_CommandType == cmd.fType && c == cmd.fChar) {
+ cmd.fFunction();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool CommandSet::onSoftkey(const SkString& softkey) {
+ for (const Command& cmd : fCommands) {
+ if (cmd.getSoftkeyString().equals(softkey)) {
+ cmd.fFunction();
+ return true;
+ }
+ }
+ return false;
+}
+
+void CommandSet::addCommand(SkUnichar c, const char* group, const char* description,
+ std::function<void(void)> function) {
+ fCommands.push_back(Command(c, group, description, function));
+}
+
+void CommandSet::addCommand(Window::Key k, const char* keyName, const char* group,
+ const char* description, std::function<void(void)> function) {
+ fCommands.push_back(Command(k, keyName, group, description, function));
+}
+
+#if defined(SK_BUILD_FOR_WIN32)
+ #define SK_strcasecmp _stricmp
+#else
+ #define SK_strcasecmp strcasecmp
+#endif
+
+bool CommandSet::compareCommandKey(const Command& first, const Command& second) {
+ return SK_strcasecmp(first.fKeyName.c_str(), second.fKeyName.c_str()) < 0;
+}
+
+bool CommandSet::compareCommandGroup(const Command& first, const Command& second) {
+ return SK_strcasecmp(first.fGroup.c_str(), second.fGroup.c_str()) < 0;
+}
+
+void CommandSet::drawHelp(SkCanvas* canvas) {
+ if (kNone_HelpMode == fHelpMode) {
+ return;
+ }
+
+ // Sort commands for current mode:
+ SkTQSort(fCommands.begin(), fCommands.end() - 1,
+ kAlphabetical_HelpMode == fHelpMode ? compareCommandKey : compareCommandGroup);
+
+ SkPaint bgPaint;
+ bgPaint.setColor(0xC0000000);
+ canvas->drawPaint(bgPaint);
+
+ SkPaint paint;
+ paint.setTextSize(16);
+ paint.setAntiAlias(true);
+ paint.setColor(0xFFFFFFFF);
+
+ SkPaint groupPaint;
+ groupPaint.setTextSize(18);
+ groupPaint.setAntiAlias(true);
+ groupPaint.setColor(0xFFFFFFFF);
+
+ SkScalar x = SkIntToScalar(10);
+ SkScalar y = SkIntToScalar(10);
+
+ // Measure all key strings:
+ SkScalar keyWidth = 0;
+ for (Command& cmd : fCommands) {
+ keyWidth = SkMaxScalar(keyWidth,
+ paint.measureText(cmd.fKeyName.c_str(), cmd.fKeyName.size()));
+ }
+ keyWidth += paint.measureText(" ", 1);
+
+ // If we're grouping by category, we'll be adding text height on every new group (including the
+ // first), so no need to do that here. Otherwise, skip down so the first line is where we want.
+ if (kGrouped_HelpMode != fHelpMode) {
+ y += paint.getTextSize();
+ }
+
+ // Print everything:
+ SkString lastGroup;
+ for (Command& cmd : fCommands) {
+ if (kGrouped_HelpMode == fHelpMode && lastGroup != cmd.fGroup) {
+ // Group change. Advance and print header:
+ y += paint.getTextSize();
+ canvas->drawString(cmd.fGroup, x, y, groupPaint);
+ y += groupPaint.getTextSize() + 2;
+ lastGroup = cmd.fGroup;
+ }
+
+ canvas->drawString(cmd.fKeyName, x, y, paint);
+ SkString text = SkStringPrintf(": %s", cmd.fDescription.c_str());
+ canvas->drawString(text, x + keyWidth, y, paint);
+ y += paint.getTextSize() + 2;
+ }
+}
+
+std::vector<SkString> CommandSet::getCommandsAsSoftkeys() const {
+ std::vector<SkString> result;
+ for(const Command& command : fCommands) {
+ result.push_back(command.getSoftkeyString());
+ }
+ return result;
+}
+
+} // namespace sk_app
diff --git a/tools/sk_app/CommandSet.h b/tools/sk_app/CommandSet.h
new file mode 100644
index 0000000000..0784a3875e
--- /dev/null
+++ b/tools/sk_app/CommandSet.h
@@ -0,0 +1,117 @@
+/*
+* 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 CommandSet_DEFINED
+#define CommandSet_DEFINED
+
+#include "SkString.h"
+#include "Window.h"
+
+#include <functional>
+#include <vector>
+
+class SkCanvas;
+
+namespace sk_app {
+
+/**
+ * Helper class used by applications that want to hook keypresses to trigger events.
+ *
+ * An app can simply store an instance of CommandSet and then use it as follows:
+ * 1) Attach to the Window at initialization time.
+ * 2) Register commands to be executed for characters or keys. Each command needs a Group and a
+ * description (both just strings). Commands attached to Keys (rather than characters) also need
+ * a displayable name for the Key. Finally, a function to execute when the key or character is
+ * pressed must be supplied. The easiest option to is pass in a lambda that captures [this]
+ * (your application object), and performs whatever action is desired.
+ * 3) Register key and char handlers with the Window, and - depending on your state - forward those
+ * events to the CommandSet's onKey, onChar, and onSoftKey.
+ * 4) At the end of your onPaint, call drawHelp, and pass in the application's canvas.
+
+ * The CommandSet always binds 'h' to cycle through two different help screens. The first shows
+ * all commands, organized by Group (with headings for each Group). The second shows all commands
+ * alphabetically by key/character.
+ */
+class CommandSet {
+public:
+ CommandSet();
+
+ void attach(Window* window);
+ bool onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers);
+ bool onChar(SkUnichar, uint32_t modifiers);
+ bool onSoftkey(const SkString& softkey);
+
+ void addCommand(SkUnichar c, const char* group, const char* description,
+ std::function<void(void)> function);
+ void addCommand(Window::Key k, const char* keyName, const char* group, const char* description,
+ std::function<void(void)> function);
+
+ void drawHelp(SkCanvas* canvas);
+
+ std::vector<SkString> getCommandsAsSoftkeys() const;
+
+private:
+ struct Command {
+ enum CommandType {
+ kChar_CommandType,
+ kKey_CommandType,
+ };
+
+ Command(SkUnichar c, const char* group, const char* description,
+ std::function<void(void)> function)
+ : fType(kChar_CommandType)
+ , fChar(c)
+ , fKeyName(' ' == c ? SkString("Space") : SkStringPrintf("%c", c))
+ , fGroup(group)
+ , fDescription(description)
+ , fFunction(function) {}
+
+ Command(Window::Key k, const char* keyName, const char* group, const char* description,
+ std::function<void(void)> function)
+ : fType(kKey_CommandType)
+ , fKey(k)
+ , fKeyName(keyName)
+ , fGroup(group)
+ , fDescription(description)
+ , fFunction(function) {}
+
+ CommandType fType;
+
+ // For kChar_CommandType
+ SkUnichar fChar;
+
+ // For kKey_CommandType
+ Window::Key fKey;
+
+ // Common to all command types
+ SkString fKeyName;
+ SkString fGroup;
+ SkString fDescription;
+ std::function<void(void)> fFunction;
+
+ SkString getSoftkeyString() const {
+ return SkStringPrintf("%s (%s)", fKeyName.c_str(), fDescription.c_str());
+ }
+ };
+
+ static bool compareCommandKey(const Command& first, const Command& second);
+ static bool compareCommandGroup(const Command& first, const Command& second);
+
+ enum HelpMode {
+ kNone_HelpMode,
+ kGrouped_HelpMode,
+ kAlphabetical_HelpMode,
+ };
+
+ Window* fWindow;
+ SkTArray<Command> fCommands;
+ HelpMode fHelpMode;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/DisplayParams.h b/tools/sk_app/DisplayParams.h
new file mode 100644
index 0000000000..959735e8ff
--- /dev/null
+++ b/tools/sk_app/DisplayParams.h
@@ -0,0 +1,29 @@
+/*
+ * 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 DisplayParams_DEFINED
+#define DisplayParams_DEFINED
+
+#include "GrContextOptions.h"
+#include "SkImageInfo.h"
+
+namespace sk_app {
+
+struct DisplayParams {
+ DisplayParams()
+ : fColorType(kN32_SkColorType)
+ , fColorSpace(nullptr)
+ , fMSAASampleCount(0) {}
+
+ SkColorType fColorType;
+ sk_sp<SkColorSpace> fColorSpace;
+ int fMSAASampleCount;
+ GrContextOptions fGrContextOptions;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/GLWindowContext.cpp b/tools/sk_app/GLWindowContext.cpp
new file mode 100644
index 0000000000..bdfa12a8ec
--- /dev/null
+++ b/tools/sk_app/GLWindowContext.cpp
@@ -0,0 +1,108 @@
+
+/*
+ * 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 "GrBackendSurface.h"
+#include "GrContext.h"
+#include "GLWindowContext.h"
+
+#include "gl/GrGLDefines.h"
+#include "gl/GrGLUtil.h"
+
+#include "SkCanvas.h"
+#include "SkImage_Base.h"
+#include "SkMathPriv.h"
+#include "SkSurface.h"
+
+namespace sk_app {
+
+GLWindowContext::GLWindowContext(const DisplayParams& params)
+ : WindowContext(params)
+ , fBackendContext(nullptr)
+ , fSurface(nullptr) {
+ fDisplayParams.fMSAASampleCount = fDisplayParams.fMSAASampleCount ?
+ GrNextPow2(fDisplayParams.fMSAASampleCount) :
+ 0;
+}
+
+void GLWindowContext::initializeContext() {
+ SkASSERT(!fContext);
+
+ fBackendContext = this->onInitializeContext();
+ fContext = GrContext::MakeGL(fBackendContext.get(), fDisplayParams.fGrContextOptions);
+ if (!fContext && fDisplayParams.fMSAASampleCount) {
+ fDisplayParams.fMSAASampleCount /= 2;
+ this->initializeContext();
+ return;
+ }
+
+ if (fContext) {
+ // We may not have real sRGB support (ANGLE, in particular), so check for
+ // that, and fall back to L32:
+ fPixelConfig = fContext->caps()->srgbSupport() && fDisplayParams.fColorSpace
+ ? kSRGBA_8888_GrPixelConfig : kRGBA_8888_GrPixelConfig;
+ } else {
+ fPixelConfig = kUnknown_GrPixelConfig;
+ }
+}
+
+void GLWindowContext::destroyContext() {
+ fSurface.reset(nullptr);
+
+ if (fContext) {
+ // in case we have outstanding refs to this guy (lua?)
+ fContext->abandonContext();
+ fContext.reset();
+ }
+
+ fBackendContext.reset(nullptr);
+
+ this->onDestroyContext();
+}
+
+sk_sp<SkSurface> GLWindowContext::getBackbufferSurface() {
+ if (nullptr == fSurface) {
+ if (fContext) {
+ GrGLFramebufferInfo fbInfo;
+ GrGLint buffer;
+ GR_GL_CALL(fBackendContext.get(), GetIntegerv(GR_GL_FRAMEBUFFER_BINDING,
+ &buffer));
+ fbInfo.fFBOID = buffer;
+
+ GrBackendRenderTarget backendRT(fWidth,
+ fHeight,
+ fSampleCount,
+ fStencilBits,
+ fPixelConfig,
+ fbInfo);
+
+ fSurface = SkSurface::MakeFromBackendRenderTarget(fContext.get(), backendRT,
+ kBottomLeft_GrSurfaceOrigin,
+ fDisplayParams.fColorSpace,
+ &fSurfaceProps);
+ }
+ }
+
+ return fSurface;
+}
+
+void GLWindowContext::swapBuffers() {
+ this->onSwapBuffers();
+}
+
+void GLWindowContext::resize(int w, int h) {
+ this->destroyContext();
+ this->initializeContext();
+}
+
+void GLWindowContext::setDisplayParams(const DisplayParams& params) {
+ this->destroyContext();
+ fDisplayParams = params;
+ this->initializeContext();
+}
+
+} //namespace sk_app
diff --git a/tools/sk_app/GLWindowContext.h b/tools/sk_app/GLWindowContext.h
new file mode 100644
index 0000000000..44810c93d2
--- /dev/null
+++ b/tools/sk_app/GLWindowContext.h
@@ -0,0 +1,59 @@
+
+/*
+ * 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 GLWindowContext_DEFINED
+#define GLWindowContext_DEFINED
+
+
+#include "gl/GrGLInterface.h"
+
+#include "SkRefCnt.h"
+#include "SkSurface.h"
+
+#include "WindowContext.h"
+
+class GrContext;
+
+namespace sk_app {
+
+class GLWindowContext : public WindowContext {
+public:
+ sk_sp<SkSurface> getBackbufferSurface() override;
+
+ bool isValid() override { return SkToBool(fBackendContext.get()); }
+
+ void resize(int w, int h) override;
+ void swapBuffers() override;
+
+ void setDisplayParams(const DisplayParams& params) override;
+
+ GrBackendContext getBackendContext() override {
+ return (GrBackendContext) fBackendContext.get();
+ }
+
+protected:
+ GLWindowContext(const DisplayParams&);
+ // This should be called by subclass constructor. It is also called when window/display
+ // parameters change. This will in turn call onInitializeContext().
+ void initializeContext();
+ virtual sk_sp<const GrGLInterface> onInitializeContext() = 0;
+
+ // This should be called by subclass destructor. It is also called when window/display
+ // parameters change prior to initializing a new GL context. This will in turn call
+ // onDestroyContext().
+ void destroyContext();
+ virtual void onDestroyContext() = 0;
+
+ virtual void onSwapBuffers() = 0;
+
+ sk_sp<const GrGLInterface> fBackendContext;
+ sk_sp<SkSurface> fSurface;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/RasterWindowContext.h b/tools/sk_app/RasterWindowContext.h
new file mode 100644
index 0000000000..75bde03ad7
--- /dev/null
+++ b/tools/sk_app/RasterWindowContext.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 RasterWindowContext_DEFINED
+#define RasterWindowContext_DEFINED
+
+#include "WindowContext.h"
+
+namespace sk_app {
+
+class RasterWindowContext : public WindowContext {
+public:
+ RasterWindowContext(const DisplayParams& params) : WindowContext(params) {}
+
+ // Explicitly convert nullptr to GrBackendContext is needed for compiling
+ GrBackendContext getBackendContext() override { return (GrBackendContext) nullptr; }
+
+protected:
+ bool isGpuContext() override { return false; }
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/VulkanWindowContext.cpp b/tools/sk_app/VulkanWindowContext.cpp
new file mode 100644
index 0000000000..5e0f12412e
--- /dev/null
+++ b/tools/sk_app/VulkanWindowContext.cpp
@@ -0,0 +1,621 @@
+
+/*
+ * 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 "GrBackendSurface.h"
+#include "GrContext.h"
+#include "SkAutoMalloc.h"
+#include "SkSurface.h"
+#include "VulkanWindowContext.h"
+
+#include "vk/GrVkInterface.h"
+#include "vk/GrVkMemory.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) fGetInstanceProcAddr(instance, "vk" #F)
+#define GET_DEV_PROC(F) f ## F = (PFN_vk ## F) fGetDeviceProcAddr(device, "vk" #F)
+
+namespace sk_app {
+
+VulkanWindowContext::VulkanWindowContext(const DisplayParams& params,
+ CreateVkSurfaceFn createVkSurface,
+ CanPresentFn canPresent,
+ PFN_vkGetInstanceProcAddr instProc,
+ PFN_vkGetDeviceProcAddr devProc)
+ : WindowContext(params)
+ , fCreateVkSurfaceFn(createVkSurface)
+ , fCanPresentFn(canPresent)
+ , fSurface(VK_NULL_HANDLE)
+ , fSwapchain(VK_NULL_HANDLE)
+ , fImages(nullptr)
+ , fImageLayouts(nullptr)
+ , fSurfaces(nullptr)
+ , fCommandPool(VK_NULL_HANDLE)
+ , fBackbuffers(nullptr) {
+ fGetInstanceProcAddr = instProc;
+ fGetDeviceProcAddr = devProc;
+ this->initializeContext();
+}
+
+void VulkanWindowContext::initializeContext() {
+ // any config code here (particularly for msaa)?
+ fBackendContext.reset(GrVkBackendContext::Create(fGetInstanceProcAddr, fGetDeviceProcAddr,
+ &fPresentQueueIndex, fCanPresentFn));
+
+ 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);
+ GET_DEV_PROC(GetDeviceQueue);
+
+ fContext = GrContext::MakeVulkan(fBackendContext.get(), fDisplayParams.fGrContextOptions);
+
+ fSurface = fCreateVkSurfaceFn(instance);
+ 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, fDisplayParams)) {
+ this->destroyContext();
+ return;
+ }
+
+ // create presentQueue
+ fGetDeviceQueue(fBackendContext->fDevice, fPresentQueueIndex, 0, &fPresentQueue);
+}
+
+bool VulkanWindowContext::createSwapchain(int width, int height,
+ const DisplayParams& params) {
+ // 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, just make sure it matches our sRGB request:
+ VkFormat surfaceFormat = VK_FORMAT_UNDEFINED;
+ VkColorSpaceKHR colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
+ auto srgbColorSpace = SkColorSpace::MakeSRGB();
+ bool wantSRGB = srgbColorSpace == params.fColorSpace;
+ for (uint32_t i = 0; i < surfaceFormatCount; ++i) {
+ GrPixelConfig config = GrVkFormatToPixelConfig(surfaceFormats[i].format);
+ if (kUnknown_GrPixelConfig != config &&
+ GrPixelConfigIsSRGB(config) == wantSRGB) {
+ surfaceFormat = surfaceFormats[i].format;
+ colorSpace = surfaceFormats[i].colorSpace;
+ break;
+ }
+ }
+ fDisplayParams = params;
+ fSampleCount = params.fMSAASampleCount;
+ fStencilBits = 8;
+
+ if (VK_FORMAT_UNDEFINED == surfaceFormat) {
+ return false;
+ }
+
+ // 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 = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+ 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) {
+ fPixelConfig = GrVkFormatToPixelConfig(format);
+ SkASSERT(kUnknown_GrPixelConfig != 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;
+
+ GrVkImageInfo info;
+ info.fImage = fImages[i];
+ info.fAlloc = { VK_NULL_HANDLE, 0, 0, 0 };
+ info.fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ info.fImageTiling = VK_IMAGE_TILING_OPTIMAL;
+ info.fFormat = format;
+ info.fLevelCount = 1;
+
+ GrBackendTexture backendTex(fWidth, fHeight, info);
+
+ fSurfaces[i] = SkSurface::MakeFromBackendTextureAsRenderTarget(fContext.get(), backendTex,
+ kTopLeft_GrSurfaceOrigin,
+ fSampleCount,
+ fDisplayParams.fColorSpace,
+ &fSurfaceProps);
+ }
+
+ // 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;
+
+ // Does this actually free the surfaces?
+ 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, QueueWaitIdle(fPresentQueue));
+ 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;
+ }
+
+ fContext.reset();
+
+ 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;
+}
+
+sk_sp<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(-1, -1, fDisplayParams)) {
+ return nullptr;
+ }
+ backbuffer = this->getAvailableBackbuffer();
+ GR_VK_CALL_ERRCHECK(fBackendContext->fInterface,
+ ResetFences(fBackendContext->fDevice, 2, backbuffer->fUsageFences));
+
+ // 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];
+ SkASSERT(VK_IMAGE_LAYOUT_UNDEFINED == layout || VK_IMAGE_LAYOUT_PRESENT_SRC_KHR == layout);
+ 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]));
+
+ GrVkImageInfo* imageInfo;
+ SkSurface* surface = fSurfaces[backbuffer->fImageIndex].get();
+ surface->getRenderTargetHandle((GrBackendObject*)&imageInfo,
+ SkSurface::kFlushRead_BackendHandleAccess);
+ imageInfo->updateImageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
+
+ return sk_ref_sp(surface);
+}
+
+void VulkanWindowContext::swapBuffers() {
+
+ BackbufferInfo* backbuffer = fBackbuffers + fCurrentBackbufferIndex;
+ GrVkImageInfo* imageInfo;
+ SkSurface* surface = fSurfaces[backbuffer->fImageIndex].get();
+ surface->getRenderTargetHandle((GrBackendObject*)&imageInfo,
+ SkSurface::kFlushRead_BackendHandleAccess);
+ // Check to make sure we never change the actually wrapped image
+ SkASSERT(imageInfo->fImage == fImages[backbuffer->fImageIndex]);
+
+ VkImageLayout layout = imageInfo->fImageLayout;
+ VkPipelineStageFlags srcStageMask = GrVkMemory::LayoutToPipelineStageFlags(layout);
+ VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
+ VkAccessFlags srcAccessMask = GrVkMemory::LayoutToSrcAccessMask(layout);
+ 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/sk_app/VulkanWindowContext.h b/tools/sk_app/VulkanWindowContext.h
new file mode 100644
index 0000000000..d02b11428e
--- /dev/null
+++ b/tools/sk_app/VulkanWindowContext.h
@@ -0,0 +1,122 @@
+
+/*
+ * 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
+
+#include "SkTypes.h" // required to pull in any SkUserConfig defines
+
+#ifdef SK_VULKAN
+
+#include "vk/GrVkBackendContext.h"
+#include "WindowContext.h"
+
+class GrRenderTarget;
+
+namespace sk_app {
+
+class VulkanWindowContext : public WindowContext {
+public:
+ ~VulkanWindowContext() override;
+
+ sk_sp<SkSurface> getBackbufferSurface() override;
+ void swapBuffers() override;
+
+ bool isValid() override { return SkToBool(fBackendContext.get()); }
+
+ void resize(int w, int h) override {
+ this->createSwapchain(w, h, fDisplayParams);
+ }
+
+ void setDisplayParams(const DisplayParams& params) override {
+ this->destroyContext();
+ fDisplayParams = params;
+ this->initializeContext();
+ }
+
+ GrBackendContext getBackendContext() override {
+ return (GrBackendContext) fBackendContext.get();
+ }
+
+ /** Platform specific function that creates a VkSurfaceKHR for a window */
+ using CreateVkSurfaceFn = std::function<VkSurfaceKHR(VkInstance)>;
+ /** Platform specific function that determines whether presentation will succeed. */
+ using CanPresentFn = GrVkBackendContext::CanPresentFn;
+
+ VulkanWindowContext(const DisplayParams&, CreateVkSurfaceFn, CanPresentFn,
+ PFN_vkGetInstanceProcAddr, PFN_vkGetDeviceProcAddr);
+
+private:
+ void initializeContext();
+ 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(int width, int height, const DisplayParams& params);
+ void createBuffers(VkFormat format);
+ void destroyBuffers();
+
+ sk_sp<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;
+ };
+
+ // Create functions
+ CreateVkSurfaceFn fCreateVkSurfaceFn;
+ CanPresentFn fCanPresentFn;
+
+ // Vulkan GetProcAddr functions
+ VkPtr<PFN_vkGetInstanceProcAddr> fGetInstanceProcAddr;
+ VkPtr<PFN_vkGetDeviceProcAddr> fGetDeviceProcAddr;
+
+ // 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_vkGetDeviceQueue> fGetDeviceQueue;
+
+ VkSurfaceKHR fSurface;
+ VkSwapchainKHR fSwapchain;
+ uint32_t fPresentQueueIndex;
+ VkQueue fPresentQueue;
+
+ uint32_t fImageCount;
+ VkImage* fImages; // images in the swapchain
+ VkImageLayout* fImageLayouts; // layouts of these images when not color attachment
+ sk_sp<SkSurface>* fSurfaces; // surfaces client renders to (may not be based on rts)
+ VkCommandPool fCommandPool;
+ BackbufferInfo* fBackbuffers;
+ uint32_t fCurrentBackbufferIndex;
+};
+
+} // namespace sk_app
+
+#endif // SK_VULKAN
+
+#endif
diff --git a/tools/sk_app/Window.cpp b/tools/sk_app/Window.cpp
new file mode 100644
index 0000000000..d7904fd8a7
--- /dev/null
+++ b/tools/sk_app/Window.cpp
@@ -0,0 +1,173 @@
+/*
+* 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 "WindowContext.h"
+
+namespace sk_app {
+
+static void default_backend_created_func(void* userData) {}
+
+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 bool default_mouse_wheel_func(float delta, uint32_t modifiers, void* userData) {
+ return false;
+}
+
+static bool default_touch_func(intptr_t owner, Window::InputState state, float x, float y,
+ void* userData) {
+ return false;
+}
+
+static void default_ui_state_changed_func(
+ const SkString& stateName, const SkString& stateValue, void* userData) {}
+
+static void default_paint_func(SkCanvas*, void* userData) {}
+
+Window::Window() : fBackendCreatedFunc(default_backend_created_func)
+ , fCharFunc(default_char_func)
+ , fKeyFunc(default_key_func)
+ , fMouseFunc(default_mouse_func)
+ , fMouseWheelFunc(default_mouse_wheel_func)
+ , fTouchFunc(default_touch_func)
+ , fUIStateChangedFunc(default_ui_state_changed_func)
+ , fPaintFunc(default_paint_func) {
+}
+
+void Window::detach() {
+ delete fWindowContext;
+ fWindowContext = nullptr;
+}
+
+void Window::onBackendCreated() {
+ fBackendCreatedFunc(fBackendCreatedUserData);
+}
+
+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);
+}
+
+bool Window::onMouseWheel(float delta, uint32_t modifiers) {
+ return fMouseWheelFunc(delta, modifiers, fMouseWheelUserData);
+}
+
+bool Window::onTouch(intptr_t owner, InputState state, float x, float y) {
+ return fTouchFunc(owner, state, x, y, fTouchUserData);
+}
+
+void Window::onUIStateChanged(const SkString& stateName, const SkString& stateValue) {
+ return fUIStateChangedFunc(stateName, stateValue, fUIStateChangedUserData);
+}
+
+void Window::onPaint() {
+ if (!fWindowContext) {
+ return;
+ }
+ markInvalProcessed();
+ sk_sp<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 {
+ printf("no backbuffer!?\n");
+ // try recreating testcontext
+ }
+}
+
+void Window::onResize(int w, int h) {
+ if (!fWindowContext) {
+ return;
+ }
+ fWindowContext->resize(w, h);
+}
+
+int Window::width() {
+ if (!fWindowContext) {
+ return 0;
+ }
+ return fWindowContext->width();
+}
+
+int Window::height() {
+ if (!fWindowContext) {
+ return 0;
+ }
+ return fWindowContext->height();
+}
+
+void Window::setRequestedDisplayParams(const DisplayParams& params, bool /* allowReattach */) {
+ fRequestedDisplayParams = params;
+ if (fWindowContext) {
+ fWindowContext->setDisplayParams(fRequestedDisplayParams);
+ }
+}
+
+int Window::sampleCount() const {
+ if (!fWindowContext) {
+ return -1;
+ }
+ return fWindowContext->sampleCount();
+}
+
+int Window::stencilBits() const {
+ if (!fWindowContext) {
+ return -1;
+ }
+ return fWindowContext->stencilBits();
+}
+
+const GrContext* Window::getGrContext() const {
+ if (!fWindowContext) {
+ return nullptr;
+ }
+ return fWindowContext->getGrContext();
+}
+
+void Window::inval() {
+ if (!fWindowContext) {
+ return;
+ }
+ if (!fIsContentInvalidated) {
+ fIsContentInvalidated = true;
+ onInval();
+ }
+}
+
+void Window::markInvalProcessed() {
+ fIsContentInvalidated = false;
+}
+
+} // namespace sk_app
diff --git a/tools/sk_app/Window.h b/tools/sk_app/Window.h
new file mode 100644
index 0000000000..4d40780964
--- /dev/null
+++ b/tools/sk_app/Window.h
@@ -0,0 +1,239 @@
+/*
+* 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 "DisplayParams.h"
+#include "SkRect.h"
+#include "SkTouchGesture.h"
+#include "SkTypes.h"
+
+class GrContext;
+class SkCanvas;
+class SkSurface;
+
+namespace sk_app {
+
+class WindowContext;
+
+class Window {
+public:
+ static Window* CreateNativeWindow(void* platformData);
+
+ virtual ~Window() { this->detach(); }
+
+ virtual void setTitle(const char*) = 0;
+ virtual void show() = 0;
+
+ // JSON-formatted UI state for Android. Do nothing by default
+ virtual void setUIState(const char*) {}
+
+ // Shedules an invalidation event for window if one is not currently pending.
+ // Make sure that either onPaint or markInvalReceived is called when the client window consumes
+ // the the inval event. They unset fIsContentInvalided which allow future onInval.
+ void inval();
+
+ virtual bool scaleContentToFit() const { return false; }
+
+ enum BackendType {
+ kNativeGL_BackendType,
+#if SK_ANGLE && defined(SK_BUILD_FOR_WIN)
+ kANGLE_BackendType,
+#endif
+#ifdef SK_VULKAN
+ kVulkan_BackendType,
+#endif
+ kRaster_BackendType,
+
+ kLast_BackendType = kRaster_BackendType
+ };
+ enum {
+ kBackendTypeCount = kLast_BackendType + 1
+ };
+
+ virtual bool attach(BackendType) = 0;
+ void detach();
+
+ // input handling
+ enum class Key {
+ kNONE, //corresponds to android's UNKNOWN
+
+ kLeftSoftKey,
+ kRightSoftKey,
+
+ kHome, //!< the home key - added to match android
+ kBack, //!< (CLR)
+ kSend, //!< the green (talk) key
+ kEnd, //!< the red key
+
+ k0,
+ k1,
+ k2,
+ k3,
+ k4,
+ k5,
+ k6,
+ k7,
+ k8,
+ k9,
+ kStar, //!< the * key
+ kHash, //!< the # key
+
+ kUp,
+ kDown,
+ kLeft,
+ kRight,
+
+ // Keys needed by ImGui
+ kTab,
+ kPageUp,
+ kPageDown,
+ kDelete,
+ kEscape,
+ kShift,
+ kCtrl,
+ kOption, // AKA Alt
+ kA,
+ kC,
+ kV,
+ kX,
+ kY,
+ kZ,
+
+ kOK, //!< the center key
+
+ kVolUp, //!< volume up - match android
+ kVolDown, //!< volume down - same
+ kPower, //!< power button - same
+ kCamera, //!< camera - same
+
+ kLast = kCamera
+ };
+ static const int kKeyCount = static_cast<int>(Key::kLast) + 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 void(*OnBackendCreatedFunc)(void* userData);
+ 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 bool(*OnMouseWheelFunc)(float delta, uint32_t modifiers, void* userData);
+ typedef bool(*OnTouchFunc)(intptr_t owner, InputState state, float x, float y, void* userData);
+ typedef void(*OnUIStateChangedFunc)(
+ const SkString& stateName, const SkString& stateValue, void* userData);
+ typedef void(*OnPaintFunc)(SkCanvas*, void* userData);
+
+ void registerBackendCreatedFunc(OnBackendCreatedFunc func, void* userData) {
+ fBackendCreatedFunc = func;
+ fBackendCreatedUserData = 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 registerMouseWheelFunc(OnMouseWheelFunc func, void* userData) {
+ fMouseWheelFunc = func;
+ fMouseWheelUserData = userData;
+ }
+
+ void registerPaintFunc(OnPaintFunc func, void* userData) {
+ fPaintFunc = func;
+ fPaintUserData = userData;
+ }
+
+ void registerTouchFunc(OnTouchFunc func, void* userData) {
+ fTouchFunc = func;
+ fTouchUserData = userData;
+ }
+
+ void registerUIStateChangedFunc(OnUIStateChangedFunc func, void* userData) {
+ fUIStateChangedFunc = func;
+ fUIStateChangedUserData = userData;
+ }
+
+ void onBackendCreated();
+ 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);
+ bool onMouseWheel(float delta, uint32_t modifiers);
+ bool onTouch(intptr_t owner, InputState state, float x, float y); // multi-owner = multi-touch
+ void onUIStateChanged(const SkString& stateName, const SkString& stateValue);
+ void onPaint();
+ void onResize(int width, int height);
+
+ int width();
+ int height();
+
+ virtual const DisplayParams& getRequestedDisplayParams() { return fRequestedDisplayParams; }
+ virtual void setRequestedDisplayParams(const DisplayParams&, bool allowReattach = true);
+
+ // Actual parameters in effect, obtained from the native window.
+ int sampleCount() const;
+ int stencilBits() const;
+
+ // Returns null if there is not a GPU backend or if the backend is not yet created.
+ const GrContext* getGrContext() const;
+
+protected:
+ Window();
+
+ OnBackendCreatedFunc fBackendCreatedFunc;
+ void* fBackendCreatedUserData;
+ OnCharFunc fCharFunc;
+ void* fCharUserData;
+ OnKeyFunc fKeyFunc;
+ void* fKeyUserData;
+ OnMouseFunc fMouseFunc;
+ void* fMouseUserData;
+ OnMouseWheelFunc fMouseWheelFunc;
+ void* fMouseWheelUserData;
+ OnTouchFunc fTouchFunc;
+ void* fTouchUserData;
+ OnUIStateChangedFunc fUIStateChangedFunc;
+ void* fUIStateChangedUserData;
+ OnPaintFunc fPaintFunc;
+ void* fPaintUserData;
+ DisplayParams fRequestedDisplayParams;
+
+ WindowContext* fWindowContext = nullptr;
+
+ virtual void onInval() = 0;
+
+ // Uncheck fIsContentInvalided to allow future inval/onInval.
+ void markInvalProcessed();
+
+ bool fIsContentInvalidated = false; // use this to avoid duplicate invalidate events
+};
+
+} // namespace sk_app
+#endif
diff --git a/tools/sk_app/WindowContext.h b/tools/sk_app/WindowContext.h
new file mode 100644
index 0000000000..cd4c357e20
--- /dev/null
+++ b/tools/sk_app/WindowContext.h
@@ -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.
+ */
+#ifndef WindowContext_DEFINED
+#define WindowContext_DEFINED
+
+#include "DisplayParams.h"
+#include "GrContext.h"
+#include "GrTypes.h"
+#include "SkRefCnt.h"
+#include "SkSurfaceProps.h"
+
+class SkSurface;
+class GrRenderTarget;
+
+namespace sk_app {
+
+class WindowContext {
+public:
+ WindowContext(const DisplayParams& params)
+ : fContext(nullptr)
+ , fDisplayParams(params)
+ , fSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType)
+ , fSampleCount(0)
+ , fStencilBits(0) {}
+
+ virtual ~WindowContext() {}
+
+ virtual sk_sp<SkSurface> getBackbufferSurface() = 0;
+
+ virtual void swapBuffers() = 0;
+
+ virtual bool isValid() = 0;
+
+ virtual void resize(int w, int h) = 0;
+
+ const DisplayParams& getDisplayParams() { return fDisplayParams; }
+ virtual void setDisplayParams(const DisplayParams& params) = 0;
+
+ SkSurfaceProps getSurfaceProps() const { return fSurfaceProps; }
+ void setSurfaceProps(const SkSurfaceProps& props) {
+ fSurfaceProps = props;
+ }
+
+ virtual GrBackendContext getBackendContext() = 0;
+ GrContext* getGrContext() const { return fContext.get(); }
+
+ int width() const { return fWidth; }
+ int height() const { return fHeight; }
+ int sampleCount() const { return fSampleCount; }
+ int stencilBits() const { return fStencilBits; }
+
+protected:
+ virtual bool isGpuContext() { return true; }
+
+ sk_sp<GrContext> fContext;
+
+ int fWidth;
+ int fHeight;
+ DisplayParams fDisplayParams;
+ GrPixelConfig fPixelConfig;
+ SkSurfaceProps fSurfaceProps;
+
+ // parameters obtained from the native window
+ // Note that the platform .cpp file is responsible for
+ // initializing fSampleCount and fStencilBits!
+ int fSampleCount;
+ int fStencilBits;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/android/GLWindowContext_android.cpp b/tools/sk_app/android/GLWindowContext_android.cpp
new file mode 100644
index 0000000000..944865909b
--- /dev/null
+++ b/tools/sk_app/android/GLWindowContext_android.cpp
@@ -0,0 +1,170 @@
+
+/*
+ * 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 <EGL/egl.h>
+#include <GLES/gl.h>
+#include "../GLWindowContext.h"
+#include "WindowContextFactory_android.h"
+#include "gl/GrGLInterface.h"
+
+using sk_app::GLWindowContext;
+using sk_app::DisplayParams;
+
+namespace {
+class GLWindowContext_android : public GLWindowContext {
+public:
+
+ GLWindowContext_android(ANativeWindow*, const DisplayParams&);
+
+ ~GLWindowContext_android() override;
+
+ void onSwapBuffers() override;
+
+ sk_sp<const GrGLInterface> onInitializeContext() override;
+ void onDestroyContext() override;
+
+private:
+
+ EGLDisplay fDisplay;
+ EGLContext fEGLContext;
+ EGLSurface fSurfaceAndroid;
+
+ // For setDisplayParams and resize which call onInitializeContext with null platformData
+ ANativeWindow* fNativeWindow = nullptr;
+
+ typedef GLWindowContext INHERITED;
+};
+
+GLWindowContext_android::GLWindowContext_android(ANativeWindow* window,
+ const DisplayParams& params)
+ : INHERITED(params)
+ , fDisplay(EGL_NO_DISPLAY)
+ , fEGLContext(EGL_NO_CONTEXT)
+ , fSurfaceAndroid(EGL_NO_SURFACE)
+ , fNativeWindow(window) {
+
+ // any config code here (particularly for msaa)?
+
+ this->initializeContext();
+}
+
+GLWindowContext_android::~GLWindowContext_android() {
+ this->destroyContext();
+}
+
+sk_sp<const GrGLInterface> GLWindowContext_android::onInitializeContext() {
+ fWidth = ANativeWindow_getWidth(fNativeWindow);
+ fHeight = ANativeWindow_getHeight(fNativeWindow);
+
+ fDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+
+ EGLint majorVersion;
+ EGLint minorVersion;
+ eglInitialize(fDisplay, &majorVersion, &minorVersion);
+
+ SkAssertResult(eglBindAPI(EGL_OPENGL_ES_API));
+
+ EGLint numConfigs = 0;
+ const EGLint configAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_STENCIL_SIZE, 8,
+ EGL_SAMPLE_BUFFERS, fDisplayParams.fMSAASampleCount ? 1 : 0,
+ EGL_SAMPLES, fDisplayParams.fMSAASampleCount,
+ EGL_NONE
+ };
+
+ EGLConfig surfaceConfig;
+ SkAssertResult(eglChooseConfig(fDisplay, configAttribs, &surfaceConfig, 1, &numConfigs));
+ SkASSERT(numConfigs > 0);
+
+ static const EGLint kEGLContextAttribsForOpenGLES[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+ fEGLContext = eglCreateContext(
+ fDisplay, surfaceConfig, nullptr, kEGLContextAttribsForOpenGLES);
+ SkASSERT(EGL_NO_CONTEXT != fEGLContext);
+
+// SkDebugf("EGL: %d.%d", majorVersion, minorVersion);
+// SkDebugf("Vendor: %s", eglQueryString(fDisplay, EGL_VENDOR));
+// SkDebugf("Extensions: %s", eglQueryString(fDisplay, EGL_EXTENSIONS));
+
+ // These values are the same as the corresponding VG colorspace attributes,
+ // which were accepted starting in EGL 1.2. For some reason in 1.4, sRGB
+ // became hidden behind an extension, but it looks like devices aren't
+ // advertising that extension (including Nexus 5X). So just check version?
+ const EGLint srgbWindowAttribs[] = {
+ /*EGL_GL_COLORSPACE_KHR*/ 0x309D, /*EGL_GL_COLORSPACE_SRGB_KHR*/ 0x3089,
+ EGL_NONE,
+ };
+ const EGLint* windowAttribs = nullptr;
+ auto srgbColorSpace = SkColorSpace::MakeSRGB();
+ if (srgbColorSpace == fDisplayParams.fColorSpace && majorVersion == 1 && minorVersion >= 2) {
+ windowAttribs = srgbWindowAttribs;
+ }
+
+ fSurfaceAndroid = eglCreateWindowSurface(fDisplay, surfaceConfig, fNativeWindow, windowAttribs);
+ if (EGL_NO_SURFACE == fSurfaceAndroid && windowAttribs) {
+ // Try again without sRGB
+ fSurfaceAndroid = eglCreateWindowSurface(fDisplay, surfaceConfig, fNativeWindow, nullptr);
+ }
+ SkASSERT(EGL_NO_SURFACE != fSurfaceAndroid);
+
+ SkAssertResult(eglMakeCurrent(fDisplay, fSurfaceAndroid, fSurfaceAndroid, fEGLContext));
+ // GLWindowContext::initializeContext will call GrGLCreateNativeInterface so we
+ // won't call it here.
+
+ glClearStencil(0);
+ glClearColor(0, 0, 0, 0);
+ glStencilMask(0xffffffff);
+ glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ eglGetConfigAttrib(fDisplay, surfaceConfig, EGL_STENCIL_SIZE, &fStencilBits);
+ eglGetConfigAttrib(fDisplay, surfaceConfig, EGL_SAMPLES, &fSampleCount);
+
+ return sk_sp<const GrGLInterface>(GrGLCreateNativeInterface());
+}
+
+void GLWindowContext_android::onDestroyContext() {
+ if (!fDisplay || !fEGLContext || !fSurfaceAndroid) {
+ return;
+ }
+ eglMakeCurrent(fDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ SkAssertResult(eglDestroySurface(fDisplay, fSurfaceAndroid));
+ SkAssertResult(eglDestroyContext(fDisplay, fEGLContext));
+ fEGLContext = EGL_NO_CONTEXT;
+ fSurfaceAndroid = EGL_NO_SURFACE;
+}
+
+void GLWindowContext_android::onSwapBuffers() {
+ if (fDisplay && fEGLContext && fSurfaceAndroid) {
+ eglSwapBuffers(fDisplay, fSurfaceAndroid);
+ }
+}
+
+} // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewGLForAndroid(ANativeWindow* window, const DisplayParams& params) {
+ WindowContext* ctx = new GLWindowContext_android(window, params);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+} // namespace sk_app
diff --git a/tools/sk_app/android/RasterWindowContext_android.cpp b/tools/sk_app/android/RasterWindowContext_android.cpp
new file mode 100644
index 0000000000..101e51ef42
--- /dev/null
+++ b/tools/sk_app/android/RasterWindowContext_android.cpp
@@ -0,0 +1,108 @@
+
+/*
+ * 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 "WindowContextFactory_android.h"
+#include "../RasterWindowContext.h"
+#include "SkSurface.h"
+#include "SkTypes.h"
+
+using sk_app::RasterWindowContext;
+using sk_app::DisplayParams;
+
+namespace {
+class RasterWindowContext_android : public RasterWindowContext {
+public:
+ RasterWindowContext_android(ANativeWindow*, const DisplayParams& params);
+
+ sk_sp<SkSurface> getBackbufferSurface() override;
+ void swapBuffers() override;
+
+ bool isValid() override { return SkToBool(fNativeWindow); }
+ void resize(int w, int h) override;
+ void setDisplayParams(const DisplayParams& params) override;
+
+private:
+ void setBuffersGeometry();
+ sk_sp<SkSurface> fBackbufferSurface = nullptr;
+ ANativeWindow* fNativeWindow = nullptr;
+ ANativeWindow_Buffer fBuffer;
+ ARect fBounds;
+
+ typedef RasterWindowContext INHERITED;
+};
+
+RasterWindowContext_android::RasterWindowContext_android(ANativeWindow* window,
+ const DisplayParams& params)
+ : INHERITED(params) {
+ fNativeWindow = window;
+ fWidth = ANativeWindow_getWidth(fNativeWindow);
+ fHeight = ANativeWindow_getHeight(fNativeWindow);
+ this->setBuffersGeometry();
+}
+
+void RasterWindowContext_android::setBuffersGeometry() {
+ int32_t format = 0;
+ switch(fDisplayParams.fColorType) {
+ case kRGBA_8888_SkColorType:
+ format = WINDOW_FORMAT_RGBA_8888;
+ break;
+ case kRGB_565_SkColorType:
+ format = WINDOW_FORMAT_RGB_565;
+ break;
+ default:
+ SK_ABORT("Unsupported Android color type");
+ }
+ ANativeWindow_setBuffersGeometry(fNativeWindow, fWidth, fHeight, format);
+}
+
+void RasterWindowContext_android::setDisplayParams(const DisplayParams& params) {
+ fDisplayParams = params;
+ this->setBuffersGeometry();
+}
+
+void RasterWindowContext_android::resize(int w, int h) {
+ fWidth = w;
+ fHeight = h;
+ this->setBuffersGeometry();
+}
+
+sk_sp<SkSurface> RasterWindowContext_android::getBackbufferSurface() {
+ if (nullptr == fBackbufferSurface) {
+ ANativeWindow_lock(fNativeWindow, &fBuffer, &fBounds);
+ const int bytePerPixel = fBuffer.format == WINDOW_FORMAT_RGB_565 ? 2 : 4;
+ SkImageInfo info = SkImageInfo::Make(fWidth, fHeight,
+ fDisplayParams.fColorType,
+ kPremul_SkAlphaType,
+ fDisplayParams.fColorSpace);
+ fBackbufferSurface = SkSurface::MakeRasterDirect(
+ info, fBuffer.bits, fBuffer.stride * bytePerPixel, nullptr);
+ }
+ return fBackbufferSurface;
+}
+
+
+void RasterWindowContext_android::swapBuffers() {
+ ANativeWindow_unlockAndPost(fNativeWindow);
+ fBackbufferSurface.reset(nullptr);
+}
+} // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewRasterForAndroid(ANativeWindow* window, const DisplayParams& params) {
+ WindowContext* ctx = new RasterWindowContext_android(window, params);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+}
+
+}
+} // namespace sk_app
diff --git a/tools/sk_app/android/VulkanWindowContext_android.cpp b/tools/sk_app/android/VulkanWindowContext_android.cpp
new file mode 100644
index 0000000000..a7d8aa7ea1
--- /dev/null
+++ b/tools/sk_app/android/VulkanWindowContext_android.cpp
@@ -0,0 +1,58 @@
+
+/*
+ * 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 "WindowContextFactory_android.h"
+#include "../VulkanWindowContext.h"
+
+#include "vk/VkTestUtils.h"
+
+namespace sk_app {
+
+namespace window_context_factory {
+
+WindowContext* NewVulkanForAndroid(ANativeWindow* window, const DisplayParams& params) {
+ PFN_vkGetInstanceProcAddr instProc;
+ PFN_vkGetDeviceProcAddr devProc;
+ if (!sk_gpu_test::LoadVkLibraryAndGetProcAddrFuncs(&instProc, &devProc)) {
+ return nullptr;
+ }
+
+ auto createVkSurface = [window, instProc] (VkInstance instance) -> VkSurfaceKHR {
+ PFN_vkCreateAndroidSurfaceKHR createAndroidSurfaceKHR =
+ (PFN_vkCreateAndroidSurfaceKHR) instProc(instance, "vkCreateAndroidSurfaceKHR");
+
+ if (!window) {
+ return VK_NULL_HANDLE;
+ }
+ 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 = window;
+
+ VkResult res = createAndroidSurfaceKHR(instance, &surfaceCreateInfo,
+ nullptr, &surface);
+ return (VK_SUCCESS == res) ? surface : VK_NULL_HANDLE;
+ };
+
+ auto canPresent = [](VkInstance, VkPhysicalDevice, uint32_t) { return true; };
+
+ WindowContext* ctx = new VulkanWindowContext(params, createVkSurface, canPresent,
+ instProc, devProc);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+} // namespace sk_app
diff --git a/tools/sk_app/android/WindowContextFactory_android.h b/tools/sk_app/android/WindowContextFactory_android.h
new file mode 100644
index 0000000000..00198da8d3
--- /dev/null
+++ b/tools/sk_app/android/WindowContextFactory_android.h
@@ -0,0 +1,32 @@
+
+/*
+ * 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 WindowContextFactory_android_DEFINED
+#define WindowContextFactory_android_DEFINED
+
+#include <android/native_window_jni.h>
+
+
+namespace sk_app {
+
+class WindowContext;
+struct DisplayParams;
+
+namespace window_context_factory {
+
+WindowContext* NewVulkanForAndroid(ANativeWindow*, const DisplayParams&);
+
+WindowContext* NewGLForAndroid(ANativeWindow*, const DisplayParams&);
+
+WindowContext* NewRasterForAndroid(ANativeWindow*, const DisplayParams&);
+
+} // namespace window_context_factory
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/android/Window_android.cpp b/tools/sk_app/android/Window_android.cpp
new file mode 100644
index 0000000000..96acfc6564
--- /dev/null
+++ b/tools/sk_app/android/Window_android.cpp
@@ -0,0 +1,85 @@
+/*
+* 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 "WindowContextFactory_android.h"
+#include "../WindowContext.h"
+
+namespace sk_app {
+
+Window* Window::CreateNativeWindow(void* platformData) {
+ Window_android* window = new Window_android();
+ if (!window->init((SkiaAndroidApp*)platformData)) {
+ delete window;
+ return nullptr;
+ }
+ return window;
+}
+
+bool Window_android::init(SkiaAndroidApp* skiaAndroidApp) {
+ SkASSERT(skiaAndroidApp);
+ fSkiaAndroidApp = skiaAndroidApp;
+ fSkiaAndroidApp->fWindow = this;
+ return true;
+}
+
+void Window_android::setTitle(const char* title) {
+ fSkiaAndroidApp->setTitle(title);
+}
+
+void Window_android::setUIState(const char* state) {
+ fSkiaAndroidApp->setUIState(state);
+}
+
+bool Window_android::attach(BackendType attachType) {
+ fBackendType = attachType;
+
+ // We delay the creation of fWindowContext until Android informs us that
+ // the native window is ready to use.
+ // The creation will be done in initDisplay, which is initiated by kSurfaceCreated event.
+ return true;
+}
+
+void Window_android::initDisplay(ANativeWindow* window) {
+ SkASSERT(window);
+ switch (fBackendType) {
+ case kNativeGL_BackendType:
+ default:
+ fWindowContext = window_context_factory::NewGLForAndroid(window,
+ fRequestedDisplayParams);
+ break;
+ case kRaster_BackendType:
+ fWindowContext = window_context_factory::NewRasterForAndroid(window,
+ fRequestedDisplayParams);
+ break;
+#ifdef SK_VULKAN
+ case kVulkan_BackendType:
+ fWindowContext = window_context_factory::NewVulkanForAndroid(window,
+ fRequestedDisplayParams);
+ break;
+#endif
+ }
+ this->onBackendCreated();
+}
+
+void Window_android::onDisplayDestroyed() {
+ detach();
+}
+
+void Window_android::onInval() {
+ fSkiaAndroidApp->postMessage(Message(kContentInvalidated));
+}
+
+void Window_android::paintIfNeeded() {
+ if (fWindowContext) { // Check if initDisplay has already been called
+ onPaint();
+ } else {
+ markInvalProcessed();
+ }
+}
+
+} // namespace sk_app
diff --git a/tools/sk_app/android/Window_android.h b/tools/sk_app/android/Window_android.h
new file mode 100644
index 0000000000..9e28a8075b
--- /dev/null
+++ b/tools/sk_app/android/Window_android.h
@@ -0,0 +1,43 @@
+/*
+* 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 "surface_glue_android.h"
+
+namespace sk_app {
+
+class Window_android : public Window {
+public:
+ Window_android() : Window() {}
+ ~Window_android() override {}
+
+ bool init(SkiaAndroidApp* skiaAndroidApp);
+ void initDisplay(ANativeWindow* window);
+ void onDisplayDestroyed();
+
+ void setTitle(const char*) override;
+ void show() override {}
+
+ bool attach(BackendType) override;
+ void onInval() override;
+ void setUIState(const char* state) override;
+
+ void paintIfNeeded();
+
+ bool scaleContentToFit() const override { return true; }
+
+private:
+ SkiaAndroidApp* fSkiaAndroidApp = nullptr;
+ BackendType fBackendType;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/android/main_android.cpp b/tools/sk_app/android/main_android.cpp
new file mode 100644
index 0000000000..cb8db6c3b4
--- /dev/null
+++ b/tools/sk_app/android/main_android.cpp
@@ -0,0 +1,65 @@
+/*
+* 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"
+
+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/skps",
+ // TODO: figure out how to use am start with extra params to pass in additional arguments at
+ // runtime
+ // "--atrace",
+ };
+
+ std::unique_ptr<Application> vkApp(Application::Create(SK_ARRAY_COUNT(gCmdLine),
+ const_cast<char**>(gCmdLine),
+ state));
+
+ // 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;
+ }
+
+ vkApp->onIdle();
+ }
+ }
+}
+//END_INCLUDE(all)
diff --git a/tools/sk_app/android/surface_glue_android.cpp b/tools/sk_app/android/surface_glue_android.cpp
new file mode 100644
index 0000000000..9c734247db
--- /dev/null
+++ b/tools/sk_app/android/surface_glue_android.cpp
@@ -0,0 +1,278 @@
+/*
+* 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 "surface_glue_android.h"
+
+#include <jni.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <unordered_map>
+
+#include <android/input.h>
+#include <android/keycodes.h>
+#include <android/looper.h>
+#include <android/native_window_jni.h>
+
+#include "../Application.h"
+#include "SkTypes.h"
+#include "SkUtils.h"
+#include "Window_android.h"
+
+namespace sk_app {
+
+static const int LOOPER_ID_MESSAGEPIPE = 1;
+
+static const std::unordered_map<int, Window::Key> ANDROID_TO_WINDOW_KEYMAP({
+ {AKEYCODE_SOFT_LEFT, Window::Key::kLeft},
+ {AKEYCODE_SOFT_RIGHT, Window::Key::kRight}
+});
+
+static const std::unordered_map<int, Window::InputState> ANDROID_TO_WINDOW_STATEMAP({
+ {AMOTION_EVENT_ACTION_DOWN, Window::kDown_InputState},
+ {AMOTION_EVENT_ACTION_POINTER_DOWN, Window::kDown_InputState},
+ {AMOTION_EVENT_ACTION_UP, Window::kUp_InputState},
+ {AMOTION_EVENT_ACTION_POINTER_UP, Window::kUp_InputState},
+ {AMOTION_EVENT_ACTION_MOVE, Window::kMove_InputState},
+ {AMOTION_EVENT_ACTION_CANCEL, Window::kUp_InputState},
+});
+
+SkiaAndroidApp::SkiaAndroidApp(JNIEnv* env, jobject androidApp) {
+ env->GetJavaVM(&fJavaVM);
+ fAndroidApp = env->NewGlobalRef(androidApp);
+ jclass cls = env->GetObjectClass(fAndroidApp);
+ fSetTitleMethodID = env->GetMethodID(cls, "setTitle", "(Ljava/lang/String;)V");
+ fSetStateMethodID = env->GetMethodID(cls, "setState", "(Ljava/lang/String;)V");
+ fNativeWindow = nullptr;
+ pthread_create(&fThread, nullptr, pthread_main, this);
+}
+
+SkiaAndroidApp::~SkiaAndroidApp() {
+ fPThreadEnv->DeleteGlobalRef(fAndroidApp);
+ if (fWindow) {
+ fWindow->detach();
+ }
+ if (fNativeWindow) {
+ ANativeWindow_release(fNativeWindow);
+ fNativeWindow = nullptr;
+ }
+ if (fApp) {
+ delete fApp;
+ }
+}
+
+void SkiaAndroidApp::setTitle(const char* title) const {
+ jstring titleString = fPThreadEnv->NewStringUTF(title);
+ fPThreadEnv->CallVoidMethod(fAndroidApp, fSetTitleMethodID, titleString);
+ fPThreadEnv->DeleteLocalRef(titleString);
+}
+
+void SkiaAndroidApp::setUIState(const char* state) const {
+ jstring jstr = fPThreadEnv->NewStringUTF(state);
+ fPThreadEnv->CallVoidMethod(fAndroidApp, fSetStateMethodID, jstr);
+ fPThreadEnv->DeleteLocalRef(jstr);
+}
+
+void SkiaAndroidApp::postMessage(const Message& message) const {
+ SkDEBUGCODE(auto writeSize =) write(fPipes[1], &message, sizeof(message));
+ SkASSERT(writeSize == sizeof(message));
+}
+
+void SkiaAndroidApp::readMessage(Message* message) const {
+ SkDEBUGCODE(auto readSize =) read(fPipes[0], message, sizeof(Message));
+ SkASSERT(readSize == sizeof(Message));
+}
+
+int SkiaAndroidApp::message_callback(int fd, int events, void* data) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)data;
+ Message message;
+ skiaAndroidApp->readMessage(&message);
+ SkASSERT(message.fType != kUndefined);
+
+ switch (message.fType) {
+ case kDestroyApp: {
+ delete skiaAndroidApp;
+ pthread_exit(nullptr);
+ return 0;
+ }
+ case kContentInvalidated: {
+ ((Window_android*)skiaAndroidApp->fWindow)->paintIfNeeded();
+ break;
+ }
+ case kSurfaceCreated: {
+ SkASSERT(!skiaAndroidApp->fNativeWindow && message.fNativeWindow);
+ skiaAndroidApp->fNativeWindow = message.fNativeWindow;
+ auto window_android = (Window_android*)skiaAndroidApp->fWindow;
+ window_android->initDisplay(skiaAndroidApp->fNativeWindow);
+ ((Window_android*)skiaAndroidApp->fWindow)->paintIfNeeded();
+ break;
+ }
+ case kSurfaceChanged: {
+ SkASSERT(message.fNativeWindow);
+ int width = ANativeWindow_getWidth(skiaAndroidApp->fNativeWindow);
+ int height = ANativeWindow_getHeight(skiaAndroidApp->fNativeWindow);
+ auto window_android = (Window_android*)skiaAndroidApp->fWindow;
+ if (message.fNativeWindow != skiaAndroidApp->fNativeWindow) {
+ window_android->onDisplayDestroyed();
+ ANativeWindow_release(skiaAndroidApp->fNativeWindow);
+ skiaAndroidApp->fNativeWindow = message.fNativeWindow;
+ window_android->initDisplay(skiaAndroidApp->fNativeWindow);
+ }
+ window_android->onResize(width, height);
+ window_android->paintIfNeeded();
+ break;
+ }
+ case kSurfaceDestroyed: {
+ if (skiaAndroidApp->fNativeWindow) {
+ auto window_android = (Window_android*)skiaAndroidApp->fWindow;
+ window_android->onDisplayDestroyed();
+ ANativeWindow_release(skiaAndroidApp->fNativeWindow);
+ skiaAndroidApp->fNativeWindow = nullptr;
+ }
+ break;
+ }
+ case kKeyPressed: {
+ auto it = ANDROID_TO_WINDOW_KEYMAP.find(message.fKeycode);
+ SkASSERT(it != ANDROID_TO_WINDOW_KEYMAP.end());
+ // No modifier is supported so far
+ skiaAndroidApp->fWindow->onKey(it->second, Window::kDown_InputState, 0);
+ skiaAndroidApp->fWindow->onKey(it->second, Window::kUp_InputState, 0);
+ break;
+ }
+ case kTouched: {
+ auto it = ANDROID_TO_WINDOW_STATEMAP.find(message.fTouchState);
+ if (it != ANDROID_TO_WINDOW_STATEMAP.end()) {
+ skiaAndroidApp->fWindow->onTouch(message.fTouchOwner, it->second, message.fTouchX,
+ message.fTouchY);
+ } else {
+ SkDebugf("Unknown Touch State: %d\n", message.fTouchState);
+ }
+ break;
+ }
+ case kUIStateChanged: {
+ skiaAndroidApp->fWindow->onUIStateChanged(*message.stateName, *message.stateValue);
+ delete message.stateName;
+ delete message.stateValue;
+ break;
+ }
+ default: {
+ // do nothing
+ }
+ }
+
+ return 1; // continue receiving callbacks
+}
+
+void* SkiaAndroidApp::pthread_main(void* arg) {
+ SkDebugf("pthread_main begins");
+
+ auto skiaAndroidApp = (SkiaAndroidApp*)arg;
+
+ // Because JNIEnv is thread sensitive, we need AttachCurrentThread to set our fPThreadEnv
+ skiaAndroidApp->fJavaVM->AttachCurrentThread(&(skiaAndroidApp->fPThreadEnv), nullptr);
+
+ ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
+ pipe(skiaAndroidApp->fPipes);
+ ALooper_addFd(looper, skiaAndroidApp->fPipes[0], LOOPER_ID_MESSAGEPIPE, ALOOPER_EVENT_INPUT,
+ message_callback, skiaAndroidApp);
+
+ static const char* gCmdLine[] = {
+ "viewer",
+ // TODO: figure out how to use am start with extra params to pass in additional arguments at
+ // runtime. Or better yet make an in app switch to enable
+ // "--atrace",
+ };
+
+ skiaAndroidApp->fApp = Application::Create(SK_ARRAY_COUNT(gCmdLine),
+ const_cast<char**>(gCmdLine),
+ skiaAndroidApp);
+
+ while (true) {
+ const int ident = ALooper_pollAll(0, nullptr, nullptr, nullptr);
+
+ if (ident >= 0) {
+ SkDebugf("Unhandled ALooper_pollAll ident=%d !", ident);
+ } else {
+ skiaAndroidApp->fApp->onIdle();
+ }
+ }
+
+ SkDebugf("pthread_main ends");
+
+ return nullptr;
+}
+
+extern "C" // extern "C" is needed for JNI (although the method itself is in C++)
+ JNIEXPORT jlong JNICALL
+ Java_org_skia_viewer_ViewerApplication_createNativeApp(JNIEnv* env, jobject application) {
+ SkiaAndroidApp* skiaAndroidApp = new SkiaAndroidApp(env, application);
+ return (jlong)((size_t)skiaAndroidApp);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerApplication_destroyNativeApp(
+ JNIEnv* env, jobject application, jlong handle) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ skiaAndroidApp->postMessage(Message(kDestroyApp));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceCreated(
+ JNIEnv* env, jobject activity, jlong handle, jobject surface) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ Message message(kSurfaceCreated);
+ message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
+ skiaAndroidApp->postMessage(message);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceChanged(
+ JNIEnv* env, jobject activity, jlong handle, jobject surface) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ Message message(kSurfaceChanged);
+ message.fNativeWindow = ANativeWindow_fromSurface(env, surface);
+ skiaAndroidApp->postMessage(message);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onSurfaceDestroyed(
+ JNIEnv* env, jobject activity, jlong handle) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ skiaAndroidApp->postMessage(Message(kSurfaceDestroyed));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onKeyPressed(JNIEnv* env,
+ jobject activity,
+ jlong handle,
+ jint keycode) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ Message message(kKeyPressed);
+ message.fKeycode = keycode;
+ skiaAndroidApp->postMessage(message);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onTouched(
+ JNIEnv* env, jobject activity, jlong handle, jint owner, jint state, jfloat x, jfloat y) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ Message message(kTouched);
+ message.fTouchOwner = owner;
+ message.fTouchState = state;
+ message.fTouchX = x;
+ message.fTouchY = y;
+ skiaAndroidApp->postMessage(message);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_org_skia_viewer_ViewerActivity_onUIStateChanged(
+ JNIEnv* env, jobject activity, jlong handle, jstring stateName, jstring stateValue) {
+ auto skiaAndroidApp = (SkiaAndroidApp*)handle;
+ Message message(kUIStateChanged);
+ const char* nameChars = env->GetStringUTFChars(stateName, nullptr);
+ const char* valueChars = env->GetStringUTFChars(stateValue, nullptr);
+ message.stateName = new SkString(nameChars);
+ message.stateValue = new SkString(valueChars);
+ skiaAndroidApp->postMessage(message);
+ env->ReleaseStringUTFChars(stateName, nameChars);
+ env->ReleaseStringUTFChars(stateValue, valueChars);
+}
+
+} // namespace sk_app
diff --git a/tools/sk_app/android/surface_glue_android.h b/tools/sk_app/android/surface_glue_android.h
new file mode 100644
index 0000000000..1dd1f2854a
--- /dev/null
+++ b/tools/sk_app/android/surface_glue_android.h
@@ -0,0 +1,79 @@
+/*
+* 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 surface_glue_android_DEFINED
+#define surface_glue_android_DEFINED
+
+#include <pthread.h>
+
+#include <android/native_window_jni.h>
+
+#include "SkString.h"
+
+#include "../Application.h"
+#include "../Window.h"
+
+namespace sk_app {
+
+enum MessageType {
+ kUndefined,
+ kSurfaceCreated,
+ kSurfaceChanged,
+ kSurfaceDestroyed,
+ kDestroyApp,
+ kContentInvalidated,
+ kKeyPressed,
+ kTouched,
+ kUIStateChanged,
+};
+
+struct Message {
+ MessageType fType = kUndefined;
+ ANativeWindow* fNativeWindow = nullptr;
+ int fKeycode = 0;
+ int fTouchOwner, fTouchState;
+ float fTouchX, fTouchY;
+
+ SkString* stateName;
+ SkString* stateValue;
+
+ Message() {}
+ Message(MessageType t) : fType(t) {}
+};
+
+struct SkiaAndroidApp {
+ Application* fApp;
+ Window* fWindow;
+ jobject fAndroidApp;
+
+ SkiaAndroidApp(JNIEnv* env, jobject androidApp);
+
+ void postMessage(const Message& message) const;
+ void readMessage(Message* message) const;
+
+ // These must be called in SkiaAndroidApp's own pthread because the JNIEnv is thread sensitive
+ void setTitle(const char* title) const;
+ void setUIState(const char* state) const;
+
+private:
+ pthread_t fThread;
+ ANativeWindow* fNativeWindow;
+ int fPipes[2]; // 0 is the read message pipe, 1 is the write message pipe
+ JavaVM* fJavaVM;
+ JNIEnv* fPThreadEnv;
+ jmethodID fSetTitleMethodID, fSetStateMethodID;
+
+ // This must be called in SkiaAndroidApp's own pthread because the JNIEnv is thread sensitive
+ ~SkiaAndroidApp();
+
+ static int message_callback(int fd, int events, void* data);
+ static void* pthread_main(void*);
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/ios/GLWindowContext_ios.cpp b/tools/sk_app/ios/GLWindowContext_ios.cpp
new file mode 100644
index 0000000000..30bacf5cea
--- /dev/null
+++ b/tools/sk_app/ios/GLWindowContext_ios.cpp
@@ -0,0 +1,109 @@
+
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <OpenGLES/ES2/gl.h>
+#include "../GLWindowContext.h"
+#include "SDL.h"
+#include "WindowContextFactory_ios.h"
+#include "gl/GrGLInterface.h"
+
+using sk_app::DisplayParams;
+using sk_app::window_context_factory::IOSWindowInfo;
+using sk_app::GLWindowContext;
+
+namespace {
+
+class GLWindowContext_ios : public GLWindowContext {
+public:
+ GLWindowContext_ios(const IOSWindowInfo&, const DisplayParams&);
+
+ ~GLWindowContext_ios() override;
+
+ void onSwapBuffers() override;
+
+ sk_sp<const GrGLInterface> onInitializeContext() override;
+ void onDestroyContext() override;
+
+private:
+ SDL_Window* fWindow;
+ SDL_GLContext fGLContext;
+
+ typedef GLWindowContext INHERITED;
+};
+
+GLWindowContext_ios::GLWindowContext_ios(const IOSWindowInfo& info, const DisplayParams& params)
+ : INHERITED(params)
+ , fWindow(info.fWindow)
+ , fGLContext(nullptr) {
+
+ // any config code here (particularly for msaa)?
+
+ this->initializeContext();
+}
+
+GLWindowContext_ios::~GLWindowContext_ios() {
+ this->destroyContext();
+}
+
+sk_sp<const GrGLInterface> GLWindowContext_ios::onInitializeContext() {
+ SkASSERT(fWindow);
+
+ fGLContext = SDL_GL_CreateContext(fWindow);
+ if (!fGLContext) {
+ SkDebugf("%s\n", SDL_GetError());
+ return nullptr;
+ }
+
+ if (0 == SDL_GL_MakeCurrent(fWindow, fGLContext)) {
+ glClearStencil(0);
+ glClearColor(0, 0, 0, 0);
+ glStencilMask(0xffffffff);
+ glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &fStencilBits);
+ SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &fSampleCount);
+
+ SDL_GL_GetDrawableSize(fWindow, &fWidth, &fHeight);
+ glViewport(0, 0, fWidth, fHeight);
+ } else {
+ SkDebugf("MakeCurrent failed: %s\n", SDL_GetError());
+ }
+ return sk_sp<const GrGLInterface>(GrGLCreateNativeInterface());
+}
+
+void GLWindowContext_ios::onDestroyContext() {
+ if (!fWindow || !fGLContext) {
+ return;
+ }
+ SDL_GL_DeleteContext(fGLContext);
+ fGLContext = nullptr;
+}
+
+
+void GLWindowContext_ios::onSwapBuffers() {
+ if (fWindow && fGLContext) {
+ SDL_GL_SwapWindow(fWindow);
+ }
+}
+
+} // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewGLForIOS(const IOSWindowInfo& info, const DisplayParams& params) {
+ WindowContext* ctx = new GLWindowContext_ios(info, params);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+} // namespace sk_app
diff --git a/tools/sk_app/ios/RasterWindowContext_ios.cpp b/tools/sk_app/ios/RasterWindowContext_ios.cpp
new file mode 100644
index 0000000000..08b6560510
--- /dev/null
+++ b/tools/sk_app/ios/RasterWindowContext_ios.cpp
@@ -0,0 +1,136 @@
+
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <OpenGLES/ES2/gl.h>
+#include "../GLWindowContext.h"
+#include "SDL.h"
+#include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "WindowContextFactory_ios.h"
+#include "gl/GrGLInterface.h"
+#include "sk_tool_utils.h"
+
+using sk_app::DisplayParams;
+using sk_app::window_context_factory::IOSWindowInfo;
+using sk_app::GLWindowContext;
+
+namespace {
+
+// We use SDL to support Mac windowing mainly for convenience's sake. However, it
+// does not allow us to support a purely raster backend because we have no hooks into
+// the NSWindow's drawRect: method. Hence we use GL to handle the update. Should we
+// want to avoid this, we will probably need to write our own windowing backend.
+
+class RasterWindowContext_ios : public GLWindowContext {
+public:
+ RasterWindowContext_ios(const IOSWindowInfo&, const DisplayParams&);
+
+ ~RasterWindowContext_ios() override;
+
+ sk_sp<SkSurface> getBackbufferSurface() override;
+
+ void onSwapBuffers() override;
+
+ sk_sp<const GrGLInterface> onInitializeContext() override;
+ void onDestroyContext() override;
+
+private:
+ SDL_Window* fWindow;
+ SDL_GLContext fGLContext;
+ sk_sp<SkSurface> fBackbufferSurface;
+
+ typedef GLWindowContext INHERITED;
+};
+
+RasterWindowContext_ios::RasterWindowContext_ios(const IOSWindowInfo& info,
+ const DisplayParams& params)
+ : INHERITED(params)
+ , fWindow(info.fWindow)
+ , fGLContext(nullptr) {
+
+ // any config code here (particularly for msaa)?
+
+ this->initializeContext();
+}
+
+RasterWindowContext_ios::~RasterWindowContext_ios() {
+ this->destroyContext();
+}
+
+sk_sp<const GrGLInterface> RasterWindowContext_ios::onInitializeContext() {
+ SkASSERT(fWindow);
+
+ fGLContext = SDL_GL_CreateContext(fWindow);
+ if (!fGLContext) {
+ SkDebugf("%s\n", SDL_GetError());
+ return nullptr;
+ }
+
+ if (0 == SDL_GL_MakeCurrent(fWindow, fGLContext)) {
+ glClearStencil(0);
+ glClearColor(0, 0, 0, 0);
+ glStencilMask(0xffffffff);
+ glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &fStencilBits);
+ SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &fSampleCount);
+
+ SDL_GL_GetDrawableSize(fWindow, &fWidth, &fHeight);
+ glViewport(0, 0, fWidth, fHeight);
+ } else {
+ SkDebugf("MakeCurrent failed: %s\n", SDL_GetError());
+ }
+
+ // make the offscreen image
+ SkImageInfo info = SkImageInfo::Make(fWidth, fHeight, fDisplayParams.fColorType,
+ kPremul_SkAlphaType, fDisplayParams.fColorSpace);
+ fBackbufferSurface = SkSurface::MakeRaster(info);
+ return sk_sp<const GrGLInterface>(GrGLCreateNativeInterface());
+}
+
+void RasterWindowContext_ios::onDestroyContext() {
+ if (!fWindow || !fGLContext) {
+ return;
+ }
+ fBackbufferSurface.reset(nullptr);
+ SDL_GL_DeleteContext(fGLContext);
+ fGLContext = nullptr;
+}
+
+sk_sp<SkSurface> RasterWindowContext_ios::getBackbufferSurface() { return fBackbufferSurface; }
+
+void RasterWindowContext_ios::onSwapBuffers() {
+ if (fWindow && fGLContext) {
+ // We made/have an off-screen surface. Get the contents as an SkImage:
+ sk_sp<SkImage> snapshot = fBackbufferSurface->makeImageSnapshot();
+
+ sk_sp<SkSurface> gpuSurface = INHERITED::getBackbufferSurface();
+ SkCanvas* gpuCanvas = gpuSurface->getCanvas();
+ gpuCanvas->drawImage(snapshot, 0, 0);
+ gpuCanvas->flush();
+
+ SDL_GL_SwapWindow(fWindow);
+ }
+}
+
+} // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewRasterForIOS(const IOSWindowInfo& info, const DisplayParams& params) {
+ WindowContext* ctx = new RasterWindowContext_ios(info, params);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+} // namespace sk_app
diff --git a/tools/sk_app/ios/WindowContextFactory_ios.h b/tools/sk_app/ios/WindowContextFactory_ios.h
new file mode 100644
index 0000000000..09999c4c83
--- /dev/null
+++ b/tools/sk_app/ios/WindowContextFactory_ios.h
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef WindowContextFactory_ios_DEFINED
+#define WindowContextFactory_ios_DEFINED
+
+#include "SDL.h"
+
+namespace sk_app {
+
+class WindowContext;
+struct DisplayParams;
+
+namespace window_context_factory {
+
+struct IOSWindowInfo {
+ SDL_Window* fWindow;
+};
+
+inline WindowContext* NewVulkanForIOS(const IOSWindowInfo&, const DisplayParams&) {
+ // No Vulkan support on iOS.
+ return nullptr;
+}
+
+WindowContext* NewGLForIOS(const IOSWindowInfo&, const DisplayParams&);
+
+WindowContext* NewRasterForIOS(const IOSWindowInfo&, const DisplayParams&);
+
+} // namespace window_context_factory
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/ios/Window_ios.cpp b/tools/sk_app/ios/Window_ios.cpp
new file mode 100644
index 0000000000..c1bdeae5fc
--- /dev/null
+++ b/tools/sk_app/ios/Window_ios.cpp
@@ -0,0 +1,277 @@
+/*
+* Copyright 2017 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "SkUtils.h"
+#include "Timer.h"
+#include "WindowContextFactory_ios.h"
+#include "Window_ios.h"
+
+namespace sk_app {
+
+SkTDynamicHash<Window_ios, Uint32> Window_ios::gWindowMap;
+
+Window* Window::CreateNativeWindow(void*) {
+ Window_ios* window = new Window_ios();
+ if (!window->initWindow()) {
+ delete window;
+ return nullptr;
+ }
+
+ return window;
+}
+
+bool Window_ios::initWindow() {
+ if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) {
+ this->closeWindow();
+ }
+ // we already have a window
+ if (fWindow) {
+ return true;
+ }
+
+ constexpr int initialWidth = 1280;
+ constexpr int initialHeight = 960;
+
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+
+ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+ SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
+ SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
+
+ SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
+
+ if (fRequestedDisplayParams.fMSAASampleCount > 0) {
+ SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
+ SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, fRequestedDisplayParams.fMSAASampleCount);
+ } else {
+ SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
+ }
+ // TODO: handle other display params
+
+ uint32_t windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_ALLOW_HIGHDPI;
+ fWindow = SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
+ initialWidth, initialHeight, windowFlags);
+
+ if (!fWindow) {
+ return false;
+ }
+
+ fMSAASampleCount = fRequestedDisplayParams.fMSAASampleCount;
+
+ // add to hashtable of windows
+ fWindowID = SDL_GetWindowID(fWindow);
+ gWindowMap.add(this);
+
+ return true;
+}
+
+void Window_ios::closeWindow() {
+ if (fWindow) {
+ gWindowMap.remove(fWindowID);
+ SDL_DestroyWindow(fWindow);
+ fWindowID = 0;
+ fWindow = nullptr;
+ }
+}
+
+static Window::Key get_key(const SDL_Keysym& keysym) {
+ static const struct {
+ SDL_Keycode fSDLK;
+ Window::Key fKey;
+ } gPair[] = {
+ { SDLK_BACKSPACE, Window::Key::kBack },
+ { SDLK_CLEAR, Window::Key::kBack },
+ { SDLK_RETURN, Window::Key::kOK },
+ { SDLK_UP, Window::Key::kUp },
+ { SDLK_DOWN, Window::Key::kDown },
+ { SDLK_LEFT, Window::Key::kLeft },
+ { SDLK_RIGHT, Window::Key::kRight },
+ { SDLK_TAB, Window::Key::kTab },
+ { SDLK_PAGEUP, Window::Key::kPageUp },
+ { SDLK_PAGEDOWN, Window::Key::kPageDown },
+ { SDLK_HOME, Window::Key::kHome },
+ { SDLK_END, Window::Key::kEnd },
+ { SDLK_DELETE, Window::Key::kDelete },
+ { SDLK_ESCAPE, Window::Key::kEscape },
+ { SDLK_LSHIFT, Window::Key::kShift },
+ { SDLK_RSHIFT, Window::Key::kShift },
+ { SDLK_LCTRL, Window::Key::kCtrl },
+ { SDLK_RCTRL, Window::Key::kCtrl },
+ { SDLK_LALT, Window::Key::kOption },
+ { SDLK_LALT, Window::Key::kOption },
+ { 'A', Window::Key::kA },
+ { 'C', Window::Key::kC },
+ { 'V', Window::Key::kV },
+ { 'X', Window::Key::kX },
+ { 'Y', Window::Key::kY },
+ { 'Z', Window::Key::kZ },
+ };
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
+ if (gPair[i].fSDLK == keysym.sym) {
+ return gPair[i].fKey;
+ }
+ }
+ return Window::Key::kNONE;
+}
+
+static uint32_t get_modifiers(const SDL_Event& event) {
+ static const struct {
+ unsigned fSDLMask;
+ unsigned fSkMask;
+ } gModifiers[] = {
+ { KMOD_SHIFT, Window::kShift_ModifierKey },
+ { KMOD_CTRL, Window::kControl_ModifierKey },
+ { KMOD_ALT, Window::kOption_ModifierKey },
+ };
+
+ auto modifiers = 0;
+
+ switch (event.type) {
+ case SDL_KEYDOWN:
+ // fall through
+ case SDL_KEYUP: {
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) {
+ if (event.key.keysym.mod & gModifiers[i].fSDLMask) {
+ modifiers |= gModifiers[i].fSkMask;
+ }
+ }
+ if (0 == event.key.repeat) {
+ modifiers |= Window::kFirstPress_ModifierKey;
+ }
+ break;
+ }
+
+ default: {
+ SDL_Keymod mod = SDL_GetModState();
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) {
+ if (mod & gModifiers[i].fSDLMask) {
+ modifiers |= gModifiers[i].fSkMask;
+ }
+ }
+ break;
+ }
+ }
+ return modifiers;
+}
+
+bool Window_ios::HandleWindowEvent(const SDL_Event& event) {
+ Window_ios* win = gWindowMap.find(event.window.windowID);
+ if (win && win->handleEvent(event)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool Window_ios::handleEvent(const SDL_Event& event) {
+ switch (event.type) {
+ case SDL_WINDOWEVENT:
+ if (SDL_WINDOWEVENT_EXPOSED == event.window.event) {
+ this->onPaint();
+ } else if (SDL_WINDOWEVENT_RESIZED == event.window.event) {
+ this->onResize(event.window.data1, event.window.data2);
+ }
+ break;
+
+ case SDL_MOUSEBUTTONDOWN:
+ if (event.button.button == SDL_BUTTON_LEFT) {
+ this->onMouse(event.button.x, event.button.y,
+ Window::kDown_InputState, get_modifiers(event));
+ }
+ break;
+
+ case SDL_MOUSEBUTTONUP:
+ if (event.button.button == SDL_BUTTON_LEFT) {
+ this->onMouse(event.button.x, event.button.y,
+ Window::kUp_InputState, get_modifiers(event));
+ }
+ break;
+
+ case SDL_MOUSEMOTION:
+ this->onMouse(event.motion.x, event.motion.y,
+ Window::kMove_InputState, get_modifiers(event));
+ break;
+
+ case SDL_MOUSEWHEEL:
+ this->onMouseWheel(event.wheel.y, get_modifiers(event));
+ break;
+
+ case SDL_KEYDOWN: {
+ Window::Key key = get_key(event.key.keysym);
+ if (key != Window::Key::kNONE) {
+ if (!this->onKey(key, Window::kDown_InputState, get_modifiers(event))) {
+ if (event.key.keysym.sym == SDLK_ESCAPE) {
+ return true;
+ }
+ }
+ }
+ } break;
+
+ case SDL_KEYUP: {
+ Window::Key key = get_key(event.key.keysym);
+ if (key != Window::Key::kNONE) {
+ (void) this->onKey(key, Window::kUp_InputState,
+ get_modifiers(event));
+ }
+ } break;
+
+ case SDL_TEXTINPUT: {
+ const char* textIter = &event.text.text[0];
+ while (SkUnichar c = SkUTF8_NextUnichar(&textIter)) {
+ (void) this->onChar(c, get_modifiers(event));
+ }
+ } break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void Window_ios::setTitle(const char* title) {
+ SDL_SetWindowTitle(fWindow, title);
+}
+
+void Window_ios::show() {
+ SDL_ShowWindow(fWindow);
+}
+
+bool Window_ios::attach(BackendType attachType) {
+ this->initWindow();
+
+ window_context_factory::IOSWindowInfo info;
+ info.fWindow = fWindow;
+ switch (attachType) {
+ case kRaster_BackendType:
+ fWindowContext = NewRasterForIOS(info, fRequestedDisplayParams);
+ break;
+
+ case kNativeGL_BackendType:
+ default:
+ fWindowContext = NewGLForIOS(info, fRequestedDisplayParams);
+ break;
+ }
+ this->onBackendCreated();
+
+ return (SkToBool(fWindowContext));
+}
+
+void Window_ios::onInval() {
+ SDL_Event sdlevent;
+ sdlevent.type = SDL_WINDOWEVENT;
+ sdlevent.window.windowID = fWindowID;
+ sdlevent.window.event = SDL_WINDOWEVENT_EXPOSED;
+ SDL_PushEvent(&sdlevent);
+}
+
+} // namespace sk_app
diff --git a/tools/sk_app/ios/Window_ios.h b/tools/sk_app/ios/Window_ios.h
new file mode 100644
index 0000000000..667fa74e82
--- /dev/null
+++ b/tools/sk_app/ios/Window_ios.h
@@ -0,0 +1,64 @@
+/*
+* Copyright 2017 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#ifndef Window_ios_DEFINED
+#define Window_ios_DEFINED
+
+#include "../Window.h"
+#include "SkChecksum.h"
+#include "SkTDynamicHash.h"
+
+#include "SDL.h"
+
+namespace sk_app {
+
+class Window_ios : public Window {
+public:
+ Window_ios()
+ : INHERITED()
+ , fWindow(nullptr)
+ , fWindowID(0)
+ , fMSAASampleCount(0) {}
+ ~Window_ios() override { this->closeWindow(); }
+
+ bool initWindow();
+
+ void setTitle(const char*) override;
+ void show() override;
+
+ bool attach(BackendType) override;
+
+ void onInval() override;
+
+ static bool HandleWindowEvent(const SDL_Event& event);
+
+ static const Uint32& GetKey(const Window_ios& w) {
+ return w.fWindowID;
+ }
+
+ static uint32_t Hash(const Uint32& winID) {
+ return winID;
+ }
+
+private:
+ bool handleEvent(const SDL_Event& event);
+
+ void closeWindow();
+
+ static SkTDynamicHash<Window_ios, Uint32> gWindowMap;
+
+ SDL_Window* fWindow;
+ Uint32 fWindowID;
+
+ int fMSAASampleCount;
+
+ typedef Window INHERITED;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/ios/main_ios.cpp b/tools/sk_app/ios/main_ios.cpp
new file mode 100644
index 0000000000..fe82c46485
--- /dev/null
+++ b/tools/sk_app/ios/main_ios.cpp
@@ -0,0 +1,58 @@
+/*
+* Copyright 2017 Google Inc.
+*
+* Use of this source code is governed by a BSD-style license that can be
+* found in the LICENSE file.
+*/
+
+#include "SkTypes.h"
+#include "SkTHash.h"
+#include "SDL.h"
+#include "Timer.h"
+#include "Window_ios.h"
+#include "../Application.h"
+
+using sk_app::Application;
+
+int main(int argc, char* argv[]) {
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
+ SkDebugf("Could not initialize SDL!\n");
+ return 1;
+ }
+
+ Application* app = Application::Create(argc, argv, nullptr);
+
+ SDL_Event event;
+ bool done = false;
+ while (!done) {
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ // events handled by the windows
+ case SDL_WINDOWEVENT:
+ case SDL_MOUSEMOTION:
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ case SDL_MOUSEWHEEL:
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ case SDL_TEXTINPUT:
+ done = sk_app::Window_ios::HandleWindowEvent(event);
+ break;
+
+ case SDL_QUIT:
+ done = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ app->onIdle();
+ }
+ delete app;
+
+ SDL_Quit();
+
+ return 0;
+}
diff --git a/tools/sk_app/mac/GLWindowContext_mac.cpp b/tools/sk_app/mac/GLWindowContext_mac.cpp
new file mode 100644
index 0000000000..7f09d54522
--- /dev/null
+++ b/tools/sk_app/mac/GLWindowContext_mac.cpp
@@ -0,0 +1,109 @@
+
+/*
+ * 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 <OpenGL/gl.h>
+#include "../GLWindowContext.h"
+#include "SDL.h"
+#include "WindowContextFactory_mac.h"
+#include "gl/GrGLInterface.h"
+
+using sk_app::DisplayParams;
+using sk_app::window_context_factory::MacWindowInfo;
+using sk_app::GLWindowContext;
+
+namespace {
+
+class GLWindowContext_mac : public GLWindowContext {
+public:
+ GLWindowContext_mac(const MacWindowInfo&, const DisplayParams&);
+
+ ~GLWindowContext_mac() override;
+
+ void onSwapBuffers() override;
+
+ sk_sp<const GrGLInterface> onInitializeContext() override;
+ void onDestroyContext() override;
+
+private:
+ SDL_Window* fWindow;
+ SDL_GLContext fGLContext;
+
+ typedef GLWindowContext INHERITED;
+};
+
+GLWindowContext_mac::GLWindowContext_mac(const MacWindowInfo& info, const DisplayParams& params)
+ : INHERITED(params)
+ , fWindow(info.fWindow)
+ , fGLContext(nullptr) {
+
+ // any config code here (particularly for msaa)?
+
+ this->initializeContext();
+}
+
+GLWindowContext_mac::~GLWindowContext_mac() {
+ this->destroyContext();
+}
+
+sk_sp<const GrGLInterface> GLWindowContext_mac::onInitializeContext() {
+ SkASSERT(fWindow);
+
+ fGLContext = SDL_GL_CreateContext(fWindow);
+ if (!fGLContext) {
+ SkDebugf("%s\n", SDL_GetError());
+ return nullptr;
+ }
+
+ if (0 == SDL_GL_MakeCurrent(fWindow, fGLContext)) {
+ glClearStencil(0);
+ glClearColor(0, 0, 0, 0);
+ glStencilMask(0xffffffff);
+ glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &fStencilBits);
+ SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &fSampleCount);
+
+ SDL_GetWindowSize(fWindow, &fWidth, &fHeight);
+ glViewport(0, 0, fWidth, fHeight);
+ } else {
+ SkDebugf("MakeCurrent failed: %s\n", SDL_GetError());
+ }
+ return sk_sp<const GrGLInterface>(GrGLCreateNativeInterface());
+}
+
+void GLWindowContext_mac::onDestroyContext() {
+ if (!fWindow || !fGLContext) {
+ return;
+ }
+ SDL_GL_DeleteContext(fGLContext);
+ fGLContext = nullptr;
+}
+
+
+void GLWindowContext_mac::onSwapBuffers() {
+ if (fWindow && fGLContext) {
+ SDL_GL_SwapWindow(fWindow);
+ }
+}
+
+} // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewGLForMac(const MacWindowInfo& info, const DisplayParams& params) {
+ WindowContext* ctx = new GLWindowContext_mac(info, params);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+} // namespace sk_app
diff --git a/tools/sk_app/mac/RasterWindowContext_mac.cpp b/tools/sk_app/mac/RasterWindowContext_mac.cpp
new file mode 100644
index 0000000000..409c49f218
--- /dev/null
+++ b/tools/sk_app/mac/RasterWindowContext_mac.cpp
@@ -0,0 +1,136 @@
+
+/*
+ * 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 <OpenGL/gl.h>
+#include "../GLWindowContext.h"
+#include "SDL.h"
+#include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "WindowContextFactory_mac.h"
+#include "gl/GrGLInterface.h"
+#include "sk_tool_utils.h"
+
+using sk_app::DisplayParams;
+using sk_app::window_context_factory::MacWindowInfo;
+using sk_app::GLWindowContext;
+
+namespace {
+
+// We use SDL to support Mac windowing mainly for convenience's sake. However, it
+// does not allow us to support a purely raster backend because we have no hooks into
+// the NSWindow's drawRect: method. Hence we use GL to handle the update. Should we
+// want to avoid this, we will probably need to write our own windowing backend.
+
+class RasterWindowContext_mac : public GLWindowContext {
+public:
+ RasterWindowContext_mac(const MacWindowInfo&, const DisplayParams&);
+
+ ~RasterWindowContext_mac() override;
+
+ sk_sp<SkSurface> getBackbufferSurface() override;
+
+ void onSwapBuffers() override;
+
+ sk_sp<const GrGLInterface> onInitializeContext() override;
+ void onDestroyContext() override;
+
+private:
+ SDL_Window* fWindow;
+ SDL_GLContext fGLContext;
+ sk_sp<SkSurface> fBackbufferSurface;
+
+ typedef GLWindowContext INHERITED;
+};
+
+RasterWindowContext_mac::RasterWindowContext_mac(const MacWindowInfo& info,
+ const DisplayParams& params)
+ : INHERITED(params)
+ , fWindow(info.fWindow)
+ , fGLContext(nullptr) {
+
+ // any config code here (particularly for msaa)?
+
+ this->initializeContext();
+}
+
+RasterWindowContext_mac::~RasterWindowContext_mac() {
+ this->destroyContext();
+}
+
+sk_sp<const GrGLInterface> RasterWindowContext_mac::onInitializeContext() {
+ SkASSERT(fWindow);
+
+ fGLContext = SDL_GL_CreateContext(fWindow);
+ if (!fGLContext) {
+ SkDebugf("%s\n", SDL_GetError());
+ return nullptr;
+ }
+
+ if (0 == SDL_GL_MakeCurrent(fWindow, fGLContext)) {
+ glClearStencil(0);
+ glClearColor(0, 0, 0, 0);
+ glStencilMask(0xffffffff);
+ glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &fStencilBits);
+ SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &fSampleCount);
+
+ SDL_GetWindowSize(fWindow, &fWidth, &fHeight);
+ glViewport(0, 0, fWidth, fHeight);
+ } else {
+ SkDebugf("MakeCurrent failed: %s\n", SDL_GetError());
+ }
+
+ // make the offscreen image
+ SkImageInfo info = SkImageInfo::Make(fWidth, fHeight, fDisplayParams.fColorType,
+ kPremul_SkAlphaType, fDisplayParams.fColorSpace);
+ fBackbufferSurface = SkSurface::MakeRaster(info);
+ return sk_sp<const GrGLInterface>(GrGLCreateNativeInterface());
+}
+
+void RasterWindowContext_mac::onDestroyContext() {
+ if (!fWindow || !fGLContext) {
+ return;
+ }
+ fBackbufferSurface.reset(nullptr);
+ SDL_GL_DeleteContext(fGLContext);
+ fGLContext = nullptr;
+}
+
+sk_sp<SkSurface> RasterWindowContext_mac::getBackbufferSurface() { return fBackbufferSurface; }
+
+void RasterWindowContext_mac::onSwapBuffers() {
+ if (fWindow && fGLContext) {
+ // We made/have an off-screen surface. Get the contents as an SkImage:
+ sk_sp<SkImage> snapshot = fBackbufferSurface->makeImageSnapshot();
+
+ sk_sp<SkSurface> gpuSurface = INHERITED::getBackbufferSurface();
+ SkCanvas* gpuCanvas = gpuSurface->getCanvas();
+ gpuCanvas->drawImage(snapshot, 0, 0);
+ gpuCanvas->flush();
+
+ SDL_GL_SwapWindow(fWindow);
+ }
+}
+
+} // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewRasterForMac(const MacWindowInfo& info, const DisplayParams& params) {
+ WindowContext* ctx = new RasterWindowContext_mac(info, params);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+} // namespace sk_app
diff --git a/tools/sk_app/mac/WindowContextFactory_mac.h b/tools/sk_app/mac/WindowContextFactory_mac.h
new file mode 100644
index 0000000000..3adc68bbc2
--- /dev/null
+++ b/tools/sk_app/mac/WindowContextFactory_mac.h
@@ -0,0 +1,38 @@
+
+/*
+ * 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 WindowContextFactory_mac_DEFINED
+#define WindowContextFactory_mac_DEFINED
+
+#include "SDL.h"
+
+namespace sk_app {
+
+class WindowContext;
+struct DisplayParams;
+
+namespace window_context_factory {
+
+struct MacWindowInfo {
+ SDL_Window* fWindow;
+};
+
+inline WindowContext* NewVulkanForMac(const MacWindowInfo&, const DisplayParams&) {
+ // No Vulkan support on Mac.
+ return nullptr;
+}
+
+WindowContext* NewGLForMac(const MacWindowInfo&, const DisplayParams&);
+
+WindowContext* NewRasterForMac(const MacWindowInfo&, const DisplayParams&);
+
+} // namespace window_context_factory
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/mac/Window_mac.cpp b/tools/sk_app/mac/Window_mac.cpp
new file mode 100644
index 0000000000..8de5b10450
--- /dev/null
+++ b/tools/sk_app/mac/Window_mac.cpp
@@ -0,0 +1,277 @@
+/*
+* 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 "SkUtils.h"
+#include "Timer.h"
+#include "WindowContextFactory_mac.h"
+#include "Window_mac.h"
+
+namespace sk_app {
+
+SkTDynamicHash<Window_mac, Uint32> Window_mac::gWindowMap;
+
+Window* Window::CreateNativeWindow(void*) {
+ Window_mac* window = new Window_mac();
+ if (!window->initWindow()) {
+ delete window;
+ return nullptr;
+ }
+
+ return window;
+}
+
+bool Window_mac::initWindow() {
+ if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) {
+ this->closeWindow();
+ }
+ // we already have a window
+ if (fWindow) {
+ return true;
+ }
+
+ constexpr int initialWidth = 1280;
+ constexpr int initialHeight = 960;
+
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+
+ SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+ SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
+ SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
+
+ SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
+
+ if (fRequestedDisplayParams.fMSAASampleCount > 0) {
+ SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
+ SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, fRequestedDisplayParams.fMSAASampleCount);
+ } else {
+ SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
+ }
+ // TODO: handle other display params
+
+ uint32_t windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
+ fWindow = SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
+ initialWidth, initialHeight, windowFlags);
+
+ if (!fWindow) {
+ return false;
+ }
+
+ fMSAASampleCount = fRequestedDisplayParams.fMSAASampleCount;
+
+ // add to hashtable of windows
+ fWindowID = SDL_GetWindowID(fWindow);
+ gWindowMap.add(this);
+
+ return true;
+}
+
+void Window_mac::closeWindow() {
+ if (fWindow) {
+ gWindowMap.remove(fWindowID);
+ SDL_DestroyWindow(fWindow);
+ fWindowID = 0;
+ fWindow = nullptr;
+ }
+}
+
+static Window::Key get_key(const SDL_Keysym& keysym) {
+ static const struct {
+ SDL_Keycode fSDLK;
+ Window::Key fKey;
+ } gPair[] = {
+ { SDLK_BACKSPACE, Window::Key::kBack },
+ { SDLK_CLEAR, Window::Key::kBack },
+ { SDLK_RETURN, Window::Key::kOK },
+ { SDLK_UP, Window::Key::kUp },
+ { SDLK_DOWN, Window::Key::kDown },
+ { SDLK_LEFT, Window::Key::kLeft },
+ { SDLK_RIGHT, Window::Key::kRight },
+ { SDLK_TAB, Window::Key::kTab },
+ { SDLK_PAGEUP, Window::Key::kPageUp },
+ { SDLK_PAGEDOWN, Window::Key::kPageDown },
+ { SDLK_HOME, Window::Key::kHome },
+ { SDLK_END, Window::Key::kEnd },
+ { SDLK_DELETE, Window::Key::kDelete },
+ { SDLK_ESCAPE, Window::Key::kEscape },
+ { SDLK_LSHIFT, Window::Key::kShift },
+ { SDLK_RSHIFT, Window::Key::kShift },
+ { SDLK_LCTRL, Window::Key::kCtrl },
+ { SDLK_RCTRL, Window::Key::kCtrl },
+ { SDLK_LALT, Window::Key::kOption },
+ { SDLK_LALT, Window::Key::kOption },
+ { 'A', Window::Key::kA },
+ { 'C', Window::Key::kC },
+ { 'V', Window::Key::kV },
+ { 'X', Window::Key::kX },
+ { 'Y', Window::Key::kY },
+ { 'Z', Window::Key::kZ },
+ };
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
+ if (gPair[i].fSDLK == keysym.sym) {
+ return gPair[i].fKey;
+ }
+ }
+ return Window::Key::kNONE;
+}
+
+static uint32_t get_modifiers(const SDL_Event& event) {
+ static const struct {
+ unsigned fSDLMask;
+ unsigned fSkMask;
+ } gModifiers[] = {
+ { KMOD_SHIFT, Window::kShift_ModifierKey },
+ { KMOD_CTRL, Window::kControl_ModifierKey },
+ { KMOD_ALT, Window::kOption_ModifierKey },
+ };
+
+ auto modifiers = 0;
+
+ switch (event.type) {
+ case SDL_KEYDOWN:
+ // fall through
+ case SDL_KEYUP: {
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) {
+ if (event.key.keysym.mod & gModifiers[i].fSDLMask) {
+ modifiers |= gModifiers[i].fSkMask;
+ }
+ }
+ if (0 == event.key.repeat) {
+ modifiers |= Window::kFirstPress_ModifierKey;
+ }
+ break;
+ }
+
+ default: {
+ SDL_Keymod mod = SDL_GetModState();
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) {
+ if (mod & gModifiers[i].fSDLMask) {
+ modifiers |= gModifiers[i].fSkMask;
+ }
+ }
+ break;
+ }
+ }
+ return modifiers;
+}
+
+bool Window_mac::HandleWindowEvent(const SDL_Event& event) {
+ Window_mac* win = gWindowMap.find(event.window.windowID);
+ if (win && win->handleEvent(event)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool Window_mac::handleEvent(const SDL_Event& event) {
+ switch (event.type) {
+ case SDL_WINDOWEVENT:
+ if (SDL_WINDOWEVENT_EXPOSED == event.window.event) {
+ this->onPaint();
+ } else if (SDL_WINDOWEVENT_RESIZED == event.window.event) {
+ this->onResize(event.window.data1, event.window.data2);
+ }
+ break;
+
+ case SDL_MOUSEBUTTONDOWN:
+ if (event.button.button == SDL_BUTTON_LEFT) {
+ this->onMouse(event.button.x, event.button.y,
+ Window::kDown_InputState, get_modifiers(event));
+ }
+ break;
+
+ case SDL_MOUSEBUTTONUP:
+ if (event.button.button == SDL_BUTTON_LEFT) {
+ this->onMouse(event.button.x, event.button.y,
+ Window::kUp_InputState, get_modifiers(event));
+ }
+ break;
+
+ case SDL_MOUSEMOTION:
+ this->onMouse(event.motion.x, event.motion.y,
+ Window::kMove_InputState, get_modifiers(event));
+ break;
+
+ case SDL_MOUSEWHEEL:
+ this->onMouseWheel(event.wheel.y, get_modifiers(event));
+ break;
+
+ case SDL_KEYDOWN: {
+ Window::Key key = get_key(event.key.keysym);
+ if (key != Window::Key::kNONE) {
+ if (!this->onKey(key, Window::kDown_InputState, get_modifiers(event))) {
+ if (event.key.keysym.sym == SDLK_ESCAPE) {
+ return true;
+ }
+ }
+ }
+ } break;
+
+ case SDL_KEYUP: {
+ Window::Key key = get_key(event.key.keysym);
+ if (key != Window::Key::kNONE) {
+ (void) this->onKey(key, Window::kUp_InputState,
+ get_modifiers(event));
+ }
+ } break;
+
+ case SDL_TEXTINPUT: {
+ const char* textIter = &event.text.text[0];
+ while (SkUnichar c = SkUTF8_NextUnichar(&textIter)) {
+ (void) this->onChar(c, get_modifiers(event));
+ }
+ } break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void Window_mac::setTitle(const char* title) {
+ SDL_SetWindowTitle(fWindow, title);
+}
+
+void Window_mac::show() {
+ SDL_ShowWindow(fWindow);
+}
+
+bool Window_mac::attach(BackendType attachType) {
+ this->initWindow();
+
+ window_context_factory::MacWindowInfo info;
+ info.fWindow = fWindow;
+ switch (attachType) {
+ case kRaster_BackendType:
+ fWindowContext = NewRasterForMac(info, fRequestedDisplayParams);
+ break;
+
+ case kNativeGL_BackendType:
+ default:
+ fWindowContext = NewGLForMac(info, fRequestedDisplayParams);
+ break;
+ }
+ this->onBackendCreated();
+
+ return (SkToBool(fWindowContext));
+}
+
+void Window_mac::onInval() {
+ SDL_Event sdlevent;
+ sdlevent.type = SDL_WINDOWEVENT;
+ sdlevent.window.windowID = fWindowID;
+ sdlevent.window.event = SDL_WINDOWEVENT_EXPOSED;
+ SDL_PushEvent(&sdlevent);
+}
+
+} // namespace sk_app
diff --git a/tools/sk_app/mac/Window_mac.h b/tools/sk_app/mac/Window_mac.h
new file mode 100644
index 0000000000..aa5c8df696
--- /dev/null
+++ b/tools/sk_app/mac/Window_mac.h
@@ -0,0 +1,64 @@
+/*
+* 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_mac_DEFINED
+#define Window_mac_DEFINED
+
+#include "../Window.h"
+#include "SkChecksum.h"
+#include "SkTDynamicHash.h"
+
+#include "SDL.h"
+
+namespace sk_app {
+
+class Window_mac : public Window {
+public:
+ Window_mac()
+ : INHERITED()
+ , fWindow(nullptr)
+ , fWindowID(0)
+ , fMSAASampleCount(0) {}
+ ~Window_mac() override { this->closeWindow(); }
+
+ bool initWindow();
+
+ void setTitle(const char*) override;
+ void show() override;
+
+ bool attach(BackendType) override;
+
+ void onInval() override;
+
+ static bool HandleWindowEvent(const SDL_Event& event);
+
+ static const Uint32& GetKey(const Window_mac& w) {
+ return w.fWindowID;
+ }
+
+ static uint32_t Hash(const Uint32& winID) {
+ return winID;
+ }
+
+private:
+ bool handleEvent(const SDL_Event& event);
+
+ void closeWindow();
+
+ static SkTDynamicHash<Window_mac, Uint32> gWindowMap;
+
+ SDL_Window* fWindow;
+ Uint32 fWindowID;
+
+ int fMSAASampleCount;
+
+ typedef Window INHERITED;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/mac/main_mac.cpp b/tools/sk_app/mac/main_mac.cpp
new file mode 100644
index 0000000000..6dcf5b93f7
--- /dev/null
+++ b/tools/sk_app/mac/main_mac.cpp
@@ -0,0 +1,58 @@
+/*
+* 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 "SkTypes.h"
+#include "SkTHash.h"
+#include "SDL.h"
+#include "Timer.h"
+#include "Window_mac.h"
+#include "../Application.h"
+
+using sk_app::Application;
+
+int main(int argc, char* argv[]) {
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
+ SkDebugf("Could not initialize SDL!\n");
+ return 1;
+ }
+
+ Application* app = Application::Create(argc, argv, nullptr);
+
+ SDL_Event event;
+ bool done = false;
+ while (!done) {
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ // events handled by the windows
+ case SDL_WINDOWEVENT:
+ case SDL_MOUSEMOTION:
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ case SDL_MOUSEWHEEL:
+ case SDL_KEYDOWN:
+ case SDL_KEYUP:
+ case SDL_TEXTINPUT:
+ done = sk_app::Window_mac::HandleWindowEvent(event);
+ break;
+
+ case SDL_QUIT:
+ done = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ app->onIdle();
+ }
+ delete app;
+
+ SDL_Quit();
+
+ return 0;
+}
diff --git a/tools/sk_app/unix/GLWindowContext_unix.cpp b/tools/sk_app/unix/GLWindowContext_unix.cpp
new file mode 100644
index 0000000000..d7a4387880
--- /dev/null
+++ b/tools/sk_app/unix/GLWindowContext_unix.cpp
@@ -0,0 +1,149 @@
+
+/*
+ * 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 <GL/gl.h>
+#include "../GLWindowContext.h"
+#include "WindowContextFactory_unix.h"
+#include "gl/GrGLInterface.h"
+
+using sk_app::window_context_factory::XlibWindowInfo;
+using sk_app::DisplayParams;
+using sk_app::GLWindowContext;
+
+namespace {
+
+class GLWindowContext_xlib : public GLWindowContext {
+public:
+ GLWindowContext_xlib(const XlibWindowInfo&, const DisplayParams&);
+ ~GLWindowContext_xlib() override;
+
+ void onSwapBuffers() override;
+
+ void onDestroyContext() override;
+
+protected:
+ sk_sp<const GrGLInterface> onInitializeContext() override;
+
+private:
+ GLWindowContext_xlib(void*, const DisplayParams&);
+
+ Display* fDisplay;
+ XWindow fWindow;
+ GLXFBConfig* fFBConfig;
+ XVisualInfo* fVisualInfo;
+ GLXContext fGLContext;
+
+ typedef GLWindowContext INHERITED;
+};
+
+GLWindowContext_xlib::GLWindowContext_xlib(const XlibWindowInfo& winInfo, const DisplayParams& params)
+ : INHERITED(params)
+ , fDisplay(winInfo.fDisplay)
+ , fWindow(winInfo.fWindow)
+ , fFBConfig(winInfo.fFBConfig)
+ , fVisualInfo(winInfo.fVisualInfo)
+ , fGLContext() {
+ fWidth = winInfo.fWidth;
+ fHeight = winInfo.fHeight;
+ this->initializeContext();
+}
+
+using CreateContextAttribsFn = GLXContext(Display*, GLXFBConfig, GLXContext, Bool, const int*);
+
+sk_sp<const GrGLInterface> GLWindowContext_xlib::onInitializeContext() {
+ SkASSERT(fDisplay);
+ SkASSERT(!fGLContext);
+ // We attempt to use glXCreateContextAttribsARB as RenderDoc requires that the context be
+ // created with this rather than glXCreateContext.
+ CreateContextAttribsFn* createContextAttribs = (CreateContextAttribsFn*)glXGetProcAddressARB(
+ (const GLubyte*)"glXCreateContextAttribsARB");
+ if (createContextAttribs && fFBConfig) {
+ // Specifying 3.2 allows an arbitrarily high context version (so long as no 3.2 features
+ // have been removed).
+ for (int minor = 2; minor >= 0 && !fGLContext; --minor) {
+ // Ganesh prefers a compatibility profile for possible NVPR support. However, RenderDoc
+ // requires a core profile. Edit this code to use RenderDoc.
+ for (int profile : {GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
+ GLX_CONTEXT_CORE_PROFILE_BIT_ARB}) {
+ int attribs[] = {
+ GLX_CONTEXT_MAJOR_VERSION_ARB, 3, GLX_CONTEXT_MINOR_VERSION_ARB, minor,
+ GLX_CONTEXT_PROFILE_MASK_ARB, profile,
+ 0
+ };
+ fGLContext = createContextAttribs(fDisplay, *fFBConfig, nullptr, True, attribs);
+ if (fGLContext) {
+ break;
+ }
+ }
+ }
+ }
+ if (!fGLContext) {
+ fGLContext = glXCreateContext(fDisplay, fVisualInfo, nullptr, GL_TRUE);
+ }
+ if (!fGLContext) {
+ return nullptr;
+ }
+
+ if (!glXMakeCurrent(fDisplay, fWindow, fGLContext)) {
+ return nullptr;
+ }
+ glClearStencil(0);
+ glClearColor(0, 0, 0, 0);
+ glStencilMask(0xffffffff);
+ glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ glXGetConfig(fDisplay, fVisualInfo, GLX_STENCIL_SIZE, &fStencilBits);
+ glXGetConfig(fDisplay, fVisualInfo, GLX_SAMPLES_ARB, &fSampleCount);
+
+ XWindow root;
+ int x, y;
+ unsigned int border_width, depth;
+ XGetGeometry(fDisplay, fWindow, &root, &x, &y, (unsigned int*)&fWidth, (unsigned int*)&fHeight,
+ &border_width, &depth);
+ glViewport(0, 0, fWidth, fHeight);
+
+ return sk_sp<const GrGLInterface>(GrGLCreateNativeInterface());
+}
+
+GLWindowContext_xlib::~GLWindowContext_xlib() {
+ this->destroyContext();
+}
+
+void GLWindowContext_xlib::onDestroyContext() {
+ if (!fDisplay || !fGLContext) {
+ return;
+ }
+ glXMakeCurrent(fDisplay, None, nullptr);
+ glXDestroyContext(fDisplay, fGLContext);
+ fGLContext = nullptr;
+}
+
+void GLWindowContext_xlib::onSwapBuffers() {
+ if (fDisplay && fGLContext) {
+ glXSwapBuffers(fDisplay, fWindow);
+ }
+}
+
+} // anonymous namespace
+
+namespace sk_app {
+
+namespace window_context_factory {
+
+WindowContext* NewGLForXlib(const XlibWindowInfo& winInfo, const DisplayParams& params) {
+ WindowContext* ctx = new GLWindowContext_xlib(winInfo, params);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+
+} // namespace sk_app
diff --git a/tools/sk_app/unix/RasterWindowContext_unix.cpp b/tools/sk_app/unix/RasterWindowContext_unix.cpp
new file mode 100644
index 0000000000..6bfa6fd0be
--- /dev/null
+++ b/tools/sk_app/unix/RasterWindowContext_unix.cpp
@@ -0,0 +1,104 @@
+/*
+ * 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 "WindowContextFactory_unix.h"
+#include "../RasterWindowContext.h"
+#include "SkSurface.h"
+
+using sk_app::RasterWindowContext;
+using sk_app::DisplayParams;
+
+namespace {
+
+class RasterWindowContext_xlib : public RasterWindowContext {
+public:
+ RasterWindowContext_xlib(Display*, XWindow, int width, int height, const DisplayParams&);
+
+ sk_sp<SkSurface> getBackbufferSurface() override;
+ void swapBuffers() override;
+ bool isValid() override { return SkToBool(fWindow); }
+ void resize(int w, int h) override;
+ void setDisplayParams(const DisplayParams& params) override;
+
+protected:
+ sk_sp<SkSurface> fBackbufferSurface;
+ Display* fDisplay;
+ XWindow fWindow;
+ GC fGC;
+
+ typedef RasterWindowContext INHERITED;
+};
+
+RasterWindowContext_xlib::RasterWindowContext_xlib(Display* display, XWindow window, int width,
+ int height, const DisplayParams& params)
+ : INHERITED(params)
+ , fDisplay(display)
+ , fWindow(window) {
+ fGC = XCreateGC(fDisplay, fWindow, 0, nullptr);
+ this->resize(width, height);
+ fWidth = width;
+ fHeight = height;
+}
+
+void RasterWindowContext_xlib::setDisplayParams(const DisplayParams& params) {
+ fDisplayParams = params;
+ XWindowAttributes attrs;
+ XGetWindowAttributes(fDisplay, fWindow, &attrs);
+ this->resize(attrs.width, attrs.height);
+}
+
+void RasterWindowContext_xlib::resize(int w, int h) {
+ SkImageInfo info = SkImageInfo::Make(w, h, fDisplayParams.fColorType, kPremul_SkAlphaType,
+ fDisplayParams.fColorSpace);
+ fBackbufferSurface = SkSurface::MakeRaster(info);
+
+}
+
+sk_sp<SkSurface> RasterWindowContext_xlib::getBackbufferSurface() { return fBackbufferSurface; }
+
+void RasterWindowContext_xlib::swapBuffers() {
+ SkPixmap pm;
+ if (!fBackbufferSurface->peekPixels(&pm)) {
+ return;
+ }
+ int bitsPerPixel = pm.info().bytesPerPixel() * 8;
+ XImage image;
+ memset(&image, 0, sizeof(image));
+ image.width = pm.width();
+ image.height = pm.height();
+ image.format = ZPixmap;
+ image.data = (char*) pm.addr();
+ image.byte_order = LSBFirst;
+ image.bitmap_unit = bitsPerPixel;
+ image.bitmap_bit_order = LSBFirst;
+ image.bitmap_pad = bitsPerPixel;
+ image.depth = 24;
+ image.bytes_per_line = pm.rowBytes() - pm.width() * pm.info().bytesPerPixel();
+ image.bits_per_pixel = bitsPerPixel;
+ if (!XInitImage(&image)) {
+ return;
+ }
+ XPutImage(fDisplay, fWindow, fGC, &image, 0, 0, 0, 0, pm.width(), pm.height());
+}
+
+} // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewRasterForXlib(const XlibWindowInfo& info, const DisplayParams& params) {
+ WindowContext* ctx = new RasterWindowContext_xlib(info.fDisplay, info.fWindow, info.fWidth,
+ info.fHeight, params);
+ if (!ctx->isValid()) {
+ delete ctx;
+ ctx = nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+} // namespace sk_app
diff --git a/tools/sk_app/unix/VulkanWindowContext_unix.cpp b/tools/sk_app/unix/VulkanWindowContext_unix.cpp
new file mode 100644
index 0000000000..b2f1ffc763
--- /dev/null
+++ b/tools/sk_app/unix/VulkanWindowContext_unix.cpp
@@ -0,0 +1,86 @@
+
+/*
+ * 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 "vk/GrVkInterface.h"
+#include "vk/GrVkUtil.h"
+
+#include "vk/VkTestUtils.h"
+
+#include <X11/Xlib-xcb.h>
+
+#include "WindowContextFactory_unix.h"
+#include "../VulkanWindowContext.h"
+
+namespace sk_app {
+
+namespace window_context_factory {
+
+WindowContext* NewVulkanForXlib(const XlibWindowInfo& info, const DisplayParams& displayParams) {
+ PFN_vkGetInstanceProcAddr instProc;
+ PFN_vkGetDeviceProcAddr devProc;
+ if (!sk_gpu_test::LoadVkLibraryAndGetProcAddrFuncs(&instProc, &devProc)) {
+ return nullptr;
+ }
+
+ auto createVkSurface = [&info, instProc](VkInstance instance) -> VkSurfaceKHR {
+ static PFN_vkCreateXcbSurfaceKHR createXcbSurfaceKHR = nullptr;
+ if (!createXcbSurfaceKHR) {
+ createXcbSurfaceKHR =
+ (PFN_vkCreateXcbSurfaceKHR) instProc(instance, "vkCreateXcbSurfaceKHR");
+ }
+
+ VkSurfaceKHR surface;
+
+ VkXcbSurfaceCreateInfoKHR surfaceCreateInfo;
+ memset(&surfaceCreateInfo, 0, sizeof(VkXcbSurfaceCreateInfoKHR));
+ surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
+ surfaceCreateInfo.pNext = nullptr;
+ surfaceCreateInfo.flags = 0;
+ surfaceCreateInfo.connection = XGetXCBConnection(info.fDisplay);
+ surfaceCreateInfo.window = info.fWindow;
+
+ VkResult res = createXcbSurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
+ if (VK_SUCCESS != res) {
+ return VK_NULL_HANDLE;
+ }
+
+ return surface;
+ };
+
+ auto canPresent = [&info, instProc](VkInstance instance, VkPhysicalDevice physDev,
+ uint32_t queueFamilyIndex) {
+ static PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR
+ getPhysicalDeviceXcbPresentationSupportKHR = nullptr;
+ if (!getPhysicalDeviceXcbPresentationSupportKHR) {
+ getPhysicalDeviceXcbPresentationSupportKHR =
+ (PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)
+ instProc(instance, "vkGetPhysicalDeviceXcbPresentationSupportKHR");
+ }
+
+
+ Display* display = info.fDisplay;
+ VisualID visualID = info.fVisualInfo->visualid;
+ VkBool32 check = getPhysicalDeviceXcbPresentationSupportKHR(physDev,
+ queueFamilyIndex,
+ XGetXCBConnection(display),
+ visualID);
+ return (VK_FALSE != check);
+ };
+ WindowContext* context = new VulkanWindowContext(displayParams, createVkSurface, canPresent,
+ instProc, devProc);
+ if (!context->isValid()) {
+ delete context;
+ return nullptr;
+ }
+ return context;
+}
+
+} // namespace VulkanWindowContextFactory
+
+} // namespace sk_app
diff --git a/tools/sk_app/unix/WindowContextFactory_unix.h b/tools/sk_app/unix/WindowContextFactory_unix.h
new file mode 100644
index 0000000000..e6d033b4cd
--- /dev/null
+++ b/tools/sk_app/unix/WindowContextFactory_unix.h
@@ -0,0 +1,42 @@
+
+/*
+ * 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 WindowContextFactory_unix_DEFINED
+#define WindowContextFactory_unix_DEFINED
+
+#include <X11/Xlib.h>
+#include <GL/glx.h>
+typedef Window XWindow;
+
+namespace sk_app {
+
+class WindowContext;
+struct DisplayParams;
+
+namespace window_context_factory {
+
+struct XlibWindowInfo {
+ Display* fDisplay;
+ XWindow fWindow;
+ GLXFBConfig* fFBConfig;
+ XVisualInfo* fVisualInfo;
+ int fWidth;
+ int fHeight;
+};
+
+WindowContext* NewVulkanForXlib(const XlibWindowInfo&, const DisplayParams&);
+
+WindowContext* NewGLForXlib(const XlibWindowInfo&, const DisplayParams&);
+
+WindowContext* NewRasterForXlib(const XlibWindowInfo&, const DisplayParams&);
+
+} // namespace window_context_factory
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/unix/Window_unix.cpp b/tools/sk_app/unix/Window_unix.cpp
new file mode 100644
index 0000000000..f5ca5ee073
--- /dev/null
+++ b/tools/sk_app/unix/Window_unix.cpp
@@ -0,0 +1,387 @@
+/*
+* 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 <tchar.h>
+
+#include "WindowContextFactory_unix.h"
+
+#include "SkUtils.h"
+#include "Timer.h"
+#include "../GLWindowContext.h"
+#include "Window_unix.h"
+
+extern "C" {
+ #include "keysym2ucs.h"
+}
+#include <X11/Xutil.h>
+#include <X11/XKBlib.h>
+
+namespace sk_app {
+
+SkTDynamicHash<Window_unix, XWindow> Window_unix::gWindowMap;
+
+Window* Window::CreateNativeWindow(void* platformData) {
+ Display* display = (Display*)platformData;
+ SkASSERT(display);
+
+ Window_unix* window = new Window_unix();
+ if (!window->initWindow(display)) {
+ delete window;
+ return nullptr;
+ }
+
+ return window;
+}
+
+const long kEventMask = ExposureMask | StructureNotifyMask |
+ KeyPressMask | KeyReleaseMask |
+ PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
+
+bool Window_unix::initWindow(Display* display) {
+ if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) {
+ this->closeWindow();
+ }
+ // we already have a window
+ if (fDisplay) {
+ return true;
+ }
+ fDisplay = display;
+
+ constexpr int initialWidth = 1280;
+ constexpr int initialHeight = 960;
+
+ // Attempt to create a window that supports GL
+
+ // We prefer the more recent glXChooseFBConfig but fall back to glXChooseVisual. They have
+ // slight differences in how attributes are specified.
+ static int constexpr kChooseFBConfigAtt[] = {
+ GLX_RENDER_TYPE, GLX_RGBA_BIT,
+ GLX_DOUBLEBUFFER, True,
+ GLX_STENCIL_SIZE, 8,
+ None
+ };
+ // For some reason glXChooseVisual takes a non-const pointer to the attributes.
+ int chooseVisualAtt[] = {
+ GLX_RGBA,
+ GLX_DOUBLEBUFFER,
+ GLX_STENCIL_SIZE, 8,
+ None
+ };
+ SkASSERT(nullptr == fVisualInfo);
+ if (fRequestedDisplayParams.fMSAASampleCount > 0) {
+ static const GLint kChooseFBConifgAttCnt = SK_ARRAY_COUNT(kChooseFBConfigAtt);
+ GLint msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 4];
+ memcpy(msaaChooseFBConfigAtt, kChooseFBConfigAtt, sizeof(kChooseFBConfigAtt));
+ SkASSERT(None == msaaChooseFBConfigAtt[kChooseFBConifgAttCnt - 1]);
+ msaaChooseFBConfigAtt[kChooseFBConifgAttCnt - 1] = GLX_SAMPLE_BUFFERS_ARB;
+ msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 0] = 1;
+ msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 1] = GLX_SAMPLES_ARB;
+ msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 2] = fRequestedDisplayParams.fMSAASampleCount;
+ msaaChooseFBConfigAtt[kChooseFBConifgAttCnt + 3] = None;
+ int n;
+ fFBConfig = glXChooseFBConfig(fDisplay, DefaultScreen(fDisplay), msaaChooseFBConfigAtt, &n);
+ if (n > 0) {
+ fVisualInfo = glXGetVisualFromFBConfig(fDisplay, *fFBConfig);
+ } else {
+ static const GLint kChooseVisualAttCnt = SK_ARRAY_COUNT(chooseVisualAtt);
+ GLint msaaChooseVisualAtt[kChooseVisualAttCnt + 4];
+ memcpy(msaaChooseVisualAtt, chooseVisualAtt, sizeof(chooseVisualAtt));
+ SkASSERT(None == msaaChooseVisualAtt[kChooseVisualAttCnt - 1]);
+ msaaChooseFBConfigAtt[kChooseVisualAttCnt - 1] = GLX_SAMPLE_BUFFERS_ARB;
+ msaaChooseFBConfigAtt[kChooseVisualAttCnt + 0] = 1;
+ msaaChooseFBConfigAtt[kChooseVisualAttCnt + 1] = GLX_SAMPLES_ARB;
+ msaaChooseFBConfigAtt[kChooseVisualAttCnt + 2] =
+ fRequestedDisplayParams.fMSAASampleCount;
+ msaaChooseFBConfigAtt[kChooseVisualAttCnt + 3] = None;
+ fVisualInfo = glXChooseVisual(display, DefaultScreen(display), msaaChooseVisualAtt);
+ fFBConfig = nullptr;
+ }
+ }
+ if (nullptr == fVisualInfo) {
+ int n;
+ fFBConfig = glXChooseFBConfig(fDisplay, DefaultScreen(fDisplay), kChooseFBConfigAtt, &n);
+ if (n > 0) {
+ fVisualInfo = glXGetVisualFromFBConfig(fDisplay, *fFBConfig);
+ } else {
+ fVisualInfo = glXChooseVisual(display, DefaultScreen(display), chooseVisualAtt);
+ fFBConfig = nullptr;
+ }
+ }
+
+ if (fVisualInfo) {
+ Colormap colorMap = XCreateColormap(display,
+ RootWindow(display, fVisualInfo->screen),
+ fVisualInfo->visual,
+ AllocNone);
+ XSetWindowAttributes swa;
+ swa.colormap = colorMap;
+ swa.event_mask = kEventMask;
+ fWindow = XCreateWindow(display,
+ RootWindow(display, fVisualInfo->screen),
+ 0, 0, // x, y
+ initialWidth, initialHeight,
+ 0, // border width
+ fVisualInfo->depth,
+ InputOutput,
+ fVisualInfo->visual,
+ CWEventMask | CWColormap,
+ &swa);
+ } else {
+ // Create a simple window instead. We will not be able to show GL
+ fWindow = XCreateSimpleWindow(display,
+ DefaultRootWindow(display),
+ 0, 0, // x, y
+ initialWidth, initialHeight,
+ 0, // border width
+ 0, // border value
+ 0); // background value
+ XSelectInput(display, fWindow, kEventMask);
+ }
+
+ if (!fWindow) {
+ return false;
+ }
+
+ fMSAASampleCount = fRequestedDisplayParams.fMSAASampleCount;
+
+ // set up to catch window delete message
+ fWmDeleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", False);
+ XSetWMProtocols(display, fWindow, &fWmDeleteMessage, 1);
+
+ // add to hashtable of windows
+ gWindowMap.add(this);
+
+ // init event variables
+ fPendingPaint = false;
+ fPendingResize = false;
+
+ return true;
+}
+
+void Window_unix::closeWindow() {
+ if (fDisplay) {
+ this->detach();
+ if (fGC) {
+ XFreeGC(fDisplay, fGC);
+ fGC = nullptr;
+ }
+ gWindowMap.remove(fWindow);
+ XDestroyWindow(fDisplay, fWindow);
+ fWindow = 0;
+ if (fFBConfig) {
+ XFree(fFBConfig);
+ fFBConfig = nullptr;
+ }
+ if (fVisualInfo) {
+ XFree(fVisualInfo);
+ fVisualInfo = nullptr;
+ }
+ fDisplay = nullptr;
+ }
+}
+
+static Window::Key get_key(KeySym keysym) {
+ static const struct {
+ KeySym fXK;
+ Window::Key fKey;
+ } gPair[] = {
+ { XK_BackSpace, Window::Key::kBack },
+ { XK_Clear, Window::Key::kBack },
+ { XK_Return, Window::Key::kOK },
+ { XK_Up, Window::Key::kUp },
+ { XK_Down, Window::Key::kDown },
+ { XK_Left, Window::Key::kLeft },
+ { XK_Right, Window::Key::kRight },
+ { XK_Tab, Window::Key::kTab },
+ { XK_Page_Up, Window::Key::kPageUp },
+ { XK_Page_Down, Window::Key::kPageDown },
+ { XK_Home, Window::Key::kHome },
+ { XK_End, Window::Key::kEnd },
+ { XK_Delete, Window::Key::kDelete },
+ { XK_Escape, Window::Key::kEscape },
+ { XK_Shift_L, Window::Key::kShift },
+ { XK_Shift_R, Window::Key::kShift },
+ { XK_Control_L, Window::Key::kCtrl },
+ { XK_Control_R, Window::Key::kCtrl },
+ { XK_Alt_L, Window::Key::kOption },
+ { XK_Alt_R, Window::Key::kOption },
+ { 'A', Window::Key::kA },
+ { 'C', Window::Key::kC },
+ { 'V', Window::Key::kV },
+ { 'X', Window::Key::kX },
+ { 'Y', Window::Key::kY },
+ { 'Z', Window::Key::kZ },
+ };
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
+ if (gPair[i].fXK == keysym) {
+ return gPair[i].fKey;
+ }
+ }
+ return Window::Key::kNONE;
+}
+
+static uint32_t get_modifiers(const XEvent& event) {
+ static const struct {
+ unsigned fXMask;
+ unsigned fSkMask;
+ } gModifiers[] = {
+ { ShiftMask, Window::kShift_ModifierKey },
+ { ControlMask, Window::kControl_ModifierKey },
+ { Mod1Mask, Window::kOption_ModifierKey },
+ };
+
+ auto modifiers = 0;
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gModifiers); ++i) {
+ if (event.xkey.state & gModifiers[i].fXMask) {
+ modifiers |= gModifiers[i].fSkMask;
+ }
+ }
+ return modifiers;
+}
+
+bool Window_unix::handleEvent(const XEvent& event) {
+ switch (event.type) {
+ case MapNotify:
+ if (!fGC) {
+ fGC = XCreateGC(fDisplay, fWindow, 0, nullptr);
+ }
+ break;
+
+ case ClientMessage:
+ if ((Atom)event.xclient.data.l[0] == fWmDeleteMessage &&
+ gWindowMap.count() == 1) {
+ return true;
+ }
+ break;
+
+ case ButtonPress:
+ switch (event.xbutton.button) {
+ case Button1:
+ this->onMouse(event.xbutton.x, event.xbutton.y,
+ Window::kDown_InputState, get_modifiers(event));
+ break;
+ case Button4:
+ this->onMouseWheel(1.0f, get_modifiers(event));
+ break;
+ case Button5:
+ this->onMouseWheel(-1.0f, get_modifiers(event));
+ break;
+ }
+ break;
+
+ case ButtonRelease:
+ if (event.xbutton.button == Button1) {
+ this->onMouse(event.xbutton.x, event.xbutton.y,
+ Window::kUp_InputState, get_modifiers(event));
+ }
+ break;
+
+ case MotionNotify:
+ this->onMouse(event.xmotion.x, event.xmotion.y,
+ Window::kMove_InputState, get_modifiers(event));
+ break;
+
+ case KeyPress: {
+ int shiftLevel = (event.xkey.state & ShiftMask) ? 1 : 0;
+ KeySym keysym = XkbKeycodeToKeysym(fDisplay, event.xkey.keycode, 0, shiftLevel);
+ Window::Key key = get_key(keysym);
+ if (key != Window::Key::kNONE) {
+ if (!this->onKey(key, Window::kDown_InputState, get_modifiers(event))) {
+ if (keysym == XK_Escape) {
+ return true;
+ }
+ }
+ }
+
+ long uni = keysym2ucs(keysym);
+ if (uni != -1) {
+ (void) this->onChar((SkUnichar) uni, get_modifiers(event));
+ }
+ } break;
+
+ case KeyRelease: {
+ int shiftLevel = (event.xkey.state & ShiftMask) ? 1 : 0;
+ KeySym keysym = XkbKeycodeToKeysym(fDisplay, event.xkey.keycode,
+ 0, shiftLevel);
+ Window::Key key = get_key(keysym);
+ (void) this->onKey(key, Window::kUp_InputState,
+ get_modifiers(event));
+ } break;
+
+
+ default:
+ // these events should be handled in the main event loop
+ SkASSERT(event.type != Expose && event.type != ConfigureNotify);
+ break;
+ }
+
+ return false;
+}
+
+void Window_unix::setTitle(const char* title) {
+ XTextProperty textproperty;
+ XStringListToTextProperty(const_cast<char**>(&title), 1, &textproperty);
+ XSetWMName(fDisplay, fWindow, &textproperty);
+}
+
+void Window_unix::show() {
+ XMapWindow(fDisplay, fWindow);
+}
+
+bool Window_unix::attach(BackendType attachType) {
+ this->initWindow(fDisplay);
+
+ window_context_factory::XlibWindowInfo winInfo;
+ winInfo.fDisplay = fDisplay;
+ winInfo.fWindow = fWindow;
+ winInfo.fFBConfig = fFBConfig;
+ winInfo.fVisualInfo = fVisualInfo;
+
+ XWindowAttributes attrs;
+ if (XGetWindowAttributes(fDisplay, fWindow, &attrs)) {
+ winInfo.fWidth = attrs.width;
+ winInfo.fHeight = attrs.height;
+ } else {
+ winInfo.fWidth = winInfo.fHeight = 0;
+ }
+
+ switch (attachType) {
+#ifdef SK_VULKAN
+ case kVulkan_BackendType:
+ fWindowContext = window_context_factory::NewVulkanForXlib(winInfo,
+ fRequestedDisplayParams);
+ break;
+#endif
+ case kNativeGL_BackendType:
+ fWindowContext = window_context_factory::NewGLForXlib(winInfo, fRequestedDisplayParams);
+ break;
+ case kRaster_BackendType:
+ fWindowContext = window_context_factory::NewRasterForXlib(winInfo,
+ fRequestedDisplayParams);
+ break;
+ }
+ this->onBackendCreated();
+
+ return (SkToBool(fWindowContext));
+}
+
+void Window_unix::onInval() {
+ XEvent event;
+ event.type = Expose;
+ event.xexpose.send_event = True;
+ event.xexpose.display = fDisplay;
+ event.xexpose.window = fWindow;
+ event.xexpose.x = 0;
+ event.xexpose.y = 0;
+ event.xexpose.width = this->width();
+ event.xexpose.height = this->height();
+ event.xexpose.count = 0;
+
+ XSendEvent(fDisplay, fWindow, False, 0, &event);
+}
+
+} // namespace sk_app
diff --git a/tools/sk_app/unix/Window_unix.h b/tools/sk_app/unix/Window_unix.h
new file mode 100644
index 0000000000..b59f502eb9
--- /dev/null
+++ b/tools/sk_app/unix/Window_unix.h
@@ -0,0 +1,95 @@
+/*
+* 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_unix_DEFINED
+#define Window_unix_DEFINED
+
+#include <X11/Xlib.h>
+#include <GL/glx.h>
+#include "../Window.h"
+#include "SkChecksum.h"
+#include "SkTDynamicHash.h"
+
+typedef Window XWindow;
+
+namespace sk_app {
+
+class Window_unix : public Window {
+public:
+ Window_unix() : Window()
+ , fDisplay(nullptr)
+ , fWindow(0)
+ , fGC(nullptr)
+ , fFBConfig(nullptr)
+ , fVisualInfo(nullptr)
+ , fMSAASampleCount(0) {}
+ ~Window_unix() override { this->closeWindow(); }
+
+ bool initWindow(Display* display);
+
+ void setTitle(const char*) override;
+ void show() override;
+
+ bool attach(BackendType) override;
+
+ void onInval() override;
+
+ bool handleEvent(const XEvent& event);
+
+ static const XWindow& GetKey(const Window_unix& w) {
+ return w.fWindow;
+ }
+
+ static uint32_t Hash(const XWindow& w) {
+ return SkChecksum::Mix(w);
+ }
+
+ static SkTDynamicHash<Window_unix, XWindow> gWindowMap;
+
+ void markPendingPaint() { fPendingPaint = true; }
+ void finishPaint() {
+ if (fPendingPaint) {
+ this->onPaint();
+ fPendingPaint = false;
+ }
+ }
+
+ void markPendingResize(int width, int height) {
+ if (width != this->width() || height != this->height()){
+ fPendingResize = true;
+ fPendingWidth = width;
+ fPendingHeight = height;
+ }
+ }
+ void finishResize() {
+ if (fPendingResize) {
+ this->onResize(fPendingWidth, fPendingHeight);
+ fPendingResize = false;
+ }
+ }
+
+private:
+ void closeWindow();
+
+ Display* fDisplay;
+ XWindow fWindow;
+ GC fGC;
+ GLXFBConfig* fFBConfig;
+ XVisualInfo* fVisualInfo;
+ int fMSAASampleCount;
+
+ Atom fWmDeleteMessage;
+
+ bool fPendingPaint;
+ int fPendingWidth;
+ int fPendingHeight;
+ bool fPendingResize;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/unix/main_unix.cpp b/tools/sk_app/unix/main_unix.cpp
new file mode 100644
index 0000000000..4d9a64d6b6
--- /dev/null
+++ b/tools/sk_app/unix/main_unix.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 "SkTypes.h"
+#include "SkTHash.h"
+#include "Timer.h"
+#include "Window_unix.h"
+#include "../Application.h"
+
+using sk_app::Application;
+
+void finishWindow(sk_app::Window_unix* win) {
+ win->finishResize();
+ win->finishPaint();
+}
+
+int main(int argc, char**argv) {
+
+ Display* display = XOpenDisplay(nullptr);
+
+ Application* app = Application::Create(argc, argv, (void*)display);
+
+ // Get the file descriptor for the X display
+ int x11_fd = ConnectionNumber(display);
+ int count = x11_fd + 1;
+
+ SkTHashSet<sk_app::Window_unix*> pendingWindows;
+ bool done = false;
+ while (!done) {
+ // Create a file description set containing x11_fd
+ fd_set in_fds;
+ FD_ZERO(&in_fds);
+ FD_SET(x11_fd, &in_fds);
+
+ // Set a sleep timer
+ struct timeval tv;
+ tv.tv_usec = 100;
+ tv.tv_sec = 0;
+
+ while (!XPending(display)) {
+ // Wait for an event on the file descriptor or for timer expiration
+ (void) select(count, &in_fds, nullptr, nullptr, &tv);
+ }
+
+ // Handle XEvents (if any) and flush the input
+ int count = XPending(display);
+ while (count-- && !done) {
+ XEvent event;
+ XNextEvent(display, &event);
+
+ sk_app::Window_unix* win = sk_app::Window_unix::gWindowMap.find(event.xany.window);
+ if (!win) {
+ continue;
+ }
+
+ // paint and resize events get collapsed
+ switch (event.type) {
+ case Expose:
+ win->markPendingPaint();
+ pendingWindows.add(win);
+ break;
+ case ConfigureNotify:
+ win->markPendingResize(event.xconfigurerequest.width,
+ event.xconfigurerequest.height);
+ pendingWindows.add(win);
+ break;
+ default:
+ if (win->handleEvent(event)) {
+ done = true;
+ }
+ break;
+ }
+ }
+
+ pendingWindows.foreach(finishWindow);
+ if (pendingWindows.count() > 0) {
+ app->onIdle();
+ }
+ pendingWindows.reset();
+
+ XFlush(display);
+ }
+
+ delete app;
+
+ XCloseDisplay(display);
+
+ return 0;
+}
diff --git a/tools/sk_app/win/ANGLEWindowContext_win.cpp b/tools/sk_app/win/ANGLEWindowContext_win.cpp
new file mode 100644
index 0000000000..bfdff5c6f4
--- /dev/null
+++ b/tools/sk_app/win/ANGLEWindowContext_win.cpp
@@ -0,0 +1,177 @@
+
+/*
+ * 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 <EGL/egl.h>
+#include <EGL/eglext.h>
+#include "../GLWindowContext.h"
+#include "WindowContextFactory_win.h"
+#include "gl/GrGLAssembleInterface.h"
+#include "gl/GrGLDefines.h"
+
+using sk_app::GLWindowContext;
+using sk_app::DisplayParams;
+
+namespace {
+
+EGLDisplay get_angle_egl_display(HDC hdc) {
+ PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT;
+ eglGetPlatformDisplayEXT =
+ (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
+
+ // We expect ANGLE to support this extension
+ if (!eglGetPlatformDisplayEXT) {
+ return EGL_NO_DISPLAY;
+ }
+
+ // We currently only support D3D11 ANGLE.
+ static constexpr EGLint kType = EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE;
+ static constexpr EGLint attribs[] = {EGL_PLATFORM_ANGLE_TYPE_ANGLE, kType, EGL_NONE};
+ return eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, hdc, attribs);
+}
+
+class ANGLEGLWindowContext_win : public GLWindowContext {
+public:
+ ANGLEGLWindowContext_win(HWND, const DisplayParams&);
+ ~ANGLEGLWindowContext_win() override;
+
+protected:
+ void onSwapBuffers() override;
+
+ sk_sp<const GrGLInterface> onInitializeContext() override;
+ void onDestroyContext() override;
+
+private:
+ HWND fHWND;
+ EGLDisplay fDisplay = EGL_NO_DISPLAY;
+ EGLContext fContext = EGL_NO_CONTEXT;
+ EGLSurface fSurface = EGL_NO_SURFACE;
+
+ typedef GLWindowContext INHERITED;
+};
+
+ANGLEGLWindowContext_win::ANGLEGLWindowContext_win(HWND wnd, const DisplayParams& params)
+ : INHERITED(params), fHWND(wnd) {
+ this->initializeContext();
+}
+
+ANGLEGLWindowContext_win::~ANGLEGLWindowContext_win() { this->destroyContext(); }
+
+sk_sp<const GrGLInterface> ANGLEGLWindowContext_win::onInitializeContext() {
+ HDC dc = GetDC(fHWND);
+ fDisplay = get_angle_egl_display(dc);
+ if (EGL_NO_DISPLAY == fDisplay) {
+ return nullptr;
+ }
+
+ EGLint majorVersion;
+ EGLint minorVersion;
+ if (!eglInitialize(fDisplay, &majorVersion, &minorVersion)) {
+ SkDebugf("Could not initialize display!\n");
+ return nullptr;
+ }
+ EGLint numConfigs;
+ fSampleCount = this->getDisplayParams().fMSAASampleCount;
+ const int sampleBuffers = fSampleCount > 0 ? 1 : 0;
+ const EGLint configAttribs[] = {EGL_RENDERABLE_TYPE,
+ // We currently only support ES3.
+ EGL_OPENGL_ES3_BIT,
+ EGL_RED_SIZE,
+ 8,
+ EGL_GREEN_SIZE,
+ 8,
+ EGL_BLUE_SIZE,
+ 8,
+ EGL_ALPHA_SIZE,
+ 8,
+ EGL_SAMPLE_BUFFERS,
+ sampleBuffers,
+ EGL_SAMPLES,
+ fSampleCount,
+ EGL_NONE};
+
+ EGLConfig surfaceConfig;
+ if (!eglChooseConfig(fDisplay, configAttribs, &surfaceConfig, 1, &numConfigs)) {
+ SkDebugf("Could not create choose config!\n");
+ return nullptr;
+ }
+ // We currently only support ES3.
+ const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
+ fContext = eglCreateContext(fDisplay, surfaceConfig, nullptr, contextAttribs);
+ if (EGL_NO_CONTEXT == fContext) {
+ SkDebugf("Could not create context!\n");
+ return nullptr;
+ }
+ fSurface = eglCreateWindowSurface(fDisplay, surfaceConfig, fHWND, nullptr);
+ if (EGL_NO_SURFACE == fSurface) {
+ SkDebugf("Could not create surface!\n");
+ return nullptr;
+ }
+ if (!eglMakeCurrent(fDisplay, fSurface, fSurface, fContext)) {
+ SkDebugf("Could not make contxt current!\n");
+ return nullptr;
+ }
+
+ sk_sp<const GrGLInterface> interface(GrGLAssembleInterface(
+ nullptr,
+ [](void* ctx, const char name[]) -> GrGLFuncPtr { return eglGetProcAddress(name); }));
+ if (interface) {
+ interface->fFunctions.fClearStencil(0);
+ interface->fFunctions.fClearColor(0, 0, 0, 0);
+ interface->fFunctions.fStencilMask(0xffffffff);
+ interface->fFunctions.fClear(GR_GL_STENCIL_BUFFER_BIT | GR_GL_COLOR_BUFFER_BIT);
+
+ // use DescribePixelFormat to get the stencil depth.
+ int pixelFormat = GetPixelFormat(dc);
+ PIXELFORMATDESCRIPTOR pfd;
+ DescribePixelFormat(dc, pixelFormat, sizeof(pfd), &pfd);
+ fStencilBits = pfd.cStencilBits;
+
+ RECT rect;
+ GetClientRect(fHWND, &rect);
+ fWidth = rect.right - rect.left;
+ fHeight = rect.bottom - rect.top;
+ interface->fFunctions.fViewport(0, 0, fWidth, fHeight);
+ }
+ return interface;
+}
+
+void ANGLEGLWindowContext_win::onDestroyContext() {
+ eglMakeCurrent(fDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+ if (EGL_NO_CONTEXT != fContext) {
+ eglDestroyContext(fDisplay, fContext);
+ }
+ if (EGL_NO_SURFACE != fSurface) {
+ eglDestroySurface(fDisplay, fSurface);
+ }
+ if (EGL_NO_DISPLAY != fDisplay) {
+ eglTerminate(fDisplay);
+ }
+}
+
+void ANGLEGLWindowContext_win::onSwapBuffers() {
+ if (!eglSwapBuffers(fDisplay, fSurface)) {
+ SkDebugf("Could not complete eglSwapBuffers.\n");
+ }
+}
+
+} // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewANGLEForWin(HWND wnd, const DisplayParams& params) {
+ ANGLEGLWindowContext_win* ctx = new ANGLEGLWindowContext_win(wnd, params);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+} // namespace sk_app
diff --git a/tools/sk_app/win/GLWindowContext_win.cpp b/tools/sk_app/win/GLWindowContext_win.cpp
new file mode 100644
index 0000000000..17a6b32962
--- /dev/null
+++ b/tools/sk_app/win/GLWindowContext_win.cpp
@@ -0,0 +1,141 @@
+
+/*
+ * 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 <Windows.h>
+#include <GL/gl.h>
+#include "../GLWindowContext.h"
+#include "GrGLInterface.h"
+#include "WindowContextFactory_win.h"
+#include "win/SkWGL.h"
+
+using sk_app::GLWindowContext;
+using sk_app::DisplayParams;
+
+namespace {
+
+class GLWindowContext_win : public GLWindowContext {
+public:
+ GLWindowContext_win(HWND, const DisplayParams&);
+ ~GLWindowContext_win() override;
+
+protected:
+ void onSwapBuffers() override;
+
+ sk_sp<const GrGLInterface> onInitializeContext() override;
+ void onDestroyContext() override;
+
+private:
+ HWND fHWND;
+ HGLRC fHGLRC;
+
+ typedef GLWindowContext INHERITED;
+};
+
+GLWindowContext_win::GLWindowContext_win(HWND wnd, const DisplayParams& params)
+ : INHERITED(params)
+ , fHWND(wnd)
+ , fHGLRC(NULL) {
+
+ // any config code here (particularly for msaa)?
+
+ this->initializeContext();
+}
+
+GLWindowContext_win::~GLWindowContext_win() {
+ this->destroyContext();
+}
+
+sk_sp<const GrGLInterface> GLWindowContext_win::onInitializeContext() {
+ HDC dc = GetDC(fHWND);
+
+ fHGLRC = SkCreateWGLContext(dc, fDisplayParams.fMSAASampleCount, false /* deepColor */,
+ kGLPreferCompatibilityProfile_SkWGLContextRequest);
+ if (NULL == fHGLRC) {
+ return nullptr;
+ }
+
+ // Look to see if RenderDoc is attached. If so, re-create the context with a core profile
+ if (wglMakeCurrent(dc, fHGLRC)) {
+ const GrGLInterface* glInterface = GrGLCreateNativeInterface();
+ bool renderDocAttached = glInterface->hasExtension("GL_EXT_debug_tool");
+ SkSafeUnref(glInterface);
+ if (renderDocAttached) {
+ wglDeleteContext(fHGLRC);
+ fHGLRC = SkCreateWGLContext(dc, fDisplayParams.fMSAASampleCount, false /* deepColor */,
+ kGLPreferCoreProfile_SkWGLContextRequest);
+ if (NULL == fHGLRC) {
+ return nullptr;
+ }
+ }
+ }
+
+ if (wglMakeCurrent(dc, fHGLRC)) {
+ glClearStencil(0);
+ glClearColor(0, 0, 0, 0);
+ glStencilMask(0xffffffff);
+ glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+ // use DescribePixelFormat to get the stencil and color bit depth.
+ int pixelFormat = GetPixelFormat(dc);
+ PIXELFORMATDESCRIPTOR pfd;
+ DescribePixelFormat(dc, pixelFormat, sizeof(pfd), &pfd);
+ fStencilBits = pfd.cStencilBits;
+
+ // Get sample count if the MSAA WGL extension is present
+ SkWGLExtensions extensions;
+ if (extensions.hasExtension(dc, "WGL_ARB_multisample")) {
+ static const int kSampleCountAttr = SK_WGL_SAMPLES;
+ extensions.getPixelFormatAttribiv(dc,
+ pixelFormat,
+ 0,
+ 1,
+ &kSampleCountAttr,
+ &fSampleCount);
+ } else {
+ fSampleCount = 0;
+ }
+
+ RECT rect;
+ GetClientRect(fHWND, &rect);
+ fWidth = rect.right - rect.left;
+ fHeight = rect.bottom - rect.top;
+ glViewport(0, 0, fWidth, fHeight);
+ }
+ return sk_sp<const GrGLInterface>(GrGLCreateNativeInterface());
+}
+
+
+void GLWindowContext_win::onDestroyContext() {
+ wglDeleteContext(fHGLRC);
+ fHGLRC = NULL;
+}
+
+
+void GLWindowContext_win::onSwapBuffers() {
+ HDC dc = GetDC((HWND)fHWND);
+ SwapBuffers(dc);
+ ReleaseDC((HWND)fHWND, dc);
+}
+
+
+} // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewGLForWin(HWND wnd, const DisplayParams& params) {
+ GLWindowContext_win* ctx = new GLWindowContext_win(wnd, params);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+} // namespace sk_app
diff --git a/tools/sk_app/win/RasterWindowContext_win.cpp b/tools/sk_app/win/RasterWindowContext_win.cpp
new file mode 100644
index 0000000000..85bb65e674
--- /dev/null
+++ b/tools/sk_app/win/RasterWindowContext_win.cpp
@@ -0,0 +1,100 @@
+/*
+ * 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 "../RasterWindowContext.h"
+#include "SkAutoMalloc.h"
+#include "SkSurface.h"
+#include "WindowContextFactory_win.h"
+
+#include <Windows.h>
+
+using sk_app::RasterWindowContext;
+using sk_app::DisplayParams;
+
+namespace {
+
+class RasterWindowContext_win : public RasterWindowContext {
+public:
+ RasterWindowContext_win(HWND, const DisplayParams&);
+
+ sk_sp<SkSurface> getBackbufferSurface() override;
+ void swapBuffers() override;
+ bool isValid() override { return SkToBool(fWnd); }
+ void resize(int w, int h) override;
+ void setDisplayParams(const DisplayParams& params) override;
+
+protected:
+ SkAutoMalloc fSurfaceMemory;
+ sk_sp<SkSurface> fBackbufferSurface;
+ HWND fWnd;
+
+private:
+ typedef RasterWindowContext INHERITED;
+};
+
+RasterWindowContext_win::RasterWindowContext_win(HWND wnd, const DisplayParams& params)
+ : INHERITED(params)
+ , fWnd(wnd) {
+ RECT rect;
+ GetWindowRect(wnd, &rect);
+ this->resize(rect.right - rect.left, rect.bottom - rect.top);
+}
+
+void RasterWindowContext_win::setDisplayParams(const DisplayParams& params) {
+ fDisplayParams = params;
+ RECT rect;
+ GetWindowRect(fWnd, &rect);
+ this->resize(rect.right - rect.left, rect.bottom - rect.top);
+}
+
+void RasterWindowContext_win::resize(int w, int h) {
+ fWidth = w;
+ fHeight = h;
+ fBackbufferSurface.reset();
+ const size_t bmpSize = sizeof(BITMAPINFOHEADER) + w * h * sizeof(uint32_t);
+ fSurfaceMemory.reset(bmpSize);
+ BITMAPINFO* bmpInfo = reinterpret_cast<BITMAPINFO*>(fSurfaceMemory.get());
+ ZeroMemory(bmpInfo, sizeof(BITMAPINFO));
+ bmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bmpInfo->bmiHeader.biWidth = w;
+ bmpInfo->bmiHeader.biHeight = -h; // negative means top-down bitmap. Skia draws top-down.
+ bmpInfo->bmiHeader.biPlanes = 1;
+ bmpInfo->bmiHeader.biBitCount = 32;
+ bmpInfo->bmiHeader.biCompression = BI_RGB;
+ void* pixels = bmpInfo->bmiColors;
+
+ SkImageInfo info = SkImageInfo::Make(w, h, fDisplayParams.fColorType, kPremul_SkAlphaType,
+ fDisplayParams.fColorSpace);
+ fBackbufferSurface = SkSurface::MakeRasterDirect(info, pixels, sizeof(uint32_t) * w);
+}
+
+sk_sp<SkSurface> RasterWindowContext_win::getBackbufferSurface() { return fBackbufferSurface; }
+
+void RasterWindowContext_win::swapBuffers() {
+ BITMAPINFO* bmpInfo = reinterpret_cast<BITMAPINFO*>(fSurfaceMemory.get());
+ HDC dc = GetDC(fWnd);
+ StretchDIBits(dc, 0, 0, fWidth, fHeight, 0, 0, fWidth, fHeight, bmpInfo->bmiColors, bmpInfo,
+ DIB_RGB_COLORS, SRCCOPY);
+ ReleaseDC(fWnd, dc);
+}
+
+} // anonymous namespace
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewRasterForWin(HWND wnd, const DisplayParams& params) {
+ WindowContext* ctx = new RasterWindowContext_win(wnd, params);
+ if (!ctx->isValid()) {
+ delete ctx;
+ ctx = nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+} // namespace sk_app
diff --git a/tools/sk_app/win/VulkanWindowContext_win.cpp b/tools/sk_app/win/VulkanWindowContext_win.cpp
new file mode 100644
index 0000000000..16c527cba0
--- /dev/null
+++ b/tools/sk_app/win/VulkanWindowContext_win.cpp
@@ -0,0 +1,79 @@
+
+/*
+ * 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 "WindowContextFactory_win.h"
+
+#include "../VulkanWindowContext.h"
+#include "Window_win.h"
+
+#include "vk/GrVkInterface.h"
+#include "vk/GrVkUtil.h"
+
+#include "vk/VkTestUtils.h"
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewVulkanForWin(HWND hwnd, const DisplayParams& params) {
+ PFN_vkGetInstanceProcAddr instProc;
+ PFN_vkGetDeviceProcAddr devProc;
+ if (!sk_gpu_test::LoadVkLibraryAndGetProcAddrFuncs(&instProc, &devProc)) {
+ return nullptr;
+ }
+
+ auto createVkSurface = [hwnd, instProc] (VkInstance instance) -> VkSurfaceKHR {
+ static PFN_vkCreateWin32SurfaceKHR createWin32SurfaceKHR = nullptr;
+ if (!createWin32SurfaceKHR) {
+ createWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR)
+ instProc(instance, "vkCreateWin32SurfaceKHR");
+ }
+ HINSTANCE hinstance = GetModuleHandle(0);
+ 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 = hinstance;
+ surfaceCreateInfo.hwnd = hwnd;
+
+ VkResult res = createWin32SurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface);
+ if (VK_SUCCESS != res) {
+ return VK_NULL_HANDLE;
+ }
+
+ return surface;
+ };
+
+ auto canPresent = [instProc] (VkInstance instance, VkPhysicalDevice physDev,
+ uint32_t queueFamilyIndex) {
+ static PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR
+ getPhysicalDeviceWin32PresentationSupportKHR = nullptr;
+ if (!getPhysicalDeviceWin32PresentationSupportKHR) {
+ getPhysicalDeviceWin32PresentationSupportKHR =
+ (PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)
+ instProc(instance, "vkGetPhysicalDeviceWin32PresentationSupportKHR");
+ }
+
+ VkBool32 check = getPhysicalDeviceWin32PresentationSupportKHR(physDev, queueFamilyIndex);
+ return (VK_FALSE != check);
+ };
+
+ WindowContext* ctx = new VulkanWindowContext(params, createVkSurface, canPresent,
+ instProc, devProc);
+ if (!ctx->isValid()) {
+ delete ctx;
+ return nullptr;
+ }
+ return ctx;
+}
+
+} // namespace window_context_factory
+} // namespace sk_app
diff --git a/tools/sk_app/win/WindowContextFactory_win.h b/tools/sk_app/win/WindowContextFactory_win.h
new file mode 100644
index 0000000000..959b529f49
--- /dev/null
+++ b/tools/sk_app/win/WindowContextFactory_win.h
@@ -0,0 +1,33 @@
+
+/*
+ * 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 WindowContextFactory_win_DEFINED
+#define WindowContextFactory_win_DEFINED
+
+#include <Windows.h>
+
+namespace sk_app {
+
+class WindowContext;
+struct DisplayParams;
+
+namespace window_context_factory {
+
+WindowContext* NewVulkanForWin(HWND, const DisplayParams&);
+
+WindowContext* NewGLForWin(HWND, const DisplayParams&);
+
+WindowContext* NewANGLEForWin(HWND, const DisplayParams&);
+
+WindowContext* NewRasterForWin(HWND, const DisplayParams&);
+
+} // namespace window_context_factory
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/win/Window_win.cpp b/tools/sk_app/win/Window_win.cpp
new file mode 100644
index 0000000000..10db0ec675
--- /dev/null
+++ b/tools/sk_app/win/Window_win.cpp
@@ -0,0 +1,393 @@
+/*
+* 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 "../WindowContext.h"
+#include "WindowContextFactory_win.h"
+#ifdef SK_VULKAN
+#include "../VulkanWindowContext.h"
+#endif
+
+namespace sk_app {
+
+static int gWindowX = CW_USEDEFAULT;
+static int gWindowY = 0;
+static int gWindowWidth = CW_USEDEFAULT;
+static int gWindowHeight = 0;
+
+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;
+}
+
+void Window_win::closeWindow() {
+ RECT r;
+ if (GetWindowRect(fHWnd, &r)) {
+ gWindowX = r.left;
+ gWindowY = r.top;
+ gWindowWidth = r.right - r.left;
+ gWindowHeight = r.bottom - r.top;
+ }
+ DestroyWindow(fHWnd);
+}
+
+Window_win::~Window_win() {
+ this->closeWindow();
+}
+
+LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
+
+
+bool Window_win::init(HINSTANCE hInstance) {
+ fHInstance = hInstance ? hInstance : GetModuleHandle(nullptr);
+
+ // The main window class name
+ static const TCHAR gSZWindowClass[] = _T("SkiaApp");
+
+ static WNDCLASSEX wcex;
+ static bool wcexInit = false;
+ if (!wcexInit) {
+ 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;
+ }
+ wcexInit = true;
+ }
+
+ /*
+ 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,
+ gWindowX, gWindowY, gWindowWidth, gWindowHeight,
+ nullptr, nullptr, fHInstance, nullptr);
+ if (!fHWnd)
+ {
+ return false;
+ }
+
+ SetWindowLongPtr(fHWnd, GWLP_USERDATA, (LONG_PTR)this);
+ RegisterTouchWindow(fHWnd, 0);
+
+ return true;
+}
+
+static Window::Key get_key(WPARAM vk) {
+ static const struct {
+ WPARAM fVK;
+ Window::Key fKey;
+ } gPair[] = {
+ { VK_BACK, Window::Key::kBack },
+ { VK_CLEAR, Window::Key::kBack },
+ { VK_RETURN, Window::Key::kOK },
+ { VK_UP, Window::Key::kUp },
+ { VK_DOWN, Window::Key::kDown },
+ { VK_LEFT, Window::Key::kLeft },
+ { VK_RIGHT, Window::Key::kRight },
+ { VK_TAB, Window::Key::kTab },
+ { VK_PRIOR, Window::Key::kPageUp },
+ { VK_NEXT, Window::Key::kPageDown },
+ { VK_HOME, Window::Key::kHome },
+ { VK_END, Window::Key::kEnd },
+ { VK_DELETE, Window::Key::kDelete },
+ { VK_ESCAPE, Window::Key::kEscape },
+ { VK_SHIFT, Window::Key::kShift },
+ { VK_CONTROL, Window::Key::kCtrl },
+ { VK_MENU, Window::Key::kOption },
+ { 'A', Window::Key::kA },
+ { 'C', Window::Key::kC },
+ { 'V', Window::Key::kV },
+ { 'X', Window::Key::kX },
+ { 'Y', Window::Key::kY },
+ { 'Z', Window::Key::kZ },
+ };
+ for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
+ if (gPair[i].fVK == vk) {
+ return gPair[i].fKey;
+ }
+ }
+ return Window::Key::kNONE;
+}
+
+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:
+ case WM_MOUSEWHEEL:
+ if (wParam & MK_CONTROL) {
+ modifiers |= Window::kControl_ModifierKey;
+ }
+ if (wParam & MK_SHIFT) {
+ modifiers |= Window::kShift_ModifierKey;
+ }
+ break;
+ }
+
+ 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:
+ 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: {
+ 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;
+
+ case WM_MOUSEWHEEL:
+ eventHandled = window->onMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1.0f : -1.0f,
+ get_modifiers(message, wParam, lParam));
+ break;
+
+ case WM_TOUCH: {
+ uint16_t numInputs = LOWORD(wParam);
+ std::unique_ptr<TOUCHINPUT[]> inputs(new TOUCHINPUT[numInputs]);
+ if (GetTouchInputInfo((HTOUCHINPUT)lParam, numInputs, inputs.get(),
+ sizeof(TOUCHINPUT))) {
+ RECT rect;
+ GetClientRect(hWnd, &rect);
+ for (uint16_t i = 0; i < numInputs; ++i) {
+ TOUCHINPUT ti = inputs[i];
+ Window::InputState state;
+ if (ti.dwFlags & TOUCHEVENTF_DOWN) {
+ state = Window::kDown_InputState;
+ } else if (ti.dwFlags & TOUCHEVENTF_MOVE) {
+ state = Window::kMove_InputState;
+ } else if (ti.dwFlags & TOUCHEVENTF_UP) {
+ state = Window::kUp_InputState;
+ } else {
+ continue;
+ }
+ // TOUCHINPUT coordinates are in 100ths of pixels
+ // Adjust for that, and make them window relative
+ LONG tx = (ti.x / 100) - rect.left;
+ LONG ty = (ti.y / 100) - rect.top;
+ eventHandled = window->onTouch(ti.dwID, state, tx, ty) || eventHandled;
+ }
+ }
+ } 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) {
+ fBackend = attachType;
+
+ switch (attachType) {
+ case kNativeGL_BackendType:
+ fWindowContext = window_context_factory::NewGLForWin(fHWnd, fRequestedDisplayParams);
+ break;
+#if SK_ANGLE
+ case kANGLE_BackendType:
+ fWindowContext = window_context_factory::NewANGLEForWin(fHWnd, fRequestedDisplayParams);
+ break;
+#endif
+ case kRaster_BackendType:
+ fWindowContext = window_context_factory::NewRasterForWin(fHWnd,
+ fRequestedDisplayParams);
+ break;
+#ifdef SK_VULKAN
+ case kVulkan_BackendType:
+ fWindowContext = window_context_factory::NewVulkanForWin(fHWnd,
+ fRequestedDisplayParams);
+ break;
+#endif
+ }
+ this->onBackendCreated();
+
+ return (SkToBool(fWindowContext));
+}
+
+void Window_win::onInval() {
+ InvalidateRect(fHWnd, nullptr, false);
+}
+
+void Window_win::setRequestedDisplayParams(const DisplayParams& params, bool allowReattach) {
+ // GL on Windows doesn't let us change MSAA after the window is created
+ if (params.fMSAASampleCount != this->getRequestedDisplayParams().fMSAASampleCount
+ && allowReattach) {
+ // Need to change these early, so attach() creates the window context correctly
+ fRequestedDisplayParams = params;
+
+ delete fWindowContext;
+ this->closeWindow();
+ this->init(fHInstance);
+ this->attach(fBackend);
+ }
+
+ INHERITED::setRequestedDisplayParams(params, allowReattach);
+}
+
+} // namespace sk_app
diff --git a/tools/sk_app/win/Window_win.h b/tools/sk_app/win/Window_win.h
new file mode 100644
index 0000000000..139ab874c6
--- /dev/null
+++ b/tools/sk_app/win/Window_win.h
@@ -0,0 +1,44 @@
+/*
+* 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) override;
+
+ void onInval() override;
+
+ void setRequestedDisplayParams(const DisplayParams&, bool allowReattach) override;
+
+private:
+ void closeWindow();
+
+ HINSTANCE fHInstance;
+ HWND fHWnd;
+ BackendType fBackend;
+
+ typedef Window INHERITED;
+};
+
+} // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/win/main_win.cpp b/tools/sk_app/win/main_win.cpp
new file mode 100644
index 0000000000..4800258973
--- /dev/null
+++ b/tools/sk_app/win/main_win.cpp
@@ -0,0 +1,80 @@
+/*
+* 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
+}
+
+// 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);
+ } else {
+ app->onIdle();
+ }
+ }
+
+ delete app;
+
+ return (int)msg.wParam;
+}