0

EDIT: As ozeanix points out in the comments it is not possible to copy a D24S8 surface to system memory. Unfortunately you also cannot bind the surface (or rather its containing texture) to a pixel shader.

I ended up going the reimplementation way, intercepting all D3D9 calls. If the graphics card supports it - all cards with DX10+ should do - it is sometimes possible to exchange the D24S8 format with FOURCC INTZ, which has the same memory layout, but can be bound to a pixel shader's texture sampler and thus copied to a different texture in video memory. This texture's content can then be copied to a system memory texture with GetRenderTargetData.


Original post:

I am developing a plugin which should calculate the world position of each pixel contained in a Direct3D surface. The plugin gets called via callback when there is a newly rendered image waiting in the back buffer.

Given are two pointers (IDirect3DSurface9*) to the render target surface (D3DFMT_X8R8G8B8, D3DMULTISAMPLE_NONE) and the depth buffer surface (D3DFMT_D24S8, D3DMULTISAMPLE_NONMASKABLE). The linked device's display mode is also set to "D3DFMT_X8R8G8B8"

The settings above cannot be changed!

To be able to calculate the world position I also need access to the depth buffer values. So my first attempt was to create an offscreen plain surface with format "D24S8" in system memory, use "GetRenderTargetData" to copy the data and then lock this offscreen surface (with LockRect). Unfortunately, the very first command failed already - apparently this format is not allowed in system memory (there is no problem creating such an offscreen surface in the default pool). So I used the DirectX Caps Viewer ...

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.

What I also tried:

  1. Create a texture with only one level and format set to D3DFMT_D24S8, placed in D3DPOOL_SYSTEMMEM (also failed)

  2. Create a surface of type D32F_LOCKABLE in D3DPOOL_DEFAULT (successful) and use D3DXLoadSurfaceFromSurface to convert and copy the data so that a subsequent call to LockRect would provide a handle to the data (failed)

  3. Create depth/stencil surface with disabled multisampling (successful) and use StretchRect to copy from source to destination (failed) and check if multisampling is part of the problem

My questions

  1. According to DirectX Caps Viewer D24S8 should be supported on textures. Are those lists only applicable for ressources in default memory pool?

  2. Is there any way I can copy the depth values to system memory at all?

  3. Is there any possibility to enable extended error reporting for DirectX calls on Windows 8.1? I know that beginning with Win8.1 the OS itself uses the same API and thus the DirectX runtime cannot be set to debug mode, but perhaps I have missed an appropriate option


By the way, with D3DDevice Type "REF" there are a few depth and stencil formats listed for Plain Surfaces (D16_LOCKABLE, D16, D32F_LOCKABLE, D32_LOCKABLE, S8_LOCKABLE) and no depth/stencil entries available in the list of texture formats, but I need to use hardware acceleration, otherwise my application renders way too slow.

Apollo13
  • 119
  • 1
  • 1
  • 11
  • 1
    The only place you can access the `D24S8` data is on the GPU itself, so the only thing that occurs to me is to use a pixel shader for the conversion. I don't know whether this is possible within the bounds of your implementation. Is there a way to do a render pass with a pixel shader that receives the `D24S8` as a 2D texture and converts that into a `D32F_LOCKABLE` texture? If that general concept works, you might also be able to render the `D24S8` out to an `X8R8G8B8` offscreen plain surface and `LockRect` that surface, then "restructure" the data on the CPU side. – ozeanix Jun 12 '17 at 23:34
  • @ozeanix The two handles mentioned in my question are the only parameters that I can use to access the D3D context. But I thought about trying to inject a proxy dll. Generally speaking, is it possible to add a pixel shader via such an approach? Or do you know of any method which I could use in my callback (e.g. adding the shader upon the first call and using its results in subsequent calls)? – Apollo13 Jun 13 '17 at 09:06
  • Well, you can use those handles to get access to quite a lot of things, like the original D3D device and the top level D3D object used to create devices. You might be able to get the shared resource pointer for the existing surfaces (although that's only supposed to exist for 9Ex surfaces), then create surface using that shared pointer on your own private device, then set up your own separate render pass, binding that texture to a shading pass and rendering out to a full screen quad with a readable render target surface. Just an intuition about what direction to look. – ozeanix Jun 13 '17 at 10:22
  • If you wanted to go the proxy route, although it's hard, you would effectively re-implement d3d9.dll and proxy all the calls across to the real implementation. You could do a separate depth/stencil pass to a separate surface of your creation by intercepting the original app's depth pass. If you're really sneaky, you could implement a new COM interface in your proxy DLL to give your plugin access to that extra data it shouldn't have. You'd just have to query the IDirect3DDevice9 object for some kind of ISneakyDepthProxy interface, and provide that in the QI implementation in the proxy. – ozeanix Jun 13 '17 at 10:29
  • One other evil method would be to overwrite specific vtable pointers of the IDirect3DDevice9 you get from the surfaces, and implement your proxy that way. You can save the original vtable pointer in your own code, substitute your own function pointer in its place, and then use the original to complete the app's intended call after you're done interfering with it. I think you'd also have to keep checking every time whether the vtable pointer is pointing to your code or the original D3D code because if the device is re-created you have to re-hook the interface. Hope those suggestions help. – ozeanix Jun 13 '17 at 10:40
  • Thanks a lot! Apparently there are plenty of possibilities :) I have never done anything DirectX-related before so your suggestions are highly welcome, and yes, I think they are very helpful! Actually what you have written is a complete answer, so if you want to, feel free to repost. I will accept this as an answer if you like. – Apollo13 Jun 13 '17 at 11:31
  • @ozeanix apparently I am lacking a lot of knowledge and am already stuck. I. It is not possible to call a pixel shader on its own, but I have to SetVertexShader (probably to NULL?!) and SetPixelShader and then call an appropriate DrawXXX function, right? II. In case the former is correct, which one is the right? I wanted to go the shader way you described first (so no proxy/interception) but as I said I only got those two handles and don't know how to add the additional render pass. Right now I've got the impression that my only option is to call this Draw command ... – Apollo13 Jun 14 '17 at 13:41
  • ... inside the callback, but I think it is highly preferrable to somehow "enqueue" this shader execution so that upon handling the callback I could already copy the depth values from the other surface. – Apollo13 Jun 14 '17 at 13:44
  • 1
    I'm not sure that will work, because you'd be really disrupting the rendering passes of the app that calls your plugin. Because the vertex and pixel shaders are "stateful", you have to set them, then render a fullscreen quad into a render target or offscreen surface, and then un-set them. If you left them set and the calling app didn't re-set them on its next pass (which it likely would) then you'd disrupt the on-screen rendering greatly. That's why I think you'll need to use the original device (or make a new one) to render a new pass within your plugin. – ozeanix Jun 14 '17 at 23:38
  • @ozeanix Sorry, I am still stuck. I wrote another question which hopefully is more concrete. Perhaps you also want to take a look [here](https://stackoverflow.com/questions/44635161/direct3d9-access-depth-stencil-buffer-in-pixel-shader-and-probably-copy-it-to) – Apollo13 Jun 19 '17 at 16:04

0 Answers0