OpenFrameworks Developer
Implements interactive creative applications using the OpenFrameworks C++ toolkit. Covers application lifecycle, addon management, rendering, event handling, and data visualization patterns for artists and developers building cross-platform projects on macOS, Linux, and Windows.
TL;DR Checklist
- Extend
ofAppbase class withsetup()/update()/draw()lifecycle methods - Register event handlers (
keyPressed,mouseMoved,newFrame) viaofEventsor method overrides - Use
ofParameterandofxGui/ofxImGuifor runtime-tunable configuration - Profile rendering with
ofGetElapsedTimef()and lock framerate withofSetVerticalSync(true) - Manage memory with value semantics — never raw
new/deleteon OF objects - Load all assets with
ofToDataPath()for cross-platform portability
When to Use
Use this skill when:
- Building interactive visual applications, generative art, or data visualization with OpenFrameworks
- Integrating OF addons (ofxImGui, ofxOpenCv, ofxAudioUnit) into an existing project
- Designing the application lifecycle and event handling architecture for a creative coding project
- Porting an OpenFrameworks app between macOS, Linux, and Windows
- Implementing real-time graphics rendering with optimized draw loops
When NOT to Use
Avoid this skill for:
- Game development with complex physics — use Godot or Unity instead (OF has no built-in physics engine)
- Web-based projects — use p5.js or the OpenFrameworks JS port via Emscripten instead of native C++
- Heavy machine learning pipelines — OF's ML is lightweight; for training, use PyTorch/TensorFlow and export to OF only for inference/rendering
Core Workflow
Scaffold Project Structure — Create a new OF project using
of_create_projector copy from the examples template. The main entry point is alwayssrc/ofApp.cppwithsrc/ofApp.h. Define member variables (ofImage,ofTrueTypeFont,ofParameter) as class members inofApp.h. Checkpoint: VerifyCMakeLists.txtlists all source files and linked addons before building.Implement Application Lifecycle — Override the three core methods in
ofApp.cpp:setup()for initialization,update()for frame-by-frame logic (physics, AI, data streaming),draw()for rendering only. Keepdraw()lightweight — offload expensive computation toupdate(). Checkpoint:setup()must complete before anydraw()call — all objects initialized there.Handle Events and Input — Register both method overrides (
keyPressed,mousePressed,mouseMoved,windowResized) and/or use the event system for decoupled communication:ofEvents().newFrame.addListener(this, &ofApp::onNewFrame);. For UI controls, integrate ofxGui or ofxImGui and bind to ofParameters. Checkpoint: Mouse coordinates are (0,0) at top-left — verify coordinate space before hit-testing.Rendering Pipeline — Use OF's built-in drawing primitives (
ofDrawCircle,ofDrawRectangle,ofDrawLine) for simple geometry. For custom shaders, useofShaderwith uniform management viashader.setUniform1f(). For sprite-based rendering, useofTextureandofImageloaded from disk or generated procedurally. Enable vsync and set desired frame rate withofSetVerticalSync(true)andofSetFrameRate(60). Checkpoint: Always callofClear()at the start ofdraw()— otherwise frames accumulate and create visual artifacts.Manage Addons and Dependencies — Add third-party addons by placing them in
libs/directory or usinggit submodule addfor addon repos. Update CMakeLists.txt withtarget_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ${ADDON_NAME}). For custom addon development, useof_create_addonfrom the OF tools. Checkpoint: Addons must declare their dependencies — circular addon dependencies cause build failures.
Implementation Patterns / Reference Guide
Pattern 1: Parameter-Driven Configuration (ofParameters + ofxGui)
Use ofParameter for runtime-tunable values with automatic GUI binding. This eliminates hard-coded constants and enables real-time experimentation by artists and designers.
// ofApp.h
#pragma once
#include "ofMain.h"
#include "ofxGui.h"
class ofApp : public ofBaseApp {
public:
void setup() override;
void update() override;
void draw() override;
// Parameters exposed to GUI — name, default, min, max
ofParameter<float> particleSpeed{"speed", 2.0f, 0.1f, 10.0f};
ofParameter<int> particleCount{"count", 500, 10, 5000};
ofParameter<bool> enableGravity{"gravity", true};
// UI panel — must be declared after parameters for proper binding order
ofxPanel gui;
private:
void bindParameters();
};
// ofApp.cpp
void ofApp::setup() {
ofSetVerticalSync(true);
ofSetFrameRate(60);
bindParameters();
}
void ofApp::bindParameters() {
gui.setup("Controls", particleSpeed, particleCount, enableGravity);
gui.setPosition(10, 10);
}
void ofApp::update() {
// Parameters are auto-updated by the GUI — just read them via .get()
float speed = particleSpeed.get();
int count = particleCount.get();
for (int i = 0; i < count; ++i) {
// ... update logic using speed
}
}
Pattern 2: Efficient Particle System (BAD vs. GOOD)
// ❌ BAD — Raw pointers, no encapsulation, draw calls inside data structs
struct Particle {
float x, y;
void update() { x += 1.0f; }
void draw() { ofDrawCircle(x, y, 3); } // Never draw from a data struct!
};
std::vector<Particle*> particles; // Memory leak waiting to happen
// ✅ GOOD — Value types, pure data separation, renderer lives in draw()
struct Particle {
glm::vec2 position{0.0f, 0.0f};
glm::vec2 velocity{0.0f, 0.0f};
float life = 1.0f;
float maxLife = 1.0f;
void update(float dt, bool gravity) {
position += velocity * dt;
if (gravity) velocity.y -= 9.8f * dt;
life -= dt;
}
bool isAlive() const { return life > 0.0f; }
};
class ParticleSystem {
public:
void update(float dt, float speed, bool gravity) {
for (auto& p : particles) {
p.update(dt * speed, gravity);
}
// Remove dead particles using erase-remove idiom
particles.erase(
std::remove_if(particles.begin(), particles.end(),
[](const Particle& p){ return !p.isAlive(); }),
particles.end());
}
void draw() const {
ofSetColor(255);
for (const auto& p : particles) {
float alpha = static_cast<int>(255.0f * (p.life / p.maxLife));
ofSetColor(255, alpha);
ofDrawCircle(p.position.x, p.position.y, 4.0f);
}
}
void emit(int count, glm::vec2 origin, float spread) {
for (int i = 0; i < count; ++i) {
Particle p;
p.position = origin + glm::vec2(ofRandom(-spread, spread), ofRandom(-spread, spread));
p.velocity = glm::vec2(ofRandom(-3.0f, 3.0f), ofRandom(-5.0f, -1.0f));
p.maxLife = ofRandom(1.0f, 4.0f);
p.life = p.maxLife;
particles.push_back(std::move(p));
}
}
private:
std::vector<Particle> particles; // Value semantics — no heap fragmentation
};
Pattern 3: Event-Driven Communication (Decoupled Systems)
Use OF's event system instead of direct method calls between loosely coupled components. This enables plug-and-play architectures where producers and consumers are unaware of each other.
// DataProducer.h — publishes events without knowing who listens
#pragma once
#include "ofMain.h"
class DataProducer {
public:
void process() {
float result = compute();
// Notify all listeners — zero coupling to consumer types
ofNotifyEvent(dataUpdated, result);
}
ofEvent<float> dataUpdated; // Declares the event type
private:
float compute();
};
// ofApp.cpp — listens without requiring direct reference to producer
void ofApp::setup() {
DataProducer producer;
// Lambda listener — captures local state, no separate method needed
producer.dataUpdated.addListener([&](float value){
std::cout << "Received: " << value << std::endl;
});
// Bind to a member function — with explicit lifetime awareness
producer.dataUpdated.addListener(this, &ofApp::handleData);
}
Pattern 4: Cross-Platform File I/O (Portable Paths)
Always use ofToDataPath() for loading files — it resolves correctly on macOS (/Contents/Resources/), Linux, and Windows. Hard-coded absolute paths will break your app on other platforms.
void ofApp::setup() {
// ✅ CORRECT — cross-platform path resolution
std::string imgPath = ofToDataPath("images/background.png");
if (!background.load(imgPath)) {
ofLogError("ofApp") << "Failed to load: " << imgPath;
}
// ✅ Correct — relative paths for project config files
std::string configPath = ofToDataPath("config/settings.xml");
// ❌ WRONG — absolute or platform-specific paths break cross-platform builds
// background.load("/home/user/project/images/bg.png"); // Linux only!
// background.load("C:\\Users\\user\\project\\images\\bg.png"); // Windows only!
}
Pattern 5: Shader Management with Uniforms
Use ofShader for GPU-accelerated rendering. Always check shader compilation before using it, and set uniforms each frame since they are not persisted between frames.
// ofApp.h
#pragma once
#include "ofMain.h"
class ofApp : public ofBaseApp {
public:
void setup() override;
void draw() override;
private:
ofShader glowShader; // GPU shader program
};
// ofApp.cpp
void ofApp::setup() {
if (!glowShader.load("shaders/glow.vert", "shaders/glow.frag")) {
ofLogError("ofApp") << "Failed to load shader";
}
}
void ofApp::draw() {
ofClear(0, 0, 0);
glowShader.begin();
// Set uniforms every frame — they do NOT persist between frames
float time = ofGetElapsedTimef();
glowShader.setUniform1f("u_time", time);
glowShader.setUniform2f("u_resolution", ofGetWidth(), ofGetHeight());
glowShader.setUniform3f("u_mouse", ofGetMouseX(), ofGetMouseY(), 0.0f);
// Draw geometry — shaders apply to everything drawn between begin()/end()
ofDrawCircle(ofGetWidth() / 2, ofGetHeight() / 2, 100.0f);
glowShader.end();
}
Constraints
MUST DO
- Keep
draw()focused on rendering only — move computation toupdate() - Use
ofSetVerticalSync(true)andofSetFrameRate(60)for consistent timing - Load assets with
ofToDataPath()for cross-platform compatibility - Use
ofParameterandofxGui(orofxImGui) for runtime-tunable parameters - Initialize all member objects in
setup()before the firstdraw()call - Use
std::vectorwith value semantics instead ofstd::vector<Particle*>for data structs - Handle
windowResized()to update viewport dimensions and projection matrices - Call
ofClear()at the start of everydraw()cycle
MUST NOT DO
- Do heavy computation (file I/O, network calls, ML inference) inside
draw()— blocks rendering pipeline - Call
ofLogError()or throw exceptions fromdraw()— crashes the entire application on macOS/Linux - Use raw
new/deletefor OF objects — always use stack allocation or smart pointers (std::shared_ptr,std::unique_ptr) - Load files in
update()ordraw()— always preload insetup()with error checking - Modify
ofWindowSettingsafter window creation — must be set beforeofCreateWindow() - Mix
glmandofVec3f/ofVec2finterchangeably without explicit casting — causes subtle type bugs
Output Template
When implementing or reviewing an OpenFrameworks application, produce:
- Project Structure —
ofApp.h/ofApp.cpplayout, addon list, CMakeLists.txt dependencies - Lifecycle Plan — What runs in
setup()vsupdate()vsdraw()with justification - Event Architecture — Event types, listeners, and data flow between components
- Rendering Strategy — Draw primitives used, shader integration, optimization notes
- Cross-Platform Considerations — Path handling, window settings, platform-specific code guards
Related Skills
| Skill | Purpose |
|---|---|
of-shader-programming |
Deep dive into GLSL shader development within OpenFrameworks |
performance-optimization |
Profiling and optimizing rendering pipelines for 60fps targets |
Live References
Authoritative documentation links for OpenFrameworks development.