计算gpu前端缓冲区中像素的平均值,而不将前端缓冲区复制回系统内存

Arn*_*ann 10 c c++ directx graphics gpu

我准备为我的电脑构建一个流光溢彩的克隆.为此,我需要一种方法来计算屏幕几个区域的平均颜色.

到目前为止我发现的最快方法如下:

  pd3dDevice->CreateOffscreenPlainSurface(ddm.Width, ddm.Height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH/*D3DPOOL_SYSTEMMEM*/, &pSurface, nullptr)
  pd3dDevice->GetFrontBufferData(0, pSurface);
  D3DLOCKED_RECT lockedRect;
  pSurface->LockRect(&lockedRect, nullptr, D3DLOCK_NO_DIRTY_UPDATE|D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY);
  memcpy(pBits, (unsigned char*) lockedRect.pBits, dataLength);
  pSurface->UnlockRect();
  //calculate average over of pBits
Run Code Online (Sandbox Code Playgroud)

然而,它会将整个前端缓冲区复制回系统内存,平均需要33毫秒.显然33ms并不接近我需要的速度,因此我正在寻找一种方法来直接在gpu上计算前缓冲区域的平均值,而无需将前缓冲区复制回系统内存.

编辑:代码片段中的瓶颈是pd3dDevice->GetFrontBufferData(0, pSurface);.memcpy对性能没有明显影响.

编辑:

根据user3125280的回答,我制作了一些代码,它们应该占据屏幕的左上角并对其进行平均.但结果总是0.我错过了什么?另外请注意,pSurface现在它已经存在于视频内存中,因此GetFrontBufferData只是视频内存中的一个内存非常快.

  pd3dDevice->CreateOffscreenPlainSurface(1, 1, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pAvgSurface, nullptr);
  pd3dDevice->CreateOffscreenPlainSurface(ddm.Width, ddm.Height, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &pSurface, nullptr);

  pd3dDevice->GetFrontBufferData(0, pSurface);

  RECT r;
  r.right = 100;
  r.bottom = 100;
  r.left = 0;
  r.top = 0;
  pd3dDevice->StretchRect(pSurface, &r, pAvgSurface, nullptr, D3DTEXF_LINEAR);

  D3DLOCKED_RECT lockedRect;
  pAvgSurface->LockRect(&lockedRect, nullptr, D3DLOCK_NO_DIRTY_UPDATE|D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY);
  unsigned int color = -1;
  memcpy((unsigned char*) &color, (unsigned char*) lockedRect.pBits, 4); //FIXME there has to be a better way than memcopy
  pAvgSurface->UnlockRect();
Run Code Online (Sandbox Code Playgroud)

edit2: 显然GetFrontBufferData要求目标驻留在系统内存中.所以我回到原点.

edit3: 根据这一点,在DX11.1中应该可以:

  • 创建Direct3D 11.1设备.(也许早期也有效 - 我还没试过.我不确定是否有理由使用D3D10/10.1/11设备.)
  • 找到要复制的IDXGIOutput,并调用DuplicateOutput()以获取IDXGIOutputDuplication接口.
  • 调用AcquireNextFrame()以等待新帧到达.
  • 处理接收的纹理.
  • 调用ReleaseFrame().
  • 重复.

但是由于我对DirectX的不存在的知识,我很难实现它.

edit4: 早于Windows 8的操作系统不支持DuplicateOutput :(

编辑5: 我做了一些经典GetPixelAPI的实验,认为它可能足够快,可以进行随机抽样.可悲的是,事实并非如此.GetPixel需要花费相同的时间GetFrontBufferData.我猜它在内部调用GetFrontBufferData.

所以现在我看到两个解决方案:*禁用Aero并使用GetFrontBufferData *切换到Windows 8它们都不是很好:(

use*_*280 3

这个问题实际上在游戏代码等中很常见(显然)。一种有趣的解决方案如下:所有像素的高效像素着色器总和。这与您的具体情况特别相关,因为您可以使用较大的 mimmap 纹理来对显示的较小部分进行求和。

让屏幕变成纹理

IDirect3DTexture9* texture; // needs to be created, of course
IDirect3DSurface9* dest = NULL; // to be our level0 surface of the texture
texture->GetSurfaceLevel(0, &dest);
pD3DDevice->StretchRect(pSurface, NULL, dest, NULL, D3DTEXF_LINEAR);
Run Code Online (Sandbox Code Playgroud)

然后创建一个 mipmap 链,如下所示

// This code example assumes that m_d3dDevice is a
// valid pointer to a IDirect3DDevice9 interface

IDirect3DTexture9 * pMipMap;
m_pD3DDevice->CreateTexture(256, 256, 5, 0, D3DFMT_R8G8B8, 
D3DPOOL_MANAGED, &pMipMap);
Run Code Online (Sandbox Code Playgroud)

当然,您不必访问底部 mipmap(这是平均值)。您可以访问更高的级别以获得部分的平均值。而且这也很快,因为纹理纹理贴图在游戏和图形中通常很重要。其他过滤选项也可能可用。

对于第二次编辑,请尝试此处- 有关 gpu mem 中的纹理的读取方式不同,并且无法锁定,您需要使用 getrendertargetdata 或类似的东西。可用于将拉伸矩形表面复制到系统池中创建纹理的 cpu 端。据我所知,GPU 侧面纹理/表面不能直接进行 memcpy。