diff options
Diffstat (limited to 'video/out')
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 = §ion->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 = §ion->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(¶ms->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(¶ms->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, ¶ms->csp_params, 0); + break; + case YUV_CONVERSION_TEXT_FRAGMENT: + glSetupYUVFragmentATI(gl, ¶ms->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, ¶ms); + 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; + img.display_h = vo->aspdat.preh; + + return talloc_memdup(NULL, &img, sizeof(img)); +} + +static uint32_t draw_image(struct vo *vo, mp_image_t *mpi) +{ + struct xvctx *ctx = vo->priv; + + ctx->have_image_copy = false; + + if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK) + ; // done + else if (mpi->flags & MP_IMGFLAG_PLANAR) + draw_slice(vo, mpi->planes, mpi->stride, mpi->w, mpi->h, 0, 0); + else if (mpi->flags & MP_IMGFLAG_YUV) + // packed YUV: + memcpy_pic(ctx->xvimage[ctx->current_buf]->data + + ctx->xvimage[ctx->current_buf]->offsets[0], mpi->planes[0], + mpi->w * (mpi->bpp / 8), mpi->h, + ctx->xvimage[ctx->current_buf]->pitches[0], mpi->stride[0]); + else + return false; + + if (ctx->is_paused) { + copy_backup_image(vo, ctx->num_buffers, ctx->current_buf); + ctx->have_image_copy = true; + } + ctx->unchanged_image = true; + return true; +} + +static int query_format(struct xvctx *ctx, uint32_t format) +{ + uint32_t i; + int flag = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_OSD | VFCAP_ACCEPT_STRIDE; // FIXME! check for DOWN + + /* check image formats */ + for (i = 0; i < ctx->formats; i++) { + if (ctx->fo[i].id == format) + return flag; //xv_format = fo[i].id; + } + return 0; +} + +static void uninit(struct vo *vo) +{ + struct xvctx *ctx = vo->priv; + int i; + + ctx->visible_buf = -1; + if (ctx->ai) + XvFreeAdaptorInfo(ctx->ai); + ctx->ai = NULL; + if (ctx->fo) { + XFree(ctx->fo); + ctx->fo = NULL; + } + for (i = 0; i < ctx->total_buffers; i++) + deallocate_xvimage(vo, i); +#ifdef CONFIG_XF86VM + if (ctx->mode_switched) + vo_vm_close(vo); +#endif + // uninit() shouldn't get called unless initialization went past vo_init() + vo_x11_uninit(vo); +} + +static int preinit(struct vo *vo, const char *arg) +{ + XvPortID xv_p; + int busy_ports = 0; + unsigned int i; + strarg_t ck_src_arg = { 0, NULL }; + strarg_t ck_method_arg = { 0, NULL }; + struct xvctx *ctx = talloc_zero(vo, struct xvctx); + vo->priv = ctx; + int xv_adaptor = -1; + + if (!vo_init(vo)) + return -1; + + struct vo_x11_state *x11 = vo->x11; + + const opt_t subopts[] = + { + /* name arg type arg var test */ + { "port", OPT_ARG_INT, &x11->xv_port, int_pos }, + { "adaptor", OPT_ARG_INT, &xv_adaptor, int_non_neg }, + { "ck", OPT_ARG_STR, &ck_src_arg, xv_test_ck }, + { "ck-method", OPT_ARG_STR, &ck_method_arg, xv_test_ckm }, + { NULL } + }; + + x11->xv_port = 0; + + /* parse suboptions */ + if (subopt_parse(arg, subopts) != 0) { + return -1; + } + + /* modify colorkey settings according to the given options */ + xv_setup_colorkeyhandling(vo, ck_method_arg.str, ck_src_arg.str); + + /* check for Xvideo extension */ + unsigned int ver, rel, req, ev, err; + if (Success != XvQueryExtension(x11->display, &ver, &rel, &req, &ev, &err)) { + mp_tmsg(MSGT_VO, MSGL_ERR, "[VO_XV] Sorry, Xv not supported by this X11 version/driver\n[VO_XV] ******** Try with -vo x11 *********\n"); + goto error; + } + + /* check for Xvideo support */ + if (Success != + XvQueryAdaptors(x11->display, DefaultRootWindow(x11->display), + &ctx->adaptors, &ctx->ai)) { + mp_tmsg(MSGT_VO, MSGL_ERR, "[VO_XV] XvQueryAdaptors failed.\n"); + goto error; + } + + /* check adaptors */ + if (x11->xv_port) { + int port_found; + + for (port_found = 0, i = 0; !port_found && i < ctx->adaptors; i++) { + if ((ctx->ai[i].type & XvInputMask) + && (ctx->ai[i].type & XvImageMask)) { + for (xv_p = ctx->ai[i].base_id; + xv_p < ctx->ai[i].base_id + ctx->ai[i].num_ports; + ++xv_p) { + if (xv_p == x11->xv_port) { + port_found = 1; + break; + } + } + } + } + if (port_found) { + if (XvGrabPort(x11->display, x11->xv_port, CurrentTime)) + x11->xv_port = 0; + } else { + mp_tmsg(MSGT_VO, MSGL_WARN, "[VO_XV] Invalid port parameter, overriding with port 0.\n"); + x11->xv_port = 0; + } + } + + for (i = 0; i < ctx->adaptors && x11->xv_port == 0; i++) { + /* check if adaptor number has been specified */ + if (xv_adaptor != -1 && xv_adaptor != i) + continue; + + if ((ctx->ai[i].type & XvInputMask) && (ctx->ai[i].type & XvImageMask)) { + for (xv_p = ctx->ai[i].base_id; + xv_p < ctx->ai[i].base_id + ctx->ai[i].num_ports; ++xv_p) + if (!XvGrabPort(x11->display, xv_p, CurrentTime)) { + x11->xv_port = xv_p; + mp_msg(MSGT_VO, MSGL_V, + "[VO_XV] Using Xv Adapter #%d (%s)\n", + i, ctx->ai[i].name); + break; + } else { + mp_tmsg(MSGT_VO, MSGL_WARN, "[VO_XV] Could not grab port %i.\n", + (int) xv_p); + ++busy_ports; + } + } + } + if (!x11->xv_port) { + if (busy_ports) + mp_tmsg(MSGT_VO, MSGL_ERR, + "[VO_XV] Could not find free Xvideo port - maybe another process is already\n"\ + "[VO_XV] using it. Close all video applications, and try again. If that does\n"\ + "[VO_XV] not help, see 'mpv -vo help' for other (non-xv) video out drivers.\n"); + else + mp_tmsg(MSGT_VO, MSGL_ERR, + "[VO_XV] It seems there is no Xvideo support for your video card available.\n"\ + "[VO_XV] Run 'xvinfo' to verify its Xv support and read\n"\ + "[VO_XV] DOCS/HTML/en/video.html#xv!\n"\ + "[VO_XV] See 'mpv -vo help' for other (non-xv) video out drivers.\n"\ + "[VO_XV] Try -vo x11.\n"); + goto error; + } + + if (!vo_xv_init_colorkey(vo)) { + goto error; // bail out, colorkey setup failed + } + vo_xv_enable_vsync(vo); + vo_xv_get_max_img_dim(vo, &ctx->max_width, &ctx->max_height); + + ctx->fo = XvListImageFormats(x11->display, x11->xv_port, + (int *) &ctx->formats); + + return 0; + + error: + uninit(vo); // free resources + return -1; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct xvctx *ctx = vo->priv; + struct vo_x11_state *x11 = vo->x11; + switch (request) { + case VOCTRL_PAUSE: + return (ctx->is_paused = 1); + case VOCTRL_RESUME: + return (ctx->is_paused = 0); + case VOCTRL_QUERY_FORMAT: + return query_format(ctx, *((uint32_t *) data)); + case VOCTRL_DRAW_IMAGE: + return draw_image(vo, data); + case VOCTRL_GET_PANSCAN: + return VO_TRUE; + case VOCTRL_FULLSCREEN: + vo_x11_fullscreen(vo); + /* indended, fallthrough to update panscan on fullscreen/windowed switch */ + case VOCTRL_SET_PANSCAN: + resize(vo); + return VO_TRUE; + case VOCTRL_SET_EQUALIZER: { + vo->want_redraw = true; + struct voctrl_set_equalizer_args *args = data; + return vo_xv_set_eq(vo, x11->xv_port, args->name, args->value); + } + case VOCTRL_GET_EQUALIZER: { + struct voctrl_get_equalizer_args *args = data; + return vo_xv_get_eq(vo, x11->xv_port, args->name, args->valueptr); + } + case VOCTRL_SET_YUV_COLORSPACE:; + struct mp_csp_details* given_cspc = data; + int is_709 = given_cspc->format == MP_CSP_BT_709; + vo_xv_set_eq(vo, x11->xv_port, "bt_709", is_709 * 200 - 100); + read_xv_csp(vo); + vo->want_redraw = true; + return true; + case VOCTRL_GET_YUV_COLORSPACE:; + struct mp_csp_details* cspc = data; + read_xv_csp(vo); + *cspc = ctx->cached_csp; + 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_REDRAW_FRAME: + return redraw_frame(vo); + case VOCTRL_SCREENSHOT: { + struct voctrl_screenshot_args *args = data; + args->out_image = get_screenshot(vo); + args->has_osd = !ctx->have_image_copy; + return true; + } + } + return VO_NOTIMPL; +} + +const struct vo_driver video_out_xv = { + .is_new = 1, + .info = &info, + .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/w32_common.c b/video/out/w32_common.c new file mode 100644 index 0000000000..f60f5328de --- /dev/null +++ b/video/out/w32_common.c @@ -0,0 +1,757 @@ +/* + * 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 <limits.h> +#include <assert.h> +#include <windows.h> +#include <windowsx.h> + +#include "options.h" +#include "input/keycodes.h" +#include "input/input.h" +#include "mp_msg.h" +#include "video_out.h" +#include "aspect.h" +#include "w32_common.h" +#include "mp_fifo.h" +#include "osdep/io.h" +#include "talloc.h" + +#define WIN_ID_TO_HWND(x) ((HWND)(uint32_t)(x)) + +static const wchar_t classname[] = L"mpv"; + +static const struct mp_keymap vk_map[] = { + // special keys + {VK_ESCAPE, KEY_ESC}, {VK_BACK, KEY_BS}, {VK_TAB, KEY_TAB}, + {VK_RETURN, KEY_ENTER}, {VK_PAUSE, KEY_PAUSE}, {VK_SNAPSHOT, KEY_PRINT}, + + // cursor keys + {VK_LEFT, KEY_LEFT}, {VK_UP, KEY_UP}, {VK_RIGHT, KEY_RIGHT}, {VK_DOWN, KEY_DOWN}, + + // navigation block + {VK_INSERT, KEY_INSERT}, {VK_DELETE, KEY_DELETE}, {VK_HOME, KEY_HOME}, {VK_END, KEY_END}, + {VK_PRIOR, KEY_PAGE_UP}, {VK_NEXT, KEY_PAGE_DOWN}, + + // F-keys + {VK_F1, KEY_F+1}, {VK_F2, KEY_F+2}, {VK_F3, KEY_F+3}, {VK_F4, KEY_F+4}, + {VK_F5, KEY_F+5}, {VK_F6, KEY_F+6}, {VK_F7, KEY_F+7}, {VK_F8, KEY_F+8}, + {VK_F9, KEY_F+9}, {VK_F10, KEY_F+10}, {VK_F11, KEY_F+11}, {VK_F12, KEY_F+12}, + // numpad + {VK_NUMPAD0, KEY_KP0}, {VK_NUMPAD1, KEY_KP1}, {VK_NUMPAD2, KEY_KP2}, + {VK_NUMPAD3, KEY_KP3}, {VK_NUMPAD4, KEY_KP4}, {VK_NUMPAD5, KEY_KP5}, + {VK_NUMPAD6, KEY_KP6}, {VK_NUMPAD7, KEY_KP7}, {VK_NUMPAD8, KEY_KP8}, + {VK_NUMPAD9, KEY_KP9}, {VK_DECIMAL, KEY_KPDEC}, + + {0, 0} +}; + +static void add_window_borders(HWND hwnd, RECT *rc) +{ + AdjustWindowRect(rc, GetWindowLong(hwnd, GWL_STYLE), 0); +} + +// basically a reverse AdjustWindowRect (win32 doesn't appear to have this) +static void subtract_window_borders(HWND hwnd, RECT *rc) +{ + RECT b = { 0, 0, 0, 0 }; + add_window_borders(hwnd, &b); + rc->left -= b.left; + rc->top -= b.top; + rc->right -= b.right; + rc->bottom -= b.bottom; +} + +// turn a WMSZ_* input value in v into the border that should be resized +// returns: 0=left, 1=top, 2=right, 3=bottom, -1=undefined +static int get_resize_border(int v) { + switch (v) { + case WMSZ_LEFT: return 3; + case WMSZ_TOP: return 2; + case WMSZ_RIGHT: return 3; + case WMSZ_BOTTOM: return 2; + case WMSZ_TOPLEFT: return 1; + case WMSZ_TOPRIGHT: return 1; + case WMSZ_BOTTOMLEFT: return 3; + case WMSZ_BOTTOMRIGHT: return 3; + default: return -1; + } +} + +static bool key_state(struct vo *vo, int vk) +{ + return GetKeyState(vk) & 0x8000; +} + +static int mod_state(struct vo *vo) +{ + int res = 0; + if (key_state(vo, VK_CONTROL)) + res |= KEY_MODIFIER_CTRL; + if (key_state(vo, VK_SHIFT)) + res |= KEY_MODIFIER_SHIFT; + if (key_state(vo, VK_MENU)) + res |= KEY_MODIFIER_ALT; + return res; +} + +static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, + LPARAM lParam) +{ + if (message == WM_NCCREATE) { + CREATESTRUCT *cs = (void*)lParam; + SetWindowLongPtrW(hWnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams); + } + struct vo *vo = (void*)GetWindowLongPtrW(hWnd, GWLP_USERDATA); + // message before WM_NCCREATE, pray to Raymond Chen that it's not important + if (!vo) + return DefWindowProcW(hWnd, message, wParam, lParam); + struct vo_w32_state *w32 = vo->w32; + + switch (message) { + case WM_ERASEBKGND: // no need to erase background seperately + return 1; + case WM_PAINT: + w32->event_flags |= VO_EVENT_EXPOSE; + break; + case WM_MOVE: { + w32->event_flags |= VO_EVENT_MOVE; + POINT p = {0}; + ClientToScreen(w32->window, &p); + w32->window_x = p.x; + w32->window_y = p.y; + mp_msg(MSGT_VO, MSGL_V, "[vo] move window: %d:%d\n", + w32->window_x, w32->window_y); + break; + } + case WM_SIZE: { + w32->event_flags |= VO_EVENT_RESIZE; + RECT r; + GetClientRect(w32->window, &r); + vo->dwidth = r.right; + vo->dheight = r.bottom; + mp_msg(MSGT_VO, MSGL_V, "[vo] resize window: %d:%d\n", + vo->dwidth, vo->dheight); + break; + } + case WM_SIZING: + if (vo_keepaspect && !vo_fs && WinID < 0) { + RECT *rc = (RECT*)lParam; + // get client area of the windows if it had the rect rc + // (subtracting the window borders) + RECT r = *rc; + subtract_window_borders(w32->window, &r); + int c_w = r.right - r.left, c_h = r.bottom - r.top; + float aspect = vo->aspdat.asp; + int d_w = c_h * aspect - c_w; + int d_h = c_w / aspect - c_h; + int d_corners[4] = { d_w, d_h, -d_w, -d_h }; + int corners[4] = { rc->left, rc->top, rc->right, rc->bottom }; + int corner = get_resize_border(wParam); + if (corner >= 0) + corners[corner] -= d_corners[corner]; + *rc = (RECT) { corners[0], corners[1], corners[2], corners[3] }; + return TRUE; + } + break; + case WM_CLOSE: + mplayer_put_key(vo->key_fifo, KEY_CLOSE_WIN); + break; + case WM_SYSCOMMAND: + switch (wParam) { + case SC_SCREENSAVE: + case SC_MONITORPOWER: + mp_msg(MSGT_VO, MSGL_V, "vo: win32: killing screensaver\n"); + return 0; + } + break; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: { + int mpkey = lookup_keymap_table(vk_map, wParam); + if (mpkey) + mplayer_put_key(vo->key_fifo, mpkey | mod_state(vo)); + if (wParam == VK_F10) + return 0; + break; + } + case WM_CHAR: + case WM_SYSCHAR: { + int mods = mod_state(vo); + int code = wParam; + // Windows enables Ctrl+Alt when AltGr (VK_RMENU) is pressed. + // E.g. AltGr+9 on a German keyboard would yield Ctrl+Alt+[ + // Warning: wine handles this differently. Don't test this on wine! + if (key_state(vo, VK_RMENU)) + mods &= ~(KEY_MODIFIER_CTRL | KEY_MODIFIER_ALT); + // Apparently Ctrl+A to Ctrl+Z is special cased, and produces + // character codes from 1-26. Work it around. + // Also, enter/return (including the keypad variant) and CTRL+J both + // map to wParam==10. As a workaround, check VK_RETURN to + // distinguish these two key combinations. + if ((mods & KEY_MODIFIER_CTRL) && code >= 1 && code <= 26 + && !key_state(vo, VK_RETURN)) + code = code - 1 + (mods & KEY_MODIFIER_SHIFT ? 'A' : 'a'); + if (code >= 32 && code < (1<<21)) { + mplayer_put_key(vo->key_fifo, code | mods); + // At least with Alt+char, not calling DefWindowProcW stops + // Windows from emitting a beep. + return 0; + } + break; + } + case WM_LBUTTONDOWN: + if (!vo_nomouse_input && (vo_fs || (wParam & MK_CONTROL))) { + mplayer_put_key(vo->key_fifo, MOUSE_BTN0 | mod_state(vo)); + break; + } + if (!vo_fs) { + ReleaseCapture(); + SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); + return 0; + } + break; + case WM_MBUTTONDOWN: + if (!vo_nomouse_input) + mplayer_put_key(vo->key_fifo, MOUSE_BTN1 | mod_state(vo)); + break; + case WM_RBUTTONDOWN: + if (!vo_nomouse_input) + mplayer_put_key(vo->key_fifo, MOUSE_BTN2 | mod_state(vo)); + break; + case WM_MOUSEMOVE: + vo_mouse_movement(vo, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + break; + case WM_MOUSEWHEEL: + if (!vo_nomouse_input) { + int x = GET_WHEEL_DELTA_WPARAM(wParam); + if (x > 0) + mplayer_put_key(vo->key_fifo, MOUSE_BTN3 | mod_state(vo)); + else + mplayer_put_key(vo->key_fifo, MOUSE_BTN4 | mod_state(vo)); + } + break; + case WM_XBUTTONDOWN: + if (!vo_nomouse_input) { + int x = HIWORD(wParam); + if (x == 1) + mplayer_put_key(vo->key_fifo, MOUSE_BTN5 | mod_state(vo)); + else // if (x == 2) + mplayer_put_key(vo->key_fifo, MOUSE_BTN6 | mod_state(vo)); + } + break; + } + + return DefWindowProcW(hWnd, message, wParam, lParam); +} + +/** + * \brief Dispatch incoming window events and handle them. + * + * This function should be placed inside libvo's function "check_events". + * + * \return int with these flags possibly set, take care to handle in the right order + * if it matters in your driver: + * + * VO_EVENT_RESIZE = The window was resized. If necessary reinit your + * driver render context accordingly. + * VO_EVENT_EXPOSE = The window was exposed. Call e.g. flip_frame() to redraw + * the window if the movie is paused. + */ +int vo_w32_check_events(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + MSG msg; + w32->event_flags = 0; + while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + if (WinID >= 0) { + BOOL res; + RECT r; + POINT p; + res = GetClientRect(w32->window, &r); + if (res && (r.right != vo->dwidth || r.bottom != vo->dheight)) { + vo->dwidth = r.right; vo->dheight = r.bottom; + w32->event_flags |= VO_EVENT_RESIZE; + } + p.x = 0; p.y = 0; + ClientToScreen(w32->window, &p); + if (p.x != w32->window_x || p.y != w32->window_y) { + w32->window_x = p.x; w32->window_y = p.y; + w32->event_flags |= VO_EVENT_MOVE; + } + res = GetClientRect(WIN_ID_TO_HWND(WinID), &r); + if (res && (r.right != vo->dwidth || r.bottom != vo->dheight)) + MoveWindow(w32->window, 0, 0, r.right, r.bottom, FALSE); + if (!IsWindow(WIN_ID_TO_HWND(WinID))) + // Window has probably been closed, e.g. due to program crash + mplayer_put_key(vo->key_fifo, KEY_CLOSE_WIN); + } + + return w32->event_flags; +} + +static BOOL CALLBACK mon_enum(HMONITOR hmon, HDC hdc, LPRECT r, LPARAM p) +{ + struct vo *vo = (void*)p; + struct vo_w32_state *w32 = vo->w32; + // this defaults to the last screen if specified number does not exist + xinerama_x = r->left; + xinerama_y = r->top; + vo->opts->vo_screenwidth = r->right - r->left; + vo->opts->vo_screenheight = r->bottom - r->top; + if (w32->mon_cnt == xinerama_screen) + return FALSE; + w32->mon_cnt++; + return TRUE; +} + +/** + * \brief Update screen information. + * + * This function should be called in libvo's "control" callback + * with parameter VOCTRL_UPDATE_SCREENINFO. + * Note that this also enables the new API where geometry and aspect + * calculations are done in video_out.c:config_video_out + * + * Global libvo variables changed: + * xinerama_x + * xinerama_y + * vo_screenwidth + * vo_screenheight + */ +void w32_update_xinerama_info(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + xinerama_x = xinerama_y = 0; + if (xinerama_screen < -1) { + int tmp; + xinerama_x = GetSystemMetrics(SM_XVIRTUALSCREEN); + xinerama_y = GetSystemMetrics(SM_YVIRTUALSCREEN); + tmp = GetSystemMetrics(SM_CXVIRTUALSCREEN); + if (tmp) vo->opts->vo_screenwidth = tmp; + tmp = GetSystemMetrics(SM_CYVIRTUALSCREEN); + if (tmp) vo->opts->vo_screenheight = tmp; + } else if (xinerama_screen == -1) { + MONITORINFO mi; + HMONITOR m = MonitorFromWindow(w32->window, MONITOR_DEFAULTTOPRIMARY); + mi.cbSize = sizeof(mi); + GetMonitorInfoW(m, &mi); + xinerama_x = mi.rcMonitor.left; + xinerama_y = mi.rcMonitor.top; + vo->opts->vo_screenwidth = mi.rcMonitor.right - mi.rcMonitor.left; + vo->opts->vo_screenheight = mi.rcMonitor.bottom - mi.rcMonitor.top; + } else if (xinerama_screen > 0) { + w32->mon_cnt = 0; + EnumDisplayMonitors(NULL, NULL, mon_enum, (LONG_PTR)vo); + } + aspect_save_screenres(vo, vo->opts->vo_screenwidth, + vo->opts->vo_screenheight); +} + +static void updateScreenProperties(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + DEVMODE dm; + dm.dmSize = sizeof dm; + dm.dmDriverExtra = 0; + dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + if (!EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &dm)) { + mp_msg(MSGT_VO, MSGL_ERR, + "vo: win32: unable to enumerate display settings!\n"); + return; + } + + vo->opts->vo_screenwidth = dm.dmPelsWidth; + vo->opts->vo_screenheight = dm.dmPelsHeight; + w32->depthonscreen = dm.dmBitsPerPel; + w32_update_xinerama_info(vo); +} + +static void changeMode(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + DEVMODE dm; + dm.dmSize = sizeof dm; + dm.dmDriverExtra = 0; + + dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + dm.dmBitsPerPel = w32->depthonscreen; + dm.dmPelsWidth = vo->opts->vo_screenwidth; + dm.dmPelsHeight = vo->opts->vo_screenheight; + + if (w32->vm) { + int bestMode = -1; + int bestScore = INT_MAX; + int i; + for (i = 0; EnumDisplaySettings(0, i, &dm); ++i) { + int score = (dm.dmPelsWidth - w32->o_dwidth) + * (dm.dmPelsHeight - w32->o_dheight); + if (dm.dmBitsPerPel != w32->depthonscreen + || dm.dmPelsWidth < w32->o_dwidth + || dm.dmPelsHeight < w32->o_dheight) + continue; + + if (score < bestScore) { + bestScore = score; + bestMode = i; + } + } + + if (bestMode != -1) + EnumDisplaySettings(0, bestMode, &dm); + + ChangeDisplaySettings(&dm, CDS_FULLSCREEN); + } +} + +static void resetMode(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + if (w32->vm) + ChangeDisplaySettings(0, 0); +} + +static DWORD update_style(struct vo *vo, DWORD style) +{ + const DWORD NO_FRAME = WS_POPUP; + const DWORD FRAME = WS_OVERLAPPEDWINDOW | WS_SIZEBOX; + style &= ~(NO_FRAME | FRAME); + style |= (vo_border && !vo_fs) ? FRAME : NO_FRAME; + return style; +} + +// Update the window title, position, size, and border style from vo_* values. +static int reinit_window_state(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + HWND layer = HWND_NOTOPMOST; + RECT r; + + if (WinID >= 0) + return 1; + + wchar_t *title = mp_from_utf8(NULL, vo_get_window_title(vo)); + SetWindowTextW(w32->window, title); + talloc_free(title); + + bool toggle_fs = w32->current_fs != vo_fs; + w32->current_fs = vo_fs; + + DWORD style = update_style(vo, GetWindowLong(w32->window, GWL_STYLE)); + + if (vo_fs || vo->opts->vo_ontop) + layer = HWND_TOPMOST; + + // xxx not sure if this can trigger any unwanted messages (WM_MOVE/WM_SIZE) + if (vo_fs) { + changeMode(vo); + while (ShowCursor(0) >= 0) /**/ ; + } else { + resetMode(vo); + while (ShowCursor(1) < 0) /**/ ; + } + updateScreenProperties(vo); + + if (vo_fs) { + // Save window position and size when switching to fullscreen. + if (toggle_fs) { + w32->prev_width = vo->dwidth; + w32->prev_height = vo->dheight; + w32->prev_x = w32->window_x; + w32->prev_y = w32->window_y; + mp_msg(MSGT_VO, MSGL_V, "[vo] save window bounds: %d:%d:%d:%d\n", + w32->prev_x, w32->prev_y, w32->prev_width, w32->prev_height); + } + vo->dwidth = vo->opts->vo_screenwidth; + vo->dheight = vo->opts->vo_screenheight; + w32->window_x = xinerama_x; + w32->window_y = xinerama_y; + } else { + if (toggle_fs) { + // Restore window position and size when switching from fullscreen. + mp_msg(MSGT_VO, MSGL_V, "[vo] restore window bounds: %d:%d:%d:%d\n", + w32->prev_x, w32->prev_y, w32->prev_width, w32->prev_height); + vo->dwidth = w32->prev_width; + vo->dheight = w32->prev_height; + w32->window_x = w32->prev_x; + w32->window_y = w32->prev_y; + } + } + + r.left = w32->window_x; + r.right = r.left + vo->dwidth; + r.top = w32->window_y; + r.bottom = r.top + vo->dheight; + + SetWindowLong(w32->window, GWL_STYLE, style); + add_window_borders(w32->window, &r); + + mp_msg(MSGT_VO, MSGL_V, "[vo] reset window bounds: %ld:%ld:%ld:%ld\n", + r.left, r.top, r.right - r.left, r.bottom - r.top); + + SetWindowPos(w32->window, layer, r.left, r.top, r.right - r.left, + r.bottom - r.top, SWP_FRAMECHANGED); + // For some reason, moving SWP_SHOWWINDOW to a second call works better + // with wine: returning from fullscreen doesn't cause a bogus resize to + // screen size. + // It's not needed on Windows XP or wine with a virtual desktop. + // It doesn't seem to have any negative effects. + SetWindowPos(w32->window, NULL, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW); + + return 1; +} + +/** + * \brief Configure and show window on the screen. + * + * This function should be called in libvo's "config" callback. + * It configures a window and shows it on the screen. + * + * \return 1 - Success, 0 - Failure + */ +int vo_w32_config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t flags) +{ + struct vo_w32_state *w32 = vo->w32; + PIXELFORMATDESCRIPTOR pfd; + int pf; + HDC vo_hdc = vo_w32_get_dc(vo, w32->window); + + memset(&pfd, 0, sizeof pfd); + pfd.nSize = sizeof pfd; + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + if (flags & VOFLAG_STEREO) + pfd.dwFlags |= PFD_STEREO; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 24; + pfd.iLayerType = PFD_MAIN_PLANE; + pf = ChoosePixelFormat(vo_hdc, &pfd); + if (!pf) { + mp_msg(MSGT_VO, MSGL_ERR, "vo: win32: unable to select a valid pixel format!\n"); + vo_w32_release_dc(vo, w32->window, vo_hdc); + return 0; + } + + SetPixelFormat(vo_hdc, pf, &pfd); + vo_w32_release_dc(vo, w32->window, vo_hdc); + + // we already have a fully initialized window, so nothing needs to be done + if (flags & VOFLAG_HIDDEN) + return 1; + + bool reset_size = !(w32->o_dwidth == width && w32->o_dheight == height); + + w32->o_dwidth = width; + w32->o_dheight = height; + + // the desired size is ignored in wid mode, it always matches the window size. + if (WinID < 0) { + if (w32->window_bounds_initialized) { + // restore vo_dwidth/vo_dheight, which are reset against our will + // in vo_config() + RECT r; + GetClientRect(w32->window, &r); + vo->dwidth = r.right; + vo->dheight = r.bottom; + } else { + // first vo_config call; vo_config() will always set vo_dx/dy so + // that the window is centered on the screen, and this is the only + // time we actually want to use vo_dy/dy (this is not sane, and + // video_out.h should provide a function to query the initial + // window position instead) + w32->window_bounds_initialized = true; + reset_size = true; + w32->window_x = w32->prev_x = vo->dx; + w32->window_y = w32->prev_y = vo->dy; + } + if (reset_size) { + w32->prev_width = vo->dwidth = width; + w32->prev_height = vo->dheight = height; + } + } + + vo_fs = flags & VOFLAG_FULLSCREEN; + w32->vm = flags & VOFLAG_MODESWITCHING; + return reinit_window_state(vo); +} + +/** + * \brief Initialize w32_common framework. + * + * The first function that should be called from the w32_common framework. + * It handles window creation on the screen with proper title and attributes. + * It also initializes the framework's internal variables. The function should + * be called after your own preinit initialization and you shouldn't do any + * window management on your own. + * + * Global libvo variables changed: + * vo_w32_window + * vo_screenwidth + * vo_screenheight + * + * \return 1 = Success, 0 = Failure + */ +int vo_w32_init(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + if (w32 && w32->window) + return 1; + + if (!w32) + w32 = vo->w32 = talloc_zero(vo, struct vo_w32_state); + + HINSTANCE hInstance = GetModuleHandleW(NULL); + + HICON mplayerIcon = LoadIconW(hInstance, L"IDI_ICON1"); + + WNDCLASSEXW wcex = { + .cbSize = sizeof wcex, + .style = CS_OWNDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW, + .lpfnWndProc = WndProc, + .hInstance = hInstance, + .hIcon = mplayerIcon, + .hCursor = LoadCursor(0, IDC_ARROW), + .lpszClassName = classname, + .hIconSm = mplayerIcon, + }; + + if (!RegisterClassExW(&wcex)) { + mp_msg(MSGT_VO, MSGL_ERR, + "vo: win32: unable to register window class!\n"); + return 0; + } + + if (WinID >= 0) { + RECT r; + GetClientRect(WIN_ID_TO_HWND(WinID), &r); + vo->dwidth = r.right; vo->dheight = r.bottom; + w32->window = CreateWindowExW(WS_EX_NOPARENTNOTIFY, classname, + classname, + WS_CHILD | WS_VISIBLE, + 0, 0, vo->dwidth, vo->dheight, + WIN_ID_TO_HWND(WinID), 0, hInstance, vo); + } else { + w32->window = CreateWindowExW(0, classname, + classname, + update_style(vo, 0), + CW_USEDEFAULT, 0, 100, 100, + 0, 0, hInstance, vo); + } + + if (!w32->window) { + mp_msg(MSGT_VO, MSGL_ERR, "vo: win32: unable to create window!\n"); + return 0; + } + + if (WinID >= 0) + EnableWindow(w32->window, 0); + + updateScreenProperties(vo); + + mp_msg(MSGT_VO, MSGL_V, "vo: win32: running at %dx%d with depth %d\n", + vo->opts->vo_screenwidth, vo->opts->vo_screenheight, + w32->depthonscreen); + + return 1; +} + +/** + * \brief Toogle fullscreen / windowed mode. + * + * Should be called on VOCTRL_FULLSCREEN event. The window is + * always resized during this call, so the rendering context + * should be reinitialized with the new dimensions. + * It is unspecified if vo_check_events will create a resize + * event in addition or not. + */ + +void vo_w32_fullscreen(struct vo *vo) +{ + vo_fs = !vo_fs; + reinit_window_state(vo); +} + +/** + * \brief Toogle window border attribute. + * + * Should be called on VOCTRL_BORDER event. + */ +void vo_w32_border(struct vo *vo) +{ + vo_border = !vo_border; + reinit_window_state(vo); +} + +/** + * \brief Toogle window ontop attribute. + * + * Should be called on VOCTRL_ONTOP event. + */ +void vo_w32_ontop(struct vo *vo) +{ + vo->opts->vo_ontop = !vo->opts->vo_ontop; + reinit_window_state(vo); +} + +/** + * \brief Uninitialize w32_common framework. + * + * Should be called last in video driver's uninit function. First release + * anything built on top of the created window e.g. rendering context inside + * and call vo_w32_uninit at the end. + */ +void vo_w32_uninit(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + mp_msg(MSGT_VO, MSGL_V, "vo: win32: uninit\n"); + if (!w32) + return; + resetMode(vo); + ShowCursor(1); + DestroyWindow(w32->window); + UnregisterClassW(classname, 0); + talloc_free(w32); + vo->w32 = NULL; +} + +/** + * \brief get a device context to draw in + * + * \param wnd window the DC should belong to if it makes sense + */ +HDC vo_w32_get_dc(struct vo *vo, HWND wnd) +{ + struct vo_w32_state *w32 = vo->w32; + return GetDC(wnd); +} + +/** + * \brief release a device context + * + * \param wnd window the DC probably belongs to + */ +void vo_w32_release_dc(struct vo *vo, HWND wnd, HDC dc) +{ + struct vo_w32_state *w32 = vo->w32; + ReleaseDC(wnd, dc); +} diff --git a/video/out/w32_common.h b/video/out/w32_common.h new file mode 100644 index 0000000000..c6d9fc7653 --- /dev/null +++ b/video/out/w32_common.h @@ -0,0 +1,66 @@ +/* + * 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_W32_COMMON_H +#define MPLAYER_W32_COMMON_H + +#include <stdint.h> +#include <stdbool.h> +#include <windows.h> + +struct vo_w32_state { + HWND window; + + bool vm; + + int depthonscreen; + + // last non-fullscreen extends (updated only on fullscreen or on initialization) + int prev_width; + int prev_height; + int prev_x; + int prev_y; + + // whether the window position and size were intialized + bool window_bounds_initialized; + + bool current_fs; + + int window_x; + int window_y; + + // video size + uint32_t o_dwidth; + uint32_t o_dheight; + + int event_flags; + int mon_cnt; +}; + +int vo_w32_init(struct vo *vo); +void vo_w32_uninit(struct vo *vo); +void vo_w32_ontop(struct vo *vo); +void vo_w32_border(struct vo *vo); +void vo_w32_fullscreen(struct vo *vo); +int vo_w32_check_events(struct vo *vo); +int vo_w32_config(struct vo *vo, uint32_t, uint32_t, uint32_t); +void w32_update_xinerama_info(struct vo *vo); +HDC vo_w32_get_dc(struct vo *vo, HWND wnd); +void vo_w32_release_dc(struct vo *vo, HWND wnd, HDC dc); + +#endif /* MPLAYER_W32_COMMON_H */ diff --git a/video/out/x11_common.c b/video/out/x11_common.c new file mode 100644 index 0000000000..04d5c6880b --- /dev/null +++ b/video/out/x11_common.c @@ -0,0 +1,2404 @@ +/* + * 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 <math.h> +#include <inttypes.h> +#include <limits.h> + +#include "config.h" +#include "bstr.h" +#include "options.h" +#include "mp_msg.h" +#include "mp_fifo.h" +#include "libavutil/common.h" +#include "x11_common.h" +#include "talloc.h" + +#include <string.h> +#include <unistd.h> +#include <assert.h> + +#include "video_out.h" +#include "aspect.h" +#include "geometry.h" +#include "osdep/timer.h" + +#include <X11/Xmd.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#include <X11/keysym.h> + +#ifdef CONFIG_XSS +#include <X11/extensions/scrnsaver.h> +#endif + +#ifdef CONFIG_XDPMS +#include <X11/extensions/dpms.h> +#endif + +#ifdef CONFIG_XINERAMA +#include <X11/extensions/Xinerama.h> +#endif + +#ifdef CONFIG_XF86VM +#include <X11/extensions/xf86vmode.h> +#endif + +#ifdef CONFIG_XF86XK +#include <X11/XF86keysym.h> +#endif + +#ifdef CONFIG_XV +#include <X11/extensions/Xv.h> +#include <X11/extensions/Xvlib.h> + +#include "subopt-helper.h" +#endif + +#include "input/input.h" +#include "input/keycodes.h" + +#define WIN_LAYER_ONBOTTOM 2 +#define WIN_LAYER_NORMAL 4 +#define WIN_LAYER_ONTOP 6 +#define WIN_LAYER_ABOVE_DOCK 10 + +int fs_layer = WIN_LAYER_ABOVE_DOCK; + +int stop_xscreensaver = 1; + +static int dpms_disabled = 0; + +char *mDisplayName = NULL; + +char **vo_fstype_list; + +/* 1 means that the WM is metacity (broken as hell) */ +int metacity_hack = 0; + +#ifdef CONFIG_XF86VM +static int modecount; +static XF86VidModeModeInfo **vidmodes; +static XF86VidModeModeLine modeline; +#endif + +static int vo_x11_get_fs_type(int supported); +static void saver_off(Display *); +static void saver_on(Display *); + +/* + * Sends the EWMH fullscreen state event. + * + * action: could be one of _NET_WM_STATE_REMOVE -- remove state + * _NET_WM_STATE_ADD -- add state + * _NET_WM_STATE_TOGGLE -- toggle + */ +void vo_x11_ewmh_fullscreen(struct vo_x11_state *x11, int action) +{ + assert(action == _NET_WM_STATE_REMOVE || + action == _NET_WM_STATE_ADD || action == _NET_WM_STATE_TOGGLE); + + if (x11->fs_type & vo_wm_FULLSCREEN) + { + XEvent xev; + + /* init X event structure for _NET_WM_FULLSCREEN client message */ + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.message_type = x11->XA_NET_WM_STATE; + xev.xclient.window = x11->window; + xev.xclient.format = 32; + xev.xclient.data.l[0] = action; + xev.xclient.data.l[1] = x11->XA_NET_WM_STATE_FULLSCREEN; + xev.xclient.data.l[2] = 0; + xev.xclient.data.l[3] = 0; + xev.xclient.data.l[4] = 0; + + /* finally send that damn thing */ + if (!XSendEvent(x11->display, DefaultRootWindow(x11->display), False, + SubstructureRedirectMask | SubstructureNotifyMask, + &xev)) + { + mp_tmsg(MSGT_VO, MSGL_ERR, "\nX11: Couldn't send EWMH fullscreen event!\n"); + } + } +} + +static void vo_hidecursor(Display * disp, Window win) +{ + Cursor no_ptr; + Pixmap bm_no; + XColor black, dummy; + Colormap colormap; + const char bm_no_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + if (WinID == 0) + return; // do not hide if playing on the root window + + colormap = DefaultColormap(disp, DefaultScreen(disp)); + if ( !XAllocNamedColor(disp, colormap, "black", &black, &dummy) ) + { + return; // color alloc failed, give up + } + bm_no = XCreateBitmapFromData(disp, win, bm_no_data, 8, 8); + no_ptr = XCreatePixmapCursor(disp, bm_no, bm_no, &black, &black, 0, 0); + XDefineCursor(disp, win, no_ptr); + XFreeCursor(disp, no_ptr); + if (bm_no != None) + XFreePixmap(disp, bm_no); + XFreeColors(disp,colormap,&black.pixel,1,0); +} + +static void vo_showcursor(Display * disp, Window win) +{ + if (WinID == 0) + return; + XDefineCursor(disp, win, 0); +} + +static int x11_errorhandler(Display * display, XErrorEvent * event) +{ +#define MSGLEN 60 + char msg[MSGLEN]; + + XGetErrorText(display, event->error_code, (char *) &msg, MSGLEN); + + mp_msg(MSGT_VO, MSGL_ERR, "X11 error: %s\n", msg); + + mp_msg(MSGT_VO, MSGL_V, + "Type: %x, display: %p, resourceid: %lx, serial: %lx\n", + event->type, event->display, event->resourceid, event->serial); + mp_msg(MSGT_VO, MSGL_V, + "Error code: %x, request code: %x, minor code: %x\n", + event->error_code, event->request_code, event->minor_code); + +// abort(); + return 0; +#undef MSGLEN +} + +void fstype_help(void) +{ + mp_tmsg(MSGT_VO, MSGL_INFO, "Available fullscreen layer change modes:\n"); + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_FULL_SCREEN_TYPES\n"); + + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "none", + "don't set fullscreen window layer"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "layer", + "use _WIN_LAYER hint with default layer"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "layer=<0..15>", + "use _WIN_LAYER hint with a given layer number"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "netwm", + "force NETWM style"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "above", + "use _NETWM_STATE_ABOVE hint if available"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "below", + "use _NETWM_STATE_BELOW hint if available"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "fullscreen", + "use _NETWM_STATE_FULLSCREEN hint if available"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "stays_on_top", + "use _NETWM_STATE_STAYS_ON_TOP hint if available"); + mp_msg(MSGT_VO, MSGL_INFO, + "You can also negate the settings with simply putting '-' in the beginning"); + mp_msg(MSGT_VO, MSGL_INFO, "\n"); +} + +static void fstype_dump(int fstype) +{ + if (fstype) + { + mp_msg(MSGT_VO, MSGL_V, "[x11] Current fstype setting honours"); + if (fstype & vo_wm_LAYER) + mp_msg(MSGT_VO, MSGL_V, " LAYER"); + if (fstype & vo_wm_FULLSCREEN) + mp_msg(MSGT_VO, MSGL_V, " FULLSCREEN"); + if (fstype & vo_wm_STAYS_ON_TOP) + mp_msg(MSGT_VO, MSGL_V, " STAYS_ON_TOP"); + if (fstype & vo_wm_ABOVE) + mp_msg(MSGT_VO, MSGL_V, " ABOVE"); + if (fstype & vo_wm_BELOW) + mp_msg(MSGT_VO, MSGL_V, " BELOW"); + mp_msg(MSGT_VO, MSGL_V, " X atoms\n"); + } else + mp_msg(MSGT_VO, MSGL_V, + "[x11] Current fstype setting doesn't honour any X atoms\n"); +} + +static int net_wm_support_state_test(struct vo_x11_state *x11, Atom atom) +{ +#define NET_WM_STATE_TEST(x) { if (atom == x11->XA_NET_WM_STATE_##x) { mp_msg( MSGT_VO,MSGL_V, "[x11] Detected wm supports " #x " state.\n" ); return vo_wm_##x; } } + + NET_WM_STATE_TEST(FULLSCREEN); + NET_WM_STATE_TEST(ABOVE); + NET_WM_STATE_TEST(STAYS_ON_TOP); + NET_WM_STATE_TEST(BELOW); + return 0; +} + +static int x11_get_property(struct vo_x11_state *x11, Atom type, Atom ** args, + unsigned long *nitems) +{ + int format; + unsigned long bytesafter; + + return Success == + XGetWindowProperty(x11->display, x11->rootwin, type, 0, 16384, False, + AnyPropertyType, &type, &format, nitems, + &bytesafter, (unsigned char **) args) + && *nitems > 0; +} + +static int vo_wm_detect(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + int i; + int wm = 0; + unsigned long nitems; + Atom *args = NULL; + + if (WinID >= 0) + return 0; + +// -- supports layers + if (x11_get_property(x11, x11->XA_WIN_PROTOCOLS, &args, &nitems)) + { + mp_msg(MSGT_VO, MSGL_V, "[x11] Detected wm supports layers.\n"); + for (i = 0; i < nitems; i++) + { + if (args[i] == x11->XA_WIN_LAYER) + { + wm |= vo_wm_LAYER; + metacity_hack |= 1; + } else + /* metacity is the only window manager I know which reports + * supporting only the _WIN_LAYER hint in _WIN_PROTOCOLS. + * (what's more support for it is broken) */ + metacity_hack |= 2; + } + XFree(args); + if (wm && (metacity_hack == 1)) + { + // metacity claims to support layers, but it is not the truth :-) + wm ^= vo_wm_LAYER; + mp_msg(MSGT_VO, MSGL_V, + "[x11] Using workaround for Metacity bugs.\n"); + } + } +// --- netwm + if (x11_get_property(x11, x11->XA_NET_SUPPORTED, &args, &nitems)) + { + mp_msg(MSGT_VO, MSGL_V, "[x11] Detected wm supports NetWM.\n"); + for (i = 0; i < nitems; i++) + wm |= net_wm_support_state_test(vo->x11, args[i]); + XFree(args); + } + + if (wm == 0) + mp_msg(MSGT_VO, MSGL_V, "[x11] Unknown wm type...\n"); + return wm; +} + +#define XA_INIT(x) x11->XA##x = XInternAtom(x11->display, #x, False) +static void init_atoms(struct vo_x11_state *x11) +{ + XA_INIT(_NET_SUPPORTED); + XA_INIT(_NET_WM_STATE); + XA_INIT(_NET_WM_STATE_FULLSCREEN); + XA_INIT(_NET_WM_STATE_ABOVE); + XA_INIT(_NET_WM_STATE_STAYS_ON_TOP); + XA_INIT(_NET_WM_STATE_BELOW); + XA_INIT(_NET_WM_PID); + XA_INIT(_NET_WM_NAME); + XA_INIT(_NET_WM_ICON_NAME); + XA_INIT(_WIN_PROTOCOLS); + XA_INIT(_WIN_LAYER); + XA_INIT(_WIN_HINTS); + XA_INIT(WM_PROTOCOLS); + XA_INIT(WM_DELETE_WINDOW); + XA_INIT(UTF8_STRING); + char buf[50]; + sprintf(buf, "_NET_WM_CM_S%d", x11->screen); + x11->XA_NET_WM_CM = XInternAtom(x11->display, buf, False); +} + +void update_xinerama_info(struct vo *vo) { + struct MPOpts *opts = vo->opts; + xinerama_x = xinerama_y = 0; +#ifdef CONFIG_XINERAMA + if (xinerama_screen >= -1 && XineramaIsActive(vo->x11->display)) + { + int screen = xinerama_screen; + XineramaScreenInfo *screens; + int num_screens; + + screens = XineramaQueryScreens(vo->x11->display, &num_screens); + if (screen >= num_screens) + screen = num_screens - 1; + if (screen == -1) { + int x = vo->dx + vo->dwidth / 2; + int y = vo->dy + vo->dheight / 2; + for (screen = num_screens - 1; screen > 0; screen--) { + int left = screens[screen].x_org; + int right = left + screens[screen].width; + int top = screens[screen].y_org; + int bottom = top + screens[screen].height; + if (left <= x && x <= right && top <= y && y <= bottom) + break; + } + } + if (screen < 0) + screen = 0; + opts->vo_screenwidth = screens[screen].width; + opts->vo_screenheight = screens[screen].height; + xinerama_x = screens[screen].x_org; + xinerama_y = screens[screen].y_org; + + XFree(screens); + } +#endif + aspect_save_screenres(vo, opts->vo_screenwidth, opts->vo_screenheight); +} + +int vo_init(struct vo *vo) +{ + struct MPOpts *opts = vo->opts; +// int mScreen; + int depth, bpp; + unsigned int mask; + +// char * DisplayName = ":0.0"; +// Display * mDisplay; + XImage *mXImage = NULL; + +// Window mRootWin; + XWindowAttributes attribs; + char *dispName; + + if (vo->x11) + return 1; + + vo->x11 = vo_x11_init_state(); + struct vo_x11_state *x11 = vo->x11; + + if (vo_rootwin) + WinID = 0; // use root window + + if (x11->depthonscreen) + { + saver_off(x11->display); + return 1; // already called + } + + XSetErrorHandler(x11_errorhandler); + +#if 0 + if (!mDisplayName) + if (!(mDisplayName = getenv("DISPLAY"))) + mDisplayName = strdup(":0.0"); +#else + dispName = XDisplayName(mDisplayName); +#endif + + mp_msg(MSGT_VO, MSGL_V, "X11 opening display: %s\n", dispName); + + x11->display = XOpenDisplay(dispName); + if (!x11->display) + { + mp_msg(MSGT_VO, MSGL_ERR, + "vo: couldn't open the X11 display (%s)!\n", dispName); + talloc_free(x11); + vo->x11 = NULL; + return 0; + } + x11->screen = DefaultScreen(x11->display); // screen ID + x11->rootwin = RootWindow(x11->display, x11->screen); // root window ID + + x11->xim = XOpenIM(x11->display, NULL, NULL, NULL); + + init_atoms(vo->x11); + +#ifdef CONFIG_XF86VM + { + int clock; + + XF86VidModeGetModeLine(x11->display, x11->screen, &clock, &modeline); + if (!opts->vo_screenwidth) + opts->vo_screenwidth = modeline.hdisplay; + if (!opts->vo_screenheight) + opts->vo_screenheight = modeline.vdisplay; + } +#endif + { + if (!opts->vo_screenwidth) + opts->vo_screenwidth = DisplayWidth(x11->display, x11->screen); + if (!opts->vo_screenheight) + opts->vo_screenheight = DisplayHeight(x11->display, x11->screen); + } + // get color depth (from root window, or the best visual): + XGetWindowAttributes(x11->display, x11->rootwin, &attribs); + depth = attribs.depth; + + if (depth != 15 && depth != 16 && depth != 24 && depth != 32) + { + Visual *visual; + + depth = vo_find_depth_from_visuals(x11->display, x11->screen, &visual); + if (depth != -1) + mXImage = XCreateImage(x11->display, visual, depth, ZPixmap, + 0, NULL, 1, 1, 8, 1); + } else + mXImage = + XGetImage(x11->display, x11->rootwin, 0, 0, 1, 1, AllPlanes, ZPixmap); + + x11->depthonscreen = depth; // display depth on screen + + // get bits/pixel from XImage structure: + if (mXImage == NULL) + { + mask = 0; + } else + { + /* + * for the depth==24 case, the XImage structures might use + * 24 or 32 bits of data per pixel. The x11->depthonscreen + * field stores the amount of data per pixel in the + * XImage structure! + * + * Maybe we should rename vo_depthonscreen to (or add) vo_bpp? + */ + bpp = mXImage->bits_per_pixel; + if ((x11->depthonscreen + 7) / 8 != (bpp + 7) / 8) + x11->depthonscreen = bpp; // by A'rpi + mask = + mXImage->red_mask | mXImage->green_mask | mXImage->blue_mask; + mp_msg(MSGT_VO, MSGL_V, + "vo: X11 color mask: %X (R:%lX G:%lX B:%lX)\n", mask, + mXImage->red_mask, mXImage->green_mask, mXImage->blue_mask); + XDestroyImage(mXImage); + } + if (((x11->depthonscreen + 7) / 8) == 2) + { + if (mask == 0x7FFF) + x11->depthonscreen = 15; + else if (mask == 0xFFFF) + x11->depthonscreen = 16; + } +// XCloseDisplay( mDisplay ); +/* slightly improved local display detection AST */ + if (strncmp(dispName, "unix:", 5) == 0) + dispName += 4; + else if (strncmp(dispName, "localhost:", 10) == 0) + dispName += 9; + if (*dispName == ':' && atoi(dispName + 1) < 10) + x11->display_is_local = 1; + else + x11->display_is_local = 0; + mp_msg(MSGT_VO, MSGL_V, + "vo: X11 running at %dx%d with depth %d and %d bpp (\"%s\" => %s display)\n", + opts->vo_screenwidth, opts->vo_screenheight, depth, x11->depthonscreen, + dispName, x11->display_is_local ? "local" : "remote"); + + x11->wm_type = vo_wm_detect(vo); + + x11->fs_type = vo_x11_get_fs_type(x11->wm_type); + + fstype_dump(x11->fs_type); + + saver_off(x11->display); + return 1; +} + +void vo_uninit(struct vo_x11_state *x11) +{ + if (!x11) + return; + if (!x11->display) + { + mp_msg(MSGT_VO, MSGL_V, + "vo: x11 uninit called but X11 not initialized..\n"); + } else { + mp_msg(MSGT_VO, MSGL_V, "vo: uninit ...\n"); + if (x11->xim) + XCloseIM(x11->xim); + XSetErrorHandler(NULL); + XCloseDisplay(x11->display); + x11->depthonscreen = 0; + x11->display = NULL; + } + talloc_free(x11); +} + +static const struct mp_keymap keymap[] = { + // special keys + {XK_Pause, KEY_PAUSE}, {XK_Escape, KEY_ESC}, {XK_BackSpace, KEY_BS}, + {XK_Tab, KEY_TAB}, {XK_Return, KEY_ENTER}, + {XK_Menu, KEY_MENU}, {XK_Print, KEY_PRINT}, + + // cursor keys + {XK_Left, KEY_LEFT}, {XK_Right, KEY_RIGHT}, {XK_Up, KEY_UP}, {XK_Down, KEY_DOWN}, + + // navigation block + {XK_Insert, KEY_INSERT}, {XK_Delete, KEY_DELETE}, {XK_Home, KEY_HOME}, {XK_End, KEY_END}, + {XK_Page_Up, KEY_PAGE_UP}, {XK_Page_Down, KEY_PAGE_DOWN}, + + // F-keys + {XK_F1, KEY_F+1}, {XK_F2, KEY_F+2}, {XK_F3, KEY_F+3}, {XK_F4, KEY_F+4}, + {XK_F5, KEY_F+5}, {XK_F6, KEY_F+6}, {XK_F7, KEY_F+7}, {XK_F8, KEY_F+8}, + {XK_F9, KEY_F+9}, {XK_F10, KEY_F+10}, {XK_F11, KEY_F+11}, {XK_F12, KEY_F+12}, + + // numpad independent of numlock + {XK_KP_Subtract, '-'}, {XK_KP_Add, '+'}, {XK_KP_Multiply, '*'}, {XK_KP_Divide, '/'}, + {XK_KP_Enter, KEY_KPENTER}, + + // numpad with numlock + {XK_KP_0, KEY_KP0}, {XK_KP_1, KEY_KP1}, {XK_KP_2, KEY_KP2}, + {XK_KP_3, KEY_KP3}, {XK_KP_4, KEY_KP4}, {XK_KP_5, KEY_KP5}, + {XK_KP_6, KEY_KP6}, {XK_KP_7, KEY_KP7}, {XK_KP_8, KEY_KP8}, + {XK_KP_9, KEY_KP9}, {XK_KP_Decimal, KEY_KPDEC}, + {XK_KP_Separator, KEY_KPDEC}, + + // numpad without numlock + {XK_KP_Insert, KEY_KPINS}, {XK_KP_End, KEY_KP1}, {XK_KP_Down, KEY_KP2}, + {XK_KP_Page_Down, KEY_KP3}, {XK_KP_Left, KEY_KP4}, {XK_KP_Begin, KEY_KP5}, + {XK_KP_Right, KEY_KP6}, {XK_KP_Home, KEY_KP7}, {XK_KP_Up, KEY_KP8}, + {XK_KP_Page_Up, KEY_KP9}, {XK_KP_Delete, KEY_KPDEL}, + +#ifdef XF86XK_AudioPause + {XF86XK_MenuKB, KEY_MENU}, + {XF86XK_AudioPlay, KEY_PLAY}, {XF86XK_AudioPause, KEY_PAUSE}, {XF86XK_AudioStop, KEY_STOP}, + {XF86XK_AudioPrev, KEY_PREV}, {XF86XK_AudioNext, KEY_NEXT}, + {XF86XK_AudioMute, KEY_MUTE}, {XF86XK_AudioLowerVolume, KEY_VOLUME_DOWN}, {XF86XK_AudioRaiseVolume, KEY_VOLUME_UP}, +#endif + + {0, 0} +}; + +static int vo_x11_lookupkey(int key) +{ + static const char *passthrough_keys = " -+*/<>`~!@#$%^&()_{}:;\"\',.?\\|=[]"; + int mpkey = 0; + if ((key >= 'a' && key <= 'z') || + (key >= 'A' && key <= 'Z') || + (key >= '0' && key <= '9') || + (key > 0 && key < 256 && strchr(passthrough_keys, key))) + mpkey = key; + + if (!mpkey) + mpkey = lookup_keymap_table(keymap, key); + + return mpkey; +} + + +// ----- Motif header: ------- + +#define MWM_HINTS_FUNCTIONS (1L << 0) +#define MWM_HINTS_DECORATIONS (1L << 1) +#define MWM_HINTS_INPUT_MODE (1L << 2) +#define MWM_HINTS_STATUS (1L << 3) + +#define MWM_FUNC_ALL (1L << 0) +#define MWM_FUNC_RESIZE (1L << 1) +#define MWM_FUNC_MOVE (1L << 2) +#define MWM_FUNC_MINIMIZE (1L << 3) +#define MWM_FUNC_MAXIMIZE (1L << 4) +#define MWM_FUNC_CLOSE (1L << 5) + +#define MWM_DECOR_ALL (1L << 0) +#define MWM_DECOR_BORDER (1L << 1) +#define MWM_DECOR_RESIZEH (1L << 2) +#define MWM_DECOR_TITLE (1L << 3) +#define MWM_DECOR_MENU (1L << 4) +#define MWM_DECOR_MINIMIZE (1L << 5) +#define MWM_DECOR_MAXIMIZE (1L << 6) + +#define MWM_INPUT_MODELESS 0 +#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 +#define MWM_INPUT_SYSTEM_MODAL 2 +#define MWM_INPUT_FULL_APPLICATION_MODAL 3 +#define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL + +#define MWM_TEAROFF_WINDOW (1L<<0) + +typedef struct +{ + long flags; + long functions; + long decorations; + long input_mode; + long state; +} MotifWmHints; + +static MotifWmHints vo_MotifWmHints; +static Atom vo_MotifHints = None; + +void vo_x11_decoration(struct vo *vo, int d) +{ + struct vo_x11_state *x11 = vo->x11; + Atom mtype; + int mformat; + unsigned long mn, mb; + + if (!WinID) + return; + + if (vo_fsmode & 8) + { + XSetTransientForHint(x11->display, x11->window, + RootWindow(x11->display, x11->screen)); + } + + vo_MotifHints = XInternAtom(x11->display, "_MOTIF_WM_HINTS", 0); + if (vo_MotifHints != None) + { + if (!d) + { + MotifWmHints *mhints = NULL; + + XGetWindowProperty(x11->display, x11->window, + vo_MotifHints, 0, 20, False, + vo_MotifHints, &mtype, &mformat, &mn, + &mb, (unsigned char **) &mhints); + if (mhints) + { + if (mhints->flags & MWM_HINTS_DECORATIONS) + x11->olddecor = mhints->decorations; + if (mhints->flags & MWM_HINTS_FUNCTIONS) + x11->oldfuncs = mhints->functions; + XFree(mhints); + } + } + + memset(&vo_MotifWmHints, 0, sizeof(MotifWmHints)); + vo_MotifWmHints.flags = + MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; + if (d) + { + vo_MotifWmHints.functions = x11->oldfuncs; + d = x11->olddecor; + } +#if 0 + vo_MotifWmHints.decorations = + d | ((vo_fsmode & 2) ? 0 : MWM_DECOR_MENU); +#else + vo_MotifWmHints.decorations = + d | ((vo_fsmode & 2) ? MWM_DECOR_MENU : 0); +#endif + XChangeProperty(x11->display, x11->window, vo_MotifHints, + vo_MotifHints, 32, + PropModeReplace, + (unsigned char *) &vo_MotifWmHints, + (vo_fsmode & 4) ? 4 : 5); + } +} + +void vo_x11_classhint(struct vo *vo, Window window, const char *name) +{ + struct MPOpts *opts = vo->opts; + struct vo_x11_state *x11 = vo->x11; + XClassHint wmClass; + pid_t pid = getpid(); + + wmClass.res_name = opts->vo_winname ? opts->vo_winname : (char *)name; + wmClass.res_class = "mpv"; + XSetClassHint(x11->display, window, &wmClass); + XChangeProperty(x11->display, window, x11->XA_NET_WM_PID, XA_CARDINAL, + 32, PropModeReplace, (unsigned char *) &pid, 1); +} + +void vo_x11_uninit(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + saver_on(x11->display); + if (x11->window != None) + vo_showcursor(x11->display, x11->window); + + if (x11->f_gc != None) + { + XFreeGC(vo->x11->display, x11->f_gc); + x11->f_gc = None; + } + { + if (x11->vo_gc != None) + { + XFreeGC(vo->x11->display, x11->vo_gc); + x11->vo_gc = None; + } + if (x11->window != None) + { + XClearWindow(x11->display, x11->window); + if (WinID < 0) + { + XEvent xev; + + if (x11->xic) + XDestroyIC(x11->xic); + x11->xic = NULL; + + XUnmapWindow(x11->display, x11->window); + XSelectInput(x11->display, x11->window, StructureNotifyMask); + XDestroyWindow(x11->display, x11->window); + do + { + XNextEvent(x11->display, &xev); + } + while (xev.type != DestroyNotify + || xev.xdestroywindow.event != x11->window); + } + x11->window = None; + } + vo_fs = 0; + x11->vo_old_width = x11->vo_old_height = 0; + x11->last_video_width = 0; + x11->last_video_height = 0; + x11->size_changed_during_fs = false; + } + vo_uninit(x11); + vo->x11 = NULL; +} + +static int check_resize(struct vo *vo) +{ + int old_w = vo->dwidth, old_h = vo->dheight; + int old_x = vo->dx, old_y = vo->dy; + int rc = 0; + vo_x11_update_geometry(vo, true); + if (vo->dwidth != old_w || vo->dheight != old_h) + rc |= VO_EVENT_RESIZE; + if (vo->dx != old_x || vo->dy != old_y) + rc |= VO_EVENT_MOVE; + return rc; +} + +int vo_x11_check_events(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + struct MPOpts *opts = vo->opts; + Display *display = vo->x11->display; + int ret = 0; + XEvent Event; + + if (x11->mouse_waiting_hide && opts->cursor_autohide_delay != -1 && + (GetTimerMS() - x11->mouse_timer >= opts->cursor_autohide_delay)) { + vo_hidecursor(display, x11->window); + x11->mouse_waiting_hide = 0; + } + + if (WinID > 0) + ret |= check_resize(vo); + while (XPending(display)) + { + XNextEvent(display, &Event); +// printf("\rEvent.type=%X \n",Event.type); + switch (Event.type) + { + case Expose: + ret |= VO_EVENT_EXPOSE; + break; + case ConfigureNotify: + if (x11->window == None) + break; + ret |= check_resize(vo); + break; + case KeyPress: + { + char buf[100]; + KeySym keySym = 0; + int modifiers = 0; + if (Event.xkey.state & ShiftMask) + modifiers |= KEY_MODIFIER_SHIFT; + if (Event.xkey.state & ControlMask) + modifiers |= KEY_MODIFIER_CTRL; + if (Event.xkey.state & Mod1Mask) + modifiers |= KEY_MODIFIER_ALT; + if (Event.xkey.state & Mod4Mask) + modifiers |= KEY_MODIFIER_META; + if (x11->xic) { + Status status; + int len = Xutf8LookupString(x11->xic, &Event.xkey, buf, + sizeof(buf), &keySym, + &status); + int mpkey = vo_x11_lookupkey(keySym); + if (mpkey) { + mplayer_put_key(vo->key_fifo, mpkey | modifiers); + } else if (status == XLookupChars + || status == XLookupBoth) + { + struct bstr t = { buf, len }; + mplayer_put_key_utf8(vo->key_fifo, modifiers, t); + } + } else { + XLookupString(&Event.xkey, buf, sizeof(buf), &keySym, + &x11->compose_status); + int mpkey = vo_x11_lookupkey(keySym); + if (mpkey) + mplayer_put_key(vo->key_fifo, mpkey | modifiers); + } + ret |= VO_EVENT_KEYPRESS; + } + break; + case MotionNotify: + vo_mouse_movement(vo, Event.xmotion.x, Event.xmotion.y); + + if (opts->cursor_autohide_delay > -2) { + vo_showcursor(display, x11->window); + x11->mouse_waiting_hide = 1; + x11->mouse_timer = GetTimerMS(); + } + break; + case ButtonPress: + if (opts->cursor_autohide_delay > -2) { + vo_showcursor(display, x11->window); + x11->mouse_waiting_hide = 1; + x11->mouse_timer = GetTimerMS(); + } + mplayer_put_key(vo->key_fifo, + (MOUSE_BTN0 + Event.xbutton.button - 1) + | MP_KEY_DOWN); + break; + case ButtonRelease: + if (opts->cursor_autohide_delay > -2) { + vo_showcursor(display, x11->window); + x11->mouse_waiting_hide = 1; + x11->mouse_timer = GetTimerMS(); + } + mplayer_put_key(vo->key_fifo, + MOUSE_BTN0 + Event.xbutton.button - 1); + break; + case PropertyNotify: + { + char *name = + XGetAtomName(display, Event.xproperty.atom); + + if (!name) + break; + +// fprintf(stderr,"[ws] PropertyNotify ( 0x%x ) %s ( 0x%x )\n",vo_window,name,Event.xproperty.atom ); + + XFree(name); + } + break; + case MapNotify: + x11->vo_hint.win_gravity = x11->old_gravity; + XSetWMNormalHints(display, x11->window, &x11->vo_hint); + x11->fs_flip = 0; + break; + case DestroyNotify: + mp_msg(MSGT_VO, MSGL_WARN, "Our window was destroyed, exiting\n"); + mplayer_put_key(vo->key_fifo, KEY_CLOSE_WIN); + break; + case ClientMessage: + if (Event.xclient.message_type == x11->XAWM_PROTOCOLS && + Event.xclient.data.l[0] == x11->XAWM_DELETE_WINDOW) + mplayer_put_key(vo->key_fifo, KEY_CLOSE_WIN); + break; + } + } + return ret; +} + +/** + * \brief sets the size and position of the non-fullscreen window. + */ +static void vo_x11_nofs_sizepos(struct vo *vo, int x, int y, + int width, int height) +{ + struct vo_x11_state *x11 = vo->x11; + if (width == x11->last_video_width && height == x11->last_video_height) { + if (!vo->opts->force_window_position && !x11->size_changed_during_fs) + return; + } else if (vo_fs) + x11->size_changed_during_fs = true; + x11->last_video_height = height; + x11->last_video_width = width; + vo_x11_sizehint(vo, x, y, width, height, 0); + if (vo_fs) { + x11->vo_old_x = x; + x11->vo_old_y = y; + x11->vo_old_width = width; + x11->vo_old_height = height; + } + else + { + vo->dwidth = width; + vo->dheight = height; + if (vo->opts->force_window_position) + XMoveResizeWindow(vo->x11->display, vo->x11->window, x, y, width, + height); + else + XResizeWindow(vo->x11->display, vo->x11->window, width, height); + } +} + +void vo_x11_sizehint(struct vo *vo, int x, int y, int width, int height, int max) +{ + struct vo_x11_state *x11 = vo->x11; + x11->vo_hint.flags = 0; + if (vo_keepaspect) + { + x11->vo_hint.flags |= PAspect; + x11->vo_hint.min_aspect.x = width; + x11->vo_hint.min_aspect.y = height; + x11->vo_hint.max_aspect.x = width; + x11->vo_hint.max_aspect.y = height; + } + + x11->vo_hint.flags |= PPosition | PSize; + x11->vo_hint.x = x; + x11->vo_hint.y = y; + x11->vo_hint.width = width; + x11->vo_hint.height = height; + if (max) + { + x11->vo_hint.flags |= PMaxSize; + x11->vo_hint.max_width = width; + x11->vo_hint.max_height = height; + } else + { + x11->vo_hint.max_width = 0; + x11->vo_hint.max_height = 0; + } + + // Set minimum height/width to 4 to avoid off-by-one errors. + x11->vo_hint.flags |= PMinSize; + x11->vo_hint.min_width = x11->vo_hint.min_height = 4; + + // Set the base size. A window manager might display the window + // size to the user relative to this. + // Setting these to width/height might be nice, but e.g. fluxbox can't handle it. + x11->vo_hint.flags |= PBaseSize; + x11->vo_hint.base_width = 0 /*width*/; + x11->vo_hint.base_height = 0 /*height*/; + + x11->vo_hint.flags |= PWinGravity; + x11->vo_hint.win_gravity = StaticGravity; + XSetWMNormalHints(x11->display, x11->window, &x11->vo_hint); +} + +static int vo_x11_get_gnome_layer(struct vo_x11_state *x11, Window win) +{ + Atom type; + int format; + unsigned long nitems; + unsigned long bytesafter; + unsigned short *args = NULL; + + if (XGetWindowProperty(x11->display, win, x11->XA_WIN_LAYER, 0, 16384, + False, AnyPropertyType, &type, &format, &nitems, + &bytesafter, + (unsigned char **) &args) == Success + && nitems > 0 && args) + { + mp_msg(MSGT_VO, MSGL_V, "[x11] original window layer is %d.\n", + *args); + return *args; + } + return WIN_LAYER_NORMAL; +} + +// set a X text property that expects a UTF8_STRING type +static void vo_x11_set_property_utf8(struct vo *vo, Atom name, const char *t) +{ + struct vo_x11_state *x11 = vo->x11; + + XChangeProperty(x11->display, x11->window, name, x11->XAUTF8_STRING, 8, + PropModeReplace, t, strlen(t)); +} + +// set a X text property that expects a STRING or COMPOUND_TEXT type +static void vo_x11_set_property_string(struct vo *vo, Atom name, const char *t) +{ + struct vo_x11_state *x11 = vo->x11; + XTextProperty prop = {0}; + + if (Xutf8TextListToTextProperty(x11->display, (char **)&t, 1, + XStdICCTextStyle, &prop) == Success) + { + XSetTextProperty(x11->display, x11->window, &prop, name); + } else { + // Strictly speaking this violates the ICCCM, but there's no way we + // can do this correctly. + vo_x11_set_property_utf8(vo, name, t); + } + + if (prop.value) + XFree(prop.value); +} + +static void vo_x11_update_window_title(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + + const char *title = vo_get_window_title(vo); + vo_x11_set_property_string(vo, XA_WM_NAME, title); + vo_x11_set_property_string(vo, XA_WM_ICON_NAME, title); + vo_x11_set_property_utf8(vo, x11->XA_NET_WM_NAME, title); + vo_x11_set_property_utf8(vo, x11->XA_NET_WM_ICON_NAME, title); +} + +// +static Window vo_x11_create_smooth_window(struct vo_x11_state *x11, Window mRoot, + Visual * vis, int x, int y, + unsigned int width, unsigned int height, + int depth, Colormap col_map) +{ + unsigned long xswamask = CWBorderPixel; + XSetWindowAttributes xswa; + Window ret_win; + + if (col_map != CopyFromParent) + { + xswa.colormap = col_map; + xswamask |= CWColormap; + } + xswa.background_pixel = 0; + xswa.border_pixel = 0; + xswa.backing_store = NotUseful; + xswa.bit_gravity = StaticGravity; + + ret_win = + XCreateWindow(x11->display, x11->rootwin, x, y, width, height, 0, depth, + CopyFromParent, vis, xswamask, &xswa); + XSetWMProtocols(x11->display, ret_win, &x11->XAWM_DELETE_WINDOW, 1); + if (x11->f_gc == None) + x11->f_gc = XCreateGC(x11->display, ret_win, 0, 0); + XSetForeground(x11->display, x11->f_gc, 0); + + return ret_win; +} + +/** + * \brief create and setup a window suitable for display + * \param vis Visual to use for creating the window + * \param x x position of window + * \param y y position of window + * \param width width of window + * \param height height of window + * \param flags flags for window creation. + * Only VOFLAG_FULLSCREEN is supported so far. + * \param col_map Colourmap for window or CopyFromParent if a specific colormap isn't needed + * \param classname name to use for the classhint + * + * This also does the grunt-work like setting Window Manager hints etc. + * If vo_window is already set it just moves and resizes it. + */ +void vo_x11_create_vo_window(struct vo *vo, XVisualInfo *vis, int x, int y, + unsigned int width, unsigned int height, int flags, + Colormap col_map, const char *classname) +{ + struct MPOpts *opts = vo->opts; + struct vo_x11_state *x11 = vo->x11; + Display *mDisplay = vo->x11->display; + if (WinID >= 0) { + vo_fs = flags & VOFLAG_FULLSCREEN; + x11->window = WinID ? (Window)WinID : x11->rootwin; + if (col_map != CopyFromParent) { + unsigned long xswamask = CWColormap; + XSetWindowAttributes xswa; + xswa.colormap = col_map; + XChangeWindowAttributes(mDisplay, x11->window, xswamask, &xswa); + XInstallColormap(mDisplay, col_map); + } + if (WinID) { + // Expose events can only really be handled by us, so request them. + vo_x11_selectinput_witherr(mDisplay, x11->window, ExposureMask); + } else + // Do not capture events since it might break the parent application + // if it relies on events being forwarded to the parent of WinID. + // It also is consistent with the w32_common.c code. + vo_x11_selectinput_witherr(mDisplay, x11->window, + StructureNotifyMask | KeyPressMask | PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | ExposureMask); + + vo_x11_update_geometry(vo, true); + goto final; + } + if (x11->window == None) { + vo_fs = 0; + vo->dwidth = width; + vo->dheight = height; + x11->window = vo_x11_create_smooth_window(x11, x11->rootwin, vis->visual, + x, y, width, height, vis->depth, col_map); + x11->window_state = VOFLAG_HIDDEN; + } + if (flags & VOFLAG_HIDDEN) + goto final; + if (x11->window_state & VOFLAG_HIDDEN) { + XSizeHints hint; + x11->window_state &= ~VOFLAG_HIDDEN; + vo_x11_classhint(vo, x11->window, classname); + vo_hidecursor(mDisplay, x11->window); + XSelectInput(mDisplay, x11->window, StructureNotifyMask); + hint.x = x; hint.y = y; + hint.width = width; hint.height = height; + hint.flags = PSize; + if (geometry_xy_changed) + hint.flags |= PPosition; + XSetWMNormalHints(mDisplay, x11->window, &hint); + if (!vo_border) vo_x11_decoration(vo, 0); + // map window + x11->xic = XCreateIC(x11->xim, + XNInputStyle, XIMPreeditNone | XIMStatusNone, + XNClientWindow, x11->window, + XNFocusWindow, x11->window, + NULL); + XSelectInput(mDisplay, x11->window, NoEventMask); + vo_x11_selectinput_witherr(mDisplay, x11->window, + StructureNotifyMask | KeyPressMask | PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | ExposureMask); + XMapWindow(mDisplay, x11->window); + vo_x11_clearwindow(vo, x11->window); + } + vo_x11_update_window_title(vo); + if (opts->vo_ontop) vo_x11_setlayer(vo, x11->window, opts->vo_ontop); + vo_x11_update_geometry(vo, !geometry_xy_changed); + vo_x11_nofs_sizepos(vo, vo->dx, vo->dy, width, height); + if (!!vo_fs != !!(flags & VOFLAG_FULLSCREEN)) + vo_x11_fullscreen(vo); + else if (vo_fs) { + // if we are already in fullscreen do not switch back and forth, just + // set the size values right. + vo->dwidth = vo->opts->vo_screenwidth; + vo->dheight = vo->opts->vo_screenheight; + } +final: + if (x11->vo_gc != None) + XFreeGC(mDisplay, x11->vo_gc); + x11->vo_gc = XCreateGC(mDisplay, x11->window, 0, NULL); + + XSync(mDisplay, False); + vo->event_fd = ConnectionNumber(x11->display); +} + +void vo_x11_clearwindow_part(struct vo *vo, Window vo_window, + int img_width, int img_height) +{ + struct vo_x11_state *x11 = vo->x11; + Display *mDisplay = vo->x11->display; + int u_dheight, u_dwidth, left_ov, left_ov2; + + if (x11->f_gc == None) + return; + + u_dheight = vo->dheight; + u_dwidth = vo->dwidth; + if ((u_dheight <= img_height) && (u_dwidth <= img_width)) + return; + + left_ov = (u_dheight - img_height) / 2; + left_ov2 = (u_dwidth - img_width) / 2; + + XFillRectangle(mDisplay, vo_window, x11->f_gc, 0, 0, u_dwidth, left_ov); + XFillRectangle(mDisplay, vo_window, x11->f_gc, 0, u_dheight - left_ov - 1, + u_dwidth, left_ov + 1); + + if (u_dwidth > img_width) + { + XFillRectangle(mDisplay, vo_window, x11->f_gc, 0, left_ov, left_ov2, + img_height); + XFillRectangle(mDisplay, vo_window, x11->f_gc, u_dwidth - left_ov2 - 1, + left_ov, left_ov2 + 1, img_height); + } + + XFlush(mDisplay); +} + +void vo_x11_clearwindow(struct vo *vo, Window vo_window) +{ + struct vo_x11_state *x11 = vo->x11; + struct MPOpts *opts = vo->opts; + if (x11->f_gc == None) + return; + XFillRectangle(x11->display, vo_window, x11->f_gc, 0, 0, + opts->vo_screenwidth, opts->vo_screenheight); + // + XFlush(x11->display); +} + + +void vo_x11_setlayer(struct vo *vo, Window vo_window, int layer) +{ + struct vo_x11_state *x11 = vo->x11; + if (WinID >= 0) + return; + + if (x11->fs_type & vo_wm_LAYER) + { + XClientMessageEvent xev; + + if (!x11->orig_layer) + x11->orig_layer = vo_x11_get_gnome_layer(x11, vo_window); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.display = x11->display; + xev.window = vo_window; + xev.message_type = x11->XA_WIN_LAYER; + xev.format = 32; + xev.data.l[0] = layer ? fs_layer : x11->orig_layer; // if not fullscreen, stay on default layer + xev.data.l[1] = CurrentTime; + mp_msg(MSGT_VO, MSGL_V, + "[x11] Layered style stay on top (layer %ld).\n", + xev.data.l[0]); + XSendEvent(x11->display, x11->rootwin, False, SubstructureNotifyMask, + (XEvent *) & xev); + } else if (x11->fs_type & vo_wm_NETWM) + { + XClientMessageEvent xev; + char *state; + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.message_type = x11->XA_NET_WM_STATE; + xev.display = x11->display; + xev.window = vo_window; + xev.format = 32; + xev.data.l[0] = layer; + + if (x11->fs_type & vo_wm_STAYS_ON_TOP) + xev.data.l[1] = x11->XA_NET_WM_STATE_STAYS_ON_TOP; + else if (x11->fs_type & vo_wm_ABOVE) + xev.data.l[1] = x11->XA_NET_WM_STATE_ABOVE; + else if (x11->fs_type & vo_wm_FULLSCREEN) + xev.data.l[1] = x11->XA_NET_WM_STATE_FULLSCREEN; + else if (x11->fs_type & vo_wm_BELOW) + // This is not fallback. We can safely assume that the situation + // where only NETWM_STATE_BELOW is supported doesn't exist. + xev.data.l[1] = x11->XA_NET_WM_STATE_BELOW; + + XSendEvent(x11->display, x11->rootwin, False, SubstructureRedirectMask, + (XEvent *) & xev); + state = XGetAtomName(x11->display, xev.data.l[1]); + mp_msg(MSGT_VO, MSGL_V, + "[x11] NET style stay on top (layer %d). Using state %s.\n", + layer, state); + XFree(state); + } +} + +static int vo_x11_get_fs_type(int supported) +{ + int i; + int type = supported; + + if (vo_fstype_list) + { + for (i = 0; vo_fstype_list[i]; i++) + { + int neg = 0; + char *arg = vo_fstype_list[i]; + + if (vo_fstype_list[i][0] == '-') + { + neg = 1; + arg = vo_fstype_list[i] + 1; + } + + if (!strncmp(arg, "layer", 5)) + { + if (!neg && (arg[5] == '=')) + { + char *endptr = NULL; + int layer = strtol(vo_fstype_list[i] + 6, &endptr, 10); + + if (endptr && *endptr == '\0' && layer >= 0 + && layer <= 15) + fs_layer = layer; + } + if (neg) + type &= ~vo_wm_LAYER; + else + type |= vo_wm_LAYER; + } else if (!strcmp(arg, "above")) + { + if (neg) + type &= ~vo_wm_ABOVE; + else + type |= vo_wm_ABOVE; + } else if (!strcmp(arg, "fullscreen")) + { + if (neg) + type &= ~vo_wm_FULLSCREEN; + else + type |= vo_wm_FULLSCREEN; + } else if (!strcmp(arg, "stays_on_top")) + { + if (neg) + type &= ~vo_wm_STAYS_ON_TOP; + else + type |= vo_wm_STAYS_ON_TOP; + } else if (!strcmp(arg, "below")) + { + if (neg) + type &= ~vo_wm_BELOW; + else + type |= vo_wm_BELOW; + } else if (!strcmp(arg, "netwm")) + { + if (neg) + type &= ~vo_wm_NETWM; + else + type |= vo_wm_NETWM; + } else if (!strcmp(arg, "none")) + type = 0; // clear; keep parsing + } + } + + return type; +} + +/** + * \brief update vo->dx, vo->dy, vo->dwidth and vo->dheight with current values of vo->x11->window + * \return returns current color depth of vo->x11->window + */ +int vo_x11_update_geometry(struct vo *vo, bool update_pos) +{ + struct vo_x11_state *x11 = vo->x11; + unsigned depth, w, h; + int dummy_int; + Window dummy_win; + XGetGeometry(x11->display, x11->window, &dummy_win, &dummy_int, &dummy_int, + &w, &h, &dummy_int, &depth); + if (w <= INT_MAX && h <= INT_MAX) { + vo->dwidth = w; + vo->dheight = h; + } + if (update_pos) + XTranslateCoordinates(x11->display, x11->window, x11->rootwin, 0, 0, + &vo->dx, &vo->dy, &dummy_win); + + return depth <= INT_MAX ? depth : 0; +} + +void vo_x11_fullscreen(struct vo *vo) +{ + struct MPOpts *opts = vo->opts; + struct vo_x11_state *x11 = vo->x11; + int x, y, w, h; + x = x11->vo_old_x; + y = x11->vo_old_y; + w = x11->vo_old_width; + h = x11->vo_old_height; + + if (WinID >= 0) { + vo_fs = !vo_fs; + return; + } + if (x11->fs_flip) + return; + + if (vo_fs) + { + vo_x11_ewmh_fullscreen(x11, _NET_WM_STATE_REMOVE); // removes fullscreen state if wm supports EWMH + vo_fs = VO_FALSE; + if (x11->size_changed_during_fs && (x11->fs_type & vo_wm_FULLSCREEN)) + vo_x11_nofs_sizepos(vo, vo->dx, vo->dy, x11->last_video_width, + x11->last_video_height); + x11->size_changed_during_fs = false; + } else + { + // win->fs + vo_x11_ewmh_fullscreen(x11, _NET_WM_STATE_ADD); // sends fullscreen state to be added if wm supports EWMH + + vo_fs = VO_TRUE; + if ( ! (x11->fs_type & vo_wm_FULLSCREEN) ) // not needed with EWMH fs + { + x11->vo_old_x = vo->dx; + x11->vo_old_y = vo->dy; + x11->vo_old_width = vo->dwidth; + x11->vo_old_height = vo->dheight; + } + update_xinerama_info(vo); + x = xinerama_x; + y = xinerama_y; + w = opts->vo_screenwidth; + h = opts->vo_screenheight; + } + { + long dummy; + + XGetWMNormalHints(x11->display, x11->window, &x11->vo_hint, &dummy); + if (!(x11->vo_hint.flags & PWinGravity)) + x11->old_gravity = NorthWestGravity; + else + x11->old_gravity = x11->vo_hint.win_gravity; + } + if (x11->wm_type == 0 && !(vo_fsmode & 16)) + { + XUnmapWindow(x11->display, x11->window); // required for MWM + XWithdrawWindow(x11->display, x11->window, x11->screen); + x11->fs_flip = 1; + } + + if ( ! (x11->fs_type & vo_wm_FULLSCREEN) ) // not needed with EWMH fs + { + vo_x11_decoration(vo, vo_border && !vo_fs); + vo_x11_sizehint(vo, x, y, w, h, 0); + vo_x11_setlayer(vo, x11->window, vo_fs); + + + XMoveResizeWindow(x11->display, x11->window, x, y, w, h); + } + /* some WMs lose ontop after fullscreen */ + if ((!(vo_fs)) & opts->vo_ontop) + vo_x11_setlayer(vo, x11->window, opts->vo_ontop); + + XMapRaised(x11->display, x11->window); + if ( ! (x11->fs_type & vo_wm_FULLSCREEN) ) // some WMs change window pos on map + XMoveResizeWindow(x11->display, x11->window, x, y, w, h); + XRaiseWindow(x11->display, x11->window); + XFlush(x11->display); +} + +void vo_x11_ontop(struct vo *vo) +{ + struct MPOpts *opts = vo->opts; + opts->vo_ontop = !opts->vo_ontop; + + vo_x11_setlayer(vo, vo->x11->window, opts->vo_ontop); +} + +void vo_x11_border(struct vo *vo) +{ + vo_border = !vo_border; + vo_x11_decoration(vo, vo_border && !vo_fs); +} + +/* + * XScreensaver stuff + */ + +static int screensaver_off; +static unsigned int time_last; + +void xscreensaver_heartbeat(struct vo_x11_state *x11) +{ + unsigned int time = GetTimerMS(); + + if (x11->display && screensaver_off && (time - time_last) > 30000) + { + time_last = time; + + XResetScreenSaver(x11->display); + } +} + +static int xss_suspend(Display *mDisplay, Bool suspend) +{ +#ifndef CONFIG_XSS + return 0; +#else + int event, error, major, minor; + if (XScreenSaverQueryExtension(mDisplay, &event, &error) != True || + XScreenSaverQueryVersion(mDisplay, &major, &minor) != True) + return 0; + if (major < 1 || (major == 1 && minor < 1)) + return 0; + XScreenSaverSuspend(mDisplay, suspend); + return 1; +#endif +} + +/* + * End of XScreensaver stuff + */ + +static void saver_on(Display * mDisplay) +{ + + if (!screensaver_off) + return; + screensaver_off = 0; + if (xss_suspend(mDisplay, False)) + return; +#ifdef CONFIG_XDPMS + if (dpms_disabled) + { + int nothing; + if (DPMSQueryExtension(mDisplay, ¬hing, ¬hing)) + { + if (!DPMSEnable(mDisplay)) + { // restoring power saving settings + mp_msg(MSGT_VO, MSGL_WARN, "DPMS not available?\n"); + } else + { + // DPMS does not seem to be enabled unless we call DPMSInfo + BOOL onoff; + CARD16 state; + + DPMSForceLevel(mDisplay, DPMSModeOn); + DPMSInfo(mDisplay, &state, &onoff); + if (onoff) + { + mp_msg(MSGT_VO, MSGL_V, + "Successfully enabled DPMS\n"); + } else + { + mp_msg(MSGT_VO, MSGL_WARN, "Could not enable DPMS\n"); + } + } + } + dpms_disabled = 0; + } +#endif +} + +static void saver_off(Display * mDisplay) +{ + int nothing; + + if (!stop_xscreensaver || screensaver_off) + return; + screensaver_off = 1; + if (xss_suspend(mDisplay, True)) + return; +#ifdef CONFIG_XDPMS + if (DPMSQueryExtension(mDisplay, ¬hing, ¬hing)) + { + BOOL onoff; + CARD16 state; + + DPMSInfo(mDisplay, &state, &onoff); + if (onoff) + { + Status stat; + + mp_msg(MSGT_VO, MSGL_V, "Disabling DPMS\n"); + dpms_disabled = 1; + stat = DPMSDisable(mDisplay); // monitor powersave off + mp_msg(MSGT_VO, MSGL_V, "DPMSDisable stat: %d\n", stat); + } + } +#endif +} + +static XErrorHandler old_handler = NULL; +static int selectinput_err = 0; +static int x11_selectinput_errorhandler(Display * display, + XErrorEvent * event) +{ + if (event->error_code == BadAccess) + { + selectinput_err = 1; + mp_msg(MSGT_VO, MSGL_ERR, + "X11 error: BadAccess during XSelectInput Call\n"); + mp_msg(MSGT_VO, MSGL_ERR, + "X11 error: The 'ButtonPressMask' mask of specified window has probably already used by another appication (see man XSelectInput)\n"); + /* If you think MPlayer should shutdown with this error, + * comment out the following line */ + return 0; + } + if (old_handler != NULL) + old_handler(display, event); + else + x11_errorhandler(display, event); + return 0; +} + +void vo_x11_selectinput_witherr(Display * display, Window w, + long event_mask) +{ + XSync(display, False); + old_handler = XSetErrorHandler(x11_selectinput_errorhandler); + selectinput_err = 0; + if (vo_nomouse_input) + { + XSelectInput(display, w, + event_mask & + (~(ButtonPressMask | ButtonReleaseMask))); + } else + { + XSelectInput(display, w, event_mask); + } + XSync(display, False); + XSetErrorHandler(old_handler); + if (selectinput_err) + { + mp_msg(MSGT_VO, MSGL_ERR, + "X11 error: mpv discards mouse control (reconfiguring)\n"); + XSelectInput(display, w, + event_mask & + (~ + (ButtonPressMask | ButtonReleaseMask | + PointerMotionMask))); + } +} + +#ifdef CONFIG_XF86VM +void vo_vm_switch(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + struct MPOpts *opts = vo->opts; + Display *mDisplay = x11->display; + int vm_event, vm_error; + int vm_ver, vm_rev; + int i, j, have_vm = 0; + int X = vo->dwidth, Y = vo->dheight; + int modeline_width, modeline_height; + + if (XF86VidModeQueryExtension(mDisplay, &vm_event, &vm_error)) + { + XF86VidModeQueryVersion(mDisplay, &vm_ver, &vm_rev); + mp_msg(MSGT_VO, MSGL_V, "XF86VidMode extension v%i.%i\n", vm_ver, + vm_rev); + have_vm = 1; + } else { + mp_msg(MSGT_VO, MSGL_WARN, + "XF86VidMode extension not available.\n"); + } + + if (have_vm) + { + if (vidmodes == NULL) + XF86VidModeGetAllModeLines(mDisplay, x11->screen, &modecount, + &vidmodes); + j = 0; + modeline_width = vidmodes[0]->hdisplay; + modeline_height = vidmodes[0]->vdisplay; + + for (i = 1; i < modecount; i++) + if ((vidmodes[i]->hdisplay >= X) + && (vidmodes[i]->vdisplay >= Y)) + if ((vidmodes[i]->hdisplay <= modeline_width) + && (vidmodes[i]->vdisplay <= modeline_height)) + { + modeline_width = vidmodes[i]->hdisplay; + modeline_height = vidmodes[i]->vdisplay; + j = i; + } + + mp_tmsg(MSGT_VO, MSGL_INFO, "XF86VM: Selected video mode %dx%d for image size %dx%d.\n", + modeline_width, modeline_height, X, Y); + XF86VidModeLockModeSwitch(mDisplay, x11->screen, 0); + XF86VidModeSwitchToMode(mDisplay, x11->screen, vidmodes[j]); + XF86VidModeSwitchToMode(mDisplay, x11->screen, vidmodes[j]); + + // FIXME: all this is more of a hack than proper solution + X = (opts->vo_screenwidth - modeline_width) / 2; + Y = (opts->vo_screenheight - modeline_height) / 2; + XF86VidModeSetViewPort(mDisplay, x11->screen, X, Y); + vo->dx = X; + vo->dy = Y; + vo->dwidth = modeline_width; + vo->dheight = modeline_height; + aspect_save_screenres(vo, modeline_width, modeline_height); + } +} + +void vo_vm_close(struct vo *vo) +{ + Display *dpy = vo->x11->display; + struct MPOpts *opts = vo->opts; + if (vidmodes != NULL) + { + int i; + + free(vidmodes); + vidmodes = NULL; + XF86VidModeGetAllModeLines(dpy, vo->x11->screen, &modecount, + &vidmodes); + for (i = 0; i < modecount; i++) + if ((vidmodes[i]->hdisplay == opts->vo_screenwidth) + && (vidmodes[i]->vdisplay == opts->vo_screenheight)) + { + mp_msg(MSGT_VO, MSGL_INFO, + "Returning to original mode %dx%d\n", + opts->vo_screenwidth, opts->vo_screenheight); + break; + } + + XF86VidModeSwitchToMode(dpy, vo->x11->screen, vidmodes[i]); + XF86VidModeSwitchToMode(dpy, vo->x11->screen, vidmodes[i]); + free(vidmodes); + vidmodes = NULL; + modecount = 0; + } +} + +double vo_vm_get_fps(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + int clock; + XF86VidModeModeLine modeline; + if (!XF86VidModeGetModeLine(x11->display, x11->screen, &clock, &modeline)) + return 0; + if (modeline.privsize) + XFree(modeline.private); + return 1e3 * clock / modeline.htotal / modeline.vtotal; +} +#endif + + +/* + * Scan the available visuals on this Display/Screen. Try to find + * the 'best' available TrueColor visual that has a decent color + * depth (at least 15bit). If there are multiple visuals with depth + * >= 15bit, we prefer visuals with a smaller color depth. + */ +int vo_find_depth_from_visuals(Display * dpy, int screen, + Visual ** visual_return) +{ + XVisualInfo visual_tmpl; + XVisualInfo *visuals; + int nvisuals, i; + int bestvisual = -1; + int bestvisual_depth = -1; + + visual_tmpl.screen = screen; + visual_tmpl.class = TrueColor; + visuals = XGetVisualInfo(dpy, + VisualScreenMask | VisualClassMask, + &visual_tmpl, &nvisuals); + if (visuals != NULL) + { + for (i = 0; i < nvisuals; i++) + { + mp_msg(MSGT_VO, MSGL_V, + "vo: X11 truecolor visual %#lx, depth %d, R:%lX G:%lX B:%lX\n", + visuals[i].visualid, visuals[i].depth, + visuals[i].red_mask, visuals[i].green_mask, + visuals[i].blue_mask); + /* + * Save the visual index and its depth, if this is the first + * truecolor visul, or a visual that is 'preferred' over the + * previous 'best' visual. + */ + if (bestvisual_depth == -1 + || (visuals[i].depth >= 15 + && (visuals[i].depth < bestvisual_depth + || bestvisual_depth < 15))) + { + bestvisual = i; + bestvisual_depth = visuals[i].depth; + } + } + + if (bestvisual != -1 && visual_return != NULL) + *visual_return = visuals[bestvisual].visual; + + XFree(visuals); + } + return bestvisual_depth; +} + + +static Colormap cmap = None; +static XColor cols[256]; +static int cm_size, red_mask, green_mask, blue_mask; + + +Colormap vo_x11_create_colormap(struct vo *vo, XVisualInfo *vinfo) +{ + struct vo_x11_state *x11 = vo->x11; + unsigned k, r, g, b, ru, gu, bu, m, rv, gv, bv, rvu, gvu, bvu; + + if (vinfo->class != DirectColor) + return XCreateColormap(x11->display, x11->rootwin, vinfo->visual, + AllocNone); + + /* can this function get called twice or more? */ + if (cmap) + return cmap; + cm_size = vinfo->colormap_size; + red_mask = vinfo->red_mask; + green_mask = vinfo->green_mask; + blue_mask = vinfo->blue_mask; + ru = (red_mask & (red_mask - 1)) ^ red_mask; + gu = (green_mask & (green_mask - 1)) ^ green_mask; + bu = (blue_mask & (blue_mask - 1)) ^ blue_mask; + rvu = 65536ull * ru / (red_mask + ru); + gvu = 65536ull * gu / (green_mask + gu); + bvu = 65536ull * bu / (blue_mask + bu); + r = g = b = 0; + rv = gv = bv = 0; + m = DoRed | DoGreen | DoBlue; + for (k = 0; k < cm_size; k++) + { + int t; + + cols[k].pixel = r | g | b; + cols[k].red = rv; + cols[k].green = gv; + cols[k].blue = bv; + cols[k].flags = m; + t = (r + ru) & red_mask; + if (t < r) + m &= ~DoRed; + r = t; + t = (g + gu) & green_mask; + if (t < g) + m &= ~DoGreen; + g = t; + t = (b + bu) & blue_mask; + if (t < b) + m &= ~DoBlue; + b = t; + rv += rvu; + gv += gvu; + bv += bvu; + } + cmap = XCreateColormap(x11->display, x11->rootwin, vinfo->visual, AllocAll); + XStoreColors(x11->display, cmap, cols, cm_size); + return cmap; +} + +/* + * Via colormaps/gamma ramps we can do gamma, brightness, contrast, + * hue and red/green/blue intensity, but we cannot do saturation. + * Currently only gamma, brightness and contrast are implemented. + * Is there sufficient interest for hue and/or red/green/blue intensity? + */ +/* these values have range [-100,100] and are initially 0 */ +static int vo_gamma = 0; +static int vo_brightness = 0; +static int vo_contrast = 0; + +static int transform_color(float val, + float brightness, float contrast, float gamma) { + float s = pow(val, gamma); + s = (s - 0.5) * contrast + 0.5; + s += brightness; + if (s < 0) + s = 0; + if (s > 1) + s = 1; + return (unsigned short) (s * 65535); +} + +uint32_t vo_x11_set_equalizer(struct vo *vo, const char *name, int value) +{ + float gamma, brightness, contrast; + float rf, gf, bf; + int k; + + /* + * IMPLEMENTME: consider using XF86VidModeSetGammaRamp in the case + * of TrueColor-ed window but be careful: + * Unlike the colormaps, which are private for the X client + * who created them and thus automatically destroyed on client + * disconnect, this gamma ramp is a system-wide (X-server-wide) + * setting and _must_ be restored before the process exits. + * Unforunately when the process crashes (or gets killed + * for some reason) it is impossible to restore the setting, + * and such behaviour could be rather annoying for the users. + */ + if (cmap == None) + return VO_NOTAVAIL; + + if (!strcasecmp(name, "brightness")) + vo_brightness = value; + else if (!strcasecmp(name, "contrast")) + vo_contrast = value; + else if (!strcasecmp(name, "gamma")) + vo_gamma = value; + else + return VO_NOTIMPL; + + brightness = 0.01 * vo_brightness; + contrast = tan(0.0095 * (vo_contrast + 100) * M_PI / 4); + gamma = pow(2, -0.02 * vo_gamma); + + rf = (float) ((red_mask & (red_mask - 1)) ^ red_mask) / red_mask; + gf = (float) ((green_mask & (green_mask - 1)) ^ green_mask) / + green_mask; + bf = (float) ((blue_mask & (blue_mask - 1)) ^ blue_mask) / blue_mask; + + /* now recalculate the colormap using the newly set value */ + for (k = 0; k < cm_size; k++) + { + cols[k].red = transform_color(rf * k, brightness, contrast, gamma); + cols[k].green = transform_color(gf * k, brightness, contrast, gamma); + cols[k].blue = transform_color(bf * k, brightness, contrast, gamma); + } + + XStoreColors(vo->x11->display, cmap, cols, cm_size); + XFlush(vo->x11->display); + return VO_TRUE; +} + +uint32_t vo_x11_get_equalizer(const char *name, int *value) +{ + if (cmap == None) + return VO_NOTAVAIL; + if (!strcasecmp(name, "brightness")) + *value = vo_brightness; + else if (!strcasecmp(name, "contrast")) + *value = vo_contrast; + else if (!strcasecmp(name, "gamma")) + *value = vo_gamma; + else + return VO_NOTIMPL; + return VO_TRUE; +} + +bool vo_x11_screen_is_composited(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + return XGetSelectionOwner(x11->display, x11->XA_NET_WM_CM) != None; +} + +#ifdef CONFIG_XV + +static int xv_find_atom(struct vo *vo, uint32_t xv_port, const char *name, + bool get, int *min, int *max) +{ + Atom atom = None; + int howmany = 0; + XvAttribute *attributes = XvQueryPortAttributes(vo->x11->display, xv_port, + &howmany); + for (int i = 0; i < howmany && attributes; i++) { + int flag = get ? XvGettable : XvSettable; + if (attributes[i].flags & flag) { + atom = XInternAtom(vo->x11->display, attributes[i].name, True); + *min = attributes[i].min_value; + *max = attributes[i].max_value; +/* since we have SET_DEFAULTS first in our list, we can check if it's available + then trigger it if it's ok so that the other values are at default upon query */ + if (atom != None) { + if (!strcmp(attributes[i].name, "XV_BRIGHTNESS") && + (!strcasecmp(name, "brightness"))) + break; + else if (!strcmp(attributes[i].name, "XV_CONTRAST") && + (!strcasecmp(name, "contrast"))) + break; + else if (!strcmp(attributes[i].name, "XV_SATURATION") && + (!strcasecmp(name, "saturation"))) + break; + else if (!strcmp(attributes[i].name, "XV_HUE") && + (!strcasecmp(name, "hue"))) + break; + if (!strcmp(attributes[i].name, "XV_RED_INTENSITY") && + (!strcasecmp(name, "red_intensity"))) + break; + else if (!strcmp(attributes[i].name, "XV_GREEN_INTENSITY") + && (!strcasecmp(name, "green_intensity"))) + break; + else if (!strcmp(attributes[i].name, "XV_BLUE_INTENSITY") + && (!strcasecmp(name, "blue_intensity"))) + break; + else if ((!strcmp(attributes[i].name, "XV_ITURBT_709") //NVIDIA + || !strcmp(attributes[i].name, "XV_COLORSPACE"))//ATI + && (!strcasecmp(name, "bt_709"))) + break; + atom = None; + continue; + } + } + } + XFree(attributes); + return atom; +} + +int vo_xv_set_eq(struct vo *vo, uint32_t xv_port, const char *name, int value) +{ + mp_dbg(MSGT_VO, MSGL_V, "xv_set_eq called! (%s, %d)\n", name, value); + + int min, max; + int atom = xv_find_atom(vo, xv_port, name, false, &min, &max); + if (atom != None) { + // -100 -> min + // 0 -> (max+min)/2 + // +100 -> max + int port_value = (value + 100) * (max - min) / 200 + min; + XvSetPortAttribute(vo->x11->display, xv_port, atom, port_value); + return VO_TRUE; + } + return VO_FALSE; +} + +int vo_xv_get_eq(struct vo *vo, uint32_t xv_port, const char *name, int *value) +{ + int min, max; + int atom = xv_find_atom(vo, xv_port, name, true, &min, &max); + if (atom != None) { + int port_value = 0; + XvGetPortAttribute(vo->x11->display, xv_port, atom, &port_value); + + *value = (port_value - min) * 200 / (max - min) - 100; + mp_dbg(MSGT_VO, MSGL_V, "xv_get_eq called! (%s, %d)\n", + name, *value); + return VO_TRUE; + } + return VO_FALSE; +} + +/** + * \brief Interns the requested atom if it is available. + * + * \param atom_name String containing the name of the requested atom. + * + * \return Returns the atom if available, else None is returned. + * + */ +static Atom xv_intern_atom_if_exists(struct vo_x11_state *x11, + char const *atom_name) +{ + XvAttribute * attributes; + int attrib_count,i; + Atom xv_atom = None; + + attributes = XvQueryPortAttributes(x11->display, x11->xv_port, &attrib_count ); + if( attributes!=NULL ) + { + for ( i = 0; i < attrib_count; ++i ) + { + if ( strcmp(attributes[i].name, atom_name ) == 0 ) + { + xv_atom = XInternAtom(x11->display, atom_name, False ); + break; // found what we want, break out + } + } + XFree( attributes ); + } + + return xv_atom; +} + +/** + * \brief Try to enable vsync for xv. + * \return Returns -1 if not available, 0 on failure and 1 on success. + */ +int vo_xv_enable_vsync(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + Atom xv_atom = xv_intern_atom_if_exists(x11, "XV_SYNC_TO_VBLANK"); + if (xv_atom == None) + return -1; + return XvSetPortAttribute(x11->display, x11->xv_port, xv_atom, 1) == Success; +} + +/** + * \brief Get maximum supported source image dimensions. + * + * This function does not set the variables pointed to by + * width and height if the information could not be retrieved, + * so the caller is reponsible for properly initializing them. + * + * \param width [out] The maximum width gets stored here. + * \param height [out] The maximum height gets stored here. + * + */ +void vo_xv_get_max_img_dim(struct vo *vo, uint32_t * width, uint32_t * height) +{ + struct vo_x11_state *x11 = vo->x11; + XvEncodingInfo * encodings; + //unsigned long num_encodings, idx; to int or too long?! + unsigned int num_encodings, idx; + + XvQueryEncodings(x11->display, x11->xv_port, &num_encodings, &encodings); + + if ( encodings ) + { + for ( idx = 0; idx < num_encodings; ++idx ) + { + if ( strcmp( encodings[idx].name, "XV_IMAGE" ) == 0 ) + { + *width = encodings[idx].width; + *height = encodings[idx].height; + break; + } + } + } + + mp_msg( MSGT_VO, MSGL_V, + "[xv common] Maximum source image dimensions: %ux%u\n", + *width, *height ); + + XvFreeEncodingInfo( encodings ); +} + +/** + * \brief Print information about the colorkey method and source. + * + * \param ck_handling Integer value containing the information about + * colorkey handling (see x11_common.h). + * + * Outputs the content of |ck_handling| as a readable message. + * + */ +static void vo_xv_print_ck_info(struct vo_x11_state *x11) +{ + mp_msg( MSGT_VO, MSGL_V, "[xv common] " ); + + switch ( x11->xv_ck_info.method ) + { + case CK_METHOD_NONE: + mp_msg( MSGT_VO, MSGL_V, "Drawing no colorkey.\n" ); return; + case CK_METHOD_AUTOPAINT: + mp_msg( MSGT_VO, MSGL_V, "Colorkey is drawn by Xv." ); break; + case CK_METHOD_MANUALFILL: + mp_msg( MSGT_VO, MSGL_V, "Drawing colorkey manually." ); break; + case CK_METHOD_BACKGROUND: + mp_msg( MSGT_VO, MSGL_V, "Colorkey is drawn as window background." ); break; + } + + mp_msg( MSGT_VO, MSGL_V, "\n[xv common] " ); + + switch ( x11->xv_ck_info.source ) + { + case CK_SRC_CUR: + mp_msg( MSGT_VO, MSGL_V, "Using colorkey from Xv (0x%06lx).\n", + x11->xv_colorkey ); + break; + case CK_SRC_USE: + if ( x11->xv_ck_info.method == CK_METHOD_AUTOPAINT ) + { + mp_msg( MSGT_VO, MSGL_V, + "Ignoring colorkey from mpv (0x%06lx).\n", + x11->xv_colorkey ); + } + else + { + mp_msg( MSGT_VO, MSGL_V, + "Using colorkey from mpv (0x%06lx)." + " Use -colorkey to change.\n", + x11->xv_colorkey ); + } + break; + case CK_SRC_SET: + mp_msg( MSGT_VO, MSGL_V, + "Setting and using colorkey from mpv (0x%06lx)." + " Use -colorkey to change.\n", + x11->xv_colorkey ); + break; + } +} +/** + * \brief Init colorkey depending on the settings in xv_ck_info. + * + * \return Returns 0 on failure and 1 on success. + * + * Sets the colorkey variable according to the CK_SRC_* and CK_METHOD_* + * flags in xv_ck_info. + * + * Possiblilities: + * * Methods + * - manual colorkey drawing ( CK_METHOD_MANUALFILL ) + * - set colorkey as window background ( CK_METHOD_BACKGROUND ) + * - let Xv paint the colorkey ( CK_METHOD_AUTOPAINT ) + * * Sources + * - use currently set colorkey ( CK_SRC_CUR ) + * - use colorkey in vo_colorkey ( CK_SRC_USE ) + * - use and set colorkey in vo_colorkey ( CK_SRC_SET ) + * + * NOTE: If vo_colorkey has bits set after the first 3 low order bytes + * we don't draw anything as this means it was forced to off. + */ +int vo_xv_init_colorkey(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + Atom xv_atom; + int rez; + + /* check if colorkeying is needed */ + xv_atom = xv_intern_atom_if_exists(vo->x11, "XV_COLORKEY"); + + /* if we have to deal with colorkeying ... */ + if( xv_atom != None && !(vo_colorkey & 0xFF000000) ) + { + /* check if we should use the colorkey specified in vo_colorkey */ + if ( x11->xv_ck_info.source != CK_SRC_CUR ) + { + x11->xv_colorkey = vo_colorkey; + + /* check if we have to set the colorkey too */ + if ( x11->xv_ck_info.source == CK_SRC_SET ) + { + xv_atom = XInternAtom(x11->display, "XV_COLORKEY",False); + + rez = XvSetPortAttribute(x11->display, x11->xv_port, xv_atom, vo_colorkey); + if ( rez != Success ) + { + mp_msg( MSGT_VO, MSGL_FATAL, + "[xv common] Couldn't set colorkey!\n" ); + return 0; // error setting colorkey + } + } + } + else + { + int colorkey_ret; + + rez=XvGetPortAttribute(x11->display,x11->xv_port, xv_atom, &colorkey_ret); + if ( rez == Success ) + { + x11->xv_colorkey = colorkey_ret; + } + else + { + mp_msg( MSGT_VO, MSGL_FATAL, + "[xv common] Couldn't get colorkey!" + "Maybe the selected Xv port has no overlay.\n" ); + return 0; // error getting colorkey + } + } + + xv_atom = xv_intern_atom_if_exists(vo->x11, "XV_AUTOPAINT_COLORKEY"); + + /* should we draw the colorkey ourselves or activate autopainting? */ + if ( x11->xv_ck_info.method == CK_METHOD_AUTOPAINT ) + { + rez = !Success; // reset rez to something different than Success + + if ( xv_atom != None ) // autopaint is supported + { + rez = XvSetPortAttribute(x11->display, x11->xv_port, xv_atom, 1); + } + + if ( rez != Success ) + { + // fallback to manual colorkey drawing + x11->xv_ck_info.method = CK_METHOD_MANUALFILL; + } + } + else // disable colorkey autopainting if supported + { + if ( xv_atom != None ) // we have autopaint attribute + { + XvSetPortAttribute(x11->display, x11->xv_port, xv_atom, 0); + } + } + } + else // do no colorkey drawing at all + { + x11->xv_ck_info.method = CK_METHOD_NONE; + } /* end: should we draw colorkey */ + + /* output information about the current colorkey settings */ + vo_xv_print_ck_info(x11); + + return 1; // success +} + +/** + * \brief Draw the colorkey on the video window. + * + * Draws the colorkey depending on the set method ( colorkey_handling ). + * + * Also draws the black bars ( when the video doesn't fit the display in + * fullscreen ) separately, so they don't overlap with the video area. + * It doesn't call XFlush. + * + */ +void vo_xv_draw_colorkey(struct vo *vo, int32_t x, int32_t y, + int32_t w, int32_t h) +{ + struct vo_x11_state *x11 = vo->x11; + if( x11->xv_ck_info.method == CK_METHOD_MANUALFILL || + x11->xv_ck_info.method == CK_METHOD_BACKGROUND )//less tearing than XClearWindow() + { + XSetForeground(x11->display, x11->vo_gc, x11->xv_colorkey ); + XFillRectangle(x11->display, x11->window, x11->vo_gc, + x, y, + w, h ); + } +} + +/** \brief Tests if a valid argument for the ck suboption was given. */ +int xv_test_ck( void * arg ) +{ + strarg_t * strarg = (strarg_t *)arg; + + if ( strargcmp( strarg, "use" ) == 0 || + strargcmp( strarg, "set" ) == 0 || + strargcmp( strarg, "cur" ) == 0 ) + { + return 1; + } + + return 0; +} +/** \brief Tests if a valid arguments for the ck-method suboption was given. */ +int xv_test_ckm( void * arg ) +{ + strarg_t * strarg = (strarg_t *)arg; + + if ( strargcmp( strarg, "bg" ) == 0 || + strargcmp( strarg, "man" ) == 0 || + strargcmp( strarg, "auto" ) == 0 ) + { + return 1; + } + + return 0; +} + +/** + * \brief Modify the colorkey_handling var according to str + * + * Checks if a valid pointer ( not NULL ) to the string + * was given. And in that case modifies the colorkey_handling + * var to reflect the requested behaviour. + * If nothing happens the content of colorkey_handling stays + * the same. + * + * \param str Pointer to the string or NULL + * + */ +void xv_setup_colorkeyhandling(struct vo *vo, const char *ck_method_str, + const char *ck_str) +{ + struct vo_x11_state *x11 = vo->x11; + /* check if a valid pointer to the string was passed */ + if ( ck_str ) + { + if ( strncmp( ck_str, "use", 3 ) == 0 ) + { + x11->xv_ck_info.source = CK_SRC_USE; + } + else if ( strncmp( ck_str, "set", 3 ) == 0 ) + { + x11->xv_ck_info.source = CK_SRC_SET; + } + } + /* check if a valid pointer to the string was passed */ + if ( ck_method_str ) + { + if ( strncmp( ck_method_str, "bg", 2 ) == 0 ) + { + x11->xv_ck_info.method = CK_METHOD_BACKGROUND; + } + else if ( strncmp( ck_method_str, "man", 3 ) == 0 ) + { + x11->xv_ck_info.method = CK_METHOD_MANUALFILL; + } + else if ( strncmp( ck_method_str, "auto", 4 ) == 0 ) + { + x11->xv_ck_info.method = CK_METHOD_AUTOPAINT; + } + } +} + +#endif + +struct vo_x11_state *vo_x11_init_state(void) +{ + struct vo_x11_state *s = talloc_ptrtype(NULL, s); + *s = (struct vo_x11_state){ + .xv_ck_info = { CK_METHOD_MANUALFILL, CK_SRC_CUR }, + .olddecor = MWM_DECOR_ALL, + .oldfuncs = MWM_FUNC_MOVE | MWM_FUNC_CLOSE | MWM_FUNC_MINIMIZE | + MWM_FUNC_MAXIMIZE | MWM_FUNC_RESIZE, + .old_gravity = NorthWestGravity, + }; + return s; +} diff --git a/video/out/x11_common.h b/video/out/x11_common.h new file mode 100644 index 0000000000..cb8b39a3b1 --- /dev/null +++ b/video/out/x11_common.h @@ -0,0 +1,184 @@ +/* + * 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_X11_COMMON_H +#define MPLAYER_X11_COMMON_H + +#include <stdint.h> +#include <stdbool.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include "config.h" + +struct vo; + +struct vo_x11_state { + Display *display; + Window window; + Window rootwin; + int screen; + int display_is_local; + int depthonscreen; + + XIM xim; + XIC xic; + + GC vo_gc; + + struct xv_ck_info_s { + int method; ///< CK_METHOD_* constants + int source; ///< CK_SRC_* constants + } xv_ck_info; + unsigned long xv_colorkey; + unsigned int xv_port; + + int wm_type; + int fs_type; + int window_state; + int fs_flip; + + GC f_gc; + XSizeHints vo_hint; + unsigned int mouse_timer; + int mouse_waiting_hide; + int orig_layer; + int old_gravity; + int vo_old_x; + int vo_old_y; + int vo_old_width; + int vo_old_height; + + /* Keep track of original video width/height to determine when to + * resize window when reconfiguring. Resize window when video size + * changes, but don't force window size changes as long as video size + * stays the same (even if that size is different from the current + * window size after the user modified the latter). */ + int last_video_width; + int last_video_height; + /* Video size changed during fullscreen when we couldn't tell the new + * size to the window manager. Must set window size when turning + * fullscreen off. */ + bool size_changed_during_fs; + + unsigned int olddecor; + unsigned int oldfuncs; + XComposeStatus compose_status; + + Atom XA_NET_SUPPORTED; + Atom XA_NET_WM_STATE; + Atom XA_NET_WM_STATE_FULLSCREEN; + Atom XA_NET_WM_STATE_ABOVE; + Atom XA_NET_WM_STATE_STAYS_ON_TOP; + Atom XA_NET_WM_STATE_BELOW; + Atom XA_NET_WM_PID; + Atom XA_NET_WM_NAME; + Atom XA_NET_WM_ICON_NAME; + Atom XA_WIN_PROTOCOLS; + Atom XA_WIN_LAYER; + Atom XA_WIN_HINTS; + Atom XAWM_PROTOCOLS; + Atom XAWM_DELETE_WINDOW; + Atom XAUTF8_STRING; + Atom XA_NET_WM_CM; +}; + +#define vo_wm_LAYER 1 +#define vo_wm_FULLSCREEN 2 +#define vo_wm_STAYS_ON_TOP 4 +#define vo_wm_ABOVE 8 +#define vo_wm_BELOW 16 +#define vo_wm_NETWM (vo_wm_FULLSCREEN | vo_wm_STAYS_ON_TOP | vo_wm_ABOVE | vo_wm_BELOW) + +/* EWMH state actions, see + http://freedesktop.org/Standards/wm-spec/index.html#id2768769 */ +#define _NET_WM_STATE_REMOVE 0 /* remove/unset property */ +#define _NET_WM_STATE_ADD 1 /* add/set property */ +#define _NET_WM_STATE_TOGGLE 2 /* toggle property */ + +extern int metacity_hack; + +extern char** vo_fstype_list; + +extern char *mDisplayName; + +struct vo_x11_state *vo_x11_init_state(void); +int vo_init(struct vo *vo); +void vo_uninit(struct vo_x11_state *x11); +void vo_x11_decoration(struct vo *vo, int d ); +void vo_x11_classhint(struct vo *vo, Window window, const char *name); +void vo_x11_sizehint(struct vo *vo, int x, int y, int width, int height, int max); +int vo_x11_check_events(struct vo *vo); +void vo_x11_selectinput_witherr(Display *display, Window w, long event_mask); +void vo_x11_fullscreen(struct vo *vo); +int vo_x11_update_geometry(struct vo *vo, bool update_pos); +void vo_x11_setlayer(struct vo *vo, Window vo_window, int layer); +void vo_x11_uninit(struct vo *vo); +Colormap vo_x11_create_colormap(struct vo *vo, XVisualInfo *vinfo); +uint32_t vo_x11_set_equalizer(struct vo *vo, const char *name, int value); +uint32_t vo_x11_get_equalizer(const char *name, int *value); +bool vo_x11_screen_is_composited(struct vo *vo); +void fstype_help(void); +void vo_x11_create_vo_window(struct vo *vo, XVisualInfo *vis, + int x, int y, unsigned int width, unsigned int height, int flags, + Colormap col_map, const char *classname); +void vo_x11_clearwindow_part(struct vo *vo, Window vo_window, + int img_width, int img_height); +void vo_x11_clearwindow(struct vo *vo, Window vo_window); +void vo_x11_ontop(struct vo *vo); +void vo_x11_border(struct vo *vo); +void vo_x11_ewmh_fullscreen(struct vo_x11_state *x11, int action); + + +int vo_xv_set_eq(struct vo *vo, uint32_t xv_port, const char *name, int value); +int vo_xv_get_eq(struct vo *vo, uint32_t xv_port, const char *name, int *value); + +int vo_xv_enable_vsync(struct vo *vo); + +void vo_xv_get_max_img_dim(struct vo *vo, uint32_t * width, uint32_t * height); + +/*** colorkey handling ***/ + +#define CK_METHOD_NONE 0 ///< no colorkey drawing +#define CK_METHOD_BACKGROUND 1 ///< set colorkey as window background +#define CK_METHOD_AUTOPAINT 2 ///< let xv draw the colorkey +#define CK_METHOD_MANUALFILL 3 ///< manually draw the colorkey +#define CK_SRC_USE 0 ///< use specified / default colorkey +#define CK_SRC_SET 1 ///< use and set specified / default colorkey +#define CK_SRC_CUR 2 ///< use current colorkey ( get it from xv ) + +int vo_xv_init_colorkey(struct vo *vo); +void vo_xv_draw_colorkey(struct vo *vo, int32_t x, int32_t y, int32_t w, int32_t h); +void xv_setup_colorkeyhandling(struct vo *vo, const char *ck_method_str, const char *ck_str); + +/*** test functions for common suboptions ***/ +int xv_test_ck( void * arg ); +int xv_test_ckm( void * arg ); + +#ifdef CONFIG_XF86VM +void vo_vm_switch(struct vo *vo); +void vo_vm_close(struct vo *vo); +double vo_vm_get_fps(struct vo *vo); +#endif + +void update_xinerama_info(struct vo *vo); + +int vo_find_depth_from_visuals(Display *dpy, int screen, Visual **visual_return); +void xscreensaver_heartbeat(struct vo_x11_state *x11); + +#endif /* MPLAYER_X11_COMMON_H */ |