src.gov.nasa.worldwind.util.OGLRenderToTextureSupport Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind.util;
import com.jogamp.opengl.util.texture.Texture;
import gov.nasa.worldwind.render.DrawContext;
import javax.media.opengl.*;
/**
* OGLRenderToTextureSupport encapsulates the pattern of rendering GL commands to a destination texture. Currently only
* the color pixel values are written to the destination texture, but other values (depth, stencil) should be possible
* with modification or extension.
*
* OGLRenderToTextureSupport is compatible with GL version 1.1 or greater, but it attempts to use more recent features
* whenever possible. Different GL feature sets result in different approaches to rendering to texture, therefore the
* caller cannot depend on the mechanism by which OGLRenderToTextureSupport will write pixel values to the destination
* texture. For this reason, OGLRenderToTextureSupport must be used when the contents of the windowing system buffer
* (likely the back framebuffer) can be freely modified by OGLRenderToTextureSupport. The World Wind pre-render stage is
* a good example of when it is appropriate to use OGLRenderToTextureSupport. Fore more information on the pre-render
* stage, see {@link gov.nasa.worldwind.render.PreRenderable} and {@link gov.nasa.worldwind.layers.Layer#preRender(gov.nasa.worldwind.render.DrawContext)}.
* Note: In order to achieve consistent results across all platforms, it is essential to clear the texture's
* contents before rendering anything into the texture. Do this by invoking {@link
* #clear(gov.nasa.worldwind.render.DrawContext, java.awt.Color)} immediately after any call to {@link
* #beginRendering(gov.nasa.worldwind.render.DrawContext, int, int, int, int)}.
*
* The common usage pattern for OGLRenderToTextureSupport is as follows:
DrawContext dc = ...; // Typically
* passed in as an argument to the containing method.
Texture texture = TextureIO.newTexture(new
* TextureData(...);
// Setup the drawing rectangle to match the texture dimensions, and originate from the
* texture's lower left corner.
OGLRenderToTextureSupport rttSupport = new OGLRenderToTextureSupport();
* rttSupport.beginRendering(dc, 0, 0, texture.getWidth(), texture.getHeight());
try
{
// Bind the
* texture as the destination for color pixel writes.
rttSupport.setColorTarget(dc, texture);
// Clear the
* texture contents with transparent black.
rttSupport.clear(dc, new Color(0, 0, 0, 0));
// Invoke desired GL
* rendering commands.
}
finally
{
rttSupport.endRendering(dc);
}
*
* @author dcollins
* @version $Id: OGLRenderToTextureSupport.java 1676 2013-10-21 18:32:30Z dcollins $
*/
public class OGLRenderToTextureSupport
{
protected boolean isFramebufferObjectEnabled;
protected Texture colorTarget;
protected java.awt.Rectangle drawRegion;
protected OGLStackHandler stackHandler;
protected int framebufferObject;
/** Constructs a new OGLRenderToTextureSupport, but otherwise does nothing. */
public OGLRenderToTextureSupport()
{
this.isFramebufferObjectEnabled = true;
this.stackHandler = new OGLStackHandler();
}
/**
* Returns true if framebuffer objects are enabled for use (only applicable if the feature is available in the
* current GL runtime)
*
* @return true if framebuffer objects are enabled, and false otherwise.
*/
public boolean isEnableFramebufferObject()
{
return this.isFramebufferObjectEnabled;
}
/**
* Specifies if framebuffer objects should be used if they are available in the current GL runtime.
*
* @param enable true to enable framebuffer objects, false to disable them.
*/
public void setEnableFramebufferObject(boolean enable)
{
this.isFramebufferObjectEnabled = enable;
}
/**
* Returns the texture currently set as the color buffer target, or null if no texture is currently bound as the
* color buffer target.
*
* @return the Texture currently set as the color buffer target, or null if none exists.
*/
public Texture getColorTarget()
{
return this.colorTarget;
}
/**
* Sets the specified texture as the color buffer target. This texture receives the output of all GL commands
* affecting the color buffer. Binding a null texture specifies that no texture should receive color values. If the
* current color target texture is the same reference as the specified texture, this does nothing. Otherwise this
* flushes any buffered pixel values to the current color target, and assigns the specified texture as the new color
* target.
*
* If {@link #isEnableFramebufferObject()} is false, the supported texture formats for the color target are limited
* only by the OpenGL implementation's supported formats. If {@link #isEnableFramebufferObject()} is true and the
* DrawContext supports OpenGL framebuffer objects, the supported texture formats for the color target are as
* follows: - RGB
- RGBA
- FLOAT_R_NV (on NVidia hardware)
- FLOAT_RG_NV (on NVidia
* hardware)
- FLOAT_RGB_NV (on NVidia hardware)
- FLOAT_RGBA_NV (on NVidia hardware)
*
* @param dc the current DrawContext.
* @param texture the Texture to use as the destination for GL commands affecting the color buffer. A null value is
* permitted.
*
* @throws IllegalArgumentException if the DrawContext is null.
*/
public void setColorTarget(DrawContext dc, Texture texture)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (this.colorTarget == texture)
return;
// If we have a texture target, then write the current GL color buffer state to the current texture target
// before binding a new target.
if (this.colorTarget != null)
{
this.flushColor(dc);
}
// If framebuffer objects are enabled, then bind the target texture as the current framebuffer's color
// attachment, and GL rendering commands then affect the target texture. Otherwise, GL rendering commands affect
// the windowing system's write buffer (likely the onscreen back buffer), and are explicitly copied to the
// texture in flush() or endRendering().
if (this.useFramebufferObject(dc))
{
this.bindFramebufferColorAttachment(dc, texture);
}
this.colorTarget = texture;
}
/**
* Clears the current texture target's pixels with the specified RGBA clear color. If the current color texture
* target is null, this does nothing.
*
* @param dc the current DrawContext.
* @param color the RGBA clear color to write to the current color texture target.
*
* @throws IllegalArgumentException if either the DrawContext or the color is null.
*/
public void clear(DrawContext dc, java.awt.Color color)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (color == null)
{
String message = Logging.getMessage("nullValue.ColorIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (this.colorTarget == null)
return;
float[] compArray = new float[4];
color.getRGBComponents(compArray);
// Premultiply color components by the alpha component.
compArray[0] *= compArray[3];
compArray[1] *= compArray[3];
compArray[2] *= compArray[3];
GL gl = dc.getGL();
gl.glClearColor(compArray[0], compArray[1], compArray[2], compArray[3]);
gl.glClear(GL.GL_COLOR_BUFFER_BIT);
}
/**
* Flushes any buffered pixel values to the appropriate target textures.
*
* @param dc the current DrawContext.
*
* @throws IllegalArgumentException if the DrawContext is null.
*/
public void flush(DrawContext dc)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.flushColor(dc);
}
/**
* Configures the GL attached to the specified DrawContext for rendering a 2D model to a texture. The modelview
* matrix is set to the identity, the projection matrix is set to an orthographic projection aligned with the
* specified draw rectangle (x, y, width, height), the viewport and scissor boxes are set to the specified draw
* rectangle, and the depth test and depth write flags are disabled. Because the viewport and scissor boxes are set
* to the draw rectangle, only the texels intersecting the specified drawing rectangle (x, y, width, height) are
* affected by GL commands. Once rendering is complete, this should always be followed with a call to {@link
* #endRendering(gov.nasa.worldwind.render.DrawContext)}.
*
* @param dc the current DrawContext.
* @param x the x-coordinate of the draw region's lower left corner.
* @param y the y-coordinate of the draw region's lower left corner.
* @param width the draw region width.
* @param height the draw region height.
*
* @throws IllegalArgumentException if the DrawContext is null.
*/
public void beginRendering(DrawContext dc, int x, int y, int width, int height)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
this.drawRegion = new java.awt.Rectangle(x, y, width, height);
// Note: there is no attribute bit for framebuffer objects. The default framebuffer object state (object ID 0
// is bound as the current fbo) is restored in endRendering().
this.stackHandler.pushAttrib(gl,
GL2.GL_COLOR_BUFFER_BIT // For clear color.
| GL2.GL_DEPTH_BUFFER_BIT // For depth test and depth mask.
| GL2.GL_SCISSOR_BIT // For scissor test and scissor box.
| GL2.GL_TRANSFORM_BIT // For matrix mode.
| GL2.GL_VIEWPORT_BIT); // For viewport state.
this.stackHandler.pushTextureIdentity(gl);
this.stackHandler.pushProjectionIdentity(gl);
gl.glOrtho(x, x + width, y, y + height, -1, 1);
this.stackHandler.pushModelviewIdentity(gl);
// Disable the depth test and writing to the depth buffer. This provides consistent render to texture behavior
// regardless of whether we are using copy-to-texture or framebuffer objects. For copy-to-texture, the depth
// test and depth writing are explicitly disabled. For fbos there is no depth buffer components, so the depth
// dest is implicitly disabled.
gl.glDisable(GL.GL_DEPTH_TEST);
gl.glDepthMask(false);
// Enable the scissor test and set both the scissor box and the viewport to the specified region. This enables
// the caller to set up rendering to a subset of the texture. Note that the scissor box defines the region
// affected by a call to glClear().
gl.glEnable(GL.GL_SCISSOR_TEST);
gl.glScissor(x, y, width, height);
gl.glViewport(x, y, width, height);
if (this.useFramebufferObject(dc))
{
this.beginFramebufferObjectRendering(dc);
}
}
/**
* Flushes any buffered pixel values to the appropriate texure targets, then restores the GL state to its previous
* configuration before {@link #beginRendering(gov.nasa.worldwind.render.DrawContext, int, int, int, int)} was
* called. Finally, all texture targets associated with this OGLRenderToTextureSupport are unbound.
*
* @param dc the current DrawContext.
*
* @throws IllegalArgumentException if the DrawContext is null.
*/
public void endRendering(DrawContext dc)
{
if (dc == null)
{
String message = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
this.flush(dc);
if (this.useFramebufferObject(dc))
{
if (this.colorTarget != null)
{
this.bindFramebufferColorAttachment(dc, null);
}
this.endFramebufferObjectRendering(dc);
}
this.stackHandler.pop(gl);
this.drawRegion = null;
this.colorTarget = null;
}
protected void flushColor(DrawContext dc)
{
// If framebuffer objects are enabled, then texture contents are already affected by the any GL rendering
// commands.
if (this.useFramebufferObject(dc))
{
if (this.colorTarget != null)
{
// If the color target is attempting to use automatic mipmap generation, then we must manually update
// its mipmap chain. Automatic mipmap generation is invoked when the GL client explicitly modifies the
// texture contents by calling one of glTexImage or glTexSubImage. However when we render directly to
// the texture using framebuffer objects, automatic mipmap generation is not invoked, and the texture's
// mipmap chain contents are undefined until we explicitly update them.
if (this.colorTarget.isUsingAutoMipmapGeneration())
this.updateMipmaps(dc, this.colorTarget);
}
}
// If framebuffer objects are not enabled, then we've been rendering into the read buffer associated with the
// windowing system (likely the onscreen back buffer). Explicitly copy the read buffer contents to the texture.
else
{
if (this.colorTarget != null)
{
this.copyScreenPixelsToTexture(dc, this.drawRegion.x, this.drawRegion.y,
this.drawRegion.width, this.drawRegion.height, this.colorTarget);
}
}
}
protected void copyScreenPixelsToTexture(DrawContext dc, int x, int y, int width, int height, Texture texture)
{
int w = width;
int h = height;
// If the lower left corner of the region to copy is outside of the texture bounds, then exit and do nothing.
if (x >= texture.getWidth() || y >= texture.getHeight())
return;
// Limit the dimensions of the region to copy so they fit into the texture's dimensions.
if (w > texture.getWidth())
w = texture.getWidth();
if (h > texture.getHeight())
h = texture.getHeight();
GL gl = dc.getGL();
try
{
// We want to copy the contents of the current GL read buffer to the specified texture target. However we do
// not want to change any of the texture creation parameters (e.g. dimensions, internal format, border).
// Therefore we use glCopyTexSubImage2D() to copy a region of the read buffer to a region of the texture.
// glCopyTexSubImage2D() has two key features:
// 1. Does not change the texture creation parameters. We want to upload a new region of data without
// changing the textures' defining parameters.
// 2. Enables specification of a destination (x, y) offset in texels. This offset corresponds to the
// viewport (x, y) specified by the caller in beginRendering().
texture.enable(gl);
texture.bind(gl);
gl.glCopyTexSubImage2D(
texture.getTarget(), // target
0, // level
x, y, // xoffset, yoffset
x, y, w, h); // x, y, width, height
}
finally
{
texture.disable(gl);
}
}
protected void updateMipmaps(DrawContext dc, Texture texture)
{
GL gl = dc.getGL();
try
{
texture.enable(gl);
texture.bind(gl);
gl.glGenerateMipmap(texture.getTarget());
}
finally
{
texture.disable(gl);
}
}
protected boolean useFramebufferObject(DrawContext dc)
{
return this.isEnableFramebufferObject() && dc.getGLRuntimeCapabilities().isUseFramebufferObject();
}
protected void beginFramebufferObjectRendering(DrawContext dc)
{
// Binding a framebuffer object causes all GL operations to operate on the attached textures and renderbuffers
// (if any).
int[] framebuffers = new int[1];
GL gl = dc.getGL();
gl.glGenFramebuffers(1, framebuffers, 0);
gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, framebuffers[0]);
this.framebufferObject = framebuffers[0];
if (this.framebufferObject == 0)
{
throw new IllegalStateException("Frame Buffer Object not created.");
}
}
protected void endFramebufferObjectRendering(DrawContext dc)
{
// Binding framebuffer object 0 (the default) causes GL operations to operate on the window system attached
// framebuffer.
int[] framebuffers = new int[] {this.framebufferObject};
GL gl = dc.getGL();
gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
gl.glDeleteFramebuffers(1, framebuffers, 0);
this.framebufferObject = 0;
}
protected void bindFramebufferColorAttachment(DrawContext dc, Texture texture)
{
GL gl = dc.getGL();
// Attach the texture as color attachment 0 to the framebuffer.
if (texture != null)
{
gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D,
texture.getTextureObject(gl), 0);
this.checkFramebufferStatus(dc);
}
// If the texture is null, detach color attachment 0 from the framebuffer.
else
{
gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0, GL.GL_TEXTURE_2D, 0, 0);
}
}
protected void checkFramebufferStatus(DrawContext dc)
{
int status = dc.getGL().glCheckFramebufferStatus(GL.GL_FRAMEBUFFER);
switch (status)
{
// Framebuffer is configured correctly and supported on this hardware.
case GL.GL_FRAMEBUFFER_COMPLETE:
break;
// Framebuffer is configured correctly, but not supported on this hardware.
case GL.GL_FRAMEBUFFER_UNSUPPORTED:
throw new IllegalStateException(getFramebufferStatusString(status));
// Framebuffer is configured incorrectly. This should never happen, but we check anyway.
default:
throw new IllegalStateException(getFramebufferStatusString(status));
}
}
protected static String getFramebufferStatusString(int status)
{
switch (status)
{
case GL.GL_FRAMEBUFFER_COMPLETE:
return Logging.getMessage("OGL.FramebufferComplete");
case GL.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
return Logging.getMessage("OGL.FramebufferIncompleteAttachment");
case GL.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
return Logging.getMessage("OGL.FramebufferIncompleteDimensions");
case GL2.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
return Logging.getMessage("OGL.FramebufferIncompleteDrawBuffer");
case GL.GL_FRAMEBUFFER_INCOMPLETE_FORMATS:
return Logging.getMessage("OGL.FramebufferIncompleteFormats");
case GL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
return Logging.getMessage("OGL.FramebufferIncompleteMissingAttachment");
case GL2.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
return Logging.getMessage("OGL.FramebufferIncompleteMultisample");
case GL2.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
return Logging.getMessage("OGL.FramebufferIncompleteReadBuffer");
case GL.GL_FRAMEBUFFER_UNSUPPORTED:
return Logging.getMessage("OGL.FramebufferUnsupported");
default:
return null;
}
}
}