feat: add text rendering with SDF's
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#define DESTRUM_RENDERER2D_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
@@ -9,10 +10,18 @@
|
||||
#include <destrum/Graphics/ids.h>
|
||||
#include <destrum/Singleton.h>
|
||||
|
||||
// windows.h maps DrawText to DrawTextA/W
|
||||
#ifdef DrawText
|
||||
#undef DrawText
|
||||
#endif
|
||||
|
||||
class Font;
|
||||
|
||||
enum class Shape2DType : std::uint32_t {
|
||||
Rect = 0,
|
||||
Ellipse = 1,
|
||||
Triangle = 2,
|
||||
Text = 3,
|
||||
};
|
||||
|
||||
enum class TextureFilter : std::uint32_t {
|
||||
@@ -41,7 +50,15 @@ struct ShapeStyle {
|
||||
Texture2D texture{};
|
||||
};
|
||||
|
||||
// Mirrors Shape2D in shape2d.glsl (scalar layout)
|
||||
struct TextStyle {
|
||||
glm::vec4 color{1.0f};
|
||||
// Grows outwards from the glyphs, at most the font's SDF spread
|
||||
float outline{0.0f};
|
||||
glm::vec4 outlineColor{0.0f, 0.0f, 0.0f, 1.0f};
|
||||
};
|
||||
|
||||
// Mirrors Shape2D in shape2d.glsl (scalar layout). Text glyphs store the SDF distance
|
||||
// scale in p0.x and the outline colour in p1/p2.
|
||||
struct Shape2D {
|
||||
glm::vec2 center;
|
||||
glm::vec2 halfSize;
|
||||
@@ -94,6 +111,20 @@ public:
|
||||
const glm::vec4& color,
|
||||
LineCap cap = LineCap::Butt);
|
||||
|
||||
// size is the line size in pixels, the font has to be uploaded
|
||||
void DrawText(
|
||||
const Font& font,
|
||||
std::string_view text,
|
||||
const glm::vec2& topLeft,
|
||||
float size,
|
||||
const glm::vec4& color);
|
||||
void DrawText(
|
||||
const Font& font,
|
||||
std::string_view text,
|
||||
const glm::vec2& topLeft,
|
||||
float size,
|
||||
const TextStyle& style);
|
||||
|
||||
[[nodiscard]] const std::vector<Shape2D>& GetShapes() const { return shapes; }
|
||||
void Clear();
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
#ifndef DESTRUM_FONT_H
|
||||
#define DESTRUM_FONT_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <destrum/Graphics/ids.h>
|
||||
|
||||
class GfxDevice;
|
||||
class RenderResources;
|
||||
|
||||
struct FontGlyph {
|
||||
// In atlas pixels, relative to the pen position on the baseline, y down
|
||||
glm::vec2 offset{0.0f};
|
||||
glm::vec2 size{0.0f};
|
||||
float advance{0.0f};
|
||||
glm::vec2 uvMin{0.0f};
|
||||
glm::vec2 uvMax{0.0f};
|
||||
};
|
||||
|
||||
struct PlacedGlyph {
|
||||
const FontGlyph* glyph;
|
||||
glm::vec2 topLeft;
|
||||
glm::vec2 size;
|
||||
};
|
||||
|
||||
// Signed distance field glyph atlas, so one atlas renders crisp text at any size
|
||||
class Font final {
|
||||
public:
|
||||
static constexpr float DefaultPixelSize = 48.0f;
|
||||
static constexpr int SdfSpread = 8;
|
||||
|
||||
static Font LoadFromFile(const std::filesystem::path& path, float pixelSize = DefaultPixelSize);
|
||||
|
||||
void Upload(GfxDevice& gfxDevice, RenderResources& resources);
|
||||
|
||||
[[nodiscard]] const FontGlyph* GetGlyph(char32_t codepoint) const;
|
||||
[[nodiscard]] float GetKerning(char32_t left, char32_t right) const;
|
||||
|
||||
// Calls onGlyph(const PlacedGlyph&) for every visible glyph, in screen pixels
|
||||
template <typename F>
|
||||
void Layout(std::string_view text, const glm::vec2& topLeft, float size, F&& onGlyph) const;
|
||||
[[nodiscard]] glm::vec2 MeasureText(std::string_view text, float size) const;
|
||||
|
||||
[[nodiscard]] float GetPixelSize() const { return pixelSize; }
|
||||
[[nodiscard]] float GetAscender() const { return ascender; }
|
||||
[[nodiscard]] float GetLineHeight() const { return lineHeight; }
|
||||
[[nodiscard]] ImageID GetAtlas() const { return atlas; }
|
||||
[[nodiscard]] glm::uvec2 GetAtlasSize() const { return atlasSize; }
|
||||
[[nodiscard]] const std::vector<std::uint8_t>& GetAtlasPixels() const { return atlasPixels; }
|
||||
|
||||
static char32_t DecodeUtf8(std::string_view text, std::size_t& index);
|
||||
|
||||
private:
|
||||
float pixelSize{0.0f};
|
||||
float ascender{0.0f};
|
||||
float lineHeight{0.0f};
|
||||
|
||||
std::unordered_map<char32_t, FontGlyph> glyphs;
|
||||
std::unordered_map<std::uint64_t, float> kerning;
|
||||
|
||||
glm::uvec2 atlasSize{0};
|
||||
std::vector<std::uint8_t> atlasPixels;
|
||||
ImageID atlas{NULL_IMAGE_ID};
|
||||
};
|
||||
|
||||
template <typename F>
|
||||
void Font::Layout(std::string_view text, const glm::vec2& topLeft, float size, F&& onGlyph) const
|
||||
{
|
||||
const float scale = size / pixelSize;
|
||||
glm::vec2 pen{topLeft.x, topLeft.y + ascender * scale};
|
||||
char32_t previous = 0;
|
||||
|
||||
for (std::size_t i = 0; i < text.size();) {
|
||||
const char32_t codepoint = DecodeUtf8(text, i);
|
||||
|
||||
if (codepoint == U'\n') {
|
||||
pen = {topLeft.x, pen.y + lineHeight * scale};
|
||||
previous = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
const FontGlyph* glyph = GetGlyph(codepoint);
|
||||
if (!glyph) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (previous != 0) {
|
||||
pen.x += GetKerning(previous, codepoint) * scale;
|
||||
}
|
||||
previous = codepoint;
|
||||
|
||||
if (glyph->size.x > 0.0f && glyph->size.y > 0.0f) {
|
||||
onGlyph(PlacedGlyph{
|
||||
.glyph = glyph,
|
||||
.topLeft = pen + glyph->offset * scale,
|
||||
.size = glyph->size * scale,
|
||||
});
|
||||
}
|
||||
|
||||
pen.x += glyph->advance * scale;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // DESTRUM_FONT_H
|
||||
Reference in New Issue
Block a user