我正准备为我的电脑构建一个ambilight的克隆。为此,我需要一种方法来计算屏幕上几个区域的平均颜色。
到目前为止,我找到的最快的方法是:
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毫秒。明显的33ms远未达到我获得良好更新率所需的速度,因此我正在寻找一种方法,直接在gpu上计算前缓冲区区域的平均值,而无需将前缓冲区复制回系统内存。
edit:代码片段中的瓶颈是pd3dDevice->GetFrontBufferData(0, pSurface);
。memcpy对性能没有明显影响。
编辑:
根据用户3125280的回答,我编写了一段代码,应该取屏幕左上角的平均值。然而结果总是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();
edit2:显然GetFrontBufferData
要求目标驻留在系统内存中。所以我又回到了原点。
edit3:根据此,DX11.1:中应该可以进行以下操作
- 创建Direct3D 11.1设备。(也许早期也有效——我还没有尝试过。我不确定是否有理由使用D3D10/10.1/11设备。)
- 找到要复制的IDXGIOutput,然后调用DuplicateOutput()以获得IDXGIOoutDuplication接口
- 调用AcquireNextFrame()以等待新帧到达
- 处理收到的纹理
- 调用ReleaseFrame()
- 重复
但是,由于我不了解DirectX,我很难实现它。
edit4:DuplicateOutput在早于Windows 8的操作系统中不受支持:(
edit5:我用经典的GetPixel
API做了一些实验,认为它可能足够快,可以进行随机采样。遗憾的是,事实并非如此。CCD_ 6花费的时间与CCD_ 7花费的时间相同。我猜它内部调用GetFrontBufferData
。
因此,目前我看到了两种解决方案:*禁用Aero并使用GetFrontBufferData
*切换到窗口8两者都不是很好:(
这个问题实际上(显然)在游戏代码等中很常见。一个有趣的解决方案如下:有效的像素着色器所有像素的总和。这与您的确切情况特别相关,因为您可以使用较大的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链,如下
// 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在游戏和图形中很重要。其他过滤选项也可能可用。
对于第二次编辑,请尝试此处-gpu-mem中有关纹理的某些内容以不同的方式读取,并且无法锁定,您需要使用getrendertargetdata或类似的方法。这可以用于将stretchrect曲面复制到系统池中由纹理创建的cpu端。据我所知,gpu侧面的纹理/表面不能直接记忆。