Skip to content

Commit 695c3f3

Browse files
committed
Add backend conformance test suite with --test validation mode
1 parent e09620a commit 695c3f3

5 files changed

Lines changed: 1359 additions & 73 deletions

File tree

CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.21)
22

33
project(VEFontCache LANGUAGES C CXX)
44

5+
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded")
6+
57
set(CMAKE_CXX_STANDARD 20)
68
set(CMAKE_CXX_STANDARD_REQUIRED ON)
79
set(CMAKE_C_STANDARD 11)
@@ -84,7 +86,12 @@ if(VEFC_BUILD_DEMO)
8486
${CMAKE_CURRENT_SOURCE_DIR}/demo/include
8587
${CMAKE_CURRENT_SOURCE_DIR}/demo/src
8688
)
87-
target_link_libraries(demo PRIVATE glu32)
89+
target_link_libraries(demo PRIVATE
90+
glu32
91+
opengl32
92+
winmm
93+
xinput
94+
)
8895
vefc_apply_common_options(demo)
8996
vefc_apply_features(demo ${VEFC_ENABLE_HARFBUZZ} ${VEFC_ENABLE_FREETYPE})
9097

demo/demo.cpp

Lines changed: 207 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
#include <memory>
3232
#include <array>
3333
#include <chrono>
34+
#include <cmath>
35+
#include <cstring>
36+
#include <filesystem>
37+
#include <string>
3438
#include <glad/glad.h>
3539
#include <glad/glad.c>
3640
#include <gl/glu.h>
@@ -49,6 +53,7 @@
4953
#define VE_FONTCACHE_IMPL
5054
// #define VE_FONTCACHE_DEBUGPRINT
5155
#include "../ve_fontcache.h"
56+
#include "../ve_fontcache_backend_test.h"
5257

5358
static ve_fontcache cache;
5459

@@ -63,6 +68,42 @@ static std::vector< GLuint > fonecache_CPU_atlas_textures; // Used with VE_FONTC
6368
TinyWindow::vec2_t< unsigned int > window_size;
6469
static int mouse_scroll = 0;
6570

71+
static std::filesystem::path current_executable_directory()
72+
{
73+
std::array< char, MAX_PATH > path {};
74+
DWORD length = GetModuleFileNameA( nullptr, path.data(), static_cast< DWORD >( path.size() ) );
75+
return std::filesystem::path( std::string( path.data(), length ) ).parent_path();
76+
}
77+
78+
static std::string resolve_demo_asset_path( const char* relative_path )
79+
{
80+
const std::filesystem::path relative( relative_path );
81+
const std::filesystem::path cwd = std::filesystem::current_path();
82+
const std::filesystem::path exe_dir = current_executable_directory();
83+
const std::array< std::filesystem::path, 6 > candidates = {
84+
cwd / relative,
85+
cwd / "demo" / relative,
86+
cwd / ".." / "demo" / relative,
87+
exe_dir / relative,
88+
exe_dir / ".." / "demo" / relative,
89+
exe_dir / ".." / ".." / "demo" / relative,
90+
};
91+
92+
for ( const std::filesystem::path& candidate : candidates ) {
93+
if ( std::filesystem::exists( candidate ) ) {
94+
return candidate.lexically_normal().string();
95+
}
96+
}
97+
98+
return relative.string();
99+
}
100+
101+
static ve_font_id load_demo_font( ve_fontcache* target_cache, const char* relative_path, std::vector< uint8_t >& buffer, float size_px )
102+
{
103+
std::string resolved_path = resolve_demo_asset_path( relative_path );
104+
return ve_fontcache_loadfile( target_cache, resolved_path.c_str(), buffer, size_px );
105+
}
106+
66107
const std::string vs_source_shared = R"(
67108
#version 330 core
68109
in vec2 vpos;
@@ -422,27 +463,27 @@ void init_demo()
422463
static std::vector< uint8_t > buffer, buffer2, buffer3, buffer4, buffer5, buffer6,
423464
buffer7, buffer8, buffer9, buffer10, buffer11, buffer12, buffer13, buffer14;
424465

425-
logo_font = ve_fontcache_loadfile( &cache, "fonts/SawarabiMincho-Regular.ttf", buffer, 330.0f );
426-
title_font = ve_fontcache_loadfile( &cache, "fonts/OpenSans-Regular.ttf", buffer2, 42.0f );
427-
print_font = ve_fontcache_loadfile( &cache, "fonts/NotoSansJP-Light.otf", buffer3, 19.0f );
428-
mono_font = ve_fontcache_loadfile( &cache, "fonts/UbuntuMono-Regular.ttf", buffer4, 21.0f );
429-
small_font = ve_fontcache_loadfile( &cache, "fonts/Roboto-Regular.ttf", buffer14, 10.0f );
430-
431-
demo_sans_font = ve_fontcache_loadfile( &cache, "fonts/OpenSans-Regular.ttf", buffer2, 18.0f );
432-
demo_serif_font = ve_fontcache_loadfile( &cache, "fonts/Bitter-Regular.ttf", buffer5, 18.0f );
433-
demo_script_font = ve_fontcache_loadfile( &cache, "fonts/DancingScript-Regular.ttf", buffer6, 22.0f );
434-
demo_mono_font = ve_fontcache_loadfile( &cache, "fonts/NovaMono-Regular.ttf", buffer7, 18.0f );
435-
436-
demo_chinese_font = ve_fontcache_loadfile( &cache, "fonts/NotoSerifSC-Regular.otf", buffer8, 24.0f );
437-
demo_japanese_font = ve_fontcache_loadfile( &cache, "fonts/SawarabiMincho-Regular.ttf", buffer, 24.0f );
438-
demo_korean_font = ve_fontcache_loadfile( &cache, "fonts/NanumPenScript-Regular.ttf", buffer9, 36.0f );
439-
demo_thai_font = ve_fontcache_loadfile( &cache, "fonts/Krub-Regular.ttf", buffer10, 24.0f );
440-
demo_arabic_font = ve_fontcache_loadfile( &cache, "fonts/Tajawal-Regular.ttf", buffer11, 24.0f );
441-
demo_hebrew_font = ve_fontcache_loadfile( &cache, "fonts/DavidLibre-Regular.ttf", buffer12, 22.0f );
442-
443-
demo_raincode_font = ve_fontcache_loadfile( &cache, "fonts/NotoSansJP-Regular.otf", buffer13, 20.0f );
444-
demo_grid2_font = ve_fontcache_loadfile( &cache, "fonts/NotoSerifSC-Regular.otf", buffer8, 54.0f );
445-
demo_grid3_font = ve_fontcache_loadfile( &cache, "fonts/Bitter-Regular.ttf", buffer5, 44.0f );
466+
logo_font = load_demo_font( &cache, "fonts/SawarabiMincho-Regular.ttf", buffer, 330.0f );
467+
title_font = load_demo_font( &cache, "fonts/OpenSans-Regular.ttf", buffer2, 42.0f );
468+
print_font = load_demo_font( &cache, "fonts/NotoSansJP-Light.otf", buffer3, 19.0f );
469+
mono_font = load_demo_font( &cache, "fonts/UbuntuMono-Regular.ttf", buffer4, 21.0f );
470+
small_font = load_demo_font( &cache, "fonts/Roboto-Regular.ttf", buffer14, 10.0f );
471+
472+
demo_sans_font = load_demo_font( &cache, "fonts/OpenSans-Regular.ttf", buffer2, 18.0f );
473+
demo_serif_font = load_demo_font( &cache, "fonts/Bitter-Regular.ttf", buffer5, 18.0f );
474+
demo_script_font = load_demo_font( &cache, "fonts/DancingScript-Regular.ttf", buffer6, 22.0f );
475+
demo_mono_font = load_demo_font( &cache, "fonts/NovaMono-Regular.ttf", buffer7, 18.0f );
476+
477+
demo_chinese_font = load_demo_font( &cache, "fonts/NotoSerifSC-Regular.otf", buffer8, 24.0f );
478+
demo_japanese_font = load_demo_font( &cache, "fonts/SawarabiMincho-Regular.ttf", buffer, 24.0f );
479+
demo_korean_font = load_demo_font( &cache, "fonts/NanumPenScript-Regular.ttf", buffer9, 36.0f );
480+
demo_thai_font = load_demo_font( &cache, "fonts/Krub-Regular.ttf", buffer10, 24.0f );
481+
demo_arabic_font = load_demo_font( &cache, "fonts/Tajawal-Regular.ttf", buffer11, 24.0f );
482+
demo_hebrew_font = load_demo_font( &cache, "fonts/DavidLibre-Regular.ttf", buffer12, 22.0f );
483+
484+
demo_raincode_font = load_demo_font( &cache, "fonts/NotoSansJP-Regular.otf", buffer13, 20.0f );
485+
demo_grid2_font = load_demo_font( &cache, "fonts/NotoSerifSC-Regular.otf", buffer8, 54.0f );
486+
demo_grid3_font = load_demo_font( &cache, "fonts/Bitter-Regular.ttf", buffer5, 44.0f );
446487
}
447488

448489
void render_demo( TinyWindow::tWindow* window, float dT )
@@ -777,7 +818,144 @@ void test_plist()
777818
}
778819
}
779820

780-
int main()
821+
static bool has_flag( int argc, char** argv, const char* flag )
822+
{
823+
for ( int i = 1; i < argc; i++ ) {
824+
if ( std::strcmp( argv[ i ], flag ) == 0 ) {
825+
return true;
826+
}
827+
}
828+
829+
return false;
830+
}
831+
832+
static void clear_framebuffer_colour( GLuint framebuffer )
833+
{
834+
glBindFramebuffer( GL_FRAMEBUFFER, framebuffer );
835+
glDisable( GL_FRAMEBUFFER_SRGB );
836+
glViewport(
837+
0,
838+
0,
839+
framebuffer == 0 ? static_cast< GLsizei >( window_size.width ) : ( framebuffer == fontcache_fbo[ 0 ] ? VE_FONTCACHE_GLYPHDRAW_BUFFER_WIDTH : VE_FONTCACHE_ATLAS_WIDTH ),
840+
framebuffer == 0 ? static_cast< GLsizei >( window_size.height ) : ( framebuffer == fontcache_fbo[ 0 ] ? VE_FONTCACHE_GLYPHDRAW_BUFFER_HEIGHT : VE_FONTCACHE_ATLAS_HEIGHT ) );
841+
glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
842+
glClear( GL_COLOR_BUFFER_BIT );
843+
}
844+
845+
static void clear_backend_test_surfaces()
846+
{
847+
clear_framebuffer_colour( fontcache_fbo[ 0 ] );
848+
clear_framebuffer_colour( fontcache_fbo[ 1 ] );
849+
clear_framebuffer_colour( 0 );
850+
glBindFramebuffer( GL_FRAMEBUFFER, 0 );
851+
}
852+
853+
static bool backend_test_readback( const char* name, int x, int y, int w, int h, uint8_t* out_pixels )
854+
{
855+
GLint previous_read_framebuffer = 0;
856+
glGetIntegerv( GL_READ_FRAMEBUFFER_BINDING, &previous_read_framebuffer );
857+
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
858+
859+
if ( std::strcmp( name, "glyph_buffer" ) == 0 ) {
860+
glBindFramebuffer( GL_READ_FRAMEBUFFER, fontcache_fbo[ 0 ] );
861+
glReadBuffer( GL_COLOR_ATTACHMENT0 );
862+
glReadPixels( x, y, w, h, GL_RED, GL_UNSIGNED_BYTE, out_pixels );
863+
} else if ( std::strcmp( name, "atlas" ) == 0 ) {
864+
glBindFramebuffer( GL_READ_FRAMEBUFFER, fontcache_fbo[ 1 ] );
865+
glReadBuffer( GL_COLOR_ATTACHMENT0 );
866+
glReadPixels( x, y, w, h, GL_RED, GL_UNSIGNED_BYTE, out_pixels );
867+
} else if ( std::strcmp( name, "target" ) == 0 ) {
868+
glBindFramebuffer( GL_READ_FRAMEBUFFER, 0 );
869+
glReadBuffer( GL_BACK );
870+
glReadPixels( x, y, w, h, GL_RED, GL_UNSIGNED_BYTE, out_pixels );
871+
} else {
872+
glBindFramebuffer( GL_READ_FRAMEBUFFER, previous_read_framebuffer );
873+
return false;
874+
}
875+
876+
glBindFramebuffer( GL_READ_FRAMEBUFFER, previous_read_framebuffer );
877+
check_error( __LINE__ );
878+
return true;
879+
}
880+
881+
static void backend_test_execute()
882+
{
883+
clear_framebuffer_colour( 0 );
884+
fontcache_drawcmd();
885+
glFinish();
886+
}
887+
888+
static int run_backend_test_mode()
889+
{
890+
ve_fontcache_init( &cache, false );
891+
#ifdef VE_FONTCACHE_FREETYPE_RASTERISATION
892+
cache.use_freetype = false;
893+
#endif // VE_FONTCACHE_FREETYPE_RASTERISATION
894+
ve_fontcache_configure_snap( &cache, window_size.width, window_size.height );
895+
896+
std::vector< uint8_t > primary_buffer;
897+
std::vector< uint8_t > secondary_buffer;
898+
std::vector< uint8_t > small_buffer;
899+
std::vector< uint8_t > latin_buffer;
900+
std::vector< uint8_t > cjk_buffer;
901+
std::vector< uint8_t > huge_buffer;
902+
std::vector< std::vector< uint8_t > > reload_buffers;
903+
904+
ve_font_id primary_font = load_demo_font( &cache, "fonts/NotoSansJP-Light.otf", primary_buffer, 19.0f );
905+
ve_font_id secondary_font = load_demo_font( &cache, "fonts/OpenSans-Regular.ttf", secondary_buffer, 48.0f );
906+
ve_font_id small_test_font = load_demo_font( &cache, "fonts/NotoSansJP-Light.otf", small_buffer, 10.0f );
907+
ve_font_id latin_test_font = load_demo_font( &cache, "fonts/OpenSans-Regular.ttf", latin_buffer, 42.0f );
908+
ve_font_id cjk_test_font = load_demo_font( &cache, "fonts/NotoSerifSC-Regular.otf", cjk_buffer, 54.0f );
909+
ve_font_id huge_test_font = load_demo_font( &cache, "fonts/NotoSansJP-Light.otf", huge_buffer, 200.0f );
910+
911+
const bool fonts_ready =
912+
primary_font >= 0
913+
&& secondary_font >= 0
914+
&& small_test_font >= 0
915+
&& latin_test_font >= 0
916+
&& cjk_test_font >= 0
917+
&& huge_test_font >= 0;
918+
if ( !fonts_ready ) {
919+
printf( "VEFontCache backend tests failed to load one or more demo fonts.\n" );
920+
ve_fontcache_shutdown( &cache );
921+
return 1;
922+
}
923+
924+
clear_backend_test_surfaces();
925+
926+
ve_fontcache_backend_test_options options;
927+
options.cache = &cache;
928+
options.font = primary_font;
929+
options.secondary_font = secondary_font;
930+
options.small_font = small_test_font;
931+
options.latin_font = latin_test_font;
932+
options.cjk_font = cjk_test_font;
933+
options.huge_font = huge_test_font;
934+
options.execute = backend_test_execute;
935+
options.readback = backend_test_readback;
936+
options.reload_font = [ &reload_buffers ]() -> ve_font_id {
937+
reload_buffers.emplace_back();
938+
return load_demo_font( &cache, "fonts/NotoSansJP-Light.otf", reload_buffers.back(), 19.0f );
939+
};
940+
941+
ve_fontcache_backend_test_result result = ve_fontcache_backend_test_run( options );
942+
printf(
943+
"VEFontCache backend tests: %d passed, %d failed, %d skipped\n",
944+
result.passed,
945+
result.failed,
946+
result.skipped );
947+
for ( const std::string& failure : result.failures ) {
948+
printf( "FAIL: %s\n", failure.c_str() );
949+
}
950+
for ( const std::string& skipped : result.skipped_tests ) {
951+
printf( "SKIP: %s\n", skipped.c_str() );
952+
}
953+
954+
ve_fontcache_shutdown( &cache );
955+
return result.failed == 0 ? 0 : 1;
956+
}
957+
958+
int main( int argc, char** argv )
781959
{
782960
TinyWindow::windowSetting_t cfg;
783961
cfg.name = "VEFontCache"; cfg.versionMajor = 3; cfg.versionMinor = 3; cfg.enableSRGB = false;
@@ -815,6 +993,13 @@ int main()
815993
}
816994
#endif // VE_FONTCACHE_DEBUGPRINT
817995

996+
if ( has_flag( argc, argv, "--test" ) ) {
997+
int exit_code = run_backend_test_mode();
998+
manager->ShutDown();
999+
window.reset( nullptr );
1000+
return exit_code;
1001+
}
1002+
8181003
init_demo();
8191004
while( !window->shouldClose ) {
8201005
manager->PollForEvents();

tests/test_backend.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ UTEST( backend, conformance_header_passes )
77

88
ASSERT_GE( font, 0 );
99
ve_fontcache_backend_test_result result = ve_fontcache_backend_test_run( &ctx.cache, font );
10+
11+
for ( const auto& failure : result.failures ) {
12+
printf( "FAILED: %s\n", failure.c_str() );
13+
}
14+
1015
EXPECT_EQ( 0, result.failed );
1116
EXPECT_TRUE( result.passed > 0 );
1217
}

0 commit comments

Comments
 (0)