video: retry hardware decoder backpressure without waiting

Poll decoder and output availability instead of sleeping or waiting for a held texture. Preserve pending pictures, input packets and the presentation clock until a later player poll can proceed.

Cover busy decode and output retries, inline and threaded playback, and native D3D11 texture saturation, recovery and cancellation.
This commit is contained in:
Codex authored and libretroadmin committed 2026-10-13 15:52:24 +02:00
1 parent 45cede3586
commit bffe92cae8
16 files changed
+488 -100

No files matched your search

+13 -44
View File
@@ -64,8 +64,6 @@
#define D3D11VD_MAX_SLOTS 32
#define D3D11VD_MAX_SLICES 64 /* rh264 gathers no more */
#define D3D11VD_OUTS 4 /* shared output textures per decoder */
#define D3D11VD_WAIT_MS 1000
#define D3D11VD_BEGIN_TRIES 500 /* DecoderBeginFrame's E_PENDING, 1 ms */
/* The scaling lists in coded (zig-zag) order; a sample sets it to try
* raster order on a GPU */
#ifndef D3D11VD_QM_RASTER
@@ -129,7 +127,6 @@ typedef struct d3d11vd_dec
ID3D11Fence *ready; /* raised behind each copy */
HANDLE ready_handle;
uint64_t ready_value;
HANDLE event;
uint8_t pp[DXVA_H264_PP_SIZE];
uint8_t qm[DXVA_H264_QM_SIZE];
@@ -164,8 +161,6 @@ static void d3d11vd_destroy(d3d11vd_dec_t *d)
}
if (d->ready_handle)
CloseHandle(d->ready_handle);
if (d->event)
CloseHandle(d->event);
D3D11VD_RELEASE(d->ready);
for (i = 0; i < D3D11VD_MAX_SLOTS; i++)
D3D11VD_RELEASE(d->view[i]);
@@ -245,27 +240,6 @@ static void d3d11vd_abandon(void *owner, uint64_t done, uint64_t value)
}
}
/* Output texture @o free to be copied into: waits, on the decoding
* thread, for its reader. */
static bool d3d11vd_out_wait(d3d11vd_dec_t *d, unsigned o)
{
unsigned waited = 0;
if (d3d11vd_out_settled(d, o))
return true;
if (FAILED(d->out_done[o]->lpVtbl->SetEventOnCompletion(d->out_done[o],
d->out_value[o], d->event)))
return false;
/* in slices: an abandon raises no event */
while (waited < D3D11VD_WAIT_MS)
{
WaitForSingleObject(d->event, 10);
waited += 10;
if (d3d11vd_out_settled(d, o))
return true;
}
return false;
}
/* ---- open ---- */
static bool d3d11vd_make_device(d3d11vd_dec_t *d)
@@ -409,8 +383,7 @@ static bool d3d11vd_make_outputs(d3d11vd_dec_t *d)
goto end;
retro_atomic_store_release_int(&d->out_abandoned[o], 0);
}
ok = d3d11vd_shared_fence(dev5, &d->ready, &d->ready_handle)
&& (d->event = CreateEventA(NULL, FALSE, FALSE, NULL)) != NULL;
ok = d3d11vd_shared_fence(dev5, &d->ready, &d->ready_handle);
end:
D3D11VD_RELEASE(dev5);
return ok;
@@ -523,14 +496,10 @@ static int d3d11vd_decode(void *dec, const rh264_hw_pic_t *pic)
if (pic->refs[i].slot < 0 || (unsigned)pic->refs[i].slot >= d->slots)
return -1;
for (i = 0; ; i++)
{
hr = d->vctx->lpVtbl->DecoderBeginFrame(d->vctx, d->decoder,
d->view[pic->slot], 0, NULL);
if (hr != E_PENDING || i >= D3D11VD_BEGIN_TRIES)
break;
Sleep(1);
}
hr = d->vctx->lpVtbl->DecoderBeginFrame(d->vctx, d->decoder,
d->view[pic->slot], 0, NULL);
if (hr == E_PENDING)
return 1;
if (FAILED(hr))
return -1;
@@ -597,17 +566,16 @@ static int d3d11vd_output(void *dec, unsigned slot, video_hw_frame_t *out)
{
d3d11vd_dec_t *d = (d3d11vd_dec_t*)dec;
D3D11_BOX box;
unsigned o;
unsigned o, n;
if (slot >= d->slots)
return -1;
/* the oldest texture, once its last reader is done */
o = d->out_next++ % D3D11VD_OUTS;
if (!d3d11vd_out_wait(d, o))
{
RARCH_WARN("[D3D11] Video decode: an output frame was never given back.\n");
return -1;
}
for (n = 0; n < D3D11VD_OUTS; n++)
if (d3d11vd_out_settled(d, (d->out_next + n) % D3D11VD_OUTS))
break;
if (n == D3D11VD_OUTS)
return 1;
o = (d->out_next + n) % D3D11VD_OUTS;
box.left = d->crop_x;
box.top = d->crop_y;
box.front = 0;
@@ -621,6 +589,7 @@ static int d3d11vd_output(void *dec, unsigned slot, video_hw_frame_t *out)
return -1;
d->ready_value++;
d->ctx->lpVtbl->Flush(d->ctx);
d->out_next = (o + 1) % D3D11VD_OUTS;
memset(out, 0, sizeof(*out));
out->image = (uint64_t)(uintptr_t)d->out_handle[o];
+40 -13
View File
@@ -224,6 +224,16 @@ static bool vvd_wait(VkSemaphore sem, uint64_t value, uint64_t timeout_ns)
return vvd.wait_sems(vvd.device, &info, timeout_ns) == VK_SUCCESS;
}
static int vvd_try_wait(VkSemaphore sem, uint64_t value)
{
uint64_t now;
VkResult r;
if (!value)
return 0;
r = vvd.counter(vvd.device, sem, &now);
return r == VK_SUCCESS ? (now < value ? 1 : 0) : -1;
}
static bool vvd_reached(VkSemaphore sem, uint64_t value)
{
uint64_t now = 0;
@@ -897,7 +907,7 @@ static bool vvd_make_params(vvd_dec_t *d, const rh264_hw_pic_t *pic)
* it is done */
if (d->params)
{
if (!vvd_wait(d->tl, d->tl_value, VVD_WAIT_NS))
if (!vvd_reached(d->tl, d->tl_value))
return false;
vvd.destroy_params(vvd.device, d->params, NULL);
d->params = VK_NULL_HANDLE;
@@ -1105,18 +1115,26 @@ static int vvd_decode(void *dec, const rh264_hw_pic_t *pic)
VkCommandBuffer cb;
VkDeviceSize size;
unsigned i, n, k;
int ready;
unsigned setup = (unsigned)pic->slot;
if (setup >= d->slots || pic->num_refs > 16 || !pic->num_slices)
return -1;
if (d->have_params && pic->params_gen != d->params_gen)
{
ready = vvd_try_wait(d->tl, d->tl_value);
if (ready != 0)
return ready;
}
if ( (!d->have_params || pic->params_gen != d->params_gen)
&& !vvd_make_params(d, pic))
return -1;
/* the ring entry, once the work it last held is done */
k = d->dring++ % VVD_RING;
if (!vvd_wait(d->tl, d->dcb_value[k], VVD_WAIT_NS))
return -1;
k = d->dring % VVD_RING;
ready = vvd_try_wait(d->tl, d->dcb_value[k]);
if (ready != 0)
return ready;
size = (pic->size + d->bs_size_align - 1) / d->bs_size_align
* d->bs_size_align;
if (!vvd_bitstream(d, k, size))
@@ -1245,6 +1263,7 @@ static int vvd_decode(void *dec, const rh264_hw_pic_t *pic)
return -1;
d->reset = true;
d->dcb_value[k] = d->tl_value;
d->dring++;
return 0;
}
@@ -1256,20 +1275,26 @@ static int vvd_output(void *dec, unsigned slot, video_hw_frame_t *out)
VkCommandBuffer cb;
VkImage src;
uint32_t layer;
unsigned o, k;
unsigned o, k, n;
int ready;
if (slot >= d->slots)
return -1;
/* the oldest output image, once its last reader is done */
o = d->out_next++ % VVD_OUTS;
if (!vvd_wait(d->out_done[o], d->out_value[o], VVD_WAIT_NS))
for (n = 0; n < VVD_OUTS; n++)
{
RARCH_WARN("[Vulkan] Video decode: an output frame was never given back.\n");
return -1;
o = (d->out_next + n) % VVD_OUTS;
ready = vvd_try_wait(d->out_done[o], d->out_value[o]);
if (ready < 0)
return -1;
if (!ready)
break;
}
k = d->xring++ % VVD_RING;
if (!vvd_wait(d->tl, d->xcb_value[k], VVD_WAIT_NS))
return -1;
if (n == VVD_OUTS)
return 1;
k = d->xring % VVD_RING;
ready = vvd_try_wait(d->tl, d->xcb_value[k]);
if (ready != 0)
return ready;
src = d->dpb[d->dpb_array ? 0 : slot];
layer = d->dpb_array ? slot : 0;
@@ -1322,6 +1347,8 @@ static int vvd_output(void *dec, unsigned slot, video_hw_frame_t *out)
|| !vvd_submit(d, d->xq, cb))
return -1;
d->xcb_value[k] = d->tl_value;
d->out_next = (o + 1) % VVD_OUTS;
d->xring++;
memset(out, 0, sizeof(*out));
memcpy(&out->image, &d->out[o], sizeof(d->out[o]));
+92 -21
View File
@@ -10484,6 +10484,10 @@ typedef struct rh264_hw
int out_gen[RH264_OUT_SLOTS];
uint8_t out_used[RH264_OUT_SLOTS];
int out_len;
int pending_out;
const uint8_t *retry_data;
size_t retry_len, retry_pos;
int retrying, retry_got;
rh264_hw_ref_t refs[RH264_MAX_REFS];
video_hw_frame_t shown;
int have_shown;
@@ -10683,8 +10687,8 @@ static int rh264_hw_out_pick(const rh264_hw *hw)
static int rh264_hw_show(rh264_video *v, int i)
{
rh264_hw *hw = v->hw;
hw->out_used[i] = 0;
hw->out_len--;
video_hw_frame_t shown;
int r;
if (v->hw_readback)
{
int w = hw->sps.width, h = hw->sps.height;
@@ -10710,17 +10714,24 @@ static int rh264_hw_show(rh264_video *v, int i)
}
hw->have_cpu = 1;
hw->fresh = 1;
hw->out_used[i] = 0;
hw->out_len--;
return 0;
}
/* two pictures in one call: the first is passed over */
if (hw->fresh)
video_hw_frame_abandon(&hw->shown);
hw->have_shown = 0;
if (hw->ops->output(hw->dec, (unsigned)hw->out_slot[i], &hw->shown) != 0)
r = hw->ops->output(hw->dec, (unsigned)hw->out_slot[i], &shown);
if (r > 0)
return 2;
if (r < 0)
{
hw->broken = 1;
return -1;
}
if (hw->fresh)
video_hw_frame_abandon(&hw->shown);
hw->shown = shown;
hw->out_used[i] = 0;
hw->out_len--;
hw->have_shown = 1;
hw->fresh = 1;
return 0;
@@ -10730,7 +10741,7 @@ static int rh264_hw_show(rh264_video *v, int i)
static int rh264_hw_out_push(rh264_video *v, int poc, int is_idr)
{
rh264_hw *hw = v->hw;
int i, slot = -1;
int i, r, slot = -1;
for (i = 0; i < RH264_OUT_SLOTS; i++)
if (!hw->out_used[i]) { slot = i; break; }
if (slot < 0)
@@ -10742,8 +10753,12 @@ static int rh264_hw_out_push(rh264_video *v, int poc, int is_idr)
hw->out_len++;
if (hw->out_len <= v->reorder_delay && !(is_idr && hw->out_len == 1))
return 0;
if (rh264_hw_show(v, rh264_hw_out_pick(hw)) != 0)
return -1;
i = rh264_hw_out_pick(hw);
r = rh264_hw_show(v, i);
if (r == 2)
hw->pending_out = i + 1;
if (r != 0)
return r;
return 1;
}
@@ -10756,11 +10771,22 @@ static int rh264_hw_finish(rh264_video *v, int *got_pic)
int j, r;
unsigned n = 0;
if (hw->pending_out)
{
r = rh264_hw_show(v, hw->pending_out - 1);
if (r != 0)
return r;
hw->pending_out = 0;
*got_pic = 1;
return 0;
}
if (!hw->pic_open)
return 0;
hw->pic_open = 0;
if (hw->skip)
{
hw->pic_open = 0;
return 0;
}
/* an IDR predicts from nothing: the DPB empties under it */
for (j = 0; j < v->dpb_len && !hw->idr; j++)
@@ -10801,11 +10827,15 @@ static int rh264_hw_finish(rh264_video *v, int *got_pic)
pic.long_term = 1;
pic.long_term_idx = v->pend_mmco_a[j];
}
if (hw->ops->decode(hw->dec, &pic) != 0)
r = hw->ops->decode(hw->dec, &pic);
if (r > 0)
return 2;
if (r < 0)
{
hw->broken = 1;
return -1;
}
hw->pic_open = 0;
if (hw->idr)
{
@@ -10840,8 +10870,8 @@ static int rh264_hw_finish(rh264_video *v, int *got_pic)
}
r = rh264_hw_out_push(v, v->last_poc, epoch);
}
if (r < 0)
return -1;
if (r < 0 || r == 2)
return r;
if (r > 0)
*got_pic = 1;
return 0;
@@ -10876,8 +10906,11 @@ static int rh264_hw_slice(rh264_video *v, const uint8_t *nal, size_t len,
if (sh->first_mb_in_slice == 0)
{
/* a picture the last access unit left open ends here */
if (rh264_hw_finish(v, got_pic) != 0)
return -1;
{
int r = rh264_hw_finish(v, got_pic);
if (r != 0)
return r;
}
hw->skip = 0;
if (nri == 0 && retro_atomic_load_acquire_int(&v->skip_nonref))
{
@@ -11083,7 +11116,7 @@ static int rh264_video_handle_slice_nal(rh264_video *v, const uint8_t *nal,
if (type == 7 || type == 8)
return rh264_hw_take_ps(v, nal, nl) != 0 ? -1 : 0;
if (type == 5 || type == 1)
return rh264_hw_slice(v, nal, nl, got_pic) != 0 ? -1 : 0;
return rh264_hw_slice(v, nal, nl, got_pic);
return 0;
}
/* a switching slice was refused earlier: everything after it would
@@ -11112,6 +11145,17 @@ static int rh264_video_handle_slice_nal(rh264_video *v, const uint8_t *nal,
return 0;
}
static int rh264_hw_retry(rh264_video *v, const uint8_t *data,
size_t len, size_t pos, int got)
{
v->hw->retry_data = data;
v->hw->retry_len = len;
v->hw->retry_pos = pos;
v->hw->retry_got = got;
v->hw->retrying = 1;
return 2;
}
int rh264_video_decode(rh264_video *v, const uint8_t *data, size_t len)
{
size_t p = 0;
@@ -11119,7 +11163,17 @@ int rh264_video_decode(rh264_video *v, const uint8_t *data, size_t len)
if (!v || !data) return -1;
v->dropped = 0;
if (v->hw)
v->hw->fresh = 0;
{
if (v->hw->retrying)
{
if (data != v->hw->retry_data || len != v->hw->retry_len)
return -1;
p = v->hw->retry_pos;
got_pic = v->hw->retry_got;
}
else
v->hw->fresh = 0;
}
if (v->nal_length_size > 0 && len >= (size_t)v->nal_length_size)
{
@@ -11132,12 +11186,14 @@ int rh264_video_decode(rh264_video *v, const uint8_t *data, size_t len)
int ls = v->nal_length_size, i;
while (p + ls <= len)
{
size_t nl = 0;
size_t nl = 0, at = p;
for (i = 0; i < ls; i++) nl = (nl << 8) | data[p + i];
p += ls;
if (nl == 0 || p + nl > len) break;
{
int r = rh264_video_handle_slice_nal(v, data + p, nl, &got_pic);
if (r == 2)
return rh264_hw_retry(v, data, len, at, got_pic);
if (r < 0) return -1;
/* One coded picture per call: sequentially the picture
* shown is this sample's, complete at its last slice, and
@@ -11166,6 +11222,8 @@ int rh264_video_decode(rh264_video *v, const uint8_t *data, size_t len)
if (s < e)
{
int r = rh264_video_handle_slice_nal(v, data + s, e - s, &got_pic);
if (r == 2)
return rh264_hw_retry(v, data, len, p, got_pic);
if (r < 0) return -1;
/* One coded picture per call: sequentially the picture
* shown is this sample's, complete at its last slice, and
@@ -11179,8 +11237,15 @@ int rh264_video_decode(rh264_video *v, const uint8_t *data, size_t len)
}
}
/* on the backend a picture is whole at the end of its access unit */
if (v->hw && rh264_hw_finish(v, &got_pic) != 0)
return -1;
if (v->hw)
{
int r = rh264_hw_finish(v, &got_pic);
if (r == 2)
return rh264_hw_retry(v, data, len, len, got_pic);
v->hw->retrying = 0;
if (r < 0)
return -1;
}
return got_pic ? 1 : 0;
}
@@ -11338,9 +11403,15 @@ int rh264_video_drain(rh264_video *v)
if (v->hw)
{
int got = 0;
int r;
if (v->hw->retrying)
return 2;
v->hw->fresh = 0;
if (v->hw->broken || rh264_hw_finish(v, &got) != 0)
if (v->hw->broken)
return -1;
r = rh264_hw_finish(v, &got);
if (r != 0)
return r;
if (got)
return 0;
if ((i = rh264_hw_out_pick(v->hw)) < 0)
+13
View File
@@ -1386,6 +1386,19 @@ void *image_transfer_anim_stream_new_avail(void *buf, size_t len,
return NULL;
}
bool image_transfer_anim_stream_waiting(const void *stream,
enum image_type_enum type)
{
#ifdef HAVE_RMP4
if (type == IMAGE_TYPE_MP4)
return rmp4_video_stream_waiting((const rmp4_video_stream_t*)stream) != 0;
#else
(void)stream;
(void)type;
#endif
return false;
}
void image_transfer_anim_stream_free(void *stream,
enum image_type_enum type){
switch (type)
+36 -3
View File
@@ -68,6 +68,9 @@
struct rmp4_video_stream
{
rmp4_t *demux;
rmp4_packet pending_packet;
size_t pending_floor;
int pending, waiting;
rvp8_video *vp8;
#ifdef HAVE_RVP9
rvp9_dec *vp9;
@@ -934,6 +937,8 @@ static int rmp4_video_decode_packet(rmp4_video_stream_t *s,
return 0;
}
dec = rh264_video_decode(s->h264, pkt->data, pkt->size);
if (dec == 2)
return 2;
if (dec < 0)
{
if (pkt->keyframe)
@@ -1010,10 +1015,21 @@ static int rmp4_video_stream_step(rmp4_video_stream_t *s,
int *duration_ms)
{
rmp4_packet pkt;
int drained;
for (;;)
{
int r = rmp4_read_packet(s->demux, &pkt);
int r;
if (s->pending)
{
pkt = s->pending_packet;
r = 1;
}
else
{
s->pending_floor = rmp4_consumed(s->demux);
r = rmp4_read_packet(s->demux, &pkt);
}
if (r == RMP4_READ_AGAIN)
/* The sample's bytes are not buffered yet. Do NOT fall
* through to the drain below: packets remain, and flushing
@@ -1025,6 +1041,13 @@ static int rmp4_video_stream_step(rmp4_video_stream_t *s,
if (pkt.track != s->track)
continue;
r = rmp4_video_decode_packet(s, &pkt);
if (r == 2)
{
s->pending_packet = pkt;
s->pending = 1;
return 2;
}
s->pending = 0;
if (r < 0)
return -1; /* decode error: end the animation */
if (r == 0)
@@ -1037,7 +1060,9 @@ static int rmp4_video_stream_step(rmp4_video_stream_t *s,
/* Out of packets. Display reordering can leave the last few pictures
* queued inside the H.264 decoder; hand them out before ending the
* pass, one per call, on the same presentation clock. */
if (s->h264 && rh264_video_drain(s->h264) == 0)
if (s->h264 && (drained = rh264_video_drain(s->h264)) == 2)
return 2;
if (s->h264 && drained == 0)
{
s->rndr_kind = 0; /* the drain replaced the current picture */
if (rh264_video_is_hw(s->h264))
@@ -1079,15 +1104,22 @@ int rmp4_video_stream_skip(rmp4_video_stream_t *s, int *duration_ms)
if (!s)
return -1;
s->waiting = 0;
/* 2 (not yet resident) also ends the loop: nothing was consumed,
* and the caller retries after feeding more bytes. Fully-resident
* streams never see it. */
while ((r = rmp4_video_stream_step(s, duration_ms)) == 0)
; /* non-shown frame: keep going */
s->waiting = r == 2;
return r;
}
int rmp4_video_stream_waiting(const rmp4_video_stream_t *s)
{
return s && s->waiting;
}
int rmp4_video_stream_set_hw(rmp4_video_stream_t *s, int enable)
{
if (!s || s->codec != RMP4_CODEC_H264)
@@ -1318,7 +1350,7 @@ size_t rmp4_video_stream_media_floor(rmp4_video_stream_t *s)
size_t rmp4_video_stream_consumed(rmp4_video_stream_t *s)
{
return s ? rmp4_consumed(s->demux) : 0;
return s ? (s->pending ? s->pending_floor : rmp4_consumed(s->demux)) : 0;
}
int64_t rmp4_video_stream_duration_ns(rmp4_video_stream_t *s)
@@ -1530,6 +1562,7 @@ void rmp4_video_stream_rewind(rmp4_video_stream_t *s)
{
if (!s)
return;
s->pending = s->waiting = 0;
rmp4_video_hwf_drop(s);
s->rndr_kind = 0; /* decoder reset invalidates the planes */
rmp4_rewind(s->demux);
+20 -8
View File
@@ -64,6 +64,7 @@ enum vp_job_status
VP_JOB_FINISHED, /* loops done, or the stream failed */
VP_JOB_HELD, /* presented as QUEUED: the slot is the sink's */
VP_JOB_IDLE, /* neither the worker's nor holding a frame */
VP_JOB_WAITING, /* hardware busy; the next poll retries */
VP_JOB_CANCELLED, /* released while queued: the worker still
holds its place in the queue */
VP_JOB_DROPPED /* the worker came to it and let it go */
@@ -124,6 +125,8 @@ static const uint32_t *vp_next(void *stream, enum image_type_enum type,
duration_ms);
if (frame)
return frame;
if (image_transfer_anim_stream_waiting(stream, type))
return NULL;
if (*loops_left > 0)
(*loops_left)--;
if (*loops_left == 0)
@@ -177,13 +180,13 @@ static void vp_schedule(video_player_t *p, int duration_ms, int64_t now)
}
/* Worker or polling thread: a GPU frame into hw[@idx]. */
static bool vp_step_hw(video_player_t *p, unsigned idx, int32_t *loops_left,
static int vp_step_hw(video_player_t *p, unsigned idx, int32_t *loops_left,
int *duration_ms)
{
if (p->sink.feed && !p->sink.feed(p->sink.user))
return false;
if (!vp_next(p->stream, p->type, loops_left, duration_ms))
return false;
return image_transfer_anim_stream_waiting(p->stream, p->type) ? 2 : 0;
/* a frame that came in software, which a backend that took the
* stream once and not after a rewind gives, ends the playback */
if (!image_transfer_anim_stream_hw_frame(p->stream, p->type,
@@ -245,7 +248,7 @@ static void vp_pool_sync(void)
/* Worker: one shown frame into job->frame in its final order. false
* when the playback is over. The job is RUNNING: the stream is the
* worker's alone. */
static bool vp_job_step(vp_job_t *job)
static int vp_job_step(vp_job_t *job)
{
video_player_t *p = job->owner;
void *stream = p->stream;
@@ -257,10 +260,11 @@ static bool vp_job_step(vp_job_t *job)
if (job->gpu)
{
job->hw = vp_step_hw(p, (job == p->job[1]) ? 1 : 0,
int r = vp_step_hw(p, (job == p->job[1]) ? 1 : 0,
&job->loops_left, &duration_ms);
job->hw = r == 1;
job->duration_ms = duration_ms;
return job->hw;
return r;
}
/* Every stream emits the order asked for; one that cannot is
* swizzled below. Asked a frame at a time, and a repeat ask of the
@@ -334,7 +338,7 @@ static void vp_worker(void *unused)
(void)unused;
for (;;)
{
bool alive;
int alive;
vp_job_t *job = vp_worker_local;
if (!job)
@@ -380,7 +384,7 @@ static void vp_worker(void *unused)
/* Publishes the frame to the poll, and wakes a release waiting
* the decode out. */
retro_atomic_store_release_int(&job->status,
alive ? VP_JOB_READY : VP_JOB_FINISHED);
alive == 2 ? VP_JOB_WAITING : (alive ? VP_JOB_READY : VP_JOB_FINISHED));
retro_eventcount_notify(&vp_worker_done);
}
}
@@ -537,6 +541,11 @@ static enum video_player_state vp_poll_threaded(video_player_t *p,
* READY one is done; the pair needs no joint snapshot */
su = retro_atomic_load_acquire_int(&ju->status);
so = retro_atomic_load_acquire_int(&jo->status);
if (su == VP_JOB_WAITING)
{
vp_job_enqueue(ju);
return VIDEO_PLAYER_PLAYING;
}
/* a job that writes its slot no more lets the sink keep it no more */
if (p->sink.slot_end && !(p->flags & VP_HW))
{
@@ -637,7 +646,10 @@ static enum video_player_state vp_poll_inline(video_player_t *p,
fr.hw = NULL;
if (p->flags & VP_HW)
{
if (!vp_step_hw(p, 0, &p->loops_left, &duration_ms))
int r = vp_step_hw(p, 0, &p->loops_left, &duration_ms);
if (r == 2)
return VIDEO_PLAYER_PLAYING;
if (!r)
return VIDEO_PLAYER_ENDED;
fr.pixels = NULL;
fr.slot = 0;
+2
View File
@@ -620,6 +620,8 @@ bool image_transfer_anim_stream_set_output(void *stream,
* frame came - and read or abandoned by the caller. */
bool image_transfer_anim_stream_set_hw(void *stream,
enum image_type_enum type, bool enable);
bool image_transfer_anim_stream_waiting(const void *stream,
enum image_type_enum type);
bool image_transfer_anim_stream_hw_frame(void *stream,
enum image_type_enum type, struct video_hw_frame *out);
+5 -2
View File
@@ -88,11 +88,13 @@ int rh264_video_set_extradata(rh264_video *v, const uint8_t *avcc, size_t len);
* decode order: returns 1 when a picture is ready through rh264_video_plane,
* 0 when the data was consumed but the picture is still held for reordering,
* and negative on malformed input or an unsupported (high-profile) stream. */
/* 2 on hardware backpressure: retry with the same live data and length. */
int rh264_video_decode(rh264_video *v, const uint8_t *data, size_t len);
/* Hand out the next pending picture in display order without feeding more
* data, for end of stream. Returns 0 when a picture became available through
* rh264_video_plane, -1 when nothing is pending. */
/* 2 when hardware output is busy; retry later. */
int rh264_video_drain(rh264_video *v);
/* Bits per sample of the decoded pictures (8..14). Above 8 the planes
@@ -255,10 +257,11 @@ typedef struct rh264_hw_ops
* NULL when the backend cannot take the stream. On the decoding
* thread, as are the rest. */
void *(*open)(void *user, const rh264_hw_sps_t *sps, unsigned slots);
/* Decode @pic into its slot; 0, or negative on failure. */
/* 0 done, 1 busy without submitting, negative on failure. */
int (*decode)(void *dec, const rh264_hw_pic_t *pic);
/* The picture in @slot is to be shown: a frame of it into @out that
* stays good while the slot is decoded into again. 0, or negative. */
* stays good while the slot is decoded into again. 0 done, 1 busy
* without taking a lease, negative on failure. */
int (*output)(void *dec, unsigned slot, video_hw_frame_t *out);
void (*close)(void *dec);
/* Optional: copy the visible picture in slot to packed I420 planes.
@@ -125,6 +125,8 @@ void rmp4_video_stream_get_info(const rmp4_video_stream_t *stream,
* memory order R,G,B,A by default; see rmp4_video_stream_set_argb. */
const uint32_t *rmp4_video_stream_next(rmp4_video_stream_t *stream,
int *duration_ms);
/* NULL from next with waiting set is backpressure, not end of pass. */
int rmp4_video_stream_waiting(const rmp4_video_stream_t *stream);
/* Select the channel order of subsequent frames: non-zero emits ARGB
* words (memory order B,G,R,A on little-endian), zero restores the
@@ -107,6 +107,8 @@ typedef struct
} fake_t;
static fake_t fake;
static int inject_busy;
static int decode_busy, output_busy;
/* the sample the next decode call is for, as the test feeds them */
static int cur_sample;
@@ -139,6 +141,12 @@ static int fake_decode(void *dec, const rh264_hw_pic_t *pic)
unsigned i, j;
fake_t *f = (fake_t*)dec;
(void)f;
if (inject_busy && !decode_busy)
{
decode_busy = 1;
return 1;
}
decode_busy = 0;
if (pic->slot < 0 || (unsigned)pic->slot >= fake.slots)
{
fake_err("picture %d: slot %d out of range", cur_sample, pic->slot);
@@ -211,6 +219,12 @@ static void fake_abandon(void *owner, uint64_t done, uint64_t value)
static int fake_output(void *dec, unsigned slot, video_hw_frame_t *out)
{
(void)dec;
if (inject_busy && !output_busy)
{
output_busy = 1;
return 1;
}
output_busy = 0;
if (slot >= fake.slots || !fake.pending[slot])
{
fake_err("slot %u shown without a picture waiting", slot);
@@ -316,7 +330,14 @@ static int decode(const char *mp4, int hw, int skip_lo, int skip_hi,
cur_sample = res->n;
rh264_video_set_skip_nonref(v,
res->n >= skip_lo && res->n < skip_hi);
r = rh264_video_decode(v, pkt.data, pkt.size);
{
unsigned tries = 0;
do
r = rh264_video_decode(v, pkt.data, pkt.size);
while (r == 2 && ++tries < 100);
if (r == 2)
r = -1;
}
res->n++;
if (rh264_video_dropped(v))
res->dropped++;
@@ -328,8 +349,15 @@ static int decode(const char *mp4, int hw, int skip_lo, int skip_hi,
if (r == 1)
res->shown++;
}
while (!res->failed && rh264_video_drain(v) == 0)
while (!res->failed)
{
int r = rh264_video_drain(v);
if (r == 2)
continue;
if (r != 0)
break;
res->shown++;
}
if (hw == 2)
res->hw = rh264_video_is_hw(v);
if (res->hw)
@@ -494,6 +522,16 @@ int main(void)
decode(mp4, 1, -1, -1, 1, &r);
check("no backend: stays in software", !r.hw && !r.failed && r.shown == r.n);
inject_busy = 1;
rh264_set_hw_backend(&fake_ops);
decode_busy = output_busy = 0;
hw_case("busy_baseline", "testsrc2=s=176x144:r=25", 24,
"-profile:v baseline -preset medium -x264-params bframes=0", 1);
hw_case("busy_bpyr", "testsrc2=s=176x144:r=25", 48,
"-profile:v main -preset slow -x264-params bframes=3:b-pyramid=normal:ref=4", 1);
hw_case("busy_slices", "testsrc2=s=192x160:r=25", 24,
"-profile:v high -preset medium -x264-params slices=4:bframes=2", 1);
inject_busy = 0;
run("rm -rf '%s'", dir);
printf("rh264_hw_test: %s (%d failure%s)\n", fails ? "FAIL" : "PASS",
fails, fails == 1 ? "" : "s");
@@ -67,6 +67,9 @@ typedef struct
uint32_t *out;
int frames;
int pos;
int wait_left;
int wait_at;
bool waiting;
int duration_ms;
int catchup; /* last value set */
int catchup_sets;
@@ -111,6 +114,12 @@ const uint32_t *image_transfer_anim_stream_next(void *stream,
uint32_t *dst;
unsigned i;
(void)type;
f->waiting = f->wait_left > 0 && f->pos == f->wait_at;
if (f->waiting)
{
f->wait_left--;
return NULL;
}
if (f->pos >= f->frames)
return NULL;
if (f->hw_on)
@@ -137,6 +146,13 @@ static void fake_abandon(void *owner, uint64_t done, uint64_t value)
f->hw_abandoned++;
}
bool image_transfer_anim_stream_waiting(const void *stream,
enum image_type_enum type)
{
(void)type;
return ((const fake_t*)stream)->waiting;
}
bool image_transfer_anim_stream_set_hw(void *stream,
enum image_type_enum type, bool enable)
{
@@ -525,6 +541,8 @@ static void lane_hw_inline(void)
fake_init(&f, 6, 10);
f.hw_can = true;
f.wait_left = 3;
f.wait_at = 2;
sink_init_hw(&k, &s);
k.hw_busy_every = 4;
p = video_player_new(&f, IMAGE_TYPE_MP4, 2, &s, VIDEO_PLAYER_INLINE);
@@ -589,6 +607,8 @@ static void lane_hw_threaded(void)
fake_init(&f, 7, 5);
f.hw_can = true;
f.wait_left = 3;
f.wait_at = 2;
sink_init_hw(&k, &s);
k.queue_every = 3;
k.hw_busy_every = 5;
+21
View File
@@ -30,6 +30,7 @@ BACKEND := $(RA)/gfx/common/d3d11_video_decode.c \
TARGETS := dxva_h264_test$(EXE)
ifeq ($(EXE),.exe)
TARGETS += d3d11_video_decode_mock_test$(EXE) d3d11_video_decode_test$(EXE)
TARGETS += d3d11_backpressure_test$(EXE)
LDFLAGS += -lwinmm
WINFLAGS := -isystem $(RA)/gfx/include/dxsdk -Wno-cast-function-type
endif
@@ -49,15 +50,35 @@ d3d11_video_decode_test$(EXE): d3d11_video_decode_test.c \
$(CC) $(CFLAGS) $(WINFLAGS) -o $@ d3d11_video_decode_test.c \
$(DECODERS) $(LDFLAGS) -ld3d11 -lm
PLAYER_SOURCES := $(RA)/libretro-common/formats/video_player.c \
$(RA)/libretro-common/formats/image_transfer.c \
$(RA)/libretro-common/formats/mp4/rmp4_video.c \
$(RA)/libretro-common/formats/h265/rh265.c \
$(RA)/libretro-common/formats/vp8/rvp8.c \
$(RA)/libretro-common/formats/webm/rwebm_video.c \
$(RA)/libretro-common/formats/webm/rwebm.c \
$(RA)/libretro-common/formats/image/image_blit_bands.c \
$(RA)/libretro-common/formats/image/image_hdr_blit.c \
$(RA)/libretro-common/features/features_cpu.c \
$(RA)/libretro-common/time/rtime.c
d3d11_backpressure_test$(EXE): d3d11_backpressure_test.c \
d3d11_video_decode_test.c $(DECODERS) $(DEPS) $(BACKEND) $(PLAYER_SOURCES)
$(CC) $(CFLAGS) $(WINFLAGS) -ffunction-sections -fdata-sections -o $@ \
d3d11_backpressure_test.c $(DECODERS) $(PLAYER_SOURCES) \
$(LDFLAGS) -Wl,--gc-sections $(if $(filter .exe,$(EXE)),-ld3d11) -lm
check: all
$(RUNNER) ./dxva_h264_test$(EXE)
ifeq ($(EXE),.exe)
$(RUNNER) ./d3d11_video_decode_mock_test$(EXE)
$(RUNNER) ./d3d11_video_decode_test$(EXE)
$(RUNNER) ./d3d11_backpressure_test$(EXE)
endif
clean:
rm -f dxva_h264_test dxva_h264_test.exe \
d3d11_video_decode_mock_test.exe d3d11_video_decode_test.exe
rm -f d3d11_backpressure_test d3d11_backpressure_test.exe
.PHONY: all check clean
@@ -0,0 +1,150 @@
#ifdef _WIN32
#define main reference_main
#include "d3d11_video_decode_test.c"
#undef main
#include <formats/image.h>
#include <formats/video_player.h>
static video_hw_frame_t held_frames[8];
static unsigned held_count;
static int player_start(void *user, unsigned width, unsigned height, unsigned jobs)
{
(void)user; (void)width; (void)height; (void)jobs;
return 0;
}
static enum video_player_present player_present(void *user,
const video_player_frame_t *frame)
{
(void)user;
if (!frame->hw || held_count >= 8)
return VIDEO_PLAYER_PRESENT_FAILED;
held_frames[held_count++] = *frame->hw;
return VIDEO_PLAYER_PRESENT_DONE;
}
static void test_backpressure(int free_later, int cancel)
{
video_player_sink_t sink = {0};
video_player_t *player;
void *stream;
unsigned i, before_count;
size_t anchor;
int64_t now = 0;
int64_t due;
DWORD started;
enum video_player_state state = VIDEO_PLAYER_PLAYING;
held_count = 0;
stream = image_transfer_anim_stream_new((void*)stream_baseline,
sizeof(stream_baseline), IMAGE_TYPE_MP4);
if (!stream || !image_transfer_anim_stream_set_hw(stream, IMAGE_TYPE_MP4, true))
{
puts("d3d11_backpressure_test: SKIP (H.264 decoder unavailable)");
image_transfer_anim_stream_free(stream, IMAGE_TYPE_MP4);
return;
}
sink.start = player_start;
sink.start_hw = player_start;
sink.present = player_present;
player = video_player_new(stream, IMAGE_TYPE_MP4, 1, &sink, VIDEO_PLAYER_INLINE);
check("inline player opened", stream && player);
if (!player)
{
image_transfer_anim_stream_free(stream, IMAGE_TYPE_MP4);
return;
}
for (i = 0; i < 1000 && held_count < 4 && state == VIDEO_PLAYER_PLAYING; i++)
{
state = video_player_poll(player, now += 100000);
if (held_count < 4)
Sleep(1);
}
check("four output leases held", held_count == 4);
if (free_later && held_count == 4)
video_hw_frame_abandon(&held_frames[1]);
anchor = image_transfer_anim_stream_consumed(stream, IMAGE_TYPE_MP4);
due = video_player_due(player);
started = GetTickCount();
state = video_player_poll(player, now += 100000);
check("busy poll returns without ending", state == VIDEO_PLAYER_PLAYING
&& GetTickCount() - started < 100);
if (free_later)
check("returned non-oldest texture is selected", held_count == 5
&& held_frames[4].image == held_frames[1].image);
if (!free_later)
{
check("pending packet stays inside the window", anchor
== image_transfer_anim_stream_consumed(stream, IMAGE_TYPE_MP4));
check("busy output is not published", held_count == 4);
before_count = held_count;
state = video_player_poll(player, now += 100000);
check("repeated busy poll preserves the frame", state == VIDEO_PLAYER_PLAYING
&& held_count == before_count);
check("busy poll does not advance the clock", video_player_due(player)
== due);
}
if (cancel)
{
video_player_free(player);
image_transfer_anim_stream_free(stream, IMAGE_TYPE_MP4);
}
for (i = 0; i < held_count; i++)
video_hw_frame_abandon(&held_frames[i]);
if (!cancel)
{
before_count = held_count;
for (i = 0; i < 1000 && held_count == before_count; i++)
{
state = video_player_poll(player, now += 100000);
if (held_count == before_count)
Sleep(1);
}
check("release resumes with one frame", state == VIDEO_PLAYER_PLAYING
&& held_count == before_count + 1);
if (held_count)
video_hw_frame_abandon(&held_frames[held_count - 1]);
video_player_free(player);
image_transfer_anim_stream_free(stream, IMAGE_TYPE_MP4);
}
}
int main(void)
{
D3D_FEATURE_LEVEL level = D3D_FEATURE_LEVEL_11_0;
setvbuf(stdout, NULL, _IONBF, 0);
if (FAILED(D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0,
&level, 1, D3D11_SDK_VERSION, &dev, NULL, &ctx))
|| FAILED(dev->lpVtbl->QueryInterface(dev, &d3d11vd_iid_device5,
(void**)&dev5))
|| FAILED(ctx->lpVtbl->QueryInterface(ctx, &d3d11vd_iid_context4,
(void**)&ctx4))
|| !d3d11_video_decode_init(dev))
{
D3D11VD_RELEASE(ctx4);
D3D11VD_RELEASE(dev5);
D3D11VD_RELEASE(ctx);
D3D11VD_RELEASE(dev);
puts("d3d11_backpressure_test: SKIP (no hardware decoder)");
return 0;
}
test_backpressure(0, 0);
test_backpressure(1, 0);
test_backpressure(0, 1);
d3d11_video_decode_deinit();
D3D11VD_RELEASE(ctx4);
D3D11VD_RELEASE(dev5);
D3D11VD_RELEASE(ctx);
D3D11VD_RELEASE(dev);
printf("d3d11_backpressure_test: %s (%d failures)\n", fails ? "FAIL" : "PASS", fails);
return fails ? 1 : 0;
}
#else
#include <stdio.h>
int main(void)
{
puts("d3d11_backpressure_test: SKIP (Windows GPU test)");
return 0;
}
#endif
@@ -10,7 +10,7 @@
* slot's view, the four buffers submitted with their sizes, the
* bitstream padded to 128 bytes with zeros the last slice takes, a
* slice entry for each NAL unit's start code;
* - DecoderBeginFrame's E_PENDING waited out, and given up on;
* - DecoderBeginFrame's E_PENDING returned for a later retry;
* - a bitstream buffer too small refused, one with no room for the
* padding taking none;
* - each frame shown copied from its slot's visible window, the
@@ -727,6 +727,8 @@ static void test_direct(void)
subs_free();
pending_left = 3;
for (i = 0; i < 3; i++)
check("E_PENDING: no submission", ops->decode(dec, &pic) == 1 && !n_subs);
r = ops->decode(dec, &pic);
check("E_PENDING three times, then decoded", !r && n_subs == 1
&& !pending_left);
@@ -741,8 +743,8 @@ static void test_direct(void)
stub_quiet = 1;
r = ops->decode(dec, &pic);
stub_quiet = 0;
check("E_PENDING for good: given up on", r < 0
&& GetTickCount() - t0 < 5000);
check("E_PENDING for good: retryable at once", r == 1
&& pending_left == 99999 && GetTickCount() - t0 < 100);
pending_left = 0;
subs_free();
@@ -770,8 +772,8 @@ static void test_direct(void)
stub_quiet = 1;
r = ops->output(dec, 0, &fr[4]);
stub_quiet = 0;
check("a fifth with none read: waited for, then given up",
r < 0 && GetTickCount() - t0 >= 900);
check("a fifth with none read: retryable at once",
r == 1 && GetTickCount() - t0 < 100);
video_hw_frame_abandon(&fr[1]);
t0 = GetTickCount();
r = ops->output(dec, 2, &fr[4]);
@@ -514,7 +514,18 @@ static int decode(const unsigned char *mp4, size_t len, int hw,
{
if (pkt.track != trk)
continue;
if ((r = rh264_video_decode(v, pkt.data, pkt.size)) < 0)
{
unsigned tries = 0;
do
{
r = rh264_video_decode(v, pkt.data, pkt.size);
if (r == 2)
Sleep(1);
} while (r == 2 && ++tries < 1000);
if (r == 2)
r = -1;
}
if (r < 0)
ok = 0;
else if (r == 1)
{
@@ -525,8 +536,16 @@ static int decode(const unsigned char *mp4, size_t len, int hw,
ok = 0;
}
}
while (ok && rh264_video_drain(v) == 0)
while (ok)
{
r = rh264_video_drain(v);
if (r == 2)
{
Sleep(1);
continue;
}
if (r != 0)
break;
if (!on_hw || hw == 2)
take_sw(f, v);
else if (rh264_video_hw_frame(v, &fr) != 0
@@ -199,6 +199,12 @@ const uint32_t *image_transfer_anim_stream_next(void *stream,
return gfx_anim_preview_next((gfx_anim_preview_t*)stream, duration_ms,
&native);
}
bool image_transfer_anim_stream_waiting(const void *stream,
enum image_type_enum type)
{
(void)stream; (void)type;
return false;
}
void image_transfer_anim_stream_rewind(void *stream,
enum image_type_enum type)
{