/******************************************************************************* * Copyright 2019 metaphore * * 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.crashinvaders.vfx.framebuffer; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.glutils.FrameBuffer; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Disposable; //TODO Add javadoc. public class VfxFrameBufferPool implements Disposable { private static final String TAG = VfxFrameBufferPool.class.getSimpleName(); /** The highest number of free buffer instances. Can be reset any time. */ public int freePeak; /** A collection of all the buffers created and managed by the pool. */ protected final Array<VfxFrameBuffer> managedBuffers; /** A pool of spare buffers that are ready to be obtained. */ protected final Array<VfxFrameBuffer> freeBuffers; private int width; private int height; private Pixmap.Format pixelFormat; private Texture.TextureWrap textureWrapU = Texture.TextureWrap.ClampToEdge; private Texture.TextureWrap textureWrapV = Texture.TextureWrap.ClampToEdge; private Texture.TextureFilter textureFilterMin = Texture.TextureFilter.Nearest; private Texture.TextureFilter textureFilterMag = Texture.TextureFilter.Nearest; private boolean disposed = false; public VfxFrameBufferPool() { this(Pixmap.Format.RGBA8888, Gdx.graphics.getBackBufferWidth(), Gdx.graphics.getBackBufferHeight(), 16); } public VfxFrameBufferPool(Pixmap.Format pixelFormat, int width, int height, int initialCapacity) { this.width = width; this.height = height; this.pixelFormat = pixelFormat; this.managedBuffers = new Array<>(false, initialCapacity); this.freeBuffers = new Array<>(false, initialCapacity); } @Override public void dispose() { if (managedBuffers.size != freeBuffers.size) { int unfreedBufferAmount = managedBuffers.size - freeBuffers.size; Gdx.app.error(TAG, "At the moment of disposal, " + "the pool still has some managed buffers unfreed (" + unfreedBufferAmount +"). " + "Someone's using them and hasn't freed?"); } disposed = true; for (int i = 0; i < managedBuffers.size; i++) { managedBuffers.get(i).dispose(); } managedBuffers.clear(); freeBuffers.clear(); } public void resize(int width, int height) { this.width = width; this.height = height; cleanupInvalid(); } /** * Returns a buffer from this pool. The buffer may be * new (from {@link #createBuffer()}) or reused (previously {@link #free(VfxFrameBuffer) freed}). */ public VfxFrameBuffer obtain() { if (disposed) throw new IllegalStateException("Instance is already disposed"); return freeBuffers.size == 0 ? createBuffer() : freeBuffers.pop(); } /** * Returns the buffer in the free pool, making it eligible for {@link #obtain()}. * <p> * For performance sake, the pool does not check if the buffer is already freed, so the same buffer must not be freed multiple times. */ public void free(VfxFrameBuffer buffer) { if (disposed) throw new IllegalStateException("Instance is already disposed"); if (buffer == null) throw new IllegalArgumentException("buffer cannot be null."); if (!validateBuffer(buffer)) { managedBuffers.removeValue(buffer, true); buffer.dispose(); return; } freeBuffers.add(buffer); freePeak = Math.max(freePeak, freeBuffers.size); resetBuffer(buffer); } /** Removes all the free buffers from the pool. */ public void clearFree() { for (int i = 0; i < freeBuffers.size; i++) { VfxFrameBuffer buffer = freeBuffers.get(i); managedBuffers.removeValue(buffer, true); buffer.dispose(); } freeBuffers.clear(); } /** @return the number of the free buffers available. */ public int getFreeCount() { return freeBuffers.size; } protected VfxFrameBuffer createBuffer() { VfxFrameBuffer buffer = new VfxFrameBuffer(pixelFormat); buffer.initialize(width, height); managedBuffers.add(buffer); return buffer; } /** Called when a buffer is freed to clear the state of the buffer for possible later reuse. */ protected void resetBuffer(VfxFrameBuffer buffer) { buffer.clearRenderers(); // Reset texture params to the default ones. Texture texture = buffer.getTexture(); texture.setWrap(textureWrapU, textureWrapV); texture.setFilter(textureFilterMin, textureFilterMag); } protected boolean validateBuffer(VfxFrameBuffer buffer) { FrameBuffer fbo = buffer.getFbo(); return buffer.isInitialized() && this.width == fbo.getWidth() && this.height == fbo.getHeight() && this.pixelFormat == buffer.getPixelFormat(); } /** Checks if the buffers are valid. Those which are not will be reconstructed or deleted if they are free. */ protected void cleanupInvalid() { for (int i = 0; i < managedBuffers.size; i++) { VfxFrameBuffer buffer = managedBuffers.get(i); if (!validateBuffer(buffer)) { // Buffer is invalid - means we have to reinitialize it according to the current configuration. // FBO reinitialization is an expensive operation, no reason doing it for the buffers that are currently not in use. // So in case a buffer is free, we just dispose and delete it. boolean wasFree = freeBuffers.removeValue(buffer, true); if (wasFree) { managedBuffers.removeValue(buffer, true); buffer.dispose(); } else { buffer.initialize(width, height); } } } } public void setTextureParams(Texture.TextureWrap textureWrapU, Texture.TextureWrap textureWrapV, Texture.TextureFilter textureFilterMin, Texture.TextureFilter textureFilterMag) { this.textureWrapU = textureWrapU; this.textureWrapV = textureWrapV; this.textureFilterMin = textureFilterMin; this.textureFilterMag = textureFilterMag; // Update the free textures'. for (int i = 0; i < freeBuffers.size; i++) { Texture texture = freeBuffers.get(i).getTexture(); texture.setWrap(textureWrapU, textureWrapV); texture.setFilter(textureFilterMin, textureFilterMag); } } }