我正准备为我的电脑构建一个流光溢彩的克隆。 为此,我需要一种方法来计算屏幕多个区域的平均颜色。
目前我发现最快的方法如下:
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 上计算前缓冲区区域的平均值,而无需将前缓冲区复制回系统内存。
编辑:代码片段中的瓶颈是pd3dDevice->GetFrontBufferData(0, pSurface);
。
memcpy 对性能没有明显影响。
编辑:
根据 user3125280 的回答,我编写了一段代码,应该占据屏幕的左上角并对其进行平均。但是结果始终为 0。我错过了什么?
还要注意 pSurface
现在在视频内存中,因此 GetFrontBufferData
只是视频 ram 中的一个 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();
编辑2:
显然 GetFrontBufferData
要求目标驻留在系统内存中。所以我回到原点。
编辑3: 根据this以下内容在 DX11.1 中应该是可能的:
- 创建 Direct3D 11.1 设备。 (也许更早的版本也适用——我还没有尝试过。我不确定是否有理由使用 D3D10/10.1/11 设备。)
- 找到要复制的 IDXGIOutput,调用 DuplicateOutput() 获取 IDXGIOutputDuplication 接口(interface)。
- 调用 AcquireNextFrame() 等待新帧到达。
- 处理接收到的纹理。
- 调用 ReleaseFrame()。
- 重复。
但是,由于我对 DirectX 不了解,所以很难实现它。
编辑4: Windows 8 之前的操作系统不支持 DuplicateOutput :(
编辑5:
我用经典的 GetPixel
API 做了一些实验,认为它可能足够快以进行随机采样。可悲的是它不是。 GetPixel
花费的时间与 GetFrontBufferData
花费的时间相同。我猜它在内部调用了 GetFrontBufferData
。
所以现在我看到两个解决方案:
* 禁用 Aero 并使用 GetFrontBufferData
* 切换到 Windows 8
他们两个都不是很好:(
最佳答案
这个问题实际上(显然)在游戏代码之类的地方很常见。一个有趣的解决方案如下:Efficient pixel shader sum of all pixels .这与您的具体情况特别相关,因为您可以使用较大的 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);
然后创建一个 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可用于将 stretchrect 表面复制到系统池中 cpu 端创建的纹理。据我所知,gpu 侧纹理/表面不能直接 memcpy。
关于c++ - 在不将前缓冲区复制回系统内存的情况下计算 gpu 前缓冲区中像素的平均值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20784385/