Cocos2d-x 3.x RenderTexture渲染纹理源码分析

最近在学习3.x的源码,3.x的渲染机制改动比较大,改变了之前在每帧遍历所有的Node树节点时通过draw()方法中的直接渲染,而是通过生成渲染指令并将渲染指令发送到渲染队列,在每一帧结束时进行延迟渲染,这样就把游戏循环和渲染分离开来,2dx官方说要把渲染放在一个独立的线程来处理,按照3.x现在的架构可能不久就会实现。

Cocos2d-x提供了一个RenderTexture类来支持把帧缓冲中的数据渲染到一块纹理之上,通过它可以来实现游戏截图,获取纹理上的像素信息等效果,在3.x的RenderTexutrue则是使用了GroupCommand组渲染指令,将其内部一系列的渲染指令放到一个渲染队列中,这样这个组内的渲染指令将单独使用一个渲染队列,不受其他渲染指令的影响(如渲染的优先级)。

我使用的是Cocos2d-x3.2的版本,RenderTexture类在2d/misc-nodes/CCRenderTexture.h/.cpp下。

把源码都加了注释,可以通过RenderTexture整个类的构造->初始化->绘制过程->析构,来大致了解一下RenderTextrue是如何将这缓冲的数据渲染到纹理的工作流程。


CCRenderTexture.h文件

#ifndef __CCRENDER_TEXTURE_H__
#define __CCRENDER_TEXTURE_H__

#include "2d/CCNode.h"
#include "2d/CCSprite.h"
#include "platform/CCImage.h"
#include "renderer/CCGroupCommand.h"
#include "renderer/CCCustomCommand.h"

NS_CC_BEGIN

class EventCustom;

class CC_DLL RenderTexture : public Node 
{
protected:
    
    //flags: whether generate new modelView and projection matrix or not
    
    //是否保持矩阵
    bool         _keepMatrix;
    Rect         _rtTextureRect;
    Rect         _fullRect;
    Rect         _fullviewPort;
    
    //帧缓冲对象
    GLuint       _FBO;
    //深度渲染缓冲对象
    GLuint       _depthRenderBufffer;
    //记录默认帧缓冲对象
    GLint        _oldFBO;
    //纹理对象
    Texture2D* _texture;
    Texture2D* _textureCopy;    // a copy of _texture
    //Image对象
    Image*     _UITextureImage;
    //客户端像素格式
    Texture2D::PixelFormat _pixelFormat;
    
    // code for "auto" update
    //清屏标识位
    GLbitfield   _clearFlags;
    //颜色缓冲区清屏色
    Color4F    _clearColor;
    //深度缓冲区清屏值
    GLclampf     _clearDepth;
    //模板缓冲区清屏值
    GLint        _clearStencil;
    //是否自动绘制
    bool         _autoDraw;

    /** The Sprite being used.
     The sprite, by default, will use the following blending function: GL_ONE, GL_ONE_MINUS_SRC_ALPHA.
     The blending function can be changed in runtime by calling:
     - renderTexture->getSprite()->setBlendFunc((BlendFunc){GL_ONE, GL_ONE_MINUS_SRC_ALPHA});
     */
    //渲染到纹理时用到的精灵
    Sprite* _sprite;
    
    //组渲染指令(将下面所有的渲染指令放到同一个渲染队列中执行)
    GroupCommand _groupCommand;
    //开始时清屏渲染指令
    CustomCommand _beginWithClearCommand;
    //清理深度缓冲区渲染指令
    CustomCommand _clearDepthCommand;
    //清屏渲染指令
    CustomCommand _clearCommand;
    //开始渲染指令
    CustomCommand _beginCommand;
    //结束渲染指令
    CustomCommand _endCommand;
    //将纹理保存到文件的渲染指令
    CustomCommand _saveToFileCommand;
    
    Mat4 _oldTransMatrix, _oldProjMatrix;
    Mat4 _transformMatrix, _projectionMatrix;

};

// end of textures group
/// @}

NS_CC_END

#endif //__CCRENDER_TEXTURE_H__

CCRenderTexture.cpp文件

#include "2d/CCRenderTexture.h"

#include "base/ccUtils.h"
#include "platform/CCImage.h"
#include "platform/CCFileUtils.h"
#include "2d/CCGrid.h"
#include "base/CCEventType.h"
#include "base/CCConfiguration.h"
#include "base/CCConfiguration.h"
#include "base/CCDirector.h"
#include "base/CCEventListenerCustom.h"
#include "base/CCEventDispatcher.h"
#include "renderer/CCGLProgram.h"
#include "renderer/ccGLStateCache.h"
#include "renderer/CCTextureCache.h"
#include "renderer/CCRenderer.h"
#include "renderer/CCGroupCommand.h"
#include "renderer/CCCustomCommand.h"

#include "CCGL.h"


NS_CC_BEGIN

// implementation RenderTexture
RenderTexture::RenderTexture()
: _FBO(0)
, _depthRenderBufffer(0)
, _oldFBO(0)
, _texture(0)
, _textureCopy(0)
, _UITextureImage(nullptr)
, _pixelFormat(Texture2D::PixelFormat::RGBA8888)
, _clearFlags(0)
, _clearColor(Color4F(0,0,0,0))
, _clearDepth(0.0f)
, _clearStencil(0)
, _autoDraw(false)
, _sprite(nullptr)
, _keepMatrix(false)
, _rtTextureRect(Rect::ZERO)
, _fullRect(Rect::ZERO)
, _fullviewPort(Rect::ZERO)
{
#if CC_ENABLE_CACHE_TEXTURE_DATA
    // Listen this event to save render texture before come to background.
    // Then it can be restored after coming to foreground on Android.
    //使用事件机制(observer)来监听切到后台和切回前台事件,设置响应的回调方法
    auto toBackgroundListener = EventListenerCustom::create(EVENT_COME_TO_BACKGROUND, CC_CALLBACK_1(RenderTexture::listenToBackground, this));
    _eventDispatcher->addEventListenerWithSceneGraphPriority(toBackgroundListener, this);

    auto toForegroundListener = EventListenerCustom::create(EVENT_COME_TO_FOREGROUND, CC_CALLBACK_1(RenderTexture::listenToForeground, this));
    _eventDispatcher->addEventListenerWithSceneGraphPriority(toForegroundListener, this);
#endif
}

RenderTexture::~RenderTexture()
{
    CC_SAFE_RELEASE(_sprite);
    CC_SAFE_RELEASE(_textureCopy);
    
    //释放帧缓冲对象
    glDeleteFramebuffers(1, &_FBO);
    if (_depthRenderBufffer)
    {
        //释放深度渲染缓冲对象
        glDeleteRenderbuffers(1, &_depthRenderBufffer);
    }
    CC_SAFE_DELETE(_UITextureImage);
}

/**
 *  进入游戏后台的事件回调
 *
 *  @param event 事件对象
 */
void RenderTexture::listenToBackground(EventCustom *event)
{
    // We have not found a way to dispatch the enter background message before the texture data are destroyed.
    // So we disable this pair of message handler at present.
#if 0
    //使用了纹理缓存,Android Activiy切到后台时会将纹理缓存释放,切回前台时重新加载
#if CC_ENABLE_CACHE_TEXTURE_DATA
    //释放之前的渲染目标
    CC_SAFE_DELETE(_UITextureImage);
    
    // to get the rendered texture data
    //创建新的渲染目标,是一个Image对象
    _UITextureImage = newImage(false);

    if (_UITextureImage)
    {
        //获取纹理的大小
        const Size& s = _texture->getContentSizeInPixels();
        //在Android平台Activity切换到后台时,纹理将被释放,记录下切换到后台时的纹理信息,以便切回前台时重新创建
        VolatileTextureMgr::addDataTexture(_texture, _UITextureImage->getData(), s.width * s.height * 4, Texture2D::PixelFormat::RGBA8888, s);
        
        if ( _textureCopy )
        {
            VolatileTextureMgr::addDataTexture(_textureCopy, _UITextureImage->getData(), s.width * s.height * 4, Texture2D::PixelFormat::RGBA8888, s);
        }
    }
    else
    {
        CCLOG("Cache rendertexture failed!");
    }
    
    //释放帧缓冲对象
    glDeleteFramebuffers(1, &_FBO);
    _FBO = 0;
#endif
#endif
}

/**
 *  游戏切回前台时间回调
 *
 *  @param event 事件对象
 */
void RenderTexture::listenToForeground(EventCustom *event)
{
#if 0
#if CC_ENABLE_CACHE_TEXTURE_DATA
    // -- regenerate frame buffer object and attach the texture
    //检查帧缓冲绑定状态,返回到_obdFBO中
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO);
    //生成帧缓冲对象
    glGenFramebuffers(1, &_FBO);
    //绑定帧缓冲对象
    glBindFramebuffer(GL_FRAMEBUFFER, _FBO);
    
    //不使用抗锯齿模糊
    _texture->setAliasTexParameters();
    
    if ( _textureCopy )
    {
        _textureCopy->setAliasTexParameters();
    }
    
    //将帧缓冲数据输出到纹理
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture->getName(), 0);
    //还原帧缓冲绑定状态
    glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO);
#endif
#endif
}

/**
 *  创建RenderTexture
 *
 *  @param w                    宽度
 *  @param h                    高度
 *  @param format               客户端像素格式
 *
 *  @return RenderTexture对象
 */
RenderTexture * RenderTexture::create(int w, int h, Texture2D::PixelFormat eFormat)
{
    RenderTexture *ret = new RenderTexture();

    if(ret && ret->initWithWidthAndHeight(w, h, eFormat))
    {
        ret->autorelease();
        return ret;
    }
    CC_SAFE_DELETE(ret);
    return nullptr;
}

/**
 *  创建RenderTexture
 *
 *  @param w                    宽度
 *  @param h                    高度
 *  @param format               客户端像素格式
 *  @param depthStencilFormat   深度模板缓冲格式
 *
 *  @return RenderTexture对象
 */
RenderTexture * RenderTexture::create(int w ,int h, Texture2D::PixelFormat eFormat, GLuint uDepthStencilFormat)
{
    RenderTexture *ret = new RenderTexture();

    if(ret && ret->initWithWidthAndHeight(w, h, eFormat, uDepthStencilFormat))
    {
        ret->autorelease();
        return ret;
    }
    CC_SAFE_DELETE(ret);
    return nullptr;
}

/**
 *  创建RenderTexture
 *
 *  @param w                    宽度
 *  @param h                    高度
 *
 *  @return RenderTexture对象
 */
RenderTexture * RenderTexture::create(int w, int h)
{
    RenderTexture *ret = new RenderTexture();

    if(ret && ret->initWithWidthAndHeight(w, h, Texture2D::PixelFormat::RGBA8888, 0))
    {
        ret->autorelease();
        return ret;
    }
    CC_SAFE_DELETE(ret);
    return nullptr;
}

/**
 *  初始化RenderTexture
 *
 *  @param w                    宽度
 *  @param h                    高度
 *  @param format               客户端像素格式
 *
 *  @return true: 初始化成功 false: 初始化失败
 */
bool RenderTexture::initWithWidthAndHeight(int w, int h, Texture2D::PixelFormat eFormat)
{
    return initWithWidthAndHeight(w, h, eFormat, 0);
}

/**
 *  初始化RenderTexture
 *
 *  @param w                    宽度
 *  @param h                    高度
 *  @param format               客户端像素格式
 *  @param depthStencilFormat   深度模板缓冲格式
 *
 *  @return true: 初始化成功 false: 初始化失败
 */
bool RenderTexture::initWithWidthAndHeight(int w, int h, Texture2D::PixelFormat format, GLuint depthStencilFormat)
{
    //RenderTextrue只支持RGB和RGBA的客户端像素格式
    CCASSERT(format != Texture2D::PixelFormat::A8, "only RGB and RGBA formats are valid for a render texture");

    bool ret = false;
    void *data = nullptr;
    do 
    {
        _fullRect = _rtTextureRect = Rect(0,0,w,h);
        //Size size = Director::getInstance()->getWinSizeInPixels();
        //_fullviewPort = Rect(0,0,size.width,size.height);
        //宽度和高度乘以缩放比
        w = (int)(w * CC_CONTENT_SCALE_FACTOR());
        h = (int)(h * CC_CONTENT_SCALE_FACTOR());
        _fullviewPort = Rect(0,0,w,h);
        
        //查看帧缓冲绑定的状态,返回到oldFBO中
        glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO);

        // textures must be power of two squared
        //保存纹理的宽度和高度
        int powW = 0;
        int powH = 0;
        
        //检查设备是否支持纹理为非2的幂次方
        if (Configuration::getInstance()->supportsNPOT())
        {
            //支持就用RenderTexture的大小作为纹理的大小
            powW = w;
            powH = h;
        }
        else
        {
            //不支持,则转换为2的幂次方
            powW = ccNextPOT(w);
            powH = ccNextPOT(h);
        }
        
        //根据纹理的大小申请的字节数,每个像素4字节
        auto dataLen = powW * powH * 4;
        //申请内存
        data = malloc(dataLen);
        //申请失败,跳出
        CC_BREAK_IF(! data);
        //使用内存
        memset(data, 0, dataLen);
        
        //客户端像素格式
        _pixelFormat = format;
        
        //创建一个纹理对象
        _texture = new Texture2D();
        if (_texture)
        {
            //初始化纹理
            _texture->initWithData(data, dataLen, (Texture2D::PixelFormat)_pixelFormat, powW, powH, Size((float)w, (float)h));
        }
        else
        {
            break;
        }
        GLint oldRBO;
        glGetIntegerv(GL_RENDERBUFFER_BINDING, &oldRBO);
        
        if (Configuration::getInstance()->checkForGLExtension("GL_QCOM"))
        {
            _textureCopy = new Texture2D();
            if (_textureCopy)
            {
                _textureCopy->initWithData(data, dataLen, (Texture2D::PixelFormat)_pixelFormat, powW, powH, Size((float)w, (float)h));
            }
            else
            {
                break;
            }
        }

        // generate FBO
        //生成帧缓冲对象(数量为1个)
        glGenFramebuffers(1, &_FBO);
        //绑定帧缓冲对象
        glBindFramebuffer(GL_FRAMEBUFFER, _FBO);

        // associate texture with FBO
        //设置将帧缓冲区颜色数据输出到纹理
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture->getName(), 0);

        //使用了深度缓冲
        if (depthStencilFormat != 0)
        {
            //create and attach depth buffer
            //创建1个渲染深度缓冲对象并绑定
            glGenRenderbuffers(1, &_depthRenderBufffer);
            glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBufffer);
            //设置渲染缓冲对象的像素格式,尺寸
            glRenderbufferStorage(GL_RENDERBUFFER, depthStencilFormat, (GLsizei)powW, (GLsizei)powH);
            //将渲染缓冲对象绑定到当前的帧缓冲中名为GL_DEPTH_ATTACHMENT的逻辑缓冲区中,帧缓冲将修改该附加点的状态
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBufffer);

            // if depth format is the one with stencil part, bind same render buffer as stencil attachment
            //深度缓冲格式是24位深度,8位模版缓冲,则将渲染深度缓冲对象绑定到当前帧缓冲名为GL_STENCIL_ATTACHMENT的逻辑缓冲区中,修改附加点的状态。
            if (depthStencilFormat == GL_DEPTH24_STENCIL8)
            {
                glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBufffer);
            }
        }

        // check if it worked (probably worth doing :) )
        //帧缓冲状态必须是完成状态
        CCASSERT(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Could not attach texture to framebuffer");
        
        //设置纹理不使用抗锯齿模糊
        _texture->setAliasTexParameters();

        // retained
        //将纹理绑定到精灵,将精灵绑定到RederTexture
        setSprite(Sprite::createWithTexture(_texture));
        
        //释放纹理
        _texture->release();
        //设置精灵Y翻转
        _sprite->setFlippedY(true);
        //设置alpha的混合模式
        _sprite->setBlendFunc( BlendFunc::ALPHA_PREMULTIPLIED );
        
        //还原渲染缓冲对象和帧缓冲对象
        glBindRenderbuffer(GL_RENDERBUFFER, oldRBO);
        glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO);
        
        // Diabled by default.
        _autoDraw = false;
        
        // add sprite for backward compatibility
        addChild(_sprite);
        
        ret = true;
    } while (0);
    
    //释放像素所占的内存
    CC_SAFE_FREE(data);
    
    return ret;
}

/**
 *  设置保持矩阵
 *
 *  @param keepMatrix 是否保持矩阵
 */
void RenderTexture::setKeepMatrix(bool keepMatrix)
{
    _keepMatrix = keepMatrix;
}

/**
 *  设置虚拟视口
 *
 *  @param rtBegin      绘制区域左下角在屏幕上的位置(绘制区域平移)
 *  @param fullRect     GL的绘制区域与fullViewport一致
 *  @param fullViewport GL的绘制区域(绘制区域缩放)
 */
void RenderTexture::setVirtualViewport(const Vec2& rtBegin, const Rect& fullRect, const Rect& fullViewport)
{
    _rtTextureRect.origin.x = rtBegin.x;
    _rtTextureRect.origin.y = rtBegin.y;

    _fullRect = fullRect;

    _fullviewPort = fullViewport;
}

/**
 *  清屏操作
 *
 *  @param r R通道
 *  @param g G通道
 *  @param b B通道
 *  @param a Alpha通道
 */
void RenderTexture::beginWithClear(float r, float g, float b, float a)
{
    beginWithClear(r, g, b, a, 0, 0, GL_COLOR_BUFFER_BIT);
}

/**
 *  清屏操作
 *
 *  @param r            R通道
 *  @param g            G通道
 *  @param b            B通道
 *  @param a            Alpha通道
 *  @param depthValue   深度值
 */
void RenderTexture::beginWithClear(float r, float g, float b, float a, float depthValue)
{
    beginWithClear(r, g, b, a, depthValue, 0, GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
}

/**
 *  清屏操作
 *
 *  @param r            R通道
 *  @param g            G通道
 *  @param b            B通道
 *  @param a            Alpha通道
 *  @param depthValue   深度值
 *  @param stencilValue 模板值
 */
void RenderTexture::beginWithClear(float r, float g, float b, float a, float depthValue, int stencilValue)
{
    beginWithClear(r, g, b, a, depthValue, stencilValue, GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
}

/**
 *  清屏操作
 *
 *  @param r            R通道
 *  @param g            G通道
 *  @param b            B通道
 *  @param a            Alpha通道
 *  @param depthValue   深度值
 *  @param stencilValue 模板值
 *  @param flags        清屏标志位
 */
void RenderTexture::beginWithClear(float r, float g, float b, float a, float depthValue, int stencilValue, GLbitfield flags)
{
    setClearColor(Color4F(r, g, b, a));

    setClearDepth(depthValue);

    setClearStencil(stencilValue);

    setClearFlags(flags);
    
    //开始绘制
    this->begin();

    //clear screen
    //初始化开始清屏渲染指令
    _beginWithClearCommand.init(_globalZOrder);
    //设置回调
    _beginWithClearCommand.func = CC_CALLBACK_0(RenderTexture::onClear, this);
    //添加指令到渲染队列
    Director::getInstance()->getRenderer()->addCommand(&_beginWithClearCommand);
}

//TODO find a better way to clear the screen, there is no need to rebind render buffer there.
void RenderTexture::clear(float r, float g, float b, float a)
{
    this->beginWithClear(r, g, b, a);
    this->end();
}

/**
 *  清理深度缓冲区
 *
 *  @param depthValue 深度值
 */
void RenderTexture::clearDepth(float depthValue)
{
    setClearDepth(depthValue);

    this->begin();

    _clearDepthCommand.init(_globalZOrder);
    _clearDepthCommand.func = CC_CALLBACK_0(RenderTexture::onClearDepth, this);

    Director::getInstance()->getRenderer()->addCommand(&_clearDepthCommand);

    this->end();
}

/**
 *  清理模板缓冲区
 *
 *  @param stencilValue 模板值
 */
void RenderTexture::clearStencil(int stencilValue)
{
    // save old stencil value
    int stencilClearValue;
    glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &stencilClearValue);

    glClearStencil(stencilValue);
    glClear(GL_STENCIL_BUFFER_BIT);

    // restore clear color
    glClearStencil(stencilClearValue);
}

void RenderTexture::visit(Renderer *renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
    //重写父类的visit方法,不再遍历所有子元素
    
    // override visit.
	// Don't call visit on its children
    if (!_visible)
    {
        return;
    }
	
    //处理父节点的标识
    uint32_t flags = processParentFlags(parentTransform, parentFlags);

    
    //为了让开发者可以将2dx2.0的项目移植到2dx3.0的版本,扔支持矩阵堆栈
    //但该方法已经废弃了,不推荐使用
    Director* director = Director::getInstance();
    // IMPORTANT:
    // To ease the migration to v3.0, we still support the Mat4 stack,
    // but it is deprecated and your code should not rely on it
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _modelViewTransform);
    
    //绘制自己
    _sprite->visit(renderer, _modelViewTransform, flags);
    draw(renderer, _modelViewTransform, flags);
    
    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    _orderOfArrival = 0;
}


/**
 *  将纹理保存到磁盘的文件中(只支持png和jpg两种压缩纹理格式)
 *
 *  @param fileName 文件名
 *  @param isRGBA   是否为RGBA
 *
 *  @return true: 保存成功 false: 保存失败
 */
bool RenderTexture::saveToFile(const std::string& filename, bool isRGBA)
{
    //将文件名转为小写
    std::string basename(filename);
    std::transform(basename.begin(), basename.end(), basename.begin(), ::tolower);
    
    //包含.png,没有alpha通道
    if (basename.find(".png") != std::string::npos)
    {
        return saveToFile(filename, Image::Format::PNG, isRGBA);
    }
    else if (basename.find(".jpg") != std::string::npos)
    {
        //包含.jpg,有alpha通道
        if (isRGBA) CCLOG("RGBA is not supported for JPG format.");
        return saveToFile(filename, Image::Format::JPG, false);
    }
    else
    {
        CCLOG("Only PNG and JPG format are supported now!");
    }
    
    //默认将保存为JGP格式
    return saveToFile(filename, Image::Format::JPG, false);
}

/**
 *  将纹理保存到磁盘的文件中
 *
 *  @param fileName 文件名
 *  @param format   图片格式
 *  @param isRGBA   是否为RGBA
 *
 *  @return true: 保存成功 false: 保存失败
 */
bool RenderTexture::saveToFile(const std::string& fileName, Image::Format format, bool isRGBA)
{
    CCASSERT(format == Image::Format::JPG || format == Image::Format::PNG,
             "the image can only be saved as JPG or PNG format");
    if (isRGBA && format == Image::Format::JPG) CCLOG("RGBA is not supported for JPG format");
    
    //保存图片文件的路径
    std::string fullpath = FileUtils::getInstance()->getWritablePath() + fileName;
    //初始化将纹理保存到文件的自定义渲染指令
    _saveToFileCommand.init(_globalZOrder);
    //设置自定义渲染指令的回调函数
    _saveToFileCommand.func = CC_CALLBACK_0(RenderTexture::onSaveToFile, this, fullpath, isRGBA);
    
    Director::getInstance()->getRenderer()->addCommand(&_saveToFileCommand);
    return true;
}

/**
 *  文件保存到磁盘文件的渲染回调
 *
 *  @param filename 文件名
 *  @param isRGBA   是否为RGBA
 */
void RenderTexture::onSaveToFile(const std::string& filename, bool isRGBA)
{
    //创建一个image对象
    Image *image = newImage(true);
    if (image)
    {
        //保存到文件中
        image->saveToFile(filename.c_str(), !isRGBA);
    }
    
    //释放内存
    CC_SAFE_DELETE(image);
}

/* get buffer as Image */
/**
 *  创建一个Image对象
 *
 *  @param fliimage 是否将像素点数据上下倒置
 *
 *  @return Image对象
 */
Image* RenderTexture::newImage(bool fliimage)
{
    
    //只有RGBA8888像素格式可以保存为一个Image对象
    CCASSERT(_pixelFormat == Texture2D::PixelFormat::RGBA8888, "only RGBA8888 can be saved as image");

    if (nullptr == _texture)
    {
        return nullptr;
    }

    //获取纹理的大小
    const Size& s = _texture->getContentSizeInPixels();

    // to get the image size to save
    //        if the saving image domain exceeds the buffer texture domain,
    //        it should be cut
    //保存Image对象的大小,如果image的大小比纹理的大,则会被裁剪
    int savedBufferWidth = (int)s.width;
    int savedBufferHeight = (int)s.height;
    
    //保存最终像素数据的数组
    GLubyte *buffer = nullptr;
    //保存临时像素数据的数组
    GLubyte *tempData = nullptr;
    Image *image = new Image();

    do
    {
        //最终像素数据的数组申请内存
        CC_BREAK_IF(! (buffer = new GLubyte[savedBufferWidth * savedBufferHeight * 4]));
        
        //临时像素数据的数组申请内存
        if(! (tempData = new GLubyte[savedBufferWidth * savedBufferHeight * 4]))
        {
            delete[] buffer;
            buffer = nullptr;
            break;
        }
        
        //检查帧缓冲绑定状态,返回到_oldFBO帧缓冲对象中
        glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO);
        
        //绑定帧缓冲到_FBO帧缓冲对象中
        glBindFramebuffer(GL_FRAMEBUFFER, _FBO);

        //TODO move this to configration, so we don't check it every time
        /*  Certain Qualcomm Andreno gpu's will retain data in memory after a frame buffer switch which corrupts the render to the texture. The solution is to clear the frame buffer before rendering to the texture. However, calling glClear has the unintended result of clearing the current texture. Create a temporary texture to overcome this. At the end of RenderTexture::begin(), switch the attached texture to the second one, call glClear, and then switch back to the original texture. This solution is unnecessary for other devices as they don't have the same issue with switching frame buffers.
         */
        if (Configuration::getInstance()->checkForGLExtension("GL_QCOM"))
        {
            // -- bind a temporary texture so we can clear the render buffer without losing our texture
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _textureCopy->getName(), 0);
            CHECK_GL_ERROR_DEBUG();
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture->getName(), 0);
        }
        
        //设置像素存储模式(读取到客户端)
        glPixelStorei(GL_PACK_ALIGNMENT, 1);
        
        //将帧缓冲中的像素数据读到客户端中,保存在tempData数组中
        //像素矩形的起始位置,像素区域的大小,像素格式,数据类型,颜色数据指针
        //glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels)
        glReadPixels(0,0,savedBufferWidth, savedBufferHeight,GL_RGBA,GL_UNSIGNED_BYTE, tempData);
        
        //还原帧缓冲的绑定状态
        glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO);

        //是否保存到文件,保存到文件需要将像素点的数据上下颠倒
        if ( fliimage ) // -- flip is only required when saving image to file
        {
            // to get the actual texture data
            // #640 the image read from rendertexture is dirty
            
            ///将pTempData从下往上拷到buffer数组中,initWithWidthAndHeight中精灵成员设置了Y镜像,所以图像是上下反
            for (int i = 0; i < savedBufferHeight; ++i)
            {
                memcpy(&buffer[i * savedBufferWidth * 4],
                       &tempData[(savedBufferHeight - i - 1) * savedBufferWidth * 4],
                       savedBufferWidth * 4);
            }
            
            //初始化image对象
            image->initWithRawData(buffer, savedBufferWidth * savedBufferHeight * 4, savedBufferWidth, savedBufferHeight, 8);
        }
        else
        {
            //初始化image对象
            image->initWithRawData(tempData, savedBufferWidth * savedBufferHeight * 4, savedBufferWidth, savedBufferHeight, 8);
        }
        
    } while (0);

    //释放保存像素数据的内存
    CC_SAFE_DELETE_ARRAY(buffer);
    CC_SAFE_DELETE_ARRAY(tempData);

    return image;
}

/**
 *  开始渲染指令的回调
 */
void RenderTexture::onBegin()
{
    
    Director *director = Director::getInstance();
    
    //保存变换前矩阵
    _oldProjMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, _projectionMatrix);
    
    _oldTransMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _transformMatrix);
    
    //不保持矩阵,进行矩阵变换
    if(!_keepMatrix)
    {
        director->setProjection(director->getProjection());

#if CC_TARGET_PLATFORM == CC_PLATFORM_WP8
        Mat4 modifiedProjection = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
        modifiedProjection = CCEGLView::sharedOpenGLView()->getReverseOrientationMatrix() * modifiedProjection;
        director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION,modifiedProjection);
#endif

        const Size& texSize = _texture->getContentSizeInPixels();
        
        // Calculate the adjustment ratios based on the old and new projections
        Size size = director->getWinSizeInPixels();
        float widthRatio = size.width / texSize.width;
        float heightRatio = size.height / texSize.height;
        
        Mat4 orthoMatrix;
        Mat4::createOrthographicOffCenter((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1, 1, &orthoMatrix);
        director->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, orthoMatrix);
    }
    else
    {
#if CC_TARGET_PLATFORM == CC_PLATFORM_WP8
        Mat4 modifiedProjection = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
        modifiedProjection = CCEGLView::sharedOpenGLView()->getReverseOrientationMatrix() * modifiedProjection;
        director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, modifiedProjection);
#endif
    }
    
    //计算视口逻辑
    //calculate viewport
    {
        Rect viewport;
        viewport.size.width = _fullviewPort.size.width;
        viewport.size.height = _fullviewPort.size.height;
        float viewPortRectWidthRatio = float(viewport.size.width)/_fullRect.size.width;
        float viewPortRectHeightRatio = float(viewport.size.height)/_fullRect.size.height;
        viewport.origin.x = (_fullRect.origin.x - _rtTextureRect.origin.x) * viewPortRectWidthRatio;
        viewport.origin.y = (_fullRect.origin.y - _rtTextureRect.origin.y) * viewPortRectHeightRatio;
        //glViewport(_fullviewPort.origin.x, _fullviewPort.origin.y, (GLsizei)_fullviewPort.size.width, (GLsizei)_fullviewPort.size.height);
        glViewport(viewport.origin.x, viewport.origin.y, (GLsizei)viewport.size.width, (GLsizei)viewport.size.height);
    }

    // Adjust the orthographic projection and viewport
    //检查帧缓冲绑定状态,返回到_oldFBO中
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &_oldFBO);
    //绑定帧缓冲对象_FBO
    glBindFramebuffer(GL_FRAMEBUFFER, _FBO);

    //TODO move this to configration, so we don't check it every time
    /*  Certain Qualcomm Andreno gpu's will retain data in memory after a frame buffer switch which corrupts the render to the texture. The solution is to clear the frame buffer before rendering to the texture. However, calling glClear has the unintended result of clearing the current texture. Create a temporary texture to overcome this. At the end of RenderTexture::begin(), switch the attached texture to the second one, call glClear, and then switch back to the original texture. This solution is unnecessary for other devices as they don't have the same issue with switching frame buffers.
     */
    if (Configuration::getInstance()->checkForGLExtension("GL_QCOM"))
    {
        // -- bind a temporary texture so we can clear the render buffer without losing our texture
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _textureCopy->getName(), 0);
        CHECK_GL_ERROR_DEBUG();
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture->getName(), 0);
    }
}

/**
 *  结束渲染指令回调
 */
void RenderTexture::onEnd()
{
    Director *director = Director::getInstance();

    //检查帧缓冲状态返回到_oldFBO对象
    glBindFramebuffer(GL_FRAMEBUFFER, _oldFBO);

    //还原视口
    // restore viewport
    director->setViewport();

    director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, _oldProjMatrix);
    director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW, _oldTransMatrix);

}

/**
 *  清屏渲染指令回调
 */
void RenderTexture::onClear()
{
    // save clear color
    GLfloat oldClearColor[4] = {0.0f};
    GLfloat oldDepthClearValue = 0.0f;
    GLint oldStencilClearValue = 0;

    //使用 | 运算符组合不同的缓冲标志位,表明需要清除的缓冲,例如glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)表示要清除颜色缓冲以及深度缓冲
    
    // backup and set
    if (_clearFlags & GL_COLOR_BUFFER_BIT)
    {
        //保存颜色缓冲区的值到oldClearColor中
        glGetFloatv(GL_COLOR_CLEAR_VALUE, oldClearColor);
        //设置清屏色
        glClearColor(_clearColor.r, _clearColor.g, _clearColor.b, _clearColor.a);
    }

    if (_clearFlags & GL_DEPTH_BUFFER_BIT)
    {
        //保存深度值到oldDepthClearValue
        glGetFloatv(GL_DEPTH_CLEAR_VALUE, &oldDepthClearValue);
        //清理深度值
        glClearDepth(_clearDepth);
    }

    if (_clearFlags & GL_STENCIL_BUFFER_BIT)
    {
        //保存模板值到oldStencilClearValue
        glGetIntegerv(GL_STENCIL_CLEAR_VALUE, &oldStencilClearValue);
        //清理模板值
        glClearStencil(_clearStencil);
    }

    // clear
    //清屏操作
    glClear(_clearFlags);

    // restore
    //还原保存的颜色值,深度值和模板值
    if (_clearFlags & GL_COLOR_BUFFER_BIT)
    {
        glClearColor(oldClearColor[0], oldClearColor[1], oldClearColor[2], oldClearColor[3]);
    }
    if (_clearFlags & GL_DEPTH_BUFFER_BIT)
    {
        glClearDepth(oldDepthClearValue);
    }
    if (_clearFlags & GL_STENCIL_BUFFER_BIT)
    {
        glClearStencil(oldStencilClearValue);
    }
}

/**
 *  清理深度缓冲区渲染指令回调
 */
void RenderTexture::onClearDepth()
{
    //! save old depth value
    GLfloat depthClearValue;
    glGetFloatv(GL_DEPTH_CLEAR_VALUE, &depthClearValue);

    glClearDepth(_clearDepth);
    glClear(GL_DEPTH_BUFFER_BIT);

    // restore clear color
    glClearDepth(depthClearValue);
}

/**
 *  开始绘制
 *
 *  @param renderer  渲染对象
 *  @param transform 变换矩阵
 *  @param flags     标识
 */
void RenderTexture::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
    if (_autoDraw)
    {
        //Begin will create a render group using new render target
        //开始绘制操作,创建渲染队列等
        begin();

        //clear screen
        //初始化清屏渲染指令
        _clearCommand.init(_globalZOrder);
        //设置回调
        _clearCommand.func = CC_CALLBACK_0(RenderTexture::onClear, this);
        //加入到组渲染队列中
        renderer->addCommand(&_clearCommand);
        
        //排序子元素,全局z优先,其次是本地z
        //! make sure all children are drawn
        sortAllChildren();
        //绘制子元素
        for(const auto &child: _children)
        {
            if (child != _sprite)
                child->visit(renderer, transform, flags);
        }

        //End will pop the current render group
        end();
    }
}

/**
 *  开始绘制
 */
void RenderTexture::begin()
{
    Director* director = Director::getInstance();
    CCASSERT(nullptr != director, "Director is null when seting matrix stack");
    
    //在矩阵变换之前把当前矩阵保存在矩阵栈中
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    _projectionMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    
    director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    _transformMatrix = director->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
    
    //不使用矩阵保持,进行矩阵变换
    if(!_keepMatrix)
    {
        director->setProjection(director->getProjection());
        
        //纹理尺寸
        const Size& texSize = _texture->getContentSizeInPixels();
        
        // Calculate the adjustment ratios based on the old and new projections
        Size size = director->getWinSizeInPixels();
        
        float widthRatio = size.width / texSize.width;
        float heightRatio = size.height / texSize.height;
        
        Mat4 orthoMatrix;
        Mat4::createOrthographicOffCenter((float)-1.0 / widthRatio, (float)1.0 / widthRatio, (float)-1.0 / heightRatio, (float)1.0 / heightRatio, -1, 1, &orthoMatrix);
        director->multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION, orthoMatrix);
    }

    //初始化组渲染指令
    _groupCommand.init(_globalZOrder);
    
    //将组渲染指令加入当前组渲染队列之中
    Director::getInstance()->getRenderer()->addCommand(&_groupCommand);
    //将渲染队列Id加入到保存渲染队列Id的栈中
    Director::getInstance()->getRenderer()->pushGroup(_groupCommand.getRenderQueueID());
    
    //初始化开始渲染指令
    _beginCommand.init(_globalZOrder);
    //设置回调
    _beginCommand.func = CC_CALLBACK_0(RenderTexture::onBegin, this);
    //将开始渲染指令加入到组渲染队列之中
    Director::getInstance()->getRenderer()->addCommand(&_beginCommand);
}

/**
 *  绘制结束
 */
void RenderTexture::end()
{
    //初始化结束渲染指令
    _endCommand.init(_globalZOrder);
    //设置回调
    _endCommand.func = CC_CALLBACK_0(RenderTexture::onEnd, this);

    Director* director = Director::getInstance();
    CCASSERT(nullptr != director, "Director is null when seting matrix stack");
    
    Renderer *renderer = director->getRenderer();
    //将指令加入渲染队列
    renderer->addCommand(&_endCommand);
    //将组渲染指令生成的渲染队列弹出渲染队列
    renderer->popGroup();
    
    //还原矩阵
    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
    director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

}

NS_CC_END

本文由CC原创总结,如需转载请注明出处:http://blog.csdn.net/oktears/article/details/42197417


你可能感兴趣的:(Cocos2d-x引擎)