Skip to content

Troubleshooting & FAQ

Common issues when integrating WhatsCanvas, and how to fix them. Most stem from the library's core contract: it renders; it does not own your window, GL context, or file I/O.

Rendering

My image draws as a solid black rectangle

drawImage tints the image by the paint's color, and the default Paint color is black — so a default paint renders the image black. Use white to draw the image untouched:

wsc::Paint tint;
tint.setColor(wsc::Color::WHITE);   // original image, no tint
canvas->drawImage(image, x, y, tint);

Nothing renders (or a crash) with the OpenGL backend

The OpenGL backend never creates a window or GL context — your app (or GLFW/SDL/Qt) does. The required order is:

  1. Create a GL context and make it current.
  2. wsc::Canvas::loadOpenGL(loader) — hand over your platform's proc loader.
  3. canvas.setSize(w, h) then canvas.initializeContext().
  4. canvas.beginFrame() → draw → canvas.endFrame() with the context current.

If you skip step 1–2 or draw without a current context, GL calls fail. The software and Vulkan backends (Canvas::create(Backend::Software, ...) / Canvas::create(Backend::Vulkan, ...)) need none of this — call beginFrame() to initialize them lazily before drawing.

Colors look washed out / semi-transparent blends look wrong

By default WhatsCanvas blends in straight sRGB space. For physically-correct (linear-space) alpha blending, enable gamma correction:

wsc::Canvas::setGammaCorrect(true);

The software backend mirrors the GL behaviour exactly, so output matches across backends.

My exported PNG is upside-down

readPixelsRGBA already returns top-left-origin rows. If your image writer also flips vertically (e.g. stbi_flip_vertically_on_write(1)), you get a double flip. Don't flip again.

readPixelsRGBA returns all zeros (black / empty image)

On the Software backend's normal framebuffer path, a common cause is a second endFrame() after you already ended the frame. endFrame() consumes the recorded commands, so the second call has nothing to draw while Software clears that framebuffer to transparent. Vulkan, OpenGL, and a render-target canvas created with OffscreenTexture() retain their existing target contents on an empty submission, but the extra call is still a lifecycle error.

The correct offscreen sequence is:

canvas->beginFrame();               // initializes lazily; resets queued frame state
canvas->drawRect(/* ... */, paint);
canvas->endFrame();                 // <- exactly once, right before reading
canvas->readPixelsRGBA(pixels);     // or savePixelsPPM("out.ppm")

Call endFrame() exactly once per frame, and do not call beginFrame() after drawing (it discards queued commands before submission). Also confirm you actually drew inside the canvas bounds with a non-transparent paint color. OpenGL does not implicitly clear a host-owned framebuffer; clear it explicitly when a fresh background is required.

Text

Text doesn't appear

Check, in order:

  1. Paint color — a default (black) paint on a dark background is invisible.
  2. Font — for deterministic output, register a font and set the family:
    canvas->registerFontFace(wsc::FontFace::fromFile(wsc::FontDescriptor("Inter"),
                                                     "assets/fonts/Inter-Regular.ttf"));
    wsc::Paint p; p.setFontFamily("Inter"); p.setTextSize(24.0f);
    
  3. PositiondrawText(text, x, y, paint) places text at a baseline; make sure it is within the canvas.

Glyphs are missing or fall back to boxes

Register a fallback chain so mixed scripts resolve to the right face:

wsc::FontFallbackChain chain("Inter");
chain.addFallbackFamily("Noto Sans CJK");
canvas->setFontFallbackChain(chain);

FreeType and HarfBuzz are enabled by default (-DWHATSCANVAS_ENABLE_FREETYPE_RASTERIZER=ON, -DWHATSCANVAS_ENABLE_OPENTYPE_SHAPING=ON). When explicitly disabled or not found, WhatsCanvas falls back to stb_truetype and simple shaping and reports it in the text-backend diagnostics. The standalone Software target always uses that built-in fallback stack.

Backends

How do I know if Vulkan is available? / graceful fallback

Vulkan is opt-in at build time (-DWHATSCANVAS_ENABLE_VULKAN=ON + a Vulkan SDK) and compiles into WhatsCanvas::OpenGL (there is no separate ::Vulkan target). Probe at runtime and fall back:

using Backend = wsc::Canvas::Backend;
std::unique_ptr<wsc::Canvas> canvas =
    wsc::Canvas::isBackendAvailable(Backend::Vulkan)
        ? wsc::Canvas::create(Backend::Vulkan, w, h)
        : wsc::Canvas::create(Backend::Software, w, h);

The Vulkan backend renders off-screen by default. On Win32 it can also use OutputTarget::ToWindow(...) + present(); for portable/headless usage, read the result with readPixelsRGBA.

Build the standalone software target:

cmake -S . -B build -DWHATSCANVAS_BUILD_OPENGL=OFF -DWHATSCANVAS_BUILD_SOFTWARE=ON
target_link_libraries(MyApp PRIVATE WhatsCanvas::Software)

Context loss on mobile (Android background)

On GL context loss, release and re-initialize:

canvas.releaseResources();
// ... after the platform re-creates the context and makes it current ...
canvas.initializeContext();

present() returns false / nothing shows in my window

On-screen presentation is platform-dependent. Supported today: software (Windows GDI + Linux X11), OpenGL (WGL; GLX on Linux), and Vulkan (Windows). Checklist:

  • Call setOutputTarget(OutputTarget::ToWindow(surface)) and check its return value — it is false when presentation is unsupported for the current backend/platform or the surface has no window handle. Fall back accordingly (e.g. glfwSwapBuffers for GL, or off-screen + readPixelsRGBA).
  • Initialize the backend before configuring a window or external target: initializeContext() is required for Vulkan and for the OpenGL renderer after loadOpenGL(...); it is harmless but optional for Software.
  • Fill the surface correctly: platform = NativeSurface::Platform::Win32 and window = <HWND> (e.g. glfwGetWin32Window(window)).
  • Create the window without a GL context for the software or Vulkan backend (glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API)); for the OpenGL backend, make the GL context current and call Canvas::loadOpenGL first.
  • Present each frame after endFrame(): beginFrame → draw → endFrame → present.
  • If you include <windows.h> (or a native GLFW header) in the same file, include the wsc/ headers first and define NOMINMAX, so the min/max macros do not break WhatsCanvas headers.

See the software_present, gl_present, and vulkan_canvas_present examples for working setups.

Build & packaging

find_package(WhatsCanvas ...) can't be found

  • Build a package first (build.bat --release --package --no-run) or download a release archive, then point CMake at it: cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/package.
  • Use the exact version: find_package(WhatsCanvas 0.2.0 CONFIG REQUIRED).

Do consumers need GLFW / GLAD / GLM?

No. GLFW is only for the in-repo example windows, GLAD is compiled into the GL-family backend, and GLM is an internal math dependency. Consumers include and link only WhatsCanvas::OpenGL (or ::Software / ::OpenGLES) and include/wsc/.

Windows: LNK2019 unresolved external symbol __std_min_element_f_ (or __std_max_element_f_)

This is an MSVC standard-library (STL) toolset-version mismatch when linking a prebuilt binary. Newer MSVC toolsets dispatch std::min_element / max_element (etc.) on trivial types to out-of-line, ABI-versioned SIMD helpers whose symbols older STL runtimes do not provide, so a library compiled with a newer toolset fails to link on an older Visual Studio.

WhatsCanvas builds its own binaries with _USE_STD_VECTOR_ALGORITHMS=0 (scalar path), so the shipped libraries do not reference these version-specific symbols and link against any VS 2022 STL. If you still hit this — e.g. building WhatsCanvas yourself, or linking another prebuilt library — fix it by any of:

  • Update Visual Studio 2022 so your toolset is at least as new as the one that built the binary (VS Installer -> Update), then rebuild.
  • Build the offending library from source with your own toolset (identical STL on both sides).
  • When building a library for redistribution, compile it with -D_USE_STD_VECTOR_ALGORITHMS=0 (MSVC) so its objects avoid the versioned helpers.

There is no way to "use a different STL" here: on MSVC the standard library is the MSVC STL. The mismatch is a general C++ binary-compatibility (ABI) issue, not specific to WhatsCanvas.

Diagnostics & logging

WhatsCanvas has a built-in logging facility (wsc/Log.h) that reports recoverable problems and failures. By default only Warning and Error messages are written to stderr.

See more detail while debugging

Lower the threshold to surface informational and debug messages:

#include <wsc/Log.h>

wsc::Log::setLevel(wsc::LogLevel::Debug); // Trace/Debug/Info/Warning/Error

Route logs into your own system

Install a handler to forward every message (level, category, text) wherever you want — a file, an in-game console, spdlog, etc.:

wsc::Log::setHandler([](const wsc::LogMessage &m) {
    myLogger.log(m.level, m.category, m.message);
});

Pass nullptr to restore the default stderr sink. Use wsc::Log::setLevel(wsc::LogLevel::Off) to silence all output.

Common messages

Category Meaning
Image An image failed to decode or its texture could not be created.
DrawValidation A draw call was skipped (empty vertices, bad dimensions, invalid resource).
GLProgram / OpenGL Shader compile/link failure or a GL error was detected.
RenderDeviceFactory No usable render backend was found.
VulkanRenderDevice Vulkan device/instance setup failed, or Vulkan is not compiled in.
Deprecation A deprecated API was called (emitted once per call site).

Still stuck?