blob: 04204621b943244cce9d54ac16ac4eb42f022b3a [file] [log] [blame] [edit]
// Copyright 2018 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dawn_native/vulkan/SwapChainVk.h"
#include "common/Compiler.h"
#include "dawn_native/Instance.h"
#include "dawn_native/Surface.h"
#include "dawn_native/vulkan/AdapterVk.h"
#include "dawn_native/vulkan/BackendVk.h"
#include "dawn_native/vulkan/DeviceVk.h"
#include "dawn_native/vulkan/FencedDeleter.h"
#include "dawn_native/vulkan/TextureVk.h"
#include "dawn_native/vulkan/VulkanError.h"
#include <algorithm>
#if defined(DAWN_USE_X11)
# include "dawn_native/XlibXcbFunctions.h"
#endif // defined(DAWN_USE_X11)
namespace dawn_native { namespace vulkan {
// OldSwapChain
// static
Ref<OldSwapChain> OldSwapChain::Create(Device* device, const SwapChainDescriptor* descriptor) {
return AcquireRef(new OldSwapChain(device, descriptor));
}
OldSwapChain::OldSwapChain(Device* device, const SwapChainDescriptor* descriptor)
: OldSwapChainBase(device, descriptor) {
const auto& im = GetImplementation();
DawnWSIContextVulkan wsiContext = {};
im.Init(im.userData, &wsiContext);
ASSERT(im.textureUsage != WGPUTextureUsage_None);
mTextureUsage = static_cast<wgpu::TextureUsage>(im.textureUsage);
}
OldSwapChain::~OldSwapChain() {
}
TextureBase* OldSwapChain::GetNextTextureImpl(const TextureDescriptor* descriptor) {
const auto& im = GetImplementation();
DawnSwapChainNextTexture next = {};
DawnSwapChainError error = im.GetNextTexture(im.userData, &next);
if (error) {
GetDevice()->HandleError(InternalErrorType::Internal, error);
return nullptr;
}
::VkImage image = NativeNonDispatachableHandleFromU64<::VkImage>(next.texture.u64);
VkImage nativeTexture = VkImage::CreateFromHandle(image);
return Texture::CreateForSwapChain(ToBackend(GetDevice()), descriptor, nativeTexture)
.Detach();
}
MaybeError OldSwapChain::OnBeforePresent(TextureViewBase* view) {
Device* device = ToBackend(GetDevice());
// Perform the necessary pipeline barriers for the texture to be used with the usage
// requested by the implementation.
CommandRecordingContext* recordingContext = device->GetPendingRecordingContext();
ToBackend(view->GetTexture())
->TransitionUsageNow(recordingContext, mTextureUsage, view->GetSubresourceRange());
DAWN_TRY(device->SubmitPendingCommands());
return {};
}
// SwapChain
namespace {
ResultOrError<VkSurfaceKHR> CreateVulkanSurface(Backend* backend, Surface* surface) {
const VulkanGlobalInfo& info = backend->GetGlobalInfo();
const VulkanFunctions& fn = backend->GetFunctions();
VkInstance instance = backend->GetVkInstance();
// May not be used in the platform-specific switches below.
DAWN_UNUSED(info);
DAWN_UNUSED(fn);
DAWN_UNUSED(instance);
switch (surface->GetType()) {
#if defined(DAWN_ENABLE_BACKEND_METAL)
case Surface::Type::MetalLayer:
if (info.HasExt(InstanceExt::MetalSurface)) {
VkMetalSurfaceCreateInfoEXT createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
createInfo.pNext = nullptr;
createInfo.flags = 0;
createInfo.pLayer = surface->GetMetalLayer();
VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
DAWN_TRY(CheckVkSuccess(
fn.CreateMetalSurfaceEXT(instance, &createInfo, nullptr, &*vkSurface),
"CreateMetalSurface"));
return vkSurface;
}
break;
#endif // defined(DAWN_ENABLE_BACKEND_METAL)
#if defined(DAWN_PLATFORM_WINDOWS)
case Surface::Type::WindowsHWND:
if (info.HasExt(InstanceExt::Win32Surface)) {
VkWin32SurfaceCreateInfoKHR createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
createInfo.pNext = nullptr;
createInfo.flags = 0;
createInfo.hinstance = static_cast<HINSTANCE>(surface->GetHInstance());
createInfo.hwnd = static_cast<HWND>(surface->GetHWND());
VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
DAWN_TRY(CheckVkSuccess(
fn.CreateWin32SurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
"CreateWin32Surface"));
return vkSurface;
}
break;
#endif // defined(DAWN_PLATFORM_WINDOWS)
#if defined(DAWN_USE_X11)
case Surface::Type::Xlib: {
if (info.HasExt(InstanceExt::XlibSurface)) {
VkXlibSurfaceCreateInfoKHR createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
createInfo.pNext = nullptr;
createInfo.flags = 0;
createInfo.dpy = static_cast<Display*>(surface->GetXDisplay());
createInfo.window = surface->GetXWindow();
VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
DAWN_TRY(CheckVkSuccess(
fn.CreateXlibSurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
"CreateXlibSurface"));
return vkSurface;
}
// Fall back to using XCB surfaces if the Xlib extension isn't available.
// See https://xcb.freedesktop.org/MixingCalls/ for more information about
// interoperability between Xlib and XCB
const XlibXcbFunctions* xlibXcb =
backend->GetInstance()->GetOrCreateXlibXcbFunctions();
ASSERT(xlibXcb != nullptr);
if (info.HasExt(InstanceExt::XcbSurface) && xlibXcb->IsLoaded()) {
VkXcbSurfaceCreateInfoKHR createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;
createInfo.pNext = nullptr;
createInfo.flags = 0;
// The XCB connection lives as long as the X11 display.
createInfo.connection = xlibXcb->xGetXCBConnection(
static_cast<Display*>(surface->GetXDisplay()));
createInfo.window = surface->GetXWindow();
VkSurfaceKHR vkSurface = VK_NULL_HANDLE;
DAWN_TRY(CheckVkSuccess(
fn.CreateXcbSurfaceKHR(instance, &createInfo, nullptr, &*vkSurface),
"CreateXcbSurfaceKHR"));
return vkSurface;
}
break;
}
#endif // defined(DAWN_USE_X11)
default:
break;
}
return DAWN_FORMAT_VALIDATION_ERROR("Unsupported surface type (%s) for Vulkan.",
surface->GetType());
}
VkPresentModeKHR ToVulkanPresentMode(wgpu::PresentMode mode) {
switch (mode) {
case wgpu::PresentMode::Fifo:
return VK_PRESENT_MODE_FIFO_KHR;
case wgpu::PresentMode::Immediate:
return VK_PRESENT_MODE_IMMEDIATE_KHR;
case wgpu::PresentMode::Mailbox:
return VK_PRESENT_MODE_MAILBOX_KHR;
}
UNREACHABLE();
}
uint32_t MinImageCountForPresentMode(VkPresentModeKHR mode) {
switch (mode) {
case VK_PRESENT_MODE_FIFO_KHR:
case VK_PRESENT_MODE_IMMEDIATE_KHR:
return 2;
case VK_PRESENT_MODE_MAILBOX_KHR:
return 3;
default:
break;
}
UNREACHABLE();
}
} // anonymous namespace
// static
ResultOrError<Ref<SwapChain>> SwapChain::Create(Device* device,
Surface* surface,
NewSwapChainBase* previousSwapChain,
const SwapChainDescriptor* descriptor) {
Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, descriptor));
DAWN_TRY(swapchain->Initialize(previousSwapChain));
return swapchain;
}
SwapChain::~SwapChain() = default;
void SwapChain::DestroyImpl() {
SwapChainBase::DestroyImpl();
DetachFromSurface();
}
// Note that when we need to re-create the swapchain because it is out of date,
// previousSwapChain can be set to `this`.
MaybeError SwapChain::Initialize(NewSwapChainBase* previousSwapChain) {
Device* device = ToBackend(GetDevice());
Adapter* adapter = ToBackend(GetDevice()->GetAdapter());
VkSwapchainKHR previousVkSwapChain = VK_NULL_HANDLE;
if (previousSwapChain != nullptr) {
// TODO(crbug.com/dawn/269): The first time a surface is used with a Device, check
// it is supported with vkGetPhysicalDeviceSurfaceSupportKHR.
// TODO(crbug.com/dawn/269): figure out what should happen when surfaces are used by
// multiple backends one after the other. It probably needs to block until the backend
// and GPU are completely finished with the previous swapchain.
DAWN_INVALID_IF(previousSwapChain->GetBackendType() != wgpu::BackendType::Vulkan,
"Vulkan SwapChain cannot switch backend types from %s to %s.",
previousSwapChain->GetBackendType(), wgpu::BackendType::Vulkan);
// TODO(crbug.com/dawn/269): use ToBackend once OldSwapChainBase is removed.
SwapChain* previousVulkanSwapChain = static_cast<SwapChain*>(previousSwapChain);
// TODO(crbug.com/dawn/269): Figure out switching a single surface between multiple
// Vulkan devices on different VkInstances. Probably needs to block too!
VkInstance previousInstance =
ToBackend(previousSwapChain->GetDevice())->GetVkInstance();
DAWN_INVALID_IF(previousInstance != ToBackend(GetDevice())->GetVkInstance(),
"Vulkan SwapChain cannot switch between Vulkan instances.");
// The previous swapchain is a dawn_native::vulkan::SwapChain so we can reuse its
// VkSurfaceKHR provided since they are on the same instance.
std::swap(previousVulkanSwapChain->mVkSurface, mVkSurface);
// The previous swapchain was on the same Vulkan instance so we can use Vulkan's
// "oldSwapchain" mechanism to ensure a seamless transition. We track the previous
// swapchain for release immediately so it is not leaked in case of an error. (Vulkan
// allows destroying it immediately after the call to vkCreateSwapChainKHR but tracking
// using the fenced deleter makes the code simpler).
std::swap(previousVulkanSwapChain->mSwapChain, previousVkSwapChain);
ToBackend(previousSwapChain->GetDevice())
->GetFencedDeleter()
->DeleteWhenUnused(previousVkSwapChain);
}
if (mVkSurface == VK_NULL_HANDLE) {
DAWN_TRY_ASSIGN(mVkSurface, CreateVulkanSurface(adapter->GetBackend(), GetSurface()));
}
VulkanSurfaceInfo surfaceInfo;
DAWN_TRY_ASSIGN(surfaceInfo, GatherSurfaceInfo(*adapter, mVkSurface));
DAWN_TRY_ASSIGN(mConfig, ChooseConfig(surfaceInfo));
// TODO Choose config instead of hardcoding
VkSwapchainCreateInfoKHR createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.pNext = nullptr;
createInfo.flags = 0;
createInfo.surface = mVkSurface;
createInfo.minImageCount = mConfig.targetImageCount;
createInfo.imageFormat = mConfig.format;
createInfo.imageColorSpace = mConfig.colorSpace;
createInfo.imageExtent = mConfig.extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = mConfig.usage;
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.queueFamilyIndexCount = 0;
createInfo.pQueueFamilyIndices = nullptr;
createInfo.preTransform = mConfig.transform;
createInfo.compositeAlpha = mConfig.alphaMode;
createInfo.presentMode = mConfig.presentMode;
createInfo.clipped = false;
createInfo.oldSwapchain = previousVkSwapChain;
DAWN_TRY(CheckVkSuccess(device->fn.CreateSwapchainKHR(device->GetVkDevice(), &createInfo,
nullptr, &*mSwapChain),
"CreateSwapChain"));
// Gather the swapchain's images. Implementations are allowed to return more images than the
// number we asked for.
uint32_t count = 0;
DAWN_TRY(CheckVkSuccess(
device->fn.GetSwapchainImagesKHR(device->GetVkDevice(), mSwapChain, &count, nullptr),
"GetSwapChainImages1"));
mSwapChainImages.resize(count);
DAWN_TRY(CheckVkSuccess(
device->fn.GetSwapchainImagesKHR(device->GetVkDevice(), mSwapChain, &count,
AsVkArray(mSwapChainImages.data())),
"GetSwapChainImages2"));
return {};
}
ResultOrError<SwapChain::Config> SwapChain::ChooseConfig(
const VulkanSurfaceInfo& surfaceInfo) const {
Config config;
// Choose the present mode. The only guaranteed one is FIFO so it has to be the fallback for
// all other present modes. IMMEDIATE has tearing which is generally undesirable so it can't
// be the fallback for MAILBOX. So the fallback order is always IMMEDIATE -> MAILBOX ->
// FIFO.
{
auto HasPresentMode = [](const std::vector<VkPresentModeKHR>& modes,
VkPresentModeKHR target) -> bool {
return std::find(modes.begin(), modes.end(), target) != modes.end();
};
VkPresentModeKHR targetMode = ToVulkanPresentMode(GetPresentMode());
const std::array<VkPresentModeKHR, 3> kPresentModeFallbacks = {
VK_PRESENT_MODE_IMMEDIATE_KHR,
VK_PRESENT_MODE_MAILBOX_KHR,
VK_PRESENT_MODE_FIFO_KHR,
};
// Go to the target mode.
size_t modeIndex = 0;
while (kPresentModeFallbacks[modeIndex] != targetMode) {
modeIndex++;
}
// Find the first available fallback.
while (!HasPresentMode(surfaceInfo.presentModes, kPresentModeFallbacks[modeIndex])) {
modeIndex++;
}
ASSERT(modeIndex < kPresentModeFallbacks.size());
config.presentMode = kPresentModeFallbacks[modeIndex];
}
// Choose the target width or do a blit.
if (GetWidth() < surfaceInfo.capabilities.minImageExtent.width ||
GetWidth() > surfaceInfo.capabilities.maxImageExtent.width ||
GetHeight() < surfaceInfo.capabilities.minImageExtent.height ||
GetHeight() > surfaceInfo.capabilities.maxImageExtent.height) {
config.needsBlit = true;
} else {
config.extent.width = GetWidth();
config.extent.height = GetHeight();
}
// Choose the target usage or do a blit.
VkImageUsageFlags targetUsages =
VulkanImageUsage(GetUsage(), GetDevice()->GetValidInternalFormat(GetFormat()));
VkImageUsageFlags supportedUsages = surfaceInfo.capabilities.supportedUsageFlags;
if (!IsSubset(targetUsages, supportedUsages)) {
config.needsBlit = true;
} else {
config.usage = targetUsages;
config.wgpuUsage = GetUsage();
}
// Only support BGRA8Unorm with SRGB color space for now.
bool hasBGRA8Unorm = false;
for (const VkSurfaceFormatKHR& format : surfaceInfo.formats) {
if (format.format == VK_FORMAT_B8G8R8A8_UNORM &&
format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
hasBGRA8Unorm = true;
break;
}
}
if (!hasBGRA8Unorm) {
return DAWN_INTERNAL_ERROR(
"Vulkan SwapChain must support BGRA8Unorm with sRGB colorspace.");
}
config.format = VK_FORMAT_B8G8R8A8_UNORM;
config.colorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
config.wgpuFormat = wgpu::TextureFormat::BGRA8Unorm;
// Only the identity transform with opaque alpha is supported for now.
DAWN_INVALID_IF((surfaceInfo.capabilities.supportedTransforms &
VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) == 0,
"Vulkan SwapChain must support the identity transform.");
config.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
DAWN_INVALID_IF((surfaceInfo.capabilities.supportedCompositeAlpha &
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) == 0,
"Vulkan SwapChain must support opaque alpha.");
config.alphaMode = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
// Choose the number of images for the swapchain= and clamp it to the min and max from the
// surface capabilities. maxImageCount = 0 means there is no limit.
ASSERT(surfaceInfo.capabilities.maxImageCount == 0 ||
surfaceInfo.capabilities.minImageCount <= surfaceInfo.capabilities.maxImageCount);
uint32_t targetCount = MinImageCountForPresentMode(config.presentMode);
targetCount = std::max(targetCount, surfaceInfo.capabilities.minImageCount);
if (surfaceInfo.capabilities.maxImageCount != 0) {
targetCount = std::min(targetCount, surfaceInfo.capabilities.maxImageCount);
}
config.targetImageCount = targetCount;
// Choose a valid config for the swapchain texture that will receive the blit.
if (config.needsBlit) {
// Vulkan has provisions to have surfaces that adapt to the swapchain size. If that's
// the case it is very likely that the target extent works, but clamp it just in case.
// Using the target extent for the blit is better when possible so that texels don't
// get stretched. This case is exposed by having the special "-1" value in both
// dimensions of the extent.
constexpr uint32_t kSpecialValue = 0xFFFF'FFFF;
if (surfaceInfo.capabilities.currentExtent.width == kSpecialValue &&
surfaceInfo.capabilities.currentExtent.height == kSpecialValue) {
// extent = clamp(targetExtent, minExtent, maxExtent)
config.extent.width = GetWidth();
config.extent.width =
std::min(config.extent.width, surfaceInfo.capabilities.maxImageExtent.width);
config.extent.width =
std::max(config.extent.width, surfaceInfo.capabilities.minImageExtent.width);
config.extent.height = GetHeight();
config.extent.height =
std::min(config.extent.height, surfaceInfo.capabilities.maxImageExtent.height);
config.extent.height =
std::max(config.extent.height, surfaceInfo.capabilities.minImageExtent.height);
} else {
// If it is not an adaptable swapchain, just use the current extent for the blit
// texture.
config.extent = surfaceInfo.capabilities.currentExtent;
}
// TODO(crbug.com/dawn/269): If the swapchain image doesn't support TRANSFER_DST
// then we'll need to have a second fallback that uses a blit shader :(
if ((supportedUsages & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0) {
return DAWN_INTERNAL_ERROR(
"SwapChain cannot fallback to a blit because of a missing "
"VK_IMAGE_USAGE_TRANSFER_DST_BIT");
}
config.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
config.wgpuUsage = wgpu::TextureUsage::CopyDst;
}
return config;
}
MaybeError SwapChain::PresentImpl() {
Device* device = ToBackend(GetDevice());
CommandRecordingContext* recordingContext = device->GetPendingRecordingContext();
if (mConfig.needsBlit) {
// TODO ditto same as present below: eagerly transition the blit texture to CopySrc.
mBlitTexture->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopySrc,
mBlitTexture->GetAllSubresources());
mTexture->TransitionUsageNow(recordingContext, wgpu::TextureUsage::CopyDst,
mTexture->GetAllSubresources());
VkImageBlit region;
region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.srcSubresource.mipLevel = 0;
region.srcSubresource.baseArrayLayer = 0;
region.srcSubresource.layerCount = 1;
region.srcOffsets[0] = {0, 0, 0};
region.srcOffsets[1] = {static_cast<int32_t>(mBlitTexture->GetWidth()),
static_cast<int32_t>(mBlitTexture->GetHeight()), 1};
region.dstSubresource = region.srcSubresource;
region.dstOffsets[0] = {0, 0, 0};
region.dstOffsets[1] = {static_cast<int32_t>(mTexture->GetWidth()),
static_cast<int32_t>(mTexture->GetHeight()), 1};
device->fn.CmdBlitImage(recordingContext->commandBuffer, mBlitTexture->GetHandle(),
mBlitTexture->GetCurrentLayoutForSwapChain(),
mTexture->GetHandle(), mTexture->GetCurrentLayoutForSwapChain(),
1, &region, VK_FILTER_LINEAR);
// TODO(crbug.com/dawn/269): Find a way to reuse the blit texture between frames
// instead of creating a new one every time. This will involve "un-destroying" the
// texture or making the blit texture "external".
mBlitTexture->APIDestroy();
mBlitTexture = nullptr;
}
// TODO(crbug.com/dawn/269): Remove the need for this by eagerly transitioning the
// presentable texture to present at the end of submits that use them and ideally even
// folding that in the free layout transition at the end of render passes.
mTexture->TransitionUsageNow(recordingContext, kPresentTextureUsage,
mTexture->GetAllSubresources());
DAWN_TRY(device->SubmitPendingCommands());
// Assuming that the present queue is the same as the graphics queue, the proper
// synchronization has already been done on the queue so we don't need to wait on any
// semaphores.
// TODO(crbug.com/dawn/269): Support the present queue not being the main queue.
VkPresentInfoKHR presentInfo;
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.pNext = nullptr;
presentInfo.waitSemaphoreCount = 0;
presentInfo.pWaitSemaphores = nullptr;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &*mSwapChain;
presentInfo.pImageIndices = &mLastImageIndex;
presentInfo.pResults = nullptr;
// Free the texture before present so error handling doesn't skip that step.
mTexture->APIDestroy();
mTexture = nullptr;
VkResult result =
VkResult::WrapUnsafe(device->fn.QueuePresentKHR(device->GetQueue(), &presentInfo));
switch (result) {
case VK_SUCCESS:
// VK_SUBOPTIMAL_KHR means "a swapchain no longer matches the surface properties
// exactly, but can still be used to present to the surface successfully", so we
// can also treat it as a "success" error code of vkQueuePresentKHR().
case VK_SUBOPTIMAL_KHR:
return {};
// This present cannot be recovered. Re-initialize the VkSwapchain so that future
// presents work..
case VK_ERROR_OUT_OF_DATE_KHR:
return Initialize(this);
// TODO(crbug.com/dawn/269): Allow losing the surface at Dawn's API level?
case VK_ERROR_SURFACE_LOST_KHR:
default:
return CheckVkSuccess(::VkResult(result), "QueuePresent");
}
}
ResultOrError<TextureViewBase*> SwapChain::GetCurrentTextureViewImpl() {
return GetCurrentTextureViewInternal();
}
ResultOrError<TextureViewBase*> SwapChain::GetCurrentTextureViewInternal(bool isReentrant) {
Device* device = ToBackend(GetDevice());
// Transiently create a semaphore that will be signaled when the presentation engine is done
// with the swapchain image. Further operations on the image will wait for this semaphore.
VkSemaphoreCreateInfo createInfo;
createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
createInfo.pNext = nullptr;
createInfo.flags = 0;
VkSemaphore semaphore = VK_NULL_HANDLE;
DAWN_TRY(CheckVkSuccess(
device->fn.CreateSemaphore(device->GetVkDevice(), &createInfo, nullptr, &*semaphore),
"CreateSemaphore"));
VkResult result = VkResult::WrapUnsafe(device->fn.AcquireNextImageKHR(
device->GetVkDevice(), mSwapChain, std::numeric_limits<uint64_t>::max(), semaphore,
VkFence{}, &mLastImageIndex));
if (result == VK_SUCCESS) {
// TODO(crbug.com/dawn/269) put the semaphore on the texture so it is waited on when
// used instead of directly on the recording context?
device->GetPendingRecordingContext()->waitSemaphores.push_back(semaphore);
} else {
// The semaphore wasn't actually used (? this is unclear in the spec). Delete it when
// we get a chance.
ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(semaphore);
}
switch (result) {
// TODO(crbug.com/dawn/269): Introduce a mechanism to notify the application that
// the swapchain is in a suboptimal state?
case VK_SUBOPTIMAL_KHR:
case VK_SUCCESS:
break;
case VK_ERROR_OUT_OF_DATE_KHR: {
// Prevent infinite recursive calls to GetCurrentTextureViewInternal when the
// swapchains always return that they are out of date.
if (isReentrant) {
// TODO(crbug.com/dawn/269): Allow losing the surface instead?
return DAWN_INTERNAL_ERROR(
"Wasn't able to recuperate the surface after a VK_ERROR_OUT_OF_DATE_KHR");
}
// Re-initialize the VkSwapchain and try getting the texture again.
DAWN_TRY(Initialize(this));
return GetCurrentTextureViewInternal(true);
}
// TODO(crbug.com/dawn/269): Allow losing the surface at Dawn's API level?
case VK_ERROR_SURFACE_LOST_KHR:
default:
DAWN_TRY(CheckVkSuccess(::VkResult(result), "AcquireNextImage"));
}
TextureDescriptor textureDesc;
textureDesc.size.width = mConfig.extent.width;
textureDesc.size.height = mConfig.extent.height;
textureDesc.format = mConfig.wgpuFormat;
textureDesc.usage = mConfig.wgpuUsage;
VkImage currentImage = mSwapChainImages[mLastImageIndex];
mTexture = Texture::CreateForSwapChain(device, &textureDesc, currentImage);
// In the happy path we can use the swapchain image directly.
if (!mConfig.needsBlit) {
// TODO(dawn:723): change to not use AcquireRef for reentrant object creation.
return mTexture->APICreateView();
}
// The blit texture always perfectly matches what the user requested for the swapchain.
// We need to add the Vulkan TRANSFER_SRC flag for the vkCmdBlitImage call.
TextureDescriptor desc = GetSwapChainBaseTextureDescriptor(this);
DAWN_TRY_ASSIGN(mBlitTexture,
Texture::Create(device, &desc, VK_IMAGE_USAGE_TRANSFER_SRC_BIT));
// TODO(dawn:723): change to not use AcquireRef for reentrant object creation.
return mBlitTexture->APICreateView();
}
void SwapChain::DetachFromSurfaceImpl() {
if (mTexture != nullptr) {
mTexture->APIDestroy();
mTexture = nullptr;
}
if (mBlitTexture != nullptr) {
mBlitTexture->APIDestroy();
mBlitTexture = nullptr;
}
// The swapchain images are destroyed with the swapchain.
if (mSwapChain != VK_NULL_HANDLE) {
ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(mSwapChain);
mSwapChain = VK_NULL_HANDLE;
}
if (mVkSurface != VK_NULL_HANDLE) {
ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(mVkSurface);
mVkSurface = VK_NULL_HANDLE;
}
}
}} // namespace dawn_native::vulkan