Skip to content

Instantly share code, notes, and snippets.

@DaseinPhaos
Created April 29, 2022 18:23
Show Gist options
  • Save DaseinPhaos/39d880551f71ccacf07d138de962b783 to your computer and use it in GitHub Desktop.
Save DaseinPhaos/39d880551f71ccacf07d138de962b783 to your computer and use it in GitHub Desktop.
Minimal d3d11 triangles using Odin
// 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