From ee514710c5446154c0440a2b8a2bce7a4c7798d3 Mon Sep 17 00:00:00 2001 From: smallmodel <15067410+smallmodel@users.noreply.github.com> Date: Fri, 26 Jul 2024 23:57:35 +0200 Subject: [PATCH] Add Emscripten implementation from ioquake3 upstream --- code/qcommon/q_platform.h | 16 ++++ code/sdl/sdl_glimp.c | 141 ++++++++++++++++++++++++++---------- code/web/client-config.json | 47 ++++++++++++ code/web/client.html | 116 +++++++++++++++++++++++++++++ 4 files changed, 280 insertions(+), 40 deletions(-) create mode 100644 code/web/client-config.json create mode 100644 code/web/client.html diff --git a/code/qcommon/q_platform.h b/code/qcommon/q_platform.h index bc4960c5..a47f6739 100644 --- a/code/qcommon/q_platform.h +++ b/code/qcommon/q_platform.h @@ -355,6 +355,22 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #endif +//================================================================== EMSCRIPTEN === + +#ifdef __EMSCRIPTEN__ + +#define OS_STRING "emscripten" +#define ID_INLINE inline +#define PATH_SEP '/' + +#define ARCH_STRING "wasm32" + +#define Q3_LITTLE_ENDIAN + +#define DLL_EXT ".wasm" + +#endif + //================================================================== Q3VM === #ifdef Q3_VM diff --git a/code/sdl/sdl_glimp.c b/code/sdl/sdl_glimp.c index 528f3a06..af653d70 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,27 @@ 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_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_PolygonMode( GLenum face, GLenum mode ) { + // unsupported +} + /* =============== GLimp_GetProcAddresses @@ -293,6 +315,7 @@ static qboolean GLimp_GetProcAddresses( qboolean fixedFunction ) { } else { Com_Error( ERR_FATAL, "Unsupported OpenGL Version (%s), OpenGL 1.1 is required", version ); } + // Add compression-related GL functions for the renderer if ( QGL_VERSION_ATLEAST( 1, 3 ) ) { QGL_1_3_PROCS; } @@ -309,8 +332,11 @@ static qboolean GLimp_GetProcAddresses( qboolean fixedFunction ) { QGL_1_3_PROCS; QGL_1_5_PROCS; QGL_2_0_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; + qglDepthRange = GLimp_GLES_DepthRange; + qglDrawBuffer = GLimp_GLES_DrawBuffer; + qglPolygonMode = GLimp_GLES_PolygonMode; } else { Com_Error( ERR_FATAL, "Unsupported OpenGL Version (%s), OpenGL 2.0 is required", version ); } @@ -636,57 +662,91 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool if (!fixedFunction) { - 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); + int profileMask; - 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); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); - if ((SDL_glContext = SDL_GL_CreateContext(SDL_window)) == NULL) + SDL_GL_GetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, &profileMask ); + + if ( r_useOpenGLES->integer == 1 || ( r_useOpenGLES->integer == -1 && profileMask == SDL_GL_CONTEXT_PROFILE_ES ) ) { - 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, "Trying to get an OpenGL ES 2.0 context\n" ); + 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_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); - } - else - { - const char *renderer; - - ri.Printf(PRINT_ALL, "SDL_GL_CreateContext succeeded.\n"); - - if ( GLimp_GetProcAddresses( fixedFunction ) ) + SDL_glContext = SDL_GL_CreateContext( SDL_window ); + if ( !SDL_glContext ) { - renderer = (const char *)qglGetString(GL_RENDERER); + ri.Printf( PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError() ); } else { - ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed for OpenGL 3.2 core context\n" ); - renderer = NULL; - } + ri.Printf( PRINT_ALL, "SDL_GL_CreateContext succeeded.\n" ); - if (!renderer || (strstr(renderer, "Software Renderer") || strstr(renderer, "Software Rasterizer"))) + if ( !GLimp_GetProcAddresses( fixedFunction ) ) + { + ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed for OpenGL ES 2.0 context\n" ); + GLimp_ClearProcAddresses(); + SDL_GL_DeleteContext( SDL_glContext ); + SDL_glContext = NULL; + } + } + } + + if ( !SDL_glContext ) + { + 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 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 2 ); + + SDL_glContext = SDL_GL_CreateContext( SDL_window ); + if ( !SDL_glContext ) { - if ( renderer ) - ri.Printf(PRINT_ALL, "GL_RENDERER is %s, rejecting context\n", renderer); - - GLimp_ClearProcAddresses(); - 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); + ri.Printf( PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError() ); } + else + { + const char *renderer; + + ri.Printf( PRINT_ALL, "SDL_GL_CreateContext succeeded.\n" ); + + if ( GLimp_GetProcAddresses( fixedFunction ) ) + { + renderer = (const char *)qglGetString( GL_RENDERER ); + } + else + { + ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed for OpenGL 3.2 core context\n" ); + renderer = NULL; + } + + if ( !renderer || strstr( renderer, "Software Renderer" ) || strstr( renderer, "Software Rasterizer" ) ) + { + if ( renderer ) + ri.Printf(PRINT_ALL, "GL_RENDERER is %s, rejecting context\n", renderer); + + GLimp_ClearProcAddresses(); + SDL_GL_DeleteContext( SDL_glContext ); + SDL_glContext = NULL; + } + } + } + + if ( !SDL_glContext ) + { + ri.Printf( PRINT_ALL, "Trying to get an OpenGL 2.0 context\n" ); + + 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_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 ); + SDL_glContext = NULL; } @@ -818,7 +878,7 @@ static void GLimp_InitExtensions( qboolean fixedFunction ) glConfig.textureCompression = TC_NONE; // GL_EXT_texture_compression_s3tc - if ( SDL_GL_ExtensionSupported( "GL_ARB_texture_compression" ) && + 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 ) @@ -999,6 +1059,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_ARCHIVE | CVAR_LATCH ); if( ri.Cvar_VariableIntegerValue( "com_abnormalExit" ) ) { diff --git a/code/web/client-config.json b/code/web/client-config.json new file mode 100644 index 00000000..eddf35db --- /dev/null +++ b/code/web/client-config.json @@ -0,0 +1,47 @@ +{ + "baseq3": { + "files": [ + {"src": "baseq3/pak0.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak1.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak2.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak3.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak4.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak5.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak6.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak7.pk3", "dst": "/baseq3"}, + {"src": "baseq3/pak8.pk3", "dst": "/baseq3"}, + {"src": "baseq3/vm/cgame.qvm", "dst": "/baseq3/vm"}, + {"src": "baseq3/vm/qagame.qvm", "dst": "/baseq3/vm"}, + {"src": "baseq3/vm/ui.qvm", "dst": "/baseq3/vm"} + ] + }, + "missionpack": { + "files": [ + {"src": "missionpack/pak0.pk3", "dst": "/missionpack"}, + {"src": "missionpack/pak1.pk3", "dst": "/missionpack"}, + {"src": "missionpack/pak2.pk3", "dst": "/missionpack"}, + {"src": "missionpack/pak3.pk3", "dst": "/missionpack"}, + {"src": "missionpack/vm/cgame.qvm", "dst": "/missionpack/vm"}, + {"src": "missionpack/vm/qagame.qvm", "dst": "/missionpack/vm"}, + {"src": "missionpack/vm/ui.qvm", "dst": "/missionpack/vm"} + ] + }, + "demoq3": { + "_comment": "Copy baseq3/vm/*.qvm to demoq3/vm/ as the Quake 3 demo QVMs are not compatible. However the botfiles are not fully compatible with newer QVMs.", + "files": [ + {"src": "demoq3/pak0.pk3", "dst": "/demoq3"}, + {"src": "demoq3/vm/cgame.qvm", "dst": "/demoq3/vm"}, + {"src": "demoq3/vm/qagame.qvm", "dst": "/demoq3/vm"}, + {"src": "demoq3/vm/ui.qvm", "dst": "/demoq3/vm"} + ] + }, + "tademo": { + "_comment": "Copy missionpack/vm/*.qvm to tademo/vm/ as the Team Arena demo QVMs are not compatible.", + "files": [ + {"src": "tademo/pak0.pk3", "dst": "/tademo"}, + {"src": "tademo/vm/cgame.qvm", "dst": "/tademo/vm"}, + {"src": "tademo/vm/qagame.qvm", "dst": "/tademo/vm"}, + {"src": "tademo/vm/ui.qvm", "dst": "/tademo/vm"} + ] + } +} diff --git a/code/web/client.html b/code/web/client.html new file mode 100644 index 00000000..3a23ab92 --- /dev/null +++ b/code/web/client.html @@ -0,0 +1,116 @@ + +