PGraphics
Data / CompositeDescription
An off-screen graphics buffer. Create with createGraphics(). Draw into it with beginDraw()/endDraw(), then display with image().
Syntax
PGraphics* pg = createGraphics(w, h)
Parameters
None
Returns
PGraphicsMethods
| beginDraw() | Start drawing to the buffer |
| endDraw() | Stop drawing to the buffer |
| background(r,g,b) | Clear the buffer |
| fill(r,g,b) | Set fill color |
| stroke(r,g,b) | Set stroke color |
| ellipse(x,y,w,h) | Draw ellipse |
| rect(x,y,w,h) | Draw rectangle |
Related
Under the Hood
From Processing.h:
class PGraphics : public PImage {
public:
GLuint fbo = 0; // framebuffer object
GLuint rbo = 0; // renderbuffer (depth+stencil)
bool active = false;
// Independent per-buffer style state. Real Processing's PGraphics has
// its OWN fill/stroke/text/etc. settings, completely separate from the
// main canvas's -- setting fill() on the main canvas must never affect
// a PGraphics buffer, and vice versa. Previously every PGraphics method
// forwarded directly to the single global PApplet::g_papplet singleton,
// meaning style state silently bled between the main canvas and every
// buffer (e.g. a thick green main-canvas stroke would incorrectly show
// up on an ellipse drawn inside a buffer that never set its own
// stroke). beginDraw()/endDraw() now swap PApplet's current style out
// for this buffer's OWN remembered style, and swap it back after,
// exactly mirroring how Java's PGraphics keeps independent state.
struct StyleSnapshot {
// Defaults match PApplet's own real defaults (white fill, black
// stroke) -- these are also real Processing's documented
// beginDraw() defaults ("Sets the default properties"), NOT an
// arbitrary choice. The earlier version of this struct had
// fillR=0 (black fill), which is backwards -- every fresh
// PGraphics buffer with no explicit fill()/stroke() calls should
// look exactly like a freshly created Processing sketch: white
// fill, black stroke, weight 1.
float fillR=1, fillG=1, fillB=1, fillA=1;
float strokeR=0, strokeG=0, strokeB=0, strokeA=1;
float strokeW=1;
bool doFill=true, doStroke=true, smoothing=true;
// BUG FIX: these were raw 0/0/0 literals, which silently meant
// CORNER mode (CORNER=0) for ellipseMode specifically, when real
// Processing's actual default is CENTER (=3). That made every
// fresh PGraphics buffer's ellipse() calls interpret their first
// two arguments as the bounding box's top-left corner instead of
// its center, shifting every default-mode ellipse by half its
// width/height toward the bottom-right. rectMode's and
// imageMode's real defaults ARE actually CORNER (=0), so those
// two were correct by coincidence -- only currentEllipseMode
// needed the real CENTER constant.
// Using literal values, not the CORNER/CENTER named constants:
// those constants are declared later in this file, after
// PGraphics's own definition, so they're not in scope yet here.
// CORNER=0, CENTER=3 (see the static constexpr declarations
// further down in this file).
int currentRectMode=0 /*CORNER*/, currentEllipseMode=3 /*CENTER*/, currentImageMode=0 /*CORNER*/;
float tintR=1, tintG=1, tintB=1, tintA=1;
bool doTint=false;
int colorModeVal=0;
float colorMaxH=255.f, colorMaxS=255.f, colorMaxB=255.f, colorMaxA=255.f;
float g_textSize=14.0f;
int g_textAlignX=0, g_textAlignY=0;
float g_textLeading=0.0f;
bool initialized=false; // false until beginDraw() runs once and sets real Processing defaults
};
StyleSnapshot myStyle; // this buffer's OWN persistent style
StyleSnapshot _savedMainStyle; // main canvas's style, stashed during beginDraw()..endDraw()
// Multisampled render target: PGraphics now matches real Processing's
// default antialiasing (smooth(2) on P2D/P3D) by rendering into a
// multisample renderbuffer-backed FBO, then resolving (blitting) down
// into the plain texture-backed FBO that drawPGraphicsRect samples
// from. Without this, the main canvas's window-level MSAA never
// applied to off-screen buffers at all.
GLuint msaaFbo = 0;
GLuint msaaColorRbo = 0;
GLuint msaaDepthRbo = 0;
int samples = 0; // 0 = no multisampling
bool is3D = false; // true if created via createGraphics(w,h,P3D)
PGraphics() = default;
PGraphics(int w, int h) : PImage(w, h) {
// Can't reach PApplet::g_papplet here -- PApplet's complete type
// isn't available yet at this point in the header (PGraphics is
// defined before it). Default directly to real Processing's own
// P2D/P3D default (smooth(2)) rather than reaching across that
// forward-reference gap. A sketch wanting a different level for
// its buffers can extend this later if needed.
samples = 0; // TEMPORARY: forced off to test if MSAA itself is the bug
// Resolve target: plain, non-multisampled FBO + texture --
// unchanged from before, just filled via a blit-resolve now.
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
if (texID == 0) glGenTextures(1, &texID);
glBindTexture(GL_TEXTURE_2D, texID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texID, 0);
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Multisample render target: only created if antialiasing was
// actually requested. beginDraw() binds THIS one; endDraw() blits
// it down into the resolve target above.
if (samples > 0) {
glGenFramebuffers(1, &msaaFbo);
glBindFramebuffer(GL_FRAMEBUFFER, msaaFbo);
glGenRenderbuffers(1, &msaaColorRbo);
glBindRenderbuffer(GL_RENDERBUFFER, msaaColorRbo);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaaColorRbo);
glGenRenderbuffers(1, &msaaDepthRbo);
glBindRenderbuffer(GL_RENDERBUFFER, msaaDepthRbo);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_DEPTH24_STENCIL8, w, h);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msaaDepthRbo);
GLenum msaaStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (msaaStatus != GL_FRAMEBUFFER_COMPLETE) {
glDeleteFramebuffers(1, &msaaFbo); msaaFbo = 0;
glDeleteRenderbuffers(1, &msaaColorRbo); msaaColorRbo = 0;
glDeleteRenderbuffers(1, &msaaDepthRbo); msaaDepthRbo = 0;
samples = 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
}
PGraphics(int w, int h, bool threeD) : PGraphics(w, h) {
is3D = threeD;
}
GLint savedViewport[4] = {};
void beginDraw(); // defined after PApplet (needs its complete type for style swap)
void _endDrawImpl(); // body of endDraw(), out-of-line for the same reason
void endDraw() { _endDrawImpl(); }
// Drawing methods forwarded to Processing -- implemented after full decls
void background(float g); void background(float r, float g, float b); void background(float r, float g, float b, float a);
void fill(float g); void fill(float r, float g, float b); void fill(float r, float g, float b, float a);
void noFill(); void stroke(float g); void stroke(float r, float g, float b); void noStroke();
void strokeWeight(float w);
void ellipse(float x, float y, float w, float h);
void rect(float x, float y, float w, float h);
void line(float x1, float y1, float x2, float y2);
void point(float x, float y);
void triangle(float x1,float y1,float x2,float y2,float x3,float y3);
void text(const std::string& s, float x, float y);
void textSize(float size);
void textAlign(int alignX);
void textAlign(int alignX, int alignY);
void translate(float x, float y, float z);
void rotateX(float angle);
void rotateY(float angle);
void rotateZ(float angle);
void box(float size);
void box(float w, float h, float d);
void sphere(float r);
void lights();
void noLights();
void ambientLight(float r, float g, float b);
void ambientLight(float r, float g, float b, float x, float y, float z);
void directionalLight(float r, float g, float b, float nx, float ny, float nz);
void pointLight(float r, float g, float b, float x, float y, float z);
void spotLight(float r, float g, float b, float x, float y, float z,
float nx, float ny, float nz, float angle, float conc);
void lightFalloff(float c, float l, float q);
void lightSpecular(float r, float g, float b);
void translate(float x, float y); void rotate(float a); void scale(float s);
void pushMatrix(); void popMatrix();
void beginShape(); void endShape(int mode=0); void vertex(float x, float y);
void clear();
~PGraphics() {
// Defensive cleanup: a PGraphics can be destroyed (via delete, or
// by going out of scope) while its beginDraw() was never matched
// with an endDraw() -- e.g. "pg = createGraphics(...)" reassigns
// a pointer, leaking the OLD PGraphics it pointed to if nothing
// explicitly deleted it first; if something DOES eventually
// delete it (or CppBuild auto-inserts a delete for exactly this
// case), the destructor running mid-beginDraw() needs to
// gracefully unwind that state rather than leaving the matrix
// stack unbalanced or GL bindings dangling on whatever context
// outlives this object.
if (active) {
PDEBUG("PGraphics::~PGraphics: destroying while still active "
"(beginDraw() never matched with endDraw()) -- "
"auto-closing now. this=%p\n", (void*)this);
_endDrawImpl();
}
if (msaaFbo) glDeleteFramebuffers(1, &msaaFbo);
if (msaaColorRbo) glDeleteRenderbuffers(1, &msaaColorRbo);
if (msaaDepthRbo) glDeleteRenderbuffers(1, &msaaDepthRbo);
if (fbo) glDeleteFramebuffers(1, &fbo);
if (rbo) glDeleteRenderbuffers(1, &rbo);
}
PGraphics(const PGraphics&) __attribute__((error(
"E0001: PGraphics value-style copying is not supported. "
"Declare PGraphics* instead of PGraphics. "
"See https://processing-cpp.github.io/error/E0001.html"
)));
PGraphics& operator=(const PGraphics&) __attribute__((error(
"E0001: PGraphics value-style assignment is not supported. "
"Declare PGraphics* instead of PGraphics. "
"See https://processing-cpp.github.io/error/E0001.html"
)));
// Allow assignment from pointer (PGraphics pg; pg = createGraphics(w,h))
// [E0001] REMOVED: the legacy "PGraphics pg; pg = createGraphics(...);"
// value-style assignment is no longer supported. PGraphics owns
// unique GPU resources (FBO, renderbuffers, texture) -- unlike
// PShape/PFont, which hold only plain CPU-side data and are safely
// copyable, copying or reassigning a PGraphics VALUE has no safe
// meaning. Declare it as a pointer instead:
//
// PGraphics* pg;
// pg = createGraphics(w, h);
// pg->beginDraw();
// ...
// pg->endDraw();
//
// This explicit compile error is intentional: it tells you exactly
// what to fix, rather than silently compiling against a value-style
// declaration that would behave incorrectly or unsafely.
// See: https://processing-cpp.github.io/error/E0001
PGraphics& operator=(PGraphics* p) __attribute__((error(
"E0001: PGraphics value-style assignment is not supported. "
"Declare PGraphics* instead of PGraphics. "
"See https://processing-cpp.github.io/error/E0001"
)));
};
Under the Hood
From Processing.cpp:
PGraphics* PApplet::createGraphics(int w,int h){return new PGraphics(w,h);
return new PGraphics(w, h, renderer == P3D);