OpenGL/D3D: Как получить скриншот игры, работающей в полноэкранном режиме в Windows?
Предположим, у меня есть игра OpenGL, работающая в полноэкранном режиме (Left 4 Dead 2). Я хотел бы программно получить снимок экрана, а затем записать его в видеофайл.
Я пробовал методы GDI, D3D и OpenGL (например, glReadPixels) и либо получаю пустой экран, либо мерцание в потоке захвата.
Есть идеи?
Для чего стоит, каноническим примером чего-то похожего на то, чего я пытаюсь достичь, является Fraps.
3 ответа
Есть несколько подходов к этой проблеме. Большинство из них неприглядны, и это полностью зависит от того, на какой графический API вы хотите нацеливаться и какие функции использует целевое приложение. Большинство приложений DirectX, GDI+ и OpenGL имеют двойную или тройную буферизацию, поэтому все они вызывают:
void SwapBuffers(HDC hdc)
в какой-то момент. Они также генерируют сообщения WM_PAINT в своей очереди сообщений всякий раз, когда должно быть нарисовано окно. Это дает вам два варианта.
Вы можете установить глобальный хук или локальный хук поток в целевой процесс и захватывать сообщения WM_PAINT. Это позволяет вам копировать содержимое из контекста устройства непосредственно перед тем, как происходит рисование. Процесс может быть найден путем перечисления всех процессов в системе и поиска известного имени окна или известного дескриптора модуля.
Вы можете добавить код в локальную копию целевого процесса SwapBuffers. В Linux это было бы легко сделать через переменную окружения LD_PRELOAD или явно вызвав ld-linux.so.2, но в Windows не существует эквивалента. К счастью, есть Microsoft Research, которая может сделать это для вас, под названием Detours. Вы можете найти это здесь: ссылка.
Демосценовая группа Farbrausch создала демо-захват инструмента под названием kkapture, который использует библиотеку Detours. Однако их инструмент предназначен для приложений, которые не требуют ввода данных пользователем, поэтому они в основном запускают демонстрации с фиксированной частотой кадров, подключаясь ко всем возможным функциям времени, таким как timeGetTime(), GetTickCount() и QueryPerformanceCounter(). Это полностью рад. Презентация, написанная ryg (я думаю?) О внутренностях kkapture, может быть найдена здесь. Я думаю, что это вас интересует.
Для получения дополнительной информации о хуках Windows смотрите здесь и здесь.
РЕДАКТИРОВАТЬ:
Эта идея заинтриговала меня, поэтому я использовал Detours, чтобы подключиться к приложениям OpenGL и связываться с графикой. Вот Quake 2 с добавленным зеленым туманом:
Еще немного информации о том, как работает Detours, поскольку я использовал его из первых рук:
Обход работает на двух уровнях. Фактическое перехватывание работает только в том же пространстве процесса, что и целевой процесс. Таким образом, в Detours есть функция для внедрения DLL в процесс и принудительного запуска его DLLMain, а также функций, которые должны использоваться в этой DLL. Когда DLLMain запущен, DLL должна вызвать DetourAttach(), чтобы указать функции для перехвата, а также функцию "обход", которая является кодом, который вы хотите переопределить.
Так что это в основном работает так:
- У вас есть приложение запуска, единственной задачей которого является вызов DetourCreateProcessWithDll(). Он работает так же, как CreateProcessW, только с несколькими дополнительными параметрами. Это внедряет DLL в процесс и вызывает его DllMain().
- Вы реализуете DLL, которая вызывает функции Detour и устанавливает функции батута. Это означает вызов DetourTransactionBegin(), DetourUpdateThread(), DetourAttach(), за которым следует DetourTransactionEnd().
- Используйте панель запуска для внедрения DLL, которую вы внедрили в процесс.
Есть некоторые предостережения, хотя. Когда DllMain запущен, библиотеки, которые импортируются позже с помощью LoadLibrary(), еще не видны. Таким образом, вы не можете обязательно все настроить во время события вложения DLL. Обходной путь - отслеживать все функции, которые были переопределены до сих пор, и пытаться инициализировать другие функции внутри этих функций, которые вы уже можете вызывать. Таким образом, вы обнаружите новые функции, как только LoadLibrary отобразит их в область памяти процесса. Я не совсем уверен, насколько хорошо это будет работать для wglGetProcAddress, хотя. (Возможно, у кого-то еще есть идеи по этому поводу?)
Некоторые вызовы LoadLibrary() кажутся неудачными. Я тестировал с Quake 2, и DirectSound и waveOut API по какой-то причине не смогли инициализироваться. Я все еще расследую это.
Я нашел проект sourceforge под названием taksi:
Такси не обеспечивает захват звука, хотя.
В прошлом я писал экранные грабберы (эпоха DirectX7-9). Я обнаружил, что старый добрый DirectDraw работает замечательно хорошо и надежно захватывает биты содержимого с аппаратным ускорением / видеоэкрана, который другие методы (D3D, GDI, OpenGL), кажется, оставляют пустым или зашифрованным. Это было очень быстро тоже.