D3D12: Implement initial surface-based swapchains

Bug:dawn:269

Change-Id: I012232e71f3b7aba8ea45899b3b1790d07de835c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/33785
Reviewed-by: Austin Eng <enga@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp
index 4de7654..bb8320e 100644
--- a/src/dawn_native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn_native/d3d12/DeviceD3D12.cpp
@@ -339,13 +339,13 @@
     }
     ResultOrError<Ref<SwapChainBase>> Device::CreateSwapChainImpl(
         const SwapChainDescriptor* descriptor) {
-        return SwapChain::Create(this, descriptor);
+        return OldSwapChain::Create(this, descriptor);
     }
     ResultOrError<Ref<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/d3d12/SwapChainD3D12.cpp b/src/dawn_native/d3d12/SwapChainD3D12.cpp
index 8fd4554..de9dc30 100644
--- a/src/dawn_native/d3d12/SwapChainD3D12.cpp
+++ b/src/dawn_native/d3d12/SwapChainD3D12.cpp
@@ -14,19 +14,70 @@
 
 #include "dawn_native/d3d12/SwapChainD3D12.h"
 
+#include "dawn_native/Surface.h"
+#include "dawn_native/d3d12/D3D12Error.h"
 #include "dawn_native/d3d12/DeviceD3D12.h"
 #include "dawn_native/d3d12/TextureD3D12.h"
 
 #include <dawn/dawn_wsi.h>
 
 namespace dawn_native { namespace d3d12 {
+    namespace {
+
+        uint32_t PresentModeToBufferCount(wgpu::PresentMode mode) {
+            switch (mode) {
+                case wgpu::PresentMode::Immediate:
+                case wgpu::PresentMode::Fifo:
+                    return 2;
+                case wgpu::PresentMode::Mailbox:
+                    return 3;
+            }
+        }
+
+        uint32_t PresentModeToSwapInterval(wgpu::PresentMode mode) {
+            switch (mode) {
+                case wgpu::PresentMode::Immediate:
+                case wgpu::PresentMode::Mailbox:
+                    return 0;
+                case wgpu::PresentMode::Fifo:
+                    return 1;
+            }
+        }
+
+        UINT PresentModeToSwapChainFlags(wgpu::PresentMode mode) {
+            UINT flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
+
+            if (mode == wgpu::PresentMode::Immediate) {
+                flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
+            }
+
+            return flags;
+        }
+
+        DXGI_USAGE ToDXGIUsage(wgpu::TextureUsage usage) {
+            DXGI_USAGE dxgiUsage = DXGI_CPU_ACCESS_NONE;
+            if (usage & wgpu::TextureUsage::Sampled) {
+                dxgiUsage |= DXGI_USAGE_SHADER_INPUT;
+            }
+            if (usage & wgpu::TextureUsage::Storage) {
+                dxgiUsage |= DXGI_USAGE_UNORDERED_ACCESS;
+            }
+            if (usage & wgpu::TextureUsage::RenderAttachment) {
+                dxgiUsage |= DXGI_USAGE_RENDER_TARGET_OUTPUT;
+            }
+            return dxgiUsage;
+        }
+
+    }  // namespace
+
+    // OldSwapChain
 
     // static
-    Ref<SwapChain> SwapChain::Create(Device* device, const SwapChainDescriptor* descriptor) {
-        return AcquireRef(new SwapChain(device, descriptor));
+    Ref<OldSwapChain> OldSwapChain::Create(Device* device, const SwapChainDescriptor* descriptor) {
+        return AcquireRef(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();
         DawnWSIContextD3D12 wsiContext = {};
@@ -37,10 +88,9 @@
         mTextureUsage = static_cast<wgpu::TextureUsage>(im.textureUsage);
     }
 
-    SwapChain::~SwapChain() {
-    }
+    OldSwapChain::~OldSwapChain() = default;
 
-    TextureBase* SwapChain::GetNextTextureImpl(const TextureDescriptor* descriptor) {
+    TextureBase* OldSwapChain::GetNextTextureImpl(const TextureDescriptor* descriptor) {
         DeviceBase* device = GetDevice();
         const auto& im = GetImplementation();
         DawnSwapChainNextTexture next = {};
@@ -61,7 +111,7 @@
         return dawnTexture.Detach();
     }
 
-    MaybeError SwapChain::OnBeforePresent(TextureViewBase* view) {
+    MaybeError OldSwapChain::OnBeforePresent(TextureViewBase* view) {
         Device* device = ToBackend(GetDevice());
 
         CommandRecordingContext* commandContext;
@@ -77,4 +127,218 @@
         return {};
     }
 
+    // SwapChain
+
+    // 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() {
+        DetachFromSurface();
+    }
+
+    // Initializes the swapchain on the surface. Note that `previousSwapChain` may or may not be
+    // nullptr. If it is not nullptr it means that it is the swapchain previously in use on the
+    // surface and that we have a chance to reuse it's underlying IDXGISwapChain and "buffers".
+    MaybeError SwapChain::Initialize(NewSwapChainBase* previousSwapChain) {
+        ASSERT(GetSurface()->GetType() == Surface::Type::WindowsHWND);
+
+        // Precompute the configuration parameters we want for the DXGI swapchain.
+        mConfig.bufferCount = PresentModeToBufferCount(GetPresentMode());
+        mConfig.format = D3D12TextureFormat(GetFormat());
+        mConfig.swapChainFlags = PresentModeToSwapChainFlags(GetPresentMode());
+        mConfig.usage = ToDXGIUsage(GetUsage());
+
+        // There is no previous swapchain so we can create one directly and don't have anything else
+        // to do.
+        if (previousSwapChain == nullptr) {
+            return InitializeSwapChainFromScratch();
+        }
+
+        // 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.
+        if (previousSwapChain->GetBackendType() != wgpu::BackendType::D3D12) {
+            return DAWN_VALIDATION_ERROR("d3d12::SwapChain cannot switch between APIs");
+        }
+
+        // TODO(cwallez@chromium.org): use ToBackend once OldSwapChainBase is removed.
+        SwapChain* previousD3D12SwapChain = static_cast<SwapChain*>(previousSwapChain);
+
+        // TODO(cwallez@chromium.org): Figure out switching an HWND between devices, it might
+        // require just losing the reference to the swapchain, but might also need to wait for
+        // all previous operations to complete.
+        if (GetDevice() != previousSwapChain->GetDevice()) {
+            return DAWN_VALIDATION_ERROR("d3d12::SwapChain cannot switch between devices");
+        }
+
+        // The previous swapchain is on the same device so we want to reuse it but it is still not
+        // always possible. Because DXGI requires that a new swapchain be created if the
+        // DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING flag is changed.
+        bool canReuseSwapChain =
+            ((mConfig.swapChainFlags ^ previousD3D12SwapChain->mConfig.swapChainFlags) &
+             DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) == 0;
+
+        // We can't reuse the previous swapchain, so we destroy it and wait for all of its reference
+        // to be forgotten (otherwise DXGI complains that there are outstanding references).
+        if (!canReuseSwapChain) {
+            DAWN_TRY(previousD3D12SwapChain->DetachAndWaitForDeallocation());
+            return InitializeSwapChainFromScratch();
+        }
+
+        // After all this we know we can reuse the swapchain, see if it is possible to also reuse
+        // the buffers.
+        mDXGISwapChain = std::move(previousD3D12SwapChain->mDXGISwapChain);
+
+        bool canReuseBuffers = GetWidth() == previousSwapChain->GetWidth() &&
+                               GetHeight() == previousSwapChain->GetHeight() &&
+                               GetFormat() == previousSwapChain->GetFormat() &&
+                               GetPresentMode() == previousSwapChain->GetPresentMode();
+        if (canReuseBuffers) {
+            mBuffers = std::move(previousD3D12SwapChain->mBuffers);
+            mBufferLastUsedSerials = std::move(previousD3D12SwapChain->mBufferLastUsedSerials);
+            mCurrentBuffer = previousD3D12SwapChain->mCurrentBuffer;
+            return {};
+        }
+
+        // We can't reuse the buffers so we need to resize, IDXGSwapChain->ResizeBuffers requires
+        // that all references to buffers are lost before it is called. Contrary to D3D11, the
+        // application is responsible for keeping references to the buffers until the GPU is done
+        // using them so we have no choice but to synchrounously wait for all operations to complete
+        // on the previous swapchain and then lose references to its buffers.
+        DAWN_TRY(previousD3D12SwapChain->DetachAndWaitForDeallocation());
+        DAWN_TRY(
+            CheckHRESULT(mDXGISwapChain->ResizeBuffers(mConfig.bufferCount, GetWidth(), GetHeight(),
+                                                       mConfig.format, mConfig.swapChainFlags),
+                         "IDXGISwapChain::ResizeBuffer"));
+        return CollectSwapChainBuffers();
+    }
+
+    MaybeError SwapChain::InitializeSwapChainFromScratch() {
+        ASSERT(mDXGISwapChain == nullptr);
+
+        Device* device = ToBackend(GetDevice());
+
+        DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
+        swapChainDesc.Width = GetWidth();
+        swapChainDesc.Height = GetHeight();
+        swapChainDesc.Format = mConfig.format;
+        swapChainDesc.Stereo = false;
+        swapChainDesc.SampleDesc.Count = 1;
+        swapChainDesc.SampleDesc.Quality = 0;
+        swapChainDesc.BufferUsage = mConfig.usage;
+        swapChainDesc.BufferCount = mConfig.bufferCount;
+        swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
+        swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
+        swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
+        swapChainDesc.Flags = mConfig.swapChainFlags;
+
+        ComPtr<IDXGIFactory2> factory2 = nullptr;
+        DAWN_TRY(CheckHRESULT(device->GetFactory()->QueryInterface(IID_PPV_ARGS(&factory2)),
+                              "Getting IDXGIFactory2"));
+
+        ComPtr<IDXGISwapChain1> swapChain1;
+        DAWN_TRY(CheckHRESULT(
+            factory2->CreateSwapChainForHwnd(device->GetCommandQueue().Get(),
+                                             static_cast<HWND>(GetSurface()->GetHWND()),
+                                             &swapChainDesc, nullptr, nullptr, &swapChain1),
+            "Creating the IDXGISwapChain1"));
+        DAWN_TRY(CheckHRESULT(swapChain1.As(&mDXGISwapChain), "Gettting IDXGISwapChain1"));
+
+        return CollectSwapChainBuffers();
+    }
+
+    MaybeError SwapChain::CollectSwapChainBuffers() {
+        ASSERT(mDXGISwapChain != nullptr);
+        ASSERT(mBuffers.empty());
+
+        mBuffers.resize(mConfig.bufferCount);
+        for (uint32_t i = 0; i < mConfig.bufferCount; i++) {
+            DAWN_TRY(CheckHRESULT(mDXGISwapChain->GetBuffer(i, IID_PPV_ARGS(&mBuffers[i])),
+                                  "Getting IDXGISwapChain buffer"));
+        }
+
+        // Pretend all the buffers were last used at the beginning of time.
+        mBufferLastUsedSerials.resize(mConfig.bufferCount, ExecutionSerial(0));
+        return {};
+    }
+
+    MaybeError SwapChain::PresentImpl() {
+        Device* device = ToBackend(GetDevice());
+
+        // Transition the texture to the present state as required by IDXGISwapChain1::Present()
+        // 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.
+        CommandRecordingContext* commandContext;
+        DAWN_TRY_ASSIGN(commandContext, device->GetPendingCommandContext());
+        mApiTexture->TrackUsageAndTransitionNow(commandContext, kPresentTextureUsage,
+                                                mApiTexture->GetAllSubresources());
+        DAWN_TRY(device->ExecutePendingCommandContext());
+
+        // Do the actual present. DXGI_STATUS_OCCLUDED is a valid return value that's just a
+        // message to the application that it could stop rendering.
+        HRESULT presentResult =
+            mDXGISwapChain->Present(PresentModeToSwapInterval(GetPresentMode()), 0);
+        if (presentResult != DXGI_STATUS_OCCLUDED) {
+            DAWN_TRY(CheckHRESULT(presentResult, "IDXGISwapChain::Present"));
+        }
+
+        // Record that "new" is the last time the buffer has been used.
+        DAWN_TRY(device->NextSerial());
+        mBufferLastUsedSerials[mCurrentBuffer] = device->GetPendingCommandSerial();
+
+        mApiTexture->APIDestroy();
+        mApiTexture = nullptr;
+
+        return {};
+    }
+
+    ResultOrError<TextureViewBase*> SwapChain::GetCurrentTextureViewImpl() {
+        Device* device = ToBackend(GetDevice());
+
+        // Synchronously wait until previous operations on the next swapchain buffer are finished.
+        // This is the logic that performs frame pacing.
+        // TODO(cwallez@chromium.org): Consider whether this should  be lifted for Mailbox so that
+        // there is not frame pacing.
+        mCurrentBuffer = mDXGISwapChain->GetCurrentBackBufferIndex();
+        DAWN_TRY(device->WaitForSerial(mBufferLastUsedSerials[mCurrentBuffer]));
+
+        // Create the API side objects for this use of the swapchain's buffer.
+        TextureDescriptor descriptor = GetSwapChainBaseTextureDescriptor(this);
+        DAWN_TRY_ASSIGN(mApiTexture, Texture::Create(ToBackend(GetDevice()), &descriptor,
+                                                     mBuffers[mCurrentBuffer]));
+
+        // TODO(dawn:723): change to not use AcquireRef for reentrant object creation.
+        return mApiTexture->APICreateView();
+    }
+
+    MaybeError SwapChain::DetachAndWaitForDeallocation() {
+        DetachFromSurface();
+
+        // DetachFromSurface calls Texture->Destroy that enqueues the D3D12 resource in a
+        // SerialQueue with the current "pending serial" so that we don't destroy the texture
+        // before it is finished being used. Flush the commands and wait for that serial to be
+        // passed, then Tick the device to make sure the reference to the D3D12 texture is removed.
+        Device* device = ToBackend(GetDevice());
+        DAWN_TRY(device->NextSerial());
+        DAWN_TRY(device->WaitForSerial(device->GetLastSubmittedCommandSerial()));
+        return device->TickImpl();
+    }
+
+    void SwapChain::DetachFromSurfaceImpl() {
+        if (mApiTexture != nullptr) {
+            mApiTexture->APIDestroy();
+            mApiTexture = nullptr;
+        }
+
+        mDXGISwapChain = nullptr;
+        mBuffers.clear();
+    }
+
 }}  // namespace dawn_native::d3d12
diff --git a/src/dawn_native/d3d12/SwapChainD3D12.h b/src/dawn_native/d3d12/SwapChainD3D12.h
index 4083b04..bc476d1 100644
--- a/src/dawn_native/d3d12/SwapChainD3D12.h
+++ b/src/dawn_native/d3d12/SwapChainD3D12.h
@@ -17,23 +17,71 @@
 
 #include "dawn_native/SwapChain.h"
 
+#include "dawn_native/IntegerTypes.h"
+#include "dawn_native/d3d12/d3d12_platform.h"
+
 namespace dawn_native { namespace d3d12 {
 
     class Device;
+    class Texture;
 
-    class SwapChain final : public OldSwapChainBase {
+    class OldSwapChain final : public OldSwapChainBase {
       public:
-        static Ref<SwapChain> Create(Device* device, const SwapChainDescriptor* descriptor);
+        static Ref<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;
 
         wgpu::TextureUsage mTextureUsage;
     };
 
+    class SwapChain final : public NewSwapChainBase {
+      public:
+        static ResultOrError<Ref<SwapChain>> Create(Device* device,
+                                                Surface* surface,
+                                                NewSwapChainBase* previousSwapChain,
+                                                const SwapChainDescriptor* descriptor);
+
+      private:
+        ~SwapChain() override;
+
+        using NewSwapChainBase::NewSwapChainBase;
+        MaybeError Initialize(NewSwapChainBase* previousSwapChain);
+
+        struct Config {
+            // Information that's passed to the D3D12 swapchain creation call.
+            UINT bufferCount;
+            UINT swapChainFlags;
+            DXGI_FORMAT format;
+            DXGI_USAGE usage;
+        };
+
+        // NewSwapChainBase implementation
+        MaybeError PresentImpl() override;
+        ResultOrError<TextureViewBase*> GetCurrentTextureViewImpl() override;
+        void DetachFromSurfaceImpl() override;
+
+        // Does the swapchain initialization steps assuming there is nothing we can reuse.
+        MaybeError InitializeSwapChainFromScratch();
+        // Does the swapchain initialization step of gathering the buffers.
+        MaybeError CollectSwapChainBuffers();
+        // Calls DetachFromSurface but also synchronously waits until all references to the
+        // swapchain and buffers are removed, as that's a constraint for some DXGI operations.
+        MaybeError DetachAndWaitForDeallocation();
+
+        Config mConfig;
+
+        ComPtr<IDXGISwapChain3> mDXGISwapChain;
+        std::vector<ComPtr<ID3D12Resource>> mBuffers;
+        std::vector<ExecutionSerial> mBufferLastUsedSerials;
+        uint32_t mCurrentBuffer = 0;
+
+        Ref<Texture> mApiTexture;
+    };
+
 }}  // namespace dawn_native::d3d12
 
 #endif  // DAWNNATIVE_D3D12_SWAPCHAIN_D3D12_H_
diff --git a/src/dawn_native/d3d12/TextureD3D12.cpp b/src/dawn_native/d3d12/TextureD3D12.cpp
index fdcd43e..6274801 100644
--- a/src/dawn_native/d3d12/TextureD3D12.cpp
+++ b/src/dawn_native/d3d12/TextureD3D12.cpp
@@ -530,9 +530,6 @@
         // texture is owned externally. The texture's owning entity must remain responsible for
         // memory management.
         mResourceAllocation = { info, 0, std::move(d3d12Texture), nullptr };
-
-        SetIsSubresourceContentInitialized(true, GetAllSubresources());
-
         return {};
     }