From 5abf3c54c772983eb14aca0ae1d682d35c7221b8 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 10 Nov 2024 22:51:30 -0800 Subject: [PATCH] Add BackBufferRenderer --- CMakeLists.txt | 26 ++++++ src/Mods.cpp | 2 + src/REFramework.cpp | 14 +++ src/REFramework.hpp | 2 + src/mods/BackBufferRenderer.cpp | 151 ++++++++++++++++++++++++++++++++ src/mods/BackBufferRenderer.hpp | 67 ++++++++++++++ src/mods/vr/D3D12Component.cpp | 11 --- src/mods/vr/D3D12Component.hpp | 1 - 8 files changed, 262 insertions(+), 12 deletions(-) create mode 100644 src/mods/BackBufferRenderer.cpp create mode 100644 src/mods/BackBufferRenderer.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 57d1b6648..fa6d0e160 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2226,6 +2226,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -2276,6 +2277,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -2433,6 +2435,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -2483,6 +2486,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -4305,6 +4309,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -4355,6 +4360,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -4512,6 +4518,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -4562,6 +4569,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -4719,6 +4727,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -4769,6 +4778,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -7425,6 +7435,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -7475,6 +7486,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -7632,6 +7644,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -7682,6 +7695,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -8673,6 +8687,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -8723,6 +8738,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -9712,6 +9728,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -9762,6 +9779,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -10753,6 +10771,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -10803,6 +10822,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -11794,6 +11814,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -11844,6 +11865,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -12835,6 +12857,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -12885,6 +12908,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" @@ -13876,6 +13900,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowsMessageHook.cpp" "src/cimgui/cimgui.cpp" "src/mods/APIProxy.cpp" + "src/mods/BackBufferRenderer.cpp" "src/mods/Camera.cpp" "src/mods/DeveloperTools.cpp" "src/mods/FirstPerson.cpp" @@ -13926,6 +13951,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "src/WindowFilter.hpp" "src/WindowsMessageHook.hpp" "src/mods/APIProxy.hpp" + "src/mods/BackBufferRenderer.hpp" "src/mods/Camera.hpp" "src/mods/DeveloperTools.hpp" "src/mods/FirstPerson.hpp" diff --git a/src/Mods.cpp b/src/Mods.cpp index 9c5542074..b681dd643 100644 --- a/src/Mods.cpp +++ b/src/Mods.cpp @@ -1,5 +1,6 @@ #include +#include "mods/BackBufferRenderer.hpp" #include "mods/APIProxy.hpp" #include "mods/Camera.hpp" #include "mods/Graphics.hpp" @@ -20,6 +21,7 @@ #include "Mods.hpp" Mods::Mods() { + m_mods.emplace_back(BackBufferRenderer::get()); m_mods.emplace_back(REFrameworkConfig::get()); #if defined(REENGINE_AT) diff --git a/src/REFramework.cpp b/src/REFramework.cpp index 6ad3f488e..546f09c5d 100644 --- a/src/REFramework.cpp +++ b/src/REFramework.cpp @@ -1067,6 +1067,13 @@ void REFramework::on_post_present_d3d12() { return; } + + if (m_d3d12.graphics_memory != nullptr) { + auto& hook = get_d3d12_hook(); + auto command_queue = hook->get_command_queue(); + + m_d3d12.graphics_memory->Commit(command_queue); + } for (auto& mod : m_mods->get_mods()) { mod->on_post_present(); @@ -2165,6 +2172,13 @@ bool REFramework::init_d3d12() { auto device = m_d3d12_hook->get_device(); + spdlog::info("[D3D12] Creating DXTK graphics memory..."); + + // Realistically we only have one device. If not, then... IDK + if (m_d3d12.graphics_memory == nullptr) { + m_d3d12.graphics_memory = std::make_unique(device); + } + spdlog::info("[D3D12] Creating command allocator..."); m_d3d12.cmd_ctxs.clear(); diff --git a/src/REFramework.hpp b/src/REFramework.hpp index 6d0250f57..c6075fb74 100644 --- a/src/REFramework.hpp +++ b/src/REFramework.hpp @@ -8,6 +8,7 @@ #include #include +#include <../../directxtk12-src/Inc/GraphicsMemory.h> #include "mods/vr/d3d12/CommandContext.hpp" class Mods; @@ -315,6 +316,7 @@ class REFramework { uint32_t rt_height{}; std::array imgui_backend_datas{}; + std::unique_ptr graphics_memory{}; // for use in several places around REF } m_d3d12{}; public: diff --git a/src/mods/BackBufferRenderer.cpp b/src/mods/BackBufferRenderer.cpp new file mode 100644 index 000000000..6d03be175 --- /dev/null +++ b/src/mods/BackBufferRenderer.cpp @@ -0,0 +1,151 @@ +#include "BackBufferRenderer.hpp" + +std::shared_ptr& BackBufferRenderer::get() { + static auto instance = std::make_shared(); + return instance; +} + +std::optional BackBufferRenderer::on_initialize_d3d_thread() { + if (g_framework->is_dx12()) { + for (auto& context : m_d3d12.command_contexts) { + context = std::make_unique(); + context->setup(L"BackBufferRenderer D3D12 Command Context"); + } + + auto swapchain = g_framework->get_d3d12_hook()->get_swap_chain(); + auto device = g_framework->get_d3d12_hook()->get_device(); + + d3d12::ComPtr backbuffer{}; + if (FAILED(swapchain->GetBuffer(0, IID_PPV_ARGS(&backbuffer)))) { + return "Failed to get back buffer"; + } + + auto desc = backbuffer->GetDesc(); + + m_d3d12.default_rt_state = DirectX::RenderTargetState{desc.Format, DXGI_FORMAT_UNKNOWN}; + + spdlog::info("BackBufferRenderer D3D12 initialized"); + } else { + // TODO + spdlog::info("BackBufferRenderer D3D11 initialized"); + } + + // OK + return Mod::on_initialize(); +} + +void BackBufferRenderer::on_device_reset() { + for (auto& ctx : m_d3d12.command_contexts) { + ctx.reset(); + } + + for (auto& bb : m_d3d12.backbuffers) { + bb.reset(); + } +} + +void BackBufferRenderer::render_d3d12() { + if (m_d3d12.render_work.empty()) { + return; + } + + for (auto& ctx : m_d3d12.command_contexts) { + if (ctx == nullptr) { + ctx = std::make_unique(); + ctx->setup(L"BackBufferRenderer D3D12 Command Context"); + } + } + + auto swapchain = g_framework->get_d3d12_hook()->get_swap_chain(); + auto device = g_framework->get_d3d12_hook()->get_device(); + for (size_t i = 0; i < m_d3d12.backbuffers.size(); ++i) { + d3d12::ComPtr backbuffer{}; + if (FAILED(swapchain->GetBuffer(i, IID_PPV_ARGS(&backbuffer)))) { + break; + } + + if (m_d3d12.backbuffers[i] == nullptr || m_d3d12.backbuffers[i]->texture.Get() != backbuffer.Get()) { + spdlog::info("[ChainViewer] Setting up backbuffer {}", i); + + m_d3d12.backbuffers[i].reset(); + + m_d3d12.backbuffers[i] = std::make_unique(); + if (!m_d3d12.backbuffers[i]->setup(device, backbuffer.Get(), std::nullopt, std::nullopt, L"ChainViewer Backbuffer")) { + spdlog::error("[ChainViewer] Failed to setup backbuffer {}", i); + m_d3d12.backbuffers[i].reset(); + continue; + } + } + } + + const auto bb_index = swapchain->GetCurrentBackBufferIndex(); + + d3d12::ComPtr backbuffer{}; + if (FAILED(swapchain->GetBuffer(bb_index, IID_PPV_ARGS(&backbuffer)))) { + return; + } + + const auto desc = backbuffer->GetDesc(); + + auto& bb_context = m_d3d12.backbuffers[bb_index % m_d3d12.backbuffers.size()]; + auto& command_context = m_d3d12.command_contexts[bb_index % m_d3d12.command_contexts.size()]; + + command_context->wait(2000); + + auto& cmd_list = command_context->cmd_list; + + D3D12_RECT scissor_rect{}; + scissor_rect.left = 0; + scissor_rect.top = 0; + scissor_rect.right = (LONG)desc.Width; + scissor_rect.bottom = (LONG)desc.Height; + + D3D12_VIEWPORT viewport{}; + viewport.Width = (float)desc.Width; + viewport.Height = (float)desc.Height; + viewport.MaxDepth = 1.0f; + + D3D12_RESOURCE_BARRIER barrier{}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.pResource = backbuffer.Get(); + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + cmd_list->ResourceBarrier(1, &barrier); + + D3D12_CPU_DESCRIPTOR_HANDLE rtv_heaps[] = { bb_context->get_rtv() }; + cmd_list->OMSetRenderTargets(1, rtv_heaps, FALSE, nullptr); + + cmd_list->RSSetViewports(1, &viewport); + cmd_list->RSSetScissorRects(1, &scissor_rect); + + decltype(m_d3d12.render_work) works{}; + { + std::scoped_lock _{m_d3d12.render_work_mtx}; + works = m_d3d12.render_work; + m_d3d12.render_work.clear(); + } + + for (auto& work : works) { + work(cmd_list.Get()); + } + + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; + cmd_list->ResourceBarrier(1, &barrier); + + command_context->has_commands = true; + command_context->execute(); +} + +void BackBufferRenderer::render_d3d11() { + // TODO +} + +void BackBufferRenderer::on_present() { + if (g_framework->is_dx12()) { + render_d3d12(); + } else { + render_d3d11(); + } +} \ No newline at end of file diff --git a/src/mods/BackBufferRenderer.hpp b/src/mods/BackBufferRenderer.hpp new file mode 100644 index 000000000..249517bfc --- /dev/null +++ b/src/mods/BackBufferRenderer.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include + +#include <../directxtk12-src/Inc/SimpleMath.h> +#include <../directxtk12-src/Inc/CommonStates.h> +#include <../directxtk12-src/Inc/Effects.h> +#include <../directxtk12-src/Inc/GeometricPrimitive.h> + +#include "vr/d3d12/CommandContext.hpp" +#include "vr/d3d12/TextureContext.hpp" +#include "vr/d3d12/ComPtr.hpp" + +#include "Mod.hpp" + +// BackBufferRenderer is a backend mod that other mods can use +// to render to the backbuffer, so we don't need to have boilerplate everywhere. +// Automatically sets up render target to backbuffer, scissor rect and viewport. +class BackBufferRenderer : public Mod { +public: + static std::shared_ptr& get(); + +public: + std::string_view get_name() const override { + return "BackBufferRenderer"; + } + + std::optional on_initialize_d3d_thread() override; + void on_present() override; + void on_device_reset() override; + +public: + using D3D12RenderWorkFn = std::function; + + void submit_work_d3d12(D3D12RenderWorkFn&& work) { + std::scoped_lock _{ m_d3d12.render_work_mtx }; + m_d3d12.render_work.push_back(std::move(work)); + } + + void submit_work_d3d12(std::vector&& work) { + std::scoped_lock _{ m_d3d12.render_work_mtx }; + + if (m_d3d12.render_work.empty()) { + m_d3d12.render_work = std::move(work); + return; + } + + m_d3d12.render_work.insert(m_d3d12.render_work.end(), work.begin(), work.end()); + } + + DirectX::RenderTargetState get_default_rt_state() { + return m_d3d12.default_rt_state; + } + +private: + void render_d3d12(); + void render_d3d11(); + + struct { + std::array, 3> command_contexts{}; + std::array, 3> backbuffers{}; // For the RTV + std::vector render_work{}; + std::mutex render_work_mtx{}; + + DirectX::RenderTargetState default_rt_state{}; + } m_d3d12; +}; \ No newline at end of file diff --git a/src/mods/vr/D3D12Component.cpp b/src/mods/vr/D3D12Component.cpp index 2bb4e8066..fa7a0961a 100644 --- a/src/mods/vr/D3D12Component.cpp +++ b/src/mods/vr/D3D12Component.cpp @@ -184,12 +184,6 @@ vr::EVRCompositorError D3D12Component::on_frame(VR* vr) { } void D3D12Component::on_post_present(VR* vr) { - if (m_graphics_memory != nullptr) { - auto& hook = g_framework->get_d3d12_hook(); - auto command_queue = hook->get_command_queue(); - - m_graphics_memory->Commit(command_queue); - } } void D3D12Component::on_reset(VR* vr) { @@ -210,7 +204,6 @@ void D3D12Component::on_reset(VR* vr) { m_prev_backbuffer.Reset(); m_backbuffer_copy.reset(); m_converted_eye_tex.reset(); - m_graphics_memory.reset(); if (runtime->is_openxr() && runtime->loaded) { if (m_openxr.last_resolution[0] != vr->get_hmd_width() || m_openxr.last_resolution[1] != vr->get_hmd_height()) { @@ -243,10 +236,6 @@ void D3D12Component::setup() { return; } - if (m_graphics_memory == nullptr) { - m_graphics_memory = std::make_unique(device); - } - const auto backbuffer_desc = backbuffer->GetDesc(); m_backbuffer_is_8bit = backbuffer_desc.Format == DXGI_FORMAT_R8G8B8A8_UNORM; diff --git a/src/mods/vr/D3D12Component.hpp b/src/mods/vr/D3D12Component.hpp index afb949e22..ae2d20a8c 100644 --- a/src/mods/vr/D3D12Component.hpp +++ b/src/mods/vr/D3D12Component.hpp @@ -53,7 +53,6 @@ class D3D12Component { d3d12::TextureContext m_converted_eye_tex{}; std::array m_generic_copiers{}; - std::unique_ptr m_graphics_memory{}; std::unique_ptr m_sprite_batch{}; // Mimicking what OpenXR does.