Created
April 29, 2022 18:23
-
-
Save DaseinPhaos/39d880551f71ccacf07d138de962b783 to your computer and use it in GitHub Desktop.
Minimal d3d11 triangles using Odin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// reference: https://gist.github.com/msmshazan/dfd5362004be37ff5e016b6a42be5083 | |
package d3d11_triangle | |
import "core:strings" | |
import "core:runtime" | |
import "core:fmt" | |
import "core:sys/windows" | |
import d3d11 "vendor:directx/d3d11" | |
import dxgi "vendor:directx/dxgi" | |
import d3dc "vendor:directx/d3d_compiler" | |
main :: proc() { | |
wnd := create_hwnd() | |
if wnd == nil { | |
fmt.println("CreateWindow failed") | |
return | |
} | |
fmt.println("CreateWindow done") | |
for { | |
msg: windows.MSG | |
if windows.PeekMessageW(&msg, nil, 0, 0, windows.PM_REMOVE) { | |
if msg.message == windows.WM_QUIT { | |
break | |
} | |
windows.TranslateMessage(&msg) | |
windows.DispatchMessageW(&msg) | |
continue | |
} | |
render_frame() | |
if failed(present_frame(wnd)) { | |
break | |
} | |
} | |
} | |
create_hwnd:: proc() -> windows.HWND { | |
wc : windows.WNDCLASSEXW | |
wc.cbSize = size_of(wc) | |
wc.lpfnWndProc = wndProc | |
wc.hInstance = cast(windows.HANDLE)windows.GetModuleHandleW(nil) | |
winClassName := windows.utf8_to_wstring("d3d11_window_class") | |
winName := windows.utf8_to_wstring("d3d11_window_inst") | |
wc.lpszClassName = winClassName | |
if windows.RegisterClassExW(&wc) == 0 { | |
ec := windows.GetLastError() | |
fmt.printf("Win32 error 0x%x: Failed to register wndClass", ec) | |
return nil | |
} | |
exstyle : windows.DWORD //= windows.WS_EX_APPWINDOW | |
style :windows.DWORD = windows.WS_OVERLAPPEDWINDOW | |
style &= (~windows.DWORD(windows.WS_THICKFRAME)) & (~windows.DWORD(windows.WS_MAXIMIZEBOX)) | |
width :i32= 1280 | |
height :i32= 720 | |
rect := windows.RECT{ | |
left = 0, top = 0, right = width, bottom = height, | |
} | |
if !windows.AdjustWindowRectEx(&rect, style, windows.FALSE, exstyle) { | |
fmt.println("Failed to adjust style") | |
style = windows.WS_OVERLAPPEDWINDOW | |
} else { | |
width = rect.right - rect.left | |
height = rect.bottom - rect.top | |
} | |
return windows.CreateWindowExW(exstyle, wc.lpszClassName, winName, style | windows.WS_VISIBLE, windows.CW_USEDEFAULT, windows.CW_USEDEFAULT, width, height, nil, nil, wc.hInstance, nil) | |
} | |
wndProc :: proc "std" (wnd: windows.HWND, msg: windows.UINT, wparam: windows.WPARAM, lparam: windows.LPARAM) -> windows.LRESULT { | |
context = runtime.default_context() | |
using windows | |
switch msg { | |
case WM_CREATE: { | |
fmt.println("Receive wm_create") | |
hr := init_device(wnd) | |
if failed(hr) do return -1 | |
} | |
case WM_DESTROY: { | |
fmt.println("Receive wm_destroy") | |
destroy_device() | |
windows.PostQuitMessage(0) | |
} | |
case WM_SIZE: { | |
fmt.println("Receive wm_size") | |
if failed(resize_device(wnd, LOWORD(DWORD(lparam)), HIWORD(DWORD(lparam)))) do DestroyWindow(wnd) | |
} | |
case: { | |
return windows.DefWindowProcW(wnd, msg, wparam, lparam) | |
} | |
} | |
return 0 | |
} | |
failed ::#force_inline proc(hr: windows.HRESULT, msg: string="", loc := #caller_location) -> (ret:bool) { | |
ret = hr < 0 | |
if ret { | |
proc(hr: windows.HRESULT, msg:string, loc: runtime.Source_Code_Location) { | |
fmt.printf("Failed with 0x%x: %v\n...at %v\n", u32(hr), msg, loc) | |
}(hr, msg, loc) | |
} | |
return | |
} | |
init_device:: proc(wnd: windows.HWND) -> (hr:windows.HRESULT) { | |
using d3d11 | |
{ // device | |
flags :CREATE_DEVICE_FLAGS= {CREATE_DEVICE_FLAG.SINGLETHREADED} | |
featureLevel : FEATURE_LEVEL | |
hr = CreateDevice(nil, DRIVER_TYPE.HARDWARE, nil, flags, nil, 0, SDK_VERSION, &_device, &featureLevel, &_ctxt) | |
if failed(hr) do return | |
fmt.printf("Device created with feature level %v\n", featureLevel) | |
} | |
// TODO: IID_ID3D11DeviceContext1 | |
{// swap chain | |
using dxgi | |
factory : ^IFactory | |
hr = CreateDXGIFactory(IFactory_UUID, &factory) | |
if failed(hr) do return | |
desc : SWAP_CHAIN_DESC | |
desc.BufferDesc.Format = .R8G8B8A8_UNORM //SRGB? | |
desc.BufferDesc.RefreshRate.Numerator = 60 | |
desc.BufferDesc.RefreshRate.Denominator = 1 | |
desc.SampleDesc.Count = 1 | |
desc.SampleDesc.Quality = 0 | |
desc.BufferUsage = .RENDER_TARGET_OUTPUT | |
desc.OutputWindow = wnd | |
desc.Windowed = true | |
desc.BufferCount = 2 | |
//TODO: desc.Flags = u32(SWAP_CHAIN_FLAG.FRAME_LATENCY_WAITABLE_OBJECT) | |
// win10+ | |
desc.SwapEffect = .FLIP_DISCARD | |
hr = factory->CreateSwapChain(cast(^IUnknown)_device, &desc, &_swapChain) | |
if failed(hr) { | |
fmt.println("Fallback to win8.1") | |
desc.SwapEffect = .FLIP_SEQUENTIAL | |
hr = factory->CreateSwapChain(cast(^IUnknown)_device, &desc, &_swapChain) | |
} | |
if failed(hr) { | |
fmt.println("Fallback to even older windows") | |
desc.SwapEffect = .DISCARD | |
desc.Flags = 0 | |
hr = factory->CreateSwapChain(cast(^IUnknown)_device, &desc, &_swapChain) | |
} | |
if failed(hr) do return | |
// TODO: frame latency control | |
factory->Release() | |
} | |
{ // raster state | |
desc: RASTERIZER_DESC | |
desc.FillMode = .SOLID | |
desc.CullMode = .BACK | |
desc.FrontCounterClockwise = false | |
desc.DepthBias = 0 | |
desc.DepthBiasClamp = 0 | |
desc.SlopeScaledDepthBias = 0 | |
desc.DepthClipEnable = true | |
desc.ScissorEnable = false | |
desc.MultisampleEnable = false | |
desc.AntialiasedLineEnable = false | |
hr = _device->CreateRasterizerState(&desc, &_rasterState) | |
if failed(hr) do return | |
} | |
// TODO: depth stencil state | |
{// blend state | |
desc: BLEND_DESC | |
rtDesc := &desc.RenderTarget[0] | |
rtDesc.BlendEnable = true | |
rtDesc.SrcBlend = .SRC_ALPHA | |
rtDesc.DestBlend = .INV_SRC_ALPHA | |
rtDesc.BlendOp = .ADD | |
rtDesc.SrcBlendAlpha = .SRC_ALPHA | |
rtDesc.DestBlendAlpha = .INV_SRC_ALPHA | |
rtDesc.BlendOpAlpha = .ADD | |
rtDesc.RenderTargetWriteMask = u8(COLOR_WRITE_ENABLE.ALL) | |
hr = _device->CreateBlendState(&desc, &_blendState) | |
if failed(hr) do return | |
} | |
shader_data: cstring = ` | |
struct VSInput { | |
float2 pos : POSITION; | |
float3 col : COLOR0; | |
}; | |
struct PSInput { | |
float4 pos : SV_POSITION; | |
float3 col : COLOR0; | |
}; | |
PSInput vert(VSInput vin) { | |
PSInput vout; | |
vout.pos = float4(vin.pos.xy, 0.0f, 1.0f); | |
vout.col = vin.col; | |
return vout; | |
} | |
float4 frag(PSInput pin) : SV_Target { | |
return float4(pin.col.rgb, 0.5f); | |
} | |
` | |
compile_flags: u32 = 0 | |
when ODIN_DEBUG { | |
compile_flags |= u32(d3dc.D3DCOMPILE.DEBUG) | |
compile_flags |= u32(d3dc.D3DCOMPILE.SKIP_OPTIMIZATION) | |
} | |
{ // vertex shader, input assembler | |
compiled_code : ^IBlob | |
compile_error : ^IBlob | |
hr = d3dc.Compile(rawptr(shader_data), len(shader_data), nil, nil, nil, "vert", "vs_4_0", compile_flags, 0, &compiled_code, &compile_error) | |
if failed(hr) { | |
defer compile_error->Release() | |
error_data := compile_error->GetBufferPointer() | |
error_len := compile_error->GetBufferSize() | |
// output ascii string | |
error_msg := strings.string_from_ptr(cast(^byte)error_data, int(error_len)) | |
fmt.println(error_msg) | |
return | |
} | |
defer compiled_code->Release() | |
code_ptr := compiled_code->GetBufferPointer() | |
code_size := compiled_code->GetBufferSize() | |
hr = _device->CreateVertexShader(code_ptr, code_size, nil, &_vertShader) | |
if failed(hr) do return | |
descs :[]INPUT_ELEMENT_DESC = { | |
{"POSITION", 0, .R32G32_FLOAT, 0, u32(offset_of(Vertex, x)), .VERTEX_DATA, 0,}, | |
{"COLOR", 0, .R32G32_FLOAT, 0, u32(offset_of(Vertex, r)), .VERTEX_DATA, 0,}, | |
} | |
fmt.printf("Offset of .r in Vertex: %v\n",descs[1].AlignedByteOffset) | |
hr = _device->CreateInputLayout(&descs[0], u32(len(descs)), code_ptr, code_size, &_inputLayout) | |
if failed(hr) do return | |
} | |
{ // frag shader | |
compiled_code : ^IBlob | |
hr = d3dc.Compile(rawptr(shader_data), len(shader_data), nil, nil, nil, "frag", "ps_4_0", compile_flags, 0, &compiled_code, nil) | |
if failed(hr) do return // TODO: parse error | |
defer compiled_code->Release() | |
code_ptr := compiled_code->GetBufferPointer() | |
code_size := compiled_code->GetBufferSize() | |
hr = _device->CreatePixelShader(code_ptr, code_size, nil, &_fragShader) | |
if failed(hr) do return | |
} | |
{ // vertex buffer | |
desc: BUFFER_DESC | |
desc.ByteWidth = size_of(Vertex) * len(vertices) | |
desc.Usage = .IMMUTABLE | |
desc.BindFlags = .VERTEX_BUFFER | |
data: SUBRESOURCE_DATA | |
data.pSysMem = &vertices[0] | |
hr = _device->CreateBuffer(&desc, &data, &_vertBuffer) | |
if failed(hr) do return | |
} | |
return | |
} | |
resize_device :: proc(wnd: windows.HWND, w, h: u16) -> (hr:windows.HRESULT) { | |
if w==0||h==0 { | |
hr = S_OK; return | |
} | |
if _rtView != nil { // release existing rtv | |
_ctxt->OMSetRenderTargets(0, nil, nil) | |
_rtView->Release() | |
_rtView = nil | |
} | |
flags : u32 = 0 // TODO: latency wait | |
fmt.printf("resizing to %vx%v\n", w,h) | |
hr = _swapChain->ResizeBuffers(0, u32(w), u32(h), .UNKNOWN, flags) | |
if failed(hr) do return | |
window_buffer : ^d3d11.ITexture2D | |
hr = _swapChain->GetBuffer(0, d3d11.ITexture2D_UUID, cast(^rawptr)&window_buffer) | |
if failed(hr) do return | |
hr = _device->CreateRenderTargetView(cast(^d3d11.IResource)window_buffer, nil, &_rtView) | |
if failed(hr) do return | |
// TODO: DSV | |
viewport := d3d11.VIEWPORT{0,0,f32(w),f32(h),0,1} | |
_ctxt->RSSetViewports(1, &viewport) | |
return | |
} | |
render_frame :: proc() { | |
// TODO: latency wait | |
_ctxt->OMSetRenderTargets(1, &_rtView, nil) | |
// clear | |
clearCol := [?]f32 {0.42,0.52,0.62,1.0} | |
_ctxt->ClearRenderTargetView(_rtView, &clearCol) | |
// draw triangle | |
stride :u32= size_of(Vertex) | |
offset :u32= 0 | |
_ctxt->IASetInputLayout(_inputLayout) | |
_ctxt->IASetVertexBuffers(0,1,&_vertBuffer, &stride, &offset) | |
_ctxt->IASetPrimitiveTopology(.TRIANGLELIST) | |
_ctxt->VSSetShader(_vertShader, nil, 0) | |
_ctxt->PSSetShader(_fragShader, nil, 0) | |
_ctxt->RSSetState(_rasterState) | |
_ctxt->OMSetBlendState(_blendState, nil, ~u32(0)) | |
_ctxt->Draw(len(vertices), 0) | |
} | |
present_frame :: proc(wnd: windows.HWND) -> (hr:windows.HRESULT) { | |
hr = _swapChain->Present(1, 0) | |
switch u32(hr) { | |
case DXGI_ERROR_DEVICE_REMOVED:fallthrough | |
case DXGI_ERROR_DEVICE_RESET:fallthrough | |
case DXGI_ERROR_DRIVER_INTERNAL_ERROR: | |
hr = reinit_device(wnd) | |
if failed(hr) do return | |
rect : windows.RECT | |
if windows.GetClientRect(wnd, &rect) { | |
hr = resize_device(wnd, u16(rect.right-rect.left), u16(rect.bottom-rect.top)) | |
if failed(hr) do return | |
} | |
} | |
failed(hr) | |
return | |
} | |
destroy_device :: proc() { | |
if _ctxt != nil do _ctxt->ClearState() | |
safe_release(&_vertBuffer) | |
safe_release(&_inputLayout) | |
safe_release(&_vertShader) | |
safe_release(&_fragShader) | |
safe_release(&_rasterState) | |
safe_release(&_blendState) | |
safe_release(&_rtView) | |
safe_release(&_ctxt) | |
safe_release(&_device) | |
safe_release(&_swapChain) | |
} | |
safe_release :: #force_inline proc(handle : ^^$T) { | |
if handle != nil { | |
handle^->Release() | |
handle^ = nil | |
} | |
} | |
reinit_device :: proc(wnd : windows.HWND) -> (hr: windows.HRESULT) { | |
destroy_device() | |
hr = init_device(wnd) | |
if failed(hr) do destroy_device() | |
return | |
} | |
_device: ^d3d11.IDevice | |
_ctxt: ^d3d11.IDeviceContext | |
_swapChain: ^dxgi.ISwapChain | |
_rasterState: ^d3d11.IRasterizerState | |
_blendState: ^d3d11.IBlendState | |
_vertShader: ^d3d11.IVertexShader | |
_inputLayout: ^d3d11.IInputLayout | |
_fragShader: ^d3d11.IPixelShader | |
_vertBuffer: ^d3d11.IBuffer | |
_rtView: ^d3d11.IRenderTargetView | |
Vertex :: struct { | |
x, y: f32, | |
r, g, b: f32, | |
} | |
vertices := [?]Vertex { | |
{ 0.0, 0.8660254, 1.0, 0.0, 0.0 }, | |
{ 0.5, -0.5, 0.0, 1.0, 0.0 }, | |
{ -0.5, -0.5, 0.0, 0.0, 1.0 }, | |
{ 0.0, -0.8660254, 1.0, 0.0, 0.0 }, | |
{ -0.5, 0.5, 0.0, 0.0, 1.0 }, | |
{ 0.5, 0.5, 0.0, 1.0, 0.0 }, | |
} | |
S_OK :: 0 | |
// https://docs.microsoft.com/en-us/windows/win32/direct3ddxgi/dxgi-error | |
DXGI_ERROR_DEVICE_REMOVED :: 0x887A0005 | |
DXGI_ERROR_DEVICE_RESET :: 0x887A0007 | |
DXGI_ERROR_DRIVER_INTERNAL_ERROR :: 0x887A0020 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment