Converting textures to .dds
As I wrote in my previous post, Windows Phone 8 does not support WIC components, which means that unless you want to write a .png loader manually, you need to have your textures in .dds format. Which is actually quite a good thing, since .dds supports hardware texture compression, if you need to save on memory. The only issue is that .dds is not a common format, and most image processing software don’t support it out of the box.
So let’s take a look at various ways we can convert images from common formats (.bmp, .png, .jpg) to .dds, starting with manual conversion, and ending with an msbuild script to do the work for us.
As the name says, this will require you to convert each image manually, which is quite a pain if you have a large number of images, or if you want to be able to make changes to an image and quickly test them. Here are some options for manual conversion:
- NVIDIA Texture Tools for Adobe Photoshop – If you’re lucky enough to have Photoshop at your disposal, the NVIDIA Texture Tools is a plugin that allows you to export images to .dds format, with lots of options including format, DXT compression, mipmaps, etc.
- Visual Studio 2012 – The latest version of Visual Studio has a basic image editor built in, and it supports saving images in the .dds format. Just click File->Save as…, and you’ll be able to select .dds from the list
The disadvantages of a manual conversion process should be quite obvious, so let’s find a better solution.
Custom Build tool using texconv.exe
The idea is to add images to the WP8 project, and have them converted to .dds at build time. To do this, we’ll need a command line tool that can convert textures to .dds. Fortunately, Chuck Walbourn has our back again, and these are several places we can get such a tool from (though it’s actually the same tool).
The safest place to get the tool from is the DirectXTex project on codeplex. Just download the library and unzip it somewhere, go inside the Texconv folder, and open and build the solution. The output will be texconv.exe, which we’ll see how to use shortly. The project also contains a few other useful tools.
Now, in case something happens to the internet and Codeplex is not available, you can get the source for texconv.exe from the Samples site of the Windows Dev Center:
- DirectX 11 texture converter tool sample
- The Direct3D Resource Loading sample from the big pack of Windows 8 app samples
Either way you go, you should end up with a command line named texconv.exe (or similar), that will solve the job. The usage parameters are the same as they were in the old version of texconv.exe, which was included in the now obsolete DirectX SDK. You can see the full list of options here.
The simplest usage would simply take an image as a parameter, and spit out that image in .dds format, with all mipmap levels generated.
However, Windows Phone 8 has a Feature Level 9_3 graphics device, so if you plan to use non power-of-two textures, you’ll need to generate the image with a single mip level.
texconv.exe -m 1 input-file-name
There are other factors to take into consideration here. If you’ll want compressed textures, you’ll need to use power-of-two textures anyway, and if you’ll plan to use textures on 3D models, you’ll most likely want to have all mipmap levels generated, in which case you’ll again need power-of-two textures. But for the rest of this post I’ll go on the assumption that you want to use the textures for 2D, don’t want to use spritesheets, and don’t want to be restricted for power-of-two texture sizes.
Now that we have the proper tool, let’s hook it up to convert our textures at build time. For simplicity, let’s start with the code we had at the end of the Getting Started post: BasicDirect3DApp.zip, which simply loads a texture and draws it on the screen.
Go ahead and remove the .dds file from the project, and instead add the original CatTexture.png
By default Visual Studio adds it as a Bitmap, and treats it as Content (it is copied to the Output directory). We can easily configure Visual Studio to use texconv.exe as a custom build tool for this file. While selecting the CatTexture.png file in Solution Explorer, open the Property Pages (Shift+F4, or right-click -> Properties).
In this window, set the Content property to No, to let VS know that we don’t want it to copy this texture to the output dir, and set the Item Type to Custom Build Tool. After clicking Apply, we can now set the Custom Build Tool properties, as seen below.
The properties that interest us are:
- Command Line – the command line that should be invoked. Use the relative path to texconv.exe (I placed it in a folder named tools inside the solution’s directory. We pass %(Identity) to the tool, which points to the input file (CatTexture.png). If you want a cleaner output form build, you can use the -nologo option.
- Description – This will be shown in the Build Output window.
- Outputs – This specifies the output generated by the custom tool. In our case, this is a .dds image with the same name as the input file.
- Treat Output as Content – When set to Yes, this informs Visual Studio that is should include the generated outputs (specifies in the Outputs fiels) as content that should be included in the output (the .xap archive, in case of Windows Phone 8)
The advantage of this method is that the source image is kept in it’s original format, which is easier to edit and manipulate, and whenever you change the contents of the original image, it will be converted to .dds at build time. In the command line field, you can also use other options for textconv to ensure the resulting .dds image is in the format you want.
The disadvantage is that you need to do this configuration for all images individually, which can get tiresome after a while. Also, in it’s current forms, and output files are placed in the root directory of the output, without preserving any folder hierarchy, like we would probably like.
You can get the source code for a small sample that uses this method here: TextureConversion_CustomBuild.zip
Using a custom msbuild project
According to MSDN, “MSBuild is the build platform for Microsoft and Visual Studio”; and what the previous method actually did was to extend the msbuild script of the project to execute the custom build tool on each individual image, allowing us to do this using the Property Pages. But MSBuild allows us to extend the build process in other ways, one of which we’ll discuss next. We’ll write a script that will allow us to convert all image files inside a folder to .dds file, and keep the folder hierarchy the same in the output folder. This will be just a walkthrough to get what we need, because MSBuild is quite a complex system, and I don’t have enough experience for a more extensive tutorial.
1. The .targets file
We’ll write the MSBuild project in a .targets file, which you can then easily reuse in other projects. Open any text editor (preferable one that knows how to highlight XML syntax, I use Notepad++ and Visual Studio itself), and paste the following.
<?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!--Rest of script goes here--> </Project>
This simply contains the root Project element that will contain the rest of our script. The first this we’ll need to do is to gather references to all images that we want to convert. Now, we don’t want to simply select all images in the project because we might have certain images that we want unchanged in the final package, like the logos. So in spirit of XNA, let’s take all .png, .bmp or .jpg images inside the Content folder, and declare them as part of an ItemGroup.
<!--Gather all image files under the Content dir, with .png, .jpg or .bmp extension--> <ItemGroup> <DDSConvertFiles Include="Content\**\*.png;Content\**\*.jpg;Content\**\*.bmp;" /> </ItemGroup>
An ItemGroup is a collection of elements that can be used together. In our case, we will use them as inputs for the conversion script. We named the group DDSConvertFiles, and included in the group the files we needed. By writing Content\**\*.png we specified we want all files with the .png extension under the Content directory and its subdirectories. If you wanted to, you could include other image from other directories here.
Next we’ll convert the images using texconv.exe, and place them in the output directory. Since WP8 apps are packaged in a XAP file before being deployed, we’ll need to make sure the converted images are included in the XAP file.The next piece of script does the work for us.
<!--Target the converts input files to .dds files--> <Target Name="SimpleDDSConvert" Inputs="@(DDSConvertFiles)" Outputs="@(DDSConvertFiles->'$(OutDir)%(RelativeDir)%(Filename).DDS')"> <!--Write what we're doing to the console--> <Message Text="Converting %(DDSConvertFiles.Identity) to .dds" Importance="high" /> <!--Create directory structure--> <MakeDir Directories="$(OutDir)%(DDSConvertFiles.RelativeDir)" /> <!--Call texconv.exe--> <Exec Command="$(MSBuildThisFileDirectory)texconv.exe -nologo -o $(OutDir)%(RelativeDir) -ft dds -m 1 %(DDSConvertFiles.Identity)"/> </Target>
Let’s go through what each line does:
The Target element contains a set of tasks that MSBuild will execute sequentially. I named it SimpleDDSConvert. The inputs for these tasks will be the ItemGroup we defined eariler, and we also specify what output it should expect. Specifying the Inputs and Outputs attributes is not mandatory, but it helps MSBuild do incremental builds, so it won’t execute if the Inputs have not changed since the last time the Outputs were generated.
The expression @(DDSConvertFiles->’$(OutDir)%(RelativeDir)%(Filename).DDS’) generates the path to each output item, composed of $(OutDir) (The output directory, specified in the game’s project file), followed by the relative path and filename of the input file, ending with the .DDS extension. So for example, if one of the inputs is located in \Content\Obstacles\MeatChopper.png, MSBuild will check if the output directory contains the \Content\Obstacles\MeatChopper.DDS file. You can find more examples of transforms here.
Inside the Target element, we first added a Message task, which will print a message to the Output window for each input file. If you want a less verbose Build process, you can remove this line.
Before we convert the texture, we’ll recreate the directory hierarchy in the output dir, using the MakeDir task. For each of the input files, it will generate a directory with the same relative path.
We then use the Exec command to invoke the texconv.exe tool and convert the input files to .dds files:
- The above code assumes that texconv.exe resides in the same directory as the .targets file
- We use the -nologo option to keep the output windows cleaner
- The -o option allows us to specify where the tool should place the resulting .DDS files. We want to place them in a hierarchy similar to how they are stored in the project’s directory, so we’ll use $(OutDir)%(RelativeDir)
- The -m 1 option asks the converted to only generate one mipmap level, for the same reasons we discussed earlier (WP8 is restricted to Feature Level 9_3, which doesn’t allow mipmaps for non-power-of-two textures)
- Lastly, we specify the input as each file in the ItemGroup. We use the %Identity transform for this, similar to how we did in the Custom Build Tool
If we leave it like this, the files will be generated, but they will not be included in the XAP file, so we need to inform the build process that we want to do that. We can do that by using another Target that uses the CreateItem task, taking the output files, adding proper metadata to them (which tells msbuild where these belong inside the XAP), and then including them in the XapFilesInputCollection ItemGroup, which is used by msbuild to keep track of all ‘xappable’ files.
<Target Name="AddDDSToXap"> <CreateItem Include="@(DDSConvertFiles->'$(OutDir)%(RelativeDir)%(Filename).DDS')" AdditionalMetadata="TargetPath=%(DDSConvertFiles.RelativeDir)%(Filename).DDS"> <Output ItemName="XapFilesInputCollection" TaskParameter="Include" /> </CreateItem> </Target>
Lastly, we must decide when these script should be invoked. Since they generate files that should be placed inside the XAP file, we will extend the FilesToXap target to depend on our own SimpleDDSConvert and AddDDSToXap targets.
<!--Make sure SimpleDDSConvert and AddDDStoXap is run before Xapping files--> <PropertyGroup> <FilesToXapDependsOn> SimpleDDSConvert; AddDDSToXap; $(FilesToXapDependsOn); </FilesToXapDependsOn> </PropertyGroup>
The final script should look like:
<?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!--Gather all image files under the Content dir, with .png, .jpg or .bmp extension--> <ItemGroup> <DDSConvertFiles Include="Content\**\*.png;Content\**\*.jpg;Content\**\*.bmp;" /> </ItemGroup> <!--Make sure SimpleDDSConvert and AddDDStoXap is run before Xapping files--> <PropertyGroup> <FilesToXapDependsOn> SimpleDDSConvert; AddDDSToXap; $(FilesToXapDependsOn); </FilesToXapDependsOn> </PropertyGroup> <!--Target the converts input files to .dds files--> <Target Name="SimpleDDSConvert" Inputs="@(DDSConvertFiles)" Outputs="@(DDSConvertFiles->'$(OutDir)%(RelativeDir)%(Filename).DDS')"> <!--Write what we're doing to the console--> <Message Text="Converting %(DDSConvertFiles.Identity) to .dds" Importance="high" /> <!--Create directory structure--> <MakeDir Directories="$(OutDir)%(DDSConvertFiles.RelativeDir)" /> <!--Call texconv.exe--> <Exec Command="$(MSBuildThisFileDirectory)texconv.exe -nologo -o $(OutDir)%(RelativeDir) -ft dds -m 1 %(DDSConvertFiles.Identity)"/> </Target> <Target Name="AddDDSToXap"> <CreateItem Include="@(DDSConvertFiles->'$(OutDir)%(RelativeDir)%(Filename).DDS')" AdditionalMetadata="TargetPath=%(DDSConvertFiles.RelativeDir)%(Filename).DDS"> <Output ItemName="XapFilesInputCollection" TaskParameter="Include" /> </CreateItem> </Target> </Project>
2. Adding the script to the project
Now that the MSBuild script is written, we need to tell our Windows Phone project to actually use it. Open your project in Visual Studio, right click on it, select Build Customization…, and click Find Existing…
Browse to the .targets file and chose it. It will ask you if you want to add the path to the Build Customization Search Paths; do this if you want to use the script in other projects tool. In the end, check the checkbox next to the SimpleDDSConvert and click ok. Add some image files inside the Content folder in your game project, and they will now automatically be converted when Building the project, and will be deployed inside the .XAP file.
To load and use them in the game, use the DDSTextureLoader, as seen in the Getting Started guide.
CreateDDSTextureFromFile(m_d3dDevice.Get(), L"Content\\Chickens\\ninja.dds", nullptr, &texture)
Code and resources
The MSBuild script can be extended further, and you can even register it as a File Type to be recognized by Visual Studio and show up in the Property Pages, like the HLSL Compiler does. However, this is quite a bit harder to do. I’m still working on a more advanced version, and I’ll likely revisit this subject in the future.
The above script is now useable without requiring any more MSBuild knowledge, but in case you want to learn more and expand it’s use, here are some resources related to MSBuild that you might find useful:
- MSBuild Reference on MSDN
- Walkthrough: using MSBuild
- Creating Reliable Builds, Part 1 and Part 2
- Sayed Ibrahim Hashimi’s Blog
- Inside the Microsoft® Build Engine
To see a sample using the above script to convert images in different formats and various directories at build time, check out the code: TextureConversion_MSBuild.zip
Special thanks to Mike for reviewing my code, and making sure I don’t spread bad practices around
Until next time, Have fun coding!