vo_gpu_next: implement VOCTRL_SCREENSHOT

Somewhat annoying but still relatively straightforward. There are
several ways to approach this, but I settled on reusing the pl_queue as
a cheap way to get access to the currently mapped frame. This saves us
from having to process `vo_frame` at all, and also avoids any overhead
from re-uploading the same frame twice.

(However maybe there's some circumstance in which `vo_frame` needs to be
queried/updated first, to get a screenshot of the correct frame? I'm not
sure.)

I also had the option of going with either pl_render_image() on the
extract pl_frame, or just calling pl_render_image_mix directly on it. I
went for the latter, because in the optimal case, this allows the
rendered frame to be directly retrieved from the cache, actually
entirely avoiding any sort of recompute overhead. This makes e.g. ctrl+s
during playback essentially free. (Except for the download cost,
obviously)

It would be even neater if we could make this VOCTRL asynchronous and
thereby leverage libplacebo's asynchronous download capabilities. But oh
well. That will have to wait for a sufficiently rainy day.

Closes #9388
This commit is contained in:
Niklas Haas 2021-11-25 23:02:39 +01:00 committed by Niklas Haas
parent c66f3b0123
commit 4991ffa859
1 changed files with 120 additions and 2 deletions

View File

@ -421,6 +421,7 @@ static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src
.len = mpi->icc_profile ? mpi->icc_profile->size : 0,
},
.rotation = par->rotate / 90,
.user_data = mpi,
};
// mp_image, like AVFrame, likes communicating RGB/XYZ/YCbCr status
@ -717,10 +718,10 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
bool should_draw = sw->fns->start_frame(sw, NULL); // for wayland logic
if (!should_draw || !pl_swapchain_start_frame(p->sw, &swframe)) {
// Advance the queue state to the current PTS to discard unused frames
pl_queue_update(p->queue, NULL, &(struct pl_queue_params) {
pl_queue_update(p->queue, NULL, pl_queue_params(
.pts = frame->current->pts + vsync_offset,
.radius = pl_frame_mix_radius(&p->params),
});
));
return;
}
@ -881,6 +882,119 @@ static bool update_auto_profile(struct priv *p, int *events)
return false;
}
static void video_screenshot(struct vo *vo, struct voctrl_screenshot *args)
{
struct priv *p = vo->priv;
pl_gpu gpu = p->gpu;
pl_tex fbo = NULL;
args->res = NULL;
update_options(p);
p->params.info_callback = NULL;
p->params.skip_caching_single_frame = true;
p->params.preserve_mixing_cache = false;
p->params.allow_delayed_peak_detect = false;
p->params.frame_mixer = NULL;
// Retrieve the current frame from the frame queue
struct pl_frame_mix mix;
enum pl_queue_status status;
status = pl_queue_update(p->queue, &mix, pl_queue_params(.pts = p->last_pts));
assert(status != PL_QUEUE_EOF);
if (status == PL_QUEUE_ERR) {
MP_ERR(vo, "Unknown error occured while trying to take screenshot!\n");
return;
}
if (status == PL_QUEUE_MORE || !mix.num_frames) {
MP_ERR(vo, "No frames available to take screenshot of? Open issue\n");
return;
}
// Passing an interpolation radius of 0 guarantees that the first frame in
// the resulting mix is the correct frame for this PTS
struct pl_frame *image = (struct pl_frame *) mix.frames[0];
struct mp_image *mpi = image->user_data;
int orig_overlays = image->num_overlays;
if (!args->subs)
image->num_overlays = 0;
struct mp_rect src = p->src, dst = p->dst;
struct mp_osd_res osd = p->osd_res;
if (!args->scaled) {
src = dst = (struct mp_rect) {0, 0, mpi->params.w, mpi->params.h};
osd = (struct mp_osd_res) {
.w = mpi->params.w,
.h = mpi->params.h,
.display_par = 1.0,
};
}
// Create target FBO, try high bit depth first
int mpfmt;
for (int depth = args->high_bit_depth ? 16 : 8; depth; depth -= 8) {
mpfmt = depth == 16 ? IMGFMT_RGBA64 : IMGFMT_RGBA;
pl_fmt fmt = pl_find_fmt(gpu, PL_FMT_UNORM, 4, depth, depth,
PL_FMT_CAP_RENDERABLE | PL_FMT_CAP_HOST_READABLE);
if (!fmt)
continue;
fbo = pl_tex_create(gpu, pl_tex_params(
.w = osd.w,
.h = osd.h,
.format = fmt,
.blit_dst = true,
.renderable = true,
.host_readable = true,
.storable = fmt->caps & PL_FMT_CAP_STORABLE,
));
if (fbo)
break;
}
if (!fbo) {
MP_ERR(vo, "Failed creating target FBO for screenshot!\n");
goto done;
}
struct pl_frame target = {
.num_planes = 1,
.planes[0] = {
.texture = fbo,
.components = 4,
.component_mapping = {0, 1, 2, 3},
},
};
apply_target_options(p, &target);
apply_crop(image, src, mpi->params.w, mpi->params.h);
apply_crop(&target, dst, fbo->params.w, fbo->params.h);
if (args->osd)
write_overlays(vo, osd, 0, OSD_DRAW_OSD_ONLY, &p->osd_state, &target, false);
if (!pl_render_image_mix(p->rr, &mix, &target, &p->params)) {
MP_ERR(vo, "Failed rendering frame!\n");
goto done;
}
args->res = mp_image_alloc(mpfmt, fbo->params.w, fbo->params.h);
if (!args->res)
goto done;
bool ok = pl_tex_download(gpu, pl_tex_transfer_params(
.tex = fbo,
.ptr = args->res->planes[0],
.row_pitch = args->res->stride[0],
));
if (!ok)
TA_FREEP(&args->res);
// fall through
done:
pl_tex_destroy(gpu, &fbo);
image->num_overlays = orig_overlays;
}
static int control(struct vo *vo, uint32_t request, void *data)
{
struct priv *p = vo->priv;
@ -922,6 +1036,10 @@ static int control(struct vo *vo, uint32_t request, void *data)
case VOCTRL_PERFORMANCE_DATA:
*(struct voctrl_performance_data *) data = p->perf;
return true;
case VOCTRL_SCREENSHOT:
video_screenshot(vo, data);
return true;
}
int events = 0;