ofxGgmlStableDiffusion is an openFrameworks addon for generating images and videos from text or other images using stable diffusion techniques.
README
ofxGgmlStableDiffusion
ofxGgmlStableDiffusion is an openFrameworks addon that wraps stable-diffusion.cpp for text-to-image, image-to-image, image-to-video, and upscaling workflows.This addon is seeded from the existing
ofxStableDiffusion implementation and keeps that wrapper-first stable-diffusion.cpp backend design, with the public addon name and API surface moved under the ofxGgmlStableDiffusion line.Current addon version:
1.0.2Staging Scope
This repo is the
ofxStableDiffusion addon lineage under the ofxGgml naming scheme. The base is ofxStableDiffusion as an addon: its wrapper shape, example flow, scripts, docs, and native runtime expectations. It is not a replacement backend and it is not based on ofxGgmlDiffusion.- Keep the proven
ofxStableDiffusionstructure first;stable-diffusion.cpp
- Keep wrapper APIs, examples, and docs close to
ofxStableDiffusionwhile the
- Treat
ofxGgmlDiffusion, GGUF GAN experiments, and unrelated model workflows
- Use
ofxGgmlCoreas the default ggml provider for ecosystem builds. A
-UseBundledGgml for compatibility testing and bisecting.Requirements
- openFrameworks: 0.11.0 or later (tested with 0.12.0)
- C++ Standard: C++17 or later
- Platform Support: Windows (x64), Linux (x64), macOS (experimental Metal support)
- typed request/config/result objects
- background-thread generation
- owned image/video outputs
- explicit native-library staging
- wrapper-level callback seams for optional companion-addon scoring
- lightweight unit tests for the new video helper layer
Highlights
- Text-to-image and image-to-image generation
- Native image modes:
TextToImage,ImageToImage, andInpainting - Best-of-N image reranking through a callback seam that can be driven by an
- Image-to-video generation with
Standard,Loop,PingPong, andBoomerangpresentation modes ofxGgmlStableDiffusionRealtimeVideoSessionfor live creative-loop previews using prompt coalescing, low-step frames, and optional previous-frame img2img feedback- ESRGAN upscaling support
- Progress callbacks for diffusion steps
getCapabilities()plus capability/model-family helpers for UI gating and backend-aware workflows- parameter-tuning helpers for image/video defaults, ranges, and clamping by model family
- Optional
ofxGgmlStableDiffusionHoloscanBridgescaffold for liveframe -> conditioning -> diffusion -> previewpipelines, with a native Holoscan runtime path on Linux and a clean fallback path when Holoscan is not installed or the platform is not supported yet ofxGgmlStableDiffusionVideoWorkflowHelpers.hfor reusable video-generation presets, request validation, and richer render-manifest export on top of the existing request/result layer- Legacy entry points still available for wrapper-level migration
- Core-backed ggml runtime management by default, with bundled ggml fallback staging
Repo Layout
src/
src/core/
src/video/
FastPreview,LowVram,Balanced,Quality, andBatchStoryboardpresets- preflight request validation before a longer render starts
- richer JSON render manifests that capture prompt, dimensions, fps, seed, LoRAs, and clip summary data
libs/stable-diffusion/
scripts/
tests/
docs/
Feature Readiness
| Surface | Status | Notes |
|---|---|---|
Typed image generation (generate) | Stable | Primary production-ready wrapper API |
Typed video generation (generateVideo) | Stable | Includes owned frame results and metadata export |
| Legacy compatibility entry points | Supported | Kept for migration; new work should prefer typed requests |
ofxGgmlStableDiffusionRealtimeSession | Stable | Call update() every frame; callbacks fire from update() |
ofxGgmlStableDiffusionRealtimeVideoSession | Experimental | Useful preview workflow, but still evolving |
ofxGgmlStableDiffusionCreativeWorkflow | Experimental | Unifies realtime preview with queued higher-quality image/video renders and session snapshots |
ofxGgmlStableDiffusionHoloscanBridge | Experimental | Linux-first runtime path with fallback behavior elsewhere |
ofxGgmlStableDiffusionBatchProcessor | Experimental | Runs generator-backed grids, sweeps, A/B comparisons, and batch artifact export |
Features
- stable-diffusion.cpp wrapper lane
- typed image generation requests
- image-to-video helper workflows
- ofxGgmlCore ggml provider by default
- bundled ggml fallback runtime staging
- local wrapper validation entrypoint
Threading Contract
ofxGgmlStableDiffusionis internally synchronized unless a method explicitly documents borrowed-pointer or blocking behavior.generate(),generateVideo(),configureContext(), andsetUpscalerSettings()may be called from any thread, but only one long-running task can run at a time.isGenerating(),isBusy(),requestCancellation(),isCancellationRequested(), and copied-result accessors (getLastResult(),getImages(),getVideoClip()) are safe to query from any thread.getImagePixels(),getVideoFramePixels(), andreturnImages()expose borrowed buffers; use them only with care and never cache the returned pointers across generations.setProgressCallback()andsetImageRankCallback()run on the worker thread. Keep those callbacks lightweight and defer UI / texture updates to your mainupdate()/draw()flow.ofxGgmlStableDiffusionRealtimeSessionandofxGgmlStableDiffusionRealtimeVideoSessiondispatch their callbacks from whichever thread callsupdate().
API Shape
The addon now supports two layers of use:
Modern typed wrapper API
#include "ofxGgmlStableDiffusion.h"
ofxGgmlStableDiffusion sd;
ofxGgmlStableDiffusionContextSettings context;
context.modelPath = "data/models/sd/sd_turbo.safetensors";
context.nThreads = 8;
context.weightType = SD_TYPE_COUNT;
// Optional: let the current native runtime fit the model to available GPUs.
// A negative maxVram value keeps that many GiB free per selected device.
context.maxVram = "-1";
context.autoFit = true;
context.streamLayers = true;
sd.configureContext(context);
ofxGgmlStableDiffusionImageRequest request;
request.prompt = "cinematic portrait, rim lighting";
request.width = 512;
request.height = 512;
request.sampleSteps = 20;
sd.generate(request);
// Optional: apply a stack of LoRA/LoCon adapters per request
ofxGgmlStableDiffusionLora loraA;
loraA.path = "data/loras/edge.safetensors";
loraA.strength = 0.7f;
request.loras = {loraA};
sd.generate(request);
// Update the active LoRA stack globally
sd.setLoras({loraA});
// Hot-reload textual-inversion embeddings (reloads the context)
sd.reloadEmbeddings("data/embeddings");
// List currently discoverable embeddings (name, absolute path)
const auto embeddings = sd.listEmbeddings();
// Inspect what the currently configured model/runtime can actually do
const auto capabilities = sd.getCapabilities();
// Save generated video frames plus a JSON sidecar with per-frame prompts/seeds
sd.saveVideoFramesWithMetadata("output/storyboard", "shot");The parameter-tuning helpers can also be used outside the wrapper instance when you want model-family-aware defaults before building a request:
const auto imageProfile =
ofxGgmlStableDiffusionParameterTuningHelpers::resolveImageProfile(
context,
ofxGgmlStableDiffusionImageMode::ImageToImage);
request.cfgScale = imageProfile.defaultCfgScale;
request.sampleSteps = imageProfile.defaultSampleSteps;
request.strength = imageProfile.defaultStrength;Error Handling
The addon provides advanced error handling with error codes, messages, suggestions, and history tracking:
ofxGgmlStableDiffusionImageRequest request;
request.width = 513; // Invalid: not a multiple of 64
request.height = 512;
request.batchCount = 20; // Invalid: exceeds maximum of 16
sd.generate(request);
// Check for errors programmatically
if (sd.getLastErrorCode() != ofxGgmlStableDiffusionErrorCode::None) {
// Get detailed error information
const auto& errorInfo = sd.getLastErrorInfo();
ofLogError() << "Error: " << errorInfo.message;
ofLogError() << "Suggestion: " << errorInfo.suggestion;
ofLogError() << "Error code: " << ofxGgmlStableDiffusionErrorCodeLabel(errorInfo.code);
}
// Or use the simple string-based error (backward compatible)
if (!sd.getLastError().empty()) {
ofLogError() << "Error: " << sd.getLastError();
}
// Access error history for debugging
const auto& history = sd.getErrorHistory();
for (const auto& error : history) {
ofLogNotice() << "[" << error.timestampMicros << "] "
<< error.message << " -> " << error.suggestion;
}
// Clear error history
sd.clearErrorHistory();Available Error Codes:
None- No errorModelNotFound- Model file not foundModelCorrupted- Model file corruptedModelLoadFailed- Model loading failedOutOfMemory- Insufficient memoryInvalidDimensions- Invalid width/heightInvalidBatchCount- Invalid batch countInvalidFrameCount- Invalid video frame countMissingInputImage- Input image required but not providedGenerationFailed- Generation process failedThreadBusy- Another task is runningUpscaleFailed- Upscaling failedUnknown- Unknown error
Legacy compatibility API
The older
newSdCtx, txt2img, img2img, and img2vid entry points are still available so existing call sites, including addon-to-addon bridges, do not have to migrate immediately.Realtime Creative Loop
ofxGgmlStableDiffusionRealtimeVideoSession adapts the interaction pattern of realtime video tools to the local stable-diffusion.cpp backend. It keeps only the latest prompt while a frame is generating, uses low-step preview requests for responsiveness, can refine an idle prompt with a higher step budget, and can feed the previous output frame back through img2img for visual continuity.#include "ofxGgmlStableDiffusion.h"
ofxGgmlStableDiffusionRealtimeVideoSettings liveSettings;
liveSettings.previewSteps = 4;
liveSettings.refineSteps = 16;
liveSettings.previewStrength = 0.68f;
liveSettings.refineStrength = 0.35f;
liveSettings.usePreviousFrameFeedback = true;
ofxGgmlStableDiffusionRealtimeVideoSession liveVideo;
liveVideo.start(liveSettings, sd);
ofxGgmlStableDiffusionRealtimeVideoRequest liveRequest;
liveRequest.prompt = "a fashion portrait reacting to colored stage lights";
liveVideo.submit(liveRequest);
// In update():
liveVideo.update();This is a near-realtime preview workflow, not a proprietary HD world model. For best results use fast models, modest preview dimensions, and low preview step counts.
Video Behavior
Video generation returns owned frames through
ofxGgmlStableDiffusionVideoClip.Animated requests can also drive per-frame prompt interpolation, parameter animation, and seed sequencing through
ofxGgmlStableDiffusionVideoRequest::animationSettings. Each returned frame now carries the prompt / negative prompt / CFG / strength / seed values that were actually used, and clips can export both PNG sequences and JSON metadata.Useful video-export helpers:
ofxGgmlStableDiffusionVideoClip::saveMetadataJson(...)ofxGgmlStableDiffusionVideoClip::saveFrameSequenceWithMetadata(...)ofxGgmlStableDiffusionVideoClip::saveWebm(...)ofxGgmlStableDiffusion::saveVideoMetadata(...)ofxGgmlStableDiffusion::saveVideoFramesWithMetadata(...)
saveWebm(...) accepts .webp, .webm, and .avi paths. .webp/.webm use the upstream stable-diffusion.cpp media writer when the rebuilt runtime stages its media libraries; .avi remains a small local MJPEG fallback for compatibility and debugging.Long video generation (chunked rendering):
ofxGgmlStableDiffusionLongVideoManifest+ofxGgmlStableDiffusionLongVideoChunk
ofxGgmlStableDiffusion::renderLongVideo(...)renders all chunks sequentially,
metadata.json.Supported playback/presentation modes:
Standard
Loop
PingPong
Boomerang
This is especially useful when a UI layer or another addon wants a more natural preview clip without re-asking the native runtime for more frames.
Video Performance Recommendations
- Use fast models for previews: Prefer LCM/Turbo-style checkpoints with 4-8 steps for quick iteration; only re-run hero frames at higher quality.
- Quantize when possible: F16 or Q8/Q5 levels often cut VRAM use by 50-75% and speed up inference, enabling higher resolutions or longer clips without swapping.
- Keep the model warm and resident: Reuse the same loaded context via the model manager; avoid swapping checkpoints mid-run and consider a throwaway warmup frame to eliminate first-run latency.
- Minimize unique frames: Generate the smallest necessary source frame count, then stretch duration with
PingPong,Boomerang, orLoopplayback instead of regenerating. - Right-size resolution, fps, and steps: Lower resolution and fps where acceptable; clamp
sampleStepsto ~15-25 for finals (lower for previews). Per-frame time scales directly with unique frames. - Trim preview I/O: Skip metadata/JSON exports on preview passes; save sidecar files only on final renders to avoid extra disk churn.
Coding Conventions (openFrameworks-aligned)
- Use tabs (as in existing headers) with K&R braces; lowerCamelCase for functions/members and PascalCase for types, keeping the
ofxGgmlStableDiffusionprefix for public types. - Include order:
ofMain.h, then addon headers, then STL/system headers. - Document public API with
///Doxygen-style comments; avoid mixing//for header docs. - Avoid exceptions; return
boolor error codes and populateofxGgmlStableDiffusionErrorfor failures. - Keep small helpers inline in headers; move heavier logic to
.cppfiles to limit inline bloat. - Prefer RAII and STL containers over raw
new/delete. - Keep UI, textures, and other openFrameworks rendering objects on the main thread; worker-thread callbacks should only capture/copy lightweight state.
- Favor openFrameworks core types at the API edge:
ofPixels/ofImagefor images,ofJsonfor metadata,ofVec*/ofFloatColor/ofRectanglewhere geometry or color is needed. Convert to STL or native structs internally only when necessary for performance or binding.
Image Modes
The typed image request layer exposes the native image modes:
TextToImageImageToImageInpainting
CLIP-Rerank Integration
ofxGgmlStableDiffusion now exposes a wrapper-level image ranking callback for Best-of-N workflows.That is the intended integration point for companion-addon CLIP scoring:
- generate a batch in
ofxGgmlStableDiffusion - score the outputs with a CLIP runtime owned by another addon or app layer
- rerank or collapse to the best image without sharing native runtimes
Companion Addon Integration
- use
ofxGgmlCoreas the shared ggml/runtime base for companion addons - integrate companion addons with it through the addon API
- keep model-specific workflow logic in companion addons, not in Core
- Core gives the ecosystem one managed ggml provider by default
- wrapper-level integration keeps model behavior inside the stable-diffusion lane
- the bundled ggml fallback remains available if a stable-diffusion.cpp update
More detail: docs/ARCHITECTURE.md, docs/STAGING_PLAN.md
Native Runtime
The addon stages native artifacts into addon-local paths:
libs/stable-diffusion/includelibs/stable-diffusion/lib/vslibs/stable-diffusion/lib/Linux64libs/ggml/include(bundled fallback builds only)libs/ggml/lib/vs(bundled fallback builds only)libs/variants/<backend>/...
scripts/build-stable-diffusion.ps1scripts/build-stable-diffusion.batscripts/build-stable-diffusion.shscripts/download-stable-diffusion-release.ps1scripts/setup_addon.ps1scripts/setup_windows.bat
Backend flags now follow the same style as
ofxGgml, but the build selects one backend per build:--cpu,--cpu-only/-CpuOnly
--gpu,--cuda/-Cuda
--cuda-architectures LIST/-CudaArchitectures LIST
86 for RTX 3090); OFXGGML_CUDA_ARCHITECTURES is supported too --vulkan/-Vulkan
--metal/-Metal
--all/-All
cuda, then vulkan, then cpu-onlyEach backend build is also snapshotted under
libs/variants/<backend>, so you can switch the canonical addon runtime later without rebuilding everything.Variant selector helpers:
scripts/select-stable-diffusion-backend.ps1 -Backend cudascripts/setup_windows.bat --skip-native --select-backend cuda
scripts/setup_windows.bat and scripts/setup_addon.ps1 refresh the vendored source from the configured release-tag snapshot, then build the native runtime locally.--source-release-tag TAG
When an upstream Windows zip does not include
stable-diffusion.lib, the addon setup script synthesizes the Visual Studio import library from the downloaded DLL exports automatically.Current Native Source Status
Default Core-compatible rebuilds use this upstream
stable-diffusion.cpp pin:- upstream repo:
https://github.com/leejet/stable-diffusion.cpp - upstream release tag:
master-813-bfbef5b - upstream commit:
bfbef5b7e64e89a0205894853de25d19a7ba54b9 - vendored on:
2026-08-10
master-820-de298c2 (commit de298c225bed97c3f9026b73cd7b71e7879bd41b, published 2026-08-12) was verified on Windows with CUDA and model-backed SD-Turbo inference on 2026-08-13. It uses diffusion-fork-only ggml INT8 ConvRot APIs that are not in Core's official ggml, so select it explicitly with -UseBundledGgml -SourceReleaseTag master-820-de298c2. The default stays on master-813-bfbef5b for the shared Core/system ggml lane. Source: stable-diffusion.cpp releasesThe addon now includes the upstream header directly through
libs/stable-diffusion/include/stable-diffusion.h, without re-exporting the older enum aliases. Addon code should use the current upstream names such as EULER_A_SAMPLE_METHOD, `
ofxAddons