//----------------------------------------------------------------------------------------------- // Copyright (c) 2012 Andrew Garrison //----------------------------------------------------------------------------------------------- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software // and associated documentation files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. //----------------------------------------------------------------------------------------------- #include "DirectXBase.h" #include #include using namespace Microsoft::WRL; using namespace Windows::UI::Core; using namespace Windows::UI::Xaml::Controls; using namespace Windows::Foundation; using namespace Windows::Graphics::Display; using namespace D2D1; // Constructor. DirectXBase::DirectXBase() : m_dpi(-1.0f) { } // Initialize the DirectX resources required to run. void DirectXBase::Initialize(CoreWindow^ window, SwapChainBackgroundPanel^ panel, float dpi) { m_window = window; m_panel = panel; CreateDeviceIndependentResources(); CreateDeviceResources(); SetDpi(dpi); } // These are the resources required independent of the device. void DirectXBase::CreateDeviceIndependentResources() { D2D1_FACTORY_OPTIONS options; ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS)); #if defined(_DEBUG) // If the project is in a debug build, enable Direct2D debugging via SDK Layers options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION; #endif DX::ThrowIfFailed( D2D1CreateFactory( D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory1), &options, &m_d2dFactory ) ); DX::ThrowIfFailed( DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), &m_dwriteFactory ) ); DX::ThrowIfFailed( CoCreateInstance( CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_wicFactory) ) ); } // These are the resources that depend on the device. void DirectXBase::CreateDeviceResources() { // This flag adds support for surfaces with a different color channel ordering than the API default. // It is recommended usage, and is required for compatibility with Direct2D. UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; ComPtr dxgiDevice; #if defined(_DEBUG) // If the project is in a debug build, enable debugging via SDK Layers with this flag. creationFlags |= D3D11_CREATE_DEVICE_DEBUG; #endif // This array defines the set of DirectX hardware feature levels this app will support. // Note the ordering should be preserved. // Don't forget to declare your application's minimum required feature level in its // description. All applications are assumed to support 9.1 unless otherwise stated. D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1 }; // Create the DX11 API device object, and get a corresponding context. ComPtr device; ComPtr context; DX::ThrowIfFailed( D3D11CreateDevice( nullptr, // specify null to use the default adapter D3D_DRIVER_TYPE_HARDWARE, 0, // leave as 0 unless software device creationFlags, // optionally set debug and Direct2D compatibility flags featureLevels, // list of feature levels this app can support ARRAYSIZE(featureLevels), // number of entries in above list D3D11_SDK_VERSION, // always set this to D3D11_SDK_VERSION for Metro style apps &device, // returns the Direct3D device created &m_featureLevel, // returns feature level of device created &context // returns the device immediate context ) ); // Get the DirectX11.1 device by QI off the DirectX11 one. DX::ThrowIfFailed( device.As(&m_d3dDevice) ); // And get the corresponding device context in the same way. DX::ThrowIfFailed( context.As(&m_d3dContext) ); // Obtain the underlying DXGI device of the Direct3D11.1 device. DX::ThrowIfFailed( m_d3dDevice.As(&dxgiDevice) ); // Obtain the Direct2D device for 2-D rendering. DX::ThrowIfFailed( m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice) ); // And get its corresponding device context object. DX::ThrowIfFailed( m_d2dDevice->CreateDeviceContext( D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &m_d2dContext ) ); // Release the swap chain (if it exists) as it will be incompatible with the new device. m_swapChain = nullptr; } // Helps track the DPI in the helper class. // This is called in the dpiChanged event handler in the view class. void DirectXBase::SetDpi(float dpi) { if (dpi != m_dpi) { // Save the DPI of this display in our class. m_dpi = dpi; // Update Direct2D's stored DPI. m_d2dContext->SetDpi(m_dpi, m_dpi); // Often a DPI change implies a window size change. In some cases Windows will issues // both a size changed event and a DPI changed event. In this case, the resulting bounds // will not change, and the window resize code will only be executed once. UpdateForWindowSizeChange(); } } // This routine is called in the event handler for the view SizeChanged event. void DirectXBase::UpdateForWindowSizeChange() { // Only handle window size changed if there is no pending DPI change. if (m_dpi != DisplayProperties::LogicalDpi) return; if (m_window->Bounds.Width != m_windowBounds.Width || m_window->Bounds.Height != m_windowBounds.Height) { m_d2dContext->SetTarget(nullptr); m_d2dTargetBitmap = nullptr; m_renderTargetView = nullptr; m_depthStencilView = nullptr; CreateWindowSizeDependentResources(); } } // Allocate all memory resources that change on a window SizeChanged event. void DirectXBase::CreateWindowSizeDependentResources() { // Store the window bounds so the next time we get a SizeChanged event we can // avoid rebuilding everything if the size is identical. m_windowBounds = m_window->Bounds; // Calculate the necessary swap chain and render target size in pixels. m_renderTargetSize.Width = ConvertDipsToPixels(m_windowBounds.Width); m_renderTargetSize.Height = ConvertDipsToPixels(m_windowBounds.Height); // If the swap chain already exists, resize it. if (m_swapChain != nullptr) { DX::ThrowIfFailed( m_swapChain->ResizeBuffers( 2, static_cast(m_renderTargetSize.Width), static_cast(m_renderTargetSize.Height), DXGI_FORMAT_B8G8R8A8_UNORM, 0 ) ); } // Otherwise, create a new one. else { // Allocate a descriptor. DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0}; swapChainDesc.Width = static_cast(m_renderTargetSize.Width); // Match the size of the windowm. swapChainDesc.Height = static_cast(m_renderTargetSize.Height); swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swapchain format. swapChainDesc.Stereo = false; swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling. swapChainDesc.SampleDesc.Quality = 0; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.BufferCount = 2; // Use double buffering to enable flip. swapChainDesc.Scaling = DXGI_SCALING_STRETCH; swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Metro style apps must use this SwapEffect. swapChainDesc.Flags = 0; // Once the desired swap chain description is configured, it must be created on the same adapter as our D3D Device. // First, retrieve the underlying DXGI Device from the D3D Device. ComPtr dxgiDevice; DX::ThrowIfFailed( m_d3dDevice.As(&dxgiDevice) ); // Identify the physical adapter (GPU or card) this device is running on. ComPtr dxgiAdapter; DX::ThrowIfFailed( dxgiDevice->GetAdapter(&dxgiAdapter) ); // And obtain the factory object that created it. ComPtr dxgiFactory; DX::ThrowIfFailed( dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory)) ); // Create the swap chain and then associate it with the SwapChainBackgroundPanel. DX::ThrowIfFailed( dxgiFactory->CreateSwapChainForComposition( m_d3dDevice.Get(), &swapChainDesc, nullptr, &m_swapChain ) ); ComPtr panelNative; DX::ThrowIfFailed( reinterpret_cast(m_panel)->QueryInterface(IID_PPV_ARGS(&panelNative)) ); DX::ThrowIfFailed( panelNative->SetSwapChain(m_swapChain.Get()) ); // Ensure that DXGI does not queue more than one frame at a time. This both reduces // latency and ensures that the application will only render after each VSync, minimizing // power consumption. DX::ThrowIfFailed( dxgiDevice->SetMaximumFrameLatency(1) ); } // Obtain the backbuffer for this window which will be the final 3D rendertarget. ComPtr backBuffer; DX::ThrowIfFailed( m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)) ); // Create a view interface on the rendertarget to use on bind. DX::ThrowIfFailed( m_d3dDevice->CreateRenderTargetView( backBuffer.Get(), nullptr, &m_renderTargetView ) ); // Create a descriptor for the depth/stencil buffer. CD3D11_TEXTURE2D_DESC depthStencilDesc( DXGI_FORMAT_D24_UNORM_S8_UINT, static_cast(m_renderTargetSize.Width), static_cast(m_renderTargetSize.Height), 1, 1, D3D11_BIND_DEPTH_STENCIL ); // Allocate a 2-D surface as the depth/stencil buffer. ComPtr depthStencil; DX::ThrowIfFailed( m_d3dDevice->CreateTexture2D( &depthStencilDesc, nullptr, &depthStencil ) ); // Create a DepthStencil view on this surface to use on bind. CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D); DX::ThrowIfFailed( m_d3dDevice->CreateDepthStencilView( depthStencil.Get(), &depthStencilViewDesc, &m_depthStencilView ) ); // Create a viewport descriptor of the full window size. CD3D11_VIEWPORT viewport( 0.0f, 0.0f, m_renderTargetSize.Width, m_renderTargetSize.Height ); // Set the current viewport using the descriptor. m_d3dContext->RSSetViewports(1, &viewport); // Now we set up the Direct2D render target bitmap linked to the swapchain. // Whenever we render to this bitmap, it will be directly rendered to the // swapchain associated with the window. D2D1_BITMAP_PROPERTIES1 bitmapProperties = BitmapProperties1( D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), m_dpi, m_dpi ); // Direct2D needs the DXGI version of the backbuffer surface pointer. ComPtr dxgiBackBuffer; DX::ThrowIfFailed( m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiBackBuffer)) ); // Get a D2D surface from the DXGI back buffer to use as the D2D render target. DX::ThrowIfFailed( m_d2dContext->CreateBitmapFromDxgiSurface( dxgiBackBuffer.Get(), &bitmapProperties, &m_d2dTargetBitmap ) ); // So now we can set the Direct2D render target. m_d2dContext->SetTarget(m_d2dTargetBitmap.Get()); // Set D2D text anti-alias mode to Grayscale to ensure proper rendering of text on intermediate surfaces. m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); // Set the blend function. ID3D11BlendState* g_pBlendState = NULL; D3D11_BLEND_DESC blendDesc; ZeroMemory(&blendDesc, sizeof(D3D11_BLEND_DESC)); blendDesc.AlphaToCoverageEnable = false; blendDesc.IndependentBlendEnable = false; D3D11_RENDER_TARGET_BLEND_DESC rtBlendDesc; rtBlendDesc.BlendEnable = false; rtBlendDesc.SrcBlend = D3D11_BLEND_SRC_ALPHA; rtBlendDesc.DestBlend = D3D11_BLEND_INV_SRC_ALPHA; rtBlendDesc.BlendOp = D3D11_BLEND_OP_ADD; rtBlendDesc.SrcBlendAlpha = D3D11_BLEND_ZERO; rtBlendDesc.DestBlendAlpha = D3D11_BLEND_ZERO; rtBlendDesc.BlendOpAlpha = D3D11_BLEND_OP_ADD; rtBlendDesc.RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; blendDesc.RenderTarget[0] = rtBlendDesc; auto hr1 = m_d3dDevice->CreateBlendState(&blendDesc, &g_pBlendState); m_d3dContext->OMSetBlendState(g_pBlendState, 0, 0xffffffff); D3D11_DEPTH_STENCIL_DESC dsDesc; // Depth test parameters dsDesc.DepthEnable = false; dsDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; dsDesc.DepthFunc = D3D11_COMPARISON_LESS; // Stencil test parameters dsDesc.StencilEnable = true; dsDesc.StencilReadMask = 0xFF; dsDesc.StencilWriteMask = 0xFF; // Stencil operations if pixel is front-facing dsDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; dsDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; dsDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; dsDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; // Stencil operations if pixel is back-facing dsDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; dsDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; dsDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; dsDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; // Create depth stencil state ID3D11DepthStencilState * pDSState; m_d3dDevice->CreateDepthStencilState(&dsDesc, &pDSState); m_d3dContext->OMSetDepthStencilState(pDSState, 1); m_d3dContext->OMSetRenderTargets(1, m_renderTargetView.GetAddressOf(), m_depthStencilView.Get()); } // Method to deliver the final image to the display. void DirectXBase::Present() { // The application may optionally specify "dirty" or "scroll" rects to improve efficiency // in certain scenarios. DXGI_PRESENT_PARAMETERS parameters = {0}; parameters.DirtyRectsCount = 0; parameters.pDirtyRects = nullptr; parameters.pScrollRect = nullptr; parameters.pScrollOffset = nullptr; // The first argument instructs DXGI to block until VSync, putting the application // to sleep until the next VSync. This ensures we don't waste any cycles rendering // frames that will never be displayed to the screen. HRESULT hr = m_swapChain->Present1(1, 0, ¶meters); m_d3dContext->OMSetRenderTargets(1,m_renderTargetView.GetAddressOf(),m_depthStencilView.Get()); // If the device was removed either by a disconnect or a driver upgrade, we // must completely reinitialize the renderer. if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { Initialize(m_window.Get(), m_panel, m_dpi); } else { DX::ThrowIfFailed(hr); } } // Method to convert a length in device-independent pixels (DIPs) to a length in physical pixels. float DirectXBase::ConvertDipsToPixels(float dips) { static const float dipsPerInch = 96.0f; return floor(dips * m_dpi / dipsPerInch + 0.5f); // Round to nearest integer. }