1 /*
2  * This file is part of mpv.
3  *
4  * mpv is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * mpv is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <assert.h>
19 #include <errno.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <math.h>
24 #include <stdbool.h>
25 
26 #include <libavutil/hwcontext.h>
27 #include <libavutil/hwcontext_drm.h>
28 
29 #include "common.h"
30 #include "video/hwdec.h"
31 #include "common/msg.h"
32 #include "options/m_config.h"
33 #include "libmpv/render_gl.h"
34 #include "video/out/drm_common.h"
35 #include "video/out/drm_prime.h"
36 #include "video/out/gpu/hwdec.h"
37 #include "video/mp_image.h"
38 
39 extern const struct m_sub_options drm_conf;
40 
41 struct drm_frame {
42     struct drm_prime_framebuffer fb;
43     struct mp_image *image; // associated mpv image
44 };
45 
46 struct priv {
47     struct mp_log *log;
48     struct mp_hwdec_ctx hwctx;
49 
50     struct mp_image_params params;
51 
52     struct drm_atomic_context *ctx;
53     struct drm_frame current_frame, last_frame, old_frame;
54 
55     struct mp_rect src, dst;
56 
57     int display_w, display_h;
58 
59     struct drm_prime_handle_refs handle_refs;
60 };
61 
set_current_frame(struct ra_hwdec * hw,struct drm_frame * frame)62 static void set_current_frame(struct ra_hwdec *hw, struct drm_frame *frame)
63 {
64     struct priv *p = hw->priv;
65 
66     // frame will be on screen after next vsync
67     // current_frame is currently the displayed frame and will be replaced
68     // by frame after next vsync.
69     // We used old frame as triple buffering to make sure that the drm framebuffer
70     // is not being displayed when we release it.
71 
72     if (p->ctx) {
73         drm_prime_destroy_framebuffer(p->log, p->ctx->fd, &p->old_frame.fb, &p->handle_refs);
74     }
75 
76     mp_image_setrefp(&p->old_frame.image, p->last_frame.image);
77     p->old_frame.fb = p->last_frame.fb;
78 
79     mp_image_setrefp(&p->last_frame.image, p->current_frame.image);
80     p->last_frame.fb = p->current_frame.fb;
81 
82     if (frame) {
83         p->current_frame.fb = frame->fb;
84         mp_image_setrefp(&p->current_frame.image, frame->image);
85     } else {
86         memset(&p->current_frame.fb, 0, sizeof(p->current_frame.fb));
87         mp_image_setrefp(&p->current_frame.image, NULL);
88     }
89 }
90 
scale_dst_rect(struct ra_hwdec * hw,int source_w,int source_h,struct mp_rect * src,struct mp_rect * dst)91 static void scale_dst_rect(struct ra_hwdec *hw, int source_w, int source_h ,struct mp_rect *src, struct mp_rect *dst)
92 {
93     struct priv *p = hw->priv;
94 
95     // drm can allow to have a layer that has a different size from framebuffer
96     // we scale here the destination size to video mode
97     double hratio = p->display_w / (double)source_w;
98     double vratio = p->display_h / (double)source_h;
99     double ratio = hratio <= vratio ? hratio : vratio;
100 
101     dst->x0 = src->x0 * ratio;
102     dst->x1 = src->x1 * ratio;
103     dst->y0 = src->y0 * ratio;
104     dst->y1 = src->y1 * ratio;
105 
106     int offset_x = (p->display_w - ratio * source_w) / 2;
107     int offset_y = (p->display_h - ratio * source_h) / 2;
108 
109     dst->x0 += offset_x;
110     dst->x1 += offset_x;
111     dst->y0 += offset_y;
112     dst->y1 += offset_y;
113 }
114 
disable_video_plane(struct ra_hwdec * hw)115 static void disable_video_plane(struct ra_hwdec *hw)
116 {
117     struct priv *p = hw->priv;
118     if (!p->ctx)
119         return;
120 
121     if (!p->ctx->drmprime_video_plane)
122         return;
123 
124     // Disabling the drmprime video plane is needed on some devices when using
125     // the primary plane for video. Primary buffer can't be active with no
126     // framebuffer associated. So we need this function to commit it right away
127     // as mpv will free all framebuffers on playback end.
128     drmModeAtomicReqPtr request = drmModeAtomicAlloc();
129     if (request) {
130         drm_object_set_property(request, p->ctx->drmprime_video_plane, "FB_ID", 0);
131         drm_object_set_property(request, p->ctx->drmprime_video_plane, "CRTC_ID", 0);
132 
133         int ret = drmModeAtomicCommit(p->ctx->fd, request,
134                                   DRM_MODE_ATOMIC_NONBLOCK, NULL);
135 
136         if (ret)
137             MP_ERR(hw, "Failed to commit disable plane request (code %d)", ret);
138         drmModeAtomicFree(request);
139     }
140 }
141 
overlay_frame(struct ra_hwdec * hw,struct mp_image * hw_image,struct mp_rect * src,struct mp_rect * dst,bool newframe)142 static int overlay_frame(struct ra_hwdec *hw, struct mp_image *hw_image,
143                          struct mp_rect *src, struct mp_rect *dst, bool newframe)
144 {
145     struct priv *p = hw->priv;
146     AVDRMFrameDescriptor *desc = NULL;
147     drmModeAtomicReq *request = NULL;
148     struct drm_frame next_frame = {0};
149     int ret;
150 
151     // grab atomic request from native resources
152     if (p->ctx) {
153         struct mpv_opengl_drm_params_v2 *drm_params;
154         drm_params = (mpv_opengl_drm_params_v2 *)ra_get_native_resource(hw->ra, "drm_params_v2");
155         if (!drm_params) {
156             MP_ERR(hw, "Failed to retrieve drm params from native resources\n");
157             return -1;
158         }
159         if (drm_params->atomic_request_ptr) {
160             request = *drm_params->atomic_request_ptr;
161         } else {
162             MP_ERR(hw, "drm params pointer to atomic request is invalid");
163             return -1;
164         }
165     }
166 
167     if (hw_image) {
168 
169         // grab draw plane windowing info to eventually upscale the overlay
170         // as egl windows could be upscaled to draw plane.
171         struct mpv_opengl_drm_draw_surface_size *draw_surface_size = ra_get_native_resource(hw->ra, "drm_draw_surface_size");
172         if (draw_surface_size) {
173             scale_dst_rect(hw, draw_surface_size->width, draw_surface_size->height, dst, &p->dst);
174         } else {
175             p->dst = *dst;
176         }
177         p->src = *src;
178 
179         next_frame.image = hw_image;
180         desc = (AVDRMFrameDescriptor *)hw_image->planes[0];
181 
182         if (desc) {
183             int srcw = p->src.x1 - p->src.x0;
184             int srch = p->src.y1 - p->src.y0;
185             int dstw = MP_ALIGN_UP(p->dst.x1 - p->dst.x0, 2);
186             int dsth = MP_ALIGN_UP(p->dst.y1 - p->dst.y0, 2);
187 
188             if (drm_prime_create_framebuffer(p->log, p->ctx->fd, desc, srcw, srch, &next_frame.fb, &p->handle_refs)) {
189                 ret = -1;
190                 goto fail;
191             }
192 
193             if (request) {
194                 drm_object_set_property(request, p->ctx->drmprime_video_plane, "FB_ID", next_frame.fb.fb_id);
195                 drm_object_set_property(request, p->ctx->drmprime_video_plane, "CRTC_ID", p->ctx->crtc->id);
196                 drm_object_set_property(request, p->ctx->drmprime_video_plane, "SRC_X",   p->src.x0 << 16);
197                 drm_object_set_property(request, p->ctx->drmprime_video_plane, "SRC_Y",   p->src.y0 << 16);
198                 drm_object_set_property(request, p->ctx->drmprime_video_plane, "SRC_W",   srcw << 16);
199                 drm_object_set_property(request, p->ctx->drmprime_video_plane, "SRC_H",   srch << 16);
200                 drm_object_set_property(request, p->ctx->drmprime_video_plane, "CRTC_X",  MP_ALIGN_DOWN(p->dst.x0, 2));
201                 drm_object_set_property(request, p->ctx->drmprime_video_plane, "CRTC_Y",  MP_ALIGN_DOWN(p->dst.y0, 2));
202                 drm_object_set_property(request, p->ctx->drmprime_video_plane, "CRTC_W",  dstw);
203                 drm_object_set_property(request, p->ctx->drmprime_video_plane, "CRTC_H",  dsth);
204                 drm_object_set_property(request, p->ctx->drmprime_video_plane, "ZPOS",    0);
205             } else {
206                 ret = drmModeSetPlane(p->ctx->fd, p->ctx->drmprime_video_plane->id, p->ctx->crtc->id, next_frame.fb.fb_id, 0,
207                                       MP_ALIGN_DOWN(p->dst.x0, 2), MP_ALIGN_DOWN(p->dst.y0, 2), dstw, dsth,
208                                       p->src.x0 << 16, p->src.y0 << 16 , srcw << 16, srch << 16);
209                 if (ret < 0) {
210                     MP_ERR(hw, "Failed to set the drmprime video plane %d (buffer %d).\n",
211                            p->ctx->drmprime_video_plane->id, next_frame.fb.fb_id);
212                     goto fail;
213                 }
214             }
215         }
216     } else {
217         disable_video_plane(hw);
218 
219         while (p->old_frame.fb.fb_id)
220           set_current_frame(hw, NULL);
221     }
222 
223     set_current_frame(hw, &next_frame);
224     return 0;
225 
226  fail:
227     drm_prime_destroy_framebuffer(p->log, p->ctx->fd, &next_frame.fb, &p->handle_refs);
228     return ret;
229 }
230 
uninit(struct ra_hwdec * hw)231 static void uninit(struct ra_hwdec *hw)
232 {
233     struct priv *p = hw->priv;
234 
235     disable_video_plane(hw);
236     set_current_frame(hw, NULL);
237 
238     hwdec_devices_remove(hw->devs, &p->hwctx);
239     av_buffer_unref(&p->hwctx.av_device_ref);
240 
241     if (p->ctx) {
242         drm_atomic_destroy_context(p->ctx);
243         p->ctx = NULL;
244     }
245 }
246 
init(struct ra_hwdec * hw)247 static int init(struct ra_hwdec *hw)
248 {
249     struct priv *p = hw->priv;
250     int draw_plane, drmprime_video_plane;
251 
252     p->log = hw->log;
253 
254     void *tmp = talloc_new(NULL);
255     struct drm_opts *opts = mp_get_config_group(tmp, hw->global, &drm_conf);
256     draw_plane = opts->drm_draw_plane;
257     drmprime_video_plane = opts->drm_drmprime_video_plane;
258     talloc_free(tmp);
259 
260     struct mpv_opengl_drm_params_v2 *drm_params;
261 
262     drm_params = ra_get_native_resource(hw->ra, "drm_params_v2");
263     if (drm_params) {
264         p->ctx = drm_atomic_create_context(p->log, drm_params->fd, drm_params->crtc_id,
265                                            drm_params->connector_id, draw_plane, drmprime_video_plane);
266         if (!p->ctx) {
267             mp_err(p->log, "Failed to retrieve DRM atomic context.\n");
268             goto err;
269         }
270         if (!p->ctx->drmprime_video_plane) {
271             mp_warn(p->log, "No drmprime video plane. You might need to specify it manually using --drm-drmprime-video-plane\n");
272             goto err;
273         }
274     } else {
275         mp_verbose(p->log, "Failed to retrieve DRM fd from native display.\n");
276         goto err;
277     }
278 
279     drmModeCrtcPtr crtc;
280     crtc = drmModeGetCrtc(p->ctx->fd, p->ctx->crtc->id);
281     if (crtc) {
282         p->display_w = crtc->mode.hdisplay;
283         p->display_h = crtc->mode.vdisplay;
284         drmModeFreeCrtc(crtc);
285     }
286 
287     uint64_t has_prime;
288     if (drmGetCap(p->ctx->fd, DRM_CAP_PRIME, &has_prime) < 0) {
289         MP_ERR(hw, "Card does not support prime handles.\n");
290         goto err;
291     }
292 
293     if (has_prime) {
294         drm_prime_init_handle_ref_count(p, &p->handle_refs);
295     }
296 
297     disable_video_plane(hw);
298 
299     p->hwctx = (struct mp_hwdec_ctx) {
300         .driver_name = hw->driver->name,
301     };
302     if (!av_hwdevice_ctx_create(&p->hwctx.av_device_ref, AV_HWDEVICE_TYPE_DRM,
303                                 drmGetDeviceNameFromFd2(p->ctx->fd), NULL, 0)) {
304         hwdec_devices_add(hw->devs, &p->hwctx);
305     }
306 
307     return 0;
308 
309 err:
310     uninit(hw);
311     return -1;
312 }
313 
314 const struct ra_hwdec_driver ra_hwdec_drmprime_drm = {
315     .name = "drmprime-drm",
316     .priv_size = sizeof(struct priv),
317     .imgfmts = {IMGFMT_DRMPRIME, 0},
318     .init = init,
319     .overlay_frame = overlay_frame,
320     .uninit = uninit,
321 };
322