From dd4fc45be3429b4f62bcc49e1dbaf48cbc1ae857 Mon Sep 17 00:00:00 2001 From: veclavtalica Date: Fri, 21 Feb 2025 18:07:04 +0300 Subject: [PATCH] attempt to build web version out of emscripten legacy gl wrapper --- CMakeLists.txt | 29 ++++++------- apps/demos/platformer/CMakeLists.txt | 4 +- bin/twnbuild | 43 ++++++++++++------- src/rendering/twn_gl_15_rendering.c | 63 ++++++++++++++++++++++++---- src/rendering/twn_gl_any_rendering.c | 20 ++------- src/rendering/twn_text.c | 3 -- src/shell_minimal.html | 3 +- src/twn_filewatch.c | 18 +++++++- src/twn_loop.c | 6 --- src/twn_stb.c | 2 + 10 files changed, 123 insertions(+), 68 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e893e46..fb83db8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,15 +30,15 @@ option(TWN_USE_AMALGAM "Enable use of twn_amalgam.c as a single compilation unit option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON) set(TWN_OUT_DIR ${CMAKE_SOURCE_DIR} CACHE PATH "Artifact destination") -# todo: figure out how to compile for dynamic linking instead -if(EMSCRIPTEN) +# todo: figure out how to compile for dynamic linking instead? +if(HAIKU OR EMSCRIPTEN) if(TWN_FEATURE_DYNLIB_GAME) message(WARNING "TWN_FEATURE_DYNLIB_GAME is set, but not supported - it is turned off") set(TWN_FEATURE_DYNLIB_GAME OFF CACHE INTERNAL "") endif() endif() -if(HAIKU) +if(HAIKU OR EMSCRIPTEN) if(TWN_SANITIZE) message(WARNING "TWN_SANITIZE is set, but not supported - it is turned off") set(TWN_SANITIZE OFF CACHE INTERNAL "") @@ -64,11 +64,7 @@ set(PHYSFS_ARCHIVE_7Z OFF CACHE INTERNAL "") add_subdirectory(third-party/physfs ${CMAKE_CURRENT_BINARY_DIR}/third-party/physfs SYSTEM) add_subdirectory(third-party/libxm ${CMAKE_CURRENT_BINARY_DIR}/third-party/libxm SYSTEM) -if(EMSCRIPTEN) - set(TWN_RENDERING_API WEBGL1) -else() - set(TWN_RENDERING_API OPENGL_15) -endif() +set(TWN_RENDERING_API OPENGL_15) if(TWN_RENDERING_API MATCHES OPENGL_15) set(SYSTEM_SOURCE_FILES ${SYSTEM_SOURCE_FILES} @@ -140,7 +136,7 @@ set_target_properties(${TWN_TARGET} PROPERTIES # precompile commonly used not-so-small headers target_precompile_headers(${TWN_TARGET} PRIVATE $<$>:third-party/glad/include/glad/glad.h> - ${SDL2_INCLUDE_DIR}/SDL.h + $<$>:${SDL2_INCLUDE_DIR}/SDL.h> third-party/physfs/src/physfs.h) @@ -170,6 +166,13 @@ function(give_options_without_warnings target) $<$:-fstack-protector-all -fsanitize=undefined -fsanitize=address> $<$:-gsource-map>) + set(LINK_FLAGS + -Bsymbolic-functions + + $<$:-sLEGACY_GL_EMULATION -sGL_FFP_ONLY -sGL_UNSAFE_OPTS> + $<$>:-Wl,--as-needed> + $<$:-Wl,--hash-style=gnu>) + set(LINK_FLAGS_RELEASE $<$:-Wl,--strip-all> ${BUILD_FLAGS_RELEASE}) @@ -194,12 +197,10 @@ function(give_options_without_warnings target) target_link_options(${target} PUBLIC ${BUILD_FLAGS} + ${LINK_FLAGS} # -Wl,--no-undefined # TODO: use later for implementing no-libc $<$:${LINK_FLAGS_RELEASE}> - $<$:${BUILD_FLAGS_DEBUG}> - -Bsymbolic-functions - -Wl,--as-needed - $<$:-Wl,--hash-style=gnu>) + $<$:${BUILD_FLAGS_DEBUG}>) get_target_property(target_type ${target} TYPE) if (target_type MATCHES SHARED_LIBRARY) @@ -274,7 +275,7 @@ endfunction() function(link_deps target) target_link_libraries(${target} PUBLIC $<$>:SDL2::SDL2> - $<$>:SDL2::SDL2main> + $<$,$>>:SDL2::SDL2main> physfs-static xms) target_include_directories(${target} PUBLIC ${SDL2_INCLUDE_DIRS}) diff --git a/apps/demos/platformer/CMakeLists.txt b/apps/demos/platformer/CMakeLists.txt index 2d7593f..1f17161 100644 --- a/apps/demos/platformer/CMakeLists.txt +++ b/apps/demos/platformer/CMakeLists.txt @@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() -add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build) +add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn) set(SOURCE_FILES game.c @@ -20,4 +20,4 @@ set(SOURCE_FILES scenes/ingame.c scenes/ingame.h ) -use_townengine("${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR}) +use_townengine("${SOURCE_FILES}" ${TWN_OUT_DIR}) diff --git a/bin/twnbuild b/bin/twnbuild index fa671cb..a8fac73 100755 --- a/bin/twnbuild +++ b/bin/twnbuild @@ -7,45 +7,56 @@ from pathlib import Path from sys import argv import tomllib +#TODO: support for default pack override +#TODO: automatic full rebuild on git head change (such as new commits) + has_ninja = getoutput("command -v ninja") != "" has_clang = getoutput("command -v clang") != "" -#TODO: support for default pack override +target_web = "--target=web" in argv + +#TODO: infer what "native" means for current env +build_dir = "build/web" if target_web else "build/native" +build_dir += "/release" if "--release" in argv else "/debug" + +cmake = ["emcmake", "cmake"] if target_web else ["cmake"] +# cmake configuration command +command = [] -cmake = ["cmake"] # check whether clang is around (it's just better) if has_clang: - cmake += ["-DCMAKE_C_COMPILER=clang"] + command += ["-DCMAKE_C_COMPILER=clang"] # check whether ninja is around (you better start running) if has_ninja: - cmake += ["-G", "Ninja"] -cmake += ["-B", "build"] + command += ["-G", "Ninja"] + +command += ["-B", build_dir] # TODO: have it --fast=1 instead, where separate --debug=0 would mean stripping the debug info if "--release" in argv: - cmake += ["-DCMAKE_BUILD_TYPE=Release"] + command += ["-DCMAKE_BUILD_TYPE=Release"] elif "--debug" in argv: - cmake += ["-DCMAKE_BUILD_TYPE=Debug"] + command += ["-DCMAKE_BUILD_TYPE=Debug"] if "--unified=1" in argv: - cmake += ["-DTWN_FEATURE_DYNLIB_GAME=ON"] + command += ["-DTWN_FEATURE_DYNLIB_GAME=ON"] elif "--unified=0" in argv: - cmake += ["-DTWN_FEATURE_DYNLIB_GAME=OFF"] + command += ["-DTWN_FEATURE_DYNLIB_GAME=OFF"] if "--sanitize=1" in argv: - cmake += ["-DTWN_SANITIZE=ON"] + command += ["-DTWN_SANITIZE=ON"] elif "--sanitize=0" in argv: - cmake += ["-DTWN_SANITIZE=OFF"] + command += ["-DTWN_SANITIZE=OFF"] -cmake += [f"-DTWN_OUT_DIR={getcwd()}"] +command += [f"-DTWN_OUT_DIR={getcwd()}"] # pass arbitrary arguments over if "--" in argv: - cmake += argv[argv.index("--")+1:] + command += argv[argv.index("--")+1:] # if no root cmake file is present, infer it from `twn.toml:game.interpreter` if not Path("CMakeLists.txt").is_file(): config = tomllib.loads(Path("data/twn.toml").read_text()) - cmake += ["-S", expandvars(config["game"]["interpreter"])] + command += ["-S", expandvars(config["game"]["interpreter"])] -run(cmake, check=True) -run(["cmake", "--build", "build", "--parallel"], check=True) +run(cmake + command, check=True) +run(["cmake"] + ["--build", build_dir, "--parallel"], check=True) diff --git a/src/rendering/twn_gl_15_rendering.c b/src/rendering/twn_gl_15_rendering.c index 72944e4..b443a84 100644 --- a/src/rendering/twn_gl_15_rendering.c +++ b/src/rendering/twn_gl_15_rendering.c @@ -5,7 +5,13 @@ #include "twn_types.h" #include "twn_deferred_commands.h" +#ifdef EMSCRIPTEN +#define GL_GLEXT_PROTOTYPES +#include +#include +#else #include +#endif #include @@ -35,13 +41,16 @@ static void APIENTRY opengl_log(GLenum source, bool render_init(void) { +#ifndef EMSCRIPTEN if (gladLoadGLLoader(&SDL_GL_GetProcAddress) == 0) { CRY("Init", "GLAD failed"); return false; } +#endif log_info("OpenGL context: %s\n", glGetString(GL_VERSION)); +#ifndef EMSCRIPTEN glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST); glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST); glHint(GL_FOG_HINT, GL_FASTEST); @@ -51,6 +60,7 @@ bool render_init(void) { glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(opengl_log, NULL); } +#endif return true; } @@ -88,16 +98,21 @@ static void finally_use_space_pipeline(void) { depth_range_high = 1.0; depth_range_low = 0.0; +#ifndef EMSCRIPTEN static GLuint list = 0; if (!list) { list = glGenLists(1); - glNewList(list, GL_COMPILE); { + glNewList(list, GL_COMPILE); +#endif + { glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glShadeModel(GL_FLAT); + #ifndef EMSCRIPTEN if (GLAD_GL_ARB_depth_clamp) glDisable(GL_DEPTH_CLAMP); + #endif if (ctx.cull_faces) glEnable(GL_CULL_FACE); @@ -110,10 +125,12 @@ static void finally_use_space_pipeline(void) { /* solid white, no modulation */ glColor4ub(255, 255, 255, 255); +#ifndef EMSCRIPTEN } glEndList(); - } glCallList(list); +#endif + } glMatrixMode(GL_PROJECTION); glLoadMatrixf(&camera_projection_matrix.row[0].x); @@ -135,26 +152,30 @@ static void finally_use_2d_pipeline(void) { if (pipeline_last_used == PIPELINE_2D) return; +#ifndef EMSCRIPTEN static GLuint list = 0; if (!list) { list = glGenLists(1); - glNewList(list, GL_COMPILE); { +#endif glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); glShadeModel(GL_FLAT); + #ifndef EMSCRIPTEN /* removes near/far plane comparison and discard */ if (GLAD_GL_ARB_depth_clamp) glEnable(GL_DEPTH_CLAMP); + #endif glEnable(GL_TEXTURE_2D); glActiveTexture(GL_TEXTURE0); glDisable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); +#ifndef EMSCRIPTEN } glEndList(); - } - glCallList(list); + } glCallList(list); +#endif glMatrixMode(GL_PROJECTION); glLoadIdentity(); @@ -180,34 +201,41 @@ static void finally_use_texture_mode(TextureMode mode) { if (texture_mode_last_used == mode) return; +#ifndef EMSCRIPTEN static GLuint lists = 0; if (!lists) { lists = glGenLists(3); /* ghostly */ glNewList(lists + 0, GL_COMPILE); { +#endif glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDepthFunc(GL_LESS); glDepthMask(GL_FALSE); glDisable(GL_ALPHA_TEST); +#ifndef EMSCRIPTEN } glEndList(); /* seethrough */ glNewList(lists + 1, GL_COMPILE); { +#endif glDisable(GL_BLEND); glDepthFunc(GL_LESS); glDepthMask(GL_TRUE); glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_EQUAL, 1.0f); +#ifndef EMSCRIPTEN } glEndList(); /* opaque */ glNewList(lists + 2, GL_COMPILE); { +#endif glDisable(GL_BLEND); glDepthFunc(GL_LESS); glDepthMask(GL_TRUE); glDisable(GL_ALPHA_TEST); +#ifndef EMSCRIPTEN } glEndList(); } @@ -219,6 +247,8 @@ static void finally_use_texture_mode(TextureMode mode) { glCallList(lists + 2); } +#endif + texture_mode_last_used = mode; } @@ -227,7 +257,11 @@ VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) { SDL_assert(bytes != 0); glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferData(GL_ARRAY_BUFFER, bytes, NULL, GL_STREAM_DRAW); +#ifndef EMSCRIPTEN void *mapping = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); +#else + void *mapping = SDL_malloc(bytes); +#endif if (!mapping) CRY("build_vertex_buffer", "Error mapping a vertex array buffer"); @@ -239,8 +273,13 @@ VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) { void finish_vertex_builder(VertexBufferBuilder *builder) { +#ifndef EMSCRIPTEN if (!glUnmapBuffer(GL_ARRAY_BUFFER)) CRY("finish_vertex_builder", "Error unmapping a vertex array buffer"); +#else + glBufferData(GL_ARRAY_BUFFER, builder->size, builder->base, GL_STREAM_DRAW); + SDL_free(builder->base); +#endif glBindBuffer(GL_ARRAY_BUFFER, 0); builder->base = 0; @@ -333,18 +372,23 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) { /* TODO: figure out which coordinates to use to not have issues with far z */ /* TODO: recalculate the list if far z requirement changes */ +#ifndef EMSCRIPTEN static GLuint list = 0; if (!list) { list = glGenLists(1); - glNewList(list, GL_COMPILE); { + glNewList(list, GL_COMPILE); +#endif + { /* note: assumes that space pipeline is applied already */ glDisable(GL_ALPHA_TEST); glDepthMask(GL_FALSE); + #ifndef EMSCRIPTEN /* removes near/far plane comparison and discard */ if (GLAD_GL_ARB_depth_clamp) glEnable(GL_DEPTH_CLAMP); + #endif glBegin(GL_QUADS); { /* up */ @@ -408,16 +452,19 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) { glVertex3f(-50.f, 50.f, -50.f); } glEnd(); + #ifndef EMSCRIPTEN if (GLAD_GL_ARB_depth_clamp) glDisable(GL_DEPTH_CLAMP); + #endif glDepthMask(GL_TRUE); glDisable(GL_TEXTURE_CUBE_MAP); +#ifndef EMSCRIPTEN } glEndList(); - } - glCallList(list); +#endif + } } diff --git a/src/rendering/twn_gl_any_rendering.c b/src/rendering/twn_gl_any_rendering.c index d61e96f..ec6e922 100644 --- a/src/rendering/twn_gl_any_rendering.c +++ b/src/rendering/twn_gl_any_rendering.c @@ -5,12 +5,13 @@ #include #ifdef EMSCRIPTEN -#include +#define GL_GLEXT_PROTOTYPES +#include +#include #else #include #endif - void setup_viewport(int x, int y, int width, int height) { glViewport(x, y, width, height); } @@ -127,12 +128,7 @@ GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps, int c SDL_assert(width > 0 && height > 0); SDL_assert(channels > 0 && channels <= 4); -#if !defined(EMSCRIPTEN) glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLboolean)generate_mipmaps); -#else - if (generate_mipmaps) - glGenerateMipmap(GL_TEXTURE_2D); -#endif if (filter == TEXTURE_FILTER_NEAREAST) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -145,24 +141,14 @@ GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps, int c glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); -#if !defined(EMSCRIPTEN) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); -#endif int format_internal, format; if (channels == 4) { - #ifdef EMSCRIPTEN - format_internal = GL_RGBA; - #else format_internal = GL_RGBA8; - #endif format = GL_RGBA; } else if (channels == 3) { - #ifdef EMSCRIPTEN - format_internal = GL_RGBA; - #else format_internal = GL_RGBA8; - #endif format = GL_RGB; } else if (channels == 1) { format_internal = GL_ALPHA; diff --git a/src/rendering/twn_text.c b/src/rendering/twn_text.c index 78e0e4a..52ff4ee 100644 --- a/src/rendering/twn_text.c +++ b/src/rendering/twn_text.c @@ -398,7 +398,4 @@ void finally_draw_text(FontData const *font_data, }; arrpush(deferred_commands, final_command); - - /* TODO: why doesn't it get restored if not placed here? */ - // glDepthMask(GL_TRUE); } diff --git a/src/shell_minimal.html b/src/shell_minimal.html index 871ccaf..75d3c4c 100644 --- a/src/shell_minimal.html +++ b/src/shell_minimal.html @@ -127,7 +127,8 @@ monitorRunDependencies: (left) => { this.totalDependencies = Math.max(this.totalDependencies, left); Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.'); - } + }, + GL_MAX_TEXTURE_IMAGE_UNITS = 2 }; Module.setStatus('Downloading...'); window.onerror = () => { diff --git a/src/twn_filewatch.c b/src/twn_filewatch.c index 12e7d4a..2d135e9 100644 --- a/src/twn_filewatch.c +++ b/src/twn_filewatch.c @@ -2,12 +2,12 @@ #include "twn_util.h" #include "twn_engine_context_c.h" +#ifndef EMSCRIPTEN #define DMON_IMPL #include #include #include - struct FilewatchEntry { char *path; FilewatchCallback callback; @@ -118,3 +118,19 @@ void filewatch_poll(void) { SDL_UnlockMutex(filewatcher_lock); } + +#else + +bool filewatch_add_directory(char const *dir, FilewatchCallback callback) { + (void)dir; (void)callback; + return true; +} + +bool filewatch_add_file(char const *filepath, FilewatchCallback callback) { + (void)filepath; (void)callback; + return true; +} + +void filewatch_poll(void) { (void)0; } + +#endif diff --git a/src/twn_loop.c b/src/twn_loop.c index d6a58f3..5cb9706 100644 --- a/src/twn_loop.c +++ b/src/twn_loop.c @@ -425,11 +425,6 @@ static bool initialize(void) { toml_datum_t datum_debug = toml_bool_in(game, "debug"); ctx.game.debug = datum_debug.ok ? datum_debug.u.b : true; -#ifdef EMSCRIPTEN - /* emscripten interpretes those as GL ES version against WebGL */ - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); -#else SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5); @@ -437,7 +432,6 @@ static bool initialize(void) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); else SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_NO_ERROR); -#endif SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); diff --git a/src/twn_stb.c b/src/twn_stb.c index 2896d13..82f5121 100644 --- a/src/twn_stb.c +++ b/src/twn_stb.c @@ -1,5 +1,7 @@ /* single compilation unit for every stb implementation */ +#include + #include #define STB_DS_IMPLEMENTATION