102 lines
3.7 KiB
C++
102 lines
3.7 KiB
C++
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
||
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
// Issue #55: once the font atlas is full, a glyph ShapeText places can take a
|
||
|
|
// cell that held another glyph, whose SDF the GPU image keeps until the atlas
|
||
|
|
// is uploaded again. DispatchText and DispatchFused uploaded it before their
|
||
|
|
// kernels; the generic UIRenderer::Dispatch, the Tier 1 path a custom text
|
||
|
|
// kernel sampling FontAtlasImageSlot() takes, did not, so for that frame the
|
||
|
|
// kernel drew the cell's old glyph. Every kernel the renderer dispatches now
|
||
|
|
// uploads the atlas first.
|
||
|
|
//
|
||
|
|
// What is asserted, in one onBuild: ShapeText of new glyphs leaves the atlas
|
||
|
|
// dirty; a ui.Dispatch of a custom kernel (the standard quads one, given no
|
||
|
|
// items) leaves it clean, and the frame renders without validation errors
|
||
|
|
// (which counts only with CRAFTER_GRAPHICS_VALIDATION set).
|
||
|
|
//
|
||
|
|
// Needs the Wayland backend and a live compositor (the UIRenderer needs a
|
||
|
|
// Window), the UI kernels and a font, which project.cpp copies next to the
|
||
|
|
// binary.
|
||
|
|
|
||
|
|
#include "vulkan/vulkan.h"
|
||
|
|
#include <cstdlib>
|
||
|
|
|
||
|
|
import Crafter.Graphics;
|
||
|
|
import Crafter.Event;
|
||
|
|
import std;
|
||
|
|
|
||
|
|
using namespace Crafter;
|
||
|
|
namespace fs = std::filesystem;
|
||
|
|
|
||
|
|
namespace {
|
||
|
|
|
||
|
|
int failures = 0;
|
||
|
|
|
||
|
|
void Check(bool ok, std::string_view what) {
|
||
|
|
std::println("{} {}", ok ? "PASS" : "FAIL", what);
|
||
|
|
if (!ok) ++failures;
|
||
|
|
}
|
||
|
|
|
||
|
|
fs::path ExeDir() {
|
||
|
|
return fs::read_symlink("/proc/self/exe").parent_path();
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
int main() {
|
||
|
|
const fs::path dir = ExeDir();
|
||
|
|
|
||
|
|
Device::Initialize();
|
||
|
|
Window window(320, 200, "UIDispatchAtlasFlush test");
|
||
|
|
VkCommandBuffer init = window.StartInit();
|
||
|
|
|
||
|
|
GraphicsDescriptorHeap heap;
|
||
|
|
heap.Initialize(/*images*/ 4, /*buffers*/ 4, /*samplers*/ 2);
|
||
|
|
window.descriptorHeap = &heap;
|
||
|
|
|
||
|
|
Font font(dir / "font.ttf");
|
||
|
|
FontAtlas atlas;
|
||
|
|
atlas.Initialize(init);
|
||
|
|
|
||
|
|
UIRenderer ui;
|
||
|
|
ui.fontAtlas = &atlas;
|
||
|
|
ui.Initialize(window, heap, init,
|
||
|
|
dir / "ui-quads.comp.spv", dir / "ui-circles.comp.spv",
|
||
|
|
dir / "ui-images.comp.spv", dir / "ui-text.comp.spv",
|
||
|
|
dir / "ui-fused.comp.spv");
|
||
|
|
window.passes.push_back(&ui);
|
||
|
|
|
||
|
|
GraphicsBuffer<QuadItem, true> quads;
|
||
|
|
quads.Create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
||
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, 1);
|
||
|
|
BufferSlot quadsSlot = ui.RegisterBuffer(quads);
|
||
|
|
|
||
|
|
bool built = false, dirtyAfterShaping = false, dirtyAfterDispatch = true;
|
||
|
|
EventListener<UIBuildArgs> buildSub(&ui.onBuild, [&](UIBuildArgs a) {
|
||
|
|
if (built) return;
|
||
|
|
built = true;
|
||
|
|
std::array<GlyphItem, 32> glyphs{};
|
||
|
|
ui.ShapeText(font, 24.0f, 10.0f, 40.0f, "Tier 1 kernels", {1, 1, 1, 1},
|
||
|
|
glyphs.data(), static_cast<std::uint32_t>(glyphs.size()));
|
||
|
|
dirtyAfterShaping = atlas.dirty;
|
||
|
|
const UIDispatchHeader header = ui.FillHeader(quadsSlot, 0);
|
||
|
|
ui.Dispatch(a.cmd, ui.drawQuads, &header, sizeof(header),
|
||
|
|
(window.width + 7) / 8, (window.height + 7) / 8);
|
||
|
|
dirtyAfterDispatch = atlas.dirty;
|
||
|
|
});
|
||
|
|
|
||
|
|
window.FinishInit();
|
||
|
|
window.Render();
|
||
|
|
|
||
|
|
Check(built, "onBuild ran");
|
||
|
|
Check(dirtyAfterShaping, "shaping new glyphs leaves the atlas dirty");
|
||
|
|
Check(!dirtyAfterDispatch, "a custom kernel's ui.Dispatch uploads the atlas before it runs");
|
||
|
|
if (std::getenv("CRAFTER_GRAPHICS_VALIDATION")) {
|
||
|
|
Check(Device::validationErrorCount == 0,
|
||
|
|
std::format("no validation errors ({} seen)", Device::validationErrorCount));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (failures == 0) std::println("\nAll UI dispatch atlas flush checks passed.");
|
||
|
|
else std::println("\n{} UI dispatch atlas flush check(s) FAILED.", failures);
|
||
|
|
return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||
|
|
}
|