2124f6be07
Cierra 0049b. El context de fn::run_app pide ahora GL 4.3 core con forward-compat global, habilitando compute shaders, SSBOs, image load/store y atomic counters — bloques esenciales del graph_renderer GPU del proyecto osint_graph (issues 0049f y 0049h). Cambios: - cpp/framework/app_base.cpp: 4.3 core + forward-compat. Comentario marcando que es backward-compatible con shaders #version 330. - cpp/apps/primitives_gallery/capture.cpp: deja explicitamente 3.3 core porque WSL Mesa no entrega 4.3 offscreen (GLXBadFBConfig); ImGui + ImPlot funcionan igual en 3.3 para los goldens. - primitives_gallery: nuevo demo Gfx > gl_info que muestra Vendor/Renderer/Version/GLSL en runtime + status 4.3 (verde) + limites (MAX_TEXTURE_SIZE, MAX_VERTEX_ATTRIBS, MAX_UNIFORM_BLOCK_SIZE y, si 4.3+, MAX_SHADER_STORAGE_BUFFER_BINDINGS y compute shared mem). Solo glGetString/glGetIntegerv — sin loader extra. - About bumped a 0.4.0 con la nota del nuevo demo y de GL 4.3. - cpp/tests/test_visual.cpp: usa LIBGL_ALWAYS_SOFTWARE=1 al lanzar el capture para alinear el driver con update_goldens.sh; sin esto las diferencias de strings (llvmpipe vs d3d12) hacen que gl_info supere el 1% de tolerancia. - cpp/tests/golden/gl_info.png: nuevo golden. Build verificado en Linux (cmake build OK) + Windows cross-compile (cmake build OK). Las 27 pruebas pasan (incluida test_visual con 42 demos comparadas).
145 lines
5.3 KiB
C++
145 lines
5.3 KiB
C++
// test_visual — golden-image diff de las demos de primitives_gallery.
|
|
//
|
|
// Asume que existen PNGs en `cpp/tests/golden/<demo>.png` generados por
|
|
// `cpp/scripts/update_goldens.sh`. Si no existen, los SKIPea con INFO.
|
|
//
|
|
// El binario primitives_gallery se localiza relativo al directorio de build:
|
|
// <build>/apps/primitives_gallery/primitives_gallery --capture <tmpdir>
|
|
//
|
|
// Si el entorno no puede crear contexto GL (WSL minimo, container sin Mesa),
|
|
// el binario falla; el test reporta SKIP en lugar de FAIL para no bloquear
|
|
// CI en entornos donde el render headless no es posible.
|
|
|
|
#define CATCH_CONFIG_MAIN
|
|
#include "catch_amalgamated.hpp"
|
|
|
|
#include "png_diff.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <dirent.h>
|
|
#include <filesystem>
|
|
#include <string>
|
|
#include <sys/stat.h>
|
|
#include <vector>
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
// Defaults inyectados por CMake en el target test_visual:
|
|
// FN_TEST_GOLDEN_DIR — cpp/tests/golden (absoluto)
|
|
// FN_TEST_GALLERY_BIN — path absoluto al binario primitives_gallery
|
|
// FN_TEST_TMP_DIR — directorio temporal donde la corrida actual
|
|
// escribe sus PNGs (build/tests/visual_actual)
|
|
#ifndef FN_TEST_GOLDEN_DIR
|
|
#define FN_TEST_GOLDEN_DIR "cpp/tests/golden"
|
|
#endif
|
|
#ifndef FN_TEST_GALLERY_BIN
|
|
#define FN_TEST_GALLERY_BIN ""
|
|
#endif
|
|
#ifndef FN_TEST_TMP_DIR
|
|
#define FN_TEST_TMP_DIR "/tmp/primitives_gallery_visual"
|
|
#endif
|
|
#ifndef FN_TEST_REPO_ROOT
|
|
#define FN_TEST_REPO_ROOT "."
|
|
#endif
|
|
|
|
static std::vector<std::string> list_pngs(const std::string& dir) {
|
|
std::vector<std::string> out;
|
|
if (!fs::exists(dir) || !fs::is_directory(dir)) return out;
|
|
for (const auto& entry : fs::directory_iterator(dir)) {
|
|
if (!entry.is_regular_file()) continue;
|
|
const auto path = entry.path();
|
|
if (path.extension() == ".png") {
|
|
out.push_back(path.stem().string());
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static bool file_exists(const std::string& p) {
|
|
struct stat st{};
|
|
return ::stat(p.c_str(), &st) == 0;
|
|
}
|
|
|
|
TEST_CASE("primitives_gallery visual goldens", "[visual]") {
|
|
const std::string golden_dir = FN_TEST_GOLDEN_DIR;
|
|
const std::string gallery_bin = FN_TEST_GALLERY_BIN;
|
|
const std::string tmp_dir = FN_TEST_TMP_DIR;
|
|
|
|
INFO("golden dir: " << golden_dir);
|
|
INFO("gallery bin: " << gallery_bin);
|
|
INFO("tmp dir: " << tmp_dir);
|
|
|
|
auto goldens = list_pngs(golden_dir);
|
|
if (goldens.empty()) {
|
|
WARN("No goldens found in '" << golden_dir << "'. "
|
|
"Run cpp/scripts/update_goldens.sh to generate them. "
|
|
"Visual diff test SKIPPED.");
|
|
SUCCEED("no goldens — skipped");
|
|
return;
|
|
}
|
|
|
|
if (gallery_bin.empty() || !file_exists(gallery_bin)) {
|
|
WARN("primitives_gallery binary not found at '" << gallery_bin
|
|
<< "'. Build target primitives_gallery first. SKIPPED.");
|
|
SUCCEED("gallery binary missing — skipped");
|
|
return;
|
|
}
|
|
|
|
// Crear tmp_dir y correr captura.
|
|
std::error_code ec;
|
|
fs::create_directories(tmp_dir, ec);
|
|
|
|
// Ejecutar binario en modo --capture desde la raiz del repo. Algunas
|
|
// demos resuelven paths relativos (p.ej. sql_workbench busca registry.db);
|
|
// correr desde la raiz garantiza determinismo entre maquinas.
|
|
//
|
|
// LIBGL_ALWAYS_SOFTWARE=1 fuerza llvmpipe igual que update_goldens.sh, asi
|
|
// demos que muestran strings del driver (gl_info) no fallan por diferencias
|
|
// entre llvmpipe / d3d12 / drivers vendor.
|
|
std::string cmd = std::string("cd '") + FN_TEST_REPO_ROOT + "' && "
|
|
+ "LIBGL_ALWAYS_SOFTWARE=1 '"
|
|
+ gallery_bin + "' --capture '" + tmp_dir + "' 2>&1";
|
|
INFO("capture cmd: " << cmd);
|
|
const int rc = std::system(cmd.c_str());
|
|
if (rc != 0) {
|
|
WARN("primitives_gallery --capture exited with rc=" << rc
|
|
<< " (likely no GL context — WSL/headless). "
|
|
<< "Run with LIBGL_ALWAYS_SOFTWARE=1 or install Mesa. SKIPPED.");
|
|
SUCCEED("capture failed — environment lacks GL — skipped");
|
|
return;
|
|
}
|
|
|
|
int matched = 0, missing_actual = 0, diffed = 0;
|
|
for (const auto& demo_id : goldens) {
|
|
const std::string g_path = golden_dir + "/" + demo_id + ".png";
|
|
const std::string a_path = tmp_dir + "/" + demo_id + ".png";
|
|
if (!file_exists(a_path)) {
|
|
WARN("No actual capture for '" << demo_id << "' at " << a_path);
|
|
++missing_actual;
|
|
continue;
|
|
}
|
|
auto r = fn_test::pixel_diff_ratio(g_path, a_path, /*channel_threshold=*/5);
|
|
INFO("demo: " << demo_id
|
|
<< " diff_ratio=" << r.diff_ratio
|
|
<< " (" << r.pixels_different << "/" << r.pixels_total << ")");
|
|
// Tolerancia 1% por defecto.
|
|
if (r.diff_ratio > 0.01) {
|
|
++diffed;
|
|
FAIL_CHECK("Visual diff for '" << demo_id << "' exceeds 1%: "
|
|
<< (r.diff_ratio * 100.0) << "%. "
|
|
<< "Compare " << a_path << " vs " << g_path
|
|
<< " — if the change is intentional, run "
|
|
<< "cpp/scripts/update_goldens.sh.");
|
|
} else {
|
|
++matched;
|
|
}
|
|
}
|
|
|
|
INFO("visual goldens — matched=" << matched
|
|
<< " diffed=" << diffed
|
|
<< " missing_actual=" << missing_actual);
|
|
REQUIRE(diffed == 0);
|
|
}
|