DirectX 11 - Textures (C/C++ & Win32)
How to load an image and map it on a surface.
Updated Jun 26, 2022

main.cpp

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

LRESULT CALLBACK WindowProc(HWND Window, UINT Message, WPARAM WParam, LPARAM LParam) {
    switch(Message) {
        case WM_KEYDOWN: {
            switch(WParam) {
                case 'O': { DestroyWindow(Window); };
            }
        } break;
        case WM_DESTROY: { PostQuitMessage(0); } break;
        
        default: {
            return DefWindowProc(Window, Message, WParam,  LParam);
        }
    }
    
    return 0;
}

int WINAPI wWinMain(HINSTANCE Instance, HINSTANCE PrevInstance, PWSTR CmdLine, int CmdShow) {
    
    WNDCLASS WindowClass = {};
    
    const wchar_t ClassName[] = L"MyWindowClass";
    
    WindowClass.lpfnWndProc = WindowProc;
    WindowClass.hInstance = Instance;
    WindowClass.lpszClassName = ClassName;
    WindowClass.hCursor = LoadCursor(0, IDC_CROSS);
    
    if(!RegisterClass(&WindowClass)) {
        MessageBox(0, L"RegisterClass failed", 0, 0);
        return GetLastError();
    }
    
    HWND Window = CreateWindowEx(0, ClassName, 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 WindowRect = {};
    GetClientRect(Window, &WindowRect);
    
    int ClientWidth = WindowRect.right - WindowRect.left;
    int ClientHeight = WindowRect.bottom - WindowRect.top;
    
    D3D11_VIEWPORT Viewport = { 
        0.0f, 0.0f, 
        (float)ClientWidth, (float) ClientHeight, 
        0.0f, 1.0f 
    };
    
    // Device & Context
    
    ID3D11Device *BaseDevice;
    ID3D11DeviceContext *BaseContext;
    
    UINT CreationFlags = 0;
#ifdef _DEBUG
    CreationFlags = D3D11_CREATE_DEVICE_DEBUG;
#endif
    
    D3D_FEATURE_LEVEL FeatureLevels[] = {
        D3D_FEATURE_LEVEL_11_0
    };
    
    HRESULT Result = D3D11CreateDevice(0, D3D_DRIVER_TYPE_HARDWARE, 0,
                                       CreationFlags, FeatureLevels,
                                       ARRAYSIZE(FeatureLevels),
                                       D3D11_SDK_VERSION, &BaseDevice, 0,
                                       &BaseContext);
    
    if(FAILED(Result)) {
        MessageBox(0, L"D3D11CreateDevice failed", 0, 0);
        return GetLastError();
    }
    
    ID3D11Device1 *Device;
    ID3D11DeviceContext1 *Context;
    
    Result = BaseDevice->QueryInterface(__uuidof(ID3D11Device1), (void **)&Device);
    assert(SUCCEEDED(Result));
    BaseDevice->Release();
    
    Result = BaseContext->QueryInterface(__uuidof(ID3D11DeviceContext1), (void **)&Context);
    assert(SUCCEEDED(Result));
    BaseContext->Release();
    
    // Swap chain
    
    DXGI_SWAP_CHAIN_DESC1 SwapChainDesc = {};
    
    SwapChainDesc.Width = 0;
    SwapChainDesc.Height = 0;
    SwapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
    SwapChainDesc.SampleDesc.Count = 1;
    SwapChainDesc.SampleDesc.Quality = 0;
    SwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    SwapChainDesc.BufferCount = 1;
    SwapChainDesc.Scaling = DXGI_SCALING_STRETCH;
    SwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
    SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
    SwapChainDesc.Flags = 0;
    
    IDXGISwapChain1 *SwapChain;
    
    IDXGIDevice2 *DxgiDevice;
    Result = Device->QueryInterface(__uuidof(IDXGIDevice2), (void **)&DxgiDevice);
    assert(SUCCEEDED(Result));
    
    IDXGIAdapter *DxgiAdapter;
    Result = DxgiDevice->GetAdapter(&DxgiAdapter);
    assert(SUCCEEDED(Result));
    DxgiDevice->Release();
    
    IDXGIFactory2 *DxgiFactory;
    Result = DxgiAdapter->GetParent(__uuidof(IDXGIFactory2), (void **)&DxgiFactory);
    assert(SUCCEEDED(Result));
    DxgiAdapter->Release();
    
    Result = DxgiFactory->CreateSwapChainForHwnd(Device, Window,
                                                 &SwapChainDesc, 0, 0, &SwapChain);
    assert(SUCCEEDED(Result));
    DxgiFactory->Release();
    
    // Frame buffer
    
    ID3D11Texture2D *FrameBuffer;
    Result = SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void **)&FrameBuffer);
    assert(SUCCEEDED(Result));
    
    ID3D11RenderTargetView *FrameBufferView;
    Result = Device->CreateRenderTargetView(FrameBuffer, 0, &FrameBufferView);
    assert(SUCCEEDED(Result));
    FrameBuffer->Release();
    
    // Shaders
    
    ID3D10Blob *VSBlob;
    Result = D3DCompileFromFile(L"shaders.hlsl", 0, 0, "vs_main",
                                "vs_5_0", 0, 0, &VSBlob, 0);
    assert(SUCCEEDED(Result));
    
    ID3D11VertexShader *VertexShader;
    Result = Device->CreateVertexShader(VSBlob->GetBufferPointer(),
                                        VSBlob->GetBufferSize(),
                                        0,
                                        &VertexShader);
    assert(SUCCEEDED(Result));
    
    ID3D10Blob *PSBlob;
    Result = D3DCompileFromFile(L"shaders.hlsl", 0, 0, "ps_main",
                                "ps_5_0", 0, 0, &PSBlob, 0);
    assert(SUCCEEDED(Result));
    
    ID3D11PixelShader *PixelShader;
    Result = Device->CreatePixelShader(PSBlob->GetBufferPointer(),
                                       PSBlob->GetBufferSize(),
                                       0,
                                       &PixelShader);
    assert(SUCCEEDED(Result));
    PSBlob->Release();
    
    // Input layout
    
    D3D11_INPUT_ELEMENT_DESC InputElementDesc[] = {
        {
            "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 *InputLayout;
    Result = Device->CreateInputLayout(InputElementDesc,
                                       ARRAYSIZE(InputElementDesc),
                                       VSBlob->GetBufferPointer(),
                                       VSBlob->GetBufferSize(),
                                       &InputLayout
                                       );
    assert(SUCCEEDED(Result));
    VSBlob->Release();
    
    // Vertex data, x,y position & uv coords
    
    float VertexData[] = {
        -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 VertexDataDesc = {
        sizeof(VertexData),
        D3D11_USAGE_DEFAULT,
        D3D11_BIND_VERTEX_BUFFER,
        0, 0, 0
    };
    D3D11_SUBRESOURCE_DATA VertexDataInitial = { VertexData };
    ID3D11Buffer *VertexDataBuffer;
    Result = Device->CreateBuffer(&VertexDataDesc,
                                  &VertexDataInitial,
                                  &VertexDataBuffer);
    assert(SUCCEEDED(Result));
    
    UINT Stride = 4 * sizeof(float);
    UINT Offset = 0;
    UINT NumVertices = sizeof(VertexData) / Stride;
    
    // Load image
    
    int ImageWidth;
    int ImageHeight;
    int ImageChannels;
    int ImageDesiredChannels = 4;
    
    unsigned char *ImageData = stbi_load("image.jpg",
                                         &ImageWidth, 
                                         &ImageHeight, 
                                         &ImageChannels, ImageDesiredChannels);
    assert(ImageData);
    
    int ImagePitch = ImageWidth * 4;
    
    // Texture
    
    D3D11_TEXTURE2D_DESC ImageTextureDesc = {};
    
    ImageTextureDesc.Width = ImageWidth;
    ImageTextureDesc.Height = ImageHeight;
    ImageTextureDesc.MipLevels = 1;
    ImageTextureDesc.ArraySize = 1;
    ImageTextureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
    ImageTextureDesc.SampleDesc.Count = 1;
    ImageTextureDesc.SampleDesc.Quality = 0;
    ImageTextureDesc.Usage = D3D11_USAGE_IMMUTABLE;
    ImageTextureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    
    D3D11_SUBRESOURCE_DATA ImageSubresourceData = {};
    
    ImageSubresourceData.pSysMem = ImageData; 
    ImageSubresourceData.SysMemPitch = ImagePitch; 
    
    ID3D11Texture2D *ImageTexture;
    
    Result = Device->CreateTexture2D(&ImageTextureDesc,
                                     &ImageSubresourceData,
                                     &ImageTexture
                                     );
    
    assert(SUCCEEDED(Result));
    
    free(ImageData);
    
    // Shader resource view
    
    ID3D11ShaderResourceView *ImageShaderResourceView;
    
    Result = Device->CreateShaderResourceView(ImageTexture,
                                              nullptr,
                                              &ImageShaderResourceView
                                              );
    assert(SUCCEEDED(Result));
    
    // Sampler
    
    D3D11_SAMPLER_DESC ImageSamplerDesc = {};
    
    ImageSamplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    ImageSamplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
    ImageSamplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
    ImageSamplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
    ImageSamplerDesc.MipLODBias = 0.0f;
    ImageSamplerDesc.MaxAnisotropy = 1;
    ImageSamplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    ImageSamplerDesc.BorderColor[0] = 1.0f;
    ImageSamplerDesc.BorderColor[1] = 1.0f;
    ImageSamplerDesc.BorderColor[2] = 1.0f;
    ImageSamplerDesc.BorderColor[3] = 1.0f;
    ImageSamplerDesc.MinLOD = -FLT_MAX;
    ImageSamplerDesc.MaxLOD = FLT_MAX;
    
    ID3D11SamplerState *ImageSamplerState;
    
    Result = Device->CreateSamplerState(&ImageSamplerDesc,
                                        &ImageSamplerState);
    
    assert(SUCCEEDED(Result));
    
    bool Running = true;
    
    while(Running) {
        MSG Message;
        while(PeekMessage(&Message, nullptr, 0, 0, PM_REMOVE)) {
            if(Message.message == WM_QUIT) Running = false;
            TranslateMessage(&Message);
            DispatchMessage(&Message);
        }
        
        float Color[4] = {0.3f, 0.3f, 0.3f, 1.0f};

        Context->ClearRenderTargetView(FrameBufferView, Color);
        
        Context->RSSetViewports(1, &Viewport);
        
        Context->OMSetRenderTargets(1, &FrameBufferView, 0);

        Context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        Context->IASetInputLayout(InputLayout);
        
        Context->VSSetShader(VertexShader, 0, 0);
        Context->PSSetShader(PixelShader, 0, 0);
        
        Context->PSSetShaderResources(0, 1, &ImageShaderResourceView);
        Context->PSSetSamplers(0, 1, &ImageSamplerState);
        
        Context->IASetVertexBuffers(0, 1, &VertexDataBuffer, &Stride, &Offset);

        Context->Draw(NumVertices, 0);
        
        SwapChain->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

cl main.cpp /link user32.lib d3d11.lib d3dcompiler.lib

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%

Result