directx/d3d12_triangle_sdl2 Windows

Code

d3d12_triangle_sdl.odin ¶
505 linesSource

1// D3D12 single-function triangle sample.
2//
3// Usage:
4// - copy SDL2.dll from Odin/vendor/sdl2 to your project directory
5// - odin run .
6//
7// Contributors:
8// - Karl Zylinski <karl@zylinski.se> (version 1, version 3)
9// - Jakub Tomšů (version 2)
10//
11// Based on:
12// - https://gist.github.com/karl-zylinski/e1d1d0925ac5db0f12e4837435c5bbfb
13// - https://gist.github.com/jakubtomsu/ecd83e61976d974c7730f9d7ad3e1fd0
14// - https://github.com/rdunnington/d3d12-hello-triangle/blob/master/main.c
15
16package d3d12_triangle
17
18import "core:fmt"
19import "core:mem"
20import "core:sys/windows"
21import "core:os"
22import sdl "vendor:sdl2"
23import d3d12 "vendor:directx/d3d12"
24import dxgi "vendor:directx/dxgi"
25import d3dc "vendor:directx/d3d_compiler"
26
27NUM_RENDERTARGETS :: 2
28
29check :: proc(res: d3d12.HRESULT, message: string) {
30	if (res >= 0) {
31		return
32	}
33
34	fmt.printf("%v. Error code: %0x\n", message, u32(res))
35	os.exit(-1)
36}
37
38main :: proc() {
39	// Init SDL and create window
40
41	if err := sdl.Init({.VIDEO}); err != 0 {
42		fmt.eprintln(err)
43		return
44	}
45
46	defer sdl.Quit()
47	wx := i32(640)
48	wy := i32(480)
49	window := sdl.CreateWindow("d3d12 triangle", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, wx, wy, { .ALLOW_HIGHDPI, .SHOWN, .RESIZABLE })
50
51	if window == nil {
52		fmt.eprintln(sdl.GetError())
53		return
54	}
55
56	defer sdl.DestroyWindow(window)
57	hr: d3d12.HRESULT
58
59	// Init DXGI factory. DXGI is the link between the window and DirectX
60	factory: ^dxgi.IFactory4
61
62	{
63		flags: dxgi.CREATE_FACTORY
64
65		when ODIN_DEBUG {
66			flags += { .DEBUG }
67		}
68
69		hr = dxgi.CreateDXGIFactory2(flags, dxgi.IFactory4_UUID, cast(^rawptr)&factory)
70		check(hr, "Failed creating factory")
71	}
72
73	// Find the DXGI adapter (GPU)
74	adapter: ^dxgi.IAdapter1
75	error_not_found := dxgi.HRESULT(-142213123)
76
77	for i: u32 = 0; factory->EnumAdapters1(i, &adapter) != error_not_found; i += 1 {
78		desc: dxgi.ADAPTER_DESC1
79		adapter->GetDesc1(&desc)
80		if .SOFTWARE in desc.Flags {
81			continue
82		}
83
84		if d3d12.CreateDevice((^dxgi.IUnknown)(adapter), ._12_0, d3d12.IDevice_UUID, nil) >= 0 {
85			break
86		} else {
87			fmt.println("Failed to create device")
88		}
89	}
90
91	if adapter == nil {
92		fmt.println("Could not find hardware adapter")
93		return
94	}
95
96	// Create D3D12 device that represents the GPU
97	device: ^d3d12.IDevice
98	hr = d3d12.CreateDevice((^dxgi.IUnknown)(adapter), ._12_0, d3d12.IDevice_UUID, (^rawptr)(&device))
99	check(hr, "Failed to create device")
100	queue: ^d3d12.ICommandQueue
101
102	{
103		desc := d3d12.COMMAND_QUEUE_DESC {
104			Type = .DIRECT,
105		}
106
107		hr = device->CreateCommandQueue(&desc, d3d12.ICommandQueue_UUID, (^rawptr)(&queue))
108		check(hr, "Failed creating command queue")
109	}
110
111	// Get the window handle from SDL
112	window_info: sdl.SysWMinfo
113	sdl.GetWindowWMInfo(window, &window_info)
114	window_handle := dxgi.HWND(window_info.info.win.window)
115	
116	// Create the swapchain, it's the thing that contains render targets that we draw into. It has 2 render targets (NUM_RENDERTARGETS), giving us double buffering.
117	swapchain: ^dxgi.ISwapChain3
118	
119	{
120		desc := dxgi.SWAP_CHAIN_DESC1 {
121			Width = u32(wx),
122			Height = u32(wy),
123			Format = .R8G8B8A8_UNORM,
124			SampleDesc = {
125				Count = 1,
126				Quality = 0,
127			},
128			BufferUsage = {.RENDER_TARGET_OUTPUT},
129			BufferCount = NUM_RENDERTARGETS,
130			Scaling = .NONE,
131			SwapEffect = .FLIP_DISCARD,
132			AlphaMode = .UNSPECIFIED,
133		}
134
135		hr = factory->CreateSwapChainForHwnd((^dxgi.IUnknown)(queue), window_handle, &desc, nil, nil, (^^dxgi.ISwapChain1)(&swapchain))
136		check(hr, "Failed to create swap chain")
137	}
138
139	frame_index := swapchain->GetCurrentBackBufferIndex()
140
141	// Descripors describe the GPU data and are allocated from a Descriptor Heap
142	rtv_descriptor_heap: ^d3d12.IDescriptorHeap
143
144	{
145		desc := d3d12.DESCRIPTOR_HEAP_DESC {
146			NumDescriptors = NUM_RENDERTARGETS,
147			Type = .RTV,
148			Flags = {},
149		}
150
151		hr = device->CreateDescriptorHeap(&desc, d3d12.IDescriptorHeap_UUID, (^rawptr)(&rtv_descriptor_heap))
152		check(hr, "Failed creating descriptor heap")
153	}
154
155	// Fetch the two render targets from the swapchain
156	targets: [NUM_RENDERTARGETS]^d3d12.IResource
157
158	{
159		rtv_descriptor_size: u32 = device->GetDescriptorHandleIncrementSize(.RTV)
160
161		rtv_descriptor_handle: d3d12.CPU_DESCRIPTOR_HANDLE
162		rtv_descriptor_heap->GetCPUDescriptorHandleForHeapStart(&rtv_descriptor_handle)
163
164		for i :u32= 0; i < NUM_RENDERTARGETS; i += 1 {
165			hr = swapchain->GetBuffer(i, d3d12.IResource_UUID, (^rawptr)(&targets[i]))
166			check(hr, "Failed getting render target")
167			device->CreateRenderTargetView(targets[i], nil, rtv_descriptor_handle)
168			rtv_descriptor_handle.ptr += uint(rtv_descriptor_size)
169		}
170	}
171
172	// The command allocator is used to create the commandlist that is used to tell the GPU what to draw
173	command_allocator: ^d3d12.ICommandAllocator
174	hr = device->CreateCommandAllocator(.DIRECT, d3d12.ICommandAllocator_UUID, (^rawptr)(&command_allocator))
175	check(hr, "Failed creating command allocator")
176
177	/* 
178	From https://docs.microsoft.com/en-us/windows/win32/direct3d12/root-signatures-overview:
179	
180		A root signature is configured by the app and links command lists to the resources the shaders require.
181		The graphics command list has both a graphics and compute root signature. A compute command list will
182		simply have one compute root signature. These root signatures are independent of each other.
183	*/
184	root_signature: ^d3d12.IRootSignature 
185
186	{
187		desc := d3d12.VERSIONED_ROOT_SIGNATURE_DESC {
188			Version = ._1_0,
189		}
190
191		desc.Desc_1_0.Flags = {.ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT}
192		serialized_desc: ^d3d12.IBlob
193		hr = d3d12.SerializeVersionedRootSignature(&desc, &serialized_desc, nil)
194		check(hr, "Failed to serialize root signature")
195		hr = device->CreateRootSignature(0, serialized_desc->GetBufferPointer(), serialized_desc->GetBufferSize(), d3d12.IRootSignature_UUID, (^rawptr)(&root_signature))
196		check(hr, "Failed creating root signature")
197		serialized_desc->Release()
198	}
199
200	// The pipeline contains the shaders etc to use
201	pipeline: ^d3d12.IPipelineState
202
203	{
204		// Compile vertex and pixel shaders
205		data :cstring=
206			`struct PSInput {
207			   float4 position : SV_POSITION;
208			   float4 color : COLOR;
209			};
210			PSInput VSMain(float4 position : POSITION0, float4 color : COLOR0) {
211			   PSInput result;
212			   result.position = position;
213			   result.color = color;
214			   return result;
215			}
216			float4 PSMain(PSInput input) : SV_TARGET {
217			   return input.color;
218			};`
219
220		data_size: uint = len(data)
221
222		compile_flags: u32 = 0
223		when ODIN_DEBUG {
224			compile_flags |= u32(d3dc.D3DCOMPILE.DEBUG)
225			compile_flags |= u32(d3dc.D3DCOMPILE.SKIP_OPTIMIZATION)
226		}
227
228		vs: ^d3d12.IBlob = nil
229		ps: ^d3d12.IBlob = nil
230
231		hr = d3dc.Compile(rawptr(data), data_size, nil, nil, nil, "VSMain", "vs_4_0", compile_flags, 0, &vs, nil)
232		check(hr, "Failed to compile vertex shader")
233
234		hr =d3dc.Compile(rawptr(data), data_size, nil, nil, nil, "PSMain", "ps_4_0", compile_flags, 0, &ps, nil)
235		check(hr, "Failed to compile pixel shader")
236
237		// This layout matches the vertices data defined further down
238		vertex_format: []d3d12.INPUT_ELEMENT_DESC = {
239			{ 
240				SemanticName = "POSITION", 
241				Format = .R32G32B32_FLOAT, 
242				InputSlotClass = .PER_VERTEX_DATA, 
243			},
244			{   
245				SemanticName = "COLOR", 
246				Format = .R32G32B32A32_FLOAT, 
247				AlignedByteOffset = size_of(f32) * 3, 
248				InputSlotClass = .PER_VERTEX_DATA, 
249			},
250		}
251
252		default_blend_state := d3d12.RENDER_TARGET_BLEND_DESC {
253			BlendEnable = false,
254			LogicOpEnable = false,
255
256			SrcBlend = .ONE,
257			DestBlend = .ZERO,
258			BlendOp = .ADD,
259
260			SrcBlendAlpha = .ONE,
261			DestBlendAlpha = .ZERO,
262			BlendOpAlpha = .ADD,
263
264			LogicOp = .NOOP,
265			RenderTargetWriteMask = u8(d3d12.COLOR_WRITE_ENABLE_ALL),
266		}
267
268		pipeline_state_desc := d3d12.GRAPHICS_PIPELINE_STATE_DESC {
269			pRootSignature = root_signature,
270			VS = {
271				pShaderBytecode = vs->GetBufferPointer(),
272				BytecodeLength = vs->GetBufferSize(),
273			},
274			PS = {
275				pShaderBytecode = ps->GetBufferPointer(),
276				BytecodeLength = ps->GetBufferSize(),
277			},
278			StreamOutput = {},
279			BlendState = {
280				AlphaToCoverageEnable = false,
281				IndependentBlendEnable = false,
282				RenderTarget = { 0 = default_blend_state, 1..<7 = {} },
283			},
284			SampleMask = 0xFFFFFFFF,
285			RasterizerState = {
286				FillMode = .SOLID,
287				CullMode = .BACK,
288				FrontCounterClockwise = false,
289				DepthBias = 0,
290				DepthBiasClamp = 0,
291				SlopeScaledDepthBias = 0,
292				DepthClipEnable = true,
293				MultisampleEnable = false,
294				AntialiasedLineEnable = false,
295				ForcedSampleCount = 0,
296				ConservativeRaster = .OFF,
297			},
298			DepthStencilState = {
299				DepthEnable = false,
300				StencilEnable = false,
301			},
302			InputLayout = {
303				pInputElementDescs = &vertex_format[0],
304				NumElements = u32(len(vertex_format)),
305			},
306			PrimitiveTopologyType = .TRIANGLE,
307			NumRenderTargets = 1,
308			RTVFormats = { 0 = .R8G8B8A8_UNORM, 1..<7 = .UNKNOWN },
309			DSVFormat = .UNKNOWN,
310			SampleDesc = {
311				Count = 1,
312				Quality = 0,
313			},
314		}
315		
316		hr = device->CreateGraphicsPipelineState(&pipeline_state_desc, d3d12.IPipelineState_UUID, (^rawptr)(&pipeline))
317		check(hr, "Pipeline creation failed")
318
319		vs->Release()
320		ps->Release()
321	}
322
323	// Create the commandlist that is reused further down.
324	cmdlist: ^d3d12.IGraphicsCommandList
325	hr = device->CreateCommandList(0, .DIRECT, command_allocator, pipeline, d3d12.ICommandList_UUID, (^rawptr)(&cmdlist))
326	check(hr, "Failed to create command list")
327	hr = cmdlist->Close()
328	check(hr, "Failed to close command list")
329
330	vertex_buffer: ^d3d12.IResource
331	vertex_buffer_view: d3d12.VERTEX_BUFFER_VIEW
332
333	{
334		// The position and color data for the triangle's vertices go together per-vertex
335		vertices := [?]f32 {
336			// pos            color
337			 0.0 , 0.5, 0.0,  1,0,0,0,
338			 0.5, -0.5, 0.0,  0,1,0,0,
339			-0.5, -0.5, 0.0,  0,0,1,0,
340		}
341
342		heap_props := d3d12.HEAP_PROPERTIES {
343			Type = .UPLOAD,
344		}
345
346		vertex_buffer_size := len(vertices) * size_of(vertices[0])
347
348		resource_desc := d3d12.RESOURCE_DESC {
349			Dimension = .BUFFER,
350			Alignment = 0,
351			Width = u64(vertex_buffer_size),
352			Height = 1,
353			DepthOrArraySize = 1,
354			MipLevels = 1,
355			Format = .UNKNOWN,
356			SampleDesc = { Count = 1, Quality = 0 },
357			Layout = .ROW_MAJOR,
358			Flags = {},
359		}
360
361		hr = device->CreateCommittedResource(&heap_props, {}, &resource_desc, d3d12.RESOURCE_STATE_GENERIC_READ, nil, d3d12.IResource_UUID, (^rawptr)(&vertex_buffer))
362		check(hr, "Failed creating vertex buffer")
363
364		gpu_data: rawptr
365		read_range: d3d12.RANGE
366
367		hr = vertex_buffer->Map(0, &read_range, &gpu_data)
368		check(hr, "Failed creating verex buffer resource")
369
370		mem.copy(gpu_data, &vertices[0], vertex_buffer_size)
371		vertex_buffer->Unmap(0, nil)
372
373		vertex_buffer_view = d3d12.VERTEX_BUFFER_VIEW {
374			BufferLocation = vertex_buffer->GetGPUVirtualAddress(),
375			StrideInBytes = u32(vertex_buffer_size/3),
376			SizeInBytes = u32(vertex_buffer_size),
377		}
378	}
379
380	// This fence is used to wait for frames to finish
381	fence_value: u64
382	fence: ^d3d12.IFence
383	fence_event: windows.HANDLE
384
385	{
386		hr = device->CreateFence(fence_value, {}, d3d12.IFence_UUID, (^rawptr)(&fence))
387		check(hr, "Failed to create fence")
388		fence_value += 1
389		manual_reset: windows.BOOL = false
390		initial_state: windows.BOOL = false
391		fence_event = windows.CreateEventW(nil, manual_reset, initial_state, nil)
392		if fence_event == nil {
393			fmt.println("Failed to create fence event")
394			return
395		}
396	}
397
398	main_loop: for {
399		for e: sdl.Event; sdl.PollEvent(&e); {
400			#partial switch e.type {
401				case .QUIT:
402					break main_loop
403				case .WINDOWEVENT:
404					// This is equivalent to WM_PAINT in win32 API
405					if e.window.event == .EXPOSED {
406						hr = command_allocator->Reset()
407						check(hr, "Failed resetting command allocator")
408
409						hr = cmdlist->Reset(command_allocator, pipeline)
410						check(hr, "Failed to reset command list")
411
412						viewport := d3d12.VIEWPORT {
413							Width = f32(wx),
414							Height = f32(wy),
415							MinDepth = 0,
416							MaxDepth = 1,
417						}
418
419						scissor_rect := d3d12.RECT {
420							left = 0, right = wx,
421							top = 0, bottom = wy,
422						}
423
424						// This state is reset everytime the cmd list is reset, so we need to rebind it
425						cmdlist->SetGraphicsRootSignature(root_signature)
426						cmdlist->RSSetViewports(1, &viewport)
427						cmdlist->RSSetScissorRects(1, &scissor_rect)
428
429						to_render_target_barrier := d3d12.RESOURCE_BARRIER {
430							Type = .TRANSITION,
431							Flags = {},
432						}
433
434						to_render_target_barrier.Transition = {
435							pResource = targets[frame_index],
436							StateBefore = d3d12.RESOURCE_STATE_PRESENT,
437							StateAfter = {.RENDER_TARGET},
438							Subresource = d3d12.RESOURCE_BARRIER_ALL_SUBRESOURCES,
439						}
440
441						cmdlist->ResourceBarrier(1, &to_render_target_barrier)
442
443						rtv_handle: d3d12.CPU_DESCRIPTOR_HANDLE
444						rtv_descriptor_heap->GetCPUDescriptorHandleForHeapStart(&rtv_handle)
445
446						if (frame_index > 0) {
447							s := device->GetDescriptorHandleIncrementSize(.RTV)
448							rtv_handle.ptr += uint(frame_index * s)
449						}
450
451						cmdlist->OMSetRenderTargets(1, &rtv_handle, false, nil)
452
453						// clear backbuffer
454						clearcolor := [?]f32 { 0.05, 0.05, 0.05, 1.0 }
455						cmdlist->ClearRenderTargetView(rtv_handle, &clearcolor, 0, nil)
456
457						// draw call
458						cmdlist->IASetPrimitiveTopology(.TRIANGLELIST)
459						cmdlist->IASetVertexBuffers(0, 1, &vertex_buffer_view)
460						cmdlist->DrawInstanced(3, 1, 0, 0)
461						
462						to_present_barrier := to_render_target_barrier
463						to_present_barrier.Transition.StateBefore = {.RENDER_TARGET}
464						to_present_barrier.Transition.StateAfter = d3d12.RESOURCE_STATE_PRESENT
465
466						cmdlist->ResourceBarrier(1, &to_present_barrier)
467
468						hr = cmdlist->Close()
469						check(hr, "Failed to close command list")
470
471						// execute
472						cmdlists := [?]^d3d12.IGraphicsCommandList { cmdlist }
473						queue->ExecuteCommandLists(len(cmdlists), (^^d3d12.ICommandList)(&cmdlists[0]))
474
475						// present
476						{
477							flags: dxgi.PRESENT
478							params: dxgi.PRESENT_PARAMETERS
479							hr = swapchain->Present1(1, flags, &params)
480							check(hr, "Present failed")
481						}
482
483						// wait for frame to finish
484						{
485							current_fence_value := fence_value
486
487							hr = queue->Signal(fence, current_fence_value)
488							check(hr, "Failed to signal fence")
489
490							fence_value += 1
491							completed := fence->GetCompletedValue()
492
493							if completed < current_fence_value {
494								hr = fence->SetEventOnCompletion(current_fence_value, fence_event)
495								check(hr, "Failed to set event on completion flag")
496								windows.WaitForSingleObject(fence_event, windows.INFINITE)
497							}
498
499							frame_index = swapchain->GetCurrentBackBufferIndex()
500						}
501					}
502			}
503		}
504	}
505}

Declarations Used 77