Files
2026-08-16 18:24:52 +07:00

482 lines
13 KiB
C++

// 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 <d3d12.h>
#include <dcomp.h>
#include <dxgi1_4.h>
#include <wrl.h>
#include <comdef.h>
#include <mutex>
#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<IDirect3D12Renderer>;
//------------------------------------------------------------------------
struct GPUFence
{
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
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<ID3D12Fence> m_fence;
UINT64 m_value {0};
};
//------------------------------------------------------------------------
struct Direct3D12View : public ExternalHWNDBase,
IDirect3D12View
{
template<typename T>
using ComPtr = Microsoft::WRL::ComPtr<T>;
Direct3D12View (HINSTANCE instance, const Direct3D12RendererPtr& renderer,
ComPtr<IDXGIFactory4> factory = nullptr, ComPtr<ID3D12Device> device = nullptr, ComPtr<ID3D12CommandQueue> 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<Direct3D12View> make (HINSTANCE instance,
const Direct3D12RendererPtr& renderer,
ComPtr<IDXGIFactory4> factory = nullptr,
ComPtr<ID3D12Device> device = nullptr,
ComPtr<ID3D12CommandQueue> queue = nullptr)
{
return std::make_shared<Direct3D12View> (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<UINT> (m_size.width),
static_cast<UINT> (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<ID3D12Debug> 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<IDXGIAdapter1> 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<UINT> (m_size.width);
swapChainDesc.Height = static_cast<UINT> (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<IDXGISwapChain1> 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<IDXGIAdapter1> 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 mutex;
IntSize m_size {100, 100};
IntRect m_visibleRect {};
double m_scaleFactor {1.};
UINT m_frameIndex {0};
// Synchronization objects.
GPUFence m_fence;
ComPtr<IDXGIFactory4> m_factory;
ComPtr<IDXGISwapChain3> m_swapChain;
ComPtr<ID3D12Device> m_device;
ComPtr<ID3D12CommandQueue> m_commandQueue;
ComPtr<ID3D12CommandAllocator> m_commandAllocator;
// DirectComposition objects.
ComPtr<IDCompositionDevice> m_dcompDevice;
ComPtr<IDCompositionTarget> m_dcompTarget;
ComPtr<IDCompositionVisual> m_dcompVisual;
Direct3D12RendererPtr m_renderer;
};
//------------------------------------------------------------------------
} // ExternalView
} // VSTGUI