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

@ -2,47 +2,53 @@
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
#include "vulkan/vulkan.h"
#endif
#include "../lib/stb_truetype.h"
module Crafter.Graphics:FontAtlas_impl;
import :FontAtlas;
import :Font;
import :GraphicsTypes;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
import :ImageVulkan;
import :Device;
#else
import :WebGPU;
#endif
import std;
using namespace Crafter;
void FontAtlas::Initialize(VkCommandBuffer cmd) {
std::uint8_t* FontAtlas::PixelPtr() noexcept {
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
return image.buffer.value;
#else
return staging.data();
#endif
}
void FontAtlas::Initialize(GraphicsCommandBuffer cmd) {
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
image.Create(
kAtlasSize, kAtlasSize, /*mipLevels*/ 1, cmd,
VK_FORMAT_R8_UNORM,
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
);
// Staging buffer is mapped; clear it so empty atlas regions sample as
// distance < onedge (i.e. fully outside any glyph).
std::memset(image.buffer.value, 0, kAtlasSize * kAtlasSize);
dirty = true;
#else
(void)cmd;
staging.assign(kAtlasSize * kAtlasSize, 0);
textureHandle = WebGPU::wgpuCreateAtlasTexture(kAtlasSize, kAtlasSize);
dirty = true;
#endif
}
bool FontAtlas::ShelfPlace(int w, int h, int& outX, int& outY) {
// Try existing shelves first — same height heuristic keeps fragmentation low.
for (Shelf& s : shelves_) {
if (h <= s.height && s.cursorX + w <= kAtlasSize) {
outX = s.cursorX;
@ -51,7 +57,6 @@ bool FontAtlas::ShelfPlace(int w, int h, int& outX, int& outY) {
return true;
}
}
// New shelf below current ones.
if (nextShelfY_ + h > kAtlasSize) return false;
Shelf s{};
s.y = nextShelfY_;
@ -70,7 +75,6 @@ bool FontAtlas::Ensure(Font& font, std::uint32_t codepoint) {
float fontScale = stbtt_ScaleForPixelHeight(&font.font, kBaseSize);
// Advance is always present, even for empty glyphs (e.g. space).
int advanceUnits = 0, lsb = 0;
stbtt_GetCodepointHMetrics(&font.font, static_cast<int>(codepoint), &advanceUnits, &lsb);
@ -90,12 +94,12 @@ bool FontAtlas::Ensure(Font& font, std::uint32_t codepoint) {
int px = 0, py = 0;
if (!ShelfPlace(sw, sh, px, py)) {
stbtt_FreeSDF(sdf, nullptr);
return false; // V1: silently drop overflow; V2: grow atlas
return false;
}
// Blit row-by-row into the mapped staging buffer.
std::uint8_t* dst = PixelPtr();
for (int row = 0; row < sh; ++row) {
std::memcpy(
image.buffer.value + (py + row) * kAtlasSize + px,
dst + (py + row) * kAtlasSize + px,
sdf + row * sw,
static_cast<std::size_t>(sw)
);
@ -110,8 +114,6 @@ bool FontAtlas::Ensure(Font& font, std::uint32_t codepoint) {
g.v1 = static_cast<float>(py + sh) / kAtlasSize;
dirty = true;
}
// For empty glyphs (whitespace) we still cache the entry — the size-0
// fields tell the emitter to skip the quad but advance the cursor.
cache_.emplace(key, g);
return true;
@ -122,8 +124,18 @@ const Glyph* FontAtlas::Lookup(Font& font, std::uint32_t codepoint) const {
return it == cache_.end() ? nullptr : &it->second;
}
void FontAtlas::Update(VkCommandBuffer cmd) {
void FontAtlas::Update(GraphicsCommandBuffer cmd) {
if (!dirty) return;
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
image.Update(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
#else
(void)cmd;
// Full-atlas upload. Future: track dirty region.
WebGPU::wgpuWriteAtlasRegion(
textureHandle, staging.data(),
kAtlasSize, kAtlasSize, kAtlasSize,
0, 0, kAtlasSize, kAtlasSize
);
#endif
dirty = false;
}