How to copy depth buffer to memory in Metal?

Discussion area about developing with Ogre-Next (2.1, 2.2 and beyond)


Post Reply
jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

How to copy depth buffer to memory in Metal?

Post by jwwalker »

I want to take a snapshot of the depth buffer, so I tried to imitate working code for the color buffer:

Code: Select all

_ogreWindow->setWantsToDownload( true );
_ogreWindow->setManualSwapRelease( true );
Ogre::Root::getSingleton().renderOneFrame();
if (_ogreWindow->canDownloadData())
{
    Ogre::TextureGpu* texture = _ogreWindow->getDepthBuffer();
    Ogre::Image2 img;
    img.convertFromTexture( texture, 0, 0 );
....
But the img.convertFromTexture call crashes inside Metal with a divide by 0 error.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5296
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1278
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by dark_sylinc »

I got the same error and I don't know why it happens. But I do know that it works just fine if I disable API validation, which leads me to suspect it may be a validation bug.
jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by jwwalker »

You're right that turning off Metal API validation makes the crash go away. However, my buffer ends up with nothing but zeros in it, so it doesn't seem like it is actually working.
jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by jwwalker »

I still can't get any actual nonzero data from the depth buffer, though no error is being reported. Any ideas?

jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by jwwalker »

I found that I can get some sort of a depth image if, instead of using a Window, I use a RenderToTexture render target, and say

Code: Select all

	Ogre::TextureGpu* depthTexture = renderSystem->
		getDepthBufferFor( colorTexture, Ogre::DepthBuffer::POOL_DEFAULT, false,
			Ogre::PixelFormatGpu::PFG_D24_UNORM_S8_UINT );

It's important that I'm using the format PFG_D24_UNORM_S8_UINT rather than the default depth format for Metal, PFG_D32_FLOAT_S8X24_UINT. That wouldn't work with a Window, because when a texture comes from a Window, getDepthBufferFor just returns the texture from Window::getDepthBuffer and ignores the other parameters.

I still have some trouble, though, because if I call my depth snapshot function twice, ASAN detects a heap buffer overflow in Ogre::TextureGpu::isTexture().

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5296
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1278
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by dark_sylinc »

Can you post ASAN's report?

jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by jwwalker »

I wasn't sure how to clean up after my attempt to capture the depth buffer, specifically whether I should call destroyTexture with the depth texture obtained using getDepthBufferFor. When I omit that step, I don't get the ASAN error, so I assume I wasn't supposed to do that.

However, there is another issue: When I quit my app, I get an assertion failure, with this in the log:

Code: Select all

*-*-* OGRE Shutdown
Unregistering ResourceManager for type OldSkeleton
Unregistering ResourceManager for type Mesh2
Unregistering ResourceManager for type Mesh
Unregistering ResourceManager for type Material
Unregistering ResourceManager for type HighLevelGpuProgram
Uninstalling plugin: Metal RenderSystem
/Volumes/Work/git-repos/ogre-next-fork/OgreMain/src/OgreRenderSystem.cpp(793): Assert Failure: 'mSharedDepthBufferRefs.empty() && "destroyAllRenderPassDescriptors followed by _cleanupDepthBuffers should've " "emptied mSharedDepthBufferRefs. Please report this bug to " "https://github.com/OGRECave/ogre-next/issues/"' 
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5296
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1278
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by dark_sylinc »

Yeah. Textures created by getDepthBufferFor are auto-managed.

In fact users are no longer supposed to call getDepthBufferFor since Ogre >= 2.2; since manually assigned depth buffers are meant now to be created manually like any other texture via TextureGpuManager::createTexture; and linked via RTVs (RenderTargetView) in the compositor.

If you still want to use getDepthBufferFor though, you can call _dereferenceSharedDepthBuffer to release it. It's a simple manual reference count scheme.

jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by jwwalker »

Thank you, using _dereferenceSharedDepthBuffer fixes my error messages.

If users aren't supposed to call getDepthBufferFor any more, maybe the API docs for getDepthBufferFor should say so.

I'll try to learn about RTVs. Do RTVs have any relevance to the problem of reading the depth buffer from a Window?

jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by jwwalker »

I worked around the problem with reading depth from a window. In app initialization, I call

Code: Select all

Ogre::DepthBuffer::AvailableDepthFormats =
    Ogre::DepthBuffer::DFM_D24 | Ogre::DepthBuffer::DFM_S8;

which results in the depth buffer being created with the format PFG_D24_UNORM_S8_UINT rather than PFG_D32_FLOAT_S8X24_UINT.

jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by jwwalker »

Although I've gotten things to work somewhat, I still wish I could use the default (and higher-resolution) depth stencil format, PFG_D32_FLOAT_S8X24_UINT, which when using Metal translates to MTLPixelFormatDepth32Float_Stencil8.

One thing I wonder about is that I found some messages online saying that in order to copy from Metal textures reliably, you need to use MTLStoreActionStore, not MTLStoreActionDontCare. And it looks like Ogre is using MTLStoreActionDontCare. I tried changing that, by messing with the mStoreActionDepth member of a CompositorPassSceneDef, but it didn't seem to fix anything.

I also notice that Ogre seems to assume that PFG_D32_FLOAT_S8X24_UINT is a 64-bit format, which might not be the case for MTLPixelFormatDepth32Float_Stencil8. Apple's docs on MTLPixelFormatDepth32Float_Stencil8 say it is a 40-bit format, but "some Metal device objects allocate 64-bits per pixel". So presumably in some other cases, it is 40 bits per pixel.

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5296
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1278
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by dark_sylinc »

One thing I wonder about is that I found some messages online saying that in order to copy from Metal textures reliably, you need to use MTLStoreActionStore, not MTLStoreActionDontCare. And it looks like Ogre is using MTLStoreActionDontCare. I tried changing that, by messing with the mStoreActionDepth member of a CompositorPassSceneDef, but it didn't seem to fix anything.

Ahhh!!! I never realized that could be the problem.

You can do that but you should change the CompositorPassSceneDef before the workspace instance is initialized (also change both stencil and depth just in case), i.e. before the compositorManager->addWorkspace call.

I also notice that Ogre seems to assume that PFG_D32_FLOAT_S8X24_UINT is a 64-bit format, which might not be the case for MTLPixelFormatDepth32Float_Stencil8. Apple's docs on MTLPixelFormatDepth32Float_Stencil8 say it is a 40-bit format, but "some Metal device objects allocate 64-bits per pixel". So presumably in some other cases, it is 40 bits per pixel.

Desktop GPUs (e.g. NV, AMD) typically use two 32-bit buffers and treat them as one buffer (32 for depth, 32 for stencil).
Apple GPUs (i.e. iPhone and M1) keeps them separate, so it's one 32-bit for depth and one 8-bit for stencil.

Initially Apple didn't have the MTLPixelFormatDepth32Float_Stencil8 format (you could use MTLPixelFormatDepth32Float + MTLPixelFormatDepthStencil8), but MTLPixelFormatDepth32Float_Stencil8 was added when they ported Metal to macOS (because Desktop GPUs couldn't separate depth from stencil; while iOS can simply pretend they're joined together).

jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by jwwalker »

dark_sylinc wrote: Tue Apr 05, 2022 1:10 am

You can do that but you should change the CompositorPassSceneDef before the workspace instance is initialized (also change both stencil and depth just in case), i.e. before the compositorManager->addWorkspace call.

I tried adding

Code: Select all

passSceneDef->mStoreActionDepth = Ogre::StoreAction::Store;
passSceneDef->mStoreActionStencil = Ogre::StoreAction::Store;

just before the addWorkspace call, but the problem persists. Just to be sure, I also tried changing the store actions in the source code of CompositorManager2::createBasicWorkspaceDef.

jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by jwwalker »

I finally got some nonzero depth data by some additional hackery. In MetalAsyncTextureTicket::downloadFromGpu, I added

Code: Select all

MTLBlitOption blitOption = MTLBlitOptionNone;
if (PixelFormatGpuUtils::isDepth( textureSrc->getPixelFormat() ))
{
	blitOption = MTLBlitOptionDepthFromDepthStencil;
}

Then later in the same function, inside the loop, I supply the blit option:

Code: Select all

[blitEncoder copyFromTexture:srcTextureMetal->getFinalTextureName()
				 sourceSlice:srcTextureBox.sliceStart + i
				 sourceLevel:mipLevel
				sourceOrigin:mtlOrigin
				  sourceSize:mtlSize
					toBuffer:mVboName
		   destinationOffset:destBytesPerImage * i
	  destinationBytesPerRow:destBytesPerRow
	destinationBytesPerImage:destBytesPerImage
                     options:blitOption];

This hack is not ideal for at least two reasons. One, what if you actually want to read the stencil buffer? And two, after calling Image2::convertFromTexture, Image2::getData will claim that you're getting 8 bytes per pixel, but you're actually getting 4 bytes per pixel.

jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by jwwalker »

One more thing: in Apple's docs for -[MTLBlitCommandEncoder copyFromTexture:sourceSlice:sourceLevel:sourceOrigin:sourceSize:toBuffer:destinationOffset:destinationBytesPerRow:destinationBytesPerImage:options:], it says

If the texture’s pixel format is a combined depth/stencil format, then options must be set to either blit the depth attachment portion or blit the stencil attachment portion.

That explains why it's necessary to specify MTLBlitOptionDepthFromDepthStencil.

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5296
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1278
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by dark_sylinc »

Oh... my... God.

Most APIs just ignore the stencil part and you can only grab it with special means; defaulting to Depth (which is what Ogre Next does so far).

It appears Metal expects a 40-bit buffer. That would explain the division by 0 in the validation layers: bytes supplied / bytes needed = truncates to 0 when bytes supplied < bytes needed.

We need to use MTLBlitOptionDepthFromDepthStencil.
Thanks for looking into this.

jwwalker
Goblin
Posts: 224
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 17
Contact:

Re: How to copy depth buffer to memory in Metal?

Post by jwwalker »

So, maybe in the Metal case this pixel format should be called PFG_D32_FLOAT_S8_UINT rather than PFG_D32_FLOAT_S8X24_UINT, since there's no 'X24' part. And, should PixelFormatGpuUtils::getBytesPerPixel return 4 or 5, since on the GPU it may be 5 bytes per pixel but when you read out the depth you get 4 bytes per pixel?

Post Reply