Browse Source

Add manual release of render manager to allow render interface to be destroyed before Rml::Shutdown, see #703

Michael Ragazzon 1 year ago
parent
commit
42a1a9138b

+ 5 - 1
Include/RmlUi/Core/Core.h

@@ -108,7 +108,7 @@ RMLUICORE_API TextInputHandler* GetTextInputHandler();
 /// @param[in] text_input_handler The custom text input handler to use, or nullptr to use the default.
 /// @lifetime If specified, the render interface and the text input handler must be kept alive until after the call to
 ///           Rml::Shutdown. Alternatively, the render interface can be destroyed after all contexts it belongs to have been
-///           destroyed and a subsequent call has been made to Rml::ReleaseTextures.
+///           destroyed and a subsequent call has been made to Rml::ReleaseRenderManagers.
 /// @return A non-owning pointer to the new context, or nullptr if the context could not be created.
 RMLUICORE_API Context* CreateContext(const String& name, Vector2i dimensions, RenderInterface* render_interface = nullptr,
 	TextInputHandler* text_input_handler = nullptr);
@@ -179,6 +179,10 @@ RMLUICORE_API void ReleaseCompiledGeometry(RenderInterface* render_interface = n
 /// Releases unused font textures and rendered glyphs to free up memory, and regenerates actively used fonts.
 /// @note Invalidates all existing FontFaceHandles returned from the font engine.
 RMLUICORE_API void ReleaseFontResources();
+/// Releases render managers that are not used by any contexts.
+/// @note Any resources referring to the render manager in user space must be cleared first, including callback textures and compiled geometry.
+/// @note Also releases font resources, which invalidates all existing FontFaceHandles returned from the font engine.
+RMLUICORE_API void ReleaseRenderManagers();
 
 } // namespace Rml
 

+ 21 - 2
Source/Core/Core.cpp

@@ -51,7 +51,6 @@
 #include "StyleSheetFactory.h"
 #include "StyleSheetParser.h"
 #include "TemplateCache.h"
-#include "TextureDatabase.h"
 
 #ifdef RMLUI_FONT_ENGINE_FREETYPE
 	#include "FontEngineDefault/FontEngineInterfaceDefault.h"
@@ -65,7 +64,7 @@
 	#include "../SVG/SVGPlugin.h"
 #endif
 
-#include "Pool.h"
+#include <algorithm>
 
 namespace Rml {
 
@@ -452,6 +451,26 @@ void ReleaseFontResources()
 		name_context.second->Update();
 }
 
+void ReleaseRenderManagers()
+{
+	auto& contexts = core_data->contexts;
+	auto& render_managers = core_data->render_managers;
+
+	ReleaseFontResources();
+
+	for (auto it = render_managers.begin(); it != render_managers.end();)
+	{
+		RenderManager* render_manager = it->second.get();
+		const auto num_contexts_using_manager = std::count_if(contexts.begin(), contexts.end(),
+			[&](const auto& context_pair) { return &context_pair.second->GetRenderManager() == render_manager; });
+
+		if (num_contexts_using_manager == 0)
+			it = render_managers.erase(it);
+		else
+			++it;
+	}
+}
+
 // Functions that need to be accessible within the Core library, but not publicly.
 namespace CoreInternal {
 

+ 1 - 0
Source/Core/RenderManagerAccess.h

@@ -70,6 +70,7 @@ private:
 	friend bool Rml::ReleaseTexture(const String&, RenderInterface*);
 	friend void Rml::ReleaseTextures(RenderInterface*);
 	friend void Rml::ReleaseCompiledGeometry(RenderInterface*);
+	friend void Rml::ReleaseRenderManagers();
 };
 
 } // namespace Rml

+ 83 - 0
Tests/Source/UnitTests/Core.cpp

@@ -32,6 +32,7 @@
 #include <RmlUi/Core/Core.h>
 #include <RmlUi/Core/Element.h>
 #include <RmlUi/Core/ElementDocument.h>
+#include <Shell.h>
 #include <algorithm>
 #include <doctest.h>
 
@@ -82,6 +83,39 @@ static const String document_textures_rml = R"(
 </rml>
 )";
 
+static const String document_basic_rml = R"(
+<rml>
+<head>
+	<title>Test</title>
+	<link type="text/rcss" href="/assets/rml.rcss"/>
+	<style>
+		body {
+			left: 0;
+			top: 0;
+			right: 0;
+			bottom: 0;
+			font-family: LatoLatin;
+			font-size: 16px;
+		}
+		@spritesheet aliens {
+			src: /assets/high_scores_alien_3.tga;
+			alien3: 0px 0px 64px 64px;
+		}
+		div.sprite {
+			height: 100px;
+			decorator: image(alien3);
+		}
+	</style>
+</head>
+
+<body>
+	<img src="/assets/high_scores_alien_1.tga"/>
+	<div class="sprite"/>
+	abc
+</body>
+</rml>
+)";
+
 static inline StringList GetSortedTextureSourceList()
 {
 	StringList list = Rml::GetTextureSourceList();
@@ -281,3 +315,52 @@ TEST_CASE("core.observer_ptr")
 
 	TestsShell::ShutdownShell();
 }
+
+TEST_CASE("core.RemoveContext")
+{
+	TestsSystemInterface* system_interface = TestsShell::GetTestsSystemInterface();
+	TestsRenderInterface* render_interface = TestsShell::GetTestsRenderInterface();
+	// This test only works with the dummy renderer.
+	if (!render_interface)
+		return;
+
+	const Vector2i window_size = {1280, 720};
+
+	Shell::Initialize();
+	Rml::SetSystemInterface(system_interface);
+	REQUIRE(Rml::GetRenderInterface() == nullptr);
+	REQUIRE(Rml::Initialise());
+	Shell::LoadFonts();
+
+	Context* context = Rml::CreateContext("main", window_size, render_interface);
+	REQUIRE(context);
+
+	ElementDocument* document = context->LoadDocumentFromMemory(document_basic_rml);
+	document->Show();
+
+	context->Update();
+	context->Render();
+
+	REQUIRE(Rml::RemoveContext(context->GetName()));
+
+	SUBCASE("Normal shutdown")
+	{
+		Rml::Shutdown();
+		TestsShell::ResetTestsRenderInterface();
+	}
+
+	SUBCASE("Destroy render interface before shutdown")
+	{
+		ReleaseRenderManagers();
+
+		const auto counters = render_interface->GetCounters();
+		CHECK(counters.release_texture == counters.generate_texture + counters.load_texture);
+		CHECK(counters.release_geometry == counters.compile_geometry);
+
+		TestsShell::ResetTestsRenderInterface();
+
+		Rml::Shutdown();
+	}
+
+	Shell::Shutdown();
+}