Skip to content

Instantly share code, notes, and snippets.

@d7samurai
Last active March 27, 2024 17:50
Show Gist options
  • Save d7samurai/aee35fd5d132c51e8b0a78699cbaa1e4 to your computer and use it in GitHub Desktop.
Save d7samurai/aee35fd5d132c51e8b0a78699cbaa1e4 to your computer and use it in GitHub Desktop.
Minimal D3D11 pt2

Minimal D3D11 pt2

Follow-up to Minimal D3D11, adding instanced rendering. As before: An uncluttered Direct3D 11 setup & basic rendering primer / API familiarizer. Complete, runnable Windows application contained in a single function and laid out in a linear, step-by-step fashion. No modern C++ / OOP / obscuring cruft.

The main difference here is that the hollow cube is rendered using DrawIndexedInstanced (which saves a lot of vertices compared to the original, so model data is now small enough to be included in the source without being too much in the way), but also all trigonometry and matrix math is moved to the vertex shader, further simplifying the main code.

Each instance is merely this piece of geometry, consisting of 4 triangles:

instanced1

..which is then repeated 24 times, rotated and colored:

instanced2

Note: There are two separate instance buffers in play. The first one is holding the rotations that are applied to each instance, the second one contains the colors. Rotations are stored as triplets of integers, signifying how many multiples of 90 degree rotation that will be applied to each axis, in XYZ order. Colors are stored as RGB float triplets, and since each face consists of four instances, all sharing the same color, this buffer is advanced just once every four instances (hence separate buffers).

Also check out Minimal D3D11 pt3, with shadowmapping + showcasing a range of alternative setup / rendering techniques.

#pragma comment(lib, "user32")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d3dcompiler")
///////////////////////////////////////////////////////////////////////////////////////////////////
#include <windows.h>
#include <d3d11_1.h>
#include <d3dcompiler.h>
///////////////////////////////////////////////////////////////////////////////////////////////////
#define TITLE "Minimal D3D11 pt2 by d7samurai"
///////////////////////////////////////////////////////////////////////////////////////////////////
UINT TextureData[] = { 0xffffffff, 0xff7f7f7f, 0xff7f7f7f, 0xffffffff }; // 2x2 pixels
float VertexData[] = // pos.x, pos.y, pos.z, nor.x, nor.y, nor.z, tex.u, tex.v, ...
{
-1.00f, 1.00f, -1.00f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.00f, 1.00f, -1.00f, 0.0f, 0.0f, -1.0f, 9.5f, 0.0f,
0.58f, 0.58f, -1.00f, 0.0f, 0.0f, -1.0f, 7.5f, 2.0f, -0.58f, 0.58f, -1.00f, 0.0f, 0.0f, -1.0f, 2.0f, 2.0f,
-0.58f, 0.58f, -1.00f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.58f, 0.58f, -1.00f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
0.58f, 0.58f, -0.58f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, -0.58f, 0.58f, -0.58f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
};
UINT IndexData[] = { 0, 1, 3, 1, 2, 3, 4, 5, 7, 5, 6, 7 };
UINT InstanceRotationData[] = { 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 3, 1, 0, 0, 1, 1, 0, 1, 2, 0, 1, 3, 0, 2, 0, 0, 2, 0, 1, 2, 0, 2, 2, 0, 3, 3, 0, 0, 3, 1, 0, 3, 2, 0, 3, 3, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 1, 3, 1, 1, 0, 3, 1, 1, 3, 1, 2, 3, 1, 3, 3 }; // rot.x, rot.y, rot.z, ... in multiples of 90 degrees
float InstanceColorData[] = { 0.973f, 0.480f, 0.002f, 0.897f, 0.163f, 0.011f, 0.612f, 0.000f, 0.069f, 0.127f, 0.116f, 0.408f, 0.000f, 0.254f, 0.637f, 0.001f, 0.447f, 0.067f }; // col.r, col.g, col.b, ...
///////////////////////////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASSA wndClass = { 0, DefWindowProcA, 0, 0, 0, 0, 0, 0, 0, TITLE };
RegisterClassA(&wndClass);
HWND window = CreateWindowExA(0, TITLE, TITLE, WS_POPUP | WS_MAXIMIZE | WS_VISIBLE, 0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_0 };
ID3D11Device* baseDevice;
ID3D11DeviceContext* baseDeviceContext;
D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, featureLevels, ARRAYSIZE(featureLevels), D3D11_SDK_VERSION, &baseDevice, nullptr, &baseDeviceContext);
///////////////////////////////////////////////////////////////////////////////////////////////
ID3D11Device1* device;
baseDevice->QueryInterface(__uuidof(ID3D11Device1), reinterpret_cast<void**>(&device));
ID3D11DeviceContext1* deviceContext;
baseDeviceContext->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void**>(&deviceContext));
///////////////////////////////////////////////////////////////////////////////////////////////
IDXGIDevice1* dxgiDevice;
device->QueryInterface(__uuidof(IDXGIDevice1), reinterpret_cast<void**>(&dxgiDevice));
IDXGIAdapter* dxgiAdapter;
dxgiDevice->GetAdapter(&dxgiAdapter);
IDXGIFactory2* dxgiFactory;
dxgiAdapter->GetParent(__uuidof(IDXGIFactory2), reinterpret_cast<void**>(&dxgiFactory));
///////////////////////////////////////////////////////////////////////////////////////////////
DXGI_SWAP_CHAIN_DESC1 swapChainDesc;
swapChainDesc.Width = 0; // use window width
swapChainDesc.Height = 0; // use window height
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // can't specify _SRGB here when using DXGI_SWAP_EFFECT_FLIP_* ...;
swapChainDesc.Stereo = FALSE;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
swapChainDesc.Flags = 0;
IDXGISwapChain1* swapChain;
dxgiFactory->CreateSwapChainForHwnd(device, window, &swapChainDesc, nullptr, nullptr, &swapChain);
///////////////////////////////////////////////////////////////////////////////////////////////
ID3D11Texture2D* frameBuffer;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&frameBuffer));
D3D11_RENDER_TARGET_VIEW_DESC framebufferDesc = {};
framebufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; // ... so do this to get _SRGB swapchain
framebufferDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
ID3D11RenderTargetView* frameBufferView;
device->CreateRenderTargetView(frameBuffer, &framebufferDesc, &frameBufferView);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_TEXTURE2D_DESC depthBufferDesc;
frameBuffer->GetDesc(&depthBufferDesc); // copy from framebuffer properties
depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
ID3D11Texture2D* depthBuffer;
device->CreateTexture2D(&depthBufferDesc, nullptr, &depthBuffer);
ID3D11DepthStencilView* depthBufferView;
device->CreateDepthStencilView(depthBuffer, nullptr, &depthBufferView);
///////////////////////////////////////////////////////////////////////////////////////////////
ID3DBlob* vsBlob;
D3DCompileFromFile(L"shaders.hlsl", nullptr, nullptr, "vs_main", "vs_5_0", 0, 0, &vsBlob, nullptr);
ID3D11VertexShader* vertexShader;
device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &vertexShader);
D3D11_INPUT_ELEMENT_DESC inputElementDesc[] = // float3 position, float3 normal, float2 texcoord, uint3 rotation, float3 color
{
{ "POS", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEX", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "ROT", 0, DXGI_FORMAT_R32G32B32_UINT, 1, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 1 }, // change every instance
{ "COL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 2, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_INSTANCE_DATA, 4 }, // change every 4th instance, i.e. every face
};
ID3D11InputLayout* inputLayout;
device->CreateInputLayout(inputElementDesc, ARRAYSIZE(inputElementDesc), vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), &inputLayout);
///////////////////////////////////////////////////////////////////////////////////////////////
ID3DBlob* psBlob;
D3DCompileFromFile(L"shaders.hlsl", nullptr, nullptr, "ps_main", "ps_5_0", 0, 0, &psBlob, nullptr);
ID3D11PixelShader* pixelShader;
device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &pixelShader);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_RASTERIZER_DESC1 rasterizerDesc = {};
rasterizerDesc.FillMode = D3D11_FILL_SOLID;
rasterizerDesc.CullMode = D3D11_CULL_BACK;
ID3D11RasterizerState1* rasterizerState;
device->CreateRasterizerState1(&rasterizerDesc, &rasterizerState);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_SAMPLER_DESC samplerDesc = {};
samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
ID3D11SamplerState* samplerState;
device->CreateSamplerState(&samplerDesc, &samplerState);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_DEPTH_STENCIL_DESC depthStencilDesc = {};
depthStencilDesc.DepthEnable = TRUE;
depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
ID3D11DepthStencilState* depthStencilState;
device->CreateDepthStencilState(&depthStencilDesc, &depthStencilState);
///////////////////////////////////////////////////////////////////////////////////////////////
struct float4 { float x, y, z, w; };
struct Constants
{
float4 Projection[4];
float4 LightVector;
float4 Rotate;
float4 Scale;
float4 Translate;
};
D3D11_BUFFER_DESC constantBufferDesc = {};
constantBufferDesc.ByteWidth = sizeof(Constants) + 0xf & 0xfffffff0; // ensure constant buffer size is multiple of 16 bytes
constantBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
ID3D11Buffer* constantBuffer;
device->CreateBuffer(&constantBufferDesc, nullptr, &constantBuffer);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_BUFFER_DESC vertexBufferDesc = {};
vertexBufferDesc.ByteWidth = sizeof(VertexData);
vertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
D3D11_SUBRESOURCE_DATA vertexData = { VertexData };
ID3D11Buffer* vertexBuffer;
device->CreateBuffer(&vertexBufferDesc, &vertexData, &vertexBuffer);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_BUFFER_DESC indexBufferDesc = {};
indexBufferDesc.ByteWidth = sizeof(IndexData);
indexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
D3D11_SUBRESOURCE_DATA indexData = { IndexData };
ID3D11Buffer* indexBuffer;
device->CreateBuffer(&indexBufferDesc, &indexData, &indexBuffer);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_BUFFER_DESC instanceBufferDesc = {};
instanceBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
instanceBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
D3D11_SUBRESOURCE_DATA instanceData = {};
ID3D11Buffer* instanceRotationBuffer;
instanceBufferDesc.ByteWidth = sizeof(InstanceRotationData);
instanceData.pSysMem = { InstanceRotationData };
device->CreateBuffer(&instanceBufferDesc, &instanceData, &instanceRotationBuffer);
ID3D11Buffer* instanceColorBuffer;
instanceBufferDesc.ByteWidth = sizeof(InstanceColorData);
instanceData.pSysMem = { InstanceColorData };
device->CreateBuffer(&instanceBufferDesc, &instanceData, &instanceColorBuffer);
///////////////////////////////////////////////////////////////////////////////////////////////
D3D11_TEXTURE2D_DESC textureDesc = {};
textureDesc.Width = 2;
textureDesc.Height = 2;
textureDesc.MipLevels = 1;
textureDesc.ArraySize = 1;
textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
textureDesc.SampleDesc.Count = 1;
textureDesc.Usage = D3D11_USAGE_IMMUTABLE;
textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
D3D11_SUBRESOURCE_DATA textureData = {};
textureData.pSysMem = TextureData;
textureData.SysMemPitch = 2 * sizeof(UINT); // texture is 2 pixels wide, 4 bytes per pixel
ID3D11Texture2D* texture;
device->CreateTexture2D(&textureDesc, &textureData, &texture);
ID3D11ShaderResourceView* textureView;
device->CreateShaderResourceView(texture, nullptr, &textureView);
///////////////////////////////////////////////////////////////////////////////////////////////
FLOAT backgroundColor[4] = { 0.025f, 0.025f, 0.025f, 1.0f };
ID3D11Buffer* buffers[] = { vertexBuffer, instanceRotationBuffer, instanceColorBuffer };
UINT strides[] = { 8 * sizeof(float), 3 * sizeof(UINT), 3 * sizeof(float) }; // vertex (float3 position, float3 normal, float2 texcoord), instance rotation (uint3 rotation), instance color (float3 color)
UINT offsets[] = { 0, 0, 0 };
D3D11_VIEWPORT viewport = { 0.0f, 0.0f, static_cast<float>(depthBufferDesc.Width), static_cast<float>(depthBufferDesc.Height), 0.0f, 1.0f };
///////////////////////////////////////////////////////////////////////////////////////////////
float w = viewport.Width / viewport.Height; // width (aspect ratio)
float h = 1.0f; // height
float n = 1.0f; // near
float f = 9.0f; // far
Constants constants = { 2 * n / w, 0, 0, 0, 0, 2 * n / h, 0, 0, 0, 0, f / (f - n), 1, 0, 0, n * f / (n - f), 0 }; // projection matrix
constants.LightVector = { 1.0f, -1.0f, 1.0f };
constants.Rotate = { 0.0f, 0.0f, 0.0f };
constants.Scale = { 1.0f, 1.0f, 1.0f };
constants.Translate = { 0.0f, 0.0f, 4.0f };
///////////////////////////////////////////////////////////////////////////////////////////////
while (true)
{
MSG msg;
while (PeekMessageA(&msg, nullptr, 0, 0, PM_REMOVE))
{
if (msg.message == WM_KEYDOWN) return 0;
DispatchMessageA(&msg);
}
///////////////////////////////////////////////////////////////////////////////////////////
constants.Rotate.x += 0.005f;
constants.Rotate.y += 0.009f;
constants.Rotate.z += 0.001f;
///////////////////////////////////////////////////////////////////////////////////////////
D3D11_MAPPED_SUBRESOURCE mappedSubresource;
deviceContext->Map(constantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedSubresource);
*reinterpret_cast<Constants*>(mappedSubresource.pData) = constants;
deviceContext->Unmap(constantBuffer, 0);
///////////////////////////////////////////////////////////////////////////////////////////
deviceContext->ClearRenderTargetView(frameBufferView, backgroundColor);
deviceContext->ClearDepthStencilView(depthBufferView, D3D11_CLEAR_DEPTH, 1.0f, 0);
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
deviceContext->IASetInputLayout(inputLayout);
deviceContext->IASetVertexBuffers(0, 3, buffers, strides, offsets);
deviceContext->IASetIndexBuffer(indexBuffer, DXGI_FORMAT_R32_UINT, 0);
deviceContext->VSSetShader(vertexShader, nullptr, 0);
deviceContext->VSSetConstantBuffers(0, 1, &constantBuffer);
deviceContext->RSSetViewports(1, &viewport);
deviceContext->RSSetState(rasterizerState);
deviceContext->PSSetShader(pixelShader, nullptr, 0);
deviceContext->PSSetShaderResources(0, 1, &textureView);
deviceContext->PSSetSamplers(0, 1, &samplerState);
deviceContext->OMSetRenderTargets(1, &frameBufferView, depthBufferView);
deviceContext->OMSetDepthStencilState(depthStencilState, 0);
deviceContext->OMSetBlendState(nullptr, nullptr, 0xffffffff); // use default blend mode (i.e. disable)
///////////////////////////////////////////////////////////////////////////////////////////
deviceContext->DrawIndexedInstanced(ARRAYSIZE(IndexData), 24, 0, 0, 0);
///////////////////////////////////////////////////////////////////////////////////////////
swapChain->Present(1, 0);
}
}
cbuffer constants : register(b0)
{
row_major float4x4 projection;
float3 lightvector;
float3 rotate;
float3 scale;
float3 translate;
}
struct vs_in
{
float3 position : POS;
float3 normal : NOR;
float2 texcoord : TEX;
uint3 rotation : ROT; // instance rotation
float3 color : COL; // instance color
};
struct vs_out
{
float4 position : SV_POSITION;
float2 texcoord : TEX;
float4 color : COL;
};
Texture2D mytexture : register(t0);
SamplerState mysampler : register(s0);
float4x4 get_rotation_matrix(float3 r)
{
float4x4 x = { 1, 0, 0, 0, 0, cos(r.x), -sin(r.x), 0, 0, sin(r.x), cos(r.x), 0, 0, 0, 0, 1 };
float4x4 y = { cos(r.y), 0, sin(r.y), 0, 0, 1, 0, 0, -sin(r.y), 0, cos(r.y), 0, 0, 0, 0, 1 };
float4x4 z = { cos(r.z), -sin(r.z), 0, 0, sin(r.z), cos(r.z), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
return mul(mul(x, y), z);
}
vs_out vs_main(vs_in input)
{
float4x4 scalematrix = { scale.x, 0, 0, 0, 0, scale.y, 0, 0, 0, 0, scale.z, 0, 0, 0, 0, 1 };
float4x4 translatematrix = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, translate.x, translate.y, translate.z, 1 };
float4x4 transform = mul(mul(mul(get_rotation_matrix(1.5708f * input.rotation), get_rotation_matrix(rotate)), scalematrix), translatematrix);
float light = clamp(dot(mul(input.normal, transform), normalize(-lightvector)), 0.0f, 1.0f) * 0.8f + 0.2f;
vs_out output;
output.position = mul(float4(input.position, 1.0f), mul(transform, projection));
output.texcoord = input.texcoord;
output.color = float4(input.color * light, 1.0f);
return output;
}
float4 ps_main(vs_out input) : SV_TARGET
{
return mytexture.Sample(mysampler, input.texcoord) * input.color;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment