Compare commits

...

10 Commits

Author SHA1 Message Date
veclavtalica
a223506a5f /bin/twn: support run command for --target=web 2025-02-22 00:39:24 +03:00
veclavtalica
98d7d76a42 twn.png 2025-02-21 23:34:12 +03:00
veclavtalica
814269ab0c textures working on web, separation of vertex and index buffers (actually matters) 2025-02-21 23:34:01 +03:00
veclavtalica
d76ea06470 poll workers for non-threaded support, emscripten main loop, remove twn_model.c 2025-02-21 21:16:15 +03:00
veclavtalica
dc6b298532 proper-er vector op flags 2025-02-21 19:37:22 +03:00
veclavtalica
f25e27b102 changes to build system, emscipten progress (can render solid color, yippie!) 2025-02-21 19:06:19 +03:00
veclavtalica
dd4fc45be3 attempt to build web version out of emscripten legacy gl wrapper 2025-02-21 18:07:04 +03:00
veclavtalica
85e47dd677 api return restriction 2025-02-21 12:48:51 +03:00
veclavtalica
a020b92824 /apps/demos/crawl: facing textures 2025-02-21 02:00:00 +03:00
veclavtalica
b6347996f9 /apps/demos/crawl: new tile texture, solid walls 2025-02-21 01:36:46 +03:00
36 changed files with 449 additions and 414 deletions

View File

@ -5,6 +5,8 @@ project(townengine LANGUAGES C)
set(CMAKE_MESSAGE_LOG_LEVEL "WARNING") set(CMAKE_MESSAGE_LOG_LEVEL "WARNING")
set(CMAKE_INSTALL_MESSAGE NEVER) set(CMAKE_INSTALL_MESSAGE NEVER)
# TODO: test whether webgl 1 is good enough.
# SDL dependencies # SDL dependencies
# for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default # for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default
if(NOT EMSCRIPTEN) if(NOT EMSCRIPTEN)
@ -30,15 +32,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) option(TWN_FEATURE_DYNLIB_GAME "Enable dynamic library loading support" ON)
set(TWN_OUT_DIR ${CMAKE_SOURCE_DIR} CACHE PATH "Artifact destination") set(TWN_OUT_DIR ${CMAKE_SOURCE_DIR} CACHE PATH "Artifact destination")
# todo: figure out how to compile for dynamic linking instead # todo: figure out how to compile for dynamic linking instead?
if(EMSCRIPTEN) if(HAIKU OR EMSCRIPTEN)
if(TWN_FEATURE_DYNLIB_GAME) if(TWN_FEATURE_DYNLIB_GAME)
message(WARNING "TWN_FEATURE_DYNLIB_GAME is set, but not supported - it is turned off") message(WARNING "TWN_FEATURE_DYNLIB_GAME is set, but not supported - it is turned off")
set(TWN_FEATURE_DYNLIB_GAME OFF CACHE INTERNAL "") set(TWN_FEATURE_DYNLIB_GAME OFF CACHE INTERNAL "")
endif() endif()
endif() endif()
if(HAIKU) if(HAIKU OR EMSCRIPTEN)
if(TWN_SANITIZE) if(TWN_SANITIZE)
message(WARNING "TWN_SANITIZE is set, but not supported - it is turned off") message(WARNING "TWN_SANITIZE is set, but not supported - it is turned off")
set(TWN_SANITIZE OFF CACHE INTERNAL "") set(TWN_SANITIZE OFF CACHE INTERNAL "")
@ -64,11 +66,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/physfs ${CMAKE_CURRENT_BINARY_DIR}/third-party/physfs SYSTEM)
add_subdirectory(third-party/libxm ${CMAKE_CURRENT_BINARY_DIR}/third-party/libxm SYSTEM) add_subdirectory(third-party/libxm ${CMAKE_CURRENT_BINARY_DIR}/third-party/libxm SYSTEM)
if(EMSCRIPTEN) set(TWN_RENDERING_API OPENGL_15)
set(TWN_RENDERING_API WEBGL1)
else()
set(TWN_RENDERING_API OPENGL_15)
endif()
if(TWN_RENDERING_API MATCHES OPENGL_15) if(TWN_RENDERING_API MATCHES OPENGL_15)
set(SYSTEM_SOURCE_FILES ${SYSTEM_SOURCE_FILES} set(SYSTEM_SOURCE_FILES ${SYSTEM_SOURCE_FILES}
@ -140,7 +138,7 @@ set_target_properties(${TWN_TARGET} PROPERTIES
# precompile commonly used not-so-small headers # precompile commonly used not-so-small headers
target_precompile_headers(${TWN_TARGET} PRIVATE target_precompile_headers(${TWN_TARGET} PRIVATE
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h> $<$<NOT:$<BOOL:${EMSCRIPTEN}>>:third-party/glad/include/glad/glad.h>
${SDL2_INCLUDE_DIR}/SDL.h $<$<NOT:$<BOOL:${EMSCRIPTEN}>>:${SDL2_INCLUDE_DIR}/SDL.h>
third-party/physfs/src/physfs.h) third-party/physfs/src/physfs.h)
@ -152,12 +150,14 @@ function(give_options_without_warnings target)
-fno-signed-zeros -fno-signed-zeros
-fno-trapping-math -fno-trapping-math
-freciprocal-math -freciprocal-math
# TODO: require packaging for web case
$<$<BOOL:${EMSCRIPTEN}>:-sUSE_SDL=2>) $<$<BOOL:${EMSCRIPTEN}>:-sUSE_SDL=2>)
set(BUILD_FLAGS_RELEASE set(BUILD_FLAGS_RELEASE
-O3 -O3
-flto=$<IF:$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>,thin,auto> -flto=$<IF:$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>,thin,auto>
-mavx -mavx2 $<$<STREQUAL:${CMAKE_SYSTEM_PROCESSOR},AMD64>:-sse2 -mavx -mavx2>
$<$<BOOL:${EMSCRIPTEN}>:-msimd128 -mrelaxed-simd>
-funroll-loops -funroll-loops
-fomit-frame-pointer -fomit-frame-pointer
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-s>) $<$<STREQUAL:${CMAKE_C_COMPILER_ID},Gnu>:-s>)
@ -170,6 +170,15 @@ function(give_options_without_warnings target)
$<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address> $<$<BOOL:${TWN_SANITIZE}>:-fstack-protector-all -fsanitize=undefined -fsanitize=address>
$<$<BOOL:${EMSCRIPTEN}>:-gsource-map>) $<$<BOOL:${EMSCRIPTEN}>:-gsource-map>)
set(LINK_FLAGS
-Bsymbolic-functions
$<$<BOOL:${EMSCRIPTEN}>:-sLEGACY_GL_EMULATION -sGL_FFP_ONLY -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2
-sENVIRONMENT=web -sDEFAULT_TO_CXX=0>
$<$<BOOL:${EMSCRIPTEN}>:--preload-file ${TWN_OUT_DIR}/data@data -sALLOW_MEMORY_GROWTH>
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:-Wl,--as-needed>
$<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
set(LINK_FLAGS_RELEASE set(LINK_FLAGS_RELEASE
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,--strip-all> $<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,--strip-all>
${BUILD_FLAGS_RELEASE}) ${BUILD_FLAGS_RELEASE})
@ -194,12 +203,9 @@ function(give_options_without_warnings target)
target_link_options(${target} PUBLIC target_link_options(${target} PUBLIC
${BUILD_FLAGS} ${BUILD_FLAGS}
# -Wl,--no-undefined # TODO: use later for implementing no-libc ${LINK_FLAGS}
$<$<CONFIG:Release>:${LINK_FLAGS_RELEASE}> $<$<CONFIG:Release>:${LINK_FLAGS_RELEASE}>
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}> $<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>)
-Bsymbolic-functions
-Wl,--as-needed
$<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
get_target_property(target_type ${target} TYPE) get_target_property(target_type ${target} TYPE)
if (target_type MATCHES SHARED_LIBRARY) if (target_type MATCHES SHARED_LIBRARY)
@ -274,7 +280,7 @@ endfunction()
function(link_deps target) function(link_deps target)
target_link_libraries(${target} PUBLIC target_link_libraries(${target} PUBLIC
$<$<NOT:$<BOOL:${EMSCRIPTEN}>>:SDL2::SDL2> $<$<NOT:$<BOOL:${EMSCRIPTEN}>>:SDL2::SDL2>
$<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:SDL2::SDL2main> $<$<NOT:$<OR:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>,$<BOOL:${EMSCRIPTEN}>>>:SDL2::SDL2main>
physfs-static physfs-static
xms) xms)
target_include_directories(${target} PUBLIC ${SDL2_INCLUDE_DIRS}) target_include_directories(${target} PUBLIC ${SDL2_INCLUDE_DIRS})

View File

@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif() endif()
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build) add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
set(SOURCE_FILES set(SOURCE_FILES
game.c game.c

BIN
apps/demos/crawl/data/assets/castledoors.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
apps/demos/crawl/data/assets/lever.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
apps/demos/crawl/data/assets/mossy_rock.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -13,19 +13,24 @@
-- Defines classes under symbols, which could have properties attached. -- Defines classes under symbols, which could have properties attached.
# stone_wall # stone_wall
wall_texture : /assets/brick.png wall_texture : /assets/brick.png
solid : 1
. stone_floor . stone_floor
tile_texture : /assets/pebbles.png tile_texture : /assets/mossy_rock.png
@ player_spawn @ player_spawn
unique : unique : 1
face : south face : south
hold : torch hold : torch
tile_texture : /assets/pebbles.png tile_texture : /assets/mossy_rock.png
X door X door
open_on_signal : sg_torch0 open_on_signal : sg_torch0
tile_texture : /assets/pebbles.png tile_texture : /assets/mossy_rock.png
face : horizon
face_texture : /assets/castledoors.png
/ lever / lever
on_interact_emit : sg_torch0 on_interact_emit : sg_torch0
tile_texture : /assets/pebbles.png tile_texture : /assets/mossy_rock.png
face : observer
face_texture : /assets/lever.png
@meta @meta
-- Arbitrary sections could be defined with value pairs. -- Arbitrary sections could be defined with value pairs.

View File

@ -36,15 +36,19 @@ function game_tick()
ctx.udata.player.direction = { x = -ctx.udata.player.direction.z, y = ctx.udata.player.direction.y, z = ctx.udata.player.direction.x } ctx.udata.player.direction = { x = -ctx.udata.player.direction.z, y = ctx.udata.player.direction.y, z = ctx.udata.player.direction.x }
end end
local direction = { x = 0, z = 0 } local move = { x = 0, y = 0 }
if input_action_just_released { name = "walk_forward" } then if input_action_just_released { name = "walk_forward" } then
direction = { x = direction.x + ctx.udata.player.direction.z, z = direction.z + ctx.udata.player.direction.x } move = { x = move.x + ctx.udata.player.direction.z, y = move.y + ctx.udata.player.direction.x }
end end
if input_action_just_released { name = "walk_backward" } then if input_action_just_released { name = "walk_backward" } then
direction = { x = direction.x - ctx.udata.player.direction.z, z = direction.z - ctx.udata.player.direction.x } move = { x = move.x - ctx.udata.player.direction.z, y = move.y - ctx.udata.player.direction.x }
end end
ctx.udata.player.position = { x = ctx.udata.player.position.x + direction.x, y = ctx.udata.player.position.y + direction.z } if ctx.udata.level.grid[ctx.udata.player.position.y + move.y][ctx.udata.player.position.x + move.x].solid ~= nil then
move = { x = 0, y = 0 }
end
ctx.udata.player.position = { x = ctx.udata.player.position.x + move.x, y = ctx.udata.player.position.y + move.y }
ctx.udata.player.position_lerp.x = qlerp(ctx.udata.player.position_lerp.x, ctx.udata.player.position.x, ctx.frame_duration * 30) ctx.udata.player.position_lerp.x = qlerp(ctx.udata.player.position_lerp.x, ctx.udata.player.position.x, ctx.frame_duration * 30)
ctx.udata.player.position_lerp.y = qlerp(ctx.udata.player.position_lerp.y, ctx.udata.player.position.y, ctx.frame_duration * 30) ctx.udata.player.position_lerp.y = qlerp(ctx.udata.player.position_lerp.y, ctx.udata.player.position.y, ctx.frame_duration * 30)
@ -53,9 +57,9 @@ function game_tick()
draw_camera { draw_camera {
position = { position = {
x = ctx.udata.player.position_lerp.y * 2 + 1, x = ctx.udata.player.position_lerp.y * 2 + 1 - ctx.udata.player.direction.x / 2,
y = 1, y = 1,
z = ctx.udata.player.position_lerp.x * 2 + 1, z = ctx.udata.player.position_lerp.x * 2 + 1 - ctx.udata.player.direction.z / 2,
}, },
direction = ctx.udata.player.direction_lerp, direction = ctx.udata.player.direction_lerp,
} }

View File

@ -54,6 +54,33 @@ function render_dungeon(dungeon)
texture_region = { w = 128, h = 128 }, texture_region = { w = 128, h = 128 },
} }
end end
if dungeon.grid[y][x].face_texture ~= nil then
if dungeon.grid[y][x].face == "horizon" then
draw_quad {
texture = dungeon.grid[y][x].face_texture,
v3 = { x = y * 2, y = 2, z = x * 2 + 1 },
v2 = { x = y * 2, y = 0, z = x * 2 + 1 },
v1 = { x = y * 2 + 2, y = 0, z = x * 2 + 1 },
v0 = { x = y * 2 + 2, y = 2, z = x * 2 + 1 },
texture_region = { w = 64, h = 96 },
}
draw_quad {
texture = dungeon.grid[y][x].face_texture,
v3 = { x = y * 2 + 2, y = 2, z = x * 2 + 1 },
v2 = { x = y * 2 + 2, y = 0, z = x * 2 + 1 },
v1 = { x = y * 2, y = 0, z = x * 2 + 1 },
v0 = { x = y * 2, y = 2, z = x * 2 + 1 },
texture_region = { w = 64, h = 96 },
}
elseif dungeon.grid[y][x].face == "observer" then
draw_billboard {
texture = dungeon.grid[y][x].face_texture,
position = { x = y * 2 + 1, y = 1, z = x * 2 + 1 },
size = { x = 1, y = 1 },
}
end
end
end end
end end
end end

View File

@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif() endif()
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build) add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
set(SOURCE_FILES set(SOURCE_FILES
game.c game.c
@ -20,4 +20,4 @@ set(SOURCE_FILES
scenes/ingame.c scenes/ingame.h scenes/ingame.c scenes/ingame.h
) )
use_townengine("${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR}) use_townengine("${SOURCE_FILES}" ${TWN_OUT_DIR})

View File

@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif() endif()
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build) add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
set(SOURCE_FILES set(SOURCE_FILES
game.c game.c

View File

@ -6,7 +6,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif() endif()
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build) add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
set(SOURCE_FILES set(SOURCE_FILES
game.c game.c

View File

@ -5,7 +5,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif() endif()
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build) add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
set(SOURCE_FILES set(SOURCE_FILES
game.c game.c
@ -13,4 +13,4 @@ set(SOURCE_FILES
) )
cmake_path(GET CMAKE_SOURCE_DIR STEM LAST_ONLY GAME_PROJECT_NAME) cmake_path(GET CMAKE_SOURCE_DIR STEM LAST_ONLY GAME_PROJECT_NAME)
use_townengine(${GAME_PROJECT_NAME} "${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR}) use_townengine("${SOURCE_FILES}" ${CMAKE_CURRENT_SOURCE_DIR})

BIN
apps/templates/c/data/twn.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -14,6 +14,7 @@ dev_id = "you"
# Game runtime details # Game runtime details
[game] [game]
resolution = [ 640, 480 ] resolution = [ 640, 480 ]
background_color = [ 255, 125, 0, 255 ]
#debug = true #debug = true
# Engine tweaks. You probably don't need to change these # Engine tweaks. You probably don't need to change these

View File

@ -17,6 +17,11 @@ void game_tick(void) {
struct state *state = ctx.udata; struct state *state = ctx.udata;
++state->counter; ++state->counter;
m_sprite("twn.png",
(Rect) { .w = 128, .h = 64, },
m_opt(stretch, true)
);
} }

View File

@ -7,7 +7,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Debug) set(CMAKE_BUILD_TYPE Debug)
endif() endif()
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build) add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
set(FLAGS set(FLAGS
$<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:--no-dynlib-game> $<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:--no-dynlib-game>
@ -34,7 +34,7 @@ add_compile_definitions(LUA_32BITS=1)
set(SOURCE_FILES set(SOURCE_FILES
game.c game.c
state.h minilua.c
) )
use_townengine("${SOURCE_FILES}" ${TWN_OUT_DIR}) use_townengine("${SOURCE_FILES}" ${TWN_OUT_DIR})

View File

@ -1,7 +1,6 @@
#include "twn_game_api.h" #include "twn_game_api.h"
/* TODO: actually move it back it its own file, it doesn't give any compilation benefits */ /* TODO: actually move it back it its own file, it doesn't give any compilation benefits */
#define LUA_IMPL
#include "minilua.h" #include "minilua.h"
#include "state.h" #include "state.h"

2
apps/twnlua/minilua.c Normal file
View File

@ -0,0 +1,2 @@
#define LUA_IMPL
#include "minilua.h"

11
bin/twn
View File

@ -11,7 +11,16 @@ case "$1" in
build ) "$toolpath"/twnbuild "${@:2}" build ) "$toolpath"/twnbuild "${@:2}"
;; ;;
run ) $0 build "${@:2}" && ./$exe run ) $0 build "${@:2}"
if [[ "$*" == *"--target=web"* ]]; then
if [ "$OS" = "Windows_NT" ]; then
explorer "http://0.0.0.0:8000/$exe.html" & python3 -m http.server
else
xdg-open "http://0.0.0.0:8000/$exe.html" & python3 -m http.server
fi
else
./$exe
fi
;; ;;
gdb ) unset DEBUGINFOD_URLS gdb ) unset DEBUGINFOD_URLS

View File

@ -7,45 +7,56 @@ from pathlib import Path
from sys import argv from sys import argv
import tomllib 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_ninja = getoutput("command -v ninja") != ""
has_clang = getoutput("command -v clang") != "" 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) # check whether clang is around (it's just better)
if has_clang: if has_clang:
cmake += ["-DCMAKE_C_COMPILER=clang"] command += ["-DCMAKE_C_COMPILER=clang"]
# check whether ninja is around (you better start running) # check whether ninja is around (you better start running)
if has_ninja: if has_ninja:
cmake += ["-G", "Ninja"] command += ["-G", "Ninja"]
cmake += ["-B", "build"]
command += ["-B", build_dir]
# TODO: have it --fast=1 instead, where separate --debug=0 would mean stripping the debug info # TODO: have it --fast=1 instead, where separate --debug=0 would mean stripping the debug info
if "--release" in argv: if "--release" in argv:
cmake += ["-DCMAKE_BUILD_TYPE=Release"] command += ["-DCMAKE_BUILD_TYPE=Release"]
elif "--debug" in argv: elif "--debug" in argv:
cmake += ["-DCMAKE_BUILD_TYPE=Debug"] command += ["-DCMAKE_BUILD_TYPE=Debug"]
if "--unified=1" in argv: if "--unified=1" in argv:
cmake += ["-DTWN_FEATURE_DYNLIB_GAME=ON"] command += ["-DTWN_FEATURE_DYNLIB_GAME=ON"]
elif "--unified=0" in argv: elif "--unified=0" in argv:
cmake += ["-DTWN_FEATURE_DYNLIB_GAME=OFF"] command += ["-DTWN_FEATURE_DYNLIB_GAME=OFF"]
if "--sanitize=1" in argv: if "--sanitize=1" in argv:
cmake += ["-DTWN_SANITIZE=ON"] command += ["-DTWN_SANITIZE=ON"]
elif "--sanitize=0" in argv: 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 # pass arbitrary arguments over
if "--" in argv: 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 no root cmake file is present, infer it from `twn.toml:game.interpreter`
if not Path("CMakeLists.txt").is_file(): if not Path("CMakeLists.txt").is_file():
config = tomllib.loads(Path("data/twn.toml").read_text()) 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 + command, check=True)
run(["cmake", "--build", "build", "--parallel"], check=True) run(["cmake"] + ["--build", build_dir, "--parallel"], check=True)

View File

@ -73,6 +73,7 @@
<li>Procedure can't have more than 8 parameters. <li>Procedure can't have more than 8 parameters.
<li>Decimal portions of floating points are lossy both due to rounding errors and text representation, <li>Decimal portions of floating points are lossy both due to rounding errors and text representation,
thus they cannot be relied to hold for equality. Integer parts of floats are good up to 2^24. thus they cannot be relied to hold for equality. Integer parts of floats are good up to 2^24.
<li>Returns must be restricted/closed on being constant over frame, parameters or asset frame.
</ul> </ul>
</blockquote> </blockquote>
</body> </body>

View File

@ -19,7 +19,8 @@
"params": [ "params": [
{ "name": "name", "type": "char *" } { "name": "name", "type": "char *" }
], ],
"return": "bool" "return": "bool",
"restriction": "frame"
}, },
"input_action_just_pressed": { "input_action_just_pressed": {
@ -29,7 +30,8 @@
"params": [ "params": [
{ "name": "name", "type": "char *" } { "name": "name", "type": "char *" }
], ],
"return": "bool" "return": "bool",
"restriction": "frame"
}, },
"input_action_just_released": { "input_action_just_released": {
@ -39,7 +41,8 @@
"params": [ "params": [
{ "name": "name", "type": "char *" } { "name": "name", "type": "char *" }
], ],
"return": "bool" "return": "bool",
"restriction": "frame"
}, },
"input_action_position": { "input_action_position": {
@ -49,7 +52,8 @@
"params": [ "params": [
{ "name": "name", "type": "char *" } { "name": "name", "type": "char *" }
], ],
"return": "Vec2" "return": "Vec2",
"restriction": "frame"
}, },
"draw_sprite": { "draw_sprite": {
@ -111,7 +115,8 @@
{ "name": "height", "type": "float", "default": 22 }, { "name": "height", "type": "float", "default": 22 },
{ "name": "font", "type": "char *", "default": {} } { "name": "font", "type": "char *", "default": {} }
], ],
"return": "float" "return": "float",
"restriction": "asset"
}, },
"draw_nine_slice": { "draw_nine_slice": {
@ -228,7 +233,8 @@
{ "name": "up", "type": "Vec3" } { "name": "up", "type": "Vec3" }
], ],
"c_type": "DrawCameraFromPrincipalAxesResult" "c_type": "DrawCameraFromPrincipalAxesResult"
} },
"restriction": "parameters"
}, },
"draw_skybox": { "draw_skybox": {
@ -271,7 +277,8 @@
"params": [ "params": [
{ "name": "file", "type": "char *" } { "name": "file", "type": "char *" }
], ],
"return": "char *" "return": "char *",
"restriction": "asset"
}, },
"timer_tick_seconds": { "timer_tick_seconds": {
@ -281,7 +288,8 @@
"params": [ "params": [
{ "name": "seconds_left", "type": "float" } { "name": "seconds_left", "type": "float" }
], ],
"return": "float" "return": "float",
"restriction": "parameters"
}, },
"timer_elapse_seconds": { "timer_elapse_seconds": {
@ -299,7 +307,8 @@
{ "name": "elapsed", "type": "bool" } { "name": "elapsed", "type": "bool" }
], ],
"c_type": "TimerElapseSecondsResult" "c_type": "TimerElapseSecondsResult"
} },
"restriction": "parameters"
}, },
"log_vec2": { "log_vec2": {

View File

@ -39,12 +39,18 @@ enum {
typedef uint32_t VertexBuffer; typedef uint32_t VertexBuffer;
typedef uint32_t IndexBuffer;
typedef struct VertexBufferBuilder { typedef struct VertexBufferBuilder {
size_t size; size_t size;
void *base; void *base;
} VertexBufferBuilder; } VertexBufferBuilder;
typedef struct IndexBufferBuilder {
size_t size;
void *base;
} IndexBufferBuilder;
typedef struct SpritePrimitive { typedef struct SpritePrimitive {
Rect rect; Rect rect;
@ -268,19 +274,19 @@ void text_cache_reset_arena(TextCache *cache);
/* vertex buffer */ /* vertex buffer */
VertexBuffer create_vertex_buffer(void); VertexBuffer create_vertex_buffer(void);
void restart_scratch_vertex_arrays(void); void restart_scratch_vertex_arrays(void);
VertexBuffer get_scratch_vertex_array(void); VertexBuffer get_scratch_vertex_array(void);
void delete_vertex_buffer(VertexBuffer buffer); void delete_vertex_buffer(VertexBuffer buffer);
void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes); void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes);
VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes); VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes);
void finish_vertex_builder(VertexBufferBuilder *builder); void finish_vertex_builder(VertexBufferBuilder *builder);
IndexBuffer create_index_buffer(void);
void delete_index_buffer(IndexBuffer buffer);
void specify_index_buffer(IndexBuffer buffer, void const *data, size_t bytes);
IndexBufferBuilder build_index_buffer(IndexBuffer buffer, size_t bytes);
void finish_index_builder(IndexBufferBuilder *builder);
/* 2d */ /* 2d */
/* fills two existing arrays with the geometry data of a circle */ /* fills two existing arrays with the geometry data of a circle */
@ -304,9 +310,8 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len);
void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch); void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch);
void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch); void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);
VertexBuffer get_quad_element_buffer(void); IndexBuffer get_quad_element_buffer(void);
IndexBuffer get_circle_element_buffer(void);
VertexBuffer get_circle_element_buffer(void);
void render_circle(const CirclePrimitive *circle); void render_circle(const CirclePrimitive *circle);

View File

@ -5,7 +5,13 @@
#include "twn_types.h" #include "twn_types.h"
#include "twn_deferred_commands.h" #include "twn_deferred_commands.h"
#ifdef __EMSCRIPTEN__
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#else
#include <glad/glad.h> #include <glad/glad.h>
#endif
#include <stb_ds.h> #include <stb_ds.h>
@ -35,14 +41,18 @@ static void APIENTRY opengl_log(GLenum source,
bool render_init(void) { bool render_init(void) {
#ifndef __EMSCRIPTEN__
if (gladLoadGLLoader(&SDL_GL_GetProcAddress) == 0) { if (gladLoadGLLoader(&SDL_GL_GetProcAddress) == 0) {
CRY("Init", "GLAD failed"); CRY("Init", "GLAD failed");
return false; return false;
} }
#endif
log_info("OpenGL context: %s\n", glGetString(GL_VERSION)); log_info("OpenGL context: %s\n", glGetString(GL_VERSION));
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST); glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
#ifndef __EMSCRIPTEN__
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST); glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
glHint(GL_FOG_HINT, GL_FASTEST); glHint(GL_FOG_HINT, GL_FASTEST);
@ -51,6 +61,7 @@ bool render_init(void) {
glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(opengl_log, NULL); glDebugMessageCallback(opengl_log, NULL);
} }
#endif
return true; return true;
} }
@ -88,16 +99,21 @@ static void finally_use_space_pipeline(void) {
depth_range_high = 1.0; depth_range_high = 1.0;
depth_range_low = 0.0; depth_range_low = 0.0;
#ifndef __EMSCRIPTEN__
static GLuint list = 0; static GLuint list = 0;
if (!list) { if (!list) {
list = glGenLists(1); list = glGenLists(1);
glNewList(list, GL_COMPILE); { glNewList(list, GL_COMPILE);
#endif
{
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glShadeModel(GL_FLAT); glShadeModel(GL_FLAT);
#ifndef __EMSCRIPTEN__
if (GLAD_GL_ARB_depth_clamp) if (GLAD_GL_ARB_depth_clamp)
glDisable(GL_DEPTH_CLAMP); glDisable(GL_DEPTH_CLAMP);
#endif
if (ctx.cull_faces) if (ctx.cull_faces)
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
@ -110,10 +126,12 @@ static void finally_use_space_pipeline(void) {
/* solid white, no modulation */ /* solid white, no modulation */
glColor4ub(255, 255, 255, 255); glColor4ub(255, 255, 255, 255);
#ifndef __EMSCRIPTEN__
} glEndList(); } glEndList();
}
glCallList(list); glCallList(list);
#endif
}
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
glLoadMatrixf(&camera_projection_matrix.row[0].x); glLoadMatrixf(&camera_projection_matrix.row[0].x);
@ -135,26 +153,30 @@ static void finally_use_2d_pipeline(void) {
if (pipeline_last_used == PIPELINE_2D) if (pipeline_last_used == PIPELINE_2D)
return; return;
#ifndef __EMSCRIPTEN__
static GLuint list = 0; static GLuint list = 0;
if (!list) { if (!list) {
list = glGenLists(1); list = glGenLists(1);
glNewList(list, GL_COMPILE); { glNewList(list, GL_COMPILE); {
#endif
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
glShadeModel(GL_FLAT); glShadeModel(GL_FLAT);
#ifndef __EMSCRIPTEN__
/* removes near/far plane comparison and discard */ /* removes near/far plane comparison and discard */
if (GLAD_GL_ARB_depth_clamp) if (GLAD_GL_ARB_depth_clamp)
glEnable(GL_DEPTH_CLAMP); glEnable(GL_DEPTH_CLAMP);
#endif
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glDisable(GL_CULL_FACE); glDisable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
#ifndef __EMSCRIPTEN__
} glEndList(); } glEndList();
}
glCallList(list); } glCallList(list);
#endif
glMatrixMode(GL_PROJECTION); glMatrixMode(GL_PROJECTION);
glLoadIdentity(); glLoadIdentity();
@ -175,48 +197,77 @@ static void finally_use_2d_pipeline(void) {
pipeline_last_used = PIPELINE_2D; pipeline_last_used = PIPELINE_2D;
} }
static void setup_ghostly_texture_mode(void) {
#ifndef __EMSCRIPTEN__
static GLuint list = 0;
if (!list) {
list = glGenLists(1);
glNewList(list, 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();
}
glCallList(list);
#endif
}
static void setup_seethrough_texture_mode(void) {
#ifndef __EMSCRIPTEN__
static GLuint list = 0;
if (!list) {
list = glGenLists(1);
glNewList(list, GL_COMPILE);
#endif
glDisable(GL_BLEND);
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_EQUAL, 1.0f);
#ifndef __EMSCRIPTEN__
glEndList();
}
glCallList(list);
#endif
}
static void setup_opaque_texture_mode(void) {
#ifndef __EMSCRIPTEN__
static GLuint list = 0;
if (!list) {
list = glGenLists(1);
glNewList(list, GL_COMPILE);
#endif
glDisable(GL_BLEND);
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
glDisable(GL_ALPHA_TEST);
#ifndef __EMSCRIPTEN__
glEndList();
}
glCallList(list);
#endif
}
/* TODO: ensure we minimize depth func switching to enable Hi-Z (hierarchical depth) optimizations */ /* TODO: ensure we minimize depth func switching to enable Hi-Z (hierarchical depth) optimizations */
static void finally_use_texture_mode(TextureMode mode) { static void finally_use_texture_mode(TextureMode mode) {
if (texture_mode_last_used == mode) if (texture_mode_last_used == mode)
return; return;
static GLuint lists = 0;
if (!lists) {
lists = glGenLists(3);
/* ghostly */
glNewList(lists + 0, GL_COMPILE); {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_LESS);
glDepthMask(GL_FALSE);
glDisable(GL_ALPHA_TEST);
} glEndList();
/* seethrough */
glNewList(lists + 1, GL_COMPILE); {
glDisable(GL_BLEND);
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_EQUAL, 1.0f);
} glEndList();
/* opaque */
glNewList(lists + 2, GL_COMPILE); {
glDisable(GL_BLEND);
glDepthFunc(GL_LESS);
glDepthMask(GL_TRUE);
glDisable(GL_ALPHA_TEST);
} glEndList();
}
if (mode == TEXTURE_MODE_GHOSTLY) { if (mode == TEXTURE_MODE_GHOSTLY) {
glCallList(lists + 0); setup_ghostly_texture_mode();
} else if (mode == TEXTURE_MODE_SEETHROUGH) { } else if (mode == TEXTURE_MODE_SEETHROUGH) {
glCallList(lists + 1); setup_seethrough_texture_mode();
} else { } else {
glCallList(lists + 2); setup_opaque_texture_mode();
} }
texture_mode_last_used = mode; texture_mode_last_used = mode;
@ -227,7 +278,11 @@ VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) {
SDL_assert(bytes != 0); SDL_assert(bytes != 0);
glBindBuffer(GL_ARRAY_BUFFER, buffer); glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, bytes, NULL, GL_STREAM_DRAW); glBufferData(GL_ARRAY_BUFFER, bytes, NULL, GL_STREAM_DRAW);
#ifndef __EMSCRIPTEN__
void *mapping = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); void *mapping = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
#else
void *mapping = SDL_malloc(bytes);
#endif
if (!mapping) if (!mapping)
CRY("build_vertex_buffer", "Error mapping a vertex array buffer"); CRY("build_vertex_buffer", "Error mapping a vertex array buffer");
@ -239,8 +294,13 @@ VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) {
void finish_vertex_builder(VertexBufferBuilder *builder) { void finish_vertex_builder(VertexBufferBuilder *builder) {
#ifndef __EMSCRIPTEN__
if (!glUnmapBuffer(GL_ARRAY_BUFFER)) if (!glUnmapBuffer(GL_ARRAY_BUFFER))
CRY("finish_vertex_builder", "Error unmapping a vertex 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); glBindBuffer(GL_ARRAY_BUFFER, 0);
builder->base = 0; builder->base = 0;
@ -248,6 +308,40 @@ void finish_vertex_builder(VertexBufferBuilder *builder) {
} }
IndexBufferBuilder build_index_buffer(IndexBuffer buffer, size_t bytes) {
SDL_assert(bytes != 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, bytes, NULL, GL_STREAM_DRAW);
#ifndef __EMSCRIPTEN__
void *mapping = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
#else
void *mapping = SDL_malloc(bytes);
#endif
if (!mapping)
CRY("build_vertex_buffer", "Error mapping a vertex array buffer");
return (IndexBufferBuilder) {
.base = mapping,
.size = bytes,
};
}
void finish_index_builder(IndexBufferBuilder *builder) {
#ifndef __EMSCRIPTEN__
if (!glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER))
CRY("finish_vertex_builder", "Error unmapping a vertex array buffer");
#else
glBufferData(GL_ELEMENT_ARRAY_BUFFER, builder->size, builder->base, GL_STREAM_DRAW);
SDL_free(builder->base);
#endif
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
builder->base = 0;
builder->size = 0;
}
static void load_cubemap_side(const char *path, GLenum target) { static void load_cubemap_side(const char *path, GLenum target) {
SDL_Surface *surface = textures_load_surface(path); SDL_Surface *surface = textures_load_surface(path);
/* TODO: sanity check whether all of them have same dimensions? */ /* TODO: sanity check whether all of them have same dimensions? */
@ -333,18 +427,23 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) {
/* TODO: figure out which coordinates to use to not have issues with far z */ /* TODO: figure out which coordinates to use to not have issues with far z */
/* TODO: recalculate the list if far z requirement changes */ /* TODO: recalculate the list if far z requirement changes */
#ifndef __EMSCRIPTEN__
static GLuint list = 0; static GLuint list = 0;
if (!list) { if (!list) {
list = glGenLists(1); list = glGenLists(1);
glNewList(list, GL_COMPILE); { glNewList(list, GL_COMPILE);
#endif
{
/* note: assumes that space pipeline is applied already */ /* note: assumes that space pipeline is applied already */
glDisable(GL_ALPHA_TEST); glDisable(GL_ALPHA_TEST);
glDepthMask(GL_FALSE); glDepthMask(GL_FALSE);
#ifndef __EMSCRIPTEN__
/* removes near/far plane comparison and discard */ /* removes near/far plane comparison and discard */
if (GLAD_GL_ARB_depth_clamp) if (GLAD_GL_ARB_depth_clamp)
glEnable(GL_DEPTH_CLAMP); glEnable(GL_DEPTH_CLAMP);
#endif
glBegin(GL_QUADS); { glBegin(GL_QUADS); {
/* up */ /* up */
@ -408,16 +507,19 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) {
glVertex3f(-50.f, 50.f, -50.f); glVertex3f(-50.f, 50.f, -50.f);
} glEnd(); } glEnd();
#ifndef __EMSCRIPTEN__
if (GLAD_GL_ARB_depth_clamp) if (GLAD_GL_ARB_depth_clamp)
glDisable(GL_DEPTH_CLAMP); glDisable(GL_DEPTH_CLAMP);
#endif
glDepthMask(GL_TRUE); glDepthMask(GL_TRUE);
glDisable(GL_TEXTURE_CUBE_MAP); glDisable(GL_TEXTURE_CUBE_MAP);
#ifndef __EMSCRIPTEN__
} glEndList(); } glEndList();
}
glCallList(list); glCallList(list);
#endif
}
} }

View File

@ -4,13 +4,14 @@
#include <stb_ds.h> #include <stb_ds.h>
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
#include <GLES2/gl2.h> #define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#else #else
#include <glad/glad.h> #include <glad/glad.h>
#endif #endif
void setup_viewport(int x, int y, int width, int height) { void setup_viewport(int x, int y, int width, int height) {
glViewport(x, y, width, height); glViewport(x, y, width, height);
} }
@ -28,6 +29,18 @@ void delete_vertex_buffer(VertexBuffer buffer) {
} }
IndexBuffer create_index_buffer(void) {
GLuint result;
glGenBuffers(1, &result);
return result;
}
void delete_index_buffer(IndexBuffer buffer) {
glDeleteBuffers(1, &buffer);
}
void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes) { void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes) {
glBindBuffer(GL_ARRAY_BUFFER, buffer); glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, bytes, data, GL_STREAM_DRAW); glBufferData(GL_ARRAY_BUFFER, bytes, data, GL_STREAM_DRAW);
@ -35,14 +48,21 @@ void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes)
} }
VertexBuffer get_quad_element_buffer(void) { void specify_index_buffer(IndexBuffer buffer, void const *data, size_t bytes) {
static VertexBuffer buffer = 0; glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, bytes, data, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
IndexBuffer get_quad_element_buffer(void) {
static IndexBuffer buffer = 0;
/* it's only generated once at runtime */ /* it's only generated once at runtime */
/* TODO: use builder interface, not direct calls (glMapBuffer isn't portable) */ /* TODO: use builder interface, not direct calls (glMapBuffer isn't portable) */
if (buffer == 0) { if (buffer == 0) {
buffer = create_vertex_buffer(); buffer = create_index_buffer();
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 ); IndexBufferBuilder builder = build_index_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) { for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
((GLshort *)builder.base)[i * 6 + 0] = (GLshort)(i * 4 + 0); ((GLshort *)builder.base)[i * 6 + 0] = (GLshort)(i * 4 + 0);
@ -53,7 +73,7 @@ VertexBuffer get_quad_element_buffer(void) {
((GLshort *)builder.base)[i * 6 + 5] = (GLshort)(i * 4 + 0); ((GLshort *)builder.base)[i * 6 + 5] = (GLshort)(i * 4 + 0);
} }
finish_vertex_builder(&builder); finish_index_builder(&builder);
} }
SDL_assert_always(buffer); SDL_assert_always(buffer);
@ -62,12 +82,12 @@ VertexBuffer get_quad_element_buffer(void) {
} }
VertexBuffer get_circle_element_buffer(void) { IndexBuffer get_circle_element_buffer(void) {
static VertexBuffer buffer = 0; static IndexBuffer buffer = 0;
if (buffer == 0) { if (buffer == 0) {
buffer = create_vertex_buffer(); buffer = create_index_buffer();
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * (CIRCLE_VERTICES_MAX - 2) * 3); IndexBufferBuilder builder = build_index_buffer(buffer, sizeof (GLshort) * (CIRCLE_VERTICES_MAX - 2) * 3);
for (size_t i = 1; i < CIRCLE_VERTICES_MAX - 1; ++i) { for (size_t i = 1; i < CIRCLE_VERTICES_MAX - 1; ++i) {
/* first one is center point index, always zero */ /* first one is center point index, always zero */
@ -78,7 +98,7 @@ VertexBuffer get_circle_element_buffer(void) {
((GLshort *)builder.base)[(i - 1) * 3 + 2] = (GLshort)i + 1; ((GLshort *)builder.base)[(i - 1) * 3 + 2] = (GLshort)i + 1;
} }
finish_vertex_builder(&builder); finish_index_builder(&builder);
} }
SDL_assert_always(buffer); SDL_assert_always(buffer);
@ -127,11 +147,10 @@ GPUTexture create_gpu_texture(TextureFilter filter, bool generate_mipmaps, int c
SDL_assert(width > 0 && height > 0); SDL_assert(width > 0 && height > 0);
SDL_assert(channels > 0 && channels <= 4); SDL_assert(channels > 0 && channels <= 4);
#if !defined(EMSCRIPTEN) #ifndef __EMSCRIPTEN__
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLboolean)generate_mipmaps); glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLboolean)generate_mipmaps);
#else #else
if (generate_mipmaps) (void)generate_mipmaps;
glGenerateMipmap(GL_TEXTURE_2D);
#endif #endif
if (filter == TEXTURE_FILTER_NEAREAST) { if (filter == TEXTURE_FILTER_NEAREAST) {
@ -145,24 +164,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_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, 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); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#endif
int format_internal, format; int format_internal, format;
if (channels == 4) { if (channels == 4) {
#ifdef EMSCRIPTEN
format_internal = GL_RGBA;
#else
format_internal = GL_RGBA8; format_internal = GL_RGBA8;
#endif
format = GL_RGBA; format = GL_RGBA;
} else if (channels == 3) { } else if (channels == 3) {
#ifdef EMSCRIPTEN
format_internal = GL_RGBA;
#else
format_internal = GL_RGBA8; format_internal = GL_RGBA8;
#endif
format = GL_RGB; format = GL_RGB;
} else if (channels == 1) { } else if (channels == 1) {
format_internal = GL_ALPHA; format_internal = GL_ALPHA;

View File

@ -1,235 +0,0 @@
#include "twn_draw_c.h"
#include "twn_draw.h"
#include "twn_workers_c.h"
#include "twn_textures_c.h"
#define FAST_OBJ_IMPLEMENTATION
#define FAST_OBJ_REALLOC SDL_realloc
#define FAST_OBJ_FREE SDL_free
#include <fast_obj.h>
#include <stb_ds.h>
#include <physfs.h>
#include <physfsrwops.h>
static struct ModelCacheItem {
char *key;
struct ModelCacheItemValue {
fastObjMesh *mesh;
} value;
} *model_cache;
/* TODO: store index to model cache instead */
static struct ModelDrawCommand {
char *model;
Vec3 position;
Vec3 rotation;
Vec3 scale;
} *model_draw_commands;
/* deferred queue of model files to load from worker threads */
static SDL_mutex *model_load_mutex;
static char const **model_load_queue;
static size_t model_load_queued;
static bool model_load_initialized;
/* use streaming via callbacks to reduce memory congestion */
static void model_load_callback_close(void *handle, void *udata) {
(void)udata;
((SDL_RWops *)handle)->close(handle);
}
static void *model_load_callback_open(const char *path, void *udata) {
(void)udata;
return PHYSFSRWOPS_openRead(path);
}
static size_t model_load_callback_read(void *handle, void *dst, size_t bytes, void *udata) {
(void)udata;
return ((SDL_RWops *)handle)->read(handle, dst, 1, bytes);
}
static unsigned long model_load_callback_size(void *handle, void *udata) {
(void)udata;
return ((SDL_RWops *)handle)->size(handle);
}
/* TODO: is there a way to do this nicely while locking main thread? */
/* sleeping over atomic counter might be good enough i guess */
/* it's safe to access everything without lock after this returns true and no public api is possible to call */
static bool model_load_workers_finished(void) {
bool result;
SDL_LockMutex(model_load_mutex);
result = model_load_queued == 0;
SDL_UnlockMutex(model_load_mutex);
return result;
}
/* entry point for workers, polled every time a job semaphore is posted */
/* returns false if there was nothing to do */
bool model_load_workers_thread(void) {
/* attempt to grab something to work on */
char const *load_request = NULL;
SDL_LockMutex(model_load_mutex);
if (arrlenu(model_load_queue) != 0)
load_request = arrpop(model_load_queue);
SDL_UnlockMutex(model_load_mutex);
/* nothing to do, bail */
if (!load_request)
return false;
fastObjCallbacks const callbacks = {
.file_close = model_load_callback_close,
.file_open = model_load_callback_open,
.file_read = model_load_callback_read,
.file_size = model_load_callback_size
};
fastObjMesh *const mesh = fast_obj_read_with_callbacks(load_request, &callbacks, NULL);
SDL_LockMutex(model_load_mutex);
struct ModelCacheItem *item = shgetp(model_cache, load_request);
item->value.mesh = mesh;
model_load_queued--;
SDL_UnlockMutex(model_load_mutex);
return true;
}
void draw_model(const char *model,
Vec3 position,
Vec3 rotation,
Vec3 scale)
{
if (!model_load_initialized) {
model_load_mutex = SDL_CreateMutex();
model_load_initialized = true;
}
struct ModelCacheItem const *item;
/* TODO: make it lockless */
/* if model is missing, queue it up for loading */
SDL_LockMutex(model_load_mutex);
if (!(item = shgetp_null(model_cache, model))) {
model = SDL_strdup(model);
shput(model_cache, model, (struct ModelCacheItemValue){0});
arrpush(model_load_queue, model);
model_load_queued++;
SDL_SemPost(workers_job_semaphore);
} else
model = item->key;
SDL_UnlockMutex(model_load_mutex);
struct ModelDrawCommand const command = {
.model = (char *)model,
.position = position,
.rotation = rotation,
.scale = scale
};
arrpush(model_draw_commands, command);
}
void finally_draw_models(void) {
while (!model_load_workers_finished())
SDL_Delay(1);
/* TODO: have special path for them, preserving the buffers and potentially using instanced draw */
for (int i = 0; i < arrlen(model_draw_commands); ++i) {
struct ModelDrawCommand const *const command = &model_draw_commands[i];
fastObjMesh const *const mesh = model_cache[shgeti(model_cache, command->model)].value.mesh;
SDL_assert(mesh);
for (unsigned int g = 0; g < mesh->group_count; ++g) {
fastObjGroup const *const group = &mesh->groups[g];
unsigned int idx = 0;
for (unsigned int f = 0; f < group->face_count; ++f) {
unsigned int const vertices = mesh->face_vertices[group->face_offset + f];
// fastObjTexture const *const texture = &mesh->textures[group->face_offset + f];
// log_info("material: %s", material->name);
/* TODO: support arbitrary fans */
unsigned int const material_index = mesh->face_materials[group->index_offset + f];
fastObjMaterial const *const material = mesh->materials ? &mesh->materials[material_index] : NULL;
if (vertices == 4) {
fastObjIndex const i0 = mesh->indices[group->index_offset + idx + 0];
fastObjIndex const i1 = mesh->indices[group->index_offset + idx + 1];
fastObjIndex const i2 = mesh->indices[group->index_offset + idx + 2];
fastObjIndex const i3 = mesh->indices[group->index_offset + idx + 3];
draw_quad(
material ? mesh->textures[material->map_Kd].name : NULL,
(Vec3) { mesh->positions[3 * i0.p + 0] * command->scale.x, mesh->positions[3 * i0.p + 1] * command->scale.y, mesh->positions[3 * i0.p + 2] * command->scale.z },
(Vec3) { mesh->positions[3 * i1.p + 0] * command->scale.x, mesh->positions[3 * i1.p + 1] * command->scale.y, mesh->positions[3 * i1.p + 2] * command->scale.z },
(Vec3) { mesh->positions[3 * i2.p + 0] * command->scale.x, mesh->positions[3 * i2.p + 1] * command->scale.y, mesh->positions[3 * i2.p + 2] * command->scale.z },
(Vec3) { mesh->positions[3 * i3.p + 0] * command->scale.x, mesh->positions[3 * i3.p + 1] * command->scale.y, mesh->positions[3 * i3.p + 2] * command->scale.z },
(Rect) { .w = 64, .h = 64 },
(Color) { 255, 255, 255, 255 }
);
} else if (vertices == 3) {
fastObjIndex const i0 = mesh->indices[group->index_offset + idx + 0];
fastObjIndex const i1 = mesh->indices[group->index_offset + idx + 1];
fastObjIndex const i2 = mesh->indices[group->index_offset + idx + 2];
draw_triangle(
material ? mesh->textures[material->map_Kd].name : NULL,
(Vec3) { mesh->positions[3 * i0.p + 0] * command->scale.x, mesh->positions[3 * i0.p + 1] * command->scale.y, mesh->positions[3 * i0.p + 2] * command->scale.z },
(Vec3) { mesh->positions[3 * i1.p + 0] * command->scale.x, mesh->positions[3 * i1.p + 1] * command->scale.y, mesh->positions[3 * i1.p + 2] * command->scale.z },
(Vec3) { mesh->positions[3 * i2.p + 0] * command->scale.x, mesh->positions[3 * i2.p + 1] * command->scale.y, mesh->positions[3 * i2.p + 2] * command->scale.z },
(Vec2) {0,0},
(Vec2) {0,0},
(Vec2) {0,0},
(Color){255, 255, 255, 255},
(Color){255, 255, 255, 255},
(Color){255, 255, 255, 255}
);
} else {
fastObjIndex const i0 = mesh->indices[group->index_offset + idx];
for (unsigned int z = 0; z < vertices - 2; ++z) {
fastObjIndex const i1 = mesh->indices[group->index_offset + idx + 1 + z];
fastObjIndex const i2 = mesh->indices[group->index_offset + idx + 2 + z];
draw_triangle(
material ? mesh->textures[material->map_Kd].name : NULL,
(Vec3) { mesh->positions[3 * i0.p + 0] * command->scale.x, mesh->positions[3 * i0.p + 1] * command->scale.y, mesh->positions[3 * i0.p + 2] * command->scale.z },
(Vec3) { mesh->positions[3 * i1.p + 0] * command->scale.x, mesh->positions[3 * i1.p + 1] * command->scale.y, mesh->positions[3 * i1.p + 2] * command->scale.z },
(Vec3) { mesh->positions[3 * i2.p + 0] * command->scale.x, mesh->positions[3 * i2.p + 1] * command->scale.y, mesh->positions[3 * i2.p + 2] * command->scale.z },
(Vec2) {0,0},
(Vec2) {0,0},
(Vec2) {0,0},
(Color){255, 255, 255, 255},
(Color){255, 255, 255, 255},
(Color){255, 255, 255, 255}
);
}
}
idx += vertices;
}
}
}
arrsetlen(model_draw_commands, 0);
}
/* drop model caches */
void free_model_cache(void) {
while (!model_load_workers_finished())
SDL_Delay(1);
for (size_t i = 0; i < shlenu(model_cache); ++i) {
fast_obj_destroy(model_cache[i].value.mesh);
SDL_free(model_cache[i].key);
}
shfree(model_cache);
}
void model_state_deinit(void) {
if (!model_load_initialized)
return;
free_model_cache();
arrfree(model_load_queue);
SDL_DestroyMutex(model_load_mutex);
model_load_initialized = false;
}

View File

@ -162,7 +162,7 @@ void draw_model(const char *model,
}; };
arrpush(model_load_queue, request); arrpush(model_load_queue, request);
SDL_UnlockMutex(model_load_mutex); SDL_UnlockMutex(model_load_mutex);
SDL_SemPost(workers_job_semaphore); workers_add_job();
} else } else
modelcopy = item->key; modelcopy = item->key;

View File

@ -398,7 +398,4 @@ void finally_draw_text(FontData const *font_data,
}; };
arrpush(deferred_commands, final_command); arrpush(deferred_commands, final_command);
/* TODO: why doesn't it get restored if not placed here? */
// glDepthMask(GL_TRUE);
} }

View File

@ -127,7 +127,8 @@
monitorRunDependencies: (left) => { monitorRunDependencies: (left) => {
this.totalDependencies = Math.max(this.totalDependencies, left); this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.'); Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
} },
GL_MAX_TEXTURE_IMAGE_UNITS = 2
}; };
Module.setStatus('Downloading...'); Module.setStatus('Downloading...');
window.onerror = () => { window.onerror = () => {

View File

@ -2,12 +2,12 @@
#include "twn_util.h" #include "twn_util.h"
#include "twn_engine_context_c.h" #include "twn_engine_context_c.h"
#ifndef __EMSCRIPTEN__
#define DMON_IMPL #define DMON_IMPL
#include <dmon.h> #include <dmon.h>
#include <stb_ds.h> #include <stb_ds.h>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
struct FilewatchEntry { struct FilewatchEntry {
char *path; char *path;
FilewatchCallback callback; FilewatchCallback callback;
@ -118,3 +118,19 @@ void filewatch_poll(void) {
SDL_UnlockMutex(filewatcher_lock); 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

View File

@ -16,7 +16,9 @@
#include <stdbool.h> #include <stdbool.h>
#include <limits.h> #include <limits.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#define TICKS_PER_SECOND_DEFAULT 60 #define TICKS_PER_SECOND_DEFAULT 60
#define PACKAGE_EXTENSION "btw" #define PACKAGE_EXTENSION "btw"
@ -24,9 +26,9 @@
static void pack_contents_modified(char const *path, enum FilewatchAction action); static void pack_contents_modified(char const *path, enum FilewatchAction action);
#ifndef __EMSCRIPTEN__
static SDL_sem *opengl_load_semaphore; static SDL_sem *opengl_load_semaphore;
#endif
/* note: it drives most of IO implicitly, such as audio callbacks */ /* note: it drives most of IO implicitly, such as audio callbacks */
static void poll_events(void) { static void poll_events(void) {
@ -105,14 +107,20 @@ static void update_viewport(void) {
} }
static void garbage_collect(void) {
file_read_garbage_collect();
}
static void main_loop(void) { static void main_loop(void) {
/* #ifdef __EMSCRIPTEN__
if (!ctx.is_running) { if (!ctx.is_running)
end(ctx);
clean_up(ctx);
emscripten_cancel_main_loop(); emscripten_cancel_main_loop();
} #endif
*/
/* dispatch all filewatch driven events, such as game object and asset pack reload */
filewatch_poll();
/* frame timer */ /* frame timer */
int64_t current_frame_time = SDL_GetPerformanceCounter(); int64_t current_frame_time = SDL_GetPerformanceCounter();
@ -228,6 +236,8 @@ static void main_loop(void) {
ctx.game.initialization_needed = false; ctx.game.initialization_needed = false;
} }
workers_poll();
/* TODO: in some cases machine might want to assume frames will be fed as much as possible */ /* TODO: in some cases machine might want to assume frames will be fed as much as possible */
/* which for now is broken as glBufferData with NULL is used all over right after render */ /* which for now is broken as glBufferData with NULL is used all over right after render */
if (frames != 0) if (frames != 0)
@ -236,6 +246,8 @@ static void main_loop(void) {
/* don't waste clock cycles on useless work */ /* don't waste clock cycles on useless work */
/* TODO: make it adjustable from config */ /* TODO: make it adjustable from config */
SDL_Delay((uint32_t)(ctx.desired_frametime - ctx.frame_accumulator) / 1250000); SDL_Delay((uint32_t)(ctx.desired_frametime - ctx.frame_accumulator) / 1250000);
garbage_collect();
} }
@ -425,11 +437,7 @@ static bool initialize(void) {
toml_datum_t datum_debug = toml_bool_in(game, "debug"); toml_datum_t datum_debug = toml_bool_in(game, "debug");
ctx.game.debug = datum_debug.ok ? datum_debug.u.b : true; ctx.game.debug = datum_debug.ok ? datum_debug.u.b : true;
#ifdef EMSCRIPTEN #ifndef __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_MAJOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
@ -437,7 +445,6 @@ static bool initialize(void) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
else else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_NO_ERROR); 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_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
@ -447,6 +454,13 @@ static bool initialize(void) {
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
#else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
#endif
toml_datum_t datum_title = toml_string_in(about, "title"); toml_datum_t datum_title = toml_string_in(about, "title");
if (!datum_title.ok) if (!datum_title.ok)
datum_title.u.s = SDL_strdup("townengine project"); datum_title.u.s = SDL_strdup("townengine project");
@ -630,9 +644,11 @@ static bool initialize(void) {
profile_end("game object load"); profile_end("game object load");
/* delayed as further as possible so that more work is done before we have to wait */ /* delayed as further as possible so that more work is done before we have to wait */
#ifndef __EMSCRIPTEN__
SDL_SemWait(opengl_load_semaphore); SDL_SemWait(opengl_load_semaphore);
SDL_DestroySemaphore(opengl_load_semaphore); SDL_DestroySemaphore(opengl_load_semaphore);
profile_end("opengl loading"); profile_end("opengl loading");
#endif
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1"); SDL_SetHint(SDL_HINT_WINDOWS_NO_CLOSE_ON_ALT_F4, "1");
@ -754,11 +770,6 @@ static bool try_mounting_root_pack(char *path) {
} }
static void garbage_collect(void) {
file_read_garbage_collect();
}
int enter_loop(int argc, char **argv) { int enter_loop(int argc, char **argv) {
profile_start("startup"); profile_start("startup");
@ -769,6 +780,7 @@ int enter_loop(int argc, char **argv) {
} }
profile_end("SDL initialization"); profile_end("SDL initialization");
#ifndef __EMSCRIPTEN__
profile_start("opengl loading"); profile_start("opengl loading");
opengl_load_semaphore = SDL_CreateSemaphore(0); opengl_load_semaphore = SDL_CreateSemaphore(0);
SDL_Thread *opengl_load_thread = SDL_CreateThread(opengl_load_thread_fn, "opengl loader", opengl_load_semaphore); SDL_Thread *opengl_load_thread = SDL_CreateThread(opengl_load_thread_fn, "opengl loader", opengl_load_semaphore);
@ -779,6 +791,7 @@ int enter_loop(int argc, char **argv) {
SDL_DetachThread(opengl_load_thread); SDL_DetachThread(opengl_load_thread);
opengl_load_thread = NULL; opengl_load_thread = NULL;
#endif
ctx.argc = argc; ctx.argc = argc;
ctx.argv = argv; ctx.argv = argv;
@ -867,12 +880,12 @@ int enter_loop(int argc, char **argv) {
profile_end("startup"); profile_end("startup");
while (ctx.is_running) { #ifdef __EMSCRIPTEN__
/* dispatch all filewatch driven events, such as game object and asset pack reload */ emscripten_set_main_loop(main_loop, 0, true);
filewatch_poll(); #else
while (ctx.is_running)
main_loop(); main_loop();
garbage_collect(); #endif
}
if (ctx.game.debug) if (ctx.game.debug)
profile_list_stats(); profile_list_stats();

View File

@ -1,6 +1,6 @@
#include "twn_loop_c.h" #include "twn_loop_c.h"
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
#define SDL_MAIN_HANDLED #define SDL_MAIN_HANDLED
#endif #endif
#include <SDL2/SDL.h> #include <SDL2/SDL.h>

View File

@ -1,5 +1,7 @@
/* single compilation unit for every stb implementation */ /* single compilation unit for every stb implementation */
#include <SDL2/SDL.h>
#include <stdint.h> #include <stdint.h>
#define STB_DS_IMPLEMENTATION #define STB_DS_IMPLEMENTATION

View File

@ -466,7 +466,7 @@ static TextureKey textures_load(TextureCache *cache, const char *path) {
SDL_UnlockMutex(textures_load_mutex); SDL_UnlockMutex(textures_load_mutex);
/* signal work to do */ /* signal work to do */
SDL_SemPost(workers_job_semaphore); workers_add_job();
cache->is_dirty = true; cache->is_dirty = true;

View File

@ -2,12 +2,19 @@
#include "twn_workers_c.h" #include "twn_workers_c.h"
#include "rendering/twn_draw_c.h" #include "rendering/twn_draw_c.h"
#ifndef __EMSCRIPTEN__
SDL_sem *workers_job_semaphore; SDL_sem *workers_job_semaphore;
static size_t workers_pool_size;
static SDL_mutex *workers_mutex; static SDL_mutex *workers_mutex;
static bool workers_should_exit;
static SDL_sem *workers_exit_semaphore; /* should come to count of `workers_pool_size` */ static SDL_sem *workers_exit_semaphore; /* should come to count of `workers_pool_size` */
static bool workers_should_exit;
#else
/* accomulate to late poll from main thread itself */
static uint32_t workers_job_count;
#endif
static size_t workers_pool_size;
/* logic is such that when job is posted, worker threads attempt to grab it from any possible entry point */ /* logic is such that when job is posted, worker threads attempt to grab it from any possible entry point */
/* if it did something, which is signaled by `true` return, go back to waiting on semaphore, so that it's decremented properly */ /* if it did something, which is signaled by `true` return, go back to waiting on semaphore, so that it's decremented properly */
@ -15,6 +22,7 @@ static int worker_thread(void *udata) {
(void)udata; (void)udata;
while (true) { while (true) {
#ifndef __EMSCRIPTEN__
/* check whether loop should end */ /* check whether loop should end */
SDL_LockMutex(workers_mutex); SDL_LockMutex(workers_mutex);
if (workers_should_exit) { if (workers_should_exit) {
@ -26,6 +34,11 @@ static int worker_thread(void *udata) {
/* wait and occasionally go back to check whether it all should end */ /* wait and occasionally go back to check whether it all should end */
if (SDL_SemWaitTimeout(workers_job_semaphore, 100) == SDL_MUTEX_TIMEDOUT) if (SDL_SemWaitTimeout(workers_job_semaphore, 100) == SDL_MUTEX_TIMEDOUT)
continue; continue;
#else
if (workers_job_count <= 0)
break;
workers_job_count--;
#endif
/* process models, which will trigger texture loads */ /* process models, which will trigger texture loads */
if (models_load_workers_thread()) if (models_load_workers_thread())
@ -35,8 +48,10 @@ static int worker_thread(void *udata) {
continue; continue;
} }
#ifndef __EMSCRIPTEN__
/* let the main thread collect it */ /* let the main thread collect it */
SDL_SemPost(workers_exit_semaphore); SDL_SemPost(workers_exit_semaphore);
#endif
return 0; return 0;
} }
@ -50,6 +65,7 @@ bool workers_init(size_t worker_count) {
if (worker_count > MAX_WORKERS) if (worker_count > MAX_WORKERS)
worker_count = MAX_WORKERS; worker_count = MAX_WORKERS;
#ifndef __EMSCRIPTEN__
/* spawn a bunch of detached threads without references to them */ /* spawn a bunch of detached threads without references to them */
for (size_t i = 0; i < worker_count; ++i) { for (size_t i = 0; i < worker_count; ++i) {
SDL_Thread *thread = SDL_CreateThread(worker_thread, "worker", NULL); SDL_Thread *thread = SDL_CreateThread(worker_thread, "worker", NULL);
@ -61,11 +77,13 @@ bool workers_init(size_t worker_count) {
workers_job_semaphore = SDL_CreateSemaphore(0); workers_job_semaphore = SDL_CreateSemaphore(0);
workers_exit_semaphore = SDL_CreateSemaphore(0); workers_exit_semaphore = SDL_CreateSemaphore(0);
workers_mutex = SDL_CreateMutex(); workers_mutex = SDL_CreateMutex();
#endif
return true; return true;
} }
void workers_deinit(void) { void workers_deinit(void) {
#ifndef __EMSCRIPTEN__
SDL_LockMutex(workers_mutex); SDL_LockMutex(workers_mutex);
workers_should_exit = true; workers_should_exit = true;
SDL_UnlockMutex(workers_mutex); SDL_UnlockMutex(workers_mutex);
@ -77,5 +95,22 @@ void workers_deinit(void) {
SDL_DestroyMutex(workers_mutex); SDL_DestroyMutex(workers_mutex);
SDL_DestroySemaphore(workers_job_semaphore); SDL_DestroySemaphore(workers_job_semaphore);
SDL_DestroySemaphore(workers_exit_semaphore); SDL_DestroySemaphore(workers_exit_semaphore);
#endif
workers_pool_size = 0; workers_pool_size = 0;
} }
void workers_add_job(void) {
#ifndef __EMSCRIPTEN__
SDL_SemPost(workers_job_semaphore);
#else
workers_job_count++;
#endif
}
void workers_poll(void) {
#ifdef __EMSCRIPTEN__
worker_thread(NULL);
#else
#endif
}

View File

@ -7,11 +7,10 @@
#define MAX_WORKERS 9 #define MAX_WORKERS 9
/* workers are waiting on this, increment this value when some work needs to be done */
/* for now every possible job path is hardcoded in twn_workers.c itself */
extern SDL_sem *workers_job_semaphore;
bool workers_init(size_t worker_count); bool workers_init(size_t worker_count);
void workers_deinit(void); void workers_deinit(void);
void workers_add_job(void);
/* for targets lacking threading support main thread could do the work */
void workers_poll(void);
#endif #endif