how would i go about reading the FrameBuffer that PGraphics uses for rendering to AFTER beginDraw();
and BEFORE `endDraw()’
as my goal is to only call beginDraw()
ONCE during setup, then read LIVE pixel data in onDraw() to reduce the overhead of calling beginDraw()
and endDraw()
60 times a second every draw()
DirectRenderer g2;
void settings() {
// 23 FPS
//fullScreen(P3D);
// 60FPS
size(400, 400, P3D);
}
void setup() {
background(0);
g2 = new DirectRenderer(this, 500,500);
//noLoop();
}
void exit() {
//g2.DirectRendererExit();
super.exit();
}
void draw_() {
// this makes no difference wether beginDraw is used or beginDraw_() is used
g2.beginDraw_();
g2.background(0);
g2.lights();
g2.noStroke();
g2.translate(width/2, height/2);
g2.rotateX(frameCount/100.0);
g2.rotateY(frameCount/200.0);
g2.box(40);
g2.endDraw_();
}
void draw() {
background(0);
int oldColor = getGraphics().fillColor;
fill(255);
textSize(16);
for (int i = 0; i < 32; i++) draw_();
image(g2.DirectRendererImage, 0, 0);
text("FPS: " + frameRate, 10, 20);
fill(oldColor);
}
as so far i have this
/**
* OpenGL renderer.
*/
public class DirectRenderer extends PGraphics3D {
// to be safe, prefix everything with DirectRenderer
boolean DirectRendererLogging = false;
// use java.lang.reflection wrappers class to simplify code
// all methods in DirectRendererReflection will cache if possible
// DirectRendererReflection must extent PGraphicsOpenGL
// in order to access TexCache class and methods from inside it
DirectRendererReflection reflection = new DirectRendererReflection();
DirectRenderer DirectRendererImage = null;
private void DirectRendererInit(PApplet papplet, int w, int h, boolean createDirectRendererImage) {
// PApplet cannot be cast
setParent(papplet);
setPrimary(false);
setSize(w, h);
if (createDirectRendererImage) {
DirectRendererImage = new DirectRenderer(papplet, w, h, false);
}
}
DirectRenderer(PApplet papplet, int w, int h, boolean createDirectRendererImage) {
DirectRendererInit(papplet, w, h, createDirectRendererImage);
}
DirectRenderer(PApplet papplet, int w, int h) {
DirectRendererInit(papplet, w, h, true);
}
void DirectRendererSetup() {
beginDraw();
}
void DirectRendererExit() {
DirectRendererImage.endDraw();
}
protected void pushFramebuffer_() {
PGraphicsOpenGL ppg = getPrimaryPG();
// all of the following is protected
// lets use reflection to access these
// fbStackDepth is protected
int fbStackDepth = reflection.get_fbStackDepth(ppg);
if (fbStackDepth == FB_STACK_DEPTH) {
throw new RuntimeException("Too many pushFramebuffer calls");
}
// fbStack is protected
FrameBuffer[] fbStack = reflection.get_fbStack(ppg);
// currentFramebuffer is protected
fbStack[fbStackDepth] = reflection.get_currentFramebuffer(ppg);
reflection.put_fbStack(ppg, fbStack);
fbStackDepth++;
reflection.put_fbStackDepth(ppg, fbStackDepth);
}
protected void setFramebuffer_(FrameBuffer fbo) {
PGraphicsOpenGL ppg = getPrimaryPG();
// all of the following is protected
// lets use reflection to access these
// currentFramebuffer is protected
FrameBuffer currentFramebuffer = reflection.get_currentFramebuffer(ppg);
if (currentFramebuffer != fbo) {
currentFramebuffer = fbo;
reflection.put_currentFramebuffer(ppg, currentFramebuffer);
if (currentFramebuffer != null) currentFramebuffer.bind();
}
}
/**
* Flips intArray along the X axis.
* @param intArray int[]
* @param mult int
*/
protected void flipArrayOnX(int[] intArray, int mult) {
int index = 0;
int xindex = mult * (width - 1);
for (int x = 0; x < width / 2; x++) {
for (int y = 0; y < height; y++) {
int i = index + mult * y * width;
int j = xindex + mult * y * width;
for (int c = 0; c < mult; c++) {
int temp = intArray[i];
intArray[i] = intArray[j];
intArray[j] = temp;
i++;
j++;
}
}
index += mult;
xindex -= mult;
}
}
/**
* Flips intArray along the Y axis.
* @param intArray int[]
* @param mult int
*/
protected void flipArrayOnY(int[] intArray, int mult) {
int index = 0;
int yindex = mult * (height - 1) * width;
for (int y = 0; y < height / 2; y++) {
for (int x = 0; x < mult * width; x++) {
int temp = intArray[index];
intArray[index] = intArray[yindex];
intArray[yindex] = temp;
index++;
yindex++;
}
yindex -= mult * width * 2;
}
}
/**
* Reorders an OpenGL pixel array (RGBA) into ARGB. The array must be
* of size width * height.
* @param pixels int[]
*/
protected void convertToARGB(int[] pixels) {
int t = 0;
int p = 0;
// BIG_Endian is private
if (reflection.get_BigEndian()) {
// RGBA to ARGB conversion: shifting RGB 8 bits to the right,
// and placing A 24 bits to the left.
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = pixels[p++];
pixels[t++] = (pixel >>> 8) | ((pixel << 24) & 0xFF000000);
}
}
} else {
// We have to convert ABGR into ARGB, so R and B must be swapped,
// A and G just brought back in.
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pixel = pixels[p++];
pixels[t++] = ((pixel & 0xFF) << 16) | ((pixel & 0xFF0000) >> 16) |
(pixel & 0xFF00FF00);
}
}
}
}
//@Override
protected void popFramebuffer_() {
PGraphicsOpenGL ppg = getPrimaryPG();
// all of the following is protected
// lets use reflection to access these
// fbStackDepth is protected
int fbStackDepth = reflection.get_fbStackDepth(ppg);
if (fbStackDepth == 0) {
throw new RuntimeException("popFramebuffer call is unbalanced.");
}
fbStackDepth--;
reflection.put_fbStackDepth(ppg, fbStackDepth);
// fbStack is protected
FrameBuffer[] fbStack = reflection.get_fbStack(ppg);
FrameBuffer fbo = fbStack[fbStackDepth];
// currentFramebuffer is protected
FrameBuffer currentFramebuffer = reflection.get_currentFramebuffer(ppg);
if (currentFramebuffer != fbo) {
if (DirectRendererLogging) println("framebuffer != fbo");
currentFramebuffer.finish();
currentFramebuffer = fbo;
reflection.put_currentFramebuffer(ppg, currentFramebuffer);
if (currentFramebuffer != null) {
if (DirectRendererLogging) println("bind framebuffer");
currentFramebuffer.bind();
}
}
}
protected void initOffscreen_() {
// Getting the context and capabilities from the main renderer.
loadTextureImpl(textureSampling, false);
FrameBuffer ofb = offscreenFramebuffer;
FrameBuffer mfb = multisampleFramebuffer;
// dispose is private, obtain it via reflection
// In case of re-initialization (for example, when the smooth level
// is changed), we make sure that all the OpenGL resources associated
// to the surface are released by calling delete().
if (ofb != null || mfb != null) {
reflection.get_dispose_begin();
if (ofb != null) {
reflection.invoke_dispose(ofb);
ofb = null;
}
if (mfb != null) {
reflection.invoke_dispose(mfb);
mfb = null;
}
}
boolean packed = depthBits == 24 && stencilBits == 8 &&
packedDepthStencilSupported;
// FrameBuffer is private, obtain it via reflection
reflection.get_FrameBuffer7Args_begin();
if (PGraphicsOpenGL.fboMultisampleSupported && 1 < PGL.smoothToSamples(smooth)) {
mfb = reflection.invoke_FrameBuffer7Args(this, texture.glWidth, texture.glHeight, PGL.smoothToSamples(smooth), 0,
depthBits, stencilBits, packed, false);
mfb.clear();
multisampleFramebuffer = mfb;
offscreenMultisample = true;
// The offscreen framebuffer where the multisampled image is finally drawn
// to. If depth reading is disabled it doesn't need depth and stencil buffers
// since they are part of the multisampled framebuffer.
if (hints[ENABLE_BUFFER_READING]) {
ofb = reflection.invoke_FrameBuffer7Args(this, texture.glWidth, texture.glHeight, 1, 1,
depthBits, stencilBits, packed, false);
} else {
ofb = reflection.invoke_FrameBuffer7Args(this, texture.glWidth, texture.glHeight, 1, 1,
0, 0, false, false);
}
} else {
smooth = 0;
ofb = reflection.invoke_FrameBuffer7Args(this, texture.glWidth, texture.glHeight, 1, 1,
depthBits, stencilBits, packed, false);
offscreenMultisample = false;
}
ofb.setColorBuffer(texture);
ofb.clear();
offscreenFramebuffer = ofb;
initialized = true;
}
protected void swapOffscreenTextures_() {
FrameBuffer ofb = offscreenFramebuffer;
if (texture != null && ptexture != null && ofb != null) {
int temp = texture.glName;
texture.glName = ptexture.glName;
ptexture.glName = temp;
ofb.setColorBuffer(texture);
}
}
protected void drawPTexture_() {
if (ptexture != null) {
// No blend so the texure replaces wherever is on the screen,
// irrespective of the alpha
pgl.disable(PGL.BLEND);
pgl.drawTexture(ptexture.glTarget, ptexture.glName,
ptexture.glWidth, ptexture.glHeight,
0, 0, width, height);
pgl.enable(PGL.BLEND);
}
}
protected void restartPGL_() {
initialized = false;
}
protected void beginOffscreenDraw_() {
if (!initialized) {
if (DirectRendererLogging) println("not initialized");
initOffscreen_();
} else {
if (DirectRendererLogging) println("initialized");
FrameBuffer ofb = offscreenFramebuffer;
FrameBuffer mfb = multisampleFramebuffer;
// contextIsOutdated is private, obtain it via reflection
reflection.get_contextIsOutdated_begin();
boolean outdated = ofb != null && reflection.invoke_contextIsOutdated(ofb);
boolean outdatedMulti = mfb != null && reflection.invoke_contextIsOutdated(mfb);
if (outdated || outdatedMulti) {
if (DirectRendererLogging) println("outdated");
restartPGL_();
initOffscreen_();
} else {
if (DirectRendererLogging) println("swapOffscreenTextures");
// The back texture of the past frame becomes the front,
// and the front texture becomes the new back texture where the
// new frame is drawn to.
swapOffscreenTextures_();
}
}
if (DirectRendererLogging) println("pushFramebuffer");
pushFramebuffer_();
if (offscreenMultisample) {
FrameBuffer mfb = multisampleFramebuffer;
if (mfb != null) {
if (DirectRendererLogging) println("set framebuffer mfb");
setFramebuffer_(mfb);
}
} else {
FrameBuffer ofb = offscreenFramebuffer;
if (ofb != null) {
if (DirectRendererLogging) println("set framebuffer ofb");
setFramebuffer_(ofb);
}
}
// Render previous back texture (now is the front) as background
if (DirectRendererLogging) println("drawPTexture");
drawPTexture_();
// Restoring the clipping configuration of the offscreen surface.
if (clip) {
pgl.enable(PGL.SCISSOR_TEST);
pgl.scissor(clipRect[0], clipRect[1], clipRect[2], clipRect[3]);
} else {
pgl.disable(PGL.SCISSOR_TEST);
}
}
//@Override
protected void endOffscreenDraw_() {
// the only functions which appear to copy the texture are:
// popFramebuffer();
// if (texture != null) {
// texture.updateTexels(); // Mark all texels in screen texture as modified.
// }
if (offscreenMultisample) {
if (DirectRendererLogging) println("multi sample offscreen");
FrameBuffer ofb = offscreenFramebuffer;
FrameBuffer mfb = multisampleFramebuffer;
if (ofb != null && mfb != null) {
mfb.copyColor(ofb);
}
}
if (DirectRendererLogging) println("pop frame buffer");
popFramebuffer_();
if (backgroundA == 1) {
// this seems to have no effect
if (DirectRendererLogging) println("backgroundA");
// Set alpha channel to opaque in order to match behavior of JAVA2D, not
// on the multisampled FBO because it leads to wrong background color
// on some Macbooks with AMD graphics.
pgl.colorMask(false, false, false, true);
pgl.clearColor(0, 0, 0, backgroundA);
pgl.clear(PGL.COLOR_BUFFER_BIT);
pgl.colorMask(true, true, true, true);
}
if (texture != null) {
texture.updateTexels(); // Mark all texels in screen texture as modified.
}
// the function restoreGL is protected, lets call it via Reflection
reflection.get_restoreGL_begin();
reflection.invoke_restoreGL(getPrimaryPG());
}
void texture_get(int[] pixels) {
// texture.pg is protected
PGraphicsOpenGL pg = reflection.get_texture_pg(texture);
// FrameBuffer is private, obtain it via reflection
FrameBuffer tempFbo = null;
reflection.get_FrameBuffer3Args_begin();
tempFbo = reflection.invoke_FrameBuffer3Args(pg, texture.glWidth, texture.glHeight);
tempFbo.setColorBuffer(texture);
if (DirectRendererLogging) println("pushFramebuffer");
pushFramebuffer();
if (DirectRendererLogging) println("setFramebuffer");
setFramebuffer(tempFbo);
tempFbo.readPixels();
if (DirectRendererLogging) println("popFramebuffer");
popFramebuffer();
tempFbo.getPixels(pixels);
convertToARGB(pixels);
// texture.invertedX and texture.invertedY are private, obtain it via reflection
if (reflection.get_texture_invertedX(texture)) {
if (DirectRendererLogging) println("flipArrayOnX");
flipArrayOnX(pixels, 1);
}
if (reflection.get_texture_invertedY(texture)) {
if (DirectRendererLogging) println("flipArrayOnY");
flipArrayOnY(pixels, 1);
}
}
void beginDraw_() {
// the function getGL is protected, lets call it via Reflection
reflection.get_getGL_begin();
reflection.invoke_getGL(pgl, getPrimaryPGL());
// the function setCurrentPG is protected, lets call it via Reflection
reflection.get_setCurrentPG1Arg_begin();
reflection.invoke_setCurrentPG1Arg(getPrimaryPG(), this);
// This has to go after the surface initialization, otherwise offscreen
// surfaces will have a null gl object.
report("top beginDraw()");
if (!checkGLThread()) {
if (DirectRendererLogging) println("if (!checkGLThread()) return;");
return;
}
if (drawing) {
if (DirectRendererLogging) println("if (drawing) return;");
return;
}
// the method texCache is protected
// lets access it via reflection
// the function texCache.containsTexture(this) is protected
// lets call it via Reflection
TexCache texCache = (TexCache) reflection.get_texCache(getPrimaryPG());
if (!primaryGraphics && reflection.invoke_containsTexture(texCache, this)) {
// This offscreen surface is being used as a texture earlier in draw,
// so we should update the rendering up to this point since it will be
// modified.
if (DirectRendererLogging) println("getPrimaryPG().flush();");
getPrimaryPG().flush();
}
if (!glParamsRead) {
if (DirectRendererLogging) println("getGLParameters();");
getGLParameters();
}
setViewport();
if (DirectRendererLogging) println("beginOffscreenDraw();");
beginOffscreenDraw_();
checkSettings();
drawing = true;
report("bot beginDraw()");
}
void endDraw_() {
report("top endDraw()");
if (!drawing) {
return;
}
flush();
endOffscreenDraw_();
// the function setCurrentPG is protected, lets call it via Reflection
reflection.get_setCurrentPG0Args_begin();
reflection.invoke_setCurrentPG0Args(getPrimaryPG());
drawing = false;
// 27 - 30 FPS if not null, otherwise 60 FPS
if (DirectRendererImage != null) {
// fast - no FPS reduction
DirectRendererImage.beginDraw();
// slow - reduces FPS by a range from 10 to 16 -- 27 to 30 FPS
// image draws of the texture of the argument given to it
DirectRendererImage.image_(this, 0, 0);
// slow - reduces FPS by a range from 13 to 20 -- 40 to 47 FPS
// if DirectRendererImage is not null this will recursive loop untill null
DirectRendererImage.endDraw_();
}
//DirectRendererImage.loadPixels();
//image_(this, width, height);
// IMPORTANT: without texture.get() the performance jumps up signifigantly
// with: 11 FPS for 400x400 32 draws
// without: 60 FPS for 400x400 32 draws
// DirectRendererImage = get(); // THIS IS AS SLOW AS texture.get()
//PImage test = createImage(500, 500, ARGB);
//test.loadPixels();
//texture_get(test.pixels);
//test.updatePixels();
//test = null;
//DirectRendererImage.updatePixels();
report("bot endDraw()");
}
/**
* Expects x1, y1, x2, y2 coordinates where (x2 >= x1) and (y2 >= y1).
* If tint() has been called, the image will be colored.
* <p/>
* The default implementation draws an image as a textured quad.
* The (u, v) coordinates are in image space (they're ints, after all..)
*/
protected void imageImpl_(PImage img,
float x1, float y1, float x2, float y2,
int u1, int v1, int u2, int v2) {
boolean savedStroke = stroke;
int savedTextureMode = textureMode;
stroke = false;
textureMode = IMAGE;
u1 *= img.pixelDensity;
u2 *= img.pixelDensity;
v1 *= img.pixelDensity;
v2 *= img.pixelDensity;
beginShape(QUADS);
//texture(img);
// texture(img) just calls:
// public void texture(PImage image) { textureImage = image; }
textureImage = img;
vertex(x1, y1, u1, v1);
vertex(x1, y2, u1, v2);
vertex(x2, y2, u2, v2);
vertex(x2, y1, u2, v1);
endShape();
stroke = savedStroke;
textureMode = savedTextureMode;
}
public void image_(PImage img, float a, float b) {
// Starting in release 0144, image errors are simply ignored.
// loadImageAsync() sets width and height to -1 when loading fails.
if (img.width == -1 || img.height == -1) return;
if (imageMode == CORNER || imageMode == CORNERS) {
imageImpl_(img,
a, b, a+img.width, b+img.height,
0, 0, img.width, img.height);
} else if (imageMode == CENTER) {
float x1 = a - img.width/2;
float y1 = b - img.height/2;
imageImpl_(img,
x1, y1, x1+img.width, y1+img.height,
0, 0, img.width, img.height);
}
}
/**
* Report on anything from glError().
* Don't use this inside glBegin/glEnd otherwise it'll
* throw an GL_INVALID_OPERATION error.
*/
@Override
protected void report(String where) {
if (DirectRendererLogging) println(where);
if (!hints[DISABLE_OPENGL_ERRORS]) {
int err = pgl.getError();
if (err != 0) {
String errString = pgl.errorString(err);
String msg = "OpenGL error " + err + " at " + where + ": " + errString;
PGraphics.showWarning(msg);
}
}
}
}
spefically -
// 27 - 30 FPS if not null, otherwise 60 FPS
if (DirectRendererImage != null) {
// fast - no FPS reduction
DirectRendererImage.beginDraw();
// slow - reduces FPS by a range from 10 to 16 -- 27 to 30 FPS
// image draws of the texture of the argument given to it
DirectRendererImage.image_(this, 0, 0);
// slow - reduces FPS by a range from 13 to 20 -- 40 to 47 FPS
// if DirectRendererImage is not null this will recursive loop untill null
// however we know this is only non-null for the instance that is doing the drawing
DirectRendererImage.endDraw_();
}