// This file is part of VSTGUI. It is subject to the license terms // in the LICENSE file found in the top-level directory of this // distribution and at http://github.com/steinbergmedia/vstgui/LICENSE #pragma once #include "externalview_hwnd.h" #include #include #include #include #include #include #ifdef _MSC_VER #pragma comment(lib, "dcomp.lib") #pragma comment(lib, "d3d12.lib") #pragma comment(lib, "dxgi.lib") #endif //------------------------------------------------------------------------ namespace VSTGUI { //------------------------------------------------------------------------ //------------------------------------------------------------------------ struct Win32Exception : std::exception { explicit Win32Exception (HRESULT hr) : _hr (hr) { FormatMessageA (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, hr, MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&_errorStr, 0, NULL); } ~Win32Exception () noexcept { if (_errorStr) LocalFree ((HLOCAL)_errorStr); } const char* what () const noexcept override { return _errorStr; } HRESULT hr () const noexcept { return _hr; } private: HRESULT _hr; char* _errorStr {nullptr}; }; inline void ThrowIfFailed (HRESULT hr) { if (FAILED (hr)) { throw Win32Exception (hr); } } //------------------------------------------------------------------------ namespace ExternalView { //------------------------------------------------------------------------ struct IDirect3D12View { virtual ~IDirect3D12View () noexcept = default; virtual ID3D12CommandAllocator* getCommandAllocator () const = 0; virtual IDXGISwapChain3* getSwapChain () const = 0; virtual ID3D12Device* getDevice () const = 0; virtual INT getFrameIndex () const = 0; virtual void setFrameIndex (INT index) = 0; virtual void render () = 0; }; //------------------------------------------------------------------------ struct IDirect3D12Renderer { virtual ~IDirect3D12Renderer () noexcept = default; virtual bool init (IDirect3D12View* view) = 0; virtual void render (ID3D12CommandQueue* queue) = 0; virtual void beforeSizeUpdate () = 0; virtual void onSizeUpdate (IntSize newSize, double scaleFactor) = 0; virtual void onAttach () = 0; virtual void onRemove () = 0; virtual uint32_t getFrameCount () const = 0; }; //------------------------------------------------------------------------ using Direct3D12RendererPtr = std::shared_ptr; //------------------------------------------------------------------------ struct GPUFence { template using ComPtr = Microsoft::WRL::ComPtr; GPUFence () = default; GPUFence (ID3D12Device* device, UINT64 initialValue = 0) { ThrowIfFailed (device->CreateFence (0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS (&m_fence))); m_event = CreateEvent (nullptr, FALSE, FALSE, nullptr); if (m_event == nullptr) { ThrowIfFailed (HRESULT_FROM_WIN32 (GetLastError ())); } m_value = initialValue; } ~GPUFence () noexcept { if (m_event) CloseHandle (m_event); } GPUFence& operator=(GPUFence&& o) noexcept { m_event = o.m_event; m_fence = o.m_fence; m_value = o.m_value; o.m_event = nullptr; o.m_fence.Reset (); o.m_value = {}; return *this; } void wait (ID3D12CommandQueue* queue) { if (m_fence == nullptr) return; const auto value = m_value; ThrowIfFailed (queue->Signal (m_fence.Get (), value)); m_value++; if (m_fence->GetCompletedValue () < value) { ThrowIfFailed (m_fence->SetEventOnCompletion (value, m_event)); WaitForSingleObject (m_event, INFINITE); } } HANDLE m_event {nullptr}; ComPtr m_fence; UINT64 m_value {0}; }; //------------------------------------------------------------------------ struct Direct3D12View : public ExternalHWNDBase, IDirect3D12View { template using ComPtr = Microsoft::WRL::ComPtr; Direct3D12View (HINSTANCE instance, const Direct3D12RendererPtr& renderer, ComPtr factory = nullptr, ComPtr device = nullptr, ComPtr commandQueue = nullptr) : Base (instance), m_renderer (renderer), m_factory (factory), m_device (device), m_commandQueue (commandQueue) { vstgui_assert ((factory && device) || (!factory && !device), "Either both factory and device are provided or none of both!"); vstgui_assert (commandQueue ? device : true, "If a command queue is provided, the device must also be provided!"); } static std::shared_ptr make (HINSTANCE instance, const Direct3D12RendererPtr& renderer, ComPtr factory = nullptr, ComPtr device = nullptr, ComPtr queue = nullptr) { return std::make_shared (instance, renderer, factory, device, queue); } void render () override { doRender (); } Direct3D12RendererPtr& getRenderer () { return m_renderer; } const Direct3D12RendererPtr& getRenderer () const { return m_renderer; } private: ID3D12CommandAllocator* getCommandAllocator () const { return m_commandAllocator.Get (); } IDXGISwapChain3* getSwapChain () const { return m_swapChain.Get (); } ID3D12Device* getDevice () const { return m_device.Get (); } INT getFrameIndex () const { return m_frameIndex; } void setFrameIndex (INT index) { m_frameIndex = index; } void doRender () { if (mutex.try_lock ()) { if (m_commandQueue) { HRESULT result = S_FALSE; try { waitForPreviousFrame (); m_renderer->render (m_commandQueue.Get ()); result = getSwapChain ()->Present (1, 0); ThrowIfFailed (result); } catch (const Win32Exception& e) { try { freeResources (); } catch (...) { } throw (e); } } mutex.unlock (); } } bool attach (void* parent, PlatformViewType parentViewType) override { if (Base::attach (parent, parentViewType)) { try { if (m_renderer->init (this)) { init (); m_renderer->onAttach (); } } catch (...) { auto reasonHR = m_device->GetDeviceRemovedReason (); Win32Exception e (reasonHR); freeResources (); } return true; } return false; } bool remove () override { Guard g (mutex); waitForPreviousFrame (); freeResources (); return Base::remove (); } void setViewSize (IntRect frame, IntRect visible) override { Guard g (mutex); Base::setViewSize (frame, visible); m_visibleRect = visible; updateSizes (); } void setContentScaleFactor (double factor) override { Guard g (mutex); m_scaleFactor = factor; updateSizes (); } void updateSizes () { if (m_swapChain) { if (m_size.width == m_visibleRect.size.width && m_size.height == m_visibleRect.size.height) return; m_size = m_visibleRect.size; waitForPreviousFrame (); m_renderer->beforeSizeUpdate (); ThrowIfFailed (m_dcompVisual->SetContent (nullptr)); ThrowIfFailed (m_swapChain->ResizeBuffers ( m_renderer->getFrameCount (), static_cast (m_size.width), static_cast (m_size.height), DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_SWAP_EFFECT_FLIP_DISCARD)); ThrowIfFailed (m_dcompVisual->SetContent (m_swapChain.Get ())); m_renderer->onSizeUpdate (m_size, m_scaleFactor); ThrowIfFailed (m_dcompDevice->Commit ()); } } void freeResources () { m_fence = {}; try { m_renderer->onRemove (); } catch (...) { } m_commandAllocator.Reset (); m_commandQueue.Reset (); m_dcompDevice.Reset (); m_dcompTarget.Reset (); m_dcompVisual.Reset (); m_swapChain.Reset (); m_device.Reset (); } void init () { #if defined(_DEBUG) // Enable the D3D12 debug layer. { ComPtr debugController; if (SUCCEEDED (D3D12GetDebugInterface (IID_PPV_ARGS (&debugController)))) { debugController->EnableDebugLayer (); } } #endif if (!m_factory) ThrowIfFailed (CreateDXGIFactory1 (IID_PPV_ARGS (&m_factory))); if (m_device == nullptr) { ComPtr hardwareAdapter; getHardwareAdapter (m_factory.Get (), &hardwareAdapter); if (!hardwareAdapter) { throw; } ThrowIfFailed (D3D12CreateDevice (hardwareAdapter.Get (), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS (&m_device))); } if (!m_commandQueue) { // Describe and create the command queue. D3D12_COMMAND_QUEUE_DESC queueDesc = {}; queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; ThrowIfFailed (m_device->CreateCommandQueue (&queueDesc, IID_PPV_ARGS (&m_commandQueue))); } try { createSwapChain (m_factory.Get ()); setupDirectComposition (); } catch (...) { auto exc = std::current_exception (); throw (exc); } ThrowIfFailed ( m_factory->MakeWindowAssociation (container.getHWND (), DXGI_MWA_NO_ALT_ENTER)); ThrowIfFailed (m_device->CreateCommandAllocator (D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS (&m_commandAllocator))); m_fence = GPUFence (m_device.Get (), 1); } void createSwapChain (IDXGIFactory4* factory) { // Describe and create the swap chain. DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; swapChainDesc.BufferCount = m_renderer->getFrameCount (); swapChainDesc.Width = static_cast (m_size.width); swapChainDesc.Height = static_cast (m_size.height); swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; swapChainDesc.SampleDesc.Count = 1; swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; ComPtr swapChain; ThrowIfFailed (factory->CreateSwapChainForComposition ( m_commandQueue.Get (), // Swap chain needs the queue so that it can force a flush on it. &swapChainDesc, nullptr, &swapChain)); ThrowIfFailed (swapChain.As (&m_swapChain)); } void setupDirectComposition () { // Create the DirectComposition device ThrowIfFailed (DCompositionCreateDevice ( nullptr, IID_PPV_ARGS (m_dcompDevice.ReleaseAndGetAddressOf ()))); // Create a DirectComposition target associated with the window (pass in hWnd here) ThrowIfFailed (m_dcompDevice->CreateTargetForHwnd ( container.getHWND (), true, m_dcompTarget.ReleaseAndGetAddressOf ())); // Create a DirectComposition "visual" ThrowIfFailed (m_dcompDevice->CreateVisual (m_dcompVisual.ReleaseAndGetAddressOf ())); // Associate the visual with the swap chain ThrowIfFailed (m_dcompVisual->SetContent (m_swapChain.Get ())); // Set the visual as the root of the DirectComposition target's composition tree ThrowIfFailed (m_dcompTarget->SetRoot (m_dcompVisual.Get ())); ThrowIfFailed (m_dcompDevice->Commit ()); } static void getHardwareAdapter (IDXGIFactory2* pFactory, IDXGIAdapter1** ppAdapter) { ComPtr adapter; *ppAdapter = nullptr; for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != pFactory->EnumAdapters1 (adapterIndex, &adapter); ++adapterIndex) { DXGI_ADAPTER_DESC1 desc; adapter->GetDesc1 (&desc); if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) { // Don't select the Basic Render Driver adapter. // If you want a software adapter, pass in "/warp" on the command line. continue; } // Check to see if the adapter supports Direct3D 12, but don't create the // actual device yet. if (SUCCEEDED (D3D12CreateDevice (adapter.Get (), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr))) { break; } } *ppAdapter = adapter.Detach (); } void waitForPreviousFrame () { if (!m_commandQueue || !m_swapChain) return; // WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE. // This is code implemented as such for simplicity. The D3D12HelloFrameBuffering // sample illustrates how to use fences for efficient resource usage and to // maximize GPU utilization. m_fence.wait (m_commandQueue.Get ()); m_frameIndex = m_swapChain->GetCurrentBackBufferIndex (); } using Mutex = std::recursive_mutex; using Guard = std::lock_guard; Mutex mutex; IntSize m_size {100, 100}; IntRect m_visibleRect {}; double m_scaleFactor {1.}; UINT m_frameIndex {0}; // Synchronization objects. GPUFence m_fence; ComPtr m_factory; ComPtr m_swapChain; ComPtr m_device; ComPtr m_commandQueue; ComPtr m_commandAllocator; // DirectComposition objects. ComPtr m_dcompDevice; ComPtr m_dcompTarget; ComPtr m_dcompVisual; Direct3D12RendererPtr m_renderer; }; //------------------------------------------------------------------------ } // ExternalView } // VSTGUI