BUG: DDSFile::load() provides incorrect mipmap count
by Edward Rotberg · in Torque 3D Professional · 04/29/2010 (2:10 pm) · 12 replies
Hi,
SDK 1.0.1 here. I'm trying to load a texture that has no mipmaps in order to cause Torque to set the D3DUSAGE_AUTOGENMIPMAP flag for the texture under DirectX. I have verified, using NVidia's nvddsinfo.exe tool from their Texture Tools 2 kit, that the texture has 0 (ZERO) mipmaps, yet, when the dds file is loaded, it reports 1 (count 'em, one) mipmap. This is unfortunate, as the D3D9 createTexture call will set the D3DUSAGE_AUTOGENMIPMAP flag if numMipmaps == 0 (among other things). As a result I cannot force this flag to get set.
To avoid unnecessary digression, yes, I am very certain that I need this flag set for something I'm trying to do.
= Ed =
SDK 1.0.1 here. I'm trying to load a texture that has no mipmaps in order to cause Torque to set the D3DUSAGE_AUTOGENMIPMAP flag for the texture under DirectX. I have verified, using NVidia's nvddsinfo.exe tool from their Texture Tools 2 kit, that the texture has 0 (ZERO) mipmaps, yet, when the dds file is loaded, it reports 1 (count 'em, one) mipmap. This is unfortunate, as the D3D9 createTexture call will set the D3DUSAGE_AUTOGENMIPMAP flag if numMipmaps == 0 (among other things). As a result I cannot force this flag to get set.
To avoid unnecessary digression, yes, I am very certain that I need this flag set for something I'm trying to do.
= Ed =
About the author
#2
04/29/2010 (4:14 pm)
OK, more information. Even working around this issue by setting the flag manually in the DDS file won't fix this as dds files are never allowed to get the auotgen flag set. I'm going to try this wil a .png file as see where I get.
#3
Looking at the code the trick is that the non-DDS image path we always pass 0 down thru the system which tells it autogenerate mips.
On the DDS path we pass whatever the DDS file says its its mip count. In that case 1 means one mip level... 0 would mean there are no images at all in the DDS which isn't valid. Anyway... so if you look at GFXTextureManager::_createTexture() (the one that takes a DDSFile*) you can do this...
... then in GFXD3D9TextureManager::_loadTexture()...
... didn't test this a bunch and i suspect it might break a any cases where we have a DDS texture that is purposefully only one level.
Still give it a shot and let me know and i'll get a proper fix into 1.1 beta 2.
04/29/2010 (4:32 pm)
Seems like a bug.Looking at the code the trick is that the non-DDS image path we always pass 0 down thru the system which tells it autogenerate mips.
On the DDS path we pass whatever the DDS file says its its mip count. In that case 1 means one mip level... 0 would mean there are no images at all in the DDS which isn't valid. Anyway... so if you look at GFXTextureManager::_createTexture() (the one that takes a DDSFile*) you can do this...
// Ignore padding from the profile. U32 numMips = dds->mMipMapCount == 1 ? 0 : dds->mMipMapCount; // CHANGED! GFXFormat fmt = dds->mFormat; _validateTexParams( dds->getHeight(), dds->getWidth(), profile, numMips, fmt );
... then in GFXD3D9TextureManager::_loadTexture()...
GFXD3D9TextureObject *texture = static_cast<GFXD3D9TextureObject*>(aTexture);
// Fill the texture...
for( int i = 0; i < dds->getNumMipLevels(); i++ ) // CHANGED
{
PROFILE_SCOPE(GFXD3DTexMan_loadSurface);
LPDIRECT3DSURFACE9 surf = NULL;... didn't test this a bunch and i suspect it might break a any cases where we have a DDS texture that is purposefully only one level.
Still give it a shot and let me know and i'll get a proper fix into 1.1 beta 2.
#4
Yea... i don;t think the fix is changing the value of DDSFile::mMipMapCount. I believe that is ok... the issue is when we create the texture like i have in the code above.
We really assumed that for DDS textures autogeneration was never nessasary as you can create as many or few mips as you like with DDS.
04/29/2010 (4:42 pm)
Seems like we posted at the same time.Yea... i don;t think the fix is changing the value of DDSFile::mMipMapCount. I believe that is ok... the issue is when we create the texture like i have in the code above.
We really assumed that for DDS textures autogeneration was never nessasary as you can create as many or few mips as you like with DDS.
#5
getNumMipLevels not is a member of dds
From ddsFile.h:
04/29/2010 (5:02 pm)
For the 1.1 beta 1, i think that is correct:GFXD3D9TextureObject *texture = static_cast<GFXD3D9TextureObject*>(aTexture);
// Fill the texture...
//for( int i = 0; i < aTexture->mMipLevels; i++ )
//for( int i = 0; i < dds->getNumMipLevels(); i++ ) // CHANGED
for( int i = 0; i < dds->getMipLevels(); i++ ) // CHANGED
{
PROFILE_SCOPE(GFXD3DTexMan_loadSurface);
LPDIRECT3DSURFACE9 surf = NULL;getNumMipLevels not is a member of dds
From ddsFile.h:
U32 getMipLevels() const { return mMipMapCount; }
#6
Thanks all!
= Ed =
04/29/2010 (5:07 pm)
Interesting. In any event, I've switched over to using .png files to force D3DUSAGE_AUTOGENMIPMAP to be set for the D3D9 Texture. Hopefully I can get the rest of this stuff to work now.Thanks all!
= Ed =
#7
04/29/2010 (6:35 pm)
Changing the code as you indicated, something strange is happening. Even loading of PNG, are subject to change.
#8
However, my problems now lie with D3D9 (sigh). I have verified that my texture sets the D3DUSAGE_AUTOGENMIPMAP usage flag when the D3D9Texture gets created. However, even after calling IDirect3DBaseTexture9::GenerateMipSubLevels(), my mipmap level is only 1. But at least this is not a Torque issue :/
= Ed =
04/29/2010 (8:35 pm)
Well, there is also code (looks like it's pretty old and put in to fix a problem with a certain class of video card), that disables the ability to autogen mipmaps if your texture is not a power of 2 in both dimensions.However, my problems now lie with D3D9 (sigh). I have verified that my texture sets the D3DUSAGE_AUTOGENMIPMAP usage flag when the D3D9Texture gets created. However, even after calling IDirect3DBaseTexture9::GenerateMipSubLevels(), my mipmap level is only 1. But at least this is not a Torque issue :/
= Ed =
#9
So in my own hacking way, I went into one of the texture files of interest (.dds file), and set the flag that indicates that the mipcount in the file is to be used. This in an effort to trick Torque into setting the mipLevels to 0, BUT NOT setting the D3DUSAGE_AUTOGENMIPMAP usage flag. A breakpoint in gfxD3D9TextureManager.cpp at line 229, and then single stepping after the texture in question is created, show that D3D( now correctly auto-generates the complete mip set! Unfortunately, this causes a crash later when it tries to actually load the texture (since Torque does this after the D3D9Texture is created), attempting to load all of the miplevels that it now thinks exists in the file (because, of course, Torque never did set D3DUSAGE_AUTOGENMIPMAP).
Oh, what a tangled mess...
So now I'm trying to find other ways to go what I need. Oy!
04/30/2010 (11:30 am)
OK, more fuel for this fire. It turns out that Microsloth's documentation is BS when it comes to D3DUSAGE_AUTOGENMIPMAP. Some careful, controlled testing reveals that if you set the usage to D3DUSAGE_AUTOGENMIPMAP, it will NOT autogen the mipmaps, no matter what you do! However, if you do NOT set this flag, but set the number of mipLevels to 0 (zero), it WILL generate a complete mipmap set.So in my own hacking way, I went into one of the texture files of interest (.dds file), and set the flag that indicates that the mipcount in the file is to be used. This in an effort to trick Torque into setting the mipLevels to 0, BUT NOT setting the D3DUSAGE_AUTOGENMIPMAP usage flag. A breakpoint in gfxD3D9TextureManager.cpp at line 229, and then single stepping after the texture in question is created, show that D3D( now correctly auto-generates the complete mip set! Unfortunately, this causes a crash later when it tries to actually load the texture (since Torque does this after the D3D9Texture is created), attempting to load all of the miplevels that it now thinks exists in the file (because, of course, Torque never did set D3DUSAGE_AUTOGENMIPMAP).
Oh, what a tangled mess...
So now I'm trying to find other ways to go what I need. Oy!
#10
T3D has always supported and used autogeneration for single surface image formats like PNG. If not we wouldn't have any mipmapping on most the existing demos and user projects as they often do not use DDS.
Stepping thru in the debugger most images pass 0 numMipLevels to CreateTexture. Its just the non-power of 2 case, texture profiles that explicitly require no mips, or DDS files with mips that pass numMipLevels > 0.
04/30/2010 (11:48 am)
Yea... sounds like a mess, but i don't understand what your problem is now if your using PNG.T3D has always supported and used autogeneration for single surface image formats like PNG. If not we wouldn't have any mipmapping on most the existing demos and user projects as they often do not use DDS.
Stepping thru in the debugger most images pass 0 numMipLevels to CreateTexture. Its just the non-power of 2 case, texture profiles that explicitly require no mips, or DDS files with mips that pass numMipLevels > 0.
#11
So if we use power of 2 sized textures, it's all good.
= Ed =
04/30/2010 (1:48 pm)
OK, so using a .PNG file actually does work, as Tom pointed out to me. it's just that DirectX won't report it as being there. The only issue left is that the code still prohibits the Auto-generation of mipmaps if the texture is not a power of 2 size on both directions. This is sort of old code, and I tried commenting out the code that enforced this restriction. Unfortunately, this lead to a texture leak which I don't have time to find right now.So if we use power of 2 sized textures, it's all good.
= Ed =
#12
04/30/2010 (2:05 pm)
Support for mip maps for non-pow2 textures started with the GF6 cards, and I'm unsure if supporting it is mandatory for DX9.0c compliance. Even if it is, there is the issue of DX offering automatic mipmap generation for non-pow2 textures, since an Nvidia PDF on this makes it sound like a non-trivial process.
Torque 3D Owner Edward Rotberg
In order to fix the code at line 470 one would only need to change:
for(S32 i=1; i<mMipMapCount; i++)
to
for(S32 i=0; i<mMipMapCount; i++)
in order to make the reading process work.
Fixing the rest of the ddsloader.cpp to work correctly with files that have no mimpaps (i.e. just the main texture) is equally trivial, however I have no idea how many other places in the code incorrectly use this data.
Of course until this is fixed, there is no way to force auto-generation of mip levels. Even though the engines "sort of" supports it, there is no path that will allow that code to execute without manually editing the header file of a .dds file to set the mipmap flag in the header. Lord knows what havoc that would cause, but I'm probably going to find out :(