diff options
author | Brian Osman <brianosman@google.com> | 2017-11-21 13:18:02 -0500 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2017-11-21 18:37:19 +0000 |
commit | eff04b5ec287e0fee0d44207c10d2d11f7eade8a (patch) | |
tree | ea2cf00ea329c81611536aaa9a9f1eca47c67e9a /tools/sk_app | |
parent | 2aa09dbe8aced37aa6bb285e62df45deb0e81650 (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')
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; +} |