Search code examples
winapiassemblyx86-64gdifasm

fasm x64 windows gdi programming struggles - call to stretchdibits not painting screen as expected


I have a simple fasm program, in this program I get some zeroed memory from windows through VirtualAlloc. I then have a procedure where I simply set up the parameters and make a call to StretchDIBits passing a pointer to the empty memory buffer. I therefore expect the screen should be drawn black. This however is not the case, and I can't for the life of me figure out why.

Below is the code.

format PE64 GUI 
entry main 

include 'C:\Users\bmowo\Desktop\Tools\fasm\INCLUDE\win64a.inc'
include '.\main_data.asm'

section '.text' code readable executable 
main:
sub rsp,8 ;alignment

invoke  GetModuleHandle,0
mov [WindowClass.hInstance], rax
invoke LoadIcon,0,IDI_APPLICATION
mov [WindowClass.hIcon],rax
mov [WindowClass.hIconSm],rax
invoke LoadCursor,0,IDC_ARROW
mov [WindowClass.hCursor],rax

mov rax, CS_OWNDC or CS_HREDRAW or CS_VREDRAW
mov [WindowClass.style], eax

invoke  RegisterClassExA,WindowClass
test rax, rax
jz exit 

invoke CreateWindowExA,0,WindowClassName,WindowTitle, WS_VISIBLE+WS_OVERLAPPEDWINDOW,\
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0,0,[WindowClass.hInstance],0

mov [WindowHandle], rax
test rax, rax
jz exit

mov rax, 2*gigabyte 
mov r10d, MEM_COMMIT or MEM_RESERVE
invoke VirtualAlloc,0,eax,r10d,PAGE_READWRITE
mov [Memory], rax

mov [GlobalRunning], 1

.main_loop:
cmp [GlobalRunning], 0
je exit

stdcall Win32MessagePump
jmp .main_loop

exit:
invoke  ExitProcess,[Message.wParam]

proc BlitBuffer
locals
    Width dd 0      
    Height dd 0
    PixelsBase dq 0
    DC dq 0
    BitmapInfo BITMAPINFO 0
    ScreenRect RECT 
endl

;get window region
lea rax, [ScreenRect]
invoke GetClientRect, [WindowHandle], rax

;calculate width and height
mov ecx, [ScreenRect.bottom]
sub ecx, [ScreenRect.top]
mov [Height], ecx

mov ecx, [ScreenRect.left]
sub ecx, [ScreenRect.right]
mov [Width], ecx


BitmapInfoHeaderSize equ 40
BI_RGB equ 0
mov [BitmapInfo.biSize], BitmapInfoHeaderSize ;sizeof bitmapinfoheader is 40
mov eax, [Width]
mov [BitmapInfo.biWidth], eax
mov eax, [Height]
mov [BitmapInfo.biHeight], eax
mov [BitmapInfo.biPlanes], 1
mov [BitmapInfo.biBitCount], 32
mov [BitmapInfo.biCompression], BI_RGB
mov [BitmapInfo.biSizeImage], 0
mov [BitmapInfo.biXPelsPerMeter], 0
mov [BitmapInfo.biYPelsPerMeter], 0
mov [BitmapInfo.biClrUsed], 0 
mov [BitmapInfo.biClrImportant], 0
mov [BitmapInfo.RGBQUADa], 0
mov [BitmapInfo.RGBQUADb], 0
mov [BitmapInfo.RGBQUADc], 0
mov [BitmapInfo.RGBQUADd], 0

mov rax, [Memory] 
mov [PixelsBase], rax

invoke GetDC, [WindowHandle]
mov [DC], rax

lea rax, [BitmapInfo]

DIB_RGB_COLORS equ 0
invoke StretchDIBits, [DC],0,0,[Width],[Height],0,0,[Width],[Height],[PixelsBase], rax,DIB_RGB_COLORS,SRCCOPY

ret
endp 

proc Win32ToggleWindowFullScreen

locals
    WindowStyle dd 0
    MonitorInfo MONITORINFO sizeof.MONITORINFO
endl

GWL_STYLE equ -16
SWP_NOOWNERZORDER equ 0x0200 
SWP_FRAMECHANGED equ 0x0020
SWP_NOMOVE equ 0x0002 
SWP_NOSIZE equ 0x0001 
SWP_NOZORDER equ 0x0004
HWND_TOP equ 0 
SWP_FRAMECHANGED equ 0x0020
WS_OVERLAPPEDWINDOW equ 0xcf0000
MONITOR_DEFAULTTOPRIMARY equ 1 

invoke GetWindowLongA, [WindowHandle], GWL_STYLE
mov [WindowStyle], eax

mov rbx, WS_OVERLAPPEDWINDOW
and eax, ebx
jz .else

invoke GetWindowPlacement, [WindowHandle], GlobalWindowPlacement
mov r8, rax
cmp rax, 1
jb .end 

invoke MonitorFromWindow,[WindowHandle], MONITOR_DEFAULTTOPRIMARY
lea rbx, [MonitorInfo]
invoke GetMonitorInfoA,rax,rbx
cmp rax, 1
jb .end

mov rbx, not WS_OVERLAPPEDWINDOW ;not rbx
mov eax, [WindowStyle]
and eax, ebx

invoke SetWindowLongA, [WindowHandle], GWL_STYLE, eax

mov eax, [MonitorInfo.rcMonitor.right]
sub eax, [MonitorInfo.rcMonitor.left]

mov r10d, [MonitorInfo.rcMonitor.bottom]
sub r10d, [MonitorInfo.rcMonitor.top] 

mov r11, HWND_TOP or SWP_NOOWNERZORDER or SWP_FRAMECHANGED 

invoke SetWindowPos, [WindowHandle],0,[MonitorInfo.rcMonitor.left],[MonitorInfo.rcMonitor.top],eax,r10d,r11d
jmp .end

.else:
mov eax, [WindowStyle]
or rax, WS_OVERLAPPEDWINDOW
invoke SetWindowLongA, [WindowHandle],GWL_STYLE,eax
invoke SetWindowPlacement,[WindowHandle],GlobalWindowPlacement

mov rax, SWP_NOOWNERZORDER or SWP_FRAMECHANGED or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER 
invoke SetWindowPos,[WindowHandle],0,0,0,0,0,eax

.end:
ret
endp

proc Win32MessagePump

.while_message:  

invoke PeekMessageA,Message,[WindowHandle],0,0,PM_REMOVE 
cmp eax, 0
je .end

cmp [Message.message], WM_KEYDOWN
je .keydown

cmp [Message.message], WM_PAINT
je .paint

.default:
invoke TranslateMessage,Message
invoke DispatchMessage,Message
jmp .while_message

.keydown:
stdcall Win32ToggleWindowFullScreen

.paint:

    invoke BeginPaint,[WindowHandle],PaintStruct

    stdcall BlitBuffer

    invoke EndPaint,[WindowHandle],PaintStruct

jmp .while_message

.end:

ret
endp

proc Win32CallbackProc ;hwnd,wmsg,wparam,lparam

cmp edx, WM_DESTROY
    je .close 
cmp edx, WM_CLOSE
    je .close
cmp edx, WM_QUIT
    je .close

.default:
    invoke  DefWindowProcA,rcx,rdx,r8,r9
    jmp .end

.close:
    mov [GlobalRunning], 0
    jmp .end
            
.end:
ret
endp

'''

GlobalWindowPlacement WINDOWPLACEMENT sizeof.WINDOWPLACEMENT

breakit equ int3

kilobyte equ 1024
megabyte equ 1024*1024
gigabyte equ 1024*1024*1024

struct BITMAPINFO
biSize                  dd ?
biWidth                 dd ?
biHeight                dd ?
biPlanes                dw ?
biBitCount          dw ?
biSizeImage         dd 0
biXPelsPerMeter dd 0
biYPelsPerMeter dd 0
biClrUsed               dd 0
biClrImportant  dd 0
RGBQUADa                db 0
RGBQUADb                db 0 
RGBQUADc                db 0
RGBQUADd                db 0
ends

PaintStruct PAINTSTRUCT 

struct MONITORINFO 
cbSize          dd ?
rcMonitor   RECT ?
rcWork      RECT ?
dwFlags         dd ?
ends

section '.data' data readable writeable 

GlobalRunning db 0

WindowClassName db "fasm app",0
WindowTitle db "Raytracer or Rasteriser or somethin",0

;WindowClass WNDCLASSEX sizeof.WNDCLASSEX,0,Win32CallbackProc,0,0,0,0,0,COLOR_WINDOW,0,WindowClassName,0
WindowClass WNDCLASSEX sizeof.WNDCLASSEX,0,Win32CallbackProc,0,0,0,0,0,0,0,WindowClassName,0
Message MSG
WindowHandle dq 0
WindowDC dq 0
Memory dq 0

section '.idata' import data readable writeable

library kernel,'kernel32.dll',\
  user,'user32.dll',\
    gdi, 'gdi32.dll'

import kernel,\
 GetModuleHandle,'GetModuleHandleA',\
 ExitProcess,'ExitProcess',\
 VirtualAlloc, 'VirtualAlloc',\
 VirtualFree, 'VirtualFree',\
 GetLastError, 'GetLastError',\
 SetLastError, 'SetLastError'\

import user,\
 RegisterClassExA,'RegisterClassExA',\
 CreateWindowExA,'CreateWindowExA',\
 ShowWindow,'ShowWindow',\
 UpdateWindow,'UpdateWindow',\
 DefWindowProcA,'DefWindowProcA',\
 GetMessage,'GetMessageA',\
 TranslateMessage,'TranslateMessage',\
 DispatchMessage,'DispatchMessageA',\
 LoadCursor,'LoadCursorA',\
 LoadIcon,'LoadIconA',\
 GetClientRect,'GetClientRect',\
 GetDC,'GetDC',\
 ReleaseDC,'ReleaseDC',\
 BeginPaint,'BeginPaint',\
 EndPaint,'EndPaint',\
 PostQuitMessage,'PostQuitMessage',\
 MessageBoxA, 'MessageBoxA',\
 PeekMessageA, 'PeekMessageA',\
 GetWindowLongA, 'GetWindowLongA',\
 GetWindowPlacement,'GetWindowPlacement',\
 SetWindowPlacement, 'SetWindowPlacement',\
 GetMonitorInfoA, 'GetMonitorInfoA',\
 SetWindowLongA, 'SetWindowLongA',\
 SetWindowPos, 'SetWindowPos',\
 MonitorFromWindow, 'MonitorFromWindow'

 import gdi,\
    StretchDIBits, 'StretchDIBits',\
    PatBlt, 'PatBlt'

conclusion. It's been pointed out to me that the invoke macro is clobbering the rax register which I was using for [BitmapInfo] so that was dumb. Fixing that issue results in the expected black screen.


Solution

  • Invoke macro is clobbering the parameter being passed through rax.