Skip to content

Commit 0185ad2

Browse files
author
Michael Saunders
committed
Build font contours concurrently
Using a MIT licensed thread pool written by Barak Shoshany (BS::thread_pool)
1 parent 40fa99d commit 0185ad2

File tree

5 files changed

+2484
-35
lines changed

5 files changed

+2484
-35
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ vsg_add_target_clang_format(
5353
${VSGXCHANGE_SOURCE_DIR}/src/stbi/stb_image.h
5454
${VSGXCHANGE_SOURCE_DIR}/src/stbi/stb_image_write.h
5555
${VSGXCHANGE_SOURCE_DIR}/src/dds/tinyddsloader.h
56+
${VSGXCHANGE_SOURCE_DIR}/src/thread-pool/BS_thread_pool.hpp
5657
)
5758
vsg_add_target_clobber()
5859
vsg_add_target_cppcheck(

src/freetype/build_vars.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ if(${vsgXchange_freetype})
99
set(SOURCES ${SOURCES}
1010
freetype/freetype.cpp
1111
)
12-
set(EXTRA_INCLUDES ${EXTRA_INCLUDES} ${FREETYPE_INCLUDE_DIRS})
12+
set(EXTRA_INCLUDES ${EXTRA_INCLUDES} ${VSGXCHANGE_SOURCE_DIR}/src/thread-pool ${FREETYPE_INCLUDE_DIRS})
1313
set(EXTRA_LIBRARIES ${EXTRA_LIBRARIES} ${FREETYPE_LIBRARIES})
1414
set(EXTRA_DEFINES ${EXTRA_DEFINES} USE_FREETYPE)
1515

src/freetype/freetype.cpp

Lines changed: 88 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
2727
#include <chrono>
2828
#include <iostream>
2929
#include <set>
30+
#include <thread>
31+
#include <utility>
32+
33+
#include <BS_thread_pool.hpp>
3034

3135
namespace vsgXchange
3236
{
@@ -547,12 +551,34 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
547551
init();
548552
if (!_library) return {};
549553

550-
FT_Face face;
551-
FT_Long face_index = 0;
554+
FT_UInt pixel_size = 48;
555+
FT_UInt freetype_pixel_size = pixel_size;
556+
float freetype_pixel_size_scale = float(pixel_size) / (64.0f * float(freetype_pixel_size));
557+
558+
// create and initialize a new freetype face
559+
auto const createNewFace = [&]{
560+
FT_Face face;
561+
FT_Long face_index = 0;
562+
// Windows workaround for no wchar_t support in Freetype, convert vsg::Path's std::wstring to UTF8 std::string
563+
std::string filenameToUse_string = filenameToUse.string();
564+
int error = FT_New_Face(_library, filenameToUse_string.c_str(), face_index, &face);
565+
if (error)
566+
{
567+
face = nullptr;
568+
}
569+
else
570+
{
571+
error = FT_Set_Pixel_Sizes(face, freetype_pixel_size, freetype_pixel_size);
572+
if (error)
573+
{
574+
FT_Done_Face(face);
575+
face = nullptr;
576+
}
577+
}
578+
return std::make_pair(error,face);
579+
};
552580

553-
// Windows workaround for no wchar_t support in Freetype, convert vsg::Path's std::wstring to UTF8 std::string
554-
std::string filenameToUse_string = filenameToUse.string();
555-
int error = FT_New_Face(_library, filenameToUse_string.c_str(), face_index, &face);
581+
auto[error,face] = createNewFace();
556582
if (error == FT_Err_Unknown_File_Format)
557583
{
558584
std::cout << "Warning: FreeType unable to read font file : " << filenameToUse << ", error = " << FT_Err_Unknown_File_Format << std::endl;
@@ -564,14 +590,6 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
564590
return {};
565591
}
566592

567-
FT_UInt pixel_size = 48;
568-
FT_UInt freetype_pixel_size = pixel_size;
569-
float freetype_pixel_size_scale = float(pixel_size) / (64.0f * float(freetype_pixel_size));
570-
571-
{
572-
error = FT_Set_Pixel_Sizes(face, freetype_pixel_size, freetype_pixel_size);
573-
}
574-
575593
FT_Int32 load_flags = FT_LOAD_NO_BITMAP;
576594
FT_Render_Mode render_mode = FT_RENDER_MODE_NORMAL;
577595

@@ -737,26 +755,28 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
737755
// initialize charmap to zeros.
738756
for (auto& c : *charmap) c = 0;
739757

740-
for (auto& glyphQuad : sortedGlyphQuads)
741-
{
742-
error = FT_Load_Glyph(face, glyphQuad.glyph_index, load_flags);
743-
if (error) continue;
758+
// setup a thread pool for accepting contour generation tasks
759+
auto numThreads{std::thread::hardware_concurrency()};
760+
std::vector<FT_Face> threadFaceData(numThreads, FT_Face{nullptr});
761+
BS::thread_pool threadPool{numThreads, [&](auto threadIndex){
762+
int freetype_error{0};
763+
std::tie(freetype_error,threadFaceData[threadIndex]) = createNewFace();
764+
}};
765+
766+
auto const generateContours = [&](GlyphQuad const& glyphQuad, unsigned int glyphXpos, unsigned int glyphYpos) {
767+
auto const threadIndex = *BS::this_thread::get_index();
768+
auto const faceData = threadFaceData[threadIndex];
769+
error = FT_Load_Glyph(faceData, glyphQuad.glyph_index, load_flags);
770+
if (error) return;
744771

745772
unsigned int width = glyphQuad.width;
746773
unsigned int height = glyphQuad.height;
747-
auto metrics = face->glyph->metrics;
748-
749-
if ((xpos + width + texel_margin) > atlas->width())
750-
{
751-
// glyph doesn't fit in present row so shift to next row.
752-
xpos = texel_margin;
753-
ypos = ytop;
754-
}
774+
auto metrics = faceData->glyph->metrics;
755775

756776
if (useOutline)
757777
{
758778
Contours contours;
759-
generateOutlines(face->glyph->outline, contours);
779+
generateOutlines(faceData->glyph->outline, contours);
760780

761781
// scale and offset the outline geometry
762782
vsg::vec2 offset(float(metrics.horiBearingX) * freetype_pixel_size_scale, float(metrics.horiBearingY) * freetype_pixel_size_scale);
@@ -823,7 +843,7 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
823843
int delta = quad_margin - 2;
824844
for (int r = -delta; r < static_cast<int>(height + delta); ++r)
825845
{
826-
std::size_t index = atlas->index(xpos - delta, ypos + r);
846+
std::size_t index = atlas->index(glyphXpos - delta, glyphYpos + r);
827847
for (int c = -delta; c < static_cast<int>(width + delta); ++c)
828848
{
829849
vsg::vec2 v;
@@ -858,23 +878,23 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
858878
}
859879
else
860880
{
861-
if (face->glyph->format != FT_GLYPH_FORMAT_BITMAP)
881+
if (faceData->glyph->format != FT_GLYPH_FORMAT_BITMAP)
862882
{
863-
error = FT_Render_Glyph(face->glyph, render_mode);
864-
if (error) continue;
883+
error = FT_Render_Glyph(faceData->glyph, render_mode);
884+
if (error) return;
865885
}
866886

867-
if (face->glyph->format != FT_GLYPH_FORMAT_BITMAP) continue;
887+
if (faceData->glyph->format != FT_GLYPH_FORMAT_BITMAP) return;
868888

869-
const FT_Bitmap& bitmap = face->glyph->bitmap;
889+
const FT_Bitmap& bitmap = faceData->glyph->bitmap;
870890

871891
// copy pixels
872892
if (computeSDF)
873893
{
874894
int delta = quad_margin - 2;
875895
for (int r = -delta; r < static_cast<int>(bitmap.rows + delta); ++r)
876896
{
877-
std::size_t index = atlas->index(xpos - delta, ypos + r);
897+
std::size_t index = atlas->index(glyphXpos - delta, glyphYpos + r);
878898
for (int c = -delta; c < static_cast<int>(bitmap.width + delta); ++c)
879899
{
880900
atlas->at(index++) = nearest_edge(bitmap, c, r, quad_margin);
@@ -886,14 +906,36 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
886906
const unsigned char* ptr = bitmap.buffer;
887907
for (unsigned int r = 0; r < bitmap.rows; ++r)
888908
{
889-
std::size_t index = atlas->index(xpos, ypos + r);
909+
std::size_t index = atlas->index(glyphXpos, glyphYpos + r);
890910
for (unsigned int c = 0; c < bitmap.width; ++c)
891911
{
892912
atlas->at(index++) = *ptr++;
893913
}
894914
}
895915
}
896916
}
917+
};
918+
919+
for (auto& glyphQuad : sortedGlyphQuads)
920+
{
921+
error = FT_Load_Glyph(face, glyphQuad.glyph_index, load_flags);
922+
if (error) continue;
923+
924+
unsigned int width = glyphQuad.width;
925+
unsigned int height = glyphQuad.height;
926+
auto metrics = face->glyph->metrics;
927+
928+
if ((xpos + width + texel_margin) > atlas->width())
929+
{
930+
// glyph doesn't fit in present row so shift to next row.
931+
xpos = texel_margin;
932+
ypos = ytop;
933+
}
934+
935+
// submit the task to generate contours concurrently
936+
threadPool.detach_task([&generateContours, glyphQuad, xpos, ypos] {
937+
generateContours(glyphQuad, xpos, ypos);
938+
});
897939

898940
vsg::vec4 uvrect(
899941
(float(xpos - quad_margin) - 1.0f) / float(atlas->width() - 1), float(ypos + height + quad_margin) / float(atlas->height() - 1),
@@ -929,5 +971,17 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
929971
font->glyphMetrics = glyphMetrics;
930972
font->charmap = charmap;
931973

974+
FT_Done_Face(face);
975+
976+
// wait for all tasks to finish
977+
threadPool.wait();
978+
979+
// cleanup thread storage
980+
for (auto& faceData : threadFaceData)
981+
{
982+
if (faceData)
983+
FT_Done_Face(faceData);
984+
}
985+
932986
return font;
933987
}

0 commit comments

Comments
 (0)