diff --git a/include/mpv/render.h b/include/mpv/render.h index 99aadeb5d837d..51505f2f9b043 100644 --- a/include/mpv/render.h +++ b/include/mpv/render.h @@ -422,6 +422,16 @@ typedef enum mpv_render_param_type { * See MPV_RENDER_PARAM_SW_STRIDE for alignment requirements. */ MPV_RENDER_PARAM_SW_POINTER = 20, + /** + * The name of the render backend to use. Valid for mpv_render_context_create(). + * + * Type: char* + * + * Defined backends: + * "gpu" (default) + * "gpu-next" + */ + MPV_RENDER_PARAM_BACKEND = 21, } mpv_render_param_type; /** diff --git a/meson.build b/meson.build index b5b6c2ca5023a..c196785491003 100644 --- a/meson.build +++ b/meson.build @@ -237,6 +237,9 @@ sources = files( 'video/out/gpu/utils.c', 'video/out/gpu/video.c', 'video/out/gpu/video_shaders.c', + 'video/out/gpu_next/libmpv_gpu_next.c', + 'video/out/gpu_next/ra.c', + 'video/out/gpu_next/video.c', 'video/out/libmpv_sw.c', 'video/out/vo.c', 'video/out/vo_gpu.c', diff --git a/video/out/gpu_next/context.c b/video/out/gpu_next/context.c index b4bfa9d643a9a..84f4b303b2746 100644 --- a/video/out/gpu_next/context.c +++ b/video/out/gpu_next/context.c @@ -15,22 +15,32 @@ * License along with mpv. If not, see . */ -#include +#include // for PL_HAVE_OPENGL, PL_API_VER #ifdef PL_HAVE_D3D11 #include #endif #ifdef PL_HAVE_OPENGL -#include +#include "mpv/render_gl.h" // for mpv_opengl_init_params +#include // for pl_opengl_destroy +#include "video/out/gpu_next/libmpv_gpu_next.h" // for libmpv_gpu_next_context +#include "video/out/gpu_next/ra.h" // for ra_pl_create, ra_pl_... #endif -#include "context.h" -#include "config.h" -#include "common/common.h" -#include "options/m_config.h" -#include "video/out/placebo/utils.h" -#include "video/out/gpu/video.h" +#include // for NULL +#include "config.h" // for HAVE_GL, HAVE_D3D11 +#include "context.h" // for gpu_ctx +#include "common/msg.h" // for MP_ERR, mp_msg, mp_msg_err +#include "mpv/client.h" // for mpv_error +#include "mpv/render.h" // for mpv_render_param +#include "options/options.h" // for mp_vo_opts +#include "ta/ta_talloc.h" // for talloc_zero, talloc_... +#include "video/out/gpu/context.h" // for ra_ctx_opts, ra_ctx +#include "video/out/libmpv.h" // for get_mpv_render_param +#include "video/out/opengl/common.h" // for GL +#include "video/out/placebo/utils.h" // for mppl_log_set_probing +#include "video/out/vo.h" // for vo #if HAVE_D3D11 #include "osdep/windows_utils.h" @@ -39,15 +49,27 @@ #endif #if HAVE_GL -#include "video/out/opengl/context.h" -#include "video/out/opengl/ra_gl.h" +#include "video/out/opengl/ra_gl.h" // for ra_is_gl, ra_gl_get # if HAVE_EGL -#include +#include // for eglGetCurrentContext # endif #endif #if HAVE_VULKAN -#include "video/out/vulkan/context.h" +#include "video/out/vulkan/context.h" // for ra_vk_ctx_get +#endif + +#if HAVE_GL +// Store Libplacebo OpenGL context information. +struct priv { + pl_log pl_log; + pl_opengl gl; + pl_gpu gpu; + struct ra_next *ra; + + // Store a persistent copy of the init params to avoid a dangling pointer. + mpv_opengl_init_params gl_params; +}; #endif #if HAVE_D3D11 @@ -235,3 +257,177 @@ void gpu_ctx_destroy(struct gpu_ctx **ctxp) talloc_free(ctx); *ctxp = NULL; } + +#if HAVE_GL && defined(PL_HAVE_OPENGL) +/** + * @brief Callback to make the OpenGL context current. + * @param priv Pointer to the private data (mpv_opengl_init_params). + * @return True on success, false on failure. + */ +static bool pl_callback_makecurrent_gl(void *priv) +{ + mpv_opengl_init_params *gl_params = priv; + // The mpv render API contract specifies that the client must make the + // context current inside its get_proc_address callback. We can trigger + // this by calling it with a harmless, common function name. + if (gl_params && gl_params->get_proc_address) { + gl_params->get_proc_address(gl_params->get_proc_address_ctx, "glGetString"); + return true; + } + + return false; +} + +/** + * @brief Callback to release the OpenGL context. + * @param priv Pointer to the private data (mpv_opengl_init_params). + */ +static void pl_callback_releasecurrent_gl(void *priv) +{ +} + +/** + * @brief Callback to log messages from libplacebo. + * @param log_priv Pointer to the private data (mp_log). + * @param level The log level. + * @param msg The log message. + */ +static void pl_log_cb(void *log_priv, enum pl_log_level level, const char *msg) +{ + struct mp_log *log = log_priv; + mp_msg(log, MSGL_WARN, "[gpu-next:pl] %s\n", msg); +} + +/** + * @brief Initializes the OpenGL context for the GPU next renderer. + * @param ctx The libmpv_gpu_next_context to initialize. + * @param params The render parameters. + * @return 0 on success, negative error code on failure. + */ +static int libmpv_gpu_next_init_gl(struct libmpv_gpu_next_context *ctx, mpv_render_param *params) +{ + ctx->priv = talloc_zero(NULL, struct priv); + struct priv *p = ctx->priv; + + mpv_opengl_init_params *gl_params = + get_mpv_render_param(params, MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, NULL); + if (!gl_params || !gl_params->get_proc_address) + return MPV_ERROR_INVALID_PARAMETER; + + // Make a persistent copy of the params struct's contents. + p->gl_params = *gl_params; + + // Setup libplacebo logging + struct pl_log_params log_params = { + .log_level = PL_LOG_DEBUG + }; + + // Enable verbose logging if trace is enabled + if (mp_msg_test(ctx->log, MSGL_TRACE)) { + log_params.log_cb = pl_log_cb; + log_params.log_priv = ctx->log; + } + + p->pl_log = pl_log_create(PL_API_VER, &log_params); + p->gl = pl_opengl_create(p->pl_log, pl_opengl_params( + .get_proc_addr_ex = (pl_voidfunc_t (*)(void*, const char*))gl_params->get_proc_address, + .proc_ctx = gl_params->get_proc_address_ctx, + .make_current = pl_callback_makecurrent_gl, + .release_current = pl_callback_releasecurrent_gl, + .priv = &p->gl_params // Pass the ADDRESS of our persistent copy + )); + + if (!p->gl) { + MP_ERR(ctx, "Failed to create libplacebo OpenGL context.\n"); + pl_log_destroy(&p->pl_log); + return MPV_ERROR_UNSUPPORTED; + } + p->gpu = p->gl->gpu; + + // Pass the libplacebo log to the RA as well. + p->ra = ra_pl_create(p->gpu, ctx->log, p->pl_log); + if (!p->ra) { + pl_opengl_destroy(&p->gl); + pl_log_destroy(&p->pl_log); + return MPV_ERROR_VO_INIT_FAILED; + } + + ctx->ra = p->ra; + ctx->gpu = p->gpu; + return 0; +} + +/** + * @brief Wraps an OpenGL framebuffer object (FBO) as a libplacebo texture. + * @param ctx The libmpv_gpu_next_context. + * @param params The render parameters. + * @param out_tex Pointer to the output texture. + * @return 0 on success, negative error code on failure. + */ +static int libmpv_gpu_next_wrap_fbo_gl(struct libmpv_gpu_next_context *ctx, + mpv_render_param *params, pl_tex *out_tex) +{ + struct priv *p = ctx->priv; + *out_tex = NULL; + + // Get the FBO from the render parameters + mpv_opengl_fbo *fbo = + get_mpv_render_param(params, MPV_RENDER_PARAM_OPENGL_FBO, NULL); + if (!fbo) + return MPV_ERROR_INVALID_PARAMETER; + + // Wrap the FBO as a libplacebo texture + pl_tex tex = pl_opengl_wrap(p->gpu, pl_opengl_wrap_params( + .framebuffer = fbo->fbo, + .width = fbo->w, + .height = fbo->h, + .iformat = fbo->internal_format + )); + + if (!tex) { + MP_ERR(ctx, "Failed to wrap provided FBO as a libplacebo texture.\n"); + return MPV_ERROR_GENERIC; + } + + *out_tex = tex; + return 0; +} + +/** + * @brief Callback to mark the end of a frame rendering. + * @param ctx The libmpv_gpu_next_context. + */ +static void libmpv_gpu_next_done_frame_gl(struct libmpv_gpu_next_context *ctx) +{ + // Nothing to do (yet), leaving the function empty. +} + +/** + * @brief Destroys the OpenGL context for the GPU next renderer. + * @param ctx The libmpv_gpu_next_context to destroy. + */ +static void libmpv_gpu_next_destroy_gl(struct libmpv_gpu_next_context *ctx) +{ + struct priv *p = ctx->priv; + if (!p) + return; + + if (p->ra) { + ra_pl_destroy(&p->ra); + } + + pl_opengl_destroy(&p->gl); + pl_log_destroy(&p->pl_log); +} + +/** + * @brief Context functions for the OpenGL GPU next renderer. + */ +const struct libmpv_gpu_next_context_fns libmpv_gpu_next_context_gl = { + .api_name = MPV_RENDER_API_TYPE_OPENGL, + .init = libmpv_gpu_next_init_gl, + .wrap_fbo = libmpv_gpu_next_wrap_fbo_gl, + .done_frame = libmpv_gpu_next_done_frame_gl, + .destroy = libmpv_gpu_next_destroy_gl, +}; +#endif diff --git a/video/out/gpu_next/context.h b/video/out/gpu_next/context.h index aa441964fb098..d15ab6ca6e7ca 100644 --- a/video/out/gpu_next/context.h +++ b/video/out/gpu_next/context.h @@ -17,13 +17,20 @@ #pragma once -#include +#include "libplacebo/gpu.h" // for pl_gpu +#include "libplacebo/log.h" // for pl_log +#include "libplacebo/swapchain.h" // for pl_swapchain +#include "stdbool.h" // for bool -struct mp_log; -struct ra_ctx; +/** + * The rendering abstraction context. + */ struct ra_ctx_opts; struct vo; +/** + * The main GPU context structure. + */ struct gpu_ctx { struct mp_log *log; struct ra_ctx *ra_ctx; diff --git a/video/out/gpu_next/libmpv_gpu_next.c b/video/out/gpu_next/libmpv_gpu_next.c new file mode 100644 index 0000000000000..31c0cb50bc986 --- /dev/null +++ b/video/out/gpu_next/libmpv_gpu_next.c @@ -0,0 +1,307 @@ +#include "libmpv_gpu_next.h" +#include // for NULL +#include "common/msg.h" // for mp_log_new, MP_ERR +#include "config.h" // for HAVE_GL +#include "libplacebo/config.h" // for PL_HAVE_OPENGL +#include "libplacebo/gpu.h" // for pl_tex, pl_tex_params, pl_tex_t +#include "mpv/client.h" // for mpv_error +#include "mpv/render.h" // for mpv_render_param, mpv_render_param_type +#include "ra.h" // for ra_next_tex_destroy +#include "stdbool.h" // for bool, false +#include "string.h" // for strcmp +#include "ta/ta_talloc.h" // for talloc_free, talloc_zero +#include "video.h" // for pl_video_check_format, pl_video_init +#include "video/hwdec.h" // for hwdec_devices_create, hwdec_devices_d... +#include "video/out/libmpv.h" // for render_backend, get_mpv_render_param +#include "video/out/vo.h" // for vo_frame (ptr only), voctrl_screenshot + +/* + * Structure for the image parameters. + */ +struct mp_image_params; +struct mp_osd_res; +struct mp_rect; + +/* + * Private data for the GPU next render backend. + */ +struct priv { + struct libmpv_gpu_next_context *context; // Manages the API (e.g., OpenGL) + struct pl_video *video_engine; // Manages synchronous libplacebo rendering +}; + +/* +* List of available API context implementations (e.g., GL, Vulkan - currently only OpenGL) +*/ +static const struct libmpv_gpu_next_context_fns *context_backends[] = { +#if HAVE_GL && defined(PL_HAVE_OPENGL) + &libmpv_gpu_next_context_gl, +#endif + NULL +}; + +/* + * @brief Initializes the render_backend layer. + * @param ctx The render_backend context. + * @param params The render parameters. + * @return 0 on success, negative error code on failure. + */ +static int init(struct render_backend *ctx, mpv_render_param *params) +{ + ctx->priv = talloc_zero(NULL, struct priv); + struct priv *p = ctx->priv; + + // Get the API type from the render parameters. + char *api = get_mpv_render_param(params, MPV_RENDER_PARAM_API_TYPE, NULL); + if (!api) { + MP_ERR(ctx, "API type not specified.\n"); + return MPV_ERROR_INVALID_PARAMETER; + } + + // Find and initialize the requested API context (e.g., _gl.c). + // This will create the pl_gpu and the `ra` (libplacebo render abstraction). + for (int n = 0; context_backends[n]; n++) { + const struct libmpv_gpu_next_context_fns *backend = context_backends[n]; + if (strcmp(backend->api_name, api) == 0) { + p->context = talloc_zero(p, struct libmpv_gpu_next_context); + *p->context = (struct libmpv_gpu_next_context){ + .global = ctx->global, + .log = mp_log_new(p, ctx->log, "gpu-next-ctx"), + .fns = backend, + }; + break; + } + } + if (!p->context) { + MP_ERR(ctx, "Requested API type '%s' is not supported.\n", api); + return MPV_ERROR_NOT_IMPLEMENTED; + } + int err = p->context->fns->init(p->context, params); + if (err < 0) { + talloc_free(p->context); + p->context = NULL; + return err; + } + + // Initialize our synchronous libplacebo rendering engine. + p->video_engine = pl_video_init(ctx->global, ctx->log, p->context->ra); + if (!p->video_engine) { + p->context->fns->destroy(p->context); + talloc_free(p->context); + return MPV_ERROR_VO_INIT_FAILED; + } + + // Create hardware decoder devices. + ctx->hwdec_devs = hwdec_devices_create(); + ctx->driver_caps = VO_CAP_ROTATE90 | VO_CAP_VFLIP; + return 0; +} + +/* + * @brief Destroys the render_backend layer. + * @param ctx The render_backend context. + */ +static void destroy(struct render_backend *ctx) +{ + struct priv *p = ctx->priv; + if (!p) return; + + hwdec_devices_destroy(ctx->hwdec_devs); + pl_video_uninit(&p->video_engine); + if (p->context) { + p->context->fns->destroy(p->context); // This destroys the RA + talloc_free(p->context); + } + talloc_free(p); + ctx->priv = NULL; +} + +/* + * @brief Renders a video frame. + * @param ctx The render_backend context. + * @param params The render parameters. + * @param frame The video frame to render. + * @return 0 on success, negative error code on failure. + */ +static int render(struct render_backend *ctx, mpv_render_param *params, + struct vo_frame *frame) +{ + struct priv *p = ctx->priv; + if (!p->video_engine) return MPV_ERROR_UNINITIALIZED; + + // Wrap the framebuffer object (FBO) for rendering. + pl_tex target_tex = NULL; + int err = p->context->fns->wrap_fbo(p->context, params, &target_tex); + if (err < 0) return err; + if (!target_tex) return MPV_ERROR_GENERIC; + + // Render the video frame. + pl_video_render(p->video_engine, frame, target_tex); + + // Destroy the temporary wrapper texture via the RA. + ra_next_tex_destroy(p->context->ra, &target_tex); + + if (p->context->fns->done_frame) + p->context->fns->done_frame(p->context); + + return 0; +} + +/* + * @brief Reconfigures the video engine with new image parameters. + * @param ctx The render_backend context. + * @param params The new image parameters. + */ +static void reconfig(struct render_backend *ctx, struct mp_image_params *params) +{ + struct priv *p = ctx->priv; + if (p->video_engine) + pl_video_reconfig(p->video_engine, params); +} + +/* + * @brief Resizes the video output. + * @param ctx The render_backend context. + * @param src The source rectangle. + * @param dst The destination rectangle. + * @param osd The OSD rectangle. + */ +static void resize(struct render_backend *ctx, struct mp_rect *src, + struct mp_rect *dst, struct mp_osd_res *osd) +{ + struct priv *p = ctx->priv; + if (p->video_engine) + pl_video_resize(p->video_engine, dst, osd); +} + +/* + * @brief Updates the external state of the render_backend. + * @param ctx The render_backend context. + * @param vo The video output context. + */ +static void update_external(struct render_backend *ctx, struct vo *vo) +{ + struct priv *p = ctx->priv; + if (p->video_engine) + pl_video_update_osd(p->video_engine, vo ? vo->osd : NULL); +} + +/* + * @brief Resets the video engine. + * @param ctx The render_backend context. + */ +static void reset(struct render_backend *ctx) +{ + struct priv *p = ctx->priv; + if (p->video_engine) + pl_video_reset(p->video_engine); +} + +/* + * @brief Checks if the given image format is supported. + * @param ctx The render_backend context. + * @param imgfmt The image format to check. + * @return True if the format is supported, false otherwise. + */ +static bool check_format(struct render_backend *ctx, int imgfmt) +{ + struct priv *p = ctx->priv; + return p->video_engine ? pl_video_check_format(p->video_engine, imgfmt) : false; +} + +/* + * @brief Gets the target size for rendering. + * @param ctx The render_backend context. + * @param params The render parameters. + * @param out_w Pointer to the output width. + * @param out_h Pointer to the output height. + * @return 0 on success, negative error code on failure. + */ +static int get_target_size(struct render_backend *ctx, mpv_render_param *params, int *out_w, int *out_h) +{ + struct priv *p = ctx->priv; + if (!p->context || !p->context->fns || !p->context->ra) return MPV_ERROR_UNINITIALIZED; + pl_tex tex = NULL; + int err = p->context->fns->wrap_fbo(p->context, params, &tex); + if (err < 0) return err; + if (!tex) return MPV_ERROR_GENERIC; + *out_w = tex->params.w; + *out_h = tex->params.h; + // Destroy the temporary wrapper texture via the RA. + ra_next_tex_destroy(p->context->ra, &tex); + return 0; +} + +/* + * @brief Takes a screenshot of the current video frame. + * @param ctx The render_backend context. + * @param frame The video frame to capture. + * @param args The screenshot arguments. + */ +static void screenshot(struct render_backend *ctx, struct vo_frame *frame, + struct voctrl_screenshot *args) +{ + struct priv *p = ctx->priv; + args->res = NULL; + if (!p || !p->video_engine) + return; + + /* Let the pl_video engine perform the screenshot (uploads, tone-mapping, + * render to an sRGB temporary, download). Returns an mp_image* or NULL. */ + struct mp_image *img = pl_video_screenshot(p->video_engine, frame); + if (img) + args->res = img; +} + +/* + * @brief Sets a render parameter. + * @param ctx The render_backend context. + * @param param The render parameter to set. + * @return 0 on success, negative error code on failure. + */ +static int set_parameter(struct render_backend *ctx, mpv_render_param param) +{ + return MPV_ERROR_NOT_IMPLEMENTED; +} + +/* + * @brief Gets an image for rendering. + * @param ctx The render_backend context. + * @param imgfmt The image format. + * @param w The width of the image. + * @param h The height of the image. + * @param stride_align The stride alignment. + * @param flags The image flags. + * @return A pointer to the image, or NULL on failure. + */ +static struct mp_image *get_image(struct render_backend *ctx, int imgfmt, + int w, int h, int stride_align, int flags) +{ + return NULL; +} + +/* + * @brief Collects performance data from the render_backend. + * @param ctx The render_backend context. + * @param out The output structure to fill with performance data. + */ +static void perfdata(struct render_backend *ctx, + struct voctrl_performance_data *out) +{ +} + +const struct render_backend_fns render_backend_gpu_next = { + .init = init, + .destroy = destroy, + .render = render, + .check_format = check_format, + .set_parameter = set_parameter, + .reconfig = reconfig, + .reset = reset, + .update_external = update_external, + .resize = resize, + .get_target_size = get_target_size, + .get_image = get_image, + .screenshot = screenshot, + .perfdata = perfdata, +}; diff --git a/video/out/gpu_next/libmpv_gpu_next.h b/video/out/gpu_next/libmpv_gpu_next.h new file mode 100644 index 0000000000000..d48c67ee433c0 --- /dev/null +++ b/video/out/gpu_next/libmpv_gpu_next.h @@ -0,0 +1,80 @@ +#pragma once + +#include // for pl_gpu, pl_tex +#include "mpv/render.h" // for mpv_render_param + +/** + * This struct represents an instance of a specific API context implementation. + * It's created by the Host (`libmpv_gpu_next.c`) and passed to the concrete + * implementation (e.g., `libmpv_gpu_next_gl.c`) so it can access global state + * and return its results. + */ +struct libmpv_gpu_next_context { + // Inputs from the Host + struct mpv_global *global; + struct mp_log *log; + void *priv; // Private state for the implementation's use + + const struct libmpv_gpu_next_context_fns *fns; + + // The abstract "toolbox" that the engine will use. + struct ra_next *ra; + // The underlying GPU object, needed by the Host for resource management. + pl_gpu gpu; +}; + +/** + * This struct defines the "vtable" or "interface" for a specific API context + * implementation. Our Host (`libmpv_gpu_next.c`) will find an implementation + * that matches the user's request and will then call these functions through + * this table. + */ +struct libmpv_gpu_next_context_fns { + // The name of the API, e.g., "opengl". This must match the string provided + // by the user in MPV_RENDER_PARAM_API_TYPE. + const char *api_name; + + /** + * Initializes the graphics context based on user parameters. + * On success, it must populate the `ra` and `gpu` fields of the + * `libmpv_gpu_next_context` struct. + * @param ctx The context instance to initialize. + * @param params The list of parameters from the user. + * @return 0 on success, or a negative mpv_error code. + */ + int (*init)(struct libmpv_gpu_next_context *ctx, mpv_render_param *params); + + /** + * Wraps the user's render target (provided in `params`) into a `pl_tex` + * that our engine can understand. + * @param ctx The context instance. + * @param params The list of parameters from the user, containing the target. + * @param out_tex On success, this will point to a newly created, temporary + * `pl_tex` that wraps the user's target. The caller is + * responsible for freeing this texture. + * @return 0 on success, or a negative mpv_error code. + */ + int (*wrap_fbo)(struct libmpv_gpu_next_context *ctx, + mpv_render_param *params, pl_tex *out_tex); + + /** + * Called by the Host after a frame has been rendered to the user's target. + * This can be used for presentation timing or swapchain management. + * This function can be NULL if no action is needed. + * @param ctx The context instance. + */ + void (*done_frame)(struct libmpv_gpu_next_context *ctx); + + /** + * Destroys the graphics context and all associated resources. + * @param ctx The context instance to destroy. + */ + void (*destroy)(struct libmpv_gpu_next_context *ctx); +}; + +/** + * A forward declaration for the concrete OpenGL API context implementation, + * which is defined in `libmpv_gpu_next_gl.c`. The Host needs this to add it + * to its list of available backends. + */ +extern const struct libmpv_gpu_next_context_fns libmpv_gpu_next_context_gl; diff --git a/video/out/gpu_next/ra.c b/video/out/gpu_next/ra.c new file mode 100644 index 0000000000000..c8411dc5f3615 --- /dev/null +++ b/video/out/gpu_next/ra.c @@ -0,0 +1,409 @@ +#include "video/out/gpu_next/ra.h" +#include // for pl_frame, pl_plane, pl_ren... +#include // for pl_plane_data, pl_upload_p... +#include // for uint64_t +#include // for NULL, abs +#include // for memset +#include "common/common.h" // for MPSWAP, MPMAX +#include "common/msg.h" // for mp_msg, MSGL_DEBUG, MSGL_ERR +#include "libplacebo/colorspace.h" // for pl_bit_encoding, pl_bit_en... +#include "libplacebo/gpu.h" // for pl_tex, pl_fmt_type, pl_fi... +#include "libplacebo/log.h" // for pl_log +#include "libplacebo/utils/frame_queue.h" // for pl_queue_create, pl_queue_... +#include "ta/ta_talloc.h" // for talloc_free, talloc_zero +#include "video/img_format.h" // for mp_imgfmt_comp_desc, mp_im... +#include "video/mp_image.h" // for mp_image, mp_image_params +#include "video/out/vo.h" // for vo + +/** + * @brief Private state for the libplacebo rendering abstraction. + * + * This struct contains all the internal state for the `ra_pl` implementation. + * The public `ra_next` struct must be the first member to allow for safe casting + * from a `ra_next*` to a `ra_priv*`. + */ +struct ra_priv { + struct ra_next pub; // Public interface, must be the first member. + pl_renderer renderer; // The core libplacebo renderer instance. + struct vo *vo; // Back-pointer to the video output instance for context. + + // OSD state + pl_tex *sub_tex; // Texture pool for subtitles and OSD bitmaps. + int num_sub_tex; // Current number of textures in the pool. + pl_tex overlay_tex; // A texture for overlays. + pl_log pl_log; // The libplacebo logging context. +}; + +/* --- New Abstraction Implementations --- */ + +ra_queue ra_next_queue_create(struct ra_next *ra) +{ + return pl_queue_create(ra->gpu); +} + +void ra_next_queue_destroy(ra_queue *queue) +{ + pl_queue_destroy(queue); +} + +void ra_next_queue_push(ra_queue queue, const struct pl_source_frame *frame) +{ + pl_queue_push(queue, frame); +} + +void ra_next_queue_update(ra_queue queue, struct pl_frame_mix *mix, const struct pl_queue_params *params) +{ + pl_queue_update(queue, mix, params); +} + +void ra_next_queue_reset(ra_queue queue) +{ + pl_queue_reset(queue); +} + +pl_tex ra_next_tex_create(struct ra_next *ra, const struct pl_tex_params *params) +{ + return pl_tex_create(ra->gpu, params); +} + +void ra_next_tex_destroy(struct ra_next *ra, pl_tex *tex) +{ + pl_tex_destroy(ra->gpu, tex); +} + +bool ra_next_tex_recreate(struct ra_next *ra, pl_tex *tex, const struct pl_tex_params *params) +{ + return pl_tex_recreate(ra->gpu, tex, params); +} + +bool ra_next_tex_upload(struct ra_next *ra, const struct pl_tex_transfer_params *params) +{ + return pl_tex_upload(ra->gpu, params); +} + +bool ra_next_tex_download(struct ra_next *ra, const struct pl_tex_transfer_params *params) +{ + return pl_tex_download(ra->gpu, params); +} + +bool ra_next_render_image_mix(struct ra_next *ra, const struct pl_frame_mix *mix, + struct pl_frame *target, const struct pl_render_params *params) +{ + struct ra_priv *p = (struct ra_priv *)ra; + if (!p->renderer) return false; + return pl_render_image_mix(p->renderer, mix, target, params); +} + +bool ra_next_render_image(struct ra_next *ra, const struct pl_frame *src, + struct pl_frame *target, const struct pl_render_params *params) +{ + struct ra_priv *p = (struct ra_priv *)ra; + if (!p->renderer) return false; + return pl_render_image(p->renderer, src, target, params); +} + +pl_fmt ra_next_find_fmt(struct ra_next *ra, enum pl_fmt_type type, int num_comps, + int comp_bits, int alpha_bits, unsigned caps) +{ + return pl_find_fmt(ra->gpu, type, num_comps, comp_bits, alpha_bits, caps); +} + + +/** + * @brief Public wrapper to upload an mpv image to a libplacebo frame. + * @param ra The rendering abstraction context. + * @param out_frame The destination pl_frame to be populated. + * @param img The source mp_image containing pixel data. + * @return True on success, false on failure. + */ +bool ra_upload_mp_image(struct ra_next *ra, struct pl_frame *out_frame, + const struct mp_image *img) +{ + if (!img || !out_frame) return false; + return upload_mp_image_to_pl_frame(ra, out_frame, img); // reuse existing impl +} + +/** + * @brief Public wrapper to clean up GPU resources associated with a pl_frame. + * @param ra The rendering abstraction context. + * @param frame The pl_frame whose textures should be destroyed. + */ +void ra_cleanup_pl_frame(struct ra_next *ra, struct pl_frame *frame) +{ + ra_pl_cleanup_frame(ra, frame); // reuse existing impl +} + +/** + * @brief Gets the underlying libplacebo renderer instance. + * @param ra The rendering abstraction context. + * @return A pointer to the pl_renderer, or NULL if not initialized. + */ +pl_renderer ra_get_renderer(struct ra_next *ra) +{ + struct ra_priv *p = (struct ra_priv *)ra; + return p ? p->renderer : NULL; +} + +/** + * @brief Gets the underlying libplacebo GPU handle. + * @param ra The rendering abstraction context. + * @return The pl_gpu handle. + */ +pl_gpu ra_get_gpu(struct ra_next *ra) +{ + return ra ? ra->gpu : NULL; +} + +/** + * @brief Gets the libplacebo logging context. + * @param ra The rendering abstraction context. + * @return The pl_log handle. + */ +pl_log ra_get_pl_log(struct ra_next *ra) +{ + if (!ra) + return NULL; + struct ra_priv *p = (struct ra_priv *)ra; + return p->pl_log; +} + +/** + * @brief Associates a video output (vo) context with the rendering abstraction. + * @param ra The rendering abstraction context. + * @param vo The video output context to associate. + */ +void ra_pl_set_vo(struct ra_next *ra, struct vo *vo) +{ + struct ra_priv *p = (struct ra_priv *)ra; + p->vo = vo; + mp_msg(ra->log, MSGL_DEBUG, "ra_pl_set_vo: vo=%p osd=%p\n", (void*)vo, vo ? (void*)vo->osd : NULL); +} + +/** + * @brief Frees all GPU textures associated with a given `pl_frame`. + * @param ra The rendering abstraction context. + * @param frame The frame to clean up. + */ +void ra_pl_cleanup_frame(struct ra_next *ra, struct pl_frame *frame) +{ + if (!frame) + return; + // Iterate over each plane and destroy its associated texture. + for (int i = 0; i < frame->num_planes; i++) + ra_next_tex_destroy(ra, &frame->planes[i].texture); +} + +/** + * @brief Translates an mpv image format enum into a libplacebo plane description. + * This is a key translation layer between mpv's and libplacebo's data structures. + * @param out_data The output array of `pl_plane_data` to be populated. + * @param out_bits The output bit encoding information. + * @param imgfmt The input mpv image format enum. + * @param use_uint Whether to use unsigned integer formats. + * @return The number of planes on success, 0 on failure. + */ +static int plane_data_from_imgfmt(struct pl_plane_data out_data[4], + struct pl_bit_encoding *out_bits, + enum mp_imgfmt imgfmt, bool use_uint) +{ + struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(imgfmt); + if (!desc.num_planes || !(desc.flags & MP_IMGFLAG_HAS_COMPS)) + return 0; + + // Filter out unsupported or non-CPU formats. + if (desc.flags & MP_IMGFLAG_HWACCEL || !(desc.flags & MP_IMGFLAG_NE) || + desc.flags & MP_IMGFLAG_PAL || + ((desc.flags & MP_IMGFLAG_TYPE_FLOAT) && (desc.flags & MP_IMGFLAG_YUV))) + return 0; + + bool has_bits = false; + bool any_padded = false; + + // Iterate through each plane of the mpv image format. + for (int p = 0; p < desc.num_planes; p++) { + struct pl_plane_data *data = &out_data[p]; + struct mp_imgfmt_comp_desc sorted[MP_NUM_COMPONENTS]; + int num_comps = 0; + if (desc.bpp[p] % 8) + return 0; // Require byte-aligned pixels. + + // Collect and sort all components belonging to the current plane. + for (int c = 0; c < mp_imgfmt_desc_get_num_comps(&desc); c++) { + if (desc.comps[c].plane != p) + continue; + + data->component_map[num_comps] = c; + sorted[num_comps] = desc.comps[c]; + num_comps++; + + // Simple insertion sort to order components by memory offset. + for (int i = num_comps - 1; i > 0; i--) { + if (sorted[i].offset >= sorted[i - 1].offset) + break; + MPSWAP(struct mp_imgfmt_comp_desc, sorted[i], sorted[i - 1]); + MPSWAP(int, data->component_map[i], data->component_map[i - 1]); + } + } + + // Calculate component sizes, padding, and bit encoding for libplacebo. + uint64_t total_bits = 0; + memset(data->component_size, 0, sizeof(data->component_size)); + for (int c = 0; c < num_comps; c++) { + data->component_size[c] = sorted[c].size; + data->component_pad[c] = sorted[c].offset - total_bits; + total_bits += data->component_pad[c] + data->component_size[c]; + any_padded |= sorted[c].pad; + + if (!out_bits || data->component_map[c] == 3) // PL_CHANNEL_A + continue; + + struct pl_bit_encoding bits = { + .sample_depth = data->component_size[c], + .color_depth = sorted[c].size - abs(sorted[c].pad), + .bit_shift = MPMAX(sorted[c].pad, 0), + }; + + // Ensure all components have the same bit encoding. + if (!has_bits) { + *out_bits = bits; + has_bits = true; + } else if (!pl_bit_encoding_equal(out_bits, &bits)) { + *out_bits = (struct pl_bit_encoding){0}; + out_bits = NULL; + } + } + + data->pixel_stride = desc.bpp[p] / 8; + data->type = (desc.flags & MP_IMGFLAG_TYPE_FLOAT) + ? PL_FMT_FLOAT + : (use_uint ? PL_FMT_UINT : PL_FMT_UNORM); + } + + if (any_padded && !out_bits) + return 0; + + return desc.num_planes; +} + +/** + * Uploads an `mp_image` to a `pl_frame`. + * @param ra The rendering abstraction context. + * @param out_frame The output `pl_frame`. + * @param img The input `mp_image`. + * @return True on success, false on failure. + */ +bool upload_mp_image_to_pl_frame(struct ra_next *ra, struct pl_frame *out_frame, + const struct mp_image *img) +{ + // Initialize the frame with color space and crop metadata. + *out_frame = (struct pl_frame){ + .color = img->params.color, + .repr = img->params.repr, + .crop = { + .x0 = 0, .y0 = 0, + .x1 = img->w, .y1 = img->h, + }, + }; + + // Convert the mpv format to libplacebo's plane data description. + struct pl_plane_data data[4] = {0}; + int planes = plane_data_from_imgfmt(data, &out_frame->repr.bits, img->imgfmt, false); + if (!planes) { + mp_msg(ra->log, MSGL_ERR, "Failed to describe image format '%s'\n", + mp_imgfmt_to_name(img->imgfmt)); + return false; + } + + out_frame->num_planes = planes; + + // Upload each plane's pixel data to a new GPU texture. + for (int n = 0; n < planes; n++) { + data[n].width = mp_image_plane_w((struct mp_image *)img, n); + data[n].height = mp_image_plane_h((struct mp_image *)img, n); + data[n].row_stride = img->stride[n]; + data[n].pixels = img->planes[n]; + + // Let libplacebo handle the texture creation and data upload. + if (!pl_upload_plane(ra->gpu, &out_frame->planes[n], + &out_frame->planes[n].texture, &data[n])) + { + mp_msg(ra->log, MSGL_ERR, "Failed to upload mp_image plane %d\n", n); + goto error; + } + } + + return true; + +error: + // Clean up any successfully created textures if one fails. + ra_pl_cleanup_frame(ra, out_frame); + return false; +} + +/** + * @brief Resets the renderer by flushing internal caches. + * @param ra The rendering abstraction context. + */ +void ra_pl_reset(struct ra_next *ra) +{ + struct ra_priv *p = (struct ra_priv *)ra; + if (p && p->renderer) + pl_renderer_flush_cache(p->renderer); +} + +/** + * @brief Destroys the rendering abstraction context and all associated resources. + * This is the corrected version that fixes the valgrind crash. + * @param rap A pointer to the rendering abstraction context pointer. The pointer + * will be set to NULL after destruction to prevent use-after-free. + */ +void ra_pl_destroy(struct ra_next **rap) +{ + if (!rap || !*rap) + return; + + // Correctly dereference the double pointer to get the struct pointer. + struct ra_priv *p = (struct ra_priv *)*rap; + + // Destroy the renderer (if any) + if (p->renderer) { + mp_msg(p->pub.log, MSGL_DEBUG, "ra_pl_destroy: destroying renderer %p\n", + (void*)p->renderer); + pl_renderer_destroy(&p->renderer); + } + + // Finally free the ra_priv block itself + mp_msg(p->pub.log, MSGL_DEBUG, "ra_pl_destroy: freeing ra_priv %p\n", (void*)p); + talloc_free(p); + + // Nullify the original pointer to prevent dangling pointers. + *rap = NULL; +} + +/** + * @brief Creates a new libplacebo-based rendering abstraction context. + * @param gpu The libplacebo GPU object to use. + * @param log The parent mpv logging context. + * @param log_pl The libplacebo logging context. + * @return A new ra_next context on success, or NULL on failure. + */ +struct ra_next *ra_pl_create(pl_gpu gpu, struct mp_log *log, pl_log log_pl) +{ + // Allocate the private implementation struct. + struct ra_priv *p = talloc_zero(NULL, struct ra_priv); + struct ra_next *ra = &p->pub; + + // Initialize public members. + ra->gpu = gpu; + ra->log = mp_log_new(p, log, "ra-pl"); // Create a sub-logger for this module. + p->pl_log = log_pl; + + // Create renderer (needed by the higher-level pl_render_image calls) + p->renderer = pl_renderer_create(log_pl, gpu); + if (!p->renderer) { + talloc_free(p); + return NULL; + } + + return ra; +} diff --git a/video/out/gpu_next/ra.h b/video/out/gpu_next/ra.h new file mode 100644 index 0000000000000..a6729b97a1fc6 --- /dev/null +++ b/video/out/gpu_next/ra.h @@ -0,0 +1,88 @@ +#pragma once + +#include // for pl_gpu, pl_tex, pl_fmt +#include // for pl_log +#include // for pl_renderer +#include // for pl_queue +#include "stdbool.h" // for bool + +/* Forward declarations from mpv */ +struct mp_image; +struct mp_log; +struct vo; + +/* Opaque handle for the frame queue, managed by the RA */ +typedef pl_queue ra_queue; + +/* Public RA handle exposed to higher layers (minimal surface). */ +struct ra_next { + pl_gpu gpu; + struct mpv_global *global; + struct mp_log *log; +}; + +/* Upload an mp_image into a pl_frame suitable for pl_render_image. + * Caller must call ra_cleanup_pl_frame() to free any textures created. + * Returns true on success, false on failure. */ +bool ra_upload_mp_image(struct ra_next *ra, struct pl_frame *out_frame, + const struct mp_image *img); + +/* Cleanup any textures/resources attached to a pl_frame created by upload. */ +void ra_cleanup_pl_frame(struct ra_next *ra, struct pl_frame *frame); + +/* --- New Texture Management Wrappers (prefixed to avoid conflicts) --- */ +pl_tex ra_next_tex_create(struct ra_next *ra, const struct pl_tex_params *params); +void ra_next_tex_destroy(struct ra_next *ra, pl_tex *tex); +bool ra_next_tex_recreate(struct ra_next *ra, pl_tex *tex, const struct pl_tex_params *params); +bool ra_next_tex_upload(struct ra_next *ra, const struct pl_tex_transfer_params *params); +bool ra_next_tex_download(struct ra_next *ra, const struct pl_tex_transfer_params *params); + +/* --- New Frame Queue Wrappers --- */ +ra_queue ra_next_queue_create(struct ra_next *ra); +void ra_next_queue_destroy(ra_queue *queue); +void ra_next_queue_push(ra_queue queue, const struct pl_source_frame *frame); +void ra_next_queue_update(ra_queue queue, struct pl_frame_mix *mix, const struct pl_queue_params *params); +void ra_next_queue_reset(ra_queue queue); + +/* --- New Rendering Wrappers --- */ +bool ra_next_render_image_mix(struct ra_next *ra, const struct pl_frame_mix *mix, + struct pl_frame *target, const struct pl_render_params *params); +bool ra_next_render_image(struct ra_next *ra, const struct pl_frame *src, + struct pl_frame *target, const struct pl_render_params *params); + +/* --- New Utility Wrappers --- */ +pl_fmt ra_next_find_fmt(struct ra_next *ra, enum pl_fmt_type type, int num_comps, + int comp_bits, int alpha_bits, unsigned caps); + + +/* Get the pl_renderer associated with this RA (may be NULL). */ +pl_renderer ra_get_renderer(struct ra_next *ra); + +/* Get the raw pl_gpu (for pl_tex_create / pl_tex_download etc). */ +pl_gpu ra_get_gpu(struct ra_next *ra); + +/* Flush libplacebo internal caches (wrapper for pl_renderer_flush_cache). */ +void ra_reset(struct ra_next *ra); + +/* Create the pl-specific RA implementation. */ +struct ra_next *ra_pl_create(pl_gpu gpu, struct mp_log *log, pl_log log_pl); + +/* Destroys the pl-specific RA implementation. */ +void ra_pl_destroy(struct ra_next **rap); + +/* Optional helper: let VO set a vo pointer on RA implementation. */ +void ra_pl_set_vo(struct ra_next *ra, struct vo *vo); + +/* Return the pl_log associated with the RA (or NULL). */ +pl_log ra_get_pl_log(struct ra_next *ra); + +/* Reset the RA (flush caches etc). */ +void ra_pl_reset(struct ra_next *ra); + +/* Internal helper: upload an mp_image to a pl_frame (used by ra_upload_mp_image). + * This is exposed for use in pl_video_screenshot. */ +bool upload_mp_image_to_pl_frame(struct ra_next *ra, struct pl_frame *out_frame, const struct mp_image *img); + +/* Internal helper: cleanup a pl_frame (used by ra_cleanup_pl_frame). + * This is exposed for use in pl_video_screenshot. */ +void ra_pl_cleanup_frame(struct ra_next *ra, struct pl_frame *frame); diff --git a/video/out/gpu_next/video.c b/video/out/gpu_next/video.c new file mode 100644 index 0000000000000..bb2e15eef39f6 --- /dev/null +++ b/video/out/gpu_next/video.c @@ -0,0 +1,582 @@ +#include "video.h" +#include // for pl_source_frame, pl_queue_... +#include // for NULL +#include // for uint64_t, uint32_t, uintptr_t +#include "assert.h" // for assert +#include "common/common.h" // for mp_rect, MPMAX, MP_ARRAY_SIZE +#include "common/msg.h" // for mp_msg, MSGL_ERR, MSGL_WARN +#include "libplacebo/colorspace.h" // for pl_color_adjustment, pl_co... +#include "libplacebo/filters.h" // for pl_filter_nearest +#include "libplacebo/gpu.h" // for pl_tex_params, pl_tex_t +#include "libplacebo/renderer.h" // for pl_frame_mix, pl_frame +#include "sub/draw_bmp.h" // for mp_draw_sub_formats +#include "sub/osd.h" // for sub_bitmap, sub_bitmaps +#include "ta/ta_talloc.h" // for talloc_free, talloc_zero +#include "video/csputils.h" // for mp_csp_params, mp_csp_equa... +#include "video/img_format.h" // for mp_imgfmt +#include "video/mp_image.h" // for mp_image, mp_image_params +#include "video/out/gpu_next/ra.h" // for ra_next_find_fmt, ra_next_... +#include "video/out/vo.h" // for vo_frame + +// Forward declarations +struct mp_log; +struct mpv_global; +struct osd_state; + +/** + * @brief Holds GPU resources for a single piece of the On-Screen Display (OSD). + * + * This struct contains a GPU texture (`pl_tex`) and an array of `pl_overlay_part` + * which define how different parts of the texture should be drawn on the screen. + * This is used for rendering subtitles and other OSD elements. + */ +struct pl_video_osd_entry { + pl_tex tex; // The GPU texture containing the bitmap for this OSD part. + struct pl_overlay_part *parts; // Array of parts describing how to render the texture. + int num_parts; // The number of parts in the array. +}; + +/** + * @brief Manages the state for all OSD elements. + * + * This struct holds arrays for all possible OSD parts and the corresponding + * libplacebo overlay structures that will be passed to the renderer. + */ +struct pl_video_osd_state { + struct pl_video_osd_entry entries[MAX_OSD_PARTS]; // Storage for individual OSD parts. + struct pl_overlay overlays[MAX_OSD_PARTS]; // The final overlays to be rendered. +}; + +/** + * @brief Main structure managing synchronous libplacebo video rendering. + * + * This struct encapsulates all state needed for rendering video frames + * and OSD elements using libplacebo. It includes the frame queue, current + * video parameters, OSD resources, and color adjustment state. + */ +struct pl_video { + struct mp_log *log; + struct ra_next *ra; // The libplacebo rendering abstraction + ra_queue queue; // The frame queue for handling video frames and interpolation. + uint64_t last_frame_id;// To avoid pushing duplicate frames into the queue. + double last_pts; // Last presentation timestamp we rendered at, for redraws. + + // Render State + struct mp_image_params current_params; // Current video parameters (resolution, colorspace, etc.). + struct mp_rect current_dst; // The current destination rectangle on the target surface. + struct osd_state *current_osd_state; // Pointer to the core's logical OSD state. + + // OSD rendering resources + struct mp_osd_res osd_res; // OSD resolution and aspect ratio information. + struct pl_video_osd_state osd_state_storage; // Storage for our GPU resources. + pl_fmt osd_fmt[SUBBITMAP_COUNT]; // Cached libplacebo formats for different OSD bitmap types. + pl_tex *sub_tex; // Texture pool for OSD textures. + int num_sub_tex; // The number of textures in the pool. + + // Color adjustment state + struct mp_csp_equalizer_state *video_eq; // Manages brightness, contrast, hue, etc. +}; + +/** + * @brief Private data attached to each mp_image pushed into the queue. + * This allows the static callback functions to access the main pl_video state. + */ +struct frame_priv { + struct pl_video *p; // A pointer back to the main pl_video engine struct. +}; + +/** + * @brief Callback to map an mp_image to a pl_frame for rendering. + * + * This function is called by the queue when it needs to prepare a frame for rendering. + * It handles uploading the image data from CPU memory to a GPU texture. + * @param gpu The libplacebo GPU handle. + * @param tex (unused) A pointer for an older API, not used here. + * @param src The source frame from the queue, containing the mp_image. + * @param frame The destination `pl_frame` to be populated with GPU texture info. + * @return True on success, false on failure. + */ +static bool map_frame(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src, + struct pl_frame *frame) +{ + struct mp_image *mpi = src->frame_data; + struct frame_priv *fp = mpi->priv; + struct pl_video *p = fp->p; + + // Use the RA helper to upload the mp_image data to a new set of textures + // and populate the pl_frame struct with the result. + if (!ra_upload_mp_image(p->ra, frame, mpi)) { + talloc_free(mpi); // Clean up the mp_image reference on failure + return false; + } + + // Store a pointer back to the original mp_image. This is used to get a unique + // signature for the frame and to access metadata (like colorspace) later. + frame->user_data = mpi; + return true; +} + +/** + * @brief Callback to unmap a pl_frame after it has been rendered. + * + * This function is called by the queue to free the GPU resources associated + * with a frame that is no longer needed for rendering. + * @param gpu The libplacebo GPU handle. + * @param frame The `pl_frame` containing the GPU textures to be destroyed. + * @param src The source frame from the queue. + */ +static void unmap_frame(pl_gpu gpu, struct pl_frame *frame, + const struct pl_source_frame *src) +{ + struct mp_image *mpi = src->frame_data; + struct frame_priv *fp = mpi->priv; + struct pl_video *p = fp->p; + + // Use the RA helper to destroy the GPU textures associated with the frame. + ra_cleanup_pl_frame(p->ra, frame); + // Free the mp_image reference itself. + talloc_free(mpi); +} + +/** + * @brief Callback to discard a frame that was pushed into the queue but never rendered. + * + * This is called for frames that are dropped (e.g., due to performance issues). + * It frees the CPU-side mp_image without touching the GPU. + * @param src The source frame from the queue to be discarded. + */ +static void discard_frame(const struct pl_source_frame *src) +{ + // We only need to free the mp_image reference, as no GPU resources were created. + struct mp_image *mpi = src->frame_data; + talloc_free(mpi); +} + +/** + * @brief Initializes the synchronous libplacebo rendering engine. + * @param global The mpv global context. + * @param log The logging context. + * @param ra The rendering abstraction context. + * @return A pointer to the newly created pl_video engine, or NULL on failure. + */ +struct pl_video *pl_video_init(struct mpv_global *global, struct mp_log *log, struct ra_next *ra) { + struct pl_video *p = talloc_zero(NULL, struct pl_video); + p->log = log; + p->ra = ra; + p->queue = ra_next_queue_create(ra); + + // Pre-find the texture formats we'll need for OSD bitmaps for efficiency. + p->osd_fmt[SUBBITMAP_LIBASS] = ra_next_find_fmt(p->ra, PL_FMT_UNORM, 1, 8, 8, 0); + p->osd_fmt[SUBBITMAP_BGRA] = ra_next_find_fmt(p->ra, PL_FMT_UNORM, 4, 8, 8, 0); + + // Create the state object that tracks brightness, contrast, etc. + p->video_eq = mp_csp_equalizer_create(p, global); + + return p; +} + +/** + * @brief Shuts down and destroys the rendering engine and its resources. + * @param p_ptr A pointer to the pl_video engine pointer to be freed. + */ +void pl_video_uninit(struct pl_video **p_ptr) { + struct pl_video *p = *p_ptr; + if (!p) return; + + ra_next_queue_destroy(&p->queue); + + // Clean up all allocated OSD GPU resources + for (int i = 0; i < MP_ARRAY_SIZE(p->osd_state_storage.entries); i++) { + struct pl_video_osd_entry *entry = &p->osd_state_storage.entries[i]; + ra_next_tex_destroy(p->ra, &entry->tex); + talloc_free(entry->parts); + } + for (int i = 0; i < p->num_sub_tex; i++) { + ra_next_tex_destroy(p->ra, &p->sub_tex[i]); + } + talloc_free(p->sub_tex); + + talloc_free(p); + *p_ptr = NULL; +} + +/** + * @brief Updates the OSD overlays for the current video frame. + * @param p The pl_video context. + * @param res The OSD resolution. + * @param flags The OSD update flags. + * @param coords The overlay coordinates. + * @param state The current OSD state. + * @param frame The video frame to update. + * @param src The source image for the frame. + */ +static void update_overlays(struct pl_video *p, struct mp_osd_res res, + int flags, enum pl_overlay_coords coords, + struct pl_video_osd_state *state, struct pl_frame *frame, + struct mp_image *src) +{ + frame->num_overlays = 0; + if (!p->current_osd_state) + return; + + // Render the logical OSD state into a list of bitmaps. + double pts = src ? src->pts : 0; + struct sub_bitmap_list *subs = osd_render(p->current_osd_state, res, pts, flags, mp_draw_sub_formats); + if (!subs) return; + + frame->overlays = state->overlays; + + // Iterate through each bitmap and convert it into a libplacebo overlay. + for (int n = 0; n < subs->num_items; n++) { + const struct sub_bitmaps *item = subs->items[n]; + if (!item->num_parts || !item->packed) + continue; + + struct pl_video_osd_entry *entry = &state->entries[item->render_index]; + pl_fmt tex_fmt = p->osd_fmt[item->format]; + + // Reuse a texture from the pool if available. + if (!entry->tex) + MP_TARRAY_POP(p->sub_tex, p->num_sub_tex, &entry->tex); + + // Recreate the texture if its size needs to change. + bool ok = ra_next_tex_recreate(p->ra, &entry->tex, &(struct pl_tex_params) { + .format = tex_fmt, + .w = MPMAX(item->packed_w, entry->tex ? entry->tex->params.w : 0), + .h = MPMAX(item->packed_h, entry->tex ? entry->tex->params.h : 0), + .host_writable = true, + .sampleable = true, + }); + if (!ok) { + mp_msg(p->log, MSGL_ERR, "Failed recreating OSD texture!\n"); + break; + } + + // Upload the new bitmap data to the GPU texture. + ok = ra_next_tex_upload(p->ra, &(struct pl_tex_transfer_params) { + .tex = entry->tex, + .rc = { .x1 = item->packed_w, .y1 = item->packed_h, }, + .row_pitch = item->packed->stride[0], + .ptr = item->packed->planes[0], + }); + if (!ok) { + mp_msg(p->log, MSGL_ERR, "Failed uploading OSD texture!\n"); + break; + } + + entry->num_parts = 0; + talloc_free(entry->parts); + entry->parts = talloc_array(p, struct pl_overlay_part, item->num_parts); + + // Convert each sub-bitmap part into a pl_overlay_part. + for (int i = 0; i < item->num_parts; i++) { + const struct sub_bitmap *b = &item->parts[i]; + if (b->dw == 0 || b->dh == 0) + continue; + uint32_t c = b->libass.color; + struct pl_overlay_part part = { + .src = { b->src_x, b->src_y, b->src_x + b->w, b->src_y + b->h }, + .dst = { b->x, b->y, b->x + b->dw, b->y + b->dh }, + .color = { + (c >> 24) / 255.0f, + ((c >> 16) & 0xFF) / 255.0f, + ((c >> 8) & 0xFF) / 255.0f, + (255 - (c & 0xFF)) / 255.0f, + } + }; + entry->parts[entry->num_parts++] = part; + } + + // Create the final pl_overlay structure for rendering. + struct pl_overlay *ol = &state->overlays[frame->num_overlays++]; + *ol = (struct pl_overlay) { + .tex = entry->tex, + .parts = entry->parts, + .num_parts = entry->num_parts, + .color = { .primaries = PL_COLOR_PRIM_BT_709, .transfer = PL_COLOR_TRC_SRGB }, + .coords = coords, + }; + + // Set blending modes based on the OSD bitmap format. + if (item->format == SUBBITMAP_BGRA) { + ol->mode = PL_OVERLAY_NORMAL; + ol->repr.alpha = PL_ALPHA_PREMULTIPLIED; + if (src) ol->color = src->params.color; + } else if (item->format == SUBBITMAP_LIBASS) { + ol->mode = PL_OVERLAY_MONOCHROME; + ol->repr.alpha = PL_ALPHA_INDEPENDENT; + if (src && item->video_color_space) ol->color = src->params.color; + } + } + + talloc_free(subs); +} + +/** + * @brief Main rendering function for a single video frame. + * @param p The pl_video engine context. + * @param frame The mpv frame to render, containing the current image. + * @param target_tex The destination GPU texture to render to. + */ +void pl_video_render(struct pl_video *p, struct vo_frame *frame, pl_tex target_tex) +{ + // Describe the target surface for libplacebo. + struct pl_frame target_frame = { + .num_planes = 1, + .planes[0] = { .texture = target_tex, .components = 4, .component_mapping = {0,1,2,3} }, + .crop = { .x0 = p->current_dst.x0, .y0 = p->current_dst.y0, .x1 = p->current_dst.x1, .y1 = p->current_dst.y1 }, + .color = pl_color_space_srgb, + .repr = pl_color_repr_rgb, + }; + + // The libmpv VO provides one new frame at a time in frame->current. + // We check the frame_id to avoid pushing duplicates. + if (frame && frame->current && frame->frame_id > p->last_frame_id) { + struct mp_image *mpi = mp_image_new_ref(frame->current); + // Attach our private data to the image for the callbacks. + struct frame_priv *fp = talloc_zero(mpi, struct frame_priv); + fp->p = p; + mpi->priv = fp; + + // Push the frame into the queue with its callbacks. + ra_next_queue_push(p->queue, &(struct pl_source_frame) { + .pts = mpi->pts, + .frame_data = mpi, + .map = map_frame, + .unmap = unmap_frame, + .discard = discard_frame, + }); + + p->last_frame_id = frame->frame_id; + } + + // If this is a redraw request, frame->current will be NULL. In that case, + // we reuse the last known PTS to query the queue for the correct frame. + double target_pts = (frame && frame->current) ? frame->current->pts : p->last_pts; + p->last_pts = target_pts; + + struct pl_frame_mix queue_mix = {0}; + struct pl_queue_params qparams = *pl_queue_params(.pts = target_pts); + + ra_next_queue_update(p->queue, &queue_mix, &qparams); + + // To render OSD, we need a representative source frame to get color space info. + // The first frame in the mix is a perfect candidate. + struct mp_image *representative_img = NULL; + if (queue_mix.num_frames > 0 && queue_mix.frames) { + representative_img = queue_mix.frames[0]->user_data; + } + + // Manually build the final mix for the renderer, including the signatures. + // We need a local array to hold the signature data. 32 is a safe upper bound. + uint64_t signatures[32]; + assert(queue_mix.num_frames < MP_ARRAY_SIZE(signatures)); + for (int i = 0; i < queue_mix.num_frames; i++) { + // Use the mp_image pointer as a unique signature for caching. + signatures[i] = (uintptr_t)queue_mix.frames[i]->user_data; + } + struct pl_frame_mix mix = queue_mix; + mix.signatures = signatures; + + // Generate and attach OSD overlays to the target frame. If mix.num_frames is 0, + // representative_img will be NULL, and update_overlays will correctly render + // OSD against a black background. + update_overlays(p, p->osd_res, 0, PL_OVERLAY_COORDS_DST_FRAME, + &p->osd_state_storage, &target_frame, representative_img); + + // For a simple, non-interpolating backend, we can assume the display + // frame's duration is equivalent to one source frame (1.0 in normalized time). + mix.vsync_duration = 1.0f; + + // Prepare the rendering parameters for libplacebo + struct pl_render_params params = { + .upscaler = &pl_filter_nearest, + .downscaler = &pl_filter_nearest, + }; + + // Declare a local struct to hold the color adjustment values. + struct pl_color_adjustment color_adj; + + // Query the current brightness/contrast/etc values from the equalizer + struct mp_csp_params cparams = MP_CSP_PARAMS_DEFAULTS; + mp_csp_equalizer_state_get(p->video_eq, &cparams); + + // Fill our local struct with the values. + color_adj.brightness = cparams.brightness; + color_adj.contrast = cparams.contrast; + color_adj.hue = cparams.hue; + color_adj.saturation = cparams.saturation; + color_adj.gamma = cparams.gamma; + + // Point the render params' pointer to our local struct. + params.color_adjustment = &color_adj; + + // Render the mix. libplacebo handles the empty mix case (no video) correctly. + if (!ra_next_render_image_mix(p->ra, &mix, &target_frame, ¶ms)) { + mp_msg(p->log, MSGL_ERR, "Rendering failed.\n"); + } +} + + /** + * @brief Takes a screenshot of the current video frame. + * @param ctx The render_backend context. + * @param frame The video frame to capture. + * @param args The screenshot arguments. + */ +struct mp_image *pl_video_screenshot(struct pl_video *p, struct vo_frame *frame) +{ + if (!p || !p->ra || !frame || !frame->current) { + mp_msg(p->log, MSGL_WARN, "pl_video_screenshot: invalid arguments\n"); + return NULL; + } + + struct mp_image *res = NULL; + struct pl_frame source_frame = {0}; + pl_tex fbo = NULL; + + // Upload the mp_image to a pl_frame via the RA helper. + if (!ra_upload_mp_image(p->ra, &source_frame, frame->current)) { + mp_msg(p->log, MSGL_ERR, "pl_video_screenshot: failed to upload source image\n"); + return NULL; + } + + // Find an 8-bit RGBA renderable + host-readable format. + pl_fmt fbo_fmt = ra_next_find_fmt(p->ra, PL_FMT_UNORM, 4, 8, 8, + PL_FMT_CAP_RENDERABLE | PL_FMT_CAP_HOST_READABLE); + if (!fbo_fmt) { + mp_msg(p->log, MSGL_ERR, "pl_video_screenshot: failed to find screenshot format\n"); + goto done; + } + + // Create a temporary renderable, host-readable texture sized to the source. + int w = frame->current->w; + int h = frame->current->h; + fbo = ra_next_tex_create(p->ra, pl_tex_params( + .w = w, + .h = h, + .format = fbo_fmt, + .renderable = true, + .host_readable = true + )); + if (!fbo) { + mp_msg(p->log, MSGL_ERR, "pl_video_screenshot: failed to create temporary texture\n"); + goto done; + } + + // Describe the target as an sRGB SDR frame (so libplacebo performs tone-mapping). + struct pl_frame target_frame = { + .num_planes = 1, + .planes[0] = { + .texture = fbo, + .components = 4, + .component_mapping = {0, 1, 2, 3}, + }, + .color = pl_color_space_srgb, + .repr = pl_color_repr_rgb, + }; + + // Define the OSD canvas size to match the video frame for the screenshot. + struct mp_osd_res osd_res = { + .w = frame->current->w, + .h = frame->current->h, + .display_par = 1.0, // Screenshots have square pixels + }; + + // Generate and attach OSD/subtitle overlays to the screenshot's target frame. + update_overlays(p, osd_res, 0, PL_OVERLAY_COORDS_DST_FRAME, + &p->osd_state_storage, &target_frame, frame->current); + + const struct pl_render_params params = { + .upscaler = &pl_filter_nearest, + .downscaler = &pl_filter_nearest, + }; + + if (!ra_next_render_image(p->ra, &source_frame, &target_frame, ¶ms)) { + mp_msg(p->log, MSGL_ERR, "pl_video_screenshot: rendering failed\n"); + goto done; + } + + // Allocate an mp_image for RGBA result and download the texture into it. + res = mp_image_alloc(IMGFMT_RGBA, w, h); + if (!res) { + mp_msg(p->log, MSGL_ERR, "pl_video_screenshot: failed to allocate mp_image\n"); + goto done; + } + + bool ok = ra_next_tex_download(p->ra, &(struct pl_tex_transfer_params){ + .tex = fbo, + .ptr = res->planes[0], + .row_pitch = res->stride[0], + }); + + if (!ok) { + mp_msg(p->log, MSGL_ERR, "pl_video_screenshot: texture download failed\n"); + talloc_free(res); + res = NULL; + goto done; + } + +done: + if (fbo) + ra_next_tex_destroy(p->ra, &fbo); + + ra_cleanup_pl_frame(p->ra, &source_frame); + + return res; +} + +/** + * @brief Informs the engine that the video parameters have changed. + * @param p The pl_video engine context. + * @param params The new image parameters. + */ +void pl_video_reconfig(struct pl_video *p, const struct mp_image_params *params) { + if (params) + p->current_params = *params; +} + +/** + * @brief Informs the engine that the output viewport has been resized. + * @param p The pl_video engine context. + * @param dst The new destination rectangle. + * @param osd The new OSD resolution information. + */ +void pl_video_resize(struct pl_video *p, const struct mp_rect *dst, const struct mp_osd_res *osd) { + if (dst) + p->current_dst = *dst; + if (osd) + p->osd_res = *osd; +} + +/** + * @brief Provides the engine with the current On-Screen Display state. + * @param p The pl_video engine context. + * @param osd A pointer to the current OSD state. + */ +void pl_video_update_osd(struct pl_video *p, struct osd_state *osd) { + p->current_osd_state = osd; +} + +/** + * @brief Informs the engine that it should flush internal caches. + * @param p The pl_video engine context. + */ +void pl_video_reset(struct pl_video *p) { + if (!p || !p->ra) return; + ra_pl_reset(p->ra); + ra_next_queue_reset(p->queue); // Also reset the frame queue. + p->last_frame_id = 0; + p->last_pts = 0; +} + +/** + * @brief Asks the engine if a specific image format is supported. + * @param p The pl_video engine context. + * @param imgfmt The image format to check. + * @return True if the format is likely supported, false otherwise. + */ +bool pl_video_check_format(struct pl_video *p, int imgfmt) { + // For simplicity, we assume libplacebo can handle it. + // A more robust implementation might query libplacebo's capabilities. + return true; +} diff --git a/video/out/gpu_next/video.h b/video/out/gpu_next/video.h new file mode 100644 index 0000000000000..0f62ee2e94a2a --- /dev/null +++ b/video/out/gpu_next/video.h @@ -0,0 +1,63 @@ +#pragma once + +#include "stdbool.h" // for bool +#include // for pl_gpu, pl_tex + +// Forward declarations +struct mp_image_params; +struct mp_log; +struct mp_osd_res; +struct mp_rect; +struct mpv_global; +struct osd_state; +struct ra_next; +struct vo_frame; + +/** + * Initializes the rendering engine. + */ +struct pl_video *pl_video_init(struct mpv_global *global, struct mp_log *log, struct ra_next *ra); + +/** + * Shuts down and destroys the rendering engine. + */ +void pl_video_uninit(struct pl_video **p_ptr); + +/** + * Synchronously renders a video frame to a display target using libplacebo. + */ +void pl_video_render(struct pl_video *p, struct vo_frame *frame, pl_tex target_tex); + +/** + * Synchronously renders `frame` into an sRGB temporary and returns a newly + * allocated mp_image (RGBA) with the result. Caller must free the returned + * mp_image with talloc_free() or mp_image_free if available. + * + * Returns NULL on failure. + */ +struct mp_image *pl_video_screenshot(struct pl_video *p, struct vo_frame *frame); + +/** + * Informs the engine that the video parameters have changed. + */ +void pl_video_reconfig(struct pl_video *p, const struct mp_image_params *params); + +/** + * Informs the engine that the output viewport has been resized. + */ +// void pl_video_resize(struct pl_video *p, const struct mp_rect *dst); +void pl_video_resize(struct pl_video *p, const struct mp_rect *dst, const struct mp_osd_res *osd); +/** + * Provides the engine with the current On-Screen Display state. + */ +void pl_video_update_osd(struct pl_video *p, struct osd_state *osd); + +/** + * Informs the engine that it should flush libplacebo's internal caches. + */ +void pl_video_reset(struct pl_video *p); + +/** + * Asks the engine if a specific image format is supported. + */ +bool pl_video_check_format(struct pl_video *p, int imgfmt); diff --git a/video/out/vo_libmpv.c b/video/out/vo_libmpv.c index a91016df4f4d6..5f19eb33110dd 100644 --- a/video/out/vo_libmpv.c +++ b/video/out/vo_libmpv.c @@ -1,31 +1,30 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mpv_talloc.h" -#include "common/common.h" -#include "misc/bstr.h" -#include "misc/dispatch.h" -#include "common/msg.h" -#include "options/m_config.h" -#include "options/options.h" -#include "aspect.h" -#include "dr_helper.h" -#include "vo.h" -#include "video/mp_image.h" -#include "sub/osd.h" -#include "osdep/threads.h" -#include "osdep/timer.h" - -#include "common/global.h" -#include "player/client.h" - -#include "libmpv.h" +#include // for INFINITY +#include // for atomic_load, atomic_bool, atomic_... +#include // for true, bool, false +#include // for int64_t, intptr_t, uint64_t, uint... +#include // for NULL, abs +#include // for strcmp +#include "aspect.h" // for mp_get_src_dst_rects +#include "common/common.h" // for mp_rect +#include "common/global.h" // for mpv_global +#include "common/msg.h" // for MP_VERBOSE, MP_STATS, mp_log_new +#include "config.h" // for HAVE_MACOS_COCOA_CB +#include "dr_helper.h" // for dr_helper_acquire_thread, dr_help... +#include "libmpv.h" // for render_backend, render_backend_fns +#include "misc/dispatch.h" // for mp_dispatch_queue_process, mp_dis... +#include "misc/mp_assert.h" // for mp_assert +#include "mpv/client.h" // for mpv_error, mpv_handle +#include "mpv/render.h" // for mpv_render_context, mpv_render_pa... +#include "options/m_config_core.h" // for m_config_cache_alloc, m_config_ca... +#include "options/options.h" // for vo_sub_opts +#include "osdep/threads.h" // for mp_mutex_unlock, mp_mutex_lock +#include "osdep/timer.h" // for mp_time_ns, MP_TIME_MS_TO_NS +#include "player/client.h" // for mp_set_main_render_context, kill_... +#include "sub/osd.h" // for mp_osd_res +#include "ta/ta_talloc.h" // for talloc_free, talloc_zero, TA_FREEP +#include "video/img_format.h" // for mp_imgfmt +#include "video/mp_image.h" // for mp_image_params +#include "vo.h" // for vo, vo_frame, mp_voctrl, vo_frame... #if HAVE_MACOS_COCOA_CB #include "osdep/mac/app_bridge.h" @@ -111,11 +110,10 @@ struct mpv_render_context { struct mp_vo_opts *vo_opts; }; -const struct render_backend_fns *render_backends[] = { - &render_backend_gpu, - &render_backend_sw, - NULL -}; +// Forward declare the available render backends +extern const struct render_backend_fns render_backend_gpu; +extern const struct render_backend_fns render_backend_gpu_next; +extern const struct render_backend_fns render_backend_sw; static void update(struct mpv_render_context *ctx) { @@ -184,6 +182,27 @@ int mpv_render_context_create(mpv_render_context **res, mpv_handle *mpv, if (GET_MPV_RENDER_PARAM(params, MPV_RENDER_PARAM_ADVANCED_CONTROL, int, 0)) ctx->advanced_control = true; + char *backend_name = get_mpv_render_param(params, MPV_RENDER_PARAM_BACKEND, ""); + const struct render_backend_fns **render_backends; + + if (backend_name && strcmp(backend_name, "gpu-next") == 0) { + MP_VERBOSE(ctx, "Using gpu-next backend.\n"); + static const struct render_backend_fns* backends[] = { + &render_backend_gpu_next, + &render_backend_sw, + NULL + }; + render_backends = backends; + } else { + MP_VERBOSE(ctx, "Using default gpu backend.\n"); + static const struct render_backend_fns* backends[] = { + &render_backend_gpu, + &render_backend_sw, + NULL + }; + render_backends = backends; + } + int err = MPV_ERROR_NOT_IMPLEMENTED; for (int n = 0; render_backends[n]; n++) { ctx->renderer = talloc_zero(NULL, struct render_backend);