summaryrefslogtreecommitdiffstats
path: root/external/skia/share-grcontext.patch.1
diff options
context:
space:
mode:
Diffstat (limited to 'external/skia/share-grcontext.patch.1')
-rw-r--r--external/skia/share-grcontext.patch.1872
1 files changed, 872 insertions, 0 deletions
diff --git a/external/skia/share-grcontext.patch.1 b/external/skia/share-grcontext.patch.1
new file mode 100644
index 000000000..2a957c284
--- /dev/null
+++ b/external/skia/share-grcontext.patch.1
@@ -0,0 +1,872 @@
+diff --git a/tools/sk_app/MetalWindowContext.h b/tools/sk_app/MetalWindowContext.h
+index 106d366415..08dc19b5c8 100644
+--- a/tools/sk_app/MetalWindowContext.h
++++ b/tools/sk_app/MetalWindowContext.h
+@@ -14,13 +14,18 @@
+
+ #include "tools/sk_app/WindowContext.h"
+
++#ifdef __OBJC__
+ #import <Metal/Metal.h>
+ #import <QuartzCore/CAMetalLayer.h>
++#endif
+
+ namespace sk_app {
+
++#ifdef __OBJC__
+ class MetalWindowContext : public WindowContext {
+ public:
++ static GrDirectContext* getSharedGrDirectContext() { return fGlobalShared ? fGlobalShared->fContext.get() : nullptr; }
++
+ sk_sp<SkSurface> getBackbufferSurface() override;
+
+ bool isValid() override { return fValid; }
+@@ -46,16 +51,34 @@ protected:
+ void destroyContext();
+ virtual void onDestroyContext() = 0;
+
++ static void checkDestroyShared();
++
+ bool fValid;
++
++ // We need to use just one GrDirectContext, so share all the relevant data.
++ struct Shared : public SkRefCnt
++ {
+ sk_cfp<id<MTLDevice>> fDevice;
+ sk_cfp<id<MTLCommandQueue>> fQueue;
+- CAMetalLayer* fMetalLayer;
+- GrMTLHandle fDrawableHandle;
+ #if GR_METAL_SDK_VERSION >= 230
+ // wrapping this in sk_cfp throws up an availability warning, so we'll track lifetime manually
+ id<MTLBinaryArchive> fPipelineArchive SK_API_AVAILABLE(macos(11.0), ios(14.0));
+ #endif
++
++ sk_sp<GrDirectContext> fContext;
++ };
++
++ sk_sp<Shared> fShared;
++
++ static sk_sp<Shared> fGlobalShared;
++
++ CAMetalLayer* fMetalLayer;
++ GrMTLHandle fDrawableHandle;
+ };
++#endif // __OBJC__
++
++// Access function when header is used from C++ code that wouldn't handle ObjC++ headers.
++extern "C" SK_API GrDirectContext* getMetalSharedGrDirectContext();
+
+ } // namespace sk_app
+
+diff --git a/tools/sk_app/MetalWindowContext.mm b/tools/sk_app/MetalWindowContext.mm
+index d972e321a6..9f576944b7 100644
+--- a/tools/sk_app/MetalWindowContext.mm
++++ b/tools/sk_app/MetalWindowContext.mm
+@@ -37,24 +37,30 @@ NSURL* MetalWindowContext::CacheURL() {
+ }
+
+ void MetalWindowContext::initializeContext() {
++ fShared = fGlobalShared;
++ if( !fShared )
++ {
++ // TODO do we need a mutex?
++
++ fGlobalShared = sk_make_sp<Shared>();
++ Shared* d = fGlobalShared.get(); // shorter variable name
++
+ SkASSERT(!fContext);
+
+- fDevice.reset(MTLCreateSystemDefaultDevice());
+- fQueue.reset([*fDevice newCommandQueue]);
++ d->fDevice.reset(MTLCreateSystemDefaultDevice());
++ d->fQueue.reset([*d->fDevice newCommandQueue]);
+
+ if (fDisplayParams.fMSAASampleCount > 1) {
+ if (@available(macOS 10.11, iOS 9.0, *)) {
+- if (![*fDevice supportsTextureSampleCount:fDisplayParams.fMSAASampleCount]) {
++ if (![*d->fDevice supportsTextureSampleCount:fDisplayParams.fMSAASampleCount]) {
++ fGlobalShared.reset();
+ return;
+ }
+ } else {
++ fGlobalShared.reset();
+ return;
+ }
+ }
+- fSampleCount = fDisplayParams.fMSAASampleCount;
+- fStencilBits = 8;
+-
+- fValid = this->onInitializeContext();
+
+ #if GR_METAL_SDK_VERSION >= 230
+ if (fDisplayParams.fEnableBinaryArchive) {
+@@ -62,11 +68,11 @@ void MetalWindowContext::initializeContext() {
+ sk_cfp<MTLBinaryArchiveDescriptor*> desc([MTLBinaryArchiveDescriptor new]);
+ (*desc).url = CacheURL(); // try to load
+ NSError* error;
+- fPipelineArchive = [*fDevice newBinaryArchiveWithDescriptor:*desc error:&error];
+- if (!fPipelineArchive) {
++ d->fPipelineArchive = [*d->fDevice newBinaryArchiveWithDescriptor:*desc error:&error];
++ if (!d->fPipelineArchive) {
+ (*desc).url = nil; // create new
+- fPipelineArchive = [*fDevice newBinaryArchiveWithDescriptor:*desc error:&error];
+- if (!fPipelineArchive) {
++ d->fPipelineArchive = [*d->fDevice newBinaryArchiveWithDescriptor:*desc error:&error];
++ if (!d->fPipelineArchive) {
+ SkDebugf("Error creating MTLBinaryArchive:\n%s\n",
+ error.debugDescription.UTF8String);
+ }
+@@ -74,46 +80,75 @@ void MetalWindowContext::initializeContext() {
+ }
+ } else {
+ if (@available(macOS 11.0, iOS 14.0, *)) {
+- fPipelineArchive = nil;
++ d->fPipelineArchive = nil;
+ }
+ }
+ #endif
+
+ GrMtlBackendContext backendContext = {};
+- backendContext.fDevice.retain((GrMTLHandle)fDevice.get());
+- backendContext.fQueue.retain((GrMTLHandle)fQueue.get());
++ backendContext.fDevice.retain((GrMTLHandle)d->fDevice.get());
++ backendContext.fQueue.retain((GrMTLHandle)d->fQueue.get());
+ #if GR_METAL_SDK_VERSION >= 230
+ if (@available(macOS 11.0, iOS 14.0, *)) {
+- backendContext.fBinaryArchive.retain((__bridge GrMTLHandle)fPipelineArchive);
++ backendContext.fBinaryArchive.retain((__bridge GrMTLHandle)d->fPipelineArchive);
+ }
+ #endif
+- fContext = GrDirectContext::MakeMetal(backendContext, fDisplayParams.fGrContextOptions);
+- if (!fContext && fDisplayParams.fMSAASampleCount > 1) {
++ d->fContext = GrDirectContext::MakeMetal(backendContext, fDisplayParams.fGrContextOptions);
++ if (!d->fContext && fDisplayParams.fMSAASampleCount > 1) {
+ fDisplayParams.fMSAASampleCount /= 2;
++ fGlobalShared.reset();
+ this->initializeContext();
+ return;
+ }
++
++ fShared = fGlobalShared;
++ } // if( !fShared )
++
++ fContext = fShared->fContext;
++
++ fSampleCount = fDisplayParams.fMSAASampleCount;
++ fStencilBits = 8;
++
++ fValid = this->onInitializeContext();
+ }
+
+ void MetalWindowContext::destroyContext() {
+- if (fContext) {
+- // in case we have outstanding refs to this (lua?)
+- fContext->abandonContext();
+- fContext.reset();
+- }
+-
+ this->onDestroyContext();
+
+ fMetalLayer = nil;
+ fValid = false;
+
++ fContext.reset();
++ fShared.reset();
++
++ checkDestroyShared();
++}
++
++void MetalWindowContext::checkDestroyShared()
++{
++ if(!fGlobalShared || !fGlobalShared->unique()) // TODO mutex?
++ return;
++#ifndef SK_TRACE_VK_RESOURCES
++ if(!fGlobalShared->fContext->unique())
++ return;
++#endif
++ SkASSERT(fGlobalShared->fContext->unique());
++
++ if (fGlobalShared->fContext) {
++ // in case we have outstanding refs to this (lua?)
++ fGlobalShared->fContext->abandonContext();
++ fGlobalShared->fContext.reset();
++ }
++
+ #if GR_METAL_SDK_VERSION >= 230
+ if (@available(macOS 11.0, iOS 14.0, *)) {
+- [fPipelineArchive release];
++ [fGlobalShared->fPipelineArchive release];
+ }
+ #endif
+- fQueue.reset();
+- fDevice.reset();
++ fGlobalShared->fQueue.reset();
++ fGlobalShared->fDevice.reset();
++
++ fGlobalShared.reset();
+ }
+
+ sk_sp<SkSurface> MetalWindowContext::getBackbufferSurface() {
+@@ -154,7 +189,7 @@ sk_sp<SkSurface> MetalWindowContext::getBackbufferSurface() {
+ void MetalWindowContext::swapBuffers() {
+ id<CAMetalDrawable> currentDrawable = (id<CAMetalDrawable>)fDrawableHandle;
+
+- id<MTLCommandBuffer> commandBuffer([*fQueue commandBuffer]);
++ id<MTLCommandBuffer> commandBuffer([*fShared->fQueue commandBuffer]);
+ commandBuffer.label = @"Present";
+
+ [commandBuffer presentDrawable:currentDrawable];
+@@ -175,9 +210,9 @@ void MetalWindowContext::activate(bool isActive) {
+ if (!isActive) {
+ #if GR_METAL_SDK_VERSION >= 230
+ if (@available(macOS 11.0, iOS 14.0, *)) {
+- if (fPipelineArchive) {
++ if (fShared->fPipelineArchive) {
+ NSError* error;
+- [fPipelineArchive serializeToURL:CacheURL() error:&error];
++ [fShared->fPipelineArchive serializeToURL:CacheURL() error:&error];
+ if (error) {
+ SkDebugf("Error storing MTLBinaryArchive:\n%s\n",
+ error.debugDescription.UTF8String);
+@@ -188,4 +223,11 @@ void MetalWindowContext::activate(bool isActive) {
+ }
+ }
+
++SK_API sk_sp<MetalWindowContext::Shared> MetalWindowContext::fGlobalShared;
++
++GrDirectContext* getMetalSharedGrDirectContext()
++{
++ return MetalWindowContext::getSharedGrDirectContext();
++}
++
+ } //namespace sk_app
+diff --git a/tools/sk_app/VulkanWindowContext.cpp b/tools/sk_app/VulkanWindowContext.cpp
+index c9db528ca4..634034da5a 100644
+--- a/tools/sk_app/VulkanWindowContext.cpp
++++ b/tools/sk_app/VulkanWindowContext.cpp
+@@ -25,9 +25,13 @@
+ #endif
+
+ #define GET_PROC(F) f ## F = \
+- (PFN_vk ## F) backendContext.fGetProc("vk" #F, fInstance, VK_NULL_HANDLE)
++ (PFN_vk ## F) fGlobalShared->backendContext.fGetProc("vk" #F, fShared->fInstance, VK_NULL_HANDLE)
+ #define GET_DEV_PROC(F) f ## F = \
+- (PFN_vk ## F) backendContext.fGetProc("vk" #F, VK_NULL_HANDLE, fDevice)
++ (PFN_vk ## F) fGlobalShared->backendContext.fGetProc("vk" #F, VK_NULL_HANDLE, fShared->fDevice)
++#define GET_PROC_GLOBAL(F) fGlobalShared->f ## F = \
++ (PFN_vk ## F) fGlobalShared->backendContext.fGetProc("vk" #F, fGlobalShared->fInstance, VK_NULL_HANDLE)
++#define GET_DEV_PROC_GLOBAL(F) fGlobalShared->f ## F = \
++ (PFN_vk ## F) fGlobalShared->backendContext.fGetProc("vk" #F, VK_NULL_HANDLE, fGlobalShared->fDevice)
+
+ namespace sk_app {
+
+@@ -49,31 +53,39 @@ VulkanWindowContext::VulkanWindowContext(const DisplayParams& params,
+ }
+
+ void VulkanWindowContext::initializeContext() {
++ fShared = fGlobalShared;
++ if( !fShared )
++ {
++ // TODO do we need a mutex?
++
++ fGlobalShared = sk_make_sp<Shared>();
++ Shared* d = fGlobalShared.get(); // shorter variable name
++
+ SkASSERT(!fContext);
+ // any config code here (particularly for msaa)?
+
+ PFN_vkGetInstanceProcAddr getInstanceProc = fGetInstanceProcAddr;
+- GrVkBackendContext backendContext;
++ GrVkBackendContext& backendContext = fGlobalShared->backendContext;
+ GrVkExtensions extensions;
+- VkPhysicalDeviceFeatures2 features;
+- if (!sk_gpu_test::CreateVkBackendContext(getInstanceProc, &backendContext, &extensions,
+- &features, &fDebugCallback, &fPresentQueueIndex,
+- fCanPresentFn)) {
+- sk_gpu_test::FreeVulkanFeaturesStructs(&features);
++ if (!sk_gpu_test::CreateVkBackendContext(getInstanceProc, &backendContext, &extensions, &d->features,
++ &d->fDebugCallback, &d->fPresentQueueIndex, fCanPresentFn)) {
++ sk_gpu_test::FreeVulkanFeaturesStructs(&d->features);
++ fGlobalShared.reset();
+ return;
+ }
+
+ if (!extensions.hasExtension(VK_KHR_SURFACE_EXTENSION_NAME, 25) ||
+ !extensions.hasExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, 68)) {
+- sk_gpu_test::FreeVulkanFeaturesStructs(&features);
++ sk_gpu_test::FreeVulkanFeaturesStructs(&d->features);
++ fGlobalShared.reset();
+ return;
+ }
+
+- fInstance = backendContext.fInstance;
+- fPhysicalDevice = backendContext.fPhysicalDevice;
+- fDevice = backendContext.fDevice;
+- fGraphicsQueueIndex = backendContext.fGraphicsQueueIndex;
+- fGraphicsQueue = backendContext.fQueue;
++ d->fInstance = backendContext.fInstance;
++ d->fPhysicalDevice = backendContext.fPhysicalDevice;
++ d->fDevice = backendContext.fDevice;
++ d->fGraphicsQueueIndex = backendContext.fGraphicsQueueIndex;
++ d->fGraphicsQueue = backendContext.fQueue;
+
+ PFN_vkGetPhysicalDeviceProperties localGetPhysicalDeviceProperties =
+ reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(
+@@ -81,21 +93,30 @@ void VulkanWindowContext::initializeContext() {
+ backendContext.fInstance,
+ VK_NULL_HANDLE));
+ if (!localGetPhysicalDeviceProperties) {
+- sk_gpu_test::FreeVulkanFeaturesStructs(&features);
++ sk_gpu_test::FreeVulkanFeaturesStructs(&d->features);
++ fGlobalShared.reset();
+ return;
+ }
+- VkPhysicalDeviceProperties physDeviceProperties;
+- localGetPhysicalDeviceProperties(backendContext.fPhysicalDevice, &physDeviceProperties);
+- uint32_t physDevVersion = physDeviceProperties.apiVersion;
++ localGetPhysicalDeviceProperties(backendContext.fPhysicalDevice, &d->physDeviceProperties);
++ uint32_t physDevVersion = d->physDeviceProperties.apiVersion;
+
+- fInterface.reset(new GrVkInterface(backendContext.fGetProc, fInstance, fDevice,
++ d->fInterface.reset(new GrVkInterface(backendContext.fGetProc, d->fInstance, d->fDevice,
+ backendContext.fInstanceVersion, physDevVersion,
+ &extensions));
+
+- GET_PROC(DestroyInstance);
+- if (fDebugCallback != VK_NULL_HANDLE) {
+- GET_PROC(DestroyDebugReportCallbackEXT);
++ d->fContext = GrDirectContext::MakeVulkan(backendContext, fDisplayParams.fGrContextOptions);
++
++ GET_PROC_GLOBAL(DestroyInstance);
++ GET_DEV_PROC_GLOBAL(DestroyDevice);
++ if (fGlobalShared->fDebugCallback != VK_NULL_HANDLE) {
++ GET_PROC_GLOBAL(DestroyDebugReportCallbackEXT);
+ }
++
++ fShared = fGlobalShared;
++ } // if( !fShared )
++
++ fContext = fShared->fContext;
++
+ GET_PROC(DestroySurfaceKHR);
+ GET_PROC(GetPhysicalDeviceSurfaceSupportKHR);
+ GET_PROC(GetPhysicalDeviceSurfaceCapabilitiesKHR);
+@@ -103,7 +124,6 @@ void VulkanWindowContext::initializeContext() {
+ GET_PROC(GetPhysicalDeviceSurfacePresentModesKHR);
+ GET_DEV_PROC(DeviceWaitIdle);
+ GET_DEV_PROC(QueueWaitIdle);
+- GET_DEV_PROC(DestroyDevice);
+ GET_DEV_PROC(CreateSwapchainKHR);
+ GET_DEV_PROC(DestroySwapchainKHR);
+ GET_DEV_PROC(GetSwapchainImagesKHR);
+@@ -111,46 +131,44 @@ void VulkanWindowContext::initializeContext() {
+ GET_DEV_PROC(QueuePresentKHR);
+ GET_DEV_PROC(GetDeviceQueue);
+
+- fContext = GrDirectContext::MakeVulkan(backendContext, fDisplayParams.fGrContextOptions);
++ // No actual window, used just to create the shared GrContext.
++ if(fCreateVkSurfaceFn == nullptr)
++ return;
+
+- fSurface = fCreateVkSurfaceFn(fInstance);
++ fSurface = fCreateVkSurfaceFn(fShared->fInstance);
+ if (VK_NULL_HANDLE == fSurface) {
+ this->destroyContext();
+- sk_gpu_test::FreeVulkanFeaturesStructs(&features);
+ return;
+ }
+
++ // create presentQueue
++ fGetDeviceQueue(fShared->fDevice, fShared->fPresentQueueIndex, 0, &fPresentQueue);
++
+ VkBool32 supported;
+- VkResult res = fGetPhysicalDeviceSurfaceSupportKHR(fPhysicalDevice, fPresentQueueIndex,
++ VkResult res = fGetPhysicalDeviceSurfaceSupportKHR(fShared->fPhysicalDevice, fShared->fPresentQueueIndex,
+ fSurface, &supported);
+ if (VK_SUCCESS != res) {
+ this->destroyContext();
+- sk_gpu_test::FreeVulkanFeaturesStructs(&features);
+ return;
+ }
+
+ if (!this->createSwapchain(-1, -1, fDisplayParams)) {
+ this->destroyContext();
+- sk_gpu_test::FreeVulkanFeaturesStructs(&features);
+ return;
+ }
+-
+- // create presentQueue
+- fGetDeviceQueue(fDevice, fPresentQueueIndex, 0, &fPresentQueue);
+- sk_gpu_test::FreeVulkanFeaturesStructs(&features);
+ }
+
+ bool VulkanWindowContext::createSwapchain(int width, int height,
+ const DisplayParams& params) {
+ // check for capabilities
+ VkSurfaceCapabilitiesKHR caps;
+- VkResult res = fGetPhysicalDeviceSurfaceCapabilitiesKHR(fPhysicalDevice, fSurface, &caps);
++ VkResult res = fGetPhysicalDeviceSurfaceCapabilitiesKHR(fShared->fPhysicalDevice, fSurface, &caps);
+ if (VK_SUCCESS != res) {
+ return false;
+ }
+
+ uint32_t surfaceFormatCount;
+- res = fGetPhysicalDeviceSurfaceFormatsKHR(fPhysicalDevice, fSurface, &surfaceFormatCount,
++ res = fGetPhysicalDeviceSurfaceFormatsKHR(fShared->fPhysicalDevice, fSurface, &surfaceFormatCount,
+ nullptr);
+ if (VK_SUCCESS != res) {
+ return false;
+@@ -158,14 +176,14 @@ bool VulkanWindowContext::createSwapchain(int width, int height,
+
+ SkAutoMalloc surfaceFormatAlloc(surfaceFormatCount * sizeof(VkSurfaceFormatKHR));
+ VkSurfaceFormatKHR* surfaceFormats = (VkSurfaceFormatKHR*)surfaceFormatAlloc.get();
+- res = fGetPhysicalDeviceSurfaceFormatsKHR(fPhysicalDevice, fSurface, &surfaceFormatCount,
++ res = fGetPhysicalDeviceSurfaceFormatsKHR(fShared->fPhysicalDevice, fSurface, &surfaceFormatCount,
+ surfaceFormats);
+ if (VK_SUCCESS != res) {
+ return false;
+ }
+
+ uint32_t presentModeCount;
+- res = fGetPhysicalDeviceSurfacePresentModesKHR(fPhysicalDevice, fSurface, &presentModeCount,
++ res = fGetPhysicalDeviceSurfacePresentModesKHR(fShared->fPhysicalDevice, fSurface, &presentModeCount,
+ nullptr);
+ if (VK_SUCCESS != res) {
+ return false;
+@@ -173,7 +191,7 @@ bool VulkanWindowContext::createSwapchain(int width, int height,
+
+ SkAutoMalloc presentModeAlloc(presentModeCount * sizeof(VkPresentModeKHR));
+ VkPresentModeKHR* presentModes = (VkPresentModeKHR*)presentModeAlloc.get();
+- res = fGetPhysicalDeviceSurfacePresentModesKHR(fPhysicalDevice, fSurface, &presentModeCount,
++ res = fGetPhysicalDeviceSurfacePresentModesKHR(fShared->fPhysicalDevice, fSurface, &presentModeCount,
+ presentModes);
+ if (VK_SUCCESS != res) {
+ return false;
+@@ -286,8 +304,8 @@ bool VulkanWindowContext::createSwapchain(int width, int height,
+ swapchainCreateInfo.imageArrayLayers = 1;
+ swapchainCreateInfo.imageUsage = usageFlags;
+
+- uint32_t queueFamilies[] = { fGraphicsQueueIndex, fPresentQueueIndex };
+- if (fGraphicsQueueIndex != fPresentQueueIndex) {
++ uint32_t queueFamilies[] = { fShared->fGraphicsQueueIndex, fShared->fPresentQueueIndex };
++ if (fShared->fGraphicsQueueIndex != fShared->fPresentQueueIndex) {
+ swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
+ swapchainCreateInfo.queueFamilyIndexCount = 2;
+ swapchainCreateInfo.pQueueFamilyIndices = queueFamilies;
+@@ -303,27 +321,27 @@ bool VulkanWindowContext::createSwapchain(int width, int height,
+ swapchainCreateInfo.clipped = true;
+ swapchainCreateInfo.oldSwapchain = fSwapchain;
+
+- res = fCreateSwapchainKHR(fDevice, &swapchainCreateInfo, nullptr, &fSwapchain);
++ res = fCreateSwapchainKHR(fShared->fDevice, &swapchainCreateInfo, nullptr, &fSwapchain);
+ if (VK_SUCCESS != res) {
+ return false;
+ }
+
+ // destroy the old swapchain
+ if (swapchainCreateInfo.oldSwapchain != VK_NULL_HANDLE) {
+- fDeviceWaitIdle(fDevice);
++ fDeviceWaitIdle(fShared->fDevice);
+
+ this->destroyBuffers();
+
+- fDestroySwapchainKHR(fDevice, swapchainCreateInfo.oldSwapchain, nullptr);
++ fDestroySwapchainKHR(fShared->fDevice, swapchainCreateInfo.oldSwapchain, nullptr);
+ }
+
+ if (!this->createBuffers(swapchainCreateInfo.imageFormat, usageFlags, colorType,
+ swapchainCreateInfo.imageSharingMode)) {
+- fDeviceWaitIdle(fDevice);
++ fDeviceWaitIdle(fShared->fDevice);
+
+ this->destroyBuffers();
+
+- fDestroySwapchainKHR(fDevice, swapchainCreateInfo.oldSwapchain, nullptr);
++ fDestroySwapchainKHR(fShared->fDevice, swapchainCreateInfo.oldSwapchain, nullptr);
+ }
+
+ return true;
+@@ -332,10 +350,10 @@ bool VulkanWindowContext::createSwapchain(int width, int height,
+ bool VulkanWindowContext::createBuffers(VkFormat format, VkImageUsageFlags usageFlags,
+ SkColorType colorType,
+ VkSharingMode sharingMode) {
+- fGetSwapchainImagesKHR(fDevice, fSwapchain, &fImageCount, nullptr);
++ fGetSwapchainImagesKHR(fShared->fDevice, fSwapchain, &fImageCount, nullptr);
+ SkASSERT(fImageCount);
+ fImages = new VkImage[fImageCount];
+- fGetSwapchainImagesKHR(fDevice, fSwapchain, &fImageCount, fImages);
++ fGetSwapchainImagesKHR(fShared->fDevice, fSwapchain, &fImageCount, fImages);
+
+ // set up initial image layouts and create surfaces
+ fImageLayouts = new VkImageLayout[fImageCount];
+@@ -351,7 +369,7 @@ bool VulkanWindowContext::createBuffers(VkFormat format, VkImageUsageFlags usage
+ info.fFormat = format;
+ info.fImageUsageFlags = usageFlags;
+ info.fLevelCount = 1;
+- info.fCurrentQueueFamily = fPresentQueueIndex;
++ info.fCurrentQueueFamily = fShared->fPresentQueueIndex;
+ info.fSharingMode = sharingMode;
+
+ if (usageFlags & VK_IMAGE_USAGE_SAMPLED_BIT) {
+@@ -387,8 +405,8 @@ bool VulkanWindowContext::createBuffers(VkFormat format, VkImageUsageFlags usage
+ fBackbuffers = new BackbufferInfo[fImageCount + 1];
+ for (uint32_t i = 0; i < fImageCount + 1; ++i) {
+ fBackbuffers[i].fImageIndex = -1;
+- SkDEBUGCODE(VkResult result = )GR_VK_CALL(fInterface,
+- CreateSemaphore(fDevice, &semaphoreInfo, nullptr,
++ SkDEBUGCODE(VkResult result = )GR_VK_CALL(fShared->fInterface,
++ CreateSemaphore(fShared->fDevice, &semaphoreInfo, nullptr,
+ &fBackbuffers[i].fRenderSemaphore));
+ SkASSERT(result == VK_SUCCESS);
+ }
+@@ -401,8 +419,8 @@ void VulkanWindowContext::destroyBuffers() {
+ if (fBackbuffers) {
+ for (uint32_t i = 0; i < fImageCount + 1; ++i) {
+ fBackbuffers[i].fImageIndex = -1;
+- GR_VK_CALL(fInterface,
+- DestroySemaphore(fDevice,
++ GR_VK_CALL(fShared->fInterface,
++ DestroySemaphore(fShared->fDevice,
+ fBackbuffers[i].fRenderSemaphore,
+ nullptr));
+ }
+@@ -427,42 +445,59 @@ VulkanWindowContext::~VulkanWindowContext() {
+ void VulkanWindowContext::destroyContext() {
+ if (this->isValid()) {
+ fQueueWaitIdle(fPresentQueue);
+- fDeviceWaitIdle(fDevice);
++ fDeviceWaitIdle(fShared->fDevice);
+
+ this->destroyBuffers();
+
+ if (VK_NULL_HANDLE != fSwapchain) {
+- fDestroySwapchainKHR(fDevice, fSwapchain, nullptr);
++ fDestroySwapchainKHR(fShared->fDevice, fSwapchain, nullptr);
+ fSwapchain = VK_NULL_HANDLE;
+ }
+
+ if (VK_NULL_HANDLE != fSurface) {
+- fDestroySurfaceKHR(fInstance, fSurface, nullptr);
++ fDestroySurfaceKHR(fShared->fInstance, fSurface, nullptr);
+ fSurface = VK_NULL_HANDLE;
+ }
+ }
+
+- SkASSERT(fContext->unique());
+ fContext.reset();
+- fInterface.reset();
++ fShared.reset();
++
++ checkDestroyShared();
++}
++
++void VulkanWindowContext::checkDestroyShared()
++{
++ if(!fGlobalShared || !fGlobalShared->unique()) // TODO mutex?
++ return;
++#ifndef SK_TRACE_VK_RESOURCES
++ if(!fGlobalShared->fContext->unique())
++ return;
++#endif
++ SkASSERT(fGlobalShared->fContext->unique());
++ fGlobalShared->fContext.reset();
++ fGlobalShared->fInterface.reset();
+
+- if (VK_NULL_HANDLE != fDevice) {
+- fDestroyDevice(fDevice, nullptr);
+- fDevice = VK_NULL_HANDLE;
++ if (VK_NULL_HANDLE != fGlobalShared->fDevice) {
++ fGlobalShared->fDestroyDevice(fGlobalShared->fDevice, nullptr);
++ fGlobalShared->fDevice = VK_NULL_HANDLE;
+ }
+
+ #ifdef SK_ENABLE_VK_LAYERS
+- if (fDebugCallback != VK_NULL_HANDLE) {
+- fDestroyDebugReportCallbackEXT(fInstance, fDebugCallback, nullptr);
++ if (fGlobalShared->fDebugCallback != VK_NULL_HANDLE) {
++ fGlobalShared->fDestroyDebugReportCallbackEXT(fGlobalShared->fInstance, fDebugCallback, nullptr);
+ }
+ #endif
+
+- fPhysicalDevice = VK_NULL_HANDLE;
++ fGlobalShared->fPhysicalDevice = VK_NULL_HANDLE;
+
+- if (VK_NULL_HANDLE != fInstance) {
+- fDestroyInstance(fInstance, nullptr);
+- fInstance = VK_NULL_HANDLE;
++ if (VK_NULL_HANDLE != fGlobalShared->fInstance) {
++ fGlobalShared->fDestroyInstance(fGlobalShared->fInstance, nullptr);
++ fGlobalShared->fInstance = VK_NULL_HANDLE;
+ }
++
++ sk_gpu_test::FreeVulkanFeaturesStructs(&fGlobalShared->features);
++ fGlobalShared.reset();
+ }
+
+ VulkanWindowContext::BackbufferInfo* VulkanWindowContext::getAvailableBackbuffer() {
+@@ -488,35 +523,35 @@ sk_sp<SkSurface> VulkanWindowContext::getBackbufferSurface() {
+ semaphoreInfo.pNext = nullptr;
+ semaphoreInfo.flags = 0;
+ VkSemaphore semaphore;
+- SkDEBUGCODE(VkResult result = )GR_VK_CALL(fInterface, CreateSemaphore(fDevice, &semaphoreInfo,
++ SkDEBUGCODE(VkResult result = )GR_VK_CALL(fShared->fInterface, CreateSemaphore(fShared->fDevice, &semaphoreInfo,
+ nullptr, &semaphore));
+ SkASSERT(result == VK_SUCCESS);
+
+ // acquire the image
+- VkResult res = fAcquireNextImageKHR(fDevice, fSwapchain, UINT64_MAX,
++ VkResult res = fAcquireNextImageKHR(fShared->fDevice, fSwapchain, UINT64_MAX,
+ semaphore, 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
+- GR_VK_CALL(fInterface, DestroySemaphore(fDevice, semaphore, nullptr));
++ GR_VK_CALL(fShared->fInterface, DestroySemaphore(fShared->fDevice, semaphore, nullptr));
+ return nullptr;
+ }
+ if (VK_ERROR_OUT_OF_DATE_KHR == res) {
+ // tear swapchain down and try again
+ if (!this->createSwapchain(-1, -1, fDisplayParams)) {
+- GR_VK_CALL(fInterface, DestroySemaphore(fDevice, semaphore, nullptr));
++ GR_VK_CALL(fShared->fInterface, DestroySemaphore(fShared->fDevice, semaphore, nullptr));
+ return nullptr;
+ }
+ backbuffer = this->getAvailableBackbuffer();
+
+ // acquire the image
+- res = fAcquireNextImageKHR(fDevice, fSwapchain, UINT64_MAX,
++ res = fAcquireNextImageKHR(fShared->fDevice, fSwapchain, UINT64_MAX,
+ semaphore, VK_NULL_HANDLE,
+ &backbuffer->fImageIndex);
+
+ if (VK_SUCCESS != res) {
+- GR_VK_CALL(fInterface, DestroySemaphore(fDevice, semaphore, nullptr));
++ GR_VK_CALL(fShared->fInterface, DestroySemaphore(fShared->fDevice, semaphore, nullptr));
+ return nullptr;
+ }
+ }
+@@ -542,7 +577,7 @@ void VulkanWindowContext::swapBuffers() {
+ GrFlushInfo info;
+ info.fNumSemaphores = 1;
+ info.fSignalSemaphores = &beSemaphore;
+- GrBackendSurfaceMutableState presentState(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, fPresentQueueIndex);
++ GrBackendSurfaceMutableState presentState(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, fShared->fPresentQueueIndex);
+ surface->flush(info, &presentState);
+ surface->recordingContext()->asDirectContext()->submit();
+
+@@ -562,4 +597,6 @@ void VulkanWindowContext::swapBuffers() {
+ fQueuePresentKHR(fPresentQueue, &presentInfo);
+ }
+
++SK_API sk_sp<VulkanWindowContext::Shared> VulkanWindowContext::fGlobalShared;
++
+ } //namespace sk_app
+diff --git a/tools/sk_app/VulkanWindowContext.h b/tools/sk_app/VulkanWindowContext.h
+index 7e1fdd9af5..946bca7522 100644
+--- a/tools/sk_app/VulkanWindowContext.h
++++ b/tools/sk_app/VulkanWindowContext.h
+@@ -19,18 +19,22 @@
+ #include "tools/gpu/vk/VkTestUtils.h"
+ #include "tools/sk_app/WindowContext.h"
+
++#include <cassert>
++
+ class GrRenderTarget;
+
+ namespace sk_app {
+
+-class VulkanWindowContext : public WindowContext {
++class SK_API VulkanWindowContext : public WindowContext {
+ public:
+ ~VulkanWindowContext() override;
+
++ static GrDirectContext* getSharedGrDirectContext() { return fGlobalShared ? fGlobalShared->fContext.get() : nullptr; }
++
+ sk_sp<SkSurface> getBackbufferSurface() override;
+ void swapBuffers() override;
+
+- bool isValid() override { return fDevice != VK_NULL_HANDLE; }
++ bool isValid() override { return fSurface != VK_NULL_HANDLE; }
+
+ void resize(int w, int h) override {
+ this->createSwapchain(w, h, fDisplayParams);
+@@ -50,9 +54,15 @@ public:
+ VulkanWindowContext(const DisplayParams&, CreateVkSurfaceFn, CanPresentFn,
+ PFN_vkGetInstanceProcAddr);
+
++ static const VkPhysicalDeviceProperties& getPhysDeviceProperties() {
++ assert( fGlobalShared != nullptr );
++ return fGlobalShared->physDeviceProperties;
++ }
++
+ private:
+ void initializeContext();
+ void destroyContext();
++ static void checkDestroyShared();
+
+ struct BackbufferInfo {
+ uint32_t fImageIndex; // image this is associated with
+@@ -64,11 +74,6 @@ private:
+ bool createBuffers(VkFormat format, VkImageUsageFlags, SkColorType colorType, VkSharingMode);
+ void destroyBuffers();
+
+- VkInstance fInstance = VK_NULL_HANDLE;
+- VkPhysicalDevice fPhysicalDevice = VK_NULL_HANDLE;
+- VkDevice fDevice = VK_NULL_HANDLE;
+- VkDebugReportCallbackEXT fDebugCallback = VK_NULL_HANDLE;
+-
+ // Create functions
+ CreateVkSurfaceFn fCreateVkSurfaceFn;
+ CanPresentFn fCanPresentFn;
+@@ -88,20 +93,46 @@ private:
+ PFN_vkAcquireNextImageKHR fAcquireNextImageKHR = nullptr;
+ PFN_vkQueuePresentKHR fQueuePresentKHR = nullptr;
+
+- PFN_vkDestroyInstance fDestroyInstance = nullptr;
+ PFN_vkDeviceWaitIdle fDeviceWaitIdle = nullptr;
+- PFN_vkDestroyDebugReportCallbackEXT fDestroyDebugReportCallbackEXT = nullptr;
+ PFN_vkQueueWaitIdle fQueueWaitIdle = nullptr;
+- PFN_vkDestroyDevice fDestroyDevice = nullptr;
+ PFN_vkGetDeviceQueue fGetDeviceQueue = nullptr;
+
++ // We need to use just one GrDirectContext, so share all the relevant data.
++ struct Shared : public SkRefCnt
++ {
++ PFN_vkDestroyInstance fDestroyInstance = nullptr;
++ PFN_vkDestroyDevice fDestroyDevice = nullptr;
++ PFN_vkDestroyDebugReportCallbackEXT fDestroyDebugReportCallbackEXT = nullptr;
++
++ VkInstance fInstance = VK_NULL_HANDLE;
++ VkPhysicalDevice fPhysicalDevice = VK_NULL_HANDLE;
++ VkDevice fDevice = VK_NULL_HANDLE;
++ VkDebugReportCallbackEXT fDebugCallback = VK_NULL_HANDLE;
++
+ sk_sp<const GrVkInterface> fInterface;
+
+- VkSurfaceKHR fSurface;
+- VkSwapchainKHR fSwapchain;
++ // Original code had this as a function-local variable, but that seems wrong.
++ // It should exist as long as the context exists.
++ VkPhysicalDeviceFeatures2 features;
++
++ // Store this to make it accessible.
++ VkPhysicalDeviceProperties physDeviceProperties;
++
++ GrVkBackendContext backendContext;
++
+ uint32_t fGraphicsQueueIndex;
+ VkQueue fGraphicsQueue;
+ uint32_t fPresentQueueIndex;
++
++ sk_sp<GrDirectContext> fContext;
++ };
++
++ sk_sp<Shared> fShared;
++
++ static sk_sp<Shared> fGlobalShared;
++
++ VkSurfaceKHR fSurface;
++ VkSwapchainKHR fSwapchain;
+ VkQueue fPresentQueue;
+
+ uint32_t fImageCount;
+diff --git a/tools/sk_app/WindowContext.h b/tools/sk_app/WindowContext.h
+index 65ab8b9aa4..2d222385a3 100644
+--- a/tools/sk_app/WindowContext.h
++++ b/tools/sk_app/WindowContext.h
+@@ -10,9 +10,9 @@
+ #include "include/core/SkRefCnt.h"
+ #include "include/core/SkSurfaceProps.h"
+ #include "include/gpu/GrTypes.h"
++#include "include/gpu/GrDirectContext.h"
+ #include "tools/sk_app/DisplayParams.h"
+
+-class GrDirectContext;
+ class SkSurface;
+ #ifdef SK_GRAPHITE_ENABLED
+ namespace skgpu::graphite {
+diff --git a/tools/sk_app/mac/MetalWindowContext_mac.mm b/tools/sk_app/mac/MetalWindowContext_mac.mm
+index 5bea8578fa..f7df061af0 100644
+--- a/tools/sk_app/mac/MetalWindowContext_mac.mm
++++ b/tools/sk_app/mac/MetalWindowContext_mac.mm
+@@ -49,10 +49,14 @@ MetalWindowContext_mac::~MetalWindowContext_mac() {
+ }
+
+ bool MetalWindowContext_mac::onInitializeContext() {
++ // Allow creating just the shared context, without an associated window.
++ if(fMainView == nil)
++ return true;
++
+ SkASSERT(nil != fMainView);
+
+ fMetalLayer = [CAMetalLayer layer];
+- fMetalLayer.device = fDevice.get();
++ fMetalLayer.device = fShared->fDevice.get();
+ fMetalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
+
+ // resize ignores the passed values and uses the fMainView directly.
+diff --git a/tools/sk_app/unix/VulkanWindowContext_unix.cpp b/tools/sk_app/unix/VulkanWindowContext_unix.cpp
+index 2b31fedc19..0c05fbfc92 100644
+--- a/tools/sk_app/unix/VulkanWindowContext_unix.cpp
++++ b/tools/sk_app/unix/VulkanWindowContext_unix.cpp
+@@ -30,7 +30,7 @@ std::unique_ptr<WindowContext> MakeVulkanForXlib(const XlibWindowInfo& info,
+ return nullptr;
+ }
+
+- auto createVkSurface = [&info, instProc](VkInstance instance) -> VkSurfaceKHR {
++ VulkanWindowContext::CreateVkSurfaceFn createVkSurface = [&info, instProc](VkInstance instance) -> VkSurfaceKHR {
+ static PFN_vkCreateXcbSurfaceKHR createXcbSurfaceKHR = nullptr;
+ if (!createXcbSurfaceKHR) {
+ createXcbSurfaceKHR =
+@@ -54,6 +54,9 @@ std::unique_ptr<WindowContext> MakeVulkanForXlib(const XlibWindowInfo& info,
+
+ return surface;
+ };
++ // Allow creating just the shared context, without an associated window.
++ if(info.fWindow == None)
++ createVkSurface = nullptr;
+
+ auto canPresent = [&info, instProc](VkInstance instance, VkPhysicalDevice physDev,
+ uint32_t queueFamilyIndex) {
+@@ -76,7 +79,7 @@ std::unique_ptr<WindowContext> MakeVulkanForXlib(const XlibWindowInfo& info,
+ };
+ std::unique_ptr<WindowContext> ctx(
+ new VulkanWindowContext(displayParams, createVkSurface, canPresent, instProc));
+- if (!ctx->isValid()) {
++ if (!ctx->isValid() && createVkSurface != nullptr) {
+ return nullptr;
+ }
+ return ctx;
+diff --git a/tools/sk_app/win/VulkanWindowContext_win.cpp b/tools/sk_app/win/VulkanWindowContext_win.cpp
+index 976c42556e..c8f6b162bf 100644
+--- a/tools/sk_app/win/VulkanWindowContext_win.cpp
++++ b/tools/sk_app/win/VulkanWindowContext_win.cpp
+@@ -29,7 +29,7 @@ std::unique_ptr<WindowContext> MakeVulkanForWin(HWND hwnd, const DisplayParams&
+ return nullptr;
+ }
+
+- auto createVkSurface = [hwnd, instProc] (VkInstance instance) -> VkSurfaceKHR {
++ VulkanWindowContext::CreateVkSurfaceFn createVkSurface = [hwnd, instProc] (VkInstance instance) -> VkSurfaceKHR {
+ static PFN_vkCreateWin32SurfaceKHR createWin32SurfaceKHR = nullptr;
+ if (!createWin32SurfaceKHR) {
+ createWin32SurfaceKHR = (PFN_vkCreateWin32SurfaceKHR)
+@@ -53,6 +53,9 @@ std::unique_ptr<WindowContext> MakeVulkanForWin(HWND hwnd, const DisplayParams&
+
+ return surface;
+ };
++ // Allow creating just the shared context, without an associated window.
++ if(hwnd == nullptr)
++ createVkSurface = nullptr;
+
+ auto canPresent = [instProc] (VkInstance instance, VkPhysicalDevice physDev,
+ uint32_t queueFamilyIndex) {
+@@ -70,7 +73,7 @@ std::unique_ptr<WindowContext> MakeVulkanForWin(HWND hwnd, const DisplayParams&
+
+ std::unique_ptr<WindowContext> ctx(
+ new VulkanWindowContext(params, createVkSurface, canPresent, instProc));
+- if (!ctx->isValid()) {
++ if (!ctx->isValid() && createVkSurface != nullptr) {
+ return nullptr;
+ }
+ return ctx;