Skip to content

Instantly share code, notes, and snippets.

@ranjian0
Created October 11, 2025 12:59
Show Gist options
  • Select an option

  • Save ranjian0/34e46a27e0529edbf2da6ddcf216be9e to your computer and use it in GitHub Desktop.

Select an option

Save ranjian0/34e46a27e0529edbf2da6ddcf216be9e to your computer and use it in GitHub Desktop.
SDL3_D3D_Examples
#include <SDL3/SDL.h>
#include <d3d11.h>
#include <d3dcompiler.h>
#include <iostream>
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")
// Simple vertex structure
struct Vertex {
float x, y, z;
float r, g, b;
};
// Vertex shader source
const char* vsSource = R"(
struct VS_INPUT {
float3 pos : POSITION;
float3 color : COLOR;
};
struct VS_OUTPUT {
float4 pos : SV_POSITION;
float4 color : COLOR;
};
VS_OUTPUT main(VS_INPUT input) {
VS_OUTPUT output;
output.pos = float4(input.pos, 1.0f);
output.color = float4(input.color, 1.0f);
return output;
}
)";
// Pixel shader source
const char* psSource = R"(
float4 main(float4 pos : SV_POSITION, float4 color : COLOR) : SV_TARGET {
return color;
}
)";
int main(int argc, char* argv[]) {
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl;
return -1;
}
SDL_Window* window = SDL_CreateWindow("D3D11 Triangle with SDL3", 800, 600, 0);
if (!window) {
std::cerr << "Failed to create window: " << SDL_GetError() << std::endl;
SDL_Quit();
return -1;
}
HWND hwnd = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
if (!hwnd) {
std::cerr << "Failed to get native window handle: " << SDL_GetError() << std::endl;
SDL_DestroyWindow(window);
SDL_Quit();
return -1;
}
// --- Direct3D 11 Initialization ---
ID3D11Device* device = nullptr;
ID3D11DeviceContext* context = nullptr;
IDXGISwapChain* swapChain = nullptr;
ID3D11RenderTargetView* renderTargetView = nullptr;
DXGI_SWAP_CHAIN_DESC scd = {};
scd.BufferCount = 1;
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
scd.OutputWindow = hwnd;
scd.SampleDesc.Count = 1;
scd.Windowed = TRUE;
HRESULT hr = D3D11CreateDeviceAndSwapChain(
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0,
D3D11_SDK_VERSION, &scd, &swapChain, &device, nullptr, &context);
if (FAILED(hr)) {
std::cerr << "D3D11CreateDeviceAndSwapChain failed." << std::endl;
// Cleanup SDL
return -1;
}
ID3D11Texture2D* pBackBuffer = nullptr;
swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
device->CreateRenderTargetView(pBackBuffer, nullptr, &renderTargetView);
pBackBuffer->Release();
context->OMSetRenderTargets(1, &renderTargetView, nullptr);
// --- Shader and Vertex Buffer Creation ---
ID3DBlob* vsBlob = nullptr;
ID3DBlob* errorBlob = nullptr;
D3DCompile(vsSource, strlen(vsSource), nullptr, nullptr, nullptr, "main", "vs_5_0", 0, 0, &vsBlob, &errorBlob);
if (errorBlob) {
std::cerr << "Vertex shader compilation error: " << (char*)errorBlob->GetBufferPointer() << std::endl;
// Cleanup
return -1;
}
ID3DBlob* psBlob = nullptr;
D3DCompile(psSource, strlen(psSource), nullptr, nullptr, nullptr, "main", "ps_5_0", 0, 0, &psBlob, &errorBlob);
if (errorBlob) {
std::cerr << "Pixel shader compilation error: " << (char*)errorBlob->GetBufferPointer() << std::endl;
// Cleanup
return -1;
}
ID3D11VertexShader* vertexShader = nullptr;
ID3D11PixelShader* pixelShader = nullptr;
device->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &vertexShader);
device->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &pixelShader);
Vertex vertices[] = {
{ 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f },
{ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f },
{ -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f }
};
D3D11_BUFFER_DESC bd = {};
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = sizeof(Vertex) * 3;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
D3D11_SUBRESOURCE_DATA sd = {};
sd.pSysMem = vertices;
ID3D11Buffer* vertexBuffer = nullptr;
device->CreateBuffer(&bd, &sd, &vertexBuffer);
// --- Input Layout ---
D3D11_INPUT_ELEMENT_DESC ied[] = {
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
ID3D11InputLayout* inputLayout = nullptr;
device->CreateInputLayout(ied, 2, vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), &inputLayout);
// --- Main Loop ---
bool running = true;
while (running) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
running = false;
}
}
// --- Rendering ---
float clearColor[] = { 0.1f, 0.1f, 0.1f, 1.0f };
context->ClearRenderTargetView(renderTargetView, clearColor);
UINT stride = sizeof(Vertex);
UINT offset = 0;
context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
context->IASetInputLayout(inputLayout);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
context->VSSetShader(vertexShader, nullptr, 0);
context->PSSetShader(pixelShader, nullptr, 0);
D3D11_VIEWPORT viewport = {};
viewport.Width = 800;
viewport.Height = 600;
viewport.MaxDepth = 1.0f;
context->RSSetViewports(1, &viewport);
context->Draw(3, 0);
swapChain->Present(1, 0);
}
// --- Cleanup ---
vertexShader->Release();
pixelShader->Release();
vsBlob->Release();
psBlob->Release();
vertexBuffer->Release();
inputLayout->Release();
renderTargetView->Release();
swapChain->Release();
context->Release();
device->Release();
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
#include <SDL3/SDL.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>
#include <iostream>
#include <vector>
#include <wrl.h> // For Microsoft::WRL::ComPtr
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3dcompiler.lib")
using namespace DirectX;
using Microsoft::WRL::ComPtr;
// Simple vertex structure
struct Vertex {
XMFLOAT3 pos;
XMFLOAT4 color;
};
// Global constants
const UINT FrameCount = 2; // Number of back buffers (double-buffering)
// D3D12 objects
ComPtr<ID3D12Device> g_device;
ComPtr<ID3D12CommandQueue> g_commandQueue;
ComPtr<IDXGISwapChain3> g_swapChain;
ComPtr<ID3D12DescriptorHeap> g_rtvHeap;
ComPtr<ID3D12Resource> g_renderTargets[FrameCount];
ComPtr<ID3D12CommandAllocator> g_commandAllocator;
ComPtr<ID3D12RootSignature> g_rootSignature;
ComPtr<ID3D12PipelineState> g_pipelineState;
ComPtr<ID3D12GraphicsCommandList> g_commandList;
UINT g_rtvDescriptorSize;
// Vertex buffer
ComPtr<ID3D12Resource> g_vertexBuffer;
D3D12_VERTEX_BUFFER_VIEW g_vertexBufferView;
// Synchronization objects
UINT g_frameIndex;
ComPtr<ID3D12Fence> g_fence;
UINT64 g_fenceValue;
HANDLE g_fenceEvent;
// Helper function to wait for the GPU to finish commands
void WaitForPreviousFrame() {
// Signal and increment the fence value.
const UINT64 fence = g_fenceValue;
g_commandQueue->Signal(g_fence.Get(), fence);
g_fenceValue++;
// Wait until the previous frame is finished.
if (g_fence->GetCompletedValue() < fence) {
g_fence->SetEventOnCompletion(fence, g_fenceEvent);
WaitForSingleObject(g_fenceEvent, INFINITE);
}
g_frameIndex = g_swapChain->GetCurrentBackBufferIndex();
}
int main(int argc, char* argv[]) {
// --- 1. SDL Initialization and Window Creation ---
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl;
return -1;
}
SDL_Window* window = SDL_CreateWindow("D3D12 Triangle with SDL3", 1280, 720, 0);
if (!window) {
std::cerr << "Failed to create window: " << SDL_GetError() << std::endl;
SDL_Quit();
return -1;
}
HWND hwnd = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
if (!hwnd) {
std::cerr << "Failed to get native window handle." << std::endl;
SDL_DestroyWindow(window);
SDL_Quit();
return -1;
}
// --- 2. D3D12 Initialization ---
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) {
debugController->EnableDebugLayer();
}
ComPtr<IDXGIFactory4> factory;
CreateDXGIFactory1(IID_PPV_ARGS(&factory));
if (FAILED(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&g_device)))) {
std::cerr << "D3D12CreateDevice failed." << std::endl;
return -1;
}
// Create Command Queue
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
g_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&g_commandQueue));
// Create Swap Chain
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = FrameCount;
swapChainDesc.Width = 1280;
swapChainDesc.Height = 720;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;
ComPtr<IDXGISwapChain1> swapChain;
factory->CreateSwapChainForHwnd(
g_commandQueue.Get(), // The command queue
hwnd,
&swapChainDesc,
nullptr,
nullptr,
&swapChain
);
factory->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER);
swapChain.As(&g_swapChain);
g_frameIndex = g_swapChain->GetCurrentBackBufferIndex();
// Create RTV Descriptor Heap
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = FrameCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
g_device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&g_rtvHeap));
g_rtvDescriptorSize = g_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
// Create Render Target Views
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = g_rtvHeap->GetCPUDescriptorHandleForHeapStart();
for (UINT n = 0; n < FrameCount; n++) {
g_swapChain->GetBuffer(n, IID_PPV_ARGS(&g_renderTargets[n]));
g_device->CreateRenderTargetView(g_renderTargets[n].Get(), nullptr, rtvHandle);
rtvHandle.ptr += g_rtvDescriptorSize;
}
// Create Command Allocator
g_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&g_commandAllocator));
// --- 3. Create Pipeline State ---
// Create an empty root signature
D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = {};
rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error);
g_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&g_rootSignature));
// Create shaders
ComPtr<ID3DBlob> vertexShader;
ComPtr<ID3DBlob> pixelShader;
UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
const char* shaderSource = R"(
struct PSInput {
float4 position : SV_POSITION;
float4 color : COLOR;
};
PSInput VSMain(float4 position : POSITION, float4 color : COLOR) {
PSInput result;
result.position = position;
result.color = color;
return result;
}
float4 PSMain(PSInput input) : SV_TARGET {
return input.color;
}
)";
D3DCompile(shaderSource, strlen(shaderSource), nullptr, nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &vertexShader, nullptr);
D3DCompile(shaderSource, strlen(shaderSource), nullptr, nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &pixelShader, nullptr);
// Define the vertex input layout
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
// Describe and create the graphics pipeline state object (PSO)
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
psoDesc.pRootSignature = g_rootSignature.Get();
psoDesc.VS = { vertexShader->GetBufferPointer(), vertexShader->GetBufferSize() };
psoDesc.PS = { pixelShader->GetBufferPointer(), pixelShader->GetBufferSize() };
psoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
psoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
psoDesc.BlendState.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
g_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&g_pipelineState));
// --- 4. Create Command List and Vertex Buffer ---
g_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_commandAllocator.Get(), g_pipelineState.Get(), IID_PPV_ARGS(&g_commandList));
// Define triangle vertices
Vertex triangleVertices[] = {
{ { 0.0f, 0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } },
{ { 0.5f, -0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } },
{ { -0.5f, -0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } }
};
const UINT vertexBufferSize = sizeof(triangleVertices);
// Create the vertex buffer resource
D3D12_HEAP_PROPERTIES heapProps = { D3D12_HEAP_TYPE_UPLOAD };
D3D12_RESOURCE_DESC bufferDesc = { D3D12_RESOURCE_DIMENSION_BUFFER, 0, vertexBufferSize, 1, 1, 1, DXGI_FORMAT_UNKNOWN, {1, 0}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_NONE };
g_device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&g_vertexBuffer)
);
// Copy the triangle data to the vertex buffer
UINT8* pVertexDataBegin;
D3D12_RANGE readRange = { 0, 0 };
g_vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin));
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
g_vertexBuffer->Unmap(0, nullptr);
// Initialize the vertex buffer view
g_vertexBufferView.BufferLocation = g_vertexBuffer->GetGPUVirtualAddress();
g_vertexBufferView.StrideInBytes = sizeof(Vertex);
g_vertexBufferView.SizeInBytes = vertexBufferSize;
// Command list is created, now close it to be ready for execution
g_commandList->Close();
// --- 5. Create Synchronization Objects ---
g_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&g_fence));
g_fenceValue = 1;
g_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
// --- 6. Main Loop ---
bool running = true;
while (running) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_EVENT_QUIT) {
running = false;
}
}
// --- RECORD COMMANDS ---
g_commandAllocator->Reset();
g_commandList->Reset(g_commandAllocator.Get(), g_pipelineState.Get());
// Set necessary states
g_commandList->SetGraphicsRootSignature(g_rootSignature.Get());
D3D12_VIEWPORT viewport = { 0.0f, 0.0f, 1280.0f, 720.0f, 0.0f, 1.0f };
D3D12_RECT scissorRect = { 0, 0, 1280, 720 };
g_commandList->RSSetViewports(1, &viewport);
g_commandList->RSSetScissorRects(1, &scissorRect);
// Indicate that the back buffer will be used as a render target
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = g_renderTargets[g_frameIndex].Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
g_commandList->ResourceBarrier(1, &barrier);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = g_rtvHeap->GetCPUDescriptorHandleForHeapStart();
rtvHandle.ptr += g_frameIndex * g_rtvDescriptorSize;
g_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
// Record drawing commands
const float clearColor[] = { 0.1f, 0.1f, 0.1f, 1.0f };
g_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
g_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
g_commandList->IASetVertexBuffers(0, 1, &g_vertexBufferView);
g_commandList->DrawInstanced(3, 1, 0, 0); // Draw 3 vertices
// Indicate that the back buffer will now be used to present
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
g_commandList->ResourceBarrier(1, &barrier);
g_commandList->Close();
// --- EXECUTE and PRESENT ---
ID3D12CommandList* ppCommandLists[] = { g_commandList.Get() };
g_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
g_swapChain->Present(1, 0);
WaitForPreviousFrame();
}
// Wait for the GPU to be done with all resources before cleaning up.
WaitForPreviousFrame();
CloseHandle(g_fenceEvent);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment