Updated Sep 26, 2021

DirectX 11 - Textures (C/C++ & Win32)

How to load an image and map it on a surface.

main.cpp

#define WIN32_LEAN_AND_MEAN
#define UNICODE
#include <windows.h>
#include <assert.h>
#include <d3d11_1.h>
#include <d3dcompiler.h>
#include <float.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

LRESULT CALLBACK 
window_proc(HWND window, 
            UINT message, 
            WPARAM w_param, 
            LPARAM l_param)
{
    switch(message)
    {
        case WM_KEYDOWN:
        {
            switch(w_param)
            {
                case 'O':
                {
                    DestroyWindow(window);
                };
            }
        } break;
        
        case WM_DESTROY:
        {
            PostQuitMessage(0);
        } break;
        
        default:
        {
            return DefWindowProc(window, 
                                 message, 
                                 w_param, 
                                 l_param);
        }
    }
    
    return 0;
}

int WINAPI 
wWinMain(HINSTANCE instance, 
         HINSTANCE prev_instance, 
         PWSTR cmd_line, 
         int cmd_show)
{
    // window creation
    
    WNDCLASS window_class = {};
    
    const wchar_t class_name[] = L"MyWindowClass";
    
    window_class.lpfnWndProc = window_proc;
    window_class.hInstance = instance;
    window_class.lpszClassName = class_name;
    window_class.hCursor = LoadCursor(0, IDC_CROSS);
    
    if(!RegisterClass(&window_class))
    {
        MessageBox(0, L"RegisterClass failed", 0, 0);
        return GetLastError();
    }
    
    HWND window = CreateWindowEx(0,
                                 class_name,
                                 L"Window",
                                 WS_OVERLAPPEDWINDOW|WS_VISIBLE,
                                 CW_USEDEFAULT,
                                 CW_USEDEFAULT,
                                 CW_USEDEFAULT,
                                 CW_USEDEFAULT,
                                 0,
                                 0,
                                 instance,
                                 0);
    
    if(!window)
    {
        MessageBox(0, L"CreateWindowEx failed", 0, 0);
        return GetLastError();
    }
    
    // viewport
    
    RECT window_rect = {};
    GetClientRect(window, &window_rect);
    
    int client_width = window_rect.right - window_rect.left;
    int client_height = window_rect.bottom - window_rect.top;
    
    D3D11_VIEWPORT viewport = { 
        0.0f, 0.0f, 
        (float)client_width, (float) client_height, 
        0.0f, 1.0f 
    };
    
    // dx device & context
    
    ID3D11Device *base_device;
    ID3D11DeviceContext *base_context;
    
    UINT creation_flags = 0;
#ifdef _DEBUG
    creation_flags = D3D11_CREATE_DEVICE_DEBUG;
#endif
    
    D3D_FEATURE_LEVEL feature_levels[] = {
        D3D_FEATURE_LEVEL_11_0
    };
    
    HRESULT result = D3D11CreateDevice(0,
                                       D3D_DRIVER_TYPE_HARDWARE,
                                       0,
                                       creation_flags,
                                       feature_levels,
                                       ARRAYSIZE(feature_levels),
                                       D3D11_SDK_VERSION,
                                       &base_device,
                                       0,
                                       &base_context
                                       );
    
    if(FAILED(result))
    {
        MessageBox(0, L"D3D11CreateDevice failed", 0, 0);
        return GetLastError();
    }
    
    ID3D11Device1 *device;
    ID3D11DeviceContext1 *context;
    
    result = base_device->QueryInterface(__uuidof(ID3D11Device1), (void **)&device);
    assert(SUCCEEDED(result));
    base_device->Release();
    
    result = base_context->QueryInterface(__uuidof(ID3D11DeviceContext1), (void **)&context);
    assert(SUCCEEDED(result));
    base_context->Release();
    
    // swap chain
    
    DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
    
    swap_chain_desc.Width = 0;
    swap_chain_desc.Height = 0;
    swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
    swap_chain_desc.SampleDesc.Count = 1;
    swap_chain_desc.SampleDesc.Quality = 0;
    swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swap_chain_desc.BufferCount = 1;
    swap_chain_desc.Scaling = DXGI_SCALING_STRETCH;
    swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
    swap_chain_desc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
    swap_chain_desc.Flags = 0;
    
    IDXGISwapChain1 *swap_chain;
    
    IDXGIDevice2 * dxgi_device;
    result = device->QueryInterface(__uuidof(IDXGIDevice2), (void **)&dxgi_device);
    assert(SUCCEEDED(result));
    
    IDXGIAdapter * dxgi_adapter;
    result = dxgi_device->GetAdapter(&dxgi_adapter);
    
    assert(SUCCEEDED(result));
    dxgi_device->Release();
    
    IDXGIFactory2 * dxgi_factory;
    result = dxgi_adapter->GetParent(__uuidof(IDXGIFactory2), (void **)&dxgi_factory);
    assert(SUCCEEDED(result));
    dxgi_adapter->Release();
    
    result = dxgi_factory->CreateSwapChainForHwnd(device,
                                                  window,
                                                  &swap_chain_desc,
                                                  0,
                                                  0,
                                                  &swap_chain
                                                  );
    assert(SUCCEEDED(result));
    dxgi_factory->Release();
    
    // frame buffer view
    
    ID3D11Texture2D *frame_buffer;
    result = swap_chain->GetBuffer(0,
                                   __uuidof(ID3D11Texture2D),
                                   (void **)&frame_buffer
                                   );
    assert(SUCCEEDED(result));
    
    ID3D11RenderTargetView *frame_buffer_view;
    result = device->CreateRenderTargetView(frame_buffer,
                                            0,
                                            &frame_buffer_view
                                            );
    assert(SUCCEEDED(result));
    frame_buffer->Release();
    
    // shaders
    
    ID3D10Blob *vs_blob;
    result = D3DCompileFromFile(L"shaders.hlsl", 0, 0, "vs_main",
                                "vs_5_0", 0, 0, &vs_blob, 0);
    assert(SUCCEEDED(result));
    
    ID3D11VertexShader *vertex_shader;
    result = device->CreateVertexShader(vs_blob->GetBufferPointer(),
                                        vs_blob->GetBufferSize(),
                                        0,
                                        &vertex_shader);
    assert(SUCCEEDED(result));
    
    ID3D10Blob *ps_blob;
    result = D3DCompileFromFile(L"shaders.hlsl", 0, 0, "ps_main",
                                "ps_5_0", 0, 0, &ps_blob, 0);
    assert(SUCCEEDED(result));
    
    ID3D11PixelShader *pixel_shader;
    result = device->CreatePixelShader(ps_blob->GetBufferPointer(),
                                       ps_blob->GetBufferSize(),
                                       0,
                                       &pixel_shader);
    assert(SUCCEEDED(result));
    ps_blob->Release();
    
    // input layout
    
    D3D11_INPUT_ELEMENT_DESC input_element_desc[] = {
        {
            "POSITION", 0, 
            DXGI_FORMAT_R32G32_FLOAT, 
            0, 0, 
            D3D11_INPUT_PER_VERTEX_DATA, 0
        },
        {
            "UV", 0, 
            DXGI_FORMAT_R32G32_FLOAT, 
            0, D3D11_APPEND_ALIGNED_ELEMENT, 
            D3D11_INPUT_PER_VERTEX_DATA, 0
        }
    };
    
    ID3D11InputLayout *input_layout;
    result = device->CreateInputLayout(input_element_desc,
                                       ARRAYSIZE(input_element_desc),
                                       vs_blob->GetBufferPointer(),
                                       vs_blob->GetBufferSize(),
                                       &input_layout
                                       );
    assert(SUCCEEDED(result));
    vs_blob->Release();
    
    // vertex data, pos & uv
    
    float vertex_data[] = {
        -0.5f, -0.5f, 0.0f, 1.0f, 
        -0.5f, 0.5f, 0.0f, 0.0f,
        0.5f, 0.5f, 1.0f, 0.0f,
        -0.5f, -0.5f, 0.0f, 1.0f,
        0.5f, 0.5f, 1.0f, 0.0f,
        0.5f, -0.5f, 1.0f, 1.0f
    };
    
    // vertex buffer
    
    D3D11_BUFFER_DESC vertex_data_desc = {
        sizeof(vertex_data),
        D3D11_USAGE_DEFAULT,
        D3D11_BIND_VERTEX_BUFFER,
        0, 0, 0
    };
    D3D11_SUBRESOURCE_DATA vertex_data_initial = { vertex_data };
    ID3D11Buffer *vertex_data_buffer;
    result = device->CreateBuffer(&vertex_data_desc,
                                  &vertex_data_initial,
                                  &vertex_data_buffer);
    assert(SUCCEEDED(result));
    
    UINT stride = 4 * sizeof(float);
    UINT offset = 0;
    UINT num_vertices = sizeof(vertex_data) / stride;
    
    // load image
    
    int image_width;
    int image_height;
    int image_channels;
    int image_desired_channels = 4;
    
    unsigned char *image_data = stbi_load("image.jpg",
                                          &image_width, 
                                          &image_height, 
                                          &image_channels, image_desired_channels);
    assert(image_data);
    
    int image_pitch = image_width * 4;
    
    // texture
    
    D3D11_TEXTURE2D_DESC image_texture_desc = {};
    
    image_texture_desc.Width = image_width;
    image_texture_desc.Height = image_height;
    image_texture_desc.MipLevels = 1;
    image_texture_desc.ArraySize = 1;
    image_texture_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
    image_texture_desc.SampleDesc.Count = 1;
    image_texture_desc.SampleDesc.Quality = 0;
    image_texture_desc.Usage = D3D11_USAGE_IMMUTABLE;
    image_texture_desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    
    D3D11_SUBRESOURCE_DATA image_subresource_data = {};
    
    image_subresource_data.pSysMem = image_data; 
    image_subresource_data.SysMemPitch = image_pitch; 
    
    ID3D11Texture2D *image_texture;
    
    result = device->CreateTexture2D(&image_texture_desc,
                                     &image_subresource_data,
                                     &image_texture
                                     );
    
    assert(SUCCEEDED(result));
    
    free(image_data);
    
    // shader resource view
    
    ID3D11ShaderResourceView *image_shader_resource_view;
    
    result = device->CreateShaderResourceView(image_texture,
                                              nullptr,
                                              &image_shader_resource_view
                                              );
    assert(SUCCEEDED(result));
    
    // sampler
    
    D3D11_SAMPLER_DESC image_sampler_desc = {};
    
    image_sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    image_sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
    image_sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
    image_sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
    image_sampler_desc.MipLODBias = 0.0f;
    image_sampler_desc.MaxAnisotropy = 1;
    image_sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    image_sampler_desc.BorderColor[0] = 1.0f;
    image_sampler_desc.BorderColor[1] = 1.0f;
    image_sampler_desc.BorderColor[2] = 1.0f;
    image_sampler_desc.BorderColor[3] = 1.0f;
    image_sampler_desc.MinLOD = -FLT_MAX;
    image_sampler_desc.MaxLOD = FLT_MAX;
    
    ID3D11SamplerState *image_sampler_state;
    
    result = device->CreateSamplerState(&image_sampler_desc,
                                        &image_sampler_state);
    
    assert(SUCCEEDED(result));
    
    // loop
    
    bool running = true;
    
    while(running)
    {
        MSG msg;
        while(PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
        {
            if(msg.message == WM_QUIT) running = false;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        
        float color[4] = {0.3f, 0.3f, 0.3f, 1.0};
        
        context->ClearRenderTargetView(frame_buffer_view, color);
        
        context->RSSetViewports(1, &viewport);
        
        context->OMSetRenderTargets(1, 
                                    &frame_buffer_view, 
                                    0);
        context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        context->IASetInputLayout(input_layout);
        
        context->VSSetShader(vertex_shader, 0, 0);
        context->PSSetShader(pixel_shader, 0, 0);
        
        context->PSSetShaderResources(0, 1, &image_shader_resource_view);
        context->PSSetSamplers(0, 1, &image_sampler_state);
        
        context->IASetVertexBuffers(0,
                                    1,
                                    &vertex_data_buffer,
                                    &stride,
                                    &offset
                                    );
        
        context->Draw(num_vertices, 0);
        
        swap_chain->Present(1, 0);
    }
    
    return 0;
}

shaders.hlsl

struct VS_Input
{
	float2 position: POSITION;
	float2 uv: UV;
};

struct VS_Output
{
	float4 position: SV_POSITION;
	float2 uv: UV;
};

VS_Output vs_main(VS_Input input)
{
	VS_Output output;
	output.position = float4(input.position, 0.0f, 1.0f);
	output.uv = input.uv;
	return output;
};

Texture2D my_texture;
SamplerState my_sampler;

float4 ps_main(VS_Output input): SV_TARGET
{	
	return my_texture.Sample(my_sampler, input.uv);
};

build.bat

@echo off

set COMMON_COMPILER_FLAGS=/W4 /wd4100 /Oi /nologo /Fegame.exe

set DEBUG_FLAGS=/Zi /MTd
set RELEASE_FLAGS=/O2

set COMPILER_FLAGS=%COMMON_COMPILER_FLAGS% %DEBUG_FLAGS%
REM set COMPILER_FLAGS=%COMMON_COMPILER_FLAGS% %RELEASE_FLAGS%

set LINKER_FLAGS=/INCREMENTAL:NO /OPT:REF
set LIBS=user32.lib D3D11.lib D3Dcompiler.lib

cl %COMPILER_FLAGS% main.cpp /link %LINKER_FLAGS% %LIBS%