#include #include #include #include "config.h" #include "mp_msg.h" #include "video_out.h" #include "video/vfcap.h" #include "video/img_format.h" #include #include #include #include #include #include #define DEFAULT_INTERNAL_FORMAT GL_RGB typedef enum { VO_GL_NO_CONV, VO_GL_REG_COMB_CONV, VO_GL_FRAG_PROG_CONV } glConvType; struct vo_buffer_priv_s { GLuint planeTexture[4]; GLuint glDisplayList; }; struct vo_priv_s { Window window; Display *display; int img_width; int img_height; int d_width; int d_height; char numPlanes; GLint planeInternalFormat[4]; GLsizei planeTextureWidth[4]; GLsizei planeTextureHeight[4]; int planeWidth[4]; int planeHeight[4]; char planeBytesPerPixel[4]; GLenum planeFormat[4]; GLenum planeType[4]; glConvType conversion; char supportYUV; char useGL12; XVisualInfo vinfo; GLXContext context; glConvType bestYUVConversion; void (APIENTRY * glCombinerParameterfv) (GLenum, const GLfloat *); void (APIENTRY * glCombinerParameteri) (GLenum, GLint); void (APIENTRY * glCombinerInput) (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum); void (APIENTRY * glCombinerOutput) (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean); void (APIENTRY * glActiveTexture) (GLenum); void (APIENTRY * glMultiTexCoord2f) (GLenum, GLfloat, GLfloat); }; static void resize (vo_instance_t * vo, int src[4]) { mp_msg (MSGT_VO, MSGL_V, "[gl] resize "); mp_msg (MSGT_VO, MSGL_V, "(%i, %i, %i, %i),", vo->x, vo->y, vo->w, vo->h); if (src) mp_msg (MSGT_VO, MSGL_V, "(%i, %i, %i, %i)\n", src[0], src[1], src[2], src[3]); else mp_msg (MSGT_VO, MSGL_V, "NULL\n"); glViewport (vo->x, vo->y, vo->w, vo->h); if (src) { glMatrixMode (GL_PROJECTION); glLoadIdentity (); // FIXME: how do we find out when we should flip ?? glTranslatef (-src[0], -src[1], 0); glOrtho (0, src[2], src[3], 0, -1, 1); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); } } static void getGLFunctions (vo_instance_t * vo) { GLint maxGeneralCombiners; GLint maxTextureUnits; vo->priv->glCombinerParameterfv = glXGetProcAddressARB ("glCombinerParameterfvNV"); vo->priv->glCombinerParameteri = glXGetProcAddressARB ("glCombinerParameteriNV"); vo->priv->glCombinerInput = glXGetProcAddressARB ("glCombinerInputNV"); vo->priv->glCombinerOutput = glXGetProcAddressARB ("glCombinerOutputNV"); vo->priv->glActiveTexture = glXGetProcAddressARB ("glActiveTextureARB"); vo->priv->glMultiTexCoord2f = glXGetProcAddressARB ("glMultiTexCoord2fARB"); glGetIntegerv (GL_MAX_GENERAL_COMBINERS_NV, &maxGeneralCombiners); glGetIntegerv (GL_MAX_TEXTURE_UNITS, &maxTextureUnits); if (maxTextureUnits >= 3 && maxGeneralCombiners >= 2) vo->priv->bestYUVConversion = VO_GL_REG_COMB_CONV; else vo->priv->bestYUVConversion = VO_GL_NO_CONV; } static int control (vo_instance_t * vo, int request, void *data, ...) { switch (request) { case VOCTRL_SET_WINDOW: vo->priv->window = *((Window *) data); if (!vo->priv->context) { vo->priv->context = glXCreateContext (vo->priv->display, &vo->priv->vinfo, NULL, True); } else glFinish (); glXMakeCurrent (vo->priv->display, vo->priv->window, vo->priv->context); return VO_TRUE; case VOCTRL_MOVE_DEST: vo->x = ((int *) data)[0]; vo->y = ((int *) data)[1]; resize (vo, NULL); return VO_TRUE; case VOCTRL_RESIZE_DEST: vo->w = ((int *) data)[0]; vo->h = ((int *) data)[1]; resize (vo, NULL); return VO_TRUE; case VOCTRL_RESIZE_SRC: resize (vo, data); return VO_TRUE; } return VO_NOTIMPL; } static unsigned int query_format (vo_instance_t * vo, unsigned int format, int width, int height) { const int supported = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_FLIP; switch (format) { case IMGFMT_RGB8: case IMGFMT_RGB15: case IMGFMT_RGB16: case IMGFMT_RGB24: case IMGFMT_RGB32: case IMGFMT_BGR8: case IMGFMT_BGR15: case IMGFMT_BGR16: case IMGFMT_BGR24: case IMGFMT_BGR32: case IMGFMT_Y800: case IMGFMT_YV12: return supported; } return 0; } static void uninit (vo_instance_t * vo) { if (vo->priv->context) glXDestroyContext (vo->priv->display, vo->priv->context); free (vo->priv); free (vo); } typedef struct { int pixelBytes; GLint internalFormat; GLenum format; GLenum type; int width_div; int height_div; } PlaneInfo; typedef struct { unsigned int format; char numPlanes; PlaneInfo plane[4]; } FormatInfo; FormatInfo formatInfos[] = { { IMGFMT_RGB8, 1, { {1, 0, GL_RGB, GL_UNSIGNED_BYTE_2_3_3_REV, 1, 1}, } }, { IMGFMT_RGB15, 1, { {2, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 1, 1}, } }, { IMGFMT_RGB16, 1, { {2, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, 1, 1}, } }, { IMGFMT_RGB24, 1, { {3, 0, GL_RGB, GL_UNSIGNED_BYTE, 1, 1}, } }, { IMGFMT_RGB32, 1, { {4, 0, GL_RGBA, GL_UNSIGNED_BYTE, 1, 1}, } }, { IMGFMT_BGR8, 1, { {1, 0, GL_RGB, GL_UNSIGNED_BYTE_3_3_2, 1, 1}, } }, { IMGFMT_BGR15, 1, { {2, 0, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 1, 1}, } }, { IMGFMT_BGR16, 1, { {2, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 1, 1}, } }, { IMGFMT_BGR24, 1, { {3, 0, GL_BGR, GL_UNSIGNED_BYTE, 1, 1}, } }, { IMGFMT_BGR32, 1, { {4, 0, GL_BGRA, GL_UNSIGNED_BYTE, 1, 1}, } }, { IMGFMT_Y800, 1, { {1, GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE, 1, 1}, } }, { IMGFMT_YV12, 3, { {1, GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE, 1, 1}, {1, GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE, 2, 2}, {1, GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE, 2, 2}, } }, { IMGFMT_444P, 3, { {1, GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE, 1, 1}, {1, GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE, 1, 1}, {1, GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE, 1, 1}, } }, { 0, 0, { {0, 0, 0, 0, 0, 0}, } } }; static void analyzeFormat (vo_instance_t * vo, unsigned int format) { int i = 0; FormatInfo *fi; while (formatInfos[i].format != 0 && formatInfos[i].format != format) i++; if (formatInfos[i].format != format) { mp_msg (MSGT_VO, MSGL_ERR, "[gl] Unknown image format. This shouldn't happen!\n"); }; fi = &formatInfos[i]; vo->priv->numPlanes = fi->numPlanes; for (i = 0; i < fi->numPlanes; i++) { vo->priv->planeWidth[i] = vo->priv->img_width / fi->plane[i].width_div; vo->priv->planeHeight[i] = vo->priv->img_height / fi->plane[i].height_div; vo->priv->planeBytesPerPixel[i] = fi->plane[i].pixelBytes; if (fi->plane[i].internalFormat != 0) vo->priv->planeInternalFormat[i] = fi->plane[i].internalFormat; else vo->priv->planeInternalFormat[i] = DEFAULT_INTERNAL_FORMAT; vo->priv->planeFormat[i] = fi->plane[i].format; vo->priv->planeType[i] = fi->plane[i].type; } } static void findTextureSizes (vo_instance_t * vo) { int i, tmp; for (i = 0; i < vo->priv->numPlanes; i++) { tmp = 1; while (tmp < vo->priv->planeWidth[i]) tmp *= 2; vo->priv->planeTextureWidth[i] = tmp; tmp = 1; while (tmp < vo->priv->planeHeight[i]) tmp *= 2; vo->priv->planeTextureHeight[i] = tmp; } } static void createTextures (vo_instance_t * vo, vo_buffer_t * buf) { int i; char *texInitData = NULL; char initVal = 0; GLfloat borderColor[4]; int size; glGenTextures (vo->priv->numPlanes, buf->priv->planeTexture); for (i = 0; i < vo->priv->numPlanes; i++) { glActiveTexture (GL_TEXTURE0 + i); glEnable (GL_TEXTURE_2D); glBindTexture (GL_TEXTURE_2D, buf->priv->planeTexture[i]); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); mp_msg (MSGT_VO, MSGL_V, "[gl] Allocating texture of size %dx%d ...\n", vo->priv->planeTextureWidth[i], vo->priv->planeTextureHeight[i]); size = vo->priv->planeTextureWidth[i] * vo->priv->planeTextureHeight[i]; texInitData = malloc (size); if (i == 0) { borderColor[0] = 0.0; borderColor[1] = 0.0; borderColor[2] = 0.0; borderColor[3] = 0.0; initVal = 0; } else { // no green borders in YUV borderColor[0] = 0.5; borderColor[1] = 0.5; borderColor[2] = 0.5; borderColor[3] = 0.0; initVal = 128; } memset (texInitData, initVal, size); glTexImage2D (GL_TEXTURE_2D, 0, vo->priv->planeInternalFormat[i], vo->priv->planeTextureWidth[i], vo->priv->planeTextureHeight[i], 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texInitData); mp_msg (MSGT_VO, MSGL_V, "[gl] glTexImage2D finished with %d\n", glGetError ()); free (texInitData); glTexParameterfv (GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); } } static void setupConversion (vo_instance_t * vo) { int i; GLfloat ucoef[] = { 0, -0.344, 1.770, 0 }; GLfloat vcoef[] = { 1.403, -0.714, 0, 0 }; switch (vo->priv->conversion) { case VO_GL_NO_CONV: break; case VO_GL_REG_COMB_CONV: // It seems that coefficient must be in [0,1] range, so here comes // the trick: // first put them in [-0.5, 0.5] range, then add 0.5; // can be undone with the HALF_BIAS and SCALE_BY_FOUR arguments // for CombinerInput and CombinerOutput for (i = 0; i < 4; i++) { ucoef[i] = ucoef[i] * 0.25 + 0.5; vcoef[i] = vcoef[i] * 0.25 + 0.5; } vo->priv->glCombinerParameterfv (GL_CONSTANT_COLOR0_NV, ucoef); vo->priv->glCombinerParameterfv (GL_CONSTANT_COLOR1_NV, vcoef); // UV first, like this green component can't overflow vo->priv->glCombinerInput (GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE1, GL_HALF_BIAS_NORMAL_NV, GL_RGB); vo->priv->glCombinerInput (GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_CONSTANT_COLOR0_NV, GL_HALF_BIAS_NORMAL_NV, GL_RGB); vo->priv->glCombinerInput (GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE2, GL_HALF_BIAS_NORMAL_NV, GL_RGB); vo->priv->glCombinerInput (GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_CONSTANT_COLOR1_NV, GL_HALF_BIAS_NORMAL_NV, GL_RGB); vo->priv->glCombinerOutput (GL_COMBINER0_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE0_NV, GL_SCALE_BY_FOUR_NV, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE); vo->priv->glCombinerInput (GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_SIGNED_IDENTITY_NV, GL_RGB); vo->priv->glCombinerInput (GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB); vo->priv->glCombinerInput (GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE0, GL_SIGNED_IDENTITY_NV, GL_RGB); vo->priv->glCombinerInput (GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB); vo->priv->glCombinerOutput (GL_COMBINER1_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE0_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE); // leave final combiner stage in default mode vo->priv->glCombinerParameteri (GL_NUM_GENERAL_COMBINERS_NV, 2); break; case VO_GL_FRAG_PROG_CONV: break; default: mp_msg (MSGT_VO, MSGL_ERR, "[gl] unknown conversion type!"); break; } } static void activateConversion (vo_instance_t * vo, vo_buffer_t * buf) { glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, buf->priv->planeTexture[0]); switch (vo->priv->conversion) { case VO_GL_NO_CONV: break; case VO_GL_REG_COMB_CONV: vo->priv->glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, buf->priv->planeTexture[0]); vo->priv->glActiveTexture (GL_TEXTURE1); glBindTexture (GL_TEXTURE_2D, buf->priv->planeTexture[2]); vo->priv->glActiveTexture (GL_TEXTURE2); glBindTexture (GL_TEXTURE_2D, buf->priv->planeTexture[1]); glEnable (GL_REGISTER_COMBINERS_NV); break; case VO_GL_FRAG_PROG_CONV: break; default: mp_msg (MSGT_VO, MSGL_ERR, "[gl] unknown conversion type!"); break; } } static void deactivateConversion (vo_instance_t * vo, vo_buffer_t * buf) { switch (vo->priv->conversion) { case VO_GL_NO_CONV: break; case VO_GL_REG_COMB_CONV: glDisable (GL_REGISTER_COMBINERS_NV); break; case VO_GL_FRAG_PROG_CONV: break; default: mp_msg (MSGT_VO, MSGL_ERR, "[gl] unknown conversion type!"); break; } } static void createDisplayList (vo_instance_t * vo, vo_buffer_t * buf) { int i; GLfloat xcov[4], ycov[4]; buf->priv->glDisplayList = glGenLists (1); for (i = 0; i < vo->priv->numPlanes; i++) { xcov[i] = (GLfloat) vo->priv->planeWidth[i] / (GLfloat) vo->priv->planeTextureWidth[i]; ycov[i] = (GLfloat) vo->priv->planeHeight[i] / (GLfloat) vo->priv->planeTextureHeight[i]; } // TODO: set this at an appropriate place to a useable default if (vo->priv->numPlanes == 3) vo->priv->conversion = vo->priv->bestYUVConversion; setupConversion (vo); glNewList (buf->priv->glDisplayList, GL_COMPILE); activateConversion (vo, buf); glBegin (GL_QUADS); for (i = 0; i < vo->priv->numPlanes; i++) glMultiTexCoord2f (GL_TEXTURE0 + i, 0, 0); glVertex2i (0, 0); for (i = 0; i < vo->priv->numPlanes; i++) glMultiTexCoord2f (GL_TEXTURE0 + i, xcov[i], 0); glVertex2i (vo->priv->img_width, 0); for (i = 0; i < vo->priv->numPlanes; i++) glMultiTexCoord2f (GL_TEXTURE0 + i, xcov[i], ycov[i]); glVertex2i (vo->priv->img_width, vo->priv->img_height); for (i = 0; i < vo->priv->numPlanes; i++) glMultiTexCoord2f (GL_TEXTURE0 + i, 0, ycov[i]); glVertex2i (0, vo->priv->img_height); glEnd (); deactivateConversion (vo, buf); glEndList (); } static int config (vo_instance_t * vo, int width, int height, int d_width, int d_height, unsigned int format) { int dest_coords[] = { 0, 0, width, height }; vo->priv->img_width = width; vo->priv->img_height = height; vo->priv->d_width = d_width; vo->priv->d_height = d_height; vo->buffer_w = width; vo->buffer_h = height; vo->w = d_width; vo->h = d_height; getGLFunctions (vo); analyzeFormat (vo, format); findTextureSizes (vo); glDisable (GL_BLEND); glDisable (GL_DEPTH_TEST); glDepthMask (GL_FALSE); glDisable (GL_CULL_FACE); glClearColor (0, 0, 0, 0); resize (vo, dest_coords); return VO_TRUE; } static void updateTexture (vo_instance_t * vo, vo_buffer_t * buf, int planeNr, int x, int y, int w, int h, unsigned char *data) { mp_msg (MSGT_VO, MSGL_DBG1, "[gl] updateTexture (%i, %i, %i, %i, %i, %x)\n", planeNr, x, y, w, h, data); glBindTexture (GL_TEXTURE_2D, buf->priv->planeTexture[planeNr]); glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, w, h, vo->priv->planeFormat[planeNr], vo->priv->planeType[planeNr], data); mp_msg (MSGT_VO, MSGL_DBG1, "[gl] glTexSubImage2D finished with %d\n", glGetError ()); } static void draw_slice (vo_instance_t * vo, vo_buffer_t * buf, unsigned char *src[], int stride[], int w, int h, int x, int y) { mp_msg (MSGT_VO, MSGL_DBG1, "[gl] draw_slice\n"); } static int frame_done (vo_instance_t * vo, vo_buffer_t * buf) { int i; mp_msg (MSGT_VO, MSGL_DBG1, "[gl] frame_done\n"); for (i = 0; i < vo->priv->numPlanes; i++) updateTexture (vo, buf, i, 0, 0, vo->priv->planeWidth[i], vo->priv->planeHeight[i], buf->planes[i]); glFlush (); return 0; } static void show_frame (vo_instance_t * vo, vo_buffer_t * buf, unsigned int time_pos) { mp_msg (MSGT_VO, MSGL_DBG1, "[gl] show_frame\n"); glCallList (buf->priv->glDisplayList); glXSwapBuffers (vo->priv->display, vo->priv->window); glClear (GL_COLOR_BUFFER_BIT); } static vo_buffer_t *get_buffer (vo_instance_t * vo) { int i; vo_buffer_t *buffer = calloc (sizeof (vo_buffer_t), 1); buffer->priv = calloc (sizeof (struct vo_buffer_priv_s), 1); createTextures (vo, buffer); createDisplayList (vo, buffer); buffer->status = 0; for (i = 0; i < vo->priv->numPlanes; i++) { buffer->planes[i] = calloc (vo->priv->planeWidth[i] * vo->priv->planeHeight[i] * vo->priv->planeBytesPerPixel[i], 1); buffer->stride[i] = vo->priv->planeWidth[i] * vo->priv->planeBytesPerPixel[i]; } return buffer; } static void release_buffer (vo_instance_t * vo, vo_buffer_t * buf) { int i; for (i = 0; i < vo->priv->numPlanes; i++) free (buf->planes[i]); glDeleteLists (buf->priv->glDisplayList, 1); glDeleteTextures (vo->priv->numPlanes, buf->priv->planeTexture); free (buf->priv); free (buf); } static vo_instance_t *preinit (char *args, void *x11_display) { vo_instance_t *vo; if (!x11_display) return NULL; // X11 not available vo = calloc (sizeof (vo_instance_t), 1); vo->priv = calloc (sizeof (struct vo_priv_s), 1); memset (vo->priv, 0, sizeof (struct vo_priv_s)); vo->priv->useGL12 = 1; vo->priv->supportYUV = 0; vo->priv->display = x11_display; XMatchVisualInfo (vo->priv->display, DefaultScreen (vo->priv->display), 24, TrueColor, &vo->priv->vinfo); #if 0 if (arg) { if (strcmp (arg, "gl11") == 0) { useGL12 = 0; } else if (strcmp (arg, "glyuv") == 0) { supportYUV = 1; } else { printf ("[gl] Unknown subdevice: %s\n", arg); return ENOSYS; } } #endif vo->buffer_flags = 0; vo->num_buffers = 0; // functions: vo->query_format = query_format; vo->control = control; vo->config = config; vo->draw_slice = draw_slice; vo->frame_done = frame_done; vo->show_frame = show_frame; vo->uninit = uninit; vo->get_buffer = get_buffer; vo->release_buffer = release_buffer; return vo; } vo_info_t vo_driver_gl = { MODULE_TYPE_VO, MODULE_VERSION, preinit, "gl", "OpenGL driver", "Reimar Döffinger", "Reimar Döffinger", NULL, VO_MODULE_CAPS_X11 | VO_MODULE_CAPS_WINDOW | VO_MODULE_CAPS_GUI, NULL, NULL, 0 };