diff --git a/Makefile b/Makefile index c167162bc5..ac47639628 100644 --- a/Makefile +++ b/Makefile @@ -104,6 +104,23 @@ ARCH=$(COMPILE_ARCH) endif export ARCH +ifeq ($(findstring /emcc,$(CC)),/emcc) + PLATFORM=emscripten + ARCH=wasm32 + BINEXT=.js + # Dynamic linking is not supported. + USE_RENDERER_DLOPEN=0 + USE_OPENAL_DLOPEN=0 + BUILD_GAME_SO=0 + USE_CURL=0 + # Would be interesting to try to get the server working via WebRTC data channel (uses UDP). + BUILD_SERVER=0 + # gl4es translates GL 1.1 to ES 2, and Emscripten translates from ES 2 to WebGL. + USE_GL4ES=1 + # In theory gl4es supports GL 2.1, but in practice not really. + BUILD_RENDERER_OPENGL2=0 +endif + ifneq ($(PLATFORM),$(COMPILE_PLATFORM)) CROSS_COMPILING=1 else @@ -207,6 +224,10 @@ ifndef USE_FREETYPE USE_FREETYPE=0 endif +ifndef USE_GL4ES +USE_GL4ES=0 +endif + ifndef USE_INTERNAL_LIBS USE_INTERNAL_LIBS=1 endif @@ -231,6 +252,10 @@ ifndef USE_INTERNAL_JPEG USE_INTERNAL_JPEG=$(USE_INTERNAL_LIBS) endif +ifndef USE_INTERNAL_GL4ES +USE_INTERNAL_GL4ES=$(USE_INTERNAL_LIBS) +endif + ifndef USE_LOCAL_HEADERS USE_LOCAL_HEADERS=$(USE_INTERNAL_LIBS) endif @@ -275,6 +300,7 @@ OGGDIR=$(MOUNT_DIR)/libogg-1.3.3 VORBISDIR=$(MOUNT_DIR)/libvorbis-1.3.6 OPUSDIR=$(MOUNT_DIR)/opus-1.2.1 OPUSFILEDIR=$(MOUNT_DIR)/opusfile-0.9 +GL4ESDIR=$(MOUNT_DIR)/gl4es ZDIR=$(MOUNT_DIR)/zlib TOOLSDIR=$(MOUNT_DIR)/tools Q3ASMDIR=$(MOUNT_DIR)/tools/asm @@ -714,7 +740,9 @@ ifdef MINGW SHLIBCFLAGS= SHLIBLDFLAGS=-shared $(LDFLAGS) - BINEXT=.exe + ifndef BINEXT + BINEXT=.exe + endif ifeq ($(CROSS_COMPILING),0) TOOLS_BINEXT=.exe @@ -1063,6 +1091,34 @@ endif #NetBSD endif #IRIX endif #SunOS +############################################################################# +# SETUP AND BUILD -- EMSCRIPTEN +############################################################################# +ifeq ($(PLATFORM),emscripten) + OPTIMIZEVM=-O3 + OPTIMIZE=$(OPTIMIZEVM) -ffast-math + HAVE_VM_COMPILED=false + # Some of these warnings may actually be legit problems and should be fixed at some point. + BASE_CFLAGS+=-Wno-deprecated-non-prototype -Wno-dangling-else -Wno-implicit-const-int-float-conversion -Wno-misleading-indentation -Wno-format-overflow -Wno-logical-not-parentheses + DEBUG_CFLAGS=-g3 -O0 # -fsanitize=address -fsanitize=undefined + # Emscripten needs debug flags to be passed to the linker as well + DEBUG_LDFLAGS=$(DEBUG_CFLAGS) + CLIENT_CFLAGS+=-sUSE_SDL=2 + CLIENT_LDFLAGS+=\ + -O3 \ + -sALLOW_MEMORY_GROWTH \ + -sEXIT_RUNTIME=1 \ + -sFULL_ES2=1 \ + -sSTACK_SIZE=5MB \ + -sEXPORT_ES6 \ + -sEXPORT_NAME=ioquake3 \ + -sUSE_ES6_IMPORT_META \ + -sEXPORTED_RUNTIME_METHODS=FS,addRunDependency,removeRunDependency + BUILD_GAME_SO=0 + BUILD_GAME_QVM=0 +endif + + ifndef CC CC=gcc endif @@ -1157,6 +1213,14 @@ ifeq ($(USE_CURL),1) endif endif +ifeq ($(USE_GL4ES),1) + CLIENT_CFLAGS += -DUSE_GL4ES +endif + +ifeq ($(USE_INTERNAL_GL4ES),1) + BASE_CFLAGS += -I$(GL4ESDIR)/include -DNOEGL -DNOX11 -DEGL_NO_X11 -DNO_GBM +endif + ifeq ($(USE_VOIP),1) CLIENT_CFLAGS += -DUSE_VOIP SERVER_CFLAGS += -DUSE_VOIP @@ -1408,7 +1472,8 @@ all: debug release debug: @$(MAKE) targets B=$(BD) CFLAGS="$(CFLAGS) $(BASE_CFLAGS) $(DEPEND_CFLAGS)" \ OPTIMIZE="$(DEBUG_CFLAGS)" OPTIMIZEVM="$(DEBUG_CFLAGS)" \ - CLIENT_CFLAGS="$(CLIENT_CFLAGS)" SERVER_CFLAGS="$(SERVER_CFLAGS)" V=$(V) + CLIENT_CFLAGS="$(CLIENT_CFLAGS)" SERVER_CFLAGS="$(SERVER_CFLAGS)" V=$(V) \ + LDFLAGS="$(LDFLAGS) $(DEBUG_LDFLAGS)" release: @$(MAKE) targets B=$(BR) CFLAGS="$(CFLAGS) $(BASE_CFLAGS) $(DEPEND_CFLAGS)" \ @@ -1525,6 +1590,7 @@ makedirs: @$(MKDIR) $(B)/autoupdater @$(MKDIR) $(B)/client/opus @$(MKDIR) $(B)/client/vorbis + @$(MKDIR) $(B)/client/gl4es @$(MKDIR) $(B)/renderergl1 @$(MKDIR) $(B)/renderergl2 @$(MKDIR) $(B)/renderergl2/glsl @@ -1873,10 +1939,15 @@ Q3OBJ = \ ifdef MINGW Q3OBJ += \ $(B)/client/con_passive.o +else +ifeq ($(PLATFORM),emscripten) + Q3OBJ += \ + $(B)/client/con_passive.o else Q3OBJ += \ $(B)/client/con_tty.o endif +endif Q3R2OBJ = \ $(B)/renderergl2/tr_animation.o \ @@ -2062,6 +2133,83 @@ ifeq ($(ARCH),x86_64) $(B)/client/ftola.o endif +ifeq ($(USE_INTERNAL_GL4ES),1) + # Don't build this file, it has too many local variables and causes the emscripten build to throw an error on load. + # We don't need this functionality so we compile a non-functioning stub version instead, arbgenerator_stubs.c + # $(B)/client/gl4es/arbgenerator.o + + Q3OBJ += \ + $(B)/client/gl4es/arbgenerator_stubs.o \ + $(B)/client/gl4es/hardext.o \ + $(B)/client/gl4es/texture_compressed.o \ + $(B)/client/gl4es/envvars.o \ + $(B)/client/gl4es/string_utils.o \ + $(B)/client/gl4es/matheval.o \ + $(B)/client/gl4es/texture_read.o \ + $(B)/client/gl4es/depth.o \ + $(B)/client/gl4es/vertexattrib.o \ + $(B)/client/gl4es/decompress.o \ + $(B)/client/gl4es/uniform.o \ + $(B)/client/gl4es/raster.o \ + $(B)/client/gl4es/queries.o \ + $(B)/client/gl4es/gl_lookup.o \ + $(B)/client/gl4es/matvec.o \ + $(B)/client/gl4es/matrix.o \ + $(B)/client/gl4es/listrl.o \ + $(B)/client/gl4es/texture_params.o \ + $(B)/client/gl4es/glstate.o \ + $(B)/client/gl4es/enable.o \ + $(B)/client/gl4es/texture_3d.o \ + $(B)/client/gl4es/pixel.o \ + $(B)/client/gl4es/shaderconv.o \ + $(B)/client/gl4es/framebuffers.o \ + $(B)/client/gl4es/logs.o \ + $(B)/client/gl4es/drawing.o \ + $(B)/client/gl4es/list.o \ + $(B)/client/gl4es/arbhelper.o \ + $(B)/client/gl4es/pointsprite.o \ + $(B)/client/gl4es/debug.o \ + $(B)/client/gl4es/arbconverter.o \ + $(B)/client/gl4es/texenv.o \ + $(B)/client/gl4es/eval.o \ + $(B)/client/gl4es/init.o \ + $(B)/client/gl4es/gl4es.o \ + $(B)/client/gl4es/program.o \ + $(B)/client/gl4es/shader_hacks.o \ + $(B)/client/gl4es/fpe_cache.o \ + $(B)/client/gl4es/getter.o \ + $(B)/client/gl4es/line.o \ + $(B)/client/gl4es/hint.o \ + $(B)/client/gl4es/samplers.o \ + $(B)/client/gl4es/fpe.o \ + $(B)/client/gl4es/directstate.o \ + $(B)/client/gl4es/listdraw.o \ + $(B)/client/gl4es/face.o \ + $(B)/client/gl4es/array.o \ + $(B)/client/gl4es/preproc.o \ + $(B)/client/gl4es/blit.o \ + $(B)/client/gl4es/stack.o \ + $(B)/client/gl4es/fog.o \ + $(B)/client/gl4es/gles.o \ + $(B)/client/gl4es/glstub.o \ + $(B)/client/gl4es/gl4eswraps.o \ + $(B)/client/gl4es/stubs.o \ + $(B)/client/gl4es/render.o \ + $(B)/client/gl4es/texture.o \ + $(B)/client/gl4es/light.o \ + $(B)/client/gl4es/build_info.o \ + $(B)/client/gl4es/loader.o \ + $(B)/client/gl4es/fpe_shader.o \ + $(B)/client/gl4es/shader.o \ + $(B)/client/gl4es/texgen.o \ + $(B)/client/gl4es/planes.o \ + $(B)/client/gl4es/arbparser.o \ + $(B)/client/gl4es/oldprogram.o \ + $(B)/client/gl4es/buffers.o \ + $(B)/client/gl4es/stencil.o \ + $(B)/client/gl4es/blend.o +endif + ifeq ($(NEED_OPUS),1) ifeq ($(USE_INTERNAL_OPUS),1) Q3OBJ += \ @@ -2802,6 +2950,18 @@ $(B)/client/opus/%.o: $(OPUSDIR)/silk/float/%.c $(B)/client/%.o: $(OPUSFILEDIR)/src/%.c $(DO_CC) +$(B)/client/gl4es/%.o: $(GL4ESDIR)/src/gl/%.c + $(DO_CC) + +$(B)/client/gl4es/%.o: $(GL4ESDIR)/src/gl/math/%.c + $(DO_CC) + +$(B)/client/gl4es/%.o: $(GL4ESDIR)/src/gl/wrap/%.c + $(DO_CC) + +$(B)/client/gl4es/%.o: $(GL4ESDIR)/src/glx/%.c + $(DO_CC) + $(B)/client/%.o: $(ZDIR)/%.c $(DO_CC) diff --git a/README.md b/README.md index ce2999cba1..2ff5ad07fe 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Some of the major features currently implemented are: * Multiuser support on Windows systems (user specific game data is stored in "%APPDATA%\Quake3") * PNG support + * Web support via Emscripten and GL4ES * Many, many bug fixes The map editor and associated compiling tools are not included. We suggest you @@ -98,6 +99,22 @@ For macOS, building a Universal Binary 2 (macOS 10.9+, arm64, x86_64) 4. Copy the resulting ioquake3.app in /build/release-darwin-universal2 to your /Applications/ioquake3 folder. +For Web, building with Emscripten + 1. Follow the installation instructions for the Emscripten SDK including + setting up the environment with emsdk_env. + 2. Run `emmake make debug` (or release, but if you do both then you will + need to pass an extra URL parameter to the HTML file to select the + build you want). + 3. Start a web server that serves this directory and also a directory + containing your game data files (baseq3, missionpack, etc). + `python3 -m http.server` is an easy default that you may already have + installed. + 4. Open code/web/ioquake3.html?pakPathname=/path/to/your/baseq3 in a web + browser. Open the developer console to see errors and warnings. + 5. Debugging the C code is possible using a Chrome extension. For details + see https://developer.chrome.com/blog/wasm-debugging-2020 + + Installation, for *nix 1. Set the COPYDIR variable in the shell to be where you installed Quake 3 to. By default it will be /usr/local/games/quake3 if you haven't set it. @@ -151,6 +168,7 @@ Makefile.local: USE_INTERNAL_OGG - build and link against internal ogg library USE_INTERNAL_OPUS - build and link against internal opus/opusfile libraries USE_INTERNAL_VORBIS - build and link against internal Vorbis library + USE_INTERNAL_GL4ES - build and link against internal GL4ES library USE_LOCAL_HEADERS - use headers local to ioq3 instead of system ones DEBUG_CFLAGS - C compiler flags to use for building debug version COPYDIR - the target installation directory diff --git a/code/qcommon/q_platform.h b/code/qcommon/q_platform.h index 72dbfe1de1..44ece0a157 100644 --- a/code/qcommon/q_platform.h +++ b/code/qcommon/q_platform.h @@ -304,6 +304,19 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #endif +//============================================================ EMSCRIPTEN === + +#if defined(__EMSCRIPTEN__) +#define OS_STRING "emscripten" +#define ARCH_STRING "wasm32" +#define ID_INLINE inline +#define PATH_SEP '/' + +#define Q3_LITTLE_ENDIAN + +#define DLL_EXT ".wasm" +#endif + //=========================================================================== //catch missing defines in above blocks diff --git a/code/renderergl2/tr_extensions.c b/code/renderergl2/tr_extensions.c index ebc985a9e4..1f4175eac5 100644 --- a/code/renderergl2/tr_extensions.c +++ b/code/renderergl2/tr_extensions.c @@ -27,6 +27,13 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # include #endif +#ifdef USE_GL4ES +#include +#define ioq3_GetProcAddress gl4es_GetProcAddress +#else +#define ioq3_GetProcAddress SDL_GL_GetProcAddress +#endif + #include "tr_local.h" #include "tr_dsa.h" @@ -51,7 +58,7 @@ void GLimp_InitExtraExtensions(void) #undef GLE // GL function loader, based on https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a -#define GLE(ret, name, ...) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name); +#define GLE(ret, name, ...) qgl##name = (name##proc *) ioq3_GetProcAddress("gl" #name); // OpenGL 1.5 - GL_ARB_occlusion_query glRefConfig.occlusionQuery = qtrue; diff --git a/code/sdl/sdl_glimp.c b/code/sdl/sdl_glimp.c index 30986ab118..8a9b360f0a 100644 --- a/code/sdl/sdl_glimp.c +++ b/code/sdl/sdl_glimp.c @@ -35,6 +35,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "../sys/sys_local.h" #include "sdl_icon.h" +#ifdef USE_GL4ES +#include +#define ioq3_GetProcAddress gl4es_GetProcAddress +#else +#define ioq3_GetProcAddress SDL_GL_GetProcAddress +#endif + + typedef enum { RSERR_OK, @@ -244,7 +252,7 @@ static qboolean GLimp_GetProcAddresses( qboolean fixedFunction ) { #ifdef __SDL_NOGETPROCADDR__ #define GLE( ret, name, ... ) qgl##name = gl#name; #else -#define GLE( ret, name, ... ) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name); \ +#define GLE( ret, name, ... ) qgl##name = (name##proc *) ioq3_GetProcAddress("gl" #name); \ if ( qgl##name == NULL ) { \ ri.Printf( PRINT_ALL, "ERROR: Missing OpenGL function %s\n", "gl" #name ); \ success = qfalse; \ @@ -382,6 +390,10 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool ri.Printf( PRINT_ALL, "Initializing OpenGL display\n"); +#if defined(USE_GL4ES) + initialize_gl4es(); +#endif + if ( r_allowResize->integer ) flags |= SDL_WINDOW_RESIZABLE; @@ -885,9 +897,9 @@ static void GLimp_InitExtensions( qboolean fixedFunction ) { if ( r_ext_multitexture->value ) { - qglMultiTexCoord2fARB = SDL_GL_GetProcAddress( "glMultiTexCoord2fARB" ); - qglActiveTextureARB = SDL_GL_GetProcAddress( "glActiveTextureARB" ); - qglClientActiveTextureARB = SDL_GL_GetProcAddress( "glClientActiveTextureARB" ); + qglMultiTexCoord2fARB = ioq3_GetProcAddress( "glMultiTexCoord2fARB" ); + qglActiveTextureARB = ioq3_GetProcAddress( "glActiveTextureARB" ); + qglClientActiveTextureARB = ioq3_GetProcAddress( "glClientActiveTextureARB" ); if ( qglActiveTextureARB ) { @@ -923,8 +935,8 @@ static void GLimp_InitExtensions( qboolean fixedFunction ) if ( r_ext_compiled_vertex_array->value ) { ri.Printf( PRINT_ALL, "...using GL_EXT_compiled_vertex_array\n" ); - qglLockArraysEXT = ( void ( APIENTRY * )( GLint, GLint ) ) SDL_GL_GetProcAddress( "glLockArraysEXT" ); - qglUnlockArraysEXT = ( void ( APIENTRY * )( void ) ) SDL_GL_GetProcAddress( "glUnlockArraysEXT" ); + qglLockArraysEXT = ( void ( APIENTRY * )( GLint, GLint ) ) ioq3_GetProcAddress( "glLockArraysEXT" ); + qglUnlockArraysEXT = ( void ( APIENTRY * )( void ) ) ioq3_GetProcAddress( "glUnlockArraysEXT" ); if (!qglLockArraysEXT || !qglUnlockArraysEXT) { ri.Error (ERR_FATAL, "bad getprocaddress"); diff --git a/code/sys/sys_main.c b/code/sys/sys_main.c index 0113920b61..08d711292e 100644 --- a/code/sys/sys_main.c +++ b/code/sys/sys_main.c @@ -47,6 +47,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "../qcommon/q_shared.h" #include "../qcommon/qcommon.h" +#ifdef __EMSCRIPTEN__ +# include +#endif + static char binaryPath[ MAX_OSPATH ] = { 0 }; static char installPath[ MAX_OSPATH ] = { 0 }; @@ -863,10 +867,14 @@ int main( int argc, char **argv ) signal( SIGTERM, Sys_SigHandler ); signal( SIGINT, Sys_SigHandler ); +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(Com_Frame, 0, 1); +#else while( 1 ) { Com_Frame( ); } +#endif return 0; } diff --git a/code/web/ioquake3.html b/code/web/ioquake3.html new file mode 100644 index 0000000000..f0dfa89c70 --- /dev/null +++ b/code/web/ioquake3.html @@ -0,0 +1,71 @@ + +ioquake3 Emscripten demo + + + + +