diff --git a/README.md b/README.md index 5e16a3da6e8..71251ba59a8 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.2+ 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 2f0dffd6759..c23fccb7f39 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 38f9919f433..996fe1a4f36 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 9c7e176bb52..ca29700cb78 100644 --- a/code/renderercommon/tr_common.h +++ b/code/renderercommon/tr_common.h @@ -80,6 +80,7 @@ extern glconfig_t glConfig; // outside of TR since it shouldn't be cleared duri extern qboolean textureFilterAnisotropic; extern int maxAnisotropy; extern float displayAspect; +extern qboolean readFormatAvailable; // // cvars diff --git a/code/renderergl1/tr_backend.c b/code/renderergl1/tr_backend.c index 760262617b5..6e2917c07fe 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, GL_CLAMP_TO_EDGE ); @@ -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 89bb88a389f..a1dc74a145e 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 7bd95194f04..3b02867e8d4 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 9603ff7169e..0a126930ad5 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 2c7d91de2e1..43aa537292f 100644 --- a/code/renderergl1/tr_init.c +++ b/code/renderergl1/tr_init.c @@ -27,6 +27,7 @@ glconfig_t glConfig; qboolean textureFilterAnisotropic = qfalse; int maxAnisotropy = 0; float displayAspect = 0.0f; +qboolean readFormatAvailable = qfalse; glstate_t glState; @@ -364,20 +365,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; @@ -756,26 +792,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; @@ -786,7 +847,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); @@ -809,7 +884,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); @@ -960,7 +1035,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; @@ -1294,6 +1369,7 @@ void RE_Shutdown( qboolean destroyWindow ) { textureFilterAnisotropic = qfalse; maxAnisotropy = 0; displayAspect = 0.0f; + readFormatAvailable = qfalse; Com_Memset( &glState, 0, sizeof( glState ) ); } diff --git a/code/renderergl1/tr_local.h b/code/renderergl1/tr_local.h index d1bb13f9eb0..e69c652c8a0 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 @@ -1598,6 +1598,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 2e78320fe63..d8a0ea68565 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 bf8303214b6..0b614d8316c 100644 --- a/code/renderergl2/tr_init.c +++ b/code/renderergl2/tr_init.c @@ -30,6 +30,7 @@ glRefConfig_t glRefConfig; qboolean textureFilterAnisotropic = qfalse; int maxAnisotropy = 0; float displayAspect = 0.0f; +qboolean readFormatAvailable = qfalse; glstate_t glState; @@ -1537,6 +1538,7 @@ void RE_Shutdown( qboolean destroyWindow ) { textureFilterAnisotropic = qfalse; maxAnisotropy = 0; displayAspect = 0.0f; + readFormatAvailable = qfalse; Com_Memset( &glState, 0, sizeof( glState ) ); } diff --git a/code/sdl/sdl_glimp.c b/code/sdl/sdl_glimp.c index d904837b526..a455151a8e9 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\n", 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.2 is required\n", 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, 2); } 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 ) @@ -965,6 +1059,20 @@ static void GLimp_InitExtensions( qboolean fixedFunction ) { ri.Printf( PRINT_ALL, "...GL_EXT_texture_filter_anisotropic 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 @@ -985,6 +1093,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" ) ) {