// Copyright 2017 The Dawn Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "dawn/native/SwapChain.h" #include "dawn/common/Constants.h" #include "dawn/native/Adapter.h" #include "dawn/native/Device.h" #include "dawn/native/ObjectType_autogen.h" #include "dawn/native/Surface.h" #include "dawn/native/Texture.h" #include "dawn/native/ValidationUtils_autogen.h" namespace dawn::native { namespace { class ErrorSwapChain final : public SwapChainBase { public: ErrorSwapChain(DeviceBase* device) : SwapChainBase(device, ObjectBase::kError) { } private: void APIConfigure(wgpu::TextureFormat format, wgpu::TextureUsage allowedUsage, uint32_t width, uint32_t height) override { GetDevice()->ConsumedError( DAWN_FORMAT_VALIDATION_ERROR("%s is an error swapchain.", this)); } TextureViewBase* APIGetCurrentTextureView() override { GetDevice()->ConsumedError( DAWN_FORMAT_VALIDATION_ERROR("%s is an error swapchain.", this)); return TextureViewBase::MakeError(GetDevice()); } void APIPresent() override { GetDevice()->ConsumedError( DAWN_FORMAT_VALIDATION_ERROR("%s is an error swapchain.", this)); } }; } // anonymous namespace MaybeError ValidateSwapChainDescriptor(const DeviceBase* device, const Surface* surface, const SwapChainDescriptor* descriptor) { if (descriptor->implementation != 0) { DAWN_INVALID_IF(surface != nullptr, "Exactly one of surface or implementation must be set"); DawnSwapChainImplementation* impl = reinterpret_cast(descriptor->implementation); DAWN_INVALID_IF(!impl->Init || !impl->Destroy || !impl->Configure || !impl->GetNextTexture || !impl->Present, "Implementation is incomplete"); } else { DAWN_INVALID_IF(surface == nullptr, "At least one of surface or implementation must be set"); DAWN_TRY(ValidatePresentMode(descriptor->presentMode)); // TODO(crbug.com/dawn/160): Lift this restriction once wgpu::Instance::GetPreferredSurfaceFormat is // implemented. // TODO(dawn:286): #if defined(DAWN_PLATFORM_ANDROID) constexpr wgpu::TextureFormat kRequireSwapChainFormat = wgpu::TextureFormat::RGBA8Unorm; #else constexpr wgpu::TextureFormat kRequireSwapChainFormat = wgpu::TextureFormat::BGRA8Unorm; #endif // !defined(DAWN_PLATFORM_ANDROID) DAWN_INVALID_IF(descriptor->format != kRequireSwapChainFormat, "Format (%s) is not %s, which is (currently) the only accepted format.", descriptor->format, kRequireSwapChainFormat); DAWN_INVALID_IF(descriptor->usage != wgpu::TextureUsage::RenderAttachment, "Usage (%s) is not %s, which is (currently) the only accepted usage.", descriptor->usage, wgpu::TextureUsage::RenderAttachment); DAWN_INVALID_IF(descriptor->width == 0 || descriptor->height == 0, "Swap Chain size (width: %u, height: %u) is empty.", descriptor->width, descriptor->height); DAWN_INVALID_IF( descriptor->width > device->GetLimits().v1.maxTextureDimension2D || descriptor->height > device->GetLimits().v1.maxTextureDimension2D, "Swap Chain size (width: %u, height: %u) is greater than the maximum 2D texture " "size (width: %u, height: %u).", descriptor->width, descriptor->height, device->GetLimits().v1.maxTextureDimension2D, device->GetLimits().v1.maxTextureDimension2D); } return {}; } TextureDescriptor GetSwapChainBaseTextureDescriptor(NewSwapChainBase* swapChain) { TextureDescriptor desc; desc.usage = swapChain->GetUsage(); desc.dimension = wgpu::TextureDimension::e2D; desc.size = {swapChain->GetWidth(), swapChain->GetHeight(), 1}; desc.format = swapChain->GetFormat(); desc.mipLevelCount = 1; desc.sampleCount = 1; return desc; } // SwapChainBase SwapChainBase::SwapChainBase(DeviceBase* device) : ApiObjectBase(device, kLabelNotImplemented) { TrackInDevice(); } SwapChainBase::SwapChainBase(DeviceBase* device, ObjectBase::ErrorTag tag) : ApiObjectBase(device, tag) { } SwapChainBase::~SwapChainBase() { } void SwapChainBase::DestroyImpl() { } // static SwapChainBase* SwapChainBase::MakeError(DeviceBase* device) { return new ErrorSwapChain(device); } ObjectType SwapChainBase::GetType() const { return ObjectType::SwapChain; } // OldSwapChainBase OldSwapChainBase::OldSwapChainBase(DeviceBase* device, const SwapChainDescriptor* descriptor) : SwapChainBase(device), mImplementation( *reinterpret_cast(descriptor->implementation)) { } OldSwapChainBase::~OldSwapChainBase() { if (!IsError()) { const auto& im = GetImplementation(); im.Destroy(im.userData); } } void OldSwapChainBase::APIConfigure(wgpu::TextureFormat format, wgpu::TextureUsage allowedUsage, uint32_t width, uint32_t height) { if (GetDevice()->ConsumedError(ValidateConfigure(format, allowedUsage, width, height))) { return; } ASSERT(!IsError()); allowedUsage |= wgpu::TextureUsage::Present; mFormat = format; mAllowedUsage = allowedUsage; mWidth = width; mHeight = height; mImplementation.Configure(mImplementation.userData, static_cast(format), static_cast(allowedUsage), width, height); } TextureViewBase* OldSwapChainBase::APIGetCurrentTextureView() { if (GetDevice()->ConsumedError(ValidateGetCurrentTextureView())) { return TextureViewBase::MakeError(GetDevice()); } ASSERT(!IsError()); // Return the same current texture view until Present is called. if (mCurrentTextureView != nullptr) { // Calling GetCurrentTextureView always returns a new reference so add it even when // reuse the existing texture view. mCurrentTextureView->Reference(); return mCurrentTextureView.Get(); } // Create the backing texture and the view. TextureDescriptor descriptor; descriptor.dimension = wgpu::TextureDimension::e2D; descriptor.size.width = mWidth; descriptor.size.height = mHeight; descriptor.size.depthOrArrayLayers = 1; descriptor.sampleCount = 1; descriptor.format = mFormat; descriptor.mipLevelCount = 1; descriptor.usage = mAllowedUsage; // Get the texture but remove the external refcount because it is never passed outside // of dawn_native mCurrentTexture = AcquireRef(GetNextTextureImpl(&descriptor)); mCurrentTextureView = mCurrentTexture->APICreateView(); return mCurrentTextureView.Get(); } void OldSwapChainBase::APIPresent() { if (GetDevice()->ConsumedError(ValidatePresent())) { return; } ASSERT(!IsError()); if (GetDevice()->ConsumedError(OnBeforePresent(mCurrentTextureView.Get()))) { return; } mImplementation.Present(mImplementation.userData); mCurrentTexture = nullptr; mCurrentTextureView = nullptr; } const DawnSwapChainImplementation& OldSwapChainBase::GetImplementation() { ASSERT(!IsError()); return mImplementation; } MaybeError OldSwapChainBase::ValidateConfigure(wgpu::TextureFormat format, wgpu::TextureUsage allowedUsage, uint32_t width, uint32_t height) const { DAWN_TRY(GetDevice()->ValidateIsAlive()); DAWN_TRY(GetDevice()->ValidateObject(this)); DAWN_TRY(ValidateTextureUsage(allowedUsage)); DAWN_TRY(ValidateTextureFormat(format)); DAWN_INVALID_IF(width == 0 || height == 0, "Configuration size (width: %u, height: %u) for %s is empty.", width, height, this); return {}; } MaybeError OldSwapChainBase::ValidateGetCurrentTextureView() const { DAWN_TRY(GetDevice()->ValidateIsAlive()); DAWN_TRY(GetDevice()->ValidateObject(this)); // If width is 0, it implies swap chain has never been configured DAWN_INVALID_IF(mWidth == 0, "%s was not configured prior to calling GetNextTexture.", this); return {}; } MaybeError OldSwapChainBase::ValidatePresent() const { DAWN_TRY(GetDevice()->ValidateIsAlive()); DAWN_TRY(GetDevice()->ValidateObject(this)); DAWN_INVALID_IF( mCurrentTextureView == nullptr, "GetCurrentTextureView was not called on %s this frame prior to calling Present.", this); return {}; } // Implementation of NewSwapChainBase NewSwapChainBase::NewSwapChainBase(DeviceBase* device, Surface* surface, const SwapChainDescriptor* descriptor) : SwapChainBase(device), mAttached(false), mWidth(descriptor->width), mHeight(descriptor->height), mFormat(descriptor->format), mUsage(descriptor->usage), mPresentMode(descriptor->presentMode), mSurface(surface) { } NewSwapChainBase::~NewSwapChainBase() { if (mCurrentTextureView != nullptr) { ASSERT(mCurrentTextureView->GetTexture()->GetTextureState() == TextureBase::TextureState::Destroyed); } ASSERT(!mAttached); } void NewSwapChainBase::DetachFromSurface() { if (mAttached) { DetachFromSurfaceImpl(); mSurface = nullptr; mAttached = false; } } void NewSwapChainBase::SetIsAttached() { mAttached = true; } void NewSwapChainBase::APIConfigure(wgpu::TextureFormat format, wgpu::TextureUsage allowedUsage, uint32_t width, uint32_t height) { GetDevice()->ConsumedError( DAWN_FORMAT_VALIDATION_ERROR("Configure is invalid for surface-based swapchains.")); } TextureViewBase* NewSwapChainBase::APIGetCurrentTextureView() { Ref result; if (GetDevice()->ConsumedError(GetCurrentTextureView(), &result, "calling %s.GetCurrentTextureView()", this)) { return TextureViewBase::MakeError(GetDevice()); } return result.Detach(); } ResultOrError> NewSwapChainBase::GetCurrentTextureView() { DAWN_TRY(ValidateGetCurrentTextureView()); if (mCurrentTextureView != nullptr) { // Calling GetCurrentTextureView always returns a new reference. return mCurrentTextureView; } DAWN_TRY_ASSIGN(mCurrentTextureView, GetCurrentTextureViewImpl()); // Check that the return texture view matches exactly what was given for this descriptor. ASSERT(mCurrentTextureView->GetTexture()->GetFormat().format == mFormat); ASSERT(IsSubset(mUsage, mCurrentTextureView->GetTexture()->GetUsage())); ASSERT(mCurrentTextureView->GetLevelCount() == 1); ASSERT(mCurrentTextureView->GetLayerCount() == 1); ASSERT(mCurrentTextureView->GetDimension() == wgpu::TextureViewDimension::e2D); ASSERT(mCurrentTextureView->GetTexture() ->GetMipLevelVirtualSize(mCurrentTextureView->GetBaseMipLevel()) .width == mWidth); ASSERT(mCurrentTextureView->GetTexture() ->GetMipLevelVirtualSize(mCurrentTextureView->GetBaseMipLevel()) .height == mHeight); return mCurrentTextureView; } void NewSwapChainBase::APIPresent() { if (GetDevice()->ConsumedError(ValidatePresent())) { return; } if (GetDevice()->ConsumedError(PresentImpl())) { return; } ASSERT(mCurrentTextureView->GetTexture()->GetTextureState() == TextureBase::TextureState::Destroyed); mCurrentTextureView = nullptr; } uint32_t NewSwapChainBase::GetWidth() const { return mWidth; } uint32_t NewSwapChainBase::GetHeight() const { return mHeight; } wgpu::TextureFormat NewSwapChainBase::GetFormat() const { return mFormat; } wgpu::TextureUsage NewSwapChainBase::GetUsage() const { return mUsage; } wgpu::PresentMode NewSwapChainBase::GetPresentMode() const { return mPresentMode; } Surface* NewSwapChainBase::GetSurface() const { 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)); DAWN_INVALID_IF(!mAttached, "Cannot call Present called on detached %s.", this); DAWN_INVALID_IF( mCurrentTextureView == nullptr, "GetCurrentTextureView was not called on %s this frame prior to calling Present.", this); return {}; } MaybeError NewSwapChainBase::ValidateGetCurrentTextureView() const { DAWN_TRY(GetDevice()->ValidateIsAlive()); DAWN_TRY(GetDevice()->ValidateObject(this)); DAWN_INVALID_IF(!mAttached, "Cannot call GetCurrentTextureView on detached %s.", this); return {}; } } // namespace dawn::native