Files
processing4/core/src/processing/opengl/FrameBuffer.java
2013-05-04 12:24:41 -04:00

544 lines
15 KiB
Java

/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2011-12 Ben Fry and Casey Reas
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
*/
package processing.opengl;
import processing.core.PApplet;
import processing.core.PConstants;
import java.nio.IntBuffer;
/**
* Encapsulates a Frame Buffer Object for offscreen rendering.
* When created with onscreen == true, it represents the normal
* framebuffer. Needed by the stack mechanism in OPENGL2 to return
* to onscreen rendering after a sequence of pushFramebuffer calls.
* It transparently handles the situations when the FBO extension is
* not available.
*
* By Andres Colubri.
*/
public class FrameBuffer implements PConstants {
protected PGL pgl;
protected int context; // The context that created this framebuffer.
public int glFbo;
public int glDepth;
public int glStencil;
public int glDepthStencil;
public int glMultisample;
public int width;
public int height;
protected int depthBits;
protected int stencilBits;
protected boolean packedDepthStencil;
protected boolean multisample;
protected int nsamples;
protected int numColorBuffers;
protected Texture[] colorBufferTex;
protected boolean screenFb;
protected boolean noDepth;
protected IntBuffer pixelBuffer;
FrameBuffer() {
pgl = PGraphicsOpenGL.pgl;
context = pgl.createEmptyContext();
}
FrameBuffer(int w, int h, int samples, int colorBuffers,
int depthBits, int stencilBits, boolean packedDepthStencil,
boolean screen) {
this();
glFbo = 0;
glDepth = 0;
glStencil = 0;
glDepthStencil = 0;
glMultisample = 0;
if (screen) {
// If this framebuffer is used to represent a on-screen buffer,
// then it doesn't make it sense for it to have multisampling,
// color, depth or stencil buffers.
depthBits = stencilBits = samples = colorBuffers = 0;
}
width = w;
height = h;
if (1 < samples) {
multisample = true;
nsamples = samples;
} else {
multisample = false;
nsamples = 1;
}
numColorBuffers = colorBuffers;
colorBufferTex = new Texture[numColorBuffers];
for (int i = 0; i < numColorBuffers; i++) {
colorBufferTex[i] = null;
}
if (depthBits < 1 && stencilBits < 1) {
this.depthBits = 0;
this.stencilBits = 0;
this.packedDepthStencil = false;
} else {
if (packedDepthStencil) {
// When combined depth/stencil format is required, the depth and stencil
// bits are overriden and the 24/8 combination for a 32 bits surface is
// used.
this.depthBits = 24;
this.stencilBits = 8;
this.packedDepthStencil = true;
} else {
this.depthBits = depthBits;
this.stencilBits = stencilBits;
this.packedDepthStencil = false;
}
}
screenFb = screen;
allocate();
noDepth = false;
pixelBuffer = null;
}
FrameBuffer(int w, int h) {
this(w, h, 1, 1, 0, 0, false, false);
}
FrameBuffer(int w, int h, boolean screen) {
this(w, h, 1, 1, 0, 0, false, screen);
}
@Override
protected void finalize() throws Throwable {
try {
// PApplet.println("finalize FBO");
if (!screenFb) {
if (glFbo != 0) {
PGraphicsOpenGL.finalizeFrameBufferObject(glFbo, context);
}
if (glDepth != 0) {
PGraphicsOpenGL.finalizeRenderBufferObject(glDepth, context);
}
if (glStencil != 0) {
PGraphicsOpenGL.finalizeRenderBufferObject(glStencil, context);
}
if (glMultisample != 0) {
PGraphicsOpenGL.finalizeRenderBufferObject(glMultisample, context);
}
if (glDepthStencil != 0) {
PGraphicsOpenGL.finalizeRenderBufferObject(glDepthStencil, context);
}
}
} finally {
super.finalize();
}
}
public void clear() {
PGraphicsOpenGL.pushFramebuffer();
PGraphicsOpenGL.setFramebuffer(this);
pgl.clearDepth(1);
pgl.clearStencil(0);
pgl.clearColor(0, 0, 0, 0);
pgl.clear(PGL.DEPTH_BUFFER_BIT |
PGL.STENCIL_BUFFER_BIT |
PGL.COLOR_BUFFER_BIT);
PGraphicsOpenGL.popFramebuffer();
}
public void copy(FrameBuffer dest, FrameBuffer current) {
pgl.bindFramebuffer(PGL.READ_FRAMEBUFFER, this.glFbo);
pgl.bindFramebuffer(PGL.DRAW_FRAMEBUFFER, dest.glFbo);
pgl.blitFramebuffer(0, 0, this.width, this.height,
0, 0, dest.width, dest.height,
PGL.COLOR_BUFFER_BIT, PGL.NEAREST);
pgl.bindFramebuffer(PGL.READ_FRAMEBUFFER, current.glFbo);
pgl.bindFramebuffer(PGL.DRAW_FRAMEBUFFER, current.glFbo);
}
public void bind() {
pgl.bindFramebuffer(PGL.FRAMEBUFFER, glFbo);
}
public void disableDepthTest() {
noDepth = true;
}
public void finish(PGraphicsOpenGL pg) {
if (noDepth) {
// No need to clear depth buffer because depth testing was disabled.
if (pg.getHint(ENABLE_DEPTH_TEST)) {
pgl.enable(PGL.DEPTH_TEST);
} else {
pgl.disable(PGL.DEPTH_TEST);
}
}
}
public void readPixels() {
if (pixelBuffer == null) createPixelBuffer();
pixelBuffer.rewind();
pgl.readPixels(0, 0, width, height, PGL.RGBA, PGL.UNSIGNED_BYTE,
pixelBuffer);
}
public void getPixels(int[] pixels) {
if (pixelBuffer != null) {
pixelBuffer.get(pixels, 0, pixels.length);
pixelBuffer.rewind();
}
}
public IntBuffer getPixelBuffer() {
return pixelBuffer;
}
public boolean hasDepthBuffer() {
return 0 < depthBits;
}
public boolean hasStencilBuffer() {
return 0 < stencilBits;
}
public void setFBO(int id) {
if (screenFb) {
glFbo = id;
}
}
///////////////////////////////////////////////////////////
// Color buffer setters.
public void setColorBuffer(Texture tex) {
setColorBuffers(new Texture[] { tex }, 1);
}
public void setColorBuffers(Texture[] textures) {
setColorBuffers(textures, textures.length);
}
public void setColorBuffers(Texture[] textures, int n) {
if (screenFb) return;
if (numColorBuffers != PApplet.min(n, textures.length)) {
throw new RuntimeException("Wrong number of textures to set the color " +
"buffers.");
}
for (int i = 0; i < numColorBuffers; i++) {
colorBufferTex[i] = textures[i];
}
PGraphicsOpenGL.pushFramebuffer();
PGraphicsOpenGL.setFramebuffer(this);
// Making sure nothing is attached.
for (int i = 0; i < numColorBuffers; i++) {
pgl.framebufferTexture2D(PGL.FRAMEBUFFER, PGL.COLOR_ATTACHMENT0 + i,
PGL.TEXTURE_2D, 0, 0);
}
for (int i = 0; i < numColorBuffers; i++) {
pgl.framebufferTexture2D(PGL.FRAMEBUFFER, PGL.COLOR_ATTACHMENT0 + i,
colorBufferTex[i].glTarget,
colorBufferTex[i].glName, 0);
}
pgl.validateFramebuffer();
PGraphicsOpenGL.popFramebuffer();
}
public void swapColorBuffers() {
for (int i = 0; i < numColorBuffers - 1; i++) {
int i1 = (i + 1);
Texture tmp = colorBufferTex[i];
colorBufferTex[i] = colorBufferTex[i1];
colorBufferTex[i1] = tmp;
}
PGraphicsOpenGL.pushFramebuffer();
PGraphicsOpenGL.setFramebuffer(this);
for (int i = 0; i < numColorBuffers; i++) {
pgl.framebufferTexture2D(PGL.FRAMEBUFFER, PGL.COLOR_ATTACHMENT0 + i,
colorBufferTex[i].glTarget,
colorBufferTex[i].glName, 0);
}
pgl.validateFramebuffer();
PGraphicsOpenGL.popFramebuffer();
}
public int getDefaultReadBuffer() {
if (screenFb) {
return pgl.getDefaultReadBuffer();
} else {
return PGL.COLOR_ATTACHMENT0;
}
}
public int getDefaultDrawBuffer() {
if (screenFb) {
return pgl.getDefaultDrawBuffer();
} else {
return PGL.COLOR_ATTACHMENT0;
}
}
///////////////////////////////////////////////////////////
// Allocate/release framebuffer.
protected void allocate() {
dispose(); // Just in the case this object is being re-allocated.
context = pgl.getCurrentContext();
if (screenFb) {
glFbo = 0;
} else {
//create the FBO object...
glFbo = PGraphicsOpenGL.createFrameBufferObject(context);
// ... and then create the rest of the stuff.
if (multisample) {
createColorBufferMultisample();
}
if (packedDepthStencil) {
createPackedDepthStencilBuffer();
} else {
if (0 < depthBits) {
createDepthBuffer();
}
if (0 < stencilBits) {
createStencilBuffer();
}
}
}
}
protected void dispose() {
if (screenFb) return;
if (glFbo != 0) {
PGraphicsOpenGL.finalizeFrameBufferObject(glFbo, context);
glFbo = 0;
}
if (glDepth != 0) {
PGraphicsOpenGL.finalizeRenderBufferObject(glDepth, context);
glDepth = 0;
}
if (glStencil != 0) {
PGraphicsOpenGL.finalizeRenderBufferObject(glStencil, context);
glStencil = 0;
}
if (glMultisample != 0) {
PGraphicsOpenGL.finalizeRenderBufferObject(glMultisample, context);
glMultisample = 0;
}
if (glDepthStencil != 0) {
PGraphicsOpenGL.finalizeRenderBufferObject(glDepthStencil, context);
glDepthStencil = 0;
}
}
protected boolean contextIsOutdated() {
if (screenFb) return false;
boolean outdated = !pgl.contextIsCurrent(context);
if (outdated) {
PGraphicsOpenGL.removeFrameBufferObject(glFbo, context);
PGraphicsOpenGL.removeRenderBufferObject(glDepth, context);
PGraphicsOpenGL.removeRenderBufferObject(glStencil, context);
PGraphicsOpenGL.removeRenderBufferObject(glDepthStencil, context);
PGraphicsOpenGL.removeRenderBufferObject(glMultisample, context);
glFbo = 0;
glDepth = 0;
glStencil = 0;
glDepthStencil = 0;
glMultisample = 0;
for (int i = 0; i < numColorBuffers; i++) {
colorBufferTex[i] = null;
}
}
return outdated;
}
protected void createColorBufferMultisample() {
if (screenFb) return;
PGraphicsOpenGL.pushFramebuffer();
PGraphicsOpenGL.setFramebuffer(this);
glMultisample = PGraphicsOpenGL.createRenderBufferObject(context);
pgl.bindRenderbuffer(PGL.RENDERBUFFER, glMultisample);
pgl.renderbufferStorageMultisample(PGL.RENDERBUFFER, nsamples,
PGL.RGBA8, width, height);
pgl.framebufferRenderbuffer(PGL.FRAMEBUFFER, PGL.COLOR_ATTACHMENT0,
PGL.RENDERBUFFER, glMultisample);
PGraphicsOpenGL.popFramebuffer();
}
protected void createPackedDepthStencilBuffer() {
if (screenFb) return;
if (width == 0 || height == 0) {
throw new RuntimeException("PFramebuffer: size undefined.");
}
PGraphicsOpenGL.pushFramebuffer();
PGraphicsOpenGL.setFramebuffer(this);
glDepthStencil = PGraphicsOpenGL.createRenderBufferObject(context);
pgl.bindRenderbuffer(PGL.RENDERBUFFER, glDepthStencil);
if (multisample) {
pgl.renderbufferStorageMultisample(PGL.RENDERBUFFER, nsamples,
PGL.DEPTH24_STENCIL8, width, height);
} else {
pgl.renderbufferStorage(PGL.RENDERBUFFER, PGL.DEPTH24_STENCIL8,
width, height);
}
pgl.framebufferRenderbuffer(PGL.FRAMEBUFFER, PGL.DEPTH_ATTACHMENT,
PGL.RENDERBUFFER, glDepthStencil);
pgl.framebufferRenderbuffer(PGL.FRAMEBUFFER, PGL.STENCIL_ATTACHMENT,
PGL.RENDERBUFFER, glDepthStencil);
PGraphicsOpenGL.popFramebuffer();
}
protected void createDepthBuffer() {
if (screenFb) return;
if (width == 0 || height == 0) {
throw new RuntimeException("PFramebuffer: size undefined.");
}
PGraphicsOpenGL.pushFramebuffer();
PGraphicsOpenGL.setFramebuffer(this);
glDepth = PGraphicsOpenGL.createRenderBufferObject(context);
pgl.bindRenderbuffer(PGL.RENDERBUFFER, glDepth);
int glConst = PGL.DEPTH_COMPONENT16;
if (depthBits == 16) {
glConst = PGL.DEPTH_COMPONENT16;
} else if (depthBits == 24) {
glConst = PGL.DEPTH_COMPONENT24;
} else if (depthBits == 32) {
glConst = PGL.DEPTH_COMPONENT32;
}
if (multisample) {
pgl.renderbufferStorageMultisample(PGL.RENDERBUFFER, nsamples, glConst,
width, height);
} else {
pgl.renderbufferStorage(PGL.RENDERBUFFER, glConst, width, height);
}
pgl.framebufferRenderbuffer(PGL.FRAMEBUFFER, PGL.DEPTH_ATTACHMENT,
PGL.RENDERBUFFER, glDepth);
PGraphicsOpenGL.popFramebuffer();
}
protected void createStencilBuffer() {
if (screenFb) return;
if (width == 0 || height == 0) {
throw new RuntimeException("PFramebuffer: size undefined.");
}
PGraphicsOpenGL.pushFramebuffer();
PGraphicsOpenGL.setFramebuffer(this);
glStencil = PGraphicsOpenGL.createRenderBufferObject(context);
pgl.bindRenderbuffer(PGL.RENDERBUFFER, glStencil);
int glConst = PGL.STENCIL_INDEX1;
if (stencilBits == 1) {
glConst = PGL.STENCIL_INDEX1;
} else if (stencilBits == 4) {
glConst = PGL.STENCIL_INDEX4;
} else if (stencilBits == 8) {
glConst = PGL.STENCIL_INDEX8;
}
if (multisample) {
pgl.renderbufferStorageMultisample(PGL.RENDERBUFFER, nsamples, glConst,
width, height);
} else {
pgl.renderbufferStorage(PGL.RENDERBUFFER, glConst, width, height);
}
pgl.framebufferRenderbuffer(PGL.FRAMEBUFFER, PGL.STENCIL_ATTACHMENT,
PGL.RENDERBUFFER, glStencil);
PGraphicsOpenGL.popFramebuffer();
}
protected void createPixelBuffer() {
pixelBuffer = IntBuffer.allocate(width * height);
pixelBuffer.rewind();
}
}