Skip to content

Commit

Permalink
Web build support with Emscripten
Browse files Browse the repository at this point in the history
  • Loading branch information
jdarpinian committed May 3, 2024
1 parent ad107bb commit 7618f10
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 9 deletions.
164 changes: 162 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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).

This comment has been minimized.

Copy link
@kungfooman

kungfooman Jun 5, 2024

For my emscripten version I simply use WebSockets, server running in nodejs via WASM. Or do you mean WebRTC to native servers via UDP without proxy?

This comment has been minimized.

Copy link
@jdarpinian

jdarpinian Jun 6, 2024

Author

WebRTC wouldn't be able to communicate with unmodified native servers unfortunately. But it would allow using unreliable UDP (DataChannel) instead of TCP (WebSocket), eliminating delays due to retransmission or out of order delivery. It would also make peer-to-peer play possible, hosting a server in the browser.

This comment has been minimized.

Copy link
@kungfooman

kungfooman Jun 6, 2024

Potential for P2P sounds great! Didn't think about that 👍

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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -714,7 +740,9 @@ ifdef MINGW
SHLIBCFLAGS=
SHLIBLDFLAGS=-shared $(LDFLAGS)

BINEXT=.exe
ifndef BINEXT
BINEXT=.exe
endif

ifeq ($(CROSS_COMPILING),0)
TOOLS_BINEXT=.exe
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)" \
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 \
Expand Down Expand Up @@ -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 += \
Expand Down Expand Up @@ -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)

Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions code/qcommon/q_platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion code/renderergl2/tr_extensions.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# include <SDL.h>
#endif

#ifdef USE_GL4ES
#include <gl4esinit.h>
#define ioq3_GetProcAddress gl4es_GetProcAddress
#else
#define ioq3_GetProcAddress SDL_GL_GetProcAddress
#endif

#include "tr_local.h"
#include "tr_dsa.h"

Expand All @@ -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;
Expand Down
24 changes: 18 additions & 6 deletions code/sdl/sdl_glimp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <gl4esinit.h>
#define ioq3_GetProcAddress gl4es_GetProcAddress
#else
#define ioq3_GetProcAddress SDL_GL_GetProcAddress
#endif


typedef enum
{
RSERR_OK,
Expand Down Expand Up @@ -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; \
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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");
Expand Down
Loading

0 comments on commit 7618f10

Please sign in to comment.