D3D12: views

The driver draws a core's views through video_views_draw.h as the
D3D11 driver does: straight from the frame, through a canvas for
anaglyph and interlaced, with the UI drawn once into a layer composited
into each eye. Its SDR pipes keep coverage in alpha, which the layer is
composited by. With a preset or HDR output the frame is drawn whole.

Windows-wine-views.yml runs the views end to end on it as well, on
Wine's vkd3d over lavapipe.
This commit is contained in:
libretroadmin committed 2026-10-12 18:25:46 +02:00
1 parent c05e75d71d
commit 204f03cfec
3 files changed
+673 -24

No files matched your search

+18 -14
View File
@@ -1,13 +1,14 @@
name: CI Windows views under Wine
# Views on the D3D11 driver, end to end: retroarch.exe cross-built with
# mingw-w64 runs the video_views test core under Wine, on a virtual
# display through Mesa's software rasteriser, and
# samples/cores/video_views/e2e/run.py checks the screen in each stereo
# mode, from the core's software frames and its D3D11 textures, with
# the menu laid out per eye and the pointer over it. Wine's own shader
# compiler can't take the driver's HLSL, so vkd3d's, from its release,
# is put in as D3DCompiler_47.dll.
# Views on the D3D11 and D3D12 drivers, end to end: retroarch.exe
# cross-built with mingw-w64 runs the video_views test core under Wine,
# on a virtual display through Mesa's software rasterisers (D3D12 on
# Wine's vkd3d over lavapipe), and samples/cores/video_views/e2e/run.py
# checks the screen in each stereo mode, from the core's software
# frames and its D3D11 and D3D12 textures, with the menu laid out per
# eye and the pointer over it. Wine's own shader compiler can't take the
# drivers' HLSL, so vkd3d's, from its release, is put in as
# D3DCompiler_47.dll.
on:
push:
@@ -15,6 +16,7 @@ on:
- master
paths:
- 'gfx/drivers/d3d11.c'
- 'gfx/drivers/d3d12.c'
- 'gfx/drivers/d3d_shaders/views_sm4.hlsl.h'
- 'gfx/video_views.*'
- 'gfx/video_views_draw.h'
@@ -25,6 +27,7 @@ on:
pull_request:
paths:
- 'gfx/drivers/d3d11.c'
- 'gfx/drivers/d3d12.c'
- 'gfx/drivers/d3d_shaders/views_sm4.hlsl.h'
- 'gfx/video_views.*'
- 'gfx/video_views_draw.h'
@@ -43,15 +46,15 @@ concurrency:
jobs:
windows-wine-views:
name: Views on D3D11 under Wine
name: Views on D3D11 and D3D12 under Wine
runs-on: ubuntu-24.04
timeout-minutes: 90
timeout-minutes: 150
steps:
- name: Install dependencies
timeout-minutes: 20
run: |
pkgs="build-essential ccache gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 mingw-w64-tools wine64 xvfb imagemagick libgl1-mesa-dri autoconf automake libtool flex bison pkg-config libvulkan-dev spirv-headers python3"
pkgs="build-essential ccache gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 mingw-w64-tools wine64 xvfb imagemagick libgl1-mesa-dri mesa-vulkan-drivers libvulkan1 autoconf automake libtool flex bison pkg-config libvulkan-dev spirv-headers python3"
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
@@ -109,7 +112,7 @@ jobs:
cp .libs/libvkd3d-shader-1.dll .libs/libvkd3d-1.dll \
"$GITHUB_WORKSPACE/"
- name: Build retroarch.exe with mingw-w64 (D3D11, slang)
- name: Build retroarch.exe with mingw-w64 (D3D11, D3D12, slang)
shell: bash
run: |
set -eu
@@ -129,13 +132,14 @@ jobs:
done
make -C samples/cores/video_views DLL=1 CC=x86_64-w64-mingw32-gcc
- name: Views on D3D11, end to end (Wine, Xvfb, llvmpipe)
- name: Views on D3D11 and D3D12, end to end (Wine, Xvfb, llvmpipe, lavapipe)
shell: bash
run: |
set -eu
trap 'wineserver -k >/dev/null 2>&1 || true' EXIT
export LIBGL_ALWAYS_SOFTWARE=1
export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.json
# The prefix is made before the first case is timed.
WINEDEBUG=-all wineboot -i > /dev/null 2>&1 || true
RA_E2E_WINE=1 python3 samples/cores/video_views/e2e/run.py \
./retroarch.exe "$RUNNER_TEMP/views-e2e" d3d11
./retroarch.exe "$RUNNER_TEMP/views-e2e" d3d11 d3d12
+649 -8
View File
@@ -470,6 +470,43 @@ typedef struct
float values[12];
bool seen;
} frame_rect;
/* Views, drawn straight from the frame as the stock chain draws it;
* with a preset the frame is drawn whole. A view's stock chain is
* the driver itself. The canvas holds both eyes for anaglyph and
* interlaced, the UI layer the UI drawn once for both eyes, both in
* the swap chain's format, which the pipes are made for (format).
* Each draw's constants take a slot of cb, from the first each
* frame: the last frame is done with them. src_dims is the texture
* the frame is in, frame_dims the frame. */
struct
{
video_views_chains_t chains;
d3d12_texture_t canvas;
d3d12_texture_t ui;
video_views_target_t canvas_target;
video_views_target_t ui_target;
video_views_ui_dims_t saved;
D3D12_VIEWPORT saved_viewport;
D3D12_RECT saved_scissor;
D3D12PipelineState rect;
D3D12PipelineState layer;
D3D12PipelineState anaglyph;
D3D12PipelineState interlaced;
DXGI_FORMAT format;
D3D12Resource cb;
uint8_t *cb_mapped;
D3D12_GPU_VIRTUAL_ADDRESS cb_va;
unsigned cb_next;
unsigned src_dims;
unsigned frame_dims;
/* For get_flags on the main thread: whether views may be drawn,
* and whether the last frame with a map was drawn whole. */
retro_atomic_int_t allowed;
retro_atomic_int_t fallback;
bool failed;
bool active;
bool direct_seen;
} views;
IDXGIAdapter1 *adapters[D3D12_MAX_GPU_COUNT];
struct string_list *gpu_list;
@@ -850,6 +887,8 @@ static void d3d12_frame_import_deinit(d3d12_video_t *d3d12);
static void d3d12_record_free(d3d12_video_t *d3d12);
static void d3d12_sw_fb_free(d3d12_video_t *d3d12);
static void d3d12_record_teardown(d3d12_video_t *d3d12);
static void d3d12_views_release(d3d12_video_t *d3d12);
static void d3d12_views_publish(d3d12_video_t *d3d12);
#define D3D12_ROLLING_SCANLINE_SIMULATION
@@ -2973,7 +3012,8 @@ static bool d3d12_font_get_line_metrics(void* data, struct font_line_metrics **m
static uint32_t d3d12_get_flags(void *data)
{
uint32_t flags = 0;
uint32_t flags = 0;
d3d12_video_t *d3d12 = (d3d12_video_t*)data;
#if defined(HAVE_WINDOW) && defined(HAVE_MONITOR)
/* a borderless fullscreen toggle restyles the window; see
@@ -2995,6 +3035,10 @@ static uint32_t d3d12_get_flags(void *data)
BIT32_SET(flags, GFX_CTX_FLAGS_FAST_TOGGLE_SHADERS);
BIT32_SET(flags, GFX_CTX_FLAGS_SCREEN_10BPC_SOURCE);
#endif
if (d3d12 && retro_atomic_load_acquire_int(&d3d12->views.allowed))
BIT32_SET(flags, GFX_CTX_FLAGS_VIDEO_VIEWS);
if (d3d12 && retro_atomic_load_acquire_int(&d3d12->views.fallback))
BIT32_SET(flags, GFX_CTX_FLAGS_VIDEO_VIEWS_FALLBACK);
return flags;
}
@@ -4708,6 +4752,9 @@ static bool d3d12_gfx_init_pipelines(d3d12_video_t* d3d12)
#endif
desc.BlendState.RenderTarget[0] = d3d12_blend_enable_desc;
/* Alpha keeps coverage, which the views UI layer is composited by;
* the swap chain's alpha is not shown. */
desc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE;
/* The format these draw into, not an assumption about it. Every
* pipeline below targets the swapchain, and the swapchain is not
* always 8-bit: with HDR off and video_swapchain_bit_depth set to
@@ -4776,6 +4823,7 @@ static bool d3d12_gfx_init_pipelines(d3d12_video_t* d3d12)
&& d3d_compile(shader, sizeof(shader), NULL, "PSMain", "ps_5_0", &ps_code))
{
desc.BlendState.RenderTarget[0] = d3d12_blend_enable_desc;
desc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE;
desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
desc.InputLayout.pInputElementDescs = inputElementDesc;
desc.InputLayout.NumElements = countof(inputElementDesc);
@@ -4941,6 +4989,7 @@ static void d3d12_gfx_free(void* data)
return;
d3d12_queue_drain(d3d12);
d3d12_views_release(d3d12);
#ifdef D3D12_FRAME_IMPORT
d3d12_frame_import_deinit(d3d12);
#endif
@@ -5568,7 +5617,8 @@ static void d3d12_init_descriptors(d3d12_video_t* d3d12)
* + 1 reserved at the end for the HDR readback tonemap target used by
* d3d12_gfx_read_viewport(). The reserved slot is at fixed offset
* (countof(renderTargets) + GFX_MAX_SHADERS * 2). */
d3d12->desc.rtv_heap.desc.NumDescriptors = countof(d3d12->chain.renderTargets) + GFX_MAX_SHADERS * 2 + 1;
d3d12->desc.rtv_heap.desc.NumDescriptors = countof(d3d12->chain.renderTargets) + GFX_MAX_SHADERS * 2 + 1
+ 2; /* the views canvas and UI layer (d3d12_views_make) */
d3d12->desc.rtv_heap.desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
d3d12_init_descriptor_heap(d3d12->device, &d3d12->desc.rtv_heap);
@@ -6215,6 +6265,7 @@ static void *d3d12_gfx_init(const video_info_t* video)
#ifdef D3D12_FRAME_IMPORT
d3d12_frame_import_init(d3d12);
#endif
d3d12_views_publish(d3d12);
return d3d12;
error:
@@ -6992,6 +7043,506 @@ static bool d3d12_frame_mem_window(d3d12_video_t *d3d12,
return true;
}
/* Views: what each draw's constants slot holds, and how many slots a
* frame has: every placement, the blend and both eyes' UI layer. */
typedef struct
{
math_matrix_4x4 mvp;
float rect[4];
float clamp[4];
} d3d12_views_ubo_t;
#define D3D12_VIEWS_CB_SLOTS (VIDEO_VIEWS_MAX_PLACEMENTS + 3)
/* What a frame's placement draws read */
struct d3d12_views_draw
{
const video_frame_info_t *video_info;
unsigned src_dims;
};
static void d3d12_views_draw_direct(d3d12_video_t *d3d12,
const struct d3d12_views_draw *d, unsigned view,
const video_views_rect_t *dst, const video_views_rect_t *cut);
static bool d3d12_views_make(d3d12_video_t *d3d12, d3d12_texture_t *t,
unsigned dims);
static bool d3d12_views_retire(d3d12_video_t *d3d12, d3d12_texture_t *t);
/* The views compositor, with these calls bound in. Views are drawn
* straight from the frame only, so a view's chain is the driver. */
#define VV_FN(name) d3d12_vv_##name
#define VV_DRV d3d12_video_t
#define VV_CHAIN d3d12_video_t
#define VV_CREATE_STOCK(drv) ((void*)(drv))
#define VV_SYNC_PARAMS(drv, c) ((void)0)
#define VV_DRAW struct d3d12_views_draw
#define VV_DIRECT(p, d, v, r, c) d3d12_views_draw_direct(p, d, v, r, c)
#define VV_PASS(p, d, f, r, c, a) ((void)0)
#define VV_TARGET d3d12_texture_t
#define VV_TARGET_MAKE(drv, t, dims) d3d12_views_make(drv, t, dims)
#define VV_TARGET_RETIRE(drv, t) d3d12_views_retire(drv, t)
#include "../video_views_draw.h"
/* The swap chain's format, which views draw in */
static DXGI_FORMAT d3d12_views_format(const d3d12_video_t *d3d12)
{
#ifdef HAVE_DXGI_HDR
return d3d12->chain.formats[d3d12->chain.bit_depth];
#else
return DXGI_FORMAT_R8G8B8A8_UNORM;
#endif
}
/* Views: not with a preset, HDR output, or without their pipes */
static bool d3d12_views_allowed(const d3d12_video_t *d3d12)
{
return !d3d12->views.failed
&& !(d3d12->shader_preset && d3d12->shader_preset->passes)
#ifdef HAVE_DXGI_HDR
&& !(d3d12->flags & D3D12_ST_FLAG_HDR_ENABLE)
#endif
;
}
static void d3d12_views_publish(d3d12_video_t *d3d12)
{
retro_atomic_store_release_int(&d3d12->views.allowed,
d3d12_views_allowed(d3d12) ? 1 : 0);
}
static void d3d12_views_release_pipes(d3d12_video_t *d3d12)
{
Release(d3d12->views.rect);
Release(d3d12->views.layer);
Release(d3d12->views.anaglyph);
Release(d3d12->views.interlaced);
d3d12->views.rect = NULL;
d3d12->views.layer = NULL;
d3d12->views.anaglyph = NULL;
d3d12->views.interlaced = NULL;
d3d12->views.format = DXGI_FORMAT_UNKNOWN;
}
/* After a queue drain */
static void d3d12_views_release(d3d12_video_t *d3d12)
{
d3d12_views_release_pipes(d3d12);
Release(d3d12->views.cb);
d3d12->views.cb = NULL;
d3d12->views.cb_mapped = NULL;
d3d12_release_texture(&d3d12->views.canvas);
d3d12_release_texture(&d3d12->views.ui);
}
/* The pipes views draw with, made for the swap chain's format on first
* use and again when it changes, and the constants they read. False
* when they can't be: views are then drawn whole. */
static bool d3d12_views_init(d3d12_video_t *d3d12)
{
static const char shader[] =
#include "d3d_shaders/views_sm4.hlsl.h"
;
static const D3D12_INPUT_ELEMENT_DESC input[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0,
offsetof(d3d12_vertex_t, position),
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0,
offsetof(d3d12_vertex_t, texcoord),
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0,
offsetof(d3d12_vertex_t, color),
D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
D3D12_GRAPHICS_PIPELINE_STATE_DESC desc;
D3DBlob vs = NULL, ps_rect = NULL, ps_layer = NULL;
D3DBlob ps_anaglyph = NULL, ps_interlaced = NULL;
DXGI_FORMAT format = d3d12_views_format(d3d12);
if (d3d12->views.failed)
return false;
if (d3d12->views.rect && d3d12->views.format == format)
return true;
d3d12_views_release_pipes(d3d12);
if (!d3d12->views.cb)
{
D3D12_RANGE read_range;
void *mapped = NULL;
read_range.Begin = 0;
read_range.End = 0;
d3d12->views.cb_va = d3d12_create_buffer(d3d12->device,
d3d12->cpu_write_heap,
D3D12_VIEWS_CB_SLOTS * D3D12_MESH_CB_SIZE, &d3d12->views.cb);
if ( d3d12->views.cb
&& SUCCEEDED(D3D12Map(d3d12->views.cb, 0, &read_range, &mapped)))
d3d12->views.cb_mapped = (uint8_t*)mapped;
}
if ( d3d12->views.cb_mapped
&& d3d_compile(shader, sizeof(shader), NULL, "VSMain", "vs_5_0", &vs)
&& d3d_compile(shader, sizeof(shader), NULL, "PSRect", "ps_5_0",
&ps_rect)
&& d3d_compile(shader, sizeof(shader), NULL, "PSLayer", "ps_5_0",
&ps_layer)
&& d3d_compile(shader, sizeof(shader), NULL, "PSAnaglyph", "ps_5_0",
&ps_anaglyph)
&& d3d_compile(shader, sizeof(shader), NULL, "PSInterlaced",
"ps_5_0", &ps_interlaced))
{
memset(&desc, 0, sizeof(desc));
desc.pRootSignature = d3d12->desc.rootSignature;
desc.BlendState.RenderTarget[0] = d3d12_blend_disable_desc;
desc.RTVFormats[0] = format;
desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
desc.InputLayout.pInputElementDescs = input;
desc.InputLayout.NumElements = countof(input);
d3d12_init_pipeline(d3d12->device, vs, ps_rect, NULL, &desc,
&d3d12->views.rect);
d3d12_init_pipeline(d3d12->device, vs, ps_anaglyph, NULL, &desc,
&d3d12->views.anaglyph);
d3d12_init_pipeline(d3d12->device, vs, ps_interlaced, NULL, &desc,
&d3d12->views.interlaced);
/* The UI layer, premultiplied */
desc.BlendState.RenderTarget[0] = d3d12_blend_enable_desc;
desc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_ONE;
d3d12_init_pipeline(d3d12->device, vs, ps_layer, NULL, &desc,
&d3d12->views.layer);
}
Release(vs);
Release(ps_rect);
Release(ps_layer);
Release(ps_anaglyph);
Release(ps_interlaced);
if ( d3d12->views.rect && d3d12->views.layer
&& d3d12->views.anaglyph && d3d12->views.interlaced)
{
d3d12->views.format = format;
return true;
}
RARCH_WARN("[D3D12] Views can't be drawn: their pipes failed.\n");
d3d12_views_release_pipes(d3d12);
d3d12->views.failed = true;
return false;
}
/* Let go behind this frame, which may still draw from it */
static bool d3d12_views_retire(d3d12_video_t *d3d12, d3d12_texture_t *t)
{
d3d12_texture_t *copy = (d3d12_texture_t*)malloc(sizeof(*copy));
if (!copy)
return false;
*copy = *t;
d3d12_retire(d3d12, copy, NULL, d3d12->queue.fenceValue + 1);
memset(t, 0, sizeof(*t));
return true;
}
/* A render target in the swap chain's format sized dims, at rest as
* one, with an RTV slot of its own past the readback's */
static bool d3d12_views_make(d3d12_video_t *d3d12, d3d12_texture_t *t,
unsigned dims)
{
unsigned slot = countof(d3d12->chain.renderTargets)
+ GFX_MAX_SHADERS * 2 + 1 + (t == &d3d12->views.ui ? 1 : 0);
memset(t, 0, sizeof(*t));
t->desc.Width = VIDEO_SCALE_W(dims);
t->desc.Height = VIDEO_SCALE_H(dims);
t->desc.Format = d3d12_views_format(d3d12);
t->desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
t->srv_heap = &d3d12->desc.srv_heap;
t->rt_view.ptr = d3d12->desc.rtv_heap.cpu.ptr
+ slot * d3d12->desc.rtv_heap.stride;
d3d12_init_texture(d3d12->device, t);
if (t->handle && t->desc.Format == d3d12_views_format(d3d12))
return true;
RARCH_WARN("[D3D12] Views can't draw into a %ux%u image. "
"Drawing without it.\n", VIDEO_SCALE_W(dims), VIDEO_SCALE_H(dims));
d3d12_release_texture(t);
memset(t, 0, sizeof(*t));
return false;
}
/* The next constants slot, written: @mvp, the part of the texture
* drawn and where its filtering stops. 0 when the frame has used them
* all, which its placements can't. */
static D3D12_GPU_VIRTUAL_ADDRESS d3d12_views_cb(d3d12_video_t *d3d12,
const math_matrix_4x4 *mvp, const float *rect, const float *clamp)
{
d3d12_views_ubo_t *ubo;
unsigned slot = d3d12->views.cb_next;
if (slot >= D3D12_VIEWS_CB_SLOTS)
return 0;
d3d12->views.cb_next = slot + 1;
ubo = (d3d12_views_ubo_t*)(d3d12->views.cb_mapped
+ slot * D3D12_MESH_CB_SIZE);
ubo->mvp = *mvp;
memcpy(ubo->rect, rect, sizeof(ubo->rect));
memcpy(ubo->clamp, clamp, sizeof(ubo->clamp));
return d3d12->views.cb_va + slot * D3D12_MESH_CB_SIZE;
}
/* A top-left rectangle as a viewport and as a scissor */
static void d3d12_views_vp(const video_views_rect_t *r, D3D12_VIEWPORT *vp,
D3D12_RECT *sc)
{
vp->TopLeftX = (float)VIDEO_POS_X(r->pos);
vp->TopLeftY = (float)VIDEO_POS_Y(r->pos);
vp->Width = (float)VIDEO_SCALE_W(r->dims);
vp->Height = (float)VIDEO_SCALE_H(r->dims);
vp->MinDepth = 0.0f;
vp->MaxDepth = 1.0f;
sc->left = VIDEO_POS_X(r->pos);
sc->top = VIDEO_POS_Y(r->pos);
sc->right = sc->left + (LONG)VIDEO_SCALE_W(r->dims);
sc->bottom = sc->top + (LONG)VIDEO_SCALE_H(r->dims);
}
/* View @view of the frame over @dst, cut to @cut, as the stock chain
* draws a copy of it: the same texels, the same filter, stopping at the
* view's edge as at a texture's. */
static void d3d12_views_draw_direct(d3d12_video_t *d3d12,
const struct d3d12_views_draw *d, unsigned view,
const video_views_rect_t *dst, const video_views_rect_t *cut)
{
D3D12_VIEWPORT vp, vp_cut;
D3D12_RECT sc_dst, sc;
D3D12_GPU_VIRTUAL_ADDRESS cb;
float rect[4], clamp[4];
D3D12GraphicsCommandList cmd = d3d12->queue.cmd;
const struct retro_video_view *v = &d->video_info->views.views[view];
float tw = (float)VIDEO_SCALE_W(d->src_dims);
float th = (float)VIDEO_SCALE_H(d->src_dims);
rect[0] = (float)v->x / tw;
rect[1] = (float)v->y / th;
rect[2] = (float)(v->x + v->width) / tw;
rect[3] = (float)(v->y + v->height) / th;
clamp[0] = rect[0] + 0.5f / tw;
clamp[1] = rect[1] + 0.5f / th;
clamp[2] = rect[2] - 0.5f / tw;
clamp[3] = rect[3] - 0.5f / th;
if (!(cb = d3d12_views_cb(d3d12, &d3d12->mvp, rect, clamp)))
return;
d3d12_views_vp(dst, &vp, &sc_dst);
d3d12_views_vp(cut, &vp_cut, &sc);
cmd->lpVtbl->SetGraphicsRootConstantBufferView(cmd, ROOT_ID_UBO, cb);
cmd->lpVtbl->RSSetViewports(cmd, 1, &vp);
cmd->lpVtbl->RSSetScissorRects(cmd, 1, &sc);
cmd->lpVtbl->DrawInstanced(cmd, 4, 1, 0, 0);
}
/* Draws @view over the whole of @r through @pipe, unrotated */
static void d3d12_views_quad(d3d12_video_t *d3d12, D3D12PipelineState pipe,
D3D12_GPU_DESCRIPTOR_HANDLE view, D3D12_GPU_DESCRIPTOR_HANDLE sampler,
const video_views_rect_t *r)
{
static const float whole[4] = { 0.0f, 0.0f, 1.0f, 1.0f };
D3D12_VIEWPORT vp;
D3D12_RECT sc;
D3D12GraphicsCommandList cmd = d3d12->queue.cmd;
D3D12_GPU_VIRTUAL_ADDRESS cb = d3d12_views_cb(d3d12, &d3d12->mvp_no_rot,
whole, whole);
if (!cb)
return;
d3d12_views_vp(r, &vp, &sc);
cmd->lpVtbl->SetPipelineState(cmd, pipe);
cmd->lpVtbl->SetGraphicsRootConstantBufferView(cmd, ROOT_ID_UBO, cb);
cmd->lpVtbl->SetGraphicsRootDescriptorTable(cmd, ROOT_ID_TEXTURE_T, view);
cmd->lpVtbl->SetGraphicsRootDescriptorTable(cmd, ROOT_ID_SAMPLER_T,
sampler);
cmd->lpVtbl->RSSetViewports(cmd, 1, &vp);
cmd->lpVtbl->RSSetScissorRects(cmd, 1, &sc);
cmd->lpVtbl->DrawInstanced(cmd, 4, 1, 0, 0);
}
/* The root signature and quad every views draw uses */
static void d3d12_views_bind(d3d12_video_t *d3d12)
{
D3D12GraphicsCommandList cmd = d3d12->queue.cmd;
cmd->lpVtbl->SetGraphicsRootSignature(cmd, d3d12->desc.rootSignature);
cmd->lpVtbl->IASetPrimitiveTopology(cmd,
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
cmd->lpVtbl->IASetVertexBuffers(cmd, 0, 1, &d3d12->frame.vbo_view);
}
/* Whether this frame draws its views, with each drawn view's chain
* picked, and in *blend whether they draw into the canvas. */
static bool d3d12_views_setup(d3d12_video_t *d3d12,
const video_frame_info_t *video_info, const void *frame,
bool *blend, d3d12_video_t **chains, video_views_rect_t *rects)
{
bool views;
const video_views_layout_t *layout = &video_info->views_layout;
bool offscreen = video_info->views.num_views && layout->offscreen;
d3d12->views.cb_next = 0;
/* A target made for another format is made again. */
if ( d3d12->views.format
&& d3d12->views.format != d3d12_views_format(d3d12))
{
d3d12_vv_target_drop(d3d12, &d3d12->views.canvas_target,
&d3d12->views.canvas);
d3d12_vv_target_drop(d3d12, &d3d12->views.ui_target,
&d3d12->views.ui);
}
views = video_info->views.num_views
&& d3d12_views_allowed(d3d12)
&& d3d12_views_init(d3d12);
/* Anaglyph and interlaced draw both eyes into the canvas, or without
* one only the left eye's area into the window. */
*blend = views && offscreen
&& d3d12_vv_target_ensure(d3d12, &d3d12->views.canvas_target,
&d3d12->views.canvas, layout->canvas_dims);
/* A real frame without the blend modes frees the canvas. */
if (frame && !offscreen)
d3d12_vv_target_drop(d3d12, &d3d12->views.canvas_target,
&d3d12->views.canvas);
/* A rolling scanline is drawn by the frame's own draw. */
views = views
&& !(video_info->shader_subframes > 1 && video_info->scan_subframes)
&& video_views_fit_frame(&video_info->views,
d3d12->views.frame_dims)
&& d3d12_vv_pick_chains(d3d12, &d3d12->views.chains,
&video_info->views, layout, true, *blend || !layout->offscreen,
*blend ? layout->canvas_dims : video_info->dims, chains, rects);
*blend = views && *blend;
if (views && !d3d12->views.direct_seen)
{
d3d12->views.direct_seen = true;
RARCH_LOG("[D3D12] Views drawn straight from the frame.\n");
}
return views;
}
/* Draws the views into @rtv, sized as the window, or with @blend into
* the canvas, whose halves one pass then blends into @rtv. */
static void d3d12_views_render(d3d12_video_t *d3d12,
const video_frame_info_t *video_info, d3d12_video_t **chains,
D3D12_CPU_DESCRIPTOR_HANDLE rtv, D3D12_GPU_DESCRIPTOR_HANDLE src,
bool blend)
{
static const float clear[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
struct d3d12_views_draw d;
D3D12GraphicsCommandList cmd = d3d12->queue.cmd;
const video_views_layout_t *layout = &video_info->views_layout;
d3d12_views_bind(d3d12);
if (blend)
{
cmd->lpVtbl->OMSetRenderTargets(cmd, 1, &d3d12->views.canvas.rt_view,
FALSE, NULL);
cmd->lpVtbl->ClearRenderTargetView(cmd, d3d12->views.canvas.rt_view,
clear, 0, NULL);
}
cmd->lpVtbl->SetPipelineState(cmd, d3d12->views.rect);
cmd->lpVtbl->SetGraphicsRootDescriptorTable(cmd, ROOT_ID_TEXTURE_T, src);
cmd->lpVtbl->SetGraphicsRootDescriptorTable(cmd, ROOT_ID_SAMPLER_T,
d3d12->samplers[RARCH_FILTER_UNSPEC][RARCH_WRAP_EDGE]);
d.video_info = video_info;
d.src_dims = d3d12->views.src_dims;
d3d12_vv_draw_placements(d3d12, &d, layout, chains,
blend || !layout->offscreen,
blend ? layout->canvas_dims : video_info->dims, true);
if (blend)
{
video_views_rect_t whole;
whole.pos = 0;
whole.dims = video_info->dims;
D3D12_RESOURCE_TRANSITION(cmd, d3d12->views.canvas.handle,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
cmd->lpVtbl->OMSetRenderTargets(cmd, 1, &rtv, FALSE, NULL);
d3d12_views_quad(d3d12,
(layout->stereo_mode == VIDEO_STEREO_MODE_INTERLACED)
? d3d12->views.interlaced : d3d12->views.anaglyph,
d3d12->views.canvas.gpu_descriptor[0],
d3d12->samplers[RARCH_FILTER_NEAREST][RARCH_WRAP_EDGE], &whole);
D3D12_RESOURCE_TRANSITION(cmd, d3d12->views.canvas.handle,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET);
}
}
/* Moves UI drawing into the UI layer, laid out at one eye's size */
static void d3d12_views_ui_begin(d3d12_video_t *d3d12,
video_frame_info_t *video_info)
{
static const float clear[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
D3D12GraphicsCommandList cmd = d3d12->queue.cmd;
const video_views_layout_t *layout = &video_info->views_layout;
unsigned w = VIDEO_SCALE_W(layout->ui_dims);
unsigned h = VIDEO_SCALE_H(layout->ui_dims);
cmd->lpVtbl->OMSetRenderTargets(cmd, 1, &d3d12->views.ui.rt_view,
FALSE, NULL);
cmd->lpVtbl->ClearRenderTargetView(cmd, d3d12->views.ui.rt_view, clear,
0, NULL);
/* Menus and widgets size from video_info->dims, fonts from the
* chain's viewport and vp.full_dims. */
video_views_ui_enter(&d3d12->views.saved, &video_info->dims,
&d3d12->vp.full_dims, layout->ui_dims);
d3d12->views.saved_viewport = d3d12->chain.viewport;
d3d12->views.saved_scissor = d3d12->chain.scissorRect;
d3d12->chain.viewport.TopLeftX = 0.0f;
d3d12->chain.viewport.TopLeftY = 0.0f;
d3d12->chain.viewport.Width = (float)w;
d3d12->chain.viewport.Height = (float)h;
d3d12->chain.scissorRect.left = 0;
d3d12->chain.scissorRect.top = 0;
d3d12->chain.scissorRect.right = (LONG)w;
d3d12->chain.scissorRect.bottom = (LONG)h;
cmd->lpVtbl->RSSetViewports(cmd, 1, &d3d12->chain.viewport);
cmd->lpVtbl->RSSetScissorRects(cmd, 1, &d3d12->chain.scissorRect);
}
/* Back onto @rtv, and the UI layer into each eye's area. It was drawn
* over transparent black with the alpha the SDR pipes keep, so it is
* premultiplied. */
static void d3d12_views_ui_end(d3d12_video_t *d3d12,
video_frame_info_t *video_info, D3D12_CPU_DESCRIPTOR_HANDLE rtv)
{
unsigned a;
D3D12GraphicsCommandList cmd = d3d12->queue.cmd;
const video_views_layout_t *layout = &video_info->views_layout;
/* Only full side by side places it unscaled. */
D3D12_GPU_DESCRIPTOR_HANDLE sampler = d3d12->samplers[
(layout->stereo_mode == VIDEO_STEREO_MODE_SBS_FULL)
? RARCH_FILTER_NEAREST : RARCH_FILTER_LINEAR][RARCH_WRAP_EDGE];
d3d12->chain.viewport = d3d12->views.saved_viewport;
d3d12->chain.scissorRect = d3d12->views.saved_scissor;
video_views_ui_leave(&d3d12->views.saved, &video_info->dims,
&d3d12->vp.full_dims);
D3D12_RESOURCE_TRANSITION(cmd, d3d12->views.ui.handle,
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
cmd->lpVtbl->OMSetRenderTargets(cmd, 1, &rtv, FALSE, NULL);
d3d12_views_bind(d3d12);
for (a = 0; a < layout->num_areas; a++)
d3d12_views_quad(d3d12, d3d12->views.layer,
d3d12->views.ui.gpu_descriptor[0], sampler, &layout->areas[a]);
D3D12_RESOURCE_TRANSITION(cmd, d3d12->views.ui.handle,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
D3D12_RESOURCE_STATE_RENDER_TARGET);
cmd->lpVtbl->RSSetViewports(cmd, 1, &d3d12->chain.viewport);
cmd->lpVtbl->RSSetScissorRects(cmd, 1, &d3d12->chain.scissorRect);
}
static void d3d12_set_view_count(void *data, unsigned count)
{
unsigned i;
d3d12_video_t *d3d12 = (d3d12_video_t*)data;
if (!d3d12)
return;
d3d12->views.chains.count = MIN(count, RETRO_VIDEO_VIEWS_MAX);
for (i = d3d12->views.chains.count; i < RETRO_VIDEO_VIEWS_MAX; i++)
d3d12->views.chains.stock[i] = NULL;
}
static bool d3d12_gfx_frame(
void* data,
const void* frame,
@@ -7017,6 +7568,12 @@ static bool d3d12_gfx_frame(
bool menu_is_alive = (video_info->menu_st_flags & MENU_ST_FLAG_ALIVE) ? true : false;
bool overlay_behind_menu = video_info->overlay_behind_menu;
D3D12GraphicsCommandList cmd;
d3d12_video_t *view_chains[RETRO_VIDEO_VIEWS_MAX];
video_views_rect_t view_rects[RETRO_VIDEO_VIEWS_MAX];
bool views = false;
bool views_blend = false;
bool views_per_eye = false;
bool views_ui = false;
bool message_visible;
bool draw_direct = false;
#ifdef HAVE_GFX_WIDGETS
@@ -7301,7 +7858,7 @@ static bool d3d12_gfx_frame(
/* Custom viewport doesn't call apply_state_changes, so we can't rely on this for now */
if (d3d12->resize_viewport)
#endif
d3d12_update_viewport(d3d12, false);
d3d12_update_viewport(d3d12, d3d12->views.active);
cmd->lpVtbl->IASetPrimitiveTopology(cmd,
D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
@@ -7462,6 +8019,8 @@ static bool d3d12_gfx_frame(
}
}
}
d3d12->views.src_dims = tex_dims;
d3d12->views.frame_dims = VIDEO_SCALE_PACK(width, height);
#ifdef HAVE_SLANG
slang_rect_values(d3d12->frame_rect.values,
VIDEO_SCALE_PACK(width, height), 0, tex_dims);
@@ -7563,6 +8122,18 @@ static bool d3d12_gfx_frame(
}
cmd->lpVtbl->IASetVertexBuffers(cmd, 0, 1, &d3d12->frame.vbo_view);
views = d3d12_views_setup(d3d12, video_info, frame, &views_blend,
view_chains, view_rects);
/* d3d12->vp is the whole window while views draw. */
if (views != d3d12->views.active)
{
d3d12->views.active = views;
d3d12_update_viewport(d3d12, views);
}
retro_atomic_store_release_int(&d3d12->views.fallback,
(video_info->views.num_views && !views) ? 1 : 0);
d3d12_views_publish(d3d12);
texture = d3d12->frame.texture;
if (d3d12->shader_preset && video_info->shader_active)
@@ -7907,7 +8478,7 @@ static bool d3d12_gfx_frame(
D3D12_RESOURCE_STATE_COPY_SOURCE);
}
if (texture)
if (texture && !views)
{
cmd->lpVtbl->SetGraphicsRootSignature(cmd,
d3d12->desc.rootSignature);
@@ -8062,6 +8633,13 @@ static bool d3d12_gfx_frame(
* COPY_SOURCE, and expects to find it there. It is made readable for
* this one draw and put back, on every frame that draws from it,
* repeats included. */
/* Views read the core's texture as the stock draw does. */
if (views && d3d12->hw_direct.current)
{
draw_direct = true;
d3d12->hw_v2.frame_took_texture = true;
}
if (draw_direct)
D3D12_RESOURCE_TRANSITION(
cmd,
@@ -8069,7 +8647,13 @@ static bool d3d12_gfx_frame(
D3D12_RESOURCE_STATE_COPY_SOURCE,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
cmd->lpVtbl->DrawInstanced(cmd, 4, 1, 0, 0);
if (views)
d3d12_views_render(d3d12, video_info, view_chains,
d3d12->chain.desc_handles[d3d12->chain.frame_index],
draw_direct ? d3d12->hw_direct.current_gpu
: d3d12->frame.texture[0].gpu_descriptor[0], views_blend);
else
cmd->lpVtbl->DrawInstanced(cmd, 4, 1, 0, 0);
if (draw_direct)
D3D12_RESOURCE_TRANSITION(
@@ -8195,6 +8779,29 @@ static bool d3d12_gfx_frame(
}
#endif
/* As the main thread laid the UI out, also in a frame drawn whole:
* it lays out later frames whole once it reads the fallback. */
views_per_eye = video_views_ui_per_eye(&video_info->views,
&video_info->views_layout, d3d12_views_allowed(d3d12));
/* A real frame without it frees the UI layer. */
if (frame && !views_per_eye)
d3d12_vv_target_drop(d3d12, &d3d12->views.ui_target,
&d3d12->views.ui);
/* Drawn and composited only when there is UI; overlays are hidden. */
views_ui = VIDEO_VIEWS_UI_WANTED(views_per_eye,
(d3d12->flags & D3D12_ST_FLAG_MENU_ENABLE), msg, statistics_show,
#ifdef HAVE_GFX_WIDGETS
widgets_active && gfx_widgets_visible(video_info)
#else
false
#endif
)
&& d3d12->views.rect
&& d3d12_vv_target_ensure(d3d12, &d3d12->views.ui_target,
&d3d12->views.ui, video_info->views_layout.ui_dims);
if (views_ui)
d3d12_views_ui_begin(d3d12, video_info);
#ifdef HAVE_DXGI_HDR
/* When HDR is on the menu/overlay draws into the HDR-format
* back_buffer. D3D12 requires PSO RTVFormats to match the bound
@@ -8282,6 +8889,33 @@ static bool d3d12_gfx_frame(
cmd->lpVtbl->RSSetViewports(cmd, 1, &d3d12->chain.viewport);
cmd->lpVtbl->RSSetScissorRects(cmd, 1, &d3d12->chain.scissorRect);
}
else if (d3d12->views.active)
{
/* d3d12->vp is the whole window while views draw, so fit the
* menu to this pass's target here. */
struct video_viewport vp;
D3D12_VIEWPORT menu_vp;
D3D12_RECT menu_sc;
memset(&vp, 0, sizeof(vp));
vp.full_dims = VIDEO_SCALE_PACK(
(unsigned)d3d12->chain.viewport.Width,
(unsigned)d3d12->chain.viewport.Height);
video_driver_update_viewport(&vp, false,
(d3d12->flags & D3D12_ST_FLAG_KEEP_ASPECT) ? true : false,
false);
menu_vp.TopLeftX = (float)VIDEO_POS_X(vp.pos);
menu_vp.TopLeftY = (float)VIDEO_POS_Y(vp.pos);
menu_vp.Width = (float)VIDEO_SCALE_W(vp.dims);
menu_vp.Height = (float)VIDEO_SCALE_H(vp.dims);
menu_vp.MinDepth = 0.0f;
menu_vp.MaxDepth = 1.0f;
menu_sc.left = VIDEO_POS_X(vp.pos);
menu_sc.top = VIDEO_POS_Y(vp.pos);
menu_sc.right = menu_sc.left + (LONG)VIDEO_SCALE_W(vp.dims);
menu_sc.bottom = menu_sc.top + (LONG)VIDEO_SCALE_H(vp.dims);
cmd->lpVtbl->RSSetViewports(cmd, 1, &menu_vp);
cmd->lpVtbl->RSSetScissorRects(cmd, 1, &menu_sc);
}
cmd->lpVtbl->SetGraphicsRootDescriptorTable(cmd, ROOT_ID_TEXTURE_T,
d3d12->menu.texture.gpu_descriptor[0]);
@@ -8303,7 +8937,9 @@ static bool d3d12_gfx_frame(
d3d12->flags |= D3D12_ST_FLAG_SPRITES_ENABLE;
#ifdef HAVE_OVERLAY
if ((d3d12->flags & D3D12_ST_FLAG_OVERLAYS_ENABLE) && overlay_behind_menu)
/* Touch overlays would straddle both eyes. */
if ( (d3d12->flags & D3D12_ST_FLAG_OVERLAYS_ENABLE) && overlay_behind_menu
&& !views_per_eye)
d3d12_render_overlay(d3d12);
#endif
@@ -8341,7 +8977,8 @@ static bool d3d12_gfx_frame(
}
}
#ifdef HAVE_OVERLAY
if ((d3d12->flags & D3D12_ST_FLAG_OVERLAYS_ENABLE) && !overlay_behind_menu)
if ( (d3d12->flags & D3D12_ST_FLAG_OVERLAYS_ENABLE) && !overlay_behind_menu
&& !views_per_eye)
d3d12_render_overlay(d3d12);
#endif
@@ -8391,6 +9028,10 @@ static bool d3d12_gfx_frame(
}
d3d12->flags &= ~D3D12_ST_FLAG_SPRITES_ENABLE;
if (views_ui)
d3d12_views_ui_end(d3d12, video_info,
d3d12->chain.desc_handles[d3d12->chain.frame_index]);
#if defined(_WIN32) && !defined(__WINRT__)
video_driver_update_title(NULL);
#endif
@@ -10974,7 +11615,7 @@ static const video_poke_interface_t d3d12_poke_interface = {
d3d12_texture_lend,
d3d12_texture_lend_ready,
NULL, /* get_last_present_wait */
NULL, /* set_view_count */
d3d12_set_view_count,
NULL, /* hw_context_destroying */
NULL, /* get_headset_refresh */
d3d12_set_frame_format,
+6 -2
View File
@@ -139,6 +139,9 @@ HW_CASES = {
'd3d11': [(name, 'd3d11')
for name in ('3ds-2d', '3ds-sbs-full', '3ds-anaglyph',
'3ds-interlaced', '3ds-sbs-full-threaded')],
'd3d12': [(name, 'd3d12')
for name in ('3ds-2d', '3ds-sbs-full', '3ds-anaglyph',
'3ds-interlaced', '3ds-sbs-full-threaded')],
}
# A frame past the declared maximum in the core's own Vulkan image.
@@ -253,6 +256,7 @@ EXACT_CASES = [
('exact-3ds', '3ds', NOISE, 'gl_topleft'),
('exact-3ds', '3ds', NOISE, 'vulkan'),
('exact-3ds', '3ds', NOISE, 'd3d11'),
('exact-3ds', '3ds', NOISE, 'd3d12'),
# No map: the frame drawn whole. A core's GL framebuffer as large as
# its maximum holds the frame in a corner, so the stock chain would
# copy it out; glcore draws it straight from there instead.
@@ -260,7 +264,7 @@ EXACT_CASES = [
('whole-3ds', 'none', NOISE_LARGE, 'gl_topleft'),
]
EXACT_HW = {'glcore': ('off', 'gl', 'gl_topleft'), 'vulkan': ('off', 'vulkan'),
'd3d11': ('off', 'd3d11')}
'd3d11': ('off', 'd3d11'), 'd3d12': ('off', 'd3d12')}
# Drivers whose views take a preset: on the others a preset draws the
# frame whole, so the cases that need one are left out there.
PRESET_DRIVERS = ('glcore', 'vulkan')
@@ -278,7 +282,7 @@ SMOOTH_SETTINGS = dict(EXACT_SETTINGS, video_smooth='true')
LINEAR_PRESET = os.path.join(HERE, 'history_linear.slangp')
# Drivers that draw the stock chain's views straight from the frame,
# and that draw a frame whole straight from a larger texture.
DIRECT_DRIVERS = ('glcore', 'vulkan', 'd3d11')
DIRECT_DRIVERS = ('glcore', 'vulkan', 'd3d11', 'd3d12')
DIRECT_RE = re.compile(r'\] Views drawn straight from the frame\.')
WHOLE_DRIVERS = ('glcore',)
WHOLE_RE = re.compile(r'\] Frames drawn straight from the core\'s texture\.')