Donner SVG 0.8.0-pre
SVG editor and embeddable C⁠+⁠+⁠20 engine.
Loading...
Searching...
No Matches
geode_embed.cc

Minimal windowed host for Geode's embedded mode.

Minimal windowed host for Geode's embedded mode. Loads an SVG from disk, opens a fixed-size GLFW window, and renders the document into the swap-chain texture on every frame via the Geode embedding API:

  • The host creates a wgpu::Instance, selects an adapter, creates a wgpu::Device, and configures the window surface itself.
  • GeodeDevice::CreateFromExternal(config) wraps the host's device/queue/format without taking ownership of the underlying WebGPU objects.
  • RendererGeode::setTargetTexture(surfaceTex) points the renderer at the current swap-chain texture; draw(document) issues work into the host's device/queue; wgpuSurfacePresent ships the frame.

Intentionally minimal: no input handling, no resize, no DPI scaling. The goal is a clean walkthrough of the embedding boundary for the docs/guides/embedding_geode.md guide.

To run:

bazel run --config=geode //examples:geode_embed -- donner_splash.svg
/**
* @example geode_embed.cc Minimal windowed host for Geode's embedded mode.
*
* Loads an SVG from disk, opens a fixed-size GLFW window, and renders the
* document into the swap-chain texture on every frame via the Geode
* embedding API:
*
* - The host creates a `wgpu::Instance`, selects an adapter, creates a
* `wgpu::Device`, and configures the window surface itself.
* - `GeodeDevice::CreateFromExternal(config)` wraps the host's
* device/queue/format without taking ownership of the underlying
* WebGPU objects.
* - `RendererGeode::setTargetTexture(surfaceTex)` points the renderer at
* the current swap-chain texture; `draw(document)` issues work into the
* host's device/queue; `wgpuSurfacePresent` ships the frame.
*
* Intentionally minimal: no input handling, no resize, no DPI scaling. The
* goal is a clean walkthrough of the embedding boundary for the
* `docs/guides/embedding_geode.md` guide.
*
* To run:
*
* ```sh
* bazel run --config=geode //examples:geode_embed -- donner_splash.svg
* ```
*/
// Intentionally ordered: donner + webgpu-cpp headers first so their class
// names (notably `wgpu::Status`) are fully declared before any platform
// native header can `#define` colliding macros. The GLFW native-surface
// extraction lives in separate TUs (`geode_embed_surface_linux.cc`,
// `geode_embed_surface_macos.mm`) so X11 / Cocoa macros never leak into
// donner headers.
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <webgpu/webgpu.hpp>
#include "donner/svg/SVG.h"
extern "C" {
#include "GLFW/glfw3.h"
}
namespace {
constexpr int kWindowWidth = 800;
constexpr int kWindowHeight = 600;
void GlfwErrorCallback(int error, const char* description) {
std::fprintf(stderr, "GLFW error %d: %s\n", error, description);
}
std::string LoadFile(const char* path) {
auto result =
if (const auto* contents = std::get_if<std::string>(&result)) {
return *contents;
}
return {};
}
/// Pick a surface format. The first entry in `formats` is the adapter's
/// preferred format; we accept it if it is one of the two SRGB-less 8-bit
/// formats Geode pipelines support, otherwise fall back to `BGRA8Unorm`
/// which every desktop backend lists.
wgpu::TextureFormat ChooseSurfaceFormat(const wgpu::SurfaceCapabilities& caps) {
for (size_t i = 0; i < caps.formatCount; ++i) {
const auto f = caps.formats[i];
if (f == WGPUTextureFormat_BGRA8Unorm || f == WGPUTextureFormat_RGBA8Unorm) {
return wgpu::TextureFormat{f};
}
}
return wgpu::TextureFormat::BGRA8Unorm;
}
} // namespace
int main(int argc, char* argv[]) {
if (const char* bwd = std::getenv("BUILD_WORKING_DIRECTORY")) {
std::filesystem::current_path(bwd);
}
if (argc != 2) {
std::fprintf(stderr, "USAGE: geode_embed <svg-file>\n");
return 1;
}
// --- Parse the SVG once up front ---
const std::string svgData = LoadFile(argv[1]);
if (svgData.empty()) {
const std::string safePath = donner::EscapeTerminalText(argv[1]);
std::fprintf(stderr, "Failed to open or empty SVG: %s\n", safePath.c_str());
return 1;
}
auto maybeDocument = donner::svg::parser::SVGParser::ParseSVG(svgData, warnings);
if (maybeDocument.hasError()) {
std::ostringstream diagnostic;
diagnostic << maybeDocument.error();
std::cerr << "SVG parse error: " << donner::EscapeTerminalText(diagnostic.str()) << "\n";
return 1;
}
donner::svg::SVGDocument document = std::move(maybeDocument.result());
document.setCanvasSize(kWindowWidth, kWindowHeight);
// --- GLFW window (no GL context; WebGPU drives the surface directly) ---
glfwSetErrorCallback(GlfwErrorCallback);
if (!glfwInit()) {
std::fprintf(stderr, "glfwInit failed\n");
return 1;
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
GLFWwindow* window =
glfwCreateWindow(kWindowWidth, kWindowHeight, "Donner Geode Embed", nullptr, nullptr);
if (window == nullptr) {
std::fprintf(stderr, "glfwCreateWindow failed\n");
glfwTerminate();
return 1;
}
// --- WebGPU: instance / adapter / device on the HOST side ---
wgpu::Instance instance = wgpu::createInstance();
if (!instance) {
std::fprintf(stderr, "wgpuCreateInstance returned null\n");
glfwDestroyWindow(window);
glfwTerminate();
return 1;
}
wgpu::Surface surface = donner::example::CreateSurfaceFromGlfwWindow(instance, window);
if (!surface) {
std::fprintf(stderr, "Failed to create WebGPU surface from GLFW window\n");
glfwDestroyWindow(window);
glfwTerminate();
return 1;
}
// wgpu-native's sync `requestAdapter` and `requestDevice` internally loop on
// the callback-based C API; see `GeodeDevice.cc` for the same pattern used
// for the headless path. We rely on those wrappers here to keep the host
// code compact.
wgpu::RequestAdapterOptions adapterOptions = {};
adapterOptions.compatibleSurface = surface;
wgpu::Adapter adapter = instance.requestAdapter(adapterOptions);
if (!adapter) {
std::fprintf(stderr, "No WebGPU adapter available.\n");
return 1;
}
wgpu::DeviceDescriptor deviceDesc = {};
deviceDesc.label = wgpu::StringView{std::string_view{"GeodeEmbedDevice"}};
wgpu::Device device = adapter.requestDevice(deviceDesc);
if (!device) {
std::fprintf(stderr, "Failed to create WebGPU device.\n");
return 1;
}
wgpu::Queue queue = device.getQueue();
// --- Surface configuration ---
wgpu::SurfaceCapabilities caps;
surface.getCapabilities(adapter, &caps);
const wgpu::TextureFormat surfaceFormat = ChooseSurfaceFormat(caps);
wgpu::SurfaceConfiguration surfaceConfig(wgpu::Default);
surfaceConfig.device = device;
surfaceConfig.format = surfaceFormat;
surfaceConfig.usage = wgpu::TextureUsage::RenderAttachment;
surfaceConfig.width = kWindowWidth;
surfaceConfig.height = kWindowHeight;
surfaceConfig.presentMode = wgpu::PresentMode::Fifo;
if (caps.alphaModeCount > 0) {
surfaceConfig.alphaMode = caps.alphaModes[0];
} else {
surfaceConfig.alphaMode = wgpu::CompositeAlphaMode::Auto;
}
surface.configure(surfaceConfig);
caps.freeMembers();
// --- Geode: wrap host device via the embedding API (non-owning) ---
embedConfig.instance = instance;
embedConfig.device = device;
embedConfig.queue = queue;
embedConfig.adapter = adapter;
embedConfig.textureFormat = surfaceFormat;
// `shared_ptr` so the constructed renderer can share ownership. The wrapper
// itself does NOT own the underlying wgpu handles - the locals above retain
// that responsibility.
std::shared_ptr<donner::geode::GeodeDevice> geodeDevice =
if (!geodeDevice) {
std::fprintf(stderr, "GeodeDevice::CreateFromExternal failed\n");
return 1;
}
// Scope the renderer so its destructor runs while the host's wgpu::Device
// is still alive, before we unconfigure the surface and tear down GLFW.
{
donner::svg::RendererGeode renderer(geodeDevice);
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
wgpu::SurfaceTexture surfaceTex;
surface.getCurrentTexture(&surfaceTex);
// wgpu-native distinguishes fully-fresh (`SuccessOptimal`) from
// stale-but-usable (`SuccessSuboptimal`) textures. Both are safe to
// render into; anything else means the swap chain needs reconfiguring
// (window minimized, display resumed) or the device is gone. For this
// minimal example we simply skip the frame.
if (surfaceTex.status != WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal &&
surfaceTex.status != WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal) {
wgpu::Texture(surfaceTex.texture));
continue;
}
donner::geode::ScopedWgpuHandle<wgpu::Texture> target(wgpu::Texture(surfaceTex.texture));
renderer.setTargetTexture(target.get());
renderer.draw(document);
renderer.clearTargetTexture();
surface.present();
}
}
geodeDevice.reset();
surface.unconfigure();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
RAII wrapper around a WebGPU device - headless or host-provided.
Small utility shims on top of the wgpu:: C++ wrapper (from eliemichel/WebGPU-distribution's webgpu....
Geode (WebGPU/Slug) implementation of donner::svg::RendererInterface.
Collects parse warnings during parsing.
Definition ParseWarningSink.h:29
static std::unique_ptr< GeodeDevice > CreateFromExternal(const GeodeEmbedConfig &config)
Create a GeodeDevice wrapping a host-provided device and queue.
Move-only RAII owner for a single WebGPU handle.
Definition GeodeWgpuUtil.h:104
Geode rendering backend - GPU-native via WebGPU + the Slug algorithm.
Definition RendererGeode.h:213
Represents a parsed SVG document containing a tree of SVGElement nodes.
Definition SVGDocument.h:58
void setCanvasSize(int width, int height)
Set the canvas (output image) size to a fixed width and height, in pixels.
static ParseResult< SVGDocument > ParseSVG(std::string_view source, ParseWarningSink &warningSink, Options options={}, SVGDocument::Settings settings={}) noexcept
Parses an SVG XML document from a string (typically the contents of a .svg file).
static constexpr size_t kDefaultMaximumInputSize
Default maximum number of source or expanded SVG bytes accepted from untrusted input.
Definition SVGParser.h:20
Platform-native WebGPU surface creation helper for the geode_embed example.
wgpu::Surface CreateSurfaceFromGlfwWindow(const wgpu::Instance &instance, GLFWwindow *window)
Create a WebGPU surface backed by the platform-native handle of window.
Definition geode_embed_surface_linux.cc:27
std::string EscapeTerminalText(std::string_view text, bool preserveNewlines=false)
Escape untrusted bytes and terminal format controls while preserving printable UTF-8.
Definition TerminalEscape.h:13
FileReadResult ReadFileBounded(const std::filesystem::path &path, size_t maximumSize)
Open and read a regular file without allocating more than maximumSize bytes.
Configuration for embedding Geode into a host application that already owns a WebGPU device.
Definition GeodeDevice.h:91
wgpu::Instance instance
Optional host-provided WebGPU instance. Browser embedders should provide it so synchronous snapshot r...
Definition GeodeDevice.h:95
wgpu::Device device
Host-provided WebGPU device. Must not be null.
Definition GeodeDevice.h:98
wgpu::Queue queue
Host-provided queue associated with device. Must not be null.
Definition GeodeDevice.h:101
wgpu::Adapter adapter
Optional adapter handle. Preserved for hosts that need to query the adapter associated with the exter...
Definition GeodeDevice.h:109
wgpu::TextureFormat textureFormat
Texture format for render targets. Must match the format of any texture passed to RendererGeode::setT...
Definition GeodeDevice.h:105