1 /**************************************************************************** 2 Copyright (c) 2010-2012 cocos2d-x.org 3 Copyright (c) 2009 Jason Booth 4 Copyright (c) 2008-2010 Ricardo Quesada 5 Copyright (c) 2011 Zynga Inc. 6 7 http://www.cocos2d-x.org 8 9 Permission is hereby granted, free of charge, to any person obtaining a copy 10 of this software and associated documentation files (the "Software"), to deal 11 in the Software without restriction, including without limitation the rights 12 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 copies of the Software, and to permit persons to whom the Software is 14 furnished to do so, subject to the following conditions: 15 16 The above copyright notice and this permission notice shall be included in 17 all copies or substantial portions of the Software. 18 19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 THE SOFTWARE. 26 ****************************************************************************/ 27 28 /** 29 * enum for jpg 30 * @constant 31 * @type Number 32 */ 33 cc.IMAGE_FORMAT_JPEG = 0; 34 /** 35 * enum for png 36 * @constant 37 * @type Number 38 */ 39 cc.IMAGE_FORMAT_PNG = 1; 40 /** 41 * enum for raw 42 * @constant 43 * @type Number 44 */ 45 cc.IMAGE_FORMAT_RAWDATA = 2; 46 47 /** 48 * @param {Number} x 49 * @return {Number} 50 * Constructor 51 */ 52 cc.NextPOT = function (x) { 53 x = x - 1; 54 x = x | (x >> 1); 55 x = x | (x >> 2); 56 x = x | (x >> 4); 57 x = x | (x >> 8); 58 x = x | (x >> 16); 59 return x + 1; 60 }; 61 62 /** 63 * cc.RenderTexture is a generic rendering target. To render things into it,<br/> 64 * simply construct a render target, call begin on it, call visit on any cocos<br/> 65 * scenes or objects to render them, and call end. For convienience, render texture<br/> 66 * adds a sprite as it's display child with the results, so you can simply add<br/> 67 * the render texture to your scene and treat it like any other CocosNode.<br/> 68 * There are also functions for saving the render texture to disk in PNG or JPG format. 69 * @class 70 * @extends cc.Node 71 */ 72 cc.RenderTexture = cc.Node.extend(/** @lends cc.RenderTexture# */{ 73 /** 74 * the offscreen canvas for rendering and storing the texture 75 * @type HTMLCanvasElement 76 */ 77 canvas:null, 78 /** 79 * stores a reference to the canvas context object 80 * @type CanvasContext 81 */ 82 context:null, 83 _fBO:0, 84 _depthRenderBuffer:0, 85 _oldFBO:0, 86 _texture:null, 87 _uITextureImage:null, 88 _pixelFormat:cc.TEXTURE_2D_PIXEL_FORMAT_RGBA8888, 89 _sprite:null, 90 91 /** 92 * Constructor 93 */ 94 ctor:function () { 95 this.canvas = document.createElement('canvas'); 96 this.context = this.canvas.getContext('2d'); 97 this.setAnchorPoint(cc.p(0, 0)); 98 99 // Listen this event to save render texture before come to background. 100 // Then it can be restored after coming to foreground on Android. 101 /*extension.CCNotificationCenter.sharedNotificationCenter().addObserver(this, 102 callfuncO_selector(CCRenderTexture.listenToBackground), 103 EVENT_COME_TO_BACKGROUND, 104 null);*/ 105 }, 106 107 /** 108 * The sprite 109 * @return {cc.Sprite} 110 */ 111 getSprite:function () { 112 return this._sprite; 113 }, 114 115 /** 116 * @param {cc.Sprite} sprite 117 */ 118 setSprite:function (sprite) { 119 this._sprite = sprite; 120 }, 121 122 /** 123 * @return {HTMLCanvasElement} 124 */ 125 getCanvas:function () { 126 return this.canvas; 127 }, 128 129 /** 130 * @param {cc.Size} size 131 */ 132 setContentSize:function (size) { 133 if (!size) { 134 return; 135 } 136 137 //if (!cc.Size.CCSizeEqualToSize(size, this._contentSize)) { 138 this._super(size); 139 this.canvas.width = size.width * 1.5; 140 this.canvas.height = size.height * 1.5; 141 142 this.context.translate(0, this.canvas.height); 143 //} 144 }, 145 146 /** 147 * @param {Number} width 148 * @param {Number} height 149 * @param {cc.IMAGE_FORMAT_JPEG|cc.IMAGE_FORMAT_PNG|cc.IMAGE_FORMAT_RAWDATA} format 150 * @param {Number} depthStencilFormat 151 * @return {Boolean} 152 */ 153 initWithWidthAndHeight:function (width, height, format, depthStencilFormat) { 154 if (cc.renderContextType == cc.CANVAS) { 155 this.canvas.width = width || 10; 156 this.canvas.height = height || 10; 157 158 this.context.translate(0, this.canvas.height); 159 160 this._sprite = cc.Sprite.createWithTexture(this.canvas); 161 162 return true; 163 } else { 164 //TODO 165 cc.Assert(this._pixelFormat != cc.TEXTURE_2D_PIXEL_FORMAT_A8, "only RGB and RGBA formats are valid for a render texture"); 166 167 try { 168 width *= cc.CONTENT_SCALE_FACTOR(); 169 height *= cc.CONTENT_SCALE_FACTOR(); 170 171 glGetIntegerv(gl.FRAMEBUFFER_BINDING, this._oldFBO); 172 173 // textures must be power of two squared 174 var powW = 0; 175 var powH = 0; 176 177 if (cc.Configuration.getInstance().supportsNPOT()) { 178 powW = width; 179 powH = height; 180 } else { 181 powW = cc.NextPOT(width); 182 powH = cc.NextPOT(height); 183 } 184 185 //void *data = malloc(powW * powH * 4); 186 var data = []; 187 //memset(data, 0, (int)(powW * powH * 4)); 188 for (var i = 0; i < powW * powH * 4; i++) { 189 data[i] = 0; 190 } 191 192 this._pixelFormat = format; 193 194 this._texture = new cc.Texture2D(); 195 if (!this._texture) 196 return false; 197 198 this._texture.initWithData(data, this._pixelFormat, powW, powH, cc.size(width, height)); 199 //free( data ); 200 201 var oldRBO; 202 glGetIntegerv(GL_RENDERBUFFER_BINDING, oldRBO); 203 204 // generate FBO 205 glGenFramebuffers(1, this._fBO); 206 glBindFramebuffer(GL_FRAMEBUFFER, this._fBO); 207 208 // associate texture with FBO 209 glFramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, GL_TEXTURE_2D, this._texture.getName(), 0); 210 211 if (this._depthRenderBuffer != 0) { 212 //create and attach depth buffer 213 glGenRenderbuffers(1, this._depthRenderBuffer); 214 glBindRenderbuffer(GL_RENDERBUFFER, this._depthRenderBuffer); 215 glRenderbufferStorage(GL_RENDERBUFFER, depthStencilFormat, powW, powH); 216 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, this._depthRenderBuffer); 217 218 // if depth format is the one with stencil part, bind same render buffer as stencil attachment 219 if (depthStencilFormat == gl.DEPTH24_STENCIL8) 220 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, this._depthRenderBuffer); 221 } 222 223 // check if it worked (probably worth doing :) ) 224 cc.Assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Could not attach texture to framebuffer"); 225 226 this._texture.setAliasTexParameters(); 227 228 this._sprite = cc.Sprite.createWithTexture(this._texture); 229 230 this._sprite.setScaleY(-1); 231 this.addChild(this._sprite); 232 233 this._sprite.setBlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 234 235 glBindRenderbuffer(GL_RENDERBUFFER, oldRBO); 236 glBindFramebuffer(GL_FRAMEBUFFER, this._oldFBO); 237 } catch (ex) { 238 return false; 239 } 240 return true; 241 } 242 }, 243 244 /** 245 * starts grabbing 246 */ 247 begin:function () { 248 //TODO 249 // Save the current matrix 250 kmGLPushMatrix(); 251 252 var texSize = this._texture.getContentSizeInPixels(); 253 254 // Calculate the adjustment ratios based on the old and new projections 255 var size = cc.Director.getInstance().getWinSizeInPixels(); 256 var widthRatio = size.width / texSize.width; 257 var heightRatio = size.height / texSize.height; 258 259 // Adjust the orthographic projection and viewport 260 glViewport(0, 0, texSize.width, texSize.height); 261 262 var orthoMatrix; 263 kmMat4OrthographicProjection(orthoMatrix, -1.0 / widthRatio, 1.0 / widthRatio, 264 -1.0 / heightRatio, 1.0 / heightRatio, -1, 1); 265 kmGLMultMatrix(orthoMatrix); 266 267 glGetIntegerv(gl.FRAMEBUFFER_BINDING, this._oldFBO); 268 glBindFramebuffer(gl.FRAMEBUFFER, this._fBO);//Will direct drawing to the frame buffer created above 269 }, 270 271 /** 272 * starts rendering to the texture while clearing the texture first.<br/> 273 * This is more efficient then calling -clear first and then -begin 274 * @param {Number} r red 0-255 275 * @param {Number} g green 0-255 276 * @param {Number} b blue 0-255 277 * @param {Number} depthValue 278 * @param {Number} stencilValue 279 * @param {Number} a alpha 0-255 0 is transparent 280 */ 281 beginWithClear:function (r, g, b, a, depthValue, stencilValue) { 282 //TODO 283 var clearColor; 284 switch (arguments.length) { 285 case 4: 286 this.begin(); 287 288 // save clear color 289 clearColor = [0, 0, 0, 0]; 290 glGetFloatv(GL_COLOR_CLEAR_VALUE, clearColor); 291 292 glClearColor(r, g, b, a); 293 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 294 295 // restore clear color 296 glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); 297 break; 298 case 5: 299 this.begin(); 300 301 // save clear color 302 clearColor = [0, 0, 0, 0]; 303 var depthClearValue; 304 glGetFloatv(GL_COLOR_CLEAR_VALUE, clearColor); 305 glGetFloatv(GL_DEPTH_CLEAR_VALUE, depthClearValue); 306 307 glClearColor(r, g, b, a); 308 glClearDepth(depthValue); 309 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 310 311 // restore clear color 312 glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); 313 glClearDepth(depthClearValue); 314 break; 315 case 6: 316 this.begin(); 317 318 // save clear color 319 clearColor = [0, 0, 0, 0]; 320 var depthClearValue; 321 var stencilClearValue; 322 glGetFloatv(GL_COLOR_CLEAR_VALUE, clearColor); 323 glGetFloatv(GL_DEPTH_CLEAR_VALUE, depthClearValue); 324 glGetIntegerv(GL_STENCIL_CLEAR_VALUE, stencilClearValue); 325 326 glClearColor(r, g, b, a); 327 glClearDepth(depthValue); 328 glClearStencil(stencilValue); 329 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 330 331 // restore clear color 332 glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]); 333 glClearDepth(depthClearValue); 334 glClearStencil(stencilClearValue); 335 break; 336 default : 337 throw "unknown arguments"; 338 break; 339 } 340 }, 341 342 /** 343 * ends grabbing 344 */ 345 end:function () { 346 glBindFramebuffer(GL_FRAMEBUFFER, this._oldFBO); 347 kmGLPopMatrix(); 348 349 var director = cc.Director.getInstance(); 350 351 var size = director.getWinSizeInPixels(); 352 353 // restore viewport 354 glViewport(0, 0, size.width * cc.CONTENT_SCALE_FACTOR(), size.height * cc.CONTENT_SCALE_FACTOR()); 355 356 // special viewport for 3d projection + retina display 357 if (director.getProjection() == cc.DIRECTOR_PROJECTION_3D && cc.CONTENT_SCALE_FACTOR() != 1) { 358 glViewport((-size.width / 2), (-size.height / 2), (size.width * cc.CONTENT_SCALE_FACTOR()), (size.height * cc.CONTENT_SCALE_FACTOR())); 359 } 360 361 director.setProjection(director.getProjection()); 362 }, 363 364 /** 365 * clears the texture with a color 366 * @param {Number} r red 0-255 367 * @param {Number} g green 0-255 368 * @param {Number} b blue 0-255 369 * @param {Number} a alpha 0-255 370 */ 371 clear:function (r, g, b, a) { 372 if (cc.renderContextType == cc.CANVAS) { 373 var rect = r; 374 if (rect) { 375 this.context.clearRect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height); 376 } else { 377 this.context.clearRect(0, 0, this.canvas.width, -this.canvas.height); 378 } 379 } else { 380 this.beginWithClear(r, g, b, a); 381 this.end(); 382 } 383 }, 384 385 /** 386 * clears the texture with a specified depth value 387 * @param {Number} dep 388 */ 389 clearDepth:function (dep) { 390 this.begin(); 391 //! save old depth value 392 var depthClearValue; 393 glGetFloatv(GL_DEPTH_CLEAR_VALUE, depthClearValue); 394 395 glClearDepth(depthValue); 396 glClear(GL_DEPTH_BUFFER_BIT); 397 398 // restore clear color 399 glClearDepth(depthClearValue); 400 this.end(); 401 }, 402 403 /** 404 * clears the texture with a specified stencil value 405 * @param {Number} stencilValue 406 */ 407 clearStencil:function (stencilValue) { 408 // save old stencil value 409 var stencilClearValue; 410 glGetIntegerv(GL_STENCIL_CLEAR_VALUE, stencilClearValue); 411 412 glClearStencil(stencilValue); 413 glClear(GL_STENCIL_BUFFER_BIT); 414 415 // restore clear color 416 glClearStencil(stencilClearValue); 417 }, 418 419 /** 420 * creates a new CCImage from with the texture's data. Caller is responsible for releasing it by calling delete. 421 * @return {cc.Image} 422 */ 423 newCCImage:function () { 424 cc.Assert(this._pixelFormat == cc.TEXTURE_2D_PIXEL_FORMAT_RGBA8888, "only RGBA8888 can be saved as image"); 425 426 if (!this._texture) { 427 return null; 428 } 429 430 var size = this._texture.getContentSizeInPixels(); 431 432 // to get the image size to save 433 // if the saving image domain exeeds the buffer texture domain, 434 // it should be cut 435 var nSavedBufferWidth = size.width; 436 var nSavedBufferHeight = size.height; 437 438 var pBuffer = null; 439 var pTempData = null; 440 var pImage = new cc.Image(); 441 442 try { 443 pBuffer = []; 444 pBuffer.length = nSavedBufferWidth * nSavedBufferHeight * 4; 445 if (!(pBuffer)) 446 return pImage; 447 448 pTempData = []; 449 pTempData.length = nSavedBufferWidth * nSavedBufferHeight * 4; 450 if (!(pTempData)) { 451 pBuffer = null; 452 return pImage; 453 } 454 455 this.begin(); 456 glPixelStorei(GL_PACK_ALIGNMENT, 1); 457 glReadPixels(0, 0, nSavedBufferWidth, nSavedBufferHeight, GL_RGBA, GL_UNSIGNED_BYTE, pTempData); 458 this.end(); 459 460 // to get the actual texture data 461 // #640 the image read from rendertexture is upseted 462 for (var i = 0; i < nSavedBufferHeight; ++i) { 463 this._memcpy(pBuffer, i * nSavedBufferWidth * 4, 464 pTempData, (nSavedBufferHeight - i - 1) * nSavedBufferWidth * 4, 465 nSavedBufferWidth * 4); 466 } 467 468 pImage.initWithImageData(pBuffer, nSavedBufferWidth * nSavedBufferHeight * 4, cc.FMT_RAWDATA, nSavedBufferWidth, nSavedBufferHeight, 8); 469 } catch (ex) { 470 return pImage; 471 } 472 473 pBuffer = null; 474 pTempData = null; 475 return pImage; 476 }, 477 478 _memcpy:function (destArr, destIndex, srcArr, srcIndex, size) { 479 for (var i = 0; i < size; i++) { 480 destArr[destIndex + i] = srcArr[srcIndex + i]; 481 } 482 }, 483 484 /** 485 * saves the texture into a file using JPEG format. The file will be saved in the Documents folder. 486 * Returns YES if the operation is successful. 487 * @param {Number} filePath 488 * @param {Number} format 489 */ 490 saveToFile:function (filePath, format) { 491 if (!format) 492 filePath = cc.FileUtils.getInstance().getWriteablePath() + filePath; 493 format = format || cc.IMAGE_FORMAT_JPEG; 494 495 cc.Assert(format == cc.IMAGE_FORMAT_JPEG || format == cc.IMAGE_FORMAT_PNG, 496 "the image can only be saved as JPG or PNG format"); 497 498 var pImage = this.newCCImage(); 499 if (pImage) { 500 return pImage.saveToFile(filePath, true); 501 } 502 return false; 503 }, 504 505 /** 506 * Listen "come to background" message, and save render texture. It only has effect on Android. 507 * @param {cc.Class} obj 508 */ 509 listenToBackground:function (obj) { 510 if (cc.ENABLE_CACHE_TEXTURE_DATA) { 511 cc.SAFE_DELETE(this.pITextureImage); 512 513 // to get the rendered texture data 514 this.pITextureImage = this.newCCImage(); 515 516 if (this.pITextureImage) { 517 var s = this._texture.getContentSizeInPixels(); 518 VolatileTexture.addDataTexture(this._texture, this.pITextureImage.getData(), cc.TEXTURE_2D_PIXEL_FORMAT_RGBA8888, s); 519 } else { 520 cc.log("Cache rendertexture failed!"); 521 } 522 } 523 } 524 }); 525 526 /** 527 * creates a RenderTexture object with width and height in Points and a pixel format, only RGB and RGBA formats are valid 528 * @param {Number} width 529 * @param {Number} height 530 * @param {cc.IMAGE_FORMAT_JPEG|cc.IMAGE_FORMAT_PNG|cc.IMAGE_FORMAT_RAWDATA} format 531 * @param {Number} depthStencilFormat 532 * @return {cc.RenderTexture} 533 * @example 534 * // Example 535 * var rt = cc.RenderTexture.create() 536 */ 537 cc.RenderTexture.create = function (width, height, format, depthStencilFormat) { 538 format = format || cc.TEXTURE_2D_PIXEL_FORMAT_RGBA8888; 539 depthStencilFormat = depthStencilFormat || 0; 540 541 var ret = new cc.RenderTexture(); 542 if (ret && ret.initWithWidthAndHeight(width, height, format, depthStencilFormat)) { 543 return ret; 544 } 545 return null; 546 }; 547