/*******************************************************************************
* Copyright 2012 bmanuel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.bitfire.postprocessing.utils;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
/** Encapsulates a framebuffer with the ability to ping-pong between two buffers.
*
* Upon {@link #begin()} the buffer is reset to a known initial state, this is usually done just before the first usage of the
* buffer.
*
* Subsequent {@link #capture()} calls will initiate writing to the next available buffer, returning the previously used one,
* effectively ping-ponging between the two. Until {@link #end()} is called, chained rendering will be possible by retrieving the
* necessary buffers via {@link #getSourceTexture()}, {@link #getSourceBuffer()}, {@link #getResultTexture()} or
* {@link #getResultBuffer}.
*
* When finished, {@link #end()} should be called to stop capturing. When the OpenGL context is lost, {@link #rebind()} should be
* called.
*
* @author bmanuel */
public final class PingPongBuffer {
public FrameBuffer buffer1, buffer2;
public Texture texture1, texture2;
public int width, height;
public final boolean ownResources;
// internal state
private Texture texResult, texSrc;
private FrameBuffer bufResult, bufSrc;
private boolean writeState, pending1, pending2;
// save/restore state
private final FrameBuffer owned1, owned2;
private FrameBuffer ownedResult, ownedSource;
private int ownedW, ownedH;
/** Creates a new ping-pong buffer and owns the resources. */
public PingPongBuffer (int width, int height, Format frameBufferFormat, boolean hasDepth) {
ownResources = true;
owned1 = new FrameBuffer(frameBufferFormat, width, height, hasDepth);
owned2 = new FrameBuffer(frameBufferFormat, width, height, hasDepth);
set(owned1, owned2);
}
/** Creates a new ping-pong buffer with the given buffers. */
public PingPongBuffer (FrameBuffer buffer1, FrameBuffer buffer2) {
ownResources = false;
owned1 = null;
owned2 = null;
set(buffer1, buffer2);
}
/** An instance of this object can also be used to manipulate some other externally-allocated buffers, applying just the same
* ping-ponging behavior.
*
* If this instance of the object was owning the resources, they will be preserved and will be restored by a {@link #reset()}
* call.
*
* @param buffer1 the first buffer
* @param buffer2 the second buffer */
public void set (FrameBuffer buffer1, FrameBuffer buffer2) {
if (ownResources) {
ownedResult = bufResult;
ownedSource = bufSrc;
ownedW = width;
ownedH = height;
}
this.buffer1 = buffer1;
this.buffer2 = buffer2;
width = this.buffer1.getWidth();
height = this.buffer1.getHeight();
rebind();
}
/** Restore the previous buffers if the instance was owning resources. */
public void reset () {
if (ownResources) {
buffer1 = owned1;
buffer2 = owned2;
width = ownedW;
height = ownedH;
bufResult = ownedResult;
bufSrc = ownedSource;
}
}
/** Free the resources, if any. */
public void dispose () {
if (ownResources) {
// make sure we delete what we own
// if the caller didn't call {@link #reset()}
owned1.dispose();
owned2.dispose();
}
}
/** When needed graphics memory could be invalidated so buffers should be rebuilt. */
public void rebind () {
texture1 = buffer1.getColorBufferTexture();
texture2 = buffer2.getColorBufferTexture();
}
/** Ensures the initial buffer state is always the same before starting ping-ponging. */
public void begin () {
pending1 = false;
pending2 = false;
writeState = true;
texSrc = texture1;
bufSrc = buffer1;
texResult = texture2;
bufResult = buffer2;
}
/** Starts and/or continue ping-ponging, begin capturing on the next available buffer, returns the result of the previous
* {@link #capture()} call.
*
* @return the Texture containing the result. */
public Texture capture () {
endPending();
if (writeState) {
// set src
texSrc = texture1;
bufSrc = buffer1;
// set result
texResult = texture2;
bufResult = buffer2;
// write to other
pending2 = true;
buffer2.begin();
} else {
texSrc = texture2;
bufSrc = buffer2;
texResult = texture1;
bufResult = buffer1;
pending1 = true;
buffer1.begin();
}
writeState = !writeState;
return texSrc;
}
/** Finishes ping-ponging, must always be called after a call to {@link #capture()} */
public void end () {
endPending();
}
/** @return the source texture of the current ping-pong chain. */
public Texture getSouceTexture () {
return texSrc;
}
/** @return the source buffer of the current ping-pong chain. */
public FrameBuffer getSourceBuffer () {
return bufSrc;
}
/** @return the result's texture of the latest {@link #capture()}. */
public Texture getResultTexture () {
return texResult;
}
/** @return Returns the result's buffer of the latest {@link #capture()}. */
public FrameBuffer getResultBuffer () {
return bufResult;
}
// internal use
// finish writing to the buffers, mark as not pending anymore.
private void endPending () {
if (pending1) {
buffer1.end();
pending1 = false;
}
if (pending2) {
buffer2.end();
pending2 = false;
}
}
}