Browse Source

Add more layout cache testing and benchmarks

Michael Ragazzon 4 tháng trước cách đây
mục cha
commit
41195e8f21

+ 173 - 66
Tests/Source/Benchmarks/ElementDocument.cpp

@@ -32,6 +32,7 @@
 #include <RmlUi/Core/Element.h>
 #include <RmlUi/Core/ElementDocument.h>
 #include <RmlUi/Core/Factory.h>
+#include <RmlUi/Core/StringUtilities.h>
 #include <RmlUi/Core/Types.h>
 #include <doctest.h>
 #include <nanobench.h>
@@ -52,7 +53,7 @@ static const String document_rml = R"(
 			width: 800px;
 			height: 200px;
 		}
-		#performance 
+		#performance
 		{
 			width: 500px;
 			height: 300px;
@@ -86,97 +87,203 @@ TEST_CASE("elementdocument")
 	Context* context = TestsShell::GetContext();
 	REQUIRE(context);
 
+	std::string title = "ElementDocument";
+	String modified_document_rml = document_rml;
+	bool clear_style_sheet_cache = false;
+
+	SUBCASE("ElementDocument")
+	{
+		clear_style_sheet_cache = false;
+	}
+	SUBCASE("ElementDocument w/o Layout Cache")
 	{
-		ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
+		title += " w/o Layout Cache";
+		clear_style_sheet_cache = false;
+		modified_document_rml = StringUtilities::Replace(document_rml, "<body", "<body rmlui-disable-layout-cache");
+	}
+	SUBCASE("ElementDocument w/o Style Sheet Cache")
+	{
+		title += " w/o Style Sheet Cache";
+		clear_style_sheet_cache = true;
+	}
+
+	auto ConditionallyClearStyleSheetCache = [&]() {
+		if (clear_style_sheet_cache)
+			Factory::ClearStyleSheetCache();
+	};
+
+	nanobench::Bench bench;
+	bench.title(title);
+	bench.timeUnit(std::chrono::microseconds(1), "us");
+	bench.relative(true);
+	bench.warmup(10);
+
+	bench.run("LoadDocument", [&] {
+		ConditionallyClearStyleSheetCache();
+		ElementDocument* document = context->LoadDocumentFromMemory(modified_document_rml);
+		document->Close();
+		context->Update();
+	});
+
+	bench.run("LoadDocument + Show", [&] {
+		ConditionallyClearStyleSheetCache();
+		ElementDocument* document = context->LoadDocumentFromMemory(modified_document_rml);
 		document->Show();
+		document->Close();
+		context->Update();
+	});
 
-		const String stats = TestsShell::GetRenderStats();
-		MESSAGE(stats);
+	bench.run("LoadDocument + Show + Update", [&] {
+		ConditionallyClearStyleSheetCache();
+		ElementDocument* document = context->LoadDocumentFromMemory(modified_document_rml);
+		document->Show();
+		context->Update();
+		document->Close();
+		context->Update();
+	});
 
-		TestsShell::RenderLoop();
+	bench.run("LoadDocument + Show + Update + Render", [&] {
+		ConditionallyClearStyleSheetCache();
+		ElementDocument* document = context->LoadDocumentFromMemory(modified_document_rml);
+		document->Show();
+		context->Update();
+		TestsShell::BeginFrame();
+		context->Render();
+		TestsShell::PresentFrame();
 		document->Close();
 		context->Update();
-	}
+	});
+}
 
-	{
-		nanobench::Bench bench;
-		bench.title("ElementDocument");
-		bench.timeUnit(std::chrono::microseconds(1), "us");
-		bench.relative(true);
+TEST_CASE("elementdocument.render_stats")
+{
+	Context* context = TestsShell::GetContext();
+	REQUIRE(context);
 
-		bench.run("LoadDocument", [&] {
-			ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
-			document->Close();
-			context->Update();
-		});
+	ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
+	document->Show();
 
-		bench.run("LoadDocument + Show", [&] {
-			ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
-			document->Show();
-			document->Close();
-			context->Update();
-		});
+	const String stats = TestsShell::GetRenderStats();
+	MESSAGE(stats);
 
-		bench.run("LoadDocument + Show + Update", [&] {
-			ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
-			document->Show();
-			context->Update();
-			document->Close();
-			context->Update();
-		});
+	TestsShell::RenderLoop();
+	document->Close();
+}
 
-		bench.run("LoadDocument + Show + Update + Render", [&] {
-			ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
-			document->Show();
-			context->Update();
-			TestsShell::BeginFrame();
-			context->Render();
-			TestsShell::PresentFrame();
-			document->Close();
-			context->Update();
-		});
+static const String document_isolation_rml = R"(
+<rml>
+<head>
+    <link type="text/rcss" href="/assets/rml.rcss"/>
+    <style>
+        body {
+            width: 800px;
+            height: 600px;
+            background-color: #ddd;
+			font-family: LatoLatin;
+			font-size: 14px;
+        }
+		scrollbarvertical {
+			width: 12px;
+			cursor: arrow;
+			margin-right: -1px;
+			padding-right: 1px;
+		}
+		scrollbarvertical slidertrack {
+			background-color: #f0f0f0;
+		}
+		scrollbarvertical sliderbar {
+			background-color: #666;
+		}
+        .container {
+            width: 200px;
+            margin: 20px;
+            padding: 10px;
+            background-color: #bbb;
+        }
+        .container > div {
+            background-color: #aaa;
+            margin: 5px;
+            padding: 10px;
+        }
+        #overflow-container {
+            overflow: scroll;
+            height: 150px;
+        }
+        #absolute-container {
+            position: absolute;
+            top: 300px;
+            left: 300px;
+			max-height: 300px;
+			overflow: scroll;
+        }
+    </style>
+</head>
+<body %s>
+    <div class="container" id="overflow-container">
+        <div id="overflow-item">Overflow item</div>
+        %s
+    </div>
+
+    <div class="container" id="absolute-container">
+        <div id="absolute-item">Absolute item</div>
+		%s
+    </div>
+</body>
+</rml>
+)";
+
+TEST_CASE("elementdocument.layout_cache")
+{
+	Context* context = TestsShell::GetContext();
+
+	auto CreateIsolationDocument = [&](bool disable_layout_cache, int num_overflow_items, int num_absolute_items) {
+		return CreateString(document_isolation_rml.c_str(), disable_layout_cache ? "rmlui-disable-layout-cache" : "",
+			StringUtilities::RepeatString("<div>Overflow item</div>", num_overflow_items).c_str(),
+			StringUtilities::RepeatString("<div>Absolute item</div>", num_absolute_items).c_str());
+	};
+
+	constexpr int num_items = 20;
+	{
+		ElementDocument* document = context->LoadDocumentFromMemory(CreateIsolationDocument(false, num_items, num_items));
+		document->Show();
+		TestsShell::RenderLoop();
+		document->Close();
+		context->Update();
 	}
 
+	for (const String& modify_item_id : {"overflow-item", "absolute-item"})
 	{
 		nanobench::Bench bench;
-		bench.title("ElementDocument w/ClearStyleSheetCache");
+		bench.title("Layout cache (" + modify_item_id + ")");
 		bench.timeUnit(std::chrono::microseconds(1), "us");
 		bench.relative(true);
 
-		bench.run("Clear + LoadDocument", [&] {
-			Factory::ClearStyleSheetCache();
-			ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
-			document->Close();
-			context->Update();
-		});
-
-		bench.run("Clear + LoadDocument + Show", [&] {
-			Factory::ClearStyleSheetCache();
-			ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
-			document->Show();
-			document->Close();
-			context->Update();
-		});
-
-		bench.run("Clear + LoadDocument + Show + Update", [&] {
-			Factory::ClearStyleSheetCache();
-			ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
+		{
+			ElementDocument* document = context->LoadDocumentFromMemory(CreateIsolationDocument(false, num_items, num_items));
 			document->Show();
 			context->Update();
+			Element* element = document->GetElementById(modify_item_id);
+			bench.run("Enabled layout cache", [&] {
+				element->SetInnerRML("Changed item");
+				context->Update();
+			});
 			document->Close();
 			context->Update();
-		});
+		}
 
-		bench.run("Clear + LoadDocument + Show + Update + Render", [&] {
-			Factory::ClearStyleSheetCache();
-			ElementDocument* document = context->LoadDocumentFromMemory(document_rml);
+		{
+			ElementDocument* document = context->LoadDocumentFromMemory(CreateIsolationDocument(true, num_items, num_items));
 			document->Show();
 			context->Update();
-			TestsShell::BeginFrame();
-			context->Render();
-			TestsShell::PresentFrame();
+			Element* element = document->GetElementById(modify_item_id);
+			bench.run("Disabled layout cache", [&] {
+				element->SetInnerRML("Changed item");
+				context->Update();
+			});
 			document->Close();
 			context->Update();
-		});
+		}
 	}
+
+	TestsShell::ShutdownShell();
 }

+ 77 - 25
Tests/Source/UnitTests/LayoutIsolation.cpp

@@ -186,40 +186,40 @@ TEST_CASE("LayoutIsolation.InsideOutsideFormattingContexts")
 	{
 		Element* element = document->GetElementById("flex-item");
 		Element* next_sibling = element->GetNextSibling();
-		Element* next_outside_overflow = document->GetElementById("normal-container");
-		REQUIRE(element);
-		REQUIRE(next_sibling);
-		REQUIRE(next_outside_overflow);
+		Element* normal_container = document->GetElementById("normal-container");
+		Element* normal_item = document->GetElementById("normal-item");
 
 		const Vector2f absolute_offset_element = element->GetAbsoluteOffset();
 		const Vector2f absolute_offset_next_sibling = next_sibling->GetAbsoluteOffset();
-		const Vector2f absolute_offset_next_outside_overflow = next_outside_overflow->GetAbsoluteOffset();
+		const Vector2f absolute_offset_normal_container = normal_container->GetAbsoluteOffset();
+		const Vector2f absolute_offset_normal_item = normal_item->GetAbsoluteOffset();
 		rmlui_dynamic_cast<ElementText*>(element->GetFirstChild())->SetText("Modified text that is long enough to cause line break");
 
 		TestsShell::RenderLoop();
 		CHECK(element->GetAbsoluteOffset() == absolute_offset_element);
 		CHECK(next_sibling->GetAbsoluteOffset() != absolute_offset_next_sibling);
-		CHECK(next_outside_overflow->GetAbsoluteOffset() != absolute_offset_next_outside_overflow);
+		CHECK(normal_container->GetAbsoluteOffset() != absolute_offset_normal_container);
+		CHECK(normal_item->GetAbsoluteOffset() != absolute_offset_normal_item);
 	}
 
 	SUBCASE("Overflow")
 	{
 		Element* element = document->GetElementById("overflow-item");
 		Element* next_sibling = element->GetNextSibling();
-		Element* next_outside_overflow = document->GetElementById("normal-container");
-		REQUIRE(element);
-		REQUIRE(next_sibling);
-		REQUIRE(next_outside_overflow);
+		Element* normal_container = document->GetElementById("normal-container");
+		Element* normal_item = document->GetElementById("normal-item");
 
 		const Vector2f absolute_offset_element = element->GetAbsoluteOffset();
 		const Vector2f absolute_offset_next_sibling = next_sibling->GetAbsoluteOffset();
-		const Vector2f absolute_offset_next_outside_overflow = next_outside_overflow->GetAbsoluteOffset();
+		const Vector2f absolute_offset_normal_container = normal_container->GetAbsoluteOffset();
+		const Vector2f absolute_offset_normal_item = normal_item->GetAbsoluteOffset();
 		rmlui_dynamic_cast<ElementText*>(element->GetFirstChild())->SetText("Modified text that is long enough to cause line break");
 
 		TestsShell::RenderLoop();
 		CHECK(element->GetAbsoluteOffset() == absolute_offset_element);
 		CHECK(next_sibling->GetAbsoluteOffset() != absolute_offset_next_sibling);
-		CHECK(next_outside_overflow->GetAbsoluteOffset() == absolute_offset_next_outside_overflow);
+		CHECK(normal_container->GetAbsoluteOffset() == absolute_offset_normal_container);
+		CHECK(normal_item->GetAbsoluteOffset() == absolute_offset_normal_item);
 	}
 
 	SUBCASE("Absolute")
@@ -263,22 +263,36 @@ TEST_CASE("LayoutIsolation.InsideOutsideFormattingContexts")
 TEST_CASE("LayoutIsolation.FullLayoutFormatIndependentCount")
 {
 	Context* context = TestsShell::GetContext();
-	FormatIndependentDebugTracker format_independent_tracker;
-	ElementDocument* document = context->LoadDocumentFromMemory(document_isolation_rml);
+	ElementDocument* document = nullptr;
 
-	document->Show();
-	TestsShell::RenderLoop();
+	{
+		FormatIndependentDebugTracker format_independent_tracker;
+		document = context->LoadDocumentFromMemory(document_isolation_rml);
+		document->Show();
+		TestsShell::RenderLoop();
 
-	format_independent_tracker.LogMessage();
+		format_independent_tracker.LogMessage();
+
+		const auto count_level_1 = std::count_if(format_independent_tracker.GetEntries().begin(), format_independent_tracker.GetEntries().end(),
+			[](const auto& entry) { return entry.level == 1 && !entry.from_cache; });
+		CHECK_MESSAGE(count_level_1 == 3, "Expecting one entry for each of flex, overflow, and absolute");
+
+		// There are quite a few flex item format occurrences being performed currently. We might reduce the following
+		// number while working on the flex formatting engine. If this fails for any other reason, it is likely a bug.
+		CHECK(format_independent_tracker.CountEntries() == 10);
+		CHECK(format_independent_tracker.CountFormattedEntries() == 10);
+	}
 
-	const auto count_level_1 = std::count_if(format_independent_tracker.GetEntries().begin(), format_independent_tracker.GetEntries().end(),
-		[](const auto& entry) { return entry.level == 1; });
-	CHECK_MESSAGE(count_level_1 == 3, "Expecting one entry for each of flex, overflow, and absolute");
+	{
+		FormatIndependentDebugTracker format_independent_tracker;
+		Element* element = document->GetElementById("flex-item");
+		rmlui_dynamic_cast<ElementText*>(element->GetFirstChild())->SetText("Modified text that is long enough to cause line break");
 
-	// There are quite a few flex item format occurrences being performed currently. We might reduce the following
-	// number while working on the flex formatting engine. If this fails for any other reason, it is likely a bug.
-	CHECK(format_independent_tracker.CountEntries() == 10);
-	CHECK(format_independent_tracker.CountFormattedEntries() == 10);
+		TestsShell::RenderLoop();
+		const auto count_level_1 = std::count_if(format_independent_tracker.GetEntries().begin(), format_independent_tracker.GetEntries().end(),
+			[](const auto& entry) { return entry.level == 1 && !entry.from_cache; });
+		CHECK_MESSAGE(count_level_1 == 1, "Only the flexbox should be formatted, the others should be cached");
+	}
 
 	document->Close();
 	TestsShell::ShutdownShell();
@@ -658,7 +672,7 @@ static const String rml_flexbox_shrink_to_fit_nested = R"(
 	Before
 	<div class="outer">
 		%s
-		<div class="inner">Flex</div>
+		<div id="innermost" class="inner">Flex</div>
 		%s
 	</div>
 	After
@@ -714,4 +728,42 @@ TEST_CASE("LayoutIsolation.FlexFormat.shrink-to-fit")
 	TestsShell::ShutdownShell();
 }
 
+TEST_CASE("LayoutIsolation.FlexFormat.shrink-to-fit.cache")
+{
+	Context* context = TestsShell::GetContext();
+	REQUIRE(context);
+
+	constexpr int num_nest_levels = 2;
+	const Rml::String document_rml = Rml::CreateString(rml_flexbox_shrink_to_fit_nested.c_str(),
+		StringUtilities::RepeatString(R"(<div class="inner"><div class="outer">)", num_nest_levels - 1).c_str(),
+		StringUtilities::RepeatString(R"(</div></div>)", num_nest_levels - 1).c_str());
+
+	ElementDocument* document = nullptr;
+	{
+		FormatIndependentDebugTracker format_independent_tracker;
+		document = context->LoadDocumentFromMemory(document_rml);
+		document->Show();
+		TestsShell::RenderLoop();
+		MESSAGE("Initial layout: ", format_independent_tracker.CountFormattedEntries());
+	}
+
+	{
+		FormatIndependentDebugTracker format_independent_tracker;
+		document->GetElementById("innermost")->SetInnerRML("Flex");
+		TestsShell::RenderLoop();
+		MESSAGE("With cache: ", format_independent_tracker.CountFormattedEntries());
+	}
+
+	{
+		FormatIndependentDebugTracker format_independent_tracker;
+		document->GetElementById("innermost")->SetInnerRML("Flex");
+		document->SetAttribute("rmlui-disable-layout-cache", true);
+		TestsShell::RenderLoop();
+		MESSAGE("Without cache: ", format_independent_tracker.CountFormattedEntries());
+	}
+
+	document->Close();
+	TestsShell::ShutdownShell();
+}
+
 #endif // RMLUI_DEBUG