hello_window.asm

src/1.getting_started/1.1.hello_window/hello_window.asm · 184 lines

;---------------------------------------------------------------------------------------------------
; 1.1 Hello window
;---------------------------------------------------------------------------------------------------
; A window, a D3D11 device, and a colour every frame: what GLFW and a GL context give LearnOpenGL,
; done by hand because on Windows it is only a page.
;
;   1. register a window class, naming the procedure that receives the window's events, and
;      create the window
;   2. create the device (which makes resources), its immediate context (which records drawing)
;      and a swap chain (the images that take turns being shown and drawn into)
;   3. a render target view of the swap chain's back buffer, so it can be cleared and drawn into
;   4. loop: hand window events to window_proc, clear, present
;
; Everything is in this one file, which is the whole program: ml64 assembles it, and the includes
; are pulled in as text. From here on the conventions are:
;
;   * Win64 calling: arguments in rcx, rdx, r8, r9, then the stack; results in rax (or xmm0).
;     rbx, rsi, rdi, r12-r15 and xmm6-xmm15 survive calls, everything else may not.
;   * `invoke Function, args` makes such a call; masm64's prologue gives every proc a stack frame
;     with room for the arguments of the calls it makes.
;   * `vcall object, Interface_Method, args` calls a COM method (macros.asm). D3D11 is all COM.
;   * names that MASM reserves get an underscore: a struct's `Width` is `_Width`.
include rt.inc
include d3d11.inc
include macros.asm
include cstr.asm
include print.asm

externdef __imp_SetProcessDPIAware:PPROC
SetProcessDPIAware equ <__imp_SetProcessDPIAware>

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

align 8
window qword 0
swap_chain qword 0              ; IDXGISwapChain
device qword 0                  ; ID3D11Device
context qword 0                 ; ID3D11DeviceContext
backbuffer qword 0              ; ID3D11Texture2D
backbuffer_rtv qword 0          ; ID3D11RenderTargetView

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
    cmp dword ptr msg, WM_DESTROY
    jne default
        invoke PostQuitMessage, 0
        xor eax, eax
        ret
    default:
    invoke DefWindowProcA, hwnd, msg, wparam, lparam
    ret
window_proc endp

; process all input: ask Windows whether relevant keys are down this frame and react accordingly
; ----------------------------------------------------------------------------------------------
; The top bit of GetAsyncKeyState is set while a key is held, which makes the SHORT negative.
align 16
process_input proc
    invoke GetAsyncKeyState, VK_ESCAPE
    test ax, ax
    jns done
        invoke DestroyWindow, window
    done:
    ret
process_input 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

    ; 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. The size asked for is the inside of the window, and
    ; AdjustWindowRect adds the borders.
    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

    ; 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

        ; render
        ; ------
        vcall context, ID3D11DeviceContext_ClearRenderTargetView, backbuffer_rtv, floats(0.2, 0.3, 0.3, 1.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:

    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.