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