Skip to content

Commit

Permalink
Defer texture loading until it is visible
Browse files Browse the repository at this point in the history
- Try to load a texture only once in case of a failed load, or until release is called on it.
- Add unit tests for the expected behavior.
  • Loading branch information
mikke89 committed Jan 27, 2025
1 parent 8f3eb7f commit 3b96321
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 15 deletions.
2 changes: 1 addition & 1 deletion Source/Core/RenderManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Texture RenderManager::LoadTexture(const String& source, const String& document_
else
GetSystemInterface()->JoinPath(path, StringUtilities::Replace(document_path, '|', ':'), source);

return Texture(this, texture_database->file_database.LoadTexture(render_interface, path));
return Texture(this, texture_database->file_database.InsertTexture(path));
}

CallbackTexture RenderManager::MakeCallbackTexture(CallbackTextureFunction callback)
Expand Down
24 changes: 12 additions & 12 deletions Source/Core/TextureDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,22 +116,16 @@ FileTextureDatabase::~FileTextureDatabase()
#endif
}

TextureFileIndex FileTextureDatabase::LoadTexture(RenderInterface* render_interface, const String& source)
TextureFileIndex FileTextureDatabase::InsertTexture(const String& source)
{
auto it = texture_map.find(source);
if (it != texture_map.end())
return it->second;

FileTextureEntry entry = LoadTextureEntry(render_interface, source);
if (!entry.texture_handle)
{
Rml::Log::Message(Rml::Log::LT_WARNING, "Could not load texture: %s", source.c_str());
return TextureFileIndex::Invalid;
}

// The texture is not yet loaded from the render interface. That is deferred until the texture is needed, such as when it becomes visible.
const auto index = TextureFileIndex(texture_list.size());
texture_map[source] = index;
texture_list.push_back(std::move(entry));
texture_list.push_back({});

return index;
}
Expand All @@ -140,6 +134,11 @@ FileTextureDatabase::FileTextureEntry FileTextureDatabase::LoadTextureEntry(Rend
{
FileTextureEntry result = {};
result.texture_handle = render_interface->LoadTexture(result.dimensions, source);
if (!result.texture_handle)
{
result.load_texture_failed = true;
Rml::Log::Message(Rml::Log::LT_WARNING, "Could not load texture: %s", source.c_str());
}
return result;
}

Expand All @@ -151,7 +150,8 @@ FileTextureDatabase::FileTextureEntry& FileTextureDatabase::EnsureLoaded(RenderI
auto it = std::find_if(texture_map.begin(), texture_map.end(), [index](const auto& pair) { return pair.second == index; });
RMLUI_ASSERT(it != texture_map.end());
const String& source = it->first;
entry = LoadTextureEntry(render_interface, source);
if (!entry.load_texture_failed)
entry = LoadTextureEntry(render_interface, source);
}
return entry;
}
Expand Down Expand Up @@ -185,7 +185,7 @@ bool FileTextureDatabase::ReleaseTexture(RenderInterface* render_interface, cons
if (texture.texture_handle)
{
render_interface->ReleaseTexture(texture.texture_handle);
texture.texture_handle = {};
texture = {};
return true;
}

Expand All @@ -199,7 +199,7 @@ void FileTextureDatabase::ReleaseAllTextures(RenderInterface* render_interface)
if (texture.texture_handle)
{
render_interface->ReleaseTexture(texture.texture_handle);
texture.texture_handle = {};
texture = {};
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion Source/Core/TextureDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class FileTextureDatabase : NonCopyMoveable {
FileTextureDatabase();
~FileTextureDatabase();

TextureFileIndex LoadTexture(RenderInterface* render_interface, const String& source);
TextureFileIndex InsertTexture(const String& source);

TextureHandle GetHandle(RenderInterface* render_interface, TextureFileIndex index);
Vector2i GetDimensions(RenderInterface* render_interface, TextureFileIndex index);
Expand All @@ -83,6 +83,7 @@ class FileTextureDatabase : NonCopyMoveable {
struct FileTextureEntry {
TextureHandle texture_handle = {};
Vector2i dimensions;
bool load_texture_failed = false;
};

FileTextureEntry LoadTextureEntry(RenderInterface* render_interface, const String& source);
Expand Down
5 changes: 4 additions & 1 deletion Tests/Source/Common/TestsInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,12 @@ void TestsRenderInterface::RenderToClipMask(Rml::ClipMaskOperation /*mask_operat
counters.render_to_clip_mask += 1;
}

Rml::TextureHandle TestsRenderInterface::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& /*source*/)
Rml::TextureHandle TestsRenderInterface::LoadTexture(Rml::Vector2i& texture_dimensions, const Rml::String& source)
{
counters.load_texture += 1;
if (source.find("invalid") != Rml::String::npos)
return 0;

texture_dimensions.x = 512;
texture_dimensions.y = 256;
return 1;
Expand Down
91 changes: 91 additions & 0 deletions Tests/Source/UnitTests/Core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ static const String document_textures_rml = R"(
top: 0;
right: 0;
bottom: 0;
font-family: LatoLatin;
}
div.file {
height: 100px;
Expand Down Expand Up @@ -78,6 +79,7 @@ static const String document_textures_rml = R"(
<div class="file"/>
<div class="sprite"/>
<progress direction="clockwise" start-edge="bottom" value="0.5"/>
abc
</div>
</body>
</rml>
Expand Down Expand Up @@ -158,6 +160,95 @@ TEST_CASE("core.texture_source_list")
TestsShell::ShutdownShell();
}

TEST_CASE("core.load_texture_only_when_visible")
{
TestsRenderInterface* render_interface = TestsShell::GetTestsRenderInterface();
// This test only works with the dummy renderer.
if (!render_interface)
return;

const auto& counters = render_interface->GetCounters();
REQUIRE(counters.load_texture == 0);
REQUIRE(counters.generate_texture == 0);
REQUIRE(counters.release_texture == 0);

Context* context = TestsShell::GetContext();
REQUIRE(context);

ElementDocument* document = context->LoadDocumentFromMemory(document_textures_rml);
Element* child_div = document->GetFirstChild();

SUBCASE("Invisible")
{
CHECK(counters.load_texture == 0);
CHECK(counters.generate_texture == 0);
CHECK(counters.release_texture == 0);

TestsShell::RenderLoop();
CHECK(counters.load_texture == 0);
CHECK(counters.generate_texture == 0);
CHECK(counters.release_texture == 0);

document->Show();
TestsShell::RenderLoop();
CHECK(counters.load_texture == 0);
CHECK(counters.generate_texture == 0);
CHECK(counters.release_texture == 0);
}

SUBCASE("Visible")
{
child_div->SetProperty(PropertyId::Display, Style::Display::Block);
document->Show();
TestsShell::RenderLoop();
REQUIRE(counters.load_texture == 4);
REQUIRE(counters.generate_texture > 0);
REQUIRE(counters.release_texture == 0);
}

SUBCASE("Partially visible")
{
child_div->SetProperty(PropertyId::Display, Style::Display::Block);
ElementList elements;
document->GetElementsByTagName(elements, "img");
REQUIRE(elements.size() == 1);
elements.front()->SetProperty(PropertyId::Display, Style::Display::None);

document->Show();
TestsShell::RenderLoop();
REQUIRE(counters.load_texture == 3);
REQUIRE(counters.generate_texture > 0);
REQUIRE(counters.release_texture == 0);
}

document->Close();
TestsShell::ShutdownShell();
}

TEST_CASE("core.warn_missing_texture_once_when_visible")
{
Context* context = TestsShell::GetContext();
REQUIRE(context);

const String document_missing_textures_rml = StringUtilities::Replace(document_textures_rml, ".tga", "_invalid.tga");
ElementDocument* document = context->LoadDocumentFromMemory(document_missing_textures_rml);
Element* child_div = document->GetFirstChild();

TestsShell::SetNumExpectedWarnings(0);
document->Show();
TestsShell::RenderLoop();

TestsShell::SetNumExpectedWarnings(4);
child_div->SetProperty(PropertyId::Display, Style::Display::Block);
TestsShell::RenderLoop();

TestsShell::SetNumExpectedWarnings(0);
TestsShell::RenderLoop();

document->Close();
TestsShell::ShutdownShell();
}

TEST_CASE("core.release_resources")
{
TestsRenderInterface* render_interface = TestsShell::GetTestsRenderInterface();
Expand Down

0 comments on commit 3b96321

Please sign in to comment.