All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.util.OGLRenderToTextureSupport Maven / Gradle / Ivy

The newest version!
/*
 * 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 com.jogamp.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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy