DeviceResources.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. #include "pch.h"
  2. #include "DeviceResources.h"
  3. #include "DirectXHelper.h"
  4. using namespace DirectX;
  5. using namespace Microsoft::WRL;
  6. using namespace Windows::Foundation;
  7. using namespace Windows::Graphics::Display;
  8. using namespace Windows::UI::Core;
  9. using namespace Windows::UI::Xaml::Controls;
  10. using namespace Platform;
  11. // Constants used to calculate screen rotations.
  12. namespace ScreenRotation
  13. {
  14. // 0-degree Z-rotation
  15. static const XMFLOAT4X4 Rotation0(
  16. 1.0f, 0.0f, 0.0f, 0.0f,
  17. 0.0f, 1.0f, 0.0f, 0.0f,
  18. 0.0f, 0.0f, 1.0f, 0.0f,
  19. 0.0f, 0.0f, 0.0f, 1.0f
  20. );
  21. // 90-degree Z-rotation
  22. static const XMFLOAT4X4 Rotation90(
  23. 0.0f, 1.0f, 0.0f, 0.0f,
  24. -1.0f, 0.0f, 0.0f, 0.0f,
  25. 0.0f, 0.0f, 1.0f, 0.0f,
  26. 0.0f, 0.0f, 0.0f, 1.0f
  27. );
  28. // 180-degree Z-rotation
  29. static const XMFLOAT4X4 Rotation180(
  30. -1.0f, 0.0f, 0.0f, 0.0f,
  31. 0.0f, -1.0f, 0.0f, 0.0f,
  32. 0.0f, 0.0f, 1.0f, 0.0f,
  33. 0.0f, 0.0f, 0.0f, 1.0f
  34. );
  35. // 270-degree Z-rotation
  36. static const XMFLOAT4X4 Rotation270(
  37. 0.0f, -1.0f, 0.0f, 0.0f,
  38. 1.0f, 0.0f, 0.0f, 0.0f,
  39. 0.0f, 0.0f, 1.0f, 0.0f,
  40. 0.0f, 0.0f, 0.0f, 1.0f
  41. );
  42. };
  43. // Constructor for DeviceResources.
  44. DX::DeviceResources::DeviceResources() :
  45. m_currentFrame(0),
  46. m_screenViewport(),
  47. m_rtvDescriptorSize(0),
  48. m_fenceEvent(0),
  49. m_d3dRenderTargetSize(),
  50. m_outputSize(),
  51. m_logicalSize(),
  52. m_nativeOrientation(DisplayOrientations::None),
  53. m_currentOrientation(DisplayOrientations::None),
  54. m_dpi(-1.0f),
  55. m_deviceRemoved(false)
  56. {
  57. ZeroMemory(m_fenceValues, sizeof(m_fenceValues));
  58. CreateDeviceIndependentResources();
  59. CreateDeviceResources();
  60. }
  61. // Configures resources that don't depend on the Direct3D device.
  62. void DX::DeviceResources::CreateDeviceIndependentResources()
  63. {
  64. }
  65. // Configures the Direct3D device, and stores handles to it and the device context.
  66. void DX::DeviceResources::CreateDeviceResources()
  67. {
  68. #if defined(_DEBUG)
  69. // If the project is in a debug build, enable debugging via SDK Layers.
  70. {
  71. ComPtr<ID3D12Debug> debugController;
  72. if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
  73. {
  74. debugController->EnableDebugLayer();
  75. }
  76. }
  77. #endif
  78. DX::ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&m_dxgiFactory)));
  79. // Create the Direct3D 12 API device object
  80. HRESULT hr = D3D12CreateDevice(
  81. nullptr, // Specify nullptr to use the default adapter.
  82. D3D_FEATURE_LEVEL_11_0, // Minimum feature level this app can support.
  83. IID_PPV_ARGS(&m_d3dDevice) // Returns the Direct3D device created.
  84. );
  85. if (FAILED(hr))
  86. {
  87. // If the initialization fails, fall back to the WARP device.
  88. // For more information on WARP, see:
  89. // http://go.microsoft.com/fwlink/?LinkId=286690
  90. ComPtr<IDXGIAdapter> warpAdapter;
  91. m_dxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&warpAdapter));
  92. DX::ThrowIfFailed(
  93. D3D12CreateDevice(
  94. warpAdapter.Get(),
  95. D3D_FEATURE_LEVEL_11_0,
  96. IID_PPV_ARGS(&m_d3dDevice)
  97. )
  98. );
  99. }
  100. // Create the command queue.
  101. D3D12_COMMAND_QUEUE_DESC queueDesc = {};
  102. queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
  103. queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
  104. DX::ThrowIfFailed(m_d3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue)));
  105. for (UINT n = 0; n < c_frameCount; n++)
  106. {
  107. DX::ThrowIfFailed(
  108. m_d3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocators[n]))
  109. );
  110. }
  111. // Create synchronization objects.
  112. DX::ThrowIfFailed(m_d3dDevice->CreateFence(m_fenceValues[m_currentFrame], D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)));
  113. m_fenceValues[m_currentFrame]++;
  114. m_fenceEvent = CreateEventEx(nullptr, FALSE, FALSE, EVENT_ALL_ACCESS);
  115. }
  116. // These resources need to be recreated every time the window size is changed.
  117. void DX::DeviceResources::CreateWindowSizeDependentResources()
  118. {
  119. // Wait until all previous GPU work is complete.
  120. WaitForGpu();
  121. // Clear the previous window size specific content.
  122. for (UINT n = 0; n < c_frameCount; n++)
  123. {
  124. m_renderTargets[n] = nullptr;
  125. }
  126. m_rtvHeap = nullptr;
  127. // Calculate the necessary render target size in pixels.
  128. m_outputSize.Width = DX::ConvertDipsToPixels(m_logicalSize.Width, m_dpi);
  129. m_outputSize.Height = DX::ConvertDipsToPixels(m_logicalSize.Height, m_dpi);
  130. // Prevent zero size DirectX content from being created.
  131. m_outputSize.Width = max(m_outputSize.Width, 1);
  132. m_outputSize.Height = max(m_outputSize.Height, 1);
  133. // The width and height of the swap chain must be based on the window's
  134. // natively-oriented width and height. If the window is not in the native
  135. // orientation, the dimensions must be reversed.
  136. DXGI_MODE_ROTATION displayRotation = ComputeDisplayRotation();
  137. bool swapDimensions = displayRotation == DXGI_MODE_ROTATION_ROTATE90 || displayRotation == DXGI_MODE_ROTATION_ROTATE270;
  138. m_d3dRenderTargetSize.Width = swapDimensions ? m_outputSize.Height : m_outputSize.Width;
  139. m_d3dRenderTargetSize.Height = swapDimensions ? m_outputSize.Width : m_outputSize.Height;
  140. if (m_swapChain != nullptr)
  141. {
  142. // If the swap chain already exists, resize it.
  143. HRESULT hr = m_swapChain->ResizeBuffers(
  144. c_frameCount,
  145. lround(m_d3dRenderTargetSize.Width),
  146. lround(m_d3dRenderTargetSize.Height),
  147. DXGI_FORMAT_B8G8R8A8_UNORM,
  148. 0
  149. );
  150. if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
  151. {
  152. // If the device was removed for any reason, a new device and swap chain will need to be created.
  153. m_deviceRemoved = true;
  154. // Do not continue execution of this method. DeviceResources will be destroyed and re-created.
  155. return;
  156. }
  157. else
  158. {
  159. DX::ThrowIfFailed(hr);
  160. }
  161. }
  162. else
  163. {
  164. // Otherwise, create a new one using the same adapter as the existing Direct3D device.
  165. DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
  166. swapChainDesc.Width = lround(m_d3dRenderTargetSize.Width); // Match the size of the window.
  167. swapChainDesc.Height = lround(m_d3dRenderTargetSize.Height);
  168. swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format.
  169. swapChainDesc.Stereo = false;
  170. swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
  171. swapChainDesc.SampleDesc.Quality = 0;
  172. swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
  173. swapChainDesc.BufferCount = c_frameCount; // Use triple-buffering to minimize latency.
  174. swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; // All Windows Universal apps must use _FLIP_ SwapEffects
  175. swapChainDesc.Flags = 0;
  176. swapChainDesc.Scaling = DXGI_SCALING_NONE;
  177. swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
  178. ComPtr<IDXGISwapChain1> swapChain;
  179. DX::ThrowIfFailed(
  180. m_dxgiFactory->CreateSwapChainForCoreWindow(
  181. m_commandQueue.Get(),
  182. reinterpret_cast<IUnknown*>(m_window.Get()),
  183. &swapChainDesc,
  184. nullptr,
  185. &swapChain
  186. )
  187. );
  188. DX::ThrowIfFailed(swapChain.As(&m_swapChain));
  189. }
  190. // Set the proper orientation for the swap chain, and generate
  191. // 3D matrix transformations for rendering to the rotated swap chain.
  192. // The 3D matrix is specified explicitly to avoid rounding errors.
  193. switch (displayRotation)
  194. {
  195. case DXGI_MODE_ROTATION_IDENTITY:
  196. m_orientationTransform3D = ScreenRotation::Rotation0;
  197. break;
  198. case DXGI_MODE_ROTATION_ROTATE90:
  199. m_orientationTransform3D = ScreenRotation::Rotation270;
  200. break;
  201. case DXGI_MODE_ROTATION_ROTATE180:
  202. m_orientationTransform3D = ScreenRotation::Rotation180;
  203. break;
  204. case DXGI_MODE_ROTATION_ROTATE270:
  205. m_orientationTransform3D = ScreenRotation::Rotation90;
  206. break;
  207. default:
  208. throw ref new FailureException();
  209. }
  210. DX::ThrowIfFailed(
  211. m_swapChain->SetRotation(displayRotation)
  212. );
  213. // Create a render target view of the swap chain back buffer.
  214. {
  215. D3D12_DESCRIPTOR_HEAP_DESC desc = {};
  216. desc.NumDescriptors = c_frameCount;
  217. desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
  218. desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
  219. DX::ThrowIfFailed(m_d3dDevice->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&m_rtvHeap)));
  220. m_rtvHeap->SetName(L"Render Target View Descriptor Heap");
  221. // All pending GPU work was already finished. Update the tracked fence values
  222. // to the last value signaled.
  223. for (UINT n = 0; n < c_frameCount; n++)
  224. {
  225. m_fenceValues[n] = m_fenceValues[m_currentFrame];
  226. }
  227. m_currentFrame = 0;
  228. CD3DX12_CPU_DESCRIPTOR_HANDLE rtvDescriptor(m_rtvHeap->GetCPUDescriptorHandleForHeapStart());
  229. m_rtvDescriptorSize = m_d3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
  230. for (UINT n = 0; n < c_frameCount; n++)
  231. {
  232. DX::ThrowIfFailed(m_swapChain->GetBuffer(n, IID_PPV_ARGS(&m_renderTargets[n])));
  233. m_d3dDevice->CreateRenderTargetView(m_renderTargets[n].Get(), nullptr, rtvDescriptor);
  234. rtvDescriptor.Offset(m_rtvDescriptorSize);
  235. WCHAR name[25];
  236. swprintf_s(name, L"Render Target %d", n);
  237. m_renderTargets[n]->SetName(name);
  238. }
  239. }
  240. // Set the 3D rendering viewport to target the entire window.
  241. m_screenViewport = { 0.0f, 0.0f, m_d3dRenderTargetSize.Width, m_d3dRenderTargetSize.Height, 0.0f, 1.0f };
  242. }
  243. // This method is called when the CoreWindow is created (or re-created).
  244. void DX::DeviceResources::SetWindow(CoreWindow^ window)
  245. {
  246. DisplayInformation^ currentDisplayInformation = DisplayInformation::GetForCurrentView();
  247. m_window = window;
  248. m_logicalSize = Windows::Foundation::Size(window->Bounds.Width, window->Bounds.Height);
  249. m_nativeOrientation = currentDisplayInformation->NativeOrientation;
  250. m_currentOrientation = currentDisplayInformation->CurrentOrientation;
  251. m_dpi = currentDisplayInformation->LogicalDpi;
  252. CreateWindowSizeDependentResources();
  253. }
  254. // This method is called in the event handler for the SizeChanged event.
  255. void DX::DeviceResources::SetLogicalSize(Windows::Foundation::Size logicalSize)
  256. {
  257. if (m_logicalSize != logicalSize)
  258. {
  259. m_logicalSize = logicalSize;
  260. CreateWindowSizeDependentResources();
  261. }
  262. }
  263. // This method is called in the event handler for the DpiChanged event.
  264. void DX::DeviceResources::SetDpi(float dpi)
  265. {
  266. if (dpi != m_dpi)
  267. {
  268. m_dpi = dpi;
  269. // When the display DPI changes, the logical size of the window (measured in Dips) also changes and needs to be updated.
  270. m_logicalSize = Windows::Foundation::Size(m_window->Bounds.Width, m_window->Bounds.Height);
  271. CreateWindowSizeDependentResources();
  272. }
  273. }
  274. // This method is called in the event handler for the OrientationChanged event.
  275. void DX::DeviceResources::SetCurrentOrientation(DisplayOrientations currentOrientation)
  276. {
  277. if (m_currentOrientation != currentOrientation)
  278. {
  279. m_currentOrientation = currentOrientation;
  280. CreateWindowSizeDependentResources();
  281. }
  282. }
  283. // This method is called in the event handler for the DisplayContentsInvalidated event.
  284. void DX::DeviceResources::ValidateDevice()
  285. {
  286. // The D3D Device is no longer valid if the default adapter changed since the device
  287. // was created or if the device has been removed.
  288. // First, get the LUID for the adapter from when the device was created.
  289. LUID previousAdapterLuid = m_d3dDevice->GetAdapterLuid();
  290. // Next, get the information for the current default adapter.
  291. ComPtr<IDXGIFactory2> currentFactory;
  292. DX::ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&currentFactory)));
  293. ComPtr<IDXGIAdapter1> currentDefaultAdapter;
  294. DX::ThrowIfFailed(currentFactory->EnumAdapters1(0, &currentDefaultAdapter));
  295. DXGI_ADAPTER_DESC currentDesc;
  296. DX::ThrowIfFailed(currentDefaultAdapter->GetDesc(&currentDesc));
  297. // If the adapter LUIDs don't match, or if the device reports that it has been removed,
  298. // a new D3D device must be created.
  299. if (previousAdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart ||
  300. previousAdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart ||
  301. FAILED(m_d3dDevice->GetDeviceRemovedReason()))
  302. {
  303. m_deviceRemoved = true;
  304. }
  305. }
  306. // Present the contents of the swap chain to the screen.
  307. void DX::DeviceResources::Present()
  308. {
  309. // The first argument instructs DXGI to block until VSync, putting the application
  310. // to sleep until the next VSync. This ensures we don't waste any cycles rendering
  311. // frames that will never be displayed to the screen.
  312. HRESULT hr = m_swapChain->Present(1, 0);
  313. // If the device was removed either by a disconnection or a driver upgrade, we
  314. // must recreate all device resources.
  315. if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
  316. {
  317. m_deviceRemoved = true;
  318. }
  319. else
  320. {
  321. DX::ThrowIfFailed(hr);
  322. MoveToNextFrame();
  323. }
  324. }
  325. // Wait for pending GPU work to complete.
  326. void DX::DeviceResources::WaitForGpu()
  327. {
  328. // Schedule a Signal command in the queue.
  329. DX::ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), m_fenceValues[m_currentFrame]));
  330. // Wait until the fence has been crossed.
  331. DX::ThrowIfFailed(m_fence->SetEventOnCompletion(m_fenceValues[m_currentFrame], m_fenceEvent));
  332. WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
  333. // Increment the fence value for the current frame.
  334. m_fenceValues[m_currentFrame]++;
  335. }
  336. // Prepare to render the next frame.
  337. void DX::DeviceResources::MoveToNextFrame()
  338. {
  339. // Schedule a Signal command in the queue.
  340. const UINT64 currentFenceValue = m_fenceValues[m_currentFrame];
  341. DX::ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), currentFenceValue));
  342. // Advance the frame index.
  343. m_currentFrame = (m_currentFrame + 1) % c_frameCount;
  344. // Check to see if the next frame is ready to start.
  345. if (m_fence->GetCompletedValue() < m_fenceValues[m_currentFrame])
  346. {
  347. DX::ThrowIfFailed(m_fence->SetEventOnCompletion(m_fenceValues[m_currentFrame], m_fenceEvent));
  348. WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);
  349. }
  350. // Set the fence value for the next frame.
  351. m_fenceValues[m_currentFrame] = currentFenceValue + 1;
  352. }
  353. // This method determines the rotation between the display device's native Orientation and the
  354. // current display orientation.
  355. DXGI_MODE_ROTATION DX::DeviceResources::ComputeDisplayRotation()
  356. {
  357. DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
  358. // Note: NativeOrientation can only be Landscape or Portrait even though
  359. // the DisplayOrientations enum has other values.
  360. switch (m_nativeOrientation)
  361. {
  362. case DisplayOrientations::Landscape:
  363. switch (m_currentOrientation)
  364. {
  365. case DisplayOrientations::Landscape:
  366. rotation = DXGI_MODE_ROTATION_IDENTITY;
  367. break;
  368. case DisplayOrientations::Portrait:
  369. rotation = DXGI_MODE_ROTATION_ROTATE270;
  370. break;
  371. case DisplayOrientations::LandscapeFlipped:
  372. rotation = DXGI_MODE_ROTATION_ROTATE180;
  373. break;
  374. case DisplayOrientations::PortraitFlipped:
  375. rotation = DXGI_MODE_ROTATION_ROTATE90;
  376. break;
  377. }
  378. break;
  379. case DisplayOrientations::Portrait:
  380. switch (m_currentOrientation)
  381. {
  382. case DisplayOrientations::Landscape:
  383. rotation = DXGI_MODE_ROTATION_ROTATE90;
  384. break;
  385. case DisplayOrientations::Portrait:
  386. rotation = DXGI_MODE_ROTATION_IDENTITY;
  387. break;
  388. case DisplayOrientations::LandscapeFlipped:
  389. rotation = DXGI_MODE_ROTATION_ROTATE270;
  390. break;
  391. case DisplayOrientations::PortraitFlipped:
  392. rotation = DXGI_MODE_ROTATION_ROTATE180;
  393. break;
  394. }
  395. break;
  396. }
  397. return rotation;
  398. }