1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.jme3.texture; 34 35 import com.jme3.renderer.Caps; 36 import com.jme3.renderer.Renderer; 37 import com.jme3.texture.Image.Format; 38 import com.jme3.util.NativeObject; 39 import java.util.ArrayList; 40 41 /** 42 * <p> 43 * <code>FrameBuffer</code>s are rendering surfaces allowing 44 * off-screen rendering and render-to-texture functionality. 45 * Instead of the scene rendering to the screen, it is rendered into the 46 * FrameBuffer, the result can be either a texture or a buffer. 47 * <p> 48 * A <code>FrameBuffer</code> supports two methods of rendering, 49 * using a {@link Texture} or using a buffer. 50 * When using a texture, the result of the rendering will be rendered 51 * onto the texture, after which the texture can be placed on an object 52 * and rendered as if the texture was uploaded from disk. 53 * When using a buffer, the result is rendered onto 54 * a buffer located on the GPU, the data of this buffer is not accessible 55 * to the user. buffers are useful if one 56 * wishes to retrieve only the color content of the scene, but still desires 57 * depth testing (which requires a depth buffer). 58 * Buffers can be copied to other framebuffers 59 * including the main screen, by using 60 * {@link Renderer#copyFrameBuffer(com.jme3.texture.FrameBuffer, com.jme3.texture.FrameBuffer) }. 61 * The content of a {@link RenderBuffer} can be retrieved by using 62 * {@link Renderer#readFrameBuffer(com.jme3.texture.FrameBuffer, java.nio.ByteBuffer) }. 63 * <p> 64 * <code>FrameBuffer</code>s have several attachment points, there are 65 * several <em>color</em> attachment points and a single <em>depth</em> 66 * attachment point. 67 * The color attachment points support image formats such as 68 * {@link Format#RGBA8}, allowing rendering the color content of the scene. 69 * The depth attachment point requires a depth image format. 70 * 71 * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) 72 * 73 * @author Kirill Vainer 74 */ 75 public class FrameBuffer extends NativeObject { 76 77 private int width = 0; 78 private int height = 0; 79 private int samples = 1; 80 private ArrayList<RenderBuffer> colorBufs = new ArrayList<RenderBuffer>(); 81 private RenderBuffer depthBuf = null; 82 private int colorBufIndex = 0; 83 84 /** 85 * <code>RenderBuffer</code> represents either a texture or a 86 * buffer that will be rendered to. <code>RenderBuffer</code>s 87 * are attached to an attachment slot on a <code>FrameBuffer</code>. 88 */ 89 public class RenderBuffer { 90 91 Texture tex; 92 Image.Format format; 93 int id = -1; 94 int slot = -1; 95 96 /** 97 * @return The image format of the render buffer. 98 */ 99 public Format getFormat() { 100 return format; 101 } 102 103 /** 104 * @return The texture to render to for this <code>RenderBuffer</code> 105 * or null if content should be rendered into a buffer. 106 */ 107 public Texture getTexture(){ 108 return tex; 109 } 110 111 /** 112 * Do not use. 113 */ 114 public int getId() { 115 return id; 116 } 117 118 /** 119 * Do not use. 120 */ 121 public void setId(int id){ 122 this.id = id; 123 } 124 125 /** 126 * Do not use. 127 */ 128 public int getSlot() { 129 return slot; 130 } 131 132 public void resetObject(){ 133 id = -1; 134 } 135 136 public RenderBuffer createDestructableClone(){ 137 if (tex != null){ 138 return null; 139 }else{ 140 RenderBuffer destructClone = new RenderBuffer(); 141 destructClone.id = id; 142 return destructClone; 143 } 144 } 145 146 @Override 147 public String toString(){ 148 if (tex != null){ 149 return "TextureTarget[format=" + format + "]"; 150 }else{ 151 return "BufferTarget[format=" + format + "]"; 152 } 153 } 154 } 155 156 /** 157 * <p> 158 * Creates a new FrameBuffer with the given width, height, and number 159 * of samples. If any textures are attached to this FrameBuffer, then 160 * they must have the same number of samples as given in this constructor. 161 * <p> 162 * Note that if the {@link Renderer} does not expose the 163 * {@link Caps#NonPowerOfTwoTextures}, then an exception will be thrown 164 * if the width and height arguments are not power of two. 165 * 166 * @param width The width to use 167 * @param height The height to use 168 * @param samples The number of samples to use for a multisampled 169 * framebuffer, or 1 if the framebuffer should be singlesampled. 170 * 171 * @throws IllegalArgumentException If width or height are not positive. 172 */ 173 public FrameBuffer(int width, int height, int samples){ 174 super(FrameBuffer.class); 175 if (width <= 0 || height <= 0) 176 throw new IllegalArgumentException("FrameBuffer must have valid size."); 177 178 this.width = width; 179 this.height = height; 180 this.samples = samples == 0 ? 1 : samples; 181 } 182 183 protected FrameBuffer(FrameBuffer src){ 184 super(FrameBuffer.class, src.id); 185 /* 186 for (RenderBuffer renderBuf : src.colorBufs){ 187 RenderBuffer clone = renderBuf.createDestructableClone(); 188 if (clone != null) 189 this.colorBufs.add(clone); 190 } 191 192 this.depthBuf = src.depthBuf.createDestructableClone(); 193 */ 194 } 195 196 /** 197 * Enables the use of a depth buffer for this <code>FrameBuffer</code>. 198 * 199 * @param format The format to use for the depth buffer. 200 * @throws IllegalArgumentException If <code>format</code> is not a depth format. 201 */ 202 public void setDepthBuffer(Image.Format format){ 203 if (id != -1) 204 throw new UnsupportedOperationException("FrameBuffer already initialized."); 205 206 if (!format.isDepthFormat()) 207 throw new IllegalArgumentException("Depth buffer format must be depth."); 208 209 depthBuf = new RenderBuffer(); 210 depthBuf.slot = -100; // -100 == special slot for DEPTH_BUFFER 211 depthBuf.format = format; 212 } 213 214 /** 215 * Enables the use of a color buffer for this <code>FrameBuffer</code>. 216 * 217 * @param format The format to use for the color buffer. 218 * @throws IllegalArgumentException If <code>format</code> is not a color format. 219 */ 220 public void setColorBuffer(Image.Format format){ 221 if (id != -1) 222 throw new UnsupportedOperationException("FrameBuffer already initialized."); 223 224 if (format.isDepthFormat()) 225 throw new IllegalArgumentException("Color buffer format must be color/luminance."); 226 227 RenderBuffer colorBuf = new RenderBuffer(); 228 colorBuf.slot = 0; 229 colorBuf.format = format; 230 231 colorBufs.clear(); 232 colorBufs.add(colorBuf); 233 } 234 235 private void checkSetTexture(Texture tex, boolean depth){ 236 Image img = tex.getImage(); 237 if (img == null) 238 throw new IllegalArgumentException("Texture not initialized with RTT."); 239 240 if (depth && !img.getFormat().isDepthFormat()) 241 throw new IllegalArgumentException("Texture image format must be depth."); 242 else if (!depth && img.getFormat().isDepthFormat()) 243 throw new IllegalArgumentException("Texture image format must be color/luminance."); 244 245 // check that resolution matches texture resolution 246 if (width != img.getWidth() || height != img.getHeight()) 247 throw new IllegalArgumentException("Texture image resolution " + 248 "must match FB resolution"); 249 250 if (samples != tex.getImage().getMultiSamples()) 251 throw new IllegalStateException("Texture samples must match framebuffer samples"); 252 } 253 254 /** 255 * If enabled, any shaders rendering into this <code>FrameBuffer</code> 256 * will be able to write several results into the renderbuffers 257 * by using the <code>gl_FragData</code> array. Every slot in that 258 * array maps into a color buffer attached to this framebuffer. 259 * 260 * @param enabled True to enable MRT (multiple rendering targets). 261 */ 262 public void setMultiTarget(boolean enabled){ 263 if (enabled) colorBufIndex = -1; 264 else colorBufIndex = 0; 265 } 266 267 /** 268 * @return True if MRT (multiple rendering targets) is enabled. 269 * @see FrameBuffer#setMultiTarget(boolean) 270 */ 271 public boolean isMultiTarget(){ 272 return colorBufIndex == -1; 273 } 274 275 /** 276 * If MRT is not enabled ({@link FrameBuffer#setMultiTarget(boolean) } is false) 277 * then this specifies the color target to which the scene should be rendered. 278 * <p> 279 * By default the value is 0. 280 * 281 * @param index The color attachment index. 282 * @throws IllegalArgumentException If index is negative or doesn't map 283 * to any attachment on this framebuffer. 284 */ 285 public void setTargetIndex(int index){ 286 if (index < 0 || index >= 16) 287 throw new IllegalArgumentException("Target index must be between 0 and 16"); 288 289 if (colorBufs.size() < index) 290 throw new IllegalArgumentException("The target at " + index + " is not set!"); 291 292 colorBufIndex = index; 293 setUpdateNeeded(); 294 } 295 296 /** 297 * @return The color target to which the scene should be rendered. 298 * 299 * @see FrameBuffer#setTargetIndex(int) 300 */ 301 public int getTargetIndex(){ 302 return colorBufIndex; 303 } 304 305 /** 306 * Set the color texture to use for this framebuffer. 307 * This automatically clears all existing textures added previously 308 * with {@link FrameBuffer#addColorTexture(com.jme3.texture.Texture2D) } 309 * and adds this texture as the only target. 310 * 311 * @param tex The color texture to set. 312 */ 313 public void setColorTexture(Texture2D tex){ 314 clearColorTargets(); 315 addColorTexture(tex); 316 } 317 318 /** 319 * Clears all color targets that were set or added previously. 320 */ 321 public void clearColorTargets(){ 322 colorBufs.clear(); 323 } 324 325 /** 326 * Add a color texture to use for this framebuffer. 327 * If MRT is enabled, then each subsequently added texture can be 328 * rendered to through a shader that writes to the array <code>gl_FragData</code>. 329 * If MRT is not enabled, then the index set with {@link FrameBuffer#setTargetIndex(int) } 330 * is rendered to by the shader. 331 * 332 * @param tex The texture to add. 333 */ 334 public void addColorTexture(Texture2D tex) { 335 if (id != -1) 336 throw new UnsupportedOperationException("FrameBuffer already initialized."); 337 338 Image img = tex.getImage(); 339 checkSetTexture(tex, false); 340 341 RenderBuffer colorBuf = new RenderBuffer(); 342 colorBuf.slot = colorBufs.size(); 343 colorBuf.tex = tex; 344 colorBuf.format = img.getFormat(); 345 346 colorBufs.add(colorBuf); 347 } 348 349 /** 350 * Set the depth texture to use for this framebuffer. 351 * 352 * @param tex The color texture to set. 353 */ 354 public void setDepthTexture(Texture2D tex){ 355 if (id != -1) 356 throw new UnsupportedOperationException("FrameBuffer already initialized."); 357 358 Image img = tex.getImage(); 359 checkSetTexture(tex, true); 360 361 depthBuf = new RenderBuffer(); 362 depthBuf.slot = -100; // indicates GL_DEPTH_ATTACHMENT 363 depthBuf.tex = tex; 364 depthBuf.format = img.getFormat(); 365 } 366 367 /** 368 * @return The number of color buffers attached to this texture. 369 */ 370 public int getNumColorBuffers(){ 371 return colorBufs.size(); 372 } 373 374 /** 375 * @param index 376 * @return The color buffer at the given index. 377 */ 378 public RenderBuffer getColorBuffer(int index){ 379 return colorBufs.get(index); 380 } 381 382 /** 383 * @return The first color buffer attached to this FrameBuffer, or null 384 * if no color buffers are attached. 385 */ 386 public RenderBuffer getColorBuffer() { 387 if (colorBufs.isEmpty()) 388 return null; 389 390 return colorBufs.get(0); 391 } 392 393 /** 394 * @return The depth buffer attached to this FrameBuffer, or null 395 * if no depth buffer is attached 396 */ 397 public RenderBuffer getDepthBuffer() { 398 return depthBuf; 399 } 400 401 /** 402 * @return The height in pixels of this framebuffer. 403 */ 404 public int getHeight() { 405 return height; 406 } 407 408 /** 409 * @return The width in pixels of this framebuffer. 410 */ 411 public int getWidth() { 412 return width; 413 } 414 415 /** 416 * @return The number of samples when using a multisample framebuffer, or 417 * 1 if this is a singlesampled framebuffer. 418 */ 419 public int getSamples() { 420 return samples; 421 } 422 423 @Override 424 public String toString(){ 425 StringBuilder sb = new StringBuilder(); 426 String mrtStr = colorBufIndex >= 0 ? "" + colorBufIndex : "mrt"; 427 sb.append("FrameBuffer[format=").append(width).append("x").append(height) 428 .append("x").append(samples).append(", drawBuf=").append(mrtStr).append("]\n"); 429 if (depthBuf != null) 430 sb.append("Depth => ").append(depthBuf).append("\n"); 431 for (RenderBuffer colorBuf : colorBufs){ 432 sb.append("Color(").append(colorBuf.slot) 433 .append(") => ").append(colorBuf).append("\n"); 434 } 435 return sb.toString(); 436 } 437 438 @Override 439 public void resetObject() { 440 this.id = -1; 441 442 for (int i = 0; i < colorBufs.size(); i++) { 443 colorBufs.get(i).resetObject(); 444 } 445 446 if (depthBuf != null) 447 depthBuf.resetObject(); 448 449 setUpdateNeeded(); 450 } 451 452 @Override 453 public void deleteObject(Object rendererObject) { 454 ((Renderer)rendererObject).deleteFrameBuffer(this); 455 } 456 457 public NativeObject createDestructableClone(){ 458 return new FrameBuffer(this); 459 } 460 } 461