/** * @file video_display/gl.cpp * @author Lukas Hejtmanek * @author Milos Liska * @author Martin Piatka <445597@mail.muni.cz> * @author Martin Pulec */ /* * Copyright (c) 2010-2019 CESNET, z. s. p. o. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Neither the name of CESNET nor the names of its contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include "config.h" #include "config_unix.h" #include "config_win32.h" #endif #include #ifdef HAVE_MACOSX #include #include // CGL #include #include #elif defined HAVE_LINUX #include #include #include #include #include "x11_common.h" #else // WIN32 #include #include #endif /* HAVE_MACOSX */ #ifdef FREEGLUT #include #endif /* FREEGLUT */ #include "spout_sender.h" #include "syphon_server.h" #include #include #ifdef WIN32 #include #else #include #endif #include #include #include #include "debug.h" #include "gl_context.h" #include "host.h" #include "keyboard_control.h" #include "lib_common.h" #include "messaging.h" #include "module.h" #include "rang.hpp" #include "video.h" #include "video_display.h" #include "video_display/splashscreen.h" #include "tv.h" #define MAGIC_GL 0x1331018e #define MODULE_NAME "[GL] " #define DEFAULT_WIN_NAME "Ultragrid - OpenGL Display" #define MAX_BUFFER_SIZE 1 #define SYSTEM_VSYNC 0xFE #define SINGLE_BUF 0xFF // use single buffering instead of double #ifdef HWACC_VDPAU #include "hwaccel_vdpau.h" typedef GLintptr vdpauSurfaceNV; #define NV_CAST(x) ((void *)(uintptr_t)(x)) #endif using namespace std; static const char * yuv422_to_rgb_fp = R"raw( #version 110 uniform sampler2D image; uniform float imageWidth; void main() { vec4 yuv; yuv.rgba = texture2D(image, gl_TexCoord[0].xy).grba; if(gl_TexCoord[0].x * imageWidth / 2.0 - floor(gl_TexCoord[0].x * imageWidth / 2.0) > 0.5) yuv.r = yuv.a; yuv.r = 1.1643 * (yuv.r - 0.0625); yuv.g = yuv.g - 0.5; yuv.b = yuv.b - 0.5; float tmp; // this is a workaround over broken Gallium3D with Nouveau in U14.04 (and perhaps others) tmp = -0.2664 * yuv.b; tmp = 2.0 * tmp; gl_FragColor.r = yuv.r + 1.7926 * yuv.b; gl_FragColor.g = yuv.r - 0.2132 * yuv.g + tmp; gl_FragColor.b = yuv.r + 2.1124 * yuv.g; gl_FragColor.a = 1.0; } )raw"; /* DXT YUV (FastDXT) related */ static const char *fp_display_dxt1 = R"raw( #version 110 uniform sampler2D yuvtex; void main(void) { vec4 col = texture2D(yuvtex, gl_TexCoord[0].st); float Y = 1.1643 * (col[0] - 0.0625); float U = (col[1] - 0.5); float V = (col[2] - 0.5); float R = Y + 1.7926 * V; float G = Y - 0.2132 * U - 0.5328 * V; float B = Y + 2.1124 * U; gl_FragColor=vec4(R,G,B,1.0); } )raw"; static const char * vert = R"raw( #version 110 void main() { gl_TexCoord[0] = gl_MultiTexCoord0; gl_Position = ftransform(); } )raw"; static const char fp_display_dxt5ycocg[] = R"raw( #version 110 uniform sampler2D image; void main() { vec4 color; float Co; float Cg; float Y; float scale; color = texture2D(image, gl_TexCoord[0].xy); scale = (color.z * ( 255.0 / 8.0 )) + 1.0; Co = (color.x - (0.5 * 256.0 / 255.0)) / scale; Cg = (color.y - (0.5 * 256.0 / 255.0)) / scale; Y = color.w; gl_FragColor = vec4(Y + Co - Cg, Y + Cg, Y - Co - Cg, 1.0); } // main end )raw"; #ifdef HWACC_VDPAU struct state_vdpau { bool initialized = false; GLuint textures[4] = {0}; hw_vdpau_frame lastFrame; bool interopInitialized = false; bool mixerInitialized = false; VdpDevice device = VDP_INVALID_HANDLE; VdpGetProcAddress *get_proc_address = nullptr; vdpauSurfaceNV vdpgl_surf = 0; VdpOutputSurface out_surf = VDP_INVALID_HANDLE; VdpVideoMixer mixer = VDP_INVALID_HANDLE; uint32_t surf_width = 0; uint32_t surf_height = 0; VdpChromaType surf_ct = 0; void (*VDPAUInitNV)(const void * /*vdpDevice*/, const void * /*getProcAddress*/); void (*VDPAUFiniNV)(void); vdpauSurfaceNV (*VDPAURegisterVideoSurfaceNV)(const void * /*vdpSurface*/, GLenum /*target*/, GLsizei /*numTextureNames*/, const uint * /*textureNames*/); vdpauSurfaceNV (*VDPAURegisterOutputSurfaceNV)(const void * /*vdpSurface*/, GLenum /*target*/, GLsizei /*numTextureNames*/, const uint * /*textureNames*/); GLboolean (*VDPAUIsSurfaceNV)(vdpauSurfaceNV /*surface*/); void (*VDPAUUnregisterSurfaceNV)(vdpauSurfaceNV /*surface*/); void (*VDPAUGetSurfaceivNV)(vdpauSurfaceNV /*surface*/, GLenum /*pname*/, GLsizei /*bufSize*/, GLsizei * /*length*/, int * /*values*/); void (*VDPAUSurfaceAccessNV)(vdpauSurfaceNV /*surface*/, GLenum /*access*/); void (*VDPAUMapSurfacesNV)(GLsizei /*numSurfaces*/, const vdpauSurfaceNV * /*surfaces*/); void (*VDPAUUnmapSurfacesNV)(GLsizei /*numSurfaces*/, const vdpauSurfaceNV * /*surfaces*/); bool loadVdpGlFuncs(); bool init(); void checkInterop(VdpDevice device, VdpGetProcAddress *get_proc_address); void initInterop(VdpDevice device, VdpGetProcAddress *get_proc_address); void uninitInterop(); void initMixer(uint32_t w, uint32_t h, VdpChromaType ct); void mixerRender(VdpVideoSurface f); void uninitMixer(); void uninit(); vdp_funcs funcs; }; #endif struct state_gl { GLuint PHandle_uyvy, PHandle_dxt, PHandle_dxt5; // Framebuffer GLuint fbo_id; GLuint texture_display; GLuint texture_uyvy; GLuint pbo_id; /* For debugging... */ uint32_t magic; int window; bool fs; bool deinterlace; struct video_frame *current_frame; queue frame_queue; queue free_frame_queue; struct video_desc current_desc; struct video_desc current_display_desc; mutex lock; condition_variable new_frame_ready_cv; condition_variable frame_consumed_cv; double aspect; double video_aspect; unsigned long int frames; int dxt_height; struct timeval tv; int vsync; bool paused; enum show_cursor_t { SC_TRUE, SC_FALSE, SC_AUTOHIDE } show_cursor; chrono::steady_clock::time_point cursor_shown_from; ///< indicates time point from which is cursor show if show_cursor == SC_AUTOHIDE, timepoint() means cursor is not currently shown string syphon_spout_srv_name; bool should_exit_main_loop; // used only for GLUT (not freeglut) double window_size_factor; struct module mod; void *syphon_spout; bool hide_window; bool fixed_size, first_run; int fixed_w, fixed_h; bool nodecorate = false; #ifdef HWACC_VDPAU struct state_vdpau vdp; #endif state_gl(struct module *parent) : PHandle_uyvy(0), PHandle_dxt(0), PHandle_dxt5(0), fbo_id(0), texture_display(0), texture_uyvy(0), pbo_id(0), magic(MAGIC_GL), window(-1), fs(false), deinterlace(false), current_frame(nullptr), aspect(0.0), video_aspect(0.0), frames(0ul), dxt_height(0), vsync(1), paused(false), show_cursor(SC_AUTOHIDE), should_exit_main_loop(false), window_size_factor(1.0), syphon_spout(nullptr), hide_window(false), fixed_size(false), first_run(true), fixed_w(0), fixed_h(0) { gettimeofday(&tv, NULL); memset(¤t_desc, 0, sizeof(current_desc)); memset(¤t_display_desc, 0, sizeof(current_display_desc)); module_init_default(&mod); mod.cls = MODULE_CLASS_DATA; module_register(&mod, parent); } ~state_gl() { module_done(&mod); } }; static struct state_gl *gl; /* Prototyping */ static int display_gl_putf(void *state, struct video_frame *frame, int nonblock); static int display_gl_reconfigure(void *state, struct video_desc desc); static void gl_draw(double ratio, double bottom_offset, bool double_buf); static void gl_show_help(void); static void gl_change_aspect(struct state_gl *s, int width, int height); static void gl_resize(int width, int height); static void gl_render_uyvy(struct state_gl *s, char *data); static void gl_reconfigure_screen(struct state_gl *s, struct video_desc desc); static void glut_idle_callback(void); static void glut_key_callback(int key, bool is_special); static void glut_mouse_callback(int x, int y); static void glut_close_callback(void); static void glut_resize_window(bool fs, int height, double aspect, double window_size_factor); static void display_gl_set_sync_on_vblank(int value); static void screenshot(struct video_frame *frame); static void upload_texture(struct state_gl *s, char *data); static void display_gl_register_keyboard_ctl(struct module *mod); #ifdef HWACC_VDPAU static void gl_render_vdpau(struct state_gl *s, char *data) ATTRIBUTE(unused); #endif #ifdef HAVE_MACOSX extern "C" void NSApplicationLoad(void); #endif /** * Show help */ static void gl_show_help(void) { cout << "GL options:\n"; cout << rang::style::bold << rang::fg::red << "\t-d gl" << rang::fg::reset << "[:d|:fs|:aspect=/|:cursor|:size=X%%|:syphon[=]|:spout[=]|:nodecorate|:fixed_size[=WxH]|:vsync[|single]]* | help\n\n" << rang::style::reset; printf("\t\td\t\tdeinterlace\n"); printf("\t\tfs\t\tfullscreen\n"); printf("\t\tnodecorate\tdisable window decorations (works with X11 only)\n"); printf("\t\tnovsync\t\tdo not turn sync on VBlank\n"); printf("\t\tvsync=\tsets vsync to: 0 - disable; 1 - enable; -1 - adaptive vsync; D - leaves system default\n"); printf("\t\tsingle\t\tuse single buffer (instead of double-buffering\n"); printf("\t\taspect=/\trequested video aspect (eg. 16/9). Leave unset if PAR = 1.\n"); printf("\t\tcursor\t\tshow visible cursor\n"); printf("\t\tsize\t\tspecifies desired size of window compared " "to native resolution (in percents)\n"); printf("\t\tsyphon\t\tuse Syphon (optionally with name)\n"); printf("\t\tspout\t\tuse Spout (optionally with name)\n"); printf("\t\thide-window\tdo not show OpenGL window (useful with Syphon)\n"); printf("\n\nKeyboard shortcuts:\n"); printf("\t\t'f'\t\ttoggle fullscreen\n"); printf("\t\t'q'\t\tquit\n"); printf("\t\t'd'\t\ttoggle deinterlace\n"); printf("\t\t'p'\t\tpause video\n"); printf("\t\t's'\t\tscreenshot\n"); printf("\t\t'c'\t\tshow/hide cursor\n"); printf("\t\t'+'\t\tmake window smaller by a factor of 50%%\n"); printf("\t\t'-'\t\tmake window twice as big\n"); } static void gl_load_splashscreen(struct state_gl *s) { struct video_desc desc; desc.width = 512; desc.height = 512; desc.color_spec = RGBA; desc.interlacing = PROGRESSIVE; desc.fps = 1; desc.tile_count = 1; display_gl_reconfigure(s, desc); struct video_frame *frame = vf_alloc_desc_data(desc); const char *data = splash_data; memset(frame->tiles[0].data, 0, frame->tiles[0].data_len); for (unsigned int y = 0; y < splash_height; ++y) { char *line = frame->tiles[0].data; line += vc_get_linesize(frame->tiles[0].width, frame->color_spec) * (((frame->tiles[0].height - splash_height) / 2) + y); line += vc_get_linesize( (frame->tiles[0].width - splash_width)/2, frame->color_spec); for (unsigned int x = 0; x < splash_width; ++x) { HEADER_PIXEL(data,line); line += 4; } } s->frame_queue.push(frame); } static void * display_gl_init(struct module *parent, const char *fmt, unsigned int flags) { UNUSED(flags); if (gl) { LOG(LOG_LEVEL_ERROR) << "Multiple instances of GL display is disallowed!\n"; return nullptr; } struct state_gl *s = new state_gl(parent); // parse parameters if (fmt != NULL) { if (strcmp(fmt, "help") == 0) { gl_show_help(); delete s; return &display_init_noerr; } char *tmp, *ptr; tmp = ptr = strdup(fmt); char *tok, *save_ptr = NULL; while((tok = strtok_r(ptr, ":", &save_ptr)) != NULL) { if(!strcmp(tok, "d")) { s->deinterlace = true; } else if(!strcmp(tok, "fs")) { s->fs = true; } else if(!strncmp(tok, "aspect=", strlen("aspect="))) { s->video_aspect = atof(tok + strlen("aspect=")); char *pos = strchr(tok,'/'); if(pos) s->video_aspect /= atof(pos + 1); } else if(!strcasecmp(tok, "nodecorate")) { #ifdef HAVE_LINUX s->nodecorate = true; #else log_msg(LOG_LEVEL_WARNING, "[GL] Nodecorate not supported for current platform!\n"); #endif } else if(!strcasecmp(tok, "novsync")) { s->vsync = 0; } else if(!strcasecmp(tok, "single")) { s->vsync = SINGLE_BUF; } else if (!strncmp(tok, "vsync=", strlen("vsync="))) { if (toupper((tok + strlen("vsync="))[0]) == 'D') { s->vsync = SYSTEM_VSYNC; } else { s->vsync = atoi(tok + strlen("vsync=")); } } else if (!strcasecmp(tok, "cursor")) { s->show_cursor = state_gl::SC_TRUE; } else if (strstr(tok, "syphon") == tok || strstr(tok, "spout") == tok) { #if defined HAVE_SYPHON || defined HAVE_SPOUT if (strchr(tok, '=')) { s->syphon_spout_srv_name = strchr(tok, '=') + 1; } else { s->syphon_spout_srv_name = "UltraGrid"; } #else log_msg(LOG_LEVEL_ERROR, "[GL] Syphon/Spout support not compiled in.\n"); free(tmp); delete s; return NULL; #endif } else if (!strcasecmp(tok, "hide-window")) { s->hide_window = true; } else if(!strncmp(tok, "size=", strlen("size="))) { s->window_size_factor = atof(tok + strlen("size=")) / 100.0; } else if (strncmp(tok, "fixed_size", strlen("fixed_size")) == 0) { s->fixed_size = true; if (strncmp(tok, "fixed_size=", strlen("fixed_size=")) == 0) { char *size = tok + strlen("fixed_size="); if (strchr(size, 'x')) { s->fixed_w = atoi(size); s->fixed_h = atoi(strchr(size, 'x') + 1); } } } else { log_msg(LOG_LEVEL_ERROR, "[GL] Unknown option: %s\n", tok); free(tmp); delete s; return NULL; } ptr = NULL; } free(tmp); } fprintf(stdout,"GL setup: fullscreen: %s, deinterlace: %s\n", s->fs ? "ON" : "OFF", s->deinterlace ? "ON" : "OFF"); if (s->fixed_size && s->fixed_w && s->fixed_h) { struct video_desc desc; desc.width = s->fixed_w; desc.height = s->fixed_h; desc.color_spec = RGBA; desc.interlacing = PROGRESSIVE; desc.fps = 1; desc.tile_count = 1; s->frame_queue.push(vf_alloc_desc_data(desc)); } gl_load_splashscreen(s); display_gl_register_keyboard_ctl(&s->mod); /* GLUT callbacks take only some arguments so we need static variable */ gl = s; return (void*)s; } /** * This function just sets new video description. */ static int display_gl_reconfigure(void *state, struct video_desc desc) { struct state_gl *s = (struct state_gl *) state; assert (desc.color_spec == RGBA || desc.color_spec == RGB || desc.color_spec == UYVY || desc.color_spec == DXT1 || desc.color_spec == DXT1_YUV || desc.color_spec == DXT5 #ifdef HWACC_VDPAU || desc.color_spec == HW_VDPAU #endif ); s->current_desc = desc; return TRUE; } static void glut_resize_window(bool fs, int height, double aspect, double window_size_factor) { log_msg(LOG_LEVEL_INFO, MODULE_NAME "glut_resize_window - fullscreen: %d, aspect: %lf, factor %lf\n", (int) fs, aspect, window_size_factor); if (fs) { glutReshapeWindow(glutGet(GLUT_SCREEN_WIDTH), glutGet(GLUT_SCREEN_HEIGHT)); glutFullScreen(); } else { glutReshapeWindow(window_size_factor * height * aspect, window_size_factor * height); } glutPostRedisplay(); } /* * Please note, that setting the value of 0 to GLX function is invalid according to * documentation. However. reportedly NVidia driver does unset VSync. */ static void display_gl_set_sync_on_vblank(int value) { if (value == SYSTEM_VSYNC) { return; } bool have_ext_swap_control_tear = false; #ifdef HAVE_LINUX if (strstr(glXQueryExtensionsString(glXGetCurrentDisplay(), 0), "GLX_EXT_swap_control_tear")) { have_ext_swap_control_tear = true; } #elif defined WIN32 const char * (*wglGetExtensionsStringARBProc)(HDC hdc) = (const char *(*)(HDC)) wglGetProcAddress("wglGetExtensionsStringARB"); if (strstr(wglGetExtensionsStringARBProc(wglGetCurrentDC()), "WGL_EXT_swap_control_tear")) { have_ext_swap_control_tear = true; } #endif if (value == -1) { if (!have_ext_swap_control_tear) { log_msg(LOG_LEVEL_WARNING, "WGL/GLX_EXT_swap_control_tear not detected, using normal vsync.\n"); value = 1; } else { log_msg(LOG_LEVEL_VERBOSE, "WGL/GLX_EXT_swap_control_tear detected, using adaptive vsync\n"); } } #ifdef HAVE_MACOSX int swap_interval = value; CGLContextObj cgl_context = CGLGetCurrentContext(); CGLSetParameter(cgl_context, kCGLCPSwapInterval, &swap_interval); #elif HAVE_LINUX /* using GLX_SGI_swap_control * * Also it is worth considering to use GLX_EXT_swap_control (instead?). * But we would need both Display and GLXDrawable variables which we do not currently have */ int (*glXSwapIntervalSGIProc)(int interval) = 0; glXSwapIntervalSGIProc = (int (*)(int)) glXGetProcAddressARB( (const GLubyte *) "glXSwapIntervalSGI"); if(glXSwapIntervalSGIProc) { glXSwapIntervalSGIProc(value); } else { log_msg(LOG_LEVEL_WARNING, "[GL display] GLX_SGI_swap_control is presumably not supported. Unable to set sync-on-VBlank.\n"); } #elif WIN32 BOOL (*wglSwapIntervalEXTProc)(int interval) = 0; wglSwapIntervalEXTProc = (BOOL (*)(int)) wglGetProcAddress("wglSwapIntervalEXT"); if (wglSwapIntervalEXTProc) { BOOL ret = wglSwapIntervalEXTProc(value); if (!ret) { log_msg(LOG_LEVEL_WARNING, "[GL display] Unable to set sync-on-VBlank.\n"); } } else { log_msg(LOG_LEVEL_WARNING, "[GL display] WGL_EXT_swap_control is presumably not supported. Unable to set sync-on-VBlank.\n"); } #endif } static void screenshot(struct video_frame *frame) { char name[128]; time_t t; t = time(NULL); struct tm *time_ptr; #ifdef WIN32 time_ptr = localtime(&t); #else struct tm time_tmp; localtime_r(&t, &time_tmp); time_ptr = &time_tmp; #endif strftime(name, sizeof(name), "screenshot-%a, %d %b %Y %T %z.pnm", time_ptr); if (save_video_frame_as_pnm(frame, name)) { log_msg(LOG_LEVEL_NOTICE, "[GL] Generated screenshot \"%s\".\n", name); } else { log_msg(LOG_LEVEL_ERROR, "[GL] Unable to generate screenshot!\n"); } } /** * This function does the actual reconfiguration of GL state. * * This function must be called only from GL thread. */ static void gl_reconfigure_screen(struct state_gl *s, struct video_desc desc) { assert(s->magic == MAGIC_GL); if(desc.color_spec == DXT1 || desc.color_spec == DXT1_YUV || desc.color_spec == DXT5) { s->dxt_height = (desc.height + 3) / 4 * 4; } else { s->dxt_height = desc.height; } if(!s->video_aspect) s->aspect = (double) desc.width / desc.height; else s->aspect = s->video_aspect; fprintf(stdout,"Setting GL window size %dx%d (%dx%d).\n", (int)(s->aspect * desc.height), desc.height, desc.width, desc.height); if (!s->hide_window) glutShowWindow(); glUseProgram(0); gl_check_error(); if(desc.color_spec == DXT1 || desc.color_spec == DXT1_YUV) { glBindTexture(GL_TEXTURE_2D,s->texture_display); size_t data_len = ((desc.width + 3) / 4 * 4* s->dxt_height)/2; char *buffer = (char *) malloc(data_len); glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, (desc.width + 3) / 4 * 4, s->dxt_height, 0, data_len, /* passing NULL here isn't allowed and some rigid implementations * will crash here. We just pass some egliable buffer to fulfil * this requirement. glCompressedSubTexImage2D works as expected. */ buffer); free(buffer); if(desc.color_spec == DXT1_YUV) { glBindTexture(GL_TEXTURE_2D,s->texture_display); glUseProgram(s->PHandle_dxt); } } else if (desc.color_spec == UYVY) { glActiveTexture(GL_TEXTURE0 + 2); glBindTexture(GL_TEXTURE_2D,s->texture_uyvy); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, desc.width / 2, desc.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glUseProgram(s->PHandle_uyvy); glUniform1i(glGetUniformLocation(s->PHandle_uyvy, "image"), 2); glUniform1f(glGetUniformLocation(s->PHandle_uyvy, "imageWidth"), (GLfloat) desc.width); glUseProgram(0); glActiveTexture(GL_TEXTURE0 + 0); glBindTexture(GL_TEXTURE_2D,s->texture_display); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, desc.width, desc.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); } else if (desc.color_spec == RGBA) { glBindTexture(GL_TEXTURE_2D,s->texture_display); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, desc.width, desc.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); } else if (desc.color_spec == RGB) { glBindTexture(GL_TEXTURE_2D,s->texture_display); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, desc.width, desc.height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); } else if (desc.color_spec == DXT5) { glUseProgram(s->PHandle_dxt5); glBindTexture(GL_TEXTURE_2D,s->texture_display); glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (desc.width + 3) / 4 * 4, s->dxt_height, 0, (desc.width + 3) / 4 * 4 * s->dxt_height, NULL); } #ifdef HWACC_VDPAU else if (desc.color_spec == HW_VDPAU) { s->vdp.init(); } #endif gl_check_error(); glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, s->pbo_id); glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, vc_get_linesize(desc.width, desc.color_spec) * desc.height, 0, GL_STREAM_DRAW_ARB); glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0); gl_check_error(); if (!s->fixed_size || s->first_run) { glut_resize_window(s->fs, desc.height, s->aspect, s->window_size_factor); s->first_run = false; } else { // s->aspect might have changed so we may want to run this to reflect it gl_change_aspect(s, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT)); } gl_check_error(); display_gl_set_sync_on_vblank(s->vsync); gl_check_error(); #ifdef HAVE_SYPHON if (!s->syphon_spout && !s->syphon_spout_srv_name.empty()) { s->syphon_spout = syphon_server_register(CGLGetCurrentContext(), s->syphon_spout_srv_name.c_str()); } #endif #ifdef HAVE_SPOUT if (!s->syphon_spout_srv_name.empty()) { if (s->syphon_spout) { spout_sender_unregister(s->syphon_spout); } s->syphon_spout = spout_sender_register(s->syphon_spout_srv_name.c_str(), desc.width, desc.height); } #endif if (s->nodecorate) { #ifdef HAVE_LINUX // https://stackoverflow.com/questions/12343390/how-to-get-x-window-id-of-a-window-created-by-glut GLXDrawable d = glXGetCurrentDrawable(); x11_unset_window_decorations(glXGetCurrentDisplay(), d); #endif } s->current_display_desc = desc; } static void gl_render(struct state_gl *s, char *data) { /* for DXT, deinterlacing doesn't make sense since it is * always deinterlaced before comrpression */ if(s->deinterlace && (s->current_display_desc.color_spec == RGBA || s->current_display_desc.color_spec == UYVY)) vc_deinterlace((unsigned char *) data, vc_get_linesize(s->current_display_desc.width, s->current_display_desc.color_spec), s->current_display_desc.height); gl_check_error(); switch(s->current_display_desc.color_spec) { case DXT1: glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (s->current_display_desc.width + 3) / 4 * 4, s->dxt_height, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, ((s->current_display_desc.width + 3) / 4 * 4 * s->dxt_height)/2, data); break; case DXT1_YUV: glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, s->current_display_desc.width, s->current_display_desc.height, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, (s->current_display_desc.width * s->current_display_desc.height/16)*8, data); break; case UYVY: gl_render_uyvy(s, data); break; case RGB: case RGBA: upload_texture(s, data); break; case DXT5: glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, (s->current_display_desc.width + 3) / 4 * 4, s->dxt_height, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (s->current_display_desc.width + 3) / 4 * 4 * s->dxt_height, data); break; #ifdef HWACC_VDPAU case HW_VDPAU: gl_render_vdpau(s, data); break; #endif default: fprintf(stderr, "[GL] Fatal error - received unsupported codec.\n"); exit_uv(EXIT_FAILURE); return; } gl_check_error(); } static void pop_frame(struct state_gl *s) { unique_lock lk(s->lock); s->frame_queue.pop(); lk.unlock(); s->frame_consumed_cv.notify_one(); } static void glut_idle_callback(void) { struct state_gl *s = gl; struct timeval tv; double seconds; struct video_frame *frame; struct message *msg; while ((msg = check_message(&s->mod))) { auto msg_univ = reinterpret_cast(msg); struct response *r; if (strncasecmp(msg_univ->text, "win-title ", strlen("win_title ")) == 0) { glutSetWindowTitle(msg_univ->text + strlen("win_title ")); r = new_response(RESPONSE_OK, NULL); } else if (strstr(msg_univ->text, "fullscreen") != nullptr) { gl->fs = !gl->fs; glut_resize_window(gl->fs, gl->current_display_desc.height, gl->aspect, gl->window_size_factor); r = new_response(RESPONSE_OK, NULL); } else { fprintf(stderr, "[GL] Unknown command received: %s\n", msg_univ->text); r = new_response(RESPONSE_BAD_REQUEST, NULL); } free_message(msg, r); } if (gl->show_cursor == state_gl::SC_AUTOHIDE) { if (gl->cursor_shown_from != chrono::steady_clock::time_point()) { auto now = chrono::steady_clock::now(); if (chrono::duration_cast(now - gl->cursor_shown_from).count() > 2) { glutSetCursor(GLUT_CURSOR_NONE); gl->cursor_shown_from = chrono::steady_clock::time_point(); } } } { unique_lock lk(s->lock); double timeout = min(2.0 / s->current_display_desc.fps, 0.1); s->new_frame_ready_cv.wait_for(lk, chrono::duration(timeout), [s] { return s->frame_queue.size() > 0;}); if (s->frame_queue.size() == 0) { return; } frame = s->frame_queue.front(); } if (!frame) { #ifdef FREEGLUT glutLeaveMainLoop(); #endif pop_frame(s); return; } if (s->paused) { pop_frame(s); unique_lock lk(s->lock); vf_recycle(frame); s->free_frame_queue.push(frame); return; } if (s->current_frame) { s->lock.lock(); vf_recycle(s->current_frame); s->free_frame_queue.push(s->current_frame); s->lock.unlock(); } s->current_frame = frame; if (!video_desc_eq(video_desc_from_frame(frame), s->current_display_desc)) { gl_reconfigure_screen(s, video_desc_from_frame(frame)); } gl_render(s, frame->tiles[0].data); gl_draw(s->aspect, (gl->dxt_height - gl->current_display_desc.height) / (float) gl->dxt_height * 2, gl->vsync != SINGLE_BUF); // publish to Syphon/Spout if (s->syphon_spout) { #ifdef HAVE_SYPHON syphon_server_publish(s->syphon_spout, frame->tiles[0].width, frame->tiles[0].height, s->texture_display); #elif defined HAVE_SPOUT spout_sender_sendframe(s->syphon_spout, frame->tiles[0].width, frame->tiles[0].height, s->texture_display); glBindTexture(GL_TEXTURE_2D, s->texture_display); #endif // HAVE_SPOUT } glutSwapBuffers(); pop_frame(s); /* FPS Data, this is pretty ghetto though.... */ s->frames++; gettimeofday(&tv, NULL); seconds = tv_diff(tv, s->tv); if (seconds > 5) { double fps = s->frames / seconds; log_msg(LOG_LEVEL_INFO, "[GL] %lu frames in %g seconds = %g FPS\n", s->frames, seconds, fps); s->frames = 0; s->tv = tv; } } static int64_t translate_glut_to_ug(int key, bool is_special) { #ifdef FREEGLUT if (is_special && (key == GLUT_KEY_CTRL_L || key == GLUT_KEY_CTRL_R)) { return 0; } #endif // defined FREEGLUT if (!is_special) { return key; } switch (key) { case GLUT_KEY_LEFT: return K_LEFT; case GLUT_KEY_UP: return K_UP; case GLUT_KEY_RIGHT: return K_RIGHT; case GLUT_KEY_DOWN: return K_DOWN; } return -1; } static void glut_key_callback(int key, bool is_special) { log_msg(LOG_LEVEL_VERBOSE, MODULE_NAME "%s %d pressed\n", is_special ? "Special key" : "Key", key); switch (key | (is_special ? 0x10000LL : 0)) { // prefix special keys case 'f': gl->fs = !gl->fs; glut_resize_window(gl->fs, gl->current_display_desc.height, gl->aspect, gl->window_size_factor); break; case 'q': #if defined FREEGLUT || defined HAVE_MACOSX exit_uv(0); #else /// @todo /// This shouldn't happen (?). We have either freeglut (Linux, MSW) or /// original GLUT on OS X glutDestroyWindow(gl->window); exit(1); #endif break; case 'd': gl->deinterlace = !gl->deinterlace; printf("Deinterlacing: %s\n", gl->deinterlace ? "ON" : "OFF"); break; case 'p': gl->paused = !gl->paused; LOG(LOG_LEVEL_NOTICE) << "[GL] " << (gl->paused ? "Paused (press 'p' to unpause)" : "Unpaused") << "\n"; break; case 's': screenshot(gl->current_frame); break; case 'c': gl->show_cursor = (state_gl::show_cursor_t) (((int) gl->show_cursor + 1) % 3); LOG(LOG_LEVEL_NOTICE) << MODULE_NAME << "Show cursor (0 - on, 1 - off, 2 - autohide): " << gl->show_cursor << "\n"; glutSetCursor(gl->show_cursor == state_gl::SC_TRUE ? GLUT_CURSOR_INHERIT : GLUT_CURSOR_NONE); break; case '+': gl->window_size_factor *= 2; glut_resize_window(gl->fs, gl->current_display_desc.height, gl->aspect, gl->window_size_factor); break; case '-': gl->window_size_factor /= 2; glut_resize_window(gl->fs, gl->current_display_desc.height, gl->aspect, gl->window_size_factor); break; default: { int64_t ugk = translate_glut_to_ug(key, is_special); if (ugk > 0) { if (glutGetModifiers() == GLUT_ACTIVE_CTRL) { ugk = K_CTRL(ugk); } keycontrol_send_key(get_root_module(&gl->mod), ugk); } else if (ugk != 0) { log_msg(LOG_LEVEL_WARNING, MODULE_NAME "Cannot translate%s key %d!\n", is_special ? " special" : "", key); } } } } static void glut_mouse_callback(int /* x */, int /* y */) { if (gl->show_cursor == state_gl::SC_AUTOHIDE) { if (gl->cursor_shown_from == chrono::steady_clock::time_point()) { glutSetCursor(GLUT_CURSOR_INHERIT); } gl->cursor_shown_from = chrono::steady_clock::now(); } } #ifdef FREEGLUT jmp_buf error_env; static void glut_init_error_callback(const char *fmt, va_list ap) { va_list aq; va_copy(aq, ap); // get number of required bytes int size = vsnprintf(NULL, 0, fmt, ap); va_end(aq); // format the string auto buffer = (char *) alloca(size + 1); va_copy(aq, ap); if (vsprintf(buffer, fmt, ap) >= 0) { LOG(LOG_LEVEL_ERROR) << "[GL] " << buffer << "\n"; } va_end(aq); // This is required - if there is no noexit function call, glutInit() // continues normally until it crashes. longjmp(error_env, EXIT_FAIL_DISPLAY); } #endif static bool display_gl_check_gl_version() { auto version = (const char *) glGetString(GL_VERSION); if (!version) { log_msg(LOG_LEVEL_ERROR, MODULE_NAME "Unable to get OpenGL version!\n"); } if (atof(version) < 2.0) { log_msg(LOG_LEVEL_ERROR, MODULE_NAME "ERROR: OpenGL 2.0 is not supported, try updating your drivers...\n"); return false; } log_msg(LOG_LEVEL_INFO, MODULE_NAME "OpenGL 2.0 is supported...\n"); return true; } static bool display_gl_init_opengl(struct state_gl *s) { #if defined HAVE_LINUX || defined WIN32 GLenum err; #endif // HAVE_LINUX #ifdef FREEGLUT glutInitErrorFunc(glut_init_error_callback); if (setjmp(error_env) == 0) { #endif glutInit(&uv_argc, uv_argv); #ifdef FREEGLUT } else { exit_uv(EXIT_FAIL_DISPLAY); return false; } #endif glutInitDisplayMode(GLUT_RGBA | (s->vsync == SINGLE_BUF ? GLUT_SINGLE : GLUT_DOUBLE)); #ifdef HAVE_MACOSX /* Startup function to call when running Cocoa code from a Carbon application. Whatever the fuck that means. */ /* Avoids uncaught exception (1002) when creating CGSWindow */ NSApplicationLoad(); #endif #ifdef FREEGLUT glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION); #endif glutIdleFunc(glut_idle_callback); s->window = glutCreateWindow(get_commandline_param("window-title") ? get_commandline_param("window-title") : DEFAULT_WIN_NAME); if (s->hide_window) glutHideWindow(); glutSetCursor(s->show_cursor == state_gl::SC_TRUE ? GLUT_CURSOR_INHERIT : GLUT_CURSOR_NONE); //glutHideWindow(); glutKeyboardFunc([](unsigned char key, int /*x*/, int /*y*/) { glut_key_callback(key, false); }); glutSpecialFunc([](int key, int /*x*/, int /*y*/) { glut_key_callback(key, true); }); // special keys glutDisplayFunc((void (*)())glutSwapBuffers); // cast is needed because glutSwapBuffers is stdcall on MSW glutMotionFunc(glut_mouse_callback); glutPassiveMotionFunc(glut_mouse_callback); #ifdef HAVE_MACOSX glutWMCloseFunc(glut_close_callback); #elif FREEGLUT glutCloseFunc(glut_close_callback); #endif glutReshapeFunc(gl_resize); if (!display_gl_check_gl_version()) { return false; } #if defined HAVE_LINUX || defined WIN32 err = glewInit(); if (GLEW_OK != err) { /* Problem: glewInit failed, something is seriously wrong. */ fprintf(stderr, "GLEW Error: %d\n", err); return false; } #endif /* HAVE_LINUX */ glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); glEnable( GL_TEXTURE_2D ); glGenTextures(1, &s->texture_display); glBindTexture(GL_TEXTURE_2D, s->texture_display); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glGenTextures(1, &s->texture_uyvy); glBindTexture(GL_TEXTURE_2D, s->texture_uyvy); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); s->PHandle_uyvy = glsl_compile_link(vert, yuv422_to_rgb_fp); // Create fbo glGenFramebuffersEXT(1, &s->fbo_id); s->PHandle_dxt = glsl_compile_link(vert, fp_display_dxt1); glUseProgram(s->PHandle_dxt); glUniform1i(glGetUniformLocation(s->PHandle_dxt,"yuvtex"),0); glUseProgram(0); s->PHandle_dxt5 = glsl_compile_link(vert, fp_display_dxt5ycocg); /*if (pthread_create(&(s->thread_id), NULL, display_thread_gl, (void *) s) != 0) { perror("Unable to create display thread\n"); return NULL; }*/ glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // set row alignment to 1 byte instead of default // 4 bytes which won't work on row-unaligned RGB glGenBuffersARB(1, &s->pbo_id); return true; } static void display_gl_run(void *arg) { struct state_gl *s = (struct state_gl *) arg; if (!display_gl_init_opengl(s)) { exit_uv(1); return; } #if defined FREEGLUT glutMainLoop(); #else while (!s->should_exit_main_loop) { glut_idle_callback(); glutCheckLoop(); } #endif } static void gl_change_aspect(struct state_gl *s, int width, int height) { double screen_ratio; double x = 1.0, y = 1.0; glMatrixMode( GL_PROJECTION ); glLoadIdentity( ); screen_ratio = (double) width / height; if(screen_ratio > s->aspect) { x = (double) height * s->aspect / width; } else { y = (double) width / (height * s->aspect); } glScalef(x, y, 1); glOrtho(-1,1,-1/s->aspect,1/s->aspect,10,-10); } static void gl_resize(int width, int height) { debug_msg("Resized to: %dx%d\n", width, height); glViewport( 0, 0, ( GLint )width, ( GLint )height ); gl_change_aspect(gl, width, height); glMatrixMode( GL_MODELVIEW ); glLoadIdentity( ); if (gl->vsync == SINGLE_BUF) { glDrawBuffer(GL_FRONT); /* Clear the screen */ glClear(GL_COLOR_BUFFER_BIT); } if (gl->current_frame) { // redraw last frame for (int i = 0; i < 2; ++i) { gl_render(gl, gl->current_frame->tiles[0].data); gl_draw(gl->aspect, (gl->dxt_height - gl->current_display_desc.height) / (float) gl->dxt_height * 2, gl->vsync != SINGLE_BUF); glutSwapBuffers(); } glutPostRedisplay(); } } static void upload_texture(struct state_gl *s, char *data) { #if ! defined GL_NO_PBO GLuint format = s->current_display_desc.color_spec == RGB ? GL_RGB : GL_RGBA; GLint width = s->current_display_desc.width; if (s->current_display_desc.color_spec == UYVY) { width /= 2; } glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, s->pbo_id); // current pbo glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, s->current_display_desc.height, format, GL_UNSIGNED_BYTE, 0); int data_size = vc_get_linesize(s->current_display_desc.width, s->current_display_desc.color_spec) * s->current_display_desc.height; glBufferDataARB(GL_PIXEL_UNPACK_BUFFER_ARB, data_size, 0, GL_STREAM_DRAW_ARB); void *ptr = (GLubyte*) glMapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY_ARB); if (ptr) { // update data directly on the mapped buffer memcpy(ptr, data, data_size); glUnmapBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB); // release pointer to mapping buffer } glBindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0); #else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, s->current_display_desc.height, format, GL_UNSIGNED_BYTE, data); #endif } static void gl_render_uyvy(struct state_gl *s, char *data) { int status; glBindTexture(GL_TEXTURE_2D, 0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, s->fbo_id); gl_check_error(); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, s->texture_display, 0); status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); assert(status == GL_FRAMEBUFFER_COMPLETE_EXT); glActiveTexture(GL_TEXTURE0 + 2); glBindTexture(GL_TEXTURE_2D, s->texture_uyvy); glMatrixMode( GL_PROJECTION ); glPushMatrix(); glLoadIdentity( ); glMatrixMode( GL_MODELVIEW ); glPushMatrix(); glLoadIdentity( ); glPushAttrib(GL_VIEWPORT_BIT); glViewport( 0, 0, s->current_display_desc.width, s->current_display_desc.height); upload_texture(s, data); glUseProgram(s->PHandle_uyvy); glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); gl_check_error(); glBegin(GL_QUADS); glTexCoord2f(0.0, 0.0); glVertex2f(-1.0, -1.0); glTexCoord2f(1.0, 0.0); glVertex2f(1.0, -1.0); glTexCoord2f(1.0, 1.0); glVertex2f(1.0, 1.0); glTexCoord2f(0.0, 1.0); glVertex2f(-1.0, 1.0); glEnd(); glPopAttrib(); glMatrixMode( GL_PROJECTION ); glPopMatrix(); glMatrixMode( GL_MODELVIEW ); glPopMatrix(); glUseProgram(0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glActiveTexture(GL_TEXTURE0 + 0); glBindTexture(GL_TEXTURE_2D, s->texture_display); } #ifdef HWACC_VDPAU /** * @brief Uninitializes VdpMixer */ void state_vdpau::uninitMixer(){ VdpStatus st; if (mixer != VDP_INVALID_HANDLE){ st = funcs.videoMixerDestroy(mixer); mixer = VDP_INVALID_HANDLE; if(st != VDP_STATUS_OK){ log_msg(LOG_LEVEL_ERROR, "Failed to destroy VdpVideoMixer: %s\n", funcs.getErrorString(st)); } } if (out_surf != VDP_INVALID_HANDLE){ st = funcs.outputSurfaceDestroy(out_surf); if(st != VDP_STATUS_OK){ log_msg(LOG_LEVEL_ERROR, "Failed to destroy VdpOutputSurface: %s\n", funcs.getErrorString(st)); } out_surf = VDP_INVALID_HANDLE; surf_width = 0; surf_height = 0; surf_ct = 0; } VDPAUUnregisterSurfaceNV(vdpgl_surf); mixerInitialized = false; } /** * @brief Initializes VdpMixer */ void state_vdpau::initMixer(uint32_t w, uint32_t h, VdpChromaType ct){ uninitMixer(); surf_ct = ct; surf_width = w; surf_height = h; VdpStatus st; VdpRGBAFormat rgbaFormat = VDP_RGBA_FORMAT_B8G8R8A8; st = funcs.outputSurfaceCreate(device, rgbaFormat, surf_width, surf_height, &out_surf); if(st != VDP_STATUS_OK){ log_msg(LOG_LEVEL_ERROR, "Failed to create VdpOutputSurface: %s\n", funcs.getErrorString(st)); } VdpVideoMixerParameter params[] = { VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE, VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH, VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT }; void *param_vals[] = { &surf_ct, &surf_width, &surf_height }; st = funcs.videoMixerCreate(device, 0, NULL, 3, params, param_vals, &mixer); if(st != VDP_STATUS_OK){ log_msg(LOG_LEVEL_ERROR, "Failed to create VdpVideoMixer: %s\n", funcs.getErrorString(st)); } vdpgl_surf = VDPAURegisterOutputSurfaceNV(NV_CAST(out_surf), GL_TEXTURE_2D, 1, textures); VDPAUSurfaceAccessNV(vdpgl_surf, GL_WRITE_DISCARD_NV); mixerInitialized = true; } /** * @brief Renders VdpVideoSurface into a VdpOutputSurface */ void state_vdpau::mixerRender(VdpVideoSurface f){ VdpStatus st = funcs.videoMixerRender(mixer, VDP_INVALID_HANDLE, NULL, VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME, 0, NULL, f, 0, NULL, NULL, out_surf, NULL, NULL, 0, NULL); if(st != VDP_STATUS_OK){ log_msg(LOG_LEVEL_ERROR, "Failed to render: %s\n", funcs.getErrorString(st)); } } /** * @brief Checks VdpMixer parameters and reinitializes it if they don't match the video parameters */ static void check_mixer(struct state_gl *s, hw_vdpau_frame *frame){ uint32_t frame_w; uint32_t frame_h; VdpChromaType ct; VdpStatus st = s->vdp.funcs.videoSurfaceGetParameters(frame->surface, &ct, &frame_w, &frame_h); if(st != VDP_STATUS_OK){ log_msg(LOG_LEVEL_ERROR, "Failed to get surface parameters: %s\n", s->vdp.funcs.getErrorString(st)); } if(s->vdp.surf_width != frame_w || s->vdp.surf_height != frame_h || s->vdp.surf_ct != ct || !s->vdp.mixerInitialized) { s->vdp.initMixer(frame_w, frame_h, ct); } } /** * @brief Renders a vdpau frame */ static void gl_render_vdpau(struct state_gl *s, char *data) { assert(s->vdp.initialized); hw_vdpau_frame * frame = (hw_vdpau_frame *) data; glBindTexture(GL_TEXTURE_2D, 0); int state = 0; int len = 0; if(s->vdp.vdpgl_surf){ s->vdp.VDPAUGetSurfaceivNV(s->vdp.vdpgl_surf, GL_SURFACE_STATE_NV, 1, &len, &state ); } if(state == GL_SURFACE_MAPPED_NV) s->vdp.VDPAUUnmapSurfacesNV(1, &s->vdp.vdpgl_surf); s->vdp.checkInterop(frame->hwctx.device, frame->hwctx.get_proc_address); check_mixer(s, frame); s->vdp.mixerRender(frame->surface); s->vdp.VDPAUMapSurfacesNV(1, &s->vdp.vdpgl_surf); glBindTexture(GL_TEXTURE_2D, s->vdp.textures[0]); hw_vdpau_frame_unref(&s->vdp.lastFrame); s->vdp.lastFrame = hw_vdpau_frame_copy(frame); } /** * @brief Checks if the vdpau-GL interoperability is initialized with the same hw contexts * and reinitializes it if needed */ void state_vdpau::checkInterop(VdpDevice device, VdpGetProcAddress *get_proc_address){ if(this->device != device || this->get_proc_address != get_proc_address){ uninitInterop(); initInterop(device, get_proc_address); } } /** * @brief Initializes vdpau-GL interoperability */ void state_vdpau::initInterop(VdpDevice device, VdpGetProcAddress *get_proc_address){ if(interopInitialized) uninitInterop(); VDPAUInitNV(NV_CAST(device), (void *) get_proc_address); this->device = device; this->get_proc_address = get_proc_address; vdp_funcs_load(&funcs, device, get_proc_address); interopInitialized = true; } /** * @brief Uninitializes vdpau-GL interoperability */ void state_vdpau::uninitInterop(){ if(!interopInitialized) return; VDPAUFiniNV(); device = 0; get_proc_address = nullptr; interopInitialized = false; mixerInitialized = false; //VDPAUFiniNV() unmaps and unregisters all surfaces automatically vdpgl_surf = 0; } /** * @brief Initializes state_vdpau */ bool state_vdpau::init(){ loadVdpGlFuncs(); initialized = true; glGenTextures(4, textures); hw_vdpau_frame_init(&lastFrame); return true; } /** * @brief Uninitializes state_vdpau */ void state_vdpau::uninit(){ glDeleteTextures(4, textures); for(int i = 0; i < 4; i++){ textures[i] = 0; } } /** * @brief Loads GL functions for the GL_NV_vdpau_interop extension */ bool state_vdpau::loadVdpGlFuncs(){ if (!strstr((const char *) glGetString(GL_EXTENSIONS), "GL_NV_vdpau_interop")) { log_msg(LOG_LEVEL_ERROR, "VDPAU interop NOT supported!\n"); log_msg(LOG_LEVEL_DEBUG, "Available extensions:%s\n", glGetString(GL_EXTENSIONS)); return false; } VDPAUInitNV = (void (*)(const void *, const void *)) glXGetProcAddressARB( (const GLubyte *) "glVDPAUInitNV"); VDPAUFiniNV = (void (*)(void)) glXGetProcAddressARB( (const GLubyte *) "glVDPAUFiniNV"); VDPAURegisterVideoSurfaceNV = (vdpauSurfaceNV (*)(const void *, GLenum, GLsizei, const uint *)) glXGetProcAddressARB( (const GLubyte *) "glVDPAURegisterVideoSurfaceNV"); VDPAURegisterOutputSurfaceNV = (vdpauSurfaceNV (*)(const void *, GLenum, GLsizei, const uint *)) glXGetProcAddressARB( (const GLubyte *) "glVDPAURegisterOutputSurfaceNV"); VDPAUIsSurfaceNV = (GLboolean (*)(vdpauSurfaceNV)) glXGetProcAddressARB( (const GLubyte *) "glVDPAUIsSurfaceNV"); VDPAUUnregisterSurfaceNV = (void (*)(vdpauSurfaceNV)) glXGetProcAddressARB( (const GLubyte *) "glVDPAUUnregisterSurfaceNV"); VDPAUGetSurfaceivNV = (void (*)(vdpauSurfaceNV, GLenum, GLsizei, GLsizei *, int *)) glXGetProcAddressARB( (const GLubyte *) "glVDPAUGetSurfaceivNV"); VDPAUSurfaceAccessNV = (void (*)(vdpauSurfaceNV, GLenum)) glXGetProcAddressARB( (const GLubyte *) "glVDPAUSurfaceAccessNV"); VDPAUMapSurfacesNV = (void (*)(GLsizei, const vdpauSurfaceNV *)) glXGetProcAddressARB( (const GLubyte *) "glVDPAUMapSurfacesNV"); VDPAUUnmapSurfacesNV = (void (*)(GLsizei, const vdpauSurfaceNV *)) glXGetProcAddressARB( (const GLubyte *) "glVDPAUUnmapSurfacesNV"); return true; } #endif static void gl_draw(double ratio, double bottom_offset, bool double_buf) { float bottom; gl_check_error(); glDrawBuffer(double_buf ? GL_BACK : GL_FRONT); if (double_buf) { glClear(GL_COLOR_BUFFER_BIT); } glLoadIdentity( ); glTranslatef( 0.0f, 0.0f, -1.35f ); /* Reflect that we may have higher texture than actual data * if we use DXT and source height was not divisible by 4 * In normal case, there would be 1.0 */ bottom = 1.0f - bottom_offset; gl_check_error(); glBegin(GL_QUADS); /* Front Face */ /* Bottom Left Of The Texture and Quad */ glTexCoord2f( 0.0f, bottom ); glVertex2f( -1.0f, -1/ratio); /* Bottom Right Of The Texture and Quad */ glTexCoord2f( 1.0f, bottom ); glVertex2f( 1.0f, -1/ratio); /* Top Right Of The Texture and Quad */ glTexCoord2f( 1.0f, 0.0f ); glVertex2f( 1.0f, 1/ratio); /* Top Left Of The Texture and Quad */ glTexCoord2f( 0.0f, 0.0f ); glVertex2f( -1.0f, 1/ratio); glEnd( ); gl_check_error(); } static void glut_close_callback(void) { gl->should_exit_main_loop = true; gl->window = -1; exit_uv(0); } static int display_gl_get_property(void *state, int property, void *val, size_t *len) { UNUSED(state); codec_t codecs[] = { #ifdef HWACC_VDPAU HW_VDPAU, #endif UYVY, RGBA, RGB, DXT1, DXT1_YUV, DXT5 }; enum interlacing_t supported_il_modes[] = {PROGRESSIVE, INTERLACED_MERGED, SEGMENTED_FRAME}; int rgb_shift[] = {0, 8, 16}; switch (property) { case DISPLAY_PROPERTY_CODECS: if(sizeof(codecs) <= *len) { memcpy(val, codecs, sizeof(codecs)); } else { return FALSE; } *len = sizeof(codecs); break; case DISPLAY_PROPERTY_RGB_SHIFT: if(sizeof(rgb_shift) > *len) { return FALSE; } memcpy(val, rgb_shift, sizeof(rgb_shift)); *len = sizeof(rgb_shift); break; case DISPLAY_PROPERTY_BUF_PITCH: *(int *) val = PITCH_DEFAULT; *len = sizeof(int); break; case DISPLAY_PROPERTY_SUPPORTED_IL_MODES: if(sizeof(supported_il_modes) <= *len) { memcpy(val, supported_il_modes, sizeof(supported_il_modes)); } else { return FALSE; } *len = sizeof(supported_il_modes); break; default: return FALSE; } return TRUE; } static void display_gl_done(void *state) { struct state_gl *s = (struct state_gl *) state; assert(s->magic == MAGIC_GL); //pthread_join(s->thread_id, NULL); if(s->window != -1) { glutDestroyWindow(s->window); } if (s->PHandle_uyvy) glDeleteProgram(s->PHandle_uyvy); if (s->PHandle_dxt) glDeleteProgram(s->PHandle_dxt); if (s->PHandle_dxt5) glDeleteProgram(s->PHandle_dxt5); if (s->texture_display) glDeleteTextures(1, &s->texture_display); if (s->texture_uyvy) glDeleteTextures(1, &s->texture_uyvy); if (s->fbo_id) glDeleteFramebuffersEXT(1, &s->fbo_id); if (s->pbo_id) glDeleteBuffersARB(1, &s->pbo_id); while (s->free_frame_queue.size() > 0) { struct video_frame *buffer = s->free_frame_queue.front(); s->free_frame_queue.pop(); vf_free(buffer); } while (s->frame_queue.size() > 0) { struct video_frame *buffer = s->frame_queue.front(); s->frame_queue.pop(); vf_free(buffer); } vf_free(s->current_frame); if (s->syphon_spout) { #ifdef HAVE_SYPHON syphon_server_unregister(s->syphon_spout); #elif defined HAVE_SPOUT spout_sender_unregister(s->syphon_spout); #endif } gl = nullptr; delete s; } static struct video_frame * display_gl_getf(void *state) { struct state_gl *s = (struct state_gl *) state; assert(s->magic == MAGIC_GL); lock_guard lock(s->lock); while (s->free_frame_queue.size() > 0) { struct video_frame *buffer = s->free_frame_queue.front(); s->free_frame_queue.pop(); if (video_desc_eq(video_desc_from_frame(buffer), s->current_desc)) { return buffer; } else { vf_free(buffer); } } struct video_frame *buffer = vf_alloc_desc_data(s->current_desc); clear_video_buffer(reinterpret_cast(buffer->tiles[0].data), vc_get_linesize(buffer->tiles[0].width, buffer->color_spec), vc_get_linesize(buffer->tiles[0].width, buffer->color_spec), buffer->tiles[0].height, buffer->color_spec); return buffer; } static int display_gl_putf(void *state, struct video_frame *frame, int nonblock) { struct state_gl *s = (struct state_gl *) state; assert(s->magic == MAGIC_GL); unique_lock lk(s->lock); if(!frame) { s->should_exit_main_loop = true; // used only for GLUT (not freeglut) s->frame_queue.push(frame); lk.unlock(); s->new_frame_ready_cv.notify_one(); return 0; } if (nonblock == PUTF_DISCARD) { vf_recycle(frame); s->free_frame_queue.push(frame); return 0; } if (s->frame_queue.size() >= MAX_BUFFER_SIZE && nonblock == PUTF_NONBLOCK) { vf_recycle(frame); s->free_frame_queue.push(frame); return 1; } s->frame_consumed_cv.wait(lk, [s]{return s->frame_queue.size() < MAX_BUFFER_SIZE;}); s->frame_queue.push(frame); lk.unlock(); s->new_frame_ready_cv.notify_one(); return 0; } static void display_gl_put_audio_frame(void *state, struct audio_frame *frame) { UNUSED(state); UNUSED(frame); } static int display_gl_reconfigure_audio(void *state, int quant_samples, int channels, int sample_rate) { UNUSED(state); UNUSED(quant_samples); UNUSED(channels); UNUSED(sample_rate); return FALSE; } static const struct video_display_info display_gl_info = { [](struct device_info **available_cards, int *count, void (**deleter)(void *)) { UNUSED(deleter); *count = 1; *available_cards = (struct device_info *) calloc(1, sizeof(struct device_info)); strcpy((*available_cards)[0].id, "gl"); strcpy((*available_cards)[0].name, "OpenGL SW display"); (*available_cards)[0].repeatable = true; }, display_gl_init, display_gl_run, display_gl_done, display_gl_getf, display_gl_putf, display_gl_reconfigure, display_gl_get_property, display_gl_put_audio_frame, display_gl_reconfigure_audio, }; static void display_gl_register_keyboard_ctl(struct module *mod) { char my_path[1024]; if (!module_get_path_str(mod, my_path, sizeof my_path)) { log_msg(LOG_LEVEL_ERROR, MODULE_NAME "Cannot register keyboard control for GL!\n"); } struct msg_universal *m = (struct msg_universal *) new_message(sizeof(struct msg_universal)); sprintf(m->text, "map #%d %s fullscreen#toggle fullscreen", (int) 'f', my_path); struct response *r = send_message_sync(get_root_module(mod), "keycontrol", (struct message *) m, 100, SEND_MESSAGE_FLAG_QUIET | SEND_MESSAGE_FLAG_NO_STORE); if (response_get_status(r) != RESPONSE_OK) { log_msg(LOG_LEVEL_ERROR, MODULE_NAME "Cannot register keyboard control for GL(error %d)!\n", response_get_status(r)); } free_response(r); } REGISTER_MODULE(gl, &display_gl_info, LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION);