Texture with Mipmaps
In the v2 release of C++ AMP through Visual Studio 2013, one of our main efforts were focused around extending the features of textures by supporting Texture Mipmaps.
Introduction
Mipmaps are the sequence of images that accompany the main texture image. Each dimension in subsequent image is half of the previous one. For example:
64x64 -> 32x32 -> 16x16 -> 8x8 -> 4x4 -> 2x2 -> 1x1
A texture with mipmaps occupies 33% more space than a texture without mipmaps. The primary usage of mipmaps is to speed up processing time when a full texture image is not needed.
Creation of Texture with Mipmaps in C++ AMP
A texture with mipmaps can be adopted from DirectX or it can be created directly in C++ AMP.
Adopting DirectX Texture with Mipmaps
A DirectX texture with mipmaps can be adopted in C++ AMP with concurrency::graphics::direct3d::make_texture , just like the texture adoption done in the v1 release.
Let’s slightly modify the code snippet given in ‘Interop Example aka Show me the Code’ section of our earlier blog post about texture interop apis , to show creation of texture with mipmaps in Directx and then its adoption into amp code.
// Modifying the ‘MipLevels’ property of D3D11_TEXTURE2D_DESC object
// to indicate that D3DTexture object should be created with 3 mip levels 10x10,
// 5x5,2x2
desc.MipLevels = 3;
.........
.........
.........
// Now adopting the texture
texture<uint4, 2> adoptedTexture = make_texture<uint4, 2>(acc_view,dx_texture);
// Printing that the number of miplevels are 3.
std::cout << adoptedTexture.mipmap_levels << std::endl;
So, the texture<uint4, 2> created via make_texture call, holds mipmaps already available in the Direct3D texture object, i.e. 10x10, 5x5, 2x2.
Creation of Texture with Mipmaps directly
More new constructors are added to concurrency::graphics::texturewhich gives the ability to create a texture with all possible mipmaps or a subset of mipmaps.
texture(const extent<N>& _Ext, unsigned int _Bits_per_scalar_element, unsigned int _Mipmap_levels);
texture(const extent<N>& _Ext, unsigned int _Bits_per_scalar_element, unsigned int _Mipmap_levels, const accelerator_view& _Acc_view)
- Value 0 for ‘_Mipmap_levels’, will cause constructor to generate full set of uninitialized mipmaps.
- Value greater than 0 for ‘_Mipmap_levels’, will generate specific amount of mimaps.
- In all other constructors that do not explicitly specify Mipmap levels, the texture construction will be similar to the construction of textures with value 1 for ‘_Mipmap_levels’
Let’s see few examples of constructions below :
// Texture object creation without specifying mipmaps.
texture<int, 2> tex1(extent<2>(8,4)); // By default it contains only 1 mipmap .ie., 0th mip 8x4 - The most detailed one
// Texture object creation with all possible mipmaps
texture<int, 2> tex2(extent<2>(8, 4), 32U, 0); // Creates texture with 4 mipmaps - 0th mip : 8x4 - The most detailed mip ,1st mip : 4x2, 2nd mip 2x1, 3rd mip 1x1
// Texture object creation with two mipmaps.
texture<int, 2> tex3(extent<2>(7, 3), 32U, 2); // Created texture with 2 mipmaps - 0th mip 7x3, 1st mip 3x1
Changes in the properties of Textures
The support for texture with Mipmaps affected the common properties of C++ Texture objects. A new property is added and the definition of one of the existing properties got changed. The below table summarizes the changes in the properties from v1.
Property Name | Changes in V2 |
bits_per_scalar_element | - |
extent | - |
data_length | If the texture/texture_view object contains Mipmaps, then the data_length is sum of data lengths at each mipmap level |
mipmap_levels | This is the new property. It gives the number of Mipmap levels available in the texture/texture_view object. For readonly texture views , this property can be queried even in AMP Kernel functions. |
Writing to Texture Mipmap
Just like writing data to texture object using texture_view<T,R>, we also need to create a texture_view object to write data to a texture mipmap. The writable texture view constructed for texture object having a mipmap, represents only one mipmap level. So, we have to explicitly specify the mipmap level on which the writable view needs to be constructed.
texture_view(texture<T,N>& _Src, unsigned int _Mipmap_level = 0) restrict(cpu);
Please note that the labels/names/indexing of mipmap levels start from 0. ‘0 mipmap level’ indicates the most detailed level.
Let’s see an example to how to write to a particular mipmap :
texture<int, 2> tex(extent<2>(16, 16), 16U, 3); // Creating texture with 3 mipmaps : 16x16, 8x8,4x4
texture_view<int, 2> w_view(tex, 2); // Creating writable view to the last mipmap :4x4
parallel_for_each(w_view.extent, [=](index<2> idx) restrict(amp)
{
w_view.set(idx, 123);
});
texture_view<int, 2> w_view1(tex); // This is identical to texture_view<int, 2> w_view1(tex,0);
Note that the extent<N> of the texture_view object may not be equal to the extent of the source texture object. It changes accordingly to the shape of the mipmap on which the view is created.
Reading data from Texture Mipmap
To read data from texture mipmaps, we also need to create ready only texture views. Unlike the writable texture views, which allows to create view only one particular mipmap level, the read only texture views can be created for a particular range of mipmap levels.
texture_view(const texture<T,N>& _Src, unsigned int _Most_detailed_mip, unsigned int _Mip_levels) restrict(cpu);
texture_view(const texture_view<const T,N>& _Other, unsigned int _Most_detailed_mip, unsigned int _Mip_levels) restrict(cpu);
Note that new read only views can be only created in CPU context only. In GPU context, it can only be copy constructed.
In addition to the new constructors, a new member function is added to read only texture views which help reading from the selected mipmap levels.
const value_type get(const index<N>& _Index, unsigned int _Mip_level = 0) const restrict(amp);
Let’s see an example of reading data from Mipmaps :
texture<int, 2> tex(16, 16, 32U, 0); // Creating texture with 5 mip maps : 16x16, 8x8, 4x4, 2x2, 1x1
texture_view<const int, 2> r_view(tex, 1, 3); // Creating readonly texture views that starts at 1st mip to cover upto 3 miplevels. The view is created to read data from mips 8x8, 4x4, 2x2
texture_view<const int, 2> sub_r_view(r_view, 0, 2); // Creates sub_r_view that helps read data from 8x8,4x4
parallel_for_each(extent<2>(4, 4), [=](index<2> idx) restrict(amp)
{
int sum = 0;
for (unsigned int i = 0; i < sub_r_view.mipmap_levels; i++)
{
sum += sub_r_view.get(idx, i);
}
....
....
});
Sampling with Mipmaps
Our earlier blog posts about Sampling already gave the full details on how to do Sampling using Texture objects. Introduction of Texture Mipmaps extended the samping functionality to Mipmaps. The sample member function on texture<const T, N> will take additional argument which defines mipmap level on which to sample (also called level of detail).
const value_type sample(const sampler& _Sampler, const coordinates_type& _Coord, float _Level_of_detail = 0.0f) const restrict(amp);
template<filter_mode _Filter_mode = filter_linear, address_mode _Address_mode = address_clamp>
const value_type sample(const coordinates_type& _Coord, float _Level_of_detail = 0.0f) const restrict(amp);
The fractional values of _Level_of_detail are used to interpolate between two mipmap levels. The values <=0 will use the largest (zero’th) mipmap. Similarly the values greater than number of available mip levels will use the smallest (last) mipmap. For fractional values of _Level_of_detail, the value is interpolated using the values between the two mipmap levels based on the sampler settings.
Let’s see an example of how to sample using data from Mipmaps:
texture<unorm_4, 2> tex = createTextureMipmapFromD3DTexture(); // Assume createTextureMipmapfromD3DTexture is any function that returns texture having mipmap. For example, let’s say it returns texture with 4 mipmaps
texture_view<const unorm_4, 2> r_texView(tex, 0, tex.mipmap_levels); // Creating reading only texture view to all mipmap levels
float_4 bcolor(0.0f, 0.0f, 0.0f, 0.0f);
sampler s1(filter_linear, address_border, bcolor); // User defined sampler
sampler s2(filter_point, address_mirror, bcolor); // User defined sampler
parallel_for_each(r_texView.extent, [=](index<2> idx) restrict(amp)
{
float_2 sampleIdx;
sampleIdx.x = 0.5f;
sampleIdx.y = 0.5f;
unorm_4 f4, f5;
f4 = r_texView.sample(sampleIdx, 1.0f); // Sampling using the data at mipmap level 1 using the second sampler function
f5 = r_texView.sample<filter_point>(sampleIdx, 0.4f); // Sampling using the data from mipmap levels 0 and 1 using the second sampler function
f4 = r_texView.sample(s1, sampleIdx, 2.0f); // Sampling using the data at mipmap level 2 , using user defined sampler.
f5 = r_texView.sample(s2, sampleIdx, 2.7f); // Sampling using the data from mipmap levels 2 and 3 using user defined sampler.
});
In Closing
In this post, we learnt in detail about textures with Mipmaps. Stay tuned to know what C++ AMP offers you in Visual Studio 2013. As usual, I would love to read your comments below or in our MSDN forum.
Comments
- Anonymous
November 29, 2013
I would love to see a complete pure C++ AMP code sample that loads "lena.bmp" and samples from it (with interpolation). There seems to be a huge number of examples that fail to describe how to do this, and even more web pages that describe theory without code.