Skip to content

Instantly share code, notes, and snippets.

@barcharcraz
Created August 29, 2015 02:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barcharcraz/d2a8c47b00e159935362 to your computer and use it in GitHub Desktop.
Save barcharcraz/d2a8c47b00e159935362 to your computer and use it in GitHub Desktop.
import windows
import dxgi
import d3d12
import d3dcompiler
const winClass = "directNimrod"
#rendering constants
const width = 800
const height = 600
#direct3D globals
var debugLayer: ptr ID3D12Debug
var device: ptr ID3D12Device
var commandQueue: ptr ID3D12CommandQueue
var swapChain: ptr IDXGISwapChain3
var factory: ptr IDXGIFactory4
var rtvHeap: ptr ID3D12DescriptorHeap
var rtvDescriptorSize: uint32
var renderTargets: array[0..1, ID3D12Resource]
var commandAllocator: ID3D12CommandAllocator
var rootSignature: ptr ID3D12RootSignature
var pipelineState: ptr ID3D12PipelineState
var commandList: ptr ID3D12GraphicsCommandList
var vertexBuffer: ptr ID3D12Resource
var vertexBufferView: D3D12_VERTEX_BUFFER_VIEW
var fence: ptr ID3D12Fence
var fenceEvent: HANDLE
var fenceValue: uint64
var frameIndex: uint32
var viewport: D3D12_VIEWPORT
var scissorRect: D3D12_RECT
type Vertex = object
position: array[3, float32]
color: array[4, float32]
proc WaitForPreviousFrame() =
let fenceV = fenceValue
hr = commandQueue.lpVtbl.Signal(commandQueue, fence, fenceV)
if hr != S_OK: quit("signal failed")
inc(fenceValue)
if fence.lpVtbl.GetCompletedValue(fence) < fenceV:
hr = fence.lpVtbl.SetEventOnCompletion(fence, fenceV, fenceEvent)
WaitForSingleObject(fenceEvent, high(uint32))
frameIndex = swapChain.lpVtbl.GetCurrentBackBufferIndex(swapChain)
proc InitializePipeline(hwnd: HWND) =
viewport.Width = float32(width)
viewport.Height = float32(height)
viewport.MaxDepth = 1.0'f32
scissorRect.right = int32(width)
scissorRect.botton = int32(height)
var result: HRESULT
result = D3D12GetDebugInterface(addr IID_ID3D12Debug, cast[ptr pointer](addr debugLayer))
if result != S_OK: quit("Could not get debug interface")
result = CreateDXGIFactory1(addr IID_IDXGIFactory4, cast[ptr pointer](addr factory))
if result != S_OK: quit("Could not create factory interface")
result = D3D12CreateDevice(nil, D3D_FEATURE_LEVEL_12_1, addr IID_ID3D12Device, cast[ptr pointer](addr device))
if result != S_OK: quit("could not create the device")
#create the direct3D command queue
D3D12_COMMAND_QUEUE_DESC queueDesc
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT
result = device.lpVtbl.CreateCommandQueue(device, addr IID_ID3D12CommandQueue, cast[ptr pointer](addr commandQueue))
if result != S_OK: quit("could not create the command queue")
tmpSwapChain: ptr IDXGISwapChain
DXGI_SWAP_CHAIN_DESC swapChainDesc
swapChainDesc.BufferCount = 2
swapChainDesc.BufferDesc.Width = width
swapChainDesc.BufferDesc.Height = height
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD
swapChainDesc.OutputWindow = hwnd
swapChainDesc.SampleDesc.Count = 1
swapChainDesc.Windowed = true
result = factory.lpVtbl.CreateSwapChain(commandQueue.lpVtbl.Get(commandQueue), addr swapChainDesc, addr tmpSwapChain)
if result != S_OK: quit("could not create the swap chain")
result = tmpSwapChain.lpVtbl.QueryInterface(tmpSwapChain, addr IID_IDXGISwapChain3, cast[ptr pointer](addr swapChain))
if result != S_OK: quit("could not QI to SwapChain3")
tmpSwapChain.lpVtbl.Release(tmpSwapChain)
tmpSwapChain = nil
#create descriptor heaps
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc
rtvHeapDesc.NumDescriptors = 2
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV
rtvHeapDesc.Flgas = D3D12_DESCRIPTOR_HEAP_FLAG_NONE
result = device.lpVtbl.CreateDescirptorHeap(device, addr rtvHeapDesc, addr IID_ID3D12DescriptorHeap, cast[ptr pointer](addr rtvHeap))
if result != S_OK: quit("could not create descriptor heap")
rtvDescriptorSize = device.lpVtbl.GetDescriptorHandleIncrementSize(device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV)
#create frame resources
var rtvHandle = rtvHeap.lpVtbl.GetCPUDescriptorHandleForHeapStart(rtvHeap)
for n in 0..1:
result = swapChain.lpVtbl.GetBuffer(swapChain, n, addr IID_ID3D12Resource, cast[ptr pointer](addr renderTargets[n]))
if result != S_OK: quit("could not get render target")
device.lpVtbl.CreateRenderTargetView(device, renderTargets[n], nil, rtvHandle)
rtvHandle.ptr += rtvDescriptorSize
result = device.lpVtbl.CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_DIRECT, addr IID_ID3D12CommandAllocator, cast[ptr pointer](addr commandAllocator))
if result != S_OK: quit("could not create command commandAllocator")
#asset loading
proc LoadAssets() =
var rootSignatureDesc: D3D12_ROOT_SIGNATURE_DESC
rootSignatureDesc.NumParameters = 0
rootSignatureDesc.pParameters = nil
rootSignatureDesc.NumStaticSamplers = 0
rootSignatureDesc.pStaticSamplers = nil
rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
var signature: ptr ID3DBlob
var error: ptr ID3DBlob
var hr: HRESULT
hr = D3D12SerializeRootSignature(addr rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, addr signature, addr error)
if hr != S_OK: quit("could not serialize the root signature")
hr = device.lpVtbl.CreateRootSignature(device, 0,
signature.lpVtbl.GetBufferPointer(signature),
signature.lpVtbl.GetBufferSize(signature),
addr IID_ID3D12RootSignature, cast[ptr pointer](addr rootSignature))
if hr != S_OK: quit("could not create root signature")
#create the pipeline state
var vertexShader: ptr ID3DBlob
var pixelShader: ptr ID3DBlob
uint32 compilerFlags = D3DCOMPILE_DEBUG or D3DCOMPILE_SKIP_OPTIMIZATION
hr = D3DCompileFromFile("dx12Shaders.hlsl", nil, nil, "VSMain", "vs_5_0", compilerFlags, 0, addr vertexShader, nil)
if hr != S_OK: quit("could not compile vertex shader")
hr = D3DCompileFromFile("dx12Shaders.hlsl", nil, nil, "PSMain", "ps_5_0", compilerFlags, 0, addr pixelShader, nil)
if hr != S_OK: quit("could not compile pixel shader")
# define the input layout
var inputElementDesc: array[0..1, D3D12_INPUT_ELEMENT_DESC]
inputElementDesc[0].SemanticName = "POSITION"
inputElementDesc[0].SemanticIndex = 0
inputElementDesc[0].Format = DXGI_FORMAT_R32G32B32_FLOAT
inputElementDesc[0].InputSlot = 0
inputElementDesc[0].AlignedByteOffset = 0
inputElementDesc[0].InputSlotClass = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA
inputElementDesc[0].InstanceDataStepRate = 0
inputElementDesc[1].SemanticName = "COLOR"
inputElementDesc[1].SemanticIndex = 0
inputElementDesc[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT
inputElementDesc[1].InputSlot = 0
inputElementDesc[1].AlignedByteOffset = 12
inputElementDesc[1].InputSlotClass= = D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA
inputElementDesc[1].InstanceDataStepRate = 0
#describe and create the PSO
var defaultRTBlendDesc: D3D12_RENDER_TARGET_BLEND_DESC
defaultRTBlendDesc.BlendEnable = false
defaultRTBlendDesc.LogicOpEnable = false
defaultRTBlendDesc.SrcBlend = D3D12_BLEND_ONE
defaultRTBlendDesc.DestBlend = D3D12_BLEND_ZERO
defaultRTBlendDesc.BlendOp = D3D12_BLEND_OP_ADD
defaultRTBlendDesc.SrcBlendAlpha = D3D12_BLEND_ONE
defaultRTBlendDesc.DestBlendAlpha = D3D12_BLEND_ZERO
defaultRTBlendDesc.BlendOpAlpha = D3D12_BLEND_OP_ADD
defaultRTBlendDesc.LogicOp = D3D12_LOGIC_OP_NOOP
defaultRTBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL
var psoDesc: D3D12_GRAPHICS_PIPELINE_STATE_DESC
psoDesc.InputLayout.pInputElementDescs = addr inputElementDesc[0]
psoDesc.InputLayout.NumElements = len(inputElementDesc)
psoDesc.pRootSignature = rootSignature
psoDesc.VS.pShaderBytecode = vertexShader.lpVtbl.GetBufferPointer(vertexShader)
psoDesc.VS.BytecodeLength = vertexShader.lpVtbl.GetBufferSize(vertexShader)
psoDesc.PS.pShaderBytecode = pixelShader.lpVtbl.GetBufferPointer(pixelShader)
psoDesc.PS.BytecodeLength = pixelShader.lpVtbl.GetBufferSize(pixelShader)
psoDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID
psoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_BACK
psoDesc.RasterizerState.FrontCounterClockwise = false
psoDesc.RasterizerState.DepthBias = D3D12_DEFAULT_DEPTH_BIAS
psoDesc.RasterizerState.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS
psoDesc.RasterizerState.DepthClipEnable = true
psoDesc.RasterizerState.MultisampleEnable = false
psoDesc.RasterizerState.AntialiasedLineEnable = false
psoDesc.RasterizerState.ForcedSampleCount = 0
psoDesc.RasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF
psoDesc.BlendState.AlphaToCoverageEnable = false
psoDesc.BlendState.IndependentBlendEnable = false
for i in 0..D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT-1:
psoDesc.RenderTarget[i] = defaultRTBlendDesc
psoDesc.DepthStencilState.DepthEnable = false
psoDesc.DepthStencilState.StencilEnable = false
psoDesc.SampleMask = high(uint32)
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TRIANGLE
psoDesc.NumRenderTargets = 1
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM
psoDesc.SampleDesc.Count = 1
hr = device.lpVtbl.CreateGraphicsPipelineState(device, addr psoDesc, addr IID_ID3D12PipelineState, cast[ptr pointer](addr pipelineState))
if hr != S_OK: quit("could not create pipeline state")
#create the command list
hr = device.lpVtbl.CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator, pipelineState, addr IID_ID3D12GraphicsCommandList, cast[ptr pointer](addr commandList))
if hr != S_OK: quit("could not create the command list")
#command list was created in the open state but the main loop
#expects it to be closed, so we close it now
hr = commandList.lpVtbl.Close(commandList)
if hr != S_OK: quit("could not close command list")
#create the vertex buffer
#define the geometry for the triangle
var triangleVertices = [
Vertex(position: [0.0, 0.25, 0.0], color: [1.0, 0.0, 0.0, 1.0]),
Vertex(position: [0.25, -0.25, 0.0], color: [0.0, 1.0, 0.0, 1.0]),
Vertex(position: [-0.25, -0.25, 0.0], color: [0.0, 0.0, 1.0, 1.0])
]
var vertexBufferSize: uint32 = sizeof(triangleVertices)
#note: using upload heaps to transfer static data like vert buffers
# is not recommended. Every time the GPU needs it the upload heap
# will be marshalled over. Please read up on Default heap usage.
# un upload heap is used here from simplicity and vecause there
# are so few verts
var defaultHeapProps: D3D12_HEAP_PROPERTIES
defaultHeapProps.Type = D3D12_HEAP_TYPE_UPLOAD
defaultHeapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN
defaultHeapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN
defaultHeapProps.CreationNodeMask = 1
defaultHeapProps.VisibleNodeMask = 1
var bufferProps: D3D12_RESOURCE_DESC
bufferProps.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER
bufferProps.Alignment = 0
bufferProps.Width = vertexBufferSize
bufferProps.Height = 1
bufferProps.DepthOrArraySize = 1
bufferProps.MipLevels = 1
bufferProps.Format = DXGI_FORMAT_UNKNOWN
bufferProps.SampleDesc.Count = 1
bufferProps.SampleDesc.Quality = 0
bufferProps.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR
bufferProps.Flags = D3D12_RESOURCE_FLAG_NONE
hr = device.lpVtbl.CreateCommittedResource(device,
addr defaultHeapProps,
D3D12_HEAP_FLAG_NONE,
addr bufferProps,
D3D12_RESOURCE_STATE_GENERIC_READ,
nil,
addr IID_ID3D12Resource,
cast[ptr pointer](addr vertexBuffer)
)
if hr != S_OK: quit("could not create vertex buffer")
#copy the triangle data to the vertex buffer
var dataBegin: pointer
hr = vertexBuffer.lpVtbl.Map(vertexBuffer, 0, nil, cast[ptr pointer](addr dataBegin))
if hr != S_OK: quit("could not map buffer")
copyMem(dataBegin, addr triangleVertices[0], sizeof(triangleVertices))
vertexBuffer.lpVtbl.Unmap(vertexBuffer, 0, nil)
#initialize the vertex buffer view
vertexBufferView.BufferLocation = vertexBuffer.lpVtbl.GetGPUVirtualAddress(vertexBuffer)
vertexBufferView.StrideInBytes = sizeof(Vertex)
vertexBufferView.SizeInBytes = vertexBufferSize
#create synchronization objects
hr = device.lpVtbl.CreateFence(device, 0, D3D12_FENCE_FLAG_NONE, addr IID_ID3D12Fence, cast[ptr pointer](addr fence))
if hr != S_OK: quit("could not create fence")
fenceEvent = CreateEvent(nil, false, false, nil)
if fenceEvent == nil:
quit("could not create event error: " & $GetLastError())
WaitForPreviousFrame()
proc PopulateCommandList() =
var hr: HRESULT
# Command list allocators can only be reset when the associated
# command lists have finished execution on the GPU; apps should use
# fences to determine GPU execution progress
hr = commandAllocator.lpVtbl.Reset(commandAllocator)
if hr != S_OK: quit("could not reset command allocator")
#however when ExecuteCommandList() is called on a particular command list
# that command list can then be reset at any time and must be before re-recording
hr = commandList.lpVtbl.Reset(commandList, pipelineState)
#set the nessassary state
commandList.lpVtbl.SetGraphicsRootSignature(commandList, rootSignature)
commandList.lpVtbl.RSSetViewports(commandList, 1, addr viewport)
commandList.lpVtbl.RSSetScissorRects(commandList, 1, addr scissorRect)
#indicate the back buffer will be used as the render target
var barrier: D3D12_RESOURCE_BARRIER
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE
barrier.Transition.pResource = renderTargets[frameIndex]
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES
commandList.lpVtbl.ResourceBarrier(commandList, 1, addr barrier)
var descriptorHandle = D3D12_CPU_DESCRIPTOR_HANDLE
descriptorHandle.ptr = rtvHeap.lpVtbl.GetCPUDescriptorHandleForHeapStart(rtvHeap).ptr
descriptorHandle.ptr += frameIndex * rtvDescriptorSize
commandList.lpVtbl.OMSetRenderTargets(1, addr rtvHandle, false, nil)
#record commands
var clearColor = [0.0'f32, 0.2'f32, 0.4'f32, 1.0'f32]
commandList.lpVtbl.ClearRenderTargetView(commandList, rtvHandle, addr clearColor[0], 0, nil)
commandList.lpVtbl.IASetPrimitiveTopology(commandList, D3D_PRIMITIVE_TOPOLOGY_TRAINGLELIST)
commandList.lpVtbl.IASetVertexBuffers(commandList, 0, 1, addr vertexBufferView)
commandList.lpVtbl.DrawInstanced(commandList, 3, 1, 0, 0)
barrier.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET
barrier.StateAfter = D3D12_RESOURCE_STATE_PRESENT
commandList.ResourceBarrier(commandList, 1, addr barrier)
hr = commandList.lpVtbl.Close(commandList)
if hr != S_OK: quit("could not close command list")
# windows API related things
proc WndProc(wnd: HWND, message: int32, wp: WPARAM, lp: LPARAM): LRESULT {.stdcall.} =
var
wmId: cint
wmEvent: cint
ps: PAINTSTRUCT
hdcr: HDC
case message:
of WM_DESTROY:
PostQuitMessage(0)
else:
return windows.DefWindowProc(wnd, message, wp, lp)
return 0.LRESULT
proc main() =
var wcex: WNDCLASSEX
wcex.cbSize = sizeof(WNDCLASSEX).int32
wcex.lpfnWndProc = WndProc
wcex.style = CS_HREDRAW or CS_VREDRAW
wcex.cbClsExtra = 0
wcex.cbWndExtra = 0
wcex.hinstance = GetModuleHandle(nil)
wcex.hIcon = LoadIcon(0, IDI_APPLICATION)
wcex.hCursor = LoadCursor(0, IDC_ARROW)
wcex.hbrBackground = cast[HBRUSH](COLOR_WINDOW+1)
wcex.lpszMenuName = nil
wcex.lpszClassName = winClass
wcex.hIconSm = 0
echo(sizeof(WNDCLASSEX))
var class = RegisterClassEx(addr wcex)
if class == 0:
quit("could not create win class " & $GetLastError())
var wnd: HWND
wnd = CreateWindow(winClass, winClass, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0,
0, GetModuleHandle(nil), nil)
if wnd == 0:
quit("failed to make hwnd")
var hr: HRESULT
InitializePipeline()
LoadAssets()
discard ShowWindow(wnd, 10)
discard UpdateWindow(wnd)
var message: MSG
while message.message != WM_QUIT:
if PeekMessage(addr message, cast[HWND](nil), 0, 0, PM_REMOVE) > 0:
discard TranslateMessage(addr message)
discard DispatchMessage(addr message)
PopulateCommandList()
var ppCommandLists: array[1, ptr ID3D12CommandList] = [commandList]
commandQueue.lpVtbl.ExecuteCommandLists(commandQueue, 1, addr ppCommandLists[0])
hr = swapChain.lpVtbl.Present(swapChain, 1, 0)
if hr != S_OK: quit("could not present frame")
WaitForPreviousFrame()
#cleanup
WaitForPreviousFrame()
CloseHandle(fenceEvent)
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment