2026-05-01 23:35:37 +02:00
|
|
|
/*
|
|
|
|
|
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
|
|
|
|
|
*/
|
|
|
|
|
export module Crafter.Graphics:UIDrawList;
|
|
|
|
|
import std;
|
|
|
|
|
import :UILength;
|
|
|
|
|
import :UIWidget;
|
|
|
|
|
|
|
|
|
|
export namespace Crafter::UI {
|
|
|
|
|
class FontAtlas; // forward decl (full def in :UIAtlas)
|
|
|
|
|
|
|
|
|
|
// Item type tags. Must match the shader-side constants exactly.
|
|
|
|
|
enum class ItemType : std::uint32_t {
|
|
|
|
|
Rect = 0,
|
|
|
|
|
RoundRect = 1,
|
|
|
|
|
Glyph = 2,
|
|
|
|
|
Image = 3,
|
|
|
|
|
ClipPush = 5,
|
|
|
|
|
ClipPop = 6,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// GPU-bound draw item. Layout matches the shader's UIItem struct under
|
|
|
|
|
// GL_EXT_scalar_block_layout (no std140/std430 padding). Keep this in
|
|
|
|
|
// sync with shaders/ui.comp.glsl.
|
|
|
|
|
//
|
|
|
|
|
// Field meanings by ItemType:
|
|
|
|
|
// Rect: posPx, sizePx, color (alpha-premultiplied).
|
|
|
|
|
// RoundRect: same as Rect + cornerRadiusPx.
|
|
|
|
|
// Glyph: posPx/sizePx = on-screen quad; uvRect = atlas region;
|
|
|
|
|
// color tints the SDF sample; cornerRadiusPx unused.
|
|
|
|
|
// Image: posPx/sizePx = quad; uvRect = source rect (0..1);
|
|
|
|
|
// imageIdx = bindless slot offset; color tints.
|
|
|
|
|
// ClipPush: posPx/sizePx = clip rect to push (intersected with current).
|
|
|
|
|
// ClipPop: fields ignored.
|
|
|
|
|
struct UIItem {
|
|
|
|
|
std::uint32_t type; // ItemType
|
|
|
|
|
std::uint32_t flags;
|
|
|
|
|
float posPx[2];
|
|
|
|
|
float sizePx[2];
|
|
|
|
|
float color[4];
|
|
|
|
|
float colorB[4];
|
|
|
|
|
float uvRect[4];
|
|
|
|
|
std::uint32_t imageIdx;
|
|
|
|
|
std::uint32_t cornerRadiusPx;
|
|
|
|
|
float reserved[2];
|
|
|
|
|
};
|
|
|
|
|
static_assert(sizeof(UIItem) == 88, "UIItem size must match shader-side struct");
|
|
|
|
|
|
|
|
|
|
// CPU-side accumulator. Widgets call `Add(...)` (or convenience helpers)
|
|
|
|
|
// during their Emit pass; the renderer copies the resulting buffer into
|
|
|
|
|
// the per-frame mapped SSBO and dispatches the compute shader.
|
|
|
|
|
class DrawList {
|
|
|
|
|
public:
|
|
|
|
|
std::vector<UIItem> items;
|
|
|
|
|
|
|
|
|
|
// Set by the renderer before EmitTree(). Widgets that draw text or
|
|
|
|
|
// images consult these — without an atlas, glyph emission is a
|
|
|
|
|
// no-op (useful for layout-only debug dumps).
|
|
|
|
|
FontAtlas* atlas = nullptr;
|
|
|
|
|
std::uint32_t bindlessBaseHeapIdx = 0; // base heap slot for Image widgets
|
|
|
|
|
float scale = 1.0f; // device scale (mirrors LayoutContext::scale)
|
2026-05-02 00:03:24 +02:00
|
|
|
float time = 0.0f; // seconds since scene init (drives blink etc.)
|
2026-05-01 23:35:37 +02:00
|
|
|
|
|
|
|
|
void Reset() { items.clear(); }
|
|
|
|
|
|
|
|
|
|
void Add(const UIItem& it) { items.push_back(it); }
|
|
|
|
|
|
|
|
|
|
// Convenience constructors for common items. These keep widget
|
|
|
|
|
// Emit code short and self-documenting.
|
|
|
|
|
void AddRect(Rect r, Color c) {
|
|
|
|
|
UIItem it{};
|
|
|
|
|
it.type = static_cast<std::uint32_t>(ItemType::Rect);
|
|
|
|
|
it.posPx[0] = r.x; it.posPx[1] = r.y;
|
|
|
|
|
it.sizePx[0] = r.w; it.sizePx[1] = r.h;
|
|
|
|
|
// Premultiply alpha so the shader's "OVER" operator works without
|
|
|
|
|
// a per-pixel multiply.
|
|
|
|
|
it.color[0] = c.r * c.a;
|
|
|
|
|
it.color[1] = c.g * c.a;
|
|
|
|
|
it.color[2] = c.b * c.a;
|
|
|
|
|
it.color[3] = c.a;
|
|
|
|
|
items.push_back(it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AddRoundRect(Rect r, Color c, float radiusPx) {
|
|
|
|
|
UIItem it{};
|
|
|
|
|
it.type = static_cast<std::uint32_t>(ItemType::RoundRect);
|
|
|
|
|
it.posPx[0] = r.x; it.posPx[1] = r.y;
|
|
|
|
|
it.sizePx[0] = r.w; it.sizePx[1] = r.h;
|
|
|
|
|
it.color[0] = c.r * c.a;
|
|
|
|
|
it.color[1] = c.g * c.a;
|
|
|
|
|
it.color[2] = c.b * c.a;
|
|
|
|
|
it.color[3] = c.a;
|
|
|
|
|
it.cornerRadiusPx = static_cast<std::uint32_t>(radiusPx);
|
|
|
|
|
items.push_back(it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Glyph item: `quad` is the glyph's on-screen rect, `atlasUV` is
|
|
|
|
|
// its (x, y, w, h) region in 0..1 atlas-UV space.
|
|
|
|
|
void AddGlyph(Rect quad, Color color, std::array<float, 4> atlasUV) {
|
|
|
|
|
UIItem it{};
|
|
|
|
|
it.type = static_cast<std::uint32_t>(ItemType::Glyph);
|
|
|
|
|
it.posPx[0] = quad.x; it.posPx[1] = quad.y;
|
|
|
|
|
it.sizePx[0] = quad.w; it.sizePx[1] = quad.h;
|
|
|
|
|
it.color[0] = color.r * color.a;
|
|
|
|
|
it.color[1] = color.g * color.a;
|
|
|
|
|
it.color[2] = color.b * color.a;
|
|
|
|
|
it.color[3] = color.a;
|
|
|
|
|
it.uvRect[0] = atlasUV[0]; it.uvRect[1] = atlasUV[1];
|
|
|
|
|
it.uvRect[2] = atlasUV[2]; it.uvRect[3] = atlasUV[3];
|
|
|
|
|
items.push_back(it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Image item: `imageHeapOffset` is added to the renderer's
|
|
|
|
|
// bindless-base slot at draw time to find the right descriptor.
|
|
|
|
|
void AddImage(Rect quad, Color tint, std::uint32_t imageHeapOffset,
|
|
|
|
|
std::array<float, 4> sourceUV = {0, 0, 1, 1}) {
|
|
|
|
|
UIItem it{};
|
|
|
|
|
it.type = static_cast<std::uint32_t>(ItemType::Image);
|
|
|
|
|
it.posPx[0] = quad.x; it.posPx[1] = quad.y;
|
|
|
|
|
it.sizePx[0] = quad.w; it.sizePx[1] = quad.h;
|
|
|
|
|
it.color[0] = tint.r * tint.a;
|
|
|
|
|
it.color[1] = tint.g * tint.a;
|
|
|
|
|
it.color[2] = tint.b * tint.a;
|
|
|
|
|
it.color[3] = tint.a;
|
|
|
|
|
it.uvRect[0] = sourceUV[0]; it.uvRect[1] = sourceUV[1];
|
|
|
|
|
it.uvRect[2] = sourceUV[2]; it.uvRect[3] = sourceUV[3];
|
|
|
|
|
it.imageIdx = imageHeapOffset;
|
|
|
|
|
items.push_back(it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clip stack — emit a ClipPush at the start of the clipped region
|
|
|
|
|
// and a matching ClipPop at the end. The shader maintains a small
|
|
|
|
|
// fixed-size stack and intersects pushes with the existing clip.
|
|
|
|
|
void PushClip(Rect r) {
|
|
|
|
|
UIItem it{};
|
|
|
|
|
it.type = static_cast<std::uint32_t>(ItemType::ClipPush);
|
|
|
|
|
it.posPx[0] = r.x; it.posPx[1] = r.y;
|
|
|
|
|
it.sizePx[0] = r.w; it.sizePx[1] = r.h;
|
|
|
|
|
items.push_back(it);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PopClip() {
|
|
|
|
|
UIItem it{};
|
|
|
|
|
it.type = static_cast<std::uint32_t>(ItemType::ClipPop);
|
|
|
|
|
items.push_back(it);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Walk the laid-out tree and emit every widget's items.
|
|
|
|
|
inline void EmitTree(const Widget& root, DrawList& dl) {
|
|
|
|
|
root.Emit(dl);
|
|
|
|
|
}
|
|
|
|
|
}
|