From d26ee85fba6d511eff4362f5b72eaca2dd36657c Mon Sep 17 00:00:00 2001 From: Corentin Wallez Date: Sat, 25 Jan 2020 10:05:40 +0000 Subject: [PATCH] Implement the webgpu.h swapchains in the frontend and Null backend. The state-tracking of the webgpu.h swapchain is a bit complicated because contrary to implementation-based swapchains, they have more guarantees and a "replacing mechanism". For example instead of hoping the implementation-based swapchain resize automatically, the surface-based swapchain needs to be replaced by a new swapchain and invalidated. This mechanism of invalidation also needs to be triggered when the last reference to the surface is lost because we don't want to risk the application destroying the window from under us. Adds tests for all the cases of invalidation I could think of apart from device loss. Bug: dawn:269 Change-Id: Id515dbb640e13c6e30bb1f1e93b8e54f1e2bba4b Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/15400 Commit-Queue: Corentin Wallez Reviewed-by: Kai Ninomiya --- src/dawn_native/Device.cpp | 15 +- src/dawn_native/Device.h | 4 +- src/dawn_native/Forward.h | 1 + src/dawn_native/Surface.cpp | 16 +- src/dawn_native/Surface.h | 8 + src/dawn_native/SwapChain.cpp | 114 ++++++++++++- src/dawn_native/SwapChain.h | 43 ++++- src/dawn_native/d3d12/DeviceD3D12.cpp | 3 +- src/dawn_native/d3d12/DeviceD3D12.h | 3 +- src/dawn_native/metal/DeviceMTL.h | 3 +- src/dawn_native/metal/DeviceMTL.mm | 3 +- src/dawn_native/null/DeviceNull.cpp | 38 ++++- src/dawn_native/null/DeviceNull.h | 17 +- src/dawn_native/opengl/DeviceGL.cpp | 3 +- src/dawn_native/opengl/DeviceGL.h | 3 +- src/dawn_native/vulkan/DeviceVk.cpp | 3 +- src/dawn_native/vulkan/DeviceVk.h | 3 +- .../end2end/SwapChainValidationTests.cpp | 150 +++++++++++++++++- 18 files changed, 404 insertions(+), 26 deletions(-) diff --git a/src/dawn_native/Device.cpp b/src/dawn_native/Device.cpp index 391c393281..81a3177b68 100644 --- a/src/dawn_native/Device.cpp +++ b/src/dawn_native/Device.cpp @@ -35,6 +35,7 @@ #include "dawn_native/RenderPipeline.h" #include "dawn_native/Sampler.h" #include "dawn_native/ShaderModule.h" +#include "dawn_native/Surface.h" #include "dawn_native/SwapChain.h" #include "dawn_native/Texture.h" #include "dawn_native/ValidationUtils_autogen.h" @@ -833,7 +834,19 @@ namespace dawn_native { DAWN_TRY_ASSIGN(*result, CreateSwapChainImpl(descriptor)); } else { ASSERT(descriptor->implementation == 0); - DAWN_TRY_ASSIGN(*result, CreateSwapChainImpl(surface, descriptor)); + + NewSwapChainBase* previousSwapChain = surface->GetAttachedSwapChain(); + NewSwapChainBase* newSwapChain; + DAWN_TRY_ASSIGN(newSwapChain, + CreateSwapChainImpl(surface, previousSwapChain, descriptor)); + + if (previousSwapChain != nullptr) { + ASSERT(!previousSwapChain->IsAttached()); + } + ASSERT(newSwapChain->IsAttached()); + + surface->SetAttachedSwapChain(newSwapChain); + *result = newSwapChain; } return {}; } diff --git a/src/dawn_native/Device.h b/src/dawn_native/Device.h index cffb169c36..622c436e02 100644 --- a/src/dawn_native/Device.h +++ b/src/dawn_native/Device.h @@ -224,8 +224,10 @@ namespace dawn_native { const ShaderModuleDescriptor* descriptor) = 0; virtual ResultOrError CreateSwapChainImpl( const SwapChainDescriptor* descriptor) = 0; - virtual ResultOrError CreateSwapChainImpl( + // Note that previousSwapChain may be nullptr, or come from a different backend. + virtual ResultOrError CreateSwapChainImpl( Surface* surface, + NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) = 0; virtual ResultOrError CreateTextureImpl( const TextureDescriptor* descriptor) = 0; diff --git a/src/dawn_native/Forward.h b/src/dawn_native/Forward.h index 538c3f3f07..05538e85ee 100644 --- a/src/dawn_native/Forward.h +++ b/src/dawn_native/Forward.h @@ -42,6 +42,7 @@ namespace dawn_native { class ShaderModuleBase; class StagingBufferBase; class SwapChainBase; + class NewSwapChainBase; class TextureBase; class TextureViewBase; diff --git a/src/dawn_native/Surface.cpp b/src/dawn_native/Surface.cpp index fa90d78620..eee9335a16 100644 --- a/src/dawn_native/Surface.cpp +++ b/src/dawn_native/Surface.cpp @@ -16,6 +16,7 @@ #include "common/Platform.h" #include "dawn_native/Instance.h" +#include "dawn_native/SwapChain.h" #if defined(DAWN_PLATFORM_WINDOWS) # include "common/windows_with_undefs.h" @@ -137,7 +138,20 @@ namespace dawn_native { } } - Surface::~Surface() = default; + Surface::~Surface() { + if (mSwapChain != nullptr) { + mSwapChain->DetachFromSurface(); + mSwapChain = nullptr; + } + } + + NewSwapChainBase* Surface::GetAttachedSwapChain() const { + return mSwapChain; + } + + void Surface::SetAttachedSwapChain(NewSwapChainBase* swapChain) { + mSwapChain = swapChain; + } InstanceBase* Surface::GetInstance() { return mInstance.Get(); diff --git a/src/dawn_native/Surface.h b/src/dawn_native/Surface.h index 8f9ca146ad..b33cf8a80e 100644 --- a/src/dawn_native/Surface.h +++ b/src/dawn_native/Surface.h @@ -29,11 +29,16 @@ namespace dawn_native { // A surface is a sum types of all the kind of windows Dawn supports. The OS-specific types // aren't used because they would cause compilation errors on other OSes (or require // ObjectiveC). + // The surface is also used to store the current swapchain so that we can detach it when it is + // replaced. class Surface final : public RefCounted { public: Surface(InstanceBase* instance, const SurfaceDescriptor* descriptor); ~Surface(); + void SetAttachedSwapChain(NewSwapChainBase* swapChain); + NewSwapChainBase* GetAttachedSwapChain() const; + // These are valid to call on all Surfaces. enum class Type { MetalLayer, WindowsHWND, Xlib }; Type GetType() const; @@ -54,6 +59,9 @@ namespace dawn_native { Ref mInstance; Type mType; + // The swapchain will set this to null when it is destroyed. + NewSwapChainBase* mSwapChain = nullptr; + // MetalLayer void* mMetalLayer = nullptr; diff --git a/src/dawn_native/SwapChain.cpp b/src/dawn_native/SwapChain.cpp index 7118e94ad5..1053aafefe 100644 --- a/src/dawn_native/SwapChain.cpp +++ b/src/dawn_native/SwapChain.cpp @@ -15,6 +15,7 @@ #include "dawn_native/SwapChain.h" #include "common/Constants.h" +#include "dawn_native/Adapter.h" #include "dawn_native/Device.h" #include "dawn_native/Surface.h" #include "dawn_native/Texture.h" @@ -96,6 +97,19 @@ namespace dawn_native { return {}; } + TextureDescriptor GetSwapChainBaseTextureDescriptor(NewSwapChainBase* swapChain) { + TextureDescriptor desc; + desc.usage = swapChain->GetUsage(); + desc.dimension = wgpu::TextureDimension::e2D; + desc.size = {swapChain->GetWidth(), swapChain->GetHeight(), 1}; + desc.arrayLayerCount = 1; + desc.format = swapChain->GetFormat(); + desc.mipLevelCount = 1; + desc.sampleCount = 1; + + return desc; + } + // SwapChainBase SwapChainBase::SwapChainBase(DeviceBase* device) : ObjectBase(device) { @@ -246,6 +260,7 @@ namespace dawn_native { Surface* surface, const SwapChainDescriptor* descriptor) : SwapChainBase(device), + mAttached(true), mWidth(descriptor->width), mHeight(descriptor->height), mFormat(descriptor->format), @@ -253,6 +268,25 @@ namespace dawn_native { mSurface(surface) { } + NewSwapChainBase::~NewSwapChainBase() { + if (mCurrentTextureView.Get() != nullptr) { + ASSERT(mCurrentTextureView->GetTexture()->GetTextureState() == + TextureBase::TextureState::Destroyed); + } + + ASSERT(!mAttached); + ASSERT(mSurface == nullptr); + } + + void NewSwapChainBase::DetachFromSurface() { + if (mAttached) { + DetachFromSurfaceImpl(); + GetSurface()->SetAttachedSwapChain(nullptr); + mSurface = nullptr; + mAttached = false; + } + } + void NewSwapChainBase::Configure(wgpu::TextureFormat format, wgpu::TextureUsage allowedUsage, uint32_t width, @@ -262,14 +296,48 @@ namespace dawn_native { } TextureViewBase* NewSwapChainBase::GetCurrentTextureView() { - GetDevice()->ConsumedError(DAWN_VALIDATION_ERROR( - "GetCurrentTextureView not implemented yet for surface-based swapchains")); - return TextureViewBase::MakeError(GetDevice()); + if (GetDevice()->ConsumedError(ValidateGetCurrentTextureView())) { + return TextureViewBase::MakeError(GetDevice()); + } + + if (mCurrentTextureView.Get() != nullptr) { + // Calling GetCurrentTextureView always returns a new reference so add it even when + // reusing the existing texture view. + mCurrentTextureView->Reference(); + return mCurrentTextureView.Get(); + } + + TextureViewBase* view = nullptr; + if (GetDevice()->ConsumedError(GetCurrentTextureViewImpl(), &view)) { + return TextureViewBase::MakeError(GetDevice()); + } + + // Check that the return texture view matches exactly what was given for this descriptor. + ASSERT(view->GetTexture()->GetFormat().format == mFormat); + ASSERT((view->GetTexture()->GetUsage() & mUsage) == mUsage); + ASSERT(view->GetLevelCount() == 1); + ASSERT(view->GetLayerCount() == 1); + ASSERT(view->GetDimension() == wgpu::TextureViewDimension::e2D); + ASSERT(view->GetTexture()->GetMipLevelVirtualSize(view->GetBaseMipLevel()).width == mWidth); + ASSERT(view->GetTexture()->GetMipLevelVirtualSize(view->GetBaseMipLevel()).height == + mHeight); + + mCurrentTextureView = view; + return view; } void NewSwapChainBase::Present() { - GetDevice()->ConsumedError( - DAWN_VALIDATION_ERROR("Present not implemented yet for surface-based swapchains")); + if (GetDevice()->ConsumedError(ValidatePresent())) { + return; + } + + if (GetDevice()->ConsumedError(PresentImpl())) { + return; + } + + ASSERT(mCurrentTextureView->GetTexture()->GetTextureState() == + TextureBase::TextureState::Destroyed); + mCurrentTextureView = nullptr; } uint32_t NewSwapChainBase::GetWidth() const { @@ -289,7 +357,41 @@ namespace dawn_native { } Surface* NewSwapChainBase::GetSurface() { - return mSurface.Get(); + return mSurface; + } + + bool NewSwapChainBase::IsAttached() const { + return mAttached; + } + + wgpu::BackendType NewSwapChainBase::GetBackendType() const { + return GetDevice()->GetAdapter()->GetBackendType(); + } + + MaybeError NewSwapChainBase::ValidatePresent() const { + DAWN_TRY(GetDevice()->ValidateIsAlive()); + DAWN_TRY(GetDevice()->ValidateObject(this)); + + if (!mAttached) { + return DAWN_VALIDATION_ERROR("Presenting on detached swapchain"); + } + + if (mCurrentTextureView.Get() == nullptr) { + return DAWN_VALIDATION_ERROR("Presenting without prior GetCurrentTextureView"); + } + + return {}; + } + + MaybeError NewSwapChainBase::ValidateGetCurrentTextureView() const { + DAWN_TRY(GetDevice()->ValidateIsAlive()); + DAWN_TRY(GetDevice()->ValidateObject(this)); + + if (!mAttached) { + return DAWN_VALIDATION_ERROR("Getting view on detached swapchain"); + } + + return {}; } } // namespace dawn_native diff --git a/src/dawn_native/SwapChain.h b/src/dawn_native/SwapChain.h index cade83d443..ff16e02c63 100644 --- a/src/dawn_native/SwapChain.h +++ b/src/dawn_native/SwapChain.h @@ -28,6 +28,8 @@ namespace dawn_native { const Surface* surface, const SwapChainDescriptor* descriptor); + TextureDescriptor GetSwapChainBaseTextureDescriptor(NewSwapChainBase* swapChain); + class SwapChainBase : public ObjectBase { public: SwapChainBase(DeviceBase* device); @@ -91,7 +93,24 @@ namespace dawn_native { NewSwapChainBase(DeviceBase* device, Surface* surface, const SwapChainDescriptor* descriptor); + ~NewSwapChainBase() override; + // This is called when the swapchain is detached for any reason: + // + // - The swapchain is being destroyed. + // - The surface it is attached to is being destroyed. + // - The swapchain is being replaced by another one on the surface. + // + // The call for the old swapchain being replaced should be called inside the backend + // implementation of SwapChains. This is to allow them to acquire any resources before + // calling detach to make a seamless transition from the previous swapchain. + // + // Likewise the call for the swapchain being destroyed must be done in the backend's + // swapchain's destructor since C++ says it is UB to call virtual methods in the base class + // destructor. + void DetachFromSurface(); + + // Dawn API void Configure(wgpu::TextureFormat format, wgpu::TextureUsage allowedUsage, uint32_t width, @@ -104,14 +123,36 @@ namespace dawn_native { wgpu::TextureFormat GetFormat() const; wgpu::TextureUsage GetUsage() const; Surface* GetSurface(); + bool IsAttached() const; + wgpu::BackendType GetBackendType() const; private: + bool mAttached; uint32_t mWidth; uint32_t mHeight; wgpu::TextureFormat mFormat; wgpu::TextureUsage mUsage; - Ref mSurface; + // This is a weak reference to the surface. If the surface is destroyed it will call + // DetachFromSurface and mSurface will be updated to nullptr. + Surface* mSurface = nullptr; + Ref mCurrentTextureView; + + MaybeError ValidatePresent() const; + MaybeError ValidateGetCurrentTextureView() const; + + // GetCurrentTextureViewImpl and PresentImpl are guaranteed to be called in an interleaved + // manner, starting with GetCurrentTextureViewImpl. + + // The returned texture view must match the swapchain descriptor exactly. + virtual ResultOrError GetCurrentTextureViewImpl() = 0; + // The call to present must destroy the current view's texture so further access to it are + // invalid. + virtual MaybeError PresentImpl() = 0; + + // Guaranteed to be called exactly once during the lifetime of the SwapChain. After it is + // called no other virtual method can be called. + virtual void DetachFromSurfaceImpl() = 0; }; } // namespace dawn_native diff --git a/src/dawn_native/d3d12/DeviceD3D12.cpp b/src/dawn_native/d3d12/DeviceD3D12.cpp index c019879b4c..18734e691c 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.cpp +++ b/src/dawn_native/d3d12/DeviceD3D12.cpp @@ -254,8 +254,9 @@ namespace dawn_native { namespace d3d12 { const SwapChainDescriptor* descriptor) { return new SwapChain(this, descriptor); } - ResultOrError Device::CreateSwapChainImpl( + ResultOrError Device::CreateSwapChainImpl( Surface* surface, + NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) { return DAWN_VALIDATION_ERROR("New swapchains not implemented."); } diff --git a/src/dawn_native/d3d12/DeviceD3D12.h b/src/dawn_native/d3d12/DeviceD3D12.h index 62a70c43fe..65edada7a2 100644 --- a/src/dawn_native/d3d12/DeviceD3D12.h +++ b/src/dawn_native/d3d12/DeviceD3D12.h @@ -122,8 +122,9 @@ namespace dawn_native { namespace d3d12 { const ShaderModuleDescriptor* descriptor) override; ResultOrError CreateSwapChainImpl( const SwapChainDescriptor* descriptor) override; - ResultOrError CreateSwapChainImpl( + ResultOrError CreateSwapChainImpl( Surface* surface, + NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) override; ResultOrError CreateTextureImpl(const TextureDescriptor* descriptor) override; ResultOrError CreateTextureViewImpl( diff --git a/src/dawn_native/metal/DeviceMTL.h b/src/dawn_native/metal/DeviceMTL.h index e2000d00ed..d881ea0401 100644 --- a/src/dawn_native/metal/DeviceMTL.h +++ b/src/dawn_native/metal/DeviceMTL.h @@ -85,8 +85,9 @@ namespace dawn_native { namespace metal { const ShaderModuleDescriptor* descriptor) override; ResultOrError CreateSwapChainImpl( const SwapChainDescriptor* descriptor) override; - ResultOrError CreateSwapChainImpl( + ResultOrError CreateSwapChainImpl( Surface* surface, + NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) override; ResultOrError CreateTextureImpl(const TextureDescriptor* descriptor) override; ResultOrError CreateTextureViewImpl( diff --git a/src/dawn_native/metal/DeviceMTL.mm b/src/dawn_native/metal/DeviceMTL.mm index d4a4e5cfd7..830b99a5ae 100644 --- a/src/dawn_native/metal/DeviceMTL.mm +++ b/src/dawn_native/metal/DeviceMTL.mm @@ -116,8 +116,9 @@ namespace dawn_native { namespace metal { const SwapChainDescriptor* descriptor) { return new SwapChain(this, descriptor); } - ResultOrError Device::CreateSwapChainImpl( + ResultOrError Device::CreateSwapChainImpl( Surface* surface, + NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) { return DAWN_VALIDATION_ERROR("New swapchains not implemented."); } diff --git a/src/dawn_native/null/DeviceNull.cpp b/src/dawn_native/null/DeviceNull.cpp index 579aec1939..2d29183c8a 100644 --- a/src/dawn_native/null/DeviceNull.cpp +++ b/src/dawn_native/null/DeviceNull.cpp @@ -153,10 +153,11 @@ namespace dawn_native { namespace null { const SwapChainDescriptor* descriptor) { return new OldSwapChain(this, descriptor); } - ResultOrError Device::CreateSwapChainImpl( + ResultOrError Device::CreateSwapChainImpl( Surface* surface, + NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) { - return new SwapChain(this, surface, descriptor); + return new SwapChain(this, surface, previousSwapChain, descriptor); } ResultOrError Device::CreateTextureImpl(const TextureDescriptor* descriptor) { return new Texture(this, descriptor, TextureBase::TextureState::OwnedInternal); @@ -358,11 +359,42 @@ namespace dawn_native { namespace null { // SwapChain - SwapChain::SwapChain(Device* device, Surface* surface, const SwapChainDescriptor* descriptor) + SwapChain::SwapChain(Device* device, + Surface* surface, + NewSwapChainBase* previousSwapChain, + const SwapChainDescriptor* descriptor) : NewSwapChainBase(device, surface, descriptor) { + if (previousSwapChain != nullptr) { + // 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::Null); + previousSwapChain->DetachFromSurface(); + } } SwapChain::~SwapChain() { + DetachFromSurface(); + } + + MaybeError SwapChain::PresentImpl() { + mTexture->Destroy(); + mTexture = nullptr; + return {}; + } + + ResultOrError SwapChain::GetCurrentTextureViewImpl() { + TextureDescriptor textureDesc = GetSwapChainBaseTextureDescriptor(this); + mTexture = AcquireRef( + new Texture(GetDevice(), &textureDesc, TextureBase::TextureState::OwnedInternal)); + return mTexture->CreateView(nullptr); + } + + void SwapChain::DetachFromSurfaceImpl() { + if (mTexture.Get() != nullptr) { + mTexture->Destroy(); + mTexture = nullptr; + } } // OldSwapChain diff --git a/src/dawn_native/null/DeviceNull.h b/src/dawn_native/null/DeviceNull.h index f7b8d56c3a..dee944aabd 100644 --- a/src/dawn_native/null/DeviceNull.h +++ b/src/dawn_native/null/DeviceNull.h @@ -125,8 +125,9 @@ namespace dawn_native { namespace null { const ShaderModuleDescriptor* descriptor) override; ResultOrError CreateSwapChainImpl( const SwapChainDescriptor* descriptor) override; - ResultOrError CreateSwapChainImpl( + ResultOrError CreateSwapChainImpl( Surface* surface, + NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) override; ResultOrError CreateTextureImpl(const TextureDescriptor* descriptor) override; ResultOrError CreateTextureViewImpl( @@ -202,8 +203,18 @@ namespace dawn_native { namespace null { class SwapChain : public NewSwapChainBase { public: - SwapChain(Device* device, Surface* surface, const SwapChainDescriptor* descriptor); - ~SwapChain(); + SwapChain(Device* device, + Surface* surface, + NewSwapChainBase* previousSwapChain, + const SwapChainDescriptor* descriptor); + ~SwapChain() override; + + private: + Ref mTexture; + + MaybeError PresentImpl() override; + ResultOrError GetCurrentTextureViewImpl() override; + void DetachFromSurfaceImpl() override; }; class OldSwapChain : public OldSwapChainBase { diff --git a/src/dawn_native/opengl/DeviceGL.cpp b/src/dawn_native/opengl/DeviceGL.cpp index 38f030969a..f6c2e1a2b1 100644 --- a/src/dawn_native/opengl/DeviceGL.cpp +++ b/src/dawn_native/opengl/DeviceGL.cpp @@ -96,8 +96,9 @@ namespace dawn_native { namespace opengl { const SwapChainDescriptor* descriptor) { return new SwapChain(this, descriptor); } - ResultOrError Device::CreateSwapChainImpl( + ResultOrError Device::CreateSwapChainImpl( Surface* surface, + NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) { return DAWN_VALIDATION_ERROR("New swapchains not implemented."); } diff --git a/src/dawn_native/opengl/DeviceGL.h b/src/dawn_native/opengl/DeviceGL.h index 9c2600fe10..7d0b803191 100644 --- a/src/dawn_native/opengl/DeviceGL.h +++ b/src/dawn_native/opengl/DeviceGL.h @@ -80,8 +80,9 @@ namespace dawn_native { namespace opengl { const ShaderModuleDescriptor* descriptor) override; ResultOrError CreateSwapChainImpl( const SwapChainDescriptor* descriptor) override; - ResultOrError CreateSwapChainImpl( + ResultOrError CreateSwapChainImpl( Surface* surface, + NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) override; ResultOrError CreateTextureImpl(const TextureDescriptor* descriptor) override; ResultOrError CreateTextureViewImpl( diff --git a/src/dawn_native/vulkan/DeviceVk.cpp b/src/dawn_native/vulkan/DeviceVk.cpp index f31ea969c6..bc4179a98b 100644 --- a/src/dawn_native/vulkan/DeviceVk.cpp +++ b/src/dawn_native/vulkan/DeviceVk.cpp @@ -161,8 +161,9 @@ namespace dawn_native { namespace vulkan { const SwapChainDescriptor* descriptor) { return SwapChain::Create(this, descriptor); } - ResultOrError Device::CreateSwapChainImpl( + ResultOrError Device::CreateSwapChainImpl( Surface* surface, + NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) { return DAWN_VALIDATION_ERROR("New swapchains not implemented."); } diff --git a/src/dawn_native/vulkan/DeviceVk.h b/src/dawn_native/vulkan/DeviceVk.h index bd8df744d2..7175710566 100644 --- a/src/dawn_native/vulkan/DeviceVk.h +++ b/src/dawn_native/vulkan/DeviceVk.h @@ -119,8 +119,9 @@ namespace dawn_native { namespace vulkan { const ShaderModuleDescriptor* descriptor) override; ResultOrError CreateSwapChainImpl( const SwapChainDescriptor* descriptor) override; - ResultOrError CreateSwapChainImpl( + ResultOrError CreateSwapChainImpl( Surface* surface, + NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) override; ResultOrError CreateTextureImpl(const TextureDescriptor* descriptor) override; ResultOrError CreateTextureViewImpl( diff --git a/src/tests/end2end/SwapChainValidationTests.cpp b/src/tests/end2end/SwapChainValidationTests.cpp index 1a91958b18..19006041b1 100644 --- a/src/tests/end2end/SwapChainValidationTests.cpp +++ b/src/tests/end2end/SwapChainValidationTests.cpp @@ -16,6 +16,7 @@ #include "common/Constants.h" #include "common/Log.h" +#include "utils/ComboRenderPipelineDescriptor.h" #include "utils/GLFWUtils.h" #include "utils/WGPUHelpers.h" @@ -69,11 +70,26 @@ class SwapChainValidationTests : public ValidationTest { pass.EndPass(); ASSERT_DEVICE_ERROR(encoder.Finish()); } + + // Checks that an OutputAttachment view is an error by trying to create a render pass on it. + void CheckTextureViewIsDestroyed(wgpu::TextureView view) { + utils::ComboRenderPassDescriptor renderPassDesc({view}); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc); + pass.EndPass(); + wgpu::CommandBuffer commands = encoder.Finish(); + + wgpu::Queue queue = device.CreateQueue(); + ASSERT_DEVICE_ERROR(queue.Submit(1, &commands)); + } }; -// Control case for a successful swapchain creation. +// Control case for a successful swapchain creation and presenting. TEST_F(SwapChainValidationTests, CreationSuccess) { - device.CreateSwapChain(surface, &goodDescriptor); + wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor); + wgpu::TextureView view = swapchain.GetCurrentTextureView(); + swapchain.Present(); } // Checks that the creation size must be a valid 2D texture size. @@ -144,3 +160,133 @@ TEST_F(SwapChainValidationTests, OperationsOnErrorSwapChain) { ASSERT_DEVICE_ERROR(swapchain.Present()); } + +// Check it is invalid to call present without getting a current view. +TEST_F(SwapChainValidationTests, PresentWithoutCurrentView) { + wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor); + + // Check it is invalid if we never called GetCurrentTextureView + ASSERT_DEVICE_ERROR(swapchain.Present()); + + // Check it is invalid if we never called since the last present. + swapchain.GetCurrentTextureView(); + swapchain.Present(); + ASSERT_DEVICE_ERROR(swapchain.Present()); +} + +// Check that the current view is in the destroyed state after the swapchain is destroyed. +TEST_F(SwapChainValidationTests, ViewDestroyedAfterSwapChainDestruction) { + wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor); + wgpu::TextureView view = swapchain.GetCurrentTextureView(); + swapchain = nullptr; + + CheckTextureViewIsDestroyed(view); +} + +// Check that the current view is the destroyed state after present. +TEST_F(SwapChainValidationTests, ViewDestroyedAfterPresent) { + wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor); + wgpu::TextureView view = swapchain.GetCurrentTextureView(); + swapchain.Present(); + + CheckTextureViewIsDestroyed(view); +} + +// Check that returned view is of the current format / usage / dimension / size / sample count +TEST_F(SwapChainValidationTests, ReturnedViewCharacteristics) { + utils::ComboRenderPipelineDescriptor pipelineDesc(device); + pipelineDesc.vertexStage.module = + utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"( + #version 450 + void main() { + gl_Position = vec4(0.0, 0.0, 0.0, 1.0); + })"); + pipelineDesc.cFragmentStage.module = + utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"( + #version 450 + layout(location = 0) out vec4 fragColor; + void main() { + fragColor = vec4(0.0, 1.0, 0.0, 1.0); + })"); + // Validation will check that the sample count of the view matches this format. + pipelineDesc.sampleCount = 1; + pipelineDesc.colorStateCount = 2; + // Validation will check that the format of the view matches this format. + pipelineDesc.cColorStates[0].format = wgpu::TextureFormat::BGRA8Unorm; + pipelineDesc.cColorStates[1].format = wgpu::TextureFormat::R8Unorm; + device.CreateRenderPipeline(&pipelineDesc); + + // Create a second texture to be used as render pass attachment. Validation will check that the + // size of the view matches the size of this texture. + wgpu::TextureDescriptor textureDesc; + textureDesc.usage = wgpu::TextureUsage::OutputAttachment; + textureDesc.dimension = wgpu::TextureDimension::e2D; + textureDesc.size = {1, 1, 1}; + textureDesc.format = wgpu::TextureFormat::R8Unorm; + textureDesc.sampleCount = 1; + wgpu::Texture secondTexture = device.CreateTexture(&textureDesc); + + // Get the swapchain view and try to use it in the render pass to trigger all the validation. + wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor); + wgpu::TextureView view = swapchain.GetCurrentTextureView(); + + // Validation will also check the dimension of the view is 2D, and it's usage contains + // OutputAttachment + utils::ComboRenderPassDescriptor renderPassDesc({view, secondTexture.CreateView()}); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDesc); + pass.EndPass(); + wgpu::CommandBuffer commands = encoder.Finish(); + + wgpu::Queue queue = device.CreateQueue(); + queue.Submit(1, &commands); + + // Check that view doesn't have extra formats like Sampled. + // TODO(cwallez@chromium.org): also check for [Readonly]Storage once that's implemented. + wgpu::BindGroupLayout bgl = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Fragment, wgpu::BindingType::SampledTexture}}); + ASSERT_DEVICE_ERROR(utils::MakeBindGroup(device, bgl, {{0, view}})); +} + +// Check that failing to create a new swapchain doesn't replace the previous one. +TEST_F(SwapChainValidationTests, ErrorSwapChainDoesntReplacePreviousOne) { + wgpu::SwapChain swapchain = device.CreateSwapChain(surface, &goodDescriptor); + ASSERT_DEVICE_ERROR(device.CreateSwapChain(surface, &badDescriptor)); + + wgpu::TextureView view = swapchain.GetCurrentTextureView(); + swapchain.Present(); +} + +// Check that after replacement, all swapchain operations are errors and the view is destroyed. +TEST_F(SwapChainValidationTests, ReplacedSwapChainIsInvalid) { + { + wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor); + device.CreateSwapChain(surface, &goodDescriptor); + ASSERT_DEVICE_ERROR(replacedSwapChain.GetCurrentTextureView()); + } + + { + wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor); + wgpu::TextureView view = replacedSwapChain.GetCurrentTextureView(); + device.CreateSwapChain(surface, &goodDescriptor); + + CheckTextureViewIsDestroyed(view); + ASSERT_DEVICE_ERROR(replacedSwapChain.Present()); + } +} + +// Check that after surface destruction, all swapchain operations are errors and the view is +// destroyed. The test is split in two to reset the wgpu::Surface in the middle. +TEST_F(SwapChainValidationTests, SwapChainIsInvalidAfterSurfaceDestruction_GetView) { + wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor); + surface = nullptr; + ASSERT_DEVICE_ERROR(replacedSwapChain.GetCurrentTextureView()); +} +TEST_F(SwapChainValidationTests, SwapChainIsInvalidAfterSurfaceDestruction_AfterGetView) { + wgpu::SwapChain replacedSwapChain = device.CreateSwapChain(surface, &goodDescriptor); + wgpu::TextureView view = replacedSwapChain.GetCurrentTextureView(); + surface = nullptr; + + CheckTextureViewIsDestroyed(view); + ASSERT_DEVICE_ERROR(replacedSwapChain.Present()); +}