Skip to content

Commit ff65ab1

Browse files
committed
gdi: present bmp_menu via SetDIBitsToDevice instead of BitBlt
The bmp_menu present path was creating a temporary DC, selecting the DIB section into it, BitBlt-ing through that DC to the window DC, then tearing the temporary DC down — every frame: HDC menu_dc = CreateCompatibleDC(gdi->winDC); if (menu_dc) { HBITMAP menu_old = (HBITMAP)SelectObject(menu_dc, gdi->bmp_menu); GdiFlush(); BitBlt(gdi->winDC, ..., menu_dc, ..., SRCCOPY); SelectObject(menu_dc, menu_old); DeleteDC(menu_dc); } SetDIBitsToDevice does the same thing — copy a DIB to a window DC at 1:1 scale — but takes the raw pixel pointer directly, skipping the temporary DC framework entirely. We already keep a uint32_t* into the DIB pixels (gdi->menu_pixels, populated by gdi_ensure_menu_surface) so the substitution is straightforward: GdiFlush(); SetDIBitsToDevice(gdi->winDC, 0, 0, surface_width, surface_height, 0, 0, 0, surface_height, gdi->menu_pixels, &bmi, DIB_RGB_COLORS); Per frame this saves a CreateCompatibleDC, two SelectObjects, and a DeleteDC syscall. The pixel-copy bandwidth is unchanged (the GDI implementation still has to move surface_width * surface_height * 4 bytes from system RAM to the window's displayable surface), but the API path is shorter and avoids some of GDI's DC-based source-routing overhead. bmp_menu is allocated at exactly the window surface size so there's no scaling involved, which is the key precondition for SetDIBitsToDevice (the no-scaling, simpler cousin of StretchDIBits). The DIB is top-down (biHeight is negative in gdi_ensure_menu_surface) and SetDIBitsToDevice supports top-down DIBs natively, so the bit layout matches without needing to flip rows. Compatibility: SetDIBitsToDevice is part of the original Win32 API. Same availability surface as BitBlt itself — Win95, NT 3.5+, Win32s. No regression vs the previous path. The legacy bmp + WM_PAINT + StretchBlt route (used when no widgets, no textured menu, no OSD/stats) is untouched — that path actually does scale (small core frame to window-sized viewport), so SetDIBitsToDevice doesn't fit there.
1 parent b984124 commit ff65ab1

2 files changed

Lines changed: 41 additions & 20 deletions

File tree

gfx/common/gdi_defines.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ typedef struct gdi
4949
* without round-tripping through DDB conversion every frame. */
5050
HBITMAP bmp_menu;
5151
HBITMAP bmp_menu_old;
52-
uint32_t *menu_pixels; /* DIB-backing pointer; currently unused but kept for potential direct-access fast paths. */
52+
uint32_t *menu_pixels; /* DIB-backing pointer; passed straight to SetDIBitsToDevice in the present path. */
5353
unsigned menu_surface_width;
5454
unsigned menu_surface_height;
5555

gfx/drivers/gdi_gfx.c

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3106,33 +3106,54 @@ static bool gdi_frame(void *data, const void *frame,
31063106
* Two cases:
31073107
* - menu_textured_active (textured menu OR widgets/OSD/stats
31083108
* present): bmp_menu holds the final image at window size.
3109-
* Manually BitBlt to winDC and ValidateRect.
3109+
* Hand the DIB pixel buffer straight to SetDIBitsToDevice
3110+
* and ValidateRect.
31103111
* - Otherwise (just core, just RGUI): bmp holds the final
31113112
* image at native size. InvalidateRect → WM_PAINT does the
31123113
* StretchBlt to the window. This single-target,
31133114
* single-present design is the legacy behaviour and it
31143115
* does not flicker. */
31153116
if (gdi->winDC)
31163117
{
3117-
if (gdi->menu_textured_active && gdi->bmp_menu)
3118+
if (gdi->menu_textured_active && gdi->bmp_menu && gdi->menu_pixels)
31183119
{
3119-
HDC menu_dc = CreateCompatibleDC(gdi->winDC);
3120-
if (menu_dc)
3121-
{
3122-
HBITMAP menu_old = (HBITMAP)SelectObject(menu_dc, gdi->bmp_menu);
3123-
/* Drain GDI's batch queue so all draws into bmp_menu
3124-
* (FillRect, AlphaBlend, GradientFill, font glyph
3125-
* blits, etc.) have actually committed to the
3126-
* underlying DIB section before we sample it. Without
3127-
* this, the BitBlt below can read a stale snapshot
3128-
* and produce missing-draw artifacts. */
3129-
GdiFlush();
3130-
BitBlt(gdi->winDC,
3131-
0, 0, surface_width, surface_height,
3132-
menu_dc, 0, 0, SRCCOPY);
3133-
SelectObject(menu_dc, menu_old);
3134-
DeleteDC(menu_dc);
3135-
}
3120+
/* SetDIBitsToDevice takes the raw DIB pixel buffer
3121+
* (gdi->menu_pixels) directly, skipping the round-trip
3122+
* through a temporary CreateCompatibleDC / SelectObject /
3123+
* BitBlt / DeleteDC sequence. No scaling is involved
3124+
* here — bmp_menu was allocated at exactly
3125+
* surface_width x surface_height — so the no-stretch
3126+
* SetDIBitsToDevice form fits perfectly.
3127+
*
3128+
* The DIB is top-down (biHeight = -surface_height in
3129+
* gdi_ensure_menu_surface), so we pass a top-down
3130+
* BITMAPINFOHEADER here too and StartScan / cLines
3131+
* count from row 0 down. Available since Windows 95,
3132+
* so no compatibility regression vs the previous BitBlt
3133+
* path.
3134+
*
3135+
* GdiFlush before sampling: bmp_menu was the selected
3136+
* draw target up to step 13. Direct-access reads
3137+
* (which is what SetDIBitsToDevice does — it reads
3138+
* lpvBits straight) need the GDI command queue drained
3139+
* so all prior FillRect / AlphaBlend / etc. calls have
3140+
* actually committed to the underlying pixel buffer.
3141+
* Without this, sporadic missing-draw artifacts. */
3142+
BITMAPINFO bmi;
3143+
memset(&bmi, 0, sizeof(bmi));
3144+
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
3145+
bmi.bmiHeader.biWidth = (LONG)surface_width;
3146+
bmi.bmiHeader.biHeight = -(LONG)surface_height;
3147+
bmi.bmiHeader.biPlanes = 1;
3148+
bmi.bmiHeader.biBitCount = 32;
3149+
bmi.bmiHeader.biCompression = BI_RGB;
3150+
3151+
GdiFlush();
3152+
SetDIBitsToDevice(gdi->winDC,
3153+
0, 0, surface_width, surface_height,
3154+
0, 0, 0, surface_height,
3155+
gdi->menu_pixels, &bmi, DIB_RGB_COLORS);
3156+
31363157
/* We just painted everything; suppress the WM_PAINT that
31373158
* the legacy code path would otherwise queue. */
31383159
ValidateRect(hwnd, NULL);

0 commit comments

Comments
 (0)