Initial Commit

This commit is contained in:
Yousef Khadadeh 2025-07-06 20:43:53 +03:00
commit 6088fc29ed
978 changed files with 353647 additions and 0 deletions

View file

@ -0,0 +1,641 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* Copyright 2023 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "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 <src/base/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/ToolUtils.h>
#include "GraphiteToolUtils.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)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))
, fCreateVkSurfaceFn(std::move(createVkSurface))
, fCanPresentFn(std::move(canPresent))
, fSurface(VK_NULL_HANDLE)
, fSwapchain(VK_NULL_HANDLE)
, fImages(nullptr)
, fImageLayouts(nullptr)
, fSurfaces(nullptr)
, fBackbuffers(nullptr) {
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;
VkPhysicalDeviceFeatures2 features;
if (!sk_gpu_test::CreateVkBackendContext(getInstanceProc,
&backendContext,
&extensions,
&features,
&fDebugCallback,
&fPresentQueueIndex,
fCanPresentFn,
fDisplayParams->createProtectedNativeBackend())) {
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
return;
}
if (!extensions.hasExtension(VK_KHR_SURFACE_EXTENSION_NAME, 25) ||
!extensions.hasExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, 68)) {
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
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) {
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
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 (fDebugCallback != VK_NULL_HANDLE) {
GET_PROC(DestroyDebugReportCallbackEXT);
}
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);
skgpu::graphite::ContextOptions contextOptions;
skgpu::graphite::ContextOptionsPriv contextOptionsPriv;
// Needed to make synchronous readPixels work
contextOptionsPriv.fStoreContextRefInRecorder = true;
contextOptions.fOptionsPriv = &contextOptionsPriv;
fGraphiteContext = skgpu::graphite::ContextFactory::MakeVulkan(backendContext, contextOptions);
fGraphiteRecorder = fGraphiteContext->makeRecorder(ToolUtils::CreateTestingRecorderOptions());
fSurface = fCreateVkSurfaceFn(fInstance);
if (VK_NULL_HANDLE == fSurface) {
this->destroyContext();
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
return;
}
VkBool32 supported;
VkResult res = fGetPhysicalDeviceSurfaceSupportKHR(
fPhysicalDevice, fPresentQueueIndex, fSurface, &supported);
if (VK_SUCCESS != res) {
this->destroyContext();
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
return;
}
if (!this->createSwapchain(-1, -1)) {
this->destroyContext();
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
return;
}
// create presentQueue
fGetDeviceQueue(fDevice, fPresentQueueIndex, 0, &fPresentQueue);
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
}
bool GraphiteVulkanWindowContext::createSwapchain(int width, int height) {
// check for capabilities
VkSurfaceCapabilitiesKHR caps;
VkResult res = fGetPhysicalDeviceSurfaceCapabilitiesKHR(fPhysicalDevice, fSurface, &caps);
if (VK_SUCCESS != res) {
return false;
}
uint32_t surfaceFormatCount;
res = fGetPhysicalDeviceSurfaceFormatsKHR(
fPhysicalDevice, fSurface, &surfaceFormatCount, nullptr);
if (VK_SUCCESS != res) {
return false;
}
SkAutoMalloc surfaceFormatAlloc(surfaceFormatCount * sizeof(VkSurfaceFormatKHR));
VkSurfaceFormatKHR* surfaceFormats = (VkSurfaceFormatKHR*)surfaceFormatAlloc.get();
res = fGetPhysicalDeviceSurfaceFormatsKHR(
fPhysicalDevice, fSurface, &surfaceFormatCount, surfaceFormats);
if (VK_SUCCESS != res) {
return false;
}
uint32_t presentModeCount;
res = fGetPhysicalDeviceSurfacePresentModesKHR(
fPhysicalDevice, fSurface, &presentModeCount, nullptr);
if (VK_SUCCESS != res) {
return false;
}
SkAutoMalloc presentModeAlloc(presentModeCount * sizeof(VkPresentModeKHR));
VkPresentModeKHR* presentModes = (VkPresentModeKHR*)presentModeAlloc.get();
res = fGetPhysicalDeviceSurfacePresentModesKHR(
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);
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;
}
SkColorType colorType;
switch (surfaceFormat) {
case VK_FORMAT_R8G8B8A8_UNORM:
colorType = kRGBA_8888_SkColorType;
break;
case VK_FORMAT_B8G8R8A8_UNORM:
colorType = kBGRA_8888_SkColorType;
break;
default:
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 = fSurface;
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->destroyBuffers();
fDestroySwapchainKHR(fDevice, swapchainCreateInfo.oldSwapchain, nullptr);
swapchainCreateInfo.oldSwapchain = VK_NULL_HANDLE;
}
if (!this->createBuffers(swapchainCreateInfo.imageFormat,
usageFlags,
colorType,
swapchainCreateInfo.imageSharingMode)) {
fDeviceWaitIdle(fDevice);
this->destroyBuffers();
fDestroySwapchainKHR(fDevice, swapchainCreateInfo.oldSwapchain, nullptr);
swapchainCreateInfo.oldSwapchain = VK_NULL_HANDLE;
return false;
}
return true;
}
bool GraphiteVulkanWindowContext::createBuffers(VkFormat format,
VkImageUsageFlags usageFlags,
SkColorType colorType,
VkSharingMode sharingMode) {
fGetSwapchainImagesKHR(fDevice, fSwapchain, &fImageCount, nullptr);
SkASSERT(fImageCount);
fImages = new VkImage[fImageCount];
fGetSwapchainImagesKHR(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;
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],
skgpu::VulkanAlloc());
fSurfaces[i] = SkSurfaces::WrapBackendTexture(this->graphiteRecorder(),
backendTex,
colorType,
fDisplayParams->colorSpace(),
&fDisplayParams->surfaceProps());
if (!fSurfaces[i]) {
return false;
}
}
// 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;
// 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;
VkResult result;
VULKAN_CALL_RESULT_NOCHECK(
fInterface,
result,
CreateSemaphore(
fDevice, &semaphoreInfo, nullptr, &fBackbuffers[i].fRenderSemaphore));
}
fCurrentBackbufferIndex = fImageCount;
return true;
}
void GraphiteVulkanWindowContext::destroyBuffers() {
if (fBackbuffers) {
for (uint32_t i = 0; i < fImageCount + 1; ++i) {
fBackbuffers[i].fImageIndex = -1;
VULKAN_CALL(fInterface,
DestroySemaphore(fDevice, fBackbuffers[i].fRenderSemaphore, nullptr));
}
}
delete[] fBackbuffers;
fBackbuffers = nullptr;
// Does this actually free the surfaces?
delete[] fSurfaces;
fSurfaces = nullptr;
delete[] fImageLayouts;
fImageLayouts = nullptr;
delete[] fImages;
fImages = nullptr;
}
GraphiteVulkanWindowContext::~GraphiteVulkanWindowContext() { this->destroyContext(); }
void GraphiteVulkanWindowContext::destroyContext() {
if (this->isValid()) {
if (fPresentQueue != VK_NULL_HANDLE) {
fQueueWaitIdle(fPresentQueue);
}
fDeviceWaitIdle(fDevice);
if (fWaitSemaphore != VK_NULL_HANDLE) {
VULKAN_CALL(fInterface, DestroySemaphore(fDevice, fWaitSemaphore, nullptr));
fWaitSemaphore = VK_NULL_HANDLE;
}
this->destroyBuffers();
if (fSwapchain != VK_NULL_HANDLE) {
fDestroySwapchainKHR(fDevice, fSwapchain, nullptr);
fSwapchain = VK_NULL_HANDLE;
}
if (fSurface != VK_NULL_HANDLE) {
fDestroySurfaceKHR(fInstance, fSurface, nullptr);
fSurface = VK_NULL_HANDLE;
}
}
if (fGraphiteContext) {
fGraphiteRecorder.reset();
fGraphiteContext.reset();
}
fInterface.reset();
if (fDevice != VK_NULL_HANDLE) {
fDestroyDevice(fDevice, nullptr);
fDevice = VK_NULL_HANDLE;
}
#ifdef SK_ENABLE_VK_LAYERS
if (fDebugCallback != VK_NULL_HANDLE) {
fDestroyDebugReportCallbackEXT(fInstance, fDebugCallback, nullptr);
}
#endif
fPhysicalDevice = VK_NULL_HANDLE;
if (fInstance != VK_NULL_HANDLE) {
fDestroyInstance(fInstance, nullptr);
fInstance = VK_NULL_HANDLE;
}
}
GraphiteVulkanWindowContext::BackbufferInfo* GraphiteVulkanWindowContext::getAvailableBackbuffer() {
SkASSERT(fBackbuffers);
++fCurrentBackbufferIndex;
if (fCurrentBackbufferIndex > fImageCount) {
fCurrentBackbufferIndex = 0;
}
BackbufferInfo* backbuffer = fBackbuffers + fCurrentBackbufferIndex;
return backbuffer;
}
sk_sp<SkSurface> GraphiteVulkanWindowContext::getBackbufferSurface() {
BackbufferInfo* backbuffer = this->getAvailableBackbuffer();
SkASSERT(backbuffer);
// semaphores should be in unsignaled state
VkSemaphoreCreateInfo semaphoreInfo;
memset(&semaphoreInfo, 0, sizeof(VkSemaphoreCreateInfo));
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreInfo.pNext = nullptr;
semaphoreInfo.flags = 0;
VkResult result;
VULKAN_CALL_RESULT_NOCHECK(
fInterface, result, CreateSemaphore(fDevice, &semaphoreInfo, nullptr, &fWaitSemaphore));
// acquire the image
VkResult res = fAcquireNextImageKHR(fDevice,
fSwapchain,
UINT64_MAX,
fWaitSemaphore,
VK_NULL_HANDLE,
&backbuffer->fImageIndex);
if (VK_ERROR_SURFACE_LOST_KHR == res) {
// TODO: Recreate fSurface using fCreateVkSurfaceFn, and then rebuild the swapchain
VULKAN_CALL(fInterface, DestroySemaphore(fDevice, fWaitSemaphore, nullptr));
return nullptr;
}
if (VK_ERROR_OUT_OF_DATE_KHR == res) {
// tear swapchain down and try again
if (!this->createSwapchain(-1, -1)) {
VULKAN_CALL(fInterface, DestroySemaphore(fDevice, fWaitSemaphore, nullptr));
return nullptr;
}
backbuffer = this->getAvailableBackbuffer();
// acquire the image
res = fAcquireNextImageKHR(fDevice,
fSwapchain,
UINT64_MAX,
fWaitSemaphore,
VK_NULL_HANDLE,
&backbuffer->fImageIndex);
if (VK_SUCCESS != res) {
VULKAN_CALL(fInterface, DestroySemaphore(fDevice, fWaitSemaphore, nullptr));
return nullptr;
}
}
SkSurface* surface = fSurfaces[backbuffer->fImageIndex].get();
return sk_ref_sp(surface);
}
void GraphiteVulkanWindowContext::onSwapBuffers() {
if (!fGraphiteContext) {
return;
}
SkASSERT(fGraphiteRecorder);
BackbufferInfo* backbuffer = fBackbuffers + fCurrentBackbufferIndex;
// Rather than using submitToGpu we explicitly do that work here
// so we can set up the swapchain semaphores.
std::unique_ptr<skgpu::graphite::Recording> recording = fGraphiteRecorder->snap();
if (recording) {
skgpu::graphite::InsertRecordingInfo info;
info.fRecording = recording.get();
// set up surface for layout transition
info.fTargetSurface = fSurfaces[backbuffer->fImageIndex].get();
skgpu::MutableTextureState presentState = skgpu::MutableTextureStates::MakeVulkan(
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, fPresentQueueIndex);
info.fTargetTextureState = &presentState;
SkASSERT(fWaitSemaphore != VK_NULL_HANDLE);
auto beWaitSemaphore = skgpu::graphite::BackendSemaphores::MakeVulkan(fWaitSemaphore);
info.fNumWaitSemaphores = 1;
info.fWaitSemaphores = &beWaitSemaphore;
auto beSignalSemaphore =
skgpu::graphite::BackendSemaphores::MakeVulkan(backbuffer->fRenderSemaphore);
info.fNumSignalSemaphores = 1;
info.fSignalSemaphores = &beSignalSemaphore;
// Insert finishedProc to delete waitSemaphore when done
struct FinishContext {
sk_sp<const skgpu::VulkanInterface> interface;
VkDevice device;
VkSemaphore waitSemaphore;
};
auto* finishContext = new FinishContext{fInterface, fDevice, fWaitSemaphore};
skgpu::graphite::GpuFinishedProc finishCallback = [](skgpu::graphite::GpuFinishedContext c,
skgpu::CallbackResult status) {
// regardless of the status we need to destroy the semaphore
const auto* context = reinterpret_cast<const FinishContext*>(c);
VULKAN_CALL(context->interface,
DestroySemaphore(context->device, context->waitSemaphore, nullptr));
};
info.fFinishedContext = finishContext;
info.fFinishedProc = finishCallback;
fGraphiteContext->insertRecording(info);
fGraphiteContext->submit(skgpu::graphite::SyncToCpu::kNo);
fWaitSemaphore = VK_NULL_HANDLE; // FinishCallback will destroy this
}
// Submit present operation to present queue
const VkPresentInfoKHR presentInfo = {
VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, // sType
nullptr, // pNext
1, // waitSemaphoreCount
&backbuffer->fRenderSemaphore, // pWaitSemaphores
1, // swapchainCount
&fSwapchain, // pSwapchains
&backbuffer->fImageIndex, // pImageIndices
nullptr // pResults
};
fQueuePresentKHR(fPresentQueue, &presentInfo);
}
} // namespace skwindow::internal

View file

@ -0,0 +1,119 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* Copyright 2023 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GraphiteVulkanWindowContext_DEFINED
#define GraphiteVulkanWindowContext_DEFINED
#include <include/core/SkTypes.h>
#include "VkTestUtils.h"
#include "WindowContext.h"
class GrRenderTarget;
namespace skgpu {
struct VulkanInterface;
}
namespace skwindow::internal {
class GraphiteVulkanWindowContext : public WindowContext {
public:
~GraphiteVulkanWindowContext() override;
sk_sp<SkSurface> getBackbufferSurface() override;
bool isValid() override { return fDevice != VK_NULL_HANDLE; }
void resize(int w, int h) override { this->createSwapchain(w, h); }
void setDisplayParams(std::unique_ptr<const DisplayParams> params) override {
this->destroyContext();
fDisplayParams = std::move(params);
this->initializeContext();
}
/** 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 = sk_gpu_test::CanPresentFn;
GraphiteVulkanWindowContext(std::unique_ptr<const DisplayParams>,
CreateVkSurfaceFn,
CanPresentFn,
PFN_vkGetInstanceProcAddr);
private:
void initializeContext();
void destroyContext();
struct BackbufferInfo {
uint32_t fImageIndex; // image this is associated with
VkSemaphore fRenderSemaphore; // we wait on this for rendering to be done
};
BackbufferInfo* getAvailableBackbuffer();
bool createSwapchain(int width, int height);
bool createBuffers(VkFormat format, VkImageUsageFlags, SkColorType colorType, VkSharingMode);
void destroyBuffers();
void onSwapBuffers() override;
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;
PFN_vkGetInstanceProcAddr fGetInstanceProcAddr = nullptr;
// WSI interface functions
PFN_vkDestroySurfaceKHR fDestroySurfaceKHR = nullptr;
PFN_vkGetPhysicalDeviceSurfaceSupportKHR fGetPhysicalDeviceSurfaceSupportKHR = nullptr;
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR fGetPhysicalDeviceSurfaceCapabilitiesKHR =
nullptr;
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR fGetPhysicalDeviceSurfaceFormatsKHR = nullptr;
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR fGetPhysicalDeviceSurfacePresentModesKHR =
nullptr;
PFN_vkCreateSwapchainKHR fCreateSwapchainKHR = nullptr;
PFN_vkDestroySwapchainKHR fDestroySwapchainKHR = nullptr;
PFN_vkGetSwapchainImagesKHR fGetSwapchainImagesKHR = nullptr;
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;
sk_sp<const skgpu::VulkanInterface> fInterface;
VkSurfaceKHR fSurface;
VkSwapchainKHR fSwapchain;
uint32_t fGraphicsQueueIndex;
VkQueue fGraphicsQueue;
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)
BackbufferInfo* fBackbuffers;
uint32_t fCurrentBackbufferIndex;
VkSemaphore fWaitSemaphore = VK_NULL_HANDLE;
};
} // namespace skwindow::internal
#endif // GraphiteVulkanWindowContext_DEFINED

View file

@ -0,0 +1,114 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* Copyright 2024 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "tools/graphite/GraphiteToolUtils.h"
#include <include/core/SkCanvas.h>
#include <include/core/SkImage.h>
#include <include/core/SkTiledImageUtils.h>
#include <include/gpu/graphite/Image.h>
#include <include/gpu/graphite/ImageProvider.h>
#include <include/gpu/graphite/Recorder.h>
#include <src/core/SkLRUCache.h>
namespace {
// Currently, we give each new Recorder its own ImageProvider. This means we don't have to deal
// w/ any threading issues.
// TODO: We should probably have this class generate and report some cache stats
// TODO: Hook up to listener system?
// TODO: add testing of a single ImageProvider passed to multiple recorders
class TestingImageProvider : public skgpu::graphite::ImageProvider {
public:
TestingImageProvider() : fCache(kDefaultNumCachedImages) {}
~TestingImageProvider() override {}
sk_sp<SkImage> findOrCreate(skgpu::graphite::Recorder* recorder,
const SkImage* image,
SkImage::RequiredProperties requiredProps) override {
if (!requiredProps.fMipmapped) {
// If no mipmaps are required, check to see if we have a mipmapped version anyway -
// since it can be used in that case.
// TODO: we could get fancy and, if ever a mipmapped key eclipsed a non-mipmapped
// key, we could remove the hidden non-mipmapped key/image from the cache.
ImageKey mipMappedKey(image, /* mipmapped= */ true);
auto result = fCache.find(mipMappedKey);
if (result) {
return *result;
}
}
ImageKey key(image, requiredProps.fMipmapped);
auto result = fCache.find(key);
if (result) {
return *result;
}
sk_sp<SkImage> newImage = SkImages::TextureFromImage(recorder, image, requiredProps);
if (!newImage) {
return nullptr;
}
result = fCache.insert(key, std::move(newImage));
SkASSERT(result);
return *result;
}
private:
static constexpr int kDefaultNumCachedImages = 256;
class ImageKey {
public:
ImageKey(const SkImage* image, bool mipmapped) {
uint32_t flags = mipmapped ? 0x1 : 0x0;
SkTiledImageUtils::GetImageKeyValues(image, &fValues[1]);
fValues[kNumValues - 1] = flags;
fValues[0] = SkChecksum::Hash32(&fValues[1], (kNumValues - 1) * sizeof(uint32_t));
}
uint32_t hash() const { return fValues[0]; }
bool operator==(const ImageKey& other) const {
for (int i = 0; i < kNumValues; ++i) {
if (fValues[i] != other.fValues[i]) {
return false;
}
}
return true;
}
bool operator!=(const ImageKey& other) const { return !(*this == other); }
private:
static const int kNumValues = SkTiledImageUtils::kNumImageKeyValues + 2;
uint32_t fValues[kNumValues];
};
struct ImageHash {
size_t operator()(const ImageKey& key) const { return key.hash(); }
};
SkLRUCache<ImageKey, sk_sp<SkImage>, ImageHash> fCache;
};
} // anonymous namespace
namespace ToolUtils {
skgpu::graphite::RecorderOptions CreateTestingRecorderOptions() {
skgpu::graphite::RecorderOptions options;
options.fImageProvider.reset(new TestingImageProvider);
return options;
}
} // namespace ToolUtils

View file

@ -0,0 +1,25 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* Copyright 2024 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GraphteToolUtils_DEFINED
#define GraphteToolUtils_DEFINED
#include "include/core/SkTypes.h"
namespace skgpu::graphite {
struct RecorderOptions;
}
namespace ToolUtils {
skgpu::graphite::RecorderOptions CreateTestingRecorderOptions();
} // namespace ToolUtils
#endif

View file

@ -0,0 +1,267 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* Copyright 2024 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include <tools/gpu/vk/VkTestMemoryAllocator.h>
#include <include/gpu/vk/VulkanExtensions.h>
#include <src/core/SkTraceEvent.h>
#include <src/gpu/vk/VulkanInterface.h>
namespace sk_gpu_test {
sk_sp<skgpu::VulkanMemoryAllocator> VkTestMemoryAllocator::Make(
VkInstance instance,
VkPhysicalDevice physicalDevice,
VkDevice device,
uint32_t physicalDeviceVersion,
const skgpu::VulkanExtensions* extensions,
const skgpu::VulkanInterface* interface) {
#define SKGPU_COPY_FUNCTION(NAME) functions.vk##NAME = interface->fFunctions.f##NAME
#define SKGPU_COPY_FUNCTION_KHR(NAME) functions.vk##NAME##KHR = interface->fFunctions.f##NAME
VmaVulkanFunctions functions;
// We should be setting all the required functions (at least through vulkan 1.1), but this is
// just extra belt and suspenders to make sure there isn't unitialized values here.
memset(&functions, 0, sizeof(VmaVulkanFunctions));
// We don't use dynamic function getting in the allocator so we set the getProc functions to
// null.
functions.vkGetInstanceProcAddr = nullptr;
functions.vkGetDeviceProcAddr = nullptr;
SKGPU_COPY_FUNCTION(GetPhysicalDeviceProperties);
SKGPU_COPY_FUNCTION(GetPhysicalDeviceMemoryProperties);
SKGPU_COPY_FUNCTION(AllocateMemory);
SKGPU_COPY_FUNCTION(FreeMemory);
SKGPU_COPY_FUNCTION(MapMemory);
SKGPU_COPY_FUNCTION(UnmapMemory);
SKGPU_COPY_FUNCTION(FlushMappedMemoryRanges);
SKGPU_COPY_FUNCTION(InvalidateMappedMemoryRanges);
SKGPU_COPY_FUNCTION(BindBufferMemory);
SKGPU_COPY_FUNCTION(BindImageMemory);
SKGPU_COPY_FUNCTION(GetBufferMemoryRequirements);
SKGPU_COPY_FUNCTION(GetImageMemoryRequirements);
SKGPU_COPY_FUNCTION(CreateBuffer);
SKGPU_COPY_FUNCTION(DestroyBuffer);
SKGPU_COPY_FUNCTION(CreateImage);
SKGPU_COPY_FUNCTION(DestroyImage);
SKGPU_COPY_FUNCTION(CmdCopyBuffer);
SKGPU_COPY_FUNCTION_KHR(GetBufferMemoryRequirements2);
SKGPU_COPY_FUNCTION_KHR(GetImageMemoryRequirements2);
SKGPU_COPY_FUNCTION_KHR(BindBufferMemory2);
SKGPU_COPY_FUNCTION_KHR(BindImageMemory2);
SKGPU_COPY_FUNCTION_KHR(GetPhysicalDeviceMemoryProperties2);
VmaAllocatorCreateInfo info;
info.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
if (physicalDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) ||
(extensions->hasExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, 1) &&
extensions->hasExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 1))) {
info.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT;
}
info.physicalDevice = physicalDevice;
info.device = device;
// 4MB was picked for the size here by looking at memory usage of Android apps and runs of DM.
// It seems to be a good compromise of not wasting unused allocated space and not making too
// many small allocations. The AMD allocator will start making blocks at 1/8 the max size and
// builds up block size as needed before capping at the max set here.
info.preferredLargeHeapBlockSize = 4 * 1024 * 1024;
info.pAllocationCallbacks = nullptr;
info.pDeviceMemoryCallbacks = nullptr;
info.pHeapSizeLimit = nullptr;
info.pVulkanFunctions = &functions;
info.instance = instance;
// TODO: Update our interface and headers to support vulkan 1.3 and add in the new required
// functions for 1.3 that the allocator needs. Until then we just clamp the version to 1.1.
info.vulkanApiVersion = std::min(physicalDeviceVersion, VK_MAKE_VERSION(1, 1, 0));
info.pTypeExternalMemoryHandleTypes = nullptr;
VmaAllocator allocator;
vmaCreateAllocator(&info, &allocator);
return sk_sp<VkTestMemoryAllocator>(new VkTestMemoryAllocator(allocator));
}
VkTestMemoryAllocator::VkTestMemoryAllocator(VmaAllocator allocator) : fAllocator(allocator) {}
VkTestMemoryAllocator::~VkTestMemoryAllocator() {
vmaDestroyAllocator(fAllocator);
fAllocator = VK_NULL_HANDLE;
}
VkResult VkTestMemoryAllocator::allocateImageMemory(VkImage image,
uint32_t allocationPropertyFlags,
skgpu::VulkanBackendMemory* backendMemory) {
TRACE_EVENT0_ALWAYS("skia.gpu", TRACE_FUNC);
VmaAllocationCreateInfo info;
info.flags = 0;
info.usage = VMA_MEMORY_USAGE_UNKNOWN;
info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
info.preferredFlags = 0;
info.memoryTypeBits = 0;
info.pool = VK_NULL_HANDLE;
info.pUserData = nullptr;
if (kDedicatedAllocation_AllocationPropertyFlag & allocationPropertyFlags) {
info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
}
if (kLazyAllocation_AllocationPropertyFlag & allocationPropertyFlags) {
info.requiredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
}
if (kProtected_AllocationPropertyFlag & allocationPropertyFlags) {
info.requiredFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
}
VmaAllocation allocation;
VkResult result = vmaAllocateMemoryForImage(fAllocator, image, &info, &allocation, nullptr);
if (VK_SUCCESS == result) {
*backendMemory = (skgpu::VulkanBackendMemory)allocation;
}
return result;
}
VkResult VkTestMemoryAllocator::allocateBufferMemory(VkBuffer buffer,
BufferUsage usage,
uint32_t allocationPropertyFlags,
skgpu::VulkanBackendMemory* backendMemory) {
TRACE_EVENT0("skia.gpu", TRACE_FUNC);
VmaAllocationCreateInfo info;
info.flags = 0;
info.usage = VMA_MEMORY_USAGE_UNKNOWN;
info.memoryTypeBits = 0;
info.pool = VK_NULL_HANDLE;
info.pUserData = nullptr;
switch (usage) {
case BufferUsage::kGpuOnly:
info.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
info.preferredFlags = 0;
break;
case BufferUsage::kCpuWritesGpuReads:
// When doing cpu writes and gpu reads the general rule of thumb is to use coherent
// memory. Though this depends on the fact that we are not doing any cpu reads and the
// cpu writes are sequential. For sparse writes we'd want cpu cached memory, however we
// don't do these types of writes in Skia.
//
// TODO: In the future there may be times where specific types of memory could benefit
// from a coherent and cached memory. Typically these allow for the gpu to read cpu
// writes from the cache without needing to flush the writes throughout the cache. The
// reverse is not true and GPU writes tend to invalidate the cache regardless. Also
// these gpu cache read access are typically lower bandwidth than non-cached memory.
// For now Skia doesn't really have a need or want of this type of memory. But if we
// ever do we could pass in an AllocationPropertyFlag that requests the cached property.
info.requiredFlags =
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
info.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
break;
case BufferUsage::kTransfersFromCpuToGpu:
info.requiredFlags =
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
info.preferredFlags = 0;
break;
case BufferUsage::kTransfersFromGpuToCpu:
info.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
info.preferredFlags = VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
break;
}
if (kDedicatedAllocation_AllocationPropertyFlag & allocationPropertyFlags) {
info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
}
if ((kLazyAllocation_AllocationPropertyFlag & allocationPropertyFlags) &&
BufferUsage::kGpuOnly == usage) {
info.preferredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
}
if (kPersistentlyMapped_AllocationPropertyFlag & allocationPropertyFlags) {
SkASSERT(BufferUsage::kGpuOnly != usage);
info.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
}
if (kProtected_AllocationPropertyFlag & allocationPropertyFlags) {
info.requiredFlags |= VK_MEMORY_PROPERTY_PROTECTED_BIT;
}
VmaAllocation allocation;
VkResult result = vmaAllocateMemoryForBuffer(fAllocator, buffer, &info, &allocation, nullptr);
if (VK_SUCCESS == result) {
*backendMemory = (skgpu::VulkanBackendMemory)allocation;
}
return result;
}
void VkTestMemoryAllocator::freeMemory(const skgpu::VulkanBackendMemory& memoryHandle) {
TRACE_EVENT0("skia.gpu", TRACE_FUNC);
const VmaAllocation allocation = (VmaAllocation)memoryHandle;
vmaFreeMemory(fAllocator, allocation);
}
void VkTestMemoryAllocator::getAllocInfo(const skgpu::VulkanBackendMemory& memoryHandle,
skgpu::VulkanAlloc* alloc) const {
const VmaAllocation allocation = (VmaAllocation)memoryHandle;
VmaAllocationInfo vmaInfo;
vmaGetAllocationInfo(fAllocator, allocation, &vmaInfo);
VkMemoryPropertyFlags memFlags;
vmaGetMemoryTypeProperties(fAllocator, vmaInfo.memoryType, &memFlags);
uint32_t flags = 0;
if (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT & memFlags) {
flags |= skgpu::VulkanAlloc::kMappable_Flag;
}
if (!SkToBool(VK_MEMORY_PROPERTY_HOST_COHERENT_BIT & memFlags)) {
flags |= skgpu::VulkanAlloc::kNoncoherent_Flag;
}
if (VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT & memFlags) {
flags |= skgpu::VulkanAlloc::kLazilyAllocated_Flag;
}
alloc->fMemory = vmaInfo.deviceMemory;
alloc->fOffset = vmaInfo.offset;
alloc->fSize = vmaInfo.size;
alloc->fFlags = flags;
alloc->fBackendMemory = memoryHandle;
}
VkResult VkTestMemoryAllocator::mapMemory(const skgpu::VulkanBackendMemory& memoryHandle,
void** data) {
TRACE_EVENT0("skia.gpu", TRACE_FUNC);
const VmaAllocation allocation = (VmaAllocation)memoryHandle;
return vmaMapMemory(fAllocator, allocation, data);
}
void VkTestMemoryAllocator::unmapMemory(const skgpu::VulkanBackendMemory& memoryHandle) {
TRACE_EVENT0("skia.gpu", TRACE_FUNC);
const VmaAllocation allocation = (VmaAllocation)memoryHandle;
vmaUnmapMemory(fAllocator, allocation);
}
VkResult VkTestMemoryAllocator::flushMemory(const skgpu::VulkanBackendMemory& memoryHandle,
VkDeviceSize offset,
VkDeviceSize size) {
TRACE_EVENT0("skia.gpu", TRACE_FUNC);
const VmaAllocation allocation = (VmaAllocation)memoryHandle;
return vmaFlushAllocation(fAllocator, allocation, offset, size);
}
VkResult VkTestMemoryAllocator::invalidateMemory(const skgpu::VulkanBackendMemory& memoryHandle,
VkDeviceSize offset,
VkDeviceSize size) {
TRACE_EVENT0("skia.gpu", TRACE_FUNC);
const VmaAllocation allocation = (VmaAllocation)memoryHandle;
return vmaInvalidateAllocation(fAllocator, allocation, offset, size);
}
std::pair<uint64_t, uint64_t> VkTestMemoryAllocator::totalAllocatedAndUsedMemory() const {
VmaTotalStatistics stats;
vmaCalculateStatistics(fAllocator, &stats);
return {stats.total.statistics.blockBytes, stats.total.statistics.allocationBytes};
}
} // namespace sk_gpu_test

View file

@ -0,0 +1,69 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* Copyright 2024 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef VkTestMemoryAllocator_DEFINED
#define VkTestMemoryAllocator_DEFINED
#include <vk_mem_alloc.h>
#include <include/gpu/vk/VulkanMemoryAllocator.h>
namespace skgpu {
class VulkanExtensions;
struct VulkanInterface;
} // namespace skgpu
namespace sk_gpu_test {
// A test-only Vulkan memory allocator. Based on
// https://skia.googlesource.com/skia/+/c3fbd20fbc7d3662dd31a0e4139226d2951d1ae4/src/gpu/vk/VulkanAMDMemoryAllocator.h.
class VkTestMemoryAllocator : public skgpu::VulkanMemoryAllocator {
public:
static sk_sp<VulkanMemoryAllocator> Make(VkInstance instance,
VkPhysicalDevice physicalDevice,
VkDevice device,
uint32_t physicalDeviceVersion,
const skgpu::VulkanExtensions* extensions,
const skgpu::VulkanInterface* interface);
~VkTestMemoryAllocator() override;
VkResult allocateImageMemory(VkImage image,
uint32_t allocationPropertyFlags,
skgpu::VulkanBackendMemory*) override;
VkResult allocateBufferMemory(VkBuffer buffer,
BufferUsage usage,
uint32_t allocationPropertyFlags,
skgpu::VulkanBackendMemory*) override;
void freeMemory(const skgpu::VulkanBackendMemory&) override;
void getAllocInfo(const skgpu::VulkanBackendMemory&, skgpu::VulkanAlloc*) const override;
VkResult mapMemory(const skgpu::VulkanBackendMemory&, void** data) override;
void unmapMemory(const skgpu::VulkanBackendMemory&) override;
VkResult flushMemory(const skgpu::VulkanBackendMemory&,
VkDeviceSize offset,
VkDeviceSize size) override;
VkResult invalidateMemory(const skgpu::VulkanBackendMemory&,
VkDeviceSize offset,
VkDeviceSize size) override;
std::pair<uint64_t, uint64_t> totalAllocatedAndUsedMemory() const override;
private:
VkTestMemoryAllocator(VmaAllocator allocator);
VmaAllocator fAllocator;
};
} // namespace sk_gpu_test
#endif // VkTestMemoryAllocator_DEFINED

View file

@ -0,0 +1,879 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* 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 <SDL3/SDL_vulkan.h> // Looking for instance required extensions for SDL
#include <src/gpu/vk/VulkanInterface.h>
#include "VkTestMemoryAllocator.h"
#include "VkTestUtils.h"
#ifndef SK_GPU_TOOLS_VK_LIBRARY_NAME
#if defined _WIN32
#define SK_GPU_TOOLS_VK_LIBRARY_NAME vulkan-1.dll
#elif defined SK_BUILD_FOR_MAC
#define SK_GPU_TOOLS_VK_LIBRARY_NAME libvk_swiftshader.dylib
#else
#define SK_GPU_TOOLS_VK_LIBRARY_NAME libvulkan.so
#define SK_GPU_TOOLS_VK_LIBRARY_NAME_BACKUP libvulkan.so.1
#endif
#endif
#define STRINGIFY2(S) #S
#define STRINGIFY(S) STRINGIFY2(S)
#include <algorithm>
#if defined(__GLIBC__)
#include <execinfo.h>
#endif
#include "include/gpu/vk/VulkanBackendContext.h"
#include "include/gpu/vk/VulkanExtensions.h"
#include "src/base/SkAutoMalloc.h"
#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS)
#include <sanitizer/lsan_interface.h>
#endif
using namespace skia_private;
namespace sk_gpu_test {
////////////////////////////////////////////////////////////////////////////////
// Helper code to set up Vulkan context objects
#ifdef SK_ENABLE_VK_LAYERS
const char* kDebugLayerNames[] = {
// single merged layer
"VK_LAYER_KHRONOS_validation",
// not included in standard_validation
//"VK_LAYER_LUNARG_api_dump",
//"VK_LAYER_LUNARG_vktrace",
//"VK_LAYER_LUNARG_screenshot",
};
static uint32_t remove_patch_version(uint32_t specVersion) {
return (specVersion >> 12) << 12;
}
// Returns the index into layers array for the layer we want. Returns -1 if not supported.
static int should_include_debug_layer(const char* layerName,
uint32_t layerCount, VkLayerProperties* layers,
uint32_t version) {
for (uint32_t i = 0; i < layerCount; ++i) {
if (!strcmp(layerName, layers[i].layerName)) {
// Since the layers intercept the vulkan calls and forward them on, we need to make sure
// layer was written against a version that isn't older than the version of Vulkan we're
// using so that it has all the api entry points.
if (version <= remove_patch_version(layers[i].specVersion)) {
return i;
}
return -1;
}
}
return -1;
}
static void print_backtrace() {
#if defined(__GLIBC__)
void* stack[64];
int count = backtrace(stack, std::size(stack));
backtrace_symbols_fd(stack, count, 2);
#else
// Please add implementations for other platforms.
#endif
}
VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(
VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType,
uint64_t object,
size_t location,
int32_t messageCode,
const char* pLayerPrefix,
const char* pMessage,
void* pUserData) {
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) {
// See https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/1887
if (strstr(pMessage, "VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-01521") ||
strstr(pMessage, "VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-01522")) {
return VK_FALSE;
}
// See https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/2171
if (strstr(pMessage, "VUID-vkCmdDraw-None-02686") ||
strstr(pMessage, "VUID-vkCmdDrawIndexed-None-02686")) {
return VK_FALSE;
}
SkDebugf("Vulkan error [%s]: code: %d: %s\n", pLayerPrefix, messageCode, pMessage);
print_backtrace();
SkDEBUGFAIL("Vulkan debug layer error");
return VK_TRUE; // skip further layers
} else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) {
SkDebugf("Vulkan warning [%s]: code: %d: %s\n", pLayerPrefix, messageCode, pMessage);
print_backtrace();
} else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT) {
SkDebugf("Vulkan perf warning [%s]: code: %d: %s\n", pLayerPrefix, messageCode, pMessage);
print_backtrace();
} else {
SkDebugf("Vulkan info/debug [%s]: code: %d: %s\n", pLayerPrefix, messageCode, pMessage);
}
return VK_FALSE;
}
#endif
#define ACQUIRE_VK_INST_PROC_LOCAL(name, instance) \
PFN_vk##name grVk##name = \
reinterpret_cast<PFN_vk##name>(getInstProc(instance, "vk" #name)); \
do { \
if (grVk##name == nullptr) { \
SkDebugf("Function ptr for vk%s could not be acquired\n", #name); \
return false; \
} \
} while (0)
// Returns the index into layers array for the layer we want. Returns -1 if not supported.
static bool should_include_extension(const char* extensionName) {
const char* kValidExtensions[] = {
// single merged layer
VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME,
VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME,
VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
VK_EXT_FRAME_BOUNDARY_EXTENSION_NAME,
VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
VK_EXT_RGBA10X6_FORMATS_EXTENSION_NAME,
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,
VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME,
VK_KHR_MAINTENANCE1_EXTENSION_NAME,
VK_KHR_MAINTENANCE2_EXTENSION_NAME,
VK_KHR_MAINTENANCE3_EXTENSION_NAME,
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
VK_KHR_SURFACE_EXTENSION_NAME,
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
// Below are all platform specific extensions. The name macros like we use above are
// all defined in platform specific vulkan headers. We currently don't include these
// headers as they are a little bit of a pain (e.g. windows headers requires including
// <windows.h> which causes all sorts of fun annoyances/problems. So instead we are
// just listing the strings these macros are defined to. This really shouldn't cause
// any long term issues as the chances of the strings connected to the name macros
// changing is next to zero.
"VK_KHR_win32_surface", // VK_KHR_WIN32_SURFACE_EXTENSION_NAME
"VK_KHR_xcb_surface", // VK_KHR_XCB_SURFACE_EXTENSION_NAME,
"VK_ANDROID_external_memory_android_hardware_buffer",
// VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
"VK_KHR_android_surface", // VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
};
for (size_t i = 0; i < std::size(kValidExtensions); ++i) {
if (!strcmp(extensionName, kValidExtensions[i])) {
return true;
}
}
return false;
}
static bool init_instance_extensions_and_layers(PFN_vkGetInstanceProcAddr getInstProc,
uint32_t specVersion,
TArray<VkExtensionProperties>* instanceExtensions,
TArray<VkLayerProperties>* instanceLayers) {
if (getInstProc == nullptr) {
return false;
}
ACQUIRE_VK_INST_PROC_LOCAL(EnumerateInstanceExtensionProperties, VK_NULL_HANDLE);
ACQUIRE_VK_INST_PROC_LOCAL(EnumerateInstanceLayerProperties, VK_NULL_HANDLE);
VkResult res;
uint32_t layerCount = 0;
#ifdef SK_ENABLE_VK_LAYERS
// instance layers
res = grVkEnumerateInstanceLayerProperties(&layerCount, nullptr);
if (VK_SUCCESS != res) {
return false;
}
VkLayerProperties* layers = new VkLayerProperties[layerCount];
res = grVkEnumerateInstanceLayerProperties(&layerCount, layers);
if (VK_SUCCESS != res) {
delete[] layers;
return false;
}
uint32_t nonPatchVersion = remove_patch_version(specVersion);
for (size_t i = 0; i < std::size(kDebugLayerNames); ++i) {
int idx = should_include_debug_layer(kDebugLayerNames[i], layerCount, layers,
nonPatchVersion);
if (idx != -1) {
instanceLayers->push_back() = layers[idx];
}
}
delete[] layers;
#endif
// instance extensions
// via Vulkan implementation and implicitly enabled layers
{
uint32_t extensionCount = 0;
res = grVkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
if (VK_SUCCESS != res) {
return false;
}
// Check if SDL extension requirements are met
uint32_t sdlRequiredExtensionCount = 0;
char const * const * sdlRequiredExtensions = SDL_Vulkan_GetInstanceExtensions(&sdlRequiredExtensionCount);
VkExtensionProperties* extensions = new VkExtensionProperties[extensionCount];
res = grVkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions);
if (VK_SUCCESS != res) {
delete[] extensions;
return false;
}
for (uint32_t i = 0; i < extensionCount; ++i) {
if (should_include_extension(extensions[i].extensionName)) {
instanceExtensions->push_back() = extensions[i];
}
else {
for(uint32_t j = 0; j < sdlRequiredExtensionCount; j++) {
if(strcmp(extensions[i].extensionName, sdlRequiredExtensions[j]) == 0) {
instanceExtensions->push_back() = extensions[i];
break;
}
}
}
}
delete [] extensions;
}
// via explicitly enabled layers
layerCount = instanceLayers->size();
for (uint32_t layerIndex = 0; layerIndex < layerCount; ++layerIndex) {
uint32_t extensionCount = 0;
res = grVkEnumerateInstanceExtensionProperties((*instanceLayers)[layerIndex].layerName,
&extensionCount, nullptr);
if (VK_SUCCESS != res) {
return false;
}
VkExtensionProperties* extensions = new VkExtensionProperties[extensionCount];
res = grVkEnumerateInstanceExtensionProperties((*instanceLayers)[layerIndex].layerName,
&extensionCount, extensions);
if (VK_SUCCESS != res) {
delete[] extensions;
return false;
}
for (uint32_t i = 0; i < extensionCount; ++i) {
if (should_include_extension(extensions[i].extensionName)) {
instanceExtensions->push_back() = extensions[i];
}
}
delete[] extensions;
}
return true;
}
#define GET_PROC_LOCAL(F, inst, device) PFN_vk ## F F = (PFN_vk ## F) getProc("vk" #F, inst, device)
static bool init_device_extensions_and_layers(const skgpu::VulkanGetProc& getProc,
uint32_t specVersion, VkInstance inst,
VkPhysicalDevice physDev,
TArray<VkExtensionProperties>* deviceExtensions,
TArray<VkLayerProperties>* deviceLayers) {
if (getProc == nullptr) {
return false;
}
GET_PROC_LOCAL(EnumerateDeviceExtensionProperties, inst, VK_NULL_HANDLE);
GET_PROC_LOCAL(EnumerateDeviceLayerProperties, inst, VK_NULL_HANDLE);
if (!EnumerateDeviceExtensionProperties ||
!EnumerateDeviceLayerProperties) {
return false;
}
VkResult res;
// device layers
uint32_t layerCount = 0;
#ifdef SK_ENABLE_VK_LAYERS
res = EnumerateDeviceLayerProperties(physDev, &layerCount, nullptr);
if (VK_SUCCESS != res) {
return false;
}
VkLayerProperties* layers = new VkLayerProperties[layerCount];
res = EnumerateDeviceLayerProperties(physDev, &layerCount, layers);
if (VK_SUCCESS != res) {
delete[] layers;
return false;
}
uint32_t nonPatchVersion = remove_patch_version(specVersion);
for (size_t i = 0; i < std::size(kDebugLayerNames); ++i) {
int idx = should_include_debug_layer(kDebugLayerNames[i], layerCount, layers,
nonPatchVersion);
if (idx != -1) {
deviceLayers->push_back() = layers[idx];
}
}
delete[] layers;
#endif
// device extensions
// via Vulkan implementation and implicitly enabled layers
{
uint32_t extensionCount = 0;
res = EnumerateDeviceExtensionProperties(physDev, nullptr, &extensionCount, nullptr);
if (VK_SUCCESS != res) {
return false;
}
VkExtensionProperties* extensions = new VkExtensionProperties[extensionCount];
res = EnumerateDeviceExtensionProperties(physDev, nullptr, &extensionCount, extensions);
if (VK_SUCCESS != res) {
delete[] extensions;
return false;
}
for (uint32_t i = 0; i < extensionCount; ++i) {
if (should_include_extension(extensions[i].extensionName)) {
deviceExtensions->push_back() = extensions[i];
}
}
delete[] extensions;
}
// via explicitly enabled layers
layerCount = deviceLayers->size();
for (uint32_t layerIndex = 0; layerIndex < layerCount; ++layerIndex) {
uint32_t extensionCount = 0;
res = EnumerateDeviceExtensionProperties(physDev,
(*deviceLayers)[layerIndex].layerName,
&extensionCount, nullptr);
if (VK_SUCCESS != res) {
return false;
}
VkExtensionProperties* extensions = new VkExtensionProperties[extensionCount];
res = EnumerateDeviceExtensionProperties(physDev,
(*deviceLayers)[layerIndex].layerName,
&extensionCount, extensions);
if (VK_SUCCESS != res) {
delete[] extensions;
return false;
}
for (uint32_t i = 0; i < extensionCount; ++i) {
if (should_include_extension(extensions[i].extensionName)) {
deviceExtensions->push_back() = extensions[i];
}
}
delete[] extensions;
}
return true;
}
#define ACQUIRE_VK_INST_PROC_NOCHECK(name, instance) \
PFN_vk##name grVk##name = reinterpret_cast<PFN_vk##name>(getInstProc(instance, "vk" #name))
#define ACQUIRE_VK_INST_PROC(name, instance) \
PFN_vk##name grVk##name = \
reinterpret_cast<PFN_vk##name>(getInstProc(instance, "vk" #name)); \
do { \
if (grVk##name == nullptr) { \
SkDebugf("Function ptr for vk%s could not be acquired\n", #name); \
if (inst != VK_NULL_HANDLE) { \
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension); \
} \
return false; \
} \
} while (0)
#define ACQUIRE_VK_PROC_NOCHECK(name, instance, device) \
PFN_vk##name grVk##name = reinterpret_cast<PFN_vk##name>(getProc("vk" #name, instance, device))
#define ACQUIRE_VK_PROC(name, instance, device) \
PFN_vk##name grVk##name = \
reinterpret_cast<PFN_vk##name>(getProc("vk" #name, instance, device)); \
do { \
if (grVk##name == nullptr) { \
SkDebugf("Function ptr for vk%s could not be acquired\n", #name); \
if (inst != VK_NULL_HANDLE) { \
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension); \
} \
return false; \
} \
} while (0)
#define ACQUIRE_VK_PROC_LOCAL(name, instance, device) \
PFN_vk##name grVk##name = \
reinterpret_cast<PFN_vk##name>(getProc("vk" #name, instance, device)); \
do { \
if (grVk##name == nullptr) { \
SkDebugf("Function ptr for vk%s could not be acquired\n", #name); \
return false; \
} \
} while (0)
static bool destroy_instance(PFN_vkGetInstanceProcAddr getInstProc, VkInstance inst,
VkDebugReportCallbackEXT* debugCallback,
bool hasDebugExtension) {
if (hasDebugExtension && *debugCallback != VK_NULL_HANDLE) {
ACQUIRE_VK_INST_PROC_LOCAL(DestroyDebugReportCallbackEXT, inst);
grVkDestroyDebugReportCallbackEXT(inst, *debugCallback, nullptr);
*debugCallback = VK_NULL_HANDLE;
}
ACQUIRE_VK_INST_PROC_LOCAL(DestroyInstance, inst);
grVkDestroyInstance(inst, nullptr);
return true;
}
static bool setup_features(const skgpu::VulkanGetProc& getProc, VkInstance inst,
VkPhysicalDevice physDev, uint32_t physDeviceVersion,
skgpu::VulkanExtensions* extensions, VkPhysicalDeviceFeatures2* features,
bool isProtected) {
SkASSERT(physDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) ||
extensions->hasExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 1));
// Setup all extension feature structs we may want to use.
void** tailPNext = &features->pNext;
// If |isProtected| is given, attach that first
VkPhysicalDeviceProtectedMemoryFeatures* protectedMemoryFeatures = nullptr;
if (isProtected) {
SkASSERT(physDeviceVersion >= VK_MAKE_VERSION(1, 1, 0));
protectedMemoryFeatures =
(VkPhysicalDeviceProtectedMemoryFeatures*)sk_malloc_throw(
sizeof(VkPhysicalDeviceProtectedMemoryFeatures));
protectedMemoryFeatures->sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
protectedMemoryFeatures->pNext = nullptr;
*tailPNext = protectedMemoryFeatures;
tailPNext = &protectedMemoryFeatures->pNext;
}
VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT* blend = nullptr;
if (extensions->hasExtension(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME, 2)) {
blend = (VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT*) sk_malloc_throw(
sizeof(VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT));
blend->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT;
blend->pNext = nullptr;
*tailPNext = blend;
tailPNext = &blend->pNext;
}
VkPhysicalDeviceSamplerYcbcrConversionFeatures* ycbcrFeature = nullptr;
if (physDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) ||
extensions->hasExtension(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, 1)) {
ycbcrFeature = (VkPhysicalDeviceSamplerYcbcrConversionFeatures*) sk_malloc_throw(
sizeof(VkPhysicalDeviceSamplerYcbcrConversionFeatures));
ycbcrFeature->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
ycbcrFeature->pNext = nullptr;
ycbcrFeature->samplerYcbcrConversion = VK_TRUE;
*tailPNext = ycbcrFeature;
tailPNext = &ycbcrFeature->pNext;
}
if (physDeviceVersion >= VK_MAKE_VERSION(1, 1, 0)) {
ACQUIRE_VK_PROC_LOCAL(GetPhysicalDeviceFeatures2, inst, VK_NULL_HANDLE);
grVkGetPhysicalDeviceFeatures2(physDev, features);
} else {
SkASSERT(extensions->hasExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
1));
ACQUIRE_VK_PROC_LOCAL(GetPhysicalDeviceFeatures2KHR, inst, VK_NULL_HANDLE);
grVkGetPhysicalDeviceFeatures2KHR(physDev, features);
}
if (isProtected) {
if (!protectedMemoryFeatures->protectedMemory) {
return false;
}
}
return true;
// If we want to disable any extension features do so here.
}
bool CreateVkBackendContext(PFN_vkGetInstanceProcAddr getInstProc,
skgpu::VulkanBackendContext* ctx,
skgpu::VulkanExtensions* extensions,
VkPhysicalDeviceFeatures2* features,
VkDebugReportCallbackEXT* debugCallback,
uint32_t* presentQueueIndexPtr,
const CanPresentFn& canPresent,
bool isProtected) {
VkResult err;
ACQUIRE_VK_INST_PROC_NOCHECK(EnumerateInstanceVersion, VK_NULL_HANDLE);
uint32_t instanceVersion = 0;
if (!grVkEnumerateInstanceVersion) {
instanceVersion = VK_MAKE_VERSION(1, 0, 0);
} else {
err = grVkEnumerateInstanceVersion(&instanceVersion);
if (err) {
SkDebugf("failed to enumerate instance version. Err: %d\n", err);
return false;
}
}
SkASSERT(instanceVersion >= VK_MAKE_VERSION(1, 0, 0));
if (isProtected && instanceVersion < VK_MAKE_VERSION(1, 1, 0)) {
SkDebugf("protected requires vk instance version 1.1\n");
return false;
}
uint32_t apiVersion = VK_MAKE_VERSION(1, 0, 0);
if (instanceVersion >= VK_MAKE_VERSION(1, 1, 0)) {
// If the instance version is 1.0 we must have the apiVersion also be 1.0. However, if the
// instance version is 1.1 or higher, we can set the apiVersion to be whatever the highest
// api we may use in skia (technically it can be arbitrary). So for now we set it to 1.1
// since that is the highest vulkan version.
apiVersion = VK_MAKE_VERSION(1, 1, 0);
}
instanceVersion = std::min(instanceVersion, apiVersion);
STArray<2, VkPhysicalDevice> physDevs;
VkDevice device;
VkInstance inst = VK_NULL_HANDLE;
const VkApplicationInfo app_info = {
VK_STRUCTURE_TYPE_APPLICATION_INFO, // sType
nullptr, // pNext
"vktest", // pApplicationName
0, // applicationVersion
"vktest", // pEngineName
0, // engineVerison
apiVersion, // apiVersion
};
TArray<VkLayerProperties> instanceLayers;
TArray<VkExtensionProperties> instanceExtensions;
if (!init_instance_extensions_and_layers(getInstProc, instanceVersion,
&instanceExtensions,
&instanceLayers)) {
return false;
}
TArray<const char*> instanceLayerNames;
TArray<const char*> instanceExtensionNames;
for (int i = 0; i < instanceLayers.size(); ++i) {
instanceLayerNames.push_back(instanceLayers[i].layerName);
}
for (int i = 0; i < instanceExtensions.size(); ++i) {
instanceExtensionNames.push_back(instanceExtensions[i].extensionName);
}
const VkInstanceCreateInfo instance_create = {
VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // sType
nullptr, // pNext
0, // flags
&app_info, // pApplicationInfo
(uint32_t) instanceLayerNames.size(), // enabledLayerNameCount
instanceLayerNames.begin(), // ppEnabledLayerNames
(uint32_t) instanceExtensionNames.size(), // enabledExtensionNameCount
instanceExtensionNames.begin(), // ppEnabledExtensionNames
};
bool hasDebugExtension = false;
ACQUIRE_VK_INST_PROC(CreateInstance, VK_NULL_HANDLE);
err = grVkCreateInstance(&instance_create, nullptr, &inst);
if (err < 0) {
SkDebugf("vkCreateInstance failed: %d\n", err);
return false;
}
ACQUIRE_VK_INST_PROC(GetDeviceProcAddr, inst);
auto getProc = [getInstProc, grVkGetDeviceProcAddr](const char* proc_name,
VkInstance instance, VkDevice device) {
if (device != VK_NULL_HANDLE) {
return grVkGetDeviceProcAddr(device, proc_name);
}
return getInstProc(instance, proc_name);
};
#ifdef SK_ENABLE_VK_LAYERS
*debugCallback = VK_NULL_HANDLE;
for (int i = 0; i < instanceExtensionNames.size() && !hasDebugExtension; ++i) {
if (!strcmp(instanceExtensionNames[i], VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) {
hasDebugExtension = true;
}
}
if (hasDebugExtension) {
// Setup callback creation information
VkDebugReportCallbackCreateInfoEXT callbackCreateInfo;
callbackCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
callbackCreateInfo.pNext = nullptr;
callbackCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT |
VK_DEBUG_REPORT_WARNING_BIT_EXT |
// VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
// VK_DEBUG_REPORT_DEBUG_BIT_EXT |
VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
callbackCreateInfo.pfnCallback = &DebugReportCallback;
callbackCreateInfo.pUserData = nullptr;
ACQUIRE_VK_PROC(CreateDebugReportCallbackEXT, inst, VK_NULL_HANDLE);
// Register the callback
grVkCreateDebugReportCallbackEXT(inst, &callbackCreateInfo, nullptr, debugCallback);
}
#endif
ACQUIRE_VK_PROC(EnumeratePhysicalDevices, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(GetPhysicalDeviceProperties, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(GetPhysicalDeviceQueueFamilyProperties, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(GetPhysicalDeviceFeatures, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(CreateDevice, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(GetDeviceQueue, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(DeviceWaitIdle, inst, VK_NULL_HANDLE);
ACQUIRE_VK_PROC(DestroyDevice, inst, VK_NULL_HANDLE);
uint32_t gpuCount;
err = grVkEnumeratePhysicalDevices(inst, &gpuCount, nullptr);
if (err) {
SkDebugf("vkEnumeratePhysicalDevices failed: %d\n", err);
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension);
return false;
}
if (!gpuCount) {
SkDebugf("vkEnumeratePhysicalDevices returned no supported devices.\n");
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension);
return false;
}
// Allocate enough storage for all available physical devices. We should be able to just ask for
// the first one, but a bug in RenderDoc (https://github.com/baldurk/renderdoc/issues/2766)
// will smash the stack if we do that.
physDevs.resize(gpuCount);
err = grVkEnumeratePhysicalDevices(inst, &gpuCount, physDevs.data());
if (err) {
SkDebugf("vkEnumeratePhysicalDevices failed: %d\n", err);
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension);
return false;
}
// We just use the first physical device.
// TODO: find best match for our needs
VkPhysicalDevice physDev = physDevs.front();
VkPhysicalDeviceProperties physDeviceProperties;
grVkGetPhysicalDeviceProperties(physDev, &physDeviceProperties);
uint32_t physDeviceVersion = std::min(physDeviceProperties.apiVersion, apiVersion);
if (isProtected && physDeviceVersion < VK_MAKE_VERSION(1, 1, 0)) {
SkDebugf("protected requires vk physical device version 1.1\n");
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension);
return false;
}
// query to get the initial queue props size
uint32_t queueCount;
grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
if (!queueCount) {
SkDebugf("vkGetPhysicalDeviceQueueFamilyProperties returned no queues.\n");
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension);
return false;
}
SkAutoMalloc queuePropsAlloc(queueCount * sizeof(VkQueueFamilyProperties));
// now get the actual queue props
VkQueueFamilyProperties* queueProps = (VkQueueFamilyProperties*)queuePropsAlloc.get();
grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueProps);
// iterate to find the graphics queue
uint32_t graphicsQueueIndex = queueCount;
for (uint32_t i = 0; i < queueCount; i++) {
if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
graphicsQueueIndex = i;
break;
}
}
if (graphicsQueueIndex == queueCount) {
SkDebugf("Could not find any supported graphics queues.\n");
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension);
return false;
}
// iterate to find the present queue, if needed
uint32_t presentQueueIndex = queueCount;
if (presentQueueIndexPtr && canPresent) {
for (uint32_t i = 0; i < queueCount; i++) {
if (canPresent(inst, physDev, i)) {
presentQueueIndex = i;
break;
}
}
if (presentQueueIndex == queueCount) {
SkDebugf("Could not find any supported present queues.\n");
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension);
return false;
}
*presentQueueIndexPtr = presentQueueIndex;
} else {
// Just setting this so we end up make a single queue for graphics since there was no
// request for a present queue.
presentQueueIndex = graphicsQueueIndex;
}
TArray<VkLayerProperties> deviceLayers;
TArray<VkExtensionProperties> deviceExtensions;
if (!init_device_extensions_and_layers(getProc, physDeviceVersion,
inst, physDev,
&deviceExtensions,
&deviceLayers)) {
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension);
return false;
}
TArray<const char*> deviceLayerNames;
TArray<const char*> deviceExtensionNames;
for (int i = 0; i < deviceLayers.size(); ++i) {
deviceLayerNames.push_back(deviceLayers[i].layerName);
}
for (int i = 0; i < deviceExtensions.size(); ++i) {
deviceExtensionNames.push_back(deviceExtensions[i].extensionName);
}
extensions->init(getProc, inst, physDev,
(uint32_t) instanceExtensionNames.size(),
instanceExtensionNames.begin(),
(uint32_t) deviceExtensionNames.size(),
deviceExtensionNames.begin());
memset(features, 0, sizeof(VkPhysicalDeviceFeatures2));
features->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
features->pNext = nullptr;
VkPhysicalDeviceFeatures* deviceFeatures = &features->features;
void* pointerToFeatures = nullptr;
if (physDeviceVersion >= VK_MAKE_VERSION(1, 1, 0) ||
extensions->hasExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, 1)) {
if (!setup_features(getProc, inst, physDev, physDeviceVersion, extensions, features,
isProtected)) {
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension);
return false;
}
// If we set the pNext of the VkDeviceCreateInfo to our VkPhysicalDeviceFeatures2 struct,
// the device creation will use that instead of the ppEnabledFeatures.
pointerToFeatures = features;
} else {
grVkGetPhysicalDeviceFeatures(physDev, deviceFeatures);
}
// this looks like it would slow things down,
// and we can't depend on it on all platforms
deviceFeatures->robustBufferAccess = VK_FALSE;
VkDeviceQueueCreateFlags flags = isProtected ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0;
float queuePriorities[1] = { 0.0 };
// Here we assume no need for swapchain queue
// If one is needed, the client will need its own setup code
const VkDeviceQueueCreateInfo queueInfo[2] = {
{
VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType
nullptr, // pNext
flags, // VkDeviceQueueCreateFlags
graphicsQueueIndex, // queueFamilyIndex
1, // queueCount
queuePriorities, // pQueuePriorities
},
{
VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType
nullptr, // pNext
0, // VkDeviceQueueCreateFlags
presentQueueIndex, // queueFamilyIndex
1, // queueCount
queuePriorities, // pQueuePriorities
}
};
uint32_t queueInfoCount = (presentQueueIndex != graphicsQueueIndex) ? 2 : 1;
const VkDeviceCreateInfo deviceInfo = {
VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, // sType
pointerToFeatures, // pNext
0, // VkDeviceCreateFlags
queueInfoCount, // queueCreateInfoCount
queueInfo, // pQueueCreateInfos
(uint32_t) deviceLayerNames.size(), // layerCount
deviceLayerNames.begin(), // ppEnabledLayerNames
(uint32_t) deviceExtensionNames.size(), // extensionCount
deviceExtensionNames.begin(), // ppEnabledExtensionNames
pointerToFeatures ? nullptr : deviceFeatures // ppEnabledFeatures
};
{
#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS)
// skia:8712
__lsan::ScopedDisabler lsanDisabler;
#endif
err = grVkCreateDevice(physDev, &deviceInfo, nullptr, &device);
}
if (err) {
SkDebugf("CreateDevice failed: %d\n", err);
destroy_instance(getInstProc, inst, debugCallback, hasDebugExtension);
return false;
}
VkQueue queue;
if (isProtected) {
ACQUIRE_VK_PROC(GetDeviceQueue2, inst, device);
SkASSERT(grVkGetDeviceQueue2 != nullptr);
VkDeviceQueueInfo2 queue_info2 = {
VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, // sType
nullptr, // pNext
VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT, // flags
graphicsQueueIndex, // queueFamilyIndex
0 // queueIndex
};
grVkGetDeviceQueue2(device, &queue_info2, &queue);
} else {
grVkGetDeviceQueue(device, graphicsQueueIndex, 0, &queue);
}
skgpu::VulkanInterface interface = skgpu::VulkanInterface(
getProc, inst, device, instanceVersion, physDeviceVersion, extensions);
SkASSERT(interface.validate(instanceVersion, physDeviceVersion, extensions));
sk_sp<skgpu::VulkanMemoryAllocator> memoryAllocator = VkTestMemoryAllocator::Make(
inst, physDev, device, physDeviceVersion, extensions, &interface);
ctx->fInstance = inst;
ctx->fPhysicalDevice = physDev;
ctx->fDevice = device;
ctx->fQueue = queue;
ctx->fGraphicsQueueIndex = graphicsQueueIndex;
ctx->fMaxAPIVersion = apiVersion;
ctx->fVkExtensions = extensions;
ctx->fDeviceFeatures2 = features;
ctx->fGetProc = getProc;
ctx->fProtectedContext = skgpu::Protected(isProtected);
ctx->fMemoryAllocator = memoryAllocator;
return true;
}
void FreeVulkanFeaturesStructs(const VkPhysicalDeviceFeatures2* features) {
// All Vulkan structs that could be part of the features chain will start with the
// structure type followed by the pNext pointer. We cast to the CommonVulkanHeader
// so we can get access to the pNext for the next struct.
struct CommonVulkanHeader {
VkStructureType sType;
void* pNext;
};
void* pNext = features->pNext;
while (pNext) {
void* current = pNext;
pNext = static_cast<CommonVulkanHeader*>(current)->pNext;
sk_free(current);
}
}
} // namespace sk_gpu_test

View file

@ -0,0 +1,40 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* 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 VkTestUtils_DEFINED
#define VkTestUtils_DEFINED
#include <include/core/SkTypes.h>
#include <include/gpu/vk/VulkanBackendContext.h>
#include <tools/gpu/vk/GrVulkanDefines.h>
#include <functional>
namespace skgpu {
struct VulkanBackendContext;
class VulkanExtensions;
}
namespace sk_gpu_test {
using CanPresentFn = std::function<bool(VkInstance, VkPhysicalDevice,
uint32_t queueFamilyIndex)>;
bool CreateVkBackendContext(PFN_vkGetInstanceProcAddr getInstProc,
skgpu::VulkanBackendContext* ctx,
skgpu::VulkanExtensions*,
VkPhysicalDeviceFeatures2*,
VkDebugReportCallbackEXT* debugCallback,
uint32_t* presentQueueIndexPtr = nullptr,
const CanPresentFn& canPresent = CanPresentFn(),
bool isProtected = false);
void FreeVulkanFeaturesStructs(const VkPhysicalDeviceFeatures2*);
} // namespace sk_gpu_test
#endif

View file

@ -0,0 +1,600 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* 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 "VulkanWindowContext.h"
#include <include/core/SkSurface.h>
#include <include/gpu/ganesh/GrBackendSemaphore.h>
#include <include/gpu/ganesh/GrBackendSurface.h>
#include <include/gpu/ganesh/GrDirectContext.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
#include <include/gpu/ganesh/vk/GrVkTypes.h>
#include <include/gpu/vk/VulkanExtensions.h>
#include <src/gpu/GpuTypesPriv.h>
#include <include/gpu/vk/VulkanMutableTextureState.h>
#include <src/base/SkAutoMalloc.h>
#include <src/gpu/ganesh/vk/GrVkImage.h>
#include <src/gpu/ganesh/vk/GrVkUtil.h>
#include <src/gpu/vk/VulkanInterface.h>
#include <src/gpu/vk/vulkanmemoryallocator/VulkanAMDMemoryAllocator.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) 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 {
VulkanWindowContext::VulkanWindowContext(std::unique_ptr<const DisplayParams> params,
CreateVkSurfaceFn createVkSurface,
CanPresentFn canPresent,
PFN_vkGetInstanceProcAddr instProc)
: WindowContext(std::move(params))
, fCreateVkSurfaceFn(std::move(createVkSurface))
, fCanPresentFn(std::move(canPresent))
, fSurface(VK_NULL_HANDLE)
, fSwapchain(VK_NULL_HANDLE)
, fImages(nullptr)
, fImageLayouts(nullptr)
, fSurfaces(nullptr)
, fBackbuffers(nullptr) {
fGetInstanceProcAddr = instProc;
this->initializeContext();
}
void VulkanWindowContext::initializeContext() {
SkASSERT(!fContext);
// any config code here (particularly for msaa)?
PFN_vkGetInstanceProcAddr getInstanceProc = fGetInstanceProcAddr;
skgpu::VulkanBackendContext backendContext;
skgpu::VulkanExtensions extensions;
VkPhysicalDeviceFeatures2 features;
if (!sk_gpu_test::CreateVkBackendContext(getInstanceProc,
&backendContext,
&extensions,
&features,
&fDebugCallback,
&fPresentQueueIndex,
fCanPresentFn,
fDisplayParams->createProtectedNativeBackend())) {
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
return;
}
if (!extensions.hasExtension(VK_KHR_SURFACE_EXTENSION_NAME, 25) ||
!extensions.hasExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, 68)) {
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
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) {
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
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 (fDebugCallback != VK_NULL_HANDLE) {
GET_PROC(DestroyDebugReportCallbackEXT);
}
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);
backendContext.fMemoryAllocator =
skgpu::VulkanAMDMemoryAllocator::Make(fInstance,
backendContext.fPhysicalDevice,
backendContext.fDevice,
physDevVersion,
&extensions,
fInterface.get(),
skgpu::ThreadSafe::kNo,
/*blockSize=*/std::nullopt);
fContext = GrDirectContexts::MakeVulkan(backendContext, fDisplayParams->grContextOptions());
fSurface = fCreateVkSurfaceFn(fInstance);
if (VK_NULL_HANDLE == fSurface) {
this->destroyContext();
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
return;
}
VkBool32 supported;
VkResult res = fGetPhysicalDeviceSurfaceSupportKHR(fPhysicalDevice, fPresentQueueIndex,
fSurface, &supported);
if (VK_SUCCESS != res) {
this->destroyContext();
sk_gpu_test::FreeVulkanFeaturesStructs(&features);
return;
}
if (!this->createSwapchain(-1, -1)) {
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) {
// check for capabilities
VkSurfaceCapabilitiesKHR caps;
VkResult res = fGetPhysicalDeviceSurfaceCapabilitiesKHR(fPhysicalDevice, fSurface, &caps);
if (VK_SUCCESS != res) {
return false;
}
uint32_t surfaceFormatCount;
res = fGetPhysicalDeviceSurfaceFormatsKHR(fPhysicalDevice, fSurface, &surfaceFormatCount,
nullptr);
if (VK_SUCCESS != res) {
return false;
}
SkAutoMalloc surfaceFormatAlloc(surfaceFormatCount * sizeof(VkSurfaceFormatKHR));
VkSurfaceFormatKHR* surfaceFormats = (VkSurfaceFormatKHR*)surfaceFormatAlloc.get();
res = fGetPhysicalDeviceSurfaceFormatsKHR(fPhysicalDevice, fSurface, &surfaceFormatCount,
surfaceFormats);
if (VK_SUCCESS != res) {
return false;
}
uint32_t presentModeCount;
res = fGetPhysicalDeviceSurfacePresentModesKHR(fPhysicalDevice, fSurface, &presentModeCount,
nullptr);
if (VK_SUCCESS != res) {
return false;
}
SkAutoMalloc presentModeAlloc(presentModeCount * sizeof(VkPresentModeKHR));
VkPresentModeKHR* presentModes = (VkPresentModeKHR*)presentModeAlloc.get();
res = fGetPhysicalDeviceSurfacePresentModesKHR(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);
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;
if (GrVkFormatIsSupported(localFormat)) {
surfaceFormat = localFormat;
colorSpace = surfaceFormats[i].colorSpace;
break;
}
}
fSampleCount = std::max(1, fDisplayParams->msaaSampleCount());
fStencilBits = 8;
if (VK_FORMAT_UNDEFINED == surfaceFormat) {
return false;
}
SkColorType colorType;
switch (surfaceFormat) {
case VK_FORMAT_R8G8B8A8_UNORM: // fall through
case VK_FORMAT_R8G8B8A8_SRGB:
colorType = kRGBA_8888_SkColorType;
break;
case VK_FORMAT_B8G8R8A8_UNORM: // fall through
colorType = kBGRA_8888_SkColorType;
break;
default:
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 = fSurface;
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->destroyBuffers();
fDestroySwapchainKHR(fDevice, swapchainCreateInfo.oldSwapchain, nullptr);
}
if (!this->createBuffers(swapchainCreateInfo.imageFormat, usageFlags, colorType,
swapchainCreateInfo.imageSharingMode)) {
fDeviceWaitIdle(fDevice);
this->destroyBuffers();
fDestroySwapchainKHR(fDevice, swapchainCreateInfo.oldSwapchain, nullptr);
}
return true;
}
bool VulkanWindowContext::createBuffers(VkFormat format,
VkImageUsageFlags usageFlags,
SkColorType colorType,
VkSharingMode sharingMode) {
fGetSwapchainImagesKHR(fDevice, fSwapchain, &fImageCount, nullptr);
SkASSERT(fImageCount);
fImages = new VkImage[fImageCount];
fGetSwapchainImagesKHR(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 = skgpu::VulkanAlloc();
info.fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
info.fImageTiling = VK_IMAGE_TILING_OPTIMAL;
info.fFormat = format;
info.fImageUsageFlags = usageFlags;
info.fLevelCount = 1;
info.fCurrentQueueFamily = fPresentQueueIndex;
info.fProtected = skgpu::Protected(fDisplayParams->createProtectedNativeBackend());
info.fSharingMode = sharingMode;
if (usageFlags & VK_IMAGE_USAGE_SAMPLED_BIT) {
GrBackendTexture backendTexture = GrBackendTextures::MakeVk(fWidth, fHeight, info);
fSurfaces[i] = SkSurfaces::WrapBackendTexture(fContext.get(),
backendTexture,
kTopLeft_GrSurfaceOrigin,
fDisplayParams->msaaSampleCount(),
colorType,
fDisplayParams->colorSpace(),
&fDisplayParams->surfaceProps());
} else {
if (fDisplayParams->msaaSampleCount() > 1) {
return false;
}
info.fSampleCount = fSampleCount;
GrBackendRenderTarget backendRT = GrBackendRenderTargets::MakeVk(fWidth, fHeight, info);
fSurfaces[i] = SkSurfaces::WrapBackendRenderTarget(fContext.get(),
backendRT,
kTopLeft_GrSurfaceOrigin,
colorType,
fDisplayParams->colorSpace(),
&fDisplayParams->surfaceProps());
}
if (!fSurfaces[i]) {
return false;
}
}
// 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;
// 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;
SkDEBUGCODE(VkResult result = )GR_VK_CALL(fInterface,
CreateSemaphore(fDevice, &semaphoreInfo, nullptr,
&fBackbuffers[i].fRenderSemaphore));
SkASSERT(result == VK_SUCCESS);
}
fCurrentBackbufferIndex = fImageCount;
return true;
}
void VulkanWindowContext::destroyBuffers() {
if (fBackbuffers) {
for (uint32_t i = 0; i < fImageCount + 1; ++i) {
fBackbuffers[i].fImageIndex = -1;
GR_VK_CALL(fInterface,
DestroySemaphore(fDevice,
fBackbuffers[i].fRenderSemaphore,
nullptr));
}
}
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 (this->isValid()) {
fQueueWaitIdle(fPresentQueue);
fDeviceWaitIdle(fDevice);
this->destroyBuffers();
if (VK_NULL_HANDLE != fSwapchain) {
fDestroySwapchainKHR(fDevice, fSwapchain, nullptr);
fSwapchain = VK_NULL_HANDLE;
}
if (VK_NULL_HANDLE != fSurface) {
fDestroySurfaceKHR(fInstance, fSurface, nullptr);
fSurface = VK_NULL_HANDLE;
}
}
SkASSERT(fContext->unique());
fContext.reset();
fInterface.reset();
if (VK_NULL_HANDLE != fDevice) {
fDestroyDevice(fDevice, nullptr);
fDevice = VK_NULL_HANDLE;
}
#ifdef SK_ENABLE_VK_LAYERS
if (fDebugCallback != VK_NULL_HANDLE) {
fDestroyDebugReportCallbackEXT(fInstance, fDebugCallback, nullptr);
}
#endif
fPhysicalDevice = VK_NULL_HANDLE;
if (VK_NULL_HANDLE != fInstance) {
fDestroyInstance(fInstance, nullptr);
fInstance = VK_NULL_HANDLE;
}
}
VulkanWindowContext::BackbufferInfo* VulkanWindowContext::getAvailableBackbuffer() {
SkASSERT(fBackbuffers);
++fCurrentBackbufferIndex;
if (fCurrentBackbufferIndex > fImageCount) {
fCurrentBackbufferIndex = 0;
}
BackbufferInfo* backbuffer = fBackbuffers + fCurrentBackbufferIndex;
return backbuffer;
}
sk_sp<SkSurface> VulkanWindowContext::getBackbufferSurface() {
BackbufferInfo* backbuffer = this->getAvailableBackbuffer();
SkASSERT(backbuffer);
// semaphores should be in unsignaled state
VkSemaphoreCreateInfo semaphoreInfo;
memset(&semaphoreInfo, 0, sizeof(VkSemaphoreCreateInfo));
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreInfo.pNext = nullptr;
semaphoreInfo.flags = 0;
VkSemaphore semaphore;
SkDEBUGCODE(VkResult result = )GR_VK_CALL(fInterface, CreateSemaphore(fDevice, &semaphoreInfo,
nullptr, &semaphore));
SkASSERT(result == VK_SUCCESS);
// acquire the image
VkResult res = fAcquireNextImageKHR(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));
return nullptr;
}
if (VK_ERROR_OUT_OF_DATE_KHR == res) {
// tear swapchain down and try again
if (!this->createSwapchain(-1, -1)) {
GR_VK_CALL(fInterface, DestroySemaphore(fDevice, semaphore, nullptr));
return nullptr;
}
backbuffer = this->getAvailableBackbuffer();
// acquire the image
res = fAcquireNextImageKHR(fDevice, fSwapchain, UINT64_MAX,
semaphore, VK_NULL_HANDLE,
&backbuffer->fImageIndex);
if (VK_SUCCESS != res) {
GR_VK_CALL(fInterface, DestroySemaphore(fDevice, semaphore, nullptr));
return nullptr;
}
}
SkSurface* surface = fSurfaces[backbuffer->fImageIndex].get();
GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(semaphore);
surface->wait(1, &beSemaphore);
return sk_ref_sp(surface);
}
void VulkanWindowContext::onSwapBuffers() {
BackbufferInfo* backbuffer = fBackbuffers + fCurrentBackbufferIndex;
SkSurface* surface = fSurfaces[backbuffer->fImageIndex].get();
GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(backbuffer->fRenderSemaphore);
GrFlushInfo info;
info.fNumSemaphores = 1;
info.fSignalSemaphores = &beSemaphore;
skgpu::MutableTextureState presentState = skgpu::MutableTextureStates::MakeVulkan(
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, fPresentQueueIndex);
auto dContext = surface->recordingContext()->asDirectContext();
dContext->flush(surface, info, &presentState);
dContext->submit();
// Submit present operation to present queue
const VkPresentInfoKHR presentInfo =
{
VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, // sType
nullptr, // pNext
1, // waitSemaphoreCount
&backbuffer->fRenderSemaphore, // pWaitSemaphores
1, // swapchainCount
&fSwapchain, // pSwapchains
&backbuffer->fImageIndex, // pImageIndices
nullptr // pResults
};
fQueuePresentKHR(fPresentQueue, &presentInfo);
}
} // namespace skwindow::internal

View file

@ -0,0 +1,114 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* Copyright 2023 Google LLC
*
* 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 <include/core/SkTypes.h>
#include <include/gpu/vk/VulkanBackendContext.h>
#include "VkTestUtils.h"
#include "WindowContext.h"
class GrRenderTarget;
namespace skgpu { struct VulkanInterface; }
namespace skwindow::internal {
class VulkanWindowContext : public WindowContext {
public:
~VulkanWindowContext() override;
sk_sp<SkSurface> getBackbufferSurface() override;
bool isValid() override { return fDevice != VK_NULL_HANDLE; }
void resize(int w, int h) override { this->createSwapchain(w, h); }
void setDisplayParams(std::unique_ptr<const DisplayParams> params) override {
this->destroyContext();
fDisplayParams = std::move(params);
this->initializeContext();
}
/** 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 = sk_gpu_test::CanPresentFn;
VulkanWindowContext(std::unique_ptr<const DisplayParams>,
CreateVkSurfaceFn,
CanPresentFn,
PFN_vkGetInstanceProcAddr);
private:
void initializeContext();
void destroyContext();
struct BackbufferInfo {
uint32_t fImageIndex; // image this is associated with
VkSemaphore fRenderSemaphore; // we wait on this for rendering to be done
};
BackbufferInfo* getAvailableBackbuffer();
bool createSwapchain(int width, int height);
bool createBuffers(VkFormat format, VkImageUsageFlags, SkColorType colorType, VkSharingMode);
void destroyBuffers();
void onSwapBuffers() override;
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;
PFN_vkGetInstanceProcAddr fGetInstanceProcAddr = nullptr;
// WSI interface functions
PFN_vkDestroySurfaceKHR fDestroySurfaceKHR = nullptr;
PFN_vkGetPhysicalDeviceSurfaceSupportKHR fGetPhysicalDeviceSurfaceSupportKHR = nullptr;
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR fGetPhysicalDeviceSurfaceCapabilitiesKHR =nullptr;
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR fGetPhysicalDeviceSurfaceFormatsKHR = nullptr;
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR fGetPhysicalDeviceSurfacePresentModesKHR =nullptr;
PFN_vkCreateSwapchainKHR fCreateSwapchainKHR = nullptr;
PFN_vkDestroySwapchainKHR fDestroySwapchainKHR = nullptr;
PFN_vkGetSwapchainImagesKHR fGetSwapchainImagesKHR = nullptr;
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;
sk_sp<const skgpu::VulkanInterface> fInterface;
VkSurfaceKHR fSurface;
VkSwapchainKHR fSwapchain;
uint32_t fGraphicsQueueIndex;
VkQueue fGraphicsQueue;
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)
BackbufferInfo* fBackbuffers;
uint32_t fCurrentBackbufferIndex;
};
} // namespace skwindow::internal
#endif

View file

@ -0,0 +1,89 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* 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/WindowContext.h>
#include <include/gpu/ganesh/GrDirectContext.h>
#if defined(SK_GRAPHITE)
#include <include/gpu/graphite/Context.h>
#include <include/gpu/graphite/Recorder.h>
#endif
namespace skwindow {
WindowContext::WindowContext(std::unique_ptr<const DisplayParams> params)
: fDisplayParams(std::move(params)) {}
WindowContext::~WindowContext() {}
void WindowContext::swapBuffers() {
this->onSwapBuffers();
}
bool WindowContext::supportsGpuTimer() const {
auto flags = skgpu::GpuStatsFlags::kNone;
#if defined(SK_GRAPHITE)
if (fGraphiteContext) {
flags = fGraphiteContext->supportedGpuStats();
} else
#endif
if (fContext) {
flags = fContext->supportedGpuStats();
}
using T = std::underlying_type_t<skgpu::GpuStatsFlags>;
return static_cast<T>(flags) & static_cast<T>(skgpu::GpuStatsFlags::kElapsedTime);
}
void WindowContext::submitToGpu(GpuTimerCallback statsCallback) {
#if defined(SK_GRAPHITE)
if (fGraphiteContext) {
SkASSERT(fGraphiteRecorder);
std::unique_ptr<skgpu::graphite::Recording> recording = fGraphiteRecorder->snap();
if (recording) {
skgpu::graphite::InsertRecordingInfo info;
if (statsCallback) {
auto callback = std::make_unique<GpuTimerCallback>(std::move(statsCallback));
info.fFinishedContext = callback.release();
info.fFinishedWithStatsProc = [](skgpu::graphite::GpuFinishedContext context,
skgpu::CallbackResult,
const skgpu::GpuStats& stats) {
std::unique_ptr<GpuTimerCallback> callback{
static_cast<GpuTimerCallback*>(context)};
(*callback)(stats.elapsedTime);
};
info.fGpuStatsFlags = skgpu::GpuStatsFlags::kElapsedTime;
}
info.fRecording = recording.get();
fGraphiteContext->insertRecording(info);
fGraphiteContext->submit(skgpu::graphite::SyncToCpu::kNo);
}
return;
}
#endif
if (auto dc = this->directContext()) {
GrFlushInfo info;
if (statsCallback) {
auto callback = std::make_unique<GpuTimerCallback>(std::move(statsCallback));
info.fFinishedContext = callback.release();
info.fFinishedWithStatsProc = [](GrGpuFinishedContext context,
const skgpu::GpuStats& stats) {
std::unique_ptr<GpuTimerCallback> callback{static_cast<GpuTimerCallback*>(context)};
(*callback)(stats.elapsedTime);
};
info.fGpuStatsFlags = skgpu::GpuStatsFlags::kElapsedTime;
}
dc->flush(info);
dc->submit();
return;
}
if (statsCallback) {
statsCallback(0);
}
}
} // namespace skwindow

View file

@ -0,0 +1,87 @@
// This file has been copied from the Skia library, and may be slightly modified. Please check the NOTICE file for more details
/*
* Copyright 2023 Google LLC
*
* 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 <include/core/SkRefCnt.h>
#include <include/core/SkSurfaceProps.h>
#include <include/gpu/ganesh/GrTypes.h>
#include <tools/window/DisplayParams.h>
class GrDirectContext;
class SkSurface;
#if defined(SK_GRAPHITE)
namespace skgpu::graphite {
class Context;
class Recorder;
}
#endif
namespace skwindow {
class WindowContext {
public:
WindowContext(std::unique_ptr<const DisplayParams>);
virtual ~WindowContext();
virtual sk_sp<SkSurface> getBackbufferSurface() = 0;
void swapBuffers();
virtual bool isValid() = 0;
virtual void resize(int w, int h) = 0;
virtual void activate(bool isActive) {}
const DisplayParams* getDisplayParams() { return fDisplayParams.get(); }
virtual void setDisplayParams(std::unique_ptr<const DisplayParams>) = 0;
GrDirectContext* directContext() const { return fContext.get(); }
#if defined(SK_GRAPHITE)
skgpu::graphite::Context* graphiteContext() const { return fGraphiteContext.get(); }
skgpu::graphite::Recorder* graphiteRecorder() const { return fGraphiteRecorder.get(); }
#endif
using GpuTimerCallback = std::function<void(uint64_t ns)>;
void submitToGpu(GpuTimerCallback = {});
bool supportsGpuTimer() const;
int width() const { return fWidth; }
int height() const { return fHeight; }
SkISize dimensions() const { return {fWidth, fHeight}; }
int sampleCount() const { return fSampleCount; }
int stencilBits() const { return fStencilBits; }
sk_sp<GrDirectContext> fContext;
protected:
virtual bool isGpuContext() { return true; }
virtual void onSwapBuffers() = 0;
#if defined(SK_GRAPHITE)
std::unique_ptr<skgpu::graphite::Context> fGraphiteContext;
std::unique_ptr<skgpu::graphite::Recorder> fGraphiteRecorder;
#endif
int fWidth;
int fHeight;
std::unique_ptr<const DisplayParams> fDisplayParams;
// parameters obtained from the native window
// Note that the platform .cpp file is responsible for
// initializing fSampleCount and fStencilBits!
int fSampleCount = 1;
int fStencilBits = 0;
};
} // namespace skwindow
#endif