@@ -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
3134namespace 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