ioquake3 resync to commit c1ab47a7 from 60dfabbe

Use nanosleep(2) instead of usleep(3)
Makefile: fix compilation on FreeBSD
Update macOS version for GitHub CI
OpenGL2: Add OpenGL ES 2.0+ support
OpenGL2: Add run-time support for unsigned short indexes
OpenGL2: Use CPU vertex animation if too few vertex attributes
Merge pull request #664 from zturtleman/opengles2
OpenGL2: Fix issues running under WebGL
OpenGL2: Fallback to OpenGL ES if OpenGL fails
Add minimal emscripten support
Fix emscripten build directions
Use BASEGAME for emscripten assets
Fix using emscripten 3.1.27+
Allow building for emscripten with "emmake make"
Add -ffast-math for emscripten to match other platforms
Add .vscode and baseq3 to gitignore
Rebuild every target if Makefile changes
ioquake3.html replaces Emscripten-generated HTML shell
Get rid of "Nothing to be done for _.zip" message
Fix build when specifying PLATFORM=emscripten manually instead of using emmake
Silence compiler warnings
Support debug Emscripten build
Speed up GitHub Actions with -j
Add GitHub Actions workflow for web/Emscripten
Add Emscripten to README
Add DEPEND_MAKEFILE to disable rebuild on Makefile edit
Add BUILD_RENDERER_OPENGL1 to disable opengl1
Clean up emscripten in Makefile
Make emscripten --preload-file opt-in
Allow web client to use unzipped QVMs
Customize the web client HTML file
Add support for overriding basegame to web client
Add support for mods to web client
Rename client-config.json based on CLIENTBIN
This commit is contained in:
Zack Middleton 2024-06-13 15:37:24 -05:00
parent 157273b493
commit 04ea0f76f8
29 changed files with 1263 additions and 247 deletions

View file

@ -12,7 +12,7 @@ jobs:
sudo apt-get update
sudo apt-get install libsdl2-dev
- name: Compile
run: make release -C engine
run: make release -j$(nproc) -C engine
env:
ARCHIVE: 1
- uses: actions/upload-artifact@v4
@ -27,7 +27,7 @@ jobs:
- name: Compile
run: |
choco install zip
make release -C engine
make release -j $env:NUMBER_OF_PROCESSORS -C engine
env:
ARCHIVE: 1
- uses: actions/upload-artifact@v4
@ -36,14 +36,37 @@ jobs:
path: engine/build/*.zip
macos:
name: macOS
runs-on: macos-11
runs-on: macos-12
steps:
- uses: actions/checkout@v4
- name: Compile
run: make release -C engine
run: make release -j$(sysctl -n hw.logicalcpu) -C engine
env:
ARCHIVE: 1
- uses: actions/upload-artifact@v4
with:
name: macOS
path: engine/build/*.zip
web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: emscripten-core/emsdk
path: emsdk
- name: Install Dependencies
run: |
cd emsdk
./emsdk install 3.1.58
./emsdk activate 3.1.58
- name: Compile
env:
ARCHIVE: 1
run: |
source emsdk/emsdk_env.sh
emmake make release -j$(nproc) -C engine
- uses: actions/upload-artifact@v4
with:
name: Web
path: engine/build/*.zip

View file

@ -40,6 +40,9 @@ endif
ifndef BUILD_MISSIONPACK
BUILD_MISSIONPACK= 0
endif
ifndef BUILD_RENDERER_OPENGL1
BUILD_RENDERER_OPENGL1=
endif
ifndef BUILD_RENDERER_OPENGL2
BUILD_RENDERER_OPENGL2=
endif
@ -48,7 +51,7 @@ ifndef BUILD_AUTOUPDATER # DON'T build unless you mean to!
endif
# ioquake3 git commit that this is based on
IOQ3_REVISION = 60dfabbe
IOQ3_REVISION = c1ab47a7
#############################################################################
#
@ -64,6 +67,11 @@ ifeq ($(COMPILE_PLATFORM),cygwin)
PLATFORM=mingw32
endif
# detect "emmake make"
ifeq ($(findstring /emcc,$(CC)),/emcc)
PLATFORM=emscripten
endif
ifndef PLATFORM
PLATFORM=$(COMPILE_PLATFORM)
endif
@ -295,6 +303,7 @@ LIBTOMCRYPTSRCDIR=$(AUTOUPDATERSRCDIR)/rsa_tools/libtomcrypt-1.17
TOMSFASTMATHSRCDIR=$(AUTOUPDATERSRCDIR)/rsa_tools/tomsfastmath-0.13.1
LOKISETUPDIR=misc/setup
NSISDIR=misc/nsis
WEBDIR=$(MOUNT_DIR)/web
SDLHDIR=$(MOUNT_DIR)/SDL2
LIBSDIR=$(MOUNT_DIR)/libs
@ -826,6 +835,8 @@ else # ifdef MINGW
#############################################################################
ifeq ($(PLATFORM),freebsd)
# Use the default C compiler
TOOLS_CC=cc
# flags
BASE_CFLAGS = \
@ -1061,6 +1072,67 @@ ifeq ($(PLATFORM),sunos)
else # ifeq sunos
#############################################################################
# SETUP AND BUILD -- emscripten
#############################################################################
ifeq ($(PLATFORM),emscripten)
ifneq ($(findstring /emcc,$(CC)),/emcc)
CC=emcc
endif
ARCH=wasm32
BINEXT=.js
# dlopen(), opengl1, and networking are not functional
USE_RENDERER_DLOPEN=0
USE_OPENAL_DLOPEN=0
BUILD_GAME_SO=0
BUILD_RENDERER_OPENGL1=0
BUILD_SERVER=0
CLIENT_CFLAGS+=-s USE_SDL=2
CLIENT_LDFLAGS+=-s TOTAL_MEMORY=256MB
CLIENT_LDFLAGS+=-s STACK_SIZE=5MB
CLIENT_LDFLAGS+=-s MIN_WEBGL_VERSION=1 -s MAX_WEBGL_VERSION=2
# The HTML file can use these functions to load extra files before the game starts.
CLIENT_LDFLAGS+=-s EXPORTED_RUNTIME_METHODS=FS,addRunDependency,removeRunDependency
CLIENT_LDFLAGS+=-s EXIT_RUNTIME=1
CLIENT_LDFLAGS+=-s EXPORT_ES6
CLIENT_LDFLAGS+=-s EXPORT_NAME=ioquake3
# Game data files can be packaged by emcc into a .data file that lives next to the wasm bundle
# and added to the virtual filesystem before the game starts. This requires the game data to be
# present at build time and it can't be changed afterward.
# For more flexibility, game data files can be loaded from a web server at runtime by listing
# them in client-config.json. This way they don't have to be present at build time and can be
# changed later.
ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
ifeq ($(wildcard $(BASEGAME)/*),)
$(error "No files in '$(BASEGAME)' directory for emscripten to preload.")
endif
CLIENT_LDFLAGS+=--preload-file $(BASEGAME)
endif
OPTIMIZEVM = -O3
OPTIMIZE = $(OPTIMIZEVM) -ffast-math
# These allow a warning-free build.
# 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 -Wno-absolute-value
DEBUG_CFLAGS=-g3 -O0 # -fsanitize=address -fsanitize=undefined
# Emscripten needs debug compiler flags to be passed to the linker as well
DEBUG_LDFLAGS=$(DEBUG_CFLAGS)
SHLIBEXT=wasm
SHLIBCFLAGS=-fPIC
SHLIBLDFLAGS=-s SIDE_MODULE
else # ifeq emscripten
#############################################################################
# SETUP AND BUILD -- GENERIC
#############################################################################
@ -1079,6 +1151,7 @@ endif #OpenBSD
endif #NetBSD
endif #IRIX
endif #SunOS
endif #emscripten
ifndef CC
CC=gcc
@ -1115,12 +1188,18 @@ endif
ifneq ($(BUILD_CLIENT),0)
ifneq ($(USE_RENDERER_DLOPEN),0)
TARGETS += $(B)/$(CLIENTBIN)$(FULLBINEXT) $(B)/renderer_opengl1_$(SHLIBNAME)
TARGETS += $(B)/$(CLIENTBIN)$(FULLBINEXT)
ifneq ($(BUILD_RENDERER_OPENGL1),0)
TARGETS += $(B)/renderer_opengl1_$(SHLIBNAME)
endif
ifneq ($(BUILD_RENDERER_OPENGL2),0)
TARGETS += $(B)/renderer_opengl2_$(SHLIBNAME)
endif
else
TARGETS += $(B)/$(CLIENTBIN)$(FULLBINEXT)
ifneq ($(BUILD_RENDERER_OPENGL1),0)
TARGETS += $(B)/$(CLIENTBIN)$(FULLBINEXT)
endif
ifneq ($(BUILD_RENDERER_OPENGL2),0)
TARGETS += $(B)/$(CLIENTBIN)_opengl2$(FULLBINEXT)
endif
@ -1167,6 +1246,42 @@ ifneq ($(BUILD_AUTOUPDATER),0)
TARGETS += $(B)/$(AUTOUPDATER_BIN)
endif
ifeq ($(PLATFORM),emscripten)
ifneq ($(BUILD_SERVER),0)
GENERATEDTARGETS += $(B)/$(SERVERBIN).$(ARCH).wasm
ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
GENERATEDTARGETS += $(B)/$(SERVERBIN).$(ARCH).data
endif
endif
ifneq ($(BUILD_CLIENT),0)
TARGETS += $(B)/$(CLIENTBIN).html
ifneq ($(EMSCRIPTEN_PRELOAD_FILE),1)
TARGETS += $(B)/$(CLIENTBIN)-config.json
endif
ifneq ($(USE_RENDERER_DLOPEN),0)
GENERATEDTARGETS += $(B)/$(CLIENTBIN).$(ARCH).wasm
ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
GENERATEDTARGETS += $(B)/$(CLIENTBIN).$(ARCH).data
endif
else
ifneq ($(BUILD_RENDERER_OPENGL1),0)
GENERATEDTARGETS += $(B)/$(CLIENTBIN).$(ARCH).wasm
ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
GENERATEDTARGETS += $(B)/$(CLIENTBIN).$(ARCH).data
endif
endif
ifneq ($(BUILD_RENDERER_OPENGL2),0)
GENERATEDTARGETS += $(B)/$(CLIENTBIN)_opengl2.$(ARCH).wasm
ifeq ($(EMSCRIPTEN_PRELOAD_FILE),1)
GENERATEDTARGETS += $(B)/$(CLIENTBIN)_opengl2.$(ARCH).data
endif
endif
endif
endif
endif
ifeq ($(USE_OPENAL),1)
CLIENT_CFLAGS += -DUSE_OPENAL
ifeq ($(USE_OPENAL_DLOPEN),1)
@ -1451,7 +1566,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)" \
@ -1488,6 +1604,7 @@ ifneq ($(BUILD_CLIENT),0)
endif
NAKED_TARGETS=$(shell echo $(TARGETS) | sed -e "s!$(B)/!!g")
NAKED_GENERATEDTARGETS=$(shell echo $(GENERATEDTARGETS) | sed -e "s!$(B)/!!g")
print_list=-@for i in $(1); \
do \
@ -1546,6 +1663,7 @@ endif
@echo ""
@echo " Output:"
$(call print_list, $(NAKED_TARGETS))
$(call print_list, $(NAKED_GENERATEDTARGETS))
@echo ""
ifneq ($(TARGETS),)
ifndef DEBUG_MAKEFILE
@ -1562,9 +1680,10 @@ endif
ifneq ($(PLATFORM),darwin)
ifdef ARCHIVE
@rm -f $@
@(cd $(B) && zip -r9 ../../$@ $(NAKED_TARGETS))
@(cd $(B) && zip -r9 ../../$@ $(NAKED_TARGETS) $(NAKED_GENERATEDTARGETS))
endif
endif
@:
makedirs:
@$(MKDIR) $(B)/autoupdater
@ -1920,10 +2039,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 \
@ -3084,6 +3208,19 @@ $(B)/$(MISSIONPACK)/qcommon/%.asm: $(CMDIR)/%.c $(Q3LCC)
$(DO_Q3LCC_MISSIONPACK)
#############################################################################
# EMSCRIPTEN
#############################################################################
$(B)/$(CLIENTBIN).html: $(WEBDIR)/client.html
$(echo_cmd) "SED $@"
$(Q)sed 's/__CLIENTBIN__/$(CLIENTBIN)/g;s/__BASEGAME__/$(BASEGAME)/g;s/__EMSCRIPTEN_PRELOAD_FILE__/$(EMSCRIPTEN_PRELOAD_FILE)/g' < $< > $@
$(B)/$(CLIENTBIN)-config.json: $(WEBDIR)/client-config.json
$(echo_cmd) "CP $@"
$(Q)cp $< $@
#############################################################################
# MISC
#############################################################################
@ -3107,13 +3244,18 @@ ifneq ($(BUILD_GAME_SO),0)
endif
ifneq ($(BUILD_CLIENT),0)
$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/$(CLIENTBIN)$(FULLBINEXT) $(COPYBINDIR)/$(CLIENTBIN)$(FULLBINEXT)
ifneq ($(USE_RENDERER_DLOPEN),0)
$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/$(CLIENTBIN)$(FULLBINEXT) $(COPYBINDIR)/$(CLIENTBIN)$(FULLBINEXT)
ifneq ($(BUILD_RENDERER_OPENGL1),0)
$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/renderer_opengl1_$(SHLIBNAME) $(COPYBINDIR)/renderer_opengl1_$(SHLIBNAME)
endif
ifneq ($(BUILD_RENDERER_OPENGL2),0)
$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/renderer_opengl2_$(SHLIBNAME) $(COPYBINDIR)/renderer_opengl2_$(SHLIBNAME)
endif
else
ifneq ($(BUILD_RENDERER_OPENGL1),0)
$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/$(CLIENTBIN)$(FULLBINEXT) $(COPYBINDIR)/$(CLIENTBIN)$(FULLBINEXT)
endif
ifneq ($(BUILD_RENDERER_OPENGL2),0)
$(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/$(CLIENTBIN)_opengl2$(FULLBINEXT) $(COPYBINDIR)/$(CLIENTBIN)_opengl2$(FULLBINEXT)
endif
@ -3164,6 +3306,7 @@ clean2:
@rm -f $(OBJ_D_FILES)
@rm -f $(STRINGOBJ)
@rm -f $(TARGETS)
@rm -f $(GENERATEDTARGETS)
toolsclean: toolsclean-debug toolsclean-release
@ -3203,6 +3346,11 @@ dist:
# DEPENDENCIES
#############################################################################
# Rebuild every target if Makefile or Makefile.local changes
ifneq ($(DEPEND_MAKEFILE),0)
.EXTRA_PREREQS:= $(MAKEFILE_LIST)
endif
ifneq ($(B),)
OBJ_D_FILES=$(filter %.d,$(OBJ:%.o=%.d))
TOOLSOBJ_D_FILES=$(filter %.d,$(TOOLSOBJ:%.o=%.d))

View file

@ -933,10 +933,13 @@ static void waitToApplyUpdates(void)
OS forcibly closes the pipe), we will unblock. Then we can loop on
kill() until the process is truly gone. */
int x = 0;
struct timespec req;
req.tv_sec = 0;
req.tv_nsec = 100000000;
read(3, &x, sizeof (x));
info("Pipe has closed, waiting for process to fully go away now.");
while (kill(options.waitforprocess, 0) == 0) {
usleep(100000);
nanosleep(&req, NULL);
}
#endif
}

View file

@ -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 but OpenGL ES reads RGBA and converts to RGB in-place.
// 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);

View file

@ -290,6 +290,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

View file

@ -80,7 +80,6 @@ extern void (APIENTRYP qglUnlockArraysEXT) (void);
GLE(void, TexParameterf, GLenum target, GLenum pname, GLfloat param) \
GLE(void, TexParameteri, GLenum target, GLenum pname, GLint param) \
GLE(void, TexSubImage2D, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) \
GLE(void, Translatef, GLfloat x, GLfloat y, GLfloat z) \
GLE(void, Viewport, GLint x, GLint y, GLsizei width, GLsizei height) \
// OpenGL 1.0/1.1 and OpenGL ES 1.x but not OpenGL 3.2 core profile
@ -98,6 +97,7 @@ extern void (APIENTRYP qglUnlockArraysEXT) (void);
GLE(void, ShadeModel, GLenum mode) \
GLE(void, TexCoordPointer, GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) \
GLE(void, TexEnvf, GLenum target, GLenum pname, GLfloat param) \
GLE(void, Translatef, GLfloat x, GLfloat y, GLfloat z) \
GLE(void, VertexPointer, GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) \
// OpenGL 1.0/1.1 and 3.2 core profile but not OpenGL ES 1.x

View file

@ -52,10 +52,9 @@ vec4 depthGaussian1D(sampler2D imageMap, sampler2D depthMap, vec2 tex, float zFa
#endif
float zLimit = 5.0 / zFar;
int i, j;
for (i = 0; i < 2; i++)
for (int i = 0; i < 2; i++)
{
for (j = 1; j < BLUR_SIZE; j++)
for (int j = 1; j < BLUR_SIZE; j++)
{
vec2 offset = direction * (float(j) - 0.25) + nudge;
#if defined(USE_DEPTH)

View file

@ -77,8 +77,7 @@ float ambientOcclusion(sampler2D depthMap, const vec2 tex, const float zFarDivZN
float invZFar = 1.0 / zFar;
float zLimit = 20.0 * invZFar;
int i;
for (i = 0; i < NUM_SAMPLES; i++)
for (int i = 0; i < NUM_SAMPLES; i++)
{
vec2 offset = rmat * poissonDisc[i] * offsetScale;
float sampleDiff = getLinearDepth(depthMap, tex + offset, zFarDivZNear) - sampleZ;

View file

@ -732,6 +732,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;
GLuint texture;
if (!tr.scratchImage[client])
@ -746,7 +747,18 @@ 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;
qglTextureImage2DEXT(texture, GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, 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 );
qglTextureImage2DEXT(texture, GL_TEXTURE_2D, 0, GL_RGB, cols, rows, 0, GL_RGB, GL_UNSIGNED_BYTE, buffer);
ri.Hunk_FreeTempMemory( buffer );
} else {
qglTextureImage2DEXT(texture, GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
}
qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
qglTextureParameterfEXT(texture, GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
@ -755,7 +767,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
qglTextureSubImage2DEXT(texture, 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 );
qglTextureSubImage2DEXT(texture, GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGB, GL_UNSIGNED_BYTE, buffer);
ri.Hunk_FreeTempMemory( buffer );
} else {
qglTextureSubImage2DEXT(texture, GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data);
}
}
}
}
@ -1140,14 +1161,14 @@ const void *RB_DrawSurfs( const void *data ) {
if (glRefConfig.occlusionQuery)
{
tr.sunFlareQueryActive[tr.sunFlareQueryIndex] = qtrue;
qglBeginQuery(GL_SAMPLES_PASSED, tr.sunFlareQuery[tr.sunFlareQueryIndex]);
qglBeginQuery(glRefConfig.occlusionQueryTarget, tr.sunFlareQuery[tr.sunFlareQueryIndex]);
}
RB_DrawSun(0.3, tr.sunFlareShader);
if (glRefConfig.occlusionQuery)
{
qglEndQuery(GL_SAMPLES_PASSED);
qglEndQuery(glRefConfig.occlusionQueryTarget);
}
FBO_Bind(oldFbo);

View file

@ -276,7 +276,7 @@ static void R_LoadLightmaps( lump_t *l, lump_t *surfs ) {
tr.deluxemaps = ri.Hunk_Alloc( tr.numLightmaps * sizeof(image_t *), h_low );
textureInternalFormat = GL_RGBA8;
if (r_hdr->integer)
if (r_hdr->integer && !qglesMajorVersion)
{
// Check for the first hdr lightmap, if it exists, use GL_RGBA16 for textures.
char filename[MAX_QPATH];

View file

@ -348,7 +348,13 @@ void RE_BeginFrame( stereoFrame_t stereoFrame ) {
//
if ( r_measureOverdraw->integer )
{
if ( glConfig.stencilBits < 4 )
if ( qglesMajorVersion >= 1 && !glRefConfig.readStencil )
{
ri.Printf( PRINT_WARNING, "OpenGL ES needs GL_NV_read_stencil to read stencil bits to measure overdraw\n" );
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" );
@ -426,6 +432,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)

View file

@ -45,6 +45,17 @@ void GLimp_InitExtraExtensions(void)
if (strstr((char *)qglGetString(GL_RENDERER), "Intel"))
glRefConfig.intelGraphics = qtrue;
if (qglesMajorVersion)
{
glRefConfig.vaoCacheGlIndexType = GL_UNSIGNED_SHORT;
glRefConfig.vaoCacheGlIndexSize = sizeof(unsigned short);
}
else
{
glRefConfig.vaoCacheGlIndexType = GL_UNSIGNED_INT;
glRefConfig.vaoCacheGlIndexSize = sizeof(unsigned int);
}
// set DSA fallbacks
#define GLE(ret, name, ...) qgl##name = GLDSA_##name;
QGL_EXT_direct_state_access_PROCS;
@ -53,8 +64,107 @@ void GLimp_InitExtraExtensions(void)
// GL function loader, based on https://gist.github.com/rygorous/16796a0c876cf8a5f542caddb55bce8a
#define GLE(ret, name, ...) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name);
//
// OpenGL ES extensions
//
if (qglesMajorVersion)
{
if (!r_allowExtensions->integer)
goto done;
extension = "GL_EXT_occlusion_query_boolean";
if (qglesMajorVersion >= 3 || SDL_GL_ExtensionSupported(extension))
{
glRefConfig.occlusionQuery = qtrue;
glRefConfig.occlusionQueryTarget = GL_ANY_SAMPLES_PASSED;
if (qglesMajorVersion >= 3) {
QGL_ARB_occlusion_query_PROCS;
} else {
// GL_EXT_occlusion_query_boolean uses EXT suffix
#undef GLE
#define GLE(ret, name, ...) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name "EXT");
QGL_ARB_occlusion_query_PROCS;
#undef GLE
#define GLE(ret, name, ...) qgl##name = (name##proc *) SDL_GL_GetProcAddress("gl" #name);
}
ri.Printf(PRINT_ALL, result[glRefConfig.occlusionQuery], extension);
}
else
{
ri.Printf(PRINT_ALL, result[2], extension);
}
// GL_NV_read_depth
extension = "GL_NV_read_depth";
if (SDL_GL_ExtensionSupported(extension))
{
glRefConfig.readDepth = qtrue;
ri.Printf(PRINT_ALL, result[glRefConfig.readDepth], extension);
}
else
{
ri.Printf(PRINT_ALL, result[2], extension);
}
// GL_NV_read_stencil
extension = "GL_NV_read_stencil";
if (SDL_GL_ExtensionSupported(extension))
{
glRefConfig.readStencil = qtrue;
ri.Printf(PRINT_ALL, result[glRefConfig.readStencil], extension);
}
else
{
ri.Printf(PRINT_ALL, result[2], extension);
}
// GL_EXT_shadow_samplers
extension = "GL_EXT_shadow_samplers";
if (qglesMajorVersion >= 3 || SDL_GL_ExtensionSupported(extension))
{
glRefConfig.shadowSamplers = qtrue;
ri.Printf(PRINT_ALL, result[glRefConfig.shadowSamplers], extension);
}
else
{
ri.Printf(PRINT_ALL, result[2], extension);
}
// GL_OES_standard_derivatives
extension = "GL_OES_standard_derivatives";
if (qglesMajorVersion >= 3 || SDL_GL_ExtensionSupported(extension))
{
glRefConfig.standardDerivatives = qtrue;
ri.Printf(PRINT_ALL, result[glRefConfig.standardDerivatives], extension);
}
else
{
ri.Printf(PRINT_ALL, result[2], extension);
}
// GL_OES_element_index_uint
extension = "GL_OES_element_index_uint";
if (qglesMajorVersion >= 3 || SDL_GL_ExtensionSupported(extension))
{
glRefConfig.vaoCacheGlIndexType = GL_UNSIGNED_INT;
glRefConfig.vaoCacheGlIndexSize = sizeof(unsigned int);
ri.Printf(PRINT_ALL, result[1], extension);
}
else
{
ri.Printf(PRINT_ALL, result[2], extension);
}
goto done;
}
// OpenGL 1.5 - GL_ARB_occlusion_query
glRefConfig.occlusionQuery = qtrue;
glRefConfig.occlusionQueryTarget = GL_SAMPLES_PASSED;
QGL_ARB_occlusion_query_PROCS;
// OpenGL 3.0 - GL_ARB_framebuffer_object
@ -146,18 +256,6 @@ void GLimp_InitExtraExtensions(void)
ri.Printf(PRINT_ALL, result[2], extension);
}
// Determine GLSL version
if (1)
{
char version[256];
Q_strncpyz(version, (char *)qglGetString(GL_SHADING_LANGUAGE_VERSION), sizeof(version));
sscanf(version, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion);
ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version);
}
glRefConfig.memInfo = MI_NONE;
// GL_NVX_gpu_memory_info
@ -249,5 +347,26 @@ void GLimp_InitExtraExtensions(void)
ri.Printf(PRINT_ALL, result[2], extension);
}
done:
// Determine GLSL version
if (1)
{
char version[256], *version_p;
Q_strncpyz(version, (char *)qglGetString(GL_SHADING_LANGUAGE_VERSION), sizeof(version));
// Skip leading text such as "OpenGL ES GLSL ES "
version_p = version;
while ( *version_p && !isdigit( *version_p ) )
{
version_p++;
}
sscanf(version_p, "%d.%d", &glRefConfig.glslMajorVersion, &glRefConfig.glslMinorVersion);
ri.Printf(PRINT_ALL, "...using GLSL version %s\n", version);
}
#undef GLE
}

View file

@ -478,6 +478,14 @@ void RB_RenderFlares (void) {
return;
}
if ( r_flares->modified ) {
if ( qglesMajorVersion >= 1 && !glRefConfig.readDepth ) {
ri.Printf( PRINT_WARNING, "OpenGL ES needs GL_NV_read_depth to read depth to determine if flares are visible\n" );
ri.Cvar_Set( "r_flares", "0" );
}
r_flares->modified = qfalse;
}
if(r_flareCoeff->modified)
{
R_SetFlareCoeff();

View file

@ -249,11 +249,25 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char *
// HACK: abuse the GLSL preprocessor to turn GLSL 1.20 shaders into 1.30 ones
if(glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 30))
{
if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 50))
if (qglesMajorVersion >= 3 && glRefConfig.glslMajorVersion >= 3)
Q_strcat(dest, size, "#version 300 es\n");
else if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 50))
Q_strcat(dest, size, "#version 150\n");
else
Q_strcat(dest, size, "#version 130\n");
// `extra' may contain #extension which must be directly after #version
if (extra)
{
Q_strcat(dest, size, extra);
}
if (qglesMajorVersion >= 2)
{
Q_strcat(dest, size, "precision mediump float;\n");
Q_strcat(dest, size, "precision mediump sampler2DShadow;\n");
}
if(shaderType == GL_VERTEX_SHADER)
{
Q_strcat(dest, size, "#define attribute in\n");
@ -272,8 +286,34 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char *
}
else
{
Q_strcat(dest, size, "#version 120\n");
Q_strcat(dest, size, "#define shadow2D(a,b) shadow2D(a,b).r \n");
if (qglesMajorVersion >= 2)
{
Q_strcat(dest, size, "#version 100\n");
if (extra)
{
Q_strcat(dest, size, extra);
}
Q_strcat(dest, size, "precision mediump float;\n");
if (glRefConfig.shadowSamplers)
{
Q_strcat(dest, size, "precision mediump sampler2DShadow;\n");
Q_strcat(dest, size, "#define shadow2D(a,b) shadow2DEXT(a,b)\n");
}
}
else
{
Q_strcat(dest, size, "#version 120\n");
if (extra)
{
Q_strcat(dest, size, extra);
}
Q_strcat(dest, size, "#define shadow2D(a,b) shadow2D(a,b).r\n");
}
}
// HACK: add some macros to avoid extra uniforms and save speed and code maintenance
@ -361,11 +401,6 @@ static void GLSL_GetShaderHeader( GLenum shaderType, const GLchar *extra, char *
Q_strcat(dest, size, va("#define ROUGHNESS_MIPS float(%d)\n", numRoughnessMips));
}
if (extra)
{
Q_strcat(dest, size, extra);
}
// OK we added a lot of stuff but if we do something bad in the GLSL shaders then we want the proper line
// so we have to reset the line counting
Q_strcat(dest, size, "#line 0\n");
@ -933,6 +968,15 @@ void GLSL_InitGPUShaders(void)
startTime = ri.Milliseconds();
// OpenGL ES may not have enough attributes to fit ones used for vertex animation
if ( glRefConfig.maxVertexAttribs > ATTR_INDEX_NORMAL2 ) {
ri.Printf(PRINT_ALL, "Using GPU vertex animation\n");
glRefConfig.gpuVertexAnimation = qtrue;
} else {
ri.Printf(PRINT_ALL, "Using CPU vertex animation\n");
glRefConfig.gpuVertexAnimation = qfalse;
}
for (i = 0; i < GENERICDEF_COUNT; i++)
{
if ((i & GENERICDEF_USE_VERTEX_ANIMATION) && (i & GENERICDEF_USE_BONE_ANIMATION))
@ -955,6 +999,9 @@ void GLSL_InitGPUShaders(void)
if (i & GENERICDEF_USE_VERTEX_ANIMATION)
{
if (!glRefConfig.gpuVertexAnimation)
continue;
Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n");
attribs |= ATTR_POSITION2 | ATTR_NORMAL2;
}
@ -1006,6 +1053,9 @@ void GLSL_InitGPUShaders(void)
if ((i & FOGDEF_USE_VERTEX_ANIMATION) && (i & FOGDEF_USE_BONE_ANIMATION))
continue;
if ((i & FOGDEF_USE_VERTEX_ANIMATION) && !glRefConfig.gpuVertexAnimation)
continue;
if ((i & FOGDEF_USE_BONE_ANIMATION) && !glRefConfig.glslMaxAnimatedBones)
continue;
@ -1186,12 +1236,17 @@ void GLSL_InitGPUShaders(void)
if (i & LIGHTDEF_ENTITY_VERTEX_ANIMATION)
{
Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n#define USE_MODELMATRIX\n");
attribs |= ATTR_POSITION2 | ATTR_NORMAL2;
Q_strcat(extradefines, 1024, "#define USE_MODELMATRIX\n");
if (r_normalMapping->integer)
if (glRefConfig.gpuVertexAnimation)
{
attribs |= ATTR_TANGENT2;
Q_strcat(extradefines, 1024, "#define USE_VERTEX_ANIMATION\n");
attribs |= ATTR_POSITION2 | ATTR_NORMAL2;
if (r_normalMapping->integer)
{
attribs |= ATTR_TANGENT2;
}
}
}
else if (i & LIGHTDEF_ENTITY_BONE_ANIMATION)
@ -1226,6 +1281,9 @@ void GLSL_InitGPUShaders(void)
if ((i & SHADOWMAPDEF_USE_VERTEX_ANIMATION) && (i & SHADOWMAPDEF_USE_BONE_ANIMATION))
continue;
if ((i & SHADOWMAPDEF_USE_VERTEX_ANIMATION) && !glRefConfig.gpuVertexAnimation)
continue;
if ((i & SHADOWMAPDEF_USE_BONE_ANIMATION) && !glRefConfig.glslMaxAnimatedBones)
continue;
@ -1350,86 +1408,110 @@ void GLSL_InitGPUShaders(void)
}
attribs = ATTR_POSITION | ATTR_TEXCOORD;
extradefines[0] = '\0';
if (r_shadowFilter->integer >= 1)
Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER\n");
if (r_shadowFilter->integer >= 2)
Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER2\n");
if (r_shadowCascadeZFar->integer != 0)
Q_strcat(extradefines, 1024, "#define USE_SHADOW_CASCADE\n");
Q_strcat(extradefines, 1024, va("#define r_shadowMapSize %f\n", r_shadowMapSize->value));
Q_strcat(extradefines, 1024, va("#define r_shadowCascadeZFar %f\n", r_shadowCascadeZFar->value));
if (!GLSL_InitGPUShader(&tr.shadowmaskShader, "shadowmask", attribs, qtrue, extradefines, qtrue, fallbackShader_shadowmask_vp, fallbackShader_shadowmask_fp))
{
ri.Error(ERR_FATAL, "Could not load shadowmask shader!");
}
GLSL_InitUniforms(&tr.shadowmaskShader);
GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP, TB_SHADOWMAP);
GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP2, TB_SHADOWMAP2);
GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP3, TB_SHADOWMAP3);
GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP4, TB_SHADOWMAP4);
GLSL_FinishGPUShader(&tr.shadowmaskShader);
numEtcShaders++;
attribs = ATTR_POSITION | ATTR_TEXCOORD;
extradefines[0] = '\0';
if (!GLSL_InitGPUShader(&tr.ssaoShader, "ssao", attribs, qtrue, extradefines, qtrue, fallbackShader_ssao_vp, fallbackShader_ssao_fp))
{
ri.Error(ERR_FATAL, "Could not load ssao shader!");
}
GLSL_InitUniforms(&tr.ssaoShader);
GLSL_SetUniformInt(&tr.ssaoShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
GLSL_FinishGPUShader(&tr.ssaoShader);
numEtcShaders++;
for (i = 0; i < 4; i++)
// GLSL 1.10+ or GL_EXT_shadow_samplers extension are required for sampler2DShadow type
if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 10)
|| glRefConfig.shadowSamplers)
{
attribs = ATTR_POSITION | ATTR_TEXCOORD;
extradefines[0] = '\0';
if (i & 1)
Q_strcat(extradefines, 1024, "#define USE_VERTICAL_BLUR\n");
else
Q_strcat(extradefines, 1024, "#define USE_HORIZONTAL_BLUR\n");
if (!(i & 2))
Q_strcat(extradefines, 1024, "#define USE_DEPTH\n");
if (!GLSL_InitGPUShader(&tr.depthBlurShader[i], "depthBlur", attribs, qtrue, extradefines, qtrue, fallbackShader_depthblur_vp, fallbackShader_depthblur_fp))
if (qglesMajorVersion < 3 && glRefConfig.shadowSamplers)
{
ri.Error(ERR_FATAL, "Could not load depthBlur shader!");
Q_strcat(extradefines, 1024, "#extension GL_EXT_shadow_samplers : enable\n");
}
GLSL_InitUniforms(&tr.depthBlurShader[i]);
GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENIMAGEMAP, TB_COLORMAP);
GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP);
if (r_shadowFilter->integer >= 1)
Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER\n");
GLSL_FinishGPUShader(&tr.depthBlurShader[i]);
if (r_shadowFilter->integer >= 2)
Q_strcat(extradefines, 1024, "#define USE_SHADOW_FILTER2\n");
if (r_shadowCascadeZFar->integer != 0)
Q_strcat(extradefines, 1024, "#define USE_SHADOW_CASCADE\n");
Q_strcat(extradefines, 1024, va("#define r_shadowMapSize %f\n", r_shadowMapSize->value));
Q_strcat(extradefines, 1024, va("#define r_shadowCascadeZFar %f\n", r_shadowCascadeZFar->value));
if (!GLSL_InitGPUShader(&tr.shadowmaskShader, "shadowmask", attribs, qtrue, extradefines, qtrue, fallbackShader_shadowmask_vp, fallbackShader_shadowmask_fp))
{
ri.Error(ERR_FATAL, "Could not load shadowmask shader!");
}
GLSL_InitUniforms(&tr.shadowmaskShader);
GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP, TB_SHADOWMAP);
GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP2, TB_SHADOWMAP2);
GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP3, TB_SHADOWMAP3);
GLSL_SetUniformInt(&tr.shadowmaskShader, UNIFORM_SHADOWMAP4, TB_SHADOWMAP4);
GLSL_FinishGPUShader(&tr.shadowmaskShader);
numEtcShaders++;
}
// GLSL 1.10+ or GL_OES_standard_derivatives extension are required for dFdx() and dFdy() GLSL functions
if (glRefConfig.glslMajorVersion > 1 || (glRefConfig.glslMajorVersion == 1 && glRefConfig.glslMinorVersion >= 10)
|| glRefConfig.standardDerivatives)
{
attribs = ATTR_POSITION | ATTR_TEXCOORD;
extradefines[0] = '\0';
if (qglesMajorVersion < 3 && glRefConfig.standardDerivatives)
{
Q_strcat(extradefines, 1024, "#extension GL_OES_standard_derivatives : enable\n");
}
if (!GLSL_InitGPUShader(&tr.ssaoShader, "ssao", attribs, qtrue, extradefines, qtrue, fallbackShader_ssao_vp, fallbackShader_ssao_fp))
{
ri.Error(ERR_FATAL, "Could not load ssao shader!");
}
GLSL_InitUniforms(&tr.ssaoShader);
GLSL_SetUniformInt(&tr.ssaoShader, UNIFORM_SCREENDEPTHMAP, TB_COLORMAP);
GLSL_FinishGPUShader(&tr.ssaoShader);
numEtcShaders++;
for (i = 0; i < 4; i++)
{
attribs = ATTR_POSITION | ATTR_TEXCOORD;
extradefines[0] = '\0';
if (qglesMajorVersion < 3 && glRefConfig.standardDerivatives)
{
Q_strcat(extradefines, 1024, "#extension GL_OES_standard_derivatives : enable\n");
}
if (i & 1)
Q_strcat(extradefines, 1024, "#define USE_VERTICAL_BLUR\n");
else
Q_strcat(extradefines, 1024, "#define USE_HORIZONTAL_BLUR\n");
if (!(i & 2))
Q_strcat(extradefines, 1024, "#define USE_DEPTH\n");
if (!GLSL_InitGPUShader(&tr.depthBlurShader[i], "depthBlur", attribs, qtrue, extradefines, qtrue, fallbackShader_depthblur_vp, fallbackShader_depthblur_fp))
{
ri.Error(ERR_FATAL, "Could not load depthBlur shader!");
}
GLSL_InitUniforms(&tr.depthBlurShader[i]);
GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENIMAGEMAP, TB_COLORMAP);
GLSL_SetUniformInt(&tr.depthBlurShader[i], UNIFORM_SCREENDEPTHMAP, TB_LIGHTMAP);
GLSL_FinishGPUShader(&tr.depthBlurShader[i]);
numEtcShaders++;
}
}
#if 0
attribs = ATTR_POSITION | ATTR_TEXCOORD;
extradefines[0] = '\0';
@ -1462,7 +1544,7 @@ void GLSL_ShutdownGPUShaders(void)
ri.Printf(PRINT_ALL, "------- GLSL_ShutdownGPUShaders -------\n");
for (i = 0; i < ATTR_INDEX_COUNT; i++)
for (i = 0; i < ATTR_INDEX_COUNT && i < glRefConfig.maxVertexAttribs; i++)
qglDisableVertexAttribArray(i);
GL_BindNullProgram();

View file

@ -1455,6 +1455,106 @@ byte mipBlendColors[16][4] = {
{0,0,255,128},
};
/*
==================
R_ConvertTextureFormat
Convert RGBA unsigned byte to specified format and type
==================
*/
#define ROW_PADDING( width, bpp, alignment ) PAD( (width) * (bpp), (alignment) ) - (width) * (bpp)
void R_ConvertTextureFormat( const byte *in, int width, int height, GLenum format, GLenum type, byte *out )
{
int x, y, rowPadding;
int unpackAlign = 4; // matches GL_UNPACK_ALIGNMENT default
if ( format == GL_RGB && type == GL_UNSIGNED_BYTE )
{
rowPadding = ROW_PADDING( width, 3, unpackAlign );
for ( y = 0; y < height; y++ )
{
for ( x = 0; x < width; x++ )
{
*out++ = *in++;
*out++ = *in++;
*out++ = *in++;
in++;
}
out += rowPadding;
}
}
else if ( format == GL_LUMINANCE && type == GL_UNSIGNED_BYTE )
{
rowPadding = ROW_PADDING( width, 1, unpackAlign );
for ( y = 0; y < height; y++ )
{
for ( x = 0; x < width; x++ )
{
*out++ = *in++; // red
in += 3;
}
out += rowPadding;
}
}
else if ( format == GL_LUMINANCE_ALPHA && type == GL_UNSIGNED_BYTE )
{
rowPadding = ROW_PADDING( width, 2, unpackAlign );
for ( y = 0; y < height; y++ )
{
for ( x = 0; x < width; x++ )
{
*out++ = *in++; // red
in += 2;
*out++ = *in++; // alpha
}
out += rowPadding;
}
}
else if ( format == GL_RGB && type == GL_UNSIGNED_SHORT_5_6_5 )
{
rowPadding = ROW_PADDING( width, 2, unpackAlign );
for ( y = 0; y < height; y++ )
{
for ( x = 0; x < width; x++, in += 4, out += 2 )
{
*((unsigned short*)out) = ( (unsigned short)( in[0] >> 3 ) << 11 )
| ( (unsigned short)( in[1] >> 2 ) << 5 )
| ( (unsigned short)( in[2] >> 3 ) << 0 );
}
out += rowPadding;
}
}
else if ( format == GL_RGBA && type == GL_UNSIGNED_SHORT_4_4_4_4 )
{
rowPadding = ROW_PADDING( width, 2, unpackAlign );
for ( y = 0; y < height; y++ )
{
for ( x = 0; x < width; x++, in += 4, out += 2 )
{
*((unsigned short*)out) = ( (unsigned short)( in[0] >> 4 ) << 12 )
| ( (unsigned short)( in[1] >> 4 ) << 8 )
| ( (unsigned short)( in[2] >> 4 ) << 4 )
| ( (unsigned short)( in[3] >> 4 ) << 0 );
}
out += rowPadding;
}
}
else
{
ri.Error( ERR_DROP, "Unable to convert RGBA image to OpenGL format 0x%X and type 0x%X", format, type );
}
}
static void RawImage_SwizzleRA( byte *data, int width, int height )
{
int i;
@ -1944,18 +2044,20 @@ static GLenum PixelDataFormatFromInternalFormat(GLenum internalFormat)
}
}
static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int width, int height, GLenum target, GLenum picFormat, int numMips, GLenum internalFormat, imgType_t type, imgFlags_t flags, qboolean subtexture )
static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int width, int height, GLenum target, GLenum picFormat, GLenum dataFormat, GLenum dataType, int numMips, GLenum internalFormat, imgType_t type, imgFlags_t flags, qboolean subtexture )
{
GLenum dataFormat, dataType;
qboolean rgtc = internalFormat == GL_COMPRESSED_RG_RGTC2;
qboolean rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT;
qboolean rgba = rgba8 || picFormat == GL_RGBA16;
qboolean mipmap = !!(flags & IMGFLAG_MIPMAP);
int size, miplevel;
qboolean lastMip = qfalse;
byte *formatBuffer = NULL;
dataFormat = PixelDataFormatFromInternalFormat(internalFormat);
dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
if (qglesMajorVersion && rgba8 && (dataFormat != GL_RGBA || dataType != GL_UNSIGNED_BYTE))
{
formatBuffer = ri.Hunk_AllocateTempMemory(4 * width * height);
}
miplevel = 0;
do
@ -1974,6 +2076,11 @@ static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int
if (rgba8 && rgtc)
RawImage_UploadToRgtc2Texture(texture, miplevel, x, y, width, height, data);
else if (formatBuffer)
{
R_ConvertTextureFormat(data, width, height, dataFormat, dataType, formatBuffer);
qglTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, dataFormat, dataType, formatBuffer);
}
else
qglTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, dataFormat, dataType, data);
}
@ -2007,6 +2114,9 @@ static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int
}
}
while (!lastMip);
if (formatBuffer != NULL)
ri.Hunk_FreeTempMemory(formatBuffer);
}
@ -2016,7 +2126,7 @@ Upload32
===============
*/
static void Upload32(byte *data, int x, int y, int width, int height, GLenum picFormat, int numMips, image_t *image, qboolean scaled)
static void Upload32(byte *data, int x, int y, int width, int height, GLenum picFormat, GLenum dataFormat, GLenum dataType, int numMips, image_t *image, qboolean scaled)
{
int i, c;
byte *scan;
@ -2071,7 +2181,7 @@ static void Upload32(byte *data, int x, int y, int width, int height, GLenum pic
for (i = 0; i < 6; i++)
{
int w2 = width, h2 = height;
RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, picFormat, numMips, internalFormat, type, flags, qfalse);
RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, picFormat, dataFormat, dataType, numMips, internalFormat, type, flags, qfalse);
for (c = numMips; c; c--)
{
data += CalculateMipSize(w2, h2, picFormat);
@ -2082,7 +2192,7 @@ static void Upload32(byte *data, int x, int y, int width, int height, GLenum pic
}
else
{
RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_2D, picFormat, numMips, internalFormat, type, flags, qfalse);
RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_2D, picFormat, dataFormat, dataType, numMips, internalFormat, type, flags, qfalse);
}
GL_CheckErrors();
@ -2108,7 +2218,7 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe
qboolean picmip = !!(flags & IMGFLAG_PICMIP);
qboolean lastMip;
GLenum textureTarget = cubemap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
GLenum dataFormat;
GLenum dataFormat, dataType;
if (strlen(name) >= MAX_QPATH ) {
ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long", name);
@ -2140,6 +2250,53 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe
if (!internalFormat)
internalFormat = RawImage_GetFormat(pic, width * height, picFormat, isLightmap, image->type, image->flags);
dataFormat = PixelDataFormatFromInternalFormat(internalFormat);
dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
// Convert image data format for OpenGL ES, data is converted for each mip level
if (qglesMajorVersion)
{
switch (internalFormat)
{
case GL_LUMINANCE:
case GL_LUMINANCE8:
internalFormat = GL_LUMINANCE;
dataFormat = GL_LUMINANCE;
dataType = GL_UNSIGNED_BYTE;
break;
case GL_LUMINANCE_ALPHA:
case GL_LUMINANCE8_ALPHA8:
internalFormat = GL_LUMINANCE_ALPHA;
dataFormat = GL_LUMINANCE_ALPHA;
dataType = GL_UNSIGNED_BYTE;
break;
case GL_RGB:
case GL_RGB8:
internalFormat = GL_RGB;
dataFormat = GL_RGB;
dataType = GL_UNSIGNED_BYTE;
break;
case GL_RGB5:
internalFormat = GL_RGB;
dataFormat = GL_RGB;
dataType = GL_UNSIGNED_SHORT_5_6_5;
break;
case GL_RGBA:
case GL_RGBA8:
internalFormat = GL_RGBA;
dataFormat = GL_RGBA;
dataType = GL_UNSIGNED_BYTE;
break;
case GL_RGBA4:
internalFormat = GL_RGBA;
dataFormat = GL_RGBA;
dataType = GL_UNSIGNED_SHORT_4_4_4_4;
break;
default:
ri.Error( ERR_DROP, "Missing OpenGL ES support for image '%s' with internal format 0x%X\n", name, internalFormat );
}
}
image->internalFormat = internalFormat;
// Possibly scale image before uploading.
@ -2164,7 +2321,6 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe
image->uploadHeight = height;
// Allocate texture storage so we don't have to worry about it later.
dataFormat = PixelDataFormatFromInternalFormat(internalFormat);
mipWidth = width;
mipHeight = height;
miplevel = 0;
@ -2176,11 +2332,11 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe
int i;
for (i = 0; i < 6; i++)
qglTextureImage2DEXT(image->texnum, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL);
qglTextureImage2DEXT(image->texnum, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, dataType, NULL);
}
else
{
qglTextureImage2DEXT(image->texnum, GL_TEXTURE_2D, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL);
qglTextureImage2DEXT(image->texnum, GL_TEXTURE_2D, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, dataType, NULL);
}
mipWidth = MAX(1, mipWidth >> 1);
@ -2191,7 +2347,7 @@ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLe
// Upload data.
if (pic)
Upload32(pic, 0, 0, width, height, picFormat, numMips, image, scaled);
Upload32(pic, 0, 0, width, height, picFormat, dataFormat, dataType, numMips, image, scaled);
if (resampledBuffer != NULL)
ri.Hunk_FreeTempMemory(resampledBuffer);
@ -2252,7 +2408,13 @@ image_t *R_CreateImage(const char *name, byte *pic, int width, int height, imgTy
void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height, GLenum picFormat )
{
Upload32(pic, x, y, width, height, picFormat, 0, image, qfalse);
GLenum dataFormat, dataType;
// TODO: This is fine for lightmaps but (unused) general RGBA images need to store dataFormat / dataType in image_t for OpenGL ES?
dataFormat = PixelDataFormatFromInternalFormat(image->internalFormat);
dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
Upload32(pic, x, y, width, height, picFormat, dataFormat, dataType, 0, image, qfalse);
}
//===================================================================

View file

@ -279,8 +279,16 @@ static void InitOpenGL( void )
qglGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS, &temp );
glConfig.numTextureUnits = temp;
qglGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &temp );
glRefConfig.maxVertexAttribs = temp;
// reserve 160 components for other uniforms
qglGetIntegerv( GL_MAX_VERTEX_UNIFORM_COMPONENTS, &temp );
if ( qglesMajorVersion ) {
qglGetIntegerv( GL_MAX_VERTEX_UNIFORM_VECTORS, &temp );
temp *= 4;
} else {
qglGetIntegerv( GL_MAX_VERTEX_UNIFORM_COMPONENTS, &temp );
}
glRefConfig.glslMaxAnimatedBones = Com_Clamp( 0, IQM_MAX_JOINTS, ( temp - 160 ) / 16 );
if ( glRefConfig.glslMaxAnimatedBones < 12 ) {
glRefConfig.glslMaxAnimatedBones = 0;
@ -451,21 +459,43 @@ 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;
// OpenGL ES is only required to support reading GL_RGBA
if (qglesMajorVersion >= 1) {
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);
bufstart = PADP((intptr_t) buffer + *offset, packAlign);
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;
@ -877,9 +907,10 @@ 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;
// finish any 2D drawing if needed
if(tess.numIndexes)
@ -887,20 +918,32 @@ const void *RB_TakeVideoFrameCmd( const void *data )
cmd = (const videoFrameCommand_t *)data;
// OpenGL ES is only required to support reading GL_RGBA
if (qglesMajorVersion >= 1) {
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;
@ -911,7 +954,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);
@ -934,7 +991,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);

View file

@ -49,8 +49,10 @@ QGL_ARB_vertex_array_object_PROCS;
QGL_EXT_direct_state_access_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;
typedef unsigned int vaoCacheGlIndex_t;
#define BUFFER_OFFSET(i) ((char *)NULL + (i))
@ -1406,6 +1408,7 @@ typedef struct {
qboolean intelGraphics;
qboolean occlusionQuery;
GLenum occlusionQueryTarget;
int glslMajorVersion;
int glslMinorVersion;
@ -1429,6 +1432,18 @@ typedef struct {
qboolean vertexArrayObject;
qboolean directStateAccess;
int maxVertexAttribs;
qboolean gpuVertexAnimation;
GLenum vaoCacheGlIndexType; // GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
size_t vaoCacheGlIndexSize; // must be <= sizeof( vaoCacheGlIndex_t )
// OpenGL ES extensions
qboolean readDepth;
qboolean readStencil;
qboolean shadowSamplers;
qboolean standardDerivatives;
} glRefConfig_t;
@ -2213,6 +2228,7 @@ void R_VaoList_f(void);
void RB_UpdateTessVao(unsigned int attribBits);
void VaoCache_Commit(void);
void VaoCache_DrawElements(int numIndexes, int firstIndex);
void VaoCache_Init(void);
void VaoCache_BindVao(void);
void VaoCache_CheckAdd(qboolean *endSurface, qboolean *recycleVertexBuffer, qboolean *recycleIndexBuffer, int numVerts, int numIndexes);
@ -2502,5 +2518,7 @@ size_t RE_SaveJPGToBuffer(byte *buffer, size_t bufSize, int quality,
void RE_TakeVideoFrame( int width, int height,
byte *captureBuffer, byte *encodeBuffer, qboolean motionJpeg );
void R_ConvertTextureFormat( const byte *in, int width, int height, GLenum format, GLenum type, byte *out );
#endif //TR_LOCAL_H

View file

@ -282,9 +282,10 @@ R_AddMD3Surfaces
*/
void R_AddMD3Surfaces( trRefEntity_t *ent ) {
int i;
mdvModel_t *model = NULL;
mdvSurface_t *surface = NULL;
shader_t *shader = NULL;
mdvModel_t *model;
mdvSurface_t *surface;
void *drawSurf;
shader_t *shader;
int cull;
int lod;
int fogNum;
@ -382,6 +383,12 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) {
shader = tr.shaders[ surface->shaderIndexes[ ent->e.skinNum % surface->numShaderIndexes ] ];
}
if ( model->numVaoSurfaces > 0 ) {
drawSurf = &model->vaoSurfaces[i];
} else {
drawSurf = surface;
}
// we will add shadows even if the main object isn't visible in the view
// stencil shadows can't do personal models unless I polyhedron clip
@ -390,7 +397,7 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) {
&& fogNum == 0
&& !(ent->e.renderfx & ( RF_NOSHADOW | RF_DEPTHHACK ) )
&& shader->sort == SS_OPAQUE ) {
R_AddDrawSurf( (void *)&model->vaoSurfaces[i], tr.shadowShader, 0, qfalse, qfalse, 0 );
R_AddDrawSurf( drawSurf, tr.shadowShader, 0, qfalse, qfalse, 0 );
}
// projection shadows work fine with personal models
@ -398,12 +405,12 @@ void R_AddMD3Surfaces( trRefEntity_t *ent ) {
&& fogNum == 0
&& (ent->e.renderfx & RF_SHADOW_PLANE )
&& shader->sort == SS_OPAQUE ) {
R_AddDrawSurf( (void *)&model->vaoSurfaces[i], tr.projectionShadowShader, 0, qfalse, qfalse, 0 );
R_AddDrawSurf( drawSurf, tr.projectionShadowShader, 0, qfalse, qfalse, 0 );
}
// don't add third_person objects if not viewing through a portal
if ( !personalModel ) {
R_AddDrawSurf((void *)&model->vaoSurfaces[i], shader, fogNum, qfalse, qfalse, cubemapIndex );
R_AddDrawSurf( drawSurf, shader, fogNum, qfalse, qfalse, cubemapIndex );
}
surface++;

View file

@ -664,6 +664,12 @@ static qboolean R_LoadMD3(model_t * mod, int lod, void *buffer, int bufferSize,
surf++;
}
if (mdvModel->numFrames > 1 && !glRefConfig.gpuVertexAnimation)
{
mdvModel->numVaoSurfaces = 0;
mdvModel->vaoSurfaces = NULL;
}
else
{
srfVaoMdvMesh_t *vaoSurf;

View file

@ -290,6 +290,8 @@ static qboolean RB_UpdateSunFlareVis(void)
ri.Printf(PRINT_DEVELOPER, "Waited %d iterations\n", iter);
}
// Note: On desktop OpenGL this is a sample count (glRefConfig.occlusionQueryTarget == GL_SAMPLES_PASSED)
// but on OpenGL ES this is a boolean (glRefConfig.occlusionQueryTarget == GL_ANY_SAMPLES_PASSED)
qglGetQueryObjectuiv(tr.sunFlareQuery[tr.sunFlareQueryIndex], GL_QUERY_RESULT, &sampleCount);
return sampleCount > 0;
}

View file

@ -40,7 +40,14 @@ R_DrawElements
void R_DrawElements( int numIndexes, int firstIndex )
{
qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t)));
if (tess.useCacheVao)
{
VaoCache_DrawElements(numIndexes, firstIndex);
}
else
{
qglDrawElements(GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, BUFFER_OFFSET(firstIndex * sizeof(glIndex_t)));
}
}
@ -1720,6 +1727,8 @@ void RB_EndSurface( void ) {
tess.numIndexes = 0;
tess.numVertexes = 0;
tess.firstIndex = 0;
tess.useCacheVao = qfalse;
tess.useInternalVao = qfalse;
GLimp_LogComment( "----------\n" );
}

View file

@ -676,7 +676,7 @@ static struct
srfVert_t vertexes[VAOCACHE_QUEUE_MAX_VERTEXES];
int vertexCommitSize;
glIndex_t indexes[VAOCACHE_QUEUE_MAX_INDEXES];
vaoCacheGlIndex_t indexes[VAOCACHE_QUEUE_MAX_INDEXES];
int indexCommitSize;
}
vcq;
@ -687,18 +687,13 @@ vcq;
// srfVert_t is 60 bytes
// assuming each vert is referenced 4 times, need 16 bytes (4 glIndex_t) per vert
// -> need about 4/15ths the space for indexes as vertexes
#if GL_INDEX_TYPE == GL_UNSIGNED_SHORT
#define VAOCACHE_VERTEX_BUFFER_SIZE (sizeof(srfVert_t) * USHRT_MAX)
#define VAOCACHE_INDEX_BUFFER_SIZE (sizeof(glIndex_t) * USHRT_MAX * 4)
#else // GL_UNSIGNED_INT
#define VAOCACHE_VERTEX_BUFFER_SIZE (16 * 1024 * 1024)
#define VAOCACHE_INDEX_BUFFER_SIZE (5 * 1024 * 1024)
#endif
typedef struct buffered_s
{
void *data;
int size;
glIndex_t *indexes;
int numIndexes;
int bufferOffset;
}
buffered_t;
@ -736,7 +731,7 @@ void VaoCache_Commit(void)
buffered_t *indexSet2 = indexSet;
for (surf = vcq.surfaces; surf < end; surf++, indexSet2++)
{
if (surf->indexes != indexSet2->data || (surf->numIndexes * sizeof(glIndex_t)) != indexSet2->size)
if (surf->indexes != indexSet2->indexes || surf->numIndexes != indexSet2->numIndexes)
break;
}
@ -750,7 +745,7 @@ void VaoCache_Commit(void)
// If found, use it
if (indexSet < vc.surfaceIndexSets + vc.numSurfaces)
{
tess.firstIndex = indexSet->bufferOffset / sizeof(glIndex_t);
tess.firstIndex = indexSet->bufferOffset / glRefConfig.vaoCacheGlIndexSize;
//ri.Printf(PRINT_ALL, "firstIndex %d numIndexes %d as %d\n", tess.firstIndex, tess.numIndexes, (int)(batchLength - vc.batchLengths));
//ri.Printf(PRINT_ALL, "vc.numSurfaces %d vc.numBatches %d\n", vc.numSurfaces, vc.numBatches);
}
@ -759,20 +754,21 @@ void VaoCache_Commit(void)
else
{
srfVert_t *dstVertex = vcq.vertexes;
glIndex_t *dstIndex = vcq.indexes;
vaoCacheGlIndex_t *dstIndex = vcq.indexes;
unsigned short *dstIndexUshort = (unsigned short *)vcq.indexes;
batchLength = vc.batchLengths + vc.numBatches;
*batchLength = vcq.numSurfaces;
vc.numBatches++;
tess.firstIndex = vc.indexOffset / sizeof(glIndex_t);
tess.firstIndex = vc.indexOffset / glRefConfig.vaoCacheGlIndexSize;
vcq.vertexCommitSize = 0;
vcq.indexCommitSize = 0;
for (surf = vcq.surfaces; surf < end; surf++)
{
glIndex_t *srcIndex = surf->indexes;
int vertexesSize = surf->numVerts * sizeof(srfVert_t);
int indexesSize = surf->numIndexes * sizeof(glIndex_t);
int indexesSize = surf->numIndexes * glRefConfig.vaoCacheGlIndexSize;
int i, indexOffset = (vc.vertexOffset + vcq.vertexCommitSize) / sizeof(srfVert_t);
Com_Memcpy(dstVertex, surf->vertexes, vertexesSize);
@ -781,13 +777,21 @@ void VaoCache_Commit(void)
vcq.vertexCommitSize += vertexesSize;
indexSet = vc.surfaceIndexSets + vc.numSurfaces;
indexSet->data = surf->indexes;
indexSet->size = indexesSize;
indexSet->indexes = surf->indexes;
indexSet->numIndexes = surf->numIndexes;
indexSet->bufferOffset = vc.indexOffset + vcq.indexCommitSize;
vc.numSurfaces++;
for (i = 0; i < surf->numIndexes; i++)
*dstIndex++ = *srcIndex++ + indexOffset;
if (glRefConfig.vaoCacheGlIndexType == GL_UNSIGNED_SHORT)
{
for (i = 0; i < surf->numIndexes; i++)
*dstIndexUshort++ = *srcIndex++ + indexOffset;
}
else
{
for (i = 0; i < surf->numIndexes; i++)
*dstIndex++ = *srcIndex++ + indexOffset;
}
vcq.indexCommitSize += indexesSize;
}
@ -810,9 +814,30 @@ void VaoCache_Commit(void)
}
}
void VaoCache_DrawElements(int numIndexes, int firstIndex)
{
assert( glState.currentVao == vc.vao );
qglDrawElements(GL_TRIANGLES, numIndexes, glRefConfig.vaoCacheGlIndexType, BUFFER_OFFSET(firstIndex * glRefConfig.vaoCacheGlIndexSize));
}
void VaoCache_Init(void)
{
vc.vao = R_CreateVao("VaoCache", NULL, VAOCACHE_VERTEX_BUFFER_SIZE, NULL, VAOCACHE_INDEX_BUFFER_SIZE, VAO_USAGE_DYNAMIC);
int vertexBufferSize;
int indexBufferSize;
if (glRefConfig.vaoCacheGlIndexType == GL_UNSIGNED_SHORT)
{
vertexBufferSize = sizeof(srfVert_t) * USHRT_MAX;
indexBufferSize = sizeof(unsigned short) * USHRT_MAX * 4;
}
else
{
vertexBufferSize = VAOCACHE_VERTEX_BUFFER_SIZE;
indexBufferSize = VAOCACHE_INDEX_BUFFER_SIZE;
}
vc.vao = R_CreateVao("VaoCache", NULL, vertexBufferSize, NULL, indexBufferSize, VAO_USAGE_DYNAMIC);
vc.vao->attribs[ATTR_INDEX_POSITION].enabled = 1;
vc.vao->attribs[ATTR_INDEX_TEXCOORD].enabled = 1;
@ -881,7 +906,7 @@ void VaoCache_BindVao(void)
void VaoCache_CheckAdd(qboolean *endSurface, qboolean *recycleVertexBuffer, qboolean *recycleIndexBuffer, int numVerts, int numIndexes)
{
int vertexesSize = sizeof(srfVert_t) * numVerts;
int indexesSize = sizeof(glIndex_t) * numIndexes;
int indexesSize = glRefConfig.vaoCacheGlIndexSize * numIndexes;
if (vc.vao->vertexesSize < vc.vertexOffset + vcq.vertexCommitSize + vertexesSize)
{
@ -924,7 +949,7 @@ void VaoCache_CheckAdd(qboolean *endSurface, qboolean *recycleVertexBuffer, qboo
*endSurface = qtrue;
}
if (VAOCACHE_QUEUE_MAX_INDEXES * sizeof(glIndex_t) < vcq.indexCommitSize + indexesSize)
if (VAOCACHE_QUEUE_MAX_INDEXES * glRefConfig.vaoCacheGlIndexSize < vcq.indexCommitSize + indexesSize)
{
//ri.Printf(PRINT_ALL, "out of queued indexes\n");
*endSurface = qtrue;
@ -964,5 +989,5 @@ void VaoCache_AddSurface(srfVert_t *verts, int numVerts, glIndex_t *indexes, int
vcq.numSurfaces++;
vcq.vertexCommitSize += sizeof(srfVert_t) * numVerts;
vcq.indexCommitSize += sizeof(glIndex_t) * numIndexes;
vcq.indexCommitSize += glRefConfig.vaoCacheGlIndexSize * numIndexes;
}

View file

@ -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_preferOpenGLES;
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
@ -306,8 +328,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 );
}
@ -369,6 +394,12 @@ GLimp_SetMode
*/
static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qboolean fixedFunction)
{
struct GLimp_ContextType {
int profileMask;
int majorVersion;
int minorVersion;
} contexts[4];
int numContexts, type;
const char *glstring;
int perChannelColorBits;
int colorBits, depthBits, stencilBits;
@ -499,6 +530,63 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool
stencilBits = r_stencilbits->value;
samples = r_ext_multisample->value;
numContexts = 0;
if ( !fixedFunction ) {
int profileMask;
qboolean preferOpenGLES;
SDL_GL_ResetAttributes();
SDL_GL_GetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, &profileMask );
preferOpenGLES = ( r_preferOpenGLES->integer == 1 ||
( r_preferOpenGLES->integer == -1 && profileMask == SDL_GL_CONTEXT_PROFILE_ES ) );
if ( preferOpenGLES ) {
#ifdef __EMSCRIPTEN__
// WebGL 2.0 isn't fully backward compatible so you have to ask for it specifically
contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_ES;
contexts[numContexts].majorVersion = 3;
contexts[numContexts].minorVersion = 0;
numContexts++;
#endif
contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_ES;
contexts[numContexts].majorVersion = 2;
contexts[numContexts].minorVersion = 0;
numContexts++;
}
contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_CORE;
contexts[numContexts].majorVersion = 3;
contexts[numContexts].minorVersion = 2;
numContexts++;
contexts[numContexts].profileMask = 0;
contexts[numContexts].majorVersion = 2;
contexts[numContexts].minorVersion = 0;
numContexts++;
if ( !preferOpenGLES ) {
#ifdef __EMSCRIPTEN__
contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_ES;
contexts[numContexts].majorVersion = 3;
contexts[numContexts].minorVersion = 0;
numContexts++;
#endif
contexts[numContexts].profileMask = SDL_GL_CONTEXT_PROFILE_ES;
contexts[numContexts].majorVersion = 2;
contexts[numContexts].minorVersion = 0;
numContexts++;
}
} else {
contexts[numContexts].profileMask = 0;
contexts[numContexts].majorVersion = 1;
contexts[numContexts].minorVersion = 1;
numContexts++;
}
for (i = 0; i < 16; i++)
{
int testColorBits, testDepthBits, testStencilBits;
@ -631,82 +719,68 @@ static int GLimp_SetMode(int mode, qboolean fullscreen, qboolean noborder, qbool
SDL_SetWindowIcon( SDL_window, icon );
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);
for ( type = 0; type < numContexts; type++ ) {
char contextName[32];
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)
{
ri.Printf(PRINT_ALL, "SDL_GL_CreateContext failed: %s\n", SDL_GetError());
ri.Printf(PRINT_ALL, "Reverting to default 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);
switch ( contexts[type].profileMask ) {
default:
case 0:
Com_sprintf( contextName, sizeof( contextName ), "OpenGL %d.%d",
contexts[type].majorVersion, contexts[type].minorVersion );
break;
case SDL_GL_CONTEXT_PROFILE_CORE:
Com_sprintf( contextName, sizeof( contextName ), "OpenGL %d.%d Core",
contexts[type].majorVersion, contexts[type].minorVersion );
break;
case SDL_GL_CONTEXT_PROFILE_ES:
Com_sprintf( contextName, sizeof( contextName ), "OpenGL ES %d.%d",
contexts[type].majorVersion, contexts[type].minorVersion );
break;
}
else
SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, contexts[type].profileMask );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, contexts[type].majorVersion );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, contexts[type].minorVersion );
SDL_glContext = SDL_GL_CreateContext( SDL_window );
if ( !SDL_glContext )
{
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;
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
{
SDL_glContext = NULL;
}
if ( !SDL_glContext )
{
if( ( SDL_glContext = SDL_GL_CreateContext( SDL_window ) ) == NULL )
{
ri.Printf( PRINT_DEVELOPER, "SDL_GL_CreateContext failed: %s\n", SDL_GetError( ) );
SDL_DestroyWindow( SDL_window );
SDL_window = NULL;
ri.Printf( PRINT_ALL, "SDL_GL_CreateContext() for %s context failed: %s\n", contextName, SDL_GetError() );
continue;
}
if ( !GLimp_GetProcAddresses( fixedFunction ) )
{
ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() failed\n" );
ri.Printf( PRINT_ALL, "GLimp_GetProcAddresses() for %s context failed\n", contextName );
GLimp_ClearProcAddresses();
SDL_GL_DeleteContext( SDL_glContext );
SDL_glContext = NULL;
SDL_DestroyWindow( SDL_window );
SDL_window = NULL;
continue;
}
if ( contexts[type].profileMask == SDL_GL_CONTEXT_PROFILE_CORE ) {
const char *renderer;
renderer = (const char *)qglGetString( GL_RENDERER );
if ( !renderer || strstr( renderer, "Software Renderer" ) || strstr( renderer, "Software Rasterizer" ) )
{
ri.Printf( PRINT_ALL, "GL_RENDERER is %s, rejecting %s context\n", renderer, contextName );
GLimp_ClearProcAddresses();
SDL_GL_DeleteContext( SDL_glContext );
SDL_glContext = NULL;
continue;
}
}
break;
}
if ( !SDL_glContext ) {
SDL_DestroyWindow( SDL_window );
SDL_window = NULL;
continue;
}
qglClearColor( 0, 0, 0, 1 );
@ -815,7 +889,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 )
@ -996,6 +1070,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_preferOpenGLES = ri.Cvar_Get( "r_preferOpenGLES", "-1", CVAR_ARCHIVE | CVAR_LATCH );
if( ri.Cvar_VariableIntegerValue( "com_abnormalExit" ) )
{

View file

@ -31,6 +31,10 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include <ctype.h>
#include <errno.h>
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
#ifndef DEDICATED
#ifdef USE_LOCAL_HEADERS
# include "SDL.h"
@ -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;
}

View file

@ -38,6 +38,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include <fcntl.h>
#include <fenv.h>
#include <sys/wait.h>
#include <time.h>
qboolean stdinIsATTY;
@ -548,11 +549,15 @@ void Sys_Sleep( int msec )
}
else
{
struct timespec req;
// With nothing to select() on, we can't wait indefinitely
if( msec < 0 )
msec = 10;
usleep( msec * 1000 );
req.tv_sec = msec/1000;
req.tv_nsec = (msec%1000)*1000000;
nanosleep(&req, NULL);
}
}

View file

@ -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"}
]
}
}

116
engine/code/web/client.html Normal file
View file

@ -0,0 +1,116 @@
<!DOCTYPE html><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>__CLIENTBIN__ Emscripten demo</title>
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: rgb(0, 0, 0); display:flex; align-items: center; justify-content: center; }
canvas { max-width: 100%; max-height: 100%; min-width: 100%; min-height: 100%; object-fit: contain; }
</style>
<canvas id=canvas></canvas>
<script type=module>
// These strings are set in the generated HTML file in the build directory.
let CLIENTBIN = '__CLIENTBIN__';
let BASEGAME = '__BASEGAME__';
let EMSCRIPTEN_PRELOAD_FILE = Number('__EMSCRIPTEN_PRELOAD_FILE__');
// Detect if it's not the generated HTML file.
let clientHtmlFallback = (CLIENTBIN === '\_\_CLIENTBIN\_\_');
// Path or URL containing the client engine .js, .wasm, and possibly .data.
let enginePath = './';
// Path or URL containing fs_game directories.
let dataPath = './';
// Path or URL for config file that specifies the files to load for each fs_game.
let configFilename = `./${CLIENTBIN}-config.json`;
// If displaying the unmodified HTML file, fallback to defaults.
if (clientHtmlFallback) {
CLIENTBIN='ioquake3';
BASEGAME='baseq3';
EMSCRIPTEN_PRELOAD_FILE=0;
configFilename='./client-config.json';
}
if (window.location.protocol === 'file:') throw new Error(`Unfortunately browser security restrictions prevent loading wasm from a file: URL. This file must be loaded from a web server. The easiest way to do this is probably to use Python\'s built-in web server by running \`python3 -m http.server\` in the top level source directory and then navigate to http://localhost:8000/build/debug-emscripten-wasm32/${CLIENTBIN}.html`);
// First set up the command line arguments and the Emscripten filesystem.
const urlParams = new URLSearchParams(window.location.search);
const com_basegame = urlParams.get('com_basegame') || BASEGAME;
const fs_basegame = urlParams.get('fs_basegame') || '';
const fs_game = urlParams.get('fs_game') || '';
let generatedArguments = `
+set sv_pure 0
+set net_enabled 0
+set r_mode -2
+set com_basegame "${com_basegame}"
+set fs_basegame "${fs_basegame}"
+set fs_game "${fs_game}"
`;
// Note that unfortunately "+" needs to be encoded as "%2b" in URL query strings or it will be stripped by the browser.
const queryArgs = urlParams.get('args');
if (queryArgs) generatedArguments += ` ${queryArgs} `;
// If displaying the unmodified HTML file, the engine and data are probably located in a different directory.
if (clientHtmlFallback) {
// If buildPath is not specified, try to find a build in one of a few default paths.
let buildPath = urlParams.get('buildPath');
if (buildPath && !buildPath.endsWith('/')) buildPath += '/';
const buildPaths = buildPath ? [buildPath] : ['../../build/debug-emscripten-wasm32/', '../../build/release-emscripten-wasm32/', './'];
const scriptPaths = buildPaths.map(buildPath => buildPath + `${CLIENTBIN}_opengl2.wasm32.js`);
const scriptResponses = await Promise.all(scriptPaths.map(p => fetch(p, {method: 'HEAD'})));
const validBuilds = scriptResponses.filter(r => r.ok).length;
const goodURL = (newPath) => {
const url = new URL(window.location);
url.searchParams.set('buildPath', newPath);
return url.toString().replace(/%2f/gi, '/');
};
if (validBuilds === 0) throw new Error(`Didn't find any wasm builds. Run \`emmake make debug\` to build one, or use the buildPath query parameter to specify a directory containing ${CLIENTBIN}_opengl2.wasm32.[js,wasm,data], e.g. ${goodURL('../../build/debug-emscripten-wasm32/')}`);
if (validBuilds > 1) throw new Error(`Found multiple valid builds at the following paths: [${buildPaths.filter((path, i)=>scriptResponses[i].ok)}]. Please specify which one to run by adding a buildPath query parameter to the URL, e.g. ${goodURL(buildPaths.filter((path, i)=>scriptResponses[i].ok)[0])}`);
const buildIndex = scriptResponses.findIndex(r => r.ok);
enginePath = buildPaths[buildIndex];
dataPath = buildPaths[buildIndex];
}
const dataURL = new URL(dataPath, location.origin + location.pathname);
const configPromise = ( EMSCRIPTEN_PRELOAD_FILE === 1 ) ? Promise.resolve({[BASEGAME]: {files: []}})
: fetch(configFilename).then(r => r.ok ? r.json() : { /* empty config */ });
const ioquake3 = (await import(enginePath + `${CLIENTBIN}_opengl2.wasm32.js`)).default;
ioquake3({
canvas: canvas,
arguments: generatedArguments.trim().split(/\s+/),
locateFile: (file) => enginePath + file,
preRun: [async (module) => {
module.addRunDependency('setup-ioq3-filesystem');
try {
const config = await configPromise;
const gamedirs = [com_basegame,fs_basegame,fs_game];
for (let g = 0; g < gamedirs.length; g++) {
const gamedir = gamedirs[g];
if (gamedir === '') {
continue;
}
if (config[gamedir] === null
|| config[gamedir].files === null) {
console.warn(`Game directory '${gamedir}' cannot be used. It must have files listed in ${configFilename}.`);
continue;
}
const files = config[gamedir].files;
const fetches = files.map(file => fetch(new URL(file.src, dataURL)));
for (let i = 0; i < files.length; i++) {
const response = await fetches[i];
if (!response.ok) continue;
const data = await response.arrayBuffer();
let name = files[i].src.match(/[^/]+$/)[0];
let dir = files[i].dst;
module.FS.mkdirTree(dir);
module.FS.writeFile(`${dir}/${name}`, new Uint8Array(data));
}
}
} finally {
module.removeRunDependency('setup-ioq3-filesystem');
}
}],
});
</script>

View file

@ -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
* Many, many bug fixes
The map editor and associated compiling tools are not included. We suggest you
@ -98,6 +99,20 @@ 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).
3. Copy or symlink your baseq3 pk3 files into the `build/debug-emscripten-wasm32/baseq3`
directory so they can be loaded at run-time. Only game files listed in
`client-config.json` will be loaded.
4. Start a web server serving this directory. `python3 -m http.server`
is an easy default that you may already have installed.
5. Open `http://localhost:8000/build/debug-emscripten-wasm32/ioquake3.html`
in a web browser. Open the developer console to see errors and warnings.
6. 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.
@ -115,6 +130,8 @@ The following variables may be set, either on the command line or in
Makefile.local:
```
DEPEND_MAKEFILE - set to 0 to disable rebuilding all targets when
the Makefile or Makefile.local is changed
CFLAGS - use this for custom CFLAGS
V - set to show cc command line when building
DEFAULT_BASEDIR - extra path to search for baseq3 and such
@ -128,6 +145,8 @@ Makefile.local:
SERVERBIN - rename 'ioq3ded' server binary
CLIENTBIN - rename 'ioquake3' client binary
USE_RENDERER_DLOPEN - build and use the renderer in a library
BUILD_RENDERER_OPENGL1 build the opengl1 client / renderer library
BUILD_RENDERER_OPENGL2 build the opengl2 client / renderer library
USE_YACC - use yacc to update code/tools/lcc/lburg/gram.c
BASEGAME - rename 'baseq3'
BASEGAME_CFLAGS - custom CFLAGS for basegame
@ -155,11 +174,31 @@ Makefile.local:
DEBUG_CFLAGS - C compiler flags to use for building debug version
COPYDIR - the target installation directory
TEMPDIR - specify user defined directory for temp files
EMSCRIPTEN_PRELOAD_FILE - set to 1 to package 'baseq3' (BASEGAME) directory
containing pk3s and loose files as a single
.data file that is loaded instead of listing
individual files in client-config.json
```
The defaults for these variables differ depending on the target platform.
# OpenGL ES support
The opengl2 renderer (the default) supports OpenGL ES 2+. Though there
are many missing features and the performance may not be sufficient for
embedded System-on-a-Chip and mobile platforms.
The opengl1 renderer does not have OpenGL ES support.
The opengl2 renderer will try both OpenGL and OpenGL ES APIs to find one that
works. The `r_preferOpenGLES` cvar controls which API to try first.
Set it to -1 for auto (default), 0 for OpenGL, and 1 for OpenGL ES. It should be
set using command line arguments:
ioquake3 +set cl_renderer opengl2 +set r_preferOpenGLES 1
# Console
## New cvars

View file

@ -63,6 +63,14 @@ For Win32:
CVARS
-------------------------------------------------------------------------------
Cvars for API:
* `r_preferOpenGLES` - This sets the preference for using OpenGL or OpenGL ES 2.
Many features are not supported when using OpenGL ES such as sun shadows and HDR.
1 - Prefer OpenGL ES 2+.
0 - Prefer desktop OpenGL.
-1 - Automatically pick (default).
Cvars for simple rendering features:
* `r_ext_compressed_textures` - Automatically compress textures.