Вычислить среднее количество пикселей в переднем буфере 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

Однако он вынужден копировать весь передний буфер обратно в системную память, которая в среднем занимает 33 мс. Obviuosly 33ms никак не приближается к скорости, необходимой мне для приличной скорости обновления, поэтому я ищу способ вычисления среднего значения по области переднего буфера непосредственно на gpu, не копируя передний буфер обратно в системную память.

edit: узким местом в фрагменте кода является 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: DuplicateOutput не поддерживается в операционных системах старше Windows 8: (

edit5: Я провел несколько экспериментов с классическим API GetPixel, думая, что он может быть достаточно быстрым для случайной выборки. К сожалению, это не так. GetPixel занимает такое же количество времени, что и GetFrontBufferData. Я предполагаю, что он внутренне вызывает GetFrontBufferData.

Итак, теперь я вижу два решения:  * Отключите Aero и используйте GetFrontBufferData  * Переключиться на windows 8 Оба они не очень хороши: (

Ответ 1

Эта проблема на самом деле распространена (по-видимому) в коде игры и т.п. Одним из интересных решений является следующее: Эффективная сумма пиксельных шейдеров всех пикселей. Это особенно актуально для вашей конкретной ситуации, поскольку вы можете использовать большую текстуру 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);

Конечно, вам не нужно иметь доступ к нижней мип-карте (которая является средней). Вы можете получить доступ к нескольким уровням выше, чтобы получить среднее количество разделов. Кроме того, это быстро, потому что текстура mipmapping важна в играх и графике в целом. Другие параметры фильтрации также могут быть доступны.

Для второго варианта редактирования здесь - что-то о текстурах в gpu mem читается по-разному и не может быть заблокировано, вам нужно будет использовать getrendertargetdata или некоторые например. Этот можно использовать, чтобы скопировать поверхность растяжения на текстуру, созданную стороной процессора в системном пуле. Насколько я знаю, стороны gpu текстуры/поверхности не могут быть memcpy'ed напрямую.