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 {};
}