C# - 插入大字节数组(RGB到RGBA)的最快方法

问题描述 投票:1回答:2

我正在将帧从相机上传到GPU上的纹理以进行处理(使用SharpDX)。我的问题是ATM是我有24bit RGB的帧,但DX11不再有24bit RGB纹理格式,只有32位RGBA。在每3个字节之后,我需要添加另一个字节,其值为255(无透明度)。我已经尝试过迭代通过字节数组添加它的方法,但它太贵了。使用GDI位图进行转换也非常昂贵。

                int count = 0;
                for (int i = 0; i < frameDataBGRA.Length - 3; i+=4)
                {

                    frameDataBGRA[i] = frameData[i - count];
                    frameDataBGRA[i + 1] = frameData[(i + 1) - count];
                    frameDataBGRA[i + 2] = frameData[(i + 2) - count];
                    frameDataBGRA[i + 3] = 255;
                    count++;
    }
c# bitmap sharpdx
2个回答
0
投票

假设您可以使用unsafe进行编译,在这种情况下使用指针将为您提供显着的提升。

首先创建两个结构以打包方式保存数据:

[StructLayout(LayoutKind.Sequential)]
public struct RGBA
{
    public byte r;
    public byte g;
    public byte b;
    public byte a;
}

[StructLayout(LayoutKind.Sequential)]
public struct RGB
{
    public byte r;
    public byte g;
    public byte b;
}

第一版:

    static void Process_Pointer_PerChannel(int pixelCount, byte[] rgbData, byte[] rgbaData)
    {
        fixed (byte* rgbPtr = &rgbData[0])
        {
            fixed (byte* rgbaPtr = &rgbaData[0])
            {
                RGB* rgb = (RGB*)rgbPtr;
                RGBA* rgba = (RGBA*)rgbaPtr;
                for (int i = 0; i < pixelCount; i++)
                {
                    rgba->r = rgb->r;
                    rgba->g = rgb->g;
                    rgba->b = rgb->b;
                    rgba->a = 255;
                    rgb++;
                    rgba++;
                }
            }
        }
    }

这避免了大量索引,并直接传递数据。

另一个版本稍快一点,直接装箱:

    static void Process_Pointer_Cast(int pixelCount, byte[] rgbData, byte[] rgbaData)
    {
        fixed (byte* rgbPtr = &rgbData[0])
        {
            fixed (byte* rgbaPtr = &rgbaData[0])
            {
                RGB* rgb = (RGB*)rgbPtr;
                RGBA* rgba = (RGBA*)rgbaPtr;
                for (int i = 0; i < pixelCount; i++)
                {
                    RGB* cp = (RGB*)rgba;
                    *cp = *rgb;
                    rgba->a = 255;
                    rgb++;
                    rgba++;
                }
            }
        }
    }

一个小的额外优化(这是边缘的),如果你一直保持相同的数组并重复使用它,你可以将alpha设置为255初始化一次,例如:

    static void InitRGBA_Alpha(int pixelCount, byte[] rgbaData)
    {
        for (int i = 0; i < pixelCount; i++)
        {
            rgbaData[i * 4 + 3] = 255;
        }
    }

然后,由于您永远不会更改此频道,其他功能不再需要写入其中:

    static void Process_Pointer_Cast_NoAlpha (int pixelCount, byte[] rgbData, byte[] rgbaData)
    {
        fixed (byte* rgbPtr = &rgbData[0])
        {
            fixed (byte* rgbaPtr = &rgbaData[0])
            {
                RGB* rgb = (RGB*)rgbPtr;
                RGBA* rgba = (RGBA*)rgbaPtr;
                for (int i = 0; i < pixelCount; i++)
                {
                    RGB* cp = (RGB*)rgba;
                    *cp = *rgb;
                    rgb++;
                    rgba++;
                }
            }
        }
    }

在我的测试中(运行1920 * 1080图像,100次迭代),我得到(i7,x64版本构建,平均运行时间)

  • 你的版本:6.81ms
  • Process_Pointer_PerChannel:4.3ms
  • Process_Pointer_Cast:3.8ms
  • Process_Pointer_Cast_NoAlpha:3.5ms

请注意,当然所有这些功能都可以轻松分块,部件可以在多线程版本中运行。

如果您需要更高的性能,您有两个选择(问题的范围超出范围)

  • 将图像上传到字节地址缓冲区(作为rgb),并在计算着色器中执行到纹理的转换。这涉及一些位移和一些摆弄格式,但实现起来相当简单。
  • 通常,相机图像采用Yuv格式(使用u和v下采样),因此在该颜色空间中上传图像并在像素着色器或计算着色器中执行转换为rgba的速度更快。如果你的相机sdk允许以原生格式获取像素数据,那就是你要走的路。

0
投票

@catflier:干得好,但可以快一点。 ;-)

在我的硬件上重现的时间:

  • 基础版本:5.48ms
  • Process_Pointer_PerChannel:2.84ms
  • Process_Pointer_Cast:2.16ms
  • Process_Pointer_Cast_NoAlpha:1.60ms

我的实验:

  • FastConvert:1.45ms
  • FastConvert4:1.13ms(此处:像素数必须可被4整除,但通常没有问题)

速度提升的事情:

  • 您的RGB结构必须始终读取每个像素3个单字节,但读取整个uint(4个字节)更快,并忽略最后一个字节
  • 然后可以将alpha值直接添加到uint位计算中
  • 现代处理器通常可以比具有增量的指针更快地处理具有偏移位置的固定指针。
  • x64模式中的偏移量变量也应该直接使用64位数据值(long而不是int),这样可以减少访问的开销
  • 部分滚出内环会再次增加一些性能

代码:

static void FastConvert(int pixelCount, byte[] rgbData, byte[] rgbaData)
{
  fixed (byte* rgbP = &rgbData[0], rgbaP = &rgbaData[0])
  {
    for (long i = 0, offsetRgb = 0; i < pixelCount; i++, offsetRgb += 3)
    {
      ((uint*)rgbaP)[i] = *(uint*)(rgbP + offsetRgb) | 0xff000000;
    }
  }
}

static void FastConvert4Loop(long pixelCount, byte* rgbP, byte* rgbaP)
{
  for (long i = 0, offsetRgb = 0; i < pixelCount; i += 4, offsetRgb += 12)
  {
    uint c1 = *(uint*)(rgbP + offsetRgb);
    uint c2 = *(uint*)(rgbP + offsetRgb + 3);
    uint c3 = *(uint*)(rgbP + offsetRgb + 6);
    uint c4 = *(uint*)(rgbP + offsetRgb + 9);
    ((uint*)rgbaP)[i] = c1 | 0xff000000;
    ((uint*)rgbaP)[i + 1] = c2 | 0xff000000;
    ((uint*)rgbaP)[i + 2] = c3 | 0xff000000;
    ((uint*)rgbaP)[i + 3] = c4 | 0xff000000;
  }
}

static void FastConvert4(int pixelCount, byte[] rgbData, byte[] rgbaData)
{
  if ((pixelCount & 3) != 0) throw new ArgumentException();
  fixed (byte* rgbP = &rgbData[0], rgbaP = &rgbaData[0])
  {
    FastConvert4Loop(pixelCount, rgbP, rgbaP);
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.