Reputation: 390
I'm currently trying to set texture data on a Texture2D object manually with SetData<> after loading it from file. The Texture2D has been created with MipMaps enabled. The data has been loaded from a .dds file saved with DXT1 compression, and is 512x512 in size.
This is the code responsible for creating the texture and loading the data into it.
texture = new Texture2D(graphics, (int)ddsHeader.Width, (int)ddsHeader.Height, mipMapCount > 1, sFormat);
for (int i = 0; i < (int)mipMapCount; i++)
{
int pow = (int)Math.Pow(2, i);
int width = (int)ddsHeader.Width / pow;
int height = (int)ddsHeader.Height / pow;
Rectangle? rect = null;
// get image size
int blockSize = GetBlockSize(width, height);
Console.WriteLine(string.Format("Width {0} Height {1}", width, height));
Console.WriteLine("Block size: " + blockSize + " " + (int)ddsHeader.PitchOrLinearSize);
// read the texture
byte[] textureData = reader.ReadBytes(blockSize);
rect = new Rectangle(0, 0, width, height);
// set the color into the appropriate level of the texture
if (sFormat == SurfaceFormat.Color && dxFormat == DXGIFormat.A8B8G8R8)
{
Color[] colors = ProcessUncompressedTexture(textureData, width, height);
texture.SetData<Color>(i, rect, colors, 0, width * height);
}
else
{
texture.SetData<byte>(i, rect, textureData, 0, blockSize);
}
}
It works fine, data is correctly loaded and set into each mip level correctly - until it reaches the 9th level, where it throws the following: ArgumentException was unhandled: The size of the data passed in is too large or too small for this resource.
This is the output.
Width 512 Height 512 // level 0
Block size: 262144 262144
Width 256 Height 256 // level 1
Block size: 65536 262144
Width 128 Height 128 // level 2
Block size: 16384 262144
Width 64 Height 64 // level 3
Block size: 4096 262144
Width 32 Height 32 // level 4
Block size: 1024 262144
Width 16 Height 16 // level 5
Block size: 256 262144
Width 8 Height 8 // level 6
Block size: 64 262144
Width 4 Height 4 // level 7
Block size: 16 262144
Width 2 Height 2 // level 8
Block size: 4 262144
Now, textureData has a count of 4. The width and height of this mip level is 2. The Rectangle is also 2 units in size. I can't see what is causing this error to occur, as the previous 8 levels get set fine.
Does anyone know what might be happening here to cause this?
Upvotes: 2
Views: 591
Reputation: 390
Ok, think I've found the answer, and it kind of makes sense.
This thread here seems to indicate that the smallest allowable size for a DXT compressed mipmap is a 4x4 block. This matches with what I'm seeing, as both DXT1 and 5 error with the above, but if I switch to say DXGIFormat.A8B8G8R8, it goes down to 1x1 mip-mapped sized blocks.
The reason for the minimum 4x4 block size is that the DXT compression algorithm compresses the block down to a minimum of 4x1.
In any case, I've modified my code to resize the block to read to be at least 4x4 if I'm using a DXT compressed texture, and it works.
For the sake of the next person (whoever they may be) who encounters the same problem, here is my updated mipmap set code:
// create the texture
texture = new Texture2D(graphics, (int)ddsHeader.Width, (int)ddsHeader.Height, mipMapCount > 1, sFormat);
Console.WriteLine(texture.LevelCount);
for (int i = 0; i < (int)mipMapCount; i++)
{
int pow = (int)Math.Pow(2, i);
int width = (int)ddsHeader.Width / pow;
int height = (int)ddsHeader.Height / pow;
Rectangle? rect = new Rectangle(0, 0, width, height);
if (dxFormat == DXGIFormat.DXT1 || dxFormat == DXGIFormat.DXT5)
{
if (width < 4 && height < 4)
{
width = 4;
height = 4;
}
}
// get image size
int blockSize = GetBlockSize(width, height);
Console.WriteLine(string.Format("Width {0} Height {1} // level {2}", width, height, i));
Console.WriteLine("Block size: " + blockSize + " " + (int)ddsHeader.PitchOrLinearSize);
// read the texture
byte[] textureData = reader.ReadBytes(blockSize);
Console.WriteLine("Data size: " + textureData.Length);
// set the color into the appropriate level of the texture
if (sFormat == SurfaceFormat.Color && dxFormat == DXGIFormat.A8B8G8R8)
{
Color[] colors = ProcessUncompressedTexture(textureData, width, height);
texture.SetData<Color>(i, rect, colors, 0, width * height);
}
else
{
texture.SetData<byte>(i, rect, textureData, 0, blockSize);
}
}
I moved the Rectangle? rect = ...
call up, and placed the DXT less than 4x4 block detection below it. Its not the tidiest, but it works.
Upvotes: 1
Reputation: 3185
You may want to have a look at the format type after image compression (full explanation of the problem an answer can be found here):
Here are the possible format types.
You are going to have to check texture.Format
and use the correct datastructure for its SurfaceFormat
.
For example.
var b = new Bgr565[result.Width * result.Height];
tex.SetData(b);
The below SurfaceFormat
have a corresponding value types that can be used.
Color
Bgr565
Bgra5551
Bgra4444
NormalizedByte2
NormalizedByte4
Rgba1010102
Rg32
Rgba64
Alpha8
Single
Vector2
Vector4
HalfSingle
HalfVector2
HalfVector4
The Dxt
format means that the texture is compressed and you are going to need to know the size after the it gets compressed, get the data and then decompress it.
You may be able to find a DXT1 and DXT5 decompression library somewhere. Unfortunately I can't find anything managed so unsafe C# code is probably the best bet for converting it over. According to Wikipedia, 16 pixels are stored in 8 bytes which makes have a byte per pixel so theoretically byte[] data = new byte[(Width * Height) / 2]
should work for extracting the data.
Dxt1
Dxt3
Dxt5
This one is a special case just use HalfVector4
for the type and you are fine.
HdrBlendable
Upvotes: 0