Initial new swapchains on Vulkan.

Bug: dawn:269

Change-Id: I5b7a4edfa876d5a64f8f135adb619be021e4f534
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/17966
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
diff --git a/examples/SampleUtils.cpp b/examples/SampleUtils.cpp
index 174b539..f1fbb6c 100644
--- a/examples/SampleUtils.cpp
+++ b/examples/SampleUtils.cpp
@@ -96,6 +96,7 @@
 
     // Create the test window and discover adapters using it (esp. for OpenGL)
     utils::SetupGLFWWindowHintsForBackend(backendType);
+    glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
     window = glfwCreateWindow(640, 480, "Dawn window", nullptr, nullptr);
     if (!window) {
         return wgpu::Device();
diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp
index 981a940..71f42a8 100644
--- a/src/dawn_native/vulkan/DeviceVk.cpp
+++ b/src/dawn_native/vulkan/DeviceVk.cpp
@@ -141,13 +141,13 @@
     }
     ResultOrError<SwapChainBase*> Device::CreateSwapChainImpl(
         const SwapChainDescriptor* descriptor) {
-        return SwapChain::Create(this, descriptor);
+        return OldSwapChain::Create(this, descriptor);
     }
     ResultOrError<NewSwapChainBase*> Device::CreateSwapChainImpl(
         Surface* surface,
         NewSwapChainBase* previousSwapChain,
         const SwapChainDescriptor* descriptor) {
-        return DAWN_VALIDATION_ERROR("New swapchains not implemented.");
+        return SwapChain::Create(this, surface, previousSwapChain, descriptor);
     }
     ResultOrError<Ref<TextureBase>> Device::CreateTextureImpl(const TextureDescriptor* descriptor) {
         return Texture::Create(this, descriptor);
diff --git a/src/dawn_native/vulkan/SwapChainVk.cpp b/src/dawn_native/vulkan/SwapChainVk.cpp
index 30d774d..9d2649c 100644
--- a/src/dawn_native/vulkan/SwapChainVk.cpp
+++ b/src/dawn_native/vulkan/SwapChainVk.cpp
@@ -14,17 +14,24 @@
 
 #include "dawn_native/vulkan/SwapChainVk.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"
 
 namespace dawn_native { namespace vulkan {
 
+    // OldSwapChain
+
     // static
-    SwapChain* SwapChain::Create(Device* device, const SwapChainDescriptor* descriptor) {
-        return new SwapChain(device, descriptor);
+    OldSwapChain* OldSwapChain::Create(Device* device, const SwapChainDescriptor* descriptor) {
+        return new OldSwapChain(device, descriptor);
     }
 
-    SwapChain::SwapChain(Device* device, const SwapChainDescriptor* descriptor)
+    OldSwapChain::OldSwapChain(Device* device, const SwapChainDescriptor* descriptor)
         : OldSwapChainBase(device, descriptor) {
         const auto& im = GetImplementation();
         DawnWSIContextVulkan wsiContext = {};
@@ -34,10 +41,10 @@
         mTextureUsage = static_cast<wgpu::TextureUsage>(im.textureUsage);
     }
 
-    SwapChain::~SwapChain() {
+    OldSwapChain::~OldSwapChain() {
     }
 
-    TextureBase* SwapChain::GetNextTextureImpl(const TextureDescriptor* descriptor) {
+    TextureBase* OldSwapChain::GetNextTextureImpl(const TextureDescriptor* descriptor) {
         const auto& im = GetImplementation();
         DawnSwapChainNextTexture next = {};
         DawnSwapChainError error = im.GetNextTexture(im.userData, &next);
@@ -53,7 +60,7 @@
             .Detach();
     }
 
-    MaybeError SwapChain::OnBeforePresent(TextureViewBase* view) {
+    MaybeError OldSwapChain::OnBeforePresent(TextureViewBase* view) {
         Device* device = ToBackend(GetDevice());
 
         // Perform the necessary pipeline barriers for the texture to be used with the usage
@@ -67,4 +74,335 @@
         return {};
     }
 
+    // SwapChain
+
+    namespace {
+
+        ResultOrError<VkSurfaceKHR> CreateVulkanSurface(Backend* backend, Surface* surface) {
+            const VulkanGlobalInfo& info = backend->GetGlobalInfo();
+            const VulkanFunctions& fn = backend->GetFunctions();
+            VkInstance instance = backend->GetVkInstance();
+
+            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),
+                            "CreateWin32Surface"));
+                        return vkSurface;
+                    }
+                    break;
+#endif  // defined(DAWN_USE_X11)
+
+                default:
+                    break;
+            }
+
+            return DAWN_VALIDATION_ERROR("Unsupported surface type for Vulkan");
+        }
+
+        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;
+            }
+        }
+
+    }  // anonymous namespace
+
+    // static
+    ResultOrError<SwapChain*> SwapChain::Create(Device* device,
+                                                Surface* surface,
+                                                NewSwapChainBase* previousSwapChain,
+                                                const SwapChainDescriptor* descriptor) {
+        std::unique_ptr<SwapChain> swapchain =
+            std::make_unique<SwapChain>(device, surface, descriptor);
+        DAWN_TRY(swapchain->Initialize(previousSwapChain));
+        return swapchain.release();
+    }
+
+    SwapChain::~SwapChain() {
+        DetachFromSurface();
+    }
+
+    MaybeError SwapChain::Initialize(NewSwapChainBase* previousSwapChain) {
+        Device* device = ToBackend(GetDevice());
+        Adapter* adapter = ToBackend(GetDevice()->GetAdapter());
+
+        VkSwapchainKHR oldVkSwapChain = VK_NULL_HANDLE;
+
+        if (previousSwapChain != nullptr) {
+            // TODO(cwallez@chromium.org): The first time a surface is used with a Device, check
+            // it is supported with vkGetPhysicalDeviceSurfaceSupportKHR.
+
+            // TODO(cwallez@chromium.org): 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.
+            ASSERT(previousSwapChain->GetBackendType() == wgpu::BackendType::Vulkan);
+
+            // The previous swapchain is a dawn_native::vulkan::SwapChain so we can reuse its
+            // VkSurfaceKHR provided they are on the same instance.
+            // TODO(cwallez@chromium.org): check they are the same instance.
+            // TODO(cwallez@chromium.org): use ToBackend once OldSwapChainBase is removed.
+            SwapChain* previousVulkanSwapChain = static_cast<SwapChain*>(previousSwapChain);
+            std::swap(previousVulkanSwapChain->mVkSurface, mVkSurface);
+
+            // TODO(cwallez@chromium.org): Figure out switching a single surface between multiple
+            // Vulkan devices. Probably needs to block too, but could reuse the surface!
+            ASSERT(previousSwapChain->GetDevice() == GetDevice());
+
+            // The previous swapchain was on the same Vulkan device so we can use Vulkan's
+            // "oldSwapchain" mechanism to ensure a seamless transition. We track the old 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, oldVkSwapChain);
+            device->GetFencedDeleter()->DeleteWhenUnused(oldVkSwapChain);
+
+            previousSwapChain->DetachFromSurface();
+        }
+
+        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 = 3;                                    // TODO
+        createInfo.imageFormat = VK_FORMAT_B8G8R8A8_UNORM;               // TODO
+        createInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;  // TODO?
+        createInfo.imageExtent.width = GetWidth();                       // TODO
+        createInfo.imageExtent.height = GetHeight();                     // TODO
+        createInfo.imageArrayLayers = 1;
+        createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+        createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+        createInfo.queueFamilyIndexCount = 0;
+        createInfo.pQueueFamilyIndices = nullptr;
+        createInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;  // TODO
+        createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;    // TODO
+        createInfo.presentMode = mConfig.presentMode;
+        createInfo.clipped = false;
+        createInfo.oldSwapchain = oldVkSwapChain;
+
+        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"));
+
+        // TODO(cwallez@chromium.org): Figure out if we can only have more swapchain images, or also
+        // less than requested (and what should happen in that case).
+        ASSERT(count >= 3);
+        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];
+        }
+
+        return config;
+    }
+
+    MaybeError SwapChain::PresentImpl() {
+        Device* device = ToBackend(GetDevice());
+
+        // Transition the texture to the present usage.
+        // TODO(cwallez@chromium.org): 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.
+        CommandRecordingContext* recordingContext = device->GetPendingRecordingContext();
+        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(cwallez@chromium.org): 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->Destroy();
+        mTexture = nullptr;
+
+        VkResult result =
+            VkResult::WrapUnsafe(device->fn.QueuePresentKHR(device->GetQueue(), &presentInfo));
+        if (result == VK_ERROR_OUT_OF_DATE_KHR) {
+            // TODO reinitialize?
+        } else if (result == VK_ERROR_SURFACE_LOST_KHR) {
+            // TODO IDK what to do here, just lose the device?
+        } else {
+            DAWN_TRY(CheckVkSuccess(::VkResult(result), "QueuePresent"));
+        }
+
+        return {};
+    }
+
+    ResultOrError<TextureViewBase*> SwapChain::GetCurrentTextureViewImpl() {
+        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"));
+
+        // TODO(cwallez@chromium.org) 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);
+
+        VkResult result = VkResult::WrapUnsafe(device->fn.AcquireNextImageKHR(
+            device->GetVkDevice(), mSwapChain, std::numeric_limits<uint64_t>::max(), semaphore,
+            VkFence{}, &mLastImageIndex));
+        if (result == VK_SUBOPTIMAL_KHR) {
+            // TODO reinitialize?
+        } else if (result == VK_ERROR_OUT_OF_DATE_KHR) {
+            // TODO reinitialize?
+        } else if (result == VK_ERROR_SURFACE_LOST_KHR) {
+            // TODO IDK what to do here, just lose the device?
+        } else {
+            DAWN_TRY(CheckVkSuccess(::VkResult(result), "AcquireNextImage"));
+        }
+
+        VkImage currentImage = mSwapChainImages[mLastImageIndex];
+
+        TextureDescriptor textureDesc = GetSwapChainBaseTextureDescriptor(this);
+        mTexture = Texture::CreateForSwapChain(device, &textureDesc, currentImage);
+        return mTexture->CreateView(nullptr);
+    }
+
+    void SwapChain::DetachFromSurfaceImpl() {
+        if (mTexture.Get() != nullptr) {
+            mTexture->Destroy();
+            mTexture = 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
diff --git a/src/dawn_native/vulkan/SwapChainVk.h b/src/dawn_native/vulkan/SwapChainVk.h
index 9ee1792..009c14c 100644
--- a/src/dawn_native/vulkan/SwapChainVk.h
+++ b/src/dawn_native/vulkan/SwapChainVk.h
@@ -19,25 +19,65 @@
 
 #include "common/vulkan_platform.h"
 
+#include <vector>
+
 namespace dawn_native { namespace vulkan {
 
     class Device;
+    class Texture;
+    struct VulkanSurfaceInfo;
 
-    class SwapChain final : public OldSwapChainBase {
+    class OldSwapChain : public OldSwapChainBase {
       public:
-        static SwapChain* Create(Device* device, const SwapChainDescriptor* descriptor);
+        static OldSwapChain* Create(Device* device, const SwapChainDescriptor* descriptor);
 
       protected:
-        SwapChain(Device* device, const SwapChainDescriptor* descriptor);
-        ~SwapChain() override;
+        OldSwapChain(Device* device, const SwapChainDescriptor* descriptor);
+        ~OldSwapChain() override;
 
         TextureBase* GetNextTextureImpl(const TextureDescriptor* descriptor) override;
-        MaybeError OnBeforePresent(TextureViewBase* view) override;
+        MaybeError OnBeforePresent(TextureViewBase* texture) override;
 
       private:
         wgpu::TextureUsage mTextureUsage;
     };
 
+    class SwapChain : public NewSwapChainBase {
+      public:
+        static ResultOrError<SwapChain*> Create(Device* device,
+                                                Surface* surface,
+                                                NewSwapChainBase* previousSwapChain,
+                                                const SwapChainDescriptor* descriptor);
+        ~SwapChain() override;
+
+      private:
+        using NewSwapChainBase::NewSwapChainBase;
+        MaybeError Initialize(NewSwapChainBase* previousSwapChain);
+
+        struct Config {
+            // Information that's passed to swapchain creation.
+            VkPresentModeKHR presentMode;
+
+            // TODO more information used to create the swapchain.
+            // TODO information about the blit that needs to happen.
+        };
+        ResultOrError<Config> ChooseConfig(const VulkanSurfaceInfo& surfaceInfo) const;
+
+        // NewSwapChainBase implementation
+        MaybeError PresentImpl() override;
+        ResultOrError<TextureViewBase*> GetCurrentTextureViewImpl() override;
+        void DetachFromSurfaceImpl() override;
+
+        Config mConfig;
+
+        VkSurfaceKHR mVkSurface = VK_NULL_HANDLE;
+        VkSwapchainKHR mSwapChain = VK_NULL_HANDLE;
+        std::vector<VkImage> mSwapChainImages;
+        uint32_t mLastImageIndex = 0;
+
+        Ref<Texture> mTexture;
+    };
+
 }}  // namespace dawn_native::vulkan
 
 #endif  // DAWNNATIVE_VULKAN_SWAPCHAINVK_H_