Skip to content

Commit

Permalink
utils/frame_queue: add new interpolation helper
Browse files Browse the repository at this point in the history
The API for `pl_frame_mix` is currently a bit abstract and high-level.
Most clients will probably not care to reimplement the massive amounts
of logic that go into constructing a proper frame queue.

Enter `pl_queue`, the solution for all your frame queuing needs! This
will interface with the decoder via your choice of a push or pull API,
and translate all decoded frames into an abstract internal timeline,
`pl_frame_mix` slices of which can be probed for arbitrary timestamps.

As an extra bonus, this also contains code for things like FPS
estimation and VPS (vsyncs per second) estimation, somewhat mirroring
the logic in e.g. mpv's interpolation/display-sync code.

It's worth pointing out that this is a rather severe API break for
`pl_frame_mix`, but I'm pretty sure this API has absolutely zero users,
given the massive amount of effort required to use it before this
commit. So I didn't bother with backwards compatibility, aside from an
API bump as usualy.
  • Loading branch information
haasn committed Mar 26, 2021
1 parent ac44e8f commit 0e59440
Show file tree
Hide file tree
Showing 10 changed files with 775 additions and 42 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ identifiers (so they can be freely merged together).
- `renderer.h`: A high-level renderer which combines the shader primitives
and dispatch mechanism into a fully-fledged rendering pipeline that takes
raw texture data and transforms it into the desired output image.
- `utils/frame_queue.h`: A high-level frame queuing abstraction. This API
can be used to interface with a decoder (or other source of frames), and
takes care of translating timestamped frames into a virtual stream of
presentation events suitable for use with `renderer.h`, including any extra
context required for frame interpolation (`pl_frame_mix`).
- `utils/upload.h`: A high-level helper for uploading generic data in some
user-described format to a plane texture suitable for use with `renderer.h`.
These helpers essentially take care of picking/mapping a good image format
Expand Down
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ project('libplacebo', ['c', 'cpp'],
license: 'LGPL2.1+',
default_options: ['c_std=c99', 'cpp_std=c++11', 'warning_level=2'],
meson_version: '>=0.51',
version: '3.117.0',
version: '3.118.0',
)

# Version number
Expand Down
1 change: 1 addition & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
#include "include/libplacebo/shaders/lut.h"
#include "include/libplacebo/shaders/sampling.h"
#include "include/libplacebo/swapchain.h"
#include "include/libplacebo/utils/frame_queue.h"
#include "include/libplacebo/utils/upload.h"

#ifdef PL_HAVE_LCMS
Expand Down
33 changes: 25 additions & 8 deletions src/include/libplacebo/renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,15 @@ struct pl_render_params {
// Configures the algorithm used for frame mixing (when using
// `pl_render_image_mix`). Ignored otherwise. As a special requirement,
// this must be a filter config with `polar` set to false, since it's only
// used for 1D mixing and thus only 1D filters are compatible. If left as
// NULL, then libplacebo will use a built-in, inexpensive frame mixing
// algorithm.
// used for 1D mixing and thus only 1D filters are compatible.
//
// It's worth pointing out that this built-in frame mixing can often be
// better than any of the available filter configurations. So it's not a
// bad idea to leave this as NULL. In fact, that's the recommended default.
// As a special case, if `frame_mixer->kernel` is NULL, then libplacebo
// will use a built-in, inexpensive and relatively unobtrusive oversampling
// frame mixing algorithm. (See `pl_oversample_frame_mixer`)
//
// If set to NULL, frame mixing is disabled, in which case
// `pl_render_image_mix` behaves as `pl_render_image`, also completely
// bypassing the mixing cache.
const struct pl_filter_config *frame_mixer;

// Configures the settings used to deband source textures. Leaving this as
Expand Down Expand Up @@ -252,6 +254,10 @@ extern const struct pl_render_params pl_render_default_params;
// and where maximum image quality is desired.
extern const struct pl_render_params pl_render_high_quality_params;

// Special filter config for the built-in oversampling frame mixing algorithm.
// This is equivalent to (struct pl_filter_config) {0}.
extern const struct pl_filter_config pl_oversample_frame_mixer;

#define PL_MAX_PLANES 4

// High level description of a single slice of an image. This basically
Expand Down Expand Up @@ -518,7 +524,10 @@ struct pl_frame_mix {

// A list of the frames themselves. The frames can have different
// colorspaces, configurations of planes, or even sizes.
const struct pl_frame *frames;
//
// Note: This is a list of pointers, to avoid users having to copy
// around `pl_frame` structs when re-organizing this array.
const struct pl_frame **frames;

// A list of unique signatures, one for each frame. These are used to
// identify frames across calls to this function, so it's crucial that they
Expand Down Expand Up @@ -556,7 +565,7 @@ struct pl_frame_mix {
float vsync_duration;

// Explanation of the frame mixing radius: The algorithm chosen in
// `pl_render_params.frame_mixing` has a canonical radius equal to
// `pl_render_params.frame_mixer` has a canonical radius equal to
// `pl_filter_config.kernel->radius`. This means that the frame mixing
// algorithm will (only) need to consult all of the frames that have a
// distance within the interval [-radius, radius]. As such, the user should
Expand All @@ -568,6 +577,14 @@ struct pl_frame_mix {
// "next" frames.
};

// Helper function to calculate the frame mixing radius.
static inline float pl_frame_mix_radius(const struct pl_render_params *params)
{
return (params->frame_mixer && params->frame_mixer->kernel)
? params->frame_mixer->kernel->radius
: 0.0;
}

// Render a mixture of images to the target using the given parameters. This
// functions much like a generalization of `pl_render_image`, for when the API
// user has more control over the frame queue / vsync loop, and can provide a
Expand Down
149 changes: 149 additions & 0 deletions src/include/libplacebo/utils/frame_queue.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* This file is part of libplacebo.
*
* libplacebo is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* libplacebo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with libplacebo. If not, see <http://www.gnu.org/licenses/>.
*/

#include <libplacebo/renderer.h>

#ifndef LIBPLACEBO_FRAME_QUEUE_H
#define LIBPLACEBO_FRAME_QUEUE_H

// This file contains an abstraction layer for automatically turning a
// conceptual stream of (frame, pts) pairs, as emitted by a decoder or filter
// graph, into a `pl_frame_mix` suitable for `pl_render_image_mix`.
//
// This API ensures that minimal work is performed (e.g. only mapping frames
// that are actually required), while also satisfying the requirements
// of any configured frame mixer.

enum pl_queue_status {
QUEUE_OK, // success
QUEUE_EOF, // no more frames are available
QUEUE_MORE, // more frames needed, but not (yet) available
QUEUE_ERR = -1, // some unknown error occurred while retrieving frames
};

struct pl_source_frame {
// The frame's presentation timestamp, in seconds relative to the first
// frame. These must be monotonically increasing for subsequent frames.
// To implement a discontinuous jump, users must explicitly reset the
// frame queue with `pl_queue_reset` and restart from PTS 0.0.
float pts;

// Abstract frame data itself. To allow mapping frames only when they're
// actually needed, frames use a lazy representation. The provided
// callbacks will be invoked to interface with it.
void *frame_data;

// This will be called to map the frame to the GPU, only if needed.
//
// `tex` is a pointer to an array of 4 texture objects (or NULL), which
// *may* serve as backing storage for the texture being mapped. These are
// intended to be recreated by `map`, e.g. using `pl_tex_recreate` or
// `pl_upload_plane` as appropriate. They will be managed internally by
// `pl_queue` and destroyed at some unspecified future point in time.
//
// Note: If `map` fails, it will not be retried, nor will `discard` be run.
// The user should clean up state in this case.
bool (*map)(const struct pl_gpu *gpu, const struct pl_tex **tex,
const struct pl_source_frame *src, struct pl_frame *out_frame);

// If present, this will be called on frames that are done being used by
// `pl_queue`. This may be useful to e.g. unmap textures backed by external
// APIs such as hardware decoders. (Optional)
void (*unmap)(const struct pl_gpu *gpu, struct pl_frame *frame,
const struct pl_source_frame *src);

// This function will be called for frames that are deemed unnecessary
// (e.g. never became visible) and should instead be cleanly freed.
// (Optional)
void (*discard)(const struct pl_source_frame *src);
};

// Create a new, empty frame queue.
//
// It's highly recommended to fully render a single frame with `pts == 0.0`,
// and flush the GPU pipeline with `pl_gpu_finish`, prior to starting the timed
// playback loop.
struct pl_queue *pl_queue_create(const struct pl_gpu *gpu);
void pl_queue_destroy(struct pl_queue **queue);

// Explicitly clear the queue. This is essentially equivalent to destroying
// and recreating the queue, but preserves any internal memory allocations.
void pl_queue_reset(struct pl_queue *queue);

// Explicitly push a frame. This is an alternative way to feed the frame queue
// with incoming frames, the other method being the asynchronous callback
// specified as `pl_queue_params.get_frame`. Both methods may be used
// simultaneously, although providing `get_frame` is recommended since it
// avoids the risk of the queue underrunning.
//
// When no more frames are available, call this function with `frame == NULL`
// to indicate EOF and begin draining the frame queue.
void pl_queue_push(struct pl_queue *queue, const struct pl_source_frame *frame);

struct pl_queue_params {
// The PTS of the frame that will be rendered. This should be set to the
// timestamp (in seconds) of the next vsync, relative to the initial frame.
//
// These must be monotonically increasing. To implement a discontinuous
// jump, users must explicitly reset the frame queue with `pl_queue_reset`
// and restart from PTS 0.0.
float pts;

// The radius of the configured mixer. This should be set to the value
// as returned by `pl_frame_mix_radius`.
float radius;

// The estimated duration of a vsync, in seconds. This will only be used as
// a hint, the true value will be estimated by comparing `pts` timestamps
// between calls to `pl_queue_update`. (Optional)
float vsync_duration;

// The estimated duration of a frame, in seconds. This will only be used as
// an initial hint, the true value will be estimated by comparing `pts`
// timestamps between source frames. (Optional)
float frame_duration;

// This callback will be used to pull new frames from the decoder. It may
// block if needed. The user is responsible for setting appropriate time
// limits and/or returning and interpreting QUEUE_MORE as sensible.
//
// Providing this callback is entirely optional. Users can instead choose
// to manually feed the frame queue with new frames using `pl_queue_push`.
enum pl_queue_status (*get_frame)(struct pl_source_frame *out_frame,
const struct pl_queue_params *params);
void *priv;
};

// Advance the frame queue's internal state to the target timestamp. Any frames
// which are no longer needed (i.e. too far in the past) are automatically
// unmapped and evicted. Any future frames which are needed to fill the queue
// must either have been pushed in advance, or will be requested using the
// provided `get_frame` callback.
//
// This function may fail with QUEUE_MORE, in which case the user must
// ensure more frames are available and then re-run this function with
// the same parameters.
//
// The resulting mix of frames in `out_mix` will represent the neighbourhood of
// the target timestamp, and can be passed to `pl_render_image_mix` as-is.
//
// Note: `out_mix` will only remain valid until the next call to `pl_queue_*`.
enum pl_queue_status pl_queue_update(struct pl_queue *queue,
struct pl_frame_mix *out_mix,
const struct pl_queue_params *params);

#endif // LIBPLACEBO_FRAME_QUEUE_H
2 changes: 2 additions & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ headers = [
'swapchain.h',
'utils/dav1d.h',
'utils/dav1d_internal.h',
'utils/frame_queue.h',
'utils/libav.h',
'utils/libav_internal.h',
'utils/upload.h',
Expand Down Expand Up @@ -192,6 +193,7 @@ sources = [
'shaders/sampling.c',
'spirv.c',
'swapchain.c',
'utils/frame_queue.c',
'utils/upload.c',
]

Expand Down
12 changes: 8 additions & 4 deletions src/pl_alloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,14 +145,18 @@ void pl_ref_deref(struct pl_ref **ref);
} \
} while (0)

#define PL_ARRAY_REMOVE_AT(arr, idx) \
#define PL_ARRAY_REMOVE_RANGE(arr, idx, count) \
do { \
size_t _idx = (idx); \
assert(_idx < (arr).num); \
memmove(&(arr).elem[_idx], &(arr).elem[_idx + 1], \
(--(arr).num - _idx) * sizeof((arr).elem[0])); \
size_t _count = (count); \
assert(_idx + _count <= (arr).num); \
memmove(&(arr).elem[_idx], &(arr).elem[_idx + _count], \
((arr).num - _idx - _count) * sizeof((arr).elem[0])); \
(arr).num -= _count; \
} while (0)

#define PL_ARRAY_REMOVE_AT(arr, idx) PL_ARRAY_REMOVE_RANGE(arr, idx, 1)

#define PL_ARRAY_INSERT_AT(parent, arr, idx, ...) \
do { \
size_t _idx = (idx); \
Expand Down
Loading

0 comments on commit 0e59440

Please sign in to comment.