summaryrefslogtreecommitdiffstats
path: root/video/out
diff options
context:
space:
mode:
Diffstat (limited to 'video/out')
-rw-r--r--video/out/aspect.c148
-rw-r--r--video/out/aspect.h37
-rw-r--r--video/out/bitmap_packer.c227
-rw-r--r--video/out/bitmap_packer.h68
-rw-r--r--video/out/cocoa_common.h55
-rw-r--r--video/out/cocoa_common.m865
-rw-r--r--video/out/d3d_shader_yuv.h142
-rw-r--r--video/out/d3d_shader_yuv.hlsl44
-rw-r--r--video/out/d3d_shader_yuv_2ch.h170
-rw-r--r--video/out/filter_kernels.c279
-rw-r--r--video/out/filter_kernels.h45
-rw-r--r--video/out/geometry.c112
-rw-r--r--video/out/geometry.h29
-rw-r--r--video/out/gl_common.c2654
-rw-r--r--video/out/gl_common.h396
-rw-r--r--video/out/gl_header_fixes.h257
-rw-r--r--video/out/gl_osd.c324
-rw-r--r--video/out/gl_osd.h43
-rw-r--r--video/out/osx_common.c145
-rw-r--r--video/out/osx_common.h27
-rw-r--r--video/out/pnm_loader.c97
-rw-r--r--video/out/pnm_loader.h52
-rw-r--r--video/out/vo.c530
-rw-r--r--video/out/vo.h352
-rw-r--r--video/out/vo_caca.c395
-rw-r--r--video/out/vo_corevideo.h28
-rw-r--r--video/out/vo_corevideo.m457
-rw-r--r--video/out/vo_direct3d.c2101
-rw-r--r--video/out/vo_image.c198
-rw-r--r--video/out/vo_lavc.c553
-rw-r--r--video/out/vo_null.c103
-rw-r--r--video/out/vo_opengl.c2419
-rw-r--r--video/out/vo_opengl_old.c1166
-rw-r--r--video/out/vo_opengl_shaders.glsl355
-rw-r--r--video/out/vo_vdpau.c1718
-rw-r--r--video/out/vo_x11.c620
-rw-r--r--video/out/vo_xv.c716
-rw-r--r--video/out/w32_common.c757
-rw-r--r--video/out/w32_common.h66
-rw-r--r--video/out/x11_common.c2404
-rw-r--r--video/out/x11_common.h184
41 files changed, 21338 insertions, 0 deletions
diff --git a/video/out/aspect.c b/video/out/aspect.c
new file mode 100644
index 0000000000..f3cd00a5e5
--- /dev/null
+++ b/video/out/aspect.c
@@ -0,0 +1,148 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* Stuff for correct aspect scaling. */
+#include "aspect.h"
+#include "geometry.h"
+#include "video_out.h"
+#include "mp_msg.h"
+#include "options.h"
+
+#include "video_out.h"
+
+void aspect_save_videores(struct vo *vo, int w, int h, int d_w, int d_h)
+{
+ vo->aspdat.orgw = w;
+ vo->aspdat.orgh = h;
+ vo->aspdat.prew = d_w;
+ vo->aspdat.preh = d_h;
+ vo->aspdat.par = (double)d_w / d_h * h / w;
+}
+
+void aspect_save_screenres(struct vo *vo, int scrw, int scrh)
+{
+ mp_msg(MSGT_VO, MSGL_DBG2, "aspect_save_screenres %dx%d\n", scrw, scrh);
+ struct MPOpts *opts = vo->opts;
+ if (scrw <= 0 && scrh <= 0)
+ scrw = 1024;
+ if (scrh <= 0)
+ scrh = (scrw * 3 + 3) / 4;
+ if (scrw <= 0)
+ scrw = (scrh * 4 + 2) / 3;
+ vo->aspdat.scrw = scrw;
+ vo->aspdat.scrh = scrh;
+ if (opts->force_monitor_aspect)
+ vo->monitor_par = opts->force_monitor_aspect * scrh / scrw;
+ else
+ vo->monitor_par = 1.0 / opts->monitor_pixel_aspect;
+}
+
+/* aspect is called with the source resolution and the
+ * resolution, that the scaled image should fit into
+ */
+
+void aspect_fit(struct vo *vo, int *srcw, int *srch, int fitw, int fith)
+{
+ struct aspect_data *aspdat = &vo->aspdat;
+ float pixelaspect = vo->monitor_par;
+
+ mp_msg(MSGT_VO, MSGL_DBG2, "aspect(0) fitin: %dx%d monitor_par: %.2f\n",
+ fitw, fith, vo->monitor_par);
+ *srcw = fitw;
+ *srch = (float)fitw / aspdat->prew * aspdat->preh / pixelaspect;
+ *srch += *srch % 2; // round
+ mp_msg(MSGT_VO, MSGL_DBG2, "aspect(1) wh: %dx%d (org: %dx%d)\n",
+ *srcw, *srch, aspdat->prew, aspdat->preh);
+ if (*srch > fith || *srch < aspdat->orgh) {
+ int tmpw = (float)fith / aspdat->preh * aspdat->prew * pixelaspect;
+ tmpw += tmpw % 2; // round
+ if (tmpw <= fitw) {
+ *srch = fith;
+ *srcw = tmpw;
+ } else if (*srch > fith) {
+ mp_tmsg(MSGT_VO, MSGL_WARN,
+ "[ASPECT] Warning: No suitable new res found!\n");
+ }
+ }
+ aspdat->asp = *srcw / (float)*srch;
+ mp_msg(MSGT_VO, MSGL_DBG2, "aspect(2) wh: %dx%d (org: %dx%d)\n",
+ *srcw, *srch, aspdat->prew, aspdat->preh);
+}
+
+static void get_max_dims(struct vo *vo, int *w, int *h, int zoom)
+{
+ struct aspect_data *aspdat = &vo->aspdat;
+ *w = zoom ? aspdat->scrw : aspdat->prew;
+ *h = zoom ? aspdat->scrh : aspdat->preh;
+ if (zoom && WinID >= 0)
+ zoom = A_WINZOOM;
+ if (zoom == A_WINZOOM) {
+ *w = vo->dwidth;
+ *h = vo->dheight;
+ }
+}
+
+void aspect(struct vo *vo, int *srcw, int *srch, int zoom)
+{
+ int fitw;
+ int fith;
+ get_max_dims(vo, &fitw, &fith, zoom);
+ if (!zoom && geometry_wh_changed) {
+ mp_msg(MSGT_VO, MSGL_DBG2, "aspect(0) no aspect forced!\n");
+ return; // the user doesn't want to fix aspect
+ }
+ aspect_fit(vo, srcw, srch, fitw, fith);
+}
+
+void panscan_init(struct vo *vo)
+{
+ vo->panscan_x = 0;
+ vo->panscan_y = 0;
+ vo->panscan_amount = 0.0f;
+}
+
+static void panscan_calc_internal(struct vo *vo, int zoom)
+{
+ int fwidth, fheight;
+ int vo_panscan_area;
+ int max_w, max_h;
+ get_max_dims(vo, &max_w, &max_h, zoom);
+ struct MPOpts *opts = vo->opts;
+
+ if (opts->vo_panscanrange > 0) {
+ aspect(vo, &fwidth, &fheight, zoom);
+ vo_panscan_area = max_h - fheight;
+ if (!vo_panscan_area)
+ vo_panscan_area = max_w - fwidth;
+ vo_panscan_area *= opts->vo_panscanrange;
+ } else
+ vo_panscan_area = -opts->vo_panscanrange * max_h;
+
+ vo->panscan_amount = vo_fs || zoom == A_WINZOOM ? vo_panscan : 0;
+ vo->panscan_x = vo_panscan_area * vo->panscan_amount * vo->aspdat.asp;
+ vo->panscan_y = vo_panscan_area * vo->panscan_amount;
+}
+
+/**
+ * vos that set vo_dwidth and v_dheight correctly should call this to update
+ * vo_panscan_x and vo_panscan_y
+ */
+void panscan_calc_windowed(struct vo *vo)
+{
+ panscan_calc_internal(vo, A_WINZOOM);
+}
diff --git a/video/out/aspect.h b/video/out/aspect.h
new file mode 100644
index 0000000000..c5247421d2
--- /dev/null
+++ b/video/out/aspect.h
@@ -0,0 +1,37 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_ASPECT_H
+#define MPLAYER_ASPECT_H
+/* Stuff for correct aspect scaling. */
+
+struct vo;
+void panscan_init(struct vo *vo);
+void panscan_calc_windowed(struct vo *vo);
+
+void aspect_save_videores(struct vo *vo, int w, int h, int d_w, int d_h);
+void aspect_save_screenres(struct vo *vo, int scrw, int scrh);
+
+#define A_WINZOOM 2 ///< zoom to fill window size
+#define A_ZOOM 1
+#define A_NOZOOM 0
+
+void aspect(struct vo *vo, int *srcw, int *srch, int zoom);
+void aspect_fit(struct vo *vo, int *srcw, int *srch, int fitw, int fith);
+
+#endif /* MPLAYER_ASPECT_H */
diff --git a/video/out/bitmap_packer.c b/video/out/bitmap_packer.c
new file mode 100644
index 0000000000..603a6ce410
--- /dev/null
+++ b/video/out/bitmap_packer.c
@@ -0,0 +1,227 @@
+/*
+ * Calculate how to pack bitmap rectangles into a larger surface
+ *
+ * Copyright 2009, 2012 Uoti Urpala
+ *
+ * This file is part of mplayer2.
+ *
+ * mplayer2 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mplayer2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mplayer2. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+
+#include <libavutil/common.h>
+
+#include "talloc.h"
+#include "bitmap_packer.h"
+#include "mp_msg.h"
+#include "mpcommon.h"
+#include "sub/dec_sub.h"
+#include "fastmemcpy.h"
+
+#define IS_POWER_OF_2(x) (((x) > 0) && !(((x) - 1) & (x)))
+
+void packer_reset(struct bitmap_packer *packer)
+{
+ struct bitmap_packer old = *packer;
+ *packer = (struct bitmap_packer) {
+ .w_max = old.w_max,
+ .h_max = old.h_max,
+ };
+ talloc_free_children(packer);
+}
+
+void packer_get_bb(struct bitmap_packer *packer, struct pos out_bb[2])
+{
+ out_bb[0] = (struct pos) {0};
+ out_bb[1] = (struct pos) {
+ FFMIN(packer->used_width + packer->padding, packer->w),
+ FFMIN(packer->used_height + packer->padding, packer->h),
+ };
+}
+
+#define HEIGHT_SORT_BITS 4
+static int size_index(int s)
+{
+ int n = av_log2_16bit(s);
+ return (n << HEIGHT_SORT_BITS)
+ + (- 1 - (s << HEIGHT_SORT_BITS >> n) & (1 << HEIGHT_SORT_BITS) - 1);
+}
+
+/* Pack the given rectangles into an area of size w * h.
+ * The size of each rectangle is read from in[i].x / in[i].y.
+ * The height of each rectangle must be less than 65536.
+ * 'scratch' must point to work memory for num_rects+16 ints.
+ * The packed position for rectangle number i is set in out[i].
+ * Return 0 on success, -1 if the rectangles did not fit in w*h.
+ *
+ * The rectangles are placed in rows in order approximately sorted by
+ * height (the approximate sorting is simpler than a full one would be,
+ * and allows the algorithm to work in linear time). Additionally, to
+ * reduce wasted space when there are a few tall rectangles, empty
+ * lower-right parts of rows are filled recursively when the size of
+ * rectangles in the row drops past a power-of-two threshold. So if a
+ * row starts with rectangles of size 3x50, 10x40 and 5x20 then the
+ * free rectangle with corners (13, 20)-(w, 50) is filled recursively.
+ */
+static int pack_rectangles(struct pos *in, struct pos *out, int num_rects,
+ int w, int h, int *scratch, int *used_width)
+{
+ int bins[16 << HEIGHT_SORT_BITS];
+ int sizes[16 << HEIGHT_SORT_BITS] = { 0 };
+ for (int i = 0; i < num_rects; i++)
+ sizes[size_index(in[i].y)]++;
+ int idx = 0;
+ for (int i = 0; i < 16 << HEIGHT_SORT_BITS; i += 1 << HEIGHT_SORT_BITS) {
+ for (int j = 0; j < 1 << HEIGHT_SORT_BITS; j++) {
+ bins[i + j] = idx;
+ idx += sizes[i + j];
+ }
+ scratch[idx++] = -1;
+ }
+ for (int i = 0; i < num_rects; i++)
+ scratch[bins[size_index(in[i].y)]++] = i;
+ for (int i = 0; i < 16; i++)
+ bins[i] = bins[i << HEIGHT_SORT_BITS] - sizes[i << HEIGHT_SORT_BITS];
+ struct {
+ int size, x, bottom;
+ } stack[16] = {{15, 0, h}}, s = {};
+ int stackpos = 1;
+ int y;
+ while (stackpos) {
+ y = s.bottom;
+ s = stack[--stackpos];
+ s.size++;
+ while (s.size--) {
+ int maxy = -1;
+ int obj;
+ while ((obj = scratch[bins[s.size]]) >= 0) {
+ int bottom = y + in[obj].y;
+ if (bottom > s.bottom)
+ break;
+ int right = s.x + in[obj].x;
+ if (right > w)
+ break;
+ bins[s.size]++;
+ out[obj] = (struct pos){s.x, y};
+ num_rects--;
+ if (maxy < 0)
+ stack[stackpos++] = s;
+ s.x = right;
+ maxy = FFMAX(maxy, bottom);
+ }
+ *used_width = FFMAX(*used_width, s.x);
+ if (maxy > 0)
+ s.bottom = maxy;
+ }
+ }
+ return num_rects ? -1 : y;
+}
+
+int packer_pack(struct bitmap_packer *packer)
+{
+ if (packer->count == 0)
+ return 0;
+ int w_orig = packer->w, h_orig = packer->h;
+ struct pos *in = packer->in;
+ int xmax = 0, ymax = 0;
+ for (int i = 0; i < packer->count; i++) {
+ if (in[i].x <= packer->padding || in[i].y <= packer->padding)
+ in[i] = (struct pos){0, 0};
+ if (in[i].x < 0 || in [i].x > 65535 || in[i].y < 0 || in[i].y > 65535) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "Invalid OSD / subtitle bitmap size\n");
+ abort();
+ }
+ xmax = FFMAX(xmax, in[i].x);
+ ymax = FFMAX(ymax, in[i].y);
+ }
+ xmax = FFMAX(0, xmax - packer->padding);
+ ymax = FFMAX(0, ymax - packer->padding);
+ if (xmax > packer->w)
+ packer->w = 1 << av_log2(xmax - 1) + 1;
+ if (ymax > packer->h)
+ packer->h = 1 << av_log2(ymax - 1) + 1;
+ while (1) {
+ int used_width = 0;
+ int y = pack_rectangles(in, packer->result, packer->count,
+ packer->w + packer->padding,
+ packer->h + packer->padding,
+ packer->scratch, &used_width);
+ if (y >= 0) {
+ // No padding at edges
+ packer->used_width = FFMIN(used_width, packer->w);
+ packer->used_height = FFMIN(y, packer->h);
+ assert(packer->w == 0 || IS_POWER_OF_2(packer->w));
+ assert(packer->h == 0 || IS_POWER_OF_2(packer->h));
+ return packer->w != w_orig || packer->h != h_orig;
+ }
+ if (packer->w <= packer->h && packer->w != packer->w_max)
+ packer->w = FFMIN(packer->w * 2, packer->w_max);
+ else if (packer->h != packer->h_max)
+ packer->h = FFMIN(packer->h * 2, packer->h_max);
+ else {
+ packer->w = w_orig;
+ packer->h = h_orig;
+ return -1;
+ }
+ }
+}
+
+void packer_set_size(struct bitmap_packer *packer, int size)
+{
+ packer->count = size;
+ if (size <= packer->asize)
+ return;
+ packer->asize = FFMAX(packer->asize * 2, size);
+ talloc_free(packer->result);
+ talloc_free(packer->scratch);
+ packer->in = talloc_realloc(packer, packer->in, struct pos, packer->asize);
+ packer->result = talloc_array_ptrtype(packer, packer->result,
+ packer->asize);
+ packer->scratch = talloc_array_ptrtype(packer, packer->scratch,
+ packer->asize + 16);
+}
+
+int packer_pack_from_subbitmaps(struct bitmap_packer *packer,
+ struct sub_bitmaps *b)
+{
+ packer->count = 0;
+ if (b->format == SUBBITMAP_EMPTY)
+ return 0;
+ packer_set_size(packer, b->num_parts);
+ int a = packer->padding;
+ for (int i = 0; i < b->num_parts; i++)
+ packer->in[i] = (struct pos){b->parts[i].w + a, b->parts[i].h + a};
+ return packer_pack(packer);
+}
+
+void packer_copy_subbitmaps(struct bitmap_packer *packer, struct sub_bitmaps *b,
+ void *data, int pixel_stride, int stride)
+{
+ assert(packer->count == b->num_parts);
+ if (packer->padding) {
+ struct pos bb[2];
+ packer_get_bb(packer, bb);
+ memset_pic(data, 0, bb[1].x * pixel_stride, bb[1].y, stride);
+ }
+ for (int n = 0; n < packer->count; n++) {
+ struct sub_bitmap *s = &b->parts[n];
+ struct pos p = packer->result[n];
+
+ void *pdata = (uint8_t *)data + p.y * stride + p.x * pixel_stride;
+ memcpy_pic(pdata, s->bitmap, s->w * pixel_stride, s->h,
+ stride, s->stride);
+ }
+}
diff --git a/video/out/bitmap_packer.h b/video/out/bitmap_packer.h
new file mode 100644
index 0000000000..b86c3ec4f9
--- /dev/null
+++ b/video/out/bitmap_packer.h
@@ -0,0 +1,68 @@
+#ifndef MPLAYER_PACK_RECTANGLES_H
+#define MPLAYER_PACK_RECTANGLES_H
+
+struct pos {
+ int x;
+ int y;
+};
+
+struct bitmap_packer {
+ int w;
+ int h;
+ int w_max;
+ int h_max;
+ int padding;
+ int count;
+ struct pos *in;
+ struct pos *result;
+ int used_width;
+ int used_height;
+
+ // internal
+ int *scratch;
+ int asize;
+};
+
+struct ass_image;
+struct sub_bitmaps;
+
+// Clear all internal state. Leave the following fields: w_max, h_max
+void packer_reset(struct bitmap_packer *packer);
+
+// Get the bounding box used for bitmap data (including padding).
+// The bounding box doesn't exceed (0,0)-(packer->w,packer->h).
+void packer_get_bb(struct bitmap_packer *packer, struct pos out_bb[2]);
+
+/* Reallocate packer->in for at least to desired number of items.
+ * Also sets packer->count to the same value.
+ */
+void packer_set_size(struct bitmap_packer *packer, int size);
+
+/* To use this, set packer->count to number of rectangles, w_max and h_max
+ * to maximum output rectangle size, and w and h to start size (may be 0).
+ * Write input sizes in packer->in.
+ * Resulting packing will be written in packer->result.
+ * w and h will be increased if necessary for successful packing.
+ * There is a strong guarantee that w and h will be powers of 2 (or set to 0).
+ * Return value is -1 if packing failed because w and h were set to max
+ * values but that wasn't enough, 1 if w or h was increased, and 0 otherwise.
+ */
+int packer_pack(struct bitmap_packer *packer);
+
+/* Like above, but packer->count will be automatically set and
+ * packer->in will be reallocated if needed and filled from the
+ * given image list.
+ */
+int packer_pack_from_subbitmaps(struct bitmap_packer *packer,
+ struct sub_bitmaps *b);
+
+// Copy the (already packed) sub-bitmaps from b to the image in data.
+// data must point to an image that is at least (packer->w, packer->h) big.
+// The image has the given stride (bytes between (x, y) to (x, y + 1)), and the
+// pixel format used by both the sub-bitmaps and the image uses pixel_stride
+// bytes per pixel (bytes between (x, y) to (x + 1, y)).
+// If packer->padding is set, the padding borders are cleared with 0.
+void packer_copy_subbitmaps(struct bitmap_packer *packer, struct sub_bitmaps *b,
+ void *data, int pixel_stride, int stride);
+
+#endif
diff --git a/video/out/cocoa_common.h b/video/out/cocoa_common.h
new file mode 100644
index 0000000000..079e497441
--- /dev/null
+++ b/video/out/cocoa_common.h
@@ -0,0 +1,55 @@
+/*
+ * Cocoa OpenGL Backend
+ *
+ * This file is part of mplayer2.
+ *
+ * mplayer2 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mplayer2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mplayer2. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef MPLAYER_COCOA_COMMON_H
+#define MPLAYER_COCOA_COMMON_H
+
+#include "video_out.h"
+
+struct vo_cocoa_state;
+
+bool vo_cocoa_gui_running(void);
+void *vo_cocoa_glgetaddr(const char *s);
+
+int vo_cocoa_init(struct vo *vo);
+void vo_cocoa_uninit(struct vo *vo);
+
+void vo_cocoa_update_xinerama_info(struct vo *vo);
+
+int vo_cocoa_change_attributes(struct vo *vo);
+int vo_cocoa_create_window(struct vo *vo, uint32_t d_width,
+ uint32_t d_height, uint32_t flags,
+ int gl3profile);
+
+void vo_cocoa_swap_buffers(struct vo *vo);
+int vo_cocoa_check_events(struct vo *vo);
+void vo_cocoa_fullscreen(struct vo *vo);
+void vo_cocoa_ontop(struct vo *vo);
+void vo_cocoa_pause(struct vo *vo);
+void vo_cocoa_resume(struct vo *vo);
+
+// returns an int to conform to the gl extensions from other platforms
+int vo_cocoa_swap_interval(int enabled);
+
+void *vo_cocoa_cgl_context(struct vo *vo);
+void *vo_cocoa_cgl_pixel_format(struct vo *vo);
+
+int vo_cocoa_cgl_color_size(struct vo *vo);
+
+#endif /* MPLAYER_COCOA_COMMON_H */
diff --git a/video/out/cocoa_common.m b/video/out/cocoa_common.m
new file mode 100644
index 0000000000..337e0a32be
--- /dev/null
+++ b/video/out/cocoa_common.m
@@ -0,0 +1,865 @@
+/*
+ * Cocoa OpenGL Backend
+ *
+ * This file is part of mplayer2.
+ *
+ * mplayer2 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mplayer2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mplayer2. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import <Cocoa/Cocoa.h>
+#import <CoreServices/CoreServices.h> // for CGDisplayHideCursor
+#import <IOKit/pwr_mgt/IOPMLib.h>
+#include <dlfcn.h>
+
+#include "cocoa_common.h"
+
+#include "config.h"
+
+#include "options.h"
+#include "video_out.h"
+#include "aspect.h"
+
+#include "mp_fifo.h"
+#include "talloc.h"
+
+#include "input/input.h"
+#include "input/keycodes.h"
+#include "osx_common.h"
+#include "mp_msg.h"
+
+#ifndef NSOpenGLPFAOpenGLProfile
+#define NSOpenGLPFAOpenGLProfile 99
+#endif
+
+#ifndef NSOpenGLProfileVersionLegacy
+#define NSOpenGLProfileVersionLegacy 0x1000
+#endif
+
+#ifndef NSOpenGLProfileVersion3_2Core
+#define NSOpenGLProfileVersion3_2Core 0x3200
+#endif
+
+#define NSLeftAlternateKeyMask (0x000020 | NSAlternateKeyMask)
+#define NSRightAlternateKeyMask (0x000040 | NSAlternateKeyMask)
+
+// add methods not available on OSX versions prior to 10.7
+#ifndef MAC_OS_X_VERSION_10_7
+@interface NSView (IntroducedInLion)
+- (NSRect)convertRectToBacking:(NSRect)aRect;
+- (void)setWantsBestResolutionOpenGLSurface:(BOOL)aBool;
+@end
+#endif
+
+// add power management assertion not available on OSX versions prior to 10.7
+#ifndef kIOPMAssertionTypePreventUserIdleDisplaySleep
+#define kIOPMAssertionTypePreventUserIdleDisplaySleep \
+ CFSTR("PreventUserIdleDisplaySleep")
+#endif
+
+@interface GLMPlayerWindow : NSWindow <NSWindowDelegate> {
+ struct vo *_vo;
+}
+- (void)setVideoOutput:(struct vo *)vo;
+- (BOOL)canBecomeKeyWindow;
+- (BOOL)canBecomeMainWindow;
+- (void)fullscreen;
+- (void)mouseEvent:(NSEvent *)theEvent;
+- (void)mulSize:(float)multiplier;
+- (void)setContentSize:(NSSize)newSize keepCentered:(BOOL)keepCentered;
+@end
+
+@interface GLMPlayerOpenGLView : NSView
+@end
+
+struct vo_cocoa_state {
+ NSAutoreleasePool *pool;
+ GLMPlayerWindow *window;
+ NSOpenGLContext *glContext;
+ NSOpenGLPixelFormat *pixelFormat;
+
+ NSSize current_video_size;
+ NSSize previous_video_size;
+
+ NSRect screen_frame;
+ NSScreen *screen_handle;
+ NSArray *screen_array;
+
+ NSInteger windowed_mask;
+ NSInteger fullscreen_mask;
+
+ NSRect windowed_frame;
+
+ NSString *window_title;
+
+ NSInteger window_level;
+ NSInteger fullscreen_window_level;
+
+ int display_cursor;
+ int cursor_timer;
+ int cursor_autohide_delay;
+
+ bool did_resize;
+ bool out_fs_resize;
+
+ IOPMAssertionID power_mgmt_assertion;
+};
+
+static int _instances = 0;
+
+static void create_menu(void);
+
+static struct vo_cocoa_state *vo_cocoa_init_state(struct vo *vo)
+{
+ struct vo_cocoa_state *s = talloc_ptrtype(vo, s);
+ *s = (struct vo_cocoa_state){
+ .pool = [[NSAutoreleasePool alloc] init],
+ .did_resize = NO,
+ .current_video_size = {0,0},
+ .previous_video_size = {0,0},
+ .windowed_mask = NSTitledWindowMask|NSClosableWindowMask|
+ NSMiniaturizableWindowMask|NSResizableWindowMask,
+ .fullscreen_mask = NSBorderlessWindowMask,
+ .windowed_frame = {{0,0},{0,0}},
+ .out_fs_resize = NO,
+ .display_cursor = 1,
+ .cursor_autohide_delay = vo->opts->cursor_autohide_delay,
+ .power_mgmt_assertion = kIOPMNullAssertionID,
+ };
+ return s;
+}
+
+static bool supports_hidpi(NSView *view)
+{
+ SEL hdpi_selector = @selector(setWantsBestResolutionOpenGLSurface:);
+ return is_osx_version_at_least(10, 7, 0) && view &&
+ [view respondsToSelector:hdpi_selector];
+}
+
+bool vo_cocoa_gui_running(void)
+{
+ return _instances > 0;
+}
+
+void *vo_cocoa_glgetaddr(const char *s)
+{
+ void *ret = NULL;
+ void *handle = dlopen(
+ "/System/Library/Frameworks/OpenGL.framework/OpenGL",
+ RTLD_LAZY | RTLD_LOCAL);
+ if (!handle)
+ return NULL;
+ ret = dlsym(handle, s);
+ dlclose(handle);
+ return ret;
+}
+
+static void enable_power_management(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ if (!s->power_mgmt_assertion) return;
+ IOPMAssertionRelease(s->power_mgmt_assertion);
+ s->power_mgmt_assertion = kIOPMNullAssertionID;
+}
+
+static void disable_power_management(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ if (s->power_mgmt_assertion) return;
+
+ CFStringRef assertion_type = kIOPMAssertionTypeNoDisplaySleep;
+ if (is_osx_version_at_least(10, 7, 0))
+ assertion_type = kIOPMAssertionTypePreventUserIdleDisplaySleep;
+
+ IOPMAssertionCreateWithName(assertion_type, kIOPMAssertionLevelOn,
+ CFSTR("org.mplayer2.power_mgmt"), &s->power_mgmt_assertion);
+}
+
+int vo_cocoa_init(struct vo *vo)
+{
+ vo->cocoa = vo_cocoa_init_state(vo);
+ _instances++;
+
+ NSApplicationLoad();
+ NSApp = [NSApplication sharedApplication];
+ [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
+ disable_power_management(vo);
+
+ return 1;
+}
+
+void vo_cocoa_uninit(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ CGDisplayShowCursor(kCGDirectMainDisplay);
+ enable_power_management(vo);
+ [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+
+ [s->window release];
+ s->window = nil;
+ [s->glContext release];
+ s->glContext = nil;
+ [s->pool release];
+ s->pool = nil;
+
+ _instances--;
+}
+
+void vo_cocoa_pause(struct vo *vo)
+{
+ enable_power_management(vo);
+}
+
+void vo_cocoa_resume(struct vo *vo)
+{
+ disable_power_management(vo);
+}
+
+static int current_screen_has_dock_or_menubar(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ NSRect f = s->screen_frame;
+ NSRect vf = [s->screen_handle visibleFrame];
+ return f.size.height > vf.size.height || f.size.width > vf.size.width;
+}
+
+static void update_screen_info(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ s->screen_array = [NSScreen screens];
+ if (xinerama_screen >= (int)[s->screen_array count]) {
+ mp_msg(MSGT_VO, MSGL_INFO, "[cocoa] Device ID %d does not exist, "
+ "falling back to main device\n", xinerama_screen);
+ xinerama_screen = -1;
+ }
+
+ if (xinerama_screen < 0) { // default behaviour
+ if (! (s->screen_handle = [s->window screen]) )
+ s->screen_handle = [s->screen_array objectAtIndex:0];
+ } else {
+ s->screen_handle = [s->screen_array objectAtIndex:(xinerama_screen)];
+ }
+
+ s->screen_frame = [s->screen_handle frame];
+}
+
+void vo_cocoa_update_xinerama_info(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ struct MPOpts *opts = vo->opts;
+
+ update_screen_info(vo);
+ aspect_save_screenres(vo, s->screen_frame.size.width,
+ s->screen_frame.size.height);
+ opts->vo_screenwidth = s->screen_frame.size.width;
+ opts->vo_screenheight = s->screen_frame.size.height;
+ xinerama_x = s->screen_frame.origin.x;
+ xinerama_y = s->screen_frame.origin.y;
+}
+
+int vo_cocoa_change_attributes(struct vo *vo)
+{
+ return 0;
+}
+
+static void resize_window(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ NSView *view = [s->window contentView];
+ NSRect frame;
+
+ if (supports_hidpi(view)) {
+ frame = [view convertRectToBacking: [view frame]];
+ } else {
+ frame = [view frame];
+ }
+
+ vo->dwidth = frame.size.width;
+ vo->dheight = frame.size.height;
+ [s->glContext update];
+}
+
+static void vo_set_level(struct vo *vo, int ontop)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ if (ontop) {
+ s->window_level = NSNormalWindowLevel + 1;
+ } else {
+ s->window_level = NSNormalWindowLevel;
+ }
+
+ if (!vo_fs)
+ [s->window setLevel:s->window_level];
+}
+
+void vo_cocoa_ontop(struct vo *vo)
+{
+ struct MPOpts *opts = vo->opts;
+ opts->vo_ontop = !opts->vo_ontop;
+ vo_set_level(vo, opts->vo_ontop);
+}
+
+static void update_state_sizes(struct vo_cocoa_state *s,
+ uint32_t d_width, uint32_t d_height)
+{
+ if (s->current_video_size.width > 0 || s->current_video_size.height > 0)
+ s->previous_video_size = s->current_video_size;
+ s->current_video_size = NSMakeSize(d_width, d_height);
+}
+
+static int create_window(struct vo *vo, uint32_t d_width, uint32_t d_height,
+ uint32_t flags, int gl3profile)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ struct MPOpts *opts = vo->opts;
+
+ const NSRect window_rect = NSMakeRect(0, 0, d_width, d_height);
+ const NSRect glview_rect = NSMakeRect(0, 0, 100, 100);
+
+ s->window =
+ [[GLMPlayerWindow alloc] initWithContentRect:window_rect
+ styleMask:s->windowed_mask
+ backing:NSBackingStoreBuffered
+ defer:NO];
+
+ GLMPlayerOpenGLView *glView =
+ [[GLMPlayerOpenGLView alloc] initWithFrame:glview_rect];
+
+ // check for HiDPI support and enable it (available on 10.7 +)
+ if (supports_hidpi(glView))
+ [glView setWantsBestResolutionOpenGLSurface:YES];
+
+ int i = 0;
+ NSOpenGLPixelFormatAttribute attr[32];
+ if (is_osx_version_at_least(10, 7, 0)) {
+ attr[i++] = NSOpenGLPFAOpenGLProfile;
+ if (gl3profile) {
+ attr[i++] = NSOpenGLProfileVersion3_2Core;
+ } else {
+ attr[i++] = NSOpenGLProfileVersionLegacy;
+ }
+ } else if(gl3profile) {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "[cocoa] Invalid pixel format attribute "
+ "(GL3 is not supported on OSX versions prior to 10.7)\n");
+ return -1;
+ }
+ attr[i++] = NSOpenGLPFADoubleBuffer; // double buffered
+ attr[i] = (NSOpenGLPixelFormatAttribute)0;
+
+ s->pixelFormat =
+ [[[NSOpenGLPixelFormat alloc] initWithAttributes:attr] autorelease];
+ if (!s->pixelFormat) {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "[cocoa] Invalid pixel format attribute "
+ "(GL3 not supported?)\n");
+ return -1;
+ }
+ s->glContext =
+ [[NSOpenGLContext alloc] initWithFormat:s->pixelFormat
+ shareContext:nil];
+
+ create_menu();
+
+ [s->window setContentView:glView];
+ [glView release];
+ [s->window setAcceptsMouseMovedEvents:YES];
+ [s->glContext setView:glView];
+ [s->glContext makeCurrentContext];
+ [s->window setVideoOutput:vo];
+
+ [NSApp setDelegate:s->window];
+ [s->window setDelegate:s->window];
+ [s->window setContentSize:s->current_video_size];
+ [s->window setContentAspectRatio:s->current_video_size];
+ [s->window setFrameOrigin:NSMakePoint(vo->dx, vo->dy)];
+
+ if (flags & VOFLAG_HIDDEN) {
+ [s->window orderOut:nil];
+ } else {
+ [s->window makeKeyAndOrderFront:nil];
+ [NSApp activateIgnoringOtherApps:YES];
+ }
+
+ if (flags & VOFLAG_FULLSCREEN)
+ vo_cocoa_fullscreen(vo);
+
+ vo_set_level(vo, opts->vo_ontop);
+
+ return 0;
+}
+
+static void update_window(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+
+ if (s->current_video_size.width != s->previous_video_size.width ||
+ s->current_video_size.height != s->previous_video_size.height) {
+ if (vo_fs) {
+ // we will resize as soon as we get out of fullscreen
+ s->out_fs_resize = YES;
+ } else {
+ // only if we are not in fullscreen and the video size did
+ // change we resize the window and set a new aspect ratio
+ [s->window setContentSize:s->current_video_size
+ keepCentered:YES];
+ [s->window setContentAspectRatio:s->current_video_size];
+ }
+ }
+}
+
+int vo_cocoa_create_window(struct vo *vo, uint32_t d_width,
+ uint32_t d_height, uint32_t flags,
+ int gl3profile)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+
+ update_state_sizes(s, d_width, d_height);
+
+ if (!(s->window || s->glContext)) {
+ if (create_window(vo, d_width, d_height, flags, gl3profile) < 0)
+ return -1;
+ } else {
+ update_window(vo);
+ }
+
+ resize_window(vo);
+
+ if (s->window_title)
+ [s->window_title release];
+
+ s->window_title =
+ [[NSString alloc] initWithUTF8String:vo_get_window_title(vo)];
+ [s->window setTitle: s->window_title];
+
+ return 0;
+}
+
+void vo_cocoa_swap_buffers(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ [s->glContext flushBuffer];
+}
+
+static void vo_cocoa_display_cursor(struct vo *vo, int requested_state)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ if (requested_state) {
+ if (!vo_fs || s->cursor_autohide_delay > -2) {
+ s->display_cursor = requested_state;
+ CGDisplayShowCursor(kCGDirectMainDisplay);
+ }
+ } else {
+ if (s->cursor_autohide_delay != -1) {
+ s->display_cursor = requested_state;
+ CGDisplayHideCursor(kCGDirectMainDisplay);
+ }
+ }
+}
+
+int vo_cocoa_check_events(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ NSEvent *event;
+ int ms_time = (int) ([[NSProcessInfo processInfo] systemUptime] * 1000);
+
+ // automatically hide mouse cursor
+ if (vo_fs && s->display_cursor &&
+ (ms_time - s->cursor_timer >= s->cursor_autohide_delay)) {
+ vo_cocoa_display_cursor(vo, 0);
+ s->cursor_timer = ms_time;
+ }
+
+ event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil
+ inMode:NSEventTrackingRunLoopMode dequeue:YES];
+ if (event == nil)
+ return 0;
+ [NSApp sendEvent:event];
+
+ if (s->did_resize) {
+ s->did_resize = NO;
+ resize_window(vo);
+ return VO_EVENT_RESIZE;
+ }
+ // Without SDL's bootstrap code (include SDL.h in mplayer.c),
+ // on Leopard, we have trouble to get the play window automatically focused
+ // when the app is actived. The Following code fix this problem.
+ if ([event type] == NSAppKitDefined
+ && [event subtype] == NSApplicationActivatedEventType) {
+ [s->window makeMainWindow];
+ [s->window makeKeyAndOrderFront:nil];
+ }
+ return 0;
+}
+
+void vo_cocoa_fullscreen(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ [s->window fullscreen];
+ resize_window(vo);
+}
+
+int vo_cocoa_swap_interval(int enabled)
+{
+ [[NSOpenGLContext currentContext] setValues:&enabled
+ forParameter:NSOpenGLCPSwapInterval];
+ return 0;
+}
+
+void *vo_cocoa_cgl_context(struct vo *vo)
+{
+ struct vo_cocoa_state *s = vo->cocoa;
+ return [s->glContext CGLContextObj];
+}
+
+void *vo_cocoa_cgl_pixel_format(struct vo *vo)
+{
+ return CGLGetPixelFormat(vo_cocoa_cgl_context(vo));
+}
+
+int vo_cocoa_cgl_color_size(struct vo *vo)
+{
+ GLint value;
+ CGLDescribePixelFormat(vo_cocoa_cgl_pixel_format(vo), 0,
+ kCGLPFAColorSize, &value);
+ switch (value) {
+ case 32:
+ case 24:
+ return 8;
+ case 16:
+ return 5;
+ }
+
+ return 8;
+}
+
+static NSMenuItem *new_menu_item(NSMenu *parent_menu, NSString *title,
+ SEL action, NSString *key_equivalent)
+{
+ NSMenuItem *new_item =
+ [[NSMenuItem alloc] initWithTitle:title action:action
+ keyEquivalent:key_equivalent];
+ [parent_menu addItem:new_item];
+ return [new_item autorelease];
+}
+
+static NSMenuItem *new_main_menu_item(NSMenu *parent_menu, NSMenu *child_menu,
+ NSString *title)
+{
+ NSMenuItem *new_item =
+ [[NSMenuItem alloc] initWithTitle:title action:nil
+ keyEquivalent:@""];
+ [new_item setSubmenu:child_menu];
+ [parent_menu addItem:new_item];
+ return [new_item autorelease];
+}
+
+void create_menu()
+{
+ NSAutoreleasePool *pool = [NSAutoreleasePool new];
+ NSMenu *main_menu, *m_menu, *w_menu;
+ NSMenuItem *app_menu_item;
+
+ main_menu = [[NSMenu new] autorelease];
+ app_menu_item = [[NSMenuItem new] autorelease];
+ [main_menu addItem:app_menu_item];
+ [NSApp setMainMenu: main_menu];
+
+ m_menu = [[[NSMenu alloc] initWithTitle:@"Movie"] autorelease];
+ new_menu_item(m_menu, @"Half Size", @selector(halfSize), @"0");
+ new_menu_item(m_menu, @"Normal Size", @selector(normalSize), @"1");
+ new_menu_item(m_menu, @"Double Size", @selector(doubleSize), @"2");
+
+ new_main_menu_item(main_menu, m_menu, @"Movie");
+
+ w_menu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
+ new_menu_item(w_menu, @"Minimize", @selector(performMiniaturize:), @"m");
+ new_menu_item(w_menu, @"Zoom", @selector(performZoom:), @"z");
+
+ new_main_menu_item(main_menu, w_menu, @"Window");
+ [pool release];
+}
+
+@implementation GLMPlayerWindow
+- (void)setVideoOutput:(struct vo *)vo
+{
+ _vo = vo;
+}
+
+- (void)windowDidResize:(NSNotification *) notification
+{
+ if (_vo) {
+ struct vo_cocoa_state *s = _vo->cocoa;
+ s->did_resize = YES;
+ }
+}
+
+- (void)fullscreen
+{
+ struct vo_cocoa_state *s = _vo->cocoa;
+ if (!vo_fs) {
+ update_screen_info(_vo);
+ if (current_screen_has_dock_or_menubar(_vo))
+ [NSApp setPresentationOptions:NSApplicationPresentationHideDock|
+ NSApplicationPresentationHideMenuBar];
+ s->windowed_frame = [self frame];
+ [self setHasShadow:NO];
+ [self setStyleMask:s->fullscreen_mask];
+ [self setFrame:s->screen_frame display:YES animate:NO];
+ vo_fs = VO_TRUE;
+ vo_cocoa_display_cursor(_vo, 0);
+ [self setMovableByWindowBackground: NO];
+ } else {
+ [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+ [self setHasShadow:YES];
+ [self setStyleMask:s->windowed_mask];
+ [self setTitle:s->window_title];
+ [self setFrame:s->windowed_frame display:YES animate:NO];
+ if (s->out_fs_resize) {
+ [self setContentSize:s->current_video_size keepCentered:YES];
+ s->out_fs_resize = NO;
+ }
+ [self setContentAspectRatio:s->current_video_size];
+ vo_fs = VO_FALSE;
+ vo_cocoa_display_cursor(_vo, 1);
+ [self setMovableByWindowBackground: YES];
+ }
+}
+
+- (BOOL)canBecomeMainWindow { return YES; }
+- (BOOL)canBecomeKeyWindow { return YES; }
+- (BOOL)acceptsFirstResponder { return YES; }
+- (BOOL)becomeFirstResponder { return YES; }
+- (BOOL)resignFirstResponder { return YES; }
+- (BOOL)windowShouldClose:(id)sender
+{
+ mplayer_put_key(_vo->key_fifo, KEY_CLOSE_WIN);
+ // We have to wait for MPlayer to handle this,
+ // otherwise we are in trouble if the
+ // KEY_CLOSE_WIN handler is disabled
+ return NO;
+}
+
+- (BOOL)isMovableByWindowBackground
+{
+ // this is only valid as a starting value. it will be rewritten in the
+ // -fullscreen method.
+ return !vo_fs;
+}
+
+- (void)handleQuitEvent:(NSAppleEventDescriptor*)e
+ withReplyEvent:(NSAppleEventDescriptor*)r
+{
+ mplayer_put_key(_vo->key_fifo, KEY_CLOSE_WIN);
+}
+
+- (void)keyDown:(NSEvent *)theEvent
+{
+ unsigned char charcode;
+ if (([theEvent modifierFlags] & NSRightAlternateKeyMask) ==
+ NSRightAlternateKeyMask)
+ charcode = *[[theEvent characters] UTF8String];
+ else
+ charcode = [[theEvent charactersIgnoringModifiers] characterAtIndex:0];
+
+ int key = convert_key([theEvent keyCode], charcode);
+
+ if (key > -1) {
+ if ([theEvent modifierFlags] & NSShiftKeyMask)
+ key |= KEY_MODIFIER_SHIFT;
+ if ([theEvent modifierFlags] & NSControlKeyMask)
+ key |= KEY_MODIFIER_CTRL;
+ if (([theEvent modifierFlags] & NSLeftAlternateKeyMask) ==
+ NSLeftAlternateKeyMask)
+ key |= KEY_MODIFIER_ALT;
+ if ([theEvent modifierFlags] & NSCommandKeyMask)
+ key |= KEY_MODIFIER_META;
+ mplayer_put_key(_vo->key_fifo, key);
+ }
+}
+
+- (void)mouseMoved: (NSEvent *) theEvent
+{
+ if (vo_fs)
+ vo_cocoa_display_cursor(_vo, 1);
+}
+
+- (void)mouseDragged:(NSEvent *)theEvent
+{
+ [self mouseEvent: theEvent];
+}
+
+- (void)mouseDown:(NSEvent *)theEvent
+{
+ [self mouseEvent: theEvent];
+}
+
+- (void)mouseUp:(NSEvent *)theEvent
+{
+ [self mouseEvent: theEvent];
+}
+
+- (void)rightMouseDown:(NSEvent *)theEvent
+{
+ [self mouseEvent: theEvent];
+}
+
+- (void)rightMouseUp:(NSEvent *)theEvent
+{
+ [self mouseEvent: theEvent];
+}
+
+- (void)otherMouseDown:(NSEvent *)theEvent
+{
+ [self mouseEvent: theEvent];
+}
+
+- (void)otherMouseUp:(NSEvent *)theEvent
+{
+ [self mouseEvent: theEvent];
+}
+
+- (void)scrollWheel:(NSEvent *)theEvent
+{
+ if ([theEvent deltaY] > 0)
+ mplayer_put_key(_vo->key_fifo, MOUSE_BTN3);
+ else
+ mplayer_put_key(_vo->key_fifo, MOUSE_BTN4);
+}
+
+- (void)mouseEvent:(NSEvent *)theEvent
+{
+ if ([theEvent buttonNumber] >= 0 && [theEvent buttonNumber] <= 9) {
+ int buttonNumber = [theEvent buttonNumber];
+ // Fix to mplayer defined button order: left, middle, right
+ if (buttonNumber == 1) buttonNumber = 2;
+ else if (buttonNumber == 2) buttonNumber = 1;
+ switch ([theEvent type]) {
+ case NSLeftMouseDown:
+ case NSRightMouseDown:
+ case NSOtherMouseDown:
+ mplayer_put_key(_vo->key_fifo,
+ (MOUSE_BTN0 + buttonNumber) | MP_KEY_DOWN);
+ // Looks like Cocoa doesn't create MouseUp events when we are
+ // doing the second click in a double click. Put in the key_fifo
+ // the key that would be put from the MouseUp handling code.
+ if([theEvent clickCount] == 2)
+ mplayer_put_key(_vo->key_fifo, MOUSE_BTN0 + buttonNumber);
+ break;
+ case NSLeftMouseUp:
+ case NSRightMouseUp:
+ case NSOtherMouseUp:
+ mplayer_put_key(_vo->key_fifo, MOUSE_BTN0 + buttonNumber);
+ break;
+ }
+ }
+}
+
+- (void)applicationWillBecomeActive:(NSNotification *)aNotification
+{
+ if (vo_fs && current_screen_has_dock_or_menubar(_vo)) {
+ [NSApp setPresentationOptions:NSApplicationPresentationHideDock|
+ NSApplicationPresentationHideMenuBar];
+ }
+}
+
+- (void)applicationWillResignActive:(NSNotification *)aNotification
+{
+ if (vo_fs) {
+ [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+ }
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification*)notification
+{
+ // Install an event handler so the Quit menu entry works
+ // The proper way using NSApp setDelegate: and
+ // applicationShouldTerminate: does not work,
+ // probably NSApplication never installs its handler.
+ [[NSAppleEventManager sharedAppleEventManager]
+ setEventHandler:self
+ andSelector:@selector(handleQuitEvent:withReplyEvent:)
+ forEventClass:kCoreEventClass
+ andEventID:kAEQuitApplication];
+}
+
+- (void)normalSize
+{
+ struct vo_cocoa_state *s = _vo->cocoa;
+ if (!vo_fs)
+ [self setContentSize:s->current_video_size keepCentered:YES];
+}
+
+- (void)halfSize { [self mulSize:0.5f];}
+
+- (void)doubleSize { [self mulSize:2.0f];}
+
+- (void)mulSize:(float)multiplier
+{
+ if (!vo_fs) {
+ struct vo_cocoa_state *s = _vo->cocoa;
+ NSSize size = [[self contentView] frame].size;
+ size.width = s->current_video_size.width * (multiplier);
+ size.height = s->current_video_size.height * (multiplier);
+ [self setContentSize:size keepCentered:YES];
+ }
+}
+
+- (void)setCenteredContentSize:(NSSize)ns
+{
+ NSRect nf = [self frame];
+ NSRect vf = [[self screen] visibleFrame];
+ NSRect cb = [[self contentView] bounds];
+ int title_height = nf.size.height - cb.size.height;
+ double ratio = (double)ns.width / (double)ns.height;
+
+ // clip the new size to the visibleFrame's size if needed
+ if (ns.width > vf.size.width || ns.height + title_height > vf.size.height) {
+ ns = vf.size;
+ ns.height -= title_height; // make space for the title bar
+
+ if (ns.width > ns.height) {
+ ns.height = ((double)ns.width * 1/ratio + 0.5);
+ } else {
+ ns.width = ((double)ns.height * ratio + 0.5);
+ }
+ }
+
+ int dw = nf.size.width - ns.width;
+ int dh = nf.size.height - ns.height - title_height;
+
+ nf.origin.x += dw / 2;
+ nf.origin.y += dh / 2;
+
+ NSRect new_frame =
+ NSMakeRect(nf.origin.x, nf.origin.y, ns.width, ns.height + title_height);
+ [self setFrame:new_frame display:YES animate:NO];
+}
+
+- (void)setContentSize:(NSSize)ns keepCentered:(BOOL)keepCentered
+{
+ if (keepCentered) {
+ [self setCenteredContentSize:ns];
+ } else {
+ [self setContentSize:ns];
+ }
+}
+@end
+
+@implementation GLMPlayerOpenGLView
+- (void)drawRect: (NSRect)rect
+{
+ [[NSColor clearColor] set];
+ NSRectFill([self bounds]);
+}
+@end
diff --git a/video/out/d3d_shader_yuv.h b/video/out/d3d_shader_yuv.h
new file mode 100644
index 0000000000..49ef753b4c
--- /dev/null
+++ b/video/out/d3d_shader_yuv.h
@@ -0,0 +1,142 @@
+#if 0
+//
+// Generated by Microsoft (R) HLSL Shader Compiler 9.27.952.3022
+//
+// fxc /Tps_2_0 /Fhz:\tmp\mplayer\libvo\d3d_shader_yuv.h
+// z:\tmp\mplayer\libvo\d3d_shader_yuv.hlsl /Vnd3d_shader_yuv
+//
+//
+// Parameters:
+//
+// float4x4 colormatrix;
+// sampler2D tex0;
+// sampler2D tex1;
+// sampler2D tex2;
+//
+//
+// Registers:
+//
+// Name Reg Size
+// ------------ ----- ----
+// colormatrix c0 4
+// tex0 s0 1
+// tex1 s1 1
+// tex2 s2 1
+//
+
+ ps_2_0
+ def c4, 1, 0, 0, 0
+ dcl t0.xy
+ dcl t1.xy
+ dcl t2.xy
+ dcl_2d s0
+ dcl_2d s1
+ dcl_2d s2
+ texld r0, t0, s0
+ texld r1, t1, s1
+ texld r2, t2, s2
+ mov r0.y, r1.x
+ mov r0.z, r2.x
+ mov r0.w, c4.x
+ dp4 r1.x, r0, c0
+ dp4 r1.y, r0, c1
+ dp4 r1.z, r0, c2
+ dp4 r1.w, r0, c3
+ mov oC0, r1
+
+// approximately 11 instruction slots used (3 texture, 8 arithmetic)
+#endif
+
+const BYTE d3d_shader_yuv[] =
+{
+ 0, 2, 255, 255, 254, 255,
+ 67, 0, 67, 84, 65, 66,
+ 28, 0, 0, 0, 215, 0,
+ 0, 0, 0, 2, 255, 255,
+ 4, 0, 0, 0, 28, 0,
+ 0, 0, 0, 1, 0, 0,
+ 208, 0, 0, 0, 108, 0,
+ 0, 0, 2, 0, 0, 0,
+ 4, 0, 2, 0, 120, 0,
+ 0, 0, 0, 0, 0, 0,
+ 136, 0, 0, 0, 3, 0,
+ 0, 0, 1, 0, 2, 0,
+ 144, 0, 0, 0, 0, 0,
+ 0, 0, 160, 0, 0, 0,
+ 3, 0, 1, 0, 1, 0,
+ 6, 0, 168, 0, 0, 0,
+ 0, 0, 0, 0, 184, 0,
+ 0, 0, 3, 0, 2, 0,
+ 1, 0, 10, 0, 192, 0,
+ 0, 0, 0, 0, 0, 0,
+ 99, 111, 108, 111, 114, 109,
+ 97, 116, 114, 105, 120, 0,
+ 3, 0, 3, 0, 4, 0,
+ 4, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 116, 101,
+ 120, 48, 0, 171, 171, 171,
+ 4, 0, 12, 0, 1, 0,
+ 1, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 116, 101,
+ 120, 49, 0, 171, 171, 171,
+ 4, 0, 12, 0, 1, 0,
+ 1, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 116, 101,
+ 120, 50, 0, 171, 171, 171,
+ 4, 0, 12, 0, 1, 0,
+ 1, 0, 1, 0, 0, 0,
+ 0, 0, 0, 0, 112, 115,
+ 95, 50, 95, 48, 0, 77,
+ 105, 99, 114, 111, 115, 111,
+ 102, 116, 32, 40, 82, 41,
+ 32, 72, 76, 83, 76, 32,
+ 83, 104, 97, 100, 101, 114,
+ 32, 67, 111, 109, 112, 105,
+ 108, 101, 114, 32, 57, 46,
+ 50, 55, 46, 57, 53, 50,
+ 46, 51, 48, 50, 50, 0,
+ 81, 0, 0, 5, 4, 0,
+ 15, 160, 0, 0, 128, 63,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 31, 0, 0, 2, 0, 0,
+ 0, 128, 0, 0, 3, 176,
+ 31, 0, 0, 2, 0, 0,
+ 0, 128, 1, 0, 3, 176,
+ 31, 0, 0, 2, 0, 0,
+ 0, 128, 2, 0, 3, 176,
+ 31, 0, 0, 2, 0, 0,
+ 0, 144, 0, 8, 15, 160,
+ 31, 0, 0, 2, 0, 0,
+ 0, 144, 1, 8, 15, 160,
+ 31, 0, 0, 2, 0, 0,
+ 0, 144, 2, 8, 15, 160,
+ 66, 0, 0, 3, 0, 0,
+ 15, 128, 0, 0, 228, 176,
+ 0, 8, 228, 160, 66, 0,
+ 0, 3, 1, 0, 15, 128,
+ 1, 0, 228, 176, 1, 8,
+ 228, 160, 66, 0, 0, 3,
+ 2, 0, 15, 128, 2, 0,
+ 228, 176, 2, 8, 228, 160,
+ 1, 0, 0, 2, 0, 0,
+ 2, 128, 1, 0, 0, 128,
+ 1, 0, 0, 2, 0, 0,
+ 4, 128, 2, 0, 0, 128,
+ 1, 0, 0, 2, 0, 0,
+ 8, 128, 4, 0, 0, 160,
+ 9, 0, 0, 3, 1, 0,
+ 1, 128, 0, 0, 228, 128,
+ 0, 0, 228, 160, 9, 0,
+ 0, 3, 1, 0, 2, 128,
+ 0, 0, 228, 128, 1, 0,
+ 228, 160, 9, 0, 0, 3,
+ 1, 0, 4, 128, 0, 0,
+ 228, 128, 2, 0, 228, 160,
+ 9, 0, 0, 3, 1, 0,
+ 8, 128, 0, 0, 228, 128,
+ 3, 0, 228, 160, 1, 0,
+ 0, 2, 0, 8, 15, 128,
+ 1, 0, 228, 128, 255, 255,
+ 0, 0
+};
diff --git a/video/out/d3d_shader_yuv.hlsl b/video/out/d3d_shader_yuv.hlsl
new file mode 100644
index 0000000000..b17e257210
--- /dev/null
+++ b/video/out/d3d_shader_yuv.hlsl
@@ -0,0 +1,44 @@
+// Compile with:
+// fxc.exe /Tps_2_0 /Fhd3d_shader_yuv.h d3d_shader_yuv.hlsl /Vnd3d_shader_yuv
+// fxc.exe /Tps_2_0 /Fhd3d_shader_yuv_2ch.h d3d_shader_yuv.hlsl /Vnd3d_shader_yuv_2ch /DUSE_2CH=1
+
+// Be careful with this shader. You can't use constant slots, since we don't
+// load the shader with D3DX. All uniform variables are mapped to hardcoded
+// constant slots.
+
+sampler2D tex0 : register(s0);
+sampler2D tex1 : register(s1);
+sampler2D tex2 : register(s2);
+
+uniform float4x4 colormatrix : register(c0);
+uniform float2 depth : register(c5);
+
+#ifdef USE_2CH
+
+float1 sample(sampler2D tex, float2 t)
+{
+ // Sample from A8L8 format as if we sampled a single value from L16.
+ // We compute the 2 channel values back into one.
+ return dot(tex2D(tex, t).xw, depth);
+}
+
+#else
+
+float1 sample(sampler2D tex, float2 t)
+{
+ return tex2D(tex, t).x;
+}
+
+#endif
+
+float4 main(float2 t0 : TEXCOORD0,
+ float2 t1 : TEXCOORD1,
+ float2 t2 : TEXCOORD2)
+ : COLOR
+{
+ float4 c = float4(sample(tex0, t0),
+ sample(tex1, t1),
+ sample(tex2, t2),
+ 1);
+ return mul(c, colormatrix);
+}
diff --git a/video/out/d3d_shader_yuv_2ch.h b/video/out/d3d_shader_yuv_2ch.h
new file mode 100644
index 0000000000..45dcc73992
--- /dev/null
+++ b/video/out/d3d_shader_yuv_2ch.h
@@ -0,0 +1,170 @@
+#if 0
+//
+// Generated by Microsoft (R) HLSL Shader Compiler 9.27.952.3022
+//
+// fxc /Tps_2_0 /Fhz:\tmp\mplayer\libvo\d3d_shader_yuv_2ch.h
+// z:\tmp\mplayer\libvo\d3d_shader_yuv.hlsl /Vnd3d_shader_yuv_2ch
+// /DUSE_2CH=1
+//
+//
+// Parameters:
+//
+// float4x4 colormatrix;
+// float2 depth;
+// sampler2D tex0;
+// sampler2D tex1;
+// sampler2D tex2;
+//
+//
+// Registers:
+//
+// Name Reg Size
+// ------------ ----- ----
+// colormatrix c0 4
+// depth c5 1
+// tex0 s0 1
+// tex1 s1 1
+// tex2 s2 1
+//
+
+ ps_2_0
+ def c4, 1, 0, 0, 0
+ dcl t0.xy
+ dcl t1.xy
+ dcl t2.xy
+ dcl_2d s0
+ dcl_2d s1
+ dcl_2d s2
+ texld r0, t0, s0
+ texld r1, t1, s1
+ texld r2, t2, s2
+ mul r0.x, r0.x, c5.x
+ mad r0.x, r0.w, c5.y, r0.x
+ mul r1.x, r1.x, c5.x
+ mad r0.y, r1.w, c5.y, r1.x
+ mul r1.x, r2.x, c5.x
+ mad r0.z, r2.w, c5.y, r1.x
+ mov r0.w, c4.x
+ dp4 r1.x, r0, c0
+ dp4 r1.y, r0, c1
+ dp4 r1.z, r0, c2
+ dp4 r1.w, r0, c3
+ mov oC0, r1
+
+// approximately 15 instruction slots used (3 texture, 12 arithmetic)
+#endif
+
+const BYTE d3d_shader_yuv_2ch[] =
+{
+ 0, 2, 255, 255, 254, 255,
+ 78, 0, 67, 84, 65, 66,
+ 28, 0, 0, 0, 3, 1,
+ 0, 0, 0, 2, 255, 255,
+ 5, 0, 0, 0, 28, 0,
+ 0, 0, 0, 1, 0, 0,
+ 252, 0, 0, 0, 128, 0,
+ 0, 0, 2, 0, 0, 0,
+ 4, 0, 2, 0, 140, 0,
+ 0, 0, 0, 0, 0, 0,
+ 156, 0, 0, 0, 2, 0,
+ 5, 0, 1, 0, 22, 0,
+ 164, 0, 0, 0, 0, 0,
+ 0, 0, 180, 0, 0, 0,
+ 3, 0, 0, 0, 1, 0,
+ 2, 0, 188, 0, 0, 0,
+ 0, 0, 0, 0, 204, 0,
+ 0, 0, 3, 0, 1, 0,
+ 1, 0, 6, 0, 212, 0,
+ 0, 0, 0, 0, 0, 0,
+ 228, 0, 0, 0, 3, 0,
+ 2, 0, 1, 0, 10, 0,
+ 236, 0, 0, 0, 0, 0,
+ 0, 0, 99, 111, 108, 111,
+ 114, 109, 97, 116, 114, 105,
+ 120, 0, 3, 0, 3, 0,
+ 4, 0, 4, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0,
+ 100, 101, 112, 116, 104, 0,
+ 171, 171, 1, 0, 3, 0,
+ 1, 0, 2, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0,
+ 116, 101, 120, 48, 0, 171,
+ 171, 171, 4, 0, 12, 0,
+ 1, 0, 1, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0,
+ 116, 101, 120, 49, 0, 171,
+ 171, 171, 4, 0, 12, 0,
+ 1, 0, 1, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0,
+ 116, 101, 120, 50, 0, 171,
+ 171, 171, 4, 0, 12, 0,
+ 1, 0, 1, 0, 1, 0,
+ 0, 0, 0, 0, 0, 0,
+ 112, 115, 95, 50, 95, 48,
+ 0, 77, 105, 99, 114, 111,
+ 115, 111, 102, 116, 32, 40,
+ 82, 41, 32, 72, 76, 83,
+ 76, 32, 83, 104, 97, 100,
+ 101, 114, 32, 67, 111, 109,
+ 112, 105, 108, 101, 114, 32,
+ 57, 46, 50, 55, 46, 57,
+ 53, 50, 46, 51, 48, 50,
+ 50, 0, 81, 0, 0, 5,
+ 4, 0, 15, 160, 0, 0,
+ 128, 63, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0,
+ 0, 0, 31, 0, 0, 2,
+ 0, 0, 0, 128, 0, 0,
+ 3, 176, 31, 0, 0, 2,
+ 0, 0, 0, 128, 1, 0,
+ 3, 176, 31, 0, 0, 2,
+ 0, 0, 0, 128, 2, 0,
+ 3, 176, 31, 0, 0, 2,
+ 0, 0, 0, 144, 0, 8,
+ 15, 160, 31, 0, 0, 2,
+ 0, 0, 0, 144, 1, 8,
+ 15, 160, 31, 0, 0, 2,
+ 0, 0, 0, 144, 2, 8,
+ 15, 160, 66, 0, 0, 3,
+ 0, 0, 15, 128, 0, 0,
+ 228, 176, 0, 8, 228, 160,
+ 66, 0, 0, 3, 1, 0,
+ 15, 128, 1, 0, 228, 176,
+ 1, 8, 228, 160, 66, 0,
+ 0, 3, 2, 0, 15, 128,
+ 2, 0, 228, 176, 2, 8,
+ 228, 160, 5, 0, 0, 3,
+ 0, 0, 1, 128, 0, 0,
+ 0, 128, 5, 0, 0, 160,
+ 4, 0, 0, 4, 0, 0,
+ 1, 128, 0, 0, 255, 128,
+ 5, 0, 85, 160, 0, 0,
+ 0, 128, 5, 0, 0, 3,
+ 1, 0, 1, 128, 1, 0,
+ 0, 128, 5, 0, 0, 160,
+ 4, 0, 0, 4, 0, 0,
+ 2, 128, 1, 0, 255, 128,
+ 5, 0, 85, 160, 1, 0,
+ 0, 128, 5, 0, 0, 3,
+ 1, 0, 1, 128, 2, 0,
+ 0, 128, 5, 0, 0, 160,
+ 4, 0, 0, 4, 0, 0,
+ 4, 128, 2, 0, 255, 128,
+ 5, 0, 85, 160, 1, 0,
+ 0, 128, 1, 0, 0, 2,
+ 0, 0, 8, 128, 4, 0,
+ 0, 160, 9, 0, 0, 3,
+ 1, 0, 1, 128, 0, 0,
+ 228, 128, 0, 0, 228, 160,
+ 9, 0, 0, 3, 1, 0,
+ 2, 128, 0, 0, 228, 128,
+ 1, 0, 228, 160, 9, 0,
+ 0, 3, 1, 0, 4, 128,
+ 0, 0, 228, 128, 2, 0,
+ 228, 160, 9, 0, 0, 3,
+ 1, 0, 8, 128, 0, 0,
+ 228, 128, 3, 0, 228, 160,
+ 1, 0, 0, 2, 0, 8,
+ 15, 128, 1, 0, 228, 128,
+ 255, 255, 0, 0
+};
diff --git a/video/out/filter_kernels.c b/video/out/filter_kernels.c
new file mode 100644
index 0000000000..2c2f56ee51
--- /dev/null
+++ b/video/out/filter_kernels.c
@@ -0,0 +1,279 @@
+/*
+ * This file is part of mplayer2.
+ *
+ * Most code for computing the weights is taken from Anti-Grain Geometry (AGG)
+ * (licensed under GPL 2 or later), with modifications.
+ * Copyright (C) 2002-2006 Maxim Shemanarev
+ * http://vector-agg.cvs.sourceforge.net/viewvc/vector-agg/agg-2.5/include/agg_image_filters.h?view=markup
+ *
+ * Also see glumpy (BSD licensed), contains the same code in Python:
+ * http://code.google.com/p/glumpy/source/browse/glumpy/image/filter.py
+ *
+ * Also see: Paul Heckbert's "zoom"
+ *
+ * Also see XBMC: ConvolutionKernels.cpp etc.
+ *
+ * mplayer2 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mplayer2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mplayer2; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "filter_kernels.h"
+
+// NOTE: all filters are separable, symmetric, and are intended for use with
+// a lookup table/texture.
+
+const struct filter_kernel *mp_find_filter_kernel(const char *name)
+{
+ for (const struct filter_kernel *k = mp_filter_kernels; k->name; k++) {
+ if (strcmp(k->name, name) == 0)
+ return k;
+ }
+ return NULL;
+}
+
+// sizes = sorted list of available filter sizes, terminated with size 0
+// inv_scale = source_size / dest_size
+bool mp_init_filter(struct filter_kernel *filter, const int *sizes,
+ double inv_scale)
+{
+ // only downscaling requires widening the filter
+ filter->inv_scale = inv_scale >= 1.0 ? inv_scale : 1.0;
+ double support = filter->radius * filter->inv_scale;
+ int size = ceil(2.0 * support);
+ // round up to smallest available size that's still large enough
+ if (size < sizes[0])
+ size = sizes[0];
+ const int *cursize = sizes;
+ while (size > *cursize && *cursize)
+ cursize++;
+ if (*cursize) {
+ filter->size = *cursize;
+ return true;
+ } else {
+ // The filter doesn't fit - instead of failing completely, use the
+ // largest filter available. This is incorrect, but better than refusing
+ // to do anything.
+ filter->size = cursize[-1];
+ filter->inv_scale = filter->size / 2.0 / filter->radius;
+ return false;
+ }
+}
+
+// Calculate the 1D filtering kernel for N sample points.
+// N = number of samples, which is filter->size
+// The weights will be stored in out_w[0] to out_w[N - 1]
+// f = x0 - abs(x0), subpixel position in the range [0,1) or [0,1].
+void mp_compute_weights(struct filter_kernel *filter, double f, float *out_w)
+{
+ assert(filter->size > 0);
+ double sum = 0;
+ for (int n = 0; n < filter->size; n++) {
+ double x = f - (n - filter->size / 2 + 1);
+ double w = filter->weight(filter, fabs(x) / filter->inv_scale);
+ out_w[n] = w;
+ sum += w;
+ }
+ //normalize
+ for (int n = 0; n < filter->size; n++)
+ out_w[n] /= sum;
+}
+
+// Fill the given array with weights for the range [0.0, 1.0]. The array is
+// interpreted as rectangular array of count * filter->size items.
+void mp_compute_lut(struct filter_kernel *filter, int count, float *out_array)
+{
+ for (int n = 0; n < count; n++) {
+ mp_compute_weights(filter, n / (double)(count - 1),
+ out_array + filter->size * n);
+ }
+}
+
+typedef struct filter_kernel kernel;
+
+static double bilinear(kernel *k, double x)
+{
+ return 1.0 - x;
+}
+
+static double hanning(kernel *k, double x)
+{
+ return 0.5 + 0.5 * cos(M_PI * x);
+}
+
+static double hamming(kernel *k, double x)
+{
+ return 0.54 + 0.46 * cos(M_PI * x);
+}
+
+static double hermite(kernel *k, double x)
+{
+ return (2.0 * x - 3.0) * x * x + 1.0;
+}
+
+static double quadric(kernel *k, double x)
+{
+ // NOTE: glumpy uses 0.75, AGG uses 0.5
+ if (x < 0.5)
+ return 0.75 - x * x;
+ if (x < 1.5)
+ return 0.5 * (x - 1.5) * (x - 1.5);
+ return 0;
+}
+
+static double bc_pow3(double x)
+{
+ return (x <= 0) ? 0 : x * x * x;
+}
+
+static double bicubic(kernel *k, double x)
+{
+ return (1.0/6.0) * ( bc_pow3(x + 2)
+ - 4 * bc_pow3(x + 1)
+ + 6 * bc_pow3(x)
+ - 4 * bc_pow3(x - 1));
+}
+
+static double bessel_i0(double epsilon, double x)
+{
+ double sum = 1;
+ double y = x * x / 4;
+ double t = y;
+ for (int i = 2; t > epsilon; i++) {
+ sum += t;
+ t *= y / (i * i);
+ }
+ return sum;
+}
+
+static double kaiser(kernel *k, double x)
+{
+ double a = k->params[0];
+ double b = k->params[1];
+ double epsilon = 1e-12;
+ double i0a = 1 / bessel_i0(epsilon, b);
+ return bessel_i0(epsilon, a * sqrt(1 - x * x)) * i0a;
+}
+
+static double catmull_rom(kernel *k, double x)
+{
+ if (x < 1.0)
+ return 0.5 * (2.0 + x * x * (-5.0 + x * 3.0));
+ if (x < 2.0)
+ return 0.5 * (4.0 + x * (-8.0 + x * (5.0 - x)));
+ return 0;
+}
+
+// Mitchell-Netravali
+static double mitchell(kernel *k, double x)
+{
+ double b = k->params[0];
+ double c = k->params[1];
+ double
+ p0 = (6.0 - 2.0 * b) / 6.0,
+ p2 = (-18.0 + 12.0 * b + 6.0 * c) / 6.0,
+ p3 = (12.0 - 9.0 * b - 6.0 * c) / 6.0,
+ q0 = (8.0 * b + 24.0 * c) / 6.0,
+ q1 = (-12.0 * b - 48.0 * c) / 6.0,
+ q2 = (6.0 * b + 30.0 * c) / 6.0,
+ q3 = (-b - 6.0 * c) / 6.0;
+ if (x < 1.0)
+ return p0 + x * x * (p2 + x * p3);
+ if (x < 2.0)
+ return q0 + x * (q1 + x * (q2 + x * q3));
+ return 0;
+}
+
+static double spline16(kernel *k, double x)
+{
+ if (x < 1.0)
+ return ((x - 9.0/5.0 ) * x - 1.0/5.0 ) * x + 1.0;
+ return ((-1.0/3.0 * (x-1) + 4.0/5.0) * (x-1) - 7.0/15.0 ) * (x-1);
+}
+
+static double spline36(kernel *k, double x)
+{
+ if(x < 1.0)
+ return ((13.0/11.0 * x - 453.0/209.0) * x - 3.0/209.0) * x + 1.0;
+ if(x < 2.0)
+ return ((-6.0/11.0 * (x - 1) + 270.0/209.0) * (x - 1) - 156.0/209.0)
+ * (x - 1);
+ return ((1.0/11.0 * (x - 2) - 45.0/209.0) * (x - 2) + 26.0/209.0)
+ * (x - 2);
+}
+
+static double gaussian(kernel *k, double x)
+{
+ return exp(-2.0 * x * x) * sqrt(2.0 / M_PI);
+}
+
+static double sinc(kernel *k, double x)
+{
+ if (x == 0.0)
+ return 1.0;
+ double pix = M_PI * x;
+ return sin(pix) / pix;
+}
+
+static double lanczos(kernel *k, double x)
+{
+ double radius = k->size / 2;
+ if (x < -radius || x > radius)
+ return 0;
+ if (x == 0)
+ return 1;
+ double pix = M_PI * x;
+ return radius * sin(pix) * sin(pix / radius) / (pix * pix);
+}
+
+static double blackman(kernel *k, double x)
+{
+ double radius = k->size / 2;
+ if (x == 0.0)
+ return 1.0;
+ if (x > radius)
+ return 0.0;
+ x *= M_PI;
+ double xr = x / radius;
+ return (sin(x) / x) * (0.42 + 0.5 * cos(xr) + 0.08 * cos(2 * xr));
+}
+
+const struct filter_kernel mp_filter_kernels[] = {
+ {"bilinear_slow", 1, bilinear},
+ {"hanning", 1, hanning},
+ {"hamming", 1, hamming},
+ {"hermite", 1, hermite},
+ {"quadric", 1.5, quadric},
+ {"bicubic", 2, bicubic},
+ {"kaiser", 1, kaiser, .params = {6.33, 6.33} },
+ {"catmull_rom", 2, catmull_rom},
+ {"mitchell", 2, mitchell, .params = {1.0/3.0, 1.0/3.0} },
+ {"spline16", 2, spline16},
+ {"spline36", 3, spline36},
+ {"gaussian", 2, gaussian},
+ {"sinc2", 2, sinc},
+ {"sinc3", 3, sinc},
+ {"sinc4", 4, sinc},
+ {"lanczos2", 2, lanczos},
+ {"lanczos3", 3, lanczos},
+ {"lanczos4", 4, lanczos},
+ {"blackman2", 2, blackman},
+ {"blackman3", 3, blackman},
+ {"blackman4", 4, blackman},
+ {0}
+};
diff --git a/video/out/filter_kernels.h b/video/out/filter_kernels.h
new file mode 100644
index 0000000000..46a392c40a
--- /dev/null
+++ b/video/out/filter_kernels.h
@@ -0,0 +1,45 @@
+/*
+ * This file is part of mplayer2.
+ *
+ * mplayer2 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mplayer2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mplayer2; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_FILTER_KERNELS_H
+#define MPLAYER_FILTER_KERNELS_H
+
+#include <stdbool.h>
+
+struct filter_kernel {
+ const char *name;
+ double radius;
+ double (*weight)(struct filter_kernel *kernel, double x);
+
+ // The filter params can be changed at runtime. Only used by some filters.
+ float params[2];
+ // The following values are set by mp_init_filter() at runtime.
+ // Number of coefficients; equals the rounded up radius multiplied with 2.
+ int size;
+ double inv_scale;
+};
+
+extern const struct filter_kernel mp_filter_kernels[];
+
+const struct filter_kernel *mp_find_filter_kernel(const char *name);
+bool mp_init_filter(struct filter_kernel *filter, const int *sizes,
+ double scale);
+void mp_compute_weights(struct filter_kernel *filter, double f, float *out_w);
+void mp_compute_lut(struct filter_kernel *filter, int count, float *out_array);
+
+#endif /* MPLAYER_FILTER_KERNELS_H */
diff --git a/video/out/geometry.c b/video/out/geometry.c
new file mode 100644
index 0000000000..941528aea9
--- /dev/null
+++ b/video/out/geometry.c
@@ -0,0 +1,112 @@
+/*
+ * copyright (C) 2002 Mark Zealey <mark@zealos.org>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include "geometry.h"
+#include "mp_msg.h"
+
+/* A string of the form [WxH][+X+Y] or xpos[%]:ypos[%] */
+char *vo_geometry;
+// set when either width or height is changed
+int geometry_wh_changed;
+int geometry_xy_changed;
+
+// xpos,ypos: position of the left upper corner
+// widw,widh: width and height of the window
+// scrw,scrh: width and height of the current screen
+int geometry(int *xpos, int *ypos, int *widw, int *widh, int scrw, int scrh)
+{
+ if(vo_geometry != NULL) {
+ char xsign[2], ysign[2], dummy[2];
+ int width, height, xoff, yoff, xper, yper;
+ int i;
+ int ok = 0;
+ for (i = 0; !ok && i < 9; i++) {
+ width = height = xoff = yoff = xper = yper = INT_MIN;
+ strcpy(xsign, "+");
+ strcpy(ysign, "+");
+ switch (i) {
+ case 0:
+ ok = sscanf(vo_geometry, "%ix%i%1[+-]%i%1[+-]%i%c",
+ &width, &height, xsign, &xoff, ysign,
+ &yoff, dummy) == 6;
+ break;
+ case 1:
+ ok = sscanf(vo_geometry, "%ix%i%c", &width, &height, dummy) == 2;
+ break;
+ case 2:
+ ok = sscanf(vo_geometry, "%1[+-]%i%1[+-]%i%c",
+ xsign, &xoff, ysign, &yoff, dummy) == 4;
+ break;
+ case 3:
+ ok = sscanf(vo_geometry, "%i%%:%i%1[%]%c", &xper, &yper, dummy, dummy) == 3;
+ break;
+ case 4:
+ ok = sscanf(vo_geometry, "%i:%i%1[%]%c", &xoff, &yper, dummy, dummy) == 3;
+ break;
+ case 5:
+ ok = sscanf(vo_geometry, "%i%%:%i%c", &xper, &yoff, dummy) == 2;
+ break;
+ case 6:
+ ok = sscanf(vo_geometry, "%i:%i%c", &xoff, &yoff, dummy) == 2;
+ break;
+ case 7:
+ ok = sscanf(vo_geometry, "%i%1[%]%c", &xper, dummy, dummy) == 2;
+ break;
+ case 8:
+ ok = sscanf(vo_geometry, "%i%c", &xoff, dummy) == 1;
+ break;
+ }
+ }
+ if (!ok) {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "-geometry must be in [WxH][[+-]X[+-]Y] | [X[%%]:[Y[%%]]] format, incorrect (%s)\n", vo_geometry);
+ return 0;
+ }
+
+ mp_msg(MSGT_VO, MSGL_V,"geometry window parameter: widw: %i,"
+ " widh: %i, scrw: %i, scrh: %i\n",*widw, *widh, scrw, scrh);
+
+ mp_msg(MSGT_VO, MSGL_V,"geometry set to width: %i,"
+ "height: %i, xoff: %s%i, yoff: %s%i, xper: %i, yper: %i\n",
+ width, height, xsign, xoff, ysign, yoff, xper, yper);
+
+ if (width > 0) *widw = width;
+ if (height > 0) *widh = height;
+
+ if(xoff != INT_MIN && xsign[0] == '-') xoff = scrw - *widw - xoff;
+ if(yoff != INT_MIN && ysign[0] == '-') yoff = scrh - *widh - yoff;
+ if(xper >= 0 && xper <= 100) xoff = (scrw - *widw) * ((float)xper / 100.0);
+ if(yper >= 0 && yper <= 100) yoff = (scrh - *widh) * ((float)yper / 100.0);
+
+ mp_msg(MSGT_VO, MSGL_V,"geometry set to width: %i,"
+ "height: %i, xoff: %i, yoff: %i, xper: %i, yper: %i\n",
+ width, height, xoff, yoff, xper, yper);
+
+ if (xoff != INT_MIN && xpos) *xpos = xoff;
+ if (yoff != INT_MIN && ypos) *ypos = yoff;
+
+ geometry_wh_changed = width > 0 || height > 0;
+ geometry_xy_changed = xoff != INT_MIN || yoff != INT_MIN;
+ }
+ return 1;
+}
diff --git a/video/out/geometry.h b/video/out/geometry.h
new file mode 100644
index 0000000000..77868a5aad
--- /dev/null
+++ b/video/out/geometry.h
@@ -0,0 +1,29 @@
+/*
+ * copyright (C) 2002 Mark Zealey <mark@zealos.org>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_GEOMETRY_H
+#define MPLAYER_GEOMETRY_H
+
+extern char *vo_geometry;
+extern int geometry_wh_changed;
+extern int geometry_xy_changed;
+int geometry(int *xpos, int *ypos, int *widw, int *widh, int scrw, int scrh);
+
+#endif /* MPLAYER_GEOMETRY_H */
diff --git a/video/out/gl_common.c b/video/out/gl_common.c
new file mode 100644
index 0000000000..80db2eacc4
--- /dev/null
+++ b/video/out/gl_common.c
@@ -0,0 +1,2654 @@
+/*
+ * common OpenGL routines
+ *
+ * copyleft (C) 2005-2010 Reimar Döffinger <Reimar.Doeffinger@gmx.de>
+ * Special thanks go to the xine team and Matthias Hopf, whose video_out_opengl.c
+ * gave me lots of good ideas.
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * You can alternatively redistribute this file 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.
+ */
+
+/**
+ * \file gl_common.c
+ * \brief OpenGL helper functions used by vo_gl.c and vo_gl2.c
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <math.h>
+#include <assert.h>
+#include "talloc.h"
+#include "gl_common.h"
+#include "csputils.h"
+#include "aspect.h"
+#include "pnm_loader.h"
+#include "options.h"
+#include "sub/sub.h"
+#include "bitmap_packer.h"
+
+//! \defgroup glgeneral OpenGL general helper functions
+
+// GLU has this as gluErrorString (we don't use GLU, as it is legacy-OpenGL)
+static const char *gl_error_to_string(GLenum error)
+{
+ switch (error) {
+ case GL_INVALID_ENUM: return "INVALID_ENUM";
+ case GL_INVALID_VALUE: return "INVALID_VALUE";
+ case GL_INVALID_OPERATION: return "INVALID_OPERATION";
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ return "INVALID_FRAMEBUFFER_OPERATION";
+ case GL_OUT_OF_MEMORY: return "OUT_OF_MEMORY";
+ default: return "unknown";
+ }
+}
+
+void glCheckError(GL *gl, const char *info)
+{
+ for (;;) {
+ GLenum error = gl->GetError();
+ if (error == GL_NO_ERROR)
+ break;
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] %s: OpenGL error %s.\n", info,
+ gl_error_to_string(error));
+ }
+}
+
+//! \defgroup glcontext OpenGL context management helper functions
+
+//! \defgroup gltexture OpenGL texture handling helper functions
+
+//! \defgroup glconversion OpenGL conversion helper functions
+
+/**
+ * \brief adjusts the GL_UNPACK_ALIGNMENT to fit the stride.
+ * \param stride number of bytes per line for which alignment should fit.
+ * \ingroup glgeneral
+ */
+void glAdjustAlignment(GL *gl, int stride)
+{
+ GLint gl_alignment;
+ if (stride % 8 == 0)
+ gl_alignment = 8;
+ else if (stride % 4 == 0)
+ gl_alignment = 4;
+ else if (stride % 2 == 0)
+ gl_alignment = 2;
+ else
+ gl_alignment = 1;
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, gl_alignment);
+ gl->PixelStorei(GL_PACK_ALIGNMENT, gl_alignment);
+}
+
+//! always return this format as internal texture format in glFindFormat
+#define TEXTUREFORMAT_ALWAYS GL_RGB8
+#undef TEXTUREFORMAT_ALWAYS
+
+/**
+ * \brief find the OpenGL settings coresponding to format.
+ *
+ * All parameters may be NULL.
+ * \param fmt MPlayer format to analyze.
+ * \param bpp [OUT] bits per pixel of that format.
+ * \param gl_texfmt [OUT] internal texture format that fits the
+ * image format, not necessarily the best for performance.
+ * \param gl_format [OUT] OpenGL format for this image format.
+ * \param gl_type [OUT] OpenGL type for this image format.
+ * \return 1 if format is supported by OpenGL, 0 if not.
+ * \ingroup gltexture
+ */
+int glFindFormat(uint32_t fmt, int have_texture_rg, int *bpp, GLint *gl_texfmt,
+ GLenum *gl_format, GLenum *gl_type)
+{
+ int supported = 1;
+ int dummy1;
+ GLenum dummy2;
+ GLint dummy3;
+ if (!bpp)
+ bpp = &dummy1;
+ if (!gl_texfmt)
+ gl_texfmt = &dummy3;
+ if (!gl_format)
+ gl_format = &dummy2;
+ if (!gl_type)
+ gl_type = &dummy2;
+
+ if (mp_get_chroma_shift(fmt, NULL, NULL, NULL)) {
+ // reduce the possible cases a bit
+ if (IMGFMT_IS_YUVP16_LE(fmt))
+ fmt = IMGFMT_420P16_LE;
+ else if (IMGFMT_IS_YUVP16_BE(fmt))
+ fmt = IMGFMT_420P16_BE;
+ else
+ fmt = IMGFMT_YV12;
+ }
+
+ *bpp = IMGFMT_IS_BGR(fmt) ? IMGFMT_BGR_DEPTH(fmt) : IMGFMT_RGB_DEPTH(fmt);
+ *gl_texfmt = 3;
+ switch (fmt) {
+ case IMGFMT_RGB48NE:
+ *gl_format = GL_RGB;
+ *gl_type = GL_UNSIGNED_SHORT;
+ break;
+ case IMGFMT_RGB24:
+ *gl_format = GL_RGB;
+ *gl_type = GL_UNSIGNED_BYTE;
+ break;
+ case IMGFMT_RGBA:
+ *gl_texfmt = 4;
+ *gl_format = GL_RGBA;
+ *gl_type = GL_UNSIGNED_BYTE;
+ break;
+ case IMGFMT_420P16:
+ supported = 0; // no native YUV support
+ *gl_texfmt = have_texture_rg ? GL_R16 : GL_LUMINANCE16;
+ *bpp = 16;
+ *gl_format = have_texture_rg ? GL_RED : GL_LUMINANCE;
+ *gl_type = GL_UNSIGNED_SHORT;
+ break;
+ case IMGFMT_YV12:
+ supported = 0; // no native YV12 support
+ case IMGFMT_Y800:
+ case IMGFMT_Y8:
+ *gl_texfmt = 1;
+ *bpp = 8;
+ *gl_format = GL_LUMINANCE;
+ *gl_type = GL_UNSIGNED_BYTE;
+ break;
+ case IMGFMT_UYVY:
+ // IMGFMT_YUY2 would be more logical for the _REV format,
+ // but gives clearly swapped colors.
+ case IMGFMT_YVYU:
+ *gl_texfmt = GL_YCBCR_MESA;
+ *bpp = 16;
+ *gl_format = GL_YCBCR_MESA;
+ *gl_type = fmt == IMGFMT_UYVY ? GL_UNSIGNED_SHORT_8_8 : GL_UNSIGNED_SHORT_8_8_REV;
+ break;
+#if 0
+ // we do not support palettized formats, although the format the
+ // swscale produces works
+ case IMGFMT_RGB8:
+ *gl_format = GL_RGB;
+ *gl_type = GL_UNSIGNED_BYTE_2_3_3_REV;
+ break;
+#endif
+ case IMGFMT_RGB15:
+ *gl_format = GL_RGBA;
+ *gl_type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
+ break;
+ case IMGFMT_RGB16:
+ *gl_format = GL_RGB;
+ *gl_type = GL_UNSIGNED_SHORT_5_6_5_REV;
+ break;
+#if 0
+ case IMGFMT_BGR8:
+ // special case as red and blue have a different number of bits.
+ // GL_BGR and GL_UNSIGNED_BYTE_3_3_2 isn't supported at least
+ // by nVidia drivers, and in addition would give more bits to
+ // blue than to red, which isn't wanted
+ *gl_format = GL_RGB;
+ *gl_type = GL_UNSIGNED_BYTE_3_3_2;
+ break;
+#endif
+ case IMGFMT_BGR15:
+ *gl_format = GL_BGRA;
+ *gl_type = GL_UNSIGNED_SHORT_1_5_5_5_REV;
+ break;
+ case IMGFMT_BGR16:
+ *gl_format = GL_RGB;
+ *gl_type = GL_UNSIGNED_SHORT_5_6_5;
+ break;
+ case IMGFMT_BGR24:
+ *gl_format = GL_BGR;
+ *gl_type = GL_UNSIGNED_BYTE;
+ break;
+ case IMGFMT_BGRA:
+ *gl_texfmt = 4;
+ *gl_format = GL_BGRA;
+ *gl_type = GL_UNSIGNED_BYTE;
+ break;
+ default:
+ *gl_texfmt = 4;
+ *gl_format = GL_RGBA;
+ *gl_type = GL_UNSIGNED_BYTE;
+ supported = 0;
+ }
+#ifdef TEXTUREFORMAT_ALWAYS
+ *gl_texfmt = TEXTUREFORMAT_ALWAYS;
+#endif
+ return supported;
+}
+
+struct feature {
+ int id;
+ const char *name;
+};
+
+static const struct feature features[] = {
+ {MPGL_CAP_GL, "Basic OpenGL"},
+ {MPGL_CAP_GL_LEGACY, "Legacy OpenGL"},
+ {MPGL_CAP_GL2, "OpenGL 2.0"},
+ {MPGL_CAP_GL21, "OpenGL 2.1"},
+ {MPGL_CAP_GL3, "OpenGL 3.0"},
+ {MPGL_CAP_FB, "Framebuffers"},
+ {MPGL_CAP_VAO, "VAOs"},
+ {MPGL_CAP_SRGB_TEX, "sRGB textures"},
+ {MPGL_CAP_SRGB_FB, "sRGB framebuffers"},
+ {MPGL_CAP_FLOAT_TEX, "Float textures"},
+ {MPGL_CAP_TEX_RG, "RG textures"},
+ {MPGL_CAP_NO_SW, "NO_SW"},
+ {0},
+};
+
+static void list_features(int set, int msgl, bool invert)
+{
+ for (const struct feature *f = &features[0]; f->id; f++) {
+ if (invert == !(f->id & set))
+ mp_msg(MSGT_VO, msgl, " [%s]", f->name);
+ }
+ mp_msg(MSGT_VO, msgl, "\n");
+}
+
+// This guesses if the current GL context is a suspected software renderer.
+static bool is_software_gl(GL *gl)
+{
+ const char *renderer = gl->GetString(GL_RENDERER);
+ const char *vendor = gl->GetString(GL_VENDOR);
+ return !(renderer && vendor) ||
+ strcmp(renderer, "Software Rasterizer") == 0 ||
+ strstr(renderer, "llvmpipe") ||
+ strcmp(vendor, "Microsoft Corporation") == 0 ||
+ strcmp(renderer, "Mesa X11") == 0;
+}
+
+#ifdef HAVE_LIBDL
+#include <dlfcn.h>
+#endif
+/**
+ * \brief find address of a linked function
+ * \param s name of function to find
+ * \return address of function or NULL if not found
+ */
+static void *getdladdr(const char *s)
+{
+ void *ret = NULL;
+#ifdef HAVE_LIBDL
+ void *handle = dlopen(NULL, RTLD_LAZY);
+ if (!handle)
+ return NULL;
+ ret = dlsym(handle, s);
+ dlclose(handle);
+#endif
+ return ret;
+}
+
+#define FN_OFFS(name) offsetof(GL, name)
+
+// Define the function with a "hard" reference to the function as fallback.
+// (This requires linking with a compatible OpenGL library.)
+#define DEF_FN_HARD(name) {FN_OFFS(name), {"gl" # name}, gl ## name}
+
+#define DEF_FN(name) {FN_OFFS(name), {"gl" # name}}
+#define DEF_FN_NAMES(name, ...) {FN_OFFS(name), {__VA_ARGS__}}
+
+struct gl_function {
+ ptrdiff_t offset;
+ char *funcnames[7];
+ void *fallback;
+};
+
+struct gl_functions {
+ const char *extension; // introduced with this extension in any version
+ int provides; // bitfield of MPGL_CAP_* constants
+ int ver_core; // introduced as required function
+ int ver_removed; // removed as required function (no replacement)
+ bool partial_ok; // loading only some functions is ok
+ struct gl_function *functions;
+};
+
+#define MAX_FN_COUNT 50 // max functions per gl_functions section
+
+struct gl_functions gl_functions[] = {
+ // GL functions which are always available anywhere at least since 1.1
+ {
+ .ver_core = MPGL_VER(1, 1),
+ .provides = MPGL_CAP_GL,
+ .functions = (struct gl_function[]) {
+ DEF_FN_HARD(Viewport),
+ DEF_FN_HARD(Clear),
+ DEF_FN_HARD(GenTextures),
+ DEF_FN_HARD(DeleteTextures),
+ DEF_FN_HARD(TexEnvi),
+ DEF_FN_HARD(ClearColor),
+ DEF_FN_HARD(Enable),
+ DEF_FN_HARD(Disable),
+ DEF_FN_HARD(DrawBuffer),
+ DEF_FN_HARD(DepthMask),
+ DEF_FN_HARD(BlendFunc),
+ DEF_FN_HARD(Flush),
+ DEF_FN_HARD(Finish),
+ DEF_FN_HARD(PixelStorei),
+ DEF_FN_HARD(TexImage1D),
+ DEF_FN_HARD(TexImage2D),
+ DEF_FN_HARD(TexSubImage2D),
+ DEF_FN_HARD(GetTexImage),
+ DEF_FN_HARD(TexParameteri),
+ DEF_FN_HARD(TexParameterf),
+ DEF_FN_HARD(TexParameterfv),
+ DEF_FN_HARD(GetIntegerv),
+ DEF_FN_HARD(GetBooleanv),
+ DEF_FN_HARD(ColorMask),
+ DEF_FN_HARD(ReadPixels),
+ DEF_FN_HARD(ReadBuffer),
+ DEF_FN_HARD(DrawArrays),
+ DEF_FN_HARD(GetString),
+ DEF_FN_HARD(GetError),
+ {0}
+ },
+ },
+ // GL 2.0-3.x functions
+ {
+ .ver_core = MPGL_VER(2, 0),
+ .provides = MPGL_CAP_GL2,
+ .functions = (struct gl_function[]) {
+ DEF_FN(GenBuffers),
+ DEF_FN(DeleteBuffers),
+ DEF_FN(BindBuffer),
+ DEF_FN(MapBuffer),
+ DEF_FN(UnmapBuffer),
+ DEF_FN(BufferData),
+ DEF_FN(ActiveTexture),
+ DEF_FN(BindTexture),
+ DEF_FN(GetAttribLocation),
+ DEF_FN(EnableVertexAttribArray),
+ DEF_FN(DisableVertexAttribArray),
+ DEF_FN(VertexAttribPointer),
+ DEF_FN(UseProgram),
+ DEF_FN(GetUniformLocation),
+ DEF_FN(CompileShader),
+ DEF_FN(CreateProgram),
+ DEF_FN(CreateShader),
+ DEF_FN(ShaderSource),
+ DEF_FN(LinkProgram),
+ DEF_FN(AttachShader),
+ DEF_FN(DeleteShader),
+ DEF_FN(DeleteProgram),
+ DEF_FN(GetShaderInfoLog),
+ DEF_FN(GetShaderiv),
+ DEF_FN(GetProgramInfoLog),
+ DEF_FN(GetProgramiv),
+ DEF_FN(BindAttribLocation),
+ DEF_FN(Uniform1f),
+ DEF_FN(Uniform2f),
+ DEF_FN(Uniform3f),
+ DEF_FN(Uniform1i),
+ DEF_FN(UniformMatrix3fv),
+ DEF_FN(TexImage3D),
+ {0},
+ },
+ },
+ // GL 2.1-3.x functions (also: GLSL 120 shaders)
+ {
+ .ver_core = MPGL_VER(2, 1),
+ .provides = MPGL_CAP_GL21,
+ .functions = (struct gl_function[]) {
+ DEF_FN(UniformMatrix4x3fv),
+ {0}
+ },
+ },
+ // GL 3.x core only functions.
+ {
+ .ver_core = MPGL_VER(3, 0),
+ .provides = MPGL_CAP_GL3 | MPGL_CAP_SRGB_TEX | MPGL_CAP_SRGB_FB,
+ .functions = (struct gl_function[]) {
+ DEF_FN(GetStringi),
+ {0}
+ },
+ },
+ // Framebuffers, extension in GL 2.x, core in GL 3.x core.
+ {
+ .ver_core = MPGL_VER(3, 0),
+ .extension = "GL_ARB_framebuffer_object",
+ .provides = MPGL_CAP_FB,
+ .functions = (struct gl_function[]) {
+ DEF_FN(BindFramebuffer),
+ DEF_FN(GenFramebuffers),
+ DEF_FN(DeleteFramebuffers),
+ DEF_FN(CheckFramebufferStatus),
+ DEF_FN(FramebufferTexture2D),
+ {0}
+ },
+ },
+ // Framebuffers, alternative extension name.
+ {
+ .ver_removed = MPGL_VER(3, 0), // don't touch these fn names in 3.x
+ .extension = "GL_EXT_framebuffer_object",
+ .provides = MPGL_CAP_FB,
+ .functions = (struct gl_function[]) {
+ DEF_FN_NAMES(BindFramebuffer, "glBindFramebufferEXT"),
+ DEF_FN_NAMES(GenFramebuffers, "glGenFramebuffersEXT"),
+ DEF_FN_NAMES(DeleteFramebuffers, "glDeleteFramebuffersEXT"),
+ DEF_FN_NAMES(CheckFramebufferStatus, "glCheckFramebufferStatusEXT"),
+ DEF_FN_NAMES(FramebufferTexture2D, "glFramebufferTexture2DEXT"),
+ {0}
+ },
+ },
+ // VAOs, extension in GL 2.x, core in GL 3.x core.
+ {
+ .ver_core = MPGL_VER(3, 0),
+ .extension = "GL_ARB_vertex_array_object",
+ .provides = MPGL_CAP_VAO,
+ .functions = (struct gl_function[]) {
+ DEF_FN(GenVertexArrays),
+ DEF_FN(BindVertexArray),
+ DEF_FN(DeleteVertexArrays),
+ {0}
+ }
+ },
+ // sRGB textures, extension in GL 2.x, core in GL 3.x core.
+ {
+ .ver_core = MPGL_VER(3, 0),
+ .extension = "GL_EXT_texture_sRGB",
+ .provides = MPGL_CAP_SRGB_TEX,
+ .functions = (struct gl_function[]) {{0}},
+ },
+ // sRGB framebuffers, extension in GL 2.x, core in GL 3.x core.
+ {
+ .ver_core = MPGL_VER(3, 0),
+ .extension = "GL_EXT_framebuffer_sRGB",
+ .provides = MPGL_CAP_SRGB_FB,
+ .functions = (struct gl_function[]) {{0}},
+ },
+ // Float textures, extension in GL 2.x, core in GL 3.x core.
+ {
+ .ver_core = MPGL_VER(3, 0),
+ .extension = "GL_ARB_texture_float",
+ .provides = MPGL_CAP_FLOAT_TEX,
+ .functions = (struct gl_function[]) {{0}},
+ },
+ // GL_RED / GL_RG textures, extension in GL 2.x, core in GL 3.x core.
+ {
+ .ver_core = MPGL_VER(3, 0),
+ .extension = "GL_ARB_texture_rg",
+ .provides = MPGL_CAP_TEX_RG,
+ .functions = (struct gl_function[]) {{0}},
+ },
+ // Swap control, always an OS specific extension
+ {
+ .extension = "_swap_control",
+ .functions = (struct gl_function[]) {
+ DEF_FN_NAMES(SwapInterval, "glXSwapIntervalSGI", "glXSwapInterval",
+ "wglSwapIntervalSGI", "wglSwapInterval",
+ "wglSwapIntervalEXT"),
+ {0}
+ },
+ },
+ // GL legacy functions in GL 1.x - 2.x, removed from GL 3.x
+ {
+ .ver_core = MPGL_VER(1, 1),
+ .ver_removed = MPGL_VER(3, 0),
+ .provides = MPGL_CAP_GL_LEGACY,
+ .functions = (struct gl_function[]) {
+ DEF_FN_HARD(Begin),
+ DEF_FN_HARD(End),
+ DEF_FN_HARD(MatrixMode),
+ DEF_FN_HARD(LoadIdentity),
+ DEF_FN_HARD(Translated),
+ DEF_FN_HARD(Scaled),
+ DEF_FN_HARD(Ortho),
+ DEF_FN_HARD(PushMatrix),
+ DEF_FN_HARD(PopMatrix),
+ DEF_FN_HARD(GenLists),
+ DEF_FN_HARD(DeleteLists),
+ DEF_FN_HARD(NewList),
+ DEF_FN_HARD(EndList),
+ DEF_FN_HARD(CallList),
+ DEF_FN_HARD(CallLists),
+ DEF_FN_HARD(Color4ub),
+ DEF_FN_HARD(Color4f),
+ DEF_FN_HARD(TexCoord2f),
+ DEF_FN_HARD(TexCoord2fv),
+ DEF_FN_HARD(Vertex2f),
+ DEF_FN_HARD(VertexPointer),
+ DEF_FN_HARD(ColorPointer),
+ DEF_FN_HARD(TexCoordPointer),
+ DEF_FN_HARD(EnableClientState),
+ DEF_FN_HARD(DisableClientState),
+ {0}
+ },
+ },
+ // Loading of old extensions, which are later added to GL 2.0.
+ // NOTE: actually we should be checking the extension strings: the OpenGL
+ // library could provide an entry point, but not implement it.
+ // But the previous code didn't do that, and nobody ever complained.
+ {
+ .ver_removed = MPGL_VER(2, 1),
+ .partial_ok = true,
+ .functions = (struct gl_function[]) {
+ DEF_FN_NAMES(GenBuffers, "glGenBuffers", "glGenBuffersARB"),
+ DEF_FN_NAMES(DeleteBuffers, "glDeleteBuffers", "glDeleteBuffersARB"),
+ DEF_FN_NAMES(BindBuffer, "glBindBuffer", "glBindBufferARB"),
+ DEF_FN_NAMES(MapBuffer, "glMapBuffer", "glMapBufferARB"),
+ DEF_FN_NAMES(UnmapBuffer, "glUnmapBuffer", "glUnmapBufferARB"),
+ DEF_FN_NAMES(BufferData, "glBufferData", "glBufferDataARB"),
+ DEF_FN_NAMES(ActiveTexture, "glActiveTexture", "glActiveTextureARB"),
+ DEF_FN_NAMES(BindTexture, "glBindTexture", "glBindTextureARB", "glBindTextureEXT"),
+ DEF_FN_NAMES(MultiTexCoord2f, "glMultiTexCoord2f", "glMultiTexCoord2fARB"),
+ DEF_FN_NAMES(TexImage3D, "glTexImage3D"),
+ {0}
+ },
+ },
+ // Ancient ARB shaders.
+ {
+ .extension = "_program",
+ .ver_removed = MPGL_VER(3, 0),
+ .functions = (struct gl_function[]) {
+ DEF_FN_NAMES(GenPrograms, "glGenProgramsARB"),
+ DEF_FN_NAMES(DeletePrograms, "glDeleteProgramsARB"),
+ DEF_FN_NAMES(BindProgram, "glBindProgramARB"),
+ DEF_FN_NAMES(ProgramString, "glProgramStringARB"),
+ DEF_FN_NAMES(GetProgramivARB, "glGetProgramivARB"),
+ DEF_FN_NAMES(ProgramEnvParameter4f, "glProgramEnvParameter4fARB"),
+ {0}
+ },
+ },
+ // Ancient ATI extensions.
+ {
+ .extension = "ATI_fragment_shader",
+ .ver_removed = MPGL_VER(3, 0),
+ .functions = (struct gl_function[]) {
+ DEF_FN_NAMES(BeginFragmentShader, "glBeginFragmentShaderATI"),
+ DEF_FN_NAMES(EndFragmentShader, "glEndFragmentShaderATI"),
+ DEF_FN_NAMES(SampleMap, "glSampleMapATI"),
+ DEF_FN_NAMES(ColorFragmentOp2, "glColorFragmentOp2ATI"),
+ DEF_FN_NAMES(ColorFragmentOp3, "glColorFragmentOp3ATI"),
+ DEF_FN_NAMES(SetFragmentShaderConstant, "glSetFragmentShaderConstantATI"),
+ {0}
+ },
+ },
+};
+
+#undef FN_OFFS
+#undef DEF_FN_HARD
+#undef DEF_FN
+#undef DEF_FN_NAMES
+
+
+/**
+ * \brief find the function pointers of some useful OpenGL extensions
+ * \param getProcAddress function to resolve function names, may be NULL
+ * \param ext2 an extra extension string
+ */
+static void getFunctions(GL *gl, void *(*getProcAddress)(const GLubyte *),
+ const char *ext2, bool gl3)
+{
+ talloc_free_children(gl);
+ *gl = (GL) {
+ .extensions = talloc_strdup(gl, ext2 ? ext2 : ""),
+ };
+
+ if (!getProcAddress)
+ getProcAddress = (void *)getdladdr;
+
+ GLint major = 0, minor = 0;
+ if (gl3) {
+ gl->GetStringi = getProcAddress("glGetStringi");
+ gl->GetIntegerv = getProcAddress("glGetIntegerv");
+
+ if (!(gl->GetStringi && gl->GetIntegerv))
+ return;
+
+ gl->GetIntegerv(GL_MAJOR_VERSION, &major);
+ gl->GetIntegerv(GL_MINOR_VERSION, &minor);
+
+ GLint exts;
+ gl->GetIntegerv(GL_NUM_EXTENSIONS, &exts);
+ for (int n = 0; n < exts; n++) {
+ gl->extensions
+ = talloc_asprintf_append(gl->extensions, " %s",
+ gl->GetStringi(GL_EXTENSIONS, n));
+ }
+ } else {
+ gl->GetString = getProcAddress("glGetString");
+ if (!gl->GetString)
+ gl->GetString = glGetString;
+
+ const char *ext = (char*)gl->GetString(GL_EXTENSIONS);
+ gl->extensions = talloc_asprintf_append(gl->extensions, " %s", ext);
+
+ const char *version = gl->GetString(GL_VERSION);
+ sscanf(version, "%d.%d", &major, &minor);
+ }
+ gl->version = MPGL_VER(major, minor);
+
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Detected OpenGL %d.%d.\n", major, minor);
+ mp_msg(MSGT_VO, MSGL_DBG2, "[gl] Combined OpenGL extensions string:\n%s\n",
+ gl->extensions);
+
+ for (int n = 0; n < sizeof(gl_functions) / sizeof(gl_functions[0]); n++) {
+ struct gl_functions *section = &gl_functions[n];
+
+ // With gl3=false, we could have a legacy context, where functionality
+ // is never removed. (E.g. the context could be at version >= 3.0, but
+ // legacy functions like glBegin still exist and work.)
+ if (gl3 && section->ver_removed && gl->version >= section->ver_removed)
+ continue;
+
+ bool must_exist = section->ver_core && gl->version >= section->ver_core
+ && !section->partial_ok;
+
+ if (!must_exist && section->extension &&
+ !strstr(gl->extensions, section->extension))
+ continue;
+
+ void *loaded[MAX_FN_COUNT] = {0};
+ bool all_loaded = true;
+
+ for (int i = 0; section->functions[i].funcnames[0]; i++) {
+ struct gl_function *fn = &section->functions[i];
+ void *ptr = NULL;
+ for (int x = 0; fn->funcnames[x]; x++) {
+ ptr = getProcAddress((const GLubyte *)fn->funcnames[x]);
+ if (ptr)
+ break;
+ }
+ if (!ptr)
+ ptr = fn->fallback;
+ if (!ptr) {
+ all_loaded = false;
+ if (must_exist) {
+ // Either we or the driver are not conforming to OpenGL.
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] Required function '%s' not "
+ "found.\n", fn->funcnames[0]);
+ talloc_free_children(gl);
+ *gl = (GL) {0};
+ return;
+ }
+ }
+ assert(i < MAX_FN_COUNT);
+ loaded[i] = ptr;
+ }
+
+ if (all_loaded || section->partial_ok) {
+ gl->mpgl_caps |= section->provides;
+ for (int i = 0; section->functions[i].funcnames[0]; i++) {
+ struct gl_function *fn = &section->functions[i];
+ void **funcptr = (void**)(((char*)gl) + fn->offset);
+ if (loaded[i])
+ *funcptr = loaded[i];
+ }
+ }
+ }
+
+ gl->glsl_version = 0;
+ if (gl->version >= MPGL_VER(2, 0))
+ gl->glsl_version = 110;
+ if (gl->version >= MPGL_VER(2, 1))
+ gl->glsl_version = 120;
+ if (gl->version >= MPGL_VER(3, 0))
+ gl->glsl_version = 130;
+ // Specifically needed for OSX (normally we request 3.0 contexts only, but
+ // OSX always creates 3.2 contexts when requesting a core context).
+ if (gl->version >= MPGL_VER(3, 2))
+ gl->glsl_version = 150;
+
+ if (!is_software_gl(gl))
+ gl->mpgl_caps |= MPGL_CAP_NO_SW;
+
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Detected OpenGL features:");
+ list_features(gl->mpgl_caps, MSGL_V, false);
+}
+
+/**
+ * \brief create a texture and set some defaults
+ * \param target texture taget, usually GL_TEXTURE_2D
+ * \param fmt internal texture format
+ * \param format texture host data format
+ * \param type texture host data type
+ * \param filter filter used for scaling, e.g. GL_LINEAR
+ * \param w texture width
+ * \param h texture height
+ * \param val luminance value to fill texture with
+ * \ingroup gltexture
+ */
+void glCreateClearTex(GL *gl, GLenum target, GLenum fmt, GLenum format,
+ GLenum type, GLint filter, int w, int h,
+ unsigned char val)
+{
+ GLfloat fval = (GLfloat)val / 255.0;
+ GLfloat border[4] = {
+ fval, fval, fval, fval
+ };
+ int stride;
+ char *init;
+ if (w == 0)
+ w = 1;
+ if (h == 0)
+ h = 1;
+ stride = w * glFmt2bpp(format, type);
+ if (!stride)
+ return;
+ init = malloc(stride * h);
+ memset(init, val, stride * h);
+ glAdjustAlignment(gl, stride);
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, w);
+ gl->TexImage2D(target, 0, fmt, w, h, 0, format, type, init);
+ gl->TexParameterf(target, GL_TEXTURE_PRIORITY, 1.0);
+ gl->TexParameteri(target, GL_TEXTURE_MIN_FILTER, filter);
+ gl->TexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
+ gl->TexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ gl->TexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ // Border texels should not be used with CLAMP_TO_EDGE
+ // We set a sane default anyway.
+ gl->TexParameterfv(target, GL_TEXTURE_BORDER_COLOR, border);
+ free(init);
+}
+
+static GLint detect_hqtexfmt(GL *gl)
+{
+ const char *extensions = (const char *)gl->GetString(GL_EXTENSIONS);
+ if (strstr(extensions, "_texture_float"))
+ return GL_RGB32F;
+ else if (strstr(extensions, "NV_float_buffer"))
+ return GL_FLOAT_RGB32_NV;
+ return GL_RGB16;
+}
+
+/**
+ * \brief creates a texture from a PPM file
+ * \param target texture taget, usually GL_TEXTURE_2D
+ * \param fmt internal texture format, 0 for default
+ * \param filter filter used for scaling, e.g. GL_LINEAR
+ * \param f file to read PPM from
+ * \param width [out] width of texture
+ * \param height [out] height of texture
+ * \param maxval [out] maxval value from PPM file
+ * \return 0 on error, 1 otherwise
+ * \ingroup gltexture
+ */
+int glCreatePPMTex(GL *gl, GLenum target, GLenum fmt, GLint filter,
+ FILE *f, int *width, int *height, int *maxval)
+{
+ int w, h, m, bpp;
+ GLenum type;
+ uint8_t *data = read_pnm(f, &w, &h, &bpp, &m);
+ GLint hqtexfmt = detect_hqtexfmt(gl);
+ if (!data || (bpp != 3 && bpp != 6)) {
+ free(data);
+ return 0;
+ }
+ if (!fmt) {
+ fmt = bpp == 6 ? hqtexfmt : 3;
+ if (fmt == GL_FLOAT_RGB32_NV && target != GL_TEXTURE_RECTANGLE)
+ fmt = GL_RGB16;
+ }
+ type = bpp == 6 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
+ glCreateClearTex(gl, target, fmt, GL_RGB, type, filter, w, h, 0);
+ glUploadTex(gl, target, GL_RGB, type,
+ data, w * bpp, 0, 0, w, h, 0);
+ free(data);
+ if (width)
+ *width = w;
+ if (height)
+ *height = h;
+ if (maxval)
+ *maxval = m;
+ return 1;
+}
+
+/**
+ * \brief return the number of bytes per pixel for the given format
+ * \param format OpenGL format
+ * \param type OpenGL type
+ * \return bytes per pixel
+ * \ingroup glgeneral
+ *
+ * Does not handle all possible variants, just those used by MPlayer
+ */
+int glFmt2bpp(GLenum format, GLenum type)
+{
+ int component_size = 0;
+ switch (type) {
+ case GL_UNSIGNED_BYTE_3_3_2:
+ case GL_UNSIGNED_BYTE_2_3_3_REV:
+ return 1;
+ case GL_UNSIGNED_SHORT_5_5_5_1:
+ case GL_UNSIGNED_SHORT_1_5_5_5_REV:
+ case GL_UNSIGNED_SHORT_5_6_5:
+ case GL_UNSIGNED_SHORT_5_6_5_REV:
+ return 2;
+ case GL_UNSIGNED_BYTE:
+ component_size = 1;
+ break;
+ case GL_UNSIGNED_SHORT:
+ component_size = 2;
+ break;
+ }
+ switch (format) {
+ case GL_LUMINANCE:
+ case GL_ALPHA:
+ return component_size;
+ case GL_YCBCR_MESA:
+ return 2;
+ case GL_RGB:
+ case GL_BGR:
+ return 3 * component_size;
+ case GL_RGBA:
+ case GL_BGRA:
+ return 4 * component_size;
+ case GL_RED:
+ return component_size;
+ case GL_RG:
+ case GL_LUMINANCE_ALPHA:
+ return 2 * component_size;
+ }
+ abort(); // unknown
+}
+
+/**
+ * \brief upload a texture, handling things like stride and slices
+ * \param target texture target, usually GL_TEXTURE_2D
+ * \param format OpenGL format of data
+ * \param type OpenGL type of data
+ * \param dataptr data to upload
+ * \param stride data stride
+ * \param x x offset in texture
+ * \param y y offset in texture
+ * \param w width of the texture part to upload
+ * \param h height of the texture part to upload
+ * \param slice height of an upload slice, 0 for all at once
+ * \ingroup gltexture
+ */
+void glUploadTex(GL *gl, GLenum target, GLenum format, GLenum type,
+ const void *dataptr, int stride,
+ int x, int y, int w, int h, int slice)
+{
+ const uint8_t *data = dataptr;
+ int y_max = y + h;
+ if (w <= 0 || h <= 0)
+ return;
+ if (slice <= 0)
+ slice = h;
+ if (stride < 0) {
+ data += (h - 1) * stride;
+ stride = -stride;
+ }
+ // this is not always correct, but should work for MPlayer
+ glAdjustAlignment(gl, stride);
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, stride / glFmt2bpp(format, type));
+ for (; y + slice <= y_max; y += slice) {
+ gl->TexSubImage2D(target, 0, x, y, w, slice, format, type, data);
+ data += stride * slice;
+ }
+ if (y < y_max)
+ gl->TexSubImage2D(target, 0, x, y, w, y_max - y, format, type, data);
+}
+
+// Like glUploadTex, but upload a byte array with all elements set to val.
+// If scratch is not NULL, points to a resizeable talloc memory block than can
+// be freely used by the function (for avoiding temporary memory allocations).
+void glClearTex(GL *gl, GLenum target, GLenum format, GLenum type,
+ int x, int y, int w, int h, uint8_t val, void **scratch)
+{
+ int bpp = glFmt2bpp(format, type);
+ int stride = w * bpp;
+ int size = h * stride;
+ if (size < 1)
+ return;
+ void *data = scratch ? *scratch : NULL;
+ if (talloc_get_size(data) < size)
+ data = talloc_realloc(NULL, data, char *, size);
+ memset(data, val, size);
+ glAdjustAlignment(gl, stride);
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, w);
+ gl->TexSubImage2D(target, 0, x, y, w, h, format, type, data);
+ if (scratch) {
+ *scratch = data;
+ } else {
+ talloc_free(data);
+ }
+}
+
+/**
+ * \brief download a texture, handling things like stride and slices
+ * \param target texture target, usually GL_TEXTURE_2D
+ * \param format OpenGL format of data
+ * \param type OpenGL type of data
+ * \param dataptr destination memory for download
+ * \param stride data stride (must be positive)
+ * \ingroup gltexture
+ */
+void glDownloadTex(GL *gl, GLenum target, GLenum format, GLenum type,
+ void *dataptr, int stride)
+{
+ // this is not always correct, but should work for MPlayer
+ glAdjustAlignment(gl, stride);
+ gl->PixelStorei(GL_PACK_ROW_LENGTH, stride / glFmt2bpp(format, type));
+ gl->GetTexImage(target, 0, format, type, dataptr);
+}
+
+/**
+ * \brief Setup ATI version of register combiners for YUV to RGB conversion.
+ * \param csp_params parameters used for colorspace conversion
+ * \param text if set use the GL_ATI_text_fragment_shader API as
+ * used on OS X.
+ */
+static void glSetupYUVFragmentATI(GL *gl, struct mp_csp_params *csp_params,
+ int text)
+{
+ GLint i;
+ float yuv2rgb[3][4];
+
+ gl->GetIntegerv(GL_MAX_TEXTURE_UNITS, &i);
+ if (i < 3)
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "[gl] 3 texture units needed for YUV combiner (ATI) support (found %i)\n", i);
+
+ mp_get_yuv2rgb_coeffs(csp_params, yuv2rgb);
+ for (i = 0; i < 3; i++) {
+ int j;
+ yuv2rgb[i][3] -= -0.5 * (yuv2rgb[i][1] + yuv2rgb[i][2]);
+ for (j = 0; j < 4; j++) {
+ yuv2rgb[i][j] *= 0.125;
+ yuv2rgb[i][j] += 0.5;
+ if (yuv2rgb[i][j] > 1)
+ yuv2rgb[i][j] = 1;
+ if (yuv2rgb[i][j] < 0)
+ yuv2rgb[i][j] = 0;
+ }
+ }
+ if (text == 0) {
+ GLfloat c0[4] = { yuv2rgb[0][0], yuv2rgb[1][0], yuv2rgb[2][0] };
+ GLfloat c1[4] = { yuv2rgb[0][1], yuv2rgb[1][1], yuv2rgb[2][1] };
+ GLfloat c2[4] = { yuv2rgb[0][2], yuv2rgb[1][2], yuv2rgb[2][2] };
+ GLfloat c3[4] = { yuv2rgb[0][3], yuv2rgb[1][3], yuv2rgb[2][3] };
+ if (!gl->BeginFragmentShader || !gl->EndFragmentShader ||
+ !gl->SetFragmentShaderConstant || !gl->SampleMap ||
+ !gl->ColorFragmentOp2 || !gl->ColorFragmentOp3) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Combiner (ATI) functions missing!\n");
+ return;
+ }
+ gl->GetIntegerv(GL_NUM_FRAGMENT_REGISTERS_ATI, &i);
+ if (i < 3)
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "[gl] 3 registers needed for YUV combiner (ATI) support (found %i)\n", i);
+ gl->BeginFragmentShader();
+ gl->SetFragmentShaderConstant(GL_CON_0_ATI, c0);
+ gl->SetFragmentShaderConstant(GL_CON_1_ATI, c1);
+ gl->SetFragmentShaderConstant(GL_CON_2_ATI, c2);
+ gl->SetFragmentShaderConstant(GL_CON_3_ATI, c3);
+ gl->SampleMap(GL_REG_0_ATI, GL_TEXTURE0, GL_SWIZZLE_STR_ATI);
+ gl->SampleMap(GL_REG_1_ATI, GL_TEXTURE1, GL_SWIZZLE_STR_ATI);
+ gl->SampleMap(GL_REG_2_ATI, GL_TEXTURE2, GL_SWIZZLE_STR_ATI);
+ gl->ColorFragmentOp2(GL_MUL_ATI, GL_REG_1_ATI, GL_NONE, GL_NONE,
+ GL_REG_1_ATI, GL_NONE, GL_BIAS_BIT_ATI,
+ GL_CON_1_ATI, GL_NONE, GL_BIAS_BIT_ATI);
+ gl->ColorFragmentOp3(GL_MAD_ATI, GL_REG_2_ATI, GL_NONE, GL_NONE,
+ GL_REG_2_ATI, GL_NONE, GL_BIAS_BIT_ATI,
+ GL_CON_2_ATI, GL_NONE, GL_BIAS_BIT_ATI,
+ GL_REG_1_ATI, GL_NONE, GL_NONE);
+ gl->ColorFragmentOp3(GL_MAD_ATI, GL_REG_0_ATI, GL_NONE, GL_NONE,
+ GL_REG_0_ATI, GL_NONE, GL_NONE,
+ GL_CON_0_ATI, GL_NONE, GL_BIAS_BIT_ATI,
+ GL_REG_2_ATI, GL_NONE, GL_NONE);
+ gl->ColorFragmentOp2(GL_ADD_ATI, GL_REG_0_ATI, GL_NONE, GL_8X_BIT_ATI,
+ GL_REG_0_ATI, GL_NONE, GL_NONE,
+ GL_CON_3_ATI, GL_NONE, GL_BIAS_BIT_ATI);
+ gl->EndFragmentShader();
+ } else {
+ static const char template[] =
+ "!!ATIfs1.0\n"
+ "StartConstants;\n"
+ " CONSTANT c0 = {%e, %e, %e};\n"
+ " CONSTANT c1 = {%e, %e, %e};\n"
+ " CONSTANT c2 = {%e, %e, %e};\n"
+ " CONSTANT c3 = {%e, %e, %e};\n"
+ "EndConstants;\n"
+ "StartOutputPass;\n"
+ " SampleMap r0, t0.str;\n"
+ " SampleMap r1, t1.str;\n"
+ " SampleMap r2, t2.str;\n"
+ " MUL r1.rgb, r1.bias, c1.bias;\n"
+ " MAD r2.rgb, r2.bias, c2.bias, r1;\n"
+ " MAD r0.rgb, r0, c0.bias, r2;\n"
+ " ADD r0.rgb.8x, r0, c3.bias;\n"
+ "EndPass;\n";
+ char buffer[512];
+ snprintf(buffer, sizeof(buffer), template,
+ yuv2rgb[0][0], yuv2rgb[1][0], yuv2rgb[2][0],
+ yuv2rgb[0][1], yuv2rgb[1][1], yuv2rgb[2][1],
+ yuv2rgb[0][2], yuv2rgb[1][2], yuv2rgb[2][2],
+ yuv2rgb[0][3], yuv2rgb[1][3], yuv2rgb[2][3]);
+ mp_msg(MSGT_VO, MSGL_DBG2, "[gl] generated fragment program:\n%s\n",
+ buffer);
+ loadGPUProgram(gl, GL_TEXT_FRAGMENT_SHADER_ATI, buffer);
+ }
+}
+
+// Replace all occurances of variables named "$"+name (e.g. $foo) in *text with
+// replace, and return the result. *text must have been allocated with talloc.
+static void replace_var_str(char **text, const char *name, const char *replace)
+{
+ size_t namelen = strlen(name);
+ char *nextvar = *text;
+ void *parent = talloc_parent(*text);
+ for (;;) {
+ nextvar = strchr(nextvar, '$');
+ if (!nextvar)
+ break;
+ char *until = nextvar;
+ nextvar++;
+ if (strncmp(nextvar, name, namelen) != 0)
+ continue;
+ nextvar += namelen;
+ // try not to replace prefixes of other vars (e.g. $foo vs. $foo_bar)
+ char term = nextvar[0];
+ if (isalnum(term) || term == '_')
+ continue;
+ int prelength = until - *text;
+ int postlength = nextvar - *text;
+ char *n = talloc_asprintf(parent, "%.*s%s%s", prelength, *text, replace,
+ nextvar);
+ talloc_free(*text);
+ *text = n;
+ nextvar = *text + postlength;
+ }
+}
+
+static void replace_var_float(char **text, const char *name, float replace)
+{
+ char *s = talloc_asprintf(NULL, "%e", replace);
+ replace_var_str(text, name, s);
+ talloc_free(s);
+}
+
+static void replace_var_char(char **text, const char *name, char replace)
+{
+ char s[2] = { replace, '\0' };
+ replace_var_str(text, name, s);
+}
+
+// Append template to *text. Possibly initialize *text if it's NULL.
+static void append_template(char **text, const char* template)
+{
+ if (!text)
+ *text = talloc_strdup(NULL, template);
+ else
+ *text = talloc_strdup_append(*text, template);
+}
+
+/**
+ * \brief helper function for gen_spline_lookup_tex
+ * \param x subpixel-position ((0,1) range) to calculate weights for
+ * \param dst where to store transformed weights, must provide space for 4 GLfloats
+ *
+ * calculates the weights and stores them after appropriate transformation
+ * for the scaler fragment program.
+ */
+static void store_weights(float x, GLfloat *dst)
+{
+ float w0 = (((-1 * x + 3) * x - 3) * x + 1) / 6;
+ float w1 = (((3 * x - 6) * x + 0) * x + 4) / 6;
+ float w2 = (((-3 * x + 3) * x + 3) * x + 1) / 6;
+ float w3 = (((1 * x + 0) * x + 0) * x + 0) / 6;
+ *dst++ = 1 + x - w1 / (w0 + w1);
+ *dst++ = 1 - x + w3 / (w2 + w3);
+ *dst++ = w0 + w1;
+ *dst++ = 0;
+}
+
+//! to avoid artefacts this should be rather large
+#define LOOKUP_BSPLINE_RES (2 * 1024)
+/**
+ * \brief creates the 1D lookup texture needed for fast higher-order filtering
+ * \param unit texture unit to attach texture to
+ */
+static void gen_spline_lookup_tex(GL *gl, GLenum unit)
+{
+ GLfloat *tex = calloc(4 * LOOKUP_BSPLINE_RES, sizeof(*tex));
+ GLfloat *tp = tex;
+ int i;
+ for (i = 0; i < LOOKUP_BSPLINE_RES; i++) {
+ float x = (float)(i + 0.5) / LOOKUP_BSPLINE_RES;
+ store_weights(x, tp);
+ tp += 4;
+ }
+ store_weights(0, tex);
+ store_weights(1, &tex[4 * (LOOKUP_BSPLINE_RES - 1)]);
+ gl->ActiveTexture(unit);
+ gl->TexImage1D(GL_TEXTURE_1D, 0, GL_RGBA16, LOOKUP_BSPLINE_RES, 0, GL_RGBA,
+ GL_FLOAT, tex);
+ gl->TexParameterf(GL_TEXTURE_1D, GL_TEXTURE_PRIORITY, 1.0);
+ gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ gl->ActiveTexture(GL_TEXTURE0);
+ free(tex);
+}
+
+#define NOISE_RES 2048
+
+/**
+ * \brief creates the 1D lookup texture needed to generate pseudo-random numbers.
+ * \param unit texture unit to attach texture to
+ */
+static void gen_noise_lookup_tex(GL *gl, GLenum unit) {
+ GLfloat *tex = calloc(NOISE_RES, sizeof(*tex));
+ uint32_t lcg = 0x79381c11;
+ int i;
+ for (i = 0; i < NOISE_RES; i++)
+ tex[i] = (double)i / (NOISE_RES - 1);
+ for (i = 0; i < NOISE_RES - 1; i++) {
+ int remain = NOISE_RES - i;
+ int idx = i + (lcg >> 16) % remain;
+ GLfloat tmp = tex[i];
+ tex[i] = tex[idx];
+ tex[idx] = tmp;
+ lcg = lcg * 1664525 + 1013904223;
+ }
+ gl->ActiveTexture(unit);
+ gl->TexImage1D(GL_TEXTURE_1D, 0, 1, NOISE_RES, 0, GL_RED, GL_FLOAT, tex);
+ gl->TexParameterf(GL_TEXTURE_1D, GL_TEXTURE_PRIORITY, 1.0);
+ gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ gl->ActiveTexture(GL_TEXTURE0);
+ free(tex);
+}
+
+#define SAMPLE(dest, coord, texture) \
+ "TEX textemp, " coord ", " texture ", $tex_type;\n" \
+ "MOV " dest ", textemp.r;\n"
+
+static const char bilin_filt_template[] =
+ SAMPLE("yuv.$out_comp","fragment.texcoord[$in_tex]","texture[$in_tex]");
+
+#define BICUB_FILT_MAIN \
+ /* first y-interpolation */ \
+ "ADD coord, fragment.texcoord[$in_tex].xyxy, cdelta.xyxw;\n" \
+ "ADD coord2, fragment.texcoord[$in_tex].xyxy, cdelta.zyzw;\n" \
+ SAMPLE("a.r","coord.xyxy","texture[$in_tex]") \
+ SAMPLE("a.g","coord.zwzw","texture[$in_tex]") \
+ /* second y-interpolation */ \
+ SAMPLE("b.r","coord2.xyxy","texture[$in_tex]") \
+ SAMPLE("b.g","coord2.zwzw","texture[$in_tex]") \
+ "LRP a.b, parmy.b, a.rrrr, a.gggg;\n" \
+ "LRP a.a, parmy.b, b.rrrr, b.gggg;\n" \
+ /* x-interpolation */ \
+ "LRP yuv.$out_comp, parmx.b, a.bbbb, a.aaaa;\n"
+
+static const char bicub_filt_template_2D[] =
+ "MAD coord.xy, fragment.texcoord[$in_tex], {$texw, $texh}, {0.5, 0.5};\n"
+ "TEX parmx, coord.x, texture[$texs], 1D;\n"
+ "MUL cdelta.xz, parmx.rrgg, {-$ptw, 0, $ptw, 0};\n"
+ "TEX parmy, coord.y, texture[$texs], 1D;\n"
+ "MUL cdelta.yw, parmy.rrgg, {0, -$pth, 0, $pth};\n"
+ BICUB_FILT_MAIN;
+
+static const char bicub_filt_template_RECT[] =
+ "ADD coord, fragment.texcoord[$in_tex], {0.5, 0.5};\n"
+ "TEX parmx, coord.x, texture[$texs], 1D;\n"
+ "MUL cdelta.xz, parmx.rrgg, {-1, 0, 1, 0};\n"
+ "TEX parmy, coord.y, texture[$texs], 1D;\n"
+ "MUL cdelta.yw, parmy.rrgg, {0, -1, 0, 1};\n"
+ BICUB_FILT_MAIN;
+
+#define CALCWEIGHTS(t, s) \
+ "MAD "t ", {-0.5, 0.1666, 0.3333, -0.3333}, "s ", {1, 0, -0.5, 0.5};\n" \
+ "MAD "t ", "t ", "s ", {0, 0, -0.5, 0.5};\n" \
+ "MAD "t ", "t ", "s ", {-0.6666, 0, 0.8333, 0.1666};\n" \
+ "RCP a.x, "t ".z;\n" \
+ "RCP a.y, "t ".w;\n" \
+ "MAD "t ".xy, "t ".xyxy, a.xyxy, {1, 1, 0, 0};\n" \
+ "ADD "t ".x, "t ".xxxx, "s ";\n" \
+ "SUB "t ".y, "t ".yyyy, "s ";\n"
+
+static const char bicub_notex_filt_template_2D[] =
+ "MAD coord.xy, fragment.texcoord[$in_tex], {$texw, $texh}, {0.5, 0.5};\n"
+ "FRC coord.xy, coord.xyxy;\n"
+ CALCWEIGHTS("parmx", "coord.xxxx")
+ "MUL cdelta.xz, parmx.rrgg, {-$ptw, 0, $ptw, 0};\n"
+ CALCWEIGHTS("parmy", "coord.yyyy")
+ "MUL cdelta.yw, parmy.rrgg, {0, -$pth, 0, $pth};\n"
+ BICUB_FILT_MAIN;
+
+static const char bicub_notex_filt_template_RECT[] =
+ "ADD coord, fragment.texcoord[$in_tex], {0.5, 0.5};\n"
+ "FRC coord.xy, coord.xyxy;\n"
+ CALCWEIGHTS("parmx", "coord.xxxx")
+ "MUL cdelta.xz, parmx.rrgg, {-1, 0, 1, 0};\n"
+ CALCWEIGHTS("parmy", "coord.yyyy")
+ "MUL cdelta.yw, parmy.rrgg, {0, -1, 0, 1};\n"
+ BICUB_FILT_MAIN;
+
+#define BICUB_X_FILT_MAIN \
+ "ADD coord.xy, fragment.texcoord[$in_tex].xyxy, cdelta.xyxy;\n" \
+ "ADD coord2.xy, fragment.texcoord[$in_tex].xyxy, cdelta.zyzy;\n" \
+ SAMPLE("a.r","coord","texture[$in_tex]") \
+ SAMPLE("b.r","coord2","texture[$in_tex]") \
+ /* x-interpolation */ \
+ "LRP yuv.$out_comp, parmx.b, a.rrrr, b.rrrr;\n"
+
+static const char bicub_x_filt_template_2D[] =
+ "MAD coord.x, fragment.texcoord[$in_tex], {$texw}, {0.5};\n"
+ "TEX parmx, coord, texture[$texs], 1D;\n"
+ "MUL cdelta.xyz, parmx.rrgg, {-$ptw, 0, $ptw};\n"
+ BICUB_X_FILT_MAIN;
+
+static const char bicub_x_filt_template_RECT[] =
+ "ADD coord.x, fragment.texcoord[$in_tex], {0.5};\n"
+ "TEX parmx, coord, texture[$texs], 1D;\n"
+ "MUL cdelta.xyz, parmx.rrgg, {-1, 0, 1};\n"
+ BICUB_X_FILT_MAIN;
+
+static const char unsharp_filt_template[] =
+ "PARAM dcoord$out_comp = {$ptw_05, $pth_05, $ptw_05, -$pth_05};\n"
+ "ADD coord, fragment.texcoord[$in_tex].xyxy, dcoord$out_comp;\n"
+ "SUB coord2, fragment.texcoord[$in_tex].xyxy, dcoord$out_comp;\n"
+ SAMPLE("a.r","fragment.texcoord[$in_tex]","texture[$in_tex]")
+ SAMPLE("b.r","coord.xyxy","texture[$in_tex]")
+ SAMPLE("b.g","coord.zwzw","texture[$in_tex]")
+ "ADD b.r, b.r, b.g;\n"
+ SAMPLE("b.b","coord2.xyxy","texture[$in_tex]")
+ SAMPLE("b.g","coord2.zwzw","texture[$in_tex]")
+ "DP3 b, b, {0.25, 0.25, 0.25};\n"
+ "SUB b.r, a.r, b.r;\n"
+ "MAD textemp.r, b.r, {$strength}, a.r;\n"
+ "MOV yuv.$out_comp, textemp.r;\n";
+
+static const char unsharp_filt_template2[] =
+ "PARAM dcoord$out_comp = {$ptw_12, $pth_12, $ptw_12, -$pth_12};\n"
+ "PARAM dcoord2$out_comp = {$ptw_15, 0, 0, $pth_15};\n"
+ "ADD coord, fragment.texcoord[$in_tex].xyxy, dcoord$out_comp;\n"
+ "SUB coord2, fragment.texcoord[$in_tex].xyxy, dcoord$out_comp;\n"
+ SAMPLE("a.r","fragment.texcoord[$in_tex]","texture[$in_tex]")
+ SAMPLE("b.r","coord.xyxy","texture[$in_tex]")
+ SAMPLE("b.g","coord.zwzw","texture[$in_tex]")
+ "ADD b.r, b.r, b.g;\n"
+ SAMPLE("b.b","coord2.xyxy","texture[$in_tex]")
+ SAMPLE("b.g","coord2.zwzw","texture[$in_tex]")
+ "ADD b.r, b.r, b.b;\n"
+ "ADD b.a, b.r, b.g;\n"
+ "ADD coord, fragment.texcoord[$in_tex].xyxy, dcoord2$out_comp;\n"
+ "SUB coord2, fragment.texcoord[$in_tex].xyxy, dcoord2$out_comp;\n"
+ SAMPLE("b.r","coord.xyxy","texture[$in_tex]")
+ SAMPLE("b.g","coord.zwzw","texture[$in_tex]")
+ "ADD b.r, b.r, b.g;\n"
+ SAMPLE("b.b","coord2.xyxy","texture[$in_tex]")
+ SAMPLE("b.g","coord2.zwzw","texture[$in_tex]")
+ "DP4 b.r, b, {-0.1171875, -0.1171875, -0.1171875, -0.09765625};\n"
+ "MAD b.r, a.r, {0.859375}, b.r;\n"
+ "MAD textemp.r, b.r, {$strength}, a.r;\n"
+ "MOV yuv.$out_comp, textemp.r;\n";
+
+static const char yuv_prog_template[] =
+ "PARAM ycoef = {$cm11, $cm21, $cm31};\n"
+ "PARAM ucoef = {$cm12, $cm22, $cm32};\n"
+ "PARAM vcoef = {$cm13, $cm23, $cm33};\n"
+ "PARAM offsets = {$cm14, $cm24, $cm34};\n"
+ "TEMP res;\n"
+ "MAD res.rgb, yuv.rrrr, ycoef, offsets;\n"
+ "MAD res.rgb, yuv.gggg, ucoef, res;\n"
+ "MAD res.rgb, yuv.bbbb, vcoef, res;\n";
+
+static const char yuv_pow_prog_template[] =
+ "PARAM ycoef = {$cm11, $cm21, $cm31};\n"
+ "PARAM ucoef = {$cm12, $cm22, $cm32};\n"
+ "PARAM vcoef = {$cm13, $cm23, $cm33};\n"
+ "PARAM offsets = {$cm14, $cm24, $cm34};\n"
+ "PARAM gamma = {$gamma_r, $gamma_g, $gamma_b};\n"
+ "TEMP res;\n"
+ "MAD res.rgb, yuv.rrrr, ycoef, offsets;\n"
+ "MAD res.rgb, yuv.gggg, ucoef, res;\n"
+ "MAD_SAT res.rgb, yuv.bbbb, vcoef, res;\n"
+ "POW res.r, res.r, gamma.r;\n"
+ "POW res.g, res.g, gamma.g;\n"
+ "POW res.b, res.b, gamma.b;\n";
+
+static const char yuv_lookup_prog_template[] =
+ "PARAM ycoef = {$cm11, $cm21, $cm31, 0};\n"
+ "PARAM ucoef = {$cm12, $cm22, $cm32, 0};\n"
+ "PARAM vcoef = {$cm13, $cm23, $cm33, 0};\n"
+ "PARAM offsets = {$cm14, $cm24, $cm34, 0.125};\n"
+ "TEMP res;\n"
+ "MAD res, yuv.rrrr, ycoef, offsets;\n"
+ "MAD res.rgb, yuv.gggg, ucoef, res;\n"
+ "MAD res.rgb, yuv.bbbb, vcoef, res;\n"
+ "TEX res.r, res.raaa, texture[$conv_tex0], 2D;\n"
+ "ADD res.a, res.a, 0.25;\n"
+ "TEX res.g, res.gaaa, texture[$conv_tex0], 2D;\n"
+ "ADD res.a, res.a, 0.25;\n"
+ "TEX res.b, res.baaa, texture[$conv_tex0], 2D;\n";
+
+static const char yuv_lookup3d_prog_template[] =
+ "TEMP res;\n"
+ "TEX res, yuv, texture[$conv_tex0], 3D;\n";
+
+static const char noise_filt_template[] =
+ "MUL coord.xy, fragment.texcoord[0], {$noise_sx, $noise_sy};\n"
+ "TEMP rand;\n"
+ "TEX rand.r, coord.x, texture[$noise_filt_tex], 1D;\n"
+ "ADD rand.r, rand.r, coord.y;\n"
+ "TEX rand.r, rand.r, texture[$noise_filt_tex], 1D;\n"
+ "MAD res.rgb, rand.rrrr, {$noise_str, $noise_str, $noise_str}, res;\n";
+
+/**
+ * \brief creates and initializes helper textures needed for scaling texture read
+ * \param scaler scaler type to create texture for
+ * \param texu contains next free texture unit number
+ * \param texs texture unit ids for the scaler are stored in this array
+ */
+static void create_scaler_textures(GL *gl, int scaler, int *texu, char *texs)
+{
+ switch (scaler) {
+ case YUV_SCALER_BILIN:
+ case YUV_SCALER_BICUB_NOTEX:
+ case YUV_SCALER_UNSHARP:
+ case YUV_SCALER_UNSHARP2:
+ break;
+ case YUV_SCALER_BICUB:
+ case YUV_SCALER_BICUB_X:
+ texs[0] = (*texu)++;
+ gen_spline_lookup_tex(gl, GL_TEXTURE0 + texs[0]);
+ texs[0] += '0';
+ break;
+ default:
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] unknown scaler type %i\n", scaler);
+ }
+}
+
+//! resolution of texture for gamma lookup table
+#define LOOKUP_RES 512
+//! resolution for 3D yuv->rgb conversion lookup table
+#define LOOKUP_3DRES 32
+/**
+ * \brief creates and initializes helper textures needed for yuv conversion
+ * \param params struct containing parameters like brightness, gamma, ...
+ * \param texu contains next free texture unit number
+ * \param texs texture unit ids for the conversion are stored in this array
+ */
+static void create_conv_textures(GL *gl, gl_conversion_params_t *params,
+ int *texu, char *texs)
+{
+ unsigned char *lookup_data = NULL;
+ int conv = YUV_CONVERSION(params->type);
+ switch (conv) {
+ case YUV_CONVERSION_FRAGMENT:
+ case YUV_CONVERSION_FRAGMENT_POW:
+ break;
+ case YUV_CONVERSION_FRAGMENT_LOOKUP:
+ texs[0] = (*texu)++;
+ gl->ActiveTexture(GL_TEXTURE0 + texs[0]);
+ lookup_data = malloc(4 * LOOKUP_RES);
+ mp_gen_gamma_map(lookup_data, LOOKUP_RES, params->csp_params.rgamma);
+ mp_gen_gamma_map(&lookup_data[LOOKUP_RES], LOOKUP_RES,
+ params->csp_params.ggamma);
+ mp_gen_gamma_map(&lookup_data[2 * LOOKUP_RES], LOOKUP_RES,
+ params->csp_params.bgamma);
+ glCreateClearTex(gl, GL_TEXTURE_2D, GL_LUMINANCE8, GL_LUMINANCE,
+ GL_UNSIGNED_BYTE, GL_LINEAR, LOOKUP_RES, 4, 0);
+ glUploadTex(gl, GL_TEXTURE_2D, GL_LUMINANCE, GL_UNSIGNED_BYTE,
+ lookup_data, LOOKUP_RES, 0, 0, LOOKUP_RES, 4, 0);
+ gl->ActiveTexture(GL_TEXTURE0);
+ texs[0] += '0';
+ break;
+ case YUV_CONVERSION_FRAGMENT_LOOKUP3D:
+ {
+ int sz = LOOKUP_3DRES + 2; // texture size including borders
+ if (!gl->TexImage3D) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] Missing 3D texture function!\n");
+ break;
+ }
+ texs[0] = (*texu)++;
+ gl->ActiveTexture(GL_TEXTURE0 + texs[0]);
+ lookup_data = malloc(3 * sz * sz * sz);
+ mp_gen_yuv2rgb_map(&params->csp_params, lookup_data, LOOKUP_3DRES);
+ glAdjustAlignment(gl, sz);
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ gl->TexImage3D(GL_TEXTURE_3D, 0, 3, sz, sz, sz, 1,
+ GL_RGB, GL_UNSIGNED_BYTE, lookup_data);
+ gl->TexParameterf(GL_TEXTURE_3D, GL_TEXTURE_PRIORITY, 1.0);
+ gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP);
+ gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+ gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP);
+ gl->ActiveTexture(GL_TEXTURE0);
+ texs[0] += '0';
+ }
+ break;
+ default:
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] unknown conversion type %i\n", conv);
+ }
+ free(lookup_data);
+}
+
+/**
+ * \brief adds a scaling texture read at the current fragment program position
+ * \param scaler type of scaler to insert
+ * \param prog pointer to fragment program so far
+ * \param texs array containing the texture unit identifiers for this scaler
+ * \param in_tex texture unit the scaler should read from
+ * \param out_comp component of the yuv variable the scaler stores the result in
+ * \param rect if rectangular (pixel) adressing should be used for in_tex
+ * \param texw width of the in_tex texture
+ * \param texh height of the in_tex texture
+ * \param strength strength of filter effect if the scaler does some kind of filtering
+ */
+static void add_scaler(int scaler, char **prog, char *texs,
+ char in_tex, char out_comp, int rect, int texw, int texh,
+ double strength)
+{
+ const char *ttype = rect ? "RECT" : "2D";
+ const float ptw = rect ? 1.0 : 1.0 / texw;
+ const float pth = rect ? 1.0 : 1.0 / texh;
+ switch (scaler) {
+ case YUV_SCALER_BILIN:
+ append_template(prog, bilin_filt_template);
+ break;
+ case YUV_SCALER_BICUB:
+ if (rect)
+ append_template(prog, bicub_filt_template_RECT);
+ else
+ append_template(prog, bicub_filt_template_2D);
+ break;
+ case YUV_SCALER_BICUB_X:
+ if (rect)
+ append_template(prog, bicub_x_filt_template_RECT);
+ else
+ append_template(prog, bicub_x_filt_template_2D);
+ break;
+ case YUV_SCALER_BICUB_NOTEX:
+ if (rect)
+ append_template(prog, bicub_notex_filt_template_RECT);
+ else
+ append_template(prog, bicub_notex_filt_template_2D);
+ break;
+ case YUV_SCALER_UNSHARP:
+ append_template(prog, unsharp_filt_template);
+ break;
+ case YUV_SCALER_UNSHARP2:
+ append_template(prog, unsharp_filt_template2);
+ break;
+ }
+
+ replace_var_char(prog, "texs", texs[0]);
+ replace_var_char(prog, "in_tex", in_tex);
+ replace_var_char(prog, "out_comp", out_comp);
+ replace_var_str(prog, "tex_type", ttype);
+ replace_var_float(prog, "texw", texw);
+ replace_var_float(prog, "texh", texh);
+ replace_var_float(prog, "ptw", ptw);
+ replace_var_float(prog, "pth", pth);
+
+ // this is silly, not sure if that couldn't be in the shader source instead
+ replace_var_float(prog, "ptw_05", ptw * 0.5);
+ replace_var_float(prog, "pth_05", pth * 0.5);
+ replace_var_float(prog, "ptw_15", ptw * 1.5);
+ replace_var_float(prog, "pth_15", pth * 1.5);
+ replace_var_float(prog, "ptw_12", ptw * 1.2);
+ replace_var_float(prog, "pth_12", pth * 1.2);
+
+ replace_var_float(prog, "strength", strength);
+}
+
+static const struct {
+ const char *name;
+ GLenum cur;
+ GLenum max;
+} progstats[] = {
+ {"instructions", 0x88A0, 0x88A1},
+ {"native instructions", 0x88A2, 0x88A3},
+ {"temporaries", 0x88A4, 0x88A5},
+ {"native temporaries", 0x88A6, 0x88A7},
+ {"parameters", 0x88A8, 0x88A9},
+ {"native parameters", 0x88AA, 0x88AB},
+ {"attribs", 0x88AC, 0x88AD},
+ {"native attribs", 0x88AE, 0x88AF},
+ {"ALU instructions", 0x8805, 0x880B},
+ {"TEX instructions", 0x8806, 0x880C},
+ {"TEX indirections", 0x8807, 0x880D},
+ {"native ALU instructions", 0x8808, 0x880E},
+ {"native TEX instructions", 0x8809, 0x880F},
+ {"native TEX indirections", 0x880A, 0x8810},
+ {NULL, 0, 0}
+};
+
+/**
+ * \brief load the specified GPU Program
+ * \param target program target to load into, only GL_FRAGMENT_PROGRAM is tested
+ * \param prog program string
+ * \return 1 on success, 0 otherwise
+ */
+int loadGPUProgram(GL *gl, GLenum target, char *prog)
+{
+ int i;
+ GLint cur = 0, max = 0, err = 0;
+ if (!gl->ProgramString) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] Missing GPU program function\n");
+ return 0;
+ }
+ gl->ProgramString(target, GL_PROGRAM_FORMAT_ASCII, strlen(prog), prog);
+ gl->GetIntegerv(GL_PROGRAM_ERROR_POSITION, &err);
+ if (err != -1) {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "[gl] Error compiling fragment program, make sure your card supports\n"
+ "[gl] GL_ARB_fragment_program (use glxinfo to check).\n"
+ "[gl] Error message:\n %s at %.10s\n",
+ gl->GetString(GL_PROGRAM_ERROR_STRING), &prog[err]);
+ return 0;
+ }
+ if (!gl->GetProgramivARB || !mp_msg_test(MSGT_VO, MSGL_DBG2))
+ return 1;
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Program statistics:\n");
+ for (i = 0; progstats[i].name; i++) {
+ gl->GetProgramivARB(target, progstats[i].cur, &cur);
+ gl->GetProgramivARB(target, progstats[i].max, &max);
+ mp_msg(MSGT_VO, MSGL_V, "[gl] %s: %i/%i\n", progstats[i].name, cur,
+ max);
+ }
+ return 1;
+}
+
+#define MAX_PROGSZ (1024 * 1024)
+
+/**
+ * \brief setup a fragment program that will do YUV->RGB conversion
+ * \param parms struct containing parameters like conversion and scaler type,
+ * brightness, ...
+ */
+static void glSetupYUVFragprog(GL *gl, gl_conversion_params_t *params)
+{
+ int type = params->type;
+ int texw = params->texw;
+ int texh = params->texh;
+ int rect = params->target == GL_TEXTURE_RECTANGLE;
+ static const char prog_hdr[] =
+ "!!ARBfp1.0\n"
+ "OPTION ARB_precision_hint_fastest;\n"
+ // all scaler variables must go here so they aren't defined
+ // multiple times when the same scaler is used more than once
+ "TEMP coord, coord2, cdelta, parmx, parmy, a, b, yuv, textemp;\n";
+ char *yuv_prog = NULL;
+ char **prog = &yuv_prog;
+ int cur_texu = 3;
+ char lum_scale_texs[1] = {0};
+ char chrom_scale_texs[1] = {0};
+ char conv_texs[1];
+ char filt_texs[1] = {0};
+ GLint i;
+ // this is the conversion matrix, with y, u, v factors
+ // for red, green, blue and the constant offsets
+ float yuv2rgb[3][4];
+ int noise = params->noise_strength != 0;
+ create_conv_textures(gl, params, &cur_texu, conv_texs);
+ create_scaler_textures(gl, YUV_LUM_SCALER(type), &cur_texu, lum_scale_texs);
+ if (YUV_CHROM_SCALER(type) == YUV_LUM_SCALER(type))
+ memcpy(chrom_scale_texs, lum_scale_texs, sizeof(chrom_scale_texs));
+ else
+ create_scaler_textures(gl, YUV_CHROM_SCALER(type), &cur_texu,
+ chrom_scale_texs);
+
+ if (noise) {
+ gen_noise_lookup_tex(gl, cur_texu);
+ filt_texs[0] = '0' + cur_texu++;
+ }
+
+ gl->GetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &i);
+ if (i < cur_texu)
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "[gl] %i texture units needed for this type of YUV fragment support (found %i)\n",
+ cur_texu, i);
+ if (!gl->ProgramString) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] ProgramString function missing!\n");
+ return;
+ }
+ append_template(prog, prog_hdr);
+ add_scaler(YUV_LUM_SCALER(type), prog, lum_scale_texs,
+ '0', 'r', rect, texw, texh, params->filter_strength);
+ add_scaler(YUV_CHROM_SCALER(type), prog,
+ chrom_scale_texs, '1', 'g', rect, params->chrom_texw,
+ params->chrom_texh, params->filter_strength);
+ add_scaler(YUV_CHROM_SCALER(type), prog,
+ chrom_scale_texs, '2', 'b', rect, params->chrom_texw,
+ params->chrom_texh, params->filter_strength);
+ mp_get_yuv2rgb_coeffs(&params->csp_params, yuv2rgb);
+ switch (YUV_CONVERSION(type)) {
+ case YUV_CONVERSION_FRAGMENT:
+ append_template(prog, yuv_prog_template);
+ break;
+ case YUV_CONVERSION_FRAGMENT_POW:
+ append_template(prog, yuv_pow_prog_template);
+ break;
+ case YUV_CONVERSION_FRAGMENT_LOOKUP:
+ append_template(prog, yuv_lookup_prog_template);
+ break;
+ case YUV_CONVERSION_FRAGMENT_LOOKUP3D:
+ append_template(prog, yuv_lookup3d_prog_template);
+ break;
+ default:
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] unknown conversion type %i\n",
+ YUV_CONVERSION(type));
+ break;
+ }
+ for (int r = 0; r < 3; r++) {
+ for (int c = 0; c < 4; c++) {
+ // "cmRC"
+ char var[] = { 'c', 'm', '1' + r, '1' + c, '\0' };
+ replace_var_float(prog, var, yuv2rgb[r][c]);
+ }
+ }
+ replace_var_float(prog, "gamma_r", (float)1.0 / params->csp_params.rgamma);
+ replace_var_float(prog, "gamma_g", (float)1.0 / params->csp_params.ggamma);
+ replace_var_float(prog, "gamma_b", (float)1.0 / params->csp_params.bgamma);
+ replace_var_char(prog, "conv_tex0", conv_texs[0]);
+
+ if (noise) {
+ // 1.0 strength is suitable for dithering 8 to 6 bit
+ double str = params->noise_strength * (1.0 / 64);
+ double scale_x = (double)NOISE_RES / texw;
+ double scale_y = (double)NOISE_RES / texh;
+ if (rect) {
+ scale_x /= texw;
+ scale_y /= texh;
+ }
+ append_template(prog, noise_filt_template);
+ replace_var_float(prog, "noise_sx", scale_x);
+ replace_var_float(prog, "noise_sy", scale_y);
+ replace_var_char(prog, "noise_filt_tex", filt_texs[0]);
+ replace_var_float(prog, "noise_str", str);
+ }
+
+ append_template(prog, "MOV result.color.rgb, res;\nEND");
+
+ mp_msg(MSGT_VO, MSGL_DBG2, "[gl] generated fragment program:\n%s\n",
+ yuv_prog);
+ loadGPUProgram(gl, GL_FRAGMENT_PROGRAM, yuv_prog);
+ talloc_free(yuv_prog);
+}
+
+/**
+ * \brief detect the best YUV->RGB conversion method available
+ */
+int glAutodetectYUVConversion(GL *gl)
+{
+ const char *extensions = gl->GetString(GL_EXTENSIONS);
+ if (!extensions || !gl->MultiTexCoord2f)
+ return YUV_CONVERSION_NONE;
+ if (strstr(extensions, "GL_ARB_fragment_program"))
+ return YUV_CONVERSION_FRAGMENT;
+ if (strstr(extensions, "GL_ATI_text_fragment_shader"))
+ return YUV_CONVERSION_TEXT_FRAGMENT;
+ if (strstr(extensions, "GL_ATI_fragment_shader"))
+ return YUV_CONVERSION_COMBINERS_ATI;
+ return YUV_CONVERSION_NONE;
+}
+
+/**
+ * \brief setup YUV->RGB conversion
+ * \param parms struct containing parameters like conversion and scaler type,
+ * brightness, ...
+ * \ingroup glconversion
+ */
+void glSetupYUVConversion(GL *gl, gl_conversion_params_t *params)
+{
+ if (params->chrom_texw == 0)
+ params->chrom_texw = 1;
+ if (params->chrom_texh == 0)
+ params->chrom_texh = 1;
+ switch (YUV_CONVERSION(params->type)) {
+ case YUV_CONVERSION_COMBINERS_ATI:
+ glSetupYUVFragmentATI(gl, &params->csp_params, 0);
+ break;
+ case YUV_CONVERSION_TEXT_FRAGMENT:
+ glSetupYUVFragmentATI(gl, &params->csp_params, 1);
+ break;
+ case YUV_CONVERSION_FRAGMENT_LOOKUP:
+ case YUV_CONVERSION_FRAGMENT_LOOKUP3D:
+ case YUV_CONVERSION_FRAGMENT:
+ case YUV_CONVERSION_FRAGMENT_POW:
+ glSetupYUVFragprog(gl, params);
+ break;
+ case YUV_CONVERSION_NONE:
+ break;
+ default:
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] unknown conversion type %i\n",
+ YUV_CONVERSION(params->type));
+ }
+}
+
+/**
+ * \brief enable the specified YUV conversion
+ * \param target texture target for Y, U and V textures (e.g. GL_TEXTURE_2D)
+ * \param type type of YUV conversion
+ * \ingroup glconversion
+ */
+void glEnableYUVConversion(GL *gl, GLenum target, int type)
+{
+ switch (YUV_CONVERSION(type)) {
+ case YUV_CONVERSION_COMBINERS_ATI:
+ gl->ActiveTexture(GL_TEXTURE1);
+ gl->Enable(target);
+ gl->ActiveTexture(GL_TEXTURE2);
+ gl->Enable(target);
+ gl->ActiveTexture(GL_TEXTURE0);
+ gl->Enable(GL_FRAGMENT_SHADER_ATI);
+ break;
+ case YUV_CONVERSION_TEXT_FRAGMENT:
+ gl->ActiveTexture(GL_TEXTURE1);
+ gl->Enable(target);
+ gl->ActiveTexture(GL_TEXTURE2);
+ gl->Enable(target);
+ gl->ActiveTexture(GL_TEXTURE0);
+ gl->Enable(GL_TEXT_FRAGMENT_SHADER_ATI);
+ break;
+ case YUV_CONVERSION_FRAGMENT_LOOKUP3D:
+ case YUV_CONVERSION_FRAGMENT_LOOKUP:
+ case YUV_CONVERSION_FRAGMENT_POW:
+ case YUV_CONVERSION_FRAGMENT:
+ case YUV_CONVERSION_NONE:
+ gl->Enable(GL_FRAGMENT_PROGRAM);
+ break;
+ }
+}
+
+/**
+ * \brief disable the specified YUV conversion
+ * \param target texture target for Y, U and V textures (e.g. GL_TEXTURE_2D)
+ * \param type type of YUV conversion
+ * \ingroup glconversion
+ */
+void glDisableYUVConversion(GL *gl, GLenum target, int type)
+{
+ switch (YUV_CONVERSION(type)) {
+ case YUV_CONVERSION_COMBINERS_ATI:
+ gl->ActiveTexture(GL_TEXTURE1);
+ gl->Disable(target);
+ gl->ActiveTexture(GL_TEXTURE2);
+ gl->Disable(target);
+ gl->ActiveTexture(GL_TEXTURE0);
+ gl->Disable(GL_FRAGMENT_SHADER_ATI);
+ break;
+ case YUV_CONVERSION_TEXT_FRAGMENT:
+ gl->Disable(GL_TEXT_FRAGMENT_SHADER_ATI);
+ // HACK: at least the Mac OS X 10.5 PPC Radeon drivers are broken and
+ // without this disable the texture units while the program is still
+ // running (10.4 PPC seems to work without this though).
+ gl->Flush();
+ gl->ActiveTexture(GL_TEXTURE1);
+ gl->Disable(target);
+ gl->ActiveTexture(GL_TEXTURE2);
+ gl->Disable(target);
+ gl->ActiveTexture(GL_TEXTURE0);
+ break;
+ case YUV_CONVERSION_FRAGMENT_LOOKUP3D:
+ case YUV_CONVERSION_FRAGMENT_LOOKUP:
+ case YUV_CONVERSION_FRAGMENT_POW:
+ case YUV_CONVERSION_FRAGMENT:
+ case YUV_CONVERSION_NONE:
+ gl->Disable(GL_FRAGMENT_PROGRAM);
+ break;
+ }
+}
+
+void glEnable3DLeft(GL *gl, int type)
+{
+ GLint buffer;
+ switch (type) {
+ case GL_3D_RED_CYAN:
+ gl->ColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE);
+ break;
+ case GL_3D_GREEN_MAGENTA:
+ gl->ColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
+ break;
+ case GL_3D_QUADBUFFER:
+ gl->GetIntegerv(GL_DRAW_BUFFER, &buffer);
+ switch (buffer) {
+ case GL_FRONT:
+ case GL_FRONT_LEFT:
+ case GL_FRONT_RIGHT:
+ buffer = GL_FRONT_LEFT;
+ break;
+ case GL_BACK:
+ case GL_BACK_LEFT:
+ case GL_BACK_RIGHT:
+ buffer = GL_BACK_LEFT;
+ break;
+ }
+ gl->DrawBuffer(buffer);
+ break;
+ }
+}
+
+void glEnable3DRight(GL *gl, int type)
+{
+ GLint buffer;
+ switch (type) {
+ case GL_3D_RED_CYAN:
+ gl->ColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);
+ break;
+ case GL_3D_GREEN_MAGENTA:
+ gl->ColorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE);
+ break;
+ case GL_3D_QUADBUFFER:
+ gl->GetIntegerv(GL_DRAW_BUFFER, &buffer);
+ switch (buffer) {
+ case GL_FRONT:
+ case GL_FRONT_LEFT:
+ case GL_FRONT_RIGHT:
+ buffer = GL_FRONT_RIGHT;
+ break;
+ case GL_BACK:
+ case GL_BACK_LEFT:
+ case GL_BACK_RIGHT:
+ buffer = GL_BACK_RIGHT;
+ break;
+ }
+ gl->DrawBuffer(buffer);
+ break;
+ }
+}
+
+void glDisable3D(GL *gl, int type)
+{
+ GLint buffer;
+ switch (type) {
+ case GL_3D_RED_CYAN:
+ case GL_3D_GREEN_MAGENTA:
+ gl->ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ break;
+ case GL_3D_QUADBUFFER:
+ gl->DrawBuffer(GL_BACK);
+ gl->GetIntegerv(GL_DRAW_BUFFER, &buffer);
+ switch (buffer) {
+ case GL_FRONT:
+ case GL_FRONT_LEFT:
+ case GL_FRONT_RIGHT:
+ buffer = GL_FRONT;
+ break;
+ case GL_BACK:
+ case GL_BACK_LEFT:
+ case GL_BACK_RIGHT:
+ buffer = GL_BACK;
+ break;
+ }
+ gl->DrawBuffer(buffer);
+ break;
+ }
+}
+
+/**
+ * \brief draw a texture part at given 2D coordinates
+ * \param x screen top coordinate
+ * \param y screen left coordinate
+ * \param w screen width coordinate
+ * \param h screen height coordinate
+ * \param tx texture top coordinate in pixels
+ * \param ty texture left coordinate in pixels
+ * \param tw texture part width in pixels
+ * \param th texture part height in pixels
+ * \param sx width of texture in pixels
+ * \param sy height of texture in pixels
+ * \param rect_tex whether this texture uses texture_rectangle extension
+ * \param is_yv12 if != 0, also draw the textures from units 1 and 2,
+ * bits 8 - 15 and 16 - 23 specify the x and y scaling of those textures
+ * \param flip flip the texture upside down
+ * \ingroup gltexture
+ */
+void glDrawTex(GL *gl, GLfloat x, GLfloat y, GLfloat w, GLfloat h,
+ GLfloat tx, GLfloat ty, GLfloat tw, GLfloat th,
+ int sx, int sy, int rect_tex, int is_yv12, int flip)
+{
+ int chroma_x_shift = (is_yv12 >> 8) & 31;
+ int chroma_y_shift = (is_yv12 >> 16) & 31;
+ GLfloat xscale = 1 << chroma_x_shift;
+ GLfloat yscale = 1 << chroma_y_shift;
+ GLfloat tx2 = tx / xscale, ty2 = ty / yscale, tw2 = tw / xscale, th2 = th / yscale;
+ if (!rect_tex) {
+ tx /= sx;
+ ty /= sy;
+ tw /= sx;
+ th /= sy;
+ tx2 = tx, ty2 = ty, tw2 = tw, th2 = th;
+ }
+ if (flip) {
+ y += h;
+ h = -h;
+ }
+ gl->Begin(GL_QUADS);
+ gl->TexCoord2f(tx, ty);
+ if (is_yv12) {
+ gl->MultiTexCoord2f(GL_TEXTURE1, tx2, ty2);
+ gl->MultiTexCoord2f(GL_TEXTURE2, tx2, ty2);
+ }
+ gl->Vertex2f(x, y);
+ gl->TexCoord2f(tx, ty + th);
+ if (is_yv12) {
+ gl->MultiTexCoord2f(GL_TEXTURE1, tx2, ty2 + th2);
+ gl->MultiTexCoord2f(GL_TEXTURE2, tx2, ty2 + th2);
+ }
+ gl->Vertex2f(x, y + h);
+ gl->TexCoord2f(tx + tw, ty + th);
+ if (is_yv12) {
+ gl->MultiTexCoord2f(GL_TEXTURE1, tx2 + tw2, ty2 + th2);
+ gl->MultiTexCoord2f(GL_TEXTURE2, tx2 + tw2, ty2 + th2);
+ }
+ gl->Vertex2f(x + w, y + h);
+ gl->TexCoord2f(tx + tw, ty);
+ if (is_yv12) {
+ gl->MultiTexCoord2f(GL_TEXTURE1, tx2 + tw2, ty2);
+ gl->MultiTexCoord2f(GL_TEXTURE2, tx2 + tw2, ty2);
+ }
+ gl->Vertex2f(x + w, y);
+ gl->End();
+}
+
+mp_image_t *glGetWindowScreenshot(GL *gl)
+{
+ GLint vp[4]; //x, y, w, h
+ gl->GetIntegerv(GL_VIEWPORT, vp);
+ mp_image_t *image = alloc_mpi(vp[2], vp[3], IMGFMT_RGB24);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ gl->PixelStorei(GL_PACK_ALIGNMENT, 0);
+ gl->PixelStorei(GL_PACK_ROW_LENGTH, 0);
+ gl->ReadBuffer(GL_FRONT);
+ //flip image while reading
+ for (int y = 0; y < vp[3]; y++) {
+ gl->ReadPixels(vp[0], vp[1] + vp[3] - y - 1, vp[2], 1,
+ GL_RGB, GL_UNSIGNED_BYTE,
+ image->planes[0] + y * image->stride[0]);
+ }
+ return image;
+}
+
+#ifdef CONFIG_GL_COCOA
+#include "cocoa_common.h"
+
+static bool create_window_cocoa(struct MPGLContext *ctx, uint32_t d_width,
+ uint32_t d_height, uint32_t flags, bool gl3)
+{
+ int rv = vo_cocoa_create_window(ctx->vo, d_width, d_height, flags, gl3);
+ if (rv != 0)
+ return false;
+
+ getFunctions(ctx->gl, (void *)vo_cocoa_glgetaddr, NULL, gl3);
+
+ if (gl3) {
+ ctx->depth_r = vo_cocoa_cgl_color_size(ctx->vo);
+ ctx->depth_g = vo_cocoa_cgl_color_size(ctx->vo);
+ ctx->depth_b = vo_cocoa_cgl_color_size(ctx->vo);
+ }
+
+ if (!ctx->gl->SwapInterval)
+ ctx->gl->SwapInterval = vo_cocoa_swap_interval;
+
+ return true;
+}
+
+static bool create_window_cocoa_old(struct MPGLContext *ctx, uint32_t d_width,
+ uint32_t d_height, uint32_t flags)
+{
+ return create_window_cocoa(ctx, d_width, d_height, flags, false);
+}
+
+static bool create_window_cocoa_gl3(struct MPGLContext *ctx, uint32_t d_width,
+ uint32_t d_height, uint32_t flags)
+{
+ return create_window_cocoa(ctx, d_width, d_height, flags, true);
+}
+
+static void releaseGlContext_cocoa(MPGLContext *ctx)
+{
+}
+
+static void swapGlBuffers_cocoa(MPGLContext *ctx)
+{
+ vo_cocoa_swap_buffers(ctx->vo);
+}
+#endif
+
+#ifdef CONFIG_GL_WIN32
+#include <windows.h>
+#include "w32_common.h"
+
+struct w32_context {
+ HGLRC context;
+};
+
+static void *w32gpa(const GLubyte *procName)
+{
+ HMODULE oglmod;
+ void *res = wglGetProcAddress(procName);
+ if (res)
+ return res;
+ oglmod = GetModuleHandle("opengl32.dll");
+ return GetProcAddress(oglmod, procName);
+}
+
+static bool create_window_w32_old(struct MPGLContext *ctx, uint32_t d_width,
+ uint32_t d_height, uint32_t flags)
+{
+ GL *gl = ctx->gl;
+
+ if (!vo_w32_config(ctx->vo, d_width, d_height, flags))
+ return false;
+
+ struct w32_context *w32_ctx = ctx->priv;
+ HGLRC *context = &w32_ctx->context;
+
+ if (*context) {
+ gl->Finish(); // supposedly to prevent flickering
+ return true;
+ }
+
+ HWND win = ctx->vo->w32->window;
+ HDC windc = vo_w32_get_dc(ctx->vo, win);
+ bool res = false;
+
+ HGLRC new_context = wglCreateContext(windc);
+ if (!new_context) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not create GL context!\n");
+ goto out;
+ }
+
+ if (!wglMakeCurrent(windc, new_context)) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not set GL context!\n");
+ wglDeleteContext(new_context);
+ goto out;
+ }
+
+ *context = new_context;
+
+ getFunctions(ctx->gl, w32gpa, NULL, false);
+ res = true;
+
+out:
+ vo_w32_release_dc(ctx->vo, win, windc);
+ return res;
+}
+
+static bool create_window_w32_gl3(struct MPGLContext *ctx, uint32_t d_width,
+ uint32_t d_height, uint32_t flags)
+{
+ if (!vo_w32_config(ctx->vo, d_width, d_height, flags))
+ return false;
+
+ struct w32_context *w32_ctx = ctx->priv;
+ HGLRC *context = &w32_ctx->context;
+
+ if (*context) // reuse existing context
+ return true; // not reusing it breaks gl3!
+
+ HWND win = ctx->vo->w32->window;
+ HDC windc = vo_w32_get_dc(ctx->vo, win);
+ HGLRC new_context = 0;
+
+ new_context = wglCreateContext(windc);
+ if (!new_context) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not create GL context!\n");
+ return false;
+ }
+
+ // set context
+ if (!wglMakeCurrent(windc, new_context)) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not set GL context!\n");
+ goto out;
+ }
+
+ const char *(GLAPIENTRY *wglGetExtensionsStringARB)(HDC hdc)
+ = w32gpa((const GLubyte*)"wglGetExtensionsStringARB");
+
+ if (!wglGetExtensionsStringARB)
+ goto unsupported;
+
+ const char *wgl_exts = wglGetExtensionsStringARB(windc);
+ if (!strstr(wgl_exts, "WGL_ARB_create_context"))
+ goto unsupported;
+
+ HGLRC (GLAPIENTRY *wglCreateContextAttribsARB)(HDC hDC, HGLRC hShareContext,
+ const int *attribList)
+ = w32gpa((const GLubyte*)"wglCreateContextAttribsARB");
+
+ if (!wglCreateContextAttribsARB)
+ goto unsupported;
+
+ int gl_version = ctx->requested_gl_version;
+ int attribs[] = {
+ WGL_CONTEXT_MAJOR_VERSION_ARB, MPGL_VER_GET_MAJOR(gl_version),
+ WGL_CONTEXT_MINOR_VERSION_ARB, MPGL_VER_GET_MINOR(gl_version),
+ WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
+ WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
+ 0
+ };
+
+ *context = wglCreateContextAttribsARB(windc, 0, attribs);
+ if (! *context) {
+ // NVidia, instead of ignoring WGL_CONTEXT_FLAGS_ARB, will error out if
+ // it's present on pre-3.2 contexts.
+ // Remove it from attribs and retry the context creation.
+ attribs[6] = attribs[7] = 0;
+ *context = wglCreateContextAttribsARB(windc, 0, attribs);
+ }
+ if (! *context) {
+ int err = GetLastError();
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not create an OpenGL 3.x"
+ " context: error 0x%x\n", err);
+ goto out;
+ }
+
+ wglMakeCurrent(NULL, NULL);
+ wglDeleteContext(new_context);
+
+ if (!wglMakeCurrent(windc, *context)) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not set GL3 context!\n");
+ wglDeleteContext(*context);
+ return false;
+ }
+
+ /* update function pointers */
+ getFunctions(ctx->gl, w32gpa, NULL, true);
+
+ int pfmt = GetPixelFormat(windc);
+ PIXELFORMATDESCRIPTOR pfd;
+ if (DescribePixelFormat(windc, pfmt, sizeof(PIXELFORMATDESCRIPTOR), &pfd)) {
+ ctx->depth_r = pfd.cRedBits;
+ ctx->depth_g = pfd.cGreenBits;
+ ctx->depth_b = pfd.cBlueBits;
+ }
+
+ return true;
+
+unsupported:
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] The current OpenGL implementation does"
+ " not support OpenGL 3.x \n");
+out:
+ wglDeleteContext(new_context);
+ return false;
+}
+
+static void releaseGlContext_w32(MPGLContext *ctx)
+{
+ struct w32_context *w32_ctx = ctx->priv;
+ HGLRC *context = &w32_ctx->context;
+ if (*context) {
+ wglMakeCurrent(0, 0);
+ wglDeleteContext(*context);
+ }
+ *context = 0;
+}
+
+static void swapGlBuffers_w32(MPGLContext *ctx)
+{
+ HDC vo_hdc = vo_w32_get_dc(ctx->vo, ctx->vo->w32->window);
+ SwapBuffers(vo_hdc);
+ vo_w32_release_dc(ctx->vo, ctx->vo->w32->window, vo_hdc);
+}
+#endif
+
+#ifdef CONFIG_GL_X11
+#include <X11/Xlib.h>
+#include <GL/glx.h>
+#include "x11_common.h"
+
+struct glx_context {
+ XVisualInfo *vinfo;
+ GLXContext context;
+ GLXFBConfig fbc;
+};
+
+// The GL3/FBC initialization code roughly follows/copies from:
+// http://www.opengl.org/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX)
+// but also uses some of the old code.
+
+static GLXFBConfig select_fb_config(struct vo *vo, const int *attribs)
+{
+ int fbcount;
+ GLXFBConfig *fbc = glXChooseFBConfig(vo->x11->display, vo->x11->screen,
+ attribs, &fbcount);
+ if (!fbc)
+ return NULL;
+
+ // The list in fbc is sorted (so that the first element is the best).
+ GLXFBConfig fbconfig = fbc[0];
+
+ XFree(fbc);
+
+ return fbconfig;
+}
+
+static bool create_glx_window(struct MPGLContext *ctx, uint32_t d_width,
+ uint32_t d_height, uint32_t flags)
+{
+ struct vo *vo = ctx->vo;
+ struct glx_context *glx_ctx = ctx->priv;
+
+ if (glx_ctx->context) {
+ // GL context and window already exist.
+ // Only update window geometry etc.
+ Colormap colormap = XCreateColormap(vo->x11->display, vo->x11->rootwin,
+ glx_ctx->vinfo->visual, AllocNone);
+ vo_x11_create_vo_window(vo, glx_ctx->vinfo, vo->dx, vo->dy, d_width,
+ d_height, flags, colormap, "gl");
+ XFreeColormap(vo->x11->display, colormap);
+ return true;
+ }
+
+ int glx_major, glx_minor;
+
+ // FBConfigs were added in GLX version 1.3.
+ if (!glXQueryVersion(vo->x11->display, &glx_major, &glx_minor) ||
+ (MPGL_VER(glx_major, glx_minor) < MPGL_VER(1, 3)))
+ {
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] GLX version older than 1.3.\n");
+ return false;
+ }
+
+ const int glx_attribs_stereo_value_idx = 1; // index of GLX_STEREO + 1
+ int glx_attribs[] = {
+ GLX_STEREO, False,
+ GLX_X_RENDERABLE, True,
+ GLX_RED_SIZE, 1,
+ GLX_GREEN_SIZE, 1,
+ GLX_BLUE_SIZE, 1,
+ GLX_DOUBLEBUFFER, True,
+ None
+ };
+ GLXFBConfig fbc = NULL;
+ if (flags & VOFLAG_STEREO) {
+ glx_attribs[glx_attribs_stereo_value_idx] = True;
+ fbc = select_fb_config(vo, glx_attribs);
+ if (!fbc) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] Could not find a stereo visual,"
+ " 3D will probably not work!\n");
+ glx_attribs[glx_attribs_stereo_value_idx] = False;
+ }
+ }
+ if (!fbc)
+ fbc = select_fb_config(vo, glx_attribs);
+ if (!fbc) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] no GLX support present\n");
+ return false;
+ }
+
+ glx_ctx->fbc = fbc;
+ glx_ctx->vinfo = glXGetVisualFromFBConfig(vo->x11->display, fbc);
+
+ mp_msg(MSGT_VO, MSGL_V, "[gl] GLX chose visual with ID 0x%x\n",
+ (int)glx_ctx->vinfo->visualid);
+
+ glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_RED_SIZE, &ctx->depth_r);
+ glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_GREEN_SIZE, &ctx->depth_g);
+ glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_BLUE_SIZE, &ctx->depth_b);
+
+ Colormap colormap = XCreateColormap(vo->x11->display, vo->x11->rootwin,
+ glx_ctx->vinfo->visual, AllocNone);
+ vo_x11_create_vo_window(vo, glx_ctx->vinfo, vo->dx, vo->dy, d_width,
+ d_height, flags, colormap, "gl");
+ XFreeColormap(vo->x11->display, colormap);
+
+ return true;
+}
+
+static bool create_window_x11_old(struct MPGLContext *ctx, uint32_t d_width,
+ uint32_t d_height, uint32_t flags)
+{
+ struct glx_context *glx_ctx = ctx->priv;
+ Display *display = ctx->vo->x11->display;
+ struct vo *vo = ctx->vo;
+ GL *gl = ctx->gl;
+
+ if (!create_glx_window(ctx, d_width, d_height, flags))
+ return false;
+
+ if (glx_ctx->context)
+ return true;
+
+ GLXContext new_context = glXCreateContext(display, glx_ctx->vinfo, NULL,
+ True);
+ if (!new_context) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not create GLX context!\n");
+ return false;
+ }
+
+ if (!glXMakeCurrent(display, ctx->vo->x11->window, new_context)) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not set GLX context!\n");
+ glXDestroyContext(display, new_context);
+ return false;
+ }
+
+ void *(*getProcAddress)(const GLubyte *);
+ getProcAddress = getdladdr("glXGetProcAddress");
+ if (!getProcAddress)
+ getProcAddress = getdladdr("glXGetProcAddressARB");
+
+ const char *glxstr = "";
+ const char *(*glXExtStr)(Display *, int)
+ = getdladdr("glXQueryExtensionsString");
+ if (glXExtStr)
+ glxstr = glXExtStr(display, ctx->vo->x11->screen);
+
+ getFunctions(gl, getProcAddress, glxstr, false);
+ if (!gl->GenPrograms && gl->GetString &&
+ gl->version < MPGL_VER(3, 0) &&
+ getProcAddress &&
+ strstr(gl->GetString(GL_EXTENSIONS), "GL_ARB_vertex_program"))
+ {
+ mp_msg(MSGT_VO, MSGL_WARN,
+ "Broken glXGetProcAddress detected, trying workaround\n");
+ getFunctions(gl, NULL, glxstr, false);
+ }
+
+ glx_ctx->context = new_context;
+
+ if (!glXIsDirect(vo->x11->display, new_context))
+ ctx->gl->mpgl_caps &= ~MPGL_CAP_NO_SW;
+
+ return true;
+}
+
+typedef GLXContext (*glXCreateContextAttribsARBProc)
+ (Display*, GLXFBConfig, GLXContext, Bool, const int*);
+
+static bool create_window_x11_gl3(struct MPGLContext *ctx, uint32_t d_width,
+ uint32_t d_height, uint32_t flags)
+{
+ struct glx_context *glx_ctx = ctx->priv;
+ struct vo *vo = ctx->vo;
+
+ if (!create_glx_window(ctx, d_width, d_height, flags))
+ return false;
+
+ if (glx_ctx->context)
+ return true;
+
+ glXCreateContextAttribsARBProc glXCreateContextAttribsARB =
+ (glXCreateContextAttribsARBProc)
+ glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
+
+ const char *glxstr = "";
+ const char *(*glXExtStr)(Display *, int)
+ = getdladdr("glXQueryExtensionsString");
+ if (glXExtStr)
+ glxstr = glXExtStr(vo->x11->display, vo->x11->screen);
+ bool have_ctx_ext = glxstr && !!strstr(glxstr, "GLX_ARB_create_context");
+
+ if (!(have_ctx_ext && glXCreateContextAttribsARB)) {
+ return false;
+ }
+
+ int gl_version = ctx->requested_gl_version;
+ int context_attribs[] = {
+ GLX_CONTEXT_MAJOR_VERSION_ARB, MPGL_VER_GET_MAJOR(gl_version),
+ GLX_CONTEXT_MINOR_VERSION_ARB, MPGL_VER_GET_MINOR(gl_version),
+ GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
+ GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB
+ | (flags & VOFLAG_GL_DEBUG ? GLX_CONTEXT_DEBUG_BIT_ARB : 0),
+ None
+ };
+ GLXContext context = glXCreateContextAttribsARB(vo->x11->display,
+ glx_ctx->fbc, 0, True,
+ context_attribs);
+ if (!context) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not create GLX context!\n");
+ return false;
+ }
+
+ // set context
+ if (!glXMakeCurrent(vo->x11->display, vo->x11->window, context)) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not set GLX context!\n");
+ glXDestroyContext(vo->x11->display, context);
+ return false;
+ }
+
+ glx_ctx->context = context;
+
+ getFunctions(ctx->gl, (void *)glXGetProcAddress, glxstr, true);
+
+ if (!glXIsDirect(vo->x11->display, context))
+ ctx->gl->mpgl_caps &= ~MPGL_CAP_NO_SW;
+
+ return true;
+}
+
+/**
+ * \brief free the VisualInfo and GLXContext of an OpenGL context.
+ * \ingroup glcontext
+ */
+static void releaseGlContext_x11(MPGLContext *ctx)
+{
+ struct glx_context *glx_ctx = ctx->priv;
+ XVisualInfo **vinfo = &glx_ctx->vinfo;
+ GLXContext *context = &glx_ctx->context;
+ Display *display = ctx->vo->x11->display;
+ GL *gl = ctx->gl;
+ if (*vinfo)
+ XFree(*vinfo);
+ *vinfo = NULL;
+ if (*context) {
+ if (gl->Finish)
+ gl->Finish();
+ glXMakeCurrent(display, None, NULL);
+ glXDestroyContext(display, *context);
+ }
+ *context = 0;
+}
+
+static void swapGlBuffers_x11(MPGLContext *ctx)
+{
+ glXSwapBuffers(ctx->vo->x11->display, ctx->vo->x11->window);
+}
+#endif
+
+
+struct backend {
+ const char *name;
+ enum MPGLType type;
+};
+
+static struct backend backends[] = {
+ {"auto", GLTYPE_AUTO},
+ {"cocoa", GLTYPE_COCOA},
+ {"win", GLTYPE_W32},
+ {"x11", GLTYPE_X11},
+ // mplayer-svn aliases (note that mplayer-svn couples these with the numeric
+ // values of the internal GLTYPE_* constants)
+ {"-1", GLTYPE_AUTO},
+ { "0", GLTYPE_W32},
+ { "1", GLTYPE_X11},
+
+ {0}
+};
+
+int mpgl_find_backend(const char *name)
+{
+ for (const struct backend *entry = backends; entry->name; entry++) {
+ if (strcmp(entry->name, name) == 0)
+ return entry->type;
+ }
+ return -1;
+}
+
+MPGLContext *mpgl_init(enum MPGLType type, struct vo *vo)
+{
+ MPGLContext *ctx;
+ if (type == GLTYPE_AUTO) {
+ ctx = mpgl_init(GLTYPE_COCOA, vo);
+ if (ctx)
+ return ctx;
+ ctx = mpgl_init(GLTYPE_W32, vo);
+ if (ctx)
+ return ctx;
+ return mpgl_init(GLTYPE_X11, vo);
+ }
+ ctx = talloc_zero(NULL, MPGLContext);
+ *ctx = (MPGLContext) {
+ .gl = talloc_zero(ctx, GL),
+ .type = type,
+ .vo = vo,
+ .requested_gl_version = MPGL_VER(3, 0),
+ .vo_init_ok = true,
+ };
+ switch (ctx->type) {
+#ifdef CONFIG_GL_COCOA
+ case GLTYPE_COCOA:
+ ctx->create_window_old = create_window_cocoa_old;
+ ctx->create_window_gl3 = create_window_cocoa_gl3;
+ ctx->releaseGlContext = releaseGlContext_cocoa;
+ ctx->swapGlBuffers = swapGlBuffers_cocoa;
+ ctx->check_events = vo_cocoa_check_events;
+ ctx->update_xinerama_info = vo_cocoa_update_xinerama_info;
+ ctx->fullscreen = vo_cocoa_fullscreen;
+ ctx->ontop = vo_cocoa_ontop;
+ ctx->vo_init = vo_cocoa_init;
+ ctx->pause = vo_cocoa_pause;
+ ctx->resume = vo_cocoa_resume;
+ ctx->vo_uninit = vo_cocoa_uninit;
+ break;
+#endif
+#ifdef CONFIG_GL_WIN32
+ case GLTYPE_W32:
+ ctx->priv = talloc_zero(ctx, struct w32_context);
+ ctx->create_window_old = create_window_w32_old;
+ ctx->create_window_gl3 = create_window_w32_gl3;
+ ctx->releaseGlContext = releaseGlContext_w32;
+ ctx->swapGlBuffers = swapGlBuffers_w32;
+ ctx->update_xinerama_info = w32_update_xinerama_info;
+ ctx->border = vo_w32_border;
+ ctx->check_events = vo_w32_check_events;
+ ctx->fullscreen = vo_w32_fullscreen;
+ ctx->ontop = vo_w32_ontop;
+ ctx->vo_init = vo_w32_init;
+ ctx->vo_uninit = vo_w32_uninit;
+ break;
+#endif
+#ifdef CONFIG_GL_X11
+ case GLTYPE_X11:
+ ctx->priv = talloc_zero(ctx, struct glx_context);
+ ctx->create_window_old = create_window_x11_old;
+ ctx->create_window_gl3 = create_window_x11_gl3;
+ ctx->releaseGlContext = releaseGlContext_x11;
+ ctx->swapGlBuffers = swapGlBuffers_x11;
+ ctx->update_xinerama_info = update_xinerama_info;
+ ctx->border = vo_x11_border;
+ ctx->check_events = vo_x11_check_events;
+ ctx->fullscreen = vo_x11_fullscreen;
+ ctx->ontop = vo_x11_ontop;
+ ctx->vo_init = vo_init;
+ ctx->vo_uninit = vo_x11_uninit;
+ break;
+#endif
+ }
+ if (ctx->vo_init && ctx->vo_init(vo))
+ return ctx;
+ talloc_free(ctx);
+ return NULL;
+}
+
+bool mpgl_destroy_window(struct MPGLContext *ctx)
+{
+ ctx->releaseGlContext(ctx);
+ *ctx->gl = (GL) {0};
+ // This is a caveat. At least on X11, this will recreate the X display
+ // connection. Also, if vo_init() fails, unspecified things will happen.
+ ctx->vo_uninit(ctx->vo);
+ ctx->vo_init_ok = ctx->vo_init(ctx->vo);
+ return ctx->vo_init_ok;
+}
+
+static bool create_window(struct MPGLContext *ctx, int gl_caps,
+ bool (*create)(struct MPGLContext *, uint32_t,
+ uint32_t, uint32_t),
+ uint32_t d_width, uint32_t d_height, uint32_t flags)
+{
+ if (!create || !ctx->vo_init_ok)
+ return false;
+ if (create(ctx, d_width, d_height, flags)) {
+ int missing = (ctx->gl->mpgl_caps & gl_caps) ^ gl_caps;
+ if (!missing) {
+ ctx->selected_create_window = create;
+ return true;
+ }
+ mp_msg(MSGT_VO, MSGL_WARN, "[gl] Missing OpenGL features:");
+ list_features(missing, MSGL_WARN, false);
+ if (missing & MPGL_CAP_NO_SW) {
+ mp_msg(MSGT_VO, MSGL_WARN, "[gl] Rejecting suspected software "
+ "OpenGL renderer.\n");
+ }
+ }
+ // If we tried to create a GL 3 context, and we're going to create a legacy
+ // context after this, the window should be recreated at least on X11.
+ mpgl_destroy_window(ctx);
+ return false;
+}
+
+bool mpgl_create_window(struct MPGLContext *ctx, int gl_caps, uint32_t d_width,
+ uint32_t d_height, uint32_t flags)
+{
+ assert(ctx->vo_init_ok);
+ if (ctx->selected_create_window)
+ return ctx->selected_create_window(ctx, d_width, d_height, flags);
+
+ bool allow_gl3 = !(gl_caps & MPGL_CAP_GL_LEGACY);
+ bool allow_legacy = !(gl_caps & MPGL_CAP_GL3);
+ gl_caps |= MPGL_CAP_GL;
+
+ if (allow_gl3 && create_window(ctx, gl_caps, ctx->create_window_gl3,
+ d_width, d_height, flags))
+ return true;
+
+ if (allow_legacy && create_window(ctx, gl_caps, ctx->create_window_old,
+ d_width, d_height, flags))
+ return true;
+
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] OpenGL context creation failed!\n");
+ return false;
+}
+
+void mpgl_uninit(MPGLContext *ctx)
+{
+ if (!ctx)
+ return;
+ if (ctx->vo_init_ok) {
+ ctx->releaseGlContext(ctx);
+ ctx->vo_uninit(ctx->vo);
+ }
+ talloc_free(ctx);
+}
+
+void mp_log_source(int mod, int lev, const char *src)
+{
+ int line = 1;
+ if (!src)
+ return;
+ while (*src) {
+ const char *end = strchr(src, '\n');
+ const char *next = end + 1;
+ if (!end)
+ next = end = src + strlen(src);
+ mp_msg(mod, lev, "[%3d] %.*s\n", line, (int)(end - src), src);
+ line++;
+ src = next;
+ }
+}
diff --git a/video/out/gl_common.h b/video/out/gl_common.h
new file mode 100644
index 0000000000..9816566097
--- /dev/null
+++ b/video/out/gl_common.h
@@ -0,0 +1,396 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * You can alternatively redistribute this file 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.
+ */
+
+#ifndef MPLAYER_GL_COMMON_H
+#define MPLAYER_GL_COMMON_H
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include "config.h"
+#include "mp_msg.h"
+
+#include "video_out.h"
+#include "csputils.h"
+
+#include "libmpcodecs/mp_image.h"
+
+#if defined(CONFIG_GL_COCOA) && !defined(CONFIG_GL_X11)
+#ifdef GL_VERSION_3_0
+#include <OpenGL/gl3.h>
+#else
+#include <OpenGL/gl.h>
+#endif
+#include <OpenGL/glext.h>
+#else
+#include <GL/gl.h>
+#include <GL/glext.h>
+#endif
+
+#include "libvo/gl_header_fixes.h"
+
+struct GL;
+typedef struct GL GL;
+
+void glAdjustAlignment(GL *gl, int stride);
+
+int glFindFormat(uint32_t format, int have_texture_rg, int *bpp,
+ GLint *gl_texfmt, GLenum *gl_format, GLenum *gl_type);
+int glFmt2bpp(GLenum format, GLenum type);
+void glCreateClearTex(GL *gl, GLenum target, GLenum fmt, GLenum format,
+ GLenum type, GLint filter, int w, int h,
+ unsigned char val);
+int glCreatePPMTex(GL *gl, GLenum target, GLenum fmt, GLint filter,
+ FILE *f, int *width, int *height, int *maxval);
+void glUploadTex(GL *gl, GLenum target, GLenum format, GLenum type,
+ const void *dataptr, int stride,
+ int x, int y, int w, int h, int slice);
+void glClearTex(GL *gl, GLenum target, GLenum format, GLenum type,
+ int x, int y, int w, int h, uint8_t val, void **scratch);
+void glDownloadTex(GL *gl, GLenum target, GLenum format, GLenum type,
+ void *dataptr, int stride);
+void glDrawTex(GL *gl, GLfloat x, GLfloat y, GLfloat w, GLfloat h,
+ GLfloat tx, GLfloat ty, GLfloat tw, GLfloat th,
+ int sx, int sy, int rect_tex, int is_yv12, int flip);
+int loadGPUProgram(GL *gl, GLenum target, char *prog);
+void glCheckError(GL *gl, const char *info);
+mp_image_t *glGetWindowScreenshot(GL *gl);
+
+/** \addtogroup glconversion
+ * \{ */
+//! do not use YUV conversion, this should always stay 0
+#define YUV_CONVERSION_NONE 0
+//! use nVidia specific register combiners for YUV conversion
+//! implementation has been removed
+#define YUV_CONVERSION_COMBINERS 1
+//! use a fragment program for YUV conversion
+#define YUV_CONVERSION_FRAGMENT 2
+//! use a fragment program for YUV conversion with gamma using POW
+#define YUV_CONVERSION_FRAGMENT_POW 3
+//! use a fragment program with additional table lookup for YUV conversion
+#define YUV_CONVERSION_FRAGMENT_LOOKUP 4
+//! use ATI specific register combiners ("fragment program")
+#define YUV_CONVERSION_COMBINERS_ATI 5
+//! use a fragment program with 3D table lookup for YUV conversion
+#define YUV_CONVERSION_FRAGMENT_LOOKUP3D 6
+//! use ATI specific "text" register combiners ("fragment program")
+#define YUV_CONVERSION_TEXT_FRAGMENT 7
+//! use normal bilinear scaling for textures
+#define YUV_SCALER_BILIN 0
+//! use higher quality bicubic scaling for textures
+#define YUV_SCALER_BICUB 1
+//! use cubic scaling in X and normal linear scaling in Y direction
+#define YUV_SCALER_BICUB_X 2
+//! use cubic scaling without additional lookup texture
+#define YUV_SCALER_BICUB_NOTEX 3
+#define YUV_SCALER_UNSHARP 4
+#define YUV_SCALER_UNSHARP2 5
+//! mask for conversion type
+#define YUV_CONVERSION_MASK 0xF
+//! mask for scaler type
+#define YUV_SCALER_MASK 0xF
+//! shift value for luminance scaler type
+#define YUV_LUM_SCALER_SHIFT 8
+//! shift value for chrominance scaler type
+#define YUV_CHROM_SCALER_SHIFT 12
+//! extract conversion out of type
+#define YUV_CONVERSION(t) ((t) & YUV_CONVERSION_MASK)
+//! extract luminance scaler out of type
+#define YUV_LUM_SCALER(t) (((t) >> YUV_LUM_SCALER_SHIFT) & YUV_SCALER_MASK)
+//! extract chrominance scaler out of type
+#define YUV_CHROM_SCALER(t) (((t) >> YUV_CHROM_SCALER_SHIFT) & YUV_SCALER_MASK)
+#define SET_YUV_CONVERSION(c) ((c) & YUV_CONVERSION_MASK)
+#define SET_YUV_LUM_SCALER(s) (((s) & YUV_SCALER_MASK) << YUV_LUM_SCALER_SHIFT)
+#define SET_YUV_CHROM_SCALER(s) (((s) & YUV_SCALER_MASK) << YUV_CHROM_SCALER_SHIFT)
+//! returns whether the yuv conversion supports large brightness range etc.
+static inline int glYUVLargeRange(int conv)
+{
+ switch (conv) {
+ case YUV_CONVERSION_NONE:
+ case YUV_CONVERSION_COMBINERS_ATI:
+ case YUV_CONVERSION_FRAGMENT_LOOKUP3D:
+ case YUV_CONVERSION_TEXT_FRAGMENT:
+ return 0;
+ }
+ return 1;
+}
+/** \} */
+
+typedef struct {
+ GLenum target;
+ int type;
+ struct mp_csp_params csp_params;
+ int texw;
+ int texh;
+ int chrom_texw;
+ int chrom_texh;
+ float filter_strength;
+ float noise_strength;
+} gl_conversion_params_t;
+
+int glAutodetectYUVConversion(GL *gl);
+void glSetupYUVConversion(GL *gl, gl_conversion_params_t *params);
+void glEnableYUVConversion(GL *gl, GLenum target, int type);
+void glDisableYUVConversion(GL *gl, GLenum target, int type);
+
+#define GL_3D_RED_CYAN 1
+#define GL_3D_GREEN_MAGENTA 2
+#define GL_3D_QUADBUFFER 3
+
+void glEnable3DLeft(GL *gl, int type);
+void glEnable3DRight(GL *gl, int type);
+void glDisable3D(GL *gl, int type);
+
+enum MPGLType {
+ GLTYPE_AUTO,
+ GLTYPE_COCOA,
+ GLTYPE_W32,
+ GLTYPE_X11,
+};
+
+enum {
+ MPGL_CAP_GL = (1 << 0), // GL was successfully loaded
+ MPGL_CAP_GL_LEGACY = (1 << 1), // GL 1.1 (but not 3.x)
+ MPGL_CAP_GL2 = (1 << 2), // GL 2.0 (3.x core subset)
+ MPGL_CAP_GL21 = (1 << 3), // GL 2.1 (3.x core subset)
+ MPGL_CAP_GL3 = (1 << 4), // GL 3.x core
+ MPGL_CAP_FB = (1 << 5),
+ MPGL_CAP_VAO = (1 << 6),
+ MPGL_CAP_SRGB_TEX = (1 << 7),
+ MPGL_CAP_SRGB_FB = (1 << 8),
+ MPGL_CAP_FLOAT_TEX = (1 << 9),
+ MPGL_CAP_TEX_RG = (1 << 10), // GL_ARB_texture_rg / GL 3.x
+ MPGL_CAP_NO_SW = (1 << 30), // used to block sw. renderers
+};
+
+#define MPGL_VER(major, minor) (((major) << 16) | (minor))
+#define MPGL_VER_GET_MAJOR(ver) ((ver) >> 16)
+#define MPGL_VER_GET_MINOR(ver) ((ver) & ((1 << 16) - 1))
+
+#define MPGL_VER_P(ver) MPGL_VER_GET_MAJOR(ver), MPGL_VER_GET_MINOR(ver)
+
+typedef struct MPGLContext {
+ GL *gl;
+ enum MPGLType type;
+ struct vo *vo;
+
+ // Bit size of each component in the created framebuffer. 0 if unknown.
+ int depth_r, depth_g, depth_b;
+
+ // GL version requested from create_window_gl3 backend.
+ // (Might be different from the actual version in gl->version.)
+ int requested_gl_version;
+
+ void (*swapGlBuffers)(struct MPGLContext *);
+ int (*check_events)(struct vo *vo);
+ void (*fullscreen)(struct vo *vo);
+ int (*vo_init)(struct vo *vo);
+ void (*vo_uninit)(struct vo *vo);
+ void (*releaseGlContext)(struct MPGLContext *);
+
+ // Creates GL 1.x/2.x legacy context.
+ bool (*create_window_old)(struct MPGLContext *ctx, uint32_t d_width,
+ uint32_t d_height, uint32_t flags);
+
+ // Creates GL 3.x core context.
+ bool (*create_window_gl3)(struct MPGLContext *ctx, uint32_t d_width,
+ uint32_t d_height, uint32_t flags);
+
+ // optional
+ void (*pause)(struct vo *vo);
+ void (*resume)(struct vo *vo);
+ void (*ontop)(struct vo *vo);
+ void (*border)(struct vo *vo);
+ void (*update_xinerama_info)(struct vo *vo);
+
+ // For free use by the backend.
+ void *priv;
+ // Internal to gl_common.c.
+ bool (*selected_create_window)(struct MPGLContext *ctx, uint32_t d_width,
+ uint32_t d_height, uint32_t flags);
+ bool vo_init_ok;
+} MPGLContext;
+
+int mpgl_find_backend(const char *name);
+
+MPGLContext *mpgl_init(enum MPGLType type, struct vo *vo);
+void mpgl_uninit(MPGLContext *ctx);
+
+// Create a VO window and create a GL context on it.
+// (Calls create_window_gl3 or create_window+setGlWindow.)
+// gl_caps: bitfield of MPGL_CAP_* (required GL version and feature set)
+// flags: passed to the backend's create window function
+// Returns success.
+bool mpgl_create_window(struct MPGLContext *ctx, int gl_caps, uint32_t d_width,
+ uint32_t d_height, uint32_t flags);
+
+// Destroy the window, without resetting GL3 vs. GL2 context choice.
+// If this fails (false), mpgl_uninit(ctx) must be called.
+bool mpgl_destroy_window(struct MPGLContext *ctx);
+
+// print a multi line string with line numbers (e.g. for shader sources)
+// mod, lev: module and log level, as in mp_msg()
+void mp_log_source(int mod, int lev, const char *src);
+
+//function pointers loaded from the OpenGL library
+struct GL {
+ int version; // MPGL_VER() mangled
+ int glsl_version; // e.g. 130 for GLSL 1.30
+ char *extensions; // Equivalent to GL_EXTENSIONS
+ int mpgl_caps; // Bitfield of MPGL_CAP_* constants
+
+ void (GLAPIENTRY *Begin)(GLenum);
+ void (GLAPIENTRY *End)(void);
+ void (GLAPIENTRY *Viewport)(GLint, GLint, GLsizei, GLsizei);
+ void (GLAPIENTRY *MatrixMode)(GLenum);
+ void (GLAPIENTRY *LoadIdentity)(void);
+ void (GLAPIENTRY *Translated)(double, double, double);
+ void (GLAPIENTRY *Scaled)(double, double, double);
+ void (GLAPIENTRY *Ortho)(double, double, double, double, double,double);
+ void (GLAPIENTRY *PushMatrix)(void);
+ void (GLAPIENTRY *PopMatrix)(void);
+ void (GLAPIENTRY *Clear)(GLbitfield);
+ GLuint (GLAPIENTRY *GenLists)(GLsizei);
+ void (GLAPIENTRY *DeleteLists)(GLuint, GLsizei);
+ void (GLAPIENTRY *NewList)(GLuint, GLenum);
+ void (GLAPIENTRY *EndList)(void);
+ void (GLAPIENTRY *CallList)(GLuint);
+ void (GLAPIENTRY *CallLists)(GLsizei, GLenum, const GLvoid *);
+ void (GLAPIENTRY *GenTextures)(GLsizei, GLuint *);
+ void (GLAPIENTRY *DeleteTextures)(GLsizei, const GLuint *);
+ void (GLAPIENTRY *TexEnvi)(GLenum, GLenum, GLint);
+ void (GLAPIENTRY *Color4ub)(GLubyte, GLubyte, GLubyte, GLubyte);
+ void (GLAPIENTRY *Color4f)(GLfloat, GLfloat, GLfloat, GLfloat);
+ void (GLAPIENTRY *ClearColor)(GLclampf, GLclampf, GLclampf, GLclampf);
+ void (GLAPIENTRY *Enable)(GLenum);
+ void (GLAPIENTRY *Disable)(GLenum);
+ const GLubyte *(GLAPIENTRY * GetString)(GLenum);
+ void (GLAPIENTRY *DrawBuffer)(GLenum);
+ void (GLAPIENTRY *DepthMask)(GLboolean);
+ void (GLAPIENTRY *BlendFunc)(GLenum, GLenum);
+ void (GLAPIENTRY *Flush)(void);
+ void (GLAPIENTRY *Finish)(void);
+ void (GLAPIENTRY *PixelStorei)(GLenum, GLint);
+ void (GLAPIENTRY *TexImage1D)(GLenum, GLint, GLint, GLsizei, GLint,
+ GLenum, GLenum, const GLvoid *);
+ void (GLAPIENTRY *TexImage2D)(GLenum, GLint, GLint, GLsizei, GLsizei,
+ GLint, GLenum, GLenum, const GLvoid *);
+ void (GLAPIENTRY *TexSubImage2D)(GLenum, GLint, GLint, GLint,
+ GLsizei, GLsizei, GLenum, GLenum,
+ const GLvoid *);
+ void (GLAPIENTRY *GetTexImage)(GLenum, GLint, GLenum, GLenum, GLvoid *);
+ void (GLAPIENTRY *TexParameteri)(GLenum, GLenum, GLint);
+ void (GLAPIENTRY *TexParameterf)(GLenum, GLenum, GLfloat);
+ void (GLAPIENTRY *TexParameterfv)(GLenum, GLenum, const GLfloat *);
+ void (GLAPIENTRY *TexCoord2f)(GLfloat, GLfloat);
+ void (GLAPIENTRY *TexCoord2fv)(const GLfloat *);
+ void (GLAPIENTRY *Vertex2f)(GLfloat, GLfloat);
+ void (GLAPIENTRY *GetIntegerv)(GLenum, GLint *);
+ void (GLAPIENTRY *GetBooleanv)(GLenum, GLboolean *);
+ void (GLAPIENTRY *ColorMask)(GLboolean, GLboolean, GLboolean, GLboolean);
+ void (GLAPIENTRY *ReadPixels)(GLint, GLint, GLsizei, GLsizei, GLenum,
+ GLenum, GLvoid *);
+ void (GLAPIENTRY *ReadBuffer)(GLenum);
+ void (GLAPIENTRY *VertexPointer)(GLint, GLenum, GLsizei, const GLvoid *);
+ void (GLAPIENTRY *ColorPointer)(GLint, GLenum, GLsizei, const GLvoid *);
+ void (GLAPIENTRY *TexCoordPointer)(GLint, GLenum, GLsizei, const GLvoid *);
+ void (GLAPIENTRY *DrawArrays)(GLenum, GLint, GLsizei);
+ void (GLAPIENTRY *EnableClientState)(GLenum);
+ void (GLAPIENTRY *DisableClientState)(GLenum);
+ GLenum (GLAPIENTRY *GetError)(void);
+
+ void (GLAPIENTRY *GenBuffers)(GLsizei, GLuint *);
+ void (GLAPIENTRY *DeleteBuffers)(GLsizei, const GLuint *);
+ void (GLAPIENTRY *BindBuffer)(GLenum, GLuint);
+ GLvoid * (GLAPIENTRY * MapBuffer)(GLenum, GLenum);
+ GLboolean (GLAPIENTRY *UnmapBuffer)(GLenum);
+ void (GLAPIENTRY *BufferData)(GLenum, intptr_t, const GLvoid *, GLenum);
+ void (GLAPIENTRY *ActiveTexture)(GLenum);
+ void (GLAPIENTRY *BindTexture)(GLenum, GLuint);
+ void (GLAPIENTRY *MultiTexCoord2f)(GLenum, GLfloat, GLfloat);
+ void (GLAPIENTRY *GenPrograms)(GLsizei, GLuint *);
+ void (GLAPIENTRY *DeletePrograms)(GLsizei, const GLuint *);
+ void (GLAPIENTRY *BindProgram)(GLenum, GLuint);
+ void (GLAPIENTRY *ProgramString)(GLenum, GLenum, GLsizei, const GLvoid *);
+ void (GLAPIENTRY *GetProgramivARB)(GLenum, GLenum, GLint *);
+ void (GLAPIENTRY *ProgramEnvParameter4f)(GLenum, GLuint, GLfloat, GLfloat,
+ GLfloat, GLfloat);
+ int (GLAPIENTRY *SwapInterval)(int);
+ void (GLAPIENTRY *TexImage3D)(GLenum, GLint, GLenum, GLsizei, GLsizei,
+ GLsizei, GLint, GLenum, GLenum,
+ const GLvoid *);
+
+ void (GLAPIENTRY *BeginFragmentShader)(void);
+ void (GLAPIENTRY *EndFragmentShader)(void);
+ void (GLAPIENTRY *SampleMap)(GLuint, GLuint, GLenum);
+ void (GLAPIENTRY *ColorFragmentOp2)(GLenum, GLuint, GLuint, GLuint, GLuint,
+ GLuint, GLuint, GLuint, GLuint, GLuint);
+ void (GLAPIENTRY *ColorFragmentOp3)(GLenum, GLuint, GLuint, GLuint, GLuint,
+ GLuint, GLuint, GLuint, GLuint, GLuint,
+ GLuint, GLuint, GLuint);
+ void (GLAPIENTRY *SetFragmentShaderConstant)(GLuint, const GLfloat *);
+
+ void (GLAPIENTRY *GenVertexArrays)(GLsizei, GLuint *);
+ void (GLAPIENTRY *BindVertexArray)(GLuint);
+ GLint (GLAPIENTRY *GetAttribLocation)(GLuint, const GLchar *);
+ void (GLAPIENTRY *EnableVertexAttribArray)(GLuint);
+ void (GLAPIENTRY *DisableVertexAttribArray)(GLuint);
+ void (GLAPIENTRY *VertexAttribPointer)(GLuint, GLint, GLenum, GLboolean,
+ GLsizei, const GLvoid *);
+ void (GLAPIENTRY *DeleteVertexArrays)(GLsizei, const GLuint *);
+ void (GLAPIENTRY *UseProgram)(GLuint);
+ GLint (GLAPIENTRY *GetUniformLocation)(GLuint, const GLchar *);
+ void (GLAPIENTRY *CompileShader)(GLuint);
+ GLuint (GLAPIENTRY *CreateProgram)(void);
+ GLuint (GLAPIENTRY *CreateShader)(GLenum);
+ void (GLAPIENTRY *ShaderSource)(GLuint, GLsizei, const GLchar **,
+ const GLint *);
+ void (GLAPIENTRY *LinkProgram)(GLuint);
+ void (GLAPIENTRY *AttachShader)(GLuint, GLuint);
+ void (GLAPIENTRY *DeleteShader)(GLuint);
+ void (GLAPIENTRY *DeleteProgram)(GLuint);
+ void (GLAPIENTRY *GetShaderInfoLog)(GLuint, GLsizei, GLsizei *, GLchar *);
+ void (GLAPIENTRY *GetShaderiv)(GLuint, GLenum, GLint *);
+ void (GLAPIENTRY *GetProgramInfoLog)(GLuint, GLsizei, GLsizei *, GLchar *);
+ void (GLAPIENTRY *GetProgramiv)(GLenum, GLenum, GLint *);
+ const GLubyte* (GLAPIENTRY *GetStringi)(GLenum, GLuint);
+ void (GLAPIENTRY *BindAttribLocation)(GLuint, GLuint, const GLchar *);
+ void (GLAPIENTRY *BindFramebuffer)(GLenum, GLuint);
+ void (GLAPIENTRY *GenFramebuffers)(GLsizei, GLuint *);
+ void (GLAPIENTRY *DeleteFramebuffers)(GLsizei, const GLuint *);
+ GLenum (GLAPIENTRY *CheckFramebufferStatus)(GLenum);
+ void (GLAPIENTRY *FramebufferTexture2D)(GLenum, GLenum, GLenum, GLuint,
+ GLint);
+
+ void (GLAPIENTRY *Uniform1f)(GLint, GLfloat);
+ void (GLAPIENTRY *Uniform2f)(GLint, GLfloat, GLfloat);
+ void (GLAPIENTRY *Uniform3f)(GLint, GLfloat, GLfloat, GLfloat);
+ void (GLAPIENTRY *Uniform4f)(GLint, GLfloat, GLfloat, GLfloat, GLfloat);
+ void (GLAPIENTRY *Uniform1i)(GLint, GLint);
+ void (GLAPIENTRY *UniformMatrix3fv)(GLint, GLsizei, GLboolean,
+ const GLfloat *);
+ void (GLAPIENTRY *UniformMatrix4x3fv)(GLint, GLsizei, GLboolean,
+ const GLfloat *);
+};
+
+#endif /* MPLAYER_GL_COMMON_H */
diff --git a/video/out/gl_header_fixes.h b/video/out/gl_header_fixes.h
new file mode 100644
index 0000000000..d149a9970a
--- /dev/null
+++ b/video/out/gl_header_fixes.h
@@ -0,0 +1,257 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * You can alternatively redistribute this file 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.
+ */
+
+// workaround for some gl.h headers
+#ifndef GLAPIENTRY
+#ifdef APIENTRY
+#define GLAPIENTRY APIENTRY
+#elif defined(CONFIG_GL_WIN32)
+#define GLAPIENTRY __stdcall
+#else
+#define GLAPIENTRY
+#endif
+#endif
+
+/**
+ * \defgroup glextdefines OpenGL extension defines
+ *
+ * conditionally define all extension defines used.
+ * vendor specific extensions should be marked as such
+ * (e.g. _NV), _ARB is not used to ease readability.
+ * \{
+ */
+#ifndef GL_TEXTURE_3D
+#define GL_TEXTURE_3D 0x806F
+#endif
+#ifndef GL_TEXTURE_WRAP_R
+#define GL_TEXTURE_WRAP_R 0x8072
+#endif
+#ifndef GL_CLAMP_TO_EDGE
+#define GL_CLAMP_TO_EDGE 0x812F
+#endif
+#ifndef GL_GENERATE_MIPMAP
+#define GL_GENERATE_MIPMAP 0x8191
+#endif
+#ifndef GL_TEXT_FRAGMENT_SHADER_ATI
+#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200
+#endif
+#ifndef GL_FRAGMENT_SHADER_ATI
+#define GL_FRAGMENT_SHADER_ATI 0x8920
+#endif
+#ifndef GL_NUM_FRAGMENT_REGISTERS_ATI
+#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E
+#endif
+#ifndef GL_REG_0_ATI
+#define GL_REG_0_ATI 0x8921
+#endif
+#ifndef GL_REG_1_ATI
+#define GL_REG_1_ATI 0x8922
+#endif
+#ifndef GL_REG_2_ATI
+#define GL_REG_2_ATI 0x8923
+#endif
+#ifndef GL_CON_0_ATI
+#define GL_CON_0_ATI 0x8941
+#endif
+#ifndef GL_CON_1_ATI
+#define GL_CON_1_ATI 0x8942
+#endif
+#ifndef GL_CON_2_ATI
+#define GL_CON_2_ATI 0x8943
+#endif
+#ifndef GL_CON_3_ATI
+#define GL_CON_3_ATI 0x8944
+#endif
+#ifndef GL_ADD_ATI
+#define GL_ADD_ATI 0x8963
+#endif
+#ifndef GL_MUL_ATI
+#define GL_MUL_ATI 0x8964
+#endif
+#ifndef GL_MAD_ATI
+#define GL_MAD_ATI 0x8968
+#endif
+#ifndef GL_SWIZZLE_STR_ATI
+#define GL_SWIZZLE_STR_ATI 0x8976
+#endif
+#ifndef GL_4X_BIT_ATI
+#define GL_4X_BIT_ATI 2
+#endif
+#ifndef GL_8X_BIT_ATI
+#define GL_8X_BIT_ATI 4
+#endif
+#ifndef GL_BIAS_BIT_ATI
+#define GL_BIAS_BIT_ATI 8
+#endif
+#ifndef GL_MAX_TEXTURE_UNITS
+#define GL_MAX_TEXTURE_UNITS 0x84E2
+#endif
+#ifndef GL_TEXTURE0
+#define GL_TEXTURE0 0x84C0
+#endif
+#ifndef GL_TEXTURE1
+#define GL_TEXTURE1 0x84C1
+#endif
+#ifndef GL_TEXTURE2
+#define GL_TEXTURE2 0x84C2
+#endif
+#ifndef GL_TEXTURE3
+#define GL_TEXTURE3 0x84C3
+#endif
+#ifndef GL_TEXTURE_RECTANGLE
+#define GL_TEXTURE_RECTANGLE 0x84F5
+#endif
+#ifndef GL_PIXEL_UNPACK_BUFFER
+#define GL_PIXEL_UNPACK_BUFFER 0x88EC
+#endif
+#ifndef GL_STREAM_DRAW
+#define GL_STREAM_DRAW 0x88E0
+#endif
+#ifndef GL_DYNAMIC_DRAW
+#define GL_DYNAMIC_DRAW 0x88E8
+#endif
+#ifndef GL_WRITE_ONLY
+#define GL_WRITE_ONLY 0x88B9
+#endif
+#ifndef GL_BGR
+#define GL_BGR 0x80E0
+#endif
+#ifndef GL_BGRA
+#define GL_BGRA 0x80E1
+#endif
+#ifndef GL_UNSIGNED_BYTE_3_3_2
+#define GL_UNSIGNED_BYTE_3_3_2 0x8032
+#endif
+#ifndef GL_UNSIGNED_BYTE_2_3_3_REV
+#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362
+#endif
+#ifndef GL_UNSIGNED_SHORT_4_4_4_4
+#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033
+#endif
+#ifndef GL_UNSIGNED_SHORT_4_4_4_4_REV
+#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365
+#endif
+#ifndef GL_UNSIGNED_SHORT_5_6_5
+#define GL_UNSIGNED_SHORT_5_6_5 0x8363
+#endif
+#ifndef GL_UNSIGNED_INT_8_8_8_8
+#define GL_UNSIGNED_INT_8_8_8_8 0x8035
+#endif
+#ifndef GL_UNSIGNED_INT_8_8_8_8_REV
+#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
+#endif
+#ifndef GL_UNSIGNED_SHORT_5_6_5_REV
+#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364
+#endif
+#ifndef GL_UNSIGNED_INT_10_10_10_2
+#define GL_UNSIGNED_INT_10_10_10_2 0x8036
+#endif
+#ifndef GL_UNSIGNED_INT_2_10_10_10_REV
+#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368
+#endif
+#ifndef GL_UNSIGNED_SHORT_5_5_5_1
+#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034
+#endif
+#ifndef GL_UNSIGNED_SHORT_1_5_5_5_REV
+#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366
+#endif
+#ifndef GL_UNSIGNED_SHORT_8_8
+#define GL_UNSIGNED_SHORT_8_8 0x85BA
+#endif
+#ifndef GL_UNSIGNED_SHORT_8_8_REV
+#define GL_UNSIGNED_SHORT_8_8_REV 0x85BB
+#endif
+#ifndef GL_YCBCR_MESA
+#define GL_YCBCR_MESA 0x8757
+#endif
+#ifndef GL_RGB32F
+#define GL_RGB32F 0x8815
+#endif
+#ifndef GL_FLOAT_RGB32_NV
+#define GL_FLOAT_RGB32_NV 0x8889
+#endif
+#ifndef GL_LUMINANCE16
+#define GL_LUMINANCE16 0x8042
+#endif
+#ifndef GL_R16
+#define GL_R16 0x822A
+#endif
+#ifndef GL_UNPACK_CLIENT_STORAGE_APPLE
+#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2
+#endif
+#ifndef GL_FRAGMENT_PROGRAM
+#define GL_FRAGMENT_PROGRAM 0x8804
+#endif
+#ifndef GL_PROGRAM_FORMAT_ASCII
+#define GL_PROGRAM_FORMAT_ASCII 0x8875
+#endif
+#ifndef GL_PROGRAM_ERROR_POSITION
+#define GL_PROGRAM_ERROR_POSITION 0x864B
+#endif
+#ifndef GL_MAX_TEXTURE_IMAGE_UNITS
+#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872
+#endif
+#ifndef GL_PROGRAM_ERROR_STRING
+#define GL_PROGRAM_ERROR_STRING 0x8874
+#endif
+/** \} */ // end of glextdefines group
+
+
+#if defined(CONFIG_GL_WIN32) && !defined(WGL_CONTEXT_MAJOR_VERSION_ARB)
+/* these are supposed to be defined in wingdi.h but mingw's is too old */
+/* only the bits actually used by mplayer are defined */
+/* reference: http://www.opengl.org/registry/specs/ARB/wgl_create_context.txt */
+
+#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
+#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
+#define WGL_CONTEXT_FLAGS_ARB 0x2094
+#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
+#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002
+#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
+#endif
+
+// Define just enough constants to make the OpenGL 3 code compile against
+// older SDKs. Values are taken straight from OpenGL/gl3.h
+#if defined __APPLE__ && !(defined GL_VERSION_3_0)
+#define GL_RGBA16F 0x881A
+#define GL_RGB16F 0x881B
+#define GL_MAJOR_VERSION 0x821B
+#define GL_MINOR_VERSION 0x821C
+#define GL_NUM_EXTENSIONS 0x821D
+
+#ifndef GL_ARB_framebuffer_sRGB
+#define GL_FRAMEBUFFER_SRGB 0x8DB9
+#endif
+#endif
+
+// FreeBSD 10.0-CURRENT lacks the GLX_ARB_create_context extension completely
+#ifndef GLX_CONTEXT_MAJOR_VERSION_ARB
+#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
+#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
+#define GLX_CONTEXT_FLAGS_ARB 0x2094
+#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126
+#define GLX_CONTEXT_DEBUG_BIT_ARB 0x0001
+#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002
+#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
+#define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
+#endif
diff --git a/video/out/gl_osd.c b/video/out/gl_osd.c
new file mode 100644
index 0000000000..81485cabe9
--- /dev/null
+++ b/video/out/gl_osd.c
@@ -0,0 +1,324 @@
+/*
+ * This file is part of mplayer.
+ *
+ * mplayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mplayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mplayer. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <assert.h>
+#include <libavutil/common.h>
+
+#include "bitmap_packer.h"
+
+#include "gl_osd.h"
+
+struct osd_fmt_entry {
+ GLint internal_format;
+ GLint format;
+ GLenum type;
+};
+
+// glBlendFunc() arguments
+static const int blend_factors[SUBBITMAP_COUNT][2] = {
+ [SUBBITMAP_LIBASS] = {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA},
+ [SUBBITMAP_RGBA] = {GL_ONE, GL_ONE_MINUS_SRC_ALPHA},
+};
+
+static const struct osd_fmt_entry osd_to_gl3_formats[SUBBITMAP_COUNT] = {
+ [SUBBITMAP_LIBASS] = {GL_RED, GL_RED, GL_UNSIGNED_BYTE},
+ [SUBBITMAP_RGBA] = {GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE},
+};
+
+static const struct osd_fmt_entry osd_to_gl_legacy_formats[SUBBITMAP_COUNT] = {
+ [SUBBITMAP_LIBASS] = {GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE},
+ [SUBBITMAP_RGBA] = {GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE},
+};
+
+struct mpgl_osd *mpgl_osd_init(GL *gl, bool legacy)
+{
+ GLint max_texture_size;
+ gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
+
+ struct mpgl_osd *ctx = talloc_ptrtype(NULL, ctx);
+ *ctx = (struct mpgl_osd) {
+ .gl = gl,
+ .fmt_table = legacy ? osd_to_gl_legacy_formats : osd_to_gl3_formats,
+ .scratch = talloc_zero_size(ctx, 1),
+ };
+
+ for (int n = 0; n < MAX_OSD_PARTS; n++) {
+ struct mpgl_osd_part *p = talloc_ptrtype(ctx, p);
+ *p = (struct mpgl_osd_part) {
+ .packer = talloc_struct(p, struct bitmap_packer, {
+ .w_max = max_texture_size,
+ .h_max = max_texture_size,
+ }),
+ };
+ ctx->parts[n] = p;
+ }
+
+ for (int n = 0; n < SUBBITMAP_COUNT; n++)
+ ctx->formats[n] = ctx->fmt_table[n].type != 0;
+
+ return ctx;
+}
+
+void mpgl_osd_destroy(struct mpgl_osd *ctx)
+{
+ GL *gl = ctx->gl;
+
+ for (int n = 0; n < MAX_OSD_PARTS; n++) {
+ struct mpgl_osd_part *p = ctx->parts[n];
+ gl->DeleteTextures(1, &p->texture);
+ if (gl->DeleteBuffers)
+ gl->DeleteBuffers(1, &p->buffer);
+ }
+ talloc_free(ctx);
+}
+
+static bool upload_pbo(struct mpgl_osd *ctx, struct mpgl_osd_part *osd,
+ struct sub_bitmaps *imgs)
+{
+ GL *gl = ctx->gl;
+ bool success = true;
+ struct osd_fmt_entry fmt = ctx->fmt_table[imgs->format];
+ int pix_stride = glFmt2bpp(fmt.format, fmt.type);
+
+ if (!osd->buffer) {
+ gl->GenBuffers(1, &osd->buffer);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, osd->buffer);
+ gl->BufferData(GL_PIXEL_UNPACK_BUFFER, osd->w * osd->h * pix_stride,
+ NULL, GL_DYNAMIC_COPY);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, osd->buffer);
+ char *data = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
+ if (!data) {
+ success = false;
+ } else {
+ struct pos bb[2];
+ packer_get_bb(osd->packer, bb);
+ size_t stride = osd->w * pix_stride;
+ packer_copy_subbitmaps(osd->packer, imgs, data, pix_stride, stride);
+ if (!gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER))
+ success = false;
+ glUploadTex(gl, GL_TEXTURE_2D, fmt.format, fmt.type, NULL, stride,
+ bb[0].x, bb[0].y, bb[1].x - bb[0].x, bb[1].y - bb[0].y,
+ 0);
+ }
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+
+ if (!success) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Error: can't upload subtitles! "
+ "Remove the 'pbo' suboption.\n");
+ }
+
+ return success;
+}
+
+static void upload_tex(struct mpgl_osd *ctx, struct mpgl_osd_part *osd,
+ struct sub_bitmaps *imgs)
+{
+ struct osd_fmt_entry fmt = ctx->fmt_table[imgs->format];
+ if (osd->packer->padding) {
+ struct pos bb[2];
+ packer_get_bb(osd->packer, bb);
+ glClearTex(ctx->gl, GL_TEXTURE_2D, fmt.format, fmt.type,
+ bb[0].x, bb[0].y, bb[1].x - bb[0].y, bb[1].y - bb[0].y,
+ 0, &ctx->scratch);
+ }
+ for (int n = 0; n < osd->packer->count; n++) {
+ struct sub_bitmap *s = &imgs->parts[n];
+ struct pos p = osd->packer->result[n];
+
+ glUploadTex(ctx->gl, GL_TEXTURE_2D, fmt.format, fmt.type,
+ s->bitmap, s->stride, p.x, p.y, s->w, s->h, 0);
+ }
+}
+
+static bool upload_osd(struct mpgl_osd *ctx, struct mpgl_osd_part *osd,
+ struct sub_bitmaps *imgs)
+{
+ GL *gl = ctx->gl;
+
+ // assume 2x2 filter on scaling
+ osd->packer->padding = ctx->scaled || imgs->scaled;
+ int r = packer_pack_from_subbitmaps(osd->packer, imgs);
+ if (r < 0) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] OSD bitmaps do not fit on "
+ "a surface with the maximum supported size %dx%d.\n",
+ osd->packer->w_max, osd->packer->h_max);
+ return false;
+ }
+
+ struct osd_fmt_entry fmt = ctx->fmt_table[imgs->format];
+ assert(fmt.type != 0);
+
+ if (!osd->texture)
+ gl->GenTextures(1, &osd->texture);
+
+ gl->BindTexture(GL_TEXTURE_2D, osd->texture);
+
+ if (osd->packer->w > osd->w || osd->packer->h > osd->h
+ || osd->format != imgs->format)
+ {
+ osd->format = imgs->format;
+ osd->w = FFMAX(32, osd->packer->w);
+ osd->h = FFMAX(32, osd->packer->h);
+
+ gl->TexImage2D(GL_TEXTURE_2D, 0, fmt.internal_format, osd->w, osd->h,
+ 0, fmt.format, fmt.type, NULL);
+
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ 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);
+
+ if (gl->DeleteBuffers)
+ gl->DeleteBuffers(1, &osd->buffer);
+ osd->buffer = 0;
+ }
+
+ bool uploaded = false;
+ if (ctx->use_pbo)
+ uploaded = upload_pbo(ctx, osd, imgs);
+ if (!uploaded)
+ upload_tex(ctx, osd, imgs);
+
+ gl->BindTexture(GL_TEXTURE_2D, 0);
+
+ return true;
+}
+
+struct mpgl_osd_part *mpgl_osd_generate(struct mpgl_osd *ctx,
+ struct sub_bitmaps *imgs)
+{
+ if (imgs->num_parts == 0 || !ctx->formats[imgs->format])
+ return NULL;
+
+ struct mpgl_osd_part *osd = ctx->parts[imgs->render_index];
+
+ if (imgs->bitmap_pos_id != osd->bitmap_pos_id) {
+ if (imgs->bitmap_id != osd->bitmap_id) {
+ if (!upload_osd(ctx, osd, imgs))
+ osd->packer->count = 0;
+ }
+
+ osd->bitmap_id = imgs->bitmap_id;
+ osd->bitmap_pos_id = imgs->bitmap_pos_id;
+ osd->num_vertices = 0;
+ }
+
+ return osd->packer->count ? osd : NULL;
+}
+
+void mpgl_osd_set_gl_state(struct mpgl_osd *ctx, struct mpgl_osd_part *p)
+{
+ GL *gl = ctx->gl;
+
+ gl->BindTexture(GL_TEXTURE_2D, p->texture);
+ gl->Enable(GL_BLEND);
+ gl->BlendFunc(blend_factors[p->format][0], blend_factors[p->format][1]);
+}
+
+void mpgl_osd_unset_gl_state(struct mpgl_osd *ctx, struct mpgl_osd_part *p)
+{
+ GL *gl = ctx->gl;
+
+ gl->Disable(GL_BLEND);
+ gl->BindTexture(GL_TEXTURE_2D, 0);
+}
+
+struct vertex {
+ float position[2];
+ uint8_t color[4];
+ float texcoord[2];
+};
+
+static void draw_legacy_cb(void *pctx, struct sub_bitmaps *imgs)
+{
+ struct mpgl_osd *ctx = pctx;
+ struct mpgl_osd_part *osd = mpgl_osd_generate(ctx, imgs);
+ if (!osd)
+ return;
+
+ if (!osd->num_vertices) {
+ // 2 triangles primitives per quad = 6 vertices per quad
+ // not using GL_QUADS, as it is deprecated in OpenGL 3.x and later
+ osd->vertices = talloc_realloc(osd, osd->vertices, struct vertex,
+ osd->packer->count * 6);
+
+ struct vertex *va = osd->vertices;
+ float tex_w = osd->w;
+ float tex_h = osd->h;
+
+ for (int n = 0; n < osd->packer->count; n++) {
+ struct sub_bitmap *b = &imgs->parts[n];
+ struct pos p = osd->packer->result[n];
+
+ uint32_t c = imgs->format == SUBBITMAP_LIBASS
+ ? b->libass.color : 0xFFFFFF00;
+ uint8_t color[4] = { c >> 24, (c >> 16) & 0xff,
+ (c >> 8) & 0xff, 255 - (c & 0xff) };
+
+ float x0 = b->x;
+ float y0 = b->y;
+ float x1 = b->x + b->dw;
+ float y1 = b->y + b->dh;
+ float tx0 = p.x / tex_w;
+ float ty0 = p.y / tex_h;
+ float tx1 = (p.x + b->w) / tex_w;
+ float ty1 = (p.y + b->h) / tex_h;
+
+#define COLOR_INIT {color[0], color[1], color[2], color[3]}
+ struct vertex *v = &va[osd->num_vertices];
+ v[0] = (struct vertex) { {x0, y0}, COLOR_INIT, {tx0, ty0} };
+ v[1] = (struct vertex) { {x0, y1}, COLOR_INIT, {tx0, ty1} };
+ v[2] = (struct vertex) { {x1, y0}, COLOR_INIT, {tx1, ty0} };
+ v[3] = (struct vertex) { {x1, y1}, COLOR_INIT, {tx1, ty1} };
+ v[4] = v[2];
+ v[5] = v[1];
+#undef COLOR_INIT
+ osd->num_vertices += 6;
+ }
+ }
+
+ GL *gl = ctx->gl;
+
+ struct vertex *va = osd->vertices;
+ size_t stride = sizeof(va[0]);
+
+ gl->VertexPointer(2, GL_FLOAT, stride, &va[0].position[0]);
+ gl->ColorPointer(4, GL_UNSIGNED_BYTE, stride, &va[0].color[0]);
+ gl->TexCoordPointer(2, GL_FLOAT, stride, &va[0].texcoord[0]);
+
+ gl->EnableClientState(GL_VERTEX_ARRAY);
+ gl->EnableClientState(GL_TEXTURE_COORD_ARRAY);
+ gl->EnableClientState(GL_COLOR_ARRAY);
+
+ mpgl_osd_set_gl_state(ctx, osd);
+ gl->DrawArrays(GL_TRIANGLES, 0, osd->num_vertices);
+ mpgl_osd_unset_gl_state(ctx, osd);
+
+ gl->DisableClientState(GL_VERTEX_ARRAY);
+ gl->DisableClientState(GL_TEXTURE_COORD_ARRAY);
+ gl->DisableClientState(GL_COLOR_ARRAY);
+}
+
+void mpgl_osd_draw_legacy(struct mpgl_osd *ctx, struct osd_state *osd,
+ struct mp_osd_res res)
+{
+ osd_draw(osd, res, osd->vo_pts, 0, ctx->formats, draw_legacy_cb, ctx);
+}
diff --git a/video/out/gl_osd.h b/video/out/gl_osd.h
new file mode 100644
index 0000000000..cf3182ffb2
--- /dev/null
+++ b/video/out/gl_osd.h
@@ -0,0 +1,43 @@
+#ifndef MPLAYER_GL_OSD_H
+#define MPLAYER_GL_OSD_H
+
+#include <stdbool.h>
+#include <inttypes.h>
+
+#include "gl_common.h"
+#include "sub/sub.h"
+
+struct mpgl_osd_part {
+ enum sub_bitmap_format format;
+ int bitmap_id, bitmap_pos_id;
+ GLuint texture;
+ int w, h;
+ GLuint buffer;
+ int num_vertices;
+ void *vertices;
+ struct bitmap_packer *packer;
+};
+
+struct mpgl_osd {
+ GL *gl;
+ bool use_pbo;
+ bool scaled;
+ struct mpgl_osd_part *parts[MAX_OSD_PARTS];
+ const struct osd_fmt_entry *fmt_table;
+ bool formats[SUBBITMAP_COUNT];
+ void *scratch;
+};
+
+struct mpgl_osd *mpgl_osd_init(GL *gl, bool legacy);
+void mpgl_osd_destroy(struct mpgl_osd *ctx);
+
+struct mpgl_osd_part *mpgl_osd_generate(struct mpgl_osd *ctx,
+ struct sub_bitmaps *b);
+
+void mpgl_osd_set_gl_state(struct mpgl_osd *ctx, struct mpgl_osd_part *p);
+void mpgl_osd_unset_gl_state(struct mpgl_osd *ctx, struct mpgl_osd_part *p);
+
+void mpgl_osd_draw_legacy(struct mpgl_osd *ctx, struct osd_state *osd,
+ struct mp_osd_res res);
+
+#endif
diff --git a/video/out/osx_common.c b/video/out/osx_common.c
new file mode 100644
index 0000000000..2aa0a28126
--- /dev/null
+++ b/video/out/osx_common.c
@@ -0,0 +1,145 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "config.h"
+
+// only to get keycode definitions from HIToolbox/Events.h
+#include <Carbon/Carbon.h>
+#include <CoreServices/CoreServices.h>
+#include "config.h"
+#include "osx_common.h"
+#include "video_out.h"
+#include "input/keycodes.h"
+#include "input/input.h"
+#include "mp_msg.h"
+
+/*
+ * Define keycodes only found in OSX >= 10.5 for older versions
+ */
+#if MAC_OS_X_VERSION_MAX_ALLOWED <= 1040
+#define kVK_ANSI_Keypad0 0x52
+#define kVK_ANSI_Keypad1 0x53
+#define kVK_ANSI_Keypad2 0x54
+#define kVK_ANSI_Keypad3 0x55
+#define kVK_ANSI_Keypad4 0x56
+#define kVK_ANSI_Keypad5 0x57
+#define kVK_ANSI_Keypad6 0x58
+#define kVK_ANSI_Keypad7 0x59
+#define kVK_ANSI_Keypad8 0x5b
+#define kVK_ANSI_Keypad9 0x5c
+#define kVK_ANSI_KeypadDecimal 0x41
+#define kVK_ANSI_KeypadDivide 0x4b
+#define kVK_ANSI_KeypadEnter 0x4c
+#define kVK_ANSI_KeypadMinus 0x4e
+#define kVK_ANSI_KeypadMultiply 0x43
+#define kVK_ANSI_KeypadPlus 0x45
+#define kVK_Control 0x3b
+#define kVK_Delete 0x33
+#define kVK_DownArrow 0x7d
+#define kVK_End 0x77
+#define kVK_Escape 0x35
+#define kVK_F1 0x7a
+#define kVK_F10 0x6d
+#define kVK_F11 0x67
+#define kVK_F12 0x6f
+#define kVK_F2 0x78
+#define kVK_F3 0x63
+#define kVK_F4 0x76
+#define kVK_F5 0x60
+#define kVK_F6 0x61
+#define kVK_F7 0x62
+#define kVK_F8 0x64
+#define kVK_F9 0x65
+#define kVK_ForwardDelete 0x75
+#define kVK_Help 0x72
+#define kVK_Home 0x73
+#define kVK_LeftArrow 0x7b
+#define kVK_Option 0x3a
+#define kVK_PageDown 0x79
+#define kVK_PageUp 0x74
+#define kVK_Return 0x24
+#define kVK_RightArrow 0x7c
+#define kVK_Shift 0x38
+#define kVK_Tab 0x30
+#define kVK_UpArrow 0x7e
+#endif /* MAC_OS_X_VERSION_MAX_ALLOWED <= 1040 */
+
+static const struct mp_keymap keymap[] = {
+ // special keys
+ {0x34, KEY_ENTER}, // Enter key on some iBooks?
+ {kVK_Return, KEY_ENTER},
+ {kVK_Escape, KEY_ESC},
+ {kVK_Delete, KEY_BACKSPACE}, {kVK_Option, KEY_BACKSPACE}, {kVK_Control, KEY_BACKSPACE}, {kVK_Shift, KEY_BACKSPACE},
+ {kVK_Tab, KEY_TAB},
+
+ // cursor keys
+ {kVK_UpArrow, KEY_UP}, {kVK_DownArrow, KEY_DOWN}, {kVK_LeftArrow, KEY_LEFT}, {kVK_RightArrow, KEY_RIGHT},
+
+ // navigation block
+ {kVK_Help, KEY_INSERT}, {kVK_ForwardDelete, KEY_DELETE}, {kVK_Home, KEY_HOME},
+ {kVK_End, KEY_END}, {kVK_PageUp, KEY_PAGE_UP}, {kVK_PageDown, KEY_PAGE_DOWN},
+
+ // F-keys
+ {kVK_F1, KEY_F + 1}, {kVK_F2, KEY_F + 2}, {kVK_F3, KEY_F + 3}, {kVK_F4, KEY_F + 4},
+ {kVK_F5, KEY_F + 5}, {kVK_F6, KEY_F + 6}, {kVK_F7, KEY_F + 7}, {kVK_F8, KEY_F + 8},
+ {kVK_F9, KEY_F + 9}, {kVK_F10, KEY_F + 10}, {kVK_F11, KEY_F + 11}, {kVK_F12, KEY_F + 12},
+
+ // numpad
+ {kVK_ANSI_KeypadPlus, '+'}, {kVK_ANSI_KeypadMinus, '-'}, {kVK_ANSI_KeypadMultiply, '*'},
+ {kVK_ANSI_KeypadDivide, '/'}, {kVK_ANSI_KeypadEnter, KEY_KPENTER}, {kVK_ANSI_KeypadDecimal, KEY_KPDEC},
+ {kVK_ANSI_Keypad0, KEY_KP0}, {kVK_ANSI_Keypad1, KEY_KP1}, {kVK_ANSI_Keypad2, KEY_KP2}, {kVK_ANSI_Keypad3, KEY_KP3},
+ {kVK_ANSI_Keypad4, KEY_KP4}, {kVK_ANSI_Keypad5, KEY_KP5}, {kVK_ANSI_Keypad6, KEY_KP6}, {kVK_ANSI_Keypad7, KEY_KP7},
+ {kVK_ANSI_Keypad8, KEY_KP8}, {kVK_ANSI_Keypad9, KEY_KP9},
+
+ {0, 0}
+};
+
+int convert_key(unsigned key, unsigned charcode)
+{
+ int mpkey = lookup_keymap_table(keymap, key);
+ if (mpkey)
+ return mpkey;
+ return charcode;
+}
+
+/**
+ * Checks at runtime that OSX version is the same or newer than the one
+ * provided as input.
+ */
+int is_osx_version_at_least(int majorv, int minorv, int bugfixv)
+{
+ OSErr err;
+ SInt32 major, minor, bugfix;
+ if ((err = Gestalt(gestaltSystemVersionMajor, &major)) != noErr)
+ goto fail;
+ if ((err = Gestalt(gestaltSystemVersionMinor, &minor)) != noErr)
+ goto fail;
+ if ((err = Gestalt(gestaltSystemVersionBugFix, &bugfix)) != noErr)
+ goto fail;
+
+ if(major > majorv ||
+ (major == majorv && (minor > minorv ||
+ (minor == minorv && bugfix >= bugfixv))))
+ return 1;
+ else
+ return 0;
+fail:
+ // There's no reason the Gestalt system call should fail on OSX.
+ mp_msg(MSGT_VO, MSGL_FATAL, "[osx] Failed to get system version number. "
+ "Please contact the developers. Error code: %ld\n", (long)err);
+ return 0;
+}
diff --git a/video/out/osx_common.h b/video/out/osx_common.h
new file mode 100644
index 0000000000..ae31a6353d
--- /dev/null
+++ b/video/out/osx_common.h
@@ -0,0 +1,27 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_OSX_COMMON_H
+#define MPLAYER_OSX_COMMON_H
+
+struct vo;
+
+int convert_key(unsigned key, unsigned charcode);
+int is_osx_version_at_least(int majorv, int minorv, int bugfixv);
+
+#endif /* MPLAYER_OSX_COMMON_H */
diff --git a/video/out/pnm_loader.c b/video/out/pnm_loader.c
new file mode 100644
index 0000000000..048461e51f
--- /dev/null
+++ b/video/out/pnm_loader.c
@@ -0,0 +1,97 @@
+/*
+ * PNM image files loader
+ *
+ * copyleft (C) 2005-2010 Reimar Döffinger <Reimar.Doeffinger@gmx.de>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * You can alternatively redistribute this file 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.
+ */
+
+/**
+ * \file pnm_loader.c
+ * \brief PNM image files loader
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <ctype.h>
+#include "pnm_loader.h"
+
+/**
+ * \brief skips whitespace and comments
+ * \param f file to read from
+ */
+static void ppm_skip(FILE *f) {
+ int c, comment = 0;
+ do {
+ c = fgetc(f);
+ if (c == '#')
+ comment = 1;
+ if (c == '\n')
+ comment = 0;
+ } while (c != EOF && (isspace(c) || comment));
+ if (c != EOF)
+ ungetc(c, f);
+}
+
+#define MAXDIM (16 * 1024)
+
+uint8_t *read_pnm(FILE *f, int *width, int *height,
+ int *bytes_per_pixel, int *maxval) {
+ uint8_t *data;
+ int type;
+ unsigned w, h, m, val, bpp;
+ *width = *height = *bytes_per_pixel = *maxval = 0;
+ ppm_skip(f);
+ if (fgetc(f) != 'P')
+ return NULL;
+ type = fgetc(f);
+ if (type != '5' && type != '6')
+ return NULL;
+ ppm_skip(f);
+ if (fscanf(f, "%u", &w) != 1)
+ return NULL;
+ ppm_skip(f);
+ if (fscanf(f, "%u", &h) != 1)
+ return NULL;
+ ppm_skip(f);
+ if (fscanf(f, "%u", &m) != 1)
+ return NULL;
+ val = fgetc(f);
+ if (!isspace(val))
+ return NULL;
+ if (w > MAXDIM || h > MAXDIM)
+ return NULL;
+ bpp = (m > 255) ? 2 : 1;
+ if (type == '6')
+ bpp *= 3;
+ data = malloc(w * h * bpp);
+ if (fread(data, w * bpp, h, f) != h) {
+ free(data);
+ return NULL;
+ }
+ *width = w;
+ *height = h;
+ *bytes_per_pixel = bpp;
+ *maxval = m;
+ return data;
+}
diff --git a/video/out/pnm_loader.h b/video/out/pnm_loader.h
new file mode 100644
index 0000000000..e00cce2e63
--- /dev/null
+++ b/video/out/pnm_loader.h
@@ -0,0 +1,52 @@
+/*
+ * PNM image files loader
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * You can alternatively redistribute this file 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.
+ */
+
+#ifndef MPLAYER_PNM_LOADER_H
+#define MPLAYER_PNM_LOADER_H
+
+#include <stdio.h>
+#include <stdint.h>
+
+/**
+ * Read a "portable anymap" image.
+ * Supports raw PGM (P5) and PNM (P6).
+ *
+ * @param[in] f input stream.
+ * @param[out] width width of the loaded image.
+ * @param[out] height height of the loaded image.
+ * @param[out] bytes_per_pixel format of the loaded image.
+ * @param[out] maxval maximum pixel value; possible values are:
+ * 1 for 8 bits gray,
+ * 2 for 16 bits gray,
+ * 3 for 8 bits per component RGB,
+ * 6 for 16 bits per component RGB.
+ * @return a newly allocated array of
+ * width*height*bytes_per_pixel bytes,
+ * or NULL in case of error.
+ */
+uint8_t *read_pnm(FILE *f, int *width, int *height,
+ int *bytes_per_pixel, int *maxval);
+
+#endif /* MPLAYER_PNM_LOADER_H */
diff --git a/video/out/vo.c b/video/out/vo.c
new file mode 100644
index 0000000000..571f00da4d
--- /dev/null
+++ b/video/out/vo.c
@@ -0,0 +1,530 @@
+/*
+ * libvo common functions, variables used by many/all drivers.
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <stdbool.h>
+
+#include <unistd.h>
+//#include <sys/mman.h>
+
+#include "config.h"
+#include "options.h"
+#include "talloc.h"
+#include "bstr.h"
+#include "video_out.h"
+#include "aspect.h"
+#include "geometry.h"
+#include "input/input.h"
+#include "mp_fifo.h"
+#include "m_config.h"
+#include "mp_msg.h"
+#include "libmpcodecs/vfcap.h"
+#include "sub/sub.h"
+
+#include "osdep/shmem.h"
+#ifdef CONFIG_X11
+#include "x11_common.h"
+#endif
+
+int xinerama_screen = -1;
+int xinerama_x;
+int xinerama_y;
+
+int vo_nomouse_input = 0;
+int vo_grabpointer = 1;
+int vo_vsync = 1;
+int vo_fs = 0;
+int vo_fsmode = 0;
+float vo_panscan = 0.0f;
+int vo_refresh_rate=0;
+int vo_keepaspect=1;
+int vo_rootwin=0;
+int vo_border=1;
+int64_t WinID = -1;
+
+int vo_pts=0; // for hw decoding
+float vo_fps=0;
+
+int vo_colorkey = 0x0000ff00; // default colorkey is green
+ // (0xff000000 means that colorkey has been disabled)
+
+//
+// Externally visible list of all vo drivers
+//
+extern struct vo_driver video_out_x11;
+extern struct vo_driver video_out_vdpau;
+extern struct vo_driver video_out_xv;
+extern struct vo_driver video_out_opengl;
+extern struct vo_driver video_out_opengl_hq;
+extern struct vo_driver video_out_opengl_old;
+extern struct vo_driver video_out_null;
+extern struct vo_driver video_out_image;
+extern struct vo_driver video_out_lavc;
+extern struct vo_driver video_out_caca;
+extern struct vo_driver video_out_direct3d;
+extern struct vo_driver video_out_direct3d_shaders;
+extern struct vo_driver video_out_corevideo;
+
+const struct vo_driver *video_out_drivers[] =
+{
+#ifdef CONFIG_DIRECT3D
+ &video_out_direct3d_shaders,
+ &video_out_direct3d,
+#endif
+#ifdef CONFIG_GL_COCOA
+ &video_out_opengl,
+ &video_out_opengl_old,
+#endif
+#ifdef CONFIG_COREVIDEO
+ &video_out_corevideo,
+#endif
+#if CONFIG_VDPAU
+ &video_out_vdpau,
+#endif
+#ifdef CONFIG_XV
+ &video_out_xv,
+#endif
+#ifdef CONFIG_GL
+#if !defined CONFIG_GL_COCOA
+ &video_out_opengl,
+ &video_out_opengl_old,
+#endif
+#endif
+#ifdef CONFIG_X11
+ &video_out_x11,
+#endif
+#ifdef CONFIG_CACA
+ &video_out_caca,
+#endif
+ &video_out_null,
+ // should not be auto-selected
+ &video_out_image,
+#ifdef CONFIG_ENCODING
+ &video_out_lavc,
+#endif
+#ifdef CONFIG_GL
+ &video_out_opengl_hq,
+#endif
+ NULL
+};
+
+
+static int vo_preinit(struct vo *vo, char *arg)
+{
+ if (vo->driver->priv_size) {
+ vo->priv = talloc_zero_size(vo, vo->driver->priv_size);
+ if (vo->driver->priv_defaults)
+ memcpy(vo->priv, vo->driver->priv_defaults, vo->driver->priv_size);
+ }
+ if (vo->driver->options) {
+ struct m_config *cfg = m_config_simple(vo->priv);
+ talloc_steal(vo->priv, cfg);
+ m_config_register_options(cfg, vo->driver->options);
+ char n[50];
+ int l = snprintf(n, sizeof(n), "vo/%s", vo->driver->info->short_name);
+ assert(l < sizeof(n));
+ int r = m_config_parse_suboptions(cfg, n, arg);
+ if (r < 0)
+ return r;
+ }
+ return vo->driver->preinit(vo, arg);
+}
+
+int vo_control(struct vo *vo, uint32_t request, void *data)
+{
+ return vo->driver->control(vo, request, data);
+}
+
+// Return -1 if driver appears not to support a draw_image interface,
+// 0 otherwise (whether the driver actually drew something or not).
+int vo_draw_image(struct vo *vo, struct mp_image *mpi, double pts)
+{
+ if (!vo->config_ok)
+ return 0;
+ if (vo->driver->buffer_frames) {
+ vo->driver->draw_image(vo, mpi, pts);
+ return 0;
+ }
+ vo->frame_loaded = true;
+ vo->next_pts = pts;
+ // Guaranteed to support at least DRAW_IMAGE later
+ if (vo->driver->is_new) {
+ vo->waiting_mpi = mpi;
+ return 0;
+ }
+ if (vo_control(vo, VOCTRL_DRAW_IMAGE, mpi) == VO_NOTIMPL)
+ return -1;
+ return 0;
+}
+
+int vo_redraw_frame(struct vo *vo)
+{
+ if (!vo->config_ok || !vo->hasframe)
+ return -1;
+ if (vo_control(vo, VOCTRL_REDRAW_FRAME, NULL) == true) {
+ vo->redrawing = true;
+ return 0;
+ }
+ return -1;
+}
+
+int vo_get_buffered_frame(struct vo *vo, bool eof)
+{
+ if (!vo->config_ok)
+ return -1;
+ if (vo->frame_loaded)
+ return 0;
+ if (!vo->driver->buffer_frames)
+ return -1;
+ vo->driver->get_buffered_frame(vo, eof);
+ return vo->frame_loaded ? 0 : -1;
+}
+
+void vo_skip_frame(struct vo *vo)
+{
+ vo_control(vo, VOCTRL_SKIPFRAME, NULL);
+ vo->frame_loaded = false;
+}
+
+int vo_draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, int x, int y)
+{
+ return vo->driver->draw_slice(vo, src, stride, w, h, x, y);
+}
+
+void vo_new_frame_imminent(struct vo *vo)
+{
+ if (!vo->driver->is_new)
+ return;
+ if (vo->driver->buffer_frames)
+ vo_control(vo, VOCTRL_NEWFRAME, NULL);
+ else {
+ vo_control(vo, VOCTRL_DRAW_IMAGE, vo->waiting_mpi);
+ vo->waiting_mpi = NULL;
+ }
+}
+
+void vo_draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ if (vo->config_ok && (vo->default_caps & VFCAP_OSD))
+ vo->driver->draw_osd(vo, osd);
+}
+
+void vo_flip_page(struct vo *vo, unsigned int pts_us, int duration)
+{
+ if (!vo->config_ok)
+ return;
+ if (!vo->redrawing) {
+ vo->frame_loaded = false;
+ vo->next_pts = MP_NOPTS_VALUE;
+ }
+ vo->want_redraw = false;
+ vo->redrawing = false;
+ if (vo->driver->flip_page_timed)
+ vo->driver->flip_page_timed(vo, pts_us, duration);
+ else
+ vo->driver->flip_page(vo);
+ vo->hasframe = true;
+}
+
+void vo_check_events(struct vo *vo)
+{
+ if (!vo->config_ok) {
+ if (vo->registered_fd != -1)
+ mp_input_rm_key_fd(vo->input_ctx, vo->registered_fd);
+ vo->registered_fd = -1;
+ return;
+ }
+ vo->driver->check_events(vo);
+}
+
+void vo_seek_reset(struct vo *vo)
+{
+ vo_control(vo, VOCTRL_RESET, NULL);
+ vo->frame_loaded = false;
+ vo->hasframe = false;
+}
+
+void vo_destroy(struct vo *vo)
+{
+ if (vo->registered_fd != -1)
+ mp_input_rm_key_fd(vo->input_ctx, vo->registered_fd);
+ vo->driver->uninit(vo);
+ talloc_free(vo);
+}
+
+void list_video_out(void)
+{
+ int i = 0;
+ mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "Available video output drivers:\n");
+ mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_VIDEO_OUTPUTS\n");
+ while (video_out_drivers[i]) {
+ const vo_info_t *info = video_out_drivers[i++]->info;
+ mp_msg(MSGT_GLOBAL, MSGL_INFO,"\t%s\t%s\n", info->short_name, info->name);
+ }
+ mp_msg(MSGT_GLOBAL, MSGL_INFO,"\n");
+}
+
+static void replace_legacy_vo_name(bstr *name)
+{
+ bstr new = *name;
+ if (bstr_equals0(*name, "gl"))
+ new = bstr0("opengl");
+ if (bstr_equals0(*name, "gl3"))
+ new = bstr0("opengl-hq");
+ if (!bstr_equals(*name, new)) {
+ mp_tmsg(MSGT_CPLAYER, MSGL_ERR, "VO driver '%.*s' has been replaced "
+ "with '%.*s'!\n", BSTR_P(*name), BSTR_P(new));
+ }
+ *name = new;
+}
+
+struct vo *init_best_video_out(struct MPOpts *opts,
+ struct mp_fifo *key_fifo,
+ struct input_ctx *input_ctx,
+ struct encode_lavc_context *encode_lavc_ctx)
+{
+ char **vo_list = opts->video_driver_list;
+ int i;
+ struct vo *vo = talloc_ptrtype(NULL, vo);
+ struct vo initial_values = {
+ .opts = opts,
+ .key_fifo = key_fifo,
+ .encode_lavc_ctx = encode_lavc_ctx,
+ .input_ctx = input_ctx,
+ .event_fd = -1,
+ .registered_fd = -1,
+ };
+ // first try the preferred drivers, with their optional subdevice param:
+ if (vo_list && vo_list[0])
+ while (vo_list[0][0]) {
+ char *arg = vo_list[0];
+ bstr name = bstr0(arg);
+ char *params = strchr(arg, ':');
+ if (params) {
+ name = bstr_splice(name, 0, params - arg);
+ params++;
+ }
+ replace_legacy_vo_name(&name);
+ for (i = 0; video_out_drivers[i]; i++) {
+ const struct vo_driver *video_driver = video_out_drivers[i];
+ const vo_info_t *info = video_driver->info;
+ if (bstr_equals0(name, info->short_name)) {
+ // name matches, try it
+ *vo = initial_values;
+ vo->driver = video_driver;
+ if (!vo_preinit(vo, params))
+ return vo; // success!
+ talloc_free_children(vo);
+ }
+ }
+ // continue...
+ ++vo_list;
+ if (!(vo_list[0])) {
+ talloc_free(vo);
+ return NULL; // do NOT fallback to others
+ }
+ }
+ // now try the rest...
+ for (i = 0; video_out_drivers[i]; i++) {
+ const struct vo_driver *video_driver = video_out_drivers[i];
+ *vo = initial_values;
+ vo->driver = video_driver;
+ if (!vo_preinit(vo, NULL))
+ return vo; // success!
+ talloc_free_children(vo);
+ }
+ talloc_free(vo);
+ return NULL;
+}
+
+static int event_fd_callback(void *ctx, int fd)
+{
+ struct vo *vo = ctx;
+ vo_check_events(vo);
+ return MP_INPUT_NOTHING;
+}
+
+int vo_config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ struct MPOpts *opts = vo->opts;
+ panscan_init(vo);
+ aspect_save_videores(vo, width, height, d_width, d_height);
+
+ if (vo_control(vo, VOCTRL_UPDATE_SCREENINFO, NULL) == VO_TRUE) {
+ aspect(vo, &d_width, &d_height, A_NOZOOM);
+ vo->dx = (int)(opts->vo_screenwidth - d_width) / 2;
+ vo->dy = (int)(opts->vo_screenheight - d_height) / 2;
+ geometry(&vo->dx, &vo->dy, &d_width, &d_height,
+ opts->vo_screenwidth, opts->vo_screenheight);
+ geometry_xy_changed |= xinerama_screen >= 0;
+ vo->dx += xinerama_x;
+ vo->dy += xinerama_y;
+ vo->dwidth = d_width;
+ vo->dheight = d_height;
+ }
+
+ vo->default_caps = vo_control(vo, VOCTRL_QUERY_FORMAT, &format);
+
+ int ret = vo->driver->config(vo, width, height, d_width, d_height, flags,
+ format);
+ vo->config_ok = (ret == 0);
+ vo->config_count += vo->config_ok;
+ if (!vo->config_ok)
+ vo->default_caps = 0;
+ if (vo->registered_fd == -1 && vo->event_fd != -1 && vo->config_ok) {
+ mp_input_add_key_fd(vo->input_ctx, vo->event_fd, 1, event_fd_callback,
+ NULL, vo);
+ vo->registered_fd = vo->event_fd;
+ }
+ vo->frame_loaded = false;
+ vo->waiting_mpi = NULL;
+ vo->redrawing = false;
+ vo->hasframe = false;
+ return ret;
+}
+
+/**
+ * \brief lookup an integer in a table, table must have 0 as the last key
+ * \param key key to search for
+ * \result translation corresponding to key or "to" value of last mapping
+ * if not found.
+ */
+int lookup_keymap_table(const struct mp_keymap *map, int key) {
+ while (map->from && map->from != key) map++;
+ return map->to;
+}
+
+static void print_video_rect(struct vo *vo, struct mp_rect src,
+ struct mp_rect dst, struct mp_osd_res osd)
+{
+ int lv = MSGL_V;
+
+ int sw = src.x1 - src.x0, sh = src.y1 - src.y0;
+ int dw = dst.x1 - dst.x0, dh = dst.y1 - dst.y0;
+
+ mp_msg(MSGT_VO, lv, "[vo] Window size: %dx%d\n",
+ vo->dwidth, vo->dheight);
+ mp_msg(MSGT_VO, lv, "[vo] Video source: %dx%d (%dx%d)\n",
+ vo->aspdat.orgw, vo->aspdat.orgh,
+ vo->aspdat.prew, vo->aspdat.preh);
+ mp_msg(MSGT_VO, lv, "[vo] Video display: (%d, %d) %dx%d -> (%d, %d) %dx%d\n",
+ src.x0, src.y0, sw, sh, dst.x0, dst.y0, dw, dh);
+ mp_msg(MSGT_VO, lv, "[vo] Video scale: %f/%f\n",
+ (double)dw / sw, (double)dh / sh);
+ mp_msg(MSGT_VO, lv, "[vo] OSD borders: l=%d t=%d r=%d b=%d\n",
+ osd.ml, osd.mt, osd.mr, osd.mb);
+ mp_msg(MSGT_VO, lv, "[vo] Video borders: l=%d t=%d r=%d b=%d\n",
+ dst.x0, dst.y0, vo->dwidth - dst.x1, vo->dheight - dst.y1);
+}
+
+static void src_dst_split_scaling(int src_size, int dst_size,
+ int scaled_src_size, int *src_start,
+ int *src_end, int *dst_start, int *dst_end)
+{
+ if (scaled_src_size > dst_size) {
+ int border = src_size * (scaled_src_size - dst_size) / scaled_src_size;
+ // round to a multiple of 2, this is at least needed for vo_direct3d
+ // and ATI cards
+ border = (border / 2 + 1) & ~1;
+ *src_start = border;
+ *src_end = src_size - border;
+ *dst_start = 0;
+ *dst_end = dst_size;
+ } else {
+ *src_start = 0;
+ *src_end = src_size;
+ *dst_start = (dst_size - scaled_src_size) / 2;
+ *dst_end = *dst_start + scaled_src_size;
+ }
+}
+
+// Calculate the appropriate source and destination rectangle to
+// get a correctly scaled picture, including pan-scan.
+// out_src: visible part of the video
+// out_dst: area of screen covered by the video source rectangle
+// out_osd: OSD size, OSD margins, etc.
+void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src,
+ struct mp_rect *out_dst, struct mp_osd_res *out_osd)
+{
+ int src_w = vo->aspdat.orgw;
+ int src_h = vo->aspdat.orgh;
+ struct mp_rect dst = {0, 0, vo->dwidth, vo->dheight};
+ struct mp_rect src = {0, 0, src_w, src_h};
+ struct mp_osd_res osd = {
+ .w = vo->dwidth,
+ .h = vo->dheight,
+ .display_par = vo->monitor_par,
+ .video_par = vo->aspdat.par,
+ };
+ if (aspect_scaling()) {
+ int scaled_width = 0, scaled_height = 0;
+ aspect(vo, &scaled_width, &scaled_height, A_WINZOOM);
+ panscan_calc_windowed(vo);
+ scaled_width += vo->panscan_x;
+ scaled_height += vo->panscan_y;
+ int border_w = vo->dwidth - scaled_width;
+ int border_h = vo->dheight - scaled_height;
+ osd.ml = border_w / 2;
+ osd.mt = border_h / 2;
+ osd.mr = border_w - osd.ml;
+ osd.mb = border_h - osd.mt;
+ src_dst_split_scaling(src_w, vo->dwidth, scaled_width,
+ &src.x0, &src.x1, &dst.x0, &dst.x1);
+ src_dst_split_scaling(src_h, vo->dheight, scaled_height,
+ &src.y0, &src.y1, &dst.y0, &dst.y1);
+ }
+
+ *out_src = src;
+ *out_dst = dst;
+ *out_osd = osd;
+
+ print_video_rect(vo, src, dst, osd);
+}
+
+// Return the window title the VO should set. Always returns a null terminated
+// string. The string is valid until frontend code is invoked again. Copy it if
+// you need to keep the string for an extended period of time.
+const char *vo_get_window_title(struct vo *vo)
+{
+ if (!vo->window_title)
+ vo->window_title = talloc_strdup(vo, "");
+ return vo->window_title;
+}
+
+/**
+ * Generates a mouse movement message if those are enable and sends it
+ * to the "main" MPlayer.
+ *
+ * \param posx new x position of mouse
+ * \param posy new y position of mouse
+ */
+void vo_mouse_movement(struct vo *vo, int posx, int posy)
+{
+ char cmd_str[40];
+ if (!enable_mouse_movements)
+ return;
+ snprintf(cmd_str, sizeof(cmd_str), "set_mouse_pos %i %i", posx, posy);
+ mp_input_queue_cmd(vo->input_ctx, mp_input_parse_cmd(bstr0(cmd_str), ""));
+}
diff --git a/video/out/vo.h b/video/out/vo.h
new file mode 100644
index 0000000000..ac2ded9b3c
--- /dev/null
+++ b/video/out/vo.h
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) Aaron Holtzman - Aug 1999
+ * Strongly modified, most parts rewritten: A'rpi/ESP-team - 2000-2001
+ * (C) MPlayer developers
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_VIDEO_OUT_H
+#define MPLAYER_VIDEO_OUT_H
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "libmpcodecs/img_format.h"
+#include "mpcommon.h"
+
+#define VO_EVENT_EXPOSE 1
+#define VO_EVENT_RESIZE 2
+#define VO_EVENT_KEYPRESS 4
+#define VO_EVENT_REINIT 8
+#define VO_EVENT_MOVE 16
+
+enum mp_voctrl {
+ /* does the device support the required format */
+ VOCTRL_QUERY_FORMAT = 1,
+ /* signal a device reset seek */
+ VOCTRL_RESET,
+ /* used to switch to fullscreen */
+ VOCTRL_FULLSCREEN,
+ /* signal a device pause */
+ VOCTRL_PAUSE,
+ /* start/resume playback */
+ VOCTRL_RESUME,
+ /* libmpcodecs direct rendering */
+ VOCTRL_GET_IMAGE,
+ VOCTRL_DRAW_IMAGE,
+ VOCTRL_GET_PANSCAN,
+ VOCTRL_SET_PANSCAN,
+ VOCTRL_SET_EQUALIZER, // struct voctrl_set_equalizer_args
+ VOCTRL_GET_EQUALIZER, // struct voctrl_get_equalizer_args
+ VOCTRL_DUPLICATE_FRAME,
+
+ VOCTRL_START_SLICE,
+
+ VOCTRL_NEWFRAME,
+ VOCTRL_SKIPFRAME,
+ VOCTRL_REDRAW_FRAME,
+
+ VOCTRL_ONTOP,
+ VOCTRL_ROOTWIN,
+ VOCTRL_BORDER,
+
+ VOCTRL_SET_DEINTERLACE,
+ VOCTRL_GET_DEINTERLACE,
+
+ VOCTRL_UPDATE_SCREENINFO,
+
+ VOCTRL_SET_YUV_COLORSPACE, // struct mp_csp_details
+ VOCTRL_GET_YUV_COLORSPACE, // struct mp_csp_details
+
+ VOCTRL_SCREENSHOT, // struct voctrl_screenshot_args
+
+ VOCTRL_SET_COMMAND_LINE, // char*
+};
+
+// VOCTRL_SET_EQUALIZER
+struct voctrl_set_equalizer_args {
+ const char *name;
+ int value;
+};
+
+// VOCTRL_GET_EQUALIZER
+struct voctrl_get_equalizer_args {
+ const char *name;
+ int *valueptr;
+};
+
+// VOCTRL_SCREENSHOT
+struct voctrl_screenshot_args {
+ // 0: Save image of the currently displayed video frame, in original
+ // resolution.
+ // 1: Save full screenshot of the window. Should contain OSD, EOSD, and the
+ // scaled video.
+ // The value of this variable can be ignored if only a single method is
+ // implemented.
+ int full_window;
+ // Will be set to a newly allocated image, that contains the screenshot.
+ // The caller has to free the pointer with free_mp_image().
+ // It is not specified whether the image data is a copy or references the
+ // image data directly.
+ // Is never NULL. (Failure has to be indicated by returning VO_FALSE.)
+ struct mp_image *out_image;
+ // Whether the VO rendered OSD/subtitles into out_image
+ bool has_osd;
+};
+
+typedef struct {
+ int x,y;
+ int w,h;
+} mp_win_t;
+
+#define VO_TRUE 1
+#define VO_FALSE 0
+#define VO_ERROR -1
+#define VO_NOTAVAIL -2
+#define VO_NOTIMPL -3
+
+#define VOFLAG_FULLSCREEN 0x01
+#define VOFLAG_MODESWITCHING 0x02
+#define VOFLAG_SWSCALE 0x04
+#define VOFLAG_FLIPPING 0x08
+#define VOFLAG_HIDDEN 0x10 //< Use to create a hidden window
+#define VOFLAG_STEREO 0x20 //< Use to create a stereo-capable window
+#define VOFLAG_GL_DEBUG 0x40 // Hint to request debug OpenGL context
+
+typedef struct vo_info_s
+{
+ /* driver name ("Matrox Millennium G200/G400" */
+ const char *name;
+ /* short name (for config strings) ("vdpau") */
+ const char *short_name;
+ /* author ("Aaron Holtzman <aholtzma@ess.engr.uvic.ca>") */
+ const char *author;
+ /* any additional comments */
+ const char *comment;
+} vo_info_t;
+
+struct vo;
+struct osd_state;
+struct mp_image;
+
+struct vo_driver {
+ // Driver uses new API
+ bool is_new;
+ // Driver buffers or adds (deinterlace) frames and will keep track
+ // of pts values itself
+ bool buffer_frames;
+
+ const vo_info_t *info;
+ /*
+ * Preinitializes driver (real INITIALIZATION)
+ * arg - currently it's vo_subdevice
+ * returns: zero on successful initialization, non-zero on error.
+ */
+ int (*preinit)(struct vo *vo, const char *arg);
+ /*
+ * Initialize (means CONFIGURE) the display driver.
+ * params:
+ * width,height: image source size
+ * d_width,d_height: size of the requested window size, just a hint
+ * fullscreen: flag, 0=windowd 1=fullscreen, just a hint
+ * title: window title, if available
+ * format: fourcc of pixel format
+ * returns : zero on successful initialization, non-zero on error.
+ */
+ int (*config)(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t fullscreen,
+ uint32_t format);
+
+ /*
+ * Control interface
+ */
+ int (*control)(struct vo *vo, uint32_t request, void *data);
+
+ void (*draw_image)(struct vo *vo, struct mp_image *mpi, double pts);
+
+ /*
+ * Get extra frames from the VO, such as those added by VDPAU
+ * deinterlace. Preparing the next such frame if any could be done
+ * automatically by the VO after a previous flip_page(), but having
+ * it as a separate step seems to allow making code more robust.
+ */
+ void (*get_buffered_frame)(struct vo *vo, bool eof);
+
+ /*
+ * Draw a planar YUV slice to the buffer:
+ * params:
+ * src[3] = source image planes (Y,U,V)
+ * stride[3] = source image planes line widths (in bytes)
+ * w,h = width*height of area to be copied (in Y pixels)
+ * x,y = position at the destination image (in Y pixels)
+ */
+ int (*draw_slice)(struct vo *vo, uint8_t *src[], int stride[], int w,
+ int h, int x, int y);
+
+ /*
+ * Draws OSD to the screen buffer
+ */
+ void (*draw_osd)(struct vo *vo, struct osd_state *osd);
+
+ /*
+ * Blit/Flip buffer to the screen. Must be called after each frame!
+ */
+ void (*flip_page)(struct vo *vo);
+ void (*flip_page_timed)(struct vo *vo, unsigned int pts_us, int duration);
+
+ /*
+ * This func is called after every frames to handle keyboard and
+ * other events. It's called in PAUSE mode too!
+ */
+ void (*check_events)(struct vo *vo);
+
+ /*
+ * Closes driver. Should restore the original state of the system.
+ */
+ void (*uninit)(struct vo *vo);
+
+ // Size of private struct for automatic allocation (0 doesn't allocate)
+ int priv_size;
+
+ // If not NULL, it's copied into the newly allocated private struct.
+ const void *priv_defaults;
+
+ // List of options to parse into priv struct (requires privsize to be set)
+ const struct m_option *options;
+};
+
+struct vo {
+ int config_ok; // Last config call was successful?
+ int config_count; // Total number of successful config calls
+ int default_caps; // query_format() result for configured video format
+
+ bool frame_loaded; // Is there a next frame the VO could flip to?
+ struct mp_image *waiting_mpi;
+ double next_pts; // pts value of the next frame if any
+ double next_pts2; // optional pts of frame after that
+ bool want_redraw; // visible frame wrong (window resize), needs refresh
+ bool redrawing; // between redrawing frame and flipping it
+ bool hasframe; // >= 1 frame has been drawn, so redraw is possible
+
+ double flip_queue_offset; // queue flip events at most this much in advance
+
+ const struct vo_driver *driver;
+ void *priv;
+ struct MPOpts *opts;
+ struct vo_x11_state *x11;
+ struct vo_w32_state *w32;
+ struct vo_cocoa_state *cocoa;
+ struct mp_fifo *key_fifo;
+ struct encode_lavc_context *encode_lavc_ctx;
+ struct input_ctx *input_ctx;
+ int event_fd; // check_events() should be called when this has input
+ int registered_fd; // set to event_fd when registered in input system
+
+ // requested position/resolution (usually window position/window size)
+ int dx;
+ int dy;
+ int dwidth;
+ int dheight;
+
+ int panscan_x;
+ int panscan_y;
+ float panscan_amount;
+ float monitor_par;
+ struct aspect_data {
+ int orgw; // real width
+ int orgh; // real height
+ int prew; // prescaled width
+ int preh; // prescaled height
+ float par; // pixel aspect ratio out of orgw/orgh and prew/preh
+ int scrw; // horizontal resolution
+ int scrh; // vertical resolution
+ float asp;
+ } aspdat;
+
+ char *window_title;
+};
+
+struct vo *init_best_video_out(struct MPOpts *opts,
+ struct mp_fifo *key_fifo,
+ struct input_ctx *input_ctx,
+ struct encode_lavc_context *encode_lavc_ctx);
+int vo_config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format);
+void list_video_out(void);
+
+int vo_control(struct vo *vo, uint32_t request, void *data);
+int vo_draw_image(struct vo *vo, struct mp_image *mpi, double pts);
+int vo_redraw_frame(struct vo *vo);
+int vo_get_buffered_frame(struct vo *vo, bool eof);
+void vo_skip_frame(struct vo *vo);
+int vo_draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, int x, int y);
+void vo_new_frame_imminent(struct vo *vo);
+void vo_draw_osd(struct vo *vo, struct osd_state *osd);
+void vo_flip_page(struct vo *vo, unsigned int pts_us, int duration);
+void vo_check_events(struct vo *vo);
+void vo_seek_reset(struct vo *vo);
+void vo_destroy(struct vo *vo);
+
+const char *vo_get_window_title(struct vo *vo);
+
+// NULL terminated array of all drivers
+extern const struct vo_driver *video_out_drivers[];
+
+extern int xinerama_screen;
+extern int xinerama_x;
+extern int xinerama_y;
+
+extern int vo_grabpointer;
+extern int vo_vsync;
+extern int vo_fs;
+extern int vo_fsmode;
+extern float vo_panscan;
+extern int vo_refresh_rate;
+extern int vo_keepaspect;
+extern int vo_rootwin;
+extern int vo_border;
+
+extern int vo_nomouse_input;
+extern int enable_mouse_movements;
+
+extern int vo_pts;
+extern float vo_fps;
+
+extern int vo_colorkey;
+
+extern int64_t WinID;
+
+struct mp_keymap {
+ int from;
+ int to;
+};
+int lookup_keymap_table(const struct mp_keymap *map, int key);
+
+void vo_mouse_movement(struct vo *vo, int posx, int posy);
+
+struct mp_osd_res;
+void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src,
+ struct mp_rect *out_dst, struct mp_osd_res *out_osd);
+
+static inline int aspect_scaling(void)
+{
+ return vo_keepaspect || vo_fs;
+}
+
+#endif /* MPLAYER_VIDEO_OUT_H */
diff --git a/video/out/vo_caca.c b/video/out/vo_caca.c
new file mode 100644
index 0000000000..2d998bf91d
--- /dev/null
+++ b/video/out/vo_caca.c
@@ -0,0 +1,395 @@
+/*
+ * video output driver for libcaca
+ *
+ * by Pigeon <pigeon@pigeond.net>
+ *
+ * Some functions/codes/ideas are from x11 and aalib vo
+ *
+ * TODO: support draw_alpha?
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <assert.h>
+#include <caca.h>
+
+#include "config.h"
+#include "video_out.h"
+#include "sub/sub.h"
+#include "libmpcodecs/mp_image.h"
+#include "libmpcodecs/vfcap.h"
+
+#include "input/keycodes.h"
+#include "input/input.h"
+#include "mp_msg.h"
+#include "mp_fifo.h"
+
+/* caca stuff */
+static caca_canvas_t *canvas;
+static caca_display_t *display;
+static caca_dither_t *dither = NULL;
+static const char *dither_antialias = "default";
+static const char *dither_charset = "default";
+static const char *dither_color = "default";
+static const char *dither_algo = "none";
+
+/* image infos */
+static int image_format;
+static int image_width;
+static int image_height;
+
+static int screen_w, screen_h;
+
+/* We want 24bpp always for now */
+static unsigned int bpp = 24;
+static unsigned int depth = 3;
+static unsigned int rmask = 0xff0000;
+static unsigned int gmask = 0x00ff00;
+static unsigned int bmask = 0x0000ff;
+static unsigned int amask = 0;
+
+#define MESSAGE_SIZE 512
+#define MESSAGE_DURATION 5
+
+static time_t stoposd = 0;
+static int showosdmessage = 0;
+static char osdmessagetext[MESSAGE_SIZE];
+static char posbar[MESSAGE_SIZE];
+
+static int osdx = 0, osdy = 0;
+static int posbary = 2;
+
+static void osdmessage(int duration, const char *fmt, ...)
+{
+ /* for outputting a centered string at the window bottom for a while */
+ va_list ar;
+ char m[MESSAGE_SIZE];
+
+ va_start(ar, fmt);
+ vsprintf(m, fmt, ar);
+ va_end(ar);
+ strcpy(osdmessagetext, m);
+
+ showosdmessage = 1;
+ stoposd = time(NULL) + duration;
+ osdx = (screen_w - strlen(osdmessagetext)) / 2;
+ posbar[0] = '\0';
+}
+
+static void osdpercent(int duration, int min, int max, int val,
+ const char *desc, const char *unit)
+{
+ /* prints a bar for setting values */
+ float step;
+ int where, i;
+
+ step = (float)screen_w / (float)(max - min);
+ where = (val - min) * step;
+ osdmessage(duration, "%s: %i%s", desc, val, unit);
+ posbar[0] = '|';
+ posbar[screen_w - 1] = '|';
+
+ for (i = 0; i < screen_w; i++) {
+ if (i == where)
+ posbar[i] = '#';
+ else
+ posbar[i] = '-';
+ }
+
+ if (where != 0)
+ posbar[0] = '|';
+
+ if (where != (screen_w - 1))
+ posbar[screen_w - 1] = '|';
+
+ posbar[screen_w] = '\0';
+}
+
+static int resize(void)
+{
+ screen_w = caca_get_canvas_width(canvas);
+ screen_h = caca_get_canvas_height(canvas);
+
+ caca_free_dither(dither);
+
+ dither = caca_create_dither(bpp, image_width, image_height,
+ depth * image_width,
+ rmask, gmask, bmask, amask);
+ if (dither == NULL) {
+ mp_msg(MSGT_VO, MSGL_FATAL, "vo_caca: caca_create_dither failed!\n");
+ return ENOSYS;
+ }
+
+ /* Default libcaca features */
+ caca_set_dither_antialias(dither, dither_antialias);
+ caca_set_dither_charset(dither, dither_charset);
+ caca_set_dither_color(dither, dither_color);
+ caca_set_dither_algorithm(dither, dither_algo);
+
+ return 0;
+}
+
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ image_height = height;
+ image_width = width;
+ image_format = format;
+
+ showosdmessage = 0;
+ posbar[0] = '\0';
+
+ return resize();
+}
+
+static uint32_t draw_image(struct vo *vo, mp_image_t *mpi)
+{
+ assert(mpi->stride[0] == image_width * 3);
+ caca_dither_bitmap(canvas, 0, 0, screen_w, screen_h, dither,
+ mpi->planes[0]);
+ return true;
+}
+
+static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h,
+ int x, int y)
+{
+ return 0;
+}
+
+static void flip_page(struct vo *vo)
+{
+ if (showosdmessage) {
+ if (time(NULL) >= stoposd) {
+ showosdmessage = 0;
+ if (*posbar)
+ posbar[0] = '\0';
+ } else {
+ caca_put_str(canvas, osdx, osdy, osdmessagetext);
+ if (*posbar)
+ caca_put_str(canvas, 0, posbary, posbar);
+ }
+ }
+
+ caca_refresh_display(display);
+}
+
+static void set_next_str(const char * const *list, const char **str,
+ const char **msg)
+{
+ int ind;
+ for (ind = 0; list[ind]; ind += 2) {
+ if (strcmp(list[ind], *str) == 0) {
+ if (list[ind + 2] == NULL)
+ ind = -2;
+ *str = list[ind + 2];
+ *msg = list[ind + 3];
+ return;
+ }
+ }
+
+ *str = list[0];
+ *msg = list[1];
+}
+
+static const struct mp_keymap keysym_map[] = {
+ {CACA_KEY_RETURN, KEY_ENTER}, {CACA_KEY_ESCAPE, KEY_ESC},
+ {CACA_KEY_UP, KEY_DOWN}, {CACA_KEY_DOWN, KEY_DOWN},
+ {CACA_KEY_LEFT, KEY_LEFT}, {CACA_KEY_RIGHT, KEY_RIGHT},
+ {CACA_KEY_PAGEUP, KEY_PAGE_UP}, {CACA_KEY_PAGEDOWN, KEY_PAGE_DOWN},
+ {CACA_KEY_HOME, KEY_HOME}, {CACA_KEY_END, KEY_END},
+ {CACA_KEY_INSERT, KEY_INSERT}, {CACA_KEY_DELETE, KEY_DELETE},
+ {CACA_KEY_BACKSPACE, KEY_BACKSPACE}, {CACA_KEY_TAB, KEY_TAB},
+ {CACA_KEY_PAUSE, KEY_PAUSE},
+ {CACA_KEY_F1, KEY_F+1}, {CACA_KEY_F2, KEY_F+2},
+ {CACA_KEY_F3, KEY_F+3}, {CACA_KEY_F4, KEY_F+4},
+ {CACA_KEY_F5, KEY_F+5}, {CACA_KEY_F6, KEY_F+6},
+ {CACA_KEY_F7, KEY_F+7}, {CACA_KEY_F8, KEY_F+8},
+ {CACA_KEY_F9, KEY_F+9}, {CACA_KEY_F10, KEY_F+10},
+ {CACA_KEY_F11, KEY_F+11}, {CACA_KEY_F12, KEY_F+12},
+ {CACA_KEY_F13, KEY_F+13}, {CACA_KEY_F14, KEY_F+14},
+ {CACA_KEY_F15, KEY_F+15},
+ {0, 0}
+};
+
+static void check_events(struct vo *vo)
+{
+ caca_event_t cev;
+ while (caca_get_event(display, CACA_EVENT_ANY, &cev, 0)) {
+
+ switch (cev.type) {
+ case CACA_EVENT_RESIZE:
+ caca_refresh_display(display);
+ resize();
+ break;
+ case CACA_EVENT_QUIT:
+ mplayer_put_key(vo->key_fifo, KEY_CLOSE_WIN);
+ break;
+ case CACA_EVENT_MOUSE_MOTION:
+ vo_mouse_movement(vo, cev.data.mouse.x, cev.data.mouse.y);
+ break;
+ case CACA_EVENT_MOUSE_PRESS:
+ if (!vo_nomouse_input)
+ mplayer_put_key(vo->key_fifo,
+ (MOUSE_BTN0 + cev.data.mouse.button - 1) | MP_KEY_DOWN);
+ break;
+ case CACA_EVENT_MOUSE_RELEASE:
+ if (!vo_nomouse_input)
+ mplayer_put_key(vo->key_fifo,
+ MOUSE_BTN0 + cev.data.mouse.button - 1);
+ break;
+ case CACA_EVENT_KEY_PRESS:
+ {
+ int key = cev.data.key.ch;
+ int mpkey = lookup_keymap_table(keysym_map, key);
+ const char *msg_name;
+
+ if (mpkey)
+ mplayer_put_key(vo->key_fifo, mpkey);
+ else
+ switch (key) {
+ case 'd':
+ case 'D':
+ /* Toggle dithering algorithm */
+ set_next_str(caca_get_dither_algorithm_list(dither),
+ &dither_algo, &msg_name);
+ caca_set_dither_algorithm(dither, dither_algo);
+ osdmessage(MESSAGE_DURATION, "Using %s", msg_name);
+ break;
+
+ case 'a':
+ case 'A':
+ /* Toggle antialiasing method */
+ set_next_str(caca_get_dither_antialias_list(dither),
+ &dither_antialias, &msg_name);
+ caca_set_dither_antialias(dither, dither_antialias);
+ osdmessage(MESSAGE_DURATION, "Using %s", msg_name);
+ break;
+
+ case 'h':
+ case 'H':
+ /* Toggle charset method */
+ set_next_str(caca_get_dither_charset_list(dither),
+ &dither_charset, &msg_name);
+ caca_set_dither_charset(dither, dither_charset);
+ osdmessage(MESSAGE_DURATION, "Using %s", msg_name);
+ break;
+
+ case 'c':
+ case 'C':
+ /* Toggle color method */
+ set_next_str(caca_get_dither_color_list(dither),
+ &dither_color, &msg_name);
+ caca_set_dither_color(dither, dither_color);
+ osdmessage(MESSAGE_DURATION, "Using %s", msg_name);
+ break;
+
+ default:
+ if (key <= 255)
+ mplayer_put_key(vo->key_fifo, key);
+ break;
+ }
+ }
+ }
+ }
+}
+
+static void uninit(struct vo *vo)
+{
+ caca_free_dither(dither);
+ dither = NULL;
+ caca_free_display(display);
+ caca_free_canvas(canvas);
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ if (osd->progbar_type != -1)
+ osdpercent(MESSAGE_DURATION, 0, 255, osd->progbar_value,
+ sub_osd_names[osd->progbar_type], "");
+}
+
+static int preinit(struct vo *vo, const char *arg)
+{
+ if (arg) {
+ mp_msg(MSGT_VO, MSGL_ERR, "vo_caca: Unknown subdevice: %s\n", arg);
+ return ENOSYS;
+ }
+
+ canvas = caca_create_canvas(0, 0);
+ if (canvas == NULL) {
+ mp_msg(MSGT_VO, MSGL_ERR, "vo_caca: failed to create canvas\n");
+ return ENOSYS;
+ }
+
+ display = caca_create_display(canvas);
+
+ if (display == NULL) {
+ mp_msg(MSGT_VO, MSGL_ERR, "vo_caca: failed to create display\n");
+ caca_free_canvas(canvas);
+ return ENOSYS;
+ }
+
+ caca_set_display_title(display, "mpv");
+
+ return 0;
+}
+
+static int query_format(uint32_t format)
+{
+ if (format == IMGFMT_BGR24)
+ return VFCAP_OSD | VFCAP_CSP_SUPPORTED;
+
+ return 0;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ switch (request) {
+ case VOCTRL_QUERY_FORMAT:
+ return query_format(*((uint32_t *)data));
+ case VOCTRL_DRAW_IMAGE:
+ return draw_image(vo, data);
+ default:
+ return VO_NOTIMPL;
+ }
+}
+
+const struct vo_driver video_out_caca = {
+ .is_new = false,
+ .info = &(const vo_info_t) {
+ "libcaca",
+ "caca",
+ "Pigeon <pigeon@pigeond.net>",
+ ""
+ },
+ .preinit = preinit,
+ .config = config,
+ .control = control,
+ .draw_slice = draw_slice,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+ .check_events = check_events,
+ .uninit = uninit,
+};
diff --git a/video/out/vo_corevideo.h b/video/out/vo_corevideo.h
new file mode 100644
index 0000000000..cfb86621bc
--- /dev/null
+++ b/video/out/vo_corevideo.h
@@ -0,0 +1,28 @@
+/*
+ * CoreVideo video output driver
+ *
+ * Copyright (c) 2005 Nicolas Plourde <nicolasplourde@gmail.com>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPLAYER_VO_COREVIDEO_H
+#define MPLAYER_VO_COREVIDEO_H
+
+#import <QuartzCore/QuartzCore.h>
+
+#endif /* MPLAYER_VO_COREVIDEO_H */
diff --git a/video/out/vo_corevideo.m b/video/out/vo_corevideo.m
new file mode 100644
index 0000000000..5116ab653c
--- /dev/null
+++ b/video/out/vo_corevideo.m
@@ -0,0 +1,457 @@
+/*
+ * CoreVideo video output driver
+ * Copyright (c) 2005 Nicolas Plourde <nicolasplourde@gmail.com>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <assert.h>
+
+#import "vo_corevideo.h"
+
+// mplayer includes
+#import "fastmemcpy.h"
+#import "talloc.h"
+#import "video_out.h"
+#import "aspect.h"
+#import "sub/sub.h"
+#import "subopt-helper.h"
+
+#import "csputils.h"
+#import "libmpcodecs/vfcap.h"
+#import "libmpcodecs/mp_image.h"
+
+#import "gl_common.h"
+#import "gl_osd.h"
+#import "cocoa_common.h"
+
+struct quad {
+ GLfloat lowerLeft[2];
+ GLfloat lowerRight[2];
+ GLfloat upperRight[2];
+ GLfloat upperLeft[2];
+};
+
+struct priv {
+ MPGLContext *mpglctx;
+ OSType pixelFormat;
+ unsigned int image_width;
+ unsigned int image_height;
+ struct mp_csp_details colorspace;
+ int ass_border_x, ass_border_y;
+
+ CVPixelBufferRef pixelBuffer;
+ CVOpenGLTextureCacheRef textureCache;
+ CVOpenGLTextureRef texture;
+ struct quad *quad;
+
+ struct mpgl_osd *osd;
+};
+
+static void resize(struct vo *vo, int width, int height)
+{
+ struct priv *p = vo->priv;
+ GL *gl = p->mpglctx->gl;
+
+ gl->Viewport(0, 0, width, height);
+ gl->MatrixMode(GL_PROJECTION);
+ gl->LoadIdentity();
+ p->ass_border_x = p->ass_border_y = 0;
+ if (aspect_scaling()) {
+ int new_w, new_h;
+ GLdouble scale_x, scale_y;
+
+ aspect(vo, &new_w, &new_h, A_WINZOOM);
+ panscan_calc_windowed(vo);
+ new_w += vo->panscan_x;
+ new_h += vo->panscan_y;
+ scale_x = (GLdouble)new_w / (GLdouble)width;
+ scale_y = (GLdouble)new_h / (GLdouble)height;
+ gl->Scaled(scale_x, scale_y, 1);
+ p->ass_border_x = (vo->dwidth - new_w) / 2;
+ p->ass_border_y = (vo->dheight - new_h) / 2;
+ }
+
+ gl->Ortho(0, p->image_width, p->image_height, 0, -1.0, 1.0);
+ gl->MatrixMode(GL_MODELVIEW);
+ gl->LoadIdentity();
+
+ gl->Clear(GL_COLOR_BUFFER_BIT);
+ vo->want_redraw = true;
+}
+
+static int init_gl(struct vo *vo, uint32_t d_width, uint32_t d_height)
+{
+ struct priv *p = vo->priv;
+ GL *gl = p->mpglctx->gl;
+
+ const char *vendor = gl->GetString(GL_VENDOR);
+ const char *version = gl->GetString(GL_VERSION);
+ const char *renderer = gl->GetString(GL_RENDERER);
+
+ mp_msg(MSGT_VO, MSGL_V, "[vo_corevideo] Running on OpenGL '%s' by '%s',"
+ " version '%s'\n", renderer, vendor, version);
+
+ gl->Disable(GL_BLEND);
+ gl->Disable(GL_DEPTH_TEST);
+ gl->DepthMask(GL_FALSE);
+ gl->Disable(GL_CULL_FACE);
+ gl->Enable(GL_TEXTURE_2D);
+ gl->DrawBuffer(GL_BACK);
+ gl->TexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+ if (!p->osd)
+ p->osd = mpgl_osd_init(gl, true);
+
+ resize(vo, d_width, d_height);
+
+ gl->ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ gl->Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ if (gl->SwapInterval)
+ gl->SwapInterval(1);
+ return 1;
+}
+
+static void release_cv_entities(struct vo *vo) {
+ struct priv *p = vo->priv;
+ CVPixelBufferRelease(p->pixelBuffer);
+ p->pixelBuffer = NULL;
+ CVOpenGLTextureRelease(p->texture);
+ p->texture = NULL;
+ CVOpenGLTextureCacheRelease(p->textureCache);
+ p->textureCache = NULL;
+}
+
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ struct priv *p = vo->priv;
+ release_cv_entities(vo);
+ p->image_width = width;
+ p->image_height = height;
+
+ int mpgl_caps = MPGL_CAP_GL_LEGACY;
+ if (!mpgl_create_window(p->mpglctx, mpgl_caps, d_width, d_height, flags))
+ return -1;
+
+ init_gl(vo, vo->dwidth, vo->dheight);
+
+ return 0;
+}
+
+static void check_events(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ int e = p->mpglctx->check_events(vo);
+ if (e & VO_EVENT_RESIZE)
+ resize(vo, vo->dwidth, vo->dheight);
+}
+
+static void prepare_texture(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ struct quad *q = p->quad;
+ CVReturn error;
+
+ CVOpenGLTextureRelease(p->texture);
+ error = CVOpenGLTextureCacheCreateTextureFromImage(NULL,
+ p->textureCache, p->pixelBuffer, 0, &p->texture);
+ if(error != kCVReturnSuccess)
+ mp_msg(MSGT_VO, MSGL_ERR,"[vo_corevideo] Failed to create OpenGL"
+ " texture(%d)\n", error);
+
+ CVOpenGLTextureGetCleanTexCoords(p->texture, q->lowerLeft, q->lowerRight,
+ q->upperRight, q->upperLeft);
+}
+
+static void do_render(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ struct quad *q = p->quad;
+ GL *gl = p->mpglctx->gl;
+ prepare_texture(vo);
+
+ float x0 = 0;
+ float y0 = 0;
+ float w = p->image_width;
+ float h = p->image_height;
+
+ // vertically flips the image
+ y0 += h;
+ h = -h;
+
+ float xm = x0 + w;
+ float ym = y0 + h;
+
+ gl->Enable(CVOpenGLTextureGetTarget(p->texture));
+ gl->BindTexture(
+ CVOpenGLTextureGetTarget(p->texture),
+ CVOpenGLTextureGetName(p->texture));
+
+ gl->Begin(GL_QUADS);
+ gl->TexCoord2fv(q->lowerLeft); gl->Vertex2f(x0, y0);
+ gl->TexCoord2fv(q->upperLeft); gl->Vertex2f(x0, ym);
+ gl->TexCoord2fv(q->upperRight); gl->Vertex2f(xm, ym);
+ gl->TexCoord2fv(q->lowerRight); gl->Vertex2f(xm, y0);
+ gl->End();
+
+ gl->Disable(CVOpenGLTextureGetTarget(p->texture));
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ p->mpglctx->swapGlBuffers(p->mpglctx);
+ p->mpglctx->gl->Clear(GL_COLOR_BUFFER_BIT);
+}
+
+static uint32_t draw_image(struct vo *vo, mp_image_t *mpi)
+{
+ struct priv *p = vo->priv;
+ CVReturn error;
+
+ if (!p->textureCache || !p->pixelBuffer) {
+ error = CVOpenGLTextureCacheCreate(NULL, 0, vo_cocoa_cgl_context(vo),
+ vo_cocoa_cgl_pixel_format(vo), 0, &p->textureCache);
+ if(error != kCVReturnSuccess)
+ mp_msg(MSGT_VO, MSGL_ERR,"[vo_corevideo] Failed to create OpenGL"
+ " texture Cache(%d)\n", error);
+
+ error = CVPixelBufferCreateWithBytes(NULL, mpi->width, mpi->height,
+ p->pixelFormat, mpi->planes[0], mpi->width * mpi->bpp / 8,
+ NULL, NULL, NULL, &p->pixelBuffer);
+ if(error != kCVReturnSuccess)
+ mp_msg(MSGT_VO, MSGL_ERR,"[vo_corevideo] Failed to create Pixel"
+ "Buffer(%d)\n", error);
+ }
+
+ do_render(vo);
+ return VO_TRUE;
+}
+
+static int query_format(struct vo *vo, uint32_t format)
+{
+ struct priv *p = vo->priv;
+ const int flags = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW |
+ VFCAP_OSD | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN |
+ VOCAP_NOSLICES;
+ switch (format) {
+ case IMGFMT_YUY2:
+ p->pixelFormat = kYUVSPixelFormat;
+ return flags;
+
+ case IMGFMT_RGB24:
+ p->pixelFormat = k24RGBPixelFormat;
+ return flags;
+
+ case IMGFMT_ARGB:
+ p->pixelFormat = k32ARGBPixelFormat;
+ return flags;
+
+ case IMGFMT_BGRA:
+ p->pixelFormat = k32BGRAPixelFormat;
+ return flags;
+ }
+ return 0;
+}
+
+static void uninit(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ if (p->osd)
+ mpgl_osd_destroy(p->osd);
+ release_cv_entities(vo);
+ mpgl_uninit(p->mpglctx);
+}
+
+
+static int preinit(struct vo *vo, const char *arg)
+{
+ struct priv *p = vo->priv;
+
+ *p = (struct priv) {
+ .mpglctx = mpgl_init(GLTYPE_COCOA, vo),
+ .colorspace = MP_CSP_DETAILS_DEFAULTS,
+ .quad = talloc_ptrtype(p, p->quad),
+ };
+
+ return 0;
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ struct priv *p = vo->priv;
+ GL *gl = p->mpglctx->gl;
+ assert(p->osd);
+
+ gl->MatrixMode(GL_PROJECTION);
+ gl->PushMatrix();
+ gl->LoadIdentity();
+ gl->Ortho(0, vo->dwidth, vo->dheight, 0, -1, 1);
+
+ struct mp_osd_res res = {
+ .w = vo->dwidth,
+ .h = vo->dheight,
+ .display_par = vo->monitor_par,
+ .video_par = vo->aspdat.par,
+ };
+
+ if (aspect_scaling()) {
+ res.ml = res.mr = p->ass_border_x;
+ res.mt = res.mb = p->ass_border_y;
+ }
+
+ mpgl_osd_draw_legacy(p->osd, osd, res);
+
+ gl->PopMatrix();
+}
+
+static CFStringRef get_cv_csp_matrix(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ switch (p->colorspace.format) {
+ case MP_CSP_BT_601:
+ return kCVImageBufferYCbCrMatrix_ITU_R_601_4;
+ case MP_CSP_BT_709:
+ return kCVImageBufferYCbCrMatrix_ITU_R_709_2;
+ case MP_CSP_SMPTE_240M:
+ return kCVImageBufferYCbCrMatrix_SMPTE_240M_1995;
+ }
+ return kCVImageBufferYCbCrMatrix_ITU_R_601_4;
+}
+
+static void set_yuv_colorspace(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ CVBufferSetAttachment(p->pixelBuffer,
+ kCVImageBufferYCbCrMatrixKey, get_cv_csp_matrix(vo),
+ kCVAttachmentMode_ShouldPropagate);
+ vo->want_redraw = true;
+}
+
+static int get_image_fmt(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ switch (p->pixelFormat) {
+ case kYUVSPixelFormat: return IMGFMT_YUY2;
+ case k24RGBPixelFormat: return IMGFMT_RGB24;
+ case k32ARGBPixelFormat: return IMGFMT_ARGB;
+ case k32BGRAPixelFormat: return IMGFMT_BGRA;
+ }
+ mp_msg(MSGT_VO, MSGL_ERR, "[vo_corevideo] Failed to convert pixel format. "
+ "Please contact the developers. PixelFormat: %d\n", p->pixelFormat);
+ return -1;
+}
+
+static mp_image_t *get_screenshot(struct vo *vo)
+{
+ int img_fmt = get_image_fmt(vo);
+ if (img_fmt < 0) return NULL;
+
+ struct priv *p = vo->priv;
+ void *base = CVPixelBufferGetBaseAddress(p->pixelBuffer);
+
+ size_t width = CVPixelBufferGetWidth(p->pixelBuffer);
+ size_t height = CVPixelBufferGetHeight(p->pixelBuffer);
+ size_t stride = CVPixelBufferGetBytesPerRow(p->pixelBuffer);
+ size_t image_size = stride * height;
+
+ mp_image_t *image = alloc_mpi(width, height, img_fmt);
+ memcpy(image->planes[0], base, image_size);
+ image->stride[0] = stride;
+
+ image->display_w = vo->aspdat.prew;
+ image->display_h = vo->aspdat.preh;
+
+ mp_image_set_colorspace_details(image, &p->colorspace);
+
+ return image;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct priv *p = vo->priv;
+ switch (request) {
+ case VOCTRL_DRAW_IMAGE:
+ return draw_image(vo, data);
+ case VOCTRL_QUERY_FORMAT:
+ return query_format(vo, *(uint32_t*)data);
+ case VOCTRL_ONTOP:
+ p->mpglctx->ontop(vo);
+ return VO_TRUE;
+ case VOCTRL_PAUSE:
+ if (!p->mpglctx->pause)
+ break;
+ p->mpglctx->pause(vo);
+ return VO_TRUE;
+ case VOCTRL_RESUME:
+ if (!p->mpglctx->resume)
+ break;
+ p->mpglctx->resume(vo);
+ return VO_TRUE;
+ case VOCTRL_FULLSCREEN:
+ p->mpglctx->fullscreen(vo);
+ resize(vo, vo->dwidth, vo->dheight);
+ return VO_TRUE;
+ case VOCTRL_GET_PANSCAN:
+ return VO_TRUE;
+ case VOCTRL_SET_PANSCAN:
+ resize(vo, vo->dwidth, vo->dheight);
+ return VO_TRUE;
+ case VOCTRL_UPDATE_SCREENINFO:
+ p->mpglctx->update_xinerama_info(vo);
+ return VO_TRUE;
+ case VOCTRL_REDRAW_FRAME:
+ do_render(vo);
+ return VO_TRUE;
+ case VOCTRL_SET_YUV_COLORSPACE:
+ p->colorspace.format = ((struct mp_csp_details *)data)->format;
+ set_yuv_colorspace(vo);
+ return VO_TRUE;
+ case VOCTRL_GET_YUV_COLORSPACE:
+ *(struct mp_csp_details *)data = p->colorspace;
+ return VO_TRUE;
+ case VOCTRL_SCREENSHOT: {
+ struct voctrl_screenshot_args *args = data;
+ if (args->full_window)
+ args->out_image = glGetWindowScreenshot(p->mpglctx->gl);
+ else
+ args->out_image = get_screenshot(vo);
+ return VO_TRUE;
+ }
+ }
+ return VO_NOTIMPL;
+}
+
+const struct vo_driver video_out_corevideo = {
+ .is_new = true,
+ .info = &(const vo_info_t) {
+ "Mac OS X Core Video",
+ "corevideo",
+ "Nicolas Plourde <nicolas.plourde@gmail.com> and others",
+ ""
+ },
+ .preinit = preinit,
+ .config = config,
+ .control = control,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+ .check_events = check_events,
+ .uninit = uninit,
+ .priv_size = sizeof(struct priv),
+};
diff --git a/video/out/vo_direct3d.c b/video/out/vo_direct3d.c
new file mode 100644
index 0000000000..294a101ffe
--- /dev/null
+++ b/video/out/vo_direct3d.c
@@ -0,0 +1,2101 @@
+/*
+ * Copyright (c) 2008 Georgi Petrov (gogothebee) <gogothebee@gmail.com>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <windows.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <d3d9.h>
+#include "config.h"
+#include "options.h"
+#include "subopt-helper.h"
+#include "talloc.h"
+#include "video_out.h"
+#include "libmpcodecs/vfcap.h"
+#include "csputils.h"
+#include "libmpcodecs/mp_image.h"
+#include "libmpcodecs/img_format.h"
+#include "fastmemcpy.h"
+#include "mp_msg.h"
+#include "aspect.h"
+#include "w32_common.h"
+#include "libavutil/common.h"
+#include "sub/sub.h"
+#include "bitmap_packer.h"
+
+// shaders generated by fxc.exe from d3d_shader_yuv.hlsl
+#include "d3d_shader_yuv.h"
+#include "d3d_shader_yuv_2ch.h"
+
+
+// TODO: beg someone to add this (there is already IMGFMT_Y8)
+// equals MAKEFOURCC('Y', '1', '6', ' ')
+#define IMGFMT_Y16 0x20363159
+#define IMGFMT_A8Y8 MAKEFOURCC('A', '8', 'Y', '8')
+
+#define IMGFMT_IS_Y(x) ((x) == IMGFMT_Y8 || (x) == IMGFMT_Y16 || (x) == IMGFMT_A8Y8)
+#define IMGFMT_Y_DEPTH(x) ((x) == IMGFMT_Y8 ? 8 : 16)
+
+#define DEVTYPE D3DDEVTYPE_HAL
+//#define DEVTYPE D3DDEVTYPE_REF
+
+#define D3DFVF_OSD_VERTEX (D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_DIFFUSE)
+
+typedef struct {
+ float x, y, z;
+ D3DCOLOR color;
+ float tu, tv;
+} vertex_osd;
+
+#define D3DFVF_VIDEO_VERTEX (D3DFVF_XYZ | D3DFVF_TEX3)
+
+typedef struct {
+ float x, y, z;
+ // pairs of texture coordinates for up to 3 planes
+ float t[3][2];
+} vertex_video;
+
+
+struct d3dtex {
+ // user-requested size
+ int w, h;
+ // allocated texture size
+ int tex_w, tex_h;
+ // D3DPOOL_SYSTEMMEM (or others) texture:
+ // - can be locked in order to write (and even read) data
+ // - can _not_ (probably) be used as texture for rendering
+ // This is always non-NULL if d3dtex_allocate succeeds.
+ IDirect3DTexture9 *system;
+ // D3DPOOL_DEFAULT texture:
+ // - can't be locked (Probably.)
+ // - must be used for rendering
+ // This will be NULL on systems with device_texture_sys != 0.
+ IDirect3DTexture9 *device;
+};
+
+struct texplane {
+ int bytes_per_pixel;
+ int bits_per_pixel;
+ // chroma shifts
+ // e.g. get the plane's width in pixels with (priv->src_width >> shift_x)
+ int shift_x, shift_y;
+ D3DFORMAT d3d_format;
+ struct d3dtex texture;
+ // temporary locking during uploading the frame (e.g. for draw_slice)
+ D3DLOCKED_RECT locked_rect;
+ // value used to clear the image with memset (YUV chroma planes do not use
+ // the value 0 for this)
+ uint8_t clearval;
+};
+
+struct osdpart {
+ enum sub_bitmap_format format;
+ int bitmap_id, bitmap_pos_id;
+ struct d3dtex texture;
+ int num_vertices;
+ vertex_osd *vertices;
+ struct bitmap_packer *packer;
+};
+
+/* Global variables "priv" structure. I try to keep their count low.
+ */
+typedef struct d3d_priv {
+ int opt_prefer_stretchrect;
+ int opt_disable_textures;
+ int opt_disable_stretchrect;
+ int opt_disable_shaders;
+ int opt_only_8bit;
+ int opt_disable_osd;
+ int opt_disable_texture_align;
+ // debugging
+ int opt_force_power_of_2;
+ int opt_texture_memory;
+ int opt_swap_discard;
+ int opt_exact_backbuffer;
+ int opt_16bit_textures;
+
+ struct vo *vo;
+
+ int is_clear_needed; /**< 1 = Clear the backbuffer before StretchRect
+ 0 = (default) Don't clear it */
+ D3DLOCKED_RECT locked_rect; /**< The locked offscreen surface */
+ RECT fs_movie_rect; /**< Rect (upscaled) of the movie when displayed
+ in fullscreen */
+ RECT fs_panscan_rect; /**< PanScan source surface cropping in
+ fullscreen */
+ int src_width; /**< Source (movie) width */
+ int src_height; /**< Source (movie) heigth */
+ struct mp_osd_res osd_res;
+ int image_format; /**< mplayer image format */
+ bool use_textures; /**< use 3D texture rendering, instead of
+ StretchRect */
+ bool use_shaders; /**< use shader for YUV color conversion
+ (or possibly for RGB video equalizers) */
+ bool use_2ch_hack; /**< 2 byte YUV formats use 2 channel hack */
+
+ int plane_count;
+ struct texplane planes[3];
+
+ IDirect3DPixelShader9 *pixel_shader;
+ const BYTE *pixel_shader_data;
+
+ D3DFORMAT movie_src_fmt; /**< Movie colorspace format (depends on
+ the movie's codec) */
+ D3DFORMAT desktop_fmt; /**< Desktop (screen) colorspace format.
+ Usually XRGB */
+
+ HANDLE d3d9_dll; /**< d3d9 Library HANDLE */
+ IDirect3D9 * (WINAPI *pDirect3DCreate9)(UINT); /**< pointer to Direct3DCreate9 function */
+
+ LPDIRECT3D9 d3d_handle; /**< Direct3D Handle */
+ LPDIRECT3DDEVICE9 d3d_device; /**< The Direct3D Adapter */
+ bool d3d_in_scene; /**< BeginScene was called, EndScene not */
+ IDirect3DSurface9 *d3d_surface; /**< Offscreen Direct3D Surface. MPlayer
+ renders inside it. Uses colorspace
+ priv->movie_src_fmt */
+ IDirect3DSurface9 *d3d_backbuf; /**< Video card's back buffer (used to
+ display next frame) */
+ int cur_backbuf_width; /**< Current backbuffer width */
+ int cur_backbuf_height; /**< Current backbuffer height */
+ int device_caps_power2_only; /**< 1 = texture sizes have to be power 2
+ 0 = texture sizes can be anything */
+ int device_caps_square_only; /**< 1 = textures have to be square
+ 0 = textures do not have to be square */
+ int device_texture_sys; /**< 1 = device can texture from system memory
+ 0 = device requires shadow */
+ int max_texture_width; /**< from the device capabilities */
+ int max_texture_height; /**< from the device capabilities */
+
+ D3DMATRIX d3d_colormatrix;
+ float d3d_depth_vector[4];
+ struct mp_csp_details colorspace;
+ struct mp_csp_equalizer video_eq;
+
+ struct osdpart *osd[MAX_OSD_PARTS];
+} d3d_priv;
+
+struct fmt_entry {
+ const unsigned int mplayer_fmt; /**< Given by MPlayer */
+ const D3DFORMAT fourcc; /**< Required by D3D's test function */
+};
+
+/* Map table from reported MPlayer format to the required
+ fourcc. This is needed to perform the format query. */
+
+static const struct fmt_entry fmt_table[] = {
+ // planar YUV
+ {IMGFMT_YV12, MAKEFOURCC('Y','V','1','2')},
+ {IMGFMT_I420, MAKEFOURCC('I','4','2','0')},
+ {IMGFMT_IYUV, MAKEFOURCC('I','Y','U','V')},
+ {IMGFMT_YVU9, MAKEFOURCC('Y','V','U','9')},
+ // packed YUV
+ {IMGFMT_YUY2, D3DFMT_YUY2},
+ {IMGFMT_UYVY, D3DFMT_UYVY},
+ // packed RGB
+ {IMGFMT_BGR32, D3DFMT_X8R8G8B8},
+ {IMGFMT_RGB32, D3DFMT_X8B8G8R8},
+ {IMGFMT_BGR24, D3DFMT_R8G8B8}, //untested
+ {IMGFMT_BGR16, D3DFMT_R5G6B5},
+ {IMGFMT_BGR15, D3DFMT_X1R5G5B5},
+ {IMGFMT_BGR8 , D3DFMT_R3G3B2}, //untested
+ // grayscale (can be considered both packed and planar)
+ {IMGFMT_Y8, D3DFMT_L8},
+ {IMGFMT_Y16, D3DFMT_L16},
+ {IMGFMT_A8Y8, D3DFMT_A8L8},
+ {0},
+};
+
+static const D3DFORMAT osd_fmt_table[SUBBITMAP_COUNT] = {
+ [SUBBITMAP_LIBASS] = D3DFMT_A8,
+ [SUBBITMAP_RGBA] = D3DFMT_A8R8G8B8,
+};
+static const bool osd_fmt_supported[SUBBITMAP_COUNT] = {
+ [SUBBITMAP_LIBASS] = true,
+ [SUBBITMAP_RGBA] = true,
+};
+
+
+static void update_colorspace(d3d_priv *priv);
+static void d3d_clear_video_textures(d3d_priv *priv);
+static bool resize_d3d(d3d_priv *priv);
+static uint32_t d3d_draw_frame(d3d_priv *priv);
+static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h,
+ int x, int y);
+static void uninit(struct vo *vo);
+static void flip_page(struct vo *vo);
+static mp_image_t *get_screenshot(d3d_priv *priv);
+static mp_image_t *get_window_screenshot(d3d_priv *priv);
+
+
+static void d3d_matrix_identity(D3DMATRIX *m)
+{
+ memset(m, 0, sizeof(D3DMATRIX));
+ m->_11 = m->_22 = m->_33 = m->_44 = 1.0f;
+}
+
+static void d3d_matrix_ortho(D3DMATRIX *m, float left, float right,
+ float bottom, float top)
+{
+ d3d_matrix_identity(m);
+ m->_11 = 2.0f / (right - left);
+ m->_22 = 2.0f / (top - bottom);
+ m->_33 = 1.0f;
+ m->_41 = -(right + left) / (right - left);
+ m->_42 = -(top + bottom) / (top - bottom);
+ m->_43 = 0;
+ m->_44 = 1.0f;
+}
+
+/****************************************************************************
+ * *
+ * *
+ * *
+ * Direct3D specific implementation functions *
+ * *
+ * *
+ * *
+ ****************************************************************************/
+
+static bool d3d_begin_scene(d3d_priv *priv)
+{
+ if (!priv->d3d_in_scene) {
+ if (FAILED(IDirect3DDevice9_BeginScene(priv->d3d_device))) {
+ mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>BeginScene failed.\n");
+ return false;
+ }
+ priv->d3d_in_scene = true;
+ }
+ return true;
+}
+
+/** @brief Calculate scaled fullscreen movie rectangle with
+ * preserved aspect ratio.
+ */
+static void calc_fs_rect(d3d_priv *priv)
+{
+ struct mp_rect src_rect;
+ struct mp_rect dst_rect;
+ vo_get_src_dst_rects(priv->vo, &src_rect, &dst_rect, &priv->osd_res);
+
+ priv->fs_movie_rect.left = dst_rect.x0;
+ priv->fs_movie_rect.right = dst_rect.x1;
+ priv->fs_movie_rect.top = dst_rect.y0;
+ priv->fs_movie_rect.bottom = dst_rect.y1;
+ priv->fs_panscan_rect.left = src_rect.x0;
+ priv->fs_panscan_rect.right = src_rect.x1;
+ priv->fs_panscan_rect.top = src_rect.y0;
+ priv->fs_panscan_rect.bottom = src_rect.y1;
+
+ mp_msg(MSGT_VO, MSGL_V,
+ "<vo_direct3d>Video rectangle: t: %ld, l: %ld, r: %ld, b:%ld\n",
+ priv->fs_movie_rect.top, priv->fs_movie_rect.left,
+ priv->fs_movie_rect.right, priv->fs_movie_rect.bottom);
+
+ /* The backbuffer should be cleared before next StretchRect. This is
+ * necessary because our new draw area could be smaller than the
+ * previous one used by StretchRect and without it, leftovers from the
+ * previous frame will be left. */
+ priv->is_clear_needed = 1;
+}
+
+// Adjust the texture size *width/*height to fit the requirements of the D3D
+// device. The texture size is only increased.
+static void d3d_fix_texture_size(d3d_priv *priv, int *width, int *height)
+{
+ int tex_width = *width;
+ int tex_height = *height;
+
+ // avoid nasty special cases with 0-sized textures and texture sizes
+ tex_width = FFMAX(tex_width, 1);
+ tex_height = FFMAX(tex_height, 1);
+
+ if (priv->device_caps_power2_only) {
+ tex_width = 1;
+ tex_height = 1;
+ while (tex_width < *width) tex_width <<= 1;
+ while (tex_height < *height) tex_height <<= 1;
+ }
+ if (priv->device_caps_square_only)
+ /* device only supports square textures */
+ tex_width = tex_height = FFMAX(tex_width, tex_height);
+ // better round up to a multiple of 16
+ if (!priv->opt_disable_texture_align) {
+ tex_width = (tex_width + 15) & ~15;
+ tex_height = (tex_height + 15) & ~15;
+ }
+
+ *width = tex_width;
+ *height = tex_height;
+}
+
+static void d3dtex_release(d3d_priv *priv, struct d3dtex *tex)
+{
+ if (tex->system)
+ IDirect3DTexture9_Release(tex->system);
+ tex->system = NULL;
+
+ if (tex->device)
+ IDirect3DTexture9_Release(tex->device);
+ tex->device = NULL;
+
+ tex->tex_w = tex->tex_h = 0;
+}
+
+static bool d3dtex_allocate(d3d_priv *priv, struct d3dtex *tex, D3DFORMAT fmt,
+ int w, int h)
+{
+ d3dtex_release(priv, tex);
+
+ tex->w = w;
+ tex->h = h;
+
+ int tw = w, th = h;
+ d3d_fix_texture_size(priv, &tw, &th);
+
+ int memtype = D3DPOOL_SYSTEMMEM;
+ switch (priv->opt_texture_memory) {
+ case 1: memtype = D3DPOOL_MANAGED; break;
+ case 2: memtype = D3DPOOL_DEFAULT; break;
+ }
+
+ if (FAILED(IDirect3DDevice9_CreateTexture(priv->d3d_device, tw, th, 1,
+ D3DUSAGE_DYNAMIC, fmt, memtype, &tex->system, NULL)))
+ {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "<vo_direct3d>Allocating %dx%d texture in system RAM failed.\n",
+ w, h);
+ goto error_exit;
+ }
+
+ if (!priv->device_texture_sys && !priv->opt_texture_memory) {
+ if (FAILED(IDirect3DDevice9_CreateTexture(priv->d3d_device, tw, th, 1,
+ D3DUSAGE_DYNAMIC, fmt, D3DPOOL_DEFAULT, &tex->device, NULL)))
+ {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "<vo_direct3d>Allocating %dx%d texture in video RAM failed.\n",
+ w, h);
+ goto error_exit;
+ }
+ }
+
+ tex->tex_w = tw;
+ tex->tex_h = th;
+
+ return true;
+
+error_exit:
+ d3dtex_release(priv, tex);
+ return false;
+}
+
+static IDirect3DBaseTexture9 *d3dtex_get_render_texture(d3d_priv *priv,
+ struct d3dtex *tex)
+{
+ return (IDirect3DBaseTexture9 *)(tex->device ? tex->device : tex->system);
+}
+
+// Copy system texture contents to device texture.
+static bool d3dtex_update(d3d_priv *priv, struct d3dtex *tex)
+{
+ if (!tex->device)
+ return true;
+ return !FAILED(IDirect3DDevice9_UpdateTexture(priv->d3d_device,
+ (IDirect3DBaseTexture9 *)tex->system,
+ (IDirect3DBaseTexture9 *)tex->device));
+}
+
+static void d3d_unlock_video_objects(d3d_priv *priv)
+{
+ bool any_failed = false;
+
+ if (priv->locked_rect.pBits) {
+ if (FAILED(IDirect3DSurface9_UnlockRect(priv->d3d_surface)))
+ any_failed = true;
+ }
+ priv->locked_rect.pBits = NULL;
+
+ for (int n = 0; n < priv->plane_count; n++) {
+ struct texplane *plane = &priv->planes[n];
+ if (plane->locked_rect.pBits) {
+ if (FAILED(IDirect3DTexture9_UnlockRect(plane->texture.system, 0)))
+ any_failed = true;
+ }
+ plane->locked_rect.pBits = NULL;
+ }
+
+ if (any_failed) {
+ mp_msg(MSGT_VO, MSGL_V,
+ "<vo_direct3d>Unlocking video objects failed.\n");
+ }
+}
+
+// Free video surface/textures, shaders, etc.
+static void d3d_destroy_video_objects(d3d_priv *priv)
+{
+ d3d_unlock_video_objects(priv);
+
+ if (priv->d3d_surface)
+ IDirect3DSurface9_Release(priv->d3d_surface);
+ priv->d3d_surface = NULL;
+
+ for (int n = 0; n < priv->plane_count; n++) {
+ d3dtex_release(priv, &priv->planes[n].texture);
+ }
+
+ if (priv->pixel_shader)
+ IDirect3DPixelShader9_Release(priv->pixel_shader);
+ priv->pixel_shader = NULL;
+}
+
+/** @brief Destroy D3D Offscreen and Backbuffer surfaces.
+ */
+static void destroy_d3d_surfaces(d3d_priv *priv)
+{
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>destroy_d3d_surfaces called.\n");
+
+ d3d_destroy_video_objects(priv);
+
+ for (int n = 0; n < MAX_OSD_PARTS; n++) {
+ struct osdpart *osd = priv->osd[n];
+ d3dtex_release(priv, &osd->texture);
+ osd->bitmap_id = osd->bitmap_pos_id = -1;
+ }
+
+ if (priv->d3d_backbuf)
+ IDirect3DSurface9_Release(priv->d3d_backbuf);
+ priv->d3d_backbuf = NULL;
+
+ priv->d3d_in_scene = false;
+}
+
+// Allocate video surface or textures, and create shaders if needed.
+static bool d3d_configure_video_objects(d3d_priv *priv)
+{
+ int n;
+ bool need_clear = false;
+
+ assert(priv->image_format != 0);
+
+ if (priv->use_textures) {
+ for (n = 0; n < priv->plane_count; n++) {
+ struct texplane *plane = &priv->planes[n];
+
+ if (!plane->texture.system) {
+ if (!d3dtex_allocate(priv,
+ &plane->texture,
+ plane->d3d_format,
+ priv->src_width >> plane->shift_x,
+ priv->src_height >> plane->shift_y))
+ {
+ mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Allocating plane %d"
+ " failed.\n", n);
+ return false;
+ }
+
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Allocated plane %d:"
+ " %d bit, shift=%d/%d size=%d/%d (%d/%d).\n", n,
+ plane->bits_per_pixel,
+ plane->shift_x, plane->shift_y,
+ plane->texture.w, plane->texture.h,
+ plane->texture.tex_w, plane->texture.tex_h);
+
+ need_clear = true;
+ }
+ }
+
+ if (need_clear)
+ d3d_clear_video_textures(priv);
+
+ if (priv->pixel_shader_data) {
+ if (!priv->pixel_shader &&
+ FAILED(IDirect3DDevice9_CreatePixelShader(priv->d3d_device,
+ (DWORD *)priv->pixel_shader_data, &priv->pixel_shader)))
+ {
+ mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Failed to create "
+ "YUV conversion pixel shader.\n");
+ return false;
+ }
+ }
+
+ } else {
+
+ if (!priv->d3d_surface &&
+ FAILED(IDirect3DDevice9_CreateOffscreenPlainSurface(
+ priv->d3d_device, priv->src_width, priv->src_height,
+ priv->movie_src_fmt, D3DPOOL_DEFAULT, &priv->d3d_surface, NULL)))
+ {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "<vo_direct3d>Allocating offscreen surface failed.\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool d3d_lock_video_textures(d3d_priv *priv)
+{
+ for (int n = 0; n < priv->plane_count; n++) {
+ struct texplane *plane = &priv->planes[n];
+
+ if (!plane->locked_rect.pBits) {
+ if (FAILED(IDirect3DTexture9_LockRect(plane->texture.system, 0,
+ &plane->locked_rect, NULL, 0)))
+ {
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Texture lock failure.\n");
+ d3d_unlock_video_objects(priv);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static void d3d_clear_video_textures(d3d_priv *priv)
+{
+ if (!d3d_lock_video_textures(priv))
+ return;
+
+ for (int n = 0; n < priv->plane_count; n++) {
+ struct texplane *plane = &priv->planes[n];
+ memset(plane->locked_rect.pBits, plane->clearval,
+ plane->locked_rect.Pitch * plane->texture.tex_h);
+ }
+
+ d3d_unlock_video_objects(priv);
+}
+
+// Recreate and initialize D3D objects if necessary. The amount of work that
+// needs to be done can be quite different: it could be that full initialization
+// is required, or that some objects need to be created, or that nothing is
+// done.
+static bool create_d3d_surfaces(d3d_priv *priv)
+{
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>create_d3d_surfaces called.\n");
+
+ if (!priv->d3d_backbuf &&
+ FAILED(IDirect3DDevice9_GetBackBuffer(priv->d3d_device, 0, 0,
+ D3DBACKBUFFER_TYPE_MONO,
+ &priv->d3d_backbuf))) {
+ mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Allocating backbuffer failed.\n");
+ return 0;
+ }
+
+ if (!d3d_configure_video_objects(priv))
+ return 0;
+
+ /* setup default renderstate */
+ IDirect3DDevice9_SetRenderState(priv->d3d_device,
+ D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
+ IDirect3DDevice9_SetRenderState(priv->d3d_device,
+ D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
+ IDirect3DDevice9_SetRenderState(priv->d3d_device,
+ D3DRS_ALPHAFUNC, D3DCMP_GREATER);
+ IDirect3DDevice9_SetRenderState(priv->d3d_device,
+ D3DRS_ALPHAREF, (DWORD)0x0);
+ IDirect3DDevice9_SetRenderState(priv->d3d_device,
+ D3DRS_LIGHTING, FALSE);
+
+ // we use up to 3 samplers for up to 3 YUV planes
+ for (int n = 0; n < 3; n++) {
+ IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_MINFILTER,
+ D3DTEXF_LINEAR);
+ IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_MAGFILTER,
+ D3DTEXF_LINEAR);
+ IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_ADDRESSU,
+ D3DTADDRESS_CLAMP);
+ IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_ADDRESSV,
+ D3DTADDRESS_CLAMP);
+ }
+
+ return 1;
+}
+
+static bool init_d3d(d3d_priv *priv)
+{
+ D3DDISPLAYMODE disp_mode;
+ D3DCAPS9 disp_caps;
+ DWORD texture_caps;
+ DWORD dev_caps;
+
+ priv->d3d_handle = priv->pDirect3DCreate9(D3D_SDK_VERSION);
+ if (!priv->d3d_handle) {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "<vo_direct3d>Initializing Direct3D failed.\n");
+ return false;
+ }
+
+ if (FAILED(IDirect3D9_GetAdapterDisplayMode(priv->d3d_handle,
+ D3DADAPTER_DEFAULT,
+ &disp_mode))) {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "<vo_direct3d>Reading display mode failed.\n");
+ return false;
+ }
+
+ priv->desktop_fmt = disp_mode.Format;
+ priv->cur_backbuf_width = disp_mode.Width;
+ priv->cur_backbuf_height = disp_mode.Height;
+
+ mp_msg(MSGT_VO, MSGL_V,
+ "<vo_direct3d>Setting backbuffer dimensions to (%dx%d).\n",
+ disp_mode.Width, disp_mode.Height);
+
+ if (FAILED(IDirect3D9_GetDeviceCaps(priv->d3d_handle,
+ D3DADAPTER_DEFAULT,
+ DEVTYPE,
+ &disp_caps)))
+ {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "<vo_direct3d>Reading display capabilities failed.\n");
+ return false;
+ }
+
+ /* Store relevant information reguarding caps of device */
+ texture_caps = disp_caps.TextureCaps;
+ dev_caps = disp_caps.DevCaps;
+ priv->device_caps_power2_only = (texture_caps & D3DPTEXTURECAPS_POW2) &&
+ !(texture_caps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL);
+ priv->device_caps_square_only = texture_caps & D3DPTEXTURECAPS_SQUAREONLY;
+ priv->device_texture_sys = dev_caps & D3DDEVCAPS_TEXTURESYSTEMMEMORY;
+ priv->max_texture_width = disp_caps.MaxTextureWidth;
+ priv->max_texture_height = disp_caps.MaxTextureHeight;
+
+ if (priv->opt_force_power_of_2)
+ priv->device_caps_power2_only = 1;
+
+ mp_msg(MSGT_VO, MSGL_V,
+ "<vo_direct3d>device_caps_power2_only %d, device_caps_square_only %d\n"
+ "<vo_direct3d>device_texture_sys %d\n"
+ "<vo_direct3d>max_texture_width %d, max_texture_height %d\n",
+ priv->device_caps_power2_only, priv->device_caps_square_only,
+ priv->device_texture_sys, priv->max_texture_width,
+ priv->max_texture_height);
+
+ return true;
+}
+
+/** @brief Fill D3D Presentation parameters
+ */
+static void fill_d3d_presentparams(d3d_priv *priv,
+ D3DPRESENT_PARAMETERS *present_params)
+{
+ /* Prepare Direct3D initialization parameters. */
+ memset(present_params, 0, sizeof(D3DPRESENT_PARAMETERS));
+ present_params->Windowed = TRUE;
+ present_params->SwapEffect =
+ priv->opt_swap_discard ? D3DSWAPEFFECT_DISCARD : D3DSWAPEFFECT_COPY;
+ present_params->Flags = D3DPRESENTFLAG_VIDEO;
+ present_params->hDeviceWindow = priv->vo->w32->window;
+ present_params->BackBufferWidth = priv->cur_backbuf_width;
+ present_params->BackBufferHeight = priv->cur_backbuf_height;
+ present_params->MultiSampleType = D3DMULTISAMPLE_NONE;
+ present_params->PresentationInterval = D3DPRESENT_INTERVAL_ONE;
+ present_params->BackBufferFormat = priv->desktop_fmt;
+ present_params->BackBufferCount = 1;
+ present_params->EnableAutoDepthStencil = FALSE;
+}
+
+
+// Create a new backbuffer. Create or Reset the D3D device.
+static bool change_d3d_backbuffer(d3d_priv *priv)
+{
+ D3DPRESENT_PARAMETERS present_params;
+
+ int window_w = priv->vo->dwidth;
+ int window_h = priv->vo->dheight;
+
+ /* Grow the backbuffer in the required dimension. */
+ if (window_w > priv->cur_backbuf_width)
+ priv->cur_backbuf_width = window_w;
+
+ if (window_h > priv->cur_backbuf_height)
+ priv->cur_backbuf_height = window_h;
+
+ if (priv->opt_exact_backbuffer) {
+ priv->cur_backbuf_width = window_w;
+ priv->cur_backbuf_height = window_h;
+ }
+
+ /* The grown backbuffer dimensions are ready and fill_d3d_presentparams
+ * will use them, so we can reset the device.
+ */
+ fill_d3d_presentparams(priv, &present_params);
+
+ if (!priv->d3d_device) {
+ if (FAILED(IDirect3D9_CreateDevice(priv->d3d_handle,
+ D3DADAPTER_DEFAULT,
+ DEVTYPE, priv->vo->w32->window,
+ D3DCREATE_SOFTWARE_VERTEXPROCESSING
+ | D3DCREATE_FPU_PRESERVE,
+ &present_params, &priv->d3d_device)))
+ {
+ mp_msg(MSGT_VO, MSGL_V,
+ "<vo_direct3d>Creating Direct3D device failed.\n");
+ return 0;
+ }
+ } else {
+ if (FAILED(IDirect3DDevice9_Reset(priv->d3d_device, &present_params))) {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "<vo_direct3d>Reseting Direct3D device failed.\n");
+ return 0;
+ }
+ }
+
+ mp_msg(MSGT_VO, MSGL_V,
+ "<vo_direct3d>New backbuffer (%dx%d), VO (%dx%d)\n",
+ present_params.BackBufferWidth, present_params.BackBufferHeight,
+ window_w, window_h);
+
+ return 1;
+}
+
+/** @brief Reconfigure the whole Direct3D. Called only
+ * when the video adapter becomes uncooperative. ("Lost" devices)
+ * @return 1 on success, 0 on failure
+ */
+static int reconfigure_d3d(d3d_priv *priv)
+{
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>reconfigure_d3d called.\n");
+
+ destroy_d3d_surfaces(priv);
+
+ if (priv->d3d_device)
+ IDirect3DDevice9_Release(priv->d3d_device);
+ priv->d3d_device = NULL;
+
+ // Force complete destruction of the D3D state.
+ // Note: this step could be omitted. The resize_d3d call below would detect
+ // that d3d_device is NULL, and would properly recreate it. I'm not sure why
+ // the following code to release and recreate the d3d_handle exists.
+ if (priv->d3d_handle)
+ IDirect3D9_Release(priv->d3d_handle);
+ priv->d3d_handle = NULL;
+ if (!init_d3d(priv))
+ return 0;
+
+ // Proper re-initialization.
+ if (!resize_d3d(priv))
+ return 0;
+
+ return 1;
+}
+
+// Resize Direct3D context on window resize.
+// This function also is called when major initializations need to be done.
+static bool resize_d3d(d3d_priv *priv)
+{
+ D3DVIEWPORT9 vp = {0, 0, priv->vo->dwidth, priv->vo->dheight, 0, 1};
+
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>resize_d3d called.\n");
+
+ /* Make sure that backbuffer is large enough to accomodate the new
+ viewport dimensions. Grow it if necessary. */
+
+ bool backbuf_resize = priv->vo->dwidth > priv->cur_backbuf_width ||
+ priv->vo->dheight > priv->cur_backbuf_height;
+
+ if (priv->opt_exact_backbuffer) {
+ backbuf_resize = priv->vo->dwidth != priv->cur_backbuf_width ||
+ priv->vo->dheight != priv->cur_backbuf_height;
+ }
+
+ if (backbuf_resize || !priv->d3d_device)
+ {
+ destroy_d3d_surfaces(priv);
+ if (!change_d3d_backbuffer(priv))
+ return 0;
+ }
+
+ if (!priv->d3d_device)
+ return 1;
+
+ if (!create_d3d_surfaces(priv))
+ return 0;
+
+ if (FAILED(IDirect3DDevice9_SetViewport(priv->d3d_device, &vp))) {
+ mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Setting viewport failed.\n");
+ return 0;
+ }
+
+ // so that screen coordinates map to D3D ones
+ D3DMATRIX view;
+ d3d_matrix_ortho(&view, 0.5f, vp.Width + 0.5f, vp.Height + 0.5f, 0.5f);
+ IDirect3DDevice9_SetTransform(priv->d3d_device, D3DTS_VIEW, &view);
+
+ calc_fs_rect(priv);
+
+ priv->vo->want_redraw = true;
+
+ return 1;
+}
+
+/** @brief Uninitialize Direct3D and close the window.
+ */
+static void uninit_d3d(d3d_priv *priv)
+{
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>uninit_d3d called.\n");
+
+ destroy_d3d_surfaces(priv);
+
+ if (priv->d3d_device)
+ IDirect3DDevice9_Release(priv->d3d_device);
+ priv->d3d_device = NULL;
+
+ if (priv->d3d_handle) {
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Stopping Direct3D.\n");
+ IDirect3D9_Release(priv->d3d_handle);
+ }
+ priv->d3d_handle = NULL;
+}
+
+static uint32_t d3d_upload_and_render_frame_texture(d3d_priv *priv,
+ mp_image_t *mpi)
+{
+ if (!(mpi->flags & MP_IMGFLAG_DRAW_CALLBACK))
+ draw_slice(priv->vo, mpi->planes, mpi->stride, mpi->w, mpi->h, 0, 0);
+
+ d3d_unlock_video_objects(priv);
+
+ for (int n = 0; n < priv->plane_count; n++) {
+ d3dtex_update(priv, &priv->planes[n].texture);
+ }
+
+ return d3d_draw_frame(priv);
+}
+
+/** @brief Render a frame on the screen.
+ * @param mpi mpi structure with the decoded frame inside
+ * @return VO_TRUE on success, VO_ERROR on failure
+ */
+static uint32_t d3d_upload_and_render_frame(d3d_priv *priv, mp_image_t *mpi)
+{
+ if (!priv->d3d_device)
+ return VO_TRUE;
+
+ if (priv->use_textures)
+ return d3d_upload_and_render_frame_texture(priv, mpi);
+
+ if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK)
+ goto skip_upload;
+
+ if (mpi->flags & MP_IMGFLAG_PLANAR) { /* Copy a planar frame. */
+ draw_slice(priv->vo, mpi->planes, mpi->stride, mpi->w, mpi->h, 0, 0);
+ goto skip_upload;
+ }
+
+ /* If we're here, then we should lock the rect and copy a packed frame */
+ if (!priv->locked_rect.pBits) {
+ if (FAILED(IDirect3DSurface9_LockRect(priv->d3d_surface,
+ &priv->locked_rect, NULL, 0))) {
+ mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Surface lock failed.\n");
+ return VO_ERROR;
+ }
+ }
+
+ memcpy_pic(priv->locked_rect.pBits, mpi->planes[0], mpi->stride[0],
+ mpi->height, priv->locked_rect.Pitch, mpi->stride[0]);
+
+skip_upload:
+ d3d_unlock_video_objects(priv);
+
+ return d3d_draw_frame(priv);
+}
+
+static uint32_t d3d_draw_frame(d3d_priv *priv)
+{
+ int n;
+
+ if (!priv->d3d_device)
+ return VO_TRUE;
+
+ if (!d3d_begin_scene(priv))
+ return VO_ERROR;
+
+ if (priv->is_clear_needed || priv->opt_swap_discard) {
+ IDirect3DDevice9_Clear(priv->d3d_device, 0, NULL,
+ D3DCLEAR_TARGET, 0, 0, 0);
+ priv->is_clear_needed = 0;
+ }
+
+ if (priv->use_textures) {
+
+ for (n = 0; n < priv->plane_count; n++) {
+ IDirect3DDevice9_SetTexture(priv->d3d_device, n,
+ d3dtex_get_render_texture(priv, &priv->planes[n].texture));
+ }
+
+ RECT rm = priv->fs_movie_rect;
+ RECT rs = priv->fs_panscan_rect;
+
+ vertex_video vb[] = {
+ { rm.left, rm.top, 0.0f},
+ { rm.right, rm.top, 0.0f},
+ { rm.left, rm.bottom, 0.0f},
+ { rm.right, rm.bottom, 0.0f}
+ };
+
+ float texc[4][2] = {
+ { rs.left, rs.top},
+ { rs.right, rs.top},
+ { rs.left, rs.bottom},
+ { rs.right, rs.bottom}
+ };
+
+ for (n = 0; n < priv->plane_count; n++) {
+ float s_x = (1.0f / (1 << priv->planes[n].shift_x))
+ / priv->planes[n].texture.tex_w;
+ float s_y = (1.0f / (1 << priv->planes[n].shift_y))
+ / priv->planes[n].texture.tex_h;
+ for (int i = 0; i < 4; i++) {
+ vb[i].t[n][0] = texc[i][0] * s_x;
+ vb[i].t[n][1] = texc[i][1] * s_y;
+ }
+ }
+
+ if (priv->pixel_shader) {
+ IDirect3DDevice9_SetPixelShader(priv->d3d_device, priv->pixel_shader);
+ IDirect3DDevice9_SetPixelShaderConstantF(priv->d3d_device, 0,
+ &priv->d3d_colormatrix._11,
+ 4);
+ IDirect3DDevice9_SetPixelShaderConstantF(priv->d3d_device, 5,
+ priv->d3d_depth_vector,
+ 1);
+ }
+
+ IDirect3DDevice9_SetFVF(priv->d3d_device, D3DFVF_VIDEO_VERTEX);
+ IDirect3DDevice9_DrawPrimitiveUP(priv->d3d_device, D3DPT_TRIANGLESTRIP,
+ 2, &vb[0], sizeof(vertex_video));
+
+ IDirect3DDevice9_SetPixelShader(priv->d3d_device, NULL);
+
+ for (n = 0; n < priv->plane_count; n++) {
+ IDirect3DDevice9_SetTexture(priv->d3d_device, n, NULL);
+ }
+
+ } else {
+ if (FAILED(IDirect3DDevice9_StretchRect(priv->d3d_device,
+ priv->d3d_surface,
+ &priv->fs_panscan_rect,
+ priv->d3d_backbuf,
+ &priv->fs_movie_rect,
+ D3DTEXF_LINEAR))) {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "<vo_direct3d>Copying frame to the backbuffer failed.\n");
+ return VO_ERROR;
+ }
+ }
+
+ return VO_TRUE;
+}
+
+// Return the high byte of the value that represents white in chroma (U/V)
+static int get_chroma_clear_val(int bit_depth)
+{
+ return 1 << (bit_depth - 1 & 7);
+}
+
+// this macro is supposed to work on all formats supported by 3D rendering, and
+// that produce "reasonable" output (i.e. no mixed up colors)
+#define IMGFMT_IS_ANY_RND(x) \
+ (IMGFMT_IS_BGR(x) || IMGFMT_IS_RGB(x) || IMGFMT_IS_Y(x))
+
+// pixel size in bit for any IMGFMT_IS_ANY_RND(x)==true
+// we assume that the actual pixel strides are always aligned on bytes
+static int imgfmt_any_rnd_depth(int fmt)
+{
+ if (IMGFMT_IS_BGR(fmt))
+ return IMGFMT_BGR_DEPTH(fmt);
+ if (IMGFMT_IS_RGB(fmt))
+ return IMGFMT_RGB_DEPTH(fmt);
+ if (IMGFMT_IS_Y(fmt))
+ return IMGFMT_Y_DEPTH(fmt);
+ assert(false);
+ return 0;
+}
+
+static D3DFORMAT check_format(d3d_priv *priv, uint32_t movie_fmt,
+ bool as_texture)
+{
+ const char *type = as_texture ? "texture rendering" : "StretchRect";
+ const struct fmt_entry *cur = &fmt_table[0];
+
+ // Don't try to handle weird packed texture formats (although I don't know
+ // if D3D9 would even accept any such format for 3D rendering; and we
+ // certainly don't try any tricks like matching it to RGB formats and
+ // applying a YUV conversion matrix)
+ if (as_texture && !IMGFMT_IS_ANY_RND(movie_fmt))
+ return 0;
+
+ while (cur->mplayer_fmt) {
+ if (cur->mplayer_fmt == movie_fmt) {
+ HRESULT res;
+ if (as_texture) {
+ res = IDirect3D9_CheckDeviceFormat(priv->d3d_handle,
+ D3DADAPTER_DEFAULT,
+ DEVTYPE,
+ priv->desktop_fmt,
+ D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER,
+ D3DRTYPE_TEXTURE,
+ cur->fourcc);
+ } else {
+ /* Test conversion from Movie colorspace to
+ * display's target colorspace. */
+ res = IDirect3D9_CheckDeviceFormatConversion(priv->d3d_handle,
+ D3DADAPTER_DEFAULT,
+ DEVTYPE,
+ cur->fourcc,
+ priv->desktop_fmt);
+ }
+ if (FAILED(res)) {
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Rejected image format "
+ "(%s): %s\n", type, vo_format_name(cur->mplayer_fmt));
+ return 0;
+ }
+
+ mp_msg(MSGT_VO, MSGL_DBG2, "<vo_direct3d>Accepted image format "
+ "(%s): %s\n", type, vo_format_name(cur->mplayer_fmt));
+
+ return cur->fourcc;
+ }
+ cur++;
+ }
+
+ return 0;
+}
+
+// Check whether YUV conversion with shaders can be done.
+// Returns the format the individual planes should use (or 0 on failure)
+static D3DFORMAT check_shader_conversion(d3d_priv *priv, uint32_t fmt)
+{
+ if (priv->opt_disable_shaders)
+ return 0;
+ int component_bits;
+ if (!mp_get_chroma_shift(fmt, NULL, NULL, &component_bits))
+ return 0;
+ if (component_bits < 8 || component_bits > 16)
+ return 0;
+ bool is_8bit = component_bits == 8;
+ if (!is_8bit && priv->opt_only_8bit)
+ return 0;
+ int texfmt = IMGFMT_Y8;
+ if (!is_8bit) {
+ if (priv->opt_16bit_textures)
+ texfmt = IMGFMT_Y16;
+ else
+ texfmt = IMGFMT_A8Y8;
+ }
+ return check_format(priv, texfmt, true);
+}
+
+// Return if the image format can be used. If it can, decide which rendering
+// and conversion mode to use.
+// If initialize is true, actually setup all variables to use the picked
+// rendering mode.
+static bool init_rendering_mode(d3d_priv *priv, uint32_t fmt, bool initialize)
+{
+ int n;
+ int blit_d3dfmt = check_format(priv, fmt, false);
+ int texture_d3dfmt = check_format(priv, fmt, true);
+ int shader_d3dfmt = check_shader_conversion(priv, fmt);
+
+ if (priv->opt_disable_textures)
+ texture_d3dfmt = 0;
+ if (priv->opt_disable_shaders)
+ shader_d3dfmt = 0;
+ if (priv->opt_disable_stretchrect)
+ blit_d3dfmt = 0;
+
+ if (!(blit_d3dfmt || shader_d3dfmt || texture_d3dfmt))
+ return false;
+
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Accepted rendering methods for "
+ "format='%s': StretchRect=%#x, Texture=%#x, Texture+Shader=%#x.\n",
+ vo_format_name(fmt), blit_d3dfmt, texture_d3dfmt, shader_d3dfmt);
+
+ if (!initialize)
+ return true;
+
+ // initialization doesn't fail beyond this point
+
+ priv->use_shaders = false;
+ priv->use_textures = false;
+ priv->use_2ch_hack = false;
+ priv->movie_src_fmt = 0;
+ priv->pixel_shader_data = NULL;
+ priv->plane_count = 0;
+ priv->image_format = fmt;
+
+ if (blit_d3dfmt && priv->opt_prefer_stretchrect)
+ texture_d3dfmt = shader_d3dfmt = 0;
+
+ if (texture_d3dfmt) {
+ priv->use_textures = true;
+ } else if (shader_d3dfmt) {
+ priv->use_textures = true;
+ priv->use_shaders = true;
+ } else {
+ assert(!!blit_d3dfmt);
+ }
+
+ if (priv->use_textures) {
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Using 3D rendering.\n");
+
+ struct texplane *planes = &priv->planes[0];
+ planes[0].shift_x = planes[0].shift_y = 0;
+ planes[0].clearval = 0;
+
+ if (!priv->use_shaders) {
+ assert(IMGFMT_IS_ANY_RND(priv->image_format));
+ priv->plane_count = 1;
+ planes[0].bits_per_pixel = imgfmt_any_rnd_depth(priv->image_format);
+ planes[0].d3d_format = texture_d3dfmt;
+ } else {
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Using YUV shaders.\n");
+
+ int sx, sy, component_bits;
+ mp_get_chroma_shift(priv->image_format, &sx, &sy, &component_bits);
+ priv->plane_count = 3;
+ for (n = 0; n < 3; n++) {
+ planes[n].d3d_format = shader_d3dfmt;
+ planes[n].bits_per_pixel = component_bits;
+ if (n > 0) {
+ planes[n].shift_x = sx;
+ planes[n].shift_y = sy;
+ planes[n].clearval = get_chroma_clear_val(component_bits);
+ }
+ }
+ if (shader_d3dfmt != D3DFMT_A8L8) {
+ priv->pixel_shader_data = d3d_shader_yuv;
+ } else {
+ mp_msg(MSGT_VO, MSGL_WARN, "<vo_direct3d>Using YUV 2ch hack.\n");
+
+ priv->pixel_shader_data = d3d_shader_yuv_2ch;
+ priv->use_2ch_hack = true;
+ }
+ }
+
+ for (n = 0; n < priv->plane_count; n++) {
+ planes[n].bytes_per_pixel = (planes[n].bits_per_pixel + 7) / 8;
+ }
+
+ } else {
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Using StretchRect.\n");
+
+ priv->movie_src_fmt = blit_d3dfmt;
+ }
+
+ update_colorspace(priv);
+
+ return true;
+}
+
+/** @brief Query if movie colorspace is supported by the HW.
+ * @return 0 on failure, device capabilities (not probed
+ * currently) on success.
+ */
+static int query_format(d3d_priv *priv, uint32_t movie_fmt)
+{
+ if (!init_rendering_mode(priv, movie_fmt, false))
+ return 0;
+
+ int osd_caps = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW
+ | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN;
+ if (!priv->opt_disable_osd)
+ osd_caps |= VFCAP_OSD;
+ return osd_caps;
+}
+
+/****************************************************************************
+ * *
+ * *
+ * *
+ * libvo Control / Callback functions *
+ * *
+ * *
+ * *
+ ****************************************************************************/
+
+static void get_2ch_depth_multiplier(int depth, float *out_f1, float *out_f2) {
+ // How to get these values:
+ // The suffix i8 and i16 is for values with 8/16 bit fixed point numbers.
+ // The suffix f is for float, ideally in the range 0.0-1.0.
+ // c_i8 is a two component vector, sampled from a two channel texture.
+ // (c_i8.x is the low byte, c_i8.y is the high byte)
+ // r_f is the resulting color scalar value.
+ //
+ // c_i8 = c_f * (2^8-1)
+ // r_i16 = c_i8.x + c_i8.y * 2^8
+ // r_f = r_i16 / (2^16-1)
+ // = c_f.x * (2^8-1) / (2^16-1) + c_f.y * (2^8-1) * 2^8 / (2^16-1)
+ // = c_f.x * ((2^8-1) / (2^16-1)) + c_f.y * (2^8 * ((2^8-1) / (2^16-1)))
+ // out = ((2^8-1) / (2^16-1), 2^8 * ((2^8-1) / (2^16-1)))
+ // The result color is r_f = dot(c_f, out).
+ // Same goes for other bit depth, such as 10 bit. Assuming (2^depth-1) is
+ // the maximum possible value at that depth, you have to scale the value
+ // r_i16 with it, the factor (2^16-1) in the formula above has to be
+ // replaced with (2^depth-1).
+ float factor = (float)((1 << 8) - 1) / (float)((1 << depth) - 1);
+ *out_f1 = factor;
+ *out_f2 = 256.0 * factor;
+}
+
+static void update_colorspace(d3d_priv *priv)
+{
+ float coeff[3][4];
+ struct mp_csp_params csp = { .colorspace = priv->colorspace };
+ mp_csp_copy_equalizer_values(&csp, &priv->video_eq);
+
+ if (priv->use_shaders) {
+ if (!priv->use_2ch_hack) {
+ csp.input_bits = priv->planes[0].bits_per_pixel;
+ csp.texture_bits = (csp.input_bits + 7) & ~7;
+ } else {
+ float f1, f2;
+ get_2ch_depth_multiplier(priv->planes[0].bits_per_pixel, &f1, &f2);
+ priv->d3d_depth_vector[0] = f1;
+ priv->d3d_depth_vector[1] = f2;
+ priv->d3d_depth_vector[2] = priv->d3d_depth_vector[3] = 0;
+ // no change
+ csp.input_bits = 8;
+ csp.texture_bits = 8;
+ }
+
+ mp_get_yuv2rgb_coeffs(&csp, coeff);
+ for (int row = 0; row < 3; row++) {
+ for (int col = 0; col < 4; col++) {
+ priv->d3d_colormatrix.m[row][col] = coeff[row][col];
+ }
+ }
+ }
+}
+
+const char *options_help_text = "-vo direct3d command line help:\n"
+"Example: -vo direct3d:disable-osd:disable-textures\n"
+"Options:\n"
+" prefer-stretchrect\n"
+" Use IDirect3DDevice9::StretchRect over other methods if possible.\n"
+" disable-stretchrect\n"
+" Never render the video using IDirect3DDevice9::StretchRect.\n"
+" disable-textures\n"
+" Never render the video using D3D texture rendering. (Rendering with\n"
+" textures + shader will still be allowed. Add disable-shaders to\n"
+" completely disable video rendering with textures.)\n"
+" disable-shaders\n"
+" Never use shaders when rendering video.\n"
+" only-8bit\n"
+" Never render YUV video with more than 8 bits per component.\n"
+" (Using this flag will force software conversion to 8 bit.)\n"
+" disable-osd\n"
+" Disable OSD rendering.\n"
+" (Using this flag might force the insertion of the 'ass' video filter,\n"
+" which will render the subtitles in software.)\n"
+" disable-texture-align\n"
+" Normally texture sizes are always aligned to 16. With this option\n"
+" enabled, the video texture will always have exactly the same size as\n"
+" the video itself.\n"
+"Debug options. These might be incorrect, might be removed in the future, might\n"
+"crash, might cause slow downs, etc. Contact the developers if you actually need\n"
+"any of these for performance or proper operation.\n"
+" force-power-of-2\n"
+" Always force textures to power of 2, even if the device reports\n"
+" non-power-of-2 texture sizes as supported.\n"
+" texture-memory=N\n"
+" Only affects operation with shaders/texturing enabled, and (E)OSD.\n"
+" Values for N:\n"
+" 0 default, will often use an additional shadow texture + copy\n"
+" 1 use D3DPOOL_MANAGED\n"
+" 2 use D3DPOOL_DEFAULT\n"
+" 3 use D3DPOOL_SYSTEMMEM, but without shadow texture\n"
+" swap-discard\n"
+" Use D3DSWAPEFFECT_DISCARD, which might be faster.\n"
+" Might be slower too, as it must (?) clear every frame.\n"
+" exact-backbuffer\n"
+" Always resize the backbuffer to window size.\n"
+" no16bit-textures\n"
+" Don't use textures with a 16 bit color channel for YUV formats that\n"
+" use more than 8 bits per component. Instead, use D3DFMT_A8L8 textures\n"
+" and compute the values sampled from the 2 channels back into one.\n"
+" Might be slower, since the shader becomes slightly more complicated.\n"
+" Might work better, if your drivers either don't support D3DFMT_L16,\n"
+" or if either the texture unit or the shaders don't operate in at least\n"
+" 16 bit precision.\n"
+"";
+
+/** @brief libvo Callback: Preinitialize the video card.
+ * Preinit the hardware just enough to be queried about
+ * supported formats.
+ *
+ * @return 0 on success, -1 on failure
+ */
+
+static int preinit_internal(struct vo *vo, const char *arg, bool allow_shaders)
+{
+ d3d_priv *priv = talloc_zero(vo, d3d_priv);
+ vo->priv = priv;
+
+ *priv = (d3d_priv) {
+ .vo = vo,
+
+ .opt_16bit_textures = true,
+
+ .colorspace = MP_CSP_DETAILS_DEFAULTS,
+ .video_eq = { MP_CSP_EQ_CAPS_COLORMATRIX },
+ };
+
+ for (int n = 0; n < MAX_OSD_PARTS; n++) {
+ struct osdpart *osd = talloc_ptrtype(priv, osd);
+ *osd = (struct osdpart) {
+ .packer = talloc_zero(osd, struct bitmap_packer),
+ };
+ priv->osd[n] = osd;
+ }
+
+ if (!allow_shaders) {
+ priv->opt_disable_shaders = priv->opt_disable_textures = true;
+ }
+
+ const opt_t subopts[] = {
+ {"prefer-stretchrect", OPT_ARG_BOOL, &priv->opt_prefer_stretchrect},
+ {"disable-textures", OPT_ARG_BOOL, &priv->opt_disable_textures},
+ {"disable-stretchrect", OPT_ARG_BOOL, &priv->opt_disable_stretchrect},
+ {"disable-shaders", OPT_ARG_BOOL, &priv->opt_disable_shaders},
+ {"only-8bit", OPT_ARG_BOOL, &priv->opt_only_8bit},
+ {"disable-osd", OPT_ARG_BOOL, &priv->opt_disable_osd},
+ {"force-power-of-2", OPT_ARG_BOOL, &priv->opt_force_power_of_2},
+ {"disable-texture-align", OPT_ARG_BOOL, &priv->opt_disable_texture_align},
+ {"texture-memory", OPT_ARG_INT, &priv->opt_texture_memory},
+ {"swap-discard", OPT_ARG_BOOL, &priv->opt_swap_discard},
+ {"exact-backbuffer", OPT_ARG_BOOL, &priv->opt_exact_backbuffer},
+ {"16bit-textures", OPT_ARG_BOOL, &priv->opt_16bit_textures},
+ {NULL}
+ };
+ if (subopt_parse(arg, subopts) != 0) {
+ mp_msg(MSGT_VO, MSGL_FATAL, options_help_text);
+ return -1;
+ }
+
+ priv->d3d9_dll = LoadLibraryA("d3d9.dll");
+ if (!priv->d3d9_dll) {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "<vo_direct3d>Unable to dynamically load d3d9.dll\n");
+ goto err_out;
+ }
+
+ priv->pDirect3DCreate9 = (void *)GetProcAddress(priv->d3d9_dll,
+ "Direct3DCreate9");
+ if (!priv->pDirect3DCreate9) {
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "<vo_direct3d>Unable to find entry point of Direct3DCreate9\n");
+ goto err_out;
+ }
+
+ if (!init_d3d(priv))
+ goto err_out;
+
+ /* w32_common framework call. Configures window on the screen, gets
+ * fullscreen dimensions and does other useful stuff.
+ */
+ if (!vo_w32_init(vo)) {
+ mp_msg(MSGT_VO, MSGL_V,
+ "<vo_direct3d>Configuring onscreen window failed.\n");
+ goto err_out;
+ }
+
+ return 0;
+
+err_out:
+ uninit(vo);
+ return -1;
+}
+
+static int preinit_standard(struct vo *vo, const char *arg)
+{
+ return preinit_internal(vo, arg, false);
+}
+
+static int preinit_shaders(struct vo *vo, const char *arg)
+{
+ return preinit_internal(vo, arg, true);
+}
+
+/** @brief libvo Callback: Handle control requests.
+ * @return VO_TRUE on success, VO_NOTIMPL when not implemented
+ */
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ d3d_priv *priv = vo->priv;
+
+ switch (request) {
+ case VOCTRL_QUERY_FORMAT:
+ return query_format(priv, *(uint32_t*) data);
+ case VOCTRL_DRAW_IMAGE:
+ return d3d_upload_and_render_frame(priv, data);
+ case VOCTRL_FULLSCREEN:
+ vo_w32_fullscreen(vo);
+ resize_d3d(priv);
+ return VO_TRUE;
+ case VOCTRL_RESET:
+ return VO_NOTIMPL;
+ case VOCTRL_REDRAW_FRAME:
+ priv->is_clear_needed = 1;
+ d3d_draw_frame(priv);
+ return VO_TRUE;
+ case VOCTRL_SET_YUV_COLORSPACE:
+ priv->colorspace = *(struct mp_csp_details *)data;
+ update_colorspace(priv);
+ vo->want_redraw = true;
+ return VO_TRUE;
+ case VOCTRL_GET_YUV_COLORSPACE:
+ if (!priv->use_shaders)
+ break; // no idea what the heck D3D YUV uses
+ *(struct mp_csp_details *)data = priv->colorspace;
+ return VO_TRUE;
+ case VOCTRL_SET_EQUALIZER: {
+ if (!priv->use_shaders)
+ break;
+ struct voctrl_set_equalizer_args *args = data;
+ if (mp_csp_equalizer_set(&priv->video_eq, args->name, args->value) < 0)
+ return VO_NOTIMPL;
+ update_colorspace(priv);
+ vo->want_redraw = true;
+ return VO_TRUE;
+ }
+ case VOCTRL_GET_EQUALIZER: {
+ if (!priv->use_shaders)
+ break;
+ struct voctrl_get_equalizer_args *args = data;
+ return mp_csp_equalizer_get(&priv->video_eq, args->name, args->valueptr)
+ >= 0 ? VO_TRUE : VO_NOTIMPL;
+ }
+ case VOCTRL_ONTOP:
+ vo_w32_ontop(vo);
+ return VO_TRUE;
+ case VOCTRL_BORDER:
+ vo_w32_border(vo);
+ return VO_TRUE;
+ case VOCTRL_UPDATE_SCREENINFO:
+ w32_update_xinerama_info(vo);
+ return VO_TRUE;
+ case VOCTRL_SET_PANSCAN:
+ calc_fs_rect(priv);
+ return VO_TRUE;
+ case VOCTRL_GET_PANSCAN:
+ return VO_TRUE;
+ case VOCTRL_SCREENSHOT: {
+ struct voctrl_screenshot_args *args = data;
+ if (args->full_window)
+ args->out_image = get_window_screenshot(priv);
+ else
+ args->out_image = get_screenshot(priv);
+ return !!args->out_image;
+ }
+ }
+ return VO_NOTIMPL;
+}
+
+/** @brief libvo Callback: Configre the Direct3D adapter.
+ * @param width Movie source width
+ * @param height Movie source height
+ * @param d_width Screen (destination) width
+ * @param d_height Screen (destination) height
+ * @param options Options bitmap
+ * @param format Movie colorspace format (using MPlayer's
+ * defines, e.g. IMGFMT_YUY2)
+ * @return 0 on success, VO_ERROR on failure
+ */
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t options,
+ uint32_t format)
+{
+ d3d_priv *priv = vo->priv;
+
+ /* w32_common framework call. Creates window on the screen with
+ * the given coordinates.
+ */
+ if (!vo_w32_config(vo, d_width, d_height, options)) {
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Creating window failed.\n");
+ return VO_ERROR;
+ }
+
+ if ((priv->image_format != format)
+ || (priv->src_width != width)
+ || (priv->src_height != height))
+ {
+ d3d_destroy_video_objects(priv);
+
+ priv->src_width = width;
+ priv->src_height = height;
+ init_rendering_mode(priv, format, true);
+ }
+
+ if (!resize_d3d(priv))
+ return VO_ERROR;
+
+ return 0; /* Success */
+}
+
+/** @brief libvo Callback: Flip next already drawn frame on the
+ * screen.
+ */
+static void flip_page(struct vo *vo)
+{
+ d3d_priv *priv = vo->priv;
+
+ if (priv->d3d_device && priv->d3d_in_scene) {
+ if (FAILED(IDirect3DDevice9_EndScene(priv->d3d_device))) {
+ mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>EndScene failed.\n");
+ }
+ }
+ priv->d3d_in_scene = false;
+
+ RECT rect = {0, 0, vo->dwidth, vo->dheight};
+ if (!priv->d3d_device ||
+ FAILED(IDirect3DDevice9_Present(priv->d3d_device, &rect, 0, 0, 0))) {
+ mp_msg(MSGT_VO, MSGL_V,
+ "<vo_direct3d>Trying to reinitialize uncooperative video adapter.\n");
+ if (!reconfigure_d3d(priv)) {
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Reinitialization failed.\n");
+ return;
+ } else {
+ mp_msg(MSGT_VO, MSGL_V,
+ "<vo_direct3d>Video adapter reinitialized.\n");
+ }
+ }
+}
+
+/** @brief libvo Callback: Uninitializes all pointers and closes
+ * all D3D related stuff,
+ */
+static void uninit(struct vo *vo)
+{
+ d3d_priv *priv = vo->priv;
+
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>uninit called.\n");
+
+ uninit_d3d(priv);
+ vo_w32_uninit(vo);
+ if (priv->d3d9_dll)
+ FreeLibrary(priv->d3d9_dll);
+ priv->d3d9_dll = NULL;
+}
+
+/** @brief libvo Callback: Handles video window events.
+ */
+static void check_events(struct vo *vo)
+{
+ d3d_priv *priv = vo->priv;
+
+ int flags = vo_w32_check_events(vo);
+ if (flags & VO_EVENT_RESIZE)
+ resize_d3d(priv);
+
+ if (flags & VO_EVENT_EXPOSE)
+ vo->want_redraw = true;
+}
+
+static int draw_slice_textures(d3d_priv *priv, uint8_t *src[], int stride[],
+ int w, int h, int x, int y)
+{
+ if (!d3d_lock_video_textures(priv))
+ return VO_FALSE;
+
+ for (int n = 0; n < priv->plane_count; n++) {
+ struct texplane *plane = &priv->planes[n];
+
+ int dst_stride = plane->locked_rect.Pitch;
+ uint8_t *pdst = (uint8_t*)plane->locked_rect.pBits
+ + (y >> plane->shift_y) * dst_stride
+ + (x >> plane->shift_x) * plane->bytes_per_pixel;
+
+ memcpy_pic(pdst, src[n], (w >> plane->shift_x) * plane->bytes_per_pixel,
+ h >> plane->shift_y, dst_stride, stride[n]);
+ }
+
+ return 0;
+}
+
+/** @brief libvo Callback: Draw slice
+ * @return 0 on success
+ */
+static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h,
+ int x, int y)
+{
+ d3d_priv *priv = vo->priv;
+
+ if (!priv->d3d_device)
+ return 0;
+
+ char *my_src; /**< Pointer to the source image */
+ char *dst; /**< Pointer to the destination image */
+ int uv_stride; /**< Stride of the U/V planes */
+
+ if (priv->use_textures)
+ return draw_slice_textures(priv, src, stride, w, h, x, y);
+
+ /* Lock the offscreen surface if it's not already locked. */
+ if (!priv->locked_rect.pBits) {
+ if (FAILED(IDirect3DSurface9_LockRect(priv->d3d_surface,
+ &priv->locked_rect, NULL, 0))) {
+ mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Surface lock failure.\n");
+ return VO_FALSE;
+ }
+ }
+
+ uv_stride = priv->locked_rect.Pitch / 2;
+
+ /* Copy Y */
+ dst = priv->locked_rect.pBits;
+ dst = dst + priv->locked_rect.Pitch * y + x;
+ my_src = src[0];
+ memcpy_pic(dst, my_src, w, h, priv->locked_rect.Pitch, stride[0]);
+
+ w /= 2;
+ h /= 2;
+ x /= 2;
+ y /= 2;
+
+ /* Copy U */
+ dst = priv->locked_rect.pBits;
+ dst = dst + priv->locked_rect.Pitch * priv->src_height
+ + uv_stride * y + x;
+ if (priv->movie_src_fmt == MAKEFOURCC('Y','V','1','2'))
+ my_src = src[2];
+ else
+ my_src = src[1];
+
+ memcpy_pic(dst, my_src, w, h, uv_stride, stride[1]);
+
+ /* Copy V */
+ dst = priv->locked_rect.pBits;
+ dst = dst + priv->locked_rect.Pitch * priv->src_height
+ + uv_stride * (priv->src_height / 2) + uv_stride * y + x;
+ if (priv->movie_src_fmt == MAKEFOURCC('Y','V','1','2'))
+ my_src=src[1];
+ else
+ my_src=src[2];
+
+ memcpy_pic(dst, my_src, w, h, uv_stride, stride[2]);
+
+ return 0; /* Success */
+}
+
+static bool get_screenshot_from_surface(d3d_priv *priv, mp_image_t *image)
+{
+ if (!priv->locked_rect.pBits) {
+ if (FAILED(IDirect3DSurface9_LockRect(priv->d3d_surface,
+ &priv->locked_rect, NULL, 0))) {
+ mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Surface lock failed.\n");
+ return false;
+ }
+ }
+
+ if (image->flags & MP_IMGFLAG_PLANAR) {
+ char *src;
+ int w = priv->src_width;
+ int h = priv->src_height;
+ int swapped = priv->movie_src_fmt == MAKEFOURCC('Y','V','1','2');
+ int plane1 = swapped ? 2 : 1;
+ int plane2 = swapped ? 1 : 2;
+
+ int uv_stride = priv->locked_rect.Pitch / 2;
+
+ /* Copy Y */
+ src = priv->locked_rect.pBits;
+ memcpy_pic(image->planes[0], src, w, h, priv->locked_rect.Pitch,
+ image->stride[0]);
+
+ w /= 2;
+ h /= 2;
+
+ /* Copy U */
+ src = priv->locked_rect.pBits;
+ src = src + priv->locked_rect.Pitch * priv->src_height;
+
+ memcpy_pic(image->planes[plane1], src, w, h, uv_stride,
+ image->stride[1]);
+
+ /* Copy V */
+ src = priv->locked_rect.pBits;
+ src = src + priv->locked_rect.Pitch * priv->src_height
+ + uv_stride * (priv->src_height / 2);
+
+ memcpy_pic(image->planes[plane2], src, w, h, uv_stride,
+ image->stride[2]);
+ } else {
+ // packed YUV or RGB
+ memcpy_pic(image->planes[0], priv->locked_rect.pBits, image->stride[0],
+ image->height, priv->locked_rect.Pitch, image->stride[0]);
+ }
+
+ d3d_unlock_video_objects(priv);
+ return true;
+}
+
+static bool get_screenshot_from_texture(d3d_priv *priv, mp_image_t *image)
+{
+ if (!d3d_lock_video_textures(priv)) {
+ d3d_unlock_video_objects(priv);
+ return false;
+ }
+
+ assert(image->num_planes == priv->plane_count);
+
+ for (int n = 0; n < priv->plane_count; n++) {
+ struct texplane *plane = &priv->planes[n];
+
+ int width = priv->src_width >> plane->shift_x;
+ int height = priv->src_height >> plane->shift_y;
+
+ memcpy_pic(image->planes[n], plane->locked_rect.pBits,
+ width * plane->bytes_per_pixel, height,
+ image->stride[n], plane->locked_rect.Pitch);
+ }
+
+ return true;
+}
+
+static mp_image_t *get_screenshot(d3d_priv *priv)
+{
+ if (!priv->d3d_device)
+ return NULL;
+
+ mp_image_t *image = alloc_mpi(priv->src_width, priv->src_height,
+ priv->image_format);
+
+ bool res;
+
+ if (priv->use_textures)
+ res = get_screenshot_from_texture(priv, image);
+ else
+ res = get_screenshot_from_surface(priv, image);
+
+ if (!res) {
+ free_mp_image(image);
+ return NULL;
+ }
+
+ image->display_w = priv->vo->aspdat.prew;
+ image->display_h = priv->vo->aspdat.preh;
+
+ mp_image_set_colorspace_details(image, &priv->colorspace);
+
+ return image;
+}
+
+static mp_image_t *get_window_screenshot(d3d_priv *priv)
+{
+ D3DDISPLAYMODE mode;
+ mp_image_t *image = NULL;
+ RECT window_rc;
+ RECT screen_rc;
+ RECT visible;
+ POINT pt;
+ D3DLOCKED_RECT locked_rect;
+ int width, height;
+
+ if (FAILED(IDirect3DDevice9_GetDisplayMode(priv->d3d_device, 0, &mode))) {
+ mp_msg(MSGT_VO,MSGL_ERR, "<vo_direct3d>GetDisplayMode failed.\n");
+ goto error_exit;
+ }
+
+ IDirect3DSurface9 *surface = NULL;
+ if (FAILED(IDirect3DDevice9_CreateOffscreenPlainSurface(priv->d3d_device,
+ mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface,
+ NULL)))
+ {
+ mp_msg(MSGT_VO,MSGL_ERR, "<vo_direct3d>Couldn't create surface.\n");
+ goto error_exit;
+ }
+
+ if (FAILED(IDirect3DDevice9_GetFrontBufferData(priv->d3d_device, 0,
+ surface)))
+ {
+ mp_msg(MSGT_VO,MSGL_ERR, "<vo_direct3d>Couldn't copy frontbuffer.\n");
+ goto error_exit;
+ }
+
+ GetClientRect(priv->vo->w32->window, &window_rc);
+ pt = (POINT) { 0, 0 };
+ ClientToScreen(priv->vo->w32->window, &pt);
+ window_rc.left = pt.x;
+ window_rc.top = pt.y;
+ window_rc.right += window_rc.left;
+ window_rc.bottom += window_rc.top;
+
+ screen_rc = (RECT) { 0, 0, mode.Width, mode.Height };
+
+ if (!IntersectRect(&visible, &screen_rc, &window_rc))
+ goto error_exit;
+ width = visible.right - visible.left;
+ height = visible.bottom - visible.top;
+ if (width < 1 || height < 1)
+ goto error_exit;
+
+ image = alloc_mpi(width, height, IMGFMT_BGR32);
+
+ IDirect3DSurface9_LockRect(surface, &locked_rect, NULL, 0);
+
+ memcpy_pic(image->planes[0], (char*)locked_rect.pBits + visible.top *
+ locked_rect.Pitch + visible.left * 4, width * 4, height,
+ image->stride[0], locked_rect.Pitch);
+
+ IDirect3DSurface9_UnlockRect(surface);
+ IDirect3DSurface9_Release(surface);
+
+ return image;
+
+error_exit:
+ if (image)
+ free_mp_image(image);
+ if (surface)
+ IDirect3DSurface9_Release(surface);
+ return NULL;
+}
+
+static bool upload_osd(d3d_priv *priv, struct osdpart *osd,
+ struct sub_bitmaps *imgs)
+{
+ D3DFORMAT fmt = osd_fmt_table[imgs->format];
+
+ osd->packer->w_max = priv->max_texture_width;
+ osd->packer->h_max = priv->max_texture_height;
+
+ osd->packer->padding = imgs->scaled; // assume 2x2 filter on scaling
+ int r = packer_pack_from_subbitmaps(osd->packer, imgs);
+ if (r < 0) {
+ mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>OSD bitmaps do not fit on "
+ "a surface with the maximum supported size %dx%d.\n",
+ osd->packer->w_max, osd->packer->h_max);
+ return false;
+ }
+
+ if (osd->packer->w > osd->texture.tex_w
+ || osd->packer->h > osd->texture.tex_h
+ || osd->format != imgs->format)
+ {
+ osd->format = imgs->format;
+
+ int new_w = osd->packer->w;
+ int new_h = osd->packer->h;
+ d3d_fix_texture_size(priv, &new_w, &new_h);
+
+ mp_msg(MSGT_VO, MSGL_DBG2, "<vo_direct3d>reallocate OSD surface.\n");
+
+ d3dtex_release(priv, &osd->texture);
+ d3dtex_allocate(priv, &osd->texture, fmt, new_w, new_h);
+
+ if (!osd->texture.system)
+ return false; // failed to allocate
+ }
+
+ struct pos bb[2];
+ packer_get_bb(osd->packer, bb);
+ RECT dirty_rc = { bb[0].x, bb[0].y, bb[1].x, bb[1].y };
+
+ D3DLOCKED_RECT locked_rect;
+
+ if (FAILED(IDirect3DTexture9_LockRect(osd->texture.system, 0, &locked_rect,
+ &dirty_rc, 0)))
+ {
+ mp_msg(MSGT_VO,MSGL_ERR, "<vo_direct3d>OSD texture lock failed.\n");
+ return false;
+ }
+
+ int ps = fmt == D3DFMT_A8 ? 1 : 4;
+ packer_copy_subbitmaps(osd->packer, imgs, locked_rect.pBits, ps,
+ locked_rect.Pitch);
+
+ if (FAILED(IDirect3DTexture9_UnlockRect(osd->texture.system, 0))) {
+ mp_msg(MSGT_VO,MSGL_ERR, "<vo_direct3d>OSD texture unlock failed.\n");
+ return false;
+ }
+
+ return d3dtex_update(priv, &osd->texture);
+}
+
+static struct osdpart *generate_osd(d3d_priv *priv, struct sub_bitmaps *imgs)
+{
+ if (imgs->num_parts == 0 || !osd_fmt_table[imgs->format])
+ return NULL;
+
+ struct osdpart *osd = priv->osd[imgs->render_index];
+
+ if (imgs->bitmap_pos_id != osd->bitmap_pos_id) {
+ if (imgs->bitmap_id != osd->bitmap_id) {
+ if (!upload_osd(priv, osd, imgs))
+ osd->packer->count = 0;
+ }
+
+ osd->bitmap_id = imgs->bitmap_id;
+ osd->bitmap_pos_id = imgs->bitmap_pos_id;
+ osd->num_vertices = 0;
+ }
+
+ return osd->packer->count ? osd : NULL;
+}
+
+static D3DCOLOR ass_to_d3d_color(uint32_t color)
+{
+ uint32_t r = (color >> 24) & 0xff;
+ uint32_t g = (color >> 16) & 0xff;
+ uint32_t b = (color >> 8) & 0xff;
+ uint32_t a = 0xff - (color & 0xff);
+ return D3DCOLOR_ARGB(a, r, g, b);
+}
+
+static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs)
+{
+ d3d_priv *priv = ctx;
+
+ struct osdpart *osd = generate_osd(priv, imgs);
+ if (!osd)
+ return;
+
+ if (osd->packer->count && !osd->num_vertices) {
+ // We need 2 primitives per quad which makes 6 vertices (we could reduce
+ // the number of vertices by using an indexed vertex array, but it's
+ // probably not worth doing)
+ osd->num_vertices = osd->packer->count * 6;
+ osd->vertices = talloc_realloc(osd, osd->vertices, vertex_osd,
+ osd->num_vertices);
+
+ float tex_w = osd->texture.tex_w;
+ float tex_h = osd->texture.tex_h;
+
+ for (int n = 0; n < osd->packer->count; n++) {
+ struct sub_bitmap *b = &imgs->parts[n];
+ struct pos p = osd->packer->result[n];
+
+ D3DCOLOR color = imgs->format == SUBBITMAP_LIBASS
+ ? ass_to_d3d_color(b->libass.color)
+ : D3DCOLOR_ARGB(255, 255, 255, 255);
+
+ float x0 = b->x;
+ float y0 = b->y;
+ float x1 = b->x + b->dw;
+ float y1 = b->y + b->dh;
+ float tx0 = p.x / tex_w;
+ float ty0 = p.y / tex_h;
+ float tx1 = (p.x + b->w) / tex_w;
+ float ty1 = (p.y + b->h) / tex_h;
+
+ vertex_osd *v = &osd->vertices[n * 6];
+ v[0] = (vertex_osd) { x0, y0, 0, color, tx0, ty0 };
+ v[1] = (vertex_osd) { x1, y0, 0, color, tx1, ty0 };
+ v[2] = (vertex_osd) { x0, y1, 0, color, tx0, ty1 };
+ v[3] = (vertex_osd) { x1, y1, 0, color, tx1, ty1 };
+ v[4] = v[2];
+ v[5] = v[1];
+ }
+ }
+
+ d3d_begin_scene(priv);
+
+ IDirect3DDevice9_SetRenderState(priv->d3d_device,
+ D3DRS_ALPHABLENDENABLE, TRUE);
+
+ IDirect3DDevice9_SetTexture(priv->d3d_device, 0,
+ d3dtex_get_render_texture(priv, &osd->texture));
+
+ if (imgs->format == SUBBITMAP_LIBASS) {
+ // do not use the color value from the A8 texture, because that is black
+ IDirect3DDevice9_SetRenderState(priv->d3d_device,D3DRS_TEXTUREFACTOR,
+ 0xFFFFFFFF);
+ IDirect3DDevice9_SetTextureStageState(priv->d3d_device,0,
+ D3DTSS_COLORARG1, D3DTA_TFACTOR);
+
+ IDirect3DDevice9_SetTextureStageState(priv->d3d_device, 0,
+ D3DTSS_ALPHAOP, D3DTOP_MODULATE);
+ } else {
+ IDirect3DDevice9_SetRenderState(priv->d3d_device, D3DRS_SRCBLEND,
+ D3DBLEND_ONE);
+ }
+
+ IDirect3DDevice9_SetFVF(priv->d3d_device, D3DFVF_OSD_VERTEX);
+ IDirect3DDevice9_DrawPrimitiveUP(priv->d3d_device, D3DPT_TRIANGLELIST,
+ osd->num_vertices / 3,
+ osd->vertices, sizeof(vertex_osd));
+
+ IDirect3DDevice9_SetTextureStageState(priv->d3d_device,0,
+ D3DTSS_COLORARG1, D3DTA_TEXTURE);
+ IDirect3DDevice9_SetTextureStageState(priv->d3d_device, 0,
+ D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
+ IDirect3DDevice9_SetRenderState(priv->d3d_device,
+ D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
+
+ IDirect3DDevice9_SetTexture(priv->d3d_device, 0, NULL);
+
+ IDirect3DDevice9_SetRenderState(priv->d3d_device,
+ D3DRS_ALPHABLENDENABLE, FALSE);
+}
+
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ d3d_priv *priv = vo->priv;
+ if (!priv->d3d_device)
+ return;
+
+ osd_draw(osd, priv->osd_res, osd->vo_pts, 0, osd_fmt_supported,
+ draw_osd_cb, priv);
+}
+
+#define AUTHOR "Georgi Petrov (gogothebee) <gogothebee@gmail.com> and others"
+
+const struct vo_driver video_out_direct3d = {
+ .is_new = true,
+ .info = &(const vo_info_t) {
+ "Direct3D 9 Renderer",
+ "direct3d",
+ AUTHOR,
+ ""
+ },
+ .preinit = preinit_standard,
+ .config = config,
+ .control = control,
+ .draw_slice = draw_slice,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+ .check_events = check_events,
+ .uninit = uninit,
+};
+
+const struct vo_driver video_out_direct3d_shaders = {
+ .is_new = true,
+ .info = &(const vo_info_t) {
+ "Direct3D 9 Renderer (using shaders for YUV conversion)",
+ "direct3d_shaders",
+ AUTHOR,
+ ""
+ },
+ .preinit = preinit_shaders,
+ .config = config,
+ .control = control,
+ .draw_slice = draw_slice,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+ .check_events = check_events,
+ .uninit = uninit,
+};
diff --git a/video/out/vo_image.c b/video/out/vo_image.c
new file mode 100644
index 0000000000..7f80a16f35
--- /dev/null
+++ b/video/out/vo_image.c
@@ -0,0 +1,198 @@
+/*
+ * This file is part of mplayer.
+ *
+ * mplayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mplayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mplayer. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+
+#include <libswscale/swscale.h>
+
+#include "config.h"
+#include "bstr.h"
+#include "osdep/io.h"
+#include "path.h"
+#include "talloc.h"
+#include "mp_msg.h"
+#include "libvo/video_out.h"
+#include "libvo/csputils.h"
+#include "libmpcodecs/vfcap.h"
+#include "libmpcodecs/mp_image.h"
+#include "fmt-conversion.h"
+#include "image_writer.h"
+#include "m_config.h"
+#include "m_option.h"
+
+struct priv {
+ struct image_writer_opts *opts;
+ char *outdir;
+
+ int frame;
+
+ uint32_t d_width;
+ uint32_t d_height;
+
+ struct mp_csp_details colorspace;
+};
+
+static bool checked_mkdir(const char *buf)
+{
+ mp_msg(MSGT_VO, MSGL_INFO, "[vo_image] Creating output directory '%s'...\n",
+ buf);
+ if (mkdir(buf, 0755) < 0) {
+ char *errstr = strerror(errno);
+ if (errno == EEXIST) {
+ struct stat stat_p;
+ if (mp_stat(buf, &stat_p ) == 0 && S_ISDIR(stat_p.st_mode))
+ return true;
+ }
+ mp_msg(MSGT_VO, MSGL_ERR, "[vo_image] Error creating output directory"
+ ": %s\n", errstr);
+ return false;
+ }
+ return true;
+}
+
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ struct priv *p = vo->priv;
+
+ p->d_width = d_width;
+ p->d_height = d_height;
+
+ if (p->outdir && vo->config_count < 1)
+ if (!checked_mkdir(p->outdir))
+ return -1;
+
+ return 0;
+}
+
+static void check_events(struct vo *vo)
+{
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+}
+
+static void flip_page(struct vo *vo)
+{
+}
+
+static uint32_t draw_image(struct vo *vo, mp_image_t *mpi)
+{
+ struct priv *p = vo->priv;
+
+ mp_image_t img = *mpi;
+ img.width = p->d_width;
+ img.height = p->d_height;
+ mp_image_set_colorspace_details(&img, &p->colorspace);
+
+ void *t = talloc_new(NULL);
+ char *filename = talloc_asprintf(t, "%08d.%s", p->frame,
+ image_writer_file_ext(p->opts));
+
+ if (p->outdir && strlen(p->outdir))
+ filename = mp_path_join(t, bstr0(p->outdir), bstr0(filename));
+
+ mp_msg(MSGT_VO, MSGL_STATUS, "\nSaving %s\n", filename);
+ write_image(&img, p->opts, filename);
+
+ talloc_free(t);
+
+ (p->frame)++;
+
+ return VO_TRUE;
+}
+
+static int query_format(struct vo *vo, uint32_t fmt)
+{
+ enum PixelFormat av_format = imgfmt2pixfmt(fmt);
+
+ // NOTE: accept everything that can be converted by swscale. screenshot.c
+ // always wants RGB (at least for now), but it probably doesn't matter
+ // whether we or screenshot.c do the conversion.
+ if (av_format != PIX_FMT_NONE && sws_isSupportedInput(av_format))
+ return VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW |
+ VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN;
+ return 0;
+}
+
+static void uninit(struct vo *vo)
+{
+}
+
+static int preinit(struct vo *vo, const char *arg)
+{
+ return 0;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct priv *p = vo->priv;
+
+ switch (request) {
+ case VOCTRL_QUERY_FORMAT:
+ return query_format(vo, *(uint32_t *)data);
+ case VOCTRL_DRAW_IMAGE:
+ return draw_image(vo, data);
+ case VOCTRL_SET_YUV_COLORSPACE:
+ p->colorspace = *(struct mp_csp_details *)data;
+ return true;
+ case VOCTRL_GET_YUV_COLORSPACE:
+ *(struct mp_csp_details *)data = p->colorspace;
+ return true;
+ // prevent random frame stepping by frontend
+ case VOCTRL_REDRAW_FRAME:
+ return true;
+ }
+ return VO_NOTIMPL;
+}
+
+#undef OPT_BASE_STRUCT
+#define OPT_BASE_STRUCT struct priv
+
+const struct vo_driver video_out_image =
+{
+ .is_new = true,
+ .info = &(const vo_info_t) {
+ "Write video frames to image files",
+ "image",
+ "wm4",
+ ""
+ },
+ .priv_size = sizeof(struct priv),
+ .priv_defaults = &(const struct priv) {
+ .colorspace = MP_CSP_DETAILS_DEFAULTS,
+ },
+ .options = (const struct m_option[]) {
+ OPT_SUBSTRUCT(opts, image_writer_conf, M_OPT_MERGE),
+ OPT_STRING("outdir", outdir, 0),
+ {0},
+ },
+ .preinit = preinit,
+ .config = config,
+ .control = control,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+ .check_events = check_events,
+ .uninit = uninit,
+};
diff --git a/video/out/vo_lavc.c b/video/out/vo_lavc.c
new file mode 100644
index 0000000000..b86cd76509
--- /dev/null
+++ b/video/out/vo_lavc.c
@@ -0,0 +1,553 @@
+/*
+ * video encoding using libavformat
+ * Copyright (C) 2010 Nicolas George <george@nsup.org>
+ * Copyright (C) 2011 Rudolf Polzer <divVerent@xonotic.org>
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "mpcommon.h"
+#include "options.h"
+#include "fmt-conversion.h"
+#include "libmpcodecs/mp_image.h"
+#include "libmpcodecs/vfcap.h"
+#include "subopt-helper.h"
+#include "talloc.h"
+#include "video_out.h"
+
+#include "encode_lavc.h"
+
+#include "sub/sub.h"
+#include "sub/dec_sub.h"
+
+struct priv {
+ uint8_t *buffer;
+ size_t buffer_size;
+ AVStream *stream;
+ int have_first_packet;
+
+ int harddup;
+
+ double lastpts;
+ int64_t lastipts;
+ int64_t lastframeipts;
+ double expected_next_pts;
+ mp_image_t *lastimg;
+ int lastimg_wants_osd;
+ int lastdisplaycount;
+
+ AVRational worst_time_base;
+ int worst_time_base_is_stream;
+
+ struct mp_csp_details colorspace;
+};
+
+static int preinit(struct vo *vo, const char *arg)
+{
+ struct priv *vc;
+ if (!encode_lavc_available(vo->encode_lavc_ctx)) {
+ mp_msg(MSGT_ENCODE, MSGL_ERR,
+ "vo-lavc: the option -o (output file) must be specified\n");
+ return -1;
+ }
+ vo->priv = talloc_zero(vo, struct priv);
+ vc = vo->priv;
+ vc->harddup = vo->encode_lavc_ctx->options->harddup;
+ vc->colorspace = (struct mp_csp_details) MP_CSP_DETAILS_DEFAULTS;
+ return 0;
+}
+
+static void draw_image(struct vo *vo, mp_image_t *mpi, double pts);
+static void uninit(struct vo *vo)
+{
+ struct priv *vc = vo->priv;
+ if (!vc)
+ return;
+
+ if (vc->lastipts >= 0 && vc->stream)
+ draw_image(vo, NULL, MP_NOPTS_VALUE);
+
+ if (vc->lastimg) {
+ // palette hack
+ if (vc->lastimg->imgfmt == IMGFMT_RGB8
+ || vc->lastimg->imgfmt == IMGFMT_BGR8)
+ vc->lastimg->planes[1] = NULL;
+ free_mp_image(vc->lastimg);
+ vc->lastimg = NULL;
+ }
+
+ vo->priv = NULL;
+}
+
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ struct priv *vc = vo->priv;
+ enum PixelFormat pix_fmt = imgfmt2pixfmt(format);
+ AVRational display_aspect_ratio, image_aspect_ratio;
+ AVRational aspect;
+
+ if (!vc)
+ return -1;
+
+ display_aspect_ratio.num = d_width;
+ display_aspect_ratio.den = d_height;
+ image_aspect_ratio.num = width;
+ image_aspect_ratio.den = height;
+ aspect = av_div_q(display_aspect_ratio, image_aspect_ratio);
+
+ if (vc->stream) {
+ /* NOTE:
+ * in debug builds we get a "comparison between signed and unsigned"
+ * warning here. We choose to ignore that; just because ffmpeg currently
+ * uses a plain 'int' for these struct fields, it doesn't mean it always
+ * will */
+ if (width == vc->stream->codec->width &&
+ height == vc->stream->codec->height) {
+ if (aspect.num != vc->stream->codec->sample_aspect_ratio.num ||
+ aspect.den != vc->stream->codec->sample_aspect_ratio.den) {
+ /* aspect-only changes are not critical */
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: unsupported pixel aspect "
+ "ratio change from %d:%d to %d:%d\n",
+ vc->stream->codec->sample_aspect_ratio.num,
+ vc->stream->codec->sample_aspect_ratio.den,
+ aspect.num, aspect.den);
+ }
+ return 0;
+ }
+
+ /* FIXME Is it possible with raw video? */
+ mp_msg(MSGT_ENCODE, MSGL_ERR,
+ "vo-lavc: resolution changes not supported.\n");
+ goto error;
+ }
+
+ vc->lastipts = MP_NOPTS_VALUE;
+ vc->lastframeipts = MP_NOPTS_VALUE;
+
+ if (pix_fmt == PIX_FMT_NONE)
+ goto error; /* imgfmt2pixfmt already prints something */
+
+ vc->stream = encode_lavc_alloc_stream(vo->encode_lavc_ctx,
+ AVMEDIA_TYPE_VIDEO);
+ vc->stream->sample_aspect_ratio = vc->stream->codec->sample_aspect_ratio =
+ aspect;
+ vc->stream->codec->width = width;
+ vc->stream->codec->height = height;
+ vc->stream->codec->pix_fmt = pix_fmt;
+
+ encode_lavc_set_csp(vo->encode_lavc_ctx, vc->stream, vc->colorspace.format);
+ encode_lavc_set_csp_levels(vo->encode_lavc_ctx, vc->stream, vc->colorspace.levels_out);
+
+ if (encode_lavc_open_codec(vo->encode_lavc_ctx, vc->stream) < 0)
+ goto error;
+
+ vc->colorspace.format = encode_lavc_get_csp(vo->encode_lavc_ctx, vc->stream);
+ vc->colorspace.levels_out = encode_lavc_get_csp_levels(vo->encode_lavc_ctx, vc->stream);
+
+ vc->buffer_size = 6 * width * height + 200;
+ if (vc->buffer_size < FF_MIN_BUFFER_SIZE)
+ vc->buffer_size = FF_MIN_BUFFER_SIZE;
+ if (vc->buffer_size < sizeof(AVPicture))
+ vc->buffer_size = sizeof(AVPicture);
+
+ vc->buffer = talloc_size(vc, vc->buffer_size);
+
+ vc->lastimg = alloc_mpi(width, height, format);
+
+ // palette hack
+ if (vc->lastimg->imgfmt == IMGFMT_RGB8 ||
+ vc->lastimg->imgfmt == IMGFMT_BGR8)
+ vc->lastimg->planes[1] = talloc_zero_size(vc, 1024);
+
+ return 0;
+
+error:
+ uninit(vo);
+ return -1;
+}
+
+static int query_format(struct vo *vo, uint32_t format)
+{
+ enum PixelFormat pix_fmt = imgfmt2pixfmt(format);
+
+ if (!vo->encode_lavc_ctx)
+ return 0;
+
+ if (!encode_lavc_supports_pixfmt(vo->encode_lavc_ctx, pix_fmt))
+ return 0;
+
+ return
+ VFCAP_CSP_SUPPORTED |
+ // we can do it
+ VFCAP_CSP_SUPPORTED_BY_HW |
+ // we don't convert colorspaces here
+ VFCAP_OSD |
+ // we have OSD
+ VOCAP_NOSLICES;
+ // we don't use slices
+}
+
+static void write_packet(struct vo *vo, int size, AVPacket *packet)
+{
+ struct priv *vc = vo->priv;
+
+ if (size < 0) {
+ mp_msg(MSGT_ENCODE, MSGL_ERR, "vo-lavc: error encoding\n");
+ return;
+ }
+
+ if (size > 0) {
+ packet->stream_index = vc->stream->index;
+ if (packet->pts != AV_NOPTS_VALUE) {
+ packet->pts = av_rescale_q(packet->pts,
+ vc->stream->codec->time_base,
+ vc->stream->time_base);
+ } else {
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: codec did not provide pts\n");
+ packet->pts = av_rescale_q(vc->lastipts, vc->worst_time_base,
+ vc->stream->time_base);
+ }
+ if (packet->dts != AV_NOPTS_VALUE) {
+ packet->dts = av_rescale_q(packet->dts,
+ vc->stream->codec->time_base,
+ vc->stream->time_base);
+ }
+ if (packet->duration > 0) {
+ packet->duration = av_rescale_q(packet->duration,
+ vc->stream->codec->time_base,
+ vc->stream->time_base);
+ } else {
+ // HACK: libavformat calculates dts wrong if the initial packet
+ // duration is not set, but ONLY if the time base is "high" and if we
+ // have b-frames!
+ if (!packet->duration)
+ if (!vc->have_first_packet)
+ if (vc->stream->codec->has_b_frames
+ || vc->stream->codec->max_b_frames)
+ if (vc->stream->time_base.num * 1000LL <=
+ vc->stream->time_base.den)
+ packet->duration = FFMAX(1, av_rescale_q(1,
+ vc->stream->codec->time_base, vc->stream->time_base));
+ }
+
+ if (encode_lavc_write_frame(vo->encode_lavc_ctx, packet) < 0) {
+ mp_msg(MSGT_ENCODE, MSGL_ERR, "vo-lavc: error writing\n");
+ return;
+ }
+
+ vc->have_first_packet = 1;
+ }
+}
+
+static int encode_video(struct vo *vo, AVFrame *frame, AVPacket *packet)
+{
+ struct priv *vc = vo->priv;
+ if (encode_lavc_oformat_flags(vo->encode_lavc_ctx) & AVFMT_RAWPICTURE) {
+ if (!frame)
+ return 0;
+ memcpy(vc->buffer, frame, sizeof(AVPicture));
+ mp_msg(MSGT_ENCODE, MSGL_DBG2, "vo-lavc: got pts %f\n",
+ frame->pts * (double) vc->stream->codec->time_base.num /
+ (double) vc->stream->codec->time_base.den);
+ packet->size = sizeof(AVPicture);
+ return packet->size;
+ } else {
+ int got_packet = 0;
+ int status = avcodec_encode_video2(vc->stream->codec, packet,
+ frame, &got_packet);
+ int size = (status < 0) ? status : got_packet ? packet->size : 0;
+
+ if (frame)
+ mp_msg(MSGT_ENCODE, MSGL_DBG2, "vo-lavc: got pts %f; out size: %d\n",
+ frame->pts * (double) vc->stream->codec->time_base.num /
+ (double) vc->stream->codec->time_base.den, size);
+
+ encode_lavc_write_stats(vo->encode_lavc_ctx, vc->stream);
+ return size;
+ }
+}
+
+static void draw_image(struct vo *vo, mp_image_t *mpi, double pts)
+{
+ struct priv *vc = vo->priv;
+ struct encode_lavc_context *ectx = vo->encode_lavc_ctx;
+ int i, size;
+ AVFrame *frame;
+ AVCodecContext *avc;
+ int64_t frameipts;
+ double nextpts;
+
+ if (!vc)
+ return;
+ if (!encode_lavc_start(ectx)) {
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: NOTE: skipped initial video frame (probably because audio is not there yet)\n");
+ return;
+ }
+ if (pts == MP_NOPTS_VALUE) {
+ if (mpi)
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: frame without pts, please report; synthesizing pts instead\n");
+ pts = vc->expected_next_pts;
+ }
+
+ avc = vc->stream->codec;
+
+ if (vc->worst_time_base.den == 0) {
+ //if (avc->time_base.num / avc->time_base.den >= vc->stream->time_base.num / vc->stream->time_base.den)
+ if (avc->time_base.num * (double) vc->stream->time_base.den >=
+ vc->stream->time_base.num * (double) avc->time_base.den) {
+ mp_msg(MSGT_ENCODE, MSGL_V, "vo-lavc: NOTE: using codec time base "
+ "(%d/%d) for frame dropping; the stream base (%d/%d) is "
+ "not worse.\n", (int)avc->time_base.num,
+ (int)avc->time_base.den, (int)vc->stream->time_base.num,
+ (int)vc->stream->time_base.den);
+ vc->worst_time_base = avc->time_base;
+ vc->worst_time_base_is_stream = 0;
+ } else {
+ mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: NOTE: not using codec time "
+ "base (%d/%d) for frame dropping; the stream base (%d/%d) "
+ "is worse.\n", (int)avc->time_base.num,
+ (int)avc->time_base.den, (int)vc->stream->time_base.num,
+ (int)vc->stream->time_base.den);
+ vc->worst_time_base = vc->stream->time_base;
+ vc->worst_time_base_is_stream = 1;
+ }
+
+ // NOTE: we use the following "axiom" of av_rescale_q:
+ // if time base A is worse than time base B, then
+ // av_rescale_q(av_rescale_q(x, A, B), B, A) == x
+ // this can be proven as long as av_rescale_q rounds to nearest, which
+ // it currently does
+
+ // av_rescale_q(x, A, B) * B = "round x*A to nearest multiple of B"
+ // and:
+ // av_rescale_q(av_rescale_q(x, A, B), B, A) * A
+ // == "round av_rescale_q(x, A, B)*B to nearest multiple of A"
+ // == "round 'round x*A to nearest multiple of B' to nearest multiple of A"
+ //
+ // assume this fails. Then there is a value of x*A, for which the
+ // nearest multiple of B is outside the range [(x-0.5)*A, (x+0.5)*A[.
+ // Absurd, as this range MUST contain at least one multiple of B.
+ }
+
+ double timeunit = (double)vc->worst_time_base.num / vc->worst_time_base.den;
+
+ double outpts;
+ if (ectx->options->rawts)
+ outpts = pts;
+ else if (ectx->options->copyts) {
+ // fix the discontinuity pts offset
+ nextpts = pts;
+ if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) {
+ ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts;
+ }
+ else if (fabs(nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts) > 30) {
+ mp_msg(MSGT_ENCODE, MSGL_WARN,
+ "vo-lavc: detected an unexpected discontinuity (pts jumped by "
+ "%f seconds)\n",
+ nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts);
+ ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts;
+ }
+
+ outpts = pts + ectx->discontinuity_pts_offset;
+ }
+ else {
+ // adjust pts by knowledge of audio pts vs audio playback time
+ double duration = 0;
+ if (ectx->last_video_in_pts != MP_NOPTS_VALUE)
+ duration = pts - ectx->last_video_in_pts;
+ if (duration < 0)
+ duration = timeunit; // XXX warn about discontinuity?
+ outpts = vc->lastpts + duration;
+ if (ectx->audio_pts_offset != MP_NOPTS_VALUE) {
+ double adj = outpts - pts - ectx->audio_pts_offset;
+ adj = FFMIN(adj, duration * 0.1);
+ adj = FFMAX(adj, -duration * 0.1);
+ outpts -= adj;
+ }
+ }
+ vc->lastpts = outpts;
+ ectx->last_video_in_pts = pts;
+ frameipts = floor((outpts + encode_lavc_getoffset(ectx, vc->stream))
+ / timeunit + 0.5);
+
+ // calculate expected pts of next video frame
+ vc->expected_next_pts = pts + timeunit;
+
+ if (!ectx->options->rawts && ectx->options->copyts) {
+ // set next allowed output pts value
+ nextpts = vc->expected_next_pts + ectx->discontinuity_pts_offset;
+ if (nextpts > ectx->next_in_pts)
+ ectx->next_in_pts = nextpts;
+ }
+
+ // never-drop mode
+ if (ectx->options->neverdrop && frameipts <= vc->lastipts) {
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "vo-lavc: -oneverdrop increased pts by %d\n",
+ (int) (vc->lastipts - frameipts + 1));
+ frameipts = vc->lastipts + 1;
+ vc->lastpts = frameipts * timeunit - encode_lavc_getoffset(ectx, vc->stream);
+ }
+
+ if (vc->lastipts != MP_NOPTS_VALUE) {
+ frame = avcodec_alloc_frame();
+
+ // we have a valid image in lastimg
+ while (vc->lastipts < frameipts) {
+ int64_t thisduration = vc->harddup ? 1 : (frameipts - vc->lastipts);
+ AVPacket packet;
+
+ avcodec_get_frame_defaults(frame);
+
+ // this is a nop, unless the worst time base is the STREAM time base
+ frame->pts = av_rescale_q(vc->lastipts, vc->worst_time_base,
+ avc->time_base);
+
+ for (i = 0; i < 4; i++) {
+ frame->data[i] = vc->lastimg->planes[i];
+ frame->linesize[i] = vc->lastimg->stride[i];
+ }
+ frame->quality = avc->global_quality;
+
+ av_init_packet(&packet);
+ packet.data = vc->buffer;
+ packet.size = vc->buffer_size;
+ size = encode_video(vo, frame, &packet);
+ write_packet(vo, size, &packet);
+
+ vc->lastipts += thisduration;
+ ++vc->lastdisplaycount;
+ }
+
+ avcodec_free_frame(&frame);
+ }
+
+ if (!mpi) {
+ // finish encoding
+ do {
+ AVPacket packet;
+ av_init_packet(&packet);
+ packet.data = vc->buffer;
+ packet.size = vc->buffer_size;
+ size = encode_video(vo, NULL, &packet);
+ write_packet(vo, size, &packet);
+ } while (size > 0);
+ } else {
+ if (frameipts >= vc->lastframeipts) {
+ if (vc->lastframeipts != MP_NOPTS_VALUE && vc->lastdisplaycount != 1)
+ mp_msg(MSGT_ENCODE, MSGL_INFO,
+ "vo-lavc: Frame at pts %d got displayed %d times\n",
+ (int) vc->lastframeipts, vc->lastdisplaycount);
+ copy_mpi(vc->lastimg, mpi);
+ vc->lastimg_wants_osd = true;
+
+ // palette hack
+ if (vc->lastimg->imgfmt == IMGFMT_RGB8 ||
+ vc->lastimg->imgfmt == IMGFMT_BGR8)
+ memcpy(vc->lastimg->planes[1], mpi->planes[1], 1024);
+
+ vc->lastframeipts = vc->lastipts = frameipts;
+ if (ectx->options->rawts && vc->lastipts < 0) {
+ mp_msg(MSGT_ENCODE, MSGL_ERR, "vo-lavc: why does this happen? DEBUG THIS! vc->lastipts = %lld\n", (long long) vc->lastipts);
+ vc->lastipts = -1;
+ }
+ vc->lastdisplaycount = 0;
+ } else {
+ mp_msg(MSGT_ENCODE, MSGL_INFO, "vo-lavc: Frame at pts %d got dropped "
+ "entirely because pts went backwards\n", (int) frameipts);
+ vc->lastimg_wants_osd = false;
+ }
+ }
+}
+
+static void flip_page_timed(struct vo *vo, unsigned int pts_us, int duration)
+{
+}
+
+static void check_events(struct vo *vo)
+{
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ struct priv *vc = vo->priv;
+
+ if (vc->lastimg && vc->lastimg_wants_osd) {
+ struct aspect_data asp = vo->aspdat;
+ double sar = (double)asp.orgw / asp.orgh;
+ double dar = (double)asp.prew / asp.preh;
+
+ struct mp_osd_res dim = {
+ .w = asp.orgw,
+ .h = asp.orgh,
+ .display_par = sar / dar,
+ .video_par = dar / sar,
+ };
+
+ mp_image_set_colorspace_details(vc->lastimg, &vc->colorspace);
+
+ osd_draw_on_image(osd, dim, osd->vo_pts, OSD_DRAW_SUB_ONLY, vc->lastimg);
+ }
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct priv *vc = vo->priv;
+ switch (request) {
+ case VOCTRL_QUERY_FORMAT:
+ return query_format(vo, *((uint32_t *)data));
+ case VOCTRL_DRAW_IMAGE:
+ draw_image(vo, (mp_image_t *)data, vo->next_pts);
+ return 0;
+ case VOCTRL_SET_YUV_COLORSPACE:
+ vc->colorspace = *(struct mp_csp_details *)data;
+ if (vc->stream) {
+ encode_lavc_set_csp(vo->encode_lavc_ctx, vc->stream, vc->colorspace.format);
+ encode_lavc_set_csp_levels(vo->encode_lavc_ctx, vc->stream, vc->colorspace.levels_out);
+ vc->colorspace.format = encode_lavc_get_csp(vo->encode_lavc_ctx, vc->stream);
+ vc->colorspace.levels_out = encode_lavc_get_csp_levels(vo->encode_lavc_ctx, vc->stream);
+ }
+ return 1;
+ case VOCTRL_GET_YUV_COLORSPACE:
+ *(struct mp_csp_details *)data = vc->colorspace;
+ return 1;
+ }
+ return VO_NOTIMPL;
+}
+
+const struct vo_driver video_out_lavc = {
+ .is_new = true,
+ .buffer_frames = false,
+ .info = &(const struct vo_info_s){
+ "video encoding using libavcodec",
+ "lavc",
+ "Nicolas George <george@nsup.org>, Rudolf Polzer <divVerent@xonotic.org>",
+ ""
+ },
+ .preinit = preinit,
+ .config = config,
+ .control = control,
+ .uninit = uninit,
+ .check_events = check_events,
+ .draw_osd = draw_osd,
+ .flip_page_timed = flip_page_timed,
+};
+
+// vim: sw=4 ts=4 et tw=80
diff --git a/video/out/vo_null.c b/video/out/vo_null.c
new file mode 100644
index 0000000000..1f307f7f5b
--- /dev/null
+++ b/video/out/vo_null.c
@@ -0,0 +1,103 @@
+/*
+ * based on video_out_null.c from mpeg2dec
+ *
+ * Copyright (C) Aaron Holtzman - June 2000
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "config.h"
+#include "mp_msg.h"
+#include "video_out.h"
+#include "libmpcodecs/vfcap.h"
+#include "libmpcodecs/mp_image.h"
+
+static int draw_slice(struct vo *vo, uint8_t *image[], int stride[],
+ int w, int h, int x, int y)
+{
+ return 0;
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+}
+
+static void flip_page(struct vo *vo)
+{
+}
+
+static int query_format(struct vo *vo, uint32_t format)
+{
+ if (IMGFMT_IS_HWACCEL(format))
+ return 0;
+ return VFCAP_CSP_SUPPORTED;
+}
+
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ return 0;
+}
+
+static void uninit(struct vo *vo)
+{
+}
+
+static void check_events(struct vo *vo)
+{
+}
+
+static int preinit(struct vo *vo, const char *arg)
+{
+ if (arg) {
+ mp_tmsg(MSGT_VO, MSGL_WARN, "[VO_NULL] Unknown subdevice: %s.\n", arg);
+ return ENOSYS;
+ }
+ return 0;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ switch (request) {
+ case VOCTRL_QUERY_FORMAT:
+ return query_format(vo, *((uint32_t *)data));
+ }
+ return VO_NOTIMPL;
+}
+
+const struct vo_driver video_out_null = {
+ .is_new = false,
+ .info = &(const vo_info_t) {
+ "Null video output",
+ "null",
+ "Aaron Holtzman <aholtzma@ess.engr.uvic.ca>",
+ ""
+ },
+ .preinit = preinit,
+ .config = config,
+ .control = control,
+ .draw_slice = draw_slice,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+ .check_events = check_events,
+ .uninit = uninit,
+};
diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c
new file mode 100644
index 0000000000..7b5289838f
--- /dev/null
+++ b/video/out/vo_opengl.c
@@ -0,0 +1,2419 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * You can alternatively redistribute this file 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdbool.h>
+#include <assert.h>
+#include "config.h"
+
+#include <libavutil/common.h>
+
+#ifdef CONFIG_LCMS2
+#include <lcms2.h>
+#include "stream/stream.h"
+#endif
+
+#include "talloc.h"
+#include "mpcommon.h"
+#include "bstr.h"
+#include "mp_msg.h"
+#include "subopt-helper.h"
+#include "video_out.h"
+#include "libmpcodecs/vfcap.h"
+#include "libmpcodecs/mp_image.h"
+#include "geometry.h"
+#include "sub/sub.h"
+#include "bitmap_packer.h"
+
+#include "gl_common.h"
+#include "gl_osd.h"
+#include "filter_kernels.h"
+#include "aspect.h"
+#include "fastmemcpy.h"
+
+static const char vo_opengl_shaders[] =
+// Generated from libvo/vo_opengl_shaders.glsl
+#include "libvo/vo_opengl_shaders.h"
+;
+
+// Pixel width of 1D lookup textures.
+#define LOOKUP_TEXTURE_SIZE 256
+
+// Texture units 0-2 are used by the video, with unit 0 for free use.
+// Units 3-4 are used for scaler LUTs.
+#define TEXUNIT_SCALERS 3
+#define TEXUNIT_3DLUT 5
+#define TEXUNIT_DITHER 6
+
+// lscale/cscale arguments that map directly to shader filter routines.
+// Note that the convolution filters are not included in this list.
+static const char *fixed_scale_filters[] = {
+ "bilinear",
+ "bicubic_fast",
+ "sharpen3",
+ "sharpen5",
+ NULL
+};
+
+struct lut_tex_format {
+ int pixels;
+ GLint internal_format;
+ GLenum format;
+};
+
+// Indexed with filter_kernel->size.
+// This must match the weightsN functions in the shader.
+// Each entry uses (size+3)/4 pixels per LUT entry, and size/pixels components
+// per pixel.
+struct lut_tex_format lut_tex_formats[] = {
+ [2] = {1, GL_RG16F, GL_RG},
+ [4] = {1, GL_RGBA16F, GL_RGBA},
+ [6] = {2, GL_RGB16F, GL_RGB},
+ [8] = {2, GL_RGBA16F, GL_RGBA},
+ [12] = {3, GL_RGBA16F, GL_RGBA},
+ [16] = {4, GL_RGBA16F, GL_RGBA},
+};
+
+// must be sorted, and terminated with 0
+static const int filter_sizes[] = {2, 4, 6, 8, 12, 16, 0};
+
+struct vertex {
+ float position[2];
+ uint8_t color[4];
+ float texcoord[2];
+};
+
+#define VERTEX_ATTRIB_POSITION 0
+#define VERTEX_ATTRIB_COLOR 1
+#define VERTEX_ATTRIB_TEXCOORD 2
+
+// 2 triangles primitives per quad = 6 vertices per quad
+// (GL_QUAD is deprecated, strips can't be used with OSD image lists)
+#define VERTICES_PER_QUAD 6
+
+struct texplane {
+ int shift_x, shift_y;
+ GLuint gl_texture;
+ int gl_buffer;
+ int buffer_size;
+ void *buffer_ptr;
+};
+
+struct scaler {
+ int index;
+ const char *name;
+ float params[2];
+ struct filter_kernel *kernel;
+ GLuint gl_lut;
+ const char *lut_name;
+
+ // kernel points here
+ struct filter_kernel kernel_storage;
+};
+
+struct fbotex {
+ GLuint fbo;
+ GLuint texture;
+ int tex_w, tex_h; // size of .texture
+ int vp_w, vp_h; // viewport of fbo / used part of the texture
+};
+
+struct gl_priv {
+ struct vo *vo;
+ MPGLContext *glctx;
+ GL *gl;
+
+ int use_indirect;
+ int use_gamma;
+ int use_srgb;
+ int use_scale_sep;
+ int use_fancy_downscaling;
+ int use_lut_3d;
+ int use_npot;
+ int use_pbo;
+ int use_glFinish;
+ int use_gl_debug;
+ int allow_sw;
+
+ int dither_depth;
+ int swap_interval;
+ GLint fbo_format;
+ int stereo_mode;
+ int osd_color;
+
+ struct gl_priv *defaults;
+ struct gl_priv *orig_cmdline;
+
+ GLuint vertex_buffer;
+ GLuint vao;
+
+ GLuint osd_programs[SUBBITMAP_COUNT];
+ GLuint indirect_program, scale_sep_program, final_program;
+
+ struct mpgl_osd *osd;
+
+ GLuint lut_3d_texture;
+ int lut_3d_w, lut_3d_h, lut_3d_d;
+ void *lut_3d_data;
+
+ GLuint dither_texture;
+ float dither_quantization;
+ float dither_multiply;
+ int dither_size;
+
+ uint32_t image_width;
+ uint32_t image_height;
+ uint32_t image_format;
+ int texture_width;
+ int texture_height;
+
+ bool is_yuv;
+ bool is_linear_rgb;
+
+ // per pixel (full pixel when packed, each component when planar)
+ int plane_bytes;
+ int plane_bits;
+ int component_bits;
+
+ GLint gl_internal_format;
+ GLenum gl_format;
+ GLenum gl_type;
+
+ int plane_count;
+ struct texplane planes[3];
+
+ struct fbotex indirect_fbo; // RGB target
+ struct fbotex scale_sep_fbo; // first pass when doing 2 pass scaling
+
+ // state for luma (0) and chroma (1) scalers
+ struct scaler scalers[2];
+ // luma scaler parameters (the same are used for chroma)
+ float scaler_params[2];
+
+ struct mp_csp_details colorspace;
+ struct mp_csp_equalizer video_eq;
+
+ int mpi_flipped;
+ int vo_flipped;
+
+ struct mp_rect src_rect; // displayed part of the source video
+ struct mp_rect dst_rect; // video rectangle on output window
+ struct mp_osd_res osd_rect; // OSD size/margins
+ int vp_x, vp_y, vp_w, vp_h; // GL viewport
+
+ int frames_rendered;
+
+ void *scratch;
+};
+
+struct fmt_entry {
+ int mp_format;
+ GLint internal_format;
+ GLenum format;
+ int component_bits;
+ GLenum type;
+};
+
+static const struct fmt_entry mp_to_gl_formats[] = {
+ {IMGFMT_RGB48NE, GL_RGB16, GL_RGB, 16, GL_UNSIGNED_SHORT},
+ {IMGFMT_RGB24, GL_RGB, GL_RGB, 8, GL_UNSIGNED_BYTE},
+ {IMGFMT_RGBA, GL_RGBA, GL_RGBA, 8, GL_UNSIGNED_BYTE},
+ {IMGFMT_RGB15, GL_RGBA, GL_RGBA, 5, GL_UNSIGNED_SHORT_1_5_5_5_REV},
+ {IMGFMT_RGB16, GL_RGB, GL_RGB, 6, GL_UNSIGNED_SHORT_5_6_5_REV},
+ {IMGFMT_BGR15, GL_RGBA, GL_BGRA, 5, GL_UNSIGNED_SHORT_1_5_5_5_REV},
+ {IMGFMT_BGR16, GL_RGB, GL_RGB, 6, GL_UNSIGNED_SHORT_5_6_5},
+ {IMGFMT_BGR24, GL_RGB, GL_BGR, 8, GL_UNSIGNED_BYTE},
+ {IMGFMT_BGRA, GL_RGBA, GL_BGRA, 8, GL_UNSIGNED_BYTE},
+ {0},
+};
+
+static const char *osd_shaders[SUBBITMAP_COUNT] = {
+ [SUBBITMAP_LIBASS] = "frag_osd_libass",
+ [SUBBITMAP_RGBA] = "frag_osd_rgba",
+};
+
+
+static const char help_text[];
+
+static void uninit_rendering(struct gl_priv *p);
+static void delete_shaders(struct gl_priv *p);
+static bool reparse_cmdline(struct gl_priv *p, char *arg);
+
+
+static void default_tex_params(struct GL *gl, GLenum target, GLint filter)
+{
+ gl->TexParameteri(target, GL_TEXTURE_MIN_FILTER, filter);
+ gl->TexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
+ gl->TexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ gl->TexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
+static void debug_check_gl(struct gl_priv *p, const char *msg)
+{
+ if (p->use_gl_debug || p->frames_rendered < 5)
+ glCheckError(p->gl, msg);
+}
+
+static void tex_size(struct gl_priv *p, int w, int h, int *texw, int *texh)
+{
+ if (p->use_npot) {
+ *texw = w;
+ *texh = h;
+ } else {
+ *texw = 32;
+ while (*texw < w)
+ *texw *= 2;
+ *texh = 32;
+ while (*texh < h)
+ *texh *= 2;
+ }
+}
+
+static void draw_triangles(struct gl_priv *p, struct vertex *vb, int vert_count)
+{
+ GL *gl = p->gl;
+
+ assert(vert_count % 3 == 0);
+
+ gl->BindBuffer(GL_ARRAY_BUFFER, p->vertex_buffer);
+ gl->BufferData(GL_ARRAY_BUFFER, vert_count * sizeof(struct vertex), vb,
+ GL_DYNAMIC_DRAW);
+ gl->BindBuffer(GL_ARRAY_BUFFER, 0);
+
+ if (gl->BindVertexArray)
+ gl->BindVertexArray(p->vao);
+
+ gl->DrawArrays(GL_TRIANGLES, 0, vert_count);
+
+ if (gl->BindVertexArray)
+ gl->BindVertexArray(0);
+
+ debug_check_gl(p, "after rendering");
+}
+
+// Write a textured quad to a vertex array.
+// va = destination vertex array, VERTICES_PER_QUAD entries will be overwritten
+// x0, y0, x1, y1 = destination coordinates of the quad
+// tx0, ty0, tx1, ty1 = source texture coordinates (usually in pixels)
+// texture_w, texture_h = size of the texture, or an inverse factor
+// color = optional color for all vertices, NULL for opaque white
+// flip = flip vertically
+static void write_quad(struct vertex *va,
+ float x0, float y0, float x1, float y1,
+ float tx0, float ty0, float tx1, float ty1,
+ float texture_w, float texture_h,
+ const uint8_t color[4], bool flip)
+{
+ static const uint8_t white[4] = { 255, 255, 255, 255 };
+
+ if (!color)
+ color = white;
+
+ tx0 /= texture_w;
+ ty0 /= texture_h;
+ tx1 /= texture_w;
+ ty1 /= texture_h;
+
+ if (flip) {
+ float tmp = ty0;
+ ty0 = ty1;
+ ty1 = tmp;
+ }
+
+#define COLOR_INIT {color[0], color[1], color[2], color[3]}
+ va[0] = (struct vertex) { {x0, y0}, COLOR_INIT, {tx0, ty0} };
+ va[1] = (struct vertex) { {x0, y1}, COLOR_INIT, {tx0, ty1} };
+ va[2] = (struct vertex) { {x1, y0}, COLOR_INIT, {tx1, ty0} };
+ va[3] = (struct vertex) { {x1, y1}, COLOR_INIT, {tx1, ty1} };
+ va[4] = va[2];
+ va[5] = va[1];
+#undef COLOR_INIT
+}
+
+static bool fbotex_init(struct gl_priv *p, struct fbotex *fbo, int w, int h)
+{
+ GL *gl = p->gl;
+ bool res = true;
+
+ assert(gl->mpgl_caps & MPGL_CAP_FB);
+ assert(!fbo->fbo);
+ assert(!fbo->texture);
+
+ tex_size(p, w, h, &fbo->tex_w, &fbo->tex_h);
+
+ fbo->vp_w = w;
+ fbo->vp_h = h;
+
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Create FBO: %dx%d\n", fbo->tex_w, fbo->tex_h);
+
+ gl->GenFramebuffers(1, &fbo->fbo);
+ gl->GenTextures(1, &fbo->texture);
+ gl->BindTexture(GL_TEXTURE_2D, fbo->texture);
+ gl->TexImage2D(GL_TEXTURE_2D, 0, p->fbo_format, fbo->tex_w, fbo->tex_h, 0,
+ GL_RGB, GL_UNSIGNED_BYTE, NULL);
+ default_tex_params(gl, GL_TEXTURE_2D, GL_LINEAR);
+ gl->BindFramebuffer(GL_FRAMEBUFFER, fbo->fbo);
+ gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, fbo->texture, 0);
+
+ if (gl->CheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] Error: framebuffer completeness "
+ "check failed!\n");
+ res = false;
+ }
+
+ gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ debug_check_gl(p, "after creating framebuffer & associated texture");
+
+ return res;
+}
+
+static void fbotex_uninit(struct gl_priv *p, struct fbotex *fbo)
+{
+ GL *gl = p->gl;
+
+ if (gl->mpgl_caps & MPGL_CAP_FB) {
+ gl->DeleteFramebuffers(1, &fbo->fbo);
+ gl->DeleteTextures(1, &fbo->texture);
+ *fbo = (struct fbotex) {0};
+ }
+}
+
+static void matrix_ortho2d(float m[3][3], float x0, float x1,
+ float y0, float y1)
+{
+ memset(m, 0, 9 * sizeof(float));
+ m[0][0] = 2.0f / (x1 - x0);
+ m[1][1] = 2.0f / (y1 - y0);
+ m[2][0] = -(x1 + x0) / (x1 - x0);
+ m[2][1] = -(y1 + y0) / (y1 - y0);
+ m[2][2] = 1.0f;
+}
+
+static void update_uniforms(struct gl_priv *p, GLuint program)
+{
+ GL *gl = p->gl;
+ GLint loc;
+
+ if (program == 0)
+ return;
+
+ gl->UseProgram(program);
+
+ struct mp_csp_params cparams = {
+ .colorspace = p->colorspace,
+ .input_bits = p->plane_bits,
+ .texture_bits = (p->plane_bits + 7) & ~7,
+ };
+ mp_csp_copy_equalizer_values(&cparams, &p->video_eq);
+
+ loc = gl->GetUniformLocation(program, "transform");
+ if (loc >= 0) {
+ float matrix[3][3];
+ matrix_ortho2d(matrix, 0, p->vp_w, p->vp_h, 0);
+ gl->UniformMatrix3fv(loc, 1, GL_FALSE, &matrix[0][0]);
+ }
+
+ loc = gl->GetUniformLocation(program, "colormatrix");
+ if (loc >= 0) {
+ float yuv2rgb[3][4] = {{0}};
+ if (p->is_yuv)
+ mp_get_yuv2rgb_coeffs(&cparams, yuv2rgb);
+ gl->UniformMatrix4x3fv(loc, 1, GL_TRUE, &yuv2rgb[0][0]);
+ }
+
+ gl->Uniform3f(gl->GetUniformLocation(program, "inv_gamma"),
+ 1.0 / cparams.rgamma,
+ 1.0 / cparams.ggamma,
+ 1.0 / cparams.bgamma);
+
+ for (int n = 0; n < p->plane_count; n++) {
+ char textures_n[32];
+ char textures_size_n[32];
+ snprintf(textures_n, sizeof(textures_n), "textures[%d]", n);
+ snprintf(textures_size_n, sizeof(textures_size_n), "textures_size[%d]", n);
+
+ gl->Uniform1i(gl->GetUniformLocation(program, textures_n), n);
+ gl->Uniform2f(gl->GetUniformLocation(program, textures_size_n),
+ p->texture_width >> p->planes[n].shift_x,
+ p->texture_height >> p->planes[n].shift_y);
+ }
+
+ gl->Uniform2f(gl->GetUniformLocation(program, "dither_size"),
+ p->dither_size, p->dither_size);
+
+ gl->Uniform1i(gl->GetUniformLocation(program, "lut_3d"), TEXUNIT_3DLUT);
+
+ for (int n = 0; n < 2; n++) {
+ const char *lut = p->scalers[n].lut_name;
+ if (lut)
+ gl->Uniform1i(gl->GetUniformLocation(program, lut),
+ TEXUNIT_SCALERS + n);
+ }
+
+ gl->Uniform1i(gl->GetUniformLocation(program, "dither"), TEXUNIT_DITHER);
+ gl->Uniform1f(gl->GetUniformLocation(program, "dither_quantization"),
+ p->dither_quantization);
+ gl->Uniform1f(gl->GetUniformLocation(program, "dither_multiply"),
+ p->dither_multiply);
+
+ float sparam1 = p->scaler_params[0];
+ gl->Uniform1f(gl->GetUniformLocation(program, "filter_param1"),
+ isnan(sparam1) ? 0.5f : sparam1);
+
+ loc = gl->GetUniformLocation(program, "osd_color");
+ if (loc >= 0) {
+ int r = (p->osd_color >> 16) & 0xff;
+ int g = (p->osd_color >> 8) & 0xff;
+ int b = p->osd_color & 0xff;
+ int a = 0xff - (p->osd_color >> 24);
+ gl->Uniform4f(loc, r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
+ }
+
+ gl->UseProgram(0);
+
+ debug_check_gl(p, "update_uniforms()");
+}
+
+static void update_all_uniforms(struct gl_priv *p)
+{
+ for (int n = 0; n < SUBBITMAP_COUNT; n++)
+ update_uniforms(p, p->osd_programs[n]);
+ update_uniforms(p, p->indirect_program);
+ update_uniforms(p, p->scale_sep_program);
+ update_uniforms(p, p->final_program);
+}
+
+#define SECTION_HEADER "#!section "
+
+static char *get_section(void *talloc_ctx, struct bstr source,
+ const char *section)
+{
+ char *res = talloc_strdup(talloc_ctx, "");
+ bool copy = false;
+ while (source.len) {
+ struct bstr line = bstr_strip_linebreaks(bstr_getline(source, &source));
+ if (bstr_eatstart(&line, bstr0(SECTION_HEADER))) {
+ copy = bstrcmp0(line, section) == 0;
+ } else if (copy) {
+ res = talloc_asprintf_append_buffer(res, "%.*s\n", BSTR_P(line));
+ }
+ }
+ return res;
+}
+
+static char *t_concat(void *talloc_ctx, const char *s1, const char *s2)
+{
+ return talloc_asprintf(talloc_ctx, "%s%s", s1, s2);
+}
+
+static GLuint create_shader(GL *gl, GLenum type, const char *header,
+ const char *source)
+{
+ void *tmp = talloc_new(NULL);
+ const char *full_source = t_concat(tmp, header, source);
+
+ GLuint shader = gl->CreateShader(type);
+ gl->ShaderSource(shader, 1, &full_source, NULL);
+ gl->CompileShader(shader);
+ GLint status;
+ gl->GetShaderiv(shader, GL_COMPILE_STATUS, &status);
+ GLint log_length;
+ gl->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
+
+ int pri = status ? (log_length > 1 ? MSGL_V : MSGL_DBG2) : MSGL_ERR;
+ const char *typestr = type == GL_VERTEX_SHADER ? "vertex" : "fragment";
+ if (mp_msg_test(MSGT_VO, pri)) {
+ mp_msg(MSGT_VO, pri, "[gl] %s shader source:\n", typestr);
+ mp_log_source(MSGT_VO, pri, full_source);
+ }
+ if (log_length > 1) {
+ GLchar *log = talloc_zero_size(tmp, log_length + 1);
+ gl->GetShaderInfoLog(shader, log_length, NULL, log);
+ mp_msg(MSGT_VO, pri, "[gl] %s shader compile log (status=%d):\n%s\n",
+ typestr, status, log);
+ }
+
+ talloc_free(tmp);
+
+ return shader;
+}
+
+static void prog_create_shader(GL *gl, GLuint program, GLenum type,
+ const char *header, const char *source)
+{
+ GLuint shader = create_shader(gl, type, header, source);
+ gl->AttachShader(program, shader);
+ gl->DeleteShader(shader);
+}
+
+static void link_shader(GL *gl, GLuint program)
+{
+ gl->LinkProgram(program);
+ GLint status;
+ gl->GetProgramiv(program, GL_LINK_STATUS, &status);
+ GLint log_length;
+ gl->GetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
+
+ int pri = status ? (log_length > 1 ? MSGL_V : MSGL_DBG2) : MSGL_ERR;
+ if (mp_msg_test(MSGT_VO, pri)) {
+ GLchar *log = talloc_zero_size(NULL, log_length + 1);
+ gl->GetProgramInfoLog(program, log_length, NULL, log);
+ mp_msg(MSGT_VO, pri, "[gl] shader link log (status=%d): %s\n",
+ status, log);
+ talloc_free(log);
+ }
+}
+
+static void bind_attrib_locs(GL *gl, GLuint program)
+{
+ gl->BindAttribLocation(program, VERTEX_ATTRIB_POSITION, "vertex_position");
+ gl->BindAttribLocation(program, VERTEX_ATTRIB_COLOR, "vertex_color");
+ gl->BindAttribLocation(program, VERTEX_ATTRIB_TEXCOORD, "vertex_texcoord");
+}
+
+static GLuint create_program(GL *gl, const char *name, const char *header,
+ const char *vertex, const char *frag)
+{
+ mp_msg(MSGT_VO, MSGL_V, "[gl] compiling shader program '%s'\n", name);
+ mp_msg(MSGT_VO, MSGL_V, "[gl] header:\n");
+ mp_log_source(MSGT_VO, MSGL_V, header);
+ GLuint prog = gl->CreateProgram();
+ prog_create_shader(gl, prog, GL_VERTEX_SHADER, header, vertex);
+ prog_create_shader(gl, prog, GL_FRAGMENT_SHADER, header, frag);
+ bind_attrib_locs(gl, prog);
+ link_shader(gl, prog);
+ return prog;
+}
+
+static void shader_def(char **shader, const char *name,
+ const char *value)
+{
+ *shader = talloc_asprintf_append(*shader, "#define %s %s\n", name, value);
+}
+
+static void shader_def_opt(char **shader, const char *name, bool b)
+{
+ if (b)
+ shader_def(shader, name, "1");
+}
+
+static void shader_setup_scaler(char **shader, struct scaler *scaler, int pass)
+{
+ const char *target = scaler->index == 0 ? "SAMPLE_L" : "SAMPLE_C";
+ if (!scaler->kernel) {
+ *shader = talloc_asprintf_append(*shader, "#define %s sample_%s\n",
+ target, scaler->name);
+ } else {
+ int size = scaler->kernel->size;
+ if (pass != -1) {
+ // The direction/pass assignment is rather arbitrary, but fixed in
+ // other parts of the code (like FBO setup).
+ const char *direction = pass == 0 ? "0, 1" : "1, 0";
+ *shader = talloc_asprintf_append(*shader, "#define %s(p0, p1, p2) "
+ "sample_convolution_sep%d(vec2(%s), %s, p0, p1, p2)\n",
+ target, size, direction, scaler->lut_name);
+ } else {
+ *shader = talloc_asprintf_append(*shader, "#define %s(p0, p1, p2) "
+ "sample_convolution%d(%s, p0, p1, p2)\n",
+ target, size, scaler->lut_name);
+ }
+ }
+}
+
+// return false if RGB or 4:4:4 YUV
+static bool input_is_subsampled(struct gl_priv *p)
+{
+ for (int i = 0; i < p->plane_count; i++)
+ if (p->planes[i].shift_x || p->planes[i].shift_y)
+ return true;
+ return false;
+}
+
+static void compile_shaders(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+
+ delete_shaders(p);
+
+ void *tmp = talloc_new(NULL);
+
+ struct bstr src = bstr0(vo_opengl_shaders);
+ char *vertex_shader = get_section(tmp, src, "vertex_all");
+ char *shader_prelude = get_section(tmp, src, "prelude");
+ char *s_video = get_section(tmp, src, "frag_video");
+
+ char *header = talloc_asprintf(tmp, "#version %d\n%s", gl->glsl_version,
+ shader_prelude);
+
+ char *header_osd = talloc_strdup(tmp, header);
+ shader_def_opt(&header_osd, "USE_OSD_LINEAR_CONV", p->use_srgb &&
+ !p->use_lut_3d);
+ shader_def_opt(&header_osd, "USE_OSD_3DLUT", p->use_lut_3d);
+ shader_def_opt(&header_osd, "USE_OSD_SRGB", p->use_srgb);
+
+ for (int n = 0; n < SUBBITMAP_COUNT; n++) {
+ const char *name = osd_shaders[n];
+ if (name) {
+ char *s_osd = get_section(tmp, src, name);
+ p->osd_programs[n] =
+ create_program(gl, name, header_osd, vertex_shader, s_osd);
+ }
+ }
+
+ char *header_conv = talloc_strdup(tmp, "");
+ char *header_final = talloc_strdup(tmp, "");
+ char *header_sep = NULL;
+
+ bool convert_input_to_linear = !p->is_linear_rgb
+ && (p->use_srgb || p->use_lut_3d);
+
+ shader_def_opt(&header_conv, "USE_PLANAR", p->plane_count > 1);
+ shader_def_opt(&header_conv, "USE_GBRP", p->image_format == IMGFMT_GBRP);
+ shader_def_opt(&header_conv, "USE_YGRAY", p->is_yuv && p->plane_count == 1);
+ shader_def_opt(&header_conv, "USE_COLORMATRIX", p->is_yuv);
+ shader_def_opt(&header_conv, "USE_LINEAR_CONV", convert_input_to_linear);
+
+ shader_def_opt(&header_final, "USE_LINEAR_CONV_INV", p->use_lut_3d);
+ shader_def_opt(&header_final, "USE_GAMMA_POW", p->use_gamma);
+ shader_def_opt(&header_final, "USE_3DLUT", p->use_lut_3d);
+ shader_def_opt(&header_final, "USE_SRGB", p->use_srgb);
+ shader_def_opt(&header_final, "USE_DITHER", p->dither_texture != 0);
+
+ if (p->use_scale_sep && p->scalers[0].kernel) {
+ header_sep = talloc_strdup(tmp, "");
+ shader_def_opt(&header_sep, "FIXED_SCALE", true);
+ shader_setup_scaler(&header_sep, &p->scalers[0], 0);
+ shader_setup_scaler(&header_final, &p->scalers[0], 1);
+ } else {
+ shader_setup_scaler(&header_final, &p->scalers[0], -1);
+ }
+
+ // We want to do scaling in linear light. Scaling is closely connected to
+ // texture sampling due to how the shader is structured (or if GL bilinear
+ // scaling is used). The purpose of the "indirect" pass is to convert the
+ // input video to linear RGB.
+ // Another purpose is reducing input to a single texture for scaling.
+ bool use_indirect = p->use_indirect;
+
+ // Don't sample from input video textures before converting the input to
+ // linear light. (Unneeded when sRGB textures are used.)
+ if (convert_input_to_linear)
+ use_indirect = true;
+
+ // It doesn't make sense to scale the chroma with cscale in the 1. scale
+ // step and with lscale in the 2. step. If the chroma is subsampled, a
+ // convolution filter wouldn't even work entirely correctly, because the
+ // luma scaler would sample two texels instead of one per tap for chroma.
+ // Also, even with 4:4:4 YUV or planar RGB, the indirection might be faster,
+ // because the shader can't use one scaler for sampling from 3 textures. It
+ // has to fetch the coefficients for each texture separately, even though
+ // they're the same (this is not an inherent restriction, but would require
+ // to restructure the shader).
+ if (header_sep && p->plane_count > 1)
+ use_indirect = true;
+
+ if (input_is_subsampled(p)) {
+ shader_setup_scaler(&header_conv, &p->scalers[1], -1);
+ } else {
+ // Force using the luma scaler on chroma. If the "indirect" stage is
+ // used, the actual scaling will happen in the next stage.
+ shader_def(&header_conv, "SAMPLE_C",
+ use_indirect ? "sample_bilinear" : "SAMPLE_L");
+ }
+
+ if (use_indirect) {
+ // We don't use filtering for the Y-plane (luma), because it's never
+ // scaled in this scenario.
+ shader_def(&header_conv, "SAMPLE_L", "sample_bilinear");
+ shader_def_opt(&header_conv, "FIXED_SCALE", true);
+ header_conv = t_concat(tmp, header, header_conv);
+ p->indirect_program =
+ create_program(gl, "indirect", header_conv, vertex_shader, s_video);
+ } else if (header_sep) {
+ header_sep = t_concat(tmp, header_sep, header_conv);
+ } else {
+ header_final = t_concat(tmp, header_final, header_conv);
+ }
+
+ if (header_sep) {
+ header_sep = t_concat(tmp, header, header_sep);
+ p->scale_sep_program =
+ create_program(gl, "scale_sep", header_sep, vertex_shader, s_video);
+ }
+
+ header_final = t_concat(tmp, header, header_final);
+ p->final_program =
+ create_program(gl, "final", header_final, vertex_shader, s_video);
+
+ debug_check_gl(p, "shader compilation");
+
+ talloc_free(tmp);
+}
+
+static void delete_program(GL *gl, GLuint *prog)
+{
+ gl->DeleteProgram(*prog);
+ *prog = 0;
+}
+
+static void delete_shaders(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+
+ for (int n = 0; n < SUBBITMAP_COUNT; n++)
+ delete_program(gl, &p->osd_programs[n]);
+ delete_program(gl, &p->indirect_program);
+ delete_program(gl, &p->scale_sep_program);
+ delete_program(gl, &p->final_program);
+}
+
+static double get_scale_factor(struct gl_priv *p)
+{
+ double sx = (p->dst_rect.x1 - p->dst_rect.x0) /
+ (double)(p->src_rect.x1 - p->src_rect.x0);
+ double sy = (p->dst_rect.y1 - p->dst_rect.y0) /
+ (double)(p->src_rect.y1 - p->src_rect.y0);
+ // xxx: actually we should use different scalers in X/Y directions if the
+ // scale factors are different due to anamorphic content
+ return FFMIN(sx, sy);
+}
+
+static bool update_scale_factor(struct gl_priv *p, struct filter_kernel *kernel)
+{
+ double scale = get_scale_factor(p);
+ if (!p->use_fancy_downscaling && scale < 1.0)
+ scale = 1.0;
+ return mp_init_filter(kernel, filter_sizes, FFMAX(1.0, 1.0 / scale));
+}
+
+static void init_scaler(struct gl_priv *p, struct scaler *scaler)
+{
+ GL *gl = p->gl;
+
+ assert(scaler->name);
+
+ scaler->kernel = NULL;
+
+ const struct filter_kernel *t_kernel = mp_find_filter_kernel(scaler->name);
+ if (!t_kernel)
+ return;
+
+ scaler->kernel_storage = *t_kernel;
+ scaler->kernel = &scaler->kernel_storage;
+
+ for (int n = 0; n < 2; n++) {
+ if (!isnan(p->scaler_params[n]))
+ scaler->kernel->params[n] = p->scaler_params[n];
+ }
+
+ update_scale_factor(p, scaler->kernel);
+
+ int size = scaler->kernel->size;
+ assert(size < FF_ARRAY_ELEMS(lut_tex_formats));
+ struct lut_tex_format *fmt = &lut_tex_formats[size];
+ bool use_2d = fmt->pixels > 1;
+ bool is_luma = scaler->index == 0;
+ scaler->lut_name = use_2d
+ ? (is_luma ? "lut_l_2d" : "lut_c_2d")
+ : (is_luma ? "lut_l_1d" : "lut_c_1d");
+
+ gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_SCALERS + scaler->index);
+ GLenum target = use_2d ? GL_TEXTURE_2D : GL_TEXTURE_1D;
+
+ if (!scaler->gl_lut)
+ gl->GenTextures(1, &scaler->gl_lut);
+
+ gl->BindTexture(target, scaler->gl_lut);
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+
+ float *weights = talloc_array(NULL, float, LOOKUP_TEXTURE_SIZE * size);
+ mp_compute_lut(scaler->kernel, LOOKUP_TEXTURE_SIZE, weights);
+ if (use_2d) {
+ gl->TexImage2D(GL_TEXTURE_2D, 0, fmt->internal_format, fmt->pixels,
+ LOOKUP_TEXTURE_SIZE, 0, fmt->format, GL_FLOAT,
+ weights);
+ } else {
+ gl->TexImage1D(GL_TEXTURE_1D, 0, fmt->internal_format,
+ LOOKUP_TEXTURE_SIZE, 0, fmt->format, GL_FLOAT,
+ weights);
+ }
+ talloc_free(weights);
+
+ gl->TexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ gl->TexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gl->TexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ gl->TexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ gl->ActiveTexture(GL_TEXTURE0);
+
+ debug_check_gl(p, "after initializing scaler");
+}
+
+static void make_dither_matrix(unsigned char *m, int size)
+{
+ m[0] = 0;
+ for (int sz = 1; sz < size; sz *= 2) {
+ int offset[] = {sz*size, sz, sz * (size+1), 0};
+ for (int i = 0; i < 4; i++)
+ for (int y = 0; y < sz * size; y += size)
+ for (int x = 0; x < sz; x++)
+ m[x+y+offset[i]] = m[x+y] * 4 + (3-i) * 256/size/size;
+ }
+}
+
+static void init_dither(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+
+ // Assume 8 bits per component if unknown.
+ int dst_depth = p->glctx->depth_g ? p->glctx->depth_g : 8;
+ if (p->dither_depth > 0)
+ dst_depth = p->dither_depth;
+
+ int src_depth = p->component_bits;
+ if (p->use_lut_3d)
+ src_depth = 16;
+
+ if (dst_depth >= src_depth || p->dither_depth < 0 || src_depth < 0)
+ return;
+
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Dither %d->%d.\n", src_depth, dst_depth);
+
+ // This defines how many bits are considered significant for output on
+ // screen. The superfluous bits will be used for rounded according to the
+ // dither matrix. The precision of the source implicitly decides how many
+ // dither patterns can be visible.
+ p->dither_quantization = (1 << dst_depth) - 1;
+ int size = 8;
+ p->dither_multiply = p->dither_quantization + 1.0 / (size*size);
+ unsigned char dither[256];
+ make_dither_matrix(dither, size);
+
+ p->dither_size = size;
+
+ gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_DITHER);
+ gl->GenTextures(1, &p->dither_texture);
+ gl->BindTexture(GL_TEXTURE_2D, p->dither_texture);
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RED, size, size, 0, GL_RED,
+ GL_UNSIGNED_BYTE, dither);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ gl->ActiveTexture(GL_TEXTURE0);
+}
+
+static void reinit_rendering(struct gl_priv *p)
+{
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Reinit rendering.\n");
+
+ if (p->gl->SwapInterval && p->swap_interval >= 0)
+ p->gl->SwapInterval(p->swap_interval);
+
+ debug_check_gl(p, "before scaler initialization");
+
+ uninit_rendering(p);
+
+ init_dither(p);
+
+ init_scaler(p, &p->scalers[0]);
+ init_scaler(p, &p->scalers[1]);
+
+ compile_shaders(p);
+
+ if (p->indirect_program && !p->indirect_fbo.fbo)
+ fbotex_init(p, &p->indirect_fbo, p->texture_width, p->texture_height);
+
+ if (!p->osd) {
+ p->osd = mpgl_osd_init(p->gl, false);
+ p->osd->use_pbo = p->use_pbo;
+ }
+}
+
+static void uninit_rendering(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+
+ delete_shaders(p);
+
+ for (int n = 0; n < 2; n++) {
+ gl->DeleteTextures(1, &p->scalers[n].gl_lut);
+ p->scalers[n].gl_lut = 0;
+ p->scalers[n].lut_name = NULL;
+ p->scalers[n].kernel = NULL;
+ }
+
+ gl->DeleteTextures(1, &p->dither_texture);
+ p->dither_texture = 0;
+
+ if (p->osd)
+ mpgl_osd_destroy(p->osd);
+ p->osd = NULL;
+}
+
+static void init_lut_3d(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+
+ gl->GenTextures(1, &p->lut_3d_texture);
+ gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_3DLUT);
+ gl->BindTexture(GL_TEXTURE_3D, p->lut_3d_texture);
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ gl->TexImage3D(GL_TEXTURE_3D, 0, GL_RGB16, p->lut_3d_w, p->lut_3d_h,
+ p->lut_3d_d, 0, GL_RGB, GL_UNSIGNED_SHORT, p->lut_3d_data);
+ gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+ gl->ActiveTexture(GL_TEXTURE0);
+
+ debug_check_gl(p, "after 3d lut creation");
+}
+
+static void init_video(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+
+ if (p->use_lut_3d && !p->lut_3d_texture)
+ init_lut_3d(p);
+
+ if (!p->is_yuv && (p->use_srgb || p->use_lut_3d)) {
+ p->is_linear_rgb = true;
+ p->gl_internal_format = GL_SRGB;
+ }
+
+ int eq_caps = MP_CSP_EQ_CAPS_GAMMA;
+ if (p->is_yuv)
+ eq_caps |= MP_CSP_EQ_CAPS_COLORMATRIX;
+ p->video_eq.capabilities = eq_caps;
+
+ debug_check_gl(p, "before video texture creation");
+
+ tex_size(p, p->image_width, p->image_height,
+ &p->texture_width, &p->texture_height);
+
+ for (int n = 0; n < p->plane_count; n++) {
+ struct texplane *plane = &p->planes[n];
+
+ int w = p->texture_width >> plane->shift_x;
+ int h = p->texture_height >> plane->shift_y;
+
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Texture for plane %d: %dx%d\n", n, w, h);
+
+ gl->ActiveTexture(GL_TEXTURE0 + n);
+ gl->GenTextures(1, &plane->gl_texture);
+ gl->BindTexture(GL_TEXTURE_2D, plane->gl_texture);
+
+ gl->TexImage2D(GL_TEXTURE_2D, 0, p->gl_internal_format, w, h, 0,
+ p->gl_format, p->gl_type, NULL);
+ default_tex_params(gl, GL_TEXTURE_2D, GL_LINEAR);
+ }
+ gl->ActiveTexture(GL_TEXTURE0);
+
+ debug_check_gl(p, "after video texture creation");
+
+ reinit_rendering(p);
+}
+
+static void uninit_video(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+
+ uninit_rendering(p);
+
+ for (int n = 0; n < 3; n++) {
+ struct texplane *plane = &p->planes[n];
+
+ gl->DeleteTextures(1, &plane->gl_texture);
+ plane->gl_texture = 0;
+ gl->DeleteBuffers(1, &plane->gl_buffer);
+ plane->gl_buffer = 0;
+ plane->buffer_ptr = NULL;
+ plane->buffer_size = 0;
+ }
+
+ fbotex_uninit(p, &p->indirect_fbo);
+ fbotex_uninit(p, &p->scale_sep_fbo);
+}
+
+static void render_to_fbo(struct gl_priv *p, struct fbotex *fbo, int w, int h,
+ int tex_w, int tex_h)
+{
+ GL *gl = p->gl;
+
+ gl->Viewport(0, 0, fbo->vp_w, fbo->vp_h);
+ gl->BindFramebuffer(GL_FRAMEBUFFER, fbo->fbo);
+
+ struct vertex vb[VERTICES_PER_QUAD];
+ write_quad(vb, -1, -1, 1, 1,
+ 0, 0, w, h,
+ tex_w, tex_h,
+ NULL, false);
+ draw_triangles(p, vb, VERTICES_PER_QUAD);
+
+ gl->BindFramebuffer(GL_FRAMEBUFFER, 0);
+ gl->Viewport(p->vp_x, p->vp_y, p->vp_w, p->vp_h);
+
+}
+
+static void handle_pass(struct gl_priv *p, struct fbotex **source,
+ struct fbotex *fbo, GLuint program)
+{
+ GL *gl = p->gl;
+
+ if (!program)
+ return;
+
+ gl->BindTexture(GL_TEXTURE_2D, (*source)->texture);
+ gl->UseProgram(program);
+ render_to_fbo(p, fbo, (*source)->vp_w, (*source)->vp_h,
+ (*source)->tex_w, (*source)->tex_h);
+ *source = fbo;
+}
+
+static void do_render(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+ struct vertex vb[VERTICES_PER_QUAD];
+ bool is_flipped = p->mpi_flipped ^ p->vo_flipped;
+
+ // Order of processing:
+ // [indirect -> [scale_sep ->]] final
+
+ struct fbotex dummy = {
+ .vp_w = p->image_width, .vp_h = p->image_height,
+ .tex_w = p->texture_width, .tex_h = p->texture_height,
+ .texture = p->planes[0].gl_texture,
+ };
+ struct fbotex *source = &dummy;
+
+ handle_pass(p, &source, &p->indirect_fbo, p->indirect_program);
+ handle_pass(p, &source, &p->scale_sep_fbo, p->scale_sep_program);
+
+ gl->BindTexture(GL_TEXTURE_2D, source->texture);
+ gl->UseProgram(p->final_program);
+
+ float final_texw = p->image_width * source->tex_w / (float)source->vp_w;
+ float final_texh = p->image_height * source->tex_h / (float)source->vp_h;
+
+ if (p->stereo_mode) {
+ int w = p->src_rect.x1 - p->src_rect.x0;
+ int imgw = p->image_width;
+
+ glEnable3DLeft(gl, p->stereo_mode);
+
+ write_quad(vb,
+ p->dst_rect.x0, p->dst_rect.y0,
+ p->dst_rect.x1, p->dst_rect.y1,
+ p->src_rect.x0 / 2, p->src_rect.y0,
+ p->src_rect.x0 / 2 + w / 2, p->src_rect.y1,
+ final_texw, final_texh,
+ NULL, is_flipped);
+ draw_triangles(p, vb, VERTICES_PER_QUAD);
+
+ glEnable3DRight(gl, p->stereo_mode);
+
+ write_quad(vb,
+ p->dst_rect.x0, p->dst_rect.y0,
+ p->dst_rect.x1, p->dst_rect.y1,
+ p->src_rect.x0 / 2 + imgw / 2, p->src_rect.y0,
+ p->src_rect.x0 / 2 + imgw / 2 + w / 2, p->src_rect.y1,
+ final_texw, final_texh,
+ NULL, is_flipped);
+ draw_triangles(p, vb, VERTICES_PER_QUAD);
+
+ glDisable3D(gl, p->stereo_mode);
+ } else {
+ write_quad(vb,
+ p->dst_rect.x0, p->dst_rect.y0,
+ p->dst_rect.x1, p->dst_rect.y1,
+ p->src_rect.x0, p->src_rect.y0,
+ p->src_rect.x1, p->src_rect.y1,
+ final_texw, final_texh,
+ NULL, is_flipped);
+ draw_triangles(p, vb, VERTICES_PER_QUAD);
+ }
+
+ gl->UseProgram(0);
+
+ debug_check_gl(p, "after video rendering");
+}
+
+static void update_window_sized_objects(struct gl_priv *p)
+{
+ if (p->scale_sep_program) {
+ int h = p->dst_rect.y1 - p->dst_rect.y0;
+ if (h > p->scale_sep_fbo.tex_h) {
+ fbotex_uninit(p, &p->scale_sep_fbo);
+ // Round up to an arbitrary alignment to make window resizing or
+ // panscan controls smoother (less texture reallocations).
+ int height = FFALIGN(h, 256);
+ fbotex_init(p, &p->scale_sep_fbo, p->image_width, height);
+ }
+ p->scale_sep_fbo.vp_w = p->image_width;
+ p->scale_sep_fbo.vp_h = h;
+ }
+}
+
+static void resize(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+ struct vo *vo = p->vo;
+
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Resize: %dx%d\n", vo->dwidth, vo->dheight);
+ p->vp_x = 0, p->vp_y = 0;
+ p->vp_w = vo->dwidth, p->vp_h = vo->dheight;
+ gl->Viewport(p->vp_x, p->vp_y, p->vp_w, p->vp_h);
+
+ vo_get_src_dst_rects(vo, &p->src_rect, &p->dst_rect, &p->osd_rect);
+
+ bool need_scaler_reinit = false; // filter size change needed
+ bool need_scaler_update = false; // filter LUT change needed
+ bool too_small = false;
+ for (int n = 0; n < 2; n++) {
+ if (p->scalers[n].kernel) {
+ struct filter_kernel tkernel = *p->scalers[n].kernel;
+ struct filter_kernel old = tkernel;
+ bool ok = update_scale_factor(p, &tkernel);
+ too_small |= !ok;
+ need_scaler_reinit |= (tkernel.size != old.size);
+ need_scaler_update |= (tkernel.inv_scale != old.inv_scale);
+ }
+ }
+ if (need_scaler_reinit) {
+ reinit_rendering(p);
+ } else if (need_scaler_update) {
+ init_scaler(p, &p->scalers[0]);
+ init_scaler(p, &p->scalers[1]);
+ }
+ if (too_small)
+ mp_msg(MSGT_VO, MSGL_WARN, "[gl] Can't downscale that much, window "
+ "output may look suboptimal.\n");
+
+ update_window_sized_objects(p);
+ update_all_uniforms(p);
+
+ gl->Clear(GL_COLOR_BUFFER_BIT);
+ vo->want_redraw = true;
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ if (p->use_glFinish)
+ gl->Finish();
+
+ p->glctx->swapGlBuffers(p->glctx);
+
+ if (p->dst_rect.x0 > p->vp_x || p->dst_rect.y0 > p->vp_y
+ || p->dst_rect.x1 < p->vp_x + p->vp_w
+ || p->dst_rect.y1 < p->vp_y + p->vp_h)
+ {
+ gl->Clear(GL_COLOR_BUFFER_BIT);
+ }
+
+ p->frames_rendered++;
+}
+
+static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h,
+ int x, int y)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ p->mpi_flipped = stride[0] < 0;
+
+ for (int n = 0; n < p->plane_count; n++) {
+ gl->ActiveTexture(GL_TEXTURE0 + n);
+ gl->BindTexture(GL_TEXTURE_2D, p->planes[n].gl_texture);
+ int xs = p->planes[n].shift_x, ys = p->planes[n].shift_y;
+ glUploadTex(gl, GL_TEXTURE_2D, p->gl_format, p->gl_type, src[n],
+ stride[n], x >> xs, y >> ys, w >> xs, h >> ys, 0);
+ }
+ gl->ActiveTexture(GL_TEXTURE0);
+
+ return 0;
+}
+
+static uint32_t get_image(struct vo *vo, mp_image_t *mpi)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ if (!p->use_pbo)
+ return VO_FALSE;
+
+ // We don't support alpha planes. (Disabling PBOs with normal draw calls is
+ // an undesired, but harmless side-effect.)
+ if (mpi->num_planes != p->plane_count)
+ return VO_FALSE;
+
+ if (mpi->flags & MP_IMGFLAG_READABLE)
+ return VO_FALSE;
+ if (mpi->type != MP_IMGTYPE_STATIC && mpi->type != MP_IMGTYPE_TEMP &&
+ (mpi->type != MP_IMGTYPE_NUMBERED || mpi->number))
+ return VO_FALSE;
+ mpi->flags &= ~MP_IMGFLAG_COMMON_PLANE;
+ for (int n = 0; n < p->plane_count; n++) {
+ struct texplane *plane = &p->planes[n];
+ mpi->stride[n] = (mpi->width >> plane->shift_x) * p->plane_bytes;
+ int needed_size = (mpi->height >> plane->shift_y) * mpi->stride[n];
+ if (!plane->gl_buffer)
+ gl->GenBuffers(1, &plane->gl_buffer);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
+ if (needed_size > plane->buffer_size) {
+ plane->buffer_size = needed_size;
+ gl->BufferData(GL_PIXEL_UNPACK_BUFFER, plane->buffer_size,
+ NULL, GL_DYNAMIC_DRAW);
+ }
+ if (!plane->buffer_ptr)
+ plane->buffer_ptr = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER,
+ GL_WRITE_ONLY);
+ mpi->planes[n] = plane->buffer_ptr;
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+ mpi->flags |= MP_IMGFLAG_DIRECT;
+ return VO_TRUE;
+}
+
+static uint32_t draw_image(struct gl_priv *p, mp_image_t *mpi)
+{
+ GL *gl = p->gl;
+ int n;
+
+ assert(mpi->num_planes >= p->plane_count);
+
+ mp_image_t mpi2 = *mpi;
+ int w = mpi->w, h = mpi->h;
+ if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK)
+ goto skip_upload;
+ mpi2.flags = 0;
+ mpi2.type = MP_IMGTYPE_TEMP;
+ mpi2.width = mpi2.w;
+ mpi2.height = mpi2.h;
+ if (!(mpi->flags & MP_IMGFLAG_DIRECT)
+ && !p->planes[0].buffer_ptr
+ && get_image(p->vo, &mpi2) == VO_TRUE)
+ {
+ for (n = 0; n < p->plane_count; n++) {
+ struct texplane *plane = &p->planes[n];
+ int xs = plane->shift_x, ys = plane->shift_y;
+ int line_bytes = (mpi->w >> xs) * p->plane_bytes;
+ memcpy_pic(mpi2.planes[n], mpi->planes[n], line_bytes, mpi->h >> ys,
+ mpi2.stride[n], mpi->stride[n]);
+ }
+ mpi = &mpi2;
+ }
+ p->mpi_flipped = mpi->stride[0] < 0;
+ for (n = 0; n < p->plane_count; n++) {
+ struct texplane *plane = &p->planes[n];
+ int xs = plane->shift_x, ys = plane->shift_y;
+ void *plane_ptr = mpi->planes[n];
+ if (mpi->flags & MP_IMGFLAG_DIRECT) {
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer);
+ if (!gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER))
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Video PBO upload failed. "
+ "Remove the 'pbo' suboption.\n");
+ plane->buffer_ptr = NULL;
+ plane_ptr = NULL; // PBO offset 0
+ }
+ gl->ActiveTexture(GL_TEXTURE0 + n);
+ gl->BindTexture(GL_TEXTURE_2D, plane->gl_texture);
+ glUploadTex(gl, GL_TEXTURE_2D, p->gl_format, p->gl_type, plane_ptr,
+ mpi->stride[n], 0, 0, w >> xs, h >> ys, 0);
+ }
+ gl->ActiveTexture(GL_TEXTURE0);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+skip_upload:
+ do_render(p);
+ return VO_TRUE;
+}
+
+static mp_image_t *get_screenshot(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+
+ mp_image_t *image = alloc_mpi(p->texture_width, p->texture_height,
+ p->image_format);
+
+ // NOTE about image formats with alpha plane: we don't even have the alpha
+ // anymore. We never upload it to any texture, as it would be a waste of
+ // time. On the other hand, we can't find a "similar", non-alpha image
+ // format easily. So we just leave the alpha plane of the newly allocated
+ // image as-is, and hope that the alpha is ignored by the receiver of the
+ // screenshot. (If not, code should be added to make it fully opaque.)
+
+ for (int n = 0; n < p->plane_count; n++) {
+ gl->ActiveTexture(GL_TEXTURE0 + n);
+ gl->BindTexture(GL_TEXTURE_2D, p->planes[n].gl_texture);
+ glDownloadTex(gl, GL_TEXTURE_2D, p->gl_format, p->gl_type,
+ image->planes[n], image->stride[n]);
+ }
+ gl->ActiveTexture(GL_TEXTURE0);
+
+ image->w = p->image_width;
+ image->h = p->image_height;
+ image->display_w = p->vo->aspdat.prew;
+ image->display_h = p->vo->aspdat.preh;
+
+ mp_image_set_colorspace_details(image, &p->colorspace);
+
+ return image;
+}
+
+static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs)
+{
+ struct gl_priv *p = ctx;
+ GL *gl = p->gl;
+
+ struct mpgl_osd_part *osd = mpgl_osd_generate(p->osd, imgs);
+ if (!osd)
+ return;
+
+ assert(osd->format != SUBBITMAP_EMPTY);
+
+ if (!osd->num_vertices) {
+ osd->vertices = talloc_realloc(osd, osd->vertices, struct vertex,
+ osd->packer->count * VERTICES_PER_QUAD);
+
+ struct vertex *va = osd->vertices;
+
+ for (int n = 0; n < osd->packer->count; n++) {
+ struct sub_bitmap *b = &imgs->parts[n];
+ struct pos p = osd->packer->result[n];
+
+ // NOTE: the blend color is used with SUBBITMAP_LIBASS only, so it
+ // doesn't matter that we upload garbage for the other formats
+ uint32_t c = b->libass.color;
+ uint8_t color[4] = { c >> 24, (c >> 16) & 0xff,
+ (c >> 8) & 0xff, 255 - (c & 0xff) };
+
+ write_quad(&va[osd->num_vertices],
+ b->x, b->y, b->x + b->dw, b->y + b->dh,
+ p.x, p.y, p.x + b->w, p.y + b->h,
+ osd->w, osd->h, color, false);
+ osd->num_vertices += VERTICES_PER_QUAD;
+ }
+ }
+
+ debug_check_gl(p, "before drawing osd");
+
+ gl->UseProgram(p->osd_programs[osd->format]);
+ mpgl_osd_set_gl_state(p->osd, osd);
+ draw_triangles(p, osd->vertices, osd->num_vertices);
+ mpgl_osd_unset_gl_state(p->osd, osd);
+ gl->UseProgram(0);
+
+ debug_check_gl(p, "after drawing osd");
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ struct gl_priv *p = vo->priv;
+ assert(p->osd);
+
+ osd_draw(osd, p->osd_rect, osd->vo_pts, 0, p->osd->formats, draw_osd_cb, p);
+}
+
+// Disable features that are not supported with the current OpenGL version.
+static void check_gl_features(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+ bool have_float_tex = gl->mpgl_caps & MPGL_CAP_FLOAT_TEX;
+ bool have_fbo = gl->mpgl_caps & MPGL_CAP_FB;
+ bool have_srgb = gl->mpgl_caps & MPGL_CAP_SRGB_TEX;
+
+ // srgb_compand() not available
+ if (gl->glsl_version < 130)
+ have_srgb = false;
+
+ char *disabled[10];
+ int n_disabled = 0;
+
+ if (have_fbo) {
+ struct fbotex fbo = {0};
+ have_fbo = fbotex_init(p, &fbo, 16, 16);
+ fbotex_uninit(p, &fbo);
+ }
+
+ // Disable these only if the user didn't disable scale-sep on the command
+ // line, so convolution filter can still be forced to be run.
+ // Normally, we want to disable them by default if FBOs are unavailable,
+ // because they will be slow (not critically slow, but still slower).
+ // Without FP textures, we must always disable them.
+ if (!have_float_tex || (!have_fbo && p->use_scale_sep)) {
+ for (int n = 0; n < 2; n++) {
+ struct scaler *scaler = &p->scalers[n];
+ if (mp_find_filter_kernel(scaler->name)) {
+ scaler->name = "bilinear";
+ disabled[n_disabled++]
+ = have_float_tex ? "scaler (FBO)" : "scaler (float tex.)";
+ }
+ }
+ }
+
+ if (!have_srgb && p->use_srgb) {
+ p->use_srgb = false;
+ disabled[n_disabled++] = "sRGB";
+ }
+ if (!have_fbo && p->use_lut_3d) {
+ p->use_lut_3d = false;
+ disabled[n_disabled++] = "color management (FBO)";
+ }
+ if (!have_srgb && p->use_lut_3d) {
+ p->use_lut_3d = false;
+ disabled[n_disabled++] = "color management (sRGB)";
+ }
+
+ if (!have_fbo) {
+ p->use_scale_sep = false;
+ p->use_indirect = false;
+ }
+
+ if (n_disabled) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] Some OpenGL extensions not detected, "
+ "disabling: ");
+ for (int n = 0; n < n_disabled; n++) {
+ if (n)
+ mp_msg(MSGT_VO, MSGL_ERR, ", ");
+ mp_msg(MSGT_VO, MSGL_ERR, "%s", disabled[n]);
+ }
+ mp_msg(MSGT_VO, MSGL_ERR, ".\n");
+ }
+}
+
+static void setup_vertex_array(GL *gl)
+{
+ size_t stride = sizeof(struct vertex);
+
+ gl->EnableVertexAttribArray(VERTEX_ATTRIB_POSITION);
+ gl->VertexAttribPointer(VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE,
+ stride, (void*)offsetof(struct vertex, position));
+
+ gl->EnableVertexAttribArray(VERTEX_ATTRIB_COLOR);
+ gl->VertexAttribPointer(VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE,
+ stride, (void*)offsetof(struct vertex, color));
+
+ gl->EnableVertexAttribArray(VERTEX_ATTRIB_TEXCOORD);
+ gl->VertexAttribPointer(VERTEX_ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE,
+ stride, (void*)offsetof(struct vertex, texcoord));
+}
+
+static int init_gl(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+
+ debug_check_gl(p, "before init_gl");
+
+ const char *vendor = gl->GetString(GL_VENDOR);
+ const char *version = gl->GetString(GL_VERSION);
+ const char *renderer = gl->GetString(GL_RENDERER);
+ const char *glsl = gl->GetString(GL_SHADING_LANGUAGE_VERSION);
+ mp_msg(MSGT_VO, MSGL_V, "[gl] GL_RENDERER='%s', GL_VENDOR='%s', "
+ "GL_VERSION='%s', GL_SHADING_LANGUAGE_VERSION='%s'"
+ "\n", renderer, vendor, version, glsl);
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Display depth: R=%d, G=%d, B=%d\n",
+ p->glctx->depth_r, p->glctx->depth_g, p->glctx->depth_b);
+
+ check_gl_features(p);
+
+ gl->Disable(GL_DITHER);
+ gl->Disable(GL_BLEND);
+ gl->Disable(GL_DEPTH_TEST);
+ gl->DepthMask(GL_FALSE);
+ gl->Disable(GL_CULL_FACE);
+ gl->DrawBuffer(GL_BACK);
+
+ gl->GenBuffers(1, &p->vertex_buffer);
+ gl->BindBuffer(GL_ARRAY_BUFFER, p->vertex_buffer);
+
+ if (gl->BindVertexArray) {
+ gl->GenVertexArrays(1, &p->vao);
+ gl->BindVertexArray(p->vao);
+ setup_vertex_array(gl);
+ gl->BindVertexArray(0);
+ } else {
+ setup_vertex_array(gl);
+ }
+
+ gl->BindBuffer(GL_ARRAY_BUFFER, 0);
+
+ gl->ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ gl->Clear(GL_COLOR_BUFFER_BIT);
+
+ debug_check_gl(p, "after init_gl");
+
+ return 1;
+}
+
+static void uninit_gl(struct gl_priv *p)
+{
+ GL *gl = p->gl;
+
+ // NOTE: GL functions might not be loaded yet
+ if (!(p->glctx && p->gl->DeleteTextures))
+ return;
+
+ uninit_video(p);
+
+ if (gl->DeleteVertexArrays)
+ gl->DeleteVertexArrays(1, &p->vao);
+ p->vao = 0;
+ gl->DeleteBuffers(1, &p->vertex_buffer);
+ p->vertex_buffer = 0;
+
+ gl->DeleteTextures(1, &p->lut_3d_texture);
+ p->lut_3d_texture = 0;
+}
+
+static bool init_format(int fmt, struct gl_priv *init)
+{
+ bool supported = false;
+ struct gl_priv dummy;
+ if (!init)
+ init = &dummy;
+
+ mp_image_t dummy_img = {0};
+ mp_image_setfmt(&dummy_img, fmt);
+
+ init->image_format = fmt;
+ init->component_bits = -1;
+
+ // RGB/packed formats
+ for (const struct fmt_entry *e = mp_to_gl_formats; e->mp_format; e++) {
+ if (e->mp_format == fmt) {
+ supported = true;
+ init->plane_bits = dummy_img.bpp;
+ init->gl_format = e->format;
+ init->gl_internal_format = e->internal_format;
+ init->component_bits = e->component_bits;
+ init->gl_type = e->type;
+ break;
+ }
+ }
+
+ // YUV/planar formats
+ if (!supported && mp_get_chroma_shift(fmt, NULL, NULL, &init->plane_bits)) {
+ init->gl_format = GL_RED;
+ init->component_bits = init->plane_bits;
+ if (init->plane_bits == 8) {
+ supported = true;
+ init->gl_internal_format = GL_RED;
+ init->gl_type = GL_UNSIGNED_BYTE;
+ } else if (IMGFMT_IS_YUVP16_NE(fmt)) {
+ supported = true;
+ init->gl_internal_format = GL_R16;
+ init->gl_type = GL_UNSIGNED_SHORT;
+ }
+ }
+
+ // RGB/planar
+ if (!supported && fmt == IMGFMT_GBRP) {
+ supported = true;
+ init->plane_bits = init->component_bits = 8;
+ init->gl_format = GL_RED;
+ init->gl_internal_format = GL_RED;
+ init->gl_type = GL_UNSIGNED_BYTE;
+ }
+
+ if (!supported)
+ return false;
+
+ init->plane_bytes = (init->plane_bits + 7) / 8;
+ init->is_yuv = dummy_img.flags & MP_IMGFLAG_YUV;
+ init->is_linear_rgb = false;
+
+ // NOTE: we throw away the additional alpha plane, if one exists.
+ init->plane_count = dummy_img.num_planes > 2 ? 3 : 1;
+ assert(dummy_img.num_planes >= init->plane_count);
+ assert(dummy_img.num_planes <= init->plane_count + 1);
+
+ for (int n = 0; n < init->plane_count; n++) {
+ struct texplane *plane = &init->planes[n];
+
+ plane->shift_x = n > 0 ? dummy_img.chroma_x_shift : 0;
+ plane->shift_y = n > 0 ? dummy_img.chroma_y_shift : 0;
+ }
+
+ return true;
+}
+
+static int query_format(uint32_t format)
+{
+ int caps = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_FLIP |
+ VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_ACCEPT_STRIDE |
+ VFCAP_OSD;
+ if (!init_format(format, NULL))
+ return 0;
+ return caps;
+}
+
+static bool create_window(struct gl_priv *p, uint32_t d_width,
+ uint32_t d_height, uint32_t flags)
+{
+ if (p->stereo_mode == GL_3D_QUADBUFFER)
+ flags |= VOFLAG_STEREO;
+
+ if (p->use_gl_debug)
+ flags |= VOFLAG_GL_DEBUG;
+
+ int mpgl_caps = MPGL_CAP_GL21 | MPGL_CAP_TEX_RG;
+ if (!p->allow_sw)
+ mpgl_caps |= MPGL_CAP_NO_SW;
+ return mpgl_create_window(p->glctx, mpgl_caps, d_width, d_height, flags);
+}
+
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ struct gl_priv *p = vo->priv;
+
+ if (!create_window(p, d_width, d_height, flags))
+ return -1;
+
+ if (!p->vertex_buffer)
+ init_gl(p);
+
+ p->vo_flipped = !!(flags & VOFLAG_FLIPPING);
+
+ if (p->image_format != format || p->image_width != width
+ || p->image_height != height)
+ {
+ uninit_video(p);
+ p->image_height = height;
+ p->image_width = width;
+ init_format(format, p);
+ init_video(p);
+ }
+
+ resize(p);
+
+ return 0;
+}
+
+static void check_events(struct vo *vo)
+{
+ struct gl_priv *p = vo->priv;
+
+ int e = p->glctx->check_events(vo);
+ if (e & VO_EVENT_REINIT) {
+ uninit_gl(p);
+ init_gl(p);
+ init_video(p);
+ resize(p);
+ }
+ if (e & VO_EVENT_RESIZE)
+ resize(p);
+ if (e & VO_EVENT_EXPOSE)
+ vo->want_redraw = true;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct gl_priv *p = vo->priv;
+
+ switch (request) {
+ case VOCTRL_QUERY_FORMAT:
+ return query_format(*(uint32_t *)data);
+ case VOCTRL_DRAW_IMAGE:
+ return draw_image(p, data);
+ case VOCTRL_ONTOP:
+ if (!p->glctx->ontop)
+ break;
+ p->glctx->ontop(vo);
+ return VO_TRUE;
+ case VOCTRL_PAUSE:
+ if (!p->glctx->pause)
+ break;
+ p->glctx->pause(vo);
+ return VO_TRUE;
+ case VOCTRL_RESUME:
+ if (!p->glctx->resume)
+ break;
+ p->glctx->resume(vo);
+ return VO_TRUE;
+ case VOCTRL_FULLSCREEN:
+ p->glctx->fullscreen(vo);
+ resize(p);
+ return VO_TRUE;
+ case VOCTRL_BORDER:
+ if (!p->glctx->border)
+ break;
+ p->glctx->border(vo);
+ resize(p);
+ return VO_TRUE;
+ case VOCTRL_GET_PANSCAN:
+ return VO_TRUE;
+ case VOCTRL_SET_PANSCAN:
+ resize(p);
+ return VO_TRUE;
+ case VOCTRL_GET_EQUALIZER: {
+ struct voctrl_get_equalizer_args *args = data;
+ return mp_csp_equalizer_get(&p->video_eq, args->name, args->valueptr)
+ >= 0 ? VO_TRUE : VO_NOTIMPL;
+ }
+ case VOCTRL_SET_EQUALIZER: {
+ struct voctrl_set_equalizer_args *args = data;
+ if (mp_csp_equalizer_set(&p->video_eq, args->name, args->value) < 0)
+ return VO_NOTIMPL;
+ if (!p->use_gamma && p->video_eq.values[MP_CSP_EQ_GAMMA] != 0) {
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Auto-enabling gamma.\n");
+ p->use_gamma = true;
+ compile_shaders(p);
+ }
+ update_all_uniforms(p);
+ vo->want_redraw = true;
+ return VO_TRUE;
+ }
+ case VOCTRL_SET_YUV_COLORSPACE: {
+ if (p->is_yuv) {
+ p->colorspace = *(struct mp_csp_details *)data;
+ update_all_uniforms(p);
+ vo->want_redraw = true;
+ }
+ return VO_TRUE;
+ }
+ case VOCTRL_GET_YUV_COLORSPACE:
+ *(struct mp_csp_details *)data = p->colorspace;
+ return VO_TRUE;
+ case VOCTRL_UPDATE_SCREENINFO:
+ if (!p->glctx->update_xinerama_info)
+ break;
+ p->glctx->update_xinerama_info(vo);
+ return VO_TRUE;
+ case VOCTRL_SCREENSHOT: {
+ struct voctrl_screenshot_args *args = data;
+ if (args->full_window)
+ args->out_image = glGetWindowScreenshot(p->gl);
+ else
+ args->out_image = get_screenshot(p);
+ return true;
+ }
+ case VOCTRL_REDRAW_FRAME:
+ do_render(p);
+ return true;
+ case VOCTRL_SET_COMMAND_LINE: {
+ char *arg = data;
+ if (!reparse_cmdline(p, arg))
+ return false;
+ check_gl_features(p);
+ reinit_rendering(p);
+ resize(p);
+ vo->want_redraw = true;
+ return true;
+ }
+ }
+ return VO_NOTIMPL;
+}
+
+static void uninit(struct vo *vo)
+{
+ struct gl_priv *p = vo->priv;
+
+ uninit_gl(p);
+ mpgl_uninit(p->glctx);
+ p->glctx = NULL;
+ p->gl = NULL;
+}
+
+#ifdef CONFIG_LCMS2
+
+static void lcms2_error_handler(cmsContext ctx, cmsUInt32Number code,
+ const char *msg)
+{
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] lcms2: %s\n", msg);
+}
+
+static struct bstr load_file(struct gl_priv *p, void *talloc_ctx,
+ const char *filename)
+{
+ struct bstr res = {0};
+ stream_t *s = open_stream(filename, p->vo->opts, NULL);
+ if (s) {
+ res = stream_read_complete(s, talloc_ctx, 1000000000, 0);
+ free_stream(s);
+ }
+ return res;
+}
+
+#define LUT3D_CACHE_HEADER "mpv 3dlut cache 1.0\n"
+
+static bool load_icc(struct gl_priv *p, const char *icc_file,
+ const char *icc_cache, int icc_intent,
+ int s_r, int s_g, int s_b)
+{
+ void *tmp = talloc_new(p);
+ uint16_t *output = talloc_array(tmp, uint16_t, s_r * s_g * s_b * 3);
+
+ if (icc_intent == -1)
+ icc_intent = INTENT_ABSOLUTE_COLORIMETRIC;
+
+ mp_msg(MSGT_VO, MSGL_INFO, "[gl] Opening ICC profile '%s'\n", icc_file);
+ struct bstr iccdata = load_file(p, tmp, icc_file);
+ if (!iccdata.len)
+ goto error_exit;
+
+ char *cache_info = talloc_asprintf(tmp, "intent=%d, size=%dx%dx%d\n",
+ icc_intent, s_r, s_g, s_b);
+
+ // check cache
+ if (icc_cache) {
+ mp_msg(MSGT_VO, MSGL_INFO, "[gl] Opening 3D LUT cache in file '%s'.\n",
+ icc_cache);
+ struct bstr cachedata = load_file(p, tmp, icc_cache);
+ if (bstr_eatstart(&cachedata, bstr0(LUT3D_CACHE_HEADER))
+ && bstr_eatstart(&cachedata, bstr0(cache_info))
+ && bstr_eatstart(&cachedata, iccdata)
+ && cachedata.len == talloc_get_size(output))
+ {
+ memcpy(output, cachedata.start, cachedata.len);
+ goto done;
+ } else {
+ mp_msg(MSGT_VO, MSGL_WARN, "[gl] 3D LUT cache invalid!\n");
+ }
+ }
+
+ cmsSetLogErrorHandler(lcms2_error_handler);
+
+ cmsHPROFILE profile = cmsOpenProfileFromMem(iccdata.start, iccdata.len);
+ if (!profile)
+ goto error_exit;
+
+ cmsCIExyY d65;
+ cmsWhitePointFromTemp(&d65, 6504);
+ static const cmsCIExyYTRIPLE bt709prim = {
+ .Red = {0.64, 0.33, 1.0},
+ .Green = {0.30, 0.60, 1.0},
+ .Blue = {0.15, 0.06, 1.0},
+ };
+ cmsToneCurve *tonecurve = cmsBuildGamma(NULL, 2.2);
+ cmsHPROFILE vid_profile = cmsCreateRGBProfile(&d65, &bt709prim,
+ (cmsToneCurve*[3]){tonecurve, tonecurve, tonecurve});
+ cmsFreeToneCurve(tonecurve);
+ cmsHTRANSFORM trafo = cmsCreateTransform(vid_profile, TYPE_RGB_16,
+ profile, TYPE_RGB_16,
+ icc_intent,
+ cmsFLAGS_HIGHRESPRECALC);
+ cmsCloseProfile(profile);
+ cmsCloseProfile(vid_profile);
+
+ if (!trafo)
+ goto error_exit;
+
+ // transform a (s_r)x(s_g)x(s_b) cube, with 3 components per channel
+ uint16_t *input = talloc_array(tmp, uint16_t, s_r * 3);
+ for (int b = 0; b < s_b; b++) {
+ for (int g = 0; g < s_g; g++) {
+ for (int r = 0; r < s_r; r++) {
+ input[r * 3 + 0] = r * 65535 / (s_r - 1);
+ input[r * 3 + 1] = g * 65535 / (s_g - 1);
+ input[r * 3 + 2] = b * 65535 / (s_b - 1);
+ }
+ size_t base = (b * s_r * s_g + g * s_r) * 3;
+ cmsDoTransform(trafo, input, output + base, s_r);
+ }
+ }
+
+ cmsDeleteTransform(trafo);
+
+ if (icc_cache) {
+ FILE *out = fopen(icc_cache, "wb");
+ if (out) {
+ fprintf(out, "%s%s", LUT3D_CACHE_HEADER, cache_info);
+ fwrite(iccdata.start, iccdata.len, 1, out);
+ fwrite(output, talloc_get_size(output), 1, out);
+ fclose(out);
+ }
+ }
+
+done:
+
+ p->lut_3d_data = talloc_steal(p, output);
+ p->lut_3d_w = s_r, p->lut_3d_h = s_g, p->lut_3d_d = s_b;
+ p->use_lut_3d = true;
+
+ talloc_free(tmp);
+ return true;
+
+error_exit:
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Error loading ICC profile.\n");
+ talloc_free(tmp);
+ return false;
+}
+
+#else /* CONFIG_LCMS2 */
+
+static bool load_icc(struct gl_priv *p, ...)
+{
+ mp_msg(MSGT_VO, MSGL_FATAL, "[gl] LCMS2 support not compiled.\n");
+ return false;
+}
+
+#endif /* CONFIG_LCMS2 */
+
+static bool parse_3dlut_size(const char *s, int *p1, int *p2, int *p3)
+{
+ if (sscanf(s, "%dx%dx%d", p1, p2, p3) != 3)
+ return false;
+ for (int n = 0; n < 3; n++) {
+ int s = ((int[]) { *p1, *p2, *p3 })[n];
+ if (s < 2 || s > 256 || ((s - 1) & s))
+ return false;
+ }
+ return true;
+}
+
+static int lut3d_size_valid(void *arg)
+{
+ char *s = *(char **)arg;
+ int p1, p2, p3;
+ return parse_3dlut_size(s, &p1, &p2, &p3);
+}
+
+static int backend_valid(void *arg)
+{
+ return mpgl_find_backend(*(const char **)arg) >= 0;
+}
+
+struct fbo_format {
+ const char *name;
+ GLint format;
+};
+
+const struct fbo_format fbo_formats[] = {
+ {"rgb", GL_RGB},
+ {"rgba", GL_RGBA},
+ {"rgb8", GL_RGB8},
+ {"rgb10", GL_RGB10},
+ {"rgb16", GL_RGB16},
+ {"rgb16f", GL_RGB16F},
+ {"rgb32f", GL_RGB32F},
+ {0}
+};
+
+static GLint find_fbo_format(const char *name)
+{
+ for (const struct fbo_format *fmt = fbo_formats; fmt->name; fmt++) {
+ if (strcmp(fmt->name, name) == 0)
+ return fmt->format;
+ }
+ return -1;
+}
+
+static int fbo_format_valid(void *arg)
+{
+ return find_fbo_format(*(const char **)arg) >= 0;
+}
+
+static bool can_use_filter_kernel(const struct filter_kernel *kernel)
+{
+ if (!kernel)
+ return false;
+ struct filter_kernel k = *kernel;
+ return mp_init_filter(&k, filter_sizes, 1);
+}
+
+static const char* handle_scaler_opt(const char *name)
+{
+ const struct filter_kernel *kernel = mp_find_filter_kernel(name);
+ if (can_use_filter_kernel(kernel))
+ return kernel->name;
+
+ for (const char **filter = fixed_scale_filters; *filter; filter++) {
+ if (strcmp(*filter, name) == 0)
+ return *filter;
+ }
+
+ return NULL;
+}
+
+static int scaler_valid(void *arg)
+{
+ return handle_scaler_opt(*(const char **)arg) != NULL;
+}
+
+#if 0
+static void print_scalers(void)
+{
+ mp_msg(MSGT_VO, MSGL_INFO, "Available scalers:\n");
+ for (const char **e = fixed_scale_filters; *e; e++) {
+ mp_msg(MSGT_VO, MSGL_INFO, " %s\n", *e);
+ }
+ for (const struct filter_kernel *e = mp_filter_kernels; e->name; e++) {
+ if (can_use_filter_kernel(e))
+ mp_msg(MSGT_VO, MSGL_INFO, " %s\n", e->name);
+ }
+}
+#endif
+
+static bool reparse_cmdline(struct gl_priv *p, char *arg)
+{
+ struct gl_priv tmp = *p->defaults;
+ struct gl_priv *opt = &tmp;
+
+ if (strcmp(arg, "-") == 0) {
+ tmp = *p->orig_cmdline;
+ arg = "";
+ }
+
+ char *scalers[2] = {0};
+ char *fbo_format = NULL;
+
+ const opt_t subopts[] = {
+ {"srgb", OPT_ARG_BOOL, &opt->use_srgb},
+ {"pbo", OPT_ARG_BOOL, &opt->use_pbo},
+ {"glfinish", OPT_ARG_BOOL, &opt->use_glFinish},
+ {"swapinterval", OPT_ARG_INT, &opt->swap_interval},
+ {"osdcolor", OPT_ARG_INT, &opt->osd_color},
+ {"lscale", OPT_ARG_MSTRZ, &scalers[0], scaler_valid},
+ {"cscale", OPT_ARG_MSTRZ, &scalers[1], scaler_valid},
+ {"lparam1", OPT_ARG_FLOAT, &opt->scaler_params[0]},
+ {"lparam2", OPT_ARG_FLOAT, &opt->scaler_params[1]},
+ {"fancy-downscaling", OPT_ARG_BOOL, &opt->use_fancy_downscaling},
+ {"indirect", OPT_ARG_BOOL, &opt->use_indirect},
+ {"scale-sep", OPT_ARG_BOOL, &opt->use_scale_sep},
+ {"fbo-format", OPT_ARG_MSTRZ, &fbo_format, fbo_format_valid},
+ {"dither-depth", OPT_ARG_INT, &opt->dither_depth},
+ {NULL}
+ };
+
+ if (subopt_parse(arg, subopts) != 0)
+ return false;
+
+ p->fbo_format = opt->fbo_format;
+ if (fbo_format)
+ p->fbo_format = find_fbo_format(fbo_format);
+ free(fbo_format);
+
+ for (int n = 0; n < 2; n++) {
+ p->scalers[n].name = opt->scalers[n].name;
+ if (scalers[n])
+ p->scalers[n].name = handle_scaler_opt(scalers[n]);
+ free(scalers[n]);
+ }
+
+ // xxx ideally we'd put all options into an option struct, and just copy
+ p->use_srgb = opt->use_srgb; //xxx changing srgb will be wrong on RGB input!
+ p->use_pbo = opt->use_pbo;
+ p->use_glFinish = opt->use_glFinish;
+ p->swap_interval = opt->swap_interval;
+ p->osd_color = opt->osd_color;
+ memcpy(p->scaler_params, opt->scaler_params, sizeof(p->scaler_params));
+ p->use_fancy_downscaling = opt->use_fancy_downscaling;
+ p->use_indirect = opt->use_indirect;
+ p->use_scale_sep = opt->use_scale_sep;
+ p->dither_depth = opt->dither_depth;
+
+ check_gl_features(p);
+
+ return true;
+}
+
+static int preinit(struct vo *vo, const char *arg)
+{
+ struct gl_priv *p = talloc_zero(vo, struct gl_priv);
+ vo->priv = p;
+
+ bool hq = strcmp(vo->driver->info->short_name, "opengl-hq") == 0;
+
+ *p = (struct gl_priv) {
+ .vo = vo,
+ .colorspace = MP_CSP_DETAILS_DEFAULTS,
+ .use_npot = 1,
+ .use_pbo = hq,
+ .swap_interval = vo_vsync,
+ .osd_color = 0xffffff,
+ .dither_depth = hq ? 0 : -1,
+ .fbo_format = hq ? GL_RGB16 : GL_RGB,
+ .use_scale_sep = 1,
+ .scalers = {
+ { .index = 0, .name = hq ? "lanczos2" : "bilinear" },
+ { .index = 1, .name = "bilinear" },
+ },
+ .scaler_params = {NAN, NAN},
+ .scratch = talloc_zero_array(p, char *, 1),
+ };
+
+ p->defaults = talloc(p, struct gl_priv);
+ *p->defaults = *p;
+
+ char *scalers[2] = {0};
+ char *backend_arg = NULL;
+ char *fbo_format = NULL;
+ char *icc_profile = NULL;
+ char *icc_cache = NULL;
+ int icc_intent = -1;
+ char *icc_size_str = NULL;
+
+ const opt_t subopts[] = {
+ {"gamma", OPT_ARG_BOOL, &p->use_gamma},
+ {"srgb", OPT_ARG_BOOL, &p->use_srgb},
+ {"npot", OPT_ARG_BOOL, &p->use_npot},
+ {"pbo", OPT_ARG_BOOL, &p->use_pbo},
+ {"glfinish", OPT_ARG_BOOL, &p->use_glFinish},
+ {"swapinterval", OPT_ARG_INT, &p->swap_interval},
+ {"osdcolor", OPT_ARG_INT, &p->osd_color},
+ {"stereo", OPT_ARG_INT, &p->stereo_mode},
+ {"lscale", OPT_ARG_MSTRZ, &scalers[0], scaler_valid},
+ {"cscale", OPT_ARG_MSTRZ, &scalers[1], scaler_valid},
+ {"lparam1", OPT_ARG_FLOAT, &p->scaler_params[0]},
+ {"lparam2", OPT_ARG_FLOAT, &p->scaler_params[1]},
+ {"fancy-downscaling", OPT_ARG_BOOL, &p->use_fancy_downscaling},
+ {"debug", OPT_ARG_BOOL, &p->use_gl_debug},
+ {"indirect", OPT_ARG_BOOL, &p->use_indirect},
+ {"scale-sep", OPT_ARG_BOOL, &p->use_scale_sep},
+ {"fbo-format", OPT_ARG_MSTRZ, &fbo_format, fbo_format_valid},
+ {"backend", OPT_ARG_MSTRZ, &backend_arg, backend_valid},
+ {"sw", OPT_ARG_BOOL, &p->allow_sw},
+ {"icc-profile", OPT_ARG_MSTRZ, &icc_profile},
+ {"icc-cache", OPT_ARG_MSTRZ, &icc_cache},
+ {"icc-intent", OPT_ARG_INT, &icc_intent},
+ {"3dlut-size", OPT_ARG_MSTRZ, &icc_size_str,
+ lut3d_size_valid},
+ {"dither-depth", OPT_ARG_INT, &p->dither_depth},
+ {NULL}
+ };
+
+ if (subopt_parse(arg, subopts) != 0) {
+ mp_msg(MSGT_VO, MSGL_FATAL, help_text);
+ goto err_out;
+ }
+
+ int backend = backend_arg ? mpgl_find_backend(backend_arg) : GLTYPE_AUTO;
+ free(backend_arg);
+
+ if (fbo_format)
+ p->fbo_format = find_fbo_format(fbo_format);
+ free(fbo_format);
+
+ for (int n = 0; n < 2; n++) {
+ if (scalers[n])
+ p->scalers[n].name = handle_scaler_opt(scalers[n]);
+ free(scalers[n]);
+ }
+
+ int s_r = 128, s_g = 256, s_b = 64;
+ if (icc_size_str)
+ parse_3dlut_size(icc_size_str, &s_r, &s_g, &s_b);
+ free(icc_size_str);
+
+ bool success = true;
+ if (icc_profile) {
+ success = load_icc(p, icc_profile, icc_cache, icc_intent,
+ s_r, s_g, s_b);
+ }
+ free(icc_profile);
+ free(icc_cache);
+
+ if (!success)
+ goto err_out;
+
+ p->orig_cmdline = talloc(p, struct gl_priv);
+ *p->orig_cmdline = *p;
+
+ p->glctx = mpgl_init(backend, vo);
+ if (!p->glctx)
+ goto err_out;
+ p->gl = p->glctx->gl;
+
+ if (!create_window(p, 320, 200, VOFLAG_HIDDEN))
+ goto err_out;
+ check_gl_features(p);
+ // We created a window to test whether the GL context could be
+ // created and so on. Destroy that window to make sure all state
+ // associated with it is lost.
+ uninit_gl(p);
+ if (!mpgl_destroy_window(p->glctx))
+ goto err_out;
+
+ return 0;
+
+err_out:
+ uninit(vo);
+ return -1;
+}
+
+const struct vo_driver video_out_opengl = {
+ .is_new = true,
+ .info = &(const vo_info_t) {
+ "Extended OpenGL Renderer",
+ "opengl",
+ "Based on vo_gl.c by Reimar Doeffinger",
+ ""
+ },
+ .preinit = preinit,
+ .config = config,
+ .control = control,
+ .draw_slice = draw_slice,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+ .check_events = check_events,
+ .uninit = uninit,
+};
+
+const struct vo_driver video_out_opengl_hq = {
+ .is_new = true,
+ .info = &(const vo_info_t) {
+ "Extended OpenGL Renderer (high quality rendering preset)",
+ "opengl-hq",
+ "Based on vo_gl.c by Reimar Doeffinger",
+ ""
+ },
+ .preinit = preinit,
+ .config = config,
+ .control = control,
+ .draw_slice = draw_slice,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+ .check_events = check_events,
+ .uninit = uninit,
+};
+
+static const char help_text[] =
+"\n--vo=opengl command line help:\n"
+"Example: mpv --vo=opengl:scale-sep:lscale=lanczos2\n"
+"\nOptions:\n"
+" lscale=<filter>\n"
+" Set the scaling filter. Possible choices:\n"
+" bilinear: bilinear texture filtering (fastest).\n"
+" bicubic_fast: bicubic filter (without lookup texture).\n"
+" sharpen3: unsharp masking (sharpening) with radius=3.\n"
+" sharpen5: unsharp masking (sharpening) with radius=5.\n"
+" lanczos2: Lanczos with radius=2 (recommended).\n"
+" lanczos3: Lanczos with radius=3 (not recommended).\n"
+" mitchell: Mitchell-Netravali.\n"
+" Default: bilinear\n"
+" lparam1=<value> / lparam2=<value>\n"
+" Set parameters for configurable filters. Affects chroma scaler\n"
+" as well.\n"
+" Filters which use this:\n"
+" mitchell: b and c params (defaults: b=1/3 c=1/3)\n"
+" kaiser: (defaults: 6.33 6.33)\n"
+" sharpen3: lparam1 sets sharpening strength (default: 0.5)\n"
+" sharpen5: as with sharpen3\n"
+" osdcolor=<0xAARRGGBB>\n"
+" Use the given color for the OSD.\n"
+" stereo=<n>\n"
+" 0: normal display\n"
+" 1: side-by-side to red-cyan stereo\n"
+" 2: side-by-side to green-magenta stereo\n"
+" 3: side-by-side to quadbuffer stereo\n"
+" srgb\n"
+" Enable gamma-correct scaling by working in linear light. This\n"
+" makes use of sRGB textures and framebuffers.\n"
+" This option forces the options 'indirect' and 'gamma'.\n"
+" NOTE: For YUV colorspaces, gamma 2.2 is assumed. RGB input is always\n"
+" assumed to be in sRGB.\n"
+" pbo\n"
+" Enable use of PBOs. This is faster, but can sometimes lead to\n"
+" sporadic and temporary image corruption.\n"
+" dither-depth=<n>\n"
+" Positive non-zero values select the target bit depth.\n"
+" -1: Disable any dithering done by mpv.\n"
+" 0: Automatic selection. If output bit depth can't be detected,\n"
+" 8 bits per component are assumed.\n"
+" 8: Dither to 8 bit output.\n"
+" Default: -1.\n"
+" Note that dithering will always be disabled if the bit depth\n"
+" of the video is lower or qual to the detected dither-depth.\n"
+" If color management is enabled, input depth is assumed to be\n"
+" 16 bits, because the 3D LUT output is 16 bit wide.\n"
+" debug\n"
+" Check for OpenGL errors, i.e. call glGetError(). Also request a\n"
+" debug OpenGL context.\n"
+"Less useful options:\n"
+" swapinterval=<n>\n"
+" Interval in displayed frames between to buffer swaps.\n"
+" 1 is equivalent to enable VSYNC, 0 to disable VSYNC.\n"
+" no-scale-sep\n"
+" When using a separable scale filter for luma, usually two filter\n"
+" passes are done. This is often faster. However, it forces\n"
+" conversion to RGB in an extra pass, so it can actually be slower\n"
+" if used with fast filters on small screen resolutions. Using\n"
+" this options will make rendering a single operation.\n"
+" Note that chroma scalers are always done as 1-pass filters.\n"
+" cscale=<n>\n"
+" As lscale but for chroma (2x slower with little visible effect).\n"
+" Note that with some scaling filters, upscaling is always done in\n"
+" RGB. If chroma is not subsampled, this option is ignored, and the\n"
+" luma scaler is used instead. Setting this option is often useless.\n"
+" fancy-downscaling\n"
+" When using convolution based filters, extend the filter size\n"
+" when downscaling. Trades quality for reduced downscaling performance.\n"
+" no-npot\n"
+" Force use of power-of-2 texture sizes. For debugging only.\n"
+" Borders will look discolored due to filtering.\n"
+" glfinish\n"
+" Call glFinish() before swapping buffers\n"
+" backend=<sys>\n"
+" auto: auto-select (default)\n"
+" cocoa: Cocoa/OSX\n"
+" win: Win32/WGL\n"
+" x11: X11/GLX\n"
+" indirect\n"
+" Do YUV conversion and scaling as separate passes. This will\n"
+" first render the video into a video-sized RGB texture, and\n"
+" draw the result on screen. The luma scaler is used to scale\n"
+" the RGB image when rendering to screen. The chroma scaler\n"
+" is used only on YUV conversion, and only if the video uses\n"
+" chroma-subsampling.\n"
+" This mechanism is disabled on RGB input.\n"
+" fbo-format=<fmt>\n"
+" Selects the internal format of any FBO textures used.\n"
+" fmt can be one of: rgb, rgba, rgb8, rgb10, rgb16, rgb16f, rgb32f\n"
+" Default: rgb.\n"
+" gamma\n"
+" Always enable gamma control. (Disables delayed enabling.)\n"
+"Color management:\n"
+" icc-profile=<file>\n"
+" Load an ICC profile and use it to transform linear RGB to\n"
+" screen output. Needs LittleCMS2 support compiled in.\n"
+" icc-cache=<file>\n"
+" Store and load the 3D LUT created from the ICC profile in\n"
+" this file. This can be used to speed up loading, since\n"
+" LittleCMS2 can take a while to create the 3D LUT.\n"
+" Note that this file will be up to ~100 MB big.\n"
+" icc-intent=<value>\n"
+" 0: perceptual\n"
+" 1: relative colorimetric\n"
+" 2: saturation\n"
+" 3: absolute colorimetric (default)\n"
+" 3dlut-size=<r>x<g>x<b>\n"
+" Size of the 3D LUT generated from the ICC profile in each\n"
+" dimension. Default is 128x256x64.\n"
+" Sizes must be a power of two, and 256 at most.\n"
+"Note: all defaults mentioned are for 'opengl', not 'opengl-hq'.\n"
+"\n";
diff --git a/video/out/vo_opengl_old.c b/video/out/vo_opengl_old.c
new file mode 100644
index 0000000000..b8b1fd4813
--- /dev/null
+++ b/video/out/vo_opengl_old.c
@@ -0,0 +1,1166 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * You can alternatively redistribute this file 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdbool.h>
+#include <assert.h>
+
+#include "config.h"
+#include "talloc.h"
+#include "mp_msg.h"
+#include "subopt-helper.h"
+#include "video_out.h"
+#include "libmpcodecs/vfcap.h"
+#include "libmpcodecs/mp_image.h"
+#include "geometry.h"
+#include "sub/sub.h"
+
+#include "gl_common.h"
+#include "gl_osd.h"
+#include "aspect.h"
+#include "fastmemcpy.h"
+
+//for gl_priv.use_yuv
+#define MASK_ALL_YUV (~(1 << YUV_CONVERSION_NONE))
+#define MASK_NOT_COMBINERS (~((1 << YUV_CONVERSION_NONE) | (1 << YUV_CONVERSION_COMBINERS)))
+#define MASK_GAMMA_SUPPORT (MASK_NOT_COMBINERS & ~(1 << YUV_CONVERSION_FRAGMENT))
+
+struct gl_priv {
+ MPGLContext *glctx;
+ GL *gl;
+
+ int allow_sw;
+
+ int use_osd;
+ int scaled_osd;
+ struct mpgl_osd *osd;
+ int osd_color;
+
+ int use_ycbcr;
+ int use_yuv;
+ struct mp_csp_details colorspace;
+ int is_yuv;
+ int lscale;
+ int cscale;
+ float filter_strength;
+ float noise_strength;
+ int yuvconvtype;
+ int use_rectangle;
+ int err_shown;
+ uint32_t image_width;
+ uint32_t image_height;
+ uint32_t image_format;
+ int many_fmts;
+ int have_texture_rg;
+ int ati_hack;
+ int force_pbo;
+ int use_glFinish;
+ int swap_interval;
+ GLenum target;
+ GLint texfmt;
+ GLenum gl_format;
+ GLenum gl_type;
+ GLuint buffer;
+ GLuint buffer_uv[2];
+ int buffersize;
+ int buffersize_uv;
+ void *bufferptr;
+ void *bufferptr_uv[2];
+ GLuint fragprog;
+ GLuint default_texs[22];
+ char *custom_prog;
+ char *custom_tex;
+ int custom_tlin;
+ int custom_trect;
+ int mipmap_gen;
+ int stereo_mode;
+
+ struct mp_csp_equalizer video_eq;
+
+ int texture_width;
+ int texture_height;
+ int mpi_flipped;
+ int vo_flipped;
+ int ass_border_x, ass_border_y;
+
+ unsigned int slice_height;
+};
+
+static void resize(struct vo *vo, int x, int y)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Resize: %dx%d\n", x, y);
+ gl->Viewport(0, 0, x, y);
+
+ gl->MatrixMode(GL_PROJECTION);
+ gl->LoadIdentity();
+ p->ass_border_x = p->ass_border_y = 0;
+ if (aspect_scaling()) {
+ int new_w, new_h;
+ GLdouble scale_x, scale_y;
+ aspect(vo, &new_w, &new_h, A_WINZOOM);
+ panscan_calc_windowed(vo);
+ new_w += vo->panscan_x;
+ new_h += vo->panscan_y;
+ scale_x = (GLdouble)new_w / (GLdouble)x;
+ scale_y = (GLdouble)new_h / (GLdouble)y;
+ gl->Scaled(scale_x, scale_y, 1);
+ p->ass_border_x = (vo->dwidth - new_w) / 2;
+ p->ass_border_y = (vo->dheight - new_h) / 2;
+ }
+ gl->Ortho(0, p->image_width, p->image_height, 0, -1, 1);
+
+ gl->MatrixMode(GL_MODELVIEW);
+ gl->LoadIdentity();
+
+ gl->Clear(GL_COLOR_BUFFER_BIT);
+ vo->want_redraw = true;
+}
+
+static void texSize(struct vo *vo, int w, int h, int *texw, int *texh)
+{
+ struct gl_priv *p = vo->priv;
+
+ if (p->use_rectangle) {
+ *texw = w;
+ *texh = h;
+ } else {
+ *texw = 32;
+ while (*texw < w)
+ *texw *= 2;
+ *texh = 32;
+ while (*texh < h)
+ *texh *= 2;
+ }
+ if (p->ati_hack)
+ *texw = (*texw + 511) & ~511;
+}
+
+//! maximum size of custom fragment program
+#define MAX_CUSTOM_PROG_SIZE (1024 * 1024)
+static void update_yuvconv(struct vo *vo)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ int xs, ys, depth;
+ struct mp_csp_params cparams = { .colorspace = p->colorspace };
+ mp_csp_copy_equalizer_values(&cparams, &p->video_eq);
+ gl_conversion_params_t params = {
+ p->target, p->yuvconvtype, cparams,
+ p->texture_width, p->texture_height, 0, 0, p->filter_strength,
+ p->noise_strength
+ };
+ mp_get_chroma_shift(p->image_format, &xs, &ys, &depth);
+ params.chrom_texw = params.texw >> xs;
+ params.chrom_texh = params.texh >> ys;
+ params.csp_params.input_bits = depth;
+ params.csp_params.texture_bits = depth+7 & ~7;
+ glSetupYUVConversion(gl, &params);
+ if (p->custom_prog) {
+ FILE *f = fopen(p->custom_prog, "rb");
+ if (!f) {
+ mp_msg(MSGT_VO, MSGL_WARN,
+ "[gl] Could not read customprog %s\n", p->custom_prog);
+ } else {
+ char *prog = calloc(1, MAX_CUSTOM_PROG_SIZE + 1);
+ fread(prog, 1, MAX_CUSTOM_PROG_SIZE, f);
+ fclose(f);
+ loadGPUProgram(gl, GL_FRAGMENT_PROGRAM, prog);
+ free(prog);
+ }
+ gl->ProgramEnvParameter4f(GL_FRAGMENT_PROGRAM, 0,
+ 1.0 / p->texture_width,
+ 1.0 / p->texture_height,
+ p->texture_width, p->texture_height);
+ }
+ if (p->custom_tex) {
+ FILE *f = fopen(p->custom_tex, "rb");
+ if (!f) {
+ mp_msg(MSGT_VO, MSGL_WARN,
+ "[gl] Could not read customtex %s\n", p->custom_tex);
+ } else {
+ int width, height, maxval;
+ gl->ActiveTexture(GL_TEXTURE3);
+ if (glCreatePPMTex(gl, p->custom_trect ? GL_TEXTURE_RECTANGLE : GL_TEXTURE_2D,
+ 0, p->custom_tlin ? GL_LINEAR : GL_NEAREST,
+ f, &width, &height, &maxval)) {
+ gl->ProgramEnvParameter4f(GL_FRAGMENT_PROGRAM, 1,
+ 1.0 / width, 1.0 / height,
+ width, height);
+ } else
+ mp_msg(MSGT_VO, MSGL_WARN,
+ "[gl] Error parsing customtex %s\n", p->custom_tex);
+ fclose(f);
+ gl->ActiveTexture(GL_TEXTURE0);
+ }
+ }
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+ assert(p->osd);
+
+ if (!p->use_osd)
+ return;
+
+ if (!p->scaled_osd) {
+ gl->MatrixMode(GL_PROJECTION);
+ gl->PushMatrix();
+ gl->LoadIdentity();
+ gl->Ortho(0, vo->dwidth, vo->dheight, 0, -1, 1);
+ }
+
+ gl->Color4ub((p->osd_color >> 16) & 0xff, (p->osd_color >> 8) & 0xff,
+ p->osd_color & 0xff, 0xff - (p->osd_color >> 24));
+
+ struct mp_osd_res res = {
+ .w = vo->dwidth,
+ .h = vo->dheight,
+ .display_par = vo->monitor_par,
+ .video_par = vo->aspdat.par,
+ };
+ if (p->scaled_osd) {
+ res.w = p->image_width;
+ res.h = p->image_height;
+ } else if (aspect_scaling()) {
+ res.ml = res.mr = p->ass_border_x;
+ res.mt = res.mb = p->ass_border_y;
+ }
+
+ mpgl_osd_draw_legacy(p->osd, osd, res);
+
+ if (!p->scaled_osd)
+ gl->PopMatrix();
+}
+
+/**
+ * \brief uninitialize OpenGL context, freeing textures, buffers etc.
+ */
+static void uninitGl(struct vo *vo)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ if (!gl)
+ return;
+
+ int i = 0;
+ if (gl->DeletePrograms && p->fragprog)
+ gl->DeletePrograms(1, &p->fragprog);
+ p->fragprog = 0;
+ while (p->default_texs[i] != 0)
+ i++;
+ if (i)
+ gl->DeleteTextures(i, p->default_texs);
+ p->default_texs[0] = 0;
+ if (p->osd)
+ mpgl_osd_destroy(p->osd);
+ p->osd = NULL;
+ p->buffer = 0;
+ p->buffersize = 0;
+ p->bufferptr = NULL;
+ if (gl->DeleteBuffers && p->buffer_uv[0])
+ gl->DeleteBuffers(2, p->buffer_uv);
+ p->buffer_uv[0] = p->buffer_uv[1] = 0;
+ p->buffersize_uv = 0;
+ p->bufferptr_uv[0] = p->bufferptr_uv[1] = 0;
+ p->err_shown = 0;
+}
+
+static void autodetectGlExtensions(struct vo *vo)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ const char *extensions = gl->GetString(GL_EXTENSIONS);
+ const char *vendor = gl->GetString(GL_VENDOR);
+ const char *version = gl->GetString(GL_VERSION);
+ const char *renderer = gl->GetString(GL_RENDERER);
+ int is_ati = vendor && strstr(vendor, "ATI") != NULL;
+ int ati_broken_pbo = 0;
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Running on OpenGL '%s' by '%s', version '%s'\n",
+ renderer, vendor, version);
+ if (is_ati && strncmp(version, "2.1.", 4) == 0) {
+ int ver = atoi(version + 4);
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Detected ATI driver version: %i\n", ver);
+ ati_broken_pbo = ver && ver < 8395;
+ }
+ if (p->ati_hack == -1)
+ p->ati_hack = ati_broken_pbo;
+ if (p->force_pbo == -1) {
+ p->force_pbo = 0;
+ if (extensions && strstr(extensions, "_pixel_buffer_object"))
+ p->force_pbo = is_ati;
+ }
+ p->have_texture_rg = extensions && strstr(extensions, "GL_ARB_texture_rg");
+ if (p->use_rectangle == -1) {
+ p->use_rectangle = 0;
+ if (extensions) {
+// if (strstr(extensions, "_texture_non_power_of_two"))
+ if (strstr(extensions, "_texture_rectangle"))
+ p->use_rectangle = renderer
+ && strstr(renderer, "Mesa DRI R200") ? 1 : 0;
+ }
+ }
+ if (p->use_osd == -1)
+ p->use_osd = gl->BindTexture != NULL;
+ if (p->use_yuv == -1)
+ p->use_yuv = glAutodetectYUVConversion(gl);
+
+ int eq_caps = 0;
+ int yuv_mask = (1 << p->use_yuv);
+ if (!(yuv_mask & MASK_NOT_COMBINERS)) {
+ // combiners
+ eq_caps = (1 << MP_CSP_EQ_HUE) | (1 << MP_CSP_EQ_SATURATION);
+ } else if (yuv_mask & MASK_ALL_YUV) {
+ eq_caps = MP_CSP_EQ_CAPS_COLORMATRIX;
+ if (yuv_mask & MASK_GAMMA_SUPPORT)
+ eq_caps |= MP_CSP_EQ_CAPS_GAMMA;
+ }
+ p->video_eq.capabilities = eq_caps;
+
+ if (is_ati && (p->lscale == 1 || p->lscale == 2 || p->cscale == 1 || p->cscale == 2))
+ mp_msg(MSGT_VO, MSGL_WARN, "[gl] Selected scaling mode may be broken on"
+ " ATI cards.\n"
+ "Tell _them_ to fix GL_REPEAT if you have issues.\n");
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Settings after autodetection: ati-hack = %i, "
+ "force-pbo = %i, rectangle = %i, yuv = %i\n",
+ p->ati_hack, p->force_pbo, p->use_rectangle, p->use_yuv);
+}
+
+static GLint get_scale_type(struct vo *vo, int chroma)
+{
+ struct gl_priv *p = vo->priv;
+
+ int nearest = (chroma ? p->cscale : p->lscale) & 64;
+ if (nearest)
+ return p->mipmap_gen ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST;
+ return p->mipmap_gen ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR;
+}
+
+// Return the high byte of the value that represents white in chroma (U/V)
+static int get_chroma_clear_val(int bit_depth)
+{
+ return 1 << (bit_depth - 1 & 7);
+}
+
+/**
+ * \brief Initialize a (new or reused) OpenGL context.
+ * set global gl-related variables to their default values
+ */
+static int initGl(struct vo *vo, uint32_t d_width, uint32_t d_height)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ GLint scale_type = get_scale_type(vo, 0);
+ autodetectGlExtensions(vo);
+ p->target = p->use_rectangle == 1 ? GL_TEXTURE_RECTANGLE : GL_TEXTURE_2D;
+ p->yuvconvtype = SET_YUV_CONVERSION(p->use_yuv) |
+ SET_YUV_LUM_SCALER(p->lscale) |
+ SET_YUV_CHROM_SCALER(p->cscale);
+
+ texSize(vo, p->image_width, p->image_height,
+ &p->texture_width, &p->texture_height);
+
+ gl->Disable(GL_BLEND);
+ gl->Disable(GL_DEPTH_TEST);
+ gl->DepthMask(GL_FALSE);
+ gl->Disable(GL_CULL_FACE);
+ gl->Enable(p->target);
+ gl->DrawBuffer(GL_BACK);
+ gl->TexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Creating %dx%d texture...\n",
+ p->texture_width, p->texture_height);
+
+ glCreateClearTex(gl, p->target, p->texfmt, p->gl_format,
+ p->gl_type, scale_type,
+ p->texture_width, p->texture_height, 0);
+
+ if (p->mipmap_gen)
+ gl->TexParameteri(p->target, GL_GENERATE_MIPMAP, GL_TRUE);
+
+ if (p->is_yuv) {
+ int i;
+ int xs, ys, depth;
+ scale_type = get_scale_type(vo, 1);
+ mp_get_chroma_shift(p->image_format, &xs, &ys, &depth);
+ int clear = get_chroma_clear_val(depth);
+ gl->GenTextures(21, p->default_texs);
+ p->default_texs[21] = 0;
+ for (i = 0; i < 7; i++) {
+ gl->ActiveTexture(GL_TEXTURE1 + i);
+ gl->BindTexture(GL_TEXTURE_2D, p->default_texs[i]);
+ gl->BindTexture(GL_TEXTURE_RECTANGLE, p->default_texs[i + 7]);
+ gl->BindTexture(GL_TEXTURE_3D, p->default_texs[i + 14]);
+ }
+ gl->ActiveTexture(GL_TEXTURE1);
+ glCreateClearTex(gl, p->target, p->texfmt, p->gl_format,
+ p->gl_type, scale_type,
+ p->texture_width >> xs, p->texture_height >> ys,
+ clear);
+ if (p->mipmap_gen)
+ gl->TexParameteri(p->target, GL_GENERATE_MIPMAP, GL_TRUE);
+ gl->ActiveTexture(GL_TEXTURE2);
+ glCreateClearTex(gl, p->target, p->texfmt, p->gl_format,
+ p->gl_type, scale_type,
+ p->texture_width >> xs, p->texture_height >> ys,
+ clear);
+ if (p->mipmap_gen)
+ gl->TexParameteri(p->target, GL_GENERATE_MIPMAP, GL_TRUE);
+ gl->ActiveTexture(GL_TEXTURE0);
+ gl->BindTexture(p->target, 0);
+ }
+ if (p->is_yuv || p->custom_prog) {
+ if ((MASK_NOT_COMBINERS & (1 << p->use_yuv)) || p->custom_prog) {
+ if (!gl->GenPrograms || !gl->BindProgram)
+ mp_msg(MSGT_VO, MSGL_ERR,
+ "[gl] fragment program functions missing!\n");
+ else {
+ gl->GenPrograms(1, &p->fragprog);
+ gl->BindProgram(GL_FRAGMENT_PROGRAM, p->fragprog);
+ }
+ }
+ update_yuvconv(vo);
+ }
+
+ p->osd = mpgl_osd_init(gl, true);
+ p->osd->scaled = p->scaled_osd;
+
+ resize(vo, d_width, d_height);
+
+ gl->ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ gl->Clear(GL_COLOR_BUFFER_BIT);
+ if (gl->SwapInterval && p->swap_interval >= 0)
+ gl->SwapInterval(p->swap_interval);
+ return 1;
+}
+
+static bool create_window(struct vo *vo, uint32_t d_width, uint32_t d_height,
+ uint32_t flags)
+{
+ struct gl_priv *p = vo->priv;
+
+ if (p->stereo_mode == GL_3D_QUADBUFFER)
+ flags |= VOFLAG_STEREO;
+
+ int mpgl_caps = MPGL_CAP_GL_LEGACY;
+ if (!p->allow_sw)
+ mpgl_caps |= MPGL_CAP_NO_SW;
+ return mpgl_create_window(p->glctx, mpgl_caps, d_width, d_height, flags);
+}
+
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ struct gl_priv *p = vo->priv;
+
+ int xs, ys;
+ p->image_height = height;
+ p->image_width = width;
+ p->image_format = format;
+ p->is_yuv = mp_get_chroma_shift(p->image_format, &xs, &ys, NULL) > 0;
+ p->is_yuv |= (xs << 8) | (ys << 16);
+ glFindFormat(format, p->have_texture_rg, NULL, &p->texfmt, &p->gl_format,
+ &p->gl_type);
+
+ p->vo_flipped = !!(flags & VOFLAG_FLIPPING);
+
+ if (vo->config_count)
+ uninitGl(vo);
+
+ if (!create_window(vo, d_width, d_height, flags))
+ return -1;
+
+ initGl(vo, vo->dwidth, vo->dheight);
+
+ return 0;
+}
+
+static void check_events(struct vo *vo)
+{
+ struct gl_priv *p = vo->priv;
+
+ int e = p->glctx->check_events(vo);
+ if (e & VO_EVENT_REINIT) {
+ uninitGl(vo);
+ initGl(vo, vo->dwidth, vo->dheight);
+ }
+ if (e & VO_EVENT_RESIZE)
+ resize(vo, vo->dwidth, vo->dheight);
+ if (e & VO_EVENT_EXPOSE)
+ vo->want_redraw = true;
+}
+
+static void do_render(struct vo *vo)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+// Enable(GL_TEXTURE_2D);
+// BindTexture(GL_TEXTURE_2D, texture_id);
+
+ gl->Color4f(1, 1, 1, 1);
+ if (p->is_yuv || p->custom_prog)
+ glEnableYUVConversion(gl, p->target, p->yuvconvtype);
+ if (p->stereo_mode) {
+ glEnable3DLeft(gl, p->stereo_mode);
+ glDrawTex(gl, 0, 0, p->image_width, p->image_height,
+ 0, 0, p->image_width >> 1, p->image_height,
+ p->texture_width, p->texture_height,
+ p->use_rectangle == 1, p->is_yuv,
+ p->mpi_flipped ^ p->vo_flipped);
+ glEnable3DRight(gl, p->stereo_mode);
+ glDrawTex(gl, 0, 0, p->image_width, p->image_height,
+ p->image_width >> 1, 0, p->image_width >> 1,
+ p->image_height, p->texture_width, p->texture_height,
+ p->use_rectangle == 1, p->is_yuv,
+ p->mpi_flipped ^ p->vo_flipped);
+ glDisable3D(gl, p->stereo_mode);
+ } else {
+ glDrawTex(gl, 0, 0, p->image_width, p->image_height,
+ 0, 0, p->image_width, p->image_height,
+ p->texture_width, p->texture_height,
+ p->use_rectangle == 1, p->is_yuv,
+ p->mpi_flipped ^ p->vo_flipped);
+ }
+ if (p->is_yuv || p->custom_prog)
+ glDisableYUVConversion(gl, p->target, p->yuvconvtype);
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ if (p->use_glFinish)
+ gl->Finish();
+ p->glctx->swapGlBuffers(p->glctx);
+ if (aspect_scaling())
+ gl->Clear(GL_COLOR_BUFFER_BIT);
+}
+
+static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h,
+ int x, int y)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ p->mpi_flipped = stride[0] < 0;
+ glUploadTex(gl, p->target, p->gl_format, p->gl_type, src[0], stride[0],
+ x, y, w, h, p->slice_height);
+ if (p->is_yuv) {
+ int xs, ys;
+ mp_get_chroma_shift(p->image_format, &xs, &ys, NULL);
+ gl->ActiveTexture(GL_TEXTURE1);
+ glUploadTex(gl, p->target, p->gl_format, p->gl_type, src[1], stride[1],
+ x >> xs, y >> ys, w >> xs, h >> ys, p->slice_height);
+ gl->ActiveTexture(GL_TEXTURE2);
+ glUploadTex(gl, p->target, p->gl_format, p->gl_type, src[2], stride[2],
+ x >> xs, y >> ys, w >> xs, h >> ys, p->slice_height);
+ gl->ActiveTexture(GL_TEXTURE0);
+ }
+ return 0;
+}
+
+static uint32_t get_image(struct vo *vo, mp_image_t *mpi)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ int needed_size;
+ if (!gl->GenBuffers || !gl->BindBuffer || !gl->BufferData || !gl->MapBuffer) {
+ if (!p->err_shown)
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] extensions missing for dr\n"
+ "Expect a _major_ speed penalty\n");
+ p->err_shown = 1;
+ return VO_FALSE;
+ }
+ if (mpi->flags & MP_IMGFLAG_READABLE)
+ return VO_FALSE;
+ if (mpi->type != MP_IMGTYPE_STATIC && mpi->type != MP_IMGTYPE_TEMP &&
+ (mpi->type != MP_IMGTYPE_NUMBERED || mpi->number))
+ return VO_FALSE;
+ if (p->ati_hack) {
+ mpi->width = p->texture_width;
+ mpi->height = p->texture_height;
+ }
+ mpi->stride[0] = mpi->width * mpi->bpp / 8;
+ needed_size = mpi->stride[0] * mpi->height;
+ if (!p->buffer)
+ gl->GenBuffers(1, &p->buffer);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer);
+ if (needed_size > p->buffersize) {
+ p->buffersize = needed_size;
+ gl->BufferData(GL_PIXEL_UNPACK_BUFFER, p->buffersize,
+ NULL, GL_DYNAMIC_DRAW);
+ }
+ if (!p->bufferptr)
+ p->bufferptr = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
+ mpi->planes[0] = p->bufferptr;
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ if (!mpi->planes[0]) {
+ if (!p->err_shown)
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] could not acquire buffer for dr\n"
+ "Expect a _major_ speed penalty\n");
+ p->err_shown = 1;
+ return VO_FALSE;
+ }
+ if (p->is_yuv) {
+ // planar YUV
+ int xs, ys, component_bits;
+ mp_get_chroma_shift(p->image_format, &xs, &ys, &component_bits);
+ int bp = (component_bits + 7) / 8;
+ mpi->flags |= MP_IMGFLAG_COMMON_STRIDE | MP_IMGFLAG_COMMON_PLANE;
+ mpi->stride[0] = mpi->width * bp;
+ mpi->planes[1] = mpi->planes[0] + mpi->stride[0] * mpi->height;
+ mpi->stride[1] = (mpi->width >> xs) * bp;
+ mpi->planes[2] = mpi->planes[1] + mpi->stride[1] * (mpi->height >> ys);
+ mpi->stride[2] = (mpi->width >> xs) * bp;
+ if (p->ati_hack) {
+ mpi->flags &= ~MP_IMGFLAG_COMMON_PLANE;
+ if (!p->buffer_uv[0])
+ gl->GenBuffers(2, p->buffer_uv);
+ int buffer_size = mpi->stride[1] * mpi->height;
+ if (buffer_size > p->buffersize_uv) {
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[0]);
+ gl->BufferData(GL_PIXEL_UNPACK_BUFFER, buffer_size, NULL,
+ GL_DYNAMIC_DRAW);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[1]);
+ gl->BufferData(GL_PIXEL_UNPACK_BUFFER, buffer_size, NULL,
+ GL_DYNAMIC_DRAW);
+ p->buffersize_uv = buffer_size;
+ }
+ if (!p->bufferptr_uv[0]) {
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[0]);
+ p->bufferptr_uv[0] = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER,
+ GL_WRITE_ONLY);
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[1]);
+ p->bufferptr_uv[1] = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER,
+ GL_WRITE_ONLY);
+ }
+ mpi->planes[1] = p->bufferptr_uv[0];
+ mpi->planes[2] = p->bufferptr_uv[1];
+ }
+ }
+ mpi->flags |= MP_IMGFLAG_DIRECT;
+ return VO_TRUE;
+}
+
+static void clear_border(struct vo *vo, uint8_t *dst, int start, int stride,
+ int height, int full_height, int value)
+{
+ int right_border = stride - start;
+ int bottom_border = full_height - height;
+ while (height > 0) {
+ if (right_border > 0)
+ memset(dst + start, value, right_border);
+ dst += stride;
+ height--;
+ }
+ if (bottom_border > 0)
+ memset(dst, value, stride * bottom_border);
+}
+
+static uint32_t draw_image(struct vo *vo, mp_image_t *mpi)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ int slice = p->slice_height;
+ int stride[3];
+ unsigned char *planes[3];
+ mp_image_t mpi2 = *mpi;
+ int w = mpi->w, h = mpi->h;
+ if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK)
+ goto skip_upload;
+ mpi2.flags = 0;
+ mpi2.type = MP_IMGTYPE_TEMP;
+ mpi2.width = mpi2.w;
+ mpi2.height = mpi2.h;
+ if (p->force_pbo && !(mpi->flags & MP_IMGFLAG_DIRECT) && !p->bufferptr
+ && get_image(vo, &mpi2) == VO_TRUE)
+ {
+ int bp = mpi->bpp / 8;
+ int xs, ys, component_bits;
+ mp_get_chroma_shift(p->image_format, &xs, &ys, &component_bits);
+ if (p->is_yuv)
+ bp = (component_bits + 7) / 8;
+ memcpy_pic(mpi2.planes[0], mpi->planes[0], mpi->w * bp, mpi->h,
+ mpi2.stride[0], mpi->stride[0]);
+ int uv_bytes = (mpi->w >> xs) * bp;
+ if (p->is_yuv) {
+ memcpy_pic(mpi2.planes[1], mpi->planes[1], uv_bytes, mpi->h >> ys,
+ mpi2.stride[1], mpi->stride[1]);
+ memcpy_pic(mpi2.planes[2], mpi->planes[2], uv_bytes, mpi->h >> ys,
+ mpi2.stride[2], mpi->stride[2]);
+ }
+ if (p->ati_hack) {
+ // since we have to do a full upload we need to clear the borders
+ clear_border(vo, mpi2.planes[0], mpi->w * bp, mpi2.stride[0],
+ mpi->h, mpi2.height, 0);
+ if (p->is_yuv) {
+ int clear = get_chroma_clear_val(component_bits);
+ clear_border(vo, mpi2.planes[1], uv_bytes, mpi2.stride[1],
+ mpi->h >> ys, mpi2.height >> ys, clear);
+ clear_border(vo, mpi2.planes[2], uv_bytes, mpi2.stride[2],
+ mpi->h >> ys, mpi2.height >> ys, clear);
+ }
+ }
+ mpi = &mpi2;
+ }
+ stride[0] = mpi->stride[0];
+ stride[1] = mpi->stride[1];
+ stride[2] = mpi->stride[2];
+ planes[0] = mpi->planes[0];
+ planes[1] = mpi->planes[1];
+ planes[2] = mpi->planes[2];
+ p->mpi_flipped = stride[0] < 0;
+ if (mpi->flags & MP_IMGFLAG_DIRECT) {
+ intptr_t base = (intptr_t)planes[0];
+ if (p->ati_hack) {
+ w = p->texture_width;
+ h = p->texture_height;
+ }
+ if (p->mpi_flipped)
+ base += (mpi->h - 1) * stride[0];
+ planes[0] -= base;
+ planes[1] -= base;
+ planes[2] -= base;
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer);
+ gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+ p->bufferptr = NULL;
+ if (!(mpi->flags & MP_IMGFLAG_COMMON_PLANE))
+ planes[0] = planes[1] = planes[2] = NULL;
+ slice = 0; // always "upload" full texture
+ }
+ glUploadTex(gl, p->target, p->gl_format, p->gl_type, planes[0],
+ stride[0], 0, 0, w, h, slice);
+ if (p->is_yuv) {
+ int xs, ys;
+ mp_get_chroma_shift(p->image_format, &xs, &ys, NULL);
+ if ((mpi->flags & MP_IMGFLAG_DIRECT) && !(mpi->flags & MP_IMGFLAG_COMMON_PLANE)) {
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[0]);
+ gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+ p->bufferptr_uv[0] = NULL;
+ }
+ gl->ActiveTexture(GL_TEXTURE1);
+ glUploadTex(gl, p->target, p->gl_format, p->gl_type, planes[1],
+ stride[1], 0, 0, w >> xs, h >> ys, slice);
+ if ((mpi->flags & MP_IMGFLAG_DIRECT) && !(mpi->flags & MP_IMGFLAG_COMMON_PLANE)) {
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[1]);
+ gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+ p->bufferptr_uv[1] = NULL;
+ }
+ gl->ActiveTexture(GL_TEXTURE2);
+ glUploadTex(gl, p->target, p->gl_format, p->gl_type, planes[2],
+ stride[2], 0, 0, w >> xs, h >> ys, slice);
+ gl->ActiveTexture(GL_TEXTURE0);
+ }
+ if (mpi->flags & MP_IMGFLAG_DIRECT) {
+ gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
+ }
+skip_upload:
+ do_render(vo);
+ return VO_TRUE;
+}
+
+static mp_image_t *get_screenshot(struct vo *vo)
+{
+ struct gl_priv *p = vo->priv;
+ GL *gl = p->gl;
+
+ mp_image_t *image = alloc_mpi(p->texture_width, p->texture_height,
+ p->image_format);
+
+ glDownloadTex(gl, p->target, p->gl_format, p->gl_type, image->planes[0],
+ image->stride[0]);
+
+ if (p->is_yuv) {
+ gl->ActiveTexture(GL_TEXTURE1);
+ glDownloadTex(gl, p->target, p->gl_format, p->gl_type, image->planes[1],
+ image->stride[1]);
+ gl->ActiveTexture(GL_TEXTURE2);
+ glDownloadTex(gl, p->target, p->gl_format, p->gl_type, image->planes[2],
+ image->stride[2]);
+ gl->ActiveTexture(GL_TEXTURE0);
+ }
+
+ image->w = p->image_width;
+ image->h = p->image_height;
+ image->display_w = vo->aspdat.prew;
+ image->display_h = vo->aspdat.preh;
+
+ mp_image_set_colorspace_details(image, &p->colorspace);
+
+ return image;
+}
+
+static int query_format(struct vo *vo, uint32_t format)
+{
+ struct gl_priv *p = vo->priv;
+
+ int depth;
+ int caps = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_FLIP |
+ VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_ACCEPT_STRIDE;
+ if (p->use_osd)
+ caps |= VFCAP_OSD;
+ if (format == IMGFMT_RGB24 || format == IMGFMT_RGBA)
+ return caps;
+ if (p->use_yuv && mp_get_chroma_shift(format, NULL, NULL, &depth) &&
+ (depth == 8 || depth == 16 || glYUVLargeRange(p->use_yuv)) &&
+ (IMGFMT_IS_YUVP16_NE(format) || !IMGFMT_IS_YUVP16(format)))
+ return caps;
+ // HACK, otherwise we get only b&w with some filters (e.g. -vf eq)
+ // ideally MPlayer should be fixed instead not to use Y800 when it has the choice
+ if (!p->use_yuv && (format == IMGFMT_Y8 || format == IMGFMT_Y800))
+ return 0;
+ if (!p->use_ycbcr && (format == IMGFMT_UYVY || format == IMGFMT_YVYU))
+ return 0;
+ if (p->many_fmts &&
+ glFindFormat(format, p->have_texture_rg, NULL, NULL, NULL, NULL))
+ return caps;
+ return 0;
+}
+
+static void uninit(struct vo *vo)
+{
+ struct gl_priv *p = vo->priv;
+
+ uninitGl(vo);
+ free(p->custom_prog);
+ p->custom_prog = NULL;
+ free(p->custom_tex);
+ p->custom_tex = NULL;
+ mpgl_uninit(p->glctx);
+ p->glctx = NULL;
+ p->gl = NULL;
+}
+
+static int backend_valid(void *arg)
+{
+ return mpgl_find_backend(*(const char **)arg) >= 0;
+}
+
+static int preinit(struct vo *vo, const char *arg)
+{
+ struct gl_priv *p = talloc_zero(vo, struct gl_priv);
+ vo->priv = p;
+
+ *p = (struct gl_priv) {
+ .many_fmts = 1,
+ .use_osd = -1,
+ .use_yuv = -1,
+ .colorspace = MP_CSP_DETAILS_DEFAULTS,
+ .filter_strength = 0.5,
+ .use_rectangle = -1,
+ .ati_hack = -1,
+ .force_pbo = -1,
+ .swap_interval = vo_vsync,
+ .custom_prog = NULL,
+ .custom_tex = NULL,
+ .custom_tlin = 1,
+ .osd_color = 0xffffff,
+ };
+
+ char *backend_arg = NULL;
+
+ //essentially unused; for legacy warnings only
+ int user_colorspace = 0;
+ int levelconv = -1;
+ int aspect = -1;
+
+ const opt_t subopts[] = {
+ {"manyfmts", OPT_ARG_BOOL, &p->many_fmts, NULL},
+ {"osd", OPT_ARG_BOOL, &p->use_osd, NULL},
+ {"scaled-osd", OPT_ARG_BOOL, &p->scaled_osd, NULL},
+ {"ycbcr", OPT_ARG_BOOL, &p->use_ycbcr, NULL},
+ {"slice-height", OPT_ARG_INT, &p->slice_height, int_non_neg},
+ {"rectangle", OPT_ARG_INT, &p->use_rectangle,int_non_neg},
+ {"yuv", OPT_ARG_INT, &p->use_yuv, int_non_neg},
+ {"lscale", OPT_ARG_INT, &p->lscale, int_non_neg},
+ {"cscale", OPT_ARG_INT, &p->cscale, int_non_neg},
+ {"filter-strength", OPT_ARG_FLOAT, &p->filter_strength, NULL},
+ {"noise-strength", OPT_ARG_FLOAT, &p->noise_strength, NULL},
+ {"ati-hack", OPT_ARG_BOOL, &p->ati_hack, NULL},
+ {"force-pbo", OPT_ARG_BOOL, &p->force_pbo, NULL},
+ {"glfinish", OPT_ARG_BOOL, &p->use_glFinish, NULL},
+ {"swapinterval", OPT_ARG_INT, &p->swap_interval,NULL},
+ {"customprog", OPT_ARG_MSTRZ,&p->custom_prog, NULL},
+ {"customtex", OPT_ARG_MSTRZ,&p->custom_tex, NULL},
+ {"customtlin", OPT_ARG_BOOL, &p->custom_tlin, NULL},
+ {"customtrect", OPT_ARG_BOOL, &p->custom_trect, NULL},
+ {"mipmapgen", OPT_ARG_BOOL, &p->mipmap_gen, NULL},
+ {"osdcolor", OPT_ARG_INT, &p->osd_color, NULL},
+ {"stereo", OPT_ARG_INT, &p->stereo_mode, NULL},
+ {"sw", OPT_ARG_BOOL, &p->allow_sw, NULL},
+ {"backend", OPT_ARG_MSTRZ,&backend_arg, backend_valid},
+ // Removed options.
+ // They are only parsed to notify the user about the replacements.
+ {"aspect", OPT_ARG_BOOL, &aspect, NULL},
+ {"colorspace", OPT_ARG_INT, &user_colorspace, NULL},
+ {"levelconv", OPT_ARG_INT, &levelconv, NULL},
+ {NULL}
+ };
+
+ if (subopt_parse(arg, subopts) != 0) {
+ mp_msg(MSGT_VO, MSGL_FATAL,
+ "\n-vo opengl_old command line help:\n"
+ "Example: mpv -vo opengl_old:slice-height=4\n"
+ "\nOptions:\n"
+ " nomanyfmts\n"
+ " Disable extended color formats for OpenGL 1.2 and later\n"
+ " slice-height=<0-...>\n"
+ " Slice size for texture transfer, 0 for whole image\n"
+ " noosd\n"
+ " Do not use OpenGL OSD code\n"
+ " scaled-osd\n"
+ " Render OSD at movie resolution and scale it\n"
+ " rectangle=<0,1,2>\n"
+ " 0: use power-of-two textures\n"
+ " 1: use texture_rectangle\n"
+ " 2: use texture_non_power_of_two\n"
+ " ati-hack\n"
+ " Workaround ATI bug with PBOs\n"
+ " force-pbo\n"
+ " Force use of PBO even if this involves an extra memcpy\n"
+ " glfinish\n"
+ " Call glFinish() before swapping buffers\n"
+ " swapinterval=<n>\n"
+ " Interval in displayed frames between to buffer swaps.\n"
+ " 1 is equivalent to enable VSYNC, 0 to disable VSYNC.\n"
+ " Requires GLX_SGI_swap_control support to work.\n"
+ " ycbcr\n"
+ " also try to use the GL_MESA_ycbcr_texture extension\n"
+ " yuv=<n>\n"
+ " 0: use software YUV to RGB conversion.\n"
+ " 1: deprecated, will use yuv=2 (used to be nVidia register combiners).\n"
+ " 2: use fragment program.\n"
+ " 3: use fragment program with gamma correction.\n"
+ " 4: use fragment program with gamma correction via lookup.\n"
+ " 5: use ATI-specific method (for older cards).\n"
+ " 6: use lookup via 3D texture.\n"
+ " lscale=<n>\n"
+ " 0: use standard bilinear scaling for luma.\n"
+ " 1: use improved bicubic scaling for luma.\n"
+ " 2: use cubic in X, linear in Y direction scaling for luma.\n"
+ " 3: as 1 but without using a lookup texture.\n"
+ " 4: experimental unsharp masking (sharpening).\n"
+ " 5: experimental unsharp masking (sharpening) with larger radius.\n"
+ " cscale=<n>\n"
+ " as lscale but for chroma (2x slower with little visible effect).\n"
+ " filter-strength=<value>\n"
+ " set the effect strength for some lscale/cscale filters\n"
+ " noise-strength=<value>\n"
+ " set how much noise to add. 1.0 is suitable for dithering to 6 bit.\n"
+ " customprog=<filename>\n"
+ " use a custom YUV conversion program\n"
+ " customtex=<filename>\n"
+ " use a custom YUV conversion lookup texture\n"
+ " nocustomtlin\n"
+ " use GL_NEAREST scaling for customtex texture\n"
+ " customtrect\n"
+ " use texture_rectangle for customtex texture\n"
+ " mipmapgen\n"
+ " generate mipmaps for the video image (use with TXB in customprog)\n"
+ " osdcolor=<0xAARRGGBB>\n"
+ " use the given color for the OSD\n"
+ " stereo=<n>\n"
+ " 0: normal display\n"
+ " 1: side-by-side to red-cyan stereo\n"
+ " 2: side-by-side to green-magenta stereo\n"
+ " 3: side-by-side to quadbuffer stereo\n"
+ " sw\n"
+ " allow using a software renderer, if such is detected\n"
+ " backend=<sys>\n"
+ " auto: auto-select (default)\n"
+ " cocoa: Cocoa/OSX\n"
+ " win: Win32/WGL\n"
+ " x11: X11/GLX\n"
+ "\n");
+ return -1;
+ }
+ if (user_colorspace != 0 || levelconv != -1) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] \"colorspace\" and \"levelconv\" "
+ "suboptions have been removed. Use options --colormatrix and"
+ " --colormatrix-input-range/--colormatrix-output-range instead.\n");
+ return -1;
+ }
+ if (aspect != -1) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[gl] \"noaspect\" suboption has been "
+ "removed. Use --noaspect instead.\n");
+ return -1;
+ }
+ if (p->use_yuv == 1) {
+ mp_msg(MSGT_VO, MSGL_WARN, "[gl] yuv=1 (nVidia register combiners) have"
+ " been removed, using yuv=2 instead.\n");
+ p->use_yuv = 2;
+ }
+
+ int backend = backend_arg ? mpgl_find_backend(backend_arg) : GLTYPE_AUTO;
+ free(backend_arg);
+
+ p->glctx = mpgl_init(backend, vo);
+ if (!p->glctx)
+ goto err_out;
+ p->gl = p->glctx->gl;
+
+ if (p->use_yuv == -1) {
+ if (!create_window(vo, 320, 200, VOFLAG_HIDDEN))
+ goto err_out;
+ autodetectGlExtensions(vo);
+ // We created a window to test whether the GL context supports hardware
+ // acceleration and so on. Destroy that window to make sure all state
+ // associated with it is lost.
+ uninitGl(vo);
+ if (!mpgl_destroy_window(p->glctx))
+ goto err_out;
+ }
+ if (p->many_fmts)
+ mp_msg(MSGT_VO, MSGL_INFO, "[gl] using extended formats. "
+ "Use -vo gl:nomanyfmts if playback fails.\n");
+ mp_msg(MSGT_VO, MSGL_V, "[gl] Using %d as slice height "
+ "(0 means image height).\n", p->slice_height);
+
+ return 0;
+
+err_out:
+ uninit(vo);
+ return -1;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct gl_priv *p = vo->priv;
+
+ switch (request) {
+ case VOCTRL_QUERY_FORMAT:
+ return query_format(vo, *(uint32_t *)data);
+ case VOCTRL_DRAW_IMAGE:
+ return draw_image(vo, data);
+ case VOCTRL_ONTOP:
+ if (!p->glctx->ontop)
+ break;
+ p->glctx->ontop(vo);
+ return VO_TRUE;
+ case VOCTRL_FULLSCREEN:
+ p->glctx->fullscreen(vo);
+ resize(vo, vo->dwidth, vo->dheight);
+ return VO_TRUE;
+ case VOCTRL_BORDER:
+ if (!p->glctx->border)
+ break;
+ p->glctx->border(vo);
+ resize(vo, vo->dwidth, vo->dheight);
+ return VO_TRUE;
+ case VOCTRL_GET_PANSCAN:
+ return VO_TRUE;
+ case VOCTRL_SET_PANSCAN:
+ resize(vo, vo->dwidth, vo->dheight);
+ return VO_TRUE;
+ case VOCTRL_GET_EQUALIZER:
+ if (p->is_yuv) {
+ struct voctrl_get_equalizer_args *args = data;
+ return mp_csp_equalizer_get(&p->video_eq, args->name, args->valueptr)
+ >= 0 ? VO_TRUE : VO_NOTIMPL;
+ }
+ break;
+ case VOCTRL_SET_EQUALIZER:
+ if (p->is_yuv) {
+ struct voctrl_set_equalizer_args *args = data;
+ if (mp_csp_equalizer_set(&p->video_eq, args->name, args->value) < 0)
+ return VO_NOTIMPL;
+ update_yuvconv(vo);
+ vo->want_redraw = true;
+ return VO_TRUE;
+ }
+ break;
+ case VOCTRL_SET_YUV_COLORSPACE: {
+ bool supports_csp = (1 << p->use_yuv) & MASK_NOT_COMBINERS;
+ if (vo->config_count && supports_csp) {
+ p->colorspace = *(struct mp_csp_details *)data;
+ update_yuvconv(vo);
+ vo->want_redraw = true;
+ }
+ return VO_TRUE;
+ }
+ case VOCTRL_GET_YUV_COLORSPACE:
+ *(struct mp_csp_details *)data = p->colorspace;
+ return VO_TRUE;
+ case VOCTRL_UPDATE_SCREENINFO:
+ if (!p->glctx->update_xinerama_info)
+ break;
+ p->glctx->update_xinerama_info(vo);
+ return VO_TRUE;
+ case VOCTRL_REDRAW_FRAME:
+ do_render(vo);
+ return true;
+ case VOCTRL_PAUSE:
+ if (!p->glctx->pause)
+ break;
+ p->glctx->pause(vo);
+ return VO_TRUE;
+ case VOCTRL_RESUME:
+ if (!p->glctx->resume)
+ break;
+ p->glctx->resume(vo);
+ return VO_TRUE;
+ case VOCTRL_SCREENSHOT: {
+ struct voctrl_screenshot_args *args = data;
+ if (args->full_window)
+ args->out_image = glGetWindowScreenshot(p->gl);
+ else
+ args->out_image = get_screenshot(vo);
+ return true;
+ }
+ }
+ return VO_NOTIMPL;
+}
+
+const struct vo_driver video_out_opengl_old = {
+ .is_new = true,
+ .info = &(const vo_info_t) {
+ "OpenGL",
+ "opengl-old",
+ "Reimar Doeffinger <Reimar.Doeffinger@gmx.de>",
+ ""
+ },
+ .preinit = preinit,
+ .config = config,
+ .control = control,
+ .draw_slice = draw_slice,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+ .check_events = check_events,
+ .uninit = uninit,
+};
diff --git a/video/out/vo_opengl_shaders.glsl b/video/out/vo_opengl_shaders.glsl
new file mode 100644
index 0000000000..1f302889e4
--- /dev/null
+++ b/video/out/vo_opengl_shaders.glsl
@@ -0,0 +1,355 @@
+/*
+ * This file is part of mplayer2.
+ *
+ * mplayer2 is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * mplayer2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mplayer2; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+// Note that this file is not directly passed as shader, but run through some
+// text processing functions, and in fact contains multiple vertex and fragment
+// shaders.
+
+// inserted at the beginning of all shaders
+#!section prelude
+
+// GLSL 1.20 compatibility layer
+// texture() should be assumed to always map to texture2D()
+#if __VERSION__ >= 130
+# define texture1D texture
+# define texture3D texture
+# define DECLARE_FRAGPARMS \
+ out vec4 out_color;
+#else
+# define texture texture2D
+# define DECLARE_FRAGPARMS
+# define out_color gl_FragColor
+# define in varying
+#endif
+
+// Earlier GLSL doesn't support mix() with bvec
+#if __VERSION__ >= 130
+vec3 srgb_compand(vec3 v)
+{
+ return mix(1.055 * pow(v, vec3(1.0/2.4)) - vec3(0.055), v * 12.92,
+ lessThanEqual(v, vec3(0.0031308)));
+}
+#endif
+
+#!section vertex_all
+
+#if __VERSION__ < 130
+# undef in
+# define in attribute
+# define out varying
+#endif
+
+uniform mat3 transform;
+uniform sampler3D lut_3d;
+
+in vec2 vertex_position;
+in vec4 vertex_color;
+out vec4 color;
+in vec2 vertex_texcoord;
+out vec2 texcoord;
+
+void main() {
+ vec3 position = vec3(vertex_position, 1);
+#ifndef FIXED_SCALE
+ position = transform * position;
+#endif
+ gl_Position = vec4(position, 1);
+ color = vertex_color;
+
+#ifdef USE_OSD_LINEAR_CONV
+ // If no 3dlut is being used, we need to pull up to linear light for
+ // the sRGB function. *IF* 3dlut is used, we do not.
+ color.rgb = pow(color.rgb, vec3(2.2));
+#endif
+#ifdef USE_OSD_3DLUT
+ color = vec4(texture3D(lut_3d, color.rgb).rgb, color.a);
+#endif
+#ifdef USE_OSD_SRGB
+ color.rgb = srgb_compand(color.rgb);
+#endif
+
+ texcoord = vertex_texcoord;
+}
+
+#!section frag_osd_libass
+uniform sampler2D textures[3];
+
+in vec2 texcoord;
+in vec4 color;
+DECLARE_FRAGPARMS
+
+void main() {
+ out_color = vec4(color.rgb, color.a * texture(textures[0], texcoord).r);
+}
+
+#!section frag_osd_rgba
+uniform sampler2D textures[3];
+
+in vec2 texcoord;
+DECLARE_FRAGPARMS
+
+void main() {
+ out_color = texture(textures[0], texcoord);
+}
+
+#!section frag_video
+uniform sampler2D textures[3];
+uniform vec2 textures_size[3];
+uniform sampler1D lut_c_1d;
+uniform sampler1D lut_l_1d;
+uniform sampler2D lut_c_2d;
+uniform sampler2D lut_l_2d;
+uniform sampler3D lut_3d;
+uniform sampler2D dither;
+uniform mat4x3 colormatrix;
+uniform vec3 inv_gamma;
+uniform float conv_gamma;
+uniform float dither_quantization;
+uniform float dither_multiply;
+uniform float filter_param1;
+uniform vec2 dither_size;
+
+in vec2 texcoord;
+DECLARE_FRAGPARMS
+
+vec4 sample_bilinear(sampler2D tex, vec2 texsize, vec2 texcoord) {
+ return texture(tex, texcoord);
+}
+
+// Explanation how bicubic scaling with only 4 texel fetches is done:
+// http://www.mate.tue.nl/mate/pdfs/10318.pdf
+// 'Efficient GPU-Based Texture Interpolation using Uniform B-Splines'
+// Explanation why this algorithm normally always blurs, even with unit scaling:
+// http://bigwww.epfl.ch/preprints/ruijters1001p.pdf
+// 'GPU Prefilter for Accurate Cubic B-spline Interpolation'
+vec4 calcweights(float s) {
+ vec4 t = vec4(-0.5, 0.1666, 0.3333, -0.3333) * s + vec4(1, 0, -0.5, 0.5);
+ t = t * s + vec4(0, 0, -0.5, 0.5);
+ t = t * s + vec4(-0.6666, 0, 0.8333, 0.1666);
+ vec2 a = vec2(1 / t.z, 1 / t.w);
+ t.xy = t.xy * a + vec2(1, 1);
+ t.x = t.x + s;
+ t.y = t.y - s;
+ return t;
+}
+
+vec4 sample_bicubic_fast(sampler2D tex, vec2 texsize, vec2 texcoord) {
+ vec2 pt = 1 / texsize;
+ vec2 fcoord = fract(texcoord * texsize + vec2(0.5, 0.5));
+ vec4 parmx = calcweights(fcoord.x);
+ vec4 parmy = calcweights(fcoord.y);
+ vec4 cdelta;
+ cdelta.xz = parmx.rg * vec2(-pt.x, pt.x);
+ cdelta.yw = parmy.rg * vec2(-pt.y, pt.y);
+ // first y-interpolation
+ vec4 ar = texture(tex, texcoord + cdelta.xy);
+ vec4 ag = texture(tex, texcoord + cdelta.xw);
+ vec4 ab = mix(ag, ar, parmy.b);
+ // second y-interpolation
+ vec4 br = texture(tex, texcoord + cdelta.zy);
+ vec4 bg = texture(tex, texcoord + cdelta.zw);
+ vec4 aa = mix(bg, br, parmy.b);
+ // x-interpolation
+ return mix(aa, ab, parmx.b);
+}
+
+float[2] weights2(sampler1D lookup, float f) {
+ vec4 c = texture1D(lookup, f);
+ return float[2](c.r, c.g);
+}
+
+float[4] weights4(sampler1D lookup, float f) {
+ vec4 c = texture1D(lookup, f);
+ return float[4](c.r, c.g, c.b, c.a);
+}
+
+float[6] weights6(sampler2D lookup, float f) {
+ vec4 c1 = texture(lookup, vec2(0.25, f));
+ vec4 c2 = texture(lookup, vec2(0.75, f));
+ return float[6](c1.r, c1.g, c1.b, c2.r, c2.g, c2.b);
+}
+
+float[8] weights8(sampler2D lookup, float f) {
+ vec4 c1 = texture(lookup, vec2(0.25, f));
+ vec4 c2 = texture(lookup, vec2(0.75, f));
+ return float[8](c1.r, c1.g, c1.b, c1.a, c2.r, c2.g, c2.b, c2.a);
+}
+
+float[12] weights12(sampler2D lookup, float f) {
+ vec4 c1 = texture(lookup, vec2(1.0/6.0, f));
+ vec4 c2 = texture(lookup, vec2(0.5, f));
+ vec4 c3 = texture(lookup, vec2(5.0/6.0, f));
+ return float[12](c1.r, c1.g, c1.b, c1.a,
+ c2.r, c2.g, c2.b, c2.a,
+ c3.r, c3.g, c3.b, c3.a);
+}
+
+float[16] weights16(sampler2D lookup, float f) {
+ vec4 c1 = texture(lookup, vec2(0.125, f));
+ vec4 c2 = texture(lookup, vec2(0.375, f));
+ vec4 c3 = texture(lookup, vec2(0.625, f));
+ vec4 c4 = texture(lookup, vec2(0.875, f));
+ return float[16](c1.r, c1.g, c1.b, c1.a, c2.r, c2.g, c2.b, c2.a,
+ c3.r, c3.g, c3.b, c3.a, c4.r, c4.g, c4.b, c4.a);
+}
+
+#define CONVOLUTION_SEP_N(NAME, N) \
+ vec4 NAME(sampler2D tex, vec2 texcoord, vec2 pt, float weights[N]) { \
+ vec4 res = vec4(0); \
+ for (int n = 0; n < N; n++) { \
+ res += weights[n] * texture(tex, texcoord + pt * n); \
+ } \
+ return res; \
+ }
+
+CONVOLUTION_SEP_N(convolution_sep2, 2)
+CONVOLUTION_SEP_N(convolution_sep4, 4)
+CONVOLUTION_SEP_N(convolution_sep6, 6)
+CONVOLUTION_SEP_N(convolution_sep8, 8)
+CONVOLUTION_SEP_N(convolution_sep12, 12)
+CONVOLUTION_SEP_N(convolution_sep16, 16)
+
+// The dir parameter is (0, 1) or (1, 0), and we expect the shader compiler to
+// remove all the redundant multiplications and additions.
+#define SAMPLE_CONVOLUTION_SEP_N(NAME, N, SAMPLERT, CONV_FUNC, WEIGHTS_FUNC)\
+ vec4 NAME(vec2 dir, SAMPLERT lookup, sampler2D tex, vec2 texsize, \
+ vec2 texcoord) { \
+ vec2 pt = (1 / texsize) * dir; \
+ float fcoord = dot(fract(texcoord * texsize - 0.5), dir); \
+ vec2 base = texcoord - fcoord * pt; \
+ return CONV_FUNC(tex, base - pt * (N / 2 - 1), pt, \
+ WEIGHTS_FUNC(lookup, fcoord)); \
+ }
+
+SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep2, 2, sampler1D, convolution_sep2, weights2)
+SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep4, 4, sampler1D, convolution_sep4, weights4)
+SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep6, 6, sampler2D, convolution_sep6, weights6)
+SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep8, 8, sampler2D, convolution_sep8, weights8)
+SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep12, 12, sampler2D, convolution_sep12, weights12)
+SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep16, 16, sampler2D, convolution_sep16, weights16)
+
+
+#define CONVOLUTION_N(NAME, N) \
+ vec4 NAME(sampler2D tex, vec2 texcoord, vec2 pt, float taps_x[N], \
+ float taps_y[N]) { \
+ vec4 res = vec4(0); \
+ for (int y = 0; y < N; y++) { \
+ vec4 line = vec4(0); \
+ for (int x = 0; x < N; x++) \
+ line += taps_x[x] * texture(tex, texcoord + pt * vec2(x, y));\
+ res += taps_y[y] * line; \
+ } \
+ return res; \
+ }
+
+CONVOLUTION_N(convolution2, 2)
+CONVOLUTION_N(convolution4, 4)
+CONVOLUTION_N(convolution6, 6)
+CONVOLUTION_N(convolution8, 8)
+CONVOLUTION_N(convolution12, 12)
+CONVOLUTION_N(convolution16, 16)
+
+#define SAMPLE_CONVOLUTION_N(NAME, N, SAMPLERT, CONV_FUNC, WEIGHTS_FUNC) \
+ vec4 NAME(SAMPLERT lookup, sampler2D tex, vec2 texsize, vec2 texcoord) {\
+ vec2 pt = 1 / texsize; \
+ vec2 fcoord = fract(texcoord * texsize - 0.5); \
+ vec2 base = texcoord - fcoord * pt; \
+ return CONV_FUNC(tex, base - pt * (N / 2 - 1), pt, \
+ WEIGHTS_FUNC(lookup, fcoord.x), \
+ WEIGHTS_FUNC(lookup, fcoord.y)); \
+ }
+
+SAMPLE_CONVOLUTION_N(sample_convolution2, 2, sampler1D, convolution2, weights2)
+SAMPLE_CONVOLUTION_N(sample_convolution4, 4, sampler1D, convolution4, weights4)
+SAMPLE_CONVOLUTION_N(sample_convolution6, 6, sampler2D, convolution6, weights6)
+SAMPLE_CONVOLUTION_N(sample_convolution8, 8, sampler2D, convolution8, weights8)
+SAMPLE_CONVOLUTION_N(sample_convolution12, 12, sampler2D, convolution12, weights12)
+SAMPLE_CONVOLUTION_N(sample_convolution16, 16, sampler2D, convolution16, weights16)
+
+
+// Unsharp masking
+vec4 sample_sharpen3(sampler2D tex, vec2 texsize, vec2 texcoord) {
+ vec2 pt = 1 / texsize;
+ vec2 st = pt * 0.5;
+ vec4 p = texture(tex, texcoord);
+ vec4 sum = texture(tex, texcoord + st * vec2(+1, +1))
+ + texture(tex, texcoord + st * vec2(+1, -1))
+ + texture(tex, texcoord + st * vec2(-1, +1))
+ + texture(tex, texcoord + st * vec2(-1, -1));
+ return p + (p - 0.25 * sum) * filter_param1;
+}
+
+vec4 sample_sharpen5(sampler2D tex, vec2 texsize, vec2 texcoord) {
+ vec2 pt = 1 / texsize;
+ vec2 st1 = pt * 1.2;
+ vec4 p = texture(tex, texcoord);
+ vec4 sum1 = texture(tex, texcoord + st1 * vec2(+1, +1))
+ + texture(tex, texcoord + st1 * vec2(+1, -1))
+ + texture(tex, texcoord + st1 * vec2(-1, +1))
+ + texture(tex, texcoord + st1 * vec2(-1, -1));
+ vec2 st2 = pt * 1.5;
+ vec4 sum2 = texture(tex, texcoord + st2 * vec2(+1, 0))
+ + texture(tex, texcoord + st2 * vec2( 0, +1))
+ + texture(tex, texcoord + st2 * vec2(-1, 0))
+ + texture(tex, texcoord + st2 * vec2( 0, -1));
+ vec4 t = p * 0.859375 + sum2 * -0.1171875 + sum1 * -0.09765625;
+ return p + t * filter_param1;
+}
+
+void main() {
+#ifdef USE_PLANAR
+ vec3 color = vec3(SAMPLE_L(textures[0], textures_size[0], texcoord).r,
+ SAMPLE_C(textures[1], textures_size[1], texcoord).r,
+ SAMPLE_C(textures[2], textures_size[2], texcoord).r);
+#else
+ vec3 color = SAMPLE_L(textures[0], textures_size[0], texcoord).rgb;
+#endif
+#ifdef USE_GBRP
+ color.gbr = color;
+#endif
+#ifdef USE_YGRAY
+ // NOTE: actually slightly wrong for 16 bit input video, and completely
+ // wrong for 9/10 bit input
+ color.gb = vec2(128.0/255.0);
+#endif
+#ifdef USE_COLORMATRIX
+ color = mat3(colormatrix) * color + colormatrix[3];
+#endif
+#ifdef USE_LINEAR_CONV
+ color = pow(color, vec3(2.2));
+#endif
+#ifdef USE_LINEAR_CONV_INV
+ // Convert from linear RGB to gamma RGB before putting it through the 3D-LUT
+ // in the final stage.
+ color = pow(color, vec3(1.0/2.2));
+#endif
+#ifdef USE_GAMMA_POW
+ color = pow(color, inv_gamma);
+#endif
+#ifdef USE_3DLUT
+ color = texture3D(lut_3d, color).rgb;
+#endif
+#ifdef USE_SRGB
+ color.rgb = srgb_compand(color.rgb);
+#endif
+#ifdef USE_DITHER
+ float dither_value = texture(dither, gl_FragCoord.xy / dither_size).r;
+ color = floor(color * dither_multiply + dither_value ) / dither_quantization;
+#endif
+ out_color = vec4(color, 1);
+}
diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c
new file mode 100644
index 0000000000..a523ea5815
--- /dev/null
+++ b/video/out/vo_vdpau.c
@@ -0,0 +1,1718 @@
+/*
+ * VDPAU video output driver
+ *
+ * Copyright (C) 2008 NVIDIA
+ * Copyright (C) 2009 Uoti Urpala
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ * Actual decoding and presentation are implemented here.
+ * All necessary frame information is collected through
+ * the "vdpau_render_state" structure after parsing all headers
+ * etc. in libavcodec for different codecs.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <assert.h>
+
+#include <libavutil/common.h>
+#include <libavcodec/vdpau.h>
+
+#include "config.h"
+#include "mp_msg.h"
+#include "options.h"
+#include "talloc.h"
+#include "video_out.h"
+#include "x11_common.h"
+#include "aspect.h"
+#include "csputils.h"
+#include "sub/sub.h"
+#include "m_option.h"
+#include "libmpcodecs/vfcap.h"
+#include "libmpcodecs/mp_image.h"
+#include "osdep/timer.h"
+#include "bitmap_packer.h"
+
+#define WRAP_ADD(x, a, m) ((a) < 0 \
+ ? ((x)+(a)+(m) < (m) ? (x)+(a)+(m) : (x)+(a)) \
+ : ((x)+(a) < (m) ? (x)+(a) : (x)+(a)-(m)))
+
+#define CHECK_ST_ERROR(message) \
+ do { \
+ if (vdp_st != VDP_STATUS_OK) { \
+ mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] %s: %s\n", \
+ message, vdp->get_error_string(vdp_st)); \
+ return -1; \
+ } \
+ } while (0)
+
+#define CHECK_ST_WARNING(message) \
+ do { \
+ if (vdp_st != VDP_STATUS_OK) \
+ mp_msg(MSGT_VO, MSGL_WARN, "[ vdpau] %s: %s\n", \
+ message, vdp->get_error_string(vdp_st)); \
+ } while (0)
+
+/* number of video and output surfaces */
+#define MAX_OUTPUT_SURFACES 15
+#define MAX_VIDEO_SURFACES 50
+#define NUM_BUFFERED_VIDEO 5
+
+/* Pixelformat used for output surfaces */
+#define OUTPUT_RGBA_FORMAT VDP_RGBA_FORMAT_B8G8R8A8
+
+/*
+ * Global variable declaration - VDPAU specific
+ */
+
+struct vdp_functions {
+#define VDP_FUNCTION(vdp_type, _, mp_name) vdp_type *mp_name;
+#include "vdpau_template.c"
+#undef VDP_FUNCTION
+};
+
+struct vdpctx {
+ struct vdp_functions *vdp;
+
+ VdpDevice vdp_device;
+ bool is_preempted;
+ bool preemption_acked;
+ bool preemption_user_notified;
+ unsigned int last_preemption_retry_fail;
+ VdpGetProcAddress *vdp_get_proc_address;
+
+ VdpPresentationQueueTarget flip_target;
+ VdpPresentationQueue flip_queue;
+ uint64_t last_vdp_time;
+ unsigned int last_sync_update;
+
+ VdpOutputSurface output_surfaces[MAX_OUTPUT_SURFACES];
+ VdpOutputSurface screenshot_surface;
+ int num_output_surfaces;
+ struct buffered_video_surface {
+ VdpVideoSurface surface;
+ double pts;
+ mp_image_t *mpi;
+ } buffered_video[NUM_BUFFERED_VIDEO];
+ int deint_queue_pos;
+ int output_surface_width, output_surface_height;
+
+ VdpVideoMixer video_mixer;
+ struct mp_csp_details colorspace;
+ int deint;
+ int deint_type;
+ int deint_counter;
+ int pullup;
+ float denoise;
+ float sharpen;
+ int hqscaling;
+ int chroma_deint;
+ int flip_offset_window;
+ int flip_offset_fs;
+ int top_field_first;
+ bool flip;
+
+ VdpDecoder decoder;
+ int decoder_max_refs;
+
+ VdpRect src_rect_vid;
+ VdpRect out_rect_vid;
+ struct mp_osd_res osd_rect;
+
+ struct vdpau_render_state surface_render[MAX_VIDEO_SURFACES];
+ int surface_num;
+ int query_surface_num;
+ VdpTime recent_vsync_time;
+ float user_fps;
+ int composite_detect;
+ unsigned int vsync_interval;
+ uint64_t last_queue_time;
+ uint64_t queue_time[MAX_OUTPUT_SURFACES];
+ uint64_t last_ideal_time;
+ bool dropped_frame;
+ uint64_t dropped_time;
+ uint32_t vid_width, vid_height;
+ uint32_t image_format;
+ VdpChromaType vdp_chroma_type;
+ VdpYCbCrFormat vdp_pixel_format;
+
+ // OSD
+ struct osd_bitmap_surface {
+ VdpRGBAFormat format;
+ VdpBitmapSurface surface;
+ uint32_t max_width;
+ uint32_t max_height;
+ struct bitmap_packer *packer;
+ // List of surfaces to be rendered
+ struct osd_target {
+ VdpRect source;
+ VdpRect dest;
+ VdpColor color;
+ } *targets;
+ int targets_size;
+ int render_count;
+ int bitmap_id;
+ int bitmap_pos_id;
+ } osd_surfaces[MAX_OSD_PARTS];
+
+ // Video equalizer
+ struct mp_csp_equalizer video_eq;
+
+ // These tell what's been initialized and uninit() should free/uninitialize
+ bool mode_switched;
+};
+
+static bool status_ok(struct vo *vo);
+
+static int change_vdptime_sync(struct vdpctx *vc, unsigned int *t)
+{
+ struct vdp_functions *vdp = vc->vdp;
+ VdpStatus vdp_st;
+ VdpTime vdp_time;
+ vdp_st = vdp->presentation_queue_get_time(vc->flip_queue, &vdp_time);
+ CHECK_ST_ERROR("Error when calling vdp_presentation_queue_get_time");
+ unsigned int t1 = *t;
+ unsigned int t2 = GetTimer();
+ uint64_t old = vc->last_vdp_time + (t1 - vc->last_sync_update) * 1000ULL;
+ if (vdp_time > old)
+ if (vdp_time > old + (t2 - t1) * 1000ULL)
+ vdp_time -= (t2 - t1) * 1000ULL;
+ else
+ vdp_time = old;
+ mp_msg(MSGT_VO, MSGL_DBG2, "[vdpau] adjusting VdpTime offset by %f µs\n",
+ (int64_t)(vdp_time - old) / 1000.);
+ vc->last_vdp_time = vdp_time;
+ vc->last_sync_update = t1;
+ *t = t2;
+ return 0;
+}
+
+static uint64_t sync_vdptime(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+
+ unsigned int t = GetTimer();
+ if (t - vc->last_sync_update > 5000000)
+ change_vdptime_sync(vc, &t);
+ uint64_t now = (t - vc->last_sync_update) * 1000ULL + vc->last_vdp_time;
+ // Make sure nanosecond inaccuracies don't make things inconsistent
+ now = FFMAX(now, vc->recent_vsync_time);
+ return now;
+}
+
+static uint64_t convert_to_vdptime(struct vo *vo, unsigned int t)
+{
+ struct vdpctx *vc = vo->priv;
+ return (int)(t - vc->last_sync_update) * 1000LL + vc->last_vdp_time;
+}
+
+static int render_video_to_output_surface(struct vo *vo,
+ VdpOutputSurface output_surface,
+ VdpRect *output_rect)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ VdpTime dummy;
+ VdpStatus vdp_st;
+ if (vc->deint_queue_pos < 0)
+ return -1;
+
+ struct buffered_video_surface *bv = vc->buffered_video;
+ int field = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME;
+ unsigned int dp = vc->deint_queue_pos;
+ // dp==0 means last field of latest frame, 1 earlier field of latest frame,
+ // 2 last field of previous frame and so on
+ if (vc->deint) {
+ field = vc->top_field_first ^ (dp & 1) ?
+ VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD:
+ VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD;
+ }
+ const VdpVideoSurface *past_fields = (const VdpVideoSurface []){
+ bv[(dp+1)/2].surface, bv[(dp+2)/2].surface};
+ const VdpVideoSurface *future_fields = (const VdpVideoSurface []){
+ dp >= 1 ? bv[(dp-1)/2].surface : VDP_INVALID_HANDLE};
+ vdp_st = vdp->presentation_queue_block_until_surface_idle(vc->flip_queue,
+ output_surface,
+ &dummy);
+ CHECK_ST_WARNING("Error when calling "
+ "vdp_presentation_queue_block_until_surface_idle");
+
+ vdp_st = vdp->video_mixer_render(vc->video_mixer, VDP_INVALID_HANDLE,
+ 0, field, 2, past_fields,
+ bv[dp/2].surface, 1, future_fields,
+ &vc->src_rect_vid, output_surface,
+ NULL, output_rect, 0, NULL);
+ CHECK_ST_WARNING("Error when calling vdp_video_mixer_render");
+ return 0;
+}
+
+static int video_to_output_surface(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+
+ return render_video_to_output_surface(vo,
+ vc->output_surfaces[vc->surface_num],
+ &vc->out_rect_vid);
+}
+
+static int next_deint_queue_pos(struct vo *vo, bool eof)
+{
+ struct vdpctx *vc = vo->priv;
+
+ int dqp = vc->deint_queue_pos;
+ if (dqp < 0)
+ dqp += 1000;
+ else
+ dqp = vc->deint >= 2 ? dqp - 1 : dqp - 2 | 1;
+ if (dqp < (eof ? 0 : 3))
+ return -1;
+ return dqp;
+}
+
+static void set_next_frame_info(struct vo *vo, bool eof)
+{
+ struct vdpctx *vc = vo->priv;
+
+ vo->frame_loaded = false;
+ int dqp = next_deint_queue_pos(vo, eof);
+ if (dqp < 0)
+ return;
+ vo->frame_loaded = true;
+
+ // Set pts values
+ struct buffered_video_surface *bv = vc->buffered_video;
+ int idx = dqp >> 1;
+ if (idx == 0) { // no future frame/pts available
+ vo->next_pts = bv[0].pts;
+ vo->next_pts2 = MP_NOPTS_VALUE;
+ } else if (!(vc->deint >= 2)) { // no field-splitting deinterlace
+ vo->next_pts = bv[idx].pts;
+ vo->next_pts2 = bv[idx - 1].pts;
+ } else { // deinterlace with separate fields
+ double intermediate_pts;
+ double diff = bv[idx - 1].pts - bv[idx].pts;
+ if (diff > 0 && diff < 0.5)
+ intermediate_pts = (bv[idx].pts + bv[idx - 1].pts) / 2;
+ else
+ intermediate_pts = bv[idx].pts;
+ if (dqp & 1) { // first field
+ vo->next_pts = bv[idx].pts;
+ vo->next_pts2 = intermediate_pts;
+ } else {
+ vo->next_pts = intermediate_pts;
+ vo->next_pts2 = bv[idx - 1].pts;
+ }
+ }
+}
+
+static void add_new_video_surface(struct vo *vo, VdpVideoSurface surface,
+ struct mp_image *reserved_mpi, double pts)
+{
+ struct vdpctx *vc = vo->priv;
+ struct buffered_video_surface *bv = vc->buffered_video;
+
+ if (reserved_mpi)
+ reserved_mpi->usage_count++;
+ if (bv[NUM_BUFFERED_VIDEO - 1].mpi)
+ bv[NUM_BUFFERED_VIDEO - 1].mpi->usage_count--;
+
+ for (int i = NUM_BUFFERED_VIDEO - 1; i > 0; i--)
+ bv[i] = bv[i - 1];
+ bv[0] = (struct buffered_video_surface){
+ .mpi = reserved_mpi,
+ .surface = surface,
+ .pts = pts,
+ };
+
+ vc->deint_queue_pos = FFMIN(vc->deint_queue_pos + 2,
+ NUM_BUFFERED_VIDEO * 2 - 3);
+ set_next_frame_info(vo, false);
+}
+
+static void forget_frames(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+
+ vc->deint_queue_pos = -1001;
+ vc->dropped_frame = false;
+ for (int i = 0; i < NUM_BUFFERED_VIDEO; i++) {
+ struct buffered_video_surface *p = vc->buffered_video + i;
+ if (p->mpi)
+ p->mpi->usage_count--;
+ *p = (struct buffered_video_surface){
+ .surface = VDP_INVALID_HANDLE,
+ };
+ }
+}
+
+static void resize(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ VdpStatus vdp_st;
+ struct mp_rect src_rect;
+ struct mp_rect dst_rect;
+ vo_get_src_dst_rects(vo, &src_rect, &dst_rect, &vc->osd_rect);
+ vc->out_rect_vid.x0 = dst_rect.x0;
+ vc->out_rect_vid.x1 = dst_rect.x1;
+ vc->out_rect_vid.y0 = dst_rect.y0;
+ vc->out_rect_vid.y1 = dst_rect.y1;
+ vc->src_rect_vid.x0 = src_rect.x0;
+ vc->src_rect_vid.x1 = src_rect.x1;
+ vc->src_rect_vid.y0 = vc->flip ? src_rect.y1 : src_rect.y0;
+ vc->src_rect_vid.y1 = vc->flip ? src_rect.y0 : src_rect.y1;
+
+ int flip_offset_ms = vo_fs ? vc->flip_offset_fs : vc->flip_offset_window;
+ vo->flip_queue_offset = flip_offset_ms / 1000.;
+
+ if (vc->output_surface_width < vo->dwidth
+ || vc->output_surface_height < vo->dheight) {
+ if (vc->output_surface_width < vo->dwidth) {
+ vc->output_surface_width += vc->output_surface_width >> 1;
+ vc->output_surface_width = FFMAX(vc->output_surface_width,
+ vo->dwidth);
+ }
+ if (vc->output_surface_height < vo->dheight) {
+ vc->output_surface_height += vc->output_surface_height >> 1;
+ vc->output_surface_height = FFMAX(vc->output_surface_height,
+ vo->dheight);
+ }
+ // Creation of output_surfaces
+ for (int i = 0; i < vc->num_output_surfaces; i++)
+ if (vc->output_surfaces[i] != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->output_surface_destroy(vc->output_surfaces[i]);
+ CHECK_ST_WARNING("Error when calling "
+ "vdp_output_surface_destroy");
+ }
+ for (int i = 0; i < vc->num_output_surfaces; i++) {
+ vdp_st = vdp->output_surface_create(vc->vdp_device,
+ OUTPUT_RGBA_FORMAT,
+ vc->output_surface_width,
+ vc->output_surface_height,
+ &vc->output_surfaces[i]);
+ CHECK_ST_WARNING("Error when calling vdp_output_surface_create");
+ mp_msg(MSGT_VO, MSGL_DBG2, "vdpau out create: %u\n",
+ vc->output_surfaces[i]);
+ }
+ }
+ vo->want_redraw = true;
+}
+
+static void preemption_callback(VdpDevice device, void *context)
+{
+ struct vdpctx *vc = context;
+ vc->is_preempted = true;
+ vc->preemption_acked = false;
+}
+
+/* Initialize vdp_get_proc_address, called from preinit() */
+static int win_x11_init_vdpau_procs(struct vo *vo)
+{
+ struct vo_x11_state *x11 = vo->x11;
+ struct vdpctx *vc = vo->priv;
+ if (vc->vdp) // reinitialization after preemption
+ memset(vc->vdp, 0, sizeof(*vc->vdp));
+ else
+ vc->vdp = talloc_zero(vc, struct vdp_functions);
+ struct vdp_functions *vdp = vc->vdp;
+ VdpStatus vdp_st;
+
+ struct vdp_function {
+ const int id;
+ int offset;
+ };
+
+ const struct vdp_function *dsc;
+
+ static const struct vdp_function vdp_func[] = {
+#define VDP_FUNCTION(_, macro_name, mp_name) {macro_name, offsetof(struct vdp_functions, mp_name)},
+#include "vdpau_template.c"
+#undef VDP_FUNCTION
+ {0, -1}
+ };
+
+ vdp_st = vdp_device_create_x11(x11->display, x11->screen, &vc->vdp_device,
+ &vc->vdp_get_proc_address);
+ if (vdp_st != VDP_STATUS_OK) {
+ if (vc->is_preempted)
+ mp_msg(MSGT_VO, MSGL_DBG2, "[vdpau] Error calling "
+ "vdp_device_create_x11 while preempted: %d\n", vdp_st);
+ else
+ mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Error when calling "
+ "vdp_device_create_x11: %d\n", vdp_st);
+ return -1;
+ }
+
+ vdp->get_error_string = NULL;
+ for (dsc = vdp_func; dsc->offset >= 0; dsc++) {
+ vdp_st = vc->vdp_get_proc_address(vc->vdp_device, dsc->id,
+ (void **)((char *)vdp + dsc->offset));
+ if (vdp_st != VDP_STATUS_OK) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Error when calling "
+ "vdp_get_proc_address(function id %d): %s\n", dsc->id,
+ vdp->get_error_string ? vdp->get_error_string(vdp_st) : "?");
+ return -1;
+ }
+ }
+ vdp_st = vdp->preemption_callback_register(vc->vdp_device,
+ preemption_callback, vc);
+ return 0;
+}
+
+static int win_x11_init_vdpau_flip_queue(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ struct vo_x11_state *x11 = vo->x11;
+ VdpStatus vdp_st;
+
+ if (vc->flip_target == VDP_INVALID_HANDLE) {
+ vdp_st = vdp->presentation_queue_target_create_x11(vc->vdp_device,
+ x11->window,
+ &vc->flip_target);
+ CHECK_ST_ERROR("Error when calling "
+ "vdp_presentation_queue_target_create_x11");
+ }
+
+ /* Emperically this seems to be the first call which fails when we
+ * try to reinit after preemption while the user is still switched
+ * from X to a virtual terminal (creating the vdp_device initially
+ * succeeds, as does creating the flip_target above). This is
+ * probably not guaranteed behavior, but we'll assume it as a simple
+ * way to reduce warnings while trying to recover from preemption.
+ */
+ if (vc->flip_queue == VDP_INVALID_HANDLE) {
+ vdp_st = vdp->presentation_queue_create(vc->vdp_device, vc->flip_target,
+ &vc->flip_queue);
+ if (vc->is_preempted && vdp_st != VDP_STATUS_OK) {
+ mp_msg(MSGT_VO, MSGL_DBG2, "[vdpau] Failed to create flip queue "
+ "while preempted: %s\n", vdp->get_error_string(vdp_st));
+ return -1;
+ } else
+ CHECK_ST_ERROR("Error when calling vdp_presentation_queue_create");
+ }
+
+ VdpTime vdp_time;
+ vdp_st = vdp->presentation_queue_get_time(vc->flip_queue, &vdp_time);
+ CHECK_ST_ERROR("Error when calling vdp_presentation_queue_get_time");
+ vc->last_vdp_time = vdp_time;
+ vc->last_sync_update = GetTimer();
+
+ vc->vsync_interval = 1;
+ if (vc->composite_detect && vo_x11_screen_is_composited(vo)) {
+ mp_msg(MSGT_VO, MSGL_INFO, "[vdpau] Compositing window manager "
+ "detected. Assuming timing info is inaccurate.\n");
+ } else if (vc->user_fps > 0) {
+ vc->vsync_interval = 1e9 / vc->user_fps;
+ mp_msg(MSGT_VO, MSGL_INFO, "[vdpau] Assuming user-specified display "
+ "refresh rate of %.3f Hz.\n", vc->user_fps);
+ } else if (vc->user_fps == 0) {
+#ifdef CONFIG_XF86VM
+ double fps = vo_vm_get_fps(vo);
+ if (!fps)
+ mp_msg(MSGT_VO, MSGL_WARN, "[vdpau] Failed to get display FPS\n");
+ else {
+ vc->vsync_interval = 1e9 / fps;
+ // This is verbose, but I'm not yet sure how common wrong values are
+ mp_msg(MSGT_VO, MSGL_INFO,
+ "[vdpau] Got display refresh rate %.3f Hz.\n"
+ "[vdpau] If that value looks wrong give the "
+ "-vo vdpau:fps=X suboption manually.\n", fps);
+ }
+#else
+ mp_msg(MSGT_VO, MSGL_INFO, "[vdpau] This binary has been compiled "
+ "without XF86VidMode support.\n");
+ mp_msg(MSGT_VO, MSGL_INFO, "[vdpau] Can't use vsync-aware timing "
+ "without manually provided -vo vdpau:fps=X suboption.\n");
+#endif
+ } else
+ mp_msg(MSGT_VO, MSGL_V, "[vdpau] framedrop/timing logic disabled by "
+ "user.\n");
+
+ return 0;
+}
+
+static int set_video_attribute(struct vdpctx *vc, VdpVideoMixerAttribute attr,
+ const void *value, char *attr_name)
+{
+ struct vdp_functions *vdp = vc->vdp;
+ VdpStatus vdp_st;
+
+ vdp_st = vdp->video_mixer_set_attribute_values(vc->video_mixer, 1, &attr,
+ &value);
+ if (vdp_st != VDP_STATUS_OK) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Error setting video mixer "
+ "attribute %s: %s\n", attr_name, vdp->get_error_string(vdp_st));
+ return -1;
+ }
+ return 0;
+}
+
+static void update_csc_matrix(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+
+ mp_msg(MSGT_VO, MSGL_V, "[vdpau] Updating CSC matrix\n");
+
+ // VdpCSCMatrix happens to be compatible with mplayer's CSC matrix type
+ // both are float[3][4]
+ VdpCSCMatrix matrix;
+
+ struct mp_csp_params cparams = {
+ .colorspace = vc->colorspace, .input_bits = 8, .texture_bits = 8 };
+ mp_csp_copy_equalizer_values(&cparams, &vc->video_eq);
+ mp_get_yuv2rgb_coeffs(&cparams, matrix);
+
+ set_video_attribute(vc, VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX,
+ &matrix, "CSC matrix");
+}
+
+#define SET_VIDEO_ATTR(attr_name, attr_type, value) set_video_attribute(vc, \
+ VDP_VIDEO_MIXER_ATTRIBUTE_ ## attr_name, &(attr_type){value},\
+ # attr_name)
+static int create_vdp_mixer(struct vo *vo, VdpChromaType vdp_chroma_type)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+#define VDP_NUM_MIXER_PARAMETER 3
+#define MAX_NUM_FEATURES 6
+ int i;
+ VdpStatus vdp_st;
+
+ if (vc->video_mixer != VDP_INVALID_HANDLE)
+ return 0;
+
+ int feature_count = 0;
+ VdpVideoMixerFeature features[MAX_NUM_FEATURES];
+ VdpBool feature_enables[MAX_NUM_FEATURES];
+ static const VdpVideoMixerParameter parameters[VDP_NUM_MIXER_PARAMETER] = {
+ VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH,
+ VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT,
+ VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE,
+ };
+ const void *const parameter_values[VDP_NUM_MIXER_PARAMETER] = {
+ &vc->vid_width,
+ &vc->vid_height,
+ &vdp_chroma_type,
+ };
+ features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL;
+ if (vc->deint_type == 4)
+ features[feature_count++] =
+ VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL;
+ if (vc->pullup)
+ features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE;
+ if (vc->denoise)
+ features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION;
+ if (vc->sharpen)
+ features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_SHARPNESS;
+ if (vc->hqscaling) {
+ VdpVideoMixerFeature hqscaling_feature =
+ VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1 + vc->hqscaling-1;
+ VdpBool hqscaling_available;
+ vdp_st = vdp->video_mixer_query_feature_support(vc->vdp_device,
+ hqscaling_feature,
+ &hqscaling_available);
+ CHECK_ST_ERROR("Error when calling video_mixer_query_feature_support");
+ if (hqscaling_available)
+ features[feature_count++] = hqscaling_feature;
+ else
+ mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Your hardware or VDPAU "
+ "library does not support requested hqscaling.\n");
+ }
+
+ vdp_st = vdp->video_mixer_create(vc->vdp_device, feature_count, features,
+ VDP_NUM_MIXER_PARAMETER,
+ parameters, parameter_values,
+ &vc->video_mixer);
+ CHECK_ST_ERROR("Error when calling vdp_video_mixer_create");
+
+ for (i = 0; i < feature_count; i++)
+ feature_enables[i] = VDP_TRUE;
+ if (vc->deint < 3)
+ feature_enables[0] = VDP_FALSE;
+ if (vc->deint_type == 4 && vc->deint < 4)
+ feature_enables[1] = VDP_FALSE;
+ if (feature_count) {
+ vdp_st = vdp->video_mixer_set_feature_enables(vc->video_mixer,
+ feature_count, features,
+ feature_enables);
+ CHECK_ST_WARNING("Error calling vdp_video_mixer_set_feature_enables");
+ }
+ if (vc->denoise)
+ SET_VIDEO_ATTR(NOISE_REDUCTION_LEVEL, float, vc->denoise);
+ if (vc->sharpen)
+ SET_VIDEO_ATTR(SHARPNESS_LEVEL, float, vc->sharpen);
+ if (!vc->chroma_deint)
+ SET_VIDEO_ATTR(SKIP_CHROMA_DEINTERLACE, uint8_t, 1);
+
+ update_csc_matrix(vo);
+ return 0;
+}
+
+// Free everything specific to a certain video file
+static void free_video_specific(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ int i;
+ VdpStatus vdp_st;
+
+ if (vc->decoder != VDP_INVALID_HANDLE)
+ vdp->decoder_destroy(vc->decoder);
+ vc->decoder = VDP_INVALID_HANDLE;
+ vc->decoder_max_refs = -1;
+
+ forget_frames(vo);
+
+ for (i = 0; i < MAX_VIDEO_SURFACES; i++) {
+ if (vc->surface_render[i].surface != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->video_surface_destroy(vc->surface_render[i].surface);
+ CHECK_ST_WARNING("Error when calling vdp_video_surface_destroy");
+ }
+ vc->surface_render[i].surface = VDP_INVALID_HANDLE;
+ }
+
+ if (vc->video_mixer != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->video_mixer_destroy(vc->video_mixer);
+ CHECK_ST_WARNING("Error when calling vdp_video_mixer_destroy");
+ }
+ vc->video_mixer = VDP_INVALID_HANDLE;
+
+ if (vc->screenshot_surface != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->output_surface_destroy(vc->screenshot_surface);
+ CHECK_ST_WARNING("Error when calling vdp_output_surface_destroy");
+ }
+ vc->screenshot_surface = VDP_INVALID_HANDLE;
+}
+
+static int create_vdp_decoder(struct vo *vo, int max_refs)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ VdpStatus vdp_st;
+ VdpDecoderProfile vdp_decoder_profile;
+ if (vc->decoder != VDP_INVALID_HANDLE)
+ vdp->decoder_destroy(vc->decoder);
+ switch (vc->image_format) {
+ case IMGFMT_VDPAU_MPEG1:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG1;
+ break;
+ case IMGFMT_VDPAU_MPEG2:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG2_MAIN;
+ break;
+ case IMGFMT_VDPAU_H264:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_H264_HIGH;
+ mp_msg(MSGT_VO, MSGL_V, "[vdpau] Creating H264 hardware decoder "
+ "for %d reference frames.\n", max_refs);
+ break;
+ case IMGFMT_VDPAU_WMV3:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_MAIN;
+ break;
+ case IMGFMT_VDPAU_VC1:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_ADVANCED;
+ break;
+ case IMGFMT_VDPAU_MPEG4:
+ vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG4_PART2_ASP;
+ break;
+ default:
+ mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Unknown image format!\n");
+ goto fail;
+ }
+ vdp_st = vdp->decoder_create(vc->vdp_device, vdp_decoder_profile,
+ vc->vid_width, vc->vid_height, max_refs,
+ &vc->decoder);
+ CHECK_ST_WARNING("Failed creating VDPAU decoder");
+ if (vdp_st != VDP_STATUS_OK) {
+ fail:
+ vc->decoder = VDP_INVALID_HANDLE;
+ vc->decoder_max_refs = 0;
+ return 0;
+ }
+ vc->decoder_max_refs = max_refs;
+ return 1;
+}
+
+static int initialize_vdpau_objects(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+
+ vc->vdp_chroma_type = VDP_CHROMA_TYPE_420;
+ switch (vc->image_format) {
+ case IMGFMT_YV12:
+ case IMGFMT_I420:
+ case IMGFMT_IYUV:
+ vc->vdp_pixel_format = VDP_YCBCR_FORMAT_YV12;
+ break;
+ case IMGFMT_NV12:
+ vc->vdp_pixel_format = VDP_YCBCR_FORMAT_NV12;
+ break;
+ case IMGFMT_YUY2:
+ vc->vdp_pixel_format = VDP_YCBCR_FORMAT_YUYV;
+ vc->vdp_chroma_type = VDP_CHROMA_TYPE_422;
+ break;
+ case IMGFMT_UYVY:
+ vc->vdp_pixel_format = VDP_YCBCR_FORMAT_UYVY;
+ vc->vdp_chroma_type = VDP_CHROMA_TYPE_422;
+ }
+ if (win_x11_init_vdpau_flip_queue(vo) < 0)
+ return -1;
+
+ if (create_vdp_mixer(vo, vc->vdp_chroma_type) < 0)
+ return -1;
+
+ forget_frames(vo);
+ resize(vo);
+ return 0;
+}
+
+static void mark_vdpau_objects_uninitialized(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+
+ vc->decoder = VDP_INVALID_HANDLE;
+ for (int i = 0; i < MAX_VIDEO_SURFACES; i++)
+ vc->surface_render[i].surface = VDP_INVALID_HANDLE;
+ forget_frames(vo);
+ vc->video_mixer = VDP_INVALID_HANDLE;
+ vc->flip_queue = VDP_INVALID_HANDLE;
+ vc->flip_target = VDP_INVALID_HANDLE;
+ for (int i = 0; i < MAX_OUTPUT_SURFACES; i++)
+ vc->output_surfaces[i] = VDP_INVALID_HANDLE;
+ vc->screenshot_surface = VDP_INVALID_HANDLE;
+ vc->vdp_device = VDP_INVALID_HANDLE;
+ for (int i = 0; i < MAX_OSD_PARTS; i++) {
+ struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i];
+ talloc_free(sfc->packer);
+ sfc->bitmap_id = sfc->bitmap_pos_id = 0;
+ *sfc = (struct osd_bitmap_surface){
+ .surface = VDP_INVALID_HANDLE,
+ };
+ }
+ vc->output_surface_width = vc->output_surface_height = -1;
+}
+
+static int handle_preemption(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+
+ if (!vc->is_preempted)
+ return 0;
+ if (!vc->preemption_acked)
+ mark_vdpau_objects_uninitialized(vo);
+ vc->preemption_acked = true;
+ if (!vc->preemption_user_notified) {
+ mp_tmsg(MSGT_VO, MSGL_ERR, "[vdpau] Got display preemption notice! "
+ "Will attempt to recover.\n");
+ vc->preemption_user_notified = true;
+ }
+ /* Trying to initialize seems to be quite slow, so only try once a
+ * second to avoid using 100% CPU. */
+ if (vc->last_preemption_retry_fail
+ && GetTimerMS() - vc->last_preemption_retry_fail < 1000)
+ return -1;
+ if (win_x11_init_vdpau_procs(vo) < 0 || initialize_vdpau_objects(vo) < 0) {
+ vc->last_preemption_retry_fail = GetTimerMS() | 1;
+ return -1;
+ }
+ vc->last_preemption_retry_fail = 0;
+ vc->is_preempted = false;
+ vc->preemption_user_notified = false;
+ mp_tmsg(MSGT_VO, MSGL_INFO, "[vdpau] Recovered from display preemption.\n");
+ return 1;
+}
+
+/*
+ * connect to X server, create and map window, initialize all
+ * VDPAU objects, create different surfaces etc.
+ */
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vo_x11_state *x11 = vo->x11;
+ XVisualInfo vinfo;
+ XSetWindowAttributes xswa;
+ XWindowAttributes attribs;
+ unsigned long xswamask;
+ int depth;
+
+#ifdef CONFIG_XF86VM
+ int vm = flags & VOFLAG_MODESWITCHING;
+#endif
+
+ if (handle_preemption(vo) < 0)
+ return -1;
+
+ vc->flip = flags & VOFLAG_FLIPPING;
+ vc->image_format = format;
+ vc->vid_width = width;
+ vc->vid_height = height;
+
+ free_video_specific(vo);
+ if (IMGFMT_IS_VDPAU(vc->image_format) && !create_vdp_decoder(vo, 2))
+ return -1;
+
+#ifdef CONFIG_XF86VM
+ if (vm) {
+ vo_vm_switch(vo);
+ vc->mode_switched = true;
+ }
+#endif
+ XGetWindowAttributes(x11->display, DefaultRootWindow(x11->display),
+ &attribs);
+ depth = attribs.depth;
+ if (depth != 15 && depth != 16 && depth != 24 && depth != 32)
+ depth = 24;
+ XMatchVisualInfo(x11->display, x11->screen, depth, TrueColor, &vinfo);
+
+ xswa.background_pixel = 0;
+ xswa.border_pixel = 0;
+ /* Do not use CWBackPixel: It leads to VDPAU errors after
+ * aspect ratio changes. */
+ xswamask = CWBorderPixel;
+
+ vo_x11_create_vo_window(vo, &vinfo, vo->dx, vo->dy, d_width, d_height,
+ flags, CopyFromParent, "vdpau");
+ XChangeWindowAttributes(x11->display, x11->window, xswamask, &xswa);
+
+#ifdef CONFIG_XF86VM
+ if (vm) {
+ /* Grab the mouse pointer in our window */
+ if (vo_grabpointer)
+ XGrabPointer(x11->display, x11->window, True, 0,
+ GrabModeAsync, GrabModeAsync,
+ x11->window, None, CurrentTime);
+ XSetInputFocus(x11->display, x11->window, RevertToNone, CurrentTime);
+ }
+#endif
+
+ if (initialize_vdpau_objects(vo) < 0)
+ return -1;
+
+ return 0;
+}
+
+static void check_events(struct vo *vo)
+{
+ if (handle_preemption(vo) < 0)
+ return;
+
+ int e = vo_x11_check_events(vo);
+
+ if (e & VO_EVENT_RESIZE)
+ resize(vo);
+ else if (e & VO_EVENT_EXPOSE) {
+ vo->want_redraw = true;
+ }
+}
+
+static struct bitmap_packer *make_packer(struct vo *vo, VdpRGBAFormat format)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+
+ struct bitmap_packer *packer = talloc_zero(vo, struct bitmap_packer);
+ uint32_t w_max = 0, h_max = 0;
+ VdpStatus vdp_st = vdp->
+ bitmap_surface_query_capabilities(vc->vdp_device, format,
+ &(VdpBool){0}, &w_max, &h_max);
+ CHECK_ST_WARNING("Query to get max OSD surface size failed");
+ packer->w_max = w_max;
+ packer->h_max = h_max;
+ return packer;
+}
+
+static void draw_osd_part(struct vo *vo, int index)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ VdpStatus vdp_st;
+ struct osd_bitmap_surface *sfc = &vc->osd_surfaces[index];
+ VdpOutputSurface output_surface = vc->output_surfaces[vc->surface_num];
+ int i;
+
+ VdpOutputSurfaceRenderBlendState blend_state = {
+ .struct_version = VDP_OUTPUT_SURFACE_RENDER_BLEND_STATE_VERSION,
+ .blend_factor_source_color =
+ VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_SRC_ALPHA,
+ .blend_factor_source_alpha =
+ VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE,
+ .blend_factor_destination_color =
+ VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA,
+ .blend_factor_destination_alpha =
+ VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_SRC_ALPHA,
+ .blend_equation_color = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD,
+ .blend_equation_alpha = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD,
+ };
+
+ VdpOutputSurfaceRenderBlendState blend_state_premultiplied = blend_state;
+ blend_state_premultiplied.blend_factor_source_color =
+ VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE;
+
+ for (i = 0; i < sfc->render_count; i++) {
+ VdpOutputSurfaceRenderBlendState *blend = &blend_state;
+ if (sfc->format == VDP_RGBA_FORMAT_B8G8R8A8)
+ blend = &blend_state_premultiplied;
+ vdp_st = vdp->
+ output_surface_render_bitmap_surface(output_surface,
+ &sfc->targets[i].dest,
+ sfc->surface,
+ &sfc->targets[i].source,
+ &sfc->targets[i].color,
+ blend,
+ VDP_OUTPUT_SURFACE_RENDER_ROTATE_0);
+ CHECK_ST_WARNING("OSD: Error when rendering");
+ }
+}
+
+static void generate_osd_part(struct vo *vo, struct sub_bitmaps *imgs)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ VdpStatus vdp_st;
+ struct osd_bitmap_surface *sfc = &vc->osd_surfaces[imgs->render_index];
+ bool need_upload = false;
+
+ if (imgs->bitmap_pos_id == sfc->bitmap_pos_id)
+ return; // Nothing changed and we still have the old data
+
+ sfc->render_count = 0;
+
+ if (imgs->format == SUBBITMAP_EMPTY || imgs->num_parts == 0)
+ return;
+
+ if (imgs->bitmap_id == sfc->bitmap_id)
+ goto osd_skip_upload;
+
+ need_upload = true;
+ VdpRGBAFormat format;
+ int format_size;
+ switch (imgs->format) {
+ case SUBBITMAP_LIBASS:
+ format = VDP_RGBA_FORMAT_A8;
+ format_size = 1;
+ break;
+ case SUBBITMAP_RGBA:
+ format = VDP_RGBA_FORMAT_B8G8R8A8;
+ format_size = 4;
+ break;
+ default:
+ abort();
+ };
+ if (sfc->format != format) {
+ talloc_free(sfc->packer);
+ sfc->packer = NULL;
+ };
+ sfc->format = format;
+ if (!sfc->packer)
+ sfc->packer = make_packer(vo, format);
+ sfc->packer->padding = imgs->scaled; // assume 2x2 filter on scaling
+ int r = packer_pack_from_subbitmaps(sfc->packer, imgs);
+ if (r < 0) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] OSD bitmaps do not fit on "
+ "a surface with the maximum supported size\n");
+ return;
+ } else if (r == 1) {
+ if (sfc->surface != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->bitmap_surface_destroy(sfc->surface);
+ CHECK_ST_WARNING("Error when calling vdp_bitmap_surface_destroy");
+ }
+ mp_msg(MSGT_VO, MSGL_V, "[vdpau] Allocating a %dx%d surface for "
+ "OSD bitmaps.\n", sfc->packer->w, sfc->packer->h);
+ vdp_st = vdp->bitmap_surface_create(vc->vdp_device, format,
+ sfc->packer->w, sfc->packer->h,
+ true, &sfc->surface);
+ if (vdp_st != VDP_STATUS_OK)
+ sfc->surface = VDP_INVALID_HANDLE;
+ CHECK_ST_WARNING("OSD: error when creating surface");
+ }
+ if (imgs->scaled) {
+ char zeros[sfc->packer->used_width * format_size];
+ memset(zeros, 0, sizeof(zeros));
+ vdp_st = vdp->bitmap_surface_put_bits_native(sfc->surface,
+ &(const void *){zeros}, &(uint32_t){0},
+ &(VdpRect){0, 0, sfc->packer->used_width,
+ sfc->packer->used_height});
+ }
+
+osd_skip_upload:
+ if (sfc->surface == VDP_INVALID_HANDLE)
+ return;
+ if (sfc->packer->count > sfc->targets_size) {
+ talloc_free(sfc->targets);
+ sfc->targets_size = sfc->packer->count;
+ sfc->targets = talloc_size(vc, sfc->targets_size
+ * sizeof(*sfc->targets));
+ }
+
+ for (int i = 0 ;i < sfc->packer->count; i++) {
+ struct sub_bitmap *b = &imgs->parts[i];
+ struct osd_target *target = sfc->targets + sfc->render_count;
+ int x = sfc->packer->result[i].x;
+ int y = sfc->packer->result[i].y;
+ target->source = (VdpRect){x, y, x + b->w, y + b->h};
+ target->dest = (VdpRect){b->x, b->y, b->x + b->dw, b->y + b->dh};
+ target->color = (VdpColor){1, 1, 1, 1};
+ if (imgs->format == SUBBITMAP_LIBASS) {
+ uint32_t color = b->libass.color;
+ target->color.alpha = 1.0 - ((color >> 0) & 0xff) / 255.0;
+ target->color.blue = ((color >> 8) & 0xff) / 255.0;
+ target->color.green = ((color >> 16) & 0xff) / 255.0;
+ target->color.red = ((color >> 24) & 0xff) / 255.0;
+ }
+ if (need_upload) {
+ vdp_st = vdp->
+ bitmap_surface_put_bits_native(sfc->surface,
+ &(const void *){b->bitmap},
+ &(uint32_t){b->stride},
+ &target->source);
+ CHECK_ST_WARNING("OSD: putbits failed");
+ }
+ sfc->render_count++;
+ }
+
+ sfc->bitmap_id = imgs->bitmap_id;
+ sfc->bitmap_pos_id = imgs->bitmap_pos_id;
+}
+
+static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs)
+{
+ struct vo *vo = ctx;
+ generate_osd_part(vo, imgs);
+ draw_osd_part(vo, imgs->render_index);
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ struct vdpctx *vc = vo->priv;
+
+ if (!status_ok(vo))
+ return;
+
+ static const bool formats[SUBBITMAP_COUNT] = {
+ [SUBBITMAP_LIBASS] = true,
+ [SUBBITMAP_RGBA] = true,
+ };
+
+ osd_draw(osd, vc->osd_rect, osd->vo_pts, 0, formats, draw_osd_cb, vo);
+}
+
+static int update_presentation_queue_status(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ VdpStatus vdp_st;
+
+ while (vc->query_surface_num != vc->surface_num) {
+ VdpTime vtime;
+ VdpPresentationQueueStatus status;
+ VdpOutputSurface surface = vc->output_surfaces[vc->query_surface_num];
+ vdp_st = vdp->presentation_queue_query_surface_status(vc->flip_queue,
+ surface,
+ &status, &vtime);
+ CHECK_ST_WARNING("Error calling "
+ "presentation_queue_query_surface_status");
+ if (status == VDP_PRESENTATION_QUEUE_STATUS_QUEUED)
+ break;
+ if (vc->vsync_interval > 1) {
+ uint64_t qtime = vc->queue_time[vc->query_surface_num];
+ if (vtime < qtime + vc->vsync_interval / 2)
+ mp_msg(MSGT_VO, MSGL_V, "[vdpau] Frame shown too early\n");
+ if (vtime > qtime + vc->vsync_interval)
+ mp_msg(MSGT_VO, MSGL_V, "[vdpau] Frame shown late\n");
+ }
+ vc->query_surface_num = WRAP_ADD(vc->query_surface_num, 1,
+ vc->num_output_surfaces);
+ vc->recent_vsync_time = vtime;
+ }
+ int num_queued = WRAP_ADD(vc->surface_num, -vc->query_surface_num,
+ vc->num_output_surfaces);
+ mp_msg(MSGT_VO, MSGL_DBG3, "[vdpau] Queued surface count (before add): "
+ "%d\n", num_queued);
+ return num_queued;
+}
+
+static inline uint64_t prev_vs2(struct vdpctx *vc, uint64_t ts, int shift)
+{
+ uint64_t offset = ts - vc->recent_vsync_time;
+ // Fix negative values for 1<<shift vsyncs before vc->recent_vsync_time
+ offset += (uint64_t)vc->vsync_interval << shift;
+ offset %= vc->vsync_interval;
+ return ts - offset;
+}
+
+static void flip_page_timed(struct vo *vo, unsigned int pts_us, int duration)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ VdpStatus vdp_st;
+ uint32_t vsync_interval = vc->vsync_interval;
+
+ if (handle_preemption(vo) < 0)
+ return;
+
+ if (duration > INT_MAX / 1000)
+ duration = -1;
+ else
+ duration *= 1000;
+
+ if (vc->vsync_interval == 1)
+ duration = -1; // Make sure drop logic is disabled
+
+ uint64_t now = sync_vdptime(vo);
+ uint64_t pts = pts_us ? convert_to_vdptime(vo, pts_us) : now;
+ uint64_t ideal_pts = pts;
+ uint64_t npts = duration >= 0 ? pts + duration : UINT64_MAX;
+
+#define PREV_VS2(ts, shift) prev_vs2(vc, ts, shift)
+ // Only gives accurate results for ts >= vc->recent_vsync_time
+#define PREV_VSYNC(ts) PREV_VS2(ts, 0)
+
+ /* We hope to be here at least one vsync before the frame should be shown.
+ * If we are running late then don't drop the frame unless there is
+ * already one queued for the next vsync; even if we _hope_ to show the
+ * next frame soon enough to mean this one should be dropped we might
+ * not make the target time in reality. Without this check we could drop
+ * every frame, freezing the display completely if video lags behind.
+ */
+ if (now > PREV_VSYNC(FFMAX(pts, vc->last_queue_time + vsync_interval)))
+ npts = UINT64_MAX;
+
+ /* Allow flipping a frame at a vsync if its presentation time is a
+ * bit after that vsync and the change makes the flip time delta
+ * from previous frame better match the target timestamp delta.
+ * This avoids instability with frame timestamps falling near vsyncs.
+ * For example if the frame timestamps were (with vsyncs at
+ * integer values) 0.01, 1.99, 4.01, 5.99, 8.01, ... then
+ * straightforward timing at next vsync would flip the frames at
+ * 1, 2, 5, 6, 9; this changes it to 1, 2, 4, 6, 8 and so on with
+ * regular 2-vsync intervals.
+ *
+ * Also allow moving the frame forward if it looks like we dropped
+ * the previous frame incorrectly (now that we know better after
+ * having final exact timestamp information for this frame) and
+ * there would unnecessarily be a vsync without a frame change.
+ */
+ uint64_t vsync = PREV_VSYNC(pts);
+ if (pts < vsync + vsync_interval / 4
+ && (vsync - PREV_VS2(vc->last_queue_time, 16)
+ > pts - vc->last_ideal_time + vsync_interval / 2
+ || vc->dropped_frame && vsync > vc->dropped_time))
+ pts -= vsync_interval / 2;
+
+ vc->dropped_frame = true; // changed at end if false
+ vc->dropped_time = ideal_pts;
+
+ pts = FFMAX(pts, vc->last_queue_time + vsync_interval);
+ pts = FFMAX(pts, now);
+ if (npts < PREV_VSYNC(pts) + vsync_interval)
+ return;
+
+ int num_flips = update_presentation_queue_status(vo);
+ vsync = vc->recent_vsync_time + num_flips * vc->vsync_interval;
+ now = sync_vdptime(vo);
+ pts = FFMAX(pts, now);
+ pts = FFMAX(pts, vsync + (vsync_interval >> 2));
+ vsync = PREV_VSYNC(pts);
+ if (npts < vsync + vsync_interval)
+ return;
+ pts = vsync + (vsync_interval >> 2);
+ vdp_st =
+ vdp->presentation_queue_display(vc->flip_queue,
+ vc->output_surfaces[vc->surface_num],
+ vo->dwidth, vo->dheight, pts);
+ CHECK_ST_WARNING("Error when calling vdp_presentation_queue_display");
+
+ vc->last_queue_time = pts;
+ vc->queue_time[vc->surface_num] = pts;
+ vc->last_ideal_time = ideal_pts;
+ vc->dropped_frame = false;
+ vc->surface_num = WRAP_ADD(vc->surface_num, 1, vc->num_output_surfaces);
+}
+
+static int draw_slice(struct vo *vo, uint8_t *image[], int stride[], int w,
+ int h, int x, int y)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ VdpStatus vdp_st;
+
+ if (handle_preemption(vo) < 0)
+ return VO_TRUE;
+
+ struct vdpau_render_state *rndr = (struct vdpau_render_state *)image[0];
+ int max_refs = vc->image_format == IMGFMT_VDPAU_H264 ?
+ rndr->info.h264.num_ref_frames : 2;
+ if (!IMGFMT_IS_VDPAU(vc->image_format))
+ return VO_FALSE;
+ if ((vc->decoder == VDP_INVALID_HANDLE || vc->decoder_max_refs < max_refs)
+ && !create_vdp_decoder(vo, max_refs))
+ return VO_FALSE;
+
+ vdp_st = vdp->decoder_render(vc->decoder, rndr->surface,
+ (void *)&rndr->info,
+ rndr->bitstream_buffers_used,
+ rndr->bitstream_buffers);
+ CHECK_ST_WARNING("Failed VDPAU decoder rendering");
+ return VO_TRUE;
+}
+
+
+static struct vdpau_render_state *get_surface(struct vo *vo, int number)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+
+ if (number >= MAX_VIDEO_SURFACES)
+ return NULL;
+ if (vc->surface_render[number].surface == VDP_INVALID_HANDLE
+ && !vc->is_preempted) {
+ VdpStatus vdp_st;
+ vdp_st = vdp->video_surface_create(vc->vdp_device, vc->vdp_chroma_type,
+ vc->vid_width, vc->vid_height,
+ &vc->surface_render[number].surface);
+ CHECK_ST_WARNING("Error when calling vdp_video_surface_create");
+ }
+ mp_msg(MSGT_VO, MSGL_DBG3, "vdpau vid create: %u\n",
+ vc->surface_render[number].surface);
+ return &vc->surface_render[number];
+}
+
+static void draw_image(struct vo *vo, mp_image_t *mpi, double pts)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+ struct mp_image *reserved_mpi = NULL;
+ struct vdpau_render_state *rndr;
+
+ if (IMGFMT_IS_VDPAU(vc->image_format)) {
+ rndr = mpi->priv;
+ reserved_mpi = mpi;
+ } else if (!(mpi->flags & MP_IMGFLAG_DRAW_CALLBACK)) {
+ rndr = get_surface(vo, vc->deint_counter);
+ vc->deint_counter = WRAP_ADD(vc->deint_counter, 1, NUM_BUFFERED_VIDEO);
+ if (handle_preemption(vo) >= 0) {
+ VdpStatus vdp_st;
+ const void *destdata[3] = {mpi->planes[0], mpi->planes[2],
+ mpi->planes[1]};
+ if (vc->image_format == IMGFMT_NV12)
+ destdata[1] = destdata[2];
+ vdp_st = vdp->video_surface_put_bits_y_cb_cr(rndr->surface,
+ vc->vdp_pixel_format, destdata, mpi->stride);
+ CHECK_ST_WARNING("Error when calling "
+ "vdp_video_surface_put_bits_y_cb_cr");
+ }
+ } else
+ // We don't support slice callbacks so this shouldn't occur -
+ // I think the flags test above in pointless, but I'm adding
+ // this instead of removing it just in case.
+ abort();
+ if (mpi->fields & MP_IMGFIELD_ORDERED)
+ vc->top_field_first = !!(mpi->fields & MP_IMGFIELD_TOP_FIRST);
+ else
+ vc->top_field_first = 1;
+
+ add_new_video_surface(vo, rndr->surface, reserved_mpi, pts);
+
+ return;
+}
+
+// warning: the size and pixel format of surface must match that of the
+// surfaces in vc->output_surfaces
+static struct mp_image *read_output_surface(struct vdpctx *vc,
+ VdpOutputSurface surface,
+ int width, int height)
+{
+ VdpStatus vdp_st;
+ struct vdp_functions *vdp = vc->vdp;
+ struct mp_image *image = alloc_mpi(width, height, IMGFMT_BGR32);
+ image->colorspace = MP_CSP_RGB;
+ image->levels = vc->colorspace.levels_out; // hardcoded with conv. matrix
+
+ void *dst_planes[] = { image->planes[0] };
+ uint32_t dst_pitches[] = { image->stride[0] };
+ vdp_st = vdp->output_surface_get_bits_native(surface, NULL, dst_planes,
+ dst_pitches);
+ CHECK_ST_WARNING("Error when calling vdp_output_surface_get_bits_native");
+
+ return image;
+}
+
+static struct mp_image *get_screenshot(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+ VdpStatus vdp_st;
+ struct vdp_functions *vdp = vc->vdp;
+
+ if (vc->screenshot_surface == VDP_INVALID_HANDLE) {
+ vdp_st = vdp->output_surface_create(vc->vdp_device,
+ OUTPUT_RGBA_FORMAT,
+ vc->vid_width, vc->vid_height,
+ &vc->screenshot_surface);
+ CHECK_ST_WARNING("Error when calling vdp_output_surface_create");
+ }
+
+ VdpRect rc = { .x1 = vc->vid_width, .y1 = vc->vid_height };
+ render_video_to_output_surface(vo, vc->screenshot_surface, &rc);
+
+ struct mp_image *image = read_output_surface(vc, vc->screenshot_surface,
+ vc->vid_width, vc->vid_height);
+
+ image->display_w = vo->aspdat.prew;
+ image->display_h = vo->aspdat.preh;
+
+ return image;
+}
+
+static struct mp_image *get_window_screenshot(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+ int last_surface = WRAP_ADD(vc->surface_num, -1, vc->num_output_surfaces);
+ VdpOutputSurface screen = vc->output_surfaces[last_surface];
+ struct mp_image *image = read_output_surface(vo->priv, screen,
+ vc->output_surface_width,
+ vc->output_surface_height);
+ image->width = image->w = vo->dwidth;
+ image->height = image->h = vo->dheight;
+ return image;
+}
+
+static uint32_t get_image(struct vo *vo, mp_image_t *mpi)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdpau_render_state *rndr;
+
+ // no dr for non-decoding for now
+ if (!IMGFMT_IS_VDPAU(vc->image_format))
+ return VO_FALSE;
+ if (mpi->type != MP_IMGTYPE_NUMBERED)
+ return VO_FALSE;
+
+ rndr = get_surface(vo, mpi->number);
+ if (!rndr) {
+ mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] no surfaces available in "
+ "get_image\n");
+ // TODO: this probably breaks things forever, provide a dummy buffer?
+ return VO_FALSE;
+ }
+ mpi->flags |= MP_IMGFLAG_DIRECT;
+ mpi->stride[0] = mpi->stride[1] = mpi->stride[2] = 0;
+ mpi->planes[0] = mpi->planes[1] = mpi->planes[2] = NULL;
+ // hack to get around a check and to avoid a special-case in vd_ffmpeg.c
+ mpi->planes[0] = (void *)rndr;
+ mpi->num_planes = 1;
+ mpi->priv = rndr;
+ return VO_TRUE;
+}
+
+static int query_format(uint32_t format)
+{
+ int default_flags = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW
+ | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_OSD
+ | VFCAP_FLIP;
+ switch (format) {
+ case IMGFMT_YV12:
+ case IMGFMT_I420:
+ case IMGFMT_IYUV:
+ case IMGFMT_NV12:
+ case IMGFMT_YUY2:
+ case IMGFMT_UYVY:
+ return default_flags | VOCAP_NOSLICES;
+ case IMGFMT_VDPAU_MPEG1:
+ case IMGFMT_VDPAU_MPEG2:
+ case IMGFMT_VDPAU_H264:
+ case IMGFMT_VDPAU_WMV3:
+ case IMGFMT_VDPAU_VC1:
+ case IMGFMT_VDPAU_MPEG4:
+ return default_flags;
+ }
+ return 0;
+}
+
+static void destroy_vdpau_objects(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+
+ int i;
+ VdpStatus vdp_st;
+
+ free_video_specific(vo);
+
+ if (vc->flip_queue != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->presentation_queue_destroy(vc->flip_queue);
+ CHECK_ST_WARNING("Error when calling vdp_presentation_queue_destroy");
+ }
+
+ if (vc->flip_target != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->presentation_queue_target_destroy(vc->flip_target);
+ CHECK_ST_WARNING("Error when calling "
+ "vdp_presentation_queue_target_destroy");
+ }
+
+ for (i = 0; i < vc->num_output_surfaces; i++) {
+ if (vc->output_surfaces[i] == VDP_INVALID_HANDLE)
+ continue;
+ vdp_st = vdp->output_surface_destroy(vc->output_surfaces[i]);
+ CHECK_ST_WARNING("Error when calling vdp_output_surface_destroy");
+ }
+
+ for (int i = 0; i < MAX_OSD_PARTS; i++) {
+ struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i];
+ if (sfc->surface != VDP_INVALID_HANDLE) {
+ vdp_st = vdp->bitmap_surface_destroy(sfc->surface);
+ CHECK_ST_WARNING("Error when calling vdp_bitmap_surface_destroy");
+ }
+ }
+
+ vdp_st = vdp->device_destroy(vc->vdp_device);
+ CHECK_ST_WARNING("Error when calling vdp_device_destroy");
+}
+
+static void uninit(struct vo *vo)
+{
+ struct vdpctx *vc = vo->priv;
+
+ /* Destroy all vdpau objects */
+ destroy_vdpau_objects(vo);
+
+#ifdef CONFIG_XF86VM
+ if (vc->mode_switched)
+ vo_vm_close(vo);
+#endif
+ vo_x11_uninit(vo);
+
+ // Free bitstream buffers allocated by FFmpeg
+ for (int i = 0; i < MAX_VIDEO_SURFACES; i++)
+ av_freep(&vc->surface_render[i].bitstream_buffers);
+}
+
+static int preinit(struct vo *vo, const char *arg)
+{
+ struct vdpctx *vc = vo->priv;
+
+ // Mark everything as invalid first so uninit() can tell what has been
+ // allocated
+ mark_vdpau_objects_uninitialized(vo);
+
+ vc->colorspace = (struct mp_csp_details) MP_CSP_DETAILS_DEFAULTS;
+ vc->video_eq.capabilities = MP_CSP_EQ_CAPS_COLORMATRIX;
+
+ vc->deint_type = vc->deint ? FFABS(vc->deint) : 3;
+ if (vc->deint < 0)
+ vc->deint = 0;
+
+ if (!vo_init(vo))
+ return -1;
+
+ // After this calling uninit() should work to free resources
+
+ if (win_x11_init_vdpau_procs(vo) < 0) {
+ if (vc->vdp && vc->vdp->device_destroy)
+ vc->vdp->device_destroy(vc->vdp_device);
+ vo_x11_uninit(vo);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int get_equalizer(struct vo *vo, const char *name, int *value)
+{
+ struct vdpctx *vc = vo->priv;
+ return mp_csp_equalizer_get(&vc->video_eq, name, value) >= 0 ?
+ VO_TRUE : VO_NOTIMPL;
+}
+
+static bool status_ok(struct vo *vo)
+{
+ if (!vo->config_ok || handle_preemption(vo) < 0)
+ return false;
+ return true;
+}
+
+static int set_equalizer(struct vo *vo, const char *name, int value)
+{
+ struct vdpctx *vc = vo->priv;
+
+ if (mp_csp_equalizer_set(&vc->video_eq, name, value) < 0)
+ return VO_NOTIMPL;
+
+ if (status_ok(vo))
+ update_csc_matrix(vo);
+ return true;
+}
+
+static void checked_resize(struct vo *vo)
+{
+ if (!status_ok(vo))
+ return;
+ resize(vo);
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct vdpctx *vc = vo->priv;
+ struct vdp_functions *vdp = vc->vdp;
+
+ handle_preemption(vo);
+
+ switch (request) {
+ case VOCTRL_GET_DEINTERLACE:
+ *(int *)data = vc->deint;
+ return VO_TRUE;
+ case VOCTRL_SET_DEINTERLACE:
+ vc->deint = *(int *)data;
+ if (vc->deint)
+ vc->deint = vc->deint_type;
+ if (vc->deint_type > 2 && status_ok(vo)) {
+ VdpStatus vdp_st;
+ VdpVideoMixerFeature features[1] =
+ {vc->deint_type == 3 ?
+ VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL :
+ VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL};
+ VdpBool feature_enables[1] = {vc->deint ? VDP_TRUE : VDP_FALSE};
+ vdp_st = vdp->video_mixer_set_feature_enables(vc->video_mixer,
+ 1, features,
+ feature_enables);
+ CHECK_ST_WARNING("Error changing deinterlacing settings");
+ }
+ vo->want_redraw = true;
+ return VO_TRUE;
+ case VOCTRL_PAUSE:
+ if (vc->dropped_frame)
+ vo->want_redraw = true;
+ return true;
+ case VOCTRL_QUERY_FORMAT:
+ return query_format(*(uint32_t *)data);
+ case VOCTRL_GET_IMAGE:
+ return get_image(vo, data);
+ case VOCTRL_DRAW_IMAGE:
+ abort(); // draw_image() should get called directly
+ case VOCTRL_BORDER:
+ vo_x11_border(vo);
+ checked_resize(vo);
+ return VO_TRUE;
+ case VOCTRL_FULLSCREEN:
+ vo_x11_fullscreen(vo);
+ checked_resize(vo);
+ return VO_TRUE;
+ case VOCTRL_GET_PANSCAN:
+ return VO_TRUE;
+ case VOCTRL_SET_PANSCAN:
+ checked_resize(vo);
+ return VO_TRUE;
+ case VOCTRL_SET_EQUALIZER: {
+ vo->want_redraw = true;
+ struct voctrl_set_equalizer_args *args = data;
+ return set_equalizer(vo, args->name, args->value);
+ }
+ case VOCTRL_GET_EQUALIZER: {
+ struct voctrl_get_equalizer_args *args = data;
+ return get_equalizer(vo, args->name, args->valueptr);
+ }
+ case VOCTRL_SET_YUV_COLORSPACE:
+ vc->colorspace = *(struct mp_csp_details *)data;
+ if (status_ok(vo))
+ update_csc_matrix(vo);
+ vo->want_redraw = true;
+ return true;
+ case VOCTRL_GET_YUV_COLORSPACE:
+ *(struct mp_csp_details *)data = vc->colorspace;
+ return true;
+ case VOCTRL_ONTOP:
+ vo_x11_ontop(vo);
+ return VO_TRUE;
+ case VOCTRL_UPDATE_SCREENINFO:
+ update_xinerama_info(vo);
+ return VO_TRUE;
+ case VOCTRL_NEWFRAME:
+ vc->deint_queue_pos = next_deint_queue_pos(vo, true);
+ if (status_ok(vo))
+ video_to_output_surface(vo);
+ return true;
+ case VOCTRL_SKIPFRAME:
+ vc->deint_queue_pos = next_deint_queue_pos(vo, true);
+ return true;
+ case VOCTRL_REDRAW_FRAME:
+ if (status_ok(vo))
+ video_to_output_surface(vo);
+ return true;
+ case VOCTRL_RESET:
+ forget_frames(vo);
+ return true;
+ case VOCTRL_SCREENSHOT: {
+ if (!status_ok(vo))
+ return false;
+ struct voctrl_screenshot_args *args = data;
+ if (args->full_window)
+ args->out_image = get_window_screenshot(vo);
+ else
+ args->out_image = get_screenshot(vo);
+ return true;
+ }
+ }
+ return VO_NOTIMPL;
+}
+
+#undef OPT_BASE_STRUCT
+#define OPT_BASE_STRUCT struct vdpctx
+
+const struct vo_driver video_out_vdpau = {
+ .is_new = true,
+ .buffer_frames = true,
+ .info = &(const struct vo_info_s){
+ "VDPAU with X11",
+ "vdpau",
+ "Rajib Mahapatra <rmahapatra@nvidia.com> and others",
+ ""
+ },
+ .preinit = preinit,
+ .config = config,
+ .control = control,
+ .draw_image = draw_image,
+ .get_buffered_frame = set_next_frame_info,
+ .draw_slice = draw_slice,
+ .draw_osd = draw_osd,
+ .flip_page_timed = flip_page_timed,
+ .check_events = check_events,
+ .uninit = uninit,
+ .priv_size = sizeof(struct vdpctx),
+ .options = (const struct m_option []){
+ OPT_INTRANGE("deint", deint, 0, -4, 4),
+ OPT_FLAG_ON("chroma-deint", chroma_deint, 0, OPTDEF_INT(1)),
+ OPT_FLAG_OFF("nochroma-deint", chroma_deint, 0),
+ OPT_MAKE_FLAGS("pullup", pullup, 0),
+ OPT_FLOATRANGE("denoise", denoise, 0, 0, 1),
+ OPT_FLOATRANGE("sharpen", sharpen, 0, -1, 1),
+ OPT_INTRANGE("hqscaling", hqscaling, 0, 0, 9),
+ OPT_FLOAT("fps", user_fps, 0),
+ OPT_FLAG_ON("composite-detect", composite_detect, 0, OPTDEF_INT(1)),
+ OPT_INT("queuetime_windowed", flip_offset_window, 0, OPTDEF_INT(50)),
+ OPT_INT("queuetime_fs", flip_offset_fs, 0, OPTDEF_INT(50)),
+ OPT_INTRANGE("output_surfaces", num_output_surfaces, 0,
+ 2, MAX_OUTPUT_SURFACES, OPTDEF_INT(3)),
+ {NULL},
+ }
+};
diff --git a/video/out/vo_x11.c b/video/out/vo_x11.c
new file mode 100644
index 0000000000..2358b0a295
--- /dev/null
+++ b/video/out/vo_x11.c
@@ -0,0 +1,620 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "config.h"
+#include "video_out.h"
+#include "aspect.h"
+#include "csputils.h"
+#include "libmpcodecs/mp_image.h"
+#include "libmpcodecs/vfcap.h"
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <errno.h>
+
+#include "x11_common.h"
+
+#ifdef HAVE_SHM
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/XShm.h>
+#endif
+
+#include "sub/sub.h"
+
+#include "libmpcodecs/sws_utils.h"
+#define MODE_RGB 0x1
+#define MODE_BGR 0x2
+
+#include "mp_msg.h"
+
+extern int sws_flags;
+
+struct priv {
+ struct vo *vo;
+
+ /* local data */
+ unsigned char *ImageData;
+ //! original unaligned pointer for free
+ unsigned char *ImageDataOrig;
+
+ /* X11 related variables */
+ XImage *myximage;
+ int depth, bpp;
+ XWindowAttributes attribs;
+
+ int int_pause;
+
+ int Flip_Flag;
+ int zoomFlag;
+
+ uint32_t image_width;
+ uint32_t image_height;
+ uint32_t in_format;
+ uint32_t out_format;
+ int out_offset;
+ int srcW;
+ int srcH;
+
+ int old_vo_dwidth;
+ int old_vo_dheight;
+
+ struct SwsContext *swsContext;
+ int dst_width;
+
+ XVisualInfo vinfo;
+
+ int firstTime;
+
+#ifdef HAVE_SHM
+ int Shmem_Flag;
+
+ XShmSegmentInfo Shminfo[1];
+ int gXErrorFlag;
+ int CompletionType;
+#endif
+};
+
+static void flip_page(struct vo *vo);
+
+static void check_events(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+
+ int ret = vo_x11_check_events(vo);
+
+ if (ret & VO_EVENT_RESIZE)
+ vo_x11_clearwindow(vo, vo->x11->window);
+ else if (ret & VO_EVENT_EXPOSE)
+ vo_x11_clearwindow_part(vo, vo->x11->window, p->myximage->width,
+ p->myximage->height);
+ if (ret & VO_EVENT_EXPOSE && p->int_pause)
+ flip_page(vo);
+}
+
+static void getMyXImage(struct priv *p)
+{
+ struct vo *vo = p->vo;
+#ifdef HAVE_SHM
+ if (vo->x11->display_is_local && XShmQueryExtension(vo->x11->display))
+ p->Shmem_Flag = 1;
+ else {
+ p->Shmem_Flag = 0;
+ mp_msg(MSGT_VO, MSGL_WARN,
+ "Shared memory not supported\nReverting to normal Xlib\n");
+ }
+ if (p->Shmem_Flag)
+ p->CompletionType = XShmGetEventBase(vo->x11->display) + ShmCompletion;
+
+ if (p->Shmem_Flag) {
+ p->myximage =
+ XShmCreateImage(vo->x11->display, p->vinfo.visual, p->depth,
+ ZPixmap, NULL, &p->Shminfo[0], p->image_width,
+ p->image_height);
+ if (p->myximage == NULL) {
+ mp_msg(MSGT_VO, MSGL_WARN,
+ "Shared memory error,disabling ( Ximage error )\n");
+ goto shmemerror;
+ }
+ p->Shminfo[0].shmid = shmget(IPC_PRIVATE,
+ p->myximage->bytes_per_line *
+ p->myximage->height,
+ IPC_CREAT | 0777);
+ if (p->Shminfo[0].shmid < 0) {
+ XDestroyImage(p->myximage);
+ mp_msg(MSGT_VO, MSGL_V, "%s\n", strerror(errno));
+ //perror( strerror( errno ) );
+ mp_msg(MSGT_VO, MSGL_WARN,
+ "Shared memory error,disabling ( seg id error )\n");
+ goto shmemerror;
+ }
+ p->Shminfo[0].shmaddr = (char *) shmat(p->Shminfo[0].shmid, 0, 0);
+
+ if (p->Shminfo[0].shmaddr == ((char *) -1)) {
+ XDestroyImage(p->myximage);
+ if (p->Shminfo[0].shmaddr != ((char *) -1))
+ shmdt(p->Shminfo[0].shmaddr);
+ mp_msg(MSGT_VO, MSGL_WARN,
+ "Shared memory error,disabling ( address error )\n");
+ goto shmemerror;
+ }
+ p->myximage->data = p->Shminfo[0].shmaddr;
+ p->ImageData = (unsigned char *) p->myximage->data;
+ p->Shminfo[0].readOnly = False;
+ XShmAttach(vo->x11->display, &p->Shminfo[0]);
+
+ XSync(vo->x11->display, False);
+
+ if (p->gXErrorFlag) {
+ XDestroyImage(p->myximage);
+ shmdt(p->Shminfo[0].shmaddr);
+ mp_msg(MSGT_VO, MSGL_WARN, "Shared memory error,disabling.\n");
+ p->gXErrorFlag = 0;
+ goto shmemerror;
+ } else
+ shmctl(p->Shminfo[0].shmid, IPC_RMID, 0);
+
+ if (!p->firstTime) {
+ mp_msg(MSGT_VO, MSGL_V, "Sharing memory.\n");
+ p->firstTime = 1;
+ }
+ } else {
+shmemerror:
+ p->Shmem_Flag = 0;
+#endif
+ p->myximage =
+ XCreateImage(vo->x11->display, p->vinfo.visual, p->depth, ZPixmap,
+ 0, NULL, p->image_width, p->image_height, 8, 0);
+ p->ImageDataOrig =
+ malloc(p->myximage->bytes_per_line * p->image_height + 32);
+ p->myximage->data = p->ImageDataOrig + 16 - ((long)p->ImageDataOrig & 15);
+ memset(p->myximage->data, 0, p->myximage->bytes_per_line * p->image_height);
+ p->ImageData = p->myximage->data;
+#ifdef HAVE_SHM
+}
+#endif
+}
+
+static void freeMyXImage(struct priv *p)
+{
+ struct vo *vo = p->vo;
+#ifdef HAVE_SHM
+ if (p->Shmem_Flag) {
+ XShmDetach(vo->x11->display, &p->Shminfo[0]);
+ XDestroyImage(p->myximage);
+ shmdt(p->Shminfo[0].shmaddr);
+ } else
+#endif
+ {
+ p->myximage->data = p->ImageDataOrig;
+ XDestroyImage(p->myximage);
+ p->ImageDataOrig = NULL;
+ }
+ p->myximage = NULL;
+ p->ImageData = NULL;
+}
+
+#if BYTE_ORDER == BIG_ENDIAN
+#define BO_NATIVE MSBFirst
+#define BO_NONNATIVE LSBFirst
+#else
+#define BO_NATIVE LSBFirst
+#define BO_NONNATIVE MSBFirst
+#endif
+const struct fmt2Xfmtentry_s {
+ uint32_t mpfmt;
+ int byte_order;
+ unsigned red_mask;
+ unsigned green_mask;
+ unsigned blue_mask;
+} fmt2Xfmt[] = {
+ {IMGFMT_RGB8, BO_NATIVE, 0x00000007, 0x00000038, 0x000000C0},
+ {IMGFMT_RGB8, BO_NONNATIVE, 0x00000007, 0x00000038, 0x000000C0},
+ {IMGFMT_BGR8, BO_NATIVE, 0x000000E0, 0x0000001C, 0x00000003},
+ {IMGFMT_BGR8, BO_NONNATIVE, 0x000000E0, 0x0000001C, 0x00000003},
+ {IMGFMT_RGB15, BO_NATIVE, 0x0000001F, 0x000003E0, 0x00007C00},
+ {IMGFMT_BGR15, BO_NATIVE, 0x00007C00, 0x000003E0, 0x0000001F},
+ {IMGFMT_RGB16, BO_NATIVE, 0x0000001F, 0x000007E0, 0x0000F800},
+ {IMGFMT_BGR16, BO_NATIVE, 0x0000F800, 0x000007E0, 0x0000001F},
+ {IMGFMT_RGB24, MSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF},
+ {IMGFMT_RGB24, LSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000},
+ {IMGFMT_BGR24, MSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000},
+ {IMGFMT_BGR24, LSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF},
+ {IMGFMT_RGB32, BO_NATIVE, 0x000000FF, 0x0000FF00, 0x00FF0000},
+ {IMGFMT_RGB32, BO_NONNATIVE, 0xFF000000, 0x00FF0000, 0x0000FF00},
+ {IMGFMT_BGR32, BO_NATIVE, 0x00FF0000, 0x0000FF00, 0x000000FF},
+ {IMGFMT_BGR32, BO_NONNATIVE, 0x0000FF00, 0x00FF0000, 0xFF000000},
+ {IMGFMT_ARGB, MSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF},
+ {IMGFMT_ARGB, LSBFirst, 0x0000FF00, 0x00FF0000, 0xFF000000},
+ {IMGFMT_ABGR, MSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000},
+ {IMGFMT_ABGR, LSBFirst, 0xFF000000, 0x00FF0000, 0x0000FF00},
+ {IMGFMT_RGBA, MSBFirst, 0xFF000000, 0x00FF0000, 0x0000FF00},
+ {IMGFMT_RGBA, LSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000},
+ {IMGFMT_BGRA, MSBFirst, 0x0000FF00, 0x00FF0000, 0xFF000000},
+ {IMGFMT_BGRA, LSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF},
+ {0}
+};
+
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ struct priv *p = vo->priv;
+
+ Colormap theCmap;
+ const struct fmt2Xfmtentry_s *fmte = fmt2Xfmt;
+
+#ifdef CONFIG_XF86VM
+ int vm = flags & VOFLAG_MODESWITCHING;
+#endif
+ p->Flip_Flag = flags & VOFLAG_FLIPPING;
+ p->zoomFlag = flags & VOFLAG_SWSCALE;
+
+ p->old_vo_dwidth = -1;
+ p->old_vo_dheight = -1;
+
+ p->in_format = format;
+ p->srcW = width;
+ p->srcH = height;
+
+ XGetWindowAttributes(vo->x11->display, vo->x11->rootwin, &p->attribs);
+ p->depth = p->attribs.depth;
+
+ if (p->depth != 15 && p->depth != 16 && p->depth != 24 && p->depth != 32) {
+ Visual *visual;
+
+ p->depth = vo_find_depth_from_visuals(vo->x11->display, vo->x11->screen,
+ &visual);
+ }
+ if (!XMatchVisualInfo(vo->x11->display, vo->x11->screen, p->depth,
+ DirectColor, &p->vinfo)
+ || (WinID > 0
+ && p->vinfo.visualid != XVisualIDFromVisual(p->attribs.visual)))
+ {
+ XMatchVisualInfo(vo->x11->display, vo->x11->screen, p->depth, TrueColor,
+ &p->vinfo);
+ }
+
+ /* set image size (which is indeed neither the input nor output size),
+ if zoom is on it will be changed during draw_slice anyway so we don't
+ duplicate the aspect code here
+ */
+ p->image_width = (width + 7) & (~7);
+ p->image_height = height;
+
+ {
+#ifdef CONFIG_XF86VM
+ if (vm)
+ vo_vm_switch(vo);
+
+#endif
+ theCmap = vo_x11_create_colormap(vo, &p->vinfo);
+
+ vo_x11_create_vo_window(vo, &p->vinfo, vo->dx, vo->dy, vo->dwidth,
+ vo->dheight, flags, theCmap, "x11");
+ if (WinID > 0)
+ p->depth = vo_x11_update_geometry(vo, true);
+
+#ifdef CONFIG_XF86VM
+ if (vm) {
+ /* Grab the mouse pointer in our window */
+ if (vo_grabpointer)
+ XGrabPointer(vo->x11->display, vo->x11->window, True, 0,
+ GrabModeAsync, GrabModeAsync,
+ vo->x11->window, None, CurrentTime);
+ XSetInputFocus(vo->x11->display, vo->x11->window, RevertToNone,
+ CurrentTime);
+ }
+#endif
+ }
+
+ if (p->myximage) {
+ freeMyXImage(p);
+ sws_freeContext(p->swsContext);
+ }
+ getMyXImage(p);
+
+ while (fmte->mpfmt) {
+ int depth = IMGFMT_RGB_DEPTH(fmte->mpfmt);
+ /* bits_per_pixel in X seems to be set to 16 for 15 bit formats
+ => force depth to 16 so that only the color masks are used for the format check */
+ if (depth == 15)
+ depth = 16;
+
+ if (depth == p->myximage->bits_per_pixel &&
+ fmte->byte_order == p->myximage->byte_order &&
+ fmte->red_mask == p->myximage->red_mask &&
+ fmte->green_mask == p->myximage->green_mask &&
+ fmte->blue_mask == p->myximage->blue_mask)
+ break;
+ fmte++;
+ }
+ if (!fmte->mpfmt) {
+ mp_msg(
+ MSGT_VO, MSGL_ERR,
+ "X server image format not supported, please contact the developers\n");
+ return -1;
+ }
+ p->out_format = fmte->mpfmt;
+ p->bpp = p->myximage->bits_per_pixel;
+ p->out_offset = 0;
+ // We can easily "emulate" non-native RGB32 and BGR32
+ if (p->out_format == (IMGFMT_BGR32 | 128)
+ || p->out_format == (IMGFMT_RGB32 | 128))
+ {
+ p->out_format &= ~128;
+#if BYTE_ORDER == BIG_ENDIAN
+ p->out_offset = 1;
+#else
+ p->out_offset = -1;
+#endif
+ }
+
+ /* always allocate swsContext as size could change between frames */
+ p->swsContext = sws_getContextFromCmdLine(width, height, p->in_format,
+ width, height, p->out_format);
+ if (!p->swsContext)
+ return -1;
+
+ p->dst_width = width;
+
+ return 0;
+}
+
+static void Display_Image(struct priv *p, XImage *myximage, uint8_t *ImageData)
+{
+ struct vo *vo = p->vo;
+
+ int x = (vo->dwidth - p->dst_width) / 2;
+ int y = (vo->dheight - p->myximage->height) / 2;
+
+ // do not draw if the image needs rescaling
+ if ((p->old_vo_dwidth != vo->dwidth ||
+ p->old_vo_dheight != vo->dheight) && p->zoomFlag)
+ return;
+
+ if (WinID == 0) {
+ x = vo->dx;
+ y = vo->dy;
+ }
+ p->myximage->data += p->out_offset;
+#ifdef HAVE_SHM
+ if (p->Shmem_Flag) {
+ XShmPutImage(vo->x11->display, vo->x11->window, vo->x11->vo_gc,
+ p->myximage, 0, 0, x, y, p->dst_width, p->myximage->height,
+ True);
+ } else
+#endif
+ {
+ XPutImage(vo->x11->display, vo->x11->window, vo->x11->vo_gc,
+ p->myximage, 0, 0, x, y, p->dst_width, p->myximage->height);
+ }
+ p->myximage->data -= p->out_offset;
+}
+
+static struct mp_image get_x_buffer(struct priv *p)
+{
+ struct mp_image img = {0};
+ img.w = img.width = p->image_width;
+ img.h = img.height = p->image_height;
+ mp_image_setfmt(&img, p->out_format);
+
+ img.planes[0] = p->ImageData;
+ img.stride[0] = p->image_width * ((p->bpp + 7) / 8);
+
+ return img;
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ struct priv *p = vo->priv;
+
+ struct mp_image img = get_x_buffer(p);
+
+ struct mp_osd_res res = {
+ .w = img.w,
+ .h = img.h,
+ .display_par = vo->monitor_par,
+ .video_par = vo->aspdat.par,
+ };
+
+ osd_draw_on_image(osd, res, osd->vo_pts, 0, &img);
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ Display_Image(p, p->myximage, p->ImageData);
+ XSync(vo->x11->display, False);
+}
+
+static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h,
+ int x, int y)
+{
+ struct priv *p = vo->priv;
+ uint8_t *dst[MP_MAX_PLANES] = {NULL};
+ int dstStride[MP_MAX_PLANES] = {0};
+
+ if ((p->old_vo_dwidth != vo->dwidth || p->old_vo_dheight != vo->dheight)
+ /*&& y==0 */ && p->zoomFlag)
+ {
+ int newW = vo->dwidth;
+ int newH = vo->dheight;
+ struct SwsContext *oldContext = p->swsContext;
+
+ p->old_vo_dwidth = vo->dwidth;
+ p->old_vo_dheight = vo->dheight;
+
+ if (vo_fs)
+ aspect(vo, &newW, &newH, A_ZOOM);
+ if (sws_flags == 0)
+ newW &= (~31); // not needed but, if the user wants the FAST_BILINEAR SCALER, then its needed
+
+ p->swsContext
+ = sws_getContextFromCmdLine(p->srcW, p->srcH, p->in_format, newW,
+ newH, p->out_format);
+ if (p->swsContext) {
+ p->image_width = (newW + 7) & (~7);
+ p->image_height = newH;
+
+ freeMyXImage(p);
+ getMyXImage(p);
+ sws_freeContext(oldContext);
+ } else
+ p->swsContext = oldContext;
+ p->dst_width = newW;
+ }
+
+ dstStride[0] = p->image_width * ((p->bpp + 7) / 8);
+ dst[0] = p->ImageData;
+ if (p->Flip_Flag) {
+ dst[0] += dstStride[0] * (p->image_height - 1);
+ dstStride[0] = -dstStride[0];
+ }
+ sws_scale(p->swsContext, (const uint8_t **)src, stride, y, h, dst,
+ dstStride);
+ return 0;
+}
+
+static int query_format(struct vo *vo, uint32_t format)
+{
+ mp_msg(MSGT_VO, MSGL_DBG2,
+ "vo_x11: query_format was called: %x (%s)\n", format,
+ vo_format_name(format));
+ if (IMGFMT_IS_BGR(format)) {
+ if (IMGFMT_BGR_DEPTH(format) <= 8)
+ return 0; // TODO 8bpp not yet fully implemented
+ if (IMGFMT_BGR_DEPTH(format) == vo->x11->depthonscreen)
+ return VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW |
+ VFCAP_OSD | VFCAP_SWSCALE | VFCAP_FLIP |
+ VFCAP_ACCEPT_STRIDE;
+ else
+ return VFCAP_CSP_SUPPORTED | VFCAP_OSD | VFCAP_SWSCALE |
+ VFCAP_FLIP |
+ VFCAP_ACCEPT_STRIDE;
+ }
+
+ switch (format) {
+ case IMGFMT_I420:
+ case IMGFMT_IYUV:
+ case IMGFMT_YV12:
+ return VFCAP_CSP_SUPPORTED | VFCAP_OSD | VFCAP_SWSCALE |
+ VFCAP_ACCEPT_STRIDE;
+ }
+ return 0;
+}
+
+
+static void uninit(struct vo *vo)
+{
+ struct priv *p = vo->priv;
+ if (p->myximage)
+ freeMyXImage(p);
+
+#ifdef CONFIG_XF86VM
+ vo_vm_close(vo);
+#endif
+
+ p->zoomFlag = 0;
+ vo_x11_uninit(vo);
+
+ sws_freeContext(p->swsContext);
+}
+
+static int preinit(struct vo *vo, const char *arg)
+{
+ struct priv *p = vo->priv;
+ p->vo = vo;
+
+ if (arg) {
+ mp_msg(MSGT_VO, MSGL_ERR, "vo_x11: Unknown subdevice: %s\n", arg);
+ return ENOSYS;
+ }
+
+ if (!vo_init(vo))
+ return -1; // Can't open X11
+ return 0;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct priv *p = vo->priv;
+ switch (request) {
+ case VOCTRL_PAUSE:
+ return p->int_pause = 1;
+ case VOCTRL_RESUME:
+ return p->int_pause = 0;
+ case VOCTRL_QUERY_FORMAT:
+ return query_format(vo, *((uint32_t *) data));
+ case VOCTRL_FULLSCREEN:
+ vo_x11_fullscreen(vo);
+ vo_x11_clearwindow(vo, vo->x11->window);
+ return VO_TRUE;
+ case VOCTRL_SET_EQUALIZER:
+ {
+ struct voctrl_set_equalizer_args *args = data;
+ return vo_x11_set_equalizer(vo, args->name, args->value);
+ }
+ case VOCTRL_GET_EQUALIZER:
+ {
+ struct voctrl_get_equalizer_args *args = data;
+ return vo_x11_get_equalizer(args->name, args->valueptr);
+ }
+ case VOCTRL_ONTOP:
+ vo_x11_ontop(vo);
+ return VO_TRUE;
+ case VOCTRL_UPDATE_SCREENINFO:
+ update_xinerama_info(vo);
+ return VO_TRUE;
+ }
+ return VO_NOTIMPL;
+}
+
+const struct vo_driver video_out_x11 = {
+ .is_new = false,
+ .info = &(const vo_info_t) {
+ "X11 ( XImage/Shm )",
+ "x11",
+ "Aaron Holtzman <aholtzma@ess.engr.uvic.ca>",
+ ""
+ },
+ .priv_size = sizeof(struct priv),
+ .priv_defaults = &(const struct priv) {
+ .srcW = -1,
+ .srcH = -1,
+ .old_vo_dwidth = -1,
+ .old_vo_dheight = -1,
+#ifdef HAVE_SHM
+ .CompletionType = -1,
+#endif
+ },
+ .preinit = preinit,
+ .config = config,
+ .control = control,
+ .draw_slice = draw_slice,
+ .draw_osd = draw_osd,
+ .flip_page = flip_page,
+ .check_events = check_events,
+ .uninit = uninit,
+};
diff --git a/video/out/vo_xv.c b/video/out/vo_xv.c
new file mode 100644
index 0000000000..3673764ed4
--- /dev/null
+++ b/video/out/vo_xv.c
@@ -0,0 +1,716 @@
+/*
+ * X11 Xv interface
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include <libavutil/common.h>
+
+#include "config.h"
+
+#ifdef HAVE_SHM
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <X11/extensions/XShm.h>
+#endif
+
+// Note: depends on the inclusion of X11/extensions/XShm.h
+#include <X11/extensions/Xv.h>
+#include <X11/extensions/Xvlib.h>
+
+#include "options.h"
+#include "talloc.h"
+#include "mp_msg.h"
+#include "video_out.h"
+#include "libmpcodecs/vfcap.h"
+#include "libmpcodecs/mp_image.h"
+#include "x11_common.h"
+#include "fastmemcpy.h"
+#include "sub/sub.h"
+#include "aspect.h"
+#include "csputils.h"
+#include "subopt-helper.h"
+
+static const vo_info_t info = {
+ "X11/Xv",
+ "xv",
+ "Gerd Knorr <kraxel@goldbach.in-berlin.de> and others",
+ ""
+};
+
+struct xvctx {
+ XvAdaptorInfo *ai;
+ XvImageFormatValues *fo;
+ unsigned int formats, adaptors, xv_format;
+ int current_buf;
+ int current_ip_buf;
+ int num_buffers;
+ int total_buffers;
+ bool have_image_copy;
+ bool unchanged_image;
+ int visible_buf;
+ XvImage *xvimage[2 + 1];
+ uint32_t image_width;
+ uint32_t image_height;
+ uint32_t image_format;
+ struct mp_csp_details cached_csp;
+ int is_paused;
+ struct mp_rect src_rect;
+ struct mp_rect dst_rect;
+ uint32_t max_width, max_height; // zero means: not set
+ int mode_switched;
+#ifdef HAVE_SHM
+ XShmSegmentInfo Shminfo[2 + 1];
+ int Shmem_Flag;
+#endif
+};
+
+static void allocate_xvimage(struct vo *, int);
+static void deallocate_xvimage(struct vo *vo, int foo);
+
+static void read_xv_csp(struct vo *vo)
+{
+ struct xvctx *ctx = vo->priv;
+ struct vo_x11_state *x11 = vo->x11;
+ struct mp_csp_details *cspc = &ctx->cached_csp;
+ *cspc = (struct mp_csp_details) MP_CSP_DETAILS_DEFAULTS;
+ int bt709_enabled;
+ if (vo_xv_get_eq(vo, x11->xv_port, "bt_709", &bt709_enabled))
+ cspc->format = bt709_enabled == 100 ? MP_CSP_BT_709 : MP_CSP_BT_601;
+}
+
+static void resize(struct vo *vo)
+{
+ struct xvctx *ctx = vo->priv;
+
+ // Can't be used, because the function calculates screen-space coordinates,
+ // while we need video-space.
+ struct mp_osd_res unused;
+
+ vo_get_src_dst_rects(vo, &ctx->src_rect, &ctx->dst_rect, &unused);
+
+ struct mp_rect *dst = &ctx->dst_rect;
+ int dw = dst->x1 - dst->x0, dh = dst->y1 - dst->y0;
+ vo_x11_clearwindow_part(vo, vo->x11->window, dw, dh);
+ vo_xv_draw_colorkey(vo, dst->x0, dst->y0, dw, dh);
+ read_xv_csp(vo);
+}
+
+/*
+ * connect to server, create and map window,
+ * allocate colors and (shared) memory
+ */
+static int config(struct vo *vo, uint32_t width, uint32_t height,
+ uint32_t d_width, uint32_t d_height, uint32_t flags,
+ uint32_t format)
+{
+ struct vo_x11_state *x11 = vo->x11;
+ XVisualInfo vinfo;
+ XSetWindowAttributes xswa;
+ XWindowAttributes attribs;
+ unsigned long xswamask;
+ int depth;
+ struct xvctx *ctx = vo->priv;
+ int i;
+
+ ctx->image_height = height;
+ ctx->image_width = width;
+ ctx->image_format = format;
+
+ if ((ctx->max_width != 0 && ctx->max_height != 0)
+ && (ctx->image_width > ctx->max_width
+ || ctx->image_height > ctx->max_height)) {
+ mp_tmsg(MSGT_VO, MSGL_ERR, "Source image dimensions are too high: %ux%u (maximum is %ux%u)\n",
+ ctx->image_width, ctx->image_height, ctx->max_width,
+ ctx->max_height);
+ return -1;
+ }
+
+ ctx->visible_buf = -1;
+ ctx->have_image_copy = false;
+
+ /* check image formats */
+ ctx->xv_format = 0;
+ for (i = 0; i < ctx->formats; i++) {
+ mp_msg(MSGT_VO, MSGL_V, "Xvideo image format: 0x%x (%4.4s) %s\n",
+ ctx->fo[i].id, (char *) &ctx->fo[i].id,
+ (ctx->fo[i].format == XvPacked) ? "packed" : "planar");
+ if (ctx->fo[i].id == format)
+ ctx->xv_format = ctx->fo[i].id;
+ }
+ if (!ctx->xv_format)
+ return -1;
+
+ {
+#ifdef CONFIG_XF86VM
+ int vm = flags & VOFLAG_MODESWITCHING;
+ if (vm) {
+ vo_vm_switch(vo);
+ ctx->mode_switched = 1;
+ }
+#endif
+ XGetWindowAttributes(x11->display, DefaultRootWindow(x11->display),
+ &attribs);
+ depth = attribs.depth;
+ if (depth != 15 && depth != 16 && depth != 24 && depth != 32)
+ depth = 24;
+ XMatchVisualInfo(x11->display, x11->screen, depth, TrueColor, &vinfo);
+
+ xswa.border_pixel = 0;
+ xswamask = CWBorderPixel;
+ if (x11->xv_ck_info.method == CK_METHOD_BACKGROUND) {
+ xswa.background_pixel = x11->xv_colorkey;
+ xswamask |= CWBackPixel;
+ }
+
+ vo_x11_create_vo_window(vo, &vinfo, vo->dx, vo->dy, vo->dwidth,
+ vo->dheight, flags, CopyFromParent, "xv");
+ XChangeWindowAttributes(x11->display, x11->window, xswamask, &xswa);
+
+#ifdef CONFIG_XF86VM
+ if (vm) {
+ /* Grab the mouse pointer in our window */
+ if (vo_grabpointer)
+ XGrabPointer(x11->display, x11->window, True, 0, GrabModeAsync,
+ GrabModeAsync, x11->window, None, CurrentTime);
+ XSetInputFocus(x11->display, x11->window, RevertToNone,
+ CurrentTime);
+ }
+#endif
+ }
+
+ mp_msg(MSGT_VO, MSGL_V, "using Xvideo port %d for hw scaling\n",
+ x11->xv_port);
+
+ // In case config has been called before
+ for (i = 0; i < ctx->total_buffers; i++)
+ deallocate_xvimage(vo, i);
+
+ ctx->num_buffers = 2;
+ ctx->total_buffers = ctx->num_buffers + 1;
+
+ for (i = 0; i < ctx->total_buffers; i++)
+ allocate_xvimage(vo, i);
+
+ ctx->current_buf = 0;
+ ctx->current_ip_buf = 0;
+
+
+ resize(vo);
+
+ return 0;
+}
+
+static void allocate_xvimage(struct vo *vo, int foo)
+{
+ struct xvctx *ctx = vo->priv;
+ struct vo_x11_state *x11 = vo->x11;
+ /*
+ * allocate XvImages. FIXME: no error checking, without
+ * mit-shm this will bomb... trzing to fix ::atmos
+ */
+#ifdef HAVE_SHM
+ if (x11->display_is_local && XShmQueryExtension(x11->display))
+ ctx->Shmem_Flag = 1;
+ else {
+ ctx->Shmem_Flag = 0;
+ mp_tmsg(MSGT_VO, MSGL_INFO, "[VO_XV] Shared memory not supported\nReverting to normal Xv.\n");
+ }
+ if (ctx->Shmem_Flag) {
+ ctx->xvimage[foo] =
+ (XvImage *) XvShmCreateImage(x11->display, x11->xv_port,
+ ctx->xv_format, NULL,
+ ctx->image_width, ctx->image_height,
+ &ctx->Shminfo[foo]);
+
+ ctx->Shminfo[foo].shmid = shmget(IPC_PRIVATE,
+ ctx->xvimage[foo]->data_size,
+ IPC_CREAT | 0777);
+ ctx->Shminfo[foo].shmaddr = (char *) shmat(ctx->Shminfo[foo].shmid, 0,
+ 0);
+ ctx->Shminfo[foo].readOnly = False;
+
+ ctx->xvimage[foo]->data = ctx->Shminfo[foo].shmaddr;
+ XShmAttach(x11->display, &ctx->Shminfo[foo]);
+ XSync(x11->display, False);
+ shmctl(ctx->Shminfo[foo].shmid, IPC_RMID, 0);
+ } else
+#endif
+ {
+ ctx->xvimage[foo] =
+ (XvImage *) XvCreateImage(x11->display, x11->xv_port,
+ ctx->xv_format, NULL, ctx->image_width,
+ ctx->image_height);
+ ctx->xvimage[foo]->data = malloc(ctx->xvimage[foo]->data_size);
+ XSync(x11->display, False);
+ }
+ memset(ctx->xvimage[foo]->data, 128, ctx->xvimage[foo]->data_size);
+ return;
+}
+
+static void deallocate_xvimage(struct vo *vo, int foo)
+{
+ struct xvctx *ctx = vo->priv;
+#ifdef HAVE_SHM
+ if (ctx->Shmem_Flag) {
+ XShmDetach(vo->x11->display, &ctx->Shminfo[foo]);
+ shmdt(ctx->Shminfo[foo].shmaddr);
+ } else
+#endif
+ {
+ free(ctx->xvimage[foo]->data);
+ }
+ XFree(ctx->xvimage[foo]);
+
+ XSync(vo->x11->display, False);
+ return;
+}
+
+static inline void put_xvimage(struct vo *vo, XvImage *xvi)
+{
+ struct xvctx *ctx = vo->priv;
+ struct vo_x11_state *x11 = vo->x11;
+ struct mp_rect *src = &ctx->src_rect;
+ struct mp_rect *dst = &ctx->dst_rect;
+ int dw = dst->x1 - dst->x0, dh = dst->y1 - dst->y0;
+ int sw = src->x1 - src->x0, sh = src->y1 - src->y0;
+#ifdef HAVE_SHM
+ if (ctx->Shmem_Flag) {
+ XvShmPutImage(x11->display, x11->xv_port, x11->window, x11->vo_gc, xvi,
+ src->x0, src->y0, sw, sh,
+ dst->x0, dst->y0, dw, dh,
+ False);
+ } else
+#endif
+ {
+ XvPutImage(x11->display, x11->xv_port, x11->window, x11->vo_gc, xvi,
+ src->x0, src->y0, sw, sh,
+ dst->x0, dst->y0, dw, dh);
+ }
+}
+
+static struct mp_image get_xv_buffer(struct vo *vo, int buf_index)
+{
+ struct xvctx *ctx = vo->priv;
+ XvImage *xv_image = ctx->xvimage[buf_index];
+
+ struct mp_image img = {0};
+ img.w = img.width = xv_image->width;
+ img.h = img.height = xv_image->height;
+ mp_image_setfmt(&img, ctx->image_format);
+
+ bool swapuv = ctx->image_format == IMGFMT_YV12;
+ for (int n = 0; n < img.num_planes; n++) {
+ int sn = n > 0 && swapuv ? (n == 1 ? 2 : 1) : n;
+ img.planes[n] = xv_image->data + xv_image->offsets[sn];
+ img.stride[n] = xv_image->pitches[sn];
+ }
+
+ mp_image_set_colorspace_details(&img, &ctx->cached_csp);
+
+ return img;
+}
+
+static void copy_backup_image(struct vo *vo, int dest, int src)
+{
+ struct mp_image img_dest = get_xv_buffer(vo, dest);
+ struct mp_image img_src = get_xv_buffer(vo, src);
+
+ copy_mpi(&img_dest, &img_src);
+}
+
+static void check_events(struct vo *vo)
+{
+ int e = vo_x11_check_events(vo);
+
+ if (e & VO_EVENT_EXPOSE || e & VO_EVENT_RESIZE) {
+ resize(vo);
+ vo->want_redraw = true;
+ }
+}
+
+static void draw_osd(struct vo *vo, struct osd_state *osd)
+{
+ struct xvctx *ctx = vo->priv;
+
+ struct mp_image img = get_xv_buffer(vo, ctx->current_buf);
+
+ struct mp_rect *src = &ctx->src_rect;
+ struct mp_rect *dst = &ctx->dst_rect;
+ int dw = dst->x1 - dst->x0, dh = dst->y1 - dst->y0;
+ int sw = src->x1 - src->x0, sh = src->y1 - src->y0;
+ double xvpar = (double)dw / dh * sh / sw;
+
+ struct mp_osd_res res = {
+ .w = ctx->image_width,
+ .h = ctx->image_height,
+ .display_par = vo->monitor_par / xvpar,
+ .video_par = vo->aspdat.par,
+ };
+
+ if (osd_draw_on_image(osd, res, osd->vo_pts, 0, &img))
+ ctx->unchanged_image = false;
+}
+
+static int redraw_frame(struct vo *vo)
+{
+ struct xvctx *ctx = vo->priv;
+
+ if (ctx->have_image_copy)
+ copy_backup_image(vo, ctx->visible_buf, ctx->num_buffers);
+ else if (ctx->unchanged_image) {
+ copy_backup_image(vo, ctx->num_buffers, ctx->visible_buf);
+ ctx->have_image_copy = true;
+ } else
+ return false;
+ ctx->current_buf = ctx->visible_buf;
+ return true;
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct xvctx *ctx = vo->priv;
+ put_xvimage(vo, ctx->xvimage[ctx->current_buf]);
+
+ /* remember the currently visible buffer */
+ ctx->visible_buf = ctx->current_buf;
+
+ ctx->current_buf = (ctx->current_buf + 1) % ctx->num_buffers;
+ XFlush(vo->x11->display);
+ return;
+}
+
+static int draw_slice(struct vo *vo, uint8_t *image[], int stride[], int w,
+ int h, int x, int y)
+{
+ struct xvctx *ctx = vo->priv;
+ uint8_t *dst;
+ XvImage *current_image = ctx->xvimage[ctx->current_buf];
+
+ dst = current_image->data + current_image->offsets[0]
+ + current_image->pitches[0] * y + x;
+ memcpy_pic(dst, image[0], w, h, current_image->pitches[0], stride[0]);
+
+ x /= 2;
+ y /= 2;
+ w /= 2;
+ h /= 2;
+
+ dst = current_image->data + current_image->offsets[1]
+ + current_image->pitches[1] * y + x;
+ if (ctx->image_format != IMGFMT_YV12)
+ memcpy_pic(dst, image[1], w, h, current_image->pitches[1], stride[1]);
+ else
+ memcpy_pic(dst, image[2], w, h, current_image->pitches[1], stride[2]);
+
+ dst = current_image->data + current_image->offsets[2]
+ + current_image->pitches[2] * y + x;
+ if (ctx->image_format == IMGFMT_YV12)
+ memcpy_pic(dst, image[1], w, h, current_image->pitches[1], stride[1]);
+ else
+ memcpy_pic(dst, image[2], w, h, current_image->pitches[1], stride[2]);
+
+ return 0;
+}
+
+static mp_image_t *get_screenshot(struct vo *vo)
+{
+ struct xvctx *ctx = vo->priv;
+
+ // try to get an image without OSD
+ int id = ctx->have_image_copy ? ctx->num_buffers : ctx->visible_buf;
+ struct mp_image img = get_xv_buffer(vo, id);
+ img.display_w = vo->aspdat.prew;