Skip to content

Instantly share code, notes, and snippets.

@mmozeiko
Created January 31, 2024 22:06
Show Gist options
  • Save mmozeiko/3c9ba3a0ec0c54aff8b2dec7ac724208 to your computer and use it in GitHub Desktop.
Save mmozeiko/3c9ba3a0ec0c54aff8b2dec7ac724208 to your computer and use it in GitHub Desktop.
instancing example
#define COBJMACROS
#define WIN32_LEAN_AND_MEAN
#include <initguid.h>
#include <windows.h>
#include <d3dcompiler.h>
#include <dxgidebug.h>
#include <dxgi1_3.h>
#include <d3d11.h>
#include "c2d.h"
#define _USE_MATH_DEFINES
#include <math.h>
#include <string.h>
#include <stddef.h>
#include <intrin.h>
#define Assert(cond) do { if (!(cond)) __debugbreak(); } while (0)
#define AssertHR(hr) Assert(SUCCEEDED(hr))
#pragma comment (lib, "gdi32")
#pragma comment (lib, "user32")
#pragma comment (lib, "dxguid")
#pragma comment (lib, "dxgi")
#pragma comment (lib, "d3d11")
#pragma comment (lib, "d3dcompiler")
static LRESULT CALLBACK WindowProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(wnd, msg, wparam, lparam);
}
int main()
{
HINSTANCE instance = 0;
WNDCLASSEXW wc =
{
.cbSize = sizeof(wc),
.lpfnWndProc = WindowProc,
.hInstance = instance,
.hIcon = LoadIcon(NULL, IDI_APPLICATION),
.hCursor = LoadCursor(NULL, IDC_ARROW),
.lpszClassName = L"d3d11_window_class",
};
ATOM atom = RegisterClassExW(&wc);
Assert(atom && "Failed to register window class");
HWND window = CreateWindowExW(
WS_EX_APPWINDOW, wc.lpszClassName, L"D3D11 Window", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, wc.hInstance, NULL);
Assert(window && "Failed to create window");
HRESULT hr;
ID3D11Device* device;
ID3D11DeviceContext* context;
// create D3D11 device & context
{
UINT flags = D3D11_CREATE_DEVICE_DEBUG | D3D11_CREATE_DEVICE_BGRA_SUPPORT;
D3D_FEATURE_LEVEL levels[] = { D3D_FEATURE_LEVEL_11_0 };
hr = D3D11CreateDevice(
NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, levels, ARRAYSIZE(levels),
D3D11_SDK_VERSION, &device, NULL, &context);
AssertHR(hr);
}
{
ID3D11InfoQueue* info;
ID3D11Device_QueryInterface(device, &IID_ID3D11InfoQueue, (void**)&info);
ID3D11InfoQueue_SetBreakOnSeverity(info, D3D11_MESSAGE_SEVERITY_CORRUPTION, TRUE);
ID3D11InfoQueue_SetBreakOnSeverity(info, D3D11_MESSAGE_SEVERITY_ERROR, TRUE);
ID3D11InfoQueue_Release(info);
IDXGIInfoQueue* dxgiInfo;
hr = DXGIGetDebugInterface1(0, &IID_IDXGIInfoQueue, (void**)&dxgiInfo);
AssertHR(hr);
IDXGIInfoQueue_SetBreakOnSeverity(dxgiInfo, DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, TRUE);
IDXGIInfoQueue_SetBreakOnSeverity(dxgiInfo, DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, TRUE);
IDXGIInfoQueue_Release(dxgiInfo);
}
// create DXGI swap chain
IDXGISwapChain1* swapChain;
{
// get DXGI device from D3D11 device
IDXGIDevice* dxgiDevice;
IDXGIAdapter* dxgiAdapter;
IDXGIFactory2* factory;
AssertHR(ID3D11Device_QueryInterface(device, &IID_IDXGIDevice, (void**)&dxgiDevice));
AssertHR(IDXGIDevice_GetAdapter(dxgiDevice, &dxgiAdapter));
AssertHR(IDXGIAdapter_GetParent(dxgiAdapter, &IID_IDXGIFactory2, (void**)&factory));
DXGI_SWAP_CHAIN_DESC1 desc =
{
.Format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB,
.SampleDesc = { 1, 0 },
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = 2,
.Scaling = DXGI_SCALING_STRETCH,
.SwapEffect = DXGI_SWAP_EFFECT_DISCARD,
};
AssertHR(IDXGIFactory2_CreateSwapChainForHwnd(factory, (IUnknown*)device, window, &desc, NULL, NULL, &swapChain));
// disable silly Alt+Enter changing monitor resolution to match window size
IDXGIFactory_MakeWindowAssociation(factory, window, DXGI_MWA_NO_ALT_ENTER);
IDXGIFactory2_Release(factory);
IDXGIAdapter_Release(dxgiAdapter);
IDXGIDevice_Release(dxgiDevice);
}
struct Vertex
{
float position[2];
};
struct VertexInstance
{
float position[2];
float color[4];
};
ID3D11Buffer* vbuffer;
{
D3D11_BUFFER_DESC desc =
{
.ByteWidth = 2*3 * sizeof(struct Vertex) + 3 * sizeof(struct VertexInstance),
.Usage = D3D11_USAGE_DYNAMIC,
.BindFlags = D3D11_BIND_VERTEX_BUFFER,
.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE,
};
ID3D11Device_CreateBuffer(device,&desc, NULL, &vbuffer);
}
// vertex & pixel shaders for drawing triangle, plus input layout for vertex input
ID3D11InputLayout* layout;
ID3D11VertexShader* vshader;
ID3D11PixelShader* pshader;
{
// these must match vertex shader input layout (VS_INPUT in vertex shader source below)
D3D11_INPUT_ELEMENT_DESC desc[] =
{
{ "V_POS", 0, DXGI_FORMAT_R32G32_FLOAT, 0, offsetof(struct Vertex, position), D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "I_POS", 0, DXGI_FORMAT_R32G32_FLOAT, 1, offsetof(struct VertexInstance, position), D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, offsetof(struct VertexInstance, color), D3D11_INPUT_PER_INSTANCE_DATA, 1 },
};
const char hlsl[] =
" struct VS_INPUT \n"
" { \n"
" float2 vpos : V_POS; \n"
" float2 ipos : I_POS; \n"
" float4 color : COLOR; \n"
" }; \n"
" \n"
" struct PS_INPUT \n"
" { \n"
" float4 pos : SV_POSITION; \n"
" float4 color : COLOR; \n"
" }; \n"
" \n"
" PS_INPUT vs(VS_INPUT input) \n"
" { \n"
" PS_INPUT output; \n"
" float2 pos = input.vpos + input.ipos; \n"
" output.pos = float4(pos, 0, 1); \n"
" output.color = input.color; \n"
" return output; \n"
" } \n"
" \n"
" float4 ps(PS_INPUT input) : SV_TARGET \n"
" { \n"
" return input.color; \n"
" } \n"
;
UINT flags = D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS;
#ifndef NDEBUG
flags |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
flags |= D3DCOMPILE_OPTIMIZATION_LEVEL3;
#endif
ID3DBlob* error;
ID3DBlob* vblob;
hr = D3DCompile(hlsl, sizeof(hlsl), NULL, NULL, NULL, "vs", "vs_5_0", flags, 0, &vblob, &error);
if (FAILED(hr))
{
const char* message = ID3D10Blob_GetBufferPointer(error);
OutputDebugStringA(message);
Assert(!"Failed to compile vertex shader!");
}
ID3DBlob* pblob;
hr = D3DCompile(hlsl, sizeof(hlsl), NULL, NULL, NULL, "ps", "ps_5_0", flags, 0, &pblob, &error);
if (FAILED(hr))
{
const char* message = ID3D10Blob_GetBufferPointer(error);
OutputDebugStringA(message);
Assert(!"Failed to compile pixel shader!");
}
ID3D11Device_CreateVertexShader(device, ID3D10Blob_GetBufferPointer(vblob), ID3D10Blob_GetBufferSize(vblob), NULL, &vshader);
ID3D11Device_CreatePixelShader(device, ID3D10Blob_GetBufferPointer(pblob), ID3D10Blob_GetBufferSize(pblob), NULL, &pshader);
ID3D11Device_CreateInputLayout(device, desc, ARRAYSIZE(desc), ID3D10Blob_GetBufferPointer(vblob), ID3D10Blob_GetBufferSize(vblob), &layout);
ID3D10Blob_Release(pblob);
ID3D10Blob_Release(vblob);
}
ID3D11RenderTargetView* rtView = NULL;
// show the window
ShowWindow(window, SW_SHOWDEFAULT);
IDWriteFactory* dwFactory;
IDWriteTextFormat* dwTextFormat;
ID2D1Factory* d2Factory;
ID2D1RenderTarget* d2RenderTarget = NULL;
ID2D1SolidColorBrush* d2Brush = NULL;
const wchar_t wszText[] =
L"Hello World using DirectWrite!\n"
// single emoji
L"\U0001F603\n"
// combined emoji, should render just one glyph
L"\U0001F469\U0001F3FD\U0000200D\U0001F468\U0001F3FD\U0000200D\U0001F466\U0001F3FD";
UINT32 cTextLength = sizeof(wszText)/2 - 1;
D2D1_FACTORY_OPTIONS options = { D2D1_DEBUG_LEVEL_NONE };
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &IID_ID2D1Factory, &options, (void**)&d2Factory);
AssertHR(hr);
hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, &IID_IDWriteFactory, (void**)&dwFactory);
AssertHR(hr);
hr = IDWriteFactory_CreateTextFormat(dwFactory, L"Gabriola", NULL, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 72.0f, L"en-us", &dwTextFormat);
AssertHR(hr);
hr = IDWriteTextFormat_SetTextAlignment(dwTextFormat, DWRITE_TEXT_ALIGNMENT_CENTER);
AssertHR(hr);
hr = IDWriteTextFormat_SetParagraphAlignment(dwTextFormat, DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
AssertHR(hr);
hr = IDWriteTextFormat_SetWordWrapping(dwTextFormat, DWRITE_WORD_WRAPPING_NO_WRAP);
D2D1_STROKE_STYLE_PROPERTIES sProps =
{
.startCap = D2D1_CAP_STYLE_FLAT,
.endCap = D2D1_CAP_STYLE_FLAT,
.dashCap = D2D1_CAP_STYLE_FLAT,
.lineJoin = D2D1_LINE_JOIN_MITER,
.miterLimit = 1.f,
.dashStyle = D2D1_DASH_STYLE_SOLID,
.dashOffset = 0.f,
};
ID2D1StrokeStyle* strokeStyle;
hr = ID2D1Factory_CreateStrokeStyle(d2Factory, &sProps, NULL, 0, &strokeStyle);
AssertHR(hr);
int currentWidth = 0;
int currentHeight = 0;
for (;;)
{
MSG msg;
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
ExitProcess(0);
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
// get current size for window client area
RECT rect;
GetClientRect(window, &rect);
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
// resize swap chain if needed
if (rtView == NULL || width != currentWidth || height != currentHeight)
{
if (rtView)
{
// release old swap chain buffers
ID3D11DeviceContext_ClearState(context);
ID3D11RenderTargetView_Release(rtView);
rtView = NULL;
}
if (d2Brush) ID2D1SolidColorBrush_Release(d2Brush);
if (d2RenderTarget) ID2D1RenderTarget_Release(d2RenderTarget);
d2Brush = NULL;
d2RenderTarget = NULL;
// resize to new size for non-zero size
if (width != 0 && height != 0)
{
IDXGISwapChain1_ResizeBuffers(swapChain, 0, 0, 0, DXGI_FORMAT_UNKNOWN, 0);
// create RenderTarget view for new backbuffer texture
ID3D11Texture2D* backbuffer;
IDXGISwapChain1_GetBuffer(swapChain, 0, &IID_ID3D11Texture2D, (void**)&backbuffer);
ID3D11Device_CreateRenderTargetView(device, (ID3D11Resource*)backbuffer, NULL, &rtView);
IDXGISurface* surface;
ID3D11Texture2D_QueryInterface(backbuffer, &IID_IDXGISurface, (void**)&surface);
D2D1_RENDER_TARGET_PROPERTIES rtProps =
{
.type = D2D1_RENDER_TARGET_TYPE_DEFAULT,
.pixelFormat =
{
.format = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB,
.alphaMode = D2D1_ALPHA_MODE_IGNORE,
},
.dpiX = 0,
.dpiY = 0,
.usage = D2D1_RENDER_TARGET_USAGE_NONE,
.minLevel = D2D1_FEATURE_LEVEL_DEFAULT,
};
AssertHR(ID2D1Factory_CreateDxgiSurfaceRenderTarget(d2Factory, surface, &rtProps, &d2RenderTarget));
D2D1_COLOR_F yellow = { 1, 1, 0, 1 };
AssertHR(ID2D1RenderTarget_CreateSolidColorBrush(d2RenderTarget, &yellow, NULL, &d2Brush));
IDXGISurface_Release(surface);
ID3D11Texture2D_Release(backbuffer);
}
currentWidth = width;
currentHeight = height;
}
if (rtView)
{
D3D11_MAPPED_SUBRESOURCE mapped;
ID3D11DeviceContext_Map(context, (ID3D11Resource*)vbuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
struct Vertex* vtx = mapped.pData;
float w = (float)width;
float h = (float)height;
const float qsize = 400;
const float pos[3][2] = { { 100, 300 }, { 300, 500 }, { 600, 300 } };
const float col[3][4] = { {1,0,0,1}, {0,1,0,1}, {0,0,1,1} };
const float corner[4][2] = { { 0, 0 }, { 0, 1 }, { 1, 1 }, { 1, 0 } };
{
float ax = (qsize * corner[0][0]) / w * 2 - 1;
float ay = (qsize * corner[0][1]) / h * 2 - 1;
float bx = (qsize * corner[1][0]) / w * 2 - 1;
float by = (qsize * corner[1][1]) / h * 2 - 1;
float cx = (qsize * corner[2][0]) / w * 2 - 1;
float cy = (qsize * corner[2][1]) / h * 2 - 1;
float dx = (qsize * corner[3][0]) / w * 2 - 1;
float dy = (qsize * corner[3][1]) / h * 2 - 1;
*vtx++ = (struct Vertex) { { ax, -ay } };
*vtx++ = (struct Vertex) { { cx, -cy } };
*vtx++ = (struct Vertex) { { bx, -by } };
*vtx++ = (struct Vertex) { { cx, -cy } };
*vtx++ = (struct Vertex) { { ax, -ay } };
*vtx++ = (struct Vertex) { { dx, -dy } };
}
struct VertexInstance* ivtx = (void*)vtx;
for (int q=0; q<3; q++)
{
const float* c = col[q];
const float* p = pos[q];
float x = p[0] / w * 2;
float y = p[1] / h * 2;
*ivtx++ = (struct VertexInstance) { { x, -y }, { c[0], c[1], c[2], c[3] } };
}
ID3D11DeviceContext_Unmap(context, (ID3D11Resource*)vbuffer, 0);
D3D11_VIEWPORT viewport =
{
.TopLeftX = 0,
.TopLeftY = 0,
.Width = w,
.Height = h,
.MinDepth = 0,
.MaxDepth = 1,
};
// clear screen
FLOAT color[] = { 0.127f, 0.306f, 0.850f, 1.f };
ID3D11DeviceContext_ClearRenderTargetView(context, rtView, color);
// Input Assembler
ID3D11DeviceContext_IASetInputLayout(context, layout);
ID3D11DeviceContext_IASetPrimitiveTopology(context, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
UINT strides[2] = { sizeof(struct Vertex), sizeof(struct VertexInstance) };
UINT offsets[2] = { 0, 2*3 * sizeof(struct Vertex) };
ID3D11Buffer* vbuffers[2] = { vbuffer, vbuffer };
ID3D11DeviceContext_IASetVertexBuffers(context, 0, 2, vbuffers, strides, offsets);
ID3D11DeviceContext_VSSetShader(context, vshader, NULL, 0);
ID3D11DeviceContext_PSSetShader(context, pshader, NULL, 0);
ID3D11DeviceContext_RSSetViewports(context, 1, &viewport);
ID3D11DeviceContext_OMSetRenderTargets(context, 1, &rtView, NULL);
ID3D11DeviceContext_DrawInstanced(context, 3*2, 3, 0, 0);
ID2D1RenderTarget_BeginDraw(d2RenderTarget);
D2D1_RECT_F layout = { 200, 300, 200 + 600, 300 + 300 };
ID2D1RenderTarget_DrawText(d2RenderTarget, wszText, cTextLength, dwTextFormat, &layout, d2Brush, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT, DWRITE_MEASURING_MODE_NATURAL);
D2D1_ROUNDED_RECT rrect = {
.rect = { 200, 700, 200 + 600, 700 + 50 },
.radiusX = 25.f,
.radiusY = 25.f,
};
ID2D1RenderTarget_DrawRoundedRectangle(d2RenderTarget, &rrect, d2Brush, 5.f, strokeStyle);
hr = ID2D1RenderTarget_EndDraw(d2RenderTarget, NULL, NULL);
if (hr == D2DERR_RECREATE_TARGET) { } // error
AssertHR(hr);
}
hr = IDXGISwapChain1_Present(swapChain, 1, 0);
if (hr == DXGI_STATUS_OCCLUDED)
{
Sleep(10);
}
else
{
AssertHR(hr);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment