diff --git a/Backends/RmlUi_Backend.h b/Backends/RmlUi_Backend.h index 7015c91e5..bef0d0c07 100644 --- a/Backends/RmlUi_Backend.h +++ b/Backends/RmlUi_Backend.h @@ -46,8 +46,61 @@ using KeyDownCallback = bool (*)(Rml::Context* context, Rml::Input::KeyIdentifie */ namespace Backend { +/// @brief This class defines information for not initializating renderers fully. Fully initialization means that renderer on RmlUi side will +/// initialize device, queues and other things by its own (in case of OpenGL it will load functions & will init OpenGL) so in case where you already +/// have own renderer and that renderer is initialized you want to prevent a such initialization and thus RmlUi needs to know user data. +class RmlRenderInitInfo { +public: + /// @brief This is constructor for initializing class + /// @param p_user_device ID3D12Device raw pointer (e.g. ID3D12Device* don't pass ComPtr instance!) or you pass VkDevice instance. + RmlRenderInitInfo(void* p_window_handle, void* p_user_device, bool use_vsync) : + m_is_full_initialization{true}, m_is_use_vsync{use_vsync}, m_p_native_window_handle{p_window_handle}, m_p_user_device{p_user_device} + { + RMLUI_ASSERT(p_user_device && + "if you want to initialize renderer by system you don't need to pass this parameter as nullptr just use second constructor and set " + "is_full_initialization to true!"); + } + + /// @brief This is constructor for older graphics API where don't require Devices, Queues etc... + /// @param p_window_handle HWND or similar types to OS's window handle + RmlRenderInitInfo(void* p_window_handle, bool is_full_initialization, bool use_vsync) : + m_is_full_initialization{is_full_initialization}, m_is_use_vsync{use_vsync}, m_p_native_window_handle{p_window_handle}, m_p_user_device{} + {} + + ~RmlRenderInitInfo() {} + + /// @brief Returns hidden type of ID3D12Device* or VkDevice; It is user's responsibility to pass a valid type for it otherwise system will be + /// broken and renderer couldn't be initialized! + /// @return you need to use reinterpret_cast to ID3D12Device* type or VkDevice. Depends on what user wants to initialize. + void* Get_UserDevice() { return this->m_p_user_device; } + + /// @brief Returns hidden type of native window handle (on Windows e.g. HWND) + /// @return window native type of handle (HWND etc depends on OS) + void* Get_WindowHandle() { return this->m_p_native_window_handle; } + + /// @brief returns flag of full initialization. It means that if user wants full initialization that system will create and initialize GAPI and + /// renderer by its own. In case of OpenGL system loads functions and initialize renderer, io case of DirectX 12 and Vulkan system initializes + /// Device, Queues and etc. Otherwise it is not full initialization and user passed own Device, Queues or set is_full_initialize to false in + /// the second constructor for older GAPIs like OpenGL + /// @param nothing, because it is getter + /// @return field of class m_is_full_initialization if it is true it means that user didn't passed Device, Queues from user's renderer or set + /// is_full_initialization to true + bool Is_FullInitialization(void) const { return this->m_is_full_initialization; } + + bool Is_UseVSync() const { return this->m_is_use_vsync; } + +private: + bool m_is_full_initialization; + /// @brief use vsync feature of render api (if supports at all); true = means enable feature; false = disable feature. + bool m_is_use_vsync; + /// @brief it is user's handle of OS's handle, so if it is Windows you pass HWND* here + void* m_p_native_window_handle; + /// @brief Type that hides ID3D12Device*, VkDevice or something else depends on context and which renderer user initializes + void* m_p_user_device; +}; + // Initializes the backend, including the custom system and render interfaces, and opens a window for rendering the RmlUi context. -bool Initialize(const char* window_name, int width, int height, bool allow_resize); +bool Initialize(const char* window_name, int width, int height, bool allow_resize, RmlRenderInitInfo* p_info = nullptr); // Closes the window and release all resources owned by the backend, including the system and render interfaces. void Shutdown(); diff --git a/Backends/RmlUi_Backend_SDL_GL3.cpp b/Backends/RmlUi_Backend_SDL_GL3.cpp index a67204506..0b7d2f551 100644 --- a/Backends/RmlUi_Backend_SDL_GL3.cpp +++ b/Backends/RmlUi_Backend_SDL_GL3.cpp @@ -125,7 +125,7 @@ struct BackendData { }; static Rml::UniquePtr data; -bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize) +bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize, RmlRenderInitInfo* p_info) { RMLUI_ASSERT(!data); diff --git a/Backends/RmlUi_Backend_Win32_DX12.cpp b/Backends/RmlUi_Backend_Win32_DX12.cpp new file mode 100644 index 000000000..9d46a19af --- /dev/null +++ b/Backends/RmlUi_Backend_Win32_DX12.cpp @@ -0,0 +1,471 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * 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 "RmlUi/Config/Config.h" +#include "RmlUi_Backend.h" +#include "RmlUi_Include_Windows.h" +#include "RmlUi_Platform_Win32.h" +#include "RmlUi_Renderer_DX12.h" +#include +#include +#include +#include + +/** + High DPI support using Windows Per Monitor V2 DPI awareness. + + Requires Windows 10, version 1703. + */ +#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 + #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((HANDLE)-4) +#endif +#ifndef WM_DPICHANGED + #define WM_DPICHANGED 0x02E0 +#endif + +// Declare pointers to the DPI aware Windows API functions. +using ProcSetProcessDpiAwarenessContext = BOOL(WINAPI*)(HANDLE value); +using ProcGetDpiForWindow = UINT(WINAPI*)(HWND hwnd); +using ProcAdjustWindowRectExForDpi = BOOL(WINAPI*)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi); + +static bool has_dpi_support = false; +static ProcSetProcessDpiAwarenessContext procSetProcessDpiAwarenessContext = NULL; +static ProcGetDpiForWindow procGetDpiForWindow = NULL; +static ProcAdjustWindowRectExForDpi procAdjustWindowRectExForDpi = NULL; + +// Make ourselves DPI aware on supported Windows versions. +static void InitializeDpiSupport() +{ + // Cast function pointers to void* first for MinGW not to emit errors. + procSetProcessDpiAwarenessContext = + (ProcSetProcessDpiAwarenessContext)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "SetProcessDpiAwarenessContext"); + procGetDpiForWindow = (ProcGetDpiForWindow)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "GetDpiForWindow"); + procAdjustWindowRectExForDpi = + (ProcAdjustWindowRectExForDpi)(void*)GetProcAddress(GetModuleHandle(TEXT("User32.dll")), "AdjustWindowRectExForDpi"); + + if (!has_dpi_support && procSetProcessDpiAwarenessContext != NULL && procGetDpiForWindow != NULL && procAdjustWindowRectExForDpi != NULL) + { + // Activate Per Monitor V2. + if (procSetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) + has_dpi_support = true; + } +} + +static UINT GetWindowDpi(HWND window_handle) +{ + if (has_dpi_support) + { + UINT dpi = procGetDpiForWindow(window_handle); + if (dpi != 0) + return dpi; + } + return USER_DEFAULT_SCREEN_DPI; +} + +static float GetDensityIndependentPixelRatio(HWND window_handle) +{ + return float(GetWindowDpi(window_handle)) / float(USER_DEFAULT_SCREEN_DPI); +} + +static void DisplayError(HWND window_handle, const Rml::String& msg) +{ + MessageBoxW(window_handle, RmlWin32::ConvertToUTF16(msg).c_str(), L"Backend Error", MB_OK); +} + +// Create the window but don't show it yet. Returns the pixel size of the window, which may be different than the passed size due to DPI settings. +static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize); +// Create the Win32 Vulkan surface. +// static bool CreateVulkanSurface(VkInstance instance, VkSurfaceKHR* out_surface); + +/** + Global data used by this backend. + + Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown(). + */ +struct BackendData { + SystemInterface_Win32 system_interface; + // deferred initialization, because of Render + RenderInterface_DX12* render_interface{}; + + HINSTANCE instance_handle = nullptr; + std::wstring instance_name; + HWND window_handle = nullptr; + + bool context_dimensions_dirty = true; + Rml::Vector2i window_dimensions; + bool running = true; + + // Arguments set during event processing and nulled otherwise. + Rml::Context* context = nullptr; + KeyDownCallback key_down_callback = nullptr; +}; +static Rml::UniquePtr data; + +// we need to deallocate manually +Backend::RmlRenderInitInfo* p_legacy_instance{}; + +bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize, RmlRenderInitInfo* p_info) +{ + RMLUI_ASSERT(!data); + + const std::wstring name = RmlWin32::ConvertToUTF16(Rml::String(window_name)); + + data = Rml::MakeUnique(); + + data->instance_handle = GetModuleHandle(nullptr); + data->instance_name = name; + + InitializeDpiSupport(); + + // Initialize the window but don't show it yet. + HWND window_handle = InitializeWindow(data->instance_handle, name, width, height, allow_resize); + if (!window_handle) + return false; + + data->window_handle = window_handle; + + /* + TODO: [wl] delete + As a standard we need to use namespace RmlRendererName and call from it Initialize function where we pass p_info structure + Rml::Vector extensions; + extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + if (!data->render_interface.Initialize(std::move(extensions), CreateVulkanSurface)) + { + DisplayError(window_handle, "Could not initialize Vulkan render interface."); + ::CloseWindow(window_handle); + data.reset(); + return false; + } + */ + + if (!p_info) + { + // legacy calling, by default we think that user in that case wants to initialize renderer fully + p_info = new RmlRenderInitInfo(data->window_handle, true, false); + + // remember pointer in order to delete it + p_legacy_instance = p_info; + } + + data->render_interface = RmlDX12::Initialize(nullptr, p_info); + + if (!data->render_interface) + { + DisplayError(window_handle, "Could not initialize DirectX 12 render interface."); + ::CloseWindow(window_handle); + + if (data->render_interface) + { + delete data->render_interface; + data->render_interface = nullptr; + } + + data.reset(); + return false; + } + + + + data->system_interface.SetWindow(window_handle); + data->render_interface->SetViewport(width, height); + + // Now we are ready to show the window. + ::ShowWindow(window_handle, SW_SHOW); + ::SetForegroundWindow(window_handle); + ::SetFocus(window_handle); + + return true; +} + +void Backend::Shutdown() +{ + RMLUI_ASSERT(data); + RMLUI_ASSERT(data->render_interface); + + if (p_legacy_instance) + { + delete p_legacy_instance; + p_legacy_instance = nullptr; + } + + RmlDX12::Shutdown(data->render_interface); + + if (data->render_interface) + { + delete data->render_interface; + data->render_interface = nullptr; + } + + ::DestroyWindow(data->window_handle); + ::UnregisterClassW((LPCWSTR)data->instance_name.data(), data->instance_handle); + + data.reset(); +} + +Rml::SystemInterface* Backend::GetSystemInterface() +{ + RMLUI_ASSERT(data); + return &data->system_interface; +} + +Rml::RenderInterface* Backend::GetRenderInterface() +{ + RMLUI_ASSERT(data); + return data->render_interface; +} + +static bool NextEvent(MSG& message, UINT timeout) +{ + if (timeout != 0) + { + UINT_PTR timer_id = SetTimer(NULL, NULL, timeout, NULL); + BOOL res = GetMessage(&message, NULL, 0, 0); + KillTimer(NULL, timer_id); + if (message.message != WM_TIMER || message.hwnd != nullptr || message.wParam != timer_id) + return res; + } + return PeekMessage(&message, nullptr, 0, 0, PM_REMOVE); +} + +bool Backend::ProcessEvents(Rml::Context* context, KeyDownCallback key_down_callback, bool power_save) +{ + RMLUI_ASSERT(data && context); + + // The initial window size may have been affected by system DPI settings, apply the actual pixel size and dp-ratio to the context. + if (data->context_dimensions_dirty) + { + data->context_dimensions_dirty = false; + const float dp_ratio = GetDensityIndependentPixelRatio(data->window_handle); + context->SetDimensions(data->window_dimensions); + context->SetDensityIndependentPixelRatio(dp_ratio); + } + + data->context = context; + data->key_down_callback = key_down_callback; + + MSG message; + // Process events. + bool has_message = NextEvent(message, power_save ? static_cast(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0) * 1000.0) : 0); + while (has_message || !data->render_interface->IsSwapchainValid()) + { + if (has_message) + { + // Dispatch the message to our local event handler below. + TranslateMessage(&message); + DispatchMessage(&message); + } + + // In some situations the swapchain may become invalid, such as when the window is minimized. In this state the renderer cannot accept any + // render calls. Since we don't have full control over the main loop here we may risk calls to Context::Render if we were to return. Instead, + // we trap the application inside this loop until we are able to recreate the swapchain and render again. + if (!data->render_interface->IsSwapchainValid()) + data->render_interface->RecreateSwapchain(); + + has_message = NextEvent(message, 0); + } + + data->context = nullptr; + data->key_down_callback = nullptr; + + return data->running; +} + +void Backend::RequestExit() +{ + RMLUI_ASSERT(data); + data->running = false; +} + +void Backend::BeginFrame() +{ + RMLUI_ASSERT(data); + RMLUI_ASSERT(data->render_interface); + + data->render_interface->BeginFrame(); + data->render_interface->Clear(); +} + +void Backend::PresentFrame() +{ + RMLUI_ASSERT(data); + RMLUI_ASSERT(data->render_interface); + + data->render_interface->EndFrame(); + + // Optional, used to mark frames during performance profiling. + RMLUI_FrameMark; +} + +// Local event handler for window and input events. +static LRESULT CALLBACK WindowProcedureHandler(HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param) +{ + RMLUI_ASSERT(data); + + switch (message) + { + case WM_CLOSE: + { + data->running = false; + return 0; + } + break; + case WM_SIZE: + { + const int width = LOWORD(l_param); + const int height = HIWORD(l_param); + data->window_dimensions.x = width; + data->window_dimensions.y = height; + if (data->context) + { + data->render_interface->SetViewport(width, height); + data->context->SetDimensions(data->window_dimensions); + } + return 0; + } + break; + case WM_DPICHANGED: + { + RECT* new_pos = (RECT*)l_param; + SetWindowPos(window_handle, NULL, new_pos->left, new_pos->top, new_pos->right - new_pos->left, new_pos->bottom - new_pos->top, + SWP_NOZORDER | SWP_NOACTIVATE); + if (data->context && has_dpi_support) + data->context->SetDensityIndependentPixelRatio(GetDensityIndependentPixelRatio(window_handle)); + return 0; + } + break; + case WM_KEYDOWN: + { + // Override the default key event callback to add global shortcuts for the samples. + Rml::Context* context = data->context; + KeyDownCallback key_down_callback = data->key_down_callback; + + const Rml::Input::KeyIdentifier rml_key = RmlWin32::ConvertKey((int)w_param); + const int rml_modifier = RmlWin32::GetKeyModifierState(); + const float native_dp_ratio = GetDensityIndependentPixelRatio(window_handle); + + // See if we have any global shortcuts that take priority over the context. + if (key_down_callback && !key_down_callback(context, rml_key, rml_modifier, native_dp_ratio, true)) + return 0; + // Otherwise, hand the event over to the context by calling the input handler as normal. + if (!RmlWin32::WindowProcedure(context, window_handle, message, w_param, l_param)) + return 0; + // The key was not consumed by the context either, try keyboard shortcuts of lower priority. + if (key_down_callback && !key_down_callback(context, rml_key, rml_modifier, native_dp_ratio, false)) + return 0; + return 0; + } + break; + default: + { + // Submit it to the platform handler for default input handling. + if (!RmlWin32::WindowProcedure(data->context, window_handle, message, w_param, l_param)) + return 0; + } + break; + } + + // All unhandled messages go to DefWindowProc. + return DefWindowProc(window_handle, message, w_param, l_param); +} + +static HWND InitializeWindow(HINSTANCE instance_handle, const std::wstring& name, int& inout_width, int& inout_height, bool allow_resize) +{ + // Fill out the window class struct. + WNDCLASSW window_class; + window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + window_class.lpfnWndProc = &WindowProcedureHandler; // Attach our local event handler. + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = instance_handle; + window_class.hIcon = LoadIcon(nullptr, IDI_WINLOGO); + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.hbrBackground = nullptr; + window_class.lpszMenuName = nullptr; + window_class.lpszClassName = name.data(); + + if (!RegisterClassW(&window_class)) + { + DisplayError(NULL, "Could not register window class."); + return nullptr; + } + + HWND window_handle = CreateWindowExW(WS_EX_APPWINDOW | WS_EX_WINDOWEDGE, + name.data(), // Window class name. + name.data(), WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW, 0, 0, // Window position. + 0, 0, // Window size. + nullptr, nullptr, instance_handle, nullptr); + + if (!window_handle) + { + DisplayError(NULL, "Could not create window."); + return nullptr; + } + + UINT window_dpi = GetWindowDpi(window_handle); + inout_width = (inout_width * (int)window_dpi) / USER_DEFAULT_SCREEN_DPI; + inout_height = (inout_height * (int)window_dpi) / USER_DEFAULT_SCREEN_DPI; + + DWORD style = (allow_resize ? WS_OVERLAPPEDWINDOW : (WS_OVERLAPPEDWINDOW & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX)); + DWORD extended_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; + + // Adjust the window size to take the edges into account. + RECT window_rect; + window_rect.top = 0; + window_rect.left = 0; + window_rect.right = inout_width; + window_rect.bottom = inout_height; + if (has_dpi_support) + procAdjustWindowRectExForDpi(&window_rect, style, FALSE, extended_style, window_dpi); + else + AdjustWindowRectEx(&window_rect, style, FALSE, extended_style); + + SetWindowLong(window_handle, GWL_EXSTYLE, extended_style); + SetWindowLong(window_handle, GWL_STYLE, style); + + // Resize the window. + SetWindowPos(window_handle, HWND_TOP, 0, 0, window_rect.right - window_rect.left, window_rect.bottom - window_rect.top, SWP_NOACTIVATE); + + return window_handle; +} + +/* +bool CreateVulkanSurface(VkInstance instance, VkSurfaceKHR* out_surface) +{ + VkWin32SurfaceCreateInfoKHR info = {}; + info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + info.hinstance = GetModuleHandle(NULL); + info.hwnd = data->window_handle; + + VkResult status = vkCreateWin32SurfaceKHR(instance, &info, nullptr, out_surface); + + bool result = (status == VK_SUCCESS); + RMLUI_VK_ASSERTMSG(result, "Failed to create Win32 Vulkan surface"); + return result; +} +*/ \ No newline at end of file diff --git a/Backends/RmlUi_DirectX/.clang-format b/Backends/RmlUi_DirectX/.clang-format new file mode 100644 index 000000000..47a38a93f --- /dev/null +++ b/Backends/RmlUi_DirectX/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: Never diff --git a/Backends/RmlUi_DirectX/D3D12MemAlloc.cpp b/Backends/RmlUi_DirectX/D3D12MemAlloc.cpp new file mode 100644 index 000000000..68e92ad9d --- /dev/null +++ b/Backends/RmlUi_DirectX/D3D12MemAlloc.cpp @@ -0,0 +1,10247 @@ +// +// Copyright (c) 2019-2022 Advanced Micro Devices, Inc. All rights reserved. +// +// 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 "D3D12MemAlloc.h" + +#include +#include +#include +#include +#include +#include +#include // for _aligned_malloc, _aligned_free +#ifndef _WIN32 + #include +#endif + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Configuration Begin +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +#ifndef _D3D12MA_CONFIGURATION + +#ifdef _WIN32 + #if !defined(WINVER) || WINVER < 0x0600 + #error Required at least WinAPI version supporting: client = Windows Vista, server = Windows Server 2008. + #endif +#endif + +#ifndef D3D12MA_SORT + #define D3D12MA_SORT(beg, end, cmp) std::sort(beg, end, cmp) +#endif + +#ifndef D3D12MA_D3D12_HEADERS_ALREADY_INCLUDED + #include + #if D3D12MA_DXGI_1_4 + #include + #endif +#endif + +#ifndef D3D12MA_ASSERT + #include + #define D3D12MA_ASSERT(cond) assert(cond) +#endif + +// Assert that will be called very often, like inside data structures e.g. operator[]. +// Making it non-empty can make program slow. +#ifndef D3D12MA_HEAVY_ASSERT + #ifdef _DEBUG + #define D3D12MA_HEAVY_ASSERT(expr) //D3D12MA_ASSERT(expr) + #else + #define D3D12MA_HEAVY_ASSERT(expr) + #endif +#endif + +#ifndef D3D12MA_DEBUG_ALIGNMENT + /* + Minimum alignment of all allocations, in bytes. + Set to more than 1 for debugging purposes only. Must be power of two. + */ + #define D3D12MA_DEBUG_ALIGNMENT (1) +#endif + +#ifndef D3D12MA_DEBUG_MARGIN + // Minimum margin before and after every allocation, in bytes. + // Set nonzero for debugging purposes only. + #define D3D12MA_DEBUG_MARGIN (0) +#endif + +#ifndef D3D12MA_DEBUG_GLOBAL_MUTEX + /* + Set this to 1 for debugging purposes only, to enable single mutex protecting all + entry calls to the library. Can be useful for debugging multithreading issues. + */ + #define D3D12MA_DEBUG_GLOBAL_MUTEX (0) +#endif + +/* +Define this macro for debugging purposes only to force specific D3D12_RESOURCE_HEAP_TIER, +especially to test compatibility with D3D12_RESOURCE_HEAP_TIER_1 on modern GPUs. +*/ +//#define D3D12MA_FORCE_RESOURCE_HEAP_TIER D3D12_RESOURCE_HEAP_TIER_1 + +#ifndef D3D12MA_DEFAULT_BLOCK_SIZE + /// Default size of a block allocated as single ID3D12Heap. + #define D3D12MA_DEFAULT_BLOCK_SIZE (64ull * 1024 * 1024) +#endif + +#endif // _D3D12MA_CONFIGURATION +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Configuration End +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +#define D3D12MA_IID_PPV_ARGS(ppType) __uuidof(**(ppType)), reinterpret_cast(ppType) + +namespace D3D12MA +{ +static constexpr UINT HEAP_TYPE_COUNT = 4; +static constexpr UINT STANDARD_HEAP_TYPE_COUNT = 3; // Only DEFAULT, UPLOAD, READBACK. +static constexpr UINT DEFAULT_POOL_MAX_COUNT = 9; +static const UINT NEW_BLOCK_SIZE_SHIFT_MAX = 3; +// Minimum size of a free suballocation to register it in the free suballocation collection. +static const UINT64 MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER = 16; + +static const WCHAR* const HeapTypeNames[] = +{ + L"DEFAULT", + L"UPLOAD", + L"READBACK", + L"CUSTOM", +}; + +static const D3D12_HEAP_FLAGS RESOURCE_CLASS_HEAP_FLAGS = + D3D12_HEAP_FLAG_DENY_BUFFERS | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES; + +#ifndef _D3D12MA_ENUM_DECLARATIONS + +// Local copy of this enum, as it is provided only by , so it may not be available. +enum DXGI_MEMORY_SEGMENT_GROUP_COPY +{ + DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY = 0, + DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY = 1, + DXGI_MEMORY_SEGMENT_GROUP_COUNT +}; + +enum class ResourceClass +{ + Unknown, Buffer, Non_RT_DS_Texture, RT_DS_Texture +}; + +enum SuballocationType +{ + SUBALLOCATION_TYPE_FREE = 0, + SUBALLOCATION_TYPE_ALLOCATION = 1, +}; + +#endif // _D3D12MA_ENUM_DECLARATIONS + + +#ifndef _D3D12MA_FUNCTIONS + +static void* DefaultAllocate(size_t Size, size_t Alignment, void* /*pPrivateData*/) +{ +#ifdef _WIN32 + return _aligned_malloc(Size, Alignment); +#else + return aligned_alloc(Alignment, Size); +#endif +} +static void DefaultFree(void* pMemory, void* /*pPrivateData*/) +{ +#ifdef _WIN32 + return _aligned_free(pMemory); +#else + return free(pMemory); +#endif +} + +static void* Malloc(const ALLOCATION_CALLBACKS& allocs, size_t size, size_t alignment) +{ + void* const result = (*allocs.pAllocate)(size, alignment, allocs.pPrivateData); + D3D12MA_ASSERT(result); + return result; +} +static void Free(const ALLOCATION_CALLBACKS& allocs, void* memory) +{ + (*allocs.pFree)(memory, allocs.pPrivateData); +} + +template +static T* Allocate(const ALLOCATION_CALLBACKS& allocs) +{ + return (T*)Malloc(allocs, sizeof(T), __alignof(T)); +} +template +static T* AllocateArray(const ALLOCATION_CALLBACKS& allocs, size_t count) +{ + return (T*)Malloc(allocs, sizeof(T) * count, __alignof(T)); +} + +#define D3D12MA_NEW(allocs, type) new(D3D12MA::Allocate(allocs))(type) +#define D3D12MA_NEW_ARRAY(allocs, type, count) new(D3D12MA::AllocateArray((allocs), (count)))(type) + +template +void D3D12MA_DELETE(const ALLOCATION_CALLBACKS& allocs, T* memory) +{ + if (memory) + { + memory->~T(); + Free(allocs, memory); + } +} +template +void D3D12MA_DELETE_ARRAY(const ALLOCATION_CALLBACKS& allocs, T* memory, size_t count) +{ + if (memory) + { + for (size_t i = count; i--; ) + { + memory[i].~T(); + } + Free(allocs, memory); + } +} + +static void SetupAllocationCallbacks(ALLOCATION_CALLBACKS& outAllocs, const ALLOCATION_CALLBACKS* allocationCallbacks) +{ + if (allocationCallbacks) + { + outAllocs = *allocationCallbacks; + D3D12MA_ASSERT(outAllocs.pAllocate != NULL && outAllocs.pFree != NULL); + } + else + { + outAllocs.pAllocate = &DefaultAllocate; + outAllocs.pFree = &DefaultFree; + outAllocs.pPrivateData = NULL; + } +} + +#define SAFE_RELEASE(ptr) do { if(ptr) { (ptr)->Release(); (ptr) = NULL; } } while(false) + +#define D3D12MA_VALIDATE(cond) do { if(!(cond)) { \ + D3D12MA_ASSERT(0 && "Validation failed: " #cond); \ + return false; \ +} } while(false) + +template +static T D3D12MA_MIN(const T& a, const T& b) { return a <= b ? a : b; } +template +static T D3D12MA_MAX(const T& a, const T& b) { return a <= b ? b : a; } + +template +static void D3D12MA_SWAP(T& a, T& b) { T tmp = a; a = b; b = tmp; } + +// Scans integer for index of first nonzero bit from the Least Significant Bit (LSB). If mask is 0 then returns UINT8_MAX +static UINT8 BitScanLSB(UINT64 mask) +{ +#if defined(_MSC_VER) && defined(_WIN64) + unsigned long pos; + if (_BitScanForward64(&pos, mask)) + return static_cast(pos); + return UINT8_MAX; +#elif defined __GNUC__ || defined __clang__ + return static_cast(__builtin_ffsll(mask)) - 1U; +#else + UINT8 pos = 0; + UINT64 bit = 1; + do + { + if (mask & bit) + return pos; + bit <<= 1; + } while (pos++ < 63); + return UINT8_MAX; +#endif +} +// Scans integer for index of first nonzero bit from the Least Significant Bit (LSB). If mask is 0 then returns UINT8_MAX +static UINT8 BitScanLSB(UINT32 mask) +{ +#ifdef _MSC_VER + unsigned long pos; + if (_BitScanForward(&pos, mask)) + return static_cast(pos); + return UINT8_MAX; +#elif defined __GNUC__ || defined __clang__ + return static_cast(__builtin_ffs(mask)) - 1U; +#else + UINT8 pos = 0; + UINT32 bit = 1; + do + { + if (mask & bit) + return pos; + bit <<= 1; + } while (pos++ < 31); + return UINT8_MAX; +#endif +} + +// Scans integer for index of first nonzero bit from the Most Significant Bit (MSB). If mask is 0 then returns UINT8_MAX +static UINT8 BitScanMSB(UINT64 mask) +{ +#if defined(_MSC_VER) && defined(_WIN64) + unsigned long pos; + if (_BitScanReverse64(&pos, mask)) + return static_cast(pos); +#elif defined __GNUC__ || defined __clang__ + if (mask) + return 63 - static_cast(__builtin_clzll(mask)); +#else + UINT8 pos = 63; + UINT64 bit = 1ULL << 63; + do + { + if (mask & bit) + return pos; + bit >>= 1; + } while (pos-- > 0); +#endif + return UINT8_MAX; +} +// Scans integer for index of first nonzero bit from the Most Significant Bit (MSB). If mask is 0 then returns UINT8_MAX +static UINT8 BitScanMSB(UINT32 mask) +{ +#ifdef _MSC_VER + unsigned long pos; + if (_BitScanReverse(&pos, mask)) + return static_cast(pos); +#elif defined __GNUC__ || defined __clang__ + if (mask) + return 31 - static_cast(__builtin_clz(mask)); +#else + UINT8 pos = 31; + UINT32 bit = 1UL << 31; + do + { + if (mask & bit) + return pos; + bit >>= 1; + } while (pos-- > 0); +#endif + return UINT8_MAX; +} + +/* +Returns true if given number is a power of two. +T must be unsigned integer number or signed integer but always nonnegative. +For 0 returns true. +*/ +template +static bool IsPow2(T x) { return (x & (x - 1)) == 0; } + +// Aligns given value up to nearest multiply of align value. For example: AlignUp(11, 8) = 16. +// Use types like UINT, uint64_t as T. +template +static T AlignUp(T val, T alignment) +{ + D3D12MA_HEAVY_ASSERT(IsPow2(alignment)); + return (val + alignment - 1) & ~(alignment - 1); +} +// Aligns given value down to nearest multiply of align value. For example: AlignUp(11, 8) = 8. +// Use types like UINT, uint64_t as T. +template +static T AlignDown(T val, T alignment) +{ + D3D12MA_HEAVY_ASSERT(IsPow2(alignment)); + return val & ~(alignment - 1); +} + +// Division with mathematical rounding to nearest number. +template +static T RoundDiv(T x, T y) { return (x + (y / (T)2)) / y; } +template +static T DivideRoundingUp(T x, T y) { return (x + y - 1) / y; } + +static WCHAR HexDigitToChar(UINT8 digit) +{ + if(digit < 10) + return L'0' + digit; + else + return L'A' + (digit - 10); +} + +/* +Performs binary search and returns iterator to first element that is greater or +equal to `key`, according to comparison `cmp`. + +Cmp should return true if first argument is less than second argument. + +Returned value is the found element, if present in the collection or place where +new element with value (key) should be inserted. +*/ +template +static IterT BinaryFindFirstNotLess(IterT beg, IterT end, const KeyT& key, const CmpLess& cmp) +{ + size_t down = 0, up = (end - beg); + while (down < up) + { + const size_t mid = (down + up) / 2; + if (cmp(*(beg + mid), key)) + { + down = mid + 1; + } + else + { + up = mid; + } + } + return beg + down; +} + +/* +Performs binary search and returns iterator to an element that is equal to `key`, +according to comparison `cmp`. + +Cmp should return true if first argument is less than second argument. + +Returned value is the found element, if present in the collection or end if not +found. +*/ +template +static IterT BinaryFindSorted(const IterT& beg, const IterT& end, const KeyT& value, const CmpLess& cmp) +{ + IterT it = BinaryFindFirstNotLess(beg, end, value, cmp); + if (it == end || + (!cmp(*it, value) && !cmp(value, *it))) + { + return it; + } + return end; +} + +static UINT HeapTypeToIndex(D3D12_HEAP_TYPE type) +{ + switch (type) + { + case D3D12_HEAP_TYPE_DEFAULT: return 0; + case D3D12_HEAP_TYPE_UPLOAD: return 1; + case D3D12_HEAP_TYPE_READBACK: return 2; + case D3D12_HEAP_TYPE_CUSTOM: return 3; + default: D3D12MA_ASSERT(0); return UINT_MAX; + } +} + +static D3D12_HEAP_TYPE IndexToHeapType(UINT heapTypeIndex) +{ + D3D12MA_ASSERT(heapTypeIndex < 4); + // D3D12_HEAP_TYPE_DEFAULT starts at 1. + return (D3D12_HEAP_TYPE)(heapTypeIndex + 1); +} + +static UINT64 HeapFlagsToAlignment(D3D12_HEAP_FLAGS flags, bool denyMsaaTextures) +{ + /* + Documentation of D3D12_HEAP_DESC structure says: + + - D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT defined as 64KB. + - D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT defined as 4MB. An + application must decide whether the heap will contain multi-sample + anti-aliasing (MSAA), in which case, the application must choose [this flag]. + + https://docs.microsoft.com/en-us/windows/desktop/api/d3d12/ns-d3d12-d3d12_heap_desc + */ + + if (denyMsaaTextures) + return D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; + + const D3D12_HEAP_FLAGS denyAllTexturesFlags = + D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES; + const bool canContainAnyTextures = + (flags & denyAllTexturesFlags) != denyAllTexturesFlags; + return canContainAnyTextures ? + D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT : D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT; +} + +static ResourceClass HeapFlagsToResourceClass(D3D12_HEAP_FLAGS heapFlags) +{ + const bool allowBuffers = (heapFlags & D3D12_HEAP_FLAG_DENY_BUFFERS) == 0; + const bool allowRtDsTextures = (heapFlags & D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES) == 0; + const bool allowNonRtDsTextures = (heapFlags & D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES) == 0; + + const uint8_t allowedGroupCount = (allowBuffers ? 1 : 0) + (allowRtDsTextures ? 1 : 0) + (allowNonRtDsTextures ? 1 : 0); + if (allowedGroupCount != 1) + return ResourceClass::Unknown; + + if (allowRtDsTextures) + return ResourceClass::RT_DS_Texture; + if (allowNonRtDsTextures) + return ResourceClass::Non_RT_DS_Texture; + return ResourceClass::Buffer; +} + +static bool IsHeapTypeStandard(D3D12_HEAP_TYPE type) +{ + return type == D3D12_HEAP_TYPE_DEFAULT || + type == D3D12_HEAP_TYPE_UPLOAD || + type == D3D12_HEAP_TYPE_READBACK; +} + +static D3D12_HEAP_PROPERTIES StandardHeapTypeToHeapProperties(D3D12_HEAP_TYPE type) +{ + D3D12MA_ASSERT(IsHeapTypeStandard(type)); + D3D12_HEAP_PROPERTIES result = {}; + result.Type = type; + return result; +} + +static bool IsFormatCompressed(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + return true; + default: + return false; + } +} + +// Only some formats are supported. For others it returns 0. +static UINT GetBitsPerPixel(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_R32G32B32A32_TYPELESS: + case DXGI_FORMAT_R32G32B32A32_FLOAT: + case DXGI_FORMAT_R32G32B32A32_UINT: + case DXGI_FORMAT_R32G32B32A32_SINT: + return 128; + case DXGI_FORMAT_R32G32B32_TYPELESS: + case DXGI_FORMAT_R32G32B32_FLOAT: + case DXGI_FORMAT_R32G32B32_UINT: + case DXGI_FORMAT_R32G32B32_SINT: + return 96; + case DXGI_FORMAT_R16G16B16A16_TYPELESS: + case DXGI_FORMAT_R16G16B16A16_FLOAT: + case DXGI_FORMAT_R16G16B16A16_UNORM: + case DXGI_FORMAT_R16G16B16A16_UINT: + case DXGI_FORMAT_R16G16B16A16_SNORM: + case DXGI_FORMAT_R16G16B16A16_SINT: + return 64; + case DXGI_FORMAT_R32G32_TYPELESS: + case DXGI_FORMAT_R32G32_FLOAT: + case DXGI_FORMAT_R32G32_UINT: + case DXGI_FORMAT_R32G32_SINT: + return 64; + case DXGI_FORMAT_R32G8X24_TYPELESS: + case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: + case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: + case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT: + return 64; + case DXGI_FORMAT_R10G10B10A2_TYPELESS: + case DXGI_FORMAT_R10G10B10A2_UNORM: + case DXGI_FORMAT_R10G10B10A2_UINT: + case DXGI_FORMAT_R11G11B10_FLOAT: + return 32; + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + case DXGI_FORMAT_R8G8B8A8_UINT: + case DXGI_FORMAT_R8G8B8A8_SNORM: + case DXGI_FORMAT_R8G8B8A8_SINT: + return 32; + case DXGI_FORMAT_R16G16_TYPELESS: + case DXGI_FORMAT_R16G16_FLOAT: + case DXGI_FORMAT_R16G16_UNORM: + case DXGI_FORMAT_R16G16_UINT: + case DXGI_FORMAT_R16G16_SNORM: + case DXGI_FORMAT_R16G16_SINT: + return 32; + case DXGI_FORMAT_R32_TYPELESS: + case DXGI_FORMAT_D32_FLOAT: + case DXGI_FORMAT_R32_FLOAT: + case DXGI_FORMAT_R32_UINT: + case DXGI_FORMAT_R32_SINT: + return 32; + case DXGI_FORMAT_R24G8_TYPELESS: + case DXGI_FORMAT_D24_UNORM_S8_UINT: + case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: + case DXGI_FORMAT_X24_TYPELESS_G8_UINT: + return 32; + case DXGI_FORMAT_R8G8_TYPELESS: + case DXGI_FORMAT_R8G8_UNORM: + case DXGI_FORMAT_R8G8_UINT: + case DXGI_FORMAT_R8G8_SNORM: + case DXGI_FORMAT_R8G8_SINT: + return 16; + case DXGI_FORMAT_R16_TYPELESS: + case DXGI_FORMAT_R16_FLOAT: + case DXGI_FORMAT_D16_UNORM: + case DXGI_FORMAT_R16_UNORM: + case DXGI_FORMAT_R16_UINT: + case DXGI_FORMAT_R16_SNORM: + case DXGI_FORMAT_R16_SINT: + return 16; + case DXGI_FORMAT_R8_TYPELESS: + case DXGI_FORMAT_R8_UNORM: + case DXGI_FORMAT_R8_UINT: + case DXGI_FORMAT_R8_SNORM: + case DXGI_FORMAT_R8_SINT: + case DXGI_FORMAT_A8_UNORM: + return 8; + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + return 4; + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + return 8; + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + return 8; + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + return 4; + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + return 8; + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + return 8; + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + return 8; + default: + return 0; + } +} + +template +static ResourceClass ResourceDescToResourceClass(const D3D12_RESOURCE_DESC_T& resDesc) +{ + if (resDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) + return ResourceClass::Buffer; + // Else: it's surely a texture. + const bool isRenderTargetOrDepthStencil = + (resDesc.Flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) != 0; + return isRenderTargetOrDepthStencil ? ResourceClass::RT_DS_Texture : ResourceClass::Non_RT_DS_Texture; +} + +// This algorithm is overly conservative. +template +static bool CanUseSmallAlignment(const D3D12_RESOURCE_DESC_T& resourceDesc) +{ + if (resourceDesc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D) + return false; + if ((resourceDesc.Flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) != 0) + return false; + if (resourceDesc.SampleDesc.Count > 1) + return false; + if (resourceDesc.DepthOrArraySize != 1) + return false; + + UINT sizeX = (UINT)resourceDesc.Width; + UINT sizeY = resourceDesc.Height; + UINT bitsPerPixel = GetBitsPerPixel(resourceDesc.Format); + if (bitsPerPixel == 0) + return false; + + if (IsFormatCompressed(resourceDesc.Format)) + { + sizeX = DivideRoundingUp(sizeX, 4u); + sizeY = DivideRoundingUp(sizeY, 4u); + bitsPerPixel *= 16; + } + + UINT tileSizeX = 0, tileSizeY = 0; + switch (bitsPerPixel) + { + case 8: tileSizeX = 64; tileSizeY = 64; break; + case 16: tileSizeX = 64; tileSizeY = 32; break; + case 32: tileSizeX = 32; tileSizeY = 32; break; + case 64: tileSizeX = 32; tileSizeY = 16; break; + case 128: tileSizeX = 16; tileSizeY = 16; break; + default: return false; + } + + const UINT tileCount = DivideRoundingUp(sizeX, tileSizeX) * DivideRoundingUp(sizeY, tileSizeY); + return tileCount <= 16; +} + +static bool ValidateAllocateMemoryParameters( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + Allocation** ppAllocation) +{ + return pAllocDesc && + pAllocInfo && + ppAllocation && + (pAllocInfo->Alignment == 0 || + pAllocInfo->Alignment == D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT || + pAllocInfo->Alignment == D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT) && + pAllocInfo->SizeInBytes != 0 && + pAllocInfo->SizeInBytes % (64ull * 1024) == 0; +} + +#endif // _D3D12MA_FUNCTIONS + +#ifndef _D3D12MA_STATISTICS_FUNCTIONS + +static void ClearStatistics(Statistics& outStats) +{ + outStats.BlockCount = 0; + outStats.AllocationCount = 0; + outStats.BlockBytes = 0; + outStats.AllocationBytes = 0; +} + +static void ClearDetailedStatistics(DetailedStatistics& outStats) +{ + ClearStatistics(outStats.Stats); + outStats.UnusedRangeCount = 0; + outStats.AllocationSizeMin = UINT64_MAX; + outStats.AllocationSizeMax = 0; + outStats.UnusedRangeSizeMin = UINT64_MAX; + outStats.UnusedRangeSizeMax = 0; +} + +static void AddStatistics(Statistics& inoutStats, const Statistics& src) +{ + inoutStats.BlockCount += src.BlockCount; + inoutStats.AllocationCount += src.AllocationCount; + inoutStats.BlockBytes += src.BlockBytes; + inoutStats.AllocationBytes += src.AllocationBytes; +} + +static void AddDetailedStatistics(DetailedStatistics& inoutStats, const DetailedStatistics& src) +{ + AddStatistics(inoutStats.Stats, src.Stats); + inoutStats.UnusedRangeCount += src.UnusedRangeCount; + inoutStats.AllocationSizeMin = D3D12MA_MIN(inoutStats.AllocationSizeMin, src.AllocationSizeMin); + inoutStats.AllocationSizeMax = D3D12MA_MAX(inoutStats.AllocationSizeMax, src.AllocationSizeMax); + inoutStats.UnusedRangeSizeMin = D3D12MA_MIN(inoutStats.UnusedRangeSizeMin, src.UnusedRangeSizeMin); + inoutStats.UnusedRangeSizeMax = D3D12MA_MAX(inoutStats.UnusedRangeSizeMax, src.UnusedRangeSizeMax); +} + +static void AddDetailedStatisticsAllocation(DetailedStatistics& inoutStats, UINT64 size) +{ + inoutStats.Stats.AllocationCount++; + inoutStats.Stats.AllocationBytes += size; + inoutStats.AllocationSizeMin = D3D12MA_MIN(inoutStats.AllocationSizeMin, size); + inoutStats.AllocationSizeMax = D3D12MA_MAX(inoutStats.AllocationSizeMax, size); +} + +static void AddDetailedStatisticsUnusedRange(DetailedStatistics& inoutStats, UINT64 size) +{ + inoutStats.UnusedRangeCount++; + inoutStats.UnusedRangeSizeMin = D3D12MA_MIN(inoutStats.UnusedRangeSizeMin, size); + inoutStats.UnusedRangeSizeMax = D3D12MA_MAX(inoutStats.UnusedRangeSizeMax, size); +} + +#endif // _D3D12MA_STATISTICS_FUNCTIONS + + +#ifndef _D3D12MA_MUTEX + +#ifndef D3D12MA_MUTEX + class Mutex + { + public: + void Lock() { m_Mutex.lock(); } + void Unlock() { m_Mutex.unlock(); } + + private: + std::mutex m_Mutex; + }; + #define D3D12MA_MUTEX Mutex +#endif + +#ifndef D3D12MA_RW_MUTEX +#ifdef _WIN32 + class RWMutex + { + public: + RWMutex() { InitializeSRWLock(&m_Lock); } + void LockRead() { AcquireSRWLockShared(&m_Lock); } + void UnlockRead() { ReleaseSRWLockShared(&m_Lock); } + void LockWrite() { AcquireSRWLockExclusive(&m_Lock); } + void UnlockWrite() { ReleaseSRWLockExclusive(&m_Lock); } + + private: + SRWLOCK m_Lock; + }; +#else // #ifdef _WIN32 + class RWMutex + { + public: + RWMutex() {} + void LockRead() { m_Mutex.lock_shared(); } + void UnlockRead() { m_Mutex.unlock_shared(); } + void LockWrite() { m_Mutex.lock(); } + void UnlockWrite() { m_Mutex.unlock(); } + + private: + std::shared_timed_mutex m_Mutex; + }; +#endif // #ifdef _WIN32 + #define D3D12MA_RW_MUTEX RWMutex +#endif // #ifndef D3D12MA_RW_MUTEX + +// Helper RAII class to lock a mutex in constructor and unlock it in destructor (at the end of scope). +struct MutexLock +{ + D3D12MA_CLASS_NO_COPY(MutexLock); +public: + MutexLock(D3D12MA_MUTEX& mutex, bool useMutex = true) : + m_pMutex(useMutex ? &mutex : NULL) + { + if (m_pMutex) m_pMutex->Lock(); + } + ~MutexLock() { if (m_pMutex) m_pMutex->Unlock(); } + +private: + D3D12MA_MUTEX* m_pMutex; +}; + +// Helper RAII class to lock a RW mutex in constructor and unlock it in destructor (at the end of scope), for reading. +struct MutexLockRead +{ + D3D12MA_CLASS_NO_COPY(MutexLockRead); +public: + MutexLockRead(D3D12MA_RW_MUTEX& mutex, bool useMutex) + : m_pMutex(useMutex ? &mutex : NULL) + { + if(m_pMutex) + { + m_pMutex->LockRead(); + } + } + ~MutexLockRead() { if (m_pMutex) m_pMutex->UnlockRead(); } + +private: + D3D12MA_RW_MUTEX* m_pMutex; +}; + +// Helper RAII class to lock a RW mutex in constructor and unlock it in destructor (at the end of scope), for writing. +struct MutexLockWrite +{ + D3D12MA_CLASS_NO_COPY(MutexLockWrite); +public: + MutexLockWrite(D3D12MA_RW_MUTEX& mutex, bool useMutex) + : m_pMutex(useMutex ? &mutex : NULL) + { + if (m_pMutex) m_pMutex->LockWrite(); + } + ~MutexLockWrite() { if (m_pMutex) m_pMutex->UnlockWrite(); } + +private: + D3D12MA_RW_MUTEX* m_pMutex; +}; + +#if D3D12MA_DEBUG_GLOBAL_MUTEX + static D3D12MA_MUTEX g_DebugGlobalMutex; + #define D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK MutexLock debugGlobalMutexLock(g_DebugGlobalMutex, true); +#else + #define D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK +#endif +#endif // _D3D12MA_MUTEX + +#ifndef _D3D12MA_VECTOR +/* +Dynamically resizing continuous array. Class with interface similar to std::vector. +T must be POD because constructors and destructors are not called and memcpy is +used for these objects. +*/ +template +class Vector +{ +public: + using value_type = T; + using iterator = T*; + + // allocationCallbacks externally owned, must outlive this object. + Vector(const ALLOCATION_CALLBACKS& allocationCallbacks); + Vector(size_t count, const ALLOCATION_CALLBACKS& allocationCallbacks); + Vector(const Vector& src); + ~Vector(); + + const ALLOCATION_CALLBACKS& GetAllocs() const { return m_AllocationCallbacks; } + bool empty() const { return m_Count == 0; } + size_t size() const { return m_Count; } + T* data() { return m_pArray; } + const T* data() const { return m_pArray; } + void clear(bool freeMemory = false) { resize(0, freeMemory); } + + iterator begin() { return m_pArray; } + iterator end() { return m_pArray + m_Count; } + iterator rend() { return begin() - 1; } + iterator rbegin() { return end() - 1; } + + const iterator cbegin() const { return m_pArray; } + const iterator cend() const { return m_pArray + m_Count; } + const iterator crbegin() const { return cend() - 1; } + const iterator crend() const { return cbegin() - 1; } + + void push_front(const T& src) { insert(0, src); } + void push_back(const T& src); + void pop_front(); + void pop_back(); + + T& front(); + T& back(); + const T& front() const; + const T& back() const; + + void reserve(size_t newCapacity, bool freeMemory = false); + void resize(size_t newCount, bool freeMemory = false); + void insert(size_t index, const T& src); + void remove(size_t index); + + template + size_t InsertSorted(const T& value, const CmpLess& cmp); + template + bool RemoveSorted(const T& value, const CmpLess& cmp); + + Vector& operator=(const Vector& rhs); + T& operator[](size_t index); + const T& operator[](size_t index) const; + +private: + const ALLOCATION_CALLBACKS& m_AllocationCallbacks; + T* m_pArray; + size_t m_Count; + size_t m_Capacity; +}; + +#ifndef _D3D12MA_VECTOR_FUNCTIONS +template +Vector::Vector(const ALLOCATION_CALLBACKS& allocationCallbacks) + : m_AllocationCallbacks(allocationCallbacks), + m_pArray(NULL), + m_Count(0), + m_Capacity(0) {} + +template +Vector::Vector(size_t count, const ALLOCATION_CALLBACKS& allocationCallbacks) + : m_AllocationCallbacks(allocationCallbacks), + m_pArray(count ? AllocateArray(allocationCallbacks, count) : NULL), + m_Count(count), + m_Capacity(count) {} + +template +Vector::Vector(const Vector& src) + : m_AllocationCallbacks(src.m_AllocationCallbacks), + m_pArray(src.m_Count ? AllocateArray(src.m_AllocationCallbacks, src.m_Count) : NULL), + m_Count(src.m_Count), + m_Capacity(src.m_Count) +{ + if (m_Count > 0) + { + memcpy(m_pArray, src.m_pArray, m_Count * sizeof(T)); + } +} + +template +Vector::~Vector() +{ + Free(m_AllocationCallbacks, m_pArray); +} + +template +void Vector::push_back(const T& src) +{ + const size_t newIndex = size(); + resize(newIndex + 1); + m_pArray[newIndex] = src; +} + +template +void Vector::pop_front() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + remove(0); +} + +template +void Vector::pop_back() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + resize(size() - 1); +} + +template +T& Vector::front() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + return m_pArray[0]; +} + +template +T& Vector::back() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + return m_pArray[m_Count - 1]; +} + +template +const T& Vector::front() const +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + return m_pArray[0]; +} + +template +const T& Vector::back() const +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + return m_pArray[m_Count - 1]; +} + +template +void Vector::reserve(size_t newCapacity, bool freeMemory) +{ + newCapacity = D3D12MA_MAX(newCapacity, m_Count); + + if ((newCapacity < m_Capacity) && !freeMemory) + { + newCapacity = m_Capacity; + } + + if (newCapacity != m_Capacity) + { + T* const newArray = newCapacity ? AllocateArray(m_AllocationCallbacks, newCapacity) : NULL; + if (m_Count != 0) + { + memcpy(newArray, m_pArray, m_Count * sizeof(T)); + } + Free(m_AllocationCallbacks, m_pArray); + m_Capacity = newCapacity; + m_pArray = newArray; + } +} + +template +void Vector::resize(size_t newCount, bool freeMemory) +{ + size_t newCapacity = m_Capacity; + if (newCount > m_Capacity) + { + newCapacity = D3D12MA_MAX(newCount, D3D12MA_MAX(m_Capacity * 3 / 2, (size_t)8)); + } + else if (freeMemory) + { + newCapacity = newCount; + } + + if (newCapacity != m_Capacity) + { + T* const newArray = newCapacity ? AllocateArray(m_AllocationCallbacks, newCapacity) : NULL; + const size_t elementsToCopy = D3D12MA_MIN(m_Count, newCount); + if (elementsToCopy != 0) + { + memcpy(newArray, m_pArray, elementsToCopy * sizeof(T)); + } + Free(m_AllocationCallbacks, m_pArray); + m_Capacity = newCapacity; + m_pArray = newArray; + } + + m_Count = newCount; +} + +template +void Vector::insert(size_t index, const T& src) +{ + D3D12MA_HEAVY_ASSERT(index <= m_Count); + const size_t oldCount = size(); + resize(oldCount + 1); + if (index < oldCount) + { + memmove(m_pArray + (index + 1), m_pArray + index, (oldCount - index) * sizeof(T)); + } + m_pArray[index] = src; +} + +template +void Vector::remove(size_t index) +{ + D3D12MA_HEAVY_ASSERT(index < m_Count); + const size_t oldCount = size(); + if (index < oldCount - 1) + { + memmove(m_pArray + index, m_pArray + (index + 1), (oldCount - index - 1) * sizeof(T)); + } + resize(oldCount - 1); +} + +template template +size_t Vector::InsertSorted(const T& value, const CmpLess& cmp) +{ + const size_t indexToInsert = BinaryFindFirstNotLess( + m_pArray, + m_pArray + m_Count, + value, + cmp) - m_pArray; + insert(indexToInsert, value); + return indexToInsert; +} + +template template +bool Vector::RemoveSorted(const T& value, const CmpLess& cmp) +{ + const iterator it = BinaryFindFirstNotLess( + m_pArray, + m_pArray + m_Count, + value, + cmp); + if ((it != end()) && !cmp(*it, value) && !cmp(value, *it)) + { + size_t indexToRemove = it - begin(); + remove(indexToRemove); + return true; + } + return false; +} + +template +Vector& Vector::operator=(const Vector& rhs) +{ + if (&rhs != this) + { + resize(rhs.m_Count); + if (m_Count != 0) + { + memcpy(m_pArray, rhs.m_pArray, m_Count * sizeof(T)); + } + } + return *this; +} + +template +T& Vector::operator[](size_t index) +{ + D3D12MA_HEAVY_ASSERT(index < m_Count); + return m_pArray[index]; +} + +template +const T& Vector::operator[](size_t index) const +{ + D3D12MA_HEAVY_ASSERT(index < m_Count); + return m_pArray[index]; +} +#endif // _D3D12MA_VECTOR_FUNCTIONS +#endif // _D3D12MA_VECTOR + +#ifndef _D3D12MA_STRING_BUILDER +class StringBuilder +{ +public: + StringBuilder(const ALLOCATION_CALLBACKS& allocationCallbacks) : m_Data(allocationCallbacks) {} + + size_t GetLength() const { return m_Data.size(); } + LPCWSTR GetData() const { return m_Data.data(); } + + void Add(WCHAR ch) { m_Data.push_back(ch); } + void Add(LPCWSTR str); + void AddNewLine() { Add(L'\n'); } + void AddNumber(UINT num); + void AddNumber(UINT64 num); + void AddPointer(const void* ptr); + +private: + Vector m_Data; +}; + +#ifndef _D3D12MA_STRING_BUILDER_FUNCTIONS +void StringBuilder::Add(LPCWSTR str) +{ + const size_t len = wcslen(str); + if (len > 0) + { + const size_t oldCount = m_Data.size(); + m_Data.resize(oldCount + len); + memcpy(m_Data.data() + oldCount, str, len * sizeof(WCHAR)); + } +} + +void StringBuilder::AddNumber(UINT num) +{ + WCHAR buf[11]; + buf[10] = L'\0'; + WCHAR *p = &buf[10]; + do + { + *--p = L'0' + (num % 10); + num /= 10; + } + while (num); + Add(p); +} + +void StringBuilder::AddNumber(UINT64 num) +{ + WCHAR buf[21]; + buf[20] = L'\0'; + WCHAR *p = &buf[20]; + do + { + *--p = L'0' + (num % 10); + num /= 10; + } + while (num); + Add(p); +} + +void StringBuilder::AddPointer(const void* ptr) +{ + WCHAR buf[21]; + uintptr_t num = (uintptr_t)ptr; + buf[20] = L'\0'; + WCHAR *p = &buf[20]; + do + { + *--p = HexDigitToChar((UINT8)(num & 0xF)); + num >>= 4; + } + while (num); + Add(p); +} + +#endif // _D3D12MA_STRING_BUILDER_FUNCTIONS +#endif // _D3D12MA_STRING_BUILDER + +#ifndef _D3D12MA_JSON_WRITER +/* +Allows to conveniently build a correct JSON document to be written to the +StringBuilder passed to the constructor. +*/ +class JsonWriter +{ +public: + // stringBuilder - string builder to write the document to. Must remain alive for the whole lifetime of this object. + JsonWriter(const ALLOCATION_CALLBACKS& allocationCallbacks, StringBuilder& stringBuilder); + ~JsonWriter(); + + // Begins object by writing "{". + // Inside an object, you must call pairs of WriteString and a value, e.g.: + // j.BeginObject(true); j.WriteString("A"); j.WriteNumber(1); j.WriteString("B"); j.WriteNumber(2); j.EndObject(); + // Will write: { "A": 1, "B": 2 } + void BeginObject(bool singleLine = false); + // Ends object by writing "}". + void EndObject(); + + // Begins array by writing "[". + // Inside an array, you can write a sequence of any values. + void BeginArray(bool singleLine = false); + // Ends array by writing "[". + void EndArray(); + + // Writes a string value inside "". + // pStr can contain any UTF-16 characters, including '"', new line etc. - they will be properly escaped. + void WriteString(LPCWSTR pStr); + + // Begins writing a string value. + // Call BeginString, ContinueString, ContinueString, ..., EndString instead of + // WriteString to conveniently build the string content incrementally, made of + // parts including numbers. + void BeginString(LPCWSTR pStr = NULL); + // Posts next part of an open string. + void ContinueString(LPCWSTR pStr); + // Posts next part of an open string. The number is converted to decimal characters. + void ContinueString(UINT num); + void ContinueString(UINT64 num); + void ContinueString_Pointer(const void* ptr); + // Posts next part of an open string. Pointer value is converted to characters + // using "%p" formatting - shown as hexadecimal number, e.g.: 000000081276Ad00 + // void ContinueString_Pointer(const void* ptr); + // Ends writing a string value by writing '"'. + void EndString(LPCWSTR pStr = NULL); + + // Writes a number value. + void WriteNumber(UINT num); + void WriteNumber(UINT64 num); + // Writes a boolean value - false or true. + void WriteBool(bool b); + // Writes a null value. + void WriteNull(); + + void AddAllocationToObject(const Allocation& alloc); + void AddDetailedStatisticsInfoObject(const DetailedStatistics& stats); + +private: + static const WCHAR* const INDENT; + + enum CollectionType + { + COLLECTION_TYPE_OBJECT, + COLLECTION_TYPE_ARRAY, + }; + struct StackItem + { + CollectionType type; + UINT valueCount; + bool singleLineMode; + }; + + StringBuilder& m_SB; + Vector m_Stack; + bool m_InsideString; + + void BeginValue(bool isString); + void WriteIndent(bool oneLess = false); +}; + +#ifndef _D3D12MA_JSON_WRITER_FUNCTIONS +const WCHAR* const JsonWriter::INDENT = L" "; + +JsonWriter::JsonWriter(const ALLOCATION_CALLBACKS& allocationCallbacks, StringBuilder& stringBuilder) + : m_SB(stringBuilder), + m_Stack(allocationCallbacks), + m_InsideString(false) {} + +JsonWriter::~JsonWriter() +{ + D3D12MA_ASSERT(!m_InsideString); + D3D12MA_ASSERT(m_Stack.empty()); +} + +void JsonWriter::BeginObject(bool singleLine) +{ + D3D12MA_ASSERT(!m_InsideString); + + BeginValue(false); + m_SB.Add(L'{'); + + StackItem stackItem; + stackItem.type = COLLECTION_TYPE_OBJECT; + stackItem.valueCount = 0; + stackItem.singleLineMode = singleLine; + m_Stack.push_back(stackItem); +} + +void JsonWriter::EndObject() +{ + D3D12MA_ASSERT(!m_InsideString); + D3D12MA_ASSERT(!m_Stack.empty() && m_Stack.back().type == COLLECTION_TYPE_OBJECT); + D3D12MA_ASSERT(m_Stack.back().valueCount % 2 == 0); + + WriteIndent(true); + m_SB.Add(L'}'); + + m_Stack.pop_back(); +} + +void JsonWriter::BeginArray(bool singleLine) +{ + D3D12MA_ASSERT(!m_InsideString); + + BeginValue(false); + m_SB.Add(L'['); + + StackItem stackItem; + stackItem.type = COLLECTION_TYPE_ARRAY; + stackItem.valueCount = 0; + stackItem.singleLineMode = singleLine; + m_Stack.push_back(stackItem); +} + +void JsonWriter::EndArray() +{ + D3D12MA_ASSERT(!m_InsideString); + D3D12MA_ASSERT(!m_Stack.empty() && m_Stack.back().type == COLLECTION_TYPE_ARRAY); + + WriteIndent(true); + m_SB.Add(L']'); + + m_Stack.pop_back(); +} + +void JsonWriter::WriteString(LPCWSTR pStr) +{ + BeginString(pStr); + EndString(); +} + +void JsonWriter::BeginString(LPCWSTR pStr) +{ + D3D12MA_ASSERT(!m_InsideString); + + BeginValue(true); + m_InsideString = true; + m_SB.Add(L'"'); + if (pStr != NULL) + { + ContinueString(pStr); + } +} + +void JsonWriter::ContinueString(LPCWSTR pStr) +{ + D3D12MA_ASSERT(m_InsideString); + D3D12MA_ASSERT(pStr); + + for (const WCHAR *p = pStr; *p; ++p) + { + // the strings we encode are assumed to be in UTF-16LE format, the native + // windows wide character Unicode format. In this encoding Unicode code + // points U+0000 to U+D7FF and U+E000 to U+FFFF are encoded in two bytes, + // and everything else takes more than two bytes. We will reject any + // multi wchar character encodings for simplicity. + UINT val = (UINT)*p; + D3D12MA_ASSERT(((val <= 0xD7FF) || (0xE000 <= val && val <= 0xFFFF)) && + "Character not currently supported."); + switch (*p) + { + case L'"': m_SB.Add(L'\\'); m_SB.Add(L'"'); break; + case L'\\': m_SB.Add(L'\\'); m_SB.Add(L'\\'); break; + case L'/': m_SB.Add(L'\\'); m_SB.Add(L'/'); break; + case L'\b': m_SB.Add(L'\\'); m_SB.Add(L'b'); break; + case L'\f': m_SB.Add(L'\\'); m_SB.Add(L'f'); break; + case L'\n': m_SB.Add(L'\\'); m_SB.Add(L'n'); break; + case L'\r': m_SB.Add(L'\\'); m_SB.Add(L'r'); break; + case L'\t': m_SB.Add(L'\\'); m_SB.Add(L't'); break; + default: + // conservatively use encoding \uXXXX for any Unicode character + // requiring more than one byte. + if (32 <= val && val < 256) + m_SB.Add(*p); + else + { + m_SB.Add(L'\\'); + m_SB.Add(L'u'); + for (UINT i = 0; i < 4; ++i) + { + UINT hexDigit = (val & 0xF000) >> 12; + val <<= 4; + if (hexDigit < 10) + m_SB.Add(L'0' + (WCHAR)hexDigit); + else + m_SB.Add(L'A' + (WCHAR)hexDigit); + } + } + break; + } + } +} + +void JsonWriter::ContinueString(UINT num) +{ + D3D12MA_ASSERT(m_InsideString); + m_SB.AddNumber(num); +} + +void JsonWriter::ContinueString(UINT64 num) +{ + D3D12MA_ASSERT(m_InsideString); + m_SB.AddNumber(num); +} + +void JsonWriter::ContinueString_Pointer(const void* ptr) +{ + D3D12MA_ASSERT(m_InsideString); + m_SB.AddPointer(ptr); +} + +void JsonWriter::EndString(LPCWSTR pStr) +{ + D3D12MA_ASSERT(m_InsideString); + + if (pStr) + ContinueString(pStr); + m_SB.Add(L'"'); + m_InsideString = false; +} + +void JsonWriter::WriteNumber(UINT num) +{ + D3D12MA_ASSERT(!m_InsideString); + BeginValue(false); + m_SB.AddNumber(num); +} + +void JsonWriter::WriteNumber(UINT64 num) +{ + D3D12MA_ASSERT(!m_InsideString); + BeginValue(false); + m_SB.AddNumber(num); +} + +void JsonWriter::WriteBool(bool b) +{ + D3D12MA_ASSERT(!m_InsideString); + BeginValue(false); + if (b) + m_SB.Add(L"true"); + else + m_SB.Add(L"false"); +} + +void JsonWriter::WriteNull() +{ + D3D12MA_ASSERT(!m_InsideString); + BeginValue(false); + m_SB.Add(L"null"); +} + +void JsonWriter::AddAllocationToObject(const Allocation& alloc) +{ + WriteString(L"Type"); + switch (alloc.m_PackedData.GetResourceDimension()) { + case D3D12_RESOURCE_DIMENSION_UNKNOWN: + WriteString(L"UNKNOWN"); + break; + case D3D12_RESOURCE_DIMENSION_BUFFER: + WriteString(L"BUFFER"); + break; + case D3D12_RESOURCE_DIMENSION_TEXTURE1D: + WriteString(L"TEXTURE1D"); + break; + case D3D12_RESOURCE_DIMENSION_TEXTURE2D: + WriteString(L"TEXTURE2D"); + break; + case D3D12_RESOURCE_DIMENSION_TEXTURE3D: + WriteString(L"TEXTURE3D"); + break; + default: D3D12MA_ASSERT(0); break; + } + + WriteString(L"Size"); + WriteNumber(alloc.GetSize()); + WriteString(L"Usage"); + WriteNumber((UINT)alloc.m_PackedData.GetResourceFlags()); + + void* privateData = alloc.GetPrivateData(); + if (privateData) + { + WriteString(L"CustomData"); + BeginString(); + ContinueString_Pointer(privateData); + EndString(); + } + + LPCWSTR name = alloc.GetName(); + if (name != NULL) + { + WriteString(L"Name"); + WriteString(name); + } + if (alloc.m_PackedData.GetTextureLayout()) + { + WriteString(L"Layout"); + WriteNumber((UINT)alloc.m_PackedData.GetTextureLayout()); + } +} + +void JsonWriter::AddDetailedStatisticsInfoObject(const DetailedStatistics& stats) +{ + BeginObject(); + + WriteString(L"BlockCount"); + WriteNumber(stats.Stats.BlockCount); + WriteString(L"BlockBytes"); + WriteNumber(stats.Stats.BlockBytes); + WriteString(L"AllocationCount"); + WriteNumber(stats.Stats.AllocationCount); + WriteString(L"AllocationBytes"); + WriteNumber(stats.Stats.AllocationBytes); + WriteString(L"UnusedRangeCount"); + WriteNumber(stats.UnusedRangeCount); + + if (stats.Stats.AllocationCount > 1) + { + WriteString(L"AllocationSizeMin"); + WriteNumber(stats.AllocationSizeMin); + WriteString(L"AllocationSizeMax"); + WriteNumber(stats.AllocationSizeMax); + } + if (stats.UnusedRangeCount > 1) + { + WriteString(L"UnusedRangeSizeMin"); + WriteNumber(stats.UnusedRangeSizeMin); + WriteString(L"UnusedRangeSizeMax"); + WriteNumber(stats.UnusedRangeSizeMax); + } + EndObject(); +} + +void JsonWriter::BeginValue(bool isString) +{ + if (!m_Stack.empty()) + { + StackItem& currItem = m_Stack.back(); + if (currItem.type == COLLECTION_TYPE_OBJECT && currItem.valueCount % 2 == 0) + { + D3D12MA_ASSERT(isString); + } + + if (currItem.type == COLLECTION_TYPE_OBJECT && currItem.valueCount % 2 == 1) + { + m_SB.Add(L':'); m_SB.Add(L' '); + } + else if (currItem.valueCount > 0) + { + m_SB.Add(L','); m_SB.Add(L' '); + WriteIndent(); + } + else + { + WriteIndent(); + } + ++currItem.valueCount; + } +} + +void JsonWriter::WriteIndent(bool oneLess) +{ + if (!m_Stack.empty() && !m_Stack.back().singleLineMode) + { + m_SB.AddNewLine(); + + size_t count = m_Stack.size(); + if (count > 0 && oneLess) + { + --count; + } + for (size_t i = 0; i < count; ++i) + { + m_SB.Add(INDENT); + } + } +} +#endif // _D3D12MA_JSON_WRITER_FUNCTIONS +#endif // _D3D12MA_JSON_WRITER + +#ifndef _D3D12MA_POOL_ALLOCATOR +/* +Allocator for objects of type T using a list of arrays (pools) to speed up +allocation. Number of elements that can be allocated is not bounded because +allocator can create multiple blocks. +T should be POD because constructor and destructor is not called in Alloc or +Free. +*/ +template +class PoolAllocator +{ + D3D12MA_CLASS_NO_COPY(PoolAllocator) +public: + // allocationCallbacks externally owned, must outlive this object. + PoolAllocator(const ALLOCATION_CALLBACKS& allocationCallbacks, UINT firstBlockCapacity); + ~PoolAllocator() { Clear(); } + + void Clear(); + template + T* Alloc(Types... args); + void Free(T* ptr); + +private: + union Item + { + UINT NextFreeIndex; // UINT32_MAX means end of list. + alignas(T) char Value[sizeof(T)]; + }; + + struct ItemBlock + { + Item* pItems; + UINT Capacity; + UINT FirstFreeIndex; + }; + + const ALLOCATION_CALLBACKS& m_AllocationCallbacks; + const UINT m_FirstBlockCapacity; + Vector m_ItemBlocks; + + ItemBlock& CreateNewBlock(); +}; + +#ifndef _D3D12MA_POOL_ALLOCATOR_FUNCTIONS +template +PoolAllocator::PoolAllocator(const ALLOCATION_CALLBACKS& allocationCallbacks, UINT firstBlockCapacity) + : m_AllocationCallbacks(allocationCallbacks), + m_FirstBlockCapacity(firstBlockCapacity), + m_ItemBlocks(allocationCallbacks) +{ + D3D12MA_ASSERT(m_FirstBlockCapacity > 1); +} + +template +void PoolAllocator::Clear() +{ + for(size_t i = m_ItemBlocks.size(); i--; ) + { + D3D12MA_DELETE_ARRAY(m_AllocationCallbacks, m_ItemBlocks[i].pItems, m_ItemBlocks[i].Capacity); + } + m_ItemBlocks.clear(true); +} + +template template +T* PoolAllocator::Alloc(Types... args) +{ + for(size_t i = m_ItemBlocks.size(); i--; ) + { + ItemBlock& block = m_ItemBlocks[i]; + // This block has some free items: Use first one. + if(block.FirstFreeIndex != UINT32_MAX) + { + Item* const pItem = &block.pItems[block.FirstFreeIndex]; + block.FirstFreeIndex = pItem->NextFreeIndex; + T* result = (T*)&pItem->Value; + new(result)T(std::forward(args)...); // Explicit constructor call. + return result; + } + } + + // No block has free item: Create new one and use it. + ItemBlock& newBlock = CreateNewBlock(); + Item* const pItem = &newBlock.pItems[0]; + newBlock.FirstFreeIndex = pItem->NextFreeIndex; + T* result = (T*)pItem->Value; + new(result)T(std::forward(args)...); // Explicit constructor call. + return result; +} + +template +void PoolAllocator::Free(T* ptr) +{ + // Search all memory blocks to find ptr. + for(size_t i = m_ItemBlocks.size(); i--; ) + { + ItemBlock& block = m_ItemBlocks[i]; + + Item* pItemPtr; + memcpy(&pItemPtr, &ptr, sizeof(pItemPtr)); + + // Check if pItemPtr is in address range of this block. + if((pItemPtr >= block.pItems) && (pItemPtr < block.pItems + block.Capacity)) + { + ptr->~T(); // Explicit destructor call. + const UINT index = static_cast(pItemPtr - block.pItems); + pItemPtr->NextFreeIndex = block.FirstFreeIndex; + block.FirstFreeIndex = index; + return; + } + } + D3D12MA_ASSERT(0 && "Pointer doesn't belong to this memory pool."); +} + +template +typename PoolAllocator::ItemBlock& PoolAllocator::CreateNewBlock() +{ + const UINT newBlockCapacity = m_ItemBlocks.empty() ? + m_FirstBlockCapacity : m_ItemBlocks.back().Capacity * 3 / 2; + + const ItemBlock newBlock = { + D3D12MA_NEW_ARRAY(m_AllocationCallbacks, Item, newBlockCapacity), + newBlockCapacity, + 0 }; + + m_ItemBlocks.push_back(newBlock); + + // Setup singly-linked list of all free items in this block. + for(UINT i = 0; i < newBlockCapacity - 1; ++i) + { + newBlock.pItems[i].NextFreeIndex = i + 1; + } + newBlock.pItems[newBlockCapacity - 1].NextFreeIndex = UINT32_MAX; + return m_ItemBlocks.back(); +} +#endif // _D3D12MA_POOL_ALLOCATOR_FUNCTIONS +#endif // _D3D12MA_POOL_ALLOCATOR + +#ifndef _D3D12MA_LIST +/* +Doubly linked list, with elements allocated out of PoolAllocator. +Has custom interface, as well as STL-style interface, including iterator and +const_iterator. +*/ +template +class List +{ + D3D12MA_CLASS_NO_COPY(List) +public: + struct Item + { + Item* pPrev; + Item* pNext; + T Value; + }; + + class reverse_iterator; + class const_reverse_iterator; + class iterator + { + friend class List; + friend class const_iterator; + + public: + iterator() = default; + iterator(const reverse_iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + T& operator*() const; + T* operator->() const; + + iterator& operator++(); + iterator& operator--(); + iterator operator++(int); + iterator operator--(int); + + bool operator==(const iterator& rhs) const; + bool operator!=(const iterator& rhs) const; + + private: + List* m_pList = NULL; + Item* m_pItem = NULL; + + iterator(List* pList, Item* pItem) : m_pList(pList), m_pItem(pItem) {} + }; + + class reverse_iterator + { + friend class List; + friend class const_reverse_iterator; + + public: + reverse_iterator() = default; + reverse_iterator(const iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + T& operator*() const; + T* operator->() const; + + reverse_iterator& operator++(); + reverse_iterator& operator--(); + reverse_iterator operator++(int); + reverse_iterator operator--(int); + + bool operator==(const reverse_iterator& rhs) const; + bool operator!=(const reverse_iterator& rhs) const; + + private: + List* m_pList = NULL; + Item* m_pItem = NULL; + + reverse_iterator(List* pList, Item* pItem) + : m_pList(pList), m_pItem(pItem) {} + }; + + class const_iterator + { + friend class List; + + public: + const_iterator() = default; + const_iterator(const iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + const_iterator(const reverse_iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + const_iterator(const const_reverse_iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + iterator dropConst() const; + const T& operator*() const; + const T* operator->() const; + + const_iterator& operator++(); + const_iterator& operator--(); + const_iterator operator++(int); + const_iterator operator--(int); + + bool operator==(const const_iterator& rhs) const; + bool operator!=(const const_iterator& rhs) const; + + private: + const List* m_pList = NULL; + const Item* m_pItem = NULL; + + const_iterator(const List* pList, const Item* pItem) + : m_pList(pList), m_pItem(pItem) {} + }; + + class const_reverse_iterator + { + friend class List; + + public: + const_reverse_iterator() = default; + const_reverse_iterator(const iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + const_reverse_iterator(const reverse_iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + const_reverse_iterator(const const_iterator& src) + : m_pList(src.m_pList), m_pItem(src.m_pItem) {} + + reverse_iterator dropConst() const; + const T& operator*() const; + const T* operator->() const; + + const_reverse_iterator& operator++(); + const_reverse_iterator& operator--(); + const_reverse_iterator operator++(int); + const_reverse_iterator operator--(int); + + bool operator==(const const_reverse_iterator& rhs) const; + bool operator!=(const const_reverse_iterator& rhs) const; + + private: + const List* m_pList = NULL; + const Item* m_pItem = NULL; + + const_reverse_iterator(const List* pList, const Item* pItem) + : m_pList(pList), m_pItem(pItem) {} + }; + + // allocationCallbacks externally owned, must outlive this object. + List(const ALLOCATION_CALLBACKS& allocationCallbacks); + // Intentionally not calling Clear, because that would be unnecessary + // computations to return all items to m_ItemAllocator as free. + ~List() = default; + + size_t GetCount() const { return m_Count; } + bool IsEmpty() const { return m_Count == 0; } + + Item* Front() { return m_pFront; } + const Item* Front() const { return m_pFront; } + Item* Back() { return m_pBack; } + const Item* Back() const { return m_pBack; } + + bool empty() const { return IsEmpty(); } + size_t size() const { return GetCount(); } + void push_back(const T& value) { PushBack(value); } + iterator insert(iterator it, const T& value) { return iterator(this, InsertBefore(it.m_pItem, value)); } + void clear() { Clear(); } + void erase(iterator it) { Remove(it.m_pItem); } + + iterator begin() { return iterator(this, Front()); } + iterator end() { return iterator(this, NULL); } + reverse_iterator rbegin() { return reverse_iterator(this, Back()); } + reverse_iterator rend() { return reverse_iterator(this, NULL); } + + const_iterator cbegin() const { return const_iterator(this, Front()); } + const_iterator cend() const { return const_iterator(this, NULL); } + const_iterator begin() const { return cbegin(); } + const_iterator end() const { return cend(); } + + const_reverse_iterator crbegin() const { return const_reverse_iterator(this, Back()); } + const_reverse_iterator crend() const { return const_reverse_iterator(this, NULL); } + const_reverse_iterator rbegin() const { return crbegin(); } + const_reverse_iterator rend() const { return crend(); } + + Item* PushBack(); + Item* PushFront(); + Item* PushBack(const T& value); + Item* PushFront(const T& value); + void PopBack(); + void PopFront(); + + // Item can be null - it means PushBack. + Item* InsertBefore(Item* pItem); + // Item can be null - it means PushFront. + Item* InsertAfter(Item* pItem); + Item* InsertBefore(Item* pItem, const T& value); + Item* InsertAfter(Item* pItem, const T& value); + + void Clear(); + void Remove(Item* pItem); + +private: + const ALLOCATION_CALLBACKS& m_AllocationCallbacks; + PoolAllocator m_ItemAllocator; + Item* m_pFront; + Item* m_pBack; + size_t m_Count; +}; + +#ifndef _D3D12MA_LIST_ITERATOR_FUNCTIONS +template +T& List::iterator::operator*() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return m_pItem->Value; +} + +template +T* List::iterator::operator->() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return &m_pItem->Value; +} + +template +typename List::iterator& List::iterator::operator++() +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + m_pItem = m_pItem->pNext; + return *this; +} + +template +typename List::iterator& List::iterator::operator--() +{ + if (m_pItem != NULL) + { + m_pItem = m_pItem->pPrev; + } + else + { + D3D12MA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Back(); + } + return *this; +} + +template +typename List::iterator List::iterator::operator++(int) +{ + iterator result = *this; + ++* this; + return result; +} + +template +typename List::iterator List::iterator::operator--(int) +{ + iterator result = *this; + --* this; + return result; +} + +template +bool List::iterator::operator==(const iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem == rhs.m_pItem; +} + +template +bool List::iterator::operator!=(const iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem != rhs.m_pItem; +} +#endif // _D3D12MA_LIST_ITERATOR_FUNCTIONS + +#ifndef _D3D12MA_LIST_REVERSE_ITERATOR_FUNCTIONS +template +T& List::reverse_iterator::operator*() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return m_pItem->Value; +} + +template +T* List::reverse_iterator::operator->() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return &m_pItem->Value; +} + +template +typename List::reverse_iterator& List::reverse_iterator::operator++() +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + m_pItem = m_pItem->pPrev; + return *this; +} + +template +typename List::reverse_iterator& List::reverse_iterator::operator--() +{ + if (m_pItem != NULL) + { + m_pItem = m_pItem->pNext; + } + else + { + D3D12MA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Front(); + } + return *this; +} + +template +typename List::reverse_iterator List::reverse_iterator::operator++(int) +{ + reverse_iterator result = *this; + ++* this; + return result; +} + +template +typename List::reverse_iterator List::reverse_iterator::operator--(int) +{ + reverse_iterator result = *this; + --* this; + return result; +} + +template +bool List::reverse_iterator::operator==(const reverse_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem == rhs.m_pItem; +} + +template +bool List::reverse_iterator::operator!=(const reverse_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem != rhs.m_pItem; +} +#endif // _D3D12MA_LIST_REVERSE_ITERATOR_FUNCTIONS + +#ifndef _D3D12MA_LIST_CONST_ITERATOR_FUNCTIONS +template +typename List::iterator List::const_iterator::dropConst() const +{ + return iterator(const_cast*>(m_pList), const_cast(m_pItem)); +} + +template +const T& List::const_iterator::operator*() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return m_pItem->Value; +} + +template +const T* List::const_iterator::operator->() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return &m_pItem->Value; +} + +template +typename List::const_iterator& List::const_iterator::operator++() +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + m_pItem = m_pItem->pNext; + return *this; +} + +template +typename List::const_iterator& List::const_iterator::operator--() +{ + if (m_pItem != NULL) + { + m_pItem = m_pItem->pPrev; + } + else + { + D3D12MA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Back(); + } + return *this; +} + +template +typename List::const_iterator List::const_iterator::operator++(int) +{ + const_iterator result = *this; + ++* this; + return result; +} + +template +typename List::const_iterator List::const_iterator::operator--(int) +{ + const_iterator result = *this; + --* this; + return result; +} + +template +bool List::const_iterator::operator==(const const_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem == rhs.m_pItem; +} + +template +bool List::const_iterator::operator!=(const const_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem != rhs.m_pItem; +} +#endif // _D3D12MA_LIST_CONST_ITERATOR_FUNCTIONS + +#ifndef _D3D12MA_LIST_CONST_REVERSE_ITERATOR_FUNCTIONS +template +typename List::reverse_iterator List::const_reverse_iterator::dropConst() const +{ + return reverse_iterator(const_cast*>(m_pList), const_cast(m_pItem)); +} + +template +const T& List::const_reverse_iterator::operator*() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return m_pItem->Value; +} + +template +const T* List::const_reverse_iterator::operator->() const +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + return &m_pItem->Value; +} + +template +typename List::const_reverse_iterator& List::const_reverse_iterator::operator++() +{ + D3D12MA_HEAVY_ASSERT(m_pItem != NULL); + m_pItem = m_pItem->pPrev; + return *this; +} + +template +typename List::const_reverse_iterator& List::const_reverse_iterator::operator--() +{ + if (m_pItem != NULL) + { + m_pItem = m_pItem->pNext; + } + else + { + D3D12MA_HEAVY_ASSERT(!m_pList->IsEmpty()); + m_pItem = m_pList->Front(); + } + return *this; +} + +template +typename List::const_reverse_iterator List::const_reverse_iterator::operator++(int) +{ + const_reverse_iterator result = *this; + ++* this; + return result; +} + +template +typename List::const_reverse_iterator List::const_reverse_iterator::operator--(int) +{ + const_reverse_iterator result = *this; + --* this; + return result; +} + +template +bool List::const_reverse_iterator::operator==(const const_reverse_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem == rhs.m_pItem; +} + +template +bool List::const_reverse_iterator::operator!=(const const_reverse_iterator& rhs) const +{ + D3D12MA_HEAVY_ASSERT(m_pList == rhs.m_pList); + return m_pItem != rhs.m_pItem; +} +#endif // _D3D12MA_LIST_CONST_REVERSE_ITERATOR_FUNCTIONS + +#ifndef _D3D12MA_LIST_FUNCTIONS +template +List::List(const ALLOCATION_CALLBACKS& allocationCallbacks) + : m_AllocationCallbacks(allocationCallbacks), + m_ItemAllocator(allocationCallbacks, 128), + m_pFront(NULL), + m_pBack(NULL), + m_Count(0) {} + +template +void List::Clear() +{ + if(!IsEmpty()) + { + Item* pItem = m_pBack; + while(pItem != NULL) + { + Item* const pPrevItem = pItem->pPrev; + m_ItemAllocator.Free(pItem); + pItem = pPrevItem; + } + m_pFront = NULL; + m_pBack = NULL; + m_Count = 0; + } +} + +template +typename List::Item* List::PushBack() +{ + Item* const pNewItem = m_ItemAllocator.Alloc(); + pNewItem->pNext = NULL; + if(IsEmpty()) + { + pNewItem->pPrev = NULL; + m_pFront = pNewItem; + m_pBack = pNewItem; + m_Count = 1; + } + else + { + pNewItem->pPrev = m_pBack; + m_pBack->pNext = pNewItem; + m_pBack = pNewItem; + ++m_Count; + } + return pNewItem; +} + +template +typename List::Item* List::PushFront() +{ + Item* const pNewItem = m_ItemAllocator.Alloc(); + pNewItem->pPrev = NULL; + if(IsEmpty()) + { + pNewItem->pNext = NULL; + m_pFront = pNewItem; + m_pBack = pNewItem; + m_Count = 1; + } + else + { + pNewItem->pNext = m_pFront; + m_pFront->pPrev = pNewItem; + m_pFront = pNewItem; + ++m_Count; + } + return pNewItem; +} + +template +typename List::Item* List::PushBack(const T& value) +{ + Item* const pNewItem = PushBack(); + pNewItem->Value = value; + return pNewItem; +} + +template +typename List::Item* List::PushFront(const T& value) +{ + Item* const pNewItem = PushFront(); + pNewItem->Value = value; + return pNewItem; +} + +template +void List::PopBack() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + Item* const pBackItem = m_pBack; + Item* const pPrevItem = pBackItem->pPrev; + if(pPrevItem != NULL) + { + pPrevItem->pNext = NULL; + } + m_pBack = pPrevItem; + m_ItemAllocator.Free(pBackItem); + --m_Count; +} + +template +void List::PopFront() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + Item* const pFrontItem = m_pFront; + Item* const pNextItem = pFrontItem->pNext; + if(pNextItem != NULL) + { + pNextItem->pPrev = NULL; + } + m_pFront = pNextItem; + m_ItemAllocator.Free(pFrontItem); + --m_Count; +} + +template +void List::Remove(Item* pItem) +{ + D3D12MA_HEAVY_ASSERT(pItem != NULL); + D3D12MA_HEAVY_ASSERT(m_Count > 0); + + if(pItem->pPrev != NULL) + { + pItem->pPrev->pNext = pItem->pNext; + } + else + { + D3D12MA_HEAVY_ASSERT(m_pFront == pItem); + m_pFront = pItem->pNext; + } + + if(pItem->pNext != NULL) + { + pItem->pNext->pPrev = pItem->pPrev; + } + else + { + D3D12MA_HEAVY_ASSERT(m_pBack == pItem); + m_pBack = pItem->pPrev; + } + + m_ItemAllocator.Free(pItem); + --m_Count; +} + +template +typename List::Item* List::InsertBefore(Item* pItem) +{ + if(pItem != NULL) + { + Item* const prevItem = pItem->pPrev; + Item* const newItem = m_ItemAllocator.Alloc(); + newItem->pPrev = prevItem; + newItem->pNext = pItem; + pItem->pPrev = newItem; + if(prevItem != NULL) + { + prevItem->pNext = newItem; + } + else + { + D3D12MA_HEAVY_ASSERT(m_pFront == pItem); + m_pFront = newItem; + } + ++m_Count; + return newItem; + } + else + { + return PushBack(); + } +} + +template +typename List::Item* List::InsertAfter(Item* pItem) +{ + if(pItem != NULL) + { + Item* const nextItem = pItem->pNext; + Item* const newItem = m_ItemAllocator.Alloc(); + newItem->pNext = nextItem; + newItem->pPrev = pItem; + pItem->pNext = newItem; + if(nextItem != NULL) + { + nextItem->pPrev = newItem; + } + else + { + D3D12MA_HEAVY_ASSERT(m_pBack == pItem); + m_pBack = newItem; + } + ++m_Count; + return newItem; + } + else + return PushFront(); +} + +template +typename List::Item* List::InsertBefore(Item* pItem, const T& value) +{ + Item* const newItem = InsertBefore(pItem); + newItem->Value = value; + return newItem; +} + +template +typename List::Item* List::InsertAfter(Item* pItem, const T& value) +{ + Item* const newItem = InsertAfter(pItem); + newItem->Value = value; + return newItem; +} +#endif // _D3D12MA_LIST_FUNCTIONS +#endif // _D3D12MA_LIST + +#ifndef _D3D12MA_INTRUSIVE_LINKED_LIST +/* +Expected interface of ItemTypeTraits: +struct MyItemTypeTraits +{ + using ItemType = MyItem; + static ItemType* GetPrev(const ItemType* item) { return item->myPrevPtr; } + static ItemType* GetNext(const ItemType* item) { return item->myNextPtr; } + static ItemType*& AccessPrev(ItemType* item) { return item->myPrevPtr; } + static ItemType*& AccessNext(ItemType* item) { return item->myNextPtr; } +}; +*/ +template +class IntrusiveLinkedList +{ +public: + using ItemType = typename ItemTypeTraits::ItemType; + static ItemType* GetPrev(const ItemType* item) { return ItemTypeTraits::GetPrev(item); } + static ItemType* GetNext(const ItemType* item) { return ItemTypeTraits::GetNext(item); } + + // Movable, not copyable. + IntrusiveLinkedList() = default; + IntrusiveLinkedList(const IntrusiveLinkedList&) = delete; + IntrusiveLinkedList(IntrusiveLinkedList&& src); + IntrusiveLinkedList& operator=(const IntrusiveLinkedList&) = delete; + IntrusiveLinkedList& operator=(IntrusiveLinkedList&& src); + ~IntrusiveLinkedList() { D3D12MA_HEAVY_ASSERT(IsEmpty()); } + + size_t GetCount() const { return m_Count; } + bool IsEmpty() const { return m_Count == 0; } + + ItemType* Front() { return m_Front; } + ItemType* Back() { return m_Back; } + const ItemType* Front() const { return m_Front; } + const ItemType* Back() const { return m_Back; } + + void PushBack(ItemType* item); + void PushFront(ItemType* item); + ItemType* PopBack(); + ItemType* PopFront(); + + // MyItem can be null - it means PushBack. + void InsertBefore(ItemType* existingItem, ItemType* newItem); + // MyItem can be null - it means PushFront. + void InsertAfter(ItemType* existingItem, ItemType* newItem); + + void Remove(ItemType* item); + void RemoveAll(); + +private: + ItemType* m_Front = NULL; + ItemType* m_Back = NULL; + size_t m_Count = 0; +}; + +#ifndef _D3D12MA_INTRUSIVE_LINKED_LIST_FUNCTIONS +template +IntrusiveLinkedList::IntrusiveLinkedList(IntrusiveLinkedList&& src) + : m_Front(src.m_Front), m_Back(src.m_Back), m_Count(src.m_Count) +{ + src.m_Front = src.m_Back = NULL; + src.m_Count = 0; +} + +template +IntrusiveLinkedList& IntrusiveLinkedList::operator=(IntrusiveLinkedList&& src) +{ + if (&src != this) + { + D3D12MA_HEAVY_ASSERT(IsEmpty()); + m_Front = src.m_Front; + m_Back = src.m_Back; + m_Count = src.m_Count; + src.m_Front = src.m_Back = NULL; + src.m_Count = 0; + } + return *this; +} + +template +void IntrusiveLinkedList::PushBack(ItemType* item) +{ + D3D12MA_HEAVY_ASSERT(ItemTypeTraits::GetPrev(item) == NULL && ItemTypeTraits::GetNext(item) == NULL); + if (IsEmpty()) + { + m_Front = item; + m_Back = item; + m_Count = 1; + } + else + { + ItemTypeTraits::AccessPrev(item) = m_Back; + ItemTypeTraits::AccessNext(m_Back) = item; + m_Back = item; + ++m_Count; + } +} + +template +void IntrusiveLinkedList::PushFront(ItemType* item) +{ + D3D12MA_HEAVY_ASSERT(ItemTypeTraits::GetPrev(item) == NULL && ItemTypeTraits::GetNext(item) == NULL); + if (IsEmpty()) + { + m_Front = item; + m_Back = item; + m_Count = 1; + } + else + { + ItemTypeTraits::AccessNext(item) = m_Front; + ItemTypeTraits::AccessPrev(m_Front) = item; + m_Front = item; + ++m_Count; + } +} + +template +typename IntrusiveLinkedList::ItemType* IntrusiveLinkedList::PopBack() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + ItemType* const backItem = m_Back; + ItemType* const prevItem = ItemTypeTraits::GetPrev(backItem); + if (prevItem != NULL) + { + ItemTypeTraits::AccessNext(prevItem) = NULL; + } + m_Back = prevItem; + --m_Count; + ItemTypeTraits::AccessPrev(backItem) = NULL; + ItemTypeTraits::AccessNext(backItem) = NULL; + return backItem; +} + +template +typename IntrusiveLinkedList::ItemType* IntrusiveLinkedList::PopFront() +{ + D3D12MA_HEAVY_ASSERT(m_Count > 0); + ItemType* const frontItem = m_Front; + ItemType* const nextItem = ItemTypeTraits::GetNext(frontItem); + if (nextItem != NULL) + { + ItemTypeTraits::AccessPrev(nextItem) = NULL; + } + m_Front = nextItem; + --m_Count; + ItemTypeTraits::AccessPrev(frontItem) = NULL; + ItemTypeTraits::AccessNext(frontItem) = NULL; + return frontItem; +} + +template +void IntrusiveLinkedList::InsertBefore(ItemType* existingItem, ItemType* newItem) +{ + D3D12MA_HEAVY_ASSERT(newItem != NULL && ItemTypeTraits::GetPrev(newItem) == NULL && ItemTypeTraits::GetNext(newItem) == NULL); + if (existingItem != NULL) + { + ItemType* const prevItem = ItemTypeTraits::GetPrev(existingItem); + ItemTypeTraits::AccessPrev(newItem) = prevItem; + ItemTypeTraits::AccessNext(newItem) = existingItem; + ItemTypeTraits::AccessPrev(existingItem) = newItem; + if (prevItem != NULL) + { + ItemTypeTraits::AccessNext(prevItem) = newItem; + } + else + { + D3D12MA_HEAVY_ASSERT(m_Front == existingItem); + m_Front = newItem; + } + ++m_Count; + } + else + PushBack(newItem); +} + +template +void IntrusiveLinkedList::InsertAfter(ItemType* existingItem, ItemType* newItem) +{ + D3D12MA_HEAVY_ASSERT(newItem != NULL && ItemTypeTraits::GetPrev(newItem) == NULL && ItemTypeTraits::GetNext(newItem) == NULL); + if (existingItem != NULL) + { + ItemType* const nextItem = ItemTypeTraits::GetNext(existingItem); + ItemTypeTraits::AccessNext(newItem) = nextItem; + ItemTypeTraits::AccessPrev(newItem) = existingItem; + ItemTypeTraits::AccessNext(existingItem) = newItem; + if (nextItem != NULL) + { + ItemTypeTraits::AccessPrev(nextItem) = newItem; + } + else + { + D3D12MA_HEAVY_ASSERT(m_Back == existingItem); + m_Back = newItem; + } + ++m_Count; + } + else + return PushFront(newItem); +} + +template +void IntrusiveLinkedList::Remove(ItemType* item) +{ + D3D12MA_HEAVY_ASSERT(item != NULL && m_Count > 0); + if (ItemTypeTraits::GetPrev(item) != NULL) + { + ItemTypeTraits::AccessNext(ItemTypeTraits::AccessPrev(item)) = ItemTypeTraits::GetNext(item); + } + else + { + D3D12MA_HEAVY_ASSERT(m_Front == item); + m_Front = ItemTypeTraits::GetNext(item); + } + + if (ItemTypeTraits::GetNext(item) != NULL) + { + ItemTypeTraits::AccessPrev(ItemTypeTraits::AccessNext(item)) = ItemTypeTraits::GetPrev(item); + } + else + { + D3D12MA_HEAVY_ASSERT(m_Back == item); + m_Back = ItemTypeTraits::GetPrev(item); + } + ItemTypeTraits::AccessPrev(item) = NULL; + ItemTypeTraits::AccessNext(item) = NULL; + --m_Count; +} + +template +void IntrusiveLinkedList::RemoveAll() +{ + if (!IsEmpty()) + { + ItemType* item = m_Back; + while (item != NULL) + { + ItemType* const prevItem = ItemTypeTraits::AccessPrev(item); + ItemTypeTraits::AccessPrev(item) = NULL; + ItemTypeTraits::AccessNext(item) = NULL; + item = prevItem; + } + m_Front = NULL; + m_Back = NULL; + m_Count = 0; + } +} +#endif // _D3D12MA_INTRUSIVE_LINKED_LIST_FUNCTIONS +#endif // _D3D12MA_INTRUSIVE_LINKED_LIST + +#ifndef _D3D12MA_ALLOCATION_OBJECT_ALLOCATOR +/* +Thread-safe wrapper over PoolAllocator free list, for allocation of Allocation objects. +*/ +class AllocationObjectAllocator +{ + D3D12MA_CLASS_NO_COPY(AllocationObjectAllocator); +public: + AllocationObjectAllocator(const ALLOCATION_CALLBACKS& allocationCallbacks) + : m_Allocator(allocationCallbacks, 1024) {} + + template + Allocation* Allocate(Types... args); + void Free(Allocation* alloc); + +private: + D3D12MA_MUTEX m_Mutex; + PoolAllocator m_Allocator; +}; + +#ifndef _D3D12MA_ALLOCATION_OBJECT_ALLOCATOR_FUNCTIONS +template +Allocation* AllocationObjectAllocator::Allocate(Types... args) +{ + MutexLock mutexLock(m_Mutex); + return m_Allocator.Alloc(std::forward(args)...); +} + +void AllocationObjectAllocator::Free(Allocation* alloc) +{ + MutexLock mutexLock(m_Mutex); + m_Allocator.Free(alloc); +} +#endif // _D3D12MA_ALLOCATION_OBJECT_ALLOCATOR_FUNCTIONS +#endif // _D3D12MA_ALLOCATION_OBJECT_ALLOCATOR + +#ifndef _D3D12MA_SUBALLOCATION +/* +Represents a region of NormalBlock that is either assigned and returned as +allocated memory block or free. +*/ +struct Suballocation +{ + UINT64 offset; + UINT64 size; + void* privateData; + SuballocationType type; +}; +using SuballocationList = List; + +// Comparator for offsets. +struct SuballocationOffsetLess +{ + bool operator()(const Suballocation& lhs, const Suballocation& rhs) const + { + return lhs.offset < rhs.offset; + } +}; + +struct SuballocationOffsetGreater +{ + bool operator()(const Suballocation& lhs, const Suballocation& rhs) const + { + return lhs.offset > rhs.offset; + } +}; + +struct SuballocationItemSizeLess +{ + bool operator()(const SuballocationList::iterator lhs, const SuballocationList::iterator rhs) const + { + return lhs->size < rhs->size; + } + bool operator()(const SuballocationList::iterator lhs, UINT64 rhsSize) const + { + return lhs->size < rhsSize; + } +}; +#endif // _D3D12MA_SUBALLOCATION + +#ifndef _D3D12MA_ALLOCATION_REQUEST +/* +Parameters of planned allocation inside a NormalBlock. +*/ +struct AllocationRequest +{ + AllocHandle allocHandle; + UINT64 size; + UINT64 algorithmData; + UINT64 sumFreeSize; // Sum size of free items that overlap with proposed allocation. + UINT64 sumItemSize; // Sum size of items to make lost that overlap with proposed allocation. + SuballocationList::iterator item; + BOOL zeroInitialized; +}; +#endif // _D3D12MA_ALLOCATION_REQUEST + +#ifndef _D3D12MA_ZERO_INITIALIZED_RANGE +/* +Keeps track of the range of bytes that are surely initialized with zeros. +Everything outside of it is considered uninitialized memory that may contain +garbage data. + +The range is left-inclusive. +*/ +class ZeroInitializedRange +{ +public: + void Reset(UINT64 size); + BOOL IsRangeZeroInitialized(UINT64 beg, UINT64 end) const; + void MarkRangeAsUsed(UINT64 usedBeg, UINT64 usedEnd); + +private: + UINT64 m_ZeroBeg = 0, m_ZeroEnd = 0; +}; + +#ifndef _D3D12MA_ZERO_INITIALIZED_RANGE_FUNCTIONS +void ZeroInitializedRange::Reset(UINT64 size) +{ + D3D12MA_ASSERT(size > 0); + m_ZeroBeg = 0; + m_ZeroEnd = size; +} + +BOOL ZeroInitializedRange::IsRangeZeroInitialized(UINT64 beg, UINT64 end) const +{ + D3D12MA_ASSERT(beg < end); + return m_ZeroBeg <= beg && end <= m_ZeroEnd; +} + +void ZeroInitializedRange::MarkRangeAsUsed(UINT64 usedBeg, UINT64 usedEnd) +{ + D3D12MA_ASSERT(usedBeg < usedEnd); + // No new bytes marked. + if (usedEnd <= m_ZeroBeg || m_ZeroEnd <= usedBeg) + { + return; + } + // All bytes marked. + if (usedBeg <= m_ZeroBeg && m_ZeroEnd <= usedEnd) + { + m_ZeroBeg = m_ZeroEnd = 0; + } + // Some bytes marked. + else + { + const UINT64 remainingZeroBefore = usedBeg > m_ZeroBeg ? usedBeg - m_ZeroBeg : 0; + const UINT64 remainingZeroAfter = usedEnd < m_ZeroEnd ? m_ZeroEnd - usedEnd : 0; + D3D12MA_ASSERT(remainingZeroBefore > 0 || remainingZeroAfter > 0); + if (remainingZeroBefore > remainingZeroAfter) + { + m_ZeroEnd = usedBeg; + } + else + { + m_ZeroBeg = usedEnd; + } + } +} +#endif // _D3D12MA_ZERO_INITIALIZED_RANGE_FUNCTIONS +#endif // _D3D12MA_ZERO_INITIALIZED_RANGE + +#ifndef _D3D12MA_BLOCK_METADATA +/* +Data structure used for bookkeeping of allocations and unused ranges of memory +in a single ID3D12Heap memory block. +*/ +class BlockMetadata +{ +public: + BlockMetadata(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual); + virtual ~BlockMetadata() = default; + + virtual void Init(UINT64 size) { m_Size = size; } + // Validates all data structures inside this object. If not valid, returns false. + virtual bool Validate() const = 0; + UINT64 GetSize() const { return m_Size; } + bool IsVirtual() const { return m_IsVirtual; } + virtual size_t GetAllocationCount() const = 0; + virtual size_t GetFreeRegionsCount() const = 0; + virtual UINT64 GetSumFreeSize() const = 0; + virtual UINT64 GetAllocationOffset(AllocHandle allocHandle) const = 0; + // Returns true if this block is empty - contains only single free suballocation. + virtual bool IsEmpty() const = 0; + + virtual void GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const = 0; + + // Tries to find a place for suballocation with given parameters inside this block. + // If succeeded, fills pAllocationRequest and returns true. + // If failed, returns false. + virtual bool CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + UINT32 strategy, + AllocationRequest* pAllocationRequest) = 0; + + // Makes actual allocation based on request. Request must already be checked and valid. + virtual void Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* PrivateData) = 0; + + virtual void Free(AllocHandle allocHandle) = 0; + // Frees all allocations. + // Careful! Don't call it if there are Allocation objects owned by pPrivateData of of cleared allocations! + virtual void Clear() = 0; + + virtual AllocHandle GetAllocationListBegin() const = 0; + virtual AllocHandle GetNextAllocation(AllocHandle prevAlloc) const = 0; + virtual UINT64 GetNextFreeRegionSize(AllocHandle alloc) const = 0; + virtual void* GetAllocationPrivateData(AllocHandle allocHandle) const = 0; + virtual void SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) = 0; + + virtual void AddStatistics(Statistics& inoutStats) const = 0; + virtual void AddDetailedStatistics(DetailedStatistics& inoutStats) const = 0; + virtual void WriteAllocationInfoToJson(JsonWriter& json) const = 0; + +protected: + const ALLOCATION_CALLBACKS* GetAllocs() const { return m_pAllocationCallbacks; } + UINT64 GetDebugMargin() const { return IsVirtual() ? 0 : D3D12MA_DEBUG_MARGIN; } + + void PrintDetailedMap_Begin(JsonWriter& json, + UINT64 unusedBytes, + size_t allocationCount, + size_t unusedRangeCount) const; + void PrintDetailedMap_Allocation(JsonWriter& json, + UINT64 offset, UINT64 size, void* privateData) const; + void PrintDetailedMap_UnusedRange(JsonWriter& json, + UINT64 offset, UINT64 size) const; + void PrintDetailedMap_End(JsonWriter& json) const; + +private: + UINT64 m_Size; + bool m_IsVirtual; + const ALLOCATION_CALLBACKS* m_pAllocationCallbacks; + + D3D12MA_CLASS_NO_COPY(BlockMetadata); +}; + +#ifndef _D3D12MA_BLOCK_METADATA_FUNCTIONS +BlockMetadata::BlockMetadata(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual) + : m_Size(0), + m_IsVirtual(isVirtual), + m_pAllocationCallbacks(allocationCallbacks) +{ + D3D12MA_ASSERT(allocationCallbacks); +} + +void BlockMetadata::PrintDetailedMap_Begin(JsonWriter& json, + UINT64 unusedBytes, size_t allocationCount, size_t unusedRangeCount) const +{ + json.WriteString(L"TotalBytes"); + json.WriteNumber(GetSize()); + + json.WriteString(L"UnusedBytes"); + json.WriteNumber(unusedBytes); + + json.WriteString(L"Allocations"); + json.WriteNumber(allocationCount); + + json.WriteString(L"UnusedRanges"); + json.WriteNumber(unusedRangeCount); + + json.WriteString(L"Suballocations"); + json.BeginArray(); +} + +void BlockMetadata::PrintDetailedMap_Allocation(JsonWriter& json, + UINT64 offset, UINT64 size, void* privateData) const +{ + json.BeginObject(true); + + json.WriteString(L"Offset"); + json.WriteNumber(offset); + + if (IsVirtual()) + { + json.WriteString(L"Size"); + json.WriteNumber(size); + if (privateData) + { + json.WriteString(L"CustomData"); + json.WriteNumber((uintptr_t)privateData); + } + } + else + { + const Allocation* const alloc = (const Allocation*)privateData; + D3D12MA_ASSERT(alloc); + json.AddAllocationToObject(*alloc); + } + json.EndObject(); +} + +void BlockMetadata::PrintDetailedMap_UnusedRange(JsonWriter& json, + UINT64 offset, UINT64 size) const +{ + json.BeginObject(true); + + json.WriteString(L"Offset"); + json.WriteNumber(offset); + + json.WriteString(L"Type"); + json.WriteString(L"FREE"); + + json.WriteString(L"Size"); + json.WriteNumber(size); + + json.EndObject(); +} + +void BlockMetadata::PrintDetailedMap_End(JsonWriter& json) const +{ + json.EndArray(); +} +#endif // _D3D12MA_BLOCK_METADATA_FUNCTIONS +#endif // _D3D12MA_BLOCK_METADATA + +#if 0 +#ifndef _D3D12MA_BLOCK_METADATA_GENERIC +class BlockMetadata_Generic : public BlockMetadata +{ +public: + BlockMetadata_Generic(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual); + virtual ~BlockMetadata_Generic() = default; + + size_t GetAllocationCount() const override { return m_Suballocations.size() - m_FreeCount; } + UINT64 GetSumFreeSize() const override { return m_SumFreeSize; } + UINT64 GetAllocationOffset(AllocHandle allocHandle) const override { return (UINT64)allocHandle - 1; } + + void Init(UINT64 size) override; + bool Validate() const override; + bool IsEmpty() const override; + void GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const override; + + bool CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + AllocationRequest* pAllocationRequest) override; + + void Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) override; + + void Free(AllocHandle allocHandle) override; + void Clear() override; + + void SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) override; + + void AddStatistics(Statistics& inoutStats) const override; + void AddDetailedStatistics(DetailedStatistics& inoutStats) const override; + void WriteAllocationInfoToJson(JsonWriter& json) const override; + +private: + UINT m_FreeCount; + UINT64 m_SumFreeSize; + SuballocationList m_Suballocations; + // Suballocations that are free and have size greater than certain threshold. + // Sorted by size, ascending. + Vector m_FreeSuballocationsBySize; + ZeroInitializedRange m_ZeroInitializedRange; + + SuballocationList::const_iterator FindAtOffset(UINT64 offset) const; + bool ValidateFreeSuballocationList() const; + + // Checks if requested suballocation with given parameters can be placed in given pFreeSuballocItem. + // If yes, fills pOffset and returns true. If no, returns false. + bool CheckAllocation( + UINT64 allocSize, + UINT64 allocAlignment, + SuballocationList::const_iterator suballocItem, + AllocHandle* pAllocHandle, + UINT64* pSumFreeSize, + UINT64* pSumItemSize, + BOOL *pZeroInitialized) const; + // Given free suballocation, it merges it with following one, which must also be free. + void MergeFreeWithNext(SuballocationList::iterator item); + // Releases given suballocation, making it free. + // Merges it with adjacent free suballocations if applicable. + // Returns iterator to new free suballocation at this place. + SuballocationList::iterator FreeSuballocation(SuballocationList::iterator suballocItem); + // Given free suballocation, it inserts it into sorted list of + // m_FreeSuballocationsBySize if it's suitable. + void RegisterFreeSuballocation(SuballocationList::iterator item); + // Given free suballocation, it removes it from sorted list of + // m_FreeSuballocationsBySize if it's suitable. + void UnregisterFreeSuballocation(SuballocationList::iterator item); + + D3D12MA_CLASS_NO_COPY(BlockMetadata_Generic) +}; + +#ifndef _D3D12MA_BLOCK_METADATA_GENERIC_FUNCTIONS +BlockMetadata_Generic::BlockMetadata_Generic(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual) + : BlockMetadata(allocationCallbacks, isVirtual), + m_FreeCount(0), + m_SumFreeSize(0), + m_Suballocations(*allocationCallbacks), + m_FreeSuballocationsBySize(*allocationCallbacks) +{ + D3D12MA_ASSERT(allocationCallbacks); +} + +void BlockMetadata_Generic::Init(UINT64 size) +{ + BlockMetadata::Init(size); + m_ZeroInitializedRange.Reset(size); + + m_FreeCount = 1; + m_SumFreeSize = size; + + Suballocation suballoc = {}; + suballoc.offset = 0; + suballoc.size = size; + suballoc.type = SUBALLOCATION_TYPE_FREE; + suballoc.privateData = NULL; + + D3D12MA_ASSERT(size > MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER); + m_Suballocations.push_back(suballoc); + SuballocationList::iterator suballocItem = m_Suballocations.end(); + --suballocItem; + m_FreeSuballocationsBySize.push_back(suballocItem); +} + +bool BlockMetadata_Generic::Validate() const +{ + D3D12MA_VALIDATE(!m_Suballocations.empty()); + + // Expected offset of new suballocation as calculated from previous ones. + UINT64 calculatedOffset = 0; + // Expected number of free suballocations as calculated from traversing their list. + UINT calculatedFreeCount = 0; + // Expected sum size of free suballocations as calculated from traversing their list. + UINT64 calculatedSumFreeSize = 0; + // Expected number of free suballocations that should be registered in + // m_FreeSuballocationsBySize calculated from traversing their list. + size_t freeSuballocationsToRegister = 0; + // True if previous visited suballocation was free. + bool prevFree = false; + + for (const auto& subAlloc : m_Suballocations) + { + // Actual offset of this suballocation doesn't match expected one. + D3D12MA_VALIDATE(subAlloc.offset == calculatedOffset); + + const bool currFree = (subAlloc.type == SUBALLOCATION_TYPE_FREE); + // Two adjacent free suballocations are invalid. They should be merged. + D3D12MA_VALIDATE(!prevFree || !currFree); + + const Allocation* const alloc = (Allocation*)subAlloc.privateData; + if (!IsVirtual()) + { + D3D12MA_VALIDATE(currFree == (alloc == NULL)); + } + + if (currFree) + { + calculatedSumFreeSize += subAlloc.size; + ++calculatedFreeCount; + if (subAlloc.size >= MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) + { + ++freeSuballocationsToRegister; + } + + // Margin required between allocations - every free space must be at least that large. + D3D12MA_VALIDATE(subAlloc.size >= GetDebugMargin()); + } + else + { + if (!IsVirtual()) + { + D3D12MA_VALIDATE(alloc->GetOffset() == subAlloc.offset); + D3D12MA_VALIDATE(alloc->GetSize() == subAlloc.size); + } + + // Margin required between allocations - previous allocation must be free. + D3D12MA_VALIDATE(GetDebugMargin() == 0 || prevFree); + } + + calculatedOffset += subAlloc.size; + prevFree = currFree; + } + + // Number of free suballocations registered in m_FreeSuballocationsBySize doesn't + // match expected one. + D3D12MA_VALIDATE(m_FreeSuballocationsBySize.size() == freeSuballocationsToRegister); + + UINT64 lastSize = 0; + for (size_t i = 0; i < m_FreeSuballocationsBySize.size(); ++i) + { + SuballocationList::iterator suballocItem = m_FreeSuballocationsBySize[i]; + + // Only free suballocations can be registered in m_FreeSuballocationsBySize. + D3D12MA_VALIDATE(suballocItem->type == SUBALLOCATION_TYPE_FREE); + // They must be sorted by size ascending. + D3D12MA_VALIDATE(suballocItem->size >= lastSize); + + lastSize = suballocItem->size; + } + + // Check if totals match calculacted values. + D3D12MA_VALIDATE(ValidateFreeSuballocationList()); + D3D12MA_VALIDATE(calculatedOffset == GetSize()); + D3D12MA_VALIDATE(calculatedSumFreeSize == m_SumFreeSize); + D3D12MA_VALIDATE(calculatedFreeCount == m_FreeCount); + + return true; +} + +bool BlockMetadata_Generic::IsEmpty() const +{ + return (m_Suballocations.size() == 1) && (m_FreeCount == 1); +} + +void BlockMetadata_Generic::GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const +{ + Suballocation& suballoc = *FindAtOffset((UINT64)allocHandle - 1).dropConst(); + outInfo.Offset = suballoc.offset; + outInfo.Size = suballoc.size; + outInfo.pPrivateData = suballoc.privateData; +} + +bool BlockMetadata_Generic::CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + AllocationRequest* pAllocationRequest) +{ + D3D12MA_ASSERT(allocSize > 0); + D3D12MA_ASSERT(!upperAddress && "ALLOCATION_FLAG_UPPER_ADDRESS can be used only with linear algorithm."); + D3D12MA_ASSERT(pAllocationRequest != NULL); + D3D12MA_HEAVY_ASSERT(Validate()); + + // There is not enough total free space in this block to fullfill the request: Early return. + if (m_SumFreeSize < allocSize + GetDebugMargin()) + { + return false; + } + + // New algorithm, efficiently searching freeSuballocationsBySize. + const size_t freeSuballocCount = m_FreeSuballocationsBySize.size(); + if (freeSuballocCount > 0) + { + // Find first free suballocation with size not less than allocSize + GetDebugMargin(). + SuballocationList::iterator* const it = BinaryFindFirstNotLess( + m_FreeSuballocationsBySize.data(), + m_FreeSuballocationsBySize.data() + freeSuballocCount, + allocSize + GetDebugMargin(), + SuballocationItemSizeLess()); + size_t index = it - m_FreeSuballocationsBySize.data(); + for (; index < freeSuballocCount; ++index) + { + if (CheckAllocation( + allocSize, + allocAlignment, + m_FreeSuballocationsBySize[index], + &pAllocationRequest->allocHandle, + &pAllocationRequest->sumFreeSize, + &pAllocationRequest->sumItemSize, + &pAllocationRequest->zeroInitialized)) + { + pAllocationRequest->item = m_FreeSuballocationsBySize[index]; + return true; + } + } + } + + return false; +} + +void BlockMetadata_Generic::Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) +{ + D3D12MA_ASSERT(request.item != m_Suballocations.end()); + Suballocation& suballoc = *request.item; + // Given suballocation is a free block. + D3D12MA_ASSERT(suballoc.type == SUBALLOCATION_TYPE_FREE); + // Given offset is inside this suballocation. + UINT64 offset = (UINT64)request.allocHandle - 1; + D3D12MA_ASSERT(offset >= suballoc.offset); + const UINT64 paddingBegin = offset - suballoc.offset; + D3D12MA_ASSERT(suballoc.size >= paddingBegin + allocSize); + const UINT64 paddingEnd = suballoc.size - paddingBegin - allocSize; + + // Unregister this free suballocation from m_FreeSuballocationsBySize and update + // it to become used. + UnregisterFreeSuballocation(request.item); + + suballoc.offset = offset; + suballoc.size = allocSize; + suballoc.type = SUBALLOCATION_TYPE_ALLOCATION; + suballoc.privateData = privateData; + + // If there are any free bytes remaining at the end, insert new free suballocation after current one. + if (paddingEnd) + { + Suballocation paddingSuballoc = {}; + paddingSuballoc.offset = offset + allocSize; + paddingSuballoc.size = paddingEnd; + paddingSuballoc.type = SUBALLOCATION_TYPE_FREE; + SuballocationList::iterator next = request.item; + ++next; + const SuballocationList::iterator paddingEndItem = + m_Suballocations.insert(next, paddingSuballoc); + RegisterFreeSuballocation(paddingEndItem); + } + + // If there are any free bytes remaining at the beginning, insert new free suballocation before current one. + if (paddingBegin) + { + Suballocation paddingSuballoc = {}; + paddingSuballoc.offset = offset - paddingBegin; + paddingSuballoc.size = paddingBegin; + paddingSuballoc.type = SUBALLOCATION_TYPE_FREE; + const SuballocationList::iterator paddingBeginItem = + m_Suballocations.insert(request.item, paddingSuballoc); + RegisterFreeSuballocation(paddingBeginItem); + } + + // Update totals. + m_FreeCount = m_FreeCount - 1; + if (paddingBegin > 0) + { + ++m_FreeCount; + } + if (paddingEnd > 0) + { + ++m_FreeCount; + } + m_SumFreeSize -= allocSize; + + m_ZeroInitializedRange.MarkRangeAsUsed(offset, offset + allocSize); +} + +void BlockMetadata_Generic::Free(AllocHandle allocHandle) +{ + FreeSuballocation(FindAtOffset((UINT64)allocHandle - 1).dropConst()); +} + +void BlockMetadata_Generic::Clear() +{ + m_FreeCount = 1; + m_SumFreeSize = GetSize(); + + m_Suballocations.clear(); + Suballocation suballoc = {}; + suballoc.offset = 0; + suballoc.size = GetSize(); + suballoc.type = SUBALLOCATION_TYPE_FREE; + m_Suballocations.push_back(suballoc); + + m_FreeSuballocationsBySize.clear(); + m_FreeSuballocationsBySize.push_back(m_Suballocations.begin()); +} + +SuballocationList::const_iterator BlockMetadata_Generic::FindAtOffset(UINT64 offset) const +{ + const UINT64 last = m_Suballocations.crbegin()->offset; + if (last == offset) + return m_Suballocations.crbegin(); + const UINT64 first = m_Suballocations.cbegin()->offset; + if (first == offset) + return m_Suballocations.cbegin(); + + const size_t suballocCount = m_Suballocations.size(); + const UINT64 step = (last - first + m_Suballocations.cbegin()->size) / suballocCount; + auto findSuballocation = [&](auto begin, auto end) -> SuballocationList::const_iterator + { + for (auto suballocItem = begin; + suballocItem != end; + ++suballocItem) + { + const Suballocation& suballoc = *suballocItem; + if (suballoc.offset == offset) + return suballocItem; + } + D3D12MA_ASSERT(false && "Not found!"); + return m_Suballocations.end(); + }; + // If requested offset is closer to the end of range, search from the end + if ((offset - first) > suballocCount * step / 2) + { + return findSuballocation(m_Suballocations.crbegin(), m_Suballocations.crend()); + } + return findSuballocation(m_Suballocations.cbegin(), m_Suballocations.cend()); +} + +bool BlockMetadata_Generic::ValidateFreeSuballocationList() const +{ + UINT64 lastSize = 0; + for (size_t i = 0, count = m_FreeSuballocationsBySize.size(); i < count; ++i) + { + const SuballocationList::iterator it = m_FreeSuballocationsBySize[i]; + + D3D12MA_VALIDATE(it->type == SUBALLOCATION_TYPE_FREE); + D3D12MA_VALIDATE(it->size >= MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER); + D3D12MA_VALIDATE(it->size >= lastSize); + lastSize = it->size; + } + return true; +} + +bool BlockMetadata_Generic::CheckAllocation( + UINT64 allocSize, + UINT64 allocAlignment, + SuballocationList::const_iterator suballocItem, + AllocHandle* pAllocHandle, + UINT64* pSumFreeSize, + UINT64* pSumItemSize, + BOOL* pZeroInitialized) const +{ + D3D12MA_ASSERT(allocSize > 0); + D3D12MA_ASSERT(suballocItem != m_Suballocations.cend()); + D3D12MA_ASSERT(pAllocHandle != NULL && pZeroInitialized != NULL); + + *pSumFreeSize = 0; + *pSumItemSize = 0; + *pZeroInitialized = FALSE; + + const Suballocation& suballoc = *suballocItem; + D3D12MA_ASSERT(suballoc.type == SUBALLOCATION_TYPE_FREE); + + *pSumFreeSize = suballoc.size; + + // Size of this suballocation is too small for this request: Early return. + if (suballoc.size < allocSize) + { + return false; + } + + // Start from offset equal to beginning of this suballocation and debug margin of previous allocation if present. + UINT64 offset = suballoc.offset + (suballocItem == m_Suballocations.cbegin() ? 0 : GetDebugMargin()); + + // Apply alignment. + offset = AlignUp(offset, allocAlignment); + + // Calculate padding at the beginning based on current offset. + const UINT64 paddingBegin = offset - suballoc.offset; + + // Fail if requested size plus margin after is bigger than size of this suballocation. + if (paddingBegin + allocSize + GetDebugMargin() > suballoc.size) + { + return false; + } + + // All tests passed: Success. Offset is already filled. + *pZeroInitialized = m_ZeroInitializedRange.IsRangeZeroInitialized(offset, offset + allocSize); + *pAllocHandle = (AllocHandle)(offset + 1); + return true; +} + +void BlockMetadata_Generic::MergeFreeWithNext(SuballocationList::iterator item) +{ + D3D12MA_ASSERT(item != m_Suballocations.end()); + D3D12MA_ASSERT(item->type == SUBALLOCATION_TYPE_FREE); + + SuballocationList::iterator nextItem = item; + ++nextItem; + D3D12MA_ASSERT(nextItem != m_Suballocations.end()); + D3D12MA_ASSERT(nextItem->type == SUBALLOCATION_TYPE_FREE); + + item->size += nextItem->size; + --m_FreeCount; + m_Suballocations.erase(nextItem); +} + +SuballocationList::iterator BlockMetadata_Generic::FreeSuballocation(SuballocationList::iterator suballocItem) +{ + // Change this suballocation to be marked as free. + Suballocation& suballoc = *suballocItem; + suballoc.type = SUBALLOCATION_TYPE_FREE; + suballoc.privateData = NULL; + + // Update totals. + ++m_FreeCount; + m_SumFreeSize += suballoc.size; + + // Merge with previous and/or next suballocation if it's also free. + bool mergeWithNext = false; + bool mergeWithPrev = false; + + SuballocationList::iterator nextItem = suballocItem; + ++nextItem; + if ((nextItem != m_Suballocations.end()) && (nextItem->type == SUBALLOCATION_TYPE_FREE)) + { + mergeWithNext = true; + } + + SuballocationList::iterator prevItem = suballocItem; + if (suballocItem != m_Suballocations.begin()) + { + --prevItem; + if (prevItem->type == SUBALLOCATION_TYPE_FREE) + { + mergeWithPrev = true; + } + } + + if (mergeWithNext) + { + UnregisterFreeSuballocation(nextItem); + MergeFreeWithNext(suballocItem); + } + + if (mergeWithPrev) + { + UnregisterFreeSuballocation(prevItem); + MergeFreeWithNext(prevItem); + RegisterFreeSuballocation(prevItem); + return prevItem; + } + else + { + RegisterFreeSuballocation(suballocItem); + return suballocItem; + } +} + +void BlockMetadata_Generic::RegisterFreeSuballocation(SuballocationList::iterator item) +{ + D3D12MA_ASSERT(item->type == SUBALLOCATION_TYPE_FREE); + D3D12MA_ASSERT(item->size > 0); + + // You may want to enable this validation at the beginning or at the end of + // this function, depending on what do you want to check. + D3D12MA_HEAVY_ASSERT(ValidateFreeSuballocationList()); + + if (item->size >= MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) + { + if (m_FreeSuballocationsBySize.empty()) + { + m_FreeSuballocationsBySize.push_back(item); + } + else + { + m_FreeSuballocationsBySize.InsertSorted(item, SuballocationItemSizeLess()); + } + } + + //D3D12MA_HEAVY_ASSERT(ValidateFreeSuballocationList()); +} + +void BlockMetadata_Generic::UnregisterFreeSuballocation(SuballocationList::iterator item) +{ + D3D12MA_ASSERT(item->type == SUBALLOCATION_TYPE_FREE); + D3D12MA_ASSERT(item->size > 0); + + // You may want to enable this validation at the beginning or at the end of + // this function, depending on what do you want to check. + D3D12MA_HEAVY_ASSERT(ValidateFreeSuballocationList()); + + if (item->size >= MIN_FREE_SUBALLOCATION_SIZE_TO_REGISTER) + { + SuballocationList::iterator* const it = BinaryFindFirstNotLess( + m_FreeSuballocationsBySize.data(), + m_FreeSuballocationsBySize.data() + m_FreeSuballocationsBySize.size(), + item, + SuballocationItemSizeLess()); + for (size_t index = it - m_FreeSuballocationsBySize.data(); + index < m_FreeSuballocationsBySize.size(); + ++index) + { + if (m_FreeSuballocationsBySize[index] == item) + { + m_FreeSuballocationsBySize.remove(index); + return; + } + D3D12MA_ASSERT((m_FreeSuballocationsBySize[index]->size == item->size) && "Not found."); + } + D3D12MA_ASSERT(0 && "Not found."); + } + + //D3D12MA_HEAVY_ASSERT(ValidateFreeSuballocationList()); +} + +void BlockMetadata_Generic::SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) +{ + Suballocation& suballoc = *FindAtOffset((UINT64)allocHandle - 1).dropConst(); + suballoc.privateData = privateData; +} + +void BlockMetadata_Generic::AddStatistics(Statistics& inoutStats) const +{ + inoutStats.BlockCount++; + inoutStats.AllocationCount += (UINT)m_Suballocations.size() - m_FreeCount; + inoutStats.BlockBytes += GetSize(); + inoutStats.AllocationBytes += GetSize() - m_SumFreeSize; +} + +void BlockMetadata_Generic::AddDetailedStatistics(DetailedStatistics& inoutStats) const +{ + inoutStats.Stats.BlockCount++; + inoutStats.Stats.BlockBytes += GetSize(); + + for (const auto& suballoc : m_Suballocations) + { + if (suballoc.type == SUBALLOCATION_TYPE_FREE) + AddDetailedStatisticsUnusedRange(inoutStats, suballoc.size); + else + AddDetailedStatisticsAllocation(inoutStats, suballoc.size); + } +} + +void BlockMetadata_Generic::WriteAllocationInfoToJson(JsonWriter& json) const +{ + PrintDetailedMap_Begin(json, GetSumFreeSize(), GetAllocationCount(), m_FreeCount); + for (const auto& suballoc : m_Suballocations) + { + if (suballoc.type == SUBALLOCATION_TYPE_FREE) + PrintDetailedMap_UnusedRange(json, suballoc.offset, suballoc.size); + else + PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.privateData); + } + PrintDetailedMap_End(json); +} +#endif // _D3D12MA_BLOCK_METADATA_GENERIC_FUNCTIONS +#endif // _D3D12MA_BLOCK_METADATA_GENERIC +#endif // #if 0 + +#ifndef _D3D12MA_BLOCK_METADATA_LINEAR +class BlockMetadata_Linear : public BlockMetadata +{ +public: + BlockMetadata_Linear(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual); + virtual ~BlockMetadata_Linear() = default; + + UINT64 GetSumFreeSize() const override { return m_SumFreeSize; } + bool IsEmpty() const override { return GetAllocationCount() == 0; } + UINT64 GetAllocationOffset(AllocHandle allocHandle) const override { return (UINT64)allocHandle - 1; }; + + void Init(UINT64 size) override; + bool Validate() const override; + size_t GetAllocationCount() const override; + size_t GetFreeRegionsCount() const override; + void GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const override; + + bool CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + UINT32 strategy, + AllocationRequest* pAllocationRequest) override; + + void Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) override; + + void Free(AllocHandle allocHandle) override; + void Clear() override; + + AllocHandle GetAllocationListBegin() const override; + AllocHandle GetNextAllocation(AllocHandle prevAlloc) const override; + UINT64 GetNextFreeRegionSize(AllocHandle alloc) const override; + void* GetAllocationPrivateData(AllocHandle allocHandle) const override; + void SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) override; + + void AddStatistics(Statistics& inoutStats) const override; + void AddDetailedStatistics(DetailedStatistics& inoutStats) const override; + void WriteAllocationInfoToJson(JsonWriter& json) const override; + +private: + /* + There are two suballocation vectors, used in ping-pong way. + The one with index m_1stVectorIndex is called 1st. + The one with index (m_1stVectorIndex ^ 1) is called 2nd. + 2nd can be non-empty only when 1st is not empty. + When 2nd is not empty, m_2ndVectorMode indicates its mode of operation. + */ + typedef Vector SuballocationVectorType; + + enum ALLOC_REQUEST_TYPE + { + ALLOC_REQUEST_UPPER_ADDRESS, + ALLOC_REQUEST_END_OF_1ST, + ALLOC_REQUEST_END_OF_2ND, + }; + + enum SECOND_VECTOR_MODE + { + SECOND_VECTOR_EMPTY, + /* + Suballocations in 2nd vector are created later than the ones in 1st, but they + all have smaller offset. + */ + SECOND_VECTOR_RING_BUFFER, + /* + Suballocations in 2nd vector are upper side of double stack. + They all have offsets higher than those in 1st vector. + Top of this stack means smaller offsets, but higher indices in this vector. + */ + SECOND_VECTOR_DOUBLE_STACK, + }; + + UINT64 m_SumFreeSize; + SuballocationVectorType m_Suballocations0, m_Suballocations1; + UINT32 m_1stVectorIndex; + SECOND_VECTOR_MODE m_2ndVectorMode; + // Number of items in 1st vector with hAllocation = null at the beginning. + size_t m_1stNullItemsBeginCount; + // Number of other items in 1st vector with hAllocation = null somewhere in the middle. + size_t m_1stNullItemsMiddleCount; + // Number of items in 2nd vector with hAllocation = null. + size_t m_2ndNullItemsCount; + + SuballocationVectorType& AccessSuballocations1st() { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; } + SuballocationVectorType& AccessSuballocations2nd() { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } + const SuballocationVectorType& AccessSuballocations1st() const { return m_1stVectorIndex ? m_Suballocations1 : m_Suballocations0; } + const SuballocationVectorType& AccessSuballocations2nd() const { return m_1stVectorIndex ? m_Suballocations0 : m_Suballocations1; } + + Suballocation& FindSuballocation(UINT64 offset) const; + bool ShouldCompact1st() const; + void CleanupAfterFree(); + + bool CreateAllocationRequest_LowerAddress( + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest); + bool CreateAllocationRequest_UpperAddress( + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest); + + D3D12MA_CLASS_NO_COPY(BlockMetadata_Linear) +}; + +#ifndef _D3D12MA_BLOCK_METADATA_LINEAR_FUNCTIONS +BlockMetadata_Linear::BlockMetadata_Linear(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual) + : BlockMetadata(allocationCallbacks, isVirtual), + m_SumFreeSize(0), + m_Suballocations0(*allocationCallbacks), + m_Suballocations1(*allocationCallbacks), + m_1stVectorIndex(0), + m_2ndVectorMode(SECOND_VECTOR_EMPTY), + m_1stNullItemsBeginCount(0), + m_1stNullItemsMiddleCount(0), + m_2ndNullItemsCount(0) +{ + D3D12MA_ASSERT(allocationCallbacks); +} + +void BlockMetadata_Linear::Init(UINT64 size) +{ + BlockMetadata::Init(size); + m_SumFreeSize = size; +} + +bool BlockMetadata_Linear::Validate() const +{ + D3D12MA_VALIDATE(GetSumFreeSize() <= GetSize()); + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + D3D12MA_VALIDATE(suballocations2nd.empty() == (m_2ndVectorMode == SECOND_VECTOR_EMPTY)); + D3D12MA_VALIDATE(!suballocations1st.empty() || + suballocations2nd.empty() || + m_2ndVectorMode != SECOND_VECTOR_RING_BUFFER); + + if (!suballocations1st.empty()) + { + // Null item at the beginning should be accounted into m_1stNullItemsBeginCount. + D3D12MA_VALIDATE(suballocations1st[m_1stNullItemsBeginCount].type != SUBALLOCATION_TYPE_FREE); + // Null item at the end should be just pop_back(). + D3D12MA_VALIDATE(suballocations1st.back().type != SUBALLOCATION_TYPE_FREE); + } + if (!suballocations2nd.empty()) + { + // Null item at the end should be just pop_back(). + D3D12MA_VALIDATE(suballocations2nd.back().type != SUBALLOCATION_TYPE_FREE); + } + + D3D12MA_VALIDATE(m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount <= suballocations1st.size()); + D3D12MA_VALIDATE(m_2ndNullItemsCount <= suballocations2nd.size()); + + UINT64 sumUsedSize = 0; + const size_t suballoc1stCount = suballocations1st.size(); + UINT64 offset = 0; + + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const size_t suballoc2ndCount = suballocations2nd.size(); + size_t nullItem2ndCount = 0; + for (size_t i = 0; i < suballoc2ndCount; ++i) + { + const Suballocation& suballoc = suballocations2nd[i]; + const bool currFree = (suballoc.type == SUBALLOCATION_TYPE_FREE); + + const Allocation* alloc = (Allocation*)suballoc.privateData; + if (!IsVirtual()) + { + D3D12MA_VALIDATE(currFree == (alloc == NULL)); + } + D3D12MA_VALIDATE(suballoc.offset >= offset); + + if (!currFree) + { + if (!IsVirtual()) + { + D3D12MA_VALIDATE(GetAllocationOffset(alloc->GetAllocHandle()) == suballoc.offset); + D3D12MA_VALIDATE(alloc->GetSize() == suballoc.size); + } + sumUsedSize += suballoc.size; + } + else + { + ++nullItem2ndCount; + } + + offset = suballoc.offset + suballoc.size + GetDebugMargin(); + } + + D3D12MA_VALIDATE(nullItem2ndCount == m_2ndNullItemsCount); + } + + for (size_t i = 0; i < m_1stNullItemsBeginCount; ++i) + { + const Suballocation& suballoc = suballocations1st[i]; + D3D12MA_VALIDATE(suballoc.type == SUBALLOCATION_TYPE_FREE && + suballoc.privateData == NULL); + } + + size_t nullItem1stCount = m_1stNullItemsBeginCount; + + for (size_t i = m_1stNullItemsBeginCount; i < suballoc1stCount; ++i) + { + const Suballocation& suballoc = suballocations1st[i]; + const bool currFree = (suballoc.type == SUBALLOCATION_TYPE_FREE); + + const Allocation* alloc = (Allocation*)suballoc.privateData; + if (!IsVirtual()) + { + D3D12MA_VALIDATE(currFree == (alloc == NULL)); + } + D3D12MA_VALIDATE(suballoc.offset >= offset); + D3D12MA_VALIDATE(i >= m_1stNullItemsBeginCount || currFree); + + if (!currFree) + { + if (!IsVirtual()) + { + D3D12MA_VALIDATE(GetAllocationOffset(alloc->GetAllocHandle()) == suballoc.offset); + D3D12MA_VALIDATE(alloc->GetSize() == suballoc.size); + } + sumUsedSize += suballoc.size; + } + else + { + ++nullItem1stCount; + } + + offset = suballoc.offset + suballoc.size + GetDebugMargin(); + } + D3D12MA_VALIDATE(nullItem1stCount == m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount); + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + const size_t suballoc2ndCount = suballocations2nd.size(); + size_t nullItem2ndCount = 0; + for (size_t i = suballoc2ndCount; i--; ) + { + const Suballocation& suballoc = suballocations2nd[i]; + const bool currFree = (suballoc.type == SUBALLOCATION_TYPE_FREE); + + const Allocation* alloc = (Allocation*)suballoc.privateData; + if (!IsVirtual()) + { + D3D12MA_VALIDATE(currFree == (alloc == NULL)); + } + D3D12MA_VALIDATE(suballoc.offset >= offset); + + if (!currFree) + { + if (!IsVirtual()) + { + D3D12MA_VALIDATE(GetAllocationOffset(alloc->GetAllocHandle()) == suballoc.offset); + D3D12MA_VALIDATE(alloc->GetSize() == suballoc.size); + } + sumUsedSize += suballoc.size; + } + else + { + ++nullItem2ndCount; + } + + offset = suballoc.offset + suballoc.size + GetDebugMargin(); + } + + D3D12MA_VALIDATE(nullItem2ndCount == m_2ndNullItemsCount); + } + + D3D12MA_VALIDATE(offset <= GetSize()); + D3D12MA_VALIDATE(m_SumFreeSize == GetSize() - sumUsedSize); + + return true; +} + +size_t BlockMetadata_Linear::GetAllocationCount() const +{ + return AccessSuballocations1st().size() - m_1stNullItemsBeginCount - m_1stNullItemsMiddleCount + + AccessSuballocations2nd().size() - m_2ndNullItemsCount; +} + +size_t BlockMetadata_Linear::GetFreeRegionsCount() const +{ + // Function only used for defragmentation, which is disabled for this algorithm + D3D12MA_ASSERT(0); + return SIZE_MAX; +} + +void BlockMetadata_Linear::GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const +{ + const Suballocation& suballoc = FindSuballocation((UINT64)allocHandle - 1); + outInfo.Offset = suballoc.offset; + outInfo.Size = suballoc.size; + outInfo.pPrivateData = suballoc.privateData; +} + +bool BlockMetadata_Linear::CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + UINT32 strategy, + AllocationRequest* pAllocationRequest) +{ + D3D12MA_ASSERT(allocSize > 0 && "Cannot allocate empty block!"); + D3D12MA_ASSERT(pAllocationRequest != NULL); + D3D12MA_HEAVY_ASSERT(Validate()); + pAllocationRequest->size = allocSize; + return upperAddress ? + CreateAllocationRequest_UpperAddress( + allocSize, allocAlignment, pAllocationRequest) : + CreateAllocationRequest_LowerAddress( + allocSize, allocAlignment, pAllocationRequest); +} + +void BlockMetadata_Linear::Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) +{ + UINT64 offset = (UINT64)request.allocHandle - 1; + const Suballocation newSuballoc = { offset, request.size, privateData, SUBALLOCATION_TYPE_ALLOCATION }; + + switch (request.algorithmData) + { + case ALLOC_REQUEST_UPPER_ADDRESS: + { + D3D12MA_ASSERT(m_2ndVectorMode != SECOND_VECTOR_RING_BUFFER && + "CRITICAL ERROR: Trying to use linear allocator as double stack while it was already used as ring buffer."); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + suballocations2nd.push_back(newSuballoc); + m_2ndVectorMode = SECOND_VECTOR_DOUBLE_STACK; + break; + } + case ALLOC_REQUEST_END_OF_1ST: + { + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + + D3D12MA_ASSERT(suballocations1st.empty() || + offset >= suballocations1st.back().offset + suballocations1st.back().size); + // Check if it fits before the end of the block. + D3D12MA_ASSERT(offset + request.size <= GetSize()); + + suballocations1st.push_back(newSuballoc); + break; + } + case ALLOC_REQUEST_END_OF_2ND: + { + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + // New allocation at the end of 2-part ring buffer, so before first allocation from 1st vector. + D3D12MA_ASSERT(!suballocations1st.empty() && + offset + request.size <= suballocations1st[m_1stNullItemsBeginCount].offset); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + switch (m_2ndVectorMode) + { + case SECOND_VECTOR_EMPTY: + // First allocation from second part ring buffer. + D3D12MA_ASSERT(suballocations2nd.empty()); + m_2ndVectorMode = SECOND_VECTOR_RING_BUFFER; + break; + case SECOND_VECTOR_RING_BUFFER: + // 2-part ring buffer is already started. + D3D12MA_ASSERT(!suballocations2nd.empty()); + break; + case SECOND_VECTOR_DOUBLE_STACK: + D3D12MA_ASSERT(0 && "CRITICAL ERROR: Trying to use linear allocator as ring buffer while it was already used as double stack."); + break; + default: + D3D12MA_ASSERT(0); + } + + suballocations2nd.push_back(newSuballoc); + break; + } + default: + D3D12MA_ASSERT(0 && "CRITICAL INTERNAL ERROR."); + } + m_SumFreeSize -= newSuballoc.size; +} + +void BlockMetadata_Linear::Free(AllocHandle allocHandle) +{ + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + UINT64 offset = (UINT64)allocHandle - 1; + + if (!suballocations1st.empty()) + { + // First allocation: Mark it as next empty at the beginning. + Suballocation& firstSuballoc = suballocations1st[m_1stNullItemsBeginCount]; + if (firstSuballoc.offset == offset) + { + firstSuballoc.type = SUBALLOCATION_TYPE_FREE; + firstSuballoc.privateData = NULL; + m_SumFreeSize += firstSuballoc.size; + ++m_1stNullItemsBeginCount; + CleanupAfterFree(); + return; + } + } + + // Last allocation in 2-part ring buffer or top of upper stack (same logic). + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER || + m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + Suballocation& lastSuballoc = suballocations2nd.back(); + if (lastSuballoc.offset == offset) + { + m_SumFreeSize += lastSuballoc.size; + suballocations2nd.pop_back(); + CleanupAfterFree(); + return; + } + } + // Last allocation in 1st vector. + else if (m_2ndVectorMode == SECOND_VECTOR_EMPTY) + { + Suballocation& lastSuballoc = suballocations1st.back(); + if (lastSuballoc.offset == offset) + { + m_SumFreeSize += lastSuballoc.size; + suballocations1st.pop_back(); + CleanupAfterFree(); + return; + } + } + + Suballocation refSuballoc; + refSuballoc.offset = offset; + // Rest of members stays uninitialized intentionally for better performance. + + // Item from the middle of 1st vector. + { + const SuballocationVectorType::iterator it = BinaryFindSorted( + suballocations1st.begin() + m_1stNullItemsBeginCount, + suballocations1st.end(), + refSuballoc, + SuballocationOffsetLess()); + if (it != suballocations1st.end()) + { + it->type = SUBALLOCATION_TYPE_FREE; + it->privateData = NULL; + ++m_1stNullItemsMiddleCount; + m_SumFreeSize += it->size; + CleanupAfterFree(); + return; + } + } + + if (m_2ndVectorMode != SECOND_VECTOR_EMPTY) + { + // Item from the middle of 2nd vector. + const SuballocationVectorType::iterator it = m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER ? + BinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, SuballocationOffsetLess()) : + BinaryFindSorted(suballocations2nd.begin(), suballocations2nd.end(), refSuballoc, SuballocationOffsetGreater()); + if (it != suballocations2nd.end()) + { + it->type = SUBALLOCATION_TYPE_FREE; + it->privateData = NULL; + ++m_2ndNullItemsCount; + m_SumFreeSize += it->size; + CleanupAfterFree(); + return; + } + } + + D3D12MA_ASSERT(0 && "Allocation to free not found in linear allocator!"); +} + +void BlockMetadata_Linear::Clear() +{ + m_SumFreeSize = GetSize(); + m_Suballocations0.clear(); + m_Suballocations1.clear(); + // Leaving m_1stVectorIndex unchanged - it doesn't matter. + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + m_1stNullItemsBeginCount = 0; + m_1stNullItemsMiddleCount = 0; + m_2ndNullItemsCount = 0; +} + +AllocHandle BlockMetadata_Linear::GetAllocationListBegin() const +{ + // Function only used for defragmentation, which is disabled for this algorithm + D3D12MA_ASSERT(0); + return (AllocHandle)0; +} + +AllocHandle BlockMetadata_Linear::GetNextAllocation(AllocHandle prevAlloc) const +{ + // Function only used for defragmentation, which is disabled for this algorithm + D3D12MA_ASSERT(0); + return (AllocHandle)0; +} + +UINT64 BlockMetadata_Linear::GetNextFreeRegionSize(AllocHandle alloc) const +{ + // Function only used for defragmentation, which is disabled for this algorithm + D3D12MA_ASSERT(0); + return 0; +} + +void* BlockMetadata_Linear::GetAllocationPrivateData(AllocHandle allocHandle) const +{ + return FindSuballocation((UINT64)allocHandle - 1).privateData; +} + +void BlockMetadata_Linear::SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) +{ + Suballocation& suballoc = FindSuballocation((UINT64)allocHandle - 1); + suballoc.privateData = privateData; +} + +void BlockMetadata_Linear::AddStatistics(Statistics& inoutStats) const +{ + inoutStats.BlockCount++; + inoutStats.AllocationCount += (UINT)GetAllocationCount(); + inoutStats.BlockBytes += GetSize(); + inoutStats.AllocationBytes += GetSize() - m_SumFreeSize; +} + +void BlockMetadata_Linear::AddDetailedStatistics(DetailedStatistics& inoutStats) const +{ + inoutStats.Stats.BlockCount++; + inoutStats.Stats.BlockBytes += GetSize(); + + const UINT64 size = GetSize(); + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + const size_t suballoc1stCount = suballocations1st.size(); + const size_t suballoc2ndCount = suballocations2nd.size(); + + UINT64 lastOffset = 0; + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const UINT64 freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; + size_t nextAlloc2ndIndex = 0; + while (lastOffset < freeSpace2ndTo1stEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc2ndIndex < suballoc2ndCount && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + ++nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex < suballoc2ndCount) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + AddDetailedStatisticsAllocation(inoutStats, suballoc.size); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc2ndIndex; + } + // We are at the end. + else + { + // There is free space from lastOffset to freeSpace2ndTo1stEnd. + if (lastOffset < freeSpace2ndTo1stEnd) + { + const UINT64 unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace2ndTo1stEnd; + } + } + } + + size_t nextAlloc1stIndex = m_1stNullItemsBeginCount; + const UINT64 freeSpace1stTo2ndEnd = + m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size; + while (lastOffset < freeSpace1stTo2ndEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc1stIndex < suballoc1stCount && + suballocations1st[nextAlloc1stIndex].privateData == NULL) + { + ++nextAlloc1stIndex; + } + + // Found non-null allocation. + if (nextAlloc1stIndex < suballoc1stCount) + { + const Suballocation& suballoc = suballocations1st[nextAlloc1stIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + AddDetailedStatisticsAllocation(inoutStats, suballoc.size); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc1stIndex; + } + // We are at the end. + else + { + // There is free space from lastOffset to freeSpace1stTo2ndEnd. + if (lastOffset < freeSpace1stTo2ndEnd) + { + const UINT64 unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace1stTo2ndEnd; + } + } + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; + while (lastOffset < size) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc2ndIndex != SIZE_MAX && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + --nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex != SIZE_MAX) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + AddDetailedStatisticsAllocation(inoutStats, suballoc.size); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + --nextAlloc2ndIndex; + } + // We are at the end. + else + { + // There is free space from lastOffset to size. + if (lastOffset < size) + { + const UINT64 unusedRangeSize = size - lastOffset; + AddDetailedStatisticsUnusedRange(inoutStats, unusedRangeSize); + } + + // End of loop. + lastOffset = size; + } + } + } +} + +void BlockMetadata_Linear::WriteAllocationInfoToJson(JsonWriter& json) const +{ + const UINT64 size = GetSize(); + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + const size_t suballoc1stCount = suballocations1st.size(); + const size_t suballoc2ndCount = suballocations2nd.size(); + + // FIRST PASS + + size_t unusedRangeCount = 0; + UINT64 usedBytes = 0; + + UINT64 lastOffset = 0; + + size_t alloc2ndCount = 0; + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const UINT64 freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; + size_t nextAlloc2ndIndex = 0; + while (lastOffset < freeSpace2ndTo1stEnd) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex < suballoc2ndCount && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + ++nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex < suballoc2ndCount) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + ++unusedRangeCount; + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++alloc2ndCount; + usedBytes += suballoc.size; + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < freeSpace2ndTo1stEnd) + { + // There is free space from lastOffset to freeSpace2ndTo1stEnd. + ++unusedRangeCount; + } + + // End of loop. + lastOffset = freeSpace2ndTo1stEnd; + } + } + } + + size_t nextAlloc1stIndex = m_1stNullItemsBeginCount; + size_t alloc1stCount = 0; + const UINT64 freeSpace1stTo2ndEnd = + m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? suballocations2nd.back().offset : size; + while (lastOffset < freeSpace1stTo2ndEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc1stIndex < suballoc1stCount && + suballocations1st[nextAlloc1stIndex].privateData == NULL) + { + ++nextAlloc1stIndex; + } + + // Found non-null allocation. + if (nextAlloc1stIndex < suballoc1stCount) + { + const Suballocation& suballoc = suballocations1st[nextAlloc1stIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + ++unusedRangeCount; + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++alloc1stCount; + usedBytes += suballoc.size; + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc1stIndex; + } + // We are at the end. + else + { + if (lastOffset < size) + { + // There is free space from lastOffset to freeSpace1stTo2ndEnd. + ++unusedRangeCount; + } + + // End of loop. + lastOffset = freeSpace1stTo2ndEnd; + } + } + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; + while (lastOffset < size) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex != SIZE_MAX && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + --nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex != SIZE_MAX) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + ++unusedRangeCount; + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + ++alloc2ndCount; + usedBytes += suballoc.size; + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + --nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < size) + { + // There is free space from lastOffset to size. + ++unusedRangeCount; + } + + // End of loop. + lastOffset = size; + } + } + } + + const UINT64 unusedBytes = size - usedBytes; + PrintDetailedMap_Begin(json, unusedBytes, alloc1stCount + alloc2ndCount, unusedRangeCount); + + // SECOND PASS + lastOffset = 0; + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + const UINT64 freeSpace2ndTo1stEnd = suballocations1st[m_1stNullItemsBeginCount].offset; + size_t nextAlloc2ndIndex = 0; + while (lastOffset < freeSpace2ndTo1stEnd) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex < suballoc2ndCount && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + ++nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex < suballoc2ndCount) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.privateData); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < freeSpace2ndTo1stEnd) + { + // There is free space from lastOffset to freeSpace2ndTo1stEnd. + const UINT64 unusedRangeSize = freeSpace2ndTo1stEnd - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace2ndTo1stEnd; + } + } + } + + nextAlloc1stIndex = m_1stNullItemsBeginCount; + while (lastOffset < freeSpace1stTo2ndEnd) + { + // Find next non-null allocation or move nextAllocIndex to the end. + while (nextAlloc1stIndex < suballoc1stCount && + suballocations1st[nextAlloc1stIndex].privateData == NULL) + { + ++nextAlloc1stIndex; + } + + // Found non-null allocation. + if (nextAlloc1stIndex < suballoc1stCount) + { + const Suballocation& suballoc = suballocations1st[nextAlloc1stIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.privateData); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + ++nextAlloc1stIndex; + } + // We are at the end. + else + { + if (lastOffset < freeSpace1stTo2ndEnd) + { + // There is free space from lastOffset to freeSpace1stTo2ndEnd. + const UINT64 unusedRangeSize = freeSpace1stTo2ndEnd - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // End of loop. + lastOffset = freeSpace1stTo2ndEnd; + } + } + + if (m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + size_t nextAlloc2ndIndex = suballocations2nd.size() - 1; + while (lastOffset < size) + { + // Find next non-null allocation or move nextAlloc2ndIndex to the end. + while (nextAlloc2ndIndex != SIZE_MAX && + suballocations2nd[nextAlloc2ndIndex].privateData == NULL) + { + --nextAlloc2ndIndex; + } + + // Found non-null allocation. + if (nextAlloc2ndIndex != SIZE_MAX) + { + const Suballocation& suballoc = suballocations2nd[nextAlloc2ndIndex]; + + // 1. Process free space before this allocation. + if (lastOffset < suballoc.offset) + { + // There is free space from lastOffset to suballoc.offset. + const UINT64 unusedRangeSize = suballoc.offset - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // 2. Process this allocation. + // There is allocation with suballoc.offset, suballoc.size. + PrintDetailedMap_Allocation(json, suballoc.offset, suballoc.size, suballoc.privateData); + + // 3. Prepare for next iteration. + lastOffset = suballoc.offset + suballoc.size; + --nextAlloc2ndIndex; + } + // We are at the end. + else + { + if (lastOffset < size) + { + // There is free space from lastOffset to size. + const UINT64 unusedRangeSize = size - lastOffset; + PrintDetailedMap_UnusedRange(json, lastOffset, unusedRangeSize); + } + + // End of loop. + lastOffset = size; + } + } + } + + PrintDetailedMap_End(json); +} + +Suballocation& BlockMetadata_Linear::FindSuballocation(UINT64 offset) const +{ + const SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + const SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + Suballocation refSuballoc; + refSuballoc.offset = offset; + // Rest of members stays uninitialized intentionally for better performance. + + // Item from the 1st vector. + { + const SuballocationVectorType::iterator it = BinaryFindSorted( + suballocations1st.cbegin() + m_1stNullItemsBeginCount, + suballocations1st.cend(), + refSuballoc, + SuballocationOffsetLess()); + if (it != suballocations1st.cend()) + { + return *it; + } + } + + if (m_2ndVectorMode != SECOND_VECTOR_EMPTY) + { + // Rest of members stays uninitialized intentionally for better performance. + const SuballocationVectorType::iterator it = m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER ? + BinaryFindSorted(suballocations2nd.cbegin(), suballocations2nd.cend(), refSuballoc, SuballocationOffsetLess()) : + BinaryFindSorted(suballocations2nd.cbegin(), suballocations2nd.cend(), refSuballoc, SuballocationOffsetGreater()); + if (it != suballocations2nd.cend()) + { + return *it; + } + } + + D3D12MA_ASSERT(0 && "Allocation not found in linear allocator!"); + return *suballocations1st.crbegin(); // Should never occur. +} + +bool BlockMetadata_Linear::ShouldCompact1st() const +{ + const size_t nullItemCount = m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount; + const size_t suballocCount = AccessSuballocations1st().size(); + return suballocCount > 32 && nullItemCount * 2 >= (suballocCount - nullItemCount) * 3; +} + +void BlockMetadata_Linear::CleanupAfterFree() +{ + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + if (IsEmpty()) + { + suballocations1st.clear(); + suballocations2nd.clear(); + m_1stNullItemsBeginCount = 0; + m_1stNullItemsMiddleCount = 0; + m_2ndNullItemsCount = 0; + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + } + else + { + const size_t suballoc1stCount = suballocations1st.size(); + const size_t nullItem1stCount = m_1stNullItemsBeginCount + m_1stNullItemsMiddleCount; + D3D12MA_ASSERT(nullItem1stCount <= suballoc1stCount); + + // Find more null items at the beginning of 1st vector. + while (m_1stNullItemsBeginCount < suballoc1stCount && + suballocations1st[m_1stNullItemsBeginCount].type == SUBALLOCATION_TYPE_FREE) + { + ++m_1stNullItemsBeginCount; + --m_1stNullItemsMiddleCount; + } + + // Find more null items at the end of 1st vector. + while (m_1stNullItemsMiddleCount > 0 && + suballocations1st.back().type == SUBALLOCATION_TYPE_FREE) + { + --m_1stNullItemsMiddleCount; + suballocations1st.pop_back(); + } + + // Find more null items at the end of 2nd vector. + while (m_2ndNullItemsCount > 0 && + suballocations2nd.back().type == SUBALLOCATION_TYPE_FREE) + { + --m_2ndNullItemsCount; + suballocations2nd.pop_back(); + } + + // Find more null items at the beginning of 2nd vector. + while (m_2ndNullItemsCount > 0 && + suballocations2nd[0].type == SUBALLOCATION_TYPE_FREE) + { + --m_2ndNullItemsCount; + suballocations2nd.remove(0); + } + + if (ShouldCompact1st()) + { + const size_t nonNullItemCount = suballoc1stCount - nullItem1stCount; + size_t srcIndex = m_1stNullItemsBeginCount; + for (size_t dstIndex = 0; dstIndex < nonNullItemCount; ++dstIndex) + { + while (suballocations1st[srcIndex].type == SUBALLOCATION_TYPE_FREE) + { + ++srcIndex; + } + if (dstIndex != srcIndex) + { + suballocations1st[dstIndex] = suballocations1st[srcIndex]; + } + ++srcIndex; + } + suballocations1st.resize(nonNullItemCount); + m_1stNullItemsBeginCount = 0; + m_1stNullItemsMiddleCount = 0; + } + + // 2nd vector became empty. + if (suballocations2nd.empty()) + { + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + } + + // 1st vector became empty. + if (suballocations1st.size() - m_1stNullItemsBeginCount == 0) + { + suballocations1st.clear(); + m_1stNullItemsBeginCount = 0; + + if (!suballocations2nd.empty() && m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + // Swap 1st with 2nd. Now 2nd is empty. + m_2ndVectorMode = SECOND_VECTOR_EMPTY; + m_1stNullItemsMiddleCount = m_2ndNullItemsCount; + while (m_1stNullItemsBeginCount < suballocations2nd.size() && + suballocations2nd[m_1stNullItemsBeginCount].type == SUBALLOCATION_TYPE_FREE) + { + ++m_1stNullItemsBeginCount; + --m_1stNullItemsMiddleCount; + } + m_2ndNullItemsCount = 0; + m_1stVectorIndex ^= 1; + } + } + } + + D3D12MA_HEAVY_ASSERT(Validate()); +} + +bool BlockMetadata_Linear::CreateAllocationRequest_LowerAddress( + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest) +{ + const UINT64 blockSize = GetSize(); + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + if (m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK) + { + // Try to allocate at the end of 1st vector. + + UINT64 resultBaseOffset = 0; + if (!suballocations1st.empty()) + { + const Suballocation& lastSuballoc = suballocations1st.back(); + resultBaseOffset = lastSuballoc.offset + lastSuballoc.size + GetDebugMargin(); + } + + // Start from offset equal to beginning of free space. + UINT64 resultOffset = resultBaseOffset; + // Apply alignment. + resultOffset = AlignUp(resultOffset, allocAlignment); + + const UINT64 freeSpaceEnd = m_2ndVectorMode == SECOND_VECTOR_DOUBLE_STACK ? + suballocations2nd.back().offset : blockSize; + + // There is enough free space at the end after alignment. + if (resultOffset + allocSize + GetDebugMargin() <= freeSpaceEnd) + { + // All tests passed: Success. + pAllocationRequest->allocHandle = (AllocHandle)(resultOffset + 1); + // pAllocationRequest->item, customData unused. + pAllocationRequest->algorithmData = ALLOC_REQUEST_END_OF_1ST; + return true; + } + } + + // Wrap-around to end of 2nd vector. Try to allocate there, watching for the + // beginning of 1st vector as the end of free space. + if (m_2ndVectorMode == SECOND_VECTOR_EMPTY || m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + D3D12MA_ASSERT(!suballocations1st.empty()); + + UINT64 resultBaseOffset = 0; + if (!suballocations2nd.empty()) + { + const Suballocation& lastSuballoc = suballocations2nd.back(); + resultBaseOffset = lastSuballoc.offset + lastSuballoc.size + GetDebugMargin(); + } + + // Start from offset equal to beginning of free space. + UINT64 resultOffset = resultBaseOffset; + + // Apply alignment. + resultOffset = AlignUp(resultOffset, allocAlignment); + + size_t index1st = m_1stNullItemsBeginCount; + // There is enough free space at the end after alignment. + if ((index1st == suballocations1st.size() && resultOffset + allocSize + GetDebugMargin() <= blockSize) || + (index1st < suballocations1st.size() && resultOffset + allocSize + GetDebugMargin() <= suballocations1st[index1st].offset)) + { + // All tests passed: Success. + pAllocationRequest->allocHandle = (AllocHandle)(resultOffset + 1); + pAllocationRequest->algorithmData = ALLOC_REQUEST_END_OF_2ND; + // pAllocationRequest->item, customData unused. + return true; + } + } + return false; +} + +bool BlockMetadata_Linear::CreateAllocationRequest_UpperAddress( + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest) +{ + const UINT64 blockSize = GetSize(); + SuballocationVectorType& suballocations1st = AccessSuballocations1st(); + SuballocationVectorType& suballocations2nd = AccessSuballocations2nd(); + + if (m_2ndVectorMode == SECOND_VECTOR_RING_BUFFER) + { + D3D12MA_ASSERT(0 && "Trying to use pool with linear algorithm as double stack, while it is already being used as ring buffer."); + return false; + } + + // Try to allocate before 2nd.back(), or end of block if 2nd.empty(). + if (allocSize > blockSize) + { + return false; + } + UINT64 resultBaseOffset = blockSize - allocSize; + if (!suballocations2nd.empty()) + { + const Suballocation& lastSuballoc = suballocations2nd.back(); + resultBaseOffset = lastSuballoc.offset - allocSize; + if (allocSize > lastSuballoc.offset) + { + return false; + } + } + + // Start from offset equal to end of free space. + UINT64 resultOffset = resultBaseOffset; + // Apply debugMargin at the end. + if (GetDebugMargin() > 0) + { + if (resultOffset < GetDebugMargin()) + { + return false; + } + resultOffset -= GetDebugMargin(); + } + + // Apply alignment. + resultOffset = AlignDown(resultOffset, allocAlignment); + // There is enough free space. + const UINT64 endOf1st = !suballocations1st.empty() ? + suballocations1st.back().offset + suballocations1st.back().size : 0; + + if (endOf1st + GetDebugMargin() <= resultOffset) + { + // All tests passed: Success. + pAllocationRequest->allocHandle = (AllocHandle)(resultOffset + 1); + // pAllocationRequest->item unused. + pAllocationRequest->algorithmData = ALLOC_REQUEST_UPPER_ADDRESS; + return true; + } + return false; +} +#endif // _D3D12MA_BLOCK_METADATA_LINEAR_FUNCTIONS +#endif // _D3D12MA_BLOCK_METADATA_LINEAR + +#ifndef _D3D12MA_BLOCK_METADATA_TLSF +class BlockMetadata_TLSF : public BlockMetadata +{ +public: + BlockMetadata_TLSF(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual); + virtual ~BlockMetadata_TLSF(); + + size_t GetAllocationCount() const override { return m_AllocCount; } + size_t GetFreeRegionsCount() const override { return m_BlocksFreeCount + 1; } + UINT64 GetSumFreeSize() const override { return m_BlocksFreeSize + m_NullBlock->size; } + bool IsEmpty() const override { return m_NullBlock->offset == 0; } + UINT64 GetAllocationOffset(AllocHandle allocHandle) const override { return ((Block*)allocHandle)->offset; }; + + void Init(UINT64 size) override; + bool Validate() const override; + void GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const override; + + bool CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + UINT32 strategy, + AllocationRequest* pAllocationRequest) override; + + void Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) override; + + void Free(AllocHandle allocHandle) override; + void Clear() override; + + AllocHandle GetAllocationListBegin() const override; + AllocHandle GetNextAllocation(AllocHandle prevAlloc) const override; + UINT64 GetNextFreeRegionSize(AllocHandle alloc) const override; + void* GetAllocationPrivateData(AllocHandle allocHandle) const override; + void SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) override; + + void AddStatistics(Statistics& inoutStats) const override; + void AddDetailedStatistics(DetailedStatistics& inoutStats) const override; + void WriteAllocationInfoToJson(JsonWriter& json) const override; + +private: + // According to original paper it should be preferable 4 or 5: + // M. Masmano, I. Ripoll, A. Crespo, and J. Real "TLSF: a New Dynamic Memory Allocator for Real-Time Systems" + // http://www.gii.upv.es/tlsf/files/ecrts04_tlsf.pdf + static const UINT8 SECOND_LEVEL_INDEX = 5; + static const UINT16 SMALL_BUFFER_SIZE = 256; + static const UINT INITIAL_BLOCK_ALLOC_COUNT = 16; + static const UINT8 MEMORY_CLASS_SHIFT = 7; + static const UINT8 MAX_MEMORY_CLASSES = 65 - MEMORY_CLASS_SHIFT; + + class Block + { + public: + UINT64 offset; + UINT64 size; + Block* prevPhysical; + Block* nextPhysical; + + void MarkFree() { prevFree = NULL; } + void MarkTaken() { prevFree = this; } + bool IsFree() const { return prevFree != this; } + void*& PrivateData() { D3D12MA_HEAVY_ASSERT(!IsFree()); return privateData; } + Block*& PrevFree() { return prevFree; } + Block*& NextFree() { D3D12MA_HEAVY_ASSERT(IsFree()); return nextFree; } + + private: + Block* prevFree; // Address of the same block here indicates that block is taken + union + { + Block* nextFree; + void* privateData; + }; + }; + + size_t m_AllocCount = 0; + // Total number of free blocks besides null block + size_t m_BlocksFreeCount = 0; + // Total size of free blocks excluding null block + UINT64 m_BlocksFreeSize = 0; + UINT32 m_IsFreeBitmap = 0; + UINT8 m_MemoryClasses = 0; + UINT32 m_InnerIsFreeBitmap[MAX_MEMORY_CLASSES]; + UINT32 m_ListsCount = 0; + /* + * 0: 0-3 lists for small buffers + * 1+: 0-(2^SLI-1) lists for normal buffers + */ + Block** m_FreeList = NULL; + PoolAllocator m_BlockAllocator; + Block* m_NullBlock = NULL; + + UINT8 SizeToMemoryClass(UINT64 size) const; + UINT16 SizeToSecondIndex(UINT64 size, UINT8 memoryClass) const; + UINT32 GetListIndex(UINT8 memoryClass, UINT16 secondIndex) const; + UINT32 GetListIndex(UINT64 size) const; + + void RemoveFreeBlock(Block* block); + void InsertFreeBlock(Block* block); + void MergeBlock(Block* block, Block* prev); + + Block* FindFreeBlock(UINT64 size, UINT32& listIndex) const; + bool CheckBlock( + Block& block, + UINT32 listIndex, + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest); + + D3D12MA_CLASS_NO_COPY(BlockMetadata_TLSF) +}; + +#ifndef _D3D12MA_BLOCK_METADATA_TLSF_FUNCTIONS +BlockMetadata_TLSF::BlockMetadata_TLSF(const ALLOCATION_CALLBACKS* allocationCallbacks, bool isVirtual) + : BlockMetadata(allocationCallbacks, isVirtual), + m_BlockAllocator(*allocationCallbacks, INITIAL_BLOCK_ALLOC_COUNT) +{ + D3D12MA_ASSERT(allocationCallbacks); +} + +BlockMetadata_TLSF::~BlockMetadata_TLSF() +{ + D3D12MA_DELETE_ARRAY(*GetAllocs(), m_FreeList, m_ListsCount); +} + +void BlockMetadata_TLSF::Init(UINT64 size) +{ + BlockMetadata::Init(size); + + m_NullBlock = m_BlockAllocator.Alloc(); + m_NullBlock->size = size; + m_NullBlock->offset = 0; + m_NullBlock->prevPhysical = NULL; + m_NullBlock->nextPhysical = NULL; + m_NullBlock->MarkFree(); + m_NullBlock->NextFree() = NULL; + m_NullBlock->PrevFree() = NULL; + UINT8 memoryClass = SizeToMemoryClass(size); + UINT16 sli = SizeToSecondIndex(size, memoryClass); + m_ListsCount = (memoryClass == 0 ? 0 : (memoryClass - 1) * (1UL << SECOND_LEVEL_INDEX) + sli) + 1; + if (IsVirtual()) + m_ListsCount += 1UL << SECOND_LEVEL_INDEX; + else + m_ListsCount += 4; + + m_MemoryClasses = memoryClass + 2; + memset(m_InnerIsFreeBitmap, 0, MAX_MEMORY_CLASSES * sizeof(UINT32)); + + m_FreeList = D3D12MA_NEW_ARRAY(*GetAllocs(), Block*, m_ListsCount); + memset(m_FreeList, 0, m_ListsCount * sizeof(Block*)); +} + +bool BlockMetadata_TLSF::Validate() const +{ + D3D12MA_VALIDATE(GetSumFreeSize() <= GetSize()); + + UINT64 calculatedSize = m_NullBlock->size; + UINT64 calculatedFreeSize = m_NullBlock->size; + size_t allocCount = 0; + size_t freeCount = 0; + + // Check integrity of free lists + for (UINT32 list = 0; list < m_ListsCount; ++list) + { + Block* block = m_FreeList[list]; + if (block != NULL) + { + D3D12MA_VALIDATE(block->IsFree()); + D3D12MA_VALIDATE(block->PrevFree() == NULL); + while (block->NextFree()) + { + D3D12MA_VALIDATE(block->NextFree()->IsFree()); + D3D12MA_VALIDATE(block->NextFree()->PrevFree() == block); + block = block->NextFree(); + } + } + } + + D3D12MA_VALIDATE(m_NullBlock->nextPhysical == NULL); + if (m_NullBlock->prevPhysical) + { + D3D12MA_VALIDATE(m_NullBlock->prevPhysical->nextPhysical == m_NullBlock); + } + + // Check all blocks + UINT64 nextOffset = m_NullBlock->offset; + for (Block* prev = m_NullBlock->prevPhysical; prev != NULL; prev = prev->prevPhysical) + { + D3D12MA_VALIDATE(prev->offset + prev->size == nextOffset); + nextOffset = prev->offset; + calculatedSize += prev->size; + + UINT32 listIndex = GetListIndex(prev->size); + if (prev->IsFree()) + { + ++freeCount; + // Check if free block belongs to free list + Block* freeBlock = m_FreeList[listIndex]; + D3D12MA_VALIDATE(freeBlock != NULL); + + bool found = false; + do + { + if (freeBlock == prev) + found = true; + + freeBlock = freeBlock->NextFree(); + } while (!found && freeBlock != NULL); + + D3D12MA_VALIDATE(found); + calculatedFreeSize += prev->size; + } + else + { + ++allocCount; + // Check if taken block is not on a free list + Block* freeBlock = m_FreeList[listIndex]; + while (freeBlock) + { + D3D12MA_VALIDATE(freeBlock != prev); + freeBlock = freeBlock->NextFree(); + } + } + + if (prev->prevPhysical) + { + D3D12MA_VALIDATE(prev->prevPhysical->nextPhysical == prev); + } + } + + D3D12MA_VALIDATE(nextOffset == 0); + D3D12MA_VALIDATE(calculatedSize == GetSize()); + D3D12MA_VALIDATE(calculatedFreeSize == GetSumFreeSize()); + D3D12MA_VALIDATE(allocCount == m_AllocCount); + D3D12MA_VALIDATE(freeCount == m_BlocksFreeCount); + + return true; +} + +void BlockMetadata_TLSF::GetAllocationInfo(AllocHandle allocHandle, VIRTUAL_ALLOCATION_INFO& outInfo) const +{ + Block* block = (Block*)allocHandle; + D3D12MA_ASSERT(!block->IsFree() && "Cannot get allocation info for free block!"); + outInfo.Offset = block->offset; + outInfo.Size = block->size; + outInfo.pPrivateData = block->PrivateData(); +} + +bool BlockMetadata_TLSF::CreateAllocationRequest( + UINT64 allocSize, + UINT64 allocAlignment, + bool upperAddress, + UINT32 strategy, + AllocationRequest* pAllocationRequest) +{ + D3D12MA_ASSERT(allocSize > 0 && "Cannot allocate empty block!"); + D3D12MA_ASSERT(!upperAddress && "ALLOCATION_FLAG_UPPER_ADDRESS can be used only with linear algorithm."); + D3D12MA_ASSERT(pAllocationRequest != NULL); + D3D12MA_HEAVY_ASSERT(Validate()); + + allocSize += GetDebugMargin(); + // Quick check for too small pool + if (allocSize > GetSumFreeSize()) + return false; + + // If no free blocks in pool then check only null block + if (m_BlocksFreeCount == 0) + return CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, pAllocationRequest); + + // Round up to the next block + UINT64 sizeForNextList = allocSize; + UINT64 smallSizeStep = SMALL_BUFFER_SIZE / (IsVirtual() ? 1 << SECOND_LEVEL_INDEX : 4); + if (allocSize > SMALL_BUFFER_SIZE) + { + sizeForNextList += (1ULL << (BitScanMSB(allocSize) - SECOND_LEVEL_INDEX)); + } + else if (allocSize > SMALL_BUFFER_SIZE - smallSizeStep) + sizeForNextList = SMALL_BUFFER_SIZE + 1; + else + sizeForNextList += smallSizeStep; + + UINT32 nextListIndex = 0; + UINT32 prevListIndex = 0; + Block* nextListBlock = NULL; + Block* prevListBlock = NULL; + + // Check blocks according to strategies + if (strategy & ALLOCATION_FLAG_STRATEGY_MIN_TIME) + { + // Quick check for larger block first + nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex); + if (nextListBlock != NULL && CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + + // If not fitted then null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, pAllocationRequest)) + return true; + + // Null block failed, search larger bucket + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + + // Failed again, check best fit bucket + prevListBlock = FindFreeBlock(allocSize, prevListIndex); + while (prevListBlock) + { + if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + prevListBlock = prevListBlock->NextFree(); + } + } + else if (strategy & ALLOCATION_FLAG_STRATEGY_MIN_MEMORY) + { + // Check best fit bucket + prevListBlock = FindFreeBlock(allocSize, prevListIndex); + while (prevListBlock) + { + if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + prevListBlock = prevListBlock->NextFree(); + } + + // If failed check null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, pAllocationRequest)) + return true; + + // Check larger bucket + nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex); + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + } + else if (strategy & ALLOCATION_FLAG_STRATEGY_MIN_OFFSET) + { + // Perform search from the start + Vector blockList(m_BlocksFreeCount, *GetAllocs()); + + size_t i = m_BlocksFreeCount; + for (Block* block = m_NullBlock->prevPhysical; block != NULL; block = block->prevPhysical) + { + if (block->IsFree() && block->size >= allocSize) + blockList[--i] = block; + } + + for (; i < m_BlocksFreeCount; ++i) + { + Block& block = *blockList[i]; + if (CheckBlock(block, GetListIndex(block.size), allocSize, allocAlignment, pAllocationRequest)) + return true; + } + + // If failed check null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, pAllocationRequest)) + return true; + + // Whole range searched, no more memory + return false; + } + else + { + // Check larger bucket + nextListBlock = FindFreeBlock(sizeForNextList, nextListIndex); + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + + // If failed check null block + if (CheckBlock(*m_NullBlock, m_ListsCount, allocSize, allocAlignment, pAllocationRequest)) + return true; + + // Check best fit bucket + prevListBlock = FindFreeBlock(allocSize, prevListIndex); + while (prevListBlock) + { + if (CheckBlock(*prevListBlock, prevListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + prevListBlock = prevListBlock->NextFree(); + } + } + + // Worst case, full search has to be done + while (++nextListIndex < m_ListsCount) + { + nextListBlock = m_FreeList[nextListIndex]; + while (nextListBlock) + { + if (CheckBlock(*nextListBlock, nextListIndex, allocSize, allocAlignment, pAllocationRequest)) + return true; + nextListBlock = nextListBlock->NextFree(); + } + } + + // No more memory sadly + return false; +} + +void BlockMetadata_TLSF::Alloc( + const AllocationRequest& request, + UINT64 allocSize, + void* privateData) +{ + // Get block and pop it from the free list + Block* currentBlock = (Block*)request.allocHandle; + UINT64 offset = request.algorithmData; + D3D12MA_ASSERT(currentBlock != NULL); + D3D12MA_ASSERT(currentBlock->offset <= offset); + + if (currentBlock != m_NullBlock) + RemoveFreeBlock(currentBlock); + + // Append missing alignment to prev block or create new one + UINT64 misssingAlignment = offset - currentBlock->offset; + if (misssingAlignment) + { + Block* prevBlock = currentBlock->prevPhysical; + D3D12MA_ASSERT(prevBlock != NULL && "There should be no missing alignment at offset 0!"); + + if (prevBlock->IsFree() && prevBlock->size != GetDebugMargin()) + { + UINT32 oldList = GetListIndex(prevBlock->size); + prevBlock->size += misssingAlignment; + // Check if new size crosses list bucket + if (oldList != GetListIndex(prevBlock->size)) + { + prevBlock->size -= misssingAlignment; + RemoveFreeBlock(prevBlock); + prevBlock->size += misssingAlignment; + InsertFreeBlock(prevBlock); + } + else + m_BlocksFreeSize += misssingAlignment; + } + else + { + Block* newBlock = m_BlockAllocator.Alloc(); + currentBlock->prevPhysical = newBlock; + prevBlock->nextPhysical = newBlock; + newBlock->prevPhysical = prevBlock; + newBlock->nextPhysical = currentBlock; + newBlock->size = misssingAlignment; + newBlock->offset = currentBlock->offset; + newBlock->MarkTaken(); + + InsertFreeBlock(newBlock); + } + + currentBlock->size -= misssingAlignment; + currentBlock->offset += misssingAlignment; + } + + UINT64 size = request.size + GetDebugMargin(); + if (currentBlock->size == size) + { + if (currentBlock == m_NullBlock) + { + // Setup new null block + m_NullBlock = m_BlockAllocator.Alloc(); + m_NullBlock->size = 0; + m_NullBlock->offset = currentBlock->offset + size; + m_NullBlock->prevPhysical = currentBlock; + m_NullBlock->nextPhysical = NULL; + m_NullBlock->MarkFree(); + m_NullBlock->PrevFree() = NULL; + m_NullBlock->NextFree() = NULL; + currentBlock->nextPhysical = m_NullBlock; + currentBlock->MarkTaken(); + } + } + else + { + D3D12MA_ASSERT(currentBlock->size > size && "Proper block already found, shouldn't find smaller one!"); + + // Create new free block + Block* newBlock = m_BlockAllocator.Alloc(); + newBlock->size = currentBlock->size - size; + newBlock->offset = currentBlock->offset + size; + newBlock->prevPhysical = currentBlock; + newBlock->nextPhysical = currentBlock->nextPhysical; + currentBlock->nextPhysical = newBlock; + currentBlock->size = size; + + if (currentBlock == m_NullBlock) + { + m_NullBlock = newBlock; + m_NullBlock->MarkFree(); + m_NullBlock->NextFree() = NULL; + m_NullBlock->PrevFree() = NULL; + currentBlock->MarkTaken(); + } + else + { + newBlock->nextPhysical->prevPhysical = newBlock; + newBlock->MarkTaken(); + InsertFreeBlock(newBlock); + } + } + currentBlock->PrivateData() = privateData; + + if (GetDebugMargin() > 0) + { + currentBlock->size -= GetDebugMargin(); + Block* newBlock = m_BlockAllocator.Alloc(); + newBlock->size = GetDebugMargin(); + newBlock->offset = currentBlock->offset + currentBlock->size; + newBlock->prevPhysical = currentBlock; + newBlock->nextPhysical = currentBlock->nextPhysical; + newBlock->MarkTaken(); + currentBlock->nextPhysical->prevPhysical = newBlock; + currentBlock->nextPhysical = newBlock; + InsertFreeBlock(newBlock); + } + ++m_AllocCount; +} + +void BlockMetadata_TLSF::Free(AllocHandle allocHandle) +{ + Block* block = (Block*)allocHandle; + Block* next = block->nextPhysical; + D3D12MA_ASSERT(!block->IsFree() && "Block is already free!"); + + --m_AllocCount; + if (GetDebugMargin() > 0) + { + RemoveFreeBlock(next); + MergeBlock(next, block); + block = next; + next = next->nextPhysical; + } + + // Try merging + Block* prev = block->prevPhysical; + if (prev != NULL && prev->IsFree() && prev->size != GetDebugMargin()) + { + RemoveFreeBlock(prev); + MergeBlock(block, prev); + } + + if (!next->IsFree()) + InsertFreeBlock(block); + else if (next == m_NullBlock) + MergeBlock(m_NullBlock, block); + else + { + RemoveFreeBlock(next); + MergeBlock(next, block); + InsertFreeBlock(next); + } +} + +void BlockMetadata_TLSF::Clear() +{ + m_AllocCount = 0; + m_BlocksFreeCount = 0; + m_BlocksFreeSize = 0; + m_IsFreeBitmap = 0; + m_NullBlock->offset = 0; + m_NullBlock->size = GetSize(); + Block* block = m_NullBlock->prevPhysical; + m_NullBlock->prevPhysical = NULL; + while (block) + { + Block* prev = block->prevPhysical; + m_BlockAllocator.Free(block); + block = prev; + } + memset(m_FreeList, 0, m_ListsCount * sizeof(Block*)); + memset(m_InnerIsFreeBitmap, 0, m_MemoryClasses * sizeof(UINT32)); +} + +AllocHandle BlockMetadata_TLSF::GetAllocationListBegin() const +{ + if (m_AllocCount == 0) + return (AllocHandle)0; + + for (Block* block = m_NullBlock->prevPhysical; block; block = block->prevPhysical) + { + if (!block->IsFree()) + return (AllocHandle)block; + } + D3D12MA_ASSERT(false && "If m_AllocCount > 0 then should find any allocation!"); + return (AllocHandle)0; +} + +AllocHandle BlockMetadata_TLSF::GetNextAllocation(AllocHandle prevAlloc) const +{ + Block* startBlock = (Block*)prevAlloc; + D3D12MA_ASSERT(!startBlock->IsFree() && "Incorrect block!"); + + for (Block* block = startBlock->prevPhysical; block; block = block->prevPhysical) + { + if (!block->IsFree()) + return (AllocHandle)block; + } + return (AllocHandle)0; +} + +UINT64 BlockMetadata_TLSF::GetNextFreeRegionSize(AllocHandle alloc) const +{ + Block* block = (Block*)alloc; + D3D12MA_ASSERT(!block->IsFree() && "Incorrect block!"); + + if (block->prevPhysical) + return block->prevPhysical->IsFree() ? block->prevPhysical->size : 0; + return 0; +} + +void* BlockMetadata_TLSF::GetAllocationPrivateData(AllocHandle allocHandle) const +{ + Block* block = (Block*)allocHandle; + D3D12MA_ASSERT(!block->IsFree() && "Cannot get user data for free block!"); + return block->PrivateData(); +} + +void BlockMetadata_TLSF::SetAllocationPrivateData(AllocHandle allocHandle, void* privateData) +{ + Block* block = (Block*)allocHandle; + D3D12MA_ASSERT(!block->IsFree() && "Trying to set user data for not allocated block!"); + block->PrivateData() = privateData; +} + +void BlockMetadata_TLSF::AddStatistics(Statistics& inoutStats) const +{ + inoutStats.BlockCount++; + inoutStats.AllocationCount += static_cast(m_AllocCount); + inoutStats.BlockBytes += GetSize(); + inoutStats.AllocationBytes += GetSize() - GetSumFreeSize(); +} + +void BlockMetadata_TLSF::AddDetailedStatistics(DetailedStatistics& inoutStats) const +{ + inoutStats.Stats.BlockCount++; + inoutStats.Stats.BlockBytes += GetSize(); + + for (Block* block = m_NullBlock->prevPhysical; block != NULL; block = block->prevPhysical) + { + if (block->IsFree()) + AddDetailedStatisticsUnusedRange(inoutStats, block->size); + else + AddDetailedStatisticsAllocation(inoutStats, block->size); + } + + if (m_NullBlock->size > 0) + AddDetailedStatisticsUnusedRange(inoutStats, m_NullBlock->size); +} + +void BlockMetadata_TLSF::WriteAllocationInfoToJson(JsonWriter& json) const +{ + size_t blockCount = m_AllocCount + m_BlocksFreeCount; + Vector blockList(blockCount, *GetAllocs()); + + size_t i = blockCount; + if (m_NullBlock->size > 0) + { + ++blockCount; + blockList.push_back(m_NullBlock); + } + for (Block* block = m_NullBlock->prevPhysical; block != NULL; block = block->prevPhysical) + { + blockList[--i] = block; + } + D3D12MA_ASSERT(i == 0); + + PrintDetailedMap_Begin(json, GetSumFreeSize(), GetAllocationCount(), m_BlocksFreeCount + static_cast(m_NullBlock->size)); + for (; i < blockCount; ++i) + { + Block* block = blockList[i]; + if (block->IsFree()) + PrintDetailedMap_UnusedRange(json, block->offset, block->size); + else + PrintDetailedMap_Allocation(json, block->offset, block->size, block->PrivateData()); + } + PrintDetailedMap_End(json); +} + +UINT8 BlockMetadata_TLSF::SizeToMemoryClass(UINT64 size) const +{ + if (size > SMALL_BUFFER_SIZE) + return BitScanMSB(size) - MEMORY_CLASS_SHIFT; + return 0; +} + +UINT16 BlockMetadata_TLSF::SizeToSecondIndex(UINT64 size, UINT8 memoryClass) const +{ + if (memoryClass == 0) + { + if (IsVirtual()) + return static_cast((size - 1) / 8); + else + return static_cast((size - 1) / 64); + } + return static_cast((size >> (memoryClass + MEMORY_CLASS_SHIFT - SECOND_LEVEL_INDEX)) ^ (1U << SECOND_LEVEL_INDEX)); +} + +UINT32 BlockMetadata_TLSF::GetListIndex(UINT8 memoryClass, UINT16 secondIndex) const +{ + if (memoryClass == 0) + return secondIndex; + + const UINT32 index = static_cast(memoryClass - 1) * (1 << SECOND_LEVEL_INDEX) + secondIndex; + if (IsVirtual()) + return index + (1 << SECOND_LEVEL_INDEX); + else + return index + 4; +} + +UINT32 BlockMetadata_TLSF::GetListIndex(UINT64 size) const +{ + UINT8 memoryClass = SizeToMemoryClass(size); + return GetListIndex(memoryClass, SizeToSecondIndex(size, memoryClass)); +} + +void BlockMetadata_TLSF::RemoveFreeBlock(Block* block) +{ + D3D12MA_ASSERT(block != m_NullBlock); + D3D12MA_ASSERT(block->IsFree()); + + if (block->NextFree() != NULL) + block->NextFree()->PrevFree() = block->PrevFree(); + if (block->PrevFree() != NULL) + block->PrevFree()->NextFree() = block->NextFree(); + else + { + UINT8 memClass = SizeToMemoryClass(block->size); + UINT16 secondIndex = SizeToSecondIndex(block->size, memClass); + UINT32 index = GetListIndex(memClass, secondIndex); + m_FreeList[index] = block->NextFree(); + if (block->NextFree() == NULL) + { + m_InnerIsFreeBitmap[memClass] &= ~(1U << secondIndex); + if (m_InnerIsFreeBitmap[memClass] == 0) + m_IsFreeBitmap &= ~(1UL << memClass); + } + } + block->MarkTaken(); + block->PrivateData() = NULL; + --m_BlocksFreeCount; + m_BlocksFreeSize -= block->size; +} + +void BlockMetadata_TLSF::InsertFreeBlock(Block* block) +{ + D3D12MA_ASSERT(block != m_NullBlock); + D3D12MA_ASSERT(!block->IsFree() && "Cannot insert block twice!"); + + UINT8 memClass = SizeToMemoryClass(block->size); + UINT16 secondIndex = SizeToSecondIndex(block->size, memClass); + UINT32 index = GetListIndex(memClass, secondIndex); + block->PrevFree() = NULL; + block->NextFree() = m_FreeList[index]; + m_FreeList[index] = block; + if (block->NextFree() != NULL) + block->NextFree()->PrevFree() = block; + else + { + m_InnerIsFreeBitmap[memClass] |= 1U << secondIndex; + m_IsFreeBitmap |= 1UL << memClass; + } + ++m_BlocksFreeCount; + m_BlocksFreeSize += block->size; +} + +void BlockMetadata_TLSF::MergeBlock(Block* block, Block* prev) +{ + D3D12MA_ASSERT(block->prevPhysical == prev && "Cannot merge seperate physical regions!"); + D3D12MA_ASSERT(!prev->IsFree() && "Cannot merge block that belongs to free list!"); + + block->offset = prev->offset; + block->size += prev->size; + block->prevPhysical = prev->prevPhysical; + if (block->prevPhysical) + block->prevPhysical->nextPhysical = block; + m_BlockAllocator.Free(prev); +} + +BlockMetadata_TLSF::Block* BlockMetadata_TLSF::FindFreeBlock(UINT64 size, UINT32& listIndex) const +{ + UINT8 memoryClass = SizeToMemoryClass(size); + UINT32 innerFreeMap = m_InnerIsFreeBitmap[memoryClass] & (~0U << SizeToSecondIndex(size, memoryClass)); + if (!innerFreeMap) + { + // Check higher levels for avaiable blocks + UINT32 freeMap = m_IsFreeBitmap & (~0UL << (memoryClass + 1)); + if (!freeMap) + return NULL; // No more memory avaible + + // Find lowest free region + memoryClass = BitScanLSB(freeMap); + innerFreeMap = m_InnerIsFreeBitmap[memoryClass]; + D3D12MA_ASSERT(innerFreeMap != 0); + } + // Find lowest free subregion + listIndex = GetListIndex(memoryClass, BitScanLSB(innerFreeMap)); + return m_FreeList[listIndex]; +} + +bool BlockMetadata_TLSF::CheckBlock( + Block& block, + UINT32 listIndex, + UINT64 allocSize, + UINT64 allocAlignment, + AllocationRequest* pAllocationRequest) +{ + D3D12MA_ASSERT(block.IsFree() && "Block is already taken!"); + + UINT64 alignedOffset = AlignUp(block.offset, allocAlignment); + if (block.size < allocSize + alignedOffset - block.offset) + return false; + + // Alloc successful + pAllocationRequest->allocHandle = (AllocHandle)█ + pAllocationRequest->size = allocSize - GetDebugMargin(); + pAllocationRequest->algorithmData = alignedOffset; + + // Place block at the start of list if it's normal block + if (listIndex != m_ListsCount && block.PrevFree()) + { + block.PrevFree()->NextFree() = block.NextFree(); + if (block.NextFree()) + block.NextFree()->PrevFree() = block.PrevFree(); + block.PrevFree() = NULL; + block.NextFree() = m_FreeList[listIndex]; + m_FreeList[listIndex] = █ + if (block.NextFree()) + block.NextFree()->PrevFree() = █ + } + + return true; +} +#endif // _D3D12MA_BLOCK_METADATA_TLSF_FUNCTIONS +#endif // _D3D12MA_BLOCK_METADATA_TLSF + +#ifndef _D3D12MA_MEMORY_BLOCK +/* +Represents a single block of device memory (heap). +Base class for inheritance. +Thread-safety: This class must be externally synchronized. +*/ +class MemoryBlock +{ +public: + // Creates the ID3D12Heap. + MemoryBlock( + AllocatorPimpl* allocator, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 size, + UINT id); + virtual ~MemoryBlock(); + + const D3D12_HEAP_PROPERTIES& GetHeapProperties() const { return m_HeapProps; } + D3D12_HEAP_FLAGS GetHeapFlags() const { return m_HeapFlags; } + UINT64 GetSize() const { return m_Size; } + UINT GetId() const { return m_Id; } + ID3D12Heap* GetHeap() const { return m_Heap; } + +protected: + AllocatorPimpl* const m_Allocator; + const D3D12_HEAP_PROPERTIES m_HeapProps; + const D3D12_HEAP_FLAGS m_HeapFlags; + const UINT64 m_Size; + const UINT m_Id; + + HRESULT Init(ID3D12ProtectedResourceSession* pProtectedSession, bool denyMsaaTextures); + +private: + ID3D12Heap* m_Heap = NULL; + + D3D12MA_CLASS_NO_COPY(MemoryBlock) +}; +#endif // _D3D12MA_MEMORY_BLOCK + +#ifndef _D3D12MA_NORMAL_BLOCK +/* +Represents a single block of device memory (heap) with all the data about its +regions (aka suballocations, Allocation), assigned and free. +Thread-safety: This class must be externally synchronized. +*/ +class NormalBlock : public MemoryBlock +{ +public: + BlockMetadata* m_pMetadata; + + NormalBlock( + AllocatorPimpl* allocator, + BlockVector* blockVector, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 size, + UINT id); + virtual ~NormalBlock(); + + BlockVector* GetBlockVector() const { return m_BlockVector; } + + // 'algorithm' should be one of the *_ALGORITHM_* flags in enums POOL_FLAGS or VIRTUAL_BLOCK_FLAGS + HRESULT Init(UINT32 algorithm, ID3D12ProtectedResourceSession* pProtectedSession, bool denyMsaaTextures); + + // Validates all data structures inside this object. If not valid, returns false. + bool Validate() const; + +private: + BlockVector* m_BlockVector; + + D3D12MA_CLASS_NO_COPY(NormalBlock) +}; +#endif // _D3D12MA_NORMAL_BLOCK + +#ifndef _D3D12MA_COMMITTED_ALLOCATION_LIST_ITEM_TRAITS +struct CommittedAllocationListItemTraits +{ + using ItemType = Allocation; + + static ItemType* GetPrev(const ItemType* item) + { + D3D12MA_ASSERT(item->m_PackedData.GetType() == Allocation::TYPE_COMMITTED || item->m_PackedData.GetType() == Allocation::TYPE_HEAP); + return item->m_Committed.prev; + } + static ItemType* GetNext(const ItemType* item) + { + D3D12MA_ASSERT(item->m_PackedData.GetType() == Allocation::TYPE_COMMITTED || item->m_PackedData.GetType() == Allocation::TYPE_HEAP); + return item->m_Committed.next; + } + static ItemType*& AccessPrev(ItemType* item) + { + D3D12MA_ASSERT(item->m_PackedData.GetType() == Allocation::TYPE_COMMITTED || item->m_PackedData.GetType() == Allocation::TYPE_HEAP); + return item->m_Committed.prev; + } + static ItemType*& AccessNext(ItemType* item) + { + D3D12MA_ASSERT(item->m_PackedData.GetType() == Allocation::TYPE_COMMITTED || item->m_PackedData.GetType() == Allocation::TYPE_HEAP); + return item->m_Committed.next; + } +}; +#endif // _D3D12MA_COMMITTED_ALLOCATION_LIST_ITEM_TRAITS + +#ifndef _D3D12MA_COMMITTED_ALLOCATION_LIST +/* +Stores linked list of Allocation objects that are of TYPE_COMMITTED or TYPE_HEAP. +Thread-safe, synchronized internally. +*/ +class CommittedAllocationList +{ +public: + CommittedAllocationList() = default; + void Init(bool useMutex, D3D12_HEAP_TYPE heapType, PoolPimpl* pool); + ~CommittedAllocationList(); + + D3D12_HEAP_TYPE GetHeapType() const { return m_HeapType; } + PoolPimpl* GetPool() const { return m_Pool; } + UINT GetMemorySegmentGroup(AllocatorPimpl* allocator) const; + + void AddStatistics(Statistics& inoutStats); + void AddDetailedStatistics(DetailedStatistics& inoutStats); + // Writes JSON array with the list of allocations. + void BuildStatsString(JsonWriter& json); + + void Register(Allocation* alloc); + void Unregister(Allocation* alloc); + +private: + using CommittedAllocationLinkedList = IntrusiveLinkedList; + + bool m_UseMutex = true; + D3D12_HEAP_TYPE m_HeapType = D3D12_HEAP_TYPE_CUSTOM; + PoolPimpl* m_Pool = NULL; + + D3D12MA_RW_MUTEX m_Mutex; + CommittedAllocationLinkedList m_AllocationList; +}; +#endif // _D3D12MA_COMMITTED_ALLOCATION_LIST + +#ifndef _D3D12M_COMMITTED_ALLOCATION_PARAMETERS +struct CommittedAllocationParameters +{ + CommittedAllocationList* m_List = NULL; + D3D12_HEAP_PROPERTIES m_HeapProperties = {}; + D3D12_HEAP_FLAGS m_HeapFlags = D3D12_HEAP_FLAG_NONE; + ID3D12ProtectedResourceSession* m_ProtectedSession = NULL; + bool m_CanAlias = false; + + bool IsValid() const { return m_List != NULL; } +}; +#endif // _D3D12M_COMMITTED_ALLOCATION_PARAMETERS + +#ifndef _D3D12MA_BLOCK_VECTOR +/* +Sequence of NormalBlock. Represents memory blocks allocated for a specific +heap type and possibly resource type (if only Tier 1 is supported). + +Synchronized internally with a mutex. +*/ +class BlockVector +{ + friend class DefragmentationContextPimpl; + D3D12MA_CLASS_NO_COPY(BlockVector) +public: + BlockVector( + AllocatorPimpl* hAllocator, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 preferredBlockSize, + size_t minBlockCount, + size_t maxBlockCount, + bool explicitBlockSize, + UINT64 minAllocationAlignment, + UINT32 algorithm, + bool denyMsaaTextures, + ID3D12ProtectedResourceSession* pProtectedSession); + ~BlockVector(); + + const D3D12_HEAP_PROPERTIES& GetHeapProperties() const { return m_HeapProps; } + D3D12_HEAP_FLAGS GetHeapFlags() const { return m_HeapFlags; } + UINT64 GetPreferredBlockSize() const { return m_PreferredBlockSize; } + UINT32 GetAlgorithm() const { return m_Algorithm; } + bool DeniesMsaaTextures() const { return m_DenyMsaaTextures; } + // To be used only while the m_Mutex is locked. Used during defragmentation. + size_t GetBlockCount() const { return m_Blocks.size(); } + // To be used only while the m_Mutex is locked. Used during defragmentation. + NormalBlock* GetBlock(size_t index) const { return m_Blocks[index]; } + D3D12MA_RW_MUTEX& GetMutex() { return m_Mutex; } + + HRESULT CreateMinBlocks(); + bool IsEmpty(); + + HRESULT Allocate( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + size_t allocationCount, + Allocation** pAllocations); + + void Free(Allocation* hAllocation); + + HRESULT CreateResource( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + const D3D12_RESOURCE_DESC& resourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + HRESULT CreateResource2( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + const D3D12_RESOURCE_DESC1& resourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + + void AddStatistics(Statistics& inoutStats); + void AddDetailedStatistics(DetailedStatistics& inoutStats); + + void WriteBlockInfoToJson(JsonWriter& json); + +private: + AllocatorPimpl* const m_hAllocator; + const D3D12_HEAP_PROPERTIES m_HeapProps; + const D3D12_HEAP_FLAGS m_HeapFlags; + const UINT64 m_PreferredBlockSize; + const size_t m_MinBlockCount; + const size_t m_MaxBlockCount; + const bool m_ExplicitBlockSize; + const UINT64 m_MinAllocationAlignment; + const UINT32 m_Algorithm; + const bool m_DenyMsaaTextures; + ID3D12ProtectedResourceSession* const m_ProtectedSession; + /* There can be at most one allocation that is completely empty - a + hysteresis to avoid pessimistic case of alternating creation and destruction + of a ID3D12Heap. */ + bool m_HasEmptyBlock; + D3D12MA_RW_MUTEX m_Mutex; + // Incrementally sorted by sumFreeSize, ascending. + Vector m_Blocks; + UINT m_NextBlockId; + bool m_IncrementalSort = true; + + // Disable incremental sorting when freeing allocations + void SetIncrementalSort(bool val) { m_IncrementalSort = val; } + + UINT64 CalcSumBlockSize() const; + UINT64 CalcMaxBlockSize() const; + + // Finds and removes given block from vector. + void Remove(NormalBlock* pBlock); + + // Performs single step in sorting m_Blocks. They may not be fully sorted + // after this call. + void IncrementallySortBlocks(); + void SortByFreeSize(); + + HRESULT AllocatePage( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + Allocation** pAllocation); + + HRESULT AllocateFromBlock( + NormalBlock* pBlock, + UINT64 size, + UINT64 alignment, + ALLOCATION_FLAGS allocFlags, + void* pPrivateData, + UINT32 strategy, + Allocation** pAllocation); + + HRESULT CommitAllocationRequest( + AllocationRequest& allocRequest, + NormalBlock* pBlock, + UINT64 size, + UINT64 alignment, + void* pPrivateData, + Allocation** pAllocation); + + HRESULT CreateBlock( + UINT64 blockSize, + size_t* pNewBlockIndex); +}; +#endif // _D3D12MA_BLOCK_VECTOR + +#ifndef _D3D12MA_CURRENT_BUDGET_DATA +class CurrentBudgetData +{ +public: + bool ShouldUpdateBudget() const { return m_OperationsSinceBudgetFetch >= 30; } + + void GetStatistics(Statistics& outStats, UINT group) const; + void GetBudget(bool useMutex, + UINT64* outLocalUsage, UINT64* outLocalBudget, + UINT64* outNonLocalUsage, UINT64* outNonLocalBudget); + +#if D3D12MA_DXGI_1_4 + HRESULT UpdateBudget(IDXGIAdapter3* adapter3, bool useMutex); +#endif + + void AddAllocation(UINT group, UINT64 allocationBytes); + void RemoveAllocation(UINT group, UINT64 allocationBytes); + + void AddBlock(UINT group, UINT64 blockBytes); + void RemoveBlock(UINT group, UINT64 blockBytes); + +private: + D3D12MA_ATOMIC_UINT32 m_BlockCount[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + D3D12MA_ATOMIC_UINT32 m_AllocationCount[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + D3D12MA_ATOMIC_UINT64 m_BlockBytes[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + D3D12MA_ATOMIC_UINT64 m_AllocationBytes[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + + D3D12MA_ATOMIC_UINT32 m_OperationsSinceBudgetFetch = 0; + D3D12MA_RW_MUTEX m_BudgetMutex; + UINT64 m_D3D12Usage[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + UINT64 m_D3D12Budget[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; + UINT64 m_BlockBytesAtD3D12Fetch[DXGI_MEMORY_SEGMENT_GROUP_COUNT] = {}; +}; + +#ifndef _D3D12MA_CURRENT_BUDGET_DATA_FUNCTIONS +void CurrentBudgetData::GetStatistics(Statistics& outStats, UINT group) const +{ + outStats.BlockCount = m_BlockCount[group]; + outStats.AllocationCount = m_AllocationCount[group]; + outStats.BlockBytes = m_BlockBytes[group]; + outStats.AllocationBytes = m_AllocationBytes[group]; +} + +void CurrentBudgetData::GetBudget(bool useMutex, + UINT64* outLocalUsage, UINT64* outLocalBudget, + UINT64* outNonLocalUsage, UINT64* outNonLocalBudget) +{ + MutexLockRead lockRead(m_BudgetMutex, useMutex); + + if (outLocalUsage) + { + const UINT64 D3D12Usage = m_D3D12Usage[DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY]; + const UINT64 blockBytes = m_BlockBytes[DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY]; + const UINT64 blockBytesAtD3D12Fetch = m_BlockBytesAtD3D12Fetch[DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY]; + *outLocalUsage = D3D12Usage + blockBytes > blockBytesAtD3D12Fetch ? + D3D12Usage + blockBytes - blockBytesAtD3D12Fetch : 0; + } + if (outLocalBudget) + *outLocalBudget = m_D3D12Budget[DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY]; + + if (outNonLocalUsage) + { + const UINT64 D3D12Usage = m_D3D12Usage[DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY]; + const UINT64 blockBytes = m_BlockBytes[DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY]; + const UINT64 blockBytesAtD3D12Fetch = m_BlockBytesAtD3D12Fetch[DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY]; + *outNonLocalUsage = D3D12Usage + blockBytes > blockBytesAtD3D12Fetch ? + D3D12Usage + blockBytes - blockBytesAtD3D12Fetch : 0; + } + if (outNonLocalBudget) + *outNonLocalBudget = m_D3D12Budget[DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY]; +} + +#if D3D12MA_DXGI_1_4 +HRESULT CurrentBudgetData::UpdateBudget(IDXGIAdapter3* adapter3, bool useMutex) +{ + D3D12MA_ASSERT(adapter3); + + DXGI_QUERY_VIDEO_MEMORY_INFO infoLocal = {}; + DXGI_QUERY_VIDEO_MEMORY_INFO infoNonLocal = {}; + const HRESULT hrLocal = adapter3->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &infoLocal); + const HRESULT hrNonLocal = adapter3->QueryVideoMemoryInfo(0, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL, &infoNonLocal); + + if (SUCCEEDED(hrLocal) || SUCCEEDED(hrNonLocal)) + { + MutexLockWrite lockWrite(m_BudgetMutex, useMutex); + + if (SUCCEEDED(hrLocal)) + { + m_D3D12Usage[0] = infoLocal.CurrentUsage; + m_D3D12Budget[0] = infoLocal.Budget; + } + if (SUCCEEDED(hrNonLocal)) + { + m_D3D12Usage[1] = infoNonLocal.CurrentUsage; + m_D3D12Budget[1] = infoNonLocal.Budget; + } + + m_BlockBytesAtD3D12Fetch[0] = m_BlockBytes[0]; + m_BlockBytesAtD3D12Fetch[1] = m_BlockBytes[1]; + m_OperationsSinceBudgetFetch = 0; + } + + return FAILED(hrLocal) ? hrLocal : hrNonLocal; +} +#endif // #if D3D12MA_DXGI_1_4 + +void CurrentBudgetData::AddAllocation(UINT group, UINT64 allocationBytes) +{ + ++m_AllocationCount[group]; + m_AllocationBytes[group] += allocationBytes; + ++m_OperationsSinceBudgetFetch; +} + +void CurrentBudgetData::RemoveAllocation(UINT group, UINT64 allocationBytes) +{ + D3D12MA_ASSERT(m_AllocationBytes[group] >= allocationBytes); + D3D12MA_ASSERT(m_AllocationCount[group] > 0); + m_AllocationBytes[group] -= allocationBytes; + --m_AllocationCount[group]; + ++m_OperationsSinceBudgetFetch; +} + +void CurrentBudgetData::AddBlock(UINT group, UINT64 blockBytes) +{ + ++m_BlockCount[group]; + m_BlockBytes[group] += blockBytes; + ++m_OperationsSinceBudgetFetch; +} + +void CurrentBudgetData::RemoveBlock(UINT group, UINT64 blockBytes) +{ + D3D12MA_ASSERT(m_BlockBytes[group] >= blockBytes); + D3D12MA_ASSERT(m_BlockCount[group] > 0); + m_BlockBytes[group] -= blockBytes; + --m_BlockCount[group]; + ++m_OperationsSinceBudgetFetch; +} +#endif // _D3D12MA_CURRENT_BUDGET_DATA_FUNCTIONS +#endif // _D3D12MA_CURRENT_BUDGET_DATA + +#ifndef _D3D12MA_DEFRAGMENTATION_CONTEXT_PIMPL +class DefragmentationContextPimpl +{ + D3D12MA_CLASS_NO_COPY(DefragmentationContextPimpl) +public: + DefragmentationContextPimpl( + AllocatorPimpl* hAllocator, + const DEFRAGMENTATION_DESC& desc, + BlockVector* poolVector); + ~DefragmentationContextPimpl(); + + void GetStats(DEFRAGMENTATION_STATS& outStats) { outStats = m_GlobalStats; } + const ALLOCATION_CALLBACKS& GetAllocs() const { return m_Moves.GetAllocs(); } + + HRESULT DefragmentPassBegin(DEFRAGMENTATION_PASS_MOVE_INFO& moveInfo); + HRESULT DefragmentPassEnd(DEFRAGMENTATION_PASS_MOVE_INFO& moveInfo); + +private: + // Max number of allocations to ignore due to size constraints before ending single pass + static const UINT8 MAX_ALLOCS_TO_IGNORE = 16; + enum class CounterStatus { Pass, Ignore, End }; + + struct FragmentedBlock + { + UINT32 data; + NormalBlock* block; + }; + struct StateBalanced + { + UINT64 avgFreeSize = 0; + UINT64 avgAllocSize = UINT64_MAX; + }; + struct MoveAllocationData + { + UINT64 size; + UINT64 alignment; + ALLOCATION_FLAGS flags; + DEFRAGMENTATION_MOVE move = {}; + }; + + const UINT64 m_MaxPassBytes; + const UINT32 m_MaxPassAllocations; + + Vector m_Moves; + + UINT8 m_IgnoredAllocs = 0; + UINT32 m_Algorithm; + UINT32 m_BlockVectorCount; + BlockVector* m_PoolBlockVector; + BlockVector** m_pBlockVectors; + size_t m_ImmovableBlockCount = 0; + DEFRAGMENTATION_STATS m_GlobalStats = { 0 }; + DEFRAGMENTATION_STATS m_PassStats = { 0 }; + void* m_AlgorithmState = NULL; + + static MoveAllocationData GetMoveData(AllocHandle handle, BlockMetadata* metadata); + CounterStatus CheckCounters(UINT64 bytes); + bool IncrementCounters(UINT64 bytes); + bool ReallocWithinBlock(BlockVector& vector, NormalBlock* block); + bool AllocInOtherBlock(size_t start, size_t end, MoveAllocationData& data, BlockVector& vector); + + bool ComputeDefragmentation(BlockVector& vector, size_t index); + bool ComputeDefragmentation_Fast(BlockVector& vector); + bool ComputeDefragmentation_Balanced(BlockVector& vector, size_t index, bool update); + bool ComputeDefragmentation_Full(BlockVector& vector); + + void UpdateVectorStatistics(BlockVector& vector, StateBalanced& state); +}; +#endif // _D3D12MA_DEFRAGMENTATION_CONTEXT_PIMPL + +#ifndef _D3D12MA_POOL_PIMPL +class PoolPimpl +{ + friend class Allocator; + friend struct PoolListItemTraits; +public: + PoolPimpl(AllocatorPimpl* allocator, const POOL_DESC& desc); + ~PoolPimpl(); + + AllocatorPimpl* GetAllocator() const { return m_Allocator; } + const POOL_DESC& GetDesc() const { return m_Desc; } + bool SupportsCommittedAllocations() const { return m_Desc.BlockSize == 0; } + LPCWSTR GetName() const { return m_Name; } + + BlockVector* GetBlockVector() { return m_BlockVector; } + CommittedAllocationList* GetCommittedAllocationList() { return SupportsCommittedAllocations() ? &m_CommittedAllocations : NULL; } + + HRESULT Init(); + void GetStatistics(Statistics& outStats); + void CalculateStatistics(DetailedStatistics& outStats); + void AddDetailedStatistics(DetailedStatistics& inoutStats); + void SetName(LPCWSTR Name); + +private: + AllocatorPimpl* m_Allocator; // Externally owned object. + POOL_DESC m_Desc; + BlockVector* m_BlockVector; // Owned object. + CommittedAllocationList m_CommittedAllocations; + wchar_t* m_Name; + PoolPimpl* m_PrevPool = NULL; + PoolPimpl* m_NextPool = NULL; + + void FreeName(); +}; + +struct PoolListItemTraits +{ + using ItemType = PoolPimpl; + static ItemType* GetPrev(const ItemType* item) { return item->m_PrevPool; } + static ItemType* GetNext(const ItemType* item) { return item->m_NextPool; } + static ItemType*& AccessPrev(ItemType* item) { return item->m_PrevPool; } + static ItemType*& AccessNext(ItemType* item) { return item->m_NextPool; } +}; +#endif // _D3D12MA_POOL_PIMPL + + +#ifndef _D3D12MA_ALLOCATOR_PIMPL +class AllocatorPimpl +{ + friend class Allocator; + friend class Pool; +public: + std::atomic_uint32_t m_RefCount = 1; + CurrentBudgetData m_Budget; + + AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, const ALLOCATOR_DESC& desc); + ~AllocatorPimpl(); + + ID3D12Device* GetDevice() const { return m_Device; } +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + ID3D12Device4* GetDevice4() const { return m_Device4; } +#endif +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + ID3D12Device8* GetDevice8() const { return m_Device8; } +#endif + // Shortcut for "Allocation Callbacks", because this function is called so often. + const ALLOCATION_CALLBACKS& GetAllocs() const { return m_AllocationCallbacks; } + const D3D12_FEATURE_DATA_D3D12_OPTIONS& GetD3D12Options() const { return m_D3D12Options; } + BOOL IsUMA() const { return m_D3D12Architecture.UMA; } + BOOL IsCacheCoherentUMA() const { return m_D3D12Architecture.CacheCoherentUMA; } + bool SupportsResourceHeapTier2() const { return m_D3D12Options.ResourceHeapTier >= D3D12_RESOURCE_HEAP_TIER_2; } + bool UseMutex() const { return m_UseMutex; } + AllocationObjectAllocator& GetAllocationObjectAllocator() { return m_AllocationObjectAllocator; } + UINT GetCurrentFrameIndex() const { return m_CurrentFrameIndex.load(); } + /* + If SupportsResourceHeapTier2(): + 0: D3D12_HEAP_TYPE_DEFAULT + 1: D3D12_HEAP_TYPE_UPLOAD + 2: D3D12_HEAP_TYPE_READBACK + else: + 0: D3D12_HEAP_TYPE_DEFAULT + buffer + 1: D3D12_HEAP_TYPE_DEFAULT + texture + 2: D3D12_HEAP_TYPE_DEFAULT + texture RT or DS + 3: D3D12_HEAP_TYPE_UPLOAD + buffer + 4: D3D12_HEAP_TYPE_UPLOAD + texture + 5: D3D12_HEAP_TYPE_UPLOAD + texture RT or DS + 6: D3D12_HEAP_TYPE_READBACK + buffer + 7: D3D12_HEAP_TYPE_READBACK + texture + 8: D3D12_HEAP_TYPE_READBACK + texture RT or DS + */ + UINT GetDefaultPoolCount() const { return SupportsResourceHeapTier2() ? 3 : 9; } + BlockVector** GetDefaultPools() { return m_BlockVectors; } + + HRESULT Init(const ALLOCATOR_DESC& desc); + bool HeapFlagsFulfillResourceHeapTier(D3D12_HEAP_FLAGS flags) const; + UINT StandardHeapTypeToMemorySegmentGroup(D3D12_HEAP_TYPE heapType) const; + UINT HeapPropertiesToMemorySegmentGroup(const D3D12_HEAP_PROPERTIES& heapProps) const; + UINT64 GetMemoryCapacity(UINT memorySegmentGroup) const; + + HRESULT CreateResource( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + HRESULT CreateResource2( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + + HRESULT CreateAliasingResource( + Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + REFIID riidResource, + void** ppvResource); + + HRESULT AllocateMemory( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + Allocation** ppAllocation); + + // Unregisters allocation from the collection of dedicated allocations. + // Allocation object must be deleted externally afterwards. + void FreeCommittedMemory(Allocation* allocation); + // Unregisters allocation from the collection of placed allocations. + // Allocation object must be deleted externally afterwards. + void FreePlacedMemory(Allocation* allocation); + // Unregisters allocation from the collection of dedicated allocations and destroys associated heap. + // Allocation object must be deleted externally afterwards. + void FreeHeapMemory(Allocation* allocation); + + void SetCurrentFrameIndex(UINT frameIndex); + // For more deailed stats use outCutomHeaps to access statistics divided into L0 and L1 group + void CalculateStatistics(TotalStatistics& outStats, DetailedStatistics outCutomHeaps[2] = NULL); + + void GetBudget(Budget* outLocalBudget, Budget* outNonLocalBudget); + void GetBudgetForHeapType(Budget& outBudget, D3D12_HEAP_TYPE heapType); + + void BuildStatsString(WCHAR** ppStatsString, BOOL detailedMap); + void FreeStatsString(WCHAR* pStatsString); + +private: + using PoolList = IntrusiveLinkedList; + + const bool m_UseMutex; + const bool m_AlwaysCommitted; + const bool m_MsaaAlwaysCommitted; + ID3D12Device* m_Device; // AddRef +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + ID3D12Device4* m_Device4 = NULL; // AddRef, optional +#endif +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + ID3D12Device8* m_Device8 = NULL; // AddRef, optional +#endif + IDXGIAdapter* m_Adapter; // AddRef +#if D3D12MA_DXGI_1_4 + IDXGIAdapter3* m_Adapter3 = NULL; // AddRef, optional +#endif + UINT64 m_PreferredBlockSize; + ALLOCATION_CALLBACKS m_AllocationCallbacks; + D3D12MA_ATOMIC_UINT32 m_CurrentFrameIndex; + DXGI_ADAPTER_DESC m_AdapterDesc; + D3D12_FEATURE_DATA_D3D12_OPTIONS m_D3D12Options; + D3D12_FEATURE_DATA_ARCHITECTURE m_D3D12Architecture; + AllocationObjectAllocator m_AllocationObjectAllocator; + + D3D12MA_RW_MUTEX m_PoolsMutex[HEAP_TYPE_COUNT]; + PoolList m_Pools[HEAP_TYPE_COUNT]; + // Default pools. + BlockVector* m_BlockVectors[DEFAULT_POOL_MAX_COUNT]; + CommittedAllocationList m_CommittedAllocations[STANDARD_HEAP_TYPE_COUNT]; + + /* + Heuristics that decides whether a resource should better be placed in its own, + dedicated allocation (committed resource rather than placed resource). + */ + template + static bool PrefersCommittedAllocation(const D3D12_RESOURCE_DESC_T& resourceDesc); + + // Allocates and registers new committed resource with implicit heap, as dedicated allocation. + // Creates and returns Allocation object and optionally D3D12 resource. + HRESULT AllocateCommittedResource( + const CommittedAllocationParameters& committedAllocParams, + UINT64 resourceSize, bool withinBudget, void* pPrivateData, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, + Allocation** ppAllocation, REFIID riidResource, void** ppvResource); + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + HRESULT AllocateCommittedResource2( + const CommittedAllocationParameters& committedAllocParams, + UINT64 resourceSize, bool withinBudget, void* pPrivateData, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE *pOptimizedClearValue, + Allocation** ppAllocation, REFIID riidResource, void** ppvResource); +#endif + + // Allocates and registers new heap without any resources placed in it, as dedicated allocation. + // Creates and returns Allocation object. + HRESULT AllocateHeap( + const CommittedAllocationParameters& committedAllocParams, + const D3D12_RESOURCE_ALLOCATION_INFO& allocInfo, bool withinBudget, + void* pPrivateData, Allocation** ppAllocation); + + template + HRESULT CalcAllocationParams(const ALLOCATION_DESC& allocDesc, UINT64 allocSize, + const D3D12_RESOURCE_DESC_T* resDesc, // Optional + BlockVector*& outBlockVector, CommittedAllocationParameters& outCommittedAllocationParams, bool& outPreferCommitted); + + // Returns UINT32_MAX if index cannot be calculcated. + UINT CalcDefaultPoolIndex(const ALLOCATION_DESC& allocDesc, ResourceClass resourceClass) const; + void CalcDefaultPoolParams(D3D12_HEAP_TYPE& outHeapType, D3D12_HEAP_FLAGS& outHeapFlags, UINT index) const; + + // Registers Pool object in m_Pools. + void RegisterPool(Pool* pool, D3D12_HEAP_TYPE heapType); + // Unregisters Pool object from m_Pools. + void UnregisterPool(Pool* pool, D3D12_HEAP_TYPE heapType); + + HRESULT UpdateD3D12Budget(); + + D3D12_RESOURCE_ALLOCATION_INFO GetResourceAllocationInfoNative(const D3D12_RESOURCE_DESC& resourceDesc) const; +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + D3D12_RESOURCE_ALLOCATION_INFO GetResourceAllocationInfoNative(const D3D12_RESOURCE_DESC1& resourceDesc) const; +#endif + + template + D3D12_RESOURCE_ALLOCATION_INFO GetResourceAllocationInfo(D3D12_RESOURCE_DESC_T& inOutResourceDesc) const; + + bool NewAllocationWithinBudget(D3D12_HEAP_TYPE heapType, UINT64 size); + + // Writes object { } with data of given budget. + static void WriteBudgetToJson(JsonWriter& json, const Budget& budget); +}; + +#ifndef _D3D12MA_ALLOCATOR_PIMPL_FUNCTINOS +AllocatorPimpl::AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, const ALLOCATOR_DESC& desc) + : m_UseMutex((desc.Flags & ALLOCATOR_FLAG_SINGLETHREADED) == 0), + m_AlwaysCommitted((desc.Flags & ALLOCATOR_FLAG_ALWAYS_COMMITTED) != 0), + m_MsaaAlwaysCommitted((desc.Flags & ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED) != 0), + m_Device(desc.pDevice), + m_Adapter(desc.pAdapter), + m_PreferredBlockSize(desc.PreferredBlockSize != 0 ? desc.PreferredBlockSize : D3D12MA_DEFAULT_BLOCK_SIZE), + m_AllocationCallbacks(allocationCallbacks), + m_CurrentFrameIndex(0), + // Below this line don't use allocationCallbacks but m_AllocationCallbacks!!! + m_AllocationObjectAllocator(m_AllocationCallbacks) +{ + // desc.pAllocationCallbacks intentionally ignored here, preprocessed by CreateAllocator. + ZeroMemory(&m_D3D12Options, sizeof(m_D3D12Options)); + ZeroMemory(&m_D3D12Architecture, sizeof(m_D3D12Architecture)); + + ZeroMemory(m_BlockVectors, sizeof(m_BlockVectors)); + + for (UINT i = 0; i < STANDARD_HEAP_TYPE_COUNT; ++i) + { + m_CommittedAllocations[i].Init( + m_UseMutex, + (D3D12_HEAP_TYPE)(D3D12_HEAP_TYPE_DEFAULT + i), + NULL); // pool + } + + m_Device->AddRef(); + m_Adapter->AddRef(); +} + +HRESULT AllocatorPimpl::Init(const ALLOCATOR_DESC& desc) +{ +#if D3D12MA_DXGI_1_4 + desc.pAdapter->QueryInterface(D3D12MA_IID_PPV_ARGS(&m_Adapter3)); +#endif + +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + m_Device->QueryInterface(D3D12MA_IID_PPV_ARGS(&m_Device4)); +#endif + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + m_Device->QueryInterface(D3D12MA_IID_PPV_ARGS(&m_Device8)); +#endif + + HRESULT hr = m_Adapter->GetDesc(&m_AdapterDesc); + if (FAILED(hr)) + { + return hr; + } + + hr = m_Device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &m_D3D12Options, sizeof(m_D3D12Options)); + if (FAILED(hr)) + { + return hr; + } +#ifdef D3D12MA_FORCE_RESOURCE_HEAP_TIER + m_D3D12Options.ResourceHeapTier = (D3D12MA_FORCE_RESOURCE_HEAP_TIER); +#endif + + hr = m_Device->CheckFeatureSupport(D3D12_FEATURE_ARCHITECTURE, &m_D3D12Architecture, sizeof(m_D3D12Architecture)); + if (FAILED(hr)) + { + m_D3D12Architecture.UMA = FALSE; + m_D3D12Architecture.CacheCoherentUMA = FALSE; + } + + D3D12_HEAP_PROPERTIES heapProps = {}; + const UINT defaultPoolCount = GetDefaultPoolCount(); + for (UINT i = 0; i < defaultPoolCount; ++i) + { + D3D12_HEAP_FLAGS heapFlags; + CalcDefaultPoolParams(heapProps.Type, heapFlags, i); + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + if (desc.Flags & ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED) + heapFlags |= D3D12_HEAP_FLAG_CREATE_NOT_ZEROED; +#endif + + m_BlockVectors[i] = D3D12MA_NEW(GetAllocs(), BlockVector)( + this, // hAllocator + heapProps, // heapType + heapFlags, // heapFlags + m_PreferredBlockSize, + 0, // minBlockCount + SIZE_MAX, // maxBlockCount + false, // explicitBlockSize + D3D12MA_DEBUG_ALIGNMENT, // minAllocationAlignment + 0, // Default algorithm, + m_MsaaAlwaysCommitted, + NULL); // pProtectedSession + // No need to call m_pBlockVectors[i]->CreateMinBlocks here, becase minBlockCount is 0. + } + +#if D3D12MA_DXGI_1_4 + UpdateD3D12Budget(); +#endif + + return S_OK; +} + +AllocatorPimpl::~AllocatorPimpl() +{ +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + SAFE_RELEASE(m_Device8); +#endif +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + SAFE_RELEASE(m_Device4); +#endif +#if D3D12MA_DXGI_1_4 + SAFE_RELEASE(m_Adapter3); +#endif + SAFE_RELEASE(m_Adapter); + SAFE_RELEASE(m_Device); + + for (UINT i = DEFAULT_POOL_MAX_COUNT; i--; ) + { + D3D12MA_DELETE(GetAllocs(), m_BlockVectors[i]); + } + + for (UINT i = HEAP_TYPE_COUNT; i--; ) + { + if (!m_Pools[i].IsEmpty()) + { + D3D12MA_ASSERT(0 && "Unfreed pools found!"); + } + } +} + +bool AllocatorPimpl::HeapFlagsFulfillResourceHeapTier(D3D12_HEAP_FLAGS flags) const +{ + if (SupportsResourceHeapTier2()) + { + return true; + } + else + { + const bool allowBuffers = (flags & D3D12_HEAP_FLAG_DENY_BUFFERS) == 0; + const bool allowRtDsTextures = (flags & D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES) == 0; + const bool allowNonRtDsTextures = (flags & D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES) == 0; + const uint8_t allowedGroupCount = (allowBuffers ? 1 : 0) + (allowRtDsTextures ? 1 : 0) + (allowNonRtDsTextures ? 1 : 0); + return allowedGroupCount == 1; + } +} + +UINT AllocatorPimpl::StandardHeapTypeToMemorySegmentGroup(D3D12_HEAP_TYPE heapType) const +{ + D3D12MA_ASSERT(IsHeapTypeStandard(heapType)); + if (IsUMA()) + return DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY; + return heapType == D3D12_HEAP_TYPE_DEFAULT ? + DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY : DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY; +} + +UINT AllocatorPimpl::HeapPropertiesToMemorySegmentGroup(const D3D12_HEAP_PROPERTIES& heapProps) const +{ + if (IsUMA()) + return DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY; + if (heapProps.MemoryPoolPreference == D3D12_MEMORY_POOL_UNKNOWN) + return StandardHeapTypeToMemorySegmentGroup(heapProps.Type); + return heapProps.MemoryPoolPreference == D3D12_MEMORY_POOL_L1 ? + DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY : DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY; +} + +UINT64 AllocatorPimpl::GetMemoryCapacity(UINT memorySegmentGroup) const +{ + switch (memorySegmentGroup) + { + case DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY: + return IsUMA() ? + m_AdapterDesc.DedicatedVideoMemory + m_AdapterDesc.SharedSystemMemory : m_AdapterDesc.DedicatedVideoMemory; + case DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY: + return IsUMA() ? 0 : m_AdapterDesc.SharedSystemMemory; + default: + D3D12MA_ASSERT(0); + return UINT64_MAX; + } +} + +HRESULT AllocatorPimpl::CreateResource( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + D3D12MA_ASSERT(pAllocDesc && pResourceDesc && ppAllocation); + + *ppAllocation = NULL; + if (ppvResource) + { + *ppvResource = NULL; + } + + D3D12_RESOURCE_DESC finalResourceDesc = *pResourceDesc; + D3D12_RESOURCE_ALLOCATION_INFO resAllocInfo = GetResourceAllocationInfo(finalResourceDesc); + D3D12MA_ASSERT(IsPow2(resAllocInfo.Alignment)); + D3D12MA_ASSERT(resAllocInfo.SizeInBytes > 0); + + BlockVector* blockVector = NULL; + CommittedAllocationParameters committedAllocationParams = {}; + bool preferCommitted = false; + HRESULT hr = CalcAllocationParams(*pAllocDesc, resAllocInfo.SizeInBytes, + pResourceDesc, + blockVector, committedAllocationParams, preferCommitted); + if (FAILED(hr)) + return hr; + + const bool withinBudget = (pAllocDesc->Flags & ALLOCATION_FLAG_WITHIN_BUDGET) != 0; + hr = E_INVALIDARG; + if (committedAllocationParams.IsValid() && preferCommitted) + { + hr = AllocateCommittedResource(committedAllocationParams, + resAllocInfo.SizeInBytes, withinBudget, pAllocDesc->pPrivateData, + &finalResourceDesc, InitialResourceState, pOptimizedClearValue, + ppAllocation, riidResource, ppvResource); + if (SUCCEEDED(hr)) + return hr; + } + if (blockVector != NULL) + { + hr = blockVector->CreateResource(resAllocInfo.SizeInBytes, resAllocInfo.Alignment, + *pAllocDesc, finalResourceDesc, + InitialResourceState, pOptimizedClearValue, + ppAllocation, riidResource, ppvResource); + if (SUCCEEDED(hr)) + return hr; + } + if (committedAllocationParams.IsValid() && !preferCommitted) + { + hr = AllocateCommittedResource(committedAllocationParams, + resAllocInfo.SizeInBytes, withinBudget, pAllocDesc->pPrivateData, + &finalResourceDesc, InitialResourceState, pOptimizedClearValue, + ppAllocation, riidResource, ppvResource); + if (SUCCEEDED(hr)) + return hr; + } + return hr; +} + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ +HRESULT AllocatorPimpl::CreateResource2( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + D3D12MA_ASSERT(pAllocDesc && pResourceDesc && ppAllocation); + + *ppAllocation = NULL; + if (ppvResource) + { + *ppvResource = NULL; + } + if (m_Device8 == NULL) + { + return E_NOINTERFACE; + } + + D3D12_RESOURCE_DESC1 finalResourceDesc = *pResourceDesc; + D3D12_RESOURCE_ALLOCATION_INFO resAllocInfo = GetResourceAllocationInfo(finalResourceDesc); + D3D12MA_ASSERT(IsPow2(resAllocInfo.Alignment)); + D3D12MA_ASSERT(resAllocInfo.SizeInBytes > 0); + + BlockVector* blockVector = NULL; + CommittedAllocationParameters committedAllocationParams = {}; + bool preferCommitted = false; + HRESULT hr = CalcAllocationParams(*pAllocDesc, resAllocInfo.SizeInBytes, + pResourceDesc, + blockVector, committedAllocationParams, preferCommitted); + if (FAILED(hr)) + return hr; + + const bool withinBudget = (pAllocDesc->Flags & ALLOCATION_FLAG_WITHIN_BUDGET) != 0; + hr = E_INVALIDARG; + if (committedAllocationParams.IsValid() && preferCommitted) + { + hr = AllocateCommittedResource2(committedAllocationParams, + resAllocInfo.SizeInBytes, withinBudget, pAllocDesc->pPrivateData, + &finalResourceDesc, InitialResourceState, pOptimizedClearValue, + ppAllocation, riidResource, ppvResource); + if (SUCCEEDED(hr)) + return hr; + } + if (blockVector != NULL) + { + hr = blockVector->CreateResource2(resAllocInfo.SizeInBytes, resAllocInfo.Alignment, + *pAllocDesc, finalResourceDesc, + InitialResourceState, pOptimizedClearValue, + ppAllocation, riidResource, ppvResource); + if (SUCCEEDED(hr)) + return hr; + } + if (committedAllocationParams.IsValid() && !preferCommitted) + { + hr = AllocateCommittedResource2(committedAllocationParams, + resAllocInfo.SizeInBytes, withinBudget, pAllocDesc->pPrivateData, + &finalResourceDesc, InitialResourceState, pOptimizedClearValue, + ppAllocation, riidResource, ppvResource); + if (SUCCEEDED(hr)) + return hr; + } + return hr; +} +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + +HRESULT AllocatorPimpl::AllocateMemory( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + Allocation** ppAllocation) +{ + *ppAllocation = NULL; + + BlockVector* blockVector = NULL; + CommittedAllocationParameters committedAllocationParams = {}; + bool preferCommitted = false; + HRESULT hr = CalcAllocationParams(*pAllocDesc, pAllocInfo->SizeInBytes, + NULL, // pResDesc + blockVector, committedAllocationParams, preferCommitted); + if (FAILED(hr)) + return hr; + + const bool withinBudget = (pAllocDesc->Flags & ALLOCATION_FLAG_WITHIN_BUDGET) != 0; + hr = E_INVALIDARG; + if (committedAllocationParams.IsValid() && preferCommitted) + { + hr = AllocateHeap(committedAllocationParams, *pAllocInfo, withinBudget, pAllocDesc->pPrivateData, ppAllocation); + if (SUCCEEDED(hr)) + return hr; + } + if (blockVector != NULL) + { + hr = blockVector->Allocate(pAllocInfo->SizeInBytes, pAllocInfo->Alignment, + *pAllocDesc, 1, (Allocation**)ppAllocation); + if (SUCCEEDED(hr)) + return hr; + } + if (committedAllocationParams.IsValid() && !preferCommitted) + { + hr = AllocateHeap(committedAllocationParams, *pAllocInfo, withinBudget, pAllocDesc->pPrivateData, ppAllocation); + if (SUCCEEDED(hr)) + return hr; + } + return hr; +} + +HRESULT AllocatorPimpl::CreateAliasingResource( + Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + REFIID riidResource, + void** ppvResource) +{ + *ppvResource = NULL; + + D3D12_RESOURCE_DESC resourceDesc2 = *pResourceDesc; + D3D12_RESOURCE_ALLOCATION_INFO resAllocInfo = GetResourceAllocationInfo(resourceDesc2); + D3D12MA_ASSERT(IsPow2(resAllocInfo.Alignment)); + D3D12MA_ASSERT(resAllocInfo.SizeInBytes > 0); + + ID3D12Heap* const existingHeap = pAllocation->GetHeap(); + const UINT64 existingOffset = pAllocation->GetOffset(); + const UINT64 existingSize = pAllocation->GetSize(); + const UINT64 newOffset = existingOffset + AllocationLocalOffset; + + if (existingHeap == NULL || + AllocationLocalOffset + resAllocInfo.SizeInBytes > existingSize || + newOffset % resAllocInfo.Alignment != 0) + { + return E_INVALIDARG; + } + + return m_Device->CreatePlacedResource( + existingHeap, + newOffset, + &resourceDesc2, + InitialResourceState, + pOptimizedClearValue, + riidResource, + ppvResource); +} + +void AllocatorPimpl::FreeCommittedMemory(Allocation* allocation) +{ + D3D12MA_ASSERT(allocation && allocation->m_PackedData.GetType() == Allocation::TYPE_COMMITTED); + + CommittedAllocationList* const allocList = allocation->m_Committed.list; + allocList->Unregister(allocation); + + const UINT memSegmentGroup = allocList->GetMemorySegmentGroup(this); + const UINT64 allocSize = allocation->GetSize(); + m_Budget.RemoveAllocation(memSegmentGroup, allocSize); + m_Budget.RemoveBlock(memSegmentGroup, allocSize); +} + +void AllocatorPimpl::FreePlacedMemory(Allocation* allocation) +{ + D3D12MA_ASSERT(allocation && allocation->m_PackedData.GetType() == Allocation::TYPE_PLACED); + + NormalBlock* const block = allocation->m_Placed.block; + D3D12MA_ASSERT(block); + BlockVector* const blockVector = block->GetBlockVector(); + D3D12MA_ASSERT(blockVector); + m_Budget.RemoveAllocation(HeapPropertiesToMemorySegmentGroup(block->GetHeapProperties()), allocation->GetSize()); + blockVector->Free(allocation); +} + +void AllocatorPimpl::FreeHeapMemory(Allocation* allocation) +{ + D3D12MA_ASSERT(allocation && allocation->m_PackedData.GetType() == Allocation::TYPE_HEAP); + + CommittedAllocationList* const allocList = allocation->m_Committed.list; + allocList->Unregister(allocation); + SAFE_RELEASE(allocation->m_Heap.heap); + + const UINT memSegmentGroup = allocList->GetMemorySegmentGroup(this); + const UINT64 allocSize = allocation->GetSize(); + m_Budget.RemoveAllocation(memSegmentGroup, allocSize); + m_Budget.RemoveBlock(memSegmentGroup, allocSize); +} + +void AllocatorPimpl::SetCurrentFrameIndex(UINT frameIndex) +{ + m_CurrentFrameIndex.store(frameIndex); + +#if D3D12MA_DXGI_1_4 + UpdateD3D12Budget(); +#endif +} + +void AllocatorPimpl::CalculateStatistics(TotalStatistics& outStats, DetailedStatistics outCutomHeaps[2]) +{ + // Init stats + for (size_t i = 0; i < HEAP_TYPE_COUNT; i++) + ClearDetailedStatistics(outStats.HeapType[i]); + for (size_t i = 0; i < DXGI_MEMORY_SEGMENT_GROUP_COUNT; i++) + ClearDetailedStatistics(outStats.MemorySegmentGroup[i]); + ClearDetailedStatistics(outStats.Total); + if (outCutomHeaps) + { + ClearDetailedStatistics(outCutomHeaps[0]); + ClearDetailedStatistics(outCutomHeaps[1]); + } + + // Process default pools. 3 standard heap types only. Add them to outStats.HeapType[i]. + if (SupportsResourceHeapTier2()) + { + // DEFAULT, UPLOAD, READBACK. + for (size_t heapTypeIndex = 0; heapTypeIndex < STANDARD_HEAP_TYPE_COUNT; ++heapTypeIndex) + { + BlockVector* const pBlockVector = m_BlockVectors[heapTypeIndex]; + D3D12MA_ASSERT(pBlockVector); + pBlockVector->AddDetailedStatistics(outStats.HeapType[heapTypeIndex]); + } + } + else + { + // DEFAULT, UPLOAD, READBACK. + for (size_t heapTypeIndex = 0; heapTypeIndex < STANDARD_HEAP_TYPE_COUNT; ++heapTypeIndex) + { + for (size_t heapSubType = 0; heapSubType < 3; ++heapSubType) + { + BlockVector* const pBlockVector = m_BlockVectors[heapTypeIndex * 3 + heapSubType]; + D3D12MA_ASSERT(pBlockVector); + pBlockVector->AddDetailedStatistics(outStats.HeapType[heapTypeIndex]); + } + } + } + + // Sum them up to memory segment groups. + AddDetailedStatistics( + outStats.MemorySegmentGroup[StandardHeapTypeToMemorySegmentGroup(D3D12_HEAP_TYPE_DEFAULT)], + outStats.HeapType[0]); + AddDetailedStatistics( + outStats.MemorySegmentGroup[StandardHeapTypeToMemorySegmentGroup(D3D12_HEAP_TYPE_UPLOAD)], + outStats.HeapType[1]); + AddDetailedStatistics( + outStats.MemorySegmentGroup[StandardHeapTypeToMemorySegmentGroup(D3D12_HEAP_TYPE_READBACK)], + outStats.HeapType[2]); + + // Process custom pools. + DetailedStatistics tmpStats; + for (size_t heapTypeIndex = 0; heapTypeIndex < HEAP_TYPE_COUNT; ++heapTypeIndex) + { + MutexLockRead lock(m_PoolsMutex[heapTypeIndex], m_UseMutex); + PoolList& poolList = m_Pools[heapTypeIndex]; + for (PoolPimpl* pool = poolList.Front(); pool != NULL; pool = poolList.GetNext(pool)) + { + const D3D12_HEAP_PROPERTIES& poolHeapProps = pool->GetDesc().HeapProperties; + ClearDetailedStatistics(tmpStats); + pool->AddDetailedStatistics(tmpStats); + AddDetailedStatistics( + outStats.HeapType[heapTypeIndex], tmpStats); + + UINT memorySegment = HeapPropertiesToMemorySegmentGroup(poolHeapProps); + AddDetailedStatistics( + outStats.MemorySegmentGroup[memorySegment], tmpStats); + + if (outCutomHeaps) + AddDetailedStatistics(outCutomHeaps[memorySegment], tmpStats); + } + } + + // Process committed allocations. 3 standard heap types only. + for (UINT heapTypeIndex = 0; heapTypeIndex < STANDARD_HEAP_TYPE_COUNT; ++heapTypeIndex) + { + ClearDetailedStatistics(tmpStats); + m_CommittedAllocations[heapTypeIndex].AddDetailedStatistics(tmpStats); + AddDetailedStatistics( + outStats.HeapType[heapTypeIndex], tmpStats); + AddDetailedStatistics( + outStats.MemorySegmentGroup[StandardHeapTypeToMemorySegmentGroup(IndexToHeapType(heapTypeIndex))], tmpStats); + } + + // Sum up memory segment groups to totals. + AddDetailedStatistics(outStats.Total, outStats.MemorySegmentGroup[0]); + AddDetailedStatistics(outStats.Total, outStats.MemorySegmentGroup[1]); + + D3D12MA_ASSERT(outStats.Total.Stats.BlockCount == + outStats.MemorySegmentGroup[0].Stats.BlockCount + outStats.MemorySegmentGroup[1].Stats.BlockCount); + D3D12MA_ASSERT(outStats.Total.Stats.AllocationCount == + outStats.MemorySegmentGroup[0].Stats.AllocationCount + outStats.MemorySegmentGroup[1].Stats.AllocationCount); + D3D12MA_ASSERT(outStats.Total.Stats.BlockBytes == + outStats.MemorySegmentGroup[0].Stats.BlockBytes + outStats.MemorySegmentGroup[1].Stats.BlockBytes); + D3D12MA_ASSERT(outStats.Total.Stats.AllocationBytes == + outStats.MemorySegmentGroup[0].Stats.AllocationBytes + outStats.MemorySegmentGroup[1].Stats.AllocationBytes); + D3D12MA_ASSERT(outStats.Total.UnusedRangeCount == + outStats.MemorySegmentGroup[0].UnusedRangeCount + outStats.MemorySegmentGroup[1].UnusedRangeCount); + + D3D12MA_ASSERT(outStats.Total.Stats.BlockCount == + outStats.HeapType[0].Stats.BlockCount + outStats.HeapType[1].Stats.BlockCount + + outStats.HeapType[2].Stats.BlockCount + outStats.HeapType[3].Stats.BlockCount); + D3D12MA_ASSERT(outStats.Total.Stats.AllocationCount == + outStats.HeapType[0].Stats.AllocationCount + outStats.HeapType[1].Stats.AllocationCount + + outStats.HeapType[2].Stats.AllocationCount + outStats.HeapType[3].Stats.AllocationCount); + D3D12MA_ASSERT(outStats.Total.Stats.BlockBytes == + outStats.HeapType[0].Stats.BlockBytes + outStats.HeapType[1].Stats.BlockBytes + + outStats.HeapType[2].Stats.BlockBytes + outStats.HeapType[3].Stats.BlockBytes); + D3D12MA_ASSERT(outStats.Total.Stats.AllocationBytes == + outStats.HeapType[0].Stats.AllocationBytes + outStats.HeapType[1].Stats.AllocationBytes + + outStats.HeapType[2].Stats.AllocationBytes + outStats.HeapType[3].Stats.AllocationBytes); + D3D12MA_ASSERT(outStats.Total.UnusedRangeCount == + outStats.HeapType[0].UnusedRangeCount + outStats.HeapType[1].UnusedRangeCount + + outStats.HeapType[2].UnusedRangeCount + outStats.HeapType[3].UnusedRangeCount); +} + +void AllocatorPimpl::GetBudget(Budget* outLocalBudget, Budget* outNonLocalBudget) +{ + if (outLocalBudget) + m_Budget.GetStatistics(outLocalBudget->Stats, DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY); + if (outNonLocalBudget) + m_Budget.GetStatistics(outNonLocalBudget->Stats, DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY); + +#if D3D12MA_DXGI_1_4 + if (m_Adapter3) + { + if (!m_Budget.ShouldUpdateBudget()) + { + m_Budget.GetBudget(m_UseMutex, + outLocalBudget ? &outLocalBudget->UsageBytes : NULL, + outLocalBudget ? &outLocalBudget->BudgetBytes : NULL, + outNonLocalBudget ? &outNonLocalBudget->UsageBytes : NULL, + outNonLocalBudget ? &outNonLocalBudget->BudgetBytes : NULL); + } + else + { + UpdateD3D12Budget(); + GetBudget(outLocalBudget, outNonLocalBudget); // Recursion + } + } + else +#endif + { + if (outLocalBudget) + { + outLocalBudget->UsageBytes = outLocalBudget->Stats.BlockBytes; + outLocalBudget->BudgetBytes = GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_LOCAL_COPY) * 8 / 10; // 80% heuristics. + } + if (outNonLocalBudget) + { + outNonLocalBudget->UsageBytes = outNonLocalBudget->Stats.BlockBytes; + outNonLocalBudget->BudgetBytes = GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL_COPY) * 8 / 10; // 80% heuristics. + } + } +} + +void AllocatorPimpl::GetBudgetForHeapType(Budget& outBudget, D3D12_HEAP_TYPE heapType) +{ + switch (heapType) + { + case D3D12_HEAP_TYPE_DEFAULT: + GetBudget(&outBudget, NULL); + break; + case D3D12_HEAP_TYPE_UPLOAD: + case D3D12_HEAP_TYPE_READBACK: + GetBudget(NULL, &outBudget); + break; + default: D3D12MA_ASSERT(0); + } +} + +void AllocatorPimpl::BuildStatsString(WCHAR** ppStatsString, BOOL detailedMap) +{ + StringBuilder sb(GetAllocs()); + { + Budget localBudget = {}, nonLocalBudget = {}; + GetBudget(&localBudget, &nonLocalBudget); + + TotalStatistics stats; + DetailedStatistics customHeaps[2]; + CalculateStatistics(stats, customHeaps); + + JsonWriter json(GetAllocs(), sb); + json.BeginObject(); + { + json.WriteString(L"General"); + json.BeginObject(); + { + json.WriteString(L"API"); + json.WriteString(L"Direct3D 12"); + + json.WriteString(L"GPU"); + json.WriteString(m_AdapterDesc.Description); + + json.WriteString(L"DedicatedVideoMemory"); + json.WriteNumber(m_AdapterDesc.DedicatedVideoMemory); + json.WriteString(L"DedicatedSystemMemory"); + json.WriteNumber(m_AdapterDesc.DedicatedSystemMemory); + json.WriteString(L"SharedSystemMemory"); + json.WriteNumber(m_AdapterDesc.SharedSystemMemory); + + json.WriteString(L"ResourceHeapTier"); + json.WriteNumber(static_cast(m_D3D12Options.ResourceHeapTier)); + + json.WriteString(L"ResourceBindingTier"); + json.WriteNumber(static_cast(m_D3D12Options.ResourceBindingTier)); + + json.WriteString(L"TiledResourcesTier"); + json.WriteNumber(static_cast(m_D3D12Options.TiledResourcesTier)); + + json.WriteString(L"TileBasedRenderer"); + json.WriteBool(m_D3D12Architecture.TileBasedRenderer); + + json.WriteString(L"UMA"); + json.WriteBool(m_D3D12Architecture.UMA); + json.WriteString(L"CacheCoherentUMA"); + json.WriteBool(m_D3D12Architecture.CacheCoherentUMA); + } + json.EndObject(); + } + { + json.WriteString(L"Total"); + json.AddDetailedStatisticsInfoObject(stats.Total); + } + { + json.WriteString(L"MemoryInfo"); + json.BeginObject(); + { + json.WriteString(L"L0"); + json.BeginObject(); + { + json.WriteString(L"Budget"); + WriteBudgetToJson(json, IsUMA() ? localBudget : nonLocalBudget); // When UMA device only L0 present as local + + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.MemorySegmentGroup[!IsUMA()]); + + json.WriteString(L"MemoryPools"); + json.BeginObject(); + { + if (IsUMA()) + { + json.WriteString(L"DEFAULT"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[0]); + } + json.EndObject(); + } + json.WriteString(L"UPLOAD"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[1]); + } + json.EndObject(); + + json.WriteString(L"READBACK"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[2]); + } + json.EndObject(); + + json.WriteString(L"CUSTOM"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(customHeaps[!IsUMA()]); + } + json.EndObject(); + } + json.EndObject(); + } + json.EndObject(); + if (!IsUMA()) + { + json.WriteString(L"L1"); + json.BeginObject(); + { + json.WriteString(L"Budget"); + WriteBudgetToJson(json, localBudget); + + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.MemorySegmentGroup[0]); + + json.WriteString(L"MemoryPools"); + json.BeginObject(); + { + json.WriteString(L"DEFAULT"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(stats.HeapType[0]); + } + json.EndObject(); + + json.WriteString(L"CUSTOM"); + json.BeginObject(); + { + json.WriteString(L"Stats"); + json.AddDetailedStatisticsInfoObject(customHeaps[0]); + } + json.EndObject(); + } + json.EndObject(); + } + json.EndObject(); + } + } + json.EndObject(); + } + + if (detailedMap) + { + const auto writeHeapInfo = [&](BlockVector* blockVector, CommittedAllocationList* committedAllocs, bool customHeap) + { + D3D12MA_ASSERT(blockVector); + + D3D12_HEAP_FLAGS flags = blockVector->GetHeapFlags(); + json.WriteString(L"Flags"); + json.BeginArray(true); + { + if (flags & D3D12_HEAP_FLAG_SHARED) + json.WriteString(L"HEAP_FLAG_SHARED"); + if (flags & D3D12_HEAP_FLAG_ALLOW_DISPLAY) + json.WriteString(L"HEAP_FLAG_ALLOW_DISPLAY"); + if (flags & D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER) + json.WriteString(L"HEAP_FLAG_CROSS_ADAPTER"); + if (flags & D3D12_HEAP_FLAG_HARDWARE_PROTECTED) + json.WriteString(L"HEAP_FLAG_HARDWARE_PROTECTED"); + if (flags & D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH) + json.WriteString(L"HEAP_FLAG_ALLOW_WRITE_WATCH"); + if (flags & D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS) + json.WriteString(L"HEAP_FLAG_ALLOW_SHADER_ATOMICS"); +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + if (flags & D3D12_HEAP_FLAG_CREATE_NOT_RESIDENT) + json.WriteString(L"HEAP_FLAG_CREATE_NOT_RESIDENT"); + if (flags & D3D12_HEAP_FLAG_CREATE_NOT_ZEROED) + json.WriteString(L"HEAP_FLAG_CREATE_NOT_ZEROED"); +#endif + + if (flags & D3D12_HEAP_FLAG_DENY_BUFFERS) + json.WriteString(L"HEAP_FLAG_DENY_BUFFERS"); + if (flags & D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES) + json.WriteString(L"HEAP_FLAG_DENY_RT_DS_TEXTURES"); + if (flags & D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES) + json.WriteString(L"HEAP_FLAG_DENY_NON_RT_DS_TEXTURES"); + + flags &= ~(D3D12_HEAP_FLAG_SHARED + | D3D12_HEAP_FLAG_DENY_BUFFERS + | D3D12_HEAP_FLAG_ALLOW_DISPLAY + | D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER + | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES + | D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES + | D3D12_HEAP_FLAG_HARDWARE_PROTECTED + | D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH + | D3D12_HEAP_FLAG_ALLOW_SHADER_ATOMICS); +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + flags &= ~(D3D12_HEAP_FLAG_CREATE_NOT_RESIDENT + | D3D12_HEAP_FLAG_CREATE_NOT_ZEROED); +#endif + if (flags != 0) + json.WriteNumber((UINT)flags); + + if (customHeap) + { + const D3D12_HEAP_PROPERTIES& properties = blockVector->GetHeapProperties(); + switch (properties.MemoryPoolPreference) + { + default: + D3D12MA_ASSERT(0); + case D3D12_MEMORY_POOL_UNKNOWN: + json.WriteString(L"MEMORY_POOL_UNKNOWN"); + break; + case D3D12_MEMORY_POOL_L0: + json.WriteString(L"MEMORY_POOL_L0"); + break; + case D3D12_MEMORY_POOL_L1: + json.WriteString(L"MEMORY_POOL_L1"); + break; + } + switch (properties.CPUPageProperty) + { + default: + D3D12MA_ASSERT(0); + case D3D12_CPU_PAGE_PROPERTY_UNKNOWN: + json.WriteString(L"CPU_PAGE_PROPERTY_UNKNOWN"); + break; + case D3D12_CPU_PAGE_PROPERTY_NOT_AVAILABLE: + json.WriteString(L"CPU_PAGE_PROPERTY_NOT_AVAILABLE"); + break; + case D3D12_CPU_PAGE_PROPERTY_WRITE_COMBINE: + json.WriteString(L"CPU_PAGE_PROPERTY_WRITE_COMBINE"); + break; + case D3D12_CPU_PAGE_PROPERTY_WRITE_BACK: + json.WriteString(L"CPU_PAGE_PROPERTY_WRITE_BACK"); + break; + } + } + } + json.EndArray(); + + json.WriteString(L"PreferredBlockSize"); + json.WriteNumber(blockVector->GetPreferredBlockSize()); + + json.WriteString(L"Blocks"); + blockVector->WriteBlockInfoToJson(json); + + json.WriteString(L"DedicatedAllocations"); + json.BeginArray(); + if (committedAllocs) + committedAllocs->BuildStatsString(json); + json.EndArray(); + }; + + json.WriteString(L"DefaultPools"); + json.BeginObject(); + { + if (SupportsResourceHeapTier2()) + { + for (uint8_t heapType = 0; heapType < STANDARD_HEAP_TYPE_COUNT; ++heapType) + { + json.WriteString(HeapTypeNames[heapType]); + json.BeginObject(); + writeHeapInfo(m_BlockVectors[heapType], m_CommittedAllocations + heapType, false); + json.EndObject(); + } + } + else + { + for (uint8_t heapType = 0; heapType < STANDARD_HEAP_TYPE_COUNT; ++heapType) + { + for (uint8_t heapSubType = 0; heapSubType < 3; ++heapSubType) + { + static const WCHAR* const heapSubTypeName[] = { + L" - Buffers", + L" - Textures", + L" - Textures RT/DS", + }; + json.BeginString(HeapTypeNames[heapType]); + json.EndString(heapSubTypeName[heapSubType]); + + json.BeginObject(); + writeHeapInfo(m_BlockVectors[heapType + heapSubType], m_CommittedAllocations + heapType, false); + json.EndObject(); + } + } + } + } + json.EndObject(); + + json.WriteString(L"CustomPools"); + json.BeginObject(); + for (uint8_t heapTypeIndex = 0; heapTypeIndex < HEAP_TYPE_COUNT; ++heapTypeIndex) + { + MutexLockRead mutex(m_PoolsMutex[heapTypeIndex], m_UseMutex); + auto* item = m_Pools[heapTypeIndex].Front(); + if (item != NULL) + { + size_t index = 0; + json.WriteString(HeapTypeNames[heapTypeIndex]); + json.BeginArray(); + do + { + json.BeginObject(); + json.WriteString(L"Name"); + json.BeginString(); + json.ContinueString(index++); + if (item->GetName()) + { + json.ContinueString(L" - "); + json.ContinueString(item->GetName()); + } + json.EndString(); + + writeHeapInfo(item->GetBlockVector(), item->GetCommittedAllocationList(), heapTypeIndex == 3); + json.EndObject(); + } while ((item = PoolList::GetNext(item)) != NULL); + json.EndArray(); + } + } + json.EndObject(); + } + json.EndObject(); + } + + const size_t length = sb.GetLength(); + WCHAR* result = AllocateArray(GetAllocs(), length + 2); + result[0] = 0xFEFF; + memcpy(result + 1, sb.GetData(), length * sizeof(WCHAR)); + result[length + 1] = L'\0'; + *ppStatsString = result; +} + +void AllocatorPimpl::FreeStatsString(WCHAR* pStatsString) +{ + D3D12MA_ASSERT(pStatsString); + Free(GetAllocs(), pStatsString); +} + +template +bool AllocatorPimpl::PrefersCommittedAllocation(const D3D12_RESOURCE_DESC_T& resourceDesc) +{ + // Intentional. It may change in the future. + return false; +} + +HRESULT AllocatorPimpl::AllocateCommittedResource( + const CommittedAllocationParameters& committedAllocParams, + UINT64 resourceSize, bool withinBudget, void* pPrivateData, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE* pOptimizedClearValue, + Allocation** ppAllocation, REFIID riidResource, void** ppvResource) +{ + D3D12MA_ASSERT(committedAllocParams.IsValid()); + + HRESULT hr; + ID3D12Resource* res = NULL; + // Allocate aliasing memory with explicit heap + if (committedAllocParams.m_CanAlias) + { + D3D12_RESOURCE_ALLOCATION_INFO heapAllocInfo = {}; + heapAllocInfo.SizeInBytes = resourceSize; + heapAllocInfo.Alignment = HeapFlagsToAlignment(committedAllocParams.m_HeapFlags, m_MsaaAlwaysCommitted); + hr = AllocateHeap(committedAllocParams, heapAllocInfo, withinBudget, pPrivateData, ppAllocation); + if (SUCCEEDED(hr)) + { + hr = m_Device->CreatePlacedResource((*ppAllocation)->GetHeap(), 0, + pResourceDesc, InitialResourceState, + pOptimizedClearValue, D3D12MA_IID_PPV_ARGS(&res)); + if (SUCCEEDED(hr)) + { + if (ppvResource != NULL) + hr = res->QueryInterface(riidResource, ppvResource); + if (SUCCEEDED(hr)) + { + (*ppAllocation)->SetResourcePointer(res, pResourceDesc); + return hr; + } + res->Release(); + } + FreeHeapMemory(*ppAllocation); + } + return hr; + } + + if (withinBudget && + !NewAllocationWithinBudget(committedAllocParams.m_HeapProperties.Type, resourceSize)) + { + return E_OUTOFMEMORY; + } + + /* D3D12 ERROR: + * ID3D12Device::CreateCommittedResource: + * When creating a committed resource, D3D12_HEAP_FLAGS must not have either + * D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES, + * D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES, + * nor D3D12_HEAP_FLAG_DENY_BUFFERS set. + * These flags will be set automatically to correspond with the committed resource type. + * + * [ STATE_CREATION ERROR #640: CREATERESOURCEANDHEAP_INVALIDHEAPMISCFLAGS] + */ +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + if (m_Device4) + { + hr = m_Device4->CreateCommittedResource1( + &committedAllocParams.m_HeapProperties, + committedAllocParams.m_HeapFlags & ~RESOURCE_CLASS_HEAP_FLAGS, + pResourceDesc, InitialResourceState, + pOptimizedClearValue, committedAllocParams.m_ProtectedSession, D3D12MA_IID_PPV_ARGS(&res)); + } + else +#endif + { + if (committedAllocParams.m_ProtectedSession == NULL) + { + hr = m_Device->CreateCommittedResource( + &committedAllocParams.m_HeapProperties, + committedAllocParams.m_HeapFlags & ~RESOURCE_CLASS_HEAP_FLAGS, + pResourceDesc, InitialResourceState, + pOptimizedClearValue, D3D12MA_IID_PPV_ARGS(&res)); + } + else + hr = E_NOINTERFACE; + } + + if (SUCCEEDED(hr)) + { + if (ppvResource != NULL) + { + hr = res->QueryInterface(riidResource, ppvResource); + } + if (SUCCEEDED(hr)) + { + const BOOL wasZeroInitialized = TRUE; + Allocation* alloc = m_AllocationObjectAllocator.Allocate(this, resourceSize, pResourceDesc->Alignment, wasZeroInitialized); + alloc->InitCommitted(committedAllocParams.m_List); + alloc->SetResourcePointer(res, pResourceDesc); + alloc->SetPrivateData(pPrivateData); + + *ppAllocation = alloc; + + committedAllocParams.m_List->Register(alloc); + + const UINT memSegmentGroup = HeapPropertiesToMemorySegmentGroup(committedAllocParams.m_HeapProperties); + m_Budget.AddBlock(memSegmentGroup, resourceSize); + m_Budget.AddAllocation(memSegmentGroup, resourceSize); + } + else + { + res->Release(); + } + } + return hr; +} + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ +HRESULT AllocatorPimpl::AllocateCommittedResource2( + const CommittedAllocationParameters& committedAllocParams, + UINT64 resourceSize, bool withinBudget, void* pPrivateData, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, const D3D12_CLEAR_VALUE* pOptimizedClearValue, + Allocation** ppAllocation, REFIID riidResource, void** ppvResource) +{ + D3D12MA_ASSERT(committedAllocParams.IsValid()); + + if (m_Device8 == NULL) + { + return E_NOINTERFACE; + } + + HRESULT hr; + ID3D12Resource* res = NULL; + // Allocate aliasing memory with explicit heap + if (committedAllocParams.m_CanAlias) + { + D3D12_RESOURCE_ALLOCATION_INFO heapAllocInfo = {}; + heapAllocInfo.SizeInBytes = resourceSize; + heapAllocInfo.Alignment = HeapFlagsToAlignment(committedAllocParams.m_HeapFlags, m_MsaaAlwaysCommitted); + hr = AllocateHeap(committedAllocParams, heapAllocInfo, withinBudget, pPrivateData, ppAllocation); + if (SUCCEEDED(hr)) + { + hr = m_Device8->CreatePlacedResource1((*ppAllocation)->GetHeap(), 0, + pResourceDesc, InitialResourceState, + pOptimizedClearValue, D3D12MA_IID_PPV_ARGS(&res)); + if (SUCCEEDED(hr)) + { + if (ppvResource != NULL) + hr = res->QueryInterface(riidResource, ppvResource); + if (SUCCEEDED(hr)) + { + (*ppAllocation)->SetResourcePointer(res, pResourceDesc); + return hr; + } + res->Release(); + } + FreeHeapMemory(*ppAllocation); + } + return hr; + } + + if (withinBudget && + !NewAllocationWithinBudget(committedAllocParams.m_HeapProperties.Type, resourceSize)) + { + return E_OUTOFMEMORY; + } + + hr = m_Device8->CreateCommittedResource2( + &committedAllocParams.m_HeapProperties, + committedAllocParams.m_HeapFlags & ~RESOURCE_CLASS_HEAP_FLAGS, // D3D12 ERROR: ID3D12Device::CreateCommittedResource: When creating a committed resource, D3D12_HEAP_FLAGS must not have either D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES, D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES, nor D3D12_HEAP_FLAG_DENY_BUFFERS set. These flags will be set automatically to correspond with the committed resource type. [ STATE_CREATION ERROR #640: CREATERESOURCEANDHEAP_INVALIDHEAPMISCFLAGS] + pResourceDesc, InitialResourceState, + pOptimizedClearValue, committedAllocParams.m_ProtectedSession, D3D12MA_IID_PPV_ARGS(&res)); + if (SUCCEEDED(hr)) + { + if (ppvResource != NULL) + { + hr = res->QueryInterface(riidResource, ppvResource); + } + if (SUCCEEDED(hr)) + { + const BOOL wasZeroInitialized = TRUE; + Allocation* alloc = m_AllocationObjectAllocator.Allocate(this, resourceSize, pResourceDesc->Alignment, wasZeroInitialized); + alloc->InitCommitted(committedAllocParams.m_List); + alloc->SetResourcePointer(res, pResourceDesc); + alloc->SetPrivateData(pPrivateData); + + *ppAllocation = alloc; + + committedAllocParams.m_List->Register(alloc); + + const UINT memSegmentGroup = HeapPropertiesToMemorySegmentGroup(committedAllocParams.m_HeapProperties); + m_Budget.AddBlock(memSegmentGroup, resourceSize); + m_Budget.AddAllocation(memSegmentGroup, resourceSize); + } + else + { + res->Release(); + } + } + return hr; +} +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + +HRESULT AllocatorPimpl::AllocateHeap( + const CommittedAllocationParameters& committedAllocParams, + const D3D12_RESOURCE_ALLOCATION_INFO& allocInfo, bool withinBudget, + void* pPrivateData, Allocation** ppAllocation) +{ + D3D12MA_ASSERT(committedAllocParams.IsValid()); + + *ppAllocation = nullptr; + + if (withinBudget && + !NewAllocationWithinBudget(committedAllocParams.m_HeapProperties.Type, allocInfo.SizeInBytes)) + { + return E_OUTOFMEMORY; + } + + D3D12_HEAP_DESC heapDesc = {}; + heapDesc.SizeInBytes = allocInfo.SizeInBytes; + heapDesc.Properties = committedAllocParams.m_HeapProperties; + heapDesc.Alignment = allocInfo.Alignment; + heapDesc.Flags = committedAllocParams.m_HeapFlags; + + HRESULT hr; + ID3D12Heap* heap = nullptr; +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + if (m_Device4) + hr = m_Device4->CreateHeap1(&heapDesc, committedAllocParams.m_ProtectedSession, D3D12MA_IID_PPV_ARGS(&heap)); + else +#endif + { + if (committedAllocParams.m_ProtectedSession == NULL) + hr = m_Device->CreateHeap(&heapDesc, D3D12MA_IID_PPV_ARGS(&heap)); + else + hr = E_NOINTERFACE; + } + + if (SUCCEEDED(hr)) + { + const BOOL wasZeroInitialized = TRUE; + (*ppAllocation) = m_AllocationObjectAllocator.Allocate(this, allocInfo.SizeInBytes, allocInfo.Alignment, wasZeroInitialized); + (*ppAllocation)->InitHeap(committedAllocParams.m_List, heap); + (*ppAllocation)->SetPrivateData(pPrivateData); + committedAllocParams.m_List->Register(*ppAllocation); + + const UINT memSegmentGroup = HeapPropertiesToMemorySegmentGroup(committedAllocParams.m_HeapProperties); + m_Budget.AddBlock(memSegmentGroup, allocInfo.SizeInBytes); + m_Budget.AddAllocation(memSegmentGroup, allocInfo.SizeInBytes); + } + return hr; +} + +template +HRESULT AllocatorPimpl::CalcAllocationParams(const ALLOCATION_DESC& allocDesc, UINT64 allocSize, + const D3D12_RESOURCE_DESC_T* resDesc, + BlockVector*& outBlockVector, CommittedAllocationParameters& outCommittedAllocationParams, bool& outPreferCommitted) +{ + outBlockVector = NULL; + outCommittedAllocationParams = CommittedAllocationParameters(); + outPreferCommitted = false; + + bool msaaAlwaysCommitted; + if (allocDesc.CustomPool != NULL) + { + PoolPimpl* const pool = allocDesc.CustomPool->m_Pimpl; + + msaaAlwaysCommitted = pool->GetBlockVector()->DeniesMsaaTextures(); + outBlockVector = pool->GetBlockVector(); + + outCommittedAllocationParams.m_ProtectedSession = pool->GetDesc().pProtectedSession; + outCommittedAllocationParams.m_HeapProperties = pool->GetDesc().HeapProperties; + outCommittedAllocationParams.m_HeapFlags = pool->GetDesc().HeapFlags; + outCommittedAllocationParams.m_List = pool->GetCommittedAllocationList(); + } + else + { + if (!IsHeapTypeStandard(allocDesc.HeapType)) + { + return E_INVALIDARG; + } + msaaAlwaysCommitted = m_MsaaAlwaysCommitted; + + outCommittedAllocationParams.m_HeapProperties = StandardHeapTypeToHeapProperties(allocDesc.HeapType); + outCommittedAllocationParams.m_HeapFlags = allocDesc.ExtraHeapFlags; + outCommittedAllocationParams.m_List = &m_CommittedAllocations[HeapTypeToIndex(allocDesc.HeapType)]; + + const ResourceClass resourceClass = (resDesc != NULL) ? + ResourceDescToResourceClass(*resDesc) : HeapFlagsToResourceClass(allocDesc.ExtraHeapFlags); + const UINT defaultPoolIndex = CalcDefaultPoolIndex(allocDesc, resourceClass); + if (defaultPoolIndex != UINT32_MAX) + { + outBlockVector = m_BlockVectors[defaultPoolIndex]; + const UINT64 preferredBlockSize = outBlockVector->GetPreferredBlockSize(); + if (allocSize > preferredBlockSize) + { + outBlockVector = NULL; + } + else if (allocSize > preferredBlockSize / 2) + { + // Heuristics: Allocate committed memory if requested size if greater than half of preferred block size. + outPreferCommitted = true; + } + } + + const D3D12_HEAP_FLAGS extraHeapFlags = allocDesc.ExtraHeapFlags & ~RESOURCE_CLASS_HEAP_FLAGS; + if (outBlockVector != NULL && extraHeapFlags != 0) + { + outBlockVector = NULL; + } + } + + if ((allocDesc.Flags & ALLOCATION_FLAG_COMMITTED) != 0 || + m_AlwaysCommitted) + { + outBlockVector = NULL; + } + if ((allocDesc.Flags & ALLOCATION_FLAG_NEVER_ALLOCATE) != 0) + { + outCommittedAllocationParams.m_List = NULL; + } + outCommittedAllocationParams.m_CanAlias = allocDesc.Flags & ALLOCATION_FLAG_CAN_ALIAS; + + if (resDesc != NULL) + { + if (resDesc->SampleDesc.Count > 1 && msaaAlwaysCommitted) + outBlockVector = NULL; + if (!outPreferCommitted && PrefersCommittedAllocation(*resDesc)) + outPreferCommitted = true; + } + + return (outBlockVector != NULL || outCommittedAllocationParams.m_List != NULL) ? S_OK : E_INVALIDARG; +} + +UINT AllocatorPimpl::CalcDefaultPoolIndex(const ALLOCATION_DESC& allocDesc, ResourceClass resourceClass) const +{ + const D3D12_HEAP_FLAGS extraHeapFlags = allocDesc.ExtraHeapFlags & ~RESOURCE_CLASS_HEAP_FLAGS; + if (extraHeapFlags != 0) + { + return UINT32_MAX; + } + + UINT poolIndex = UINT_MAX; + switch (allocDesc.HeapType) + { + case D3D12_HEAP_TYPE_DEFAULT: poolIndex = 0; break; + case D3D12_HEAP_TYPE_UPLOAD: poolIndex = 1; break; + case D3D12_HEAP_TYPE_READBACK: poolIndex = 2; break; + default: D3D12MA_ASSERT(0); + } + + if (SupportsResourceHeapTier2()) + return poolIndex; + else + { + switch (resourceClass) + { + case ResourceClass::Buffer: + return poolIndex * 3; + case ResourceClass::Non_RT_DS_Texture: + return poolIndex * 3 + 1; + case ResourceClass::RT_DS_Texture: + return poolIndex * 3 + 2; + default: + return UINT32_MAX; + } + } +} + +void AllocatorPimpl::CalcDefaultPoolParams(D3D12_HEAP_TYPE& outHeapType, D3D12_HEAP_FLAGS& outHeapFlags, UINT index) const +{ + outHeapType = D3D12_HEAP_TYPE_DEFAULT; + outHeapFlags = D3D12_HEAP_FLAG_NONE; + + if (!SupportsResourceHeapTier2()) + { + switch (index % 3) + { + case 0: + outHeapFlags = D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES | D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES; + break; + case 1: + outHeapFlags = D3D12_HEAP_FLAG_DENY_BUFFERS | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES; + break; + case 2: + outHeapFlags = D3D12_HEAP_FLAG_DENY_BUFFERS | D3D12_HEAP_FLAG_DENY_NON_RT_DS_TEXTURES; + break; + } + + index /= 3; + } + + switch (index) + { + case 0: + outHeapType = D3D12_HEAP_TYPE_DEFAULT; + break; + case 1: + outHeapType = D3D12_HEAP_TYPE_UPLOAD; + break; + case 2: + outHeapType = D3D12_HEAP_TYPE_READBACK; + break; + default: + D3D12MA_ASSERT(0); + } +} + +void AllocatorPimpl::RegisterPool(Pool* pool, D3D12_HEAP_TYPE heapType) +{ + const UINT heapTypeIndex = HeapTypeToIndex(heapType); + + MutexLockWrite lock(m_PoolsMutex[heapTypeIndex], m_UseMutex); + m_Pools[heapTypeIndex].PushBack(pool->m_Pimpl); +} + +void AllocatorPimpl::UnregisterPool(Pool* pool, D3D12_HEAP_TYPE heapType) +{ + const UINT heapTypeIndex = HeapTypeToIndex(heapType); + + MutexLockWrite lock(m_PoolsMutex[heapTypeIndex], m_UseMutex); + m_Pools[heapTypeIndex].Remove(pool->m_Pimpl); +} + +HRESULT AllocatorPimpl::UpdateD3D12Budget() +{ +#if D3D12MA_DXGI_1_4 + if (m_Adapter3) + return m_Budget.UpdateBudget(m_Adapter3, m_UseMutex); + else + return E_NOINTERFACE; +#else + return S_OK; +#endif +} + +D3D12_RESOURCE_ALLOCATION_INFO AllocatorPimpl::GetResourceAllocationInfoNative(const D3D12_RESOURCE_DESC& resourceDesc) const +{ + return m_Device->GetResourceAllocationInfo(0, 1, &resourceDesc); +} + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ +D3D12_RESOURCE_ALLOCATION_INFO AllocatorPimpl::GetResourceAllocationInfoNative(const D3D12_RESOURCE_DESC1& resourceDesc) const +{ + D3D12MA_ASSERT(m_Device8 != NULL); + D3D12_RESOURCE_ALLOCATION_INFO1 info1Unused; + return m_Device8->GetResourceAllocationInfo2(0, 1, &resourceDesc, &info1Unused); +} +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + +template +D3D12_RESOURCE_ALLOCATION_INFO AllocatorPimpl::GetResourceAllocationInfo(D3D12_RESOURCE_DESC_T& inOutResourceDesc) const +{ + /* Optional optimization: Microsoft documentation says: + https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-getresourceallocationinfo + + Your application can forgo using GetResourceAllocationInfo for buffer resources + (D3D12_RESOURCE_DIMENSION_BUFFER). Buffers have the same size on all adapters, + which is merely the smallest multiple of 64KB that's greater or equal to + D3D12_RESOURCE_DESC::Width. + */ + if (inOutResourceDesc.Alignment == 0 && + inOutResourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER) + { + return { + AlignUp(inOutResourceDesc.Width, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT), // SizeInBytes + D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT }; // Alignment + } + +#if D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT + if (inOutResourceDesc.Alignment == 0 && + inOutResourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && + (inOutResourceDesc.Flags & (D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL)) == 0 +#if D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT == 1 + && CanUseSmallAlignment(inOutResourceDesc) +#endif + ) + { + /* + The algorithm here is based on Microsoft sample: "Small Resources Sample" + https://github.com/microsoft/DirectX-Graphics-Samples/tree/master/Samples/Desktop/D3D12SmallResources + */ + const UINT64 smallAlignmentToTry = inOutResourceDesc.SampleDesc.Count > 1 ? + D3D12_SMALL_MSAA_RESOURCE_PLACEMENT_ALIGNMENT : + D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT; + inOutResourceDesc.Alignment = smallAlignmentToTry; + const D3D12_RESOURCE_ALLOCATION_INFO smallAllocInfo = GetResourceAllocationInfoNative(inOutResourceDesc); + // Check if alignment requested has been granted. + if (smallAllocInfo.Alignment == smallAlignmentToTry) + { + return smallAllocInfo; + } + inOutResourceDesc.Alignment = 0; // Restore original + } +#endif // #if D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT + + return GetResourceAllocationInfoNative(inOutResourceDesc); +} + +bool AllocatorPimpl::NewAllocationWithinBudget(D3D12_HEAP_TYPE heapType, UINT64 size) +{ + Budget budget = {}; + GetBudgetForHeapType(budget, heapType); + return budget.UsageBytes + size <= budget.BudgetBytes; +} + +void AllocatorPimpl::WriteBudgetToJson(JsonWriter& json, const Budget& budget) +{ + json.BeginObject(); + { + json.WriteString(L"BudgetBytes"); + json.WriteNumber(budget.BudgetBytes); + json.WriteString(L"UsageBytes"); + json.WriteNumber(budget.UsageBytes); + } + json.EndObject(); +} +#endif // _D3D12MA_ALLOCATOR_PIMPL +#endif // _D3D12MA_ALLOCATOR_PIMPL + +#ifndef _D3D12MA_VIRTUAL_BLOCK_PIMPL +class VirtualBlockPimpl +{ +public: + const ALLOCATION_CALLBACKS m_AllocationCallbacks; + const UINT64 m_Size; + BlockMetadata* m_Metadata; + + VirtualBlockPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, const VIRTUAL_BLOCK_DESC& desc); + ~VirtualBlockPimpl(); +}; + +#ifndef _D3D12MA_VIRTUAL_BLOCK_PIMPL_FUNCTIONS +VirtualBlockPimpl::VirtualBlockPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, const VIRTUAL_BLOCK_DESC& desc) + : m_AllocationCallbacks(allocationCallbacks), m_Size(desc.Size) +{ + switch (desc.Flags & VIRTUAL_BLOCK_FLAG_ALGORITHM_MASK) + { + case VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR: + m_Metadata = D3D12MA_NEW(allocationCallbacks, BlockMetadata_Linear)(&m_AllocationCallbacks, true); + break; + default: + D3D12MA_ASSERT(0); + case 0: + m_Metadata = D3D12MA_NEW(allocationCallbacks, BlockMetadata_TLSF)(&m_AllocationCallbacks, true); + break; + } + m_Metadata->Init(m_Size); +} + +VirtualBlockPimpl::~VirtualBlockPimpl() +{ + D3D12MA_DELETE(m_AllocationCallbacks, m_Metadata); +} +#endif // _D3D12MA_VIRTUAL_BLOCK_PIMPL_FUNCTIONS +#endif // _D3D12MA_VIRTUAL_BLOCK_PIMPL + + +#ifndef _D3D12MA_MEMORY_BLOCK_FUNCTIONS +MemoryBlock::MemoryBlock( + AllocatorPimpl* allocator, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 size, + UINT id) + : m_Allocator(allocator), + m_HeapProps(heapProps), + m_HeapFlags(heapFlags), + m_Size(size), + m_Id(id) {} + +MemoryBlock::~MemoryBlock() +{ + if (m_Heap) + { + m_Heap->Release(); + m_Allocator->m_Budget.RemoveBlock( + m_Allocator->HeapPropertiesToMemorySegmentGroup(m_HeapProps), m_Size); + } +} + +HRESULT MemoryBlock::Init(ID3D12ProtectedResourceSession* pProtectedSession, bool denyMsaaTextures) +{ + D3D12MA_ASSERT(m_Heap == NULL && m_Size > 0); + + D3D12_HEAP_DESC heapDesc = {}; + heapDesc.SizeInBytes = m_Size; + heapDesc.Properties = m_HeapProps; + heapDesc.Alignment = HeapFlagsToAlignment(m_HeapFlags, denyMsaaTextures); + heapDesc.Flags = m_HeapFlags; + + HRESULT hr; +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + ID3D12Device4* const device4 = m_Allocator->GetDevice4(); + if (device4) + hr = m_Allocator->GetDevice4()->CreateHeap1(&heapDesc, pProtectedSession, D3D12MA_IID_PPV_ARGS(&m_Heap)); + else +#endif + { + if (pProtectedSession == NULL) + hr = m_Allocator->GetDevice()->CreateHeap(&heapDesc, D3D12MA_IID_PPV_ARGS(&m_Heap)); + else + hr = E_NOINTERFACE; + } + + if (SUCCEEDED(hr)) + { + m_Allocator->m_Budget.AddBlock( + m_Allocator->HeapPropertiesToMemorySegmentGroup(m_HeapProps), m_Size); + } + return hr; +} +#endif // _D3D12MA_MEMORY_BLOCK_FUNCTIONS + +#ifndef _D3D12MA_NORMAL_BLOCK_FUNCTIONS +NormalBlock::NormalBlock( + AllocatorPimpl* allocator, + BlockVector* blockVector, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 size, + UINT id) + : MemoryBlock(allocator, heapProps, heapFlags, size, id), + m_pMetadata(NULL), + m_BlockVector(blockVector) {} + +NormalBlock::~NormalBlock() +{ + if (m_pMetadata != NULL) + { + // THIS IS THE MOST IMPORTANT ASSERT IN THE ENTIRE LIBRARY! + // Hitting it means you have some memory leak - unreleased Allocation objects. + D3D12MA_ASSERT(m_pMetadata->IsEmpty() && "Some allocations were not freed before destruction of this memory block!"); + + D3D12MA_DELETE(m_Allocator->GetAllocs(), m_pMetadata); + } +} + +HRESULT NormalBlock::Init(UINT32 algorithm, ID3D12ProtectedResourceSession* pProtectedSession, bool denyMsaaTextures) +{ + HRESULT hr = MemoryBlock::Init(pProtectedSession, denyMsaaTextures); + if (FAILED(hr)) + { + return hr; + } + + switch (algorithm) + { + case POOL_FLAG_ALGORITHM_LINEAR: + m_pMetadata = D3D12MA_NEW(m_Allocator->GetAllocs(), BlockMetadata_Linear)(&m_Allocator->GetAllocs(), false); + break; + default: + D3D12MA_ASSERT(0); + case 0: + m_pMetadata = D3D12MA_NEW(m_Allocator->GetAllocs(), BlockMetadata_TLSF)(&m_Allocator->GetAllocs(), false); + break; + } + m_pMetadata->Init(m_Size); + + return hr; +} + +bool NormalBlock::Validate() const +{ + D3D12MA_VALIDATE(GetHeap() && + m_pMetadata && + m_pMetadata->GetSize() != 0 && + m_pMetadata->GetSize() == GetSize()); + return m_pMetadata->Validate(); +} +#endif // _D3D12MA_NORMAL_BLOCK_FUNCTIONS + +#ifndef _D3D12MA_COMMITTED_ALLOCATION_LIST_FUNCTIONS +void CommittedAllocationList::Init(bool useMutex, D3D12_HEAP_TYPE heapType, PoolPimpl* pool) +{ + m_UseMutex = useMutex; + m_HeapType = heapType; + m_Pool = pool; +} + +CommittedAllocationList::~CommittedAllocationList() +{ + if (!m_AllocationList.IsEmpty()) + { + D3D12MA_ASSERT(0 && "Unfreed committed allocations found!"); + } +} + +UINT CommittedAllocationList::GetMemorySegmentGroup(AllocatorPimpl* allocator) const +{ + if (m_Pool) + return allocator->HeapPropertiesToMemorySegmentGroup(m_Pool->GetDesc().HeapProperties); + else + return allocator->StandardHeapTypeToMemorySegmentGroup(m_HeapType); +} + +void CommittedAllocationList::AddStatistics(Statistics& inoutStats) +{ + MutexLockRead lock(m_Mutex, m_UseMutex); + + for (Allocation* alloc = m_AllocationList.Front(); + alloc != NULL; alloc = m_AllocationList.GetNext(alloc)) + { + const UINT64 size = alloc->GetSize(); + inoutStats.BlockCount++; + inoutStats.AllocationCount++; + inoutStats.BlockBytes += size; + inoutStats.AllocationBytes += size; + } +} + +void CommittedAllocationList::AddDetailedStatistics(DetailedStatistics& inoutStats) +{ + MutexLockRead lock(m_Mutex, m_UseMutex); + + for (Allocation* alloc = m_AllocationList.Front(); + alloc != NULL; alloc = m_AllocationList.GetNext(alloc)) + { + const UINT64 size = alloc->GetSize(); + inoutStats.Stats.BlockCount++; + inoutStats.Stats.BlockBytes += size; + AddDetailedStatisticsAllocation(inoutStats, size); + } +} + +void CommittedAllocationList::BuildStatsString(JsonWriter& json) +{ + MutexLockRead lock(m_Mutex, m_UseMutex); + + for (Allocation* alloc = m_AllocationList.Front(); + alloc != NULL; alloc = m_AllocationList.GetNext(alloc)) + { + json.BeginObject(true); + json.AddAllocationToObject(*alloc); + json.EndObject(); + } +} + +void CommittedAllocationList::Register(Allocation* alloc) +{ + MutexLockWrite lock(m_Mutex, m_UseMutex); + m_AllocationList.PushBack(alloc); +} + +void CommittedAllocationList::Unregister(Allocation* alloc) +{ + MutexLockWrite lock(m_Mutex, m_UseMutex); + m_AllocationList.Remove(alloc); +} +#endif // _D3D12MA_COMMITTED_ALLOCATION_LIST_FUNCTIONS + +#ifndef _D3D12MA_BLOCK_VECTOR_FUNCTIONS +BlockVector::BlockVector( + AllocatorPimpl* hAllocator, + const D3D12_HEAP_PROPERTIES& heapProps, + D3D12_HEAP_FLAGS heapFlags, + UINT64 preferredBlockSize, + size_t minBlockCount, + size_t maxBlockCount, + bool explicitBlockSize, + UINT64 minAllocationAlignment, + UINT32 algorithm, + bool denyMsaaTextures, + ID3D12ProtectedResourceSession* pProtectedSession) + : m_hAllocator(hAllocator), + m_HeapProps(heapProps), + m_HeapFlags(heapFlags), + m_PreferredBlockSize(preferredBlockSize), + m_MinBlockCount(minBlockCount), + m_MaxBlockCount(maxBlockCount), + m_ExplicitBlockSize(explicitBlockSize), + m_MinAllocationAlignment(minAllocationAlignment), + m_Algorithm(algorithm), + m_DenyMsaaTextures(denyMsaaTextures), + m_ProtectedSession(pProtectedSession), + m_HasEmptyBlock(false), + m_Blocks(hAllocator->GetAllocs()), + m_NextBlockId(0) {} + +BlockVector::~BlockVector() +{ + for (size_t i = m_Blocks.size(); i--; ) + { + D3D12MA_DELETE(m_hAllocator->GetAllocs(), m_Blocks[i]); + } +} + +HRESULT BlockVector::CreateMinBlocks() +{ + for (size_t i = 0; i < m_MinBlockCount; ++i) + { + HRESULT hr = CreateBlock(m_PreferredBlockSize, NULL); + if (FAILED(hr)) + { + return hr; + } + } + return S_OK; +} + +bool BlockVector::IsEmpty() +{ + MutexLockRead lock(m_Mutex, m_hAllocator->UseMutex()); + return m_Blocks.empty(); +} + +HRESULT BlockVector::Allocate( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + size_t allocationCount, + Allocation** pAllocations) +{ + size_t allocIndex; + HRESULT hr = S_OK; + + { + MutexLockWrite lock(m_Mutex, m_hAllocator->UseMutex()); + for (allocIndex = 0; allocIndex < allocationCount; ++allocIndex) + { + hr = AllocatePage( + size, + alignment, + allocDesc, + pAllocations + allocIndex); + if (FAILED(hr)) + { + break; + } + } + } + + if (FAILED(hr)) + { + // Free all already created allocations. + while (allocIndex--) + { + Free(pAllocations[allocIndex]); + } + ZeroMemory(pAllocations, sizeof(Allocation*) * allocationCount); + } + + return hr; +} + +void BlockVector::Free(Allocation* hAllocation) +{ + NormalBlock* pBlockToDelete = NULL; + + bool budgetExceeded = false; + if (IsHeapTypeStandard(m_HeapProps.Type)) + { + Budget budget = {}; + m_hAllocator->GetBudgetForHeapType(budget, m_HeapProps.Type); + budgetExceeded = budget.UsageBytes >= budget.BudgetBytes; + } + + // Scope for lock. + { + MutexLockWrite lock(m_Mutex, m_hAllocator->UseMutex()); + + NormalBlock* pBlock = hAllocation->m_Placed.block; + + pBlock->m_pMetadata->Free(hAllocation->GetAllocHandle()); + D3D12MA_HEAVY_ASSERT(pBlock->Validate()); + + const size_t blockCount = m_Blocks.size(); + // pBlock became empty after this deallocation. + if (pBlock->m_pMetadata->IsEmpty()) + { + // Already has empty Allocation. We don't want to have two, so delete this one. + if ((m_HasEmptyBlock || budgetExceeded) && + blockCount > m_MinBlockCount) + { + pBlockToDelete = pBlock; + Remove(pBlock); + } + // We now have first empty block. + else + { + m_HasEmptyBlock = true; + } + } + // pBlock didn't become empty, but we have another empty block - find and free that one. + // (This is optional, heuristics.) + else if (m_HasEmptyBlock && blockCount > m_MinBlockCount) + { + NormalBlock* pLastBlock = m_Blocks.back(); + if (pLastBlock->m_pMetadata->IsEmpty()) + { + pBlockToDelete = pLastBlock; + m_Blocks.pop_back(); + m_HasEmptyBlock = false; + } + } + + IncrementallySortBlocks(); + } + + // Destruction of a free Allocation. Deferred until this point, outside of mutex + // lock, for performance reason. + if (pBlockToDelete != NULL) + { + D3D12MA_DELETE(m_hAllocator->GetAllocs(), pBlockToDelete); + } +} + +HRESULT BlockVector::CreateResource( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + const D3D12_RESOURCE_DESC& resourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + HRESULT hr = Allocate(size, alignment, allocDesc, 1, ppAllocation); + if (SUCCEEDED(hr)) + { + ID3D12Resource* res = NULL; + hr = m_hAllocator->GetDevice()->CreatePlacedResource( + (*ppAllocation)->m_Placed.block->GetHeap(), + (*ppAllocation)->GetOffset(), + &resourceDesc, + InitialResourceState, + pOptimizedClearValue, + D3D12MA_IID_PPV_ARGS(&res)); + if (SUCCEEDED(hr)) + { + if (ppvResource != NULL) + { + hr = res->QueryInterface(riidResource, ppvResource); + } + if (SUCCEEDED(hr)) + { + (*ppAllocation)->SetResourcePointer(res, &resourceDesc); + } + else + { + res->Release(); + SAFE_RELEASE(*ppAllocation); + } + } + else + { + SAFE_RELEASE(*ppAllocation); + } + } + return hr; +} + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ +HRESULT BlockVector::CreateResource2( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + const D3D12_RESOURCE_DESC1& resourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + ID3D12Device8* const device8 = m_hAllocator->GetDevice8(); + if (device8 == NULL) + { + return E_NOINTERFACE; + } + + HRESULT hr = Allocate(size, alignment, allocDesc, 1, ppAllocation); + if (SUCCEEDED(hr)) + { + ID3D12Resource* res = NULL; + hr = device8->CreatePlacedResource1( + (*ppAllocation)->m_Placed.block->GetHeap(), + (*ppAllocation)->GetOffset(), + &resourceDesc, + InitialResourceState, + pOptimizedClearValue, + D3D12MA_IID_PPV_ARGS(&res)); + if (SUCCEEDED(hr)) + { + if (ppvResource != NULL) + { + hr = res->QueryInterface(riidResource, ppvResource); + } + if (SUCCEEDED(hr)) + { + (*ppAllocation)->SetResourcePointer(res, &resourceDesc); + } + else + { + res->Release(); + SAFE_RELEASE(*ppAllocation); + } + } + else + { + SAFE_RELEASE(*ppAllocation); + } + } + return hr; +} +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + +void BlockVector::AddStatistics(Statistics& inoutStats) +{ + MutexLockRead lock(m_Mutex, m_hAllocator->UseMutex()); + + for (size_t i = 0; i < m_Blocks.size(); ++i) + { + const NormalBlock* const pBlock = m_Blocks[i]; + D3D12MA_ASSERT(pBlock); + D3D12MA_HEAVY_ASSERT(pBlock->Validate()); + pBlock->m_pMetadata->AddStatistics(inoutStats); + } +} + +void BlockVector::AddDetailedStatistics(DetailedStatistics& inoutStats) +{ + MutexLockRead lock(m_Mutex, m_hAllocator->UseMutex()); + + for (size_t i = 0; i < m_Blocks.size(); ++i) + { + const NormalBlock* const pBlock = m_Blocks[i]; + D3D12MA_ASSERT(pBlock); + D3D12MA_HEAVY_ASSERT(pBlock->Validate()); + pBlock->m_pMetadata->AddDetailedStatistics(inoutStats); + } +} + +void BlockVector::WriteBlockInfoToJson(JsonWriter& json) +{ + MutexLockRead lock(m_Mutex, m_hAllocator->UseMutex()); + + json.BeginObject(); + + for (size_t i = 0, count = m_Blocks.size(); i < count; ++i) + { + const NormalBlock* const pBlock = m_Blocks[i]; + D3D12MA_ASSERT(pBlock); + D3D12MA_HEAVY_ASSERT(pBlock->Validate()); + json.BeginString(); + json.ContinueString(pBlock->GetId()); + json.EndString(); + + json.BeginObject(); + pBlock->m_pMetadata->WriteAllocationInfoToJson(json); + json.EndObject(); + } + + json.EndObject(); +} + +UINT64 BlockVector::CalcSumBlockSize() const +{ + UINT64 result = 0; + for (size_t i = m_Blocks.size(); i--; ) + { + result += m_Blocks[i]->m_pMetadata->GetSize(); + } + return result; +} + +UINT64 BlockVector::CalcMaxBlockSize() const +{ + UINT64 result = 0; + for (size_t i = m_Blocks.size(); i--; ) + { + result = D3D12MA_MAX(result, m_Blocks[i]->m_pMetadata->GetSize()); + if (result >= m_PreferredBlockSize) + { + break; + } + } + return result; +} + +void BlockVector::Remove(NormalBlock* pBlock) +{ + for (size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) + { + if (m_Blocks[blockIndex] == pBlock) + { + m_Blocks.remove(blockIndex); + return; + } + } + D3D12MA_ASSERT(0); +} + +void BlockVector::IncrementallySortBlocks() +{ + if (!m_IncrementalSort) + return; + // Bubble sort only until first swap. + for (size_t i = 1; i < m_Blocks.size(); ++i) + { + if (m_Blocks[i - 1]->m_pMetadata->GetSumFreeSize() > m_Blocks[i]->m_pMetadata->GetSumFreeSize()) + { + D3D12MA_SWAP(m_Blocks[i - 1], m_Blocks[i]); + return; + } + } +} + +void BlockVector::SortByFreeSize() +{ + D3D12MA_SORT(m_Blocks.begin(), m_Blocks.end(), + [](auto* b1, auto* b2) + { + return b1->m_pMetadata->GetSumFreeSize() < b2->m_pMetadata->GetSumFreeSize(); + }); +} + +HRESULT BlockVector::AllocatePage( + UINT64 size, + UINT64 alignment, + const ALLOCATION_DESC& allocDesc, + Allocation** pAllocation) +{ + // Early reject: requested allocation size is larger that maximum block size for this block vector. + if (size + D3D12MA_DEBUG_MARGIN > m_PreferredBlockSize) + { + return E_OUTOFMEMORY; + } + + UINT64 freeMemory = UINT64_MAX; + if (IsHeapTypeStandard(m_HeapProps.Type)) + { + Budget budget = {}; + m_hAllocator->GetBudgetForHeapType(budget, m_HeapProps.Type); + freeMemory = (budget.UsageBytes < budget.BudgetBytes) ? (budget.BudgetBytes - budget.UsageBytes) : 0; + } + + const bool canCreateNewBlock = + ((allocDesc.Flags & ALLOCATION_FLAG_NEVER_ALLOCATE) == 0) && + (m_Blocks.size() < m_MaxBlockCount) && + // Even if we don't have to stay within budget with this allocation, when the + // budget would be exceeded, we don't want to allocate new blocks, but always + // create resources as committed. + freeMemory >= size; + + // 1. Search existing allocations + { + // Forward order in m_Blocks - prefer blocks with smallest amount of free space. + for (size_t blockIndex = 0; blockIndex < m_Blocks.size(); ++blockIndex) + { + NormalBlock* const pCurrBlock = m_Blocks[blockIndex]; + D3D12MA_ASSERT(pCurrBlock); + HRESULT hr = AllocateFromBlock( + pCurrBlock, + size, + alignment, + allocDesc.Flags, + allocDesc.pPrivateData, + allocDesc.Flags & ALLOCATION_FLAG_STRATEGY_MASK, + pAllocation); + if (SUCCEEDED(hr)) + { + return hr; + } + } + } + + // 2. Try to create new block. + if (canCreateNewBlock) + { + // Calculate optimal size for new block. + UINT64 newBlockSize = m_PreferredBlockSize; + UINT newBlockSizeShift = 0; + + if (!m_ExplicitBlockSize) + { + // Allocate 1/8, 1/4, 1/2 as first blocks. + const UINT64 maxExistingBlockSize = CalcMaxBlockSize(); + for (UINT i = 0; i < NEW_BLOCK_SIZE_SHIFT_MAX; ++i) + { + const UINT64 smallerNewBlockSize = newBlockSize / 2; + if (smallerNewBlockSize > maxExistingBlockSize && smallerNewBlockSize >= size * 2) + { + newBlockSize = smallerNewBlockSize; + ++newBlockSizeShift; + } + else + { + break; + } + } + } + + size_t newBlockIndex = 0; + HRESULT hr = newBlockSize <= freeMemory ? + CreateBlock(newBlockSize, &newBlockIndex) : E_OUTOFMEMORY; + // Allocation of this size failed? Try 1/2, 1/4, 1/8 of m_PreferredBlockSize. + if (!m_ExplicitBlockSize) + { + while (FAILED(hr) && newBlockSizeShift < NEW_BLOCK_SIZE_SHIFT_MAX) + { + const UINT64 smallerNewBlockSize = newBlockSize / 2; + if (smallerNewBlockSize >= size) + { + newBlockSize = smallerNewBlockSize; + ++newBlockSizeShift; + hr = newBlockSize <= freeMemory ? + CreateBlock(newBlockSize, &newBlockIndex) : E_OUTOFMEMORY; + } + else + { + break; + } + } + } + + if (SUCCEEDED(hr)) + { + NormalBlock* const pBlock = m_Blocks[newBlockIndex]; + D3D12MA_ASSERT(pBlock->m_pMetadata->GetSize() >= size); + + hr = AllocateFromBlock( + pBlock, + size, + alignment, + allocDesc.Flags, + allocDesc.pPrivateData, + allocDesc.Flags & ALLOCATION_FLAG_STRATEGY_MASK, + pAllocation); + if (SUCCEEDED(hr)) + { + return hr; + } + else + { + // Allocation from new block failed, possibly due to D3D12MA_DEBUG_MARGIN or alignment. + return E_OUTOFMEMORY; + } + } + } + + return E_OUTOFMEMORY; +} + +HRESULT BlockVector::AllocateFromBlock( + NormalBlock* pBlock, + UINT64 size, + UINT64 alignment, + ALLOCATION_FLAGS allocFlags, + void* pPrivateData, + UINT32 strategy, + Allocation** pAllocation) +{ + alignment = D3D12MA_MAX(alignment, m_MinAllocationAlignment); + + AllocationRequest currRequest = {}; + if (pBlock->m_pMetadata->CreateAllocationRequest( + size, + alignment, + allocFlags & ALLOCATION_FLAG_UPPER_ADDRESS, + strategy, + &currRequest)) + { + return CommitAllocationRequest(currRequest, pBlock, size, alignment, pPrivateData, pAllocation); + } + return E_OUTOFMEMORY; +} + +HRESULT BlockVector::CommitAllocationRequest( + AllocationRequest& allocRequest, + NormalBlock* pBlock, + UINT64 size, + UINT64 alignment, + void* pPrivateData, + Allocation** pAllocation) +{ + // We no longer have an empty Allocation. + if (pBlock->m_pMetadata->IsEmpty()) + m_HasEmptyBlock = false; + + *pAllocation = m_hAllocator->GetAllocationObjectAllocator().Allocate(m_hAllocator, size, alignment, allocRequest.zeroInitialized); + pBlock->m_pMetadata->Alloc(allocRequest, size, *pAllocation); + + (*pAllocation)->InitPlaced(allocRequest.allocHandle, pBlock); + (*pAllocation)->SetPrivateData(pPrivateData); + + D3D12MA_HEAVY_ASSERT(pBlock->Validate()); + m_hAllocator->m_Budget.AddAllocation(m_hAllocator->HeapPropertiesToMemorySegmentGroup(m_HeapProps), size); + + return S_OK; +} + +HRESULT BlockVector::CreateBlock( + UINT64 blockSize, + size_t* pNewBlockIndex) +{ + NormalBlock* const pBlock = D3D12MA_NEW(m_hAllocator->GetAllocs(), NormalBlock)( + m_hAllocator, + this, + m_HeapProps, + m_HeapFlags, + blockSize, + m_NextBlockId++); + HRESULT hr = pBlock->Init(m_Algorithm, m_ProtectedSession, m_DenyMsaaTextures); + if (FAILED(hr)) + { + D3D12MA_DELETE(m_hAllocator->GetAllocs(), pBlock); + return hr; + } + + m_Blocks.push_back(pBlock); + if (pNewBlockIndex != NULL) + { + *pNewBlockIndex = m_Blocks.size() - 1; + } + + return hr; +} +#endif // _D3D12MA_BLOCK_VECTOR_FUNCTIONS + +#ifndef _D3D12MA_DEFRAGMENTATION_CONTEXT_PIMPL_FUNCTIONS +DefragmentationContextPimpl::DefragmentationContextPimpl( + AllocatorPimpl* hAllocator, + const DEFRAGMENTATION_DESC& desc, + BlockVector* poolVector) + : m_MaxPassBytes(desc.MaxBytesPerPass == 0 ? UINT64_MAX : desc.MaxBytesPerPass), + m_MaxPassAllocations(desc.MaxAllocationsPerPass == 0 ? UINT32_MAX : desc.MaxAllocationsPerPass), + m_Moves(hAllocator->GetAllocs()) +{ + m_Algorithm = desc.Flags & DEFRAGMENTATION_FLAG_ALGORITHM_MASK; + + if (poolVector != NULL) + { + m_BlockVectorCount = 1; + m_PoolBlockVector = poolVector; + m_pBlockVectors = &m_PoolBlockVector; + m_PoolBlockVector->SetIncrementalSort(false); + m_PoolBlockVector->SortByFreeSize(); + } + else + { + m_BlockVectorCount = hAllocator->GetDefaultPoolCount(); + m_PoolBlockVector = NULL; + m_pBlockVectors = hAllocator->GetDefaultPools(); + for (UINT32 i = 0; i < m_BlockVectorCount; ++i) + { + BlockVector* vector = m_pBlockVectors[i]; + if (vector != NULL) + { + vector->SetIncrementalSort(false); + vector->SortByFreeSize(); + } + } + } + + switch (m_Algorithm) + { + case 0: // Default algorithm + m_Algorithm = DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED; + case DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED: + { + m_AlgorithmState = D3D12MA_NEW_ARRAY(hAllocator->GetAllocs(), StateBalanced, m_BlockVectorCount); + break; + } + } +} + +DefragmentationContextPimpl::~DefragmentationContextPimpl() +{ + if (m_PoolBlockVector != NULL) + m_PoolBlockVector->SetIncrementalSort(true); + else + { + for (UINT32 i = 0; i < m_BlockVectorCount; ++i) + { + BlockVector* vector = m_pBlockVectors[i]; + if (vector != NULL) + vector->SetIncrementalSort(true); + } + } + + if (m_AlgorithmState) + { + switch (m_Algorithm) + { + case DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED: + D3D12MA_DELETE_ARRAY(m_Moves.GetAllocs(), reinterpret_cast(m_AlgorithmState), m_BlockVectorCount); + break; + default: + D3D12MA_ASSERT(0); + } + } +} + +HRESULT DefragmentationContextPimpl::DefragmentPassBegin(DEFRAGMENTATION_PASS_MOVE_INFO& moveInfo) +{ + if (m_PoolBlockVector != NULL) + { + MutexLockWrite lock(m_PoolBlockVector->GetMutex(), m_PoolBlockVector->m_hAllocator->UseMutex()); + + if (m_PoolBlockVector->GetBlockCount() > 1) + ComputeDefragmentation(*m_PoolBlockVector, 0); + else if (m_PoolBlockVector->GetBlockCount() == 1) + ReallocWithinBlock(*m_PoolBlockVector, m_PoolBlockVector->GetBlock(0)); + + // Setup index into block vector + for (size_t i = 0; i < m_Moves.size(); ++i) + m_Moves[i].pDstTmpAllocation->SetPrivateData(0); + } + else + { + for (UINT32 i = 0; i < m_BlockVectorCount; ++i) + { + if (m_pBlockVectors[i] != NULL) + { + MutexLockWrite lock(m_pBlockVectors[i]->GetMutex(), m_pBlockVectors[i]->m_hAllocator->UseMutex()); + + bool end = false; + size_t movesOffset = m_Moves.size(); + if (m_pBlockVectors[i]->GetBlockCount() > 1) + { + end = ComputeDefragmentation(*m_pBlockVectors[i], i); + } + else if (m_pBlockVectors[i]->GetBlockCount() == 1) + { + end = ReallocWithinBlock(*m_pBlockVectors[i], m_pBlockVectors[i]->GetBlock(0)); + } + + // Setup index into block vector + for (; movesOffset < m_Moves.size(); ++movesOffset) + m_Moves[movesOffset].pDstTmpAllocation->SetPrivateData(reinterpret_cast(static_cast(i))); + + if (end) + break; + } + } + } + + moveInfo.MoveCount = static_cast(m_Moves.size()); + if (moveInfo.MoveCount > 0) + { + moveInfo.pMoves = m_Moves.data(); + return S_FALSE; + } + + moveInfo.pMoves = NULL; + return S_OK; +} + +HRESULT DefragmentationContextPimpl::DefragmentPassEnd(DEFRAGMENTATION_PASS_MOVE_INFO& moveInfo) +{ + D3D12MA_ASSERT(moveInfo.MoveCount > 0 ? moveInfo.pMoves != NULL : true); + + HRESULT result = S_OK; + Vector immovableBlocks(m_Moves.GetAllocs()); + + for (uint32_t i = 0; i < moveInfo.MoveCount; ++i) + { + DEFRAGMENTATION_MOVE& move = moveInfo.pMoves[i]; + size_t prevCount = 0, currentCount = 0; + UINT64 freedBlockSize = 0; + + UINT32 vectorIndex; + BlockVector* vector; + if (m_PoolBlockVector != NULL) + { + vectorIndex = 0; + vector = m_PoolBlockVector; + } + else + { + vectorIndex = static_cast(reinterpret_cast(move.pDstTmpAllocation->GetPrivateData())); + vector = m_pBlockVectors[vectorIndex]; + D3D12MA_ASSERT(vector != NULL); + } + + switch (move.Operation) + { + case DEFRAGMENTATION_MOVE_OPERATION_COPY: + { + move.pSrcAllocation->SwapBlockAllocation(move.pDstTmpAllocation); + + // Scope for locks, Free have it's own lock + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + prevCount = vector->GetBlockCount(); + freedBlockSize = move.pDstTmpAllocation->GetBlock()->m_pMetadata->GetSize(); + } + move.pDstTmpAllocation->Release(); + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + currentCount = vector->GetBlockCount(); + } + + result = S_FALSE; + break; + } + case DEFRAGMENTATION_MOVE_OPERATION_IGNORE: + { + m_PassStats.BytesMoved -= move.pSrcAllocation->GetSize(); + --m_PassStats.AllocationsMoved; + move.pDstTmpAllocation->Release(); + + NormalBlock* newBlock = move.pSrcAllocation->GetBlock(); + bool notPresent = true; + for (const FragmentedBlock& block : immovableBlocks) + { + if (block.block == newBlock) + { + notPresent = false; + break; + } + } + if (notPresent) + immovableBlocks.push_back({ vectorIndex, newBlock }); + break; + } + case DEFRAGMENTATION_MOVE_OPERATION_DESTROY: + { + m_PassStats.BytesMoved -= move.pSrcAllocation->GetSize(); + --m_PassStats.AllocationsMoved; + // Scope for locks, Free have it's own lock + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + prevCount = vector->GetBlockCount(); + freedBlockSize = move.pSrcAllocation->GetBlock()->m_pMetadata->GetSize(); + } + move.pSrcAllocation->Release(); + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + currentCount = vector->GetBlockCount(); + } + freedBlockSize *= prevCount - currentCount; + + UINT64 dstBlockSize; + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + dstBlockSize = move.pDstTmpAllocation->GetBlock()->m_pMetadata->GetSize(); + } + move.pDstTmpAllocation->Release(); + { + MutexLockRead lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + freedBlockSize += dstBlockSize * (currentCount - vector->GetBlockCount()); + currentCount = vector->GetBlockCount(); + } + + result = S_FALSE; + break; + } + default: + D3D12MA_ASSERT(0); + } + + if (prevCount > currentCount) + { + size_t freedBlocks = prevCount - currentCount; + m_PassStats.HeapsFreed += static_cast(freedBlocks); + m_PassStats.BytesFreed += freedBlockSize; + } + } + moveInfo.MoveCount = 0; + moveInfo.pMoves = NULL; + m_Moves.clear(); + + // Update stats + m_GlobalStats.AllocationsMoved += m_PassStats.AllocationsMoved; + m_GlobalStats.BytesFreed += m_PassStats.BytesFreed; + m_GlobalStats.BytesMoved += m_PassStats.BytesMoved; + m_GlobalStats.HeapsFreed += m_PassStats.HeapsFreed; + m_PassStats = { 0 }; + + // Move blocks with immovable allocations according to algorithm + if (immovableBlocks.size() > 0) + { + // Move to the begining + for (const FragmentedBlock& block : immovableBlocks) + { + BlockVector* vector = m_pBlockVectors[block.data]; + MutexLockWrite lock(vector->GetMutex(), vector->m_hAllocator->UseMutex()); + + for (size_t i = m_ImmovableBlockCount; i < vector->GetBlockCount(); ++i) + { + if (vector->GetBlock(i) == block.block) + { + D3D12MA_SWAP(vector->m_Blocks[i], vector->m_Blocks[m_ImmovableBlockCount++]); + break; + } + } + } + } + return result; +} + +bool DefragmentationContextPimpl::ComputeDefragmentation(BlockVector& vector, size_t index) +{ + switch (m_Algorithm) + { + case DEFRAGMENTATION_FLAG_ALGORITHM_FAST: + return ComputeDefragmentation_Fast(vector); + default: + D3D12MA_ASSERT(0); + case DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED: + return ComputeDefragmentation_Balanced(vector, index, true); + case DEFRAGMENTATION_FLAG_ALGORITHM_FULL: + return ComputeDefragmentation_Full(vector); + } +} + +DefragmentationContextPimpl::MoveAllocationData DefragmentationContextPimpl::GetMoveData( + AllocHandle handle, BlockMetadata* metadata) +{ + MoveAllocationData moveData; + moveData.move.pSrcAllocation = (Allocation*)metadata->GetAllocationPrivateData(handle); + moveData.size = moveData.move.pSrcAllocation->GetSize(); + moveData.alignment = moveData.move.pSrcAllocation->GetAlignment(); + moveData.flags = ALLOCATION_FLAG_NONE; + + return moveData; +} + +DefragmentationContextPimpl::CounterStatus DefragmentationContextPimpl::CheckCounters(UINT64 bytes) +{ + // Ignore allocation if will exceed max size for copy + if (m_PassStats.BytesMoved + bytes > m_MaxPassBytes) + { + if (++m_IgnoredAllocs < MAX_ALLOCS_TO_IGNORE) + return CounterStatus::Ignore; + else + return CounterStatus::End; + } + return CounterStatus::Pass; +} + +bool DefragmentationContextPimpl::IncrementCounters(UINT64 bytes) +{ + m_PassStats.BytesMoved += bytes; + // Early return when max found + if (++m_PassStats.AllocationsMoved >= m_MaxPassAllocations || m_PassStats.BytesMoved >= m_MaxPassBytes) + { + D3D12MA_ASSERT(m_PassStats.AllocationsMoved == m_MaxPassAllocations || + m_PassStats.BytesMoved == m_MaxPassBytes && "Exceeded maximal pass threshold!"); + return true; + } + return false; +} + +bool DefragmentationContextPimpl::ReallocWithinBlock(BlockVector& vector, NormalBlock* block) +{ + BlockMetadata* metadata = block->m_pMetadata; + + for (AllocHandle handle = metadata->GetAllocationListBegin(); + handle != (AllocHandle)0; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.pSrcAllocation->GetPrivateData() == this) + continue; + switch (CheckCounters(moveData.move.pSrcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + default: + D3D12MA_ASSERT(0); + case CounterStatus::Pass: + break; + } + + UINT64 offset = moveData.move.pSrcAllocation->GetOffset(); + if (offset != 0 && metadata->GetSumFreeSize() >= moveData.size) + { + AllocationRequest request = {}; + if (metadata->CreateAllocationRequest( + moveData.size, + moveData.alignment, + false, + ALLOCATION_FLAG_STRATEGY_MIN_OFFSET, + &request)) + { + if (metadata->GetAllocationOffset(request.allocHandle) < offset) + { + if (SUCCEEDED(vector.CommitAllocationRequest( + request, + block, + moveData.size, + moveData.alignment, + this, + &moveData.move.pDstTmpAllocation))) + { + m_Moves.push_back(moveData.move); + if (IncrementCounters(moveData.size)) + return true; + } + } + } + } + } + return false; +} + +bool DefragmentationContextPimpl::AllocInOtherBlock(size_t start, size_t end, MoveAllocationData& data, BlockVector& vector) +{ + for (; start < end; ++start) + { + NormalBlock* dstBlock = vector.GetBlock(start); + if (dstBlock->m_pMetadata->GetSumFreeSize() >= data.size) + { + if (SUCCEEDED(vector.AllocateFromBlock(dstBlock, + data.size, + data.alignment, + data.flags, + this, + 0, + &data.move.pDstTmpAllocation))) + { + m_Moves.push_back(data.move); + if (IncrementCounters(data.size)) + return true; + break; + } + } + } + return false; +} + +bool DefragmentationContextPimpl::ComputeDefragmentation_Fast(BlockVector& vector) +{ + // Move only between blocks + + // Go through allocations in last blocks and try to fit them inside first ones + for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i) + { + BlockMetadata* metadata = vector.GetBlock(i)->m_pMetadata; + + for (AllocHandle handle = metadata->GetAllocationListBegin(); + handle != (AllocHandle)0; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.pSrcAllocation->GetPrivateData() == this) + continue; + switch (CheckCounters(moveData.move.pSrcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + default: + D3D12MA_ASSERT(0); + case CounterStatus::Pass: + break; + } + + // Check all previous blocks for free space + if (AllocInOtherBlock(0, i, moveData, vector)) + return true; + } + } + return false; +} + +bool DefragmentationContextPimpl::ComputeDefragmentation_Balanced(BlockVector& vector, size_t index, bool update) +{ + // Go over every allocation and try to fit it in previous blocks at lowest offsets, + // if not possible: realloc within single block to minimize offset (exclude offset == 0), + // but only if there are noticable gaps between them (some heuristic, ex. average size of allocation in block) + D3D12MA_ASSERT(m_AlgorithmState != NULL); + + StateBalanced& vectorState = reinterpret_cast(m_AlgorithmState)[index]; + if (update && vectorState.avgAllocSize == UINT64_MAX) + UpdateVectorStatistics(vector, vectorState); + + const size_t startMoveCount = m_Moves.size(); + UINT64 minimalFreeRegion = vectorState.avgFreeSize / 2; + for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i) + { + NormalBlock* block = vector.GetBlock(i); + BlockMetadata* metadata = block->m_pMetadata; + UINT64 prevFreeRegionSize = 0; + + for (AllocHandle handle = metadata->GetAllocationListBegin(); + handle != (AllocHandle)0; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.pSrcAllocation->GetPrivateData() == this) + continue; + switch (CheckCounters(moveData.move.pSrcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + default: + D3D12MA_ASSERT(0); + case CounterStatus::Pass: + break; + } + + // Check all previous blocks for free space + const size_t prevMoveCount = m_Moves.size(); + if (AllocInOtherBlock(0, i, moveData, vector)) + return true; + + UINT64 nextFreeRegionSize = metadata->GetNextFreeRegionSize(handle); + // If no room found then realloc within block for lower offset + UINT64 offset = moveData.move.pSrcAllocation->GetOffset(); + if (prevMoveCount == m_Moves.size() && offset != 0 && metadata->GetSumFreeSize() >= moveData.size) + { + // Check if realloc will make sense + if (prevFreeRegionSize >= minimalFreeRegion || + nextFreeRegionSize >= minimalFreeRegion || + moveData.size <= vectorState.avgFreeSize || + moveData.size <= vectorState.avgAllocSize) + { + AllocationRequest request = {}; + if (metadata->CreateAllocationRequest( + moveData.size, + moveData.alignment, + false, + ALLOCATION_FLAG_STRATEGY_MIN_OFFSET, + &request)) + { + if (metadata->GetAllocationOffset(request.allocHandle) < offset) + { + if (SUCCEEDED(vector.CommitAllocationRequest( + request, + block, + moveData.size, + moveData.alignment, + this, + &moveData.move.pDstTmpAllocation))) + { + m_Moves.push_back(moveData.move); + if (IncrementCounters(moveData.size)) + return true; + } + } + } + } + } + prevFreeRegionSize = nextFreeRegionSize; + } + } + + // No moves perfomed, update statistics to current vector state + if (startMoveCount == m_Moves.size() && !update) + { + vectorState.avgAllocSize = UINT64_MAX; + return ComputeDefragmentation_Balanced(vector, index, false); + } + return false; +} + +bool DefragmentationContextPimpl::ComputeDefragmentation_Full(BlockVector& vector) +{ + // Go over every allocation and try to fit it in previous blocks at lowest offsets, + // if not possible: realloc within single block to minimize offset (exclude offset == 0) + + for (size_t i = vector.GetBlockCount() - 1; i > m_ImmovableBlockCount; --i) + { + NormalBlock* block = vector.GetBlock(i); + BlockMetadata* metadata = block->m_pMetadata; + + for (AllocHandle handle = metadata->GetAllocationListBegin(); + handle != (AllocHandle)0; + handle = metadata->GetNextAllocation(handle)) + { + MoveAllocationData moveData = GetMoveData(handle, metadata); + // Ignore newly created allocations by defragmentation algorithm + if (moveData.move.pSrcAllocation->GetPrivateData() == this) + continue; + switch (CheckCounters(moveData.move.pSrcAllocation->GetSize())) + { + case CounterStatus::Ignore: + continue; + case CounterStatus::End: + return true; + default: + D3D12MA_ASSERT(0); + case CounterStatus::Pass: + break; + } + + // Check all previous blocks for free space + const size_t prevMoveCount = m_Moves.size(); + if (AllocInOtherBlock(0, i, moveData, vector)) + return true; + + // If no room found then realloc within block for lower offset + UINT64 offset = moveData.move.pSrcAllocation->GetOffset(); + if (prevMoveCount == m_Moves.size() && offset != 0 && metadata->GetSumFreeSize() >= moveData.size) + { + AllocationRequest request = {}; + if (metadata->CreateAllocationRequest( + moveData.size, + moveData.alignment, + false, + ALLOCATION_FLAG_STRATEGY_MIN_OFFSET, + &request)) + { + if (metadata->GetAllocationOffset(request.allocHandle) < offset) + { + if (SUCCEEDED(vector.CommitAllocationRequest( + request, + block, + moveData.size, + moveData.alignment, + this, + &moveData.move.pDstTmpAllocation))) + { + m_Moves.push_back(moveData.move); + if (IncrementCounters(moveData.size)) + return true; + } + } + } + } + } + } + return false; +} + +void DefragmentationContextPimpl::UpdateVectorStatistics(BlockVector& vector, StateBalanced& state) +{ + size_t allocCount = 0; + size_t freeCount = 0; + state.avgFreeSize = 0; + state.avgAllocSize = 0; + + for (size_t i = 0; i < vector.GetBlockCount(); ++i) + { + BlockMetadata* metadata = vector.GetBlock(i)->m_pMetadata; + + allocCount += metadata->GetAllocationCount(); + freeCount += metadata->GetFreeRegionsCount(); + state.avgFreeSize += metadata->GetSumFreeSize(); + state.avgAllocSize += metadata->GetSize(); + } + + state.avgAllocSize = (state.avgAllocSize - state.avgFreeSize) / allocCount; + state.avgFreeSize /= freeCount; +} +#endif // _D3D12MA_DEFRAGMENTATION_CONTEXT_PIMPL_FUNCTIONS + +#ifndef _D3D12MA_POOL_PIMPL_FUNCTIONS +PoolPimpl::PoolPimpl(AllocatorPimpl* allocator, const POOL_DESC& desc) + : m_Allocator(allocator), + m_Desc(desc), + m_BlockVector(NULL), + m_Name(NULL) +{ + const bool explicitBlockSize = desc.BlockSize != 0; + const UINT64 preferredBlockSize = explicitBlockSize ? desc.BlockSize : D3D12MA_DEFAULT_BLOCK_SIZE; + UINT maxBlockCount = desc.MaxBlockCount != 0 ? desc.MaxBlockCount : UINT_MAX; + +#ifndef __ID3D12Device4_INTERFACE_DEFINED__ + D3D12MA_ASSERT(m_Desc.pProtectedSession == NULL); +#endif + + m_BlockVector = D3D12MA_NEW(allocator->GetAllocs(), BlockVector)( + allocator, desc.HeapProperties, desc.HeapFlags, + preferredBlockSize, + desc.MinBlockCount, maxBlockCount, + explicitBlockSize, + D3D12MA_MAX(desc.MinAllocationAlignment, (UINT64)D3D12MA_DEBUG_ALIGNMENT), + desc.Flags & POOL_FLAG_ALGORITHM_MASK, + desc.Flags & POOL_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED, + desc.pProtectedSession); +} + +PoolPimpl::~PoolPimpl() +{ + D3D12MA_ASSERT(m_PrevPool == NULL && m_NextPool == NULL); + FreeName(); + D3D12MA_DELETE(m_Allocator->GetAllocs(), m_BlockVector); +} + +HRESULT PoolPimpl::Init() +{ + m_CommittedAllocations.Init(m_Allocator->UseMutex(), m_Desc.HeapProperties.Type, this); + return m_BlockVector->CreateMinBlocks(); +} + +void PoolPimpl::GetStatistics(Statistics& outStats) +{ + ClearStatistics(outStats); + m_BlockVector->AddStatistics(outStats); + m_CommittedAllocations.AddStatistics(outStats); +} + +void PoolPimpl::CalculateStatistics(DetailedStatistics& outStats) +{ + ClearDetailedStatistics(outStats); + AddDetailedStatistics(outStats); +} + +void PoolPimpl::AddDetailedStatistics(DetailedStatistics& inoutStats) +{ + m_BlockVector->AddDetailedStatistics(inoutStats); + m_CommittedAllocations.AddDetailedStatistics(inoutStats); +} + +void PoolPimpl::SetName(LPCWSTR Name) +{ + FreeName(); + + if (Name) + { + const size_t nameCharCount = wcslen(Name) + 1; + m_Name = D3D12MA_NEW_ARRAY(m_Allocator->GetAllocs(), WCHAR, nameCharCount); + memcpy(m_Name, Name, nameCharCount * sizeof(WCHAR)); + } +} + +void PoolPimpl::FreeName() +{ + if (m_Name) + { + const size_t nameCharCount = wcslen(m_Name) + 1; + D3D12MA_DELETE_ARRAY(m_Allocator->GetAllocs(), m_Name, nameCharCount); + m_Name = NULL; + } +} +#endif // _D3D12MA_POOL_PIMPL_FUNCTIONS + + +#ifndef _D3D12MA_PUBLIC_INTERFACE +HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator) +{ + if (!pDesc || !ppAllocator || !pDesc->pDevice || !pDesc->pAdapter || + !(pDesc->PreferredBlockSize == 0 || (pDesc->PreferredBlockSize >= 16 && pDesc->PreferredBlockSize < 0x10000000000ull))) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to CreateAllocator."); + return E_INVALIDARG; + } + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + ALLOCATION_CALLBACKS allocationCallbacks; + SetupAllocationCallbacks(allocationCallbacks, pDesc->pAllocationCallbacks); + + *ppAllocator = D3D12MA_NEW(allocationCallbacks, Allocator)(allocationCallbacks, *pDesc); + HRESULT hr = (*ppAllocator)->m_Pimpl->Init(*pDesc); + if (FAILED(hr)) + { + D3D12MA_DELETE(allocationCallbacks, *ppAllocator); + *ppAllocator = NULL; + } + return hr; +} + +HRESULT CreateVirtualBlock(const VIRTUAL_BLOCK_DESC* pDesc, VirtualBlock** ppVirtualBlock) +{ + if (!pDesc || !ppVirtualBlock) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to CreateVirtualBlock."); + return E_INVALIDARG; + } + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + ALLOCATION_CALLBACKS allocationCallbacks; + SetupAllocationCallbacks(allocationCallbacks, pDesc->pAllocationCallbacks); + + *ppVirtualBlock = D3D12MA_NEW(allocationCallbacks, VirtualBlock)(allocationCallbacks, *pDesc); + return S_OK; +} + +#ifndef _D3D12MA_IUNKNOWN_IMPL_FUNCTIONS +HRESULT STDMETHODCALLTYPE IUnknownImpl::QueryInterface(REFIID riid, void** ppvObject) +{ + if (ppvObject == NULL) + return E_POINTER; + if (riid == IID_IUnknown) + { + ++m_RefCount; + *ppvObject = this; + return S_OK; + } + *ppvObject = NULL; + return E_NOINTERFACE; +} + +ULONG STDMETHODCALLTYPE IUnknownImpl::AddRef() +{ + return ++m_RefCount; +} + +ULONG STDMETHODCALLTYPE IUnknownImpl::Release() +{ + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + const uint32_t newRefCount = --m_RefCount; + if (newRefCount == 0) + ReleaseThis(); + return newRefCount; +} +#endif // _D3D12MA_IUNKNOWN_IMPL_FUNCTIONS + +#ifndef _D3D12MA_ALLOCATION_FUNCTIONS +void Allocation::PackedData::SetType(Type type) +{ + const UINT u = (UINT)type; + D3D12MA_ASSERT(u < (1u << 2)); + m_Type = u; +} + +void Allocation::PackedData::SetResourceDimension(D3D12_RESOURCE_DIMENSION resourceDimension) +{ + const UINT u = (UINT)resourceDimension; + D3D12MA_ASSERT(u < (1u << 3)); + m_ResourceDimension = u; +} + +void Allocation::PackedData::SetResourceFlags(D3D12_RESOURCE_FLAGS resourceFlags) +{ + const UINT u = (UINT)resourceFlags; + D3D12MA_ASSERT(u < (1u << 24)); + m_ResourceFlags = u; +} + +void Allocation::PackedData::SetTextureLayout(D3D12_TEXTURE_LAYOUT textureLayout) +{ + const UINT u = (UINT)textureLayout; + D3D12MA_ASSERT(u < (1u << 9)); + m_TextureLayout = u; +} + +UINT64 Allocation::GetOffset() const +{ + switch (m_PackedData.GetType()) + { + case TYPE_COMMITTED: + case TYPE_HEAP: + return 0; + case TYPE_PLACED: + return m_Placed.block->m_pMetadata->GetAllocationOffset(m_Placed.allocHandle); + default: + D3D12MA_ASSERT(0); + return 0; + } +} + +void Allocation::SetResource(ID3D12Resource* pResource) +{ + if (pResource != m_Resource) + { + if (m_Resource) + m_Resource->Release(); + m_Resource = pResource; + if (m_Resource) + m_Resource->AddRef(); + } +} + +ID3D12Heap* Allocation::GetHeap() const +{ + switch (m_PackedData.GetType()) + { + case TYPE_COMMITTED: + return NULL; + case TYPE_PLACED: + return m_Placed.block->GetHeap(); + case TYPE_HEAP: + return m_Heap.heap; + default: + D3D12MA_ASSERT(0); + return 0; + } +} + +void Allocation::SetName(LPCWSTR Name) +{ + FreeName(); + + if (Name) + { + const size_t nameCharCount = wcslen(Name) + 1; + m_Name = D3D12MA_NEW_ARRAY(m_Allocator->GetAllocs(), WCHAR, nameCharCount); + memcpy(m_Name, Name, nameCharCount * sizeof(WCHAR)); + } +} + +void Allocation::ReleaseThis() +{ + if (this == NULL) + { + return; + } + + SAFE_RELEASE(m_Resource); + + switch (m_PackedData.GetType()) + { + case TYPE_COMMITTED: + m_Allocator->FreeCommittedMemory(this); + break; + case TYPE_PLACED: + m_Allocator->FreePlacedMemory(this); + break; + case TYPE_HEAP: + m_Allocator->FreeHeapMemory(this); + break; + } + + FreeName(); + + m_Allocator->GetAllocationObjectAllocator().Free(this); +} + +Allocation::Allocation(AllocatorPimpl* allocator, UINT64 size, UINT64 alignment, BOOL wasZeroInitialized) + : m_Allocator{ allocator }, + m_Size{ size }, + m_Alignment{ alignment }, + m_Resource{ NULL }, + m_Name{ NULL } +{ + D3D12MA_ASSERT(allocator); + + m_PackedData.SetType(TYPE_COUNT); + m_PackedData.SetResourceDimension(D3D12_RESOURCE_DIMENSION_UNKNOWN); + m_PackedData.SetResourceFlags(D3D12_RESOURCE_FLAG_NONE); + m_PackedData.SetTextureLayout(D3D12_TEXTURE_LAYOUT_UNKNOWN); + m_PackedData.SetWasZeroInitialized(wasZeroInitialized); +} + +void Allocation::InitCommitted(CommittedAllocationList* list) +{ + m_PackedData.SetType(TYPE_COMMITTED); + m_Committed.list = list; + m_Committed.prev = NULL; + m_Committed.next = NULL; +} + +void Allocation::InitPlaced(AllocHandle allocHandle, NormalBlock* block) +{ + m_PackedData.SetType(TYPE_PLACED); + m_Placed.allocHandle = allocHandle; + m_Placed.block = block; +} + +void Allocation::InitHeap(CommittedAllocationList* list, ID3D12Heap* heap) +{ + m_PackedData.SetType(TYPE_HEAP); + m_Heap.list = list; + m_Committed.prev = NULL; + m_Committed.next = NULL; + m_Heap.heap = heap; +} + +void Allocation::SwapBlockAllocation(Allocation* allocation) +{ + D3D12MA_ASSERT(allocation != NULL); + D3D12MA_ASSERT(m_PackedData.GetType() == TYPE_PLACED); + D3D12MA_ASSERT(allocation->m_PackedData.GetType() == TYPE_PLACED); + + D3D12MA_SWAP(m_Resource, allocation->m_Resource); + m_PackedData.SetWasZeroInitialized(allocation->m_PackedData.WasZeroInitialized()); + m_Placed.block->m_pMetadata->SetAllocationPrivateData(m_Placed.allocHandle, allocation); + D3D12MA_SWAP(m_Placed, allocation->m_Placed); + m_Placed.block->m_pMetadata->SetAllocationPrivateData(m_Placed.allocHandle, this); +} + +AllocHandle Allocation::GetAllocHandle() const +{ + switch (m_PackedData.GetType()) + { + case TYPE_COMMITTED: + case TYPE_HEAP: + return (AllocHandle)0; + case TYPE_PLACED: + return m_Placed.allocHandle; + default: + D3D12MA_ASSERT(0); + return (AllocHandle)0; + } +} + +NormalBlock* Allocation::GetBlock() +{ + switch (m_PackedData.GetType()) + { + case TYPE_COMMITTED: + case TYPE_HEAP: + return NULL; + case TYPE_PLACED: + return m_Placed.block; + default: + D3D12MA_ASSERT(0); + return NULL; + } +} + +template +void Allocation::SetResourcePointer(ID3D12Resource* resource, const D3D12_RESOURCE_DESC_T* pResourceDesc) +{ + D3D12MA_ASSERT(m_Resource == NULL && pResourceDesc); + m_Resource = resource; + m_PackedData.SetResourceDimension(pResourceDesc->Dimension); + m_PackedData.SetResourceFlags(pResourceDesc->Flags); + m_PackedData.SetTextureLayout(pResourceDesc->Layout); +} + +void Allocation::FreeName() +{ + if (m_Name) + { + const size_t nameCharCount = wcslen(m_Name) + 1; + D3D12MA_DELETE_ARRAY(m_Allocator->GetAllocs(), m_Name, nameCharCount); + m_Name = NULL; + } +} +#endif // _D3D12MA_ALLOCATION_FUNCTIONS + +#ifndef _D3D12MA_DEFRAGMENTATION_CONTEXT_FUNCTIONS +HRESULT DefragmentationContext::BeginPass(DEFRAGMENTATION_PASS_MOVE_INFO* pPassInfo) +{ + D3D12MA_ASSERT(pPassInfo); + return m_Pimpl->DefragmentPassBegin(*pPassInfo); +} + +HRESULT DefragmentationContext::EndPass(DEFRAGMENTATION_PASS_MOVE_INFO* pPassInfo) +{ + D3D12MA_ASSERT(pPassInfo); + return m_Pimpl->DefragmentPassEnd(*pPassInfo); +} + +void DefragmentationContext::GetStats(DEFRAGMENTATION_STATS* pStats) +{ + D3D12MA_ASSERT(pStats); + m_Pimpl->GetStats(*pStats); +} + +void DefragmentationContext::ReleaseThis() +{ + if (this == NULL) + { + return; + } + + D3D12MA_DELETE(m_Pimpl->GetAllocs(), this); +} + +DefragmentationContext::DefragmentationContext(AllocatorPimpl* allocator, + const DEFRAGMENTATION_DESC& desc, + BlockVector* poolVector) + : m_Pimpl(D3D12MA_NEW(allocator->GetAllocs(), DefragmentationContextPimpl)(allocator, desc, poolVector)) {} + +DefragmentationContext::~DefragmentationContext() +{ + D3D12MA_DELETE(m_Pimpl->GetAllocs(), m_Pimpl); +} +#endif // _D3D12MA_DEFRAGMENTATION_CONTEXT_FUNCTIONS + +#ifndef _D3D12MA_POOL_FUNCTIONS +POOL_DESC Pool::GetDesc() const +{ + return m_Pimpl->GetDesc(); +} + +void Pool::GetStatistics(Statistics* pStats) +{ + D3D12MA_ASSERT(pStats); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->GetStatistics(*pStats); +} + +void Pool::CalculateStatistics(DetailedStatistics* pStats) +{ + D3D12MA_ASSERT(pStats); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->CalculateStatistics(*pStats); +} + +void Pool::SetName(LPCWSTR Name) +{ + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->SetName(Name); +} + +LPCWSTR Pool::GetName() const +{ + return m_Pimpl->GetName(); +} + +HRESULT Pool::BeginDefragmentation(const DEFRAGMENTATION_DESC* pDesc, DefragmentationContext** ppContext) +{ + D3D12MA_ASSERT(pDesc && ppContext); + + // Check for support + if (m_Pimpl->GetBlockVector()->GetAlgorithm() & POOL_FLAG_ALGORITHM_LINEAR) + return E_NOINTERFACE; + + AllocatorPimpl* allocator = m_Pimpl->GetAllocator(); + *ppContext = D3D12MA_NEW(allocator->GetAllocs(), DefragmentationContext)(allocator, *pDesc, m_Pimpl->GetBlockVector()); + return S_OK; +} + +void Pool::ReleaseThis() +{ + if (this == NULL) + { + return; + } + + D3D12MA_DELETE(m_Pimpl->GetAllocator()->GetAllocs(), this); +} + +Pool::Pool(Allocator* allocator, const POOL_DESC& desc) + : m_Pimpl(D3D12MA_NEW(allocator->m_Pimpl->GetAllocs(), PoolPimpl)(allocator->m_Pimpl, desc)) {} + +Pool::~Pool() +{ + m_Pimpl->GetAllocator()->UnregisterPool(this, m_Pimpl->GetDesc().HeapProperties.Type); + + D3D12MA_DELETE(m_Pimpl->GetAllocator()->GetAllocs(), m_Pimpl); +} +#endif // _D3D12MA_POOL_FUNCTIONS + +#ifndef _D3D12MA_ALLOCATOR_FUNCTIONS +const D3D12_FEATURE_DATA_D3D12_OPTIONS& Allocator::GetD3D12Options() const +{ + return m_Pimpl->GetD3D12Options(); +} + +BOOL Allocator::IsUMA() const +{ + return m_Pimpl->IsUMA(); +} + +BOOL Allocator::IsCacheCoherentUMA() const +{ + return m_Pimpl->IsCacheCoherentUMA(); +} + +UINT64 Allocator::GetMemoryCapacity(UINT memorySegmentGroup) const +{ + return m_Pimpl->GetMemoryCapacity(memorySegmentGroup); +} + +HRESULT Allocator::CreateResource( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + if (!pAllocDesc || !pResourceDesc || !ppAllocation) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreateResource."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->CreateResource(pAllocDesc, pResourceDesc, InitialResourceState, pOptimizedClearValue, ppAllocation, riidResource, ppvResource); +} + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ +HRESULT Allocator::CreateResource2( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + if (!pAllocDesc || !pResourceDesc || !ppAllocation) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreateResource2."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->CreateResource2(pAllocDesc, pResourceDesc, InitialResourceState, pOptimizedClearValue, ppAllocation, riidResource, ppvResource); +} +#endif // #ifdef __ID3D12Device8_INTERFACE_DEFINED__ + +HRESULT Allocator::AllocateMemory( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + Allocation** ppAllocation) +{ + if (!ValidateAllocateMemoryParameters(pAllocDesc, pAllocInfo, ppAllocation)) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::AllocateMemory."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->AllocateMemory(pAllocDesc, pAllocInfo, ppAllocation); +} + +HRESULT Allocator::CreateAliasingResource( + Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE* pOptimizedClearValue, + REFIID riidResource, + void** ppvResource) +{ + if (!pAllocation || !pResourceDesc || !ppvResource) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreateAliasingResource."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->CreateAliasingResource(pAllocation, AllocationLocalOffset, pResourceDesc, InitialResourceState, pOptimizedClearValue, riidResource, ppvResource); +} + +HRESULT Allocator::CreatePool( + const POOL_DESC* pPoolDesc, + Pool** ppPool) +{ + if (!pPoolDesc || !ppPool || + (pPoolDesc->MaxBlockCount > 0 && pPoolDesc->MaxBlockCount < pPoolDesc->MinBlockCount) || + (pPoolDesc->MinAllocationAlignment > 0 && !IsPow2(pPoolDesc->MinAllocationAlignment))) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreatePool."); + return E_INVALIDARG; + } + if (!m_Pimpl->HeapFlagsFulfillResourceHeapTier(pPoolDesc->HeapFlags)) + { + D3D12MA_ASSERT(0 && "Invalid pPoolDesc->HeapFlags passed to Allocator::CreatePool. Did you forget to handle ResourceHeapTier=1?"); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + * ppPool = D3D12MA_NEW(m_Pimpl->GetAllocs(), Pool)(this, *pPoolDesc); + HRESULT hr = (*ppPool)->m_Pimpl->Init(); + if (SUCCEEDED(hr)) + { + m_Pimpl->RegisterPool(*ppPool, pPoolDesc->HeapProperties.Type); + } + else + { + D3D12MA_DELETE(m_Pimpl->GetAllocs(), *ppPool); + *ppPool = NULL; + } + return hr; +} + +void Allocator::SetCurrentFrameIndex(UINT frameIndex) +{ + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->SetCurrentFrameIndex(frameIndex); +} + +void Allocator::GetBudget(Budget* pLocalBudget, Budget* pNonLocalBudget) +{ + if (pLocalBudget == NULL && pNonLocalBudget == NULL) + { + return; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->GetBudget(pLocalBudget, pNonLocalBudget); +} + +void Allocator::CalculateStatistics(TotalStatistics* pStats) +{ + D3D12MA_ASSERT(pStats); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->CalculateStatistics(*pStats); +} + +void Allocator::BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap) const +{ + D3D12MA_ASSERT(ppStatsString); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->BuildStatsString(ppStatsString, DetailedMap); +} + +void Allocator::FreeStatsString(WCHAR* pStatsString) const +{ + if (pStatsString != NULL) + { + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + m_Pimpl->FreeStatsString(pStatsString); + } +} + +void Allocator::BeginDefragmentation(const DEFRAGMENTATION_DESC* pDesc, DefragmentationContext** ppContext) +{ + D3D12MA_ASSERT(pDesc && ppContext); + + *ppContext = D3D12MA_NEW(m_Pimpl->GetAllocs(), DefragmentationContext)(m_Pimpl, *pDesc, NULL); +} + +void Allocator::ReleaseThis() +{ + // Copy is needed because otherwise we would call destructor and invalidate the structure with callbacks before using it to free memory. + const ALLOCATION_CALLBACKS allocationCallbacksCopy = m_Pimpl->GetAllocs(); + D3D12MA_DELETE(allocationCallbacksCopy, this); +} + +Allocator::Allocator(const ALLOCATION_CALLBACKS& allocationCallbacks, const ALLOCATOR_DESC& desc) + : m_Pimpl(D3D12MA_NEW(allocationCallbacks, AllocatorPimpl)(allocationCallbacks, desc)) {} + +Allocator::~Allocator() +{ + D3D12MA_DELETE(m_Pimpl->GetAllocs(), m_Pimpl); +} +#endif // _D3D12MA_ALLOCATOR_FUNCTIONS + +#ifndef _D3D12MA_VIRTUAL_BLOCK_FUNCTIONS +BOOL VirtualBlock::IsEmpty() const +{ + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + return m_Pimpl->m_Metadata->IsEmpty() ? TRUE : FALSE; +} + +void VirtualBlock::GetAllocationInfo(VirtualAllocation allocation, VIRTUAL_ALLOCATION_INFO* pInfo) const +{ + D3D12MA_ASSERT(allocation.AllocHandle != (AllocHandle)0 && pInfo); + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + m_Pimpl->m_Metadata->GetAllocationInfo(allocation.AllocHandle, *pInfo); +} + +HRESULT VirtualBlock::Allocate(const VIRTUAL_ALLOCATION_DESC* pDesc, VirtualAllocation* pAllocation, UINT64* pOffset) +{ + if (!pDesc || !pAllocation || pDesc->Size == 0 || !IsPow2(pDesc->Alignment)) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to VirtualBlock::Allocate."); + return E_INVALIDARG; + } + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + const UINT64 alignment = pDesc->Alignment != 0 ? pDesc->Alignment : 1; + AllocationRequest allocRequest = {}; + if (m_Pimpl->m_Metadata->CreateAllocationRequest( + pDesc->Size, + alignment, + pDesc->Flags & VIRTUAL_ALLOCATION_FLAG_UPPER_ADDRESS, + pDesc->Flags & VIRTUAL_ALLOCATION_FLAG_STRATEGY_MASK, + &allocRequest)) + { + m_Pimpl->m_Metadata->Alloc(allocRequest, pDesc->Size, pDesc->pPrivateData); + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); + pAllocation->AllocHandle = allocRequest.allocHandle; + + if (pOffset) + *pOffset = m_Pimpl->m_Metadata->GetAllocationOffset(allocRequest.allocHandle); + return S_OK; + } + + pAllocation->AllocHandle = (AllocHandle)0; + if (pOffset) + *pOffset = UINT64_MAX; + + return E_OUTOFMEMORY; +} + +void VirtualBlock::FreeAllocation(VirtualAllocation allocation) +{ + if (allocation.AllocHandle == (AllocHandle)0) + return; + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + m_Pimpl->m_Metadata->Free(allocation.AllocHandle); + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); +} + +void VirtualBlock::Clear() +{ + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + m_Pimpl->m_Metadata->Clear(); + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); +} + +void VirtualBlock::SetAllocationPrivateData(VirtualAllocation allocation, void* pPrivateData) +{ + D3D12MA_ASSERT(allocation.AllocHandle != (AllocHandle)0); + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + m_Pimpl->m_Metadata->SetAllocationPrivateData(allocation.AllocHandle, pPrivateData); +} + +void VirtualBlock::GetStatistics(Statistics* pStats) const +{ + D3D12MA_ASSERT(pStats); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); + ClearStatistics(*pStats); + m_Pimpl->m_Metadata->AddStatistics(*pStats); +} + +void VirtualBlock::CalculateStatistics(DetailedStatistics* pStats) const +{ + D3D12MA_ASSERT(pStats); + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); + ClearDetailedStatistics(*pStats); + m_Pimpl->m_Metadata->AddDetailedStatistics(*pStats); +} + +void VirtualBlock::BuildStatsString(WCHAR** ppStatsString) const +{ + D3D12MA_ASSERT(ppStatsString); + + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + + StringBuilder sb(m_Pimpl->m_AllocationCallbacks); + { + JsonWriter json(m_Pimpl->m_AllocationCallbacks, sb); + D3D12MA_HEAVY_ASSERT(m_Pimpl->m_Metadata->Validate()); + m_Pimpl->m_Metadata->WriteAllocationInfoToJson(json); + } // Scope for JsonWriter + + const size_t length = sb.GetLength(); + WCHAR* result = AllocateArray(m_Pimpl->m_AllocationCallbacks, length + 1); + memcpy(result, sb.GetData(), length * sizeof(WCHAR)); + result[length] = L'\0'; + *ppStatsString = result; +} + +void VirtualBlock::FreeStatsString(WCHAR* pStatsString) const +{ + if (pStatsString != NULL) + { + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + D3D12MA::Free(m_Pimpl->m_AllocationCallbacks, pStatsString); + } +} + +void VirtualBlock::ReleaseThis() +{ + // Copy is needed because otherwise we would call destructor and invalidate the structure with callbacks before using it to free memory. + const ALLOCATION_CALLBACKS allocationCallbacksCopy = m_Pimpl->m_AllocationCallbacks; + D3D12MA_DELETE(allocationCallbacksCopy, this); +} + +VirtualBlock::VirtualBlock(const ALLOCATION_CALLBACKS& allocationCallbacks, const VIRTUAL_BLOCK_DESC& desc) + : m_Pimpl(D3D12MA_NEW(allocationCallbacks, VirtualBlockPimpl)(allocationCallbacks, desc)) {} + +VirtualBlock::~VirtualBlock() +{ + // THIS IS AN IMPORTANT ASSERT! + // Hitting it means you have some memory leak - unreleased allocations in this virtual block. + D3D12MA_ASSERT(m_Pimpl->m_Metadata->IsEmpty() && "Some allocations were not freed before destruction of this virtual block!"); + + D3D12MA_DELETE(m_Pimpl->m_AllocationCallbacks, m_Pimpl); +} +#endif // _D3D12MA_VIRTUAL_BLOCK_FUNCTIONS +#endif // _D3D12MA_PUBLIC_INTERFACE +} // namespace D3D12MA diff --git a/Backends/RmlUi_DirectX/D3D12MemAlloc.h b/Backends/RmlUi_DirectX/D3D12MemAlloc.h new file mode 100644 index 000000000..43e6ca4b9 --- /dev/null +++ b/Backends/RmlUi_DirectX/D3D12MemAlloc.h @@ -0,0 +1,2533 @@ +// +// Copyright (c) 2019-2022 Advanced Micro Devices, Inc. All rights reserved. +// +// 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. +// + +#pragma once + +/** \mainpage D3D12 Memory Allocator + +Version 2.0.1 (2022-04-05) + +Copyright (c) 2019-2022 Advanced Micro Devices, Inc. All rights reserved. \n +License: MIT + +Documentation of all members: D3D12MemAlloc.h + +\section main_table_of_contents Table of contents + +- \subpage quick_start + - [Project setup](@ref quick_start_project_setup) + - [Creating resources](@ref quick_start_creating_resources) + - [Resource reference counting](@ref quick_start_resource_reference_counting) + - [Mapping memory](@ref quick_start_mapping_memory) +- \subpage custom_pools +- \subpage defragmentation +- \subpage statistics +- \subpage resource_aliasing +- \subpage linear_algorithm +- \subpage virtual_allocator +- \subpage configuration + - [Custom CPU memory allocator](@ref custom_memory_allocator) + - [Debug margins](@ref debug_margins) +- \subpage general_considerations + - [Thread safety](@ref general_considerations_thread_safety) + - [Versioning and compatibility](@ref general_considerations_versioning_and_compatibility) + - [Features not supported](@ref general_considerations_features_not_supported) + +\section main_see_also See also + +- [Product page on GPUOpen](https://gpuopen.com/gaming-product/d3d12-memory-allocator/) +- [Source repository on GitHub](https://github.com/GPUOpen-LibrariesAndSDKs/D3D12MemoryAllocator) +*/ + +// If using this library on a platform different than Windows PC or want to use different version of DXGI, +// you should include D3D12-compatible headers before this library on your own and define this macro. +#ifndef D3D12MA_D3D12_HEADERS_ALREADY_INCLUDED + #include + #include +#endif + +// Define this macro to 0 to disable usage of DXGI 1.4 (needed for IDXGIAdapter3 and query for memory budget). +#ifndef D3D12MA_DXGI_1_4 + #ifdef __IDXGIAdapter3_INTERFACE_DEFINED__ + #define D3D12MA_DXGI_1_4 1 + #else + #define D3D12MA_DXGI_1_4 0 + #endif +#endif + +/* +When defined to value other than 0, the library will try to use +D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT or D3D12_SMALL_MSAA_RESOURCE_PLACEMENT_ALIGNMENT +for created textures when possible, which can save memory because some small textures +may get their alignment 4K and their size a multiply of 4K instead of 64K. + +#define D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT 0 + Disables small texture alignment. +#define D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT 1 + Enables conservative algorithm that will use small alignment only for some textures + that are surely known to support it. +#define D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT 2 + Enables query for small alignment to D3D12 (based on Microsoft sample) which will + enable small alignment for more textures, but will also generate D3D Debug Layer + error #721 on call to ID3D12Device::GetResourceAllocationInfo, which you should just + ignore. +*/ +#ifndef D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT + #define D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT 1 +#endif + +/// \cond INTERNAL + +#define D3D12MA_CLASS_NO_COPY(className) \ + private: \ + className(const className&) = delete; \ + className(className&&) = delete; \ + className& operator=(const className&) = delete; \ + className& operator=(className&&) = delete; + +// To be used with MAKE_HRESULT to define custom error codes. +#define FACILITY_D3D12MA 3542 + +/* +If providing your own implementation, you need to implement a subset of std::atomic. +*/ +#if !defined(D3D12MA_ATOMIC_UINT32) || !defined(D3D12MA_ATOMIC_UINT64) + #include +#endif + +#ifndef D3D12MA_ATOMIC_UINT32 + #define D3D12MA_ATOMIC_UINT32 std::atomic +#endif + +#ifndef D3D12MA_ATOMIC_UINT64 + #define D3D12MA_ATOMIC_UINT64 std::atomic +#endif + +#ifdef D3D12MA_EXPORTS + #define D3D12MA_API __declspec(dllexport) +#elif defined(D3D12MA_IMPORTS) + #define D3D12MA_API __declspec(dllimport) +#else + #define D3D12MA_API +#endif + +// Forward declaration if ID3D12ProtectedResourceSession is not defined inside the headers (older SDK, pre ID3D12Device4) +struct ID3D12ProtectedResourceSession; + +namespace D3D12MA +{ +class D3D12MA_API IUnknownImpl : public IUnknown +{ +public: + virtual ~IUnknownImpl() = default; + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject); + virtual ULONG STDMETHODCALLTYPE AddRef(); + virtual ULONG STDMETHODCALLTYPE Release(); +protected: + virtual void ReleaseThis() { delete this; } +private: + D3D12MA_ATOMIC_UINT32 m_RefCount = 1; +}; +} // namespace D3D12MA + +/// \endcond + +namespace D3D12MA +{ + +/// \cond INTERNAL +class DefragmentationContextPimpl; +class AllocatorPimpl; +class PoolPimpl; +class NormalBlock; +class BlockVector; +class CommittedAllocationList; +class JsonWriter; +class VirtualBlockPimpl; +/// \endcond + +class Pool; +class Allocator; +struct Statistics; +struct DetailedStatistics; +struct TotalStatistics; + +/// \brief Unique identifier of single allocation done inside the memory heap. +typedef UINT64 AllocHandle; + +/// Pointer to custom callback function that allocates CPU memory. +using ALLOCATE_FUNC_PTR = void* (*)(size_t Size, size_t Alignment, void* pPrivateData); +/** +\brief Pointer to custom callback function that deallocates CPU memory. + +`pMemory = null` should be accepted and ignored. +*/ +using FREE_FUNC_PTR = void (*)(void* pMemory, void* pPrivateData); + +/// Custom callbacks to CPU memory allocation functions. +struct ALLOCATION_CALLBACKS +{ + /// %Allocation function. + ALLOCATE_FUNC_PTR pAllocate; + /// Dellocation function. + FREE_FUNC_PTR pFree; + /// Custom data that will be passed to allocation and deallocation functions as `pUserData` parameter. + void* pPrivateData; +}; + + +/// \brief Bit flags to be used with ALLOCATION_DESC::Flags. +enum ALLOCATION_FLAGS +{ + /// Zero + ALLOCATION_FLAG_NONE = 0, + + /** + Set this flag if the allocation should have its own dedicated memory allocation (committed resource with implicit heap). + + Use it for special, big resources, like fullscreen textures used as render targets. + + - When used with functions like D3D12MA::Allocator::CreateResource, it will use `ID3D12Device::CreateCommittedResource`, + so the created allocation will contain a resource (D3D12MA::Allocation::GetResource() `!= NULL`) but will not have + a heap (D3D12MA::Allocation::GetHeap() `== NULL`), as the heap is implicit. + - When used with raw memory allocation like D3D12MA::Allocator::AllocateMemory, it will use `ID3D12Device::CreateHeap`, + so the created allocation will contain a heap (D3D12MA::Allocation::GetHeap() `!= NULL`) and its offset will always be 0. + */ + ALLOCATION_FLAG_COMMITTED = 0x1, + + /** + Set this flag to only try to allocate from existing memory heaps and never create new such heap. + + If new allocation cannot be placed in any of the existing heaps, allocation + fails with `E_OUTOFMEMORY` error. + + You should not use D3D12MA::ALLOCATION_FLAG_COMMITTED and + D3D12MA::ALLOCATION_FLAG_NEVER_ALLOCATE at the same time. It makes no sense. + */ + ALLOCATION_FLAG_NEVER_ALLOCATE = 0x2, + + /** Create allocation only if additional memory required for it, if any, won't exceed + memory budget. Otherwise return `E_OUTOFMEMORY`. + */ + ALLOCATION_FLAG_WITHIN_BUDGET = 0x4, + + /** Allocation will be created from upper stack in a double stack pool. + + This flag is only allowed for custom pools created with #POOL_FLAG_ALGORITHM_LINEAR flag. + */ + ALLOCATION_FLAG_UPPER_ADDRESS = 0x8, + + /** Set this flag if the allocated memory will have aliasing resources. + + Use this when calling D3D12MA::Allocator::CreateResource() and similar to + guarantee creation of explicit heap for desired allocation and prevent it from using `CreateCommittedResource`, + so that new allocation object will always have `allocation->GetHeap() != NULL`. + */ + ALLOCATION_FLAG_CAN_ALIAS = 0x10, + + /** Allocation strategy that chooses smallest possible free range for the allocation + to minimize memory usage and fragmentation, possibly at the expense of allocation time. + */ + ALLOCATION_FLAG_STRATEGY_MIN_MEMORY = 0x00010000, + + /** Allocation strategy that chooses first suitable free range for the allocation - + not necessarily in terms of the smallest offset but the one that is easiest and fastest to find + to minimize allocation time, possibly at the expense of allocation quality. + */ + ALLOCATION_FLAG_STRATEGY_MIN_TIME = 0x00020000, + + /** Allocation strategy that chooses always the lowest offset in available space. + This is not the most efficient strategy but achieves highly packed data. + Used internally by defragmentation, not recomended in typical usage. + */ + ALLOCATION_FLAG_STRATEGY_MIN_OFFSET = 0x0004000, + + /// Alias to #ALLOCATION_FLAG_STRATEGY_MIN_MEMORY. + ALLOCATION_FLAG_STRATEGY_BEST_FIT = ALLOCATION_FLAG_STRATEGY_MIN_MEMORY, + /// Alias to #ALLOCATION_FLAG_STRATEGY_MIN_TIME. + ALLOCATION_FLAG_STRATEGY_FIRST_FIT = ALLOCATION_FLAG_STRATEGY_MIN_TIME, + + /// A bit mask to extract only `STRATEGY` bits from entire set of flags. + ALLOCATION_FLAG_STRATEGY_MASK = + ALLOCATION_FLAG_STRATEGY_MIN_MEMORY | + ALLOCATION_FLAG_STRATEGY_MIN_TIME | + ALLOCATION_FLAG_STRATEGY_MIN_OFFSET, +}; + +/// \brief Parameters of created D3D12MA::Allocation object. To be used with Allocator::CreateResource. +struct ALLOCATION_DESC +{ + /// Flags. + ALLOCATION_FLAGS Flags; + /** \brief The type of memory heap where the new allocation should be placed. + + It must be one of: `D3D12_HEAP_TYPE_DEFAULT`, `D3D12_HEAP_TYPE_UPLOAD`, `D3D12_HEAP_TYPE_READBACK`. + + When D3D12MA::ALLOCATION_DESC::CustomPool != NULL this member is ignored. + */ + D3D12_HEAP_TYPE HeapType; + /** \brief Additional heap flags to be used when allocating memory. + + In most cases it can be 0. + + - If you use D3D12MA::Allocator::CreateResource(), you don't need to care. + Necessary flag `D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS`, `D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES`, + or `D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES` is added automatically. + - If you use D3D12MA::Allocator::AllocateMemory(), you should specify one of those `ALLOW_ONLY` flags. + Except when you validate that D3D12MA::Allocator::GetD3D12Options()`.ResourceHeapTier == D3D12_RESOURCE_HEAP_TIER_1` - + then you can leave it 0. + - You can specify additional flags if needed. Then the memory will always be allocated as + separate block using `D3D12Device::CreateCommittedResource` or `CreateHeap`, not as part of an existing larget block. + + When D3D12MA::ALLOCATION_DESC::CustomPool != NULL this member is ignored. + */ + D3D12_HEAP_FLAGS ExtraHeapFlags; + /** \brief Custom pool to place the new resource in. Optional. + + When not NULL, the resource will be created inside specified custom pool. + It will then never be created as committed. + */ + Pool* CustomPool; + /// Custom general-purpose pointer that will be stored in D3D12MA::Allocation. + void* pPrivateData; +}; + +/** \brief Calculated statistics of memory usage e.g. in a specific memory heap type, +memory segment group, custom pool, or total. + +These are fast to calculate. +See functions: D3D12MA::Allocator::GetBudget(), D3D12MA::Pool::GetStatistics(). +*/ +struct Statistics +{ + /** \brief Number of D3D12 memory blocks allocated - `ID3D12Heap` objects and committed resources. + */ + UINT BlockCount; + /** \brief Number of D3D12MA::Allocation objects allocated. + + Committed allocations have their own blocks, so each one adds 1 to `AllocationCount` as well as `BlockCount`. + */ + UINT AllocationCount; + /** \brief Number of bytes allocated in memory blocks. + */ + UINT64 BlockBytes; + /** \brief Total number of bytes occupied by all D3D12MA::Allocation objects. + + Always less or equal than `BlockBytes`. + Difference `(BlockBytes - AllocationBytes)` is the amount of memory allocated from D3D12 + but unused by any D3D12MA::Allocation. + */ + UINT64 AllocationBytes; +}; + +/** \brief More detailed statistics than D3D12MA::Statistics. + +These are slower to calculate. Use for debugging purposes. +See functions: D3D12MA::Allocator::CalculateStatistics(), D3D12MA::Pool::CalculateStatistics(). + +Averages are not provided because they can be easily calculated as: + +\code +UINT64 AllocationSizeAvg = DetailedStats.Statistics.AllocationBytes / detailedStats.Statistics.AllocationCount; +UINT64 UnusedBytes = DetailedStats.Statistics.BlockBytes - DetailedStats.Statistics.AllocationBytes; +UINT64 UnusedRangeSizeAvg = UnusedBytes / DetailedStats.UnusedRangeCount; +\endcode +*/ +struct DetailedStatistics +{ + /// Basic statistics. + Statistics Stats; + /// Number of free ranges of memory between allocations. + UINT UnusedRangeCount; + /// Smallest allocation size. `UINT64_MAX` if there are 0 allocations. + UINT64 AllocationSizeMin; + /// Largest allocation size. 0 if there are 0 allocations. + UINT64 AllocationSizeMax; + /// Smallest empty range size. `UINT64_MAX` if there are 0 empty ranges. + UINT64 UnusedRangeSizeMin; + /// Largest empty range size. 0 if there are 0 empty ranges. + UINT64 UnusedRangeSizeMax; +}; + +/** \brief General statistics from current state of the allocator - +total memory usage across all memory heaps and segments. + +These are slower to calculate. Use for debugging purposes. +See function D3D12MA::Allocator::CalculateStatistics(). +*/ +struct TotalStatistics +{ + /** \brief One element for each type of heap located at the following indices: + + - 0 = `D3D12_HEAP_TYPE_DEFAULT` + - 1 = `D3D12_HEAP_TYPE_UPLOAD` + - 2 = `D3D12_HEAP_TYPE_READBACK` + - 3 = `D3D12_HEAP_TYPE_CUSTOM` + */ + DetailedStatistics HeapType[4]; + /** \brief One element for each memory segment group located at the following indices: + + - 0 = `DXGI_MEMORY_SEGMENT_GROUP_LOCAL` + - 1 = `DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL` + + Meaning of these segment groups is: + + - When `IsUMA() == FALSE` (discrete graphics card): + - `DXGI_MEMORY_SEGMENT_GROUP_LOCAL` (index 0) represents GPU memory + (resources allocated in `D3D12_HEAP_TYPE_DEFAULT` or `D3D12_MEMORY_POOL_L1`). + - `DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL` (index 1) represents system memory + (resources allocated in `D3D12_HEAP_TYPE_UPLOAD`, `D3D12_HEAP_TYPE_READBACK`, or `D3D12_MEMORY_POOL_L0`). + - When `IsUMA() == TRUE` (integrated graphics chip): + - `DXGI_MEMORY_SEGMENT_GROUP_LOCAL` = (index 0) represents memory shared for all the resources. + - `DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL` = (index 1) is unused and always 0. + */ + DetailedStatistics MemorySegmentGroup[2]; + /// Total statistics from all memory allocated from D3D12. + DetailedStatistics Total; +}; + +/** \brief %Statistics of current memory usage and available budget for a specific memory segment group. + +These are fast to calculate. See function D3D12MA::Allocator::GetBudget(). +*/ +struct Budget +{ + /** \brief %Statistics fetched from the library. + */ + Statistics Stats; + /** \brief Estimated current memory usage of the program. + + Fetched from system using `IDXGIAdapter3::QueryVideoMemoryInfo` if possible. + + It might be different than `BlockBytes` (usually higher) due to additional implicit objects + also occupying the memory, like swapchain, pipeline state objects, descriptor heaps, command lists, or + heaps and resources allocated outside of this library, if any. + */ + UINT64 UsageBytes; + /** \brief Estimated amount of memory available to the program. + + Fetched from system using `IDXGIAdapter3::QueryVideoMemoryInfo` if possible. + + It might be different (most probably smaller) than memory capacity returned + by D3D12MA::Allocator::GetMemoryCapacity() due to factors + external to the program, decided by the operating system. + Difference `BudgetBytes - UsageBytes` is the amount of additional memory that can probably + be allocated without problems. Exceeding the budget may result in various problems. + */ + UINT64 BudgetBytes; +}; + + +/// \brief Represents single memory allocation done inside VirtualBlock. +struct D3D12MA_API VirtualAllocation +{ + /// \brief Unique idenitfier of current allocation. 0 means null/invalid. + AllocHandle AllocHandle; +}; + +/** \brief Represents single memory allocation. + +It may be either implicit memory heap dedicated to a single resource or a +specific region of a bigger heap plus unique offset. + +To create such object, fill structure D3D12MA::ALLOCATION_DESC and call function +Allocator::CreateResource. + +The object remembers size and some other information. +To retrieve this information, use methods of this class. + +The object also remembers `ID3D12Resource` and "owns" a reference to it, +so it calls `%Release()` on the resource when destroyed. +*/ +class D3D12MA_API Allocation : public IUnknownImpl +{ +public: + /** \brief Returns offset in bytes from the start of memory heap. + + You usually don't need to use this offset. If you create a buffer or a texture together with the allocation using function + D3D12MA::Allocator::CreateResource, functions that operate on that resource refer to the beginning of the resource, + not entire memory heap. + + If the Allocation represents committed resource with implicit heap, returns 0. + */ + UINT64 GetOffset() const; + + /// Returns alignment that resource was created with. + UINT64 GetAlignment() const { return m_Alignment; } + + /** \brief Returns size in bytes of the allocation. + + - If you created a buffer or a texture together with the allocation using function D3D12MA::Allocator::CreateResource, + this is the size of the resource returned by `ID3D12Device::GetResourceAllocationInfo`. + - For allocations made out of bigger memory blocks, this also is the size of the memory region assigned exclusively to this allocation. + - For resources created as committed, this value may not be accurate. DirectX implementation may optimize memory usage internally + so that you may even observe regions of `ID3D12Resource::GetGPUVirtualAddress()` + Allocation::GetSize() to overlap in memory and still work correctly. + */ + UINT64 GetSize() const { return m_Size; } + + /** \brief Returns D3D12 resource associated with this object. + + Calling this method doesn't increment resource's reference counter. + */ + ID3D12Resource* GetResource() const { return m_Resource; } + + /// Releases the resource currently pointed by the allocation (if any), sets it to new one, incrementing its reference counter (if not null). + void SetResource(ID3D12Resource* pResource); + + /** \brief Returns memory heap that the resource is created in. + + If the Allocation represents committed resource with implicit heap, returns NULL. + */ + ID3D12Heap* GetHeap() const; + + /// Changes custom pointer for an allocation to a new value. + void SetPrivateData(void* pPrivateData) { m_pPrivateData = pPrivateData; } + + /// Get custom pointer associated with the allocation. + void* GetPrivateData() const { return m_pPrivateData; } + + /** \brief Associates a name with the allocation object. This name is for use in debug diagnostics and tools. + + Internal copy of the string is made, so the memory pointed by the argument can be + changed of freed immediately after this call. + + `Name` can be null. + */ + void SetName(LPCWSTR Name); + + /** \brief Returns the name associated with the allocation object. + + Returned string points to an internal copy. + + If no name was associated with the allocation, returns null. + */ + LPCWSTR GetName() const { return m_Name; } + + /** \brief Returns `TRUE` if the memory of the allocation was filled with zeros when the allocation was created. + + Returns `TRUE` only if the allocator is sure that the entire memory where the + allocation was created was filled with zeros at the moment the allocation was made. + + Returns `FALSE` if the memory could potentially contain garbage data. + If it's a render-target or depth-stencil texture, it then needs proper + initialization with `ClearRenderTargetView`, `ClearDepthStencilView`, `DiscardResource`, + or a copy operation, as described on page + "ID3D12Device::CreatePlacedResource method - Notes on the required resource initialization" in Microsoft documentation. + Please note that rendering a fullscreen triangle or quad to the texture as + a render target is not a proper way of initialization! + + See also articles: + + - "Coming to DirectX 12: More control over memory allocation" on DirectX Developer Blog + - ["Initializing DX12 Textures After Allocation and Aliasing"](https://asawicki.info/news_1724_initializing_dx12_textures_after_allocation_and_aliasing). + */ + BOOL WasZeroInitialized() const { return m_PackedData.WasZeroInitialized(); } + +protected: + void ReleaseThis() override; + +private: + friend class AllocatorPimpl; + friend class BlockVector; + friend class CommittedAllocationList; + friend class JsonWriter; + friend class BlockMetadata_Linear; + friend class DefragmentationContextPimpl; + friend struct CommittedAllocationListItemTraits; + template friend void D3D12MA_DELETE(const ALLOCATION_CALLBACKS&, T*); + template friend class PoolAllocator; + + enum Type + { + TYPE_COMMITTED, + TYPE_PLACED, + TYPE_HEAP, + TYPE_COUNT + }; + + AllocatorPimpl* m_Allocator; + UINT64 m_Size; + UINT64 m_Alignment; + ID3D12Resource* m_Resource; + void* m_pPrivateData; + wchar_t* m_Name; + + union + { + struct + { + CommittedAllocationList* list; + Allocation* prev; + Allocation* next; + } m_Committed; + + struct + { + AllocHandle allocHandle; + NormalBlock* block; + } m_Placed; + + struct + { + // Beginning must be compatible with m_Committed. + CommittedAllocationList* list; + Allocation* prev; + Allocation* next; + ID3D12Heap* heap; + } m_Heap; + }; + + struct PackedData + { + public: + PackedData() : + m_Type(0), m_ResourceDimension(0), m_ResourceFlags(0), m_TextureLayout(0), m_WasZeroInitialized(0) { } + + Type GetType() const { return (Type)m_Type; } + D3D12_RESOURCE_DIMENSION GetResourceDimension() const { return (D3D12_RESOURCE_DIMENSION)m_ResourceDimension; } + D3D12_RESOURCE_FLAGS GetResourceFlags() const { return (D3D12_RESOURCE_FLAGS)m_ResourceFlags; } + D3D12_TEXTURE_LAYOUT GetTextureLayout() const { return (D3D12_TEXTURE_LAYOUT)m_TextureLayout; } + BOOL WasZeroInitialized() const { return (BOOL)m_WasZeroInitialized; } + + void SetType(Type type); + void SetResourceDimension(D3D12_RESOURCE_DIMENSION resourceDimension); + void SetResourceFlags(D3D12_RESOURCE_FLAGS resourceFlags); + void SetTextureLayout(D3D12_TEXTURE_LAYOUT textureLayout); + void SetWasZeroInitialized(BOOL wasZeroInitialized) { m_WasZeroInitialized = wasZeroInitialized ? 1 : 0; } + + private: + UINT m_Type : 2; // enum Type + UINT m_ResourceDimension : 3; // enum D3D12_RESOURCE_DIMENSION + UINT m_ResourceFlags : 24; // flags D3D12_RESOURCE_FLAGS + UINT m_TextureLayout : 9; // enum D3D12_TEXTURE_LAYOUT + UINT m_WasZeroInitialized : 1; // BOOL + } m_PackedData; + + Allocation(AllocatorPimpl* allocator, UINT64 size, UINT64 alignment, BOOL wasZeroInitialized); + // Nothing here, everything already done in Release. + virtual ~Allocation() = default; + + void InitCommitted(CommittedAllocationList* list); + void InitPlaced(AllocHandle allocHandle, NormalBlock* block); + void InitHeap(CommittedAllocationList* list, ID3D12Heap* heap); + void SwapBlockAllocation(Allocation* allocation); + // If the Allocation represents committed resource with implicit heap, returns UINT64_MAX. + AllocHandle GetAllocHandle() const; + NormalBlock* GetBlock(); + template + void SetResourcePointer(ID3D12Resource* resource, const D3D12_RESOURCE_DESC_T* pResourceDesc); + void FreeName(); + + D3D12MA_CLASS_NO_COPY(Allocation) +}; + + +/// Flags to be passed as DEFRAGMENTATION_DESC::Flags. +enum DEFRAGMENTATION_FLAGS +{ + /** Use simple but fast algorithm for defragmentation. + May not achieve best results but will require least time to compute and least allocations to copy. + */ + DEFRAGMENTATION_FLAG_ALGORITHM_FAST = 0x1, + /** Default defragmentation algorithm, applied also when no `ALGORITHM` flag is specified. + Offers a balance between defragmentation quality and the amount of allocations and bytes that need to be moved. + */ + DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED = 0x2, + /** Perform full defragmentation of memory. + Can result in notably more time to compute and allocations to copy, but will achieve best memory packing. + */ + DEFRAGMENTATION_FLAG_ALGORITHM_FULL = 0x4, + + /// A bit mask to extract only `ALGORITHM` bits from entire set of flags. + DEFRAGMENTATION_FLAG_ALGORITHM_MASK = + DEFRAGMENTATION_FLAG_ALGORITHM_FAST | + DEFRAGMENTATION_FLAG_ALGORITHM_BALANCED | + DEFRAGMENTATION_FLAG_ALGORITHM_FULL +}; + +/** \brief Parameters for defragmentation. + +To be used with functions Allocator::BeginDefragmentation() and Pool::BeginDefragmentation(). +*/ +struct DEFRAGMENTATION_DESC +{ + /// Flags. + DEFRAGMENTATION_FLAGS Flags; + /** \brief Maximum numbers of bytes that can be copied during single pass, while moving allocations to different places. + + 0 means no limit. + */ + UINT64 MaxBytesPerPass; + /** \brief Maximum number of allocations that can be moved during single pass to a different place. + + 0 means no limit. + */ + UINT32 MaxAllocationsPerPass; +}; + +/// Operation performed on single defragmentation move. +enum DEFRAGMENTATION_MOVE_OPERATION +{ + /** Resource has been recreated at `pDstTmpAllocation`, data has been copied, old resource has been destroyed. + `pSrcAllocation` will be changed to point to the new place. This is the default value set by DefragmentationContext::BeginPass(). + */ + DEFRAGMENTATION_MOVE_OPERATION_COPY = 0, + /// Set this value if you cannot move the allocation. New place reserved at `pDstTmpAllocation` will be freed. `pSrcAllocation` will remain unchanged. + DEFRAGMENTATION_MOVE_OPERATION_IGNORE = 1, + /// Set this value if you decide to abandon the allocation and you destroyed the resource. New place reserved `pDstTmpAllocation` will be freed, along with `pSrcAllocation`. + DEFRAGMENTATION_MOVE_OPERATION_DESTROY = 2, +}; + +/// Single move of an allocation to be done for defragmentation. +struct DEFRAGMENTATION_MOVE +{ + /** \brief Operation to be performed on the allocation by DefragmentationContext::EndPass(). + Default value is #DEFRAGMENTATION_MOVE_OPERATION_COPY. You can modify it. + */ + DEFRAGMENTATION_MOVE_OPERATION Operation; + /// %Allocation that should be moved. + Allocation* pSrcAllocation; + /** \brief Temporary allocation pointing to destination memory that will replace `pSrcAllocation`. + + Use it to retrieve new `ID3D12Heap` and offset to create new `ID3D12Resource` and then store it here via Allocation::SetResource(). + + \warning Do not store this allocation in your data structures! It exists only temporarily, for the duration of the defragmentation pass, + to be used for storing newly created resource. DefragmentationContext::EndPass() will destroy it and make `pSrcAllocation` point to this memory. + */ + Allocation* pDstTmpAllocation; +}; + +/** \brief Parameters for incremental defragmentation steps. + +To be used with function DefragmentationContext::BeginPass(). +*/ +struct DEFRAGMENTATION_PASS_MOVE_INFO +{ + /// Number of elements in the `pMoves` array. + UINT32 MoveCount; + /** \brief Array of moves to be performed by the user in the current defragmentation pass. + + Pointer to an array of `MoveCount` elements, owned by %D3D12MA, created in DefragmentationContext::BeginPass(), destroyed in DefragmentationContext::EndPass(). + + For each element, you should: + + 1. Create a new resource in the place pointed by `pMoves[i].pDstTmpAllocation->GetHeap()` + `pMoves[i].pDstTmpAllocation->GetOffset()`. + 2. Store new resource in `pMoves[i].pDstTmpAllocation` by using Allocation::SetResource(). It will later replace old resource from `pMoves[i].pSrcAllocation`. + 3. Copy data from the `pMoves[i].pSrcAllocation` e.g. using `D3D12GraphicsCommandList::CopyResource`. + 4. Make sure these commands finished executing on the GPU. + + Only then you can finish defragmentation pass by calling DefragmentationContext::EndPass(). + After this call, the allocation will point to the new place in memory. + + Alternatively, if you cannot move specific allocation, + you can set DEFRAGMENTATION_MOVE::Operation to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_IGNORE. + + Alternatively, if you decide you want to completely remove the allocation, + set DEFRAGMENTATION_MOVE::Operation to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_DESTROY. + Then, after DefragmentationContext::EndPass() the allocation will be released. + */ + DEFRAGMENTATION_MOVE* pMoves; +}; + +/// %Statistics returned for defragmentation process by function DefragmentationContext::GetStats(). +struct DEFRAGMENTATION_STATS +{ + /// Total number of bytes that have been copied while moving allocations to different places. + UINT64 BytesMoved; + /// Total number of bytes that have been released to the system by freeing empty heaps. + UINT64 BytesFreed; + /// Number of allocations that have been moved to different places. + UINT32 AllocationsMoved; + /// Number of empty `ID3D12Heap` objects that have been released to the system. + UINT32 HeapsFreed; +}; + +/** \brief Represents defragmentation process in progress. + +You can create this object using Allocator::BeginDefragmentation (for default pools) or +Pool::BeginDefragmentation (for a custom pool). +*/ +class D3D12MA_API DefragmentationContext : public IUnknownImpl +{ +public: + /** \brief Starts single defragmentation pass. + + \param[out] pPassInfo Computed informations for current pass. + \returns + - `S_OK` if no more moves are possible. Then you can omit call to DefragmentationContext::EndPass() and simply end whole defragmentation. + - `S_FALSE` if there are pending moves returned in `pPassInfo`. You need to perform them, call DefragmentationContext::EndPass(), + and then preferably try another pass with DefragmentationContext::BeginPass(). + */ + HRESULT BeginPass(DEFRAGMENTATION_PASS_MOVE_INFO* pPassInfo); + /** \brief Ends single defragmentation pass. + + \param pPassInfo Computed informations for current pass filled by DefragmentationContext::BeginPass() and possibly modified by you. + \return Returns `S_OK` if no more moves are possible or `S_FALSE` if more defragmentations are possible. + + Ends incremental defragmentation pass and commits all defragmentation moves from `pPassInfo`. + After this call: + + - %Allocation at `pPassInfo[i].pSrcAllocation` that had `pPassInfo[i].Operation ==` #DEFRAGMENTATION_MOVE_OPERATION_COPY + (which is the default) will be pointing to the new destination place. + - %Allocation at `pPassInfo[i].pSrcAllocation` that had `pPassInfo[i].operation ==` #DEFRAGMENTATION_MOVE_OPERATION_DESTROY + will be released. + + If no more moves are possible you can end whole defragmentation. + */ + HRESULT EndPass(DEFRAGMENTATION_PASS_MOVE_INFO* pPassInfo); + /** \brief Returns statistics of the defragmentation performed so far. + */ + void GetStats(DEFRAGMENTATION_STATS* pStats); + +protected: + void ReleaseThis() override; + +private: + friend class Pool; + friend class Allocator; + template friend void D3D12MA_DELETE(const ALLOCATION_CALLBACKS&, T*); + + DefragmentationContextPimpl* m_Pimpl; + + DefragmentationContext(AllocatorPimpl* allocator, + const DEFRAGMENTATION_DESC& desc, + BlockVector* poolVector); + ~DefragmentationContext(); + + D3D12MA_CLASS_NO_COPY(DefragmentationContext) +}; + +/// \brief Bit flags to be used with POOL_DESC::Flags. +enum POOL_FLAGS +{ + /// Zero + POOL_FLAG_NONE = 0, + + /** \brief Enables alternative, linear allocation algorithm in this pool. + + Specify this flag to enable linear allocation algorithm, which always creates + new allocations after last one and doesn't reuse space from allocations freed in + between. It trades memory consumption for simplified algorithm and data + structure, which has better performance and uses less memory for metadata. + + By using this flag, you can achieve behavior of free-at-once, stack, + ring buffer, and double stack. + For details, see documentation chapter \ref linear_algorithm. + */ + POOL_FLAG_ALGORITHM_LINEAR = 0x1, + + /** \brief Optimization, allocate MSAA textures as committed resources always. + + Specify this flag to create MSAA textures with implicit heaps, as if they were created + with flag ALLOCATION_FLAG_COMMITTED. Usage of this flags enables pool to create its heaps + on smaller alignment not suitable for MSAA textures. + */ + POOL_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED = 0x2, + + // Bit mask to extract only `ALGORITHM` bits from entire set of flags. + POOL_FLAG_ALGORITHM_MASK = POOL_FLAG_ALGORITHM_LINEAR +}; + +/// \brief Parameters of created D3D12MA::Pool object. To be used with D3D12MA::Allocator::CreatePool. +struct POOL_DESC +{ + /// Flags. + POOL_FLAGS Flags; + /** \brief The parameters of memory heap where allocations of this pool should be placed. + + In the simplest case, just fill it with zeros and set `Type` to one of: `D3D12_HEAP_TYPE_DEFAULT`, + `D3D12_HEAP_TYPE_UPLOAD`, `D3D12_HEAP_TYPE_READBACK`. Additional parameters can be used e.g. to utilize UMA. + */ + D3D12_HEAP_PROPERTIES HeapProperties; + /** \brief Heap flags to be used when allocating heaps of this pool. + + It should contain one of these values, depending on type of resources you are going to create in this heap: + `D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS`, + `D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES`, + `D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES`. + Except if ResourceHeapTier = 2, then it may be `D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES` = 0. + + You can specify additional flags if needed. + */ + D3D12_HEAP_FLAGS HeapFlags; + /** \brief Size of a single heap (memory block) to be allocated as part of this pool, in bytes. Optional. + + Specify nonzero to set explicit, constant size of memory blocks used by this pool. + Leave 0 to use default and let the library manage block sizes automatically. + Then sizes of particular blocks may vary. + */ + UINT64 BlockSize; + /** \brief Minimum number of heaps (memory blocks) to be always allocated in this pool, even if they stay empty. Optional. + + Set to 0 to have no preallocated blocks and allow the pool be completely empty. + */ + UINT MinBlockCount; + /** \brief Maximum number of heaps (memory blocks) that can be allocated in this pool. Optional. + + Set to 0 to use default, which is `UINT64_MAX`, which means no limit. + + Set to same value as D3D12MA::POOL_DESC::MinBlockCount to have fixed amount of memory allocated + throughout whole lifetime of this pool. + */ + UINT MaxBlockCount; + /** \brief Additional minimum alignment to be used for all allocations created from this pool. Can be 0. + + Leave 0 (default) not to impose any additional alignment. If not 0, it must be a power of two. + */ + UINT64 MinAllocationAlignment; + /** \brief Additional parameter allowing pool to create resources with passed protected session. + + If not null then all the heaps and committed resources will be created with this parameter. + Valid only if ID3D12Device4 interface is present in current Windows SDK! + */ + ID3D12ProtectedResourceSession* pProtectedSession; +}; + +/** \brief Custom memory pool + +Represents a separate set of heaps (memory blocks) that can be used to create +D3D12MA::Allocation-s and resources in it. Usually there is no need to create custom +pools - creating resources in default pool is sufficient. + +To create custom pool, fill D3D12MA::POOL_DESC and call D3D12MA::Allocator::CreatePool. +*/ +class D3D12MA_API Pool : public IUnknownImpl +{ +public: + /** \brief Returns copy of parameters of the pool. + + These are the same parameters as passed to D3D12MA::Allocator::CreatePool. + */ + POOL_DESC GetDesc() const; + + /** \brief Retrieves basic statistics of the custom pool that are fast to calculate. + + \param[out] pStats %Statistics of the current pool. + */ + void GetStatistics(Statistics* pStats); + + /** \brief Retrieves detailed statistics of the custom pool that are slower to calculate. + + \param[out] pStats %Statistics of the current pool. + */ + void CalculateStatistics(DetailedStatistics* pStats); + + /** \brief Associates a name with the pool. This name is for use in debug diagnostics and tools. + + Internal copy of the string is made, so the memory pointed by the argument can be + changed of freed immediately after this call. + + `Name` can be NULL. + */ + void SetName(LPCWSTR Name); + + /** \brief Returns the name associated with the pool object. + + Returned string points to an internal copy. + + If no name was associated with the allocation, returns NULL. + */ + LPCWSTR GetName() const; + + /** \brief Begins defragmentation process of the current pool. + + \param pDesc Structure filled with parameters of defragmentation. + \param[out] ppContext Context object that will manage defragmentation. + \returns + - `S_OK` if defragmentation can begin. + - `E_NOINTERFACE` if defragmentation is not supported. + + For more information about defragmentation, see documentation chapter: + [Defragmentation](@ref defragmentation). + */ + HRESULT BeginDefragmentation(const DEFRAGMENTATION_DESC* pDesc, DefragmentationContext** ppContext); + +protected: + void ReleaseThis() override; + +private: + friend class Allocator; + friend class AllocatorPimpl; + template friend void D3D12MA_DELETE(const ALLOCATION_CALLBACKS&, T*); + + PoolPimpl* m_Pimpl; + + Pool(Allocator* allocator, const POOL_DESC &desc); + ~Pool(); + + D3D12MA_CLASS_NO_COPY(Pool) +}; + + +/// \brief Bit flags to be used with ALLOCATOR_DESC::Flags. +enum ALLOCATOR_FLAGS +{ + /// Zero + ALLOCATOR_FLAG_NONE = 0, + + /** + Allocator and all objects created from it will not be synchronized internally, + so you must guarantee they are used from only one thread at a time or + synchronized by you. + + Using this flag may increase performance because internal mutexes are not used. + */ + ALLOCATOR_FLAG_SINGLETHREADED = 0x1, + + /** + Every allocation will have its own memory block. + To be used for debugging purposes. + */ + ALLOCATOR_FLAG_ALWAYS_COMMITTED = 0x2, + + /** + Heaps created for the default pools will be created with flag `D3D12_HEAP_FLAG_CREATE_NOT_ZEROED`, + allowing for their memory to be not zeroed by the system if possible, + which can speed up allocation. + + Only affects default pools. + To use the flag with @ref custom_pools, you need to add it manually: + + \code + poolDesc.heapFlags |= D3D12_HEAP_FLAG_CREATE_NOT_ZEROED; + \endcode + + Only avaiable if `ID3D12Device8` is present. Otherwise, the flag is ignored. + */ + ALLOCATOR_FLAG_DEFAULT_POOLS_NOT_ZEROED = 0x4, + + /** \brief Optimization, allocate MSAA textures as committed resources always. + + Specify this flag to create MSAA textures with implicit heaps, as if they were created + with flag ALLOCATION_FLAG_COMMITTED. Usage of this flags enables all default pools + to create its heaps on smaller alignment not suitable for MSAA textures. + */ + ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED = 0x8, +}; + +/// \brief Parameters of created Allocator object. To be used with CreateAllocator(). +struct ALLOCATOR_DESC +{ + /// Flags. + ALLOCATOR_FLAGS Flags; + + /** Direct3D device object that the allocator should be attached to. + + Allocator is doing `AddRef`/`Release` on this object. + */ + ID3D12Device* pDevice; + + /** \brief Preferred size of a single `ID3D12Heap` block to be allocated. + + Set to 0 to use default, which is currently 64 MiB. + */ + UINT64 PreferredBlockSize; + + /** \brief Custom CPU memory allocation callbacks. Optional. + + Optional, can be null. When specified, will be used for all CPU-side memory allocations. + */ + const ALLOCATION_CALLBACKS* pAllocationCallbacks; + + /** DXGI Adapter object that you use for D3D12 and this allocator. + + Allocator is doing `AddRef`/`Release` on this object. + */ + IDXGIAdapter* pAdapter; +}; + +/** +\brief Represents main object of this library initialized for particular `ID3D12Device`. + +Fill structure D3D12MA::ALLOCATOR_DESC and call function CreateAllocator() to create it. +Call method `Release()` to destroy it. + +It is recommended to create just one object of this type per `ID3D12Device` object, +right after Direct3D 12 is initialized and keep it alive until before Direct3D device is destroyed. +*/ +class D3D12MA_API Allocator : public IUnknownImpl +{ +public: + /// Returns cached options retrieved from D3D12 device. + const D3D12_FEATURE_DATA_D3D12_OPTIONS& GetD3D12Options() const; + /** \brief Returns true if `D3D12_FEATURE_DATA_ARCHITECTURE1::UMA` was found to be true. + + For more information about how to use it, see articles in Microsoft Docs articles: + + - "UMA Optimizations: CPU Accessible Textures and Standard Swizzle" + - "D3D12_FEATURE_DATA_ARCHITECTURE structure (d3d12.h)" + - "ID3D12Device::GetCustomHeapProperties method (d3d12.h)" + */ + BOOL IsUMA() const; + /** \brief Returns true if `D3D12_FEATURE_DATA_ARCHITECTURE1::CacheCoherentUMA` was found to be true. + + For more information about how to use it, see articles in Microsoft Docs articles: + + - "UMA Optimizations: CPU Accessible Textures and Standard Swizzle" + - "D3D12_FEATURE_DATA_ARCHITECTURE structure (d3d12.h)" + - "ID3D12Device::GetCustomHeapProperties method (d3d12.h)" + */ + BOOL IsCacheCoherentUMA() const; + /** \brief Returns total amount of memory of specific segment group, in bytes. + + \param memorySegmentGroup use `DXGI_MEMORY_SEGMENT_GROUP_LOCAL` or DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL`. + + This information is taken from `DXGI_ADAPTER_DESC`. + It is not recommended to use this number. + You should preferably call GetBudget() and limit memory usage to D3D12MA::Budget::BudgetBytes instead. + + - When IsUMA() `== FALSE` (discrete graphics card): + - `GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_LOCAL)` returns the size of the video memory. + - `GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL)` returns the size of the system memory available for D3D12 resources. + - When IsUMA() `== TRUE` (integrated graphics chip): + - `GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_LOCAL)` returns the size of the shared memory available for all D3D12 resources. + All memory is considered "local". + - `GetMemoryCapacity(DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL)` is not applicable and returns 0. + */ + UINT64 GetMemoryCapacity(UINT memorySegmentGroup) const; + + /** \brief Allocates memory and creates a D3D12 resource (buffer or texture). This is the main allocation function. + + The function is similar to `ID3D12Device::CreateCommittedResource`, but it may + really call `ID3D12Device::CreatePlacedResource` to assign part of a larger, + existing memory heap to the new resource, which is the main purpose of this + whole library. + + If `ppvResource` is null, you receive only `ppAllocation` object from this function. + It holds pointer to `ID3D12Resource` that can be queried using function D3D12MA::Allocation::GetResource(). + Reference count of the resource object is 1. + It is automatically destroyed when you destroy the allocation object. + + If `ppvResource` is not null, you receive pointer to the resource next to allocation object. + Reference count of the resource object is then increased by calling `QueryInterface`, so you need to manually `Release` it + along with the allocation. + + \param pAllocDesc Parameters of the allocation. + \param pResourceDesc Description of created resource. + \param InitialResourceState Initial resource state. + \param pOptimizedClearValue Optional. Either null or optimized clear value. + \param[out] ppAllocation Filled with pointer to new allocation object created. + \param riidResource IID of a resource to be returned via `ppvResource`. + \param[out] ppvResource Optional. If not null, filled with pointer to new resouce created. + + \note This function creates a new resource. Sub-allocation of parts of one large buffer, + although recommended as a good practice, is out of scope of this library and could be implemented + by the user as a higher-level logic on top of it, e.g. using the \ref virtual_allocator feature. + */ + HRESULT CreateResource( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); + +#ifdef __ID3D12Device8_INTERFACE_DEFINED__ + /** \brief Similar to Allocator::CreateResource, but supports new structure `D3D12_RESOURCE_DESC1`. + + It internally uses `ID3D12Device8::CreateCommittedResource2` or `ID3D12Device8::CreatePlacedResource1`. + + To work correctly, `ID3D12Device8` interface must be available in the current system. Otherwise, `E_NOINTERFACE` is returned. + */ + HRESULT CreateResource2( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC1* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + + /** \brief Allocates memory without creating any resource placed in it. + + This function is similar to `ID3D12Device::CreateHeap`, but it may really assign + part of a larger, existing heap to the allocation. + + `pAllocDesc->heapFlags` should contain one of these values, depending on type of resources you are going to create in this memory: + `D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS`, + `D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES`, + `D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES`. + Except if you validate that ResourceHeapTier = 2 - then `heapFlags` + may be `D3D12_HEAP_FLAG_ALLOW_ALL_BUFFERS_AND_TEXTURES` = 0. + Additional flags in `heapFlags` are allowed as well. + + `pAllocInfo->SizeInBytes` must be multiply of 64KB. + `pAllocInfo->Alignment` must be one of the legal values as described in documentation of `D3D12_HEAP_DESC`. + + If you use D3D12MA::ALLOCATION_FLAG_COMMITTED you will get a separate memory block - + a heap that always has offset 0. + */ + HRESULT AllocateMemory( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + Allocation** ppAllocation); + + /** \brief Creates a new resource in place of an existing allocation. This is useful for memory aliasing. + + \param pAllocation Existing allocation indicating the memory where the new resource should be created. + It can be created using D3D12MA::Allocator::CreateResource and already have a resource bound to it, + or can be a raw memory allocated with D3D12MA::Allocator::AllocateMemory. + It must not be created as committed so that `ID3D12Heap` is available and not implicit. + \param AllocationLocalOffset Additional offset in bytes to be applied when allocating the resource. + Local from the start of `pAllocation`, not the beginning of the whole `ID3D12Heap`! + If the new resource should start from the beginning of the `pAllocation` it should be 0. + \param pResourceDesc Description of the new resource to be created. + \param InitialResourceState + \param pOptimizedClearValue + \param riidResource + \param[out] ppvResource Returns pointer to the new resource. + The resource is not bound with `pAllocation`. + This pointer must not be null - you must get the resource pointer and `Release` it when no longer needed. + + Memory requirements of the new resource are checked for validation. + If its size exceeds the end of `pAllocation` or required alignment is not fulfilled + considering `pAllocation->GetOffset() + AllocationLocalOffset`, the function + returns `E_INVALIDARG`. + */ + HRESULT CreateAliasingResource( + Allocation* pAllocation, + UINT64 AllocationLocalOffset, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + REFIID riidResource, + void** ppvResource); + + /** \brief Creates custom pool. + */ + HRESULT CreatePool( + const POOL_DESC* pPoolDesc, + Pool** ppPool); + + /** \brief Sets the index of the current frame. + + This function is used to set the frame index in the allocator when a new game frame begins. + */ + void SetCurrentFrameIndex(UINT frameIndex); + + /** \brief Retrieves information about current memory usage and budget. + + \param[out] pLocalBudget Optional, can be null. + \param[out] pNonLocalBudget Optional, can be null. + + - When IsUMA() `== FALSE` (discrete graphics card): + - `pLocalBudget` returns the budget of the video memory. + - `pNonLocalBudget` returns the budget of the system memory available for D3D12 resources. + - When IsUMA() `== TRUE` (integrated graphics chip): + - `pLocalBudget` returns the budget of the shared memory available for all D3D12 resources. + All memory is considered "local". + - `pNonLocalBudget` is not applicable and returns zeros. + + This function is called "get" not "calculate" because it is very fast, suitable to be called + every frame or every allocation. For more detailed statistics use CalculateStatistics(). + + Note that when using allocator from multiple threads, returned information may immediately + become outdated. + */ + void GetBudget(Budget* pLocalBudget, Budget* pNonLocalBudget); + + /** \brief Retrieves statistics from current state of the allocator. + + This function is called "calculate" not "get" because it has to traverse all + internal data structures, so it may be quite slow. Use it for debugging purposes. + For faster but more brief statistics suitable to be called every frame or every allocation, + use GetBudget(). + + Note that when using allocator from multiple threads, returned information may immediately + become outdated. + */ + void CalculateStatistics(TotalStatistics* pStats); + + /** \brief Builds and returns statistics as a string in JSON format. + * + @param[out] ppStatsString Must be freed using Allocator::FreeStatsString. + @param DetailedMap `TRUE` to include full list of allocations (can make the string quite long), `FALSE` to only return statistics. + */ + void BuildStatsString(WCHAR** ppStatsString, BOOL DetailedMap) const; + + /// Frees memory of a string returned from Allocator::BuildStatsString. + void FreeStatsString(WCHAR* pStatsString) const; + + /** \brief Begins defragmentation process of the default pools. + + \param pDesc Structure filled with parameters of defragmentation. + \param[out] ppContext Context object that will manage defragmentation. + + For more information about defragmentation, see documentation chapter: + [Defragmentation](@ref defragmentation). + */ + void BeginDefragmentation(const DEFRAGMENTATION_DESC* pDesc, DefragmentationContext** ppContext); + +protected: + void ReleaseThis() override; + +private: + friend D3D12MA_API HRESULT CreateAllocator(const ALLOCATOR_DESC*, Allocator**); + template friend void D3D12MA_DELETE(const ALLOCATION_CALLBACKS&, T*); + friend class DefragmentationContext; + friend class Pool; + + Allocator(const ALLOCATION_CALLBACKS& allocationCallbacks, const ALLOCATOR_DESC& desc); + ~Allocator(); + + AllocatorPimpl* m_Pimpl; + + D3D12MA_CLASS_NO_COPY(Allocator) +}; + + +/// \brief Bit flags to be used with VIRTUAL_BLOCK_DESC::Flags. +enum VIRTUAL_BLOCK_FLAGS +{ + /// Zero + VIRTUAL_BLOCK_FLAG_NONE = 0, + + /** \brief Enables alternative, linear allocation algorithm in this virtual block. + + Specify this flag to enable linear allocation algorithm, which always creates + new allocations after last one and doesn't reuse space from allocations freed in + between. It trades memory consumption for simplified algorithm and data + structure, which has better performance and uses less memory for metadata. + + By using this flag, you can achieve behavior of free-at-once, stack, + ring buffer, and double stack. + For details, see documentation chapter \ref linear_algorithm. + */ + VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR = POOL_FLAG_ALGORITHM_LINEAR, + + // Bit mask to extract only `ALGORITHM` bits from entire set of flags. + VIRTUAL_BLOCK_FLAG_ALGORITHM_MASK = POOL_FLAG_ALGORITHM_MASK +}; + +/// Parameters of created D3D12MA::VirtualBlock object to be passed to CreateVirtualBlock(). +struct VIRTUAL_BLOCK_DESC +{ + /// Flags. + VIRTUAL_BLOCK_FLAGS Flags; + /** \brief Total size of the block. + + Sizes can be expressed in bytes or any units you want as long as you are consistent in using them. + For example, if you allocate from some array of structures, 1 can mean single instance of entire structure. + */ + UINT64 Size; + /** \brief Custom CPU memory allocation callbacks. Optional. + + Optional, can be null. When specified, will be used for all CPU-side memory allocations. + */ + const ALLOCATION_CALLBACKS* pAllocationCallbacks; +}; + +/// \brief Bit flags to be used with VIRTUAL_ALLOCATION_DESC::Flags. +enum VIRTUAL_ALLOCATION_FLAGS +{ + /// Zero + VIRTUAL_ALLOCATION_FLAG_NONE = 0, + + /** \brief Allocation will be created from upper stack in a double stack pool. + + This flag is only allowed for virtual blocks created with #VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR flag. + */ + VIRTUAL_ALLOCATION_FLAG_UPPER_ADDRESS = ALLOCATION_FLAG_UPPER_ADDRESS, + + /// Allocation strategy that tries to minimize memory usage. + VIRTUAL_ALLOCATION_FLAG_STRATEGY_MIN_MEMORY = ALLOCATION_FLAG_STRATEGY_MIN_MEMORY, + /// Allocation strategy that tries to minimize allocation time. + VIRTUAL_ALLOCATION_FLAG_STRATEGY_MIN_TIME = ALLOCATION_FLAG_STRATEGY_MIN_TIME, + /** \brief Allocation strategy that chooses always the lowest offset in available space. + This is not the most efficient strategy but achieves highly packed data. + */ + VIRTUAL_ALLOCATION_FLAG_STRATEGY_MIN_OFFSET = ALLOCATION_FLAG_STRATEGY_MIN_OFFSET, + /** \brief A bit mask to extract only `STRATEGY` bits from entire set of flags. + + These strategy flags are binary compatible with equivalent flags in #ALLOCATION_FLAGS. + */ + VIRTUAL_ALLOCATION_FLAG_STRATEGY_MASK = ALLOCATION_FLAG_STRATEGY_MASK, +}; + +/// Parameters of created virtual allocation to be passed to VirtualBlock::Allocate(). +struct VIRTUAL_ALLOCATION_DESC +{ + /// Flags. + VIRTUAL_ALLOCATION_FLAGS Flags; + /** \brief Size of the allocation. + + Cannot be zero. + */ + UINT64 Size; + /** \brief Required alignment of the allocation. + + Must be power of two. Special value 0 has the same meaning as 1 - means no special alignment is required, so allocation can start at any offset. + */ + UINT64 Alignment; + /** \brief Custom pointer to be associated with the allocation. + + It can be fetched or changed later. + */ + void* pPrivateData; +}; + +/// Parameters of an existing virtual allocation, returned by VirtualBlock::GetAllocationInfo(). +struct VIRTUAL_ALLOCATION_INFO +{ + /// \brief Offset of the allocation. + UINT64 Offset; + /** \brief Size of the allocation. + + Same value as passed in VIRTUAL_ALLOCATION_DESC::Size. + */ + UINT64 Size; + /** \brief Custom pointer associated with the allocation. + + Same value as passed in VIRTUAL_ALLOCATION_DESC::pPrivateData or VirtualBlock::SetAllocationPrivateData(). + */ + void* pPrivateData; +}; + +/** \brief Represents pure allocation algorithm and a data structure with allocations in some memory block, without actually allocating any GPU memory. + +This class allows to use the core algorithm of the library custom allocations e.g. CPU memory or +sub-allocation regions inside a single GPU buffer. + +To create this object, fill in D3D12MA::VIRTUAL_BLOCK_DESC and call CreateVirtualBlock(). +To destroy it, call its method `VirtualBlock::Release()`. +You need to free all the allocations within this block or call Clear() before destroying it. + +This object is not thread-safe - should not be used from multiple threads simultaneously, must be synchronized externally. +*/ +class D3D12MA_API VirtualBlock : public IUnknownImpl +{ +public: + /** \brief Returns true if the block is empty - contains 0 allocations. + */ + BOOL IsEmpty() const; + /** \brief Returns information about an allocation - its offset, size and custom pointer. + */ + void GetAllocationInfo(VirtualAllocation allocation, VIRTUAL_ALLOCATION_INFO* pInfo) const; + + /** \brief Creates new allocation. + \param pDesc + \param[out] pAllocation Unique indentifier of the new allocation within single block. + \param[out] pOffset Returned offset of the new allocation. Optional, can be null. + \return `S_OK` if allocation succeeded, `E_OUTOFMEMORY` if it failed. + + If the allocation failed, `pAllocation->AllocHandle` is set to 0 and `pOffset`, if not null, is set to `UINT64_MAX`. + */ + HRESULT Allocate(const VIRTUAL_ALLOCATION_DESC* pDesc, VirtualAllocation* pAllocation, UINT64* pOffset); + /** \brief Frees the allocation. + + Calling this function with `allocation.AllocHandle == 0` is correct and does nothing. + */ + void FreeAllocation(VirtualAllocation allocation); + /** \brief Frees all the allocations. + */ + void Clear(); + /** \brief Changes custom pointer for an allocation to a new value. + */ + void SetAllocationPrivateData(VirtualAllocation allocation, void* pPrivateData); + /** \brief Retrieves basic statistics of the virtual block that are fast to calculate. + + \param[out] pStats %Statistics of the virtual block. + */ + void GetStatistics(Statistics* pStats) const; + /** \brief Retrieves detailed statistics of the virtual block that are slower to calculate. + + \param[out] pStats %Statistics of the virtual block. + */ + void CalculateStatistics(DetailedStatistics* pStats) const; + + /** \brief Builds and returns statistics as a string in JSON format, including the list of allocations with their parameters. + @param[out] ppStatsString Must be freed using VirtualBlock::FreeStatsString. + */ + void BuildStatsString(WCHAR** ppStatsString) const; + + /** \brief Frees memory of a string returned from VirtualBlock::BuildStatsString. + */ + void FreeStatsString(WCHAR* pStatsString) const; + +protected: + void ReleaseThis() override; + +private: + friend D3D12MA_API HRESULT CreateVirtualBlock(const VIRTUAL_BLOCK_DESC*, VirtualBlock**); + template friend void D3D12MA_DELETE(const ALLOCATION_CALLBACKS&, T*); + + VirtualBlockPimpl* m_Pimpl; + + VirtualBlock(const ALLOCATION_CALLBACKS& allocationCallbacks, const VIRTUAL_BLOCK_DESC& desc); + ~VirtualBlock(); + + D3D12MA_CLASS_NO_COPY(VirtualBlock) +}; + + +/** \brief Creates new main D3D12MA::Allocator object and returns it through `ppAllocator`. + +You normally only need to call it once and keep a single Allocator object for your `ID3D12Device`. +*/ +D3D12MA_API HRESULT CreateAllocator(const ALLOCATOR_DESC* pDesc, Allocator** ppAllocator); + +/** \brief Creates new D3D12MA::VirtualBlock object and returns it through `ppVirtualBlock`. + +Note you don't need to create D3D12MA::Allocator to use virtual blocks. +*/ +D3D12MA_API HRESULT CreateVirtualBlock(const VIRTUAL_BLOCK_DESC* pDesc, VirtualBlock** ppVirtualBlock); + +} // namespace D3D12MA + +/// \cond INTERNAL +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::ALLOCATION_FLAGS); +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::DEFRAGMENTATION_FLAGS); +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::ALLOCATOR_FLAGS); +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::POOL_FLAGS); +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::VIRTUAL_BLOCK_FLAGS); +DEFINE_ENUM_FLAG_OPERATORS(D3D12MA::VIRTUAL_ALLOCATION_FLAGS); +/// \endcond + +/** +\page quick_start Quick start + +\section quick_start_project_setup Project setup and initialization + +This is a small, standalone C++ library. It consists of a pair of 2 files: +"D3D12MemAlloc.h" header file with public interface and "D3D12MemAlloc.cpp" with +internal implementation. The only external dependencies are WinAPI, Direct3D 12, +and parts of C/C++ standard library (but STL containers, exceptions, or RTTI are +not used). + +The library is developed and tested using Microsoft Visual Studio 2019, but it +should work with other compilers as well. It is designed for 64-bit code. + +To use the library in your project: + +(1.) Copy files `D3D12MemAlloc.cpp`, `%D3D12MemAlloc.h` to your project. + +(2.) Make `D3D12MemAlloc.cpp` compiling as part of the project, as C++ code. + +(3.) Include library header in each CPP file that needs to use the library. + +\code +#include "D3D12MemAlloc.h" +\endcode + +(4.) Right after you created `ID3D12Device`, fill D3D12MA::ALLOCATOR_DESC +structure and call function D3D12MA::CreateAllocator to create the main +D3D12MA::Allocator object. + +Please note that all symbols of the library are declared inside #D3D12MA namespace. + +\code +IDXGIAdapter* adapter = (...) +ID3D12Device* device = (...) + +D3D12MA::ALLOCATOR_DESC allocatorDesc = {}; +allocatorDesc.pDevice = device; +allocatorDesc.pAdapter = adapter; + +D3D12MA::Allocator* allocator; +HRESULT hr = D3D12MA::CreateAllocator(&allocatorDesc, &allocator); +\endcode + +(5.) Right before destroying the D3D12 device, destroy the allocator object. + +Objects of this library must be destroyed by calling `Release` method. +They are somewhat compatible with COM: they implement `IUnknown` interface with its virtual methods: `AddRef`, `Release`, `QueryInterface`, +and they are reference-counted internally. +You can use smart pointers designed for COM with objects of this library - e.g. `CComPtr` or `Microsoft::WRL::ComPtr`. +The reference counter is thread-safe. +`QueryInterface` method supports only `IUnknown`, as classes of this library don't define their own GUIDs. + +\code +allocator->Release(); +\endcode + + +\section quick_start_creating_resources Creating resources + +To use the library for creating resources (textures and buffers), call method +D3D12MA::Allocator::CreateResource in the place where you would previously call +`ID3D12Device::CreateCommittedResource`. + +The function has similar syntax, but it expects structure D3D12MA::ALLOCATION_DESC +to be passed along with `D3D12_RESOURCE_DESC` and other parameters for created +resource. This structure describes parameters of the desired memory allocation, +including choice of `D3D12_HEAP_TYPE`. + +The function returns a new object of type D3D12MA::Allocation. +It represents allocated memory and can be queried for size, offset, `ID3D12Heap`. +It also holds a reference to the `ID3D12Resource`, which can be accessed by calling D3D12MA::Allocation::GetResource(). + +\code +D3D12_RESOURCE_DESC resourceDesc = {}; +resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; +resourceDesc.Alignment = 0; +resourceDesc.Width = 1024; +resourceDesc.Height = 1024; +resourceDesc.DepthOrArraySize = 1; +resourceDesc.MipLevels = 1; +resourceDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; +resourceDesc.SampleDesc.Count = 1; +resourceDesc.SampleDesc.Quality = 0; +resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; +resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + +D3D12MA::ALLOCATION_DESC allocationDesc = {}; +allocationDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; + +D3D12MA::Allocation* allocation; +HRESULT hr = allocator->CreateResource( + &allocationDesc, + &resourceDesc, + D3D12_RESOURCE_STATE_COPY_DEST, + NULL, + &allocation, + IID_NULL, NULL); + +// Use allocation->GetResource()... +\endcode + +You need to release the allocation object when no longer needed. +This will also release the D3D12 resource. + +\code +allocation->Release(); +\endcode + +The advantage of using the allocator instead of creating committed resource, and +the main purpose of this library, is that it can decide to allocate bigger memory +heap internally using `ID3D12Device::CreateHeap` and place multiple resources in +it, at different offsets, using `ID3D12Device::CreatePlacedResource`. The library +manages its own collection of allocated memory blocks (heaps) and remembers which +parts of them are occupied and which parts are free to be used for new resources. + +It is important to remember that resources created as placed don't have their memory +initialized to zeros, but may contain garbage data, so they need to be fully initialized +before usage, e.g. using Clear (`ClearRenderTargetView`), Discard (`DiscardResource`), +or copy (`CopyResource`). + +The library also automatically handles resource heap tier. +When `D3D12_FEATURE_DATA_D3D12_OPTIONS::ResourceHeapTier` equals `D3D12_RESOURCE_HEAP_TIER_1`, +resources of 3 types: buffers, textures that are render targets or depth-stencil, +and other textures must be kept in separate heaps. When `D3D12_RESOURCE_HEAP_TIER_2`, +they can be kept together. By using this library, you don't need to handle this +manually. + + +\section quick_start_resource_reference_counting Resource reference counting + +`ID3D12Resource` and other interfaces of Direct3D 12 use COM, so they are reference-counted. +Objects of this library are reference-counted as well. +An object of type D3D12MA::Allocation remembers the resource (buffer or texture) +that was created together with this memory allocation +and holds a reference to the `ID3D12Resource` object. +(Note this is a difference to Vulkan Memory Allocator, where a `VmaAllocation` object has no connection +with the buffer or image that was created with it.) +Thus, it is important to manage the resource reference counter properly. + +The simplest use case is shown in the code snippet above. +When only D3D12MA::Allocation object is obtained from a function call like D3D12MA::Allocator::CreateResource, +it remembers the `ID3D12Resource` that was created with it and holds a reference to it. +The resource can be obtained by calling `allocation->GetResource()`, which doesn't increment the resource +reference counter. +Calling `allocation->Release()` will decrease the resource reference counter, which is = 1 in this case, +so the resource will be released. + +Second option is to retrieve a pointer to the resource along with D3D12MA::Allocation. +Last parameters of the resource creation function can be used for this purpose. + +\code +D3D12MA::Allocation* allocation; +ID3D12Resource* resource; +HRESULT hr = allocator->CreateResource( + &allocationDesc, + &resourceDesc, + D3D12_RESOURCE_STATE_COPY_DEST, + NULL, + &allocation, + IID_PPV_ARGS(&resource)); + +// Use resource... +\endcode + +In this case, returned pointer `resource` is equal to `allocation->GetResource()`, +but the creation function additionally increases resource reference counter for the purpose of returning it from this call +(it actually calls `QueryInterface` internally), so the resource will have the counter = 2. +The resource then need to be released along with the allocation, in this particular order, +to make sure the resource is destroyed before its memory heap can potentially be freed. + +\code +resource->Release(); +allocation->Release(); +\endcode + +More advanced use cases are possible when we consider that an D3D12MA::Allocation object can just hold +a reference to any resource. +It can be changed by calling D3D12MA::Allocation::SetResource. This function +releases the old resource and calls `AddRef` on the new one. + +Special care must be taken when performing defragmentation. +The new resource created at the destination place should be set as `pass.pMoves[i].pDstTmpAllocation->SetResource(newRes)`, +but it is moved to the source allocation at end of the defragmentation pass, +while the old resource accessible through `pass.pMoves[i].pSrcAllocation->GetResource()` is then released. +For more information, see documentation chapter \ref defragmentation. + + +\section quick_start_mapping_memory Mapping memory + +The process of getting regular CPU-side pointer to the memory of a resource in +Direct3D is called "mapping". There are rules and restrictions to this process, +as described in D3D12 documentation of `ID3D12Resource::Map` method. + +Mapping happens on the level of particular resources, not entire memory heaps, +and so it is out of scope of this library. Just as the documentation of the `Map` function says: + +- Returned pointer refers to data of particular subresource, not entire memory heap. +- You can map same resource multiple times. It is ref-counted internally. +- Mapping is thread-safe. +- Unmapping is not required before resource destruction. +- Unmapping may not be required before using written data - some heap types on + some platforms support resources persistently mapped. + +When using this library, you can map and use your resources normally without +considering whether they are created as committed resources or placed resources in one large heap. + +Example for buffer created and filled in `UPLOAD` heap type: + +\code +const UINT64 bufSize = 65536; +const float* bufData = (...); + +D3D12_RESOURCE_DESC resourceDesc = {}; +resourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; +resourceDesc.Alignment = 0; +resourceDesc.Width = bufSize; +resourceDesc.Height = 1; +resourceDesc.DepthOrArraySize = 1; +resourceDesc.MipLevels = 1; +resourceDesc.Format = DXGI_FORMAT_UNKNOWN; +resourceDesc.SampleDesc.Count = 1; +resourceDesc.SampleDesc.Quality = 0; +resourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; +resourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE; + +D3D12MA::ALLOCATION_DESC allocationDesc = {}; +allocationDesc.HeapType = D3D12_HEAP_TYPE_UPLOAD; + +D3D12Resource* resource; +D3D12MA::Allocation* allocation; +HRESULT hr = allocator->CreateResource( + &allocationDesc, + &resourceDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, + NULL, + &allocation, + IID_PPV_ARGS(&resource)); + +void* mappedPtr; +hr = resource->Map(0, NULL, &mappedPtr); + +memcpy(mappedPtr, bufData, bufSize); + +resource->Unmap(0, NULL); +\endcode + + +\page custom_pools Custom memory pools + +A "pool" is a collection of memory blocks that share certain properties. +Allocator creates 3 default pools: for `D3D12_HEAP_TYPE_DEFAULT`, `UPLOAD`, `READBACK`. +A default pool automatically grows in size. Size of allocated blocks is also variable and managed automatically. +Typical allocations are created in these pools. You can also create custom pools. + +\section custom_pools_usage Usage + +To create a custom pool, fill in structure D3D12MA::POOL_DESC and call function D3D12MA::Allocator::CreatePool +to obtain object D3D12MA::Pool. Example: + +\code +POOL_DESC poolDesc = {}; +poolDesc.HeapProperties.Type = D3D12_HEAP_TYPE_DEFAULT; + +Pool* pool; +HRESULT hr = allocator->CreatePool(&poolDesc, &pool); +\endcode + +To allocate resources out of a custom pool, only set member D3D12MA::ALLOCATION_DESC::CustomPool. +Example: + +\code +ALLOCATION_DESC allocDesc = {}; +allocDesc.CustomPool = pool; + +D3D12_RESOURCE_DESC resDesc = ... +Allocation* alloc; +hr = allocator->CreateResource(&allocDesc, &resDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &alloc, IID_NULL, NULL); +\endcode + +All allocations must be released before releasing the pool. +The pool must be released before relasing the allocator. + +\code +alloc->Release(); +pool->Release(); +\endcode + +\section custom_pools_features_and_benefits Features and benefits + +While it is recommended to use default pools whenever possible for simplicity and to give the allocator +more opportunities for internal optimizations, custom pools may be useful in following cases: + +- To keep some resources separate from others in memory. +- To keep track of memory usage of just a specific group of resources. %Statistics can be queried using + D3D12MA::Pool::CalculateStatistics. +- To use specific size of a memory block (`ID3D12Heap`). To set it, use member D3D12MA::POOL_DESC::BlockSize. + When set to 0, the library uses automatically determined, variable block sizes. +- To reserve some minimum amount of memory allocated. To use it, set member D3D12MA::POOL_DESC::MinBlockCount. +- To limit maximum amount of memory allocated. To use it, set member D3D12MA::POOL_DESC::MaxBlockCount. +- To use extended parameters of the D3D12 memory allocation. While resources created from default pools + can only specify `D3D12_HEAP_TYPE_DEFAULT`, `UPLOAD`, `READBACK`, a custom pool may use non-standard + `D3D12_HEAP_PROPERTIES` (member D3D12MA::POOL_DESC::HeapProperties) and `D3D12_HEAP_FLAGS` + (D3D12MA::POOL_DESC::HeapFlags), which is useful e.g. for cross-adapter sharing or UMA + (see also D3D12MA::Allocator::IsUMA). + +New versions of this library support creating **committed allocations in custom pools**. +It is supported only when D3D12MA::POOL_DESC::BlockSize = 0. +To use this feature, set D3D12MA::ALLOCATION_DESC::CustomPool to the pointer to your custom pool and +D3D12MA::ALLOCATION_DESC::Flags to D3D12MA::ALLOCATION_FLAG_COMMITTED. Example: + +\code +ALLOCATION_DESC allocDesc = {}; +allocDesc.CustomPool = pool; +allocDesc.Flags = ALLOCATION_FLAG_COMMITTED; + +D3D12_RESOURCE_DESC resDesc = ... +Allocation* alloc; +ID3D12Resource* res; +hr = allocator->CreateResource(&allocDesc, &resDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &alloc, IID_PPV_ARGS(&res)); +\endcode + +This feature may seem unnecessary, but creating committed allocations from custom pools may be useful +in some cases, e.g. to have separate memory usage statistics for some group of resources or to use +extended allocation parameters, like custom `D3D12_HEAP_PROPERTIES`, which are available only in custom pools. + + +\page defragmentation Defragmentation + +Interleaved allocations and deallocations of many objects of varying size can +cause fragmentation over time, which can lead to a situation where the library is unable +to find a continuous range of free memory for a new allocation despite there is +enough free space, just scattered across many small free ranges between existing +allocations. + +To mitigate this problem, you can use defragmentation feature. +It doesn't happen automatically though and needs your cooperation, +because %D3D12MA is a low level library that only allocates memory. +It cannot recreate buffers and textures in a new place as it doesn't remember the contents of `D3D12_RESOURCE_DESC` structure. +It cannot copy their contents as it doesn't record any commands to a command list. + +Example: + +\code +D3D12MA::DEFRAGMENTATION_DESC defragDesc = {}; +defragDesc.Flags = D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_FAST; + +D3D12MA::DefragmentationContext* defragCtx; +allocator->BeginDefragmentation(&defragDesc, &defragCtx); + +for(;;) +{ + D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO pass; + HRESULT hr = defragCtx->BeginPass(&pass); + if(hr == S_OK) + break; + else if(hr != S_FALSE) + // Handle error... + + for(UINT i = 0; i < pass.MoveCount; ++i) + { + // Inspect pass.pMoves[i].pSrcAllocation, identify what buffer/texture it represents. + MyEngineResourceData* resData = (MyEngineResourceData*)pMoves[i].pSrcAllocation->GetPrivateData(); + + // Recreate this buffer/texture as placed at pass.pMoves[i].pDstTmpAllocation. + D3D12_RESOURCE_DESC resDesc = ... + ID3D12Resource* newRes; + hr = device->CreatePlacedResource( + pass.pMoves[i].pDstTmpAllocation->GetHeap(), + pass.pMoves[i].pDstTmpAllocation->GetOffset(), &resDesc, + D3D12_RESOURCE_STATE_COPY_DEST, NULL, IID_PPV_ARGS(&newRes)); + // Check hr... + + // Store new resource in the pDstTmpAllocation. + pass.pMoves[i].pDstTmpAllocation->SetResource(newRes); + + // Copy its content to the new place. + cmdList->CopyResource( + pass.pMoves[i].pDstTmpAllocation->GetResource(), + pass.pMoves[i].pSrcAllocation->GetResource()); + } + + // Make sure the copy commands finished executing. + cmdQueue->ExecuteCommandLists(...); + // ... + WaitForSingleObject(fenceEvent, INFINITE); + + // Update appropriate descriptors to point to the new places... + + hr = defragCtx->EndPass(&pass); + if(hr == S_OK) + break; + else if(hr != S_FALSE) + // Handle error... +} + +defragCtx->Release(); +\endcode + +Although functions like D3D12MA::Allocator::CreateResource() +create an allocation and a buffer/texture at once, these are just a shortcut for +allocating memory and creating a placed resource. +Defragmentation works on memory allocations only. You must handle the rest manually. +Defragmentation is an iterative process that should repreat "passes" as long as related functions +return `S_FALSE` not `S_OK`. +In each pass: + +1. D3D12MA::DefragmentationContext::BeginPass() function call: + - Calculates and returns the list of allocations to be moved in this pass. + Note this can be a time-consuming process. + - Reserves destination memory for them by creating temporary destination allocations + that you can query for their `ID3D12Heap` + offset using methods like D3D12MA::Allocation::GetHeap(). +2. Inside the pass, **you should**: + - Inspect the returned list of allocations to be moved. + - Create new buffers/textures as placed at the returned destination temporary allocations. + - Copy data from source to destination resources if necessary. + - Store the pointer to the new resource in the temporary destination allocation. +3. D3D12MA::DefragmentationContext::EndPass() function call: + - Frees the source memory reserved for the allocations that are moved. + - Modifies source D3D12MA::Allocation objects that are moved to point to the destination reserved memory + and destination resource, while source resource is released. + - Frees `ID3D12Heap` blocks that became empty. + +Defragmentation algorithm tries to move all suitable allocations. +You can, however, refuse to move some of them inside a defragmentation pass, by setting +`pass.pMoves[i].Operation` to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_IGNORE. +This is not recommended and may result in suboptimal packing of the allocations after defragmentation. +If you cannot ensure any allocation can be moved, it is better to keep movable allocations separate in a custom pool. + +Inside a pass, for each allocation that should be moved: + +- You should copy its data from the source to the destination place by calling e.g. `CopyResource()`. + - You need to make sure these commands finished executing before the source buffers/textures are released by D3D12MA::DefragmentationContext::EndPass(). +- If a resource doesn't contain any meaningful data, e.g. it is a transient render-target texture to be cleared, + filled, and used temporarily in each rendering frame, you can just recreate this texture + without copying its data. +- If the resource is in `D3D12_HEAP_TYPE_READBACK` memory, you can copy its data on the CPU + using `memcpy()`. +- If you cannot move the allocation, you can set `pass.pMoves[i].Operation` to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_IGNORE. + This will cancel the move. + - D3D12MA::DefragmentationContext::EndPass() will then free the destination memory + not the source memory of the allocation, leaving it unchanged. +- If you decide the allocation is unimportant and can be destroyed instead of moved (e.g. it wasn't used for long time), + you can set `pass.pMoves[i].Operation` to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_DESTROY. + - D3D12MA::DefragmentationContext::EndPass() will then free both source and destination memory, and will destroy the source D3D12MA::Allocation object. + +You can defragment a specific custom pool by calling D3D12MA::Pool::BeginDefragmentation +or all the default pools by calling D3D12MA::Allocator::BeginDefragmentation (like in the example above). + +Defragmentation is always performed in each pool separately. +Allocations are never moved between different heap types. +The size of the destination memory reserved for a moved allocation is the same as the original one. +Alignment of an allocation as it was determined using `GetResourceAllocationInfo()` is also respected after defragmentation. +Buffers/textures should be recreated with the same `D3D12_RESOURCE_DESC` parameters as the original ones. + +You can perform the defragmentation incrementally to limit the number of allocations and bytes to be moved +in each pass, e.g. to call it in sync with render frames and not to experience too big hitches. +See members: D3D12MA::DEFRAGMENTATION_DESC::MaxBytesPerPass, D3D12MA::DEFRAGMENTATION_DESC::MaxAllocationsPerPass. + +It is also safe to perform the defragmentation asynchronously to render frames and other Direct3D 12 and %D3D12MA +usage, possibly from multiple threads, with the exception that allocations +returned in D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO::pMoves shouldn't be released until the defragmentation pass is ended. + +Mapping is out of scope of this library and so it is not preserved after an allocation is moved during defragmentation. +You need to map the new resource yourself if needed. + +\note Defragmentation is not supported in custom pools created with D3D12MA::POOL_FLAG_ALGORITHM_LINEAR. + + +\page statistics Statistics + +This library contains several functions that return information about its internal state, +especially the amount of memory allocated from D3D12. + +\section statistics_numeric_statistics Numeric statistics + +If you need to obtain basic statistics about memory usage per memory segment group, together with current budget, +you can call function D3D12MA::Allocator::GetBudget() and inspect structure D3D12MA::Budget. +This is useful to keep track of memory usage and stay withing budget. +Example: + +\code +D3D12MA::Budget localBudget; +allocator->GetBudget(&localBudget, NULL); + +printf("My GPU memory currently has %u allocations taking %llu B,\n", + localBudget.Statistics.AllocationCount, + localBudget.Statistics.AllocationBytes); +printf("allocated out of %u D3D12 memory heaps taking %llu B,\n", + localBudget.Statistics.BlockCount, + localBudget.Statistics.BlockBytes); +printf("D3D12 reports total usage %llu B with budget %llu B.\n", + localBudget.UsageBytes, + localBudget.BudgetBytes); +\endcode + +You can query for more detailed statistics per heap type, memory segment group, and totals, +including minimum and maximum allocation size and unused range size, +by calling function D3D12MA::Allocator::CalculateStatistics() and inspecting structure D3D12MA::TotalStatistics. +This function is slower though, as it has to traverse all the internal data structures, +so it should be used only for debugging purposes. + +You can query for statistics of a custom pool using function D3D12MA::Pool::GetStatistics() +or D3D12MA::Pool::CalculateStatistics(). + +You can query for information about a specific allocation using functions of the D3D12MA::Allocation class, +e.g. `GetSize()`, `GetOffset()`, `GetHeap()`. + +\section statistics_json_dump JSON dump + +You can dump internal state of the allocator to a string in JSON format using function D3D12MA::Allocator::BuildStatsString(). +The result is guaranteed to be correct JSON. +It uses Windows Unicode (UTF-16) encoding. +Any strings provided by user (see D3D12MA::Allocation::SetName()) +are copied as-is and properly escaped for JSON. +It must be freed using function D3D12MA::Allocator::FreeStatsString(). + +The format of this JSON string is not part of official documentation of the library, +but it will not change in backward-incompatible way without increasing library major version number +and appropriate mention in changelog. + +The JSON string contains all the data that can be obtained using D3D12MA::Allocator::CalculateStatistics(). +It can also contain detailed map of allocated memory blocks and their regions - +free and occupied by allocations. +This allows e.g. to visualize the memory or assess fragmentation. + + +\page resource_aliasing Resource aliasing (overlap) + +New explicit graphics APIs (Vulkan and Direct3D 12), thanks to manual memory +management, give an opportunity to alias (overlap) multiple resources in the +same region of memory - a feature not available in the old APIs (Direct3D 11, OpenGL). +It can be useful to save video memory, but it must be used with caution. + +For example, if you know the flow of your whole render frame in advance, you +are going to use some intermediate textures or buffers only during a small range of render passes, +and you know these ranges don't overlap in time, you can create these resources in +the same place in memory, even if they have completely different parameters (width, height, format etc.). + +![Resource aliasing (overlap)](../gfx/Aliasing.png) + +Such scenario is possible using D3D12MA, but you need to create your resources +using special function D3D12MA::Allocator::CreateAliasingResource. +Before that, you need to allocate memory with parameters calculated using formula: + +- allocation size = max(size of each resource) +- allocation alignment = max(alignment of each resource) + +Following example shows two different textures created in the same place in memory, +allocated to fit largest of them. + +\code +D3D12_RESOURCE_DESC resDesc1 = {}; +resDesc1.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; +resDesc1.Alignment = 0; +resDesc1.Width = 1920; +resDesc1.Height = 1080; +resDesc1.DepthOrArraySize = 1; +resDesc1.MipLevels = 1; +resDesc1.Format = DXGI_FORMAT_R8G8B8A8_UNORM; +resDesc1.SampleDesc.Count = 1; +resDesc1.SampleDesc.Quality = 0; +resDesc1.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; +resDesc1.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + +D3D12_RESOURCE_DESC resDesc2 = {}; +resDesc2.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; +resDesc2.Alignment = 0; +resDesc2.Width = 1024; +resDesc2.Height = 1024; +resDesc2.DepthOrArraySize = 1; +resDesc2.MipLevels = 0; +resDesc2.Format = DXGI_FORMAT_R8G8B8A8_UNORM; +resDesc2.SampleDesc.Count = 1; +resDesc2.SampleDesc.Quality = 0; +resDesc2.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; +resDesc2.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + +const D3D12_RESOURCE_ALLOCATION_INFO allocInfo1 = + device->GetResourceAllocationInfo(0, 1, &resDesc1); +const D3D12_RESOURCE_ALLOCATION_INFO allocInfo2 = + device->GetResourceAllocationInfo(0, 1, &resDesc2); + +D3D12_RESOURCE_ALLOCATION_INFO finalAllocInfo = {}; +finalAllocInfo.Alignment = std::max(allocInfo1.Alignment, allocInfo2.Alignment); +finalAllocInfo.SizeInBytes = std::max(allocInfo1.SizeInBytes, allocInfo2.SizeInBytes); + +D3D12MA::ALLOCATION_DESC allocDesc = {}; +allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; +allocDesc.ExtraHeapFlags = D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES; + +D3D12MA::Allocation* alloc; +hr = allocator->AllocateMemory(&allocDesc, &finalAllocInfo, &alloc); +assert(alloc != NULL && alloc->GetHeap() != NULL); + +ID3D12Resource* res1; +hr = allocator->CreateAliasingResource( + alloc, + 0, // AllocationLocalOffset + &resDesc1, + D3D12_RESOURCE_STATE_COMMON, + NULL, // pOptimizedClearValue + IID_PPV_ARGS(&res1)); + +ID3D12Resource* res2; +hr = allocator->CreateAliasingResource( + alloc, + 0, // AllocationLocalOffset + &resDesc2, + D3D12_RESOURCE_STATE_COMMON, + NULL, // pOptimizedClearValue + IID_PPV_ARGS(&res2)); + +// You can use res1 and res2, but not at the same time! + +res2->Release(); +res1->Release(); +alloc->Release(); +\endcode + +Remember that using resouces that alias in memory requires proper synchronization. +You need to issue a special barrier of type `D3D12_RESOURCE_BARRIER_TYPE_ALIASING`. +You also need to treat a resource after aliasing as uninitialized - containing garbage data. +For example, if you use `res1` and then want to use `res2`, you need to first initialize `res2` +using either Clear, Discard, or Copy to the entire resource. + +Additional considerations: + +- D3D12 also allows to interpret contents of memory between aliasing resources consistently in some cases, + which is called "data inheritance". For details, see + Microsoft documentation chapter "Memory Aliasing and Data Inheritance". +- You can create more complex layout where different textures and buffers are bound + at different offsets inside one large allocation. For example, one can imagine + a big texture used in some render passes, aliasing with a set of many small buffers + used in some further passes. To bind a resource at non-zero offset of an allocation, + call D3D12MA::Allocator::CreateAliasingResource with appropriate value of `AllocationLocalOffset` parameter. +- Resources of the three categories: buffers, textures with `RENDER_TARGET` or `DEPTH_STENCIL` flags, and all other textures, + can be placed in the same memory only when `allocator->GetD3D12Options().ResourceHeapTier >= D3D12_RESOURCE_HEAP_TIER_2`. + Otherwise they must be placed in different memory heap types, and thus aliasing them is not possible. + + +\page linear_algorithm Linear allocation algorithm + +Each D3D12 memory block managed by this library has accompanying metadata that +keeps track of used and unused regions. By default, the metadata structure and +algorithm tries to find best place for new allocations among free regions to +optimize memory usage. This way you can allocate and free objects in any order. + +![Default allocation algorithm](../gfx/Linear_allocator_1_algo_default.png) + +Sometimes there is a need to use simpler, linear allocation algorithm. You can +create custom pool that uses such algorithm by adding flag +D3D12MA::POOL_FLAG_ALGORITHM_LINEAR to D3D12MA::POOL_DESC::Flags while creating +D3D12MA::Pool object. Then an alternative metadata management is used. It always +creates new allocations after last one and doesn't reuse free regions after +allocations freed in the middle. It results in better allocation performance and +less memory consumed by metadata. + +![Linear allocation algorithm](../gfx/Linear_allocator_2_algo_linear.png) + +With this one flag, you can create a custom pool that can be used in many ways: +free-at-once, stack, double stack, and ring buffer. See below for details. +You don't need to specify explicitly which of these options you are going to use - it is detected automatically. + +\section linear_algorithm_free_at_once Free-at-once + +In a pool that uses linear algorithm, you still need to free all the allocations +individually by calling `allocation->Release()`. You can free +them in any order. New allocations are always made after last one - free space +in the middle is not reused. However, when you release all the allocation and +the pool becomes empty, allocation starts from the beginning again. This way you +can use linear algorithm to speed up creation of allocations that you are going +to release all at once. + +![Free-at-once](../gfx/Linear_allocator_3_free_at_once.png) + +This mode is also available for pools created with D3D12MA::POOL_DESC::MaxBlockCount +value that allows multiple memory blocks. + +\section linear_algorithm_stack Stack + +When you free an allocation that was created last, its space can be reused. +Thanks to this, if you always release allocations in the order opposite to their +creation (LIFO - Last In First Out), you can achieve behavior of a stack. + +![Stack](../gfx/Linear_allocator_4_stack.png) + +This mode is also available for pools created with D3D12MA::POOL_DESC::MaxBlockCount +value that allows multiple memory blocks. + +\section linear_algorithm_double_stack Double stack + +The space reserved by a custom pool with linear algorithm may be used by two +stacks: + +- First, default one, growing up from offset 0. +- Second, "upper" one, growing down from the end towards lower offsets. + +To make allocation from the upper stack, add flag D3D12MA::ALLOCATION_FLAG_UPPER_ADDRESS +to D3D12MA::ALLOCATION_DESC::Flags. + +![Double stack](../gfx/Linear_allocator_7_double_stack.png) + +Double stack is available only in pools with one memory block - +D3D12MA::POOL_DESC::MaxBlockCount must be 1. Otherwise behavior is undefined. + +When the two stacks' ends meet so there is not enough space between them for a +new allocation, such allocation fails with usual `E_OUTOFMEMORY` error. + +\section linear_algorithm_ring_buffer Ring buffer + +When you free some allocations from the beginning and there is not enough free space +for a new one at the end of a pool, allocator's "cursor" wraps around to the +beginning and starts allocation there. Thanks to this, if you always release +allocations in the same order as you created them (FIFO - First In First Out), +you can achieve behavior of a ring buffer / queue. + +![Ring buffer](../gfx/Linear_allocator_5_ring_buffer.png) + +Ring buffer is available only in pools with one memory block - +D3D12MA::POOL_DESC::MaxBlockCount must be 1. Otherwise behavior is undefined. + +\section linear_algorithm_additional_considerations Additional considerations + +Linear algorithm can also be used with \ref virtual_allocator. +See flag D3D12MA::VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR. + + +\page virtual_allocator Virtual allocator + +As an extra feature, the core allocation algorithm of the library is exposed through a simple and convenient API of "virtual allocator". +It doesn't allocate any real GPU memory. It just keeps track of used and free regions of a "virtual block". +You can use it to allocate your own memory or other objects, even completely unrelated to D3D12. +A common use case is sub-allocation of pieces of one large GPU buffer. + +\section virtual_allocator_creating_virtual_block Creating virtual block + +To use this functionality, there is no main "allocator" object. +You don't need to have D3D12MA::Allocator object created. +All you need to do is to create a separate D3D12MA::VirtualBlock object for each block of memory you want to be managed by the allocator: + +-# Fill in D3D12MA::ALLOCATOR_DESC structure. +-# Call D3D12MA::CreateVirtualBlock. Get new D3D12MA::VirtualBlock object. + +Example: + +\code +D3D12MA::VIRTUAL_BLOCK_DESC blockDesc = {}; +blockDesc.Size = 1048576; // 1 MB + +D3D12MA::VirtualBlock *block; +HRESULT hr = CreateVirtualBlock(&blockDesc, &block); +\endcode + +\section virtual_allocator_making_virtual_allocations Making virtual allocations + +D3D12MA::VirtualBlock object contains internal data structure that keeps track of free and occupied regions +using the same code as the main D3D12 memory allocator. +A single allocation is identified by a lightweight structure D3D12MA::VirtualAllocation. +You will also likely want to know the offset at which the allocation was made in the block. + +In order to make an allocation: + +-# Fill in D3D12MA::VIRTUAL_ALLOCATION_DESC structure. +-# Call D3D12MA::VirtualBlock::Allocate. Get new D3D12MA::VirtualAllocation value that identifies the allocation. + +Example: + +\code +D3D12MA::VIRTUAL_ALLOCATION_DESC allocDesc = {}; +allocDesc.Size = 4096; // 4 KB + +D3D12MA::VirtualAllocation alloc; +UINT64 allocOffset; +hr = block->Allocate(&allocDesc, &alloc, &allocOffset); +if(SUCCEEDED(hr)) +{ + // Use the 4 KB of your memory starting at allocOffset. +} +else +{ + // Allocation failed - no space for it could be found. Handle this error! +} +\endcode + +\section virtual_allocator_deallocation Deallocation + +When no longer needed, an allocation can be freed by calling D3D12MA::VirtualBlock::FreeAllocation. + +When whole block is no longer needed, the block object can be released by calling `block->Release()`. +All allocations must be freed before the block is destroyed, which is checked internally by an assert. +However, if you don't want to call `block->FreeAllocation` for each allocation, you can use D3D12MA::VirtualBlock::Clear to free them all at once - +a feature not available in normal D3D12 memory allocator. + +Example: + +\code +block->FreeAllocation(alloc); +block->Release(); +\endcode + +\section virtual_allocator_allocation_parameters Allocation parameters + +You can attach a custom pointer to each allocation by using D3D12MA::VirtualBlock::SetAllocationPrivateData. +Its default value is `NULL`. +It can be used to store any data that needs to be associated with that allocation - e.g. an index, a handle, or a pointer to some +larger data structure containing more information. Example: + +\code +struct CustomAllocData +{ + std::string m_AllocName; +}; +CustomAllocData* allocData = new CustomAllocData(); +allocData->m_AllocName = "My allocation 1"; +block->SetAllocationPrivateData(alloc, allocData); +\endcode + +The pointer can later be fetched, along with allocation offset and size, by passing the allocation handle to function +D3D12MA::VirtualBlock::GetAllocationInfo and inspecting returned structure D3D12MA::VIRTUAL_ALLOCATION_INFO. +If you allocated a new object to be used as the custom pointer, don't forget to delete that object before freeing the allocation! +Example: + +\code +VIRTUAL_ALLOCATION_INFO allocInfo; +block->GetAllocationInfo(alloc, &allocInfo); +delete (CustomAllocData*)allocInfo.pPrivateData; + +block->FreeAllocation(alloc); +\endcode + +\section virtual_allocator_alignment_and_units Alignment and units + +It feels natural to express sizes and offsets in bytes. +If an offset of an allocation needs to be aligned to a multiply of some number (e.g. 4 bytes), you can fill optional member +D3D12MA::VIRTUAL_ALLOCATION_DESC::Alignment to request it. Example: + +\code +D3D12MA::VIRTUAL_ALLOCATION_DESC allocDesc = {}; +allocDesc.Size = 4096; // 4 KB +allocDesc.Alignment = 4; // Returned offset must be a multiply of 4 B + +D3D12MA::VirtualAllocation alloc; +UINT64 allocOffset; +hr = block->Allocate(&allocDesc, &alloc, &allocOffset); +\endcode + +Alignments of different allocations made from one block may vary. +However, if all alignments and sizes are always multiply of some size e.g. 4 B or `sizeof(MyDataStruct)`, +you can express all sizes, alignments, and offsets in multiples of that size instead of individual bytes. +It might be more convenient, but you need to make sure to use this new unit consistently in all the places: + +- D3D12MA::VIRTUAL_BLOCK_DESC::Size +- D3D12MA::VIRTUAL_ALLOCATION_DESC::Size and D3D12MA::VIRTUAL_ALLOCATION_DESC::Alignment +- Using offset returned by D3D12MA::VirtualBlock::Allocate and D3D12MA::VIRTUAL_ALLOCATION_INFO::Offset + +\section virtual_allocator_statistics Statistics + +You can obtain brief statistics of a virtual block using D3D12MA::VirtualBlock::GetStatistics(). +The function fills structure D3D12MA::Statistics - same as used by the normal D3D12 memory allocator. +Example: + +\code +D3D12MA::Statistics stats; +block->GetStatistics(&stats); +printf("My virtual block has %llu bytes used by %u virtual allocations\n", + stats.AllocationBytes, stats.AllocationCount); +\endcode + +More detailed statistics can be obtained using function D3D12MA::VirtualBlock::CalculateStatistics(), +but they are slower to calculate. + +You can also request a full list of allocations and free regions as a string in JSON format by calling +D3D12MA::VirtualBlock::BuildStatsString. +Returned string must be later freed using D3D12MA::VirtualBlock::FreeStatsString. +The format of this string may differ from the one returned by the main D3D12 allocator, but it is similar. + +\section virtual_allocator_additional_considerations Additional considerations + +Alternative, linear algorithm can be used with virtual allocator - see flag +D3D12MA::VIRTUAL_BLOCK_FLAG_ALGORITHM_LINEAR and documentation: \ref linear_algorithm. + +Note that the "virtual allocator" functionality is implemented on a level of individual memory blocks. +Keeping track of a whole collection of blocks, allocating new ones when out of free space, +deleting empty ones, and deciding which one to try first for a new allocation must be implemented by the user. + + +\page configuration Configuration + +Please check file `D3D12MemAlloc.cpp` lines between "Configuration Begin" and +"Configuration End" to find macros that you can define to change the behavior of +the library, primarily for debugging purposes. + +\section custom_memory_allocator Custom CPU memory allocator + +If you use custom allocator for CPU memory rather than default C++ operator `new` +and `delete` or `malloc` and `free` functions, you can make this library using +your allocator as well by filling structure D3D12MA::ALLOCATION_CALLBACKS and +passing it as optional member D3D12MA::ALLOCATOR_DESC::pAllocationCallbacks. +Functions pointed there will be used by the library to make any CPU-side +allocations. Example: + +\code +#include + +void* CustomAllocate(size_t Size, size_t Alignment, void* pPrivateData) +{ + void* memory = _aligned_malloc(Size, Alignment); + // Your extra bookkeeping here... + return memory; +} + +void CustomFree(void* pMemory, void* pPrivateData) +{ + // Your extra bookkeeping here... + _aligned_free(pMemory); +} + +(...) + +D3D12MA::ALLOCATION_CALLBACKS allocationCallbacks = {}; +allocationCallbacks.pAllocate = &CustomAllocate; +allocationCallbacks.pFree = &CustomFree; + +D3D12MA::ALLOCATOR_DESC allocatorDesc = {}; +allocatorDesc.pDevice = device; +allocatorDesc.pAdapter = adapter; +allocatorDesc.pAllocationCallbacks = &allocationCallbacks; + +D3D12MA::Allocator* allocator; +HRESULT hr = D3D12MA::CreateAllocator(&allocatorDesc, &allocator); +\endcode + + +\section debug_margins Debug margins + +By default, allocations are laid out in memory blocks next to each other if possible +(considering required alignment returned by `ID3D12Device::GetResourceAllocationInfo`). + +![Allocations without margin](../gfx/Margins_1.png) + +Define macro `D3D12MA_DEBUG_MARGIN` to some non-zero value (e.g. 16) inside "D3D12MemAlloc.cpp" +to enforce specified number of bytes as a margin after every allocation. + +![Allocations with margin](../gfx/Margins_2.png) + +If your bug goes away after enabling margins, it means it may be caused by memory +being overwritten outside of allocation boundaries. It is not 100% certain though. +Change in application behavior may also be caused by different order and distribution +of allocations across memory blocks after margins are applied. + +Margins work with all memory heap types. + +Margin is applied only to placed allocations made out of memory heaps and not to committed +allocations, which have their own, implicit memory heap of specific size. +It is thus not applied to allocations made using D3D12MA::ALLOCATION_FLAG_COMMITTED flag +or those automatically decided to put into committed allocations, e.g. due to its large size. + +Margins appear in [JSON dump](@ref statistics_json_dump) as part of free space. + +Note that enabling margins increases memory usage and fragmentation. + +Margins do not apply to \ref virtual_allocator. + + +\page general_considerations General considerations + +\section general_considerations_thread_safety Thread safety + +- The library has no global state, so separate D3D12MA::Allocator objects can be used independently. + In typical applications there should be no need to create multiple such objects though - one per `ID3D12Device` is enough. +- All calls to methods of D3D12MA::Allocator class are safe to be made from multiple + threads simultaneously because they are synchronized internally when needed. +- When the allocator is created with D3D12MA::ALLOCATOR_FLAG_SINGLETHREADED, + calls to methods of D3D12MA::Allocator class must be made from a single thread or synchronized by the user. + Using this flag may improve performance. +- D3D12MA::VirtualBlock is not safe to be used from multiple threads simultaneously. + +\section general_considerations_versioning_and_compatibility Versioning and compatibility + +The library uses [**Semantic Versioning**](https://semver.org/), +which means version numbers follow convention: Major.Minor.Patch (e.g. 2.3.0), where: + +- Incremented Patch version means a release is backward- and forward-compatible, + introducing only some internal improvements, bug fixes, optimizations etc. + or changes that are out of scope of the official API described in this documentation. +- Incremented Minor version means a release is backward-compatible, + so existing code that uses the library should continue to work, while some new + symbols could have been added: new structures, functions, new values in existing + enums and bit flags, new structure members, but not new function parameters. +- Incrementing Major version means a release could break some backward compatibility. + +All changes between official releases are documented in file "CHANGELOG.md". + +\warning Backward compatiblity is considered on the level of C++ source code, not binary linkage. +Adding new members to existing structures is treated as backward compatible if initializing +the new members to binary zero results in the old behavior. +You should always fully initialize all library structures to zeros and not rely on their +exact binary size. + +\section general_considerations_features_not_supported Features not supported + +Features deliberately excluded from the scope of this library: + +- **Descriptor allocation.** Although also called "heaps", objects that represent + descriptors are separate part of the D3D12 API from buffers and textures. + You can still use \ref virtual_allocator to manage descriptors and their ranges inside a descriptor heap. +- **Support for reserved (tiled) resources.** We don't recommend using them. +- Support for `ID3D12Device::Evict` and `MakeResident`. We don't recommend using them. + You can call them on the D3D12 objects manually. + Plese keep in mind, however, that eviction happens on the level of entire `ID3D12Heap` memory blocks + and not individual buffers or textures which may be placed inside them. +- **Handling CPU memory allocation failures.** When dynamically creating small C++ + objects in CPU memory (not the GPU memory), allocation failures are not + handled gracefully, because that would complicate code significantly and + is usually not needed in desktop PC applications anyway. + Success of an allocation is just checked with an assert. +- **Code free of any compiler warnings.** + There are many preprocessor macros that make some variables unused, function parameters unreferenced, + or conditional expressions constant in some configurations. + The code of this library should not be bigger or more complicated just to silence these warnings. + It is recommended to disable such warnings instead. +- This is a C++ library. **Bindings or ports to any other programming languages** are welcome as external projects but + are not going to be included into this repository. +*/ diff --git a/Backends/RmlUi_DirectX/LICENSE.txt b/Backends/RmlUi_DirectX/LICENSE.txt new file mode 100644 index 000000000..4a8dfd55b --- /dev/null +++ b/Backends/RmlUi_DirectX/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2019-2024 Advanced Micro Devices, Inc. All rights reserved. + +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. \ No newline at end of file diff --git a/Backends/RmlUi_DirectX/compile_shaders.py b/Backends/RmlUi_DirectX/compile_shaders.py new file mode 100644 index 000000000..19af6e7ff --- /dev/null +++ b/Backends/RmlUi_DirectX/compile_shaders.py @@ -0,0 +1,63 @@ +# This source file is part of RmlUi, the HTML/CSS Interface Middleware +# +# For the latest information, see http://github.com/mikke89/RmlUi +# +# Copyright (c) 2008-2014 CodePoint Ltd, Shift Technology Ltd, and contributors +# Copyright (c) 2019-2023 The RmlUi Team, and contributors +# +# 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. + +import sys +import os +import subprocess + +# Compiles all .frag and .vert files in this directory to SPIR-V binary C character arrays. Requires 'glslc' installed and available system-wide. + +out_file = "ShadersCompiledSPV.h" + +current_dir = os.path.dirname(os.path.realpath(__file__)); + +temp_spirv_path = os.path.join(current_dir, ".temp.spv") +out_path = os.path.join(current_dir, out_file) + +with open(out_path,'w') as result_file: + result_file.write('// RmlUi SPIR-V Vulkan shaders compiled using command: \'python compile_shaders.py\'. Do not edit manually.\n\n#include \n') + + for file in os.listdir(current_dir): + if file.endswith(".vert") or file.endswith(".frag"): + shader_path = os.path.join(current_dir, file) + variable_name = os.path.splitext(file)[0] + + print("Compiling '{}' to SPIR-V using glslc.".format(file)) + + subprocess.run(["glslc", shader_path, "-o", temp_spirv_path], check = True) + + print("Success, writing output to variable '{}' in {}".format(variable_name, out_file)) + + i = 0 + result_file.write('\nalignas(uint32_t) static const unsigned char {}[] = {{'.format(variable_name)) + for b in open(temp_spirv_path, 'rb').read(): + if i % 20 == 0: + result_file.write('\n\t') + result_file.write('0x%02X,' % b) + i += 1 + + result_file.write('\n};\n') + + os.remove(temp_spirv_path) diff --git a/Backends/RmlUi_DirectX/offsetAllocator.cpp b/Backends/RmlUi_DirectX/offsetAllocator.cpp new file mode 100644 index 000000000..b2808e622 --- /dev/null +++ b/Backends/RmlUi_DirectX/offsetAllocator.cpp @@ -0,0 +1,513 @@ +// (C) Sebastian Aaltonen 2023 +// MIT License (see file: LICENSE) + +#include "offsetAllocator.hpp" + +#ifdef DEBUG +#include +#define ASSERT(x) assert(x) +//#define DEBUG_VERBOSE +#else +#define ASSERT(x) +#endif + +#ifdef DEBUG_VERBOSE +#include +#endif + +#ifdef _MSC_VER +#include +#endif + +#include + +namespace OffsetAllocator +{ + inline uint32 lzcnt_nonzero(uint32 v) + { +#ifdef _MSC_VER + unsigned long retVal; + _BitScanReverse(&retVal, v); + return 31 - retVal; +#else + return __builtin_clz(v); +#endif + } + + inline uint32 tzcnt_nonzero(uint32 v) + { +#ifdef _MSC_VER + unsigned long retVal; + _BitScanForward(&retVal, v); + return retVal; +#else + return __builtin_ctz(v); +#endif + } + + namespace SmallFloat + { + static constexpr uint32 MANTISSA_BITS = 3; + static constexpr uint32 MANTISSA_VALUE = 1 << MANTISSA_BITS; + static constexpr uint32 MANTISSA_MASK = MANTISSA_VALUE - 1; + + // Bin sizes follow floating point (exponent + mantissa) distribution (piecewise linear log approx) + // This ensures that for each size class, the average overhead percentage stays the same + uint32 uintToFloatRoundUp(uint32 size) + { + uint32 exp = 0; + uint32 mantissa = 0; + + if (size < MANTISSA_VALUE) + { + // Denorm: 0..(MANTISSA_VALUE-1) + mantissa = size; + } + else + { + // Normalized: Hidden high bit always 1. Not stored. Just like float. + uint32 leadingZeros = lzcnt_nonzero(size); + uint32 highestSetBit = 31 - leadingZeros; + + uint32 mantissaStartBit = highestSetBit - MANTISSA_BITS; + exp = mantissaStartBit + 1; + mantissa = (size >> mantissaStartBit) & MANTISSA_MASK; + + uint32 lowBitsMask = (1 << mantissaStartBit) - 1; + + // Round up! + if ((size & lowBitsMask) != 0) + mantissa++; + } + + return (exp << MANTISSA_BITS) + mantissa; // + allows mantissa->exp overflow for round up + } + + uint32 uintToFloatRoundDown(uint32 size) + { + uint32 exp = 0; + uint32 mantissa = 0; + + if (size < MANTISSA_VALUE) + { + // Denorm: 0..(MANTISSA_VALUE-1) + mantissa = size; + } + else + { + // Normalized: Hidden high bit always 1. Not stored. Just like float. + uint32 leadingZeros = lzcnt_nonzero(size); + uint32 highestSetBit = 31 - leadingZeros; + + uint32 mantissaStartBit = highestSetBit - MANTISSA_BITS; + exp = mantissaStartBit + 1; + mantissa = (size >> mantissaStartBit) & MANTISSA_MASK; + } + + return (exp << MANTISSA_BITS) | mantissa; + } + + uint32 floatToUint(uint32 floatValue) + { + uint32 exponent = floatValue >> MANTISSA_BITS; + uint32 mantissa = floatValue & MANTISSA_MASK; + if (exponent == 0) + { + // Denorms + return mantissa; + } + else + { + return (mantissa | MANTISSA_VALUE) << (exponent - 1); + } + } + } + + // Utility functions + uint32 findLowestSetBitAfter(uint32 bitMask, uint32 startBitIndex) + { + uint32 maskBeforeStartIndex = (1 << startBitIndex) - 1; + uint32 maskAfterStartIndex = ~maskBeforeStartIndex; + uint32 bitsAfter = bitMask & maskAfterStartIndex; + if (bitsAfter == 0) return Allocation::NO_SPACE; + return tzcnt_nonzero(bitsAfter); + } + + // Allocator... + Allocator::Allocator(uint32 size, uint32 maxAllocs) : + m_size(size), + m_maxAllocs(maxAllocs), + m_nodes(nullptr), + m_freeNodes(nullptr) + { + if (sizeof(NodeIndex) == 2) + { + ASSERT(maxAllocs <= 65536); + } + reset(); + } + + Allocator::Allocator(Allocator &&other) : + m_size(other.m_size), + m_maxAllocs(other.m_maxAllocs), + m_freeStorage(other.m_freeStorage), + m_usedBinsTop(other.m_usedBinsTop), + m_nodes(other.m_nodes), + m_freeNodes(other.m_freeNodes), + m_freeOffset(other.m_freeOffset) + { + memcpy(m_usedBins, other.m_usedBins, sizeof(uint8) * NUM_TOP_BINS); + memcpy(m_binIndices, other.m_binIndices, sizeof(NodeIndex) * NUM_LEAF_BINS); + + other.m_nodes = nullptr; + other.m_freeNodes = nullptr; + other.m_freeOffset = 0; + other.m_maxAllocs = 0; + other.m_usedBinsTop = 0; + } + + void Allocator::reset() + { + m_freeStorage = 0; + m_usedBinsTop = 0; + m_freeOffset = m_maxAllocs - 1; + + for (uint32 i = 0 ; i < NUM_TOP_BINS; i++) + m_usedBins[i] = 0; + + for (uint32 i = 0 ; i < NUM_LEAF_BINS; i++) + m_binIndices[i] = Node::unused; + + if (m_nodes) delete[] m_nodes; + if (m_freeNodes) delete[] m_freeNodes; + + m_nodes = new Node[m_maxAllocs]; + m_freeNodes = new NodeIndex[m_maxAllocs]; + + // Freelist is a stack. Nodes in inverse order so that [0] pops first. + for (uint32 i = 0; i < m_maxAllocs; i++) + { + m_freeNodes[i] = m_maxAllocs - i - 1; + } + + // Start state: Whole storage as one big node + // Algorithm will split remainders and push them back as smaller nodes + insertNodeIntoBin(m_size, 0); + } + + Allocator::~Allocator() + { + delete[] m_nodes; + delete[] m_freeNodes; + } + + Allocation Allocator::allocate(uint32 size) + { + // Out of allocations? + if (m_freeOffset == 0) + { + Allocation result; + result.offset = Allocation::NO_SPACE; + result.metadata = Allocation::NO_SPACE; + //return {.offset = Allocation::NO_SPACE, .metadata = Allocation::NO_SPACE}; + return result; + } + + // Round up to bin index to ensure that alloc >= bin + // Gives us min bin index that fits the size + uint32 minBinIndex = SmallFloat::uintToFloatRoundUp(size); + + uint32 minTopBinIndex = minBinIndex >> TOP_BINS_INDEX_SHIFT; + uint32 minLeafBinIndex = minBinIndex & LEAF_BINS_INDEX_MASK; + + uint32 topBinIndex = minTopBinIndex; + uint32 leafBinIndex = Allocation::NO_SPACE; + + // If top bin exists, scan its leaf bin. This can fail (NO_SPACE). + if (m_usedBinsTop & (1 << topBinIndex)) + { + leafBinIndex = findLowestSetBitAfter(m_usedBins[topBinIndex], minLeafBinIndex); + } + + // If we didn't find space in top bin, we search top bin from +1 + if (leafBinIndex == Allocation::NO_SPACE) + { + topBinIndex = findLowestSetBitAfter(m_usedBinsTop, minTopBinIndex + 1); + + // Out of space? + if (topBinIndex == Allocation::NO_SPACE) + { + Allocation result; + result.offset = Allocation::NO_SPACE; + result.metadata = Allocation::NO_SPACE; + //return {.offset = Allocation::NO_SPACE, .metadata = Allocation::NO_SPACE}; + return result; + } + + // All leaf bins here fit the alloc, since the top bin was rounded up. Start leaf search from bit 0. + // NOTE: This search can't fail since at least one leaf bit was set because the top bit was set. + leafBinIndex = tzcnt_nonzero(m_usedBins[topBinIndex]); + } + + uint32 binIndex = (topBinIndex << TOP_BINS_INDEX_SHIFT) | leafBinIndex; + + // Pop the top node of the bin. Bin top = node.next. + uint32 nodeIndex = m_binIndices[binIndex]; + Node& node = m_nodes[nodeIndex]; + uint32 nodeTotalSize = node.dataSize; + node.dataSize = size; + node.used = true; + m_binIndices[binIndex] = node.binListNext; + if (node.binListNext != Node::unused) m_nodes[node.binListNext].binListPrev = Node::unused; + m_freeStorage -= nodeTotalSize; +#ifdef DEBUG_VERBOSE + printf("Free storage: %u (-%u) (allocate)\n", m_freeStorage, nodeTotalSize); +#endif + + // Bin empty? + if (m_binIndices[binIndex] == Node::unused) + { + // Remove a leaf bin mask bit + m_usedBins[topBinIndex] &= ~(1 << leafBinIndex); + + // All leaf bins empty? + if (m_usedBins[topBinIndex] == 0) + { + // Remove a top bin mask bit + m_usedBinsTop &= ~(1 << topBinIndex); + } + } + + // Push back reminder N elements to a lower bin + uint32 reminderSize = nodeTotalSize - size; + if (reminderSize > 0) + { + uint32 newNodeIndex = insertNodeIntoBin(reminderSize, node.dataOffset + size); + + // Link nodes next to each other so that we can merge them later if both are free + // And update the old next neighbor to point to the new node (in middle) + if (node.neighborNext != Node::unused) m_nodes[node.neighborNext].neighborPrev = newNodeIndex; + m_nodes[newNodeIndex].neighborPrev = nodeIndex; + m_nodes[newNodeIndex].neighborNext = node.neighborNext; + node.neighborNext = newNodeIndex; + } + + // return {.offset = node.dataOffset, .metadata = nodeIndex}; + Allocation result; + result.offset = node.dataOffset; + result.metadata = nodeIndex; + return result; + } + + void Allocator::free(Allocation allocation) + { + ASSERT(allocation.metadata != Allocation::NO_SPACE); + if (!m_nodes) return; + + uint32 nodeIndex = allocation.metadata; + Node& node = m_nodes[nodeIndex]; + + // Double delete check + ASSERT(node.used == true); + + // Merge with neighbors... + uint32 offset = node.dataOffset; + uint32 size = node.dataSize; + + if ((node.neighborPrev != Node::unused) && (m_nodes[node.neighborPrev].used == false)) + { + // Previous (contiguous) free node: Change offset to previous node offset. Sum sizes + Node& prevNode = m_nodes[node.neighborPrev]; + offset = prevNode.dataOffset; + size += prevNode.dataSize; + + // Remove node from the bin linked list and put it in the freelist + removeNodeFromBin(node.neighborPrev); + + ASSERT(prevNode.neighborNext == nodeIndex); + node.neighborPrev = prevNode.neighborPrev; + } + + if ((node.neighborNext != Node::unused) && (m_nodes[node.neighborNext].used == false)) + { + // Next (contiguous) free node: Offset remains the same. Sum sizes. + Node& nextNode = m_nodes[node.neighborNext]; + size += nextNode.dataSize; + + // Remove node from the bin linked list and put it in the freelist + removeNodeFromBin(node.neighborNext); + + ASSERT(nextNode.neighborPrev == nodeIndex); + node.neighborNext = nextNode.neighborNext; + } + + uint32 neighborNext = node.neighborNext; + uint32 neighborPrev = node.neighborPrev; + + // Insert the removed node to freelist +#ifdef DEBUG_VERBOSE + printf("Putting node %u into freelist[%u] (free)\n", nodeIndex, m_freeOffset + 1); +#endif + m_freeNodes[++m_freeOffset] = nodeIndex; + + // Insert the (combined) free node to bin + uint32 combinedNodeIndex = insertNodeIntoBin(size, offset); + + // Connect neighbors with the new combined node + if (neighborNext != Node::unused) + { + m_nodes[combinedNodeIndex].neighborNext = neighborNext; + m_nodes[neighborNext].neighborPrev = combinedNodeIndex; + } + if (neighborPrev != Node::unused) + { + m_nodes[combinedNodeIndex].neighborPrev = neighborPrev; + m_nodes[neighborPrev].neighborNext = combinedNodeIndex; + } + } + + uint32 Allocator::insertNodeIntoBin(uint32 size, uint32 dataOffset) + { + // Round down to bin index to ensure that bin >= alloc + uint32 binIndex = SmallFloat::uintToFloatRoundDown(size); + + uint32 topBinIndex = binIndex >> TOP_BINS_INDEX_SHIFT; + uint32 leafBinIndex = binIndex & LEAF_BINS_INDEX_MASK; + + // Bin was empty before? + if (m_binIndices[binIndex] == Node::unused) + { + // Set bin mask bits + m_usedBins[topBinIndex] |= 1 << leafBinIndex; + m_usedBinsTop |= 1 << topBinIndex; + } + + // Take a freelist node and insert on top of the bin linked list (next = old top) + uint32 topNodeIndex = m_binIndices[binIndex]; + uint32 nodeIndex = m_freeNodes[m_freeOffset--]; +#ifdef DEBUG_VERBOSE + printf("Getting node %u from freelist[%u]\n", nodeIndex, m_freeOffset + 1); +#endif + // m_nodes[nodeIndex] = {.dataOffset = dataOffset, .dataSize = size, .binListNext = topNodeIndex}; + Node node_instance; + node_instance.dataOffset = dataOffset; + node_instance.dataSize = size; + node_instance.binListNext = topNodeIndex; + m_nodes[nodeIndex] = node_instance; + + if (topNodeIndex != Node::unused) m_nodes[topNodeIndex].binListPrev = nodeIndex; + m_binIndices[binIndex] = nodeIndex; + + m_freeStorage += size; +#ifdef DEBUG_VERBOSE + printf("Free storage: %u (+%u) (insertNodeIntoBin)\n", m_freeStorage, size); +#endif + + return nodeIndex; + } + + void Allocator::removeNodeFromBin(uint32 nodeIndex) + { + Node &node = m_nodes[nodeIndex]; + + if (node.binListPrev != Node::unused) + { + // Easy case: We have previous node. Just remove this node from the middle of the list. + m_nodes[node.binListPrev].binListNext = node.binListNext; + if (node.binListNext != Node::unused) m_nodes[node.binListNext].binListPrev = node.binListPrev; + } + else + { + // Hard case: We are the first node in a bin. Find the bin. + + // Round down to bin index to ensure that bin >= alloc + uint32 binIndex = SmallFloat::uintToFloatRoundDown(node.dataSize); + + uint32 topBinIndex = binIndex >> TOP_BINS_INDEX_SHIFT; + uint32 leafBinIndex = binIndex & LEAF_BINS_INDEX_MASK; + + m_binIndices[binIndex] = node.binListNext; + if (node.binListNext != Node::unused) m_nodes[node.binListNext].binListPrev = Node::unused; + + // Bin empty? + if (m_binIndices[binIndex] == Node::unused) + { + // Remove a leaf bin mask bit + m_usedBins[topBinIndex] &= ~(1 << leafBinIndex); + + // All leaf bins empty? + if (m_usedBins[topBinIndex] == 0) + { + // Remove a top bin mask bit + m_usedBinsTop &= ~(1 << topBinIndex); + } + } + } + + // Insert the node to freelist +#ifdef DEBUG_VERBOSE + printf("Putting node %u into freelist[%u] (removeNodeFromBin)\n", nodeIndex, m_freeOffset + 1); +#endif + m_freeNodes[++m_freeOffset] = nodeIndex; + + m_freeStorage -= node.dataSize; +#ifdef DEBUG_VERBOSE + printf("Free storage: %u (-%u) (removeNodeFromBin)\n", m_freeStorage, node.dataSize); +#endif + } + + uint32 Allocator::allocationSize(Allocation allocation) const + { + if (allocation.metadata == Allocation::NO_SPACE) return 0; + if (!m_nodes) return 0; + + return m_nodes[allocation.metadata].dataSize; + } + + StorageReport Allocator::storageReport() const + { + uint32 largestFreeRegion = 0; + uint32 freeStorage = 0; + + // Out of allocations? -> Zero free space + if (m_freeOffset > 0) + { + freeStorage = m_freeStorage; + if (m_usedBinsTop) + { + uint32 topBinIndex = 31 - lzcnt_nonzero(m_usedBinsTop); + uint32 leafBinIndex = 31 - lzcnt_nonzero(m_usedBins[topBinIndex]); + largestFreeRegion = SmallFloat::floatToUint((topBinIndex << TOP_BINS_INDEX_SHIFT) | leafBinIndex); + ASSERT(freeStorage >= largestFreeRegion); + } + } + +// return {.totalFreeSpace = freeStorage, .largestFreeRegion = largestFreeRegion}; + StorageReport result; + result.totalFreeSpace = freeStorage; + result.largestFreeRegion = largestFreeRegion; + return result; + } + + StorageReportFull Allocator::storageReportFull() const + { + StorageReportFull report; + StorageReportFull::Region region; + for (uint32 i = 0; i < NUM_LEAF_BINS; i++) + { + uint32 count = 0; + uint32 nodeIndex = m_binIndices[i]; + while (nodeIndex != Node::unused) + { + nodeIndex = m_nodes[nodeIndex].binListNext; + count++; + } + + region.size = SmallFloat::floatToUint(i); + region.count = count; + // report.freeRegions[i] = { .size = SmallFloat::floatToUint(i), .count = count }; + report.freeRegions[i] = region; + } + return report; + } +} diff --git a/Backends/RmlUi_DirectX/offsetAllocator.hpp b/Backends/RmlUi_DirectX/offsetAllocator.hpp new file mode 100644 index 000000000..155e21dd9 --- /dev/null +++ b/Backends/RmlUi_DirectX/offsetAllocator.hpp @@ -0,0 +1,95 @@ +// (C) Sebastian Aaltonen 2023 +// MIT License (see file: LICENSE) + +//#define USE_16_BIT_OFFSETS + +namespace OffsetAllocator +{ + typedef unsigned char uint8; + typedef unsigned short uint16; + typedef unsigned int uint32; + + // 16 bit offsets mode will halve the metadata storage cost + // But it only supports up to 65536 maximum allocation count +#ifdef USE_16_BIT_NODE_INDICES + typedef uint16 NodeIndex; +#else + typedef uint32 NodeIndex; +#endif + + static constexpr uint32 NUM_TOP_BINS = 32; + static constexpr uint32 BINS_PER_LEAF = 8; + static constexpr uint32 TOP_BINS_INDEX_SHIFT = 3; + static constexpr uint32 LEAF_BINS_INDEX_MASK = 0x7; + static constexpr uint32 NUM_LEAF_BINS = NUM_TOP_BINS * BINS_PER_LEAF; + + struct Allocation + { + static constexpr uint32 NO_SPACE = 0xffffffff; + + uint32 offset = NO_SPACE; + NodeIndex metadata = NO_SPACE; // internal: node index + }; + + struct StorageReport + { + uint32 totalFreeSpace; + uint32 largestFreeRegion; + }; + + struct StorageReportFull + { + struct Region + { + uint32 size; + uint32 count; + }; + + Region freeRegions[NUM_LEAF_BINS]; + }; + + class Allocator + { + public: + Allocator(uint32 size, uint32 maxAllocs = 128 * 1024); + Allocator(Allocator &&other); + ~Allocator(); + void reset(); + + Allocation allocate(uint32 size); + void free(Allocation allocation); + + uint32 allocationSize(Allocation allocation) const; + StorageReport storageReport() const; + StorageReportFull storageReportFull() const; + + private: + uint32 insertNodeIntoBin(uint32 size, uint32 dataOffset); + void removeNodeFromBin(uint32 nodeIndex); + + struct Node + { + static constexpr NodeIndex unused = 0xffffffff; + + uint32 dataOffset = 0; + uint32 dataSize = 0; + NodeIndex binListPrev = unused; + NodeIndex binListNext = unused; + NodeIndex neighborPrev = unused; + NodeIndex neighborNext = unused; + bool used = false; // TODO: Merge as bit flag + }; + + uint32 m_size; + uint32 m_maxAllocs; + uint32 m_freeStorage; + + uint32 m_usedBinsTop; + uint8 m_usedBins[NUM_TOP_BINS]; + NodeIndex m_binIndices[NUM_LEAF_BINS]; + + Node* m_nodes; + NodeIndex* m_freeNodes; + uint32 m_freeOffset; + }; +} diff --git a/Backends/RmlUi_DirectX/shader_frag_color.hlsl b/Backends/RmlUi_DirectX/shader_frag_color.hlsl new file mode 100644 index 000000000..82921e524 --- /dev/null +++ b/Backends/RmlUi_DirectX/shader_frag_color.hlsl @@ -0,0 +1,17 @@ +/* + hlsl version of glsl shader + author: wh1t3lord +*/ + + +struct sInputData +{ + float4 inputPos : SV_Position; + float4 inputColor : COLOR; + float2 inputUV : TEXCOORD; +}; + +float4 main(const sInputData inputArgs) : SV_TARGET +{ + return inputArgs.inputColor; +} \ No newline at end of file diff --git a/Backends/RmlUi_DirectX/shader_frag_texture.hlsl b/Backends/RmlUi_DirectX/shader_frag_texture.hlsl new file mode 100644 index 000000000..486ba61ba --- /dev/null +++ b/Backends/RmlUi_DirectX/shader_frag_texture.hlsl @@ -0,0 +1,27 @@ +/* + hlsl version of glsl shader + author: wh1t3lord +*/ + + +struct sInputData +{ + float4 inputPos : SV_Position; + float4 inputColor : COLOR; + float2 inputUV : TEXCOORD; +}; + +Texture2D g_InputTexture : register(t0); + +SamplerState g_SamplerLinear +{ + Filter = MIN_MAG_MIP_LINEAR; + AddressU = Wrap; + AddressV = Wrap; +}; + + +float4 main(const sInputData inputArgs) : SV_TARGET +{ + return inputArgs.inputColor * g_InputTexture.sample(g_SamplerLinear, inputArgs.inputUV); +} \ No newline at end of file diff --git a/Backends/RmlUi_DirectX/shader_vert.hlsl b/Backends/RmlUi_DirectX/shader_vert.hlsl new file mode 100644 index 000000000..88d9df024 --- /dev/null +++ b/Backends/RmlUi_DirectX/shader_vert.hlsl @@ -0,0 +1,43 @@ +/* + hlsl version of glsl shader + author: wh1t3lord +*/ + +struct sInputData +{ + float2 inPosition : POSITION; + float4 inColor : COLOR; + float2 inTexCoord : TEXCOORD; +}; + +struct sOutputData +{ + float4 outPosition : SV_Position; + float4 outColor : COLOR; + float2 outUV : TEXCOORD; +}; + +cbuffer ConstantBuffer : register(b0) +{ + float4x4 m_transform; + float2 m_translate; +}; + +sOutputData main(const sInputData inArgs) +{ + sOutputData result; + + float2 translatedPos = inArgs.inPosition + m_translate; + float4 resPos = mul(m_transform, float4(translatedPos.x, translatedPos.y, 0.0, 1.0)); + + result.outPosition = resPos; + result.outColor = inArgs.inColor; + result.outUV = inArgs.inTexCoord; + +#if RMLUI_PREMULTIPLIED_ALPHA + // Pre-multiply vertex colors with their alpha. + result.outColor.rgb = result.outColor.rgb * result.outColor.a; +#endif + + return result; +}; \ No newline at end of file diff --git a/Backends/RmlUi_Renderer_DX12.cpp b/Backends/RmlUi_Renderer_DX12.cpp new file mode 100644 index 000000000..b4bcf9508 --- /dev/null +++ b/Backends/RmlUi_Renderer_DX12.cpp @@ -0,0 +1,5764 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * 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 "RmlUi_Renderer_DX12.h" +#include "RmlUi_Backend.h" +// #include "RmlUi_Vulkan/ShadersCompiledSPV.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef RMLUI_PLATFORM_WIN32 + #include + #ifdef RMLUI_DX_DEBUG + #include + #endif + +struct aaa { + Rml::Matrix4f m; + float m1[2]; + float m2[2]; + Rml::Vector4f m3[11]; +}; + +constexpr const char pShaderSourceText_Color[] = R"( +struct sInputData +{ + float4 inputPos : SV_Position; + float4 inputColor : COLOR; + float2 inputUV : TEXCOORD; +}; + +float4 main(const sInputData inputArgs) : SV_TARGET +{ + return inputArgs.inputColor; +} +)"; +constexpr const char pShaderSourceText_Vertex[] = R"( +struct sInputData +{ + float2 inPosition : POSITION; + float4 inColor : COLOR; + float2 inTexCoord : TEXCOORD; +}; + +struct sOutputData +{ + float4 outPosition : SV_Position; + float4 outColor : COLOR; + float2 outUV : TEXCOORD; +}; + +cbuffer ConstantBuffer : register(b0) +{ + float4x4 m_transform; + float2 m_translate; + float2 m_padding; + float4 m_padding1[11]; +}; + +sOutputData main(const sInputData inArgs) +{ + sOutputData result; + + float2 translatedPos = inArgs.inPosition + m_translate; + float4 resPos = mul(m_transform, float4(translatedPos.x, translatedPos.y, 0.0, 1.0)); + + result.outPosition = resPos; + result.outColor = inArgs.inColor; + result.outUV = inArgs.inTexCoord; + +#if defined(RMLUI_PREMULTIPLIED_ALPHA) + // Pre-multiply vertex colors with their alpha. + result.outColor.rgb = result.outColor.rgb * result.outColor.a; +#endif + + return result; +}; +)"; + +constexpr const char pShaderSourceText_Texture[] = R"( +struct sInputData +{ + float4 inputPos : SV_Position; + float4 inputColor : COLOR; + float2 inputUV : TEXCOORD; +}; + +Texture2D g_InputTexture : register(t0); + +SamplerState g_SamplerLinear : register(s0); + + +float4 main(const sInputData inputArgs) : SV_TARGET +{ + return inputArgs.inputColor * g_InputTexture.Sample(g_SamplerLinear, inputArgs.inputUV); +} +)"; + +constexpr const char pShaderSourceText_Vertex_PassThrough[] = R"( +struct sInputData +{ + float2 inPosition : POSITION; + float4 inColor : COLOR; + float2 inTexCoord : TEXCOORD; +}; + +struct sOutputData +{ + float4 outPosition : SV_Position; + float4 outColor : COLOR; + float2 outUV : TEXCOORD; +}; + +sOutputData main(const sInputData inArgs) +{ + sOutputData result; + result.outPosition = float4(inArgs.inPosition.x, inArgs.inPosition.y, 0.0f, 0.0f); + result.outColor = inArgs.inColor; + result.outUV = inArgs.inTexCoord; + + return result; +} +)"; + +constexpr const char pShaderSourceText_Pixel_Passthrough[] = R"( +struct sInputData +{ + float4 inputPos : SV_Position; + float4 inputColor : COLOR; + float2 inputUV : TEXCOORD; +}; + +Texture2D g_InputTexture : register(t0); + +SamplerState g_SamplerLinear : register(s0); + + +float4 main(const sInputData inputArgs) : SV_TARGET +{ + return g_InputTexture.Sample(g_SamplerLinear, inputArgs.inputUV); +} +)"; + +// AlignUp(314, 256) = 512 +template +static T AlignUp(T val, T alignment) +{ + return (val + alignment - (T)1) & ~(alignment - (T)1); +} + +static Rml::Colourf ConvertToColorf(Rml::ColourbPremultiplied c0) +{ + Rml::Colourf result; + for (int i = 0; i < 4; i++) + result[i] = (1.f / 255.f) * float(c0[i]); + return result; +} + +static Rml::Colourf ToPremultipliedAlpha(Rml::Colourb c0) +{ + Rml::Colourf result; + result.alpha = (1.f / 255.f) * float(c0.alpha); + result.red = (1.f / 255.f) * float(c0.red) * result.alpha; + result.green = (1.f / 255.f) * float(c0.green) * result.alpha; + result.blue = (1.f / 255.f) * float(c0.blue) * result.alpha; + return result; +} + +/// Flip vertical axis of the rectangle, and move its origin to the vertically opposite side of the viewport. +/// @note Changes coordinate system from RmlUi to OpenGL, or equivalently in reverse. +/// @note The Rectangle::Top and Rectangle::Bottom members will have reverse meaning in the returned rectangle. +static Rml::Rectanglei VerticallyFlipped(Rml::Rectanglei rect, int viewport_height) +{ + RMLUI_ASSERT(rect.Valid()); + Rml::Rectanglei flipped_rect = rect; + flipped_rect.p0.y = viewport_height - rect.p1.y; + flipped_rect.p1.y = viewport_height - rect.p0.y; + return flipped_rect; +} + +enum class ShaderGradientFunction { Linear, Radial, Conic, RepeatingLinear, RepeatingRadial, RepeatingConic }; // Must match shader definitions below. + +enum class FilterType { Invalid = 0, Passthrough, Blur, DropShadow, ColorMatrix, MaskImage }; +struct CompiledFilter { + FilterType type; + + // Passthrough + float blend_factor; + + // Blur + float sigma; + + // Drop shadow + Rml::Vector2f offset; + Rml::Colourb color; + + // ColorMatrix + Rml::Matrix4f color_matrix; +}; + +enum class CompiledShaderType { Invalid = 0, Gradient, Creation }; +struct CompiledShader { + CompiledShaderType type; + + // Gradient + ShaderGradientFunction gradient_function; + Rml::Vector2f p; + Rml::Vector2f v; + Rml::Vector stop_positions; + Rml::Vector stop_colors; + + // Shader + Rml::Vector2f dimensions; +}; + +enum class ProgramId : int { + None, + Color_Stencil_Always, + Color_Stencil_Equal, + Color_Stencil_Set, + Color_Stencil_SetInverse, + Color_Stencil_Intersect, + Color_Stencil_Disabled, + Texture_Stencil_Always, + Texture_Stencil_Equal, + Texture_Stencil_Disabled, + Gradient, + Creation, + // this is for presenting our msaa render target texture for NO MSAA RT + // if you do not correctly stuff DX12 validation will say about different + // sample count like it is expected 1 (because no MSAA) but your RT target texture was created with + // sample count = 2, so it is not correct way of using it + Passthrough, + ColorMatrix, + BlendMask, + Blur, + DropShadow, + Count +}; + +// Determines the anti-aliasing quality when creating layers. Enables better-looking visuals, especially when transforms are applied. +static constexpr int NUM_MSAA_SAMPLES = 2; + + #define RMLUI_PREMULTIPLIED_ALPHA 1 + + #define MAX_NUM_STOPS 16 + #define BLUR_SIZE 7 + #define BLUR_NUM_WEIGHTS ((BLUR_SIZE + 1) / 2) + + #define RMLUI_STRINGIFY_IMPL(x) #x + #define RMLUI_STRINGIFY(x) RMLUI_STRINGIFY_IMPL(x) + + #pragma comment(lib, "d3dcompiler.lib") + #pragma comment(lib, "d3d12.lib") + #pragma comment(lib, "dxgi.lib") + + #ifdef RMLUI_DX_DEBUG + #pragma comment(lib, "dxguid.lib") + #endif + +RenderInterface_DX12::RenderInterface_DX12(void* p_window_handle, ID3D12Device* p_user_device, IDXGISwapChain* p_user_swapchain, bool use_vsync) : + m_is_full_initialization{false}, m_is_shutdown_called{}, m_is_use_vsync{use_vsync}, m_is_use_tearing{}, m_is_scissor_was_set{}, m_width{}, + m_height{}, m_current_clip_operation{-1}, m_active_program_id{}, m_size_descriptor_heap_render_target_view{}, m_size_descriptor_heap_shaders{}, + m_p_descriptor_heap_shaders{}, m_current_back_buffer_index{} +{ + RMLUI_ASSERT(p_window_handle && "you can't pass an empty window handle! (also it must be castable to HWND)"); + RMLUI_ASSERT(p_user_device && "you can't pass an empty device!"); + RMLUI_ASSERT(p_user_swapchain && "you can't pass an empty swapchain!"); + RMLUI_ASSERT((sizeof(this->m_pipelines) / sizeof(this->m_pipelines[0])) == static_cast(ProgramId::Count) && + "you didn't update size of your variable"); + RMLUI_ASSERT((sizeof(this->m_root_signatures) / sizeof(this->m_root_signatures[0])) == static_cast(ProgramId::Count) && + "you didn't update size of your variable"); + this->m_p_window_handle = static_cast(p_window_handle); + + std::memset(this->m_pipelines, 0, sizeof(this->m_pipelines)); + std::memset(this->m_root_signatures, 0, sizeof(this->m_root_signatures)); + std::memset(this->m_constant_buffer_count_per_frame.data(), 0, sizeof(this->m_constant_buffer_count_per_frame)); + + #ifdef RMLUI_DX_DEBUG + this->m_default_shader_flags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; + #else + this->m_default_shader_flags = 0; + #endif +} + +namespace Gfx { +struct FramebufferData { +public: + FramebufferData() : + m_is_render_target{true}, m_width{}, m_height{}, m_id{-1}, m_p_texture{}, m_p_texture_depth_stencil{}, m_texture_descriptor_resource_view{}, + m_allocation_descriptor_offset{} + {} + ~FramebufferData() + { + m_id = -1; + + #ifdef RMLUI_DEBUG + RMLUI_ASSERT(this->m_p_texture == nullptr && "you must manually deallocate texture and set this field as nullptr!"); + #endif + } + + int Get_Width() const { return this->m_width; } + void Set_Width(int value) { this->m_width = value; } + + int Get_Height(void) const { return this->m_height; } + void Set_Height(int value) { this->m_height = value; } + + void Set_ID(int layer_current_size_index) { this->m_id = layer_current_size_index; } + int Get_ID(void) const { return this->m_id; } + + void Set_Texture(RenderInterface_DX12::TextureHandleType* p_texture) { this->m_p_texture = p_texture; } + RenderInterface_DX12::TextureHandleType* Get_Texture(void) const { return this->m_p_texture; } + + // ResourceView means RTV or DSV + void Set_DescriptorResourceView(const D3D12_CPU_DESCRIPTOR_HANDLE& handle) { this->m_texture_descriptor_resource_view = handle; } + const D3D12_CPU_DESCRIPTOR_HANDLE& Get_DescriptorResourceView() const { return this->m_texture_descriptor_resource_view; } + + void Set_SharedDepthStencilTexture(FramebufferData* p_data) { this->m_p_texture_depth_stencil = p_data; } + FramebufferData* Get_SharedDepthStencilTexture(void) const { return this->m_p_texture_depth_stencil; } + + D3D12MA::VirtualAllocation* Get_VirtualAllocation_Descriptor() { return &this->m_allocation_descriptor_offset; } + + bool Is_RenderTarget(void) const { return this->m_is_render_target; } + void Set_RenderTarget(bool value) { this->m_is_render_target = value; } + +private: + bool m_is_render_target; + int m_width; + int m_height; + int m_id; + RenderInterface_DX12::TextureHandleType* m_p_texture; + // this is shared texture and original pointer stored and managed at renderlayerstack class + FramebufferData* m_p_texture_depth_stencil; + D3D12_CPU_DESCRIPTOR_HANDLE m_texture_descriptor_resource_view; + // from dsv or rtv heap!! + D3D12MA::VirtualAllocation m_allocation_descriptor_offset; +}; +} // namespace Gfx + +RenderInterface_DX12::RenderInterface_DX12(void* p_window_handle, bool use_vsync) : + m_is_full_initialization{true}, m_is_shutdown_called{}, m_is_use_vsync{use_vsync}, m_is_use_tearing{}, m_is_scissor_was_set{}, + m_is_stencil_enabled{}, m_is_stencil_equal{}, m_width{}, m_height{}, m_current_clip_operation{-1}, m_active_program_id{}, + m_size_descriptor_heap_render_target_view{}, m_size_descriptor_heap_shaders{}, m_current_back_buffer_index{}, m_p_device{}, m_p_command_queue{}, + m_p_copy_queue{}, m_p_swapchain{}, m_p_command_graphics_list{}, m_p_descriptor_heap_render_target_view{}, + m_p_descriptor_heap_render_target_view_for_texture_manager{}, m_p_descriptor_heap_depth_stencil_view_for_texture_manager{}, + m_p_descriptor_heap_shaders{}, m_p_descriptor_heap_depthstencil{}, m_p_depthstencil_resource{}, m_p_backbuffer_fence{}, m_p_adapter{}, + m_p_copy_allocator{}, m_p_copy_command_list{}, m_p_allocator{}, m_p_offset_allocator_for_descriptor_heap_shaders{}, m_p_window_handle{}, + m_p_fence_event{}, m_fence_value{}, m_precompiled_fullscreen_quad_geometry{} +{ + RMLUI_ASSERT(p_window_handle && "you can't pass an empty window handle! (also it must be castable to HWND)"); + RMLUI_ASSERT(RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT >= 1 && "must be non zero!"); + RMLUI_ASSERT((sizeof(this->m_pipelines) / sizeof(this->m_pipelines[0])) == static_cast(ProgramId::Count) && + "you didn't update size of your variable"); + RMLUI_ASSERT((sizeof(this->m_root_signatures) / sizeof(this->m_root_signatures[0])) == static_cast(ProgramId::Count) && + "you didn't update size of your variable"); + + this->m_p_window_handle = static_cast(p_window_handle); + + std::memset(this->m_pipelines, 0, sizeof(this->m_pipelines)); + std::memset(this->m_root_signatures, 0, sizeof(this->m_root_signatures)); + std::memset(this->m_constant_buffer_count_per_frame.data(), 0, sizeof(this->m_constant_buffer_count_per_frame)); + + #ifdef RMLUI_DX_DEBUG + this->m_default_shader_flags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; + #else + this->m_default_shader_flags = 0; + #endif +} + +RenderInterface_DX12::~RenderInterface_DX12() +{ + RMLUI_ASSERT(this->m_is_shutdown_called == true && "something is wrong and you didn't provide a calling for shutdown method outside of class!"); + RMLUI_ASSERT(RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT >= 1 && "must be non zero!"); + + // library developers must gurantee that shutdown was called, but if it is not we gurantee that shutdown will be called in destructor + if (!this->m_is_shutdown_called) + { + this->Shutdown(); + } +} + +// TODO: finish complex checking of all managers not just only device +RenderInterface_DX12::operator bool() const +{ + return !!(this->m_p_device); +} + +void RenderInterface_DX12::SetViewport(int viewport_width, int viewport_height) +{ + if (this->m_width != viewport_width || this->m_height != viewport_height) + { + this->Flush(); + + if (this->m_p_depthstencil_resource) + { + this->Destroy_Resource_DepthStencil(); + } + + m_width = viewport_width; + m_height = viewport_height; + + this->m_projection = + Rml::Matrix4f::ProjectOrtho(0, static_cast(viewport_width), static_cast(viewport_height), 0, -10000, 10000); + + if (this->m_p_swapchain) + { + // TODO: think how to tell user for recreating swapchain on his side, providing callback??? + if (this->m_is_full_initialization) + { + this->Destroy_Resources_DependentOnSize(); + + for (int i = 0; i < RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT; ++i) + { + this->m_backbuffers_fence_values[i] = this->m_backbuffers_fence_values[this->m_current_back_buffer_index]; + } + + DXGI_SWAP_CHAIN_DESC desc; + RMLUI_DX_ASSERTMSG(this->m_p_swapchain->GetDesc(&desc), "failed to GetDesc"); + RMLUI_DX_ASSERTMSG(this->m_p_swapchain->ResizeBuffers(static_cast(RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT), + static_cast(this->m_width), static_cast(this->m_height), desc.BufferDesc.Format, desc.Flags), + "failed to ResizeBuffers"); + + this->m_current_back_buffer_index = this->m_p_swapchain->GetCurrentBackBufferIndex(); + + this->Create_Resources_DependentOnSize(); + this->Create_Resource_DepthStencil(); + } + } + } +} + +void RenderInterface_DX12::BeginFrame() +{ + // this->Update_PendingForDeletion_Texture(); + this->Update_PendingForDeletion_Geometry(); + + auto* p_command_allocator = this->m_backbuffers_allocators.at(this->m_current_back_buffer_index); + + RMLUI_ASSERT(p_command_allocator && "should be allocated and initialized! Probably early calling"); + RMLUI_ASSERT(this->m_p_command_graphics_list && "must be allocated and initialized! Probably early calling"); + + if (p_command_allocator) + { + p_command_allocator->Reset(); + } + + if (this->m_p_command_graphics_list) + { + this->m_p_command_graphics_list->Reset(p_command_allocator, nullptr); + + CD3DX12_CPU_DESCRIPTOR_HANDLE handle_rtv(this->m_p_descriptor_heap_render_target_view->GetCPUDescriptorHandleForHeapStart(), + this->m_current_back_buffer_index, this->m_size_descriptor_heap_render_target_view); + + CD3DX12_CPU_DESCRIPTOR_HANDLE handle_dsv(this->m_p_descriptor_heap_depthstencil->GetCPUDescriptorHandleForHeapStart()); + + // TODO: я не должен устанавливать здесь backbuffer который из свапчейна, а смотреть код GL3 реализации + // this->m_p_command_graphics_list->OMSetRenderTargets(1, &handle_rtv, FALSE, &handle_dsv); + + D3D12_RECT scissor; + + /* + if (!this->m_is_scissor_was_set) + { + scissor.left = 0; + scissor.top = 0; + scissor.right = this->m_width; + scissor.bottom = this->m_height; + } + else + { + RMLUI_ASSERT(this->m_scissor.Valid() && + "must be valid! it means that SetScissor was called and we apply these changes and scissor always set valid region, if it was called " + "by makeinvalid we just use default (directx-12 forced for using scissor test always so we always have to set scissors)"); + + scissor.left = this->m_scissor.Left(); + scissor.right = this->m_width; + scissor.bottom = this->m_height; + scissor.top = this->m_height - this->m_scissor.Bottom(); + this->m_is_scissor_was_set = false; + } + */ + + if (this->m_is_scissor_was_set) + { + this->m_is_scissor_was_set = false; + } + + // + // this->m_is_scissor_was_set = false; + this->SetTransform(nullptr); + + this->m_manager_render_layer.BeginFrame(this->m_width, this->m_height); + + auto* p_handle_rtv = &this->m_manager_render_layer.GetTopLayer().Get_DescriptorResourceView(); + auto* p_handle_dsv = &this->m_manager_render_layer.GetTopLayer().Get_SharedDepthStencilTexture()->Get_DescriptorResourceView(); + + this->m_p_command_graphics_list->OMSetRenderTargets(1, p_handle_rtv, FALSE, p_handle_dsv); + + D3D12_VIEWPORT viewport{}; + viewport.Height = static_cast(this->m_height); + viewport.Width = static_cast(this->m_width); + viewport.MaxDepth = 1.0f; + this->m_p_command_graphics_list->RSSetViewports(1, &viewport); + + this->UseProgram(ProgramId::None); + this->m_is_stencil_equal = false; + // this->m_program_state_transform_dirty.set(); + } +} + +void RenderInterface_DX12::EndFrame() +{ + auto* p_resource_backbuffer = this->m_backbuffers_resources.at(this->m_current_back_buffer_index); + + RMLUI_ASSERT(p_resource_backbuffer && "should be allocated and initialized! Probably early calling"); + RMLUI_ASSERT(this->m_p_command_graphics_list && "Must be allocated and initialzied. Probably early calling!"); + RMLUI_ASSERT(this->m_p_command_queue && "Must be allocated and initialzied. Probably early calling!"); + + // TODO: здесь я должен выставлять backbuffer из свапчейна и при этом делать резолв для последнего постпроцесса (см. код реализации GL3) + if (this->m_p_command_graphics_list) + { + CD3DX12_RESOURCE_BARRIER backbuffer_barrier_from_rt_to_present = + CD3DX12_RESOURCE_BARRIER::Transition(p_resource_backbuffer, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT); + + // делаем резовл из лейра для постпроцесса + // делаем OMSetRenderTargets на наш swapchain's backbuffer + // рендеринг квад и натягиваем результат постпроцесса (резолв) + // закрываем запись команд делаем презент + + const Gfx::FramebufferData& fb_active = this->m_manager_render_layer.GetTopLayer(); + const Gfx::FramebufferData& fb_postprocess = this->m_manager_render_layer.GetPostprocessPrimary(); + + ID3D12Resource* p_msaa_texture{}; + ID3D12Resource* p_postprocess_texture{}; + + TextureHandleType* p_handle_postprocess_texture = fb_postprocess.Get_Texture(); + + if (fb_active.Get_Texture()) + { + TextureHandleType* p_resource = fb_active.Get_Texture(); + + if (p_resource->Get_Info().Get_BufferIndex() != -1) + { + p_msaa_texture = static_cast(p_resource->Get_Resource()); + } + else + { + D3D12MA::Allocation* p_allocation = static_cast(p_resource->Get_Resource()); + p_msaa_texture = p_allocation->GetResource(); + } + } + + if (fb_postprocess.Get_Texture()) + { + TextureHandleType* p_resource = fb_postprocess.Get_Texture(); + + if (p_resource->Get_Info().Get_BufferIndex() != -1) + { + p_postprocess_texture = static_cast(p_resource->Get_Resource()); + } + else + { + D3D12MA::Allocation* p_allocation = static_cast(p_resource->Get_Resource()); + p_postprocess_texture = p_allocation->GetResource(); + } + } + + RMLUI_ASSERT(p_msaa_texture && "can't be, must be a valid texture!"); + RMLUI_ASSERT(p_postprocess_texture && "can't be, must be a valid texture!"); + RMLUI_ASSERT(p_handle_postprocess_texture && "must be valid!"); + + CD3DX12_RESOURCE_BARRIER barriers[2]{}; + barriers[0] = CD3DX12_RESOURCE_BARRIER::Transition(p_msaa_texture, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_RESOLVE_SOURCE); + + barriers[1] = + CD3DX12_RESOURCE_BARRIER::Transition(p_postprocess_texture, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_RESOLVE_DEST); + + CD3DX12_RESOURCE_BARRIER barrier_transition_from_msaa_resolve_source_to_rt = + CD3DX12_RESOURCE_BARRIER::Transition(p_msaa_texture, D3D12_RESOURCE_STATE_RESOLVE_SOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET); + + this->m_p_command_graphics_list->ResourceBarrier(2, barriers); + + this->m_p_command_graphics_list->ResolveSubresource(p_postprocess_texture, 0, p_msaa_texture, 0, + RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT); + + this->m_p_command_graphics_list->ResourceBarrier(1, &barrier_transition_from_msaa_resolve_source_to_rt); + + CD3DX12_RESOURCE_BARRIER offscreen_texture_barrier_for_shader = CD3DX12_RESOURCE_BARRIER::Transition(p_postprocess_texture, + D3D12_RESOURCE_STATE_RESOLVE_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + CD3DX12_RESOURCE_BARRIER restore_state_of_postprocess_texture_return_to_rt = CD3DX12_RESOURCE_BARRIER::Transition(p_postprocess_texture, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET); + + this->m_p_command_graphics_list->ResourceBarrier(1, &offscreen_texture_barrier_for_shader); + + CD3DX12_CPU_DESCRIPTOR_HANDLE handle_rtv(this->m_p_descriptor_heap_render_target_view->GetCPUDescriptorHandleForHeapStart(), + this->m_current_back_buffer_index, this->m_size_descriptor_heap_render_target_view); + + this->m_p_command_graphics_list->OMSetRenderTargets(1, &handle_rtv, FALSE, nullptr); + + this->UseProgram(ProgramId::Passthrough); + D3D12_GPU_DESCRIPTOR_HANDLE srv_handle; + srv_handle.ptr = this->m_p_descriptor_heap_shaders->GetGPUDescriptorHandleForHeapStart().ptr + + p_handle_postprocess_texture->Get_Allocation_DescriptorHeap().offset; + + this->m_p_command_graphics_list->SetGraphicsRootDescriptorTable(0, srv_handle); + + this->DrawFullscreenQuad(); + + this->m_manager_render_layer.EndFrame(); + + this->m_p_command_graphics_list->ResourceBarrier(1, &backbuffer_barrier_from_rt_to_present); + this->m_p_command_graphics_list->ResourceBarrier(1, &restore_state_of_postprocess_texture_return_to_rt); + + RMLUI_DX_ASSERTMSG(this->m_p_command_graphics_list->Close(), "failed to Close"); + + ID3D12CommandList* const lists[] = {this->m_p_command_graphics_list}; + + if (this->m_p_command_queue) + { + this->m_p_command_queue->ExecuteCommandLists(_countof(lists), lists); + } + + auto fence_value = this->Signal(); + + UINT sync_interval = this->m_is_use_vsync ? 1 : 0; + UINT present_flags = (this->m_is_use_tearing && !this->m_is_use_vsync) ? DXGI_PRESENT_ALLOW_TEARING : 0; + + RMLUI_DX_ASSERTMSG(this->m_p_swapchain->Present(sync_interval, present_flags), "failed to Present"); + + #ifdef RMLUI_DX_DEBUG + if (this->m_current_back_buffer_index == 0) + { + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12] current allocated constant buffers per draw (for frame[%d]): %zu", + this->m_current_back_buffer_index, this->m_constant_buffer_count_per_frame[m_current_back_buffer_index]); + } + #endif + + this->m_constant_buffer_count_per_frame[0] = 0; + + this->m_current_back_buffer_index = (uint32_t)this->m_p_swapchain->GetCurrentBackBufferIndex(); + + this->WaitForFenceValue(fence_value); + } +} + +void RenderInterface_DX12::Clear() +{ + RMLUI_ASSERT(this->m_p_command_graphics_list && "early calling prob!"); + + auto* p_backbuffer = this->m_backbuffers_resources.at(this->m_current_back_buffer_index); + + CD3DX12_RESOURCE_BARRIER barrier = + CD3DX12_RESOURCE_BARRIER::Transition(p_backbuffer, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET); + + this->m_p_command_graphics_list->ResourceBarrier(1, &barrier); + + FLOAT clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f}; + CD3DX12_CPU_DESCRIPTOR_HANDLE rtv(this->m_p_descriptor_heap_render_target_view->GetCPUDescriptorHandleForHeapStart(), + this->m_current_back_buffer_index, this->m_size_descriptor_heap_render_target_view); + + this->m_p_command_graphics_list->ClearDepthStencilView(this->m_p_descriptor_heap_depthstencil->GetCPUDescriptorHandleForHeapStart(), + D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr); + + this->m_p_command_graphics_list->ClearRenderTargetView(rtv, clear_color, 0, nullptr); +} + +void RenderInterface_DX12::RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture) +{ + GeometryHandleType* p_handle_geometry = reinterpret_cast(geometry); + TextureHandleType* p_handle_texture{}; + + RMLUI_ASSERT(p_handle_geometry && "expected valid pointer!"); + + ConstantBufferType* p_constant_buffer{}; + if (texture == TexturePostprocess) + {} + else if (texture) + { + p_handle_texture = reinterpret_cast(texture); + RMLUI_ASSERT(p_handle_texture && "expected valid pointer!"); + this->m_p_command_graphics_list->OMSetStencilRef(1); + + if (this->m_is_stencil_enabled) + { + if (this->m_is_stencil_equal) + { + this->UseProgram(ProgramId::Texture_Stencil_Equal); + } + else + { + this->UseProgram(ProgramId::Texture_Stencil_Always); + } + } + else + { + this->UseProgram(ProgramId::Texture_Stencil_Disabled); + } + + // this->SubmitTransformUniform(p_handle_geometry->Get_ConstantBuffer(), translation); + + p_constant_buffer = this->Get_ConstantBuffer(0); + + this->SubmitTransformUniform(*p_constant_buffer, translation); + + if (texture != TextureEnableWithoutBinding) + { + D3D12_GPU_DESCRIPTOR_HANDLE srv_handle; + srv_handle.ptr = this->m_p_descriptor_heap_shaders->GetGPUDescriptorHandleForHeapStart().ptr + + p_handle_texture->Get_Allocation_DescriptorHeap().offset; + + this->m_p_command_graphics_list->SetGraphicsRootDescriptorTable(1, srv_handle); + } + } + else + { + if (this->m_current_clip_operation == -1) + { + if (this->m_is_stencil_enabled) + { + if (this->m_is_stencil_equal) + { + this->UseProgram(ProgramId::Color_Stencil_Equal); + } + else + { + this->UseProgram(ProgramId::Color_Stencil_Always); + } + } + else + { + this->UseProgram(ProgramId::Color_Stencil_Disabled); + } + } + else if (this->m_current_clip_operation == static_cast(Rml::ClipMaskOperation::Intersect)) + { + if (this->m_is_stencil_enabled) + { + this->UseProgram(ProgramId::Color_Stencil_Intersect); + } + } + else if (this->m_current_clip_operation == static_cast(Rml::ClipMaskOperation::Set)) + { + if (this->m_is_stencil_enabled) + { + this->UseProgram(ProgramId::Color_Stencil_Set); + } + } + else if (this->m_current_clip_operation == static_cast(Rml::ClipMaskOperation::SetInverse)) + { + if (this->m_is_stencil_enabled) + { + this->UseProgram(ProgramId::Color_Stencil_SetInverse); + } + } + else + { + RMLUI_ASSERT(!"not reached code point, something is missing or corrupted data"[0]); + } + + // this->SubmitTransformUniform(p_handle_geometry->Get_ConstantBuffer(), translation); + p_constant_buffer = this->Get_ConstantBuffer(0); + + this->SubmitTransformUniform(*p_constant_buffer, translation); + } + + if (!this->m_is_scissor_was_set) + { + D3D12_RECT scissor; + scissor.left = 0; + scissor.top = 0; + scissor.right = this->m_width; + scissor.bottom = this->m_height; + + this->m_p_command_graphics_list->RSSetScissorRects(1, &scissor); + } + + if (p_constant_buffer) + { + auto* p_dx_constant_buffer = this->m_manager_buffer.Get_BufferByIndex(p_constant_buffer->Get_AllocInfo().Get_BufferIndex()); + RMLUI_ASSERT(p_dx_constant_buffer && "must be valid!"); + + if (p_dx_constant_buffer) + { + auto* p_dx_resource = p_dx_constant_buffer->GetResource(); + + RMLUI_ASSERT(p_dx_resource && "must be valid!"); + + if (p_dx_resource) + { + this->m_p_command_graphics_list->SetGraphicsRootConstantBufferView(0, + p_dx_resource->GetGPUVirtualAddress() + p_constant_buffer->Get_AllocInfo().Get_Offset()); + } + } + + auto* p_dx_buffer_vertex = this->m_manager_buffer.Get_BufferByIndex(p_handle_geometry->Get_InfoVertex().Get_BufferIndex()); + + RMLUI_ASSERT(p_dx_buffer_vertex && "must be valid!"); + + this->m_p_command_graphics_list->IASetPrimitiveTopology(D3D12_PRIMITIVE_TOPOLOGY::D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + if (p_dx_buffer_vertex) + { + auto* p_dx_resource = p_dx_buffer_vertex->GetResource(); + + RMLUI_ASSERT(p_dx_resource && "must be valid!"); + + if (p_dx_resource) + { + D3D12_VERTEX_BUFFER_VIEW view_vertex_buffer = {}; + + view_vertex_buffer.BufferLocation = p_dx_resource->GetGPUVirtualAddress() + p_handle_geometry->Get_InfoVertex().Get_Offset(); + view_vertex_buffer.StrideInBytes = sizeof(Rml::Vertex); + view_vertex_buffer.SizeInBytes = static_cast(p_handle_geometry->Get_InfoVertex().Get_Size()); + + this->m_p_command_graphics_list->IASetVertexBuffers(0, 1, &view_vertex_buffer); + } + } + + auto* p_dx_buffer_index = this->m_manager_buffer.Get_BufferByIndex(p_handle_geometry->Get_InfoIndex().Get_BufferIndex()); + + RMLUI_ASSERT(p_dx_buffer_index && "must be valid!"); + + if (p_dx_buffer_index) + { + auto* p_dx_resource = p_dx_buffer_index->GetResource(); + + RMLUI_ASSERT(p_dx_resource && "must be valid!"); + + if (p_dx_resource) + { + D3D12_INDEX_BUFFER_VIEW view_index_buffer = {}; + + view_index_buffer.BufferLocation = p_dx_resource->GetGPUVirtualAddress() + p_handle_geometry->Get_InfoIndex().Get_Offset(); + view_index_buffer.Format = DXGI_FORMAT::DXGI_FORMAT_R32_UINT; + view_index_buffer.SizeInBytes = p_handle_geometry->Get_InfoIndex().Get_Size(); + + this->m_p_command_graphics_list->IASetIndexBuffer(&view_index_buffer); + } + } + + this->m_p_command_graphics_list->DrawIndexedInstanced(p_handle_geometry->Get_NumIndecies(), 1, 0, 0, 0); + } +} + +Rml::CompiledGeometryHandle RenderInterface_DX12::CompileGeometry(Rml::Span vertices, Rml::Span indices) +{ + RMLUI_ZoneScopedN("DirectX 12 - CompileGeometry"); + + GeometryHandleType* p_handle = new GeometryHandleType(); + + if (p_handle) + { + this->m_manager_buffer.Alloc_Vertex(vertices.data(), static_cast(vertices.size()), sizeof(Rml::Vertex), p_handle); + this->m_manager_buffer.Alloc_Index(indices.data(), static_cast(indices.size()), sizeof(int), p_handle); + // p_handle->Get_ConstantBuffer().Set_AllocInfo(this->m_manager_buffer.Alloc_ConstantBuffer(&p_handle->Get_ConstantBuffer(), 72)); + } + + return reinterpret_cast(p_handle); +} + +void RenderInterface_DX12::ReleaseGeometry(Rml::CompiledGeometryHandle geometry) +{ + GeometryHandleType* p_handle = reinterpret_cast(geometry); + this->m_pending_for_deletion_geometry.push_back(p_handle); +} + +void RenderInterface_DX12::EnableScissorRegion(bool enable) +{ + if (!enable) + { + SetScissor(Rml::Rectanglei::MakeInvalid(), false); + } +} + +void RenderInterface_DX12::SetScissorRegion(Rml::Rectanglei region) +{ + SetScissor(Rml::Rectanglei::FromPositionSize({region.Left(), region.Right()}, {region.Width(), region.Height()})); +} + +void RenderInterface_DX12::EnableClipMask(bool enable) +{ + this->m_is_stencil_enabled = enable; +} + +void RenderInterface_DX12::RenderToClipMask(Rml::ClipMaskOperation mask_operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) +{ + RMLUI_ASSERT(this->m_is_stencil_enabled && "must be enabled!"); + + const bool clear_stencil = (mask_operation == Rml::ClipMaskOperation::Set || mask_operation == Rml::ClipMaskOperation::SetInverse); + + if (clear_stencil) + { + this->m_p_command_graphics_list->ClearDepthStencilView(this->m_p_descriptor_heap_depthstencil->GetCPUDescriptorHandleForHeapStart(), + D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr); + } + + switch (mask_operation) + { + case Rml::ClipMaskOperation::Set: + { + this->m_current_clip_operation = static_cast(Rml::ClipMaskOperation::Set); + // this->m_p_command_graphics_list->OMSetStencilRef(1); + break; + } + case Rml::ClipMaskOperation::SetInverse: + { + this->m_current_clip_operation = static_cast(Rml::ClipMaskOperation::SetInverse); + // this->m_p_command_graphics_list->OMSetStencilRef(0); + break; + } + case Rml::ClipMaskOperation::Intersect: + { + this->m_current_clip_operation = static_cast(Rml::ClipMaskOperation::Intersect); + break; + } + } + + RenderGeometry(geometry, translation, {}); + + this->m_is_stencil_equal = true; + this->m_current_clip_operation = -1; +} + +// Set to byte packing, or the compiler will expand our struct, which means it won't read correctly from file + #pragma pack(1) +struct TGAHeader { + char idLength; + char colourMapType; + char dataType; + short int colourMapOrigin; + short int colourMapLength; + char colourMapDepth; + short int xOrigin; + short int yOrigin; + short int width; + short int height; + char bitsPerPixel; + char imageDescriptor; +}; + // Restore packing + #pragma pack() + +static int nGlobalID{}; +Rml::TextureHandle RenderInterface_DX12::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) +{ + Rml::FileInterface* file_interface = Rml::GetFileInterface(); + Rml::FileHandle file_handle = file_interface->Open(source); + if (!file_handle) + { + return false; + } + + file_interface->Seek(file_handle, 0, SEEK_END); + size_t buffer_size = file_interface->Tell(file_handle); + file_interface->Seek(file_handle, 0, SEEK_SET); + + if (buffer_size <= sizeof(TGAHeader)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Texture file size is smaller than TGAHeader, file is not a valid TGA image."); + file_interface->Close(file_handle); + return false; + } + + using Rml::byte; + Rml::UniquePtr buffer(new byte[buffer_size]); + file_interface->Read(buffer.get(), buffer_size, file_handle); + file_interface->Close(file_handle); + + TGAHeader header; + memcpy(&header, buffer.get(), sizeof(TGAHeader)); + + int color_mode = header.bitsPerPixel / 8; + const size_t image_size = header.width * header.height * 4; // We always make 32bit textures + + if (header.dataType != 2) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported."); + return false; + } + + // Ensure we have at least 3 colors + if (color_mode < 3) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported."); + return false; + } + + const byte* image_src = buffer.get() + sizeof(TGAHeader); + Rml::UniquePtr image_dest_buffer(new byte[image_size]); + byte* image_dest = image_dest_buffer.get(); + + // Targa is BGR, swap to RGB, flip Y axis, and convert to premultiplied alpha. + for (long y = 0; y < header.height; y++) + { + long read_index = y * header.width * color_mode; + long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * 4; + for (long x = 0; x < header.width; x++) + { + image_dest[write_index] = image_src[read_index + 2]; + image_dest[write_index + 1] = image_src[read_index + 1]; + image_dest[write_index + 2] = image_src[read_index]; + if (color_mode == 4) + { + const byte alpha = image_src[read_index + 3]; + for (size_t j = 0; j < 3; j++) + image_dest[write_index + j] = byte((image_dest[write_index + j] * alpha) / 255); + image_dest[write_index + 3] = alpha; + } + else + image_dest[write_index + 3] = 255; + + write_index += 4; + read_index += color_mode; + } + } + + texture_dimensions.x = header.width; + texture_dimensions.y = header.height; + + return GenerateTexture({image_dest, image_size}, texture_dimensions); +} + +Rml::TextureHandle RenderInterface_DX12::GenerateTexture(Rml::Span source_data, Rml::Vector2i source_dimensions) +{ + RMLUI_ASSERTMSG(source_data.data(), "must be valid source"); + RMLUI_ASSERTMSG(this->m_p_allocator, "backend requires initialized allocator, but it is not initialized"); + + int width = source_dimensions.x; + int height = source_dimensions.y; + + RMLUI_ASSERTMSG(width > 0, "width is less or equal to 0"); + RMLUI_ASSERTMSG(height > 0, "height is less or equal to 0"); + + D3D12_RESOURCE_DESC desc_texture = {}; + desc_texture.MipLevels = 1; + desc_texture.DepthOrArraySize = 1; + desc_texture.Format = RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT; + desc_texture.Width = width; + desc_texture.Height = height; + desc_texture.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + desc_texture.SampleDesc.Count = 1; + desc_texture.SampleDesc.Quality = 0; + + TextureHandleType* p_resource = new TextureHandleType(); + + #ifdef RMLUI_DX_DEBUG + if (p_resource) + { + p_resource->Set_ResourceName(std::to_string(nGlobalID)); + ++nGlobalID; + } + #endif + + this->m_manager_texture.Alloc_Texture(desc_texture, p_resource, source_data.data() + #ifdef RMLUI_DX_DEBUG + , + p_resource->Get_ResourceName() + #endif + ); + + return reinterpret_cast(p_resource); +} + +void RenderInterface_DX12::ReleaseTexture(Rml::TextureHandle texture_handle) +{ + TextureHandleType* p_texture = reinterpret_cast(texture_handle); + // this->m_pending_for_deletion_textures.push_back(p_texture); + + if (p_texture) + { + #ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::LT_DEBUG, "Destroyed texture: [%s]", p_texture->Get_ResourceName().c_str()); + #endif + + this->m_manager_texture.Free_Texture(p_texture); + + delete p_texture; + } +} + +void RenderInterface_DX12::SetTransform(const Rml::Matrix4f* transform) +{ + this->m_constant_buffer_data_transform = (transform ? (this->m_projection * (*transform)) : this->m_projection); + // this->m_program_state_transform_dirty.set(); +} + +RenderInterface_DX12::RenderLayerStack::RenderLayerStack() : + m_width{}, m_height{}, m_layers_size{}, m_p_manager_texture{}, m_p_manager_buffer{}, m_p_device{}, m_p_depth_stencil{} +{ + this->m_fb_postprocess.resize(4); + + // in order to prevent calling dtor when doing push_back on m_fb_layers + // we need to reserve memory, like how much we do expect elements in array (vector) + // otherwise you will get validation assert in dtor of FramebufferData struct and + // that validation supposed to be for memory leaks or wrong resource handling (like you forgot to delete resource somehow) + // if you didn't get it check this: https://en.cppreference.com/w/cpp/container/vector/reserve + + // otherwise if your default implementation requires more layers by default, thus we have a field at compile-time (or at runtime as dynamic extension) + // RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_RESERVECOUNT_OF_RENDERSTACK_LAYERS + this->m_fb_layers.reserve(RMLUI_RENDER_BACKEND_FIELD_RESERVECOUNT_OF_RENDERSTACK_LAYERS); + this->m_p_depth_stencil = new Gfx::FramebufferData(); + this->m_p_depth_stencil->Set_RenderTarget(false); +} + +RenderInterface_DX12::RenderLayerStack::~RenderLayerStack() +{ + this->DestroyFramebuffers(); + + this->m_p_device = nullptr; + this->m_p_manager_buffer = nullptr; + this->m_p_manager_texture = nullptr; + + if (this->m_p_depth_stencil) + { + delete this->m_p_depth_stencil; + this->m_p_depth_stencil = nullptr; + } +} + +void RenderInterface_DX12::RenderLayerStack::Initialize(RenderInterface_DX12* p_owner) +{ + RMLUI_ASSERT(p_owner && "you must pass a valid pointer of RenderInterface_DX12 instance"); + + #ifdef RMLUI_DEBUG + if (p_owner) + { + RMLUI_ASSERT(p_owner->Get_Fence() && "you didn't initialize even fence or you call this method too early!"); + RMLUI_ASSERT( + p_owner->Get_TextureManager().Is_Initialized() && "early call! you must initialize texture memory manager before calling this method!"); + RMLUI_ASSERT( + p_owner->Get_BufferManager().Is_Initialized() && "early call! you must initialize buffer memory manager before calling this method!"); + RMLUI_ASSERT(p_owner->Get_Device() && "you must initialize DirectX 12 before calling this method! device is nullptr"); + } + #endif + + if (p_owner) + { + this->m_p_device = p_owner->Get_Device(); + this->m_p_manager_buffer = &p_owner->Get_BufferManager(); + this->m_p_manager_texture = &p_owner->Get_TextureManager(); + } +} + +Rml::LayerHandle RenderInterface_DX12::RenderLayerStack::PushLayer() +{ + RMLUI_ASSERT(this->m_layers_size <= static_cast(this->m_fb_layers.size()) && "overflow of layers!"); + RMLUI_ASSERT(this->m_p_depth_stencil && "must be valid!"); + + if (this->m_layers_size == static_cast(this->m_fb_layers.size())) + { + /* + // All framebuffers should share a single stencil buffer. + GLuint shared_depth_stencil = (fb_layers.empty() ? 0 : fb_layers.front().depth_stencil_buffer); + + fb_layers.push_back(Gfx::FramebufferData{}); + Gfx::CreateFramebuffer(fb_layers.back(), width, height, NUM_MSAA_SAMPLES, Gfx::FramebufferAttachment::DepthStencil, shared_depth_stencil); + */ + + if (this->m_p_depth_stencil->Get_Texture() == nullptr) + { + this->CreateFramebuffer(this->m_p_depth_stencil, m_width, m_height, RMLUI_RENDER_BACKEND_FIELD_MSAA_SAMPLE_COUNT, true); + } + + this->m_fb_layers.push_back(Gfx::FramebufferData{}); + auto* p_buffer = &this->m_fb_layers.back(); + this->CreateFramebuffer(p_buffer, m_width, m_height, RMLUI_RENDER_BACKEND_FIELD_MSAA_SAMPLE_COUNT, false); + + #ifdef RMLUI_DX_DEBUG + wchar_t framebuffer_name[32]; + wsprintf(framebuffer_name, L"framebuffer (layer): %d", this->m_layers_size); + int index_buffer = p_buffer->Get_Texture()->Get_Info().Get_BufferIndex(); + + if (index_buffer == -1) + { + D3D12MA::Allocation* p_committed_resource = static_cast(p_buffer->Get_Texture()->Get_Resource()); + + if (p_committed_resource->GetResource()) + { + p_committed_resource->GetResource()->SetName(framebuffer_name); + } + } + else + { + ID3D12Resource* p_casted = static_cast(p_buffer->Get_Texture()->Get_Resource()); + + if (p_casted) + { + p_casted->SetName(framebuffer_name); + } + } + #endif + + p_buffer->Set_SharedDepthStencilTexture(this->m_p_depth_stencil); + } + + ++this->m_layers_size; + + return GetTopLayerHandle(); +} + +void RenderInterface_DX12::RenderLayerStack::PopLayer() +{ + RMLUI_ASSERT(this->m_layers_size > 0 && "calculations are wrong, debug your code please!"); + this->m_layers_size -= 1; +} + +const Gfx::FramebufferData& RenderInterface_DX12::RenderLayerStack::GetLayer(Rml::LayerHandle layer) const +{ + RMLUI_ASSERT(static_cast(layer) < static_cast(this->m_layers_size) && + "overflow or not correct calculation or something is broken, debug the code!"); + return this->m_fb_layers.at(static_cast(layer)); +} + +const Gfx::FramebufferData& RenderInterface_DX12::RenderLayerStack::GetTopLayer() const +{ + RMLUI_ASSERT(this->m_layers_size > 0 && "early calling!"); + return this->m_fb_layers[this->m_layers_size - 1]; +} + +const Gfx::FramebufferData& RenderInterface_DX12::RenderLayerStack::Get_SharedDepthStencil() +{ + RMLUI_ASSERT(this->m_p_depth_stencil && "early calling!"); + return *this->m_p_depth_stencil; +} + +Rml::LayerHandle RenderInterface_DX12::RenderLayerStack::GetTopLayerHandle() const +{ + RMLUI_ASSERT(m_layers_size > 0 && "early calling or something is broken!"); + return static_cast(m_layers_size - 1); +} + +void RenderInterface_DX12::RenderLayerStack::SwapPostprocessPrimarySecondary() +{ + std::swap(this->m_fb_postprocess[0], this->m_fb_postprocess[1]); +} + +void RenderInterface_DX12::RenderLayerStack::BeginFrame(int width_new, int height_new) +{ + RMLUI_ASSERT(this->m_layers_size == 0 && "something is wrong and you forgot to clear/delete something!"); + + if (this->m_width != width_new || this->m_height != height_new) + { + this->m_width = width_new; + this->m_height = height_new; + + this->DestroyFramebuffers(); + } + + this->PushLayer(); +} + +void RenderInterface_DX12::RenderLayerStack::EndFrame() +{ + RMLUI_ASSERT(this->m_layers_size == 1 && "order is wrong or something is broken!"); + this->PopLayer(); +} + +void RenderInterface_DX12::RenderLayerStack::DestroyFramebuffers() +{ + RMLUI_ASSERTMSG(this->m_layers_size == 0, "Do not call this during frame rendering, that is, between BeginFrame() and EndFrame()."); + RMLUI_ASSERT(this->m_p_manager_texture && "you must initialize this manager or it is a early calling or it is a late calling, debug please!"); + + // deleting shared depth stencil + + if (this->m_p_manager_texture) + { + if (this->m_p_depth_stencil->Get_Texture()) + { + this->m_p_manager_texture->Free_Texture(this->m_p_depth_stencil); + } + } + + for (auto& fb : this->m_fb_layers) + { + if (fb.Get_Texture()) + { + this->DestroyFramebuffer(&fb); + } + } + + this->m_fb_layers.clear(); + + for (auto& fb : this->m_fb_postprocess) + { + if (fb.Get_Texture()) + { + this->DestroyFramebuffer(&fb); + } + } +} + +const Gfx::FramebufferData& RenderInterface_DX12::RenderLayerStack::EnsureFramebufferPostprocess(int index) +{ + RMLUI_ASSERT(index < static_cast(this->m_fb_postprocess.size()) && "overflow or wrong calculation, debug the code!"); + + Gfx::FramebufferData& fb = this->m_fb_postprocess.at(index); + + if (!fb.Get_Texture()) + { + this->CreateFramebuffer(&fb, this->m_width, this->m_height, 1, false); + + #ifdef RMLUI_DX_DEBUG + if (fb.Get_Texture()) + { + wchar_t framebuffer_name[32]; + wsprintf(framebuffer_name, L"framebuffer (postprocess): %d", index); + int buffer_index = fb.Get_Texture()->Get_Info().Get_BufferIndex(); + if (buffer_index == -1) + { + D3D12MA::Allocation* p_alloc = static_cast(fb.Get_Texture()->Get_Resource()); + + if (p_alloc) + { + ID3D12Resource* p_resource = p_alloc->GetResource(); + + if (p_resource) + { + p_resource->SetName(framebuffer_name); + } + } + } + else + { + ID3D12Resource* p_placement_resource = static_cast(fb.Get_Texture()->Get_Resource()); + + if (p_placement_resource) + { + p_placement_resource->SetName(framebuffer_name); + } + } + } + #endif + } + + return fb; +} + +// TODO: add argument for depth stencil thing!!!! +void RenderInterface_DX12::RenderLayerStack::CreateFramebuffer(Gfx::FramebufferData* p_result, int width, int height, int sample_count, + bool is_depth_stencil) +{ + RMLUI_ASSERT(p_result && "you must pass a valid pointer!"); + RMLUI_ASSERT(sample_count > 0 && "you must pass a valid value! it must be positive"); + + RMLUI_ASSERT(width > 0 && "pass a valid width!"); + RMLUI_ASSERT(height > 0 && "pass a valid height"); + RMLUI_ASSERT(this->m_p_manager_texture && "you must register manager texture befor calling this method (manager texture is nullptr)"); + if (this->m_p_manager_texture) + { + D3D12_RESOURCE_DESC desc_texture = {}; + + DXGI_FORMAT format{}; + + if (is_depth_stencil) + format = RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT; + else + format = RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT; + + desc_texture.MipLevels = 1; + desc_texture.DepthOrArraySize = 1; + desc_texture.Format = format; + desc_texture.Width = static_cast(width); + desc_texture.Height = static_cast(height); + desc_texture.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + desc_texture.SampleDesc.Count = sample_count; + desc_texture.SampleDesc.Quality = 0; + + TextureHandleType* p_resource = new TextureHandleType(); + + RMLUI_ASSERT(p_resource && "[OS][ERROR] not enough memory for allocation!"); + + #ifdef RMLUI_DX_DEBUG + if (p_resource) + { + const char* pWhatTypeOfTexture{}; + + constexpr const char* kHardcodedRenderTargetName = "[RT] "; + constexpr const char* kHardcodedDepthStencilName = "[DS] "; + + if (is_depth_stencil) + pWhatTypeOfTexture = kHardcodedDepthStencilName; + else + pWhatTypeOfTexture = kHardcodedRenderTargetName; + + Rml::String msaa_info; + + if (sample_count > 1) + msaa_info = " | MSAA[" + std::to_string(sample_count) + "]"; + + p_resource->Set_ResourceName(pWhatTypeOfTexture + std::string("Render2Texture[") + std::to_string(this->m_layers_size) + "]" + msaa_info); + } + #endif + + p_result->Set_Texture(p_resource); + D3D12_RESOURCE_FLAGS flags{}; + + if (is_depth_stencil) + flags = D3D12_RESOURCE_FLAGS::D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + else + flags = D3D12_RESOURCE_FLAGS::D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + + D3D12_RESOURCE_STATES states{}; + + if (is_depth_stencil) + states = D3D12_RESOURCE_STATES::D3D12_RESOURCE_STATE_DEPTH_WRITE; + else + states = D3D12_RESOURCE_STATES::D3D12_RESOURCE_STATE_RENDER_TARGET; + + #ifdef RMLUI_DEBUG + const char* pWhatTypeOfTextureForAllocationName{}; + + constexpr const char* kHardcodedNameForDepthStencil = "Render2Texture|Depth-Stencil"; + constexpr const char* kHardcodedNameForRenderTarget = "Render2Texture|Render-Target"; + + if (is_depth_stencil) + pWhatTypeOfTextureForAllocationName = kHardcodedNameForDepthStencil; + else + pWhatTypeOfTextureForAllocationName = kHardcodedNameForRenderTarget; + #endif + + this->m_p_manager_texture->Alloc_Texture(desc_texture, p_result, flags, states + #ifdef RMLUI_DEBUG + , + pWhatTypeOfTextureForAllocationName + #endif + ); + } +} + +void RenderInterface_DX12::RenderLayerStack::DestroyFramebuffer(Gfx::FramebufferData* p_data) +{ + RMLUI_ASSERT(p_data && "you must pass a valid data"); + RMLUI_ASSERT(this->m_p_manager_texture && "early/late calling?"); + + if (p_data) + { + this->m_p_manager_texture->Free_Texture(p_data); + p_data->Set_Width(-1); + p_data->Set_Height(-1); + } +} + +Rml::LayerHandle RenderInterface_DX12::PushLayer() +{ + const Rml::LayerHandle layer_handle = this->m_manager_render_layer.PushLayer(); + + const auto& framebuffer = this->m_manager_render_layer.GetLayer(layer_handle); + const auto& shared_depthstencil = this->m_manager_render_layer.Get_SharedDepthStencil(); + + this->m_p_command_graphics_list->OMSetRenderTargets(1, &framebuffer.Get_DescriptorResourceView(), FALSE, + &shared_depthstencil.Get_DescriptorResourceView()); + + FLOAT clear_color[] = {0.0f, 0.0f, 0.0f, 1.0f}; + this->m_p_command_graphics_list->ClearRenderTargetView(framebuffer.Get_DescriptorResourceView(), clear_color, 0, nullptr); + this->m_p_command_graphics_list->ClearDepthStencilView(shared_depthstencil.Get_DescriptorResourceView(), + D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr); + + return layer_handle; +} + +void RenderInterface_DX12::BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_id) +{ + const auto& source_framebuffer = this->m_manager_render_layer.GetLayer(layer_id); + const auto& destination_framebuffer = this->m_manager_render_layer.GetPostprocessPrimary(); + + RMLUI_ASSERT(!"todo: implement resolve msaa on dx12 side"[0]); +} + +static Rml::Pair SigmaToParameters(const float desired_sigma) +{ + constexpr int max_num_passes = 10; + static_assert(max_num_passes < 31, ""); + constexpr float max_single_pass_sigma = 3.0f; + + Rml::Pair result; + + result.first = Rml::Math::Clamp(Rml::Math::Log2(int(desired_sigma * (2.f / max_single_pass_sigma))), 0, max_num_passes); + result.second = Rml::Math::Clamp(desired_sigma / float(1 << result.first), 0.0f, max_single_pass_sigma); + + return result; +} + +void RenderInterface_DX12::DrawFullscreenQuad() +{ + RMLUI_ASSERT(this->m_p_command_graphics_list && "must be valid!"); + + RenderGeometry(this->m_precompiled_fullscreen_quad_geometry, {}, TexturePostprocess); +} + +void RenderInterface_DX12::DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling) +{ + RMLUI_ASSERT(this->m_p_command_graphics_list && "must be valid!"); +} + +void RenderInterface_DX12::RenderBlur(float sigma, const Gfx::FramebufferData& source_destination, const Gfx::FramebufferData& temp, + const Rml::Rectanglei window_flipped) +{ + RMLUI_ASSERT(&source_destination != &temp && "you can't pass the same object to source_destination and temp arguments!"); + RMLUI_ASSERT(source_destination.Get_Width() == temp.Get_Width() && "must be equal to the same size!"); + RMLUI_ASSERT(source_destination.Get_Height() == temp.Get_Height() && "must be equal to the same size!"); + RMLUI_ASSERT(window_flipped.Valid() && "must be valid!"); + + int pass_level = 0; + const Rml::Pair& pass_level_and_sigma = SigmaToParameters(sigma); + + const Rml::Rectanglei original_scissor = this->m_scissor; + Rml::Rectanglei scissor = window_flipped; +} + +void RenderInterface_DX12::RenderFilters(Rml::Span filter_handles) +{ + for (const Rml::CompiledFilterHandle filter_handle : filter_handles) + { + const CompiledFilter& filter = *reinterpret_cast(filter_handle); + const FilterType type = filter.type; + + switch (type) + { + case FilterType::Passthrough: + { + const Gfx::FramebufferData& source = this->m_manager_render_layer.GetPostprocessPrimary(); + const Gfx::FramebufferData& destination = this->m_manager_render_layer.GetPostprocessSecondary(); + + TextureHandleType* p_texture = source.Get_Texture(); + + // todo: useprogram and bind texture here! + this->DrawFullscreenQuad(); + this->m_manager_render_layer.SwapPostprocessPrimarySecondary(); + + break; + } + case FilterType::Blur: + { + break; + } + case FilterType::DropShadow: + { + break; + } + case FilterType::ColorMatrix: + { + break; + } + case FilterType::MaskImage: + { + break; + } + } + } +} + +void RenderInterface_DX12::CompositeLayers(Rml::LayerHandle source, Rml::LayerHandle destination, Rml::BlendMode blend_mode, + Rml::Span filters) +{ + BlitLayerToPostprocessPrimary(source); +} + +void RenderInterface_DX12::PopLayer() +{ + // RMLUI_ASSERT(false && "todo"); +} + +Rml::TextureHandle RenderInterface_DX12::SaveLayerAsTexture(Rml::Vector2i dimensions) +{ + // RMLUI_ASSERT(false && "todo"); + return Rml::TextureHandle(); +} + +Rml::CompiledFilterHandle RenderInterface_DX12::SaveLayerAsMaskImage() +{ + // RMLUI_ASSERT(false && "todo"); + return Rml::CompiledFilterHandle(); +} + +Rml::CompiledFilterHandle RenderInterface_DX12::CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) +{ + CompiledFilter filter = {}; + + if (name == "opacity") + { + filter.type = FilterType::Passthrough; + filter.blend_factor = Rml::Get(parameters, "value", 1.0f); + } + else if (name == "blur") + { + filter.type = FilterType::Blur; + filter.sigma = 0.5f * Rml::Get(parameters, "radius", 1.0f); + } + else if (name == "drop-shadow") + { + filter.type = FilterType::DropShadow; + filter.sigma = Rml::Get(parameters, "sigma", 0.f); + filter.color = Rml::Get(parameters, "color", Rml::Colourb()); + filter.offset = Rml::Get(parameters, "offset", Rml::Vector2f(0.f)); + } + else if (name == "brightness") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + filter.color_matrix = Rml::Matrix4f::Diag(value, value, value, 1.f); + } + else if (name == "contrast") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float grayness = 0.5f - 0.5f * value; + filter.color_matrix = Rml::Matrix4f::Diag(value, value, value, 1.f); + filter.color_matrix.SetColumn(3, Rml::Vector4f(grayness, grayness, grayness, 1.f)); + } + else if (name == "invert") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Math::Clamp(Rml::Get(parameters, "value", 1.0f), 0.f, 1.f); + const float inverted = 1.f - 2.f * value; + filter.color_matrix = Rml::Matrix4f::Diag(inverted, inverted, inverted, 1.f); + filter.color_matrix.SetColumn(3, Rml::Vector4f(value, value, value, 1.f)); + } + else if (name == "grayscale") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float rev_value = 1.f - value; + const Rml::Vector3f gray = value * Rml::Vector3f(0.2126f, 0.7152f, 0.0722f); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {gray.x + rev_value, gray.y, gray.z, 0.f}, + {gray.x, gray.y + rev_value, gray.z, 0.f}, + {gray.x, gray.y, gray.z + rev_value, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + else if (name == "sepia") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float rev_value = 1.f - value; + const Rml::Vector3f r_mix = value * Rml::Vector3f(0.393f, 0.769f, 0.189f); + const Rml::Vector3f g_mix = value * Rml::Vector3f(0.349f, 0.686f, 0.168f); + const Rml::Vector3f b_mix = value * Rml::Vector3f(0.272f, 0.534f, 0.131f); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {r_mix.x + rev_value, r_mix.y, r_mix.z, 0.f}, + {g_mix.x, g_mix.y + rev_value, g_mix.z, 0.f}, + {b_mix.x, b_mix.y, b_mix.z + rev_value, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + else if (name == "hue-rotate") + { + // Hue-rotation and saturation values based on: https://www.w3.org/TR/filter-effects-1/#attr-valuedef-type-huerotate + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + const float s = Rml::Math::Sin(value); + const float c = Rml::Math::Cos(value); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {0.213f + 0.787f * c - 0.213f * s, 0.715f - 0.715f * c - 0.715f * s, 0.072f - 0.072f * c + 0.928f * s, 0.f}, + {0.213f - 0.213f * c + 0.143f * s, 0.715f + 0.285f * c + 0.140f * s, 0.072f - 0.072f * c - 0.283f * s, 0.f}, + {0.213f - 0.213f * c - 0.787f * s, 0.715f - 0.715f * c + 0.715f * s, 0.072f + 0.928f * c + 0.072f * s, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + else if (name == "saturate") + { + filter.type = FilterType::ColorMatrix; + const float value = Rml::Get(parameters, "value", 1.0f); + // clang-format off + filter.color_matrix = Rml::Matrix4f::FromRows( + {0.213f + 0.787f * value, 0.715f - 0.715f * value, 0.072f - 0.072f * value, 0.f}, + {0.213f - 0.213f * value, 0.715f + 0.285f * value, 0.072f - 0.072f * value, 0.f}, + {0.213f - 0.213f * value, 0.715f - 0.715f * value, 0.072f + 0.928f * value, 0.f}, + {0.f, 0.f, 0.f, 1.f} + ); + // clang-format on + } + + if (filter.type != FilterType::Invalid) + return reinterpret_cast(new CompiledFilter(std::move(filter))); + + Rml::Log::Message(Rml::Log::LT_WARNING, "Unsupported filter type '%s'.", name.c_str()); + return {}; +} + +void RenderInterface_DX12::ReleaseFilter(Rml::CompiledFilterHandle filter) +{ + delete reinterpret_cast(filter); +} + +Rml::CompiledShaderHandle RenderInterface_DX12::CompileShader(const Rml::String& name, const Rml::Dictionary& parameters) +{ + auto ApplyColorStopList = [](CompiledShader& shader, const Rml::Dictionary& shader_parameters) { + auto it = shader_parameters.find("color_stop_list"); + RMLUI_ASSERT(it != shader_parameters.end() && it->second.GetType() == Rml::Variant::COLORSTOPLIST); + const Rml::ColorStopList& color_stop_list = it->second.GetReference(); + const int num_stops = Rml::Math::Min((int)color_stop_list.size(), MAX_NUM_STOPS); + + shader.stop_positions.resize(num_stops); + shader.stop_colors.resize(num_stops); + for (int i = 0; i < num_stops; i++) + { + const Rml::ColorStop& stop = color_stop_list[i]; + RMLUI_ASSERT(stop.position.unit == Rml::Unit::NUMBER); + shader.stop_positions[i] = stop.position.number; + shader.stop_colors[i] = ConvertToColorf(stop.color); + } + }; + + CompiledShader shader = {}; + + if (name == "linear-gradient") + { + shader.type = CompiledShaderType::Gradient; + const bool repeating = Rml::Get(parameters, "repeating", false); + shader.gradient_function = (repeating ? ShaderGradientFunction::RepeatingLinear : ShaderGradientFunction::Linear); + shader.p = Rml::Get(parameters, "p0", Rml::Vector2f(0.f)); + shader.v = Rml::Get(parameters, "p1", Rml::Vector2f(0.f)) - shader.p; + ApplyColorStopList(shader, parameters); + } + else if (name == "radial-gradient") + { + shader.type = CompiledShaderType::Gradient; + const bool repeating = Rml::Get(parameters, "repeating", false); + shader.gradient_function = (repeating ? ShaderGradientFunction::RepeatingRadial : ShaderGradientFunction::Radial); + shader.p = Rml::Get(parameters, "center", Rml::Vector2f(0.f)); + shader.v = Rml::Vector2f(1.f) / Rml::Get(parameters, "radius", Rml::Vector2f(1.f)); + ApplyColorStopList(shader, parameters); + } + else if (name == "conic-gradient") + { + shader.type = CompiledShaderType::Gradient; + const bool repeating = Rml::Get(parameters, "repeating", false); + shader.gradient_function = (repeating ? ShaderGradientFunction::RepeatingConic : ShaderGradientFunction::Conic); + shader.p = Rml::Get(parameters, "center", Rml::Vector2f(0.f)); + const float angle = Rml::Get(parameters, "angle", 0.f); + shader.v = {Rml::Math::Cos(angle), Rml::Math::Sin(angle)}; + ApplyColorStopList(shader, parameters); + } + else if (name == "shader") + { + const Rml::String value = Rml::Get(parameters, "value", Rml::String()); + if (value == "creation") + { + shader.type = CompiledShaderType::Creation; + shader.dimensions = Rml::Get(parameters, "dimensions", Rml::Vector2f(0.f)); + } + } + + if (shader.type != CompiledShaderType::Invalid) + return reinterpret_cast(new CompiledShader(std::move(shader))); + + Rml::Log::Message(Rml::Log::LT_WARNING, "Unsupported shader type '%s'.", name.c_str()); + return {}; +} + +void RenderInterface_DX12::RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle, + Rml::Vector2f translation, Rml::TextureHandle texture) +{ + // RMLUI_ASSERT(false && "todo"); +} + +void RenderInterface_DX12::ReleaseShader(Rml::CompiledShaderHandle effect_handle) +{ + delete reinterpret_cast(effect_handle); +} + +void RenderInterface_DX12::Shutdown() noexcept +{ + if (this->m_is_shutdown_called) + return; + + if (!this->m_is_shutdown_called) + { + this->m_is_shutdown_called = true; + } + + this->Flush(); + this->Destroy_Resource_Pipelines(); + + if (this->m_precompiled_fullscreen_quad_geometry) + { + this->Free_Geometry(reinterpret_cast(this->m_precompiled_fullscreen_quad_geometry)); + this->m_precompiled_fullscreen_quad_geometry = {}; + } + + this->m_manager_buffer.Shutdown(); + this->m_manager_texture.Shutdown(); + + if (this->m_p_offset_allocator_for_descriptor_heap_shaders) + { + delete this->m_p_offset_allocator_for_descriptor_heap_shaders; + this->m_p_offset_allocator_for_descriptor_heap_shaders = nullptr; + } + + if (this->m_is_full_initialization) + { + this->Destroy_Allocator(); + this->Destroy_SyncPrimitives(); + this->Destroy_Resource_RenderTagetViews(); + this->Destroy_Swapchain(); + this->Destroy_CommandAllocators(); + if (this->m_p_device) + { + this->m_p_device->Release(); + } + + if (this->m_p_adapter) + { + this->m_p_adapter->Release(); + } + + if (this->m_p_command_graphics_list) + { + this->m_p_command_graphics_list->Release(); + } + + if (this->m_p_command_queue) + { + this->m_p_command_queue->Release(); + } + + if (this->m_p_descriptor_heap_render_target_view) + { + this->m_p_descriptor_heap_render_target_view->Release(); + } + + if (this->m_p_descriptor_heap_render_target_view_for_texture_manager) + { + this->m_p_descriptor_heap_render_target_view_for_texture_manager->Release(); + } + + if (this->m_p_descriptor_heap_depth_stencil_view_for_texture_manager) + { + this->m_p_descriptor_heap_depth_stencil_view_for_texture_manager->Release(); + } + + if (this->m_p_descriptor_heap_shaders) + { + this->m_p_descriptor_heap_shaders->Release(); + } + + if (this->m_p_descriptor_heap_depthstencil) + { + this->m_p_descriptor_heap_depthstencil->Release(); + } + + if (this->m_p_copy_command_list) + { + this->m_p_copy_command_list->Release(); + } + + if (this->m_p_copy_allocator) + { + this->m_p_copy_allocator->Release(); + } + + if (this->m_p_copy_queue) + { + this->m_p_copy_queue->Release(); + } + } + + if (this->m_p_device) + { + this->m_p_device = nullptr; + } + + if (this->m_p_adapter) + { + this->m_p_adapter = nullptr; + } + + if (this->m_p_window_handle) + { + this->m_p_window_handle = nullptr; + } + + if (this->m_p_swapchain) + { + this->m_p_swapchain = nullptr; + } + + if (this->m_p_command_graphics_list) + { + this->m_p_command_graphics_list = nullptr; + } + + if (this->m_p_command_queue) + { + this->m_p_command_queue = nullptr; + } + + if (this->m_p_descriptor_heap_render_target_view) + { + this->m_p_descriptor_heap_render_target_view = nullptr; + } + + if (this->m_p_allocator) + { + this->m_p_allocator = nullptr; + } + + if (this->m_p_descriptor_heap_shaders) + { + this->m_p_descriptor_heap_shaders = nullptr; + } + + if (this->m_p_descriptor_heap_depthstencil) + { + this->m_p_descriptor_heap_depthstencil = nullptr; + } + + if (this->m_p_copy_command_list) + { + this->m_p_copy_command_list = nullptr; + } + + if (this->m_p_copy_allocator) + { + this->m_p_copy_allocator = nullptr; + } + + #ifdef RMLUI_DX_DEBUG + { + auto dll_dxgidebug = LoadLibraryEx(TEXT("dxgidebug.dll"), nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + + if (dll_dxgidebug) + { + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "loaded dxgidebug.dll for detecting memory leaks on DirectX's side!"); + + typedef HRESULT(WINAPI * LPDXGIGETDEBUGINTERFACE)(REFIID, void**); + auto callback_DXGIGetDebugInterface = + reinterpret_cast(reinterpret_cast(GetProcAddress(dll_dxgidebug, "DXGIGetDebugInterface"))); + + if (callback_DXGIGetDebugInterface) + { + IDXGIDebug* p_debug_references{}; + + auto status = callback_DXGIGetDebugInterface(IID_PPV_ARGS(&p_debug_references)); + + RMLUI_DX_ASSERTMSG(status, "failed to DXGIGetDebugInterface"); + + if (SUCCEEDED(status)) + { + if (p_debug_references) + { + p_debug_references->ReportLiveObjects(DXGI_DEBUG_ALL, + DXGI_DEBUG_RLO_FLAGS(DXGI_DEBUG_RLO_SUMMARY | DXGI_DEBUG_RLO_IGNORE_INTERNAL)); + p_debug_references->Release(); + } + } + else + { + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, + "Failed to initialize IDXGIDebug interface by DXGIGetDebugInterface function from dxgidebug.dll!"); + } + } + } + else + { + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, + "your Windows version is too old for loading dxgidebug.dll! (your OS's SDK doesn't provide a such dll)"); + } + } + #endif + + Rml::Log::Message(Rml::Log::Type::LT_INFO, "Backend DirectX 12 is destroyed!"); +} + +void RenderInterface_DX12::Initialize(void) noexcept +{ + if (this->m_is_shutdown_called) + { + this->m_is_shutdown_called = false; + } + + if (this->m_is_full_initialization) + { + this->Initialize_DebugLayer(); + this->Initialize_Adapter(); + this->Initialize_Device(); + this->m_p_command_queue = this->Create_CommandQueue(D3D12_COMMAND_LIST_TYPE_DIRECT); + this->m_p_copy_queue = this->Create_CommandQueue(D3D12_COMMAND_LIST_TYPE_COPY); + + RMLUI_ASSERT(this->m_p_command_queue && "must create command queue!"); + RMLUI_ASSERT(this->m_p_copy_queue && "must create copy queue!"); + + #ifdef RMLUI_DX_DEBUG + this->m_p_copy_queue->SetName(TEXT("Copy Queue (for texture manager)")); + #endif + + this->Initialize_Swapchain(0, 0); + + this->m_p_descriptor_heap_render_target_view = this->Create_Resource_DescriptorHeap(D3D12_DESCRIPTOR_HEAP_TYPE_RTV, + D3D12_DESCRIPTOR_HEAP_FLAGS::D3D12_DESCRIPTOR_HEAP_FLAG_NONE, RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT); + + this->m_p_descriptor_heap_render_target_view_for_texture_manager = this->Create_Resource_DescriptorHeap(D3D12_DESCRIPTOR_HEAP_TYPE_RTV, + D3D12_DESCRIPTOR_HEAP_FLAGS::D3D12_DESCRIPTOR_HEAP_FLAG_NONE, RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_RTV); + + this->m_p_descriptor_heap_shaders = this->Create_Resource_DescriptorHeap(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, + D3D12_DESCRIPTOR_HEAP_FLAGS::D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE, RMLUI_RENDER_BACKEND_FIELD_DESCRIPTORAMOUNT_FOR_SRV_CBV_UAV); + + this->m_p_descriptor_heap_depthstencil = + this->Create_Resource_DescriptorHeap(D3D12_DESCRIPTOR_HEAP_TYPE_DSV, D3D12_DESCRIPTOR_HEAP_FLAGS::D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 1); + + this->m_p_descriptor_heap_depth_stencil_view_for_texture_manager = this->Create_Resource_DescriptorHeap(D3D12_DESCRIPTOR_HEAP_TYPE_DSV, + D3D12_DESCRIPTOR_HEAP_FLAGS::D3D12_DESCRIPTOR_HEAP_FLAG_NONE, RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_DSV); + + this->m_handle_shaders = CD3DX12_CPU_DESCRIPTOR_HANDLE(this->m_p_descriptor_heap_shaders->GetCPUDescriptorHandleForHeapStart()); + + this->m_size_descriptor_heap_render_target_view = this->m_p_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + this->m_size_descriptor_heap_shaders = this->m_p_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + this->Create_Resources_DependentOnSize(); + + this->Initialize_CommandAllocators(); + this->m_p_command_graphics_list = this->Create_CommandList(this->m_backbuffers_allocators.at(this->m_current_back_buffer_index), + D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_DIRECT); + this->Initialize_Allocator(); + this->Initialize_SyncPrimitives(); + + this->m_p_copy_allocator = this->Create_CommandAllocator(D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_COPY); + this->m_p_copy_command_list = this->Create_CommandList(this->m_p_copy_allocator, D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_COPY); + + this->m_p_offset_allocator_for_descriptor_heap_shaders = + new OffsetAllocator::Allocator(RMLUI_RENDER_BACKEND_FIELD_DESCRIPTORAMOUNT_FOR_SRV_CBV_UAV * this->m_size_descriptor_heap_shaders); + + #ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "amount of srv_cbv_uav: %d size of increment: %d", + RMLUI_RENDER_BACKEND_FIELD_DESCRIPTORAMOUNT_FOR_SRV_CBV_UAV, this->m_size_descriptor_heap_shaders); + #endif + + this->m_manager_buffer.Initialize(this->m_p_device, this->m_p_allocator, this->m_p_offset_allocator_for_descriptor_heap_shaders, + &this->m_handle_shaders, this->m_size_descriptor_heap_shaders); + this->m_manager_texture.Initialize(this->m_p_allocator, this->m_p_offset_allocator_for_descriptor_heap_shaders, this->m_p_device, + this->m_p_copy_command_list, this->m_p_copy_allocator, this->m_p_descriptor_heap_shaders, + this->m_p_descriptor_heap_render_target_view_for_texture_manager, this->m_p_descriptor_heap_depth_stencil_view_for_texture_manager, + this->m_p_copy_queue, &this->m_handle_shaders, this); + this->m_manager_render_layer.Initialize(this); + + this->Create_Resource_Pipelines(); + + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(-1.f), Rml::Vector2f(2.f), {}); + + this->m_precompiled_fullscreen_quad_geometry = this->CompileGeometry(mesh.vertices, mesh.indices); + + #ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "DirectX 12 Initialize type: full"); + #endif + } + else + { + #ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "DirectX 12 Initialize type: user"); + #endif + } +} + +bool RenderInterface_DX12::IsSwapchainValid() noexcept +{ + return this->m_p_swapchain != nullptr; +} + +void RenderInterface_DX12::RecreateSwapchain() noexcept +{ + SetViewport(m_width, m_height); +} + +ID3D12Fence* RenderInterface_DX12::Get_Fence(void) +{ + return this->m_p_backbuffer_fence; +} + +HANDLE RenderInterface_DX12::Get_FenceEvent(void) +{ + return this->m_p_fence_event; +} + +Rml::Array& RenderInterface_DX12::Get_FenceValues(void) +{ + return this->m_backbuffers_fence_values; +} + +uint32_t RenderInterface_DX12::Get_CurrentFrameIndex(void) +{ + return this->m_current_back_buffer_index; +} + +ID3D12Device* RenderInterface_DX12::Get_Device(void) const +{ + return this->m_p_device; +} + +RenderInterface_DX12::TextureMemoryManager& RenderInterface_DX12::Get_TextureManager(void) +{ + return this->m_manager_texture; +} + +RenderInterface_DX12::BufferMemoryManager& RenderInterface_DX12::Get_BufferManager(void) +{ + return this->m_manager_buffer; +} + +void RenderInterface_DX12::Initialize_Device(void) noexcept +{ + RMLUI_ASSERT(this->m_p_adapter && "you must call this when you initialized adapter!"); + + ID3D12Device2* p_device{}; + + RMLUI_DX_ASSERTMSG(D3D12CreateDevice(this->m_p_adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&p_device)), "failed to D3D12CreateDevice"); + + this->m_p_device = p_device; + + #ifdef RMLUI_DX_DEBUG + ID3D12InfoQueue* p_queue{}; + if (p_device) + { + RMLUI_DX_ASSERTMSG(p_device->QueryInterface(IID_PPV_ARGS(&p_queue)), "failed to QueryInterface of ID3D12InfoQueue"); + + if (p_queue) + { + // if implemention is good breaking will NOT be caused at all, but if something is bad you will get __debugbreak in Debug after + // ReportLiveObjects calling that means system works not correctly for D3D12 API at all and you must fix these problems what your device + // reported (it might be annoying because you will not see the result of ReportLiveObjects and you should comment these line of + // SetBreakOnSeverity in order to see full report from ReportLiveObjects calling) + p_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, 1); + p_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, 1); + p_queue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, 1); + + // Suppress messages based on their severity level + D3D12_MESSAGE_SEVERITY p_sevs[] = {D3D12_MESSAGE_SEVERITY_INFO}; + + D3D12_INFO_QUEUE_FILTER info_filter = {}; + info_filter.DenyList.NumSeverities = _countof(p_sevs); + info_filter.DenyList.pSeverityList = p_sevs; + + RMLUI_DX_ASSERTMSG(p_queue->PushStorageFilter(&info_filter), "failed to PushStorageFilter"); + + p_queue->Release(); + } + } + #endif +} + +void RenderInterface_DX12::Initialize_Adapter(void) noexcept +{ + if (this->m_p_adapter) + { + this->m_p_adapter->Release(); + } + + this->m_p_adapter = this->Get_Adapter(false); +} + +void RenderInterface_DX12::Initialize_DebugLayer(void) noexcept +{ + #ifdef RMLUI_DX_DEBUG + ID3D12Debug* p_debug{}; + + RMLUI_DX_ASSERTMSG(D3D12GetDebugInterface(IID_PPV_ARGS(&p_debug)), "failed to D3D12GetDebugInterface"); + + if (p_debug) + { + p_debug->EnableDebugLayer(); + p_debug->Release(); + } + #endif +} + +ID3D12CommandQueue* RenderInterface_DX12::Create_CommandQueue(D3D12_COMMAND_LIST_TYPE type) noexcept +{ + RMLUI_ASSERT(this->m_p_device && "you must initialize device before calling this method"); + + ID3D12CommandQueue* p_result{}; + if (this->m_p_device) + { + D3D12_COMMAND_QUEUE_DESC desc = {}; + + desc.Type = type; + desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL; + desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; + desc.NodeMask = 0; + + RMLUI_DX_ASSERTMSG(this->m_p_device->CreateCommandQueue(&desc, IID_PPV_ARGS(&p_result)), "failed to CreateCommandQueue"); + } + + return p_result; +} + +void RenderInterface_DX12::Initialize_Swapchain(int width, int height) noexcept +{ + RMLUI_ASSERT(width >= 0 && "must not be a negative value"); + RMLUI_ASSERT(height >= 0 && "must not be a negative value"); + + // in dx12 0 means it will take size of window automatically + if (width < 0) + width = 0; + + if (height < 0) + height = 0; + + IDXGISwapChain4* p_swapchain{}; + IDXGIFactory4* p_factory{}; + + uint32_t create_factory_flags{}; + + #ifdef RMLUI_DX_DEBUG + create_factory_flags = DXGI_CREATE_FACTORY_DEBUG; + #endif + + RMLUI_DX_ASSERTMSG(CreateDXGIFactory2(create_factory_flags, IID_PPV_ARGS(&p_factory)), "failed to CreateDXGIFactory2"); + + // todo: use information from user's initialization data structure + // todo: if user specified MSAA as ON but forgot to pass any valid data, use this field RMLUI_RENDER_BACKEND_FIELD_MSAA_SAMPLE_COUNT as fallback + // default handling value + #pragma todo("read this!"); + this->m_desc_sample.Count = RMLUI_RENDER_BACKEND_FIELD_MSAA_SAMPLE_COUNT; + this->m_desc_sample.Quality = 0; + + DXGI_SWAP_CHAIN_DESC1 desc = {}; + + desc.Width = width; + desc.Height = height; + desc.Format = RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT; + desc.Stereo = 0; + // since we can't use rt textures with different sample count than Swapchain's so we create framebuffers and presenting them on NO-MSAA + // Swapchain + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + desc.BufferCount = RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT; + desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; + + uint32_t flags_swapchain{}; + + if (this->CheckTearingSupport()) + { + flags_swapchain = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + this->m_is_use_tearing = true; + } + + desc.Flags = flags_swapchain; + + IDXGISwapChain1* p_swapchain1{}; + + RMLUI_DX_ASSERTMSG(p_factory->CreateSwapChainForHwnd(this->m_p_command_queue, this->m_p_window_handle, &desc, nullptr, nullptr, &p_swapchain1), + "failed to CreateSwapChainForHwnd"); + + RMLUI_DX_ASSERTMSG(p_factory->MakeWindowAssociation(this->m_p_window_handle, DXGI_MWA_NO_ALT_ENTER), "failed to MakeWindowAssociation"); + + RMLUI_DX_ASSERTMSG(p_swapchain1->QueryInterface(IID_PPV_ARGS(&p_swapchain)), "failed to QueryInterface of IDXGISwapChain4"); + + this->m_p_swapchain = p_swapchain; + + if (p_swapchain1) + { + p_swapchain1->Release(); + } + + if (p_factory) + { + p_factory->Release(); + } +} + +void RenderInterface_DX12::Initialize_SyncPrimitives(void) noexcept +{ + RMLUI_ASSERT(this->m_p_device && "you must initialize device before calling this method!"); + + if (this->m_p_device) + { + if (this->m_is_full_initialization) + { + for (int i = 0; i < RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT; ++i) + { + this->m_backbuffers_fence_values[i] = 0; + } + + this->m_p_device->CreateFence(0, D3D12_FENCE_FLAGS::D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&this->m_p_backbuffer_fence)); + + this->m_p_fence_event = CreateEvent(NULL, FALSE, FALSE, NULL); + RMLUI_DX_ASSERTMSG(this->m_p_fence_event, "failed to CreateEvent (WinAPI)"); + } + } +} + +void RenderInterface_DX12::Initialize_CommandAllocators(void) +{ + for (int i = 0; i < RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT; ++i) + { + this->m_backbuffers_allocators[i] = this->Create_CommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT); + } +} + +void RenderInterface_DX12::Initialize_Allocator(void) noexcept +{ + // user provides allocator from his implementation or we signal that this backend initializes own allocator + if (this->m_is_full_initialization) + { + RMLUI_ASSERT(!this->m_p_allocator && "forgot to destroy before initialization!"); + RMLUI_ASSERT(this->m_p_device && "must be valid when you call this method!"); + RMLUI_ASSERT(this->m_p_adapter && "must be valid when you call this method!"); + + D3D12MA::ALLOCATOR_DESC desc = {}; + desc.pDevice = this->m_p_device; + desc.pAdapter = this->m_p_adapter; + + D3D12MA::Allocator* p_allocator{}; + + RMLUI_DX_ASSERTMSG(D3D12MA::CreateAllocator(&desc, &p_allocator), "failed to D3D12MA::CreateAllocator!"); + RMLUI_ASSERT(p_allocator && "failed to create allocator!"); + + this->m_p_allocator = p_allocator; + } +} + +void RenderInterface_DX12::Destroy_Swapchain() noexcept +{ + if (this->m_p_swapchain) + { + if (this->m_is_full_initialization) + { + this->m_p_swapchain->Release(); + } + } + + this->m_p_swapchain = nullptr; +} + +void RenderInterface_DX12::Destroy_SyncPrimitives(void) noexcept +{ + if (this->m_is_full_initialization) + { + if (this->m_p_backbuffer_fence) + { + this->m_p_backbuffer_fence->Release(); + } + } + + this->m_p_backbuffer_fence = nullptr; +} + +void RenderInterface_DX12::Destroy_CommandAllocators(void) noexcept +{ + for (ID3D12CommandAllocator* p_allocator : this->m_backbuffers_allocators) + { + RMLUI_ASSERT(p_allocator, "early calling or object is damaged!"); + if (p_allocator) + { + p_allocator->Release(); + } + } +} + +void RenderInterface_DX12::Destroy_CommandList(void) noexcept {} + +void RenderInterface_DX12::Destroy_Allocator(void) noexcept +{ + if (this->m_is_full_initialization) + { + if (this->m_p_allocator) + { + this->m_p_allocator->Release(); + } + } +} + +void RenderInterface_DX12::Flush() noexcept +{ + auto value = this->Signal(); + this->WaitForFenceValue(value); +} + +uint64_t RenderInterface_DX12::Signal() noexcept +{ + RMLUI_ASSERT(this->m_p_command_queue && "you must initialize it first before calling this method!"); + RMLUI_ASSERT(this->m_p_backbuffer_fence && "you must initialize it first before calling this method!"); + + if (this->m_p_command_queue) + { + if (this->m_p_backbuffer_fence) + { + auto value = (this->m_backbuffers_fence_values.at(this->m_current_back_buffer_index)++); + + RMLUI_DX_ASSERTMSG(this->m_p_command_queue->Signal(this->m_p_backbuffer_fence, value), "failed to command queue::Signal!"); + + return value; + } + } + + return 0; +} + +void RenderInterface_DX12::WaitForFenceValue(uint64_t fence_value, std::chrono::milliseconds time) +{ + RMLUI_ASSERT(this->m_p_backbuffer_fence && "you must initialize ID3D12Fence first!"); + RMLUI_ASSERT(this->m_p_fence_event && "you must initialize fence event (HANDLE)"); + + if (this->m_p_backbuffer_fence) + { + if (this->m_p_fence_event) + { + if (this->m_p_backbuffer_fence->GetCompletedValue() < fence_value) + { + RMLUI_DX_ASSERTMSG(this->m_p_backbuffer_fence->SetEventOnCompletion(fence_value, this->m_p_fence_event), + "failed to SetEventOnCompletion"); + WaitForSingleObject(this->m_p_fence_event, static_cast(time.count())); + } + } + } +} + +void RenderInterface_DX12::Create_Resources_DependentOnSize() noexcept +{ + this->Create_Resource_RenderTargetViews(); + // this->Create_Resource_Pipelines(); +} + +void RenderInterface_DX12::Destroy_Resources_DependentOnSize() noexcept +{ + // this->Destroy_Resource_Pipelines(); + this->Destroy_Resource_RenderTagetViews(); +} + +void RenderInterface_DX12::Create_Resource_DepthStencil() +{ + RMLUI_ASSERT(this->m_p_descriptor_heap_depthstencil && "you must initialize this descriptor heap before calling this method!"); + RMLUI_ASSERT(this->m_p_device && "you must create device!"); + RMLUI_ASSERT(this->m_width > 0 && "invalid width"); + RMLUI_ASSERT(this->m_height > 0 && "invalid height"); + + D3D12MA::ALLOCATION_DESC desc_alloc = {}; + desc_alloc.HeapType = D3D12_HEAP_TYPE_DEFAULT; + + D3D12_RESOURCE_DESC desc_texture = {}; + desc_texture.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; + desc_texture.Format = RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT; + desc_texture.MipLevels = 1; + desc_texture.Width = this->m_width; + desc_texture.Height = this->m_height; + desc_texture.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + desc_texture.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + desc_texture.SampleDesc.Count = 1; + desc_texture.SampleDesc.Quality = 0; + desc_texture.DepthOrArraySize = 1; + desc_texture.Alignment = 0; + + D3D12_CLEAR_VALUE depth_optimized_clear_value = {}; + depth_optimized_clear_value.Format = RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT; + depth_optimized_clear_value.DepthStencil.Depth = 1.0f; + depth_optimized_clear_value.DepthStencil.Stencil = 0; + + ID3D12Resource* p_temp{}; + auto status = this->m_p_allocator->CreateResource(&desc_alloc, &desc_texture, D3D12_RESOURCE_STATES::D3D12_RESOURCE_STATE_DEPTH_WRITE, + &depth_optimized_clear_value, &this->m_p_depthstencil_resource, IID_PPV_ARGS(&p_temp)); + + RMLUI_DX_ASSERTMSG(status, "failed to create resource as depth stencil texture!"); + + RMLUI_ASSERT(this->m_p_depthstencil_resource && "must be created!"); + RMLUI_ASSERT(p_temp && "must be created!"); + + #ifdef RMLUI_DX_DEBUG + this->m_p_depthstencil_resource->SetName(L"DepthStencil texture (resource)"); + #endif + + D3D12_DEPTH_STENCIL_VIEW_DESC desc_view = {}; + desc_view.Format = RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT; + desc_view.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; + desc_view.Flags = D3D12_DSV_FLAG_NONE; + + this->m_p_device->CreateDepthStencilView(this->m_p_depthstencil_resource->GetResource(), &desc_view, + this->m_p_descriptor_heap_depthstencil->GetCPUDescriptorHandleForHeapStart()); +} + +void RenderInterface_DX12::Destroy_Resource_DepthStencil() +{ + RMLUI_ASSERT(this->m_p_depthstencil_resource && "you must create resource for calling this method!"); + RMLUI_ASSERT(this->m_p_depthstencil_resource->GetResource() && "must be valid!"); + + if (this->m_p_depthstencil_resource) + { + if (this->m_p_depthstencil_resource->GetResource()) + { + this->m_p_depthstencil_resource->GetResource()->Release(); + } + + auto count = this->m_p_depthstencil_resource->Release(); + RMLUI_ASSERT(count == 0 && "leak!"); + + this->m_p_depthstencil_resource = nullptr; + } +} + +ID3D12DescriptorHeap* RenderInterface_DX12::Create_Resource_DescriptorHeap(D3D12_DESCRIPTOR_HEAP_TYPE type, D3D12_DESCRIPTOR_HEAP_FLAGS flags, + uint32_t descriptor_count) noexcept +{ + RMLUI_ASSERT(this->m_p_device && "early calling you have to initialize device first"); + + ID3D12DescriptorHeap* p_result{}; + + D3D12_DESCRIPTOR_HEAP_DESC desc = {}; + desc.NumDescriptors = descriptor_count; + desc.Type = type; + desc.Flags = flags; + + RMLUI_DX_ASSERTMSG(this->m_p_device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(&p_result)), "failed to CreateDescriptorHeap"); + + return p_result; +} + +ID3D12CommandAllocator* RenderInterface_DX12::Create_CommandAllocator(D3D12_COMMAND_LIST_TYPE type) +{ + ID3D12CommandAllocator* p_result{}; + + RMLUI_ASSERT(this->m_p_device && "you must initialize device first!"); + + if (this->m_p_device) + { + RMLUI_DX_ASSERTMSG(this->m_p_device->CreateCommandAllocator(type, IID_PPV_ARGS(&p_result)), "failed to CreateCommandAllocator"); + + RMLUI_ASSERT(p_result && "can't allocate command allocator!"); + } + + return p_result; +} + +ID3D12GraphicsCommandList* RenderInterface_DX12::Create_CommandList(ID3D12CommandAllocator* p_allocator, D3D12_COMMAND_LIST_TYPE type) noexcept +{ + ID3D12GraphicsCommandList* p_result{}; + + RMLUI_ASSERT(this->m_p_device && "you must initialize device first!"); + RMLUI_ASSERT(p_allocator && "you must pass a valid instance of ID3D12CommandAllocator*"); + + if (this->m_p_device) + { + RMLUI_DX_ASSERTMSG(this->m_p_device->CreateCommandList(0, type, p_allocator, nullptr, IID_PPV_ARGS(&p_result)), + "failed to CreateCommandList"); + RMLUI_ASSERT(p_result && "can't allocator command list!"); + + if (p_result) + { + RMLUI_DX_ASSERTMSG(p_result->Close(), "failed to Close command list!"); + } + } + + return p_result; +} + +void RenderInterface_DX12::Create_Resource_RenderTargetViews() +{ + RMLUI_ASSERT(this->m_p_device && "early calling you have to initialize device first!"); + RMLUI_ASSERT(this->m_p_swapchain && "early calling you have to initialize swapchain first!"); + RMLUI_ASSERT( + this->m_p_descriptor_heap_render_target_view && "early calling you have to initialize descriptor heap for render target views first!"); + + if (this->m_p_device) + { + if (this->m_p_swapchain) + { + if (this->m_p_descriptor_heap_render_target_view) + { + auto rtv_size = this->m_p_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + CD3DX12_CPU_DESCRIPTOR_HANDLE rtv_handle(this->m_p_descriptor_heap_render_target_view->GetCPUDescriptorHandleForHeapStart()); + + for (auto i = 0; i < RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT; ++i) + { + ID3D12Resource* p_back_buffer{}; + + RMLUI_DX_ASSERTMSG(this->m_p_swapchain->GetBuffer(i, IID_PPV_ARGS(&p_back_buffer)), "failed to GetBuffer from swapchain"); + + this->m_p_device->CreateRenderTargetView(p_back_buffer, nullptr, rtv_handle); + + this->m_backbuffers_resources[i] = p_back_buffer; + + rtv_handle.Offset(rtv_size); + } + } + } + } +} + +void RenderInterface_DX12::Destroy_Resource_RenderTagetViews() +{ + for (ID3D12Resource* p_backbuffer : this->m_backbuffers_resources) + { + RMLUI_ASSERT(p_backbuffer && "it is strange must be always valid pointer!"); + + if (p_backbuffer) + { + p_backbuffer->Release(); + } + } + + for (int i = 0; i < RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT; ++i) + { + this->m_backbuffers_resources[i] = nullptr; + } +} + +void RenderInterface_DX12::Create_Resource_For_Shaders(void) +{ + this->Create_Resource_For_Shaders_ConstantBufferHeap(); +} + +void RenderInterface_DX12::Create_Resource_For_Shaders_ConstantBufferHeap(void) +{ + RMLUI_ASSERT(this->m_p_allocator && "must be valid when you call this method!"); + RMLUI_ASSERT(RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT >= 1 && "must be non zero!"); + + // todo: delete + // this->m_constantbuffer.Set_AllocInfo(this->m_manager_buffer.Alloc_ConstantBuffer(&this->m_constantbuffer, 72)); +} + +void RenderInterface_DX12::Destroy_Resource_For_Shaders_ConstantBufferHeap(void) +{ + // todo: delete + // this->m_manager_buffer.Free_ConstantBuffer(&this->m_constantbuffer); +} + +void RenderInterface_DX12::Destroy_Resource_For_Shaders(void) +{ + for (auto& vec_cb_per_frame : this->m_constantbuffers) + { + for (auto& cb : vec_cb_per_frame) + { + this->m_manager_buffer.Free_ConstantBuffer(&cb); + } + } + + this->m_constantbuffers[0].clear(); + + this->Update_PendingForDeletion_Geometry(); + // this->Update_PendingForDeletion_Texture(); + + this->Destroy_Resource_For_Shaders_ConstantBufferHeap(); +} + +void RenderInterface_DX12::Free_Geometry(RenderInterface_DX12::GeometryHandleType* p_handle) +{ + RMLUI_ASSERT(p_handle && "invalid handle"); + + if (p_handle) + { + this->m_manager_buffer.Free_Geometry(p_handle); + delete p_handle; + } +} + +void RenderInterface_DX12::Free_Texture(RenderInterface_DX12::TextureHandleType* p_handle) +{ + RMLUI_ASSERT(p_handle && "must be valid!"); + + if (p_handle) + { + #ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12] Destroyed texture: [%s]", p_handle->Get_ResourceName().c_str()); + #endif + + this->m_manager_texture.Free_Texture(p_handle); + delete p_handle; + } +} + +void RenderInterface_DX12::Update_PendingForDeletion_Geometry() +{ + for (auto& p_handle : this->m_pending_for_deletion_geometry) + { + this->Free_Geometry(p_handle); + } + + this->m_pending_for_deletion_geometry.clear(); +} + +void RenderInterface_DX12::Update_PendingForDeletion_Texture() +{ + for (auto& p_handle : this->m_pending_for_deletion_textures) + { + this->Free_Texture(p_handle); + } + + this->m_pending_for_deletion_textures.clear(); +} + +void RenderInterface_DX12::Create_Resource_Pipelines() +{ + for (auto& vec_cb : this->m_constantbuffers) + { + vec_cb.resize(RMLUI_RENDER_BACKEND_FIELD_PREALLOCATED_CONSTANTBUFFERS); + } + + for (auto& vec_cb : this->m_constantbuffers) + { + for (auto& cb : vec_cb) + { + const auto& info = this->m_manager_buffer.Alloc_ConstantBuffer(&cb, 72); + cb.Set_AllocInfo(info); + } + } + + this->m_pending_for_deletion_geometry.reserve(RMLUI_RENDER_BACKEND_FIELD_PREALLOCATED_CONSTANTBUFFERS); + this->m_pending_for_deletion_textures.reserve(RMLUI_RENDER_BACKEND_FIELD_PREALLOCATED_CONSTANTBUFFERS); + + this->Create_Resource_For_Shaders(); + this->Create_Resource_Pipeline_BlendMask(); + this->Create_Resource_Pipeline_Blur(); + this->Create_Resource_Pipeline_Color(); + this->Create_Resource_Pipeline_ColorMatrix(); + this->Create_Resource_Pipeline_Count(); + this->Create_Resource_Pipeline_Creation(); + this->Create_Resource_Pipeline_DropShadow(); + this->Create_Resource_Pipeline_Gradient(); + this->Create_Resource_Pipeline_Passthrough(); + this->Create_Resource_Pipeline_Texture(); +} + +void RenderInterface_DX12::Create_Resource_Pipeline_Color() +{ + RMLUI_ASSERT(this->m_p_device && "must be valid when we call this method!"); + RMLUI_ASSERT(Rml::GetFileInterface() && "must be valid when we call this method!"); + + auto* p_filesystem = Rml::GetFileInterface(); + + if (this->m_p_device && p_filesystem) + { + /* + D3D12_DESCRIPTOR_RANGE ranges[1]; + ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; + ranges[0].NumDescriptors = 1; + ranges[0].BaseShaderRegister = 0; + ranges[0].RegisterSpace = 0; + ranges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + + D3D12_ROOT_DESCRIPTOR_TABLE table{}; + table.NumDescriptorRanges = 1; + table.pDescriptorRanges = ranges; + */ + + D3D12_ROOT_DESCRIPTOR descriptor_cbv; + descriptor_cbv.RegisterSpace = 0; + descriptor_cbv.ShaderRegister = 0; + + D3D12_ROOT_PARAMETER parameters[1]; + // parameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + // parameters[0].DescriptorTable = table; + parameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + parameters[0].Descriptor = descriptor_cbv; + parameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; + + CD3DX12_ROOT_SIGNATURE_DESC desc_rootsignature; + desc_rootsignature.Init(_countof(parameters), parameters, 0, nullptr, + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS); + + ID3DBlob* p_signature{}; + ID3DBlob* p_error{}; + auto status = D3D12SerializeRootSignature(&desc_rootsignature, D3D_ROOT_SIGNATURE_VERSION_1, &p_signature, &p_error); + RMLUI_DX_ASSERTMSG(status, "failed to D3D12SerializeRootSignature"); + + status = this->m_p_device->CreateRootSignature(0, p_signature->GetBufferPointer(), p_signature->GetBufferSize(), + IID_PPV_ARGS(&this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Always)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateRootSignature"); + + // stencil version + status = this->m_p_device->CreateRootSignature(0, p_signature->GetBufferPointer(), p_signature->GetBufferSize(), + IID_PPV_ARGS(&this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Intersect)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateRootSignature"); + + status = this->m_p_device->CreateRootSignature(0, p_signature->GetBufferPointer(), p_signature->GetBufferSize(), + IID_PPV_ARGS(&this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Set)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateRootSignature"); + + status = this->m_p_device->CreateRootSignature(0, p_signature->GetBufferPointer(), p_signature->GetBufferSize(), + IID_PPV_ARGS(&this->m_root_signatures[static_cast(ProgramId::Color_Stencil_SetInverse)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateRootSignature"); + + status = this->m_p_device->CreateRootSignature(0, p_signature->GetBufferPointer(), p_signature->GetBufferSize(), + IID_PPV_ARGS(&this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Equal)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateRootSignature"); + + status = this->m_p_device->CreateRootSignature(0, p_signature->GetBufferPointer(), p_signature->GetBufferSize(), + IID_PPV_ARGS(&this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Disabled)])); + RMLUI_DX_ASSERTMSG(status, "failed to Color_Stencil_Disabled"); + + if (p_signature) + { + p_signature->Release(); + p_signature = nullptr; + } + + if (p_error) + { + p_error->Release(); + p_error = nullptr; + } + + ID3DBlob* p_shader_vertex{}; + ID3DBlob* p_shader_pixel{}; + ID3DBlob* p_error_buff{}; + + status = D3DCompile(pShaderSourceText_Vertex, sizeof(pShaderSourceText_Vertex), nullptr, nullptr, nullptr, "main", "vs_5_0", + this->m_default_shader_flags, 0, &p_shader_vertex, &p_error_buff); + RMLUI_DX_ASSERTMSG(status, "failed to D3DCompile"); + + #ifdef RMLUI_DX_DEBUG + if (FAILED(status)) + { + Rml::Log::Message(Rml::Log::Type::LT_ERROR, "failed to compile shader: %s", (char*)p_error_buff->GetBufferPointer()); + } + #endif + + if (p_error_buff) + { + p_error_buff->Release(); + p_error_buff = nullptr; + } + + status = D3DCompile(pShaderSourceText_Color, sizeof(pShaderSourceText_Color), nullptr, nullptr, nullptr, "main", "ps_5_0", + this->m_default_shader_flags, 0, &p_shader_pixel, &p_error_buff); + RMLUI_DX_ASSERTMSG(status, "failed to D3DCompile"); + + #ifdef RMLUI_DX_DEBUG + if (FAILED(status)) + { + Rml::Log::Message(Rml::Log::Type::LT_ERROR, "failed to compile shader: %s", (char*)(p_error_buff->GetBufferPointer())); + } + #endif + + if (p_error_buff) + { + p_error_buff->Release(); + p_error_buff = nullptr; + } + + D3D12_SHADER_BYTECODE desc_bytecode_pixel_shader = {}; + desc_bytecode_pixel_shader.BytecodeLength = p_shader_pixel->GetBufferSize(); + desc_bytecode_pixel_shader.pShaderBytecode = p_shader_pixel->GetBufferPointer(); + + D3D12_SHADER_BYTECODE desc_bytecode_vertex_shader = {}; + desc_bytecode_vertex_shader.BytecodeLength = p_shader_vertex->GetBufferSize(); + desc_bytecode_vertex_shader.pShaderBytecode = p_shader_vertex->GetBufferPointer(); + + D3D12_INPUT_ELEMENT_DESC desc_input_layout_elements[] = { + {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 8, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}}; + + D3D12_INPUT_LAYOUT_DESC desc_input_layout = {}; + + desc_input_layout.NumElements = sizeof(desc_input_layout_elements) / sizeof(D3D12_INPUT_ELEMENT_DESC); + desc_input_layout.pInputElementDescs = desc_input_layout_elements; + + D3D12_RASTERIZER_DESC desc_rasterizer = {}; + + desc_rasterizer.FillMode = D3D12_FILL_MODE_SOLID; + desc_rasterizer.CullMode = D3D12_CULL_MODE_NONE; + desc_rasterizer.FrontCounterClockwise = FALSE; + desc_rasterizer.DepthBias = D3D12_DEFAULT_DEPTH_BIAS; + desc_rasterizer.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP; + desc_rasterizer.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS; + desc_rasterizer.DepthClipEnable = FALSE; + desc_rasterizer.MultisampleEnable = FALSE; + desc_rasterizer.AntialiasedLineEnable = FALSE; + desc_rasterizer.ForcedSampleCount = 0; + desc_rasterizer.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; + + D3D12_BLEND_DESC desc_blend_state = {}; + + desc_blend_state.AlphaToCoverageEnable = FALSE; + desc_blend_state.IndependentBlendEnable = FALSE; + const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = { + TRUE, + FALSE, + D3D12_BLEND_SRC_ALPHA, + D3D12_BLEND_INV_SRC_ALPHA, + D3D12_BLEND_OP_ADD, + D3D12_BLEND_SRC_ALPHA, + D3D12_BLEND_INV_SRC_ALPHA, + D3D12_BLEND_OP_SUBTRACT, + D3D12_LOGIC_OP_NOOP, + D3D12_COLOR_WRITE_ENABLE_ALL, + }; + for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) + desc_blend_state.RenderTarget[i] = defaultRenderTargetBlendDesc; + + D3D12_DEPTH_STENCIL_DESC desc_depth_stencil = {}; + + desc_depth_stencil.DepthEnable = FALSE; + desc_depth_stencil.StencilEnable = TRUE; + desc_depth_stencil.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; + desc_depth_stencil.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS; + desc_depth_stencil.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK; + desc_depth_stencil.StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK; + + desc_depth_stencil.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS; + desc_depth_stencil.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; + + desc_depth_stencil.BackFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.BackFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.BackFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS; + desc_depth_stencil.BackFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC desc_pipeline = {}; + + desc_pipeline.InputLayout = desc_input_layout; + desc_pipeline.pRootSignature = this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Always)]; + desc_pipeline.VS = desc_bytecode_vertex_shader; + desc_pipeline.PS = desc_bytecode_pixel_shader; + desc_pipeline.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + desc_pipeline.RTVFormats[0] = RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT; + desc_pipeline.DSVFormat = RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT; + desc_pipeline.SampleDesc = this->m_desc_sample; + desc_pipeline.SampleMask = 0xffffffff; + desc_pipeline.RasterizerState = desc_rasterizer; + desc_pipeline.BlendState = desc_blend_state; + desc_pipeline.DepthStencilState = desc_depth_stencil; + desc_pipeline.NumRenderTargets = 1; + + status = this->m_p_device->CreateGraphicsPipelineState(&desc_pipeline, + IID_PPV_ARGS(&this->m_pipelines[static_cast(ProgramId::Color_Stencil_Always)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateGraphicsPipelineState (color)"); + + desc_depth_stencil.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilPassOp = D3D12_STENCIL_OP_INCR; + desc_depth_stencil.BackFace = desc_depth_stencil.FrontFace; + + desc_pipeline.DepthStencilState = desc_depth_stencil; + desc_pipeline.pRootSignature = this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Intersect)]; + + desc_blend_state.AlphaToCoverageEnable = FALSE; + desc_blend_state.IndependentBlendEnable = FALSE; + const D3D12_RENDER_TARGET_BLEND_DESC editedRenderTargetBlend = { + TRUE, + FALSE, + D3D12_BLEND_SRC_ALPHA, + D3D12_BLEND_INV_SRC_ALPHA, + D3D12_BLEND_OP_ADD, + D3D12_BLEND_SRC_ALPHA, + D3D12_BLEND_INV_SRC_ALPHA, + D3D12_BLEND_OP_SUBTRACT, + D3D12_LOGIC_OP_NOOP, + 0, + }; + for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) + desc_blend_state.RenderTarget[i] = editedRenderTargetBlend; + + desc_pipeline.BlendState = desc_blend_state; + + status = this->m_p_device->CreateGraphicsPipelineState(&desc_pipeline, + IID_PPV_ARGS(&this->m_pipelines[static_cast(ProgramId::Color_Stencil_Intersect)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateGraphicsPipelineState (color_stencil_intersect)"); + + desc_depth_stencil.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE; + desc_depth_stencil.BackFace = desc_depth_stencil.FrontFace; + + desc_pipeline.DepthStencilState = desc_depth_stencil; + desc_pipeline.pRootSignature = this->m_root_signatures[static_cast(ProgramId::Color_Stencil_SetInverse)]; + + status = this->m_p_device->CreateGraphicsPipelineState(&desc_pipeline, + IID_PPV_ARGS(&this->m_pipelines[static_cast(ProgramId::Color_Stencil_SetInverse)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateGraphicsPipelineState (color_stencil_setinverse)"); + + desc_depth_stencil.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE; + desc_depth_stencil.BackFace = desc_depth_stencil.FrontFace; + + desc_pipeline.DepthStencilState = desc_depth_stencil; + desc_pipeline.pRootSignature = this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Set)]; + + status = this->m_p_device->CreateGraphicsPipelineState(&desc_pipeline, + IID_PPV_ARGS(&this->m_pipelines[static_cast(ProgramId::Color_Stencil_Set)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateGraphicsPipelineState (color_stencil_set)"); + + desc_depth_stencil.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL; + desc_depth_stencil.BackFace = desc_depth_stencil.FrontFace; + + desc_pipeline.DepthStencilState = desc_depth_stencil; + desc_pipeline.pRootSignature = this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Equal)]; + + desc_blend_state.AlphaToCoverageEnable = FALSE; + desc_blend_state.IndependentBlendEnable = FALSE; + const D3D12_RENDER_TARGET_BLEND_DESC editedRenderTargetBlend2 = { + TRUE, + FALSE, + D3D12_BLEND_SRC_ALPHA, + D3D12_BLEND_INV_SRC_ALPHA, + D3D12_BLEND_OP_ADD, + D3D12_BLEND_SRC_ALPHA, + D3D12_BLEND_INV_SRC_ALPHA, + D3D12_BLEND_OP_SUBTRACT, + D3D12_LOGIC_OP_NOOP, + D3D12_COLOR_WRITE_ENABLE_ALL, + }; + for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) + desc_blend_state.RenderTarget[i] = editedRenderTargetBlend2; + + desc_pipeline.BlendState = desc_blend_state; + + status = this->m_p_device->CreateGraphicsPipelineState(&desc_pipeline, + IID_PPV_ARGS(&this->m_pipelines[static_cast(ProgramId::Color_Stencil_Equal)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateGraphicsPipelineState (Color_Stencil_Equal)"); + + desc_depth_stencil.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL; + desc_depth_stencil.BackFace = desc_depth_stencil.FrontFace; + desc_depth_stencil.StencilEnable = FALSE; + + desc_pipeline.DepthStencilState = desc_depth_stencil; + desc_pipeline.pRootSignature = this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Disabled)]; + + desc_pipeline.BlendState = desc_blend_state; + + status = this->m_p_device->CreateGraphicsPipelineState(&desc_pipeline, + IID_PPV_ARGS(&this->m_pipelines[static_cast(ProgramId::Color_Stencil_Disabled)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateGraphicsPipelineState (Color_Stencil_Disabled)"); + + #ifdef RMLUI_DX_DEBUG + this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Always)]->SetName( + TEXT("[D3D12][Debug Name] root signature of pipeline Color_Stencil_Always")); + this->m_pipelines[static_cast(ProgramId::Color_Stencil_Always)]->SetName(TEXT("[D3D12][Debug Name] pipeline Color_Stencil_Always")); + + this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Set)]->SetName( + TEXT("[D3D12][Debug Name] root signature of pipeline Color_Stencil_Set")); + this->m_pipelines[static_cast(ProgramId::Color_Stencil_Set)]->SetName(TEXT("[D3D12][Debug Name] pipeline Color_Stencil_Set")); + + this->m_root_signatures[static_cast(ProgramId::Color_Stencil_SetInverse)]->SetName( + TEXT("[D3D12][Debug Name] root signature of pipeline Color_Stencil_SetInverse")); + this->m_pipelines[static_cast(ProgramId::Color_Stencil_SetInverse)]->SetName( + TEXT("[D3D12][Debug Name] pipeline Color_Stencil_SetInverse")); + + this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Intersect)]->SetName( + TEXT("[D3D12][Debug Name] root signature of pipeline Color_Stencil_Intersect")); + this->m_pipelines[static_cast(ProgramId::Color_Stencil_Intersect)]->SetName( + TEXT("[D3D12][Debug Name] pipeline Color_Stencil_Intersect")); + + this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Equal)]->SetName( + TEXT("[D3D12][Debug Name] root signature of pipeline Color_Stencil_Equal")); + this->m_pipelines[static_cast(ProgramId::Color_Stencil_Equal)]->SetName(TEXT("[D3D12][Debug Name] pipeline Color_Stencil_Equal")); + + this->m_root_signatures[static_cast(ProgramId::Color_Stencil_Disabled)]->SetName( + TEXT("[D3D12][Debug Name] root signature of pipeline Color_Stencil_Disabled")); + this->m_pipelines[static_cast(ProgramId::Color_Stencil_Disabled)]->SetName(TEXT("[D3D12][Debug Name] pipeline Color_Stencil_Disabled")); + #endif + } +} + +void RenderInterface_DX12::Create_Resource_Pipeline_Texture() +{ + RMLUI_ASSERT(this->m_p_device && "must be valid when we call this method!"); + RMLUI_ASSERT(Rml::GetFileInterface() && "must be valid when we call this method!"); + + auto* p_filesystem = Rml::GetFileInterface(); + + if (this->m_p_device && p_filesystem) + { + D3D12_DESCRIPTOR_RANGE ranges[1]; + ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + ranges[0].NumDescriptors = 1; + ranges[0].BaseShaderRegister = 0; + ranges[0].RegisterSpace = 0; + ranges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + + /* + ranges[1].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; + ranges[1].NumDescriptors = 1; + ranges[1].BaseShaderRegister = 0; + ranges[1].RegisterSpace = 0; + ranges[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + */ + + D3D12_ROOT_DESCRIPTOR_TABLE table{}; + table.NumDescriptorRanges = sizeof(ranges) / sizeof(decltype(ranges[0])); + table.pDescriptorRanges = ranges; + + D3D12_ROOT_DESCRIPTOR descriptor_cbv{}; + descriptor_cbv.RegisterSpace = 0; + descriptor_cbv.ShaderRegister = 0; + + D3D12_ROOT_PARAMETER parameters[2]; + parameters[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + parameters[1].DescriptorTable = table; + parameters[1].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + + parameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV; + parameters[0].Descriptor = descriptor_cbv; + parameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX; + + D3D12_STATIC_SAMPLER_DESC sampler = {}; + sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; + sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + sampler.MipLODBias = 0; + sampler.MaxAnisotropy = 0; + sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; + sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK; + sampler.MinLOD = 0.0f; + sampler.MaxLOD = D3D12_FLOAT32_MAX; + sampler.ShaderRegister = 0; + sampler.RegisterSpace = 0; + sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + + CD3DX12_ROOT_SIGNATURE_DESC desc_rootsignature; + desc_rootsignature.Init(_countof(parameters), parameters, 1, &sampler, + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS); + + ID3DBlob* p_signature{}; + ID3DBlob* p_error{}; + auto status = D3D12SerializeRootSignature(&desc_rootsignature, D3D_ROOT_SIGNATURE_VERSION_1, &p_signature, &p_error); + #ifdef RMLUI_DX_DEBUG + if (FAILED(status)) + { + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12][ERROR] failed to D3D12SerializeRootSignature: %s", + (char*)p_error->GetBufferPointer()); + } + #endif + + RMLUI_DX_ASSERTMSG(status, "failed to D3D12SerializeRootSignature"); + + status = this->m_p_device->CreateRootSignature(0, p_signature->GetBufferPointer(), p_signature->GetBufferSize(), + IID_PPV_ARGS(&this->m_root_signatures[static_cast(ProgramId::Texture_Stencil_Always)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateRootSignature"); + + status = this->m_p_device->CreateRootSignature(0, p_signature->GetBufferPointer(), p_signature->GetBufferSize(), + IID_PPV_ARGS(&this->m_root_signatures[static_cast(ProgramId::Texture_Stencil_Equal)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateRootSignature"); + + status = this->m_p_device->CreateRootSignature(0, p_signature->GetBufferPointer(), p_signature->GetBufferSize(), + IID_PPV_ARGS(&this->m_root_signatures[static_cast(ProgramId::Texture_Stencil_Disabled)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateRootSignature"); + + if (p_signature) + { + p_signature->Release(); + p_signature = nullptr; + } + + if (p_error) + { + p_error->Release(); + p_error = nullptr; + } + + ID3DBlob* p_shader_vertex{}; + ID3DBlob* p_shader_pixel{}; + ID3DBlob* p_error_buff{}; + + const D3D_SHADER_MACRO macros[] = {"RMLUI_PREMULTIPLIED_ALPHA", NULL, NULL, NULL}; + + status = D3DCompile(pShaderSourceText_Vertex, sizeof(pShaderSourceText_Vertex), nullptr, macros, nullptr, "main", "vs_5_0", + this->m_default_shader_flags, 0, &p_shader_vertex, &p_error_buff); + RMLUI_DX_ASSERTMSG(status, "failed to D3DCompile"); + + #ifdef RMLUI_DX_DEBUG + if (FAILED(status)) + { + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12][ERROR] failed to compile shader: %s", (char*)p_error_buff->GetBufferPointer()); + } + #endif + + if (p_error_buff) + { + p_error_buff->Release(); + p_error_buff = nullptr; + } + + status = D3DCompile(pShaderSourceText_Texture, sizeof(pShaderSourceText_Texture), nullptr, nullptr, nullptr, "main", "ps_5_0", + this->m_default_shader_flags, 0, &p_shader_pixel, &p_error_buff); + #ifdef RMLUI_DX_DEBUG + if (FAILED(status)) + { + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12][ERROR] failed to compile shader: %s", + (char*)(p_error_buff->GetBufferPointer())); + } + #endif + RMLUI_DX_ASSERTMSG(status, "failed to D3DCompile"); + + if (p_error_buff) + { + p_error_buff->Release(); + p_error_buff = nullptr; + } + + D3D12_SHADER_BYTECODE desc_bytecode_pixel_shader = {}; + desc_bytecode_pixel_shader.BytecodeLength = p_shader_pixel->GetBufferSize(); + desc_bytecode_pixel_shader.pShaderBytecode = p_shader_pixel->GetBufferPointer(); + + D3D12_SHADER_BYTECODE desc_bytecode_vertex_shader = {}; + desc_bytecode_vertex_shader.BytecodeLength = p_shader_vertex->GetBufferSize(); + desc_bytecode_vertex_shader.pShaderBytecode = p_shader_vertex->GetBufferPointer(); + + D3D12_INPUT_ELEMENT_DESC desc_input_layout_elements[] = { + {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 8, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}}; + + D3D12_INPUT_LAYOUT_DESC desc_input_layout = {}; + + desc_input_layout.NumElements = sizeof(desc_input_layout_elements) / sizeof(D3D12_INPUT_ELEMENT_DESC); + desc_input_layout.pInputElementDescs = desc_input_layout_elements; + + D3D12_RASTERIZER_DESC desc_rasterizer = {}; + + desc_rasterizer.AntialiasedLineEnable = FALSE; + desc_rasterizer.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; + desc_rasterizer.CullMode = D3D12_CULL_MODE_NONE; + desc_rasterizer.DepthBias = 0; + desc_rasterizer.DepthBiasClamp = 0; + desc_rasterizer.DepthClipEnable = FALSE; + desc_rasterizer.ForcedSampleCount = 0; + desc_rasterizer.FrontCounterClockwise = FALSE; + desc_rasterizer.MultisampleEnable = FALSE; + desc_rasterizer.FillMode = D3D12_FILL_MODE::D3D12_FILL_MODE_SOLID; + desc_rasterizer.SlopeScaledDepthBias = 0; + + D3D12_BLEND_DESC desc_blend_state = {}; + + desc_blend_state.AlphaToCoverageEnable = FALSE; + desc_blend_state.IndependentBlendEnable = FALSE; + const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = { + TRUE, + FALSE, + #ifdef RMLUI_PREMULTIPLIED_ALPHA + D3D12_BLEND_ONE, + #else + D3D12_BLEND_SRC_ALPHA, + #endif + D3D12_BLEND_INV_SRC_ALPHA, + D3D12_BLEND_OP_ADD, + #ifdef RMLUI_PREMULTIPLIED_ALPHA + D3D12_BLEND_ONE, + #else + D3D12_BLEND_SRC_ALPHA, + #endif + D3D12_BLEND_INV_SRC_ALPHA, + D3D12_BLEND_OP_ADD, + D3D12_LOGIC_OP_NOOP, + D3D12_COLOR_WRITE_ENABLE_ALL, + }; + for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) + desc_blend_state.RenderTarget[i] = defaultRenderTargetBlendDesc; + + D3D12_DEPTH_STENCIL_DESC desc_depth_stencil = {}; + + desc_depth_stencil.DepthEnable = FALSE; + desc_depth_stencil.StencilEnable = TRUE; + desc_depth_stencil.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; + desc_depth_stencil.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS; + desc_depth_stencil.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK; + desc_depth_stencil.StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK; + + desc_depth_stencil.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS; + desc_depth_stencil.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; + + desc_depth_stencil.BackFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.BackFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.BackFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS; + desc_depth_stencil.BackFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC desc_pipeline = {}; + + desc_pipeline.InputLayout = desc_input_layout; + desc_pipeline.pRootSignature = this->m_root_signatures[static_cast(ProgramId::Texture_Stencil_Always)]; + desc_pipeline.VS = desc_bytecode_vertex_shader; + desc_pipeline.PS = desc_bytecode_pixel_shader; + desc_pipeline.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + desc_pipeline.RTVFormats[0] = RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT; + desc_pipeline.DSVFormat = RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT; + desc_pipeline.SampleDesc = this->m_desc_sample; + desc_pipeline.SampleMask = 0xffffffff; + desc_pipeline.RasterizerState = desc_rasterizer; + desc_pipeline.BlendState = desc_blend_state; + desc_pipeline.NumRenderTargets = 1; + desc_pipeline.DepthStencilState = desc_depth_stencil; + + status = this->m_p_device->CreateGraphicsPipelineState(&desc_pipeline, + IID_PPV_ARGS(&this->m_pipelines[static_cast(ProgramId::Texture_Stencil_Always)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateGraphicsPipelineState (Texture_Stencil_Always)"); + + desc_depth_stencil.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL; + desc_depth_stencil.BackFace = desc_depth_stencil.FrontFace; + + desc_pipeline.DepthStencilState = desc_depth_stencil; + desc_pipeline.pRootSignature = this->m_root_signatures[static_cast(ProgramId::Texture_Stencil_Equal)]; + + status = this->m_p_device->CreateGraphicsPipelineState(&desc_pipeline, + IID_PPV_ARGS(&this->m_pipelines[static_cast(ProgramId::Texture_Stencil_Equal)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateGraphicsPipelineState (Texture_Stencil_Equal)"); + + desc_depth_stencil.StencilEnable = FALSE; + desc_depth_stencil.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL; + desc_depth_stencil.BackFace = desc_depth_stencil.FrontFace; + + desc_pipeline.DepthStencilState = desc_depth_stencil; + desc_pipeline.pRootSignature = this->m_root_signatures[static_cast(ProgramId::Texture_Stencil_Disabled)]; + + status = this->m_p_device->CreateGraphicsPipelineState(&desc_pipeline, + IID_PPV_ARGS(&this->m_pipelines[static_cast(ProgramId::Texture_Stencil_Disabled)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateGraphicsPipelineState (Texture_Stencil_Disabled)"); + + #ifdef RMLUI_DX_DEBUG + this->m_root_signatures[static_cast(ProgramId::Texture_Stencil_Always)]->SetName( + TEXT("[D3D12][Debug Name] root signature of pipeline Texture_Stencil_Always")); + this->m_pipelines[static_cast(ProgramId::Texture_Stencil_Always)]->SetName(TEXT("[D3D12][Debug Name] pipeline Texture_Stencil_Always")); + + this->m_root_signatures[static_cast(ProgramId::Texture_Stencil_Equal)]->SetName( + TEXT("[D3D12][Debug Name] root signature of pipeline Texture_Stencil_Equal")); + this->m_pipelines[static_cast(ProgramId::Texture_Stencil_Equal)]->SetName(TEXT("[D3D12][Debug Name] pipeline Texture_Stencil_Equal")); + + this->m_root_signatures[static_cast(ProgramId::Texture_Stencil_Disabled)]->SetName( + TEXT("[D3D12][Debug Name] root signature of pipeline Texture_Stencil_Disabled")); + this->m_pipelines[static_cast(ProgramId::Texture_Stencil_Disabled)]->SetName( + TEXT("[D3D12][Debug Name] pipeline Texture_Stencil_Disabled")); + #endif + } +} + +void RenderInterface_DX12::Create_Resource_Pipeline_Gradient() {} + +void RenderInterface_DX12::Create_Resource_Pipeline_Creation() {} + +void RenderInterface_DX12::Create_Resource_Pipeline_Passthrough() +{ + RMLUI_ASSERT(this->m_p_device && "must be valid when we call this method!"); + RMLUI_ASSERT(Rml::GetFileInterface() && "must be valid when we call this method!"); + + auto* p_filesystem = Rml::GetFileInterface(); + + if (this->m_p_device && p_filesystem) + { + D3D12_DESCRIPTOR_RANGE ranges[1]; + ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + ranges[0].NumDescriptors = 1; + ranges[0].BaseShaderRegister = 0; + ranges[0].RegisterSpace = 0; + ranges[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + + D3D12_ROOT_DESCRIPTOR_TABLE table{}; + table.NumDescriptorRanges = sizeof(ranges) / sizeof(decltype(ranges[0])); + table.pDescriptorRanges = ranges; + + D3D12_ROOT_PARAMETER parameters[1]; + parameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + parameters[0].DescriptorTable = table; + parameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + + D3D12_STATIC_SAMPLER_DESC sampler = {}; + sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; + sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP; + sampler.MipLODBias = 0; + sampler.MaxAnisotropy = 0; + sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; + sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK; + sampler.MinLOD = 0.0f; + sampler.MaxLOD = D3D12_FLOAT32_MAX; + sampler.ShaderRegister = 0; + sampler.RegisterSpace = 0; + sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + + CD3DX12_ROOT_SIGNATURE_DESC desc_rootsignature; + desc_rootsignature.Init(_countof(parameters), parameters, 1, &sampler, + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | D3D12_ROOT_SIGNATURE_FLAG_DENY_HULL_SHADER_ROOT_ACCESS | + D3D12_ROOT_SIGNATURE_FLAG_DENY_DOMAIN_SHADER_ROOT_ACCESS | D3D12_ROOT_SIGNATURE_FLAG_DENY_GEOMETRY_SHADER_ROOT_ACCESS); + + ID3DBlob* p_signature{}; + ID3DBlob* p_error{}; + auto status = D3D12SerializeRootSignature(&desc_rootsignature, D3D_ROOT_SIGNATURE_VERSION_1, &p_signature, &p_error); + #ifdef RMLUI_DX_DEBUG + if (FAILED(status)) + { + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12][ERROR] failed to D3D12SerializeRootSignature: %s", + (char*)p_error->GetBufferPointer()); + } + #endif + RMLUI_DX_ASSERTMSG(status, "failed to D3D12SerializeRootSignature"); + + status = this->m_p_device->CreateRootSignature(0, p_signature->GetBufferPointer(), p_signature->GetBufferSize(), + IID_PPV_ARGS(&this->m_root_signatures[static_cast(ProgramId::Passthrough)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateRootSignature"); + + if (p_signature) + { + p_signature->Release(); + p_signature = nullptr; + } + + if (p_error) + { + p_error->Release(); + p_error = nullptr; + } + + ID3DBlob* p_shader_vertex{}; + ID3DBlob* p_shader_pixel{}; + ID3DBlob* p_error_buff{}; + + const D3D_SHADER_MACRO macros[] = {"RMLUI_PREMULTIPLIED_ALPHA", NULL, NULL, NULL}; + + status = D3DCompile(pShaderSourceText_Vertex_PassThrough, sizeof(pShaderSourceText_Vertex_PassThrough), nullptr, macros, nullptr, "main", + "vs_5_0", this->m_default_shader_flags, 0, &p_shader_vertex, &p_error_buff); + RMLUI_DX_ASSERTMSG(status, "failed to D3DCompile"); + + #ifdef RMLUI_DX_DEBUG + if (FAILED(status)) + { + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12][ERROR] failed to compile shader: %s", (char*)p_error_buff->GetBufferPointer()); + } + #endif + + if (p_error_buff) + { + p_error_buff->Release(); + p_error_buff = nullptr; + } + + status = D3DCompile(pShaderSourceText_Pixel_Passthrough, sizeof(pShaderSourceText_Pixel_Passthrough), nullptr, nullptr, nullptr, "main", + "ps_5_0", this->m_default_shader_flags, 0, &p_shader_pixel, &p_error_buff); + #ifdef RMLUI_DX_DEBUG + if (FAILED(status)) + { + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12][ERROR] failed to compile shader: %s", + (char*)(p_error_buff->GetBufferPointer())); + } + #endif + RMLUI_DX_ASSERTMSG(status, "failed to D3DCompile"); + + if (p_error_buff) + { + p_error_buff->Release(); + p_error_buff = nullptr; + } + + D3D12_SHADER_BYTECODE desc_bytecode_pixel_shader = {}; + desc_bytecode_pixel_shader.BytecodeLength = p_shader_pixel->GetBufferSize(); + desc_bytecode_pixel_shader.pShaderBytecode = p_shader_pixel->GetBufferPointer(); + + D3D12_SHADER_BYTECODE desc_bytecode_vertex_shader = {}; + desc_bytecode_vertex_shader.BytecodeLength = p_shader_vertex->GetBufferSize(); + desc_bytecode_vertex_shader.pShaderBytecode = p_shader_vertex->GetBufferPointer(); + + D3D12_INPUT_ELEMENT_DESC desc_input_layout_elements[] = { + {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, 8, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}, + {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}}; + + D3D12_INPUT_LAYOUT_DESC desc_input_layout = {}; + + desc_input_layout.NumElements = sizeof(desc_input_layout_elements) / sizeof(D3D12_INPUT_ELEMENT_DESC); + desc_input_layout.pInputElementDescs = desc_input_layout_elements; + + D3D12_RASTERIZER_DESC desc_rasterizer = {}; + + desc_rasterizer.AntialiasedLineEnable = FALSE; + desc_rasterizer.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF; + desc_rasterizer.CullMode = D3D12_CULL_MODE_NONE; + desc_rasterizer.DepthBias = 0; + desc_rasterizer.DepthBiasClamp = 0; + desc_rasterizer.DepthClipEnable = FALSE; + desc_rasterizer.ForcedSampleCount = 0; + desc_rasterizer.FrontCounterClockwise = FALSE; + desc_rasterizer.MultisampleEnable = FALSE; + desc_rasterizer.FillMode = D3D12_FILL_MODE::D3D12_FILL_MODE_SOLID; + desc_rasterizer.SlopeScaledDepthBias = 0; + + D3D12_BLEND_DESC desc_blend_state = {}; + + desc_blend_state.AlphaToCoverageEnable = FALSE; + desc_blend_state.IndependentBlendEnable = FALSE; + const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc = { + TRUE, + FALSE, + #ifdef RMLUI_PREMULTIPLIED_ALPHA + D3D12_BLEND_ONE, + #else + D3D12_BLEND_SRC_ALPHA, + #endif + D3D12_BLEND_INV_SRC_ALPHA, + D3D12_BLEND_OP_ADD, + #ifdef RMLUI_PREMULTIPLIED_ALPHA + D3D12_BLEND_ONE, + #else + D3D12_BLEND_SRC_ALPHA, + #endif + D3D12_BLEND_INV_SRC_ALPHA, + D3D12_BLEND_OP_ADD, + D3D12_LOGIC_OP_NOOP, + D3D12_COLOR_WRITE_ENABLE_ALL, + }; + for (UINT i = 0; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) + desc_blend_state.RenderTarget[i] = defaultRenderTargetBlendDesc; + + D3D12_DEPTH_STENCIL_DESC desc_depth_stencil = {}; + + desc_depth_stencil.DepthEnable = FALSE; + desc_depth_stencil.StencilEnable = TRUE; + desc_depth_stencil.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL; + desc_depth_stencil.DepthFunc = D3D12_COMPARISON_FUNC_ALWAYS; + desc_depth_stencil.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK; + desc_depth_stencil.StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK; + + desc_depth_stencil.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS; + desc_depth_stencil.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; + + desc_depth_stencil.BackFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.BackFace.StencilFailOp = D3D12_STENCIL_OP_KEEP; + desc_depth_stencil.BackFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS; + desc_depth_stencil.BackFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC desc_pipeline = {}; + + desc_pipeline.InputLayout = desc_input_layout; + desc_pipeline.pRootSignature = this->m_root_signatures[static_cast(ProgramId::Passthrough)]; + desc_pipeline.VS = desc_bytecode_vertex_shader; + desc_pipeline.PS = desc_bytecode_pixel_shader; + desc_pipeline.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + desc_pipeline.RTVFormats[0] = RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT; + desc_pipeline.DSVFormat = RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT; + + // since it is used for presenting MSAA texture on screen, we create swapchain and all RTs as NO-MSAA, keep this in mind + // otherwise it is wrong to mix target texture with different sample count than swapchain's + desc_pipeline.SampleDesc.Count = 1; + desc_pipeline.SampleDesc.Quality = 0; + + desc_pipeline.SampleMask = 0xffffffff; + desc_pipeline.RasterizerState = desc_rasterizer; + desc_pipeline.BlendState = desc_blend_state; + desc_pipeline.NumRenderTargets = 1; + desc_pipeline.DepthStencilState = desc_depth_stencil; + + status = + this->m_p_device->CreateGraphicsPipelineState(&desc_pipeline, IID_PPV_ARGS(&this->m_pipelines[static_cast(ProgramId::Passthrough)])); + RMLUI_DX_ASSERTMSG(status, "failed to CreateGraphicsPipelineState (Passthrough)"); + + #ifdef RMLUI_DX_DEBUG + this->m_root_signatures[static_cast(ProgramId::Passthrough)]->SetName( + TEXT("[D3D12][Debug Name] root signature of pipeline Passthrough")); + this->m_pipelines[static_cast(ProgramId::Passthrough)]->SetName(TEXT("[D3D12][Debug Name] pipeline Passthrough")); + #endif + } +} + +void RenderInterface_DX12::Create_Resource_Pipeline_ColorMatrix() {} + +void RenderInterface_DX12::Create_Resource_Pipeline_BlendMask() {} + +void RenderInterface_DX12::Create_Resource_Pipeline_Blur() {} + +void RenderInterface_DX12::Create_Resource_Pipeline_DropShadow() {} + +void RenderInterface_DX12::Create_Resource_Pipeline_Count() {} + +void RenderInterface_DX12::Destroy_Resource_Pipelines() +{ + this->Destroy_Resource_For_Shaders(); + + if (this->m_p_depthstencil_resource) + { + this->Destroy_Resource_DepthStencil(); + } + + for (int i = 1; i < static_cast(ProgramId::Count); ++i) + { + if (this->m_pipelines[i]) + { + this->m_pipelines[i]->Release(); + this->m_pipelines[i] = nullptr; + } + + if (this->m_root_signatures[i]) + { + this->m_root_signatures[i]->Release(); + this->m_root_signatures[i] = nullptr; + } + } +} + +ID3DBlob* RenderInterface_DX12::Compile_Shader(const Rml::String& relative_path_to_shader, const char* entry_point, const char* shader_version, + UINT flags) +{ + return nullptr; +} + +bool RenderInterface_DX12::CheckTearingSupport() noexcept +{ + int result{}; + + IDXGIFactory4* pFactory4{}; + + if (SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&pFactory4)))) + { + IDXGIFactory5* pFactory5{}; + + if (SUCCEEDED(pFactory4->QueryInterface(IID_PPV_ARGS(&pFactory5)))) + { + if (FAILED(pFactory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &result, sizeof(result)))) + { + result = 0; + } + + if (pFactory5) + { + pFactory5->Release(); + } + } + } + + if (pFactory4) + { + pFactory4->Release(); + } + + return result == 1; +} + +IDXGIAdapter* RenderInterface_DX12::Get_Adapter(bool is_use_warp) noexcept +{ + IDXGIFactory4* p_factory{}; + + uint32_t create_flags{}; + + #ifdef RMLUI_DX_DEBUG + create_flags = DXGI_CREATE_FACTORY_DEBUG; + #endif + + RMLUI_DX_ASSERTMSG(CreateDXGIFactory2(create_flags, IID_PPV_ARGS(&p_factory)), "failed to CreateDXGIFactory2"); + + IDXGIAdapter* p_adapter{}; + IDXGIAdapter1* p_adapter1{}; + IDXGIAdapter4* p_adapter4{}; + + if (is_use_warp) + { + if (p_factory) + { + RMLUI_DX_ASSERTMSG(p_factory->EnumWarpAdapter(IID_PPV_ARGS(&p_adapter)), "failed to EnumAdapters"); + RMLUI_ASSERT(p_adapter && "returned invalid COM object from EnumWarpAdapter"); + + if (p_adapter) + { + p_adapter->QueryInterface(IID_PPV_ARGS(&p_adapter4)); + RMLUI_ASSERT(p_adapter4 && "returned invalid COM object from QueryInterface"); + this->PrintAdapterDesc(p_adapter); + } + } + } + else + { + if (p_factory) + { + size_t max_dedicated_video_memory{}; + + for (uint32_t i = 0; p_factory->EnumAdapters(i, &p_adapter) != DXGI_ERROR_NOT_FOUND; ++i) + { + if (p_adapter) + { + DXGI_ADAPTER_DESC1 desc; + + RMLUI_DX_ASSERTMSG(p_adapter->QueryInterface(IID_PPV_ARGS(&p_adapter1)), "failed to QueryInterface of IDXGIAdapter1"); + + if (p_adapter1) + { + p_adapter1->GetDesc1(&desc); + + ID3D12Device* p_test_device{}; + + if ((desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) == 0 && + SUCCEEDED(D3D12CreateDevice(p_adapter, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&p_test_device))) && + desc.DedicatedVideoMemory > max_dedicated_video_memory) + { + max_dedicated_video_memory = desc.DedicatedVideoMemory; + + // found 'device' with bigger memory means we found our discrete video card (not cpu integrated) + if (p_adapter4) + { + p_adapter4->Release(); + p_adapter4 = nullptr; + } + + RMLUI_DX_ASSERTMSG(p_adapter->QueryInterface(IID_PPV_ARGS(&p_adapter4)), "failed to QueryInterface of IDXGIAdapter4"); + + this->PrintAdapterDesc(p_adapter); + } + + if (p_test_device) + { + p_test_device->Release(); + } + } + + if (p_adapter) + { + p_adapter->Release(); + } + + if (p_adapter1) + { + p_adapter1->Release(); + p_adapter1 = nullptr; + } + } + } + } + } + + if (p_factory) + { + p_factory->Release(); + } + + if (p_adapter) + { + p_adapter->Release(); + } + + if (p_adapter1) + { + p_adapter1->Release(); + } + + return p_adapter4; +} + +void RenderInterface_DX12::PrintAdapterDesc(IDXGIAdapter* p_adapter) +{ + RMLUI_ASSERT(p_adapter && "you can't pass invalid argument"); + + if (p_adapter) + { + DXGI_ADAPTER_DESC desc; + p_adapter->GetDesc(&desc); + + char p_converted[_countof(desc.Description)]; + memset(p_converted, 0, sizeof(p_converted)); + + #ifdef UNICODE + sprintf(p_converted, "%ls", desc.Description); + #else + p_converted = desc.Description; + #endif + + float vid_mem_in_bytes = desc.DedicatedVideoMemory; + float vid_mem_in_kilobytes = vid_mem_in_bytes / 1024.0f; + float vid_mem_in_megabytes = vid_mem_in_kilobytes / 1024.0f; + float vid_mem_in_gigabytes = vid_mem_in_megabytes / 1024.0f; + + Rml::Log::Message(Rml::Log::LT_INFO, "Monitor[%s]\n VideoMemory[%f Bytes][%f Mbs][%f Gbs]\n", p_converted, vid_mem_in_bytes, + vid_mem_in_megabytes, vid_mem_in_gigabytes); + } +} + +#endif + +void RenderInterface_DX12::SetScissor(Rml::Rectanglei region, bool vertically_flip) +{ + if (region.Valid() != m_scissor.Valid()) + { + if (!region.Valid()) + { + this->m_is_scissor_was_set = false; + return; + } + } + + if (region.Valid() && vertically_flip) + { + region = VerticallyFlipped(region, this->m_height); + } + + if (region.Valid() && region != this->m_scissor) + { + if (this->m_p_command_graphics_list) + { + D3D12_RECT scissor; + scissor.left = region.Left(); + scissor.right = region.Right(); + scissor.bottom = region.Bottom(); + scissor.top = region.Top(); + this->m_p_command_graphics_list->RSSetScissorRects(1, &scissor); + this->m_is_scissor_was_set = true; + } + } + + // this->m_scissor = region; +} + +void RenderInterface_DX12::SubmitTransformUniform(ConstantBufferType& constant_buffer, const Rml::Vector2f& translation) +{ + static_assert((size_t)ProgramId::Count < RMLUI_RENDER_BACKEND_FIELD_MAXNUMPROGRAMS, "Maximum number of pipelines exceeded"); + + size_t program_index = (size_t)this->m_active_program_id; + + std::uint8_t* p_gpu_binding_start = reinterpret_cast(constant_buffer.Get_GPU_StartMemoryForBindingData()); + + // if (this->m_program_state_transform_dirty.test(program_index)) + { + RMLUI_ASSERT(p_gpu_binding_start && + "your allocated constant buffer must contain a valid pointer of beginning mapping of its GPU buffer. Otherwise you destroyed it!"); + + if (p_gpu_binding_start) + { + std::uint8_t* p_gpu_binding_offset_to_transform = p_gpu_binding_start + constant_buffer.Get_AllocInfo().Get_Offset(); + + std::memcpy(p_gpu_binding_offset_to_transform, this->m_constant_buffer_data_transform.data(), + sizeof(this->m_constant_buffer_data_transform)); + } + + // this->m_program_state_transform_dirty.set(program_index, false); + } + + if (p_gpu_binding_start) + { + std::uint8_t* p_gpu_binding_offset_to_translate = + p_gpu_binding_start + (constant_buffer.Get_AllocInfo().Get_Offset() + sizeof(this->m_constant_buffer_data_transform)); + + std::memcpy(p_gpu_binding_offset_to_translate, &translation.x, sizeof(translation)); + } +} + +void RenderInterface_DX12::UseProgram(ProgramId pipeline_id) +{ + RMLUI_ASSERT(pipeline_id < ProgramId::Count && "overflow, too big value for indexing"); + + if (pipeline_id != ProgramId::None) + { + RMLUI_ASSERT(this->m_pipelines[static_cast(pipeline_id)] && "you forgot to initialize or deleted!"); + RMLUI_ASSERT(this->m_root_signatures[static_cast(pipeline_id)] && "you forgot to initialize or deleted!"); + + this->m_p_command_graphics_list->SetPipelineState(this->m_pipelines[static_cast(pipeline_id)]); + this->m_p_command_graphics_list->SetGraphicsRootSignature(this->m_root_signatures[static_cast(pipeline_id)]); + + ID3D12DescriptorHeap* p_heaps[] = {this->m_p_descriptor_heap_shaders}; + this->m_p_command_graphics_list->SetDescriptorHeaps(_countof(p_heaps), p_heaps); + } + + this->m_active_program_id = pipeline_id; +} + +RenderInterface_DX12::ConstantBufferType* RenderInterface_DX12::Get_ConstantBuffer(uint32_t current_back_buffer_index) +{ + RMLUI_ASSERT(current_back_buffer_index != uint32_t(-1) && "invalid index!"); + + auto current_constant_buffer_index = this->m_constant_buffer_count_per_frame[current_back_buffer_index]; + + auto max_index = RMLUI_RENDER_BACKEND_FIELD_PREALLOCATED_CONSTANTBUFFERS - 1; + + if (this->m_constantbuffers[current_back_buffer_index].size() > RMLUI_RENDER_BACKEND_FIELD_PREALLOCATED_CONSTANTBUFFERS) + max_index = this->m_constantbuffers[current_back_buffer_index].size() - 1; + + if (current_constant_buffer_index > max_index) + { + // resizing... + for (auto& vec : this->m_constantbuffers) + { + vec.emplace_back(std::move(ConstantBufferType())); + } + +#ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12] allocated new constant buffer instance for frame[%d], current size of storage[%zu]", + current_back_buffer_index, this->m_constantbuffers.at(current_back_buffer_index).size()); +#endif + } + + auto& vec_cbs = this->m_constantbuffers.at(current_back_buffer_index); + + ConstantBufferType* p_result = &vec_cbs[current_constant_buffer_index]; + + if (p_result->Get_AllocInfo().Get_BufferIndex() == -1) + { + const auto& info_alloc = this->m_manager_buffer.Alloc_ConstantBuffer(p_result, 72); + p_result->Set_AllocInfo(info_alloc); + } + + ++this->m_constant_buffer_count_per_frame[current_back_buffer_index]; + + return p_result; +} + +RenderInterface_DX12* RmlDX12::Initialize(Rml::String* out_message, Backend::RmlRenderInitInfo* p_info) +{ + RenderInterface_DX12* p_result{}; + + if (p_info) + { + if (p_info->Is_FullInitialization()) + { + RMLUI_ASSERT(p_info->Get_WindowHandle() && "you must pass a valid window handle!"); + + p_result = new RenderInterface_DX12(p_info->Get_WindowHandle(), p_info->Is_UseVSync()); + } + else + { + RMLUI_ASSERT(p_info->Get_UserDevice() && "you must pass a valid pointer of Device"); + + p_result = new RenderInterface_DX12(p_info->Get_WindowHandle(), static_cast(p_info->Get_UserDevice()), nullptr, + p_info->Is_UseVSync()); + } + } + + p_result->Initialize(); + + return p_result; +} + +void RmlDX12::Shutdown(RenderInterface_DX12* p_instance) +{ + RMLUI_ASSERT(p_instance && "you must have a valid instance"); + + if (p_instance) + { + p_instance->Shutdown(); + } +} + +RenderInterface_DX12::BufferMemoryManager::BufferMemoryManager() : + m_descriptor_increment_size_srv_cbv_uav{}, m_size_for_allocation_in_bytes{}, m_size_alignment_in_bytes{}, m_p_device{}, m_p_allocator{}, + m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav{}, m_p_start_pointer_of_descriptor_heap_srv_cbv_uav{} +{} + +RenderInterface_DX12::BufferMemoryManager::~BufferMemoryManager() {} + +bool RenderInterface_DX12::BufferMemoryManager::Is_Initialized(void) const +{ + return static_cast(this->m_p_device != nullptr); +} + +void RenderInterface_DX12::BufferMemoryManager::Initialize(ID3D12Device* p_device, D3D12MA::Allocator* p_allocator, + OffsetAllocator::Allocator* p_offset_allocator_for_descriptor_heap_srv_cbv_uav, CD3DX12_CPU_DESCRIPTOR_HANDLE* p_handle, + uint32_t size_descriptor_srv_cbv_uav, size_t size_for_allocation, size_t size_alignment) +{ + RMLUI_ASSERT(p_allocator && "must be valid!"); + RMLUI_ASSERT(size_for_allocation && "must be greater than 0"); + RMLUI_ASSERT(size_alignment && "must be greater than 0"); + RMLUI_ASSERT(p_offset_allocator_for_descriptor_heap_srv_cbv_uav && "must be valid!"); + RMLUI_ASSERT(p_handle && "must be valid!"); + RMLUI_ASSERT(p_device && "must be valid!"); + + this->m_p_allocator = p_allocator; + this->m_size_for_allocation_in_bytes = size_for_allocation; + this->m_size_alignment_in_bytes = size_alignment; + this->m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav = p_offset_allocator_for_descriptor_heap_srv_cbv_uav; + this->m_p_start_pointer_of_descriptor_heap_srv_cbv_uav = p_handle; + this->m_descriptor_increment_size_srv_cbv_uav = size_descriptor_srv_cbv_uav; + this->m_p_device = p_device; + + this->Alloc_Buffer(size_for_allocation + +#ifdef RMLUI_DX_DEBUG + , + Rml::String("buffer[") + Rml::ToString(this->m_buffers.size()) + "]" +#endif + ); +} + +void RenderInterface_DX12::BufferMemoryManager::Shutdown() +{ + for (auto& pair : m_buffers) + { + auto* p_allocation = pair.first; + + if (p_allocation) + { + if (p_allocation->GetResource()) + { + p_allocation->GetResource()->Unmap(0, nullptr); + p_allocation->GetResource()->Release(); + } + + auto ref_count = p_allocation->Release(); + RMLUI_ASSERT(ref_count == 0 && "leak!"); + } + } + + for (auto* p_block : m_virtual_buffers) + { + if (p_block) + { + auto ref_count = p_block->Release(); + RMLUI_ASSERT(ref_count == 0 && "leak! (virtual block)"); + } + } + + if (this->m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav) + { + this->m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav = nullptr; + } + + if (this->m_p_allocator) + { + this->m_p_allocator = nullptr; + } +} + +void RenderInterface_DX12::BufferMemoryManager::Alloc_Vertex(const void* p_data, int num_vertices, size_t size_of_one_element_in_p_data, + GeometryHandleType* p_handle) +{ + RMLUI_ASSERT(p_data && "data for mapping to buffer must valid!"); + RMLUI_ASSERT(num_vertices && "amount of vertices must be greater than zero!"); + RMLUI_ASSERT(size_of_one_element_in_p_data > 0 && "size of one element must be greater than 0"); + RMLUI_ASSERT(p_handle && "must be valid!"); + + if (p_handle) + { + RMLUI_ASSERT(p_handle->Get_InfoVertex().Get_BufferIndex() == -1 && + "info is already initialized that means you didn't destroy your buffer! Something is wrong!"); + + p_handle->Set_NumVertices(num_vertices); + p_handle->Set_SizeOfOneVertex(size_of_one_element_in_p_data); + + GraphicsAllocationInfo info; + this->Alloc(info, num_vertices * size_of_one_element_in_p_data); + + void* p_writable_part = this->Get_WritableMemoryFromBufferByOffset(info); + + RMLUI_ASSERT(p_writable_part && "something is wrong!"); + + if (p_writable_part) + { + std::memcpy(p_writable_part, p_data, info.Get_Size()); + } + + p_handle->Set_InfoVertex(info); + +#ifdef RMLUI_DX_DEBUG + auto* p_block = this->m_virtual_buffers.at(info.Get_BufferIndex()); + + RMLUI_ASSERT(p_block && "can't be invalid!"); + + D3D12MA::Statistics stats; + p_block->GetStatistics(&stats); + + auto available_memory = stats.BlockBytes - stats.AllocationBytes; + + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, + "[DirectX-12] allocated vertex buffer with size[%zu] (in bytes) in buffer[%d] available memory for this buffer [%zu] (in bytes)", + info.Get_Size(), info.Get_BufferIndex(), available_memory); +#endif + } +} + +void RenderInterface_DX12::BufferMemoryManager::Alloc_Index(const void* p_data, int num_vertices, size_t size_of_one_element_in_p_data, + GeometryHandleType* p_handle) +{ + RMLUI_ASSERT(p_data && "data for mapping to buffer must valid!"); + RMLUI_ASSERT(num_vertices && "amount of vertices must be greater than zero!"); + RMLUI_ASSERT(size_of_one_element_in_p_data > 0 && "size of one element must be greater than 0"); + RMLUI_ASSERT(p_handle && "must be valid!"); + + if (p_handle) + { + RMLUI_ASSERT(p_handle->Get_InfoIndex().Get_BufferIndex() == -1 && + "info is already initialized that means you didn't destroy your buffer! Something is wrong!"); + + p_handle->Set_NumIndecies(num_vertices); + p_handle->Set_SizeOfOneIndex(size_of_one_element_in_p_data); + + GraphicsAllocationInfo info; + this->Alloc(info, num_vertices * size_of_one_element_in_p_data); + + void* p_writable_part = this->Get_WritableMemoryFromBufferByOffset(info); + + RMLUI_ASSERT(p_writable_part && "something is wrong!"); + + if (p_writable_part) + { + std::memcpy(p_writable_part, p_data, num_vertices * size_of_one_element_in_p_data); + } + + p_handle->Set_InfoIndex(info); + +#ifdef RMLUI_DX_DEBUG + auto* p_block = this->m_virtual_buffers.at(info.Get_BufferIndex()); + + RMLUI_ASSERT(p_block && "can't be invalid!"); + + D3D12MA::Statistics stats; + p_block->GetStatistics(&stats); + + auto available_memory = stats.BlockBytes - stats.AllocationBytes; + + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, + "[DirectX-12] allocated index buffer with size[%zu] (in bytes) in buffer[%d] available memory for this buffer [%zu] (in bytes)", + info.Get_Size(), info.Get_BufferIndex(), available_memory); +#endif + } +} + +RenderInterface_DX12::GraphicsAllocationInfo RenderInterface_DX12::BufferMemoryManager::Alloc_ConstantBuffer(ConstantBufferType* p_resource, + size_t size) +{ + RMLUI_ASSERT(!this->m_buffers.empty() && "you forgot to allocate buffer on initialize stage of this manager!"); + RMLUI_ASSERT(p_resource && "must be valid!"); + + GraphicsAllocationInfo result; + auto result_index = this->Alloc(result, size, 256); + + if (p_resource) + { + if (result_index != -1) + { +#ifdef RMLUI_DX_DEBUG + auto* p_block = this->m_virtual_buffers.at(result.Get_BufferIndex()); + + RMLUI_ASSERT(p_block && "can't be invalid!"); + + D3D12MA::Statistics stats; + p_block->GetStatistics(&stats); + + auto available_memory = stats.BlockBytes - stats.AllocationBytes; + + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, + "[DirectX-12] allocated constant buffer with size[%zu] (in bytes) in buffer[%d] available memory for this buffer [%zu] (in bytes)", + result.Get_Size(), result.Get_BufferIndex(), available_memory); +#endif + + auto* p_dx_allocation = this->m_buffers.at(result_index).first; + auto* p_dx_resource = p_dx_allocation->GetResource(); + + RMLUI_ASSERT(p_dx_allocation && "something is broken!"); + RMLUI_ASSERT(p_dx_resource && "something is broken!"); + + p_resource->Set_GPU_StartMemoryForBindingData(this->m_buffers.at(result_index).second); + + /* todo: delete + if (p_dx_resource) + { + auto descriptor_allocation = + this->m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav->allocate(this->m_descriptor_increment_size_srv_cbv_uav); + + // should we really here use microsoft-style's casting??? + auto offset_pointer = + SIZE_T(INT64(this->m_p_start_pointer_of_descriptor_heap_srv_cbv_uav->ptr) + INT64(descriptor_allocation.offset)); + + D3D12_CONSTANT_BUFFER_VIEW_DESC desc_cbv = {}; + desc_cbv.BufferLocation = p_dx_resource->GetGPUVirtualAddress() + result.Get_Offset(); + desc_cbv.SizeInBytes = size; + + D3D12_CPU_DESCRIPTOR_HANDLE cast; + cast.ptr = offset_pointer; + + this->m_p_device->CreateConstantBufferView(&desc_cbv, cast); + + p_resource->Set_Allocation_DescriptorHeap(descriptor_allocation); + } + */ + } + } + + return result; +} + +void RenderInterface_DX12::BufferMemoryManager::Free_ConstantBuffer(ConstantBufferType* p_constantbuffer) +{ + RMLUI_ASSERT(p_constantbuffer && "you must pass a valid object!"); + + if (p_constantbuffer) + { + const auto& info = p_constantbuffer->Get_AllocInfo(); + RMLUI_ASSERT(info.Get_BufferIndex() != -1 && "must be valid data of this info"); + +#ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12] deallocated constant buffer with size[%zu] in buffer[%d]", info.Get_Size(), + info.Get_BufferIndex()); +#endif + + // todo: delete + // this->m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav->free(p_constantbuffer->Get_Allocation_DescriptorHeap()); + + if (this->m_virtual_buffers.empty() == false) + { + auto* p_block = this->m_virtual_buffers.at(info.Get_BufferIndex()); + + if (p_block) + { + p_block->FreeAllocation(info.Get_VirtualAllocation()); + + GraphicsAllocationInfo invalidate; + p_constantbuffer->Set_AllocInfo(invalidate); + } + } + } +} + +void RenderInterface_DX12::BufferMemoryManager::Free_Geometry(GeometryHandleType* p_handle) +{ + RMLUI_ASSERT(p_handle && "must be valid!"); + RMLUI_ASSERT(p_handle->Get_InfoVertex().Get_BufferIndex() != -1 && "not initialized, maybe you passing twice for deallocation?"); + RMLUI_ASSERT(p_handle->Get_InfoIndex().Get_BufferIndex() != -1 && "not initialized, maybe you passing twice for deallocation?"); + + if (p_handle) + { + const auto& info_vertex = p_handle->Get_InfoVertex(); + const auto& info_index = p_handle->Get_InfoIndex(); + + if (this->m_virtual_buffers.empty() == false) + { + auto* p_block_vertex = this->m_virtual_buffers.at(info_vertex.Get_BufferIndex()); + auto* p_block_index = this->m_virtual_buffers.at(info_index.Get_BufferIndex()); + + if (p_block_vertex) + { +#ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12] deallocated vertex buffer with size[%zu] in buffer[%d]", + info_vertex.Get_Size(), info_vertex.Get_BufferIndex()); +#endif + + p_block_vertex->FreeAllocation(info_vertex.Get_VirtualAllocation()); + + GraphicsAllocationInfo invalidate; + p_handle->Set_InfoVertex(invalidate); + } + + if (p_block_index) + { +#ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12] deallocated index buffer with size[%zu] in buffer[%d]", + info_index.Get_Size(), info_index.Get_BufferIndex()); +#endif + + p_block_index->FreeAllocation(info_index.Get_VirtualAllocation()); + + GraphicsAllocationInfo invalidate; + p_handle->Set_InfoIndex(invalidate); + } + + // this->Free_ConstantBuffer(&p_handle->Get_ConstantBuffer()); + } + } +} + +void* RenderInterface_DX12::BufferMemoryManager::Get_WritableMemoryFromBufferByOffset(const GraphicsAllocationInfo& info) +{ + RMLUI_ASSERT(info.Get_BufferIndex() != -1 && "you pass not initialized graphics allocation info!"); + + void* p_result{}; + if (info.Get_BufferIndex() != -1) + { + std::uint8_t* p_begin = reinterpret_cast(this->m_buffers.at(info.Get_BufferIndex()).second); + + RMLUI_ASSERT(p_begin && "being pointer is not valid! it's terribly wrong thing!!!!"); + + p_result = p_begin + info.Get_Offset(); + } + + return p_result; +} + +D3D12MA::Allocation* RenderInterface_DX12::BufferMemoryManager::Get_BufferByIndex(int buffer_index) +{ + RMLUI_ASSERT(buffer_index >= 0 && "index must be valid!"); + RMLUI_ASSERT(buffer_index < this->m_buffers.size() && "overflow index!"); + + D3D12MA::Allocation* p_result{}; + + if (buffer_index >= 0) + { + if (buffer_index < this->m_buffers.size()) + { + p_result = this->m_buffers.at(buffer_index).first; + } + } + + return p_result; +} + +void RenderInterface_DX12::BufferMemoryManager::Alloc_Buffer(size_t size +#ifdef RMLUI_DX_DEBUG + , + const Rml::String& debug_name +#endif +) +{ + RMLUI_ASSERT(size && "must be greater than 0"); + + D3D12_RESOURCE_DESC desc_constantbuffer = {}; + desc_constantbuffer.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc_constantbuffer.Alignment = 0; + desc_constantbuffer.Width = size; + desc_constantbuffer.Height = 1; + desc_constantbuffer.DepthOrArraySize = 1; + desc_constantbuffer.MipLevels = 1; + desc_constantbuffer.Format = DXGI_FORMAT_UNKNOWN; + desc_constantbuffer.SampleDesc.Count = 1; + desc_constantbuffer.SampleDesc.Quality = 0; + desc_constantbuffer.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc_constantbuffer.Flags = D3D12_RESOURCE_FLAG_NONE; + + D3D12MA::ALLOCATION_DESC desc_allocation = {}; + desc_allocation.HeapType = D3D12_HEAP_TYPE_UPLOAD; + + ID3D12Resource* p_resource{}; + D3D12MA::Allocation* p_allocation{}; + + auto result = this->m_p_allocator->CreateResource(&desc_allocation, &desc_constantbuffer, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, + &p_allocation, IID_PPV_ARGS(&p_resource)); + + RMLUI_DX_ASSERTMSG(result, "failed to CreateResource"); + + void* p_begin_writable_data{}; + CD3DX12_RANGE range(0, 0); + result = p_allocation->GetResource()->Map(0, &range, &p_begin_writable_data); + + RMLUI_DX_ASSERTMSG(result, "failed to ID3D12Resource::Map"); + +#ifdef RMLUI_DX_DEBUG + p_allocation->SetName(RmlWin32::ConvertToUTF16(debug_name).c_str()); +#endif + + this->m_buffers.push_back({p_allocation, p_begin_writable_data}); + + D3D12MA::VIRTUAL_BLOCK_DESC desc_virtual = {}; + desc_virtual.Size = size; + + D3D12MA::VirtualBlock* p_block{}; + result = D3D12MA::CreateVirtualBlock(&desc_virtual, &p_block); + + RMLUI_DX_ASSERTMSG(result, "failed to D3D12MA::CreateVirtualBlock"); + + this->m_virtual_buffers.push_back(p_block); +} + +D3D12MA::VirtualBlock* RenderInterface_DX12::BufferMemoryManager::Get_AvailableBlock(size_t size_for_allocation, int* result_index) +{ + RMLUI_ASSERT(result_index && "must be valid part of memory!"); + + D3D12MA::VirtualBlock* p_result{}; + + int index{}; + for (auto* p_block : this->m_virtual_buffers) + { + if (p_block) + { + D3D12MA::Statistics stats; + p_block->GetStatistics(&stats); + + if ((stats.BlockBytes - stats.AllocationBytes) >= size_for_allocation) + { + p_result = p_block; + *result_index = index; + break; + } + } + ++index; + } + + return p_result; +} + +D3D12MA::VirtualBlock* RenderInterface_DX12::BufferMemoryManager::Get_NotOutOfMemoryAndAvailableBlock(size_t size_for_allocation, int* result_index) +{ + RMLUI_ASSERT(result_index && "must be valid part of memory!"); + RMLUI_ASSERT(*result_index != -1, + "use this method when you found of available block then tried to allocate from it but got out of memory status!"); + + D3D12MA::VirtualBlock* p_result{}; + + // we skip out of memory block since it shows to us as available + auto from = *result_index + 1; + + for (int i = from; i < this->m_virtual_buffers.size(); ++i) + { + auto* p_block = this->m_virtual_buffers.at(i); + if (p_block) + { + D3D12MA::Statistics stats; + p_block->GetStatistics(&stats); + + if ((stats.BlockBytes - stats.AllocationBytes) >= size_for_allocation) + { + p_result = p_block; + *result_index = i; + break; + } + } + } + + return p_result; +} + +int RenderInterface_DX12::BufferMemoryManager::Alloc(GraphicsAllocationInfo& info, size_t size, size_t alignment) +{ + RMLUI_ASSERT(!this->m_buffers.empty() && "you forgot to allocate buffer on initialize stage of this manager!"); + + // we don't want to use any recursions because it is slow af + constexpr int kHowManyRequestsWeCanDoForResolvingOutOfMemory = 15; + int result_index{-1}; + + if (alignment > 0) + size = AlignUp(size, alignment); + + if (!this->m_buffers.empty()) + { + auto* p_block = this->Get_AvailableBlock(size, &result_index); + + if (p_block) + { + D3D12MA::VIRTUAL_ALLOCATION_DESC desc_alloc = {}; + desc_alloc.Size = size; + + if (alignment % 2 == 0) + desc_alloc.Alignment = alignment; + + D3D12MA::VirtualAllocation alloc; + UINT64 offset{}; + + // TODO: make auto allocation system switchable for user when initialize render + // TODO: provide option need to allocate buffers if failed after first depth iterations? + // TODO: provide option for auto resolving large allocated size (otherwise if disabled and situation comes user will get assert) + auto status = p_block->Allocate(&desc_alloc, &alloc, &offset); + + if (status == E_OUTOFMEMORY) + { + bool bWasSucFoundInLoop{}; + for (int i = 0; i < kHowManyRequestsWeCanDoForResolvingOutOfMemory; ++i) + { + p_block = this->Get_NotOutOfMemoryAndAvailableBlock(size, &result_index); + + if (!p_block) + { + if (size > this->m_size_for_allocation_in_bytes) + { +#ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12] auto correction size for buffer from [%zu] to [%zu]", + this->m_size_for_allocation_in_bytes, size); +#endif + + this->m_size_for_allocation_in_bytes = size; + } + + this->Alloc_Buffer(this->m_size_for_allocation_in_bytes +#ifdef RMLUI_DX_DEBUG + , + Rml::String("buffer[") + Rml::ToString(this->m_buffers.size()) + "]" +#endif + ); + result_index = this->m_buffers.size() - 1; + } + + p_block = this->m_virtual_buffers.at(result_index); + + desc_alloc = {}; + desc_alloc.Size = size; + + if (alignment % 2 == 0) + desc_alloc.Alignment = alignment; + + auto status = p_block->Allocate(&desc_alloc, &alloc, &offset); + + if (status == E_OUTOFMEMORY) + { + continue; + } + else if (status == S_OK) + { + bWasSucFoundInLoop = true; + break; + } +#ifdef RMLUI_DX_DEBUG + else + { + RMLUI_ASSERT(false && "report to github"); + } +#endif + } + } + + // our heuristic depth iteration failed then, just allocate buffers + if (offset >= UINT64_MAX) + { + bool bWasSucFoundInLoop{}; + for (int i = 0; i < kHowManyRequestsWeCanDoForResolvingOutOfMemory; ++i) + { + p_block = this->Get_NotOutOfMemoryAndAvailableBlock(size, &result_index); + + if (!p_block) + { + if (size > this->m_size_for_allocation_in_bytes) + { +#ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12] auto correction size for buffer from [%zu] to [%zu]", + this->m_size_for_allocation_in_bytes, size); +#endif + + this->m_size_for_allocation_in_bytes = size; + } + + this->Alloc_Buffer(this->m_size_for_allocation_in_bytes +#ifdef RMLUI_DX_DEBUG + , + Rml::String("buffer[") + Rml::ToString(this->m_buffers.size()) + "]" +#endif + ); + result_index = this->m_buffers.size() - 1; + } + + p_block = this->m_virtual_buffers.at(result_index); + + desc_alloc = {}; + desc_alloc.Size = size; + + if (alignment % 2 == 0) + desc_alloc.Alignment = alignment; + + auto status = p_block->Allocate(&desc_alloc, &alloc, &offset); + + if (status == E_OUTOFMEMORY) + { + continue; + } + else if (status == S_OK) + { + bWasSucFoundInLoop = true; + break; + } +#ifdef RMLUI_DX_DEBUG + else + { + RMLUI_ASSERT(false && "report to github"); + } +#endif + } + + RMLUI_ASSERT(offset == UINT64_MAX && + "it was last greedy try for allocating, it is really hard case for handling (report and describe your case on github), try to " + "optimize your 'stuff' (very calmly saying) " + "by your own, it is only for really rare specials cases where user doesn't want to think but UI must survive no matter what"); + } + + info.Set_Size(size); + info.Set_VirtualAllocation(alloc); + info.Set_Offset(offset); + info.Set_BufferIndex(result_index); + } + else + { + if (size > this->m_size_for_allocation_in_bytes) + { +#ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12] auto correction size for buffer from [%zu] to [%zu]", + this->m_size_for_allocation_in_bytes, size); +#endif + + this->m_size_for_allocation_in_bytes = size; + } + + this->Alloc_Buffer(this->m_size_for_allocation_in_bytes +#ifdef RMLUI_DX_DEBUG + , + Rml::String("buffer[") + Rml::ToString(this->m_buffers.size()) + "]" +#endif + ); + + result_index = -1; + p_block = this->Get_AvailableBlock(size, &result_index); + + RMLUI_ASSERT(p_block && "can't be because in previous line of code you added new allocated fresh buffer!"); + + if (p_block) + { + D3D12MA::VIRTUAL_ALLOCATION_DESC desc_alloc = {}; + desc_alloc.Size = size; + + if (alignment % 2 == 0) + desc_alloc.Alignment = alignment; + + D3D12MA::VirtualAllocation alloc; + UINT64 offset{}; + + auto status = p_block->Allocate(&desc_alloc, &alloc, &offset); + + RMLUI_DX_ASSERTMSG(status, "failed to Allocate"); + + info.Set_Size(size); + info.Set_VirtualAllocation(alloc); + info.Set_Offset(offset); + info.Set_BufferIndex(result_index); + } + } + + this->TryToFreeAvailableBlock(); + } + + return result_index; +} + +void RenderInterface_DX12::BufferMemoryManager::TryToFreeAvailableBlock() +{ + Rml::Array::const_iterator, Rml::Vector>::const_iterator>, 1> + max_for_free; + + for (size_t i = 0; i < max_for_free.size(); ++i) + { + max_for_free[i].first = this->m_virtual_buffers.end(); + max_for_free[i].second = this->m_buffers.end(); + } + + int total_count{}; + int limit_for_break{max_for_free.size()}; + int index{}; + + for (auto cur = this->m_virtual_buffers.begin(); cur != this->m_virtual_buffers.end(); ++cur) + { + if (total_count == limit_for_break) + break; + + if (*cur) + { + auto* p_block = *cur; + D3D12MA::Statistics stats; + p_block->GetStatistics(&stats); + + if (stats.AllocationCount == 0 && stats.BlockCount == 0) + { + auto ref_count = p_block->Release(); + RMLUI_ASSERT(ref_count == 0, "leak"); + + ref_count = this->m_buffers.at(index).first->Release(); + RMLUI_ASSERT(ref_count == 0, "leak"); + + this->m_buffers.at(index).second = nullptr; + + max_for_free[total_count].first = cur; + max_for_free[total_count].second = this->m_buffers.begin() + index; + + ++total_count; + } + } + ++index; + } + + for (int i = 0; i < total_count; ++i) + { + auto& pair = max_for_free.at(i); + + this->m_virtual_buffers.erase(pair.first); + this->m_buffers.erase(pair.second); + } +} + +RenderInterface_DX12::TextureMemoryManager::TextureMemoryManager() : + m_size_for_placed_heap{}, m_size_limit_for_being_placed{}, m_size_srv_cbv_uav_descriptor{}, m_size_rtv_descriptor{}, m_size_dsv_descriptor{}, + m_fence_value{}, m_p_allocator{}, m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav{}, m_p_device{}, m_p_command_list{}, + m_p_command_allocator{}, m_p_descriptor_heap_srv{}, m_p_copy_queue{}, m_p_fence{}, m_p_fence_event{}, m_p_handle{}, m_p_renderer{}, + m_p_virtual_block_for_render_target_heap_allocations{}, m_p_virtual_block_for_depth_stencil_heap_allocations{}, m_p_descriptor_heap_rtv{} +{} + +RenderInterface_DX12::TextureMemoryManager::~TextureMemoryManager() {} + +bool RenderInterface_DX12::TextureMemoryManager::Is_Initialized(void) const +{ + return static_cast(this->m_p_device != nullptr); +} + +void RenderInterface_DX12::TextureMemoryManager::Initialize(D3D12MA::Allocator* p_allocator, + OffsetAllocator::Allocator* p_offset_allocator_for_descriptor_heap_srv_cbv_uav, ID3D12Device* p_device, ID3D12GraphicsCommandList* p_command_list, + ID3D12CommandAllocator* p_allocator_command, ID3D12DescriptorHeap* p_descriptor_heap_srv, ID3D12DescriptorHeap* p_descriptor_heap_rtv, + ID3D12DescriptorHeap* p_descriptor_heap_dsv, ID3D12CommandQueue* p_copy_queue, CD3DX12_CPU_DESCRIPTOR_HANDLE* p_handle, + RenderInterface_DX12* p_renderer, size_t size_for_placed_heap) +{ + RMLUI_ASSERT(p_allocator && "you must pass a valid allocator pointer"); + RMLUI_ASSERT(size_for_placed_heap > 0 && "there's no point in creating in such small heap"); + RMLUI_ASSERT(size_for_placed_heap != size_t(-1), "invalid value!"); + RMLUI_ASSERT(p_device && "must be valid!"); + RMLUI_ASSERT(p_command_list && "must be valid!"); + RMLUI_ASSERT(p_allocator_command && "must be valid!"); + RMLUI_ASSERT(p_descriptor_heap_srv && "must be valid!"); + RMLUI_ASSERT(p_copy_queue && "must be valid!"); + RMLUI_ASSERT(p_handle && "must be valid!"); + RMLUI_ASSERT(p_renderer && "must be valid!"); + RMLUI_ASSERT(p_offset_allocator_for_descriptor_heap_srv_cbv_uav && "must be valid!"); + RMLUI_ASSERT(p_descriptor_heap_rtv && "must be valid!"); + RMLUI_ASSERT(p_descriptor_heap_dsv && "must be valid!"); + + this->m_p_device = p_device; + this->m_p_allocator = p_allocator; + this->m_p_command_list = p_command_list; + this->m_p_command_allocator = p_allocator_command; + this->m_size_for_placed_heap = size_for_placed_heap; + this->m_p_descriptor_heap_srv = p_descriptor_heap_srv; + this->m_p_descriptor_heap_rtv = p_descriptor_heap_rtv; + this->m_p_descriptor_heap_dsv = p_descriptor_heap_dsv; + this->m_p_copy_queue = p_copy_queue; + this->m_p_renderer = p_renderer; + this->m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav = p_offset_allocator_for_descriptor_heap_srv_cbv_uav; + + // if I have 4 Mb then 1 Mb is optimal as a maximum size for determing that resource can be allocated as placed resource so we just take 25% from + // size_for_placed_heap value + this->m_size_limit_for_being_placed = size_t((double(size_for_placed_heap) / 100.0) * 25.0); + + if (this->m_p_device) + { + this->m_size_srv_cbv_uav_descriptor = this->m_p_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + this->m_size_rtv_descriptor = this->m_p_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + this->m_size_dsv_descriptor = this->m_p_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV); + } + + this->m_p_handle = p_handle; + + RMLUI_ASSERT(this->m_size_srv_cbv_uav_descriptor > 0 && "must be positive"); + RMLUI_ASSERT(this->m_size_rtv_descriptor > 0 && "must be positive"); + + RMLUI_ASSERT(this->m_size_limit_for_being_placed > 0 && this->m_size_limit_for_being_placed != size_t(-1), "something is wrong!"); + + if (this->m_p_device) + { + RMLUI_DX_ASSERTMSG(this->m_p_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&this->m_p_fence)), + "failed to create fence for upload queue"); + + this->m_p_fence_event = CreateEvent(NULL, FALSE, FALSE, TEXT("Event for copy queue fence")); + } + + if (this->m_p_descriptor_heap_rtv) + { + D3D12MA::VIRTUAL_BLOCK_DESC desc_block{}; + desc_block.Size = this->m_size_rtv_descriptor * RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_RTV; + + auto status = D3D12MA::CreateVirtualBlock(&desc_block, &this->m_p_virtual_block_for_render_target_heap_allocations); + + RMLUI_DX_ASSERTMSG(status, "failed to D3D12MA::CreateVirtualBlock (for rtv descriptor heap, texture manager)"); + } + + if (this->m_p_descriptor_heap_dsv) + { + D3D12MA::VIRTUAL_BLOCK_DESC desc_block{}; + desc_block.Size = this->m_size_dsv_descriptor * RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_DSV; + + auto status = D3D12MA::CreateVirtualBlock(&desc_block, &this->m_p_virtual_block_for_depth_stencil_heap_allocations); + + RMLUI_DX_ASSERTMSG(status, "failed to D3D12MA::CreateVirtualBlock (for dsv descriptor heap, texture manager)"); + } +} + +void RenderInterface_DX12::TextureMemoryManager::Shutdown() +{ + if (this->m_p_fence) + { + this->m_p_fence->Release(); + this->m_p_fence = nullptr; + } + + if (this->m_p_fence_event) + { + CloseHandle(this->m_p_fence_event); + this->m_p_fence_event = nullptr; + } + + size_t index{}; + for (auto* p_heap : this->m_heaps_placed) + { + p_heap->Release(); + auto ref_count = this->m_blocks.at(index)->Release(); + + RMLUI_ASSERT(ref_count == 0 && "leak"); + ++index; + } + + if (this->m_p_virtual_block_for_render_target_heap_allocations) + { + auto ref_count = this->m_p_virtual_block_for_render_target_heap_allocations->Release(); + RMLUI_ASSERT(ref_count == 0 && "leak"); + } + + if (this->m_p_virtual_block_for_depth_stencil_heap_allocations) + { + auto ref_count = this->m_p_virtual_block_for_depth_stencil_heap_allocations->Release(); + RMLUI_ASSERT(ref_count == 0 && "leak"); + } + + this->m_blocks.clear(); + this->m_heaps_placed.clear(); + + this->m_p_allocator = nullptr; + this->m_p_device = nullptr; + this->m_p_command_list = nullptr; + this->m_p_command_allocator = nullptr; + this->m_p_descriptor_heap_srv = nullptr; + this->m_p_copy_queue = nullptr; + this->m_p_handle = nullptr; + this->m_p_renderer = nullptr; + this->m_p_descriptor_heap_rtv = nullptr; + this->m_p_virtual_block_for_render_target_heap_allocations = nullptr; + this->m_p_virtual_block_for_depth_stencil_heap_allocations = nullptr; +} + +ID3D12Resource* RenderInterface_DX12::TextureMemoryManager::Alloc_Texture(D3D12_RESOURCE_DESC& desc, TextureHandleType* p_impl, + const Rml::byte* p_data +#ifdef RMLUI_DX_DEBUG + , + const Rml::String& debug_name +#endif +) +{ + RMLUI_ASSERT(desc.Dimension == D3D12_RESOURCE_DIMENSION::D3D12_RESOURCE_DIMENSION_TEXTURE2D && + "this manager doesn't support 1D or 3D textures. (why do we need to support it?)"); + RMLUI_ASSERT(desc.DepthOrArraySize <= 1 && "we don't support a such sizes"); + RMLUI_ASSERT(desc.SampleDesc.Count == 1 && "this manager not for allocating render targets or depth stencil!"); + RMLUI_ASSERT(desc.Width && "must specify value for width field"); + RMLUI_ASSERT(desc.Height && "must specify value for height field"); + RMLUI_ASSERT(p_impl && "must be valid!"); + RMLUI_ASSERT(p_data && "must be valid!"); + + ID3D12Resource* p_result{}; + + // this size stands for real size of texture and how much it will occupy for allocated block, because if it is larger than 1 Mb it is better to + // allocate as committed resource instead of using placed resource technique (by default we allocate block for 4Mb because 1.0 Mb textures can be + // occupy 4 times and smaller even more times, but it is not reasonable to use placed resources for >1Mb textures) + auto base_memory_size_for_allocation_in_bytes = desc.Width * desc.Height * this->BytesPerPixel(desc.Format); + size_t total_memory_for_allocation = base_memory_size_for_allocation_in_bytes; + size_t mipmemory = base_memory_size_for_allocation_in_bytes; + for (int i = 2; i <= desc.MipLevels; ++i) + { + if (mipmemory <= 1) + { + break; + } + + mipmemory /= 4; + total_memory_for_allocation += mipmemory; + } + +#ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX-12] allocating texture with base memory[%zu] and total memory with mips levels[%zu]", + base_memory_size_for_allocation_in_bytes, total_memory_for_allocation); +#endif + + // we need to pass full memory with mipmaps otherwise the DirectX API will validate it by its own. we must ensure what we do. + if (this->CanBePlacedResource(total_memory_for_allocation)) + { + this->Alloc_As_Placed(base_memory_size_for_allocation_in_bytes, total_memory_for_allocation, desc, p_impl, p_data); + p_result = static_cast(p_impl->Get_Resource()); + } + else + { + this->Alloc_As_Committed(base_memory_size_for_allocation_in_bytes, total_memory_for_allocation, desc, p_impl, p_data); + p_result = static_cast(p_impl->Get_Resource())->GetResource(); + } + + return p_result; +} + +ID3D12Resource* RenderInterface_DX12::TextureMemoryManager::Alloc_Texture(D3D12_RESOURCE_DESC& desc, Gfx::FramebufferData* p_impl, + D3D12_RESOURCE_FLAGS flags, D3D12_RESOURCE_STATES initial_state +#ifdef RMLUI_DEBUG + , + const Rml::String& debug_name +#endif +) +{ + RMLUI_ASSERT(desc.Dimension == D3D12_RESOURCE_DIMENSION::D3D12_RESOURCE_DIMENSION_TEXTURE2D && + "this manager doesn't support 1D or 3D textures. (why do we need to support it?)"); + RMLUI_ASSERT(desc.DepthOrArraySize <= 1 && "we don't support a such sizes"); + RMLUI_ASSERT(desc.Width && "must specify value for width field"); + RMLUI_ASSERT(desc.Height && "must specify value for height field"); + RMLUI_ASSERT(p_impl && "must be valid!"); + RMLUI_ASSERT(p_impl->Get_Texture() && + "you must allocate Gfx::FramebufferData's field (texture) before calling this method! So you need to set your allocated pointer to " + "Set_Texture method, dude..."); + + ID3D12Resource* p_result{}; + + auto base_memory_size_for_allocation_in_bytes = desc.Width * desc.Height * this->BytesPerPixel(desc.Format); + size_t total_memory_for_allocation = base_memory_size_for_allocation_in_bytes; + size_t mipmemory = base_memory_size_for_allocation_in_bytes; + for (int i = 2; i <= desc.MipLevels; ++i) + { + if (mipmemory <= 1) + { + break; + } + + mipmemory /= 4; + total_memory_for_allocation += mipmemory; + } + + desc.Flags = flags; + +#ifdef RMLUI_DX_DEBUG + bool is_rt = flags & D3D12_RESOURCE_FLAGS::D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + bool is_ds = flags & D3D12_RESOURCE_FLAGS::D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + + RMLUI_ASSERT(is_rt || is_ds && "must be allow for RT or DS otherwise you will get assert from DX12"); + + const char* type_of_flag_for_texture_debug_name{}; + + constexpr const char* hardcoded_name_render_target = "render target"; + constexpr const char* hardcoded_name_depth_stencil = "depth-stencil"; + constexpr const char* hardcoded_name_unknown = "unknown"; + + if (is_rt) + { + type_of_flag_for_texture_debug_name = hardcoded_name_render_target; + } + else if (is_ds) + { + type_of_flag_for_texture_debug_name = hardcoded_name_depth_stencil; + } + else + { + type_of_flag_for_texture_debug_name = hardcoded_name_unknown; + } + + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, + "[DirectX-12] allocating render2texture (%s) with base memory[%zu] and total memory with mips levels[%zu]", + type_of_flag_for_texture_debug_name, base_memory_size_for_allocation_in_bytes, total_memory_for_allocation); +#endif + + this->Alloc_As_Committed(base_memory_size_for_allocation_in_bytes, total_memory_for_allocation, desc, initial_state, p_impl->Get_Texture(), + p_impl); + + p_result = static_cast(p_impl->Get_Texture()->Get_Resource())->GetResource(); + + return p_result; +} + +void RenderInterface_DX12::TextureMemoryManager::Free_Texture(TextureHandleType* p_texture) +{ + RMLUI_ASSERT(p_texture && "must be valid"); + + if (p_texture) + { + auto index = p_texture->Get_Info().Get_BufferIndex(); + RMLUI_ASSERT(p_texture->Get_Resource() && "must be valid! can't be! data is corrupted! or was already destroyed!"); + + if (index != -1) + { + this->m_blocks.at(index)->FreeAllocation(p_texture->Get_Info().Get_VirtualAllocation()); + } + + if (this->m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav) + { + this->m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav->free(p_texture->Get_Allocation_DescriptorHeap()); + } + + p_texture->Destroy(); + } +} + +void RenderInterface_DX12::TextureMemoryManager::Free_Texture(Gfx::FramebufferData* p_impl) +{ + RMLUI_ASSERT(p_impl && "must be valid!"); + RMLUI_ASSERT(p_impl->Get_Texture() && "must be valid!"); + RMLUI_ASSERT(this->m_p_virtual_block_for_render_target_heap_allocations && "must be valid! early calling"); + RMLUI_ASSERT(this->m_p_virtual_block_for_depth_stencil_heap_allocations && "must be valid! early calling"); + + if (p_impl) + { + this->Free_Texture(p_impl->Get_Texture(), p_impl->Is_RenderTarget(), *(p_impl->Get_VirtualAllocation_Descriptor())); + + delete p_impl->Get_Texture(); + p_impl->Set_Texture(nullptr); + + p_impl->Set_DescriptorResourceView({}); + p_impl->Set_ID(-1); + } +} + +void RenderInterface_DX12::TextureMemoryManager::Free_Texture(TextureHandleType* p_texture, bool is_rt, D3D12MA::VirtualAllocation& allocation) +{ + RMLUI_ASSERT(p_texture && "you must pass a valid pointer!"); + + this->Free_Texture(p_texture); + + if (is_rt) + { + if (this->m_p_virtual_block_for_render_target_heap_allocations) + { + this->m_p_virtual_block_for_render_target_heap_allocations->FreeAllocation(allocation); + } + } + else + { + if (this->m_p_virtual_block_for_depth_stencil_heap_allocations) + { + this->m_p_virtual_block_for_depth_stencil_heap_allocations->FreeAllocation(allocation); + } + } +} + +bool RenderInterface_DX12::TextureMemoryManager::CanAllocate(size_t total_memory_for_allocation, D3D12MA::VirtualBlock* p_block) +{ + RMLUI_ASSERT(total_memory_for_allocation > 0 && total_memory_for_allocation != size_t(-1), "must be a valid number!"); + + RMLUI_ASSERT(p_block, "must be valid virtual block"); + + bool result{}; + + if (p_block) + { + D3D12MA::Statistics stats; + p_block->GetStatistics(&stats); + + result = (stats.BlockBytes - stats.AllocationBytes) >= total_memory_for_allocation; + } + + return result; +} + +bool RenderInterface_DX12::TextureMemoryManager::CanBePlacedResource(size_t total_memory_for_allocation) +{ + RMLUI_ASSERT(total_memory_for_allocation > 0 && total_memory_for_allocation != size_t(-1), "must be a valid number!"); + + bool result{}; + + if (total_memory_for_allocation <= this->m_size_limit_for_being_placed) + result = true; + + return result; +} + +bool RenderInterface_DX12::TextureMemoryManager::CanBeSmallResource(size_t base_memory) +{ + RMLUI_ASSERT(base_memory > 0, "must be greater than zero!"); + RMLUI_ASSERT(base_memory != size_t(-1), "must be a valid number!"); + + bool result{}; + + constexpr size_t _kNonMSAASmallResourceMemoryLimit = 128 * 128 * 4; + + // if this is not MSAA texture (because for now we didn't implement a such support) + if (base_memory <= _kNonMSAASmallResourceMemoryLimit) + result = true; + + return result; +} + +D3D12MA::VirtualBlock* RenderInterface_DX12::TextureMemoryManager::Get_AvailableBlock(size_t total_memory_for_allocation, int* result_index) +{ + RMLUI_ASSERT(this->m_p_device, "must be valid!"); + RMLUI_ASSERT(result_index, "must be valid!"); + RMLUI_ASSERT(total_memory_for_allocation <= this->m_size_limit_for_being_placed, "you can't pass a such size here!"); + RMLUI_ASSERT(this->m_size_limit_for_being_placed < this->m_size_for_placed_heap, "something is wrong and you initialized your manager wrong!!!!"); + + D3D12MA::VirtualBlock* p_result{}; + + if (this->m_blocks.empty()) + { + RMLUI_ASSERT(this->m_heaps_placed.empty(), "if blocks are empty heaps must be too!"); + + auto pair = this->Create_HeapPlaced(this->m_size_for_placed_heap); + + p_result = pair.second; + *result_index = 0; + } + else + { + int index{}; + for (auto* p_block : this->m_blocks) + { + if (p_block) + { + if (this->CanAllocate(total_memory_for_allocation, p_block)) + { + p_result = p_block; + *result_index = index; + + break; + } + else + { + if (index >= this->m_blocks.size() - 1) + { + auto pair = this->Create_HeapPlaced(this->m_size_for_placed_heap); + ++index; + *result_index = index; + p_result = pair.second; + + break; + } + } + } + ++index; + } + } + + return p_result; +} + +void RenderInterface_DX12::TextureMemoryManager::Alloc_As_Committed(size_t base_memory, size_t total_memory, D3D12_RESOURCE_DESC& desc, + TextureHandleType* p_impl, const Rml::byte* p_data) +{ + RMLUI_ASSERT(base_memory > 0 && "must be greater than zero!"); + RMLUI_ASSERT(total_memory > 0 && "must be greater than zero!"); + RMLUI_ASSERT(base_memory != size_t(-1) && "must be valid number!"); + RMLUI_ASSERT(total_memory != size_t(-1) && "must be valid number!"); + RMLUI_ASSERT(this->m_p_device && "must be valid!"); + RMLUI_ASSERT(p_impl && "must be valid!"); + RMLUI_ASSERT(this->m_p_command_allocator && "must be valid!"); + RMLUI_ASSERT(this->m_p_command_list && "must be valid!"); + RMLUI_ASSERT(this->m_p_allocator && "allocator must be valid!"); + + if (this->m_p_allocator) + { + D3D12MA::ALLOCATION_DESC desc_allocation = {}; + desc_allocation.HeapType = D3D12_HEAP_TYPE_DEFAULT; + + ID3D12Resource* p_resource{}; + D3D12MA::Allocation* p_allocation{}; + auto status = this->m_p_allocator->CreateResource(&desc_allocation, &desc, D3D12_RESOURCE_STATE_COMMON, nullptr, &p_allocation, + IID_PPV_ARGS(&p_resource)); + + RMLUI_DX_ASSERTMSG(status, "failed to CreateResource (D3D12MA)"); + + if (p_impl) + { + p_impl->Set_Resource(p_allocation); + } + + if (this->m_p_command_allocator) + { + auto status = this->m_p_command_allocator->Reset(); + RMLUI_DX_ASSERTMSG(status, "failed to Reset (command allocator)"); + } + + if (this->m_p_command_list) + { + auto status = this->m_p_command_list->Reset(this->m_p_command_allocator, nullptr); + RMLUI_DX_ASSERTMSG(status, "failed to Reset (command list)"); + } + + this->Upload(true, p_impl, desc, p_data, p_resource); + } +} + +void RenderInterface_DX12::TextureMemoryManager::Alloc_As_Committed(size_t base_memory, size_t total_memory, D3D12_RESOURCE_DESC& desc, + D3D12_RESOURCE_STATES initial_state, TextureHandleType* p_texture, Gfx::FramebufferData* p_impl) +{ + RMLUI_ASSERT(base_memory > 0, "must be greater than zero!"); + RMLUI_ASSERT(total_memory > 0, "must be greater than zero!"); + RMLUI_ASSERT(base_memory != size_t(-1), "must be valid number!"); + RMLUI_ASSERT(total_memory != size_t(-1), "must be valid number!"); + RMLUI_ASSERT(this->m_p_device, "must be valid!"); + RMLUI_ASSERT(p_impl, "must be valid!"); + RMLUI_ASSERT(this->m_p_command_allocator, "must be valid!"); + RMLUI_ASSERT(this->m_p_command_list, "must be valid!"); + RMLUI_ASSERT(this->m_p_allocator, "allocator must be valid!"); + RMLUI_ASSERT(p_texture && "must be valid!"); + + if (this->m_p_allocator) + { + D3D12MA::ALLOCATION_DESC desc_allocation = {}; + desc_allocation.HeapType = D3D12_HEAP_TYPE_DEFAULT; + + ID3D12Resource* p_resource{}; + D3D12MA::Allocation* p_allocation{}; + auto status = this->m_p_allocator->CreateResource(&desc_allocation, &desc, initial_state, nullptr, &p_allocation, IID_PPV_ARGS(&p_resource)); + + RMLUI_DX_ASSERTMSG(status, "failed to CreateResource (D3D12MA) (RenderTargetTexture)"); + + if (p_texture) + { + p_texture->Set_Resource(p_allocation); + } + + D3D12_SHADER_RESOURCE_VIEW_DESC desc_srv{}; + desc_srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + + bool is_rt = desc.Flags & D3D12_RESOURCE_FLAGS::D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + bool is_ds = desc.Flags & D3D12_RESOURCE_FLAGS::D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + + if (is_rt) + desc_srv.Format = desc.Format; + else if (is_ds) + desc_srv.Format = DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS; + + if (desc.SampleDesc.Count > 1) + { + desc_srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS; + } + else + { + desc_srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + } + + desc_srv.Texture2D.MipLevels = desc.MipLevels; + + RMLUI_ASSERT(is_rt || is_ds && "this method for dsv or rtv resources"); + + auto descriptor_allocation = this->m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav->allocate(this->m_size_srv_cbv_uav_descriptor); + + auto offset_pointer = SIZE_T(INT64(this->m_p_handle->ptr)) + INT64(descriptor_allocation.offset); + D3D12_CPU_DESCRIPTOR_HANDLE cast_offset_pointer; + cast_offset_pointer.ptr = offset_pointer; + + this->m_p_device->CreateShaderResourceView(p_resource, &desc_srv, cast_offset_pointer); + p_texture->Set_Allocation_DescriptorHeap(descriptor_allocation); + + if (is_rt) + p_impl->Set_DescriptorResourceView(this->Alloc_RenderTargetResourceView(p_resource, p_impl->Get_VirtualAllocation_Descriptor())); + else if (is_ds) + p_impl->Set_DescriptorResourceView(this->Alloc_DepthStencilResourceView(p_resource, p_impl->Get_VirtualAllocation_Descriptor())); + } +} + +void RenderInterface_DX12::TextureMemoryManager::Alloc_As_Placed(size_t base_memory, size_t total_memory, D3D12_RESOURCE_DESC& desc, + TextureHandleType* p_impl, const Rml::byte* p_data) +{ + RMLUI_ASSERT(base_memory > 0, "must be greater than zero!"); + RMLUI_ASSERT(total_memory > 0, "must be greater than zero!"); + RMLUI_ASSERT(base_memory != size_t(-1), "must be valid number!"); + RMLUI_ASSERT(total_memory != size_t(-1), "must be valid number!"); + RMLUI_ASSERT(this->m_p_device, "must be valid!"); + RMLUI_ASSERT(p_impl, "must be valid!"); + RMLUI_ASSERT(this->m_p_command_allocator, "must be valid!"); + RMLUI_ASSERT(this->m_p_command_list, "must be valid!"); + + D3D12_RESOURCE_ALLOCATION_INFO info_for_alloc{}; + + if (this->CanBeSmallResource(base_memory)) + { + desc.Alignment = D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT; + info_for_alloc = this->m_p_device->GetResourceAllocationInfo(0, 1, &desc); + + RMLUI_ASSERT(info_for_alloc.Alignment == D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT && "wrong calculation! check CanBeSmallResource method!"); + RMLUI_ASSERT(total_memory == info_for_alloc.SizeInBytes, "must be equal! check calculate how you calculate total_memory variable!"); + } + else + { + desc.Alignment = 0; + info_for_alloc = this->m_p_device->GetResourceAllocationInfo(0, 1, &desc); + + RMLUI_ASSERT(info_for_alloc.Alignment != D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT && "wrong calculation! check CanBeSmallResource method!"); + RMLUI_ASSERT(total_memory == info_for_alloc.SizeInBytes, "must be equal! check calculation how you calculate total_memory variable!"); + } + + int heap_index{-1}; + auto* p_block = this->Get_AvailableBlock(info_for_alloc.SizeInBytes, &heap_index); + + RMLUI_ASSERT(heap_index != -1 && "something is wrong!"); + RMLUI_ASSERT(p_block && "something is wrong!"); + + auto* p_heap = this->m_heaps_placed.at(heap_index); + RMLUI_ASSERT(p_heap && "something is wrong!"); + + D3D12_RESOURCE_BARRIER bar; + + D3D12MA::VIRTUAL_ALLOCATION_DESC desc_alloc{}; + desc_alloc.Size = info_for_alloc.SizeInBytes; + desc_alloc.Alignment = info_for_alloc.Alignment; + D3D12MA::VirtualAllocation alloc_virtual; + UINT64 offset{}; + auto status = p_block->Allocate(&desc_alloc, &alloc_virtual, &offset); + RMLUI_DX_ASSERTMSG(status, "can't allocate virtual alloc!"); + + ID3D12Resource* p_resource{}; + status = this->m_p_device->CreatePlacedResource(p_heap, offset, &desc, D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&p_resource)); + + RMLUI_DX_ASSERTMSG(status, "failed to CreatePlacedResource!"); + RMLUI_ASSERT(p_resource && "must be valid pointer of ID3D12Resource*!"); + + if (p_impl) + { + GraphicsAllocationInfo info_graphics; + info_graphics.Set_Size(info_for_alloc.SizeInBytes); + info_graphics.Set_BufferIndex(heap_index); + info_graphics.Set_Offset(offset); + info_graphics.Set_VirtualAllocation(alloc_virtual); + + p_impl->Set_Info(info_graphics); + p_impl->Set_Resource(p_resource); + } + + bar = CD3DX12_RESOURCE_BARRIER::Aliasing(nullptr, p_resource); + + if (this->m_p_command_allocator) + { + auto status = this->m_p_command_allocator->Reset(); + RMLUI_DX_ASSERTMSG(status, "failed to Reset (command allocator)"); + } + + if (this->m_p_command_list) + { + auto status = this->m_p_command_list->Reset(this->m_p_command_allocator, nullptr); + RMLUI_DX_ASSERTMSG(status, "failed to Reset (command list)"); + + this->m_p_command_list->ResourceBarrier(1, &bar); + } + + this->Upload(false, p_impl, desc, p_data, p_resource); +} + +void RenderInterface_DX12::TextureMemoryManager::Upload(bool is_committed, TextureHandleType* p_texture_handle, const D3D12_RESOURCE_DESC& desc, + const Rml::byte* p_data, ID3D12Resource* p_resource) +{ + RMLUI_ASSERT(this->m_p_device && "must be valid!"); + RMLUI_ASSERT(p_data && "must be valid!"); + RMLUI_ASSERT(p_resource && "must be valid!"); + RMLUI_ASSERT(this->m_p_descriptor_heap_srv && "must be valid!"); + RMLUI_ASSERT(this->m_p_allocator && "must be valid!"); + RMLUI_ASSERT(this->m_p_command_list && "must be valid!"); + RMLUI_ASSERT(this->m_p_handle && "must be valid!"); + RMLUI_ASSERT(this->m_p_copy_queue && "must be valid!"); + RMLUI_ASSERT(this->m_p_renderer && "must be valid!"); + RMLUI_ASSERT(this->m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav && "must be valid!"); + RMLUI_ASSERT(p_texture_handle && "must be valid!"); + RMLUI_ASSERT(this->m_p_fence && "must be valid!"); + RMLUI_ASSERT(this->m_p_fence_event && "must be valid!"); + + auto upload_size = GetRequiredIntermediateSize(p_resource, 0, 1); + + D3D12MA::ALLOCATION_DESC desc_alloc{}; + desc_alloc.HeapType = D3D12_HEAP_TYPE_UPLOAD; + + D3D12MA::Allocation* p_allocation{}; + + ID3D12Resource* p_upload_buffer{}; + auto buffer_desc = CD3DX12_RESOURCE_DESC::Buffer(upload_size); + auto status = this->m_p_allocator->CreateResource(&desc_alloc, &buffer_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, &p_allocation, + IID_PPV_ARGS(&p_upload_buffer)); + + RMLUI_DX_ASSERTMSG(status, "failed to CreateResource (upload buffer for texture)"); + + D3D12_SUBRESOURCE_DATA desc_data{}; + desc_data.pData = p_data; + desc_data.RowPitch = desc.Width * this->BytesPerPixel(desc.Format); + desc_data.SlicePitch = desc_data.RowPitch * desc.Height; + + auto allocated_size = UpdateSubresources<1>(this->m_p_command_list, p_resource, p_upload_buffer, 0, 0, 1, &desc_data); + +#ifdef RMLUI_DX_DEBUG + constexpr const char* p_committed = "committed"; + constexpr const char* p_placed = "placed"; + + const char* p_current_resource_type_name{}; + + if (is_committed) + p_current_resource_type_name = p_committed; + else + p_current_resource_type_name = p_placed; + + Rml::Log::Message(Rml::Log::Type::LT_DEBUG, "[DirectX 12] allocated %s resource to GPU with size: [%zu]", p_current_resource_type_name, + allocated_size); +#endif + + D3D12_SHADER_RESOURCE_VIEW_DESC desc_srv{}; + desc_srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + desc_srv.Format = desc.Format; + desc_srv.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + desc_srv.Texture2D.MipLevels = desc.MipLevels; + + auto descriptor_allocation = this->m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav->allocate(this->m_size_srv_cbv_uav_descriptor); + + auto offset_pointer = SIZE_T(INT64(this->m_p_handle->ptr) + INT64(descriptor_allocation.offset)); + D3D12_CPU_DESCRIPTOR_HANDLE cast_offset_pointer; + cast_offset_pointer.ptr = offset_pointer; + + this->m_p_device->CreateShaderResourceView(p_resource, &desc_srv, cast_offset_pointer); + p_texture_handle->Set_Allocation_DescriptorHeap(descriptor_allocation); + + status = this->m_p_command_list->Close(); + + RMLUI_DX_ASSERTMSG(status, "failed to Close (command list for copy)"); + + ID3D12CommandList* p_lists[] = {this->m_p_command_list}; + + this->m_p_copy_queue->ExecuteCommandLists(1, p_lists); + + // wait queue for completion + if (this->m_p_device) + { + ++this->m_fence_value; + + RMLUI_DX_ASSERTMSG(this->m_p_copy_queue->Signal(this->m_p_fence, this->m_fence_value), "failed to signal copy queue's fence"); + + if (this->m_p_fence->GetCompletedValue() < this->m_fence_value) + { + RMLUI_DX_ASSERTMSG(this->m_p_fence->SetEventOnCompletion(this->m_fence_value, this->m_p_fence_event), "failed to SetEventOnCompletion"); + + WaitForSingleObject(this->m_p_fence_event, INFINITE); + } + } + + if (p_allocation) + { + if (p_allocation->GetResource()) + { + p_allocation->GetResource()->Release(); + } + + auto ref_count = p_allocation->Release(); + RMLUI_ASSERT(ref_count == 0, "leak!"); + } +} + +size_t RenderInterface_DX12::TextureMemoryManager::BytesPerPixel(DXGI_FORMAT format) +{ + return this->BitsPerPixel(format) / static_cast(8); +} + +// TODO: need to add xbox's formats???? +size_t RenderInterface_DX12::TextureMemoryManager::BitsPerPixel(DXGI_FORMAT format) +{ + switch (format) + { + case DXGI_FORMAT_R32G32B32A32_TYPELESS: + case DXGI_FORMAT_R32G32B32A32_FLOAT: + case DXGI_FORMAT_R32G32B32A32_UINT: + case DXGI_FORMAT_R32G32B32A32_SINT: return 128; + + case DXGI_FORMAT_R32G32B32_TYPELESS: + case DXGI_FORMAT_R32G32B32_FLOAT: + case DXGI_FORMAT_R32G32B32_UINT: + case DXGI_FORMAT_R32G32B32_SINT: return 96; + + case DXGI_FORMAT_R16G16B16A16_TYPELESS: + case DXGI_FORMAT_R16G16B16A16_FLOAT: + case DXGI_FORMAT_R16G16B16A16_UNORM: + case DXGI_FORMAT_R16G16B16A16_UINT: + case DXGI_FORMAT_R16G16B16A16_SNORM: + case DXGI_FORMAT_R16G16B16A16_SINT: + case DXGI_FORMAT_R32G32_TYPELESS: + case DXGI_FORMAT_R32G32_FLOAT: + case DXGI_FORMAT_R32G32_UINT: + case DXGI_FORMAT_R32G32_SINT: + case DXGI_FORMAT_R32G8X24_TYPELESS: + case RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT: + case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: + case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT: + case DXGI_FORMAT_Y416: + case DXGI_FORMAT_Y210: + case DXGI_FORMAT_Y216: return 64; + + case DXGI_FORMAT_R10G10B10A2_TYPELESS: + case DXGI_FORMAT_R10G10B10A2_UNORM: + case DXGI_FORMAT_R10G10B10A2_UINT: + case DXGI_FORMAT_R11G11B10_FLOAT: + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + case DXGI_FORMAT_R8G8B8A8_UINT: + case DXGI_FORMAT_R8G8B8A8_SNORM: + case DXGI_FORMAT_R8G8B8A8_SINT: + case DXGI_FORMAT_R16G16_TYPELESS: + case DXGI_FORMAT_R16G16_FLOAT: + case DXGI_FORMAT_R16G16_UNORM: + case DXGI_FORMAT_R16G16_UINT: + case DXGI_FORMAT_R16G16_SNORM: + case DXGI_FORMAT_R16G16_SINT: + case DXGI_FORMAT_R32_TYPELESS: + case DXGI_FORMAT_D32_FLOAT: + case DXGI_FORMAT_R32_FLOAT: + case DXGI_FORMAT_R32_UINT: + case DXGI_FORMAT_R32_SINT: + case DXGI_FORMAT_R24G8_TYPELESS: + case DXGI_FORMAT_D24_UNORM_S8_UINT: + case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: + case DXGI_FORMAT_X24_TYPELESS_G8_UINT: + case DXGI_FORMAT_R9G9B9E5_SHAREDEXP: + case DXGI_FORMAT_R8G8_B8G8_UNORM: + case DXGI_FORMAT_G8R8_G8B8_UNORM: + case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8X8_UNORM: + case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM: + case DXGI_FORMAT_B8G8R8A8_TYPELESS: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + case DXGI_FORMAT_B8G8R8X8_TYPELESS: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + case DXGI_FORMAT_AYUV: + case DXGI_FORMAT_Y410: + case DXGI_FORMAT_YUY2: return 32; + + case DXGI_FORMAT_P010: + case DXGI_FORMAT_P016: return 24; + + case DXGI_FORMAT_R8G8_TYPELESS: + case DXGI_FORMAT_R8G8_UNORM: + case DXGI_FORMAT_R8G8_UINT: + case DXGI_FORMAT_R8G8_SNORM: + case DXGI_FORMAT_R8G8_SINT: + case DXGI_FORMAT_R16_TYPELESS: + case DXGI_FORMAT_R16_FLOAT: + case DXGI_FORMAT_D16_UNORM: + case DXGI_FORMAT_R16_UNORM: + case DXGI_FORMAT_R16_UINT: + case DXGI_FORMAT_R16_SNORM: + case DXGI_FORMAT_R16_SINT: + case DXGI_FORMAT_B5G6R5_UNORM: + case DXGI_FORMAT_B5G5R5A1_UNORM: + case DXGI_FORMAT_A8P8: + case DXGI_FORMAT_B4G4R4A4_UNORM: return 16; + + case DXGI_FORMAT_NV12: + case DXGI_FORMAT_420_OPAQUE: + case DXGI_FORMAT_NV11: return 12; + + case DXGI_FORMAT_R8_TYPELESS: + case DXGI_FORMAT_R8_UNORM: + case DXGI_FORMAT_R8_UINT: + case DXGI_FORMAT_R8_SNORM: + case DXGI_FORMAT_R8_SINT: + case DXGI_FORMAT_A8_UNORM: + case DXGI_FORMAT_AI44: + case DXGI_FORMAT_IA44: + case DXGI_FORMAT_P8: return 8; + + case DXGI_FORMAT_R1_UNORM: return 1; + + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: return 4; + + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: return 8; + + default: return 0; + } +} + +Rml::Pair RenderInterface_DX12::TextureMemoryManager::Create_HeapPlaced(size_t size_for_creation) +{ + RMLUI_ASSERT(this->m_p_device, "must be valid!"); + + D3D12MA::VIRTUAL_BLOCK_DESC desc_block{}; + + desc_block.Size = this->m_size_for_placed_heap; + D3D12MA::VirtualBlock* p_block{}; + auto status = D3D12MA::CreateVirtualBlock(&desc_block, &p_block); + + RMLUI_DX_ASSERTMSG(status, "failed to D3D12MA::CreateVirtualBlock"); + + this->m_blocks.push_back(p_block); + + CD3DX12_HEAP_DESC desc_heap(this->m_size_for_placed_heap, D3D12_HEAP_TYPE::D3D12_HEAP_TYPE_DEFAULT, 0, + D3D12_HEAP_FLAG_DENY_BUFFERS | D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES); + + ID3D12Heap* p_heap{}; + status = this->m_p_device->CreateHeap(&desc_heap, IID_PPV_ARGS(&p_heap)); + + RMLUI_DX_ASSERTMSG(status, "failed to CreateHeap!"); + + this->m_heaps_placed.push_back(p_heap); + + return {p_heap, p_block}; +} + +D3D12_CPU_DESCRIPTOR_HANDLE RenderInterface_DX12::TextureMemoryManager::Alloc_DepthStencilResourceView(ID3D12Resource* p_resource, + D3D12MA::VirtualAllocation* p_alloc) +{ + RMLUI_ASSERT(p_resource && "must be allocated!"); + RMLUI_ASSERT(this->m_p_device && "must be allocated!"); + RMLUI_ASSERT(this->m_p_virtual_block_for_depth_stencil_heap_allocations && "must be initialized"); + RMLUI_ASSERT(p_alloc && "must be valid!"); + RMLUI_ASSERT(this->m_p_descriptor_heap_dsv && "must be valid!"); + + D3D12_CPU_DESCRIPTOR_HANDLE calculated_offset{}; + + if (p_resource) + { + if (this->m_p_virtual_block_for_depth_stencil_heap_allocations) + { + if (this->m_p_device) + { + if (this->m_p_descriptor_heap_dsv) + { + D3D12MA::VIRTUAL_ALLOCATION_DESC desc_alloc = {}; + desc_alloc.Size = this->m_size_dsv_descriptor; + UINT64 offset{}; + + auto status = this->m_p_virtual_block_for_depth_stencil_heap_allocations->Allocate(&desc_alloc, p_alloc, &offset); + + RMLUI_DX_ASSERTMSG(status, + "failed to allocate descriptor rtv, it means you need to resize and set higher size than previous, overflow (see " + "RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_RTV constant)"); + + D3D12_DEPTH_STENCIL_VIEW_DESC desc_rtv = {}; + desc_rtv.Format = RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT; + desc_rtv.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMS; + + calculated_offset.ptr = (this->m_p_descriptor_heap_dsv->GetCPUDescriptorHandleForHeapStart().ptr + offset); + + this->m_p_device->CreateDepthStencilView(p_resource, &desc_rtv, calculated_offset); + } + } + } + } + + return calculated_offset; +} + +D3D12_CPU_DESCRIPTOR_HANDLE RenderInterface_DX12::TextureMemoryManager::Alloc_RenderTargetResourceView(ID3D12Resource* p_resource, + D3D12MA::VirtualAllocation* p_alloc) +{ + RMLUI_ASSERT(p_resource && "must be allocated!"); + RMLUI_ASSERT(this->m_p_device && "must be allocated!"); + RMLUI_ASSERT(this->m_p_virtual_block_for_render_target_heap_allocations && "must be initialized"); + RMLUI_ASSERT(p_alloc && "must be valid!"); + RMLUI_ASSERT(this->m_p_descriptor_heap_rtv && "must be valid!"); + + D3D12_CPU_DESCRIPTOR_HANDLE calculated_offset{}; + + if (p_resource) + { + if (this->m_p_virtual_block_for_render_target_heap_allocations) + { + if (this->m_p_device) + { + if (this->m_p_descriptor_heap_rtv) + { + D3D12MA::VIRTUAL_ALLOCATION_DESC desc_alloc = {}; + desc_alloc.Size = this->m_size_rtv_descriptor; + UINT64 offset{}; + + auto status = this->m_p_virtual_block_for_render_target_heap_allocations->Allocate(&desc_alloc, p_alloc, &offset); + + RMLUI_DX_ASSERTMSG(status, + "failed to allocate descriptor rtv, it means you need to resize and set higher size than previous, overflow (see " + "RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_RTV constant)"); + + D3D12_RENDER_TARGET_VIEW_DESC desc_rtv = {}; + desc_rtv.Format = RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT; + desc_rtv.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMS; + + calculated_offset.ptr = (this->m_p_descriptor_heap_rtv->GetCPUDescriptorHandleForHeapStart().ptr + offset); + + this->m_p_device->CreateRenderTargetView(p_resource, &desc_rtv, calculated_offset); + } + } + } + } + + return calculated_offset; +} + +RenderInterface_DX12::ConstantBufferType::ConstantBufferType() : m_is_free{true}, m_p_gpu_start_memory_for_binding_data{} {} + +RenderInterface_DX12::ConstantBufferType::~ConstantBufferType() {} + +const RenderInterface_DX12::GraphicsAllocationInfo& RenderInterface_DX12::ConstantBufferType::Get_AllocInfo(void) const noexcept +{ + return this->m_alloc_info; +} + +void RenderInterface_DX12::ConstantBufferType::Set_AllocInfo(const GraphicsAllocationInfo& info) noexcept +{ + this->m_alloc_info = info; +} + +void* RenderInterface_DX12::ConstantBufferType::Get_GPU_StartMemoryForBindingData(void) +{ + return this->m_p_gpu_start_memory_for_binding_data; +} + +void RenderInterface_DX12::ConstantBufferType::Set_GPU_StartMemoryForBindingData(void* p_start_pointer) +{ + this->m_p_gpu_start_memory_for_binding_data = p_start_pointer; +} + +RenderInterface_DX12::GraphicsAllocationInfo::GraphicsAllocationInfo() : m_buffer_index{-1}, m_offset{}, m_size{}, m_alloc_info{} {} + +RenderInterface_DX12::GraphicsAllocationInfo::~GraphicsAllocationInfo() {} + +const D3D12MA::VirtualAllocation& RenderInterface_DX12::GraphicsAllocationInfo::Get_VirtualAllocation(void) const noexcept +{ + return this->m_alloc_info; +} + +void RenderInterface_DX12::GraphicsAllocationInfo::Set_VirtualAllocation(const D3D12MA::VirtualAllocation& info_alloc) noexcept +{ + this->m_alloc_info = info_alloc; +} + +size_t RenderInterface_DX12::GraphicsAllocationInfo::Get_Offset(void) const noexcept +{ + return this->m_offset; +} + +void RenderInterface_DX12::GraphicsAllocationInfo::Set_Offset(size_t value) noexcept +{ + this->m_offset = value; +} + +size_t RenderInterface_DX12::GraphicsAllocationInfo::Get_Size(void) const noexcept +{ + return this->m_size; +} + +void RenderInterface_DX12::GraphicsAllocationInfo::Set_Size(size_t value) noexcept +{ + this->m_size = value; +} + +int RenderInterface_DX12::GraphicsAllocationInfo::Get_BufferIndex(void) const noexcept +{ + return this->m_buffer_index; +} + +void RenderInterface_DX12::GraphicsAllocationInfo::Set_BufferIndex(int index) noexcept +{ + this->m_buffer_index = index; +} diff --git a/Backends/RmlUi_Renderer_DX12.h b/Backends/RmlUi_Renderer_DX12.h new file mode 100644 index 000000000..910926df3 --- /dev/null +++ b/Backends/RmlUi_Renderer_DX12.h @@ -0,0 +1,915 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * 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. + * + */ + +#ifndef RMLUI_BACKENDS_RENDERER_DX12_H +#define RMLUI_BACKENDS_RENDERER_DX12_H + +#include + +/** + * Include third-party dependencies. + */ +#ifdef RMLUI_PLATFORM_WIN32 + #include "RmlUi_Include_Windows.h" +#endif + +#if (_MSC_VER > 0) + #define RMLUI_DISABLE_ALL_COMPILER_WARNINGS_PUSH _Pragma("warning(push, 0)") + #define RMLUI_DISABLE_ALL_COMPILER_WARNINGS_POP _Pragma("warning(pop)") +#elif __clang__ + #define RMLUI_DISABLE_ALL_COMPILER_WARNINGS_PUSH \ + _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wall\"") _Pragma("clang diagnostic ignored \"-Wextra\"") \ + _Pragma("clang diagnostic ignored \"-Wnullability-extension\"") \ + _Pragma("clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"") \ + _Pragma("clang diagnostic ignored \"-Wnullability-completeness\"") + #define RMLUI_DISABLE_ALL_COMPILER_WARNINGS_POP _Pragma("clang diagnostic pop") +#elif __GNUC__ + #define RMLUI_DISABLE_ALL_COMPILER_WARNINGS_PUSH \ + _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") \ + _Pragma("GCC diagnostic ignored \"-Wunused-function\"") _Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") \ + _Pragma("GCC diagnostic ignored \"-Wunused-variable\"") _Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") \ + _Pragma("GCC diagnostic ignored \"-Wswitch\"") _Pragma("GCC diagnostic ignored \"-Wpedantic\"") \ + _Pragma("GCC diagnostic ignored \"-Wattributes\"") _Pragma("GCC diagnostic ignored \"-Wignored-qualifiers\"") \ + _Pragma("GCC diagnostic ignored \"-Wparentheses\"") + #define RMLUI_DISABLE_ALL_COMPILER_WARNINGS_POP _Pragma("GCC diagnostic pop") +#else + #define RMLUI_DISABLE_ALL_COMPILER_WARNINGS_PUSH + #define RMLUI_DISABLE_ALL_COMPILER_WARNINGS_POP +#endif + +RMLUI_DISABLE_ALL_COMPILER_WARNINGS_PUSH + +RMLUI_DISABLE_ALL_COMPILER_WARNINGS_POP + +#ifdef RMLUI_DEBUG + #define RMLUI_DX_ASSERTMSG(statement, msg) RMLUI_ASSERTMSG(SUCCEEDED(statement), msg) + + // Uncomment the following line to enable additional DirectX debugging. + #define RMLUI_DX_DEBUG +#else + #define RMLUI_DX_ASSERTMSG(statement, msg) static_cast(statement) +#endif + +#ifdef RMLUI_PLATFORM_WIN32 + #include + #include + #include + #include + #include "RmlUi_DirectX/D3D12MemAlloc.h" + #include "RmlUi_DirectX/offsetAllocator.hpp" + #include + + // user's preprocessor overrides + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_RESERVECOUNT_OF_RENDERSTACK_LAYERS + // system field (it is not supposed to specify by initialization structure) + // on some render backend implementations (Vulkan/DirectX-12) we have to check memory leaks but + // if we don't reserve memory for a field that contains layers (it is vector) + // at runtime we will get a called dtor of move-to-copy object (because of reallocation) + // and for that matter we will get a false-positive trigger of assert and it is not right generally + #define RMLUI_RENDER_BACKEND_FIELD_RESERVECOUNT_OF_RENDERSTACK_LAYERS RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_RESERVECOUNT_OF_RENDERSTACK_LAYERS + #else + // system field (it is not supposed to specify by initialization structure) + // on some render backend implementations (Vulkan/DirectX-12) we have to check memory leaks but + // if we don't reserve memory for a field that contains layers (it is vector) + // at runtime we will get a called dtor of move-to-copy object (because of reallocation) + // and for that matter we will get a false-positive trigger of assert and it is not right generally + #define RMLUI_RENDER_BACKEND_FIELD_RESERVECOUNT_OF_RENDERSTACK_LAYERS 6 + #endif + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_SWAPCHAIN_BACKBUFFER_COUNT + // this field specifies the default amount of swapchain buffer that will be created + // but it is used only when user provided invalid input in initialization structure of backend + #define RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_SWAPCHAIN_BACKBUFFER_COUNT +static_assert(RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT > 0 && "invalid value for RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT"); + #else + // this field specifies the default amount of swapchain buffer that will be created + // but it is used only when user provided invalid input in initialization structure of backend + #define RMLUI_RENDER_BACKEND_FIELD_SWAPCHAIN_BACKBUFFER_COUNT 3 + #endif + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION + // this field specifies the default amount of videomemory that will be used on creation + // this field is used when user provided invalid input in initialization structure of backend + #define RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION +static_assert(RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION > 0 && + "invalid value for RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION"); + #else + // this field specifies the default amount of videomemory that will be used on creation + // this field is used when user provided invalid input in initialization structure of backend + #define RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION 1048576 // (4 * 512 * 512 = bytes or 1 Megabytes) + #endif + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION + // this field specifies the default amount of videomemory that will be used for texture creation + // on some backends it determines the size of a temp buffer that might be used not trivial (just for uploading texture by copying data in it) + // like on DirectX-12 it has a placement resources feature and making this value lower (4 Mb) the placement resource feature becomes pointless + // and doesn't gain any performance related boosting + #define RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION +static_assert(RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION > 0 && + "invalid value for RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION"); + #else + // this field specifies the default amount of videomemory that will be used for texture creation + // on some backends it determines the size of a temp buffer that might be used not trivial (just for uploading texture by copying data in it) + // like on DirectX-12 it has a placement resources feature and making this value lower (4 Mb) the placement resource feature becomes pointless + // and doesn't gain any performance related boosting + #define RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION 4194304 // (4 * 1024 * 1024 = bytes or 4 Megabytes) + #endif + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_ALIGNMENT_FOR_BUFFER + // system field that is not supposed to be in initialization structure and used as default (input user doesn't affect it at all like + // we always use it, not just because we handling invalid input from user) + #define RMLUI_RENDER_BACKEND_FIELD_ALIGNMENT_FOR_BUFFER RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_ALIGNMENT_FOR_BUFFER + #else + // system field that is not supposed to be in initialization structure and used as default (input user doesn't affect it at all like we always + // use it, not just because we handling invalid input from user) + #define RMLUI_RENDER_BACKEND_FIELD_ALIGNMENT_FOR_BUFFER D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT + #endif + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_COLOR_TEXTURE_FORMAT + // system field that is not supposed to be in initialization structure and used as default (input user doesn't affect it at all like we always + // use it, not just because we handling invalid input from user) + #define RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_COLOR_TEXTURE_FORMAT + #else + // system field that is not supposed to be in initialization structure and used as default (input user doesn't affect it at all like we always + // use it, not just because we handling invalid input from user) + #define RMLUI_RENDER_BACKEND_FIELD_COLOR_TEXTURE_FORMAT DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM + #endif + + #ifdef RMLUI_REDNER_BACKEND_OVERRIDE_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT + #define RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT RMLUI_REDNER_BACKEND_OVERRIDE_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT + #else + // system field that is not supposed to be in initialization structure and used as default (input user doesn't affect it at all like we always + // use it, not just because we handling invalid input from user) + #define RMLUI_RENDER_BACKEND_FIELD_DEPTHSTENCIL_TEXTURE_FORMAT DXGI_FORMAT::DXGI_FORMAT_D32_FLOAT_S8X24_UINT + #endif + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_DESCRIPTORAMOUNT_FOR_SRV_CBV_UAV + // system field that is not supposed to be in initialization structure and used as default (input user doesn't affect it at all like we always + // use it, not just because we handling invalid input from user) + // notice: this field is shared for all srv and cbv and uav it doesn't mean that it specifically allocates for srv, cbv and uav + #define RMLUI_RENDER_BACKEND_FIELD_DESCRIPTORAMOUNT_FOR_SRV_CBV_UAV RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_DESCRIPTORAMOUNT_FOR_SRV_CBV_UAV + #else + // system field that is not supposed to be in initialization structure and used as default (input user doesn't affect it at all like we always + // use it, not just because we handling invalid input from user) + // notice: this field is shared for all srv and cbv and uav it doesn't mean that it specifically allocates for srv 128, cbv 128 and uav 128, + // no! it allocates only on descriptor for all of such types and total amount is 128 not 3 * 128 = 384 !!! + #define RMLUI_RENDER_BACKEND_FIELD_DESCRIPTORAMOUNT_FOR_SRV_CBV_UAV 128 + #endif + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_MAXNUMPROGRAMS + #define RMLUI_RENDER_BACKEND_FIELD_MAXNUMPROGRAMS RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_MAXNUMPROGRAMS + #else + #define RMLUI_RENDER_BACKEND_FIELD_MAXNUMPROGRAMS 32 + #endif + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_PREALLOCATED_CONSTANTBUFFERS + #define RMLUI_RENDER_BACKEND_FIELD_PREALLOCATED_CONSTANTBUFFERS RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_PREALLOCATED_CONSTANTBUFFERS + #else + // for getting total size of constant buffers you should multiply kPreAllocatedConstantBuffers * kSwapchainBackBufferCount e.g. 250 * 3 = 750 + #define RMLUI_RENDER_BACKEND_FIELD_PREALLOCATED_CONSTANTBUFFERS 250 + #endif + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_MSAA_SAMPLE_COUNT + #define RMLUI_RENDER_BACKEND_FIELD_MSAA_SAMPLE_COUNT RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_MSAA_SAMPLE_COUNT + #else + #define RMLUI_RENDER_BACKEND_FIELD_MSAA_SAMPLE_COUNT 2 + #endif + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_DESCRIPTOR_HEAP_RTV + #define RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_RTV RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_DESCRIPTOR_HEAP_RTV + #else + #define RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_RTV 8 + #endif + + #ifdef RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_DESCRIPTOR_HEAP_DSV + #define RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_DSV RMLUI_RENDER_BACKEND_OVERRIDE_FIELD_DESCRIPTOR_HEAP_DSV + #else + #define RMLUI_RENDER_BACKEND_FIELD_DESCRIPTOR_HEAP_DSV 8 + #endif + +enum class ProgramId; + +class RenderLayerStack; +namespace Gfx { +struct ProgramData; +struct FramebufferData; +} // namespace Gfx + +/** + * DirectX 12 render interface implementation for RmlUi + * @author wh1t3lord (https://github.com/wh1t3lord) + */ + +class RenderInterface_DX12 : public Rml::RenderInterface { +public: + #ifdef RMLUI_DX_DEBUG + // only for your mouse course to see how it is in MBs, just move cursor to variable on left side from = and you see the value of MBs + + static constexpr size_t kDebugMB_BA = (RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION / 1024) / 1024; + static constexpr size_t kDebugMB_TA = (RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION / 1024) / 1024; + #endif + +public: + class GraphicsAllocationInfo { + public: + GraphicsAllocationInfo(); + ~GraphicsAllocationInfo(); + + const D3D12MA::VirtualAllocation& Get_VirtualAllocation(void) const noexcept; + void Set_VirtualAllocation(const D3D12MA::VirtualAllocation& info_alloc) noexcept; + + size_t Get_Offset(void) const noexcept; + void Set_Offset(size_t value) noexcept; + + size_t Get_Size(void) const noexcept; + void Set_Size(size_t value) noexcept; + + int Get_BufferIndex(void) const noexcept; + void Set_BufferIndex(int index) noexcept; + + private: + int m_buffer_index; + size_t m_offset; + size_t m_size; + D3D12MA::VirtualAllocation m_alloc_info; + }; + + class TextureHandleType { + public: + TextureHandleType() : + #ifdef RMLUI_DX_DEBUG + m_is_destroyed{}, + #endif + m_p_resource{} + {} + ~TextureHandleType() + { + #ifdef RMLUI_DX_DEBUG + RMLUI_ASSERT(this->m_is_destroyed && "you forgot to destroy the resource!"); + #endif + } + + const GraphicsAllocationInfo& Get_Info() const noexcept { return this->m_info; } + + void Set_Info(const GraphicsAllocationInfo& info) { this->m_info = info; } + + void Set_Resource(void* p_resource) { this->m_p_resource = p_resource; } + + void* Get_Resource() const noexcept { return this->m_p_resource; } + + /// @brief returns debug resource name. Implementation of this method exists only when DEBUG is enabled for this project + /// @param void, nothing + /// @return debug resource name when the source string is passed in LoadTexture method + const Rml::String& Get_ResourceName(void) const + { + #ifdef RMLUI_DX_DEBUG + return this->m_debug_resource_name; + #else + return Rml::String(); + #endif + } + + /// @brief sets m_debug_resource_name field with specified argument as resource_name + /// @param resource_name this string is getting from LoadTexture method from source argument + void Set_ResourceName(const Rml::String& resource_name) + { + #ifdef RMLUI_DX_DEBUG + this->m_debug_resource_name = resource_name; + #else + resource_name; + #endif + } + + // this method calls texture manager! don't call it manually because you must ensure that you freed virtual block if it had + void Destroy() + { + #ifdef RMLUI_DX_DEBUG + if (!this->m_is_destroyed) + { + #endif + if (this->m_p_resource) + { + if (this->m_info.Get_BufferIndex() != -1) + { + ID3D12Resource* p_placed_resource = static_cast(this->m_p_resource); + + p_placed_resource->Release(); + } + else + { + D3D12MA::Allocation* p_committed_resource = static_cast(this->m_p_resource); + + if (p_committed_resource->GetResource()) + { + p_committed_resource->GetResource()->Release(); + } + + p_committed_resource->Release(); + } + + this->m_info = GraphicsAllocationInfo(); + this->m_p_resource = nullptr; + #ifdef RMLUI_DX_DEBUG + this->m_is_destroyed = true; + #endif + } + #ifdef RMLUI_DX_DEBUG + } + #endif + } + + const OffsetAllocator::Allocation& Get_Allocation_DescriptorHeap(void) const noexcept { return this->m_allocation_descriptor_heap; } + void Set_Allocation_DescriptorHeap(const OffsetAllocator::Allocation& allocation) noexcept + { + this->m_allocation_descriptor_heap = allocation; + } + + private: + #ifdef RMLUI_DX_DEBUG + bool m_is_destroyed; + #endif + + GraphicsAllocationInfo m_info; + OffsetAllocator::Allocation m_allocation_descriptor_heap; + void* m_p_resource; // placed or committed (CreateResource from D3D12MA) + #ifdef RMLUI_DX_DEBUG + Rml::String m_debug_resource_name; + #endif + }; + + class ConstantBufferType { + public: + ConstantBufferType(); + ~ConstantBufferType(); + + const GraphicsAllocationInfo& Get_AllocInfo(void) const noexcept; + void Set_AllocInfo(const GraphicsAllocationInfo& info) noexcept; + + void* Get_GPU_StartMemoryForBindingData(void); + + void Set_GPU_StartMemoryForBindingData(void* p_start_pointer); + + bool Is_Free(void) const { return this->m_is_free; } + + private: + bool m_is_free; + GraphicsAllocationInfo m_alloc_info; + void* m_p_gpu_start_memory_for_binding_data; + }; + + class GeometryHandleType { + public: + GeometryHandleType(void) : m_num_vertices{}, m_num_indecies{}, m_one_element_vertex_size{}, m_one_element_index_size{} {} + + ~GeometryHandleType(void) {} + + void Set_InfoVertex(const GraphicsAllocationInfo& info) { this->m_info_vertex = info; } + const GraphicsAllocationInfo& Get_InfoVertex(void) const noexcept { return this->m_info_vertex; } + + void Set_InfoIndex(const GraphicsAllocationInfo& info) { this->m_info_index = info; } + const GraphicsAllocationInfo& Get_InfoIndex(void) const noexcept { return this->m_info_index; } + + void Set_NumVertices(int num) { this->m_num_vertices = num; } + + int Get_NumVertices(void) const { return this->m_num_vertices; } + + void Set_NumIndecies(int num) { this->m_num_indecies = num; } + + int Get_NumIndecies(void) const { return this->m_num_indecies; } + + void Set_SizeOfOneVertex(size_t size) { this->m_one_element_vertex_size = size; } + + size_t Get_SizeOfOneVertex(void) const { return this->m_one_element_vertex_size; } + + void Set_SizeOfOneIndex(size_t size) { this->m_one_element_index_size = size; } + + size_t Get_SizeOfOneIndex(void) const { return this->m_one_element_index_size; } + + const OffsetAllocator::Allocation& Get_Allocation_DescriptorHeap(void) const noexcept { return this->m_allocation_descriptor_heap; } + void Set_Allocation_DescriptorHeap(const OffsetAllocator::Allocation& allocation) noexcept + { + this->m_allocation_descriptor_heap = allocation; + } + + private: + int m_num_vertices; + int m_num_indecies; + size_t m_one_element_vertex_size; + size_t m_one_element_index_size; + GraphicsAllocationInfo m_info_vertex; + GraphicsAllocationInfo m_info_index; + OffsetAllocator::Allocation m_allocation_descriptor_heap; + }; + + class BufferMemoryManager { + public: + BufferMemoryManager(); + ~BufferMemoryManager(); + + void Initialize(ID3D12Device* m_p_device, D3D12MA::Allocator* p_allocator, + OffsetAllocator::Allocator* p_offset_allocator_for_descriptor_heap_srv_cbv_uav, CD3DX12_CPU_DESCRIPTOR_HANDLE* p_handle, + uint32_t size_descriptor_element, size_t size_for_allocation = RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_BUFFER_ALLOCATION, + size_t size_alignment = RMLUI_RENDER_BACKEND_FIELD_ALIGNMENT_FOR_BUFFER); + void Shutdown(); + + void Alloc_Vertex(const void* p_data, int num_vertices, size_t size_of_one_element_in_p_data, GeometryHandleType* p_handle); + void Alloc_Index(const void* p_data, int num_vertices, size_t size_of_one_element_in_p_data, GeometryHandleType* p_handle); + + GraphicsAllocationInfo Alloc_ConstantBuffer(ConstantBufferType* p_resource, size_t size); + + void Free_ConstantBuffer(ConstantBufferType* p_constantbuffer); + void Free_Geometry(GeometryHandleType* p_geometryhandle); + + void* Get_WritableMemoryFromBufferByOffset(const GraphicsAllocationInfo& info); + + D3D12MA::Allocation* Get_BufferByIndex(int buffer_index); + + bool Is_Initialized(void) const; + + private: + void Alloc_Buffer(size_t size + #ifdef RMLUI_DX_DEBUG + , + const Rml::String& debug_name + #endif + ); + + /// @brief searches for block that has enough memory for requested allocation size otherwise returns nullptr that means no block! + /// @param size_for_allocation + /// @return + D3D12MA::VirtualBlock* Get_AvailableBlock(size_t size_for_allocation, int* p_result_buffer_index); + + D3D12MA::VirtualBlock* Get_NotOutOfMemoryAndAvailableBlock(size_t size_for_allocation, int* p_result_buffer_index); + + int Alloc(GraphicsAllocationInfo& info, size_t size, size_t alignment = 0); + + /// @brief suppose we have a such situtation that user goes to really high load document page and it was allocated too much blocks and then + /// all its sessions are in low loaded documents and it means that we will have in that session block that weren't freed and thus we don't + /// spend that memory for textures or for anything else, so if we have blocks that aren't in use it is better to free them + void TryToFreeAvailableBlock(); + + private: + uint32_t m_descriptor_increment_size_srv_cbv_uav; + size_t m_size_for_allocation_in_bytes; + size_t m_size_alignment_in_bytes; + ID3D12Device* m_p_device; + D3D12MA::Allocator* m_p_allocator; + OffsetAllocator::Allocator* m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav; + CD3DX12_CPU_DESCRIPTOR_HANDLE* m_p_start_pointer_of_descriptor_heap_srv_cbv_uav; + /// @brief this is for sub allocating purposes using 'Virtual Allocation' from D3D12MA + Rml::Vector m_virtual_buffers; + /// @brief this is physical representation of VRAM and uses from CPU side for binding data + Rml::Vector> m_buffers; + }; + + /* + * the key feature of this manager is texture management and if texture size is less or equal to 1.0 MB + * it will allocate heap for placing resources and if resource is less (or equal) to 1 MB then I will be written to that heap + * Otherwise will be used heap per texture (committed resource) + */ + class TextureMemoryManager { + public: + TextureMemoryManager(); + ~TextureMemoryManager(); + + /// @brief + /// @param p_allocator from main manager + /// @param size_for_placed_heap by default it is 4Mb in bytes + void Initialize(D3D12MA::Allocator* p_allocator, OffsetAllocator::Allocator* p_offset_allocator_for_descriptor_heap_srv_cbv_uav, + ID3D12Device* p_device, ID3D12GraphicsCommandList* p_copy_command_list, ID3D12CommandAllocator* p_copy_allocator_command, + ID3D12DescriptorHeap* p_descriptor_heap_srv, ID3D12DescriptorHeap* p_descriptor_heap_rtv, ID3D12DescriptorHeap* p_descriptor_heap_dsv, + ID3D12CommandQueue* p_copy_queue, CD3DX12_CPU_DESCRIPTOR_HANDLE* p_handle, RenderInterface_DX12* p_renderer, + size_t size_for_placed_heap = RMLUI_RENDER_BACKEND_FIELD_VIDEOMEMORY_FOR_TEXTURE_ALLOCATION); + void Shutdown(); + + ID3D12Resource* Alloc_Texture(D3D12_RESOURCE_DESC& desc, TextureHandleType* p_impl, const Rml::byte* p_data + #ifdef RMLUI_DX_DEBUG + , + const Rml::String& debug_name + #endif + ); + + // if you want to create texture for rendering to it aka render target texture + ID3D12Resource* Alloc_Texture(D3D12_RESOURCE_DESC& desc, Gfx::FramebufferData* p_impl, D3D12_RESOURCE_FLAGS flags, + D3D12_RESOURCE_STATES initial_state + #ifdef RMLUI_DX_DEBUG + , + const Rml::String& debug_name + #endif + ); + + void Free_Texture(TextureHandleType* type); + + void Free_Texture(Gfx::FramebufferData* p_texture); + + void Free_Texture(TextureHandleType* p_allocated_texture_with_class, bool is_rt, D3D12MA::VirtualAllocation& allocation); + + bool Is_Initialized(void) const; + + private: + bool CanAllocate(size_t total_memory_for_allocation, D3D12MA::VirtualBlock* p_block); + + bool CanBePlacedResource(size_t total_memory_for_allocation); + + // don't use it for MSAA texture because we don't implement a such feature! + bool CanBeSmallResource(size_t base_memory); + + // use index for obtaining heap from m_heaps_placed + D3D12MA::VirtualBlock* Get_AvailableBlock(size_t total_memory_for_allocation, int* result_index); + + void Alloc_As_Committed(size_t base_memory, size_t total_memory, D3D12_RESOURCE_DESC& desc, TextureHandleType* p_impl, + const Rml::byte* p_data); + + // we don't upload to GPU because it is render target and needed to be written + void Alloc_As_Committed(size_t base_memory, size_t total_memory, D3D12_RESOURCE_DESC& desc, D3D12_RESOURCE_STATES initial_state, + TextureHandleType* p_texture, Gfx::FramebufferData* p_impl); + + void Alloc_As_Placed(size_t base_memory, size_t total_memory, D3D12_RESOURCE_DESC& desc, TextureHandleType* p_impl, const Rml::byte* p_data); + + void Upload(bool is_committed, TextureHandleType* p_texture_handle, const D3D12_RESOURCE_DESC& desc, const Rml::byte* p_data, + ID3D12Resource* p_impl); + + size_t BytesPerPixel(DXGI_FORMAT format); + + size_t BitsPerPixel(DXGI_FORMAT format); + + Rml::Pair Create_HeapPlaced(size_t size_for_creation); + + D3D12_CPU_DESCRIPTOR_HANDLE Alloc_RenderTargetResourceView(ID3D12Resource* p_resource, D3D12MA::VirtualAllocation* p_allocation); + D3D12_CPU_DESCRIPTOR_HANDLE Alloc_DepthStencilResourceView(ID3D12Resource* p_resource, D3D12MA::VirtualAllocation* p_allocation); + + private: + size_t m_size_for_placed_heap; + /// @brief limit size that we define as acceptable. On 4 Mb it is enough for placing 1 Mb but higher it is bad. 1 Mb occupies 25% from 4 Mb + /// and that means that m_size_limit_for_being_placed will be determined as 25% * m_size_for_placed_heap; + size_t m_size_limit_for_being_placed; + size_t m_size_srv_cbv_uav_descriptor; + size_t m_size_rtv_descriptor; + size_t m_size_dsv_descriptor; + size_t m_fence_value; + D3D12MA::Allocator* m_p_allocator; + OffsetAllocator::Allocator* m_p_offset_allocator_for_descriptor_heap_srv_cbv_uav; + ID3D12Device* m_p_device; + ID3D12GraphicsCommandList* m_p_command_list; + ID3D12CommandAllocator* m_p_command_allocator; + ID3D12DescriptorHeap* m_p_descriptor_heap_srv; + ID3D12CommandQueue* m_p_copy_queue; + ID3D12Fence* m_p_fence; + HANDLE m_p_fence_event; + CD3DX12_CPU_DESCRIPTOR_HANDLE* m_p_handle; + RenderInterface_DX12* m_p_renderer; + D3D12MA::VirtualBlock* m_p_virtual_block_for_render_target_heap_allocations; + D3D12MA::VirtualBlock* m_p_virtual_block_for_depth_stencil_heap_allocations; + ID3D12DescriptorHeap* m_p_descriptor_heap_rtv; + ID3D12DescriptorHeap* m_p_descriptor_heap_dsv; + Rml::Vector m_blocks; + Rml::Vector m_heaps_placed; + }; + + /* +Manages render targets, including the layer stack and postprocessing framebuffers. + +Layers can be pushed and popped, creating new framebuffers as needed. Typically, geometry is rendered to the top +layer. The layer framebuffers may have MSAA enabled. + +Postprocessing framebuffers are separate from the layers, and are commonly used to apply texture-wide effects +such as filters. They are used both as input and output during rendering, and do not use MSAA. +*/ + class RenderLayerStack { + public: + RenderLayerStack(); + ~RenderLayerStack(); + + void Initialize(RenderInterface_DX12* p_owner); + + // Push a new layer. All references to previously retrieved layers are invalidated. + Rml::LayerHandle PushLayer(); + + // Pop the top layer. All references to previously retrieved layers are invalidated. + void PopLayer(); + + const Gfx::FramebufferData& GetLayer(Rml::LayerHandle layer) const; + const Gfx::FramebufferData& GetTopLayer() const; + const Gfx::FramebufferData& Get_SharedDepthStencil(); + Rml::LayerHandle GetTopLayerHandle() const; + + const Gfx::FramebufferData& GetPostprocessPrimary() { return EnsureFramebufferPostprocess(0); } + const Gfx::FramebufferData& GetPostprocessSecondary() { return EnsureFramebufferPostprocess(1); } + const Gfx::FramebufferData& GetPostprocessTertiary() { return EnsureFramebufferPostprocess(2); } + const Gfx::FramebufferData& GetBlendMask() { return EnsureFramebufferPostprocess(3); } + + void SwapPostprocessPrimarySecondary(); + + void BeginFrame(int new_width, int new_height); + void EndFrame(); + + private: + void DestroyFramebuffers(); + const Gfx::FramebufferData& EnsureFramebufferPostprocess(int index); + + void CreateFramebuffer(Gfx::FramebufferData* p_result, int width, int height, int sample_count, bool is_depth_stencil); + + void DestroyFramebuffer(Gfx::FramebufferData* p_data); + + private: + int m_width, m_height; + // The number of active layers is manually tracked since we re-use the framebuffers stored in the fb_layers stack. + int m_layers_size; + TextureMemoryManager* m_p_manager_texture; + BufferMemoryManager* m_p_manager_buffer; + ID3D12Device* m_p_device; + Gfx::FramebufferData* m_p_depth_stencil; + Rml::Vector m_fb_layers; + Rml::Vector m_fb_postprocess; + }; + +public: + // RenderInterface_DX12(ID3D12Device* p_user_device, ID3D12CommandQueue* p_user_command_queue, + // ID3D12GraphicsCommandList* p_user_graphics_command_list); + + RenderInterface_DX12(void* p_window_handle, ID3D12Device* p_user_device, IDXGISwapChain* p_user_swapchain, bool use_vsync); + RenderInterface_DX12(void* p_window_handle, bool use_vsync); + ~RenderInterface_DX12(); + + // using CreateSurfaceCallback = bool (*)(VkInstance instance, VkSurfaceKHR* out_surface); + + // Returns true if the renderer was successfully constructed. + explicit operator bool() const; + + // The viewport should be updated whenever the window size changes. + void SetViewport(int viewport_width, int viewport_height); + + // Sets up OpenGL states for taking rendering commands from RmlUi. + void BeginFrame(); + // Draws the result to the backbuffer and restores OpenGL state. + void EndFrame(); + + // Optional, can be used to clear the active framebuffer. + void Clear(); + + // -- Inherited from Rml::RenderInterface -- + + Rml::CompiledGeometryHandle CompileGeometry(Rml::Span vertices, Rml::Span indices) override; + void RenderGeometry(Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation, Rml::TextureHandle texture) override; + void ReleaseGeometry(Rml::CompiledGeometryHandle geometry) override; + + void EnableScissorRegion(bool enable) override; + void SetScissorRegion(Rml::Rectanglei region) override; + + void EnableClipMask(bool enable) override; + void RenderToClipMask(Rml::ClipMaskOperation mask_operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) override; + + Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override; + Rml::TextureHandle GenerateTexture(Rml::Span source_data, Rml::Vector2i source_dimensions) override; + void ReleaseTexture(Rml::TextureHandle texture_handle) override; + + void SetTransform(const Rml::Matrix4f* transform) override; + + Rml::LayerHandle PushLayer() override; + void CompositeLayers(Rml::LayerHandle source, Rml::LayerHandle destination, Rml::BlendMode blend_mode, + Rml::Span filters) override; + void PopLayer() override; + + Rml::TextureHandle SaveLayerAsTexture(Rml::Vector2i dimensions) override; + + Rml::CompiledFilterHandle SaveLayerAsMaskImage() override; + + Rml::CompiledFilterHandle CompileFilter(const Rml::String& name, const Rml::Dictionary& parameters) override; + void ReleaseFilter(Rml::CompiledFilterHandle filter) override; + + Rml::CompiledShaderHandle CompileShader(const Rml::String& name, const Rml::Dictionary& parameters) override; + void RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle, Rml::Vector2f translation, + Rml::TextureHandle texture) override; + void ReleaseShader(Rml::CompiledShaderHandle effect_handle) override; + + // Can be passed to RenderGeometry() to enable texture rendering without changing the bound texture. + static constexpr Rml::TextureHandle TextureEnableWithoutBinding = Rml::TextureHandle(-1); + // Can be passed to RenderGeometry() to leave the bound texture and used program unchanged. + static constexpr Rml::TextureHandle TexturePostprocess = Rml::TextureHandle(-2); + + void Shutdown() noexcept; + void Initialize(void) noexcept; + + bool IsSwapchainValid() noexcept; + void RecreateSwapchain() noexcept; + + ID3D12Fence* Get_Fence(void); + HANDLE Get_FenceEvent(void); + Rml::Array& Get_FenceValues(void); + uint32_t Get_CurrentFrameIndex(void); + + ID3D12Device* Get_Device(void) const; + TextureMemoryManager& Get_TextureManager(void); + BufferMemoryManager& Get_BufferManager(void); + +private: + void Initialize_Device(void) noexcept; + void Initialize_Adapter(void) noexcept; + void Initialize_DebugLayer(void) noexcept; + + void Initialize_Swapchain(int width, int height) noexcept; + void Initialize_SyncPrimitives(void) noexcept; + void Initialize_CommandAllocators(void); + + void Initialize_Allocator(void) noexcept; + + void Destroy_Swapchain() noexcept; + void Destroy_SyncPrimitives(void) noexcept; + void Destroy_CommandAllocators(void) noexcept; + void Destroy_CommandList(void) noexcept; + void Destroy_Allocator(void) noexcept; + + void Flush() noexcept; + uint64_t Signal() noexcept; + void WaitForFenceValue(uint64_t fence_value, std::chrono::milliseconds time = std::chrono::milliseconds::max()); + + void Create_Resources_DependentOnSize() noexcept; + void Destroy_Resources_DependentOnSize() noexcept; + + ID3D12DescriptorHeap* Create_Resource_DescriptorHeap(D3D12_DESCRIPTOR_HEAP_TYPE type, D3D12_DESCRIPTOR_HEAP_FLAGS flags, + uint32_t descriptor_count) noexcept; + + ID3D12CommandAllocator* Create_CommandAllocator(D3D12_COMMAND_LIST_TYPE type); + ID3D12GraphicsCommandList* Create_CommandList(ID3D12CommandAllocator* p_allocator, D3D12_COMMAND_LIST_TYPE type) noexcept; + + ID3D12CommandQueue* Create_CommandQueue(D3D12_COMMAND_LIST_TYPE type) noexcept; + + void Create_Resource_RenderTargetViews(); + void Destroy_Resource_RenderTagetViews(); + + // pipelines + + void Create_Resource_For_Shaders(void); + void Create_Resource_For_Shaders_ConstantBufferHeap(void); + void Destroy_Resource_For_Shaders_ConstantBufferHeap(void); + void Destroy_Resource_For_Shaders(void); + + void Create_Resource_Pipelines(); + void Create_Resource_Pipeline_Color(); + void Create_Resource_Pipeline_Texture(); + void Create_Resource_Pipeline_Gradient(); + void Create_Resource_Pipeline_Creation(); + void Create_Resource_Pipeline_Passthrough(); + void Create_Resource_Pipeline_ColorMatrix(); + void Create_Resource_Pipeline_BlendMask(); + void Create_Resource_Pipeline_Blur(); + void Create_Resource_Pipeline_DropShadow(); + void Create_Resource_Pipeline_Count(); + + void Create_Resource_DepthStencil(); + void Destroy_Resource_DepthStencil(); + + void Destroy_Resource_Pipelines(); + + ID3DBlob* Compile_Shader(const Rml::String& relative_path_to_shader, const char* entry_point, const char* shader_version, UINT flags); + + bool CheckTearingSupport() noexcept; + + IDXGIAdapter* Get_Adapter(bool is_use_warp) noexcept; + + void PrintAdapterDesc(IDXGIAdapter* p_adapter); + + void SetScissor(Rml::Rectanglei region, bool vertically_flip = false); + + void SubmitTransformUniform(ConstantBufferType& constant_buffer, const Rml::Vector2f& translation); + + void UseProgram(ProgramId pipeline_id); + + ConstantBufferType* Get_ConstantBuffer(uint32_t current_back_buffer_index); + + void Free_Geometry(GeometryHandleType* p_handle); + void Free_Texture(TextureHandleType* p_handle); + + void Update_PendingForDeletion_Geometry(); + void Update_PendingForDeletion_Texture(); + + void BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_id); + + void RenderFilters(Rml::Span filter_handles); + void RenderBlur(float sigma, const Gfx::FramebufferData& source_destination, const Gfx::FramebufferData& temp, + const Rml::Rectanglei window_flipped); + + void DrawFullscreenQuad(); + void DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling = Rml::Vector2f(1.f)); + +private: + bool m_is_full_initialization; + bool m_is_shutdown_called; + bool m_is_use_vsync; + bool m_is_use_tearing; + bool m_is_scissor_was_set; + bool m_is_stencil_enabled; + bool m_is_stencil_equal; + int m_width; + int m_height; + int m_current_clip_operation; + ProgramId m_active_program_id; + Rml::Rectanglei m_scissor; + uint32_t m_size_descriptor_heap_render_target_view; + uint32_t m_size_descriptor_heap_shaders; + uint32_t m_current_back_buffer_index; + + /// @brief depends on compile build type if it is debug it means D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION otherwise it is 0 + UINT m_default_shader_flags; + std::bitset m_program_state_transform_dirty; + ID3D12Device* m_p_device; + ID3D12CommandQueue* m_p_command_queue; + ID3D12CommandQueue* m_p_copy_queue; + IDXGISwapChain4* m_p_swapchain; + ID3D12GraphicsCommandList* m_p_command_graphics_list; + ID3D12DescriptorHeap* m_p_descriptor_heap_render_target_view; + ID3D12DescriptorHeap* m_p_descriptor_heap_render_target_view_for_texture_manager; + ID3D12DescriptorHeap* m_p_descriptor_heap_depth_stencil_view_for_texture_manager; + // cbv; srv; uav; all in one + ID3D12DescriptorHeap* m_p_descriptor_heap_shaders; + ID3D12DescriptorHeap* m_p_descriptor_heap_depthstencil; + D3D12MA::Allocation* m_p_depthstencil_resource; + ID3D12Fence* m_p_backbuffer_fence; + IDXGIAdapter* m_p_adapter; + + ID3D12PipelineState* m_pipelines[17]; + ID3D12RootSignature* m_root_signatures[17]; + + ID3D12CommandAllocator* m_p_copy_allocator; + ID3D12GraphicsCommandList* m_p_copy_command_list; + + D3D12MA::Allocator* m_p_allocator; + OffsetAllocator::Allocator* m_p_offset_allocator_for_descriptor_heap_shaders; + // D3D12MA::Allocation* m_p_constant_buffers[kSwapchainBackBufferCount]; + + HWND m_p_window_handle; + HANDLE m_p_fence_event; + uint64_t m_fence_value; + Rml::CompiledGeometryHandle m_precompiled_fullscreen_quad_geometry; + + Rml::Array m_backbuffers_resources; + Rml::Array m_backbuffers_allocators; + Rml::Array m_backbuffers_fence_values; + Rml::Array m_constant_buffer_count_per_frame; + Rml::Array m_vertex_and_index_buffer_count_per_frame; + // per object (per draw) + Rml::Array, 1> m_constantbuffers; + Rml::Vector m_pending_for_deletion_geometry; + // todo: delete this if final implementation doesn't use this + Rml::Vector m_pending_for_deletion_textures; + // ConstantBufferType m_constantbuffer; + + // this represents user's data from initialization structure about multisampling features + DXGI_SAMPLE_DESC m_desc_sample; + CD3DX12_CPU_DESCRIPTOR_HANDLE m_handle_shaders; + BufferMemoryManager m_manager_buffer; + TextureMemoryManager m_manager_texture; + Rml::Matrix4f m_constant_buffer_data_transform; + Rml::Matrix4f m_constant_buffer_data_projection; + Rml::Matrix4f m_projection; + RenderLayerStack m_manager_render_layer; +}; + +// forward declaration +namespace Backend { +class RmlRenderInitInfo; +} + +namespace RmlDX12 { + +// If you pass a second argument and the second argument is valid you will initialize renderer partially, it means that it will be integrated into +// your engine thus it WILL NOT create device, command queue and etc, but if you don't have own renderer system and you don't want to initialize +// DirectX by your own you just don't need to pass anything (or nullptr) to second argument, so Rml will initialize renderer fully. Optionally, the +// out message describes the loaded GL version or an error message on failure. +RenderInterface_DX12* Initialize(Rml::String* out_message, Backend::RmlRenderInitInfo* p_info); + +/// @brief you need to destroy allocated object manually it just calls shutdown method! +/// @param p_instance allocated instance from RmlDX12::Initialize method +void Shutdown(RenderInterface_DX12* p_instance); + +} // namespace RmlDX12 + +#endif // RMLUI_PLATFORM_WIN32 + +#endif diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 34249cc15..b276acb25 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -1356,6 +1356,7 @@ static void SetBlurWeights(GLint weights_location, float sigma) void RenderInterface_GL3::RenderBlur(float sigma, const Gfx::FramebufferData& source_destination, const Gfx::FramebufferData& temp, const Rml::Rectanglei window_flipped) { + OutputDebugStringA("::RenderBlur()\n"); RMLUI_ASSERT(&source_destination != &temp && source_destination.width == temp.width && source_destination.height == temp.height); RMLUI_ASSERT(window_flipped.Valid()); @@ -1687,6 +1688,8 @@ Rml::CompiledShaderHandle RenderInterface_GL3::CompileShader(const Rml::String& void RenderInterface_GL3::RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle, Rml::Vector2f translation, Rml::TextureHandle /*texture*/) { + OutputDebugStringA("::RenderShader()\n"); + RMLUI_ASSERT(shader_handle && geometry_handle); const CompiledShader& shader = *reinterpret_cast(shader_handle); const CompiledShaderType type = shader.type; @@ -1744,6 +1747,7 @@ void RenderInterface_GL3::ReleaseShader(Rml::CompiledShaderHandle shader_handle) void RenderInterface_GL3::BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_handle) { + OutputDebugStringA("::BlitLayerToPostprocessPrimary()\n"); const Gfx::FramebufferData& source = render_layers.GetLayer(layer_handle); const Gfx::FramebufferData& destination = render_layers.GetPostprocessPrimary(); glBindFramebuffer(GL_READ_FRAMEBUFFER, source.framebuffer); @@ -1755,6 +1759,8 @@ void RenderInterface_GL3::BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_h void RenderInterface_GL3::RenderFilters(Rml::Span filter_handles) { + OutputDebugStringA("::RenderFilters()\n"); + for (const Rml::CompiledFilterHandle filter_handle : filter_handles) { const CompiledFilter& filter = *reinterpret_cast(filter_handle); @@ -1881,6 +1887,8 @@ void RenderInterface_GL3::RenderFilters(Rml::Span filters) { + OutputDebugStringA("::CompositeLayers()\n"); + using Rml::BlendMode; // Blit source layer to postprocessing buffer. Do this regardless of whether we actually have any filters to be @@ -1924,12 +1934,14 @@ void RenderInterface_GL3::CompositeLayers(Rml::LayerHandle source_handle, Rml::L void RenderInterface_GL3::PopLayer() { + OutputDebugStringA("::PopLayer()\n"); render_layers.PopLayer(); glBindFramebuffer(GL_FRAMEBUFFER, render_layers.GetTopLayer().framebuffer); } Rml::TextureHandle RenderInterface_GL3::SaveLayerAsTexture(Rml::Vector2i dimensions) { + OutputDebugStringA("::SaveLayerAsTexture()\n"); Rml::TextureHandle render_texture = GenerateTexture({}, dimensions); if (!render_texture) return {}; @@ -1971,6 +1983,7 @@ Rml::TextureHandle RenderInterface_GL3::SaveLayerAsTexture(Rml::Vector2i dimensi Rml::CompiledFilterHandle RenderInterface_GL3::SaveLayerAsMaskImage() { + OutputDebugStringA("::SaveLayerAsMaskImage()\n"); BlitLayerToPostprocessPrimary(render_layers.GetTopLayerHandle()); const Gfx::FramebufferData& source = render_layers.GetPostprocessPrimary(); @@ -2036,6 +2049,7 @@ RenderInterface_GL3::RenderLayerStack::~RenderLayerStack() Rml::LayerHandle RenderInterface_GL3::RenderLayerStack::PushLayer() { + OutputDebugStringA("LS::PushLayer()\n"); RMLUI_ASSERT(layers_size <= (int)fb_layers.size()); if (layers_size == (int)fb_layers.size()) @@ -2053,34 +2067,40 @@ Rml::LayerHandle RenderInterface_GL3::RenderLayerStack::PushLayer() void RenderInterface_GL3::RenderLayerStack::PopLayer() { + OutputDebugStringA("LS::PopLayer()\n"); RMLUI_ASSERT(layers_size > 0); layers_size -= 1; } const Gfx::FramebufferData& RenderInterface_GL3::RenderLayerStack::GetLayer(Rml::LayerHandle layer) const { + OutputDebugStringA("LS::GetLayer()\n"); RMLUI_ASSERT((size_t)layer < (size_t)layers_size); return fb_layers[layer]; } const Gfx::FramebufferData& RenderInterface_GL3::RenderLayerStack::GetTopLayer() const { + OutputDebugStringA("LS::GetTopLayer()\n"); return GetLayer(GetTopLayerHandle()); } Rml::LayerHandle RenderInterface_GL3::RenderLayerStack::GetTopLayerHandle() const { + OutputDebugStringA("LS::GetTopLayerHandle()\n"); RMLUI_ASSERT(layers_size > 0); return static_cast(layers_size - 1); } void RenderInterface_GL3::RenderLayerStack::SwapPostprocessPrimarySecondary() { + OutputDebugStringA("LS::SwapPostprocessPrimarySecondary()\n"); std::swap(fb_postprocess[0], fb_postprocess[1]); } void RenderInterface_GL3::RenderLayerStack::BeginFrame(int new_width, int new_height) { + OutputDebugStringA("LS::BeginFrame()\n"); RMLUI_ASSERT(layers_size == 0); if (new_width != width || new_height != height) @@ -2096,12 +2116,14 @@ void RenderInterface_GL3::RenderLayerStack::BeginFrame(int new_width, int new_he void RenderInterface_GL3::RenderLayerStack::EndFrame() { + OutputDebugStringA("LS::EndFrame()\n"); RMLUI_ASSERT(layers_size == 1); PopLayer(); } void RenderInterface_GL3::RenderLayerStack::DestroyFramebuffers() { + OutputDebugStringA("LS::DestroyFrameBuffer()\n"); RMLUI_ASSERTMSG(layers_size == 0, "Do not call this during frame rendering, that is, between BeginFrame() and EndFrame()."); for (Gfx::FramebufferData& fb : fb_layers) @@ -2115,6 +2137,7 @@ void RenderInterface_GL3::RenderLayerStack::DestroyFramebuffers() const Gfx::FramebufferData& RenderInterface_GL3::RenderLayerStack::EnsureFramebufferPostprocess(int index) { + OutputDebugStringA("LS::EnsureFramebufferPostprocess()\n"); RMLUI_ASSERT(index < (int)fb_postprocess.size()) Gfx::FramebufferData& fb = fb_postprocess[index]; if (!fb.framebuffer) diff --git a/CMake/BackendFileList.cmake b/CMake/BackendFileList.cmake index 13afccaaf..cdf1d24dc 100644 --- a/CMake/BackendFileList.cmake +++ b/CMake/BackendFileList.cmake @@ -25,6 +25,20 @@ set(Win32_VK_HDR_FILES ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Vulkan/vk_mem_alloc.h ) +set(Win32_DX12_SRC_FILES + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_Win32.cpp + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Renderer_DX12.cpp + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Backend_Win32_DX12.cpp + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_DirectX/D3D12MemAlloc.cpp + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_DirectX/offsetAllocator.cpp +) +set(Win32_DX12_HDR_FILES + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_Win32.h + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Renderer_DX12.h + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_DirectX/D3D12MemAlloc.h + ${PROJECT_SOURCE_DIR}/Backends/RmlUi_DirectX/offsetAllocator.hpp +) + set(X11_GL2_SRC_FILES ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Platform_X11.cpp ${PROJECT_SOURCE_DIR}/Backends/RmlUi_Renderer_GL2.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c20704f5..38c359b42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,7 +173,7 @@ option(BUILD_SAMPLES "Build samples" OFF) option(ENABLE_HARFBUZZ "Enable HarfBuzz for text-shaping sample. Requires the HarfBuzz library." OFF) set(SAMPLES_BACKEND "auto" CACHE STRING "Backend platform and renderer used for the samples.") -set_property(CACHE SAMPLES_BACKEND PROPERTY STRINGS auto Win32_GL2 Win32_VK X11_GL2 SDL_GL2 SDL_GL3 SDL_VK SDL_SDLrenderer SFML_GL2 GLFW_GL2 GLFW_GL3 GLFW_VK BackwardCompatible_GLFW_GL2 BackwardCompatible_GLFW_GL3) +set_property(CACHE SAMPLES_BACKEND PROPERTY STRINGS auto Win32_GL2 Win32_DX12 Win32_VK X11_GL2 SDL_GL2 SDL_GL3 SDL_VK SDL_SDLrenderer SFML_GL2 GLFW_GL2 GLFW_GL3 GLFW_VK BackwardCompatible_GLFW_GL2 BackwardCompatible_GLFW_GL3) if(SAMPLES_BACKEND STREQUAL "auto") if(EMSCRIPTEN) @@ -876,6 +876,22 @@ if(BUILD_SAMPLES OR BUILD_TESTING) target_link_libraries(shell PRIVATE ${CMAKE_DL_LIBS}) endif() + if (SAMPLES_BACKEND MATCHES "DX12$") + message("-- Using DirectX-12 (DX12) renderer backend") + + find_package(directx-headers CONFIG REQUIRED) + + option(RMLUI_DX_DEBUG "Enable debugging mode for DirectX renderers" OFF) + mark_as_advanced(RMLUI_DX_DEBUG) + + if (RMLUI_DX_DEBUG) + target_compile_definitions(shell PRIVATE RMLUI_DX_DEBUG) + endif() + + target_link_libraries(shell PRIVATE Microsoft::DirectX-Guids Microsoft::DirectX-Headers) + target_link_libraries(shell PRIVATE ${CMAKE_DL_LIBS}) + endif() + if(SAMPLES_BACKEND MATCHES "GL3$") message("-- Adding OpenGL 3 renderer backend.") if(EMSCRIPTEN) diff --git a/readme.md b/readme.md index 9ef5402b6..5be99f283 100644 --- a/readme.md +++ b/readme.md @@ -33,6 +33,7 @@ Documentation is located at https://mikke89.github.io/RmlUiDoc/ - Efficient application-wide styling, with a custom-built templating engine. - Fully featured control set: buttons, sliders, drop-downs, etc. - Runtime visual debugging suite. +- **Easy integrating graphical APIs backends (see "RmlUi Backends" section and what library supports currently), also check integration samples** ## Extensible @@ -103,11 +104,34 @@ To build RmlUi with the included samples we can use git and CMake together with ``` vcpkg install freetype +vcpkg install directx-headers git clone https://github.com/mikke89/RmlUi.git cd RmlUi cmake -B Build -S . -DBUILD_SAMPLES=ON -DCMAKE_TOOLCHAIN_FILE="/scripts/buildsystems/vcpkg.cmake" cmake --build Build ``` + +##### DirectX + +TODO: Mikke fix this section, do you need this text? + +With a new added backend as DirectX 12 you need to add these lines for successfully compiling DirectX 12's samples. + +``` +vcpkg install directx-headers +``` + +So as final result will be + +``` +vcpkg install freetype +vcpkg install directx-headers +git clone https://github.com/mikke89/RmlUi.git +cd RmlUi +cmake -B Build -S . -DBUILD_SAMPLES=ON -DCMAKE_TOOLCHAIN_FILE="/scripts/buildsystems/vcpkg.cmake" +cmake --build Build +``` + Make sure to replace the path to vcpkg. When this completes, feel free to test the freshly built samples, such as the `invader` sample, and enjoy! The executables should be located somewhere in the `Build` directory. #### Conan @@ -146,7 +170,8 @@ The provided backends on the other hand are not intended to be used directly by |-------------------|:---------------:|:----------:|:----------:|:-------:|:-------:|-------------------------------------------------------------------| | OpenGL 2 (GL2) | ✔️ | ✔️ | ✔️ | ❌ | ❌ | Uncompressed TGA | | OpenGL 3 (GL3) | ✔️ | ✔️ | ✔️ | ️ ✔️ | ️ ✔️ | Uncompressed TGA | -| Vulkan (VK) | ✔️ | ✔️ | ❌ | ❌ | ❌ | Uncompressed TGA | +| Vulkan (VK) | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | Uncompressed TGA | +| DirectX 12 (DX12) | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | Uncompressed TGA | | SDLrenderer | ✔️ | ❌ | ❌ | ❌ | ❌ | Based on [SDL_image](https://wiki.libsdl.org/SDL_image/FrontPage) | **Basic rendering**: Render geometry with colors, textures, and rectangular clipping (scissoring). Sufficient for basic 2d-layouts.\ @@ -172,19 +197,24 @@ The provided backends on the other hand are not intended to be used directly by ### Backends -| Platform \ Renderer | OpengGL 2 | OpengGL 3 | Vulkan | SDLrenderer | -|---------------------|:---------:|:---------:|:---------:|:-----------:| -| Win32 | ✔️ | | ✔️ | | -| X11 | ✔️ | | | | -| SFML | ✔️ | | | | -| GLFW | ✔️ | ✔️ | ✔️ | | -| SDL¹ | ✔️ | ✔️² | ✔️ | ✔️ | +| Platform \ Renderer | OpengGL 2 | OpengGL 3 | Vulkan | SDLrenderer | DirectX 12 | +|---------------------|:---------:|:---------:|:---------:|:-----------:|:-----------:| +| Win32 | ✔️ | | ✔️ | | ✔️ | +| X11 | ✔️ | | | | ❌ | +| SFML | ✔️ | | | | | +| GLFW | ✔️ | ✔️ | ✔️ | | ✔️ | +| SDL¹ | ✔️ | ✔️² | ✔️ | ✔️ | ✔️ | ¹ SDL backends extend their respective renderers to provide image support based on SDL_image.\ ² Supports Emscripten compilation target. +❌ - means don't support natively and thus can't be implemented for a specified platform. + When building the samples, the backend can be selected by setting the CMake option `SAMPLES_BACKEND` to `_` for any of the above supported combinations of platforms and renderers, such as `SDL_GL3`. +### Render backend integration + +Document this section please ## Example document @@ -472,6 +502,10 @@ See See [Backends/RmlUi_Vulkan/LICENSE.txt](Backends/RmlUi_Vulkan/LICENSE.txt) - MIT licensed. +#### Library included with the DirectX 12 backend *(in Backends/RmlUi_DirectX/)* + +See [Backends/RmlUi_DirectX/LICENSE.txt](Backends/RmlUi_DirectX/LICENSE.txt) - MIT licensed. + #### Libraries included with the test suite *(in Tests/Dependencies/)* See [Tests/Dependencies/LICENSE.txt](Tests/Dependencies/LICENSE.txt).