2
0
Эх сурвалжийг харах

Implement cache for box shadows

This particularly helps in situations with many elements of the same size with the same box shadow. Then the box shadow render resources will be reused between elements. This helps with the initial document render time, and can also significantly lower the total texture size permanently.

Co-authored-by: Michael Ragazzon <[email protected]>
ZilverBlade 4 сар өмнө
parent
commit
0b314964da

+ 10 - 1
Include/RmlUi/Core/RenderBox.h

@@ -76,6 +76,15 @@ private:
 	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

+ 125 - 0
Source/Core/BoxShadowCache.cpp

@@ -0,0 +1,125 @@
+/*
+ * 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/ComputedValues.h"
+#include "../../Include/RmlUi/Core/MeshUtilities.h"
+#include "../../Include/RmlUi/Core/Profiling.h"
+#include "../../Include/RmlUi/Core/RenderManager.h"
+#include "../Core/ControlledLifetimeResource.h"
+#include "BoxShadowHash.h"
+#include "GeometryBoxShadow.h"
+
+namespace Rml {
+
+struct BoxShadowCacheData {
+	StableUnorderedMap<BoxShadowGeometryInfo, WeakPtr<BoxShadowRenderable>> handles;
+};
+
+static void ReleaseHandle(BoxShadowRenderable* handle);
+
+BoxShadowRenderable::BoxShadowRenderable(const BoxShadowGeometryInfo& geometry_info) : cache_key(geometry_info) {}
+
+BoxShadowRenderable::~BoxShadowRenderable()
+{
+	ReleaseHandle(this);
+}
+
+static ControlledLifetimeResource<BoxShadowCacheData> shadow_cache_data;
+
+void BoxShadowCache::Initialize()
+{
+	shadow_cache_data.Initialize();
+}
+
+void BoxShadowCache::Shutdown()
+{
+	shadow_cache_data.Shutdown();
+}
+
+static SharedPtr<BoxShadowRenderable> GetOrCreateBoxShadow(RenderManager& render_manager, const BoxShadowGeometryInfo& info)
+{
+	RMLUI_ZoneScoped;
+	auto it_handle = shadow_cache_data->handles.find(info);
+	if (it_handle != shadow_cache_data->handles.end())
+	{
+		SharedPtr<BoxShadowRenderable> 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<BoxShadowRenderable>());
+	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<BoxShadowRenderable>& inserted_weak_data_pointer = iterator_inserted.first->second;
+
+	auto shadow_handle = MakeShared<BoxShadowRenderable>(inserted_key);
+	GeometryBoxShadow::GenerateTexture(shadow_handle->texture, shadow_handle->background_border_geometry, render_manager, inserted_key);
+
+	Mesh mesh;
+	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(BoxShadowRenderable* 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<BoxShadowRenderable> BoxShadowCache::GetHandle(Element* element, const ComputedValues& computed)
+{
+	RenderManager* render_manager = element->GetRenderManager();
+	if (!render_manager)
+		return {};
+
+	ColourbPremultiplied background_color = computed.background_color().ToPremultiplied();
+	Array<ColourbPremultiplied, 4> 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

+ 65 - 0
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-2025 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/Types.h"
+
+namespace Rml {
+namespace Style {
+	class ComputedValues;
+}
+struct BoxShadowGeometryInfo;
+
+struct BoxShadowRenderable : NonCopyMoveable {
+	BoxShadowRenderable(const BoxShadowGeometryInfo& geometry_info);
+	~BoxShadowRenderable();
+
+	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 renderable matching the element's style - creates new data if none is found.
+	/// @param[in] element Element for which to calculate and cache the box shadow.
+	/// @param[in] computed The computed style values of the element.
+	/// @return A handle to the BoxShadow data, with automatic reference counting.
+	static SharedPtr<BoxShadowRenderable> GetHandle(Element* element, const Style::ComputedValues& computed);
+};
+
+} // namespace Rml
+#endif

+ 174 - 0
Source/Core/BoxShadowHash.h

@@ -0,0 +1,174 @@
+/*
+ * 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-2025 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_BOXSHADOWHASH_H
+#define RMLUI_CORE_BOXSHADOWHASH_H
+
+#include "../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../Include/RmlUi/Core/NumericValue.h"
+#include "../../Include/RmlUi/Core/RenderBox.h"
+#include "../../Include/RmlUi/Core/Types.h"
+#include "../../Include/RmlUi/Core/Unit.h"
+#include "../../Include/RmlUi/Core/Utilities.h"
+#include "BoxShadowCache.h"
+#include "GeometryBoxShadow.h"
+
+namespace std {
+
+template <>
+struct hash<::Rml::Unit> {
+	using utype = underlying_type_t<::Rml::Unit>;
+	size_t operator()(const ::Rml::Unit& t) const noexcept
+	{
+		hash<utype> h;
+		return h(static_cast<utype>(t));
+	}
+};
+
+template <>
+struct hash<::Rml::Vector2i> {
+	size_t operator()(const ::Rml::Vector2i& v) const noexcept
+	{
+		using namespace ::Rml::Utilities;
+		size_t seed = hash<int>{}(v.x);
+		HashCombine(seed, v.y);
+		return seed;
+	}
+};
+template <>
+struct hash<::Rml::Vector2f> {
+	size_t operator()(const ::Rml::Vector2f& v) const noexcept
+	{
+		using namespace ::Rml::Utilities;
+		size_t seed = hash<float>{}(v.x);
+		HashCombine(seed, v.y);
+		return seed;
+	}
+};
+template <>
+struct hash<::Rml::Colourb> {
+	size_t operator()(const ::Rml::Colourb& v) const noexcept { return static_cast<size_t>(hash<uint32_t>{}(reinterpret_cast<const uint32_t&>(v))); }
+};
+template <>
+struct hash<::Rml::ColourbPremultiplied> {
+	size_t operator()(const ::Rml::ColourbPremultiplied& v) const noexcept
+	{
+		return static_cast<size_t>(hash<uint32_t>{}(reinterpret_cast<const uint32_t&>(v)));
+	}
+};
+
+template <>
+struct hash<::Rml::NumericValue> {
+	size_t operator()(const ::Rml::NumericValue& v) const noexcept
+	{
+		using namespace ::Rml::Utilities;
+		size_t seed = hash<float>{}(v.number);
+		HashCombine(seed, v.unit);
+		return seed;
+	}
+};
+
+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<ColourbPremultiplied>{}(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;
+	}
+};
+
+template <>
+struct hash<::Rml::RenderBox> {
+	size_t operator()(const ::Rml::RenderBox& box) const noexcept
+	{
+		using namespace ::Rml::Utilities;
+		static auto HashArray4 = [](const ::Rml::Array<float, 4>& 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;
+	}
+};
+
+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

+ 3 - 0
Source/Core/CMakeLists.txt

@@ -4,6 +4,9 @@
 add_library(rmlui_core
 	BaseXMLParser.cpp
 	Box.cpp
+	BoxShadowCache.h
+	BoxShadowCache.cpp
+	BoxShadowHash.h
 	CallbackTexture.cpp
 	Clock.cpp
 	Clock.h

+ 4 - 0
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();

+ 31 - 39
Source/Core/ElementBackgroundBorder.cpp

@@ -33,7 +33,9 @@
 #include "../../Include/RmlUi/Core/DecorationTypes.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/MeshUtilities.h"
+#include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/RenderManager.h"
+#include "BoxShadowCache.h"
 #include "GeometryBoxShadow.h"
 
 namespace Rml {
@@ -56,12 +58,14 @@ void ElementBackgroundBorder::Render(Element* element)
 		border_dirty = false;
 	}
 
-	Background* shadow = GetBackground(BackgroundType::BoxShadow);
-	if (shadow && shadow->geometry)
-		shadow->geometry.Render(element->GetAbsoluteOffset(BoxArea::Border), shadow->texture);
+	if (Background* shadow = GetBackground(BackgroundType::BoxShadowAndBackgroundBorder))
+	{
+		const Vector2f offset = element->GetAbsoluteOffset(BoxArea::Border);
+		shadow->box_shadow_and_background_border->geometry.Render(offset, shadow->box_shadow_and_background_border->texture);
+	}
 	else if (Background* background = GetBackground(BackgroundType::BackgroundBorder))
 	{
-		auto offset = element->GetAbsoluteOffset(BoxArea::Border);
+		const Vector2f offset = element->GetAbsoluteOffset(BoxArea::Border);
 		background->geometry.Render(offset);
 	}
 }
@@ -117,60 +121,48 @@ ElementBackgroundBorder::Background& ElementBackgroundBorder::GetOrCreateBackgro
 	return background;
 }
 
+void ElementBackgroundBorder::EraseBackground(BackgroundType type)
+{
+	backgrounds.erase(type);
+}
+
 void ElementBackgroundBorder::GenerateGeometry(Element* element)
 {
+	RMLUI_ZoneScoped;
 	RenderManager* render_manager = element->GetRenderManager();
 	if (!render_manager)
 		return;
 
 	const ComputedValues& computed = element->GetComputedValues();
 	const bool has_box_shadow = computed.has_box_shadow();
-	const float opacity = computed.opacity();
 
-	// Apply opacity except if we have a box shadow. In the latter case the background is rendered opaquely into the box-shadow texture, while
-	// opacity is applied to the entire box-shadow texture when that is rendered.
-	bool apply_opacity = (!has_box_shadow && opacity < 1.f);
+	if (has_box_shadow)
+	{
+		// The box shadow geometry also includes the element's background and border, thus we can skip the normal background generation.
+		EraseBackground(BackgroundType::BackgroundBorder);
+		Background& shadow_background = GetOrCreateBackground(BackgroundType::BoxShadowAndBackgroundBorder);
+		shadow_background.box_shadow_and_background_border = BoxShadowCache::GetHandle(element, computed);
+		return;
+	}
 
-	auto ConvertColor = [=](Colourb color) {
-		if (apply_opacity)
-			return color.ToPremultiplied(opacity);
-		else
-			return color.ToPremultiplied();
-	};
+	EraseBackground(BackgroundType::BoxShadowAndBackgroundBorder);
 
-	ColourbPremultiplied background_color = ConvertColor(computed.background_color());
-	ColourbPremultiplied border_colors[4] = {
-		ConvertColor(computed.border_top_color()),
-		ConvertColor(computed.border_right_color()),
-		ConvertColor(computed.border_bottom_color()),
-		ConvertColor(computed.border_left_color()),
+	const float opacity = computed.opacity();
+	ColourbPremultiplied background_color = computed.background_color().ToPremultiplied(opacity);
+	Array<ColourbPremultiplied, 4> border_colors = {
+		computed.border_top_color().ToPremultiplied(opacity),
+		computed.border_right_color().ToPremultiplied(opacity),
+		computed.border_bottom_color().ToPremultiplied(opacity),
+		computed.border_left_color().ToPremultiplied(opacity),
 	};
-	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<BoxShadowList>();
-
-		// 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());
-	}
 }
 
 } // namespace Rml

+ 6 - 3
Source/Core/ElementBackgroundBorder.h

@@ -35,10 +35,11 @@
 
 namespace Rml {
 
+struct BoxShadowRenderable;
+
 class ElementBackgroundBorder {
 public:
 	ElementBackgroundBorder();
-
 	void Render(Element* element);
 
 	void DirtyBackground();
@@ -47,14 +48,16 @@ public:
 	Geometry* GetClipGeometry(Element* element, BoxArea clip_area);
 
 private:
-	enum class BackgroundType { BackgroundBorder, BoxShadow, ClipBorder, ClipPadding, ClipContent, Count };
+	enum class BackgroundType { BackgroundBorder, BoxShadowAndBackgroundBorder, ClipBorder, ClipPadding, ClipContent, Count };
 	struct Background {
 		Geometry geometry;
-		CallbackTexture texture;
+		Texture texture;
+		SharedPtr<BoxShadowRenderable> box_shadow_and_background_border;
 	};
 
 	Background* GetBackground(BackgroundType type);
 	Background& GetOrCreateBackground(BackgroundType type);
+	void EraseBackground(BackgroundType type);
 
 	void GenerateGeometry(Element* element);
 

+ 68 - 30
Source/Core/GeometryBoxShadow.cpp

@@ -34,17 +34,24 @@
 #include "../../Include/RmlUi/Core/Geometry.h"
 #include "../../Include/RmlUi/Core/Math.h"
 #include "../../Include/RmlUi/Core/MeshUtilities.h"
+#include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/RenderManager.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<ColourbPremultiplied, 4>& border_colors, float opacity)
 {
+	RMLUI_ZoneScoped;
+
 	// 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<BoxShadowList>();
+
 	// Resolve all lengths to px units.
 	for (BoxShadow& shadow : shadow_list)
 	{
@@ -86,10 +93,48 @@ 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& out_background_border_geometry, RenderManager& render_manager,
+	const BoxShadowGeometryInfo& info)
+{
+	RMLUI_ZoneScoped;
+
+	Mesh mesh = out_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());
+	out_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, &out_background_border_geometry](const CallbackTextureInterface& texture_interface) -> bool {
+		RMLUI_ASSERT(info.border_render_boxes.size() == info.padding_render_boxes.size());
+		RMLUI_ZoneScopedN("BoxShadow::GenerateTexture::Callback");
+		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 +142,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 +151,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 +173,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();
+		out_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 +208,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 +230,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)
@@ -223,12 +267,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

+ 40 - 10
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<RenderBox>;
+struct BoxShadowGeometryInfo {
+	ColourbPremultiplied background_color;
+	Array<ColourbPremultiplied, 4> 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;
@@ -40,19 +64,25 @@ class RenderManager;
 
 class GeometryBoxShadow {
 public:
-	/// Generate the texture and geometry for a box shadow.
-	/// @param[out] out_shadow_geometry The target geometry.
+	/// Resolve the element's properties into its box geometry info.
+	/// @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<ColourbPremultiplied, 4>& border_colors, float opacity);
+
+	/// Generate the texture and geometry for a box shadow and including the element's background and border.
 	/// @param[out] out_shadow_texture The target texture, assumes pointer stability during the lifetime of the shadow geometry.
+	/// @param[out] out_background_border_geometry The generated geometry for the element's background and border.
 	/// @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& out_background_border_geometry, RenderManager& render_manager,
+		const BoxShadowGeometryInfo& shadow_geometry_info);
 };
 
 } // namespace Rml
+
 #endif