diff --git a/Backends/CMakeLists.txt b/Backends/CMakeLists.txt index 22c2c70e5..b53fe38e1 100644 --- a/Backends/CMakeLists.txt +++ b/Backends/CMakeLists.txt @@ -34,6 +34,17 @@ target_sources(rmlui_backend_Win32_VK INTERFACE ) target_link_libraries(rmlui_backend_Win32_VK INTERFACE rmlui_backend_common_headers) +add_library(rmlui_backend_Win32_DX11 INTERFACE) +target_sources(rmlui_backend_Win32_DX11 INTERFACE + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_Win32.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_DX11.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_Win32_DX11.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_Win32.h" + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_DX11.h" + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_Windows.h" +) +target_link_libraries(rmlui_backend_Win32_DX11 INTERFACE rmlui_backend_common_headers d3d11.lib dxgi.lib d3dcompiler.lib) + add_library(rmlui_backend_X11_GL2 INTERFACE) target_sources(rmlui_backend_X11_GL2 INTERFACE "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_X11.cpp" @@ -146,6 +157,17 @@ if(UNIX) target_link_libraries(rmlui_backend_GLFW_GL3 INTERFACE ${CMAKE_DL_LIBS}) endif() +add_library(rmlui_backend_GLFW_DX11 INTERFACE) +target_sources(rmlui_backend_GLFW_DX11 INTERFACE + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_DX11.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Backend_GLFW_DX11.cpp" + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.h" + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Renderer_DX11.h" + "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Include_Windows.h" +) +target_link_libraries(rmlui_backend_GLFW_DX11 INTERFACE rmlui_backend_common_headers d3d11.lib dxgi.lib d3dcompiler.lib glfw) + add_library(rmlui_backend_GLFW_VK INTERFACE) target_sources(rmlui_backend_GLFW_VK INTERFACE "${CMAKE_CURRENT_LIST_DIR}/RmlUi_Platform_GLFW.cpp" diff --git a/Backends/RmlUi_Backend_GLFW_DX11.cpp b/Backends/RmlUi_Backend_GLFW_DX11.cpp new file mode 100644 index 000000000..2557bfac7 --- /dev/null +++ b/Backends/RmlUi_Backend_GLFW_DX11.cpp @@ -0,0 +1,434 @@ +/* + * 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_Backend.h" +#include "RmlUi_Platform_GLFW.h" +#include "RmlUi_Renderer_DX11.h" +#include +#include +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#include + +#ifdef RMLUI_USE_STB_IMAGE_LOADER + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + #include + #include +#endif + +/** + Custom render interface example for the DX11/GLFW backend. + + Overloads the DX11 render interface to load textures through stb_image. + */ +class RenderInterface_DX11_Win32 : public RenderInterface_DX11 { +public: + RenderInterface_DX11_Win32() {} + +#ifdef RMLUI_USE_STB_IMAGE_LOADER + Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override + { + int texture_width = 0, texture_height = 0, num_channels = 0; + size_t image_size_bytes = 0; + uint8_t* texture_data = nullptr; + + // Find where on disk the file is + Rml::FileInterface* file_interface = Rml::GetFileInterface(); + Rml::FileHandle file_handle = file_interface->Open(source); + if (!file_handle) + { + // Tell RmlUI that the image is invalid + texture_data = nullptr; + return false; + } + + // Load the file through stb_image + texture_data = stbi_load_from_file((FILE*)file_handle, &texture_width, &texture_height, &num_channels, 0); + + // If the file data is correct + if (texture_data != nullptr) + { + // Compute number of elements in texture + image_size_bytes = texture_width * texture_height * num_channels; + + // Pre-multiply the data + for (int i = 0; i < image_size_bytes; i += 4) + { + texture_data[i + 0] = (uint8_t)((texture_data[i + 0] * texture_data[i + 3]) / 255); + texture_data[i + 1] = (uint8_t)((texture_data[i + 1] * texture_data[i + 3]) / 255); + texture_data[i + 2] = (uint8_t)((texture_data[i + 2] * texture_data[i + 3]) / 255); + } + + texture_dimensions.x = texture_width; + texture_dimensions.y = texture_height; + + Rml::TextureHandle handle = GenerateTexture({texture_data, image_size_bytes}, texture_dimensions); + + stbi_image_free(texture_data); + return handle; + } + return false; + } +#endif +}; + +static void SetupCallbacks(GLFWwindow* window); + +// D3D Creation / Cleanup functions +static bool CreateDeviceD3D(HWND hwnd); +static void CleanupDeviceD3D(); +static void CreateRenderTarget(); +static void CleanupRenderTarget(); + +static void LogErrorFromGLFW(int error, const char* description) +{ + Rml::Log::Message(Rml::Log::LT_ERROR, "GLFW error (0x%x): %s", error, description); +} + +/** + Global data used by this backend. + + Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown(). + */ +struct BackendData { + SystemInterface_GLFW system_interface; + RenderInterface_DX11_Win32 render_interface; + GLFWwindow* window = nullptr; + int glfw_active_modifiers = 0; + bool context_dimensions_dirty = true; + + // D3D11 resources + struct { + ID3D11Device* pd3dDevice = nullptr; + ID3D11DeviceContext* pd3dDeviceContext = nullptr; + IDXGISwapChain* pSwapChain = nullptr; + bool swapchain_occluded = false; + ID3D11RenderTargetView* pMainRenderTargetView = nullptr; + } d3d_resources; + + // Arguments set during event processing and nulled otherwise. + Rml::Context* context = nullptr; + KeyDownCallback key_down_callback = nullptr; +}; +static Rml::UniquePtr data; + +bool Backend::Initialize(const char* name, int width, int height, bool allow_resize) +{ + RMLUI_ASSERT(!data); + + glfwSetErrorCallback(LogErrorFromGLFW); + + if (!glfwInit()) + return false; + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, allow_resize ? GLFW_TRUE : GLFW_FALSE); + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); + + GLFWwindow* window = glfwCreateWindow(width, height, name, nullptr, nullptr); + if (!window) + return false; + + // Construct the system and render interface, this includes compiling all the shaders. If this fails, it is likely an error in the shader code. + data = Rml::MakeUnique(); + if (!data) + return false; + + data->window = window; + data->system_interface.SetWindow(window); + + HWND windowHwnd = glfwGetWin32Window(window); + if (!CreateDeviceD3D(windowHwnd)) + { + Shutdown(); + return false; + } + +#ifdef RMLUI_USE_STB_IMAGE_LOADER + // Disable pre-multiplication because loading images through stb_image will inconsistently return pre-multiplied images (only iPhone PNGs are + // pre-multipled) We handle pre-multiplication manually on decode + stbi_set_unpremultiply_on_load(true); + // iPhone PNGs are encoded as BGRA, this tells stb_image to unpack as RGBA + stbi_convert_iphone_png_to_rgb(true); +#endif + + data->render_interface.Init(data->d3d_resources.pd3dDevice); + + // The window size may have been scaled by DPI settings, get the actual pixel size. + glfwGetFramebufferSize(window, &width, &height); + data->render_interface.SetViewport(width, height); + + // Receive num lock and caps lock modifiers for proper handling of numpad inputs in text fields. + glfwSetInputMode(window, GLFW_LOCK_KEY_MODS, GLFW_TRUE); + + // Setup the input and window event callback functions. + SetupCallbacks(window); + + return true; +} + +void Backend::Shutdown() +{ + RMLUI_ASSERT(data); + + // Cleanup renderer resources + data->render_interface.Cleanup(); + + // Shutdown DirectX11 + CleanupDeviceD3D(); + + glfwDestroyWindow(data->window); + data.reset(); + glfwTerminate(); +} + +Rml::SystemInterface* Backend::GetSystemInterface() +{ + RMLUI_ASSERT(data); + return &data->system_interface; +} + +Rml::RenderInterface* Backend::GetRenderInterface() +{ + RMLUI_ASSERT(data); + return &data->render_interface; +} + +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; + + Rml::Vector2i window_size; + float dp_ratio = 1.f; + glfwGetFramebufferSize(data->window, &window_size.x, &window_size.y); + glfwGetWindowContentScale(data->window, &dp_ratio, nullptr); + + context->SetDimensions(window_size); + context->SetDensityIndependentPixelRatio(dp_ratio); + + CleanupRenderTarget(); + data->d3d_resources.pSwapChain->ResizeBuffers(0, window_size.x, window_size.y, DXGI_FORMAT_UNKNOWN, 0); + CreateRenderTarget(); + } + + data->context = context; + data->key_down_callback = key_down_callback; + + if (power_save) + glfwWaitEventsTimeout(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0)); + else + glfwPollEvents(); + + data->context = nullptr; + data->key_down_callback = nullptr; + + const bool result = !glfwWindowShouldClose(data->window); + glfwSetWindowShouldClose(data->window, GLFW_FALSE); + return result; +} + +void Backend::RequestExit() +{ + RMLUI_ASSERT(data); + glfwSetWindowShouldClose(data->window, GLFW_TRUE); +} + +void Backend::BeginFrame() +{ + RMLUI_ASSERT(data); + data->render_interface.BeginFrame(data->d3d_resources.pMainRenderTargetView); +} + +void Backend::PresentFrame() +{ + RMLUI_ASSERT(data); + data->render_interface.EndFrame(); + + // Present + HRESULT hr = data->d3d_resources.pSwapChain->Present(1, 0); // Present with vsync + // HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync + data->d3d_resources.swapchain_occluded = (hr == DXGI_STATUS_OCCLUDED); + + // Optional, used to mark frames during performance profiling. + RMLUI_FrameMark; +} + +static void SetupCallbacks(GLFWwindow* window) +{ + RMLUI_ASSERT(data); + + // Key input + glfwSetKeyCallback(window, [](GLFWwindow* /*window*/, int glfw_key, int /*scancode*/, int glfw_action, int glfw_mods) { + if (!data->context) + return; + + // Store the active modifiers for later because GLFW doesn't provide them in the callbacks to the mouse input events. + data->glfw_active_modifiers = glfw_mods; + + // 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; + + switch (glfw_action) + { + case GLFW_PRESS: + case GLFW_REPEAT: + { + const Rml::Input::KeyIdentifier key = RmlGLFW::ConvertKey(glfw_key); + const int key_modifier = RmlGLFW::ConvertKeyModifiers(glfw_mods); + float dp_ratio = 1.f; + glfwGetWindowContentScale(data->window, &dp_ratio, nullptr); + + // See if we have any global shortcuts that take priority over the context. + if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, true)) + break; + // Otherwise, hand the event over to the context by calling the input handler as normal. + if (!RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods)) + break; + // The key was not consumed by the context either, try keyboard shortcuts of lower priority. + if (key_down_callback && !key_down_callback(context, key, key_modifier, dp_ratio, false)) + break; + } + break; + case GLFW_RELEASE: RmlGLFW::ProcessKeyCallback(context, glfw_key, glfw_action, glfw_mods); break; + } + }); + + glfwSetCharCallback(window, [](GLFWwindow* /*window*/, unsigned int codepoint) { RmlGLFW::ProcessCharCallback(data->context, codepoint); }); + + glfwSetCursorEnterCallback(window, [](GLFWwindow* /*window*/, int entered) { RmlGLFW::ProcessCursorEnterCallback(data->context, entered); }); + + // Mouse input + glfwSetCursorPosCallback(window, [](GLFWwindow* window, double xpos, double ypos) { + RmlGLFW::ProcessCursorPosCallback(data->context, window, xpos, ypos, data->glfw_active_modifiers); + }); + + glfwSetMouseButtonCallback(window, [](GLFWwindow* /*window*/, int button, int action, int mods) { + data->glfw_active_modifiers = mods; + RmlGLFW::ProcessMouseButtonCallback(data->context, button, action, mods); + }); + + glfwSetScrollCallback(window, [](GLFWwindow* /*window*/, double /*xoffset*/, double yoffset) { + RmlGLFW::ProcessScrollCallback(data->context, yoffset, data->glfw_active_modifiers); + }); + + // Window events + glfwSetFramebufferSizeCallback(window, [](GLFWwindow* /*window*/, int width, int height) { + data->render_interface.SetViewport(width, height); + RmlGLFW::ProcessFramebufferSizeCallback(data->context, width, height); + }); + + glfwSetWindowContentScaleCallback(window, + [](GLFWwindow* /*window*/, float xscale, float /*yscale*/) { RmlGLFW::ProcessContentScaleCallback(data->context, xscale); }); +} + +static bool CreateDeviceD3D(HWND hwnd) +{ + // Setup swap chain + DXGI_SWAP_CHAIN_DESC sd; + ZeroMemory(&sd, sizeof(sd)); + sd.BufferCount = 2; + sd.BufferDesc.Width = 0; + sd.BufferDesc.Height = 0; + sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + sd.BufferDesc.RefreshRate.Numerator = 60; + sd.BufferDesc.RefreshRate.Denominator = 1; + sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.OutputWindow = hwnd; + sd.SampleDesc.Count = 1; + sd.SampleDesc.Quality = 0; + sd.Windowed = TRUE; + sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + + UINT createDeviceFlags = 0; +#ifdef RMLUI_DEBUG + // Enable debug layer + createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; +#endif + D3D_FEATURE_LEVEL featureLevel; + const D3D_FEATURE_LEVEL featureLevelArray[2] = { + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_0, + }; + HRESULT res = + D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, + &data->d3d_resources.pSwapChain, &data->d3d_resources.pd3dDevice, &featureLevel, &data->d3d_resources.pd3dDeviceContext); + if (res == DXGI_ERROR_UNSUPPORTED) // Try high-performance WARP software driver if hardware is not available. + res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, + &data->d3d_resources.pSwapChain, &data->d3d_resources.pd3dDevice, &featureLevel, &data->d3d_resources.pd3dDeviceContext); + if (res != S_OK) + return false; + + CreateRenderTarget(); + return true; +} + +static void CleanupDeviceD3D() +{ + CleanupRenderTarget(); + if (data->d3d_resources.pSwapChain) + { + data->d3d_resources.pSwapChain->Release(); + data->d3d_resources.pSwapChain = nullptr; + } + if (data->d3d_resources.pd3dDeviceContext) + { + data->d3d_resources.pd3dDeviceContext->Release(); + data->d3d_resources.pd3dDeviceContext = nullptr; + } + if (data->d3d_resources.pd3dDevice) + { + data->d3d_resources.pd3dDevice->Release(); + data->d3d_resources.pd3dDevice = nullptr; + } +} + +static void CreateRenderTarget() +{ + ID3D11Texture2D* pBackBuffer = nullptr; + data->d3d_resources.pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); + data->d3d_resources.pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &data->d3d_resources.pMainRenderTargetView); + pBackBuffer->Release(); +} + +static void CleanupRenderTarget() +{ + if (data->d3d_resources.pMainRenderTargetView) + { + data->d3d_resources.pMainRenderTargetView->Release(); + data->d3d_resources.pMainRenderTargetView = nullptr; + } +} \ No newline at end of file diff --git a/Backends/RmlUi_Backend_Win32_DX11.cpp b/Backends/RmlUi_Backend_Win32_DX11.cpp new file mode 100644 index 000000000..b46f68dea --- /dev/null +++ b/Backends/RmlUi_Backend_Win32_DX11.cpp @@ -0,0 +1,576 @@ +/* +* 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_Backend.h" +#include "RmlUi_Include_Windows.h" +#include "RmlUi_Platform_Win32.h" +#include "RmlUi_Renderer_DX11.h" +#include +#include +#include +#include +#include + +#ifdef RMLUI_USE_STB_IMAGE_LOADER + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + #include +#endif + +/** + Custom render interface example for the DX11/Win32 backend. + + Overloads the DX11 render interface to load textures through stb_image. + */ +class RenderInterface_DX11_Win32 : public RenderInterface_DX11 { +public: + RenderInterface_DX11_Win32() {} + +#ifdef RMLUI_USE_STB_IMAGE_LOADER + Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override + { + int texture_width = 0, texture_height = 0, num_channels = 0; + size_t image_size_bytes = 0; + uint8_t* texture_data = nullptr; + + // Find where on disk the file is + Rml::FileInterface* file_interface = Rml::GetFileInterface(); + Rml::FileHandle file_handle = file_interface->Open(source); + if (!file_handle) + { + // Tell RmlUI that the image is invalid + texture_data = nullptr; + return false; + } + + // Load the file through stb_image + texture_data = stbi_load_from_file((FILE*)file_handle, &texture_width, &texture_height, &num_channels, 0); + + // If the file data is correct + if (texture_data != nullptr) + { + // Compute number of elements in texture + image_size_bytes = texture_width * texture_height * num_channels; + + // Pre-multiply the data + for (int i = 0; i < image_size_bytes; i += 4) + { + texture_data[i + 0] = (uint8_t)((texture_data[i + 0] * texture_data[i + 3]) / 255); + texture_data[i + 1] = (uint8_t)((texture_data[i + 1] * texture_data[i + 3]) / 255); + texture_data[i + 2] = (uint8_t)((texture_data[i + 2] * texture_data[i + 3]) / 255); + } + + texture_dimensions.x = texture_width; + texture_dimensions.y = texture_height; + + Rml::TextureHandle handle = GenerateTexture({texture_data, image_size_bytes}, texture_dimensions); + + stbi_image_free(texture_data); + return handle; + } + return false; + } +#endif +}; + +/** +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); +} + +// 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); + +// D3D Creation / Cleanup functions +static bool CreateDeviceD3D(HWND hwnd); +static void CleanupDeviceD3D(); +static void CreateRenderTarget(); +static void CleanupRenderTarget(); + +/** +Global data used by this backend. + +Lifetime governed by the calls to Backend::Initialize() and Backend::Shutdown(). +*/ +struct BackendData { + SystemInterface_Win32 system_interface; + RenderInterface_DX11_Win32 render_interface; + TextInputMethodEditor_Win32 text_input_method_editor; + + HINSTANCE instance_handle = nullptr; + std::wstring instance_name; + HWND window_handle = nullptr; + + // D3D11 resources + struct { + ID3D11Device* pd3dDevice = nullptr; + ID3D11DeviceContext* pd3dDeviceContext = nullptr; + IDXGISwapChain* pSwapChain = nullptr; + bool swapchain_occluded = false; + ID3D11RenderTargetView* pMainRenderTargetView = nullptr; + } d3d_resources; + + bool context_dimensions_dirty = true; + Rml::Vector2i window_dimensions; + Rml::Vector2i resize_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; + +bool Backend::Initialize(const char* window_name, int width, int height, bool allow_resize) +{ + 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.data(); + + 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; + + if (!CreateDeviceD3D(window_handle)) { + ::CloseWindow(window_handle); + return false; + } + +#ifdef RMLUI_USE_STB_IMAGE_LOADER + // Disable pre-multiplication because loading images through stb_image will inconsistently return pre-multiplied images (only iPhone PNGs are pre-multipled) + // We handle pre-multiplication manually on decode + stbi_set_unpremultiply_on_load(true); + // iPhone PNGs are encoded as BGRA, this tells stb_image to unpack as RGBA + stbi_convert_iphone_png_to_rgb(true); +#endif + + data->render_interface.Init(data->d3d_resources.pd3dDevice); + + data->window_handle = window_handle; + data->system_interface.SetWindow(window_handle); + + // Now we are ready to show the window. + ::ShowWindow(window_handle, SW_SHOW); + ::SetForegroundWindow(window_handle); + ::SetFocus(window_handle); + + // Provide a backend-specific text input handler to manage the IME. + Rml::SetTextInputHandler(&data->text_input_method_editor); + + return true; +} + +void Backend::Shutdown() +{ + RMLUI_ASSERT(data); + + // As we forcefully override the global text input handler, we must reset it before the data is destroyed to avoid any potential use-after-free. + if (Rml::GetTextInputHandler() == &data->text_input_method_editor) + Rml::SetTextInputHandler(nullptr); + + // Cleanup renderer resources + data->render_interface.Cleanup(); + + // Shutdown DirectX11 + CleanupDeviceD3D(); + + ::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, 0, 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; + bool has_message = NextEvent(message, power_save ? static_cast(Rml::Math::Min(context->GetNextUpdateDelay(), 10.0) * 1000.0) : 0); + while (has_message) + { + // Dispatch the message to our local event handler below. + TranslateMessage(&message); + DispatchMessage(&message); + + has_message = NextEvent(message, 0); + } + + data->context = nullptr; + data->key_down_callback = nullptr; + + const bool result = data->running; + data->running = true; + return result; +} + +void Backend::RequestExit() +{ + RMLUI_ASSERT(data); + data->running = false; +} + +void Backend::BeginFrame() +{ + RMLUI_ASSERT(data); + + // Handle window resize (we don't resize directly in the WM_SIZE handler) + if (data->resize_dimensions.x != 0 && data->resize_dimensions.y != 0) + { + CleanupRenderTarget(); + data->d3d_resources.pSwapChain->ResizeBuffers(0, data->resize_dimensions.x, data->resize_dimensions.y, DXGI_FORMAT_UNKNOWN, 0); + data->resize_dimensions.x = data->resize_dimensions.y = 0; + CreateRenderTarget(); + } + + data->render_interface.BeginFrame(data->d3d_resources.pMainRenderTargetView); +} + +void Backend::PresentFrame() +{ + RMLUI_ASSERT(data); + data->render_interface.EndFrame(); + + // Present + HRESULT hr = data->d3d_resources.pSwapChain->Present(1, 0); // Present with vsync + //HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync + data->d3d_resources.swapchain_occluded = (hr == DXGI_STATUS_OCCLUDED); + + // 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_NCACTIVATE: + case WM_NCPAINT: + // Handle Windows Aero Snap + return DefWindowProcW(window_handle, message, w_param, l_param); + 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; + data->resize_dimensions.x = width; + data->resize_dimensions.y = height; + data->context_dimensions_dirty = true; + data->render_interface.SetViewport(width, height); + if (data->context) + 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, data->text_input_method_editor, 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, data->text_input_method_editor, 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; + ZeroMemory(&window_class, sizeof(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)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to 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) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "Failed to 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 and center it on the screen. + Rml::Vector2i screen_size = {GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)}; + Rml::Vector2i window_size = {int(window_rect.right - window_rect.left), int(window_rect.bottom - window_rect.top)}; + Rml::Vector2i window_pos = Rml::Math::Max((screen_size - window_size) / 2, Rml::Vector2i(0)); + + SetWindowPos(window_handle, HWND_TOP, window_pos.x, window_pos.y, window_size.x, window_size.y, SWP_NOACTIVATE); + + return window_handle; +} + +static bool CreateDeviceD3D(HWND hwnd) { + // Setup swap chain + DXGI_SWAP_CHAIN_DESC sd; + ZeroMemory(&sd, sizeof(sd)); + sd.BufferCount = 2; + sd.BufferDesc.Width = 0; + sd.BufferDesc.Height = 0; + sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + sd.BufferDesc.RefreshRate.Numerator = 60; + sd.BufferDesc.RefreshRate.Denominator = 1; + sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.OutputWindow = hwnd; + sd.SampleDesc.Count = 1; + sd.SampleDesc.Quality = 0; + sd.Windowed = TRUE; + sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + + UINT createDeviceFlags = 0; +#ifdef RMLUI_DEBUG + // Enable debug layer + createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; +#endif + D3D_FEATURE_LEVEL featureLevel; + const D3D_FEATURE_LEVEL featureLevelArray[2] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0, }; + HRESULT res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, + &data->d3d_resources.pSwapChain, &data->d3d_resources.pd3dDevice, &featureLevel, &data->d3d_resources.pd3dDeviceContext); + if (res == DXGI_ERROR_UNSUPPORTED) // Try high-performance WARP software driver if hardware is not available. + res = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, createDeviceFlags, featureLevelArray, 2, D3D11_SDK_VERSION, &sd, + &data->d3d_resources.pSwapChain, &data->d3d_resources.pd3dDevice, &featureLevel, &data->d3d_resources.pd3dDeviceContext); + if (res != S_OK) + return false; + + CreateRenderTarget(); + return true; +} + +static void CleanupDeviceD3D() { + + CleanupRenderTarget(); + if (data->d3d_resources.pSwapChain) + { + data->d3d_resources.pSwapChain->Release(); + data->d3d_resources.pSwapChain = nullptr; + } + if (data->d3d_resources.pd3dDeviceContext) + { + data->d3d_resources.pd3dDeviceContext->Release(); + data->d3d_resources.pd3dDeviceContext = nullptr; + } + if (data->d3d_resources.pd3dDevice) + { + data->d3d_resources.pd3dDevice->Release(); + data->d3d_resources.pd3dDevice = nullptr; + } +} + +static void CreateRenderTarget() +{ + ID3D11Texture2D* pBackBuffer = nullptr; + data->d3d_resources.pSwapChain->GetBuffer(0, IID_PPV_ARGS(&pBackBuffer)); + data->d3d_resources.pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &data->d3d_resources.pMainRenderTargetView); + pBackBuffer->Release(); +} + +static void CleanupRenderTarget() +{ + if (data->d3d_resources.pMainRenderTargetView) + { + data->d3d_resources.pMainRenderTargetView->Release(); + data->d3d_resources.pMainRenderTargetView = nullptr; + } +} \ No newline at end of file diff --git a/Backends/RmlUi_Renderer_DX11.cpp b/Backends/RmlUi_Renderer_DX11.cpp new file mode 100644 index 000000000..12b740f2e --- /dev/null +++ b/Backends/RmlUi_Renderer_DX11.cpp @@ -0,0 +1,2968 @@ +/* + * 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 + +#if defined RMLUI_PLATFORM_WIN32 + + #include "RmlUi_Renderer_DX11.h" + #include + #include + #include + #include + #include + #include + #include + + #include "RmlUi_Include_Windows.h" + + #include "d3dcompiler.h" + + #ifdef RMLUI_DX_DEBUG + #include + #endif + +// Shader source code + +#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) + +#define RMLUI_SHADER_HEADER \ + "#define MAX_NUM_STOPS " RMLUI_STRINGIFY(MAX_NUM_STOPS) "\n#line " RMLUI_STRINGIFY(__LINE__) "\n" + +constexpr const char shader_vert_main[] = RMLUI_SHADER_HEADER R"( +struct VS_Input +{ + float2 position : POSITION; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +struct PS_INPUT +{ + float4 position : SV_Position; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +cbuffer SharedConstantBuffer : register(b0) +{ + float4x4 m_transform; + float2 m_translate; +}; + +PS_INPUT VSMain(const VS_Input IN) +{ + PS_INPUT result = (PS_INPUT)0; + + float2 translatedPos = IN.position + m_translate; + float4 resPos = mul(m_transform, float4(translatedPos.x, translatedPos.y, 0.0, 1.0)); + + result.position = resPos; + result.color = IN.color; + result.uv = IN.uv; + + return result; +}; +)"; + +constexpr const char shader_frag_texture[] = RMLUI_SHADER_HEADER R"( +struct PS_Input +{ + float4 position : SV_Position; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +Texture2D g_InputTexture : register(t0); +SamplerState g_SamplerLinear : register(s0); + +float4 PSMain(const PS_Input IN) : SV_TARGET +{ + return IN.color * g_InputTexture.Sample(g_SamplerLinear, IN.uv); +}; +)"; + +constexpr const char shader_frag_color[] = RMLUI_SHADER_HEADER R"( +struct PS_Input +{ + float4 position : SV_Position; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +float4 PSMain(const PS_Input IN) : SV_TARGET +{ + return IN.color; +}; +)"; + +enum class ShaderGradientFunction { Linear, Radial, Conic, RepeatingLinear, RepeatingRadial, RepeatingConic }; // Must match shader definitions below. + +// We need to round up at compile-time so that we can embed the +#define CEILING(x, y) (((x) + (y)-1) / (y)) +static const char shader_frag_gradient[] = RMLUI_SHADER_HEADER "#define MAX_NUM_STOPS_PACKED (uint)" RMLUI_STRINGIFY(CEILING(MAX_NUM_STOPS, 4)) R"( +#define LINEAR 0 +#define RADIAL 1 +#define CONIC 2 +#define REPEATING_LINEAR 3 +#define REPEATING_RADIAL 4 +#define REPEATING_CONIC 5 +#define PI 3.14159265 + +cbuffer SharedConstantBuffer : register(b0) +{ + float4x4 m_transform; + float2 m_translate; + + // One to one translation of the OpenGL uniforms results in a LOT of wasted space due to CBuffer alignment rules. + // Changes from GL3: + // - Moved m_num_stops below m_func (saved 4 bytes of padding). + // - Packed m_stop_positions into a float4[MAX_NUM_STOPS / 4] array, as each array element starts a new 16-byte row. + // The below layout has 0 bytes of padding. + + int m_func; // one of the above definitions + int m_num_stops; + float2 m_p; // linear: starting point, radial: center, conic: center + float2 m_v; // linear: vector to ending point, radial: 2d curvature (inverse radius), conic: angled unit vector + float4 m_stop_colors[MAX_NUM_STOPS]; + float4 m_stop_positions[MAX_NUM_STOPS_PACKED]; // normalized, 0 -> starting point, 1 -> ending point +}; + +// Hide the way the data is packed in the cbuffer through a macro +// @NOTE: Hardcoded for MAX_NUM_STOPS 16. +// i >> 2 => i >> sqrt(MAX_NUM_STOPS) +#define GET_STOP_POS(i) (m_stop_positions[i >> 2][i & 3]) + +struct PS_Input +{ + float4 position : SV_Position; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +#define glsl_mod(x,y) (((x)-(y)*floor((x)/(y)))) + +float4 lerp_stop_colors(float t) { + float4 color = m_stop_colors[0]; + + for (int i = 1; i < m_num_stops; i++) + color = lerp(color, m_stop_colors[i], smoothstep(GET_STOP_POS(i-1), GET_STOP_POS(i), t)); + + return color; +}; + +float4 PSMain(const PS_Input IN) : SV_TARGET +{ + float t = 0.0; + + if (m_func == LINEAR || m_func == REPEATING_LINEAR) { + float dist_square = dot(m_v, m_v); + float2 V = IN.uv.xy - m_p; + t = dot(m_v, V) / dist_square; + } + else if (m_func == RADIAL || m_func == REPEATING_RADIAL) { + float2 V = IN.uv.xy - m_p; + t = length(m_v * V); + } + else if (m_func == CONIC || m_func == REPEATING_CONIC) { + float2x2 R = float2x2(m_v.x, -m_v.y, m_v.y, m_v.x); + float2 V = mul(R, (IN.uv.xy - m_p)); + t = 0.5 + atan2(V.y, -V.x) / (2.0 * PI); + } + + if (m_func == REPEATING_LINEAR || m_func == REPEATING_RADIAL || m_func == REPEATING_CONIC) { + float t0 = GET_STOP_POS(0); + float t1 = GET_STOP_POS(m_num_stops - 1); + t = t0 + glsl_mod(t - t0, t1 - t0); + } + + return IN.color * lerp_stop_colors(t); +}; +)"; + +// "Creation" by Danilo Guanabara, based on: https://www.shadertoy.com/view/XsXXDn +static const char shader_frag_creation[] = RMLUI_SHADER_HEADER R"( +struct PS_Input +{ + float4 position : SV_Position; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +cbuffer SharedConstantBuffer : register(b0) +{ + float4x4 m_transform; + float2 m_translate; + float2 m_dimensions; + float m_value; +}; + +#define glsl_mod(x,y) (((x)-(y)*floor((x)/(y)))) + +float4 PSMain(const PS_Input IN) : SV_TARGET +{ + float t = m_value; + float3 c; + float l; + for (int i = 0; i < 3; i++) { + float2 p = IN.uv; + float2 uv = p; + p -= .5; + p.x *= m_dimensions.x / m_dimensions.y; + float z = t + ((float)i) * .07; + l = length(p); + uv += p / l * (sin(z) + 1.) * abs(sin(l * 9. - z - z)); + c[i] = .01 / length(glsl_mod(uv, 1.) - .5); + } + return float4(c / l, IN.color.a); +}; +)"; + +constexpr const char shader_vert_passthrough[] = RMLUI_SHADER_HEADER R"( +struct VS_Input +{ + float2 position : POSITION; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +struct PS_Input +{ + float4 position : SV_Position; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +cbuffer ConstantBuffer : register(b0) +{ + float4x4 m_transform; + float2 m_translate; +}; + +PS_Input VSMain(const VS_Input IN) +{ + PS_Input result = (PS_Input)0; + + result.position = float4(IN.position.xy, 0.0f, 1.0f); + result.uv = float2(IN.uv.x, 1.0f - IN.uv.y); + + return result; +}; +)"; + +constexpr const char shader_frag_passthrough[] = RMLUI_SHADER_HEADER R"( +struct PS_Input +{ + float4 position : SV_Position; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +Texture2D g_InputTexture : register(t0); +SamplerState g_SamplerLinear : register(s0); + +float4 PSMain(const PS_Input IN) : SV_TARGET +{ + return g_InputTexture.Sample(g_SamplerLinear, IN.uv); +}; +)"; + +static const char shader_frag_color_matrix[] = RMLUI_SHADER_HEADER R"( + +Texture2D g_InputTexture : register(t0); +SamplerState g_SamplerLinear : register(s0); + +cbuffer ConstantBuffer : register(b1) +{ + float4x4 m_color_matrix; +}; + +struct PS_Input +{ + float4 position : SV_Position; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +float4 PSMain(const PS_Input IN) : SV_TARGET +{ + // The general case uses a 4x5 color matrix for full rgba transformation, plus a constant term with the last column. + // However, we only consider the case of rgb transformations. Thus, we could in principle use a 3x4 matrix, but we + // keep the alpha row for simplicity. + // In the general case we should do the matrix transformation in non-premultiplied space. However, without alpha + // transformations, we can do it directly in premultiplied space to avoid the extra division and multiplication + // steps. In this space, the constant term needs to be multiplied by the alpha value, instead of unity. + float4 texColor = g_InputTexture.Sample(g_SamplerLinear, IN.uv); + float3 transformedColor = mul(m_color_matrix, texColor).rgb; + return float4(transformedColor, texColor.a); +}; +)"; + +static const char shader_frag_blend_mask[] = RMLUI_SHADER_HEADER R"( +Texture2D g_InputTexture : register(t0); +Texture2D g_MaskTexture : register(t1); +SamplerState g_SamplerLinear : register(s0); + +struct PS_Input +{ + float4 position : SV_Position; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +float4 PSMain(const PS_Input IN) : SV_TARGET +{ + float4 texColor = g_InputTexture.Sample(g_SamplerLinear, IN.uv); + float maskAlpha = g_MaskTexture.Sample(g_SamplerLinear, IN.uv).a; + return texColor * maskAlpha; +}; +)"; + +#define RMLUI_SHADER_BLUR_HEADER \ + RMLUI_SHADER_HEADER "\n#define BLUR_SIZE " RMLUI_STRINGIFY(BLUR_SIZE) "\n#define BLUR_NUM_WEIGHTS " RMLUI_STRINGIFY(BLUR_NUM_WEIGHTS) + +static const char shader_vert_blur[] = RMLUI_SHADER_BLUR_HEADER R"( +struct VS_Input +{ + float2 position : POSITION; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +struct PS_INPUT +{ + float4 position : SV_Position; + float2 uv[BLUR_SIZE] : TEXCOORD; +}; + +cbuffer SharedConstantBuffer : register(b0) +{ + float4x4 m_transform; + float2 m_translate; + float2 m_texelOffset; + float4 m_weights; + float2 m_texCoordMin; + float2 m_texCoordMax; +}; + +PS_INPUT VSMain(const VS_Input IN) +{ + PS_INPUT result = (PS_INPUT)0; + + for (int i = 0; i < BLUR_SIZE; i++) { + result.uv[i] = IN.uv - float(i - BLUR_NUM_WEIGHTS + 1) * m_texelOffset; + } + result.position = float4(IN.position.xy, 1.0, 1.0); + + return result; +}; +)"; + +static const char shader_frag_blur[] = RMLUI_SHADER_BLUR_HEADER R"( +Texture2D g_InputTexture : register(t0); +SamplerState g_SamplerLinear : register(s0); + +cbuffer SharedConstantBuffer : register(b0) +{ + float4x4 m_transform; + float2 m_translate; + float2 m_texelOffset; + float4 m_weights; + float2 m_texCoordMin; + float2 m_texCoordMax; +}; + +struct PS_Input +{ + float4 position : SV_Position; + float2 uv[BLUR_SIZE] : TEXCOORD; +}; + +float4 PSMain(const PS_Input IN) : SV_TARGET +{ + float4 color = float4(0.0, 0.0, 0.0, 0.0); + for(int i = 0; i < BLUR_SIZE; i++) + { + float2 in_region = step(m_texCoordMin, IN.uv[i]) * step(IN.uv[i], m_texCoordMax); + color += g_InputTexture.Sample(g_SamplerLinear, float2(IN.uv[i].x, 1.0f - IN.uv[i].y)) * in_region.x * in_region.y * m_weights[abs(i - BLUR_NUM_WEIGHTS + 1)]; + } + return color; +}; +)"; + +static const char shader_frag_drop_shadow[] = RMLUI_SHADER_HEADER R"( +Texture2D g_InputTexture : register(t0); +SamplerState g_SamplerLinear : register(s0); + +cbuffer DropShadowBuffer : register(b1) +{ + float2 m_texCoordMin; + float2 m_texCoordMax; + float4 m_color; +}; + +struct PS_Input +{ + float4 position : SV_Position; + float4 color : COLOR; + float2 uv : TEXCOORD; +}; + +float4 PSMain(const PS_Input IN) : SV_TARGET +{ + float2 in_region = step(m_texCoordMin, IN.uv) * step(IN.uv, m_texCoordMax); + return g_InputTexture.Sample(g_SamplerLinear, IN.uv).a * in_region.x * in_region.y * m_color; +}; +)"; + +enum class ProgramId { + None, + Color, + Texture, + Gradient, + Creation, + Passthrough, + ColorMatrix, + BlendMask, + Blur, + DropShadow, + Count, +}; +enum class VertShaderId { + Main, + Passthrough, + Blur, + Count, +}; +enum class FragShaderId { + Color, + Texture, + Gradient, + Creation, + Passthrough, + ColorMatrix, + BlendMask, + Blur, + DropShadow, + Count, +}; +enum class ShaderType { + Vertex, + Fragment, +}; + +namespace Gfx { + +struct VertShaderDefinition { + VertShaderId id; + const char* name_str; + const char* code_str; + const size_t code_size; +}; +struct FragShaderDefinition { + FragShaderId id; + const char* name_str; + const char* code_str; + const size_t code_size; +}; +struct ProgramDefinition { + ProgramId id; + const char* name_str; + VertShaderId vert_shader; + FragShaderId frag_shader; +}; + +// clang-format off +static const VertShaderDefinition vert_shader_definitions[] = { + {VertShaderId::Main, "main", shader_vert_main, sizeof(shader_vert_main)}, + {VertShaderId::Passthrough, "passthrough", shader_vert_passthrough, sizeof(shader_vert_passthrough)}, + {VertShaderId::Blur, "blur", shader_vert_blur, sizeof(shader_vert_blur)}, +}; +static const FragShaderDefinition frag_shader_definitions[] = { + {FragShaderId::Color, "color", shader_frag_color, sizeof(shader_frag_color)}, + {FragShaderId::Texture, "texture", shader_frag_texture, sizeof(shader_frag_texture)}, + {FragShaderId::Gradient, "gradient", shader_frag_gradient, sizeof(shader_frag_gradient)}, + {FragShaderId::Creation, "creation", shader_frag_creation, sizeof(shader_frag_creation)}, + {FragShaderId::Passthrough, "passthrough", shader_frag_passthrough, sizeof(shader_frag_passthrough)}, + {FragShaderId::ColorMatrix, "color_matrix", shader_frag_color_matrix, sizeof(shader_frag_color_matrix)}, + {FragShaderId::BlendMask, "blend_mask", shader_frag_blend_mask, sizeof(shader_frag_blend_mask)}, + {FragShaderId::Blur, "blur", shader_frag_blur, sizeof(shader_frag_blur)}, + {FragShaderId::DropShadow, "drop_shadow", shader_frag_drop_shadow, sizeof(shader_frag_drop_shadow)}, +}; +static const ProgramDefinition program_definitions[] = { + {ProgramId::Color, "color", VertShaderId::Main, FragShaderId::Color}, + {ProgramId::Texture, "texture", VertShaderId::Main, FragShaderId::Texture}, + {ProgramId::Gradient, "gradient", VertShaderId::Main, FragShaderId::Gradient}, + {ProgramId::Creation, "creation", VertShaderId::Main, FragShaderId::Creation}, + {ProgramId::Passthrough, "passthrough", VertShaderId::Passthrough, FragShaderId::Passthrough}, + {ProgramId::ColorMatrix, "color_matrix", VertShaderId::Passthrough, FragShaderId::ColorMatrix}, + {ProgramId::BlendMask, "blend_mask", VertShaderId::Passthrough, FragShaderId::BlendMask}, + {ProgramId::Blur, "blur", VertShaderId::Blur, FragShaderId::Blur}, + {ProgramId::DropShadow, "drop_shadow", VertShaderId::Passthrough, FragShaderId::DropShadow}, +}; +// clang-format on + +template +class EnumArray { +public: + const T& operator[](Enum id) const + { + RMLUI_ASSERT((size_t)id < (size_t)Enum::Count); + return ids[size_t(id)]; + } + T& operator[](Enum id) + { + RMLUI_ASSERT((size_t)id < (size_t)Enum::Count); + return ids[size_t(id)]; + } + auto begin() const { return ids.begin(); } + auto end() const { return ids.end(); } + +private: + Rml::Array ids = {}; +}; + +struct ShaderProgram { +public: + ID3D11VertexShader* vertex_shader; + ID3D11PixelShader* pixel_shader; +}; + +using Programs = EnumArray; +using VertShaders = EnumArray; +using FragShaders = EnumArray; + +struct ProgramData { + Programs programs; + VertShaders vert_shaders; + FragShaders frag_shaders; +}; + +// Create the shader, 'shader_type' is either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. +static bool CreateShader(ID3DBlob*& out_shader_dxil, ShaderType shader_type, const char* code_string, const size_t code_length) +{ + RMLUI_ASSERT(shader_type == ShaderType::Vertex || shader_type == ShaderType::Fragment); + + #ifdef RMLUI_DX_DEBUG + static const UINT default_shader_flags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; + #else + static const UINT default_shader_flags = 0; + #endif + + // Compile shader + ID3DBlob* p_error_buff = nullptr; + + HRESULT result = D3DCompile(code_string, code_length, nullptr, nullptr, nullptr, shader_type == ShaderType::Vertex ? "VSMain" : "PSMain", + shader_type == ShaderType::Vertex ? "vs_5_0" : "ps_5_0", default_shader_flags, 0, &out_shader_dxil, &p_error_buff); + RMLUI_DX_ASSERTMSG(result, "failed to D3DCompile"); + #ifdef RMLUI_DX_DEBUG + if (FAILED(result)) + { + Rml::Log::Message(Rml::Log::Type::LT_ERROR, "failed to compile shader: %s", (char*)p_error_buff->GetBufferPointer()); + return false; + } + #endif + + DX_CLEANUP_RESOURCE_IF_CREATED(p_error_buff); + + return true; +} + +static bool CreateProgram(ID3D11Device* p_device, ShaderProgram& out_program, ID3DBlob* vertex_shader, ID3DBlob* fragment_shader) +{ + RMLUI_ASSERT(vertex_shader); + RMLUI_ASSERT(fragment_shader); + + HRESULT result = + p_device->CreateVertexShader(vertex_shader->GetBufferPointer(), vertex_shader->GetBufferSize(), nullptr, &out_program.vertex_shader); + RMLUI_DX_ASSERTMSG(result, "failed to CreateVertexShader"); + if (FAILED(result)) + { + #ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_ERROR, "failed to create vertex shader: %d", result); + #endif + return false; + } + + result = p_device->CreatePixelShader(fragment_shader->GetBufferPointer(), fragment_shader->GetBufferSize(), nullptr, &out_program.pixel_shader); + RMLUI_DX_ASSERTMSG(result, "failed to CreatePixelShader"); + if (FAILED(result)) + { + #ifdef RMLUI_DX_DEBUG + Rml::Log::Message(Rml::Log::Type::LT_ERROR, "failed to create pixel shader: %d", result); + #endif + return false; + } + + return true; +} + +struct RenderTargetData { + int width = 0, height = 0; + ID3D11RenderTargetView* render_target_view = nullptr; // To write to colour attachment buffer + ID3D11DepthStencilView* depth_stencil_view = nullptr; // To write to stencil buffer + ID3D11Texture2D* render_target_texture = nullptr; // For MSAA resolve + ID3D11ShaderResourceView* render_target_shader_resource_view = nullptr; // To blit + bool owns_depth_stencil_buffer = false; +}; + +enum class RenderTargetAttachment { None, Depth, DepthStencil }; + +static bool CreateRenderTarget(ID3D11Device* p_device, RenderTargetData& out_rt, int width, int height, int samples, + RenderTargetAttachment attachment, ID3D11DepthStencilView* shared_depth_stencil_buffer) +{ + // Generate render target + D3D11_TEXTURE2D_DESC texture_desc = {}; + ZeroMemory(&texture_desc, sizeof(D3D11_TEXTURE2D_DESC)); + texture_desc.Width = width; + texture_desc.Height = height; + texture_desc.MipLevels = 1; + texture_desc.ArraySize = 1; + texture_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + texture_desc.SampleDesc.Count = (samples > 1) ? samples : 1; // MSAA + texture_desc.SampleDesc.Quality = (samples > 1) ? D3D11_STANDARD_MULTISAMPLE_PATTERN : 0; + texture_desc.Usage = D3D11_USAGE_DEFAULT; + texture_desc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE; + texture_desc.CPUAccessFlags = 0; + texture_desc.MiscFlags = 0; + + ID3D11Texture2D* rt_texture = nullptr; + HRESULT result = p_device->CreateTexture2D(&texture_desc, nullptr, &rt_texture); + RMLUI_DX_ASSERTMSG(result, "failed to CreateTexture2D"); + if (FAILED(result)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "ID3D11Device::CreateTexture2D (%d)", result); + return false; + } + + D3D11_RENDER_TARGET_VIEW_DESC render_target_view_desc = {}; + render_target_view_desc.Format = texture_desc.Format; + render_target_view_desc.ViewDimension = samples > 0 ? D3D11_RTV_DIMENSION_TEXTURE2DMS : D3D11_RTV_DIMENSION_TEXTURE2D; + render_target_view_desc.Texture2D.MipSlice = 0; + + ID3D11RenderTargetView* render_target_view = nullptr; + result = p_device->CreateRenderTargetView(rt_texture, &render_target_view_desc, &render_target_view); + RMLUI_DX_ASSERTMSG(result, "failed to CreateRenderTargetView"); + if (FAILED(result)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "ID3D11Device::CreateRenderTargetView (%d)", result); + return false; + } + + D3D11_SHADER_RESOURCE_VIEW_DESC render_target_shader_resource_view_desc = {}; + render_target_shader_resource_view_desc.Format = texture_desc.Format; + render_target_shader_resource_view_desc.ViewDimension = samples > 0 ? D3D11_SRV_DIMENSION_TEXTURE2DMS : D3D11_SRV_DIMENSION_TEXTURE2D; + render_target_shader_resource_view_desc.Texture2D.MostDetailedMip = 0; + render_target_shader_resource_view_desc.Texture2D.MipLevels = 1; + + ID3D11ShaderResourceView* render_target_shader_resource_view = nullptr; + result = p_device->CreateShaderResourceView(rt_texture, &render_target_shader_resource_view_desc, &render_target_shader_resource_view); + RMLUI_DX_ASSERTMSG(result, "failed to CreateShaderResourceView"); + if (FAILED(result)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "ID3D11Device::CreateShaderResourceView (%d)", result); + return false; + } + + // Generate stencil buffer if necessary + ID3D11DepthStencilView* depth_stencil_view = nullptr; + + if (attachment != RenderTargetAttachment::None) + { + if (shared_depth_stencil_buffer) + { + // Share the depth/stencil buffer + depth_stencil_view = shared_depth_stencil_buffer; + depth_stencil_view->AddRef(); // Increase reference count since we're sharing it + } + else + { + // Create a new depth/stencil buffer + D3D11_TEXTURE2D_DESC depth_desc = {}; + depth_desc.Width = width; + depth_desc.Height = height; + depth_desc.MipLevels = 1; + depth_desc.ArraySize = 1; + depth_desc.Format = + (attachment == RenderTargetAttachment::DepthStencil) ? DXGI_FORMAT_D32_FLOAT_S8X24_UINT : DXGI_FORMAT_D24_UNORM_S8_UINT; + depth_desc.SampleDesc = texture_desc.SampleDesc; + depth_desc.Usage = D3D11_USAGE_DEFAULT; + depth_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL; + depth_desc.CPUAccessFlags = 0; + depth_desc.MiscFlags = 0; + + ID3D11Texture2D* depth_stencil_texture = nullptr; + result = p_device->CreateTexture2D(&depth_desc, nullptr, &depth_stencil_texture); + RMLUI_DX_ASSERTMSG(result, "failed to CreateTexture2D"); + if (FAILED(result)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "ID3D11Device::CreateTexture2D (%d)", result); + return false; + } + + D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc = {}; + dsvDesc.Format = depth_desc.Format; + dsvDesc.ViewDimension = (samples > 0) ? D3D11_DSV_DIMENSION_TEXTURE2DMS : D3D11_DSV_DIMENSION_TEXTURE2D; + dsvDesc.Texture2D.MipSlice = 0; + + result = p_device->CreateDepthStencilView(depth_stencil_texture, &dsvDesc, &depth_stencil_view); + RMLUI_DX_ASSERTMSG(result, "failed to CreateDepthStencilView"); + if (FAILED(result)) + { + depth_stencil_texture->Release(); + Rml::Log::Message(Rml::Log::LT_ERROR, "ID3D11Device::CreateDepthStencilView (%d)", result); + return false; + } + + depth_stencil_texture->Release(); + } + } + + out_rt = {}; + out_rt.width = width; + out_rt.height = height; + out_rt.render_target_view = render_target_view; + out_rt.render_target_texture = rt_texture; + out_rt.render_target_shader_resource_view = render_target_shader_resource_view; + out_rt.depth_stencil_view = depth_stencil_view; + out_rt.owns_depth_stencil_buffer = shared_depth_stencil_buffer != nullptr; + + return true; +} + +static void DestroyRenderTarget(RenderTargetData& rt) +{ + DX_CLEANUP_RESOURCE_IF_CREATED(rt.render_target_view); + DX_CLEANUP_RESOURCE_IF_CREATED(rt.render_target_texture); + DX_CLEANUP_RESOURCE_IF_CREATED(rt.render_target_shader_resource_view); + DX_CLEANUP_RESOURCE_IF_CREATED(rt.depth_stencil_view); + rt = {}; +} + +static inline void BindTexture(ID3D11DeviceContext* context, const RenderTargetData& rt, uint32_t slot = 0) +{ + context->PSSetShaderResources(slot, 1, &rt.render_target_shader_resource_view); +} + +static inline void BindRenderTarget(ID3D11DeviceContext* context, const RenderTargetData& rt) +{ + context->OMSetRenderTargets(1, &rt.render_target_view, rt.depth_stencil_view); +} + +static bool CreateShaders(ID3D11Device* p_device, ProgramData& data) +{ + RMLUI_ASSERT(std::all_of(data.vert_shaders.begin(), data.vert_shaders.end(), [](auto&& value) { return value == nullptr; })); + RMLUI_ASSERT(std::all_of(data.frag_shaders.begin(), data.frag_shaders.end(), [](auto&& value) { return value == nullptr; })); + RMLUI_ASSERT(std::all_of(data.programs.begin(), data.programs.end(), [](const ShaderProgram& value) { return value.vertex_shader == nullptr || value.pixel_shader == nullptr; })); + auto ReportError = [](const char* type, const char* name) { + Rml::Log::Message(Rml::Log::LT_ERROR, "Could not create shader %s: '%s'.", type, name); + return false; + }; + + for (const VertShaderDefinition& def : vert_shader_definitions) + { + if (!CreateShader(data.vert_shaders[def.id], ShaderType::Vertex, def.code_str, def.code_size)) + return ReportError("vertex shader", def.name_str); + } + + for (const FragShaderDefinition& def : frag_shader_definitions) + { + if (!CreateShader(data.frag_shaders[def.id], ShaderType::Fragment, def.code_str, def.code_size)) + return ReportError("fragment shader", def.name_str); + } + + for (const ProgramDefinition& def : program_definitions) + { + if (!CreateProgram(p_device, data.programs[def.id], data.vert_shaders[def.vert_shader], data.frag_shaders[def.frag_shader])) + return ReportError("program", def.name_str); + } + + return true; +} + +static void DestroyShaders(const ProgramData& data) +{ + for (ShaderProgram programs : data.programs) + { + DX_CLEANUP_RESOURCE_IF_CREATED(programs.vertex_shader); + DX_CLEANUP_RESOURCE_IF_CREATED(programs.pixel_shader); + } + + for (ID3DBlob* shader_blob : data.vert_shaders) + { + DX_CLEANUP_RESOURCE_IF_CREATED(shader_blob); + } + + for (ID3DBlob* shader_blob : data.frag_shaders) + { + DX_CLEANUP_RESOURCE_IF_CREATED(shader_blob); + } +} + +} // namespace Gfx + +RenderInterface_DX11::RenderInterface_DX11() {} +RenderInterface_DX11::~RenderInterface_DX11() {} + +void RenderInterface_DX11::Init(ID3D11Device* p_d3d_device) +{ + RMLUI_ASSERTMSG(p_d3d_device, "p_d3d_device cannot be nullptr!"); + + // Assign D3D resources + m_d3d_device = p_d3d_device; + m_d3d_device->AddRef(); + m_d3d_device->QueryInterface(__uuidof(ID3D11Device1), (void**)&m_d3d_device_1); + m_d3d_device_1->GetImmediateContext1(&m_d3d_context); + m_render_layers.SetD3DResources(m_d3d_device); + + // Pre-cache quad for blitting + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(-1), Rml::Vector2f(2), {}); + m_fullscreen_quad_geometry = RenderInterface_DX11::CompileGeometry(mesh.vertices, mesh.indices); + + if (!m_blend_state_enable) + { + // RmlUi serves vertex colors and textures with premultiplied alpha, set the blend mode accordingly. + // Equivalent to glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA). + D3D11_BLEND_DESC blend_desc{}; + ZeroMemory(&blend_desc, sizeof(blend_desc)); + blend_desc.AlphaToCoverageEnable = false; + blend_desc.IndependentBlendEnable = false; + blend_desc.RenderTarget[0].BlendEnable = true; + blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + blend_desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + blend_desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + blend_desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + blend_desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + HRESULT result = m_d3d_device->CreateBlendState(&blend_desc, &m_blend_state_enable); + RMLUI_DX_ASSERTMSG(result, "failed to CreateBlendState"); + #ifdef RMLUI_DX_DEBUG + if (FAILED(result)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "ID3D11Device::CreateBlendState (%d)", result); + return; + } + #endif + + ZeroMemory(&blend_desc, sizeof(blend_desc)); + blend_desc.RenderTarget[0].RenderTargetWriteMask = 0; + result = m_d3d_device->CreateBlendState(&blend_desc, &m_blend_state_disable_color); + RMLUI_DX_ASSERTMSG(result, "failed to CreateBlendState"); + #ifdef RMLUI_DX_DEBUG + if (FAILED(result)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "ID3D11Device::CreateBlendState (%d)", result); + return; + } + #endif + + blend_desc.RenderTarget[0].BlendEnable = true; + blend_desc.RenderTarget[0].SrcBlend = D3D11_BLEND_BLEND_FACTOR; + blend_desc.RenderTarget[0].DestBlend = D3D11_BLEND_ZERO; + blend_desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blend_desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_BLEND_FACTOR; + blend_desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; + blend_desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + blend_desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + result = m_d3d_device->CreateBlendState(&blend_desc, &m_blend_state_color_filter); + RMLUI_DX_ASSERTMSG(result, "failed to CreateBlendState"); + #ifdef RMLUI_DX_DEBUG + if (FAILED(result)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "ID3D11Device::CreateBlendState (%d)", result); + return; + } + #endif + } + + // Scissor regions require a rasterizer state. Cache one for scissor on and off + { + D3D11_RASTERIZER_DESC rasterizerDesc{}; + rasterizerDesc.FillMode = D3D11_FILL_SOLID; + rasterizerDesc.CullMode = D3D11_CULL_NONE; + rasterizerDesc.FrontCounterClockwise = false; + rasterizerDesc.DepthBias = D3D11_DEFAULT_DEPTH_BIAS; + rasterizerDesc.SlopeScaledDepthBias = D3D11_DEFAULT_SLOPE_SCALED_DEPTH_BIAS; + rasterizerDesc.DepthBiasClamp = D3D11_DEFAULT_DEPTH_BIAS_CLAMP; + rasterizerDesc.DepthClipEnable = false; + rasterizerDesc.ScissorEnable = true; + rasterizerDesc.MultisampleEnable = NUM_MSAA_SAMPLES > 1; + rasterizerDesc.AntialiasedLineEnable = NUM_MSAA_SAMPLES > 1; + + HRESULT result = m_d3d_device->CreateRasterizerState(&rasterizerDesc, &m_rasterizer_state_scissor_enabled); + RMLUI_DX_ASSERTMSG(result, "failed to CreateRasterizerState"); + #ifdef RMLUI_DX_DEBUG + if (FAILED(result)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "ID3D11Device::CreateRasterizerState (scissor: enabled) (%d)", result); + return; + } + #endif + + rasterizerDesc.ScissorEnable = false; + + result = m_d3d_device->CreateRasterizerState(&rasterizerDesc, &m_rasterizer_state_scissor_disabled); + RMLUI_DX_ASSERTMSG(result, "failed to CreateRasterizerState"); + #ifdef RMLUI_DX_DEBUG + if (FAILED(result)) + { + Rml::Log::Message(Rml::Log::LT_ERROR, "ID3D11Device::CreateRasterizerState (scissor: disabled) (%d)", result); + return; + } + #endif + } + + // Compile and load shaders + auto mut_program_data = Rml::MakeUnique(); + if (Gfx::CreateShaders(m_d3d_device, *mut_program_data)) + { + program_data = std::move(mut_program_data); + } + + // Create vertex layout. This will be constant to avoid copying to an intermediate struct. + { + D3D11_INPUT_ELEMENT_DESC polygonLayout[] = { + {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}, + {"COLOR", 0, DXGI_FORMAT_R8G8B8A8_UNORM, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}, + {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0}, + }; + uint32_t numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]); + + HRESULT result = + m_d3d_device->CreateInputLayout(polygonLayout, numElements, program_data->vert_shaders[VertShaderId::Main]->GetBufferPointer(), + program_data->vert_shaders[VertShaderId::Main]->GetBufferSize(), &m_vertex_layout); + RMLUI_DX_ASSERTMSG(result, "failed to CreateInputLayout"); + if (FAILED(result)) + { + return; + } + } + + // Create constant buffers. This is so that we can bind uniforms such as translation and color to the shaders. + { + D3D11_BUFFER_DESC cbufferDesc{}; + cbufferDesc.Usage = D3D11_USAGE_DYNAMIC; + cbufferDesc.ByteWidth = sizeof(ShaderCbuffer); + cbufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + cbufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + cbufferDesc.MiscFlags = 0; + cbufferDesc.StructureByteStride = 0; + + HRESULT result = m_d3d_device->CreateBuffer(&cbufferDesc, nullptr, &m_shader_buffer); + RMLUI_DX_ASSERTMSG(result, "failed to CreateBuffer"); + if (FAILED(result)) + { + return; + } + + cbufferDesc.Usage = D3D11_USAGE_DYNAMIC; + cbufferDesc.ByteWidth = sizeof(ColorMatrixCbuffer); + cbufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + cbufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + cbufferDesc.MiscFlags = 0; + cbufferDesc.StructureByteStride = 0; + + result = m_d3d_device->CreateBuffer(&cbufferDesc, nullptr, &m_color_matrix_cbuffer); + RMLUI_DX_ASSERTMSG(result, "failed to CreateBuffer"); + if (FAILED(result)) + { + return; + } + + cbufferDesc.Usage = D3D11_USAGE_DYNAMIC; + cbufferDesc.ByteWidth = sizeof(ShaderCbuffer::Blur); + cbufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + cbufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + cbufferDesc.MiscFlags = 0; + cbufferDesc.StructureByteStride = 0; + + result = m_d3d_device->CreateBuffer(&cbufferDesc, nullptr, &m_blur_cbuffer); + RMLUI_DX_ASSERTMSG(result, "failed to CreateBuffer"); + if (FAILED(result)) + { + return; + } + + cbufferDesc.Usage = D3D11_USAGE_DYNAMIC; + cbufferDesc.ByteWidth = sizeof(ShaderCbuffer::DropShadow); + cbufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + cbufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + cbufferDesc.MiscFlags = 0; + cbufferDesc.StructureByteStride = 0; + + result = m_d3d_device->CreateBuffer(&cbufferDesc, nullptr, &m_drop_shadow_cbuffer); + RMLUI_DX_ASSERTMSG(result, "failed to CreateBuffer"); + if (FAILED(result)) + { + return; + } + } + + // Create sampler state for textures + { + D3D11_SAMPLER_DESC samplerDesc{}; + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; + samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP; + samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP; + samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP; + samplerDesc.MipLODBias = 0.0f; + samplerDesc.MaxAnisotropy = 1; + samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; + samplerDesc.BorderColor[0] = 0; + samplerDesc.BorderColor[1] = 0; + samplerDesc.BorderColor[2] = 0; + samplerDesc.BorderColor[3] = 0; + samplerDesc.MinLOD = 0; + samplerDesc.MaxLOD = D3D11_FLOAT32_MAX; + + HRESULT result = m_d3d_device->CreateSamplerState(&samplerDesc, &m_sampler_state); + RMLUI_DX_ASSERTMSG(result, "failed to CreateSamplerState"); + if (FAILED(result)) + { + return; + } + } + + // Create depth stencil states + { + D3D11_DEPTH_STENCIL_DESC desc; + ZeroMemory(&desc, sizeof(desc)); + desc.DepthEnable = false; + desc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK; + desc.StencilWriteMask = 0; + desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; + desc.BackFace = desc.FrontFace; + desc.StencilEnable = true; + // Disabled + m_d3d_device->CreateDepthStencilState(&desc, &m_depth_stencil_state_disable); + + desc.StencilEnable = true; + desc.StencilReadMask = D3D11_DEFAULT_STENCIL_READ_MASK; + desc.StencilWriteMask = D3D11_DEFAULT_STENCIL_WRITE_MASK; + desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE; + desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; + desc.BackFace = desc.FrontFace; + // Set and SetInverse + m_d3d_device->CreateDepthStencilState(&desc, &m_depth_stencil_state_stencil_set); + + desc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_INCR; + desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; + desc.BackFace = desc.FrontFace; + // Intersect + m_d3d_device->CreateDepthStencilState(&desc, &m_depth_stencil_state_stencil_intersect); + + desc.StencilWriteMask = 0; + desc.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL; + desc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + desc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + desc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP; + desc.BackFace = desc.FrontFace; + // Stencil test + m_d3d_device->CreateDepthStencilState(&desc, &m_depth_stencil_state_stencil_test); + } +} + +void RenderInterface_DX11::Cleanup() +{ + if (m_fullscreen_quad_geometry) + { + RenderInterface_DX11::ReleaseGeometry(m_fullscreen_quad_geometry); + m_fullscreen_quad_geometry = {}; + } + + if (program_data) + { + Gfx::DestroyShaders(*program_data); + program_data.reset(); + } + + // Cleans up all general resources + DX_CLEANUP_RESOURCE_IF_CREATED(m_sampler_state); + DX_CLEANUP_RESOURCE_IF_CREATED(m_blend_state_enable); + DX_CLEANUP_RESOURCE_IF_CREATED(m_blend_state_color_filter); + DX_CLEANUP_RESOURCE_IF_CREATED(m_blend_state_disable_color); + DX_CLEANUP_RESOURCE_IF_CREATED(m_depth_stencil_state_disable); + DX_CLEANUP_RESOURCE_IF_CREATED(m_depth_stencil_state_stencil_intersect); + DX_CLEANUP_RESOURCE_IF_CREATED(m_depth_stencil_state_stencil_set); + DX_CLEANUP_RESOURCE_IF_CREATED(m_depth_stencil_state_stencil_test); + DX_CLEANUP_RESOURCE_IF_CREATED(m_rasterizer_state_scissor_disabled); + DX_CLEANUP_RESOURCE_IF_CREATED(m_rasterizer_state_scissor_enabled); + DX_CLEANUP_RESOURCE_IF_CREATED(m_shader_buffer); + DX_CLEANUP_RESOURCE_IF_CREATED(m_color_matrix_cbuffer); + DX_CLEANUP_RESOURCE_IF_CREATED(m_blur_cbuffer); + DX_CLEANUP_RESOURCE_IF_CREATED(m_drop_shadow_cbuffer); + DX_CLEANUP_RESOURCE_IF_CREATED(m_vertex_layout); + DX_CLEANUP_RESOURCE_IF_CREATED(m_d3d_context); + DX_CLEANUP_RESOURCE_IF_CREATED(m_d3d_device_1); + DX_CLEANUP_RESOURCE_IF_CREATED(m_d3d_device); +} + +void RenderInterface_DX11::BeginFrame(ID3D11RenderTargetView* p_render_target_view) +{ + RMLUI_ASSERT(m_viewport_width >= 1 && m_viewport_height >= 1); + + RMLUI_ASSERTMSG(p_render_target_view, "p_render_target_view cannot be nullptr!"); + RMLUI_ASSERTMSG(m_d3d_context, "d3d_context cannot be nullptr!"); + RMLUI_ASSERTMSG(m_d3d_device, "d3d_device cannot be nullptr!"); + + // Backup DX11 state + { + m_previous_d3d_state.scissor_rects_count = m_previous_d3d_state.viewports_count = D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE; + m_d3d_context->RSGetScissorRects(&m_previous_d3d_state.scissor_rects_count, m_previous_d3d_state.scissor_rects); + m_d3d_context->RSGetViewports(&m_previous_d3d_state.viewports_count, m_previous_d3d_state.viewports); + m_d3d_context->RSGetState(&m_previous_d3d_state.rastizer_state); + m_d3d_context->OMGetBlendState(&m_previous_d3d_state.blend_state, m_previous_d3d_state.blend_factor, &m_previous_d3d_state.sample_mask); + m_d3d_context->OMGetDepthStencilState(&m_previous_d3d_state.depth_stencil_state, &m_previous_d3d_state.stencil_ref); + m_d3d_context->PSGetShaderResources(0, 1, &m_previous_d3d_state.pixel_shader_shader_resource); + m_d3d_context->PSGetSamplers(0, 1, &m_previous_d3d_state.pixel_shader_sampler); + m_previous_d3d_state.pixel_shader_instances_count = m_previous_d3d_state.vertex_shader_instances_count = + m_previous_d3d_state.geometry_shader_instances_count = 256; + m_d3d_context->PSGetShader(&m_previous_d3d_state.pixel_shader, m_previous_d3d_state.pixel_shader_instances, + &m_previous_d3d_state.pixel_shader_instances_count); + m_d3d_context->VSGetShader(&m_previous_d3d_state.vertex_shader, m_previous_d3d_state.vertex_shader_instances, + &m_previous_d3d_state.vertex_shader_instances_count); + m_d3d_context->VSGetConstantBuffers(0, 1, &m_previous_d3d_state.vertex_shader_constant_buffer); + m_d3d_context->GSGetShader(&m_previous_d3d_state.geometry_shader, m_previous_d3d_state.geometry_shader_instances, + &m_previous_d3d_state.geometry_shader_instances_count); + + m_d3d_context->IAGetPrimitiveTopology(&m_previous_d3d_state.primitive_topology); + m_d3d_context->IAGetIndexBuffer(&m_previous_d3d_state.index_buffer, &m_previous_d3d_state.index_buffer_format, + &m_previous_d3d_state.index_buffer_offset); + m_d3d_context->IAGetVertexBuffers(0, 1, &m_previous_d3d_state.vertex_buffer, &m_previous_d3d_state.vertex_buffer_stride, + &m_previous_d3d_state.vertex_buffer_offset); + m_d3d_context->IAGetInputLayout(&m_previous_d3d_state.input_layout); + } + + m_bound_render_target = p_render_target_view; + + // Initialise DX11 state for RmlUi + D3D11_VIEWPORT d3dviewport; + d3dviewport.TopLeftX = 0; + d3dviewport.TopLeftY = 0; + d3dviewport.Width = (float) m_viewport_width; + d3dviewport.Height = (float) m_viewport_height; + d3dviewport.MinDepth = 0.0f; + d3dviewport.MaxDepth = 1.0f; + m_d3d_context->RSSetViewports(1, &d3dviewport); + + SetBlendState(m_blend_state_enable); + m_d3d_context->RSSetState(m_rasterizer_state_scissor_disabled); // Disable scissor + m_stencil_ref_value = 0; // Reset stencil + m_d3d_context->OMSetDepthStencilState(m_depth_stencil_state_disable, 0); + Clear(); + + SetTransform(nullptr); + + m_render_layers.BeginFrame(m_viewport_width, m_viewport_height); + Gfx::BindRenderTarget(m_d3d_context, m_render_layers.GetTopLayer()); + float clearColor[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + m_d3d_context->ClearRenderTargetView(m_render_layers.GetTopLayer().render_target_view, clearColor); + + UseProgram(ProgramId::None); + m_scissor_state = Rml::Rectanglei::MakeInvalid(); +} + +void RenderInterface_DX11::EndFrame() +{ + RMLUI_ASSERTMSG(m_bound_render_target, "m_bound_render_target cannot be nullptr!"); + + const Gfx::RenderTargetData& rt_active = m_render_layers.GetTopLayer(); + const Gfx::RenderTargetData& rt_postprocess = m_render_layers.GetPostprocessPrimary(); + + // Resolve MSAA to the post-process framebuffer. + m_d3d_context->ResolveSubresource(rt_postprocess.render_target_texture, 0, rt_active.render_target_texture, 0, DXGI_FORMAT_R8G8B8A8_UNORM); + + // Draw to bound_render_target (usually the swapchain RTV) + m_d3d_context->OMSetRenderTargets(1, &m_bound_render_target, nullptr); + + // Assuming we have an opaque background, we can just write to it with the premultiplied alpha blend mode and we'll get the correct result. + // Instead, if we had a transparent destination that didn't use premultiplied alpha, we would need to perform a manual un-premultiplication step. + Gfx::BindTexture(m_d3d_context, rt_postprocess); + + UseProgram(ProgramId::Passthrough); + + DrawFullscreenQuad(); + + m_render_layers.EndFrame(); + + // Reset internal state + m_bound_render_target = nullptr; + m_current_blend_state = nullptr; + + // Restore modified DX state + { + m_d3d_context->RSSetScissorRects(m_previous_d3d_state.scissor_rects_count, m_previous_d3d_state.scissor_rects); + m_d3d_context->RSSetViewports(m_previous_d3d_state.viewports_count, m_previous_d3d_state.viewports); + m_d3d_context->RSSetState(m_previous_d3d_state.rastizer_state); + if (m_previous_d3d_state.rastizer_state) + { + m_previous_d3d_state.rastizer_state->Release(); + } + m_d3d_context->OMSetBlendState(m_previous_d3d_state.blend_state, m_previous_d3d_state.blend_factor, m_previous_d3d_state.sample_mask); + if (m_previous_d3d_state.blend_state) + { + m_previous_d3d_state.blend_state->Release(); + } + m_d3d_context->OMSetDepthStencilState(m_previous_d3d_state.depth_stencil_state, m_previous_d3d_state.stencil_ref); + if (m_previous_d3d_state.depth_stencil_state) + { + m_previous_d3d_state.depth_stencil_state->Release(); + } + m_d3d_context->PSSetShaderResources(0, 1, &m_previous_d3d_state.pixel_shader_shader_resource); + if (m_previous_d3d_state.pixel_shader_shader_resource) + { + m_previous_d3d_state.pixel_shader_shader_resource->Release(); + } + m_d3d_context->PSSetSamplers(0, 1, &m_previous_d3d_state.pixel_shader_sampler); + if (m_previous_d3d_state.pixel_shader_sampler) + { + m_previous_d3d_state.pixel_shader_sampler->Release(); + } + m_d3d_context->PSSetShader(m_previous_d3d_state.pixel_shader, m_previous_d3d_state.pixel_shader_instances, + m_previous_d3d_state.pixel_shader_instances_count); + if (m_previous_d3d_state.pixel_shader) + { + m_previous_d3d_state.pixel_shader->Release(); + } + for (UINT i = 0; i < m_previous_d3d_state.pixel_shader_instances_count; i++) + { + if (m_previous_d3d_state.pixel_shader_instances[i]) + { + m_previous_d3d_state.pixel_shader_instances[i]->Release(); + } + } + m_d3d_context->VSSetShader(m_previous_d3d_state.vertex_shader, m_previous_d3d_state.vertex_shader_instances, + m_previous_d3d_state.vertex_shader_instances_count); + if (m_previous_d3d_state.vertex_shader) + { + m_previous_d3d_state.vertex_shader->Release(); + } + m_d3d_context->VSSetConstantBuffers(0, 1, &m_previous_d3d_state.vertex_shader_constant_buffer); + if (m_previous_d3d_state.vertex_shader_constant_buffer) + { + m_previous_d3d_state.vertex_shader_constant_buffer->Release(); + } + m_d3d_context->GSSetShader(m_previous_d3d_state.geometry_shader, m_previous_d3d_state.geometry_shader_instances, + m_previous_d3d_state.geometry_shader_instances_count); + if (m_previous_d3d_state.geometry_shader) + { + m_previous_d3d_state.geometry_shader->Release(); + } + for (UINT i = 0; i < m_previous_d3d_state.vertex_shader_instances_count; i++) + { + if (m_previous_d3d_state.vertex_shader_instances[i]) + { + m_previous_d3d_state.vertex_shader_instances[i]->Release(); + } + } + m_d3d_context->IASetPrimitiveTopology(m_previous_d3d_state.primitive_topology); + m_d3d_context->IASetIndexBuffer(m_previous_d3d_state.index_buffer, m_previous_d3d_state.index_buffer_format, + m_previous_d3d_state.index_buffer_offset); + if (m_previous_d3d_state.index_buffer) + { + m_previous_d3d_state.index_buffer->Release(); + } + m_d3d_context->IASetVertexBuffers(0, 1, &m_previous_d3d_state.vertex_buffer, &m_previous_d3d_state.vertex_buffer_stride, + &m_previous_d3d_state.vertex_buffer_offset); + if (m_previous_d3d_state.vertex_buffer) + { + m_previous_d3d_state.vertex_buffer->Release(); + } + m_d3d_context->IASetInputLayout(m_previous_d3d_state.input_layout); + if (m_previous_d3d_state.input_layout) + { + m_previous_d3d_state.input_layout->Release(); + } + } +} + +Rml::CompiledGeometryHandle RenderInterface_DX11::CompileGeometry(Rml::Span vertices, Rml::Span indices) +{ + ID3D11Buffer* vertex_buffer = nullptr; + ID3D11Buffer* index_buffer = nullptr; + + // Bind vertex buffer + { + D3D11_BUFFER_DESC vertexBufferDesc{}; + vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; + vertexBufferDesc.ByteWidth = static_cast(sizeof(Rml::Vertex) * vertices.size()); + vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; + vertexBufferDesc.CPUAccessFlags = 0; + vertexBufferDesc.MiscFlags = 0; + vertexBufferDesc.StructureByteStride = 0; + + D3D11_SUBRESOURCE_DATA vertexData{}; + vertexData.pSysMem = vertices.data(); + vertexData.SysMemPitch = 0; + vertexData.SysMemSlicePitch = 0; + + HRESULT result = m_d3d_device->CreateBuffer(&vertexBufferDesc, &vertexData, &vertex_buffer); + RMLUI_DX_ASSERTMSG(result, "failed to CreateBuffer"); + if (FAILED(result)) + { + return Rml::CompiledGeometryHandle(0); + } + } + + // Bind index buffer + { + D3D11_BUFFER_DESC indexBufferDesc{}; + indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; + indexBufferDesc.ByteWidth = static_cast(sizeof(int) * indices.size()); + indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER; + indexBufferDesc.CPUAccessFlags = 0; + indexBufferDesc.MiscFlags = 0; + indexBufferDesc.StructureByteStride = 0; + + D3D11_SUBRESOURCE_DATA indexData{}; + indexData.pSysMem = indices.data(); + indexData.SysMemPitch = 0; + indexData.SysMemSlicePitch = 0; + + HRESULT result = m_d3d_device->CreateBuffer(&indexBufferDesc, &indexData, &index_buffer); + RMLUI_DX_ASSERTMSG(result, "failed to CreateBuffer"); + if (FAILED(result)) + { + DX_CLEANUP_RESOURCE_IF_CREATED(vertex_buffer); + return Rml::CompiledGeometryHandle(0); + } + } + + DX11_GeometryData* geometryData = new DX11_GeometryData; + + geometryData->vertex_buffer = vertex_buffer; + geometryData->index_buffer = index_buffer; + geometryData->index_count = indices.size(); + + return (Rml::CompiledGeometryHandle)geometryData; +} + +void RenderInterface_DX11::ReleaseGeometry(Rml::CompiledGeometryHandle handle) +{ + DX11_GeometryData* geometry = (DX11_GeometryData*)handle; + + DX_CLEANUP_RESOURCE_IF_CREATED(geometry->vertex_buffer); + DX_CLEANUP_RESOURCE_IF_CREATED(geometry->index_buffer); + + delete geometry; +} + +void RenderInterface_DX11::RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) +{ + DX11_GeometryData* geometry = (DX11_GeometryData*)handle; + + if (m_translation != translation) + { + m_translation = translation; + m_cbuffer_dirty = true; + } + + if (texture == TexturePostprocess || texture == TexturePostprocessNoBinding) + { + // Do nothing. + } + else if (texture) + { + // Texture available + UseProgram(ProgramId::Texture); + if (texture != TextureEnableWithoutBinding) + { + ID3D11ShaderResourceView* texture_view = (ID3D11ShaderResourceView*)texture; + m_d3d_context->PSSetShaderResources(0, 1, &texture_view); + } + m_d3d_context->PSSetSamplers(0, 1, &m_sampler_state); + } + else + { + // No texture, use color + UseProgram(ProgramId::Color); + } + + if (texture != TexturePostprocessNoBinding) + { + UpdateConstantBuffer(); + + m_d3d_context->VSSetConstantBuffers(0, 1, &m_shader_buffer); + m_d3d_context->PSSetConstantBuffers(0, 1, &m_shader_buffer); + } + + // Bind vertex and index buffers, issue draw call + uint32_t stride = sizeof(Rml::Vertex); + uint32_t offset = 0; + m_d3d_context->IASetInputLayout(m_vertex_layout); + m_d3d_context->IASetVertexBuffers(0, 1, &geometry->vertex_buffer, &stride, &offset); + m_d3d_context->IASetIndexBuffer(geometry->index_buffer, DXGI_FORMAT_R32_UINT, 0); + m_d3d_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + m_d3d_context->DrawIndexed(static_cast(geometry->index_count), 0, 0); + + // Unbind resources + ID3D11ShaderResourceView* const nullSRV = nullptr; + m_d3d_context->PSSetShaderResources(0, 1, &nullSRV); +} + +// 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() + + +Rml::TextureHandle RenderInterface_DX11::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_DX11::GenerateTexture(Rml::Span source, Rml::Vector2i source_dimensions) +{ + ID3D11Texture2D* gpu_texture = nullptr; + ID3D11ShaderResourceView* gpu_texture_view = nullptr; + + D3D11_TEXTURE2D_DESC textureDesc{}; + textureDesc.Width = source_dimensions.x; + textureDesc.Height = source_dimensions.y; + textureDesc.MipLevels = 0; + textureDesc.ArraySize = 1; + textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + textureDesc.SampleDesc.Count = 1; + textureDesc.SampleDesc.Quality = 0; + textureDesc.Usage = D3D11_USAGE_DEFAULT; + textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + textureDesc.CPUAccessFlags = 0; + textureDesc.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS; + + HRESULT result = m_d3d_device->CreateTexture2D(&textureDesc, nullptr, &gpu_texture); + RMLUI_DX_ASSERTMSG(result, "failed to CreateTexture2D"); + if (FAILED(result)) + { + return Rml::TextureHandle(0); + } + + // Set the row pitch of the raw image data + uint32_t rowPitch = (source_dimensions.x * 4) * sizeof(unsigned char); + + // Copy the raw image data into the texture + if (source.size() > 0) + { + m_d3d_context->UpdateSubresource(gpu_texture, 0, nullptr, source.data(), rowPitch, 0); + } + + // Setup the shader resource view description. + D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc{}; + srvDesc.Format = textureDesc.Format; + srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; + srvDesc.Texture2D.MostDetailedMip = 0; + srvDesc.Texture2D.MipLevels = static_cast(-1); + + // Create the shader resource view for the texture. + result = m_d3d_device->CreateShaderResourceView(gpu_texture, &srvDesc, &gpu_texture_view); + RMLUI_DX_ASSERTMSG(result, "failed to CreateShaderResourceView"); + if (FAILED(result)) + { + DX_CLEANUP_RESOURCE_IF_CREATED(gpu_texture); + return Rml::TextureHandle(0); + } + DX_CLEANUP_RESOURCE_IF_CREATED(gpu_texture); + + // Generate mipmaps for this texture. + if (source.size() > 0) + { + m_d3d_context->GenerateMips(gpu_texture_view); + } + + return Rml::TextureHandle(gpu_texture_view); +} + +void RenderInterface_DX11::ReleaseTexture(Rml::TextureHandle texture_handle) +{ + ID3D11ShaderResourceView* texture_view = (ID3D11ShaderResourceView*)texture_handle; + DX_CLEANUP_RESOURCE_IF_CREATED(texture_view); +} + +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; +} + +void RenderInterface_DX11::DrawFullscreenQuad() +{ + RenderGeometry(m_fullscreen_quad_geometry, {}, RenderInterface_DX11::TexturePostprocess); +} + +void RenderInterface_DX11::DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling) +{ + Rml::Mesh mesh; + Rml::MeshUtilities::GenerateQuad(mesh, Rml::Vector2f(-1), Rml::Vector2f(2), {}); + if (uv_offset != Rml::Vector2f() || uv_scaling != Rml::Vector2f(1.f)) + { + for (Rml::Vertex& vertex : mesh.vertices) + vertex.tex_coord = (vertex.tex_coord * uv_scaling) + uv_offset; + } + const Rml::CompiledGeometryHandle geometry = CompileGeometry(mesh.vertices, mesh.indices); + RenderGeometry(geometry, {}, RenderInterface_DX11::TexturePostprocess); + ReleaseGeometry(geometry); +} + +/// 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; +} + + +void RenderInterface_DX11::SetScissor(Rml::Rectanglei region, bool vertically_flip) +{ + if (region.Valid() != m_scissor_state.Valid()) + { + if (region.Valid()) + { + m_d3d_context->RSSetState(m_rasterizer_state_scissor_enabled); + m_scissor_enabled = true; + } + else + { + m_d3d_context->RSSetState(m_rasterizer_state_scissor_disabled); + m_scissor_enabled = false; + } + } + + if (region.Valid() && vertically_flip) + region = VerticallyFlipped(region, m_viewport_height); + + if (region.Valid() && region != m_scissor_state) + { + // Some render APIs don't like offscreen positions (WebGL in particular), so clamp them to the viewport. + const int x_min = Rml::Math::Clamp(region.Left(), 0, m_viewport_width); + const int y_min = Rml::Math::Clamp(region.Top(), 0, m_viewport_height); + const int x_max = Rml::Math::Clamp(region.Right(), 0, m_viewport_width); + const int y_max = Rml::Math::Clamp(region.Bottom(), 0, m_viewport_height); + + D3D11_RECT rect_scissor = {}; + rect_scissor.left = x_min; + rect_scissor.top = y_min; + rect_scissor.right = x_max; + rect_scissor.bottom = y_max; + + m_d3d_context->RSSetScissorRects(1, &rect_scissor); + } + + m_scissor_state = region; +} + +void RenderInterface_DX11::EnableScissorRegion(bool enable) +{ + // Assume enable is immediately followed by a SetScissorRegion() call, and ignore it here. + if (!enable) + SetScissor(Rml::Rectanglei::MakeInvalid(), false); +} + +void RenderInterface_DX11::SetScissorRegion(Rml::Rectanglei region) +{ + SetScissor(region); +} + +void RenderInterface_DX11::SetViewport(const int width, const int height) +{ + m_viewport_width = width; + m_viewport_height = height; + m_projection = Rml::Matrix4f::ProjectOrtho(0, (float)m_viewport_width, (float)m_viewport_height, 0, -10000, 10000); +} + +void RenderInterface_DX11::Clear() +{ + float clearColor[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + m_d3d_context->ClearRenderTargetView(m_bound_render_target, clearColor); +} + +void RenderInterface_DX11::SetBlendState(ID3D11BlendState* blend_state) +{ + if (!m_current_blend_state_enabled || blend_state != m_current_blend_state) + { + const float blend_factor[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + m_d3d_context->OMSetBlendState(blend_state, blend_factor, 0xFFFFFFFF); + m_current_blend_state = blend_state; + m_current_blend_state_enabled = true; + } +} + +void RenderInterface_DX11::DisableBlend() { + if (m_current_blend_state_enabled) + { + m_d3d_context->OMSetBlendState(nullptr, nullptr, 0xFFFFFFFF); + m_current_blend_state_enabled = false; + } +} + +void RenderInterface_DX11::SetTransform(const Rml::Matrix4f* new_transform) +{ + m_transform = (new_transform ? (m_projection * (*new_transform)) : m_projection); + m_cbuffer_dirty = true; +} + +void RenderInterface_DX11::BlitRenderTarget(const Gfx::RenderTargetData& source, const Gfx::RenderTargetData& dest, int srcX0, int srcY0, int srcX1, + int srcY1, int dstX0, int dstY0, int dstX1, int dstY1) +{ + int src_width = srcX1 - srcX0; + int src_height = srcY1 - srcY0; + int dest_width = dstX1 - dstX0; + int dest_height = dstY1 - dstY0; + + bool is_flipped = src_width < 0 || src_height < 0 || dest_width < 0 || dest_height < 0; + bool is_stretched = src_width != dest_width || src_height != dest_height; + bool is_full_copy = src_width == dest_width && src_height == dest_height && srcX0 == 0 && srcY0 == 0 && dstX0 == 0 && dstY0 == 0; + + if (is_flipped || is_stretched || !is_full_copy) + { + // Full draw call. Slow path because no equivalent in DX11 + + // Cache current state + ID3D11RenderTargetView* original_render_target_view = nullptr; + ID3D11DepthStencilView* original_depth_stencil_view = nullptr; + m_d3d_context->OMGetRenderTargets(1, &original_render_target_view, &original_depth_stencil_view); + + // Bind temporary as the render target + const Gfx::RenderTargetData& temporary_rt = m_render_layers.GetTemporary(); + + // Unbind existing textures to prevent warning spam + ID3D11ShaderResourceView* null_shader_resource_views[2] = {nullptr, nullptr}; + m_d3d_context->PSSetShaderResources(0, 2, null_shader_resource_views); + + // Disable blending + DisableBlend(); + + // Resolve from source to the temporary first + m_d3d_context->ResolveSubresource(temporary_rt.render_target_texture, 0, source.render_target_texture, 0, DXGI_FORMAT_R8G8B8A8_UNORM); + + // Bind destination as our final render texture + Gfx::BindRenderTarget(m_d3d_context, dest); + + D3D11_VIEWPORT d3dviewport{}; + d3dviewport.TopLeftX = 0; + d3dviewport.TopLeftY = 0; + d3dviewport.Width = (float) dest.width; + d3dviewport.Height = (float) dest.height; + d3dviewport.MinDepth = 0.0f; + d3dviewport.MaxDepth = 1.0f; + m_d3d_context->RSSetViewports(1, &d3dviewport); + + // Bind texture + m_d3d_context->PSSetSamplers(0, 1, &m_sampler_state); + Gfx::BindTexture(m_d3d_context, temporary_rt); + UseProgram(ProgramId::Passthrough); + + // Draw a quad into temporary with source bound as the texture, and the UVs lerping to match the new dimensions + // We want to map UV 0 - 1 to src min-max + // We want to map pos -1 - +1 to dst min max + + float uv_x_min = float(srcX0) / float(source.width); // Map to 0 + float uv_y_max = float(srcY0) / float(source.height); // Map to 0 + float uv_x_max = float(srcX1) / float(source.width); // Map to +1 + float uv_y_min = float(srcY1) / float(source.height); // Map to +1 + + float pos_x_min = (dstX0 / float(dest.width)) * 2.0f - 1.0f; + float pos_y_min = ((dest.height - dstY0 - dest_height) / float(dest.height)) * 2.0f - 1.0f; + float pos_x_max = ((dstX0 + dest_width) / float(dest.width)) * 2.0f - 1.0f; + float pos_y_max = ((dest.height - dstY0) / float(dest.height)) * 2.0f - 1.0f; + + Rml::Mesh mesh; + const int v0 = (int)mesh.vertices.size(); + const int i0 = (int)mesh.indices.size(); + + mesh.vertices.resize(mesh.vertices.size() + 4); + mesh.indices.resize(mesh.indices.size() + 6); + Rml::Vertex* vertices = mesh.vertices.data(); + int* indices = mesh.indices.data(); + + vertices[v0 + 0].position.x = pos_x_min; + vertices[v0 + 0].position.y = pos_y_min; + vertices[v0 + 0].tex_coord.x = uv_x_min; + vertices[v0 + 0].tex_coord.y = uv_y_min; + + vertices[v0 + 1].position.x = pos_x_max; + vertices[v0 + 1].position.y = pos_y_min; + vertices[v0 + 1].tex_coord.x = uv_x_max; + vertices[v0 + 1].tex_coord.y = uv_y_min; + + vertices[v0 + 2].position.x = pos_x_max; + vertices[v0 + 2].position.y = pos_y_max; + vertices[v0 + 2].tex_coord.x = uv_x_max; + vertices[v0 + 2].tex_coord.y = uv_y_max; + + vertices[v0 + 3].position.x = pos_x_min; + vertices[v0 + 3].position.y = pos_y_max; + vertices[v0 + 3].tex_coord.x = uv_x_min; + vertices[v0 + 3].tex_coord.y = uv_y_max; + + indices[i0 + 0] = v0 + 0; + indices[i0 + 1] = v0 + 3; + indices[i0 + 2] = v0 + 1; + + indices[i0 + 3] = v0 + 1; + indices[i0 + 4] = v0 + 3; + indices[i0 + 5] = v0 + 2; + + const Rml::CompiledGeometryHandle geometry = CompileGeometry(mesh.vertices, mesh.indices); + RenderGeometry(geometry, {}, RenderInterface_DX11::TexturePostprocess); + ReleaseGeometry(geometry); + + // Restore state + m_d3d_context->OMSetRenderTargets(1, &original_render_target_view, original_depth_stencil_view); + SetBlendState(m_current_blend_state); + + d3dviewport.Width = static_cast(m_viewport_width); + d3dviewport.Height = static_cast(m_viewport_height); + m_d3d_context->RSSetViewports(1, &d3dviewport); + m_d3d_context->PSSetShaderResources(0, 2, null_shader_resource_views); + DX_CLEANUP_RESOURCE_IF_CREATED(original_render_target_view); + DX_CLEANUP_RESOURCE_IF_CREATED(original_depth_stencil_view); + } + else + { + // Resolve and move on + m_d3d_context->ResolveSubresource(dest.render_target_texture, 0, source.render_target_texture, 0, DXGI_FORMAT_R8G8B8A8_UNORM); + } +} + +void RenderInterface_DX11::BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_handle) +{ + const Gfx::RenderTargetData& source = m_render_layers.GetLayer(layer_handle); + const Gfx::RenderTargetData& destination = m_render_layers.GetPostprocessPrimary(); + + // Blit and resolve MSAA. Any active scissor state will restrict the size of the blit region. + BlitRenderTarget(source, destination, 0, 0, source.width, source.height, 0, 0, destination.width, destination.height); +} + +Rml::LayerHandle RenderInterface_DX11::PushLayer() +{ + const Rml::LayerHandle layer_handle = m_render_layers.PushLayer(); + + Gfx::BindRenderTarget(m_d3d_context, m_render_layers.GetTopLayer()); + float colors[4] = {0, 0, 0, 0}; + m_d3d_context->ClearRenderTargetView(m_render_layers.GetTopLayer().render_target_view, colors); + + return layer_handle; +} + +void RenderInterface_DX11::CompositeLayers(Rml::LayerHandle source_handle, Rml::LayerHandle destination_handle, Rml::BlendMode blend_mode, + Rml::Span filters) +{ + using Rml::BlendMode; + + // Blit source layer to postprocessing buffer. Do this regardless of whether we actually have any filters to be + // applied, because we need to resolve the multi-sampled framebuffer in any case. + // @performance If we have BlendMode::Replace and no filters or mask then we can just blit directly to the destination. + BlitLayerToPostprocessPrimary(source_handle); + + // Render the filters, the PostprocessPrimary framebuffer is used for both input and output. + RenderFilters(filters); + + // Render to the destination layer. + Gfx::BindRenderTarget(m_d3d_context, m_render_layers.GetLayer(destination_handle)); + Gfx::BindTexture(m_d3d_context, m_render_layers.GetPostprocessPrimary()); + + UseProgram(ProgramId::Passthrough); + + if (blend_mode == BlendMode::Replace) + DisableBlend(); + + DrawFullscreenQuad(); + + if (blend_mode == BlendMode::Replace) + SetBlendState(m_current_blend_state); + + if (destination_handle != m_render_layers.GetTopLayerHandle()) + Gfx::BindRenderTarget(m_d3d_context, m_render_layers.GetTopLayer()); +} + +void RenderInterface_DX11::PopLayer() +{ + m_render_layers.PopLayer(); + Gfx::BindRenderTarget(m_d3d_context, m_render_layers.GetTopLayer()); +} + +Rml::TextureHandle RenderInterface_DX11::SaveLayerAsTexture() +{ + RMLUI_ASSERT(m_scissor_state.Valid()); + const Rml::Rectanglei bounds = m_scissor_state; + + Rml::TextureHandle render_texture = GenerateTexture({}, bounds.Size()); + if (!render_texture) + { + return {}; + } + + BlitLayerToPostprocessPrimary(m_render_layers.GetTopLayerHandle()); + + EnableScissorRegion(false); + + const Gfx::RenderTargetData& source = m_render_layers.GetPostprocessPrimary(); + const Gfx::RenderTargetData& destination = m_render_layers.GetPostprocessSecondary(); + + // Flip the image vertically, as that convention is used for textures, and move to origin. + BlitRenderTarget(source, destination, + bounds.Left(), source.height - bounds.Bottom(), // src0 + bounds.Right(), source.height - bounds.Top(), // src1 + 0, bounds.Height(), // dst0 + bounds.Width(), 0 // dst1 + ); + + // Now we need to copy the destination texture to the final texture for rendering + // Bind the destination texture as the source for copying to the final texture (render_texture) + // after extracting the associated resource with it + ID3D11ShaderResourceView* texture_view = (ID3D11ShaderResourceView*)render_texture; + ID3D11Resource* texture_resource = nullptr; + texture_view->GetResource(&texture_resource); + + // Copy the destination texture to the final texture resource + D3D11_BOX copy_box{}; + copy_box.left = bounds.Left(); + copy_box.right = bounds.Right(); + copy_box.top = bounds.Top(); + copy_box.bottom = bounds.Bottom(); + copy_box.front = 0; + copy_box.back = 1; + + // Copy the blitted content from the destination to the final texture + m_d3d_context->CopySubresourceRegion(texture_resource, 0, 0, 0, 0, destination.render_target_texture, 0, ©_box); + + // Restore state and free memory + SetScissor(bounds); + Gfx::BindRenderTarget(m_d3d_context, m_render_layers.GetTopLayer()); + texture_resource->Release(); + + return render_texture; +} + +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::ColourbPremultiplied color; + + // ColorMatrix + Rml::Matrix4f color_matrix; +}; + +Rml::CompiledFilterHandle RenderInterface_DX11::SaveLayerAsMaskImage() +{ + BlitLayerToPostprocessPrimary(m_render_layers.GetTopLayerHandle()); + + const Gfx::RenderTargetData& source = m_render_layers.GetPostprocessPrimary(); + const Gfx::RenderTargetData& destination = m_render_layers.GetBlendMask(); + + // Unbind resources + ID3D11ShaderResourceView* const nullSRV = nullptr; + m_d3d_context->PSSetShaderResources(0, 1, &nullSRV); + + Gfx::BindRenderTarget(m_d3d_context, destination); + Gfx::BindTexture(m_d3d_context, source); + UseProgram(ProgramId::Passthrough); + ID3D11BlendState* blend_state_backup = m_current_blend_state; + DisableBlend(); + + DrawFullscreenQuad(); + + // Unbind resources + m_d3d_context->PSSetShaderResources(0, 1, &nullSRV); + + SetBlendState(blend_state_backup); + Gfx::BindRenderTarget(m_d3d_context, m_render_layers.GetTopLayer()); + + CompiledFilter filter = {}; + filter.type = FilterType::MaskImage; + return reinterpret_cast(new CompiledFilter(std::move(filter))); +} + +Rml::CompiledFilterHandle RenderInterface_DX11::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 = Rml::Get(parameters, "sigma", 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()).ToPremultiplied(); + 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_DX11::ReleaseFilter(Rml::CompiledFilterHandle filter) +{ + delete reinterpret_cast(filter); +} + +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; +}; + +Rml::CompiledShaderHandle RenderInterface_DX11::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_DX11::RenderShader(Rml::CompiledShaderHandle shader_handle, Rml::CompiledGeometryHandle geometry_handle, + Rml::Vector2f translation, Rml::TextureHandle /*texture*/) +{ + RMLUI_ASSERT(shader_handle && geometry_handle); + const CompiledShader& shader = *reinterpret_cast(shader_handle); + const CompiledShaderType type = shader.type; + + DX11_GeometryData* geometry = (DX11_GeometryData*)geometry_handle; + + switch (type) + { + case CompiledShaderType::Gradient: + { + RMLUI_ASSERT(shader.stop_positions.size() == shader.stop_colors.size()); + const int num_stops = (int)shader.stop_positions.size(); + + UseProgram(ProgramId::Gradient); + + D3D11_MAPPED_SUBRESOURCE mappedResource{}; + // Lock the constant buffer so it can be written to. + HRESULT result = m_d3d_context->Map(m_shader_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); + if (FAILED(result)) + { + return; + } + + // Get a pointer to the data in the constant buffer. + ShaderCbuffer* dataPtr = (ShaderCbuffer*)mappedResource.pData; + + // Copy the data to the GPU + dataPtr->common.transform = m_transform; + dataPtr->common.translation = translation; + dataPtr->gradient.func = static_cast(shader.gradient_function); + dataPtr->gradient.p = shader.p; + dataPtr->gradient.v = shader.v; + dataPtr->gradient.num_stops = num_stops; + // Reset stop positions and colours to 0 + memset(dataPtr->gradient.stop_positions, 0, sizeof(dataPtr->gradient.stop_positions)); + memset(dataPtr->gradient.stop_colors, 0, sizeof(dataPtr->gradient.stop_colors)); + // Copy to stop position and colours + memcpy_s(&dataPtr->gradient.stop_positions, num_stops * sizeof(float), shader.stop_positions.data(), num_stops * sizeof(float)); + memcpy_s(&dataPtr->gradient.stop_colors, num_stops * sizeof(Rml::Vector4f), shader.stop_colors.data(), num_stops * sizeof(Rml::Vector4f)); + + // Upload to the GPU. + m_d3d_context->Unmap(m_shader_buffer, 0); + + // Issue draw call + m_d3d_context->IASetInputLayout(m_vertex_layout); + m_d3d_context->VSSetConstantBuffers(0, 1, &m_shader_buffer); + m_d3d_context->PSSetConstantBuffers(0, 1, &m_shader_buffer); + uint32_t stride = sizeof(Rml::Vertex); + uint32_t offset = 0; + m_d3d_context->IASetVertexBuffers(0, 1, &geometry->vertex_buffer, &stride, &offset); + m_d3d_context->IASetIndexBuffer(geometry->index_buffer, DXGI_FORMAT_R32_UINT, 0); + m_d3d_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + m_d3d_context->DrawIndexed(static_cast(geometry->index_count), 0, 0); + } + break; + case CompiledShaderType::Creation: + { + const double time = Rml::GetSystemInterface()->GetElapsedTime(); + + UseProgram(ProgramId::Creation); + + D3D11_MAPPED_SUBRESOURCE mappedResource{}; + // Lock the constant buffer so it can be written to. + HRESULT result = m_d3d_context->Map(m_shader_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); + if (FAILED(result)) + { + return; + } + + // Get a pointer to the data in the constant buffer. + ShaderCbuffer* dataPtr = (ShaderCbuffer*)mappedResource.pData; + + // Copy the data to the GPU + dataPtr->common.transform = m_transform; + dataPtr->common.translation = translation; + dataPtr->creation.value = (float)time; + dataPtr->creation.dimensions = shader.dimensions; + + // Upload to the GPU. + m_d3d_context->Unmap(m_shader_buffer, 0); + + // Issue draw call + m_d3d_context->IASetInputLayout(m_vertex_layout); + m_d3d_context->VSSetConstantBuffers(0, 1, &m_shader_buffer); + m_d3d_context->PSSetConstantBuffers(0, 1, &m_shader_buffer); + uint32_t stride = sizeof(Rml::Vertex); + uint32_t offset = 0; + m_d3d_context->IASetVertexBuffers(0, 1, &geometry->vertex_buffer, &stride, &offset); + m_d3d_context->IASetIndexBuffer(geometry->index_buffer, DXGI_FORMAT_R32_UINT, 0); + m_d3d_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + m_d3d_context->DrawIndexed(static_cast(geometry->index_count), 0, 0); + } + break; + case CompiledShaderType::Invalid: + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Unhandled render shader %d.", (int)type); + } + break; + } +} + +void RenderInterface_DX11::ReleaseShader(Rml::CompiledShaderHandle shader_handle) +{ + delete reinterpret_cast(shader_handle); +} + +static void SigmaToParameters(const float desired_sigma, int& out_pass_level, float& out_sigma) +{ + constexpr int max_num_passes = 10; + static_assert(max_num_passes < 31, ""); + constexpr float max_single_pass_sigma = 3.0f; + out_pass_level = Rml::Math::Clamp(Rml::Math::Log2(int(desired_sigma * (2.f / max_single_pass_sigma))), 0, max_num_passes); + out_sigma = Rml::Math::Clamp(desired_sigma / float(1 << out_pass_level), 0.0f, max_single_pass_sigma); +} + +static void SetTexCoordLimits(Rml::Vector2f& p_tex_coord_min, Rml::Vector2f& p_tex_coord_max, Rml::Rectanglei rectangle_flipped, + Rml::Vector2i framebuffer_size) +{ + // Offset by half-texel values so that texture lookups are clamped to fragment centers, thereby avoiding color + // bleeding from neighboring texels due to bilinear interpolation. + const Rml::Vector2f min = (Rml::Vector2f(rectangle_flipped.p0) + Rml::Vector2f(0.5f)) / Rml::Vector2f(framebuffer_size); + const Rml::Vector2f max = (Rml::Vector2f(rectangle_flipped.p1) - Rml::Vector2f(0.5f)) / Rml::Vector2f(framebuffer_size); + + p_tex_coord_min.x = min.x; + p_tex_coord_min.y = min.y; + p_tex_coord_max.x = max.x; + p_tex_coord_max.y = max.y; +} + +static void SetBlurWeights(Rml::Vector4f& p_weights, float sigma) +{ + constexpr int num_weights = BLUR_NUM_WEIGHTS; + float weights[num_weights]; + float normalization = 0.0f; + for (int i = 0; i < num_weights; i++) + { + if (Rml::Math::Absolute(sigma) < 0.1f) + weights[i] = float(i == 0); + else + weights[i] = Rml::Math::Exp(-float(i * i) / (2.0f * sigma * sigma)) / (Rml::Math::SquareRoot(2.f * Rml::Math::RMLUI_PI) * sigma); + + normalization += (i == 0 ? 1.f : 2.0f) * weights[i]; + } + for (int i = 0; i < num_weights; i++) + weights[i] /= normalization; + + p_weights.x = weights[0]; + p_weights.y = weights[1]; + p_weights.z = weights[2]; + p_weights.w = weights[3]; +} + +void RenderInterface_DX11::RenderBlur(float sigma, const Gfx::RenderTargetData& source_destination, const Gfx::RenderTargetData& temp, + const Rml::Rectanglei window_flipped) +{ + RMLUI_ASSERT(&source_destination != &temp && source_destination.width == temp.width && source_destination.height == temp.height); + RMLUI_ASSERT(window_flipped.Valid()); + + if (window_flipped.Top() == window_flipped.Bottom() || window_flipped.Left() == window_flipped.Right()) + { + // In some cases the bounds are 0 width, so early exit since there's nothing to blur + return; + } + + int pass_level = 0; + SigmaToParameters(sigma, pass_level, sigma); + if (sigma == 0) + { + // If sigma is zero we don't have anything to blur, so early exit since blurring will have no effect on the final image + return; + } + + const Rml::Rectanglei original_scissor = m_scissor_state; + + // Begin by downscaling so that the blur pass can be done at a reduced resolution for large sigma. + Rml::Rectanglei scissor = window_flipped; + + UseProgram(ProgramId::Passthrough); + SetScissor(scissor, true); + + // Downscale by iterative half-scaling with bilinear filtering, to reduce aliasing. + D3D11_VIEWPORT d3dviewport{}; + d3dviewport.TopLeftX = 0; + d3dviewport.TopLeftY = static_cast(source_destination.height / 2); + d3dviewport.Width = static_cast(source_destination.width / 2); + d3dviewport.Height = static_cast(source_destination.height / 2); + d3dviewport.MinDepth = 0.0f; + d3dviewport.MaxDepth = 1.0f; + m_d3d_context->RSSetViewports(1, &d3dviewport); + + // Scale UVs if we have even dimensions, such that texture fetches align perfectly between texels, thereby producing a 50% blend of + // neighboring texels. + const Rml::Vector2f uv_scaling = {(source_destination.width % 2 == 1) ? (1.f - 1.f / float(source_destination.width)) : 1.f, + (source_destination.height % 2 == 1) ? (1.f - 1.f / float(source_destination.height)) : 1.f}; + + for (int i = 0; i < pass_level; i++) + { + scissor.p0 = (scissor.p0 + Rml::Vector2i(1)) / 2; + scissor.p1 = Rml::Math::Max(scissor.p1 / 2, scissor.p0); + const bool from_source = (i % 2 == 0); + Gfx::BindRenderTarget(m_d3d_context, (from_source ? temp : source_destination)); + Gfx::BindTexture(m_d3d_context, from_source ? source_destination : temp); + SetScissor(scissor, true); + + DrawFullscreenQuad({}, uv_scaling); + } + + d3dviewport.TopLeftX = 0; + d3dviewport.TopLeftY = 0; + d3dviewport.Width = static_cast(source_destination.width); + d3dviewport.Height = static_cast(source_destination.height); + d3dviewport.MinDepth = 0.0f; + d3dviewport.MaxDepth = 1.0f; + m_d3d_context->RSSetViewports(1, &d3dviewport); + + // Ensure texture data end up in the temp buffer. Depending on the last downscaling, we might need to move it from the source_destination buffer. + const bool transfer_to_temp_buffer = (pass_level % 2 == 0); + if (transfer_to_temp_buffer) + { + Gfx::BindRenderTarget(m_d3d_context, temp); + Gfx::BindTexture(m_d3d_context, source_destination); + DrawFullscreenQuad(); + } + + // Set up uniforms. + UseProgram(ProgramId::Blur); + D3D11_MAPPED_SUBRESOURCE mappedResource{}; + // Lock the constant buffer so it can be written to. + HRESULT result = m_d3d_context->Map(m_shader_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); + if (FAILED(result)) + { + return; + } + + // Get a pointer to the data in the constant buffer. + ShaderCbuffer* dataPtr = (ShaderCbuffer*)mappedResource.pData; + + SetBlurWeights(dataPtr->blur.weights, sigma); + SetTexCoordLimits(dataPtr->blur.texcoord_min, dataPtr->blur.texcoord_max, scissor, {source_destination.width, source_destination.height}); + + Rml::Vector2f texel_offset = Rml::Vector2f(0.f, 1.f) * (1.0f / float(temp.height)); + dataPtr->blur.texel_offset = texel_offset; + + // Upload to the GPU. + m_d3d_context->Unmap(m_shader_buffer, 0); + // Unbind second pass + ID3D11Buffer* null_buffer = nullptr; + m_d3d_context->VSSetConstantBuffers(1, 1, &null_buffer); + + // Blur render pass - vertical. + Gfx::BindRenderTarget(m_d3d_context, source_destination); + Gfx::BindTexture(m_d3d_context, temp); + DrawFullscreenQuad(); + + // Blur render pass - horizontal. + Gfx::BindRenderTarget(m_d3d_context, temp); + Gfx::BindTexture(m_d3d_context, source_destination); + + // Add a 1px transparent border around the blur region by first clearing with a padded scissor. This helps prevent + // artifacts when upscaling the blur result in the later step. On Intel and AMD, we have observed that during + // blitting with linear filtering, pixels outside the 'src' region can be blended into the output. On the other + // hand, it looks like Nvidia clamps the pixels to the source edge, which is what we really want. Regardless, we + // work around the issue with this extra step. + SetScissor(scissor.Extend(1), true); + // D3D11 does not support clearing based on scissor rects. So we use a D3D11Context1 to use ClearView, and define the D3D11_RECt here + Rml::Rectanglei scissor_ext = scissor.Extend(1); + scissor_ext = VerticallyFlipped(scissor_ext, m_viewport_height); + + // Some render APIs don't like offscreen positions (WebGL in particular), so clamp them to the viewport. + const int x_min = Rml::Math::Clamp(scissor_ext.Left(), 0, m_viewport_width); + const int y_min = Rml::Math::Clamp(scissor_ext.Top(), 0, m_viewport_height); + const int x_max = Rml::Math::Clamp(scissor_ext.Right(), 0, m_viewport_width); + const int y_max = Rml::Math::Clamp(scissor_ext.Bottom(), 0, m_viewport_height); + + D3D11_RECT rect_scissor = {}; + rect_scissor.left = x_min; + rect_scissor.top = y_min; + rect_scissor.right = x_max; + rect_scissor.bottom = y_max; + + FLOAT clear_color[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + m_d3d_context->ClearView(temp.render_target_view, clear_color, &rect_scissor, 1); + SetScissor(scissor, true); + + // Update texel offset for upscale pass + result = m_d3d_context->Map(m_blur_cbuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); + if (FAILED(result)) + { + ID3D11ShaderResourceView* const nullSRV = nullptr; + m_d3d_context->PSSetShaderResources(0, 1, &nullSRV); + return; + } + + ShaderCbuffer::Blur* dataPtrBlur = (ShaderCbuffer::Blur*)mappedResource.pData; + + texel_offset = Rml::Vector2f(1.f, 0.f) * (1.0f / float(source_destination.width)); + dataPtrBlur->texel_offset = texel_offset; + + // Upload to the GPU. + m_d3d_context->Unmap(m_blur_cbuffer, 0); + m_d3d_context->VSSetConstantBuffers(0, 1, &m_blur_cbuffer); + + RenderGeometry(m_fullscreen_quad_geometry, {}, RenderInterface_DX11::TexturePostprocessNoBinding); + + // Blit the blurred image to the scissor region with upscaling. + Rml::Rectanglei window_flipped_twice = VerticallyFlipped(window_flipped, m_viewport_height); + SetScissor(window_flipped_twice, false); + + const Rml::Vector2i src_min = scissor.p0; + const Rml::Vector2i src_max = scissor.p1; + const Rml::Vector2i dst_min = window_flipped_twice.p0; + const Rml::Vector2i dst_max = window_flipped_twice.p1; + BlitRenderTarget(temp, source_destination, src_min.x, src_min.y, src_max.x, src_max.y, dst_min.x, dst_max.y, dst_max.x, dst_min.y); + + // The above upscale blit might be jittery at low resolutions (large pass levels). This is especially noticeable when moving an element with + // backdrop blur around or when trying to click/hover an element within a blurred region since it may be rendered at an offset. For more stable + // and accurate rendering we next upscale the blur image by an exact power-of-two. However, this may not fill the edges completely so we need to + // do the above first. Note that this strategy may sometimes result in visible seams. Alternatively, we could try to enlarge the window to the + // next power-of-two size and then downsample and blur that. + + // We need to flip the scissor rect to have it match DX11 coords + const Rml::Vector2i target_min = src_min * (1 << pass_level); + const Rml::Vector2i target_max = src_max * (1 << pass_level); + if (target_min != window_flipped.p0 || target_max != window_flipped.p1) + { + window_flipped_twice = VerticallyFlipped(scissor, m_viewport_height); + const Rml::Vector2i target_min_blit = window_flipped_twice.p0 * (1 << pass_level); + const Rml::Vector2i target_max_blit = window_flipped_twice.p1 * (1 << pass_level); + BlitRenderTarget(temp, source_destination, src_min.x, src_min.y, src_max.x, src_max.y, target_min_blit.x, target_max_blit.y, + target_max_blit.x, target_min_blit.y); + } + + // Restore render state. + SetScissor(original_scissor); + + ID3D11ShaderResourceView* const nullSRV = nullptr; + m_d3d_context->PSSetShaderResources(0, 1, &nullSRV); +} + +void RenderInterface_DX11::RenderFilters(Rml::Span filter_handles) +{ + m_d3d_context->PSSetSamplers(0, 1, &m_sampler_state); + + // To unbind slots when necessary + ID3D11ShaderResourceView* null_shader_resource_views[2] = {nullptr, nullptr}; + + 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: + { + UseProgram(ProgramId::Passthrough); + const float blend_factor[4] = {filter.blend_factor, filter.blend_factor, filter.blend_factor, filter.blend_factor}; + m_d3d_context->OMSetBlendState(m_blend_state_color_filter, blend_factor, 0xFFFFFFFF); + m_current_blend_state = m_blend_state_color_filter; + + const Gfx::RenderTargetData& source = m_render_layers.GetPostprocessPrimary(); + const Gfx::RenderTargetData& destination = m_render_layers.GetPostprocessSecondary(); + Gfx::BindRenderTarget(m_d3d_context, destination); + Gfx::BindTexture(m_d3d_context, source); + m_d3d_context->PSSetSamplers(0, 1, &m_sampler_state); + + DrawFullscreenQuad(); + + m_render_layers.SwapPostprocessPrimarySecondary(); + SetBlendState(m_blend_state_enable); + m_d3d_context->PSSetShaderResources(0, 2, null_shader_resource_views); + } + break; + case FilterType::Blur: + { + ID3D11BlendState* blend_state_backup = m_current_blend_state; + DisableBlend(); + + const Gfx::RenderTargetData& source_destination = m_render_layers.GetPostprocessPrimary(); + const Gfx::RenderTargetData& temp = m_render_layers.GetPostprocessSecondary(); + + const Rml::Rectanglei window_flipped = VerticallyFlipped(m_scissor_state, m_viewport_height); + RenderBlur(filter.sigma, source_destination, temp, window_flipped); + + SetBlendState(blend_state_backup); + } + break; + case FilterType::DropShadow: + { + UseProgram(ProgramId::DropShadow); + ID3D11BlendState* blend_state_backup = m_current_blend_state; + DisableBlend(); + + const Gfx::RenderTargetData& primary = m_render_layers.GetPostprocessPrimary(); + const Gfx::RenderTargetData& secondary = m_render_layers.GetPostprocessSecondary(); + Gfx::BindRenderTarget(m_d3d_context, secondary); + Gfx::BindTexture(m_d3d_context, primary); + + D3D11_MAPPED_SUBRESOURCE mappedResource{}; + // Lock the constant buffer so it can be written to. + HRESULT result = m_d3d_context->Map(m_drop_shadow_cbuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); + if (FAILED(result)) + { + return; + } + + // Get a pointer to the data in the constant buffer. + ShaderCbuffer::DropShadow* dataPtr = (ShaderCbuffer::DropShadow*)mappedResource.pData; + + Rml::Colourf color = ConvertToColorf(filter.color); + + // Copy the data to the GPU + dataPtr->color.x = color.red; + dataPtr->color.y = color.green; + dataPtr->color.z = color.blue; + dataPtr->color.w = color.alpha; + + const Rml::Rectanglei window_flipped = VerticallyFlipped(m_scissor_state, m_viewport_height); + SetTexCoordLimits(dataPtr->texcoord_min, dataPtr->texcoord_max, m_scissor_state, + {primary.width, primary.height}); + + // Upload to the GPU. + m_d3d_context->Unmap(m_drop_shadow_cbuffer, 0); + m_d3d_context->PSSetConstantBuffers(1, 1, &m_drop_shadow_cbuffer); + + m_cbuffer_dirty = true; + + const Rml::Vector2f uv_offset = filter.offset / Rml::Vector2f(-(float)m_viewport_width, (float)m_viewport_height); + DrawFullscreenQuad(uv_offset); + + if (filter.sigma >= 0.5f) + { + const Gfx::RenderTargetData& tertiary = m_render_layers.GetPostprocessTertiary(); + RenderBlur(filter.sigma, secondary, tertiary, window_flipped); + } + + UseProgram(ProgramId::Passthrough); + Gfx::BindRenderTarget(m_d3d_context, secondary); + Gfx::BindTexture(m_d3d_context, primary); + SetBlendState(blend_state_backup); + DrawFullscreenQuad(); + + m_render_layers.SwapPostprocessPrimarySecondary(); + + ID3D11ShaderResourceView* const nullSRV = nullptr; + m_d3d_context->PSSetShaderResources(0, 1, &nullSRV); + m_d3d_context->PSSetShaderResources(1, 1, &nullSRV); + } + break; + case FilterType::ColorMatrix: + { + UseProgram(ProgramId::ColorMatrix); + + ID3D11BlendState* blend_state_backup = m_current_blend_state; + DisableBlend(); + + const Gfx::RenderTargetData& source = m_render_layers.GetPostprocessPrimary(); + const Gfx::RenderTargetData& destination = m_render_layers.GetPostprocessSecondary(); + Gfx::BindRenderTarget(m_d3d_context, destination); + Gfx::BindTexture(m_d3d_context, source); + + D3D11_MAPPED_SUBRESOURCE mappedResource{}; + // Lock the constant buffer so it can be written to. + HRESULT result = m_d3d_context->Map(m_color_matrix_cbuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); + if (FAILED(result)) + { + // Restore blend state + SetBlendState(blend_state_backup); + + ID3D11ShaderResourceView* const nullSRV = nullptr; + m_d3d_context->PSSetShaderResources(0, 1, &nullSRV); + m_cbuffer_dirty = true; + return; + } + + // Get a pointer to the data in the constant buffer. + ColorMatrixCbuffer* dataPtr = (ColorMatrixCbuffer*)mappedResource.pData; + + constexpr bool transpose = std::is_same::value; + + // Copy the data to the GPU + memcpy_s(&dataPtr->color_matrix, sizeof(dataPtr->color_matrix), + transpose ? filter.color_matrix.Transpose().data() : filter.color_matrix.data(), sizeof(dataPtr->color_matrix)); + + // Upload to the GPU. + m_d3d_context->Unmap(m_color_matrix_cbuffer, 0); + + m_d3d_context->PSSetConstantBuffers(1, 1, &m_color_matrix_cbuffer); + + DrawFullscreenQuad(); + + // Unbind cbuffer + ID3D11Buffer* null_cbuffer = nullptr; + m_d3d_context->PSSetConstantBuffers(1, 1, &null_cbuffer); + + m_render_layers.SwapPostprocessPrimarySecondary(); + + // Restore blend state + SetBlendState(blend_state_backup); + + ID3D11ShaderResourceView* const nullSRV = nullptr; + m_d3d_context->PSSetShaderResources(0, 1, &nullSRV); + m_cbuffer_dirty = true; + } + break; + case FilterType::MaskImage: + { + UseProgram(ProgramId::BlendMask); + ID3D11BlendState* blend_state_backup = m_current_blend_state; + DisableBlend(); + + const Gfx::RenderTargetData& source = m_render_layers.GetPostprocessPrimary(); + const Gfx::RenderTargetData& blend_mask = m_render_layers.GetBlendMask(); + const Gfx::RenderTargetData& destination = m_render_layers.GetPostprocessSecondary(); + + Gfx::BindRenderTarget(m_d3d_context, destination); + Gfx::BindTexture(m_d3d_context, source, 0); + Gfx::BindTexture(m_d3d_context, blend_mask, 1); + + DrawFullscreenQuad(); + + m_render_layers.SwapPostprocessPrimarySecondary(); + SetBlendState(blend_state_backup); + m_d3d_context->PSSetShaderResources(0, 2, null_shader_resource_views); + } + break; + case FilterType::Invalid: + { + Rml::Log::Message(Rml::Log::LT_WARNING, "Unhandled render filter %d.", (int)type); + } + break; + } + + // Unbind resources + ID3D11ShaderResourceView* const nullSRV = nullptr; + m_d3d_context->PSSetShaderResources(0, 1, &nullSRV); + } +} + +void RenderInterface_DX11::UpdateConstantBuffer() +{ + if (m_cbuffer_dirty) + { + D3D11_MAPPED_SUBRESOURCE mappedResource{}; + // Lock the constant buffer so it can be written to. + HRESULT result = m_d3d_context->Map(m_shader_buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); + if (FAILED(result)) + { + return; + } + + // Get a pointer to the data in the constant buffer. + ShaderCbuffer* dataPtr = (ShaderCbuffer*)mappedResource.pData; + + // Copy the data to the GPU + dataPtr->common.transform = m_transform; + dataPtr->common.translation = m_translation; + + // Upload to the GPU. + m_d3d_context->Unmap(m_shader_buffer, 0); + m_cbuffer_dirty = false; + } +} + +void RenderInterface_DX11::UseProgram(ProgramId program_id) { + RMLUI_ASSERT(program_data); + if (active_program != program_id) + { + if (program_id != ProgramId::None) + { + m_d3d_context->VSSetShader(program_data->programs[program_id].vertex_shader, nullptr, 0); + m_d3d_context->PSSetShader(program_data->programs[program_id].pixel_shader, nullptr, 0); + } + active_program = program_id; + } +} + +void RenderInterface_DX11::EnableClipMask(bool enable) +{ + m_is_stencil_enabled = enable; + if (enable) + { + m_d3d_context->OMSetDepthStencilState(m_depth_stencil_state_stencil_test, m_stencil_ref_value); + } + else + { + m_d3d_context->OMSetDepthStencilState(m_depth_stencil_state_disable, 0); + } +} + +void RenderInterface_DX11::RenderToClipMask(Rml::ClipMaskOperation operation, Rml::CompiledGeometryHandle geometry, Rml::Vector2f translation) +{ + RMLUI_ASSERT(m_is_stencil_enabled); + using Rml::ClipMaskOperation; + + ID3D11DepthStencilState* stencil_state = m_depth_stencil_state_disable; + + UINT stencil_test_value = m_stencil_ref_value; + switch (operation) + { + case ClipMaskOperation::Set: + { + stencil_test_value = 1; + stencil_state = m_depth_stencil_state_stencil_set; + } + break; + case ClipMaskOperation::SetInverse: + { + stencil_test_value = 0; + stencil_state = m_depth_stencil_state_stencil_set; + } + break; + case ClipMaskOperation::Intersect: + { + stencil_test_value += 1; + stencil_state = m_depth_stencil_state_stencil_intersect; + } + break; + } + + // Disable writing to the color of the render target + float blendFactor[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + m_d3d_context->OMSetBlendState(m_blend_state_disable_color, blendFactor, 0xffffffff); + + Gfx::BindRenderTarget(m_d3d_context, m_render_layers.GetTopLayer()); + m_d3d_context->OMSetDepthStencilState(stencil_state, 1); + + bool clear_stencil = (operation == ClipMaskOperation::Set || operation == ClipMaskOperation::SetInverse); + if (clear_stencil) + { + // Clear stencil buffer + Rml::LayerHandle layer_handle = m_render_layers.GetTopLayerHandle(); + const Gfx::RenderTargetData& rtData = m_render_layers.GetLayer(layer_handle); + m_d3d_context->ClearDepthStencilView(rtData.depth_stencil_view, D3D11_CLEAR_STENCIL, 1.0f, 0); + } + + RenderGeometry(geometry, translation, {}); + + // Restore blend state + m_d3d_context->OMSetBlendState(m_current_blend_state, blendFactor, 0xffffffff); + // Restore state + m_stencil_ref_value = stencil_test_value; + m_d3d_context->OMSetDepthStencilState(m_depth_stencil_state_stencil_test, m_stencil_ref_value); +} + +RenderInterface_DX11::RenderLayerStack::RenderLayerStack() +{ + m_rt_postprocess.resize(5); +} + +RenderInterface_DX11::RenderLayerStack::~RenderLayerStack() +{ + DestroyRenderTargets(); +} + +void RenderInterface_DX11::RenderLayerStack::SetD3DResources(ID3D11Device* device) +{ + RMLUI_ASSERTMSG(!m_d3d_device, "D3D11Device has already been set!"); + m_d3d_device = device; +} + +Rml::LayerHandle RenderInterface_DX11::RenderLayerStack::PushLayer() +{ + RMLUI_ASSERT(m_layers_size <= (int)m_rt_layers.size()); + + if (m_layers_size == (int)m_rt_layers.size()) + { + // All framebuffers should share a single stencil buffer. + ID3D11DepthStencilView* shared_depth_stencil = (m_rt_layers.empty() ? nullptr : m_rt_layers.front().depth_stencil_view); + + m_rt_layers.push_back(Gfx::RenderTargetData{}); + Gfx::CreateRenderTarget(m_d3d_device, m_rt_layers.back(), m_width, m_height, NUM_MSAA_SAMPLES, Gfx::RenderTargetAttachment::DepthStencil, + shared_depth_stencil); + } + + m_layers_size += 1; + return GetTopLayerHandle(); +} + +void RenderInterface_DX11::RenderLayerStack::PopLayer() +{ + RMLUI_ASSERT(m_layers_size > 0); + m_layers_size -= 1; +} + +const Gfx::RenderTargetData& RenderInterface_DX11::RenderLayerStack::GetLayer(Rml::LayerHandle layer) const +{ + RMLUI_ASSERT((size_t)layer < (size_t)m_layers_size); + return m_rt_layers[layer]; +} + +const Gfx::RenderTargetData& RenderInterface_DX11::RenderLayerStack::GetTopLayer() const +{ + return GetLayer(GetTopLayerHandle()); +} + +Rml::LayerHandle RenderInterface_DX11::RenderLayerStack::GetTopLayerHandle() const +{ + RMLUI_ASSERT(m_layers_size > 0); + return static_cast(m_layers_size - 1); +} + +void RenderInterface_DX11::RenderLayerStack::SwapPostprocessPrimarySecondary() +{ + std::swap(m_rt_postprocess[0], m_rt_postprocess[1]); +} + +void RenderInterface_DX11::RenderLayerStack::BeginFrame(int new_width, int new_height) +{ + RMLUI_ASSERT(m_layers_size == 0); + + if (new_width != m_width || new_height != m_height) + { + m_width = new_width; + m_height = new_height; + + DestroyRenderTargets(); + } + + PushLayer(); +} + +void RenderInterface_DX11::RenderLayerStack::EndFrame() +{ + RMLUI_ASSERT(m_layers_size == 1); + PopLayer(); +} + +void RenderInterface_DX11::RenderLayerStack::DestroyRenderTargets() +{ + RMLUI_ASSERTMSG(m_layers_size == 0, "Do not call this during frame rendering, that is, between BeginFrame() and EndFrame()."); + + for (Gfx::RenderTargetData& fb : m_rt_layers) + Gfx::DestroyRenderTarget(fb); + + m_rt_layers.clear(); + + for (Gfx::RenderTargetData& fb : m_rt_postprocess) + Gfx::DestroyRenderTarget(fb); +} + +const Gfx::RenderTargetData& RenderInterface_DX11::RenderLayerStack::EnsureRenderTargetPostprocess(int index) +{ + RMLUI_ASSERT(index < (int)m_rt_postprocess.size()) + Gfx::RenderTargetData& rt = m_rt_postprocess[index]; + if (!rt.render_target_view) + Gfx::CreateRenderTarget(m_d3d_device, rt, m_width, m_height, 0, Gfx::RenderTargetAttachment::None, 0); + return rt; +} +#endif // RMLUI_PLATFORM_WIN32 \ No newline at end of file diff --git a/Backends/RmlUi_Renderer_DX11.h b/Backends/RmlUi_Renderer_DX11.h new file mode 100644 index 000000000..643f39f2e --- /dev/null +++ b/Backends/RmlUi_Renderer_DX11.h @@ -0,0 +1,334 @@ +/* +* 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_DX11_H +#define RMLUI_BACKENDS_RENDERER_DX11_H + + #include + + #if defined RMLUI_PLATFORM_WIN32 + #include + + #include + #include + #include + +#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 + +// Helper for a common cleanup pattern in DX11 +#define DX_CLEANUP_RESOURCE_IF_CREATED(resource) \ + if (resource) \ + { \ + resource->Release(); \ + resource = nullptr; \ + } + +// Allow the user to override the number of MSAA samples +#ifndef NUM_MSAA_SAMPLES + #define NUM_MSAA_SAMPLES 2 +#endif + +// Use these typedefs to overload the default implementation with another image loader so that you can handle more than uncompressed TGA if you wish. +// Must define both the load and free functions. See RenderInterface_DX11:: +typedef void (*pfnLoadTextureRaw)(const Rml::String& filename, int* pWidth, int* pHeight, uint8_t** pData, size_t* pDataSize); +typedef void (*pfnFreeTextureRaw)(uint8_t* pData); + +enum class ProgramId; +class RenderLayerStack; +namespace Gfx { +struct ProgramData; +struct RenderTargetData; +} // namespace Gfx + +class RenderInterface_DX11 : public Rml::RenderInterface { +public: + RenderInterface_DX11(); + ~RenderInterface_DX11(); + + // Resource initialisation and cleanup + void Init(ID3D11Device* p_d3d_device); + void Cleanup (); + + // Sets up DirectX11 states for taking rendering commands from RmlUi. + void BeginFrame(ID3D11RenderTargetView* p_render_target_view); + void EndFrame(); + + void SetViewport(const int width, const int height); + void Clear(); + + // -- Inherited from Rml::RenderInterface -- + + Rml::CompiledGeometryHandle CompileGeometry(Rml::Span vertices, Rml::Span indices) override; + void ReleaseGeometry(Rml::CompiledGeometryHandle handle) override; + void RenderGeometry(Rml::CompiledGeometryHandle handle, Rml::Vector2f translation, Rml::TextureHandle texture) override; + + Rml::TextureHandle LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source) override; + Rml::TextureHandle GenerateTexture(Rml::Span source, Rml::Vector2i source_dimensions) override; + void ReleaseTexture(Rml::TextureHandle texture_handle) 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; + + 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() 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); + // Can be passed to RenderGeometry() to not touch the cbuffers + static constexpr Rml::TextureHandle TexturePostprocessNoBinding = Rml::TextureHandle(-3); + +private: + // Changes blend state if necessary + void SetBlendState(ID3D11BlendState* blend_state); + void DisableBlend(); + void UpdateConstantBuffer(); + void UseProgram(ProgramId program_id); + + // Mirrors the behaviour of glBlitFramebuffer. It supports sampling from a rect from source, writing to a rect at dest, and does MSAA resolve. + // Coordinates are in pixels. + void BlitRenderTarget(const Gfx::RenderTargetData& source, const Gfx::RenderTargetData& dest, int srcX0, int srcY0, int srcX1, int srcY1, + int dstX0, int dstY0, int dstX1, int dstY1); + void BlitLayerToPostprocessPrimary(Rml::LayerHandle layer_handle); + void RenderFilters(Rml::Span filter_handles); + + void SetScissor(Rml::Rectanglei region, bool vertically_flip = false); + + void DrawFullscreenQuad(); + void DrawFullscreenQuad(Rml::Vector2f uv_offset, Rml::Vector2f uv_scaling = Rml::Vector2f(1.f)); + + void RenderBlur(float sigma, const Gfx::RenderTargetData& source_destination, const Gfx::RenderTargetData& temp, Rml::Rectanglei window_flipped); + +private: + // D3D11 core resources + ID3D11Device* m_d3d_device = nullptr; + ID3D11Device1* m_d3d_device_1 = nullptr; + ID3D11DeviceContext1* m_d3d_context = nullptr; + ID3D11RenderTargetView* m_bound_render_target = nullptr; + bool m_scissor_enabled = false; + ID3D11RasterizerState* m_rasterizer_state_scissor_enabled = nullptr; + ID3D11RasterizerState* m_rasterizer_state_scissor_disabled = nullptr; + + // Depth stencil states + bool m_is_stencil_enabled = false; + bool m_stencil_dirty = false; + uint32_t m_stencil_ref_value = 0; + ID3D11DepthStencilState* m_depth_stencil_state_disable = nullptr; // Stencil Off + ID3D11DepthStencilState* m_depth_stencil_state_stencil_set = nullptr; // Clipmask Set/Inverse + ID3D11DepthStencilState* m_depth_stencil_state_stencil_intersect = nullptr; // Clipmask Intersect. Can have several test values + ID3D11DepthStencilState* m_depth_stencil_state_stencil_test = nullptr; // Clipmask test + + // Shaders + ID3D11Buffer* m_shader_buffer = nullptr; + ID3D11Buffer* m_color_matrix_cbuffer = nullptr; + ID3D11Buffer* m_blur_cbuffer = nullptr; + ID3D11Buffer* m_drop_shadow_cbuffer = nullptr; + bool m_cbuffer_dirty = true; + ID3D11SamplerState* m_sampler_state = nullptr; + + // Viewport dimensions + int m_viewport_width = 0; + int m_viewport_height = 0; + + Rml::Matrix4f m_transform = Rml::Matrix4f::Identity(); + Rml::Matrix4f m_projection = Rml::Matrix4f::Identity(); + Rml::Vector2f m_translation = {}; + + ID3D11InputLayout* m_vertex_layout = nullptr; + ID3D11BlendState* m_blend_state_enable = nullptr; + ID3D11BlendState* m_blend_state_disable_color = nullptr; + ID3D11BlendState* m_blend_state_color_filter = nullptr; + + ProgramId active_program = {}; + Rml::Rectanglei m_scissor_state = Rml::Rectanglei::MakeInvalid(); + Rml::UniquePtr program_data; + + struct DX11_GeometryData { + public: + ID3D11Buffer* vertex_buffer = nullptr; + ID3D11Buffer* index_buffer = nullptr; + size_t index_count = 0; + }; + + // D3D11 state for RmlUi rendering + bool m_current_blend_state_enabled = false; + ID3D11BlendState* m_current_blend_state = nullptr; + + // Backup of prior D3D11 state + struct D3D11State { + UINT scissor_rects_count, viewports_count; + D3D11_RECT scissor_rects[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; + D3D11_VIEWPORT viewports[D3D11_VIEWPORT_AND_SCISSORRECT_OBJECT_COUNT_PER_PIPELINE]; + ID3D11RasterizerState* rastizer_state; + ID3D11BlendState* blend_state; + FLOAT blend_factor[4]; + UINT sample_mask; + UINT stencil_ref; + ID3D11DepthStencilState* depth_stencil_state; + ID3D11ShaderResourceView* pixel_shader_shader_resource; + ID3D11SamplerState* pixel_shader_sampler; + ID3D11PixelShader* pixel_shader; + ID3D11VertexShader* vertex_shader; + ID3D11GeometryShader* geometry_shader; + UINT pixel_shader_instances_count, vertex_shader_instances_count, geometry_shader_instances_count; + // 256 is max according to PSSetShader documentation + ID3D11ClassInstance *pixel_shader_instances[256], *vertex_shader_instances[256], *geometry_shader_instances[256]; + D3D11_PRIMITIVE_TOPOLOGY primitive_topology; + ID3D11Buffer *index_buffer, *vertex_buffer, *vertex_shader_constant_buffer; + UINT index_buffer_offset, vertex_buffer_stride, vertex_buffer_offset; + DXGI_FORMAT index_buffer_format; + ID3D11InputLayout* input_layout; + }; + + D3D11State m_previous_d3d_state{}; + + #pragma pack(4) + union ShaderCbuffer { + struct { + Rml::Matrix4f transform; + Rml::Vector2f translation; + } common; + struct Gradient { + int _padding[18]; + int func; + int num_stops; + Rml::Vector2f p; + Rml::Vector2f v; + Rml::Vector4f stop_colors[16]; + float stop_positions[16]; + } gradient; + struct Blur { + int _padding[18]; + Rml::Vector2f texel_offset; + Rml::Vector4f weights; + Rml::Vector2f texcoord_min; + Rml::Vector2f texcoord_max; + } blur; + struct DropShadow { + Rml::Vector2f texcoord_min; + Rml::Vector2f texcoord_max; + Rml::Vector4f color; + } drop_shadow; + struct Creation { + int _padding[18]; + Rml::Vector2f dimensions; + float value; + } creation; + }; + struct ColorMatrixCbuffer { + Rml::Matrix4f color_matrix; + }; + #pragma pack() + + Rml::CompiledGeometryHandle m_fullscreen_quad_geometry = 0; + + /* + Manages render targets, including the layer stack and postprocessing render targets. + + Layers can be pushed and popped, creating new render targets as needed. Typically, geometry is rendered to the top + layer. The layer render targets may have MSAA enabled. + + Postprocessing render targets 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 SetD3DResources(ID3D11Device* device); + + // 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::RenderTargetData& GetLayer(Rml::LayerHandle layer) const; + const Gfx::RenderTargetData& GetTopLayer() const; + Rml::LayerHandle GetTopLayerHandle() const; + + const Gfx::RenderTargetData& GetPostprocessPrimary() { return EnsureRenderTargetPostprocess(0); } + const Gfx::RenderTargetData& GetPostprocessSecondary() { return EnsureRenderTargetPostprocess(1); } + const Gfx::RenderTargetData& GetPostprocessTertiary() { return EnsureRenderTargetPostprocess(2); } + const Gfx::RenderTargetData& GetBlendMask() { return EnsureRenderTargetPostprocess(3); } + const Gfx::RenderTargetData& GetTemporary() { return EnsureRenderTargetPostprocess(4); } + + void SwapPostprocessPrimarySecondary(); + + void BeginFrame(int new_width, int new_height); + void EndFrame(); + + private: + void DestroyRenderTargets(); + const Gfx::RenderTargetData& EnsureRenderTargetPostprocess(int index); + + int m_width = 0, m_height = 0; + + // The number of active layers is manually tracked since we re-use the render targets stored in the fb_layers stack. + int m_layers_size = 0; + + Rml::Vector m_rt_layers; + Rml::Vector m_rt_postprocess; + + ID3D11Device* m_d3d_device = nullptr; + }; + + RenderLayerStack m_render_layers; +}; + +#endif // RMLUI_PLATFORM_WIN32 + +#endif // RMLUI_BACKENDS_RENDERER_DX11_H diff --git a/Backends/RmlUi_Renderer_GL3.cpp b/Backends/RmlUi_Renderer_GL3.cpp index 183682353..db07ab0ae 100644 --- a/Backends/RmlUi_Renderer_GL3.cpp +++ b/Backends/RmlUi_Renderer_GL3.cpp @@ -1365,8 +1365,19 @@ void RenderInterface_GL3::RenderBlur(float sigma, const Gfx::FramebufferData& so RMLUI_ASSERT(&source_destination != &temp && source_destination.width == temp.width && source_destination.height == temp.height); RMLUI_ASSERT(window_flipped.Valid()); + if (window_flipped.Top() == window_flipped.Bottom() || window_flipped.Left() == window_flipped.Right()) + { + // In some cases the bounds are 0 width, so early exit since there's nothing to blur + return; + } + int pass_level = 0; SigmaToParameters(sigma, pass_level, sigma); + if (sigma == 0) + { + // If sigma is zero we don't have anything to blur, so early exit since blurring will have no effect on the final image + return; + } const Rml::Rectanglei original_scissor = scissor_state; diff --git a/CMake/OptionsLists.cmake b/CMake/OptionsLists.cmake index 67a437246..417b23800 100644 --- a/CMake/OptionsLists.cmake +++ b/CMake/OptionsLists.cmake @@ -7,6 +7,7 @@ set(RMLUI_BACKEND_OPTIONS "native" "Win32_GL2" "Win32_VK" + "Win32_DX11" "X11_GL2" "SDL_GL2" "SDL_GL3" @@ -15,6 +16,7 @@ set(RMLUI_BACKEND_OPTIONS "SFML_GL2" "GLFW_GL2" "GLFW_GL3" + "GLFW_DX11" "GLFW_VK" "BackwardCompatible_GLFW_GL2" "BackwardCompatible_GLFW_GL3" diff --git a/Samples/basic/effects/CMakeLists.txt b/Samples/basic/effects/CMakeLists.txt index 520d18e8f..d58d06cca 100644 --- a/Samples/basic/effects/CMakeLists.txt +++ b/Samples/basic/effects/CMakeLists.txt @@ -1,5 +1,5 @@ -if(NOT RMLUI_BACKEND MATCHES "GL3$") - message(STATUS "Effects sample disabled - only available on GL3 backends") +if(NOT RMLUI_BACKEND MATCHES "GL3$|DX11$") + message(STATUS "Effects sample disabled - only available on GL3 and DX11 backends") return() endif() diff --git a/readme.md b/readme.md index 712fe8c15..e909be550 100644 --- a/readme.md +++ b/readme.md @@ -168,13 +168,13 @@ 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 | DirectX11 | +|---------------------|:---------:|:---------:|:---------:|:-----------:|:-----------:| +| Win32 | ✔️ | | ✔️ | | ✔️ | +| X11 | ✔️ | | | | | +| SFML | ✔️ | | | | | +| GLFW | ✔️ | ✔️ | ✔️ | | ✔️ | +| SDL¹ | ✔️ | ✔️² | ✔️ | ✔️ | | ¹ SDL backends extend their respective renderers to provide image support based on SDL_image.\ ² Supports Emscripten compilation target.