d3d9_hlsl: a hardware core's views through a preset

A core rendering with the Direct3D 9 interface had its texture drawn
whole through a preset, its views ignored: each view's chain only took
a view of a software frame, written straight out of it into the
chain's own texture. Now each view of the core's texture is copied on
the GPU into its chain's frame, with StretchRect, point sampled, from
where it lies to the corner: the texels as they are, one copy a view,
with no trip through the CPU. The chain's frames are then render
targets in the core's texture's format, made with the chain and black
past the view, filled black again when a smaller view lands in a slot
that held a larger one. They stay its history, so a preset reading
earlier frames reads each view's own.

A chain made for one kind of frame is made again for the other. A
frame without a texture handed over draws no views.

samples/gfx/d3d9_ex checks the copy on a real Direct3D 9 device and a
Direct3D9Ex one under Wine, texel for texel and black past the view,
with a smaller view after a larger one. e2e/run.py now runs the d3d9
hardware case through D3D9's own presets too, earlier frames
included, matching the software frame's screen, and checks the log
says the views were copied out of the core's texture.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-08 02:12:23 +01:00
1 parent 87c0ebe857
commit e2c75d8fbd
5 files changed
+276 -14

No files matched your search

+4
View File
@@ -110,6 +110,10 @@ typedef struct d3d9_views
bool active;
bool direct_seen;
bool preset_seen;
/* The frame drawn is a hardware core's texture: the preset chains'
* frames are render targets each view is copied into */
bool hw_frame;
bool hw_seen;
} d3d9_views_t;
#endif
+3 -2
View File
@@ -336,8 +336,9 @@ static bool D3D9V_FN(views_setup)(d3d9_video_t *d3d,
d3d->views.preset_seen = true;
RARCH_LOG(D3D9V_TAG " Views drawn through the preset, each by a "
"chain of its own.\n");
RARCH_LOG(D3D9V_TAG " Views' presets have each view of a software "
"frame written straight in.\n");
if (!d3d->views.hw_frame)
RARCH_LOG(D3D9V_TAG " Views' presets have each view of a "
"software frame written straight in.\n");
}
return views;
}
+127 -8
View File
@@ -696,6 +696,9 @@ typedef struct hlsl_renderchain
unsigned sized_dims;
unsigned last_dims;
bool ran;
/* Its frames are render targets a hardware frame's view is copied
* into, in the core's texture's format */
bool hw;
} hlsl_renderchain_t;
static bool hlsl_d3d9_pipeline_ready(hlsl_renderchain_t *chain,
@@ -6808,12 +6811,38 @@ static hlsl_renderchain_t *d3d9_hlsl_views_chain_new(d3d9_video_t *d3d,
if (FAILED(IDirect3DTexture9_GetLevelDesc(main->chain.prev.tex[0], 0,
&desc)))
goto error;
#ifdef D3D9_HW_RENDER
/* A hardware frame's views are copied on the GPU, as they lie */
if ((c->hw = d3d->views.hw_frame) && (!d3d->hw.tex
|| FAILED(IDirect3DTexture9_GetLevelDesc(d3d->hw.tex, 0, &desc))))
goto error;
#endif
for (i = 0; i < TEXTURES; i++)
{
if ( FAILED(IDirect3DDevice9_CreateVertexBuffer(c->chain.dev,
if (FAILED(IDirect3DDevice9_CreateVertexBuffer(c->chain.dev,
4 * sizeof(struct Vertex), D3DUSAGE_WRITEONLY, 0,
D3DPOOL_DEFAULT, &c->chain.prev.vertex_buf[i], NULL))
|| FAILED(d3d9_tex_create(c->chain.dev, tw, th, 1,
D3DPOOL_DEFAULT, &c->chain.prev.vertex_buf[i], NULL)))
goto error;
if (c->hw)
{
LPDIRECT3DSURFACE9 surf = NULL;
if (FAILED(IDirect3DDevice9_CreateTexture(c->chain.dev, tw, th, 1,
D3DUSAGE_RENDERTARGET, desc.Format, D3DPOOL_DEFAULT,
(LPDIRECT3DTEXTURE9*)&c->chain.prev.tex[i], NULL)))
{
c->chain.prev.tex[i] = NULL;
goto error;
}
/* What a view smaller than it leaves is black, as a software
* frame's texture is */
if (SUCCEEDED(IDirect3DTexture9_GetSurfaceLevel(
(LPDIRECT3DTEXTURE9)c->chain.prev.tex[i], 0, &surf)))
{
IDirect3DDevice9_ColorFill(c->chain.dev, surf, NULL, 0);
IDirect3DSurface9_Release(surf);
}
}
else if (FAILED(d3d9_tex_create(c->chain.dev, tw, th, 1,
0, desc.Format,
(LPDIRECT3DTEXTURE9*)&c->chain.prev.tex[i])))
goto error;
@@ -6837,15 +6866,25 @@ static void d3d9_hlsl_views_free_chains(d3d9_video_t *d3d, unsigned first)
}
}
/* A preset chain for each of the map's views [0, count) that has none */
/* A preset chain for each of the map's views [0, count) that has none,
* or one made for the other kind of frame */
static void d3d9_hlsl_views_build_chains(d3d9_video_t *d3d,
const video_views_map_t *map)
{
unsigned i;
for (i = 0; i < d3d->views.chains.count && i < map->num_views; i++)
{
hlsl_renderchain_t *c = (hlsl_renderchain_t*)
d3d->views.chains.preset[i];
if (c && c->hw != d3d->views.hw_frame)
{
hlsl_d3d9_renderchain_free(c);
d3d->views.chains.preset[i] = NULL;
}
if (!d3d->views.chains.preset[i])
d3d->views.chains.preset[i] = d3d9_hlsl_views_chain_new(d3d, i,
map->views[i].width, map->views[i].height);
}
}
/* The MVP that puts a pass's quad over @dst within viewport @vp, turned
@@ -6905,9 +6944,52 @@ static void d3d9_hlsl_views_draw_pass(d3d9_video_t *d3d,
(LPDIRECT3DPIXELSHADER9)(d)->views.ps[i])
#include "../common/d3d9_views.h"
#ifdef D3D9_HW_RENDER
/* View @v of the core's texture @src copied into @c's frame for this
* draw, the texels as they are; false if it can't be */
static bool d3d9_hlsl_views_hw_copy(hlsl_renderchain_t *c,
LPDIRECT3DTEXTURE9 src, const struct retro_video_view *v)
{
RECT from, to;
LPDIRECT3DSURFACE9 s = NULL;
LPDIRECT3DSURFACE9 t = NULL;
bool ok = false;
unsigned slot = c->chain.prev.ptr;
unsigned dims = VIDEO_SCALE_PACK(v->width, v->height);
LPDIRECT3DTEXTURE9 dst = (LPDIRECT3DTEXTURE9)c->chain.prev.tex[slot];
if ( !src || !dst
|| FAILED(IDirect3DTexture9_GetSurfaceLevel(src, 0, &s))
|| FAILED(IDirect3DTexture9_GetSurfaceLevel(dst, 0, &t)))
goto end;
/* A larger view this slot held before leaves black behind */
if (c->chain.prev.last_dims[slot] != dims)
IDirect3DDevice9_ColorFill(c->chain.dev, t, NULL, 0);
from.left = (LONG)v->x;
from.top = (LONG)v->y;
from.right = (LONG)(v->x + v->width);
from.bottom = (LONG)(v->y + v->height);
to.left = 0;
to.top = 0;
to.right = (LONG)v->width;
to.bottom = (LONG)v->height;
ok = SUCCEEDED(IDirect3DDevice9_StretchRect(c->chain.dev, s, &from, t,
&to, D3DTEXF_POINT));
/* The size the first pass reads it at, as a software frame's put
* leaves it */
c->chain.prev.last_dims[slot] = dims;
end:
if (s)
IDirect3DSurface9_Release(s);
if (t)
IDirect3DSurface9_Release(t);
return ok;
}
#endif
/* @c's last pass over @dst, cut to @cut; with @again its frame was run
* for an earlier placement. Each view of a software frame is written
* straight from it into the chain's own texture. */
* straight from it into the chain's own texture, of a hardware frame
* copied out of the core's on the GPU. */
static void d3d9_hlsl_views_draw_pass(d3d9_video_t *d3d,
const struct d3d9_views_draw *d, void *data,
const video_views_rect_t *dst, const video_views_rect_t *cut,
@@ -6918,10 +7000,27 @@ static void d3d9_hlsl_views_draw_pass(d3d9_video_t *d3d,
hlsl_renderchain_t *c = (hlsl_renderchain_t*)data;
const struct retro_video_view *v = &d->video_info->views.views[c->view];
#ifdef D3D9_HW_RENDER
/* Made for the other kind of frame: made again on the next */
if (c->hw != d3d->views.hw_frame)
return;
#endif
if (!again)
{
unsigned old = c->chain.passes->data[0].info.tex_dims;
unsigned size = d->rects[c->view].dims;
#ifdef D3D9_HW_RENDER
if (c->hw)
{
/* Its render targets are made at the view's size, with the
* map: one larger than them is not drawn */
if ( v->width > VIDEO_SCALE_W(old)
|| v->height > VIDEO_SCALE_H(old)
|| !d3d9_hlsl_views_hw_copy(c, d->tex, v))
return;
}
else
#endif
if (!d3d9_hlsl_renderchain_fit_frame(&c->chain, v->width, v->height))
return;
if ( c->sized_dims != size
@@ -6934,6 +7033,18 @@ static void d3d9_hlsl_views_draw_pass(d3d9_video_t *d3d,
&c->view_vp);
c->sized_dims = size;
}
#ifdef D3D9_HW_RENDER
/* Read as it was copied: nothing to upload */
if (c->hw)
{
c->chain.hw_tex = (LPDIRECT3DTEXTURE9)
c->chain.prev.tex[c->chain.prev.ptr];
d3d9_hlsl_renderchain_run(d3d, c, NULL, v->width, v->height, 0,
&c->last_dims);
c->chain.hw_tex = NULL;
}
else
#endif
d3d9_hlsl_renderchain_run(d3d, c, (const uint8_t*)d->frame
+ (size_t)v->y * d->pitch + (size_t)v->x * c->chain.pixel_size,
v->width, v->height, d->pitch, &c->last_dims);
@@ -8713,9 +8824,17 @@ static bool d3d9_hlsl_frame_body(void *data, const void *frame,
d3d->views.frame_dims = VIDEO_SCALE_PACK(frame_width, frame_height);
#ifdef D3D9_HW_RENDER
/* A core's texture has its views drawn straight from it, where they
* lie; a preset, whose chains each take a view of a software frame,
* draws it whole */
if (!hw_frame || !d3d9_hlsl_views_preset(d3d))
* lie, or through a preset from copies of them made on the GPU */
d3d->views.hw_frame = hw_frame;
if ( hw_frame && d3d9_hlsl_views_preset(d3d)
&& video_info->views.num_views && !d3d->views.hw_seen)
{
d3d->views.hw_seen = true;
RARCH_LOG("[D3D9 HLSL] Views' presets have each view of a hardware "
"frame copied out of the core's texture.\n");
}
/* None handed over: nothing to draw them from */
if (!hw_frame || d3d->hw.tex)
#endif
views = d3d9_hlsl_views_setup(d3d, video_info, msg,
d3d->menu && d3d->menu->enabled, &views_blend, &views_direct,
+11 -4
View File
@@ -316,6 +316,11 @@ SW_IN_PLACE_DRIVERS = ('glcore', 'vulkan')
WRITE_SW_RE = re.compile(
r'\] Views\' presets have each view of a software frame written '
r'straight in\.')
# d3d9_hlsl's presets on a core's Direct3D 9 texture: each view copied
# out of it on the GPU
D3D9_HW_COPY_RE = re.compile(
r'\] Views\' presets have each view of a hardware frame copied out '
r'of the core\'s texture\.')
# Drivers on shader model 4, where the rect presets' gathers of more than
# the red component do not compile.
SM4_DRIVERS = ('d3d10',)
@@ -1262,6 +1267,10 @@ def run_exact_case(retroarch, root, driver, case, suffix='',
if driver in WRITE_SW_VIEWS and direct_drawn(d, WRITE_SW_RE) != written:
errors.append('views %s straight in'
% ('not written' if written else 'written'))
if driver == 'd3d9_hlsl' and hw == 'd3d9' and mapopt != 'none' \
and direct_drawn(d, D3D9_HW_COPY_RE) != bool(preset):
errors.append('views %s out of the core\'s texture'
% ('copied' if not preset else 'not copied'))
shots = sorted(x for x in os.listdir(os.path.join(d, 'shots'))
if x.endswith('.png'))
if not shots:
@@ -1543,10 +1552,8 @@ def driver_jobs(retroarch, root, driver):
if driver == 'glcore':
runs.append(('-preset-blit', NO_COPY_IMAGE,
'framebuffer blits', HISTORY_PRESET))
# A D3D9 core's texture through a preset is drawn whole
if not (driver == 'd3d9_hlsl' and case[3] != 'off'):
runs += [(suffix, None, None, preset)
for suffix, preset in DRIVER_PRESETS.get(driver, [])]
runs += [(suffix, None, None, preset)
for suffix, preset in DRIVER_PRESETS.get(driver, [])]
first, lines, failed = None, [], 0
for suffix, env_extra, path, preset in runs:
errors, rows = run_exact_case(
+131
View File
@@ -872,6 +872,135 @@ static void ring_run(HWND w, bool ex, bool dupe)
IDirect3DDevice9_Release(d3d.dev);
}
/* A render target's top level, TEX_W x TEX_H, into @out */
static bool read_target(LPDIRECT3DDEVICE9 dev, LPDIRECT3DTEXTURE9 rt,
uint32_t *out)
{
D3DLOCKED_RECT lr;
unsigned y;
bool ok = false;
LPDIRECT3DTEXTURE9 sys = NULL;
LPDIRECT3DSURFACE9 s_rt = NULL, s_sys = NULL;
if (FAILED(IDirect3DDevice9_CreateTexture(dev, TEX_W, TEX_H, 1, 0,
D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, &sys, NULL)))
return false;
IDirect3DTexture9_GetSurfaceLevel(rt, 0, &s_rt);
IDirect3DTexture9_GetSurfaceLevel(sys, 0, &s_sys);
if ( SUCCEEDED(IDirect3DDevice9_GetRenderTargetData(dev, s_rt, s_sys))
&& SUCCEEDED(IDirect3DTexture9_LockRect(sys, 0, &lr, NULL,
D3DLOCK_READONLY)))
{
for (y = 0; y < TEX_H; y++)
memcpy(out + y * TEX_W, (uint8_t*)lr.pBits + y * lr.Pitch,
TEX_W * 4);
IDirect3DTexture9_UnlockRect(sys, 0);
ok = true;
}
IDirect3DSurface9_Release(s_rt);
IDirect3DSurface9_Release(s_sys);
IDirect3DTexture9_Release(sys);
return ok;
}
/* @rt holds frame 1's @v at its corner and black past it */
static bool target_holds(LPDIRECT3DDEVICE9 dev, LPDIRECT3DTEXTURE9 rt,
const struct retro_video_view *v)
{
static uint32_t buf[TEX_W * TEX_H];
unsigned x, y;
if (!read_target(dev, rt, buf))
return false;
for (y = 0; y < TEX_H; y++)
for (x = 0; x < TEX_W; x++)
{
uint32_t got = buf[y * TEX_W + x] & 0xffffff;
if (x < v->width && y < v->height
? got != px(1, v->x + x, v->y + y) : got != 0)
return false;
}
return true;
}
/* A hardware frame's view copied out of the core's render target into
* a preset chain's, d3d9_hlsl_views_hw_copy: its texels as they are at
* the corner, black past them, however large the view the slot held */
static void views_hw_copy_run(HWND w, bool ex)
{
static uint8_t frame[TEX_W * 4 * TEX_H];
struct retro_video_view big, small;
LPDIRECT3DDEVICE9 dev = make_device(w, ex, D3DSWAPEFFECT_DISCARD);
LPDIRECT3DTEXTURE9 core = NULL, slot = NULL, sys = NULL;
LPDIRECT3DSURFACE9 s_sys = NULL, s_core = NULL;
hlsl_renderchain_t *c = (hlsl_renderchain_t*)calloc(1, sizeof(*c));
D3DLOCKED_RECT lr;
unsigned y;
printf("%s: a hardware frame's views into a preset's chain\n",
ex ? "D3D9Ex" : "D3D9");
if (!dev || !c)
{
CHECK(false, "views copy device created");
free(c);
return;
}
write_frame(frame, TEX_W * 4, 1, TEX_W, TEX_H);
CHECK( SUCCEEDED(IDirect3DDevice9_CreateTexture(dev, TEX_W, TEX_H, 1,
D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT,
&core, NULL))
&& SUCCEEDED(IDirect3DDevice9_CreateTexture(dev, TEX_W, TEX_H, 1,
D3DUSAGE_RENDERTARGET, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT,
&slot, NULL))
&& SUCCEEDED(IDirect3DDevice9_CreateTexture(dev, TEX_W, TEX_H, 1,
0, D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM, &sys, NULL)),
"the core's and the chain's render targets");
if (core && slot && sys
&& SUCCEEDED(IDirect3DTexture9_LockRect(sys, 0, &lr, NULL, 0)))
{
for (y = 0; y < TEX_H; y++)
memcpy((uint8_t*)lr.pBits + y * lr.Pitch, frame + y * TEX_W * 4,
TEX_W * 4);
IDirect3DTexture9_UnlockRect(sys, 0);
IDirect3DTexture9_GetSurfaceLevel(sys, 0, &s_sys);
IDirect3DTexture9_GetSurfaceLevel(core, 0, &s_core);
IDirect3DDevice9_UpdateSurface(dev, s_sys, NULL, s_core, NULL);
IDirect3DSurface9_Release(s_sys);
IDirect3DSurface9_Release(s_core);
c->chain.dev = dev;
c->chain.prev.tex[0] = slot;
memset(&big, 0, sizeof(big));
big.x = 40;
big.y = 24;
big.width = 100;
big.height = 60;
small = big;
small.x = 8;
small.y = 4;
small.width = 50;
small.height = 30;
CHECK(d3d9_hlsl_views_hw_copy(c, core, &big)
&& target_holds(dev, slot, &big),
"a view copied texel for texel, black past it");
CHECK(c->chain.prev.last_dims[0] == VIDEO_SCALE_PACK(100, 60),
"the slot holds the view's size");
CHECK(d3d9_hlsl_views_hw_copy(c, core, &small)
&& target_holds(dev, slot, &small),
"a smaller view after it leaves black past it");
CHECK(d3d9_hlsl_views_hw_copy(c, core, &small)
&& target_holds(dev, slot, &small),
"and again the same");
c->chain.prev.tex[0] = NULL;
}
if (sys)
IDirect3DTexture9_Release(sys);
if (slot)
IDirect3DTexture9_Release(slot);
if (core)
IDirect3DTexture9_Release(core);
free(c);
IDirect3DDevice9_Release(dev);
}
int main(void)
{
HWND w = CreateWindowA("STATIC", "d3d9_ex_test", WS_OVERLAPPEDWINDOW,
@@ -894,6 +1023,8 @@ int main(void)
ring_run(w, true, false);
ring_run(w, false, true);
ring_run(w, true, true);
views_hw_copy_run(w, false);
views_hw_copy_run(w, true);
DestroyWindow(w);
return fails ? 1 : 0;
}