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..0a4566b1b 100644 --- a/Include/RmlUi/Core/NumericValue.h +++ b/Include/RmlUi/Core/NumericValue.h @@ -52,5 +52,16 @@ inline bool operator!=(const NumericValue& a, const NumericValue& b) } } // namespace Rml - +namespace std { +template <> +struct hash<::Rml::NumericValue> { + 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 6d808a1fc..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 { @@ -76,6 +77,39 @@ 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 + +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 151a278ea..72eb622e2 100644 --- a/Include/RmlUi/Core/RenderManager.h +++ b/Include/RmlUi/Core/RenderManager.h @@ -30,13 +30,14 @@ #define RMLUI_CORE_RENDERMANAGER_H #include "CallbackTexture.h" +#include "DecorationTypes.h" #include "Mesh.h" +#include "RenderBox.h" #include "RenderInterface.h" #include "StableVector.h" #include "Types.h" namespace Rml { - class Geometry; class CompiledFilter; class CompiledShader; @@ -67,10 +68,10 @@ struct RenderState { }; /** - A wrapper over the render interface, which tracks its state and resources. + 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. - */ + All operations to be submitted to the render interface should go through this class. + */ class RMLUICORE_API RenderManager : NonCopyMoveable { public: RenderManager(RenderInterface* render_interface); diff --git a/Include/RmlUi/Core/Types.h b/Include/RmlUi/Core/Types.h index f3d2093a1..63bdf3bc8 100644 --- a/Include/RmlUi/Core/Types.h +++ b/Include/RmlUi/Core/Types.h @@ -193,6 +193,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..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 { @@ -112,4 +114,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 = underlying_type_t<::Rml::Unit>; + size_t operator()(const ::Rml::Unit& t) const noexcept + { + 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..f50247436 --- /dev/null +++ b/Source/Core/BoxShadowCache.cpp @@ -0,0 +1,134 @@ +/* + * 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/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/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; +}; + +static void ReleaseHandle(BoxShadowData* handle); + +BoxShadowData::BoxShadowData(const BoxShadowGeometryInfo& geometry_info) : + cache_key(geometry_info) +{} + +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 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 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 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); + + // 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)); + shadow_handle->geometry = render_manager.MakeGeometry(std::move(mesh)); + + 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); +} + +SharedPtr BoxShadowCache::GetHandle(Element* element, const ComputedValues& computed) +{ + RenderManager* render_manager = element->GetRenderManager(); + if (!render_manager) + return {}; + + 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, border_radius, background_color, border_colors, computed.opacity()); + 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..8f1f90a8e --- /dev/null +++ b/Source/Core/BoxShadowCache.h @@ -0,0 +1,65 @@ +/* + * 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/CallbackTexture.h" +#include "../../Include/RmlUi/Core/Geometry.h" +#include "../../Include/RmlUi/Core/RenderBox.h" +#include "../../Include/RmlUi/Core/Types.h" + +namespace Rml { +struct BoxShadowGeometryInfo; +namespace Style { + class ComputedValues; +} // namespace Style + +struct BoxShadowData : NonCopyMoveable { + BoxShadowData(const BoxShadowGeometryInfo& geometry_info); + ~BoxShadowData(); + + CallbackTexture texture; + Geometry geometry; + Geometry background_border_geometry; + 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, const Style::ComputedValues& computed); +}; + +} // namespace Rml +#endif \ No newline at end of file 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/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/ElementBackgroundBorder.cpp b/Source/Core/ElementBackgroundBorder.cpp index 621ec626d..e4bc3cdf9 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 { @@ -57,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); @@ -139,37 +140,28 @@ 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()), 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); 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)); if (has_box_shadow) { - 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; - CallbackTexture& 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()); + box_shadow_cache = BoxShadowCache::GetHandle(element, computed); + shadow_background.texture = box_shadow_cache->texture; } } diff --git a/Source/Core/ElementBackgroundBorder.h b/Source/Core/ElementBackgroundBorder.h index d93038606..ace1ceda5 100644 --- a/Source/Core/ElementBackgroundBorder.h +++ b/Source/Core/ElementBackgroundBorder.h @@ -34,11 +34,10 @@ #include "../../Include/RmlUi/Core/Types.h" namespace Rml { - +struct BoxShadowData; 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; + Texture texture; }; Background* GetBackground(BackgroundType type); @@ -62,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 93f68b743..c6b171a0b 100644 --- a/Source/Core/GeometryBoxShadow.cpp +++ b/Source/Core/GeometryBoxShadow.cpp @@ -35,16 +35,20 @@ #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, CallbackTexture& out_shadow_texture, RenderManager& render_manager, Element* element, - Geometry& background_border_geometry, BoxShadowList shadow_list, const CornerSizes border_radius, const float opacity) +BoxShadowGeometryInfo GeometryBoxShadow::Resolve(Element* element, const CornerSizes& border_radius, + 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; 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) { @@ -86,10 +90,45 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& texture_dimensions = Vector2i(texture_region.Size()); } + // 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. + 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 + BoxShadowGeometryInfo geometry_info; + 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; + 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; +} + +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 = [&background_border_geometry, element, border_radius, texture_dimensions, element_offset_in_texture, - shadow_list = std::move(shadow_list)](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(); + RenderManager& render_manager = texture_interface.GetRenderManager(); Mesh mesh_padding; // Render geometry for inner box-shadow. @@ -97,7 +136,7 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& 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; @@ -106,18 +145,18 @@ 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 (size_t 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, info.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, info.border_render_boxes[i], white); } const RenderState initial_render_state = render_manager.GetState(); render_manager.ResetState(); - render_manager.SetScissorRegion(Rectanglei::FromSize(texture_dimensions)); + render_manager.SetScissorRegion(Rectanglei::FromSize(info.texture_dimensions)); // The scissor region will be clamped to the current window size, check the resulting scissor region. const Rectanglei scissor_region = render_manager.GetScissorRegion(); @@ -128,27 +167,26 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& render_manager.SetState(initial_render_state); return false; } - if (scissor_region != Rectanglei::FromSize(texture_dimensions)) + if (scissor_region != Rectanglei::FromSize(info.texture_dimensions)) { 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.", + info.texture_dimensions.x, info.texture_dimensions.y, scissor_region.Width(), scissor_region.Height()); } render_manager.PushLayer(); + background_border_geometry.Render(info.element_offset_in_texture); - background_border_geometry.Render(element_offset_in_texture); - - 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]; @@ -164,10 +202,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 (size_t 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 ? info.padding_render_boxes : info.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)); @@ -186,23 +224,23 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& if (inset) { - render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_shadow, shadow_offset + element_offset_in_texture); + render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_shadow, shadow_offset + info.element_offset_in_texture); for (Rml::Vertex& vertex : mesh_padding.vertices) vertex.colour = shadow.color; // @performance: Don't need to copy the mesh if this is the last use of it. Geometry geometry_padding = render_manager.MakeGeometry(Mesh(mesh_padding)); - geometry_padding.Render(element_offset_in_texture); + geometry_padding.Render(info.element_offset_in_texture); - render_manager.SetClipMask(ClipMaskOperation::Set, &geometry_padding, element_offset_in_texture); + render_manager.SetClipMask(ClipMaskOperation::Set, &geometry_padding, info.element_offset_in_texture); } else { Mesh mesh = mesh_padding_border; Geometry geometry_padding_border = render_manager.MakeGeometry(std::move(mesh)); - render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_padding_border, element_offset_in_texture); - geometry_shadow.Render(shadow_offset + element_offset_in_texture); + render_manager.SetClipMask(ClipMaskOperation::SetInverse, &geometry_padding_border, info.element_offset_in_texture); + geometry_shadow.Render(shadow_offset + info.element_offset_in_texture); } if (blur) @@ -222,13 +260,6 @@ void GeometryBoxShadow::Generate(Geometry& out_shadow_geometry, CallbackTexture& return true; }; - - Mesh mesh = out_shadow_geometry.Release(Geometry::ReleaseMode::ClearMesh); - 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_geometry = render_manager.MakeGeometry(std::move(mesh)); } - } // namespace Rml diff --git a/Source/Core/GeometryBoxShadow.h b/Source/Core/GeometryBoxShadow.h index 83713848a..1ed65514b 100644 --- a/Source/Core/GeometryBoxShadow.h +++ b/Source/Core/GeometryBoxShadow.h @@ -29,10 +29,34 @@ #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" 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; + 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.opacity == b.opacity; +} +inline bool operator!=(const BoxShadowGeometryInfo& a, const BoxShadowGeometryInfo& b) +{ + return !(a == b); +} class Geometry; class CallbackTexture; @@ -41,18 +65,62 @@ class RenderManager; class GeometryBoxShadow { public: /// Generate the texture and geometry for a box shadow. - /// @param[out] out_shadow_geometry The target geometry. + /// @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. + /// @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. /// @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] shadow_list The list of box-shadows to generate. - /// @param[in] border_radius The border radius 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); + /// @param[in] shadow_geometry_info The resolved box shadow geometry of a given element. + /// @see GeometryBoxShadow::Resolve() + static void GenerateTexture(CallbackTexture& out_shadow_texture, Geometry& background_border_geometry, RenderManager& render_manager, + const BoxShadowGeometryInfo& shadow_geometry_info); }; } // 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 ::Rml::BoxShadow& v : in.shadow_list) + { + HashCombine(seed, v); + } + HashCombine(seed, in.opacity); + return seed; + } +}; +} // namespace std #endif