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_INSTALL_MESSAGE NEVER)
# TODO: test whether webgl 1 is good enough.
# SDL dependencies
# for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default
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)
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 +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/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 +138,7 @@ set_target_properties(${TWN_TARGET} PROPERTIES
# precompile commonly used not-so-small headers
target_precompile_headers(${TWN_TARGET} PRIVATE
$<$<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)
@ -152,12 +150,14 @@ function(give_options_without_warnings target)
-fno-signed-zeros
-fno-trapping-math
-freciprocal-math
# TODO: require packaging for web case
$<$<BOOL:${EMSCRIPTEN}>:-sUSE_SDL=2>)
set(BUILD_FLAGS_RELEASE
-O3
-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
-fomit-frame-pointer
$<$<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:${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
$<$<STREQUAL:${CMAKE_C_COMPILER_ID},Clang>:-Wl,--strip-all>
${BUILD_FLAGS_RELEASE})
@ -194,12 +203,9 @@ function(give_options_without_warnings target)
target_link_options(${target} PUBLIC
${BUILD_FLAGS}
# -Wl,--no-undefined # TODO: use later for implementing no-libc
${LINK_FLAGS}
$<$<CONFIG:Release>:${LINK_FLAGS_RELEASE}>
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>
-Bsymbolic-functions
-Wl,--as-needed
$<$<BOOL:${LINUX}>:-Wl,--hash-style=gnu>)
$<$<CONFIG:Debug>:${BUILD_FLAGS_DEBUG}>)
get_target_property(target_type ${target} TYPE)
if (target_type MATCHES SHARED_LIBRARY)
@ -274,7 +280,7 @@ endfunction()
function(link_deps target)
target_link_libraries(${target} PUBLIC
$<$<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
xms)
target_include_directories(${target} PUBLIC ${SDL2_INCLUDE_DIRS})

View File

@ -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

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.
# stone_wall
wall_texture : /assets/brick.png
solid : 1
. stone_floor
tile_texture : /assets/pebbles.png
tile_texture : /assets/mossy_rock.png
@ player_spawn
unique :
unique : 1
face : south
hold : torch
tile_texture : /assets/pebbles.png
tile_texture : /assets/mossy_rock.png
X door
open_on_signal : sg_torch0
tile_texture : /assets/pebbles.png
tile_texture : /assets/mossy_rock.png
face : horizon
face_texture : /assets/castledoors.png
/ lever
on_interact_emit : sg_torch0
tile_texture : /assets/pebbles.png
tile_texture : /assets/mossy_rock.png
face : observer
face_texture : /assets/lever.png
@meta
-- 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 }
end
local direction = { x = 0, z = 0 }
local move = { x = 0, y = 0 }
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
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
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.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 {
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,
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,
}

View File

@ -54,6 +54,33 @@ function render_dungeon(dungeon)
texture_region = { w = 128, h = 128 },
}
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

View File

@ -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})

View File

@ -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

View File

@ -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

View File

@ -5,7 +5,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
@ -13,4 +13,4 @@ set(SOURCE_FILES
)
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]
resolution = [ 640, 480 ]
background_color = [ 255, 125, 0, 255 ]
#debug = true
# 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;
++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)
endif()
add_subdirectory($ENV{TWNROOT} $ENV{TWNROOT}/build)
add_subdirectory($ENV{TWNROOT} ${CMAKE_BINARY_DIR}/twn)
set(FLAGS
$<$<NOT:$<BOOL:${TWN_FEATURE_DYNLIB_GAME}>>:--no-dynlib-game>
@ -34,7 +34,7 @@ add_compile_definitions(LUA_32BITS=1)
set(SOURCE_FILES
game.c
state.h
minilua.c
)
use_townengine("${SOURCE_FILES}" ${TWN_OUT_DIR})

View File

@ -1,7 +1,6 @@
#include "twn_game_api.h"
/* TODO: actually move it back it its own file, it doesn't give any compilation benefits */
#define LUA_IMPL
#include "minilua.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}"
;;
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

View File

@ -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)

View File

@ -73,6 +73,7 @@
<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,
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>
</blockquote>
</body>

View File

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

View File

@ -39,12 +39,18 @@ enum {
typedef uint32_t VertexBuffer;
typedef uint32_t IndexBuffer;
typedef struct VertexBufferBuilder {
size_t size;
void *base;
} VertexBufferBuilder;
typedef struct IndexBufferBuilder {
size_t size;
void *base;
} IndexBufferBuilder;
typedef struct SpritePrimitive {
Rect rect;
@ -268,19 +274,19 @@ void text_cache_reset_arena(TextCache *cache);
/* vertex buffer */
VertexBuffer create_vertex_buffer(void);
void restart_scratch_vertex_arrays(void);
VertexBuffer get_scratch_vertex_array(void);
void delete_vertex_buffer(VertexBuffer buffer);
void specify_vertex_buffer(VertexBuffer buffer, void const *data, size_t bytes);
VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes);
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 */
/* 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_rect_batch(const Primitive2D primitives[], struct QuadBatch batch);
VertexBuffer get_quad_element_buffer(void);
VertexBuffer get_circle_element_buffer(void);
IndexBuffer get_quad_element_buffer(void);
IndexBuffer get_circle_element_buffer(void);
void render_circle(const CirclePrimitive *circle);

View File

@ -5,7 +5,13 @@
#include "twn_types.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>
#endif
#include <stb_ds.h>
@ -35,14 +41,18 @@ 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));
glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);
#ifndef __EMSCRIPTEN__
glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
glHint(GL_FOG_HINT, GL_FASTEST);
@ -51,6 +61,7 @@ bool render_init(void) {
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(opengl_log, NULL);
}
#endif
return true;
}
@ -88,16 +99,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 +126,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 +153,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();
@ -175,48 +197,77 @@ static void finally_use_2d_pipeline(void) {
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 */
static void finally_use_texture_mode(TextureMode mode) {
if (texture_mode_last_used == mode)
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) {
glCallList(lists + 0);
setup_ghostly_texture_mode();
} else if (mode == TEXTURE_MODE_SEETHROUGH) {
glCallList(lists + 1);
setup_seethrough_texture_mode();
} else {
glCallList(lists + 2);
setup_opaque_texture_mode();
}
texture_mode_last_used = mode;
@ -227,7 +278,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 +294,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;
@ -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) {
SDL_Surface *surface = textures_load_surface(path);
/* 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: 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 +507,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
}
}

View File

@ -4,13 +4,14 @@
#include <stb_ds.h>
#ifdef EMSCRIPTEN
#include <GLES2/gl2.h>
#ifdef __EMSCRIPTEN__
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#else
#include <glad/glad.h>
#endif
void setup_viewport(int x, int y, int width, int 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) {
glBindBuffer(GL_ARRAY_BUFFER, buffer);
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) {
static VertexBuffer buffer = 0;
void specify_index_buffer(IndexBuffer buffer, void const *data, size_t bytes) {
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 */
/* TODO: use builder interface, not direct calls (glMapBuffer isn't portable) */
if (buffer == 0) {
buffer = create_vertex_buffer();
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
buffer = create_index_buffer();
IndexBufferBuilder builder = build_index_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 );
for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) {
((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);
}
finish_vertex_builder(&builder);
finish_index_builder(&builder);
}
SDL_assert_always(buffer);
@ -62,12 +82,12 @@ VertexBuffer get_quad_element_buffer(void) {
}
VertexBuffer get_circle_element_buffer(void) {
static VertexBuffer buffer = 0;
IndexBuffer get_circle_element_buffer(void) {
static IndexBuffer buffer = 0;
if (buffer == 0) {
buffer = create_vertex_buffer();
VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (GLshort) * (CIRCLE_VERTICES_MAX - 2) * 3);
buffer = create_index_buffer();
IndexBufferBuilder builder = build_index_buffer(buffer, sizeof (GLshort) * (CIRCLE_VERTICES_MAX - 2) * 3);
for (size_t i = 1; i < CIRCLE_VERTICES_MAX - 1; ++i) {
/* 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;
}
finish_vertex_builder(&builder);
finish_index_builder(&builder);
}
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(channels > 0 && channels <= 4);
#if !defined(EMSCRIPTEN)
#ifndef __EMSCRIPTEN__
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (GLboolean)generate_mipmaps);
#else
if (generate_mipmaps)
glGenerateMipmap(GL_TEXTURE_2D);
(void)generate_mipmaps;
#endif
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_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;

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);
SDL_UnlockMutex(model_load_mutex);
SDL_SemPost(workers_job_semaphore);
workers_add_job();
} else
modelcopy = item->key;

View File

@ -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);
}

View File

@ -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 = () => {

View File

@ -2,12 +2,12 @@
#include "twn_util.h"
#include "twn_engine_context_c.h"
#ifndef __EMSCRIPTEN__
#define DMON_IMPL
#include <dmon.h>
#include <stb_ds.h>
#include <SDL2/SDL.h>
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

View File

@ -16,7 +16,9 @@
#include <stdbool.h>
#include <limits.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#define TICKS_PER_SECOND_DEFAULT 60
#define PACKAGE_EXTENSION "btw"
@ -24,9 +26,9 @@
static void pack_contents_modified(char const *path, enum FilewatchAction action);
#ifndef __EMSCRIPTEN__
static SDL_sem *opengl_load_semaphore;
#endif
/* note: it drives most of IO implicitly, such as audio callbacks */
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) {
/*
if (!ctx.is_running) {
end(ctx);
clean_up(ctx);
#ifdef __EMSCRIPTEN__
if (!ctx.is_running)
emscripten_cancel_main_loop();
}
*/
#endif
/* dispatch all filewatch driven events, such as game object and asset pack reload */
filewatch_poll();
/* frame timer */
int64_t current_frame_time = SDL_GetPerformanceCounter();
@ -228,6 +236,8 @@ static void main_loop(void) {
ctx.game.initialization_needed = false;
}
workers_poll();
/* 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 */
if (frames != 0)
@ -236,6 +246,8 @@ static void main_loop(void) {
/* don't waste clock cycles on useless work */
/* TODO: make it adjustable from config */
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");
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
#ifndef __EMSCRIPTEN__
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
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);
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);
@ -447,6 +454,13 @@ static bool initialize(void) {
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 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");
if (!datum_title.ok)
datum_title.u.s = SDL_strdup("townengine project");
@ -630,9 +644,11 @@ static bool initialize(void) {
profile_end("game object load");
/* delayed as further as possible so that more work is done before we have to wait */
#ifndef __EMSCRIPTEN__
SDL_SemWait(opengl_load_semaphore);
SDL_DestroySemaphore(opengl_load_semaphore);
profile_end("opengl loading");
#endif
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
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) {
profile_start("startup");
@ -769,6 +780,7 @@ int enter_loop(int argc, char **argv) {
}
profile_end("SDL initialization");
#ifndef __EMSCRIPTEN__
profile_start("opengl loading");
opengl_load_semaphore = SDL_CreateSemaphore(0);
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);
opengl_load_thread = NULL;
#endif
ctx.argc = argc;
ctx.argv = argv;
@ -867,12 +880,12 @@ int enter_loop(int argc, char **argv) {
profile_end("startup");
while (ctx.is_running) {
/* dispatch all filewatch driven events, such as game object and asset pack reload */
filewatch_poll();
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(main_loop, 0, true);
#else
while (ctx.is_running)
main_loop();
garbage_collect();
}
#endif
if (ctx.game.debug)
profile_list_stats();

View File

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

View File

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

View File

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

View File

@ -2,12 +2,19 @@
#include "twn_workers_c.h"
#include "rendering/twn_draw_c.h"
#ifndef __EMSCRIPTEN__
SDL_sem *workers_job_semaphore;
static size_t workers_pool_size;
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 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 */
/* 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;
while (true) {
#ifndef __EMSCRIPTEN__
/* check whether loop should end */
SDL_LockMutex(workers_mutex);
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 */
if (SDL_SemWaitTimeout(workers_job_semaphore, 100) == SDL_MUTEX_TIMEDOUT)
continue;
#else
if (workers_job_count <= 0)
break;
workers_job_count--;
#endif
/* process models, which will trigger texture loads */
if (models_load_workers_thread())
@ -35,8 +48,10 @@ static int worker_thread(void *udata) {
continue;
}
#ifndef __EMSCRIPTEN__
/* let the main thread collect it */
SDL_SemPost(workers_exit_semaphore);
#endif
return 0;
}
@ -50,6 +65,7 @@ bool workers_init(size_t worker_count) {
if (worker_count > MAX_WORKERS)
worker_count = MAX_WORKERS;
#ifndef __EMSCRIPTEN__
/* spawn a bunch of detached threads without references to them */
for (size_t i = 0; i < worker_count; ++i) {
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_exit_semaphore = SDL_CreateSemaphore(0);
workers_mutex = SDL_CreateMutex();
#endif
return true;
}
void workers_deinit(void) {
#ifndef __EMSCRIPTEN__
SDL_LockMutex(workers_mutex);
workers_should_exit = true;
SDL_UnlockMutex(workers_mutex);
@ -77,5 +95,22 @@ void workers_deinit(void) {
SDL_DestroyMutex(workers_mutex);
SDL_DestroySemaphore(workers_job_semaphore);
SDL_DestroySemaphore(workers_exit_semaphore);
#endif
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
/* 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);
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