webgpu support
This commit is contained in:
parent
5352ef69a2
commit
dedf6b0467
22 changed files with 1656 additions and 324 deletions
185
interfaces/Crafter.Graphics-DescriptorHeapWebGPU.cppm
Normal file
185
interfaces/Crafter.Graphics-DescriptorHeapWebGPU.cppm
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
*/
|
||||
|
||||
// DOM-mode parallel to DescriptorHeapVulkan. WebGPU has no real bindless,
|
||||
// so the "heap" is purely a CPU-side slot allocator with a side-table
|
||||
// mapping slot → JS-side WebGPU handle. UIRenderer looks up the handle by
|
||||
// slot at dispatch time to build (or fetch from cache) the bind group.
|
||||
|
||||
export module Crafter.Graphics:DescriptorHeapWebGPU;
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import std;
|
||||
import :WebGPU;
|
||||
|
||||
export namespace Crafter {
|
||||
struct DescriptorHeapWebGPU;
|
||||
|
||||
struct DescriptorRange {
|
||||
std::uint16_t firstElement;
|
||||
std::uint16_t count;
|
||||
};
|
||||
|
||||
class BufferSlot {
|
||||
public:
|
||||
DescriptorHeapWebGPU* heap = nullptr;
|
||||
std::uint16_t firstElement = 0xFFFF;
|
||||
BufferSlot() = default;
|
||||
BufferSlot(DescriptorHeapWebGPU* h, std::uint16_t f) : heap(h), firstElement(f) {}
|
||||
BufferSlot(const BufferSlot&) = delete;
|
||||
BufferSlot& operator=(const BufferSlot&) = delete;
|
||||
BufferSlot(BufferSlot&& o) noexcept : heap(o.heap), firstElement(o.firstElement) { o.firstElement = 0xFFFF; }
|
||||
BufferSlot& operator=(BufferSlot&& o) noexcept;
|
||||
~BufferSlot();
|
||||
explicit operator bool() const noexcept { return firstElement != 0xFFFF; }
|
||||
operator std::uint16_t() const noexcept { return firstElement; }
|
||||
};
|
||||
|
||||
class ImageSlot {
|
||||
public:
|
||||
DescriptorHeapWebGPU* heap = nullptr;
|
||||
std::uint16_t firstElement = 0xFFFF;
|
||||
ImageSlot() = default;
|
||||
ImageSlot(DescriptorHeapWebGPU* h, std::uint16_t f) : heap(h), firstElement(f) {}
|
||||
ImageSlot(const ImageSlot&) = delete;
|
||||
ImageSlot& operator=(const ImageSlot&) = delete;
|
||||
ImageSlot(ImageSlot&& o) noexcept : heap(o.heap), firstElement(o.firstElement) { o.firstElement = 0xFFFF; }
|
||||
ImageSlot& operator=(ImageSlot&& o) noexcept;
|
||||
~ImageSlot();
|
||||
explicit operator bool() const noexcept { return firstElement != 0xFFFF; }
|
||||
operator std::uint16_t() const noexcept { return firstElement; }
|
||||
};
|
||||
|
||||
class SamplerSlot {
|
||||
public:
|
||||
DescriptorHeapWebGPU* heap = nullptr;
|
||||
std::uint16_t firstElement = 0xFFFF;
|
||||
SamplerSlot() = default;
|
||||
SamplerSlot(DescriptorHeapWebGPU* h, std::uint16_t f) : heap(h), firstElement(f) {}
|
||||
SamplerSlot(const SamplerSlot&) = delete;
|
||||
SamplerSlot& operator=(const SamplerSlot&) = delete;
|
||||
SamplerSlot(SamplerSlot&& o) noexcept : heap(o.heap), firstElement(o.firstElement) { o.firstElement = 0xFFFF; }
|
||||
SamplerSlot& operator=(SamplerSlot&& o) noexcept;
|
||||
~SamplerSlot();
|
||||
explicit operator bool() const noexcept { return firstElement != 0xFFFF; }
|
||||
operator std::uint16_t() const noexcept { return firstElement; }
|
||||
};
|
||||
|
||||
struct DescriptorHeapWebGPU {
|
||||
std::vector<WebGPUBufferRef> bufferTable;
|
||||
std::vector<WebGPUTextureRef> imageTable;
|
||||
std::vector<WebGPUSamplerRef> samplerTable;
|
||||
|
||||
std::vector<std::uint16_t> bufferFreelist;
|
||||
std::vector<std::uint16_t> imageFreelist;
|
||||
std::vector<std::uint16_t> samplerFreelist;
|
||||
|
||||
std::uint16_t nextBuffer = 0;
|
||||
std::uint16_t nextImage = 0;
|
||||
std::uint16_t nextSampler = 0;
|
||||
|
||||
void Initialize(std::uint16_t images, std::uint16_t buffers, std::uint16_t samplers) {
|
||||
imageTable.assign(images, 0);
|
||||
bufferTable.assign(buffers, 0);
|
||||
samplerTable.assign(samplers, 0);
|
||||
imageFreelist.reserve(images);
|
||||
bufferFreelist.reserve(buffers);
|
||||
samplerFreelist.reserve(samplers);
|
||||
}
|
||||
|
||||
DescriptorRange AllocateBufferSlots(std::uint16_t count) {
|
||||
if (count == 1 && !bufferFreelist.empty()) {
|
||||
auto f = bufferFreelist.back(); bufferFreelist.pop_back();
|
||||
return { f, 1 };
|
||||
}
|
||||
if (nextBuffer + count > bufferTable.size()) {
|
||||
std::println("DescriptorHeapWebGPU: buffer slots exhausted");
|
||||
std::abort();
|
||||
}
|
||||
DescriptorRange r{ nextBuffer, count };
|
||||
nextBuffer = static_cast<std::uint16_t>(nextBuffer + count);
|
||||
return r;
|
||||
}
|
||||
DescriptorRange AllocateImageSlots(std::uint16_t count) {
|
||||
if (count == 1 && !imageFreelist.empty()) {
|
||||
auto f = imageFreelist.back(); imageFreelist.pop_back();
|
||||
return { f, 1 };
|
||||
}
|
||||
if (nextImage + count > imageTable.size()) {
|
||||
std::println("DescriptorHeapWebGPU: image slots exhausted");
|
||||
std::abort();
|
||||
}
|
||||
DescriptorRange r{ nextImage, count };
|
||||
nextImage = static_cast<std::uint16_t>(nextImage + count);
|
||||
return r;
|
||||
}
|
||||
DescriptorRange AllocateSamplerSlots(std::uint16_t count) {
|
||||
if (count == 1 && !samplerFreelist.empty()) {
|
||||
auto f = samplerFreelist.back(); samplerFreelist.pop_back();
|
||||
return { f, 1 };
|
||||
}
|
||||
if (nextSampler + count > samplerTable.size()) {
|
||||
std::println("DescriptorHeapWebGPU: sampler slots exhausted");
|
||||
std::abort();
|
||||
}
|
||||
DescriptorRange r{ nextSampler, count };
|
||||
nextSampler = static_cast<std::uint16_t>(nextSampler + count);
|
||||
return r;
|
||||
}
|
||||
|
||||
void FreeBufferSlots(std::uint16_t first, std::uint16_t count) noexcept {
|
||||
for (std::uint16_t i = 0; i < count; ++i) {
|
||||
bufferTable[first + i] = 0;
|
||||
bufferFreelist.push_back(first + i);
|
||||
}
|
||||
}
|
||||
void FreeImageSlots(std::uint16_t first, std::uint16_t count) noexcept {
|
||||
for (std::uint16_t i = 0; i < count; ++i) {
|
||||
imageTable[first + i] = 0;
|
||||
imageFreelist.push_back(first + i);
|
||||
}
|
||||
}
|
||||
void FreeSamplerSlots(std::uint16_t first, std::uint16_t count) noexcept {
|
||||
for (std::uint16_t i = 0; i < count; ++i) {
|
||||
samplerTable[first + i] = 0;
|
||||
samplerFreelist.push_back(first + i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ─── slot dtors (defined here since they reference DescriptorHeapWebGPU) ─
|
||||
|
||||
inline BufferSlot::~BufferSlot() {
|
||||
if (firstElement != 0xFFFF && heap) heap->FreeBufferSlots(firstElement, 1);
|
||||
}
|
||||
inline BufferSlot& BufferSlot::operator=(BufferSlot&& o) noexcept {
|
||||
if (this != &o) {
|
||||
if (firstElement != 0xFFFF && heap) heap->FreeBufferSlots(firstElement, 1);
|
||||
heap = o.heap; firstElement = o.firstElement; o.firstElement = 0xFFFF;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
inline ImageSlot::~ImageSlot() {
|
||||
if (firstElement != 0xFFFF && heap) heap->FreeImageSlots(firstElement, 1);
|
||||
}
|
||||
inline ImageSlot& ImageSlot::operator=(ImageSlot&& o) noexcept {
|
||||
if (this != &o) {
|
||||
if (firstElement != 0xFFFF && heap) heap->FreeImageSlots(firstElement, 1);
|
||||
heap = o.heap; firstElement = o.firstElement; o.firstElement = 0xFFFF;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
inline SamplerSlot::~SamplerSlot() {
|
||||
if (firstElement != 0xFFFF && heap) heap->FreeSamplerSlots(firstElement, 1);
|
||||
}
|
||||
inline SamplerSlot& SamplerSlot::operator=(SamplerSlot&& o) noexcept {
|
||||
if (this != &o) {
|
||||
if (firstElement != 0xFFFF && heap) heap->FreeSamplerSlots(firstElement, 1);
|
||||
heap = o.heap; firstElement = o.firstElement; o.firstElement = 0xFFFF;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
|
|
@ -19,13 +19,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|||
*/
|
||||
|
||||
module;
|
||||
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
#include "../lib/stb_truetype.h"
|
||||
|
||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
export module Crafter.Graphics:Font;
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import std;
|
||||
|
||||
namespace Crafter {
|
||||
|
|
@ -36,7 +31,6 @@ namespace Crafter {
|
|||
if (i >= text.size()) return 0;
|
||||
std::uint8_t b0 = static_cast<std::uint8_t>(text[i]);
|
||||
|
||||
// Single-byte ASCII is the common path.
|
||||
if (b0 < 0x80) { ++i; return b0; }
|
||||
|
||||
int extra;
|
||||
|
|
@ -44,13 +38,13 @@ namespace Crafter {
|
|||
if ((b0 & 0xE0) == 0xC0) { extra = 1; cp = b0 & 0x1F; }
|
||||
else if ((b0 & 0xF0) == 0xE0) { extra = 2; cp = b0 & 0x0F; }
|
||||
else if ((b0 & 0xF8) == 0xF0) { extra = 3; cp = b0 & 0x07; }
|
||||
else { ++i; return 0xFFFD; } // continuation byte at start, or 5+-byte leader
|
||||
else { ++i; return 0xFFFD; }
|
||||
|
||||
++i;
|
||||
for (int k = 0; k < extra; ++k) {
|
||||
if (i >= text.size()) return 0xFFFD;
|
||||
std::uint8_t b = static_cast<std::uint8_t>(text[i]);
|
||||
if ((b & 0xC0) != 0x80) return 0xFFFD; // missing continuation
|
||||
if ((b & 0xC0) != 0x80) return 0xFFFD;
|
||||
cp = (cp << 6) | (b & 0x3Fu);
|
||||
++i;
|
||||
}
|
||||
|
|
@ -67,8 +61,7 @@ namespace Crafter {
|
|||
Font(const std::filesystem::path& font);
|
||||
std::uint32_t GetLineWidth(const std::string_view text, float size);
|
||||
float LineHeight(float size);
|
||||
float AscentPx(float size); // baseline offset from line-top
|
||||
float ScaleForSize(float size); // stb's pixel-units-per-em factor
|
||||
float AscentPx(float size);
|
||||
float ScaleForSize(float size);
|
||||
};
|
||||
}
|
||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
42
interfaces/Crafter.Graphics-GraphicsTypes.cppm
Normal file
42
interfaces/Crafter.Graphics-GraphicsTypes.cppm
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
*/
|
||||
|
||||
// Backend-portable type aliases. NOT an abstraction layer — these are pure
|
||||
// `using` declarations that resolve to the backend's native types per the
|
||||
// active CRAFTER_GRAPHICS_WINDOW_* define.
|
||||
|
||||
module;
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
#include "vulkan/vulkan.h"
|
||||
#endif
|
||||
export module Crafter.Graphics:GraphicsTypes;
|
||||
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import :VulkanBuffer;
|
||||
import :DescriptorHeapVulkan;
|
||||
import :ComputeShader;
|
||||
|
||||
export namespace Crafter {
|
||||
using GraphicsCommandBuffer = VkCommandBuffer;
|
||||
using GraphicsDescriptorHeap = DescriptorHeapVulkan;
|
||||
using GraphicsComputeShader = ComputeShader;
|
||||
template<class T, bool Mapped>
|
||||
using GraphicsBuffer = VulkanBuffer<T, Mapped>;
|
||||
}
|
||||
#else
|
||||
import :WebGPU;
|
||||
import :WebGPUBuffer;
|
||||
import :DescriptorHeapWebGPU;
|
||||
import :WebGPUComputeShader;
|
||||
|
||||
export namespace Crafter {
|
||||
using GraphicsCommandBuffer = WebGPUCommandEncoderRef;
|
||||
using GraphicsDescriptorHeap = DescriptorHeapWebGPU;
|
||||
using GraphicsComputeShader = WebGPUComputeShader;
|
||||
template<class T, bool Mapped>
|
||||
using GraphicsBuffer = WebGPUBuffer<T, Mapped>;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -16,20 +16,15 @@ 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 // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
export module Crafter.Graphics:RenderPass;
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import std;
|
||||
import :GraphicsTypes;
|
||||
|
||||
export namespace Crafter {
|
||||
struct Window;
|
||||
|
||||
struct RenderPass {
|
||||
virtual void Record(VkCommandBuffer cmd, std::uint32_t frameIdx, Window& window) = 0;
|
||||
virtual void Record(GraphicsCommandBuffer cmd, std::uint32_t frameIdx, Window& window) = 0;
|
||||
virtual ~RenderPass() = default;
|
||||
};
|
||||
}
|
||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
|
|
|
|||
|
|
@ -19,20 +19,27 @@ 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:UI;
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import std;
|
||||
import Crafter.Event;
|
||||
import :Device;
|
||||
import :Window;
|
||||
import :RenderPass;
|
||||
import :GraphicsTypes;
|
||||
import :FontAtlas;
|
||||
import :Font;
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import :Device;
|
||||
import :DescriptorHeapVulkan;
|
||||
import :ImageVulkan;
|
||||
import :VulkanBuffer;
|
||||
import :ComputeShader;
|
||||
import :FontAtlas;
|
||||
import :Font;
|
||||
#else
|
||||
import :DescriptorHeapWebGPU;
|
||||
import :WebGPU;
|
||||
import :WebGPUBuffer;
|
||||
import :WebGPUComputeShader;
|
||||
#endif
|
||||
|
||||
export namespace Crafter {
|
||||
// ─── push-constant header ───────────────────────────────────────────
|
||||
|
|
@ -56,8 +63,8 @@ export namespace Crafter {
|
|||
struct QuadItem {
|
||||
float x, y, w, h;
|
||||
float r, g, b, a;
|
||||
float cTL, cTR, cBR, cBL; // per-corner radius in px
|
||||
float outline, oR, oG, oB; // outline thickness + RGB
|
||||
float cTL, cTR, cBR, cBL;
|
||||
float outline, oR, oG, oB;
|
||||
};
|
||||
static_assert(sizeof(QuadItem) == 64);
|
||||
|
||||
|
|
@ -84,15 +91,11 @@ export namespace Crafter {
|
|||
static_assert(sizeof(GlyphItem) == 48);
|
||||
|
||||
// ─── tiny rect-carving helper ───────────────────────────────────────
|
||||
// Pure value semantics. No engine, just convenience. Skip if you'd rather
|
||||
// compute pixels yourself.
|
||||
struct Rect {
|
||||
float x = 0, y = 0, w = 0, h = 0;
|
||||
|
||||
enum class Anchor { Top, Bottom, Left, Right };
|
||||
|
||||
// Returns a sub-rect of `size` along the given anchor edge of self.
|
||||
// Does not modify `*this`. (Use `.Inset(...)` to drop a margin first.)
|
||||
Rect SubRect(float size, Anchor a) const noexcept {
|
||||
switch (a) {
|
||||
case Anchor::Top: return { x, y, w, std::min(size, h) };
|
||||
|
|
@ -123,121 +126,71 @@ export namespace Crafter {
|
|||
|
||||
// ─── per-frame callback args ────────────────────────────────────────
|
||||
struct UIBuildArgs {
|
||||
VkCommandBuffer cmd;
|
||||
std::uint32_t frameIdx;
|
||||
GraphicsCommandBuffer cmd;
|
||||
std::uint32_t frameIdx;
|
||||
};
|
||||
|
||||
// ─── UIRenderer ─────────────────────────────────────────────────────
|
||||
// One per Window (typically). Owns the four standard compute shaders,
|
||||
// pre-allocates heap slots for the swapchain images, and exposes a thin
|
||||
// dispatch helper for both the standard shaders and user-supplied ones.
|
||||
//
|
||||
// Workflow:
|
||||
// 1. Construct, configure (set fontAtlas if drawing text).
|
||||
// 2. Initialize(window, heap, initCmd) — once, after window.descriptorHeap
|
||||
// is set and before window.FinishInit().
|
||||
// 3. window.passes.push_back(&ui).
|
||||
// 4. Listen on `onBuild`. Inside the callback, fill your item buffers,
|
||||
// flush them, and call DispatchQuads / DispatchCircles / DispatchImages
|
||||
// / DispatchText / Dispatch as needed. Library inserts a SHADER_WRITE
|
||||
// → SHADER_READ|WRITE memory barrier between consecutive dispatches.
|
||||
class UIRenderer : public RenderPass {
|
||||
public:
|
||||
// Pre-loaded standard shaders (public so users can call Dispatch
|
||||
// directly with them if they want to embed extra push-constant fields
|
||||
// beyond the standard header).
|
||||
ComputeShader drawQuads;
|
||||
ComputeShader drawCircles;
|
||||
ComputeShader drawImages;
|
||||
ComputeShader drawText;
|
||||
GraphicsComputeShader drawQuads;
|
||||
GraphicsComputeShader drawCircles;
|
||||
GraphicsComputeShader drawImages;
|
||||
GraphicsComputeShader drawText;
|
||||
|
||||
// Optional. If set before Initialize, the atlas is registered into a
|
||||
// sampled-image slot + linear sampler slot, and Update(cmd) is called
|
||||
// at the top of every Record() so any glyphs ensured during onBuild
|
||||
// make it to the GPU before the text dispatch reads them.
|
||||
FontAtlas* fontAtlas = nullptr;
|
||||
|
||||
// User callback. Subscribe by holding a Crafter::EventListener<UIBuildArgs>:
|
||||
// EventListener<UIBuildArgs> sub(&ui.onBuild, [&](UIBuildArgs a) { ... });
|
||||
// Listener lifetime governs the subscription.
|
||||
Crafter::Event<UIBuildArgs> onBuild;
|
||||
|
||||
UIRenderer() = default;
|
||||
UIRenderer(const UIRenderer&) = delete;
|
||||
UIRenderer& operator=(const UIRenderer&) = delete;
|
||||
|
||||
// Default shader paths assume Crafter.Build placed the .spv files
|
||||
// alongside the consumer binary (this is what cfg.shaders does).
|
||||
void Initialize(Window& window, DescriptorHeapVulkan& heap, VkCommandBuffer initCmd,
|
||||
void Initialize(Window& window, GraphicsDescriptorHeap& heap, GraphicsCommandBuffer initCmd,
|
||||
std::filesystem::path quadsSpv = "ui-quads.comp.spv",
|
||||
std::filesystem::path circlesSpv = "ui-circles.comp.spv",
|
||||
std::filesystem::path imagesSpv = "ui-images.comp.spv",
|
||||
std::filesystem::path textSpv = "ui-text.comp.spv");
|
||||
|
||||
// RenderPass interface — invoked from Window::Render.
|
||||
void Record(VkCommandBuffer cmd, std::uint32_t frameIdx, Window& window) override;
|
||||
void Record(GraphicsCommandBuffer cmd, std::uint32_t frameIdx, Window& window) override;
|
||||
|
||||
// ─── helpers used inside `onBuild` ─────────────────────────────
|
||||
|
||||
// Builds a populated header. `clipRectPx` defaults to "no clip".
|
||||
UIDispatchHeader FillHeader(std::uint32_t itemBufferSlot,
|
||||
std::uint32_t itemCount,
|
||||
std::array<float,4> clipRectPx = {0.0f, 0.0f, 1e9f, 1e9f},
|
||||
std::uint32_t flags = 0) const noexcept;
|
||||
|
||||
// Convenience: dispatches the named standard shader. Group count is
|
||||
// computed from the window's surface size — the standard shaders
|
||||
// dispatch one workgroup per 8×8 screen tile and iterate every item
|
||||
// in the buffer in order, so item ORDER in the buffer == draw order
|
||||
// on screen (later items overdraw earlier ones, race-free).
|
||||
void DispatchQuads(VkCommandBuffer cmd, std::uint32_t bufferSlot, std::uint32_t itemCount,
|
||||
void DispatchQuads(GraphicsCommandBuffer cmd, std::uint32_t bufferSlot, std::uint32_t itemCount,
|
||||
std::array<float,4> clipRectPx = {0.0f, 0.0f, 1e9f, 1e9f});
|
||||
void DispatchCircles(VkCommandBuffer cmd, std::uint32_t bufferSlot, std::uint32_t itemCount,
|
||||
void DispatchCircles(GraphicsCommandBuffer cmd, std::uint32_t bufferSlot, std::uint32_t itemCount,
|
||||
std::array<float,4> clipRectPx = {0.0f, 0.0f, 1e9f, 1e9f});
|
||||
void DispatchImages(VkCommandBuffer cmd, std::uint32_t bufferSlot, std::uint32_t itemCount,
|
||||
void DispatchImages(GraphicsCommandBuffer cmd, std::uint32_t bufferSlot, std::uint32_t itemCount,
|
||||
std::array<float,4> clipRectPx = {0.0f, 0.0f, 1e9f, 1e9f});
|
||||
// For DispatchText, the font atlas image+sampler slots are taken from
|
||||
// UIRenderer's Initialize-time registration (see fontAtlasImageSlot()
|
||||
// / fontAtlasSamplerSlot()). Set `fontAtlas` before Initialize.
|
||||
void DispatchText(VkCommandBuffer cmd, std::uint32_t bufferSlot, std::uint32_t itemCount,
|
||||
void DispatchText(GraphicsCommandBuffer cmd, std::uint32_t bufferSlot, std::uint32_t itemCount,
|
||||
std::array<float,4> clipRectPx = {0.0f, 0.0f, 1e9f, 1e9f});
|
||||
|
||||
// Generic dispatch — for user-authored shaders. Inserts the standard
|
||||
// pre-dispatch barrier (skipped on the first call per frame).
|
||||
void Dispatch(VkCommandBuffer cmd, const ComputeShader& shader,
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
// Generic dispatch — user-authored shaders. Vulkan-only in v1; on DOM
|
||||
// the WebGPU side has no bindless and would need per-shader bind-group
|
||||
// declaration. See plan section 3b for the design path.
|
||||
void Dispatch(GraphicsCommandBuffer cmd, const ComputeShader& shader,
|
||||
const void* push, std::uint32_t pushBytes,
|
||||
std::uint32_t gx, std::uint32_t gy = 1, std::uint32_t gz = 1);
|
||||
#endif
|
||||
|
||||
// Allocates a heap slot for the buffer and writes its descriptor into
|
||||
// every per-frame heap. The user's mapped buffer is shared across
|
||||
// frames — fine because Window::Render currently waits idle before
|
||||
// submitting the next frame. Returns a move-only BufferSlot handle
|
||||
// whose destructor returns the slot to the heap. Implicitly converts
|
||||
// to the absolute heap index when passed to FillHeader / Dispatch*.
|
||||
// Allocates a heap slot for the buffer and registers the GPU handle.
|
||||
// Returns a move-only BufferSlot RAII handle.
|
||||
template<typename T, bool Mapped>
|
||||
BufferSlot RegisterBuffer(VulkanBuffer<T, Mapped>& buffer);
|
||||
BufferSlot RegisterBuffer(GraphicsBuffer<T, Mapped>& buffer);
|
||||
|
||||
// Same for an ImageVulkan-managed sampled image (e.g. a user texture).
|
||||
// Caller specifies the layout the image will be sampled in (typically
|
||||
// VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL).
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
template<typename Pixel>
|
||||
ImageSlot RegisterImage(ImageVulkan<Pixel>& image, VkFormat format,
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
|
||||
// Allocates a sampler slot and writes a VkSamplerCreateInfo into
|
||||
// every per-frame sampler heap. v1 takes the create-info inline.
|
||||
SamplerSlot RegisterSampler(const VkSamplerCreateInfo& info);
|
||||
|
||||
// Convenience: a linear-filter, clamp-to-edge sampler. Returns a
|
||||
// SamplerSlot handle. Useful for the FontAtlas and most plain image
|
||||
// sampling.
|
||||
#endif
|
||||
SamplerSlot RegisterLinearClampSampler();
|
||||
|
||||
// Shapes a UTF-8 string into glyph quads at (x, y) baseline. Calls
|
||||
// FontAtlas::Ensure for each codepoint (rasterising on first use),
|
||||
// emits one GlyphItem per visible glyph, returns the count written.
|
||||
// Use this to fill a GlyphItem buffer that you then dispatch.
|
||||
// Cursor advances along +X. No line-wrap, no kerning — single line.
|
||||
std::uint32_t ShapeText(Font& font, float pxSize,
|
||||
float x, float baselineY,
|
||||
std::string_view utf8,
|
||||
|
|
@ -245,79 +198,53 @@ export namespace Crafter {
|
|||
GlyphItem* out, std::uint32_t outCapacity,
|
||||
float* outAdvance = nullptr);
|
||||
|
||||
// Read after Initialize: the slot the font atlas was registered into.
|
||||
// 0xFFFF means "no atlas" (set fontAtlas before Initialize).
|
||||
std::uint16_t FontAtlasImageSlot() const noexcept { return fontAtlasImageSlot_; }
|
||||
std::uint16_t FontAtlasSamplerSlot() const noexcept { return fontAtlasSamplerSlot_; }
|
||||
|
||||
// Heap slot whose descriptor in each per-frame heap points at that
|
||||
// frame's swapchain image. Other passes (e.g. a ray-tracing pass
|
||||
// that wants to render the world directly into the swapchain) can
|
||||
// write to the same image by referencing this slot. Order in
|
||||
// window.passes controls compositing — push such passes BEFORE
|
||||
// the UI pass so UI overlays render on top.
|
||||
std::uint16_t OutImageSlot() const noexcept { return outImageSlot_; }
|
||||
|
||||
private:
|
||||
Window* window_ = nullptr;
|
||||
DescriptorHeapVulkan* heap_ = nullptr;
|
||||
|
||||
// One image slot used for the swapchain output. In each per-frame
|
||||
// heap, that slot points at THAT frame's swapchain image. So the
|
||||
// shader's `uiImages[hdr.outImage]` is always the current frame's
|
||||
// swapchain image regardless of which heap is bound.
|
||||
ImageSlot outImageSlot_;
|
||||
|
||||
// Stable VkImageViewCreateInfos for the descriptor heap to ingest.
|
||||
// These must outlive the write call.
|
||||
VkImageViewCreateInfo atlasViewCreateInfo_{};
|
||||
GraphicsDescriptorHeap* heap_ = nullptr;
|
||||
|
||||
ImageSlot outImageSlot_;
|
||||
ImageSlot fontAtlasImageSlot_;
|
||||
SamplerSlot fontAtlasSamplerSlot_;
|
||||
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
VkImageViewCreateInfo atlasViewCreateInfo_{};
|
||||
bool firstDispatchThisFrame_ = true;
|
||||
#endif
|
||||
|
||||
// Subscription to window.onResize. Each resize destroys the old
|
||||
// swapchain images, so the per-frame heap entries we wrote at
|
||||
// outImageSlot_ now reference dangling VkImage handles. The
|
||||
// listener re-writes them and flushes the descriptor heaps.
|
||||
Crafter::EventListener<void> resizeSub_;
|
||||
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
void WriteSwapchainDescriptors();
|
||||
void WriteFontAtlasDescriptor();
|
||||
|
||||
// Helper used by RegisterBuffer template (defined in impl). Writes the
|
||||
// address-range descriptor at `slot` into all per-frame heaps.
|
||||
void WriteBufferDescriptor(std::uint16_t slot, VkDeviceAddress address, std::uint32_t size);
|
||||
|
||||
// Helper used by RegisterImage template — writes a sampled-image at
|
||||
// `slot` referring to a stable VkImageViewCreateInfo (caller stores).
|
||||
void WriteSampledImageDescriptor(std::uint16_t slot,
|
||||
const VkImageViewCreateInfo& viewInfo,
|
||||
VkImageLayout layout);
|
||||
#endif
|
||||
};
|
||||
|
||||
// ─── template-method implementations ────────────────────────────────
|
||||
template<typename T, bool Mapped>
|
||||
BufferSlot UIRenderer::RegisterBuffer(VulkanBuffer<T, Mapped>& buffer) {
|
||||
BufferSlot UIRenderer::RegisterBuffer(GraphicsBuffer<T, Mapped>& buffer) {
|
||||
auto range = heap_->AllocateBufferSlots(1);
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
WriteBufferDescriptor(range.firstElement, buffer.address, buffer.size);
|
||||
// BufferSlot's operator uint16_t() folds in heap_->bufferStartElement,
|
||||
// so callers receive the absolute heap index when they convert.
|
||||
#else
|
||||
heap_->bufferTable[range.firstElement] = buffer.handle;
|
||||
#endif
|
||||
return BufferSlot{heap_, range.firstElement};
|
||||
}
|
||||
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
template<typename Pixel>
|
||||
ImageSlot UIRenderer::RegisterImage(ImageVulkan<Pixel>& image, VkFormat format,
|
||||
VkImageLayout layout) {
|
||||
auto range = heap_->AllocateImageSlots(1);
|
||||
|
||||
// Build a stable view-create-info that lives as long as the heap reads
|
||||
// it. We co-locate it on the renderer for the font atlas; for arbitrary
|
||||
// user images we lean on the fact that vkWriteResourceDescriptorsEXT
|
||||
// copies the view descriptor immediately. (Validated by the heap spec:
|
||||
// the descriptor is materialised at write time, the create-info need
|
||||
// not persist past the call.)
|
||||
VkImageViewCreateInfo info {
|
||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||
.image = image.image,
|
||||
|
|
@ -338,5 +265,5 @@ export namespace Crafter {
|
|||
WriteSampledImageDescriptor(range.firstElement, info, layout);
|
||||
return ImageSlot{heap_, range.firstElement};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
|
|
|
|||
|
|
@ -16,12 +16,7 @@ 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 // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
export module Crafter.Graphics:UIComponents;
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import std;
|
||||
import :UI;
|
||||
import :Font;
|
||||
|
|
@ -143,4 +138,3 @@ export namespace Crafter {
|
|||
std::array<float, 4> tint = {1, 1, 1, 1},
|
||||
std::array<float, 4> uv = {0, 0, 1, 1});
|
||||
}
|
||||
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
|
|
|
|||
75
interfaces/Crafter.Graphics-WebGPU.cppm
Normal file
75
interfaces/Crafter.Graphics-WebGPU.cppm
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
*/
|
||||
|
||||
// JS bridge declarations for the DOM-mode WebGPU backend. Each function
|
||||
// corresponds to one entry in `additional/dom-webgpu.js`. Handles are
|
||||
// opaque uint32 cookies into the JS-side handle tables.
|
||||
|
||||
export module Crafter.Graphics:WebGPU;
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import std;
|
||||
|
||||
export namespace Crafter {
|
||||
using WebGPUBufferRef = std::uint32_t;
|
||||
using WebGPUTextureRef = std::uint32_t;
|
||||
using WebGPUSamplerRef = std::uint32_t;
|
||||
using WebGPUCommandEncoderRef = std::uint32_t; // unused as a real handle; just a marker type for portability
|
||||
}
|
||||
|
||||
namespace Crafter::WebGPU {
|
||||
__attribute__((import_module("env"), import_name("wgpuGetCanvasWidth")))
|
||||
extern "C" std::int32_t wgpuGetCanvasWidth();
|
||||
__attribute__((import_module("env"), import_name("wgpuGetCanvasHeight")))
|
||||
extern "C" std::int32_t wgpuGetCanvasHeight();
|
||||
__attribute__((import_module("env"), import_name("wgpuSurfaceWidth")))
|
||||
extern "C" std::int32_t wgpuSurfaceWidth();
|
||||
__attribute__((import_module("env"), import_name("wgpuSurfaceHeight")))
|
||||
extern "C" std::int32_t wgpuSurfaceHeight();
|
||||
__attribute__((import_module("env"), import_name("wgpuInit")))
|
||||
extern "C" void wgpuInit();
|
||||
|
||||
__attribute__((import_module("env"), import_name("wgpuCreateBuffer")))
|
||||
extern "C" std::uint32_t wgpuCreateBuffer(std::int32_t byteSize);
|
||||
__attribute__((import_module("env"), import_name("wgpuWriteBuffer")))
|
||||
extern "C" void wgpuWriteBuffer(std::uint32_t handle, const void* srcPtr, std::int32_t byteSize);
|
||||
__attribute__((import_module("env"), import_name("wgpuDestroyBuffer")))
|
||||
extern "C" void wgpuDestroyBuffer(std::uint32_t handle);
|
||||
|
||||
__attribute__((import_module("env"), import_name("wgpuCreateAtlasTexture")))
|
||||
extern "C" std::uint32_t wgpuCreateAtlasTexture(std::int32_t w, std::int32_t h);
|
||||
__attribute__((import_module("env"), import_name("wgpuWriteAtlasRegion")))
|
||||
extern "C" void wgpuWriteAtlasRegion(std::uint32_t handle, const void* srcPtr,
|
||||
std::int32_t srcW, std::int32_t srcH,
|
||||
std::int32_t srcBytesPerRow,
|
||||
std::int32_t dstX, std::int32_t dstY,
|
||||
std::int32_t copyW, std::int32_t copyH);
|
||||
__attribute__((import_module("env"), import_name("wgpuDestroyTexture")))
|
||||
extern "C" void wgpuDestroyTexture(std::uint32_t handle);
|
||||
|
||||
__attribute__((import_module("env"), import_name("wgpuCreateLinearClampSampler")))
|
||||
extern "C" std::uint32_t wgpuCreateLinearClampSampler();
|
||||
|
||||
__attribute__((import_module("env"), import_name("wgpuFrameBegin")))
|
||||
extern "C" void wgpuFrameBegin();
|
||||
__attribute__((import_module("env"), import_name("wgpuFrameEnd")))
|
||||
extern "C" void wgpuFrameEnd();
|
||||
|
||||
__attribute__((import_module("env"), import_name("wgpuDispatchQuads")))
|
||||
extern "C" void wgpuDispatchQuads(std::uint32_t itemsHandle, const void* headerPtr,
|
||||
std::int32_t gx, std::int32_t gy);
|
||||
__attribute__((import_module("env"), import_name("wgpuDispatchCircles")))
|
||||
extern "C" void wgpuDispatchCircles(std::uint32_t itemsHandle, const void* headerPtr,
|
||||
std::int32_t gx, std::int32_t gy);
|
||||
__attribute__((import_module("env"), import_name("wgpuDispatchImages")))
|
||||
extern "C" void wgpuDispatchImages(std::uint32_t itemsHandle, const void* headerPtr,
|
||||
std::int32_t gx, std::int32_t gy,
|
||||
std::uint32_t texHandle, std::uint32_t sampHandle);
|
||||
__attribute__((import_module("env"), import_name("wgpuDispatchText")))
|
||||
extern "C" void wgpuDispatchText(std::uint32_t itemsHandle, const void* headerPtr,
|
||||
std::int32_t gx, std::int32_t gy,
|
||||
std::uint32_t atlasHandle, std::uint32_t sampHandle);
|
||||
}
|
||||
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
85
interfaces/Crafter.Graphics-WebGPUBuffer.cppm
Normal file
85
interfaces/Crafter.Graphics-WebGPUBuffer.cppm
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
*/
|
||||
|
||||
// WebGPU buffer wrapper — DOM-mode parallel to VulkanBuffer<T, Mapped>.
|
||||
// Holds a JS-side GPUBuffer handle + (when Mapped) a wasm-memory staging
|
||||
// array. `.value` points to the staging memory; the user writes into it
|
||||
// directly, and `.Flush(cmd)` copies to the GPU via queue.writeBuffer.
|
||||
|
||||
export module Crafter.Graphics:WebGPUBuffer;
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import std;
|
||||
import :WebGPU;
|
||||
|
||||
export namespace Crafter {
|
||||
class WebGPUBufferBase {
|
||||
public:
|
||||
WebGPUBufferRef handle = 0;
|
||||
std::uint32_t size = 0; // bytes
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class WebGPUBufferMapped {
|
||||
public:
|
||||
T* value = nullptr;
|
||||
};
|
||||
class WebGPUBufferMappedEmpty {};
|
||||
|
||||
template<typename T, bool Mapped>
|
||||
using WebGPUBufferMappedConditional =
|
||||
std::conditional_t<Mapped, WebGPUBufferMapped<T>, WebGPUBufferMappedEmpty>;
|
||||
|
||||
template<typename T, bool Mapped>
|
||||
class WebGPUBuffer : public WebGPUBufferBase, public WebGPUBufferMappedConditional<T, Mapped> {
|
||||
public:
|
||||
WebGPUBuffer() = default;
|
||||
WebGPUBuffer(const WebGPUBuffer&) = delete;
|
||||
WebGPUBuffer& operator=(const WebGPUBuffer&) = delete;
|
||||
|
||||
WebGPUBuffer(WebGPUBuffer&& other) noexcept {
|
||||
handle = other.handle;
|
||||
size = other.size;
|
||||
other.handle = 0;
|
||||
if constexpr (Mapped) {
|
||||
this->value = other.value;
|
||||
other.value = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Create(std::uint32_t count) {
|
||||
size = static_cast<std::uint32_t>(count * sizeof(T));
|
||||
handle = WebGPU::wgpuCreateBuffer(static_cast<std::int32_t>(size));
|
||||
if constexpr (Mapped) {
|
||||
this->value = new T[count]();
|
||||
}
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
if (handle != 0) {
|
||||
WebGPU::wgpuDestroyBuffer(handle);
|
||||
handle = 0;
|
||||
}
|
||||
if constexpr (Mapped) {
|
||||
if (this->value) { delete[] this->value; this->value = nullptr; }
|
||||
}
|
||||
}
|
||||
|
||||
void Resize(std::uint32_t count) {
|
||||
if (handle != 0) Clear();
|
||||
Create(count);
|
||||
}
|
||||
|
||||
void Flush(WebGPUCommandEncoderRef /*cmd*/) requires(Mapped) {
|
||||
WebGPU::wgpuWriteBuffer(handle, this->value, static_cast<std::int32_t>(size));
|
||||
}
|
||||
void FlushDevice() requires(Mapped) {
|
||||
WebGPU::wgpuWriteBuffer(handle, this->value, static_cast<std::int32_t>(size));
|
||||
}
|
||||
|
||||
~WebGPUBuffer() { Clear(); }
|
||||
};
|
||||
}
|
||||
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
25
interfaces/Crafter.Graphics-WebGPUComputeShader.cppm
Normal file
25
interfaces/Crafter.Graphics-WebGPUComputeShader.cppm
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Crafter®.Graphics
|
||||
Copyright (C) 2026 Catcrafts®
|
||||
catcrafts.net
|
||||
*/
|
||||
|
||||
// Placeholder ComputeShader for DOM mode. The four standard UI pipelines
|
||||
// are compiled JS-side at startup (see additional/dom-webgpu.js); the C++
|
||||
// side never sees a WGSL handle. This type exists so UIRenderer can
|
||||
// declare `WebGPUComputeShader drawQuads;` members for symmetry with the
|
||||
// Vulkan side, but `Load()` and `Dispatch()` are intentionally absent —
|
||||
// the DispatchQuads / DispatchCircles / etc convenience methods on
|
||||
// UIRenderer route directly to the JS bridge.
|
||||
|
||||
export module Crafter.Graphics:WebGPUComputeShader;
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import std;
|
||||
|
||||
export namespace Crafter {
|
||||
struct WebGPUComputeShader {
|
||||
// Marker only; pipelines live JS-side per dispatchStandard in
|
||||
// dom-webgpu.js. No state required.
|
||||
};
|
||||
}
|
||||
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
|
|
@ -50,6 +50,10 @@ import std;
|
|||
import :Types;
|
||||
import :Keys;
|
||||
import Crafter.Event;
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import :WebGPU;
|
||||
import :DescriptorHeapWebGPU;
|
||||
#endif
|
||||
|
||||
export namespace Crafter {
|
||||
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
|
|
@ -61,6 +65,9 @@ export namespace Crafter {
|
|||
};
|
||||
struct RenderPass;
|
||||
struct DescriptorHeapVulkan;
|
||||
#else
|
||||
struct RenderPass;
|
||||
struct DescriptorHeapWebGPU;
|
||||
#endif
|
||||
|
||||
struct Window {
|
||||
|
|
@ -242,12 +249,21 @@ export namespace Crafter {
|
|||
DescriptorHeapVulkan* descriptorHeap = nullptr;
|
||||
std::optional<std::array<float, 4>> clearColor;
|
||||
#else
|
||||
// DOM mode: the page IS the window. `numFrames` stays as a public
|
||||
// constant so cross-platform code can refer to Window::numFrames
|
||||
// without #ifdef'ing the reference; nothing else lives here yet.
|
||||
// V2 (WebGPU compute) will hang its GPUContext / swapchain texture
|
||||
// members off this branch.
|
||||
// DOM mode: the page IS the window. WebGPU device and canvas are
|
||||
// owned JS-side (see additional/dom-webgpu.js); this struct just
|
||||
// holds the per-Window state Crafter::Window users expect:
|
||||
// a list of render passes and a pointer to the descriptor heap.
|
||||
static constexpr std::uint8_t numFrames = 1;
|
||||
std::uint32_t currentBuffer = 0;
|
||||
std::vector<RenderPass*> passes;
|
||||
DescriptorHeapWebGPU* descriptorHeap = nullptr;
|
||||
std::optional<std::array<float, 4>> clearColor;
|
||||
|
||||
// DOM-mode StartInit/FinishInit are no-ops returning an opaque
|
||||
// command-buffer marker so cross-platform user code (HelloUI's
|
||||
// `auto init = window.StartInit();`) compiles unchanged.
|
||||
WebGPUCommandEncoderRef StartInit();
|
||||
void FinishInit();
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ export import :Input;
|
|||
export import :Device;
|
||||
export import :Animation;
|
||||
export import :ForwardDeclarations;
|
||||
export import :GraphicsTypes;
|
||||
export import :Clipboard;
|
||||
|
||||
// Vulkan-backed partitions — empty under DOM.
|
||||
|
|
@ -61,3 +62,7 @@ export import :Decompress;
|
|||
export import :Dom;
|
||||
export import :DomEvents;
|
||||
export import :Router;
|
||||
export import :WebGPU;
|
||||
export import :WebGPUBuffer;
|
||||
export import :DescriptorHeapWebGPU;
|
||||
export import :WebGPUComputeShader;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue