Skip to content

Commit ff42212

Browse files
author
Michael Saunders
committed
Build font contours concurrently using VSG's OperationThreads
1 parent 40fa99d commit ff42212

File tree

1 file changed

+148
-34
lines changed

1 file changed

+148
-34
lines changed

src/freetype/freetype.cpp

Lines changed: 148 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
1818
#include <vsg/nodes/Group.h>
1919
#include <vsg/state/ShaderStage.h>
2020
#include <vsg/text/Font.h>
21+
#include <vsg/threading/OperationThreads.h>
2122
#include <vsg/utils/CommandLine.h>
2223

2324
#include <ft2build.h>
@@ -27,6 +28,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
2728
#include <chrono>
2829
#include <iostream>
2930
#include <set>
31+
#include <thread>
32+
#include <utility>
3033

3134
namespace vsgXchange
3235
{
@@ -547,12 +550,34 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
547550
init();
548551
if (!_library) return {};
549552

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

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);
580+
auto[error,face] = createNewFace();
556581
if (error == FT_Err_Unknown_File_Format)
557582
{
558583
std::cout << "Warning: FreeType unable to read font file : " << filenameToUse << ", error = " << FT_Err_Unknown_File_Format << std::endl;
@@ -564,14 +589,6 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
564589
return {};
565590
}
566591

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-
575592
FT_Int32 load_flags = FT_LOAD_NO_BITMAP;
576593
FT_Render_Mode render_mode = FT_RENDER_MODE_NORMAL;
577594

@@ -737,26 +754,18 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
737754
// initialize charmap to zeros.
738755
for (auto& c : *charmap) c = 0;
739756

740-
for (auto& glyphQuad : sortedGlyphQuads)
741-
{
742-
error = FT_Load_Glyph(face, glyphQuad.glyph_index, load_flags);
743-
if (error) continue;
757+
auto const generateContours = [&](FT_Face faceData, GlyphQuad const& glyphQuad, unsigned int glyphXpos, unsigned int glyphYpos) {
758+
error = FT_Load_Glyph(faceData, glyphQuad.glyph_index, load_flags);
759+
if (error) return;
744760

745761
unsigned int width = glyphQuad.width;
746762
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-
}
763+
auto metrics = faceData->glyph->metrics;
755764

756765
if (useOutline)
757766
{
758767
Contours contours;
759-
generateOutlines(face->glyph->outline, contours);
768+
generateOutlines(faceData->glyph->outline, contours);
760769

761770
// scale and offset the outline geometry
762771
vsg::vec2 offset(float(metrics.horiBearingX) * freetype_pixel_size_scale, float(metrics.horiBearingY) * freetype_pixel_size_scale);
@@ -823,7 +832,7 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
823832
int delta = quad_margin - 2;
824833
for (int r = -delta; r < static_cast<int>(height + delta); ++r)
825834
{
826-
std::size_t index = atlas->index(xpos - delta, ypos + r);
835+
std::size_t index = atlas->index(glyphXpos - delta, glyphYpos + r);
827836
for (int c = -delta; c < static_cast<int>(width + delta); ++c)
828837
{
829838
vsg::vec2 v;
@@ -858,23 +867,23 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
858867
}
859868
else
860869
{
861-
if (face->glyph->format != FT_GLYPH_FORMAT_BITMAP)
870+
if (faceData->glyph->format != FT_GLYPH_FORMAT_BITMAP)
862871
{
863-
error = FT_Render_Glyph(face->glyph, render_mode);
864-
if (error) continue;
872+
error = FT_Render_Glyph(faceData->glyph, render_mode);
873+
if (error) return;
865874
}
866875

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

869-
const FT_Bitmap& bitmap = face->glyph->bitmap;
878+
const FT_Bitmap& bitmap = faceData->glyph->bitmap;
870879

871880
// copy pixels
872881
if (computeSDF)
873882
{
874883
int delta = quad_margin - 2;
875884
for (int r = -delta; r < static_cast<int>(bitmap.rows + delta); ++r)
876885
{
877-
std::size_t index = atlas->index(xpos - delta, ypos + r);
886+
std::size_t index = atlas->index(glyphXpos - delta, glyphYpos + r);
878887
for (int c = -delta; c < static_cast<int>(bitmap.width + delta); ++c)
879888
{
880889
atlas->at(index++) = nearest_edge(bitmap, c, r, quad_margin);
@@ -886,14 +895,104 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
886895
const unsigned char* ptr = bitmap.buffer;
887896
for (unsigned int r = 0; r < bitmap.rows; ++r)
888897
{
889-
std::size_t index = atlas->index(xpos, ypos + r);
898+
std::size_t index = atlas->index(glyphXpos, glyphYpos + r);
890899
for (unsigned int c = 0; c < bitmap.width; ++c)
891900
{
892901
atlas->at(index++) = *ptr++;
893902
}
894903
}
895904
}
896905
}
906+
};
907+
908+
// setup a thread storage if generating contour generation tasks concurrently
909+
auto operationThreads{options
910+
? options->operationThreads
911+
: vsg::ref_ptr<vsg::OperationThreads>{}};
912+
913+
using ThreadFaceData = std::unordered_map<std::thread::id,FT_Face>;
914+
ThreadFaceData threadFaceData;
915+
916+
if (operationThreads)
917+
{
918+
for (auto const& thread : operationThreads->threads)
919+
{
920+
auto [freetype_error,freetype_face]{createNewFace()};
921+
threadFaceData.emplace(thread.get_id(), freetype_face);
922+
}
923+
}
924+
925+
struct ContourOperation : public vsg::Inherit<vsg::Operation, ContourOperation>
926+
{
927+
using GenerateContours = std::function<void(
928+
FT_Face faceData,
929+
GlyphQuad const& glyphQuad,
930+
unsigned int glyphXpos,
931+
unsigned int glyphYpos)>;
932+
933+
GlyphQuad const& glyphQuad;
934+
unsigned int const glyphXpos;
935+
unsigned int const glyphYpos;
936+
GenerateContours const generateContours;
937+
ThreadFaceData const& threadFaceData;
938+
vsg::ref_ptr<vsg::Latch> const latch;
939+
940+
ContourOperation(
941+
GlyphQuad const& in_glyphQuad,
942+
unsigned int in_glyphXpos,
943+
unsigned int in_glyphYpos,
944+
GenerateContours const& in_generateContours,
945+
ThreadFaceData const& in_threadFaceData,
946+
vsg::ref_ptr<vsg::Latch> in_latch)
947+
: glyphQuad(in_glyphQuad)
948+
, glyphXpos(in_glyphXpos)
949+
, glyphYpos(in_glyphYpos)
950+
, generateContours(in_generateContours)
951+
, threadFaceData(in_threadFaceData)
952+
, latch(in_latch)
953+
{
954+
}
955+
956+
void run() override
957+
{
958+
auto const faceData = threadFaceData.at(std::this_thread::get_id());
959+
generateContours(faceData, glyphQuad, glyphXpos, glyphYpos);
960+
if (latch)
961+
latch->count_down();
962+
}
963+
};
964+
965+
auto latch = operationThreads
966+
? vsg::Latch::create(static_cast<int>(sortedGlyphQuads.size()))
967+
: vsg::ref_ptr<vsg::Latch>{};
968+
969+
for (auto& glyphQuad : sortedGlyphQuads)
970+
{
971+
error = FT_Load_Glyph(face, glyphQuad.glyph_index, load_flags);
972+
if (error) continue;
973+
974+
unsigned int width = glyphQuad.width;
975+
unsigned int height = glyphQuad.height;
976+
auto metrics = face->glyph->metrics;
977+
978+
if ((xpos + width + texel_margin) > atlas->width())
979+
{
980+
// glyph doesn't fit in present row so shift to next row.
981+
xpos = texel_margin;
982+
ypos = ytop;
983+
}
984+
985+
if (operationThreads)
986+
{
987+
// submit the task to generate contours concurrently
988+
auto operation = ContourOperation::create(glyphQuad, xpos, ypos, generateContours, threadFaceData, latch);
989+
operationThreads->add(operation);
990+
}
991+
else
992+
{
993+
// generate contours serially
994+
generateContours(face, glyphQuad, xpos, ypos);
995+
}
897996

898997
vsg::vec4 uvrect(
899998
(float(xpos - quad_margin) - 1.0f) / float(atlas->width() - 1), float(ypos + height + quad_margin) / float(atlas->height() - 1),
@@ -929,5 +1028,20 @@ vsg::ref_ptr<vsg::Object> freetype::Implementation::read(const vsg::Path& filena
9291028
font->glyphMetrics = glyphMetrics;
9301029
font->charmap = charmap;
9311030

1031+
FT_Done_Face(face);
1032+
1033+
if (operationThreads)
1034+
{
1035+
// wait for all tasks to finish
1036+
latch->wait();
1037+
1038+
// cleanup thread storage
1039+
for (auto& [_,faceData] : threadFaceData)
1040+
{
1041+
if (faceData)
1042+
FT_Done_Face(faceData);
1043+
}
1044+
}
1045+
9321046
return font;
9331047
}

0 commit comments

Comments
 (0)