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:
LibretroAdmin authored and libretroadmin committed 2026-09-19 20:18:30 +02:00
1 parent 2903a3a9c9
commit 357a049bf6
6 files changed
+433 -1

No files matched your search

+15
View File
@@ -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: |
+7
View File
@@ -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.
+109 -1
View File
@@ -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;
}
+55
View File
@@ -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"