Скопируйте буфер глубины в неблокируемом формате (D24S8) в системную память с помощью Direct3D9, Win 8.1

РЕДАКТИРОВАТЬ: Как указывает ozeanix в комментариях, невозможно скопировать поверхность D24S8 в системную память. К сожалению, вы также не можете привязать поверхность (точнее, содержащую ее текстуру) к пиксельному шейдеру.

В итоге я пошел по пути повторной реализации, перехватив все вызовы D3D9. Если графическая карта поддерживает его (подходят все карты с DX10+), иногда можно заменить формат D24S8 форматом FOURCC INTZ, который имеет такую ​​же структуру памяти, но может быть привязан к сэмплеру текстуры пиксельного шейдера и, таким образом, скопирован в другой текстуры в видеопамяти. Затем содержимое этой текстуры можно скопировать в текстуру системной памяти с помощью GetRenderTargetData.


Исходный пост:

Я разрабатываю плагин, который должен вычислять мировую позицию каждого пикселя, содержащегося на поверхности Direct3D. Плагин вызывается через обратный вызов, когда в заднем буфере находится новое отрендеренное изображение.

Даны два указателя (IDirect3DSurface9*) на целевую поверхность рендеринга (D3DFMT_X8R8G8B8, D3DMULTISAMPLE_NONE) и поверхность буфера глубины (D3DFMT_D24S8, D3DMULTISAMPLE_NONMASKABLE). Режим отображения связанного устройства также установлен на «D3DFMT_X8R8G8B8».

Вышеуказанные настройки изменить нельзя!

Чтобы иметь возможность вычислить мировую позицию, мне также нужен доступ к значениям буфера глубины. Поэтому моей первой попыткой было создать в системной памяти закадровую плоскую поверхность в формате «D24S8», использовать «GetRenderTargetData» для копирования данных, а затем заблокировать эту закадровую поверхность (с помощью LockRect). К сожалению, самая первая команда уже не удалась — видимо, этот формат не разрешен в системной памяти (создание такой закадровой поверхности в дефолтном пуле — не проблема). Поэтому я использовал средство просмотра заглавных букв DirectX...

D3D Device Type "HAL" and Adapter Format "D3DFMT_X8R8G8B8":

Depth/Stencil Formats: 
    "D3DFMT_D24S8", "D3DFMT_D24X8", "D3DFMT_D16", "D3DFMT_D32F_LOCKABLE"

Texture Formats (only mentioning the depth/stencil & depth formats):
    "D3DFMT_D24S8", "D3DFMT_D24X8", "D3DFMT_D16", "D3DFMT_D32F_LOCKABLE",
    "DXT1" through "DXT5" 
    - but all are only supported in conjunction with usage type "0 (Plain)".

Plain Surface Formats: No depth stencil formats at all are listed.

Что я также пробовал:

  1. Создайте текстуру только с одним уровнем и форматом D3DFMT_D24S8, помещенную в D3DPOOL_SYSTEMMEM (тоже не удалось)

  2. Создайте поверхность типа D32F_LOCKABLE в D3DPOOL_DEFAULT (успешно) и используйте D3DXLoadSurfaceFromSurface для преобразования и копирования данных, чтобы последующий вызов LockRect предоставил дескриптор данных (не удалось)

  3. Создайте поверхность глубины/трафарета с отключенной мультисэмплингом (успешно) и используйте StretchRect для копирования из источника в место назначения (сбой) и проверьте, не является ли мультисэмплинг частью проблемы

Мои вопросы

  1. Согласно DirectX Caps Viewer D24S8 должна поддерживаться текстура. Эти списки применимы только к ресурсам в пуле памяти по умолчанию?

  2. Можно ли вообще скопировать значения глубины в системную память?

  3. Есть ли возможность включить расширенные отчеты об ошибках для вызовов DirectX в Windows 8.1? Я знаю, что, начиная с Win8.1, сама ОС использует тот же API, и поэтому среда выполнения DirectX не может быть установлена ​​в режим отладки, но, возможно, я пропустил соответствующую опцию.


Между прочим, с типом D3DDevice "REF" для простых поверхностей указано несколько форматов глубины и трафарета (D16_LOCKABLE, D16, D32F_LOCKABLE, D32_LOCKABLE, S8_LOCKABLE), а в списке форматов текстур нет записей глубины/трафарета, но мне нужно использовать аппаратное ускорение, иначе мое приложение будет работать слишком медленно.


person Apollo13    schedule 12.06.2017    source источник
comment
Единственное место, где вы можете получить доступ к данным D24S8, находится на самом графическом процессоре, поэтому единственное, что приходит мне в голову, — это использовать пиксельный шейдер для преобразования. Я не знаю, возможно ли это в рамках вашей реализации. Есть ли способ сделать проход рендеринга с помощью пиксельного шейдера, который получает D24S8 как 2D-текстуру и преобразует ее в текстуру D32F_LOCKABLE? Если эта общая концепция работает, вы также можете рендерить D24S8 на X8R8G8B8 закадровую плоскую поверхность и LockRect эту поверхность, а затем реструктурировать данные на стороне процессора.   -  person ozeanix    schedule 13.06.2017
comment
@ozeanix Два дескриптора, упомянутые в моем вопросе, являются единственными параметрами, которые я могу использовать для доступа к контексту D3D. Но я подумал о попытке внедрить прокси-dll. Вообще говоря, можно ли с помощью такого подхода добавить пиксельный шейдер? Или вы знаете какой-либо метод, который я мог бы использовать в своем обратном вызове (например, добавление шейдера при первом вызове и использование его результатов при последующих вызовах)?   -  person Apollo13    schedule 13.06.2017
comment
Ну, вы можете использовать эти дескрипторы, чтобы получить доступ ко многим вещам, таким как оригинальное устройство D3D и объект D3D верхнего уровня, используемый для создания устройств. Возможно, вы сможете получить указатель общего ресурса для существующих поверхностей (хотя предполагается, что он существует только для поверхностей 9Ex), затем создать поверхность, используя этот общий указатель на своем собственном частном устройстве, а затем настроить свой собственный отдельный проход рендеринга, привязав его. текстуру в проход затенения и рендеринг в полноэкранный квадрат с удобочитаемой целевой поверхностью рендеринга. Просто интуиция о том, в каком направлении смотреть.   -  person ozeanix    schedule 13.06.2017
comment
Если вы хотите пойти по прокси-маршруту, хотя это сложно, вы должны эффективно повторно реализовать d3d9.dll и передать все вызовы через реальную реализацию. Вы можете сделать отдельный проход глубины/трафарета к отдельной поверхности вашего творения, перехватив проход глубины исходного приложения. Если вы действительно хитры, вы можете реализовать новый COM-интерфейс в вашей прокси-DLL, чтобы дать вашему плагину доступ к тем дополнительным данным, которых у него не должно быть. Вам просто нужно запросить у объекта IDirect3DDevice9 какой-либо интерфейс ISneakyDepthProxy и предоставить его в реализации QI в прокси.   -  person ozeanix    schedule 13.06.2017
comment
Еще одним злым методом может быть перезапись определенных указателей виртуальной таблицы IDirect3DDevice9, которые вы получаете с поверхностей, и таким образом реализуйте свой прокси. Вы можете сохранить исходный указатель vtable в своем собственном коде, заменить его собственным указателем на функцию, а затем использовать оригинал для завершения предполагаемого вызова приложения после того, как вы закончите вмешиваться в него. Я думаю, вам также придется каждый раз проверять, указывает ли указатель vtable на ваш код или исходный код D3D, потому что, если устройство воссоздается, вам нужно повторно подключить интерфейс. Надеюсь, эти предложения помогут.   -  person ozeanix    schedule 13.06.2017
comment
Большое спасибо! Очевидно, есть много возможностей :) Я никогда раньше не делал ничего, связанного с DirectX, поэтому ваши предложения очень приветствуются, и да, я думаю, что они очень полезны! На самом деле то, что вы написали, является полным ответом, поэтому, если хотите, не стесняйтесь делать репосты. Я приму это как ответ, если хотите.   -  person Apollo13    schedule 13.06.2017
comment
@ozeanix, видимо, мне не хватает знаний, и я уже застрял. I. Невозможно вызвать пиксельный шейдер сам по себе, но я должен установить SetVertexShader (вероятно, в NULL?!) и SetPixelShader, а затем вызвать соответствующую функцию DrawXXX, верно? II. Если верно первое, то какое верно? Сначала я хотел использовать описанный вами шейдер (так что без прокси/перехвата), но, как я уже сказал, у меня есть только эти два дескриптора, и я не знаю, как добавить дополнительный проход рендеринга. Прямо сейчас у меня сложилось впечатление, что мой единственный вариант - вызвать эту команду Draw...   -  person Apollo13    schedule 14.06.2017
comment
... внутри обратного вызова, но я думаю, что крайне предпочтительно как-то поставить выполнение этого шейдера в очередь, чтобы при обработке обратного вызова я уже мог скопировать значения глубины с другой поверхности.   -  person Apollo13    schedule 14.06.2017
comment
Я не уверен, что это сработает, потому что вы действительно нарушите проходы рендеринга приложения, которое вызывает ваш плагин. Поскольку вершинные и пиксельные шейдеры имеют состояние, вы должны установить их, затем отрендерить полноэкранный четырехугольник в цель рендеринга или внеэкранную поверхность, а затем отменить их настройку. Если вы оставили их установленными, а вызывающее приложение не переустановило их при следующем проходе (что, скорее всего, произойдет), то вы сильно нарушили бы рендеринг на экране. Вот почему я думаю, что вам нужно будет использовать исходное устройство (или создать новое) для рендеринга нового прохода в вашем плагине.   -  person ozeanix    schedule 15.06.2017
comment
@ozeanix Извините, я все еще застрял. Я написал еще один вопрос, который, надеюсь, более конкретен. Возможно, вы также захотите взглянуть на ">здесь   -  person Apollo13    schedule 19.06.2017