webgpu support

This commit is contained in:
Jorijn van der Graaf 2026-05-18 04:58:52 +02:00
commit dedf6b0467
22 changed files with 1656 additions and 324 deletions

View file

@ -19,70 +19,61 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
module;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
#include "vulkan/vulkan.h"
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
#endif
export module Crafter.Graphics:FontAtlas;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
import std;
import :Font;
import :GraphicsTypes;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
import :ImageVulkan;
import :Device;
#else
import :WebGPU;
#endif
export namespace Crafter {
// Per-glyph metrics. UVs are 0..1 in atlas space; on-screen sizes /
// offsets / advance are in *atlas pixels at the base size* and scale
// linearly with the requested font size at draw time.
struct Glyph {
float u0 = 0, v0 = 0; // top-left UV in the atlas
float u1 = 0, v1 = 0; // bottom-right UV in the atlas
float w = 0, h = 0; // glyph quad size in atlas px (= the bitmap size)
float xoff = 0, yoff = 0; // glyph bearing relative to baseline cursor
float advance = 0; // horizontal advance at base size, in atlas px
float u0 = 0, v0 = 0;
float u1 = 0, v1 = 0;
float w = 0, h = 0;
float xoff = 0, yoff = 0;
float advance = 0;
};
// Single-channel SDF atlas. Glyphs are rasterised with stb_truetype's
// GetGlyphSDF at a fixed `kBaseSize` resolution and packed via a simple
// shelf allocator. Drawing scales the glyph quad linearly; the shader
// resolves edge AA via screen-space derivatives, so a single atlas
// serves all sizes and DPI scales without re-bake.
class FontAtlas {
public:
// Build-time constants. Tweak in one place if needed; values picked
// to give crisp text from ~10pt to ~96pt and leave headroom in the
// SDF distance band so smoothstep is in the linear regime.
static constexpr int kAtlasSize = 1024;
static constexpr float kBaseSize = 32.0f; // pixel-height at which we rasterise
static constexpr int kPadding = 4; // distance-field padding around each glyph
static constexpr int kOnEdgeValue = 128; // 8-bit value mapped to "0 distance"
static constexpr float kPixelDistScale = 32.0f; // how many distance units per pixel — wider = softer AA range
static constexpr float kBaseSize = 32.0f;
static constexpr int kPadding = 4;
static constexpr int kOnEdgeValue = 128;
static constexpr float kPixelDistScale = 32.0f;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
ImageVulkan<std::uint8_t> image;
bool dirty = false; // staging has unflushed writes
#else
WebGPUTextureRef textureHandle = 0;
std::vector<std::uint8_t> staging;
#endif
bool dirty = false;
// Allocate the GPU image and zero-clear it. Must be called once
// with a one-shot init command buffer.
void Initialize(VkCommandBuffer cmd);
void Initialize(GraphicsCommandBuffer cmd);
// Returns the row-major byte pointer the CPU writes pixels into.
// Same shape on both backends.
std::uint8_t* PixelPtr() noexcept;
// Rasterise + pack the glyph if it isn't cached yet. Returns
// false only if the atlas is out of space (V2: grow). After a
// successful Ensure the bitmap lives in `image.buffer.value` and
// `dirty` is true; call Update(cmd) before reading on the GPU.
bool Ensure(Font& font, std::uint32_t codepoint);
// Lookup is cheap (hash-table). Returns nullptr if the glyph
// hasn't been Ensured.
const Glyph* Lookup(Font& font, std::uint32_t codepoint) const;
// If `dirty`, flushes staging into the GPU image and transitions
// it back to SHADER_READ_ONLY_OPTIMAL. No-op if not dirty.
void Update(VkCommandBuffer cmd);
void Update(GraphicsCommandBuffer cmd);
private:
// Shelf packer state.
struct Shelf { int y = 0; int height = 0; int cursorX = 0; };
std::vector<Shelf> shelves_;
int nextShelfY_ = 0;
// (font*, codepoint) → Glyph cache.
struct Key {
const Font* font;
std::uint32_t cp;
@ -97,8 +88,6 @@ export namespace Crafter {
};
std::unordered_map<Key, Glyph, KeyHash> cache_;
// Place a wxh glyph; returns true + writes top-left into outX/outY.
bool ShelfPlace(int w, int h, int& outX, int& outY);
};
}
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM