blob: 9002cc4c2db86bcc5b67e70476b890fb96fdc4a4 [file] [log] [blame] [edit]
// 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