From cf602ff5d83c8929d18f5c3b0bb45b5988200bf1 Mon Sep 17 00:00:00 2001 From: Zack Middleton Date: Sat, 9 Nov 2019 16:55:59 -0600 Subject: [PATCH] Add OpenGL ES 1.1 support to opengl1 renderer This requires my previous commit to use vertex arrays instead of glBegin() (loosey based on ptitSeb OpenPandora port). There isn't software gamma correction so overbright may not work (as is already the case for windowed mode on GNU/Linux). Enabling unsupported cvar settings (see README) automatically displays a warning and disables the cvar. r_useOpenGLES cvar controls whether OpenGL or OpenGL ES API is used (see README). It's not achived to avoid it being saved in mods, as there is no way to have a global cvar. --- README.md | 25 +++++ code/client/cl_avi.c | 5 +- code/renderercommon/qgl.h | 1 + code/renderercommon/tr_common.h | 1 + code/renderergl1/tr_backend.c | 26 ++++- code/renderergl1/tr_cmds.c | 16 +++- code/renderergl1/tr_flares.c | 9 ++ code/renderergl1/tr_image.c | 165 ++++++++++++++++++++++++++++++-- code/renderergl1/tr_init.c | 112 ++++++++++++++++++---- code/renderergl1/tr_local.h | 6 +- code/renderergl1/tr_shade.c | 13 ++- code/renderergl2/tr_init.c | 2 + code/sdl/sdl_glimp.c | 151 +++++++++++++++++++++++++---- 13 files changed, 477 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 5e16a3da6e..b3fd426384 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,28 @@ Makefile.local: The defaults for these variables differ depending on the target platform. +# OpenGL ES support + +ioquake3's default renderer (cl_renderer opengl2) does not support OpenGL ES. + +If you build ioquake3 on a platform that uses OpenGL ES (such as Raspberry Pi), +run the game using `./build/release-*/ioquake3.* +set cl_renderer opengl1`. + +The `r_useOpenGLES` cvar controls whether to use OpenGL or OpenGL ES API. +Set to -1 for auto (default), 0 for OpenGL, and 1 for OpenGL ES. It should be +set using command line arguments, `ioquake3 +set r_useOpenGLES 0`. + +ioquake3's opengl1 renderer supports OpenGL 1.1+ and OpenGL ES 1.1 with a few limitations. +When using OpenGL ES the opengl1 renderer does not support `r_flares`[1], +`r_measureOverdraw`[2], `r_anaglyphMode`[3], `r_stereoEnabled`[3], `r_drawBuffer GL_FRONT`[3], +and `r_primitives 1/3`[4]. + +* [1] Requires glReadPixels GL_DEPTH_COMPONENT +* [2] Requires glReadPixels GL_STENCIL_INDEX +* [3] Requires glDrawBuffer +* [4] Requires glBegin + + # Console ## New cvars @@ -291,6 +313,9 @@ The defaults for these variables differ depending on the target platform. cl_aviMotionJpeg is enabled r_mode -2 - This new video mode automatically uses the desktop resolution. + r_useOpenGLES - Controls whether to use OpenGL API or + OpenGL ES API. Set to -1 for auto (default), + 0 for OpenGL, and 1 for OpenGL ES. ``` ## New commands diff --git a/code/client/cl_avi.c b/code/client/cl_avi.c index 2f0dffd675..c23fccb7f3 100644 --- a/code/client/cl_avi.c +++ b/code/client/cl_avi.c @@ -357,11 +357,12 @@ qboolean CL_OpenAVIForWriting( const char *fileName ) else afd.motionJpeg = qfalse; - // Buffers only need to store RGB pixels. + // Capture buffer stores RGB pixels or possibly RGBA when using OpenGL ES. + // Encode buffer only needs to store RGB pixels. // Allocate a bit more space for the capture buffer to account for possible // padding at the end of pixel lines, and padding for alignment #define MAX_PACK_LEN 16 - afd.cBuffer = Z_Malloc((afd.width * 3 + MAX_PACK_LEN - 1) * afd.height + MAX_PACK_LEN - 1); + afd.cBuffer = Z_Malloc((afd.width * 4 + MAX_PACK_LEN - 1) * afd.height + MAX_PACK_LEN - 1); // raw avi files have pixel lines start on 4-byte boundaries afd.eBuffer = Z_Malloc(PAD(afd.width * 3, AVI_LINE_PADDING) * afd.height); diff --git a/code/renderercommon/qgl.h b/code/renderercommon/qgl.h index 38f9919f43..996fe1a4f3 100644 --- a/code/renderercommon/qgl.h +++ b/code/renderercommon/qgl.h @@ -87,6 +87,7 @@ extern void (APIENTRYP qglUnlockArraysEXT) (void); #define QGL_1_1_FIXED_FUNCTION_PROCS \ GLE(void, AlphaFunc, GLenum func, GLclampf ref) \ GLE(void, Color4f, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) \ + GLE(void, Color4ub, GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) \ GLE(void, ColorPointer, GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) \ GLE(void, DisableClientState, GLenum cap) \ GLE(void, EnableClientState, GLenum cap) \ diff --git a/code/renderercommon/tr_common.h b/code/renderercommon/tr_common.h index c183ca8b7e..911784f80b 100644 --- a/code/renderercommon/tr_common.h +++ b/code/renderercommon/tr_common.h @@ -81,6 +81,7 @@ extern qboolean textureFilterAnisotropic; extern int maxAnisotropy; extern float displayAspect; extern qboolean haveClampToEdge; +extern qboolean readFormatAvailable; // // cvars diff --git a/code/renderergl1/tr_backend.c b/code/renderergl1/tr_backend.c index c7f73f8ee3..b47437d5c4 100644 --- a/code/renderergl1/tr_backend.c +++ b/code/renderergl1/tr_backend.c @@ -826,6 +826,7 @@ void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte * } void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean dirty) { + byte *buffer; GL_Bind( tr.scratchImage[client] ); @@ -833,7 +834,19 @@ void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) { tr.scratchImage[client]->width = tr.scratchImage[client]->uploadWidth = cols; tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = rows; - qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + + // manually convert RGBA to RGB for OpenGL ES + if ( qglesMajorVersion >= 1 ) { + buffer = ri.Hunk_AllocateTempMemory( 3 * cols * rows ); + + R_ConvertTextureFormat( data, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer ); + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, cols, rows, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer ); + + ri.Hunk_FreeTempMemory( buffer ); + } else { + qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } + qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, haveClampToEdge ? GL_CLAMP_TO_EDGE : GL_CLAMP ); @@ -842,7 +855,16 @@ void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int if (dirty) { // otherwise, just subimage upload it so that drivers can tell we are going to be changing // it and don't try and do a texture compression - qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + if ( qglesMajorVersion >= 1 ) { + buffer = ri.Hunk_AllocateTempMemory( 3 * cols * rows ); + + R_ConvertTextureFormat( data, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer ); + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer ); + + ri.Hunk_FreeTempMemory( buffer ); + } else { + qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data ); + } } } } diff --git a/code/renderergl1/tr_cmds.c b/code/renderergl1/tr_cmds.c index 89bb88a389..a1dc74a145 100644 --- a/code/renderergl1/tr_cmds.c +++ b/code/renderergl1/tr_cmds.c @@ -302,7 +302,14 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) { // if ( r_measureOverdraw->integer ) { - if ( glConfig.stencilBits < 4 ) + if ( qglesMajorVersion >= 1 ) + { + ri.Printf( PRINT_WARNING, "OpenGL ES does not support reading stencil bits to measure overdraw\n" ); + // It can be done if GL_NV_read_stencil extension exists but it's for OpenGL ES 2+ contexts. + ri.Cvar_Set( "r_measureOverdraw", "0" ); + r_measureOverdraw->modified = qfalse; + } + else if ( glConfig.stencilBits < 4 ) { ri.Printf( PRINT_ALL, "Warning: not enough stencil bits to measure overdraw: %d\n", glConfig.stencilBits ); ri.Cvar_Set( "r_measureOverdraw", "0" ); @@ -380,6 +387,13 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) { } else { + if (qglesMajorVersion >= 1 && r_anaglyphMode->integer) + { + ri.Printf( PRINT_WARNING, "OpenGL ES does not support drawing to separate buffer for anaglyph mode\n" ); + ri.Cvar_Set( "r_anaglyphMode", "0" ); + r_anaglyphMode->modified = qfalse; + } + if(r_anaglyphMode->integer) { if(r_anaglyphMode->modified) diff --git a/code/renderergl1/tr_flares.c b/code/renderergl1/tr_flares.c index 7bd95194f0..3b02867e8d 100644 --- a/code/renderergl1/tr_flares.c +++ b/code/renderergl1/tr_flares.c @@ -463,6 +463,15 @@ void RB_RenderFlares (void) { return; } + if ( r_flares->modified ) { + if ( qglesMajorVersion >= 1 ) { + ri.Printf( PRINT_WARNING, "OpenGL ES does not support reading depth to determine if flares are visible\n" ); + // It can be done if GL_NV_read_depth extension exists but it's for OpenGL ES 2+ contexts. + ri.Cvar_Set( "r_flares", "0" ); + } + r_flares->modified = qfalse; + } + if(r_flareCoeff->modified) { R_SetFlareCoeff(); diff --git a/code/renderergl1/tr_image.c b/code/renderergl1/tr_image.c index a63de58036..4406a374a2 100644 --- a/code/renderergl1/tr_image.c +++ b/code/renderergl1/tr_image.c @@ -543,6 +543,74 @@ byte mipBlendColors[16][4] = { {0,0,255,128}, }; +/* +================== +R_ConvertTextureFormat + +Convert RGBA unsigned byte to specified format and type +================== +*/ +void R_ConvertTextureFormat( const byte *in, int width, int height, GLenum format, GLenum type, byte *out ) +{ + int i, numPixels; + + numPixels = width * height; + + if ( format == GL_RGB && type == GL_UNSIGNED_BYTE ) + { + for ( i = 0; i < numPixels; i++ ) + { + *out++ = *in++; + *out++ = *in++; + *out++ = *in++; + in++; + } + } + else if ( format == GL_LUMINANCE && type == GL_UNSIGNED_BYTE ) + { + for ( i = 0; i < numPixels; i++ ) + { + *out++ = *in++; // red + in += 3; + } + } + else if ( format == GL_LUMINANCE_ALPHA && type == GL_UNSIGNED_BYTE ) + { + for ( i = 0; i < numPixels; i++ ) + { + *out++ = *in++; // red + in += 2; + *out++ = *in++; // alpha + } + } + else if ( format == GL_RGB && type == GL_UNSIGNED_SHORT_5_6_5 ) + { + unsigned short *sout = (unsigned short *)out; + + for ( i = 0; i < numPixels; i++, in += 4 ) + { + *sout++ = ( (unsigned short)( in[0] >> 3 ) << 11 ) + | ( (unsigned short)( in[1] >> 2 ) << 5 ) + | ( (unsigned short)( in[2] >> 3 ) << 0 ); + } + } + else if ( format == GL_RGBA && type == GL_UNSIGNED_SHORT_4_4_4_4 ) + { + unsigned short *sout = (unsigned short *)out; + + for ( i = 0; i < numPixels; i++, in += 4 ) + { + *sout++ = ( (unsigned short)( in[0] >> 4 ) << 12 ) + | ( (unsigned short)( in[1] >> 4 ) << 8 ) + | ( (unsigned short)( in[2] >> 4 ) << 4 ) + | ( (unsigned short)( in[3] >> 4 ) << 0 ); + } + } + else + { + ri.Error( ERR_DROP, "Unable to convert RGBA image to OpenGL format 0x%X and type 0x%X", format, type ); + } +} /* =============== @@ -556,16 +624,19 @@ static void Upload32( unsigned *data, qboolean picmip, qboolean lightMap, qboolean allowCompression, - int *format, + int *pInternalFormat, int *pUploadWidth, int *pUploadHeight ) { int samples; unsigned *scaledBuffer = NULL; unsigned *resampledBuffer = NULL; + unsigned *formatBuffer = NULL; int scaled_width, scaled_height; int i, c; byte *scan; GLenum internalFormat = GL_RGB; + GLenum format = GL_RGBA; + GLenum type = GL_UNSIGNED_BYTE; float rMax = 0, gMax = 0, bMax = 0; // @@ -689,11 +760,11 @@ static void Upload32( unsigned *data, } else { - if ( allowCompression && glConfig.textureCompression == TC_S3TC_ARB ) + if ( !qglesMajorVersion && allowCompression && glConfig.textureCompression == TC_S3TC_ARB ) { internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; } - else if ( allowCompression && glConfig.textureCompression == TC_S3TC ) + else if ( !qglesMajorVersion && allowCompression && glConfig.textureCompression == TC_S3TC ) { internalFormat = GL_RGB4_S3TC; } @@ -738,15 +809,75 @@ static void Upload32( unsigned *data, } } + // Convert image data format for OpenGL ES + if ( qglesMajorVersion >= 1 ) + { + switch ( internalFormat ) + { + case GL_LUMINANCE: + case GL_LUMINANCE8: + internalFormat = GL_LUMINANCE; + format = GL_LUMINANCE; + type = GL_UNSIGNED_BYTE; + break; + case GL_LUMINANCE_ALPHA: + case GL_LUMINANCE8_ALPHA8: + internalFormat = GL_LUMINANCE_ALPHA; + format = GL_LUMINANCE_ALPHA; + type = GL_UNSIGNED_BYTE; + break; + case GL_RGB: + case GL_RGB8: + internalFormat = GL_RGB; + format = GL_RGB; + type = GL_UNSIGNED_BYTE; + break; + case GL_RGB5: + internalFormat = GL_RGB; + format = GL_RGB; + type = GL_UNSIGNED_SHORT_5_6_5; + break; + case GL_RGBA: + case GL_RGBA8: + internalFormat = GL_RGBA; + format = GL_RGBA; + type = GL_UNSIGNED_BYTE; + break; + case GL_RGBA4: + internalFormat = GL_RGBA; + format = GL_RGBA; + type = GL_UNSIGNED_SHORT_4_4_4_4; + break; + default: + internalFormat = GL_RGBA; + format = GL_RGBA; + type = GL_UNSIGNED_BYTE; + break; + } + } + + if ( format != GL_RGBA || type != GL_UNSIGNED_BYTE ) + { + formatBuffer = ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height ); + } + // copy or resample data as appropriate for first MIP level if ( ( scaled_width == width ) && ( scaled_height == height ) ) { if (!mipmap) { - qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + if ( formatBuffer ) + { + R_ConvertTextureFormat( (byte *)data, scaled_width, scaled_height, format, type, (byte *)formatBuffer ); + qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, format, type, formatBuffer); + } + else + { + qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + } *pUploadWidth = scaled_width; *pUploadHeight = scaled_height; - *format = internalFormat; + *pInternalFormat = internalFormat; goto done; } @@ -773,9 +904,17 @@ static void Upload32( unsigned *data, *pUploadWidth = scaled_width; *pUploadHeight = scaled_height; - *format = internalFormat; + *pInternalFormat = internalFormat; - qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + if ( formatBuffer ) + { + R_ConvertTextureFormat( (byte *)scaledBuffer, scaled_width, scaled_height, format, type, (byte *)formatBuffer ); + qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, format, type, formatBuffer ); + } + else + { + qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + } if (mipmap) { @@ -797,7 +936,15 @@ static void Upload32( unsigned *data, R_BlendOverTexture( (byte *)scaledBuffer, scaled_width * scaled_height, mipBlendColors[miplevel] ); } - qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + if ( formatBuffer ) + { + R_ConvertTextureFormat( (byte *)scaledBuffer, scaled_width, scaled_height, format, type, (byte *)formatBuffer ); + qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, format, type, formatBuffer ); + } + else + { + qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer ); + } } } done: @@ -822,6 +969,8 @@ static void Upload32( unsigned *data, GL_CheckErrors(); + if ( formatBuffer != 0 ) + ri.Hunk_FreeTempMemory( formatBuffer ); if ( scaledBuffer != 0 ) ri.Hunk_FreeTempMemory( scaledBuffer ); if ( resampledBuffer != 0 ) diff --git a/code/renderergl1/tr_init.c b/code/renderergl1/tr_init.c index 2539ffa7ab..863b6effca 100644 --- a/code/renderergl1/tr_init.c +++ b/code/renderergl1/tr_init.c @@ -28,6 +28,7 @@ qboolean textureFilterAnisotropic = qfalse; int maxAnisotropy = 0; float displayAspect = 0.0f; qboolean haveClampToEdge = qfalse; +qboolean readFormatAvailable = qfalse; glstate_t glState; @@ -365,20 +366,55 @@ Return value must be freed with ri.Hunk_FreeTempMemory() byte *RB_ReadPixels(int x, int y, int width, int height, size_t *offset, int *padlen) { byte *buffer, *bufstart; - int padwidth, linelen; - GLint packAlign; - + int padwidth, linelen, bytesPerPixel; + int yin, xin, xout; + GLint packAlign, format, type; + + // OpenGL ES is only required to support reading GL_RGBA + if (qglesMajorVersion >= 1) { + if (readFormatAvailable) { + qglGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES, &format); + qglGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES, &type); + } else { + format = GL_RGBA; + type = GL_UNSIGNED_BYTE; + } + + if (format == GL_RGB && type == GL_UNSIGNED_BYTE) { + bytesPerPixel = 3; + } else { + format = GL_RGBA; + bytesPerPixel = 4; + } + } else { + format = GL_RGB; + bytesPerPixel = 3; + } + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); - - linelen = width * 3; + + linelen = width * bytesPerPixel; padwidth = PAD(linelen, packAlign); - + // Allocate a few more bytes so that we can choose an alignment we like buffer = ri.Hunk_AllocateTempMemory(padwidth * height + *offset + packAlign - 1); - + bufstart = PADP((intptr_t) buffer + *offset, packAlign); - qglReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, bufstart); - + qglReadPixels(x, y, width, height, format, GL_UNSIGNED_BYTE, bufstart); + + linelen = width * 3; + + // Convert RGBA to RGB, in place, line by line + if (format == GL_RGBA) { + for (yin = 0; yin < height; yin++) { + for (xin = 0, xout = 0; xout < linelen; xin += 4, xout += 3) { + bufstart[yin*padwidth + xout + 0] = bufstart[yin*padwidth + xin + 0]; + bufstart[yin*padwidth + xout + 1] = bufstart[yin*padwidth + xin + 1]; + bufstart[yin*padwidth + xout + 2] = bufstart[yin*padwidth + xin + 2]; + } + } + } + *offset = bufstart - buffer; *padlen = padwidth - linelen; @@ -757,26 +793,51 @@ const void *RB_TakeVideoFrameCmd( const void *data ) { const videoFrameCommand_t *cmd; byte *cBuf; - size_t memcount, linelen; + size_t memcount, bytesPerPixel, linelen, avilinelen; int padwidth, avipadwidth, padlen, avipadlen; - GLint packAlign; + int yin, xin, xout; + GLint packAlign, format, type; cmd = (const videoFrameCommand_t *)data; + // OpenGL ES is only required to support reading GL_RGBA + if (qglesMajorVersion >= 1) { + if (readFormatAvailable) { + qglGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES, &format); + qglGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE_OES, &type); + } else { + format = GL_RGBA; + type = GL_UNSIGNED_BYTE; + } + + if (format == GL_RGB && type == GL_UNSIGNED_BYTE) { + bytesPerPixel = 3; + } else { + format = GL_RGBA; + bytesPerPixel = 4; + } + } else { + format = GL_RGB; + bytesPerPixel = 3; + } + qglGetIntegerv(GL_PACK_ALIGNMENT, &packAlign); - linelen = cmd->width * 3; + linelen = cmd->width * bytesPerPixel; // Alignment stuff for glReadPixels padwidth = PAD(linelen, packAlign); padlen = padwidth - linelen; + + avilinelen = cmd->width * 3; + // AVI line padding - avipadwidth = PAD(linelen, AVI_LINE_PADDING); - avipadlen = avipadwidth - linelen; + avipadwidth = PAD(avilinelen, AVI_LINE_PADDING); + avipadlen = avipadwidth - avilinelen; cBuf = PADP(cmd->captureBuffer, packAlign); - qglReadPixels(0, 0, cmd->width, cmd->height, GL_RGB, + qglReadPixels(0, 0, cmd->width, cmd->height, format, GL_UNSIGNED_BYTE, cBuf); memcount = padwidth * cmd->height; @@ -787,7 +848,21 @@ const void *RB_TakeVideoFrameCmd( const void *data ) if(cmd->motionJpeg) { - memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, linelen * cmd->height, + // Convert RGBA to RGB, in place, line by line + if (format == GL_RGBA) { + linelen = cmd->width * 3; + padlen = padwidth - linelen; + + for (yin = 0; yin < cmd->height; yin++) { + for (xin = 0, xout = 0; xout < linelen; xin += 4, xout += 3) { + cBuf[yin*padwidth + xout + 0] = cBuf[yin*padwidth + xin + 0]; + cBuf[yin*padwidth + xout + 1] = cBuf[yin*padwidth + xin + 1]; + cBuf[yin*padwidth + xout + 2] = cBuf[yin*padwidth + xin + 2]; + } + } + } + + memcount = RE_SaveJPGToBuffer(cmd->encodeBuffer, avilinelen * cmd->height, r_aviMotionJpegQuality->integer, cmd->width, cmd->height, cBuf, padlen); ri.CL_WriteAVIVideoFrame(cmd->encodeBuffer, memcount); @@ -810,7 +885,7 @@ const void *RB_TakeVideoFrameCmd( const void *data ) *destptr++ = srcptr[2]; *destptr++ = srcptr[1]; *destptr++ = srcptr[0]; - srcptr += 3; + srcptr += bytesPerPixel; } Com_Memset(destptr, '\0', avipadlen); @@ -961,7 +1036,7 @@ void GfxInfo_f( void ) ri.Printf( PRINT_ALL, "rendering primitives: " ); primitives = r_primitives->integer; if ( primitives == 0 ) { - if ( qglLockArraysEXT ) { + if ( qglLockArraysEXT || qglesMajorVersion >= 1 ) { primitives = 2; } else { primitives = 1; @@ -1296,6 +1371,7 @@ void RE_Shutdown( qboolean destroyWindow ) { maxAnisotropy = 0; displayAspect = 0.0f; haveClampToEdge = qfalse; + readFormatAvailable = qfalse; Com_Memset( &glState, 0, sizeof( glState ) ); } diff --git a/code/renderergl1/tr_local.h b/code/renderergl1/tr_local.h index 8aac61fc17..f40ae65644 100644 --- a/code/renderergl1/tr_local.h +++ b/code/renderergl1/tr_local.h @@ -40,8 +40,8 @@ QGL_DESKTOP_1_1_FIXED_FUNCTION_PROCS; QGL_3_0_PROCS; #undef GLE -#define GL_INDEX_TYPE GL_UNSIGNED_INT -typedef unsigned int glIndex_t; +#define GL_INDEX_TYPE GL_UNSIGNED_SHORT +typedef unsigned short glIndex_t; // 14 bits // can't be increased without changing bit packing for drawsurfs @@ -1611,6 +1611,8 @@ void RE_TakeVideoFrame( int width, int height, void R_DrawElements( int numIndexes, const glIndex_t *indexes ); void VectorArrayNormalize( vec4_t *normals, unsigned int count ); +void R_ConvertTextureFormat( const byte *in, int width, int height, GLenum format, GLenum type, byte *out ); + #ifdef idppc_altivec void LerpMeshVertexes_altivec( md3Surface_t *surf, float backlerp ); void ProjectDlightTexture_altivec( void ); diff --git a/code/renderergl1/tr_shade.c b/code/renderergl1/tr_shade.c index 2e78320fe6..d8a0ea6856 100644 --- a/code/renderergl1/tr_shade.c +++ b/code/renderergl1/tr_shade.c @@ -165,8 +165,19 @@ void R_DrawElements( int numIndexes, const glIndex_t *indexes ) { primitives = r_primitives->integer; + if ( qglesMajorVersion >= 1 ) { + if ( primitives == 0 ) { + primitives = 2; + } + + if ( primitives == 1 || primitives == 3 ) { + ri.Printf( PRINT_WARNING, "OpenGL ES does not support glBegin() required for r_primitives %d\n", primitives ); + ri.Cvar_Set( "r_primitives", "0" ); + primitives = 2; + } + } // default is to use triangles if compiled vertex arrays are present - if ( primitives == 0 ) { + else if ( primitives == 0 ) { if ( qglLockArraysEXT ) { primitives = 2; } else { diff --git a/code/renderergl2/tr_init.c b/code/renderergl2/tr_init.c index 275d621e52..8af6796f52 100644 --- a/code/renderergl2/tr_init.c +++ b/code/renderergl2/tr_init.c @@ -31,6 +31,7 @@ qboolean textureFilterAnisotropic = qfalse; int maxAnisotropy = 0; float displayAspect = 0.0f; qboolean haveClampToEdge = qfalse; +qboolean readFormatAvailable = qfalse; glstate_t glState; @@ -1554,6 +1555,7 @@ void RE_Shutdown( qboolean destroyWindow ) { maxAnisotropy = 0; displayAspect = 0.0f; haveClampToEdge = qfalse; + readFormatAvailable = qfalse; Com_Memset( &glState, 0, sizeof( glState ) ); } diff --git a/code/sdl/sdl_glimp.c b/code/sdl/sdl_glimp.c index 4b284fdb1a..92d898b498 100644 --- a/code/sdl/sdl_glimp.c +++ b/code/sdl/sdl_glimp.c @@ -52,6 +52,7 @@ cvar_t *r_allowSoftwareGL; // Don't abort out if a hardware visual can't be obta cvar_t *r_allowResize; // make window resizable cvar_t *r_centerWindow; cvar_t *r_sdlDriver; +cvar_t *r_useOpenGLES; int qglMajorVersion, qglMinorVersion; int qglesMajorVersion, qglesMinorVersion; @@ -230,6 +231,52 @@ static void GLimp_DetectAvailableModes(void) SDL_free( modes ); } +/* +=============== +OpenGL ES compatibility +=============== +*/ +static void APIENTRY GLimp_GLES_ClearDepth( GLclampd depth ) { + qglClearDepthf( depth ); +} + +static void APIENTRY GLimp_GLES_ClipPlane( GLenum plane, const GLdouble *equation ) { + GLfloat values[4]; + values[0] = equation[0]; + values[1] = equation[1]; + values[2] = equation[2]; + values[3] = equation[3]; + qglClipPlanef( plane, values ); +} + +static void APIENTRY GLimp_GLES_Color3f( GLfloat red, GLfloat green, GLfloat blue ) { + qglColor4f( red, green, blue, 1.0f ); +} + +static void APIENTRY GLimp_GLES_Color4ubv( const GLubyte *v ) { + qglColor4ub( v[0], v[1], v[2], v[3] ); +} + +static void APIENTRY GLimp_GLES_DepthRange( GLclampd near_val, GLclampd far_val ) { + qglDepthRangef( near_val, far_val ); +} + +static void APIENTRY GLimp_GLES_DrawBuffer( GLenum mode ) { + // unsupported +} + +static void APIENTRY GLimp_GLES_Frustum( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val ) { + qglFrustumf( left, right, bottom, top, near_val, far_val ); +} + +static void APIENTRY GLimp_GLES_Ortho( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val ) { + qglOrthof( left, right, bottom, top, near_val, far_val ); +} + +static void APIENTRY GLimp_GLES_PolygonMode( GLenum face, GLenum mode ) { + // unsupported +} + /* =============== GLimp_GetProcAddresses @@ -288,8 +335,16 @@ static qboolean GLimp_GetProcAddresses( qboolean fixedFunction ) { QGL_1_1_FIXED_FUNCTION_PROCS; QGL_ES_1_1_PROCS; QGL_ES_1_1_FIXED_FUNCTION_PROCS; - // error so this doesn't segfault due to NULL desktop GL functions being used - Com_Error( ERR_FATAL, "Unsupported OpenGL Version: %s", version ); + + qglClearDepth = GLimp_GLES_ClearDepth; + qglClipPlane = GLimp_GLES_ClipPlane; + qglColor3f = GLimp_GLES_Color3f; + qglColor4ubv = GLimp_GLES_Color4ubv; + qglDepthRange = GLimp_GLES_DepthRange; + qglDrawBuffer = GLimp_GLES_DrawBuffer; + qglFrustum = GLimp_GLES_Frustum; + qglOrtho = GLimp_GLES_Ortho; + qglPolygonMode = GLimp_GLES_PolygonMode; } else { Com_Error( ERR_FATAL, "Unsupported OpenGL Version (%s), OpenGL 1.1 is required", version ); } @@ -503,6 +558,9 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool { int testColorBits, testDepthBits, testStencilBits; int realColorBits[3]; + int profileMask; + + SDL_GL_ResetAttributes(); // 0 - default // 1 - minus colorBits @@ -631,13 +689,34 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool SDL_SetWindowIcon( SDL_window, icon ); - if (!fixedFunction) + SDL_GL_GetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, &profileMask ); + +#ifdef __arm__ + // make r_useOpenGLES -1 always use OpenGL ES API on ARM + if( r_useOpenGLES->integer >= 1 || r_useOpenGLES->integer == -1 ) +#else + if( r_useOpenGLES->integer >= 1 || ( r_useOpenGLES->integer == -1 && profileMask == SDL_GL_CONTEXT_PROFILE_ES ) ) +#endif { - int profileMask, majorVersion, minorVersion; - SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profileMask); - SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &majorVersion); - SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &minorVersion); + if( fixedFunction ) + { + // fixed function pipeline + SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 1 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 1 ); + } + else + { + // shader pipeline + SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 2 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 0 ); + } + SDL_glContext = NULL; + } + else if( !fixedFunction ) + { ri.Printf(PRINT_ALL, "Trying to get an OpenGL 3.2 core context\n"); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); @@ -645,11 +724,11 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool if ((SDL_glContext = SDL_GL_CreateContext(SDL_window)) == NULL) { ri.Printf(PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError()); - ri.Printf(PRINT_ALL, "Reverting to default context\n"); + ri.Printf(PRINT_ALL, "Reverting to OpenGL 2.0 context\n"); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); } else { @@ -676,15 +755,19 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool SDL_GL_DeleteContext(SDL_glContext); SDL_glContext = NULL; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profileMask); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorVersion); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorVersion); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); } } } else { SDL_glContext = NULL; + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); } if ( !SDL_glContext ) @@ -815,8 +898,8 @@ static void GLimp_InitExtensions( qboolean fixedFunction ) glConfig.textureCompression = TC_NONE; // GL_EXT_texture_compression_s3tc - if ( SDL_GL_ExtensionSupported( "GL_ARB_texture_compression" ) && - SDL_GL_ExtensionSupported( "GL_EXT_texture_compression_s3tc" ) ) + if ( ( QGLES_VERSION_ATLEAST( 2, 0 ) || SDL_GL_ExtensionSupported( "GL_ARB_texture_compression" ) ) + && SDL_GL_ExtensionSupported( "GL_EXT_texture_compression_s3tc" ) ) { if ( r_ext_compressed_textures->value ) { @@ -834,6 +917,7 @@ static void GLimp_InitExtensions( qboolean fixedFunction ) } // GL_S3_s3tc ... legacy extension before GL_EXT_texture_compression_s3tc. + // (not in OpenGL ES) if (glConfig.textureCompression == TC_NONE) { if ( SDL_GL_ExtensionSupported( "GL_S3_s3tc" ) ) @@ -859,7 +943,7 @@ static void GLimp_InitExtensions( qboolean fixedFunction ) { // GL_EXT_texture_env_add glConfig.textureEnvAddAvailable = qfalse; - if ( SDL_GL_ExtensionSupported( "GL_EXT_texture_env_add" ) ) + if ( QGLES_VERSION_ATLEAST( 1, 0 ) || SDL_GL_ExtensionSupported( "GL_EXT_texture_env_add" ) ) { if ( r_ext_texture_env_add->integer ) { @@ -881,13 +965,22 @@ static void GLimp_InitExtensions( qboolean fixedFunction ) qglMultiTexCoord2fARB = NULL; qglActiveTextureARB = NULL; qglClientActiveTextureARB = NULL; - if ( SDL_GL_ExtensionSupported( "GL_ARB_multitexture" ) ) + if ( QGLES_VERSION_ATLEAST( 1, 0 ) || SDL_GL_ExtensionSupported( "GL_ARB_multitexture" ) ) { if ( r_ext_multitexture->value ) { - qglMultiTexCoord2fARB = SDL_GL_GetProcAddress( "glMultiTexCoord2fARB" ); - qglActiveTextureARB = SDL_GL_GetProcAddress( "glActiveTextureARB" ); - qglClientActiveTextureARB = SDL_GL_GetProcAddress( "glClientActiveTextureARB" ); + if ( QGLES_VERSION_ATLEAST( 1, 0 ) ) + { + qglMultiTexCoord2fARB = NULL; + qglActiveTextureARB = SDL_GL_GetProcAddress( "glActiveTexture" ); + qglClientActiveTextureARB = SDL_GL_GetProcAddress( "glClientActiveTexture" ); + } + else + { + qglMultiTexCoord2fARB = SDL_GL_GetProcAddress( "glMultiTexCoord2fARB" ); + qglActiveTextureARB = SDL_GL_GetProcAddress( "glActiveTextureARB" ); + qglClientActiveTextureARB = SDL_GL_GetProcAddress( "glClientActiveTextureARB" ); + } if ( qglActiveTextureARB ) { @@ -918,6 +1011,7 @@ static void GLimp_InitExtensions( qboolean fixedFunction ) } // GL_EXT_compiled_vertex_array + // (not in OpenGL ES) if ( SDL_GL_ExtensionSupported( "GL_EXT_compiled_vertex_array" ) ) { if ( r_ext_compiled_vertex_array->value ) @@ -976,6 +1070,20 @@ static void GLimp_InitExtensions( qboolean fixedFunction ) { ri.Printf( PRINT_ALL, "...GL_SGIS_texture_edge_clamp not found\n" ); } + + if ( QGLES_VERSION_ATLEAST( 1, 0 ) ) + { + readFormatAvailable = qfalse; + if ( QGLES_VERSION_ATLEAST( 2, 0 ) || SDL_GL_ExtensionSupported( "GL_OES_read_format" ) ) + { + ri.Printf( PRINT_ALL, "...using GL_OES_read_format\n" ); + readFormatAvailable = qtrue; + } + else + { + ri.Printf( PRINT_ALL, "...GL_OES_read_format not found\n" ); + } + } } #define R_MODE_FALLBACK 3 // 640 * 480 @@ -996,6 +1104,7 @@ void GLimp_Init( qboolean fixedFunction ) r_sdlDriver = ri.Cvar_Get( "r_sdlDriver", "", CVAR_ROM ); r_allowResize = ri.Cvar_Get( "r_allowResize", "0", CVAR_ARCHIVE | CVAR_LATCH ); r_centerWindow = ri.Cvar_Get( "r_centerWindow", "0", CVAR_ARCHIVE | CVAR_LATCH ); + r_useOpenGLES = ri.Cvar_Get( "r_useOpenGLES", "-1", CVAR_NORESTART | CVAR_LATCH ); if( ri.Cvar_VariableIntegerValue( "com_abnormalExit" ) ) {