mirror of
https://github.com/mpv-player/mpv
synced 2024-11-14 22:48:35 +01:00
ff9f2c4b6e
Use the libavutil vdpau frame allocation code instead of our own "old" code. This also uses its code for copying a video surface to normal memory (used by vdpau-copy). Since vdpau doesn't really have an internal pixel format, 4:2:0 can be accessed as both nv12 and yuv420p - and libavutil prefers to report yuv420p. The OpenGL interop has to be adjusted accordingly. Preemption is a potential problem, but it doesn't break it more than it already is. This requires a bug fix to FFmpeg's vdpau code, or vdpau-copy (as well as taking screenshots) will fail. Libav has fixed this bug ages ago.
284 lines
8.4 KiB
C
284 lines
8.4 KiB
C
/*
|
|
* This file is part of mpv.
|
|
*
|
|
* mpv 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.
|
|
*
|
|
* mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stddef.h>
|
|
#include <assert.h>
|
|
|
|
#include <GL/glx.h>
|
|
|
|
#include "hwdec.h"
|
|
#include "utils.h"
|
|
#include "video/vdpau.h"
|
|
#include "video/vdpau_mixer.h"
|
|
|
|
// This is a GL_NV_vdpau_interop specification bug, and headers (unfortunately)
|
|
// follow it. I'm not sure about the original nvidia headers.
|
|
#define BRAINDEATH(x) ((void *)(uintptr_t)(x))
|
|
|
|
static int reinit(struct gl_hwdec *hw, struct mp_image_params *params);
|
|
|
|
struct priv {
|
|
struct mp_log *log;
|
|
struct mp_vdpau_ctx *ctx;
|
|
uint64_t preemption_counter;
|
|
struct mp_image_params image_params;
|
|
GLuint gl_textures[4];
|
|
bool vdpgl_initialized;
|
|
GLvdpauSurfaceNV vdpgl_surface;
|
|
VdpOutputSurface vdp_surface;
|
|
struct mp_vdpau_mixer *mixer;
|
|
bool direct_mode;
|
|
bool mapped;
|
|
};
|
|
|
|
static void unmap(struct gl_hwdec *hw)
|
|
{
|
|
struct priv *p = hw->priv;
|
|
GL *gl = hw->gl;
|
|
|
|
if (p->mapped) {
|
|
gl->VDPAUUnmapSurfacesNV(1, &p->vdpgl_surface);
|
|
if (p->direct_mode) {
|
|
gl->VDPAUUnregisterSurfaceNV(p->vdpgl_surface);
|
|
p->vdpgl_surface = 0;
|
|
}
|
|
}
|
|
p->mapped = false;
|
|
}
|
|
|
|
static void mark_vdpau_objects_uninitialized(struct gl_hwdec *hw)
|
|
{
|
|
struct priv *p = hw->priv;
|
|
|
|
p->vdp_surface = VDP_INVALID_HANDLE;
|
|
p->mapped = false;
|
|
}
|
|
|
|
static void destroy_objects(struct gl_hwdec *hw)
|
|
{
|
|
struct priv *p = hw->priv;
|
|
GL *gl = hw->gl;
|
|
struct vdp_functions *vdp = &p->ctx->vdp;
|
|
VdpStatus vdp_st;
|
|
|
|
unmap(hw);
|
|
|
|
if (p->vdpgl_surface)
|
|
gl->VDPAUUnregisterSurfaceNV(p->vdpgl_surface);
|
|
p->vdpgl_surface = 0;
|
|
|
|
glDeleteTextures(4, p->gl_textures);
|
|
for (int n = 0; n < 4; n++)
|
|
p->gl_textures[n] = 0;
|
|
|
|
if (p->vdp_surface != VDP_INVALID_HANDLE) {
|
|
vdp_st = vdp->output_surface_destroy(p->vdp_surface);
|
|
CHECK_VDP_WARNING(p, "Error when calling vdp_output_surface_destroy");
|
|
}
|
|
p->vdp_surface = VDP_INVALID_HANDLE;
|
|
|
|
gl_check_error(gl, hw->log, "Before uninitializing OpenGL interop");
|
|
|
|
if (p->vdpgl_initialized)
|
|
gl->VDPAUFiniNV();
|
|
|
|
p->vdpgl_initialized = false;
|
|
|
|
gl_check_error(gl, hw->log, "After uninitializing OpenGL interop");
|
|
}
|
|
|
|
static void destroy(struct gl_hwdec *hw)
|
|
{
|
|
struct priv *p = hw->priv;
|
|
|
|
destroy_objects(hw);
|
|
mp_vdpau_mixer_destroy(p->mixer);
|
|
if (p->ctx)
|
|
hwdec_devices_remove(hw->devs, &p->ctx->hwctx);
|
|
mp_vdpau_destroy(p->ctx);
|
|
}
|
|
|
|
static int create(struct gl_hwdec *hw)
|
|
{
|
|
GL *gl = hw->gl;
|
|
Display *x11disp = glXGetCurrentDisplay();
|
|
if (!x11disp)
|
|
return -1;
|
|
if (!(gl->mpgl_caps & MPGL_CAP_VDPAU))
|
|
return -1;
|
|
struct priv *p = talloc_zero(hw, struct priv);
|
|
hw->priv = p;
|
|
p->log = hw->log;
|
|
p->ctx = mp_vdpau_create_device_x11(hw->log, x11disp, true);
|
|
if (!p->ctx)
|
|
return -1;
|
|
if (mp_vdpau_handle_preemption(p->ctx, &p->preemption_counter) < 1)
|
|
return -1;
|
|
p->vdp_surface = VDP_INVALID_HANDLE;
|
|
p->mixer = mp_vdpau_mixer_create(p->ctx, hw->log);
|
|
if (hw->probing && mp_vdpau_guess_if_emulated(p->ctx)) {
|
|
destroy(hw);
|
|
return -1;
|
|
}
|
|
p->ctx->hwctx.driver_name = hw->driver->name;
|
|
hwdec_devices_add(hw->devs, &p->ctx->hwctx);
|
|
return 0;
|
|
}
|
|
|
|
static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
|
|
{
|
|
struct priv *p = hw->priv;
|
|
GL *gl = hw->gl;
|
|
struct vdp_functions *vdp = &p->ctx->vdp;
|
|
VdpStatus vdp_st;
|
|
|
|
destroy_objects(hw);
|
|
|
|
assert(params->imgfmt == hw->driver->imgfmt);
|
|
p->image_params = *params;
|
|
|
|
if (mp_vdpau_handle_preemption(p->ctx, &p->preemption_counter) < 0)
|
|
return -1;
|
|
|
|
gl->VDPAUInitNV(BRAINDEATH(p->ctx->vdp_device), p->ctx->get_proc_address);
|
|
|
|
p->vdpgl_initialized = true;
|
|
|
|
p->direct_mode = params->hw_subfmt == IMGFMT_NV12 ||
|
|
params->hw_subfmt == IMGFMT_420P;
|
|
|
|
gl->GenTextures(4, p->gl_textures);
|
|
for (int n = 0; n < 4; n++) {
|
|
gl->BindTexture(GL_TEXTURE_2D, p->gl_textures[n]);
|
|
GLenum filter = p->direct_mode ? GL_NEAREST : GL_LINEAR;
|
|
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
|
|
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
|
|
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
}
|
|
gl->BindTexture(GL_TEXTURE_2D, 0);
|
|
|
|
if (p->direct_mode) {
|
|
params->imgfmt = IMGFMT_NV12;
|
|
params->hw_subfmt = 0;
|
|
} else {
|
|
vdp_st = vdp->output_surface_create(p->ctx->vdp_device,
|
|
VDP_RGBA_FORMAT_B8G8R8A8,
|
|
params->w, params->h, &p->vdp_surface);
|
|
CHECK_VDP_ERROR(p, "Error when calling vdp_output_surface_create");
|
|
|
|
p->vdpgl_surface = gl->VDPAURegisterOutputSurfaceNV(BRAINDEATH(p->vdp_surface),
|
|
GL_TEXTURE_2D,
|
|
1, p->gl_textures);
|
|
if (!p->vdpgl_surface)
|
|
return -1;
|
|
|
|
gl->VDPAUSurfaceAccessNV(p->vdpgl_surface, GL_READ_ONLY);
|
|
|
|
params->imgfmt = IMGFMT_RGB0;
|
|
params->hw_subfmt = 0;
|
|
}
|
|
|
|
gl_check_error(gl, hw->log, "After initializing vdpau OpenGL interop");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int map_frame(struct gl_hwdec *hw, struct mp_image *hw_image,
|
|
struct gl_hwdec_frame *out_frame)
|
|
{
|
|
struct priv *p = hw->priv;
|
|
GL *gl = hw->gl;
|
|
struct vdp_functions *vdp = &p->ctx->vdp;
|
|
VdpStatus vdp_st;
|
|
|
|
int pe = mp_vdpau_handle_preemption(p->ctx, &p->preemption_counter);
|
|
if (pe < 1) {
|
|
mark_vdpau_objects_uninitialized(hw);
|
|
if (pe < 0)
|
|
return -1;
|
|
struct mp_image_params params = p->image_params;
|
|
if (reinit(hw, ¶ms) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (p->direct_mode) {
|
|
VdpVideoSurface surface = (intptr_t)hw_image->planes[3];
|
|
|
|
// We need the uncropped size.
|
|
VdpChromaType s_chroma_type;
|
|
uint32_t s_w, s_h;
|
|
vdp_st = vdp->video_surface_get_parameters(surface, &s_chroma_type, &s_w, &s_h);
|
|
CHECK_VDP_ERROR(hw, "Error when calling vdp_video_surface_get_parameters");
|
|
|
|
p->vdpgl_surface = gl->VDPAURegisterVideoSurfaceNV(BRAINDEATH(surface),
|
|
GL_TEXTURE_2D,
|
|
4, p->gl_textures);
|
|
if (!p->vdpgl_surface)
|
|
return -1;
|
|
|
|
gl->VDPAUSurfaceAccessNV(p->vdpgl_surface, GL_READ_ONLY);
|
|
gl->VDPAUMapSurfacesNV(1, &p->vdpgl_surface);
|
|
|
|
p->mapped = true;
|
|
*out_frame = (struct gl_hwdec_frame){
|
|
.vdpau_fields = true,
|
|
};
|
|
for (int n = 0; n < 4; n++) {
|
|
bool chroma = n >= 2;
|
|
out_frame->planes[n] = (struct gl_hwdec_plane){
|
|
.gl_texture = p->gl_textures[n],
|
|
.gl_target = GL_TEXTURE_2D,
|
|
.tex_w = s_w / (chroma ? 2 : 1),
|
|
.tex_h = s_h / (chroma ? 4 : 2),
|
|
};
|
|
};
|
|
} else {
|
|
if (!p->vdpgl_surface)
|
|
return -1;
|
|
|
|
mp_vdpau_mixer_render(p->mixer, NULL, p->vdp_surface, NULL, hw_image, NULL);
|
|
|
|
gl->VDPAUMapSurfacesNV(1, &p->vdpgl_surface);
|
|
|
|
p->mapped = true;
|
|
*out_frame = (struct gl_hwdec_frame){
|
|
.planes = {
|
|
{
|
|
.gl_texture = p->gl_textures[0],
|
|
.gl_target = GL_TEXTURE_2D,
|
|
.tex_w = p->image_params.w,
|
|
.tex_h = p->image_params.h,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct gl_hwdec_driver gl_hwdec_vdpau = {
|
|
.name = "vdpau-glx",
|
|
.api = HWDEC_VDPAU,
|
|
.imgfmt = IMGFMT_VDPAU,
|
|
.create = create,
|
|
.reinit = reinit,
|
|
.map_frame = map_frame,
|
|
.unmap = unmap,
|
|
.destroy = destroy,
|
|
};
|