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

texture_image
 

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>&amp; _Ext, unsigned int _Bits_per_scalar_element, unsigned int _Mipmap_levels);
  
 texture(const extent<N>&amp; _Ext, unsigned int _Bits_per_scalar_element, unsigned int _Mipmap_levels, const accelerator_view&amp; _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>&amp; _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>&amp; _Src, unsigned int _Most_detailed_mip, unsigned int _Mip_levels) restrict(cpu);
  
 texture_view(const texture_view<const T,N>&amp; _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>&amp; _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&amp; _Sampler, const coordinates_type&amp; _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&amp; _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.