Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -3589,6 +3589,8 @@ static bool check_menu_driver_compatibility(settings_t *settings)
|| (memcmp(video_driver, "glcore", 6) == 0 && video_driver[6] == '\0');
if (video_driver[1] == 'x')
return (memcmp(video_driver, "gx2", 3) == 0 && video_driver[3] == '\0');
if (video_driver[1] == 'd')
return (memcmp(video_driver, "gdi", 3) == 0 && video_driver[3] == '\0');
return false;
case 'v':
return (memcmp(video_driver, "vulkan", 6) == 0 && video_driver[6] == '\0')
Expand Down
125 changes: 125 additions & 0 deletions gfx/common/gdi_defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <retro_environment.h>
#include <boolean.h>

#include "../video_defines.h"

typedef struct gdi
{
#ifndef __WINRT__
Expand All @@ -37,6 +39,74 @@ typedef struct gdi
uint8_t *menu_frame;
size_t menu_frame_cap;

/* Backing bitmap for the menu/widget compositing surface.
* Distinct from gdi->bmp (which is a DDB sized to the core
* frame): bmp_menu is a top-down 32-bit BGRA DIB section sized
* to the *window* surface, pre-multiplied alpha-ready, used as
* the back buffer when XMB/Ozone/MaterialUI or widgets draw via
* gfx_display_ctx_gdi. The DIB lets us do fast solid-color
* fills (FillRect + cached brush) and AlphaBlend composites
* without round-tripping through DDB conversion every frame. */
HBITMAP bmp_menu;
HBITMAP bmp_menu_old;
uint32_t *menu_pixels; /* DIB-backing pointer; passed straight to SetDIBitsToDevice in the present path. */
unsigned menu_surface_width;
unsigned menu_surface_height;

/* Pre-allocated brushes for solid-fill quads. The current brush is
* cached and reused when consecutive quads share a colour, which
* is common (Ozone draws hundreds of background quads in the
* same theme colour per frame). */
HBRUSH brush_cached;
COLORREF brush_color_cached;
bool brush_color_cached_valid;

/* Scissor stack for gfx_display_ctx_gdi_scissor_{begin,end}. GDI
* clip regions don't nest natively, so we save/restore the DC
* clip region across begin/end. */
int scissor_saved;
bool scissor_active;

/* Cached scratch DIB sections for hot-path AlphaBlend sources.
*
* gfx_display_ctx_gdi_draw and the RGUI alpha helper used to
* CreateDIBSection / DeleteObject on every call: that's a kernel
* round-trip per draw and Ozone issues hundreds of draws per
* frame. These slots cache the DIB across frames; a draw asks
* for "at least W x H pixels" via gdi_ensure_scratch_*, which
* grows the DIB if the request exceeds the current cap and
* otherwise just hands back the existing pixel pointer.
*
* - scratch_1x1: fixed-size 1x1 BGRA, allocated once at gdi_init
* and held for the lifetime of gdi_t. Used by the translucent
* solid-quad path (single premultiplied pixel, AlphaBlend
* scaled across the destination). Never freed except in
* gdi_free.
* - scratch_quad: variable. Used by the per-vertex gradient
* path (sized to dst_w x dst_h) and the texture-modulated
* tint path (sized to the source sub-rect). Grow-only;
* doesn't shrink when smaller draws come along, since
* reallocation cost would defeat the purpose.
* - scratch_rgui: variable. Used by the RGUI alpha-composite
* path (sized to the menu_frame dimensions). Separate from
* scratch_quad so a frame that uses both doesn't thrash one
* slot back and forth.
*
* Each slot tracks the HBITMAP, the DIB pixel pointer (we write
* into it directly), and the current capacity in width/height.
* Width and height are tracked separately rather than as a
* pixel count because BITMAPINFOHEADER cares about both. */
HBITMAP scratch_1x1_bmp;
uint32_t *scratch_1x1_pixels;
HBITMAP scratch_quad_bmp;
uint32_t *scratch_quad_pixels;
unsigned scratch_quad_w;
unsigned scratch_quad_h;
HBITMAP scratch_rgui_bmp;
uint32_t *scratch_rgui_pixels;
unsigned scratch_rgui_w;
unsigned scratch_rgui_h;

unsigned video_width;
unsigned video_height;
unsigned screen_width;
Expand All @@ -46,6 +116,14 @@ typedef struct gdi
* from video_width / video_height which is the core's frame size. */
unsigned full_width;
unsigned full_height;
/* Actual size of gdi->bmp (the DDB). Separate from video_width
* because when RGUI is active we draw the menu (a different size
* than the core) into bmp; without a dedicated tracker, the
* comparison against video_width would trigger a destructive
* DeleteObject + CreateCompatibleBitmap on every frame, racing
* with WM_PAINT and producing visible flicker. */
unsigned bmp_width;
unsigned bmp_height;

unsigned menu_width;
unsigned menu_height;
Expand All @@ -60,6 +138,53 @@ typedef struct gdi
bool lte_win98;
bool menu_enable;
bool menu_full_screen;
/* True while a textured menu (XMB/Ozone/MaterialUI) is being
* composited onto bmp_menu in gfx_display_ctx_gdi_draw. RGUI
* still pushes a 16-bit pixel buffer via set_texture_frame and
* lives in the gdi->menu_frame path. */
bool menu_textured_active;

/* Aspect-ratio-aware viewport. full_width/full_height hold the
* window size; x/y/width/height hold the destination rect for
* the core frame inside the window after applying aspect ratio
* settings (Settings → Video → Scaling → Aspect Ratio). Mirrors
* the d3d8/d3d9 vp pattern: video_driver_update_viewport fills
* this in, the frame's StretchBlt/StretchDIBits uses x/y/width/
* height as its destination, and the area outside that rect is
* cleared to black to produce letterbox/pillarbox bars.
*
* keep_aspect is set from video_info->force_aspect at init time
* and toggled to true when the user changes aspect ratio.
*
* should_resize is the dirty flag: window-resize / aspect-ratio
* changes / state-change pokes set it, gdi_frame consumes it by
* recomputing the viewport at the start of the next frame. */
video_viewport_t vp;
bool keep_aspect;
bool should_resize;

#ifdef HAVE_OVERLAY
/* On-screen input overlay state. Each entry holds an HBITMAP
* DIB section (32-bit BGRA, premultiplied alpha) that's
* AlphaBlend'd into the active compositing target every frame.
* vert_coords and tex_coords match the d3d8/d3d9 layout: each
* is a 4-float (x, y, w, h) tuple in 0..1 space (window space
* for vert when fullscreen, viewport space otherwise; texture
* space for tex). vertex_geom flips y the same way d3d8 does
* to keep the same on-screen behaviour. */
struct gdi_overlay
{
HBITMAP bmp;
unsigned tex_w;
unsigned tex_h;
float tex_coords[4];
float vert_coords[4];
float alpha_mod;
bool fullscreen;
} *overlays;
unsigned overlays_size;
bool overlays_enabled;
#endif
} gdi_t;

#endif
25 changes: 25 additions & 0 deletions gfx/common/vulkan_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -2183,6 +2183,7 @@ bool vulkan_create_swapchain(gfx_ctx_vulkan_data_t *vk,
&& formats[i].colorSpace == VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT)
{
format = formats[i];
vk->context.flags |= VK_CTX_FLAG_HDR_SCRGB;
RARCH_LOG("[Vulkan] Selecting R16G16B16A16_SFLOAT swapchain with scRGB colour space.\n");
break;
}
Expand Down Expand Up @@ -2449,6 +2450,30 @@ bool vulkan_create_swapchain(gfx_ctx_vulkan_data_t *vk,
RARCH_LOG("[Vulkan] Got %u swapchain images.\n",
vk->context.num_swapchain_images);

/* Pre-create the per-image present-side semaphores up-front for every
* image in the new swapchain. vulkan_acquire_next_image only allocates
* swapchain_semaphores[index] for the image actually returned by
* vkAcquireNextImageKHR, leaving slots for not-yet-acquired images at
* VK_NULL_HANDLE. On the swapchain recreate path the prior teardown
* memsets the array to zero, and at least one path through the recreate
* can reach vulkan_present with current_swapchain_index pointing at an
* image whose slot has not yet been re-populated. NVIDIA real-FSE on
* Win11 then segfaults inside vkQueuePresentKHR when handed
* VK_NULL_HANDLE in pWaitSemaphores. */
{
VkSemaphoreCreateInfo sem_info_pre;
unsigned sem_idx;
sem_info_pre.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
sem_info_pre.pNext = NULL;
sem_info_pre.flags = 0;
for (sem_idx = 0; sem_idx < vk->context.num_swapchain_images; sem_idx++)
{
if (vk->context.swapchain_semaphores[sem_idx] == VK_NULL_HANDLE)
vkCreateSemaphore(vk->context.device, &sem_info_pre,
NULL, &vk->context.swapchain_semaphores[sem_idx]);
}
}

/* Force driver to reset swapchain image handles. */
vk->context.flags |= VK_CTX_FLAG_INVALID_SWAPCHAIN;
vk->context.flags &= ~VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN;
Expand Down
135 changes: 72 additions & 63 deletions gfx/common/win32_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -1339,6 +1339,72 @@ static LRESULT wnd_proc_wm_gdi_create(HWND hwnd)
return 0;
}

/* Shared WM_PAINT body for all three GDI window procs (dinput,
* winraw, common). Presents gdi->bmp scaled into the
* aspect-ratio-aware viewport rect (gdi->vp), filling the area
* outside the rect with black to produce letterbox / pillarbox
* bars. Reads bmp_width / bmp_height for the source rect (the
* DDB's actual size); when RGUI is alive bmp holds the menu image
* at the menu's resolution while gdi->video_width still tracks
* the core, so video_width is only the fallback. */
static void wnd_proc_gdi_paint(gdi_t *gdi)
{
int vp_x = gdi->vp.x;
int vp_y = gdi->vp.y;
unsigned vp_w = gdi->vp.width ? gdi->vp.width : gdi->screen_width;
unsigned vp_h = gdi->vp.height ? gdi->vp.height : gdi->screen_height;
unsigned src_w = gdi->bmp_width ? gdi->bmp_width : gdi->video_width;
unsigned src_h = gdi->bmp_height ? gdi->bmp_height : gdi->video_height;

/* Letterbox / pillarbox bars: paint the four areas outside the
* viewport rect black before the StretchBlt. We do this even
* when the viewport happens to fill the whole window — extra
* FillRects on zero-area regions are cheap. */
if (vp_x > 0 || vp_y > 0
|| vp_x + (int)vp_w < (int)gdi->screen_width
|| vp_y + (int)vp_h < (int)gdi->screen_height)
{
RECT rect;
HBRUSH black = (HBRUSH)GetStockObject(BLACK_BRUSH);
/* Top */
if (vp_y > 0)
{
rect.left = 0; rect.top = 0;
rect.right = (LONG)gdi->screen_width; rect.bottom = vp_y;
FillRect(gdi->winDC, &rect, black);
}
/* Bottom */
if (vp_y + (int)vp_h < (int)gdi->screen_height)
{
rect.left = 0; rect.top = vp_y + (int)vp_h;
rect.right = (LONG)gdi->screen_width; rect.bottom = (LONG)gdi->screen_height;
FillRect(gdi->winDC, &rect, black);
}
/* Left */
if (vp_x > 0)
{
rect.left = 0; rect.top = vp_y;
rect.right = vp_x; rect.bottom = vp_y + (int)vp_h;
FillRect(gdi->winDC, &rect, black);
}
/* Right */
if (vp_x + (int)vp_w < (int)gdi->screen_width)
{
rect.left = vp_x + (int)vp_w; rect.top = vp_y;
rect.right = (LONG)gdi->screen_width; rect.bottom = vp_y + (int)vp_h;
FillRect(gdi->winDC, &rect, black);
}
}

gdi->bmp_old = (HBITMAP)SelectObject(gdi->memDC, gdi->bmp);
StretchBlt(gdi->winDC,
vp_x, vp_y, vp_w, vp_h,
gdi->memDC,
0, 0, src_w, src_h,
SRCCOPY);
SelectObject(gdi->memDC, gdi->bmp_old);
}

#ifdef HAVE_DINPUT
LRESULT CALLBACK wnd_proc_gdi_dinput(HWND hwnd, UINT message,
WPARAM wparam, LPARAM lparam)
Expand All @@ -1347,28 +1413,9 @@ LRESULT CALLBACK wnd_proc_gdi_dinput(HWND hwnd, UINT message,
return wnd_proc_wm_gdi_create(hwnd);
else if (message == WM_PAINT)
{
gdi_t *gdi = (gdi_t*)video_driver_get_ptr();

gdi_t *gdi = (gdi_t*)video_driver_get_ptr();
if (gdi && gdi->memDC)
{
gdi->bmp_old = (HBITMAP)SelectObject(gdi->memDC, gdi->bmp);

/* Draw video content */
StretchBlt(
gdi->winDC,
0,
0,
gdi->screen_width,
gdi->screen_height,
gdi->memDC,
0,
0,
gdi->video_width,
gdi->video_height,
SRCCOPY);

SelectObject(gdi->memDC, gdi->bmp_old);
}
wnd_proc_gdi_paint(gdi);
}

return wnd_proc_common_dinput_internal(hwnd, message, wparam, lparam);
Expand All @@ -1383,28 +1430,9 @@ LRESULT CALLBACK wnd_proc_gdi_winraw(HWND hwnd, UINT message,
return wnd_proc_wm_gdi_create(hwnd);
else if (message == WM_PAINT)
{
gdi_t *gdi = (gdi_t*)video_driver_get_ptr();

gdi_t *gdi = (gdi_t*)video_driver_get_ptr();
if (gdi && gdi->memDC)
{
gdi->bmp_old = (HBITMAP)SelectObject(gdi->memDC, gdi->bmp);

/* Draw video content */
StretchBlt(
gdi->winDC,
0,
0,
gdi->screen_width,
gdi->screen_height,
gdi->memDC,
0,
0,
gdi->video_width,
gdi->video_height,
SRCCOPY);

SelectObject(gdi->memDC, gdi->bmp_old);
}
wnd_proc_gdi_paint(gdi);
}

return wnd_proc_winraw_common_internal(hwnd, message, wparam, lparam);
Expand All @@ -1418,28 +1446,9 @@ LRESULT CALLBACK wnd_proc_gdi_common(HWND hwnd, UINT message,
return wnd_proc_wm_gdi_create(hwnd);
else if (message == WM_PAINT)
{
gdi_t *gdi = (gdi_t*)video_driver_get_ptr();

gdi_t *gdi = (gdi_t*)video_driver_get_ptr();
if (gdi && gdi->memDC)
{
gdi->bmp_old = (HBITMAP)SelectObject(gdi->memDC, gdi->bmp);

/* Draw video content */
StretchBlt(
gdi->winDC,
0,
0,
gdi->screen_width,
gdi->screen_height,
gdi->memDC,
0,
0,
gdi->video_width,
gdi->video_height,
SRCCOPY);

SelectObject(gdi->memDC, gdi->bmp_old);
}
wnd_proc_gdi_paint(gdi);
}

return wnd_proc_common_internal(hwnd, message, wparam, lparam);
Expand Down
Loading
Loading