| /* | |
| * Copyright 2023 Google LLC | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "tools/window/GraphiteNativeVulkanWindowContext.h" | |
| #include "include/core/SkSurface.h" | |
| #include "include/gpu/MutableTextureState.h" | |
| #include "include/gpu/graphite/BackendSemaphore.h" | |
| #include "include/gpu/graphite/BackendTexture.h" | |
| #include "include/gpu/graphite/Context.h" | |
| #include "include/gpu/graphite/ContextOptions.h" | |
| #include "include/gpu/graphite/GraphiteTypes.h" | |
| #include "include/gpu/graphite/Recorder.h" | |
| #include "include/gpu/graphite/Surface.h" | |
| #include "include/gpu/graphite/TextureInfo.h" | |
| #include "include/gpu/graphite/vk/VulkanGraphiteContext.h" | |
| #include "include/gpu/graphite/vk/VulkanGraphiteTypes.h" | |
| #include "include/gpu/vk/VulkanExtensions.h" | |
| #include "include/gpu/vk/VulkanMutableTextureState.h" | |
| #include "include/gpu/vk/VulkanTypes.h" | |
| #include "include/private/SkLog.h" | |
| #include "src/core/SkAutoMalloc.h" | |
| #include "src/gpu/graphite/ContextOptionsPriv.h" | |
| #include "src/gpu/graphite/TextureFormat.h" | |
| #include "src/gpu/graphite/vk/VulkanGraphiteUtils.h" | |
| #include "src/gpu/vk/VulkanInterface.h" | |
| #include "src/gpu/vk/vulkanmemoryallocator/VulkanAMDMemoryAllocator.h" | |
| #include "tools/graphite/GraphiteToolUtils.h" | |
| #include "tools/graphite/TestOptions.h" | |
| #include "tools/window/GraphiteDisplayParams.h" | |
| #include <algorithm> | |
| #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)backendContext.fGetProc("vk" #F, fInstance, VK_NULL_HANDLE) | |
| #define GET_DEV_PROC(F) f##F = (PFN_vk##F)backendContext.fGetProc("vk" #F, VK_NULL_HANDLE, fDevice) | |
| namespace skwindow::internal { | |
| GraphiteVulkanWindowContext::GraphiteVulkanWindowContext( | |
| std::unique_ptr<const DisplayParams> params, | |
| CreateVkSurfaceFn createVkSurface, | |
| CanPresentFn canPresent, | |
| PFN_vkGetInstanceProcAddr instProc) | |
| : WindowContext(std::move(params)) | |
| , fDeviceSurface(VK_NULL_HANDLE) | |
| , fSwapchain(VK_NULL_HANDLE) | |
| , fCreateVkSurfaceFn(std::move(createVkSurface)) | |
| , fCanPresentFn(std::move(canPresent)) { | |
| fGetInstanceProcAddr = instProc; | |
| this->initializeContext(); | |
| } | |
| void GraphiteVulkanWindowContext::initializeContext() { | |
| SkASSERT(!fGraphiteContext && !fGraphiteRecorder); | |
| // Any config code here (particularly for msaa)? | |
| PFN_vkGetInstanceProcAddr getInstanceProc = fGetInstanceProcAddr; | |
| skgpu::VulkanBackendContext backendContext; | |
| skgpu::VulkanExtensions extensions; | |
| sk_gpu_test::TestVkFeatures features; | |
| if (!sk_gpu_test::CreateVkBackendContext(getInstanceProc, | |
| &backendContext, | |
| &extensions, | |
| &features, | |
| &fDebugMessenger, | |
| &fPresentQueueIndex, | |
| fCanPresentFn, | |
| fDisplayParams->createProtectedNativeBackend())) { | |
| return; | |
| } | |
| if (!extensions.hasExtension(VK_KHR_SURFACE_EXTENSION_NAME, 25) || | |
| !extensions.hasExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, 68)) { | |
| return; | |
| } | |
| fInstance = backendContext.fInstance; | |
| fPhysicalDevice = backendContext.fPhysicalDevice; | |
| fDevice = backendContext.fDevice; | |
| fGraphicsQueueIndex = backendContext.fGraphicsQueueIndex; | |
| fGraphicsQueue = backendContext.fQueue; | |
| PFN_vkGetPhysicalDeviceProperties localGetPhysicalDeviceProperties = | |
| reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>(backendContext.fGetProc( | |
| "vkGetPhysicalDeviceProperties", backendContext.fInstance, VK_NULL_HANDLE)); | |
| if (!localGetPhysicalDeviceProperties) { | |
| return; | |
| } | |
| VkPhysicalDeviceProperties physDeviceProperties; | |
| localGetPhysicalDeviceProperties(backendContext.fPhysicalDevice, &physDeviceProperties); | |
| uint32_t physDevVersion = physDeviceProperties.apiVersion; | |
| fInterface.reset(new skgpu::VulkanInterface(backendContext.fGetProc, | |
| fInstance, | |
| fDevice, | |
| backendContext.fMaxAPIVersion, | |
| physDevVersion, | |
| &extensions)); | |
| GET_PROC(DestroyInstance); | |
| if (fDebugMessenger != VK_NULL_HANDLE) { | |
| GET_PROC(DestroyDebugUtilsMessengerEXT); | |
| } | |
| GET_PROC(DestroySurfaceKHR); | |
| GET_PROC(GetPhysicalDeviceSurfaceSupportKHR); | |
| GET_PROC(GetPhysicalDeviceSurfaceCapabilitiesKHR); | |
| GET_PROC(GetPhysicalDeviceSurfaceFormatsKHR); | |
| 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); | |
| GET_DEV_PROC(AcquireNextImageKHR); | |
| GET_DEV_PROC(QueuePresentKHR); | |
| GET_DEV_PROC(GetDeviceQueue); | |
| SkASSERT(fDisplayParams->graphiteTestOptions()); | |
| skiatest::graphite::TestOptions opts = *fDisplayParams->graphiteTestOptions(); | |
| // Needed to make synchronous readPixels work | |
| opts.fOptionsPriv.fStoreContextRefInRecorder = true; | |
| fDisplayParams = | |
| GraphiteDisplayParamsBuilder(fDisplayParams.get()).graphiteTestOptions(opts).detach(); | |
| fGraphiteContext = skgpu::graphite::ContextFactory::MakeVulkan( | |
| backendContext, fDisplayParams->graphiteTestOptions()->fContextOptions); | |
| fGraphiteRecorder = fGraphiteContext->makeRecorder(ToolUtils::CreateTestingRecorderOptions()); | |
| fDeviceSurface = fCreateVkSurfaceFn(fInstance); | |
| if (VK_NULL_HANDLE == fDeviceSurface) { | |
| this->destroyContext(); | |
| return; | |
| } | |
| VkBool32 supported; | |
| VkResult res = fGetPhysicalDeviceSurfaceSupportKHR( | |
| fPhysicalDevice, fPresentQueueIndex, fDeviceSurface, &supported); | |
| if (VK_SUCCESS != res) { | |
| this->destroyContext(); | |
| return; | |
| } | |
| if (!this->createSwapchain(-1, -1)) { | |
| this->destroyContext(); | |
| return; | |
| } | |
| fGetDeviceQueue(fDevice, fPresentQueueIndex, /*queueIndex=*/0, &fPresentQueue); | |
| } | |
| bool GraphiteVulkanWindowContext::createSwapchain(int width, int height) { | |
| // Check surface capabilities | |
| VkSurfaceCapabilitiesKHR caps; | |
| VkResult res = fGetPhysicalDeviceSurfaceCapabilitiesKHR(fPhysicalDevice, fDeviceSurface, &caps); | |
| if (VK_SUCCESS != res) { | |
| return false; | |
| } | |
| uint32_t surfaceFormatCount; | |
| res = fGetPhysicalDeviceSurfaceFormatsKHR( | |
| fPhysicalDevice, fDeviceSurface, &surfaceFormatCount, nullptr); | |
| if (VK_SUCCESS != res) { | |
| return false; | |
| } | |
| SkAutoMalloc surfaceFormatAlloc(surfaceFormatCount * sizeof(VkSurfaceFormatKHR)); | |
| VkSurfaceFormatKHR* surfaceFormats = (VkSurfaceFormatKHR*)surfaceFormatAlloc.get(); | |
| res = fGetPhysicalDeviceSurfaceFormatsKHR( | |
| fPhysicalDevice, fDeviceSurface, &surfaceFormatCount, surfaceFormats); | |
| if (VK_SUCCESS != res) { | |
| return false; | |
| } | |
| uint32_t presentModeCount; | |
| res = fGetPhysicalDeviceSurfacePresentModesKHR( | |
| fPhysicalDevice, fDeviceSurface, &presentModeCount, nullptr); | |
| if (VK_SUCCESS != res) { | |
| return false; | |
| } | |
| SkAutoMalloc presentModeAlloc(presentModeCount * sizeof(VkPresentModeKHR)); | |
| VkPresentModeKHR* presentModes = (VkPresentModeKHR*)presentModeAlloc.get(); | |
| res = fGetPhysicalDeviceSurfacePresentModesKHR( | |
| fPhysicalDevice, fDeviceSurface, &presentModeCount, presentModes); | |
| if (VK_SUCCESS != res) { | |
| return false; | |
| } | |
| VkExtent2D extent = caps.currentExtent; | |
| // Use the hints for width + height | |
| if (extent.width == (uint32_t)-1) { | |
| extent.width = width; | |
| extent.height = height; | |
| } | |
| // Clamp the values to protect 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; | |
| } | |
| 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); | |
| if (caps.supportedUsageFlags & VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT) { | |
| usageFlags |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; | |
| } | |
| if (caps.supportedUsageFlags & VK_IMAGE_USAGE_SAMPLED_BIT) { | |
| usageFlags |= VK_IMAGE_USAGE_SAMPLED_BIT; | |
| } | |
| 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. | |
| VkFormat surfaceFormat = VK_FORMAT_UNDEFINED; | |
| VkColorSpaceKHR colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; | |
| for (uint32_t i = 0; i < surfaceFormatCount; ++i) { | |
| VkFormat localFormat = surfaceFormats[i].format; | |
| skgpu::graphite::TextureFormat format = | |
| skgpu::graphite::VkFormatToTextureFormat(localFormat); | |
| // Skip unsupported and HW sRGB formats. We can technically render to the sRGB formats | |
| // but it requires the SkColorSpace to have a linear gamut. Viewer needs to be able to | |
| // set the dst color space for legacy color management and various other modes, so we | |
| // skip those formats here for compatibility. | |
| if (format != skgpu::graphite::TextureFormat::kUnsupported && | |
| format != skgpu::graphite::TextureFormat::kRGBA8_sRGB && | |
| format != skgpu::graphite::TextureFormat::kBGRA8_sRGB) { | |
| surfaceFormat = localFormat; | |
| colorSpace = surfaceFormats[i].colorSpace; | |
| break; | |
| } | |
| } | |
| fSampleCount = std::max(1, fDisplayParams->msaaSampleCount()); | |
| 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; | |
| bool hasImmediate = false; | |
| for (uint32_t i = 0; i < presentModeCount; ++i) { | |
| // use mailbox | |
| if (VK_PRESENT_MODE_MAILBOX_KHR == presentModes[i]) { | |
| mode = VK_PRESENT_MODE_MAILBOX_KHR; | |
| } | |
| if (VK_PRESENT_MODE_IMMEDIATE_KHR == presentModes[i]) { | |
| hasImmediate = true; | |
| } | |
| } | |
| if (fDisplayParams->disableVsync() && hasImmediate) { | |
| mode = VK_PRESENT_MODE_IMMEDIATE_KHR; | |
| } | |
| VkSwapchainCreateInfoKHR swapchainCreateInfo; | |
| memset(&swapchainCreateInfo, 0, sizeof(VkSwapchainCreateInfoKHR)); | |
| swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; | |
| swapchainCreateInfo.flags = fDisplayParams->createProtectedNativeBackend() | |
| ? VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR | |
| : 0; | |
| swapchainCreateInfo.surface = fDeviceSurface; | |
| swapchainCreateInfo.minImageCount = imageCount; | |
| swapchainCreateInfo.imageFormat = surfaceFormat; | |
| swapchainCreateInfo.imageColorSpace = colorSpace; | |
| swapchainCreateInfo.imageExtent = extent; | |
| swapchainCreateInfo.imageArrayLayers = 1; | |
| swapchainCreateInfo.imageUsage = usageFlags; | |
| uint32_t queueFamilies[] = {fGraphicsQueueIndex, fPresentQueueIndex}; | |
| if (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(fDevice, &swapchainCreateInfo, nullptr, &fSwapchain); | |
| if (VK_SUCCESS != res) { | |
| return false; | |
| } | |
| // Destroy the old swapchain | |
| if (swapchainCreateInfo.oldSwapchain != VK_NULL_HANDLE) { | |
| fDeviceWaitIdle(fDevice); | |
| this->resetSwapchainImages(); | |
| fDestroySwapchainKHR(fDevice, swapchainCreateInfo.oldSwapchain, nullptr); | |
| swapchainCreateInfo.oldSwapchain = VK_NULL_HANDLE; | |
| } | |
| // If buffer creation fails, destroy the swapchain. | |
| if (!this->populateSwapchainImages(swapchainCreateInfo.imageFormat, | |
| usageFlags, | |
| swapchainCreateInfo.imageSharingMode)) { | |
| fDeviceWaitIdle(fDevice); | |
| this->resetSwapchainImages(); | |
| fDestroySwapchainKHR(fDevice, swapchainCreateInfo.oldSwapchain, nullptr); | |
| swapchainCreateInfo.oldSwapchain = VK_NULL_HANDLE; | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool GraphiteVulkanWindowContext::populateSwapchainImages(VkFormat format, | |
| VkImageUsageFlags usageFlags, | |
| VkSharingMode sharingMode) { | |
| // Determine number of swapchain images | |
| uint32_t swapchainImgCount; | |
| fGetSwapchainImagesKHR(fDevice, fSwapchain, &swapchainImgCount, /*pSwapchainImages*/nullptr); | |
| SkASSERT(swapchainImgCount); | |
| // Define an array of VkImages and query the driver to populate it. | |
| skia_private::AutoTArray<VkImage> vkImages((size_t)swapchainImgCount); | |
| std::fill_n(vkImages.get(), swapchainImgCount, VK_NULL_HANDLE); | |
| fGetSwapchainImagesKHR(fDevice, fSwapchain, &swapchainImgCount, vkImages.get()); | |
| // Populate all swapchain image representations | |
| fImages = skia_private::AutoTArray<SwapchainImage>((size_t)swapchainImgCount); | |
| VkResult result; | |
| for (uint32_t i = 0; i < swapchainImgCount; ++i) { | |
| // Make sure we were provided a valid VkImage handle | |
| SkASSERT(vkImages[i] != VK_NULL_HANDLE); | |
| fImages[i].fVkImage = vkImages[i]; | |
| // Create the semaphore that will be signaled once the image done being rendered | |
| static const VkSemaphoreCreateInfo submitSemInfo = | |
| {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, /*pNext=*/nullptr, /*flags=*/0}; | |
| VULKAN_CALL_RESULT_NOCHECK(fInterface, | |
| result, | |
| CreateSemaphore(fDevice, | |
| &submitSemInfo, | |
| /*pAllocator=*/nullptr, | |
| &fImages[i].fRenderCompletionSemaphore)); | |
| // Create a Surface associated with each image for presentation | |
| skgpu::graphite::VulkanTextureInfo info; | |
| info.fImageTiling = VK_IMAGE_TILING_OPTIMAL; | |
| info.fFormat = format; | |
| info.fImageUsageFlags = usageFlags; | |
| info.fSharingMode = sharingMode; | |
| info.fFlags = | |
| fDisplayParams->createProtectedNativeBackend() ? VK_IMAGE_CREATE_PROTECTED_BIT : 0; | |
| auto backendTex = skgpu::graphite::BackendTextures::MakeVulkan(this->dimensions(), | |
| info, | |
| VK_IMAGE_LAYOUT_UNDEFINED, | |
| fPresentQueueIndex, | |
| fImages[i].fVkImage, | |
| skgpu::VulkanAlloc()); | |
| fImages[i].fSurface = SkSurfaces::WrapBackendTexture(this->graphiteRecorder(), | |
| backendTex, | |
| fDisplayParams->colorSpace(), | |
| &fDisplayParams->surfaceProps()); | |
| if (!fImages[i].fSurface) { | |
| SKIA_LOG_W("Failed to create Surface for swapchain image"); | |
| // Clean up any previously-created semaphores before returning false to indicate failure | |
| this->resetSwapchainImages(); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| GraphiteVulkanWindowContext::~GraphiteVulkanWindowContext() { this->destroyContext(); } | |
| void GraphiteVulkanWindowContext::destroyContext() { | |
| if (this->isValid()) { | |
| if (fPresentQueue != VK_NULL_HANDLE) { | |
| fQueueWaitIdle(fPresentQueue); | |
| } | |
| fDeviceWaitIdle(fDevice); | |
| if (fAcquireSemaphore != VK_NULL_HANDLE) { | |
| VULKAN_CALL(fInterface, | |
| DestroySemaphore(fDevice, fAcquireSemaphore, /*pAllocator=*/nullptr)); | |
| fAcquireSemaphore = VK_NULL_HANDLE; | |
| } | |
| this->resetSwapchainImages(); | |
| if (fSwapchain != VK_NULL_HANDLE) { | |
| fDestroySwapchainKHR(fDevice, fSwapchain, /*pAllocator=*/nullptr); | |
| fSwapchain = VK_NULL_HANDLE; | |
| } | |
| if (fDeviceSurface != VK_NULL_HANDLE) { | |
| fDestroySurfaceKHR(fInstance, fDeviceSurface, /*pAllocator=*/nullptr); | |
| fDeviceSurface = VK_NULL_HANDLE; | |
| } | |
| } | |
| if (fGraphiteContext) { | |
| fGraphiteRecorder.reset(); | |
| fGraphiteContext.reset(); | |
| } | |
| fInterface.reset(); | |
| if (fDevice != VK_NULL_HANDLE) { | |
| fDestroyDevice(fDevice, /*pAllocator=*/nullptr); | |
| fDevice = VK_NULL_HANDLE; | |
| } | |
| #ifdef SK_ENABLE_VK_LAYERS | |
| if (fDebugMessenger != VK_NULL_HANDLE) { | |
| fDestroyDebugUtilsMessengerEXT(fInstance, fDebugMessenger, /*pAllocator=*/nullptr); | |
| } | |
| #endif | |
| fPhysicalDevice = VK_NULL_HANDLE; | |
| if (fInstance != VK_NULL_HANDLE) { | |
| fDestroyInstance(fInstance, /*pAllocator=*/nullptr); | |
| fInstance = VK_NULL_HANDLE; | |
| } | |
| } | |
| bool GraphiteVulkanWindowContext::submitToGpu() { | |
| if (!fGraphiteContext) { | |
| SKIA_LOG_W("Cannot have null graphite context when submitting work to the GPU for " | |
| "presentation."); | |
| return false; | |
| } | |
| SkASSERT(fGraphiteRecorder); | |
| std::unique_ptr<skgpu::graphite::Recording> recording = fGraphiteRecorder->snap(); | |
| if (!recording) { | |
| SKIA_LOG_W("Failed to snap recording for submitting work to the GPU for presentation. " | |
| "Will not submit the present operation to the present queue."); | |
| return false; | |
| } | |
| skgpu::graphite::InsertRecordingInfo info; | |
| info.fRecording = recording.get(); | |
| // Set up surface for layout transition | |
| info.fTargetSurface = fImages[fCurrentImageIndex].fSurface.get(); | |
| skgpu::MutableTextureState presentState = skgpu::MutableTextureStates::MakeVulkan( | |
| VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, fPresentQueueIndex); | |
| info.fTargetTextureState = &presentState; | |
| // Populate InsertRecordingInfo semaphores by wrapping VkSemaphores in graphite representation. | |
| // The recorder should wait upon image acquisition prior to submitting work to the GPU: | |
| SkASSERT(fAcquireSemaphore != VK_NULL_HANDLE); | |
| info.fNumWaitSemaphores = 1; | |
| skgpu::graphite::BackendSemaphore backendAcquireSemaphore = | |
| skgpu::graphite::BackendSemaphores::MakeVulkan(fAcquireSemaphore); | |
| info.fWaitSemaphores = &backendAcquireSemaphore; | |
| // The recorder should signal render completion for its associated image so we know when we | |
| // can submit the image to the presentation queue: | |
| info.fNumSignalSemaphores = 1; | |
| skgpu::graphite::BackendSemaphore backendRenderSemaphore = | |
| skgpu::graphite::BackendSemaphores::MakeVulkan( | |
| fImages[fCurrentImageIndex].fRenderCompletionSemaphore); | |
| info.fSignalSemaphores = &backendRenderSemaphore; | |
| // Insert finishedProc to delete the wait semaphore when done. The signal semaphore used for | |
| // rendering completion is handled by resetSwapchainImages(). | |
| struct FinishContext { | |
| sk_sp<const skgpu::VulkanInterface> fInterface; | |
| VkDevice fDevice; | |
| VkSemaphore fWaitSemaphore; | |
| }; | |
| auto* finishContext = new FinishContext{fInterface, fDevice, fAcquireSemaphore}; | |
| skgpu::graphite::GpuFinishedProc finishCallback = [](skgpu::graphite::GpuFinishedContext c, | |
| skgpu::CallbackResult status) { | |
| // Regardless of the status, we need to destroy the waitSemaphore | |
| if (status != skgpu::CallbackResult::kSuccess) { | |
| SKIA_LOG_W("Recording insertion failed."); | |
| } | |
| const auto* context = reinterpret_cast<const FinishContext*>(c); | |
| VULKAN_CALL(context->fInterface, DestroySemaphore(context->fDevice, | |
| context->fWaitSemaphore, | |
| /*pAllocator=*/nullptr)); | |
| }; | |
| info.fFinishedContext = finishContext; | |
| info.fFinishedProc = finishCallback; | |
| fGraphiteContext->insertRecording(info); | |
| fGraphiteContext->submit(skgpu::graphite::SyncToCpu::kNo); | |
| fAcquireSemaphore = {}; // FinishCallback will destroy this | |
| return true; | |
| } | |
| void GraphiteVulkanWindowContext::resetSwapchainImages() { | |
| if (fImages.empty()) { | |
| return; | |
| } | |
| // Clean up the image's semaphores | |
| for (size_t i = 0; i < fImages.size(); i++) { | |
| if (fImages[i].fRenderCompletionSemaphore != VK_NULL_HANDLE) { | |
| VULKAN_CALL(fInterface, DestroySemaphore(fDevice, | |
| fImages[i].fRenderCompletionSemaphore, | |
| /*pAllocator=*/nullptr)); | |
| } | |
| } | |
| fImages.reset(); | |
| } | |
| sk_sp<SkSurface> GraphiteVulkanWindowContext::getBackbufferSurface() { | |
| // Create a new, unsignaled semaphore to be signaled upon swapchain image acquisition. | |
| static const VkSemaphoreCreateInfo acquireSemInfo = | |
| { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, /*pNext=*/nullptr, /*flags=*/0 }; | |
| VkResult result; | |
| VULKAN_CALL_RESULT_NOCHECK(fInterface, result, CreateSemaphore(fDevice, | |
| &acquireSemInfo, | |
| /*pAllocator=*/nullptr, | |
| &fAcquireSemaphore)); | |
| SkASSERT(fAcquireSemaphore != VK_NULL_HANDLE); | |
| // Acquire the next available presentable image's index. | |
| VkResult res = fAcquireNextImageKHR(fDevice, | |
| fSwapchain, | |
| /*timeout=*/UINT64_MAX, | |
| fAcquireSemaphore, | |
| /*VkFence=*/VK_NULL_HANDLE, | |
| &fCurrentImageIndex); | |
| if (VK_ERROR_SURFACE_LOST_KHR == res) { | |
| // TODO: Recreate fDeviceSurface using fCreateVkSurfaceFn, and then rebuild the swapchain | |
| VULKAN_CALL(fInterface, | |
| DestroySemaphore(fDevice, fAcquireSemaphore, /*pAllocator=*/nullptr)); | |
| return nullptr; | |
| } | |
| if (VK_ERROR_OUT_OF_DATE_KHR == res) { | |
| SKIA_LOG_W("Failed to acquire next image for swapchain. Tearing down and trying again."); | |
| if (!this->createSwapchain(-1, -1)) { | |
| VULKAN_CALL(fInterface, | |
| DestroySemaphore(fDevice, fAcquireSemaphore, /*pAllocator=*/nullptr)); | |
| return nullptr; | |
| } | |
| res = fAcquireNextImageKHR(fDevice, | |
| fSwapchain, | |
| UINT64_MAX, | |
| fAcquireSemaphore, | |
| /*VkFence=*/VK_NULL_HANDLE, | |
| &fCurrentImageIndex); | |
| if (VK_SUCCESS != res) { | |
| VULKAN_CALL(fInterface, | |
| DestroySemaphore(fDevice, fAcquireSemaphore, /*pAllocator=*/nullptr)); | |
| return nullptr; | |
| } | |
| } | |
| // Return the SkSurface associated with the current backbuffer's image. | |
| SkSurface* surface = fImages[fCurrentImageIndex].fSurface.get(); | |
| return sk_ref_sp(surface); | |
| } | |
| void GraphiteVulkanWindowContext::onSwapBuffers() { | |
| // Submit the GPU work associated with the current backbuffer. If submission fails, exit early. | |
| if (!this->submitToGpu()) { | |
| return; | |
| } | |
| // Submit present operation to present queue | |
| auto renderCompletionSemaphore = &fImages[fCurrentImageIndex].fRenderCompletionSemaphore; | |
| const VkPresentInfoKHR presentInfo = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, // sType | |
| nullptr, // pNext | |
| 1, // waitSemaphoreCount | |
| renderCompletionSemaphore, // pWaitSemaphores | |
| 1, // swapchainCount | |
| &fSwapchain, // pSwapchains | |
| &fCurrentImageIndex, // pImageIndices | |
| nullptr}; // pResults | |
| fQueuePresentKHR(fPresentQueue, &presentInfo); | |
| } | |
| } // namespace skwindow::internal |