mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
libretro_d3d11.h: version 2, where the core and the frontend take turns on the immediate context
A D3D11 device has one immediate context, and it cannot be used from two
threads at once. Version 1 hands it to the core and takes the frame from
whatever is bound at pixel shader slot 0, which works only while the
frontend draws on the core's thread. A frontend that presents from
another thread cannot share it under those rules; all it can do is give
the core a deferred context, and a deferred context records but cannot
read back - no Map for reading, no query results. A core that reads its
render targets back loses that silently, and has to be written around a
context whose state and object lifetimes do not behave like the one it
was written for.
Version 2 shares the immediate context by taking turns:
lock_context / unlock_context
whoever holds the lock has the context to itself and everything
D3D11 can do with it, reading back included. lock_context returns
true when the frontend has used the context since the core last
released it, and the core then binds again whatever it relies on;
false means the context is exactly as it left it. Recursive. A
core may lock and unlock as often as it likes within a frame.
set_texture(texture)
the frame, named outright. The frontend is finished with it by
the time video_refresh returns - one context orders everything -
so there is no sync index, and the slot 0 convention is not needed.
context is always the immediate one in version 2. The struct is version
1's with members added at the end; RETRO_HW_RENDER_INTERFACE_D3D11_VERSION
stays 1 because existing cores reject anything else, and a core gets
version 2 only by asking through the negotiation interface libretro
already has (RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11 is new
in libretro.h), exactly as for D3D12. How the lock is made is the
frontend's business: a lock of its own is enough, since both sides take
it for every use, so nothing here needs ID3D11Multithread or Windows 10.
This is the contract and a proof that it is sufficient; no driver
implements it yet and no core asks for it, so nothing changes for anyone
until they do.
samples/gfx/d3d11_hw_interface_v2 runs it on a real D3D11 device: a core
that draws through cached state, reads its own render target back every
frame and hands the texture over, against a frontend thread that takes
the lock and draws with its own target, shaders and viewport before
checking the frame it was given. 200 frames drawn, read back and seen.
A core that ignores lock_context's answer is the control and is caught
at once ("the core drew frame 1 and read back 0"); run.sh requires that.
Built with mingw-w64, run under Wine, added to the gfx samples lane,
which fails rather than skips if it finds no D3D11 device. The header
compiles as C89 and as C++.
This commit is contained in:
1 parent
2903a3a9c9
commit
357a049bf6
6 files changed
+433
-1
No files matched your search
@@ -716,6 +716,21 @@ jobs:
|
||||
sudo apt-get install -y -qq xvfb > /dev/null
|
||||
samples/gfx/d3d11_hw_ring/run.sh
|
||||
|
||||
- name: libretro_d3d11.h version 2, the contract run (mingw + Wine, headless)
|
||||
shell: bash
|
||||
run: |
|
||||
set -o pipefail
|
||||
# Version 2 of the D3D11 hardware render interface lets a core
|
||||
# and a frontend that presents from another thread share the
|
||||
# device's one immediate context by taking turns, so the core
|
||||
# keeps everything a deferred context cannot do - reading back
|
||||
# above all. This runs the two against each other on a real
|
||||
# D3D11 device (wined3d), and requires every frame and every
|
||||
# readback to be right, and a core that ignores what
|
||||
# lock_context tells it to be caught.
|
||||
sudo apt-get install -y -qq xvfb > /dev/null
|
||||
REQUIRE_D3D11=1 samples/gfx/d3d11_hw_interface_v2/run.sh
|
||||
|
||||
- name: libretro_d3d12.h version 2, the contract run (mingw + Wine + vkd3d, headless)
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -4039,6 +4039,13 @@ enum retro_hw_render_context_negotiation_interface_type
|
||||
*/
|
||||
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D12 = 1,
|
||||
|
||||
/**
|
||||
* Denotes a context negotiation interface for Direct3D 11.
|
||||
* Carries the highest hardware render interface version the core can use.
|
||||
* @see retro_hw_render_context_negotiation_interface_d3d11
|
||||
*/
|
||||
RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11 = 2,
|
||||
|
||||
/**
|
||||
* @private Defined to ensure <tt>sizeof(retro_hw_render_context_negotiation_interface_type) == sizeof(int)</tt>.
|
||||
* Do not use.
|
||||
|
||||
@@ -30,13 +30,26 @@
|
||||
#include <d3d11.h>
|
||||
#include <d3dcompiler.h>
|
||||
|
||||
/* Version 1 is the interface every existing core was built against, and
|
||||
* what a frontend hands out unless a core has asked for more. Cores
|
||||
* compare interface_version against this macro, so it stays 1. */
|
||||
#define RETRO_HW_RENDER_INTERFACE_D3D11_VERSION 1
|
||||
|
||||
/* Version 2 lets the core and a frontend that presents from another
|
||||
* thread share the one immediate context a D3D11 device has, by taking
|
||||
* turns, and names the frame's texture outright. See "Version 2" below.
|
||||
* A core gets it only by asking, through
|
||||
* retro_hw_render_context_negotiation_interface_d3d11. */
|
||||
#define RETRO_HW_RENDER_INTERFACE_D3D11_VERSION_2 2
|
||||
|
||||
struct retro_hw_render_interface_d3d11
|
||||
{
|
||||
/* Must be set to RETRO_HW_RENDER_INTERFACE_D3D11. */
|
||||
enum retro_hw_render_interface_type interface_type;
|
||||
/* Must be set to RETRO_HW_RENDER_INTERFACE_D3D11_VERSION. */
|
||||
/* RETRO_HW_RENDER_INTERFACE_D3D11_VERSION, or
|
||||
* RETRO_HW_RENDER_INTERFACE_D3D11_VERSION_2 if the core negotiated it.
|
||||
* The members from lock_context on exist only from version 2; a core
|
||||
* must not read them on a lower version. */
|
||||
unsigned interface_version;
|
||||
|
||||
/* Opaque handle to the d3d11 backend in the frontend
|
||||
@@ -45,9 +58,104 @@ struct retro_hw_render_interface_d3d11
|
||||
*/
|
||||
void* handle;
|
||||
ID3D11Device *device;
|
||||
|
||||
/* Version 1: the context the core draws with, and the frame is the
|
||||
* shader resource view it leaves bound at pixel shader slot 0 when it
|
||||
* calls video_refresh(RETRO_HW_FRAME_BUFFER_VALID). A D3D11 device has
|
||||
* one immediate context and it is not safe to use from two threads at
|
||||
* once, so this works only while the frontend draws on the thread that
|
||||
* runs the core. A frontend that presents from another thread cannot
|
||||
* share it under version 1's rules; it can only hand the core a
|
||||
* deferred context, which records but cannot read anything back - no
|
||||
* Map for reading, no query results.
|
||||
*
|
||||
* Version 2: always the device's immediate context, whatever thread
|
||||
* the frontend presents from. The core may use it only between
|
||||
* lock_context and unlock_context. */
|
||||
ID3D11DeviceContext *context;
|
||||
D3D_FEATURE_LEVEL featureLevel;
|
||||
pD3DCompile D3DCompile;
|
||||
|
||||
/* --- Version 2 ------------------------------------------------------
|
||||
*
|
||||
* The immediate context is shared by taking turns. Whoever holds the
|
||||
* lock has the context to itself and everything D3D11 can do with it,
|
||||
* reading back included.
|
||||
*
|
||||
* A frame, from the core's side:
|
||||
*
|
||||
* if (lock_context(handle))
|
||||
* ... the frontend has had the context: rebind everything ...
|
||||
* ... draw, read back, whatever the frame needs ...
|
||||
* set_texture(handle, texture);
|
||||
* video_refresh(RETRO_HW_FRAME_BUFFER_VALID, width, height, 0);
|
||||
* unlock_context(handle);
|
||||
*
|
||||
* A core is free to lock and unlock as often as it likes within a
|
||||
* frame; holding the lock for less time lets the frontend's thread in
|
||||
* sooner. It must not hold it across a return from retro_run.
|
||||
*/
|
||||
|
||||
/* Takes the context. Blocks while the frontend is using it. Recursive:
|
||||
* a thread that holds the lock may take it again, and must release it
|
||||
* as many times.
|
||||
*
|
||||
* Returns true if the frontend has used the context since this thread
|
||||
* last released it. Every binding and every piece of pipeline state is
|
||||
* then whatever the frontend left, and the core must set again
|
||||
* whatever it relies on before it draws - shaders, input layout,
|
||||
* buffers, views, samplers, render targets, viewport, scissor, blend,
|
||||
* depth-stencil and rasterizer state. Returns false if the context is
|
||||
* exactly as the core left it. */
|
||||
bool (*lock_context)(void* handle);
|
||||
|
||||
/* Gives the context back. The core must not touch it again until it
|
||||
* has taken the lock again. */
|
||||
void (*unlock_context)(void* handle);
|
||||
|
||||
/* The frame, called before video_refresh(RETRO_HW_FRAME_BUFFER_VALID)
|
||||
* with the lock held. The frontend is finished with the texture by the
|
||||
* time video_refresh returns - it takes what it needs of it there, on
|
||||
* the context, ahead of anything the core does next - so the core may
|
||||
* draw into it again straight away; no sync index is needed, because
|
||||
* one context orders everything.
|
||||
*
|
||||
* Replaces the pixel shader slot 0 convention, which a version 2 core
|
||||
* need not follow. NULL withdraws a texture handed over earlier. */
|
||||
void (*set_texture)(void* handle, ID3D11Texture2D* texture);
|
||||
};
|
||||
|
||||
/* How a core asks for version 2.
|
||||
*
|
||||
* An older frontend hands every core version 1, and an older core
|
||||
* rejects anything that is not version 1, so neither side can simply
|
||||
* start using 2. The core asks, with the negotiation interface libretro
|
||||
* already has:
|
||||
*
|
||||
* struct retro_hw_render_context_negotiation_interface probe;
|
||||
* probe.interface_type = RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11;
|
||||
* probe.interface_version = 0;
|
||||
* if ( environ_cb(RETRO_ENVIRONMENT_GET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_SUPPORT, &probe)
|
||||
* && probe.interface_version >= 1)
|
||||
* environ_cb(RETRO_ENVIRONMENT_SET_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE, &negotiation);
|
||||
*
|
||||
* where negotiation is one of these, alive until retro_deinit. A
|
||||
* frontend that does not know the type answers version 0 and the core
|
||||
* goes on with version 1, having asked for nothing. One that does hands
|
||||
* out min(max_render_interface_version, what it implements) from
|
||||
* RETRO_ENVIRONMENT_GET_HW_RENDER_INTERFACE, and the core reads
|
||||
* interface_version to find out which it got. */
|
||||
#define RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11_VERSION 1
|
||||
|
||||
struct retro_hw_render_context_negotiation_interface_d3d11
|
||||
{
|
||||
/* Must be RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11. */
|
||||
enum retro_hw_render_context_negotiation_interface_type interface_type;
|
||||
/* Must be RETRO_HW_RENDER_CONTEXT_NEGOTIATION_INTERFACE_D3D11_VERSION. */
|
||||
unsigned interface_version;
|
||||
/* The highest retro_hw_render_interface_d3d11 version the core can
|
||||
* use. */
|
||||
unsigned max_render_interface_version;
|
||||
};
|
||||
|
||||
#endif /* LIBRETRO_DIRECT3D11_H__ */
|
||||
@@ -0,0 +1 @@
|
||||
d3d11_hw_interface_v2_test.exe
|
||||
@@ -0,0 +1,246 @@
|
||||
/* libretro_d3d11.h version 2, run: a core and a frontend that presents
|
||||
* from another thread, sharing a device's one immediate context by
|
||||
* taking turns, with the handoff the header describes.
|
||||
*
|
||||
* core each frame: takes the lock; if told the frontend has had
|
||||
* the context, binds everything again; draws frame n (a
|
||||
* colour that encodes n) with the state it believes is
|
||||
* bound; READS ITS OWN RENDER TARGET BACK and checks it -
|
||||
* the thing a core cannot do at all on the deferred context
|
||||
* version 1 leaves a threaded frontend to hand out; names
|
||||
* the texture, calls video_refresh, releases the lock
|
||||
* frontend on its own thread: takes the lock, binds its own render
|
||||
* target, shaders and viewport and draws with them - which
|
||||
* is what a real frontend's frame does to the context - then
|
||||
* reads the frame it was handed and checks it holds frame n
|
||||
*
|
||||
* Three things must hold: every frame reaches the frontend intact, every
|
||||
* readback the core makes is right, and nothing deadlocks.
|
||||
*
|
||||
* "norebind" is the control: a core that ignores lock_context's return
|
||||
* value. Its next draw goes through whatever the frontend left bound, and
|
||||
* its readback catches it. run.sh requires that to fail.
|
||||
*
|
||||
* The frontend below is the smallest thing that honours the contract; it
|
||||
* is not RetroArch's driver. What this proves is that the contract in the
|
||||
* header is enough, on a real D3D11 device.
|
||||
*
|
||||
* Windows only. run.sh builds it with mingw-w64 and runs it under Wine. */
|
||||
#ifndef CINTERFACE
|
||||
#define CINTERFACE
|
||||
#endif
|
||||
#ifndef COBJMACROS
|
||||
#define COBJMACROS
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <boolean.h>
|
||||
#include <libretro.h>
|
||||
#include <libretro_d3d11.h>
|
||||
|
||||
#define FRAMES 200
|
||||
#define W 64
|
||||
#define H 64
|
||||
#define SLOTS 3
|
||||
|
||||
static const char hlsl[] =
|
||||
"cbuffer C : register(b0) { float4 col; };"
|
||||
"float4 vs(uint id : SV_VertexID) : SV_Position {"
|
||||
" if (id == 0) return float4(-1,-1,0,1); if (id == 1) return float4(-1,3,0,1); return float4(3,-1,0,1); }"
|
||||
"float4 ps() : SV_Target { return col; }"
|
||||
"float4 ps_grey() : SV_Target { return float4(0.5, 0.5, 0.5, 1); }";
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ID3D11Device *dev; ID3D11DeviceContext *ctx;
|
||||
CRITICAL_SECTION lock; DWORD core_thread; bool disturbed;
|
||||
ID3D11Texture2D *handed; /* set_texture */
|
||||
ID3D11Texture2D *slot[SLOTS]; unsigned slot_frame[SLOTS]; unsigned next_slot;
|
||||
CRITICAL_SECTION qlock; HANDLE have_work; unsigned q[FRAMES + 8]; unsigned head, tail;
|
||||
/* the frontend's own drawing, which is what disturbs the context */
|
||||
ID3D11Texture2D *fe_rt_tex, *staging; ID3D11RenderTargetView *fe_rtv;
|
||||
ID3D11VertexShader *vs; ID3D11PixelShader *ps_grey;
|
||||
volatile LONG bad, shown, quit; unsigned frame_no;
|
||||
struct retro_hw_render_interface_d3d11 iface;
|
||||
} frontend_t;
|
||||
|
||||
/* lock_context: true if the frontend has used the context since the core
|
||||
* last released it. */
|
||||
static bool fe_lock_context(void *h)
|
||||
{
|
||||
frontend_t *fe = (frontend_t*)h; bool d;
|
||||
EnterCriticalSection(&fe->lock);
|
||||
d = fe->disturbed; fe->disturbed = false;
|
||||
return d;
|
||||
}
|
||||
static void fe_unlock_context(void *h) { LeaveCriticalSection(&((frontend_t*)h)->lock); }
|
||||
static void fe_set_texture(void *h, ID3D11Texture2D *t) { ((frontend_t*)h)->handed = t; }
|
||||
|
||||
/* video_refresh(RETRO_HW_FRAME_BUFFER_VALID), on the core's thread with
|
||||
* the lock held (it is recursive): take the frame now, on the context,
|
||||
* ahead of whatever the core does next. One call, no submission. */
|
||||
static void fe_video_refresh(frontend_t *fe)
|
||||
{
|
||||
unsigned k = fe->next_slot;
|
||||
EnterCriticalSection(&fe->lock);
|
||||
ID3D11DeviceContext_CopyResource(fe->ctx, (ID3D11Resource*)fe->slot[k], (ID3D11Resource*)fe->handed);
|
||||
LeaveCriticalSection(&fe->lock);
|
||||
fe->slot_frame[k] = fe->frame_no++;
|
||||
fe->next_slot = (k + 1) % SLOTS;
|
||||
EnterCriticalSection(&fe->qlock);
|
||||
fe->q[fe->tail++] = k | (fe->slot_frame[k] << 8);
|
||||
LeaveCriticalSection(&fe->qlock);
|
||||
SetEvent(fe->have_work);
|
||||
}
|
||||
|
||||
static unsigned read_frame_no(frontend_t *fe, ID3D11Texture2D *tex)
|
||||
{
|
||||
D3D11_MAPPED_SUBRESOURCE m; unsigned char *p; unsigned n = 0xffffffffu;
|
||||
ID3D11DeviceContext_CopyResource(fe->ctx, (ID3D11Resource*)fe->staging, (ID3D11Resource*)tex);
|
||||
if (SUCCEEDED(ID3D11DeviceContext_Map(fe->ctx, (ID3D11Resource*)fe->staging, 0, D3D11_MAP_READ, 0, &m)))
|
||||
{
|
||||
p = (unsigned char*)m.pData + (H / 2) * m.RowPitch + (W / 2) * 4;
|
||||
n = p[0] | ((unsigned)p[1] << 8);
|
||||
ID3D11DeviceContext_Unmap(fe->ctx, (ID3D11Resource*)fe->staging, 0);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static DWORD WINAPI frontend_thread(void *p)
|
||||
{
|
||||
frontend_t *fe = (frontend_t*)p; D3D11_VIEWPORT vp = {0, 0, 8, 8, 0, 1};
|
||||
for (;;)
|
||||
{
|
||||
unsigned item, k, want, got;
|
||||
EnterCriticalSection(&fe->qlock);
|
||||
if (fe->head == fe->tail)
|
||||
{
|
||||
LeaveCriticalSection(&fe->qlock);
|
||||
if (fe->quit)
|
||||
return 0;
|
||||
WaitForSingleObject(fe->have_work, 20);
|
||||
continue;
|
||||
}
|
||||
item = fe->q[fe->head++];
|
||||
LeaveCriticalSection(&fe->qlock);
|
||||
k = item & 0xff; want = item >> 8;
|
||||
|
||||
EnterCriticalSection(&fe->lock);
|
||||
fe->disturbed = true;
|
||||
/* a frontend's frame: its own target, shaders and viewport */
|
||||
ID3D11DeviceContext_OMSetRenderTargets(fe->ctx, 1, &fe->fe_rtv, NULL);
|
||||
ID3D11DeviceContext_RSSetViewports(fe->ctx, 1, &vp);
|
||||
ID3D11DeviceContext_VSSetShader(fe->ctx, fe->vs, NULL, 0);
|
||||
ID3D11DeviceContext_PSSetShader(fe->ctx, fe->ps_grey, NULL, 0);
|
||||
ID3D11DeviceContext_IASetPrimitiveTopology(fe->ctx, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
ID3D11DeviceContext_Draw(fe->ctx, 3, 0);
|
||||
got = read_frame_no(fe, fe->slot[k]);
|
||||
LeaveCriticalSection(&fe->lock);
|
||||
|
||||
/* a slot can be taken again before it is shown if the core laps us;
|
||||
* what matters is that a slot never holds a torn or foreign frame */
|
||||
if (got != want && got != fe->slot_frame[k])
|
||||
{
|
||||
if (!fe->bad)
|
||||
printf(" FAIL: the frontend read frame %u where %u was published\n", got, want);
|
||||
InterlockedExchange(&fe->bad, 1);
|
||||
}
|
||||
InterlockedIncrement(&fe->shown);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int norebind = argc > 1 && !strcmp(argv[1], "norebind");
|
||||
frontend_t fe; const struct retro_hw_render_interface_d3d11 *d3d11;
|
||||
ID3D11Texture2D *rt_tex; ID3D11RenderTargetView *rtv; ID3D11PixelShader *ps; ID3D11Buffer *cb;
|
||||
ID3DBlob *b_vs = NULL, *b_ps = NULL, *b_grey = NULL, *err = NULL;
|
||||
D3D11_TEXTURE2D_DESC td; D3D11_BUFFER_DESC bd; D3D11_VIEWPORT vp = {0, 0, W, H, 0, 1};
|
||||
D3D_FEATURE_LEVEL fl = D3D_FEATURE_LEVEL_11_0; HANDLE thread; unsigned n, i; int core_bad = 0;
|
||||
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
memset(&fe, 0, sizeof(fe));
|
||||
if (FAILED(D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &fl, 1, D3D11_SDK_VERSION, &fe.dev, NULL, &fe.ctx)))
|
||||
{ puts("no D3D11 device"); return 2; }
|
||||
InitializeCriticalSection(&fe.lock); InitializeCriticalSection(&fe.qlock);
|
||||
fe.have_work = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
|
||||
if ( FAILED(D3DCompile(hlsl, sizeof(hlsl) - 1, NULL, NULL, NULL, "vs", "vs_4_0", 0, 0, &b_vs, &err))
|
||||
|| FAILED(D3DCompile(hlsl, sizeof(hlsl) - 1, NULL, NULL, NULL, "ps", "ps_4_0", 0, 0, &b_ps, &err))
|
||||
|| FAILED(D3DCompile(hlsl, sizeof(hlsl) - 1, NULL, NULL, NULL, "ps_grey", "ps_4_0", 0, 0, &b_grey, &err)))
|
||||
{ printf("shader compile failed: %s\n", err ? (char*)ID3D10Blob_GetBufferPointer(err) : "?"); return 2; }
|
||||
ID3D11Device_CreateVertexShader(fe.dev, ID3D10Blob_GetBufferPointer(b_vs), ID3D10Blob_GetBufferSize(b_vs), NULL, &fe.vs);
|
||||
ID3D11Device_CreatePixelShader(fe.dev, ID3D10Blob_GetBufferPointer(b_ps), ID3D10Blob_GetBufferSize(b_ps), NULL, &ps);
|
||||
ID3D11Device_CreatePixelShader(fe.dev, ID3D10Blob_GetBufferPointer(b_grey), ID3D10Blob_GetBufferSize(b_grey), NULL, &fe.ps_grey);
|
||||
|
||||
memset(&td, 0, sizeof(td)); td.Width = W; td.Height = H; td.MipLevels = td.ArraySize = 1;
|
||||
td.Format = DXGI_FORMAT_R8G8B8A8_UNORM; td.SampleDesc.Count = 1;
|
||||
td.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
|
||||
ID3D11Device_CreateTexture2D(fe.dev, &td, NULL, &rt_tex);
|
||||
ID3D11Device_CreateTexture2D(fe.dev, &td, NULL, &fe.fe_rt_tex);
|
||||
for (i = 0; i < SLOTS; i++)
|
||||
ID3D11Device_CreateTexture2D(fe.dev, &td, NULL, &fe.slot[i]);
|
||||
td.BindFlags = 0; td.Usage = D3D11_USAGE_STAGING; td.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
ID3D11Device_CreateTexture2D(fe.dev, &td, NULL, &fe.staging);
|
||||
ID3D11Device_CreateRenderTargetView(fe.dev, (ID3D11Resource*)rt_tex, NULL, &rtv);
|
||||
ID3D11Device_CreateRenderTargetView(fe.dev, (ID3D11Resource*)fe.fe_rt_tex, NULL, &fe.fe_rtv);
|
||||
memset(&bd, 0, sizeof(bd)); bd.ByteWidth = 16; bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
|
||||
ID3D11Device_CreateBuffer(fe.dev, &bd, NULL, &cb);
|
||||
|
||||
fe.iface.interface_type = RETRO_HW_RENDER_INTERFACE_D3D11;
|
||||
fe.iface.interface_version = RETRO_HW_RENDER_INTERFACE_D3D11_VERSION_2;
|
||||
fe.iface.handle = &fe;
|
||||
fe.iface.device = fe.dev;
|
||||
fe.iface.context = fe.ctx;
|
||||
fe.iface.lock_context = fe_lock_context;
|
||||
fe.iface.unlock_context = fe_unlock_context;
|
||||
fe.iface.set_texture = fe_set_texture;
|
||||
d3d11 = &fe.iface;
|
||||
fe.disturbed = true; /* a fresh context is nobody's */
|
||||
thread = CreateThread(NULL, 0, frontend_thread, &fe, 0, NULL);
|
||||
|
||||
for (n = 0; n < FRAMES; n++)
|
||||
{
|
||||
float col[4]; unsigned got;
|
||||
col[0] = (float)(n & 0xff) / 255.0f; col[1] = (float)((n >> 8) & 0xff) / 255.0f; col[2] = 0.0f; col[3] = 1.0f;
|
||||
|
||||
if (d3d11->lock_context(d3d11->handle) && (!norebind || n == 0))
|
||||
{
|
||||
/* the frontend has had the context: everything, again */
|
||||
ID3D11DeviceContext_IASetPrimitiveTopology(d3d11->context, D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
ID3D11DeviceContext_VSSetShader(d3d11->context, fe.vs, NULL, 0);
|
||||
ID3D11DeviceContext_PSSetShader(d3d11->context, ps, NULL, 0);
|
||||
ID3D11DeviceContext_PSSetConstantBuffers(d3d11->context, 0, 1, &cb);
|
||||
ID3D11DeviceContext_RSSetViewports(d3d11->context, 1, &vp);
|
||||
ID3D11DeviceContext_OMSetRenderTargets(d3d11->context, 1, &rtv, NULL);
|
||||
}
|
||||
ID3D11DeviceContext_UpdateSubresource(d3d11->context, (ID3D11Resource*)cb, 0, NULL, col, 0, 0);
|
||||
ID3D11DeviceContext_Draw(d3d11->context, 3, 0);
|
||||
|
||||
/* a readback, mid-frame, on the context the core was given */
|
||||
got = read_frame_no(&fe, rt_tex);
|
||||
if (got != n)
|
||||
{
|
||||
if (!core_bad)
|
||||
printf(" FAIL: the core drew frame %u and read back %u\n", n, got);
|
||||
core_bad = 1;
|
||||
}
|
||||
|
||||
d3d11->set_texture(d3d11->handle, rt_tex);
|
||||
fe_video_refresh(&fe);
|
||||
d3d11->unlock_context(d3d11->handle);
|
||||
if ((n % 3) == 0)
|
||||
Sleep(1); /* let the other thread in */
|
||||
}
|
||||
|
||||
fe.quit = 1; SetEvent(fe.have_work);
|
||||
WaitForSingleObject(thread, 60000);
|
||||
printf(" %u frames drawn and read back by the core, %ld seen by the frontend\n", FRAMES, (long)fe.shown);
|
||||
if (core_bad || fe.bad || fe.shown != FRAMES) { puts("d3d11_hw_interface_v2: FAILED"); return 1; }
|
||||
puts("d3d11_hw_interface_v2: ok");
|
||||
return 0;
|
||||
}
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/sh
|
||||
# Builds d3d11_hw_interface_v2_test with mingw-w64 and runs it under Wine,
|
||||
# where wined3d implements D3D11 over OpenGL (Mesa's llvmpipe will do: no
|
||||
# GPU is needed). See the test for what it checks.
|
||||
#
|
||||
# Two runs. The contract as libretro_d3d11.h version 2 states it must
|
||||
# pass. A core that ignores what lock_context tells it must FAIL: if it
|
||||
# passes, the frontend in the test has stopped disturbing the context,
|
||||
# and the first run proves nothing.
|
||||
set -eu
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
root=$(cd "$here/../../.." && pwd)
|
||||
cd "$here"
|
||||
|
||||
CC=${CC:-x86_64-w64-mingw32-gcc}
|
||||
WINE=${WINE:-$(command -v wine64 || command -v wine)}
|
||||
|
||||
"$CC" -O1 -Wall -I "$root/libretro-common/include" \
|
||||
-o d3d11_hw_interface_v2_test.exe d3d11_hw_interface_v2_test.c \
|
||||
-ld3d11 -ld3dcompiler -ldxguid -luuid
|
||||
|
||||
xpid=
|
||||
if [ -z "${DISPLAY:-}" ]; then
|
||||
Xvfb :78 -screen 0 640x480x24 >/dev/null 2>&1 &
|
||||
xpid=$!
|
||||
DISPLAY=:78
|
||||
export DISPLAY
|
||||
sleep 2
|
||||
fi
|
||||
trap '[ -n "$xpid" ] && kill "$xpid" 2>/dev/null || true' EXIT
|
||||
export WINEDEBUG=${WINEDEBUG:--all}
|
||||
|
||||
echo "== version 2 as the header states it"
|
||||
out=$(timeout 300 "$WINE" ./d3d11_hw_interface_v2_test.exe | tr -d '\r') || true
|
||||
echo "$out"
|
||||
case "$out" in
|
||||
*"no D3D11 device"*)
|
||||
# CI sets REQUIRE_D3D11=1: a lane that installs Wine and an X server
|
||||
# and still has no device is broken, not skippable.
|
||||
if [ -n "${REQUIRE_D3D11:-}" ]; then
|
||||
echo "FAIL: no D3D11 device under Wine, and REQUIRE_D3D11 is set"; exit 1
|
||||
fi
|
||||
echo "SKIP: Wine has no D3D11 device here"; exit 0 ;;
|
||||
*"d3d11_hw_interface_v2: ok"*) ;;
|
||||
*) echo "FAIL"; exit 1 ;;
|
||||
esac
|
||||
|
||||
echo "== a core that ignores what lock_context tells it (must fail)"
|
||||
out=$(timeout 300 "$WINE" ./d3d11_hw_interface_v2_test.exe norebind | tr -d '\r') || true
|
||||
echo "$out"
|
||||
case "$out" in
|
||||
*"d3d11_hw_interface_v2: ok"*)
|
||||
echo "FAIL: it passed; the frontend in the test no longer disturbs the context"; exit 1 ;;
|
||||
esac
|
||||
echo " fails, as it should"
|
||||
Reference in new issue
Block a user