1 // Copyright 2017 The Dawn Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "dawn_native/d3d12/SwapChainD3D12.h" 16 17 #include "dawn_native/Surface.h" 18 #include "dawn_native/d3d12/D3D12Error.h" 19 #include "dawn_native/d3d12/DeviceD3D12.h" 20 #include "dawn_native/d3d12/TextureD3D12.h" 21 22 #include <dawn/dawn_wsi.h> 23 24 #include <windows.ui.xaml.media.dxinterop.h> 25 26 namespace dawn_native { namespace d3d12 { 27 namespace { 28 PresentModeToBufferCount(wgpu::PresentMode mode)29 uint32_t PresentModeToBufferCount(wgpu::PresentMode mode) { 30 switch (mode) { 31 case wgpu::PresentMode::Immediate: 32 case wgpu::PresentMode::Fifo: 33 return 2; 34 case wgpu::PresentMode::Mailbox: 35 return 3; 36 } 37 } 38 PresentModeToSwapInterval(wgpu::PresentMode mode)39 uint32_t PresentModeToSwapInterval(wgpu::PresentMode mode) { 40 switch (mode) { 41 case wgpu::PresentMode::Immediate: 42 case wgpu::PresentMode::Mailbox: 43 return 0; 44 case wgpu::PresentMode::Fifo: 45 return 1; 46 } 47 } 48 PresentModeToSwapChainFlags(wgpu::PresentMode mode)49 UINT PresentModeToSwapChainFlags(wgpu::PresentMode mode) { 50 UINT flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; 51 52 if (mode == wgpu::PresentMode::Immediate) { 53 flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; 54 } 55 56 return flags; 57 } 58 ToDXGIUsage(wgpu::TextureUsage usage)59 DXGI_USAGE ToDXGIUsage(wgpu::TextureUsage usage) { 60 DXGI_USAGE dxgiUsage = DXGI_CPU_ACCESS_NONE; 61 if (usage & wgpu::TextureUsage::TextureBinding) { 62 dxgiUsage |= DXGI_USAGE_SHADER_INPUT; 63 } 64 if (usage & wgpu::TextureUsage::StorageBinding) { 65 dxgiUsage |= DXGI_USAGE_UNORDERED_ACCESS; 66 } 67 if (usage & wgpu::TextureUsage::RenderAttachment) { 68 dxgiUsage |= DXGI_USAGE_RENDER_TARGET_OUTPUT; 69 } 70 return dxgiUsage; 71 } 72 73 } // namespace 74 75 // OldSwapChain 76 77 // static Create(Device * device,const SwapChainDescriptor * descriptor)78 Ref<OldSwapChain> OldSwapChain::Create(Device* device, const SwapChainDescriptor* descriptor) { 79 return AcquireRef(new OldSwapChain(device, descriptor)); 80 } 81 OldSwapChain(Device * device,const SwapChainDescriptor * descriptor)82 OldSwapChain::OldSwapChain(Device* device, const SwapChainDescriptor* descriptor) 83 : OldSwapChainBase(device, descriptor) { 84 const auto& im = GetImplementation(); 85 DawnWSIContextD3D12 wsiContext = {}; 86 wsiContext.device = ToAPI(GetDevice()); 87 im.Init(im.userData, &wsiContext); 88 89 ASSERT(im.textureUsage != WGPUTextureUsage_None); 90 mTextureUsage = static_cast<wgpu::TextureUsage>(im.textureUsage); 91 } 92 93 OldSwapChain::~OldSwapChain() = default; 94 GetNextTextureImpl(const TextureDescriptor * descriptor)95 TextureBase* OldSwapChain::GetNextTextureImpl(const TextureDescriptor* descriptor) { 96 DeviceBase* device = GetDevice(); 97 const auto& im = GetImplementation(); 98 DawnSwapChainNextTexture next = {}; 99 DawnSwapChainError error = im.GetNextTexture(im.userData, &next); 100 if (error) { 101 device->HandleError(InternalErrorType::Internal, error); 102 return nullptr; 103 } 104 105 ComPtr<ID3D12Resource> d3d12Texture = static_cast<ID3D12Resource*>(next.texture.ptr); 106 Ref<Texture> dawnTexture; 107 if (device->ConsumedError( 108 Texture::Create(ToBackend(GetDevice()), descriptor, std::move(d3d12Texture)), 109 &dawnTexture)) { 110 return nullptr; 111 } 112 113 return dawnTexture.Detach(); 114 } 115 OnBeforePresent(TextureViewBase * view)116 MaybeError OldSwapChain::OnBeforePresent(TextureViewBase* view) { 117 Device* device = ToBackend(GetDevice()); 118 119 CommandRecordingContext* commandContext; 120 DAWN_TRY_ASSIGN(commandContext, device->GetPendingCommandContext()); 121 122 // Perform the necessary transition for the texture to be presented. 123 ToBackend(view->GetTexture()) 124 ->TrackUsageAndTransitionNow(commandContext, mTextureUsage, 125 view->GetSubresourceRange()); 126 127 DAWN_TRY(device->ExecutePendingCommandContext()); 128 129 return {}; 130 } 131 132 // SwapChain 133 134 // static Create(Device * device,Surface * surface,NewSwapChainBase * previousSwapChain,const SwapChainDescriptor * descriptor)135 ResultOrError<Ref<SwapChain>> SwapChain::Create(Device* device, 136 Surface* surface, 137 NewSwapChainBase* previousSwapChain, 138 const SwapChainDescriptor* descriptor) { 139 Ref<SwapChain> swapchain = AcquireRef(new SwapChain(device, surface, descriptor)); 140 DAWN_TRY(swapchain->Initialize(previousSwapChain)); 141 return swapchain; 142 } 143 144 SwapChain::~SwapChain() = default; 145 DestroyImpl()146 void SwapChain::DestroyImpl() { 147 SwapChainBase::DestroyImpl(); 148 DetachFromSurface(); 149 } 150 151 // Initializes the swapchain on the surface. Note that `previousSwapChain` may or may not be 152 // nullptr. If it is not nullptr it means that it is the swapchain previously in use on the 153 // surface and that we have a chance to reuse it's underlying IDXGISwapChain and "buffers". Initialize(NewSwapChainBase * previousSwapChain)154 MaybeError SwapChain::Initialize(NewSwapChainBase* previousSwapChain) { 155 ASSERT(GetSurface()->GetType() == Surface::Type::WindowsHWND); 156 157 // Precompute the configuration parameters we want for the DXGI swapchain. 158 mConfig.bufferCount = PresentModeToBufferCount(GetPresentMode()); 159 mConfig.format = D3D12TextureFormat(GetFormat()); 160 mConfig.swapChainFlags = PresentModeToSwapChainFlags(GetPresentMode()); 161 mConfig.usage = ToDXGIUsage(GetUsage()); 162 163 // There is no previous swapchain so we can create one directly and don't have anything else 164 // to do. 165 if (previousSwapChain == nullptr) { 166 return InitializeSwapChainFromScratch(); 167 } 168 169 // TODO(crbug.com/dawn/269): figure out what should happen when surfaces are used by 170 // multiple backends one after the other. It probably needs to block until the backend 171 // and GPU are completely finished with the previous swapchain. 172 DAWN_INVALID_IF(previousSwapChain->GetBackendType() != wgpu::BackendType::D3D12, 173 "D3D12 SwapChain cannot switch backend types from %s to %s.", 174 previousSwapChain->GetBackendType(), wgpu::BackendType::D3D12); 175 176 // TODO(crbug.com/dawn/269): use ToBackend once OldSwapChainBase is removed. 177 SwapChain* previousD3D12SwapChain = static_cast<SwapChain*>(previousSwapChain); 178 179 // TODO(crbug.com/dawn/269): Figure out switching an HWND between devices, it might 180 // require just losing the reference to the swapchain, but might also need to wait for 181 // all previous operations to complete. 182 DAWN_INVALID_IF(GetDevice() != previousSwapChain->GetDevice(), 183 "D3D12 SwapChain cannot switch between D3D Devices"); 184 185 // The previous swapchain is on the same device so we want to reuse it but it is still not 186 // always possible. Because DXGI requires that a new swapchain be created if the 187 // DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING flag is changed. 188 bool canReuseSwapChain = 189 ((mConfig.swapChainFlags ^ previousD3D12SwapChain->mConfig.swapChainFlags) & 190 DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) == 0; 191 192 // We can't reuse the previous swapchain, so we destroy it and wait for all of its reference 193 // to be forgotten (otherwise DXGI complains that there are outstanding references). 194 if (!canReuseSwapChain) { 195 DAWN_TRY(previousD3D12SwapChain->DetachAndWaitForDeallocation()); 196 return InitializeSwapChainFromScratch(); 197 } 198 199 // After all this we know we can reuse the swapchain, see if it is possible to also reuse 200 // the buffers. 201 mDXGISwapChain = std::move(previousD3D12SwapChain->mDXGISwapChain); 202 203 bool canReuseBuffers = GetWidth() == previousSwapChain->GetWidth() && 204 GetHeight() == previousSwapChain->GetHeight() && 205 GetFormat() == previousSwapChain->GetFormat() && 206 GetPresentMode() == previousSwapChain->GetPresentMode(); 207 if (canReuseBuffers) { 208 mBuffers = std::move(previousD3D12SwapChain->mBuffers); 209 mBufferLastUsedSerials = std::move(previousD3D12SwapChain->mBufferLastUsedSerials); 210 mCurrentBuffer = previousD3D12SwapChain->mCurrentBuffer; 211 return {}; 212 } 213 214 // We can't reuse the buffers so we need to resize, IDXGSwapChain->ResizeBuffers requires 215 // that all references to buffers are lost before it is called. Contrary to D3D11, the 216 // application is responsible for keeping references to the buffers until the GPU is done 217 // using them so we have no choice but to synchrounously wait for all operations to complete 218 // on the previous swapchain and then lose references to its buffers. 219 DAWN_TRY(previousD3D12SwapChain->DetachAndWaitForDeallocation()); 220 DAWN_TRY( 221 CheckHRESULT(mDXGISwapChain->ResizeBuffers(mConfig.bufferCount, GetWidth(), GetHeight(), 222 mConfig.format, mConfig.swapChainFlags), 223 "IDXGISwapChain::ResizeBuffer")); 224 return CollectSwapChainBuffers(); 225 } 226 InitializeSwapChainFromScratch()227 MaybeError SwapChain::InitializeSwapChainFromScratch() { 228 ASSERT(mDXGISwapChain == nullptr); 229 230 Device* device = ToBackend(GetDevice()); 231 232 DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; 233 swapChainDesc.Width = GetWidth(); 234 swapChainDesc.Height = GetHeight(); 235 swapChainDesc.Format = mConfig.format; 236 swapChainDesc.Stereo = false; 237 swapChainDesc.SampleDesc.Count = 1; 238 swapChainDesc.SampleDesc.Quality = 0; 239 swapChainDesc.BufferUsage = mConfig.usage; 240 swapChainDesc.BufferCount = mConfig.bufferCount; 241 swapChainDesc.Scaling = DXGI_SCALING_STRETCH; 242 swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; 243 swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; 244 swapChainDesc.Flags = mConfig.swapChainFlags; 245 246 ComPtr<IDXGIFactory2> factory2 = nullptr; 247 DAWN_TRY(CheckHRESULT(device->GetFactory()->QueryInterface(IID_PPV_ARGS(&factory2)), 248 "Getting IDXGIFactory2")); 249 250 ComPtr<IDXGISwapChain1> swapChain1; 251 switch (GetSurface()->GetType()) { 252 case Surface::Type::WindowsHWND: { 253 DAWN_TRY(CheckHRESULT( 254 factory2->CreateSwapChainForHwnd(device->GetCommandQueue().Get(), 255 static_cast<HWND>(GetSurface()->GetHWND()), 256 &swapChainDesc, nullptr, nullptr, &swapChain1), 257 "Creating the IDXGISwapChain1")); 258 break; 259 } 260 case Surface::Type::WindowsCoreWindow: { 261 DAWN_TRY(CheckHRESULT( 262 factory2->CreateSwapChainForCoreWindow(device->GetCommandQueue().Get(), 263 GetSurface()->GetCoreWindow(), 264 &swapChainDesc, nullptr, &swapChain1), 265 "Creating the IDXGISwapChain1")); 266 break; 267 } 268 case Surface::Type::WindowsSwapChainPanel: { 269 DAWN_TRY(CheckHRESULT( 270 factory2->CreateSwapChainForComposition(device->GetCommandQueue().Get(), 271 &swapChainDesc, nullptr, &swapChain1), 272 "Creating the IDXGISwapChain1")); 273 ComPtr<ISwapChainPanelNative> swapChainPanelNative; 274 DAWN_TRY(CheckHRESULT(GetSurface()->GetSwapChainPanel()->QueryInterface( 275 IID_PPV_ARGS(&swapChainPanelNative)), 276 "Getting ISwapChainPanelNative")); 277 DAWN_TRY(CheckHRESULT(swapChainPanelNative->SetSwapChain(swapChain1.Get()), 278 "Setting SwapChain")); 279 break; 280 } 281 default: 282 UNREACHABLE(); 283 } 284 285 DAWN_TRY(CheckHRESULT(swapChain1.As(&mDXGISwapChain), "Gettting IDXGISwapChain1")); 286 287 return CollectSwapChainBuffers(); 288 } 289 CollectSwapChainBuffers()290 MaybeError SwapChain::CollectSwapChainBuffers() { 291 ASSERT(mDXGISwapChain != nullptr); 292 ASSERT(mBuffers.empty()); 293 294 mBuffers.resize(mConfig.bufferCount); 295 for (uint32_t i = 0; i < mConfig.bufferCount; i++) { 296 DAWN_TRY(CheckHRESULT(mDXGISwapChain->GetBuffer(i, IID_PPV_ARGS(&mBuffers[i])), 297 "Getting IDXGISwapChain buffer")); 298 } 299 300 // Pretend all the buffers were last used at the beginning of time. 301 mBufferLastUsedSerials.resize(mConfig.bufferCount, ExecutionSerial(0)); 302 return {}; 303 } 304 PresentImpl()305 MaybeError SwapChain::PresentImpl() { 306 Device* device = ToBackend(GetDevice()); 307 308 // Transition the texture to the present state as required by IDXGISwapChain1::Present() 309 // TODO(crbug.com/dawn/269): Remove the need for this by eagerly transitioning the 310 // presentable texture to present at the end of submits that use them. 311 CommandRecordingContext* commandContext; 312 DAWN_TRY_ASSIGN(commandContext, device->GetPendingCommandContext()); 313 mApiTexture->TrackUsageAndTransitionNow(commandContext, kPresentTextureUsage, 314 mApiTexture->GetAllSubresources()); 315 DAWN_TRY(device->ExecutePendingCommandContext()); 316 317 // Do the actual present. DXGI_STATUS_OCCLUDED is a valid return value that's just a 318 // message to the application that it could stop rendering. 319 HRESULT presentResult = 320 mDXGISwapChain->Present(PresentModeToSwapInterval(GetPresentMode()), 0); 321 if (presentResult != DXGI_STATUS_OCCLUDED) { 322 DAWN_TRY(CheckHRESULT(presentResult, "IDXGISwapChain::Present")); 323 } 324 325 // Record that "new" is the last time the buffer has been used. 326 DAWN_TRY(device->NextSerial()); 327 mBufferLastUsedSerials[mCurrentBuffer] = device->GetPendingCommandSerial(); 328 329 mApiTexture->APIDestroy(); 330 mApiTexture = nullptr; 331 332 return {}; 333 } 334 GetCurrentTextureViewImpl()335 ResultOrError<TextureViewBase*> SwapChain::GetCurrentTextureViewImpl() { 336 Device* device = ToBackend(GetDevice()); 337 338 // Synchronously wait until previous operations on the next swapchain buffer are finished. 339 // This is the logic that performs frame pacing. 340 // TODO(crbug.com/dawn/269): Consider whether this should be lifted for Mailbox so that 341 // there is not frame pacing. 342 mCurrentBuffer = mDXGISwapChain->GetCurrentBackBufferIndex(); 343 DAWN_TRY(device->WaitForSerial(mBufferLastUsedSerials[mCurrentBuffer])); 344 345 // Create the API side objects for this use of the swapchain's buffer. 346 TextureDescriptor descriptor = GetSwapChainBaseTextureDescriptor(this); 347 DAWN_TRY_ASSIGN(mApiTexture, Texture::Create(ToBackend(GetDevice()), &descriptor, 348 mBuffers[mCurrentBuffer])); 349 350 // TODO(dawn:723): change to not use AcquireRef for reentrant object creation. 351 return mApiTexture->APICreateView(); 352 } 353 DetachAndWaitForDeallocation()354 MaybeError SwapChain::DetachAndWaitForDeallocation() { 355 DetachFromSurface(); 356 357 // DetachFromSurface calls Texture->Destroy that enqueues the D3D12 resource in a 358 // SerialQueue with the current "pending serial" so that we don't destroy the texture 359 // before it is finished being used. Flush the commands and wait for that serial to be 360 // passed, then Tick the device to make sure the reference to the D3D12 texture is removed. 361 Device* device = ToBackend(GetDevice()); 362 DAWN_TRY(device->NextSerial()); 363 DAWN_TRY(device->WaitForSerial(device->GetLastSubmittedCommandSerial())); 364 return device->TickImpl(); 365 } 366 DetachFromSurfaceImpl()367 void SwapChain::DetachFromSurfaceImpl() { 368 if (mApiTexture != nullptr) { 369 mApiTexture->APIDestroy(); 370 mApiTexture = nullptr; 371 } 372 373 mDXGISwapChain = nullptr; 374 mBuffers.clear(); 375 } 376 377 }} // namespace dawn_native::d3d12 378