package com.xcompwiz.lookingglass.client.render; import java.util.Collection; import java.util.HashSet; import java.util.concurrent.ConcurrentMap; import net.minecraftforge.client.MinecraftForgeClient; import org.lwjgl.opengl.EXTFramebufferObject; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL14; import com.google.common.collect.MapMaker; import com.xcompwiz.lookingglass.client.proxyworld.WorldView; import com.xcompwiz.lookingglass.log.LoggerUtils; public class FrameBufferContainer { /** * Using this map we can detect which FBOs should be freed. The map will delete any entry where the world view object is garbage collected. It unfortunately * can't detect that the world view is otherwise leaked, though, just when it's gone. */ private static ConcurrentMap<WorldView, FrameBufferContainer> weakfbomap = new MapMaker().weakKeys().<WorldView, FrameBufferContainer> makeMap(); private static Collection<FrameBufferContainer> framebuffers = new HashSet<FrameBufferContainer>(); public static FrameBufferContainer createNewFramebuffer(WorldView view, int width, int height) { FrameBufferContainer fbo = new FrameBufferContainer(width, height); weakfbomap.put(view, fbo); framebuffers.add(fbo); return fbo; } public static void removeWorldView(WorldView view) { weakfbomap.remove(view); } public static void clearAll() { for (FrameBufferContainer fbo : framebuffers) { fbo.release(); } framebuffers.clear(); } public static synchronized void detectFreedWorldViews() { Collection<FrameBufferContainer> unpairedFBOs = new HashSet<FrameBufferContainer>(framebuffers); unpairedFBOs.removeAll(weakfbomap.values()); if (unpairedFBOs.isEmpty()) return; LoggerUtils.info("Freeing %d loose framebuffers from expired world views", unpairedFBOs.size()); for (FrameBufferContainer fbo : unpairedFBOs) { fbo.release(); } framebuffers.removeAll(unpairedFBOs); } public final int width; public final int height; private int framebuffer; private int depthBuffer; private int texture; private FrameBufferContainer(int width, int height) { this.width = width; this.height = height; allocateFrameBuffer(); } private void release() { freeFrameBuffer(); } public int getFramebuffer() { return framebuffer; } public int getTexture() { return texture; } // Always clean up your allocations private synchronized void freeFrameBuffer() { try { if (this.texture != 0) GL11.glDeleteTextures(this.texture); this.texture = 0; if (depthBuffer != 0) EXTFramebufferObject.glDeleteRenderbuffersEXT(depthBuffer); depthBuffer = 0; if (this.framebuffer != 0) EXTFramebufferObject.glDeleteFramebuffersEXT(this.framebuffer); this.framebuffer = 0; } catch (Exception e) { // Just in case, we make sure we don't crash. Because crashing is bad. LoggerUtils.error("Error while cleaning up a world view frame buffer."); } } // This method builds the frame buffer and texture references private void allocateFrameBuffer() { if (this.framebuffer != 0) return; this.framebuffer = EXTFramebufferObject.glGenFramebuffersEXT(); //Release via: EXTFramebufferObject.glDeleteFramebuffersEXT(framebuffer); this.depthBuffer = EXTFramebufferObject.glGenRenderbuffersEXT(); //Release via: EXTFramebufferObject.glDeleteRenderbuffersEXT(depthBuffer); EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, this.framebuffer); EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, depthBuffer); if (MinecraftForgeClient.getStencilBits() == 0) EXTFramebufferObject.glRenderbufferStorageEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, GL14.GL_DEPTH_COMPONENT24, width, height); else EXTFramebufferObject.glRenderbufferStorageEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, org.lwjgl.opengl.EXTPackedDepthStencil.GL_DEPTH24_STENCIL8_EXT, width, height); EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT, depthBuffer); if (MinecraftForgeClient.getStencilBits() != 0) EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_STENCIL_ATTACHMENT_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT, depthBuffer); this.texture = GL11.glGenTextures(); //Release via: GL11.glDeleteTextures(colorTexture); GL11.glBindTexture(GL11.GL_TEXTURE_2D, this.texture); GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width, height, 0, GL11.GL_RGBA, GL11.GL_INT, (java.nio.ByteBuffer) null); EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT, GL11.GL_TEXTURE_2D, this.texture, 0); EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0); } }