/**
* Copyright (c) 2008-2012 Ardor Labs, Inc.
*
* This file is part of Ardor3D.
*
* Ardor3D is free software: you can redistribute it and/or modify it
* under the terms of its license which may be found in the accompanying
* LICENSE file or at <http://www.ardor3d.com/LICENSE>.
*/
package com.ardor3d.framework.lwjgl;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.ARBMultisample;
import org.lwjgl.opengl.EXTFramebufferBlit;
import org.lwjgl.opengl.EXTFramebufferMultisample;
import org.lwjgl.opengl.EXTFramebufferObject;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.Pbuffer;
import org.lwjgl.opengl.PixelFormat;
import com.ardor3d.framework.DisplaySettings;
import com.ardor3d.framework.Scene;
import com.ardor3d.math.ColorRGBA;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.Camera;
import com.ardor3d.renderer.Camera.ProjectionMode;
import com.ardor3d.renderer.ContextManager;
import com.ardor3d.renderer.RenderContext;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.lwjgl.LwjglContextCapabilities;
import com.ardor3d.renderer.lwjgl.LwjglRenderer;
import com.ardor3d.renderer.lwjgl.LwjglTextureRenderer;
import com.ardor3d.util.Ardor3dException;
import com.ardor3d.util.geom.BufferUtils;
/**
* <p>
* A "canvas" class for use in drawing Scene data to an off-screen target. The data is read back after each call to draw
* into a local IntBuffer for use.
* </p>
*
* <p>
* Note: this class is not currently setup for use with other render contexts.
* </p>
*/
public class LwjglHeadlessCanvas {
protected Scene _scene;
protected Renderer _renderer = new LwjglRenderer();
protected final DisplaySettings _settings;
protected Camera _camera;
protected int _fboID, _depthRBID, _colorRBID;
protected int _msfboID, _msdepthRBID, _mscolorRBID;
protected boolean _useMSAA = false;
protected IntBuffer _data;
protected Pbuffer _buff;
/**
* Construct a new LwjglHeadlessCanvas. Only width, height, alpha, depth and stencil are used. Samples will be
* applied as well but may cause issues on some platforms.
*
* @param settings
* the settings to use.
* @param scene
* the scene we will render.
*/
public LwjglHeadlessCanvas(final DisplaySettings settings, final Scene scene) {
_scene = scene;
_settings = settings;
init();
}
protected void init() {
final int width = _settings.getWidth();
final int height = _settings.getHeight();
try {
// Create a Pbuffer so we can have a valid gl context to work with
final PixelFormat format = new PixelFormat(_settings.getAlphaBits(), _settings.getDepthBits(),
_settings.getStencilBits());
_buff = new Pbuffer(1, 1, format, null);
_buff.makeCurrent();
} catch (final LWJGLException ex) {
ex.printStackTrace();
}
// Set up our Ardor3D context and capabilities objects
final LwjglContextCapabilities caps = new LwjglContextCapabilities(GLContext.getCapabilities());
final RenderContext currentContext = new RenderContext(this, caps, null);
if (!caps.isFBOSupported()) {
throw new Ardor3dException("Headless requires FBO support.");
}
if (caps.isFBOMultisampleSupported() && caps.isFBOBlitSupported() && _settings.getSamples() > 0) {
_useMSAA = true;
}
// Init our FBO.
final IntBuffer buffer = BufferUtils.createIntBuffer(1);
EXTFramebufferObject.glGenFramebuffersEXT(buffer); // generate id
// Bind the FBO
_fboID = buffer.get(0);
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, _fboID);
// initialize our color renderbuffer
EXTFramebufferObject.glGenRenderbuffersEXT(buffer); // generate id
_colorRBID = buffer.get(0);
EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, _colorRBID);
EXTFramebufferObject.glRenderbufferStorageEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, GL11.GL_RGBA, width,
height);
// Attach color renderbuffer to framebuffer
EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT, _colorRBID);
// initialize our depth renderbuffer
EXTFramebufferObject.glGenRenderbuffersEXT(buffer); // generate id
_depthRBID = buffer.get(0);
EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, _depthRBID);
EXTFramebufferObject.glRenderbufferStorageEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT,
GL11.GL_DEPTH_COMPONENT, width, height);
// Attach depth renderbuffer to framebuffer
EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT, _depthRBID);
// Check FBO complete
LwjglTextureRenderer.checkFBOComplete(_fboID);
// Now do it all again for multisample, if requested and supported
if (_useMSAA) {
// Init our ms FBO.
EXTFramebufferObject.glGenFramebuffersEXT(buffer); // generate id
// Bind the ms FBO
_msfboID = buffer.get(0);
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, _msfboID);
// initialize our ms color renderbuffer
EXTFramebufferObject.glGenRenderbuffersEXT(buffer); // generate id
_mscolorRBID = buffer.get(0);
EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, _mscolorRBID);
EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT,
_settings.getSamples(), GL11.GL_RGBA, width, height);
// Attach ms color renderbuffer to ms framebuffer
EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT,
_mscolorRBID);
// initialize our ms depth renderbuffer
EXTFramebufferObject.glGenRenderbuffersEXT(buffer); // generate id
_msdepthRBID = buffer.get(0);
EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, _msdepthRBID);
EXTFramebufferMultisample.glRenderbufferStorageMultisampleEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT,
_settings.getSamples(), GL11.GL_DEPTH_COMPONENT, width, height);
// Attach ms depth renderbuffer to ms framebuffer
EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT,
EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT,
_msdepthRBID);
// Check MS FBO complete
LwjglTextureRenderer.checkFBOComplete(_msfboID);
// enable multisample
GL11.glEnable(ARBMultisample.GL_MULTISAMPLE_ARB);
}
// Setup our data buffer for storing rendered image data.
_data = ByteBuffer.allocateDirect(width * height * 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
// Add context to manager and set as active.
ContextManager.addContext(this, currentContext);
ContextManager.switchContext(this);
// Setup a default bg color.
_renderer.setBackgroundColor(ColorRGBA.BLACK);
// Setup a default camera
_camera = new Camera(width, _settings.getHeight());
_camera.setFrustumPerspective(45.0f, (float) width / (float) _settings.getHeight(), 1, 1000);
_camera.setProjectionMode(ProjectionMode.Perspective);
// setup camera orientation and position.
final Vector3 loc = new Vector3(0.0f, 0.0f, 0.0f);
final Vector3 left = new Vector3(-1.0f, 0.0f, 0.0f);
final Vector3 up = new Vector3(0.0f, 1.0f, 0.0f);
final Vector3 dir = new Vector3(0.0f, 0f, -1.0f);
_camera.setFrame(loc, left, up, dir);
// release our FBO(s) until used.
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0);
}
public void draw() {
// bind correct fbo
EXTFramebufferObject
.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, _useMSAA ? _msfboID : _fboID);
// Make sure this OpenGL context is current.
ContextManager.switchContext(this);
try {
_buff.makeCurrent();
} catch (final LWJGLException ex) {
ex.printStackTrace();
}
// make sure camera is set
if (Camera.getCurrentCamera() != _camera) {
_camera.update();
}
_camera.apply(_renderer);
// clear buffers
GL11.glDisable(GL11.GL_SCISSOR_TEST);
_renderer.clearBuffers(Renderer.BUFFER_COLOR | Renderer.BUFFER_DEPTH);
// draw our scene
_scene.renderUnto(_renderer);
_renderer.flushFrame(false);
// if we're multisampled, we need to blit to a non-multisampled fbo first
if (_useMSAA) {
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferBlit.GL_DRAW_FRAMEBUFFER_EXT, _fboID);
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferBlit.GL_READ_FRAMEBUFFER_EXT, _msfboID);
EXTFramebufferBlit.glBlitFramebufferEXT(0, 0, _settings.getWidth(), _settings.getHeight(), 0, 0,
_settings.getWidth(), _settings.getHeight(), GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT
| GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_NEAREST);
// get ready to read non-msaa fbo
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, _fboID);
}
// read data from our color buffer
_data.rewind();
GL11.glReadBuffer(EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT);
GL11.glReadPixels(0, 0, _settings.getWidth(), _settings.getHeight(), GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, _data);
// release our FBO.
EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0);
}
public void releaseContext() throws LWJGLException {
_buff.releaseContext();
}
public void cleanup() {
if (_fboID != 0) {
final IntBuffer id = BufferUtils.createIntBuffer(1);
id.put(_fboID);
id.rewind();
EXTFramebufferObject.glDeleteFramebuffersEXT(id);
_fboID = 0;
}
if (_depthRBID != 0) {
final IntBuffer id = BufferUtils.createIntBuffer(1);
id.put(_depthRBID);
id.rewind();
EXTFramebufferObject.glDeleteRenderbuffersEXT(id);
_depthRBID = 0;
}
if (_colorRBID != 0) {
final IntBuffer id = BufferUtils.createIntBuffer(1);
id.put(_colorRBID);
id.rewind();
EXTFramebufferObject.glDeleteRenderbuffersEXT(id);
_colorRBID = 0;
}
ContextManager.removeContext(this);
}
public IntBuffer getDataBuffer() {
return _data;
}
public Renderer getRenderer() {
return _renderer;
}
public Camera getCamera() {
return _camera;
}
public void setCamera(final Camera camera) {
_camera = camera;
}
}