mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-28 17:16:29 +03:00
Optimization in RichText and added benchmark.
This commit is contained in:
@@ -190,6 +190,7 @@ class EE_API RichText : public Drawable {
|
||||
Sizef size;
|
||||
Int64 startCharIndex{ 0 };
|
||||
Int64 endCharIndex{ 0 };
|
||||
Int64 _leafIndex{ -1 }; // O(1) lookup index, assigned during layout
|
||||
};
|
||||
|
||||
/** @brief Structure representing a rendered paragraph (line). */
|
||||
|
||||
@@ -1883,6 +1883,14 @@ solution "eepp"
|
||||
includedirs { "src/thirdparty" }
|
||||
build_link_configuration( "eepp-ui-perf-test", true )
|
||||
|
||||
project "eepp-benchmarks"
|
||||
kind "ConsoleApp"
|
||||
targetdir("./bin/benchmarks")
|
||||
language "C++"
|
||||
files { "src/benchmarks/*.cpp" }
|
||||
includedirs { "src/thirdparty" }
|
||||
build_link_configuration( "eepp-benchmarks", true )
|
||||
|
||||
project "eepp-unit_tests"
|
||||
kind "ConsoleApp"
|
||||
targetdir("./bin/unit_tests")
|
||||
|
||||
14
premake5.lua
14
premake5.lua
@@ -1754,6 +1754,20 @@ workspace "eepp"
|
||||
incdirs { "src/thirdparty" }
|
||||
build_link_configuration( "eepp-ui-perf-test", true )
|
||||
|
||||
project "eepp-benchmarks"
|
||||
kind "ConsoleApp"
|
||||
targetdir(_MAIN_SCRIPT_DIR .. "/bin/benchmarks")
|
||||
language "C++"
|
||||
files { "src/benchmarks/*.cpp" }
|
||||
incdirs { "src/thirdparty" }
|
||||
build_link_configuration( "eepp-benchmarks", true )
|
||||
if table.contains(backends, "SDL2") then
|
||||
defines { "EE_BACKEND_SDL_ACTIVE", "EE_SDL_VERSION_2" }
|
||||
end
|
||||
if table.contains(backends, "SDL3") then
|
||||
defines { "EE_BACKEND_SDL_ACTIVE", "EE_SDL_VERSION_3" }
|
||||
end
|
||||
|
||||
project "eepp-unit_tests"
|
||||
kind "ConsoleApp"
|
||||
targetdir(_MAIN_SCRIPT_DIR .. "/bin/unit_tests")
|
||||
|
||||
85
src/benchmarks/inline_layout_benchmark.cpp
Normal file
85
src/benchmarks/inline_layout_benchmark.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "../tests/unit_tests/utest.hpp"
|
||||
|
||||
#include <eepp/graphics/fontfamily.hpp>
|
||||
#include <eepp/graphics/fonttruetype.hpp>
|
||||
#include <eepp/graphics/richtext.hpp>
|
||||
#include <eepp/system/clock.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/sys.hpp>
|
||||
#include <eepp/window/engine.hpp>
|
||||
|
||||
using namespace EE;
|
||||
using namespace EE::Graphics;
|
||||
using namespace EE::Window;
|
||||
|
||||
static constexpr int numBoxes = 100;
|
||||
static constexpr int numSpansPerBox = 20;
|
||||
static constexpr int layoutIterations = 50;
|
||||
static constexpr Float maxWidth = 800;
|
||||
|
||||
UTEST( Benchmark, InlineLayout ) {
|
||||
Engine::instance()->createWindow( WindowSettings(
|
||||
800, 600, "bench", WindowStyle::Default, WindowBackend::Default, 32, {}, 1, false, true ) );
|
||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||
|
||||
FontTrueType* font = FontTrueType::New( "NotoSans-Regular" );
|
||||
font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" );
|
||||
if ( !font->loaded() ) {
|
||||
Engine::destroySingleton();
|
||||
UTEST_PRINT_INFO( "Failed to load font, skipping benchmark" );
|
||||
return;
|
||||
}
|
||||
FontFamily::loadFromRegular( font );
|
||||
|
||||
RichText rt;
|
||||
rt.getFontStyleConfig().Font = font;
|
||||
rt.getFontStyleConfig().CharacterSize = 12;
|
||||
rt.setMaxWidth( maxWidth );
|
||||
|
||||
String lorem = "Lorem ipsum dolor sit amet consectetur adipiscing elit sed do "
|
||||
"eiusmod tempor incididunt ut labore et dolore magna aliqua ";
|
||||
String boldText = " **bold** ";
|
||||
|
||||
for ( int b = 0; b < numBoxes; ++b ) {
|
||||
rt.pushInlineBox( { 0, 0, 0, 0 }, { 4, 0, 4, 0 }, 0,
|
||||
RichText::BaselineAlignValue( RichText::BaselineAlignment::Top ) );
|
||||
for ( int s = 0; s < numSpansPerBox; ++s ) {
|
||||
rt.addInlineText( lorem, rt.getFontStyleConfig(), { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0,
|
||||
RichText::BaselineAlignValue() );
|
||||
if ( s % 3 == 0 ) {
|
||||
FontStyleConfig boldStyle = rt.getFontStyleConfig();
|
||||
boldStyle.Style = Text::Bold;
|
||||
rt.addInlineText( boldText, boldStyle, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0,
|
||||
RichText::BaselineAlignValue() );
|
||||
}
|
||||
}
|
||||
rt.popInlineBox();
|
||||
rt.addInlineText( "\n", rt.getFontStyleConfig(), { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0,
|
||||
RichText::BaselineAlignValue() );
|
||||
}
|
||||
|
||||
rt.updateLayout();
|
||||
size_t lineCount = rt.getLines().size();
|
||||
size_t fragmentCount = rt.getInlineFragments().size();
|
||||
|
||||
Clock clock;
|
||||
for ( int i = 0; i < layoutIterations; ++i ) {
|
||||
rt.invalidateLayout();
|
||||
rt.updateLayout();
|
||||
}
|
||||
Time elapsed = clock.getElapsedTime();
|
||||
|
||||
UTEST_PRINT_INFO( String::format( "Boxes: %d, spans/box: %d, iterations: %d", numBoxes,
|
||||
numSpansPerBox, layoutIterations )
|
||||
.c_str() );
|
||||
UTEST_PRINT_INFO(
|
||||
String::format( "Lines: %zu, fragments: %zu", lineCount, fragmentCount ).c_str() );
|
||||
UTEST_PRINT_INFO( String::format( "Total: %s", elapsed.toString().c_str() ).c_str() );
|
||||
UTEST_PRINT_INFO(
|
||||
String::format( "Per iteration: %lld us", elapsed.asMicroseconds() / layoutIterations )
|
||||
.c_str() );
|
||||
|
||||
Engine::destroySingleton();
|
||||
}
|
||||
|
||||
UTEST_MAIN()
|
||||
@@ -1277,6 +1277,31 @@ class RichTextInlineLayouter {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Resolve a render span to its source InlineItem leaf node. The fast
|
||||
// path uses the sequential _leafIndex assigned during buildLayoutRuns:
|
||||
// that index directly addresses the leaves vector built by collectLeaves
|
||||
// because both walk the InlineItem tree in the same depth-first order.
|
||||
// The fallback via path-based linear search handles legacy spans that
|
||||
// lack a leaf index.
|
||||
static const InlineLeafRef* resolveLeaf( const RichText::RenderSpan& span,
|
||||
const SmallVector<InlineLeafRef, 32>& leaves,
|
||||
RichText::InlineFragment::Type type,
|
||||
size_t& resolvedLeafIndex, size_t& leafIndex ) {
|
||||
if ( span._leafIndex >= 0 && static_cast<size_t>( span._leafIndex ) < leaves.size() ) {
|
||||
resolvedLeafIndex = static_cast<size_t>( span._leafIndex );
|
||||
const auto& leaf = leaves[resolvedLeafIndex];
|
||||
if ( leaf.type == type )
|
||||
return &leaf;
|
||||
}
|
||||
const InlineLeafRef* leaf =
|
||||
findLeafByPath( leaves, span.inlinePath, type, resolvedLeafIndex );
|
||||
if ( leaf == nullptr && span.inlinePath.empty() ) {
|
||||
resolvedLeafIndex = leafIndex;
|
||||
leaf = findNextLeaf( leaves, resolvedLeafIndex, type );
|
||||
}
|
||||
return leaf;
|
||||
}
|
||||
|
||||
static std::vector<RichText::InlineFragment>
|
||||
rebuildFragments( const std::vector<RichText::InlineItem>& inlineItems,
|
||||
const std::vector<RichText::RenderParagraph>& lines ) {
|
||||
@@ -1307,7 +1332,7 @@ class RichTextInlineLayouter {
|
||||
isLastInlineLeafInBox( *ancestor.box, ancestor.path, leaf.path );
|
||||
auto it =
|
||||
std::find_if( boxFragments.begin(), boxFragments.end(), [&]( const auto& f ) {
|
||||
return f.lineIndex == lineIndex && sameInlinePath( f.path, ancestor.path );
|
||||
return f.lineIndex == lineIndex && f.box == ancestor.box;
|
||||
} );
|
||||
if ( it == boxFragments.end() ) {
|
||||
InlineBoxFragmentAccumulator acc;
|
||||
@@ -1344,14 +1369,9 @@ class RichTextInlineLayouter {
|
||||
continue;
|
||||
|
||||
size_t resolvedLeafIndex = 0;
|
||||
const InlineLeafRef* leaf = findLeafByPath(
|
||||
leaves, span.inlinePath, RichText::InlineFragment::Type::TextRun,
|
||||
resolvedLeafIndex );
|
||||
if ( leaf == nullptr && span.inlinePath.empty() ) {
|
||||
resolvedLeafIndex = leafIndex;
|
||||
leaf = findNextLeaf( leaves, resolvedLeafIndex,
|
||||
RichText::InlineFragment::Type::TextRun );
|
||||
}
|
||||
const InlineLeafRef* leaf =
|
||||
resolveLeaf( span, leaves, RichText::InlineFragment::Type::TextRun,
|
||||
resolvedLeafIndex, leafIndex );
|
||||
if ( leaf == nullptr )
|
||||
continue;
|
||||
|
||||
@@ -1389,14 +1409,9 @@ class RichTextInlineLayouter {
|
||||
continue;
|
||||
|
||||
size_t resolvedLeafIndex = 0;
|
||||
const InlineLeafRef* leaf = findLeafByPath(
|
||||
leaves, span.inlinePath, RichText::InlineFragment::Type::AtomicBox,
|
||||
resolvedLeafIndex );
|
||||
if ( leaf == nullptr && span.inlinePath.empty() ) {
|
||||
resolvedLeafIndex = leafIndex;
|
||||
leaf = findNextLeaf( leaves, resolvedLeafIndex,
|
||||
RichText::InlineFragment::Type::AtomicBox );
|
||||
}
|
||||
const InlineLeafRef* leaf =
|
||||
resolveLeaf( span, leaves, RichText::InlineFragment::Type::AtomicBox,
|
||||
resolvedLeafIndex, leafIndex );
|
||||
if ( leaf == nullptr )
|
||||
continue;
|
||||
|
||||
@@ -1439,13 +1454,14 @@ class RichTextInlineLayouter {
|
||||
buildLayoutRuns( const std::vector<RichText::InlineItem>& inlineItems ) {
|
||||
SmallVector<InlineLayoutRun, 32> runs;
|
||||
RichText::RenderSpan::InlinePath path;
|
||||
appendLayoutRuns( inlineItems, path, runs );
|
||||
Int64 nextLeafIndex = 0;
|
||||
appendLayoutRuns( inlineItems, path, runs, nextLeafIndex );
|
||||
return runs;
|
||||
}
|
||||
|
||||
static void appendLayoutRuns( const std::vector<RichText::InlineItem>& items,
|
||||
RichText::RenderSpan::InlinePath& path,
|
||||
SmallVector<InlineLayoutRun, 32>& runs ) {
|
||||
SmallVector<InlineLayoutRun, 32>& runs, Int64& nextLeafIndex ) {
|
||||
for ( size_t i = 0; i < items.size(); ++i ) {
|
||||
path.push_back( i );
|
||||
const auto& item = items[i];
|
||||
@@ -1454,11 +1470,11 @@ class RichTextInlineLayouter {
|
||||
if ( box.children.empty() )
|
||||
appendEmptyBoxLayoutRun( path, runs );
|
||||
else
|
||||
appendLayoutRuns( box.children, path, runs );
|
||||
appendLayoutRuns( box.children, path, runs, nextLeafIndex );
|
||||
} else if ( item.isTextRun() ) {
|
||||
appendTextLayoutRun( item.asTextRun(), path, runs );
|
||||
appendTextLayoutRun( item.asTextRun(), path, runs, nextLeafIndex );
|
||||
} else {
|
||||
appendAtomicLayoutRun( item.asAtomicBox(), path, runs );
|
||||
appendAtomicLayoutRun( item.asAtomicBox(), path, runs, nextLeafIndex );
|
||||
}
|
||||
path.pop_back();
|
||||
}
|
||||
@@ -1474,7 +1490,8 @@ class RichTextInlineLayouter {
|
||||
|
||||
static void appendTextLayoutRun( const RichText::InlineItem::TextRun& textRun,
|
||||
const RichText::RenderSpan::InlinePath& path,
|
||||
SmallVector<InlineLayoutRun, 32>& runs ) {
|
||||
SmallVector<InlineLayoutRun, 32>& runs,
|
||||
Int64& nextLeafIndex ) {
|
||||
InlineLayoutRun run;
|
||||
run.payload.type = RichText::RenderSpan::Type::Text;
|
||||
run.payload.text = textRun.text;
|
||||
@@ -1484,12 +1501,14 @@ class RichTextInlineLayouter {
|
||||
run.payload.baselineAlign = textRun.baselineAlign;
|
||||
run.payload.suppressBackground = textRun.suppressBackground;
|
||||
run.payload.inlinePath = path;
|
||||
run.payload._leafIndex = nextLeafIndex++;
|
||||
runs.push_back( std::move( run ) );
|
||||
}
|
||||
|
||||
static void appendAtomicLayoutRun( const RichText::InlineItem::AtomicBox& box,
|
||||
const RichText::RenderSpan::InlinePath& path,
|
||||
SmallVector<InlineLayoutRun, 32>& runs ) {
|
||||
SmallVector<InlineLayoutRun, 32>& runs,
|
||||
Int64& nextLeafIndex ) {
|
||||
InlineLayoutRun run;
|
||||
run.payload.type = box.drawable ? RichText::RenderSpan::Type::Drawable
|
||||
: RichText::RenderSpan::Type::AtomicBox;
|
||||
@@ -1501,6 +1520,7 @@ class RichTextInlineLayouter {
|
||||
run.payload.isLineBreak = box.isLineBreak;
|
||||
run.payload.baselineAlign = box.baselineAlign;
|
||||
run.payload.inlinePath = path;
|
||||
run.payload._leafIndex = nextLeafIndex++;
|
||||
runs.push_back( std::move( run ) );
|
||||
}
|
||||
|
||||
@@ -1603,6 +1623,7 @@ class RichTextInlineLayouter {
|
||||
renderSpan.baselineAlign = payload.baselineAlign;
|
||||
renderSpan.suppressBackground = payload.suppressBackground;
|
||||
renderSpan.inlinePath = payload.inlinePath;
|
||||
renderSpan._leafIndex = payload._leafIndex;
|
||||
renderSpan.position = { curX, 0 };
|
||||
renderSpan.size = Sizef( spanWidth, height );
|
||||
renderSpan.startCharIndex = curCharIdx;
|
||||
|
||||
Reference in New Issue
Block a user