| // 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 <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<OldSwapChain> 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 = reinterpret_cast<WGPUDevice>(GetDevice()); |
| im.Init(im.userData, &wsiContext); |
| |
| ASSERT(im.textureUsage != WGPUTextureUsage_None); |
| mTextureUsage = static_cast<wgpu::TextureUsage>(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<ID3D12Resource> d3d12Texture = static_cast<ID3D12Resource*>(next.texture.ptr); |
| Ref<Texture> 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<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; |
| switch (GetSurface()->GetType()) { |
| case Surface::Type::WindowsHWND: { |
| DAWN_TRY(CheckHRESULT( |
| factory2->CreateSwapChainForHwnd(device->GetCommandQueue().Get(), |
| static_cast<HWND>(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; |
| } |
| 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(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 |