使用GPU混合两个图像

时间:2014-04-06 07:40:18

标签: opengl glsl shader gpgpu fragment-shader

我需要非常快速地混合数千对图像。

我的代码目前执行以下操作:_apply是指向Blend等函数的函数指针。它是我们可以通过的众多功能之一,但它不是唯一的功能。任何函数都需要两个值并输出第三个值,并在每个像素的每个通道上完成。我更倾向于使用任何此类函数的通用解决方案,而不是特定的混合解决方案。

typedef byte (*Transform)(byte src1,byte src2); 
Transform _apply;

for (int i=0 ; i< _frameSize ; i++) 
{
    source[i] = _apply(blend[i]);
}


byte Blend(byte src, byte blend)
{
    int resultPixel = (src + blend)/2;

    return (byte)resultPixel;
}

我在CPU上这样做,但性能很糟糕。据我所知,在GPU中执行此操作非常快。我的程序需要在具有Nvidia GPU或Intel GPU的计算机上运行,​​因此我使用的任何解决方案都需要独立于供应商。如果我使用GPU,它必须是OpenGL才能与平台无关。

我认为使用GLSL像素着色器会有所帮助,但我不熟悉像素着色器或如何将它们用于2D对象(如我的图像)。

这是一个合理的解决方案吗?如果是这样,我该如何在2D中执行此操作? 如果有一个已经存在的库,那么也很高兴知道。

编辑:我收到了来自不同来源的图像对。一个总是来自opengl中的3d图形组件(因此它最初是在GPU中)。另一个来自系统内存,来自套接字(在压缩视频流中)或来自内存映射文件。 &#34;下沉&#34;得到的图像是屏幕。我希望在屏幕上显示图像,因此转到GPU是一个选项或使用SDL之类的东西来显示它们。

将要执行最多的混合函数就是这个

byte Patch(byte delta, byte lo)
{
    int resultPixel = (2 * (delta - 127)) + lo;

    if (resultPixel > 255)
       resultPixel = 255;

    if (resultPixel < 0)
       resultPixel = 0;

    return (byte)resultPixel;
}

编辑2:来自GPU陆地的图像以这种方式出现。从FBO到PBO再到系统内存

glBindFramebuffer(GL_FRAMEBUFFER,fbo);
glReadBuffer( GL_COLOR_ATTACHMENT0 );
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glReadPixels(0,0,width,height,GL_BGR,GL_UNSIGNED_BYTE,0); 
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); 
void* mappedRegion = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);

似乎最好只在GPU内存中处理所有内容。另一个位图可以来自系统内存。我们最终也可以从GPU内存中的视频解码器中获取它。

编辑3:我的一张图片来自D3D而另一幅来自OpenGL。似乎像Thrust或OpenCL这样的东西是最好的选择

1 个答案:

答案 0 :(得分:3)

Blend函数的外观来看,这是一个完全记忆限制的操作。 CPU上的缓存可能只能容纳您拥有的数千张图像中的一小部分。这意味着大部分时间都花在等待RAM来满足加载/存储请求,并且CPU会闲置很多。

通过将图像从RAM复制到GPU,让GPU算术单元等待GPU RAM为其提供数据,等待GPU RAM再次写入结果,然后将其全部复制,您将无法获得任何加速回到主RAM。使用GPU实际上可能会使事情大幅减慢。


但我可能错了,你可能不会让你的内存总线饱和。 您必须在您的系统上进行尝试并对其进行分析。以下是您可以尝试优化的一些简单事项。

1。多线程

我将专注于直接在CPU上优化算法。最简单的方法是使用多线程,这可以像在编译器中启用OpenMP一样简单并更新for循环:

#include <omp.h> // add this along with enabling OpenMP support in your compiler
...
#pragma omp parallel for // <--- compiler magic happens here
for (int i=0 ; i< _frameSize ; i++) 
{
    source[i] = _apply(blend[i]);
}

如果你的内存带宽没有达到饱和,那么无论你的系统有多少内核,都可能会加速混合。

2。微优化

您可以尝试的另一件事是使用大多数CPU现有的SIMD指令来实现Blend。如果不知道你的目标是什么CPU,我无法帮助你。

您也可以尝试展开for循环以减轻一些循环开销。

实现这两个目标的一种简单方法是通过将数据包装在其数据结构中来利用Eigen matrix library

// initialize your data and result buffer
byte *source = ...
byte *blend = ...
byte *result = ...

// tell Eigen where you data/buffer are, and to treat it like a dynamic vectory of bytes
// this is a cheap shallow copy
Map<Matrix<byte, Dynamic,1> > sourceMap(source, _frameSize);
Map<Matrix<byte, Dynamic,1> > blendMap(blend, _frameSize);
Map<Matrix<byte, Dynamic,1> > resultMap(result, _frameSize);

// perform blend using all manner of insane optimization voodoo under the covers
resultMap = (sourceMap + blendMap)/2;

3。使用GPGPU

最后,我将提供一个直接的答案,您可以轻松利用GPU,而无需了解GPU编程。最简单的方法是尝试Thrust library。您将不得不将算法重写为STL样式算法,但在您的情况下这非常简单。

// functor for blending
struct blend_functor
{
  template <typename Tuple>
  __host__ __device__
  void operator()(Tuple t)
  {
    // C[i] = (A[i] + B[i])/2;
    thrust::get<2>(t) = (thrust::get<0>(t) + thrust::get<1>(t))/2;
  }
};

// initialize your data and result buffer
byte *source = ...
byte *blend = ...
byte *result = NULL;

// copy the data to the vectors on the GPU
thrust::device_vector<byte> A(source, source + _frameSize);
thrust::device_vector<byte> B(blend, blend + _frameSize);
// allocate result vector on the GPU
thrust::device_vector<byte> C(_frameSize);

// process the data on the GPU device
thrust::for_each(thrust::make_zip_iterator(thrust::make_tuple(
                                  A.begin(), B.begin(), C.begin())),
                 thrust::make_zip_iterator(thrust::make_tuple(
                                  A.end(), B.end(), C.end())),
                 blend_functor());

// copy the data back to main RAM
thrust::host_vector<byte> resultVec = C;
result = resultVec.data();

关于推力的一个非常巧妙的事情是,一旦你以通用方式编写算法,它就可以自动使用不同的back ends进行计算。 CUDA是默认的后端,但您也可以在编译时配置它以使用OpenMP或TBB(英特尔线程库)。