gfx/d3d9: Upload RGUI menu framebuffer directly as ARGB4444

Both d3d9_hlsl and d3d9_cg previously expanded RGUI's 16bpp menu
framebuffer to 32bpp on the CPU every frame via a per-pixel loop,
then uploaded the result into a D3DFMT_A8R8G8B8 texture. RGUI
already assembles its framebuffer in 16bpp; D3D9 has supported
D3DFMT_A4R4G4B4 as a baseline texture format since launch.

Add D3D9_ARGB4444_FORMAT to d3d9_common.h (with D3DFMT_LIN_A4R4G4B4
for the _XBOX build path), and "d3d9_hlsl"/"d3d9_cg" cases to the
RGUI pixel format dispatcher selecting argb32_to_argb4444 (already
in use for the rsx/PS3 driver, which targets the same ARGB4444
bit layout).

In both menu set_texture_frame paths, allocate the menu texture
as D3DFMT_A4R4G4B4 when rgb32 is false (the only case in current
practice; RGUI is the sole caller), and upload row-by-row via
memcpy. The rgb32 = true API branch is preserved for forward
compatibility and continues to use D3DFMT_A8R8G8B8.

Track the bpp of the currently-allocated menu texture in a new
d3d9_video_t::menu_tex_rgb32 field so the texture is recreated
when the rgb32 flag flips between calls.

Endian-safe by construction: argb32_to_argb4444 produces a
host-endian uint16_t with A in bits 15..12 down to B in 3..0;
D3DFMT_A4R4G4B4 is read by D3D as host-endian 16-bit units with
the same bit assignments. Same contract as the original
ARGB8888 path, just one storage size smaller, so the ordering
holds on both LE (PC) and BE (Xbox 360) hosts without a byte swap.
This commit is contained in:
U-DESKTOP-SPFP6AQ\twistedtechre committed 2026-04-27 02:35:47 +02:00
1 parent f2f2f5aec2
commit 345edcbc05
4 files changed
+68 -43

No files matched your search

+10
View File
@@ -33,10 +33,12 @@
#define D3D9_RGB565_FORMAT D3DFMT_LIN_R5G6B5
#define D3D9_ARGB8888_FORMAT D3DFMT_LIN_A8R8G8B8
#define D3D9_XRGB8888_FORMAT D3DFMT_LIN_X8R8G8B8
#define D3D9_ARGB4444_FORMAT D3DFMT_LIN_A4R4G4B4
#else
#define D3D9_RGB565_FORMAT D3DFMT_R5G6B5
#define D3D9_ARGB8888_FORMAT D3DFMT_A8R8G8B8
#define D3D9_XRGB8888_FORMAT D3DFMT_X8R8G8B8
#define D3D9_ARGB4444_FORMAT D3DFMT_A4R4G4B4
#endif
RETRO_BEGIN_DECLS
@@ -90,6 +92,14 @@ typedef struct d3d9_video
/* Only used for Xbox */
bool widescreen_mode;
/* Bit-depth of the data most recently uploaded to `menu->tex`.
* The menu texture is created with a fixed pixel format (16bpp
* ARGB4444 for the RGUI fast path, 32bpp ARGB8888 otherwise),
* so we must recreate it when set_menu_texture_frame is called
* with a different `rgb32` value. Defaults to false; the first
* call will see a NULL tex and create one regardless. */
bool menu_tex_rgb32;
} d3d9_video_t;
bool d3d9_create_device(void *dev,
+28 -21
View File
@@ -4552,17 +4552,26 @@ static void d3d9_cg_set_menu_texture_frame(void *data,
if ( (!d3d->menu->tex)
|| (d3d->menu->tex_w != width)
|| (d3d->menu->tex_h != height))
|| (d3d->menu->tex_h != height)
|| (d3d->menu_tex_rgb32 != rgb32))
{
if (d3d->menu->tex)
IDirect3DTexture9_Release((LPDIRECT3DTEXTURE9)d3d->menu->tex);
d3d->menu->tex = NULL;
{
/* RGUI sends 16bpp ARGB4444 (the d3d9 case in RGUI's pixel
* format dispatcher selects argb32_to_argb4444), so we can
* upload it byte-for-byte into a D3DFMT_A4R4G4B4 texture and
* skip the per-pixel CPU expansion to ARGB8888 the previous
* implementation did every frame. The rgb32 path is preserved
* for callers that hand us 32bpp data; in current practice no
* such caller exists, but the API contract supports it. */
void *_tbuf = NULL;
if (SUCCEEDED(IDirect3DDevice9_CreateTexture(d3d->dev,
width, height, 1, 0,
D3DFMT_A8R8G8B8, D3DPOOL_MANAGED,
rgb32 ? D3DFMT_A8R8G8B8 : D3D9_ARGB4444_FORMAT,
D3DPOOL_MANAGED,
(struct IDirect3DTexture9**)&_tbuf, NULL)))
d3d->menu->tex = (LPDIRECT3DTEXTURE9)_tbuf;
}
@@ -4575,6 +4584,7 @@ static void d3d9_cg_set_menu_texture_frame(void *data,
d3d->menu->tex_w = width;
d3d->menu->tex_h = height;
d3d->menu_tex_rgb32 = rgb32;
}
d3d->menu->alpha_mod = alpha;
@@ -4583,7 +4593,7 @@ static void d3d9_cg_set_menu_texture_frame(void *data,
0, &d3dlr, NULL, D3DLOCK_NOSYSLOCK)))
return;
{
unsigned h, w;
unsigned h;
if (rgb32)
{
@@ -4599,26 +4609,23 @@ static void d3d9_cg_set_menu_texture_frame(void *data,
}
else
{
uint32_t *dst = (uint32_t*)d3dlr.pBits;
const uint16_t *src = (const uint16_t*)frame;
/* Direct ARGB4444 upload. The bit layout produced by
* argb32_to_argb4444 (host-endian uint16_t with A in bits
* 15..12, R 11..8, G 7..4, B 3..0) matches D3DFMT_A4R4G4B4
* exactly: D3D reads the locked memory as host-endian
* 16-bit units with the same bit assignments, so the same
* source bytes work on LE (PC) and BE (Xbox 360) hosts
* without a byte swap. */
uint8_t *dst = (uint8_t*)d3dlr.pBits;
const uint8_t *src = (const uint8_t*)frame;
unsigned src_pitch = width * sizeof(uint16_t);
unsigned row_bytes = width * sizeof(uint16_t);
for (h = 0; h < height; h++,
dst += d3dlr.Pitch >> 2,
src += width)
for (h = 0; h < height; h++, dst += d3dlr.Pitch, src += src_pitch)
{
for (w = 0; w < width; w++)
{
uint16_t c = src[w];
uint32_t r = (c >> 12) & 0xf;
uint32_t g = (c >> 8) & 0xf;
uint32_t b = (c >> 4) & 0xf;
uint32_t a = (c >> 0) & 0xf;
r = ((r << 4) | r) << 16;
g = ((g << 4) | g) << 8;
b = ((b << 4) | b) << 0;
a = ((a << 4) | a) << 24;
dst[w] = r | g | b | a;
}
memcpy(dst, src, row_bytes);
if (d3dlr.Pitch > (int)row_bytes)
memset(dst + row_bytes, 0, d3dlr.Pitch - row_bytes);
}
}
}
+27 -21
View File
@@ -7852,15 +7852,23 @@ static void d3d9_hlsl_set_menu_texture_frame(void *data,
if ( (!d3d->menu->tex)
|| (d3d->menu->tex_w != width)
|| (d3d->menu->tex_h != height))
|| (d3d->menu->tex_h != height)
|| (d3d->menu_tex_rgb32 != rgb32))
{
if (d3d->menu->tex)
IDirect3DTexture9_Release((LPDIRECT3DTEXTURE9)d3d->menu->tex);
d3d->menu->tex = NULL;
/* RGUI sends 16bpp ARGB4444 (the d3d9 case in RGUI's pixel
* format dispatcher selects argb32_to_argb4444), so we can
* upload it byte-for-byte into a D3DFMT_A4R4G4B4 texture and
* skip the per-pixel CPU expansion to ARGB8888 the previous
* implementation did every frame. The rgb32 path is preserved
* for callers that hand us 32bpp data; in current practice no
* such caller exists, but the API contract supports it. */
IDirect3DDevice9_CreateTexture(d3d->dev,
width, height, 1,
0, D3D9_ARGB8888_FORMAT,
0, rgb32 ? D3D9_ARGB8888_FORMAT : D3D9_ARGB4444_FORMAT,
D3DPOOL_MANAGED,
(struct IDirect3DTexture9**)&d3d->menu->tex, NULL);
@@ -7872,6 +7880,7 @@ static void d3d9_hlsl_set_menu_texture_frame(void *data,
d3d->menu->tex_w = width;
d3d->menu->tex_h = height;
d3d->menu_tex_rgb32 = rgb32;
}
d3d->menu->alpha_mod = alpha;
@@ -7879,7 +7888,7 @@ static void d3d9_hlsl_set_menu_texture_frame(void *data,
IDirect3DTexture9_LockRect((LPDIRECT3DTEXTURE9)d3d->menu->tex,
0, &d3dlr, NULL, D3DLOCK_NOSYSLOCK);
{
unsigned h, w;
unsigned h;
if (rgb32)
{
@@ -7895,26 +7904,23 @@ static void d3d9_hlsl_set_menu_texture_frame(void *data,
}
else
{
uint32_t *dst = (uint32_t*)d3dlr.pBits;
const uint16_t *src = (const uint16_t*)frame;
/* Direct ARGB4444 upload. The bit layout produced by
* argb32_to_argb4444 (host-endian uint16_t with A in bits
* 15..12, R 11..8, G 7..4, B 3..0) matches D3DFMT_A4R4G4B4
* exactly: D3D reads the locked memory as host-endian
* 16-bit units with the same bit assignments, so the same
* source bytes work on LE (PC) and BE (Xbox 360) hosts
* without a byte swap. */
uint8_t *dst = (uint8_t*)d3dlr.pBits;
const uint8_t *src = (const uint8_t*)frame;
unsigned src_pitch = width * sizeof(uint16_t);
unsigned row_bytes = width * sizeof(uint16_t);
for (h = 0; h < height; h++,
dst += d3dlr.Pitch >> 2,
src += width)
for (h = 0; h < height; h++, dst += d3dlr.Pitch, src += src_pitch)
{
for (w = 0; w < width; w++)
{
uint16_t c = src[w];
uint32_t r = (c >> 12) & 0xf;
uint32_t g = (c >> 8) & 0xf;
uint32_t b = (c >> 4) & 0xf;
uint32_t a = (c >> 0) & 0xf;
r = ((r << 4) | r) << 16;
g = ((g << 4) | g) << 8;
b = ((b << 4) | b) << 0;
a = ((a << 4) | a) << 24;
dst[w] = r | g | b | a;
}
memcpy(dst, src, row_bytes);
if (d3dlr.Pitch > (int)row_bytes)
memset(dst + row_bytes, 0, d3dlr.Pitch - row_bytes);
}
}
}
+3 -1
View File
@@ -1396,7 +1396,9 @@ static bool rgui_set_pixel_format_function(void)
argb32_to_pixel_platform_format = argb32_to_rgb5a3;
else if (string_is_equal(driver_ident, "psp1")) /* PSP */
argb32_to_pixel_platform_format = argb32_to_abgr4444;
else if (string_is_equal(driver_ident, "rsx")) /* PS3 */
else if ( string_is_equal(driver_ident, "rsx") /* PS3 */
|| string_is_equal(driver_ident, "d3d9_hlsl") /* D3D9 (PC + Xbox 360) */
|| string_is_equal(driver_ident, "d3d9_cg"))
argb32_to_pixel_platform_format = argb32_to_argb4444;
else if ( string_is_equal(driver_ident, "d3d10") /* D3D10/11/12 */
|| string_is_equal(driver_ident, "d3d11")