Texture Copy Improvements in C++ AMP with Visual Studio 2013

C++ AMP in Visual Studio 2013 introduces a number of improvements to texture support. In this blog post, I will cover the improvements we have made over texture copy support in Visual Studio 2012.

Texture copy support in C++ AMP was missing a few key features in Visual Studio 2012 compared to the copy support for array and array_view. We provided:

  • Copy between textures and raw pointers to memory buffers
  • Copy between two textures via the copy_to member function of the texture types
  • Copy from iterators into a texture (but not out of, and only at texture construction time)

But there were some gaps in the feature set compared to arrays:

  • No copy_async for texture-to-texture copy (i.e. no copy_to_async member function)
  • No copy between textures and arbitrary C++ iterators
  • No mechanism to copy only a portion of a texture

Each of these issues is addressed in Visual Studio 2013. These additions to texture copy are all made in the namespace scope copy functions in concurrency::graphics. The namespace scope copy functions are the preferred copy functions we encourage you to use over the copy_to member functions of texture<T> and writeonly_texture_view<T> . Not to worry – those member functions still exist and still continue to work as they did before. Moving forward the namespace scope copy functions are where most of the new functionality will be implemented and where copy support will be implemented for new data types such as the new texture_view<T> and texture_view<const T> types.

Our goal in this feature was to simplify and unify the copy APIs for C++ AMP and ensure that the full matrix of copy features are supported by the C++ AMP APIs rather than certain features, such as asynchronous copy, only being supported with certain data types or scenarios.

Copy between textures

The first and simplest change is that copying between two textures (or texture_views) can now be done with the namespace-scope copy functions instead of the member copy_to, just like arrays:

texture<float, 2> srcTex(64, 64), dstTex(64, 64);
...
copy(srcTex, dstTex); // Copy the contents of srcTex to dstTex. Can also use copy_async.

In these copy functions, and all other functions listed in this post, the texture parameter types are template parameters. You can pass any kind of texture or texture_view to these functions, with the caveat that a texture_view<const T> will static_assert if used as a copy destination (and yes, even the deprecated writeonly_texture_view works with all the new copy functionality, although we strongly encourage you to migrate to texture_view).

Iterator-based Copy

Another change we made to unify the copy functionality we provide is the introduction of iterator-based copy functions for textures, similar to the iterator-based copy functions for array<T> and array_view<T> :

template <typename InputIterator, typename _Dst_type>

void copy(InputIterator _First, InputIterator _Last, _Dst_type &_Dst);

template <typename _Src_type, typename OutputIterator>

void copy(const _Src_type &_Src, OutputIterator _Dst);

These functions copy to or from any iterator-like type and a texture. Equivalent asynchronous versions are also provided as copy_async overloads. You can use the standard C++ iterators with these functions, or provide your own. For example, you can copy data out of a texture and into a std::vector with the following:

void foo(const texture<int, 1> &tex)
{
    std::vector<int> v(tex.extent.size());
    copy(tex, v.begin());
    ...
}

These iterator-base functions have the same limitation as the iterator-based texture constructors: they work only with textures with default bits_per_scalar_element values. Attempting to copy to or from a texture with a non-default bits_per_scalar_element with an iterator will result in a runtime_exception. The reason for this limitation is the same as why there is no bits_per_scalar_element parameter in the iterator-based texture constructor: it is a runtime value but the copy function templates need to know the size of the data type to copy at compile-time.

A small gotcha

One issue to watch out for when copying from iterators to textures is the following:

std::vector<int> v(tex.extent.size());
...
copy(v.begin(),v.end(), tex); // Copy the vector contents to the texture

The above code may fail to compile with an ambiguous overload error. This can happen when std::copy(InIt, InIt, OutIt) is also found by the compiler through argument-dependent lookup, even if there is no "using namespace std" in your program. Clearly the std::copy version wouldn't work because a texture is not an output iterator, but until C++ gets concepts, std::copy has no standard way to restrict the types it will accept to only valid iterators. The solution here is to explicitly qualify the copy function:

std::vector<int> v(tex.extent.size());
...
concurrency::graphics::copy(v.begin(),v.end(), tex); // Unambiguous

We put a little template magic into these new copy overloads to ensure they will not be considered candidates unless the last parameter really is a texture type, so any code you have that really wanted std::copy won't trigger ambiguity errors because of the declarations in concurrency::graphics.

Section Copy

Finally, we introduced the ability to copy only a portion of a texture through the section copy APIs. Section copy can be thought of as describing a smaller texture of the same rank within the larger texture. Only data within that smaller texture will be copied, so you can save a lot of data transfer time with the section copy overloads if only a small part of a large texture needs to be transferred.

Section copy is provided for all of the texture copy functions: to and from raw pointers, iterators, and between two textures or texture_views, for both synchronous and asynchronous copy. All section copy overloads take an index (or two indices, for texture-to-texture copy) and an extent in addition to the normal copy parameters. These extra parameters specify a 1-, 2-, or 3-dimensional bounding box for the data to copy, depending on the texture rank.

Section copy looks like the following:

copy(srcTex, index<2>(48, 48), dstTex, index<2>(16, 112), extent<2>(16,16));

copy(pBuf, bufSize, dstTex, index<2>(16, 32), extent<2>(32,32));

The first statement copies a 16x16 texel section of srcTex starting at offset (48,48) to dstTex starting at (16, 112). The second copies data pointed to by pBuf into the texture dstTex starting at offset (16, 32) and extending for 32 texels in each dimension, i.e. extending until (48, 64). Section data is copied from a flat buffer or iterator linearly: if the texture and buffer were of type float, the value at pBuf[15] is placed at position (16, 63) in the texture and pBuf[16] at position (17, 32).

For a successful section copy, the following rules must be followed:

  • The bounding box defined by the section offset and extent must be entirely within the source and/or destination texture.
  • The copy extent must be positive in all dimensions.
  • In the case of texture-to-texture copy the ranks of the textures must be identical.
  • The source and destination textures must be distinct textures, views of distinct textures, or views of distinct mip-levels in the same texture.

Conclusion

In this post we looked at some of the new features in Visual Studio 2013 for copying textures. This is only one area of improvement we've made to textures in C++ AMP, so stay tuned for more blog posts on texture support.