From eb1a0e104dd5258ada741ede52e4d3fa6666cff6 Mon Sep 17 00:00:00 2001 From: Tristan Fransen Date: Mon, 11 Aug 2025 09:05:49 +0100 Subject: [PATCH 01/12] initial work on caching box shadows --- Include/RmlUi/Core/RenderManager.h | 238 ++++++++++++++++------------- Source/Core/GeometryBoxShadow.cpp | 39 +++-- Source/Core/RenderManager.cpp | 11 ++ 3 files changed, 171 insertions(+), 117 deletions(-) diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h index 151a278ea..3dd61fd3b 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -37,122 +37,144 @@ namespace Rml { -class Geometry; -class CompiledFilter; -class CompiledShader; -class TextureDatabase; -class Texture; -class RenderManagerAccess; - -struct ClipMaskGeometry { - ClipMaskOperation operation; - Geometry* geometry; - Vector2f absolute_offset; - const Matrix4f* transform; -}; -inline bool operator==(const ClipMaskGeometry& a, const ClipMaskGeometry& b) -{ - return a.operation == b.operation && a.geometry == b.geometry && a.absolute_offset == b.absolute_offset && a.transform == b.transform; -} -inline bool operator!=(const ClipMaskGeometry& a, const ClipMaskGeometry& b) -{ - return !(a == b); -} -using ClipMaskGeometryList = Vector; - -struct RenderState { - Rectanglei scissor_region = Rectanglei::MakeInvalid(); - ClipMaskGeometryList clip_mask_list; - Matrix4f transform = Matrix4f::Identity(); -}; - -/** - A wrapper over the render interface, which tracks its state and resources. - - All operations to be submitted to the render interface should go through this class. - */ -class RMLUICORE_API RenderManager : NonCopyMoveable { -public: - RenderManager(RenderInterface* render_interface); - ~RenderManager(); - - void PrepareRender(Vector2i dimensions); - void SetViewport(Vector2i dimensions); - Vector2i GetViewport() const; - - void DisableScissorRegion(); - void SetScissorRegion(Rectanglei region); - Rectanglei GetScissorRegion() const; - - void DisableClipMask(); - void SetClipMask(ClipMaskGeometryList clip_elements); - void SetClipMask(ClipMaskOperation operation, Geometry* geometry, Vector2f translation); - - void SetTransform(const Matrix4f* new_transform); - - // Retrieves the cached render state. If setting this state again, ensure the lifetimes of referenced objects are - // still valid. Possibly invalidating actions include destroying an element, or altering its transform property. - const RenderState& GetState() const { return state; } - void SetState(const RenderState& next); - void ResetState(); - - Geometry MakeGeometry(Mesh&& mesh); - - Texture LoadTexture(const String& source, const String& document_path = String()); - CallbackTexture MakeCallbackTexture(CallbackTextureFunction callback); - - CompiledFilter CompileFilter(const String& name, const Dictionary& parameters); - CompiledShader CompileShader(const String& name, const Dictionary& parameters); - - LayerHandle PushLayer(); - void CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span filters); - void PopLayer(); - - LayerHandle GetTopLayer() const; - LayerHandle GetNextLayer() const; - - CompiledFilter SaveLayerAsMaskImage(); - -private: - void ApplyClipMask(const ClipMaskGeometryList& clip_elements); - - StableVectorIndex InsertGeometry(Mesh&& mesh); - CompiledGeometryHandle GetCompiledGeometryHandle(StableVectorIndex index); - - void Render(const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader); - - void GetTextureSourceList(StringList& source_list) const; - const Mesh& GetMesh(const Geometry& geometry) const; - - bool ReleaseTexture(const String& texture_source); - void ReleaseAllTextures(); - void ReleaseAllCompiledGeometry(); + class Geometry; + class CompiledFilter; + class CompiledShader; + class TextureDatabase; + class Texture; + class RenderManagerAccess; + + struct BoxShadowGeometryInfo { + CornerSizes border_radius; + Vector2i texture_dimensions; + Vector2f element_offset_in_texture; + Vector padding_render_boxes; + Vector border_render_boxes; + BoxShadowList shadow_list; + }; + inline bool operator==(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) + { + return a.border_radius == b.border_radius && a.texture_dimensions == b.texture_dimensions && a.element_offset_in_texture == b.element_offset_in_texture && + a.padding_render_boxes == b.padding_render_boxes && a.border_render_boxes == b.border_render_boxes && a.shadow_list == b.shadow_list; + } + inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) + { + return !(a == b); + } + + struct ClipMaskGeometry { + ClipMaskOperation operation; + Geometry* geometry; + Vector2f absolute_offset; + const Matrix4f* transform; + }; + inline bool operator==(const ClipMaskGeometry& a, const ClipMaskGeometry& b) + { + return a.operation == b.operation && a.geometry == b.geometry && a.absolute_offset == b.absolute_offset && a.transform == b.transform; + } + inline bool operator!=(const ClipMaskGeometry& a, const ClipMaskGeometry& b) + { + return !(a == b); + } + using ClipMaskGeometryList = Vector; + + struct RenderState { + Rectanglei scissor_region = Rectanglei::MakeInvalid(); + ClipMaskGeometryList clip_mask_list; + Matrix4f transform = Matrix4f::Identity(); + }; + + /** + A wrapper over the render interface, which tracks its state and resources. + + All operations to be submitted to the render interface should go through this class. + */ + class RMLUICORE_API RenderManager : NonCopyMoveable { + public: + RenderManager(RenderInterface* render_interface); + ~RenderManager(); + + void PrepareRender(Vector2i dimensions); + void SetViewport(Vector2i dimensions); + Vector2i GetViewport() const; + + void DisableScissorRegion(); + void SetScissorRegion(Rectanglei region); + Rectanglei GetScissorRegion() const; + + void DisableClipMask(); + void SetClipMask(ClipMaskGeometryList clip_elements); + void SetClipMask(ClipMaskOperation operation, Geometry* geometry, Vector2f translation); + + void SetTransform(const Matrix4f* new_transform); + + // Retrieves the cached render state. If setting this state again, ensure the lifetimes of referenced objects are + // still valid. Possibly invalidating actions include destroying an element, or altering its transform property. + const RenderState& GetState() const { return state; } + void SetState(const RenderState& next); + void ResetState(); + + Geometry MakeGeometry(Mesh&& mesh); + + Texture LoadTexture(const String& source, const String& document_path = String()); + CallbackTexture MakeCallbackTexture(CallbackTextureFunction callback); + + CallbackTexture FindOrMakeBoxShadowCallbackTexture(const BoxShadowGeometryInfo& geometry_info, CallbackTextureFunction callback); + + CompiledFilter CompileFilter(const String& name, const Dictionary& parameters); + CompiledShader CompileShader(const String& name, const Dictionary& parameters); + + LayerHandle PushLayer(); + void CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span filters); + void PopLayer(); + + LayerHandle GetTopLayer() const; + LayerHandle GetNextLayer() const; + + CompiledFilter SaveLayerAsMaskImage(); + + private: + void ApplyClipMask(const ClipMaskGeometryList& clip_elements); + + StableVectorIndex InsertGeometry(Mesh&& mesh); + CompiledGeometryHandle GetCompiledGeometryHandle(StableVectorIndex index); + + void Render(const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader); + + void GetTextureSourceList(StringList& source_list) const; + const Mesh& GetMesh(const Geometry& geometry) const; + + bool ReleaseTexture(const String& texture_source); + void ReleaseAllTextures(); + void ReleaseAllCompiledGeometry(); + + void ReleaseResource(const CallbackTexture& texture); + Mesh ReleaseResource(const Geometry& geometry); + void ReleaseResource(const CompiledFilter& filter); + void ReleaseResource(const CompiledShader& shader); - void ReleaseResource(const CallbackTexture& texture); - Mesh ReleaseResource(const Geometry& geometry); - void ReleaseResource(const CompiledFilter& filter); - void ReleaseResource(const CompiledShader& shader); + struct GeometryData { + Mesh mesh; + CompiledGeometryHandle handle = {}; + }; - struct GeometryData { - Mesh mesh; - CompiledGeometryHandle handle = {}; - }; + RenderInterface* render_interface = nullptr; - RenderInterface* render_interface = nullptr; + StableVector geometry_list; + UniquePtr texture_database; - StableVector geometry_list; - UniquePtr texture_database; + UnorderedMap box_shadow_cache; - int compiled_filter_count = 0; - int compiled_shader_count = 0; + int compiled_filter_count = 0; + int compiled_shader_count = 0; - RenderState state; - Vector2i viewport_dimensions; + RenderState state; + Vector2i viewport_dimensions; - Vector render_stack; + Vector render_stack; - friend class RenderManagerAccess; -}; + friend class RenderManagerAccess; + }; } // namespace Rml diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index 93f68b743..0f1738396 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -88,7 +88,28 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& // Callback for generating the box-shadow texture. Using a callback ensures that the texture can be regenerated at any time, for example if the // device loses its GPU context and the client calls Rml::ReleaseTextures(). - auto texture_callback = [&background_border_geometry, element, border_radius, texture_dimensions, element_offset_in_texture, + Vector padding_render_boxes{}; + Vector border_render_boxes{}; + + for (int i = 0; i < element->GetNumBoxes(); i++) + { + padding_render_boxes.push_back(element->GetRenderBox(BoxArea::Padding, i)); + border_render_boxes.push_back(element->GetRenderBox(BoxArea::Border, i)); + } + + BoxShadowGeometryInfo geometry_info; + geometry_info.border_radius = border_radius; + geometry_info.texture_dimensions = texture_dimensions; + geometry_info.element_offset_in_texture = element_offset_in_texture; + geometry_info.padding_render_boxes = padding_render_boxes; + geometry_info.border_render_boxes = border_render_boxes; + geometry_info.shadow_list = shadow_list; + + auto texture_callback = [&background_border_geometry, border_radius, + texture_dimensions, element_offset_in_texture, + padding_render_boxes = std::move(padding_render_boxes), + num_boxes = element->GetNumBoxes(), + border_render_boxes = std::move(border_render_boxes), shadow_list = std::move(shadow_list)](const CallbackTextureInterface& texture_interface) -> bool { RenderManager& render_manager = texture_interface.GetRenderManager(); @@ -106,13 +127,13 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& } // Generate the geometry for all the element's boxes and extend the render-texture further to cover all of them. - for (int i = 0; i < element->GetNumBoxes(); i++) + for (int i = 0; i < num_boxes; i++) { ColourbPremultiplied white(255); if (has_inner_shadow) - MeshUtilities::GenerateBackground(mesh_padding, element->GetRenderBox(BoxArea::Padding, i), white); + MeshUtilities::GenerateBackground(mesh_padding, padding_render_boxes[i], white); if (has_outer_shadow) - MeshUtilities::GenerateBackground(mesh_padding_border, element->GetRenderBox(BoxArea::Border, i), white); + MeshUtilities::GenerateBackground(mesh_padding_border, border_render_boxes[i], white); } const RenderState initial_render_state = render_manager.GetState(); @@ -132,8 +153,8 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& { Log::Message(Log::LT_INFO, "The desired box-shadow texture dimensions (%d, %d) are larger than the current window region (%d, %d). " - "Results may be clipped. In element: %s", - texture_dimensions.x, texture_dimensions.y, scissor_region.Width(), scissor_region.Height(), element->GetAddress().c_str()); + "Results may be clipped. ", // FIXME: how do we log this? now that elements share box shadows? //In element: %s", + texture_dimensions.x, texture_dimensions.y, scissor_region.Width(), scissor_region.Height()/*, element->GetAddress().c_str()*/); } render_manager.PushLayer(); @@ -164,10 +185,10 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& Mesh mesh_shadow; // Generate the shadow geometry. For outer box-shadows it is rendered normally, while for inset box-shadows it is used as a clipping mask. - for (int i = 0; i < element->GetNumBoxes(); i++) + for (int i = 0; i < num_boxes; i++) { const float signed_spread_distance = (inset ? -spread_distance : spread_distance); - RenderBox render_box = element->GetRenderBox(inset ? BoxArea::Padding : BoxArea::Border, i); + RenderBox render_box = (inset ? padding_render_boxes : border_render_boxes)[i]; render_box.SetFillSize(Math::Max(render_box.GetFillSize() + Vector2f(2.f * signed_spread_distance), Vector2f{0.001f})); render_box.SetBorderRadius(spread_radii); render_box.SetBorderOffset(render_box.GetBorderOffset() - Vector2f(signed_spread_distance)); @@ -227,7 +248,7 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& const byte alpha = byte(opacity * 255.f); MeshUtilities::GenerateQuad(mesh, -element_offset_in_texture, Vector2f(texture_dimensions), ColourbPremultiplied(alpha, alpha)); - out_shadow_texture = render_manager.MakeCallbackTexture(std::move(texture_callback)); + out_shadow_texture = render_manager.FindOrMakeBoxShadowCallbackTexture(geometry_info, std::move(texture_callback)); out_shadow_geometry = render_manager.MakeGeometry(std::move(mesh)); } diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp index 5b397e3c3..d83a3c235 100644 --- a/Source/Core/RenderManager.cpp +++ b/Source/Core/RenderManager.cpp @@ -113,6 +113,17 @@ CallbackTexture RenderManager::MakeCallbackTexture(CallbackTextureFunction callb return CallbackTexture(this, texture_database->callback_database.CreateTexture(std::move(callback))); } +CallbackTexture RenderManager::FindOrMakeBoxShadowCallbackTexture(const BoxShadowGeometryInfo& geometry_info, CallbackTextureFunction callback) +{ + auto it = box_shadow_cache.find(geometry_info); + if (it != box_shadow_cache.end()) { + return it->second; + } + CallbackTexture& cb = box_shadow_cache[geometry_info]; + cb = MakeCallbackTexture(callback); + return cb; +} + void RenderManager::DisableScissorRegion() { SetScissorRegion(Rectanglei::MakeInvalid()); From f39fc20ca310524fbcae2b817bc09d4775ed79e6 Mon Sep 17 00:00:00 2001 From: Tristan Fransen Date: Tue, 12 Aug 2025 22:11:41 +0200 Subject: [PATCH 02/12] hash function for the BoxShadowGeometryInfo and passing the mesh data to the texture callback function --- Include/RmlUi/Core/RenderManager.h | 329 +++++++++++++++++------------ Source/Core/GeometryBoxShadow.cpp | 13 +- 2 files changed, 206 insertions(+), 136 deletions(-) diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h index 3dd61fd3b..2ff2a346a 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -36,145 +36,212 @@ #include "Types.h" namespace Rml { + class Geometry; + class CompiledFilter; + class CompiledShader; + class TextureDatabase; + class Texture; + class RenderManagerAccess; + + struct BoxShadowGeometryInfo { + Mesh background_border_mesh; + CornerSizes border_radius; + Vector2i texture_dimensions; + Vector2f element_offset_in_texture; + Vector padding_render_boxes; + Vector border_render_boxes; + BoxShadowList shadow_list; + }; + inline bool operator==(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) + { + return a.background_border_mesh==b.background_border_mesh && a.border_radius == b.border_radius && a.texture_dimensions == b.texture_dimensions && a.element_offset_in_texture == b.element_offset_in_texture && + a.padding_render_boxes == b.padding_render_boxes && a.border_render_boxes == b.border_render_boxes && a.shadow_list == b.shadow_list; + } + inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) + { + return !(a == b); + } + + template<> struct std::hash { + private: + // TODO is there already a hash combine function? or could this maybe be moved to a utility file? + template + inline void HashCombine(std::size_t& seed, const T& v) + { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + public: + std::size_t operator()(const BoxShadowGeometryInfo& in) const noexcept { + std::size_t result = std::size_t(849128392); + + for (const auto& v : in.background_border_mesh) { + for (const auto& w : v.indices) { + HashCombine(result, w); + } + for (const Vertex& w : v.vertices) { + HashCombine(result, (const uint32_t&)w.colour); + HashCombine(result, w.position.x); + HashCombine(result, w.position.y); + HashCombine(result, w.tex_coord.x); + HashCombine(result, w.tex_coord.y); + } + } + for (const auto& v : in.border_radius) { + HashCombine(result, v); + } + HashCombine(result, in.texture_dimensions.x); + HashCombine(result, in.texture_dimensions.y); + HashCombine(result, in.element_offset_in_texture.x); + HashCombine(result, in.element_offset_in_texture.y);\ + + static const auto fn_hash_render_box = [](const Rml::RenderBox&v) { + HashCombine(result, v.GetFillSize().x); + HashCombine(result, v.GetFillSize().y); + HashCombine(result, v.GetBorderOffset().x); + HashCombine(result, v.GetBorderOffset().y); + for (const auto& w : v.GetBorderRadius()) { + HashCombine(result, w); + } + for (const auto& w : v.GetBorderWidths()) { + HashCombine(result, w); + } + } + for (const auto& v : in.padding_render_boxes) { + fn_hash_render_box(v); + } + for (const auto& v : in.border_render_boxes) { + fn_hash_render_box(v); + } + for (const auto& v : in.shadow_list) { + HashCombine(result, v.blur_radius.number); + HashCombine(result, v.blur_radius.unit); + HashCombine(result, (const uint32_t&)v.color); + HashCombine(result, v.inset); + HashCombine(result, v.offset_x.number); + HashCombine(result, v.offset_x.unit); + HashCombine(result, v.offset_y.number); + HashCombine(result, v.offset_y.unit); + HashCombine(result, v.spread_distance.number); + HashCombine(result, v.spread_distance.unit); + } + return result; + } + }; + + struct ClipMaskGeometry { + ClipMaskOperation operation; + Geometry* geometry; + Vector2f absolute_offset; + const Matrix4f* transform; + }; + inline bool operator==(const ClipMaskGeometry& a, const ClipMaskGeometry& b) + { + return a.operation == b.operation && a.geometry == b.geometry && a.absolute_offset == b.absolute_offset && a.transform == b.transform; + } + inline bool operator!=(const ClipMaskGeometry& a, const ClipMaskGeometry& b) + { + return !(a == b); + } + using ClipMaskGeometryList = Vector; + + struct RenderState { + Rectanglei scissor_region = Rectanglei::MakeInvalid(); + ClipMaskGeometryList clip_mask_list; + Matrix4f transform = Matrix4f::Identity(); + }; + + /** + A wrapper over the render interface, which tracks its state and resources. + + All operations to be submitted to the render interface should go through this class. + */ + class RMLUICORE_API RenderManager : NonCopyMoveable { + public: + RenderManager(RenderInterface* render_interface); + ~RenderManager(); + + void PrepareRender(Vector2i dimensions); + void SetViewport(Vector2i dimensions); + Vector2i GetViewport() const; + + void DisableScissorRegion(); + void SetScissorRegion(Rectanglei region); + Rectanglei GetScissorRegion() const; + + void DisableClipMask(); + void SetClipMask(ClipMaskGeometryList clip_elements); + void SetClipMask(ClipMaskOperation operation, Geometry* geometry, Vector2f translation); + + void SetTransform(const Matrix4f* new_transform); + + // Retrieves the cached render state. If setting this state again, ensure the lifetimes of referenced objects are + // still valid. Possibly invalidating actions include destroying an element, or altering its transform property. + const RenderState& GetState() const { return state; } + void SetState(const RenderState& next); + void ResetState(); + + Geometry MakeGeometry(Mesh&& mesh); + + Texture LoadTexture(const String& source, const String& document_path = String()); + CallbackTexture MakeCallbackTexture(CallbackTextureFunction callback); + + CallbackTexture FindOrMakeBoxShadowCallbackTexture(const BoxShadowGeometryInfo& geometry_info, CallbackTextureFunction callback); + + CompiledFilter CompileFilter(const String& name, const Dictionary& parameters); + CompiledShader CompileShader(const String& name, const Dictionary& parameters); + + LayerHandle PushLayer(); + void CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span filters); + void PopLayer(); + + LayerHandle GetTopLayer() const; + LayerHandle GetNextLayer() const; + + CompiledFilter SaveLayerAsMaskImage(); + + private: + void ApplyClipMask(const ClipMaskGeometryList& clip_elements); + + StableVectorIndex InsertGeometry(Mesh&& mesh); + CompiledGeometryHandle GetCompiledGeometryHandle(StableVectorIndex index); + + void Render(const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader); + + void GetTextureSourceList(StringList& source_list) const; + const Mesh& GetMesh(const Geometry& geometry) const; + + bool ReleaseTexture(const String& texture_source); + void ReleaseAllTextures(); + void ReleaseAllCompiledGeometry(); + + void ReleaseResource(const CallbackTexture& texture); + Mesh ReleaseResource(const Geometry& geometry); + void ReleaseResource(const CompiledFilter& filter); + void ReleaseResource(const CompiledShader& shader); - class Geometry; - class CompiledFilter; - class CompiledShader; - class TextureDatabase; - class Texture; - class RenderManagerAccess; - - struct BoxShadowGeometryInfo { - CornerSizes border_radius; - Vector2i texture_dimensions; - Vector2f element_offset_in_texture; - Vector padding_render_boxes; - Vector border_render_boxes; - BoxShadowList shadow_list; - }; - inline bool operator==(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) - { - return a.border_radius == b.border_radius && a.texture_dimensions == b.texture_dimensions && a.element_offset_in_texture == b.element_offset_in_texture && - a.padding_render_boxes == b.padding_render_boxes && a.border_render_boxes == b.border_render_boxes && a.shadow_list == b.shadow_list; - } - inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) - { - return !(a == b); - } - - struct ClipMaskGeometry { - ClipMaskOperation operation; - Geometry* geometry; - Vector2f absolute_offset; - const Matrix4f* transform; - }; - inline bool operator==(const ClipMaskGeometry& a, const ClipMaskGeometry& b) - { - return a.operation == b.operation && a.geometry == b.geometry && a.absolute_offset == b.absolute_offset && a.transform == b.transform; - } - inline bool operator!=(const ClipMaskGeometry& a, const ClipMaskGeometry& b) - { - return !(a == b); - } - using ClipMaskGeometryList = Vector; - - struct RenderState { - Rectanglei scissor_region = Rectanglei::MakeInvalid(); - ClipMaskGeometryList clip_mask_list; - Matrix4f transform = Matrix4f::Identity(); - }; - - /** - A wrapper over the render interface, which tracks its state and resources. - - All operations to be submitted to the render interface should go through this class. - */ - class RMLUICORE_API RenderManager : NonCopyMoveable { - public: - RenderManager(RenderInterface* render_interface); - ~RenderManager(); - - void PrepareRender(Vector2i dimensions); - void SetViewport(Vector2i dimensions); - Vector2i GetViewport() const; - - void DisableScissorRegion(); - void SetScissorRegion(Rectanglei region); - Rectanglei GetScissorRegion() const; - - void DisableClipMask(); - void SetClipMask(ClipMaskGeometryList clip_elements); - void SetClipMask(ClipMaskOperation operation, Geometry* geometry, Vector2f translation); - - void SetTransform(const Matrix4f* new_transform); - - // Retrieves the cached render state. If setting this state again, ensure the lifetimes of referenced objects are - // still valid. Possibly invalidating actions include destroying an element, or altering its transform property. - const RenderState& GetState() const { return state; } - void SetState(const RenderState& next); - void ResetState(); - - Geometry MakeGeometry(Mesh&& mesh); - - Texture LoadTexture(const String& source, const String& document_path = String()); - CallbackTexture MakeCallbackTexture(CallbackTextureFunction callback); - - CallbackTexture FindOrMakeBoxShadowCallbackTexture(const BoxShadowGeometryInfo& geometry_info, CallbackTextureFunction callback); - - CompiledFilter CompileFilter(const String& name, const Dictionary& parameters); - CompiledShader CompileShader(const String& name, const Dictionary& parameters); - - LayerHandle PushLayer(); - void CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span filters); - void PopLayer(); - - LayerHandle GetTopLayer() const; - LayerHandle GetNextLayer() const; - - CompiledFilter SaveLayerAsMaskImage(); - - private: - void ApplyClipMask(const ClipMaskGeometryList& clip_elements); - - StableVectorIndex InsertGeometry(Mesh&& mesh); - CompiledGeometryHandle GetCompiledGeometryHandle(StableVectorIndex index); - - void Render(const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader); - - void GetTextureSourceList(StringList& source_list) const; - const Mesh& GetMesh(const Geometry& geometry) const; - - bool ReleaseTexture(const String& texture_source); - void ReleaseAllTextures(); - void ReleaseAllCompiledGeometry(); - - void ReleaseResource(const CallbackTexture& texture); - Mesh ReleaseResource(const Geometry& geometry); - void ReleaseResource(const CompiledFilter& filter); - void ReleaseResource(const CompiledShader& shader); + struct GeometryData { + Mesh mesh; + CompiledGeometryHandle handle = {}; + }; - struct GeometryData { - Mesh mesh; - CompiledGeometryHandle handle = {}; - }; + RenderInterface* render_interface = nullptr; - RenderInterface* render_interface = nullptr; + StableVector geometry_list; + UniquePtr texture_database; - StableVector geometry_list; - UniquePtr texture_database; + UnorderedMap box_shadow_cache; - UnorderedMap box_shadow_cache; + int compiled_filter_count = 0; + int compiled_shader_count = 0; - int compiled_filter_count = 0; - int compiled_shader_count = 0; + RenderState state; + Vector2i viewport_dimensions; - RenderState state; - Vector2i viewport_dimensions; + Vector render_stack; - Vector render_stack; - - friend class RenderManagerAccess; - }; + friend class RenderManagerAccess; + }; } // namespace Rml diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index 0f1738396..387a094ae 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -27,6 +27,7 @@ */ #include "GeometryBoxShadow.h" +#include "ElementBackgroundBorder.h" #include "../../Include/RmlUi/Core/Box.h" #include "../../Include/RmlUi/Core/CompiledFilterShader.h" #include "../../Include/RmlUi/Core/DecorationTypes.h" @@ -86,8 +87,8 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& texture_dimensions = Vector2i(texture_region.Size()); } - // Callback for generating the box-shadow texture. Using a callback ensures that the texture can be regenerated at any time, for example if the - // device loses its GPU context and the client calls Rml::ReleaseTextures(). + // Since we can reuse textures across multiple box shadows with the same properties, + // we need to copy the element's box shadow list and the background and border geometry. Vector padding_render_boxes{}; Vector border_render_boxes{}; @@ -96,8 +97,8 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& padding_render_boxes.push_back(element->GetRenderBox(BoxArea::Padding, i)); border_render_boxes.push_back(element->GetRenderBox(BoxArea::Border, i)); } - BoxShadowGeometryInfo geometry_info; + geometry_info.background_border_mesh = background_border_geometry.GetMesh(); geometry_info.border_radius = border_radius; geometry_info.texture_dimensions = texture_dimensions; geometry_info.element_offset_in_texture = element_offset_in_texture; @@ -105,7 +106,9 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& geometry_info.border_render_boxes = border_render_boxes; geometry_info.shadow_list = shadow_list; - auto texture_callback = [&background_border_geometry, border_radius, + // Callback for generating the box-shadow texture. Using a callback ensures that the texture can be regenerated at any time, for example if the + // device loses its GPU context and the client calls Rml::ReleaseTextures(). + auto texture_callback = [background_border_mesh = geometry_info.background_border_mesh, border_radius, texture_dimensions, element_offset_in_texture, padding_render_boxes = std::move(padding_render_boxes), num_boxes = element->GetNumBoxes(), @@ -158,7 +161,7 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& } render_manager.PushLayer(); - + Rml::Geometry background_border_geometry = render_manager.MakeGeometry(std::move(background_border_mesh)); background_border_geometry.Render(element_offset_in_texture); for (int shadow_index = (int)shadow_list.size() - 1; shadow_index >= 0; shadow_index--) From cf9e1637fd0d4ffe27cc9917ad3e3dbce72ec4be Mon Sep 17 00:00:00 2001 From: Tristan Fransen Date: Wed, 13 Aug 2025 14:35:22 +0200 Subject: [PATCH 03/12] fixed several compiler errors, using shared pointers, added cleanup function, replaced Rml::Mesh in cache with other properties --- Include/RmlUi/Core/RenderBox.h | 9 +- Include/RmlUi/Core/RenderManager.h | 385 ++++++++++++------------ Source/Core/ElementBackgroundBorder.cpp | 10 +- Source/Core/ElementBackgroundBorder.h | 3 +- Source/Core/GeometryBoxShadow.cpp | 27 +- Source/Core/GeometryBoxShadow.h | 12 +- Source/Core/RenderManager.cpp | 37 ++- 7 files changed, 263 insertions(+), 220 deletions(-) diff --git a/Include/RmlUi/Core/RenderBox.h b/Include/RmlUi/Core/RenderBox.h index 6d808a1fc..7f356f27a 100644 --- a/Include/RmlUi/Core/RenderBox.h +++ b/Include/RmlUi/Core/RenderBox.h @@ -76,6 +76,13 @@ class RenderBox { EdgeSizes border_widths; CornerSizes border_radius; }; - +inline bool operator==(const RenderBox& a, const RenderBox& b) +{ + return a.GetFillSize() == b.GetFillSize() && a.GetBorderOffset() == b.GetBorderOffset() && a.GetBorderWidths() == b.GetBorderWidths() && a.GetBorderRadius() == b.GetBorderRadius(); +} +inline bool operator!=(const RenderBox& a, const RenderBox& b) +{ + return !(a == b); +} } // namespace Rml #endif diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h index 2ff2a346a..ebbb79e08 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -30,218 +30,223 @@ #define RMLUI_CORE_RENDERMANAGER_H #include "CallbackTexture.h" +#include "DecorationTypes.h" #include "Mesh.h" #include "RenderInterface.h" #include "StableVector.h" #include "Types.h" namespace Rml { - class Geometry; - class CompiledFilter; - class CompiledShader; - class TextureDatabase; - class Texture; - class RenderManagerAccess; - - struct BoxShadowGeometryInfo { - Mesh background_border_mesh; - CornerSizes border_radius; - Vector2i texture_dimensions; - Vector2f element_offset_in_texture; - Vector padding_render_boxes; - Vector border_render_boxes; - BoxShadowList shadow_list; - }; - inline bool operator==(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) - { - return a.background_border_mesh==b.background_border_mesh && a.border_radius == b.border_radius && a.texture_dimensions == b.texture_dimensions && a.element_offset_in_texture == b.element_offset_in_texture && - a.padding_render_boxes == b.padding_render_boxes && a.border_render_boxes == b.border_render_boxes && a.shadow_list == b.shadow_list; - } - inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) +using RenderBoxList = Vector; +struct BoxShadowGeometryInfo { + ColourbPremultiplied background_color; + Array border_colors; + CornerSizes border_radius; + Vector2i texture_dimensions; + Vector2f element_offset_in_texture; + RenderBoxList padding_render_boxes; + RenderBoxList border_render_boxes; + BoxShadowList shadow_list; +}; +inline bool operator==(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) +{ + return a.background_color == b.background_color && a.border_colors == b.border_colors && a.border_radius == b.border_radius && a.texture_dimensions == b.texture_dimensions && + a.element_offset_in_texture == b.element_offset_in_texture && a.padding_render_boxes == b.padding_render_boxes && a.border_render_boxes == b.border_render_boxes && a.shadow_list == b.shadow_list; +} +inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) +{ + return !(a == b); +} +} + +// FIXME, shouldn't this be using Rml::Hash<>? +template<> struct std::hash { +private: + // TODO is there already a hash combine function? or could this maybe be moved to a utility file? + template + static inline void HashCombine(std::size_t& seed, const T& v) { - return !(a == b); + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } - - template<> struct std::hash { - private: - // TODO is there already a hash combine function? or could this maybe be moved to a utility file? - template - inline void HashCombine(std::size_t& seed, const T& v) - { - std::hash hasher; - seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +public: + std::size_t operator()(const Rml::BoxShadowGeometryInfo& in) const noexcept { + std::size_t result = std::size_t(849128392); + using namespace Rml; + + HashCombine(result, reinterpret_cast(in.background_color)); + for (const auto& v : in.border_colors) { + HashCombine(result, reinterpret_cast(v)); } - public: - std::size_t operator()(const BoxShadowGeometryInfo& in) const noexcept { - std::size_t result = std::size_t(849128392); - - for (const auto& v : in.background_border_mesh) { - for (const auto& w : v.indices) { - HashCombine(result, w); - } - for (const Vertex& w : v.vertices) { - HashCombine(result, (const uint32_t&)w.colour); - HashCombine(result, w.position.x); - HashCombine(result, w.position.y); - HashCombine(result, w.tex_coord.x); - HashCombine(result, w.tex_coord.y); - } - } - for (const auto& v : in.border_radius) { - HashCombine(result, v); - } - HashCombine(result, in.texture_dimensions.x); - HashCombine(result, in.texture_dimensions.y); - HashCombine(result, in.element_offset_in_texture.x); - HashCombine(result, in.element_offset_in_texture.y);\ - - static const auto fn_hash_render_box = [](const Rml::RenderBox&v) { - HashCombine(result, v.GetFillSize().x); - HashCombine(result, v.GetFillSize().y); - HashCombine(result, v.GetBorderOffset().x); - HashCombine(result, v.GetBorderOffset().y); - for (const auto& w : v.GetBorderRadius()) { - HashCombine(result, w); - } - for (const auto& w : v.GetBorderWidths()) { - HashCombine(result, w); - } - } - for (const auto& v : in.padding_render_boxes) { - fn_hash_render_box(v); - } - for (const auto& v : in.border_render_boxes) { - fn_hash_render_box(v); + + for (const auto& v : in.border_radius) { + HashCombine(result, v); + } + HashCombine(result, in.texture_dimensions.x); + HashCombine(result, in.texture_dimensions.y); + HashCombine(result, in.element_offset_in_texture.x); + HashCombine(result, in.element_offset_in_texture.y); + + static const auto fn_hash_render_box = [](std::size_t& result, const Rml::RenderBox& v) { + HashCombine(result, v.GetFillSize().x); + HashCombine(result, v.GetFillSize().y); + HashCombine(result, v.GetBorderOffset().x); + HashCombine(result, v.GetBorderOffset().y); + for (const auto& w : v.GetBorderRadius()) { + HashCombine(result, w); } - for (const auto& v : in.shadow_list) { - HashCombine(result, v.blur_radius.number); - HashCombine(result, v.blur_radius.unit); - HashCombine(result, (const uint32_t&)v.color); - HashCombine(result, v.inset); - HashCombine(result, v.offset_x.number); - HashCombine(result, v.offset_x.unit); - HashCombine(result, v.offset_y.number); - HashCombine(result, v.offset_y.unit); - HashCombine(result, v.spread_distance.number); - HashCombine(result, v.spread_distance.unit); + for (const auto& w : v.GetBorderWidths()) { + HashCombine(result, w); } - return result; + }; + for (const auto& v : in.padding_render_boxes) { + fn_hash_render_box(result, v); } - }; - - struct ClipMaskGeometry { - ClipMaskOperation operation; - Geometry* geometry; - Vector2f absolute_offset; - const Matrix4f* transform; - }; - inline bool operator==(const ClipMaskGeometry& a, const ClipMaskGeometry& b) - { - return a.operation == b.operation && a.geometry == b.geometry && a.absolute_offset == b.absolute_offset && a.transform == b.transform; - } - inline bool operator!=(const ClipMaskGeometry& a, const ClipMaskGeometry& b) - { - return !(a == b); + for (const auto& v : in.border_render_boxes) { + fn_hash_render_box(result, v); + } + for (const auto& v : in.shadow_list) { + HashCombine(result, v.blur_radius.number); + HashCombine(result, v.blur_radius.unit); + HashCombine(result, reinterpret_cast(v.color)); + HashCombine(result, v.inset); + HashCombine(result, v.offset_x.number); + HashCombine(result, v.offset_x.unit); + HashCombine(result, v.offset_y.number); + HashCombine(result, v.offset_y.unit); + HashCombine(result, v.spread_distance.number); + HashCombine(result, v.spread_distance.unit); + } + return result; } - using ClipMaskGeometryList = Vector; +}; - struct RenderState { - Rectanglei scissor_region = Rectanglei::MakeInvalid(); - ClipMaskGeometryList clip_mask_list; - Matrix4f transform = Matrix4f::Identity(); +namespace Rml { +class Geometry; +class CompiledFilter; +class CompiledShader; +class TextureDatabase; +class Texture; +class RenderManagerAccess; + +struct ClipMaskGeometry { + ClipMaskOperation operation; + Geometry* geometry; + Vector2f absolute_offset; + const Matrix4f* transform; +}; +inline bool operator==(const ClipMaskGeometry& a, const ClipMaskGeometry& b) +{ + return a.operation == b.operation && a.geometry == b.geometry && a.absolute_offset == b.absolute_offset && a.transform == b.transform; +} +inline bool operator!=(const ClipMaskGeometry& a, const ClipMaskGeometry& b) +{ + return !(a == b); +} +using ClipMaskGeometryList = Vector; + +struct RenderState { + Rectanglei scissor_region = Rectanglei::MakeInvalid(); + ClipMaskGeometryList clip_mask_list; + Matrix4f transform = Matrix4f::Identity(); +}; + +/** + A wrapper over the render interface, which tracks its state and resources. + + All operations to be submitted to the render interface should go through this class. + */ +class RMLUICORE_API RenderManager : NonCopyMoveable { +public: + RenderManager(RenderInterface* render_interface); + ~RenderManager(); + + void PrepareRender(Vector2i dimensions); + void SetViewport(Vector2i dimensions); + Vector2i GetViewport() const; + + void DisableScissorRegion(); + void SetScissorRegion(Rectanglei region); + Rectanglei GetScissorRegion() const; + + void DisableClipMask(); + void SetClipMask(ClipMaskGeometryList clip_elements); + void SetClipMask(ClipMaskOperation operation, Geometry* geometry, Vector2f translation); + + void SetTransform(const Matrix4f* new_transform); + + // Retrieves the cached render state. If setting this state again, ensure the lifetimes of referenced objects are + // still valid. Possibly invalidating actions include destroying an element, or altering its transform property. + const RenderState& GetState() const { return state; } + void SetState(const RenderState& next); + void ResetState(); + + Geometry MakeGeometry(Mesh&& mesh); + + Texture LoadTexture(const String& source, const String& document_path = String()); + CallbackTexture MakeCallbackTexture(CallbackTextureFunction callback); + + SharedPtr FindOrMakeBoxShadowCallbackTexture(const BoxShadowGeometryInfo& geometry_info, CallbackTextureFunction callback); + + CompiledFilter CompileFilter(const String& name, const Dictionary& parameters); + CompiledShader CompileShader(const String& name, const Dictionary& parameters); + + LayerHandle PushLayer(); + void CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span filters); + void PopLayer(); + + LayerHandle GetTopLayer() const; + LayerHandle GetNextLayer() const; + + CompiledFilter SaveLayerAsMaskImage(); + +private: + void ApplyClipMask(const ClipMaskGeometryList& clip_elements); + + StableVectorIndex InsertGeometry(Mesh&& mesh); + CompiledGeometryHandle GetCompiledGeometryHandle(StableVectorIndex index); + + void Render(const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader); + + void GetTextureSourceList(StringList& source_list) const; + const Mesh& GetMesh(const Geometry& geometry) const; + + bool ReleaseTexture(const String& texture_source); + void ReleaseAllTextures(); + void ReleaseAllCompiledGeometry(); + + void ReleaseResource(const CallbackTexture& texture); + Mesh ReleaseResource(const Geometry& geometry); + void ReleaseResource(const CompiledFilter& filter); + void ReleaseResource(const CompiledShader& shader); + + // TODO: better way to autonomously release the box shadow cache? + // References are needed to texture, and we need to be able to ref count it, so storing a WeakPtr<> will not work + // (cannot create a SharedPtr<> out of it + void CleanupDeadBoxShadowCache(); + + struct GeometryData { + Mesh mesh; + CompiledGeometryHandle handle = {}; }; - /** - A wrapper over the render interface, which tracks its state and resources. - - All operations to be submitted to the render interface should go through this class. - */ - class RMLUICORE_API RenderManager : NonCopyMoveable { - public: - RenderManager(RenderInterface* render_interface); - ~RenderManager(); - - void PrepareRender(Vector2i dimensions); - void SetViewport(Vector2i dimensions); - Vector2i GetViewport() const; + RenderInterface* render_interface = nullptr; - void DisableScissorRegion(); - void SetScissorRegion(Rectanglei region); - Rectanglei GetScissorRegion() const; + StableVector geometry_list; + UniquePtr texture_database; - void DisableClipMask(); - void SetClipMask(ClipMaskGeometryList clip_elements); - void SetClipMask(ClipMaskOperation operation, Geometry* geometry, Vector2f translation); + UnorderedMap> box_shadow_cache; - void SetTransform(const Matrix4f* new_transform); + int compiled_filter_count = 0; + int compiled_shader_count = 0; - // Retrieves the cached render state. If setting this state again, ensure the lifetimes of referenced objects are - // still valid. Possibly invalidating actions include destroying an element, or altering its transform property. - const RenderState& GetState() const { return state; } - void SetState(const RenderState& next); - void ResetState(); + RenderState state; + Vector2i viewport_dimensions; - Geometry MakeGeometry(Mesh&& mesh); + Vector render_stack; - Texture LoadTexture(const String& source, const String& document_path = String()); - CallbackTexture MakeCallbackTexture(CallbackTextureFunction callback); - - CallbackTexture FindOrMakeBoxShadowCallbackTexture(const BoxShadowGeometryInfo& geometry_info, CallbackTextureFunction callback); - - CompiledFilter CompileFilter(const String& name, const Dictionary& parameters); - CompiledShader CompileShader(const String& name, const Dictionary& parameters); - - LayerHandle PushLayer(); - void CompositeLayers(LayerHandle source, LayerHandle destination, BlendMode blend_mode, Span filters); - void PopLayer(); - - LayerHandle GetTopLayer() const; - LayerHandle GetNextLayer() const; - - CompiledFilter SaveLayerAsMaskImage(); - - private: - void ApplyClipMask(const ClipMaskGeometryList& clip_elements); - - StableVectorIndex InsertGeometry(Mesh&& mesh); - CompiledGeometryHandle GetCompiledGeometryHandle(StableVectorIndex index); - - void Render(const Geometry& geometry, Vector2f translation, Texture texture, const CompiledShader& shader); - - void GetTextureSourceList(StringList& source_list) const; - const Mesh& GetMesh(const Geometry& geometry) const; - - bool ReleaseTexture(const String& texture_source); - void ReleaseAllTextures(); - void ReleaseAllCompiledGeometry(); - - void ReleaseResource(const CallbackTexture& texture); - Mesh ReleaseResource(const Geometry& geometry); - void ReleaseResource(const CompiledFilter& filter); - void ReleaseResource(const CompiledShader& shader); - - struct GeometryData { - Mesh mesh; - CompiledGeometryHandle handle = {}; - }; - - RenderInterface* render_interface = nullptr; - - StableVector geometry_list; - UniquePtr texture_database; - - UnorderedMap box_shadow_cache; - - int compiled_filter_count = 0; - int compiled_shader_count = 0; - - RenderState state; - Vector2i viewport_dimensions; - - Vector render_stack; - - friend class RenderManagerAccess; - }; + friend class RenderManagerAccess; +}; } // namespace Rml diff --git a/Source/Core/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index 621ec626d..e7721561f 100644 --- a/Source/Core/ElementBackgroundBorder.cpp +++ b/Source/Core/ElementBackgroundBorder.cpp @@ -58,7 +58,7 @@ void ElementBackgroundBorder::Render(Element* element) Background* shadow = GetBackground(BackgroundType::BoxShadow); if (shadow && shadow->geometry) - shadow->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), shadow->texture); + shadow->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), *shadow->texture); else if (Background* background = GetBackground(BackgroundType::BackgroundBorder)) { auto offset = element->GetAbsoluteOffset(BoxArea::Border); @@ -139,7 +139,7 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) }; ColourbPremultiplied background_color = ConvertColor(computed.background_color()); - ColourbPremultiplied border_colors[4] = { + Array border_colors = { ConvertColor(computed.border_top_color()), ConvertColor(computed.border_right_color()), ConvertColor(computed.border_bottom_color()), @@ -151,7 +151,7 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); for (int i = 0; i < element->GetNumBoxes(); i++) - MeshUtilities::GenerateBackgroundBorder(mesh, element->GetRenderBox(BoxArea::Padding, i), background_color, border_colors); + MeshUtilities::GenerateBackgroundBorder(mesh, element->GetRenderBox(BoxArea::Padding, i), background_color, border_colors.data()); geometry = render_manager->MakeGeometry(std::move(mesh)); @@ -166,10 +166,10 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) // Generate the geometry for the box-shadow texture. Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadow); Geometry& shadow_geometry = shadow_background.geometry; - CallbackTexture& shadow_texture = shadow_background.texture; + SharedPtr& shadow_texture = shadow_background.texture; GeometryBoxShadow::Generate(shadow_geometry, shadow_texture, *render_manager, element, background_border_geometry, std::move(shadow_list), - border_radius, computed.opacity()); + border_radius, background_color, border_colors, computed.opacity()); } } diff --git a/Source/Core/ElementBackgroundBorder.h b/Source/Core/ElementBackgroundBorder.h index d93038606..c6c5a8fa5 100644 --- a/Source/Core/ElementBackgroundBorder.h +++ b/Source/Core/ElementBackgroundBorder.h @@ -38,7 +38,6 @@ namespace Rml { class ElementBackgroundBorder { public: ElementBackgroundBorder(); - void Render(Element* element); void DirtyBackground(); @@ -50,7 +49,7 @@ class ElementBackgroundBorder { enum class BackgroundType { BackgroundBorder, BoxShadow, ClipBorder, ClipPadding, ClipContent, Count }; struct Background { Geometry geometry; - CallbackTexture texture; + SharedPtr texture; }; Background* GetBackground(BackgroundType type); diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index 387a094ae..3c989adfd 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -39,8 +39,9 @@ namespace Rml { -void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& out_shadow_texture, RenderManager& render_manager, Element* element, - Geometry& background_border_geometry, BoxShadowList shadow_list, const CornerSizes border_radius, const float opacity) +void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, SharedPtr& out_shadow_texture, RenderManager& render_manager, + Element* element, const Geometry& background_border_geometry, BoxShadowList shadow_list, CornerSizes border_radius, + ColourbPremultiplied background_color, Array border_colors, float opacity) { // Find the box-shadow texture dimension and offset required to cover all box-shadows and element boxes combined. Vector2f element_offset_in_texture; @@ -89,16 +90,20 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& // Since we can reuse textures across multiple box shadows with the same properties, // we need to copy the element's box shadow list and the background and border geometry. - Vector padding_render_boxes{}; - Vector border_render_boxes{}; + RenderBoxList padding_render_boxes{}; + RenderBoxList border_render_boxes{}; for (int i = 0; i < element->GetNumBoxes(); i++) { padding_render_boxes.push_back(element->GetRenderBox(BoxArea::Padding, i)); border_render_boxes.push_back(element->GetRenderBox(BoxArea::Border, i)); } + + // Finally, create cache information + // We do not need to copy over the Rml::Geometry as the other properties here already implicitly define them. BoxShadowGeometryInfo geometry_info; - geometry_info.background_border_mesh = background_border_geometry.GetMesh(); + geometry_info.background_color = background_color; + geometry_info.border_colors = border_colors; geometry_info.border_radius = border_radius; geometry_info.texture_dimensions = texture_dimensions; geometry_info.element_offset_in_texture = element_offset_in_texture; @@ -106,16 +111,17 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& geometry_info.border_render_boxes = border_render_boxes; geometry_info.shadow_list = shadow_list; + Rml::Mesh background_border_mesh = background_border_geometry.GetMesh(); + // Callback for generating the box-shadow texture. Using a callback ensures that the texture can be regenerated at any time, for example if the // device loses its GPU context and the client calls Rml::ReleaseTextures(). - auto texture_callback = [background_border_mesh = geometry_info.background_border_mesh, border_radius, - texture_dimensions, element_offset_in_texture, + auto texture_callback = [border_radius, texture_dimensions, element_offset_in_texture, num_boxes = element->GetNumBoxes(), + background_border_mesh = std::move(background_border_mesh), padding_render_boxes = std::move(padding_render_boxes), - num_boxes = element->GetNumBoxes(), border_render_boxes = std::move(border_render_boxes), shadow_list = std::move(shadow_list)](const CallbackTextureInterface& texture_interface) -> bool { RenderManager& render_manager = texture_interface.GetRenderManager(); - + Mesh mesh_padding; // Render geometry for inner box-shadow. Mesh mesh_padding_border; // Clipping mask for outer box-shadow. @@ -161,8 +167,9 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& } render_manager.PushLayer(); - Rml::Geometry background_border_geometry = render_manager.MakeGeometry(std::move(background_border_mesh)); + Rml::Geometry background_border_geometry = render_manager.MakeGeometry(Rml::Mesh(background_border_mesh)); background_border_geometry.Render(element_offset_in_texture); + background_border_geometry.Release(); for (int shadow_index = (int)shadow_list.size() - 1; shadow_index >= 0; shadow_index--) { diff --git a/Source/Core/GeometryBoxShadow.h b/Source/Core/GeometryBoxShadow.h index 83713848a..aa9de2319 100644 --- a/Source/Core/GeometryBoxShadow.h +++ b/Source/Core/GeometryBoxShadow.h @@ -42,16 +42,18 @@ class GeometryBoxShadow { public: /// Generate the texture and geometry for a box shadow. /// @param[out] out_shadow_geometry The target geometry. - /// @param[out] out_shadow_texture The target texture, assumes pointer stability during the lifetime of the shadow geometry. + /// @param[out] out_shadow_texture The target texture, assumes pointer stability during the lifetime of the shadow geometry. This may also be shared between /// @param[in] render_manager The render manager to generate the shadow for. /// @param[in] element The element to generate the shadow for. - /// @param[in] background_border_geometry The geometry of the background and border, assumed to already have been generated. Assumes pointer - /// stability during the lifetime of the shadow geometry. + /// @param[in] background_border_geometry The geometry of the background and border. Used to avoid recomputing the mesh from the given render boxes and background/border colours /// @param[in] shadow_list The list of box-shadows to generate. /// @param[in] border_radius The border radius of the element. + /// @param[in] background_color The background colour of the element. + /// @param[in] border_colors The border colours of the element. /// @param[in] opacity The opacity of the element. - static void Generate(Geometry& out_shadow_geometry, CallbackTexture& out_shadow_texture, RenderManager& render_manager, Element* element, - Geometry& background_border_geometry, BoxShadowList shadow_list, CornerSizes border_radius, float opacity); + static void Generate(Geometry& out_shadow_geometry, SharedPtr& out_shadow_texture, RenderManager& render_manager, + Element* element, const Geometry& background_border_geometry, BoxShadowList shadow_list, CornerSizes border_radius, + ColourbPremultiplied background_color, Array border_colors, float opacity); }; } // namespace Rml diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp index d83a3c235..d9b642a4e 100644 --- a/Source/Core/RenderManager.cpp +++ b/Source/Core/RenderManager.cpp @@ -80,6 +80,9 @@ void RenderManager::PrepareRender(Vector2i dimensions) #endif SetViewport(dimensions); + + // is this a good place to call cleanup? + CleanupDeadBoxShadowCache(); } void RenderManager::SetViewport(Vector2i dimensions) @@ -113,17 +116,21 @@ CallbackTexture RenderManager::MakeCallbackTexture(CallbackTextureFunction callb return CallbackTexture(this, texture_database->callback_database.CreateTexture(std::move(callback))); } -CallbackTexture RenderManager::FindOrMakeBoxShadowCallbackTexture(const BoxShadowGeometryInfo& geometry_info, CallbackTextureFunction callback) +SharedPtr RenderManager::FindOrMakeBoxShadowCallbackTexture(const BoxShadowGeometryInfo& geometry_info, CallbackTextureFunction callback) { - auto it = box_shadow_cache.find(geometry_info); - if (it != box_shadow_cache.end()) { - return it->second; + { + auto it = box_shadow_cache.find(geometry_info); + if (it != box_shadow_cache.end()) { + auto& ref_tex = it->second; + return ref_tex; + } } - CallbackTexture& cb = box_shadow_cache[geometry_info]; - cb = MakeCallbackTexture(callback); - return cb; + SharedPtr& ref_tex = box_shadow_cache[geometry_info]; + ref_tex = SharedPtr(new CallbackTexture(this, texture_database->callback_database.CreateTexture(std::move(callback)))); + return ref_tex; } + void RenderManager::DisableScissorRegion() { SetScissorRegion(Rectanglei::MakeInvalid()); @@ -406,4 +413,20 @@ void RenderManager::ReleaseResource(const CompiledShader& shader) compiled_shader_count -= 1; } +void RenderManager::CleanupDeadBoxShadowCache() +{ + Vector destroyGeometries(0); + for (auto& kv : box_shadow_cache) { + RMLUI_ASSERT(kv.second && "This should never be null!"); + if (kv.second.unique()) { + kv.second->Release(); + destroyGeometries.emplace_back(std::move(kv.first)); + } + } + for (auto& info : destroyGeometries) { + box_shadow_cache.erase(info); + } +} + + } // namespace Rml From 0bd3d6bec7e9ac7fff00ce3e0f16e45e5b76bfc8 Mon Sep 17 00:00:00 2001 From: ZilverBlade <90721817+ZilverBlade@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:30:04 +0200 Subject: [PATCH 04/12] replaced HashCombine with existing implementation, and fixed build errors on certain platforms --- Include/RmlUi/Core/RenderManager.h | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h index ebbb79e08..1ad0d3034 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -32,9 +32,11 @@ #include "CallbackTexture.h" #include "DecorationTypes.h" #include "Mesh.h" +#include "RenderBox.h" #include "RenderInterface.h" #include "StableVector.h" #include "Types.h" +#include "Utilities.h" namespace Rml { using RenderBoxList = Vector; @@ -59,20 +61,12 @@ inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryIn } } -// FIXME, shouldn't this be using Rml::Hash<>? -template<> struct std::hash { -private: - // TODO is there already a hash combine function? or could this maybe be moved to a utility file? - template - static inline void HashCombine(std::size_t& seed, const T& v) - { - std::hash hasher; - seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - } -public: - std::size_t operator()(const Rml::BoxShadowGeometryInfo& in) const noexcept { - std::size_t result = std::size_t(849128392); +template<> struct ::std::hash<::Rml::BoxShadowGeometryInfo> { + ::std::size_t operator()(const ::Rml::BoxShadowGeometryInfo& in) const noexcept { using namespace Rml; + using namespace Rml::Utilities; + using namespace std; + size_t result = size_t(849128392); HashCombine(result, reinterpret_cast(in.background_color)); for (const auto& v : in.border_colors) { @@ -87,7 +81,7 @@ template<> struct std::hash { HashCombine(result, in.element_offset_in_texture.x); HashCombine(result, in.element_offset_in_texture.y); - static const auto fn_hash_render_box = [](std::size_t& result, const Rml::RenderBox& v) { + static const auto fn_hash_render_box = [](size_t& result, const RenderBox& v) { HashCombine(result, v.GetFillSize().x); HashCombine(result, v.GetFillSize().y); HashCombine(result, v.GetBorderOffset().x); From ec8990ec5d5d8ce6f03df187bcfd230460a17983 Mon Sep 17 00:00:00 2001 From: ZilverBlade <90721817+ZilverBlade@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:34:04 +0200 Subject: [PATCH 05/12] fixed global quantifier errors --- Include/RmlUi/Core/RenderManager.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h index 1ad0d3034..5842c2734 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -61,8 +61,8 @@ inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryIn } } -template<> struct ::std::hash<::Rml::BoxShadowGeometryInfo> { - ::std::size_t operator()(const ::Rml::BoxShadowGeometryInfo& in) const noexcept { +template<> struct std::hash { + std::size_t operator()(const Rml::BoxShadowGeometryInfo& in) const noexcept { using namespace Rml; using namespace Rml::Utilities; using namespace std; From efe47a5b560c76a8d2d38b9e8c2fcbc585a7a5ca Mon Sep 17 00:00:00 2001 From: Tristan Fransen Date: Sun, 17 Aug 2025 20:30:15 +0200 Subject: [PATCH 06/12] Comment cleanup and fixed resource leak error --- Include/RmlUi/Core/RenderManager.h | 4 ++-- Source/Core/RenderManager.cpp | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h index 5842c2734..dcc6ad701 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -215,8 +215,8 @@ class RMLUICORE_API RenderManager : NonCopyMoveable { void ReleaseResource(const CompiledShader& shader); // TODO: better way to autonomously release the box shadow cache? - // References are needed to texture, and we need to be able to ref count it, so storing a WeakPtr<> will not work - // (cannot create a SharedPtr<> out of it + // References are needed to texture, and we need to be able to ref count it. + // Another possibility is making a custom SharedPtr<> class and allow us to manually reduce ref counts. void CleanupDeadBoxShadowCache(); struct GeometryData { diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp index d9b642a4e..a76a5e8f1 100644 --- a/Source/Core/RenderManager.cpp +++ b/Source/Core/RenderManager.cpp @@ -46,6 +46,9 @@ RenderManager::RenderManager(RenderInterface* render_interface) : render_interfa RenderManager::~RenderManager() { + // clear cache, since the render manager always has at least 1 reference if the texture hasn't been freed yet. + // All references should be equal to 1, if they are more, then they haven't been freed properly + CleanupDeadBoxShadowCache(); struct ResourceCount { const char* name; int count; From efddb5c31b9e65d6f489d6c395e6ca5c4f3cfc4e Mon Sep 17 00:00:00 2001 From: ZilverBlade Date: Wed, 17 Sep 2025 12:06:58 +0200 Subject: [PATCH 07/12] refactored the box shadow cache (Currently broken!) to be more similar to SVG cache, and hid away the box shadow stuff from the render manager, and ran clang format --- Include/RmlUi/Core/DecorationTypes.h | 21 ++++ Include/RmlUi/Core/NumericValue.h | 15 ++- Include/RmlUi/Core/RenderBox.h | 29 ++++- Include/RmlUi/Core/RenderManager.h | 86 -------------- Include/RmlUi/Core/Types.h | 33 ++++++ Include/RmlUi/Core/Unit.h | 12 ++ Source/Core/BoxShadowCache.cpp | 144 ++++++++++++++++++++++++ Source/Core/BoxShadowCache.h | 59 ++++++++++ Source/Core/ElementBackgroundBorder.cpp | 13 +-- Source/Core/ElementBackgroundBorder.h | 5 +- Source/Core/GeometryBoxShadow.cpp | 78 ++++++------- Source/Core/GeometryBoxShadow.h | 82 ++++++++++++-- Source/Core/RenderManager.cpp | 37 ------ 13 files changed, 426 insertions(+), 188 deletions(-) create mode 100644 Source/Core/BoxShadowCache.cpp create mode 100644 Source/Core/BoxShadowCache.h diff --git a/Include/RmlUi/Core/DecorationTypes.h b/Include/RmlUi/Core/DecorationTypes.h index 3098db192..9018c46ba 100644 --- a/Include/RmlUi/Core/DecorationTypes.h +++ b/Include/RmlUi/Core/DecorationTypes.h @@ -31,6 +31,7 @@ #include "NumericValue.h" #include "Types.h" +#include "Utilities.h" namespace Rml { @@ -65,4 +66,24 @@ inline bool operator!=(const BoxShadow& a, const BoxShadow& b) } } // namespace Rml + +namespace std { +template <> +struct hash<::Rml::BoxShadow> { + size_t operator()(const ::Rml::BoxShadow& s) const noexcept + { + using namespace ::Rml; + using namespace ::Rml::Utilities; + size_t seed = std::hash{}(s.color); + + HashCombine(seed, s.offset_x); + HashCombine(seed, s.offset_y); + HashCombine(seed, s.blur_radius); + HashCombine(seed, s.spread_distance); + HashCombine(seed, s.inset); + return seed; + } +}; + +} // namespace std #endif diff --git a/Include/RmlUi/Core/NumericValue.h b/Include/RmlUi/Core/NumericValue.h index 76073e0ea..1e0fabd59 100644 --- a/Include/RmlUi/Core/NumericValue.h +++ b/Include/RmlUi/Core/NumericValue.h @@ -52,5 +52,18 @@ inline bool operator!=(const NumericValue& a, const NumericValue& b) } } // namespace Rml - +namespace std { +template <> +struct hash<::Rml::NumericValue> { + // FIXME: should the hash function NumericValue not care about unit (as long as resolved unit is the same?) + // Or should it be a regular hash combine? + size_t operator()(const ::Rml::NumericValue& v) const noexcept + { + using namespace ::Rml; + size_t h1 = hash{}(v.number); + size_t h2 = hash{}(v.unit); + return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); + } +}; +} // namespace std #endif diff --git a/Include/RmlUi/Core/RenderBox.h b/Include/RmlUi/Core/RenderBox.h index 7f356f27a..e735ce558 100644 --- a/Include/RmlUi/Core/RenderBox.h +++ b/Include/RmlUi/Core/RenderBox.h @@ -30,6 +30,7 @@ #define RMLUI_CORE_RENDERBOX_H #include "Types.h" +#include "Utilities.h" namespace Rml { @@ -78,11 +79,37 @@ class RenderBox { }; inline bool operator==(const RenderBox& a, const RenderBox& b) { - return a.GetFillSize() == b.GetFillSize() && a.GetBorderOffset() == b.GetBorderOffset() && a.GetBorderWidths() == b.GetBorderWidths() && a.GetBorderRadius() == b.GetBorderRadius(); + return a.GetFillSize() == b.GetFillSize() && a.GetBorderOffset() == b.GetBorderOffset() && a.GetBorderWidths() == b.GetBorderWidths() && + a.GetBorderRadius() == b.GetBorderRadius(); } inline bool operator!=(const RenderBox& a, const RenderBox& b) { return !(a == b); } } // namespace Rml + +namespace std { +template <> +struct hash<::Rml::RenderBox> { + + size_t operator()(const ::Rml::RenderBox& box) const noexcept + { + using namespace ::Rml::Utilities; + static auto HashArray4 = [](const ::Rml::Array& arr) -> size_t { + size_t seed = 0; + for (const auto& v : arr) + HashCombine(seed, v); + return seed; + }; + + size_t seed = 0; + HashCombine(seed, box.GetFillSize()); + HashCombine(seed, box.GetBorderOffset()); + HashCombine(seed, HashArray4(box.GetBorderRadius())); + HashCombine(seed, HashArray4(box.GetBorderWidths())); + return seed; + } +}; +} // namespace std + #endif diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h index dcc6ad701..e0ede0d94 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -38,83 +38,6 @@ #include "Types.h" #include "Utilities.h" -namespace Rml { -using RenderBoxList = Vector; -struct BoxShadowGeometryInfo { - ColourbPremultiplied background_color; - Array border_colors; - CornerSizes border_radius; - Vector2i texture_dimensions; - Vector2f element_offset_in_texture; - RenderBoxList padding_render_boxes; - RenderBoxList border_render_boxes; - BoxShadowList shadow_list; -}; -inline bool operator==(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) -{ - return a.background_color == b.background_color && a.border_colors == b.border_colors && a.border_radius == b.border_radius && a.texture_dimensions == b.texture_dimensions && - a.element_offset_in_texture == b.element_offset_in_texture && a.padding_render_boxes == b.padding_render_boxes && a.border_render_boxes == b.border_render_boxes && a.shadow_list == b.shadow_list; -} -inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) -{ - return !(a == b); -} -} - -template<> struct std::hash { - std::size_t operator()(const Rml::BoxShadowGeometryInfo& in) const noexcept { - using namespace Rml; - using namespace Rml::Utilities; - using namespace std; - size_t result = size_t(849128392); - - HashCombine(result, reinterpret_cast(in.background_color)); - for (const auto& v : in.border_colors) { - HashCombine(result, reinterpret_cast(v)); - } - - for (const auto& v : in.border_radius) { - HashCombine(result, v); - } - HashCombine(result, in.texture_dimensions.x); - HashCombine(result, in.texture_dimensions.y); - HashCombine(result, in.element_offset_in_texture.x); - HashCombine(result, in.element_offset_in_texture.y); - - static const auto fn_hash_render_box = [](size_t& result, const RenderBox& v) { - HashCombine(result, v.GetFillSize().x); - HashCombine(result, v.GetFillSize().y); - HashCombine(result, v.GetBorderOffset().x); - HashCombine(result, v.GetBorderOffset().y); - for (const auto& w : v.GetBorderRadius()) { - HashCombine(result, w); - } - for (const auto& w : v.GetBorderWidths()) { - HashCombine(result, w); - } - }; - for (const auto& v : in.padding_render_boxes) { - fn_hash_render_box(result, v); - } - for (const auto& v : in.border_render_boxes) { - fn_hash_render_box(result, v); - } - for (const auto& v : in.shadow_list) { - HashCombine(result, v.blur_radius.number); - HashCombine(result, v.blur_radius.unit); - HashCombine(result, reinterpret_cast(v.color)); - HashCombine(result, v.inset); - HashCombine(result, v.offset_x.number); - HashCombine(result, v.offset_x.unit); - HashCombine(result, v.offset_y.number); - HashCombine(result, v.offset_y.unit); - HashCombine(result, v.spread_distance.number); - HashCombine(result, v.spread_distance.unit); - } - return result; - } -}; - namespace Rml { class Geometry; class CompiledFilter; @@ -180,8 +103,6 @@ class RMLUICORE_API RenderManager : NonCopyMoveable { Texture LoadTexture(const String& source, const String& document_path = String()); CallbackTexture MakeCallbackTexture(CallbackTextureFunction callback); - SharedPtr FindOrMakeBoxShadowCallbackTexture(const BoxShadowGeometryInfo& geometry_info, CallbackTextureFunction callback); - CompiledFilter CompileFilter(const String& name, const Dictionary& parameters); CompiledShader CompileShader(const String& name, const Dictionary& parameters); @@ -214,11 +135,6 @@ class RMLUICORE_API RenderManager : NonCopyMoveable { void ReleaseResource(const CompiledFilter& filter); void ReleaseResource(const CompiledShader& shader); - // TODO: better way to autonomously release the box shadow cache? - // References are needed to texture, and we need to be able to ref count it. - // Another possibility is making a custom SharedPtr<> class and allow us to manually reduce ref counts. - void CleanupDeadBoxShadowCache(); - struct GeometryData { Mesh mesh; CompiledGeometryHandle handle = {}; @@ -229,8 +145,6 @@ class RMLUICORE_API RenderManager : NonCopyMoveable { StableVector geometry_list; UniquePtr texture_database; - UnorderedMap> box_shadow_cache; - int compiled_filter_count = 0; int compiled_shader_count = 0; diff --git a/Include/RmlUi/Core/Types.h b/Include/RmlUi/Core/Types.h index adc25fcb6..868714f49 100644 --- a/Include/RmlUi/Core/Types.h +++ b/Include/RmlUi/Core/Types.h @@ -186,6 +186,39 @@ struct hash<::Rml::FamilyId> { return h(static_cast(t)); } }; +template <> +struct hash<::Rml::Vector2i> { + size_t operator()(const ::Rml::Vector2i& v) const noexcept + { + size_t h1 = std::hash{}(v.x); + size_t h2 = std::hash{}(v.y); + return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); + } +}; +template <> +struct hash<::Rml::Vector2f> { + size_t operator()(const ::Rml::Vector2f& v) const noexcept + { + size_t h1 = hash{}(v.x); + size_t h2 = hash{}(v.y); + return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); + } +}; +template <> +struct hash<::Rml::Colourb> { + size_t operator()(const ::Rml::Colourb& v) const noexcept + { + return static_cast(hash{}(reinterpret_cast(v))); + } +}; +template <> +struct hash<::Rml::ColourbPremultiplied> { + size_t operator()(const ::Rml::ColourbPremultiplied& v) const noexcept + { + return static_cast(hash{}(reinterpret_cast(v))); + } +}; + } // namespace std #endif diff --git a/Include/RmlUi/Core/Unit.h b/Include/RmlUi/Core/Unit.h index 18132046b..5440a39b2 100644 --- a/Include/RmlUi/Core/Unit.h +++ b/Include/RmlUi/Core/Unit.h @@ -112,4 +112,16 @@ inline bool Any(Units units) } } // namespace Rml +namespace std { +// Hash specialization for enum class types (required on some older compilers) +template <> +struct hash<::Rml::Unit> { + using utype = ::std::underlying_type_t<::Rml::Unit>; + size_t operator()(const ::Rml::Unit& t) const noexcept + { + ::std::hash h; + return h(static_cast(t)); + } +}; +} // namespace std #endif diff --git a/Source/Core/BoxShadowCache.cpp b/Source/Core/BoxShadowCache.cpp new file mode 100644 index 000000000..dc6bf67f6 --- /dev/null +++ b/Source/Core/BoxShadowCache.cpp @@ -0,0 +1,144 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#include "BoxShadowCache.h" +#include "../../Include/RmlUi/Core/Box.h" +#include "../../Include/RmlUi/Core/CallbackTexture.h" +#include "../../Include/RmlUi/Core/ComputedValues.h" +#include "../../Include/RmlUi/Core/Core.h" +#include "../../Include/RmlUi/Core/DecorationTypes.h" +#include "../../Include/RmlUi/Core/Element.h" +#include "../../Include/RmlUi/Core/ElementDocument.h" +#include "../../Include/RmlUi/Core/FileInterface.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/Math.h" +#include "../../Include/RmlUi/Core/MeshUtilities.h" +#include "../../Include/RmlUi/Core/RenderManager.h" +#include "../../Include/RmlUi/Core/Texture.h" +#include "../../Include/RmlUi/Core/Utilities.h" +#include "../Core/ControlledLifetimeResource.h" +#include "ElementBackgroundBorder.h" +#include "GeometryBoxShadow.h" +#include + +namespace Rml { + +struct BoxShadowCacheData { + StableUnorderedMap> handles; +}; + +BoxShadowData::~BoxShadowData() +{ + ReleaseHandle(this); +} + +static ControlledLifetimeResource shadow_cache_data; + +void BoxShadowCache::Initialize() +{ + shadow_cache_data.Initialize(); +} + +void BoxShadowCache::Shutdown() +{ + shadow_cache_data.Shutdown(); +} + +static SharedPtr GetOrCreateBoxShadow(RenderManager& render_manager, const BoxShadowGeometryInfo& info) +{ + auto handles = shadow_cache_data->handles; + auto it_handle = std::find(shadow_cache_data->handles.begin(), shadow_cache_data->handles.end(), info); + if (it_handle == shadow_cache_data->handles.end()) + { + SharedPtr result = it_handle->second.lock(); + RMLUI_ASSERTMSG(result, "Failed to lock handle in SVG cache"); + return result; + } + const auto iterator_inserted = handles.emplace(info, WeakPtr()); + RMLUI_ASSERTMSG(iterator_inserted.second, "Could not insert entry into the SVG cache handle map, duplicate key."); + const BoxShadowGeometryInfo& inserted_key = iterator_inserted.first->first; + WeakPtr& inserted_weak_data_pointer = iterator_inserted.first->second; + + auto shadow_handle = MakeShared(/*TODO*/); + inserted_weak_data_pointer = shadow_handle; + return shadow_handle; +} + +static void ReleaseHandle(BoxShadowData* handle) +{ + // There are no longer any users of the cache entry uniquely identified by the handle address. Start from the + // tip (i.e. per-color data) and remove that entry from its parent. Move up the cache ancestry and erase any + // entries that no longer have any children. +// auto& handles = shadow_cache_data->handles; +// const BoxShadowGeometryInfo& key = handle->cache_key; +// +// auto it_handle = handles.find(key); +// RMLUI_ASSERT(it_handle != handles.cend()); +// +// handles.erase(it_handle); +// +//#ifdef RMLUI_DEBUG +// size_t count_unique_entries = 0; +// /*for (auto& document : documents) +// { +// RMLUI_ASSERT(!document.second.textures.empty()); +// for (auto& size_data : document.second.textures) +// { +// RMLUI_ASSERT(!size_data.geometries.empty()); +// count_unique_entries += size_data.geometries.size(); +// } +// }*/ +// RMLUI_ASSERT(count_unique_entries == handles.size()); +//#endif +} + +SharedPtr BoxShadowCache::GetHandle(Element* element) +{ + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return {}; + + const ComputedValues& computed = element->GetComputedValues(); + const ColourbPremultiplied colour = computed.image_color().ToPremultiplied(computed.opacity()); + + const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow); + RMLUI_ASSERT(p_box_shadow->value.GetType() == Variant::BOXSHADOWLIST); + BoxShadowList shadow_list = p_box_shadow->value.Get(); + + ColourbPremultiplied background_color = computed.background_color().ToPremultiplied(); + Array border_colors = { + computed.border_top_color().ToPremultiplied(), + computed.border_right_color().ToPremultiplied(), + computed.border_bottom_color().ToPremultiplied(), + computed.border_left_color().ToPremultiplied(), + }; + const CornerSizes border_radius = computed.border_radius(); + BoxShadowGeometryInfo geom_info = GeometryBoxShadow::Resolve(element, shadow_list, border_radius, background_color, border_colors); + return GetOrCreateBoxShadow(*render_manager, geom_info); +} +} // namespace Rml \ No newline at end of file diff --git a/Source/Core/BoxShadowCache.h b/Source/Core/BoxShadowCache.h new file mode 100644 index 000000000..4799d7471 --- /dev/null +++ b/Source/Core/BoxShadowCache.h @@ -0,0 +1,59 @@ +/* + * This source file is part of RmlUi, the HTML/CSS Interface Middleware + * + * For the latest information, see http://github.com/mikke89/RmlUi + * + * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd + * Copyright (c) 2019-2023 The RmlUi Team, and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +#ifndef RMLUI_CORE_BOXSHADOWCACHE_H +#define RMLUI_CORE_BOXSHADOWCACHE_H + +#include "../../Include/RmlUi/Core/RenderBox.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { +struct BoxShadowGeometryInfo; + +struct BoxShadowData : NonCopyMoveable { + BoxShadowData(); + ~BoxShadowData(); + + Texture texture; + Vector2f intrinsic_dimensions; + const BoxShadowGeometryInfo& cache_key; +}; + +class BoxShadowCache { +public: + static void Initialize(); + static void Shutdown(); + + /// Returns a handle to BoxShadow data matching the parameters - creates new data if none is found. + /// @param[in] element Element for which to calculate and cache the box shadow + /// @return A handle to the BoxShadow data, with automatic reference counting. + static SharedPtr GetHandle(Element* element); +}; + +} // namespace Rml +#endif \ No newline at end of file diff --git a/Source/Core/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index e7721561f..e33eadc73 100644 --- a/Source/Core/ElementBackgroundBorder.cpp +++ b/Source/Core/ElementBackgroundBorder.cpp @@ -34,6 +34,7 @@ #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/MeshUtilities.h" #include "../../Include/RmlUi/Core/RenderManager.h" +#include "BoxShadowCache.h" #include "GeometryBoxShadow.h" namespace Rml { @@ -58,7 +59,7 @@ void ElementBackgroundBorder::Render(Element* element) Background* shadow = GetBackground(BackgroundType::BoxShadow); if (shadow && shadow->geometry) - shadow->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), *shadow->texture); + shadow->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), shadow->texture); else if (Background* background = GetBackground(BackgroundType::BackgroundBorder)) { auto offset = element->GetAbsoluteOffset(BoxArea::Border); @@ -159,17 +160,11 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) { Geometry& background_border_geometry = geometry; - const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow); - RMLUI_ASSERT(p_box_shadow->value.GetType() == Variant::BOXSHADOWLIST); - BoxShadowList shadow_list = p_box_shadow->value.Get(); - // Generate the geometry for the box-shadow texture. Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadow); Geometry& shadow_geometry = shadow_background.geometry; - SharedPtr& shadow_texture = shadow_background.texture; - - GeometryBoxShadow::Generate(shadow_geometry, shadow_texture, *render_manager, element, background_border_geometry, std::move(shadow_list), - border_radius, background_color, border_colors, computed.opacity()); + box_shadow_cache = BoxShadowCache::GetHandle(element); + shadow_background.texture = box_shadow_cache->texture; } } diff --git a/Source/Core/ElementBackgroundBorder.h b/Source/Core/ElementBackgroundBorder.h index c6c5a8fa5..ace1ceda5 100644 --- a/Source/Core/ElementBackgroundBorder.h +++ b/Source/Core/ElementBackgroundBorder.h @@ -34,7 +34,7 @@ #include "../../Include/RmlUi/Core/Types.h" namespace Rml { - +struct BoxShadowData; class ElementBackgroundBorder { public: ElementBackgroundBorder(); @@ -49,7 +49,7 @@ class ElementBackgroundBorder { enum class BackgroundType { BackgroundBorder, BoxShadow, ClipBorder, ClipPadding, ClipContent, Count }; struct Background { Geometry geometry; - SharedPtr texture; + Texture texture; }; Background* GetBackground(BackgroundType type); @@ -61,6 +61,7 @@ class ElementBackgroundBorder { bool border_dirty = false; StableMap backgrounds; + SharedPtr box_shadow_cache; }; } // namespace Rml diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index 3c989adfd..82f165819 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -27,7 +27,6 @@ */ #include "GeometryBoxShadow.h" -#include "ElementBackgroundBorder.h" #include "../../Include/RmlUi/Core/Box.h" #include "../../Include/RmlUi/Core/CompiledFilterShader.h" #include "../../Include/RmlUi/Core/DecorationTypes.h" @@ -36,12 +35,11 @@ #include "../../Include/RmlUi/Core/Math.h" #include "../../Include/RmlUi/Core/MeshUtilities.h" #include "../../Include/RmlUi/Core/RenderManager.h" +#include "ElementBackgroundBorder.h" namespace Rml { - -void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, SharedPtr& out_shadow_texture, RenderManager& render_manager, - Element* element, const Geometry& background_border_geometry, BoxShadowList shadow_list, CornerSizes border_radius, - ColourbPremultiplied background_color, Array border_colors, float opacity) +BoxShadowGeometryInfo GeometryBoxShadow::Resolve(Element* element, BoxShadowList shadow_list, const CornerSizes& border_radius, + ColourbPremultiplied background_color, const Array& border_colors) { // Find the box-shadow texture dimension and offset required to cover all box-shadows and element boxes combined. Vector2f element_offset_in_texture; @@ -88,7 +86,7 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, SharedPtrGetNumBoxes(), - background_border_mesh = std::move(background_border_mesh), - padding_render_boxes = std::move(padding_render_boxes), - border_render_boxes = std::move(border_render_boxes), - shadow_list = std::move(shadow_list)](const CallbackTextureInterface& texture_interface) -> bool { + auto texture_callback = [&info](const CallbackTextureInterface& texture_interface) -> bool { + RMLUI_ASSERT(info.border_render_boxes.size() == info.padding_render_boxes.size()); + int num_boxes = info.border_render_boxes.size(); + RenderManager& render_manager = texture_interface.GetRenderManager(); - + Mesh mesh_padding; // Render geometry for inner box-shadow. Mesh mesh_padding_border; // Clipping mask for outer box-shadow. bool has_inner_shadow = false; bool has_outer_shadow = false; - for (const BoxShadow& shadow : shadow_list) + for (const BoxShadow& shadow : info.shadow_list) { if (shadow.inset) has_inner_shadow = true; @@ -140,14 +138,14 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, SharedPtrGetAddress().c_str()*/); + "Results may be clipped.", + info.texture_dimensions.x, info.texture_dimensions.y, scissor_region.Width(), scissor_region.Height()); } render_manager.PushLayer(); - Rml::Geometry background_border_geometry = render_manager.MakeGeometry(Rml::Mesh(background_border_mesh)); - background_border_geometry.Render(element_offset_in_texture); + Rml::Geometry background_border_geometry /*TODO*/; //= render_manager.MakeGeometry(Rml::Mesh(background_border_mesh)); + background_border_geometry.Render(info.element_offset_in_texture); background_border_geometry.Release(); - for (int shadow_index = (int)shadow_list.size() - 1; shadow_index >= 0; shadow_index--) + for (int shadow_index = (int)info.shadow_list.size() - 1; shadow_index >= 0; shadow_index--) { - const BoxShadow& shadow = shadow_list[shadow_index]; + const BoxShadow& shadow = info.shadow_list[shadow_index]; const Vector2f shadow_offset = {shadow.offset_x.number, shadow.offset_y.number}; const bool inset = shadow.inset; const float spread_distance = shadow.spread_distance.number; const float blur_radius = shadow.blur_radius.number; - CornerSizes spread_radii = border_radius; + CornerSizes spread_radii = info.border_radius; for (int i = 0; i < 4; i++) { float& radius = spread_radii[i]; @@ -198,7 +196,7 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, SharedPtr; +struct BoxShadowGeometryInfo { + ColourbPremultiplied background_color; + Array border_colors; + CornerSizes border_radius; + Vector2i texture_dimensions; + Vector2f element_offset_in_texture; + RenderBoxList padding_render_boxes; + RenderBoxList border_render_boxes; + BoxShadowList shadow_list; +}; +inline bool operator==(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) +{ + return a.background_color == b.background_color && a.border_colors == b.border_colors && a.border_radius == b.border_radius && + a.texture_dimensions == b.texture_dimensions && a.element_offset_in_texture == b.element_offset_in_texture && + a.padding_render_boxes == b.padding_render_boxes && a.border_render_boxes == b.border_render_boxes && a.shadow_list == b.shadow_list; +} +inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) +{ + return !(a == b); +} +} // namespace Rml + +namespace std { +template <> +struct hash<::Rml::BoxShadowGeometryInfo> { + size_t operator()(const ::Rml::BoxShadowGeometryInfo& in) const noexcept + { + using namespace ::Rml::Utilities; + size_t seed = size_t(849128392); + + HashCombine(seed, in.background_color); + for (const auto& v : in.border_colors) + { + HashCombine(seed, v); + } + + for (const auto& v : in.border_radius) + { + HashCombine(seed, v); + } + + HashCombine(seed, in.texture_dimensions); + HashCombine(seed, in.element_offset_in_texture); + + for (const auto& v : in.padding_render_boxes) + { + HashCombine(seed, v); + } + for (const auto& v : in.border_render_boxes) + { + HashCombine(seed, v); + } + for (const auto& v : in.shadow_list) + { + HashCombine(seed, v); + } + return seed; + } +}; +} // namespace std namespace Rml { class Geometry; @@ -41,19 +103,21 @@ class RenderManager; class GeometryBoxShadow { public: /// Generate the texture and geometry for a box shadow. - /// @param[out] out_shadow_geometry The target geometry. - /// @param[out] out_shadow_texture The target texture, assumes pointer stability during the lifetime of the shadow geometry. This may also be shared between - /// @param[in] render_manager The render manager to generate the shadow for. - /// @param[in] element The element to generate the shadow for. - /// @param[in] background_border_geometry The geometry of the background and border. Used to avoid recomputing the mesh from the given render boxes and background/border colours + /// @param[in] element The element to resolve. /// @param[in] shadow_list The list of box-shadows to generate. /// @param[in] border_radius The border radius of the element. /// @param[in] background_color The background colour of the element. /// @param[in] border_colors The border colours of the element. - /// @param[in] opacity The opacity of the element. - static void Generate(Geometry& out_shadow_geometry, SharedPtr& out_shadow_texture, RenderManager& render_manager, - Element* element, const Geometry& background_border_geometry, BoxShadowList shadow_list, CornerSizes border_radius, - ColourbPremultiplied background_color, Array border_colors, float opacity); + static BoxShadowGeometryInfo Resolve(Element* element, BoxShadowList shadow_list, const CornerSizes& border_radius, + ColourbPremultiplied background_color, const Array& border_colors); + + /// Generate the texture and geometry for a box shadow. + /// @param[out] out_shadow_texture The target texture, assumes pointer stability during the lifetime of the shadow geometry. + /// @param[in] render_manager The render manager to generate the shadow for. + /// @param[in] shadow_geometry_info The resolved box shadow geometry of a given element. + /// @see GeometryBoxShadow::Resolve() + static void GenerateTexture(CallbackTexture& out_shadow_texture, RenderManager& render_manager, + const BoxShadowGeometryInfo& shadow_geometry_info); }; } // namespace Rml diff --git a/Source/Core/RenderManager.cpp b/Source/Core/RenderManager.cpp index a76a5e8f1..5b397e3c3 100644 --- a/Source/Core/RenderManager.cpp +++ b/Source/Core/RenderManager.cpp @@ -46,9 +46,6 @@ RenderManager::RenderManager(RenderInterface* render_interface) : render_interfa RenderManager::~RenderManager() { - // clear cache, since the render manager always has at least 1 reference if the texture hasn't been freed yet. - // All references should be equal to 1, if they are more, then they haven't been freed properly - CleanupDeadBoxShadowCache(); struct ResourceCount { const char* name; int count; @@ -83,9 +80,6 @@ void RenderManager::PrepareRender(Vector2i dimensions) #endif SetViewport(dimensions); - - // is this a good place to call cleanup? - CleanupDeadBoxShadowCache(); } void RenderManager::SetViewport(Vector2i dimensions) @@ -119,21 +113,6 @@ CallbackTexture RenderManager::MakeCallbackTexture(CallbackTextureFunction callb return CallbackTexture(this, texture_database->callback_database.CreateTexture(std::move(callback))); } -SharedPtr RenderManager::FindOrMakeBoxShadowCallbackTexture(const BoxShadowGeometryInfo& geometry_info, CallbackTextureFunction callback) -{ - { - auto it = box_shadow_cache.find(geometry_info); - if (it != box_shadow_cache.end()) { - auto& ref_tex = it->second; - return ref_tex; - } - } - SharedPtr& ref_tex = box_shadow_cache[geometry_info]; - ref_tex = SharedPtr(new CallbackTexture(this, texture_database->callback_database.CreateTexture(std::move(callback)))); - return ref_tex; -} - - void RenderManager::DisableScissorRegion() { SetScissorRegion(Rectanglei::MakeInvalid()); @@ -416,20 +395,4 @@ void RenderManager::ReleaseResource(const CompiledShader& shader) compiled_shader_count -= 1; } -void RenderManager::CleanupDeadBoxShadowCache() -{ - Vector destroyGeometries(0); - for (auto& kv : box_shadow_cache) { - RMLUI_ASSERT(kv.second && "This should never be null!"); - if (kv.second.unique()) { - kv.second->Release(); - destroyGeometries.emplace_back(std::move(kv.first)); - } - } - for (auto& info : destroyGeometries) { - box_shadow_cache.erase(info); - } -} - - } // namespace Rml From 646de5aaa3c2ded0a377da4e620d1fb8fa8ec3e4 Mon Sep 17 00:00:00 2001 From: ZilverBlade <90721817+ZilverBlade@users.noreply.github.com> Date: Sun, 5 Oct 2025 16:04:13 +0200 Subject: [PATCH 08/12] Implemented cache, added BoxShadowCache.cpp to the cmakelists.txt and solved code reviews --- Include/RmlUi/Core/NumericValue.h | 2 - Include/RmlUi/Core/RenderManager.h | 1 - Source/Core/BoxShadowCache.cpp | 57 +++++++++++-------------- Source/Core/BoxShadowCache.h | 8 ++-- Source/Core/CMakeLists.txt | 2 + Source/Core/ElementBackgroundBorder.cpp | 9 ++-- Source/Core/GeometryBoxShadow.cpp | 7 ++- Source/Core/GeometryBoxShadow.h | 51 +++++++++++----------- 8 files changed, 62 insertions(+), 75 deletions(-) diff --git a/Include/RmlUi/Core/NumericValue.h b/Include/RmlUi/Core/NumericValue.h index 1e0fabd59..0a4566b1b 100644 --- a/Include/RmlUi/Core/NumericValue.h +++ b/Include/RmlUi/Core/NumericValue.h @@ -55,8 +55,6 @@ inline bool operator!=(const NumericValue& a, const NumericValue& b) namespace std { template <> struct hash<::Rml::NumericValue> { - // FIXME: should the hash function NumericValue not care about unit (as long as resolved unit is the same?) - // Or should it be a regular hash combine? size_t operator()(const ::Rml::NumericValue& v) const noexcept { using namespace ::Rml; diff --git a/Include/RmlUi/Core/RenderManager.h b/Include/RmlUi/Core/RenderManager.h index e0ede0d94..72eb622e2 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -36,7 +36,6 @@ #include "RenderInterface.h" #include "StableVector.h" #include "Types.h" -#include "Utilities.h" namespace Rml { class Geometry; diff --git a/Source/Core/BoxShadowCache.cpp b/Source/Core/BoxShadowCache.cpp index dc6bf67f6..61819d76b 100644 --- a/Source/Core/BoxShadowCache.cpp +++ b/Source/Core/BoxShadowCache.cpp @@ -52,6 +52,12 @@ struct BoxShadowCacheData { StableUnorderedMap> handles; }; +static void ReleaseHandle(BoxShadowData* handle); + +BoxShadowData::BoxShadowData(CallbackTexture&& texture, Geometry&& geometry, const BoxShadowGeometryInfo& geometry_info) : + geometry(std::move(geometry)), texture(std::move(texture)), cache_key(geometry_info) +{} + BoxShadowData::~BoxShadowData() { ReleaseHandle(this); @@ -69,22 +75,25 @@ void BoxShadowCache::Shutdown() shadow_cache_data.Shutdown(); } -static SharedPtr GetOrCreateBoxShadow(RenderManager& render_manager, const BoxShadowGeometryInfo& info) +static SharedPtr GetOrCreateBoxShadow(RenderManager& render_manager, const BoxShadowGeometryInfo& info, + Geometry& background_border_geometry) { - auto handles = shadow_cache_data->handles; - auto it_handle = std::find(shadow_cache_data->handles.begin(), shadow_cache_data->handles.end(), info); + auto it_handle = shadow_cache_data->handles.find(info); if (it_handle == shadow_cache_data->handles.end()) { SharedPtr result = it_handle->second.lock(); RMLUI_ASSERTMSG(result, "Failed to lock handle in SVG cache"); return result; } - const auto iterator_inserted = handles.emplace(info, WeakPtr()); + const auto iterator_inserted = shadow_cache_data->handles.emplace(info, WeakPtr()); RMLUI_ASSERTMSG(iterator_inserted.second, "Could not insert entry into the SVG cache handle map, duplicate key."); const BoxShadowGeometryInfo& inserted_key = iterator_inserted.first->first; WeakPtr& inserted_weak_data_pointer = iterator_inserted.first->second; - auto shadow_handle = MakeShared(/*TODO*/); + Geometry shadow_geometry = render_manager.MakeGeometry(background_border_geometry.Release(Geometry::ReleaseMode::ClearMesh)); + CallbackTexture shadow_texture; + GeometryBoxShadow::GenerateTexture(shadow_texture, render_manager, inserted_key); + auto shadow_handle = MakeShared(std::move(shadow_texture), std::move(shadow_geometry), inserted_key); inserted_weak_data_pointer = shadow_handle; return shadow_handle; } @@ -94,30 +103,16 @@ static void ReleaseHandle(BoxShadowData* handle) // There are no longer any users of the cache entry uniquely identified by the handle address. Start from the // tip (i.e. per-color data) and remove that entry from its parent. Move up the cache ancestry and erase any // entries that no longer have any children. -// auto& handles = shadow_cache_data->handles; -// const BoxShadowGeometryInfo& key = handle->cache_key; -// -// auto it_handle = handles.find(key); -// RMLUI_ASSERT(it_handle != handles.cend()); -// -// handles.erase(it_handle); -// -//#ifdef RMLUI_DEBUG -// size_t count_unique_entries = 0; -// /*for (auto& document : documents) -// { -// RMLUI_ASSERT(!document.second.textures.empty()); -// for (auto& size_data : document.second.textures) -// { -// RMLUI_ASSERT(!size_data.geometries.empty()); -// count_unique_entries += size_data.geometries.size(); -// } -// }*/ -// RMLUI_ASSERT(count_unique_entries == handles.size()); -//#endif + auto& handles = shadow_cache_data->handles; + const BoxShadowGeometryInfo& key = handle->cache_key; + + auto it_handle = handles.find(key); + RMLUI_ASSERT(it_handle != handles.cend()); + + handles.erase(it_handle); } -SharedPtr BoxShadowCache::GetHandle(Element* element) +SharedPtr BoxShadowCache::GetHandle(Element* element, Geometry& background_border_geometry) { RenderManager* render_manager = element->GetRenderManager(); if (!render_manager) @@ -126,10 +121,6 @@ SharedPtr BoxShadowCache::GetHandle(Element* element) const ComputedValues& computed = element->GetComputedValues(); const ColourbPremultiplied colour = computed.image_color().ToPremultiplied(computed.opacity()); - const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow); - RMLUI_ASSERT(p_box_shadow->value.GetType() == Variant::BOXSHADOWLIST); - BoxShadowList shadow_list = p_box_shadow->value.Get(); - ColourbPremultiplied background_color = computed.background_color().ToPremultiplied(); Array border_colors = { computed.border_top_color().ToPremultiplied(), @@ -138,7 +129,7 @@ SharedPtr BoxShadowCache::GetHandle(Element* element) computed.border_left_color().ToPremultiplied(), }; const CornerSizes border_radius = computed.border_radius(); - BoxShadowGeometryInfo geom_info = GeometryBoxShadow::Resolve(element, shadow_list, border_radius, background_color, border_colors); - return GetOrCreateBoxShadow(*render_manager, geom_info); + BoxShadowGeometryInfo geom_info = GeometryBoxShadow::Resolve(element, border_radius, background_color, border_colors); + return GetOrCreateBoxShadow(*render_manager, geom_info, background_border_geometry); } } // namespace Rml \ No newline at end of file diff --git a/Source/Core/BoxShadowCache.h b/Source/Core/BoxShadowCache.h index 4799d7471..bdde69b65 100644 --- a/Source/Core/BoxShadowCache.h +++ b/Source/Core/BoxShadowCache.h @@ -36,11 +36,11 @@ namespace Rml { struct BoxShadowGeometryInfo; struct BoxShadowData : NonCopyMoveable { - BoxShadowData(); + BoxShadowData(CallbackTexture&& texture, Geometry&& geometry, const BoxShadowGeometryInfo& geometry_info); ~BoxShadowData(); - Texture texture; - Vector2f intrinsic_dimensions; + CallbackTexture texture; + Geometry geometry; const BoxShadowGeometryInfo& cache_key; }; @@ -52,7 +52,7 @@ class BoxShadowCache { /// Returns a handle to BoxShadow data matching the parameters - creates new data if none is found. /// @param[in] element Element for which to calculate and cache the box shadow /// @return A handle to the BoxShadow data, with automatic reference counting. - static SharedPtr GetHandle(Element* element); + static SharedPtr GetHandle(Element* element, Geometry& background_border_geometry); }; } // namespace Rml diff --git a/Source/Core/CMakeLists.txt b/Source/Core/CMakeLists.txt index 336ec9791..d950e8469 100644 --- a/Source/Core/CMakeLists.txt +++ b/Source/Core/CMakeLists.txt @@ -4,6 +4,8 @@ add_library(rmlui_core BaseXMLParser.cpp Box.cpp + BoxShadowCache.h + BoxShadowCache.cpp CallbackTexture.cpp Clock.cpp Clock.h diff --git a/Source/Core/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index e33eadc73..89141de06 100644 --- a/Source/Core/ElementBackgroundBorder.cpp +++ b/Source/Core/ElementBackgroundBorder.cpp @@ -58,8 +58,8 @@ void ElementBackgroundBorder::Render(Element* element) } Background* shadow = GetBackground(BackgroundType::BoxShadow); - if (shadow && shadow->geometry) - shadow->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), shadow->texture); + if (shadow && box_shadow_cache) + box_shadow_cache->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), shadow->texture); else if (Background* background = GetBackground(BackgroundType::BackgroundBorder)) { auto offset = element->GetAbsoluteOffset(BoxArea::Border); @@ -158,12 +158,9 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) if (has_box_shadow) { - Geometry& background_border_geometry = geometry; - // Generate the geometry for the box-shadow texture. Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadow); - Geometry& shadow_geometry = shadow_background.geometry; - box_shadow_cache = BoxShadowCache::GetHandle(element); + box_shadow_cache = BoxShadowCache::GetHandle(element, geometry); shadow_background.texture = box_shadow_cache->texture; } } diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index 82f165819..d190cb88b 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -38,13 +38,17 @@ #include "ElementBackgroundBorder.h" namespace Rml { -BoxShadowGeometryInfo GeometryBoxShadow::Resolve(Element* element, BoxShadowList shadow_list, const CornerSizes& border_radius, +BoxShadowGeometryInfo GeometryBoxShadow::Resolve(Element* element, const CornerSizes& border_radius, ColourbPremultiplied background_color, const Array& border_colors) { // Find the box-shadow texture dimension and offset required to cover all box-shadows and element boxes combined. Vector2f element_offset_in_texture; Vector2i texture_dimensions; + const Property* p_box_shadow = element->GetLocalProperty(PropertyId::BoxShadow); + RMLUI_ASSERT(p_box_shadow->value.GetType() == Variant::BOXSHADOWLIST); + BoxShadowList shadow_list = p_box_shadow->value.Get(); + // Resolve all lengths to px units. for (BoxShadow& shadow : shadow_list) { @@ -253,5 +257,4 @@ void GeometryBoxShadow::GenerateTexture(CallbackTexture& out_shadow_texture, Ren }; out_shadow_texture = render_manager.MakeCallbackTexture(std::move(texture_callback)); } - } // namespace Rml diff --git a/Source/Core/GeometryBoxShadow.h b/Source/Core/GeometryBoxShadow.h index 8e768cde0..1405f17bc 100644 --- a/Source/Core/GeometryBoxShadow.h +++ b/Source/Core/GeometryBoxShadow.h @@ -54,6 +54,30 @@ inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryIn { return !(a == b); } + +class Geometry; +class CallbackTexture; +class RenderManager; + +class GeometryBoxShadow { +public: + /// Generate the texture and geometry for a box shadow. + /// @param[in] element The element to resolve. + /// @param[in] border_radius The border radius of the element. + /// @param[in] background_color The background colour of the element. + /// @param[in] border_colors The border colours of the element. + static BoxShadowGeometryInfo Resolve(Element* element, const CornerSizes& border_radius, + ColourbPremultiplied background_color, const Array& border_colors); + + /// Generate the texture and geometry for a box shadow. + /// @param[out] out_shadow_texture The target texture, assumes pointer stability during the lifetime of the shadow geometry. + /// @param[in] render_manager The render manager to generate the shadow for. + /// @param[in] shadow_geometry_info The resolved box shadow geometry of a given element. + /// @see GeometryBoxShadow::Resolve() + static void GenerateTexture(CallbackTexture& out_shadow_texture, RenderManager& render_manager, + const BoxShadowGeometryInfo& shadow_geometry_info); +}; + } // namespace Rml namespace std { @@ -94,31 +118,4 @@ struct hash<::Rml::BoxShadowGeometryInfo> { } }; } // namespace std -namespace Rml { - -class Geometry; -class CallbackTexture; -class RenderManager; - -class GeometryBoxShadow { -public: - /// Generate the texture and geometry for a box shadow. - /// @param[in] element The element to resolve. - /// @param[in] shadow_list The list of box-shadows to generate. - /// @param[in] border_radius The border radius of the element. - /// @param[in] background_color The background colour of the element. - /// @param[in] border_colors The border colours of the element. - static BoxShadowGeometryInfo Resolve(Element* element, BoxShadowList shadow_list, const CornerSizes& border_radius, - ColourbPremultiplied background_color, const Array& border_colors); - - /// Generate the texture and geometry for a box shadow. - /// @param[out] out_shadow_texture The target texture, assumes pointer stability during the lifetime of the shadow geometry. - /// @param[in] render_manager The render manager to generate the shadow for. - /// @param[in] shadow_geometry_info The resolved box shadow geometry of a given element. - /// @see GeometryBoxShadow::Resolve() - static void GenerateTexture(CallbackTexture& out_shadow_texture, RenderManager& render_manager, - const BoxShadowGeometryInfo& shadow_geometry_info); -}; - -} // namespace Rml #endif From b9fb7bcb7a1b8f21ac317f5b2787944a49c819a2 Mon Sep 17 00:00:00 2001 From: ZilverBlade Date: Sat, 8 Nov 2025 13:49:24 +0100 Subject: [PATCH 09/12] fixed some compiler errors and added missing initialisation and destruction for box shadow cache --- Include/RmlUi/Core/Unit.h | 4 ++-- Source/Core/BoxShadowCache.cpp | 5 +---- Source/Core/BoxShadowCache.h | 2 ++ Source/Core/Core.cpp | 4 ++++ Source/Core/GeometryBoxShadow.cpp | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Include/RmlUi/Core/Unit.h b/Include/RmlUi/Core/Unit.h index 5440a39b2..46f746ae1 100644 --- a/Include/RmlUi/Core/Unit.h +++ b/Include/RmlUi/Core/Unit.h @@ -116,10 +116,10 @@ namespace std { // Hash specialization for enum class types (required on some older compilers) template <> struct hash<::Rml::Unit> { - using utype = ::std::underlying_type_t<::Rml::Unit>; + using utype = underlying_type_t<::Rml::Unit>; size_t operator()(const ::Rml::Unit& t) const noexcept { - ::std::hash h; + hash h; return h(static_cast(t)); } }; diff --git a/Source/Core/BoxShadowCache.cpp b/Source/Core/BoxShadowCache.cpp index 61819d76b..a582fe52e 100644 --- a/Source/Core/BoxShadowCache.cpp +++ b/Source/Core/BoxShadowCache.cpp @@ -28,14 +28,12 @@ #include "BoxShadowCache.h" #include "../../Include/RmlUi/Core/Box.h" -#include "../../Include/RmlUi/Core/CallbackTexture.h" #include "../../Include/RmlUi/Core/ComputedValues.h" #include "../../Include/RmlUi/Core/Core.h" #include "../../Include/RmlUi/Core/DecorationTypes.h" #include "../../Include/RmlUi/Core/Element.h" #include "../../Include/RmlUi/Core/ElementDocument.h" #include "../../Include/RmlUi/Core/FileInterface.h" -#include "../../Include/RmlUi/Core/Geometry.h" #include "../../Include/RmlUi/Core/Math.h" #include "../../Include/RmlUi/Core/MeshUtilities.h" #include "../../Include/RmlUi/Core/RenderManager.h" @@ -79,7 +77,7 @@ static SharedPtr GetOrCreateBoxShadow(RenderManager& render_manag Geometry& background_border_geometry) { auto it_handle = shadow_cache_data->handles.find(info); - if (it_handle == shadow_cache_data->handles.end()) + if (it_handle != shadow_cache_data->handles.end()) { SharedPtr result = it_handle->second.lock(); RMLUI_ASSERTMSG(result, "Failed to lock handle in SVG cache"); @@ -119,7 +117,6 @@ SharedPtr BoxShadowCache::GetHandle(Element* element, Geometry& b return {}; const ComputedValues& computed = element->GetComputedValues(); - const ColourbPremultiplied colour = computed.image_color().ToPremultiplied(computed.opacity()); ColourbPremultiplied background_color = computed.background_color().ToPremultiplied(); Array border_colors = { diff --git a/Source/Core/BoxShadowCache.h b/Source/Core/BoxShadowCache.h index bdde69b65..321c54860 100644 --- a/Source/Core/BoxShadowCache.h +++ b/Source/Core/BoxShadowCache.h @@ -29,6 +29,8 @@ #ifndef RMLUI_CORE_BOXSHADOWCACHE_H #define RMLUI_CORE_BOXSHADOWCACHE_H +#include "../../Include/RmlUi/Core/CallbackTexture.h" +#include "../../Include/RmlUi/Core/Geometry.h" #include "../../Include/RmlUi/Core/RenderBox.h" #include "../../Include/RmlUi/Core/Types.h" diff --git a/Source/Core/Core.cpp b/Source/Core/Core.cpp index d882635df..2fb9f51a0 100644 --- a/Source/Core/Core.cpp +++ b/Source/Core/Core.cpp @@ -40,6 +40,7 @@ #include "../../Include/RmlUi/Core/SystemInterface.h" #include "../../Include/RmlUi/Core/TextInputHandler.h" #include "../../Include/RmlUi/Core/Types.h" +#include "BoxShadowCache.h" #include "ComputeProperty.h" #include "ControlledLifetimeResource.h" #include "ElementMeta.h" @@ -174,6 +175,7 @@ bool Initialise() #ifdef RMLUI_SVG_PLUGIN SVG::Initialise(); #endif + BoxShadowCache::Initialize(); // Notify all plugins we're starting up. PluginRegistry::NotifyInitialise(); @@ -193,6 +195,8 @@ void Shutdown() // Notify all plugins we're being shutdown. PluginRegistry::NotifyShutdown(); + BoxShadowCache::Shutdown(); + Factory::Shutdown(); TemplateCache::Shutdown(); StyleSheetFactory::Shutdown(); diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index d190cb88b..bc2067bb3 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -120,7 +120,7 @@ void GeometryBoxShadow::GenerateTexture(CallbackTexture& out_shadow_texture, Ren // device loses its GPU context and the client calls Rml::ReleaseTextures(). auto texture_callback = [&info](const CallbackTextureInterface& texture_interface) -> bool { RMLUI_ASSERT(info.border_render_boxes.size() == info.padding_render_boxes.size()); - int num_boxes = info.border_render_boxes.size(); + size_t num_boxes = info.border_render_boxes.size(); RenderManager& render_manager = texture_interface.GetRenderManager(); From 95008718b66edbe932021eb9e76037f692a33e59 Mon Sep 17 00:00:00 2001 From: ZilverBlade <90721817+ZilverBlade@users.noreply.github.com> Date: Sun, 9 Nov 2025 19:49:58 +0100 Subject: [PATCH 10/12] fixed compile errors and fixed missing opacity and texture offset in caching --- Source/Core/BoxShadowCache.cpp | 14 +++++++++----- Source/Core/GeometryBoxShadow.cpp | 3 ++- Source/Core/GeometryBoxShadow.h | 13 +++++++++---- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Source/Core/BoxShadowCache.cpp b/Source/Core/BoxShadowCache.cpp index a582fe52e..43ef6171c 100644 --- a/Source/Core/BoxShadowCache.cpp +++ b/Source/Core/BoxShadowCache.cpp @@ -53,7 +53,7 @@ struct BoxShadowCacheData { static void ReleaseHandle(BoxShadowData* handle); BoxShadowData::BoxShadowData(CallbackTexture&& texture, Geometry&& geometry, const BoxShadowGeometryInfo& geometry_info) : - geometry(std::move(geometry)), texture(std::move(texture)), cache_key(geometry_info) + texture(std::move(texture)), geometry(std::move(geometry)), cache_key(geometry_info) {} BoxShadowData::~BoxShadowData() @@ -80,15 +80,19 @@ static SharedPtr GetOrCreateBoxShadow(RenderManager& render_manag if (it_handle != shadow_cache_data->handles.end()) { SharedPtr result = it_handle->second.lock(); - RMLUI_ASSERTMSG(result, "Failed to lock handle in SVG cache"); + RMLUI_ASSERTMSG(result, "Failed to lock handle in Box Shadow cache"); return result; } const auto iterator_inserted = shadow_cache_data->handles.emplace(info, WeakPtr()); - RMLUI_ASSERTMSG(iterator_inserted.second, "Could not insert entry into the SVG cache handle map, duplicate key."); + RMLUI_ASSERTMSG(iterator_inserted.second, "Could not insert entry into the Box Shadow cache handle map, duplicate key."); const BoxShadowGeometryInfo& inserted_key = iterator_inserted.first->first; WeakPtr& inserted_weak_data_pointer = iterator_inserted.first->second; - Geometry shadow_geometry = render_manager.MakeGeometry(background_border_geometry.Release(Geometry::ReleaseMode::ClearMesh)); + Mesh mesh = background_border_geometry.Release(Geometry::ReleaseMode::ClearMesh); + const byte alpha = byte(info.opacity * 255.f); + // TODO: maybe cache the geometry too? Similar to the SVG cache hierarchy. + MeshUtilities::GenerateQuad(mesh, -info.element_offset_in_texture, Vector2f(info.texture_dimensions), ColourbPremultiplied(alpha, alpha)); + Geometry shadow_geometry = render_manager.MakeGeometry(std::move(mesh)); CallbackTexture shadow_texture; GeometryBoxShadow::GenerateTexture(shadow_texture, render_manager, inserted_key); auto shadow_handle = MakeShared(std::move(shadow_texture), std::move(shadow_geometry), inserted_key); @@ -126,7 +130,7 @@ SharedPtr BoxShadowCache::GetHandle(Element* element, Geometry& b computed.border_left_color().ToPremultiplied(), }; const CornerSizes border_radius = computed.border_radius(); - BoxShadowGeometryInfo geom_info = GeometryBoxShadow::Resolve(element, border_radius, background_color, border_colors); + BoxShadowGeometryInfo geom_info = GeometryBoxShadow::Resolve(element, border_radius, background_color, border_colors, computed.opacity()); return GetOrCreateBoxShadow(*render_manager, geom_info, background_border_geometry); } } // namespace Rml \ No newline at end of file diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index bc2067bb3..9bf91ebc4 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -39,7 +39,7 @@ namespace Rml { BoxShadowGeometryInfo GeometryBoxShadow::Resolve(Element* element, const CornerSizes& border_radius, - ColourbPremultiplied background_color, const Array& border_colors) + ColourbPremultiplied background_color, const Array& border_colors, float opacity) { // Find the box-shadow texture dimension and offset required to cover all box-shadows and element boxes combined. Vector2f element_offset_in_texture; @@ -111,6 +111,7 @@ BoxShadowGeometryInfo GeometryBoxShadow::Resolve(Element* element, const CornerS geometry_info.padding_render_boxes = std::move(padding_render_boxes); geometry_info.border_render_boxes = std::move(border_render_boxes); geometry_info.shadow_list = std::move(shadow_list); + geometry_info.opacity = opacity; return geometry_info; } diff --git a/Source/Core/GeometryBoxShadow.h b/Source/Core/GeometryBoxShadow.h index 1405f17bc..d33df6df2 100644 --- a/Source/Core/GeometryBoxShadow.h +++ b/Source/Core/GeometryBoxShadow.h @@ -29,6 +29,7 @@ #ifndef RMLUI_CORE_GEOMETRYBOXSHADOW_H #define RMLUI_CORE_GEOMETRYBOXSHADOW_H +#include "../../Include/RmlUi/Core/DecorationTypes.h" #include "../../Include/RmlUi/Core/RenderBox.h" #include "../../Include/RmlUi/Core/Types.h" @@ -43,12 +44,14 @@ struct BoxShadowGeometryInfo { RenderBoxList padding_render_boxes; RenderBoxList border_render_boxes; BoxShadowList shadow_list; + float opacity; }; inline bool operator==(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) { return a.background_color == b.background_color && a.border_colors == b.border_colors && a.border_radius == b.border_radius && a.texture_dimensions == b.texture_dimensions && a.element_offset_in_texture == b.element_offset_in_texture && - a.padding_render_boxes == b.padding_render_boxes && a.border_render_boxes == b.border_render_boxes && a.shadow_list == b.shadow_list; + a.padding_render_boxes == b.padding_render_boxes && a.border_render_boxes == b.border_render_boxes && a.shadow_list == b.shadow_list && + a.opacity == b.opacity; } inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) { @@ -66,8 +69,9 @@ class GeometryBoxShadow { /// @param[in] border_radius The border radius of the element. /// @param[in] background_color The background colour of the element. /// @param[in] border_colors The border colours of the element. - static BoxShadowGeometryInfo Resolve(Element* element, const CornerSizes& border_radius, - ColourbPremultiplied background_color, const Array& border_colors); + /// @param[in] opacity The computed opacity of the element. + static BoxShadowGeometryInfo Resolve(Element* element, const CornerSizes& border_radius, ColourbPremultiplied background_color, + const Array& border_colors, float opacity); /// Generate the texture and geometry for a box shadow. /// @param[out] out_shadow_texture The target texture, assumes pointer stability during the lifetime of the shadow geometry. @@ -110,10 +114,11 @@ struct hash<::Rml::BoxShadowGeometryInfo> { { HashCombine(seed, v); } - for (const auto& v : in.shadow_list) + for (const ::Rml::BoxShadow& v : in.shadow_list) { HashCombine(seed, v); } + HashCombine(seed, in.opacity); return seed; } }; From 8eb1d8a044909a85d8c10258013126c88d85f028 Mon Sep 17 00:00:00 2001 From: ZilverBlade <90721817+ZilverBlade@users.noreply.github.com> Date: Sun, 9 Nov 2025 21:02:47 +0100 Subject: [PATCH 11/12] Fixed missing background border geometry cache --- Source/Core/BoxShadowCache.cpp | 26 ++++++++++++------------- Source/Core/BoxShadowCache.h | 5 +++-- Source/Core/ElementBackgroundBorder.cpp | 4 ++-- Source/Core/GeometryBoxShadow.cpp | 16 +++++++++------ Source/Core/GeometryBoxShadow.h | 5 ++++- 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/Source/Core/BoxShadowCache.cpp b/Source/Core/BoxShadowCache.cpp index 43ef6171c..f50247436 100644 --- a/Source/Core/BoxShadowCache.cpp +++ b/Source/Core/BoxShadowCache.cpp @@ -52,8 +52,8 @@ struct BoxShadowCacheData { static void ReleaseHandle(BoxShadowData* handle); -BoxShadowData::BoxShadowData(CallbackTexture&& texture, Geometry&& geometry, const BoxShadowGeometryInfo& geometry_info) : - texture(std::move(texture)), geometry(std::move(geometry)), cache_key(geometry_info) +BoxShadowData::BoxShadowData(const BoxShadowGeometryInfo& geometry_info) : + cache_key(geometry_info) {} BoxShadowData::~BoxShadowData() @@ -73,8 +73,7 @@ void BoxShadowCache::Shutdown() shadow_cache_data.Shutdown(); } -static SharedPtr GetOrCreateBoxShadow(RenderManager& render_manager, const BoxShadowGeometryInfo& info, - Geometry& background_border_geometry) +static SharedPtr GetOrCreateBoxShadow(RenderManager& render_manager, const BoxShadowGeometryInfo& info) { auto it_handle = shadow_cache_data->handles.find(info); if (it_handle != shadow_cache_data->handles.end()) @@ -87,15 +86,16 @@ static SharedPtr GetOrCreateBoxShadow(RenderManager& render_manag RMLUI_ASSERTMSG(iterator_inserted.second, "Could not insert entry into the Box Shadow cache handle map, duplicate key."); const BoxShadowGeometryInfo& inserted_key = iterator_inserted.first->first; WeakPtr& inserted_weak_data_pointer = iterator_inserted.first->second; + auto shadow_handle = MakeShared(inserted_key); + + GeometryBoxShadow::GenerateTexture(shadow_handle->texture, shadow_handle->background_border_geometry, render_manager, inserted_key); - Mesh mesh = background_border_geometry.Release(Geometry::ReleaseMode::ClearMesh); - const byte alpha = byte(info.opacity * 255.f); // TODO: maybe cache the geometry too? Similar to the SVG cache hierarchy. + Mesh mesh = shadow_handle->geometry.Release(Geometry::ReleaseMode::ClearMesh); + const byte alpha = byte(info.opacity * 255.f); MeshUtilities::GenerateQuad(mesh, -info.element_offset_in_texture, Vector2f(info.texture_dimensions), ColourbPremultiplied(alpha, alpha)); - Geometry shadow_geometry = render_manager.MakeGeometry(std::move(mesh)); - CallbackTexture shadow_texture; - GeometryBoxShadow::GenerateTexture(shadow_texture, render_manager, inserted_key); - auto shadow_handle = MakeShared(std::move(shadow_texture), std::move(shadow_geometry), inserted_key); + shadow_handle->geometry = render_manager.MakeGeometry(std::move(mesh)); + inserted_weak_data_pointer = shadow_handle; return shadow_handle; } @@ -114,14 +114,12 @@ static void ReleaseHandle(BoxShadowData* handle) handles.erase(it_handle); } -SharedPtr BoxShadowCache::GetHandle(Element* element, Geometry& background_border_geometry) +SharedPtr BoxShadowCache::GetHandle(Element* element, const ComputedValues& computed) { RenderManager* render_manager = element->GetRenderManager(); if (!render_manager) return {}; - const ComputedValues& computed = element->GetComputedValues(); - ColourbPremultiplied background_color = computed.background_color().ToPremultiplied(); Array border_colors = { computed.border_top_color().ToPremultiplied(), @@ -131,6 +129,6 @@ SharedPtr BoxShadowCache::GetHandle(Element* element, Geometry& b }; const CornerSizes border_radius = computed.border_radius(); BoxShadowGeometryInfo geom_info = GeometryBoxShadow::Resolve(element, border_radius, background_color, border_colors, computed.opacity()); - return GetOrCreateBoxShadow(*render_manager, geom_info, background_border_geometry); + return GetOrCreateBoxShadow(*render_manager, geom_info); } } // namespace Rml \ No newline at end of file diff --git a/Source/Core/BoxShadowCache.h b/Source/Core/BoxShadowCache.h index 321c54860..11cd98a5e 100644 --- a/Source/Core/BoxShadowCache.h +++ b/Source/Core/BoxShadowCache.h @@ -38,11 +38,12 @@ namespace Rml { struct BoxShadowGeometryInfo; struct BoxShadowData : NonCopyMoveable { - BoxShadowData(CallbackTexture&& texture, Geometry&& geometry, const BoxShadowGeometryInfo& geometry_info); + BoxShadowData(const BoxShadowGeometryInfo& geometry_info); ~BoxShadowData(); CallbackTexture texture; Geometry geometry; + Geometry background_border_geometry; const BoxShadowGeometryInfo& cache_key; }; @@ -54,7 +55,7 @@ class BoxShadowCache { /// Returns a handle to BoxShadow data matching the parameters - creates new data if none is found. /// @param[in] element Element for which to calculate and cache the box shadow /// @return A handle to the BoxShadow data, with automatic reference counting. - static SharedPtr GetHandle(Element* element, Geometry& background_border_geometry); + static SharedPtr GetHandle(Element* element, const ComputedValues& computed); }; } // namespace Rml diff --git a/Source/Core/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index 89141de06..e4bc3cdf9 100644 --- a/Source/Core/ElementBackgroundBorder.cpp +++ b/Source/Core/ElementBackgroundBorder.cpp @@ -146,7 +146,7 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) ConvertColor(computed.border_bottom_color()), ConvertColor(computed.border_left_color()), }; - const CornerSizes border_radius = computed.border_radius(); + // const CornerSizes border_radius = computed.border_radius(); Geometry& geometry = GetOrCreateBackground(BackgroundType::BackgroundBorder).geometry; Mesh mesh = geometry.Release(Geometry::ReleaseMode::ClearMesh); @@ -160,7 +160,7 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element) { // Generate the geometry for the box-shadow texture. Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadow); - box_shadow_cache = BoxShadowCache::GetHandle(element, geometry); + box_shadow_cache = BoxShadowCache::GetHandle(element, computed); shadow_background.texture = box_shadow_cache->texture; } } diff --git a/Source/Core/GeometryBoxShadow.cpp b/Source/Core/GeometryBoxShadow.cpp index 9bf91ebc4..c6b171a0b 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -115,11 +115,17 @@ BoxShadowGeometryInfo GeometryBoxShadow::Resolve(Element* element, const CornerS return geometry_info; } -void GeometryBoxShadow::GenerateTexture(CallbackTexture& out_shadow_texture, RenderManager& render_manager, const BoxShadowGeometryInfo& info) +void GeometryBoxShadow::GenerateTexture(CallbackTexture& out_shadow_texture, Geometry& background_border_geometry, RenderManager& render_manager, + const BoxShadowGeometryInfo& info) { + Mesh mesh = background_border_geometry.Release(Geometry::ReleaseMode::ClearMesh); + for (size_t i = 0; i < info.padding_render_boxes.size(); i++) + MeshUtilities::GenerateBackgroundBorder(mesh, info.padding_render_boxes[i], info.background_color, info.border_colors.data()); + background_border_geometry = render_manager.MakeGeometry(std::move(mesh)); + // Callback for generating the box-shadow texture. Using a callback ensures that the texture can be regenerated at any time, for example if the // device loses its GPU context and the client calls Rml::ReleaseTextures(). - auto texture_callback = [&info](const CallbackTextureInterface& texture_interface) -> bool { + auto texture_callback = [&info, &background_border_geometry](const CallbackTextureInterface& texture_interface) -> bool { RMLUI_ASSERT(info.border_render_boxes.size() == info.padding_render_boxes.size()); size_t num_boxes = info.border_render_boxes.size(); @@ -139,7 +145,7 @@ void GeometryBoxShadow::GenerateTexture(CallbackTexture& out_shadow_texture, Ren } // Generate the geometry for all the element's boxes and extend the render-texture further to cover all of them. - for (int i = 0; i < num_boxes; i++) + for (size_t i = 0; i < num_boxes; i++) { ColourbPremultiplied white(255); if (has_inner_shadow) @@ -170,9 +176,7 @@ void GeometryBoxShadow::GenerateTexture(CallbackTexture& out_shadow_texture, Ren } render_manager.PushLayer(); - Rml::Geometry background_border_geometry /*TODO*/; //= render_manager.MakeGeometry(Rml::Mesh(background_border_mesh)); background_border_geometry.Render(info.element_offset_in_texture); - background_border_geometry.Release(); for (int shadow_index = (int)info.shadow_list.size() - 1; shadow_index >= 0; shadow_index--) { @@ -198,7 +202,7 @@ void GeometryBoxShadow::GenerateTexture(CallbackTexture& out_shadow_texture, Ren Mesh mesh_shadow; // Generate the shadow geometry. For outer box-shadows it is rendered normally, while for inset box-shadows it is used as a clipping mask. - for (int i = 0; i < num_boxes; i++) + for (size_t i = 0; i < num_boxes; i++) { const float signed_spread_distance = (inset ? -spread_distance : spread_distance); RenderBox render_box = (inset ? info.padding_render_boxes : info.border_render_boxes)[i]; diff --git a/Source/Core/GeometryBoxShadow.h b/Source/Core/GeometryBoxShadow.h index d33df6df2..87a0dd7db 100644 --- a/Source/Core/GeometryBoxShadow.h +++ b/Source/Core/GeometryBoxShadow.h @@ -29,6 +29,9 @@ #ifndef RMLUI_CORE_GEOMETRYBOXSHADOW_H #define RMLUI_CORE_GEOMETRYBOXSHADOW_H +// std::hash not defined for Unit.h, how do we solve? I assume Unit.h is a very lightweight file. +#include "../../Include/RmlUi/Core/Utilities.h" + #include "../../Include/RmlUi/Core/DecorationTypes.h" #include "../../Include/RmlUi/Core/RenderBox.h" #include "../../Include/RmlUi/Core/Types.h" @@ -78,7 +81,7 @@ class GeometryBoxShadow { /// @param[in] render_manager The render manager to generate the shadow for. /// @param[in] shadow_geometry_info The resolved box shadow geometry of a given element. /// @see GeometryBoxShadow::Resolve() - static void GenerateTexture(CallbackTexture& out_shadow_texture, RenderManager& render_manager, + static void GenerateTexture(CallbackTexture& out_shadow_texture, Geometry& background_border_geometry, RenderManager& render_manager, const BoxShadowGeometryInfo& shadow_geometry_info); }; From 360d10921491bfeb6a8b8b64e83f0e5e07979ef6 Mon Sep 17 00:00:00 2001 From: ZilverBlade <90721817+ZilverBlade@users.noreply.github.com> Date: Sun, 9 Nov 2025 21:14:33 +0100 Subject: [PATCH 12/12] fixed some compiler errors --- Include/RmlUi/Core/Unit.h | 2 ++ Source/Core/BoxShadowCache.h | 5 ++++- Source/Core/GeometryBoxShadow.h | 3 --- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Include/RmlUi/Core/Unit.h b/Include/RmlUi/Core/Unit.h index 46f746ae1..75dac3889 100644 --- a/Include/RmlUi/Core/Unit.h +++ b/Include/RmlUi/Core/Unit.h @@ -29,7 +29,9 @@ #ifndef RMLUI_CORE_UNIT_H #define RMLUI_CORE_UNIT_H +// needed for std::hash #include "Header.h" +#include "Utilities.h" #include namespace Rml { diff --git a/Source/Core/BoxShadowCache.h b/Source/Core/BoxShadowCache.h index 11cd98a5e..8f1f90a8e 100644 --- a/Source/Core/BoxShadowCache.h +++ b/Source/Core/BoxShadowCache.h @@ -36,6 +36,9 @@ namespace Rml { struct BoxShadowGeometryInfo; +namespace Style { + class ComputedValues; +} // namespace Style struct BoxShadowData : NonCopyMoveable { BoxShadowData(const BoxShadowGeometryInfo& geometry_info); @@ -55,7 +58,7 @@ class BoxShadowCache { /// Returns a handle to BoxShadow data matching the parameters - creates new data if none is found. /// @param[in] element Element for which to calculate and cache the box shadow /// @return A handle to the BoxShadow data, with automatic reference counting. - static SharedPtr GetHandle(Element* element, const ComputedValues& computed); + static SharedPtr GetHandle(Element* element, const Style::ComputedValues& computed); }; } // namespace Rml diff --git a/Source/Core/GeometryBoxShadow.h b/Source/Core/GeometryBoxShadow.h index 87a0dd7db..1ed65514b 100644 --- a/Source/Core/GeometryBoxShadow.h +++ b/Source/Core/GeometryBoxShadow.h @@ -29,9 +29,6 @@ #ifndef RMLUI_CORE_GEOMETRYBOXSHADOW_H #define RMLUI_CORE_GEOMETRYBOXSHADOW_H -// std::hash not defined for Unit.h, how do we solve? I assume Unit.h is a very lightweight file. -#include "../../Include/RmlUi/Core/Utilities.h" - #include "../../Include/RmlUi/Core/DecorationTypes.h" #include "../../Include/RmlUi/Core/RenderBox.h" #include "../../Include/RmlUi/Core/Types.h"