#include "RmlUi_Renderer_DX11.h" #include "RmlUi_Include_Windows.h" #include #include #include #include #include #include #include #include // Helper for a common cleanup pattern in DX11 template static void Dx11CleanupResourceIfCreated(T*& resource) { if (resource) { resource->Release(); resource = nullptr; } } #ifndef RMLUI_PLATFORM_WIN32 #error "Cannot build DX11 renderer on non-Windows platforms" #endif // RMLUI_PLATFORM_WIN32 #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 address each element of the float4s #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((IN.uv.xy - m_p), R); t = 0.5 + atan2(-V.x, V.y) / (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 Dx11CleanupResourceIfCreated(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) { Dx11CleanupResourceIfCreated(rt.render_target_view); Dx11CleanupResourceIfCreated(rt.render_target_texture); Dx11CleanupResourceIfCreated(rt.render_target_shader_resource_view); Dx11CleanupResourceIfCreated(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) { Dx11CleanupResourceIfCreated(programs.vertex_shader); Dx11CleanupResourceIfCreated(programs.pixel_shader); } for (ID3DBlob* shader_blob : data.vert_shaders) { Dx11CleanupResourceIfCreated(shader_blob); } for (ID3DBlob* shader_blob : data.frag_shaders) { Dx11CleanupResourceIfCreated(shader_blob); } } } // namespace Gfx RenderInterface_DX11::RenderInterface_DX11(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); } } RenderInterface_DX11::~RenderInterface_DX11() { 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 Dx11CleanupResourceIfCreated(m_sampler_state); Dx11CleanupResourceIfCreated(m_blend_state_enable); Dx11CleanupResourceIfCreated(m_blend_state_color_filter); Dx11CleanupResourceIfCreated(m_blend_state_disable_color); Dx11CleanupResourceIfCreated(m_depth_stencil_state_disable); Dx11CleanupResourceIfCreated(m_depth_stencil_state_stencil_intersect); Dx11CleanupResourceIfCreated(m_depth_stencil_state_stencil_set); Dx11CleanupResourceIfCreated(m_depth_stencil_state_stencil_test); Dx11CleanupResourceIfCreated(m_rasterizer_state_scissor_disabled); Dx11CleanupResourceIfCreated(m_rasterizer_state_scissor_enabled); Dx11CleanupResourceIfCreated(m_shader_buffer); Dx11CleanupResourceIfCreated(m_color_matrix_cbuffer); Dx11CleanupResourceIfCreated(m_blur_cbuffer); Dx11CleanupResourceIfCreated(m_drop_shadow_cbuffer); Dx11CleanupResourceIfCreated(m_vertex_layout); Dx11CleanupResourceIfCreated(m_d3d_context); Dx11CleanupResourceIfCreated(m_d3d_device_1); Dx11CleanupResourceIfCreated(m_d3d_device); } void RenderInterface_DX11::BeginFrame() { RMLUI_ASSERT(m_viewport_width >= 1 && m_viewport_height >= 1); 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.rasterizer_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); } // 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); 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, 0.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(ID3D11RenderTargetView* p_render_target_view) { RMLUI_ASSERTMSG(p_render_target_view, "p_render_target_view 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, &p_render_target_view, 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_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.rasterizer_state); if (m_previous_d3d_state.rasterizer_state) { m_previous_d3d_state.rasterizer_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)) { Dx11CleanupResourceIfCreated(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; Dx11CleanupResourceIfCreated(geometry->vertex_buffer); Dx11CleanupResourceIfCreated(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)) { Dx11CleanupResourceIfCreated(gpu_texture); return Rml::TextureHandle(0); } Dx11CleanupResourceIfCreated(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; Dx11CleanupResourceIfCreated(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)width, (float)height, 0, -10000, 10000); } void RenderInterface_DX11::Clear(ID3D11RenderTargetView* p_render_target_view) { float clearColor[4] = {0.0f, 0.0f, 0.0f, 1.0f}; m_d3d_context->ClearRenderTargetView(p_render_target_view, clearColor); } void RenderInterface_DX11::RestorePartialRenderState() { UseProgram(ProgramId::None); ID3D11BlendState* restore_blend_state = m_current_blend_state; m_current_blend_state = nullptr; SetBlendState(restore_blend_state); } 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); Dx11CleanupResourceIfCreated(original_render_target_view); Dx11CleanupResourceIfCreated(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 (float& weight : weights) weight /= 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, nullptr); return rt; }