/**
* 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.renderer.lwjgl;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.AWTGLCanvas;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.Pbuffer;
import org.lwjgl.opengl.PixelFormat;
import org.lwjgl.opengl.RenderTexture;
import com.ardor3d.framework.DisplaySettings;
import com.ardor3d.framework.Scene;
import com.ardor3d.image.Texture;
import com.ardor3d.image.Texture.Type;
import com.ardor3d.math.MathUtils;
import com.ardor3d.renderer.AbstractPbufferTextureRenderer;
import com.ardor3d.renderer.ContextCapabilities;
import com.ardor3d.renderer.ContextManager;
import com.ardor3d.renderer.RenderContext;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.renderer.TextureRendererFactory;
import com.ardor3d.renderer.state.RenderState;
import com.ardor3d.renderer.state.record.TextureRecord;
import com.ardor3d.renderer.state.record.TextureStateRecord;
import com.ardor3d.scene.state.lwjgl.LwjglTextureStateUtil;
import com.ardor3d.scene.state.lwjgl.util.LwjglTextureUtil;
import com.ardor3d.scenegraph.Spatial;
import com.ardor3d.util.Ardor3dException;
import com.ardor3d.util.TextureKey;
import com.ardor3d.util.geom.BufferUtils;
/**
* <p>
* This class is used by Ardor3D's LWJGL implementation to render textures. Users should <b>not </b> create this class
* directly.
* </p>
*
* @see TextureRendererFactory
*/
public class LwjglPbufferTextureRenderer extends AbstractPbufferTextureRenderer {
private static final Logger logger = Logger.getLogger(LwjglPbufferTextureRenderer.class.getName());
/* Pbuffer instance */
private Pbuffer _pbuffer;
private RenderTexture _texture;
public LwjglPbufferTextureRenderer(final DisplaySettings settings, final Renderer parentRenderer,
final ContextCapabilities caps) {
super(settings, parentRenderer, caps);
int pTarget = RenderTexture.RENDER_TEXTURE_2D;
if (!MathUtils.isPowerOfTwo(_width) || !MathUtils.isPowerOfTwo(_height)) {
pTarget = RenderTexture.RENDER_TEXTURE_RECTANGLE;
}
// signature: boolean useRGB, boolean useRGBA, boolean useDepth, boolean isRectangle, int target, int mipmaps
_texture = new RenderTexture(false, true, true, pTarget == RenderTexture.RENDER_TEXTURE_RECTANGLE, pTarget, 0);
setMultipleTargets(false);
}
/**
* <code>setupTexture</code> initializes a new Texture object for use with TextureRenderer. Generates a valid gl
* texture id for this texture and inits the data type for the texture.
*/
public void setupTexture(final Texture tex) {
if (tex.getType() != Type.TwoDimensional) {
throw new IllegalArgumentException("Unsupported type: " + tex.getType());
}
final RenderContext context = ContextManager.getCurrentContext();
final TextureStateRecord record = (TextureStateRecord) context.getStateRecord(RenderState.StateType.Texture);
// check if we are already setup... if so, throw error.
if (tex.getTextureKey() == null) {
tex.setTextureKey(TextureKey.getRTTKey(tex.getMinificationFilter()));
} else if (tex.getTextureIdForContext(context.getGlContextRep()) != 0) {
throw new Ardor3dException("Texture is already setup and has id.");
}
// Create the texture
final IntBuffer ibuf = BufferUtils.createIntBuffer(1);
GL11.glGenTextures(ibuf);
final int textureId = ibuf.get(0);
tex.setTextureIdForContext(context.getGlContextRep(), textureId);
LwjglTextureStateUtil.doTextureBind(tex, 0, true);
// Initialize our texture with some default data.
final int internalFormat = LwjglTextureUtil.getGLInternalFormat(tex.getTextureStoreFormat());
final int dataFormat = LwjglTextureUtil.getGLPixelFormatFromStoreFormat(tex.getTextureStoreFormat());
final int pixelDataType = LwjglTextureUtil.getGLPixelDataType(tex.getRenderedTexturePixelDataType());
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, internalFormat, _width, _height, 0, dataFormat, pixelDataType,
(ByteBuffer) null);
// Setup filtering and wrap
final TextureRecord texRecord = record.getTextureRecord(textureId, tex.getType());
LwjglTextureStateUtil.applyFilter(tex, texRecord, 0, record, context.getCapabilities());
LwjglTextureStateUtil.applyWrap(tex, texRecord, 0, record, context.getCapabilities());
logger.fine("setup pbuffer tex" + textureId + ": " + _width + "," + _height);
}
public void render(final Spatial spat, final Texture tex, final int clear) {
render(null, spat, null, tex, clear);
}
public void render(final List<? extends Spatial> spat, final Texture tex, final int clear) {
render(spat, null, null, tex, clear);
}
public void render(final Scene scene, final Texture tex, final int clear) {
render(null, null, scene, tex, clear);
}
private void render(final List<? extends Spatial> toDrawA, final Spatial toDrawB, final Scene toDrawC,
final Texture tex, final int clear) {
try {
if (_pbuffer == null || _pbuffer.isBufferLost()) {
if (_pbuffer != null && _pbuffer.isBufferLost()) {
logger.warning("PBuffer contents lost - will recreate the buffer");
deactivate();
_pbuffer.destroy();
}
initPbuffer();
}
if (_useDirectRender && !tex.getTextureStoreFormat().isDepthFormat()) {
// setup and render directly to a 2d texture.
_pbuffer.releaseTexImage(Pbuffer.FRONT_LEFT_BUFFER);
activate();
switchCameraIn(clear);
if (toDrawA != null) {
doDraw(toDrawA);
} else if (toDrawB != null) {
doDraw(toDrawB);
} else {
doDraw(toDrawC);
}
deactivate();
switchCameraOut();
LwjglTextureStateUtil.doTextureBind(tex, 0, true);
_pbuffer.bindTexImage(Pbuffer.FRONT_LEFT_BUFFER);
} else {
// render and copy to a texture
activate();
switchCameraIn(clear);
if (toDrawA != null) {
doDraw(toDrawA);
} else {
doDraw(toDrawB);
}
switchCameraOut();
copyToTexture(tex, 0, 0, _width, _height, 0, 0);
deactivate();
}
} catch (final Exception e) {
logger.logp(Level.SEVERE, this.getClass().toString(), "render(Spatial, Texture)", "Exception", e);
}
}
public void render(final Spatial spat, final List<Texture> texs, final int clear) {
render(null, spat, null, texs, clear);
}
public void render(final List<? extends Spatial> spat, final List<Texture> texs, final int clear) {
render(spat, null, null, texs, clear);
}
public void render(final Scene scene, final List<Texture> texs, final int clear) {
render(null, null, scene, texs, clear);
}
private void render(final List<? extends Spatial> toDrawA, final Spatial toDrawB, final Scene toDrawC,
final List<Texture> texs, final int clear) {
try {
if (_pbuffer == null || _pbuffer.isBufferLost()) {
if (_pbuffer != null && _pbuffer.isBufferLost()) {
logger.warning("PBuffer contents lost - will recreate the buffer");
deactivate();
_pbuffer.destroy();
}
initPbuffer();
}
if (texs.size() == 1 && _useDirectRender && !texs.get(0).getTextureStoreFormat().isDepthFormat()) {
// setup and render directly to a 2d texture.
LwjglTextureStateUtil.doTextureBind(texs.get(0), 0, true);
activate();
switchCameraIn(clear);
_pbuffer.releaseTexImage(Pbuffer.FRONT_LEFT_BUFFER);
if (toDrawA != null) {
doDraw(toDrawA);
} else {
doDraw(toDrawB);
}
switchCameraOut();
deactivate();
_pbuffer.bindTexImage(Pbuffer.FRONT_LEFT_BUFFER);
} else {
// render and copy to a texture
activate();
switchCameraIn(clear);
if (toDrawA != null) {
doDraw(toDrawA);
} else {
doDraw(toDrawB);
}
switchCameraOut();
for (int i = 0; i < texs.size(); i++) {
copyToTexture(texs.get(i), 0, 0, _width, _height, 0, 0);
}
deactivate();
}
} catch (final Exception e) {
logger.logp(Level.SEVERE, this.getClass().toString(), "render(Spatial, Texture)", "Exception", e);
}
}
public void copyToTexture(final Texture tex, final int x, final int y, final int width, final int height,
final int xoffset, final int yoffset) {
LwjglTextureStateUtil.doTextureBind(tex, 0, true);
GL11.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 0, xoffset, yoffset, x, y, width, height);
}
@Override
protected void clearBuffers(final int clear) {
GL11.glDisable(GL11.GL_SCISSOR_TEST);
_parentRenderer.clearBuffers(clear);
}
private void initPbuffer() {
try {
if (_pbuffer != null) {
giveBackContext();
ContextManager.removeContext(_pbuffer);
}
final PixelFormat format = new PixelFormat(_settings.getAlphaBits(), _settings.getDepthBits(),
_settings.getStencilBits()).withSamples(_settings.getSamples())
.withBitsPerPixel(_settings.getColorDepth()).withStereo(_settings.isStereo());
_pbuffer = new Pbuffer(_width, _height, format, _texture, null);
final Object contextKey = _pbuffer;
try {
_pbuffer.makeCurrent();
} catch (final LWJGLException e) {
throw new RuntimeException(e);
}
final LwjglContextCapabilities caps = new LwjglContextCapabilities(GLContext.getCapabilities());
ContextManager.addContext(contextKey,
new RenderContext(contextKey, caps, ContextManager.getCurrentContext()));
} catch (final Exception e) {
logger.logp(Level.SEVERE, this.getClass().toString(), "initPbuffer()", "Exception", e);
if (_texture != null && _useDirectRender) {
logger.warning("Your card claims to support Render to Texture but fails to enact it. Updating your driver might solve this problem.");
logger.warning("Attempting to fall back to Copy Texture.");
_texture = null;
_useDirectRender = false;
initPbuffer();
return;
}
logger.log(Level.WARNING, "Failed to create Pbuffer.", e);
return;
}
try {
activate();
_width = _pbuffer.getWidth();
_height = _pbuffer.getHeight();
deactivate();
} catch (final Exception e) {
logger.log(Level.WARNING, "Failed to initialize created Pbuffer.", e);
return;
}
}
private void activate() {
if (_active == 0) {
try {
_oldContext = ContextManager.getCurrentContext();
_pbuffer.makeCurrent();
ContextManager.switchContext(_pbuffer);
ContextManager.getCurrentContext().clearEnforcedStates();
ContextManager.getCurrentContext().enforceStates(_enforcedStates);
if (_bgColorDirty) {
GL11.glClearColor(_backgroundColor.getRed(), _backgroundColor.getGreen(),
_backgroundColor.getBlue(), _backgroundColor.getAlpha());
_bgColorDirty = false;
}
} catch (final LWJGLException e) {
logger.logp(Level.SEVERE, this.getClass().toString(), "activate()", "Exception", e);
throw new Ardor3dException();
}
}
_active++;
}
private void deactivate() {
if (_active == 1) {
try {
giveBackContext();
} catch (final LWJGLException e) {
logger.logp(Level.SEVERE, this.getClass().toString(), "deactivate()", "Exception", e);
throw new Ardor3dException();
}
}
_active--;
}
// XXX: Need another look at this to make it generic?
private void giveBackContext() throws LWJGLException {
if (Display.isCreated()) {
Display.makeCurrent();
ContextManager.switchContext(_oldContext.getContextKey());
} else if (_oldContext.getContextKey() instanceof AWTGLCanvas) {
((AWTGLCanvas) _oldContext.getContextKey()).makeCurrent();
ContextManager.switchContext(_oldContext.getContextKey());
}
}
public void cleanup() {
ContextManager.removeContext(_pbuffer);
_pbuffer.destroy();
}
public void setMultipleTargets(final boolean force) {
if (force) {
logger.fine("Copy Texture Pbuffer used!");
_useDirectRender = false;
_texture = null;
if (_pbuffer != null) {
try {
giveBackContext();
} catch (final LWJGLException ex) {
}
ContextManager.removeContext(_pbuffer);
}
} else {
if ((Pbuffer.getCapabilities() & Pbuffer.RENDER_TEXTURE_SUPPORTED) != 0) {
logger.fine("Render to Texture Pbuffer supported!");
if (_texture == null) {
logger.fine("No RenderTexture used in init, falling back to Copy Texture PBuffer.");
_useDirectRender = false;
} else {
_useDirectRender = true;
}
} else {
logger.fine("Copy Texture Pbuffer supported!");
_texture = null;
}
}
}
}