Jump to content
  • Advertisement
Sign in to follow this  
L. Spiro

This Just In: Manual Mipmapping Causes Memory Loss, Hair Loss

This topic is 2581 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

What was I going to post again?


Oh yes. I have been ripping out other people’s hair trying to figure out why I am getting this debug output on my screen:

CDirectX9StandardTexture::CreateApiTexture(): Mipmaps made fine.
CDirectX9StandardTexture::CreateApiTexture(): Mipmaps made fine.
CDirectX9StandardTexture::CreateApiTexture(): Mipmaps made fine.
CDirectX9StandardTexture::CreateApiTexture(): Mipmaps made fine.
Direct3D9: (WARN) :Alloc of size 1398220 FAILED!
Direct3D9: (ERROR) :Out of memory allocating memory for mip-map levels.
Direct3D9: (ERROR) :Error during initialization of texture. CreateTexture failed.
Direct3D9: (ERROR) :Failure trying to create a texture



It all started on a warm summer morning long ago at about 7:30 AM this morning when I swapped out ::D3DXFilterTexture() in exchange for my own mipmapping routine:
From this:

if ( m_bMipMaps ) {
m_pd3dtTexture->AddDirtyRect( NULL );
// TODO: Replace this.
if ( FAILED( ::D3DXFilterTexture( m_pd3dtTexture, NULL, 0UL, D3DX_FILTER_BOX ) ) ) {
//CDirectX9::SafeRelease( m_pd3dtTexture );
//return false;
}
}

to this:

if ( m_bMipMaps ) {
LSUINT32 ui32AllocNum = CMemLib::GetAllocationCounter();

LSUINT32 ui32Level = 1UL;
CImage iTemp;
ui32NewWidth >>= 1UL;
ui32NewHeight >>= 1UL;
m_pd3dtTexture->AddDirtyRect( NULL );
while ( ui32NewWidth && ui32NewHeight ) {
if ( !piUseMe->Resample( ui32NewWidth, ui32NewHeight, LSI_F_KAISER_FILTER, iTemp ) ) {
CDirectX9::SafeRelease( m_pd3dtTexture );
CStd::DebugPrintA( "CDirectX9StandardTexture::CreateApiTexture(): !piUseMe->Resample( ui32NewWidth, ui32NewHeight, LSI_F_KAISER_FILTER, iTemp )\r\n" );
return false;
}

if ( FAILED( m_pd3dtTexture->GetSurfaceLevel( ui32Level++, &pd3ds9Surface ) ) ) {
CDirectX9::SafeRelease( m_pd3dtTexture );
CStd::DebugPrintA( "CDirectX9StandardTexture::CreateApiTexture(): FAILED( m_pd3dtTexture->GetSurfaceLevel( ui32Level++, &pd3ds9Surface ) )\r\n" );
return false;
}
if ( !CopyTexelsToDirectX9Surface( pd3ds9Surface, iTemp ) ) {
CDirectX9::SafeRelease( pd3ds9Surface );
CDirectX9::SafeRelease( m_pd3dtTexture );
CStd::DebugPrintA( "CDirectX9StandardTexture::CreateApiTexture(): !CopyTexelsToDirectX9Surface( pd3ds9Surface, iTemp )\r\n" );
return false;
}
CDirectX9::SafeRelease( pd3ds9Surface );

ui32NewWidth >>= 1UL;
ui32NewHeight >>= 1UL;
if ( ui32NewWidth || ui32NewHeight ) {
// Lock one dimension to 1 if both are not 0.
ui32NewWidth = CStd::Max<LSUINT32>( ui32NewWidth, 1UL );
ui32NewHeight = CStd::Max<LSUINT32>( ui32NewHeight, 1UL );
}
}
assert( ui32Level == m_pd3dtTexture->GetLevelCount() );
CStd::DebugPrintA( "CDirectX9StandardTexture::CreateApiTexture(): Mipmaps made fine.\r\n" );

LSA_SIZE sSize = CMemLib::PrintAllocations( ui32AllocNum );
if ( sSize ) {
int gjhg = 0;
}
CMemLib::ReleaseEmptyPools();
}



Pardon some of the debug code.

As can be seen by the messages I try to print, mipmapping works fine.
CopyTexelsToDirectX9Surface() has been used for all of my successful image creations before and certainly has no problems.
If piUseMe->Resample() has a problem resampling the image (which it does not), then the result would be a jumbled image, not total failure, so that is not the issue.


Somehow my new code causes a loss of RAM or resources.
So as you can see I put in some memory checks. I know that piUseMe->Resample() uses a decent amount of RAM, so I was suspicious of a leak.
Yet I have been resampling images for a while now and no leaks have ever been reported (my memory manager will catch them all when I change states and shut down), and I added some memory-allocator debug output here to allow me to check the state of the heap before and after the resampling takes place.
Within my loop, only piUseMe->Resample() actually performs any allocations.
My diagnostics and close scrutiny of the code indicate indeed that piUseMe->Resample() is not leaking memory.


So what else could it be? Where have I leaked a resource within my new code? The new code is the only thing I have changed (aside from commenting out the old).


L. Spiro

Share this post


Link to post
Share on other sites
Advertisement
No ideas?
I can’t think of why I am consuming more RAM this way.
By generating mipmaps manually, perhaps it forces all of those mipmaps to be allocated up-front? I was under the impression this was the case anyway, but I am grasping at straws here.

Can I at least assume the leakage is something in my own code? Can I at least be sure that the way I am using the Direct3D 9 resources is not causing a leakage or unnecessary RAM overhead?
It should be the same should it not?


L. Spiro

Share this post


Link to post
Share on other sites
The normal thing I'd point my finger at here is a surface not released after being obtained via GetSurfaceLevel, but I see that you're doing that.

Have you tried cross-checking with D3DUSAGE_AUTOGENMIPMAP on the texture?

Share this post


Link to post
Share on other sites
Creation of the textures boils down to this:


HRESULT hRes = CDirectX9::GetDirectX9Device()->CreateTexture( piUseMe->GetWidth(), piUseMe->GetHeight(),
m_bMipMaps ? 0UL : 1UL,
D3DUSAGE_DYNAMIC,
D3DFMT_A8R8G8B8,
D3DPOOL_SYSTEMMEM,
&m_pd3dtTexture, NULL );
if ( FAILED( hRes ) ) { return false; }


IDirect3DSurface9 * pd3ds9Surface;
if ( FAILED( m_pd3dtTexture->GetSurfaceLevel( 0UL, &pd3ds9Surface ) ) ) {
CDirectX9::SafeRelease( m_pd3dtTexture );
return false;
}
if ( !CopyTexelsToDirectX9Surface( pd3ds9Surface, (*piUseMe) ) ) {
CDirectX9::SafeRelease( pd3ds9Surface );
CDirectX9::SafeRelease( m_pd3dtTexture );
return false;
}
CDirectX9::SafeRelease( pd3ds9Surface );


The width and height here always match those of ui32NewWidth and ui32NewHeight prior to the >>=.
In the scene I am loading, every texture is square and a power of 2, the largest being 1,024×1,024. So all of the texture data is well formated.

Once the texture is created as above, it immediately goes into the creation of mipmapping, which is in my first post.


L. Spiro

Share this post


Link to post
Share on other sites
Disabling my resampling routine fixed the error (of course the mipmaps were then filled with garbage).
There are no memory leaks, buffer overruns, or heap corruptions, but somehow the resampler is causing problems when performing extreme resizing (1,024×1,024 to 1×1 for example).
Debugging the code, everything looks fine.

In any case the bug is not related to memory use or DirectX. Thank you for your attention.


L. Spiro

Share this post


Link to post
Share on other sites
Does your memory manager not play nice with DirectX ? I just found this. Try disabling it, e.g. only for the DX API calls, if possible.

Share this post


Link to post
Share on other sites
As it turns out, this article was the cause for my headaches.
My memory manager’s diagnostics consistently showed no leaks and no buffer overflows.

While continuing to mess with my code the error later changed to Win32 itself complaining about a corrupt heap. Since it was Windows itself complaining about a heap corruption, I knew it had to be a heap that was not managed by my memory manager (and thus a heap not created by my engine).

After a lot of thought, I realized there was one place where I was messing with memory that was not allocated by my engine (directly anyway): The memory I use to send texels to textures for DirectX.
I looked at my code for filling texture RGB values and indeed there was definitely not an overrun there either.

Finally I considered, “What if I am trying to fill in more mipmaps than there are, or my mipmap dimensions are wrong?”
The MSDN document says that if an image starts 32×8, it will go down to 4×1, and then still do 2×1 and finally 1×1.
See my comment where I lock dimensions if not both are 0?
That was the bug.
In actuality, the mipmaps stop at 4×1.

Either MSDN is wrong, or there is something special about those 2×1 and 1×1 mipmaps. When I requested those surface levels, the function did not fail and I was given a pointer.
But trying to fill in that surface level resulted in overwriting memory that DirectX had allocated.
By removing that section of code and stopping mipmaps after 4×1, all bugs vanished.


L. Spiro


[EDIT]
MSDN is clearly wrong. It contradicts itself in that page.
If the top-level mipmap has dimensions of 256x128, the dimensions of the second-level mipmap are 128x64, the third-level are 64x32, and so on, down to 1x1.[/quote]
Then it goes on to say two things:
You cannot request a number of mipmap levels in Levels that would cause either the width or height of any mipmap in the chain to be smaller than 1.[/quote]
The top-level dimensions are 4x2, the second-level dimensions are 2x1, and the dimensions for the third level are 1x1. A value larger than 3 in Levels results in a fractional value in the height of the second-level mipmap, and is therefore disallowed.[/quote]
Twice it shows bringing chains all the way down to 1×1, and twice it says you can’t request a level that would cause a fractional dimension. In the end it pinpoints only the height as being at such a risk, when in fact, if it is really 1×1, both the width and the height would go into fractions.
They clearly meant that you should stop at level 2, since that is when the height next becomes fractional.

A lot of people lost a lot of hair because of these misleading examples.
[/EDIT]

Share this post


Link to post
Share on other sites
That's interesting, mipmap chains should go down to 1x1...

Maybe cross-check the number of miplevels you're assuming with the value returned from IDirect3DTexture9::GetLevelCount?

Share this post


Link to post
Share on other sites
In my original code you can see an assert() where I checked that. It was never triggered while I went down to 1×1.
Knowing that if it did not trigger going down to 1×1 then it should trigger if I stopped at 4×1, I removed that assert when I switched to stopping after 4×1.

This is most likely a DirectX 9 bug. The chains seems be generated down to 1×1, and you can access the 2×1 and 1×1 from my example, but actually modifying them is not possible.


L. Spiro

Share this post


Link to post
Share on other sites

This is most likely a DirectX 9 bug.


You think it's most likely that the bug is in a basic feature of an API that's nearly 10 years and old and has shipped hundreds of games?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!