Add rapidjson, msdf & bitmap fonts support and bmfont parser

This commit is contained in:
Nikita Lisitsa 2022-12-18 14:26:54 +03:00
parent 87c612a0eb
commit b00e59ab08
18 changed files with 418 additions and 42 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "3rdparty/rapidjson"]
path = 3rdparty/rapidjson
url = https://github.com/Tencent/rapidjson.git

3
3rdparty/CMakeLists.txt vendored Normal file
View file

@ -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")

1
3rdparty/rapidjson vendored Submodule

@ -0,0 +1 @@
Subproject commit 80b6d1c83402a5785c486603c5611923159d0894

View file

@ -41,6 +41,8 @@ if(PSEMEK_UBSAN)
list(APPEND CMAKE_CXX_FLAGS -fsanitize=undefined) list(APPEND CMAKE_CXX_FLAGS -fsanitize=undefined)
endif() endif()
add_subdirectory(3rdparty)
get_directory_property(PSEMEK_PARENT_DIRECTORY PARENT_DIRECTORY) get_directory_property(PSEMEK_PARENT_DIRECTORY PARENT_DIRECTORY)
add_subdirectory(package) add_subdirectory(package)

27
libs/ui/bmfont-converter.py Executable file
View file

@ -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))

View file

@ -0,0 +1,34 @@
#pragma once
#include <psemek/geom/vector.hpp>
#include <psemek/io/stream.hpp>
#include <string>
#include <unordered_map>
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<int, 2> size;
int baseline;
std::unordered_map<char32_t, glyph_data> glyphs;
float sdf_scale = 0.f;
static bmfont_data parse(io::istream && stream);
};
}

View file

@ -5,6 +5,7 @@
#include <psemek/util/span.hpp> #include <psemek/util/span.hpp>
#include <psemek/gfx/texture.hpp> #include <psemek/gfx/texture.hpp>
#include <psemek/io/stream.hpp>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@ -16,7 +17,7 @@ namespace psemek::ui
enum class font_type enum class font_type
{ {
bitmap, bitmap,
sdf, msdf,
}; };
struct character_range struct character_range
@ -74,6 +75,8 @@ namespace psemek::ui
virtual gfx::texture_2d const & atlas() const = 0; virtual gfx::texture_2d const & atlas() const = 0;
virtual float sdf_scale() const { return 0.f; }
virtual std::optional<geom::box<float, 2>> texcoords(char32_t character) const = 0; virtual std::optional<geom::box<float, 2>> texcoords(char32_t character) const = 0;
virtual ~font() {} virtual ~font() {}
@ -83,4 +86,7 @@ namespace psemek::ui
std::unique_ptr<font> make_default_9x12_font(); std::unique_ptr<font> make_default_9x12_font();
std::unique_ptr<font> make_default_10x12_bold_font(); std::unique_ptr<font> make_default_10x12_bold_font();
std::unique_ptr<font> make_bitmap_font(io::istream && description, io::istream && texture);
std::unique_ptr<font> make_msdf_font(io::istream && description, io::istream && texture);
} }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <psemek/ui/font.hpp> #include <psemek/ui/font.hpp>
#include <psemek/ui/bmfont.hpp>
#include <unordered_map> #include <unordered_map>
@ -10,25 +11,13 @@ namespace psemek::ui
struct kerned_font struct kerned_font
: font : font
{ {
struct glyph_data kerned_font(bmfont_data data, gfx::texture_2d atlas);
{
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<int, 2> size, int baseline_offset, gfx::texture_2d atlas,
std::unordered_map<char32_t, glyph_data> glyphs);
font_type type() const override { return font_type::bitmap; } 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<int, 2> size() const override { return size_; } geom::vector<int, 2> size() const override { return data_.size; }
bool supports_character(char32_t c) const override; bool supports_character(char32_t c) const override;
util::span<character_range const> supported_characters() const override { return ranges_; } util::span<character_range const> supported_characters() const override { return ranges_; }
@ -40,13 +29,10 @@ namespace psemek::ui
std::optional<geom::box<float, 2>> texcoords(char32_t character) const override; std::optional<geom::box<float, 2>> texcoords(char32_t character) const override;
private: protected:
std::vector<character_range> ranges_; std::vector<character_range> ranges_;
std::string_view name_; bmfont_data data_;
geom::vector<int, 2> size_;
int baseline_offset_;
gfx::texture_2d atlas_; gfx::texture_2d atlas_;
std::unordered_map<char32_t, glyph_data> glyphs_;
template <typename String> template <typename String>
std::vector<glyph> shape_impl(String const & str, shape_options const & options, geom::point<float, 2> & pen) const; std::vector<glyph> shape_impl(String const & str, shape_options const & options, geom::point<float, 2> & pen) const;

View file

@ -142,6 +142,9 @@ namespace psemek::ui
std::vector<batch> batches; std::vector<batch> batches;
geom::vector<float, 2> size{0.f, 0.f}; geom::vector<float, 2> size{0.f, 0.f};
ui::font_type font_type = ui::font_type::bitmap;
float sdf_scale = 0.f;
std::vector<std::pair<geom::box<float, 2>, std::string>> link_bboxes; std::vector<std::pair<geom::box<float, 2>, std::string>> link_bboxes;
}; };

View file

@ -0,0 +1,21 @@
#pragma once
#include <psemek/ui/font.hpp>
#include <psemek/ui/kerned_font.hpp>
#include <unordered_map>
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; }
};
}

View file

@ -45,6 +45,7 @@ namespace psemek::ui
using image_options = detail::image_options; using image_options = detail::image_options;
virtual void draw_image(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, image_options const & opts = image_options{}) = 0; virtual void draw_image(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, image_options const & opts = image_options{}) = 0;
virtual void draw_msdf_glyph(geom::box<float, 2> 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 begin_stencil() = 0;
virtual void commit_stencil() = 0; virtual void commit_stencil() = 0;

View file

@ -19,6 +19,7 @@ namespace psemek::ui
void draw_triangle(geom::triangle<geom::point<float, 2>> const & tri, gfx::color_rgba const & c0, gfx::color_rgba const & c1, gfx::color_rgba const & c2) override; void draw_triangle(geom::triangle<geom::point<float, 2>> const & tri, gfx::color_rgba const & c0, gfx::color_rgba const & c1, gfx::color_rgba const & c2) override;
void draw_image(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, image_options const & opts) override; void draw_image(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, image_options const & opts) override;
void draw_msdf_glyph(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, float sdf_scale, image_options const & opts) override;
void begin_stencil() override; void begin_stencil() override;
void commit_stencil() override; void commit_stencil() override;

101
libs/ui/source/bmfont.cpp Normal file
View file

@ -0,0 +1,101 @@
#include <psemek/ui/bmfont.hpp>
#include <psemek/util/to_string.hpp>
#include <boost/preprocessor/stringize.hpp>
#define RAPIDJSON_ASSERT(x) if (!(x)) throw ::std::runtime_error("Error parsing font description: " BOOST_PP_STRINGIZE(x));
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>
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;
}
}

View file

@ -55,7 +55,11 @@ namespace psemek::ui
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_B, gl::RED); gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_B, gl::RED);
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_A, gl::RED); gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_A, gl::RED);
std::unordered_map<char32_t, kerned_font::glyph_data> glyphs; bmfont_data data;
data.name = name;
data.size = size;
data.baseline = 2;
{ {
std::istringstream is{std::string(glyphs_resource.data)}; std::istringstream is{std::string(glyphs_resource.data)};
@ -70,14 +74,14 @@ namespace psemek::ui
std::istringstream is{std::move(line)}; std::istringstream is{std::move(line)};
int c; 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; is >> glyph.start_x >> glyph.start_y >> glyph.size_x >> glyph.size_y >> glyph.offset_x >> glyph.offset_y >> glyph.advance;
glyphs[c] = data;
} }
} }
return std::make_unique<kerned_font>(name, size, 2, std::move(atlas), std::move(glyphs)); return std::make_unique<kerned_font>(std::move(data), std::move(atlas));
} }
std::unique_ptr<font> make_default_9x12_font() std::unique_ptr<font> make_default_9x12_font()

View file

@ -5,13 +5,9 @@
namespace psemek::ui namespace psemek::ui
{ {
kerned_font::kerned_font(std::string_view name, geom::vector<int, 2> size, int baseline_offset, gfx::texture_2d atlas, kerned_font::kerned_font(bmfont_data data, gfx::texture_2d atlas)
std::unordered_map<char32_t, glyph_data> glyphs) : data_(std::move(data))
: name_{name}
, size_{size}
, baseline_offset_{baseline_offset}
, atlas_{std::move(atlas)} , atlas_{std::move(atlas)}
, glyphs_{std::move(glyphs)}
{ {
if (!supports_character('?')) if (!supports_character('?'))
throw std::runtime_error("Kerned font must support '?' 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"); throw std::runtime_error("Kerned font must support ' ' character");
std::vector<char32_t> chars; std::vector<char32_t> chars;
for (auto const & g : glyphs_) for (auto const & g : data_.glyphs)
chars.push_back(g.first); chars.push_back(g.first);
std::sort(chars.begin(), chars.end()); std::sort(chars.begin(), chars.end());
@ -47,7 +43,7 @@ namespace psemek::ui
bool kerned_font::supports_character(char32_t c) const 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<glyph> kerned_font::shape(std::string_view str, shape_options const & options, geom::point<float, 2> & pen) const std::vector<glyph> kerned_font::shape(std::string_view str, shape_options const & options, geom::point<float, 2> & pen) const
@ -68,7 +64,7 @@ namespace psemek::ui
std::vector<glyph> result; std::vector<glyph> 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) for (char32_t c : str)
{ {
if (!supports_character(c)) if (!supports_character(c))
@ -78,7 +74,7 @@ namespace psemek::ui
if (std::isspace(c)) if (std::isspace(c))
cc = ' '; cc = ' ';
auto const & data = glyphs_.at(cc); auto const & data = data_.glyphs.at(cc);
glyph g; glyph g;
g.character = c; g.character = c;
@ -88,7 +84,7 @@ namespace psemek::ui
g.position[1].max = pen[1] + offset_to_baseline - data.offset_y * options.scale; g.position[1].max = pen[1] + offset_to_baseline - data.offset_y * options.scale;
result.push_back(g); result.push_back(g);
geom::vector<float, 2> advance{data.advance * options.scale, size_[1] * options.scale}; geom::vector<float, 2> advance{data.advance * options.scale, data_.size[1] * options.scale};
pen += geom::pointwise_mult(advance_mask, advance); pen += geom::pointwise_mult(advance_mask, advance);
} }
@ -101,8 +97,8 @@ namespace psemek::ui
if (std::isspace(c)) if (std::isspace(c))
c = ' '; c = ' ';
auto it = glyphs_.find(c); auto it = data_.glyphs.find(c);
if (it == glyphs_.end()) if (it == data_.glyphs.end())
return std::nullopt; return std::nullopt;
geom::box<float, 2> box; geom::box<float, 2> box;
@ -114,4 +110,21 @@ namespace psemek::ui
return box; return box;
} }
std::unique_ptr<font> 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<kerned_font>(std::move(data), std::move(atlas));
}
} }

View file

@ -308,13 +308,25 @@ namespace psemek::ui
auto color = *st->shadow_color; auto color = *st->shadow_color;
color[3] = (color[3] * 1.f * batch.color[3]) / 255.f; color[3] = (color[3] * 1.f * batch.color[3]) / 255.f;
for (auto const & image : batch.images) 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 & batch : cached_state_->batches)
{
for (auto const & image : batch.images) 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() void label::on_state_changed()
@ -342,6 +354,9 @@ namespace psemek::ui
auto st = merged_own_style(); 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); auto const font_height = st->font->size()[1] * (*st->text_scale);
// Shape items (glyphs/images) chunk by chunk // Shape items (glyphs/images) chunk by chunk

View file

@ -0,0 +1,25 @@
#include <psemek/ui/msdf_font.hpp>
#include <psemek/gfx/pixmap.hpp>
namespace psemek::ui
{
std::unique_ptr<font> 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<msdf_font>(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))
{}
}

View file

@ -103,6 +103,58 @@ void main()
if (tex_color.a == 0.0) discard; if (tex_color.a == 0.0) discard;
out_color = tex_color * color; 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 struct colored_vertex
@ -146,6 +198,29 @@ void main()
return b1.texture == b2.texture && b1.mode == b2.mode; return b1.texture == b2.texture && b1.mode == b2.mode;
} }
struct msdf_vertex
{
geom::point<float, 2> position;
std::uint32_t depth;
gfx::color_rgba color;
geom::point<float, 2> 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<textured_vertex> vertices{};
std::vector<std::uint32_t> 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 struct stencil_batch
{ {
std::optional<bool> enable_stencil; std::optional<bool> enable_stencil;
@ -176,13 +251,16 @@ void main()
gfx::program textured_multiply_program; gfx::program textured_multiply_program;
gfx::mesh textured_mesh; gfx::mesh textured_mesh;
gfx::program msdf_program;
gfx::mesh msdf_mesh;
int stencil_level = 0; int stencil_level = 0;
static constexpr int max_stencil_level = 255; static constexpr int max_stencil_level = 255;
std::vector<geom::box<float, 2>> stencil_bbox; std::vector<geom::box<float, 2>> stencil_bbox;
geom::box<float, 2> draw_bbox; geom::box<float, 2> draw_bbox;
std::vector<std::variant<colored_batch, textured_batch, stencil_batch>> batches; std::vector<std::variant<colored_batch, textured_batch, msdf_batch, stencil_batch>> batches;
std::size_t max_batch_count = 0; std::size_t max_batch_count = 0;
std::size_t max_primitive_count = 0; std::size_t max_primitive_count = 0;
@ -207,6 +285,7 @@ void main()
: colored_program{colored_vs, colored_fs} : colored_program{colored_vs, colored_fs}
, textured_mix_program{textured_vs, textured_mix_fs} , textured_mix_program{textured_vs, textured_mix_fs}
, textured_multiply_program{textured_vs, textured_multiply_fs} , textured_multiply_program{textured_vs, textured_multiply_fs}
, msdf_program{msdf_vs, msdf_fs}
{ {
textured_mix_program.bind(); textured_mix_program.bind();
textured_mix_program["u_texture"] = 0; textured_mix_program["u_texture"] = 0;
@ -214,8 +293,12 @@ void main()
textured_multiply_program.bind(); textured_multiply_program.bind();
textured_multiply_program["u_texture"] = 0; textured_multiply_program["u_texture"] = 0;
msdf_program.bind();
msdf_program["u_texture"] = 0;
colored_mesh.setup<geom::point<float, 2>, std::uint32_t, gfx::normalized<gfx::color_rgba>>(); colored_mesh.setup<geom::point<float, 2>, std::uint32_t, gfx::normalized<gfx::color_rgba>>();
textured_mesh.setup<geom::point<float, 2>, std::uint32_t, gfx::normalized<gfx::color_rgba>, geom::vector<float, 2>>(); textured_mesh.setup<geom::point<float, 2>, std::uint32_t, gfx::normalized<gfx::color_rgba>, geom::vector<float, 2>>();
msdf_mesh.setup<geom::point<float, 2>, std::uint32_t, gfx::normalized<gfx::color_rgba>, geom::vector<float, 2>>();
} }
painter_impl::painter_impl() painter_impl::painter_impl()
@ -295,6 +378,38 @@ void main()
impl().draw_bbox |= rect; impl().draw_bbox |= rect;
} }
void painter_impl::draw_msdf_glyph(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, float sdf_scale, image_options const & options)
{
auto & batch = impl().batch<msdf_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() void painter_impl::begin_stencil()
{ {
if (impl().stencil_level == impl().max_stencil_level) if (impl().stencil_level == impl().max_stencil_level)
@ -387,6 +502,8 @@ void main()
impl().textured_mix_program["u_transform"] = transform; impl().textured_mix_program["u_transform"] = transform;
impl().textured_multiply_program.bind(); impl().textured_multiply_program.bind();
impl().textured_multiply_program["u_transform"] = transform; impl().textured_multiply_program["u_transform"] = transform;
impl().msdf_program.bind();
impl().msdf_program["u_transform"] = transform;
gl::ActiveTexture(gl::TEXTURE0); gl::ActiveTexture(gl::TEXTURE0);
@ -409,6 +526,19 @@ void main()
program.bind(); program.bind();
program["u_texture_size"] = geom::cast<float>(b.texture->size()); program["u_texture_size"] = geom::cast<float>(b.texture->size());
b.texture->bind(); 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<float>(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.load(b.vertices, b.indices, gl::TRIANGLES);
impl().textured_mesh.draw(); impl().textured_mesh.draw();
}, },