You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

x86_64汇编Windows窗口缩放时左上边缘抖动问题求助

问题原因分析

1. 系统背景擦除与自定义绘制的冲突

窗口向左/上扩展时,Windows会先触发WM_ERASEBKGND消息,用窗口类注册时的默认背景刷擦除新暴露区域。如果你的自定义填充操作在WM_PAINT中执行,就会出现“系统擦除→自定义绘制”的两次画面切换,视觉上表现为抖动。而右下边缘缩放时,新暴露区域在原客户区右侧/下方,系统擦除的痕迹会被后续绘制完全覆盖,因此无明显抖动。

2. WM_PAINT处理不规范

若你在WM_PAINT中用GetDC而非BeginPaint获取设备上下文,会导致无效区域无法被正确标记为已绘制,系统会反复发送WM_PAINT引发重复绘制,进一步加剧抖动。

3. 无重绘缓冲机制

即时绘制模式下,绘制操作直接输出到窗口DC,即便CPU能快速完成填充,窗口扩展时的坐标同步、消息队列延迟等因素,仍可能导致未完成的画面被屏幕刷新捕捉,出现抖动。

简洁修复方案

方案1:禁用系统背景擦除

  • 注册窗口类时,将WNDCLASSEXhbrBackground字段设为NULL
  • 处理WM_ERASEBKGND消息,直接返回TRUE阻止系统默认擦除:
    ; WM_ERASEBKGND 消息分支
    cmp     rcx, WM_ERASEBKGND
    je      .erase_bkgnd
    ; ...
    .erase_bkgnd:
        mov     rax, 1
        ret
    

方案2:规范WM_PAINT处理流程

必须使用BeginPaint/EndPaint管理DC与无效区域,确保绘制仅针对需要更新的区域:

; WM_PAINT 消息分支
cmp     rcx, WM_PAINT
je      .paint
; ...
.paint:
      sub     rsp, 16                 ; 分配 PAINTSTRUCT 空间
      mov     rdx, rsp
      call    BeginPaint              ; 返回窗口DC到 rax
      ; 获取客户区大小
      lea     rdx, [rsp+16]
      call    GetClientRect
      ; 创建矢车菊蓝刷子(GDI用BGR格式,对应RGB #6495ED)
      mov     r8d, 0xED9564
      call    CreateSolidBrush
      ; 填充客户区
      mov     rcx, rax                ; 窗口DC
      lea     rdx, [rsp+16]           ; 客户区RECT
      mov     r8, rax                 ; 刷子句柄
      call    FillRect
      ; 清理资源
      call    DeleteObject
      ; 结束绘制
      mov     rcx, rbx                ; hWnd
      mov     rdx, rsp
      call    EndPaint
      add     rsp, 16
      xor     rax, rax
      ret

方案3:添加简单双缓冲(彻底解决同步问题)

在内存DC中完成绘制后一次性输出到窗口,避免中间画面暴露:

.paint:
      sub     rsp, 40                 ; 分配 PAINTSTRUCT、RECT、内存DC等空间
      mov     rdx, rsp
      call    BeginPaint              ; 窗口DC存入 [rsp+24]
      mov     [rsp+24], rax

      ; 获取客户区宽高
      lea     rdx, [rsp+8]
      call    GetClientRect
      mov     edx, [rsp+16]
      sub     edx, [rsp+12]           ; 宽度
      mov     r8d, [rsp+20]
      sub     r8d, [rsp+8]            ; 高度

      ; 创建兼容内存DC与位图
      mov     rcx, [rsp+24]
      call    CreateCompatibleDC
      mov     [rsp+32], rax
      mov     rcx, [rsp+24]
      call    CreateCompatibleBitmap
      mov     [rsp+36], rax

      ; 将位图选入内存DC
      mov     rcx, [rsp+32]
      mov     rdx, [rsp+36]
      call    SelectObject

      ; 填充内存DC背景
      mov     rcx, [rsp+32]
      lea     rdx, [rsp+8]
      mov     r8d, 0xED9564
      call    CreateSolidBrush
      mov     r9, rax
      call    FillRect
      call    DeleteObject

      ; 内存DC内容复制到窗口DC
      mov     rcx, [rsp+24]
      xor     rdx, rdx                ; 目标X/Y
      xor     r8, r8
      mov     r9d, [rsp+16]
      sub     r9d, [rsp+12]           ; 宽度
      push    [rsp+20]
      pop     r10d
      sub     r10d, [rsp+8]           ; 高度
      mov     r11, [rsp+32]
      xor     r12, r12                ; 源X/Y
      xor     r13, r13
      mov     r14d, SRCCOPY           ; 复制模式
      call    BitBlt

      ; 清理资源
      mov     rcx, [rsp+32]
      mov     rdx, [rsp+36]
      call    SelectObject
      mov     rcx, [rsp+36]
      call    DeleteObject
      mov     rcx, [rsp+32]
      call    DeleteDC

      mov     rcx, rbx
      mov     rdx, rsp
      call    EndPaint
      add     rsp, 40
      xor     rax, rax
      ret

额外优化:锁定窗口更新

处理WM_SIZE时暂时禁止窗口绘制,避免多次重绘:

; WM_SIZE 消息分支
cmp     rcx, WM_SIZE
je      .size
; ...
.size:
      mov     rcx, rbx                ; 锁定当前窗口
      call    LockWindowUpdate
      ; 执行窗口大小相关逻辑(若有)
      mov     rcx, 0                  ; 解锁
      call    LockWindowUpdate
      mov     rcx, rbx                ; 触发重绘
      call    InvalidateRect
      xor     rax, rax
      ret

内容的提问来源于stack exchange,提问作者Felipe Dilho

火山引擎 最新活动