【问题标题】:Calculate average of pixels in the front buffer of the gpu without copying the front buffer back to system memory计算 gpu 前缓冲区中像素的平均值,而不将前缓冲区复制回系统内存
【发布时间】:2014-01-14 01:59:31
【问题描述】:

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

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

  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

但是,它将整个前端缓冲区复制回系统内存,平均需要 33 毫秒。显然 33 毫秒远不及获得良好更新率所需的速度,因此我正在寻找一种方法来直接在 gpu 上计算前缓冲区区域的平均值,而无需将前缓冲区复制回系统内存。

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

编辑:

根据 user3125280 的回答,我编写了一段代码,它应该占据屏幕的左上角并对其进行平均。但是结果总是0。我错过了什么? 另请注意,pSurface 现在位于显存中,因此GetFrontBufferData 只是显存中的一个 memcpy,速度非常快。

  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();

edit2: 显然GetFrontBufferData 要求目标驻留在系统内存中。所以我回到第一方。

edit3: 根据this,DX11.1 中应该可以实现以下功能:

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

但是,由于我不了解 DirectX,我很难实现它。

edit4: Windows 8 之前的操作系统不支持 DuplicateOutput :(

edit5: 我用经典的GetPixel API 做了一些实验,认为它对于随机采样可能足够快。可悲的是,事实并非如此。 GetPixel 花费的时间与 GetFrontBufferData 花费的时间相同。我猜它内部调用GetFrontBufferData

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

【问题讨论】:

  • 听起来不错,你用什么做灯光?你确定你可以用后台进程抓取整个屏幕吗?喜欢这个amblone.com
  • 你能把他的缓冲区绑定为纹理,然后在像素着色器中进行计算吗?
  • 也许这可以帮助你:stackoverflow.com/questions/10819951/…
  • @◙user3125280 我打算使用由 Arduino 驱动的this led rgb 灯条。是的,就像安布隆一样。但我想自己编写代码:D 实际上,我发布的代码 sn-p 几乎与 ambone 使用的代码相同。
  • 一个更快的选择是随机采样点,并保持移动平均

标签: c++ c graphics directx gpu


【解决方案1】:

这个问题实际上(显然)在游戏代码等中很常见。一个有趣的解决方案如下:Efficient pixel shader sum of all pixels。这与您的确切情况特别相关,因为您可以使用较大的 mimmap 纹理来汇总显示的较小部分。

Get the screen into a texture

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);

然后创建一个mipmap链为here

// 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);

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

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

【讨论】:

  • 谢谢!这绝对是一个正确方向的巨大指针。但是我真的不需要所有级别,只需要最后一个。因此,我尝试基于 StrechRect() 来做一些事情。 (见有问题的编辑)
  • 据我所知,我只尝试过使用 open gl 图形,而且我不熟悉如何操作 d3d 纹理。我只能告诉你文档中的内容
  • @Arne 在 strechrect 中将 &r 替换为 null 会发生什么?
  • 同样的情况。另外我刚刚注意到该方法返回 D3DERR_INVALIDCALL。所以我猜初始化有问题。
  • @Arne 拉伸方法无效吗?查看我的编辑
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多