mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 04:36:00 -10:00
fix(app): crt-geom and other arrays-of-arrays shader presets on macOS; GLSL at the context's version (#963)
* fix(app): explain and grey out arrays-of-arrays shader presets on macOS crt-geom failed on macOS with "extension 'GL_ARB_arrays_of_arrays' is not supported". Its mask code declares 2-D arrays (vec3 slotmask[3][4]). SPIRV-Cross generates GLSL below 4.30 for such a shader by requiring that extension unless flatten_multidimensional_arrays is set, and librashader 0.12 never sets that option. macOS stops at OpenGL 4.1 without the extension, so no GLSL version the app can ask for builds these presets there. Rendering all 498 bundled presets on macOS (M4 Max, GL 4.1 Metal) found 15 that fail this way: crt-geom/-deluxe/-tate, crt-hyllian x4, crt-nobody, crt-super-xbr, vt220, crt-fast-bilateral-super-xbr, img_mod, test-pattern-append/-prepend and res-independent-scanlines. presetsNeedingArraysOfArrays() lists them, pinned to the bundled slang-shaders commit. - ShaderChain::probeGlCaps() reads the GL version and extensions once (from VideoOut::init, and from render()). - When the context has neither GL 4.3 nor the extension, the preset list greys these out with the reason. Choosing one anyway (config.ini, file dialog) now shows "it needs OpenGL 4.3 (arrays of arrays); this system has OpenGL 4.1" instead of the GL compiler's text. The full error stays in lastError(). - shaders_bundled checks that every list entry is a valid bundled preset. shaders_gl renders each one and requires a clean, explained failure where the capability is missing and a picture where it is present, so the list cannot go stale silently. This replaces the crt-geom strstr exemption. - Documented in app/packaging/README.md (macOS notes) and app/README.md. app/shaders/README.txt no longer says slang-shaders is not bundled. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(app): generate shader GLSL at the context's version, not 3.30 The GL chain always asked librashader for GLSL 3.30. A context with more was still handed 3.30 code, so presets using GLSL 4.00 features (textureGather with a component, packUnorm4x8, bitCount/findLSB) failed to build. ShaderChain::probeGlCaps() now records the context's version, clamped to 330..460, and the chain uses it. On macOS (M4 Max, GL 4.1 Metal), rendering all 498 bundled presets at 4.10 instead of 3.30 took 462 working presets to 473. lottesRVM, nnedi3 x5, mmpx-ADV and vectorscale x4 now build and produce a picture, and no preset that worked stopped working. Linux and Windows contexts will usually get 4.60. No sweep has been run there, and CI does not run shaders_gl on a real GL context. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2c3e888d26
commit
5d2ea0b8c5
10 files changed
+170
-10
No files matched your search
@@ -253,6 +253,10 @@ a failure.
|
||||
(`computePresentRect` in `present_util.cpp`): the chain renders into a texture
|
||||
of exactly the rectangle they pick, so a CRT preset at 3x integer scale looks
|
||||
the same as the plain picture at 3x. Both paths share that one function.
|
||||
- **OpenGL version.** The chain compiles to the context's GLSL version (4.10 on macOS, usually
|
||||
4.60 elsewhere). macOS's OpenGL 4.1 cannot build presets with arrays of arrays (CRT-Geom,
|
||||
CRT-Hyllian and 13 others in the bundle); the list greys them out and says why. Details:
|
||||
[packaging/README.md](packaging/README.md#macos-notes).
|
||||
- **Settings keys.** `shader.enabled`, `shader.preset`, `shader.folder`.
|
||||
|
||||
### Getting the shader runtime
|
||||
|
||||
@@ -142,6 +142,30 @@ after each rebuild of an unsigned binary. It does not use hardened runtime and n
|
||||
goes in `Contents/Frameworks/`, one of the places the shader code probes. The dev target is
|
||||
deliberately not a `MACOSX_BUNDLE`: its POST_BUILD copies would land in `Contents/MacOS`.
|
||||
* **Universal.** Two single-architecture builds merged with `lipo` (`make-universal.sh`), because
|
||||
`VJShaders.cmake` fetches the prebuilt librashader for one architecture per configure. A gate
|
||||
(`check-universal.sh`) fails the build if any Mach-O lacks a slice, and checks every binary's
|
||||
`minos` against the deployment target (11.0).
|
||||
**Known gap:** the `x86_64-macos` archive of librashader v0.12.0 contains an arm64 dylib (verified with
|
||||
`lipo`). `librashader.dylib` therefore ships arm64-only, with a warning; on an Intel Mac the app
|
||||
reports the shader runtime as unavailable and runs without shaders. Fix: build librashader for
|
||||
`x86_64-apple-darwin` in CI (Rust) and merge.
|
||||
* **OpenGL 4.1.** macOS stops at OpenGL 4.1 and has no `GL_ARB_arrays_of_arrays`. librashader 0.12 never
|
||||
sets SPIRV-Cross's `flatten_multidimensional_arrays`, so a shader with a 2-D array (crt-geom's
|
||||
`vec3 slotmask[3][4]`) compiles to GLSL that requires that extension and cannot build there. 15 bundled
|
||||
presets do this: crt-geom, -deluxe, -tate; crt-hyllian, -ntsc, -ntsc-rainbow, -sinc-composite; crt-nobody,
|
||||
crt-super-xbr, vt220, crt-fast-bilateral-super-xbr, img_mod, test-pattern-append/-prepend and
|
||||
res-independent-scanlines. Where the context has neither GL 4.3 nor the extension, the preset list greys
|
||||
them out ("needs OpenGL 4.3"), and a preset chosen anyway (config.ini, file dialog) gets that reason in
|
||||
the on-screen message instead of the GL compiler's text. The list is `presetsNeedingArraysOfArrays()`
|
||||
(`present_util.cpp`), pinned to the bundled slang-shaders commit; `shaders_bundled` and `shaders_gl`
|
||||
check it. Real fix: librashader setting `flatten_multidimensional_arrays` when it generates GLSL below
|
||||
4.30.
|
||||
The chain asks librashader for the context's own GLSL version (4.10 here). It used to ask for 3.30, which
|
||||
cost 11 presets GLSL 4.00 features the context has (lottesRVM, nnedi3 x5, mmpx-ADV, vectorscale x4).
|
||||
Rendering all 498 bundled presets on an M4 Max (GL 4.1 Metal) at 4.10, 473 work. Besides the 15 above,
|
||||
fsr and smaa+fsr need `packHalf2x16` (GLSL 4.20), and 8 fail for reasons that do not look macOS-specific
|
||||
(not checked elsewhere): the six nedi bilateral-variant presets (unknown semantic `play`),
|
||||
crt-black_crush-koko (preset parse error) and ntsc-blastem (varying type mismatch at link).
|
||||
each configure is handed one single-architecture librashader (`-DVJ_LIBRASHADER_LIB`). A gate
|
||||
(`check-universal.sh`) fails the build if any Mach-O, `librashader.dylib` included, lacks a slice, and checks every
|
||||
binary's `minos` against the deployment target (11.0). Both dylibs are built from source in CI from the same pinned
|
||||
|
||||
@@ -5,6 +5,11 @@ the repository.
|
||||
vj-sharp-bilinear.slangp crisp scaling at non-integer window sizes
|
||||
vj-scanlines.slangp scanlines + optional RGB mask, with parameters
|
||||
|
||||
RetroArch's slang-shaders collection (CRT-Royale, CRT-Geom, ...) is NOT bundled:
|
||||
its files carry many different licences. Point Settings > Video > Preset folder
|
||||
at a clone of https://github.com/libretro/slang-shaders to use them.
|
||||
slang-shaders/ (when present) is an unmodified subset of RetroArch's
|
||||
https://github.com/libretro/slang-shaders, fetched at build time; every shader
|
||||
keeps its own licence (see slang-shaders/NOTICE-virtualjaguar.txt). For the rest
|
||||
of the collection, point Settings > Video > Preset folder at a clone of it.
|
||||
|
||||
macOS: OpenGL stops at 4.1 there, so presets that need OpenGL 4.2/4.3 features
|
||||
(CRT-Geom, CRT-Hyllian and a few more: arrays of arrays) cannot compile. The
|
||||
preset list greys them out; the plain picture is shown if one is chosen anyway.
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <set>
|
||||
@@ -277,4 +278,36 @@ std::vector<std::string> scanPresets(const std::string& root, size_t maxResults,
|
||||
return out;
|
||||
}
|
||||
|
||||
const std::vector<const char*>& presetsNeedingArraysOfArrays() {
|
||||
static const std::vector<const char*> k = {
|
||||
"crt/crt-geom-deluxe.slangp",
|
||||
"crt/crt-geom-tate.slangp",
|
||||
"crt/crt-geom.slangp",
|
||||
"crt/crt-hyllian-ntsc-rainbow.slangp",
|
||||
"crt/crt-hyllian-ntsc.slangp",
|
||||
"crt/crt-hyllian-sinc-composite.slangp",
|
||||
"crt/crt-hyllian.slangp",
|
||||
"crt/crt-nobody.slangp",
|
||||
"crt/crt-super-xbr.slangp",
|
||||
"crt/vt220.slangp",
|
||||
"denoisers/crt-fast-bilateral-super-xbr.slangp",
|
||||
"misc/img_mod.slangp",
|
||||
"misc/test-pattern-append.slangp",
|
||||
"misc/test-pattern-prepend.slangp",
|
||||
"scanlines/res-independent-scanlines.slangp",
|
||||
};
|
||||
return k;
|
||||
}
|
||||
|
||||
bool presetNeedsArraysOfArrays(const std::string& path) {
|
||||
std::string p = path;
|
||||
std::replace(p.begin(), p.end(), '\\', '/');
|
||||
for (const char* rel : presetsNeedingArraysOfArrays()) {
|
||||
size_t n = std::strlen(rel);
|
||||
if (p.size() < n || p.compare(p.size() - n, n, rel) != 0) continue;
|
||||
if (p.size() == n || p[p.size() - n - 1] == '/') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace vj
|
||||
@@ -50,4 +50,16 @@ PresetInfo inspectPreset(const std::string& path);
|
||||
std::vector<std::string> scanPresets(const std::string& root, size_t maxResults = 5000,
|
||||
int maxDepth = 10, bool* truncated = nullptr);
|
||||
|
||||
// Presets whose shaders declare arrays of arrays (crt-geom's `vec3 slotmask[3][4]`).
|
||||
// librashader 0.12 has SPIRV-Cross emit GLSL 330 for them with
|
||||
// `#extension GL_ARB_arrays_of_arrays : require`, so they compile only on OpenGL 4.3+
|
||||
// or where that extension exists; macOS stops at OpenGL 4.1 without it. The list is
|
||||
// every bundled preset that failed that way when all of them were rendered on macOS,
|
||||
// at the slang-shaders commit pinned in VJShaders.cmake (VJ_SLANG_COMMIT): redo that
|
||||
// sweep when the pin moves. Entries are paths relative to the slang-shaders root.
|
||||
const std::vector<const char*>& presetsNeedingArraysOfArrays();
|
||||
// True when `path` ends in one of those entries ("crt/crt-geom.slangp" matches the
|
||||
// bundled copy and a slang-shaders clone alike). '\\' counts as '/'.
|
||||
bool presetNeedsArraysOfArrays(const std::string& path);
|
||||
|
||||
} // namespace vj
|
||||
+34
-1
@@ -443,6 +443,29 @@ void ShaderChain::paramChanged(size_t i) {
|
||||
// frames
|
||||
// ---------------------------------------------------------------------------------------
|
||||
|
||||
void ShaderChain::probeGlCaps() {
|
||||
#if defined(VJ_HAVE_LIBRASHADER)
|
||||
if (glAoA_ >= 0) return;
|
||||
GLint major = 0, minor = 0, n = 0;
|
||||
glGetIntegerv(GL_MAJOR_VERSION, &major);
|
||||
glGetIntegerv(GL_MINOR_VERSION, &minor);
|
||||
if (major <= 0) return; // no context current: try again later
|
||||
glVer_ = std::to_string(major) + "." + std::to_string(minor);
|
||||
// GLSL to generate: the context's own (4.1 on macOS, usually 4.6 elsewhere), capped at
|
||||
// librashader's highest. 330 everywhere threw away GLSL 4.00 (textureGather with a
|
||||
// component, packUnorm4x8, bitCount/findLSB) on contexts that have it.
|
||||
int glsl = major * 100 + minor * 10;
|
||||
glsl_ = (unsigned)(glsl < 330 ? 330 : glsl > 460 ? 460 : glsl);
|
||||
bool aoa = major > 4 || (major == 4 && minor >= 3);
|
||||
glGetIntegerv(GL_NUM_EXTENSIONS, &n);
|
||||
for (GLint i = 0; !aoa && i < n; i++) {
|
||||
const char* e = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, (GLuint)i));
|
||||
if (e && std::strcmp(e, "GL_ARB_arrays_of_arrays") == 0) aoa = true;
|
||||
}
|
||||
glAoA_ = aoa ? 1 : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ShaderChain::destroyGL() {
|
||||
#if defined(VJ_HAVE_LIBRASHADER)
|
||||
if (!impl_) return;
|
||||
@@ -476,11 +499,21 @@ unsigned ShaderChain::render(const ShaderFrame& f) {
|
||||
if (!I.bindFbo)
|
||||
I.bindFbo = reinterpret_cast<BindFramebufferFn>(SDL_GL_GetProcAddress("glBindFramebuffer"));
|
||||
|
||||
probeGlCaps();
|
||||
auto fail = [&](const std::string& why) {
|
||||
failed_ = true;
|
||||
lastErr_ = why;
|
||||
I.freeChain();
|
||||
std::string brief = why.size() > 160 ? why.substr(0, 160) + "..." : why; // full text stays in lastError()
|
||||
// SPIRV-Cross's GLSL for a multi-dimensional array needs GL 4.3 or this extension
|
||||
// (see presetsNeedingArraysOfArrays()); say so instead of quoting the GL compiler.
|
||||
if (why.find("GL_ARB_arrays_of_arrays") != std::string::npos) {
|
||||
brief = "it needs OpenGL 4.3 (arrays of arrays)";
|
||||
if (!glVer_.empty()) brief += "; this system has OpenGL " + glVer_;
|
||||
lastErr_ = "This preset needs OpenGL 4.3 (GL_ARB_arrays_of_arrays), which " +
|
||||
(glVer_.empty() ? std::string("this system") : "OpenGL " + glVer_) +
|
||||
" does not provide (macOS stops at 4.1). librashader: " + why;
|
||||
}
|
||||
setNotice("Shader " + pathStem(preset) + " failed (" + brief + "); showing the plain picture");
|
||||
return 0u;
|
||||
};
|
||||
@@ -498,7 +531,7 @@ unsigned ShaderChain::render(const ShaderFrame& f) {
|
||||
filter_chain_gl_opt_t co;
|
||||
std::memset(&co, 0, sizeof co);
|
||||
co.version = apiVersion_ < (unsigned)LIBRASHADER_CURRENT_VERSION ? apiVersion_ : (unsigned)LIBRASHADER_CURRENT_VERSION;
|
||||
co.glsl_version = 330;
|
||||
co.glsl_version = (uint16_t)glsl_; // the context's, from probeGlCaps()
|
||||
co.use_dsa = false; // GL 3.3 core; DSA (and with it librashader's shader cache) needs 4.5
|
||||
co.force_no_mipmaps = false;
|
||||
co.disable_cache = false;
|
||||
|
||||
@@ -85,6 +85,14 @@ public:
|
||||
void paramChanged(size_t index); // pushes params()[index].value into the live chain
|
||||
void resetParams();
|
||||
|
||||
// ---- what the GL context can compile (context current; render() also calls it) ----
|
||||
// Reads the GL version and extensions once. Until then nothing is assumed missing.
|
||||
void probeGlCaps();
|
||||
// False when the context has neither OpenGL 4.3 nor GL_ARB_arrays_of_arrays (macOS):
|
||||
// presetNeedsArraysOfArrays() presets cannot compile there.
|
||||
bool glHasArraysOfArrays() const { return glAoA_ != 0; }
|
||||
const std::string& glVersionText() const { return glVer_; } // "4.1", once probed
|
||||
|
||||
// ---- per frame (GL context current) -----------------------------------------------
|
||||
// True when a frame would go through the chain.
|
||||
bool active() const { return enabled && !preset.empty() && !failed_ && libraryLoaded(); }
|
||||
@@ -127,6 +135,9 @@ private:
|
||||
bool noticedMissing_ = false;
|
||||
std::vector<ShaderParam> params_;
|
||||
uint64_t runs_ = 0, reuses_ = 0;
|
||||
int glAoA_ = -1; // arrays of arrays: -1 not probed yet, 0 no, 1 yes
|
||||
std::string glVer_;
|
||||
unsigned glsl_ = 330; // GLSL version librashader generates (the context's once probed)
|
||||
|
||||
void setNotice(const std::string& s);
|
||||
};
|
||||
|
||||
@@ -88,6 +88,16 @@ void drawList(App& app, Listing& l, const char* id, float height) {
|
||||
if (!f.empty() && lowerStr(rel).find(f) == std::string::npos) continue;
|
||||
std::string full = l.root + rel;
|
||||
bool sel = (full == app.video.shader.preset);
|
||||
// Shown, not hidden: a filter for "crt-geom" should find it and say why it is grey.
|
||||
if (!app.video.shader.glHasArraysOfArrays() && presetNeedsArraysOfArrays(rel)) {
|
||||
std::string label = rel + " (needs OpenGL 4.3)";
|
||||
ImGui::Selectable(label.c_str(), sel, ImGuiSelectableFlags_Disabled);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
ImGui::SetTooltip("This preset uses arrays of arrays, which need OpenGL 4.3.\n"
|
||||
"This system has OpenGL %s (macOS stops at 4.1).",
|
||||
app.video.shader.glVersionText().c_str());
|
||||
continue;
|
||||
}
|
||||
if (ImGui::Selectable(rel.c_str(), sel)) applyPreset(app, full);
|
||||
if (sel) ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ bool VideoOut::init() {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
shader.probeGlCaps(); // the preset picker marks what this GL cannot compile
|
||||
return tex_ != 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -264,6 +264,15 @@ static void testFallback() {
|
||||
|
||||
// Optional (VJ_TEST_SLANG_DIR=<slang-shaders checkout>): the parser against ~1500
|
||||
// real presets. Not part of the default run; prints what it finds.
|
||||
static void testArraysOfArraysList() {
|
||||
CHECK(presetNeedsArraysOfArrays("crt/crt-geom.slangp"), "relative path");
|
||||
CHECK(presetNeedsArraysOfArrays("/x/slang-shaders/crt/crt-geom.slangp"), "clone path");
|
||||
CHECK(presetNeedsArraysOfArrays("C:\\ss\\crt\\crt-geom.slangp"), "Windows separators");
|
||||
CHECK(!presetNeedsArraysOfArrays("/x/mycrt/crt-geom.slangp"), "match stops at a separator");
|
||||
CHECK(!presetNeedsArraysOfArrays("crt/crt-lottes.slangp"), "crt-lottes compiles on GL 4.1");
|
||||
CHECK(!presetNeedsArraysOfArrays(""), "empty path");
|
||||
}
|
||||
|
||||
static void testRealCheckout() {
|
||||
const char* dir = std::getenv("VJ_TEST_SLANG_DIR");
|
||||
if (!dir || !*dir) return;
|
||||
@@ -291,6 +300,7 @@ static int runUnit() {
|
||||
testRect();
|
||||
testPresetParsing();
|
||||
testFallback();
|
||||
testArraysOfArraysList();
|
||||
testRealCheckout();
|
||||
std::printf("shader_tests unit: %s (%s headers)\n", g_fail ? "FAIL" : "OK",
|
||||
ShaderChain::builtWithHeaders() ? "with" : "without");
|
||||
@@ -331,6 +341,12 @@ static int runBundled() {
|
||||
PresetInfo p = inspectPreset(root + rel);
|
||||
CHECK(p.ok, "%s: %s", rel, p.error.c_str());
|
||||
}
|
||||
// The needs-GL-4.3 list names bundled files: a slang-shaders bump that renames one
|
||||
// must fail here, not quietly stop greying it out.
|
||||
for (const char* rel : presetsNeedingArraysOfArrays()) {
|
||||
CHECK(inspectPreset(root + rel).ok, "arrays-of-arrays list: %s is not a valid bundled preset", rel);
|
||||
CHECK(presetNeedsArraysOfArrays(root + rel), "%s: full path should match", rel);
|
||||
}
|
||||
CHECK(fs::exists(fs::u8path(root + "NOTICE-virtualjaguar.txt")), "notice file");
|
||||
CHECK(fs::exists(fs::u8path(root + "crt/shaders/crt-royale/LICENSE.TXT")), "crt-royale licence travels with it");
|
||||
std::printf("shader_tests bundled: %s\n", g_fail ? "FAIL" : "OK");
|
||||
@@ -572,11 +588,16 @@ static int runGl() {
|
||||
{
|
||||
std::string root = VJ_SLANG_DIR;
|
||||
if (!root.empty() && root.back() != '/') root += '/';
|
||||
for (const char* rel : kBundledMustWork) {
|
||||
std::vector<const char*> rels(std::begin(kBundledMustWork), std::end(kBundledMustWork));
|
||||
for (const char* rel : presetsNeedingArraysOfArrays())
|
||||
if (std::find_if(rels.begin(), rels.end(), [&](const char* r) { return std::strcmp(r, rel) == 0; }) == rels.end())
|
||||
rels.push_back(rel);
|
||||
for (const char* rel : rels) {
|
||||
if (!fs::exists(fs::u8path(root + rel))) continue; // bundling was skipped
|
||||
// crt-geom declares GL_ARB_arrays_of_arrays, which macOS's GL 4.1 lacks: there it
|
||||
// must fall back cleanly (that is itself the check), elsewhere it must render.
|
||||
bool optional = std::strstr(rel, "crt-geom") != nullptr;
|
||||
// Presets that need arrays of arrays (GL 4.3) must render where the context has
|
||||
// them, and elsewhere (macOS, GL 4.1) fail cleanly with that reason: the check
|
||||
// that keeps presetsNeedingArraysOfArrays() honest in both directions.
|
||||
bool optional = presetNeedsArraysOfArrays(rel);
|
||||
ShaderChain ex;
|
||||
ex.setLibrarySearchDirs({});
|
||||
ex.enabled = true;
|
||||
@@ -595,8 +616,14 @@ static int runGl() {
|
||||
}
|
||||
std::printf("bundled %s: %s (energy %ld)%s%s\n", rel, t && sum > 0 ? "renders" : "FAILS", sum,
|
||||
t ? "" : " -- ", t ? "" : (used ? ex.lastError().c_str() : e2.c_str()));
|
||||
if (optional && !t) CHECK(!ex.lastError().empty() && !ex.active(), "%s: failure must be clean", rel);
|
||||
else CHECK(t != 0 && sum > 0, "%s did not render a picture", rel);
|
||||
if (optional && !ex.glHasArraysOfArrays()) {
|
||||
CHECK(t == 0 && !ex.active(), "%s rendered on GL %s: drop it from the arrays-of-arrays list", rel,
|
||||
ex.glVersionText().c_str());
|
||||
CHECK(ex.lastError().find("needs OpenGL 4.3") != std::string::npos,
|
||||
"%s: expected the arrays-of-arrays explanation, got: %s", rel, ex.lastError().c_str());
|
||||
} else {
|
||||
CHECK(t != 0 && sum > 0, "%s did not render a picture", rel);
|
||||
}
|
||||
ex.destroyGL();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user