// 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/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 #include 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::TextureBinding) { dxgiUsage |= DXGI_USAGE_SHADER_INPUT; } if (usage & wgpu::TextureUsage::StorageBinding) { dxgiUsage |= DXGI_USAGE_UNORDERED_ACCESS; } if (usage & wgpu::TextureUsage::RenderAttachment) { dxgiUsage |= DXGI_USAGE_RENDER_TARGET_OUTPUT; } return dxgiUsage; } } // namespace // OldSwapChain // static Ref OldSwapChain::Create(Device* device, const SwapChainDescriptor* descriptor) { return AcquireRef(new OldSwapChain(device, descriptor)); } OldSwapChain::OldSwapChain(Device* device, const SwapChainDescriptor* descriptor) : OldSwapChainBase(device, descriptor) { const auto& im = GetImplementation(); DawnWSIContextD3D12 wsiContext = {}; wsiContext.device = ToAPI(GetDevice()); im.Init(im.userData, &wsiContext); ASSERT(im.textureUsage != WGPUTextureUsage_None); mTextureUsage = static_cast(im.textureUsage); } OldSwapChain::~OldSwapChain() = default; TextureBase* OldSwapChain::GetNextTextureImpl(const TextureDescriptor* descriptor) { DeviceBase* device = GetDevice(); const auto& im = GetImplementation(); DawnSwapChainNextTexture next = {}; DawnSwapChainError error = im.GetNextTexture(im.userData, &next); if (error) { device->HandleError(InternalErrorType::Internal, error); return nullptr; } ComPtr d3d12Texture = static_cast(next.texture.ptr); Ref dawnTexture; if (device->ConsumedError( Texture::Create(ToBackend(GetDevice()), descriptor, std::move(d3d12Texture)), &dawnTexture)) { return nullptr; } return dawnTexture.Detach(); } MaybeError OldSwapChain::OnBeforePresent(TextureViewBase* view) { Device* device = ToBackend(GetDevice()); CommandRecordingContext* commandContext; DAWN_TRY_ASSIGN(commandContext, device->GetPendingCommandContext()); // Perform the necessary transition for the texture to be presented. ToBackend(view->GetTexture()) ->TrackUsageAndTransitionNow(commandContext, mTextureUsage, view->GetSubresourceRange()); DAWN_TRY(device->ExecutePendingCommandContext()); return {}; } // SwapChain // static ResultOrError> SwapChain::Create(Device* device, Surface* surface, NewSwapChainBase* previousSwapChain, const SwapChainDescriptor* descriptor) { Ref swapchain = AcquireRef(new SwapChain(device, surface, descriptor)); DAWN_TRY(swapchain->Initialize(previousSwapChain)); return swapchain; } SwapChain::~SwapChain() = default; void SwapChain::DestroyImpl() { SwapChainBase::DestroyImpl(); 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(crbug.com/dawn/269): 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. DAWN_INVALID_IF(previousSwapChain->GetBackendType() != wgpu::BackendType::D3D12, "D3D12 SwapChain cannot switch backend types from %s to %s.", previousSwapChain->GetBackendType(), wgpu::BackendType::D3D12); // TODO(crbug.com/dawn/269): use ToBackend once OldSwapChainBase is removed. SwapChain* previousD3D12SwapChain = static_cast(previousSwapChain); // TODO(crbug.com/dawn/269): 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. DAWN_INVALID_IF(GetDevice() != previousSwapChain->GetDevice(), "D3D12 SwapChain cannot switch between D3D 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 factory2 = nullptr; DAWN_TRY(CheckHRESULT(device->GetFactory()->QueryInterface(IID_PPV_ARGS(&factory2)), "Getting IDXGIFactory2")); ComPtr swapChain1; switch (GetSurface()->GetType()) { case Surface::Type::WindowsHWND: { DAWN_TRY(CheckHRESULT( factory2->CreateSwapChainForHwnd(device->GetCommandQueue().Get(), static_cast(GetSurface()->GetHWND()), &swapChainDesc, nullptr, nullptr, &swapChain1), "Creating the IDXGISwapChain1")); break; } case Surface::Type::WindowsCoreWindow: { DAWN_TRY(CheckHRESULT( factory2->CreateSwapChainForCoreWindow(device->GetCommandQueue().Get(), GetSurface()->GetCoreWindow(), &swapChainDesc, nullptr, &swapChain1), "Creating the IDXGISwapChain1")); break; } case Surface::Type::WindowsSwapChainPanel: { DAWN_TRY(CheckHRESULT( factory2->CreateSwapChainForComposition(device->GetCommandQueue().Get(), &swapChainDesc, nullptr, &swapChain1), "Creating the IDXGISwapChain1")); ComPtr swapChainPanelNative; DAWN_TRY(CheckHRESULT(GetSurface()->GetSwapChainPanel()->QueryInterface( IID_PPV_ARGS(&swapChainPanelNative)), "Getting ISwapChainPanelNative")); DAWN_TRY(CheckHRESULT(swapChainPanelNative->SetSwapChain(swapChain1.Get()), "Setting SwapChain")); break; } default: UNREACHABLE(); } 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(crbug.com/dawn/269): 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 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(crbug.com/dawn/269): 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