From b00e59ab08bf9d02cead8ab4bd01a83bdd27b520 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sun, 18 Dec 2022 14:26:54 +0300 Subject: [PATCH] Add rapidjson, msdf & bitmap fonts support and bmfont parser --- .gitmodules | 3 + 3rdparty/CMakeLists.txt | 3 + 3rdparty/rapidjson | 1 + CMakeLists.txt | 2 + libs/ui/bmfont-converter.py | 27 +++++ libs/ui/include/psemek/ui/bmfont.hpp | 34 ++++++ libs/ui/include/psemek/ui/font.hpp | 8 +- libs/ui/include/psemek/ui/kerned_font.hpp | 26 +--- libs/ui/include/psemek/ui/label.hpp | 3 + libs/ui/include/psemek/ui/msdf_font.hpp | 21 ++++ libs/ui/include/psemek/ui/painter.hpp | 1 + libs/ui/include/psemek/ui/painter_impl.hpp | 1 + libs/ui/source/bmfont.cpp | 101 ++++++++++++++++ libs/ui/source/default_fonts.cpp | 14 ++- libs/ui/source/kerned_font.cpp | 39 ++++-- libs/ui/source/label.cpp | 19 ++- libs/ui/source/msdf_font.cpp | 25 ++++ libs/ui/source/painter_impl.cpp | 132 ++++++++++++++++++++- 18 files changed, 418 insertions(+), 42 deletions(-) create mode 100644 .gitmodules create mode 100644 3rdparty/CMakeLists.txt create mode 160000 3rdparty/rapidjson create mode 100755 libs/ui/bmfont-converter.py create mode 100644 libs/ui/include/psemek/ui/bmfont.hpp create mode 100644 libs/ui/include/psemek/ui/msdf_font.hpp create mode 100644 libs/ui/source/bmfont.cpp create mode 100644 libs/ui/source/msdf_font.cpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..cc37f645 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "3rdparty/rapidjson"] + path = 3rdparty/rapidjson + url = https://github.com/Tencent/rapidjson.git diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt new file mode 100644 index 00000000..ec2ad2a3 --- /dev/null +++ b/3rdparty/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB_RECURSE RAPIDJSON_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/rapidjson/include/*") +add_library(rapidjson INTERFACE EXCLUDE_FROM_ALL "${RAPIDJSON_SOURCES}") +target_include_directories(rapidjson INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/rapidjson/include") diff --git a/3rdparty/rapidjson b/3rdparty/rapidjson new file mode 160000 index 00000000..80b6d1c8 --- /dev/null +++ b/3rdparty/rapidjson @@ -0,0 +1 @@ +Subproject commit 80b6d1c83402a5785c486603c5611923159d0894 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c484fa1..76e541d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,8 @@ if(PSEMEK_UBSAN) list(APPEND CMAKE_CXX_FLAGS -fsanitize=undefined) endif() +add_subdirectory(3rdparty) + get_directory_property(PSEMEK_PARENT_DIRECTORY PARENT_DIRECTORY) add_subdirectory(package) diff --git a/libs/ui/bmfont-converter.py b/libs/ui/bmfont-converter.py new file mode 100755 index 00000000..e70dae8c --- /dev/null +++ b/libs/ui/bmfont-converter.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +import json +import sys + +result = dict() +result["info"] = dict() +result["common"] = dict() +result["chars"] = list() + +with open(sys.argv[1], "rt") as in_file: + for line in in_file: + #print("Line:", line) + parts = line.strip("\n").split(" ") + #print("Parts:", parts) + key = parts[0] + obj = {p.split("=")[0]: p.split("=")[1] for p in parts[1:] if len(p) > 0} + if key == "info": + result[key]["face"] = obj["face"].strip("\"") + result[key]["size"] = abs(int(obj["size"])) + if key == "common": + result[key]["base"] = int(obj["base"]) + if key == "char": + char = {k: int(v) for k, v in obj.items()} + result["chars"].append(char) + +print(json.dumps(result)) diff --git a/libs/ui/include/psemek/ui/bmfont.hpp b/libs/ui/include/psemek/ui/bmfont.hpp new file mode 100644 index 00000000..20b20994 --- /dev/null +++ b/libs/ui/include/psemek/ui/bmfont.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include +#include + +namespace psemek::ui +{ + + struct bmfont_data + { + struct glyph_data + { + int start_x; + int start_y; + int size_x; + int size_y; + int offset_x; + int offset_y; + int advance; + }; + + std::string name; + geom::vector size; + int baseline; + std::unordered_map glyphs; + float sdf_scale = 0.f; + + static bmfont_data parse(io::istream && stream); + }; + +} diff --git a/libs/ui/include/psemek/ui/font.hpp b/libs/ui/include/psemek/ui/font.hpp index ac0a9bd4..f0b40213 100644 --- a/libs/ui/include/psemek/ui/font.hpp +++ b/libs/ui/include/psemek/ui/font.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -16,7 +17,7 @@ namespace psemek::ui enum class font_type { bitmap, - sdf, + msdf, }; struct character_range @@ -74,6 +75,8 @@ namespace psemek::ui virtual gfx::texture_2d const & atlas() const = 0; + virtual float sdf_scale() const { return 0.f; } + virtual std::optional> texcoords(char32_t character) const = 0; virtual ~font() {} @@ -83,4 +86,7 @@ namespace psemek::ui std::unique_ptr make_default_9x12_font(); std::unique_ptr make_default_10x12_bold_font(); + std::unique_ptr make_bitmap_font(io::istream && description, io::istream && texture); + std::unique_ptr make_msdf_font(io::istream && description, io::istream && texture); + } diff --git a/libs/ui/include/psemek/ui/kerned_font.hpp b/libs/ui/include/psemek/ui/kerned_font.hpp index 79174dd0..5b3a7aaa 100644 --- a/libs/ui/include/psemek/ui/kerned_font.hpp +++ b/libs/ui/include/psemek/ui/kerned_font.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include @@ -10,25 +11,13 @@ namespace psemek::ui struct kerned_font : font { - struct glyph_data - { - int start_x; - int start_y; - int size_x; - int size_y; - int offset_x; - int offset_y; - int advance; - }; - - kerned_font(std::string_view name, geom::vector size, int baseline_offset, gfx::texture_2d atlas, - std::unordered_map glyphs); + kerned_font(bmfont_data data, gfx::texture_2d atlas); font_type type() const override { return font_type::bitmap; } - std::string_view name() const override { return name_; } + std::string_view name() const override { return data_.name; } - geom::vector size() const override { return size_; } + geom::vector size() const override { return data_.size; } bool supports_character(char32_t c) const override; util::span supported_characters() const override { return ranges_; } @@ -40,13 +29,10 @@ namespace psemek::ui std::optional> texcoords(char32_t character) const override; - private: + protected: std::vector ranges_; - std::string_view name_; - geom::vector size_; - int baseline_offset_; + bmfont_data data_; gfx::texture_2d atlas_; - std::unordered_map glyphs_; template std::vector shape_impl(String const & str, shape_options const & options, geom::point & pen) const; diff --git a/libs/ui/include/psemek/ui/label.hpp b/libs/ui/include/psemek/ui/label.hpp index 3b88e042..5e706426 100644 --- a/libs/ui/include/psemek/ui/label.hpp +++ b/libs/ui/include/psemek/ui/label.hpp @@ -142,6 +142,9 @@ namespace psemek::ui std::vector batches; geom::vector size{0.f, 0.f}; + ui::font_type font_type = ui::font_type::bitmap; + float sdf_scale = 0.f; + std::vector, std::string>> link_bboxes; }; diff --git a/libs/ui/include/psemek/ui/msdf_font.hpp b/libs/ui/include/psemek/ui/msdf_font.hpp new file mode 100644 index 00000000..a36f9e6a --- /dev/null +++ b/libs/ui/include/psemek/ui/msdf_font.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +#include + +namespace psemek::ui +{ + + struct msdf_font + : kerned_font + { + msdf_font(bmfont_data data, gfx::texture_2d atlas); + + font_type type() const override { return font_type::msdf; } + + float sdf_scale() const override { return data_.sdf_scale; } + }; + +} diff --git a/libs/ui/include/psemek/ui/painter.hpp b/libs/ui/include/psemek/ui/painter.hpp index 2870944d..919ced85 100644 --- a/libs/ui/include/psemek/ui/painter.hpp +++ b/libs/ui/include/psemek/ui/painter.hpp @@ -45,6 +45,7 @@ namespace psemek::ui using image_options = detail::image_options; virtual void draw_image(geom::box const & rect, gfx::texture_view_2d const & tex, image_options const & opts = image_options{}) = 0; + virtual void draw_msdf_glyph(geom::box const & rect, gfx::texture_view_2d const & tex, float sdf_scale, image_options const & opts = image_options{}) = 0; virtual void begin_stencil() = 0; virtual void commit_stencil() = 0; diff --git a/libs/ui/include/psemek/ui/painter_impl.hpp b/libs/ui/include/psemek/ui/painter_impl.hpp index cc6af6df..b260b208 100644 --- a/libs/ui/include/psemek/ui/painter_impl.hpp +++ b/libs/ui/include/psemek/ui/painter_impl.hpp @@ -19,6 +19,7 @@ namespace psemek::ui void draw_triangle(geom::triangle> const & tri, gfx::color_rgba const & c0, gfx::color_rgba const & c1, gfx::color_rgba const & c2) override; void draw_image(geom::box const & rect, gfx::texture_view_2d const & tex, image_options const & opts) override; + void draw_msdf_glyph(geom::box const & rect, gfx::texture_view_2d const & tex, float sdf_scale, image_options const & opts) override; void begin_stencil() override; void commit_stencil() override; diff --git a/libs/ui/source/bmfont.cpp b/libs/ui/source/bmfont.cpp new file mode 100644 index 00000000..d40db2ee --- /dev/null +++ b/libs/ui/source/bmfont.cpp @@ -0,0 +1,101 @@ +#include +#include + +#include + +#define RAPIDJSON_ASSERT(x) if (!(x)) throw ::std::runtime_error("Error parsing font description: " BOOST_PP_STRINGIZE(x)); + +#include +#include + +namespace psemek::ui +{ + + namespace + { + + std::string_view to_string(rapidjson::ParseErrorCode const & code) + { + using namespace rapidjson; + + switch (code) + { + case kParseErrorNone: return "no error"; + case kParseErrorDocumentEmpty: return "document is empty"; + case kParseErrorDocumentRootNotSingular: return "document root must not be followed by other values"; + case kParseErrorValueInvalid: return "invalid value"; + case kParseErrorObjectMissName: return "missing a name for object member"; + case kParseErrorObjectMissColon: return "missing a colon after a name of object member"; + case kParseErrorObjectMissCommaOrCurlyBracket: return "missing a comma or '}' after an object member"; + case kParseErrorArrayMissCommaOrSquareBracket: return "missing a comma or ']' after an array element"; + case kParseErrorStringUnicodeEscapeInvalidHex: return R"(Incorrect hex digit after \\u escape in string)"; + case kParseErrorStringUnicodeSurrogateInvalid: return "surrogate pair in string is invalid"; + case kParseErrorStringEscapeInvalid: return "invalid escape character in string"; + case kParseErrorStringMissQuotationMark: return "missing a closing quotation mark in string"; + case kParseErrorStringInvalidEncoding: return "invalid encoding in string"; + case kParseErrorNumberTooBig: return "number too big to be stored in double"; + case kParseErrorNumberMissFraction: return "missing fraction part in number"; + case kParseErrorNumberMissExponent: return "missing exponent in number"; + case kParseErrorTermination: return "parsing was terminated"; + case kParseErrorUnspecificSyntaxError: return "unspecific syntax error"; + default: return "(unknown error)"; + } + }; + + } + + bmfont_data bmfont_data::parse(io::istream && stream) + { + auto description_str = io::read_full(std::move(stream)); + description_str.push_back(0); + + rapidjson::Document document; + document.ParseInsitu(description_str.data()); + + if (document.HasParseError()) + throw std::runtime_error(util::to_string("Error msdf font description: ", to_string(document.GetParseError()), " at ", document.GetErrorOffset())); + + bmfont_data result; + + { + auto const & info = document["info"]; + result.name = info["face"].GetString(); + int size = info["size"].GetInt(); + result.size = {size, size}; + } + + if (document.HasMember("distanceField")) + { + auto const & sdf = document["distanceField"]; + result.sdf_scale = sdf["distanceRange"].GetFloat(); + } + + { + auto const & sdf = document["common"]; + result.baseline = sdf["base"].GetInt(); + } + + { + auto chars = document["chars"].GetArray(); + + for (auto const & charInfo : chars) + { + char32_t id = charInfo["id"].GetUint(); + + auto & data = result.glyphs[id]; + data.start_x = charInfo["x"].GetInt(); + data.start_y = charInfo["y"].GetInt(); + data.size_x = charInfo["width"].GetInt(); + data.size_y = charInfo["height"].GetInt(); + data.offset_x = charInfo["xoffset"].GetInt(); + data.offset_y = charInfo["yoffset"].GetInt(); + data.advance = charInfo["xadvance"].GetInt(); + + data.offset_y = - data.offset_y - data.size_y; + } + } + + return result; + } + +} diff --git a/libs/ui/source/default_fonts.cpp b/libs/ui/source/default_fonts.cpp index 0c18bd4e..b74e470e 100644 --- a/libs/ui/source/default_fonts.cpp +++ b/libs/ui/source/default_fonts.cpp @@ -55,7 +55,11 @@ namespace psemek::ui gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_B, gl::RED); gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_A, gl::RED); - std::unordered_map glyphs; + bmfont_data data; + data.name = name; + data.size = size; + data.baseline = 2; + { std::istringstream is{std::string(glyphs_resource.data)}; @@ -70,14 +74,14 @@ namespace psemek::ui std::istringstream is{std::move(line)}; int c; - kerned_font::glyph_data data; + is >> c; + auto & glyph = data.glyphs[c]; - is >> c >> data.start_x >> data.start_y >> data.size_x >> data.size_y >> data.offset_x >> data.offset_y >> data.advance; - glyphs[c] = data; + is >> glyph.start_x >> glyph.start_y >> glyph.size_x >> glyph.size_y >> glyph.offset_x >> glyph.offset_y >> glyph.advance; } } - return std::make_unique(name, size, 2, std::move(atlas), std::move(glyphs)); + return std::make_unique(std::move(data), std::move(atlas)); } std::unique_ptr make_default_9x12_font() diff --git a/libs/ui/source/kerned_font.cpp b/libs/ui/source/kerned_font.cpp index 59acf0a6..9c59a164 100644 --- a/libs/ui/source/kerned_font.cpp +++ b/libs/ui/source/kerned_font.cpp @@ -5,13 +5,9 @@ namespace psemek::ui { - kerned_font::kerned_font(std::string_view name, geom::vector size, int baseline_offset, gfx::texture_2d atlas, - std::unordered_map glyphs) - : name_{name} - , size_{size} - , baseline_offset_{baseline_offset} + kerned_font::kerned_font(bmfont_data data, gfx::texture_2d atlas) + : data_(std::move(data)) , atlas_{std::move(atlas)} - , glyphs_{std::move(glyphs)} { if (!supports_character('?')) throw std::runtime_error("Kerned font must support '?' character"); @@ -19,7 +15,7 @@ namespace psemek::ui throw std::runtime_error("Kerned font must support ' ' character"); std::vector chars; - for (auto const & g : glyphs_) + for (auto const & g : data_.glyphs) chars.push_back(g.first); std::sort(chars.begin(), chars.end()); @@ -47,7 +43,7 @@ namespace psemek::ui bool kerned_font::supports_character(char32_t c) const { - return std::isspace(c) || glyphs_.contains(c); + return std::isspace(c) || data_.glyphs.contains(c); } std::vector kerned_font::shape(std::string_view str, shape_options const & options, geom::point & pen) const @@ -68,7 +64,7 @@ namespace psemek::ui std::vector result; - float const offset_to_baseline = (size_[1] - baseline_offset_) * options.scale; + float const offset_to_baseline = (data_.size[1] - data_.baseline) * options.scale; for (char32_t c : str) { if (!supports_character(c)) @@ -78,7 +74,7 @@ namespace psemek::ui if (std::isspace(c)) cc = ' '; - auto const & data = glyphs_.at(cc); + auto const & data = data_.glyphs.at(cc); glyph g; g.character = c; @@ -88,7 +84,7 @@ namespace psemek::ui g.position[1].max = pen[1] + offset_to_baseline - data.offset_y * options.scale; result.push_back(g); - geom::vector advance{data.advance * options.scale, size_[1] * options.scale}; + geom::vector advance{data.advance * options.scale, data_.size[1] * options.scale}; pen += geom::pointwise_mult(advance_mask, advance); } @@ -101,8 +97,8 @@ namespace psemek::ui if (std::isspace(c)) c = ' '; - auto it = glyphs_.find(c); - if (it == glyphs_.end()) + auto it = data_.glyphs.find(c); + if (it == data_.glyphs.end()) return std::nullopt; geom::box box; @@ -114,4 +110,21 @@ namespace psemek::ui return box; } + std::unique_ptr make_bitmap_font(io::istream && description, io::istream && texture) + { + auto data = bmfont_data::parse(std::move(description)); + + auto pixmap = gfx::read_png_monochrome(std::move(texture)); + gfx::texture_2d atlas; + atlas.load(pixmap); + atlas.linear_filter(); + atlas.clamp(); + gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_R, gl::ONE); + gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_G, gl::ONE); + gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_B, gl::ONE); + gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_A, gl::RED); + + return std::make_unique(std::move(data), std::move(atlas)); + } + } diff --git a/libs/ui/source/label.cpp b/libs/ui/source/label.cpp index ebb1cdae..c9af50a5 100644 --- a/libs/ui/source/label.cpp +++ b/libs/ui/source/label.cpp @@ -308,13 +308,25 @@ namespace psemek::ui auto color = *st->shadow_color; color[3] = (color[3] * 1.f * batch.color[3]) / 255.f; for (auto const & image : batch.images) - p.draw_image(image.position + offset, gfx::texture_view_2d{batch.texture, image.texcoords}, {color, painter::color_mode::multiply}); + { + if (cached_state_->font_type == font_type::bitmap) + p.draw_image(image.position + offset, gfx::texture_view_2d{batch.texture, image.texcoords}, {color, painter::color_mode::multiply}); + else if (cached_state_->font_type == font_type::msdf) + p.draw_msdf_glyph(image.position + offset, gfx::texture_view_2d{batch.texture, image.texcoords}, cached_state_->sdf_scale, {color}); + } } } for (auto const & batch : cached_state_->batches) + { for (auto const & image : batch.images) - p.draw_image(image.position, gfx::texture_view_2d{batch.texture, image.texcoords}, {batch.color, batch.text ? painter::color_mode::multiply : painter::color_mode::mix}); + { + if (cached_state_->font_type == font_type::bitmap) + p.draw_image(image.position, gfx::texture_view_2d{batch.texture, image.texcoords}, {batch.color, batch.text ? painter::color_mode::multiply : painter::color_mode::mix}); + else if (cached_state_->font_type == font_type::msdf) + p.draw_msdf_glyph(image.position, gfx::texture_view_2d{batch.texture, image.texcoords}, cached_state_->sdf_scale, {batch.color}); + } + } } void label::on_state_changed() @@ -342,6 +354,9 @@ namespace psemek::ui auto st = merged_own_style(); + state.font_type = st->font->type(); + state.sdf_scale = st->font->sdf_scale(); + auto const font_height = st->font->size()[1] * (*st->text_scale); // Shape items (glyphs/images) chunk by chunk diff --git a/libs/ui/source/msdf_font.cpp b/libs/ui/source/msdf_font.cpp new file mode 100644 index 00000000..793095bf --- /dev/null +++ b/libs/ui/source/msdf_font.cpp @@ -0,0 +1,25 @@ +#include +#include + +namespace psemek::ui +{ + + std::unique_ptr make_msdf_font(io::istream && description, io::istream && texture) + { + auto data = bmfont_data::parse(std::move(description)); + + auto pixmap = gfx::read_png(std::move(texture)); + gfx::texture_2d atlas; + atlas.load(pixmap); + atlas.linear_mipmap_filter(); + atlas.anisotropy(); + atlas.generate_mipmap(); + + return std::make_unique(std::move(data), std::move(atlas)); + } + + msdf_font::msdf_font(bmfont_data data, gfx::texture_2d atlas) + : kerned_font(std::move(data), std::move(atlas)) + {} + +} diff --git a/libs/ui/source/painter_impl.cpp b/libs/ui/source/painter_impl.cpp index 15f7925f..c9fda087 100644 --- a/libs/ui/source/painter_impl.cpp +++ b/libs/ui/source/painter_impl.cpp @@ -103,6 +103,58 @@ void main() if (tex_color.a == 0.0) discard; out_color = tex_color * color; } +)"; + + char const msdf_vs[] = +R"(#version 330 + +uniform mat4 u_transform; + +uniform vec2 u_texture_size; + +layout (location = 0) in vec2 in_position; +layout (location = 1) in float in_depth; +layout (location = 2) in vec4 in_color; +layout (location = 3) in vec2 in_texcoord; + +out vec4 color; +out vec2 texcoord; + +void main() +{ + gl_Position = u_transform * vec4(in_position, 1.0 - float(in_depth) / 16777216.0 * 2.0, 1.0); + color = in_color; + texcoord = in_texcoord / u_texture_size; +} +)"; + + char const msdf_fs[] = +R"(#version 330 + +uniform sampler2D u_texture; +uniform float u_sdf_scale; + +in vec2 texcoord; +in vec4 color; + +out vec4 out_color; + +float median(vec3 v) { + return max(min(v.r, v.g), min(max(v.r, v.g), v.b)); +} + +void main() +{ + float sdf_value = u_sdf_scale * (median(texture(u_texture, texcoord).rgb) - 0.5); + + float aa_step = length(vec2(dFdx(sdf_value), dFdy(sdf_value))) * sqrt(2.0); + + float alpha = smoothstep(-aa_step * 0.5, aa_step * 0.5, sdf_value); + + if (alpha == 0.0) discard; + + out_color = vec4(color.rgb, color.a * alpha); +} )"; struct colored_vertex @@ -146,6 +198,29 @@ void main() return b1.texture == b2.texture && b1.mode == b2.mode; } + struct msdf_vertex + { + geom::point position; + std::uint32_t depth; + gfx::color_rgba color; + geom::point texcoords; + }; + static_assert(sizeof(textured_vertex) == 24); + + struct msdf_batch + { + gfx::texture_2d const * texture = nullptr; + painter::color_mode mode = painter::color_mode::mix; + float sdf_scale{0.f}; + std::vector vertices{}; + std::vector indices{}; + }; + + bool operator == (msdf_batch const & b1, msdf_batch const & b2) + { + return b1.texture == b2.texture && b1.mode == b2.mode && b1.sdf_scale == b2.sdf_scale; + } + struct stencil_batch { std::optional enable_stencil; @@ -176,13 +251,16 @@ void main() gfx::program textured_multiply_program; gfx::mesh textured_mesh; + gfx::program msdf_program; + gfx::mesh msdf_mesh; + int stencil_level = 0; static constexpr int max_stencil_level = 255; std::vector> stencil_bbox; geom::box draw_bbox; - std::vector> batches; + std::vector> batches; std::size_t max_batch_count = 0; std::size_t max_primitive_count = 0; @@ -207,6 +285,7 @@ void main() : colored_program{colored_vs, colored_fs} , textured_mix_program{textured_vs, textured_mix_fs} , textured_multiply_program{textured_vs, textured_multiply_fs} + , msdf_program{msdf_vs, msdf_fs} { textured_mix_program.bind(); textured_mix_program["u_texture"] = 0; @@ -214,8 +293,12 @@ void main() textured_multiply_program.bind(); textured_multiply_program["u_texture"] = 0; + msdf_program.bind(); + msdf_program["u_texture"] = 0; + colored_mesh.setup, std::uint32_t, gfx::normalized>(); textured_mesh.setup, std::uint32_t, gfx::normalized, geom::vector>(); + msdf_mesh.setup, std::uint32_t, gfx::normalized, geom::vector>(); } painter_impl::painter_impl() @@ -295,6 +378,38 @@ void main() impl().draw_bbox |= rect; } + void painter_impl::draw_msdf_glyph(geom::box const & rect, gfx::texture_view_2d const & tex, float sdf_scale, image_options const & options) + { + auto & batch = impl().batch(msdf_batch{tex.texture, options.mode, sdf_scale}); + + std::uint32_t const depth = impl().depth++; + std::uint32_t const base = batch.vertices.size(); + + auto p00 = rect.corner(0.f, 0.f); + auto p10 = rect.corner(1.f, 0.f); + auto p01 = rect.corner(0.f, 1.f); + auto p11 = rect.corner(1.f, 1.f); + + if (options.rotation != 0.f) + { + auto const c = rect.center(); + + p00 = c + geom::rotate(p00 - c, options.rotation); + p10 = c + geom::rotate(p10 - c, options.rotation); + p01 = c + geom::rotate(p01 - c, options.rotation); + p11 = c + geom::rotate(p11 - c, options.rotation); + } + + batch.vertices.push_back({p00, depth, options.color, tex.part.corner(0.f, 0.f)}); + batch.vertices.push_back({p10, depth, options.color, tex.part.corner(1.f, 0.f)}); + batch.vertices.push_back({p01, depth, options.color, tex.part.corner(0.f, 1.f)}); + batch.vertices.push_back({p11, depth, options.color, tex.part.corner(1.f, 1.f)}); + + batch.indices.insert(batch.indices.end(), {base + 0, base + 1, base + 2, base + 2, base + 1, base + 3}); + + impl().draw_bbox |= rect; + } + void painter_impl::begin_stencil() { if (impl().stencil_level == impl().max_stencil_level) @@ -387,6 +502,8 @@ void main() impl().textured_mix_program["u_transform"] = transform; impl().textured_multiply_program.bind(); impl().textured_multiply_program["u_transform"] = transform; + impl().msdf_program.bind(); + impl().msdf_program["u_transform"] = transform; gl::ActiveTexture(gl::TEXTURE0); @@ -409,6 +526,19 @@ void main() program.bind(); program["u_texture_size"] = geom::cast(b.texture->size()); b.texture->bind(); + impl().msdf_mesh.load(b.vertices, b.indices, gl::TRIANGLES); + impl().msdf_mesh.draw(); + }, + [&](msdf_batch const & b){ + if (!b.texture) return; + + primitive_count += b.indices.size() / 3; + + gfx::program & program = impl().msdf_program; + program.bind(); + program["u_texture_size"] = geom::cast(b.texture->size()); + program["u_sdf_scale"] = b.sdf_scale; + b.texture->bind(); impl().textured_mesh.load(b.vertices, b.indices, gl::TRIANGLES); impl().textured_mesh.draw(); },