Add rapidjson, msdf & bitmap fonts support and bmfont parser
This commit is contained in:
parent
87c612a0eb
commit
b00e59ab08
18 changed files with 418 additions and 42 deletions
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal 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
3
3rdparty/CMakeLists.txt
vendored
Normal 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
1
3rdparty/rapidjson
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 80b6d1c83402a5785c486603c5611923159d0894
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
27
libs/ui/bmfont-converter.py
Executable file
27
libs/ui/bmfont-converter.py
Executable 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))
|
||||
34
libs/ui/include/psemek/ui/bmfont.hpp
Normal file
34
libs/ui/include/psemek/ui/bmfont.hpp
Normal 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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include <psemek/util/span.hpp>
|
||||
|
||||
#include <psemek/gfx/texture.hpp>
|
||||
#include <psemek/io/stream.hpp>
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
|
@ -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<geom::box<float, 2>> texcoords(char32_t character) const = 0;
|
||||
|
||||
virtual ~font() {}
|
||||
|
|
@ -83,4 +86,7 @@ namespace psemek::ui
|
|||
std::unique_ptr<font> make_default_9x12_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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/ui/font.hpp>
|
||||
#include <psemek/ui/bmfont.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
|
|
@ -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<int, 2> size, int baseline_offset, gfx::texture_2d atlas,
|
||||
std::unordered_map<char32_t, glyph_data> 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<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;
|
||||
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;
|
||||
|
||||
private:
|
||||
protected:
|
||||
std::vector<character_range> ranges_;
|
||||
std::string_view name_;
|
||||
geom::vector<int, 2> size_;
|
||||
int baseline_offset_;
|
||||
bmfont_data data_;
|
||||
gfx::texture_2d atlas_;
|
||||
std::unordered_map<char32_t, glyph_data> glyphs_;
|
||||
|
||||
template <typename String>
|
||||
std::vector<glyph> shape_impl(String const & str, shape_options const & options, geom::point<float, 2> & pen) const;
|
||||
|
|
|
|||
|
|
@ -142,6 +142,9 @@ namespace psemek::ui
|
|||
std::vector<batch> batches;
|
||||
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;
|
||||
};
|
||||
|
||||
|
|
|
|||
21
libs/ui/include/psemek/ui/msdf_font.hpp
Normal file
21
libs/ui/include/psemek/ui/msdf_font.hpp
Normal 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; }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ namespace psemek::ui
|
|||
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_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 commit_stencil() = 0;
|
||||
|
|
|
|||
|
|
@ -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_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 commit_stencil() override;
|
||||
|
|
|
|||
101
libs/ui/source/bmfont.cpp
Normal file
101
libs/ui/source/bmfont.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<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)};
|
||||
|
||||
|
|
@ -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<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()
|
||||
|
|
|
|||
|
|
@ -5,13 +5,9 @@
|
|||
namespace psemek::ui
|
||||
{
|
||||
|
||||
kerned_font::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)
|
||||
: 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<char32_t> 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<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;
|
||||
|
||||
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<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);
|
||||
}
|
||||
|
|
@ -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<float, 2> box;
|
||||
|
|
@ -114,4 +110,21 @@ namespace psemek::ui
|
|||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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
|
||||
|
|
|
|||
25
libs/ui/source/msdf_font.cpp
Normal file
25
libs/ui/source/msdf_font.cpp
Normal 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))
|
||||
{}
|
||||
|
||||
}
|
||||
|
|
@ -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<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
|
||||
{
|
||||
std::optional<bool> 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<geom::box<float, 2>> stencil_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_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<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>>();
|
||||
msdf_mesh.setup<geom::point<float, 2>, std::uint32_t, gfx::normalized<gfx::color_rgba>, geom::vector<float, 2>>();
|
||||
}
|
||||
|
||||
painter_impl::painter_impl()
|
||||
|
|
@ -295,6 +378,38 @@ void main()
|
|||
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()
|
||||
{
|
||||
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<float>(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<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.draw();
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue