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()); +}