101 lines
4.5 KiB
C++
101 lines
4.5 KiB
C++
|
|
//SPDX-License-Identifier: LGPL-3.0-only
|
||
|
|
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||
|
|
|
||
|
|
// The text kernels decode FontAtlas's SDF with FontAtlas's own encoding.
|
||
|
|
// stb_truetype writes kOnEdgeValue + kPixelDistScale * distance into each
|
||
|
|
// byte, and the shaders carry copies of those two numbers: UI_SDF_ON_EDGE and
|
||
|
|
// UI_SDF_PIXEL_DIST_SCALE in shaders/ui-shared.glsl, which ui-text and the
|
||
|
|
// fused kernel decode through, and again in the WGSL text kernel of
|
||
|
|
// additional/dom-webgpu.js. The decode once multiplied by kPixelDistScale
|
||
|
|
// where 255 / kPixelDistScale belongs, which left every edge pixelated, and a
|
||
|
|
// copy that drifts from FontAtlas would go unseen on WebGPU, where no test
|
||
|
|
// renders (wasm runs none) and a small drift passes TextAntialiasing's
|
||
|
|
// tolerances anyway. So this reads the sources and compares the numbers, and
|
||
|
|
// checks that both GLSL kernels decode through the shared helper rather than
|
||
|
|
// arithmetic of their own.
|
||
|
|
//
|
||
|
|
// Pure CPU: reads files, no device.
|
||
|
|
|
||
|
|
#include <cstdlib>
|
||
|
|
|
||
|
|
import Crafter.Graphics;
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
// The directory holding shaders/ and additional/, whether the test runs from
|
||
|
|
// its build output directory or the project root, as ExampleShaders finds it.
|
||
|
|
fs::path LocateRoot() {
|
||
|
|
for (const fs::path candidate : {".", "..", "../..", "../../.."}) {
|
||
|
|
std::error_code ec;
|
||
|
|
if (fs::is_regular_file(candidate / "shaders" / "ui-shared.glsl", ec)
|
||
|
|
&& fs::is_regular_file(candidate / "additional" / "dom-webgpu.js", ec)) {
|
||
|
|
return candidate;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string ReadFile(const fs::path& path) {
|
||
|
|
std::ifstream in(path, std::ios::binary);
|
||
|
|
return std::string(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
|
||
|
|
}
|
||
|
|
|
||
|
|
// The numbers `pattern`'s one capture group matches in `source`.
|
||
|
|
std::vector<float> Matches(const std::string& source, const std::string& pattern) {
|
||
|
|
std::vector<float> values;
|
||
|
|
const std::regex re(pattern);
|
||
|
|
for (auto it = std::sregex_iterator(source.begin(), source.end(), re); it != std::sregex_iterator(); ++it) {
|
||
|
|
values.push_back(std::stof((*it)[1].str()));
|
||
|
|
}
|
||
|
|
return values;
|
||
|
|
}
|
||
|
|
|
||
|
|
// `declaration` is the source up to the name, `name` the constant, followed
|
||
|
|
// by "= <number>;" in either language.
|
||
|
|
void CheckConstant(std::string_view file, const std::string& source, const std::string& declaration,
|
||
|
|
std::string_view name, float expected) {
|
||
|
|
const std::vector<float> values = Matches(source, declaration + R"(\s*=\s*([0-9]+(?:\.[0-9]+)?)\s*;)");
|
||
|
|
std::string found = values.empty() ? "none" : "";
|
||
|
|
for (std::size_t i = 0; i < values.size(); ++i) found += std::format("{}{}", i == 0 ? "" : ", ", values[i]);
|
||
|
|
Check(values.size() == 1 && values[0] == expected,
|
||
|
|
std::format("{} defines {} once, as {} (found {})", file, name, expected, found));
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace
|
||
|
|
|
||
|
|
int main() {
|
||
|
|
const fs::path root = LocateRoot();
|
||
|
|
if (root.empty()) {
|
||
|
|
std::println(std::cerr, "could not find shaders/ and additional/ from {}", fs::current_path().string());
|
||
|
|
return EXIT_FAILURE;
|
||
|
|
}
|
||
|
|
const std::string glsl = ReadFile(root / "shaders" / "ui-shared.glsl");
|
||
|
|
const std::string wgsl = ReadFile(root / "additional" / "dom-webgpu.js");
|
||
|
|
|
||
|
|
const float onEdge = static_cast<float>(FontAtlas::kOnEdgeValue);
|
||
|
|
const float pixelDistScale = FontAtlas::kPixelDistScale;
|
||
|
|
CheckConstant("ui-shared.glsl", glsl, R"(const\s+float\s+UI_SDF_ON_EDGE)", "UI_SDF_ON_EDGE", onEdge);
|
||
|
|
CheckConstant("ui-shared.glsl", glsl, R"(const\s+float\s+UI_SDF_PIXEL_DIST_SCALE)", "UI_SDF_PIXEL_DIST_SCALE", pixelDistScale);
|
||
|
|
CheckConstant("dom-webgpu.js", wgsl, R"(const\s+UI_SDF_ON_EDGE\s*:\s*f32)", "UI_SDF_ON_EDGE", onEdge);
|
||
|
|
CheckConstant("dom-webgpu.js", wgsl, R"(const\s+UI_SDF_PIXEL_DIST_SCALE\s*:\s*f32)", "UI_SDF_PIXEL_DIST_SCALE", pixelDistScale);
|
||
|
|
|
||
|
|
for (const std::string_view kernel : {"ui-text.comp.glsl", "ui-fused.comp.glsl"}) {
|
||
|
|
const std::string source = ReadFile(root / "shaders" / kernel);
|
||
|
|
Check(source.find("uiGlyphCoverage(") != std::string::npos,
|
||
|
|
std::format("{} decodes the atlas through ui-shared.glsl's uiGlyphCoverage", kernel));
|
||
|
|
}
|
||
|
|
|
||
|
|
std::println("{}", failures == 0 ? "all checks passed" : "check(s) failed");
|
||
|
|
return failures == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||
|
|
}
|