Textures
Textures store the image data we need to display on the screen. They represent a 2D array of pixels in memory that can be used for many different purposes, as you will see in this book.
Note
The pixels inside a texture are referred to as texels. We use this distinction because through filtering, scaling, and other transformations, we may lose the 1:1 mapping between a texel in the image and a pixel in the output frame buffer.
Textures also take up much of the memory available to a game, making it important that you apply techniques to reduce their size as much as possible when working on memory limited devices such as tablets or phones. Textures are generally stored on the GPU in special memory designed for quick access; however, on some platforms that memory is shared with the system memory, reducing the amount of space you have even further.
Note
GPU refers to Graphics Processing Unit. This is a piece of acceleration hardware that is designed to handle the mathematics required to create our game images at the rates required for an interactive game. The focus on rendering allows the hardware to be specialised and perform better than a CPU for rendering tasks.
Textures are static; however, by using a texture as a sprite sheet you can add animation to your game and reduce the number of files you need to load. Textures themselves do not define anything but the format of the pixel in memory, the width, and the height, leaving the interpretation and use up to you. We won't go into detail about these techniques, but there is plenty of information available online if you want to add animations to your game.
Now let's look at the different formats that we can use, and load them in ready for rendering later on.
File formats
There are many different image file formats; however, we'll focus on two major formats in use today:
- PNG
- DXT (Uses the
.dds
extension)
PNG is a well-known format, widely used online for images. It can be read by most editing programs and has plenty of ways to load and decode the image from the file. It supports features such as transparency and provides reasonable lossless compression, making it an excellent choice for storing and editing images in lieu of a format such as Photoshop Document (PSD).
DXT on the other hand was created for rendering 3D textured images, and achieved popularity when included in both Direct3D and OpenGL. As a result DXT has many benefits that other image formats lack when used in a game. The first and foremost benefit is the native support on GPUs for decoding and decompressing the format. Instead of having to load in and use the CPU to do these operations, you just need to read the file in and send it to the GPU, which can handle the rest. This greatly simplifies the content load process, and improves efficiency by reducing the amount of memory and processing time needed.
Note
A DXT texture converter, texconv.exe
, is included with the source code for this chapter.
Loading
First we need to ensure the images are packaged with the game when we create our builds for testing and for the store. To do this, we need to add the image to the project, and ensure that the Content property is set to true. This is done through the Properties window in Visual Studio. The simplest way to open it if it is not already open is to select the texture file in your solution explorer and press F4. Inside the properties inspector (with the file selected) you will see a Content property, which you can change to true. Once that is done, the images will be included, and will be ready for use.
We want to load two different textures onto the screen for this exercise:
player.dds
enemy.dds
To begin, we'll need to create something to hold and manage these images, so create a Texture
class. In the sample, I've created a Texture
class using the new C++/CX ref class system so that I can get the benefit of automatic reference counting and resource management. You can use a standard C++ class here, but you need to ensure that you manage the allocation and cleanup, either manually or using smart pointers.
Inside Texture.h
, place the following code:
#pragma once #include <SpriteBatch.h> ref class Texture sealed { private: Platform::String^ _path; Microsoft::WRL::ComPtr<ID3D11Texture2D> _tex; Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> _srv; DirectX::XMFLOAT2 _pos; internal: void Load(Microsoft::WRL::ComPtr<ID3D11Device> device); public: property float X { float get() { return _pos.x; } void set(float val) { _pos.x = val; } } property float Y { float get() { return _pos.y; } void set(float val) { _pos.y = val; } } Texture(Platform::String^ path); };
Here we're defining a simple Texture
class that has properties for the co-ordinates of the texture and a way to load the texture. We need to use internal visibility because DirectXTK is implemented as a set of standard C++ classes, and therefore can't be exposed to WinRT. By using the internal modifier we can limit access to the Load
method from code within the same assembly, allowing us to use standard pointers in a WinRT (ref) class, which does not allow standard C++ pointers to be exposed publicly.
Inside Texture.cpp
, we want to define a constructor that sets the _path
field and then define the Load
method, as follows:
void Texture::Load(Microsoft::WRL::ComPtr<ID3D11Device> device) { DirectX::CreateDDSTextureFromFile( device.Get(), _path->Data(), &_tex, &_srv ); }
Make sure you include the header file DDSTextureLoader.h
from DirectXTK for this to work.
In the preceding code, we're simply calling a nice and handy helper function that is designed to load the .dds
file and create the required D3D11 resources, ready for use. We let that method directly set the ID3D11Resource
and ID3D11ShaderResourceView
in our class.
Now we need to load in the textures that we added to the project earlier. We're going to be managing the game inside our Game
class (the one that implements Direct3DBase
). For now we'll start by adding in the two textures, and loading them up. Later on we'll create a manager to handle these textures and clean up the code.
Start by including Texture.h
in your Game
header, and add the following fields to that class:
Texture^ _player; Texture^ _enemy;
Now we need to create a place to load this content. Let's add a LoadContent
method to our Game
class, and then let's call Initialize
in GameApplication
and invoke LoadContent
.
Now create the following in our new LoadContent
method:
void Game::LoadContent() { _player = ref new Texture("textures\\player.dds"); _enemy = ref new Texture("textures\\enemy.dds"); _player->X = 50; _player->Y = 50; _enemy->X = 300; _enemy->Y = 300; _player->Load(m_d3dDevice); _enemy->Load(m_d3dDevice); }
This is all pretty straightforward. We create the textures, and place the player and enemy in different positions so they have some clear space to draw, and then we tell the game to actually proceed and load the textures.
Feel free to test the project. You won't see anything on screen but if the game loads and starts clearing the screen like it did in the previous chapter then everything should be fine so far.