hello_triangle.asm

src/1.getting_started/2.1.hello_triangle/hello_triangle.asm · 358 lines

;---------------------------------------------------------------------------------------------------
; 2.1 Hello triangle
;---------------------------------------------------------------------------------------------------
; Getting three points on screen, with every step written out:
;
;   1. compile a vertex shader and a pixel shader from HLSL
;   2. put the vertices in a GPU buffer, and describe one vertex's layout
;   3. each frame: bind it all, and draw
;
; The vertex shader runs once per vertex and says where it lands in clip space; the pixel shader
; runs once per covered pixel and says its colour. GL needs a VBO and a VAO; D3D needs the buffer
; and an input layout, which is checked against the vertex shader when it is created.
;
; The left shape is three vertices drawn in order. The right is four vertices and six indices: two
; triangles sharing a diagonal, with no vertex stored twice (LearnOpenGL's 2.2
; hello_triangle_indexed). Tab toggles wireframe, which shows the diagonal.
include rt.inc
include d3d11.inc
include macros.asm
include cstr.asm
include print.asm
include saha.asm
include learn\filesystem.asm

externdef __imp_SetProcessDPIAware:PPROC
SetProcessDPIAware equ <__imp_SetProcessDPIAware>

D3D_COMPILE_STANDARD_FILE_INCLUDE equ 1

;---------------------------------------------------------------------------------------------------
.data
;---------------------------------------------------------------------------------------------------
; settings
scr_width dword 1280
scr_height dword 720

; Positions straight in clip space: x and y from -1 to 1, the centre is 0.
; Counter-clockwise, which these lessons treat as the front.
align 4
triangle_vertices real4 -0.9, -0.5, 0.0
                  real4 -0.1, -0.5, 0.0
                  real4 -0.5,  0.5, 0.0
triangle_vertices_size equ $ - triangle_vertices

rectangle_vertices real4 0.1, -0.5, 0.0     ; bottom left
                   real4 0.9, -0.5, 0.0     ; bottom right
                   real4 0.9,  0.5, 0.0     ; top right
                   real4 0.1,  0.5, 0.0     ; top left
rectangle_vertices_size equ $ - rectangle_vertices

rectangle_indices dword 0, 1, 2, 0, 2, 3
rectangle_indices_size equ $ - rectangle_indices

; One attribute: three floats, read as POSITION by the vertex shader.
semantic_position byte "POSITION", 0
align 8
vertex_layout D3D11_INPUT_ELEMENT_DESC <offset semantic_position, 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0>

; keys pressed since they were last read, for toggles
keys_pressed byte 256 dup(0)

align 8
window qword 0
swap_chain qword 0
device qword 0
context qword 0
backbuffer qword 0
backbuffer_rtv qword 0
raster_state qword 0
wire_state qword 0
vertex_shader qword 0
pixel_shader qword 0
input_layout qword 0
triangle_vb qword 0
rectangle_vb qword 0
rectangle_ib qword 0
stride dword 12
offset_ dword 0
wireframe dword 0

window_class_name byte "LearnD3D11", 0

;---------------------------------------------------------------------------------------------------
.code
;---------------------------------------------------------------------------------------------------
; win32: every event for the window (keys, mouse, closing) arrives here
; ---------------------------------------------------------------------
align 16
window_proc proc hwnd:qword, msg:qword, wparam:qword, lparam:qword
    mov eax, dword ptr msg
    cmp eax, WM_DESTROY
    jne not_destroy
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    not_destroy:
    ; Bit 30 of lparam is set when the key was already down: a repeat.
    cmp eax, WM_KEYDOWN
    jne default
    mov rax, lparam
    bt rax, 30
    jc default
    mov rax, wparam
    cmp rax, 256
    jae default
        lea rcx, keys_pressed
        mov byte ptr [rcx + rax], 1
        xor eax, eax
        ret
    default:
    invoke DefWindowProcA, hwnd, msg, wparam, lparam
    ret
window_proc endp

; rcx: key -> rax: 1 if it was pressed since the last call
align 16
key_pressed proc
    and ecx, 0FFh
    lea rdx, keys_pressed
    movzx eax, byte ptr [rdx + rcx]
    mov byte ptr [rdx + rcx], 0
    ret
key_pressed endp

; process all input: ask Windows whether relevant keys are down this frame and react accordingly
; ----------------------------------------------------------------------------------------------
align 16
process_input proc
    invoke GetAsyncKeyState, VK_ESCAPE
    test ax, ax
    jns done
        invoke DestroyWindow, window
    done:
    ret
process_input endp

; rcx: path, rdx: source, r8: size, r9: entry point, [rsp+32]: target -> rax: the compiled code
align 16
compile_shader proc path:qword, source:qword, source_size:qword, entry:qword, target:qword
    local bytecode:qword, errors:qword
    mov bytecode, 0
    mov errors, 0
    invoke D3DCompile, source, source_size, path, 0, D3D_COMPILE_STANDARD_FILE_INCLUDE, entry, target, 0, 0, ADDR bytecode, ADDR errors
    test eax, eax
    jns compiled
        vcall errors, ID3D10Blob_GetBufferPointer
        print "ERROR::SHADER::COMPILATION_FAILED\n%s\n", rax
        invoke ExitProcess, 1
    compiled:
    mov rax, bytecode
    ret
compile_shader endp

; rcx: bind flags, rdx: data, r8: size -> rax: the buffer
;
; An immutable buffer: filled once at creation, never written by the CPU again.
align 16
create_buffer proc bind:qword, data:qword, data_size:qword
    local desc:D3D11_BUFFER_DESC, init:D3D11_SUBRESOURCE_DATA, buffer:qword
    invoke mem_zero, ADDR desc, sizeof D3D11_BUFFER_DESC
    mov eax, dword ptr data_size
    mov desc.ByteWidth, eax
    mov desc.Usage, D3D11_USAGE_IMMUTABLE
    mov eax, dword ptr bind
    mov desc.BindFlags, eax
    invoke mem_zero, ADDR init, sizeof D3D11_SUBRESOURCE_DATA
    mov rax, data
    mov init.pSysMem, rax
    vcall device, ID3D11Device_CreateBuffer, ADDR desc, ADDR init, ADDR buffer
    mov rax, buffer
    ret
create_buffer endp

align 16
main proc
    local wc:WNDCLASSEX, rect:RECT, style:dword
    local swap_desc:DXGI_SWAP_CHAIN_DESC, feature_level:dword, msg:MSG, running:dword
    local raster_desc:D3D11_RASTERIZER_DESC, viewport:D3D11_VIEWPORT
    local path:qword, source:qword, source_size:qword, vs_code:qword, ps_code:qword
    USING rbx, rsi
    SaveRegs

    ; win32: register a window class and create the window
    ; ----------------------------------------------------
    call SetProcessDPIAware
    invoke mem_zero, ADDR wc, sizeof WNDCLASSEX
    mov wc.cbSize, sizeof WNDCLASSEX
    lea rax, window_proc
    mov wc.lpfnWndProc, rax
    invoke GetModuleHandleA, 0
    mov wc.hInstance, rax
    invoke LoadCursorA, 0, IDC_ARROW
    mov wc.hCursor, rax
    lea rax, window_class_name
    mov wc.lpszClassName, rax
    invoke RegisterClassExA, ADDR wc
    ; A fixed size: the swap chain and every render target keep the size they were created with,
    ; so nothing needs rebuilding on resize.
    mov style, WS_OVERLAPPEDWINDOW and not (WS_THICKFRAME or WS_MAXIMIZEBOX)
    mov rect.left, 0
    mov rect.top, 0
    mov eax, scr_width
    mov rect.right, eax
    mov eax, scr_height
    mov rect.bottom, eax
    invoke AdjustWindowRect, ADDR rect, style, 0
    or style, WS_VISIBLE
    mov eax, rect.right
    sub eax, rect.left
    mov r10d, rect.bottom
    sub r10d, rect.top
    invoke CreateWindowExA, 0, ADDR window_class_name, "LearnD3D11", style, CW_USEDEFAULT, CW_USEDEFAULT, rax, r10, 0, 0, wc.hInstance, 0
    test rax, rax
    jnz window_created
        print "Failed to create the window\n"
        invoke ExitProcess, 1
    window_created:
    mov window, rax

    ; d3d11: create the device, its immediate context and a swap chain
    ; ----------------------------------------------------------------
    invoke mem_zero, ADDR swap_desc, sizeof DXGI_SWAP_CHAIN_DESC
    mov eax, scr_width
    mov swap_desc.BufferDesc._Width, eax
    mov eax, scr_height
    mov swap_desc.BufferDesc.Height, eax
    mov swap_desc.BufferDesc.Format, DXGI_FORMAT_R8G8B8A8_UNORM
    mov swap_desc.SampleDesc.Count, 1
    mov swap_desc.BufferUsage, DXGI_USAGE_RENDER_TARGET_OUTPUT
    mov swap_desc.BufferCount, 2
    mov rax, window
    mov swap_desc.OutputWindow, rax
    mov swap_desc.Windowed, 1
    mov swap_desc.SwapEffect, DXGI_SWAP_EFFECT_FLIP_DISCARD
    mov feature_level, D3D_FEATURE_LEVEL_11_0
    invoke D3D11CreateDeviceAndSwapChain, 0, D3D_DRIVER_TYPE_HARDWARE, 0, 0, ADDR feature_level, 1, D3D11_SDK_VERSION, \
                                          ADDR swap_desc, ADDR swap_chain, ADDR device, 0, ADDR context
    test eax, eax
    jns device_created
        print "Failed to create the D3D11 device\n"
        invoke ExitProcess, 1
    device_created:
    vcall swap_chain, IDXGISwapChain_GetBuffer, 0, ADDR IID_ID3D11Texture2D, ADDR backbuffer
    vcall device, ID3D11Device_CreateRenderTargetView, backbuffer, 0, ADDR backbuffer_rtv

    ; configure global d3d11 state
    ; ----------------------------
    ; GL's conventions rather than D3D's: counter-clockwise triangles face the viewer, and nothing
    ; is culled.
    invoke mem_zero, ADDR raster_desc, sizeof D3D11_RASTERIZER_DESC
    mov raster_desc.FillMode, D3D11_FILL_SOLID
    mov raster_desc.CullMode, D3D11_CULL_NONE
    mov raster_desc.FrontCounterClockwise, 1
    mov raster_desc.DepthClipEnable, 1
    vcall device, ID3D11Device_CreateRasterizerState, ADDR raster_desc, ADDR raster_state
    vcall context, ID3D11DeviceContext_RSSetState, raster_state
    invoke mem_zero, ADDR viewport, sizeof D3D11_VIEWPORT
    cvtsi2ss xmm0, scr_width
    movss viewport._Width, xmm0
    cvtsi2ss xmm0, scr_height
    movss viewport.Height, xmm0
    storef viewport.MaxDepth, 1.0

    ; build and compile our shader program
    ; ------------------------------------
    invoke filesystem_path, "src/1.getting_started/2.1.hello_triangle/hello_triangle.hlsl"
    mov path, rax
    invoke filesystem_read, path, ADDR source_size
    mov source, rax
    invoke compile_shader, path, source, source_size, "vs_main", "vs_5_0"
    mov vs_code, rax
    invoke compile_shader, path, source, source_size, "ps_main", "ps_5_0"
    mov ps_code, rax
    vcall vs_code, ID3D10Blob_GetBufferPointer
    mov rbx, rax
    vcall vs_code, ID3D10Blob_GetBufferSize
    mov rsi, rax
    vcall device, ID3D11Device_CreateVertexShader, rbx, rsi, 0, ADDR vertex_shader
    ; The layout is created against the compiled vertex shader, which is the one place D3D checks
    ; that the two agree.
    vcall device, ID3D11Device_CreateInputLayout, ADDR vertex_layout, 1, rbx, rsi, ADDR input_layout
    vcall ps_code, ID3D10Blob_GetBufferPointer
    mov rbx, rax
    vcall ps_code, ID3D10Blob_GetBufferSize
    vcall device, ID3D11Device_CreatePixelShader, rbx, rax, 0, ADDR pixel_shader

    ; set up vertex data (and buffer(s))
    ; ----------------------------------
    invoke create_buffer, D3D11_BIND_VERTEX_BUFFER, ADDR triangle_vertices, triangle_vertices_size
    mov triangle_vb, rax
    invoke create_buffer, D3D11_BIND_VERTEX_BUFFER, ADDR rectangle_vertices, rectangle_vertices_size
    mov rectangle_vb, rax
    invoke create_buffer, D3D11_BIND_INDEX_BUFFER, ADDR rectangle_indices, rectangle_indices_size
    mov rectangle_ib, rax

    mov raster_desc.FillMode, D3D11_FILL_WIREFRAME
    vcall device, ID3D11Device_CreateRasterizerState, ADDR raster_desc, ADDR wire_state

    ; render loop
    ; -----------
    mov running, 1
    render_loop:
        pump:
            invoke PeekMessageA, ADDR msg, 0, 0, 0, PM_REMOVE
            test eax, eax
            jz pumped
            cmp dword ptr msg.message, WM_QUIT
            jne not_quit
                mov running, 0
            not_quit:
            invoke TranslateMessage, ADDR msg
            invoke DispatchMessageA, ADDR msg
            jmp pump
        pumped:
        cmp running, 0
        je render_loop_done

        ; input
        ; -----
        call process_input
        invoke key_pressed, VK_TAB
        xor wireframe, eax

        ; render
        ; ------
        vcall context, ID3D11DeviceContext_OMSetRenderTargets, 1, ADDR backbuffer_rtv, 0
        vcall context, ID3D11DeviceContext_RSSetViewports, 1, ADDR viewport
        vcall context, ID3D11DeviceContext_ClearRenderTargetView, backbuffer_rtv, floats(0.2, 0.3, 0.3, 1.0)

        mov rax, raster_state
        cmp wireframe, 0
        je solid
            mov rax, wire_state
        solid:
        vcall context, ID3D11DeviceContext_RSSetState, rax
        vcall context, ID3D11DeviceContext_IASetInputLayout, input_layout
        vcall context, ID3D11DeviceContext_IASetPrimitiveTopology, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST
        vcall context, ID3D11DeviceContext_VSSetShader, vertex_shader, 0, 0
        vcall context, ID3D11DeviceContext_PSSetShader, pixel_shader, 0, 0

        vcall context, ID3D11DeviceContext_IASetVertexBuffers, 0, 1, ADDR triangle_vb, ADDR stride, ADDR offset_
        vcall context, ID3D11DeviceContext_Draw, 3, 0

        vcall context, ID3D11DeviceContext_IASetVertexBuffers, 0, 1, ADDR rectangle_vb, ADDR stride, ADDR offset_
        vcall context, ID3D11DeviceContext_IASetIndexBuffer, rectangle_ib, DXGI_FORMAT_R32_UINT, 0
        vcall context, ID3D11DeviceContext_DrawIndexed, 6, 0, 0

        ; dxgi: show the image just drawn, waiting for the next vertical blank
        ; --------------------------------------------------------------------
        vcall swap_chain, IDXGISwapChain_Present, 1, 0
        jmp render_loop
    render_loop_done:

    RestoreRegs
    invoke ExitProcess, 0
main endp

end
Part of masm64-dx11, LearnOpenGL's lessons rewritten for Direct3D 11 in x64 assembly. Licensed CC BY-NC 4.0, after LearnOpenGL by Joey de Vries.