CEGUI渲染流程简析

粗略分析了CEGUI的渲染流程,总结一下供日后参考。CEGUI版本是0.7.5,OpenGL渲染器。

首先在CEGUI里面每张图片,每个字符都是一个quad,每个quad由2个三角面组成,包括6个顶点的坐标,颜色,纹理坐标,是发送给GPU的最基础的渲染单元。要注意的是,CEGUI并不局限于quad,它可以构造任意多的三角面以生成各种形状。要绘制一个窗口需要很多顶点数据,例如一个简单的button,背景图像需要1个quad(根据背景类型不同可能会更多),有4个文字的话又需要4个quad,总共就有5个quad,30个顶点。所以每个Window对象都有一个GeometryBuffer对象用来缓存自己的顶点数据。

窗口绘制输出的目的地称为RenderingSurface,所以每个窗口都要从属于某个RenderingSurface,否则无法显示。在渲染的时候,CEGUI会遍历所有的窗口,并将该窗口的GeometryBuffer依次提交到该窗口所属surface的渲染队列中去。对于CEGUI来说,这个过程就是“窗口绘制”,同时会触发绘制消息,而真正的绘制操作其实是在这之后。当所有相关的GeometryBuffer都被push进队列,RenderingSurface::draw就会被调用,此时顶点数据才真正提交到Renderer进行渲染输出。Renderer输出的目的地称为RenderTarget,由具体实现而定,可能是Frame buffer,Off-screen buffer,或者Texture object。在当前OpenGLRenderer中,是用纹理对象来实现的。之所以要引入RenderTarget,是为了能缓存surface的输出。

RenderingSurface有两个派生类,一个是RenderingRoot,它是Renderer默认的surface类型,在当前CEGUI的实现中只是对RenderingSurface的简单封装,没有任何额外的功能;第二个是RenderingWindow,它的作用是将渲染队列中的内容绘制到一张纹理图像上面,然后再用该纹理来绘制自己的GeometryBuffer到其他surface上面去。CEGUI 0.7中新增的窗口旋转和各种窗口特效就是通过这种方式来实现的。

在CEGUI中,每个Window对象都有自己的GeometryBuffer,但并不是每个窗口都有自己的surface。对于普通的四平八稳的窗口,它们共享由Renderer创建的RenderingRoot对象;而只有使用了旋转或特效的窗口才会创建属于自己的RenderingWindow(不要忘记,这是一个surface派生类哦)。成员函数Window::getTargetRenderingSurface用于获取窗口对象的surface,从中可以看出surface的从属关系:

RenderingSurface& Window::getTargetRenderingSurface() const
{
    if (d_surface)
    // 优先使用自己的surface
        return *d_surface;
    else if (d_parent)
    // 如果自己没有surface,则使用父窗口的
        return d_parent->getTargetRenderingSurface();
    else
    // 最后如果自己已经是顶层窗口
    // 则使用Renderer默认的surface
        return System::getSingleton().getRenderer()->\
        getDefaultRenderingRoot();
}

实际的渲染调用流程从System::RenderGUI开始,它首先清空顶层窗口的surface渲染队列,然后调用顶层窗口的Window::render函数:

void Window::render()
{
    // 是否可见
    if (!isVisible())
        return;
 
    // 获取render context,其中包含了当前窗口所从属的surface
    RenderingContext ctx;
    getRenderingContext(ctx);
 
    // 如果是自己的surface则清空geometry buffer
    if (ctx.owner == this)
        ctx.surface->clearGeometry();
 
    // 如果没有surface或者surface被标记为无效
    if (!d_surface || d_surface->isInvalidated())
    {
        // 绘制自己(生成顶点数据并提交给surface)
        drawSelf(ctx);
 
        // 递归调用所有子窗口的render
        const size_t child_count = getChildCount();
        for (size_t i = 0; i < child_count; ++i)
            d_drawList[i]->render();
    }
 
    // 如果是自己的surface则提交GeometryBuffer到GPU进行渲染输出
    if (ctx.owner == this)
        ctx.surface->draw();
}

在Window::drawSelf中:

void Window::drawSelf(const RenderingContext& ctx)
{
    // 构造GeometryBuffer
    bufferGeometry(ctx);
    // 提交GeometryBuffer到surface
    queueGeometry(ctx);
}
 
void Window::bufferGeometry(const RenderingContext&)
{
    // 仅当需要的时候才重新构造顶点数据
    if (d_needsRedraw)
    {
        // 清空GeometryBuffer
        d_geometry->reset();
 
        // 触发相关的CEGUI渲染消息
        WindowEventArgs args(this);
        onRenderingStarted(args);
 
        // HACK: ensure our rendered string
        // content is up to date
        getRenderedString();
 
        // 这里才是真正产生顶点数据的地方
        if (d_windowRenderer)
            // 如果有指定的WindowRenderer
            // 则交给它来处理
            d_windowRenderer->render();
        else
            // 否则调用虚函数让用户自己负责生成
            populateGeometryBuffer();
 
        // 触发渲染结束消息
        args.handled = 0;
        onRenderingEnded(args);
 
        // mark ourselves as no longer needed a redraw.
        d_needsRedraw = false;
    }
 
}

值得注意的是,CEGUI会记录各种窗口状态,只有在需要的时候才会重新构建窗口对象的GeometryBuffer,同样只有在需要的时候,才会重新绘制RenderingSurface。

上面代码中还提到了WindowRenderer(注意区分RenderingWindow)。这个class的作用是根据looknfeel的描述来生成生成顶点数据。在CEGUIFalagardWRBase工程下面有一大堆Fal开头的class,例如FalButton,FalEditbox等等,就是专门干这些工作的。经过WindowRenderer的层层调用,最终会落到以下三个component上面:

  1. FrameComponent:生成边框和背景
  2. ImageryComponent:生成静态图像
  3. TextComponent:生成文字

这几个component都是由FalagardComponentBase继承而来,作用是根据各种不同的配置,例如背景的样式,是否是边框,文字的排版方式等等,生成顶点数据。

综上,CEGUI的控件逻辑,控件样式,渲染数据是完全分离的。渲染部分采用两级缓存。第一级缓存用于记录顶点数据(GeometryBuffer);第二级缓存将渲染结果保存在纹理上面(RenderingSurface)。


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值