1 // clang-format off
2 #include "pch.h"
3 
4 #include "Direct3DBase.h"
5 // clang-format on
6 
7 using namespace DirectX;
8 using namespace Microsoft::WRL;
9 using namespace Windows::UI::Core;
10 using namespace Windows::Foundation;
11 using namespace Windows::Graphics::Display;
12 
13 // Constructor.
Direct3DBase()14 Direct3DBase::Direct3DBase()
15 {
16 }
17 
18 // Initialize the Direct3D resources required to run.
19 void Direct3DBase::Initialize(CoreWindow ^ window)
20 {
21   m_window = window;
22 
23   CreateDeviceResources();
24   CreateWindowSizeDependentResources();
25 }
26 
27 // Recreate all device resources and set them back to the current state.
HandleDeviceLost()28 void Direct3DBase::HandleDeviceLost()
29 {
30   // Reset these member variables to ensure that UpdateForWindowSizeChange
31   // recreates all resources.
32   m_windowBounds.Width = 0;
33   m_windowBounds.Height = 0;
34   m_swapChain = nullptr;
35 
36   CreateDeviceResources();
37   UpdateForWindowSizeChange();
38 }
39 
40 // These are the resources that depend on the device.
CreateDeviceResources()41 void Direct3DBase::CreateDeviceResources()
42 {
43   // This flag adds support for surfaces with a different color channel
44   // ordering
45   // than the API default. It is required for compatibility with Direct2D.
46   UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
47 
48 #if defined(_DEBUG)
49   // If the project is in a debug build, enable debugging via SDK Layers with
50   // this flag.
51   creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
52 #endif
53 
54   // This array defines the set of DirectX hardware feature levels this app
55   // will support.
56   // Note the ordering should be preserved.
57   // Don't forget to declare your application's minimum required feature level
58   // in its
59   // description.  All applications are assumed to support 9.1 unless otherwise
60   // stated.
61   D3D_FEATURE_LEVEL featureLevels[] = {
62     D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
63     D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3,  D3D_FEATURE_LEVEL_9_2,
64     D3D_FEATURE_LEVEL_9_1
65   };
66 
67   // Create the Direct3D 11 API device object and a corresponding context.
68   ComPtr<ID3D11Device> device;
69   ComPtr<ID3D11DeviceContext> context;
70   DX::ThrowIfFailed(D3D11CreateDevice(
71     nullptr, // Specify nullptr to use the default adapter.
72     D3D_DRIVER_TYPE_HARDWARE, nullptr,
73     creationFlags, // Set set debug and Direct2D compatibility flags.
74     featureLevels, // List of feature levels this app can support.
75     ARRAYSIZE(featureLevels),
76     D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows
77                        // Store apps.
78     &device,           // Returns the Direct3D device created.
79     &m_featureLevel,   // Returns feature level of device created.
80     &context           // Returns the device immediate context.
81     ));
82 
83   // Get the Direct3D 11.1 API device and context interfaces.
84   DX::ThrowIfFailed(device.As(&m_d3dDevice));
85 
86   DX::ThrowIfFailed(context.As(&m_d3dContext));
87 }
88 
89 // Allocate all memory resources that change on a window SizeChanged event.
CreateWindowSizeDependentResources()90 void Direct3DBase::CreateWindowSizeDependentResources()
91 {
92   // Store the window bounds so the next time we get a SizeChanged event we can
93   // avoid rebuilding everything if the size is identical.
94   m_windowBounds = m_window->Bounds;
95 
96   // Calculate the necessary swap chain and render target size in pixels.
97   float windowWidth = ConvertDipsToPixels(m_windowBounds.Width);
98   float windowHeight = ConvertDipsToPixels(m_windowBounds.Height);
99 
100 // The width and height of the swap chain must be based on the window's
101 // landscape-oriented width and height. If the window is in a portrait
102 // orientation, the dimensions must be reversed.
103 #if WINVER > 0x0602
104   m_orientation = DisplayInformation::GetForCurrentView()->CurrentOrientation;
105 #else
106 #  if PHONE
107   // WP8 doesn't support rotations so always make it landscape
108   m_orientation = DisplayOrientations::Landscape;
109 #  else
110   m_orientation = DisplayProperties::CurrentOrientation;
111 #  endif
112 #endif
113   bool swapDimensions = m_orientation == DisplayOrientations::Portrait ||
114     m_orientation == DisplayOrientations::PortraitFlipped;
115   m_renderTargetSize.Width = swapDimensions ? windowHeight : windowWidth;
116   m_renderTargetSize.Height = swapDimensions ? windowWidth : windowHeight;
117 
118   if (m_swapChain != nullptr) {
119     // If the swap chain already exists, resize it.
120     DX::ThrowIfFailed(
121       m_swapChain->ResizeBuffers(2, // Double-buffered swap chain.
122                                  static_cast<UINT>(m_renderTargetSize.Width),
123                                  static_cast<UINT>(m_renderTargetSize.Height),
124                                  DXGI_FORMAT_B8G8R8A8_UNORM, 0));
125   } else {
126     // Otherwise, create a new one using the same adapter as the existing
127     // Direct3D device.
128     DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };
129     swapChainDesc.Width = static_cast<UINT>(
130       m_renderTargetSize.Width); // Match the size of the window.
131     swapChainDesc.Height = static_cast<UINT>(m_renderTargetSize.Height);
132     swapChainDesc.Format =
133       DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format.
134     swapChainDesc.Stereo = false;
135     swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
136     swapChainDesc.SampleDesc.Quality = 0;
137     swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
138 #if PHONE && WINVER <= 0x0602
139     swapChainDesc.BufferCount = 1; // Use double-buffering to minimize latency.
140     swapChainDesc.Scaling = DXGI_SCALING_STRETCH; // On phone, only stretch and
141                                                   // aspect-ratio stretch
142                                                   // scaling are allowed.
143     swapChainDesc.SwapEffect =
144       DXGI_SWAP_EFFECT_DISCARD; // On phone, no swap effects are supported.
145 #else
146     swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency.
147     swapChainDesc.Scaling = DXGI_SCALING_NONE;
148     swapChainDesc.SwapEffect =
149       DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Windows Store apps must use this
150                                         // SwapEffect.
151 #endif
152     swapChainDesc.Flags = 0;
153 
154     ComPtr<IDXGIDevice1> dxgiDevice;
155     DX::ThrowIfFailed(m_d3dDevice.As(&dxgiDevice));
156 
157     ComPtr<IDXGIAdapter> dxgiAdapter;
158     DX::ThrowIfFailed(dxgiDevice->GetAdapter(&dxgiAdapter));
159 
160     ComPtr<IDXGIFactory2> dxgiFactory;
161     DX::ThrowIfFailed(
162       dxgiAdapter->GetParent(__uuidof(IDXGIFactory2), &dxgiFactory));
163 
164     Windows::UI::Core::CoreWindow ^ window = m_window.Get();
165     DX::ThrowIfFailed(dxgiFactory->CreateSwapChainForCoreWindow(
166       m_d3dDevice.Get(), reinterpret_cast<IUnknown*>(window), &swapChainDesc,
167       nullptr, // Allow on all displays.
168       &m_swapChain));
169 
170     // Ensure that DXGI does not queue more than one frame at a time. This both
171     // reduces latency and
172     // ensures that the application will only render after each VSync,
173     // minimizing power consumption.
174     DX::ThrowIfFailed(dxgiDevice->SetMaximumFrameLatency(1));
175   }
176 
177   // Set the proper orientation for the swap chain, and generate the
178   // 3D matrix transformation for rendering to the rotated swap chain.
179   DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
180   switch (m_orientation) {
181     case DisplayOrientations::Landscape:
182       rotation = DXGI_MODE_ROTATION_IDENTITY;
183       m_orientationTransform3D = XMFLOAT4X4( // 0-degree Z-rotation
184         1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
185         0.0f, 0.0f, 0.0f, 1.0f);
186       break;
187 
188     case DisplayOrientations::Portrait:
189       rotation = DXGI_MODE_ROTATION_ROTATE270;
190       m_orientationTransform3D = XMFLOAT4X4( // 90-degree Z-rotation
191         0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
192         0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
193       break;
194 
195     case DisplayOrientations::LandscapeFlipped:
196       rotation = DXGI_MODE_ROTATION_ROTATE180;
197       m_orientationTransform3D = XMFLOAT4X4( // 180-degree Z-rotation
198         -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
199         0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
200       break;
201 
202     case DisplayOrientations::PortraitFlipped:
203       rotation = DXGI_MODE_ROTATION_ROTATE90;
204       m_orientationTransform3D = XMFLOAT4X4( // 270-degree Z-rotation
205         0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
206         0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
207       break;
208 
209     default:
210       throw ref new Platform::FailureException();
211   }
212 
213 #if !PHONE || WINVER > 0x0602
214   DX::ThrowIfFailed(m_swapChain->SetRotation(rotation));
215 #endif // !PHONE
216 
217   // Create a render target view of the swap chain back buffer.
218   ComPtr<ID3D11Texture2D> backBuffer;
219   DX::ThrowIfFailed(
220     m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), &backBuffer));
221 
222   DX::ThrowIfFailed(m_d3dDevice->CreateRenderTargetView(
223     backBuffer.Get(), nullptr, &m_renderTargetView));
224 
225   // Create a depth stencil view.
226   CD3D11_TEXTURE2D_DESC depthStencilDesc(
227     DXGI_FORMAT_D24_UNORM_S8_UINT, static_cast<UINT>(m_renderTargetSize.Width),
228     static_cast<UINT>(m_renderTargetSize.Height), 1, 1,
229     D3D11_BIND_DEPTH_STENCIL);
230 
231   ComPtr<ID3D11Texture2D> depthStencil;
232   DX::ThrowIfFailed(
233     m_d3dDevice->CreateTexture2D(&depthStencilDesc, nullptr, &depthStencil));
234 
235   CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(
236     D3D11_DSV_DIMENSION_TEXTURE2D);
237   DX::ThrowIfFailed(m_d3dDevice->CreateDepthStencilView(
238     depthStencil.Get(), &depthStencilViewDesc, &m_depthStencilView));
239 
240   // Set the rendering viewport to target the entire window.
241   CD3D11_VIEWPORT viewport(0.0f, 0.0f, m_renderTargetSize.Width,
242                            m_renderTargetSize.Height);
243 
244   m_d3dContext->RSSetViewports(1, &viewport);
245 }
246 
247 // This method is called in the event handler for the SizeChanged event.
UpdateForWindowSizeChange()248 void Direct3DBase::UpdateForWindowSizeChange()
249 {
250   if (m_window->Bounds.Width != m_windowBounds.Width ||
251       m_window->Bounds.Height != m_windowBounds.Height ||
252 #if WINVER > 0x0602
253       m_orientation !=
254         DisplayInformation::GetForCurrentView()->CurrentOrientation)
255 #else
256       m_orientation != DisplayProperties::CurrentOrientation)
257 #endif
258   {
259     ID3D11RenderTargetView* nullViews[] = { nullptr };
260     m_d3dContext->OMSetRenderTargets(ARRAYSIZE(nullViews), nullViews, nullptr);
261     m_renderTargetView = nullptr;
262     m_depthStencilView = nullptr;
263     m_d3dContext->Flush();
264     CreateWindowSizeDependentResources();
265   }
266 }
267 
ReleaseResourcesForSuspending()268 void Direct3DBase::ReleaseResourcesForSuspending()
269 {
270   // Phone applications operate in a memory-constrained environment, so when
271   // entering
272   // the background it is a good idea to free memory-intensive objects that
273   // will be
274   // easy to restore upon reactivation. The swapchain and backbuffer are good
275   // candidates
276   // here, as they consume a large amount of memory and can be reinitialized
277   // quickly.
278   m_swapChain = nullptr;
279   m_renderTargetView = nullptr;
280   m_depthStencilView = nullptr;
281 }
282 
283 // Method to deliver the final image to the display.
Present()284 void Direct3DBase::Present()
285 {
286 // The first argument instructs DXGI to block until VSync, putting the
287 // application
288 // to sleep until the next VSync. This ensures we don't waste any cycles
289 // rendering
290 // frames that will never be displayed to the screen.
291 #if PHONE && WINVER <= 0x0602
292   HRESULT hr = m_swapChain->Present(1, 0);
293 #else
294   // The application may optionally specify "dirty" or "scroll"
295   // rects to improve efficiency in certain scenarios.
296   DXGI_PRESENT_PARAMETERS parameters = { 0 };
297   parameters.DirtyRectsCount = 0;
298   parameters.pDirtyRects = nullptr;
299   parameters.pScrollRect = nullptr;
300   parameters.pScrollOffset = nullptr;
301 
302   HRESULT hr = m_swapChain->Present1(1, 0, &parameters);
303 #endif
304 
305   // Discard the contents of the render target.
306   // This is a valid operation only when the existing contents will be entirely
307   // overwritten. If dirty or scroll rects are used, this call should be
308   // removed.
309   m_d3dContext->DiscardView(m_renderTargetView.Get());
310 
311   // Discard the contents of the depth stencil.
312   m_d3dContext->DiscardView(m_depthStencilView.Get());
313 
314   // If the device was removed either by a disconnect or a driver upgrade, we
315   // must recreate all device resources.
316   if (hr == DXGI_ERROR_DEVICE_REMOVED) {
317     HandleDeviceLost();
318   } else {
319     DX::ThrowIfFailed(hr);
320   }
321 }
322 
323 // Method to convert a length in device-independent pixels (DIPs) to a length
324 // in physical pixels.
ConvertDipsToPixels(float dips)325 float Direct3DBase::ConvertDipsToPixels(float dips)
326 {
327   static const float dipsPerInch = 96.0f;
328 #if WINVER > 0x0602
329   return floor(dips * DisplayInformation::GetForCurrentView()->LogicalDpi /
330                  dipsPerInch +
331                0.5f); // Round to nearest integer.
332 #else
333   return floor(dips * DisplayProperties::LogicalDpi / dipsPerInch +
334                0.5f); // Round to nearest integer.
335 #endif
336 }
337