首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >加速变换计算

加速变换计算
EN

Stack Overflow用户
提问于 2011-08-03 03:05:14
回答 4查看 1.8K关注 0票数 7

我正在编写一个OpenGL3 2D引擎。目前,我正在尝试解决一个瓶颈。因此,请输入AMD Profiler的以下输出:http://h7.abload.de/img/profilerausa.png

这些数据是用几千个精灵制作的。

然而,在50.000个精灵的情况下,测试应用在5fps时就已经无法使用了。

这表明,我的瓶颈是我使用的转换函数。这就是对应的函数:http://code.google.com/p/nightlight2d/source/browse/NightLightDLL/NLBoundingBox.cpp#130

代码语言:javascript
复制
void NLBoundingBox::applyTransform(NLVertexData* vertices) 
{
    if ( needsTransform() )
    {
            // Apply Matrix
            for ( int i=0; i<6; i++ )
            {
                glm::vec4 transformed = m_rotation * m_translation * glm::vec4(vertices[i].x, vertices[i].y, 0, 1.0f);
                vertices[i].x = transformed.x;
                vertices[i].y = transformed.y;
            }
            m_translation = glm::mat4(1);
            m_rotation    = glm::mat4(1);
            m_needsTransform = false;
    }
}

我不能在着色器中这样做,因为我一次批处理所有的精灵。这意味着,我必须使用CPU来计算变换。

我的问题是:解决这个瓶颈的最佳方法是什么?

我没有使用任何线程自动柜员机,所以当我使用vsync时,我也会得到额外的性能影响,因为它会等待屏幕完成。这告诉我应该使用线程。

另一种方法可能是使用OpenCL?我想避免CUDA,因为据我所知,它只能在NVIDIA卡上运行。是那么回事吗?

后脚本:

如果你喜欢,你可以在这里下载一个演示:

http://www63.zippyshare.com/v/45025690/file.html

请注意,这需要安装VC++2008,因为它是用于运行分析器的调试版本。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-08-03 03:12:15

在进入for循环之前,我要做的第一件事就是将旋转和矩阵转换成一个矩阵。这样,您就不需要在每个for循环上计算两个矩阵乘法和一个向量;相反,您将只计算一个向量和一个矩阵的乘法。其次,您可能希望展开循环,然后使用更高的优化级别进行编译(在g++上,我至少会使用-O2,但我不熟悉MSVC,因此您必须自己转换优化级别)。这将避免代码中的分支可能产生的任何开销,特别是在缓存刷新时。最后,如果您还没有研究过它,请检查一些SSE优化,因为您正在处理向量。

更新:我将添加最后一个涉及线程化的想法...基本上,当你进行线程时,你的顶点是流水线的。例如,假设您有一台机器有8个可用的CPU线程(即具有超线程的四核)。为顶点管道处理设置六个线程,并使用非锁定的单消费者/生产者队列在管道的各个阶段之间传递消息。每个阶段都会变换六元顶点数组中的一个成员。我猜有一堆这样的六成员顶点数组,所以在流中设置通过管道传递的流,您可以非常有效地处理流,并避免使用互斥和其他锁定信号量,等等。有关快速的非锁单生产者/消费者队列的更多信息,see my answer here

UPDATE 2:您只有一个双核处理器...因此,丢弃管道的想法,因为它将遇到瓶颈,因为每个线程都会争用CPU资源。

票数 4
EN

Stack Overflow用户

发布于 2011-08-03 06:17:06

我不能在着色器中这样做,因为我一次批处理所有的精灵。这意味着,我必须使用CPU来计算变换。

这听起来像是您过早地进行了优化,假设批处理是您可以做的最重要的事情,因此您围绕进行最少数量的绘制调用来构建渲染器。现在它又回来咬你了。

你需要做的不是更少的批次。您需要有正确的批次数量。当你放弃GPU顶点变换而支持CPU变换时,你知道你在批处理方面走得太远了。

正如Datenwolf建议的那样,您需要进行一些实例化操作,以便将转换返回到GPU上。但即使这样,您也需要取消这里的一些过度批处理。您并没有过多地谈论您正在渲染的场景类型(顶部带有精灵的平铺地图、大型粒子系统等),因此很难知道该建议什么。

此外,GLM是一个很好的数学库,但它不是为实现最高性能而设计的。如果我需要在CPU上每帧变换300,000个顶点,我通常不会使用它。

票数 3
EN

Stack Overflow用户

发布于 2011-08-03 03:28:30

循环中的赋值可能是个问题,不过我对这个库并不熟悉。将其移出for循环并手动完成字段赋值可能会有所帮助。将转换移到循环之外也会有所帮助。

编辑:

这更符合我的想法。

代码语言:javascript
复制
// Apply Matrix
glm::vec4 transformed;
glm::mat4 translation = m_rotation * m_translation;
for ( int i=0; i<6; i++ )
{
    transformed.x = vertices[i].x;
    transformed.y = vertices[i].y;
    transformed.z = vertices[i].z;
    transformed.w = 1.f; // ?
    /* I can't find docs, but assume they have an in-place multiply
    transformed.mult(translation);
    // */
    vertices[i].x = transformed.x;
    vertices[i].y = transformed.y;
}

也许,仅仅是可能,赋值阻止了编译器内联或展开某些东西。我有点猜想乘法是足够大的,可以把它从指令缓存中去掉。实际上,如果你开始谈论缓存的大小,你将不会在许多平台上保持弹性。

您可以尝试复制一些堆栈,并创建更多、更小的循环。

代码语言:javascript
复制
glm::vec4 transformed[6];
for (size_t i = 0; i < 6; i++) {
    transformed[i].x = vertices[i].x;
    transformed[i].y = vertices[i].y;
    transformed[i].z = vertices[i].z;
    transformed.w = 1.f; // ?
}
glm::mat4 translation = m_rotation * m_translation;
for (size_t i = 0; i < 6; i++) {
    /* I can't find docs, but assume they have an in-place multiply
    transformed.mult(translation);
    // */
}
for (size_t i = 0; i < 6; i++) {
    vertices[i].x = transformed[i].x;
    vertices[i].y = transformed[i].y;
}

正如Jason提到的,手动展开这些循环可能很有趣。

不过,我真的不认为你会看到这些变化会有一个数量级的改善。

我怀疑少调用这个函数比让这个函数更快更重要。在这个函数中有这个needsTransform检查的事实让我认为这可能是相关的。

当您在低级代码中有这样的高级关注点时,您最终只是盲目地一遍又一遍地调用此方法,并认为它是免费的。无论你对needsTransform为真的频率的假设是否正确,都可能是非常错误的。

实际情况是,您应该只调用此方法一次。当你想applyTransform的时候,你应该applyTransform。当你想applyTransform的时候,你不应该调用applyTransform。接口应该是一种契约,就像对待它们一样。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6917206

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档