changeset 16488:3191dcb27a12

hardware color-space conversion for vo_gl and vo_gl2
author reimar
date Wed, 14 Sep 2005 22:08:04 +0000
parents bafefab46f12
children 3092afa97d36
files ChangeLog DOCS/man/en/mplayer.1 TOOLS/edgedetect.fp TOOLS/emboss.fp libvo/gl_common.c libvo/gl_common.h libvo/vo_gl.c libvo/vo_gl2.c
diffstat 8 files changed, 900 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Sep 14 21:15:38 2005 +0000
+++ b/ChangeLog	Wed Sep 14 22:08:04 2005 +0000
@@ -20,6 +20,8 @@
     * for -vo gl manyfmts is now default (since it is a lot faster), use
       -vo gl:nomanyfmts if it does not work for you
     * streaming textures for -vo gl, much faster if supported (use -dr)
+    * hardware YUV to RGB conversion for -vo gl and -vo gl2, see yuv suboption
+    * support for custom fragment programs for -vo gl (see TOOLS/*.fp)
     * use libvbe from vesautils as vesa video driver
 
     Decoders:
@@ -105,6 +107,7 @@
       done playing all files
     * lots of new slave commands (check DOCS/tech/slave.txt)
     * lots of new information provided by the -identify option
+    * fixed ugly looking OSD with -vo gl2 and MMX
 
 
   pre7try2: August 26, 2005
--- a/DOCS/man/en/mplayer.1	Wed Sep 14 21:15:38 2005 +0000
+++ b/DOCS/man/en/mplayer.1	Wed Sep 14 22:08:04 2005 +0000
@@ -2714,7 +2714,7 @@
 .RSss
 0: Use power-of-two textures (default).
 .br
-1: Use texture_rectangle.
+1: Use texture_rectangle, not compatible with hardware YUV conversion.
 .br
 2: Use texture_non_power_of_two.
 .REss
@@ -2729,6 +2729,36 @@
 This limits the framerate to (horizontal refresh rate / n).
 Requires GLX_SGI_swap_control support to work.
 With some (most/all?) implementations this only works in fullscreen mode.
+.IPs yuv=<n>
+Select the type of YUV to RGB conversion.
+.RSss
+0: Use software conversion (default).
+Compatible with all OpenGL version.
+Provides brightness, contrast and saturation control.
+.br
+1: Use register combiners.
+This uses an nVidia-specific extension (GL_NV_register_combiners).
+At least three texture units are needed.
+Provides saturation and hue control.
+.br
+2: Use a fragment program.
+Needs GL_ARB_fragment_program extension and at least three texture units.
+Provides brightness, contrast, saturation and hue control.
+.br
+3: Use a fragment program using the POW instruction.
+Needs GL_ARB_fragment_program extension and at least three texture units.
+Provides brightness, contrast, saturation, hue and gamma control.
+Gamma can also be set independently for red, green and blue.
+Method 4 is usually faster.
+.br
+4: Use a fragment program with additional lookup.
+Needs GL_ARB_fragment_program extension and at least four texture units.
+Provides brightness, contrast, saturation, hue and gamma control.
+Gamma can also be set independently for red, green and blue.
+.REss
+.IPs customprog=<filename>
+Load a custom fragment program from <filename>.
+See TOOLS/edgedect.fp for an example.
 .REss
 .RE
 .PD 1
@@ -2737,6 +2767,39 @@
 .B gl2\ \ \ \ 
 OpenGL video output driver, second generation.
 Supports OSD and videos larger than the maximum texture size.
+.PD 0
+.RSs
+.IPs (no)glfinish
+Call glFinish() before swapping buffers.
+Slower but in some cases more correct output (default: enabled).
+.IPs yuv=<n>
+Select the type of YUV to RGB conversion.
+.RSss
+0: Use software conversion (default).
+Compatible with all OpenGL version.
+Provides brightness, contrast and saturation control.
+.br
+1: Use register combiners.
+This uses an nVidia-specific extension (GL_NV_register_combiners).
+At least three texture units are needed.
+Provides saturation and hue control.
+.br
+2: Use a fragment program.
+Needs GL_ARB_fragment_program extension and at least three texture units.
+Provides brightness, contrast, saturation and hue control.
+.br
+3: Use a fragment program using the POW instruction.
+Needs GL_ARB_fragment_program extension and at least three texture units.
+Provides brightness, contrast, saturation, hue and gamma control.
+Gamma can also be set independently for red, green and blue.
+Method 4 is usually faster.
+.br
+4: Use a fragment program with additional lookup.
+Needs GL_ARB_fragment_program extension and at least four texture units.
+Provides brightness, contrast, saturation, hue and gamma control.
+Gamma can also be set independently for red, green and blue.
+.REss
+.REss
 .
 .TP
 .B null\ \ \ 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TOOLS/edgedetect.fp	Wed Sep 14 22:08:04 2005 +0000
@@ -0,0 +1,41 @@
+!!ARBfp1.0
+# Custom YUV->RGB conversion program for MPlayer's -vo gl.
+# Copyleft (C) Reimar Döffinger, 2005
+# Licensed under the GNU GPL v2
+# Usage: mplayer -vo gl:yuv=4:customprog=edgedetect.fp
+# This is some custom edge-detect like effect.
+# Try adjusting the gamma!
+# program.env[0].xy contains the size of one source texel
+PARAM sizes = program.env[0];
+TEMP res, y, u, v, pos, tmp;
+TEX y, fragment.texcoord[0], texture[0], 2D;
+MUL y, y, {4, 4, 4, 0};
+ADD pos, fragment.texcoord[0], sizes.xwww; # texel to the right
+TEX tmp, pos, texture[0], 2D;
+SUB y, y, tmp;
+SUB pos, fragment.texcoord[0], sizes.xwww; # texel to the left
+TEX tmp, pos, texture[0], 2D;
+SUB y, y, tmp;
+ADD pos, fragment.texcoord[0], sizes.wyww; # texel... umm.. above?
+TEX tmp, pos, texture[0], 2D;
+SUB y, y, tmp;
+SUB pos, fragment.texcoord[0], sizes.wyww; # texel... umm.. below?
+TEX tmp, pos, texture[0], 2D;
+SUB y, y, tmp;
+MAD res, y, {2, 2, 2, 0}, {0.5, 0.5, 0.5, 0};
+# now do the normal YUV -> RGB conversion
+MAD res, res, {1.164, 1.164, 1.164, 0}, {-0.87416, 0.53133, -1.08599, 0};
+TEX u, fragment.texcoord[1], texture[1], 2D;
+MAD res, u, {0, -0.391, 2.018, 0}, res;
+TEX v, fragment.texcoord[2], texture[2], 2D;
+MAD res, v, {1.596, -0.813, 0, 0}, res;
+# do gamma texture lookup
+ADD res.a, res.a, 0.125;
+TEX res.r, res.raaa, texture[3], 2D;
+ADD res.a, res.a, 0.25;
+TEX res.g, res.gaaa, texture[3], 2D;
+ADD res.a, res.a, 0.25;
+TEX res.b, res.baaa, texture[3], 2D;
+# move res into result, this allows easily commenting out some parts.
+ADD result.color, res, {0, 0, 0, 0};
+END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TOOLS/emboss.fp	Wed Sep 14 22:08:04 2005 +0000
@@ -0,0 +1,33 @@
+!!ARBfp1.0
+# Custom YUV->RGB conversion program for MPlayer's -vo gl.
+# Copyleft (C) Reimar Döffinger, 2005
+# Licensed under the GNU GPL v2
+# Usage: mplayer -vo gl:yuv=4:customprog=emboss.fp
+# This is an emboss effect.
+PARAM sizes = program.env[0];
+TEMP res, y, u, v, xdiff, ydiff, pos, tmp;
+TEX y, fragment.texcoord[0], texture[0], 2D;
+SUB pos, fragment.texcoord[0], sizes.xwww;
+TEX tmp, pos, texture[0], 2D;
+SUB xdiff, y, tmp;
+MAD xdiff, xdiff, {0.5, 0.5, 0.5, 0}, {0.5, 0.5, 0.5, 0};
+SUB pos, fragment.texcoord[0], sizes.wyww;
+TEX tmp, pos, texture[0], 2D;
+SUB ydiff, y, tmp;
+MAD res, ydiff, {0.8660, 0.8660, 0.8660, 0}, xdiff;
+# now do the normal YUV -> RGB conversion
+MAD res, res, {1.164, 1.164, 1.164, 0}, {-0.87416, 0.53133, -1.08599, 0};
+TEX u, fragment.texcoord[1], texture[1], 2D;
+MAD res, u, {0, -0.391, 2.018, 0}, res;
+TEX v, fragment.texcoord[2], texture[2], 2D;
+MAD res, v, {1.596, -0.813, 0, 0}, res;
+# do gamma texture lookup
+ADD res.a, res.a, 0.125;
+TEX res.r, res.raaa, texture[3], 2D;
+ADD res.a, res.a, 0.25;
+TEX res.g, res.gaaa, texture[3], 2D;
+ADD res.a, res.a, 0.25;
+TEX res.b, res.baaa, texture[3], 2D;
+# move res into result, this allows easily commenting out some parts.
+ADD result.color, res, {0, 0, 0, 0};
+END
--- a/libvo/gl_common.c	Wed Sep 14 21:15:38 2005 +0000
+++ b/libvo/gl_common.c	Wed Sep 14 22:08:04 2005 +0000
@@ -1,5 +1,14 @@
+/**
+ * Common OpenGL routines.
+ * Copyleft (C) Reimar Döffinger <Reimar.Doeffinger@stud.uni-karlsruhe.de>, 2005
+ * Licensend under the GNU GPL v2.
+ * Special thanks go to the xine team and Matthias Hopf, whose video_out_opengl.c
+ * gave me lots of good ideas.
+ */
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
+#include <math.h>
 #include "gl_common.h"
 
 void (APIENTRY *GenBuffers)(GLsizei, GLuint *);
@@ -18,6 +27,8 @@
 void (APIENTRY *ActiveTexture)(GLenum);
 void (APIENTRY *BindTexture)(GLenum, GLuint);
 void (APIENTRY *MultiTexCoord2f)(GLenum, GLfloat, GLfloat);
+void (APIENTRY *GenPrograms)(GLsizei, GLuint *);
+void (APIENTRY *DeletePrograms)(GLsizei, const GLuint *);
 void (APIENTRY *BindProgram)(GLenum, GLuint);
 void (APIENTRY *ProgramString)(GLenum, GLenum, GLsizei, const GLvoid *);
 void (APIENTRY *ProgramEnvParameter4f)(GLenum, GLuint, GLfloat, GLfloat,
@@ -249,6 +260,16 @@
   MultiTexCoord2f = getProcAddress("glMultiTexCoord2f");
   if (!MultiTexCoord2f)
     MultiTexCoord2f = getProcAddress("glMultiTexCoord2fARB");
+  GenPrograms = getProcAddress("glGenPrograms");
+  if (!GenPrograms)
+    GenPrograms = getProcAddress("glGenProgramsARB");
+  if (!GenPrograms)
+    GenPrograms = getProcAddress("glGenProgramsNV");
+  DeletePrograms = getProcAddress("glDeletePrograms");
+  if (!DeletePrograms)
+    DeletePrograms = getProcAddress("glDeleteProgramsARB");
+  if (!DeletePrograms)
+    DeletePrograms = getProcAddress("glDeleteProgramsNV");
   BindProgram = getProcAddress("glBindProgram");
   if (!BindProgram)
     BindProgram = getProcAddress("glBindProgramARB");
@@ -374,6 +395,301 @@
 }
 
 /**
+ * \brief Setup register combiners for YUV to RGB conversion.
+ * \param uvcos used for saturation and hue adjustment
+ * \param uvsin used for saturation and hue adjustment
+ */
+static void glSetupYUVCombiners(float uvcos, float uvsin) {
+  GLfloat ucoef[4];
+  GLfloat vcoef[4];
+  GLint i;
+  glGetIntegerv(GL_MAX_GENERAL_COMBINERS_NV, &i);
+  if (i < 2)
+    mp_msg(MSGT_VO, MSGL_ERR,
+           "[gl] 2 general combiners needed for YUV combiner support (found %i)\n", i);
+  glGetIntegerv (GL_MAX_TEXTURE_UNITS, &i);
+  if (i < 3)
+    mp_msg(MSGT_VO, MSGL_ERR,
+           "[gl] 3 texture units needed for YUV combiner support (found %i)\n", i);
+  if (!CombinerInput || !CombinerOutput ||
+      !CombinerParameterfv || !CombinerParameteri) {
+    mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Combiner functions missing!\n");
+    return;
+  }
+  ucoef[0] = 0 * uvcos + 1.403 * uvsin;
+  vcoef[0] = 0 * uvsin + 1.403 * uvcos;
+  ucoef[1] = -0.344 * uvcos + -0.714 * uvsin;
+  vcoef[1] = -0.344 * uvsin + -0.714 * uvcos;
+  ucoef[2] = 1.770 * uvcos + 0 * uvsin;
+  vcoef[2] = 1.770 * uvsin + 0 * uvcos;
+  ucoef[3] = 0;
+  vcoef[3] = 0;
+  // Coefficients (probably) must be in [0, 1] range, whereas they originally
+  // are in [-2, 2] range, so here comes the trick:
+  // First put them in the [-0.5, 0.5] range, then add 0.5.
+  // This 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;
+  }
+  CombinerParameterfv(GL_CONSTANT_COLOR0_NV, ucoef);
+  CombinerParameterfv(GL_CONSTANT_COLOR1_NV, vcoef);
+
+  // UV first, like this green component cannot overflow
+  CombinerInput(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV,
+                GL_TEXTURE1, GL_HALF_BIAS_NORMAL_NV, GL_RGB);
+  CombinerInput(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV,
+                GL_CONSTANT_COLOR0_NV, GL_HALF_BIAS_NORMAL_NV, GL_RGB);
+  CombinerInput(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV,
+                GL_TEXTURE2, GL_HALF_BIAS_NORMAL_NV, GL_RGB);
+  CombinerInput(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV,
+                GL_CONSTANT_COLOR1_NV, GL_HALF_BIAS_NORMAL_NV, GL_RGB);
+  CombinerOutput(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);
+
+  // stage 2
+  CombinerInput(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, GL_SPARE0_NV,
+                GL_SIGNED_IDENTITY_NV, GL_RGB);
+  CombinerInput(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, GL_ZERO,
+                 GL_UNSIGNED_INVERT_NV, GL_RGB);
+  CombinerInput(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_C_NV,
+                GL_TEXTURE0, GL_SIGNED_IDENTITY_NV, GL_RGB);
+  CombinerInput(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_D_NV, GL_ZERO,
+                GL_UNSIGNED_INVERT_NV, GL_RGB);
+  CombinerOutput(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
+  CombinerParameteri(GL_NUM_GENERAL_COMBINERS_NV, 2);
+}
+
+static const char *yuv_prog_template =
+  "!!ARBfp1.0\n"
+  "TEMP res, y, u, v;"
+  "TEX y, fragment.texcoord[0], texture[0], 2D;"
+  "MAD res, y, {%.4f, %.4f, %.4f}, {%.4f, %.4f, %.4f};"
+  "TEX u, fragment.texcoord[1], texture[1], 2D;"
+  "MAD res, u, {%.4f, %.4f, %.4f}, res;"
+  "TEX v, fragment.texcoord[2], texture[2], 2D;"
+  "MAD result.color, v, {%.4f, %.4f, %.4f}, res;"
+  "END";
+
+static const char *yuv_pow_prog_template =
+  "!!ARBfp1.0\n"
+  "TEMP res, y, u, v;"
+  "TEX y, fragment.texcoord[0], texture[0], 2D;"
+  "MAD res, y, {%.4f, %.4f, %.4f}, {%.4f, %.4f, %.4f};"
+  "TEX u, fragment.texcoord[1], texture[1], 2D;"
+  "MAD res, u, {%.4f, %.4f, %.4f}, res;"
+  "TEX v, fragment.texcoord[2], texture[2], 2D;"
+  "MAD_SAT res, v, {%.4f, %.4f, %.4f}, res;"
+  "POW result.color.r, res.r, %.4f.r;"
+  "POW result.color.g, res.g, %.4f.g;"
+  "POW result.color.b, res.b, %.4f.b;"
+  "END";
+
+static const char *yuv_lookup_prog_template =
+  "!!ARBfp1.0\n"
+  "TEMP res, y, u, v;"
+  "TEX y, fragment.texcoord[0], texture[0], 2D;"
+  "MAD res, y, {%.4f, %.4f, %.4f, 0}, {%.4f, %.4f, %.4f, 0};"
+  "TEX u, fragment.texcoord[1], texture[1], 2D;"
+  "MAD res, u, {%.4f, %.4f, %.4f, 0}, res;"
+  "TEX v, fragment.texcoord[2], texture[2], 2D;"
+  "MAD res, v, {%.4f, %.4f, %.4f, 0}, res;"
+  "ADD res.a, res.a, 0.125;"
+  "TEX result.color.r, res.raaa, texture[3], 2D;"
+  "ADD res.a, res.a, 0.25;"
+  "TEX result.color.g, res.gaaa, texture[3], 2D;"
+  "ADD res.a, res.a, 0.25;"
+  "TEX result.color.b, res.baaa, texture[3], 2D;"
+  "END";
+
+/**
+ * \brief setup a fragment program that will do YUV->RGB conversion
+ * \param brightness brightness adjustment offset
+ * \param contrast contrast adjustment factor
+ * \param uvcos used for saturation and hue adjustment
+ * \param uvsin used for saturation and hue adjustment
+ * \param lookup use fragment program that uses texture unit 4 to
+ *               do additional conversion via lookup.
+ */
+static void glSetupYUVFragprog(float brightness, float contrast,
+                        float uvcos, float uvsin, float rgamma,
+                        float ggamma, float bgamma, int type) {
+  char yuv_prog[1000];
+  const char *prog_template = yuv_prog_template;
+  int lookup = 0;
+  GLint i;
+  // this is the conversion matrix, with y, u, v factors
+  // for red, green, blue and the constant offsets
+  float ry, ru, rv, rc;
+  float gy, gu, gv, gc;
+  float by, bu, bv, bc;
+  switch (type) {
+    case YUV_CONVERSION_FRAGMENT_POW:
+      prog_template = yuv_pow_prog_template;
+      break;
+    case YUV_CONVERSION_FRAGMENT_LOOKUP:
+      prog_template = yuv_lookup_prog_template;
+      lookup = 1;
+      break;
+  }
+  glGetIntegerv (GL_MAX_TEXTURE_UNITS, &i);
+  if (i < 3)
+    mp_msg(MSGT_VO, MSGL_ERR,
+           "[gl] 3 texture units needed for YUV fragment support (found %i)\n", i);
+  if (lookup && i < 4)
+    mp_msg(MSGT_VO, MSGL_ERR,
+           "[gl] 4 texture units needed for YUV fragment support with lookup (found %i)\n", i);
+  if (!ProgramString) {
+    mp_msg(MSGT_VO, MSGL_FATAL, "[gl] ProgramString function missing!\n");
+    return;
+  }
+  ry = 1.164 * contrast;
+  gy = 1.164 * contrast;
+  by = 1.164 * contrast;
+  ru = 0 * uvcos + 1.596 * uvsin;
+  rv = 0 * uvsin + 1.596 * uvcos;
+  gu = -0.391 * uvcos + -0.813 * uvsin;
+  gv = -0.391 * uvsin + -0.813 * uvcos;
+  bu = 2.018 * uvcos + 0 * uvsin;
+  bv = 2.018 * uvsin + 0 * uvcos;
+  rc = (-16 * ry + (-128) * ru + (-128) * rv) / 255.0 + brightness;
+  gc = (-16 * gy + (-128) * gu + (-128) * gv) / 255.0 + brightness;
+  bc = (-16 * by + (-128) * bu + (-128) * bv) / 255.0 + brightness;
+  rgamma = 1.0 / rgamma;
+  ggamma = 1.0 / ggamma;
+  bgamma = 1.0 / bgamma;
+  snprintf(yuv_prog, 1000, prog_template, ry, gy, by, rc, gc, bc, ru, gu, bu,
+           rv, gv, bv, rgamma, bgamma, bgamma);
+  ProgramString(GL_FRAGMENT_PROGRAM, GL_PROGRAM_FORMAT_ASCII,
+                strlen(yuv_prog), yuv_prog);
+  glGetIntegerv(GL_PROGRAM_ERROR_POSITION, &i);
+  if (i != -1)
+    mp_msg(MSGT_VO, MSGL_ERR,
+      "[gl] Error compiling fragment program, make sure your card supports\n"
+      "GL_ARB_fragment_program (use glxinfo to check).%.10s\n", &yuv_prog[i]);
+}
+
+/**
+ * \brief little helper function to create a lookup table for gamma
+ * \param map buffer to create map into
+ * \param size size of buffer
+ * \param gamma gamma value
+ */
+static void gen_gamma_map(unsigned char *map, int size, float gamma) {
+  int i;
+  gamma = 1.0 / gamma;
+  for (i = 0; i < size; i++) {
+    float tmp = (float)i / (size - 1.0);
+    tmp = pow(tmp, gamma);
+    if (tmp > 1.0) tmp = 1.0;
+    if (tmp < 0.0) tmp = 0.0;
+    map[i] = 255 * tmp;
+  }
+}
+
+//! resolution of texture for gamma lookup table
+#define LOOKUP_RES 512
+
+/**
+ * \brief setup YUV->RGB conversion
+ * \param brightness brightness adjustment offset
+ * \param contrast contrast adjustment factor
+ * \param hue hue adjustment angle
+ * \param saturation saturation adjustment factor
+ * \param rgamma gamma value for red channel
+ * \param ggamma gamma value for green channel
+ * \param bgamma gamma value for blue channel
+ * \param type YUV conversion type
+ */
+void glSetupYUVConversion(int type, float brightness, float contrast,
+                          float hue, float saturation,
+                          float rgamma, float ggamma, float bgamma) {
+  float uvcos = saturation * cos(hue);
+  float uvsin = saturation * sin(hue);
+  switch (type) {
+    case YUV_CONVERSION_COMBINERS:
+      glSetupYUVCombiners(uvcos, uvsin);
+      break;
+    case YUV_CONVERSION_FRAGMENT_LOOKUP:
+      {
+        unsigned char lookup_data[4 * LOOKUP_RES];
+        gen_gamma_map(lookup_data, LOOKUP_RES, rgamma);
+        gen_gamma_map(&lookup_data[LOOKUP_RES], LOOKUP_RES, ggamma);
+        gen_gamma_map(&lookup_data[2 * LOOKUP_RES], LOOKUP_RES, bgamma);
+        ActiveTexture(GL_TEXTURE3);
+        glCreateClearTex(GL_TEXTURE_2D, GL_LUMINANCE8, GL_LINEAR,
+                         LOOKUP_RES, 4, 0);
+        glUploadTex(GL_TEXTURE_2D, GL_LUMINANCE, GL_UNSIGNED_BYTE, lookup_data,
+                    LOOKUP_RES, 0, 0, LOOKUP_RES, 4, 0);
+        ActiveTexture(GL_TEXTURE0);
+      }
+    case YUV_CONVERSION_FRAGMENT:
+    case YUV_CONVERSION_FRAGMENT_POW:
+      glSetupYUVFragprog(brightness, contrast, uvcos, uvsin,
+                         rgamma, ggamma, bgamma, type);
+      break;
+  }
+}
+
+/**
+ * \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
+ */
+void inline glEnableYUVConversion(GLenum target, int type) {
+  if (type <= 0) return;
+  ActiveTexture(GL_TEXTURE1);
+  glEnable(target);
+  ActiveTexture(GL_TEXTURE2);
+  glEnable(target);
+  switch (type) {
+    case YUV_CONVERSION_COMBINERS:
+      glEnable(GL_REGISTER_COMBINERS_NV);
+      break;
+    case YUV_CONVERSION_FRAGMENT_LOOKUP:
+      ActiveTexture(GL_TEXTURE3);
+      glEnable(GL_TEXTURE_2D);
+    case YUV_CONVERSION_FRAGMENT_POW:
+    case YUV_CONVERSION_FRAGMENT:
+      glEnable(GL_FRAGMENT_PROGRAM);
+      break;
+  }
+  ActiveTexture(GL_TEXTURE0);
+}
+
+/**
+ * \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
+ */
+void inline glDisableYUVConversion(GLenum target, int type) {
+  if (type <= 0) return;
+  ActiveTexture(GL_TEXTURE1);
+  glDisable(target);
+  ActiveTexture(GL_TEXTURE2);
+  glDisable(target);
+  switch (type) {
+    case YUV_CONVERSION_COMBINERS:
+      glDisable(GL_REGISTER_COMBINERS_NV);
+      break;
+    case YUV_CONVERSION_FRAGMENT_LOOKUP:
+      ActiveTexture(GL_TEXTURE3);
+      glDisable(GL_TEXTURE_2D);
+    case YUV_CONVERSION_FRAGMENT_POW:
+    case YUV_CONVERSION_FRAGMENT:
+      glDisable(GL_FRAGMENT_PROGRAM);
+      break;
+  }
+  ActiveTexture(GL_TEXTURE0);
+}
+
+/**
  * \brief draw a texture part at given 2D coordinates
  * \param x screen top coordinate
  * \param y screen left coordinate
@@ -386,21 +702,40 @@
  * \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 set, also draw the textures from units 1 and 2
  */
 void glDrawTex(GLfloat x, GLfloat y, GLfloat w, GLfloat h,
                GLfloat tx, GLfloat ty, GLfloat tw, GLfloat th,
-               int sx, int sy, int rect_tex) {
+               int sx, int sy, int rect_tex, int is_yv12) {
+  GLfloat tx2 = tx / 2, ty2 = ty / 2, tw2 = tw / 2, th2 = th / 2;
   if (!rect_tex) {
     tx /= sx; ty /= sy; tw /= sx; th /= sy;
+    tx2 = tx, ty2 = ty, tw2 = tw, th2 = th;
   }
   glBegin(GL_QUADS);
   glTexCoord2f(tx, ty);
+  if (is_yv12) {
+    MultiTexCoord2f(GL_TEXTURE1, tx2, ty2);
+    MultiTexCoord2f(GL_TEXTURE2, tx2, ty2);
+  }
   glVertex2f(x, y);
   glTexCoord2f(tx, ty + th);
+  if (is_yv12) {
+    MultiTexCoord2f(GL_TEXTURE1, tx2, ty2 + th2);
+    MultiTexCoord2f(GL_TEXTURE2, tx2, ty2 + th2);
+  }
   glVertex2f(x, y + h);
   glTexCoord2f(tx + tw, ty + th);
+  if (is_yv12) {
+    MultiTexCoord2f(GL_TEXTURE1, tx2 + tw2, ty2 + th2);
+    MultiTexCoord2f(GL_TEXTURE2, tx2 + tw2, ty2 + th2);
+  }
   glVertex2f(x + w, y + h);
   glTexCoord2f(tx + tw, ty);
+  if (is_yv12) {
+    MultiTexCoord2f(GL_TEXTURE1, tx2 + tw2, ty2);
+    MultiTexCoord2f(GL_TEXTURE2, tx2 + tw2, ty2);
+  }
   glVertex2f(x + w, y);
   glEnd();
 }
--- a/libvo/gl_common.h	Wed Sep 14 21:15:38 2005 +0000
+++ b/libvo/gl_common.h	Wed Sep 14 22:08:04 2005 +0000
@@ -17,6 +17,72 @@
 #include "x11_common.h"
 #endif
 
+// 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_MAX_GENERAL_COMBINERS_NV
+#define GL_MAX_GENERAL_COMBINERS_NV 0x854D
+#endif
+#ifndef GL_NUM_GENERAL_COMBINERS_NV
+#define GL_NUM_GENERAL_COMBINERS_NV 0x854E
+#endif
+#ifndef GL_CONSTANT_COLOR0_NV
+#define GL_CONSTANT_COLOR0_NV 0x852A
+#endif
+#ifndef GL_CONSTANT_COLOR1_NV
+#define GL_CONSTANT_COLOR1_NV 0x852B
+#endif
+#ifndef GL_COMBINER0_NV
+#define GL_COMBINER0_NV 0x8550
+#endif
+#ifndef GL_COMBINER1_NV
+#define GL_COMBINER1_NV 0x8551
+#endif
+#ifndef GL_VARIABLE_A_NV
+#define GL_VARIABLE_A_NV 0x8523
+#endif
+#ifndef GL_VARIABLE_B_NV
+#define GL_VARIABLE_B_NV 0x8524
+#endif
+#ifndef GL_VARIABLE_C_NV
+#define GL_VARIABLE_C_NV 0x8525
+#endif
+#ifndef GL_VARIABLE_D_NV
+#define GL_VARIABLE_D_NV 0x8526
+#endif
+#ifndef GL_UNSIGNED_INVERT_NV
+#define GL_UNSIGNED_INVERT_NV 0x8537
+#endif
+#ifndef GL_HALF_BIAS_NORMAL_NV
+#define GL_HALF_BIAS_NORMAL_NV 0x853A
+#endif
+#ifndef GL_SIGNED_IDENTITY_NV
+#define GL_SIGNED_IDENTITY_NV 0x853C
+#endif
+#ifndef GL_SCALE_BY_FOUR_NV
+#define GL_SCALE_BY_FOUR_NV 0x853F
+#endif
+#ifndef GL_DISCARD_NV
+#define GL_DISCARD_NV 0x8530
+#endif
+#ifndef GL_SPARE0_NV
+#define GL_SPARE0_NV 0x852E
+#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
@@ -56,6 +122,15 @@
 #ifndef GL_UNSIGNED_SHORT_1_5_5_5_REV
 #define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366
 #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
 
 void glAdjustAlignment(int stride);
 
@@ -71,7 +146,23 @@
                  int x, int y, int w, int h, int slice);
 void glDrawTex(GLfloat x, GLfloat y, GLfloat w, GLfloat h,
                GLfloat tx, GLfloat ty, GLfloat tw, GLfloat th,
-               int sx, int sy, int rect_tex);
+               int sx, int sy, int rect_tex, int is_yv12);
+
+//! do not use YUV conversion, this should always stay 0
+#define YUV_CONVERSION_NONE 0
+//! use nVidia specific register combiners for YUV conversion
+#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
+void glSetupYUVConversion(int type, float brightness, float contrast,
+                          float hue, float saturation,
+                          float rgamma, float ggamma, float bgamma);
+void inline glEnableYUVConversion(GLenum target, int type);
+void inline glDisableYUVConversion(GLenum target, int type);
 
 //! could not set new window, will continue drawing into the old one.
 #define SET_WINDOW_FAILED -1
@@ -104,6 +195,8 @@
 extern void (APIENTRY *ActiveTexture)(GLenum);
 extern void (APIENTRY *BindTexture)(GLenum, GLuint);
 extern void (APIENTRY *MultiTexCoord2f)(GLenum, GLfloat, GLfloat);
+extern void (APIENTRY *GenPrograms)(GLsizei, GLuint *);
+extern void (APIENTRY *DeletePrograms)(GLsizei, const GLuint *);
 extern void (APIENTRY *BindProgram)(GLenum, GLuint);
 extern void (APIENTRY *ProgramString)(GLenum, GLenum, GLsizei, const GLvoid *);
 extern void (APIENTRY *ProgramEnvParameter4f)(GLenum, GLuint, GLfloat, GLfloat,
--- a/libvo/vo_gl.c	Wed Sep 14 21:15:38 2005 +0000
+++ b/libvo/vo_gl.c	Wed Sep 14 22:08:04 2005 +0000
@@ -1,6 +1,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <math.h>
 
 #include "config.h"
 #include "mp_msg.h"
@@ -56,10 +57,12 @@
 static int osdtexCnt;
 
 static int use_aspect;
+static int use_yuv;
 static int use_rectangle;
 static int err_shown;
 static uint32_t image_width;
 static uint32_t image_height;
+static uint32_t image_format;
 static int many_fmts;
 static int use_glFinish;
 static int swap_interval;
@@ -69,8 +72,19 @@
 static GLenum gl_type;
 static GLuint gl_buffer;
 static int gl_buffersize;
+static GLuint fragprog;
+static GLuint uvtexs[2];
+static GLuint lookupTex;
+static char *custom_prog;
 
 static int int_pause;
+static int eq_bri = 0;
+static int eq_cont = 0;
+static int eq_sat = 0;
+static int eq_hue = 0;
+static int eq_rgamma = 0;
+static int eq_ggamma = 0;
+static int eq_bgamma = 0;
 
 static uint32_t texture_width;
 static uint32_t texture_height;
@@ -126,6 +140,39 @@
   }
 }
 
+#define MAX_CUSTOM_PROG_SIZE (1024 * 1024)
+static void update_yuvconv() {
+  float bri = eq_bri / 100.0;
+  float cont = (eq_cont + 100) / 100.0;
+  float hue = eq_hue / 100.0 * 3.1415927;
+  float sat = (eq_sat + 100) / 100.0;
+  float rgamma = exp(log(8.0) * eq_rgamma / 100.0);
+  float ggamma = exp(log(8.0) * eq_ggamma / 100.0);
+  float bgamma = exp(log(8.0) * eq_bgamma / 100.0);
+  glSetupYUVConversion(use_yuv, bri, cont, hue, sat, rgamma, ggamma, bgamma);
+  if (custom_prog) {
+    FILE *f = fopen(custom_prog, "r");
+    if (!f)
+      mp_msg(MSGT_VO, MSGL_WARN,
+             "[gl] Could not read customprog %s\n", custom_prog);
+    else {
+      int i;
+      char *prog = calloc(1, MAX_CUSTOM_PROG_SIZE + 1);
+      fread(prog, 1, MAX_CUSTOM_PROG_SIZE, f);
+      fclose(f);
+      ProgramString(GL_FRAGMENT_PROGRAM, GL_PROGRAM_FORMAT_ASCII,
+                   strlen(prog), prog);
+      glGetIntegerv(GL_PROGRAM_ERROR_POSITION, &i);
+      if (i != -1)
+        mp_msg(MSGT_VO, MSGL_ERR,
+          "[gl] Error in custom program at pos %i (%.20s)\n", i, &prog[i]);
+      free(prog);
+    }
+    ProgramEnvParameter4f(GL_FRAGMENT_PROGRAM, 0,
+               1.0 / texture_width, 1.0 / texture_height, 0, 0);
+  }
+}
+
 /**
  * \brief remove all OSD textures and display-lists, thus clearing it.
  */
@@ -144,6 +191,15 @@
  * \brief uninitialize OpenGL context, freeing textures, buffers etc.
  */
 static void uninitGl() {
+  if (DeletePrograms && fragprog)
+    DeletePrograms(1, &fragprog);
+  fragprog = 0;
+  if (uvtexs[0] || uvtexs[1])
+    glDeleteTextures(2, uvtexs);
+  uvtexs[0] = uvtexs[1] = 0;
+  if (lookupTex)
+    glDeleteTextures(1, &lookupTex);
+  lookupTex = 0;
   clearOSD();
   if (DeleteBuffers && gl_buffer)
     DeleteBuffers(1, &gl_buffer);
@@ -157,6 +213,7 @@
  */
 static int initGl(uint32_t d_width, uint32_t d_height) {
   osdtexCnt = 0; gl_buffer = 0; gl_buffersize = 0; err_shown = 0;
+  fragprog = 0; uvtexs[0] = 0; uvtexs[1] = 0; lookupTex = 0;
   texSize(image_width, image_height, &texture_width, &texture_height);
 
   glDisable(GL_BLEND); 
@@ -169,6 +226,31 @@
   mp_msg(MSGT_VO, MSGL_V, "[gl] Creating %dx%d texture...\n",
           texture_width, texture_height);
 
+  if (image_format == IMGFMT_YV12) {
+    glGenTextures(2, uvtexs);
+    ActiveTexture(GL_TEXTURE1);
+    BindTexture(gl_target, uvtexs[0]);
+    glCreateClearTex(gl_target, gl_texfmt, GL_LINEAR,
+                     texture_width / 2, texture_height / 2, 128);
+    ActiveTexture(GL_TEXTURE2);
+    BindTexture(gl_target, uvtexs[1]);
+    glCreateClearTex(gl_target, gl_texfmt, GL_LINEAR,
+                     texture_width / 2, texture_height / 2, 128);
+    switch (use_yuv) {
+      case YUV_CONVERSION_FRAGMENT_LOOKUP:
+        glGenTextures(1, &lookupTex);
+        ActiveTexture(GL_TEXTURE3);
+        glBindTexture(GL_TEXTURE_2D, lookupTex);
+      case YUV_CONVERSION_FRAGMENT_POW:
+      case YUV_CONVERSION_FRAGMENT:
+        GenPrograms(1, &fragprog);
+        BindProgram(GL_FRAGMENT_PROGRAM, fragprog);
+        break;
+    }
+    ActiveTexture(GL_TEXTURE0);
+    BindTexture(gl_target, 0);
+    update_yuvconv();
+  }
   glCreateClearTex(gl_target, gl_texfmt, GL_LINEAR,
                    texture_width, texture_height, 0);
 
@@ -189,6 +271,7 @@
 {
 	image_height = height;
 	image_width = width;
+	image_format = format;
 	glFindFormat(format, NULL, &gl_texfmt, &gl_format, &gl_type);
 
 	int_pause = 0;
@@ -374,12 +457,12 @@
   // render alpha
   glBlendFunc(GL_ZERO, GL_SRC_ALPHA);
   BindTexture(gl_target, osdatex[osdtexCnt]);
-  glDrawTex(x0, y0, w, h, 0, 0, w, h, sx, sy, use_rectangle == 1);
+  glDrawTex(x0, y0, w, h, 0, 0, w, h, sx, sy, use_rectangle == 1, 0);
 #endif
   // render OSD
   glBlendFunc (GL_ONE, GL_ONE);
   BindTexture(gl_target, osdtex[osdtexCnt]);
-  glDrawTex(x0, y0, w, h, 0, 0, w, h, sx, sy, use_rectangle == 1);
+  glDrawTex(x0, y0, w, h, 0, 0, w, h, sx, sy, use_rectangle == 1, 0);
   glEndList();
 
   osdtexCnt++;
@@ -404,9 +487,14 @@
 //  glBindTexture(GL_TEXTURE_2D, texture_id);
 
   glColor3f(1,1,1);
+  if (image_format == IMGFMT_YV12)
+    glEnableYUVConversion(gl_target, use_yuv);
   glDrawTex(0, 0, texture_width, texture_height,
             0, 0, texture_width, texture_height,
-            texture_width, texture_height, use_rectangle == 1);
+            texture_width, texture_height,
+            use_rectangle == 1, image_format == IMGFMT_YV12);
+  if (image_format == IMGFMT_YV12)
+    glDisableYUVConversion(gl_target, use_yuv);
 
   if (osdtexCnt > 0) {
     // set special rendering parameters
@@ -443,6 +531,17 @@
 //static inline uint32_t draw_slice_x11(uint8_t *src[], uint32_t slice_num)
 static int draw_slice(uint8_t *src[], int stride[], int w,int h,int x,int y)
 {
+  glUploadTex(gl_target, gl_format, gl_type, src[0], stride[0],
+              x, y, w, h, slice_height);
+  if (image_format == IMGFMT_YV12) {
+    ActiveTexture(GL_TEXTURE1);
+    glUploadTex(gl_target, gl_format, gl_type, src[1], stride[1],
+                x / 2, y / 2, w / 2, h / 2, slice_height);
+    ActiveTexture(GL_TEXTURE2);
+    glUploadTex(gl_target, gl_format, gl_type, src[2], stride[2],
+                x / 2, y / 2, w / 2, h / 2, slice_height);
+    ActiveTexture(GL_TEXTURE0);
+  }
 	return 0;
 }
 
@@ -476,6 +575,15 @@
     err_shown = 1;
     return VO_FALSE;
   }
+  if (mpi->imgfmt == IMGFMT_YV12) {
+    // YV12
+    mpi->flags |= MP_IMGFLAG_COMMON_STRIDE | MP_IMGFLAG_COMMON_PLANE;
+    mpi->stride[0] = mpi->width;
+    mpi->planes[1] = mpi->planes[0] + mpi->stride[0] * mpi->height;
+    mpi->stride[1] = mpi->width >> 1;
+    mpi->planes[2] = mpi->planes[1] + mpi->stride[1] * (mpi->height >> 1);
+    mpi->stride[2] = mpi->width >> 1;
+  }
   mpi->flags |= MP_IMGFLAG_DIRECT;
   return VO_TRUE;
 }
@@ -493,6 +601,17 @@
   }
   glUploadTex(gl_target, gl_format, gl_type, data, mpi->stride[0],
               mpi->x, mpi->y, mpi->w, mpi->h, slice);
+  if (mpi->imgfmt == IMGFMT_YV12) {
+    data += mpi->planes[1] - mpi->planes[0];
+    ActiveTexture(GL_TEXTURE1);
+    glUploadTex(gl_target, gl_format, gl_type, data, mpi->stride[1],
+                mpi->x / 2, mpi->y / 2, mpi->w / 2, mpi->h / 2, slice);
+    data += mpi->planes[2] - mpi->planes[1];
+    ActiveTexture(GL_TEXTURE2);
+    glUploadTex(gl_target, gl_format, gl_type, data, mpi->stride[2],
+                mpi->x / 2, mpi->y / 2, mpi->w / 2, mpi->h / 2, slice);
+    ActiveTexture(GL_TEXTURE0);
+  }
   if (mpi->flags & MP_IMGFLAG_DIRECT)
     BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
   return VO_TRUE;
@@ -513,6 +632,8 @@
       caps |= VFCAP_OSD;
     if ((format == IMGFMT_RGB24) || (format == IMGFMT_RGBA))
         return caps;
+    if (use_yuv && format == IMGFMT_YV12)
+        return caps;
     if (many_fmts &&
          glFindFormat(format, NULL, NULL, NULL, NULL))
         return caps;
@@ -526,6 +647,8 @@
   if ( !vo_config_count ) return;
   uninitGl();
   releaseGlContext(&gl_vinfo, &gl_context);
+  if (custom_prog) free(custom_prog);
+  custom_prog = NULL;
 #ifdef GL_WIN32
   vo_w32_uninit();
 #else
@@ -540,8 +663,10 @@
   {"aspect",       OPT_ARG_BOOL, &use_aspect,   NULL},
   {"slice-height", OPT_ARG_INT,  &slice_height, (opt_test_f)int_non_neg},
   {"rectangle",    OPT_ARG_INT,  &use_rectangle,(opt_test_f)int_non_neg},
+  {"yuv",          OPT_ARG_INT,  &use_yuv,      (opt_test_f)int_non_neg},
   {"glfinish",     OPT_ARG_BOOL, &use_glFinish, NULL},
   {"swapinterval", OPT_ARG_INT,  &swap_interval,NULL},
+  {"customprog",   OPT_ARG_MSTRZ,&custom_prog,  NULL},
   {NULL}
 };
 
@@ -552,10 +677,12 @@
     use_osd = 1;
     scaled_osd = 0;
     use_aspect = 1;
+    use_yuv = 0;
     use_rectangle = 0;
     use_glFinish = 0;
     swap_interval = 1;
     slice_height = 4;
+    custom_prog = NULL;
     if (subopt_parse(arg, subopts) != 0) {
       mp_msg(MSGT_VO, MSGL_FATAL,
               "\n-vo gl command line help:\n"
@@ -579,6 +706,16 @@
               "    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"
+              "  yuv=<n>\n"
+              "    0: use software YUV to RGB conversion.\n"
+              "    1: use register combiners (nVidia only).\n"
+              "    2: use fragment program.\n"
+              "    3: use fragment program with gamma correction.\n"
+              "    4: use fragment program with gamma correction via lookup.\n"
+              "  customprog=<filename>\n"
+              "    use a custom YUV conversion program\n"
+              "  customtex=<filename>\n"
+              "    use a custom YUV conversion lookup texture\n"
               "\n" );
       return -1;
     }
@@ -632,6 +769,81 @@
     if (!use_aspect) return VO_NOTIMPL;
     resize (vo_dwidth, vo_dheight);
     return VO_TRUE;
+  case VOCTRL_GET_EQUALIZER:
+    if (image_format == IMGFMT_YV12) {
+      va_list va;
+      int *value;
+      va_start(va, data);
+      value = va_arg(va, int *);
+      va_end(va);
+      if (strcasecmp(data, "brightness") == 0) {
+        *value = eq_bri;
+        if (use_yuv == YUV_CONVERSION_COMBINERS) break; // not supported
+      } else if (strcasecmp(data, "contrast") == 0) {
+        *value = eq_cont;
+        if (use_yuv == YUV_CONVERSION_COMBINERS) break; // not supported
+      } else if (strcasecmp(data, "saturation") == 0) {
+        *value = eq_sat;
+      } else if (strcasecmp(data, "hue") == 0) {
+        *value = eq_hue;
+      } else if (strcasecmp(data, "gamma") ==  0) {
+        *value = eq_rgamma;
+        if (use_yuv == YUV_CONVERSION_COMBINERS ||
+            use_yuv == YUV_CONVERSION_FRAGMENT) break; // not supported
+      } else if (strcasecmp(data, "red_gamma") ==  0) {
+        *value = eq_rgamma;
+        if (use_yuv == YUV_CONVERSION_COMBINERS ||
+            use_yuv == YUV_CONVERSION_FRAGMENT) break; // not supported
+      } else if (strcasecmp(data, "green_gamma") ==  0) {
+        *value = eq_ggamma;
+        if (use_yuv == YUV_CONVERSION_COMBINERS ||
+            use_yuv == YUV_CONVERSION_FRAGMENT) break; // not supported
+      } else if (strcasecmp(data, "blue_gamma") ==  0) {
+        *value = eq_bgamma;
+        if (use_yuv == YUV_CONVERSION_COMBINERS ||
+            use_yuv == YUV_CONVERSION_FRAGMENT) break; // not supported
+      }
+      return VO_TRUE;
+    }
+    break;
+  case VOCTRL_SET_EQUALIZER:
+    if (image_format == IMGFMT_YV12) {
+      va_list va;
+      int value;
+      va_start(va, data);
+      value = va_arg(va, int);
+      va_end(va);
+      if (strcasecmp(data, "brightness") == 0) {
+        eq_bri = value;
+        if (use_yuv == YUV_CONVERSION_COMBINERS) break; // not supported
+      } else if (strcasecmp(data, "contrast") == 0) {
+        eq_cont = value;
+        if (use_yuv == YUV_CONVERSION_COMBINERS) break; // not supported
+      } else if (strcasecmp(data, "saturation") == 0) {
+        eq_sat = value;
+      } else if (strcasecmp(data, "hue") == 0) {
+        eq_hue = value;
+      } else if (strcasecmp(data, "gamma") ==  0) {
+        eq_rgamma = eq_ggamma = eq_bgamma = value;
+        if (use_yuv == YUV_CONVERSION_COMBINERS ||
+            use_yuv == YUV_CONVERSION_FRAGMENT) break; // not supported
+      } else if (strcasecmp(data, "red_gamma") ==  0) {
+        eq_rgamma = value;
+        if (use_yuv == YUV_CONVERSION_COMBINERS ||
+            use_yuv == YUV_CONVERSION_FRAGMENT) break; // not supported
+      } else if (strcasecmp(data, "green_gamma") ==  0) {
+        eq_ggamma = value;
+        if (use_yuv == YUV_CONVERSION_COMBINERS ||
+            use_yuv == YUV_CONVERSION_FRAGMENT) break; // not supported
+      } else if (strcasecmp(data, "blue_gamma") ==  0) {
+        eq_bgamma = value;
+        if (use_yuv == YUV_CONVERSION_COMBINERS ||
+            use_yuv == YUV_CONVERSION_FRAGMENT) break; // not supported
+      }
+      update_yuvconv();
+      return VO_TRUE;
+    }
+    break;
   }
   return VO_NOTIMPL;
 }
--- a/libvo/vo_gl2.c	Wed Sep 14 21:15:38 2005 +0000
+++ b/libvo/vo_gl2.c	Wed Sep 14 22:08:04 2005 +0000
@@ -63,6 +63,8 @@
 static uint32_t texture_height;
 static int texnumx, texnumy, raw_line_len;
 static struct TexSquare * texgrid = NULL;
+static GLuint   fragprog;
+static GLuint   lookupTex;
 static GLint    gl_internal_format;
 static int      rgb_sz, r_sz, g_sz, b_sz, a_sz;
 static GLenum   gl_bitmap_format;
@@ -71,6 +73,7 @@
 
 static int      gl_bilinear=1;
 static int      gl_antialias=0;
+static int      use_yuv;
 static int      use_glFinish;
 
 static void (*draw_alpha_fnc)
@@ -83,6 +86,7 @@
 {
   GLubyte *texture;
   GLuint texobj;
+  GLuint uvtexobjs[2];
   int isTexture;
   GLfloat fx, fy, fw, fh;
   int isDirty;
@@ -159,6 +163,7 @@
     s*=2;
   texture_height=s;
 
+  if (image_format != IMGFMT_YV12)
   gl_internal_format = getInternalFormat();
 
   /* Test the max texture size */
@@ -234,11 +239,20 @@
       tsq->isDirty=GL_FALSE;
       tsq->isTexture=GL_FALSE;
       tsq->texobj=0;
+      tsq->uvtexobjs[0] = tsq->uvtexobjs[1] = 0;
       tsq->dirtyXoff=0; tsq->dirtyYoff=0; tsq->dirtyWidth=-1; tsq->dirtyHeight=-1;
 
       glGenTextures (1, &(tsq->texobj));
 
       glBindTexture (GL_TEXTURE_2D, tsq->texobj);
+      if (image_format == IMGFMT_YV12) {
+        glGenTextures(2, tsq->uvtexobjs);
+        ActiveTexture(GL_TEXTURE1);
+        glBindTexture (GL_TEXTURE_2D, tsq->uvtexobjs[0]);
+        ActiveTexture(GL_TEXTURE2);
+        glBindTexture (GL_TEXTURE_2D, tsq->uvtexobjs[1]);
+        ActiveTexture(GL_TEXTURE0);
+      }
       err = glGetError ();
       if(err==GL_INVALID_ENUM)
       {
@@ -257,6 +271,15 @@
                        texture_width, texture_height, 0);
 
       glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+      if (image_format == IMGFMT_YV12) {
+        ActiveTexture(GL_TEXTURE1);
+        glCreateClearTex(GL_TEXTURE_2D, gl_internal_format, GL_LINEAR,
+                         texture_width / 2, texture_height / 2, 0);
+        ActiveTexture(GL_TEXTURE2);
+        glCreateClearTex(GL_TEXTURE_2D, gl_internal_format, GL_LINEAR,
+                         texture_width / 2, texture_height / 2, 0);
+        ActiveTexture(GL_TEXTURE0);
+      }
 
       tsq++;
     }	/* for all texnumx */
@@ -439,6 +462,8 @@
 
   glColor3f(1.0,1.0,1.0);
 
+  if (image_format == IMGFMT_YV12)
+    glEnableYUVConversion(GL_TEXTURE_2D, use_yuv);
   for (y = 0; y < texnumy; y++)
   {
     for (x = 0; x < texnumx; x++)
@@ -451,6 +476,13 @@
       }
 
       glBindTexture (GL_TEXTURE_2D, square->texobj);
+      if (image_format == IMGFMT_YV12) {
+        ActiveTexture(GL_TEXTURE1);
+        glBindTexture (GL_TEXTURE_2D, square->uvtexobjs[0]);
+        ActiveTexture(GL_TEXTURE2);
+        glBindTexture (GL_TEXTURE_2D, square->uvtexobjs[1]);
+        ActiveTexture(GL_TEXTURE0);
+      }
       err = glGetError ();
       if(err==GL_INVALID_ENUM)
       {
@@ -486,10 +518,12 @@
 
       glDrawTex(square->fx, square->fy, square->fw, square->fh,
                 0, 0, texture_width, texture_height,
-                texture_width, texture_height, 0);
+                texture_width, texture_height, 0, image_format == IMGFMT_YV12);
       square++;
     } /* for all texnumx */
   } /* for all texnumy */
+  if (image_format == IMGFMT_YV12)
+    glDisableYUVConversion(GL_TEXTURE_2D, use_yuv);
 
   /* YES - let's catch this error ... 
    */
@@ -706,6 +740,7 @@
 
 static int initGl(uint32_t d_width, uint32_t d_height)
 {
+  fragprog = lookupTex = 0;
   if (initTextures() < 0)
     return -1;
 
@@ -714,6 +749,22 @@
   glDepthMask(GL_FALSE);
   glDisable(GL_CULL_FACE);
   glEnable (GL_TEXTURE_2D);
+  if (image_format == IMGFMT_YV12) {
+    switch (use_yuv) {
+      case YUV_CONVERSION_FRAGMENT_LOOKUP:
+        glGenTextures(1, &lookupTex);
+        ActiveTexture(GL_TEXTURE3);
+        glBindTexture(GL_TEXTURE_2D, lookupTex);
+        ActiveTexture(GL_TEXTURE0);
+        glBindTexture(GL_TEXTURE_2D, 0);
+      case YUV_CONVERSION_FRAGMENT_POW:
+      case YUV_CONVERSION_FRAGMENT:
+        GenPrograms(1, &fragprog);
+        BindProgram(GL_FRAGMENT_PROGRAM, fragprog);
+        break;
+    }
+    glSetupYUVConversion(use_yuv, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0);
+  }
 
   gl_set_antialias(0);
   gl_set_bilinear(1);
@@ -793,7 +844,7 @@
 	mp_msg(MSGT_VO, MSGL_INFO, "[gl2] You have OpenGL < 1.2 drivers, BAD (16bpp and BGR may be damaged!)\n");
   }
 
-  glFindFormat(format, &image_bpp, NULL, &gl_bitmap_format, &gl_bitmap_type);
+  glFindFormat(format, &image_bpp, &gl_internal_format, &gl_bitmap_format, &gl_bitmap_type);
 
   image_bytes=(image_bpp+7)/8;
 
@@ -908,12 +959,60 @@
 //static inline uint32_t draw_slice_x11(uint8_t *src[], uint32_t slice_num)
 static int draw_slice(uint8_t *src[], int stride[], int w,int h,int x,int y)
 {
+  int rem_h = h;
+  struct TexSquare *texline = &texgrid[y / texture_height * texnumx];
+  int subtex_y = y % texture_width;
+  while (rem_h > 0) {
+    int rem_w = w;
+    struct TexSquare *tsq = &texline[x / texture_width];
+    int subtex_x = x % texture_height;
+    int subtex_h = rem_h;
+    if (subtex_y + subtex_h > texture_height)
+      subtex_h = texture_height - subtex_y;
+    while (rem_w > 0) {
+      int subtex_w = rem_w;
+      if (subtex_x + subtex_w > texture_width)
+        subtex_w = texture_width - subtex_x;
+      ActiveTexture(GL_TEXTURE0);
+      glBindTexture(GL_TEXTURE_2D, tsq->texobj);
+      glUploadTex(GL_TEXTURE_2D, gl_bitmap_format,  gl_bitmap_type,
+                  src[0], stride[0], subtex_x, subtex_y,
+                  subtex_w, subtex_h, 0);
+      ActiveTexture(GL_TEXTURE1);
+      glBindTexture(GL_TEXTURE_2D, tsq->uvtexobjs[0]);
+      glUploadTex(GL_TEXTURE_2D, gl_bitmap_format,  gl_bitmap_type,
+                  src[1], stride[1], subtex_x, subtex_y,
+                  subtex_w / 2, subtex_h / 2, 0);
+      ActiveTexture(GL_TEXTURE2);
+      glBindTexture(GL_TEXTURE_2D, tsq->uvtexobjs[1]);
+      glUploadTex(GL_TEXTURE_2D, gl_bitmap_format,  gl_bitmap_type,
+                  src[2], stride[2], subtex_x, subtex_y,
+                  subtex_w / 2, subtex_h / 2, 0);
+      subtex_x = 0;
+      src[0] += subtex_w;
+      src[1] += subtex_w / 2;
+      src[2] += subtex_w / 2;
+      tsq++;
+      rem_w -= subtex_w;
+    }
+    subtex_y = 0;
+    src[0] += subtex_h * stride[0] - w;
+    src[1] += subtex_h / 2 * stride[1] - w / 2;
+    src[2] += subtex_h / 2 * stride[2] - w / 2;
+    texline += texnumx;
+    rem_h -= subtex_h;
+  }
+  ActiveTexture(GL_TEXTURE0);
     return 0;
 }
 
 static int
 draw_frame(uint8_t *src[])
 {
+  if (image_format == IMGFMT_YV12) {
+    mp_msg(MSGT_VO, MSGL_ERR, "[gl2] error: draw_frame called for YV12!\n");
+    return 0;
+  }
   ImageData=(unsigned char *)src[0];
   resetTexturePointers(ImageData);
   setupTextureDirtyArea(0, 0, image_width, image_height);
@@ -924,6 +1023,11 @@
 query_format(uint32_t format)
 {
     switch(format){
+    case IMGFMT_YV12:
+      if (use_yuv)
+        return VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_OSD |
+               VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_ACCEPT_STRIDE;
+      break;
 #ifdef SYS_DARWIN
     case IMGFMT_RGB32:
 #else
@@ -955,6 +1059,7 @@
 }
 
 static opt_t subopts[] = {
+  {"yuv",          OPT_ARG_INT,  &use_yuv,      (opt_test_f)int_non_neg},
   {"glfinish",     OPT_ARG_BOOL, &use_glFinish, NULL},
   {NULL}
 };
@@ -962,6 +1067,7 @@
 static int preinit(const char *arg)
 {
   // set defaults
+  use_yuv = 0;
   use_glFinish = 1;
   if (subopt_parse(arg, subopts) != 0) {
     mp_msg(MSGT_VO, MSGL_FATAL,
@@ -970,6 +1076,12 @@
             "\nOptions:\n"
             "  noglfinish\n"
             "    Do not call glFinish() before swapping buffers\n"
+            "  yuv=<n>\n"
+            "    0: use software YUV to RGB conversion.\n"
+            "    1: use register combiners (nVidia only).\n"
+            "    2: use fragment program.\n"
+            "    3: use fragment program with gamma correction.\n"
+            "    4: use fragment program with gamma correction via lookup.\n"
             "\n" );
     return -1;
   }